언리얼 프로젝트 폴더 내 구조

  • [PrijectName].Sln
    • C++ 소스코드 및 빌드 설정 연결 파일
  • [ProjectName].uproject
    • 언리얼 에디터 프로젝트 설정 (콘텐츠, 맵, 플러그인 등)을 불러 올 때 사용하는 파일
  • .vsconfig
    • Visual Studio 프로젝트에서 사용되는 특정 설정 구성 요소 설치 요구사항 정의
    • 프로젝트를 열 때 Visual Studio가 필요한 워크로드 및 구성 요소를 자동으로 감지한다.
  • .vs
    • Visual Studio가 제공하는 자동으로 생성으로 생성되는 폴더
    • 디버깅, 솔루션 설정 관련 파일
  • Binaries
    • C++ 코드 빌드 시 생성되는 실행 파일 (.exe, dll)이 저장되는 폴더
  • Config
    • 게임 플레이, 렌더링, 입력 등의 설정을 담은 .ini 파일
    • 에디터 혹은 코드로 특정 설정을 바꾸면 해당 ini 파일이 수정된다.
      • DefaultEditor : 에디터 환경 설정 (뷰포트, UI 등)
      • DefaultEngine : 엔진 전반 설정(렌더링, 네트워킹 등)
      • DefaultGame : 게임 플레이 관련 설정(게임 모드, 플레이어 컨트롤러 클래스 등)
      • DefaultInput : 키보드, 마우스, 패드 등의 기본 입력들을 바인딩
  • Content
    • 언리얼 에디터의 콘텐츠 브라우저와 연결되어 있다.
    • 프로젝트 내 모든 에셋들을 포함하고 있는 폴더.
  • DerivedDataCache
    • 에셋, 셰이더 등을 빠르게 처리하기 위한 캐시 파일들을 저장하는 폴더.
  • Intermediate
    • C++ 컴파일시 생성되는 임시파일을 저장하는 폴더
  • Saved
    • 자동저장, 파일, 로그, 크래시 덤프들을 저장하는 폴더
  • Source
    • C++ 소스코드가 들어있는 폴더
    • 실제 게임 로직을 작성하는 곳
    • 빌드 설정 관련 주요 파일 포함
      • [ProjectName].Build.cs : 해당 프로젝트에 필요한 모듈, 라이브러리, 종속성 등 정의
      • [ProjectName].Target.cs, Editor.Target.cs : 각각 게임 실행 용, 에디터용 빌드 방식 정의

 

C++ 재 빌드 / 클래스 삭제 후 재 빌드시 

  • sln, vs, intermediate, binaries, DericedDataCache, Save들은 빌드 시 생성, 솔루션에 대한 빌드후 정보가 저장되는 파일
  • 해당 폴더 삭제 후 uproject -> generate visual studio project files를 통한 빌드
  • 클래스 삭제 / C++클래스 디렉토리 이동 / 빌드 실패등의 경우 사용하는 방법

 

언리얼 솔루션 구조

  • Engine
    • 언리얼 엔진 자체 소스코드 및 리소스가 담긴 폴더
    • 에디터 작동, 엔진 코어 관련 빌드 포함
  • Games
    • 개발자가 만든 프로젝트 코드들에 대한 폴더
    • Source, Config, .uproject 파일 등
      • Build.cs : 프로젝트 빌드 설정을 관리하는 파일
      • Public Dependency
        • 컴파일 대상 모듈 정의
        • 어떤 모듈을 포함할 지, 어떤 종속성을 가질지 결정한다.
        • 모듈을 포함하는 다른 모듈에서도 접근이 가능하다.
          • 이 모듈을 참조하는 다른 모듈에서도 해당 라이브러리를 사용할 수 있다.
        • 해당 프로젝트에서 필수적으로 사용하는 엔진 기능들 
          • Core : 엔진의 기본 기능들
          • CoreUObject : 리플렉션 시스템, GC
          • Engine : 게임 엔진의 주요 기능
          • InputCore : 입력 시스템
          • EnhancedInput : UE5에서 추가된 입력 시스템
          • UMG : 위젯 블루프린트와 관련된 모듈
      • PrivateDependency
        • 이 모듈 내에서만 사용 가능하며, 다른 모듈에서는 접근이 불가능하다.
        • 이 모듈을 참조하는 다른 모듈은 해당 의존성을 알 필요 없을 때
        • Slate, SlateCore같은 UI관련 특정 기능을 사용할 경우
  • Programs
    • 엔진 동작에 필요한 유틸리티 프로그램, 서버 모듈
  • Rules
    • 엔진, 게임 등 각 모듈의 빌드 규칙을 정의해놓은 파일들이 모여있는 곳
    • 모듈 의존성, 플러그인 활성화 여부, 빌드 대상 등을 제어한다.
  • Visualizers
    • 디버깅시 언리얼 엔진 관련 자료구조를 보기좋게 표시하기 위한 설정 파일

 

언리얼 빌드 프로세스

  • C++ 코드 수정시 컴파일 + Link과정을 통한 동적 라이브러리 DLL 생성
  • DLL이 언리얼 에디터에서 로드, 새로 작성한 로직(함수, 클래스 등)이 게임,에디터에 즉시 반영된다.
  • 소스파일 -> 전처리기-> 컴파일 -> 링커 -> 실행
    • 전처리기 (Pre-Processing)
      • 소스코드 주석 제거, define 치환 등 .cc 파일로 변환
    • 컴파일러
      • 어셈블리 파일로 변환, .s 파일 변환
    • 어세블러
      • 오브젝트 코드 파일로 변환. .o 파일 변환
    • 링커
      • 오브젝트 파일들을 묶어 실행 코드 파일로 변환
      • .o + .lib
    • 실행
      • .exe파일로 변환이 완료된 상태

 

빌드 구성 및 플랫폼

  • DebugGame
    • 게임 로직만 디버그 정보를 포함, 엔진은 최적화된 상태로 빌드한다.
    • 에디터가 아닌 독립 실행 파일 환경에서 디버깅이 가능하다.
  • DebugGame Editor
    • 에디터 환경에서 게임 로직을 디버그하기 편한 설정
    • 에디터 플레이 중 C++ 로직을 추적, 브레이크 포인트를 걸 때
  • Development
    • 디버그 정보를 최소화하여 실행 속도를 높인 개발용 빌드
    • 독립 실행 파일 환경테스트, 개발 단계에서 주로 쓰인다
  • Developmenet Editor
    • 에디터에서도 개발, 테스트를 원활히 할 수 있도록 구성된 빌드 모드
    • Live Coding 사용 시나리오, 기본모드
  • Shipping
    • 최종 사용자(User)에게 배포할 때 사용되는 릴리스 빌드
    • 디버그 정보 제거, 성능 최적화 극대화

 

  • #include "CoreMinimal.h"
    • 언리얼 엔진에서 자주 사용하는 기본 타입(FString, TArray)등 과 매크로(UE_LOG)등 각종 유틸리티 함수들을 정의하고있는 헤더 파일
  • #include "[ClassName].generated.h"
    • 언리얼 엔진의 리플렉션 시스템에서 필요한 코드를 자동으로 생성하기 위한 매크로
  • UCLASS()
    • 해당 클래스를 언리얼 엔진의 리플렉션 시스템에서 인식하도록 하는 매크로
    • 언리얼 에디터에서 이 클래스를 블루프린트로 확장 할 수 있게하며, 에디터의 여러 기능과 연동하도록 한다.
  • [SOLUTIONNAME]_API
    • 이 클래스를 외부 모드로 Export 하기 위한 매크로
    • DLL 등으로 빌드 할 때 필요한 선언
  • GENERATED_BODY()
    • UCLASS()와 같이 엔진 리플렉션에 필요한 코드를 자동으로 생성 해주는 매크로

