Object Pooling
개요Object Pooling은 게임 개발에서 자주 사용되는 메모리 관리 기법 중 하나입니다. 이는 객체를 반복적으로 생성하고 삭제하는 비용을 줄이고 성능을 향상시키기 위해 미리 일정 개수의 객체를 생성하여 재사용하는 방식입니다. Unreal Engine에서는 Actor를 생성하고 삭제하는 비용이 크기 때문에, Object Pooling을 활용하면 퍼포먼스를 최적화할 수 있습니다. 이번 글에서는 Unreal Engine C++을 사용하여 Object Pooling을 구현하는 방법을 소개하겠습니다.Object Pooling 개념Object Pooling은 다음과 같은 원리로 작동합니다:객체 미리 생성 - 특정 개수의 객체를 미리 생성하여 Pool에 저장합니다.재사용 - 필요할 때 Pool에서 객체를 가져와..
2025.03.28
클린코딩
제가 평소에 코드를 짜면서 유의하면 좋을 것 같은 코딩 유의사항을 정리해보았습니다.1. 긴 매개변수 목록 (Long Parameter List)문제점: 너무 많은 매개변수가 있을 경우 함수의 가독성이 떨어지고, 관리가 어려워진다.나쁜 예시:void InitWeapon(FString Name, float Damage, float FireRate, int32 AmmoCount, float ReloadTime, USkeletalMesh* Mesh, USoundBase* Sound){ // 와, 많다 ...}좋은 예시: 구조체를 사용해 매개변수를 그룹화한다.struct FWeaponData{ FString Name; float Damage; float FireRate; int32 Am..
2025.03.27
C++
no image
Chaos Destruction
1. 개요 이번에는 대난투 격투 게임 프로젝트를 진행하게 되었고, 저는 아이템과 장애물 구현을 맡게 되었습니다. 작업 중 상호작용 가능한 오브젝트에 멋진 파괴 연출을 더해보고 싶다는 생각이 들어, 이번 기회에 Unreal Engine의 Chaos Destruction을 살펴보게 되었습니다.  2. Chaos DestructionChaos Destruction이란 언리얼 엔진에서 제공하는 실시간 시네마틱 퀄리티의 파괴 시뮬레이션 시스템입니다. 직관적인 워크플로우로 파괴 과정을 세밀하게 조정할 수 있으며, 최적화된 성능과 강력한 커스터마이징 기능을 제공합니다.2-1. Chaos Destruction의 주요 기능 및 특징(1). Geometry Collection(지오메트리 컬렉션): 파괴 시뮬레이션에 사용하..
2025.03.26
no image
리슨 서버 컨트롤러 구조
1. 리슨 서버리슨 서버(Listen Server)는 게임을 호스팅하면서 동시에 플레이어로 참여하는 서버즉, 호스트(Host)는 서버 역할을 하면서 직접 게임도 플레이하고, 게스트(Guest)들은 이 서버에 접속하여 게임을 진행 2. 리슨 서버 컨트롤러 구조 Host: PlayerController00 (서버 컨트롤러 + 클라이언트 컨트롤러 역할)서버에 존재하는 호스트 플레이어의 컨트롤러.동시에 서버 관리 기능도 수행.게스트 플레이어가 접속하면 해당 플레이어의 PlayerController를 추가로 생성함.PlayerController01 (게스트용 컨트롤러 생성)게스트가 접속하면 서버에서 해당 플레이어를 위한 PlayerController를 생성.클라이언트와 연결(Net Connection) 및 Pla..
2025.03.20
no image
언리얼 엔진 멀티플레이 게임의 GameMode와 GameState
1. 개요언리얼 엔진에서 GameMode와 GameState는 게임의 규칙과 상태를 관리하는 핵심 클래스특히 멀티플레이어 환경에서 중요한 역할을 수행 2. GameMode게임의 전반적인 규칙과 흐름을 관리서버에서만 존재하며, 클라이언트에는 Replicate 되지 않음플레이어의 로그인/로그아웃, 스폰 위치 결정, 게임 시작 및 종료 조건 등을 처리AGameModeBase 클래스에서 파생되며, 다양한 게임 유형에 맞게 확장 가능 2-1. GameMode 동작 순서3. GameState게임의 현재 상태와 진행 상황을 추적서버와 모든 클라이언트에 존재하며, 동기화되어 모든 플레이어가 동일한 정보를 공유합니다.게임 시간, 점수, 연결된 플레이어 목록 등의 정보를 관리합니다.AGameStateBase 클래스에서 파..
2025.03.18
Replication System
3. Replication Systems (레플리케이션 시스템)UE의 세 가지 레플리케이션 시스템:Generic Replication System (기본 복제 시스템)Replication Graph (대규모 네트워크 환경 최적화)Iris Replication System (최신 개선 시스템)3-1. Generic Replication System (기본 동기화 시스템)UE의 기본 네트워크 복제 시스템으로, 대부분의 문서가 이 시스템을 기준으로 작성됨.Actor, 속성, RPC 복제를 지원.🔹 주요 기능기능설명Dormancy특정 Actor가 클라이언트에 대한 복제 목록에서 제외되도록 설정 가능.Priority네트워크 대역폭이 제한될 경우, 어떤 Actor가 우선적으로 복제될지 결정.Relevancy특정 ..
2025.03.13
언리얼 엔진 레플리케이션(Replication)
1. 레플리케이션(Replication)서버가 게임 상태(state) 데이터를 클라이언트에 보내 동기화하는 과정.진짜 게임 상태는 서버에 존재하며, 클라이언트는 이를 복제하여 그래픽과 사운드를 렌더링.올바르게 설정하면 여러 클라이언트 간의 게임플레이가 원활하게 동기화됨.1-1. Replication 대상네트워크 동기화는 주로 AActor 및 Actor에서 파생된 클래스에서 이루어짐.AActor: 레벨에 배치되거나 동적으로 생성 가능한 기본 클래스이며, UE의 네트워크 지원이 적용된 첫 번째 클래스.UObject도 동기화 가능하지만, Replicated Subobject로 Actor에 부착되어야 동작.1-2. Replication 설정 방법C++: bReplicates = true; 설정.블루프린트: Re..
2025.03.11
브루트포스 기법
1. 부르트포스브루트포스(Brute Force)란?가능한 모든 경우의 수를 전부 시도해보는 "무식한" 문제 해결 방법.최적화된 알고리즘을 떠올리기 어려울 때나 검증 과정에서 자주 활용됩니다.브루트포스의 시간 복잡도부분집합 탐색: 2^n순열 탐색: n!중첩 반복문을 통한 조합 탐색n이 작다면 충분히 시도할 만하지만, 지수적 증가로 인해 크기에 주의 필요.예: n=10  → 부분집합: 2^10=1024, 순열: 10! =3,628,800 2. 비트마스킹비트마스킹(Bitmasking)이란?이진수(0과 1)로 이루어진 비트를 활용하여 데이터를 표현하고 조작하는 기법보통 정수형 변수의 각 비트를 ON(1) 또는 OFF(0)로 설정하여 특정 상태를 관리할 때 사용.💡 왜 사용할까?배열을 사용하지 않고도 빠르게 여..
2025.03.11
C++

Object Pooling

언리얼 거토
|2025. 3. 28. 21:03

개요

Object Pooling은 게임 개발에서 자주 사용되는 메모리 관리 기법 중 하나입니다. 이는 객체를 반복적으로 생성하고 삭제하는 비용을 줄이고 성능을 향상시키기 위해 미리 일정 개수의 객체를 생성하여 재사용하는 방식입니다. Unreal Engine에서는 Actor를 생성하고 삭제하는 비용이 크기 때문에, Object Pooling을 활용하면 퍼포먼스를 최적화할 수 있습니다. 이번 글에서는 Unreal Engine C++을 사용하여 Object Pooling을 구현하는 방법을 소개하겠습니다.


Object Pooling 개념

Object Pooling은 다음과 같은 원리로 작동합니다:

  1. 객체 미리 생성 - 특정 개수의 객체를 미리 생성하여 Pool에 저장합니다.
  2. 재사용 - 필요할 때 Pool에서 객체를 가져와 활성화합니다.
  3. 반환 - 사용이 끝난 객체를 다시 Pool에 반환하여 재사용할 수 있도록 합니다.

이 방식은 특히 다음과 같은 경우 유용합니다:

  • 총알, 아이템, 이펙트 등 짧은 시간 동안 반복적으로 생성 및 제거되는 객체 관리
  • 성능 최적화가 필요한 모바일 및 콘솔 게임
  • 네트워크 동기화가 중요한 멀티플레이어 게임

1. Object Pool Manager 생성하기

Object Pool을 관리하는 UItemPoolManager 클래스를 작성합니다. 이 클래스는 아이템을 미리 생성하고 필요할 때 재사용할 수 있도록 관리합니다.

#include "Items/Manager/ItemPoolManager.h"
#include "Items/Class/SpawnableItem.h"
#include "Engine/World.h"

void UItemPoolManager::InitializePool(TSubclassOf<ASpawnableItem> ItemClass, int32 PoolSize)
{
    if (!GetWorld() || ItemPool.Num() > 0) return;  // 이미 풀 초기화됨

    SpawnableItemClass = ItemClass;

    for (int32 i = 0; i < PoolSize; ++i)
    {
        ASpawnableItem* NewItem = GetWorld()->SpawnActor<ASpawnableItem>(ItemClass);
        if (NewItem)
        {
            NewItem->SetActorHiddenInGame(true);
            NewItem->SetActorEnableCollision(false);
            ItemPool.Add(NewItem);
        }
    }
}

ASpawnableItem* UItemPoolManager::GetItemFromPool()
{
    for (ASpawnableItem* Item : ItemPool)
    {
        if (Item && Item->IsHidden())
        {
            Item->SetActorHiddenInGame(false);
            Item->SetActorEnableCollision(true);
            return Item;
        }
    }
    return nullptr;
}

void UItemPoolManager::ReturnItemToPool(ASpawnableItem* Item)
{
    if (Item)
    {
        Item->SetActorHiddenInGame(true);
        Item->SetActorEnableCollision(false);
        ItemPool.Add(Item);
    }
}

2. 아이템 클래스 구현하기

아이템 클래스 ASpawnableItem을 작성하여 오버랩 이벤트와 상호작용 로직을 구현합니다.

#include "Items/Class/SpawnableItem.h"
#include "Components/SphereComponent.h"
#include "Kismet/GameplayStatics.h"
#include "NiagaraFunctionLibrary.h"

ASpawnableItem::ASpawnableItem()
{
    PrimaryActorTick.bCanEverTick = false;

    Scene = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));
    SetRootComponent(Scene);

    Collision = CreateDefaultSubobject<USphereComponent>(TEXT("CollisionSphere"));
    Collision->SetCollisionProfileName(TEXT("OverlapAllDynamic"));
    Collision->SetupAttachment(Scene);
    
    StaticMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ItemMesh"));
    StaticMesh->SetupAttachment(Collision);
    StaticMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);

    Collision->OnComponentBeginOverlap.AddDynamic(this, &ASpawnableItem::OnItemOverlap);
}

