UEC++ 虚幻5 “类魂”游戏笔记(三)

该博客围绕UE5游戏中UI与角色行为制作展开,详细介绍了将刀剑绑定到角色身上、武器切换逻辑、人物持剑动画混合空间配置、持剑攻击逻辑及优化、攻击动画蒙太奇配置、刀风效果设置、持剑翻滚与滑行逻辑等内容,还涉及相关类文件的编写。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

UI与角色行为制作(二)

将刀剑绑定到角色身上

  • 在角色骨骼添加两个插槽用来放剑与剑鞘
    在这里插入图片描述
    在这里插入图片描述
  • PlayerCharacte类中创建剑鞘的静态网格与剑的骨骼组件
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
	class UStaticMeshComponent* ScabbardMesh;

	UPROPERTY(EditDefaultSOnly, BlueprintReadOnly, Category = "Weapon")
	class USkeletalMeshComponent* Sword;
  • 将组件添加到插槽
APlayerCharacter::APlayerCharacter()
{
	ScabbardMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ScabbardMesh"));
	ScabbardMesh->SetupAttachment(GetMesh(), "Scabbard");

	Sword = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Sword"));
	Sword->SetupAttachment(GetMesh(), "Sword");

	LastMeleeIndex = 0;
	MeleeBehaviorStateWarToCommon = 10.;//默认十秒
}
  • 剑鞘骨骼网格体导出静态网格体
    在这里插入图片描述

  • 然后在角色蓝图中进行添加剑与剑鞘模型
    在这里插入图片描述
    在这里插入图片描述

武器切换逻辑

  • 调节好右手持剑的位置
    在这里插入图片描述
  • SoulBaseCharacte类中添加输入操作换武器,添加虚函数武器切换方法
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
UInputAction* ChangeWeaponAction;

	//攻击
	virtual void Attack();
	//翻滚
	virtual void Rolling();
	//滑行(近战:翻跟头/持剑:滑行翻滚)
	virtual void Slide();
	//切换武器
	virtual void ChangeWeapon();
  • 在子类PlayerCharacte类中重写切换武器方法
	//重写Attack
	virtual void Attack() override;
	//重写Rolling
	virtual void Rolling() override;
	//重写Slide
	virtual void Slide() override;
	//重写ChangeWeapon
	virtual void ChangeWeapon() override;
  • 绑定输入操作映射
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, &APlayerCharacter::Attack);
		EnhancedInputComponent->BindAction(RollingAction, ETriggerEvent::Started, this, &APlayerCharacter::Rolling);
		EnhancedInputComponent->BindAction(SlideAction, ETriggerEvent::Started, this, &APlayerCharacter::Slide);
		EnhancedInputComponent->BindAction(ChangeWeaponAction, ETriggerEvent::Started, this, &APlayerCharacter::ChangeWeapon);
	
	}
}
  • 添加逻辑bool函数,当前是否能切换武器
	bool CanChangeWeapon();
  • 是否能切换武器逻辑
bool APlayerCharacter::CanChangeWeapon()
{
	if (PlayerBehavior == EPlayerBehavior::EPB_IDLE)
	{
		return true;
	}
	return false;
}
  • 然后我们需要标记装备武器的状态,在SoulBaseCharacter类中添加一个bool变量来表示是否当前状态有装备武器, 默认为false
	//是否装备武器
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attack")
	bool bEquipWeapon;

bEquipWeapon = false;
  • 重写切换武器函数逻辑
void APlayerCharacter::ChangeWeapon()
{
	if (CanChangeWeapon())
	{
		if (WeaponType == EWeaponType::EWT_MELEE)
		{
			//改为持剑状态
			WeaponType = EWeaponType::EWT_SWORD;
			//将武器添加到右手
			Sword->AttachToComponent(GetMesh(), FAttachmentTransformRules::KeepRelativeTransform, "WEAPON_R");
			bEquipWeapon = true;
		}
		else if (WeaponType == EWeaponType::EWT_SWORD)
		{
			//改为普通状态
			WeaponType = EWeaponType::EWT_MELEE;
			//将武器放回剑鞘
			Sword->AttachToComponent(GetMesh(), FAttachmentTransformRules::KeepRelativeTransform, "Sword");
			bEquipWeapon = false;
		}
	}
}
  • 虚幻中添加输入操作并且绑定到映射,添加到角色蓝图上
    在这里插入图片描述
    在这里插入图片描述

配置人物持剑动画混合空间

  • 配置持剑动画资产,创建动画混合空间
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 然后在动画蓝图开始配置逻辑
  • 获取到是否准备武器状态bool变量,到时候切换状态的条件值
    在这里插入图片描述
  • 新建持剑的动画状态机
    在这里插入图片描述
  • 切换逻辑就是那个bool变量值
    在这里插入图片描述
    在这里插入图片描述
  • 添加动画混合空间
    在这里插入图片描述
  • 战备状态的动画也可以像持剑动画转换,转换条件是是否持剑
    在这里插入图片描述
    在这里插入图片描述
  • 微调持剑状态穿模,也可以设置一下平滑速度
    在这里插入图片描述
    在这里插入图片描述

人物持剑攻击逻辑

  • 人物持剑攻击逻辑与普通拳法攻击逻辑是差不多的
  • PlayerCharacte类中定义判断是否能持剑攻击的函数方法
	//是否可以持剑攻击
	bool CanSwodAttack();
  • SoulBaseCharacte类中新建持剑攻击衰减值变量并初始化
	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Attribute")
	float SwordAttackSubStamina;//持剑攻击消耗体力值