Controller에서 레벨을 열거나, UParticleSystemComponent를 사용한 파티클 생성, 월드에 배치되어있는 액터를 검색하기 등등 다양한 경우에서 인스턴스화 하지않은 UGameplayStatics를 사용하게 되었습니다.

게임 프로젝트를 제작하며 계속되서 사용되는 정적으로 사용되는 해당 클래스가 어떠한 기능을 하는지 알아보게되었으며,

학습한 자료들을 이번 포스팅에 작성하겠습니다.

 

UGameplayStatics

  • 게임 플레이 관련 유틸리티 함수를 제공하는 정적(Static) 클래스
    • 유틸리티 기능을 빠르게 호출하며, 대부분 전역적인 게임 월드에 의존한다.
    • 따라서 매번 객체를 만들지 않고 정적 클래스 및 함수로 설계되어 빠르게 호출 할 수 있다.
  • 인스턴스를 생성하지 않고 직접 호출한다.
    • UGameplayStatics::FunctionName()
  • 오디오, 내비게이션, 액터 생성,파괴, 게임 레벨 관리, 데미지/충돌 처리, 세이브/로드 등 게임 제작에 필요한 시스템들을 쉽게 사용할 수 있도록 도와준다.
    • 공통 기능들을 쉽게 호출할 수 있도록 설계된 유틸리티 클래스
  • 엔진의 다양한 시스템과 통합되어있어 AI, UI, Physcis등과 연계가 가능하다.
  • 빠르고 간편하게 사용할 수 있으며, 객체 생성이 불필요하여 메모리 관리 시 용이하다.
    • 동적 환경에서 객체를 만들고 삭제하는 메모리 생성값을 줄여 오버헤드를 줄일 수 있다.
  • 남용하거나 잘못된 사용시 성능적으로 문제가 발생 할 수 있다.
    • Ex) GetAllActorsOfClass : 월드에 배치된 모든 액터들을 검색한다.

 

UGameplayStatics::Function(...)

오디오 / 사운드

  • PlaySoundAtLocation
    • 특정 위치에서 사운드 재생
  • PlaySound2D
    • 2D 사운드 재생
  • SpawnSoundAtLocation
    • 게임 내 특정 위치에 사운드 생성, 재생

 

위치 / 내비게이션

  • GetPlayerPawn
    • 특정 플레이어의 Pawn 반환한다.
  • GetPlayerCharacter
    • 특정 플레이어의 Character 반환한다.
  • GetAllActorsOfClass
    • 특정 클래스의 모든 액터를 반환한다.
  • GetActorOfClass
    • 특정 클래스의 첫번째 액터 반환
  • ProjectPointToNavigation
    • 특정 지점을 내비게이션 메시상의 유효한 위치로 변환한다.

 

액터 생성/ 파괴

  • SpawnActor
    • 게임 월드에 새로운 액터 동적으로 생성
    • GetWorld()->SpawnActor와 동일하다.
    • GetWorld()->SpawnActorDeferred  = BeginDeferredActorSpawnFromClass 
  • DestroyActor
    • 특정 액터 삭제

 

게임 흐름 및 레벨 관리

  • OpenLevel
    • 특정 레벨을 불러온다.
  • GetCurrentLevelName
    • 현재 레벨의 이름을 반환한다.
  • SetGamePaused
    • 게임을 일시정지 / 해제한다
  • SetGlobalTimeDilation
    • 게임의 전체적인 시간의 흐름을 조정한다.

 

데미지 및 충돌처리

  • ApplyDamage
    • 액터에 피해를 가한다.
  • ApplyRadialDamage
    • 특정 반경 내에 있는 모든 액터에 피해를 가한다.
  • ApplyPointDamage
    • 특정 지점에서 한개의 액터에게만 피해를 가한다.

 

카메라 및 뷰 관련 기능

  • GetPlayerCameraManager
    • 특정 플레이어의 CameraManager 반환
  • SetGlobalTimeDilation
    • 전체 게임 속도를 조정하여 슬로우 모션 또는 빠른 진행 효과 연출

 

세이브 / 로드

  • CreateSaveGameObject
    • 세이브 데이터 객체 생성
  • SaveGameSlot
    • 데이터를 특정 슬룻에 저장
  • LoadGameFromSlot
    • 특정 슬룻에서 저장된 데이터를 불러온다.

스파르타 코딩클럽 내 프로젝트를 진행하며 액터를 스폰시키는 프로젝트가 있었습니다.

프로젝트를 진행하며 런타임 환경 내 액터를 스폰시키기위해 SpawnActor함수를 호출하며 동시에 추상클래스를 기반으로 확장성있는 액터 구조를 설계하였지만, 공통 프로퍼티를 설정하던 중 SpawnActor를 사용할 경우 BeginPlay가 바로 호출되어 추가로 설정할 수 없던 문제가 있었습니다.

해당 경우를 해결하기위해 SpawnActorDeferred를 사용하게 되었으며, 이를 계기로 기존의 SpawnActor와의 차이점 및 특징에 대해 알아보겠습니다.

 

SpawnActor + 오브젝트 생성

 

SpawnActorDeferred<T>

  • 기존의 SpawnActor은 액터가 생성되며, 즉시 BeginPlay()가 실행된다.
  • 생성된 액터에 추가적인 설정을 적용하며, FinishSpawning()을 호출하여 액터를 생성 할 수 있다.
  • 생성하는 액터에 대해 컴포넌트 및 프로퍼티들을 추가/변경이 가능하다.
  • Spawn시킨 후, 작업(추가적인 설정) 진행 후 FinishSpawning()을 사용한 BeginPlay()를 실행하는 구조.
  • Parameters
    • UClass* class : 생성할 액터 클래스
    • FTransform const& Transform : 액터의 초기 위치 및 회전 값
    • AActor* Owner = nullptr : 생성된 액터의 소유자(Owner)
    • APawn* Instigator : 생성된 액터의 행위자(Ex) 공격을 가한 Pawn)
    • ESpawnActorCollisionHandlingMethod CollisionHandlingOverride : 충돌 시 스폰 방식 설정
      • AlwaysSpawn : 충돌 여부와 상관없이 무조건 스폰
      • AdjustIfPossibleButAlwaysSpawn : 충돌 위치를 조정하고 무조건 스폰
      • AdjustIfPossibleButDon'tSpawnIfColliding : 충돌된 위치를 조정, 그래도 충돌시 스폰하지 않는다.
      • DontSpawnIfColliding : 충돌하는 경우 스폰하지 않는다.

 

