Make Unreal REAL.
article thumbnail
이득우의 언리얼 C++ 게임 개발의 정석

 

레벨을 하나의 섹션(구역) 단위로 나누고 하나의 섹션을 클리어하면 새로운 섹션이 등장하는 무한 맵을 만들기 위한 섹션을 제작해보겠다.

 

새로운 액터 클래스를 만든다.

 

 

CoreMinimal.h를 ArenaBattle.h로 변경한다.

 

 

SM_SQUARE 스태틱 메시를 저장할 컴포넌트 변수를 선언한다.

  • Tick 기능은 사용하지 않을 것이므로 Tick() 함수 선언은 삭제한다.

 

 

Tick 기능을 끄고 SM_SQUARE 스새틱 메시 에셋을 불러온 후 루트 컴포넌트로 지정한다.

 

 

SM_SQUARE 스태틱 메시에는 각 방향별로 출입문과 섹션을 이어붙일 수 있도록 총 8개의 소켓이 부착되어 있다.

 

 

현재 가로 길이가 161cm인데 좌측 끝에 피벗이 존재하므로 배치할 떄는 Y축 -80.5cm에 배치해야 한다.

  • 상단의 Show Pivot 버튼을 누르면 피벗이 등장한다.

 

 

TArray<UStaticMeshComponent*> 타입의 배열을 만들어 섹션에 4개의 게이트를 부착한다.

 

 

생성자에서 게이트 매시를 가져오고 FName[] 타입의 소켓 목록을 만들어 SM_SQUARE의 각 소켓 위치에서 (0, -80.5, 0) 오프셋 위치에 게이트를 배치한다.

  • 마지막에 TArray.Add() 함수를 통해 게이트 목록에 Static Mesh 컴포넌트를 추가한다.

 

 

이번에는 ABCharacter만을 감지하는 용도로 사용할 ABTrigger라는 Collision 프리셋을 추가한다.

  • 물리적인 처리가 아니라 겹침만 확인할 것이므로 Collision Enabled: Query Only로 설정한다.

 

 

트리거로 사용할 Box 컴포넌트를 추가한다.

  • Trigger 박스는 캐릭터가 섹션에 입장하는 것을 감지한다.
  • GateTriggers 박스들은 캐릭터가 섹션을 나가는 것을 감지한다.

 

 

Box 컴포넌트를 생성한 후 크기와 위치를 설정하고 Collision 프리셋을 ABTrigger로 설정한다.

  • GateTriggers 배열에 Box 컴포넌트를 할당할 때는 FString::Append() 함수를 이용해 소켓 이름을 만든다.

 

 

섹션에 게이트와 트리거를 설치한 모습은 아래과 같다.

다음과 같이 설정하면 깨끗하게 확인할 수 있다.

  • 월드 아웃라이너에서 레벨의 눈 버튼을 눌러 모든 액터를 끈 후 섹션만 켠다.
  • View Mode를 Unlit으로 설정한다.

 

 

이제 섹션 액터의 로직을 상태 머신으로 설계해본다.

  • READY: 시작 상태이며 게이트를 열고 대기하다가 중앙의 Trigger에 감지되면 BATTLE 상태로 전환한다.
  • BATTLE: 전투 상태이며 게이트를 닫고 AI를 소환한 후 랜덤으로 아이템 상자도 생성하다가 AI가 죽으면 COMPLETE 상태로 전환한다.
  • COMPLETE: 클리어 상태이며 게이트를 열고 4개 중 하나의 GateTrigger에 감지되면 그 앞에 새로운 섹션을 생성한다.

 

한 가지 고려해야 할 것은, 캐릭터가 처음 시작하는 섹션은 전투 없이 COMPLETE 상태에서 시작할 수 있도록 해야 한다는 것이다.

  • 이를 위해 bool 타입의 NoBattle 속성을 액터에 추가한다.

 

열거형을 선언할 때 묵시적 변환을 허용하는 enum보다는 허용하지 않는 enum class를 쓰는 것이 바람직하다.

 

 

생성자의 마지막 부분에서 NoBattle 속성의 기본값을 설정한다.

 

 

BeginPlay() 함수에서 NoBattle 속성에 따라 COMPLETE 혹은 READY 상태로 초기화한다.

 

 

