ESlateVisiblity

  • 위젯을 모니터상의 출력여부에 대한 상태를 제어할 때 사용하는 열거형
  • SetVisibility(ESlateVisibility InVisibility) : 현재 위젯의 상태를 변경할 때
  • GetVisibility() : 현재 위젯의 상태를 확인할 때 (Return ESlateVisibility Visibility)
  • 위젯 애니메이션 블렌딩의 경우
  • 마우스 입력 또는 키보드 입력값을 받을 수 있는지에 대한 여부 결정시

 

 

ESlateVisibility - SlateWrapperTypes.h

/** Is an entity visible? */
UENUM(BlueprintType)
enum class ESlateVisibility : uint8
{
	/** Visible and hit-testable (can interact with cursor). Default value. */
	Visible,
	/** Not visible and takes up no space in the layout (obviously not hit-testable). */
	Collapsed,
	/** Not visible but occupies layout space (obviously not hit-testable). */
	Hidden,
	/** Visible but not hit-testable (cannot interact with cursor) and children in the hierarchy (if any) are also not hit-testable. */
	HitTestInvisible UMETA(DisplayName = "Not Hit-Testable (Self & All Children)"),
	/** Visible but not hit-testable (cannot interact with cursor) and doesn't affect hit-testing on children (if any). */
	SelfHitTestInvisible UMETA(DisplayName = "Not Hit-Testable (Self Only)")
};

 

  • Visible
    • SetVisibility 설정하지 않을 경우의 기본값
    • 화면에 출력되며 마우스 클릭 및 터치 등의 인터렉션이 가능하다.
    • 일반적으로 위젯이 열려있으며, 버튼, 텍스트 등의 인터렉션이 필요하 경우 (우편함, 로비 플레이 버튼 등)
  • Collapsed
    • 화면에 출력되지 않으며 레이아웃에서 공간을 차지하지 않는 형태
    • 라운드 시작시 상호작용하지 못하는 위젯들 (상점 등)
  • Hidden
    • 화면에 출력되지 않으며 레이아웃에서 공간을 차지하는 형태
    • 플레이어 이름표 애니메이션 처리 또는 레이아웃 위치를 유지해야하는 경우
  • HitTestInvisible
    • 화면에 출력되지만 인터렉션이 불가능하며, Child에게도 적용된다.
    • 화면 전체에 덮인 오버레이의 경우 (대쉬 이펙트, 블러드 이펙트 등)
  • SelfHitTestInvisible
    • 화면에 출력되지만 인터렉션이 불가능한 형태. Child 위젯들에게는 적용되지 않는다.
    • 듀토리얼에서의 특정 버튼의 활성화의 경우

 

발생한 문제

프로젝트 및 월드 세팅을 언리얼 엔진에서 제공하는 GameState 클래스의 경우 컨트롤러에 빙의한 캐릭터 내
Movement Component가 자동으로 동기화가 됨.

 

게임 내 공통적으로 필요한 기능들을 구현하기 위해 GameStateBase 기반의 커스텀 GS를 생성, 게임모드 생성자에서

GameStateClass = MyGS::StatcClass() 적용, 프로젝트 및 월드세팅 확인 후 결과

다른 컴포넌트들은 정상적으로 동기화가 되었으나, 언리얼에서 제공하는 Movement Component는 동기화가 안됨.

 

해결 과정

AMyGameMode.cpp

AMyGameState::AMyGameState()
{
	bReplicates = true;
	bAlwaysRelevant = true;
	bNetLoadOnClient = true;
}

 

리플리케이티드 및 네트워크 동기화를 위한 변수 true 적용해보기

언리얼에서 제공하는 기본 게임 스테이트 클래스에서는 적용이 되어있었으나,

커스텀 게임 스테이트 클래스에서는 명시적으로 처리해야 하는 것.

  • bReplicates : 클라이언트에 해당 GameState 복제에 대한 여부
  • bAlwaysRelevant : 모든 클라이언트가 항상 리플리케이션 되도록 설정
  • bNetLoadOnClient : 클라이언트가 레벨 로드시 GameState를 생성하도록 설정

 

BP_MyCharacter

 

무브먼트 컴포넌트 내 리플리케이트 설정

  • C++ : GetMovementComponent()->SetIsReplicated(true);

 

두가지의 적용 결과 언리얼에서 제공하는 기본적인 기능들이 리플리케이션이 적용되지 않았습니다.

- Weapon관련 RPC, Health관련 RPC의 경우는 정상적으로 동작됨을 확인

 

 

AGameStateBase -> AGameState 클래스 상속 변경

 

기반 상속구조 변경 후 정상적으로 동기화가 됨.

 

 

멀티플레이 내 GameState VS GameStateBase

  • override 하지 않은 함수들의 경우, 기본적으로 GameStateBase에서 동일한 로직을 구현할 것이라 생각.
  • 생성자 관련 부분 먼저 확인해보기

GameStateBase.cpp - Constructor
GameState.cpp - Constructor

 

  • 생성자 관련 부분에서 State에서는 추가적인 멀티플레이 로직이 없는것을 확인 할 수 있었음.
  • 즉, 생성자 외 오버라이드된 함수 내에서 추가기능을 통해 Possess 관련 동기화과 있을것이라 생각.

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/game-mode-and-game-state-in-unreal-engine

 

공식문서 설명을 보면, 해당 함수 및 변수들이 멀티플레이 관련 역할을 한다고 알 수 있다.

 

// AGameStateBase.cpp
void AGameStateBase::PostInitializeComponents()
{
	Super::PostInitializeComponents();

	UWorld* World = GetWorld();
	World->SetGameState(this);

	FTimerManager& TimerManager = GetWorldTimerManager();
	if (World->IsGameWorld() && GetLocalRole() == ROLE_Authority)
	{
		UpdateServerTimeSeconds();
		if (ServerWorldTimeSecondsUpdateFrequency > 0.f)
		{
			TimerManager.SetTimer(TimerHandle_UpdateServerTimeSeconds, this, &AGameStateBase::UpdateServerTimeSeconds, ServerWorldTimeSecondsUpdateFrequency, true);
		}
	}

	for (TActorIterator<APlayerState> It(World); It; ++It)
	{
		AddPlayerState(*It);
	}
}

