1. 싱글톤 패턴 (Singleton Pattern)

정의: 싱글톤 패턴은 특정 클래스의 인스턴스가 하나만 존재하도록 보장하고, 이를 전역적으로 접근 가능하게 만드는 디자인 패턴입니다. 이 패턴은 자원의 낭비를 방지하고 전역적으로 상태를 유지해야 하는 객체를 관리할 때 유용합니다.

언리얼에서의 활용: 언리얼 엔진에서는 주로 GameInstance와 같이 전체 게임에서 공유되어야 하는 데이터를 관리할 때 싱글톤 패턴을 사용합니다. GameInstance는 게임의 라이프 사이클 동안 지속되는 객체로, 각종 설정, 네트워크 상태, 전역 변수 등을 관리하는 데 사용됩니다.

샘플 코드:

// GameInstance를 사용하여 싱글톤과 같은 기능 구현
UGameInstance* MyGameInstance = GetWorld()->GetGameInstance();
UMyGameInstanceClass* CustomInstance = Cast<UMyGameInstanceClass>(MyGameInstance);
if (CustomInstance)
{
    CustomInstance->DoSomethingGlobal();
}

이 샘플은 GetWorld()->GetGameInstance()를 통해 싱글톤 인스턴스를 가져와 사용하는 방식으로, 게임 전반에 걸쳐 동일한 인스턴스를 접근하는 것을 보장합니다.


2. 옵저버 패턴 (Observer Pattern)

정의: 옵저버 패턴은 한 객체의 상태 변화가 있을 때, 그 변화를 관찰하는 다른 객체들에게 알림을 보내는 패턴입니다. 주로 이벤트 시스템을 구현할 때 많이 사용되며, 객체 간의 느슨한 결합을 유지하면서도 상호작용을 가능하게 합니다.

언리얼에서의 활용: 언리얼에서는 **델리게이트(Delegate)**를 통해 옵저버 패턴을 구현합니다. 델리게이트는 이벤트가 발생할 때 여러 리스너에게 알림을 주는 방식으로 작동하며, 주로 캐릭터의 체력 변화나 게임 이벤트 발생 시 이를 UI 등에 알리기 위해 사용됩니다.

샘플 코드:

// 델리게이트 정의
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnHealthChanged);

// 델리게이트 선언
FOnHealthChanged OnHealthChanged;

// 델리게이트 호출 예제
void AMyCharacter::TakeDamage(float DamageAmount)
{
    Health -= DamageAmount;
    OnHealthChanged.Broadcast(); // 모든 구독자에게 알림
}

// 델리게이트 바인딩 예제
OnHealthChanged.AddDynamic(this, &AMyCharacter::UpdateHealthUI);

이 예제에서 OnHealthChanged 델리게이트를 통해 체력 변화 시 관련 UI를 자동으로 업데이트하도록 알림을 보내는 구조를 볼 수 있습니다.


3. 컴포넌트 패턴 (Component Pattern)

정의: 컴포넌트 패턴은 객체의 기능을 여러 컴포넌트 단위로 분리하고, 이를 동적으로 조합하여 객체의 기능을 확장하는 방식입니다. 이 패턴은 객체를 작고 독립적인 단위로 나누어 유지 보수성과 재사용성을 높입니다.

언리얼에서의 활용: 언리얼 엔진에서는 ActorComponentSceneComponent를 활용하여 컴포넌트 패턴을 구현합니다. 예를 들어, 캐릭터에 이동, 공격, 방어 등 다양한 기능을 추가할 때 이러한 컴포넌트를 사용합니다. 이를 통해 코드의 모듈화를 높이고, 필요에 따라 컴포넌트를 추가하거나 제거함으로써 객체의 기능을 유연하게 구성할 수 있습니다.

샘플 코드:

// 컴포넌트를 사용하여 기능을 추가하는 예제
AMyCharacter::AMyCharacter()
{
    // 캐릭터에 이동 컴포넌트 추가
    MovementComponent = CreateDefaultSubobject<UCharacterMovementComponent>(TEXT("MovementComponent"));
}

// 컴포넌트를 통해 이동 처리
void AMyCharacter::MoveForward(float Value)
{
    if (Value != 0.0f)
    {
        AddMovementInput(GetActorForwardVector(), Value);
    }
}

이 코드는 캐릭터에 이동 기능을 추가하는 MovementComponent를 사용하여 컴포넌트 패턴을 적용한 예입니다. 각 기능을 독립적인 컴포넌트로 구현함으로써 기능의 추가나 수정이 용이해집니다.


4. MVC 패턴 (Model-View-Controller Pattern)

정의: MVC 패턴은 애플리케이션을 Model, View, Controller 세 부분으로 분리하여 개발하는 디자인 패턴입니다. Model은 데이터와 비즈니스 로직을 관리하고, View는 사용자 인터페이스를, Controller는 사용자 입력을 처리하여 Model과 View를 연결하는 역할을 합니다.

언리얼에서의 활용: 언리얼에서는 UI 시스템인 UMG와 게임 로직을 분리하여 MVC 패턴을 활용합니다. 예를 들어, 캐릭터의 체력 상태를 관리하는 Model과 이를 화면에 표시하는 View, 그리고 사용자의 입력을 처리하는 Controller를 독립적으로 관리함으로써 코드의 재사용성과 유지보수성을 높입니다.

샘플 코드:

// Model: 데이터와 로직 관리
class UPlayerStats : public UObject
{
public:
    int32 Health;
    int32 Stamina;

    void ModifyHealth(int32 Amount)
    {
        Health += Amount;
    }
};

// View: UMG 위젯을 통한 UI 구현
class UPlayerStatsWidget : public UUserWidget
{
public:
    void UpdateHealthDisplay(int32 Health)
    {
        // UI 요소 업데이트
        HealthText->SetText(FText::AsNumber(Health));
    }

private:
    UPROPERTY(meta = (BindWidget))
    UTextBlock* HealthText;
};

// Controller: 입력 처리 및 모델-뷰 연결
class APlayerController : public AController
{
public:
    void SetPlayerStats(UPlayerStats* Stats, UPlayerStatsWidget* StatsWidget)
    {
        PlayerStats = Stats;
        PlayerStatsWidget = StatsWidget;
    }

    void TakeDamage(int32 DamageAmount)
    {
        PlayerStats->ModifyHealth(-DamageAmount);
        PlayerStatsWidget->UpdateHealthDisplay(PlayerStats->Health);
    }

private:
    UPlayerStats* PlayerStats;
    UPlayerStatsWidget* PlayerStatsWidget;
};

이 예제에서 Model, View, Controller가 각각 독립적으로 구현되어 데이터 관리, UI 업데이트, 사용자 입력 처리를 명확하게 분리하고 있습니다.


5. 팩토리 패턴 (Factory Pattern)

정의: 팩토리 패턴은 객체 생성 과정을 캡슐화하여 다양한 종류의 객체를 생성할 수 있게 만드는 디자인 패턴입니다. 이 패턴을 사용하면 객체 생성 로직을 클래스 내부에 감추고, 상위 클래스의 인터페이스를 통해 객체를 생성할 수 있습니다.

언리얼에서의 활용: 언리얼 엔진에서는 UObjectActor 클래스를 동적으로 생성할 때 팩토리 패턴을 사용합니다. 예를 들어, NewObject나 SpawnActor 함수를 통해 객체를 생성함으로써 코드의 유연성을 높입니다.

샘플 코드:

// UObject를 생성하는 팩토리 함수
UWeapon* NewWeapon = NewObject<UWeapon>(this);
if (NewWeapon)
{
    NewWeapon->Initialize();
}

// Actor를 스폰하는 팩토리 함수
FActorSpawnParameters SpawnParams;
AMyActor* SpawnedActor = GetWorld()->SpawnActor<AMyActor>(AMyActor::StaticClass(), SpawnLocation, SpawnRotation, SpawnParams);
if (SpawnedActor)
{
    SpawnedActor->DoSomething();
}

이 코드에서는 팩토리 패턴을 통해 다양한 객체를 동적으로 생성하여 관리할 수 있는 유연성을 보여줍니다.