void ASpawnableItem::OnItemOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor,
                                   UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
                                   bool bFromSweep, const FHitResult& SweepResult)
{
    if (OtherActor && OtherActor->ActorHasTag("Player"))
    {
        Interact(OtherActor);
    }
}

void ASpawnableItem::Interact(AActor* Activator)
{
    if (HasAuthority() && Activator)
    {
        Multicast_OnInteract();
        ResetItem();
    }
}

void ASpawnableItem::ResetItem()
{
    SetActorHiddenInGame(true);
    SetActorEnableCollision(false);

    UItemPoolManager* PoolManager = GetGameInstance()->GetSubsystem<UItemPoolManager>();
    if (PoolManager)
    {
        PoolManager->ReturnItemToPool(this);
        OnItemResetDelegate.Broadcast();
    }
}

3. 아이템 스포너 구현하기

아이템을 생성하고 일정 시간 후 다시 풀로 반환하는 역할을 수행하는 AItemSpawner 클래스를 작성합니다.

#include "Items/Class/ItemSpawner.h"
#include "Items/Manager/ItemPoolManager.h"

AItemSpawner::AItemSpawner()
{
    PrimaryActorTick.bCanEverTick = false;

    Scene = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));
    SetRootComponent(Scene);
}

void AItemSpawner::BeginPlay()
{
    Super::BeginPlay();

    UItemPoolManager* PoolManager = GetGameInstance()->GetSubsystem<UItemPoolManager>();
    if (PoolManager && ItemClass)
    {
        PoolManager->InitializePool(ItemClass, 4);
        SpawnItem();
    }
}

