다양한 기록

[UE Game Framework] #8 Item System 본문

언리얼 엔진/Unreal Game Framework

[UE Game Framework] #8 Item System

라구넹 2025. 1. 6. 04:13

트리거 박스의 설정

- 루트에 트리거 박스를 설정하고 자식에 메시 컴포넌트를 부착

- 이펙트는 기본값으로 비활성화 상태로 두고 오버랩 이벤트 발생 시 발동되도록 설정

- 이펙트 종료 시 액터가 제거되도록 설정

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ABItemBox.generated.h"

UCLASS()
class ARENABATTLE_API AABItemBox : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AABItemBox();

protected:
	UPROPERTY(VisibleAnywhere, Category = Box)
	TObjectPtr<class UBoxComponent> Trigger;

	UPROPERTY(VisibleAnywhere, Category = Box)
	TObjectPtr<class UStaticMeshComponent> Mesh;

	UPROPERTY(VisibleAnywhere, Category = Effect)
	TObjectPtr<class UParticleSystemComponent> Effect;
};

ABItemBox.h

트리거, 메쉬, 이펙트를 가짐

 

// Fill out your copyright notice in the Description page of Project Settings.


#include "Item/ABItemBox.h"
#include "Components/BoxComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Particles/ParticleSystemComponent.h"
#include "Physics/ABCollision.h"

// Sets default values
AABItemBox::AABItemBox()
{
	Trigger = CreateDefaultSubobject<UBoxComponent>(TEXT("Trgger"));
	Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
	Effect = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("Effect"));

	RootComponent = Trigger;
	Mesh->SetupAttachment(Trigger);
	Effect->SetupAttachment(Trigger);

	Trigger->SetCollisionProfileName(CPROFILE_ABTRIGGER);

	static ConstructorHelpers::FObjectFinder<UStaticMesh> BoxMeshRef(TEXT("/Script/Engine.StaticMesh'/Game/ArenaBattle/Environment/Props/SM_Env_Breakables_Box1.SM_Env_Breakables_Box1'"));
	if (BoxMeshRef.Object)
	{
		Mesh->SetStaticMesh(BoxMeshRef.Object);
	}
	Mesh->SetRelativeLocation(FVector(0.0f, -3.5f, -30.0f));
	Mesh->SetCollisionProfileName("NoCollision");

	static ConstructorHelpers::FObjectFinder<UParticleSystem> EffectRef(TEXT("/Script/Engine.ParticleSystem'/Game/ArenaBattle/Effect/P_TreasureChest_Open_Mesh.P_TreasureChest_Open_Mesh'"));
	if (EffectRef.Object)
	{
		Effect->SetTemplate(EffectRef.Object);
		Effect->bAutoActivate = false;
	}
}

트리거, 메시, 이펙트 설정

 

그리고 상자 만들어서 배치

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ABItemBox.generated.h"

UCLASS()
class ARENABATTLE_API AABItemBox : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AABItemBox();

protected:
	UPROPERTY(VisibleAnywhere, Category = Box)
	TObjectPtr<class UBoxComponent> Trigger;

	UPROPERTY(VisibleAnywhere, Category = Box)
	TObjectPtr<class UStaticMeshComponent> Mesh;

	UPROPERTY(VisibleAnywhere, Category = Effect)
	TObjectPtr<class UParticleSystemComponent> Effect;

	// 연결하려는 델리게이트가 Dynamic이라 UFUNCTION 지정 필요
	UFUNCTION()
	void OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepHitResult);

	UFUNCTION()
	void OnEffectFinished(class UParticleSystemComponent* ParticleSystem);
};

트리거랑 이펙트 델리게이트에 연결할 함수 두 개 선언

 

// Fill out your copyright notice in the Description page of Project Settings.


#include "Item/ABItemBox.h"
#include "Components/BoxComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Particles/ParticleSystemComponent.h"
#include "Physics/ABCollision.h"

// Sets default values
AABItemBox::AABItemBox()
{
	Trigger = CreateDefaultSubobject<UBoxComponent>(TEXT("Trgger"));
	Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
	Effect = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("Effect"));

	RootComponent = Trigger;
	Mesh->SetupAttachment(Trigger);
	Effect->SetupAttachment(Trigger);

	Trigger->SetCollisionProfileName(CPROFILE_ABTRIGGER);
	Trigger->SetBoxExtent(FVector( 40.0f, 42.0f, 30.0f ));
	Trigger->OnComponentBeginOverlap.AddDynamic(this, &AABItemBox::OnOverlapBegin);

	static ConstructorHelpers::FObjectFinder<UStaticMesh> BoxMeshRef(TEXT("/Script/Engine.StaticMesh'/Game/ArenaBattle/Environment/Props/SM_Env_Breakables_Box1.SM_Env_Breakables_Box1'"));
	if (BoxMeshRef.Object)
	{
		Mesh->SetStaticMesh(BoxMeshRef.Object);
	}
	Mesh->SetRelativeLocation(FVector(0.0f, -3.5f, -30.0f));
	Mesh->SetCollisionProfileName("NoCollision");

	static ConstructorHelpers::FObjectFinder<UParticleSystem> EffectRef(TEXT("/Script/Engine.ParticleSystem'/Game/ArenaBattle/Effect/P_TreasureChest_Open_Mesh.P_TreasureChest_Open_Mesh'"));
	if (EffectRef.Object)
	{
		Effect->SetTemplate(EffectRef.Object);
		Effect->bAutoActivate = false;
	}
}

void AABItemBox::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepHitResult)
{
	Effect->Activate(true);
	Mesh->SetHiddenInGame(true);
	SetActorEnableCollision(false);
	Effect->OnSystemFinished.AddDynamic(this, &AABItemBox::OnEffectFinished);
}

