简易战斗系统
删除替换父类组件
- 现在需要添加剑的组件,但是一般来说附着到蒙皮骨骼的东西,也是蒙皮骨骼,所以我们可以新建一个类重新编写,也可以直接继承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
#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();
};
#include "WeaponItem.h"
#include "Components/SkeletalMeshComponent.h"
AWeaponItem::AWeaponItem()
{
if (DisplayMesh)
{
DisplayMesh->DestroyComponent();
}
else
{
}
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;
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();
}
else
{
}
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;
}
}
}
武器与角色的交互事件绑定
PlayerInputComponent->BindAction("Interact", IE_Pressed, this, &AMainPlayer::InteractKeyDown);
- 把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;
const USkeletalMeshSocket* RightHandSocker = Player->GetMesh()->GetSocketByName(TEXT("RightHandSocker"));
if (RightHandSocker)
{
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;
const USkeletalMeshSocket* RightHandSocker = Player->GetMesh()->GetSocketByName(TEXT("RightHandSocket"));
if (RightHandSocker)
{
RightHandSocker->AttachActor(this, Player->GetMesh());
Player->bIsWeapon = true;
Player->EquipWeapon = this;
Player->OverlapWeapon = nullptr;
bRotate = false;
if (OnEquipSound)
{
UGameplayStatics::PlaySound2D(this, OnEquipSound);
}
}
}
}
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;
}
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
#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:
AMainPlayer();
UPROPERTY(visibleAnywhere,BlueprintReadOnly)
class USpringArmComponent* SpringArm;
UPROPERTY(visibleAnywhere, BlueprintReadOnly)
class UCameraComponent* FollowCamera;
float BaseTurnRate;
float BaseLookUpRate;
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:
virtual void BeginPlay() override;
public:
virtual void Tick(float DeltaTime) override;
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
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);
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
#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"
AMainPlayer::AMainPlayer()
{
PrimaryActorTick.bCanEverTick = true;
SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
SpringArm->SetupAttachment(GetRootComponent());
SpringArm->TargetArmLength = 600.f;
SpringArm->bUsePawnControlRotation = true;
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(SpringArm, NAME_None);
FollowCamera->bUsePawnControlRotation = false;
GetCapsuleComponent()->SetCapsuleSize(35.f, 100.f);
bUseControllerRotationPitch = false;
bUseControllerRotationYaw = false;
bUseControllerRotationRoll = false;
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;
bLeftShiftDown = false;
}
void AMainPlayer::BeginPlay()
{
Super::BeginPlay();
}
void AMainPlayer::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
switch (StaminaStatus)
{
case EPlayerStaminaStatus::EPSS_Normal:
if (bLeftShiftDown)
{
if (Stamina - StaminaConsumeRate * DeltaTime <= MaxStamina * ExhaustedStamina)
{
StaminaStatus = EPlayerStaminaStatus::EPSS_Exhausted;
}
Stamina -= StaminaConsumeRate * DeltaTime;
SetMovementStatus(EPlayerMovementStatus::EPMS_Sprinting);
}
else
{
Stamina = FMath::Clamp(Stamina + StaminaConsumeRate * DeltaTime, 0.f, MaxStamina);
SetMovementStatus(EPlayerMovementStatus::EPMS_Normal);
}
break;
case EPlayerStaminaStatus::EPSS_Exhausted:
if (bLeftShiftDown)
{
if (Stamina - StaminaConsumeRate * DeltaTime <= 0.f)
{
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:
if (Stamina + StaminaConsumeRate * DeltaTime >= MaxStamina * ExhaustedStamina)
{
StaminaStatus = EPlayerStaminaStatus::EPSS_Normal;
}
Stamina += StaminaConsumeRate * DeltaTime;
LeftShiftUp();
SetMovementStatus(EPlayerMovementStatus::EPMS_Normal);
break;
default:
break;
}
}
void AMainPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
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);
PlayerInputComponent->BindAction("Sprint", IE_Released, this, &AMainPlayer::LeftShiftUp);
PlayerInputComponent->BindAction("Interact", IE_Pressed, this, &AMainPlayer::InteractKeyDown);
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);
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))
{
FRotator Rotation = Controller->GetControlRotation();
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))
{
FRotator Rotation = Controller->GetControlRotation();
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)
{
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)
{
float Value = Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds();
if (Value != 0.f)
{
AddControllerYawInput(Value);
}
}
void AMainPlayer::LookUpRate(float Rate)
{
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);
}
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
#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
#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();
}
else
{
}
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();
const USkeletalMeshSocket* RightHandSocker = Player->GetMesh()->GetSocketByName(TEXT("RightHandSocket"));
if (RightHandSocker)
{
RightHandSocker->AttachActor(this, Player->GetMesh());
Player->bIsWeapon = true;
Player->EquipWeapon = this;
Player->OverlapWeapon = nullptr;
bRotate = false;
if (OnEquipSound)
{
UGameplayStatics::PlaySound2D(this, OnEquipSound);
}
}
}
}
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;
}
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);
}