void AItemSpawner::SpawnItem()
{
    UItemPoolManager* PoolManager = GetGameInstance()->GetSubsystem<UItemPoolManager>();
    if (PoolManager)
    {
        ASpawnableItem* Item = PoolManager->GetItemFromPool();
        if (Item)
        {
            FVector SpawnLocation = GetActorLocation();
            SpawnLocation.Z += 100.0f;

            Item->SetActorLocation(SpawnLocation);
            Item->SetActorHiddenInGame(false);
            Item->SetActorEnableCollision(true);

            Item->OnItemResetDelegate.AddDynamic(this, &AItemSpawner::OnItemReset);
            bIsItemActive = true;
        }
    }
}

void AItemSpawner::OnItemReset()
{
    GetWorldTimerManager().SetTimer(SpawnTimerHandle, this, &AItemSpawner::SpawnItem, SpawnCooldown, false);
    bIsItemActive = false;
}

 

'TIL > C++와 UE' 카테고리의 다른 글

언리얼엔진 멀티플레이에서의 PlayerState  (0) 2025.04.08
GameInstanceSubsystem  (0) 2025.04.02
언리얼 엔진 레플리케이션(Replication)  (0) 2025.03.11
네트워킹 및 멀티 플레이어 개요  (0) 2025.03.10
작성중  (0) 2025.02.26

클린코딩

언리얼 거토
|2025. 3. 27. 21:43

제가 평소에 코드를 짜면서 유의하면 좋을 것 같은 코딩 유의사항을 정리해보았습니다.

1. 긴 매개변수 목록 (Long Parameter List)

문제점: 너무 많은 매개변수가 있을 경우 함수의 가독성이 떨어지고, 관리가 어려워진다.

나쁜 예시:

void InitWeapon(FString Name, float Damage, float FireRate, int32 AmmoCount, float ReloadTime, USkeletalMesh* Mesh, USoundBase* Sound)
{
    // 와, 많다 ...
}

좋은 예시: 구조체를 사용해 매개변수를 그룹화한다.

struct FWeaponData
{
    FString Name;
    float Damage;
    float FireRate;
    int32 AmmoCount;
};

struct FWeaponAssets
{
    USkeletalMesh* Mesh;
    USoundBase* Sound;
};

void InitWeapon(const FWeaponData& InData, const FWeaponAssets& InAssets)
{
    // 훨씬 깔끔!
}

2. 전역 데이터 (Global Data)

문제점: 전역 데이터를 사용하면 예상치 못한 값 변경이 발생할 수 있으며, 디버깅이 어려워진다.

나쁜 예시:

UGameManager* GGameManager; // 전역!

void IncreaseScore()
{
    GGameManager->Score += 10;
}

좋은 예시: Subsystem을 활용해 데이터의 접근을 제한한다.

UCLASS()
class UScoreSystem : public UGameInstanceSubsystem
{
    GENERATED_BODY()

private:
    int32 Score;

public:
    void AddScore(int32 Amount)
    {
        Score += Amount;
    }

    int32 GetScore() const { return Score; }
};

3. 샷건 수술 (Shotgun Surgery)

문제점: 관련된 기능이 여러 클래스에 분산되어 있어 수정 시 여러 곳을 변경해야 한다.

나쁜 예시:

class APlayerCharacter : public ACharacter
{
public:
    void TakeDamage(float Amount);
};

class AWeapon : public AActor
{
public:
    float CalculateDamage();
};

class AMyGameMode : public AGameModeBase
{
public:
    void UpdateDamageLeaderboard();
};

좋은 예시: 데미지 로직을 하나의 시스템으로 모은다.

class UDamageSystem : public UObject
{
public:
    float CalculateDamage(AWeapon* Weapon, ACharacter* Target);
    void ApplyDamage(AWeapon* Weapon, ACharacter* Target);
    void UpdateDamageLeaderboard(ACharacter* Damager, ACharacter* Target, float Amount);
};

4. 기능 편애 (Feature Envy)

문제점: 특정 클래스가 다른 클래스의 데이터를 과도하게 사용하면 유지보수가 어려워진다.

나쁜 예시:

class UDamageCalculator : public UObject
{
public:
    float CalculateDamageReduction(AMyCharacter* Character, float Damage);
};

좋은 예시: 필요한 연산을 해당 클래스 내부에서 처리하도록 한다.

class AMyCharacter : public ACharacter
{
public:
    float CalculateDamageReduction(float Damage) const;
};

5. 데이터 뭉치 (Data Clumps)

문제점: 비슷한 데이터가 여러 곳에서 반복되면 유지보수가 어려워진다.

나쁜 예시:

void FireWeapon(float Damage, float Range, float Accuracy);
void ShowWeaponStats(float Damage, float Range, float Accuracy);
void UpgradeWeapon(float& Damage, float& Range, float& Accuracy);

좋은 예시: 공통 데이터를 구조체로 묶는다.

USTRUCT(BlueprintType)
struct FWeaponStats
{
    GENERATED_BODY()

    float Damage;
    float Range;
    float Accuracy;
};

6. 기본형 집착 (Primitive Obsession)

문제점: 단순한 데이터 타입을 과도하게 사용하면 데이터 무결성이 깨질 위험이 있다.

나쁜 예시:

float Health;
float MaxHealth;

좋은 예시: 전용 클래스를 만들어 로직을 포함시킨다.

class FHealth
{
public:
    void ApplyDamage(float Amount);
private:
    float Current;
    float Max;
};

7. 게으른 요소 (Lazy Element)

문제점: 불필요한 중간 함수는 코드 복잡성을 증가시킨다.

나쁜 예시:

void FireProjectile(AProjectile* Projectile, FVector Direction, float Speed);

좋은 예시: 직접 핵심 로직을 실행한다.

class AProjectile : public AActor
{
public:
    void Launch(const FVector& Dir, float Speed);
};