void AABItemBox::OnEffectFinished(UParticleSystemComponent* ParticleSystem)
{
	Destroy();
}

 

닿으면 사라지고, 이펙트까지 잘 보임

이제 아이템 상자에서 무기를 얻을 수 있도록 설정할 것

데이터 애셋 만들어서 관리해주기

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "ABItemData.generated.h"

UENUM(BlueprintType)
enum class EItemType : uint8
{
	Weapon = 0,
	Potion,
	Scroll
};

/**
 * 
 */
UCLASS()
class ARENABATTLE_API UABItemData : public UPrimaryDataAsset
{
	GENERATED_BODY()
	
public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Type)
	EItemType Type;
};

데이터 애셋

이걸 상속받은 무기 아이템을 만들 수 있음

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Item/ABItemData.h"
#include "ABWeaponItemData.generated.h"

/**
 * 
 */
UCLASS()
class ARENABATTLE_API UABWeaponItemData : public UABItemData
{
	GENERATED_BODY()
	
public:
	UPROPERTY(EditAnywhere, Category = Weapon)
	TObjectPtr<USkeletalMesh> WeaponMesh;
};

이런식으로 설정 가능

포션이랑 스크롤은 상속 안받고 그냥 사용

 

프로젝트에서 사용할 아이템 애셋

- 무기 타입 : 캐릭터에 무기를 부착 - 공격거리, 공격반경, 공격속도, 공격대미지

- 포션 타입 : 캐릭터의 HP를 회복

- 스크롤 타입 : 캐릭터의 기본 스탯을 상승 - 기본공격력, 기본방어력, 최대HP, 이동속도

* 구체적인 건 다음에 할 예정

 

의존성 분리를 위한 설계 규칙

프로젝트의 주요 레이어

- 게임 레이어 : 게임 로직을 구체적으로 구현하는데 사용 (캐릭터, 게임 모드 등등)

- 미들웨어 레이어 : 게임에 사용되는 미들웨어 모듈 (UI, 아이템, 애니메이션, AI 등등)

- 데이터 레이어 : 게임 구성하는 기본 데이터 (스탯 정보, 캐릭터 레벨 테이블 등등)

위에서 아래로는 직접 참조, 아래에서 위로는 인터페이스를 통해 접근

아이템 획득 시 캐릭터가 아이템 습득하도록 해야 함

인터페이스 만들고 TakeItem 추가

박스에서 인터페이스의 함수를 콜할 것임

 

스위치문으로 아이템 구분을 할 수도 있긴 한데 델리게이트 쓸 거임

 

델리게이트 배열로 만들어줌

C#은 델리게이트 그냥 배열로 넣을 수 있는데  C++은 구조체로 감싸야 할 수 있는듯

 

바인딩해주기

 

.h
.cpp

void AABCharacterBase::TakeItem(UABItemData* InItemData)
{
	if ( InItemData )
	{
		TakeItemActions[(uint8)InItemData->Type].ItemDelegate.ExecuteIfBound(InItemData);
	}
}

void AABCharacterBase::DrinkPotion(UABItemData* InItemData)
{
	UE_LOG(LogABCharacter, Log, TEXT("Drink Potion"));
}

void AABCharacterBase::EquipWeapon(UABItemData* InItemData)
{
	UE_LOG(LogABCharacter, Log, TEXT("Equip Weapon"));
}

void AABCharacterBase::ReadScroll(UABItemData* InItemData)
{
	UE_LOG(LogABCharacter, Log, TEXT("Read Scroll"));
}

로그 지정해서 테스트 해보기

 

각 오브젝트에서 저렇게 지정해주고 테스트

 

 

확인 가능함

이제 무기 상자 획득했을 때 캐릭터 손에 무기 주기

 

캐릭터의 특정 본에 부착할 수 있도록 소켓 이름을 줄 수 있음

 

스켈레탈 메시 설정

메시 지정은 아까 에디터에서 해줬음

 

무기 획득 성공

스켈레탈 메시 직접 들어가서 메시 변경하는 것도 가능

 

소프트 레퍼런싱

소프트 레퍼런싱 vs 하드 레퍼런싱

- 액터 로딩 시 TObjectPtr로 선언한 언리얼 오브젝트도 따라서 메모리에 로딩됨 (하드 레퍼런싱)

- 게임 진행에 필수적인 언리얼 오브젝트는 이렇게 선언해도 되지만 아이템은 굳이 그럴 필요 없음

- 필요한 데이터만 로딩하도록 TSoftObjectPtr로 선언하면 대신 애셋 주소 문자열을 지정

- 필요 시에 애셋을 로딩하도록 구현을 변경할 수 있으나 애셋 로딩 시간이 소요됨

- 현재 게임에서 로딩되어 있는 스켈레탈 메시 목록 살펴보기?

    - 콘솔 명령어: Obj List Class=SkeletalMesh

 

에디터 끄고 틸드 명령하고 콘솔 명령어 치면..

아이템 안먹었는데 드래곤 소드 스켈레탈 메시가 로딩되어 있음

아이템 박스에서 소프트 레퍼런싱으로 바꿔주고 에디터 재빌드

 

그러면 에러 뜸

소프트 레퍼런싱이라 아직 로딩이 된 건지 아닌지 모르기 때문

 

로딩되지 않은 상태면 동기적으로 로딩하고, Get으로 가져와야 함

왠진 모르겠는데, 드래곤 소드가 항상 로드되는 문제가 있었음

DerivedDataCache 폴더 삭제 등의 시도가 있었는데 결과적으로는,

데이터 에셋에 등록해뒀던 스켈레탈 메시를 클리어 후 재등록하고, 에디터 재시작하니까

소프트 레퍼런스가 정상 작동하는 걸 확인할 수 있었음