본문 바로가기

액터 생성과 관리

SpawnActor로 액터를 동적 생성하고 태그·클래스로 탐색한 뒤 참조와 파괴 시점을 안전하게 관리합니다.

이전 절에서 레벨 블루프린트와 C++ 스크립트의 역할과 사용처를 비교하며, 언리얼 엔진에서 게임 로직을 구현하는 다양한 방법을 이해했습니다.

월드에 배치하는 게임플레이 대상은 흔히 액터(Actor)로 구현합니다.

이번 절에서는 게임 월드 내에서 액터를 생성하고, 찾고, 참조하고, 파괴하는 등 액터를 효과적으로 관리하는 방법을 C++ 관점에서 심층적으로 다룰 것입니다.

월드의 규모가 커지고 액터의 수가 많아질수록 효율적인 액터 관리는 게임의 성능과 안정성에 직접적인 영향을 미칩니다.


액터 생성하기 (Spawning Actors)

게임 월드에 액터를 추가하는 방법은 크게 두 가지입니다.

에디터에서 배치 (Manual Placement)

가장 기본적인 방법으로, 언리얼 에디터의 콘텐츠 브라우저에서 원하는 액터 블루프린트(또는 C++ 액터 클래스를 기반으로 만든 블루프린트)를 뷰포트(Viewport)로 드래그 앤 드롭하는 것입니다.

배치를 레벨에 저장하면 로드할 때 해당 액터가 준비됩니다. 게임 중 파괴나 스트리밍 언로드까지 막는 영구 생존 보장은 아닙니다.

런타임에 동적 생성 (Dynamic Spawning)

게임 플레이 중 특정 이벤트(예: 플레이어가 총을 발사하면 총알이 생성, 적이 죽으면 아이템 생성, 스폰 지점에서 플레이어 캐릭터 생성)에 따라 액터를 동적으로 생성해야 할 때 사용합니다.

C++에서는 주로 UWorld::SpawnActor()를 사용합니다. 아래는 헤더의 UCLASS 본문에 둘 멤버 선언과 .cpp의 함수 정의를 함께 보여 주는 발췌입니다. ActorToSpawnClass를 실제 클래스에 할당하고, SpawnMyActor 선언도 헤더에 둡니다.

헤더 멤버와 MyGameModeBase.cpp 발췌
#include "Engine/World.h"
#include "Kismet/GameplayStatics.h" // UGameplayStatics::GetPlayerCharacter 등을 위해

// 생성할 액터 클래스 (블루프린트 또는 C++ 클래스)를 UPROPERTY로 노출
// 블루프린트에서 설정할 수 있도록 EditDefaultsOnly 또는 EditAnywhere 사용
UPROPERTY(EditDefaultsOnly, Category = "Spawning")
TSubclassOf<AActor> ActorToSpawnClass; // AActor를 상속받는 모든 클래스 타입 지정

void AMyGameModeBase::SpawnMyActor()
{
    // 월드 포인터는 GetWorld() 함수로 얻을 수 있습니다.
    UWorld* World = GetWorld();
    if (!World)
    {
        UE_LOG(LogTemp, Error, TEXT("World is null. Cannot spawn actor."));
        return;
    }

    if (!ActorToSpawnClass)
    {
        UE_LOG(LogTemp, Error, TEXT("ActorToSpawnClass is not set."));
        return;
    }

    // 액터를 생성할 위치와 회전 정보를 정의
    FVector SpawnLocation = FVector(0.0f, 0.0f, 100.0f); // 월드 원점에서 Z축으로 100cm 위
    FRotator SpawnRotation = FRotator::ZeroRotator;     // 회전 없음

    // 액터 생성 파라미터 구조체 설정
    FActorSpawnParameters SpawnParams;
    SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
    // 액터 생성 시 콜리전 처리 방식 (예: 충돌 발생 시 위치 조정 후 생성, 또는 항상 생성 등)
    SpawnParams.Owner = this; // 네트워크 소유권 등의 Owner 관계; GC 수명 소유와 다름
    SpawnParams.Instigator = GetInstigator(); // 액터의 인스티게이터 설정 (예: 데미지를 준 주체)

    // 액터 생성!
    AActor* SpawnedActor = World->SpawnActor<AActor>(ActorToSpawnClass, SpawnLocation, SpawnRotation, SpawnParams);

    if (SpawnedActor)
    {
        UE_LOG(LogTemp, Warning, TEXT("Actor %s spawned successfully at %s"), *SpawnedActor->GetName(), *SpawnedActor->GetActorLocation().ToString());
    }
}