각 상태의 설정은 다음과 같다.

  • READY: 중앙 Trigger O, 게이트 GateTrigger X, 문 열림
  • BATTLE: 중앙 Trigger X, 게이트 GateTrigger X, 문 닫힘
  • COMPLETE: 중앙 Trigger X, 게이트 GateTrigger O, 문 열림

 

 

게이트를 열고 닫는 함수이다.

 

 

현재는 플레이를 눌러야 BeginPlay() 함수에서 COMPLETE 상태를 설정하면서 문이 열린다.

 

하지만 에디터 상태에서도 NoBattle 속성에 따라 문이 열리고 닫는 것을 볼 수 있는 방법이 있다.

  • 액터에는 에디터오 연동되는 OnConstruction()이라는 특별한 함수가 설계되어 있다.
  • 에디터 상에서 액터의 트랜스폼 정보나 속성이 변경되면 이 함수가 실행된다.

 

 

이렇게 작성해주면 에디터에서도 NoBattle 속성을 true로 바꿀 경우 문이 열리게 된다.

 

 

이제 중앙 Trigger와 게이트 GateTrigger에 겹침 이벤트를 처리해야 한다.

  • 중앙 Trigger는 READY 상태에서 캐릭터가 감지되면 BATTLE 상태로 전환한다.
  • 4개의 게이트 GateTrigger는 COMPLETE 상태에서 캐릭터가 감지되면 그 앞에 새로운 섹션을 생성한다.

 

이때, 새로운 섹션을 생성하기 전에 이미 앞에 섹션이 존재하는지 확인해야 한다.

 

OnComponentBeginOverlap 델리게이트는 BP에서도 사용 가능한 다이나믹 델리게이트이므로 등록할 함수에는 UFUNCTION() 매크로를 지정해줘야 한다.

 

 

Trigger와 4개의 GateTrigger의 OnComponentBeginOverlap 델리게이트에 각각 실행할 함수를 등록한다.

  • 4개의 GateTrigger가 하는 기능은 동일하므로 같은 함수를 등록해도 된다.
  • 하지만, 어떤 게이트에서 감지됐는지 구분하기 위해 컴포넌트에 소켓 이름으로 태그를 추가한다.

 

 

중앙 Trigger는 READY 상태에서 캐릭터가 감지되면 BATTLE 상태로 전환한다.

 

 

4개의 GateTrigger는 캐릭터가 감지되었을 때 우선 태그가 추가되었는지 확인한다.

  • 태그가 설정되지 않으면 4개 중 구분이 어렵기 때문이다.
  • 물론, GateTriggers 배열을 순회하며 OverlappedComp와 비교하며 찾을 수도 있긴 하다.

 

설정된 태그에서 FString.Left(2) 함수를 이용해 앞 2글자만 가져온다.

  • +XGate의 경우 +X로 만든다.
  • +X 소켓은 +X 방향으로 다음 섹션이 생성될 위치에 있다.

 

SM_SQUARE 메시에 해당 소켓이 존재하는지 확인한 후 USceneComponent::GetSocketLocation() 함수를 통해 소켓의 위치를 가져온다.

 

기존에는 OverlapMultiByChannel() 함수를 통해 채널에 해당하는 겹침을 판별했는데, 이번에는 OverlapMultiByObjectType() 함수를 통해 오브젝트 타입에 해당하는 겹침을 판별해본다.

  • Multi이므로 TArray<FOverlapResult> 타입의 배열을 레퍼런스로 전달한다.
  • 충돌 태그를 설정하지 않고 복잡하지 않은 충돌로 진행하며 현재 액터는 무시한다.
  • FCollisionObjectQueryParams::AllObjects를 지정해 모든 오브젝트를 테스트한다.
  • 반지름이 775cm인 구를 만들어 테스트한다.

 

 

겹친 오브젝트가 있으면 소켓의 위치에 새로운 섹션을 생성할 수 없고, 없으면 새로운 섹션을 생성한다.

  • UWorld::SpawnActor<AActor>() 함수로 AActor 타입의 액터를 월드에 스폰시킬 수 있다.

 

 

새로운 섹션에 들어오니 정상적으로 감지되어 BATTLE 상태로 전환되었다.

 

무한 맵을 위한 섹션 제작에 성공했다.

 

profile

Make Unreal REAL.

@diesuki4

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

검색 태그