게임 플레이 프레임워크(Game Play Framework)

  • 언리얼 엔진을 통한 게임 개발 시 기본적으로 제공되는 핵심 클래스와 시스템들의 집합
  • GameMode, GameState, PlayerController, PlayerState, HUD 같은 클래스들이 존재
    • 플레이어 세부정보(해당 세션의 게임 작업 기여도, 킬로그, 캐릭터 클래스 등)
    • 게임의 현재 상태에 대한 정보(게임 전체 작업 진행도, 진행 시간 등)
    • 승리 조건에 대한 정보
    • 위젯 및 HUD
    • 입력

 

 

UE GameFramework - https://medium.com/project-asura/leveraging-ue4-gameplay-framework-for-our-multiplayer-game-41724f2e1dcd

 

 

Game/Player... + Base

  • 해당 클래스들의 부모 클래스
  • Base를 제외한 클래스들은 게임을 설계시 필요한 기능들을 추가적으로 구현해둔 클래스들이다.
  • Ex) GameMode
    • 언리얼에서 제공하는 멀티플레이 기능을 거의 포함, 싱글플레이 가능
    • GameState, PlayState에 대한 연동 활성화
  • GameModeBase
    • GameMode를 단순화 시키고 효율화 시킨 버전
    • 멀티플레이 관련 로직이 일부 포함.
    • 싱글플레이 / 멀티플레이 로직을 직접 구현하고 싶을 경우 

※ GameMode VS GameModeBase 및 주요 함수

 

Game Mode

  • 게임이 시작될 때 플레이어 스폰, 팀 배정, 게임 승패 조건 등 게임의 승리 조건 및 규칙을 정의한다.
  • 해당 세션의 최대 입장 가능 플레이어 인원, 게임에 들어오는 방식, 일시정지, 레벨 전환 등을 정의한다.
  • DefaultPawnClass, PlayerControllerClass 등을 사용하여 플레이어가 연결될 폰 및 컨트롤러들을 설정한다.
  • 게임당 한개만 존재, 서버에서만 실행되며, 클라이언트에서는 직접 수정 할 수 없다.
  • DefaultPawnClass, HUD Class, PlayerControllerClass, SpectatorClass, GameStateClass, PlayerStateClass 등을 설정 할 수 있다.

[UE5.5.1] GameMode Blueprints - Class

 

 

Game State

  • 서버에만 속하지만, 모든 클라이언트에 복제된다.
    • 클라이언트가 게임의 상태를 모니터링 할 수 있도록 도와준다.
  • 게임에서 규칙과 관련된 이벤트 발생 시 추적, 모든 플레이어와 공유해야 하는 데이터 들을 정의한다.
  • 해당 데이터(정보)들은 GameState를 통해 저장하는것이 적합하며, 모든 데이터들은 동기화(Sync)된다.
    • 모든 클라이언트들이 공통적으로 영향을 미치는 데이터들을 저장하는 클래스
  • 게임이 실행된 시간, 각 개별 플레이어가 게임에 참여한 시간 및 플레이어의 현재 상태
  • 게임이 시작되었는지에 대한 여부, 게임의 작업 진행도 등

 

Game Instance

  • 게임의 전체 수명동안 유지되는 클래스
  • 게임의 시작부터 종료까지 존재하며, 레벨 간 상태유지 및 글로벌 데이터 관리에 적합하다.
  • 싱글톤 구조로 설계되어있으며, 레벨이 전환되어도 삭제되지 않는다.
  • 멀티플레이어 환경에서 호스트 및 연결관리를  위한 기능들을 포함하고 있다.
    • 네트워크 관련 이벤트(연결 성공, 실패, 종료 등)을 포함하고있다.
  • 게임이 시작될 때 자동으로 생성된다.
  • 많은 데이터들을 저장시 의존성 문제가 발생한다.
    • 글로벌환경의 State들을 관리시에 동기화 혹은 충돌 가능성이 있다.
    • 이를 해결하기 위해서는 데이터를 개별 클래스로 나누고 GameInstance에서 참조하도록 설계해야한다.
  • 게임 재시작시에도 GameInstance는 초기화 되지않으며, 명시적으로 데이터를 재설정 해야한다.

 

