Что такое Actor в Unreal Engine и чем он отличается от Component?
Actor — самостоятельный объект мира (размещается в уровне, имеет трансформацию). Component — модульная часть функциональности, всегда принадлежащая Actor'у и расширяющая его поведение без самостоятельного существования.
Actor и Component в Unreal Engine
В Unreal Engine Actor — это базовый объект, который можно размещать или порождать в уровне. Он существует в мировом пространстве, имеет трансформацию (позицию, вращение, масштаб) и может содержать произвольное количество компонентов. Примеры: ACharacter, AStaticMeshActor, ATriggerBox.
Component — это модульная часть функциональности, которая прикрепляется к Actor'у и расширяет его поведение. Компонент не может существовать самостоятельно — он всегда принадлежит Actor'у. Примеры: UStaticMeshComponent, UCapsuleComponent, UCharacterMovementComponent.
Ключевые различия
- Размещение в мире: Actor размещается в уровне напрямую. Component — нет, он прикреплён к Actor'у.
- Иерархия: Компоненты образуют дерево внутри Actor'а; корневой компонент (RootComponent) определяет трансформацию Actor'а в мире.
- Репликация: Actor реплицируется как единица. Component может иметь собственный флаг репликации, но управляется через Actor.
- Spawning: Actor порождается через
UWorld::SpawnActor<T>(). Компоненты создаются черезCreateDefaultSubobject<T>()в конструкторе Actor'а или черезNewObject<T>()+RegisterComponent()во время выполнения.
Пример
// AMyShip.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/StaticMeshComponent.h"
#include "Components/SphereComponent.h"
#include "MyShip.generated.h"
UCLASS()
class MYGAME_API AMyShip : public AActor
{
GENERATED_BODY()
public:
AMyShip();
protected:
// RootComponent — точка отсчёта в мире
UPROPERTY(VisibleAnywhere)
USphereComponent* CollisionSphere;
// Дочерний компонент, прикреплён к Sphere
UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* ShipMesh;
};
// AMyShip.cpp
#include "MyShip.h"
AMyShip::AMyShip()
{
PrimaryActorTick.bCanEverTick = true;
CollisionSphere = CreateDefaultSubobject<USphereComponent>(TEXT("CollisionSphere"));
SetRootComponent(CollisionSphere);
ShipMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ShipMesh"));
ShipMesh->SetupAttachment(CollisionSphere);
}
В этом примере AMyShip — Actor. CollisionSphere и ShipMesh — компоненты. RootComponent задаёт мировую позицию Actor'а; ShipMesh прикреплён к нему и двигается вместе.
Когда создавать Actor, а когда Component
- Создайте Actor, если объект должен существовать независимо в мире: персонаж, предмет, триггер, камера.
- Создайте Component, если это переиспользуемая способность или аспект поведения, который можно добавить к разным Actor'ам: здоровье, инвентарь, звук, физика.
- Избегайте вложенных Actor'ов там, где достаточно компонентов — это упрощает репликацию и управление жизненным циклом.
Подводные камни
- CreateDefaultSubobject вне конструктора: вызов этой функции вне конструктора Actor'а приводит к краши при загрузке. Для динамического создания компонентов используйте NewObject + RegisterComponent.
- Забытый SetRootComponent: если не задать RootComponent, Actor не будет иметь трансформации в мире и его нельзя будет правильно разместить.
- SetupAttachment vs AttachToComponent: SetupAttachment — для конструктора (CDO), AttachToComponent — для рантайма. Путаница приводит к непредсказуемой иерархии.
- Удаление компонента без Unregister: вызов DestroyComponent() без UnregisterComponent() оставляет висячие ссылки и утечки.
- Компонент с тиком без нужды: по умолчанию PrimaryComponentTick.bCanEverTick = false. Включать тик только там, где нужно — иначе перформанс деградирует.
- Blueprint vs C++ иерархия компонентов: если компонент добавлен в Blueprint поверх C++ базы, его нельзя получить через GetComponentByClass в конструкторе C++ родителя — он ещё не создан.
- Репликация компонентов: компонент должен быть создан на сервере и клиенте независимо (или помечен как replicated) — иначе клиент получит nullptr при обращении к нему.
Common mistakes
- Объяснять Actor и Component только по синтаксису, без жизненного цикла и стоимости.
- Игнорировать ошибки, null/empty состояния, порядок инициализации или режим сборки.
- Давать пример, который работает в демо, но ломается при изменении владельца ресурса.
- Использовать макросы или specifier-и наугад и забывать про UObject GC.
What the interviewer is testing
- Кандидат формулирует точную модель для Actor и Component, а не только определение.
- Пример компилируем, безопасен по lifetime и соответствует версии технологии.
- Названы trade-off, ограничения и диагностируемые симптомы ошибки.
- Понимает границу между C++ кодом, runtime/framework metadata и editor/UI слоем.