iT邦幫忙

2025 iThome 鐵人賽

DAY 25
0
Software Development

30 天用 Unreal Engine 5 C++ 開發遊戲系列 第 25

# Day 25|超超超加強武器模組化

  • 分享至 

  • xImage
  •  

原本的武器功能是寫在玩家的代碼裡,很難針對不同的武器做不同的攻擊功能,所以這次讓角色只負責觸發攻擊事件,而具體攻擊邏輯由當前裝備的武器類別執行。

1. 新增類別

  1. MeleeWeapon.h/cpp
  2. MagicWeapon.h/cpp
  3. MeleeNotifyState.h/cpp
  4. MagicNotifyState.h/cpp

2. 修改代碼

  • 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 用設定的名稱來呼叫武器要執行的事件。

  • 在玩家代碼裡的 DoAttackStart() 跟 DoAttackTrace() 可以刪了,現改在各個 Weapon 類別裡執行。

3. 新增代碼

  • 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();
    }
}

4. 設置

  1. 用剛剛創建的武器類別(MeleeWeapon, MagicWeapon)重新創建武器
    https://ithelp.ithome.com.tw/upload/images/20251009/20171036qrq27xo9Tq.png
  2. 在 Animation Montage 裡 Replace 相應的 NotifyStat,然後在 Execute Name 寫要執行函式的名稱。
  • Sword Combo Montage
    https://ithelp.ithome.com.tw/upload/images/20251009/20171036lmjW2uWvt7.png
  • Unarmed Combo Montage
    https://ithelp.ithome.com.tw/upload/images/20251009/20171036EwQHFMQaRn.png
  • MagicBook Combo Montage
    https://ithelp.ithome.com.tw/upload/images/20251009/20171036FzEVnb38Yb.png
  1. 記得 DataTable 的 weapon class 也要換掉

然後就完成了!!!!

明天再來完善魔法攻擊吧。


上一篇
# Day 24|修改攻擊動畫系統
系列文
30 天用 Unreal Engine 5 C++ 開發遊戲25
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言