// AGameState.cpp
void AGameState::PostInitializeComponents()
{
	Super::PostInitializeComponents();

	FTimerManager& TimerManager = GetWorldTimerManager();
	TimerManager.SetTimer(TimerHandle_DefaultTimer, this, &AGameState::DefaultTimer, GetWorldSettings()->GetEffectiveTimeDilation() / GetWorldSettings()->DemoPlayTimeDilation, true);
}

 

  • 주요 문제는 StateBase 상속 시 Component가 동기화 되지않음
  • 즉, Component 관련 설정이 문제다. -> PlayerArray에 추가하는 로직이 오버라이드 되어있다.
  • 언리얼은 서버 동기화시 실시간성을 동시 지원하기위해 예측 및 Reconciliation 방법을 사용.
  • 동기화시 서버 월드와 클라이언트의 월드는 서로 다르기때문에 월드 서버를 기준으로 동작해야된다.
    • TimeManager를 통해 서버의 월드를 Simulate해서 클라이언트의 월드를 복제, 시간값 관련 동기화

 

 

 

 

 

아키텍처 (Architecture)

  • 하나의 시스템(어플리케이션,서비스) 등의 구성 및 동작들을 나타내는 설계도 
  • 시스템의 구성 및 동작원리를 나타내는 것
  • 구성 요소간의 관계도 또는 시스템 외부 환경과의 관계도

 

컴포넌트 기반 아키텍처 (Component Architecture)

https://www.mendix.com/ko/blog/what-is-component-based-architecture/

 

  • 모듈화가 가능하다
    • 여러 액터들이 같은 컴포넌트를 공유할 수 있다. (재사용성이 높음)
    • 기능별 독립적으로 실행할 수 있다.
  • 확장성이 높다.
    • 새로운 기능 추가시 Actor의 수정을 최소화 할 수 있다. (유지보수성이 높다)
  • 객체지향 원칙의 특징을 활용하기 적합하다.
    • Actor가 여러 역할을 할 경우 SRP를 위반하게된다.
    • 기능 추가/제거 시 수정이 최소화된다. (OCP)
  • 필요한 기능들을 개별적으로 설정할 수 있다.
    • Actor -> bReplicated시 Actor 관련 전체 네트워크 동기화
    • ActorComponent -> SetIsReplicated()를 사용하여 특정 컴포넌트만 동기화
  • 블루프린트 연동 가능
    • 비개발자들과의 원활한 협업 가능

 

컴포넌트 기반 아키텍처를 설계한 이유

 

컴포넌트 기반의 아키텍처는 위의 설명과 같이 모듈화를 함으로써 유지보수성 및 재사용성이 높습니다.

또한 하나의 컴포넌트마다 각자 하나의 책임만을 가지게 할 수 있음으로써 객체지향의 원칙을 유지할 수 있습니다.

 

LyraCharacter - Actor Components

Ex) TakeDamage를 사용한 상자 / 캐릭터 -> HealthComponent를 사용하여 동일 로직 처리

 - 하나의 컴포넌트로 다양한 액터에 동일한 로직 적용함으로써 코드 중복 제거, 재사용성 증가

 - 각 액터마다 추가 기능들이 있을 경우 HealthComponent를 상속하여 다형성을 통한 높은 확장성 보장

 

 

 

 

출처 및 참고내역

https://www.mendix.com/ko/blog/what-is-component-based-architecture/

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/lyra-sample-game-in-unreal-engine

데이터 테이블 (DataTable)

  • CSV 또는 Json과 같은 데이터 파일을 사용할 수 있다.
    • CSV/Json같은 외부 파일을 이용해 데이터를 저장/불러오기 가능
    • 에디터 내부에서도 데이터 입력 가능
  • UStruct 기반의 데이터 구조를 고정해서 사용하다
    • struct FDataTable : public FTableRowBase
  • 확장이 어려우며, 다른 데이터 구조와 연결하기가 어렵다.
  • 원하는 정보를 찾기 쉽다
  • 런타임 환경에서 데이터테이블 Row들에 대한 수정이 불가능하다.
  • TMap, TArray와 같은 컨테이너 타입의 사용이 불가능하다.
    • TArray는 기본 자료형은 가능하다. (FText, float, FVector 등)
  • 대화 / 퀘스트 시스템, 언어 시트, 게임 전역 설정, 경험치 요구량 또는 능력치 증가표 등에 적합하다.

사용 예시

1. NormalQuestDataTable.h

// 테이블 Row에 들어갈 값을 정의하는 구조체.
// FTableRowBase기반으로 구현한다.

USTRUCT(BlueprintType)
struct FQuestDataTable : public FTableRowBase
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FName QuestID; 

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FText QuestName; 

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FString QuestDescription; 

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    int32 RewardXP; 
	
	//...
};

 

 

2. 언리얼 엔진 내 데이터테이블 생성 (Row Select)

정의한 구조체 기반의 데이터테이블을 생성할 수 있다.

 

3. 정의한 데이터테이블 Row관련 Column 정의

생성한 데이터 테이블을 에디터 레벨에서도 정의할 수 있다.

또한, 정의한 데이터 테이블을 CSV,Json 타입으로 변환이 가능하다.

 

4. DataTable 조회

// DataTable 조회 방법

	for (const FName& RowName : RowNames)
	{
		FUIObjectBaseData* RowData = UICardInfoDataTable->FindRow<FUIObjectBaseData>(RowName, ContextString);

		if (RowData && RowData->CardType == CardType)
		{
			UpdateCardUI(*RowData);
			ApplyCardType = CardType;
			ApplyValue = RowData->IncreaseAmount * GradeValue;
			return;
		}
	}

어떤 Row를 기준으로 조회할 것인지, 그 후 Contaions, IsA와 같은 방법을 통해 원하는 Column을 가져올 수 있다.

 

 