SwordAttackSubStamina= 12.f;
  • PlayerCharacter类中CanSwordAttack方法逻辑
bool APlayerCharacter::CanSwodAttack()
{

	if (PlayerBehavior == EPlayerBehavior::EPB_IDLE && Stamina >= SwordAttackSubStamina)
	{
		return true;
	}
	else if (Stamina < SwordAttackSubStamina)
	{
		ShowStaminaNotEnoughText();
	}
	return false;
}
  • 添加持剑攻击蒙太奇数组,用来存储持剑攻击的蒙太奇
	//近战攻击蒙太奇
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "AnimMontage", meta = (AllowPrivateAccess = "true"))
	TArray<UAnimMontage*> MeleeAttackAnim;
	//近战翻滚蒙太奇
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "AnimMontage", meta = (AllowPrivateAccess = "true"))
	TArray<UAnimMontage*> MeleeRollingAnim;
	//近战滑行蒙太奇
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "AnimMontage", meta = (AllowPrivateAccess = "true"))
	TArray<UAnimMontage*> MeleeSlideAnim;
	//持剑攻击蒙太奇
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "AnimMontage", meta = (AllowPrivateAccess = "true"))
	TArray<UAnimMontage*> SwordAttackAnim;
  • SwordAttack方法逻辑
void APlayerCharacter::SwordAttack()
{
	if (CanSwodAttack())
	{
		UAnimInstance* CurAnimInstance = GetMesh()->GetAnimInstance();
		if (CurAnimInstance)
		{
			//将状态改变为攻击状态
			PlayerBehavior = EPlayerBehavior::EPB_ATTACK;
			Stamina -= SwordAttackSubStamina;
			//播放随机动画
			int32 AttackAnimIndex = UKismetMathLibrary::RandomIntegerInRange(0, SwordAttackAnim.Num() - 1);
			if (AttackAnimIndex != LastMeleeIndex)
			{
				LastMeleeIndex = AttackAnimIndex;
				CurAnimInstance->Montage_Play(SwordAttackAnim[AttackAnimIndex]);
			}
			else
			{
				if (AttackAnimIndex == 0)
				{
					int32 AddIndexNum = UKismetMathLibrary::RandomIntegerInRange(0, SwordAttackAnim.Num() - 2);
					AttackAnimIndex += AddIndexNum;
					LastMeleeIndex = AttackAnimIndex;
					CurAnimInstance->Montage_Play(SwordAttackAnim[AttackAnimIndex]);
				}
				else
				{
					AttackAnimIndex--;
					LastMeleeIndex = AttackAnimIndex;
					CurAnimInstance->Montage_Play(SwordAttackAnim[AttackAnimIndex]);
				}
			}
		}
	}
}

优化人物持剑逻辑

  • 人物持剑与拳法随机攻击是一样的,可以在SoulBaseCharacte类中建立一个函数将公共逻辑包含进去,这样只调用函数即可,优化代码
  • 新建一个随机攻击函数方法,然后将子类中的LastAttackAnimIndex拿到基类SoulBaseCharacte中来,并且新增一个记录上一个持剑攻击动作的标志变量
	//记录上一个拳击攻击动作的标志变量
	int32 LastMeleeIndex;
	//记录上一个持剑攻击动作的标志变量
	int32 LastSwordIndex;

//记录上一个攻击动作的标志变量
LastMeleeIndex = 0;
LastSwordIndex = 0;
//-------------------------------------------------
	//随机攻击算法
	void RandomAttack(TArray<UAnimMontage*> NeedAnim, int32& LastAttackAnimIndex);
  • 随机攻击算法逻辑
void ASoulBaseCharacter::RandomAttack(TArray<UAnimMontage*> NeedAnim, int32& LastAttackAnimIndex)
{
	UAnimInstance* CurAnimInstance = GetMesh()->GetAnimInstance();
	if (CurAnimInstance)
	{
		//播放随机动画
		int32 AttackAnimIndex = UKismetMathLibrary::RandomIntegerInRange(0, NeedAnim.Num() - 1);
		if (AttackAnimIndex != LastAttackAnimIndex)
		{
			LastAttackAnimIndex = AttackAnimIndex;
			CurAnimInstance->Montage_Play(NeedAnim[AttackAnimIndex]);
		}
		else
		{
			if (AttackAnimIndex == 0)
			{
				int32 AddIndexNum = UKismetMathLibrary::RandomIntegerInRange(0, NeedAnim.Num() - 2);
				AttackAnimIndex += AddIndexNum;
				LastAttackAnimIndex = AttackAnimIndex;
				CurAnimInstance->Montage_Play(NeedAnim[AttackAnimIndex]);
			}
			else
			{
				AttackAnimIndex--;
				LastAttackAnimIndex = AttackAnimIndex;
				CurAnimInstance->Montage_Play(NeedAnim[AttackAnimIndex]);
			}
		}
	}
}
  • PlayerCharacter类中的MeleeAttack方法就该这样写了
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]);
		//		}
		//	}		
		//}
		//将状态改变为攻击状态
		PlayerBehavior = EPlayerBehavior::EPB_ATTACK;
		//每次攻击都得减去体力值
		Stamina -= MeleeAttackSubStamina;
		//将状态改变为备战状态
		MeleeState = EMeleeState::EMS_PREPARE;
		MeleeBehaviorStateWarToCommon = 10.f;//每次攻击后重新置为10秒计时
		//摄像机震动反馈
		CameraShakeFeedBack();
		RandomAttack(MeleeAttackAnim, LastMeleeIndex);
	}
}
  • SwordAttack函数方法逻辑
