2024.06.27
깃허브! | 풀리퀘! |
---|---|
★ https://github.com/ChangJin-Lee/Project-Lena | ★ https://github.com/ChangJin-Lee/Project-Lena/pull/7 |
이번 포스팅에서는 퍼즐 기믹의 완성, InteractionActor, PickupItem, DoorActor 클래스를 중심으로 추가된 기능들을 소개합니다. 이를 통해 게임의 몰입감을 한층 더 높였습니다.
이번 작업을 진행하면서 만들었던 상속 관계도입니다. 이를 기반으로 만들었습니다.
새로 만든 클래스와 블루프린트 |
아이템을 집어야 하는 상황에서, 상호작용이 가능한 ActorComponent를 만들 것인가 아니면 InteractionActor
를 상속받은 PickupItem
을 만들 것인가에 대한 고민이 있었습니다. 저는 후자를 선택했습니다. 이유는 ActorComponent에서 owner의 Root에 특정 컴포넌트를 추가해야 했기 때문입니다.
ActorComponent 접근법은 기본적으로 재사용성과 모듈화를 높일 수 있지만, 컴포넌트의 복잡성을 증가시킬 수 있습니다. 다음은 ActorComponent를 사용하는 예제입니다:
void UInteractableComponent::Interact()
{
if (AActor* Owner = GetOwner())
{
// 상호작용 로직 구현
}
}
이 방법의 장점은 여러 액터에 동일한 컴포넌트를 추가하여 재사용할 수 있다는 점입니다. 그러나 단점은 각 액터의 Root에 접근하여 컴포넌트를 추가해야 하며, 이 과정이 복잡해질 수 있다는 점입니다.
이 접근법은 상호작용 기능을 구현한 기본 클래스인 InteractionActor
를 상속받아 PickupItem
을 생성하는 방식입니다. 이 방법의 주요 이점은 구현의 간결성과 상호작용 로직의 일관성입니다.
void APickupItem::PickUp()
{
ABase_Character* Character = Cast<ABase_Character>(UGameplayStatics::GetPlayerCharacter(this, 0));
if (Character)
{
Character->AddItemToInventory(ItemDetails);
SetActorHiddenInGame(true);
SetActorEnableCollision(false);
SetActorTickEnabled(false);
}
}
이 접근법은 상호작용 로직이 액터 클래스 내에서 직접 관리되므로, 코드의 가독성이 높아지고 유지보수가 용이해집니다.
ActorComponent를 사용하는 방법은 재사용성과 모듈화에서 장점이 있지만, 상호작용이 필요한 액터마다 별도의 설정이 필요하며, 이 과정이 복잡해질 수 있습니다. 반면에, InteractionActor
를 상속받은 PickupItem
을 사용하는 방법은 구현의 간결성과 유지보수의 용이성을 제공합니다. 따라서, 상호작용 로직이 명확히 정의된 특정 아이템을 구현하는 데 더 적합하다고 판단하였습니다.
InteractionActor
는 상호작용 가능한 액터를 구현하기 위한 기초 클래스입니다.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "InteractionActor.generated.h"
UCLASS()
class LENA_API AInteractionActor : public AActor
{
GENERATED_BODY()
public:
AInteractionActor();
protected:
virtual void BeginPlay() override;
UPROPERTY(EditDefaultsOnly)
TSubclassOf<UUserWidget> PasswordWidget;
public:
virtual void Tick(float DeltaTime) override;
UFUNCTION()
void OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
UFUNCTION()
void OnOverlapEnd(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
UFUNCTION(BlueprintCallable)
void AddWidget();
UFUNCTION(BlueprintCallable)
void RemoveWidget();
UFUNCTION(BlueprintImplementableEvent)
void OutSideEvent();
UFUNCTION(BlueprintCallable)
void HideTextRenderComponent();
UFUNCTION(BlueprintCallable)
void ShowTextRenderComponent();
UPROPERTY(BlueprintReadWrite)
bool IsDone = false;
private:
UPROPERTY(VisibleAnywhere)
USceneComponent* Root;
UPROPERTY(VisibleAnywhere)
UBoxComponent* HitBox;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
UTextRenderComponent* TextRenderComponent;
UPROPERTY(BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
APlayerController* PlayerController;
UUserWidget* Widget;
};
핵심 코드:
OnOverlapBegin
과 OnOverlapEnd
함수는 HitBox 컴포넌트에서 다른 액터와 겹칠 때 호출됩니다.AddWidget
과 RemoveWidget
함수는 위젯을 화면에 추가하거나 제거하는 역할을 합니다.void AInteractionActor::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
EnableInput(PlayerController);
if (!IsDone)
{
ShowTextRenderComponent();
}
}
void AInteractionActor::OnOverlapEnd(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
DisableInput(PlayerController);
HideTextRenderComponent();
OutSideEvent();
}
PickupItem
클래스는 InteractionActor
를 상속받아 상호작용 가능한 아이템을 구현합니다.
#pragma once
#include "CoreMinimal.h"
#include "Base_Item.h"
#include "InteractionActor.h"
#include "GameFramework/Actor.h"
#include "PickupItem.generated.h"
UCLASS()
class LENA_API APickupItem : public AInteractionActor
{
GENERATED_BODY()
public:
APickupItem();
protected:
virtual void BeginPlay() override;
public:
virtual void Tick(float DeltaTime) override;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Item")
ABase_Item* ItemDetails;
UFUNCTION(BlueprintCallable)
void PickUp();
UFUNCTION(BlueprintCallable)
void SetThisItemName(FName NewName);
private:
UPROPERTY(EditAnywhere)
UStaticMeshComponent* MeshComponent;
UPROPERTY(EditAnywhere)
USkeletalMeshComponent* SkeletalMeshComponent;
};
핵심 코드:
ItemDetails
는 아이템의 상세 정보를 담고 있는 ABase_Item
의 인스턴스입니다.PickUp
함수는 아이템을 플레이어의 인벤토리에 추가하고, 아이템을 비활성화합니다.void APickupItem::PickUp()
{
ABase_Character* Character = Cast<ABase_Character>(UGameplayStatics::GetPlayerCharacter(this, 0));
if (Character)
{
Character->AddItemToInventory(ItemDetails);
SetActorHiddenInGame(true);
SetActorEnableCollision(false);
SetActorTickEnabled(false);
}
}
DoorActor
클래스는 InteractionActor
를 상속받아 문을 여는 기능을 구현합니다.
#pragma once
#include "CoreMinimal.h"
#include "InteractionActor.h"
#include "DoorActor.generated.h"
UCLASS()
class LENA_API ADoorActor : public AInteractionActor
{
GENERATED_BODY()
public:
ADoorActor();
UFUNCTION(BlueprintCallable)
void OpenSlidingDoor(FVector Location);
UFUNCTION(BlueprintImplementableEvent, Category="Inventory")
void OpenDoorEvent();
UFUNCTION(BlueprintCallable)
bool CheckRequiredItem();
protected:
virtual void BeginPlay() override;
private:
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Door", meta = (AllowPrivateAccess = "true"))
UStaticMeshComponent* MeshComponent;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Door", meta = (AllowPrivateAccess = "true"))
FName RequiredItem;
UPROPERTY(EditAnywhere, Category="Door")
TSubclassOf<UCameraShakeBase> WrongAnswerCameraShakeClass;
};
핵심 코드:
OpenSlidingDoor
함수는 문을 여는 로직을 구현합니다.CheckRequiredItem
함수는 플레이어가 문을 열기 위해 필요한 아이템을 소지하고 있는지 확인합니다.void ADoorActor::OpenSlidingDoor(FVector Location)
{
MeshComponent->SetRelativeLocation(Location);
}
bool ADoorActor::CheckRequiredItem()
{
ABase_Character* Character = Cast<ABase_Character>(UGameplayStatics::GetPlayerCharacter(this, 0));
if (Character && Character->HasItemInInventory(RequiredItem))
{
return true;
}
if (WrongAnswerCameraShakeClass)
{
GetWorld()->GetFirstPlayerController()->ClientStartCameraShake(WrongAnswerCameraShakeClass);
}
return false;
}
BP_SlidingDoor: DoorActor
를 기반으로 문이 슬라이딩으로 열리는 블루프린트입니다. 상호작용 시 WBP_Numpad
위젯이 나타나고, 올바른 비밀번호를 입력하면 문이 열립니다.
여기서 만든 WBP_Numpad
는 Canvas 위에 균등 그리드 패널을 사용해서 만들었습니다.
WBP_Numpad |
WBP_Numpad |
버튼 클릭에 대한 함수 |
Enter, Clear 함수 |
문 열리는 부분 |
PickupItem
을 기반으로 만든 블루프린트로, 상호작용 시 아이템을 플레이어의 인벤토리에 추가합니다.아이템 이름을 지정하고 PickUp |
현재 아이템 이름을 각 블루프린트에서 직접 지정해야 하는 문제가 있습니다. 이를 해결하기 위해 고유한 값으로 바꿀 예정입니다.
WBP_Hint
를 상호작용 시 나타내는 블루프린트입니다.위젯을 띄우는 로직 | 힌트 위젯 |
Door의 문이 열리게끔 만듭니다 |