6. 상태 패턴 (State Pattern)

정의: 상태 패턴은 객체의 상태에 따라 그 객체의 행동을 변경하는 디자인 패턴입니다. 객체가 다양한 상태를 가질 수 있을 때, 각 상태에 맞는 행동을 상태 객체에 위임함으로써 코드의 복잡성을 줄일 수 있습니다.

언리얼에서의 활용: 언리얼 엔진에서는 캐릭터의 상태를 관리할 때 이 패턴을 사용합니다. 예를 들어, 캐릭터가 대기 상태, 이동 상태, 공격 상태 등 다양한 상태를 가질 수 있으며, 이러한 상태를 별도의 클래스로 나누어 관리할 수 있습니다.

샘플 코드:

class UCharacterState
{
public:
    virtual void HandleInput(class AMyCharacter* Character) = 0;
};

class UIdleState : public UCharacterState
{
public:
    virtual void HandleInput(AMyCharacter* Character) override
    {
        UE_LOG(LogTemp, Log, TEXT("캐릭터가 대기 상태입니다."));
    }
};

class UMovingState : public UCharacterState
{
public:
    virtual void HandleInput(AMyCharacter* Character) override
    {
        UE_LOG(LogTemp, Log, TEXT("캐릭터가 이동 중입니다."));
    }
};

이 패턴을 사용하면 캐릭터의 행동을 상태에 따라 쉽게 변경할 수 있고, 각 상태별 행동을 독립적으로 관리할 수 있어 코드의 가독성과 유지 보수성이 향상됩니다.


7. 전략 패턴 (Strategy Pattern)

정의: 전략 패턴은 알고리즘을 캡슐화하여 동적으로 교체할 수 있게 만드는 디자인 패턴입니다. 다양한 전략을 클래스 형태로 정의하고, 런타임에 이들 중 하나를 선택하여 사용할 수 있도록 합니다.

언리얼에서의 활용: 언리얼에서는 AI의 행동 전략을 동적으로 변경하거나 무기 시스템의 공격 방식을 변경하는 데 전략 패턴을 사용할 수 있습니다. 이를 통해 행동을 모듈화하고 쉽게 교체할 수 있습니다.

샘플 코드:

// 전략 인터페이스 정의
class IAttackStrategy
{
public:
    virtual void Attack() = 0;
};

// 근접 공격 전략 클래스
class MeleeAttack : public IAttackStrategy
{
public:
    virtual void Attack() override
    {
        UE_LOG(LogTemp, Log, TEXT("근접 공격을 수행합니다."));
    }
};

// 원거리 공격 전략 클래스
class RangedAttack : public IAttackStrategy
{
public:
    virtual void Attack() override
    {
        UE_LOG(LogTemp, Log, TEXT("원거리 공격을 수행합니다."));
    }
};

// 캐릭터가 전략을 설정하는 예제
class AMyCharacter : public ACharacter
{
public:
    void SetAttackStrategy(IAttackStrategy* NewStrategy)
    {
        CurrentStrategy = NewStrategy;
    }

    void PerformAttack()
    {
        if (CurrentStrategy)
        {
            CurrentStrategy->Attack();
        }
    }

private:
    IAttackStrategy* CurrentStrategy;
};

이 코드는 전략을 쉽게 변경할 수 있는 유연성을 제공하며, 다양한 공격 방식을 모듈화하여 관리할 수 있게 해줍니다.


8. 컴포지트 패턴 (Composite Pattern)

정의: 컴포지트 패턴은 객체를 트리 구조로 만들어 개별 객체와 객체 그룹을 동일하게 취급할 수 있도록 하는 패턴입니다. 이는 계층적인 객체 구조를 표현하는 데 유용하며, 개별 객체와 복합 객체를 동일하게 다룰 수 있습니다.

언리얼에서의 활용: 언리얼에서는 UI 요소를 계층적으로 구성할 때 컴포지트 패턴을 사용합니다. 예를 들어, 여러 개의 UI 요소를 하나의 컨테이너에 넣고, 이를 트리 형태로 관리하여 쉽게 조작할 수 있습니다.

샘플 코드:

// 컴포지트 위젯 생성
UVerticalBox* VerticalBox = WidgetTree->ConstructWidget<UVerticalBox>(UVerticalBox::StaticClass());
UTextBlock* TextBlock = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass());
VerticalBox->AddChild(TextBlock);

이 예제에서는 여러 개의 UI 요소를 계층적으로 배치하여 쉽게 관리할 수 있는 구조를 보여줍니다. 컴포지트 패턴을 통해 UI 요소를 그룹화하고, 그룹 전체에 동일한 작업을 적용할 수 있습니다.


9. 커맨드 패턴 (Command Pattern)

정의: 커맨드 패턴은 요청을 객체로 캡슐화하여 호출자와 수행자 간의 결합을 느슨하게 만드는 패턴입니다. 이 패턴을 사용하면 명령을 추상화하여 실행, 취소, 기록 등의 기능을 구현할 수 있습니다.

언리얼에서의 활용: 언리얼에서는 플레이어의 입력을 처리하는 데 커맨드 패턴을 사용할 수 있습니다. 입력과 행동을 분리하여 명령 객체로 처리함으로써, 다양한 입력 방식에 쉽게 대응할 수 있습니다.

샘플 코드:

// 커맨드 인터페이스 정의
class ICommand
{
public:
    virtual void Execute() = 0;
};

// 점프 명령 클래스
class JumpCommand : public ICommand
{
public:
    virtual void Execute() override
    {
        UE_LOG(LogTemp, Log, TEXT("캐릭터가 점프했습니다."));
    }
};

// 입력에 커맨드를 바인딩하는 예제
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    ICommand* JumpCmd = new JumpCommand();
    PlayerInputComponent->BindAction("Jump", IE_Pressed, this, [JumpCmd]() { JumpCmd->Execute(); });
}

이 코드에서는 입력과 행동을 명령 객체로 분리하여 관리하는 방식을 보여줍니다. 이를 통해 다양한 입력 처리 로직을 쉽게 추가하거나 수정할 수 있습니다.


10. 심플 팩토리 패턴 (Simple Factory Pattern)

정의: 심플 팩토리 패턴은 객체 생성을 전담하는 클래스를 통해 객체를 생성하는 간단한 형태의 팩토리 패턴입니다. 다양한 객체 생성의 책임을 한 클래스에 집중시켜 코드의 복잡성을 줄이는 장점이 있습니다.

언리얼에서의 활용: 언리얼에서는 특정 타입의 객체를 조건에 따라 생성할 때 심플 팩토리 패턴을 활용할 수 있습니다.

샘플 코드:

class WeaponFactory
{
public:
    static UWeapon* CreateWeapon(EWeaponType Type)
    {
        switch (Type)
        {
            case EWeaponType::Sword:
                return NewObject<USword>();
            case EWeaponType::Bow:
                return NewObject<UBow>();
            default:
                return nullptr;
        }
    }
};

이 예제에서는 무기 타입에 따라 다른 객체를 생성하여 반환하는 역할을 WeaponFactory가 수행합니다.


11. 팩토리 메서드 패턴 (Factory Method Pattern)

정의: 팩토리 메서드 패턴은 객체 생성의 책임을 서브클래스에 위임하여 다양한 객체의 생성을 처리하는 디자인 패턴입니다. 이 패턴은 팩토리 메서드를 오버라이드하여 객체 생성 방식을 사용자 정의할 수 있게 합니다.

언리얼에서의 활용: 언리얼에서 특정 행동을 정의할 때 하위 클래스를 통해 객체를 동적으로 생성해야 할 경우 팩토리 메서드 패턴을 사용할 수 있습니다.

샘플 코드:

class AWeaponCreator
{
public:
    virtual UWeapon* CreateWeapon() = 0;
};

class ASwordCreator : public AWeaponCreator
{
public:
    virtual UWeapon* CreateWeapon() override
    {
        return NewObject<USword>();
    }
};

이 패턴을 통해 상위 클래스인 AWeaponCreator는 생성의 책임을 하위 클래스인 ASwordCreator에게 위임합니다.


12. 추상 팩토리 패턴 (Abstract Factory Pattern)