Player State

  • GameState가 전체 게임 상태를 관리한다면, PlayerState는 플레이어별 데이터를 관리한다.
  • 서버에서 생성된 후 Replicate를 통해 클라이언트와 동기화 하는 데이터
  • 서버와 연결된 다른 클라이언트도 알아야 되는 정보
    • 플레이어 사망처리
  • 플레이어만 알아도 괜찮은 정보 -> Pawn Class, PlayerController
    • Tab키를 누른 게임 상태창같은 경우

 

PlayerController

  • 플레이어의 입력을 받아 Possess된 Pawn 또는 UI들을 제어한다.
  • 클라이언트에서 입력을 받아 서버로 요청을 보낸다.
  • 로컬 플레이어 전용 기능(UI, 카메라 조작)등을 처리한다.
  • 플레이어의 입력을 감지하고 서버로 전송하는 역할

 


출처 및 참고내역

https://medium.com/project-asura/leveraging-ue4-gameplay-framework-for-our-multiplayer-game-41724f2e1dcd

https://ebs12373.tistory.com/225

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/gameplay-framework-in-unreal-engine

언리얼 엔진을 사용하여 학습하던 중, nullptr 또는 캐스팅된 클래스가 개발자가 의도한 클래스인지 확인하기위해 

객체 또는 유효성 검사를 하였습니다.

유효성 검사중 Succeeded, Assert, IsValid, ==nullptr 등 다양한 함수를 사용하게 되었으며,

각각 어떠한 상황에 써야지 알맞는지 학습해보았습니다.

이번 포스팅에서는 상황에 맞는 유효성 검사를 수행하는 방법에 대해 알아보겠습니다. 

 

유효성 검사

  • 객체, 클래스, 상태 등이 올바르고 기대된 조건을 충족하는지 확인하는 방법
  • 입력 데이터, 객체 상태, 연산 결과 등이 유효한지 검증한다.
  • 디버깅, 런타임등의 다양한 환경에서 프로그램의 예기치 않은 동작 혹은 오류를 방지 할 수 있다.
  • 오류 발생 위치를 명확하게 알 수 있으므로, 디버깅 과정에서 시간을 절약 할 수 있다.
  • 코드의 신뢰성 및 유지보수성을 향상시킨다.
  • 객체가 삭제되거나, 메모리에서 해제된 상태에서도 참조하는 경우(Dangling Pointer)문제 방지 가능

 

== nullptr

  • 단순히 포인터가 nullptr인지 확인한다.
  • UE엔진의 Class, Actor, Object들은 모두 포인터 방식.
    • 객체 유효성 검사시 가장 쉬운 방법
  • GC객체를 감지하지 못해 IsValid보다는 안전하지 않다
  • GC가 필요하지 않은 경우에는 적합하다.
    • 삭제된 경우, !Actor은 유효할 수 있다고 판단 할 수 있다.
    • 일반 자료형(int32*, float*), 스마트포인터, UClass 리플렉션이 없는 클래스 등

 

