[UE5 TIL] Day 30 - Chpt 3. 리플렉션 시스템

JungHoon Eum·2026년 4월 7일

키워드

  • Actor Lifecycle, 라이프사이클
  • DeltaTime
  • Transform, 트랜스폼
  • Reflection System, 리플렉션
  • UCLASS
  • UPROPERTY
  • UFUNCTION

기억해야 할 것

  • find (vector.begin(), vector.end(), "Search") - vector.begin(); 로 인덱스
  • 생성자와 BeginPlay()의 역할은 다르다. 생성자는 초기 세팅, BeginPlay()는 실제 게임 시작 이후 상호작용 시점이다.
  • Tick()은 편하지만 비싸다.DeltaTime과 조건 체크를 꼭 같이.
  • 액터는 월드 기준, 컴포넌트는 부모 기준 Transform 설정.
  • 언리얼의 C++ 클래스는 리플렉션 시스템을 통해 에디터와 블루프린트에 연결된다.
  • PostInitializeComponents()
  • Destroyed() EndPlay(const EEndPlayReason::Type EndPlayReason)
  • Super::Endplay(EndPlayReason)
  • Pitch Yaw Roll - 끄덕 도리도리 갸우뚱
  • SetActorLocation()
  • AddActorLocalRotation();
  • AddActorWorldRotation()
  • UCLASS(Blueprintable, BlueprintType)
  • UPROPERTY(EditAnywhere, BlueprintReadWrite,Category = "Item|Components")
  • UFUNCTION(BlueprintCallable, Category = "Item|Actions")

[코드카타] 김서방 찾기

문제 링크

문제 요약

  • 문자열 벡터 seoul에서 "Kim"의 위치를 찾아
  • "김서방은 x에 있다" 형식으로 반환하는 문제.

내가 제출한 코드

#include <string>
#include <vector>
#include <algorithm>

using namespace std;

string solution(vector<string> seoul) {
    string answer = "";
    auto it = find (seoul.begin(), seoul.end(), "Kim");
    answer = "김서방은 " + to_string(it-seoul.begin()) + "에 있다";
    return answer;
}

피드백

  • std::find, to_string() string 더하기 다 잘 씀

배운 점

  • find 후 인덱스 구하기 = iterator - begin()

C++ 언리얼엔진 3D 게임 개발

Actor의 라이프 사이클

액터에 로그 추가하기

Actor의 라이프 사이클

  • 콜백 함수 -> 어떠한 시점이 되면 자동으로 호출이 되는 함수. like BeginPlay()
  • 생성자: C++ 객체가 메모리에 생성될 때 호출. 스폰이 되서 월드에 등록될 때가 아님에 주의.
    • CreateDefaultSubobject 등 컴포넌트 생성, 초기 변수 세팅에 활용
  • PostInitializeComponents() : Post Initialize임. 컴포넌트들 생성/초기화 후 자동 호출.
    • 컴포넌트 간 상호작용, 의존 관계 설정
  • BeginPlay(): 런타임 상에서 배치(Spawn) 직후
    • 월드/다른 액터들 준비 완료. 상호작용 가능.
  • Tick(float DeltaTime): 매 프레임. - 성능상 부담 크다.
  • Destroyed(): Destroy()는 실제 파괴하는 함수. 이러면 Destroyed() 가 호출이 됨. 삭제 되기 직전. 이거 이후 EndPlay() 도 같이 호출됨.
    • 수동 액터 제거 때 마지막 정리 코드를 넣기.
    • 게임 종료/ 언로드 시에는 EndPlay() 만 호출되므로 Destroyed() 에 과한 의존 금지.
    • 마지막에 정리해야 할 자원: 수동 할당한 자원(동적 할당)
      스폰된 자식 액터(자동 해제 안될 시)
      Delegate/Event 바인딩
      사운드/파티클 등
  • EndPlay(): 액터가 월드에서 활동하지 않게 될 때.
    • EEndPlayReason::TypeEnum. 종료사유구분.
    • Destroyed() 보다 호출 보장도 높다.
    • 타이머 정리(GetWorldTimerManager().ClearTime())
      동적 할당 리소스 정리
      데이터 저장

실 사용

  • 헤더에 virtual void PostInitializeComponents() override;
  • Tick 인자는 float DeltaTime, Endplay 인자는 const EEndPlayReason::Type EndPlayReason
  • .cppvoid AItem::BeginPlay(){}
  • 항상 Super::BeginPlay(), Super::Endplay(EndPlayReason) 같이 부모 클래스 호출 해야 함에 주의.