정의: 추상 팩토리 패턴은 관련된 객체들의 군을 생성하는 인터페이스를 제공하는 패턴입니다. 상호 호환성이 있는 객체군을 생성하는 데 적합하며, 일관된 객체 생성을 보장할 수 있습니다.

언리얼에서의 활용: 언리얼에서 상호 호환성이 있는 여러 종류의 객체를 생성해야 할 경우, 예를 들어 무기와 방어구를 동시에 만들어야 하는 상황에서 사용될 수 있습니다.

샘플 코드:

class IWeaponFactory
{
public:
    virtual UWeapon* CreateMeleeWeapon() = 0;
    virtual UWeapon* CreateRangedWeapon() = 0;
};

class MedievalWeaponFactory : public IWeaponFactory
{
public:
    virtual UWeapon* CreateMeleeWeapon() override
    {
        return NewObject<USword>();
    }
    virtual UWeapon* CreateRangedWeapon() override
    {
        return NewObject<UBow>();
    }
};

IWeaponFactory는 다양한 무기군을 생성할 수 있는 추상 팩토리 역할을 수행하며, 이를 상속하는 MedievalWeaponFactory는 무기 종류를 정의합니다.


13. 원형 패턴 (Prototype Pattern)

정의: 원형 패턴은 기존 객체를 복제하여 새로운 객체를 생성하는 패턴입니다. 언리얼에서 여러 유사한 객체를 효율적으로 생성할 때 사용할 수 있습니다.

언리얼에서의 활용: 언리얼에서 기존에 있는 객체를 복제하여 새로운 객체를 생성할 때 사용됩니다. 이는 특히 오브젝트의 초기화 과정이 복잡하거나 많은 속성을 포함할 때 유용합니다.

샘플 코드:

UWeapon* OriginalWeapon = GetSomeWeapon();
UWeapon* ClonedWeapon = DuplicateObject<UWeapon>(OriginalWeapon, nullptr);

이 코드에서는 기존 무기 객체인 OriginalWeapon을 복제하여 새로운 무기 객체 ClonedWeapon을 생성합니다.


14. 경량 패턴 (Flyweight Pattern)

정의: 경량 패턴은 공유를 통해 메모리 사용을 줄이는 패턴입니다. 많은 수의 유사 객체가 필요할 때, 객체의 중복된 데이터를 공유하여 메모리 사용을 절감할 수 있습니다.

언리얼에서의 활용: 언리얼 엔진에서는 많은 수의 비슷한 오브젝트(예: 나무, 돌 등)를 렌더링해야 할 때 경량 패턴을 사용해 메모리를 절약할 수 있습니다.

샘플 코드:

class TreeFlyweight
{
public:
    UStaticMesh* TreeMesh; // 공유되는 데이터
};

class Tree
{
public:
    FVector Position;
    TreeFlyweight* SharedTreeData;
};

여기서 TreeMesh와 같은 데이터는 많은 Tree 객체들 사이에서 공유되어 메모리 사용을 절감합니다.


15. 오브젝트 풀 패턴 (Object Pool Pattern)

정의: 오브젝트 풀 패턴은 객체 생성 비용을 줄이기 위해 미리 생성해 둔 객체들을 재사용하는 방식의 패턴입니다. 주로 반복적으로 생성 및 소멸되는 객체에서 사용됩니다.

언리얼에서의 활용: 언리얼에서는 총알, 몬스터와 같이 자주 생성되고 삭제되는 객체를 관리하는 데 오브젝트 풀 패턴을 사용할 수 있습니다.

샘플 코드:

class BulletPool
{
public:
    UBullet* GetBullet()
    {
        if (PooledBullets.Num() > 0)
        {
            return PooledBullets.Pop();
        }
        return NewObject<UBullet>();
    }

    void ReturnBullet(UBullet* Bullet)
    {
        PooledBullets.Push(Bullet);
    }

private:
    TArray<UBullet*> PooledBullets;
};

BulletPool은 총알 객체를 미리 생성하고, 사용 후 반환하여 재사용함으로써 생성 비용을 절감합니다.


16. 빌더 패턴 (Builder Pattern)

정의: 빌더 패턴은 복잡한 객체를 단계별로 생성할 수 있도록 하는 패턴입니다. 이 패턴은 객체 생성 과정을 세분화하여 각 단계별로 설정할 수 있게 합니다.

언리얼에서의 활용: 언리얼에서는 복잡한 게임 오브젝트나 여러 단계의 초기화가 필요한 UI를 만들 때 빌더 패턴을 사용할 수 있습니다.

샘플 코드:

class HouseBuilder
{
public:
    void BuildWalls() { /* 벽 생성 코드 */ }
    void BuildRoof() { /* 지붕 생성 코드 */ }
    AHouse* GetHouse() { return House; }

private:
    AHouse* House;
};

class Director
{
public:
    void Construct(HouseBuilder* Builder)
    {
        Builder->BuildWalls();
        Builder->BuildRoof();
    }
};

이 예제에서는 HouseBuilder가 집을 단계별로 생성하며, Director는 집을 구성하는 과정을 제어합니다.


17. 어댑터 패턴 (Adapter Pattern)

정의: 어댑터 패턴은 기존 클래스의 인터페이스를 변환하여 클라이언트에서 사용할 수 있도록 하는 패턴입니다. 언리얼에서 기존 API나 클래스를 다른 형태로 사용해야 할 때 유용하게 활용할 수 있습니다.

언리얼에서의 활용: 예를 들어, 언리얼에서 기존의 움직임 시스템을 새로운 방식으로 사용하고자 할 때 어댑터 패턴을 활용할 수 있습니다.

샘플 코드:

class OldMovementSystem
{
public:
    void MoveCharacter(float Speed)
    {
        // 오래된 이동 방식
    }
};

class NewMovementSystemAdapter
{
public:
    NewMovementSystemAdapter(OldMovementSystem* OldSystem) : OldSystem(OldSystem) {}

    void Move(float Velocity)
    {
        OldSystem->MoveCharacter(Velocity);
    }

private:
    OldMovementSystem* OldSystem;
};

NewMovementSystemAdapter는 OldMovementSystem을 감싸서 새 인터페이스와 호환되도록 만들어줍니다.

'Unreal > GPT with Unreal' 카테고리의 다른 글

4.모던 C++  (0) 2024.12.02
3.언리얼 멀티 쓰레드  (1) 2024.11.30
2.블루프린트  (1) 2024.11.29
1.UHT - UClass - CDO  (2) 2024.11.28
0.현대 게임 개발의 기본적인 모토  (2) 2024.11.28

 

0.모던 C++의 개념

"모던 C++"은 일반적으로 C++11 표준 이후에 추가된 새로운 기능과 문법, 스타일을 포함한 C++의 최신 발전을 지칭하는 용어예요. C++는 1983년에 처음 만들어졌고, 이후 계속해서 발전해 왔는데, 특히 C++11 이후부터는 언어 자체가 크게 변화하며 현대적인 개발을 위한 많은 기능들이 추가되었어요. 이때부터의 변화를 묶어서 "모던 C++"이라고 부르는 거죠.

1. 자동 타입 추론 (auto, decltype)

  • auto 키워드를 통한 타입 추론과 decltype을 활용한 변수의 타입 추론.
  • 타입 추론을 통해 코드의 간결성과 가독성 높이기.

예제 코드 및 설명

#include <iostream>
#include <vector>

int main() {
    auto x = 10;  // int로 추론
    auto y = 3.14; // double로 추론
    std::vector<int> vec = {1, 2, 3};
    for (auto& elem : vec) {  // 타입 추론을 통한 범위 기반 for 루프
        std::cout << elem << " ";
    }
    return 0;
}
  • auto는 변수의 타입을 컴파일러가 추론하도록 도와주며, 코드의 간결성을 높여줍니다.
  • Unreal Engine에서 자동 타입 추론은 특히 블루프린트와 C++ 간 데이터 타입을 쉽게 관리할 때 유용합니다.

Unreal Engine 예제 코드

