개요

서버가 클라이언트의 최신 상태를 유지하기 위해 주로 사용하는 것은 액터이다. 서버가 특정 클라이언트를 업데이트할 때가 되면, 서버는 지난 번 업데이트 이후 변경되었다고 보는 연관 액터 전부를 수집한 다음 해당 액터의 최신 상태를 유지하기에 충분할 만큼의 정보를 클라이언트에 전송한다.

 

네트워크 모드의 종류

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();

RepNotify 함수로 OnRep_CurrentHealth를 지정했다.

 

ThirdPersonMPCharacter.cpp에 #include 구문 추가해야 함

#include "Net/UnrealNetwork.h"
#include "Engine/Engine.h"

 

GetLifetimeReplicatedProps 오버라이드

ㄴ Replicated 지정자로 지정된 모든 프로퍼티를 리플리케이트하며, 프로퍼티의 리플리케이트 방식을 구성하도록 지원한다.

 

void AThirdPersonMPCharacter::GetLifetimeReplicatedProps(TArray <FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	//현재 체력 리플리케이트
	DOREPLIFETIME(AThirdPersonMPCharacter, CurrentHealth);
}

Super(캐릭터 클래스)의 GetLifetimeReplicatedProps를 호출하지 않으면 액터의 부모 클래스에서 상속받은 프로퍼티가 부모 클래스에서도 리플리케이트하도록 지정되어 있더라도 리플리케이트되지 않는다.

 

void AThirdPersonMPCharacter::OnRep_CurrentHealth()
{
    OnHealthUpdate();
}

 

서버에서 체력의 값이 변경되면 클라이언트에서의 OnRep_CurrentHealth (RepNotify 함수)가 호출된다.

 

플레이어가 대미지에 반응하게 만들기

/** 최대 체력 게터*/
UFUNCTION(BlueprintPure, Category="Health")
FORCEINLINE float GetMaxHealth() const { return MaxHealth; } 

/** 현재 체력 게터*/
UFUNCTION(BlueprintPure, Category="Health")
FORCEINLINE float GetCurrentHealth() const { return CurrentHealth; }

/** 현재 체력 세터. 값을 0과 MaxHealth 사이로 범위제한하고 OnHealthUpdate를 호출합니다. 서버에서만 호출되어야 합니다.*/
UFUNCTION(BlueprintCallable, Category="Health")
void SetCurrentHealth(float healthValue);

/** 대미지를 받는 이벤트. APawn에서 오버라이드됩니다.*/
UFUNCTION(BlueprintCallable, Category = "Health")
float TakeDamage( float DamageTaken, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser ) override;

캐릭터 클래스 밖에서 플레이어의 체력을 수정할 방법을 만들기 (세터 게터), 데미지 받는 이벤트를 선언한다.

 

void AThirdPersonMPCharacter::SetCurrentHealth(float healthValue)
{
    if (GetLocalRole() == ROLE_Authority)
    {
        CurrentHealth = FMath::Clamp(healthValue, 0.f, MaxHealth);
        OnHealthUpdate();
    }
}

SetHealth는 서버에서만 실행되도록 제한하기 위해 액터의 네트워크 역할을 확인하여 서버 역할인 경우에만 호출될 수 있도록 한다.

 

float AThirdPersonMPCharacter::TakeDamage(float DamageTaken, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
    float damageApplied = CurrentHealth - DamageTaken;
    SetCurrentHealth(damageApplied);
    return damageApplied;
}

TakeDamage는 Actor 클래스에 정의되어 있어서 오버라이드해서 사용한다.

 

이 구현의 장점은 서버, 클라이언트, NetMulticast RPC를 사용하지 않고 CurrentHealth 프로퍼티의 리플리케이션에만 의존하여 모든 변경을 트리거하므로 네트워크 전반에 전송되는 정보의 양이 압축된다는 점이다.

 

ThirdPersonMPProjectile 액터를 생성

SphereComponent, StaticMeshComponent, ProjectileMovementComponent, ParticleSystem, float(Damage)를 프로퍼티로 추가한다. SphereComponent를 루트 컴포넌트로 설정하고, 생성자에서는 각 컴포넌트들을 초기화 해준다.

 