예시코드

void AMoveActorSpawner::SpawnActorSettingWithRand(TSubclassOf<AMoveActorBase>& SpawnData)
{
	AMoveActorBase* SpawnActor = GetWorld()->SpawnActorDeferred<AMoveActorBase>(SpawnData, FTransform(FRotator::ZeroRotator, FVector::ZeroVector), this, nullptr,
		ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn);

	if (SpawnActor)
	{
		// ..액터 생성 전에 확인하고 싶은 로직
		// .. 추가 설정하고 싶은 로직
		SpawnActor->FinishSpawning(FTransform(FRotator::ZeroRotator, FVector::ZeroVector));
	}
}

 

 

FActorSpawnParameters

  • SpawnActorDeferred/SpawnActor에서 스폰 관련 옵션을 설정 할 수 있는 구조체
  • SpawnActor를 사용하여 지연시 Timer 또는 FActorSpawnParameters 내 bDeferConstruction = true 방식을 통해 스폰을 지연 시킬 수 있다.
  • SpawnActor + bDeferConstruction = true가 SpawnActorDeferred.

 

게임 플레이 프레임워크(Game Play Framework)

  • 언리얼 엔진을 통한 게임 개발 시 기본적으로 제공되는 핵심 클래스와 시스템들의 집합
  • GameMode, GameState, PlayerController, PlayerState, HUD 같은 클래스들이 존재
    • 플레이어 세부정보(해당 세션의 게임 작업 기여도, 킬로그, 캐릭터 클래스 등)
    • 게임의 현재 상태에 대한 정보(게임 전체 작업 진행도, 진행 시간 등)
    • 승리 조건에 대한 정보
    • 위젯 및 HUD
    • 입력

 

 

UE GameFramework - https://medium.com/project-asura/leveraging-ue4-gameplay-framework-for-our-multiplayer-game-41724f2e1dcd

 

 

Game/Player... + Base

  • 해당 클래스들의 부모 클래스
  • Base를 제외한 클래스들은 게임을 설계시 필요한 기능들을 추가적으로 구현해둔 클래스들이다.
  • Ex) GameMode
    • 언리얼에서 제공하는 멀티플레이 기능을 거의 포함, 싱글플레이 가능
    • GameState, PlayState에 대한 연동 활성화
  • GameModeBase
    • GameMode를 단순화 시키고 효율화 시킨 버전
    • 멀티플레이 관련 로직이 일부 포함.
    • 싱글플레이 / 멀티플레이 로직을 직접 구현하고 싶을 경우 

※ GameMode VS GameModeBase 및 주요 함수

 

Game Mode

  • 게임이 시작될 때 플레이어 스폰, 팀 배정, 게임 승패 조건 등 게임의 승리 조건 및 규칙을 정의한다.
  • 해당 세션의 최대 입장 가능 플레이어 인원, 게임에 들어오는 방식, 일시정지, 레벨 전환 등을 정의한다.
  • DefaultPawnClass, PlayerControllerClass 등을 사용하여 플레이어가 연결될 폰 및 컨트롤러들을 설정한다.
  • 게임당 한개만 존재, 서버에서만 실행되며, 클라이언트에서는 직접 수정 할 수 없다.
  • DefaultPawnClass, HUD Class, PlayerControllerClass, SpectatorClass, GameStateClass, PlayerStateClass 등을 설정 할 수 있다.

[UE5.5.1] GameMode Blueprints - Class

 

 

Game State

  • 서버에만 속하지만, 모든 클라이언트에 복제된다.
    • 클라이언트가 게임의 상태를 모니터링 할 수 있도록 도와준다.
  • 게임에서 규칙과 관련된 이벤트 발생 시 추적, 모든 플레이어와 공유해야 하는 데이터 들을 정의한다.
  • 해당 데이터(정보)들은 GameState를 통해 저장하는것이 적합하며, 모든 데이터들은 동기화(Sync)된다.
    • 모든 클라이언트들이 공통적으로 영향을 미치는 데이터들을 저장하는 클래스
  • 게임이 실행된 시간, 각 개별 플레이어가 게임에 참여한 시간 및 플레이어의 현재 상태
  • 게임이 시작되었는지에 대한 여부, 게임의 작업 진행도 등

 

Game Instance

  • 게임의 전체 수명동안 유지되는 클래스
  • 게임의 시작부터 종료까지 존재하며, 레벨 간 상태유지 및 글로벌 데이터 관리에 적합하다.
  • 싱글톤 구조로 설계되어있으며, 레벨이 전환되어도 삭제되지 않는다.
  • 멀티플레이어 환경에서 호스트 및 연결관리를  위한 기능들을 포함하고 있다.
    • 네트워크 관련 이벤트(연결 성공, 실패, 종료 등)을 포함하고있다.
  • 게임이 시작될 때 자동으로 생성된다.
  • 많은 데이터들을 저장시 의존성 문제가 발생한다.
    • 글로벌환경의 State들을 관리시에 동기화 혹은 충돌 가능성이 있다.
    • 이를 해결하기 위해서는 데이터를 개별 클래스로 나누고 GameInstance에서 참조하도록 설계해야한다.
  • 게임 재시작시에도 GameInstance는 초기화 되지않으며, 명시적으로 데이터를 재설정 해야한다.

 

Player State

  • GameState가 전체 게임 상태를 관리한다면, PlayerState는 플레이어별 데이터를 관리한다.
  • 서버에서 생성된 후 Replicate를 통해 클라이언트와 동기화 하는 데이터
  • 서버와 연결된 다른 클라이언트도 알아야 되는 정보
    • 플레이어 사망처리
  • 플레이어만 알아도 괜찮은 정보 -> Pawn Class, PlayerController
    • Tab키를 누른 게임 상태창같은 경우

 

PlayerController

  • 플레이어의 입력을 받아 Possess된 Pawn 또는 UI들을 제어한다.
  • 클라이언트에서 입력을 받아 서버로 요청을 보낸다.
  • 로컬 플레이어 전용 기능(UI, 카메라 조작)등을 처리한다.
  • 플레이어의 입력을 감지하고 서버로 전송하는 역할

 


출처 및 참고내역

https://medium.com/project-asura/leveraging-ue4-gameplay-framework-for-our-multiplayer-game-41724f2e1dcd

