AdvancedLocomotionSystemV
先创建一个第三人称的新手工程项目。
打开AdvancedLocomotionSystemV项目,将一些动画资源导入到我们项目的Content的内容下。
还有摄影机骨骼和模型也要。
打开项目的文件夹,将输入配置复制替换到我们的文件中。
由于第三人称的模板已经给我们了一些代码,所以直接用他的类就好了。
打开第三人称角色模板C++文件,将一些不需要的东西删除,其中最主要的就是将摄像机给删除了,添加了一些保存人物状态的变量。
为什么要将摄像机给删除了?
因为当前这个项目的摄像头是跟着胶囊体移动的,如果我们要进行第一人称第三人称视角替换的话就需要多个摄像头,并且如果我们想要摄像机跟着角色状态变化的话就有点麻烦。所以就直接另外弄一个摄像机进行统一管理。
以下是我删好了的代码:
#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();
};
#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
。
将角色导入到其中,调整到角色朝前的位置。
创建一个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:
跑步的时候运动曲线为2:
冲刺的时候运动曲线为3:
将曲线变量进行赋值。
跑步曲线思路是一样的,相对来说没有走路曲线那么抖,体现出一种加速的感觉。
创建继承动画骨骼,C++动画实例的动画蓝图。
点击创建动画层
命名为:(N)CycleBlending
BaseLayer
控制运动的状态
主输出动画:
在角色蓝图中把该动画蓝图加入其中。
逻辑很简单,就是玩家控制器调用摄像机管理器的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;
}
创建一个玩家控制器: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);
}
}