* 효율적인 TArray 사용 방법
- Add
@ 과정
Add를 호출하면 (내부적으로 Emplace -> AddUninitialized 과정을 수행한다.) 요소를 추가할 수 있으며, 이 과정에서 용량이 부족하다면 Relocation을 진행한다. Relocation은 TArray에 넘겨준 Allocator 타입에 따라서 다양하게 동작한다.(Allocator의 CalculateSlackGrow, ResizeAllocation 함수를 통해 진행된다.)
@ Add와 Relocation
Relocation 과정은 새롭게 공간을 할당하고 이전 값들을 새로운 공간에 복사한 후, 이전 공간을 삭제하는 단계로 이루어진다. (성능에 좋지 않다.)
Relocation을 진행할 때, 기본 Allocator에서는 배열에 요소가 없는 경우를 제외하고는 여분의 공간을 더 할당한다. 하지만 요소가 몇 개 추가된다면 다시 Relocation 해야 한다.
이를 방지하기 위해 요소의 개수를 알고 있다면 그 공간만큼 미리 할당해두면 Relocation 을 한번만 수행할 수 있다. TArray에서도 std::vector와 마찬가지로 Reserve 라는 함수를 제공한다.
이미 TArray에 요소가 존재하여 추가할당해야하는 경우에는 여분의 공간을 더 할당하긴하지만, 요소가 빠르게 추가되면 다시 Relocation을 해야한다. 그리고 너무 당연하지만, 함수의 입력으로 넘겨줄 때 &을 사용하여 새로운 할당을 방지해야한다.
- Remove
@ Remove, RemoveSwap 과 Relocation
Remove 함수는 배열에서 요소를 삭제 후, 뒤에 있는 요소들의 메모리를 이동하여 빈 공간을 채운다. RemoveSwap 함수는 배열에서 순서가 중요하지 않은 경우 사용할 수 있는 함수로, 맨 뒤의 요소와 스왑하여 빈 공간을 채운다.
RemoveSwap함수를 사용할 때, 배열의 크기를 줄이기 위해(Shrink) Relocation이 발생할 수 있다. 오래 사용되는 배열의 경우 사용되지 않는 공간을 확보하는 것이 좋기 때문에 필요한 옵션이다. 하지만 짧게 사용되는 배열의 경우 Relocation으로 성능이 나빠질 수 있기 때문에 bAllowShrinking 옵션을 통해 꺼주는 것이 좋다.
- Allocator : FDefaultAllocator, TInlineAllocator, TFixedAllocator
@ FDefaultAllocator(TSizedHeapAllocator<32>)
TArray에 기본적으로 사용되는 Allocator는 FDefaultAllocator(TSizedHeapAllocator<32>)로 이는 동적 메모리 Allocator이다. (힙에 할당) 상황에 따라 동적으로 메모리 할당, 해제하게 된다. 힙 메모리 할당 과정은 lock이 필요하고, 빈 블록 찾는 과정을 거치며, 또 새로운 주소의 공간을 할당하기 때문에 기존에 사용되던 캐시는 전부 버려진다.
@ TInlineAllocator
이를 방지하기 위해 Unreal Engine에서는 TInlineAllocator를 제공하고 있다. 이 Allocator를 사용하면 처음 N개의 공간을 미리 할당해 둘 수 있다.(힙에 할당하지 않고, TArray 내부에 포함된다.)
// typedef TArray<FOverlapInfo, TInlineAllocator<3>> TInlineOverlapInfoArray;와 같이 사용된다.
만약 사용할 공간을 예측할 수 있다면 힙에 메모리를 할당하는 것을 피할 수 있다. 만약 추가 메모리가 필요한 경우에는 TInlineAllocator의 SecondaryAllocator(FDefaultAllocator)를 통해 ( Secondary가 FDefaultAllocator 인 경우에 힙에) 할당된다. (Relocation 과정을 거친다.)
@ TFixedAllocator
TFixedAllocator는 TInlineAllocator와 동일하지만, SecondaryAllocator가 존재하지 않는다.
* Unreal Engine에서의 UObject 관리 간단 분석
- 생성 과정 분석
@ 간단 과정
UObject 를 생성할 때 NewObject 함수나 Actor의 경우 SpawnActor를 사용한다. SpawnActor도 내부적으로 NewObject 함수를 호출하고 있다.
내부 코드는 너무 길지만 간단히 일부 과정을 설명하면, 메모리를 적절한 사이즈에 맞게 할당하고 Archetype(Prefab, Template)을 CDO나 Blueprint 로 설정되어 초기화된다. (내부코드까지 자세히 보진 않아서 정확하지는 않다.)
그 과정에서 UObjectBase(UObject의 최상위 부모클래스) 생성자를 호출하고, 생성자에서 AddObject 함수를 통해 싱글톤 해시테이블(FUObjectHashTables)과 전역 UObject 배열(GUObjectArray)에 객체를 등록한다.
@ FUObjectHashTables
FUObjectHashTables는 다양한 역할을 하고 있지만 주로 클래스 종류별로 UObject를 관리하고 있다. 예를 들어 TActorIterator에서 특정 클래스의 객체를 찾을 때 FUObjectHashTables가 활용된다.
@ GUObjectArray
GUObjectArray은 객체의 배열을 가지고 있는 풀 자료구조로 실제 생성된 객체와 할당할 수 있는 공간을 관리한다. 객체를 등록할 때는 일반적으로 비어있는 풀의 인덱스를 가지고 있는 배열에서 인덱스를 받아서 풀에 등록하게 된다. (UObjectBase(UObject의 최상위 부모클래스)의 InternalIndex에 해당 풀 인덱스를 저장하고 있다.)
객체가 풀에 저장될 때는 실제 객체가 저장되지 않고 FUObjectItem 형태로 저장된다. FUObjectItem 객체를 담는 자료구조는 UObjectBase 포인터와 WeakObjectPtr 에서 사용되는 SerialNumber 등을 가지고 있다.
둘 다 전역객체이기 때문에 크리티컬섹션을 통해 데이터 경쟁 상태를 방지한다.
- TWeakObjectPtr 동작 방식
UObject를 생성하면 GUObjectArray이라는 풀에 저장되고 풀의 객체는 FUObjectItem형식이다. FUObjectItem 내부에 SerialNumber는 멤버로 가지고 있는데, 실제 존재하는(사용중인) 객체인 경우에만 다른 객체와 다른 고유한 값을 가진다.
따라서 최초로 TWeakObjectPtr을 할당하게 되면 객체는 고유한 SerialNumber을 FUObjectItem와 TWeakObjectPtr에 저장한다. 이후 TWeakObjecPtr을 통해 해당 객체에 접근할 때, SerialNumber가 일치하는 지 확인한다. (추가 검증 과정이 더 존재한다.)
일치한다면 경우라면 아직 존재하는 객체이고, SerialNumber가 다르다면(다시 풀에 반납할 때 SerialNumber를 0으로 초기화) 널포인터를 리턴한다.
* Role
Unreal Engine에는 리플리케이션과 관련된 Role과 RemoteRole이라는 두 가지 속성이 존재한다. Role은 간단히 현재 이 시스템에서의 역할을 의미하고, RemoteRole은 연결된 다른 쪽의 역할을 의마한다. 이 두 가지 속성을 통해 다음과 같은 사실을 확인할 수 있다.
- 누가 Actor의 권한을 가졌는지 확인할 수 있다.
- 해당 Actor가 리플리케이션된 Actor인지 확인할 수 있다.
- 리플리케이션 되는 방식에 대해 알 수 있다.
- Role의 종류와 Replication
기본적으로 Role에는 3가지 종류가 있다. (더 존재하지만, 기본적으로 3가지가 가장 많이 활용된다.)
이러한 3가지 종류의 Role은 게임을 실행중인 곳에서 각각 다르며, 이를 활용하여 적절하게 리플리케이션한다. 서버는 대역폭, 성능의 문제로 매 프레임마다 Actor들을 클라이언트로 리플리케이션하지 않기 때문에 서버와 클라이언트의 동기화를 위해서는 Role에 따라 적절한 리플리케이션이 필요하다.
@ ROLE_Authority
: 기본적으로 서버의 모든 Actor의 Role이 ROLE_Authority 로 설정되어 있다. 이로 인해 자연스럽게 클라이언트의 RemoteRole은 ROLE_Authority 가 된다. 가끔 클라이언트에만 스폰되어 존재하는 Actor나 싱글 플레이어 게임에서는 Role 이 ROLE_Authority로 설정된다.
(기본적으로 Actor를 스폰한 곳에서는 Role은 ROLE_Authority 이지만, 클라이언트가 멀티플레이어 게임에서 이 특성을 악용할 수 없다. 클라이언트에서 스폰된 Actor는 서버로 리플리케이션되지 않기 때문이다.)
@ ROLE_SimulatedProxy
: 클라이언트에서 직접 컨트롤하지 않고 서버에서 리플리케이션되어 시뮬레이션되는 Actor의 Role이 ROLE_SimulatedProxy 로 설정되어 있다.
서버에서의 통보받은 해당 Actor의 마지막 속도 등의 정보를 기반으로 시뮬레이션하고, 보간한다.
@ ROLE_AutonomousProxy
: 클라이언트에서 직접 컨트롤하는 Actor의 Role이 ROLE_AutonomousProxy 로 설정되어 있다. 간단하게 말하면 PlayerController 에 의해 소유되는 Actor 들이고, 사용자의 입력을 받게 된다. 이때 서버의 RemoteRole은 ROLE_AutonomousProxy 로 설정된다.
ROLE_SimulatedProxy 와 달리 마지막으로 받은 정보를 기반으로 시뮬레이션, 보간을 하지 않고, 입력을 기반으로 시뮬레이션하게 된다. 훨씬 더 정확한 시뮬레이션이 가능해지며, 컨트롤하고 있는 Actor가 원하는대로 시뮬레이션 될 것이다.
@ Role과 관련된 함수
- HasAuthority 함수를 통해 ROLE_Authority 를 체크할 수 있다.
- Pawn::IsLocallyControlled 함수를 사용하면 이 폰이 실제로 로컬에서 컨트롤 되는 지 알 수 있다. 싱글플레이어 게임일 때 true를 반환하고, 멀티플레이어 게임인 경우에는 클라이언트이고 ROLE_AutonomousProxy Role을 가졌을 때나, ROLE_Authority Role와 ROLE_AutonomousProxy RemoteRole을 가졌을 때 true를 반환한다.(현재 다른 플레이어가 컨트롤하고 있지 않고, 시스템이 서버인 경우)
* Replication
서버의 Actor들을 각 클라이언트로 리플리케이션하기 위한 조건은 다음과 같다.
- 리플리케이션할 Actor는 SetReplicates(true); 를 호출한다. 폰 종류는 PossessedBy가 호출될 때 알아서 호출된다.
- 컴포넌트의 경우 SetIsReplicated(true); 를 호출한다.
- SetReplicateMovement를 사용하여 움직임도 리플리케이션할 수 있다. 리플리케이션 비용이 비싸기 때문에 보통 게임에 영향을 주지 않는 경우에는 꺼두는 편이 좋다.
- 리플리케이션할 속성을 GetLifetimeReplicatedProps 함수 내부에 추가한다.
- 리플리케이션할 속성을 UPROPERTY(Replicated) 또는 UPROPERTY(ReplicatedUsing=OnRep_PropertyName)으로 지정한다.
- 속성 리플리케이션 세부 설명
Actor 들은 리플리케이션되는 속성의 리스트를 가지고 있으며 서버는 속성이 변경되었을 때 클라이언트로 리플리케이션한다. 속성 리플리케이션은 항상 보장되며, MinNetUpdateFrequency, NetUpdateFrequency 를 참조하여 특정 간격마다 가장 최신의 값이 리플리케이션된다. 만약 서버에서 리플리케이션되기 전에 값이 빠르게 변경되었을 때 가장 최종적으로 변경된 값이 리플리케이션된다.
@ 속성 지정자
속성 지정자를 통해 리플리케이션을 설정할 수 있다. 속성 지정자로 Replicated, ReplicatedUsing을 사용할 수 있으며, ReplicatedUsing 을 사용했을 경우 리플리케이션되었을 때 호출될 함수를 지정할 수 있다.
보통 함수명에 접두사로 OnRep을 붙여서 OnRep_PropertyName의 형태로 많이 사용한다.
바운딩되는 함수는 OnRep_PropertyName는 UFUNCTION 이여야 한다.
@ 리플리케이션될 속성 반환 함수 오버라이딩
GetLifetimeReplicatedProps 함수를 재정의 하여 어떤 속성을 어디로 리플리케이션할지 결정한다.
간단히 리플리케이션할 변수를 지정한다고 생각하면 된다. 이 함수에 다음과 같이 추가하면 다음 변수는 모든 클라이언트에게 리플리케이션된다.
다음과 같이 내부에 코드를 작성한다. 모든 클라이언트로 리플리케이션할 수 있고, 조건에 따라 리플리케이션할 수 있다.
- DOREPLIFETIME(ClassName, PropertyName);
- DOREPLIFETIME_CONDITION(ClassName, PropertyName, COND_OwnerOnly);
@ 속성 리플리케이션 시 주의할 점
- 서버에서 속성의 값이 변화가 없다면 클라이언트로 리플리케이션하지 않기 때문에 매번 리플리케이션이 필요한 환경에서는 이를 고려해야한다. (내부 최적화 로직)
- 즉시 리플리케이션이 되지 않아도 될 Actor는 NetUpdateFrequency를 작게 두는 것이 좋다.
* RPC
- RPC 종류
RPC에는 크게 3가지 종류가 있다.
- Server
- Client
- NetMulticast
이 3가지 함수를 구현하기 위해 적절한 속성지정자를 설정해야 한다. 각 속성 지정자는 다음과 같은 의미를 지닌다.
- Server : 서버에서 호출되는 함수
- Client : 클라이언트에서 호출되는 함수
- NetMulticast : 서버와 서버에 연결된 모든 클라이언트에서 호출되는 함수
- Reliable : 신뢰성 있는 호출을 보장한다.
- WithValidation : 보통 서버 함수를 호출하기 전에 직접 구현한 검증 함수를 추가로 호출하여 패킷이 비정상적인 상황인지 확인한다. 만약 검증에 실패했다면 클라이언트를 로그아웃 시킨다. (클라이언트 함수도 선택적으로 사용할 수 있다고 나와있다.)
- 함수 정의
예를 들어 서버 함수를 만드는 방법은 다음과 같다. 필요에 따라 속성 지정자를 설정하면 된다. 보통 함수 이름으로 서버 함수는 Server, 클라이언트 함수는 Client, 멀티캐스트 함수는 Multicast 접두사를 붙인다.
UFUNCTION(Server, Reliable, WithValidation)
void ServerFunctionName()
- 함수 구현
함수명 그대로 구현하지 않고 함수명 뒤에 _Implementation을 붙인 이름의 함수를 구현한다.
void ClassName::ServerFunctionName_Implementation()
{
// ...
}
WithValidation 속성 지정자를 사용한 경우에 다음과 같이 검증 함수도 구현해야한다. 함수명 뒤에 _Validate 을 붙인 형태다. 만약 false를 반환하게 되면 시스템이 연결을 끊어낸다.
bool ClassName::ServerFunctionName_Validate()
{
// ...
}
- 함수 호출
실제로 함수를 호출할 때는 구현한 함수명(XXX_Implementation)이 아닌 선언한 함수명을 사용하여 호출한다. (ServerFunctionName(); 와 같이 호출한다.)
서버에서 클라이언트 함수를 호출하면 해당 Actor를 소유한 클라이언트만 호출되고, 멀티캐스트 함수를 호출하면 서버와 모든 연결된 클라이언트가 호출된다. 나머지 모든 함수는 서버에서 호출된다.
클라이언트에서 서버 함수를 호출하면 해당 클라이언트가 Actor를 소유하고 있는 경우 내부적으로 패킷을 서버로 보내어 서버 함수를 호출한다. 나머지 모든 경우는 해당 클라이언트만 호출된다.
* 기타(너무 당연한 내용은 제외) - 추후 세부 내용 정리
- 게임 관련 상태 정보
* GameMode는 서버에만 존재하는 게임에 관련된 정보를 가지고 있는 클래스로 AGameMode는 너무 많은 정보를 가지고 있기 때문에 보통 AGameModeBase를 상속받아 구현한다. (GameMode 도 Actor를 상속 받고 있기 때문에 Tcik 함수를 사용할 수 있다.)
* GameMode는 서버에만 존재하기 때문에 클라이언트로 게임의 여러 상태를 리플리케이션해주기 위해 GameState 클래스가 존재한다. 주로 현재 동기화된 게임 시간 등의 정보를 담고 있다.
* 플레이어에 대한 개별적인 정보를 관리하는 PlayerState라는 클래스도 존재한다. 클라이언트로 리플리케이션된다. PlayerController가 이미 존재해서 플레이어의 정보들을 여기서 관리해도 될 것 같지만, 클라이언트는 본인의 PlayerController만 가지고 있기 때문에 PlayerState가 필요하다. (서버는 전부 다 볼 수 있다.)
- 액터
* SceneComponent 은 ActorComponent을 상속받은 클래스로 Transform 정보를 가지고 있기 때문에 월드에서 사용할 수 있다. SceneComponent를 상속받은 여러 클래스는 충돌처리를 담당하거나, 시각적인 요소를 담당하기도 한다. 일반적인 ActorComponent와 다르게 계층 구조를 이룰 수 있다.
* ActorComponent은 기능만 거의 담당하고 있으며, SceneComponent 계층 구조에 포함되지 않는다.
- 에디터 활용(블루프린트 등)
* 크래시가 발생하면 현재 켜둔 에디터에 VS를 붙여서 확인할 수도 있지만, DebugGame Editor 모드(최적화하지 않아서 조금 더 자세히 확인할 수 있다.)로 바꾸고 VS를 실행하여 새로운 에디터를 열어서 확인할 수도 있다.
* EditDefaultOnly를 통해 해당 클래스를 상속받은 블루프린트 클래스에서만 해당 속성이 수정하도록 설정할 수 있는데, 시각적으로 작업해야할 때(Mesh정보의 위치를 조정 등), 빠르게 여러 값을 테스트 해볼 때 유용하게 사용될 수 있다.
* 블루프린트는 C++ 의 접근 지정자 공식을 따르지 않는다. 만약 Protected 수준의 접근성을 부여하고 싶다면 다음과 같이 해야한다. UPROPERTY(BlueprintCallable, BlueprintProtected)
* 애니메이션 블루프린트의 애님 그래프에서 내부에 존재하는 로컬 변수를 통해 다양한 애니메이션 전이와 효과를 구현할 수 있다.
* 애니메이션 블루프린트에 존재하는 로컬 변수는 CPP 코드 상에서 직접 수정해도 되지만, 이벤트 그래프를 이용하여 세팅하여 편하게 갱신시킬 수 있다.
* UENUM(BlueprintType) 로 설정하면 블루 프린트에서도 볼 수 있다. 예를 들어 Enum 타입에 따라서 블루프린트를 통해서 UI를 쉽게 컨트롤하기 위해서 사용될 수 있다. (전방 선언시 다음과 같이 사용 enum EnumName : XXX(int8, int32 ...);)
* 델리게이트를 쓰거나 이벤트에 바인딩하는 함수는 무조건 UFUNCTION이다. 다이나믹 델리게이트는 블루프린트와 연동할 수 있고, BlueprintAssignable로 설정하면 블루프린트에서 구현할 수 있다.
* ClassGroup을 설정하면 커스텀 컴포넌트를 정렬해서 볼수 있기 때문에 편리하다.
- 수학, 물리
* FRotator 나 FQuat 를 사용하여 회전에 대항 방향을 받아올 수 있다.
* FQuat를 FRotator 로 반환할 수 있지만 변환하기 위해서 FQuat::Rotator() 함수 내부에서 Atan2를 사용하고 있기 때문에 각도는 [-180, 180]로 제한된다.
* FCollisionQueryParams를 적절히 활용하면 LineTrace에 여러 정보를 설정할 수 있다. (AddIgnoredActor : 무시할 액터 결정, bTraceComplex : 메시기반으로 조금 더 정확한 트레이싱 등)
* 프로젝트 설정에서 Physical Surface 를 커스텀으로 설정할 수 있다. 이를 설정함으로써 다양한 표면을 정의할 수 있고, 표면에 따라 다양한 효과를 표현할 수 있다.Physical Material 생성해서 Physical Surface 를 설정할 수 있고, 스켈레탈 메시의 Physica Asset으로 가서 각 부위를 Physical Material로 설정할 수 있다.
* PhysMaterial를 사용하면 타격 지점의 세부적인 정보를 설정할 수 있다. LineTrace가 적중했을 때, FHitResult에서 PhysicalMaterial의 정보(SurfaceType 등)를 받아올 수 있고, 이에 따라 다양한 처리가 가능하다. (부위 별로 다른 데미지 공식 계산 등)
* OnActorBeginOverlap 델리게이트는 액터가 다른 액터와 겹쳐졌을 때 브로드캐스팅된다.(겹쳐지는 조건은 액터의 모든 컴포넌트를 확인하여 PrimitiveComponent를 찾고, 충돌되었는지 확인한다.)
* 3차 스플라인 보간을 활용하기 위한 함수를 Unreal Engine에서도 제공한다. (CubicInterp, CubicInterpDerivative) 주로 자연스럽게 경로를 예측하여 연결하기 위해 사용된다.
- AI
* 모든 BehaviorTree 는 BlackBoard 컴포넌트와 함꼐 사용되며, BlackBoard는 내부에 데이터를 가지고 있다.
* BehaviorTree에서 데코레이터를 통해 특정 조건을 체크할 수 있으며, 기본적으로 제공하는 NPC, Player 체크 이외에 직접 구현하여 사용할 수 있다. 데코레이터에는 Observer aborts 속성이 존재하고, 다음과 같은 의미를 갖는다.
- Self : 조건이 만족되었을 때 바로 본인의 서브트리를 빠져나감
- Lower Priority : 앞의 조건(더 높은 우선순위의 서브트리)이 만족되었을 때 뒤에서 실행되는 서브트리를 빠져나감. 앞에서 설정한다.
* BehaviorTree에는 매번 실행되는 서비스라는 것이 존재한다. 예를 들어 서비스를 통해 계속 타겟을 바라보게 할 수 있다.
- EQS
* EQS(Environment Query System) 시스템은 AI와 함께 사용되는 시스템으로, 주변 환경에 대한 여러가지 정보를 얻을 수 있다. 이러한 쿼리는 Unreal Engine의 행동트리에서 노드로 사용 가능하다.
이 시스템을 사용하면 주변 환경의 노드 포인트들을 원하는 대로 생성할 수 있고, 조건에 따라 노드를 결정할 수 있다. 조건들은 어떤 액터들에 대한(컨텍스트라고 한다.) 거리나, LineTrace 가능 여부 등 으로 정말 다양하게 설정할 수 있으며, 모든 조건을 확인하고 노드에 조건들에 따라 최종 점수를 산정하여 최적의 노드를 결정한다.
이를 활용하면 체력이 얼마 남지 않았을 때 플레이어가 보지 못하는 곳으로 도망치는 AI를 만들 수도 있고, 플레이어가 보지 못하고 5000cm 이상 떨어진 무작위의 노드를 새로운 AI의 스폰지점으로 설정할 수도 있다.
하지만 아무리 최적화를 잘했다고 하더라도 조건들이 많아지거나 조건 검사 비용이 비싸지고, 쿼리가 많아짐에 따라 성능에 영향이 갈 수 밖에 없는 구조인 것 같다. 주의해서 사용해야 한다고 생각한다.
- Material
* Material 을 만들고 Material Instance를 만들어서 Mesh에 적용할 수 있다. 이를 활용한 예시를 들어보면 Material 1개 만들어 두고 여러 Material Instance를 만들고 색만 바꿔서 사용할 수 있다. 효과는 같지만 색만 다른 형태로 사용할 수 있다.
* Material의 변수를 런타임에 동적으로 변경하고 싶으면 바로 직접 변경해서는 안된다. 직접 변경하게 되면 해당 Material을 사용하는 모든 곳이 다 변경된다. Mesh에서 해당 Material의 MaterialInstanceDynamic를 만들고, 이를 변경해야 한다. (Material이 적절하게 들어가 있는 상황에서 CreateAndSetMaterialInstanceDynamicFromMaterial 함수를 사용한다.)
* Material의 내부 변수를 변경하기 위해서 SetScalarParameterValue함수를 많이 사용한다.
- Sound
* 엔진에서 Sound를 사용할 때 넣을 때 실제 파일로부터 Sound Cue를 만들어서 사용한다. 특정 위치에서 플레이할 수도 있고, 특정 컴포넌트에 붙여서 사용할 수도 있다. Sound Cue에 Attenuation(감쇠)라는 것이 있기 때문에 거리에 따라 소리를 미세하게 조절할 수 있다.
- 기타
* 리플리케이션되는 Enum 값을 그대로 넘겨주지 않고, TEnumAsByte로 래핑해서 보내준다.
* 멀티플레이어 게임에서 PlayerStart 를 통해 스폰 위치를 결정할 수 있다.
* FName은 조회 용도로 많이 사용되고, FText로 변환될 수 있다.
* ConsoleVariable 을 사용하면 런타임에 변수를 바꿀 수 있고, 이는 디버깅에 유용하다.
// 치트가 활성화된 경우에만 쓸 수 있다.
int32 DebugWeaponDrawing = 0;
FAutoConsoleVariableRef CVARDebugWeaponDrawing(TEXT("B.DebugWeapons"),
DebugWeaponDrawing,
TEXT("Draw DebugLine for Weapon"),
ECVF_Cheat);
'게임 엔진 > Unreal Engine' 카테고리의 다른 글
[UnrealEngine] Dynamic Delegate, Multicast Delegate (0) | 2022.04.03 |
---|---|
[UnrealEngine] Delegate (0) | 2022.03.30 |
[UnrealEngine] UObject 시스템(UObject System) (0) | 2022.03.30 |
[UnrealEngine] UObject (0) | 2022.03.30 |
[UnrealEngine] 속성 지정자(Property Specifiers) (0) | 2022.03.29 |