데이터 에셋 (Data Asset)

  • 에디터를 통해 데이터를 입력할 수 있다
  • 작은 규모의 정보 저장에 적합하다
    • 데이터 중첩 가능
    • 개별 아이템 등을 위한 구조에 적합
  • 확장성이 좋다. 재사용성 및 계층구조에 적합하다.
  • TArray, FStruct, TMap등 컨테이너 구조를 활용한 데이터 관리가 가능하다.
  • 데이터 테이블에 비해 로딩 속도가 빠르다
  • Input Action, Weapon Data, Item Data 등 에디터 레벨에서 설정하는 것들
    • DataPrimaryAsset을 통해 런타임 환경에서도 변경할 수 있다.

 

사용 예시

1. WeaponDataAsset.h

// 1. 무기 데이터를 리스트 형태로 하나의 데이터에셋에 관리하는 경우

USTRUCT(BlueprintType)
struct FWeaponInfo
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FName WeaponID;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FText DisplayName;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    int32 Damage;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    float Range;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    UTexture2D* Icon;
};

// WeaponDataAsset_List.h
UCLASS(BlueprintType)
class UWeaponDataAsset_List : public UDataAsset
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    TArray<FWeaponInfo> WeaponList;
};

// 2. 단일 무기에 대한 데이터 에셋의 경우 
UCLASS(BlueprintType)
class UWeaponDataAsset : public UPrimaryDataAsset
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FName WeaponID;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FText DisplayName;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    int32 Damage;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    float Range;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    UStaticMesh* WeaponMesh;

    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    USoundBase* FireSound;
};

 

 

 

 

2. WeaponDataAsset VS WeaponDataAsset_List 

DataAsset 내 데이터들을 선언이 가능하며, 해당 데이터들을 구조체를 활용하여 컨테이너를 사용한 데이터 관리가 가능하다.

 

3. DataTable_List 조회

// DataAsset 조회 방법

	for (const auto& WeaponData : WeaponDataAssets->WeaponTextures)
	{
		if (WeaponData.WeaponClass == CurrentWeaponClass)
		{
			if (WeaponTexture && WeaponData.WeaponTexture)
			{
				WeaponTexture->SetBrushFromTexture(WeaponData.WeaponTexture);
			}
			return;
		}
	}

또는 IsA, Contains를 사용한 조회 가능.

 

 

 

출처 및 참고내역

 

https://dev.epicgames.com/community/learning/tutorials/jO8J/unreal-engine-vs

 

개인 프로젝트를 진행하던 중 애니메이션 구조에 대해서 확장적인 설계에 대해 고민하게 되었으며

UE5 프레임워크의 이해를 돕기 위한 샘플 게임 프로젝트로 설계된 학습 리소스 "Lyra Starter Game"의 구조를 학습하며

이를 기반으로 애니메이션을 설계 및 리팩토링을 하고자 합니다.

이번 포스팅에서는 개인 프로젝트의 확장적 구조 설계 및 오버헤드 관련 최적화와 관련되어 학습한 Lyra 프로젝트 내
애니메이션에 대해 알아보겠습니다.

 

※ Lyra는 GAS(GameAbilitySystem)를 사용하여 설계되어있으며, 진행중인 프로젝트에서는 GAS를 사용하지 않습니다.
이로인해 GAS 관련된 학습 내용은 해당 포스팅에서 최소한의 부분만 작성하였습니다.

 

Lyra Animation 구조

 

ABP_Mannequin_Base

ABP_Mannequin_Base

C++ 클래스 LyraAnimInstance를 기반으로 생성된 애니메이션 블루프린트입니다.

 

LyraAnimInstance.h

UCLASS(Config = Game)
class ULyraAnimInstance : public UAnimInstance
{
	GENERATED_BODY()

public:

	ULyraAnimInstance(const FObjectInitializer& ObjectInitializer);

	virtual void InitializeWithAbilitySystem(UAbilitySystemComponent* ASC);

protected:

#if WITH_EDITOR
	virtual EDataValidationResult IsDataValid(class FDataValidationContext& Context) const override;
#endif // WITH_EDITOR

	virtual void NativeInitializeAnimation() override;
	virtual void NativeUpdateAnimation(float DeltaSeconds) override;

protected:

	// Gameplay tags that can be mapped to blueprint variables. The variables will automatically update as the tags are added or removed.
	// These should be used instead of manually querying for the gameplay tags.
	UPROPERTY(EditDefaultsOnly, Category = "GameplayTags")
	FGameplayTagBlueprintPropertyMap GameplayTagPropertyMap;

	UPROPERTY(BlueprintReadOnly, Category = "Character State Data")
	float GroundDistance = -1.0f;
};
  • FDataValidationContext
    • 데이터 검증을 위한 구조체
    • #if WITH_EDITOR를 사용하여 에디터 레벨에서만 컴파일되게 제한, 유효성검사를 하여 런타임 내 최적화
    • 데이터 검증에 따른 결과를 EDataValidationResult를 사용하여 반환한다.
      (Data Validation Plugin / UObjectGlobal.h)
  • FGameplayTagBlueprintPropertyMap

 

 

 

ABP_Mannequin_Base - AnimGraph

해당 애니메이션 그래프는 애니메이션을 직접 참조하는것이 아닌, 몽타주 및 LinkedAnimation Layer의 진입점을 제공하여 UpperBody / Lower Body 포즈를 혼합하여 재생할 수 있게 도와줍니다.

예를들어, Weapon들은 필요한 Montage 및 애니메이션 레이어에 대한 참조를 보유하고 있으며, 해당 Weapon이 로드될
경우 그에 맞는 애니메이션이 같이 로드되도록 합니다.

 

 

Blueprint ThreadSafe Update Animation

Anim Graph 로직 - Multi Thread 전 / 후

Multi Thread 전

ABP에 추가된 Blend, IK, Physics Simulation 등과 같은 런타임환경에서 값 계산하여 재생되는 애니메이션들은 추가될수록 성능에 대한 부정적인 영향을 줄 수 있습니다. 

이벤트 그래프 로직은 NativeUpdateAnimation 함수에서 매 틱마다 계산되며 일반적으로 Tick마다 호출되는 작업 중 이벤트 그래프 로직이 가장 큰 비중을 차지하게 됩니다.

매 틱마다 호출되는 이벤트 그래프 계산에 대한 성능 최적화를 위해 Lyra에서는 멀티스레드를 사용한 퍼포먼스 최적화 작업을 수행합니다.

 

