【AdvancedLocomotionSystemV】第一篇 C++ 实现人物移动

效果展示

查看所有文章

AdvancedLocomotionSystemV

导入资源

先创建一个第三人称的新手工程项目。
打开AdvancedLocomotionSystemV项目,将一些动画资源导入到我们项目的Content的内容下。
【AdvancedLocomotionSystemV】第一篇 C++ 实现人物移动_第1张图片
还有摄影机骨骼和模型也要。
【AdvancedLocomotionSystemV】第一篇 C++ 实现人物移动_第2张图片
打开项目的文件夹,将输入配置复制替换到我们的文件中。
【AdvancedLocomotionSystemV】第一篇 C++ 实现人物移动_第3张图片

创建角色

由于第三人称的模板已经给我们了一些代码,所以直接用他的类就好了。
打开第三人称角色模板C++文件,将一些不需要的东西删除,其中最主要的就是将摄像机给删除了,添加了一些保存人物状态的变量。

为什么要将摄像机给删除了?
因为当前这个项目的摄像头是跟着胶囊体移动的,如果我们要进行第一人称第三人称视角替换的话就需要多个摄像头,并且如果我们想要摄像机跟着角色状态变化的话就有点麻烦。所以就直接另外弄一个摄像机进行统一管理。

以下是我删好了的代码:

  • .h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "ALSVLearnDemoCharacter.generated.h"

UCLASS(config=Game)
class AALSVLearnDemoCharacter : public ACharacter
{
	GENERATED_BODY()

public:
	AALSVLearnDemoCharacter();

protected:
	/** Called for forwards/backward input */
	void MoveForward(float Value);

	/** Called for side to side input */
	void MoveRight(float Value);

protected:
	// APawn interface
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
	
	virtual void Tick(float DeltaSeconds) override;
	// End of APawn interface

public:
	// 判断角色是否处于运动状态
	UPROPERTY(BlueprintReadOnly, Category="Essential Information")
	bool bIsMoving;

	// 判断是否有运动输入
	UPROPERTY(BlueprintReadOnly, Category="Essential Information")
	bool bHasMovementInput;

	// 角色移动的水平速度,就是不包含Z轴的速度大小。
	UPROPERTY(BlueprintReadOnly, Category="Essential Information")
	float Speed;

	// 按键输入比率
	UPROPERTY(BlueprintReadOnly, Category="Essential Information")
	float MovementInputAmount;

	// 获取人物状态相关的值
	UFUNCTION(BlueprintCallable, Category="Essential Information")
	void GetEssentialValues(FVector& Velocity, bool& IsMoving, bool& HasMovementInput, float& fSpeed);

	// 更新角色运动相关变量
	UFUNCTION(BlueprintCallable, Category="Essential Information")
	void SetEssentialValues();
};
  • .cpp
#include "ALSVLearnDemoCharacter.h"
#include "HeadMountedDisplayFunctionLibrary.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/InputComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/Controller.h"
#include "GameFramework/SpringArmComponent.h"

//
// AALSVLearnDemoCharacter

AALSVLearnDemoCharacter::AALSVLearnDemoCharacter()
{
	// Set size for collision capsule
	GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);

	// Don't rotate when the controller rotates. Let that just affect the camera.
	bUseControllerRotationPitch = false;
	bUseControllerRotationYaw = false;
	bUseControllerRotationRoll = false;

	// Configure character movement
	GetCharacterMovement()->bOrientRotationToMovement = true; // Character moves in the direction of input...	
	GetCharacterMovement()->RotationRate = FRotator(0.0f, 540.0f, 0.0f); // ...at this rotation rate
	GetCharacterMovement()->JumpZVelocity = 600.f;
	GetCharacterMovement()->AirControl = 0.2f;
}

//
// Input
// 这里一定要改一下, 要和复刻项目的那个按键名一模一样
void AALSVLearnDemoCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
	// Set up gameplay key bindings
	check(PlayerInputComponent);
	PlayerInputComponent->BindAction("JumpAction", IE_Pressed, this, &ACharacter::Jump);
	PlayerInputComponent->BindAction("JumpAction", IE_Released, this, &ACharacter::StopJumping);

	PlayerInputComponent->BindAxis("MoveForward/Backwards", this, &AALSVLearnDemoCharacter::MoveForward);
	PlayerInputComponent->BindAxis("MoveRight/Left", this, &AALSVLearnDemoCharacter::MoveRight);
	
	PlayerInputComponent->BindAxis("LookLeft/Right", this, &APawn::AddControllerYawInput);
	PlayerInputComponent->BindAxis("LookUp/Down", this, &APawn::AddControllerPitchInput);
}

