【UE5.3 C++】ARPG游戏 03-创建Character

目录

效果

步骤

一、资产准备

二、创建Character类

三、通过增强输入控制角色

四、给角色添加毛发

五、动画蓝图(站立、跑)

六、C++创建动画实例

七、动画蓝图(跳)


效果

步骤

一、资产准备

Fab中找到"Windwalker Echo",点击创建项目

打开下载的工程,将EchoContent整体迁移到我们的工程中

二、创建Character类

新建C++类

选择"角色"

这里命名为"SlashCharacter"

创建基于"SlashCharacter"的蓝图类

这里命名为"BP_SlashCharacter"

打开"BP_SlashCharacter",设置骨骼网格体资产为"Echo"

调整下位置和旋转

创建游戏模式,设置默认Pawn类为"BP_SlashCharacter"

三、通过增强输入控制角色

在"EchoContent"可以找到如下输入资产,这套资源包采用的是一种"模拟旧版输入系统"的增强输入写法。它把前后和左右拆分成了两个独立的资产:IA_MoveForward(前后)和 IA_MoveRight(左右)。同理,视角也拆分成了 IA_Turn(左右看)和 IA_LookUp(上下看)。

在"SlashCharacter.h"中添加如下代码,首先添加必须的头文件

声明组件

声明输入变量和对应的回调函数

在"SlashCharacter.cpp"中添加如下代码:

引入必要的头文件

添加组件

在游戏开始时激活"映射上下文"

实现回调函数

打开"BP_SlashCharacter",进行如下设置

取消勾选"使用控制器旋转Yaw",让角色身体独立于视角

选中弹簧臂组件,勾选"使用Pawn控制旋转",使弹簧臂跟着鼠标转

选中角色移动组件,勾选"将旋转朝向运动",此时角色在前进时就会自动将身体朝向视角方向

此时我们已经可以让角色前后左右移动并旋转视角,但是还没有执行相应的动作。

四、给角色添加毛发

打开".Build.cs",添加"HairStrandsCore"和"Niagara"模块,然后最好重新生成一下vs项目文件

Ctrl+Shift+B重新编译解决方案,然后关闭Visual Studio。打开文件资源管理器,找到项目所在目录,删除如下3个目录

删除后,右键点击重新生成vs文件,等进度提示结束后会重新生成"Saved"、"Intermediate"目录

双击.uproject文件,此时会弹出是否rebuild,选择Yes。等一会后,会重新生成"Binaries"目录,并自动打开UEEditor。

关闭UEEditor,打开Visual Studio。在"SlashCharacter.h"声明两个毛发组件变量

在"SlashCharacter.cpp"中引入必要的头文件

创建两个毛发组件分别是头发和眉毛,附加到网格体上

在项目设置中勾选"支持蒙皮缓存"

编译后打开"BP_SlashCharacter",此时可以看到已经成功添加了两个毛发组件

设置GroomAsset

五、动画蓝图(站立、跑)

新建一个动画蓝图,骨骼使用"Echo_Skeleton"

这里命名为"ABP_SlashCharacter"

打开"ABP_SlashCharacter",在事件图表中,先获取角色和角色移动组件的引用

逐帧获取角色的移动速度

在动画图表中新增一个状态机,这里命名为"GroundLocomotion"

打开"GroundLocomotion",添加站立和跑步两种状态及其转换规则

站立和跑步状态分别输出对应姿势

注意设置循环动画

设置状态切换的规则

在"BP_SlashCharacter"中使用动画蓝图

此时运行游戏可以看到角色能够站立和跑步了:

六、C++创建动画实例

新建C++类

父类选择"AnimInstance"

这里命名为"SlashAnimInstance"

删除动画蓝图中事件图表的所有节点和变量,我们准备在C++中获取"BP_SlashCharacter"及其角色移动组件的引用,动画蓝图只保留"GroundSpeed"

在"SlashAnimInstance.h"中重写父类中的初始化函数"NativeInitializeAnimation",这个函数对应于动画蓝图中的 Event Blueprint Initialize Animation 节点,通常在动画实例被创建或初始化时**只执行一次,**用于初始化变量或获取其他组件的引用。最常见的用法是在这里获取拥有该动画的Pawn或Character的引用,以便后续在更新动画时使用。