Multi Thread 후

최적의 퍼포먼스로 실행하기위해 이벤트 그래프 대신 멀티스레드 애니메이션을 사용하여 애니메이션 값을 계산합니다.

애니메이션 최적화를 위한 기법 중 하나이며, Thread Safe 함수를사용하여 동시에 작업을 수행하여 각 Tick마다 필요한 시간을 줄이며 애니메이션 시스템 성능을 향상시킬 수 있습니다.

Project Settings - General Settings - Anim Blueprints - Allow MultiThreaded Animation Update

상단 이미지의 설정을 활성화하여 사용 할 수 있습니다.

 

ABP_Mannequin_Base - BlueprintThreadSafeUpdateAnimation

스레드 세이프는 이벤트 그래프에서처럼 게임 객체의 데이터에 직접 액세스 할 수 없습니다.

멀티스레드 환경에서 다른 스레드가 동시에 실행되어 해당 데이터를 변경할 수 있기 때문입니다.

Blueprint Update Animation에서는 Property Access를 사용하여 데이터 안정성이 보장될 경우(Thrad Safe할 경우) 자동으로 복사합니다.

Lyra에서는 MovementComponent, Controller, Owner 등의 값들을 AnimInstance 내부 변수로 캐싱하는데 사용하고있습니다.

 

※Property Access : 게임 스레드에서 실행되는 컴포넌트 또는 변수를 엑세스 하는 방법.
애니메이션 시스템의 퍼포먼스를 높이는 방법 중 하나이며, 스레드 세이프와 호환되도록 변환하는 방식으로 작동한다.

 

Animation Graph

ABP_Mannequin_Base - AnimGraph - Locomotion State Machine

ABP_Base내에서 스테이트 머신을 사용한 상태를 정의하며 상태의 동작은 ItemAnimLayerBase 계층에서 처리됩니다. 

 

Locomotion SM - Idle

 

Base 애니메이션 블루프린트 내에서 노드가 업데이트가 될 경우 UpdateIdleState관련 함수가 호출되도록 매핑이 되어있습니다. 해당 함수는 Idle 스테이트 로코모션 노드의 Output Pose를 계산하는데 사용되는 로직을 볼 수 있습니다.

출력되는 애니메이션은 Anim_ItemAnimLayers 기반의 애님 블루프린트 및 애니메이션 블루프린트 링크
(Animation Blueprint Linking)에서 결정하며 애니메이션 블루프린트 일부를 재사용 할 수 있습니다.

Locomotion을 재사용하기위해서 해당 애니메이션 블루프린트에 접근할 수 있도록 하는 방식은 Sub Anim Instance을 사용합니다.

 

 

애니메이션 블루프린트 링크 (Animation Blueprint Linking)

서브 애님 인스턴스 방식을 확장한 구조입니다.

애니메이션 그래프의 서브섹션을 동적으로 전환 할 수 있도록 지원합니다.

링크된 레이어 애니메이션 블루프린트를 사용하여 애니메이션 블루프린트의 여러곳을 오버라이드하여 사용 할 수 있습니다.

Lyra에서는 플레이어가 들고 있는 무기에 따라 Locomotion SM의 애니메이션 에셋, 포즈가 동적으로 전환될 수 있습니다.

 

애니메이션 레이어 인터페이스 (Animation Layer Interface)

ABP에서 애니메이션을 오버라이드할 수 있는 곳을 지정하는 역할을 합니다.

애니메이션 간 연결을 상속구조없이 재사용할 수 있게 설계된 모듈형 애니메이션 구조입니다.

Lyra에서는 모든 WeaponType이 사용하는 Aim Offset의 Yaw,Pitch와 같은 공통적으로 필요한 함수 및 프로퍼티들을 설정하고있습니다.

 

※ Sub Anim Instance - https://dev.epicgames.com/documentation/ko-kr/unreal-engine/using-sub-anim-instances-in-unreal-engine?application_version=5.0

※ Animation Blueprint Linking - https://dev.epicgames.com/documentation/ko-kr/unreal-engine/using-animation-blueprint-linking-in-unreal-engine

 

ABP_ItemAnimLayerBase

ABP_ItemAnimLayersBase - FullBody_StartState (Animation Layer)

PropertyAccess를 사용하여 값이 업데이트 될 경우 또는, 바인딩된 경우 설정한 함수를 실행합니다.

해당 함수를 실행하여 현재 애니메이션이 어떤것을 출력해야할지 동적으로 계산합니다.

Lyra에서 Anim Node Function을 사용하여 관련성이 있을 경우(바인딩 된 경우) 재생할 애니메이션을 선택하며,

또다른 시퀀스에서는 애니메이션의 재생속도를 관리하고 있습니다.

 

ABP_LocomotionLayers(Pistol, Rifle,  Shoutgun, Unarmed...)

ABP_RifleAnimLayers - Detail

ABP_ItemAnimLayersBase를 상속받은 Locomotion 폴더 내 ABP의 설정된 애니메이션을 실행합니다.

 

 

구조 정리

ABP_Mannequin_Base

  • 캐릭터에 연결된 메인 ABP
  • 애니메이션 그래프 내 Locomotion State Machine, Linked Animation Layer를 사용한 애니메이션 Entry Point 제공
  • Blend Pose를 위한 FullBody -> Upper/Lower Body Split
  • 런타임 환경에서 최종 출력될 애니메이션에 대한 값 계산
    • 애니메이션의 전체적인 흐름 제어 및 레이어 라우팅 담당

Linked Anim Layer (ALI_ItemAnimLayers)

  • ABP_Base의 Linked Animation 함수 실행
  • ALI_ItemAnimLayers에 정의된 함수들이며, 상속구조없이 바인딩가능, 유연성 및 재사용성 증가

 

ABP_ItemAnimLayerBase

  • ALI_ItemAnimLayers 내 함수 구현
  • 어떤 애니메이션을 쓸 지 결정하는 역할
    • 실제 출력되는 애니메이션은 하위 클래스 ABP_Rifle/Pistol ... 등의 설정한 애니메이션 시퀀스 출력
  • WeaponType, IsJump, bCrouch 등 바인딩된 AnimInstance의 Character Movement Component을 사용한
    최종 애니메이션 결정