이 발췌를 따라 할 때는 먼저 프로젝트의 실제 게임 모드 클래스에 ActorToSpawnClass 멤버와 SpawnMyActor() 선언을 넣고, 위 함수를 같은 클래스의 .cpp 정의로 연결합니다. AMyGameModeBase와 MYPROJECT_API 같은 프로젝트 이름은 실제 이름에 맞춥니다. 기본 게임 모드 또는 맵의 게임 모드 설정이 이 클래스를 사용하도록 하고, 그 Blueprint 기본값에 생성할 액터 클래스를 지정한 뒤 BeginPlay 등 정한 이벤트에서 한 번 호출합니다. GameMode를 쓰는 이 예는 독립 실행 또는 서버 측 게임플레이를 전제로 하며, 네트워크 클라이언트에 같은 GameMode 인스턴스가 존재한다고 가정하지 않습니다.

요청 위치의 Z=100은 100 cm를 뜻합니다. 현재 충돌 옵션은 겹침을 조정할 수 있으면 조정하되 조정하지 못해도 생성하는 선택이므로, 성공했더라도 최종 위치와 충돌을 다시 확인합니다. 반대로 결과가 nullptr이면 뒤의 성공 로그가 나오지 않습니다. 원점에 메시가 보이는지와 생성 함수의 반환값, 실제 게임 모드·클래스 기본값을 각각 확인합니다.

  • TSubclassOf<AActor>: 이 템플릿 타입은 언리얼 클래스(UClass*)에 대한 참조를 저장하며, 해당 클래스가 특정 부모 클래스(여기서는 AActor)를 상속받아야 함을 강제합니다. 에디터에서 UPROPERTY로 노출하면 드롭다운 목록에서 원하는 블루프린트 클래스를 쉽게 선택할 수 있습니다.
  • FActorSpawnParameters: 액터 생성 시의 다양한 추가 옵션을 설정할 수 있는 구조체입니다. 특히 SpawnCollisionHandlingOverride는 매우 중요하며, 액터가 생성될 위치에 다른 오브젝트가 있을 때 어떻게 처리할지를 결정합니다.
  • World->SpawnActor<T>(): 실제로 액터를 생성하는 함수입니다. T는 생성할 액터의 C++ 타입입니다. (예: <AMyBullet> 또는 <AActor>).

액터 찾기 및 참조하기

월드에 이미 존재하는 액터를 찾거나 다른 액터로부터 참조를 얻는 방법은 여러 가지가 있습니다.

태그(Tags)를 이용한 찾기

액터에 태그(Tag)를 부여하여 월드 내에서 해당 태그를 가진 액터들을 찾을 수 있습니다.

MyGameModeBase.cpp
// MyGameModeBase.cpp
#include "Kismet/GameplayStatics.h"

void AMyGameModeBase::FindActorsByTag()
{
    UWorld* World = GetWorld();
    if (!World) return;

    // 특정 태그를 가진 모든 액터 찾기
    TArray<AActor*> FoundActors;
    UGameplayStatics::GetAllActorsWithTag(World, TEXT("TargetEnemy"), FoundActors);

    for (AActor* Actor : FoundActors)
    {
        UE_LOG(LogTemp, Warning, TEXT("Found actor with tag 'TargetEnemy': %s"), *Actor->GetName());
        // 찾은 액터에 대한 추가 로직 수행
    }

    // 특정 태그를 가진 액터 중 가장 첫 번째 액터 찾기
    TArray<AActor*> PlayerStarts;
    UGameplayStatics::GetAllActorsWithTag(World, TEXT("PlayerStart"), PlayerStarts);
    AActor* FirstFoundActor = PlayerStarts.IsEmpty() ? nullptr : PlayerStarts[0];
    if (FirstFoundActor)
    {
        UE_LOG(LogTemp, Warning, TEXT("First PlayerStart actor: %s"), *FirstFoundActor->GetName());
    }
}
  • AActor::Tags: 액터 클래스의 Tags 배열에 태그를 추가할 수 있습니다. 에디터의 디테일 패널에서도 설정 가능합니다.
  • UGameplayStatics::GetAllActorsWithTag(): 주어진 태그를 가진 모든 액터를 TArray에 담아 반환합니다.
  • 배열의 첫 항목: 태그 검색 결과가 비어 있지 않을 때 한 항목을 고른 예시입니다. 태그는 유일 키가 아니고 결과 순서도 선택 정책이 아닙니다. PlayerStart라는 이름의 태그를 실제로 설정해야 하며, 클래스 이름과 태그는 별개입니다.

