icon안동민 개발노트

클래스와 객체지향 프로그래밍 (OOP) 기초


 객체지향 프로그래밍(OOP)은 현대 소프트웨어 개발의 근간을 이루는 패러다임이며, 언리얼 엔진의 구조도 이를 기반으로 설계되어 있습니다.

 이 절에서는 C++의 클래스 개념과 OOP의 기본 원칙을 언리얼 엔진의 맥락에서 살펴보겠습니다.

클래스와 객체

 클래스는 데이터(멤버 변수)와 기능(멤버 함수)을 묶은 사용자 정의 데이터 타입입니다. 객체는 클래스의 인스턴스입니다.

 언리얼 엔진에서 가장 기본적인 클래스 중 하나는 AActor입니다.

UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()
 
public:
    AMyActor();
 
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="MyActor")
    float Health;
 
    UFUNCTION(BlueprintCallable, Category="MyActor")
    void TakeDamage(float DamageAmount);
 
private:
    UPROPERTY()
    UStaticMeshComponent* MeshComponent;
};

 이 예제에서 AMyActorAActor를 상속받은 클래스이며, Health라는 멤버 변수와 TakeDamage라는 멤버 함수를 가지고 있습니다.

OOP의 핵심 개념

 1. 캡슐화

 캡슐화는 데이터와 그 데이터를 조작하는 함수를 하나의 단위로 묶는 것을 말합니다.

 언리얼 엔진에서는 UPROPERTYUFUNCTION 매크로를 통해 캡슐화를 구현합니다.

UCLASS()
class AWeapon : public AActor
{
    GENERATED_BODY()
 
private:
    UPROPERTY()
    int32 Ammo;
 
public:
    UFUNCTION(BlueprintCallable, Category="Weapon")
    void Fire();
 
    UFUNCTION(BlueprintCallable, Category="Weapon")
    void Reload(int32 AmmoToAdd);
};

 이 예제에서 Ammo는 private으로 선언되어 직접 접근이 불가능하며, FireReload 함수를 통해서만 조작할 수 있습니다.

 2. 상속

 상속은 기존 클래스의 특성을 새로운 클래스가 물려받는 것을 말합니다.

 언리얼 엔진에서는 많은 클래스들이 계층 구조를 이루고 있습니다.

UCLASS()
class ACharacter : public APawn
{
    GENERATED_BODY()
    // ...
};
 
UCLASS()
class AMyCharacter : public ACharacter
{
    GENERATED_BODY()
    // ...
};

 이 예제에서 AMyCharacterACharacter의 모든 특성을 상속받으며, ACharacterAPawn의 특성을 상속받습니다.

 3. 다형성

 다형성은 같은 인터페이스를 통해 서로 다른 구현을 제공할 수 있는 능력을 말합니다.

 언리얼 엔진에서는 가상 함수를 통해 다형성을 구현합니다.

UCLASS()
class AWeapon : public AActor
{
    GENERATED_BODY()
 
public:
    UFUNCTION(BlueprintNativeEvent, Category="Weapon")
    void Fire();
    virtual void Fire_Implementation();
};
 
UCLASS()
class AGun : public AWeapon
{
    GENERATED_BODY()
 
public:
    virtual void Fire_Implementation() override;
};
 
UCLASS()
class ABow : public AWeapon
{
    GENERATED_BODY()
 
public:
    virtual void Fire_Implementation() override;
};

 이 예제에서 AGunABow는 모두 AWeapon을 상속받지만, Fire 함수의 구현은 각각 다를 수 있습니다.

언리얼 엔진의 클래스 선언 매크로

 언리얼 엔진은 리플렉션 시스템을 위해 특별한 매크로를 사용합니다.

  1. UCLASS() : 클래스를 언리얼 엔진의 타입 시스템에 등록합니다.
  2. UPROPERTY() : 멤버 변수를 리플렉션 시스템에 등록하고, 에디터에서의 편집, 네트워크 복제, 직렬화 등의 기능을 제공합니다.
  3. UFUNCTION() : 멤버 함수를 리플렉션 시스템에 등록하고, 블루프린트에서의 호출, 네트워크 RPC 등의 기능을 제공합니다.
UCLASS(Blueprintable, BlueprintType)
class AMyActor : public AActor
{
    GENERATED_BODY()
 
public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="MyActor")
    float Health;
 
    UFUNCTION(BlueprintCallable, Category="MyActor")
    void TakeDamage(float DamageAmount);
};

 이러한 매크로들은 언리얼 헤더 도구(Unreal Header Tool, UHT)에 의해 처리되어 추가적인 메타데이터와 코드를 생성합니다.

컴포넌트 기반 구조

 언리얼 엔진은 컴포넌트 기반 구조를 채택하고 있어, 객체의 기능을 여러 컴포넌트로 분리하여 구현할 수 있습니다.

UCLASS()
class AMyCharacter : public ACharacter
{
    GENERATED_BODY()
 
public:
    AMyCharacter();
 
private:
    UPROPERTY(VisibleAnywhere, Category="Components")
    USpringArmComponent* CameraBoom;
 
    UPROPERTY(VisibleAnywhere, Category="Components")
    UCameraComponent* FollowCamera;
};
 
AMyCharacter::AMyCharacter()
{
    CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
    CameraBoom->SetupAttachment(RootComponent);
 
    FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
    FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
}

 이 예제에서 AMyCharacterUSpringArmComponentUCameraComponent를 사용하여 카메라 기능을 구현합니다.

OOP와 게임 개발

 객체지향 설계는 게임 개발에 여러 이점을 제공합니다.

  1. 코드 재사용 : 상속을 통해 기존 기능을 쉽게 확장할 수 있습니다.
  2. 모듈성 : 각 클래스가 특정 기능을 담당하므로, 복잡한 시스템을 관리하기 쉬워집니다.
  3. 유지보수성 : 캡슐화를 통해 코드의 특정 부분을 변경하더라도 다른 부분에 미치는 영향을 최소화할 수 있습니다.
  4. 협업 : 잘 정의된 인터페이스를 통해 팀 단위의 개발이 용이해집니다.

 그러나 주의해야 할 점도 있습니다.

  1. 과도한 추상화 : 너무 복잡한 클래스 계층 구조는 오히려 코드의 이해를 어렵게 만들 수 있습니다.
  2. 성능 오버헤드 : 가상 함수의 과도한 사용은 성능 저하를 일으킬 수 있습니다.
  3. 메모리 관리 : 동적으로 생성된 객체의 수명 관리에 주의해야 합니다.

결론

 객체지향 프로그래밍은 언리얼 엔진의 근간을 이루는 패러다임입니다. 클래스와 객체의 개념, 캡슐화, 상속, 다형성 등의 OOP 원칙을 잘 이해하고 적용하면, 유지보수가 용이하고 확장성 있는 게임 코드를 작성할 수 있습니다.

 언리얼 엔진의 특별한 매크로와 컴포넌트 기반 구조를 활용하면, 이러한 OOP 원칙을 더욱 효과적으로 게임 개발에 적용할 수 있습니다. 그러나 항상 성능과 복잡성의 균형을 고려하며 설계해야 합니다.