[UE5] 아이템 파밍 시스템 개선 및 구현

연하·2024년 6월 18일
0

Trapper

목록 보기
11/32
post-thumbnail

오늘은 아이템 파밍 관련 시스템을 구현했다.

  • 아이템 습득 시, 메시가 사라지고 빛 이펙트로 변경됨
  • 캐릭터의 오른손으로 습득
  • 재료 아이템이 9999개 이상일 경우 획득 불가
  • 먼저 접근한 쪽이 습득
  • 아이템 UI

메쉬 숨기기, 이펙트 적용

// Item.h

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Components")
TObjectPtr<class UStaticMeshComponent> Mesh;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Components")
class UParticleSystemComponent* LightEffect;

// Item.cpp

AItem::AItem()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
	Mesh->SetVisibility(true);
	RootComponent = Mesh;

	LightEffect = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("Light Effect"));
	LightEffect->bAutoActivate = false;
	LightEffect->SetupAttachment(Mesh);

	static ConstructorHelpers::FObjectFinder<UParticleSystem> ParticleAsset(TEXT("/Script/Engine.ParticleSystem'/Game/Blueprints/VFX/Particles/P_ky_waterBall.P_ky_waterBall'"));
	if (ParticleAsset.Succeeded())
	{
		LightEffect->SetTemplate(ParticleAsset.Object);
	}
}

Static Mesh Component와 Particle System Component를 붙혀주고, Particle System을 연결해주었다.

void AItem::SetCanBePulled(AActor* Player)
{
	Owner = Player;
    
	Mesh->SetVisibility(false);
	LightEffect->Activate();

	SetActorTickEnabled(true);
}

Tick 함수를 Enable해주는 SetCanBePulled() 함수가 호출될 때 Mesh를 숨겨주고 Effect를 활성화되도록 해줬다.

타겟 위치를 캐릭터 오른손으로 이동

void AItem::SetCanBePulled(AActor* Player)
{
	PlayerRef = Cast<ATrapperPlayer>(Player);
	if(!PlayerRef) return;

	Mesh->SetVisibility(false);
	LightEffect->Activate();

	SetActorTickEnabled(true);
}

캐릭터의 본 정보를 받아와 타겟 위치를 받아와야 하기 때문에 아이템이 ATrapperPlayer 포인터를 가지고 있도록 하였고, Owner = Player; 구문을 지운 뒤 캐릭터 포인터 변수에 캐스트하여 넣어주었다.

void AItem::BeDrawnToPlayer()
{
	FVector ActorTolerance(0.f, 0.f, 20.f);

	FVector RightHandBoneLocation = PlayerRef->GetMesh()->GetBoneLocation(TEXT("hand_r"));
	FVector ItemLocation = GetActorLocation();
	FVector NewPosition = FMath::VInterpTo(ItemLocation, RightHandBoneLocation, GetWorld()->GetDeltaSeconds(), 5.f);

	const float Tolerance = 20.f;
	if (ItemLocation.Equals(RightHandBoneLocation, Tolerance))
	{
		PlayerRef->AddBoneItem();
		Destroy();
	}

	SetActorLocation(NewPosition);
}

기존에는 Owner->GetActorLocation() 을 통해 타겟 위치를 받아오고 있었는데, 이부분을 PlayerRef->GetMesh()->GetBoneLocation(TEXT("hand_r")); 이렇게 바꿔주어 Bone의 Location을 받아와 타겟을 설정하도록 해주었다.

아이템 습득 로직 개선

플레이어의 FindMagneticItem() 함수 안에서 아이템의 Owner가 없을 때 SetCanBePulled() 함수를 호출할 수 있도록 해두었는데, Actor의 Owner를 쓰는 바람에 몬스터에서 드랍될 때 아이템이 습득되지 않는 문제가 있었다.

bool AItem::HasPlayerRef()
{
	return PlayerRef ? true : false;
}