Tick 함수로 Actor Transform 조정

Transform이란

  • 액터는 게임 세계에 존재하는 모든 오브젝트.
  • 위치(Location), 회전(Rotation), 크기(Scale) -> 3개 벡터 합쳐서 Transform 이라고 부른다. 게임 세계에 존재할 수 있다 == Transform을 가진다.
    • Root Component가 있어야 Transform 가질 수 있다.
  • Pitch Yaw Roll -> Pitch Y축, Yaw (Z축), Roll은 X축
    • 끄덕 도리도리 갸우뚱
  • 단축키 W: 이동(Translate)
  • 단축키 E: 회전(Rotate)
  • 단축키 R: 스케일(Scale)

실 사용

  • SetActorLocation(FVector(300.0f, 200.0f, 100.0f))
  • SetActorRotation(FRotator NewRotation): 360도 기반 각도
    • 대체하는 Quaternion 이 있다. FQuat
  • SetActorScale3D(FVector NewScale): 그냥 2.0f 해도 전체 2배 됨.
  • SetActorTransform() 의 경우 한방대입 가능
  • GetActorLocation(), GetActorRotation(), GetActorScale3D()
  • SetActorTransform(FTransform NewTransform)
  • 컴포넌트의 경우: SetRelativeLocation(), SetRelativeRotation() 같이 부모 기준으로.

Tick에서 사용

  • 생성자에서 PrimaryActorTick.bCanEverTick = true; 필요 -> 성능 최적화를 위해 Tick 필요없는 놈들은 꺼두는 게나을 수도 있다.
  • AddActorLocalRotation(FRotator(0.0f, RotationSpeed, 0.0f)); 이렇게 쓰면?
  • 매 프레임마다 소환 -> 컴마다 회전 속도가 다르다.
  • DeltaTime = 프레임 당 시간. 120프레임이면 1/120초
    -RotationSpeed*DeltaTime 하면 틱당 필요속도 나온다.
  • 최대한 적게 써야 한다. if(RotationSpeed != 0.0f){} 안에 넣기.
    • 하지만 RotationSpeed는 부동소수점 -> 자아아아악은 오차가 계속 발생한다. 그래서 if(!FMath::IsNearlyZero(RotationSpeed)){} 씀.
  • 월드 기준 회전은 AddActorWorldRotation()

C++ 클래스와 리플렉션 시스템

  • 블루프린트 - C++ 연동해서 쓰는 방법
  • 블루프린트는 즉각적인 테스트가 가능하다는 게 장점.
  • 가독성과 유지보수에 단점. 내부적 추가 해석 과정 때문에 병목 생김
  • C++은 엔진 코어까지 수정 가능하고, 복잡한 로직을 최적화된 방식으로 구현 가능.
    • 표준 라이브러리, 외부 라이브러리 자유롭게 사용 가능. 포인터 등으로메모리와 로직 정교하게 다룰 수 있음
    • BUT 반복 작업은 번거로움.

리플렉션이란?

  • C++ 클래스의 변수 및 함수 정보를 엔진 내부의 메타데이터 형태로 저장하고, 이를 에디터나 블루프린트에서 활용
  • 매개변수를 에디터에서 바로 조정해서 반복 테스트를 빠르게.

UClass 매크로

  • 헤더에 있는 #include "Item.generated.h": 무조건 #include 가장 아래에. 리플렉션/엔진 통합에 필요한 코드 생성
  • UCLASS() GENERATED_BODY() : 둘이 세트. 언리얼 엔진의 리플렉션 시스템에 해당 클래스 등록 -> 언리얼이 생성하는 코드들 집어넣는 용도.
    • UCLASS() 내부에 (Blueprintable, BlueprintType): Blueprintable은 상속 가능한 클래스로 만드는 것. NotBlueprintable 하면 불가능하게. BlueprintType은 변수나 참조로 다른 클래스에서 이용 가능. 둘 중 하나만 넣어도 된다. 기본은 (Blueprintable, BlueprintType)
  • C++ 클래스를 언리얼 에디터 내에서 우클릭후 Create Blueprint class based on Item 이런 식으로 블루프린트 자손 클래스를 만들어 에디팅하고 사용 가능.

