서버가 클라이언트의 최신 상태를 유지하기 위해 주로 사용하는 것은 액터이다. 서버가 특정 클라이언트를 업데이트할 때가 되면, 서버는 지난 번 업데이트 이후 변경되었다고 보는 연관 액터 전부를 수집한 다음 해당 액터의 최신 상태를 유지하기에 충분할 만큼의 정보를 클라이언트에 전송한다.
네트워크 모드의 종류
1. NM_Standalone
ㄴ 로컬 머신에서 실행되며 원격 머신에서 클라이언트를 받지 않는 서버. 싱글 플레이어, 로컬 멀티플레이 게임에 적합
2. NM_DedicatedServer
ㄴ 로컬 플레이어가 없어 그래픽, 사용자 입력, 플레이어 관련 기능을 처리하지 안하도 되어 효율적인 실행이 가능한 전용 서버, 퍼포먼스가 중요해서 신뢰성있는 서버가 필요한 경우에 사용된다.
3. NM_ListenServer
ㄴ 로컬 플레이어가 있는 서버(호스트로). 원격 플레이어에게도 접속을 허락한다.
ㄴ 데디케이티드 서버가 불필요한 경쟁 또는 협력 게임에 좋다.
ㄴ 호스트에게는 네트워크 지연 시간이 없어 유리하게 작용할 여지가 있다.
ㄴ 호스트에 의해 게임이 중단될 수 있다.
4. NM_Client
ㄴ 1~3은 서버 모드고, 4번은 클라이언트 모드
ㄴ 2, 3 서버에 접속할 수 있다.
ㄴ 서버측 로직은 실행되지 않는다.
에디터에서 테스트할 때는 Editor Preferences > Level Editor > Multiplayer Options에서 네트워크 모드를 지정해 테스트할 수 있다.
캐릭터의 스켈레탈 메시와 애니메이션 블루프린트 등의 겉모습을 표현하는 컴포넌트는 리플레케이트되지 않습니다. 하지만 캐릭터의 속도와 같이 게임플레이 및 무브먼트와 연관성이 있는 변수는 리플리케이트되며, 애니메이션 블루프린트는 업데이트될 때 이러한 변수를 읽어 들입니다. 게임플레이 변수가 정확하게 업데이트되면 각 클라이언트 측 캐릭터 사본 또한 시각적 표현을 업데이트할 것입니다.
RepNofity로 플레이어 체력 리플리케이트하기
플레이어에게는 게임플레이 도중 대미지를 가할 수 있는 체력 값이 필요합니다. 이 값은 모든 클라이언트가 각 플레이어의 체력에 대해 동기화된 정보를 갖도록 리플리케이트되어야 하며, 대미지를 입는 플레이어에게 피드백을 제공해야 합니다
ThirdPersonMPCharacter.h
/** 플레이어의 현재 체력. 0이 되면 죽은 것으로 간주됩니다.*/
UPROPERTY(ReplicatedUsing=OnRep_CurrentHealth)
float CurrentHealth;
/** 현재 체력에 가해진 변경에 대한 RepNotify*/
UFUNCTION()
void OnRep_CurrentHealth();
발사체가 오브젝트에 충돌할 때 호출할 함수, 유효한 액터와 부딪힐 경우 ApplyPointDamage 함수를 호출하여 발생한 위치에 대미지를 가하며, 발사체는 소멸된다.
//발사체 충돌 함수를 히트 이벤트에 등록
if (GetLocalRole() == ROLE_Authority)
{
SphereComponent->OnComponentHit.AddDynamic(this, &AThirdPersonMPProjectile::OnProjectileImpact);
}
서버만 이 게임플레이 로직을 실행하도록 OnProjectileImpact 등록 전에 GetLocalRole() == ROLE_Authority 을 확인합니다.
발사체 발사하기 (RPC 함수 사용하기)
UPROPERTY(EditDefaultsOnly, Category="Gameplay|Projectile")
TSubclassOf<class AThirdPersonMPProjectile> ProjectileClass;
/** 발사 딜레이, 단위는 초. 테스트 발사체의 발사 속도를 제어하는 데 사용되지만, 서버 함수의 추가분이 SpawnProjectile을 입력에 직접 바인딩하지 않게 하는 역할도 합니다.*/
UPROPERTY(EditDefaultsOnly, Category="Gameplay")
float FireRate;
/** true인 경우 발사체를 발사하는 프로세스 도중입니다. */
bool bIsFiringWeapon;
/** 무기 발사 시작 함수*/
UFUNCTION(BlueprintCallable, Category="Gameplay")
void StartFire();
/** 무기 발사 종료 함수. 호출되면 플레이어가 StartFire를 다시 사용할 수 있습니다.*/
UFUNCTION(BlueprintCallable, Category = "Gameplay")
void StopFire();
/** 발사체를 스폰하는 서버 함수*/
UFUNCTION(Server, Reliable)
void HandleFire();
발사체 발사에 사용할 변수와 함수입니다. HandleFire 는 이 튜토리얼에서 구현하는 유일한 RPC로, 서버에서 발사체를 스폰합니다. Server 지정자가 있기 때문에 클라이언트에서 이를 호출하려는 모든 시도는 네트워크를 통해 서버의 권위 있는 캐릭터로 전달됩니다.
HandleFire 에는 Reliable 지정자도 있기 때문에 호출될 때마다 신뢰할 수 있는 RPC의 큐 등록에 배치되며, 서버가 수신에 성공하면 큐 등록에서 제거됩니다. 이는 서버가 이 함수의 호출을 확실하게 수신하도록 보장합니다. 그러나 너무 많은 RPC가 제거 없이 동시에 배치되면 신뢰할 수 있는 RPC의 큐 등록이 추가분을 유발할 수 있으며, 그 경우 사용자가 강제로 연결 해제됩니다. 그러므로 플레이어가 이 함수를 호출하는 빈도에 주의해야 합니다.
HandleFire 는 서버 RPC이며 CPP 파일의 구현에서는 반드시 함수 이름에 접미사 _Implementation 이 추가되어야 하기 때문입니다. 여기서의 구현은 캐릭터의 회전 제어를 사용하여 카메라가 향한 방향을 구한 다음 해당 방향으로 발사체를 스폰하여 플레이어가 조준할 수 있게 합니다. 그러면 발사체의 발사체 무브먼트 컴포넌트가 해당 방향을 향한 이동을 처리합니다.
HandleFire_Implemetation이 아닌 HandleFire으로 선언했어도 빌드 과정에서 HandleFire_Implementation 선언부가 생성되는듯 하다.
프로젝트 이름은 BullettimeFPS라고 정했다. 심플한 FPS장르에 플레이어들이 제한된 게이지를 소모하여 월드의 시간을 느리게 할 수 있는 능력을 갖도록 하고 싶기 때문이고 (슈퍼 핫 같은 느낌), 이것이 일반적인 FPS 게임과 차별점이 될 것이다.
에픽 게임즈에서 제공하는 FirstPerson 템플릿을 사용하여 프로젝트를 시작하려 한다. 그렇기 때문에 우선 이 템플릿의 코드를 분석하는 것부터 시작하기로 했다. 6개의 소스 파일 (헤더포함하면 12개)이 기본적으로 생성된다.
BullettimeFPS
BullettimeFPSCharacter
BullettimeFPSGameMode
BullettimeFPSProjectile
TP_PickUpComponent
TP_WeaponComponent
BullettimeFPS
IMPLEMENT_PRIMARY_GAME_MODULE 매크로를 사용하여 primary 모듈을 등록 (FDefaultGameMoudleImpl)
여러 모듈을 사용할 경우 IMPLEMENT_GAME_MODULE 매크로를 사용하여 모듈들을 추가할 수 있고, 모듈들은 [게임명]\Source[모듈명] 디렉터리 아래에 들어가게 되는데 이 샘플 프로젝트는 primary 모듈만을 사용하기 때문에 Source 디렉터리만 존재한다.
게임 전용 모듈을 추가할 때는 모듈에 대한 .Build.cs 파일을 만들고, 이 모듈에 대한 레퍼런스를 게임의 Target.cs 파일에 추가하면 된다. 이렇게 하면 언리얼 빌드 툴이 자동으로 모듈을 발견하여 부가 게임 DLL 파일을 컴파일한다.
BullettimeFPSCharacter, BP_FirstPersonCharacter
ACharacter를 상속받는다.
블루프린트 BP_FirstPersonCharacter가 이 클래스를 상속받아 메시 설정 (블루프린트 에디터에서 설정해야 함)
SkeletalMeshComponent : 1인칭 시점에서 보일 팔
CameraComponent : Character에 기본적으로 달려있는 Capsule 컴포넌트에 SetupAttachment로 카메라 컴포넌트를 붙인다.
SetupPlayerInputComponent (Pawn의 인터페이스) : Project setting > Input 에 설정된 Binding과 동작을 매핑
Dynamic multicast 델리게이트가 선언되어 있다(FOnUseItem) : 아이템을
BullettimeFPSGameMode
AGameModeBase를 상속받는다. (AGameMode는 AGameModeBase의 자식 클래스인데 멀티플레이어 슈팅게임 환경에는 AGameMode가 적합다고 한다. 그렇기 때문에 이후 AGameMode를 상속받도록 전환하겠다)
FClassFinder를 사용하여 BP_FirstPersonCharacter를 찾고, DefaultPawnClass 프로퍼티에 등록한다.
의문점
게임 Play 시 Spawn되는 액터들은 누가 생성해주는가?
outliner에 노랑색 텍스트로 표시되는 액터들은 게임 시작과 동시에 spawn된다.
액터 간 종속 관계를 조사해봤더니 이런 형태이다.
AGameModeBase의 생성자 부분 : 엔진이 초기화 되는 런타임 과정에서 GameModeBase의 생성자에 의해 CDO가 생성된다. 이후 변경되지 않는다.
UObject 인스턴스 생성 시 생성자 호출은 발생하지만, 생성자 코드가 사용되지 않고 모듈 로딩 중 초기화된 CDO 값이 스폰될 액터에 복사된다.
DefaultPawnClass, PlayerControllerClass .. 들이 전부 TSubclassOf 템플릿 자료형으로 정의되어 있는데, 왜 그런 것인지
찾아보았다.
TSubClassOf는 UClass 타입의 안정성을 보장해 주는 템플릿 클래스라고 한다. (UClass* 대신에) TSubClassOf 템플릿을 사용하면 에디터에서 특정 클래스의 파생 클래스만 선택할 수 있도록 제한할 수 있다.
리플렉션 (프로퍼티 시스템)
리플렉션은 C++에서 지원하지 않기 때문에 언리얼에서 자체적으로 지원하는 시스템이다.
프로그램이 실행 시간에 자기 자신(객체 정보)을 조사하는 기능 이라고 한다.
Unreal Build Tool (UBT)는 마킹된 헤더가 최소 하나 들어있는 모듈들을 기억하고, 그 헤더들 중 지난 컴파일 이후 업데이트 된 헤더가 있다면 UHT를 다시 실행한다. UBT는 또한 현재 프로젝트의 폴더 구조와 소스 파일들을 분석해 작업하는 플랫폼(윈도우, 맥)에 맞는 개발 도구 환경(윈도우는 Visual studio)을 자동으로 생성해준다.
헤더파일 상단에 #include "파일이름.generated.h"를 추가해주면 해당 헤더 파일에 리플렉션이 있는 유형으로 마킹한다.
이제 헤더 파일에 U로 시작하는 매크로(UCLASS, UFUNCTION, UPROPERTY 등) 를 사용하여 함수나 멤버 변수, 클래스를 리플렉션 시스템에 보이게 할 수 있다.
Unreal Header Tool (UHT)는 프로젝트 컴파일 이전에 리플렉션 시스템에 보이는 유형들을 수집한다.
UHT는 언리얼 관련 클래스 메타데이터에 대한 C++ 헤더를 파싱하고 Intermediate 폴더에 .generated.h, .gen.cpp 와 같은 코드(메타 헤더 파일, 메타 소스 파일)를 생성해준다. 메타 정보들은 UClass라는 특별한 클래스를 통해 보관된다.
컴파일 단계에서는 개별 언리얼 오브젝트(UObject) 마다 UClass가 생성되고 (UClass의 인스턴스가 생성되는 것은 아님)
실행 초기의 런타임 과정에서는 하나의 언리얼 오브젝트마다 UClass 인스턴스와 CDO(Class Default Object) 인스턴스가 생성된다.
CDO는 클래스 생성자에 의해 생성된 후 변경되지 않는다. 이후 추가되는 인스턴스는 CDO를 통해 복제되어 생성된다.
이렇게 추가된 언리얼 오브젝트들은 에디터에서 편집할 수 있게 되고 런타임에서 관리할 수 있게 된다.
포물선의 움직임을 제어하는 ProjectileMovementComponent가 있다. 속도, 최대속도 등의 옵션을 설정할 수 있다.
Collision 컴포넌트를 가지고 있는데, 이 컴포넌트가 가지고 있는 델리게이트(OnComponentHit)에 이 클래스에서 만든 OnHit 메서드를 등록한다. OnHit 이벤트는 물리 시뮬레이션이 적용된 다른 액터와 충돌하면 그 액터에 힘을 가하고 자기 자신은 Destroy하도록 한다.
TP_PickUpComponent, TP_WeaponComponent, BP_Rifle
두 컴포넌트는 블루프린트 액터 (BP_Rifle)에 컴포넌트로 추가되어 있다.
게임 시작시 플레이어가 총을 들고 있는 것이 아니라 근처로 가서 주워야 하기 때문에 PickUpComponent가 붙었고,
총을 주운 이후 좌클릭을 했을 때 발사체를 생성하고, 사운드 효과를 주기 위해 WeaponComponent가 붙었다.
앞서 설명한 클래스들은 액터를 상속받았기 때문에 클래스 이름에 접두사 A가 붙었지만, 이 클래스들은 컴포넌트를 상속받고 있기 때문에 (각각 USphereComponent와 UActorComponent를 상속받는다) 접두사 U가 붙는다.
PickUpComponent의 상위 클래스인 SphereComponent의 상위 클래스인 PrimitiveComponent에 정의된 OnComponentBeginOverlap 델리게이트에 OnSphereBeginOverlap라는 이름의 사용자 정의 함수를 등록시킨다.
OnSphereBeginOverlap에서는 OnPickUp 이라는 델리게이트를 BroadCast 시킨다.
OnPickUp 델리게이트는 ABullettimeFPSCharacter* 자료형의 인자를 하나 받는 함수를 등록할 수 있는데, 이 샘플에서는 BP_Rifle 블루프린트의 이벤트 그래프에서 TP_WeaponComponent의 AttachWeapon함수를 연결시킴으로써 등록한다.
포트폴리오로써 사용할 게임을 드디어 제작하려고 합니다. 이제는 혼자서 게임을 개발할 역량을 갖췄다고 생각하기 때문입니다.
FPS 장르를 선택한 이유는 제가 가장 좋아하고 오랫동안 즐겨왔던 게임 장르이고, 다양한 개발 테크닉을 적용해 볼 수 있을 것이라 생각했기 때문입니다. 멀티 플레이 게임을 생각하고 있기 때문에 서버 쪽도 최소한의 기능만 제공할 수 있도록 간단하게 만들어보려 합니다.
개발 방향성은 핵심적인 기능들을 확실하게 구현하는 것을 목표로 먼저 개발하고, 세부적인 부분은 버전을 업그레이드 하면서 고쳐 나가는 방식으로 생각 중입니다. 개발을 진행하면서 어떤 기능을 왜 이런 방식으로 구현을 했는지, 다른 방식은 어떤 것들이 있을지를 생각해보고 기록할 것입니다.
아무 것도 없는 상태에서 게임을 제작하려는 것이 벌써부터 막막하긴 하지만, 비슷한 장르로 만들어진 샘플 프로젝트나 인터넷에 도움이 될 글들이 많기 때문에 할 수 있을 것이라 생각합니다.
개발에 도움이 될 샘플 프로젝트(TPS 장르)
남에게 보여줬을 때 부끄럽지 않을 코드를 생산하는 것이 목표이기 때문에 따로 개발 기한은 정해두지 않았습니다.
애셋은 그냥 무료 애셋을 사용할 예정입니다.
구체적인 기획 내용은 구체적으로 기록할 예정은 없고, 해당 부분 개발을 진행할 때 같이 정리할 생각입니다.
UObject와 UCLASS 매크로 UObject에서 파생되는 클래스에 UCLASS 매크로를 사용하여 태그를 해주면 UObject 처리 시스템에서 인식하게 된다. 각 UClass는 Class Default Object라는 오브젝트를 하나 유지한다. UClass에는 클래스를 정의하는 프로퍼티와 함수 세트가 들어있다. 이것들은 네이티브 코드에서 사용할 수 있는 일반 C++ 함수와 변수 + 오브젝트 시스템 안에서의 동작을 제어하는 언리얼 전용 메타데이터까지 포함한다.
UObject에 제공되는 함수성 가비지 컬렉션 직렬화 네트워크 리플리케이션 자동 에디터 통합 레퍼런스 업데이트
비동기 애셋 로딩 ㄴ UObject* 프로퍼티 대신 FSoftObjectPath를 사용하여 특정 애셋에 대한 레퍼런스를 만들되 항상 로드되지는 않도록 한다. ㄴ FSoftObjectPath를 약하게 참조하는 TSoftObjectPtr을 사용하여 애셋이 메모리에 존재하는지 값을 반환 받을 수 있고, 없다면 메모리에 로드한 다음에 다시 참조한다.
명령줄 인수 엔진 실행 방식을 조절하는 옵션 설정을 위해 엔진 실행파일에 전해줄 수 있는 인수 ㄴ 모드 : 메인 에디터 실행파일을 게임 혹은 서버로 실행시킬 수 있다. ㄴ URL 파라미터 : 게임 시동시 특정 맵을 강제로 로드시키기 위해 맵의 주소를 전달 ㄴ 스위치 : 특정 키워드를 값 또는 키-값 쌍으로 게임이나 에디터에 전달할 수 있다. ㄴ INI / 환경설정 : 게임이나 에디터가 로드하는 INI 파일을 임시로 덮어쓸 수 있다.
데이터 유효성 검사 ㄴ UObject에 정의된 IsDataValid 가상함수를 오버라이드하여 사용자 정의 조건으로 유효성을 검사할 수 있다. ㄴ Data Validation 플러그인을 사용 ㄴ 애셋이 명명 규칙을 충족하는지 확인 ㄴ 공간 및 퍼포먼스 예산 집행 ㄴ 비순환 종속성 발견 ㄴ 단일 애셋 ~ 프로젝트 전체 범위로 데이터 유효성 검사를 할 수 있다.
애셋 레지스트리 ㄴ 애셋 레지스트리는 에디터가 로드되면서 로드되지 않은 애셋에 대한 정보를 비동기적으로 모으는 서브시스템이다. ㄴ 애셋에 대한 데이터를 검색 가능하게 만드려면, 프로퍼티에 AssetRegistrySearchable 태그를 추가해 줘야 한다. ㄴ 애셋을 로드하지 않고 목록을 만들 수 있도록 메모리에 저장된다.
클래스별 애셋 목록 만들기 ㄴ 애셋 레지스트리 모듈을 로드한 다음 Module.Get().GetAssetsByClass를 호출하여 얻는다. ㄴ 목록은 FAssetData 오브젝트 배열에 반환된다. ㄴ FAssetData는 GetAsset함수를 사용하여 UObject*로 변환할 수 있다.
FAssetData에는 애셋의 프로퍼티 값의 목록인 TagsAndValues라는 이름과 값의 매핑이 들어있다. 이 정보는 애셋이 저장되어 UAsset 파일의 헤더에 기록될때 수집된다. UPROPERTY 매크로의 AssetRegistrySearchable 플래그로 마킹된 프로퍼티만 TagsAndValues 맵을 채운다. <변수이름 - 값>의 형태로
태스크 시스템 ㄴ 게임 플레이 코드를 비동기 실행할 수 있는 기능을 제공하는 작업 매니저, 종속 작업에 대해 방향성 비사이클 그래프를 만들고 실행할 수 있다. ㄴ 태스크를 실행하려면 태스크의 디버그 이름과 호출 가능한 태스크 바디 오브젝트를 제공해야 한다. ㄴ 태스크 실행(Launch)은 결과 FTask(태스크 핸들)를 반환할 수 있다. 태스크 바디에서 반환한 결과의 타입과 일치하는 TTask<타입> 으로 받는다. ㄴ 태스크 우선순위는 태스크의 실행 순서에 영향을 준다. Launch할 때 인자로 지정할 수 있다.
busy wait ㄴ 대기중인 태스크가 완료될 때까지 다른 작업을 실행하려는 시도 ㄴ 스케줄러가 태스크를 선택할 수 없다.. ㄴ 데드락의 위험도 있다.
전제조건 ㄴ 선행 태스크 핸들을 후속 태스크 Launch할 때 인자로 넣어주면 태스크 간 종속성을 표현할 수 있다.
중첩된 태스크 ㄴ 전제 조건과 다른 점은 두 태스크가 동시에 실행되어도 괜찮지만, 완료는 순서가 있다는 점 ㄴ 태스크 A의 바디에서 태스크 B를 Launch하고, AddNested(TaskB)를 호출하면 태스크B는 A의 자식 태스크가 된다 ㄴ TaskA.Wait을 하게 되면 TaskB와 TaskA가 완료될 때까지 기다린다. AddNested 함수는 태스크 내부에서 호출하지 않는 경우 Assert를 호출한다.
파이프 ㄴ 동시 실행이 아닌 연속적으로 실행되는 태스크로 이루어진 체인 ㄴ 여러 스레드에서 공유 자원에 접근할 때 뮤텍스를 잠그는 방법을 사용하지 않음 ㄴ 공유된 리소스에 대한 비동기 작업을 위해 존재한다. (비동기 인터페이스를 직접 구현할 필요가 없다) 리소스마다 파이프를 두고, 공유된 리소스에 대한 모든 액세스는 파이프에서 실행한 태스크 내부에서 수행한다.
태스크 이벤트 ㄴ 태스크 바디가 없고 실행할 수 없는 태스크 타입 ㄴ 명시적으로 Trigger함수를 호출해야 실행된다. ㄴ 다른 태스크의 전제 조건으로 활용하여 다양한 작업을 할 수 있다.
언리얼 오브젝트 (UObject) 처리 ㄴ 자동 프로퍼티 초기화 : 생성자 호출 전 초기화시 자동으로 0으로 채워진다. (클래스, UProperty, 네이티브 멤버 모두) ㄴ 레퍼런스 자동 업데이트 : AActor 또는 UActorComponent가 소멸되거나 다른 식으로 플레이에서 제거되면 리플렉션 시스템에서 보이고 있는 그에 대한 모든 레퍼런스는 자동으로 null이 된다. UProperty가 아닌 오브젝트 포인터가 필요한 경우, TWeakObjectPtr을 대신 사용 에디터에서 애셋을 Force Delete한 경우에도 참조된 UObject UProperty가 자동으로 null이 된다.
Serialization
런타임 유형 정보 및 형변환 ㄴ 자식 클래스에서 Super로 부모 클래스에 접근할 수 있다. ㄴ Cast 함수 템플릿을 사용해서 UObject를 형변환할 수 있다.
가비지 컬렉션 ㄴ 엔진에서는 레퍼런스 그래프를 만들어 어느 오브젝트가 아직 사용중이고 어느 것이 필요 없는지 ㄴ 주기적으로 소멸 예약시킨 UObject를 정리할 때, UObject 레퍼런스 트리를 검색하여 참조되지 않은 오브젝트, 즉 트리 검색에서 찾지 못한 것들은 더 이상 필요하지 않은 오브젝트라고 가정하고 제거한다. ㄴ 살려두고자 하는 오브젝트가 있다면 UPROPERTY 레퍼런스를 유지하거나 그에 대한 포인터를 언리얼 엔진 컨테이너 클래스에 저장해야 한다. ㄴ 액터는 자신의 Destroy 함수를 호출하여 명시적으로 소멸 예약시킨다. (컴포넌트는 소유 액터가 제거될 때 소멸) ㄴ 가비지 컬렉션 튜닝을 위한 세팅은 프로젝트 세팅 -> 가비지 컬렉션에서 설정할 수 있다.
네트워크 리플리케이션 ㄴ 흔히 쓰는 모델 : 서버에서 변수가 변경되면 엔진에서 그 변화를 감지하여 모든 클라이언트에 신뢰성 있게 전송하는 것 ㄴ UProperty에 태그를 설정하면 네트워크 플레이 도중 데이터 복제 여부를 엔진에게 알릴 수 있다. ㄴ UFuncion에 태그를 설정하면 server 태그가 붙은 함수는 클라이언트 머신에서 호출할 때 서버 머신에서 해당 액터의 서버 버전이 실행되도록 한다. client 함수는 서버에서 호출될 수는 있지만 해당 액터의 소유중인 클라이언트 버전이 실행된다.
언리얼 스마트 포인터 라이브러리 ㄴ TSharedPtr : 참조하는 오브젝트를 소유하며, 참조하는 쉐어드 포인터 또는 쉐어드 레퍼런스가 없을 경우에 오브젝트를 소멸시킨다. ㄴ TSharedRef : sharedPtr과 비슷하지만 항상 Null이 불가능한 오브젝트를 참조해야 한다는 점이 다르다. ㄴ TWeakPtr : 오브젝트를 소유하지 않기 때문에 생명 주기에 영향을 주지 않는다. ㄴ TUniquePtr : 다른 유니크 포인터에 넘겨줄 때는(소유권 이전) MoveTemp 함수로 넘겨준다.
스마트 포인터 구현 세부사항 ㄴ 일부 스마트 포인터 타입은 C++ 기본 포인터보다 느리기 때문에 렌더링과 같은 로우 레벨 엔진 코드에는 덜 유용하다.
스마트 포인터의 이점 ㄴ 메모리 누수 방지 : 스마트 포인터(위크 포인터 제외) 들은 공유된 레퍼런스가 없으면 오브젝트가 자동 소멸된다. ㄴ 위크 레퍼런싱 : 위크 포인터는 참조 주기에 영향을 주지 않고 댕글링 포인터를 방지한다. ㄴ 선택적인 스레드 안전 : 스레드 세이프 스마트 포인터와 그냥 스마트 포인터가 구분되어 있어서 스레드 안정성이 필요없다면 향상된 퍼포먼스를 구현할 수 있다. ㄴ 런타임 안정성 : 쉐어드 레퍼런스는 절대 null일 수 없고, 언제든지 참조 해제할 수 있다. ㄴ 메모리(?) : 스마트 포인터는 64비트 C++ 포인터 크기의 두 배(128비트)이다. 유니크 포인터는 예외로 64비트 크기 ㄴ 명확한 의도 : 관찰자 중에서 오브젝트의 소유자를 쉽게 분별할 수 있다.
스마트 포인터의 퍼포먼스 이점 ㄴ 빌드를 출시할 때, 대부분의 스마트 포인터들을 참조 해제하는 속도가 C++ 기본 포인터만큼 빠르다. ㄴ 스마트 포인터를 복사해도 절대 메모리가 할당되지 않는다. ㄴ 스레드 세이프 스마트 포인터는 잠금 없는 구조이다.
스마트 포인터의 퍼포먼스 문제점 ㄴ 스마트 포인터의 생성 및 복사는 C++ 기본 포인터의 생성 및 복사보다 더 많은 오버헤드가 발생한다. ㄴ 참조 카운트를 많이 유지하면 기본 사이클(?)이 복잡해진다.
스레드 안정성 기본적으로 스마트 포인터는 싱글 스레드가 접근하는 것에 안전하기 때문에 멀티 스레드가 접근해야한다면 스마트 포인터 클래스의 스레드 세이프 버전을 사용해야 한다.
프로그래밍 툴 : 로우 레벨 메모리 트래커 ㄴ 언리얼 엔진 프로젝트의 메모리 사용량을 추적하는 툴, 범위 태그 시스템을 사용하여 언리얼 엔진과 OS가 할당한 모든 메모리 기록을 유지함
Sparse Class Data 시스템 프로퍼티는 디자이너가 액터 비헤이비어에서 반복작업을 수행할 수 있는 좋은 방법을 제공하지만 게임을 출시할 때는 대다수의 프로퍼티가 상수처럼 사용되기 때문에 중복된 프로퍼티를 사용하는 경우 희소 클래스 데이터를 사용하면 이러한 프로퍼티를 단일 공유 구조체 인스턴스로 전송하여 메모리에 프로퍼티의 사본 단 하나만을 유지하는 동시에 디자이너가 블루프린트 그래프에서 프로퍼티 값에 액세스하고 편집하도록 할 수 있다.
게임 플레이 클래스 ㄴ 애셋이나 클래스의 레퍼런스를 찾아야 하는 경우는 한 번만 찾도록 생성자에서 static으로 선언된 FObjectFinder나 FClassFinder에 찾아 놓고 이것을 사용해 프로퍼티를 초기화한다.
언리얼 엔진 아키텍처
Assert ㄴ check 매크로 : DO_CHECK 매크로가 빌드에 컴파일되는 경우에 표현식을 실행 ㄴ verify 매크로 : DO_CHECK가 꺼져 있어도 표현식을 실행 ㄴ ensure 매크로 : 표현식을 검증하여 실패하면 그 지점까지 이르는 콜스택을 생성한다.
델리게이트 ㄴ 델리게이트는 힙에 메모리를 할당해야 하기 때문에 값으로 전달 가능하기는 하지만 가급적이면 참조 전달하는 편이 낫다. ㄴ 델리게이트 선언은 제공되어 있는 매크로 중 하나를 사용하여 이루어진다. 선언한 델리게이트 유형을 사용하여 델리게이트 클래스의 인스턴스를 생성한다. 함수에 델리게이트를 동적으로 바인딩 후 델리게이트의 Execute를 호출하면 됨 ㄴ 캐릭터에 Onattacked 델리게이트를 public으로 만들어 놓고 다른 클래스들에서 이 델리게이트에 접근하여 호출되었으면 하는 함수를 등록하는 방식으로 사용할 수 있다.
타이머 ㄴ 타이머는 글로벌 타이머 매니저에서 관리한다. GetWorldTimerManager ㄴ SetTimer와 SetTimerForNextTick 함수로 타이머를 셋업하는데, 각각 오버로드가 다수 있다. ㄴ SetTimer 함수는 딜레이 후 함수나 델리게이트를 호출하는 타이머를 설정하며, 그 함수 호출을 무한 반복하도록 설정할 수 있다. ㄴ PauseTimer는 타이머 핸들을 사용하여 실행중인 타이머를 일시정지한다. 함수 호출 실행은 막히지만 경과 및 남은 시간은 그대로 유지된다. ㄴ 액터에 종속된 다른 액터 초기화에 사용할 수 있다.
게임플레이 모듈 모듈의 루트 폴더는 해당 모듈과 이름이 동일해야 한다. ㄴ Source\[모듈명]\Public 디렉터리에 헤더 파일 ㄴ Source\[모듈명]\Private 디렉터리에 C++ 파일 IMPLEMENT_PRIMARY_GAME_MODULE( <ModuleName>, "<GameName>"); ㄴ Source\[모듈명] 디렉터리에 빌드 파일(모듈명.Build.cs)
DefaultEngine.ini 파일에 추가한 모듈에 대한 내용을 추가해야 한다.
인터페이스 트리거 볼륨에 들어서면 함정이 발동되거나, 적에게 경보가 울리거나, 플레이어에게 점수를 주는 시스템을 가진 게임 ㄴ 함정, 적, 점수에서 ReactToTrigger 함수를 구현 ㄴ 여러 클래스에서 공유 함수 기능이 필요하므로 인터페이스를 사용하여 ReactToTrigger 함수를 가상 함수로 선언
인터페이스 선언 UINTERFACE 매크로를 사용 UObject 대신 UInterface를 상속 (UInterface가 진짜 인터페이스는 아님) 헤더 파일에 UInterface 를 상속받는 클래스 이름에서 U를 I로 바꾼 클래스를 정의한다. 다른 클래스에서 상속하게 되는 실제 인터페이스는 이 I로 시작하는 클래스가 될 것이다.
프로퍼티 (UPROPERTY 매크로) 정수, (비트 마스크로 노출 가능) 부동 소수점 (float, double) 부울 스트링 ㄴ FString : 고정적인 캐릭터 동적 배열 ㄴ FName : 스트링 테이블로 된 변경불능 대소문자 구분 없는 스트링에 대한 레퍼런스 ㄴ FText : 현지화 처리를 위해 고안된 보다 탄탄한 스트링 표현'
구조체 (USTRUCT 매크로)
코어 리디렉트 ㄴ 개발 도중 기존 클래스, 프로퍼티, 함수의 이름을 변경해야 할 경우가 있을 때 사용한다. ㄴ 플러그인이나 프로젝트의 ini 파일에 구성