原本的武器功能是寫在玩家的代碼裡,很難針對不同的武器做不同的攻擊功能,所以這次讓角色只負責觸發攻擊事件,而具體攻擊邏輯由當前裝備的武器類別執行。
MeleeWeapon.h/cpp
MagicWeapon.h/cpp
MeleeNotifyState.h/cpp
MagicNotifyState.h/cpp
WeaponBase.h
增加UFUNCTION(BlueprintCallable)
void ExecuteSectionAttack(FName ExecuteName);
WeaponBase.cpp
增加void AWeaponBase::ExecuteSectionAttack(FName ExecuteName)
{
if (UFunction* Func = GetClass()->FindFunctionByName(ExecuteName)) ProcessEvent(Func, nullptr);
}
在 NotifyState 用設定的名稱來呼叫武器要執行的事件。
MeleeWeapon.h
#pragma once
#include "CoreMinimal.h"
#include "iThome30daysCharacter.h"
#include "WeaponBase.h"
#include "MeleeWeapon.generated.h"
UCLASS()
class ITHOME30DAYS_API AMeleeWeapon : public AWeaponBase
{
GENERATED_BODY()
public:
AMeleeWeapon();
virtual void BeginPlay() override;
UPROPERTY(EditAnywhere, Category="Weapon")
FName TraceStartSocket = "Start";
UPROPERTY(EditAnywhere, Category="Weapon")
FName TraceEndSocket = "End";
UPROPERTY(EditAnywhere, Category="Weapon")
float Radius=20.0f;
UPROPERTY(EditAnywhere, Category="Weapon")
float TraceThickness=5.f;
UPROPERTY(EditAnywhere, Category="Weapon")
TArray<TEnumAsByte<EObjectTypeQuery>> CollideObjectType;
UPROPERTY()
TMap<AActor*, float> DamageCooldownMap;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Weapon")
float DamageCooldown = 0.5f;
FVector LastStartSocketPos = FVector::Zero();
FVector LastEndSocketPos = FVector::Zero();
UPROPERTY()
AiThome30daysCharacter* OwnerChar = nullptr;
bool Debug = false;
UPROPERTY()
TArray<AActor*> IgnoreActors;
FCollisionQueryParams QueryParams;
virtual void StartAttackState() override;
virtual void EndAttackState() override;
virtual void UpdateAttackState() override;
UFUNCTION()
virtual void SlashAttack();
UFUNCTION()
virtual void SmashAttack();
};
MeleeWeapon.cpp
#include "MeleeWeapon.h"
#include "Component/HealthComponent.h"
AMeleeWeapon::AMeleeWeapon()
{
}
void AMeleeWeapon::BeginPlay()
{
Super::BeginPlay();
OwnerChar = Cast<AiThome30daysCharacter>(this->GetOwner());
if (OwnerChar) Debug = OwnerChar->Debug;
}
void AMeleeWeapon::StartAttackState()
{
if (!WeaponMesh) return;
LastStartSocketPos = WeaponMesh->GetSocketLocation(TraceStartSocket);
LastEndSocketPos = WeaponMesh->GetSocketLocation(TraceEndSocket);
IgnoreActors.Add(this);
IgnoreActors.Add(OwnerChar);
QueryParams.AddIgnoredActor(this);
QueryParams.AddIgnoredActor(OwnerChar);
}
void AMeleeWeapon::EndAttackState()
{
DamageCooldownMap.Empty();
}
void AMeleeWeapon::UpdateAttackState()
{
}
void AMeleeWeapon::SlashAttack()
{
/*
# 之前武器 *進階攻擊判定* (Day 15)的代碼
*/
}
void AMeleeWeapon::SmashAttack()
{
/*
# Sphere Trace 的代碼,跟上面的大同小異,只不過是比較大範圍的判定
*/
}
MagicWeapon.h/cpp
都類似於 Melee,主要差在執行的攻擊事件,我這裡只是簡單的生成圓柱 Actor 在玩家面前。
MeleeNotifyState.h
#pragma once
#include "CoreMinimal.h"
#include "Animation/AnimNotifies/AnimNotifyState.h"
#include "MeleeNotifyState.generated.h"
UCLASS()
class UMeleeNotifyState : public UAnimNotifyState
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, Category="Attack")
bool bIsUnarmed = false;
UPROPERTY(EditAnywhere, Category="Attack", meta=(EditCondition="bIsUnarmed", EditConditionHides))
FName TraceSocket = "None";
UPROPERTY(EditAnywhere, Category="Attack", meta=(EditCondition="bIsUnarmed", EditConditionHides))
float Radius = 20.0f;
UPROPERTY(EditAnywhere, Category="Attack")
FName ExecuteName;
/** Perform the Anim Notify */
virtual void NotifyTick(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float FrameDeltaTime, const FAnimNotifyEventReference& EventReference) override;
virtual void NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration, const FAnimNotifyEventReference& EventReference) override;
virtual void NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference) override;
};
MeleeNotifyState.cpp
#include "MeleeNotifyState.h"
#include "Components/SkeletalMeshComponent.h"
#include "iThome30daysCharacter.h"
void UMeleeNotifyState::NotifyTick(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float FrameDeltaTime, const FAnimNotifyEventReference& EventReference)
{
if (AiThome30daysCharacter* OwnerChar = Cast<AiThome30daysCharacter>(MeshComp->GetOwner()))
{
if (!bIsUnarmed && OwnerChar->CurrentWeapon) OwnerChar->CurrentWeapon->ExecuteSectionAttack(ExecuteName);
else if (bIsUnarmed && !OwnerChar->CurrentWeapon)OwnerChar->UpdateAttackState(TraceSocket, Radius);
// 這裡給空手攻擊也加一個自訂的攻擊判定,因為只有這一功能我就直接放玩家代碼裡了。
}
}
void UMeleeNotifyState::NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration, const FAnimNotifyEventReference& EventReference)
{
AiThome30daysCharacter* OwnerChar = Cast<AiThome30daysCharacter>(MeshComp->GetOwner());
if (OwnerChar && OwnerChar->CurrentWeapon)
{
OwnerChar->CurrentWeapon->StartAttackState();
}
}
void UMeleeNotifyState::NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference)
{
AiThome30daysCharacter* OwnerChar = Cast<AiThome30daysCharacter>(MeshComp->GetOwner());
if (OwnerChar && OwnerChar->CurrentWeapon)
{
OwnerChar->CurrentWeapon->EndAttackState();
}
}
明天再來完善魔法攻擊吧。