아이템에 플레이어 포인터를 가지고 있는지 반환해주는 함수를 하나 만들어주고,

void ATrapperPlayer::FindMagneticItem()
{
 	// 생략
    
	if (bHasOverlap)
	{
		for (auto& Result : OverlapResults)
		{
			AItem* Item = Cast<AItem>(Result.GetActor());

			if (Item && !Item->HasPlayerRef())
			{
				Item->SetCanBePulled(this);
			}
		}
	}
}

플레이어 포인터를 가지고 있지 않을때 최초로 한번 호출해도록 수정해주었다. 이렇게 하면 무조건 제일 먼저 접근한 캐릭터가 아이템의 소유자가 된다!

아이템 획득, 사용 함수 만들기

우리 게임엔 아이템의 종류가 "뼈" 하나이기도 하고, 오로지 갯수만 체크하기 때문에 그냥 플레이어 내부에 변수로 가지고 있도록 해주었다.

// TrapperPlayer.h

int32 BoneItem = 0;

UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Item")
int32 MaxItemCount = 9999;

void AddBoneItem();
bool UseBoneItem(int32 Count);

// TrapperPlayer.cpp

void ATrapperPlayer::AddBoneItem()
{
	if (BoneItem < MaxItemCount)
	{
		BoneItem++;
	}
}

bool ATrapperPlayer::UseBoneItem(int32 Count)
{
	if (BoneItem >= Count)
	{
		BoneItem -= Count;
		return true;
	}

	return false;
}

최대 개수가 넘었을 때는 아이템이 추가되지 않도록, 갯수가 필요로 하는 것보다 적을때는 false를 되돌려주는 식으로 구현해놓았다.

스폰된 아이템도 잘 습득되고 있다 :)

아이템 UI 제작

이렇게 UI를 만들어주고, ItemText를 변수로 승격시켜 주었다.

UCLASS()
class TRAPPERPROJECT_API UPlayerHUD : public UUserWidget
{
	GENERATED_BODY()
	
public:
	UPROPERTY(meta=(BindWidget), VisibleAnywhere, BlueprintReadOnly, Category = "Item")
	TObjectPtr<class UTextBlock> ItemText;

public:
	class UTextBlock* GetItemText() { return ItemText; }
};

위젯의 C++ 코드에 UPROPERTY(meta=(BindWidget)) 를 사용해 위젯과 연동해주고, Get 함수를 만들어주었다.

// Uncomment if you are using Slate UI
PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });

Build.cs 파일에서 이부분도 주석을 해제해주었음!


void AItem::BeDrawnToPlayer()
{
	// 생략
    
	const float Tolerance = 20.f;
	if (ItemLocation.Equals(RightHandBoneLocation, Tolerance))
	{
		if (PlayerRef->HasAuthority())
		{
			PlayerRef->AddBoneItem();
			PlayerRef->SetItemText();
		}

		Destroy();
	}

	SetActorLocation(NewPosition);
}

Authority를 가질 때만 아이템을 먹어주도록 구현한 뒤,

UPROPERTY(ReplicatedUsing = OnRep_BoneItemBox, EditAnywhere, BlueprintReadOnly, Category = "Item")
int32 BoneItemBox = 0;

void ATrapperPlayer::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	DOREPLIFETIME(ATrapperPlayer, BoneItemBox);
}

프로퍼티 리플리케이션을 선언해주었다.

void ATrapperPlayer::SetItemText()
{
	if (PlayerHud && PlayerHud->GetItemText())
		PlayerHud->GetItemText()->SetText(FText::FromString(FString::FromInt(BoneItemBox)));
}

void ATrapperPlayer::OnRep_BoneItemBox()
{
	if (IsLocallyControlled())
		SetItemText();
}

OnRep() 함수는 그냥 위젯에 띄워주는 역할!

그럼 이렇게 아이템을 잘 습득한다 :)

0개의 댓글