ABP_Locomotion

  • 무기 타입에 따라 분기된 실제 애니메이션 그래프
  • 무기타입 변경시 해당 클래스가 애니메이션 스타일을 시각적으로 변경한다.

Lyra 성능 최적화 방법

  • 이벤트 그래프 -> 멀티스레드 애니메이션 (Blueprint ThreadSafe UpdateAnimation)을 사용한 멀티스레드
  • 동기화를 데이터 Process Access를 사용한 ThradSafe 방식 사용
  • 재사용 및 유연성 고려한 Animation Layer Interface를 사용한 함수 정의

 

 

 


출처 및 참조링크

 

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/lyra-sample-game-in-unreal-engine

https://www.youtube.com/watch?v=ys_kSOKpTtg&list=PLNBX4kIrA68lSY6Pj3zDVH6kGDIMgwOvr&index=3

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/animation-blueprint-nodes-in-unreal-engine

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/animation-optimization-in-unreal-engine

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/data-validation-in-unreal-engine

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/property-access-in-unreal-engine

https://daekyoulibrary.tistory.com/entry/UE5-Render-thread%EC%99%80-Animation-thread%EC%97%90-%EB%8C%80%ED%95%9C-%EC%9D%B4%ED%95%B4

위젯 생성 경고

실행 종료 후 출력된 경고

 

  • Server는 사운드,비주얼과 같은 렌더링 작업을 하지 않는다. (UI, VFX, Camera ...)
  • 독립형이 아닌 Listen,Dedicated 서버의 경우 서버 컨트롤러 + 복제된 로컬 컨트롤러 생성
  • IsLocalController()를 사용하여 로컬 내 복제된 컨트롤러인지 확인 후 위젯 생성 리팩토링

 

동일한 채팅이 두번 출력되는 경우

  • 각 클라이언트는 서버와 연결된 컨트롤러 / 복제된 로컬 컨트롤러 존재
  • HasAuthority() 또는 IsLocalController()를 사용한 특정 클라이언트만 해당 함수 호출할 수 있도록 변경
    • HasAuthority() : 서버에서만 동작해야 하는 로직
    • IsLocalController() : 본인 로컬에서만 실행하거나, 입력,UI등을 처리해야 하는 경우
    • 리슨서버의 경우 호스트 구분을 위해서는 두개 다 확인해야한다.

 

HasAuthority를 사용하여 권한이 있는 경우에 함수를 실행하는 이유

  1. Listen서버에서 중복 실행되는것을 방지하기 위한 보안성
  2. 서버에 존재하는 컨트롤러는 모든 클라이언트의 정보를 보유하며, 직접 값을 변경 할 수 있음.
    클라이언트에 존재하는 컨트롤러(복제된)것을 통해 서버 컨트롤러의 내 존재하는 데이터를 수정하기 위해

 

 

난수 생성시 RPC Server / Client의 난수 값이 일치하지않아 생긴 문제

Replicated 적용 전

void AChatPlayerController::Client_ReceiveMessage_Implementation(const FString& Message, const FString& TargetRandValue)
{
	GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Cyan, Sender + TEXT("(Input Value) : ") + Message);	// 유저가 입력한 Value
	GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Cyan, Sender + TEXT("(Local): ") + RandValue);		// 서버에서 복제한 컨트롤러(Not Replicated) RandValue
	GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Cyan, Sender + TEXT("(Server) : ") + TargetRandValue); // RPC Server에서 전송한(Not Replicated) RandValue

	if (AChatPlayerState* PS = GetPlayerState<AChatPlayerState>())
	{
		GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, Sender + TEXT("(PlayerState) : ") + PS->RandValue);	// 동기화를 위한 Replicated된 PlayerState의 RandValue
	}
}
  • PlayerController BeginPlay내에서 난수를 생성하는 경우, Server, Local의 난수값이 서로 다르다.
  • RPC (Client)에서 InputValue가 Server와 비교하고 싶은 경우
    • RPC(Server)에서 Client 호출시 해당 정보를 같이 전송해야한다.
    • 현재 구조는 FString 하나의 데이터만을 전송하지만, FCharacterMoveData와 같은 구조체 전송의 경우 불필요한 대역폭 소모
  • RPC (Client)에서 InputValue Client와 비교하는 경우
    • Client (!HasAuthority())의 경우 값을 수정할 수 없음
    • 데이터 변경의 경우 서버에서만 실행된다.
    • 데이터를 변경해야 하는 경우 복제된 컨트롤러 내 값을 사용하면 일관성이 유지되지 않는다.
      • 해당 문제들은 데이터 변경시 RPC(Server) 이벤트 호출하여 실행 할 수 있다.
        하지만, 난수값과 같은 경우 Set이 아니라면 일관성을 유지하지 않는다.

 

Replicated를 사용한 리팩토링

 

Replicated를 사용한 데이터만으로 난수 비교 가능

	UPROPERTY(ReplicatedUsing = OnRep_PlayerNumbers)
	FString RandValue;

AChatPlayerState::AChatPlayerState()
{
  // PlayerState 클래스의 경우 기본적으로 RPC와같은 네트워크 지원
	bReplicates = true;
}

void AChatPlayerState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
  // Replicated한 데이터를 네트워크 복제하라는 명령 매크로
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	DOREPLIFETIME(AChatPlayerState, RandValue);
}

 

'Unreal Engine > Debugging' 카테고리의 다른 글

[UE NET] GameState vs GameStateBase  (0) 2025.04.11
[UE5] UObject 클래스 내 GetWorld Null 반환  (0) 2025.02.26
[UE5] - 유효성 검사  (0) 2025.01.27

https://github.com/drstreit/unreal_schematics/

 

Replication

 