void AALSVLearnDemoCharacter::Tick(float DeltaSeconds)
{
	Super::Tick(DeltaSeconds);

	// 更新角色运动相关变量
	SetEssentialValues();
}


void AALSVLearnDemoCharacter::GetEssentialValues(FVector& Velocity, bool& IsMoving, bool& HasMovementInput,
                                                 float& fSpeed)
{
	Velocity = GetVelocity();
	IsMoving = bIsMoving;
	HasMovementInput = bHasMovementInput;
	fSpeed = Speed;
}

void AALSVLearnDemoCharacter::SetEssentialValues()
{
	// 因为我们人物在地面上运动只需要水平的速度值大小,我们获取到人物移动速度之后将Z轴去掉的大小就是当前在地面上运动的速度大小。
	FVector LocVelocity = GetVelocity();
	Speed = FVector(LocVelocity.X, LocVelocity.Y, 0.f).Size();

	// 只要是速度大小大于1.f的都算作正在运动
	bIsMoving = Speed > 1.f;
	
	/*
	 *GetCurrentAcceleration : 当前加速度矢量(带幅值)。 每次更新都会根据输入向量、MaxAcceleration和当前移动模式的约束来计算该值。
	 *那么这个公式的意思就是只要我的加速度矢量值大小不为零,那就代表有运动按键输入
	 */
	MovementInputAmount = GetCharacterMovement()->GetCurrentAcceleration().Size() / GetCharacterMovement()->
		GetMaxAcceleration();
	bHasMovementInput = MovementInputAmount > 0.f;
}

void AALSVLearnDemoCharacter::MoveForward(float Value)
{
	if ((Controller != nullptr) && (Value != 0.0f))
	{
		// find out which way is forward
		const FRotator Rotation = Controller->GetControlRotation();
		const FRotator YawRotation(0, Rotation.Yaw, 0);

		// get forward vector
		const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
		AddMovementInput(Direction, Value);
	}
}

void AALSVLearnDemoCharacter::MoveRight(float Value)
{
	if ((Controller != nullptr) && (Value != 0.0f))
	{
		// find out which way is right
		const FRotator Rotation = Controller->GetControlRotation();
		const FRotator YawRotation(0, Rotation.Yaw, 0);

		// get right vector 
		const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
		// add movement in that direction
		AddMovementInput(Direction, Value);
	}
}

创建它的蓝图子类,命名为:ALs_AnimMan_CharacterBP
将角色导入到其中,调整到角色朝前的位置。

创建角色动画C++实例类

创建一个AnimInstance类,命名为ALSAnimInstance

.h

#pragma once

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

class AALSVLearnDemoCharacter;

/*
 * 因为分别处理各个方向的值有点麻烦,所以就将值整合到了一个结构体中,函数的调用。
 */
USTRUCT(BlueprintType)
struct FVelocityBlend
{
	GENERATED_BODY()

public:
	FVelocityBlend(float f = 0.f, float b = 0.f, float l = 0.f, float r = 0.f) : F(f), B(b), L(l), R(r)
	{
	}

	// 变量分开写, 方便后面写注释
	// 一定要加上宏定义将变量公开,要不然无法使用Split Struct Pin.
	// 四个方向的运动速度
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	float F;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	float B;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	float L;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	float R;
};

/**
 * 
 */
UCLASS()
class ALSVLEARNDEMO_API UALSAnimInstance : public UAnimInstance
{
	GENERATED_BODY()
public:

	/*
	 * 这两个函数创建动画实例的标配,一定要记住。
	 */
	virtual void NativeBeginPlay() override;

	UFUNCTION(BlueprintCallable)
	virtual void NativeUpdateAnimation(float DeltaSeconds) override;

	
	// 储存角色变量
	UPROPERTY(BlueprintReadOnly)
	AALSVLearnDemoCharacter* Character;

	/*
	* 更新角色相关变量
	*/
	UFUNCTION(BlueprintCallable)
	void UpdateCharacterInfo();