https://ebs12373.tistory.com/225

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/gameplay-framework-in-unreal-engine

액터 (Actor)

  • 컨텐츠를 구성하는 기본 단위
    • class AActor : public UObject
  • 월드안에 배치되어 상호작용 하거나 동작할 수 있는것.
  • 입력처리를 위한 컨트롤러가 없다. 
    • Pawn, Character에서의 사용 가능성 및 GetOwner()를 사용한 컨트롤러 접근을 위해 Actor.h내 전방선언
    • Actor 기반의 Trigger를 참조하거나 상호작용하기위해
    • 입력 이벤트를 바인딩하여 입력처리가 가능하다 (문열기 상호작용 등)
  • 입력이 필요없거나 환경 요소 및 비활성 오브젝트 구현시 사용
  • Translation : SetActorLocation / AddActor[Local,World]Offset
  • Rotate : SetActorRotation / AddActorWorld[Local,World]Rotation
  • Transform : SetActorTransform(FTransform)

 

폰 (Pawn)

  • Actor를 상속받는 하위클래스
    • class APawn : public AActor, public INavAgentInterface
  • 컨트롤러를 통해 AI 조작 또는 플레이어 빙의가 가능한 객체
  • SetupPlayerInputComponent를 사용한 입력처리 가능
  • C++ Class 생성시 RootComponent는 nullptr이다.
    • 개발자가 명시적으로 Component를 추가해야한다.
  • 언리얼 엔진 내 내비게이션 기능을 통해 이동 제어 가능
    • NavMovementComponent 기반의 Component를 통해 제어
    • 월드 내 내비게이션 메시를 기반으로 경로를 계산한다. (NavMeshBoundsVolume)
    • AIController과 연동하여 MoveToLocation, MoveToActor를 사용한 이동 제어
  • AI Controller / Player Controller를 통해 조작이 필요한 객체들을 대상으로 사용한다.
    • 차량, 드론, 비행기 등
    • AI - 인간형이 아닌 오브젝트
  • Rotation : AddController[Roll,Pitch,Yaw]Input
  • AI : MoveToLocation / MoveToActor

 

 

캐릭터 (Character)

  • Pawn을 상속받는 하위클래스
    • class ACharacter : public APawn
  • 인간형 폰이며, 플레이어 또는 NPC의 주요 컴포넌트들을 포함하고 있다.
    • CapsuleComponent(RootComponent) : 충돌처리
    • SkeletalMeshComponent : 캐릭터의 3D모델 및 애니메이션 렌더링
    • CharacterMovementComponent : 이동(걷기, 점프, 중력, 앉기 등)처리
  • 멀티플레이 네트워크 환경에서 캐릭터들의 움직임을 자동으로 동기화 한다.
    • Character 생성자 내 리플리케이션 활성화가 되어있다. 
  • CharacterMovementComponent 내 추가 이동 함수
    • LaunchCharacter - 물리처리를 통한 이동
    • MaxWalkSpeed - 걷기 속도 변경
      • Crouch()시 기존의 AddMovementInput()과의 속도차이를 볼 수 있다.

 

 

Character.cpp 생성자 - Default Value, Default Component Setting

ACharacter::ACharacter(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
  // ...
  
	bUseControllerRotationPitch = false;
	bUseControllerRotationRoll = false;
	bUseControllerRotationYaw = true;

  // CapsuleComponent
	CapsuleComponent = CreateDefaultSubobject<UCapsuleComponent>(ACharacter::CapsuleComponentName);
	CapsuleComponent->InitCapsuleSize(34.0f, 88.0f);
	CapsuleComponent->SetCollisionProfileName(UCollisionProfile::Pawn_ProfileName);
	RootComponent = CapsuleComponent;

  // Arrow Component
	ArrowComponent = CreateEditorOnlyDefaultSubobject<UArrowComponent>(TEXT("Arrow"));
	if (ArrowComponent)
	{
    // ...
	}

  // CharacterMovement
	CharacterMovement = CreateDefaultSubobject<UCharacterMovementComponent>(ACharacter::CharacterMovementComponentName);
	if (CharacterMovement)
	{
		CharacterMovement->UpdatedComponent = CapsuleComponent;
	}

  // SkeletalMesh
	Mesh = CreateOptionalDefaultSubobject<USkeletalMeshComponent>(ACharacter::MeshComponentName);
	if (Mesh)
	{
    // ...
	}

  // GravityDirection
	ReplicatedGravityDirection = UCharacterMovementComponent::DefaultGravityDirection;
}

 

C++를 사용한 언리얼 엔진을 학습하던 중, 오브젝트 생성 시 UObject::CreateDefaultObject, New Object<T>, UWorld::SpawnActor<T> 와같은 각기 다른 호출방식이 있었습니다. 이에 대해 어떠한 상황에서 해당 함수들을 호출하는지 학습하고 기록하고자 이번 포스팅을 하게 되었습니다. 또한, 생성자 호출 시 추가적으로 어떠한 과정이 이루어지는지에 대해 알아보던중 CDO를 알게되어 추가적으로 작성하게 되었습니다. 

 

CDO(Class Default Object)

CDO는 엔진이 실행되며 클래스가 로드 될 때 초기화, 생성되는 인스턴스화 되지 않은 기본값을 가지고 있는 객체입니다.

  • C++이 제공하지 않는 언리얼만의 추가 기능들을 제공한다.
  • UClass 클래스에서 추가기능(메타정보)들을 보관한다.
  • 메모리에 한번만 존재하며, 클래스가 로드될 때 자동으로 생성된다.
  • 언리얼 오브젝트를 생성할 때 매번 초기화 하지않고 CDO를 복제해서 사용한다.
    • 객체 생성시 초기값에 대한 일관성 유지 및 재사용으로 인한 생성 비용 감소

 

CDO 생성 및 기본 프로퍼티 값 확인 예시 코드

// AMyCharacter 클래스의 CDO에 접근
AMyCharacter* DefaultCharacter = AMyCharacter::StaticClass()->GetDefaultObject<AMyCharacter>();

// 기본 프로퍼티 값 확인
float DefaultHealth = DefaultCharacter->Health;

 

 

 

CreateDefaultSubObject

  • 클래스의 서브 객체(Ex : Compoenet)들을 생성하고 초기화 하는데 사용한다.
  • 클래스 생성자에서 호출된다.
    • BeginPlay()와 같은 생성자가 아닌 곳에서 호출 시 동적할당으로 생성하게되어 에러가 발생한다.
  • 언리얼 엔진에서 메모리를 관리한다.
  • 생성된 객체는 에디터 단계에서 조작할 수 있다.
// Camera Component와 SpringArm Component를 캐릭터의 컴포넌트로 추가
FPSCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
FPSSpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));

 

 

 

 