8. 추측성 일반화 (Speculative Generality)

문제점: 필요하지 않은 확장성을 고려하면 코드가 불필요하게 복잡해진다.

나쁜 예시:

class AWeaponBase : public AActor
{
    virtual void SpecialAttack() = 0;
};

좋은 예시: 필요한 기능만 구현하고, 필요 시 확장한다.

class AWeapon : public AActor
{
public:
    void Attack();
};

9. 메시지 체인 (Message Chains)

문제점: 객체 참조가 깊어질수록 유지보수가 어렵다.

나쁜 예시:

Player->GetInventory()->GetEquippedWeapon()->GetSound();

좋은 예시: 중간 객체가 직접 필요한 데이터를 제공하도록 한다.

class APlayer
{
public:
    USoundBase* GetEquippedWeaponSound();
};

이와 같은 클린 코딩 원칙을 적용하면 유지보수성과 가독성이 높은 코드를 작성할 수 있다!

'C++' 카테고리의 다른 글

브루트포스 기법  (0) 2025.03.11
STL 기본 구조  (0) 2025.02.18

Chaos Destruction

언리얼 거토
|2025. 3. 26. 21:58

1. 개요

이번에는 대난투 격투 게임 프로젝트를 진행하게 되었고, 저는 아이템과 장애물 구현을 맡게 되었습니다. 작업 중 상호작용 가능한 오브젝트에 멋진 파괴 연출을 더해보고 싶다는 생각이 들어, 이번 기회에 Unreal Engine의 Chaos Destruction을 살펴보게 되었습니다.

 

2. Chaos Destruction

Chaos Destruction이란 언리얼 엔진에서 제공하는 실시간 시네마틱 퀄리티의 파괴 시뮬레이션 시스템입니다. 직관적인 워크플로우로 파괴 과정을 세밀하게 조정할 수 있으며, 최적화된 성능과 강력한 커스터마이징 기능을 제공합니다.

2-1. Chaos Destruction의 주요 기능 및 특징

(1). Geometry Collection(지오메트리 컬렉션):
파괴 시뮬레이션에 사용하는 전용 에셋으로, 스태틱 메시를 기반으로 Fracture Mode에서 생성 가능.


(2). Fracture System(프랙처 시스템):
파괴가 일어나는 형태와 패턴을 직접 디자인하고 조절할 수 있는 기능


(3). Clustering(클러스터링):
여러 조각을 하나의 강체로 묶은 후, 충돌이나 외부 힘에 의해 자연스럽게 분리되도록 시뮬레이션


(4). Rigid Constraint 및 Strain 시스템:
각 조각 간 연결 강도를 물리적으로 계산하고, 한계치를 넘어설 경우 연결이 끊어지도록 동작


(5). Physics Field 연동:
필드를 활용해 연결 부위를 약화시키거나, 특정 위치의 파괴를 유도할 수 있음


(6). 대규모 파괴 시뮬레이션 최적화:
Cache System으로 고성능 시뮬레이션 결과를 리플레이 가능


(7). Niagara, Audio Mixer 연동:
파티클 효과 및 사운드를 시뮬레이션에 자연스럽게 통합 가능

 

3. Geometry Collection(지오메트리 컬렉션)

Chaos 시스템의 파괴 연출은 Geometry Collection 에셋에서 시작합니다.

 

3-1. Geometry Collection 생성

(1). 파괴 연출을 주고 싶은 StaticMesh(StaticMesh를 포함한 Actor도 무관)을 선택하고 Fracture 모드에 들어갑니다.

 

(2). Fracture 모드에서 New를 눌러 Geometry Collection을 생성해 원하는 경로에 저장합니다. (파일명: GC_"머시기")

 

3-2. Fracture(부수기)

(1). Fracture 섹션에서 원하는 분열 방식을 선택하고 Fracture 버튼을 누르면 Geometry Collection에 적용됩니다.

 

3-3. 시뮬레이션

(1). Fracture 효과를 간단히 확인하고 싶다면, Fracture 패널의 Explode Amount 값을 조절해 미리보기할 수 있습니다.

 

 

(2). 직접 시뮬레이션을 테스트해보고 싶을 때는 Geometry Collection의 Details 패널에서 Damage Threshold 값을 모두 1로 설정한 후, Play in Editor의 Simulate 버튼을 눌러 손쉽게 Destruction 효과를 확인할 수 있습니다.

 

리슨 서버 컨트롤러 구조

언리얼 거토
|2025. 3. 20. 15:52

1. 리슨 서버

  • 리슨 서버(Listen Server)는 게임을 호스팅하면서 동시에 플레이어로 참여하는 서버
  • 즉, 호스트(Host)는 서버 역할을 하면서 직접 게임도 플레이하고, 게스트(Guest)들은 이 서버에 접속하여 게임을 진행

 

2. 리슨 서버 컨트롤러 구조

 

Host: 

  • PlayerController00 (서버 컨트롤러 + 클라이언트 컨트롤러 역할)
    • 서버에 존재하는 호스트 플레이어의 컨트롤러.
    • 동시에 서버 관리 기능도 수행.
    • 게스트 플레이어가 접속하면 해당 플레이어의 PlayerController를 추가로 생성함.
  • PlayerController01 (게스트용 컨트롤러 생성)
    • 게스트가 접속하면 서버에서 해당 플레이어를 위한 PlayerController를 생성.
    • 클라이언트와 연결(Net Connection) 및 PlayerState를 관리.
    • RPC 및 상태 동기화를 처리함.

Guest:

  • PlayerController01 (클라이언트 컨트롤러)
    • 게스트 플레이어의 입력을 처리.
    • 서버의 PlayerController01과 연결되어 RPC 및 상태 동기화를 수행.

 

 

2. UI Widget을 활용한 채팅 프로그램 예시

이를 바탕으로 각 클라이언트에 채팅 위젯이 할당된 경우, 채팅을 주고 받는 과정을 살펴보겠습니다.

 

2-1. 리슨 서버 채팅 프로그램 구조

2-2. 리슨 서버 채팅 프로그램 클래스 역할

  • 각 클라이언트의 컨트롤러에는 채팅을 주고 받는 기능이 있습니다.
