Showing

[Unreal 디자인 패턴] 컴포넌트 패턴 본문

Design Pattern

[Unreal 디자인 패턴] 컴포넌트 패턴

RabbitCode 2023. 12. 11. 12:25

 

언리얼에서는 이미 컴포넌트 패턴을 적용하고 있다.

개발자들 역시 이 컴포넌트 패턴을 이용해 자주 사용할 만한 컴포넌트를 만들어놓고 필요한 액터에 붙일 수 있다.

Unreal의 컴포넌트 패턴(inventory c++ 예제)

인벤토리 컴포넌트를 만들어서 캐릭터에 붙이는 예제를 살펴보도록 한다.

InventoryComponent

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

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "InventoryComponent.generated.h"


UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class THIRDPERSONTEMPLATE_API UInventoryComponent : public UActorComponent
{
	GENERATED_BODY()

public:	
	// Sets default values for this component's properties
	UInventoryComponent();
	// 아이템 추가하기
	UFUNCTION(BlueprintCallable)
	void AddItem(const FName& ItemName, int32 Quantity);

	// 아이템 제거하기
	UFUNCTION(BlueprintCallable)
	void RemoveItem(const FName& ItemName, int32 Quantity);

	// 해당 아이템 수량 가져오기 == Find
	UFUNCTION(BlueprintPure)
	int32 GetItemQuantity(const FName& ItemName) const;

protected:
	// Called when the game starts
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
private: // 접근못하게 막고, 접근 함수만을 public으로 둔다.
	TMap<FName, int32> InventoryMap;
};

참고로 인자와 함수에 const를 달면 좋은 이유는 아래와 같이 chat gpt로 확인해보았다.

void UInventoryComponent::AddItem(const FName& ItemName, int32 Quantity)
{
	//Find로 나오는 주소
	int32* recentQuantity = InventoryMap.Find(ItemName);
	if (recentQuantity) {
		// 이미 해당 아이템이 인벤토리에 있는 경우 수량 증가
		*recentQuantity += Quantity;
	}
	else {
		InventoryMap.Add("ItemName", Quantity);
	}
}

 

void UInventoryComponent::RemoveItem(const FName& ItemName, int32 Quantity)
{
	//Find로 나오는 주소
	int32* recentQuantity = InventoryMap.Find(ItemName);
	if (recentQuantity) {
		// 이미 해당 아이템이 인벤토리에 있는 경우 수량 감소
		*recentQuantity -= Quantity;
		if (*recentQuantity <=0) {
			InventoryMap.Remove(ItemName);
		}
	}
}

 

int32 UInventoryComponent::GetItemQuantity(const FName& ItemName) const
{
	const int32* recentQuantity = InventoryMap.Find(ItemName);
	return recentQuantity ? *recentQuantity : 0;
}

위와 같이 블루프린트에서 컴포넌트로 달고 만든 함수를 가져올 수 있다.

전체 코드

#include "InventoryComponent.h"

UInventoryComponent::UInventoryComponent()
{
	PrimaryComponentTick.bCanEverTick = true;
}

void UInventoryComponent::AddItem(const FName& ItemName, int32 Quantity)
{
	//Find로 나오는 주소
	int32* recentQuantity = InventoryMap.Find(ItemName);
	if (recentQuantity) {
		// 이미 해당 아이템이 인벤토리에 있는 경우 수량 증가
		*recentQuantity += Quantity;
	}
	else {
		InventoryMap.Add("ItemName", Quantity);
	}
}

void UInventoryComponent::RemoveItem(const FName& ItemName, int32 Quantity)
{
	//Find로 나오는 주소
	int32* recentQuantity = InventoryMap.Find(ItemName);
	if (recentQuantity) {
		// 이미 해당 아이템이 인벤토리에 있는 경우 수량 감소
		*recentQuantity -= Quantity;
		if (*recentQuantity <=0) {
			InventoryMap.Remove(ItemName);
		}
	}
}

int32 UInventoryComponent::GetItemQuantity(const FName& ItemName) const
{
	const int32* recentQuantity = InventoryMap.Find(ItemName);
	return recentQuantity ? *recentQuantity : 0;
}


// Called when the game starts
void UInventoryComponent::BeginPlay()
{
	Super::BeginPlay();
}


// Called every frame
void UInventoryComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
}

 

라인트레이스 컴포넌트화 예제

라인트레이스 역시 컴포넌트화하여 여러 액터에 붙일 수 있다.