NewObject

  • 동적으로 객체를 생성할 경우 사용된다.
  • 런타임 단계에서 객체를 생성하고 싶을시에 사용한다.
  • 생성자 단계에서 생성하지 않는 객체들의 생성을 위해 일반적으로 사용된다.
// 런타임 환경에서 객체를 생성하고 싶을 때
// 생성자 이후 객체를 생성 시
UDynamicCreateObject* DCObject = NewObject<UDynamicCreateObject>(this, UDynamicCreateObject::StaticClass());

 

 

 

SpawnActor

  • 액터 객체를 월드에서 동적으로 생성하는 함수
    • 생성자 단계 이후에서 생성해야한다.
  • Transform 정보를 가지는 액터를 스폰하기 위해 사용된다.
  • 액터 라이프사이플에 따른 관리를 받는다.
// 런타임 시 월드에서 액터 스폰
FVector Location(0, 0, 100);
FRotator Rotation(0, 0, 0);
AMonsterActor* SpawnedActor = GetWorld()->SpawnActor<AMonsterActor>(AMonsterActor::StaticClass(), Location, Rotation);

 

 

 

정리

함수 역할 사용 예 주 용도
CreateDefaultSubobject 클래스의 서브 객체(컴포넌트 등) 생성 CreateDefaultSubobject<UCameraComponent>(TEXT("Camera")); 주로 클래스 내부에서 컴포넌트를 생성하고 기본값 설정
NewObject 동적 객체 생성 NewObject<UMyCustomObject>(this, UMyCustomObject::StaticClass()); 런타임에서 새로운 UObject 객체 생성, 주로 데이터 객체 생성
SpawnActor 월드에서 액터 객체 동적 생성 SpawnActor<AMyActor>(AMyActor::StaticClass(), Location, Rotation); 월드에 실제로 존재하는 액터 객체 생성, 게임에서 상호작용 가능

델리게이트 Delegate

C++ 오브젝트 상의 멤버 함수를 가리키고 실행시키는 데이터 유형

특정 객체가 해야 할 로직을 다른 객체가 대신 처리할 수 있도록 만드는 보편적인 설계의 개념

델리게이트는 임의 오브젝트의 멤버함수에 동적으로 바인딩시킬 수 있으며, 그런 다음 그 오브젝트에서 함수를 호출할 수 있습니다.

또한 값으로 전달하는 경우 Heap에 메모리를 할당하게되는데, 델리게이트는 참조 전달을 위주로 하기 때문에 복사에도 안전합니다.

 

특징 

  • 특정 이벤트나 동작이 발생시에 호출할 함수를 미리 등록해주는 콜백 메커니즘
  • C++의 함수포인터와 비슷한 역할
    • std::function이 싱글캐스트 델리게이트와 비슷하다.
  • 결합도를 낮추고 유연한 이벤트 처리를 할 수 있다.
  • 델리게이트 선언시 어떤 데이터를 전달하고 받을것인지 인자의 수와 타입의 설계에 따라 사용되는 매크로가 결정된다.

 

델리게이트 선언 매크로

DECLARE_[델리게이트 유형]_DELEGATE_[함수 정보(RetVal_OneParam...]

 

  • Simple : 함수 하나만 바인딩 가능
    • DECLARE_DELEGATE
  • Multicast : 여러개의 함수가 한 이벤트에 바인딩이 가능하다
    • DECLARE_MULTICAST_DELEGATE, DECLARE_DYNAMIC_MULTICAST_DELEGATE
  • Dynamic : 블루프린트에서 바인딩이 가능하며, 런타임에서 바인딩 과 언바인딩이 가능하다.
    • DECLARE_DYNAMIC_DELEGATE,DECLARE_DYNAMIC_MULTICAST_DELEGATE

 

유형

  • DECLARE_DELEGATE : 1:1 형태로 전달시에 사용하며, C++에서만 지원한다.
  • DECALRE_MULTICAST : 1:N 형태로 전달시에 사용되며, C++에서만 지원한다.
  • DECLARE_DYNAMIC : 1:1형태로 전달하며, 블루프린트에서도 바인딩이 가능하다.
  • DECLARE_DYNAMIC_MULTICAST : 1:N형태로 전달하며, 블루프린트에서도 바인딩이 가능하다.

함수 정보

  • 인자가 없고 반환값이 없는 경우 - DECLARE_DELEGATE
  • 인자가 하나, 반환값이 없는 경우 - DECLARE_DELEGATE_OneParam
  • 인자가 하나, 반환값이 있는 경우 - DECLARE_DELEGATE_RetVal_OneParam
    • RetVal의 경우 블루프린트 형식을 지원하는 DYNAMIC에서만 선언이 가능하다.

 

바인딩

UObject 또는 공유 포인터 클래스 멤버에 델리게이트를 바인드 할 경우 오브젝트에 대한 레퍼런스를 낮은 응집도를 유지할 수 있으며, 오브젝트 소멸시 IsBound(), ExecuteIfBound() 함수를 호출하여 처리할 수 있다.

  • Bind() : 기존 델리게이트 오브젝트에 바인딩
  • BindStatic() : 정적 함수에 바인딩
  • BindUObject() : UObject 멤버 함수에 바인딩
  • AddStatic() : 함수 바인딩

실행

  • Execute() : 바인딩 되어있는지 확인
  • ExecuteIfBound() : 반환값이 없는 델리게이트의 경우
  • IsBound() : 모든 바인딩된 함수를 호출한다. (MULTICAST에서만 가능하다.)
    // 델리게이트 선언
    DECLARE_MULTICAST_DELEGATE(FSimpleMulticastDelegate);
    
    // 델리게이트 객체
    FSimpleMulticastDelegate OnEventTriggered;

    void TriggerEvent()
    {
        // 모든 바인딩된 함수 호출
        OnEventTriggered.Broadcast();
    }
};

void MyFunction1()
{
    UE_LOG(LogTemp, Warning, TEXT("Function 1 Triggered!"));
}

void MyFunction2()
{
    UE_LOG(LogTemp, Warning, TEXT("Function 2 Triggered!"));
}

void TestMulticastDelegate()
{
    MyClass Instance;
    Instance.OnEventTriggered.AddStatic(&MyFunction1); // 함수 바인딩
    Instance.OnEventTriggered.AddStatic(&MyFunction2); // 또 다른 함수 바인딩
    Instance.TriggerEvent(); // 두 함수 모두 호출
}

 

 

 

 

 

 

 

 


https://dev.epicgames.com/documentation/ko-kr/unreal-engine/delegates-and-lamba-functions-in-unreal-engine

이득우의 언리얼 C++ 게임 개발의 정석

지난 포스팅에서 언리얼 컨테이너 라이브러리의 정의 및 특징 그리고 STL과의 차이점에 대해 알아보았습니다.

이번시간에는 지난 포스팅에 작성하지 못한 TMap, TSet을 이어서 작성해보도록 하겠습니다.

언리얼 컨테이너 라이브러리 + TArray