继续重写父类的NativeUpdateAnimation函数,对应于动画蓝图中的 Event Blueprint Update Animation 节点。简单来说,这是动画实例的"Tick"函数,每一帧都会执行,核心目的是把游戏角色的状态(C++ 数据)同步给动画蓝图(变量),以便驱动状态机或混合空间。

声明如下成员变量

在"SlashAnimInstance.cpp"中先引入必要的头文件

在NativeInitializeAnimation函数中获取动画实例的Pawn拥有者,然后转换为ASlashCharacter类型,从而获取SlashCharacter的引用,进而获取其角色移动组件的引用

在NativeUpdateAnimation函数中实时更新角色的移动速度

编译后,打开动画蓝图"ABP_Echo",将该动画蓝图的父类设置为用C++编写的动画实例"SlashAnimInstance"

重设父类后,可以看到虚幻引擎防止变量重名,自动修改了蓝图中变量名

由于父类已经创建了"GroundSpeed",这里删除之前蓝图中创建的"GroundSpeed"变量,并将转换规则中涉及到的变量进行替换

可以通过勾选"显示继承的变量"来显示我们在C++中创建的变量

七、动画蓝图(跳)

在动画实例头文件中声明变量

在动画蓝图更新时获取角色是否处于下落状态

编译后可以看到此时动画蓝图中出现变量"IsFalling"可以用于判断角色状态

在动画蓝图"ABP_SlashCharacter"中,将先前状态机"GroundLocomotion"输出的姿势存起来,然后新建一个状态机"MainState"

在"MainState"中添加"OnGround"、"InAir"和"Land"三个状态

在状态"OnGround"中使用之前存的"Cache_GroundLocomotion"

"InAir"状态使用起跳的动画序列

"Land"状态使用下落的动画序列

注意要取消循环动画

当"IsFalling"为True时,让角色状态由"OnGround"转为"InAir",表示起跳

当"IsFalling"由True变为False时让角色状态由"InAir"转为"Land",表示落地

当落地动画播放完成自动将状态由"Land"转为"OnGround"

但是由于落地动画时间较长,这里需要再增加一个转换规则

转换规则中添加如下节点。表示如果玩家落地后立即开始移动,不要播放完整的落地缓冲动画,只播放前 0.2秒,然后迅速切换到跑步状态。

为了防止玩家多次原地起跳,需要增加"Land"到"InAir"的规则

规则就是"IsFalling"为True,这样一来,无论落地动画播了多少,只要系统检测到角色在空中按下空格键导致 CharacterMovement 进入 Falling 模式,动画蓝图就会无视落地动作,瞬间切换到跳跃/空中动作,操作响应会非常灵敏

在"SlashCharacter.h"中添加跳跃的 Input Action 变量,这里不需要像 MoveForward 那样声明一个新的 void Jump(const FInputActionValue& Value) 函数,因为我们要直接使用父类 ACharacter 自带的 Jump 函数实现角色跳跃

在"SlashCharacter.cpp"中绑定输入

新增一个输入操作资产,这里命名为"IA_Jump"

在输入映射上下文中增加一个映射,按键设置为空格键

在"BP_SlashCharacter"中设置Jump Action

最终效果如文章开头所示。

代码:

cpp 复制代码
// SlashCharacter.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "InputActionValue.h"
#include "SlashCharacter.generated.h"

UCLASS()
class SLASH_API ASlashCharacter : public ACharacter
{
	GENERATED_BODY()

public:
	ASlashCharacter();

    virtual void Tick(float DeltaTime) override;
    virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

private:
    UPROPERTY(VisibleAnywhere)
    class USpringArmComponent* SpringArm;

    UPROPERTY(VisibleAnywhere)
    class UCameraComponent* ViewCamera;

    UPROPERTY(VisibleAnywhere)
    class UGroomComponent* Hair;

    UPROPERTY(VisibleAnywhere)
    class UGroomComponent* Eyebrows;

protected:
	virtual void BeginPlay() override;