//BaseBallPlayerController.h
UCLASS()
class BASEBALLGAME_API ABaseBallPlayerController : public APlayerController
{
    GENERATED_BODY()

public:
    UFUNCTION(Server, Reliable, WithValidation)
    void ServerSendChatMessage(const FString& Message);
    void ServerSendChatMessage_Implementation(const FString& Message);
    bool ServerSendChatMessage_Validate(const FString& Message) { return true; }

	void OnChatMessageReceived(const FString& Message);
    virtual void BeginPlay() override;

private:
    UChatWidget* PlayerChatWidget;
    void InitializeChatWidget();
};

 

  • GameState에서는 PlayerController의 호출을 받아 모든 클라이언트에게 채팅 메세지를 BroadCast(전파)합니다.
//BaseBallGameState.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/GameStateBase.h"
#include "BaseBallGameState.generated.h"

UCLASS()
class BASEBALLGAME_API ABaseBallGameState : public AGameStateBase
{
    GENERATED_BODY()

public:
    ABaseBallGameState();

    UPROPERTY()
    TArray<FString> ChatMessages;

    UFUNCTION(NetMulticast, Reliable)
    void MulticastOnChatMessageReceived(const FString& Message);
    void MulticastOnChatMessageReceived_Implementation(const FString& Message);
    
    void ServerAddChatMessage(const FString& Message);
};

 

2-3. 리슨 서버 채팅 프로그램 동작

(1). 클라이언트에서 채팅을 입력하면 Client - Server RPC를 활용하여 GameState에 전달합니다. 

//BaseBallPlayerController.cpp
void ABaseBallPlayerController::ServerSendChatMessage_Implementation(const FString& Message)
{
    ABaseBallGameState* GameState = GetWorld()->GetGameState<ABaseBallGameState>();
    if (GameState)
    {
        GameState->ServerAddChatMessage(Message);
    }
}

 

(2). GameState는 서버에서 생성되며 클라이언트와 동기화되기 때문에, NetMultiCast RPC를 호출하면 Host와 Guest 모두 해당 함수를 실행하게 됩니다. 따라서 GameState에서 채팅 메시지를 받은 후 NetMultiCast를 사용해 각 클라이언트의 PlayerController를 찾아야 할 때, Host에서는 Host의 PlayerController만, Guest에서는 Guest의 PlayerController만 찾도록 처리하면 됩니다. 이를 통해 각 클라이언트에 적절한 위젯 업데이트가 이루어집니다.

 

//BaseBallGameState.cpp
void ABaseBallGameState::MulticastOnChatMessageReceived_Implementation(const FString& Message)
{
	//각 클라이언트의 PlayerController에 캐스팅
    ABaseBallPlayerController* LocalBPC = Cast<ABaseBallPlayerController>(GetWorld()->GetFirstPlayerController());

    if (LocalBPC)
    {
        LocalBPC->OnChatMessageReceived(Message); // UI 업데이트
    }

}

 

(3). 다시 각 클라이언트의 PlayerController로 돌아와서 실제로 UI Widget 업데이트를 하는 코드입니다.

//BaseBallPlayerController.cpp
void ABaseBallPlayerController::OnChatMessageReceived(const FString& Message)
{
    if (PlayerChatWidget)
    {
        PlayerChatWidget->AddChatMessage(Message); // Update UI
    }
}

 

3. HasAuthority(), IsLocalPlayerController 관련

구분 HasAuthority() IsLocalPlayerController()
의미 서버에서 실행되는 객체인가? 내 화면을 담당하는 컨트롤러인가?
서버에서 실행되면? ✅ true ❌ false
클라이언트에서 실행되면? ❌ false ✅ true
사용 용도 서버/클라이언트 여부 판별 UI 업데이트, 로컬 플레이어 판단

 

기본적으로 HasAuthority()와 IsLocalPlayerController()는 위와 같은 판단 기준으로 각 결과값을 가지게 됩니다.

그러나 리슨 서버의 Host 컨트롤러의 경우 서버 컨트롤러의 역할과 클라이언트 컨트롤러 역할을 둘다 가집니다. 그래서 서버와 클라이언트가 분리된 전용 서버(Dedicated Server)와는 다르게 아래와 같은 결과값을 가지게 됩니다.

 

리슨 서버 컨트롤러 유형 HasAuthority() 값 IsLocalPlayerController() 값
Host 컨트롤러 ✅ true ✅ true
Guest 컨트롤러 ❌ false ✅ true

✏️ 멀티플레이 개념은 PlayerController가 전부다..

1. 개요

  • 언리얼 엔진에서 GameMode와 GameState는 게임의 규칙과 상태를 관리하는 핵심 클래스
  • 특히 멀티플레이어 환경에서 중요한 역할을 수행

 

2. GameMode

  • 게임의 전반적인 규칙과 흐름을 관리
  • 서버에서만 존재하며, 클라이언트에는 Replicate 되지 않음
  • 플레이어의 로그인/로그아웃, 스폰 위치 결정, 게임 시작 및 종료 조건 등을 처리
  • AGameModeBase 클래스에서 파생되며, 다양한 게임 유형에 맞게 확장 가능

 

2-1. GameMode 동작 순서

3. GameState

  • 게임의 현재 상태와 진행 상황을 추적
  • 서버와 모든 클라이언트에 존재하며, 동기화되어 모든 플레이어가 동일한 정보를 공유합니다.
  • 게임 시간, 점수, 연결된 플레이어 목록 등의 정보를 관리합니다.
  • AGameStateBase 클래스에서 파생되며, 게임의 특정 요구 사항에 맞게 확장할 수 있습니다.

4. GameMode와 GameState의 차이점

  • GameMode는 서버에서만 존재하며, 게임의 규칙과 로직을 정의
  • GameState는 서버와 클라이언트 모두에 존재하며, 게임의 상태 정보를 공유하고 동기화

Replication System

언리얼 거토
|2025. 3. 13. 21:10

3. Replication Systems (레플리케이션 시스템)

UE의 세 가지 레플리케이션 시스템:

  1. Generic Replication System (기본 복제 시스템)
  2. Replication Graph (대규모 네트워크 환경 최적화)
  3. Iris Replication System (최신 개선 시스템)