void APlayerCharacter::SwordAttack()
{
	if (CanSwodAttack())
	{
		//UAnimInstance* CurAnimInstance = GetMesh()->GetAnimInstance();
		//if (CurAnimInstance)
		//{
		//	//将状态改变为攻击状态
		//	PlayerBehavior = EPlayerBehavior::EPB_ATTACK;
		//	Stamina -= SwordAttackSubStamina;
		//	//播放随机动画
		//	int32 AttackAnimIndex = UKismetMathLibrary::RandomIntegerInRange(0, SwordAttackAnim.Num() - 1);
		//	if (AttackAnimIndex != LastMeleeIndex)
		//	{
		//		LastMeleeIndex = AttackAnimIndex;
		//		CurAnimInstance->Montage_Play(SwordAttackAnim[AttackAnimIndex]);
		//	}
		//	else
		//	{
		//		if (AttackAnimIndex == 0)
		//		{
		//			int32 AddIndexNum = UKismetMathLibrary::RandomIntegerInRange(0, SwordAttackAnim.Num() - 2);
		//			AttackAnimIndex += AddIndexNum;
		//			LastMeleeIndex = AttackAnimIndex;
		//			CurAnimInstance->Montage_Play(SwordAttackAnim[AttackAnimIndex]);
		//		}
		//		else
		//		{
		//			AttackAnimIndex--;
		//			LastMeleeIndex = AttackAnimIndex;
		//			CurAnimInstance->Montage_Play(SwordAttackAnim[AttackAnimIndex]);
		//		}
		//	}
		//}
		//将状态改变为攻击状态
		PlayerBehavior = EPlayerBehavior::EPB_ATTACK;
		//每次攻击都得减去体力值
		Stamina -= SwordAttackSubStamina;
		//摄像机震动反馈
		CameraShakeFeedBack();
		RandomAttack(SwordAttackAnim, LastSwordIndex);
	}
}

配置人物持剑攻击动画蒙太奇

  • 重定向资产,创建蒙太奇
    在这里插入图片描述

  • 将持剑攻击蒙太奇添加到角色蓝图
    在这里插入图片描述

  • 在持剑攻击蒙太奇上添加重置动作通知,并更换插槽,添加转向算法通知
    在这里插入图片描述

  • 动画蓝图事件表中,通知重置角色状态
    在这里插入图片描述

配置刀风效果

  • 添加Niagara粒子模块
    在这里插入图片描述
  • PlayerCharacter类中添加Niagara组件
	//剑风
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
	class UNiagaraComponent* SwordNiagara;
  • 创建这个组件,需要头文件 #include "NiagaraComponent.h"
APlayerCharacter::APlayerCharacter()
{
	ScabbardMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ScabbardMesh"));
	ScabbardMesh->SetupAttachment(GetMesh(), "Scabbard");

	Sword = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Sword"));
	Sword->SetupAttachment(GetMesh(), "Sword");

	SwordNiagara = CreateDefaultSubobject<UNiagaraComponent>(TEXT("SwordNiagara"));
	SwordNiagara->SetupAttachment(Sword);

	MeleeBehaviorStateWarToCommon = 10.;//默认十秒
}
  • 将Niagara特效添加到角色的Niagara组件上并且调好位置
    在这里插入图片描述
    在这里插入图片描述
  • 默认Niagara特效是不激活的,只有在挥刀的那个瞬间激活,逻辑:使用动画通知状态,开始挥刀的那个时间激活, 结束挥刀不激活
  • 默认不激活
    在这里插入图片描述
  • 新建一个动画通知状态蓝图编写逻辑
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 然后将这个动画通知状态添加到持剑攻击蒙太奇上
    在这里插入图片描述

制作持剑翻滚

  • 重定向资源
    在这里插入图片描述
  • 添加持剑翻滚蒙太奇动画重置通知,设置插槽,角色动画蓝图中重置角色状态
    在这里插入图片描述
    在这里插入图片描述
  • 持剑翻滚与普通状态的翻滚是一样的逻辑,可以优化一下代码,把播放翻滚的逻辑改为一个函数方法
  • SoulBaseCharacte类中新建一个函数方法专门用来播放翻滚以及滑行的逻辑,然后新建一个持剑翻滚消耗体力值的变量
	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Attribute")
	float SwordRollingSubStamina;//持剑翻滚消耗体力值

SwordRollingSubStamina = 12.f;
//翻滚算法	
void PlayerRollingOrSlideAnim(TArray<UAnimMontage*> RollingOrSlideAnim);
  • 翻滚算法逻辑