	/*
	 * 更新角色运动相关的值
	 */
	UFUNCTION(BlueprintCallable)
	void UpdateMovementValues();

	/*
	 * 角色移动的速度
	 */
	UPROPERTY(BlueprintReadOnly)
	FVector Velocity;

	/*
	 * 计算各个方向的速度值
	 */
	UFUNCTION(BlueprintCallable)
	FVelocityBlend CalculateVelocityBlend();

	/*
	 * 对各个方向移动的差值计算。
	 */
	UFUNCTION(BlueprintCallable)
	FVelocityBlend InterpVelocityBlend(FVelocityBlend Current, FVelocityBlend Target, float InterpSpeed,
	                                   float DeltaTime) const;

	/*
	 * 计算步幅
	 */
	UFUNCTION(BlueprintCallable)
	float CalculateStrideBlend() const;

///
	/*
	 * 判断是否处于移动状态
	 */
	UPROPERTY(BlueprintReadOnly, Category="Character Information")
	bool bIsMoving;

	/*
	 * 判断是否有移动按键输入
	 */
	UPROPERTY(BlueprintReadOnly, Category="Character Information")
	bool bHasMovementInput;

	/*
	 * 角色水平移动速度
	 */
	UPROPERTY(BlueprintReadOnly, Category="Character Information")
	float Speed;

	/*
	 * 储存各个个方向速度值
	 */
	UPROPERTY(BlueprintReadWrite, Category = "Anim Graph|Grounded")
	FVelocityBlend VelocityBlend;

	/*
	 * 判断是否可以移动
	 */
	UPROPERTY(BlueprintReadWrite, Category = "Anim Graph|Grounded")
	bool bShouldMove;

	/*
	 * 每次移动的步幅值
	 */
	UPROPERTY(BlueprintReadWrite, Category = "Anim Graph|Grounded")
	float StrideBlend;

	UFUNCTION(BlueprintCallable, Category="Grounded")
	bool ShouldMoveCheck() const;
	

/// 配置相关变量
	/*
	 * 世界增量时间
	 */
	UPROPERTY()
	float DeltaTimeX;

	/*
	 * 各个方向进行差值计算更新的速度
	 */
	UPROPERTY(EditAnywhere, Category="Config")
	float VelocityBlendInterpSpeed;
	
/
///混合曲线相关变量

	/*
	 * 向前运动的走路运动曲线
	 */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Blend Curves")
	UCurveFloat* StrideBlendNWalk;

	// UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Blend Curves")
	// UCurveFloat* StrideBlendCWalk;

	/*
	 * 向前运动的跑步运动曲线
	 */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Blend Curves")
	UCurveFloat* StrideBlendNRun;

protected:
	/*
	 * 获得动画曲线的值
	 */
	UFUNCTION()
	float GetAnimCurveClamped(FName Name, float Bias, float ClampMin, float ClampMax) const;
};

.cpp

#include "ALSAnimInstance.h"

#include "ALSVLearnDemo/ALSVLearnDemoCharacter.h"
#include "Kismet/KismetMathLibrary.h"

void UALSAnimInstance::NativeBeginPlay()
{
	Super::NativeBeginPlay();
	// 获取到角色指针
	Character = Cast<AALSVLearnDemoCharacter>(TryGetPawnOwner());

	// 初始化为 12.f
	VelocityBlendInterpSpeed = 12.f;
}

void UALSAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
	Super::NativeUpdateAnimation(DeltaSeconds);
	// 如果没有获取到角色,直接停止
	if (!Character) return;

	// 更新世界增量值
	DeltaTimeX = DeltaSeconds;

	// 更新角色相关信息
	UpdateCharacterInfo();

	bShouldMove = ShouldMoveCheck();
	UpdateMovementValues();
}

void UALSAnimInstance::UpdateMovementValues()
{
	// 获取各个方向下帧的混合速度
	VelocityBlend = InterpVelocityBlend(VelocityBlend, CalculateVelocityBlend(), VelocityBlendInterpSpeed, DeltaTimeX);

	// 计算步幅
	StrideBlend = CalculateStrideBlend();
}

void UALSAnimInstance::UpdateCharacterInfo()
{
	// 调用角色的获取变量值函数
	Character->GetEssentialValues(Velocity, bIsMoving, bHasMovementInput, Speed);
}