3-1. Generic Replication System (기본 동기화 시스템)

  • UE의 기본 네트워크 복제 시스템으로, 대부분의 문서가 이 시스템을 기준으로 작성됨.
  • Actor, 속성, RPC 복제를 지원.

🔹 주요 기능

기능 설명
Dormancy 특정 Actor가 클라이언트에 대한 복제 목록에서 제외되도록 설정 가능.
Priority 네트워크 대역폭이 제한될 경우, 어떤 Actor가 우선적으로 복제될지 결정.
Relevancy 특정 클라이언트에서 Actor가 중요하지 않을 경우 복제 제외 가능.

3-2. Replication Graph (대규모 동기화 시스템)

  • 많은 수의 Actor를 복제해야 하는 경우를 위한 확장 가능한 복제 시스템.
  • 복제할 Actor를 그룹화하여 네트워크 부하를 최적화함.
  • 예: 대규모 오픈월드 게임에서 사용.

3-3. Iris Replication System (최신 개선 시스템)

  • UE의 최신 네트워크 복제 시스템으로, 기존 복제 시스템을 보완하며 성능을 향상.
  • Generic Replication System과 함께 동작하지만, 일부 기능을 개선함.
  • 새로운 기능 제공 (예: 더 세밀한 복제 필터링, 동적 우선순위 설정).

🔹 주요 기능

기능 설명
Filtering 특정 클라이언트에 대해서만 Actor 복제 가능 (더 정밀한 네트워크 제어).
Prioritization Actor의 중요도를 동적으로 변경 가능하여 최적화 가능.

 

5. 네트워크 최적화 팁

네트워크 게임의 성능을 향상시키려면 다음과 같은 팁을 따르는 것이 좋습니다.

  • RPC 최소화: 가능한 RepNotify를 사용하고, RPC 호출 횟수를 줄입니다.
  • 멀티캐스트 RPC 절제: 네트워크 부하를 줄이기 위해 최소한으로 사용합니다.
  • 서버 전용 로직 구분: 불필요한 서버 RPC 대신 클라이언트가 실행할 필요가 없는 로직을 따로 처리합니다.
  • RPC 입력 제어: 플레이어가 빠르게 버튼을 눌러 서버를 과부하시키지 않도록 제한합니다.
  • 비신뢰성 RPC(Unreliable RPC) 사용: 자주 호출되는 RPC는 Unreliable로 설정해 네트워크 트래픽을 줄입니다.
  • 네트워크 역할(Role) 확인: HasAuthority() 또는 IsLocallyControlled()를 사용해 코드 실행 위치를 필터링합니다.
  • 네트워크 휴면 상태(Dormancy) 활용: 자주 변하지 않는 액터는 Dormant 상태로 두어 네트워크 사용량을 줄입니다.

6. 네트워크 디버깅과 테스트

멀티플레이어 게임의 복잡성을 고려하면, 디버깅과 테스트가 매우 중요합니다. 언리얼 엔진은 여러 가지 네트워크 디버깅 도구를 제공합니다.

  • Net Profiler: 네트워크 트래픽을 분석하는 도구.
  • Stat Net 명령어: 네트워크 성능을 모니터링.
  • LogNet: 네트워크 관련 로그를 출력하여 문제를 진단.
  • PIE(Play In Editor)에서 분할 실행: 클라이언트와 서버를 동시에 실행하여 테스트 가능.

 

1. 레플리케이션(Replication)

  • 서버가 게임 상태(state) 데이터를 클라이언트에 보내 동기화하는 과정.
  • 진짜 게임 상태는 서버에 존재하며, 클라이언트는 이를 복제하여 그래픽과 사운드를 렌더링.
  • 올바르게 설정하면 여러 클라이언트 간의 게임플레이가 원활하게 동기화됨.

1-1. Replication 대상

  • 네트워크 동기화는 주로 AActor 및 Actor에서 파생된 클래스에서 이루어짐.
  • AActor: 레벨에 배치되거나 동적으로 생성 가능한 기본 클래스이며, UE의 네트워크 지원이 적용된 첫 번째 클래스.
  • UObject도 동기화 가능하지만, Replicated Subobject로 Actor에 부착되어야 동작.

1-2. Replication 설정 방법

  • C++: bReplicates = true; 설정.
  • 블루프린트: Replicates 옵션 활성화.
  • bReplicateMovement = true; 설정하면 이동 정보 자동 동기화.

1-3. Replication의 주요 개념/매커니즘

(1). Replicated Properties (속성 동기화)

  • 속성 값을 서버에서 클라이언트로 자동 동기화.
  • 예시: Health, Ammo 같은 변수.
UPROPERTY(Replicated)
uint32 Health;

 

(2). Replicated Using Properties (속성 동기화 + 함수 호출)

  • 속성이 변경되면 특정 함수를 실행하도록 설정 가능.
  • 예시: OnRep_Health()를 설정하면 Health가 바뀔 때 자동 호출.
UPROPERTY(ReplicatedUsing=OnRep_HealthUpdate)
uint32 Health;

UFUNCTION()
void OnRep_HealthUpdate();

 

(3). Remote Procedure Calls (RPC, 원격 프로시저 호출)

  • 한 머신(클라이언트 or 서버)에서 다른 머신에서 실행될 함수를 호출.
  • 종류:
    • Server RPC: 클라이언트 → 서버에서 실행 (Server_TakeDamage())
    • Client RPC: 서버 → 특정 클라이언트에서 실행 (Client_ShowDamageEffect())
    • Multicast RPC: 서버 → 모든 클라이언트에서 실행 (Multicast_ExplosionEffect())
UFUNCTION(Server, Reliable, WithValidation)
void ServerDoSomething();

UFUNCTION(Client, UnReliable)
void ClientDoSomething();

UFUNCTION(NetMulticast, Reliable)
void MulticastDoSomething();

참조: https://dev.epicgames.com/documentation/en-us/unreal-engine/replicate-actor-properties-in-unreal-engine#addareplicatedproperty

 

2. 액터 레플리케이션(Actor Replication)

2-1. Actor Replication 과정