void ASoulBaseCharacter::PlayerRollingOrSlideAnim(TArray<UAnimMontage*> RollingOrSlideAnim)
{
	UAnimInstance* CurAnimInstance = GetMesh()->GetAnimInstance();
	if (CurAnimInstance)
	{
		if (RollingForwardValue == 1)
		{
			CurAnimInstance->Montage_Play(RollingOrSlideAnim[0], RollingAnimPlayRate);
		}
		else if (RollingForwardValue == -1)
		{
			CurAnimInstance->Montage_Play(RollingOrSlideAnim[1], RollingAnimPlayRate);
		}
		else if (RollingRightValue == 1)
		{
			CurAnimInstance->Montage_Play(RollingOrSlideAnim[2], RollingAnimPlayRate);
		}
		else if (RollingRightValue == -1)
		{
			CurAnimInstance->Montage_Play(RollingOrSlideAnim[3], RollingAnimPlayRate);
		}
		else
		{
			CurAnimInstance->Montage_Play(RollingOrSlideAnim[0], RollingAnimPlayRate);
		}
	}
}
  • PlayerCharacte类中之前写的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);
		//	}
		//}
		//每次翻滚减去体力
		Stamina -= MeleeRollingSubStamina;
		PlayerBehavior = EPlayerBehavior::EPB_ROLLING;
		PlayerRollingOrSlideAnim(MeleeRollingAnim);
	}
}
  • 新建是否可以持剑翻滚的方法函数和存放持剑翻滚的蒙太奇数组
	//是否可以持剑翻滚
	bool CanSwordRolling();
	
//持剑翻滚蒙太奇
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "AnimMontage", meta = (AllowPrivateAccess = "true"))
	TArray<UAnimMontage*> SwordRollingAnim;
  • 是否可以持剑翻滚逻辑
bool APlayerCharacter::CanSwordRolling()
{
	if (PlayerBehavior == EPlayerBehavior::EPB_IDLE && Stamina >= SwordRollingSubStamina)
	{
		return true;
	}
	else if (Stamina < SwordRollingSubStamina)
	{
		ShowStaminaNotEnoughText();
	}
	return false;
}
  • 持剑攻击函数逻辑
void APlayerCharacter::SwordRolling()
{
	if (CanSwordRolling())
	{
		//每次翻滚减去体力
		Stamina -= SwordRollingSubStamina;
		PlayerBehavior = EPlayerBehavior::EPB_ROLLING;
		PlayerRollingOrSlideAnim(SwordRollingAnim);
	}
}
  • 将持剑蒙太奇添加到角色蓝图上
    在这里插入图片描述

持剑滑行逻辑

  • 重定向资源
    在这里插入图片描述

  • 添加持剑滑行蒙太奇动画重置通知,设置插槽,角色动画蓝图中重置角色状态
    在这里插入图片描述
    在这里插入图片描述

  • PlayerCharacte类中之前的MeleeSlide方法也可以改写成这样

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);
		//	}
		//}
		//每次翻滚减去体力
		Stamina -= MeleeRollingSubStamina;
		PlayerBehavior = EPlayerBehavior::EPB_ROLLING;
		PlayerRollingOrSlideAnim(MeleeSlideAnim);
	}
}
  • 新建是否可以持剑滑行的方法函数和存放持剑滑行的蒙太奇数组
//是否可以切换武器
bool CanChangeWeapon();
//是否可以持剑攻击
bool CanSwodAttack();
//是否可以持剑翻滚
bool CanSwordRolling();
//是否可以持剑滑行
bool CanSwordSlide();

	//近战攻击蒙太奇
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "AnimMontage", meta = (AllowPrivateAccess = "true"))
	TArray<UAnimMontage*> MeleeAttackAnim;
	//近战翻滚蒙太奇
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "AnimMontage", meta = (AllowPrivateAccess = "true"))
	TArray<UAnimMontage*> MeleeRollingAnim;
	//近战滑行蒙太奇
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "AnimMontage", meta = (AllowPrivateAccess = "true"))
	TArray<UAnimMontage*> MeleeSlideAnim;
	//持剑攻击蒙太奇
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "AnimMontage", meta = (AllowPrivateAccess = "true"))
	TArray<UAnimMontage*> SwordAttackAnim;
	//持剑翻滚蒙太奇
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "AnimMontage", meta = (AllowPrivateAccess = "true"))
	TArray<UAnimMontage*> SwordRollingAnim;
	//持剑滑行蒙太奇
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "AnimMontage", meta = (AllowPrivateAccess = "true"))
	TArray<UAnimMontage*> SwordSlideAnim;
  • CanSwordSlide函数逻辑
bool APlayerCharacter::CanSwordSlide()
{
	if (PlayerBehavior == EPlayerBehavior::EPB_IDLE && Stamina >= SwordRollingSubStamina)
	{
		return true;
	}
	else if (Stamina < SwordRollingSubStamina)
	{
		ShowStaminaNotEnoughText();
	}
	return false;
}
  • 持剑滑行逻辑
void APlayerCharacter::SwordSlide()
{
	if (CanSwordSlide())
	{
		Stamina -= SwordRollingSubStamina;
		PlayerBehavior = EPlayerBehavior::EPB_ROLLING;
		PlayerRollingOrSlideAnim(SwordSlideAnim);
	}
}
  • 添加蒙太奇动画到角色蓝图上
    在这里插入图片描述

PlayerCharacte.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "DarkSoulsGame/Characters/SoulBaseCharacter.h"
#include "PlayerCharacter.generated.h"

/**
 * 
 */
UCLASS()
class DARKSOULSGAME_API APlayerCharacter : public ASoulBaseCharacter
{
	GENERATED_BODY()
public:
	APlayerCharacter();

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
	class UStaticMeshComponent* ScabbardMesh;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
	class USkeletalMeshComponent* Sword;

