// Fill out your copyright notice in the Description page of Project Settings.
#include "Item/ABItemBox.h"
#include "Components/BoxComponent.h"
#include "Particles/ParticleSystemComponent.h"
#include "Physics/ABCollision.h"
// Sets default values
AABItemBox::AABItemBox()
{
// 서브오브젝트로 실제 객체 할당
Trigger = CreateDefaultSubobject<UBoxComponent>(TEXT("TriggerBox"));
Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
Effect = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("Effect"));
// 계층구조 설정 (Root를 Trigger로)
RootComponent = Trigger;
Mesh->SetupAttachment(Trigger);
Effect->SetupAttachment(Trigger);
// 충돌 프로필 설정 (어떤 채널과 충돌할지, 엔진에 Collision에 정의되어 있음)
// OnComponentBeginOverlap라는 콜리전에 이미 정의된 이벤트(델리게이트)에 사용할 함수를 바인딩
// 이런 델리게이트는 블루프린트에도 사용할 수 있도록 다이나믹으로 선언되어 있다. 따라서 UFUNCTION()을 사용해야 한다.
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(TEXT("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;
}
Effect->OnSystemFinished.AddDynamic(this, &AABItemBox::OnEffectFinished); // 이벤트 바인딩은 위쪽으로 빼는게 더 바람직해 보임..
}
void AABItemBox::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepHitResult)
{
Effect->Activate(true);
Effect->Activate(true);
Mesh->SetHiddenInGame(true);
SetActorEnableCollision(false);
}
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"
// 블루프린트와 호환되도록 BlueprintType으로 선언
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:
UABWeaponItemData();
UPROPERTY(EditAnywhere, Category = Weapon)
TSoftObjectPtr<USkeletalMesh> WeaponMesh;
};
아이템 애셋의 설계
프로젝트에서 사용할 아이템 에셋
총 3가지 종류의 아이템 타입을 지정
무기 타입: 캐릭터에 무기를 부착 (무기에 의한 부가 스탯 강화)
포션 타입: 캐릭터의 HP를 회복
스크롤 타입: 캐릭터의 기본 스탯을 상승
실제 스탯 구현은 차후 강좌에서 진행
아이템 애셋의 관리
현재 ItemData를 부모 클래스로 상속 받은 세 가지 종류의 아이템 클래스를 선언하였다. UItemWeaponData과 같이 자식 클래스로 상속 구조로 가져감
의존성 분리를 위한 설계 규칙
새로운 아이템이 추가될 때 마다 새로운 작업을 해야하는 것은 번거로울 수 있다. (사실 번거러운 것은 다른 문제고 새로운 아이템이 추가될 때 마다 기존 로직에 최대한 영향을 주지 않도록 하는 것이 중요하다. 이것이 의존성 분리의 핵심)
프로젝트의 주요 레이어
데이터 레이어: 게임을 구성하는 기본 데이터 (스탯 정보, 캐릭터 레벨 테이블 등등)
미들웨어 레이어: 게임에 사용되는 미들웨어 모듈 (UI, 아이템, 애니메이션, AI 등등)
게임 레이어: 게임 로직을 구체적으로 구현하는데 사용 (캐릭터, 게임 모드 등등)
위에서 아래로는 직접 참조하되, 아래에서 위로는 인터페이스를 통해 접근하도록 한다.
이 부분 되게 중요한 듯, 미들웨어 레이어에서는 게임 레이어의 헤더를 참조하지 않도록 해야 한다.
실습
//////h/////////
// 아이템을 획득했을 때 호출되는 델리게이트 이름은 FOnTakeItemDelegate이며 인자를 하나 가지며 인자는 UABItemData* 타입이다.
DECLARE_DELEGATE_OneParam(FOnTakeItemDelegate, class UABItemData* /*InItemData*/);
// 델리게이트로 변수를 만들 수 없기 때문에, 델리게이트를 래핑하는 구조체를 만들어서 변수로 사용한다.
USTRUCT(BlueprintType)
struct FTakeItemDelegateWrapper
{
GENERATED_BODY()
FTakeItemDelegateWrapper() {}
FTakeItemDelegateWrapper(const FOnTakeItemDelegate& InItemDelegate) : ItemDelegate(InItemDelegate) {}
FOnTakeItemDelegate ItemDelegate;
};
protected:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Equipment, Meta = (AllowPrivateAccess = "true"))
TObjectPtr<class USkeletalMeshComponent> Weapon;
UPROPERTY()
TArray<FTakeItemDelegateWrapper> TakeItemActions;
virtual void TakeItem(class UABItemData* InItemData) override;
virtual void DrinkPotion(class UABItemData* InItemData);
virtual void EquipWeapon(class UABItemData* InItemData);
virtual void ReadScroll(class UABItemData* InItemData);
//////cpp/////////
// Item Actions
TakeItemActions.Add(FTakeItemDelegateWrapper(FOnTakeItemDelegate::CreateUObject(this, &AABCharacterBase::EquipWeapon)));
TakeItemActions.Add(FTakeItemDelegateWrapper(FOnTakeItemDelegate::CreateUObject(this, &AABCharacterBase::DrinkPotion)));
TakeItemActions.Add(FTakeItemDelegateWrapper(FOnTakeItemDelegate::CreateUObject(this, &AABCharacterBase::ReadScroll)));
// Weapon Component
Weapon = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Weapon"));
Weapon->SetupAttachment(GetMesh(), TEXT("hand_rSocket"));
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)
{
UABWeaponItemData* WeaponItemData = Cast<UABWeaponItemData>(InItemData);
if (WeaponItemData)
{
if (WeaponItemData->WeaponMesh.IsPending())
{
WeaponItemData->WeaponMesh.LoadSynchronous();
}
Weapon->SetSkeletalMesh(WeaponItemData->WeaponMesh.Get());
}
}
void AABCharacterBase::ReadScroll(UABItemData* InItemData)
{
UE_LOG(LogABCharacter, Log, TEXT("Read Scroll"));
}
소프트 레퍼런싱 vs 하드 레퍼런싱
액터 로딩 시 TObjectPtr로 선언한 언리얼 오브젝트도 따라서 메모리에 로딩된다. 이를 하드 레퍼런싱이라고 하는데 만약 게임 진행에 필수적인 언리얼 오브젝트는 이렇게 선언해도 되지만 아이템의 경우 데이터 라이브러리에 1000종의 아이템 목록이 있을 때 이를 모두 다 로딩할 것은 매우 비효율적이다.
따라서 필요한 데이터만 로딩하도록 TSoftObjectPtr로 선언하고 대신 애셋 주소 문자열을 지정한다. 필요시에 에셋을 로딩하도록 구현을 변경할 수 있으나 에셋 로딩 시간이 소요된다. 현재 게임에서 로딩되어 있는 스켈레탈 메시의 목록을 살펴보면 실제 로딩이 되는 것을 알 수 있다. 따라서 SoftObjectPtr로 선언하여 확인 할 수 있다.
8강: 아이템 시스템
프로젝트 준비
트리거 박스의 설정
트리거 박스의 구현
실습
아이템 애셋의 설계
프로젝트에서 사용할 아이템 에셋
아이템 애셋의 관리
현재
ItemData
를 부모 클래스로 상속 받은 세 가지 종류의 아이템 클래스를 선언하였다.UItemWeaponData
과 같이 자식 클래스로 상속 구조로 가져감의존성 분리를 위한 설계 규칙
새로운 아이템이 추가될 때 마다 새로운 작업을 해야하는 것은 번거로울 수 있다. (사실 번거러운 것은 다른 문제고 새로운 아이템이 추가될 때 마다 기존 로직에 최대한 영향을 주지 않도록 하는 것이 중요하다. 이것이 의존성 분리의 핵심)
프로젝트의 주요 레이어
이 부분 되게 중요한 듯, 미들웨어 레이어에서는 게임 레이어의 헤더를 참조하지 않도록 해야 한다.
실습
소프트 레퍼런싱 vs 하드 레퍼런싱
액터 로딩 시 TObjectPtr로 선언한 언리얼 오브젝트도 따라서 메모리에 로딩된다. 이를 하드 레퍼런싱이라고 하는데 만약 게임 진행에 필수적인 언리얼 오브젝트는 이렇게 선언해도 되지만 아이템의 경우 데이터 라이브러리에 1000종의 아이템 목록이 있을 때 이를 모두 다 로딩할 것은 매우 비효율적이다.
따라서 필요한 데이터만 로딩하도록 TSoftObjectPtr로 선언하고 대신 애셋 주소 문자열을 지정한다. 필요시에 에셋을 로딩하도록 구현을 변경할 수 있으나 에셋 로딩 시간이 소요된다. 현재 게임에서 로딩되어 있는 스켈레탈 메시의 목록을 살펴보면 실제 로딩이 되는 것을 알 수 있다. 따라서 SoftObjectPtr로 선언하여 확인 할 수 있다.
정리