TArray<int32> MyArray = {1, 2, 3, 4};
for (auto& Elem : MyArray) {
    UE_LOG(LogTemp, Warning, TEXT("Element: %d"), Elem);
}
  • Unreal Engine의 TArray와 같은 컨테이너에서 auto를 사용하여 요소의 타입을 자동으로 추론하고 쉽게 접근할 수 있습니다.

2. 스마트 포인터 (std::unique_ptr, std::shared_ptr)

  • *std::unique_ptr*와 **std::shared_ptr*의 차이점과 사용법.
  • 메모리 관리를 더 안전하게 하고 RAII (Resource Acquisition Is Initialization) 개념 이해.

예제 코드 및 설명

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() { std::cout << "Constructor called\n"; }
    ~MyClass() { std::cout << "Destructor called\n"; }
};

int main() {
    std::unique_ptr<MyClass> uniquePtr = std::make_unique<MyClass>();
    std::shared_ptr<MyClass> sharedPtr1 = std::make_shared<MyClass>();
    std::shared_ptr<MyClass> sharedPtr2 = sharedPtr1;  // 참조 횟수 증가
    return 0;
}
  • *std::unique_ptr*는 소유권을 단일 객체에만 부여하며, 다른 포인터로 소유권을 이전할 수 없습니다.
  • *std::shared_ptr*는 여러 개의 포인터가 동일한 객체를 참조할 수 있게 하여, 참조 횟수를 관리합니다.
  • Unreal Engine에서 스마트 포인터는 메모리 관리에 중요한 역할을 하며, 특히 TSharedPtr 같은 언리얼 전용 스마트 포인터는 객체 수명 관리에 필수적입니다.

Unreal Engine 예제 코드

TSharedPtr<MyClass> MySharedPtr = MakeShareable(new MyClass());
if (MySharedPtr.IsValid()) {
    UE_LOG(LogTemp, Warning, TEXT("Shared pointer is valid"));
}
  • TSharedPtr은 Unreal Engine에서 객체의 메모리를 안전하게 관리하기 위해 사용됩니다.

3. 범위 기반 for 루프

  • C++11부터 도입된 새로운 for 루프 형태.
  • 벡터, 리스트 등의 컨테이너를 순회하며 코드 간결화.

예제 코드 및 설명

#include <iostream>
#include <vector>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    for (const auto& num : numbers) {
        std::cout << num << " ";
    }
    return 0;
}
  • 범위 기반 for 루프는 컨테이너의 모든 요소를 쉽게 순회할 수 있게 해줍니다.
  • Unreal Engine에서 TArray와 같은 컨테이너를 다룰 때 코드의 간결성을 높이는 데 유용합니다.

Unreal Engine 예제 코드

TArray<FString> Names = {TEXT("Alice"), TEXT("Bob"), TEXT("Charlie")};
for (const auto& Name : Names) {
    UE_LOG(LogTemp, Warning, TEXT("Name: %s"), *Name);
}
  • TArray와 같은 Unreal 컨테이너를 순회할 때 범위 기반 for 루프를 사용하여 코드를 간결하게 유지할 수 있습니다.

4. 람다 함수

  • 람다 함수의 기본 문법, 캡처 리스트, 반환 타입 유추.
  • 함수 객체와의 차이점 이해, 코드의 유연성과 간결성을 높이는 방법.

예제 코드 및 설명

#include <iostream>
#include <algorithm>
#include <vector>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    int factor = 2;
    std::for_each(vec.begin(), vec.end(), [factor](int& n) { n *= factor; });
    for (const auto& n : vec) {
        std::cout << n << " ";
    }
    return 0;
}
  • 람다 함수는 코드 블록을 간결하게 작성할 수 있게 도와주며, 특정 변수들을 캡처하여 사용할 수 있습니다.
  • Unreal Engine에서는 콜백 함수나 블루프린트와의 상호작용을 간단히 구현하는 데 유용하게 사용됩니다.

Unreal Engine 예제 코드

TArray<int32> Values = {1, 2, 3, 4, 5};
int32 Multiplier = 3;
Values.Sort([Multiplier](int32 A, int32 B) {
    return (A * Multiplier) < (B * Multiplier);
});
  • 람다 함수를 사용하여 Unreal Engine의 배열을 정렬하거나 특정 조건에 따라 데이터를 처리할 수 있습니다.

5. 움직임 의미론 (Move Semantics)와 R값 참조 (&&)

  • 복사 vs 이동: R값 참조(&&)를 이용한 이동 생성자와 이동 할당 연산자.
  • std::move를 통해 객체의 소유권 이전 이해.

예제 코드 및 설명

#include <iostream>
#include <vector>

class MyClass {
public:
    MyClass() { std::cout << "Constructor\n"; }
    MyClass(const MyClass&) { std::cout << "Copy Constructor\n"; }
    MyClass(MyClass&&) noexcept { std::cout << "Move Constructor\n"; }
};

int main() {
    MyClass a;
    MyClass b = std::move(a);  // 이동 생성자 호출
    return 0;
}
  • 이동 의미론은 불필요한 복사를 줄여 성능을 최적화할 수 있습니다.
  • Unreal Engine에서 대규모 데이터를 처리할 때 객체를 복사하는 대신 이동하여 성능을 높이는 데 유용합니다.

Unreal Engine 예제 코드

TArray<int32> SourceArray = {1, 2, 3, 4};
TArray<int32> DestinationArray = MoveTemp(SourceArray);
UE_LOG(LogTemp, Warning, TEXT("SourceArray Num: %d"), SourceArray.Num());
UE_LOG(LogTemp, Warning, TEXT("DestinationArray Num: %d"), DestinationArray.Num());
  • MoveTemp를 사용하여 TArray의 데이터를 효율적으로 이동할 수 있습니다.

6. constexpr와 상수 표현

  • *constexpr*와 **const*의 차이점.
  • 컴파일 시간 계산의 이점과 최적화.

예제 코드 및 설명

constexpr int square(int x) {
    return x * x;
}

int main() {
    constexpr int value = square(5);  // 컴파일 시간에 계산됨
    std::cout << value << std::endl;
    return 0;
}
  • constexpr는 컴파일 시간에 계산이 가능한 상수를 정의하여 프로그램의 성능을 높입니다.
  • Unreal Engine에서 상수값이 자주 사용되는 경우, 컴파일 시간에 계산하도록 constexpr를 활용하여 최적화할 수 있습니다.

Unreal Engine 예제 코드

constexpr int32 MaxHealth = 100;
void AMyCharacter::BeginPlay() {
    Super::BeginPlay();
    CurrentHealth = MaxHealth;
    UE_LOG(LogTemp, Warning, TEXT("Character Health: %d"), CurrentHealth);
}
  • constexpr를 사용하여 캐릭터의 최대 체력 등 상수값을 정의하고 최적화할 수 있습니다.

7. 가변 인자 템플릿 (Variadic Templates)

  • 템플릿의 기본 개념부터 가변 인자를 다루는 방법.
  • 재귀적으로 가변 인자 템플릿을 처리하는 방식 이해.

예제 코드 및 설명

#include <iostream>

void print() {
    std::cout << "\n";
}

template<typename T, typename... Args>
void print(T first, Args... args) {
    std::cout << first << " ";
    print(args...);
}

int main() {
    print(1, 2, 3, 4, "Hello");
    return 0;
}
  • 가변 인자 템플릿은 다양한 인자의 개수를 처리할 수 있어 코드의 유연성을 높여줍니다.
  • Unreal Engine에서는 다양한 타입의 인자를 받아 처리하는 함수 작성에 유용합니다.

Unreal Engine 예제 코드

template<typename... Args>
void LogMultiple(Args... args) {
    (UE_LOG(LogTemp, Warning, TEXT("%s"), *args), ...);
}

LogMultiple(TEXT("Hello"), TEXT("Unreal"), TEXT("Engine"));
  • 가변 인자 템플릿을 사용하여 여러 개의 문자열을 한 번에 로그 출력할 수 있습니다.

8. 스레드와 동기화 (std::thread, std::mutex)

  • *std::thread*를 이용한 멀티스레드 프로그래밍 기본.
  • std::mutex, **std::lock_guard*를 통한 스레드 동기화 기법 이해.