(1) 클라이언트가 동기화할 Actor 결정

  • 동기화할 Actor 목록 작성
    • AActor::SetReplicates(true)가 설정된 Actor만 동기화 대상.
    • AActor::NetUpdateFrequency를 확인하여 업데이트 빈도 결정.
    • AActor::bOnlyRelevantToOwner가 true면 소유자에게만 동기화.
    • AActor::PreReplication() 호출하여 특정 속성의 동기화 여부 결정.
  • 각 클라이언트별 동기화 대상 선정
    • AActor::IsNetRelevantFor()를 호출하여 현재 클라이언트에 대해 Actor가 유효한지 확인.
    • AActor::NetDormancy를 확인하여 Actor가 비활성화(Dormant) 상태인지 체크.
    • 클라이언트가 Actor가 위치한 레벨을 로드하지 않았다면 동기화하지 않음.
  • Actor 동기화 우선순위 정렬
    • AActor::GetNetPriority()를 호출하여 우선순위가 높은 Actor부터 동기화.
  • 동기화 할 Actor 순회 및 전송
    • Actor의 속성이 변경되었는지 확인 후 UActorChannel::ReplicateActor() 호출.
    • Actor의 컴포넌트 및 속성 변화를 클라이언트로 전송.
    • 만약 5초 이상 Relevancy를 잃으면 채널을 닫음.
  • 최적화 옵션
    • -limitclientticks 커맨드라인 옵션으로 초당 클라이언트 업데이트 횟수 제한 가능.
    • NetClientTicksPerSecond 엔진 설정 변경 가능.
    • net.MaxConnectionsToTickPerServerFrame 콘솔 변수로 조정 가능.

 

(2). 서버가 속성 및 RPC 실행 순서 결정

  • 액터 속성(Actor Properties)
    • Actor 속성 동기화는 실행 순서 보장X.
    • OnRep(RepNotify)도 실행 순서 보장X.
    • 순서가 중요한 경우, 여러 변수를 구조체(struct)로 묶어 관리하는 것을 권장.
  • 원격 프로시저 호출(RPC)
    • Actor 간 실행 순서 보장되지 않음
MyActor->ClientRPC1();
OtherActor->ClientRPC2();
MyActor->ClientRPC3();

//실행 순서
RPC1 → RPC2 → RPC3
RPC2 → RPC3 → RPC1
RPC3 → RPC1 → RPC2

//해결 방법:
순서가 중요한 경우 단일 Actor 내에서만 RPC를 호출하는 것이 좋음.

 

  • 동일 Actor 내 Reliable RPC 실행 순서 보장
MyActor->ClientReliableRPC1();
MyActor->ClientReliableRPC2();
MyActor->ClientReliableRPC3();

//실행 순서
항상 RPC1 → RPC2 → RPC3 순서로 실행

 

  • Actor와 Subobject 간 실행 순서 유지됨
MyActor->RPC1();
MyActor->SubObject1->RPC2();
MyActor->SubObject2->RPC3();
MyActor->RPC4();

//실행 순서
항상 RPC1 → RPC2 → RPC3 → RPC4 순서로 실행.

 

 

  • Reliable vs Unreliable RPC 실행 순서
MyActor->ClientReliableRPC1();
MyActor->ClientUnreliableRPC2();
MyActor->ClientReliableRPC3();

//실행순서:
Reliable RPC: 실행 순서가 항상 보장됨.
Unreliable RPC: 네트워크 상태에 따라 손실될 수 있음.

네트워크 문제가 없으면: RPC1 → RPC2 → RPC3
RPC2가 유실되면: RPC1 → RPC3 (RPC2 실행되지 않음)

 

  • Multicast vs Unicast RPC
    • Reliable Multicast
MyActor->MulticastReliableRPC1();
MyActor->UnicastReliableRPC2();
MyActor->MulticastReliableRPC3();

//실행 순서:
항상 RPC1 → RPC2 → RPC3 순서로 실행

 

  • Unreliable Multicast
MyActor->MulticastUnreliableRPC1();
MyActor->UnicastReliableRPC2();
MyActor->MulticastUnreliableRPC3();
MyActor->UnicastUnreliableRPC4();

//실행 순서:
예시: RPC2 → RPC4 → RPC1 → RPC3
Unreliable Multicast는 항상 마지막에 실행될 가능성이 큼.

 

  • RPC 전송 정책 (Remote Function Send Policy)
    • Force Send(): 강제로 바로 전송
MyActor->ForceSendMulticastUnreliableRPC1();
MyActor->UnicastReliableRPC2();
MyActor->MulticastUnreliableRPC3();
MyActor->UnicastUnreliableRPC4();

//실행 순서:
RPC1 → RPC2 → RPC4 → RPC3 (RPC1이 강제로 먼저 실행됨)
void AMyActor::ForceSendToClient()
{
    if (HasAuthority())
    {
        UActorChannel* ActorChannel = GetChannelByActor(GetOwner());
        if (ActorChannel)
        {
            ActorChannel->ForceSend(); // 데이터를 즉시 보내도록 강제
        }
    }
}

 

  • Force Queue(): 강제로 대기
MyActor->ForceQueueRPC1();
MyActor->UnicastReliableRPC2();
MyActor->MulticastUnreliableRPC3();
MyActor->UnicastUnreliableRPC4();

//실행 순서: 
RPC2 → RPC4 → RPC1 → RPC3 (RPC1이 강제로 마지막에 실행됨)
void AMyActor::ForceQueueToSendLater()
{
    FQueuedMessage NewMessage;
    NewMessage.MessageType = EMessageType::MyCustomMessage;
    NewMessage.MessageData = "This is a delayed message.";

    // 큐에 넣기 (이 큐는 네트워크 전송을 나중에 할 수 있도록 설정됨)
    MessageQueue.Enqueue(NewMessage);
}

 

  • RPC vs 속성 동기화 순서
MyActor->ReliableRPC1();
MyActor->bReplicatedVar1 = true;
MyActor->MulticastUnreliableRPC2();
MyActor->bReplicatedVar2 = true;
MyActor->ReliableRPC3();

//실행 순서:
RPC1 실행
RPC3 실행
bReplicatedVar1, bReplicatedVar2 동기화
RPC2 실행 (Unreliable Multicast는 항상 마지막)

 

 

(3). 서버가 클라이언트에 데이터 전송

  • 각 클라이언트에 필요한 정보만 전송하여 최적화 수행.

 

2-2. Replication 주요 기능

