iT邦幫忙

2025 iThome 鐵人賽

DAY 13
0

今天來製作武器裝備系統,這樣之後才能切換武器、撿武器之類的。

1. 事前準備

  • 新增 WeaponData.h
    • = Editor 裡的 Structure,為 DataTable 的地基。
  • 新增 WeaponBase.h/.cpp
    • 作為 創建武器 Actor 的 parent class,這樣之後可以直接在武器 Blurprint 加想要的功能。
  • 在 UE Editor 新增 DataTable
    • 最好在 WeaponData.h 設定完之後再新增,因為 UE 的 refference 特性,在 DataTable 有東西的情況下更改 Structure 會導致崩潰。

2. Code

  • WeaponData.h
#pragma once

#include "CoreMinimal.h"
#include "Engine/DataTable.h"
#include "WeaponData.generated.h"

UENUM(BlueprintType)
enum class EWeaponType : uint8
{
    Hand,
    Sword,
    Bow,
    Staff
};

USTRUCT(BlueprintType)
struct FWeaponData : public FTableRowBase
{
    GENERATED_BODY()

public:
    // 武器類型
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    EWeaponType WeaponType = EWeaponType::Hand;

    // Socket Name
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FName SocketName;

    // 武器藍圖類別g
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    TSubclassOf<class AWeaponBase> WeaponClass = nullptr;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UAnimMontage* AttackAnimMontage = nullptr;

    // 基本攻擊力
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    float Damage = 10.f;
};

作為 DataTable 的基礎
記得 UObject 類的變數都要設預設值,不知道設甚麼就設 nullptr 就好,總之要設不然遊戲容易 Crash。💀

  • WeaponBase.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "WeaponBase.generated.h"

UCLASS()
class AWeaponBase : public AActor
{
    GENERATED_BODY()

public:
    AWeaponBase();

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Weapon")
    UStaticMeshComponent* WeaponMesh;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Weapon")
    UAnimMontage* AttackAnimMontage;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Weapon")
    FName AttachSocketName;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    float Damage=10.0f;

    UStaticMeshComponent* GetWeaponMesh() const { return WeaponMesh; }
    FName GetAttachSocketName() const { return AttachSocketName; }
    void AttachToCharacter(ACharacter* Character, FName SocketName);
    void Holster(ACharacter* Character, FName SocketName, bool Visible);
};
  • WeaponBase.cpp
#include "WeaponBase.h"
#include "GameFramework/Character.h"

AWeaponBase::AWeaponBase()
{
    WeaponMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("WeaponMesh"));
    RootComponent = WeaponMesh;

    AttachSocketName = FName("WeaponSocket"); // 預設 socket
}

void AWeaponBase::AttachToCharacter(ACharacter* Character, FName SocketName)
{
    if (!Character) return;
    AttachToComponent(Character->GetMesh(), FAttachmentTransformRules::SnapToTargetIncludingScale, SocketName);
    WeaponMesh->SetVisibility(true);
    WeaponMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}

void AWeaponBase::Holster(ACharacter* Character, FName SocketName, bool Visible)
{
    if (!Character || !WeaponMesh) return;

    AttachToComponent(Character->GetMesh(), FAttachmentTransformRules::SnapToTargetIncludingScale, SocketName);
    WeaponMesh->SetVisibility(Visible); 
}

武器的通用功能

  • iThome30daysCharacter.h
public:
    UFUNCTION(BlueprintCallable, Category="Weapon")
    virtual void AddWeapon(FName RowName, int Slot);

    UFUNCTION(BlueprintCallable, Category="Weapon")
    virtual void EquipWeapon(int EquipSlot, FName Socket);

    UFUNCTION(BlueprintCallable, Category="Weapon")
    virtual void UnEquipWeapon();

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Weapon")
    UDataTable* WeaponDataTable;
    
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Weapon")
    AWeaponBase* CurrentWeapon=nullptr;

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Weapon")
    TArray<AWeaponBase*> WeaponSlot={nullptr,nullptr,nullptr};