예제 코드 및 설명

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;

void print_thread(int id) {
    std::lock_guard<std::mutex> lock(mtx);
    std::cout << "Thread " << id << " is running\n";
}

int main() {
    std::thread t1(print_thread, 1);
    std::thread t2(print_thread, 2);
    t1.join();
    t2.join();
    return 0;
}
  • 멀티스레드 프로그래밍은 병렬 처리를 통해 성능을 향상시킬 수 있으며, **std::mutex*로 데이터의 동기화를 관리합니다.
  • Unreal Engine에서는 게임 로직의 비동기 처리나 물리 연산 등에 스레드를 사용할 수 있습니다.

Unreal Engine 예제 코드

FCriticalSection Mutex;
void AMyActor::ThreadSafeFunction() {
    FScopeLock Lock(&Mutex);
    // 안전하게 액세스 가능한 코드 블록
    UE_LOG(LogTemp, Warning, TEXT("Thread-safe operation"));
}
  • FCriticalSectionFScopeLock을 사용하여 스레드 간 안전한 데이터 접근을 구현할 수 있습니다.

9. 표준 라이브러리 컨테이너의 확장 (std::array, std::unordered_map)

  • 새로운 컨테이너 타입들 이해: std::array, std::unordered_map.
  • 언제 어떤 컨테이너를 사용하는 것이 좋은지 이해.

예제 코드 및 설명

#include <iostream>
#include <array>
#include <unordered_map>

int main() {
    std::array<int, 3> arr = {1, 2, 3};
    std::unordered_map<std::string, int> umap = { {"A", 1}, {"B", 2} };

    for (const auto& elem : arr) {
        std::cout << elem << " ";
    }
    std::cout << std::endl;

    for (const auto& pair : umap) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }
    return 0;
}
  • *std::array*는 고정 크기 배열을 사용하며, **std::unordered_map*은 해시 테이블을 사용하여 빠른 검색을 제공합니다.
  • Unreal Engine에서 데이터 매핑이 필요한 경우, TMap 같은 언리얼 컨테이너와 유사한 개념입니다.

Unreal Engine 예제 코드

TMap<FString, int32> NameAgeMap;
NameAgeMap.Add(TEXT("Alice"), 25);
NameAgeMap.Add(TEXT("Bob"), 30);
for (const auto& Elem : NameAgeMap) {
    UE_LOG(LogTemp, Warning, TEXT("Name: %s, Age: %d"), *Elem.Key, Elem.Value);
}
  • TMap을 사용하여 키-값 쌍으로 데이터를 저장하고 빠르게 검색할 수 있습니다.

10. std::chrono를 이용한 시간 관리

  • C++에서 시간 측정을 위한 std::chrono 사용법.
  • 시간 단위 변환, 코드의 성능 측정 예제.

예제 코드 및 설명

#include <iostream>
#include <chrono>
#include <thread>

int main() {
    auto start = std::chrono::high_resolution_clock::now();
    std::this_thread::sleep_for(std::chrono::seconds(1));
    auto end = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double> elapsed = end - start;
    std::cout << "Elapsed time: " << elapsed.count() << " seconds" << std::endl;
    return 0;
}
  • *std::chrono*를 사용하면 코드 실행 시간을 측정하거나 특정 시간 동안 지연시킬 수 있습니다.
  • Unreal Engine에서 성능 측정이나 시간 기반 이벤트를 관리할 때 유용하게 사용됩니다.

Unreal Engine 예제 코드

float StartTime = FPlatformTime::Seconds();
FPlatformProcess::Sleep(1.0f);
float EndTime = FPlatformTime::Seconds();
UE_LOG(LogTemp, Warning, TEXT("Elapsed time: %f seconds"), EndTime - StartTime);
  • FPlatformTimeFPlatformProcess를 사용하여 시간 측정 및 지연을 구현할 수 있습니다.

11. 예외 안전성 (noexcept, 스마트 포인터와 예외 처리)

  • 함수의 예외를 명시적으로 막기 위한 noexcept.
  • 예외 안전한 코드 작성 방법과 스마트 포인터의 역할.

예제 코드 및 설명

#include <iostream>

void safeFunction() noexcept {
    std::cout << "This function is noexcept" << std::endl;
}

int main() {
    safeFunction();
    return 0;
}
  • *noexcept*는 예외를 던지지 않는 함수임을 명시하여 컴파일러 최적화를 도와줍니다.
  • Unreal Engine에서는 예외 처리를 제한적으로 사용하기 때문에, **noexcept*를 사용해 코드 안정성을 높이는 것이 중요합니다.

Unreal Engine 예제 코드

void AMyActor::MyFunction() noexcept {
    // 이 함수는 예외를 던지지 않음을 명시
    UE_LOG(LogTemp, Warning, TEXT("This function is noexcept"));
}
  • Unreal Engine에서는 예외를 사용하지 않는 것이 권장되므로, 예외가 발생하지 않는 함수에 대해 noexcept를 사용하여 코드의 신뢰성을 높일 수 있습니다.

12. 모던 C++ 스타일 가이드

  • 범위 기반 for 루프, 스마트 포인터 사용 등 모던 C++ 스타일의 권장 사항.
  • 코드 품질을 높이기 위한 최적의 모던 C++ 사용법.

예제 코드 및 설명

  • 스마트 포인터 사용: **std::unique_ptr*와 **std::shared_ptr*을 통해 명시적인 메모리 관리.
  • 범위 기반 for 루프: 컨테이너의 순회를 간결하게.
  • auto 사용: 복잡한 타입 선언을 줄이고 가독성 향상.

Unreal Engine 예제 코드

TArray<int32> MyArray = {1, 2, 3, 4};
for (auto Elem : MyArray) {
    UE_LOG(LogTemp, Warning, TEXT("Element: %d"), Elem);
}

TUniquePtr<MyClass> MyUniquePtr = MakeUnique<MyClass>();
  • Unreal Engine에서는 모던 C++의 스타일 가이드를 따르면서 TUniquePtr, 범위 기반 for 루프 등을 활용하여 코드를 간결하고 안전하게 유지합니다.

 

'Unreal > GPT with Unreal' 카테고리의 다른 글

5.디자인패턴  (1) 2024.12.03
3.언리얼 멀티 쓰레드  (1) 2024.11.30
2.블루프린트  (1) 2024.11.29
1.UHT - UClass - CDO  (2) 2024.11.28
0.현대 게임 개발의 기본적인 모토  (2) 2024.11.28
💡

언리얼 멀티쓰레드 키워드 설명

1. Task Graph System

  • Task Graph System은 멀티스레드를 활용해 작은 작업(Task)을 여러 스레드에서 병렬로 처리하여 성능을 최적화하는 시스템입니다.

2. FRunnable

  • FRunnable은 사용자가 직접 스레드의 생명주기와 실행을 관리할 수 있는 더 저수준의 멀티스레드 방식입니다.

3. ParallelFor

  • ParallelFor는 배열이나 컬렉션과 같은 반복적인 작업을 병렬로 처리할 때 사용됩니다. 주어진 작업을 여러 스레드에서 나누어 동시에 실행하여 성능을 높이는 간단한 API입니다.
    💡

4. Async 및 Future/Promise 패턴

  • Async 함수는 비동기 작업을 쉽게 수행할 수 있도록 도와줍니다. 특히 FuturePromise 같은 패턴과 함께 사용하여 작업의 결과를 기다리거나 후속 작업을 체인으로 연결하는 비동기 프로그래밍이 가능합니다.
    💡