TSet

  • std::set과 유사한 기능을 제공한다.
  • 중복된 값을 허용하지 않는다.
  • 동적 가변 배열의 형태로 데이터가 이루어져있다.
  • 해시 테이블 형태로 해시 기반의 Key데이터가 구축되어있어 빠른 조회가 가능하다.
    • 시간복잡도 O(1)를 보장한다.
  • 동적 배열 사이에 데이터가 비어있을 수 있다. 순서가 보장되지 않는다.

 

TSet의 주요 특징은 해당 사항이 있었습니다.

또한 추가 / 제거 / 삽입하는 함수들의 대부분이 TArray와 동일하기 때문에, 그 외 다른 주요 함수들에 대해 알아보겠습니다.

 

 

슬랙 (Slack)

  • 할당된 메모리에 엘리먼트가 없는것
  • Reset() 또는 Empty를 사용하여 메모리 할당 해제 없이 모든 엘리먼트를 제거하여 슬랙을 만들 수 있다.
  • TSet을 비우고 엘리먼트 수가 같거나 적은 TSet들을 Append할 시 효율적이다.

정렬 (Sorting)

  • 정렬 이후 배열이 수정된다면, 해당 순서를 보장할 수 없다
  • Key를 사용해 검색을 해서 시간복잡도가 O(1)이기 때문에 특별한 이유가 없으면 정렬을 하지 않아도 된다.

 

TMap

  • std::unordered_map과 유사한 동작을 가지고 있다.
  • TSet과 같이 해시 기반의 Key를 가지고 있어 검색 및 삽입속도가 빠르다.
  • Key와 Value 구조로 되어있으며, Key는 중복될 수 없지만, Value는 중복될 수 있다.
    • TMultiMap은 중복된 키를 저장할 수 있다.
    • TMap은 동일 Key를 사용하면 기존 것에서 대체되며, TMultiMap은 새로 저장한다.
  • iterator를 사용하여 모든 키-값을 조회할 수 있다. 
  • 데이터를 삭제해도 재구축이 일어나지 않는다
    • 비어있는 데이터가 있을 수 있다.

KeyFuncs

한 유형에 operator== 와 멤버가 아닌 GetTypeHash 오버로드가 있는 한, 그 유형은 변경 없이 TMap 의 키 유형으로 사용해도 됩니다. 하지만 그 함수 오버로드 없이 유형을 키로 사용하고 싶은 경우가 있습니다. 이러한 경우, 별도의 커스텀 KeyFuncs 를 제공해 주면 됩니다. 키 유형에 대해 KeyFunc 를 만들려면, 다음과 같이 두 개의 typedef 및 세 개의 static 함수 정의가 필요합니다:

  • KeyInitType - 키 전달에 사용됩니다.
  • ElementInitType - 엘리먼트 전달에 사용됩니다.
  • KeyInitType GetSetKey(ElementInitType Element) - 엘리먼트의 키를 반환합니다.
  • bool Matches(KeyInitType A, KeyInitType B) - A 와 B 가 동일하면 true, 아니면 false 를 반환합니다.
  • uint32 GetKeyHash(KeyInitType Key) - 키의 해시 값을 반환합니다. 보통 외부 GetTypeHash 함수를 호출합니다.

언리얼 공식문서 참조 - https://dev.epicgames.com/documentation/ko-kr/unreal-engine/map-containers-in-unreal-engine

 

 

이상으로 언리얼 컨테이너 라이브러리 중 자주 사용하는 TMap, TSet들에 대해서 알아보았습니다.

그 외에도 다양한 자료구조들이 존재하며, 각 자료구조들에 존재하는 함수들에 대해서도 공식문서를 참조하여 알아볼 수 있습니다.

 

이번학습으로 언리얼 엔진을 사용한 게임 개발시에도 어떤 라이브러리를 사용하는게 효율적인지 알 수 있었습니다.

또한, 언리얼 내부구조에서도 이진 트리형태의 TArray혹은, TMap를 사용하여 이루어져있다는것을 알 수 있었습니다.

 

WinAPI환경에서 2D게임 개발을 할 시에는 자료구조가 필요할 시 STL에 있는 자료구조들을 자주 사용했습니다.

언리얼 엔진을 접하면서 STL의 자료구조들과 같이 언리얼 내부에 있는 자료구조들이 있는지에 대해 알아보았는데, 언리얼 컨테이너 라이브러리(Unreal Container Library)에 포함되어있는 TArray, TMap, TSet의 자료구조 외에도 다양한 클래스 및 구조체들이 있었습니다.

효율적인 리소스 사용을 위해서 어떤것들이 더 효율적인지 비교하며 학습한 내용들을 이번 포스팅을 통해 알아보겠습니다.

 

언리얼 컨테이너 라이브러리(UCL)

  • 언리얼에서 제공하는 대표 컨테이너 라이브러리
  • 언리얼 오브젝트를 안정적으로 지원하며, 다수의 오브젝트 처리에 효율적이다.
    • UCL의 라이브러리는 가비지컬렉션을 통해 메모리 관리가 가능하다.
  • 대표적인 라이브러리는 TMap, TSet, TArray가 존재한다.
  • 접두사 T는 Template Library를 의미한다.

 

UCL과 C++의 STL의 차이점

  • C++ STL은 게임외에도 사용할 수 있도록 범용적으로 설계되어 있다.
  • STL에는 많은 기능들이 포함되어 있어 컴파일 시 UCL보다 많은 시간이 걸린다.
  • STL은 런타임 오버헤드가 최소화 되어있으며, UCL은 STL보다 런타임 환경에서 오버헤드가 더 발생할 수 있다.
  • UCL은 언리얼 오브젝트에 특화되어있다. 즉, 게임 개발이라는 한정된 범위가 존재한다.
  • UCL은 게임 개발에 적합한 API와 기능들을 가지고 있어, 게임 제작에 최적화된 구조로 설계되어있다.
  • UCL은 디버거 및 로거와 연동할 수 있어 문제가 발생했을 때 원인을 쉽게 파악할 수 있다.
  • UCL은 UPROPERTY()를 사용하여 블루프린터와 연동하여 사용할 수 있다.

즉, 언리얼 엔진 환경에서의 게임 개발시에는 UCL을 사용하는것이 더 적합하다고 할 수 있습니다.

 

 

TArray

  • 동적 배열(Dynamic Array)클래스이다.
  • STL의 vector와 동작 원리가 유사하다.
  • 기존 Array의 특징과 같이, 데이터가 순차적으로 모여있기 때문에 메모리를 효과적으로 사용할 수 있다.
  • 임의 데이터를 접근하는데에 있어 빠른 속도를 보장한다. 시간복잡도 O(1)
  • 탐색, 삽입, 삭제시에는 시간복잡도 O(N)이다.
  • 중복된 값을 허용한다.