클래스(Class)를 이용한 찾기

특정 클래스 타입의 모든 액터를 찾을 때 사용합니다.

MyGameModeBase.cpp
#include "Kismet/GameplayStatics.h"
#include "MyTargetActor.h" // 찾고자 하는 액터 클래스의 헤더 포함

void AMyGameModeBase::FindActorsByClass()
{
    UWorld* World = GetWorld();
    if (!World) return;

    // 특정 클래스 타입의 모든 액터 찾기
    TArray<AActor*> FoundActors;
    UGameplayStatics::GetAllActorsOfClass(World, AMyTargetActor::StaticClass(), FoundActors);

    for (AActor* Actor : FoundActors)
    {
        AMyTargetActor* TargetActor = Cast<AMyTargetActor>(Actor);
        if (TargetActor)
        {
            UE_LOG(LogTemp, Warning, TEXT("Found MyTargetActor: %s"), *TargetActor->GetName());
        }
    }

    // 특정 클래스 타입의 첫 번째 액터 찾기
    AMyTargetActor* FirstTargetActor = Cast<AMyTargetActor>(UGameplayStatics::GetActorOfClass(World, AMyTargetActor::StaticClass()));
    if (FirstTargetActor)
    {
        UE_LOG(LogTemp, Warning, TEXT("First MyTargetActor: %s"), *FirstTargetActor->GetName());
    }
}
  • AMyTargetActor::StaticClass(): UClass* 타입으로 해당 C++ 클래스에 대한 메타데이터를 반환합니다.
  • UGameplayStatics::GetAllActorsOfClass(): 주어진 클래스 타입의 모든 액터를 찾습니다.
  • UGameplayStatics::GetActorOfClass(): 주어진 클래스 타입의 첫 번째 액터를 찾습니다.

직접 참조 (UPROPERTY)

가장 권장되는 방법 중 하나는 직접적인 참조를 UPROPERTY로 저장하는 것입니다.

이는 디자이너가 에디터에서 수동으로 액터를 할당하거나, 스폰 시 참조를 직접 넘겨주는 방식으로 사용됩니다.

MyLever.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyLever.generated.h"

class AMyDoor; // MyDoor 액터 클래스 미리 선언

UCLASS()
class MYPROJECT_API AMyLever : public AActor
{
    GENERATED_BODY()

public:
    AMyLever();

protected:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction")
    AMyDoor* ConnectedDoor; // 이 레버가 제어할 문 액터에 대한 직접 참조

    // 레버가 당겨졌을 때 호출될 함수 (예시)
    void OnLeverPulled();
};

이 방법은 런타임에 액터를 검색할 필요 없이 이미 연결된 참조를 사용하므로 성능상 이점이 있습니다.


액터 수명 주기 관리와 파괴

액터는 생성되면 월드 내에서 수명 주기를 가집니다.

특정 시점에 액터를 월드에서 제거해야 할 때가 오는데, 이를 액터 파괴(Destruction)라고 합니다.

액터의 수명 주기 함수 (Recall)

액터는 다음과 같은 주요 수명 주기 함수들을 가집니다.