bReplicates = true로 설정하면 액터가 리플리케이트 대상이 된다. (생성, 소멸이 리플리케이트 된다)

 

발사체가 대미지를 유발하게 만들기

void AThirdPersonMPProjectile::OnProjectileImpact(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{   
    if ( OtherActor )
    {
        UGameplayStatics::ApplyPointDamage(OtherActor, Damage, NormalImpulse, Hit, GetInstigator()->Controller, this, DamageType);
    }

    Destroy();
}

발사체가 오브젝트에 충돌할 때 호출할 함수, 유효한 액터와 부딪힐 경우 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 선언부가 생성되는듯 하다.

 

 

참조:

https://docs.unrealengine.com/5.0/ko/multiplayer-programming-quick-start-for-unreal-engine/

 

멀티플레이어 프로그래밍 퀵스타트

C++에서 간단한 멀티플레이어 게임을 만듭니다.

docs.unrealengine.com

 

프로젝트 이름은 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를 통해 복제되어 생성된다.

 

이렇게 추가된 언리얼 오브젝트들은 에디터에서 편집할 수 있게 되고 런타임에서 관리할 수 있게 된다.

 

참조: 

https://www.youtube.com/watch?v=VpEe9DbcZIs 

 

https://blog.naver.com/destiny9720/220934112532

 

[1-5] 클래스 기본 객체 (Class Default Object)

안녕하세요 여러분~ 이번 강좌에서는 하나의 모듈에서 다른 모듈을 참조하는 기능을 구현해보겠습니다. 모...

blog.naver.com

 

BullettimeFPSProjectile (포물체)

총을 줍고 마우스 왼클릭을 했을 때 발사되는 포물체들에 적용되는 코드이다.

포물선의 움직임을 제어하는 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함수를 연결시킴으로써 등록한다.

 

일인칭 템플릿에 대한 공식 Docs

https://docs.unrealengine.com/5.0/ko/first-person-template-in-unreal-engine/

 

일인칭 템플릿

언리얼 엔진의 일인칭 템플릿에 대해 소개합니다.

docs.unrealengine.com

포트폴리오로써 사용할 게임을 드디어 제작하려고 합니다. 이제는 혼자서 게임을 개발할 역량을 갖췄다고 생각하기 때문입니다.

 

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 파일에 구성

스태틱 메시 액터

ㄴ 메시의 지오메트리가 변화하지 않음
ㄴ 액터 자체는 움직이거나 변경될 수 있음
ㄴ 일반적으로 게임 월드 또는 기타 배경을 생성하는데 사용됨

브러시 액터

ㄴ 단순한 3D 지오메트리 (구체, 큐브, 원기둥)을 나타내는 액터
ㄴ 환경 프로토타입을 빠르게 만들고 레벨의 윤곽 작업을 할 때 사용

스켈레탈 메시 액터

ㄴ 지오메트리를 변형할 수 있는 애니메이팅된 메시를 나타내며, 일반적으로 애니메이션 시퀀스 도중 컨트롤 포인트를
사용하여 변형한다.

라이트 액터

ㄴ 디렉셔널 라이트 액터 : 평행광
ㄴ 포인트 라이트 액터 : 점광
ㄴ 스포트 라이트 액터 : 점적광
ㄴ 렉트 라이트 액터 : 사각형 평면으로부터 레벨에 라이트를 발산
ㄴ 스카이 라이트 액터 : 레벨의 먼 지역을 캡처하여 라이트 소스로 씬에 적용

카메라 액터와 시네 카메라 액터

앰비언트 사운드 액터

ㄴ 레벨의 특정 위치에서 루핑 사운드를 재생

오디오 볼륨 액터

ㄴ 오디오의 영향을 받는 영역을 정의하고 리버브 이펙트를 적용

트리거 볼륨 액터

ㄴ 레벨에서 액터가 다른 것과 인터렉션할 때 이벤트를 일으키는 액터
ㄴ 박스, 캡슐, 구체와 같은 형태를 선택할 수 있다.

플레이어 스타트 액터

ㄴ 레벨을 시작할 때 플레이어 캐릭터를 스폰할 위치를 지정

비주얼 이펙트 액터

ㄴ 레벨의 외형과 느낌을 변경하는데 사용, 한정된 3D 볼륨으로 정의

데칼

ㄴ 메시 표면에 배치되어 그 위에 머터리얼을 렌더링한다.

월드 빌딩 엑터

ㄴ 스카이 애트머스피어, 포그, 볼류메트릭 클라우드 등 사실적인 디테일을 더할 수 있다.


레벨을 만들 때 오픈 월드 템플릿을 선택하면 월드 파티션 기능을 사용하여 스트리밍 가능한 오픈 월드를 생성하는 레벨을 만들 수 있다.

 

월드 파티션

ㄴ 대규모 월드를 관리하기 위해 자동 데이터 관리, 거리 기반 레벨 스트리밍 시스템을 사용

ㄴ 스트리밍 소스와 셀의 거리에 따라 로드, 언로드하는 자동 스트리밍 시스템

ㄴ 사용자(개발자) 간의 파일 오버랩을 최소화할 수 있다.

ㄴ 엔진은 특정 시간에 플레이어가 보고 상호작용하는 부분만 로드한다. (플레이어 컨트롤러의 Enable Streaming Source 옵션이 켜져 있다면(디폴트))

 

스트리밍 소스

ㄴ 월드의 특정한 위치 주변 셀의 로딩을 유발하는 컴포넌트

ㄴ 플레이어 컨트롤러는 스트리밍 소스

ㄴ 플레이어 컨트롤러 외의 스트리밍 소스를 추가하기 위해서 월드 파티션 스트리밍 소스 컴포넌트를 사용하여 다른 스트리밍 소스를 월드에 추가할 수도 있다. (플레이어의 텔레포트를 구현해야 할 경우 사용할 수 있다.)

 

월드 파티션과 관련 있는 기능

1. One File Per Actor

ㄴ 여러 팀원이 액터 변경 작업을 하더라도 메인 레벨 파일을 저장할 필요가 없게 되어 사용자 간 작업이 겹치는 상황을 줄여준다. (에디터에서만 이용 가능하다, 모든 액터는 쿠킹될 때 각 레벨 파일에 임베드됨)

ㄴ 월드 파티션을 사용할 때 기본적으로 활성화되어 있다.

ㄴ 1. 특정한 액터에 대해 기능을 활성화 하는 방법 Detail -> Packaging Mode를 External로 설정

ㄴ 2. 전체 레벨의 모든 액터에 대해 기능을 활성화 하는 방법 World Settings -> Use External Actors 체크

현재 레벨 뿐만 아니라 모든 서브레벨을 변환하려면 레벨과 서브레벨을 자동으로 변환하는 커맨드릿을 사용할 수 있다.

 

2. 월드 파티션 - Data Layers

ㄴ 액터를 별도 레이어로 구성하고, 레이어를 로드 언로드하여 월드를 구성한다.

ㄴ 블루프린터로 데이터 레이어를 활성화 할 수 있다.

ㄴ Windows -> World Partition -> Data Layers 

ㄴ 레이어를 만들어 놓고, 액터의 detail -> Data Layers에서 액터를 특정 레이어에 추가할 수 있다.

ㄴ 레이어의 상태는 로드, 언로드, 활성화 중 하나로 표현된다.

 

3. Level Instancing

ㄴ 월드 파티션이 아닌 월드를 월드 파티션 시스템으로 쉽게 이식할 수 있게 해주는 레벨 기반 워크플로

ㄴ 여러 개의 액터를 그룹화하여 하나의 액터로 

 

4. 월드 파티션 - HLOD 

ㄴ 거리가 멀어 언로드된 월드 파티션 그리드 셀을 시각화하여 프레임당 draw 콜 수를 줄인다.

ㄴ 멀리 떨어져있고, 상호작용이 없는 액터를 계속 보이게 해야 할 경우 HLOD에 포함시키면 좋다.

ㄴ 액터를 HLOD 레이어에 추가하는 법 : 액터의 Details -> HLOD 프로퍼티

 

월드 파티션 활성화 방법

ㄴ 월드 세팅에 있는 스트리밍 활성화 옵션 체크

 

기존 레벨을 월드 파티션을 사용할 수 있도록 변환하기

방법 1. Tools -> Convert Level

방법 2. 월드 파티션 변환 커맨드릿 사용하기

 

다수의 레벨 관리

Window-> levels 

 

항상 하나의 Persistent Level이 있으며, 항상 로드되거나 레벨 스트리밍 볼륨, 블루프린트, C++ 코드를 총해 스트림 인 가능한 서브 레벨 하나 이상으로 구성된다.

 

서브레벨 생성은 기존 퍼시스턴트 레벨이나 서브레벨의 일부분을 떼어내거나, 새로운 레벨을 만들거나, 기존 레벨을 추가하는 식으로 이루어진다.

 

월드 세팅

ㄴ 일반적으로 게임 모드와 탐색에 영향을 주는 세팅과 게임의 라이팅, 오디오, 피직스 등을 환경설정하는 특수한 세팅을 포함한다.

 

'언리얼 5 > 정리' 카테고리의 다른 글

개발 구성, 엔진 아키텍처  (0) 2022.10.18
액터 종류  (0) 2022.10.18
애셋과 레퍼런스  (0) 2022.10.14
기초 지식 - 에디터 종류  (0) 2022.10.14
기초 지식 - 기본적인 용어 정리  (0) 2022.10.14

애셋 익스포트

ㄴ 프로젝트 내의 애셋은 .uasset 파일로 저장되어 있는데 (언리얼 엔진 전용 파일 포맷) 다른 언리얼 프로젝트에서 사용하려면 이 파일을 옮기면 된다.

ㄴ 애셋 유형에 따라 익스포트 가능한 파일 유형이 다르다. (FBX, OBJ, COPY 등)

 

레퍼런스 대체 툴

ㄴ 중복되는 애셋 전부를 선택하고 Asset Actions -> Replace References 

ㄴ 선택된 애셋 중 하나의 애셋만 남기고 제거하므로 조심해서 사용해야 한다.

 

애셋 이주

ㄴ 여러 프로젝트에서 같은 애셋을 이용할 때 이주 툴을 사용하여 에셋을 관련 레퍼런스 및 종속성과 함께 복사할 수 있다.

 

애셋 메타데이터

ㄴ 애셋 생성자의 이름, 프로젝트에서 애셋을 사용하려는 목적, 팀의 워크플로에서 애셋의 상태 등의 정보가 포함될 수 있다.

ㄴ 타사 애플리케이션에서 생성한 메타데이터를 애셋과 함께 언리얼 에디터로 임포트할 수도 있다.

ㄴ 런타임 게임플레이 코드에서 직접 액세스할 수는 없지만 언리얼 에디터에서 에셋 관리 작업을 스크립팅하는데 사용할 수 있다. (에디터 스크립팅 유틸리티 플러그인 설치가 필요)

ㄴ 프로젝트 세팅 -> game -> Asset Manager -> Asset Registry -> 메타데이터 태그 설정에서 특정 메타데이터 태그로 애셋을 필터링할 수도 있다.

 

자동 리임포트

ㄴ 외부 컨텐츠 제작 패키지에서 작업하여 저장한 변경사항을 별다른 사용자 입력 없이도 엔진에 자동 반영되도록 하는 기능. 사용자가 지정한 폴더 세트에 대해 소스 컨텐츠 파일의 변경사항을 모니터링하여 파일 변경이 있었으면 변경된 파일을 해당 애셋에 다시 임포트

ㄴ  editor preference -> Loading & Saving -> Auto Reimport 항목

 

클래스 뷰어

ㄴ Tools -> Class Viewer

ㄴ 사용할 수 있는 모든 클래스들을 보여준다. 필터링해서 볼 수도 있다.

 

프로퍼티 매트릭스

ㄴ 다수의 오브젝트나 액터의 프로퍼티를 한번에 편집할 수 있다.

'언리얼 5 > 정리' 카테고리의 다른 글

액터 종류  (0) 2022.10.18
레벨, 월드 세팅, 월드 파티션  (0) 2022.10.15
기초 지식 - 에디터 종류  (0) 2022.10.14
기초 지식 - 기본적인 용어 정리  (0) 2022.10.14
라이팅 설정  (0) 2022.10.05

+ Recent posts