FVelocityBlend UALSAnimInstance::CalculateVelocityBlend()
{
	// 获取速度方向相对于角色来说反方向旋转【GetActorRotation()】的向量值
	FVector LocRelativeVelocityDir = UKismetMathLibrary::LessLess_VectorRotator(
		UKismetMathLibrary::Normal(Velocity, .1f), Character->GetActorRotation());
	float Sum = FMath::Abs(LocRelativeVelocityDir.X) + FMath::Abs(LocRelativeVelocityDir.Y) + FMath::Abs(
		LocRelativeVelocityDir.Z);

	// 计算出各个方向所占的百分比 
	FVector RelativeDirection = LocRelativeVelocityDir / Sum;

	return FVelocityBlend(
		UKismetMathLibrary::FClamp(RelativeDirection.X, 0.f, 1.f), // 向前运动速度百分比
		FMath::Abs(UKismetMathLibrary::FClamp(RelativeDirection.X, -1.f, 0.f)), // 向后运动速度百分比
		FMath::Abs(UKismetMathLibrary::FClamp(RelativeDirection.Y, -1.f, 0.f)), // 向左运动速度百分比
		UKismetMathLibrary::FClamp(RelativeDirection.Y, 0.f, 1.f)); // 向右运动速度百分比
}

// 各个方向速度的差值计算
FVelocityBlend UALSAnimInstance::InterpVelocityBlend(FVelocityBlend Current, FVelocityBlend Target, float InterpSpeed,
                                                     float DeltaTime) const
{
	return FVelocityBlend(
		UKismetMathLibrary::FInterpTo(Current.F, Target.F, DeltaTime, InterpSpeed),
		UKismetMathLibrary::FInterpTo(Current.B, Target.B, DeltaTime, InterpSpeed),
		UKismetMathLibrary::FInterpTo(Current.L, Target.L, DeltaTime, InterpSpeed),
		UKismetMathLibrary::FInterpTo(Current.R, Target.R, DeltaTime, InterpSpeed));
}


float UALSAnimInstance::CalculateStrideBlend() const
{
	// 获得当前速度下向前走路的值
	float WalkValue = StrideBlendNWalk->GetFloatValue(Speed);
	// 获得当前速度下向前跑步的值
	float RunValue = StrideBlendNRun->GetFloatValue(Speed);

	/* 基于Alpha 计算 WalkValue 和 RunValue 之间的差值
	 * GetAnimCurveClamped(FName(TEXT("Weight_Gait")), -1.f, 0.f, 1.f) : 获取当前运动动画的 Weight_Gait 曲线值,
	 * Weight_Gait : 冲刺的时候为3, 跑步的时候为2, 走路的时候为1。
	 * 所以 Alpha : 冲刺、跑步的时候为1, 走路的时候为0。
	 * 当Alpha = 1的时候返回RunValue值的100%
	 * 当Alpha = 0的时候返回WalkValue值的100%
	 * 就是根据当前状态返回步幅
	 */
	return UKismetMathLibrary::Lerp(WalkValue, RunValue,
	                                GetAnimCurveClamped(FName(TEXT("Weight_Gait")), -1.f, 0.f, 1.f));

	// TODO Set CLF Curve
}

bool UALSAnimInstance::ShouldMoveCheck() const
{
	return bIsMoving && bHasMovementInput || Speed;
}

float UALSAnimInstance::GetAnimCurveClamped(FName Name, float Bias, float ClampMin, float ClampMax) const
{
	return UKismetMathLibrary::FClamp(GetCurveValue(Name) + Bias, ClampMin, ClampMax);
}

走的时候运动曲线为1:
【AdvancedLocomotionSystemV】第一篇 C++ 实现人物移动_第4张图片
跑步的时候运动曲线为2:
【AdvancedLocomotionSystemV】第一篇 C++ 实现人物移动_第5张图片
冲刺的时候运动曲线为3:
【AdvancedLocomotionSystemV】第一篇 C++ 实现人物移动_第6张图片
将曲线变量进行赋值。
【AdvancedLocomotionSystemV】第一篇 C++ 实现人物移动_第7张图片
【AdvancedLocomotionSystemV】第一篇 C++ 实现人物移动_第8张图片

