今天來製作武器裝備系統,這樣之後才能切換武器、撿武器之類的。
WeaponData.h
WeaponBase.h/.cpp
DataTable
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 : 卸下裝備。
如果目前沒有使用武器就用空手
先用123鍵做測試
我這個大劍動畫從 UE4 轉到 UE5 之後 Root Motion 就不見了 :(
下次會讓武器砍到敵人時造成傷害。