Server-Client Model

  • Server
    • 게임의 상태에 대한 권한 (Authority) 소유
    • Client의 요청 처리, 응답
  • Client
    • 서버 외 인스턴스, 서버에게 데이터를 업데이트 받는다.
    • Client는 서로 직접 통신하지 않고, Server를 통한 데이터 전송 방식
    • Client -> Server -> Client (Broadcast, Multicast)
    • Replicated, ReplicatedUsing, WithValidation을 통한 동기화를통한 일관성, 보안 강화
  • Listen Server
    • 세션의 한 플레이어가 Server 호스트 역할
      • Server이면서 Client
    • 호스트에게 UI 및 Player Controller 존재
    • 호스트가 게임을 나가면 해당 서버(세션) 종료
  • Dedicated Server
    • Server 전용 인스턴스
    • Local 플레이어 없음
    • 렌더링을 하지않는다. GPU없이 구동이 가능하다.
      • 렌더링과 같은 서버 대역폭의 부담이 큰 경우, 해당 정보를 모니터에 직접 복제하는것이 아닌
        필요한 정보들만 복제해서 대욕폭을 최소화 한다.
    • 자율적인 (Autonomous) 플레이어 컨트롤러 보유
    • 권한있는 결정 및 게임상태 업데이트는 Server에게 의존

※ Dedicated, Listen, Cloud Server - https://mynameiskgws.tistory.com/60

 

Actor Replication

  • Actor 자체를 리플리케이션 대상으로 설정
  • Transform, State 등 자동 동기화
  • bReplicates = true와 같은 설정
  • 서버에서 값 변경 후 클라이언트에 반영
  • Actor 내 Property 값 동기화 및 OwnerShip관리

 

Reliability

  • RPC의 전송 보장 여부 결정
  • Reliable : TCP/IP와 같은 반드시 전달되어야 하는 데이터 전송시 (Player isDead?)
  • Unreliable : UDP/IP와 같은 데이터 유실시에도 영향을 크게 미치지 않는 데이터 전송 시 (Player Movement) 

 

RepNotify

  • 값 변경시 서버 -> 클라이언트로 이벤트 호출하는 방식
  • 값 변경시 자동으로 바인딩된 시그니처 함수 호출
  • UPROPERTY(ReplicatedUsing = OnRep_SigatureName)
    • OnRep_ 컨벤션을 통해 언리얼에서 해당 이벤트가 Replicated 바인딩을 담당

 

Network Mode (ENetMode)

  • 현재 게임 월드 내 해당 Actor가 현재 어떤 네트워크 환경에서 실행중인지 알 수 있는 열거형
  • World 또는 NetDriver 초기화시에 결정된다.
  • 게임 실행시 실행 방식(Entry Point)에 따라 결정되며, 런타임에서 변경 불가
  • GameInstance, GameMode, Actor등 GetNetMode()를 사용한 현재 네트워크 모드 확인 가능
  •  NM_Standalone
    • 서버와 클라이언트가 모두 로컬에 있는 경우
    • 싱글 플레이 게임 모드
    • 멀티플레이 불가
    • 기본값. PIE 실행시 기본 실행
  • NM_DedicatedServer
    • UI, 플레이어 설정 불가
    • 오적 서버 로직만 수행
    • 사운드, 비주얼 렌더링 처리 없음
  • NM_ListenServer
    • 어떤 클라이언트가 서버역할을 하는 리슨서버
    • 서버 권한 HasAuthority()을 통한 로직
    • IsLocalController()을 사용한 로컬 컨트롤러 관련 로직
  • NM_Client
    • 클라이언트 모드 (서버 X)
    • 서버로부터 정보를 받아 실행하는 역할
    • 서버 전용 로직은 실행되지 않는다. (UFUNCTION(Server)...)
    • 유저(클라이언트)에게 보여줄 비주얼 작업, 입력 등 담당

 

출처

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/networking-and-multiplayer?application_version=4.27

언리얼 엔진을 활용한 멀티 플레이를 구현하기 위해 학습 하던 중, 리슨서버 및 데디케이티드 서버에 대해 학습하였습니다.

해당 과정에서 언리얼 엔진에서 제공하는 리슨서버 및 데디케이티드 서버의 특징, 차이점들을 작성하며

그 외 P2P 서버 및 클라우드 서버 등에서도 간략하게 알아보겠습니다.

 

 

멀티플레이 (MultiPlay)

  • 여러 플레이어가 네트워크를 통해 같은 게임 월드에서 상호작용 할 수 있도록 구현하는 시스템
  • 서버 - 클라이언트 구조로 이루어져있다.
  • 언리얼 엔진 내 리플리케이션, 세션을 활용하여 동작한다.
    • 서버 (Server) : 게임 상태 관리
      • 여러 클라이언트와 상호작용
      • 클라이언트에서 해킹당하면 안되는 데이터에 대한 처리
      • 플레이어의 상태 관리
    • 클라이언트 (Client) : 서버에서 관리하는 데이터를 가져와 게임을 플레이하는 방식
    • 리플리케이션(Replication) : 서버의 정보를 클라이언트에 동기화하는 기능
    • 세션 (Session) : 멀티플레이 게임에서 클라이언트들이 연결되는 로비 개념

 

리슨서버 (Listen Server)

  • 한 플레이어가 직접 서버 역할을 하며 동시에 클라이언트를 수행하는 방식
    • Player1 = Server + Client
    • Player2,3,4... = Client
  • 멀티플레이 관련 설정이 다른 서버 연결방식보다 쉽다.
  • 별도의 서버 운영이 필요 없으며, 서버 운영 비용이 발생하지 않는다.
  • 서버 호스트가 게임을 종료하면, 모든 플레이어가 해당 세션에서 종료된다.
  • 서버 호스트 PC성능에 비례하여 네트워크 성능이 저하 될 수 있다.
  • 보안에 취약하다.
    • 리슨서버는 게임 데이터를 노출시키는 형식. 데이터를 쉽게 조작 할 수 있다.
    • 클라이언트가 서버를 운영하는 방식이기 때문에 데이터를 조작하더라도 감지가 어렵다.
    • UFUNCTION(WithValidation)을 사용한 보안 강화 가능.
    • 클라우드 서버 환경에서는 토큰과 같은 보안방식을 사용하여 보안을 강화시킬 수 있다.