IsValid

  • 동적으로 생성된 객체의 유효성 검사에 특화된 함수
  • UObject를 상속한 객체의 포인터에 대한 유효성 검사
  • GC에 의해 삭제되었는지 확인 가능
  • nullptr 과 GC를 IsValid를 사용하여 확인 할 수 있다.
  • nullptr만 확인하기에는 오버헤드가 발생할 우려가 있다.
    • 단순한 nullptr 체크는 == nullptr를 사용하는것이 더 효율적이다.
  • AActor, UActorComponent 객체가 동적으로 생성된 경우 상태를 확인하기 위해 사용한다.
    • 파괴된 액터에 접근하여 크래시를 방지한다.
    • 삭제 중(IsPendingKill()인 경우, IsValid를 사용하여 삭제를 취소 할 수있다.

 

IsPendingKill

  • GC에 의해 삭제 예정 상태, 삭제 중인지 확인하고 싶을 경우 사용한다.
  • 객체가 삭제 중이지만 메모리가 여전히 존재한 경우에 사용한다.
  • nullptr의 상황은 확인하지 않는다.
  • 삭제중인 액터에 접근시, 크래시 발생 우려가 있으므로 IsPendingKill를 사용하여 확인한다.
  • 타이머/델리게이트에서 삭제된 객체를 확인한다.

 

Check

  • UE에서 디버깅중 유효성 검사를 위해 사용한다.
  • 코드에서 특성 조건이 항상 참이어야 하는 경우에 사용한다.
  • 조건이 실패시에 에디터 크래시, Assert를 출력한다.
  • 디버거에서 해당 지점을 중단점으로 잡아 잘못된 코드경로를 빠르게 확인 할  수 있다.
  • 논리적으로 실행이 안되는 코드가 실행되었을 때 크래시를 발생시키는 경우
  • Shipping 빌드시에는 사용하지 않는다.
    • Shipping 빌드시에 런타임 유효성 검사는 Ensure가 더 적합하다.
  • 개발자의 실수로 인한 경우에도 크래시를 유발 할 수 있으므로, Ensure 또는 if문을 사용한 검사가 더 효율적일 수 있다.

Ensure

  • Check와 비슷하지만, 크래시가 아닌 경고만 출력한다.
  • 조건 실패시에도 계속 실행 될 수 있다.
  • 사용자 입력의 경우 실수 가능성이 있어 Ensure를 사용하여 조건이 실패해도 계속 실행해야 하는 경우 사용한다.

Succeeded

  • 함수 실행 결과가 성공했는지에 대해 확인한다.
  • FOptionalResult, FHitResult, ConstructorHelpers같은 함수 호출 결과를 검사하는 경우 사용한다.

 

Assert

  • 조건이 실패하면 디버거 브레이크 포인트를 트리거한다.
  • Shipping 빌드시 비활성화 된다.

 

Verify

  • Shipping 빌드에서도 조건 검사를 수행한다.
  • 디버깅 및 릴리즈 환경 모두에서 조건 검증이 필요한 경우에 적합하다.
  • 실패시에 Check와 같은 크래시가 발생한다.
    • Shipping빌드 환경에서는 크래시 대신 무시된다.
    • 실패해도 크래시 대신 검증 로그를 남기고 실행을 계속해야 할 때 사용한다.
  • 특정 조건이 항상 참이여야 하는 경우에 사용하여, 런타임 환경에서도 코드 안정성을 보장한다.

 

IsTemplate

  • 객체가 CDO인지 확인하고 싶을 때
  • 클래스의 기본 값을 설정하거나, 특정 작업을 CDO에만 적용해야 하는 경우에 사용한다.
  • 게임 플레이 도중 특정 로직이 CDO에서 실행하지 않도록 제한한다.

 

GEngine→AddOnScreenDebugMessage

  • 디버깅시 메시지를 출력하고 싶을 경우 사용한다.
  • 디버깅 중 상태를 확인하기 쉽다.
  • 런타임 디버깅용으로만 사용된다.

 

 

객체 클래스 검사 및 캐스팅시의 차이점  (IsA VS Cast<T>())

 

bool UObject::IsA(const UClass* Class) const;

  • UClass타입을 확인하고자 할 때 사용한다.
  • 객체가 해당 클래스이거나, 그 클래스에서 파생된 클래스인 경우 true 반환
  • 객체 타입을 확인할 때 사용한다.
  • 런타임시 타입 정보를 확인 할 수 있다.
  • 실제 클래스 타입을 확인할 때 사용한다.

 

Cast<T>(Obj)

  • 객체를 지정된 클래스 타입으로 캐스팅 한다.
  • 타입으로 캐스팅 된 객체 포인터를 반환한다.
  • 캐스팅이 실패한 경우 nullptr를 반환한다.
  • 객체를 특정 타입으로 변환하여 작업을 수행하고자 할 때 사용한다.

 

 

 

정리

  • 디버깅 : Check, Ensure, Assert
  • 런타임 조건 검증 및 경고 : Ensure, EnsureMsgf, EnsureAlways
  • UObject 포인터 유효성 확인 : IsValid, IsPendingKill, IsValidLowLevel
  • Shipping 빌드 : Verify, Succeeded
  • 디버깅 메시지 출력 : GEngine→AddOnScreenDebugMessage

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

[UE5] UObject 클래스 내 GetWorld Null 반환  (0) 2025.02.26

액터 (Actor)

  • 컨텐츠를 구성하는 기본 단위
    • class AActor : public UObject
  • 월드안에 배치되어 상호작용 하거나 동작할 수 있는것.
  • 입력처리를 위한 컨트롤러가 없다. 
    • Pawn, Character에서의 사용 가능성 및 GetOwner()를 사용한 컨트롤러 접근을 위해 Actor.h내 전방선언
    • Actor 기반의 Trigger를 참조하거나 상호작용하기위해
    • 입력 이벤트를 바인딩하여 입력처리가 가능하다 (문열기 상호작용 등)
  • 입력이 필요없거나 환경 요소 및 비활성 오브젝트 구현시 사용
  • Translation : SetActorLocation / AddActor[Local,World]Offset
  • Rotate : SetActorRotation / AddActorWorld[Local,World]Rotation
  • Transform : SetActorTransform(FTransform)

 

폰 (Pawn)

  • Actor를 상속받는 하위클래스
    • class APawn : public AActor, public INavAgentInterface
  • 컨트롤러를 통해 AI 조작 또는 플레이어 빙의가 가능한 객체
  • SetupPlayerInputComponent를 사용한 입력처리 가능
  • C++ Class 생성시 RootComponent는 nullptr이다.
    • 개발자가 명시적으로 Component를 추가해야한다.
  • 언리얼 엔진 내 내비게이션 기능을 통해 이동 제어 가능
    • NavMovementComponent 기반의 Component를 통해 제어
    • 월드 내 내비게이션 메시를 기반으로 경로를 계산한다. (NavMeshBoundsVolume)
    • AIController과 연동하여 MoveToLocation, MoveToActor를 사용한 이동 제어
  • AI Controller / Player Controller를 통해 조작이 필요한 객체들을 대상으로 사용한다.
    • 차량, 드론, 비행기 등
    • AI - 인간형이 아닌 오브젝트
  • Rotation : AddController[Roll,Pitch,Yaw]Input
  • AI : MoveToLocation / MoveToActor

 

 

캐릭터 (Character)

  • Pawn을 상속받는 하위클래스
    • class ACharacter : public APawn
  • 인간형 폰이며, 플레이어 또는 NPC의 주요 컴포넌트들을 포함하고 있다.
    • CapsuleComponent(RootComponent) : 충돌처리
    • SkeletalMeshComponent : 캐릭터의 3D모델 및 애니메이션 렌더링
    • CharacterMovementComponent : 이동(걷기, 점프, 중력, 앉기 등)처리
  • 멀티플레이 네트워크 환경에서 캐릭터들의 움직임을 자동으로 동기화 한다.
    • Character 생성자 내 리플리케이션 활성화가 되어있다. 
  • CharacterMovementComponent 내 추가 이동 함수
    • LaunchCharacter - 물리처리를 통한 이동
    • MaxWalkSpeed - 걷기 속도 변경
      • Crouch()시 기존의 AddMovementInput()과의 속도차이를 볼 수 있다.

 

 

Character.cpp 생성자 - Default Value, Default Component Setting

ACharacter::ACharacter(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
  // ...
  
	bUseControllerRotationPitch = false;
	bUseControllerRotationRoll = false;
	bUseControllerRotationYaw = true;

  // CapsuleComponent
	CapsuleComponent = CreateDefaultSubobject<UCapsuleComponent>(ACharacter::CapsuleComponentName);
	CapsuleComponent->InitCapsuleSize(34.0f, 88.0f);
	CapsuleComponent->SetCollisionProfileName(UCollisionProfile::Pawn_ProfileName);
	RootComponent = CapsuleComponent;

  // Arrow Component
	ArrowComponent = CreateEditorOnlyDefaultSubobject<UArrowComponent>(TEXT("Arrow"));
	if (ArrowComponent)
	{
    // ...
	}

  // CharacterMovement
	CharacterMovement = CreateDefaultSubobject<UCharacterMovementComponent>(ACharacter::CharacterMovementComponentName);
	if (CharacterMovement)
	{
		CharacterMovement->UpdatedComponent = CapsuleComponent;
	}

  // SkeletalMesh
	Mesh = CreateOptionalDefaultSubobject<USkeletalMeshComponent>(ACharacter::MeshComponentName);
	if (Mesh)
	{
    // ...
	}

  // GravityDirection
	ReplicatedGravityDirection = UCharacterMovementComponent::DefaultGravityDirection;
}

 

+ Recent posts