이 함수들을 오버라이드하여 액터의 특정 단계에서 원하는 로직을 구현합니다.

  • AActor::PostInitializeComponents(): 액터의 모든 컴포넌트가 초기화된 후 호출됩니다.
  • AActor::BeginPlay(): 액터가 게임플레이를 시작할 때 호출되는 초기화 지점입니다.
  • AActor::Tick(float DeltaTime): 틱이 활성화되어 있고 설정한 실행 간격에 도달했을 때 호출됩니다. 시간의 흐름에 따라 지속적으로 업데이트되어야 하는 로직(움직임, 애니메이션 업데이트 등)에 사용됩니다. PrimaryActorTick.bCanEverTick을 false로 설정하여 비활성화할 수 있습니다.
  • AActor::EndPlay(const EEndPlayReason::Type EndPlayReason): 액터가 월드에서 제거되거나 게임이 종료될 때 호출됩니다. 리소스 해제, 연결 끊기 등의 정리 작업에 사용됩니다.

액터 파괴하기 (Destroying Actors)

액터를 월드에서 제거하려면 AActor::Destroy() 함수를 호출합니다.

MyBullet.cpp (총알이 목표물에 맞았을 때 자신을 파괴하는 예시)
void AMyBullet::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
    // ... 데미지 처리 로직 ...

    // 자신 (총알 액터)을 파괴
    Destroy();
}

// MyItem.cpp (플레이어가 아이템을 주웠을 때 아이템을 파괴하는 예시)

void AMyItem::OnCollected()
{
    // ... 아이템 획득 로직 ...

    // 자신 (아이템 액터)을 파괴
    Destroy();
}
  • Destroy(): 반환값이 false이면 파괴되지 않은 경우입니다. 파괴는 지연 처리되며, 월드에서 제거되는 과정과 실제 메모리 회수는 구분됩니다. EndPlay는 파괴뿐 아니라 레벨 전환·언로드나 게임 종료로도 발생할 수 있습니다.
  • 주의사항: 액터가 파괴되면 해당 액터에 대한 유효하지 않은 포인터가 남을 수 있습니다. 유효하지 않은 포인터에 접근하면 크래시가 발생할 수 있으므로, GC가 추적하는 참조나 TWeakObjectPtr를 사용하고 접근 시 IsValid로 상태를 확인합니다. nullptr 검사만으로 파괴 대기 상태를 거르지는 못하며, IsValid도 이미 해제된 임의의 raw 포인터를 안전하게 복구하지는 않습니다.
// 안전한 포인터 사용 예시
if (IsValid(MyActorPointer)) // GC가 추적하는 참조를 전제로 함; 약한 참조는 TWeakObjectPtr::Get()/IsValid() 사용
{
    // MyActorPointer가 유효할 때만 작업 수행
    MyActorPointer->DoSomething();
}

조회 성공과 참조 보관은 생존 보장이 아닙니다

현재 대상 조회/보관→사용 직전 유효성 확인→호출 또는 생략/재연결로 액터 종료 전후 상태를 보여 준다. 참조가 Destroy/언로드를 막지 않는 점을 유지한다.

UNREAL C++ · CH4-3

조회 성공과 참조 보관은 생존 보장이 아닙니다

현재 월드에서 액터를 찾은 결과, GC가 추적하는 참조, 약한 참조, Destroy 반환값의 의미를 각각 확인합니다.

보관한 액터를 사용하기 전에 유효성을 확인한다현재 조회하거나 할당받은 대상을 적절히 추적한 참조에 보관한다. 사용 직전에 유효한 대상을 얻었으면 동작을 호출하고, 아니면 호출을 건너뛰고 다시 연결할지 결정한다. 참조 보관은 Destroy나언로드를 막는 보장이 아니며 이미 해제된 임의의 raw 포인터를 복구하는 경로가 아니다.조회 · 할당현재 대상 얻기추적한 참조UPROPERTY / Weak유효한 대상?사용 직전 확인예아니요대상 동작 호출확인한 대상으로 작업호출 건너뜀다시 연결할지 결정
보관한 액터를 사용하기 전에 유효성을 확인한다현재 조회하거나 할당받은 대상을 적절히 추적한 참조에 보관한다. 사용 직전에 유효한 대상을 얻었으면 동작을 호출하고, 아니면 호출을 건너뛰고 다시 연결할지 결정한다. 참조 보관은 Destroy나언로드를 막는 보장이 아니며 이미 해제된 임의의 raw 포인터를 복구하는 경로가 아니다.조회 · 할당현재 대상 얻기추적한 참조UPROPERTY / Weak유효한 대상?사용 직전 확인예아니요대상 동작 호출확인한 대상으로 작업호출 건너뜀다시 연결할지 결정