    // 映射上下文
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
    class UInputMappingContext* SlashContext;

    // 声明4个独立的动作变量
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
    class UInputAction* MoveForwardAction; // 对应 IA_MoveForward

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
    class UInputAction* MoveRightAction;   // 对应 IA_MoveRight

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
    class UInputAction* TurnAction;        // 对应 IA_Turn (鼠标X)

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
    class UInputAction* LookUpAction;      // 对应 IA_LookUp (鼠标Y)

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
    class UInputAction* JumpAction; // 对应 IA_Jump

    // 声明4个对应的函数
    void MoveForward(const FInputActionValue& Value);
    void MoveRight(const FInputActionValue& Value);
    void Turn(const FInputActionValue& Value);
    void LookUp(const FInputActionValue& Value);

};
cpp 复制代码
// SlashCharacter.cpp


#include "Characters/SlashCharacter.h"
#include "Components/InputComponent.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "GameFramework/Controller.h" 
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "GroomComponent.h"

ASlashCharacter::ASlashCharacter()
{
	PrimaryActorTick.bCanEverTick = true;

    // 让角色不要跟着控制器旋转(只让摄像机转,人脸朝移动方向)
    bUseControllerRotationPitch = false;
    bUseControllerRotationYaw = false;
    bUseControllerRotationRoll = false;

    // 添加组件
    SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpingArm"));
    SpringArm->SetupAttachment(RootComponent);
    SpringArm->TargetArmLength = 300.f;

    ViewCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("ViewCamera"));
    ViewCamera->SetupAttachment(SpringArm);

    Hair = CreateDefaultSubobject<UGroomComponent>(TEXT("Hair"));
    Hair->SetupAttachment(GetMesh());

    Eyebrows = CreateDefaultSubobject<UGroomComponent>(TEXT("Eyebrows"));
    Eyebrows->SetupAttachment(GetMesh());
}

void ASlashCharacter::BeginPlay()
{
	Super::BeginPlay();

    if (APlayerController* PlayerController = Cast<APlayerController>(Controller))
    {
        if (UEnhancedInputLocalPlayerSubsystem* Subsystem = 
            ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
        {
            // 注意:SlashContext 必须在蓝图里被赋值,否则这里也会失败
            if (SlashContext)
            {
                Subsystem->AddMappingContext(SlashContext, 0);
            }
        }
    }
}

void ASlashCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

// 1. 实现 MoveForward (只负责前后)
void ASlashCharacter::MoveForward(const FInputActionValue& Value)
{
    const float MovementValue = Value.Get<float>(); // 获取浮点数
    // UE_LOG(LogTemp, Warning, TEXT("MovementValue: %f"), MovementValue);              

    if (MovementValue != 0.f && Controller != nullptr)
    {
        // 找方向:基于摄像机的 Yaw
        const FRotator Rotation = Controller->GetControlRotation();
        const FRotator YawRotation(0, Rotation.Yaw, 0);

        // 获取前方向量 (X轴)
        const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
        AddMovementInput(Direction, MovementValue);
    }
}

// 2. 实现 MoveRight (只负责左右)
void ASlashCharacter::MoveRight(const FInputActionValue& Value)
{
    const float MovementValue = Value.Get<float>();

    if (MovementValue != 0.f && Controller != nullptr)
    {
        const FRotator Rotation = Controller->GetControlRotation();
        const FRotator YawRotation(0, Rotation.Yaw, 0);

        // 获取右方向量 (Y轴)
        const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
        AddMovementInput(Direction, MovementValue);
    }
}

// 3. 实现 Turn (鼠标左右动 -> 角色左右转)
void ASlashCharacter::Turn(const FInputActionValue& Value)
{
    const float TurnValue = Value.Get<float>();
    AddControllerYawInput(TurnValue);
}

// 4. 实现 LookUp (鼠标上下动 -> 摄像机上下看)
void ASlashCharacter::LookUp(const FInputActionValue& Value)
{
    const float LookUpValue = Value.Get<float>();
    AddControllerPitchInput(LookUpValue);
}

void ASlashCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

    if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
    {
        // 分别绑定4个动作
        if (MoveForwardAction)
            EnhancedInputComponent->BindAction(MoveForwardAction, ETriggerEvent::Triggered, this, &ASlashCharacter::MoveForward);

        if (MoveRightAction)
            EnhancedInputComponent->BindAction(MoveRightAction, ETriggerEvent::Triggered, this, &ASlashCharacter::MoveRight);

        if (TurnAction)
            EnhancedInputComponent->BindAction(TurnAction, ETriggerEvent::Triggered, this, &ASlashCharacter::Turn);

        if (LookUpAction)
            EnhancedInputComponent->BindAction(LookUpAction, ETriggerEvent::Triggered, this, &ASlashCharacter::LookUp);

        if (JumpAction)
        {
            EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Triggered, this, &ACharacter::Jump);
        }
    }
}
cpp 复制代码
// SlashAnimInstance.h

