UE5官方文档(第一人称射击游戏教程)解读 第七章 好了今天来到我们的第七章今天将承上启下延伸输入部分的工作。配置角色移动Coder 03 Configure Character Movement with C in Unreal Engine | Unreal Engine 5.7 Documentation | Epic Developer Community// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include CoreMinimal.h #include GameFramework/Character.h #include EnhancedInputComponent.h #include EnhancedInputSubsystems.h #include InputActionValue.h #include AdventureCharacter.generated.h class UInputMappingContext; class UInputAction; class UInputComponent;如图所示我们在上一节对蓝图有了一些认知并且学会了引入模块使用接着学会了如何在特定代码里引入我们想要的功能。如上图所示那么在引入后我们就要正式开始写输入相关功能。声明InputMappingContext指针请先跟着官方文档来一遍。可以看见我们本小节写了下面两行东西。这是什么意思呢请回忆我们正常c开发的时候不管是在栈上开辟内存储存变量数据还是在堆上进行它似乎都不需要前面带个宏之类的。但是我们这两行代码前面是一个UPROPERTY宏然后里面有参数下面则是创建一个输入映射上下文的指针。我们之前说过UE5类内成员多使用指针是为了优化和管理也与垃圾回收内存回收的引用链有关那UPROPERTY宏是什么它的参数有什么用呢这个就和我们说过的运行时知晓类信息直接相关了。这个概念很难理解我们举一个毫不相关的代码例子。class A { public: int a10; float b20; }; A a1; float ca1.aa1.b;在类A里面我们有两个变量一个int一个float然后我们在栈上开辟内存A的大小然后我们在栈上面开辟了内存float大小存储c。运行的时候我们的程序只知道在栈上开辟内存大小是intfloat然后前半截int大小存储了对应二进制数据然后后半截float大小存储了对应二进制数据。然后栈上开辟内存float大小提取前面那块内存的前半截后后半截进行运算指令结果存入开辟的内存。运行过程中程序根本不知道这个类叫做A不知道这个类的实例叫a1不知道有个float变量叫做c不知道类内成员变量叫做a和b也不知道它们是什么int或者float或者A类型它们只知道按多少多少字节或者位去读取对应地址的输入然后进行指令c是静态语言编译的时候就决定好了一切运行时就按我前面说的那样动态语言我则不在这里赘述这是一个作业。为什么UE5需要在运行时知晓变量信息请试想一下c是静态语言编译后不会有所谓的类型信息而运行则是在编译后。我们如果想要在运行时增加变量比如生成敌人类实例生成角色类实例当然不限于这个功能还有编辑器实时显示修改都需要运行时知晓类信息这里作为一个作业拓展那我们就要以某种手段存储类的信息然后运行时可以读取这个信息去开辟内存存储数据。那么UPROPERTY宏就担任了这个功能我说过静态语言是编译期决定好一切那么被UPROPERTY宏标记的变量会在编译时把类信息传递给某些文件存着有需要的时候实时读取。那说白了就是相当于一个变量出生登记。但是不限于UPROPERY宏进行标记我们还有UCLASS宏USTRUCT宏……其实你看名字也知道是对谁用的。那么说完UPROPERTY宏的作用了UPROPERTY宏参数是什么意思呢下面是我编译后角色类蓝图的配置。首先我们知道我们的角色蓝图类继承了我们的c角色类所以我们在这里改了c角色类代码并编译作为它的子类的角色蓝图类肯定会有变化这下看懂了吧我们声明了一个IMC指针变量然后放在目录category的输入input下面EditAnywhere顾名思义就是可以在编辑器任何地方编辑然后是BlueprintReadOnly的意思是蓝图只能读取我们设定的这个数据不能进行覆写操作就像我们设置了一个int a10;a就不准再修改了。其实我们还可以看见我们之前写的IMC指针变量本质上是一个数据填充位可以填充IMC数据后面IA也是一样。这个参数其实还有很多个和instance相关的也有大家可以去了解一下作为一个不太重要的作业。那好了我们本小节的学习就完美完成了。声明跳跃和移动InputAction指针还是跟着官方文档做一遍。这部分的原理和前面一样不多赘述。为什么输入需要IMC和IA请回顾前面学习声明Move()函数还是跟着官方文档做一遍。那么在我们对于玩家输入做完一系列处理后我们终于可以写输入后的反馈了。不过还记得我们前面手动引发bug吗写完声明一定要加上实现哦哪怕实现是空的都行没有实现会在链接阶段发生问题。在这里我们漏掉了一个东西那就是函数前也要进行宏标记登记变量信息函数前面用UFUNCTION为啥函数也要用呢函数的本质可以理解为指令集你放指令也需要一块内存调用的时候也需要知道指令集开始的地址然后才能一条一条读取处理。说着就想到了函数指针这种东西拓展作业可以去完成一下这个在UE5中特指类的实例中函数是以函数指针还是纯函数成员存在如果是前者有什么好处原生c对于类实例和UE5对于类实例的处理差异非常大所以我在这里只提UE5此外我们可以看到这里函数里面获取了一个参数const FInputActionValue Value是引用类型说明不希望我们会误修改顺带希望快速传递不经过拷贝。那我们看英文顾名思义这是一个输入值那多半就是我们设备交互发出的信号经过处理后的数据比如我按着A想让角色往左走。实现移动函数设置Move()函数还是先跟着官方文档做一遍。void AAdventureCharacter::Move(const FInputActionValue Value) { // 2D Vector of movement values returned from the input action const FVector2D MovementValue Value.GetFVector2D(); // Check if the controller possessing this Actor is valid if (Controller) { } }在前面那个小节我们可以看出来FInputActionValue对数据做了一定的处理可是为什么是2D向量呢欸我们在打游戏的时候方向键其实只能走前后左右范围也就是所谓的只能在平面移动高度变化是通过跳跃等行为完成你看我们这里写的是Move函数对吧。所以使用的是2D向量。我们翻回前面IMC对应的部分看一下。这就对应上了。我们输入的原始数据是Axis2D然后经过转换成为FVector2D.使用Move()添加2D移动输入还是先根据官方文档做一遍。void AAdventureCharacter::Move(const FInputActionValue Value) { // 2D Vector of movement values returned from the input action const FVector2D MovementValue Value.GetFVector2D(); // Check if the controller posessing this Actor is valid if (Controller) { // Add left and right movement const FVector Right GetActorRightVector(); AddMovementInput(Right, MovementValue.X); // Add forward and back movement const FVector Forward GetActorForwardVector(); AddMovementInput(Forward, MovementValue.Y); } }可以看见我们这次是在Controller的if判断里面加东西。之前我们说过Pawn和Character类比起父类Actor多的就是获取Controller输入可以有很多种嘛AI操控或者玩家操控而Controller是封装成指针的。而我们知道如果Controller是空指针但使用了的话就会崩溃报错。拓展作业在哪些情况下Controller是空指针接着看下来Right和Forward可以理解为是左右方向和前后方向的值引用然后把增量加上传递回去。可以这样理解假设我的初始平面坐标是11我向前走两步是02向右走三步是30然后我们总得加出来最终坐标是43最后我们需要把坐标数据重新写回角色信息。好了今天的学习到此为止。