데디케이티드서버 (Dedicated Server)

  • 게임을 플레이하지 않는 독립적인 서버 존재, 클라이언트가 해당 서버에 접속하는 방식
  • 서버는 게임 상태 및 데이터를 관리, 클라이언트는 서버의 데이터를 받아온다.
  • 고정된 네트워크 환경에서 실행되므로 안정성이 뛰어나다.
  • 리슨서버와 다르게 호스트가 종료를 해도 게임이 중단되지 않는다.
  • 독립적인 서버를 통한 게임 데이터를 캡슐화 할 수 있다.
  • 별도 서버 운영 비용이 발생하며, 서버 유지 보수가 필요하다.
  • 고정된 서버 개수로 인해 접속자가 급등하거나 급락할 경우 서버 운영에 문제가 발생한다.

클라우드 서버 (Cloud Server)

AWS GameLift - https://aws.amazon.com/ko/blogs/korea/introducing-amazon-gamelift-anywhere-run-your-game-servers-on-your-own-infrastructure/

  • 네트워크를 통해 접근할 수 있는 가상 서버
  • 현재 게임 회사들이 가장 많이 사용하는 형태
  • 필요할 때만 서버를 생성하는 OnDemand Server 방식을 통해 서버 운영 비용을 절감할 수 있다.
  • AWS, SnowFlake, Azure 등의 클라우드 서비스를 통해 운영
  • 클라이언트 접속자가 급등하더라도 Auto Scaling기능을 통한 안전한 서버 운영 가능
  • 지역별 서버를 배포하므로 글로벌 서버의 경우에도 용이하다.
  • 클라우드 서비스 내 보안 시스템을 사용한 보안 기능을 강화시킬 수 있다.
    • AWS 모니터링, AWS AccessKey 등
  • 서버에 문제가 발생시 별도의 독립된 서버를 통해 모니터링, 프로파일링을 통해 빠른 복구가 가능하다.

 

게임 서버 품질을 보장하는 4가지

1. 안정성 (Stability)

  • 게임 서버가 얼마나 다운되지 않는가. (인듀어 테스트를 통한 검증)
  • 게임 서버가 얼마나 오작동을 하지 않는가.

2. 확장성 (Scalability)

  • 서버를 얼마나 많이 설치할 수 있는지
  • 사용자 수가 늘어나더라도 서비스 품질이 떨어지지 않고 유지되는지
    • 수직적 확장 방법 (Scale Up) : CPU,RAM 같은 서버 머신 부품 업그레이드
    • 수평적 확장 방법 (Scale Out) : 서버 머신의 개수 증설
    • 해당 방법은 데디케이티드 서버일 경우에 대한 방법. 클라우드 서비스에서는 Auto Scaling을 통한 자동 증설기능

3. 성능 (Performance)

  • 얼마나 빠르게 처리되는지
  • 게임 장르에 따른레이턴시(Latency) 고려
  • 코드 및 알고리즘 최적화
  • 서버 과부화 영역 분산
    • Code Profiling을 통한 함수처리 시간 확인, 개선
  • 네트워크 프로토콜 최적화
    • 메시지 양 줄이기 (ZLib, 양자화 등)

4. 관리 편의성

  • 백그라운드 프로세스에서도 프로그램이 실행되는지
    • 윈도우 : 서비스 / 리눅스 : 데몬

 

안정성을 고려한 서버 개발 방법

  • 치밀한 개발 및 단위테스트
    • 모든 함수 호출의 반환값을 반드시 체크하는 루틴
    • 자동화가 구현되어있는 자가 검증 (단위테스트, Unit Test)
  • 80 : 20법칙 유지
    • 성능의 80%는 20%의 소스 코드에서 나타난다는 파레토 법칙
    • 구조가 복잡해지더라도 성능 최적화 및 유지보수하기 쉬운 단순한 구조 방식 개발
  • 1인 이상의 코드리뷰
    • 코드리뷰를 통한 지식 공유, 버그 발견
  • 가정하지 말고 검증하기
    • 봇 테스트, 더미 클라이언트 테스트, 스트레스 테스트를 통한 검증
      • 입력 처리, 렌더링 과정이 생략된 프로그래밍된 행동을 반복하는 더미를 서버에 접속
      • 서버 성능 프로파일링을 통해 지표 확인
  • 불안정시 극복 과정
    • 서버가 다운되더라도 최대한 빠르게 복구
      • 서버 프로세스 중단시, 자동으로 복구되는 프로그램 설계
      • AWS 내 모니터링 시스템을 통한 자동 복구 프로그램 설계 가능
    • 서버가 다운되더라도 최대한 적은 서비스만 다운되게 설계
      • 서버 프로세스 분리를 통한 서버 장애 발생시에도 일부 서비스는 작동할 수 있는 설계
    • 서버 오작동에 대한 로깅하기

출처 및 참고내역

 

게임 서버 프로그래밍 교과서

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/networking-and-multiplayer-in-unreal-engine

https://unrealcommunity.wiki/linux-quickstart-faqs-8f7980

https://aws.amazon.com/ko/blogs/korea/introducing-amazon-gamelift-anywhere-run-your-game-servers-on-your-own-infrastructure/

'Unreal Engine > 네트워크' 카테고리의 다른 글

[UE5] - 네트워크 프레임워크  (0) 2025.03.24
[UE5] - 리플리케이션 (Replication)  (0) 2025.02.04

팀 프로젝트 미니맵 UI 제작 관련 중 발생한 문제입니다.

※ 미니맵 관련 포스팅 - https://mynameiskgws.tistory.com/50

미니맵을 초기에 설계했을 때, 각 액터 컴포넌트에 타임 핸들러를 사용하여 일정 시간마다 위치값을 전송하려고 했습니다.

이 때 각 액터 컴포넌트들이 타임핸들러를 가지고 있을 경우, 미니맵에 100개의 몬스터가 렌더링 된다 하면 타이머도 100개를 소지하게 되며 메모리 낭비 및 오버헤드 발생 우려가 있었습니다.

이를 해결하기 위해 액터의 시간을 전송하는 싱글톤패턴 기반의 Minimap Manager를 설계하게 되었습니다.

 

UObject 기반의 Manager 클래스

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTargetUpdated, const TArray<FMinimapTargetLocation>&, UpdateLocations);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTargetRemoved, AActor*, RemovedTarget);

