2 minute read

‘이득우의 언리얼 c++ 게임 개발의 정석’ 책을 참고하여 작성한 포스트입니다.


무한 맵의 생성

  • 하나의 섹션을 클리어하면 새로운 섹션이 등장하는 무한 맵 스테이지를 제작해본다
  • 섹션에서 제공하는 일은 다음과 같다.
    • 섹션의 배경과 네 방향으로 캐릭터 입장을
  • 섹션 액터 제작을 위해 Actor를 부모 클래스로 하는 클래스를 생성한다.
  • 플레이어만을 감지하는 콜리전 프리셋을 하나 추가한다.
// section.h
...
class UBoxComponent;

UCLASS()
class LOOTERSHOOTER_API ALSSection : public AActor
{
    ...
private:
	UPROPERTY(VisibleAnywhere, Category = Mesh, Meta = (AllowPrivateAccess = true))
	UStaticMeshComponent* Mesh;

	UPROPERTY(VisibleAnywhere, Category = Trigger, Meta = (AllowPrivateAccess = true))
	UBoxComponent* Trigger;
};


// Section.cpp
#include "LSSection.h"
#include "Components/BoxComponent.h"

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

	Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MESH"));
	RootComponent = Mesh;

	FString AssetPath = TEXT("/Game/LS/Meshes/item.item");
	static ConstructorHelpers::FObjectFinder<UStaticMesh> ITEM(*AssetPath);
	if (ITEM.Succeeded())
	{
		Mesh->SetStaticMesh(ITEM.Object);
	}
	else 
	{
		LSLOG_S(Warning);
	}

	//#include "Components/BoxComponent.h" 
	Trigger = CreateDefaultSubobject<UBoxComponent>(TEXT("TRIGGER"));
	Trigger->SetupAttachment(RootComponent);
	Trigger->SetBoxExtent(FVector(40.0f, 40.0f, 100.0f));
	//Trigger->SetRelativeLocation()
	Trigger->SetCollisionProfileName(TEXT("LSTrigger"));
}
...

섹션 로직

  • 스테이트 머신으로 설계해 본다.
    • 준비 스테이트
      • 플레이어의 진입을 감지하면 전투 스테이트로 이동한다
    • 전투 스테이트
      • NPC를 소환하고, 일정 시간 후 아이템 상자도 소환한다.
      • NPC가 죽으면 완료 스테이트로 이동
    • 완료 스테이트
      • 닫힌 문 혹은 공간을 연다.
      • 혹은 다음 섹션으로 나아간다.
  • 액터에는 에디터와 연동되는 OnConstruction 이라는 함수가 설계돼 있다.
    • 에디터 작업에서 선택한 액터의 속성이나 트랜스폼 정보가 변경될 때 이 OnConstruction 함수가 실행된다.
  • 박스 컴포넌트의 OnComponentBeginOverlap 델리게이트도 이용해서,
  • 특정 물체와 닿으면 게임 스테이트를 battle 로 바뀌게 구현하자.
// section.h
...
private:
	enum class ESectionState : uint8
	{
		READY = 0,
		BATTLE,
		COMPLETE
	};

	void SetState(ESectionState NewState);
	ESectionState CurrentState = ESectionState::READY;


    UFUNCTION()
	void OnTriggerBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
		UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult &SweepResult);


// section.cpp
ALSSection::ALSSection()
{
    ...
    Trigger->OnComponentBeginOverlap.AddDynamic(this, &ALSSection::OnTriggerBeginOverlap);
}
...

void ALSSection::SetState(ESectionState NewState)
{
	switch (NewState)
	{
		case ESectionState::READY :
		{
			Trigger->SetCollisionProfileName(TEXT("LSTrigger"));
			break;
		}
		case ESectionState::BATTLE :
		{
			Trigger->SetCollisionProfileName(TEXT("NoCollision"));
			break;
		}
		case ESectionState::COMPLETE :
		{
			Trigger->SetCollisionProfileName(TEXT("NoCollision"));
			break;
		}
	}

	CurrentState = NewState;
}
void ALSSection::OnTriggerBeginOverlap(UPrimitiveComponent * OverlappedComponent, AActor * OtherActor, UPrimitiveComponent * OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult)
{
	if (CurrentState == ESectionState::READY)
	{
		SetState(ESectionState::BATTLE);
		LSLOG(Warning, TEXT("Set Battle State"));
	}
}

내비게이션 메시 시스템 설정

  • NPC 와 아이템 상자를 생성하는 기능을 추가해 보자.
  • 타이머 기능을 이용한다.
  • 새로 생성되는 NPC도 네비게이션 메시 영역이 설정되게 해주려면 프로젝트 세팅의 내비게이션 메시 설정에서 Runtime Generation 속성의 값을 Dynamic으로 변경해 준다.

FTimerHandle

  • Unique handle that can be used to distinguish timers that have identical delegates.
struct FTimerHandle

UWorld::GetTimerManager

  • Returns TimerManager instance for this world.
FTimerManager & GetTimerManager() const

FTimerManager::SetTimer

  • Sets a timer to call the given native function at a set interval.
template<class UserClass>
void SetTimer
(
    FTimerHandle & InOutHandle,
    UserClass * InObj,
    typename FTimerDelegate::TUObjectMethodDelegate< UserClass >::FMethodPtr InTimerMethod,
    float InRate,
    bool InbLoop,
    float InFirstDelay
)

// FTimerManager.h
template< class UserClass >
FORCEINLINE void SetTimer(FTimerHandle& InOutHandle, UserClass* InObj, typename FTimerDelegate::TMethodPtr< UserClass > InTimerMethod, float InRate, bool InbLoop = false, float InFirstDelay = -1.f)
{
	InternalSetTimer(InOutHandle, FTimerUnifiedDelegate( FTimerDelegate::CreateUObject(InObj, InTimerMethod) ), InRate, InbLoop, InFirstDelay);
}

Leave a comment