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、GoogleIndentWidth:缩进包含的空格数UseTab:是否使用tab缩进,如:Never、AlwaysTabWidth: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

七、跳转动画,状态机制
-
添加操作映射

-
修改
STUBaseCharacterJump()是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:在空中 == trueJumpStart => JumpLoop:动画剩余时间 < 0.1JumpLoop => JumpEnd:在空中 == falseJumpEnd => 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:在奔跑 == trueRun => Walk:在奔跑 == falseRun => 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中,得到打包后的游戏