UPROPERTY는 도달 가능한 객체의 추적 멤버, 약한 참조는 TWeakObjectPtr의 유효한 대상 얻기를 전제로 합니다. 종료·언로드 뒤에도 보관한 대상의 상태를 다시 확인하며, Destroy 반환값과 즉시 메모리 회수는 같은 판정이 아닙니다.

액터를 찾은 뒤 참조가 보장하는 범위
상황얻는 것추가로 확인할 것
태그·클래스 조회현재 월드에서 찾은 액터 목록태그 유일성·순서·나중 생존을 보장하지 않음
직접 참조 보관반복 검색 없이 지정한 대상 접근UPROPERTY 참조라도 Actor의 Destroy를 막지 않음
약한 참조 보관대상을 살려 두지 않고 유효성 확인TWeakObjectPtr에서 유효한 대상을 얻은 뒤 사용
Destroy 요청파괴 처리 요청 또는 이미 파괴 중임을 나타내는 반환값호출 즉시 모든 포인터가 사라지거나 메모리가 회수되는 것은 아님
태그·클래스 조회
얻는 것: 현재 월드에서 찾은 액터 목록
추가로 확인할 것: 태그 유일성·순서·나중 생존을 보장하지 않음
직접 참조 보관
얻는 것: 반복 검색 없이 지정한 대상 접근
추가로 확인할 것: UPROPERTY 참조라도 Actor의 Destroy를 막지 않음
약한 참조 보관
얻는 것: 대상을 살려 두지 않고 유효성 확인
추가로 확인할 것: TWeakObjectPtr에서 유효한 대상을 얻은 뒤 사용
Destroy 요청
얻는 것: 파괴 처리 요청 또는 이미 파괴 중임을 나타내는 반환값
추가로 확인할 것: 호출 즉시 모든 포인터가 사라지거나 메모리가 회수되는 것은 아님

풀링은 살아 있는 액터의 재사용입니다. 숨김만으로 Tick·충돌·타이머가 모두 정지하지 않습니다. 접근 시 유효성을 확인하며 이미 해제된 임의의 raw 포인터를 IsValid로 복구할 수는 없습니다.

이 예의 조건

  • 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
  • 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
원 도표의 제목·노드·관계·조건·주석
범위원문
관계·조건 설명현재 조회하거나 할당받은 대상을 적절히 추적한 참조에 보관한다. 사용 직전에 유효한 대상을 얻었으면 동작을 호출하고, 아니면 호출을 건너뛰고 다시 연결할지 결정한다. 참조 보관은 Destroy나 언로드를 막는 보장이 아니며 이미 해제된 임의의 raw 포인터를 복구하는 경로가 아니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
대상 보관

현재 대상 Actor를 조회해 참조에 저장한다.

  • 원천: 현재 월드 대상
  • 필드: 보관 참조
  • 참조를 보관해도 Actor 종료를 금지하지 않는다.
사용 직전

대상이 아직 유효한지 확인한다.

  • 검사: 현재 유효성
  • 실패: 호출 생략
  • 조회 시점의 유효성을 후속 사용까지 자동 보장하지 않는다.
종료·재연결

Destroy/언로드 후 대상이 없으면 재조회 정책을 쓴다.

  • 기존 대상: 종료 가능
  • 후속: 생략 또는 재연결
  • 강한 참조와 Actor 게임 수명의 차이를 유지한다.

조회 성공과 참조 보관은 생존 보장이 아닙니다

현재 대상 조회/보관→사용 직전 유효성 확인→호출 또는 생략/재연결로 액터 종료 전후 상태를 보여 준다. 참조가 Destroy/언로드를 막지 않는 점을 유지한다.

UNREAL C++ · CH4-3

조회 성공과 참조 보관은 생존 보장이 아닙니다

현재 월드에서 액터를 찾은 결과, GC가 추적하는 참조, 약한 참조, Destroy 반환값의 의미를 각각 확인합니다.