5. FGraphEvent와 Job Scheduling**

  • 언리얼 엔진에서 FGraphEvent는 비동기 작업의 완료를 추적하고, 해당 작업이 완료된 후 실행되어야 할 후속 작업들을 관리하는 데 사용되는 클래스입니다. 이는 작업 그래프(Task Graph) 시스템의 핵심 요소로, 작업 간의 의존성을 효율적으로 처리하고 스케줄링하는 데 기여합니다.
    • 작업 완료 추적: 비동기 작업의 완료 상태를 추적하여, 후속 작업이 안전하게 실행될 수 있도록 보장합니다.
    • 후속 작업 관리: 작업이 완료된 후 실행되어야 할 후속 작업들을 관리하여, 작업 간의 의존성을 효율적으로 처리합니다.
    작업 스케줄링(Job Scheduling):작업 그래프 시스템의 특징:
    • 병렬 처리 지원: 작업들을 병렬로 처리하여 성능을 향상시킵니다.
    • 의존성 관리: 작업 간의 의존성을 명확하게 정의하여, 올바른 순서로 작업이 수행되도록 보장합니다.
    • 유연한 스케줄링: 작업의 우선순위와 실행 시점을 제어하여, 시스템 자원을 효율적으로 활용합니다.
  • 언리얼 엔진의 작업 스케줄링은 작업 그래프 시스템을 통해 이루어집니다. 이 시스템은 작업들을 그래프 형태로 표현하여, 작업 간의 의존성을 명확하게 정의하고 관리합니다. 이를 통해 병렬 처리를 효율적으로 수행하며, 작업의 우선순위와 실행 시점을 제어할 수 있습니다.
  • FGraphEvent의 주요 기능:

 

💡

여기서 제일 중요한 건 Task Graph System와 FRunnable이다

콘텐츠 개발자가 일반적으로 작업할 때는 Task Graph SystemFRunnable만 이해해도 대부분의 멀티스레딩 요구를 충족시킬 수 있습니다.

  • Task Graph System은 높은 수준에서 비동기 작업을 쉽게 분리하고 처리하는 방식으로, 주로 간단한 병렬 작업에 사용됩니다.
  • FRunnable은 좀 더 정교한 스레드 제어가 필요할 때 사용되며, 주로 특정 작업을 별도 스레드에서 독립적으로 실행하려는 경우에 활용됩니다.

 

💡

Task Graph System 에서 Task가 의미하는 것

언리얼 엔진에서 Task는 주로 비동기 작업이나 특정 작업 단위를 수행하는 기능을 말합니다. 이 개념은 특히 AI 시스템이나 애니메이션 블루프린트에서 자주 사용됩니다. 예를 들어, AI Task는 AI 캐릭터가 특정 행동을 수행하도록 정의하는 데 사용되며, Gameplay Task는 게임플레이와 관련된 복잡한 작업을 비동기로 처리해 성능을 최적화하는 데 도움이 됩니다.

Task 시스템을 활용하면 게임 내 다양한 작업을 비동기적으로 수행해 주기 때문에 CPU 자원을 효율적으로 사용할 수 있습니다.

 

💡

Task는 언리얼에서 제공하는 멀티스레드 기능 중 하나

Task는 언리얼 엔진에서 제공하는 멀티스레드 기능 중 하나입니다. 언리얼은 여러 가지 병렬 작업을 처리하기 위해 Task 시스템을 사용하며, 이러한 작업들은 메인 스레드가 아닌 별도의 스레드에서 비동기적으로 실행됩니다. 이를 통해 게임플레이와 관련된 무거운 연산 작업을 메인 스레드와 분리해 성능을 높이고 프레임률을 안정적으로 유지할 수 있게 합니다.

언리얼의 Task 시스템은 C++로 게임을 개발할 때 복잡한 기능을 병렬로 처리하는 데 매우 유용한 도구입니다.

 

💡

그렇지만 Task == Thread가 아니다

Task는 언리얼 엔진의 Task Graph System에 "일감" 형태로 작업을 넘겨주는 개념입니다. Task Graph System은 여러 작은 단위의 작업을 관리하고 병렬로 실행하여 CPU 자원을 효율적으로 사용하는 것을 목표로 합니다.

개발자가 Task를 넘기면 이 시스템이 그 작업을 여러 스레드에 나누어 분배하고, 각 Task는 엔진의 자원 관리 및 최적화된 스케줄링을 통해 실행됩니다. 따라서 Task는 특정 작업을 병렬로 빠르게 처리하고, 메인 스레드에 부하를 덜어주는 데 중요한 역할을 합니다.

 

💡

언리얼에서 따로 커스텀한 스레드를 쓰려면 FRunnable을 사용하자

FRunnable는 언리얼에서 Thread를 구현하는 인터페이스입니다. FRunnable은 실제로 새로운 스레드를 만들기 위한 기반 클래스로, FRunnableThread와 함께 사용하여 커스텀 스레드를 생성하고 관리합니다. 따라서 FRunnable은 하나의 Thread 개념과 거의 동일하게 작동하며, 개발자가 스레드의 생명주기(시작, 실행, 중지)를 직접 관리할 수 있습니다.

즉, FRunnable = 커스텀 스레드로 이해할 수 있습니다. 이를 통해 개발자는 특정 작업을 독립된 스레드에서 실행하고, 멀티스레드 환경에서 복잡한 작업을 병렬로 처리할 수 있습니다.

 

 

'Unreal > GPT with Unreal' 카테고리의 다른 글

5.디자인패턴  (1) 2024.12.03
4.모던 C++  (0) 2024.12.02
2.블루프린트  (1) 2024.11.29
1.UHT - UClass - CDO  (2) 2024.11.28
0.현대 게임 개발의 기본적인 모토  (2) 2024.11.28

언리얼 엔진에서 **블루프린트(Blueprint)**는 코드 작성 없이 게임플레이 메커니즘, 객체, 상호작용을 만들 수 있게 해주는 비주얼 스크립팅 시스템입니다. 노드 기반의 인터페이스를 통해 다양한 함수와 논리를 연결하여 동작을 정의하며, 프로그래머가 아닌 사람들도 복잡한 게임 기능을 빠르게 구현할 수 있도록 돕습니다.

블루프린트의 작동 방식:

  • 노드(Nodes): 블루프린트는 함수 호출, 변수 정의, 흐름 제어(루프, 분기) 및 이벤트 트리거와 같은 다양한 유형의 노드로 구성됩니다. 이러한 노드를 연결하여 게임 내 논리의 흐름을 정의합니다.
  • 비주얼 스크립팅(Visual Scripting): 블루프린트에서는 코드를 작성하지 않고, 노드를 그리드에 끌어다 놓고 연결하여 작업을 진행합니다. 각 노드는 코드의 블록을 나타내며, 이들을 연결함으로써 실행 순서와 논리를 정의합니다.
  • 이벤트와 함수(Events and Functions): 블루프린트는 플레이어 입력, 객체 충돌, 시간 기반 이벤트 등 다양한 이벤트에 반응할 수 있으며, 특정 작업을 수행하는 커스텀 함수를 만들어 프로젝트 내 여러 부분에서 재사용할 수 있습니다.
  • C++과의 통합(Integration with C++): 블루프린트는 C++ 코드와 통합될 수 있습니다. 개발자는 핵심 로직을 C++로 작성하고, 특정 함수나 변수를 블루프린트에서 사용할 수 있도록 노출시킴으로써, 성능이 중요한 영역은 C++로 처리하면서도 블루프린트의 사용 편의성을 유지할 수 있습니다.

블루프린트의 유형:

  • 액터 블루프린트(Actor Blueprints): 캐릭터, 아이템 또는 환경 요소와 같은 개별 객체의 동작을 정의합니다.
  • 레벨 블루프린트(Level Blueprints): 특정 씬에 맞춘 트리거 설정, 레벨 이벤트 또는 게임플레이 메커니즘을 스크립팅합니다.
  • 위젯 블루프린트(Widget Blueprints): 체력 바, 메뉴 또는 게임 내 텍스트와 같은 UI 요소를 관리합니다.

실시간 업데이트(Live Updates):

블루프린트는 엔진 실행 중에도 변경을 허용합니다. 값을 조정하거나 논리를 수정하고 즉시 결과를 확인할 수 있어, 게임 개발에서 빠른 반복 작업 도구로 유용합니다.

결론:

블루프린트는 게임플레이 메커니즘, AI 동작, 게임 로직 등을 빠르고 유연하게 만들 수 있는 강력한 비주얼 도구로, 개발자, 아티스트, 디자이너 모두가 게임 개발에 기여할 수 있는 효율적인 방법을 제공합니다.

'Unreal > GPT with Unreal' 카테고리의 다른 글

