UI与角色行为制作(一)
定义体力值机制
- 我们需要在
SoulBaseCharacterl
类中定义角色的各项机制变量,例如体力值、最大体力值、消耗体力值、增长体力值
//角色机制
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Attribute")
float Stamina;//体力值
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Attribute")
float MaxStamina;//最大体力值
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Attribute")
float MeleeAttackSubStamina;//攻击消耗体力值
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Attribute")
float IncreaseStamina;//增长体力值
MaxStamina = 100.f;
Stamina = MaxStamina;
MeleeAttackSubStamina = 10.f;
IncreaseStamina = 1.f;
- 这些机制值定义好了后,就开始约束写的逻辑,在角色类
PlayerCharacter
类中CanMeleeAttack()是否能攻击函数,应该加上条件,就是体力值大于等于消耗的体力值时的状态下才能攻击
bool APlayerCharacter::CanMeleeAttack()
{
if (PlayerBehavior == EPlayerBehavior::EPB_IDLE && Stamina >= MeleeAttackSubStamina)
{
return true;
}
return false;
}
- 然后在拳法攻击中,每次攻击就得减少体力
void APlayerCharacter::MeleeAttack()
{
if (CanMeleeAttack())
{
UAnimInstance* CurAnimInstance = GetMesh()->GetAnimInstance();
if (CurAnimInstance)
{
//将状态改变为攻击状态
PlayerBehavior = EPlayerBehavior::EPB_ATTACK;
//将状态改变为备战状态
MeleeState = EMeleeState::EMS_PREPARE;
MeleeBehaviorStateWarToCommon = 10.f;//每次攻击后重新置为10秒计时
//每次攻击都得减去体力值
Stamina -= MeleeAttackSubStamina;
//播放随机动画
int32 AttackAnimIndex = UKismetMathLibrary::RandomIntegerInRange(0, MeleeAttackAnim.Num() - 1);
if (AttackAnimIndex != LastMeleeIndex)
{
LastMeleeIndex = AttackAnimIndex;//更新上一个动作
CurAnimInstance->Montage_Play(MeleeAttackAnim[AttackAnimIndex]);
}
else
{
if (AttackAnimIndex == 0)
{
int32 AddIndexNum = UKismetMathLibrary::RandomIntegerInRange(0, MeleeAttackAnim.Num() - 2);
AttackAnimIndex += AddIndexNum;
LastMeleeIndex = AttackAnimIndex;
CurAnimInstance->Montage_Play(MeleeAttackAnim[AttackAnimIndex]);
}
else
{
AttackAnimIndex--;
LastMeleeIndex = AttackAnimIndex;
CurAnimInstance->Montage_Play(MeleeAttackAnim[AttackAnimIndex]);
}
}
}
}
}
- 然后在Tick中抒写体力值增加逻辑,当角色体力值小于100时并且角色没有进行攻击与防御,角色体力值才能慢慢恢复
void APlayerCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
//角色非攻击防御状态,体力小于100的时候才能开始恢复状态
if (Stamina < 100.f && PlayerBehavior != EPlayerBehavior::EPB_ATTACK && PlayerBehavior != EPlayerBehavior::EPB_DEFENCE)
{
Stamina += DeltaTime * IncreaseStamina;
GEngine->AddOnScreenDebugMessage(-1, 10, FColor::Red,
FString::Printf(TEXT("Stamina:%f"), Stamina));
}
//记录攻击倒计时,10秒之后无攻击动作才能解除备战状态
if (MeleeState == EMeleeState::EMS_PREPARE && PlayerBehavior != EPlayerBehavior::EPB_ATTACK)
{
MeleeBehaviorStateWarToCommon -= DeltaTime;
/*
GEngine->AddOnScreenDebugMessage(-1, 10, FColor::Red,
FString::Printf(TEXT("MeleeBehaviorStateWarToCommon:%f"), MeleeBehaviorStateWarToCommon));
*/
if (MeleeBehaviorStateWarToCommon <= 0)
{
MeleeState = EMeleeState::EMS_COMMON;
MeleeBehaviorStateWarToCommon = 10.f;
}
}
}
体力值UI控件配置
- 新建一个HUD类,用于加载到GameMode里注册,然后将UI在HUD里进行加载创建
- 然后创建一个主UI的基类,然后其他的UI继承这个基类进行创建
- 然后在这个基类上创建子类战斗的UI界面
- 在
UI_FightMain
类中编写组件 meta = (BindWidget)
:BindWidget是用于将C++类与UMG(Unreal Motion Graphics)界面蓝图中的UI小部件进行绑定的一种机制
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "DarkSoulUserWidgetRule.h"
#include "UI_FightMain.generated.h"
/**
*
*/
UCLASS()
class DARKSOULSGAME_API UUI_FightMain : public UDarkSoulUserWidgetRule
{
GENERATED_BODY()
public:
//UI
UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
class UProgressBar* StaminaBar;//体力条
UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
UProgressBar* PlayerHPBar;//血条
UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
class UTextBlock* Text_HP;//体力文本
UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
UTextBlock* Text_Stamina;//体力文本
};
- 然后建立这个UI_FightMain的用户组件进行编辑UI界面
- 因为进行了绑定,所以要创建和你C++编写名称一样的组件
体力值数据接入UI表现
- 在
SoulBaseCharacterl
类中添加两个共有接口,获取自身的体力与最大体力,方便在UI控件蓝图中去调用使用
UFUNCTION(BlueprintCallable,BlueprintPure)
float GetCurStamina();//获取体力
UFUNCTION(BlueprintCallable, BlueprintPure)
float GetMaxStamina();//获取最大体力
float ASoulBaseCharacter::GetCurStamina()
{
return Stamina;
}
float ASoulBaseCharacter::GetMaxStamina()
{
return MaxStamina;
}
- 然后在GameMode里面注册我们新建的HUD这个类
// Copyright Epic Games, Inc. All Rights Reserved.
#include "DarkSoulsGameGameModeBase.h"
#include "DarkSoulPlayerController.h"
#include "Characters/SoulBaseCharacter.h"
#include "DarkSoulHUD.h"
ADarkSoulsGameGameModeBase::ADarkSoulsGameGameModeBase()
{
//加载PlayerCharacter的蓝图
static ConstructorHelpers::FClassFinder<ASoulBaseCharacter>BPPlayerClass(TEXT("/Game/_Game/BP/BP_PlayerCharacter"));
if (BPPlayerClass.Class)
{
//设置默认Pawn类为PlayerCharacter蓝图
DefaultPawnClass = BPPlayerClass.Class;
}
//注册控制器
PlayerControllerClass = ADarkSoulPlayerController::StaticClass();
//注册HUD
HUDClass = ADarkSoulHUD::StaticClass();
}
- 在
DarkSoulHUD
类中添加我们的UI界面 - 新建一个存储主界面UI的模版与指针,重写BeginPlay函数
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/HUD.h"
#include "DarkSoulHUD.generated.h"
/**
*
*/
UCLASS()
class DARKSOULSGAME_API ADarkSoulHUD : public AHUD
{
GENERATED_BODY()
public:
ADarkSoulHUD();
//存储主界面UI的模版
TSubclassOf<class UUI_FightMain> FightMainUIClass;
//主界面UI的类指针
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "MainUI")
class UUI_FightMain* FightMainUI;
protected:
virtual void BeginPlay() override;
};
- 添加UI在视口的逻辑
// Fill out your copyright notice in the Description page of Project Settings.
#include "DarkSoulHUD.h"
#include "UI/UI_FightMain.h"
ADarkSoulHUD::ADarkSoulHUD()
{
static ConstructorHelpers::FClassFinder<UUI_FightMain> UI_FightMain(TEXT("/Game/_Game/BP/UI/BP_UI_FightMian"));
FightMainUIClass = UI_FightMain.Class;
}
void ADarkSoulHUD::BeginPlay()
{
//创建UI界面
FightMainUI = CreateWidget<UUI_FightMain>(GetWorld(), FightMainUIClass);
if (FightMainUI)
{
//添加到视口
FightMainUI->AddToViewport(0);
}
}
- 然后去控件蓝图UI中ProgressBar组件绑定函数,编写显示逻辑
摄像机震动反馈
- 我们在
SoulBaseCharacter
类中建立一个带BlueprintImplementableEvent
宏的摄像机震动函数,我们在蓝图中去实现这个功能
//摄像机震动反馈
UFUNCTION(BlueprintImplementableEvent)
void CameraShakeFeedBack();
- 然后在
PlayerCharacter
类中攻击函数中调用这个方法
void APlayerCharacter::MeleeAttack()
{
if (CanMeleeAttack())
{
UAnimInstance* CurAnimInstance = GetMesh()->GetAnimInstance();
if (CurAnimInstance)
{
//将状态改变为攻击状态
PlayerBehavior = EPlayerBehavior::EPB_ATTACK;
//将状态改变为备战状态
MeleeState = EMeleeState::EMS_PREPARE;
MeleeBehaviorStateWarToCommon = 10.f;//每次攻击后重新置为10秒计时
//每次攻击都得减去体力值
Stamina -= MeleeAttackSubStamina;
//摄像机震动反馈
CameraShakeFeedBack();
//播放随机动画
int32 AttackAnimIndex = UKismetMathLibrary::RandomIntegerInRange(0, MeleeAttackAnim.Num() - 1);
if (AttackAnimIndex != LastMeleeIndex)
{
LastMeleeIndex = AttackAnimIndex;//更新上一个动作
CurAnimInstance->Montage_Play(MeleeAttackAnim[AttackAnimIndex]);
}
else
{
if (AttackAnimIndex == 0)
{
int32 AddIndexNum = UKismetMathLibrary::RandomIntegerInRange(0, MeleeAttackAnim.Num() - 2);
AttackAnimIndex += AddIndexNum;
LastMeleeIndex = AttackAnimIndex;
CurAnimInstance->Montage_Play(MeleeAttackAnim[AttackAnimIndex]);
}
else
{
AttackAnimIndex--;
LastMeleeIndex = AttackAnimIndex;
CurAnimInstance->Montage_Play(MeleeAttackAnim[AttackAnimIndex]);
}
}
}
}
}
- 新建一个LegacyCameraShake蓝图,用来设置自己需要的震动参数
- 在角色蓝图中编写逻辑,使用Controller的Client Start Camera Shake节点播放震动效果
体力值不足提示
- 需求:当我们体力值不够时,将提示一秒钟的体力值不足文本
- 逻辑:编写UI控件蓝图提示文本,然后在基类角色中获取Controller中的HUD中的我们FightMain主UI,在是否可以攻击函数里面设置隐藏与显示方法函数,隐藏一秒函数方法可以使用定时器
- 编写UI控件蓝图提示文本与
UI_FightMain
中的提示文本的创建
UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
UTextBlock* Text_Stamina_Exhausted;//体力提示文本
- 在
UI_FightMain
中编写时间句柄和显示与隐藏函数逻辑
FTimerHandle ShowStaminaTimerHandle;//时间句柄
//函数方法
void ShowStaminaText();//显示文本提示
void HiddenStaminaText();//隐藏文本提示
- 逻辑:使用UTextBlock组件库函数方法,要添加头文件
#include "UMG/Public/Components/TextBlock.h"
// Fill out your copyright notice in the Description page of Project Settings.
#include "UI_FightMain.h"
#include "UMG/Public/Components/TextBlock.h"
void UUI_FightMain::ShowStaminaText()
{
Text_Stamina_Exhausted->SetVisibility(ESlateVisibility::Visible);//显示控件文本
//显示一秒钟后隐藏
GetWorld()->GetTimerManager().SetTimer(ShowStaminaTimerHandle, this, &UUI_FightMain::HiddenStaminaText, 1.f, false, 1.f);
}
void UUI_FightMain::HiddenStaminaText()
{
Text_Stamina_Exhausted->SetVisibility(ESlateVisibility::Hidden);//隐藏文本
GetWorld()->GetTimerManager().ClearTimer(ShowStaminaTimerHandle);//删除时间句柄释放资源
}
- 在
DarkSoulHUD
类中建一个可以获取到主界面UI的类指针函数方法,角色基类SoulBaseCharacter
类中去获取获取Controller中的HUD中的我们FightMain主UI - 在
DarkSoulHUD
类中建一个可以获取到主界面UI的类指针函数方法
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/HUD.h"
#include "DarkSoulHUD.generated.h"
/**
*
*/
UCLASS()
class DARKSOULSGAME_API ADarkSoulHUD : public AHUD
{
GENERATED_BODY()
public:
ADarkSoulHUD();
//存储主界面UI的模版
TSubclassOf<class UUI_FightMain> FightMainUIClass;
//主界面UI的类指针
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "MainUI")
class UUI_FightMain* FightMainUI;
protected:
virtual void BeginPlay() override;
public:
//获取主界面UI的类指针函数方法
inline UUI_FightMain* GetFightMainUI() { return FightMainUI; };
};
- 角色基类
SoulBaseCharacter
类中去获取获取Controller中的HUD中的我们FightMain主UI,显示提示文本
//获取UI并显示提示文本
void ShowStaminaNotEnoughText();
void ASoulBaseCharacter::ShowStaminaNotEnoughText()
{
//获取到Controller
ADarkSoulPlayerController* PC = Cast<ADarkSoulPlayerController>(Controller);
if (Controller)
{
//获取到HUD
ADarkSoulHUD* HUD = Cast<ADarkSoulHUD>(PC->GetHUD());
if (HUD)
{
//显示提示文本
HUD->GetFightMainUI()->ShowStaminaText();
}
}
}
- 最后在
PlayerCharacter
类中是否攻击函数中去调用ShowStaminaNotEnoughText()
方法
bool APlayerCharacter::CanMeleeAttack()
{
if (PlayerBehavior == EPlayerBehavior::EPB_IDLE && Stamina >= MeleeAttackSubStamina)
{
return true;
}
else if (Stamina < MeleeAttackSubStamina)
{
ShowStaminaNotEnoughText();
}
return false;
}
- 最后将文本开始设置为隐藏状态
人物攻击转向手感调优
- 思路:获取方向键的最后一个按键,获取它的轴值进行转向
- 在
SoulBaseCharacter
类中新建一个FRotator变量记录攻击时转向的目标值
//攻击时转向的目标值
FRotator DesiredRotation;
DesiredRotation = FRotator(0., 0., 0.);
- 然后新建一个计算按键的方向的函数方法
FRotator CalculateRotation();
- 在
Tick
中实时计算攻击转向的目标值
// Called every frame
void ASoulBaseCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
//实时计算攻击转向的目标值
DesiredRotation = CalculateRotation();
}
- 计算按键方向的函数方法逻辑
FRotator ASoulBaseCharacter::CalculateRotation()
{
//获取最后一次输入的向量
FVector LastVector = GetCharacterMovement()->GetLastInputVector();
//判断最后一次输入向量是否为0,如果是就返回这个目标值,如果不为0就返回这个Vector转换为Rotator的目标值
if (LastVector != FVector(0,0,0))
{
return UKismetMathLibrary::MakeRotFromX(LastVector);
}
else
{
return DesiredRotation;
}
}
- 然后新建一个线性插入旋转的函数方法,这个方法要在蓝图里面进行调用通知动画的
//线性插入旋转值
UFUNCTION(BlueprintCallable)
void RInterpRotation();
- 方法逻辑
void ASoulBaseCharacter::RInterpRotation()
{
//实时进行转向插值
FRotator RInterpRotation = UKismetMathLibrary::RInterpTo(GetActorRotation(), DesiredRotation,
GetWorld()->GetDeltaSeconds(), 5.f);
SetActorRotation(FRotator(0., RInterpRotation.Yaw, 0.));
}
- 在虚幻中建立一个Anim Notify State蓝图
- 实时计算转向目标值,进行转向
- 然后在蒙太奇中添加一个轨道,添加这个动画通知状态,这样就可以攻击时可以转向攻击
- 将Pawn Yaw旋转取掉,角色移动旋转勾上,也可以在代码中进行硬编码
// Sets default values
ASoulBaseCharacter::ASoulBaseCharacter()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
SpringArm->SetupAttachment(GetRootComponent());
SpringArm->TargetArmLength = 300.f;
SpringArm->bUsePawnControlRotation = true;
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(SpringArm);
//默认是不奔跑的
bIsRun = false;
WeaponType = EWeaponType::EWT_MELEE;//拳击
MeleeState = EMeleeState::EMS_COMMON;//普通状态
PlayerBehavior = EPlayerBehavior::EPB_IDLE;//空闲状态
MaxStamina = 100.f;
Stamina = MaxStamina;
MeleeAttackSubStamina = 10.f;
IncreaseStamina = 1.f;
//攻击转向默认值为0
DesiredRotation = FRotator(0., 0., 0.);
bUseControllerRotationPitch = false;
bUseControllerRotationRoll = false;
bUseControllerRotationYaw = false;
GetCharacterMovement()->bOrientRotationToMovement = true;
}
设置相机弹簧臂滞后感
- 自己设置参数即可
人物近战翻滚逻辑
逻辑(一)
- 在
SoulBaseCharacter
类中绑定翻滚的按键操作,定义翻滚虚函数方法方便继承,新建每次翻滚要减少的体力值变量赋初值
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
UInputAction* RollingAction;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Attribute")
float MeleeRollingSubStamina;//翻滚消耗体力值
MeleeRollingSubStamina = 15.f;
//攻击
virtual void Attack();
//翻滚
virtual void Rolling();
- 在
PlayerCharacte
类中去重写翻滚函数方法
//重写Attack
virtual void Attack() override;
//重写Rolling
virtual void Rolling() override;
- 在新建两个函数方法来决定是什么翻滚,因为有持剑翻滚与普通翻滚,新建一个是否可以翻滚的状态函数方法处理
void MeleeRolling();
void SwordRolling();
bool CanMeleeRolling();
- 在
PlayerCharacter
中绑定翻滚函数操作
void APlayerCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent);
if (EnhancedInputComponent)
{
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &ASoulBaseCharacter::Move);
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &ASoulBaseCharacter::Look);
EnhancedInputComponent->BindAction(RunAction, ETriggerEvent::Triggered, this, &ASoulBaseCharacter::Run);
EnhancedInputComponent->BindAction(RunAction, ETriggerEvent::Completed, this, &ASoulBaseCharacter::StopRun);
EnhancedInputComponent->BindAction(AttackAction, ETriggerEvent::Started, this, &ASoulBaseCharacter::Attack);
EnhancedInputComponent->BindAction(RollingAction, ETriggerEvent::Started, this, &ASoulBaseCharacter::Rolling);
}
}
- 实现是否可以翻滚的状态函数方法处理逻辑
bool APlayerCharacter::CanMeleeRolling()
{
if (PlayerBehavior == EPlayerBehavior::EPB_IDLE && Stamina >= MeleeRollingSubStamina)
{
return true;
}
else if (Stamina < MeleeRollingSubStamina)
{
ShowStaminaNotEnoughText();
}
return false;
}
- 翻滚逻辑:判断是什么类型的翻滚,就执行那个类型的翻滚函数
void APlayerCharacter::Rolling()
{
switch (WeaponType)
{
case EWeaponType::EWT_MELEE:
MeleeRolling();
break;
case EWeaponType::EWT_SWORD:
SwordRolling();
break;
}
}
- 定义存储翻滚的蒙太奇数组
//近战翻滚蒙太奇
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "AnimMontage", meta = (AllowPrivateAccess = "true"))
TArray<UAnimMontage*> MeleeRollingAnim;
- 翻滚的逻辑需求:在
SoulBaseCharacter
类中新建两个变量用来获取用户输入的前后左右,因为翻滚是有方法的,并初始化
//翻滚的朝向值(前后)
int32 RollingForwardValue;
//翻滚的朝向值(左右)
int32 RollingRightValue;
//翻滚方向值默认值为0
RollingForwardValue = 0.0;
RollingRightValue = 0.0;
逻辑(二)
- 我们在
SoulBaseCharacter
类的移动函数中获取到方向的输入按键,新建一个变量来控制到时候翻滚蒙太奇播放的速率
//翻滚蒙太奇播放速率
float RollingAnimPlayRate;
//翻滚动画播放速率
RollingAnimPlayRate = 1.0;
void ASoulBaseCharacter::Move(const FInputActionValue& Value)
{
FVector2D MoveVector = Value.Get<FVector2D>();
//回获取翻滚的方向值
RollingForwardValue = MoveVector.Y;
RollingRightValue = MoveVector.X;
if (Controller)
{
FRotator Rotation = Controller->GetControlRotation();
FRotator YawRotation = FRotator(0.f, Rotation.Yaw, 0.f);
FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
AddMovementInput(ForwardDirection, MoveVector.Y);
AddMovementInput(RightDirection, MoveVector.X);
}
}
- 然后去
PlayerCharacter
类中MeleeRolling方法中实现翻滚逻辑,
void APlayerCharacter::MeleeRolling()
{
if (CanMeleeRolling())
{
UAnimInstance* CurAnimInstance = GetMesh()->GetAnimInstance();
if (CurAnimInstance)
{
//每次翻滚减去体力
Stamina -= MeleeRollingSubStamina;
PlayerBehavior = EPlayerBehavior::EPB_ROLLING;
if (RollingForwardValue == 1)
{
CurAnimInstance->Montage_Play(MeleeRollingAnim[0], RollingAnimPlayRate);
}
else if (RollingForwardValue == -1)
{
CurAnimInstance->Montage_Play(MeleeRollingAnim[1], RollingAnimPlayRate);
}
else if (RollingRightValue == 1)
{
CurAnimInstance->Montage_Play(MeleeRollingAnim[2], RollingAnimPlayRate);
}
else if (RollingRightValue == -1)
{
CurAnimInstance->Montage_Play(MeleeRollingAnim[3], RollingAnimPlayRate);
}
else
{
CurAnimInstance->Montage_Play(MeleeRollingAnim[0], RollingAnimPlayRate);
}
}
}
}
在编辑器里面配置人物近战翻滚动画
-
绑定输入操作到人物蓝图
-
给翻滚动画全部创建蒙太奇,插槽换成之前的攻击插槽,然后添加动画通知,在动画蓝图中要重置状态的
-
然后将这些蒙太奇添加到人物蓝图的数组中
-
最后在动画蓝图事件中通知重置动画状态
-
把角色使用控制器所需旋转关掉,操作会比较好一点
人物翻跟头行为配置
- 首先在
SoulBaseCharacte
类中新增按键操作输入
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
UInputAction* SlideAction;
- 定义一个方法,近战是翻跟头,持剑状态是翻滚滑行
//攻击
virtual void Attack();
//翻滚
virtual void Rolling();
//滑行(近战:翻跟头/持剑:滑行翻滚)
virtual void Slide();
- 然后在
PlayerCharacter
类中重写滑行方法,然后添加函数分为持剑滑行与近战滑行,与判断是否近战滑行的函数方法
//重写Slide
virtual void Slide() override;
void MeleeSlide();
void SwordSlide();
bool CanMeleeSlide();
- Slide滑行逻辑
void APlayerCharacter::Slide()
{
switch (WeaponType)
{
case EWeaponType::EWT_MELEE:
MeleeSlide();
break;
case EWeaponType::EWT_SWORD:
SwordSlide();
break;
}
}
- CanMeleeSlide方法逻辑
bool APlayerCharacter::CanMeleeSlide()
{
if (PlayerBehavior == EPlayerBehavior::EPB_IDLE && Stamina >= MeleeRollingSubStamina)
{
return true;
}
else if (Stamina < MeleeRollingSubStamina)
{
ShowStaminaNotEnoughText();
}
return false;
}
- 新建一个存储蒙太奇的数组存放滑行蒙太奇
//近战滑行蒙太奇
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "AnimMontage", meta = (AllowPrivateAccess = "true"))
TArray<UAnimMontage*> MeleeSlideAnim;
- 近战滑行的逻辑与近战翻滚基本一样
void APlayerCharacter::MeleeSlide()
{
if (CanMeleeSlide())
{
UAnimInstance* CurAnimInstance = GetMesh()->GetAnimInstance();
if (CurAnimInstance)
{
//每次翻滚减去体力
Stamina -= MeleeRollingSubStamina;
PlayerBehavior = EPlayerBehavior::EPB_ROLLING;
if (RollingForwardValue == 1)
{
CurAnimInstance->Montage_Play(MeleeSlideAnim[0], RollingAnimPlayRate);
}
else if (RollingForwardValue == -1)
{
CurAnimInstance->Montage_Play(MeleeSlideAnim[1], RollingAnimPlayRate);
}
else if (RollingRightValue == 1)
{
CurAnimInstance->Montage_Play(MeleeSlideAnim[2], RollingAnimPlayRate);
}
else if (RollingRightValue == -1)
{
CurAnimInstance->Montage_Play(MeleeSlideAnim[3], RollingAnimPlayRate);
}
else
{
CurAnimInstance->Montage_Play(MeleeSlideAnim[0], RollingAnimPlayRate);
}
}
}
}
- 在
PlayerCharacter
中绑定滑行函数操作,虚幻中新建输入操作绑定到映射,配置到角色蓝图中
void APlayerCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent);
if (EnhancedInputComponent)
{
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &ASoulBaseCharacter::Move);
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &ASoulBaseCharacter::Look);
EnhancedInputComponent->BindAction(RunAction, ETriggerEvent::Triggered, this, &ASoulBaseCharacter::Run);
EnhancedInputComponent->BindAction(RunAction, ETriggerEvent::Completed, this, &ASoulBaseCharacter::StopRun);
EnhancedInputComponent->BindAction(AttackAction, ETriggerEvent::Started, this, &ASoulBaseCharacter::Attack);
EnhancedInputComponent->BindAction(RollingAction, ETriggerEvent::Started, this, &ASoulBaseCharacter::Rolling);
EnhancedInputComponent->BindAction(SlideAction, ETriggerEvent::Started, this, &ASoulBaseCharacter::Slide);
}
}
- 然后配置滑行的蒙太奇,添加重置通知切换插槽
- 在角色蓝图上添加上蒙太奇动画,然后去动画蓝图中接收通知重置动画状态
- 聚焦输入模式到游戏,这样就不用每次要点击游戏才能进行输入