첫 C++ Actor
4강. 첫 C++ Actor
1. 이번 강의에서 해결할 문제
어떤 초기화는 생성자에, 어떤 초기화는 BeginPlay에 써야 하는지 불분명하고 모든 로직을 Tick에 넣기 쉽습니다. 배치 가능한 첫 Actor로 수명 주기의 책임을 구분합니다.
2. 학습 목표
3. 핵심 개념
Actor는 World에 배치하거나 런타임에 Spawn할 수 있는 객체입니다. 생성자는 클래스 기본 객체를 만드는 과정에도 호출될 수 있으므로 World의 다른 Actor를 찾는 작업에 적합하지 않습니다. 기본 Subobject 생성과 기본값 설정은 생성자, 플레이 시작 시 World 상태가 필요한 작업은 BeginPlay에 둡니다.
Tick은 매 프레임 호출되므로 명확한 연속 변화가 있을 때만 사용합니다. 이번 표적은 회전을 눈으로 확인하기 위해 제한적으로 Tick을 쓰고, 13강부터 일정 작업은 Timer로 옮깁니다.
처음 등장하는 Unreal 용어
| 용어 | 역할 | 사용할 때 주의할 점 |
|---|---|---|
| Actor | World에 배치하거나 Spawn할 수 있는 게임 객체 | 위치가 필요 없는 순수 데이터까지 모두 Actor로 만들지 않습니다. |
| CDO(Class Default Object) | 클래스 기본값을 보관하기 위해 엔진이 만드는 대표 객체 | 생성자가 실제 플레이 인스턴스에만 한 번 호출된다고 가정하면 안 됩니다. |
UCLASS | 클래스를 Unreal Reflection 시스템에 등록하는 매크로 | 대응하는 GENERATED_BODY와 generated 헤더 규칙을 지킵니다. |
UPROPERTY | 멤버를 Reflection·Editor·직렬화 규칙에 참여시키는 매크로 | C++ 접근 지정자와 Editor 노출 범위는 서로 다른 개념입니다. |
BeginPlay | 해당 Actor가 플레이를 시작할 준비가 됐을 때 호출되는 함수 | 생성자보다 늦지만 모든 게임 상태가 영원히 유효하다는 뜻은 아닙니다. |
Tick | 활성 Actor에 프레임마다 전달되는 함수 | 꼭 필요한 연속 변화가 있을 때만 켭니다. |
생성자와 BeginPlay를 나누는 핵심은 “한 번인가 여러 번인가”만이 아닙니다. 생성자는 기본값과 Subobject 구조처럼 World가 없어도 결정할 수 있는 것, BeginPlay는 플레이 중 World와 다른 Actor가 필요한 인스턴스 초기화를 담당합니다.
4. 단계별 실습
파일 경로: Source/TopDownSurvival/Actors/SurvivalTarget.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "SurvivalTarget.generated.h"
UCLASS()
class TOPDOWNSURVIVAL_API ASurvivalTarget : public AActor
{
GENERATED_BODY()
public:
ASurvivalTarget();
protected:
virtual void BeginPlay() override;
virtual void Tick(float DeltaSeconds) override;
UPROPERTY(EditAnywhere, Category = "Target", meta = (ClampMin = "0.0"))
float RotationSpeed = 45.0f;
};
파일 경로: Source/TopDownSurvival/Actors/SurvivalTarget.cpp
#include "Actors/SurvivalTarget.h"
#include "Engine/Engine.h"
ASurvivalTarget::ASurvivalTarget()
{
PrimaryActorTick.bCanEverTick = true;
}
void ASurvivalTarget::BeginPlay()
{
Super::BeginPlay();
UE_LOG(LogTemp, Display, TEXT("SurvivalTarget 시작: %s"), *GetName());
}
void ASurvivalTarget::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
AddActorLocalRotation(FRotator(0.0f, RotationSpeed * DeltaSeconds, 0.0f));
}
중요한 줄을 따라가기
#include "SurvivalTarget.generated.h"는 UHT가 만든 선언을 연결하며 헤더의 마지막 include여야 합니다.TOPDOWNSURVIVAL_API는 모듈 경계를 넘어 클래스를 사용할 때 필요한 내보내기·가져오기 표식을 생성 구성에 맞게 바꿉니다.EditAnywhere는 C++ 기본값45.0f를 Blueprint 기본값이나 배치 인스턴스에서 조정할 수 있게 합니다.ClampMin = "0.0"은 Editor 입력 UI에서 음수 회전 속도를 제한합니다. 런타임 외부 입력까지 자동 검증하는 것은 아닙니다.PrimaryActorTick.bCanEverTick = true가 있어야Tick호출을 받을 수 있습니다.Super::BeginPlay()와Super::Tick(...)은 부모 Actor가 담당하는 수명 주기 처리를 유지합니다.RotationSpeed * DeltaSeconds는 초당 각도를 이번 프레임의 각도로 바꿉니다. 90이면 약 1초에 90도입니다.
실행 흐름
클래스 로드와 기본 객체 구성
↓
레벨의 BP_SurvivalTarget 인스턴스 생성
↓
BeginPlay → 시작 로그 한 번
↓
Tick(DeltaSeconds) → 매 프레임 회전값 누적
현재 Actor에는 아직 Mesh가 없으므로 Game 화면에서 회전이 보이지 않는 것이 정상입니다. Play 중 World Outliner에서 실행 중인 인스턴스를 선택해 Transform Rotation이 변하는지 확인하고, 6강에서 Mesh를 붙인 뒤 시각적으로 확인합니다.
Unreal Editor에서 연결
- C++ 컴파일이 성공한 뒤 Content Browser에서 C++
ASurvivalTarget을 부모로 하는 BlueprintBP_SurvivalTarget을 만듭니다. - Blueprint의 Class Defaults에서 Rotation Speed 기본값을
90으로 설정하고 Compile·Save합니다. - 레벨에 하나 배치합니다. Details의 Rotation Speed가 90인지 확인합니다.
- Output Log를 연 뒤 Play합니다.
SurvivalTarget 시작: BP_SurvivalTarget...로그가 배치 인스턴스당 한 번 나와야 합니다. - Play 중 World Outliner의 실행 인스턴스를 선택해 Details의 Transform Rotation 값이 변하는지 확인합니다. 빈 Actor라 화면에는 모양이 보이지 않습니다.
5. 코드가 동작하는 이유
GENERATED_BODY는 Reflection과 생성 코드가 클래스에 연결될 자리를 만듭니다. TOPDOWNSURVIVAL_API는 다른 모듈에서 이 클래스를 사용할 때 필요한 내보내기 표시입니다. DeltaSeconds를 곱하면 프레임 수가 달라도 초당 회전량이 비슷해집니다.
6. 자주 하는 실수와 해결법
7. 직접 실습
실습 목표
생성자·BeginPlay·Tick의 호출 목적을 로그와 Transform 값으로 구분하고, DeltaSeconds가 초당 속도를 만드는 이유를 검증합니다.
시작 전 상태
레벨에 BP_SurvivalTarget을 하나만 배치하고 Output Log를 Clear합니다. Play 전에 Rotation Speed와 Transform Rotation을 기록합니다.
1단계: 가장 작은 확인
Rotation Speed를 0으로 두고 Play합니다. BeginPlay 로그는 한 번 나오지만 Rotation은 변하지 않아야 합니다. 이 결과는 Tick이 호출되는 것과 값이 실제로 변하는 것이 별개임을 보여 줍니다.
2단계: 값을 바꾸고 결과 비교
Rotation Speed를 45, 180으로 바꿔 각각 약 2초 동안 실행합니다. 시작 회전이 0이었다면 Yaw 누적 변화는 각각 약 90도, 360도입니다. 수동 시작·정지 오차가 있으므로 정확히 같은 숫자보다 비율과 방향을 확인합니다.
3단계: 직접 적용
생성자에서 bCanEverTick을 false로 바꿨을 때 어떤 결과가 나올지 먼저 적고 실행합니다. BeginPlay 로그는 나오지만 Rotation은 변하지 않아야 합니다. 확인 후 다음 강의를 위해 true로 되돌립니다.
4단계: 스스로 확인
막혔을 때
| 증상 | 먼저 확인할 곳 | 해결 |
|---|---|---|
| 컴파일 시 generated 헤더 오류 | 헤더 include 순서와 클래스 매크로 | SurvivalTarget.generated.h를 마지막 include로 두고 UCLASS·GENERATED_BODY를 확인합니다. |
| Blueprint 부모로 클래스가 안 보임 | 컴파일 성공 여부와 UCLASS | Editor Target을 빌드한 뒤 Content Browser를 새로 고칩니다. |
| 시작 로그가 두 번 이상 나옴 | 레벨 인스턴스 수 또는 PIE World 수 | World Outliner에서 배치 수를 확인하고 다중 PIE라면 각 World 로그를 구분합니다. |
| 로그는 나오지만 회전을 볼 수 없음 | 아직 렌더링 Component가 없음 | 실행 인스턴스의 Transform 값을 확인하고 6강에서 Mesh를 추가합니다. |
8. 이해 점검 질문 3개
9. 핵심 요약
다음 강의 연결
다음 강의에서는 Reflection 매크로가 C++ 멤버를 Editor와 Blueprint에 어떻게 노출하는지 살펴본 뒤, 6강에서 이 Actor에 시각적 Component를 조립합니다.
첫 C++ Actor 미니 퀴즈
선택 즉시 정답과 해설을 확인할 수 있습니다. 결과는 이 브라우저에만 저장됩니다.
학습을 마쳤나요?
직접 실습과 점검 질문까지 확인한 뒤 완료로 표시하세요.