5.디자인패턴  (1) 2024.12.03
4.모던 C++  (0) 2024.12.02
3.언리얼 멀티 쓰레드  (1) 2024.11.30
1.UHT - UClass - CDO  (2) 2024.11.28
0.현대 게임 개발의 기본적인 모토  (2) 2024.11.28

 

1.UHT (Unreal Header Tool)**와 UClass의 관계

UHTUClass는 언리얼 엔진의 핵심적인 기능을 구성하며, 서로 밀접하게 연결되어 있습니다. UHT는 언리얼 엔진의 리플렉션 시스템을 지원하기 위해 작동하며, 이를 통해 UClass는 엔진 내에서 클래스에 대한 메타데이터와 런타임 정보를 제공합니다.


1. UHT (Unreal Header Tool)

역할

  • 언리얼 엔진의 코드 리플렉션 도구로, C++에서 작성된 클래스, 구조체, 함수, 프로퍼티 등을 분석하여 엔진에서 사용 가능한 메타데이터를 생성합니다.
  • UHT는 UCLASS, USTRUCT, UFUNCTION, UPROPERTY와 같은 언리얼의 매크로를 처리하여 엔진의 리플렉션 시스템을 지원하는 코드를 자동으로 생성합니다.

작동 방식

  1. UHT는 .h 파일에 선언된 UCLASS, USTRUCT, UPROPERTY 등을 파싱합니다.
  1. 파싱된 정보를 기반으로 메타데이터를 생성하고, 자동으로 .generated.h 파일을 만듭니다.
  1. 생성된 코드에는 엔진의 리플렉션 시스템과 상호작용하기 위한 코드가 포함되어 있습니다.

UHT의 산출물

  • .generated.h 파일에 포함된 리플렉션 시스템 관련 코드:
    • UClass 정의
    • 클래스 등록 코드
    • 프로퍼티 및 함수 메타데이터
    • 런타임 타입 정보를 제공하는 코드

2. UClass

역할

  • 언리얼 엔진의 리플렉션 시스템에서 사용되는 클래스 정보를 표현하는 객체입니다.
  • UClass는 C++ 클래스와 대응하며, 클래스의 이름, 부모 클래스, 함수, 변수 등의 런타임 정보를 저장하고 제공합니다.

주요 기능

  • 리플렉션 지원: 런타임에 클래스, 함수, 변수에 대한 정보를 조회하거나 조작할 수 있습니다.
  • 메타데이터 접근: 클래스나 프로퍼티에 정의된 메타데이터를 런타임에 읽을 수 있습니다.
  • 동적 객체 생성: UClass를 통해 런타임에 객체를 동적으로 생성할 수 있습니다.

UHT와 UClass

UHT가 UClass를 생성하는 과정

  1. 개발자가 UCLASS() 매크로로 클래스 정의:
    UCLASS()
    class MYGAME_API AMyActor : public AActor
    {
        GENERATED_BODY()
    
    public:
        UPROPERTY(EditAnywhere, BlueprintReadWrite)
        int32 MyValue;
    
        UFUNCTION(BlueprintCallable, Category="Test")
        void MyFunction();
    };
    
    
  1. UHT가 코드를 파싱:
    • UCLASS, UPROPERTY, UFUNCTION 매크로를 파싱하여 클래스, 프로퍼티, 함수의 메타데이터를 생성합니다.
  1. UHT가 .generated.h 파일 생성:
    • UClass 등록 코드와 메타데이터를 포함한 코드가 자동으로 생성됩니다.
    • 예: AMyActor.generated.h 파일:
      class MYGAME_API AMyActor : public AActor
      {
          // UClass 객체의 메타데이터 등록
          static void StaticRegisterNativesAMyActor();
          friend struct Z_Construct_UClass_AMyActor_Statics;
          DECLARE_CLASS(AMyActor, AActor, COMPILED_IN_FLAGS(0), CASTCLASS_None, TEXT("/Script/MyGame"), MYGAME_API)
          DECLARE_SERIALIZER(AMyActor)
      };
      
      
  1. UClass 등록:
    • UHT가 생성한 코드를 컴파일하면, 언리얼 엔진의 리플렉션 시스템에서 해당 클래스에 대한 UClass 객체가 생성되고 등록됩니다.

요약

  1. UHT (Unreal Header Tool):
    • 소스 코드에서 UCLASS, USTRUCT, UFUNCTION, UPROPERTY 등을 파싱하여 메타데이터와 리플렉션 코드를 생성합니다.
  1. UClass:
    • UHT가 생성한 코드를 통해 엔진 런타임에서 클래스 정보를 표현하는 객체로, 리플렉션 시스템에서 사용됩니다.
  1. 관계:
    • UHT는 C++ 코드를 파싱하여 UClass를 생성하기 위한 데이터를 준비합니다.
    • UClass는 런타임에 클래스 정보를 제공하고, 언리얼 엔진의 리플렉션과 동적 객체 생성 기능을 지원합니다.

 

 

3.UClass 와 CDO

UClassCDO (Class Default Object)는 언리얼 엔진에서 클래스와 객체의 기본 상태를 관리하기 위해 사용되며, 둘은 밀접하게 연결되어 있습니다. 이 관계는 **클래스 메타정보(UClass)**와 해당 클래스의 **기본값 관리(CDO)**로 요약될 수 있습니다.


1. UClass란?

UClass는 언리얼 엔진의 리플렉션 시스템을 통해 런타임에서 클래스의 정보를 표현하는 객체입니다. UClass는 특정 클래스에 대한 메타데이터(예: 이름, 부모 클래스, 함수, 프로퍼티 정보)를 저장하며, 다음과 같은 역할을 합니다:

  • 클래스 정보 제공: 클래스의 이름, 부모 클래스, 변수와 함수 등의 정보를 런타임에 제공합니다.
  • CDO와 연결: UClass는 클래스에 해당하는 기본 객체(CDO)를 생성하고 관리합니다.
  • 동적 객체 생성: 런타임에 객체를 생성하는 데 사용됩니다.

2. CDO (Class Default Object)란?

CDO(Class Default Object)는 UClass에서 관리하는 클래스의 기본 객체입니다. CDO는 해당 클래스의 초기 상태를 나타내며, 다음과 같은 특징이 있습니다:

  • 클래스별로 1개만 존재:
    • UClass는 자신의 CDO를 관리하며, 클래스당 하나의 CDO만 생성됩니다.
    • CDO는 일반 객체와 달리 메모리 내에 항상 존재하며, 기본값을 공유하기 위해 사용됩니다.
  • 기본값 관리:
    • 클래스에 정의된 모든 변수의 기본값을 저장합니다.
    • CDO는 새로 생성되는 객체가 초기화될 때 기본값을 제공하는 역할을 합니다.
  • 성능 최적화:
    • CDO는 런타임에서 객체를 생성할 때, 해당 객체를 초기화하기 위해 기본값을 복사합니다. 이를 통해 초기화 비용을 줄이고, 일관된 기본 상태를 유지할 수 있습니다.

3.UClass와 CDO의 관계

  1. CDO는 UClass가 생성하고 관리:
    • UClass는 자신의 클래스에 해당하는 CDO를 생성합니다.
    • CDO는 UClass가 생성될 때 자동으로 초기화되며, 이를 통해 클래스의 기본값을 관리합니다.
    UClass* MyClass = AMyActor::StaticClass();
    UObject* CDO = MyClass->GetDefaultObject();
    
    
  1. CDO는 기본값을 제공:
    • 새로운 객체가 생성될 때, CDO를 기반으로 해당 객체의 기본 상태를 복사합니다.
    • 이는 NewObjectSpawnActor를 호출할 때 내부적으로 작동합니다.
  1. UClass는 CDO에 접근 가능:
    • UClass::GetDefaultObject()를 사용하여 해당 클래스의 CDO에 접근할 수 있습니다.
    • CDO는 런타임에 기본값 확인이나 객체 초기화에 활용됩니다.

실제 사용 예시

CDO에서 기본값 확인

CDO를 통해 클래스의 기본값을 확인할 수 있습니다.

UClass* MyClass = AMyActor::StaticClass();
AMyActor* CDO = Cast<AMyActor>(MyClass->GetDefaultObject());