배열 만들고 채우기

	// 정의
	TArray<int32> IntArray;

	// Init : n의 값을 n번 채운다.
	IntArray.Init(10, 5);
	// IntArray == {10, 10, 10, 10, 10}

	
	// Add, Emplace : 배열의 가장 마지막 위치에 추가한다. (Dynamic Array이므로 최대 사이즈 고려 X)
	IntArray.Add(10);			// 값 타입의 경우 해당 자료형의 임시 값을 추가해 복사한다.)
	IntArray.Emplace(10);		// 임시 변수를 생성하지 않는다.
	

	FString Arr[] = { TEXT("TEXT1"), (TEXT("TEXT2")) };
	TArray<FString> StrArray;

	// Append : 해당 배열의 크기에 다수의 엘리먼트를 한꺼번에 추가한다.
	StrArray.Append(Arr, sizeof(Arr));
	// StrArray == {TEXT1, TEXT2};


	//Insert : 단일 엘리먼트 또는 엘리먼트 배열 사본을 주어진 인덱스에 추가한다.
	StrArray.Insert(TEXT("TEXT3"), 1);
	// StrArray == {TEXT1, TEXT3, TEXT2};

 

정렬 (Sort)

	TArray<FString> FStrArray = {TEXT("!"), TEXT("Hello"), TEXT("Apple"), TEXT("Boat"), TEXT("World")};
	FStrArray.Sort();		// 안정적이지 않다. FString의 경우 대소문자 구분없이 사전식 비교후 정렬을 실행한다.
	// FStrArray == {!, Apple, Boat, Hello, World};

	FStrArray.HeapSort();	// 안정적이지 않다. FString의 경우 FString의 길이별로 정렬하지만, 같은 크기의 경우 순서를 보장하지 않는다.
	// FStrArray == {!, Boat, Hello, Apple, World};

	FStrArray.StableSort();	// Merge Sort형태로 구현되어있다. 순서를 보장한다.(A->Z의 순서 정렬)
	// FStrArray == {!, Boat, Apple, Hello, World};

 

 

쿼리

	TArray<FString> StrArray = { TEXT("!"), TEXT("Hello"), TEXT("Apple"), TEXT("Boat"), TEXT("World") };

	// Num : 배열의 Element가 몇개인지 확인하기
	int32 Count = StrArray.Num();
	// Count == 5

	// GetData : 배열 내 Element에 대한 포인터를 반환받는다.
	FString* StrPtr = StrArray.GetData();
	// Index 별로 개별 접근이 가능하다.
	// StrPtr[0] == "!"
	// StrPtr[1] == "Hello"
	// ...
	// StrPtr[5] - undefined behavior

	// Element 값 받아오기
	FString Elem1 = StrArray[1];
	// Elem1 == "Hello"

	// IsValidIndex(n) : 특정 인덱스가 유효한지에 대한 검사
	bool isValid1 = StrArray.IsValidIndex(1);
	bool isValid6 = StrArray.IsValidIndex(6);
	// isValid1 == true
	// isValid6 == false

	// Contains : 배열에 특정 엘리먼트가 들어가있는지 조회하기
	bool bContain = StrArray.Contains(TEXT("Hello"));
	bool bContain2 = StrArray.Contains(TEXT("NotHello"));
	// bContain == true
	// bContain == false

 

 

제거

	TArray<int32> ValArr;
	int32 Temp[] = { 10, 20, 30, 5, 10, 15, 20, 25, 30 };
	ValArr.Append(Temp, UE_ARRAY_COUNT(Temp));

	// Remove : 배열에서 일치하는 엘리먼트를 모두 지운다.
	ValArr.Remove(20);
	// ValArr == {10,30,5,10,15,25,30}

	// RemoveSingle : 배열에서 처음으로 일치하는 엘리먼트를 하나 지운다.
	ValArr.RemoveSingle(10);
	// ValArr == {10,5,10,15,25,30}

	// RemoveAt : 인덱스N의 엘리먼트를 지운다.
	ValArr.RemoveAt(2);
	// ValArr == {10,5,15,25,30}


	// RemoveAll : 일치하는 엘리먼트를 모두 지운다.
	ValArr.RemoveAll([](int32 Val) {
		return Val % 3 == 0;
		});
	// ValArr == {10,5,25}

	// Empty : 배열에 있는 모든 것을 제거한다.
	ValArr.Empty();
	// ValArr == []

 

힙(Heap)

  • TArray는 이진 힙 데이터 구조체를 지원하는 함수가 존재하며, 이진 트리 유형의 구조로 되어있다.
  • Heapify 함수룰 사용하여 기존 배열을 Heap으로 변환할 수 있다.
	TArray<int32> HeapArr;
	for (int32 Val = 10; Val != 0; --Val)
		HeapArr.Add(Val);
	// HeapArr == [10,9,8,7,6,5,4,3,2,1]
	

	// Heapify : 이진 힙으로 변경시킨다.
	HeapArr.Heapify();
	// HeapArr == [1,2,4,3,6,5,8,10,7,9]

 

Heapify() 결과 - 이진 트리 형태의 Heap으로 변경되었다.

 

 

 

	// HeapPust : 힙에 새로운 엘리먼트를 추가하며, 내부적으로 노드 순서가 변경된다.
	HeapArr.HeapPush(4);
	// HeapArr == [1,2,4,3,4,5,8,10,7,9,6]

HeapPush를 통해 Element 4를 추가한 경우

 

 

슬랙

  • 배열이 추가될 때마다 매번 재할당을 피하기 위해 얼로케이터는 요청보다 더 넉넉한 메모리를 제공하여 Add 호출시 재할당에 드는 퍼포먼스 비용을 물지 않도록 한다.
  • 엘리먼트를 삭제한다고해도 메모리가 해제되지는 않으며, 배열에 slack(여유분)을 가지고 있는다.
  • 초기 슬랙의 값은 0이며, GetSlack함수를 사용해 해당 배열의 슬랙 크기가 얼마나 되는지 알 수 있다.
  • Max 함수를 사용해 재할당이 일어나기 전까지 배열에 저장할 수 있는 최대 엘리먼트를 알 수 있다.
	TArray<int32> SlackArray;
	// SlackArray.GetSlack() == 0
	// SlackArray.Num()      == 0
	// SlackArray.Max()      == 0

	SlackArray.Add(1);
	// SlackArray.GetSlack() == 3
	// SlackArray.Num()      == 1
	// SlackArray.Max()      == 4

	SlackArray.Add(2);
	SlackArray.Add(3);
	SlackArray.Add(4);
	SlackArray.Add(5);
	// SlackArray.GetSlack() == 17
	// SlackArray.Num()      == 5
	// SlackArray.Max()      == 22

 

