本文为B站系列教学视频 《UE5_C++多人TPS完整教程》 ------ 《P33 动画蓝图(Animation Blueprint)》 的学习笔记,该系列教学视频为 Udemy 课程 《Unreal Engine 5 C++ Multiplayer Shooter》 的中文字幕翻译版,UP主(也是译者)为 游戏引擎能吃么。
文章目录
- [P33 动画蓝图(Animation Blueprint)](#P33 动画蓝图(Animation Blueprint))
- [33.1 创建动画实例 C++ 类及蓝图类](#33.1 创建动画实例 C++ 类及蓝图类)
- [33.2 在动画实例蓝图中创建状态机](#33.2 在动画实例蓝图中创建状态机)
- [33.3 创建一维混合空间](#33.3 创建一维混合空间)
- [33.4 Summary](#33.4 Summary)
P33 动画蓝图(Animation Blueprint)
本节课将在上节课的基础上为角色移动的创建动画,因此我们需要创建动画实例类(Ainminstance),它是一个基于动画蓝图的 C++ 类。
33.1 创建动画实例 C++ 类及蓝图类
-
在虚幻引擎内容浏览器中的 "
/Blaster/Character
" 目录下新建一个 "Animinstance
" C++ 类,命名为 "BlasterAnimInstance
"。
-
在 Visual Studio 中打开头文件 "
BlasterAnimInstance.h
,声明重写原生(Native)动画类初始化函数和更新函数,以及使用动画实例的角色类,角色类实例的速度、是否在空中、是否在加速。cpp// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "Animation/AnimInstance.h" #include "BlasterAnimInstance.generated.h" /** * */ UCLASS() class BLASTER_API UBlasterAnimInstance : public UAnimInstance { GENERATED_BODY() /* P33 动画蓝图(Animation Blueprint)*/ public: virtual void NativeInitializeAnimation() override; // 原生(Native)动画类初始化函数 NativeInitializeAnimation() 重写 // https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/Engine/Animation/UAnimInstance/NativeInitializeAnimation virtual void NativeUpdateAnimation(float DeltaTime) override; // 原生(Native)动画类更新函数 NativeUpdateAnimation() 重写 // https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/Engine/Animation/UAnimInstance/NativeUpdateAnimation private: // UPROPERTY:https://dev.epicgames.com/documentation/zh-cn/unreal-engine/unreal-engine-uproperties?application_version=5.4 UPROPERTY(BlueprintReadOnly, Category = Character, meta = (AllowPrivateAccess = "true")) // 属性说明符:仅在蓝图中可读,类别为 "Character"; // 元数据说明符:由于变量为私有变量,需要设置 AllowPrivateAccess 为 true 才能在蓝图中可读 class ABlasterCharacter* BlasterCharacter; // 使用动画实例的角色类 UPROPERTY(BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true")) // 属性说明符:仅在蓝图中可读,类别为 "Movemonet"; float Speed; // 运动速度 UPROPERTY(BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true")) // 属性说明符:仅在蓝图中可读,类别为 "Movemonet"; bool bIsInAir; // 是否在空中 UPROPERTY(BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true")) // 属性说明符:仅在蓝图中可读,类别为 "Movemonet"; bool bIsAccelerating; // 是否在加速 /* P33 动画蓝图(Animation Blueprint)*/ };
从最基本的层次上讲,UnrealScript中有两种类型的类:native和非-native。Native类是一个具有native代码的简单的类,或者是使用引擎的native语言书写的代码,这里是C++。这不是说native类不能包含使用UnrealScript书写的代码,只是说native类中结合了C++代码。在游戏中出现的很多底层的类都是native类,因为它们有很多受益于native代码执行速度的复杂函数。非-native类是全部使用UnrealScript书写的。因为所有的类都是从native类Object继承而来,所以通过继承关系,这些非-native类有一些和它们相关的native代码,但是它们本身没有明显的native代码。您将要书写的所有类都是非-native类,因为创建native类需要重新编译引擎的源代码。
-
打开 "
BlasterAnimInstance.cpp
",完成 "NativeInitializeAnimation()
" 函数和 "NativeUpdateAnimation()
" 函数的定义,然后进行编译。cpp// Fill out your copyright notice in the Description page of Project Settings. /* P33 动画蓝图(Animation Blueprint)*/ #include "BlasterAnimInstance.h" // 原来自动生成的代码是 #include "Character/BlasterAnimInstance.h",这里需要把 "Character/" 去掉,否则找不到文件 "BlasterAnimInstance.h" #include "BlasterCharacter.h" #include "GameFramework/CharacterMovementComponent.h" void UBlasterAnimInstance::NativeInitializeAnimation() // 原生(Native)类初始化函数 NativeInitializeAnimation() 重写 { Super::NativeInitializeAnimation(); // 调用父类 AnimInstance 的 NativeInitializeAnimation() 函数 // https://blog.csdn.net/ttm2d/article/details/106550682 BlasterCharacter = Cast<ABlasterCharacter>(TryGetPawnOwner()); // 获取动画蓝图实例所属,并向下强制转换(Cast)为 ABlasterCharacter 类 } void UBlasterAnimInstance::NativeUpdateAnimation(float DeltaTime) // 原生(Native)类更新函数 NativeUpdateAnimation() 重写,用于在每一帧调用以更新动画 { Super::NativeUpdateAnimation(DeltaTime); // 调用父类 AnimInstance 的 NativeUpdateAnimation() 函数 if (BlasterCharacter == nullptr) { // 检查 BlasterCharacter 是否声明 BlasterCharacter = Cast<ABlasterCharacter>(TryGetPawnOwner()); // 获取动画蓝图实例所属,并向下强制转换(Cast)为 ABlasterCharacter 类 } if (BlasterCharacter == nullptr) return; FVector Velocity = BlasterCharacter->GetVelocity(); // 获取角色速度向量 Velocity.Z = 0.f; // 不关心 Z 轴速度,设置为 0 Speed = Velocity.Size(); // 获取角色速度向量的模(大小) bIsInAir = BlasterCharacter->GetCharacterMovement()->IsFalling(); // 调用 GetCharacterMovement()->IsFalling() 函数判断角色是否掉落从而判断角色是否在空中, // 需要添加头文件 "GameFramework/CharacterMovementComponent.h" bIsAccelerating = BlasterCharacter->GetCharacterMovement()->GetCurrentAcceleration().Size() > 0 ? true : false; // 调用 GetCharacterMovement()->GetCurrentAcceleration() 获取角色加速度 // 判断加速度是否的维度是否大于 0 ,大于 0 说明某个方向上有加速度 } /* P33 动画蓝图(Animation Blueprint)*/
-
在虚幻引擎内容浏览器的 "
/Blueprints/Character/
" 目录下新建文件夹 "Animation
",创建动画蓝图(Animation Blueprint),在对话框中选择骨骼为 "SK_EpicCharacter_Skeleton
",动画蓝图重命名为 "BlasterAnimBP
"。
33.2 在动画实例蓝图中创建状态机
-
双击 "
BlasterAnimBP
",在动画蓝图编辑器中打开 "BlasterAnimBP
" 的类设置(Class Settings)面板,设置父类(Parent Class)为 "BlasterAnimInstance
"。
-
在 "
AnimGraph
" 面板中添加新的动画状态机(State Machine)节点,重命名为 "Unequipped
" ,并将该节点的输出引脚 "Animation Pose
" 连接到 "输出姿势"(Output Pose)节点的 "Result
"引脚上。
-
双击 "
Unequipped
" 节点,进入状态机编辑界面,按顺序依次添加状态节点 "IdleWalkRun
"、"JumpStart
"、"Falling
" 和 "JumpStop
"。
-
双击 "
IdleWalkRun to JumpStart
" 转换按钮,添加 "Get is in air
" 节点,并将该节点的输出引脚和自动生成的 "Result
" 节点的 "Can Enter Transition
" 引脚连接,这段蓝图表示如果角色在空中则转换为起跳状态。
-
对于 "
JumpStart to Falling
" 状态转换,在右侧 "细节"(Details) 面板启用 "基于状态中的序列播放器的自动规则"(Automatic Rule Based on Sequence Player in State)选项,这样在状态 "JumpStart
" 动画播放完毕状态结束后会自动过渡到状态 "Falling
"。
基于状态中的序列播放器的自动规则(Automatic Rule Based on Sequence Player in State): 启用后,该转换节点将尝试根据最相关资产播放器节点的剩余时间和 AutomaticRuleTriggerTime 属性的值自动开始转换,忽略内部时间。此属性可用于在第一次动画播放期间,通过更快地开始混合,更动态地在状态转换之间混合。
------ 虚幻引擎官方文档 《转换规则》
-
对于 "
Falling to JumpStop
" 的状态转换,绘制类似于 "IdleWalkRun to JumpStart
" 转换规则的蓝图,只不过需要添加一个 "NOT Boolean
" 节点,表示角色不在空中跳跃结束。
-
返回 "
Unequipped
" 状态机编辑界面,双击 "JumpStart
" 节点,进入 "JumpStart
" 状态编辑器,在右侧 "资产浏览器"(Asset Browser)中将动画 "EpicCharacter_JumpUp_Start
" 拖拽至编辑器中生成节点,然后与自动生成 "Output Animation Pose
" 节点进行连接;同理,分别进入 "Falling
" 和 "JumpStop
" 状态编辑器,将动画 "EpicCharacter_JumpUp_Loop
" 和 "EpicCharacter_JumpUp_End
" 拖拽至编辑器中进行连接。
-
返回 "
Unequipped
" 状态机编辑界面,双击 "JumpStop to ldleWalkRun
" 转换按钮,添加 "Time Remaining (ratio)(EpicCharacter_JumpUp_End)
" 和 "Less equal
"(<=)节点并进行连接,这段蓝图表示当 "JumpStop
" 的动画播放到还剩下 10% 的时间时,就由状态 "JumpStop
" 转至状态 "IdleWalkRun
"。
33.3 创建一维混合空间
-
对于角色水平状态移动的状态机 "
IdleWalkRun
" 节点,我们需要创建混合空间(Blend Space),从而实现人物由怠速至行走至跑步的自然过渡。在内容浏览器的 "/Blueprints/Character/Animation
" 目录下新建 "一维混合空间"(Blend Space 1D),命名为 "UnequippedIdleWalkRun
"。
混合空间是一种资产,允许混合多个动画或姿势,方法是将其绘制到一维或二维图表中。然后,该图表可以在动画蓝图中进行引用,其中混合通过Gameplay输入或其他变量进行控制。通过使用混合空间,几乎所有类型的混合布局都可以用于你的动画。
你可以在其中沿图表上的各个点指定动画,称为 示例(samples) 。总体动画通过基于每个轴的输入值在图表上的点之间混合来计算。例如,可以创建一个在方向性移动和空闲动画之间混合的移动系统。
------ 虚幻引擎官方文档 《混合空间》
-
双击 "
UnequippedIdleWalkRun
",进入混合空间编辑器。在右侧 "资产浏览器"(Asset Browser)中将动画 "EpicCharacter_Idle
" 、"EpicCharacter_Walk
" 和 "EpicCharacter_Run
" 拖拽至编辑器下方 "动画序列" 面板中。
-
在左侧 "资产细节"(Asset Details)面板设置 "水平坐标"(Horizontal Axis) 的"名称"(Name)为 "Speed","最大轴值"(Maximum Axis Value)为 350,并在 "动画序列" 面板中对 "
EpicCharacter_Walk
" 和 "EpicCharacter_Run
" 进行拖拽、调整。
-
返回 "
Unequipped
" 状态机编辑界面,双击 "IdleWalkRun
" 状态机编辑器,绘制以下蓝图,这段蓝图表示通过获取角色移动速度大小传递至混合空间中,以决定播放角色的哪个动画。绘制完成后,进行编译并保存。
-
打开 "
BP_BlasterCharacter
" 蓝图编辑器,在左侧 "组件"(Components)中选择 "网格体"(CharacterMesh0)(骨骼网格体组件)组件,然后在右侧 "细节" 面板中的 "动画"(Animations)选项卡下设置 "动画模式"(Animation Mode)为 "使用动画蓝图"(Use Animation Blueprint),"动画类"(Anim Class)选择我们创建的动画实例蓝图类 "BlasterAnimBP
"。
-
回到关卡主界面,播放游戏进行测试。首先测试角色移动,可以看到角色可以怠速,并且按下移动键可以由怠速自然过渡到跑起来。
-
但是角色在进行左右方向的移动时,角色移动的朝向有些奇怪,因为人物移动朝向始终朝着镜头进行横移。
下面进行修改。在 Visual Studio 打开 "
BlasterCharacter.cpp
",添加代码设置人物不跟随控制器(镜头)转向,并且角色在移动时向加速度方向旋转,这一步也可以在虚幻引擎中 "BP_BlasterCharacter
" 蓝图编辑器的细节面板对相应选项进行勾选实现。cpp// Fill out your copyright notice in the Description page of Project Settings. #include "BlasterCharacter.h" #include "GameFramework/SpringArmComponent.h" #include "Camera/CameraComponent.h" /* P33 动画蓝图(Animation Blueprint)*/ #include "GameFramework/CharacterMovementComponent.h" /* P33 动画蓝图(Animation Blueprint)*/ // Sets default values ABlasterCharacter::ABlasterCharacter() { // Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; // 创建弹簧臂对象 CameraBoom 并设置 CameraBoom 的默认属性 CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom")); // 基于弹簧臂组件类创建对象 CameraBoom->SetupAttachment(GetMesh()); // 设置弹簧臂附加到角色的骨骼网格体组件,如果附加到胶囊体上,角色在做蹲下的动作时,由于胶囊体的大小和路线会发生改变,弹簧臂的高度也会发生改变(弹簧臂将会移动) CameraBoom->TargetArmLength = 600.f; // 设置弹簧臂长度 CameraBoom->bUsePawnControlRotation = true; // 设置弹簧臂跟随角色控制器旋转 // 创建摄像机对象 FollowCamera 并设置 FollowCamera 的默认属性 FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera")); // 基于摄像机组件类创建对象 FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // 将摄像机附加到弹簧臂 CameraBoom 上,并指定插槽名为虚幻引擎摄像机组件成员变量 SocketName FollowCamera->bUsePawnControlRotation = false; // 设置摄像机不跟随角色控制器旋转 // 调整弹簧臂和摄像机的相对位置(也可以在虚幻引擎的蓝图编辑器中进行设置) CameraBoom->SetRelativeLocation(FVector(0, 0, 88)); // 设置弹簧臂和摄像机在蓝图类 "BP_BlasterCharacter" 的相对位置为 (0, 0, 88),以避免它们与地面相撞 /* P33 动画蓝图(Animation Blueprint)*/ bUseControllerRotationYaw = false; // 设置人物不跟随控制器(镜头)转向,也可以在 BP_BlasterCharacter 蓝图编辑器中实现 GetCharacterMovement()->bOrientRotationToMovement = true; // 获取角色移动组件,角色移动时向加速度方向旋转角色,也可以在 BP_BlasterCharacter 蓝图编辑器中实现 /* P33 动画蓝图(Animation Blueprint)*/ }
User Controller Rotaion Yaw与User Controller Desired Rotation都将人物与镜头视角绑定,即让人物始终跟随镜头转向,区别在于第二个会让转向更加平滑的过度,也可以设置转向的速度,第一个就直接跟鼠标或手柄移动一致转向,如果想让转向更平滑可以设置第二个bool值为true即可,Orient Rotaion to Movement则在没有wasd这种move输入的时候不会让人物转向,在人物跑动的时候会有转向效果但是没有往后跑的效果,按s键人物正面就会朝着玩家,前两个bool值则相反。
------ 《虚幻4人物转向问题{User Controller Rotaion Yaw,User Controller Desired Rotation与Orient Rotation to Movement}》
在TPS(第三人称)游戏中,我设定bAim为true 举枪射击,bUseControllerRotationYaw为真,则我可以按s后退 这时我们需要将bOrientRotationToMovement设置为false,即不旋转玩家朝向后转身,我瞄准时肯定要保持朝向射击方向......
需牢记 若使用 二者 则bUseControllerRotationYaw 与 bOrientRotationToMovement 布尔值要相反。
-
接着,测试角色跳跃,可以发现角色在起跳和落下的动画都没问题,但是在落地时会播放两次 "
EpicCharacter_JumpUp_End
" 动画,并且第二次播放得很快,看起来非常奇怪。
下面进行修改。我们只需要在 "
JumpStop
" 状态机编辑器右侧 "细节" 面板中取消勾选 "设置"(Settings)选项卡下的 "重复动画"(Loop Animation) 复选框即可,最后进行编译和保存,再次测试会发现角色跳跃动画正常。
33.4 Summary
本节课为了让角色移动时能播放相应的动画,我们首先创建了动画实例 C++ 类 "BlasterAnimInstance
",基于这个 C++ 类进一步创建动画实例蓝图类 "BlasterAnimBP
";接着,为在 "BlasterAnimBP
" 蓝图编辑器中创建状态机,以实现角色水平方向运动和竖直方向跳跃之间的状态转换;然后,为了实现角色在水平方向运动时从怠速、走路以及跑步的自然过渡,我们创建了以角色移动速度大小为参量的一维混合空间;最后我们将动画实例蓝图类 "BlasterAnimBP
"运用到角色蓝图类 "BP_BlasterCharacter
" 中,并对角色水平方向运动和竖直方向跳跃进行了测试,修复了角色在水平方向运动出现的朝向问题以及在竖直方向跳跃时落地动画重复播放的问题。