기능 설명
생성 & 삭제 서버에서 생성된 레플리케이션 Actor는 클라이언트에서 자동으로 복제됨.
서버에서 삭제되면 클라이언트에서도 삭제됨.
이동 (Movement Replication) bReplicateMovement = true 설정 시 위치, 회전, 속도 자동 동기화.
속성 (Properties) UPROPERTY(Replicated) 속성은 값이 바뀌면 자동 동기화됨.
컴포넌트 (Components) 복제 설정된 컴포넌트와 그 속성, RPC도 복제 가능.
서브오브젝트 (Subobjects) UObject도 Actor에 부착하면 복제 가능.
RPC 네트워크에서 특정 함수 실행 가능 (Server, Client, Multicast).

 

2-3.  Replicate 되지 않는 요소들

  • Skeletal Mesh Component
  • Static Mesh Component
  • Materials
  • Animation Blueprints
  • Particle System Component
  • Sound Emitters
  • Physics Objects

➜ 개별적으로 실행되지만, 이들을 제어하는 변수를 복제하면 클라이언트 간 동기화 가능.

 

 

 

------------

 

 

 

 

'TIL > C++와 UE' 카테고리의 다른 글

GameInstanceSubsystem  (0) 2025.04.02
Object Pooling  (0) 2025.03.28
네트워킹 및 멀티 플레이어 개요  (0) 2025.03.10
작성중  (0) 2025.02.26
머티리얼 조작  (0) 2025.02.25

브루트포스 기법

언리얼 거토
|2025. 3. 11. 15:41

1. 부르트포스

브루트포스(Brute Force)란?

  • 가능한 모든 경우의 수를 전부 시도해보는 "무식한" 문제 해결 방법.
  • 최적화된 알고리즘을 떠올리기 어려울 때나 검증 과정에서 자주 활용됩니다.

브루트포스의 시간 복잡도

  • 부분집합 탐색: 2^n
  • 순열 탐색: n!
  • 중첩 반복문을 통한 조합 탐색

n이 작다면 충분히 시도할 만하지만, 지수적 증가로 인해 크기에 주의 필요.
예: n=10  → 부분집합: 2^10=1024, 순열: 10! =3,628,800

 

2. 비트마스킹

비트마스킹(Bitmasking)이란?

  • 이진수(0과 1)로 이루어진 비트를 활용하여 데이터를 표현하고 조작하는 기법
  • 보통 정수형 변수의 각 비트를 ON(1) 또는 OFF(0)로 설정하여 특정 상태를 관리할 때 사용.

💡 왜 사용할까?

  • 배열을 사용하지 않고도 빠르게 여러 상태를 저장할 수 있음
  • 비트 연산을 활용하면 탐색, 계산 속도가 빠름
  • 메모리를 절약할 수 있음

비트마스킹의 핵심 개념

  1. 각 비트는 특정 의미를 가질 수 있음
    예를 들어, 101이라는 이진수를 보자.
    • 첫 번째(맨 오른쪽) 비트: 1 → 켜짐 (ON)
    • 두 번째 비트: 0 → 꺼짐 (OFF)
    • 세 번째 비트: 1 → 켜짐 (ON)
  2. 비트 연산을 이용한 조작 방법
    연산 설명 예시
    AND & 특정 비트가 켜져 있는지 확인 101 & 001 = 001 (세 번째 비트가 켜져 있음)
    OR | 특정 비트를 켜기 100 | 010 = 110
    XOR ^ 특정 비트 토글(0 → 1, 1 → 0) 110 ^ 010 = 100
    SHIFT <<, >> 비트 위치 이동 001 << 2 = 100

 

3. 브루트포스 구현 방법

3-1. 중첩 반복문

  • 직관적이지만 범위가 크면 비효율적
  • 예제: 1~3까지의 숫자 3개를 중복 선택하여 나열
for (int i = 1; i <= 3; i++) {
    for (int j = 1; j <= 3; j++) {
        for (int k = 1; k <= 3; k++) {
            cout << i << " " << j << " " << k << "\n";
        }
    }
}

 

3-2. 비트마스킹을 활용한 부분집합 탐색

  • 집합 {1,2,3}의 경우 총 2^3=개의 부분집합 존재
  • 이진수 표현을 활용하여 부분집합을 탐색 가능
0 -> 000 -> 공집합
1 -> 001 -> {3}
2 -> 010 -> {2}
3 -> 011 -> {2,3}
4 -> 100 -> {1}
5 -> 101 -> {1,3}
6 -> 110 -> {1,2}
7 -> 111 -> {1,2,3}

 

#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> arr = {1, 2, 3};
    int n = arr.size();

 	// 0부터 2^n - 1까지 부분집합의 모든 경우 탐색
    for (int i = 0; i < (1 << n); i++) {
        cout << "{ ";
        for (int j = 0; j < n; j++) {
            if (i & (1 << j)) { 
            // i번째 원소가 포함되었는지 확인
            // bit는 정수지만, 2진수로 보면 비트의 집합
            // (1 << j)의 역할: 특정 비트만 체크
            // i & (1 << j) != 0 이면 True 이기 때문에 해당 자리 원소 출력
                cout << arr[j] << " "; 
            }
        }
        cout << "}\n";
    }
}

 

정수 n(1 ≤ n ≤ 100)이 주어질 때, 다음 식을 만족하는 (a, b, c)의 개수를 구하는 문제를 불필요한 중복 제거를 하여 풀어보세요. (제출 + 발표)

a + b^2 + c^3 = n
#include <iostream>
using namespace std;

int main() {
    int n = 10000;  // 입력값
    int count = 0;

    for (int b = 1; b * b <= n; b++) {  // b^2가 n을 초과하지 않는 범위까지만 탐색
        for (int c = 1; b * b + c * c * c <= n; c++) {  // b^2 + c^3이 n을 초과하지 않는 범위까지만 탐색
            int a = n - (b * b + c * c * c);
            if (a >= 1 && a <= 100) {  // a가 자연수(1~100) 범위 내에 있어야 유효한 해
                count++;
            }
        }
    }

    cout << count;
}

'C++' 카테고리의 다른 글

클린코딩  (0) 2025.03.27
STL 기본 구조  (0) 2025.02.18