#pragma once

#include "CoreMinimal.h"
#include "Animation/AnimInstance.h"
#include "SlashAnimInstance.generated.h"

/**
 * 
 */
UCLASS()
class SLASH_API USlashAnimInstance : public UAnimInstance
{
	GENERATED_BODY()
public:
	virtual void NativeInitializeAnimation() override;
	virtual void NativeUpdateAnimation(float DeltaTime) override;

	UPROPERTY(BlueprintReadOnly)
	class ASlashCharacter* SlashCharacter;

	UPROPERTY(BlueprintReadOnly, Category = Movement)
	class UCharacterMovementComponent* SlashCharacterMovement;

	UPROPERTY(BlueprintReadOnly, Category = Movement)
	float GroundSpeed;

	UPROPERTY(BlueprintReadOnly, Category = Movement)
	bool IsFalling;
};
cpp 复制代码
// SlashAnimInstance.cpp


#include "Characters/SlashAnimInstance.h"
#include "Characters/SlashCharacter.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Kismet/KismetMathLibrary.h"

void USlashAnimInstance::NativeInitializeAnimation()
{
	Super::NativeInitializeAnimation();  //调用父类的NativeInitializeAnimation函数

	SlashCharacter = Cast<ASlashCharacter>(TryGetPawnOwner());
	if (SlashCharacter)
	{
		SlashCharacterMovement = SlashCharacter->GetCharacterMovement();
	}
}

void USlashAnimInstance::NativeUpdateAnimation(float DeltaTime)
{
	Super::NativeUpdateAnimation(DeltaTime);

	if (SlashCharacterMovement)
	{
		GroundSpeed = UKismetMathLibrary::VSizeXY(SlashCharacterMovement->Velocity);
		IsFalling = SlashCharacterMovement->IsFalling();
	}
}
相关推荐
njsgcs5 小时前
枪战游戏“棋盘化”价值建模 强化学习或rag
游戏
AI视觉网奇8 小时前
Delaying 20 processes from spawning due to memory pressure
笔记·学习·ue5
zhangzhangkeji9 小时前
UE5 C++(57-1)创建与删除文件夹,涉及类 FPlatformFileManager、IPlatformFile
ue5
前端不太难10 小时前
HarmonyOS 游戏里,主线程到底该干什么?
游戏·状态模式·harmonyos
远程软件小助理11 小时前
电脑玩手游哪个模拟器启动速度最快?MuMu、雷电、应用宝对比实测
游戏
暮志未晚Webgl12 小时前
UE5使用实例化静态网格体组件实现批量生成内容
ue5
zhangzhangkeji12 小时前
UE5 C++(55-1)文件的读取与写入, 本类继承自蓝图函数库 UBlueprintFunctionLibrary,其子类只应包含静态成员函数,
ue5
zhangzhangkeji1 天前
UE5 C++(54)动态创建材质实例,类 UMateriallnstance 的继承关系,与矢量 FLinearColor、FColor
ue5
开开心心_Every1 天前
家长控制电脑软件:定时锁屏管理使用时长
网络协议·tcp/ip·游戏·微信·pdf·excel·语音识别
云边散步1 天前
godot2D游戏教程系列二(3)
笔记·学习·游戏·游戏开发