	//剑风
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
	class UNiagaraComponent* SwordNiagara;

	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
	
	virtual void Tick(float DeltaTime) override;

	//重写Attack
	virtual void Attack() override;
	//重写Rolling
	virtual void Rolling() override;
	//重写Slide
	virtual void Slide() override;
	//重写ChangeWeapon
	virtual void ChangeWeapon() override;

	void MeleeAttack();
	void SwordAttack();
	bool CanMeleeAttack();

	void MeleeRolling();
	void SwordRolling();
	bool CanMeleeRolling();

	void MeleeSlide();
	void SwordSlide();
	bool CanMeleeSlide();

	//是否可以切换武器
	bool CanChangeWeapon();
	//是否可以持剑攻击
	bool CanSwodAttack();
	//是否可以持剑翻滚
	bool CanSwordRolling();
	//是否可以持剑滑行
	bool CanSwordSlide();


	//记录从备战状态到普通状态的变量时间
	float MeleeBehaviorStateWarToCommon;

protected:

private:
	//近战攻击蒙太奇
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "AnimMontage", meta = (AllowPrivateAccess = "true"))
	TArray<UAnimMontage*> MeleeAttackAnim;
	//近战翻滚蒙太奇
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "AnimMontage", meta = (AllowPrivateAccess = "true"))
	TArray<UAnimMontage*> MeleeRollingAnim;
	//近战滑行蒙太奇
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "AnimMontage", meta = (AllowPrivateAccess = "true"))
	TArray<UAnimMontage*> MeleeSlideAnim;
	//持剑攻击蒙太奇
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "AnimMontage", meta = (AllowPrivateAccess = "true"))
	TArray<UAnimMontage*> SwordAttackAnim;
	//持剑翻滚蒙太奇
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "AnimMontage", meta = (AllowPrivateAccess = "true"))
	TArray<UAnimMontage*> SwordRollingAnim;
	//持剑滑行蒙太奇
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "AnimMontage", meta = (AllowPrivateAccess = "true"))
	TArray<UAnimMontage*> SwordSlideAnim;
};

PlayerCharacte.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "PlayerCharacter.h"
#include "DarkSoulsGame/DarkSoulPlayerController.h"
#include "Kismet/KismetMathLibrary.h"
#include "Components/SkeletalMeshComponent.h"
#include "Engine/SkeletalMeshSocket.h"
#include "NiagaraComponent.h"

APlayerCharacter::APlayerCharacter()
{
	ScabbardMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ScabbardMesh"));
	ScabbardMesh->SetupAttachment(GetMesh(), "Scabbard");

	Sword = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Sword"));
	Sword->SetupAttachment(GetMesh(), "Sword");

	SwordNiagara = CreateDefaultSubobject<UNiagaraComponent>(TEXT("SwordNiagara"));
	SwordNiagara->SetupAttachment(Sword);

	MeleeBehaviorStateWarToCommon = 10.;//默认十秒
}

void APlayerCharacter::BeginPlay()
{
	Super::BeginPlay();
	ADarkSoulPlayerController* PlayerController = Cast<ADarkSoulPlayerController>(Controller);
	if (PlayerController)
	{
		UEnhancedInputLocalPlayerSubsystem* Subsystem =
			ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer());
		if (Subsystem)
		{
			Subsystem->AddMappingContext(PlayerMappingContext, 0);
		}
	}
}

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, &APlayerCharacter::Attack);
		EnhancedInputComponent->BindAction(RollingAction, ETriggerEvent::Started, this, &APlayerCharacter::Rolling);
		EnhancedInputComponent->BindAction(SlideAction, ETriggerEvent::Started, this, &APlayerCharacter::Slide);
		EnhancedInputComponent->BindAction(ChangeWeaponAction, ETriggerEvent::Started, this, &APlayerCharacter::ChangeWeapon);
	
	}
}

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;
		}
	}
}

void APlayerCharacter::Attack()
{
	switch (WeaponType)
	{
	case EWeaponType::EWT_NONE:
		break;
	case EWeaponType::EWT_MELEE:
		MeleeAttack();
		break;
	case EWeaponType::EWT_SWORD:
		SwordAttack();
		break;
	default:
		break;
	}
}

void APlayerCharacter::Rolling()
{
	switch (WeaponType)
	{
	case EWeaponType::EWT_MELEE:
		MeleeRolling();
		break;
	case EWeaponType::EWT_SWORD:
		SwordRolling();
		break;
	}
}

void APlayerCharacter::Slide()
{
	switch (WeaponType)
	{
	case EWeaponType::EWT_MELEE:
		MeleeSlide();
		break;
	case EWeaponType::EWT_SWORD:
		SwordSlide();
		break;
	}
}