보관한 액터를 사용하기 전에 유효성을 확인한다현재 조회하거나 할당받은 대상을 적절히 추적한 참조에 보관한다. 사용 직전에 유효한 대상을 얻었으면 동작을 호출하고, 아니면 호출을 건너뛰고 다시 연결할지 결정한다. 참조 보관은 Destroy나언로드를 막는 보장이 아니며 이미 해제된 임의의 raw 포인터를 복구하는 경로가 아니다.조회 · 할당현재 대상 얻기추적한 참조UPROPERTY / Weak유효한 대상?사용 직전 확인예아니요대상 동작 호출확인한 대상으로 작업호출 건너뜀다시 연결할지 결정
보관한 액터를 사용하기 전에 유효성을 확인한다현재 조회하거나 할당받은 대상을 적절히 추적한 참조에 보관한다. 사용 직전에 유효한 대상을 얻었으면 동작을 호출하고, 아니면 호출을 건너뛰고 다시 연결할지 결정한다. 참조 보관은 Destroy나언로드를 막는 보장이 아니며 이미 해제된 임의의 raw 포인터를 복구하는 경로가 아니다.조회 · 할당현재 대상 얻기추적한 참조UPROPERTY / Weak유효한 대상?사용 직전 확인예아니요대상 동작 호출확인한 대상으로 작업호출 건너뜀다시 연결할지 결정

UPROPERTY는 도달 가능한 객체의 추적 멤버, 약한 참조는 TWeakObjectPtr의 유효한 대상 얻기를 전제로 합니다. 종료·언로드 뒤에도 보관한 대상의 상태를 다시 확인하며, Destroy 반환값과 즉시 메모리 회수는 같은 판정이 아닙니다.

액터를 찾은 뒤 참조가 보장하는 범위
상황얻는 것추가로 확인할 것
태그·클래스 조회현재 월드에서 찾은 액터 목록태그 유일성·순서·나중 생존을 보장하지 않음
직접 참조 보관반복 검색 없이 지정한 대상 접근UPROPERTY 참조라도 Actor의 Destroy를 막지 않음
약한 참조 보관대상을 살려 두지 않고 유효성 확인TWeakObjectPtr에서 유효한 대상을 얻은 뒤 사용
Destroy 요청파괴 처리 요청 또는 이미 파괴 중임을 나타내는 반환값호출 즉시 모든 포인터가 사라지거나 메모리가 회수되는 것은 아님
태그·클래스 조회
얻는 것: 현재 월드에서 찾은 액터 목록
추가로 확인할 것: 태그 유일성·순서·나중 생존을 보장하지 않음
직접 참조 보관
얻는 것: 반복 검색 없이 지정한 대상 접근
추가로 확인할 것: UPROPERTY 참조라도 Actor의 Destroy를 막지 않음
약한 참조 보관
얻는 것: 대상을 살려 두지 않고 유효성 확인
추가로 확인할 것: TWeakObjectPtr에서 유효한 대상을 얻은 뒤 사용
Destroy 요청
얻는 것: 파괴 처리 요청 또는 이미 파괴 중임을 나타내는 반환값
추가로 확인할 것: 호출 즉시 모든 포인터가 사라지거나 메모리가 회수되는 것은 아님

풀링은 살아 있는 액터의 재사용입니다. 숨김만으로 Tick·충돌·타이머가 모두 정지하지 않습니다. 접근 시 유효성을 확인하며 이미 해제된 임의의 raw 포인터를 IsValid로 복구할 수는 없습니다.

이 예의 조건

  • 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
  • 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.

관계도의 보충 설명

원 도표의 제목·노드·관계·조건·주석
범위원문
관계·조건 설명현재 조회하거나 할당받은 대상을 적절히 추적한 참조에 보관한다. 사용 직전에 유효한 대상을 얻었으면 동작을 호출하고, 아니면 호출을 건너뛰고 다시 연결할지 결정한다. 참조 보관은 Destroy나 언로드를 막는 보장이 아니며 이미 해제된 임의의 raw 포인터를 복구하는 경로가 아니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
대상 보관

현재 대상 Actor를 조회해 참조에 저장한다.

  • 원천: 현재 월드 대상
  • 필드: 보관 참조
  • 참조를 보관해도 Actor 종료를 금지하지 않는다.