똑같이 액터 컴포넌트

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

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "LineTraceDetector.generated.h"


UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class THIRDPERSONTEMPLATE_API ULineTraceDetector : public UActorComponent
{
	GENERATED_BODY()

public:	
	// Sets default values for this component's properties
	ULineTraceDetector();

protected:
	// Called when the game starts
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

	UFUNCTION(BlueprintCallable)
	void PerformLineTrace();

private:
	UPROPERTY(EditAnywhere)
	float TraceDistance = 1000.f; // 트레이스 거리
	UPROPERTY(EditAnywhere)
	TSubclassOf<AActor> ActorClassToTrace;// 찾고자 하는 액터 클래스

	UFUNCTION(BlueprintCallable)
	void OnLineTraceHit(FHitResult HitResult);
		
};

 

void ULineTraceDetector::OnLineTraceHit(FHitResult HitResult)
{
	//HitResult.GetActor()->IsA(ActorClassToTrace) 
	//HitResult.GetActor()==ActorClassToTrace
	if (HitResult.GetActor() && HitResult.GetActor()->IsA(ActorClassToTrace))
	{
		// 원하는 클래스의 액터를 찾은 경우
		AActor* FoundActor = HitResult.GetActor();
		// 여기서 원하는 작업 ex 싸운다, 죽인다
		if (FoundActor) {
			GEngine->AddOnScreenDebugMessage(-1,5.9f, FColor::Green,(TEXT("%s"), *FoundActor->GetName()));
		}
	}
}

 

void ULineTraceDetector::PerformLineTrace()
{
	FHitResult _HitOut;
	AActor* ownerActor = Cast<AActor>(GetOwner());
	FVector _Start = ownerActor->GetActorLocation();
	FVector _End = _Start + (ownerActor->GetActorForwardVector() * TraceDistance);

	FCollisionQueryParams _TraceCollisionParams;
	
	_TraceCollisionParams.AddIgnoredActor(ownerActor);

	bool bHit = GetWorld()->LineTraceSingleByChannel(_HitOut, _Start, _End, ECollisionChannel::ECC_Visibility, _TraceCollisionParams);
	if (bHit) //hit 가 있다면,
	{
		OnLineTraceHit(_HitOut);
	}
}

owner가 pawn이면 IA를 달아주어서 아래와 같이 키보드나 마우스 인터랙션을 줄 수 있겠다.

// Fill out your copyright notice in the Description page of Project Settings.
#include "LineTraceDetector.h"
#include "DrawDebugHelpers.h"
// Sets default values for this component's properties
ULineTraceDetector::ULineTraceDetector()
{
	PrimaryComponentTick.bCanEverTick = true;
}

// Called when the game starts
void ULineTraceDetector::BeginPlay()
{
	Super::BeginPlay();
}

// Called every frame
void ULineTraceDetector::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
}

void ULineTraceDetector::PerformLineTrace()
{
	FHitResult _HitOut;
	AActor* ownerActor = Cast<AActor>(GetOwner());
	FVector _Start = ownerActor->GetActorLocation();
	FVector _End = _Start + (ownerActor->GetActorForwardVector() * TraceDistance);

	FCollisionQueryParams _TraceCollisionParams;
	
	_TraceCollisionParams.AddIgnoredActor(ownerActor);

	bool bHit = GetWorld()->LineTraceSingleByChannel(_HitOut, _Start, _End, ECollisionChannel::ECC_Visibility, _TraceCollisionParams);
	DrawDebugLine(GetWorld(), _Start, _End, FColor::Black, true, 3.0f);
	if (bHit) //hit 가 있다면,
	{
		OnLineTraceHit(_HitOut);
	}
}

void ULineTraceDetector::OnLineTraceHit(FHitResult HitResult)
{
	//HitResult.GetActor()->IsA(ActorClassToTrace) 
	//HitResult.GetActor()==ActorClassToTrace
	if (HitResult.GetActor() && HitResult.GetActor()->IsA(ActorClassToTrace))
	{
		// 원하는 클래스의 액터를 찾은 경우
		AActor* FoundActor = HitResult.GetActor();
		// 여기서 원하는 작업 ex 싸운다, 죽인다
		if (FoundActor) {
			GEngine->AddOnScreenDebugMessage(-1,5.9f, FColor::Green,(TEXT("%s"), *FoundActor->GetName()));
		}
	}
}