void APlayerCharacter::ChangeWeapon()
{
	if (CanChangeWeapon())
	{

		if (WeaponType == EWeaponType::EWT_MELEE)
		{
			//改为持剑状态
			WeaponType = EWeaponType::EWT_SWORD;
			//将武器添加到右手
			Sword->AttachToComponent(GetMesh(), FAttachmentTransformRules::KeepRelativeTransform, "WEAPON_R");
			bEquipWeapon = true;
		}
		else if (WeaponType == EWeaponType::EWT_SWORD)
		{
			//改为普通状态
			WeaponType = EWeaponType::EWT_MELEE;
			//将武器放回剑鞘
			Sword->AttachToComponent(GetMesh(), FAttachmentTransformRules::KeepRelativeTransform, "Sword");
			bEquipWeapon = 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;

		//	//摄像机震动反馈
		//	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]);
		//		}
		//	}		
		//}

		//将状态改变为攻击状态
		PlayerBehavior = EPlayerBehavior::EPB_ATTACK;
		//每次攻击都得减去体力值
		Stamina -= MeleeAttackSubStamina;
		//将状态改变为备战状态
		MeleeState = EMeleeState::EMS_PREPARE;
		MeleeBehaviorStateWarToCommon = 10.f;//每次攻击后重新置为10秒计时
		//摄像机震动反馈
		CameraShakeFeedBack();
		RandomAttack(MeleeAttackAnim, LastMeleeIndex);
	}
	
}

void APlayerCharacter::SwordAttack()
{
	if (CanSwodAttack())
	{
		//UAnimInstance* CurAnimInstance = GetMesh()->GetAnimInstance();
		//if (CurAnimInstance)
		//{
		//	//将状态改变为攻击状态
		//	PlayerBehavior = EPlayerBehavior::EPB_ATTACK;
		//	Stamina -= SwordAttackSubStamina;
		//	//播放随机动画
		//	int32 AttackAnimIndex = UKismetMathLibrary::RandomIntegerInRange(0, SwordAttackAnim.Num() - 1);
		//	if (AttackAnimIndex != LastMeleeIndex)
		//	{
		//		LastMeleeIndex = AttackAnimIndex;
		//		CurAnimInstance->Montage_Play(SwordAttackAnim[AttackAnimIndex]);
		//	}
		//	else
		//	{
		//		if (AttackAnimIndex == 0)
		//		{
		//			int32 AddIndexNum = UKismetMathLibrary::RandomIntegerInRange(0, SwordAttackAnim.Num() - 2);
		//			AttackAnimIndex += AddIndexNum;
		//			LastMeleeIndex = AttackAnimIndex;
		//			CurAnimInstance->Montage_Play(SwordAttackAnim[AttackAnimIndex]);
		//		}
		//		else
		//		{
		//			AttackAnimIndex--;
		//			LastMeleeIndex = AttackAnimIndex;
		//			CurAnimInstance->Montage_Play(SwordAttackAnim[AttackAnimIndex]);
		//		}
		//	}
		//}
		//将状态改变为攻击状态
		PlayerBehavior = EPlayerBehavior::EPB_ATTACK;
		//每次攻击都得减去体力值
		Stamina -= SwordAttackSubStamina;
		//摄像机震动反馈
		CameraShakeFeedBack();
		RandomAttack(SwordAttackAnim, LastSwordIndex);
	}
}

bool APlayerCharacter::CanMeleeAttack()
{
	if (PlayerBehavior == EPlayerBehavior::EPB_IDLE && Stamina >= MeleeAttackSubStamina)
	{
		return true;
	}
	else if (Stamina < MeleeAttackSubStamina)
	{
		ShowStaminaNotEnoughText();
	}
	return false;
}

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);
		//	}
		//}
		//每次翻滚减去体力
		Stamina -= MeleeRollingSubStamina;
		PlayerBehavior = EPlayerBehavior::EPB_ROLLING;
		PlayerRollingOrSlideAnim(MeleeRollingAnim);
	}
}

void APlayerCharacter::SwordRolling()
{
	if (CanSwordRolling())
	{
		//每次翻滚减去体力
		Stamina -= SwordRollingSubStamina;
		PlayerBehavior = EPlayerBehavior::EPB_ROLLING;
		PlayerRollingOrSlideAnim(SwordRollingAnim);
	}
}

bool APlayerCharacter::CanMeleeRolling()
{
	if (PlayerBehavior == EPlayerBehavior::EPB_IDLE && Stamina >= MeleeRollingSubStamina)
	{
		return true;
	}
	else if (Stamina < MeleeRollingSubStamina)
	{
		ShowStaminaNotEnoughText();
	}
	return false;
}

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);
		//	}
		//}
		//每次翻滚减去体力
		Stamina -= MeleeRollingSubStamina;
		PlayerBehavior = EPlayerBehavior::EPB_ROLLING;
		PlayerRollingOrSlideAnim(MeleeSlideAnim);
	}
}

void APlayerCharacter::SwordSlide()
{
	if (CanSwordSlide())
	{
		Stamina -= SwordRollingSubStamina;
		PlayerBehavior = EPlayerBehavior::EPB_ROLLING;
		PlayerRollingOrSlideAnim(SwordSlideAnim);
	}
}

bool APlayerCharacter::CanMeleeSlide()
{
	if (PlayerBehavior == EPlayerBehavior::EPB_IDLE && Stamina >= MeleeRollingSubStamina)
	{
		return true;
	}
	else if (Stamina < MeleeRollingSubStamina)
	{
		ShowStaminaNotEnoughText();
	}
	return false;
}

bool APlayerCharacter::CanChangeWeapon()
{
	if (PlayerBehavior == EPlayerBehavior::EPB_IDLE)
	{
		return true;
	}
	return false;
}

bool APlayerCharacter::CanSwodAttack()
{

	if (PlayerBehavior == EPlayerBehavior::EPB_IDLE && Stamina >= SwordAttackSubStamina)
	{
		return true;
	}
	else if (Stamina < SwordAttackSubStamina)
	{
		ShowStaminaNotEnoughText();
	}
	return false;
}