if (CDO)
{
    UE_LOG(LogTemp, Log, TEXT("Default health: %f"), CDO->Health);
}

객체 생성 시 CDO 활용

새로운 객체를 생성할 때, CDO의 기본값이 복사됩니다.

AMyActor* NewActor = NewObject<AMyActor>();

// NewActor의 초기값은 CDO의 값을 기반으로 설정됩니다.
UE_LOG(LogTemp, Log, TEXT("New actor health: %f"), NewActor->Health);

CDO 값 변경

CDO를 수정하면 해당 클래스의 모든 새 객체에 영향을 미칩니다.

AMyActor* CDO = Cast<AMyActor>(AMyActor::StaticClass()->GetDefaultObject());
if (CDO)
{
    CDO->Health = 200.0f; // 기본값 수정


// 새로 생성된 객체는 수정된 기본값을 가짐
AMyActor* NewActor = NewObject<AMyActor>();
UE_LOG(LogTemp, Log, TEXT("New actor health: %f"), NewActor->Health); // 출력: 200.0


요약

  • UClass:
    • 클래스의 메타데이터를 관리하는 언리얼 엔진의 리플렉션 시스템 객체.
    • 클래스의 런타임 정보를 제공하고, 동적 객체 생성 및 기본값 관리를 담당.
  • CDO (Class Default Object):
    • UClass가 관리하는 기본 객체로, 클래스당 하나만 생성됨.
    • 객체의 기본값을 저장하며, 새 객체 생성 시 초기화에 사용.

UClassCDO는 클래스 메타데이터와 기본값 관리를 통해 런타임에서 객체 생성 및 관리를 효율적으로 수행할 수 있게 해줍니다.

 

 

 

 

 

'Unreal > GPT with Unreal' 카테고리의 다른 글

5.디자인패턴  (1) 2024.12.03
4.모던 C++  (0) 2024.12.02
3.언리얼 멀티 쓰레드  (1) 2024.11.30
2.블루프린트  (1) 2024.11.29
0.현대 게임 개발의 기본적인 모토  (2) 2024.11.28

0.현대 게임 개발의 기본적인 모토

💡

언리얼 엔진을 공부하기 전에 알아야 할 게임 개발의 기본적인 모토

데이터 주도 개발(DDD) 개념은 게임 개발에서 데이터를 독립적으로 관리하고 쉽게 업데이트할 수 있도록 하는 접근 방식입니다. 이를 통해 게임의 핵심 로직을 분리하고, 데이터 변경만으로 게임 콘텐츠를 조정할 수 있게 되어 유연하고 효율적인 개발이 가능해집니다. 다음은 이를 활용한 게임 예시와 엔진별 사용 방법입니다.

기존 게임에서의 데이터 주도 개발 활용 예시

  1. 월드 오브 워크래프트(WoW): WoW는 데이터 주도 개발의 좋은 사례로, 게임의 UI와 다양한 기능이 Lua 스크립트와 XML 파일을 통해 조작됩니다. 이를 통해 유저들은 직접 인터페이스를 커스터마이징하고, 애드온을 만들어 게임 경험을 개인화할 수 있습니다. 이 방식은 데이터를 통해 UI나 게임 기능을 유연하게 변경할 수 있게 하여, 대규모 유저 커뮤니티에서 다양한 애드온이 활발히 개발될 수 있었습니다.
  1. 심즈(The Sims): 심즈는 데이터 파일을 통해 캐릭터의 행동, 집의 배치, 의상 등 다양한 요소를 정의하고 관리합니다. 사용자는 게임 내부에서 직접적인 수정을 하거나 데이터를 변경하여 캐릭터와 게임 속 사물을 제어할 수 있어 매우 높은 자유도를 제공받습니다.
  1. 리그 오브 레전드(LoL): 챔피언 스킬, 아이템 특성 등 많은 요소가 JSON 파일과 같은 외부 데이터로 정의됩니다. 이를 통해 게임 밸런스를 쉽게 조정하고, 빠른 업데이트를 통해 변경 사항을 적용할 수 있습니다. 이런 구조 덕분에 밸런스 조정과 패치 작업이 신속하게 진행됩니다.

언리얼 엔진에서의 데이터 주도 개발 활용

  • *언리얼 엔진(Unreal Engine)**에서는 데이터 주도 개발을 여러 방식으로 지원합니다.
  1. 블루프린트(Blueprint): 언리얼은 시각적인 스크립팅 도구인 블루프린트를 통해 게임의 로직을 구성하고, 외부 데이터와 연결해 쉽게 변경할 수 있습니다. 예를 들어, 게임 내 캐릭터의 속성이나 아이템의 특성을 데이터 테이블(CSV 파일)을 통해 정의하고 이를 블루프린트로 가져와 사용하는 방식입니다.
  1. 데이터 테이블(Data Table): CSV 파일이나 JSON 파일을 데이터 테이블로 만들어 사용하며, 이를 통해 게임의 설정이나 콘텐츠를 손쉽게 변경할 수 있습니다. 데이터 테이블을 활용하면 C++ 코드 수정 없이 데이터를 통해 게임의 특정 기능이나 콘텐츠를 업데이트할 수 있습니다.

유니티에서의 데이터 주도 개발 활용

  • *유니티(Unity)**에서도 데이터 주도 개발이 중요한 역할을 합니다.
  1. 스크립터블 오브젝트(Scriptable Objects): 유니티는 Scriptable Objects를 사용해 게임의 설정 데이터를 독립적으로 관리합니다. 이를 통해 게임 내의 여러 데이터(예: 무기 속성, 캐릭터 능력치 등)를 정의하고 재사용 가능하게 만듭니다. 이는 코드와 데이터를 분리하여 수정이 쉽고, 다른 개발자와 협업 시 매우 효율적입니다.
  1. JSON/XML 데이터 파일: 유니티에서도 JSON이나 XML 파일을 이용해 게임 데이터를 관리합니다. 이를 통해 게임의 콘텐츠를 데이터 파일로 손쉽게 업데이트할 수 있으며, C# 스크립트를 통해 데이터 파일을 읽어 게임 로직과 연결하는 방식을 사용합니다. 예를 들어, 적의 속성이나 스폰 위치를 JSON 파일로 정의해 게임 로직에서 이를 활용하는 방식입니다.

이와 같이 데이터 주도 개발은 게임 엔진의 기능을 최대한 활용하여 로직과 데이터를 분리하고, 개발자와 비개발자가 각자 자신의 역할에 집중할 수 있도록 도와줍니다. 이를 통해 변경 사항을 쉽게 적용하고, 반복적인 테스트와 업데이트 과정을 효율적으로 수행할 수 있습니다.

💡

그래서 이게 현업에서는 어떻게 사용되고 있을까?

DDD 이러한 구조는 프로그래머가 개발(C++), 기획자는 기획 구현(블루프린트, Lua), 데이터 생성자는 생성 (Xml)에 집중하기 위해 채택되었다고 할 수 있습니다.

난 이런거 처음 보는데? 예를 들자면 이러한 방식을 가장 잘 활용한 것이 바로 WOW입니다. 와우의 경우에는 UI면에서 루아 스크립트와 XML을 유저들에게 제공하여, 유저들에게 맞는 다양한 커스터마이징 UI와 애드인들을 제작할 수 있게 되어 있습니다.

그리고 이러한 방법이 크게 성공하여, 수많은 유저들이 만든 다양하고 유용한 애드인 역시 상당히 많습니다. 그래서 와우 이후에도 수많은 게임들이 스크립트를 개발에 다양하게 활용하고 있고, 유니티나 언리얼 같은 게임 엔진은 스크립트만으로도 게임을 제작할 수 있는 형태를 제공하고 있습니다.

'Unreal > GPT with Unreal' 카테고리의 다른 글

5.디자인패턴  (1) 2024.12.03
4.모던 C++  (0) 2024.12.02
3.언리얼 멀티 쓰레드  (1) 2024.11.30
2.블루프린트  (1) 2024.11.29
1.UHT - UClass - CDO  (2) 2024.11.28

+ Recent posts