Ue c++、蓝图开发指南 Part 02:动画 角色四方移动
目录
[TOC]
一、创建项目,添加内容
- 创建蓝图项目
ShootThemUp
- 增加版权
- 在
编辑 => 项目设置 => 描述 => 法律 => 版权声明
中,声明版权 - 此时填写为
Shoot Them Up Game, All Rights Reserved
- 在
- 新建文件夹
Levels
,用于存放地图- 将默认地图保存为
TestLevel
- 在
项目设置 => 地图和模式
中,将TestLevel
设置为默认地图
- 将默认地图保存为
- 添加项目内容
- 点击
Epic Games => 虚幻引擎 => 示例 => 射击游戏
,创建一个工程 - 选择
内容 => Animations => TTP_Animations
中的内容,迁移到我们的项目的Content
文件夹 - 新建文件夹
ExternalContent
,将迁移过来的内容放置在其中,然后将其标记为红色
- 点击
- 创建C++类
STUGameModeBase
,继承于游戏模式基础
,类型为公共
- 在
世界场景设置 => 游戏模式重载
中,将其设置为STUGameModeBase
二、编码标准,clang-format,gitignore
-
显示行号:在
VS => 工具 => 选项 => 文本编辑器 => 所有语言
中,勾选行号
-
显示空格:在
VS => 编辑 => 高级 => 显示空白
,也可以使用快捷键CTRL + R, CTRL + W
-
格式化代码:
.clang-format
文件- 这是一个特殊的文件,在其中设置了格式化代码的规则
- 将此文件添加到项目中,进行配置,然后我们所有的代码文件都将以相同的样式设置格式
- 通常,它在项目开始时配置一次,然后自动进行所有格式化
- 在
工程目录
中,新建.clang-format
文件,然后重新生成VS工程 - 在
VS => 工具 => 选项 => 文本编辑器 => C/C++ => 代码样式 => 格式设置
中,勾选启用ClangFormat支持
- 使用快捷键
CTRL + K, CTRL + D
,即可快速格式化代码
-
.clang-format
文件的编写Language
:声明当前文件针对的语言类型BasedOnStyle
:基础格式,如:Microsoft
、Google
IndentWidth
:缩进包含的空格数UseTab
:是否使用tab缩进,如:Never
、Always
TabWidth
:tab的宽度BreakBeforeBraces
:大括号前是否需要换行,如:Attach
、Allman
Language: Cpp BasedOnStyle: Microsoft IndentWidth: '4' UseTab: Never TabWidth: '4' BreakBeforeBraces: Allman ColumnLimit: '140' AccessModifierOffset: '-4' SortIncludes: false AllowShortBlocksOnASingleLine: false AlignAfterOpenBracket: DontAlign AllowShortFunctionsOnASingleLine: Inline PointerAlignment: Left AllowShortIfStatementsOnASingleLine: true SpacesBeforeTrailingComments: 2 AllowShortCaseLabelsOnASingleLine: true IndentCaseLabels: true AlwaysBreakTemplateDeclarations: Yes
-
.gitignore
文件编写.vs *.sln DerivedDataCache/ Intermediate/ Saved/ Binaries/ Build/
-
资源文件的命名格式:https://github.com/Allar/ue5-style-guide
三、创建 ACharacter 和 APlayerController 类
-
新建C++类
STUBaseCharacter
,继承于角色
- 目录:
ShootThemUp/Source/ShootThemUp/Public/Player
- 目录:
-
新建C++类
STUPlayerController
,继承于玩家控制器
- 目录:
ShootThemUp/Source/ShootThemUp/Public/Player
- 目录:
-
在
ShootThemUp.Build.cs
中,添加目录using UnrealBuildTool; public class ShootThemUp : ModuleRules{ public ShootThemUp(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" }); PrivateDependencyModuleNames.AddRange(new string[] { }); PublicIncludePaths.AddRange(new string[] { "ShootThemUp/Public/Player" }); } }
-
修改
STUGameModeBase
,设置默认Pawn类和玩家控制器类UCLASS() class SHOOTTHEMUP_API ASTUGameModeBase : public AGameModeBase{ GENERATED_BODY() public: ASTUGameModeBase(); };
#include "STUGameModeBase.h" #include "Player/STUBaseCharacter.h" #include "Player/STUPlayerController.h" ASTUGameModeBase::ASTUGameModeBase() { DefaultPawnClass = ASTUBaseCharacter::StaticClass(); PlayerControllerClass = ASTUPlayerController::StaticClass(); }
-
创建文件夹
Content/Player
,并根据C++类创建蓝图类 -
将游戏模式重载中的对应类,设置为蓝图类
-
修改
BP_STUBaseCharacter
,设置角色的默认Mesh
-
修改
STUBaseCharacter
,添加相机组件#pragma once #include "CoreMinimal.h" #include "GameFramework/Character.h" #include "STUBaseCharacter.generated.h" class UCameraComponent; UCLASS() class SHOOTTHEMUP_API ASTUBaseCharacter : public ACharacter { ... protected: // 相机 UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Components") UCameraComponent* CameraComponent; ... };
#include "Player/STUBaseCharacter.h" #include "Camera/CameraComponent.h" ASTUBaseCharacter::ASTUBaseCharacter() { PrimaryActorTick.bCanEverTick = true; // 创建相机组件, 并设置其父组件 CameraComponent = CreateDefaultSubobject<UCameraComponent>("CameraComponent"); CameraComponent->SetupAttachment(GetRootComponent()); }
-
修改
BP_STUBaseCharacter
,设置相机组件的位置为(-250,0,70)
四、基本角色移动
-
修改
项目设置 => 输入
,添加轴映射 -
修改
STUBaseCharacter
,添加回调函数UCLASS() class SHOOTTHEMUP_API ASTUBaseCharacter : public ACharacter { ... private: // WS控制角色前后移动 void MoveForward(float Amount); // AD控制角色左右移动 void MoveRight(float Amount); };
DEFINE_LOG_CATEGORY_STATIC(LogSTUBaseCharacter, All, All); void ASTUBaseCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); // WASD控制角色移动 PlayerInputComponent->BindAxis("MoveForward", this, &ASTUBaseCharacter::MoveForward); PlayerInputComponent->BindAxis("MoveRight", this, &ASTUBaseCharacter::MoveRight); } // WS控制角色前后移动 void ASTUBaseCharacter::MoveForward(float Amount) { AddMovementInput(GetActorForwardVector(), Amount); } // AD控制角色左右移动 void ASTUBaseCharacter::MoveRight(float Amount) { AddMovementInput(GetActorRightVector(), Amount); }
五、角色相机控制
-
修改
项目设置 => 输入
,添加轴映射 -
修改
STUBaseCharacter
,添加回调函数UCLASS() class SHOOTTHEMUP_API ASTUBaseCharacter : public ACharacter { ... private: // 鼠标上下移动, 控制相机上下移动 void LookUp(float Amount); // 鼠标左右移动, 控制相机左右移动 void TurnAround(float Amount); };
```c++ void ASTUBaseCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent);
// WASD控制角色移动 PlayerInputComponent->BindAxis("MoveForward", this, &ASTUBaseCharacter::MoveForward); PlayerInputComponent->BindAxis("MoveRight", this, &ASTUBaseCharacter::MoveRight); // 鼠标控制相机移动 PlayerInputComponent->BindAxis("LookUp", this, &ASTUBaseCharacter::LookUp); PlayerInputComponent->BindAxis("TurnAround", this, &ASTUBaseCharacter::TurnAround); }
// 鼠标上下移动, 控制相机上下移动 void ASTUBaseCharacter::LookUp(float Amount) { AddControllerPitchInput(Amount); } // 鼠标左右移动, 控制相机左右移动 void ASTUBaseCharacter::TurnAround(float Amount) { AddControllerYawInput(Amount); }
-
修改
BP_STUBaseCharacter
,允许控制器控制相机的上下移动- 勾选
Camera组件 => 细节 => 使用Pawn控制旋转
- 勾选
-
修改
STUBaseCharacter
,添加弹簧臂组件,用弹簧臂控制相机的运动class UCameraComponent; class USpringArmComponent; UCLASS() class SHOOTTHEMUP_API ASTUBaseCharacter : public ACharacter { ... protected: // 相机的弹簧臂 UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Components") USpringArmComponent* SpringArmComponent; // 相机 UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Components") UCameraComponent* CameraComponent; ... }
```c++ #include “Player/STUBaseCharacter.h” #include “Camera/CameraComponent.h” #include “Components/InputComponent.h” #include “GameFramework/SpringArmComponent.h”
DEFINE_LOG_CATEGORY_STATIC(LogSTUBaseCharacter, All, All);
ASTUBaseCharacter::ASTUBaseCharacter() { PrimaryActorTick.bCanEverTick = true;
// 创建弹簧臂组件, 并设置其父组件, 允许pawn控制旋转 SpringArmComponent = CreateDefaultSubobject<USpringArmComponent>("SpringArmComponent"); SpringArmComponent->SetupAttachment(GetRootComponent()); SpringArmComponent->bUsePawnControlRotation = true; // 创建相机组件, 并设置其父组件 CameraComponent = CreateDefaultSubobject<UCameraComponent>("CameraComponent"); CameraComponent->SetupAttachment(SpringArmComponent); }
-
右击
BP_STUBaseCharacter
,资产操作 => 重新加载
- 将相机组件的位置还原为
(0,0,0)
,取消勾选Camera组件 => 细节 => 使用Pawn控制旋转
- 使用弹簧臂控制相机的位置:
- 将相机组件的位置还原为
-
添加一个默认动画:
- 修改
BP_STUBaseCharacter => 网格体 => 细节 => 动画
- 修改
-
由于
LookUp
函数与AddControllerPitchInput
均只有一个float
参数,因此我们可以不创建LookUp
,而是直接绑定AddControllerPitchInput
。TurnAround
函数同理void ASTUBaseCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); // WASD控制角色移动 PlayerInputComponent->BindAxis("MoveForward", this, &ASTUBaseCharacter::MoveForward); PlayerInputComponent->BindAxis("MoveRight", this, &ASTUBaseCharacter::MoveRight); // 鼠标控制相机移动 PlayerInputComponent->BindAxis("LookUp", this, &ASTUBaseCharacter::AddControllerPitchInput); PlayerInputComponent->BindAxis("TurnAround", this, &ASTUBaseCharacter::AddControllerYawInput); }
六、动画蓝图
-
将
Floor
删除,并添加盒体画刷
- 位置:
(0,0,0)
- 缩放:
(50,50,1)
- 位置:
-
修改
PlayerStart
的位置为:(0,0,200)
-
新建
Player/Animations
文件夹,然后创建ABP_BaseCharacter
动画蓝图- 事件图表:处理各种动画事件,还具有更新功能,实际上时蓝图的
Tick
函数 - AnimGraph:处理所有动画,最终的动画被发送到输出姿势节点,该节点将在当前帧中呈现
- 事件图表:处理各种动画事件,还具有更新功能,实际上时蓝图的
-
修改
BP_STUBaseCharacter
- 将
Mesh => 动画
修改为:使用动画蓝图ABP_BaseCharacter
- 将
-
创建混合空间1D
BS_Locomotion
,- 修改水平坐标轴设置:
- 名称:
速度
- 轴值范围:
0~600
- 名称:
- 将
Idle
动画与Run_Fwd
动画根据人物当前运动速度混合- 将
Idle
动画拖至0
的位置 - 将
Run_Fwd
动画拖至600
的位置
- 将
- 修改水平坐标轴设置:
-
修改动画蓝图
ABP_BaseCharacter
七、跳转动画,状态机制
-
添加操作映射
-
修改
STUBaseCharacter
Jump()
是Character
类的一个函数,调用后,下一个Tick会给当前角色添加一个Z轴的脉冲
void ASTUBaseCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); // WASD控制角色移动 PlayerInputComponent->BindAxis("MoveForward", this, &ASTUBaseCharacter::MoveForward); PlayerInputComponent->BindAxis("MoveRight", this, &ASTUBaseCharacter::MoveRight); // 鼠标控制相机移动 PlayerInputComponent->BindAxis("LookUp", this, &ASTUBaseCharacter::AddControllerPitchInput); PlayerInputComponent->BindAxis("TurnAround", this, &ASTUBaseCharacter::AddControllerYawInput); // 空格键控制角色跳跃 PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ASTUBaseCharacter::Jump); }
-
修改动画蓝图
ABP_BaseCharacter
-
事件图表:判断角色是否在空中
-
AnimGraph:设置不同动画之间切换的状态机
Walk => JumpStart
:在空中 == true
JumpStart => JumpLoop
:动画剩余时间 < 0.1
JumpLoop => JumpEnd
:在空中 == false
JumpEnd => Walk
:动画剩余时间 < 0.1
-
将
JumpStart => 细节 => 循环动画
取消勾选,保证JumpStart
动画只播放一次 -
将
JumpLoop => 细节 => 循环动画
勾选,保证JumpLoop
动画可以循环播放 -
将
JumpEnd => 细节 => 循环动画
取消勾选,保证JumpEnd
动画只播放一次
-
-
修改
BP_STUBaseCharacter => 角色移动
- 设置
跳跃Z速度
为600
- 设置
八、实战作业:跑步动画
任务:按
Shift + w
跑步前进,按w
平稳前进
-
添加操作映射:
-
修改
STUBaseCharacter
,添加左Shift
的回调函数UCLASS() class SHOOTTHEMUP_API ASTUBaseCharacter : public ACharacter { ... public: // 判断角色是否处于奔跑状态 UFUNCTION(BlueprintCallable, Category = "Movement") bool IsRunning() const; private: // WS控制角色前后移动 bool IsMovingForward = false; void MoveForward(float Amount); // AD控制角色左右移动 void MoveRight(float Amount); // 左Shift控制角色开始跑动 bool WantsToRun = false; // 按下Shift只能表示想要跑步, 只有当还按下W时, 才能开始跑步 void OnStartRunning(); void OnStopRunning(); };
void ASTUBaseCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); // WASD控制角色移动 PlayerInputComponent->BindAxis("MoveForward", this, &ASTUBaseCharacter::MoveForward); PlayerInputComponent->BindAxis("MoveRight", this, &ASTUBaseCharacter::MoveRight); // 鼠标控制相机移动 PlayerInputComponent->BindAxis("LookUp", this, &ASTUBaseCharacter::AddControllerPitchInput); PlayerInputComponent->BindAxis("TurnAround", this, &ASTUBaseCharacter::AddControllerYawInput); // 空格键控制角色跳跃 PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ASTUBaseCharacter::Jump); // 左Shift控制角色开始跑动 PlayerInputComponent->BindAction("Run", IE_Pressed, this, &ASTUBaseCharacter::OnStartRunning); PlayerInputComponent->BindAction("Run", IE_Released, this, &ASTUBaseCharacter::OnStopRunning); } // 判断角色是否处于奔跑状态 bool ASTUBaseCharacter::IsRunning() const { return WantsToRun && IsMovingForward && !GetVelocity().IsZero(); } // WS控制角色前后移动 void ASTUBaseCharacter::MoveForward(float Amount) { IsMovingForward = Amount > 0.0f; AddMovementInput(GetActorForwardVector(), Amount); } // AD控制角色左右移动 void ASTUBaseCharacter::MoveRight(float Amount) { AddMovementInput(GetActorRightVector(), Amount); } // 左Shift控制角色开始跑动 void ASTUBaseCharacter::OnStartRunning() { WantsToRun = true; } void ASTUBaseCharacter::OnStopRunning() { WantsToRun = false; }
-
修改动画蓝图
ABP_BaseCharacter
:-
事件图表:
-
状态机:
Walk => Run
:在奔跑 == true
Run => Walk
:在奔跑 == false
Run => JumpStart
:与Walk => JumpStart
相同,因此将两者的判断条件共享- 将
Walk => JumpStart
的规则,在细节 => 过度规则共享
中,点击提升为共享
,并命名为IsFalling
- 将
Run => JumpStart
的规则,在细节 => 过度规则共享
中,点击使用共享
,并选择IsFalling
- 将
-
-
修改角色动画
- 将原来的
BS_Locomotion
重命名为BS_LocomotionWalk
- 复制一个,并命名为
BS_LocomotionRun
,将600
处的动画修改为RoadieRun_Fwd
- 将原来的
-
创建C++类
STUCharacterMovementComponent
,继承于CharacterMovementComponent
,负责修改角色的最大速度- 目录为:
ShootThemUp/Source/ShootThemUp/Public/Components
- 目录为:
-
修改
ShootThemUp.Build.cs
,将新的目录添加进去PublicIncludePaths.AddRange(new string[] { "ShootThemUp/Public/Player", "ShootThemUp/Public/Components" });
-
修改
STUCharacterMovementComponent
#pragma once #include "CoreMinimal.h" #include "GameFramework/CharacterMovementComponent.h" #include "STUCharacterMovementComponent.generated.h" UCLASS() class SHOOTTHEMUP_API USTUCharacterMovementComponent : public UCharacterMovementComponent { GENERATED_BODY() public: // 通过meta设置值的范围 UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Movement", meta = (ClampMin = "1.5", ClampMax = "10.0")) float RunModifier = 2.0f; virtual float GetMaxSpeed() const override; };
#include "Components/STUCharacterMovementComponent.h" #include "Player/STUBaseCharacter.h" float USTUCharacterMovementComponent::GetMaxSpeed() const { float MaxSpeed = Super::GetMaxSpeed(); const ASTUBaseCharacter* Player = Cast<ASTUBaseCharacter>(GetPawnOwner()); // 如果角色在跑步, 则最大速度要变大RunModifier倍 if (Player && Player->IsRunning()) MaxSpeed *= RunModifier; return MaxSpeed; }
-
修改
STUBaseCharacter
的构造函数UCLASS() class SHOOTTHEMUP_API ASTUBaseCharacter : public ACharacter { public: // 由于CharacterMovementComponent组件是默认组件, 因此我们需要通过参数显式指定 ASTUBaseCharacter(const FObjectInitializer& ObjInit); ... }
```c++ #include “Player/STUBaseCharacter.h” #include “Camera/CameraComponent.h” #include “Components/InputComponent.h” #include “GameFramework/SpringArmComponent.h” #include “Components/STUCharacterMovementComponent.h”
DEFINE_LOG_CATEGORY_STATIC(LogSTUBaseCharacter, All, All);
// 由于CharacterMovementComponent组件是默认组件, 因此我们需要通过参数显式指定 // 在调用父类的构造函数时, 显式指定CharacterMovementComponentName使用自定义的USTUCharacterMovementComponent ASTUBaseCharacter::ASTUBaseCharacter(const FObjectInitializer& ObjInit) : Super(ObjInit.SetDefaultSubobjectClass
(ACharacter::CharacterMovementComponentName)) { PrimaryActorTick.bCanEverTick = true; // 创建弹簧臂组件, 并设置其父组件, 允许pawn控制旋转 SpringArmComponent = CreateDefaultSubobject<USpringArmComponent>("SpringArmComponent"); SpringArmComponent->SetupAttachment(GetRootComponent()); SpringArmComponent->bUsePawnControlRotation = true; // 创建相机组件, 并设置其父组件 CameraComponent = CreateDefaultSubobject<UCameraComponent>("CameraComponent"); CameraComponent->SetupAttachment(SpringArmComponent); }
-
在角色蓝图
BP_STUBaseCharacter
中,我们可以看到,其角色移动组件
继承于STUCharacterMovementComponent
- 选中
角色运动组件
,可以修改RunModifier
的值
- 选中
九、前后左右的运动动画
-
创建混合空间
BS_LocomotionWalk2
- 水平轴:速度,
0~600
- 垂直轴:方向,
-180~180
- 将动画拖入坐标轴
- 水平轴:速度,
-
修改动画蓝图
ABP_BaseCharacter
-
状态机:修改
Walk
状态的动画 -
事件图表:绘制角色的向前向量
-
事件图表:获取速度的角度值
-
事件图表:判断游戏是否处于执行状态
- 由于动画蓝图会被UE编辑器、游戏程序两者调用,因此在执行事件图表中的逻辑时,要提前判断一下
CharacterPawn
是否为Valid
- 由于动画蓝图会被UE编辑器、游戏程序两者调用,因此在执行事件图表中的逻辑时,要提前判断一下
-
-
修改
STUBaseCharacter
:获取角色速度的角度值UCLASS() class SHOOTTHEMUP_API ASTUBaseCharacter : public ACharacter { ... public: // 获取角色移动的方向 UFUNCTION(BlueprintCallable, Category = "Movement") float GetMovementDirection() const; ... }
// 获取角色移动的方向 float ASTUBaseCharacter::GetMovementDirection() const { // 特判: 速度为0 if (GetVelocity().IsZero()) return 0.0f; const FVector VelocityDirection = GetVelocity().GetSafeNormal(); const FVector ForwardDirection = GetActorForwardVector(); // 通过点乘, 获得具体的角度值 float angle = FMath::Acos(FVector::DotProduct(ForwardDirection, VelocityDirection)); angle = FMath::RadiansToDegrees(angle); // 通过叉乘结果的Z值, 判断是处于顺时针还是逆时针方向 const FVector CrossProduct = FVector::CrossProduct(ForwardDirection, VelocityDirection); // 特判: 速度与角色运动方向重合/相反 if (CrossProduct.IsZero()) return angle; angle *= FMath::Sign(CrossProduct.Z); return angle; }
-
修改动画蓝图
ABP_BaseCharacter
- 事件图表:获取角色运动的方向
-
将原来的
BS_LocomotionWalk
删除,将刚才创建的BS_LocomotionWalk2
重命名为BS_LocomotionWalk
十、组装游戏
- 点击
文件 => 打包项目 => 编译配置
,选择发行
- 点击
文件 => 打包项目 => Windows(64-bit)
,将文件打包到ShootThemUp\Build
目录下 - 然后,即可在
ShootThemUp\Build
中,得到打包后的游戏