简易战斗系统
删除替换父类组件
- 现在需要添加剑的组件,但是一般来说附着到蒙皮骨骼的东西,也是蒙皮骨骼,所以我们可以新建一个类重新编写,也可以直接继承Interoperable类然后不管UStaticMeshComponent这个组件,新建USkeletalMeshComponent,再或者直接更改UStaticMeshComponent,因为USkeletalMeshComponent与UStaticMeshComponent都继承自UMeshComponent类,我们把父类Interoperable类中的UStaticMeshComponent换成UMeshComponent类就可以在继承Interoperable的类中修改UStaticMeshComponent为USkeletalMeshComponent。
- 我们在这里介绍第三种方法
- 首先在interoperable类中更改UStaticMeshComponent为UMeshComponent。
- 然后在Weapon子类里面销毁DisplayMesh,重新创建DisplayMesh为USkeletalMeshComponent类型,注意这里的TEXT标识换一下名字,否则UE可能会崩溃,然后添加到根组件上
添加武器外观与粒子效果
- 创建Weapon蓝图添加SkeletonMesh与particle即可
武器类添加需求
- 毫无疑问父类的碰撞重叠那两个函数需要基础过来使用,然后定义剑被拾起的声音组件,是否保留被装备后的粒子效果bool变量,以及两个函数用来检测武器是否被拾起
- Weapon.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GamePlay/InteroperableItem.h"
#include "WeaponItem.generated.h"
/**
*
*/
UCLASS()
class UEGAME_API AWeaponItem : public AInteroperableItem
{
GENERATED_BODY()
public:
AWeaponItem();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon|Sound")
class USoundCue* OnEquipSound;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon|Particle")
bool bOnEquipParticle;
protected:
virtual void BeginPlay()override;
public:
virtual void OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)override;
virtual void OnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)override;
void Equip();
void UnEuip();
};
- Weapon.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "WeaponItem.h"
#include "Components/SkeletalMeshComponent.h"
AWeaponItem::AWeaponItem()
{
//销毁
if (DisplayMesh)
{
DisplayMesh->DestroyComponent();
//UE_LOG(LogTemp, Warning, TEXT("delete succeed"));
}
else
{
//UE_LOG(LogTemp, Warning, TEXT("fail to delete"));
}
//因为TEXT具有唯一性,我们不知道什么时候销毁原UStaticMeshComponent* DisplayMesh;所以这里TEXT进行一下区分
DisplayMesh=CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("DisplaySkeletalMesh"));
DisplayMesh->SetupAttachment(GetRootComponent());
}
void AWeaponItem::BeginPlay()
{
}
void AWeaponItem::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
}
void AWeaponItem::OnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
}
void AWeaponItem::Equip()
{
}
void AWeaponItem::UnEuip()
{
}
给右手添加武器
- 在人物骨骼里面新建一个socket,然后把武器插入预览调整到合适的大小及位置
- 名字最好不要有英文,因为到时候编码会用到这个插槽
拾取武器机制
通知角色当前的靠近的武器
- 设置一下轴事件映射
- 目的:当玩家走入触发剑的触发器,按F可以拾取这把剑,走到其他剑触发器时,可以交换两把剑
- 我们需要在MainPlayer类中新建一个bool变量用来检测角色是否拿着武器,新建两个WeaponItem指针来标记当前装备的剑,和是否重叠了别的剑,然后在WeaponItem类中新建一个枚举类型用来标识可拾取与已经拾取状态,新建一个枚举类型变量用来表明状态
- MainPlayer.h
//检测是否持剑
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon")
bool bIsWeapon;
//正在装备的武器
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon")
class AWeaponItem* EquipWeapon;
//正在重叠的武器
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon")
AWeaponItem* OverlapWeapon;
- Weapon.h
UENUM(BlueprintType)
enum class EWeaponState :uint8
{
EWS_CanPickUp UMETA(DisplayName = "CanPickUp"),
EWS_Equip UMETA(DisplayName = "Equip")
};
//武器拾取状态
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon")
EWeaponState WeaponState;
- 首先初始化武器是可拾取的状态,然后在开始重叠事件里面判断当前武器是否可拾取然后是不是角色进入触发器范围,如果是就告诉角色的正在重叠的武器是当前武器,在结束重叠事件里面判断,当前角色是否进入触发器范围以及当前的拾取的武器是不是当前武器,如果是就将重叠武器的指针变为空指针。
- Weapon.cpp
AWeaponItem::AWeaponItem()
{
//销毁
if (DisplayMesh)
{
DisplayMesh->DestroyComponent();
//UE_LOG(LogTemp, Warning, TEXT("delete succeed"));
}
else
{
//UE_LOG(LogTemp, Warning, TEXT("fail to delete"));
}
//因为TEXT具有唯一性,我们不知道什么时候销毁原UStaticMeshComponent* DisplayMesh;所以这里TEXT进行一下区分
DisplayMesh=CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("DisplaySkeletalMesh"));
DisplayMesh->SetupAttachment(GetRootComponent());
//拾取武器后粒子效果默认关闭
bOnEquipParticle = false;
//默认状态武器是可拾取的
WeaponState = EWeaponState::EWS_CanPickUp;
}
void AWeaponItem::BeginPlay()
{
Super::BeginPlay();
}
void AWeaponItem::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
Super::OnOverlapBegin(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex, bFromSweep, SweepResult);
if (OtherActor && WeaponState == EWeaponState::EWS_CanPickUp)
{
AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
if (Player)
{
//告诉角色正在重叠的武器是当前武器
Player->OverlapWeapon = this;
}
}
}
void AWeaponItem::OnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
Super::OnOverlapEnd(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex);
if (OtherActor)
{
AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
//判断一开始是否拾起的武器是当前武器
if (Player && Player->OverlapWeapon == this)
{
//告诉角色离开了武器触发器
Player->OverlapWeapon = nullptr;
}
}
}
武器与角色的交互事件绑定
- 新建一个与F事件绑定的函数接口
- 绑定映射F事件
PlayerInputComponent->BindAction("Interact", IE_Pressed, this, &AMainPlayer::InteractKeyDown);//按下F
- 把WeaponItem类中的两个装备卸载函数传入MainPlayer指针类型参数
void Equip(class AMainPlayer* Player);
void UnEuip(AMainPlayer* Player);
- 编写角色拾取武器逻辑
void AMainPlayer::InteractKeyDown()
{
if (OverlapWeapon)
{
if (EquipWeapon)
{
//交换武器
EquipWeapon->UnEuip(this);
OverlapWeapon->Equip(this);
}
else
{
//装备武器
OverlapWeapon->Equip(this);
}
}
else
{
if (EquipWeapon)
{
//卸载武器
EquipWeapon->UnEuip(this);
}
}
}
装备武器逻辑
- 在Equip函数里面实现装备武器逻辑,我们首先将枚举状态改为已经装备武器,然后获取到右手的Socket,然后将武器添加上去,将bIsWeapon改为true,EquipWeapon改为true,OverlapWeapon为nullptr,播放音乐与关闭粒子组件
- 头文件:
#include "Engine/SkeletalMeshSocket.h"
,const USkeletalMeshSocket* RightHandSocker = Player->GetMesh()->GetSocketByName(TEXT("RightHandSocker"));
void AWeaponItem::Equip(AMainPlayer* Player)
{
if (Player)
{
//已装备武器
WeaponState = EWeaponState::EWS_Equip;
//获取Player的Socket
const USkeletalMeshSocket* RightHandSocker = Player->GetMesh()->GetSocketByName(TEXT("RightHandSocker"));
if (RightHandSocker)
{
//让武器附属到Socket上
RightHandSocker->AttachActor(this, Player->GetMesh());
Player->bIsWeapon = true;
Player->EquipWeapon = this;
Player->OverlapWeapon = nullptr;
bRotate = false;//武器旋转关闭
if (OnEquipSound)
{
//播放音乐
UGameplayStatics::PlaySound2D(this, OnEquipSound);
}
if (!bOnEquipParticle)
{
//关闭粒子组件
ParticleEffectsComponent->Deactivate();
}
}
}
}
- 回顾一下硬编码声音
- 头文件
#include "UObject/ConstructorHelpers.h"
static ConstructorHelpers::FObjectFinder<USoundCue> SoundCueAsset(TEXT("SoundCue'/Game/Assets/Audios/Blade_Cue.Blade_Cue'"));
if (SoundCueAsset.Succeeded())
{
OnEquipSound = SoundCueAsset.Object;
}
卸载武器逻辑
- 我们首先需要明白的逻辑,当我在跳跃状态肯定是不能丢弃武器的也不能拾取武器,所以我们在判断时需要加上角色是否跳跃中,然后将其枚举变量武器状态切换成可装备武器状态,主角正在装备武器bool变量为false,装备武器为nullptr,然后我们需要判断武器重叠状态是否为空,如果是就将此时的重叠状态设置为Player自己,最后从Socket中丢弃武器,设置武器的旋转与大小位置
DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);
:分离当前WeaponItem Socket
void AWeaponItem::Equip(AMainPlayer* Player)
{
if (Player && !Player->GetMovementComponent()->IsFalling())
{
//已装备武器
WeaponState = EWeaponState::EWS_Equip;
//获取Player的Socket
const USkeletalMeshSocket* RightHandSocker = Player->GetMesh()->GetSocketByName(TEXT("RightHandSocket"));
if (RightHandSocker)
{
//让武器附属到Socket上
RightHandSocker->AttachActor(this, Player->GetMesh());
Player->bIsWeapon = true;
Player->EquipWeapon = this;
Player->OverlapWeapon = nullptr;
bRotate = false;//武器旋转关闭
if (OnEquipSound)
{
//播放音乐
UGameplayStatics::PlaySound2D(this, OnEquipSound);
}
//if (!bOnEquipParticle)
//{
// //关闭粒子组件
// ParticleEffectsComponent->Deactivate();
//
//}
}
}
}
void AWeaponItem::UnEuip(AMainPlayer* Player)
{
if (Player && !Player->GetMovementComponent()->IsFalling())
{
WeaponState = EWeaponState::EWS_CanPickUp;
Player->bIsWeapon = false;
Player->EquipWeapon = nullptr;
if (Player->OverlapWeapon == nullptr)
{
Player->OverlapWeapon = this;
}
//分离当前WeaponItem Socket
DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);
SetActorRotation(FRotator(0.f));
SetActorScale3D(FVector(1.f));
bRotate = true;
}
}
给武器添加PhysicsAsset与动态切换武器碰撞
- 我们可以创建三把剑的PhysicsAsset设置碰撞胶囊
- 新建两个函数用来激活碰撞与关闭碰撞
- 注意虚幻4.23后的更新
- PhysicsOnly:刚体、约束
- QueryOnly:扫描、重叠、移动
- QueryAndPhysics:上述全部
//动态切换碰撞
void ActiveDisplayMeshCollision();
void DeactiveDisplayMeshCollision();
void AWeaponItem::ActiveDisplayMeshCollision()
{
DisplayMesh->SetCollisionEnabled(ECollisionEnabled::PhysicsOnly);
DisplayMesh->SetCollisionObjectType(ECollisionChannel::ECC_WorldStatic);
DisplayMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
DisplayMesh->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Block);
}
void AWeaponItem::DeactiveDisplayMeshCollision()
{
DisplayMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
切换持剑移动动画
- 新建一个BlendSpace1D,添加上动画
- 然后在动画蓝图里面移动的状态机嵌套状态机添加切换持剑移动与不持剑移动
蒙太奇插入攻击片段
- 蒙太奇:可以理解为剪辑,我们可以在状态机里面插入蒙太奇去执行攻击动画
- 创建蒙太奇,将攻击片段插入好
使用Notifies添加挥剑音效
使用Notifies添加拖尾粒子特效
- 注意这个粒子特效必须要支持拖尾,方可添加,不是任何一种粒子特效都可以添加上的
- 添加两个插槽用来播放拖尾粒子的位置
分析攻击所需逻辑
- 我们得新建三个变量,一个bool变量用来标识位是否攻击键按下,一个bool变量用来判断是否要攻击在蓝图中方便使用,和蒙太奇的引用变量,然后按键事件得绑定,需要两个函数用来绑定攻击按下与抬起,然后是开始攻击函数与攻击结束函数用来抒写主要攻击动画播放逻辑,攻击结束我们不知道什么时候结束,所以我们要把攻击结束函数添加反射在蓝图中去拿到攻击结束时间
bool bAttackKeyDown;//是否按下攻击键
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Animation")
bool bIsAttacking;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Animation")
class UAnimMontage* AttackMontage;
//-------------------------------------------------------------------------
void AttackKeyDown();
FORCEINLINE void AttackKeyUp() { bAttackKeyDown = false; }
void AttackBegin();
UFUNCTION(BlueprintCallable)
void AttackEnd();
//攻击
PlayerInputComponent->BindAction("Attack", IE_Pressed, this, &AMainPlayer::AttackKeyDown);
PlayerInputComponent->BindAction("Attack", IE_Released, this, &AMainPlayer::AttackKeyUp);
void AMainPlayer::AttackKeyDown()
{
}
void AMainPlayer::AttackBegin()
{
}
void AMainPlayer::AttackEnd()
{
}
攻击逻辑实现
- 当我们按下攻击键时,将检测攻击键按下的bool变量为true,安全检测一下是否持剑,如果是就执行攻击函数,攻击函数里面就先判断是否攻击,如果是就将是否攻击先标记为true,然后拿到动画,安全检测动画与蒙太奇,设置播放速率与部分蒙太奇片段的名字,开启蒙太奇与播放指定判断,最后在攻击结束函数里面将是否攻击变量标记为false,判断攻击键是否按下,按下就形成闭环再执行攻击键按下函数。
- 获取蒙太奇函数的头文件:
#include "Animation/AnimInstance.h"
//拿到动画
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (AnimInstance && AttackMontage)
{
float PlayRate = FMath::RandRange(1.25f, 1.75f);
FString SectionName = FString::FromInt(FMath::RandRange(1, 2));
//指定片段播放
AnimInstance->Montage_Play(AttackMontage, PlayRate);
AnimInstance->Montage_JumpToSection(FName(*SectionName), AttackMontage);
}
- 攻击逻辑
void AMainPlayer::AttackKeyDown()
{
bAttackKeyDown = true;
if (bIsWeapon)
{
AttackBegin();
}
}
void AMainPlayer::AttackBegin()
{
if (!bIsAttacking)
{
bIsAttacking = true;
//拿到动画
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (AnimInstance && AttackMontage)
{
float PlayRate = FMath::RandRange(1.25f, 1.75f);
FString SectionName = FString::FromInt(FMath::RandRange(1, 2));
//指定片段播放
AnimInstance->Montage_Play(AttackMontage, PlayRate);
AnimInstance->Montage_JumpToSection(FName(*SectionName), AttackMontage);
}
}
}
void AMainPlayer::AttackEnd()
{
bIsAttacking = false;
//形成闭环
if (bAttackKeyDown)
{
AttackKeyDown();
}
}
攻击逻辑蓝图实现
-
首先在蒙太奇中新建两个结束攻击的通知
-
在动画蓝图与角色蓝图中添加蒙太奇
- 正在攻击的时候就不能卸载武器
- 移动中不可攻击
MainPlayer.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "MainPlayer.generated.h"
//声明移动状态枚举
UENUM(BlueprintType)
enum class EPlayerMovementStatus :uint8
{
EPMS_Normal UMETA(DisplayName = "Normal"),
EPMS_Sprinting UMETA(DisplayName = "Sprinting"),
EPMS_Dead UMETA(DisplayName = "Dead")
};
UENUM(BlueprintType)
enum class EPlayerStaminaStatus :uint8
{
EPSS_Normal UMETA(DisplayName = "Normal"),
EPSS_Exhausted UMETA(DisplayName = "Exhausted"),
EPSS_ExhaustedRecovering UMETA(DisplayName = "ExhaustedRecovering")
};
UCLASS()
class UEGAME_API AMainPlayer : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
AMainPlayer();
//新建一个SpringArm
UPROPERTY(visibleAnywhere,BlueprintReadOnly)
class USpringArmComponent* SpringArm;
//新建一个Camera
UPROPERTY(visibleAnywhere, BlueprintReadOnly)
class UCameraComponent* FollowCamera;
float BaseTurnRate; //使用键盘X转向的速率
float BaseLookUpRate; //使用键盘Y转向的速率
//主角状态
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Playe State")
float Health;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Playe State")
float MaxHealth;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Playe State")
float Stamina;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Playe State")
float MaxStamina;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Playe State")
float StaminaConsumeRate;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Playe State", meta = (ClampMin = 0, ClampMax = 1))
float ExhaustedStamina;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Playe State")
int Coins;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Player State")
float RunningSpeed;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Player State")
float SprintSpeed;
UPROPERTY(VisibleAnywhere,BlueprintReadWrite,Category="Player State")
EPlayerMovementStatus MovementStatus;
UPROPERTY(VisibleAnywhere,BlueprintReadWrite,Category="Player State")
EPlayerStaminaStatus StaminaStatus;
bool bLeftShiftDown;
//检测是否持剑
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon")
bool bIsWeapon;
//正在装备的武器
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon")
class AWeaponItem* EquipWeapon;
//正在重叠的武器
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon")
AWeaponItem* OverlapWeapon;
bool bAttackKeyDown;//是否按下攻击键
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Animation")
bool bIsAttacking;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Animation")
class UAnimMontage* AttackMontage;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
//重新Character类中的Jump方法
void Jump() override;
void MoveForward(float value);
void MoveRight(float value);
void Turn(float Value);
void LookUp(float Value);
void TurnRate(float Rate);
void LookUpRate(float Rate);
//改变状态
UFUNCTION(BlueprintCallable,Category="Player|State")
void AddHealth(float value);
UFUNCTION(BlueprintCallable, Category = "Player|State")
void AddStamina(float value);
UFUNCTION(BlueprintCallable, Category = "Player|State")
void AddCoin(float value);
//重写TakeDamage方法
float TakeDamage(float Damage, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) override;
//短小精悍
FORCEINLINE void LeftShiftDown() { bLeftShiftDown = true; }
FORCEINLINE void LeftShiftUp() { bLeftShiftDown = false; }
void SetMovementStatus(EPlayerMovementStatus Status);
void InteractKeyDown();
void AttackKeyDown();
FORCEINLINE void AttackKeyUp() { bAttackKeyDown = false; }
void AttackBegin();
UFUNCTION(BlueprintCallable)
void AttackEnd();
};
MainPlayer.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "MainPlayer.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/InputComponent.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GamePlay/WeaponItem.h"
#include "Animation/AnimInstance.h"
// Sets default values
AMainPlayer::AMainPlayer()
{
// 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无碰撞臂长
SpringArm->TargetArmLength = 600.f;
SpringArm->bUsePawnControlRotation = true;//硬编码SpringArm继承controlller旋转为真
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(SpringArm, NAME_None);
FollowCamera->bUsePawnControlRotation = false;//硬编码FollowCamera继承controlller旋转为假
//设置胶囊体的默认宽高
GetCapsuleComponent()->SetCapsuleSize(35.f, 100.f);
//对Character的Pawn进行硬编码
bUseControllerRotationPitch = false;
bUseControllerRotationYaw = false;
bUseControllerRotationRoll = false;
//硬编码orient Rotation to Movement,给个默认转向速率
GetCharacterMovement()->bOrientRotationToMovement = true;
GetCharacterMovement()->RotationRate = FRotator(0.f, 500.f, 0.f);
//设置跳跃初始值与在空中的坠落时横向运动控制量
GetCharacterMovement()->JumpZVelocity = 600.f;
GetCharacterMovement()->AirControl = 0.15f;
//给键盘控制转向的速率变量赋初值
BaseTurnRate = 21.f;
BaseLookUpRate = 21.f;
//初始化角色状态
MaxHealth = 100.f;
Health = MaxHealth;
MaxStamina = 200.f;
Stamina = MaxStamina;
StaminaConsumeRate = 20.f;
ExhaustedStamina = 0.167f;
Coins = 0;
RunningSpeed = 600.f;
SprintSpeed = 900.f;
MovementStatus = EPlayerMovementStatus::EPMS_Normal;
StaminaStatus = EPlayerStaminaStatus::EPSS_Normal;
//默认没有按下shift
bLeftShiftDown = false;
}
// Called when the game starts or when spawned
void AMainPlayer::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AMainPlayer::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
switch (StaminaStatus)
{
case EPlayerStaminaStatus::EPSS_Normal:
//当Shift按下
if (bLeftShiftDown)
{
if (Stamina - StaminaConsumeRate * DeltaTime <= MaxStamina * ExhaustedStamina)
{
StaminaStatus = EPlayerStaminaStatus::EPSS_Exhausted;
}
//无论是不是精疲力尽状态都要减去当前帧冲刺消耗的耐力
Stamina -= StaminaConsumeRate * DeltaTime;
SetMovementStatus(EPlayerMovementStatus::EPMS_Sprinting);
}
else
{
//当Shift没有按下,恢复耐力
Stamina = FMath::Clamp(Stamina + StaminaConsumeRate * DeltaTime, 0.f, MaxStamina);
SetMovementStatus(EPlayerMovementStatus::EPMS_Normal);
}
break;
case EPlayerStaminaStatus::EPSS_Exhausted:
if (bLeftShiftDown)
{
//如果耐力已经为0
if (Stamina - StaminaConsumeRate * DeltaTime <= 0.f)
{
//么我们需要内部编码把shift抬起,此时StaminaStatus状态转换为ExhaustedRecovering状态,然后设置移动状态为Normal
LeftShiftUp();
StaminaStatus = EPlayerStaminaStatus::EPSS_ExhaustedRecovering;
SetMovementStatus(EPlayerMovementStatus::EPMS_Normal);
}
else
{
Stamina -= StaminaConsumeRate * DeltaTime;
}
}
else
{
StaminaStatus = EPlayerStaminaStatus::EPSS_ExhaustedRecovering;
Stamina = FMath::Clamp(Stamina + StaminaConsumeRate * DeltaTime, 0.f, MaxStamina);
SetMovementStatus(EPlayerMovementStatus::EPMS_Normal);
}
break;
case EPlayerStaminaStatus::EPSS_ExhaustedRecovering:
//当恢复大于疲劳区时,StaminaStatus状态为Normal
if (Stamina + StaminaConsumeRate * DeltaTime >= MaxStamina * ExhaustedStamina)
{
StaminaStatus = EPlayerStaminaStatus::EPSS_Normal;
}
//这状态值肯定是加定了
Stamina += StaminaConsumeRate * DeltaTime;
//抬起shift
LeftShiftUp();
SetMovementStatus(EPlayerMovementStatus::EPMS_Normal);
break;
default:
break;
}
}
// Called to bind functionality to input
void AMainPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
//检查PlayerInputComponent指针,check函数只能在这使用
check(PlayerInputComponent);
//绑定跳跃轴映射事件
PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &AMainPlayer::Jump);//按下空格
PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);//抬起空格
PlayerInputComponent->BindAction("Sprint", IE_Pressed, this, &AMainPlayer::LeftShiftDown);//按下shift
PlayerInputComponent->BindAction("Sprint", IE_Released, this, &AMainPlayer::LeftShiftUp);//抬起shift
//拾取剑
PlayerInputComponent->BindAction("Interact", IE_Pressed, this, &AMainPlayer::InteractKeyDown);//按下F
//攻击
PlayerInputComponent->BindAction("Attack", IE_Pressed, this, &AMainPlayer::AttackKeyDown);
PlayerInputComponent->BindAction("Attack", IE_Released, this, &AMainPlayer::AttackKeyUp);
//绑定移动轴映射事件
PlayerInputComponent->BindAxis("MoveForward", this, &AMainPlayer::MoveForward);
PlayerInputComponent->BindAxis("MoveRight", this, &AMainPlayer::MoveRight);
//绑定Controller控制器去管理视角旋转
PlayerInputComponent->BindAxis("Turn", this, &AMainPlayer::Turn);
PlayerInputComponent->BindAxis("LookUp", this, &AMainPlayer::LookUp);
//绑定键盘鼠标轴映射事件
PlayerInputComponent->BindAxis("TurnRate", this, &AMainPlayer::TurnRate);
PlayerInputComponent->BindAxis("LookUpRate", this, &AMainPlayer::LookUpRate);
}
void AMainPlayer::Jump()
{
//继承父类的方法
Super::Jump();
}
void AMainPlayer::MoveForward(float value)
{
if (Controller != nullptr && value != 0.f && !(bIsAttacking))
{
//获取到Control旋转
FRotator Rotation = Controller->GetControlRotation();
//转向只关注水平Yaw方向,因此置0防止影响
FRotator YowRotation = FRotator(0.0f, Rotation.Yaw, 0.0f);
//获取相机(鼠标控制器的朝向),并且朝这个轴的方向移动
FVector Direction = FRotationMatrix(YowRotation).GetUnitAxis(EAxis::X);
AddMovementInput(Direction, value);
}
}
void AMainPlayer::MoveRight(float value)
{
if (Controller != nullptr && value != 0.f && !(bIsAttacking))
{
//获取到Controller旋转
FRotator Rotation = Controller->GetControlRotation();
//转向只关注水平Yaw方向,因此置0防止影响
FRotator YowRotation = FRotator(0.0f, Rotation.Yaw, 0.0f);
//获取相机(鼠标控制器的朝向),并且朝这个轴的方向移动
FVector Direction = FRotationMatrix(YowRotation).GetUnitAxis(EAxis::Y);
AddMovementInput(Direction, value);
}
}
void AMainPlayer::Turn(float Value)
{
if (Value != 0.f)
{
AddControllerYawInput(Value);
}
}
void AMainPlayer::LookUp(float Value)
{
//UE_LOG(LogTemp, Warning, TEXT("%f"), GetControlRotation().Pitch);
//控制视角
if (GetControlRotation().Pitch < 270.f && GetControlRotation().Pitch >180.f && Value > 0.f)
{
return;
}
else if (GetControlRotation().Pitch < 180.f && GetControlRotation().Pitch >45.f && Value < 0.f)
{
return;
}
AddControllerPitchInput(Value);
}
void AMainPlayer::TurnRate(float Rate)
{
//要乘以一个DeltaTime这样就可以避免高帧底帧差值问题
float Value = Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds();
if (Value != 0.f)
{
AddControllerYawInput(Value);
}
}
void AMainPlayer::LookUpRate(float Rate)
{
//要乘以一个DeltaTime这样就可以避免高帧底帧差值问题
float Value = Rate * BaseLookUpRate * GetWorld()->GetDeltaSeconds();
//控制视角
if (GetControlRotation().Pitch < 270.f && GetControlRotation().Pitch >180.f && Value > 0.f)
{
return;
}
else if (GetControlRotation().Pitch < 180.f && GetControlRotation().Pitch >45.f && Value < 0.f)
{
return;
}
AddControllerPitchInput(Value);
}
void AMainPlayer::AddHealth(float value)
{
Health = FMath::Clamp(Health + value, 0.f, MaxHealth);
}
void AMainPlayer::AddStamina(float value)
{
Stamina = FMath::Clamp(Stamina + value, 0.f, MaxStamina);
}
void AMainPlayer::AddCoin(float value)
{
Coins += value;
}
float AMainPlayer::TakeDamage(float Damage, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
if (Health - Damage <= 0.f)
{
Health = FMath::Clamp(Health - Damage, 0.f, MaxHealth);
//TODO Die();
}
else
{
Health -= Damage;
}
return Health;
}
void AMainPlayer::SetMovementStatus(EPlayerMovementStatus Status)
{
MovementStatus = Status;
//切换状态的时候改变移动速度
switch (MovementStatus)
{
case EPlayerMovementStatus::EPMS_Sprinting:
GetCharacterMovement()->MaxWalkSpeed = SprintSpeed;
break;
default:
GetCharacterMovement()->MaxWalkSpeed = RunningSpeed;
break;
}
}
void AMainPlayer::InteractKeyDown()
{
if (OverlapWeapon)
{
if (EquipWeapon)
{
//交换武器
EquipWeapon->UnEuip(this);
OverlapWeapon->Equip(this);
}
else
{
//装备武器
OverlapWeapon->Equip(this);
}
}
else
{
if (EquipWeapon)
{
//卸载武器
EquipWeapon->UnEuip(this);
}
}
}
void AMainPlayer::AttackKeyDown()
{
bAttackKeyDown = true;
if (bIsWeapon)
{
AttackBegin();
}
}
void AMainPlayer::AttackBegin()
{
if (!bIsAttacking)
{
bIsAttacking = true;
//拿到动画
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (AnimInstance && AttackMontage)
{
float PlayRate = FMath::RandRange(1.25f, 1.75f);
FString SectionName = FString::FromInt(FMath::RandRange(1, 2));
//指定片段播放
AnimInstance->Montage_Play(AttackMontage, PlayRate);
AnimInstance->Montage_JumpToSection(FName(*SectionName), AttackMontage);
}
}
}
void AMainPlayer::AttackEnd()
{
bIsAttacking = false;
//形成闭环
if (bAttackKeyDown)
{
AttackKeyDown();
}
}
WeaponItem.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GamePlay/InteroperableItem.h"
#include "WeaponItem.generated.h"
/**
*
*/
UENUM(BlueprintType)
enum class EWeaponState :uint8
{
EWS_CanPickUp UMETA(DisplayName = "CanPickUp"),
EWS_Equip UMETA(DisplayName = "Equip")
};
UCLASS()
class UEGAME_API AWeaponItem : public AInteroperableItem
{
GENERATED_BODY()
public:
AWeaponItem();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon|Sound")
class USoundCue* OnEquipSound;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon|Particle")
bool bOnEquipParticle;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon")
EWeaponState WeaponState;
protected:
virtual void BeginPlay()override;
public:
virtual void OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)override;
virtual void OnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)override;
void Equip(class AMainPlayer* Player);
void UnEuip(AMainPlayer* Player);
//动态切换碰撞
void ActiveDisplayMeshCollision();
void DeactiveDisplayMeshCollision();
};
WeaponItem.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "WeaponItem.h"
#include "Components/SkeletalMeshComponent.h"
#include "Characters/Player/MainPlayer.h"
#include "Engine/SkeletalMeshSocket.h"
#include "Kismet/GameplayStatics.h"
#include "Sound/SoundCue.h"
#include "Particles/ParticleSystemComponent.h"
#include "UObject/ConstructorHelpers.h"
#include "GameFramework/CharacterMovementComponent.h"
AWeaponItem::AWeaponItem()
{
//销毁
if (DisplayMesh)
{
DisplayMesh->DestroyComponent();
//UE_LOG(LogTemp, Warning, TEXT("delete succeed"));
}
else
{
//UE_LOG(LogTemp, Warning, TEXT("fail to delete"));
}
//因为TEXT具有唯一性,我们不知道什么时候销毁原UStaticMeshComponent* DisplayMesh;所以这里TEXT进行一下区分
DisplayMesh=CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("DisplaySkeletalMesh"));
DisplayMesh->SetupAttachment(GetRootComponent());
ActiveDisplayMeshCollision();//设置碰撞
static ConstructorHelpers::FObjectFinder<USoundCue> SoundCueAsset(TEXT("SoundCue'/Game/Assets/Audios/Blade_Cue.Blade_Cue'"));
if (SoundCueAsset.Succeeded())
{
OnEquipSound = SoundCueAsset.Object;
}
//拾取武器后粒子效果默认关闭
bOnEquipParticle = false;
//默认状态武器是可拾取的
WeaponState = EWeaponState::EWS_CanPickUp;
}
void AWeaponItem::BeginPlay()
{
Super::BeginPlay();
}
void AWeaponItem::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
Super::OnOverlapBegin(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex, bFromSweep, SweepResult);
if (OtherActor && WeaponState == EWeaponState::EWS_CanPickUp)
{
AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
if (Player)
{
//告诉角色正在重叠的武器是当前武器
Player->OverlapWeapon = this;
}
}
}
void AWeaponItem::OnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
Super::OnOverlapEnd(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex);
if (OtherActor)
{
AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
//判断一开始是否拾起的武器是当前武器
if (Player && Player->OverlapWeapon == this)
{
//告诉角色离开了武器触发器
Player->OverlapWeapon = nullptr;
}
}
}
void AWeaponItem::Equip(AMainPlayer* Player)
{
if (Player && !Player->GetMovementComponent()->IsFalling())
{
//已装备武器
WeaponState = EWeaponState::EWS_Equip;
DeactiveDisplayMeshCollision();//关闭碰撞
//获取Player的Socket
const USkeletalMeshSocket* RightHandSocker = Player->GetMesh()->GetSocketByName(TEXT("RightHandSocket"));
if (RightHandSocker)
{
//让武器附属到Socket上
RightHandSocker->AttachActor(this, Player->GetMesh());
Player->bIsWeapon = true;
Player->EquipWeapon = this;
Player->OverlapWeapon = nullptr;
bRotate = false;//武器旋转关闭
if (OnEquipSound)
{
//播放音乐
UGameplayStatics::PlaySound2D(this, OnEquipSound);
}
//if (!bOnEquipParticle)
//{
// //关闭粒子组件
// ParticleEffectsComponent->Deactivate();
//
//}
}
}
}
void AWeaponItem::UnEuip(AMainPlayer* Player)
{
if (Player && !Player->GetMovementComponent()->IsFalling() && !Player->bIsAttacking)
{
WeaponState = EWeaponState::EWS_CanPickUp;
ActiveDisplayMeshCollision();//开启碰撞
Player->bIsWeapon = false;
Player->EquipWeapon = nullptr;
if (Player->OverlapWeapon == nullptr)
{
Player->OverlapWeapon = this;
}
//分离当前WeaponItem Socket
DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);
SetActorRotation(FRotator(0.f));
SetActorScale3D(FVector(1.f));
bRotate = true;
}
}
void AWeaponItem::ActiveDisplayMeshCollision()
{
DisplayMesh->SetCollisionEnabled(ECollisionEnabled::PhysicsOnly);
DisplayMesh->SetCollisionObjectType(ECollisionChannel::ECC_WorldStatic);
DisplayMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
DisplayMesh->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Block);
}
void AWeaponItem::DeactiveDisplayMeshCollision()
{
DisplayMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}