bool APlayerCharacter::CanSwordRolling()
{
	if (PlayerBehavior == EPlayerBehavior::EPB_IDLE && Stamina >= SwordRollingSubStamina)
	{
		return true;
	}
	else if (Stamina < SwordRollingSubStamina)
	{
		ShowStaminaNotEnoughText();
	}
	return false;
}

bool APlayerCharacter::CanSwordSlide()
{
	if (PlayerBehavior == EPlayerBehavior::EPB_IDLE && Stamina >= SwordRollingSubStamina)
	{
		return true;
	}
	else if (Stamina < SwordRollingSubStamina)
	{
		ShowStaminaNotEnoughText();
	}
	return false;
}

SoulBaseCharacter.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "InputActionValue.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "../SoulEnumType.h"
#include "SoulBaseCharacter.generated.h"

UCLASS()
class DARKSOULSGAME_API ASoulBaseCharacter : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	ASoulBaseCharacter();
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
	class UInputMappingContext* PlayerMappingContext;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
	class UInputAction* MoveAction;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
	class UInputAction* LookAction;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
	class UInputAction* RunAction;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
	class UInputAction* AttackAction;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
	UInputAction* RollingAction;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
	UInputAction* SlideAction;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
	UInputAction* ChangeWeaponAction;

	//角色状态
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Character Properties")
	bool bIsRun;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Character Attribute")
	EWeaponType WeaponType;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Character Attribute")
	EPlayerBehavior PlayerBehavior;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Character Attribute")
	EMeleeState MeleeState;

	//角色机制
	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 MeleeRollingSubStamina;//翻滚消耗体力值
	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Attribute")
	float SwordRollingSubStamina;//持剑翻滚消耗体力值
	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Attribute")
	float SwordAttackSubStamina;//持剑攻击消耗体力值
	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Attribute")
	float IncreaseStamina;//增长体力值

	//攻击时转向的目标值
	FRotator DesiredRotation;

	//翻滚的朝向值(前后)
	int32 RollingForwardValue;
	//翻滚的朝向值(左右)
	int32 RollingRightValue;
	//翻滚蒙太奇播放速率
	float RollingAnimPlayRate;

	//是否装备武器
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attack")
	bool bEquipWeapon;

	//记录上一个拳击攻击动作的标志变量
	int32 LastMeleeIndex;
	//记录上一个持剑攻击动作的标志变量
	int32 LastSwordIndex;
protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	void Move(const FInputActionValue& Value);
	void Look(const FInputActionValue& Value);
	void Run();
	void StopRun();

	//攻击
	virtual void Attack();
	//翻滚
	virtual void Rolling();
	//滑行(近战:翻跟头/持剑:滑行翻滚)
	virtual void Slide();
	//切换武器
	virtual void ChangeWeapon();

	UFUNCTION(BlueprintCallable,BlueprintPure)
	float GetCurStamina();//获取体力
	UFUNCTION(BlueprintCallable, BlueprintPure)
	float GetMaxStamina();//获取最大体力

	//摄像机震动反馈
	UFUNCTION(BlueprintImplementableEvent)
	void CameraShakeFeedBack();

	//获取UI并显示提示文本
	void ShowStaminaNotEnoughText();

	//计算按键的方向
	FRotator CalculateRotation();

	//线性插入旋转值
	UFUNCTION(BlueprintCallable)
	void RInterpRotation();

	//随机攻击算法
	void RandomAttack(TArray<UAnimMontage*> NeedAnim, int32& LastAttackAnimIndex);

	//翻滚算法
	void PlayerRollingOrSlideAnim(TArray<UAnimMontage*> RollingOrSlideAnim);

private:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera", meta = (AllowPrivateAccess = "true"))
	class USpringArmComponent* SpringArm;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera", meta = (AllowPrivateAccess = "true"))
	class UCameraComponent* FollowCamera;

};

SoulBaseCharacter.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "SoulBaseCharacter.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Engine/Engine.h"
#include <DarkSoulsGame/DarkSoulPlayerController.h>
#include <DarkSoulsGame/DarkSoulHUD.h>
#include <DarkSoulsGame/UI/UI_FightMain.h>
#include "Kismet/KismetMathLibrary.h"

// 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;//空闲状态
	//默认武器装备为false
	bEquipWeapon = false;

	MaxStamina = 100.f;
	Stamina = MaxStamina;
	MeleeAttackSubStamina = 10.f;
	MeleeRollingSubStamina = 15.f;
	SwordAttackSubStamina = 12.f;
	SwordRollingSubStamina = 12.f;
	IncreaseStamina = 1.f;

	//攻击转向默认值为0
	DesiredRotation = FRotator(0., 0., 0.);

	//翻滚方向值默认值为0
	RollingForwardValue = 0.0;
	RollingRightValue = 0.0;

	//翻滚动画播放速率
	RollingAnimPlayRate = 1.0;

	bUseControllerRotationPitch = false;
	bUseControllerRotationRoll = false;
	bUseControllerRotationYaw = false;

	GetCharacterMovement()->bOrientRotationToMovement = true;

	//记录上一个攻击动作的标志变量
	LastMeleeIndex = 0;
	LastSwordIndex = 0;
}