這裡也是 記得 UObject 類的都要有預設值。

  • iThome30daysCharacter.cpp
void AiThome30daysCharacter::AddWeapon(FName RowName, int Slot)
{
    if (!WeaponDataTable) return;

    const FString Context(TEXT("AddWeapon"));
    FWeaponData* WeaponRow = WeaponDataTable->FindRow<FWeaponData>(RowName, Context);
    if (!WeaponRow || !WeaponRow->WeaponClass) return;

    // Spawn Weapon
    FActorSpawnParameters SpawnParams;
    SpawnParams.Owner = this;
    AWeaponBase* NewWeapon = GetWorld()->SpawnActor<AWeaponBase>(WeaponRow->WeaponClass, SpawnParams);
    if (!NewWeapon) return;
    if (!CurrentWeapon) CurrentWeapon = NewWeapon;

    NewWeapon->AttackAnimMontage = WeaponRow->AttackAnimMontage;
    NewWeapon->Damage = WeaponRow->Damage;

    // 一開始先放背上
    NewWeapon->Holster(this ,FName("BackSocket"), true);
    if (Slot>2) WeaponSlot[2] = NewWeapon;
    else if (Slot<0) WeaponSlot[0] = NewWeapon;
    else WeaponSlot[Slot] = NewWeapon;
}

void AiThome30daysCharacter::EquipWeapon(int EquipSlot, FName Socket)
{
    //if (CurrentWeapon==WeaponSlot[EquipSlot]) return;
    if (CurrentWeapon)
    {
        // 收回舊武器到背上
        CurrentWeapon->Holster(this, FName("BackSocket"), true);
    }

    if (EquipSlot!=-1)
    {
        CurrentWeapon = WeaponSlot[EquipSlot];
        CurrentWeapon->AttachToCharacter(this, Socket);
    } else CurrentWeapon = nullptr;
}

void AiThome30daysCharacter::UnEquipWeapon()
{
    if (!CurrentWeapon) return;
    EquipWeapon(-1, NAME_None);
}

AddWeapon : 從 DataTable 找武器資料,這裡是用 Row Name (DataTable 最左邊那個),並放在背上(預設),跟加進武器欄。
EquipWeapon : 裝備武器,如果目前已經裝備,就把目前的武器放到背上。
UnEquipWeapon : 卸下裝備。

3. Editor 裡使用方式

  • 攻擊 如果目前沒有使用武器就用空手 之後再把赤手空拳改進 DataTable,程式邏輯要再改一下
    https://ithelp.ithome.com.tw/upload/images/20250927/20171036ENwSptNBRc.png
  • 撿裝備/裝上/卸下 先用123鍵做測試
    https://ithelp.ithome.com.tw/upload/images/20250927/20171036gwCJsMWB3C.png
  • 新增 DataTable > Row Structure 選剛剛寫的 WeaponData
    https://ithelp.ithome.com.tw/upload/images/20250927/201710363aIu42v07z.png
    https://ithelp.ithome.com.tw/upload/images/20250927/20171036THP0Y7of09.png
  • 在 DataTable 裡加你的武器
    https://ithelp.ithome.com.tw/upload/images/20250927/20171036u8VMwURadf.png
  • 在 Charater 設置 DataTable
    https://ithelp.ithome.com.tw/upload/images/20250927/20171036P8fa54a6Ze.png
  • 新增 Blueprint Class > Parent class 選 WeaponBase,在 Root Component 把 Static Mesh 設成你的武器模型
    我有設但太黑了變一坨
    https://ithelp.ithome.com.tw/upload/images/20250927/20171036BEdM6Ta4XH.png

這樣就完成了!


我這個大劍動畫從 UE4 轉到 UE5 之後 Root Motion 就不見了 :(

下次會讓武器砍到敵人時造成傷害。


上一篇
# Day 12|加武器
下一篇
# Day 14|傷害(血量)系統
系列文
30 天用 Unreal Engine 5 C++ 開發遊戲15
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言