【AdvancedLocomotionSystemV】第一篇 C++ 实现人物移动_第9张图片
跑步曲线思路是一样的,相对来说没有走路曲线那么抖,体现出一种加速的感觉。
【AdvancedLocomotionSystemV】第一篇 C++ 实现人物移动_第10张图片

创建动画蓝图

创建继承动画骨骼,C++动画实例的动画蓝图。
【AdvancedLocomotionSystemV】第一篇 C++ 实现人物移动_第11张图片
点击创建动画层
【AdvancedLocomotionSystemV】第一篇 C++ 实现人物移动_第12张图片
命名为:(N)CycleBlending

  • (N)CycleBlending

在细节面板中添加输入姿势:
【AdvancedLocomotionSystemV】第一篇 C++ 实现人物移动_第13张图片

【AdvancedLocomotionSystemV】第一篇 C++ 实现人物移动_第14张图片
【AdvancedLocomotionSystemV】第一篇 C++ 实现人物移动_第15张图片
将输入进来的动画进行混合处理,使得他们的衔接更加流畅。
【AdvancedLocomotionSystemV】第一篇 C++ 实现人物移动_第16张图片

  • 创建动画层 BaseLayer

【AdvancedLocomotionSystemV】第一篇 C++ 实现人物移动_第17张图片

  • (N)Locomotion Cycles :

输出循环动画
在这里插入图片描述
将循环动画放入循环混合动画中,
【AdvancedLocomotionSystemV】第一篇 C++ 实现人物移动_第18张图片

  • (N)Locomotion Detail:

控制运动的细节
在这里插入图片描述
【AdvancedLocomotionSystemV】第一篇 C++ 实现人物移动_第19张图片

  • (N)Locomotion States:

控制运动的状态
【AdvancedLocomotionSystemV】第一篇 C++ 实现人物移动_第20张图片
主输出动画:
【AdvancedLocomotionSystemV】第一篇 C++ 实现人物移动_第21张图片
在角色蓝图中把该动画蓝图加入其中。
【AdvancedLocomotionSystemV】第一篇 C++ 实现人物移动_第22张图片

创建摄像机管理器

逻辑很简单,就是玩家控制器调用摄像机管理器的OnPossess并传递Pawn参数,摄像机管理器将Pawn类指针保存在自己的类中,在它子类蓝图中根据Pawn类的位置设置摄像机的位置。

创建继承PlayerCameraManager类的ALS_CameraManager
.h

#pragma once

#include "CoreMinimal.h"
#include "Camera/PlayerCameraManager.h"
#include "ALS_CameraManager.generated.h"

/**
 * 
 */
UCLASS()
class ALSVLEARNDEMO_API AALS_CameraManager : public APlayerCameraManager
{
	GENERATED_BODY()

public:
	UPROPERTY(BlueprintReadWrite)
	APawn* ControllerPawn;
	
	UFUNCTION(BlueprintCallable)
	void OnPossess(APawn* NewPawn);
};

.cpp

#include "ALS_CameraManager.h"

void AALS_CameraManager::OnPossess(APawn* NewPawn)
{
	ControllerPawn = NewPawn;
}

继承该类创建一个蓝图类:
在这里插入图片描述
【AdvancedLocomotionSystemV】第一篇 C++ 实现人物移动_第23张图片

创建一个玩家控制器:ALS_Player_Controller

.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "ALS_Player_Controller.generated.h"

/**
 * 
 */
UCLASS()
class ALSVLEARNDEMO_API AALS_Player_Controller : public APlayerController
{
	GENERATED_BODY()
	public:
	virtual void OnPossess(APawn* InPawn) override;
};

.cpp

#include "ALS_Player_Controller.h"
#include "ALSVLearnDemo/CameraSystem/ALS_CameraManager.h"

void AALS_Player_Controller::OnPossess(APawn* InPawn)
{
	Super::OnPossess(InPawn);
	AALS_CameraManager* CameraManager = Cast<AALS_CameraManager>(PlayerCameraManager);
	if (CameraManager)
	{
		CameraManager->OnPossess(InPawn);
	}
}

创建一个游戏模式蓝图

继承项目自带的游戏模式,打开世界设置:
【AdvancedLocomotionSystemV】第一篇 C++ 实现人物移动_第24张图片
运行就OK啦!

你可能感兴趣的:(#,高级运动系统,c++,UE4,高级运动系统)