FRotator ASoulBaseCharacter::CalculateRotation()
{
	//获取最后一次输入的向量
	FVector LastVector = GetCharacterMovement()->GetLastInputVector();
	//判断最后一次输入向量是否为0,如果是就返回这个目标值,如果不为0就返回这个Vector转换为Rotator的目标值
	if (LastVector != FVector(0,0,0))
	{
		return UKismetMathLibrary::MakeRotFromX(LastVector);
	}
	else
	{
		return DesiredRotation;
	}
}

void ASoulBaseCharacter::RInterpRotation()
{
	//实时进行转向插值
	FRotator RInterpRotation = UKismetMathLibrary::RInterpTo(GetActorRotation(), DesiredRotation, 
		GetWorld()->GetDeltaSeconds(), 5.f);
	SetActorRotation(FRotator(0., RInterpRotation.Yaw, 0.));
}

void ASoulBaseCharacter::RandomAttack(TArray<UAnimMontage*> NeedAnim, int32& LastAttackAnimIndex)
{
	UAnimInstance* CurAnimInstance = GetMesh()->GetAnimInstance();
	if (CurAnimInstance)
	{
		//播放随机动画
		int32 AttackAnimIndex = UKismetMathLibrary::RandomIntegerInRange(0, NeedAnim.Num() - 1);
		if (AttackAnimIndex != LastAttackAnimIndex)
		{
			LastAttackAnimIndex = AttackAnimIndex;
			CurAnimInstance->Montage_Play(NeedAnim[AttackAnimIndex]);
		}
		else
		{
			if (AttackAnimIndex == 0)
			{
				int32 AddIndexNum = UKismetMathLibrary::RandomIntegerInRange(0, NeedAnim.Num() - 2);
				AttackAnimIndex += AddIndexNum;
				LastAttackAnimIndex = AttackAnimIndex;
				CurAnimInstance->Montage_Play(NeedAnim[AttackAnimIndex]);
			}
			else
			{
				AttackAnimIndex--;
				LastAttackAnimIndex = AttackAnimIndex;
				CurAnimInstance->Montage_Play(NeedAnim[AttackAnimIndex]);
			}
		}
	}
}

void ASoulBaseCharacter::PlayerRollingOrSlideAnim(TArray<UAnimMontage*> RollingOrSlideAnim)
{
	UAnimInstance* CurAnimInstance = GetMesh()->GetAnimInstance();
	if (CurAnimInstance)
	{
		if (RollingForwardValue == 1)
		{
			CurAnimInstance->Montage_Play(RollingOrSlideAnim[0], RollingAnimPlayRate);
		}
		else if (RollingForwardValue == -1)
		{
			CurAnimInstance->Montage_Play(RollingOrSlideAnim[1], RollingAnimPlayRate);
		}
		else if (RollingRightValue == 1)
		{
			CurAnimInstance->Montage_Play(RollingOrSlideAnim[2], RollingAnimPlayRate);
		}
		else if (RollingRightValue == -1)
		{
			CurAnimInstance->Montage_Play(RollingOrSlideAnim[3], RollingAnimPlayRate);
		}
		else
		{
			CurAnimInstance->Montage_Play(RollingOrSlideAnim[0], RollingAnimPlayRate);
		}
	}
}

// Called when the game starts or when spawned
void ASoulBaseCharacter::BeginPlay()
{
	Super::BeginPlay();
	
}

// Called every frame
void ASoulBaseCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	//实时计算攻击转向的目标值
	DesiredRotation = CalculateRotation();
}


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);

	}
	
}

void ASoulBaseCharacter::Look(const FInputActionValue& Value)
{
	FVector2D LookAxisVector = Value.Get<FVector2D>();
	if (Controller)
	{
		AddControllerYawInput(LookAxisVector.X);
		AddControllerPitchInput(LookAxisVector.Y);
	}
}

void ASoulBaseCharacter::Run()
{
	bIsRun = true;
	GetCharacterMovement()->MaxWalkSpeed = 600.f;
	//bIsRun = !bIsRun;
	//StopRun();
	
	
}

void ASoulBaseCharacter::StopRun()
{
	bIsRun = false;
	GetCharacterMovement()->MaxWalkSpeed = 200.f;
	//float WalkSpeed = 200.0f; // 步行速度
	//float RunSpeed = 600.0f; // 奔跑速度
	//float ActualSpeed;
	//if (bIsRun)
	//{
	//	ActualSpeed = RunSpeed;
	//}
	//else
	//{
	//	ActualSpeed = WalkSpeed;
	//}
	//GetCharacterMovement()->MaxWalkSpeed = ActualSpeed;
}

void ASoulBaseCharacter::Attack()
{
}

void ASoulBaseCharacter::Rolling()
{
}

void ASoulBaseCharacter::Slide()
{
}

void ASoulBaseCharacter::ChangeWeapon()
{
}

float ASoulBaseCharacter::GetCurStamina()
{
	return Stamina;
}

float ASoulBaseCharacter::GetMaxStamina()
{
	return MaxStamina;
}

void ASoulBaseCharacter::ShowStaminaNotEnoughText()
{
	//获取到Controller
	ADarkSoulPlayerController* PC = Cast<ADarkSoulPlayerController>(Controller);
	if (Controller)
	{
		//获取到HUD
		ADarkSoulHUD* HUD = Cast<ADarkSoulHUD>(PC->GetHUD());
		if (HUD)
		{
			//显示提示文本
			HUD->GetFightMainUI()->ShowStaminaText();
		}
	}
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值