사용 직전

대상이 아직 유효한지 확인한다.

  • 검사: 현재 유효성
  • 실패: 호출 생략
  • 조회 시점의 유효성을 후속 사용까지 자동 보장하지 않는다.
종료·재연결

Destroy/언로드 후 대상이 없으면 재조회 정책을 쓴다.

  • 기존 대상: 종료 가능
  • 후속: 생략 또는 재연결
  • 강한 참조와 Actor 게임 수명의 차이를 유지한다.

액터 관리의 최적화

월드에 수백, 수천 개의 액터가 존재할 수 있는 대규모 게임에서는 액터의 생성, 찾기, 파괴, 그리고 지속적인 업데이트(Tick)에 대한 최적화가 필수적입니다.

  • 불필요한 Tick 비활성화: Tick을 전혀 쓰지 않으면 bCanEverTick=false로 둡니다. 나중에 활성화할 액터는 bCanEverTick=true를 유지하고 시작 활성 상태나 SetActorTickEnabled로 켜고 끕니다.
  • 액터 풀링(Actor Pooling): 총알이나 파티클 효과처럼 자주 생성되고 파괴되는 액터의 경우, 생성/파괴 비용이 높으므로 풀링을 검토할 수 있습니다. 재활용 시 충돌·Tick·타이머·게임 상태 초기화와 메모리 비용까지 비교해야 합니다.
  • 오브젝트 계층 구조 활용: USceneComponent를 통해 액터 내의 컴포넌트들을 계층적으로 구성하거나, 액터 자체를 다른 액터에 부착(AttachToActor 또는 AttachToComponent)하여 월드 아웃라이너를 정리하고 변환 관리를 용이하게 할 수 있습니다.
  • 오브젝트 리스너/이벤트 사용: 특정 액터를 지속적으로 Tick에서 찾거나 모든 액터를 순회하는 대신, Delegate나 Interface를 사용하여 액터 간의 통신을 이벤트 기반으로 처리하면 불필요한 연산을 줄일 수 있습니다.

예시: 문을 찾은 뒤 나중에 사용하기

레버에 연결할 문을 미리 ConnectedDoor에 지정하면 당길 때마다 월드 전체에서 문을 찾지 않아도 됩니다. 하지만 다른 로직이 문을 파괴하거나 그 레벨이 언로드되면 저장한 대상도 다시 확인해야 합니다. 헤더의 선언만으로 OnLeverPulled()가 구현되거나 Blueprint에 호출 가능한 함수가 되는 것은 아니므로, 실제 .cpp 정의와 공개할 함수의 지정자를 함께 준비합니다. 액터 참조를 보관하는 방법과 액터의 게임플레이 생존은 별개의 계약입니다.

검색 결과와 종료 뒤의 사용 판단하기

문 세 개에 같은 태그를 넣었고, 검색 결과의 첫 항목을 레버의 대상으로 골랐습니다. 이 선택이 항상 플레이어에게 가장 가까운 문을 뜻할까요? 또 GC가 추적하는 멤버에 저장한 그 문이 파괴 처리된 뒤에도, 포인터가 nullptr이 아니라는 이유만으로 열기 동작을 호출해도 될까요?

선택 정책과 유효성의 풀이

태그와 결과 배열의 첫 항목은 거리나 유일성을 보장하지 않습니다. 가까운 문이 필요하면 유효한 후보들의 거리를 비교하는 등 별도 선택 정책을 정해야 합니다. 파괴·언로드 뒤에는 적절히 추적한 참조의 유효성을 확인해 사용할지 결정합니다. 참조가 있다는 사실만으로 Destroy를 막거나 즉시 메모리를 회수했다고 판단하지 않습니다. Destroy의 true는 새 파괴 처리 또는 이미 파괴 대상으로 지정된 경우도 포함하므로, true를 새로 한 번 파괴했다는 횟수로 세지 않습니다.

스폰과 반복 검색의 비용은 실제 대상 기기에서 측정한 뒤 조정합니다. 생성 성공과 올바른 초기화·참조·종료 처리는 각각 확인해야 합니다.