UCLASS()
class GUNFIREPARAGON_API UMinimapManager : public UObject
{
	GENERATED_BODY()
	
public:
	void Initialize();
	void RegisterTarget(AActor* Target);
	void UnRegisterTarget(AActor* Target);
	void RecvLocationToBroadCast();

public:
	UPROPERTY(BlueprintAssignable, Category = "Minimap")
	FOnTargetUpdated OnAllLocationsUpdated;

	UPROPERTY(BlueprintAssignable, Category = "Minimap")
	FOnTargetRemoved OnTargetRemoved;

private:
	TMap<AActor*,FVector> TargetLocations;
	FTimerHandle UpdateTimer;

	void UpdateTargetsLocation();
};

void UMinimapManager::Initialize()
{
	TargetLocations.Empty();
}

void UMinimapManager::RegisterTarget(AActor* Target)
{
	if(!GetWorld())
	{
		UE_LOG(LogTemp, Display, TEXT("World Is Null"));
		return;
	}
	if (IsValid(Target) && !TargetLocations.Contains(Target))
	{
		TargetLocations.Add(Target, Target->GetActorLocation());

		if (!UpdateTimer.IsValid())
		{
			UpdateTargetsLocation();
		}
		UE_LOG(LogTemp, Display, TEXT("Register Test : %s"), *Target->GetName());
	}
}

void UMinimapManager::UnRegisterTarget(AActor* Target)
{
	if (TargetLocations.Contains(Target))
	{
		TargetLocations.Remove(Target);
		OnTargetRemoved.Broadcast(Target);
	}

	if (TargetLocations.Num() == 0)
	{
		if (GetWorld())
		{
			GetWorld()->GetTimerManager().ClearTimer(UpdateTimer);
		}
	}
}

void UMinimapManager::RecvLocationToBroadCast()
{
	TArray<FMinimapTargetLocation> TargetData;

	for (const auto& Target : TargetLocations)
	{
		if (Target.Key)
		{
			FMinimapTargetLocation Data;
			Data.TargetActor = Target.Key;
			Data.Location = Target.Key->GetActorLocation();
			TargetData.Add(Data);
		}
		else
		{
			UnRegisterTarget(Target.Key);
		}
	}

	OnAllLocationsUpdated.Broadcast(TargetData);
}

void UMinimapManager::UpdateTargetsLocation()
{
	float UpdateCycleTime = 0.1f;
	if (GetWorld())
	{
		GetWorld()->GetTimerManager().SetTimer(UpdateTimer, this, &UMinimapManager::RecvLocationToBroadCast, UpdateCycleTime, true);
	}
}

 

해당 클래스를 기존에는 static Get을 통한 싱글톤 패턴의 기반으로 하였으나,

미니맵에 해당 액터클래스를 등록하는 과정에서 GetWorld()가 Null이 되는 로그가 출력되었습니다.

이를 해결하기 위해, GameInstance에서 등록을 하였지만, 마찬가지로 GetWorld()가 Null이 출력되며, 크래시되는 현상이 발생하였습니다.

해당 과정은 UObject 기반의 클래스를 정적 으로 사용시에 발생한 문제입니다. 

원인 분석

  • 게임 컨셉이 레벨과 레벨을 이동하는 과정이 발생한다.
  • 해당 과정에서 레벨이 다시 실행 될 경우, 액터 라이프사이클과 같이 새 월드를 가져온다.
  • 해당 과정에서 전역 클래스인 MinimapManager를 재 호출하게되며, 순서를 보장하지 않은 상태.
  • 이로인해 MinimapManager를 다시 불러오며 동시에 새 월드가 생성되기 전 호출되어 Null반환을 하게된다.
    • 순서가 보장이 되지않는다 -> 매 실행마다 출력되는 로그가 다름. Null일경우, 아닐경우 존재를 통해 검증.
  • --- 다른 원인 ---
  • AActor에서 파생된 모든 클래스는 배치 할 수 있는 클래스. 즉, 자신이 어느 World인지 인지 할 수 있다.
  • UObject는 GetWorld()를 호출 할 수 있으나, 배치 되지 않은 경우 의미가 없음.

 

해결 과정

  • 게임 시작부터 유지되는 GEngine()->GetWorld... 를 사용한 호출
    • 수명동안 다른 세계를 로드할 수 있으므로 특정 세계를 반환할 수 없음. 실패.
  • 게임 시작부터 끝까지 데이터를 가지고 있는 Instance에서 생성, 초기화에서 호출
    • 동일 현상, 원인 파악을 못했음. 실패.
    • 기본 자료형 또는 함수는 사용 할 수 있으나, 월드간 변화시 동일 월드가 아니라서 발생한것같다.
  • Actor기반의 클래스 내 호출하여 사용하기
    • 해당 액터의 GetWorld()를 가져오기 때문에 정상적으로 작동.
    • 하지만, BeginPlay()후 GetWorld()를 호출하여 사용하면 초기 설계시 고려한 문제가 발생한다. 실패.
  • Timer를 사용하지 않고, Delegate를 사용한 이벤트 기반으로 리팩토링
    • 특정 조건에 바인딩하여 이벤트 기반 데이터 전송. 구현 성공

 

정리

  • UObject에는 GetWorld() 함수가 존재하기는 하나, 실제로는 null을 반환 할 수 있다.
  • GEngine()->GetWorld()... 를 사용한 호출은 서로 다른 세계를 가져올 수 있어 적합하지 않다.
  • Instance 내 GetWorld()를 사용할 순 있지만, 정적 클래스에는 부적합 (추정)
  • Timer 사용을 하지 않은 Delegate 이벤트 바인딩, 전송으로 인한 우회, 구현 성공

 


참고내역

https://forums.unrealengine.com/t/gengine-getworld-return-null/377571

https://husk321.tistory.com/426

https://www.inflearn.com/community/questions/989392/getworld-%EA%B0%80-nullptr?srsltid=AfmBOookfoyJK0gL44HvTFW_aOKR6EPWRV0Fc8a7JPtwvrUCHOoRy5xI

 

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

  • [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()와 같이 엔진 리플렉션에 필요한 코드를 자동으로 생성 해주는 매크로

+ Recent posts