클래스 변수 리플렉션 등록

  • 헤더에서 선언한 변수들 앞에 이렇게 UPROPERTY() 넣어서 리플렉션 시스템에 등록
  • 편집 관련 지정자
    • VisibleAnywhere: 읽기 전용으로 표시되며, 수정은 불가능
    • EditAnywhere: 클래스 기본값, 인스턴스 모두에서 수정 가능
    • EditDefaultsOnly: 클래스 기본값에서만 수정 가능
    • EditInstanceOnly: 인스턴스에서만
  • Blueprint 접근성 관련
    • BlueprintReadWrite: Getter, Setter 둘다 가능
    • BlueprintReadOnly: Getter만
  • Category 지정자
    • Category = "Item|Components" 부분.
    • 잘 정리해서 만들어 놓으면 Details 패널에서 확인하기 쉬움.
  • 메타 옵션 지정자
    • meta=(ClampMin="0.0"): 에디터 변수 지정할 때 최소값 지정 가능. Max는 최대겠지?
    • meta=(AllowPrivateAccess="true"): 해당 변수가 private 이어도 에디터/블루프린트에서 접근 가능 -> 쓸 일이 있을까?
  • 추가 지정자가 없으면 리플렉션에 등록은 되지만 에디터에서 확인이 불가능하다. 가비지 컬렉션/직렬화 이런 기능은 작동할 수 있음.
  • StaticMeshComp 이런 거는 실제 주소를 가져와서 C++에서 붙여넣어서 하는 것보다 걍 에디터에서 설정하는 게 훨씬 쉽다.

함수 리플렉션

  • 헤더 함수들 앞에 UFUNCTION() 넣어서.
  • Blueprint 관련 지정자
    • BlueprintCallable: 블루프린트 이벤트 그래프에서 호출(Execute) 가능한 함수로. 흰색 이벤트 핀이 있는 노드로 만든다는 뜻.
    • BlueprintPure: Getter 역할. Return Value 핀만 있다.
    • BlueprintImplementableEvent: 선언은 C++에서. 구현은 블루프린트에서 -> 블루프린트 함수 구현은 해봤지?
  • 카테고리 설정도 변수들과 같이 있다.

언리얼 라이브 세션

  • 스타터 컨텐츠 팩이나 3인칭 컨텐츠 팩 등등 그런 것도 Content Browser에서 Add 해서 넣을 수 있다 Fab처럼
  • 블루프린트 스트링 관련 노드Append -> 스트링 더하는 노드. A에 원하는 하드코딩 B에 변수 입력하는 식으로 가능

언리얼 과제 6번: 플랫폼 만들기

맵 설정

  • Fab에서 적절한 애셋 모음집을 구해서 이걸 기반으로 플랫폼 맵을 생성하기로 했다.

  • 이걸 기반으로 Empty Level을 만든 후 바닥에 용암 머티리얼을 채워 넣어 기본 맵을 만들었다.

  • 이후 C++ 액터 클래스를 생성하고 내부에 리플렉션을 설정한 코드를 써놓고, Blueprint 자손을 만들어서 이를 이용해 맵에 배치하였다.

트러블슈팅

  • 이렇게 선언 관련 문제가 발생한 경우 -> 생성자 안에서 각 변수를 선언을 해줄 때 변수명 앞에 FVector를 붙여버려서 선언 충돌이 난 경우였다. 이를 지워서 해결.
  • 그리고 현재도 MovingActor 의 경우 내가 생각한 대로 코드가 동작하지 않아 왕복동작이 아닌 영원히 한 방향으로 움직이는 플랫폼이 되어버렸다. 내일 수정 예정. 현재 코드 상태:
void AMovingActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	if (!FMath::IsNearlyZero(FVector::Dist(TargetLocation, GetActorLocation())))
	{
		AddActorWorldOffset(MoveSpeed * DeltaTime);
	}
	else
	{
		SetActorLocation(TargetLocation);
		ReverseMovement();
	}
}

void AMovingActor::ReverseMovement()
{
	MoveSpeed *= -1.f;
	if (TargetLocation.Equals(Destination, 1.0f))
	{
		TargetLocation = StartLocation;
	}
	else
	{
		TargetLocation = Destination;
	}	UE_LOG(LogTemp, Warning, TEXT("Movement Reversed"));
}
profile
개발지망생

0개의 댓글