원시메모리

  • TArray는 할당된 메모리를 둘러싼 포장 단위
  • AddUninitialized, InsertUninitialized : 배열에 초기화 되지 않은 공간에 Element를 추가한다.
    • 해당 엘리먼트 타입의 생성자는 호출하지 않는다.
    • Memcpy 호출로 전체 구조체를 덮어 쓰려는 경우 생성자 호출을 피할 떄 효율적이다.

 

 

 

이상으로 언리얼 컨테이너 라이브러리가 어떤것인지, 어떤 특징을 가지고 있는지

그리고 STL과 어떤 차이점이 있는지 비교하며 알아보았습니다.

 

또한 언리얼 C++의 주요 컨테이너 라이브러중 하나인 TArray와 해당 라이브러리의 주요 함수들이 어떤것들이 있는지에 대해 알아보았습니다.

 

다음 포스팅에서는 이어서 TSet, TMap이 어떤 특징을 가지고 있는지, 그리고 STL의 어떤 자료구조와 유사한지에 대해 알아보겠습니다.

 


https://dev.epicgames.com/documentation/ko-kr/unreal-engine/array-containers-in-unreal-engine

https://designerd.tistory.com/entry/UE-%EC%96%B8%EB%A6%AC%EC%96%BC-C%EC%9D%98-TArray-TSet-TMap-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC%EC%99%80-%ED%99%9C%EC%9A%A9%EB%B0%A9%EB%B2%95

언리얼 엔진에서 제공하는 다양한 클래스들은 내부 GC(Garbage Collection)을 통해 메모리를 관리한다는 것을 지난 포스팅을 통해 알게되었습니다.

UE5 - Garbage Collection 

 

이에 추가적으로 레벨에 스폰된 액터가 언제 생성되며, 언제 사라지는지, 그리고 어떻게 더이상 사용하지 않는다 라고 판단하여 GC가 해당 액터를 소멸시키는지에 대해 조금 더 자세히 파악하고자 이번 포스팅을 작성하게 되었습니다.

이러한 이유로 UE5를 사용한 액터의 라이프사이클에 대해 알아보겠습니다.

 

액터 라이프사이클 분해도 - https://dev.epicgames.com/documentation/ko-kr/unreal-engine/unreal-engine-actor-lifecycle

 

액터 라이프사이클

 

액터 생성 단계 (Spawning)

  1. 디스크에서 로드하기 (Load Map / Add to World)
    • 레벨을 변경하거나 불러올 시 언리얼 엔진 내부에서 호출되는 UnrealEngine::LoadMap이 발생하거나, UWorld::AddToWorld를 호출하는 경우같은, 이미 레벨에 있는 액터들에 대해 발생한다.
    • 패키지/레벨에 있는 액터가 디스크에서 로드되는 단계
    • 디스크에서 로드가 완료되면, 직렬화된 액터에서 PostLoad를 호출한다.
    • 호출된 해당 액터의 컴포넌트가 초기화 된 후, 레벨이 시작될 때 해당 액터의 AActor::BeginPlay()가 호출된다.
  2. 에디터가 플레이 되는 경우 (Play in Editor)
    • 에디터의 액터가 새 월드에 복제된다.
    • UObject::PostDuplicate를 호출하여 해당 액터를 복제, 직렬화한다.
    • 해당 액터에 정의된 각 컴포넌트를 생성, 초기화한다.
    • 레벨이 시작될 때 AActor::BeginPlay()가 호출된다.
  3. 스폰 (Spawn Actor)
    • 플레이를 위해 액터를 설정하기위해 호출된다.
    • 월드에 스폰된 후 직렬화 및 액터 및 액터의 컴포넌트 생성 및 변수를 초기화 한다.
    • 액터에 정의된 각 컴포넌트를 생성, 초기화, 호출한다.
    • AActor::BeginPlay()를 통해 호출된다.
  4. 디퍼드 스폰 (Deferred Spawn)
    • Expose On Spawn (스폰 시 노출)로 설정된 프로퍼티가 존재한다면, 해당 액터는 디퍼드 스폰이 가능하다.
      • Deffered Spawn : 액터의 특정 설정을 초기화한 뒤, 활성화는 나중에 하는 방식.
      • 특정 이벤트가 발생시 호출하는 액터 또는 성능최적화를 할 때 사용한다.

 

게임 실행 단계 (Game Play)

  • 액터가 배치된 레벨 및 어플리케이션이 실행되고 있는 단계.
  • 해당 액터가 매 Tick(프레임)마다 호출되는 단계
  • 또는 데미지 처리와 같은 상태 업데이트를 하는 단계
  • 언리얼 엔진 GC에서 해당 객체들을 Mark하여 메모리 해제를 방지한다. 

 

액터 라이프사이클 종료 (End Play)

  • Destory 함수에 대한 명시적 호출을 한다.
  • 에디터에서 플레이가 종료된 경우
  • 레벨 트랜지션 발생 시
  • 액터의 수명이 만료될 경우
    • 수명이 다해가는것을 보장하기 위해 언리얼 엔진 내부에서 해당 엑터의 EndPlay를 여러곳에서 호출하여 검증한다. 
  • 어플리케이션 종료 (모든 액터 소멸)

위와같이 라이프사이클 종료를 확인하기위해 여러개의 검증을 걸친 뒤, 액터를 더이상 사용하지 않는다 판단 할 시 액터는 내부에서 RF_PendingKill로 표시되어 언리얼 엔진이 해당 액터의 메모리 할당을 해제합니다.

 

※ EndPlay가 호출 되어도 트랜지션한 레벨에서 해당 액터가 사용된다면, 액터는 재사용 될 수 있다.

 

GC에 의한 오브젝트 제거 및 리소스 해제

  • UObject::BeginDestroy : 오브젝트가 메모리를 해제하고, 게임 플레이가 아닌 기타 리소스들을 처리한다.
  • UObject::IsReadyForFinishDestroy : 가비지 컬렉션에서 해당 오브젝트가 영구적으로 제거될 준비가 되었는지 확인하며, false 반환 시 다음 가비지컬렉션까지 소멸을 연기시킨다.
  • UObject::FinishDestroy : 오브젝트가 실제로 소멸되며, 해당 함수 호출 뒤 메모리가 해제된다.

 

 

위의 4단계를 통해 액터가 생성되기에는 어떠한 방법이 있는지, 어떤 검증을 걸쳐 액터가 소멸된다고 판단하는지에 대해 알아보았습니다.

기존의 GC포스팅을 하였을 때 어떻게 엔진에서 소멸자를 담당하여 메모리를 효율적으로 사용하는지에 대해 알고 있었지만, 이번 포스팅을 통해 GC를 통해 마크 앤 스윕단계를 거치기 전에 액터의 생명주기를 알 수 있었습니다.

 


 

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/unreal-engine-actor-lifecycle#%EC%95%A1%ED%84%B0%EB%9D%BC%EC%9D%B4%ED%94%84%EC%82%AC%EC%9D%B4%ED%81%B4%EC%A2%85%EB%A3%8C

+ Recent posts