이 페이지에서는 고급 언리얼 엔진 프로그래밍 개념에 대해 살펴봅니다. 아래에 나와 있는 설명은 Unity #C에 익숙하고 언리얼 엔진 C++을 배우고 싶은 사용자를 대상으로 합니다. 하지만 대부분의 경우 블루프린트를 사용해도 동일한 결과를 얻을 수 있기 때문에 가능한 한 C++과 블루프린트 샘플을 모두 추가했습니다.
가장 일반적인 게임플레이 프로그래밍 패턴과 언리얼 엔진에서의 접근법에 대해 먼저 살펴보겠습니다. 아래의 샘플은 일반적인 Unity 함수 일부와 이와 동일한 기능을 언리얼 엔진에서 구현하는 방법에 대해 다룹니다.
GameObject 인스턴스화/액터 스폰하기
Unity에서는 Instantiate
함수를 사용하여 오브젝트의 새 인스턴스를 생성합니다. 이 함수는 GameObject, MonoBehaviour 등의 UnityEngine.Object
타입을 취하여 사본을 생성합니다.
public GameObject EnemyPrefab;
public Vector3 SpawnPosition;
public Quaternion SpawnRotation;
void Start()
{
GameObject NewGO = (GameObject)Instantiate(EnemyPrefab, SpawnPosition, SpawnRotation);
NewGO.name = "MyNewGameObject";
}
언리얼 엔진에서는 두 가지 함수를 사용하여 오브젝트를 인스턴스화합니다.
NewObject
는 새UObject
타입을 생성합니다.SpawnActor
는AActor
타입을 스폰합니다.
UObject와 NewObject
언리얼 엔진에서의 UObject
서브클래싱은 Unity에서의 ScriptableObject
서브클래싱과 상당히 유사합니다. 이것은 월드에 스폰할 필요가 없거나 액터처럼 어태치된 컴포넌트를 갖출 필요가 없는 게임플레이 클래스에 유용합니다.
Unity에서 ScriptableObject
의 서브클래스를 직접 생성하면 다음과 같이 인스턴스화됩니다.
MyScriptableObject NewSO = ScriptableObject.CreateInstance<MyScriptableObject>();
언리얼 엔진에서 UObject
의 파생 타입을 생성하면 다음과 같이 인스턴스화할 수 있습니다.
UMyObject* NewObj = NewObject<UMyObject>();
AActor와 SpawnActor
액터는 SpawnActor
메서드를 사용하여 월드(C++의 UWorld
) 오브젝트에서 스폰됩니다. 일부 UObject는 GetWorld
메서드를 제공합니다. 예를 들어 모든 액터는 이 메서드를 제공합니다. 이 메서드는 월드 오브젝트를 구하는 방법과 관련됩니다.
아래 예시에서는 다른 액터를 전달하는 대신, 스폰해야 하는 액터의 클래스를 전달합니다. 이 샘플에서 클래스는 AMyEnemy의 어떤 서브클래스든 될 수 있습니다.
Unity에서 인스턴스화를 사용할 때처럼 다른 오브젝트의 사본을 만들고 싶은 경우에는 어떻게 해야 할까요?
NewObject
및 SpawnActor
함수에도 작업할 템플릿 오브젝트를 제공할 수 있습니다. 언리얼 엔진은 처음부터 오브젝트를 완전히 새로 생성하는 대신에 해당 오브젝트의 사본을 생성합니다. 이를 통해 UPROPERTY와 컴포넌트가 모두 복사됩니다.
AMyActor* CreateCloneOfMyActor(AMyActor* ExistingActor, FVector SpawnLocation, FRotator SpawnRotation)
{
UWorld* World = ExistingActor->GetWorld();
FActorSpawnParameters SpawnParams;
SpawnParams.Template = ExistingActor;
World->SpawnActor<AMyActor>(ExistingActor->GetClass(), SpawnLocation, SpawnRotation, SpawnParams);
}
여기서 '처음부터 완전히 새로'라는 표현이 어떤 뜻인지 궁금할 수 있습니다. 사용자가 생성한 각 오브젝트 클래스는 프로퍼티와 컴포넌트에 대해 디폴트 값을 포함하는 디폴트 템플릿을 갖습니다. 이러한 프로퍼티를 오버라이드하지 않고 자체 템플릿을 제공하지 않은 경우 언리얼 엔진은 위와 같은 디폴트 값을 사용하여 오브젝트를 생성합니다. 이해를 돕기 위해 MonoBehaviour 예시를 살펴보겠습니다.
public class MyComponent : MonoBehaviour
{
public int MyIntProp = 42;
public SphereCollider MyCollisionComp = null;
void Start()
{
// 콜리전 컴포넌트가 없으면 생성합니다.
if (MyCollisionComp == null)
{
MyCollisionComp = gameObject.AddComponent<SphereCollider>();
MyCollisionComp.center = Vector3.zero;
MyCollisionComp.radius = 20.0f;
}
}
}
위 예시에서는 디폴트 값이 42인 int
프로퍼티와 디폴트 반경이 20인 SphereCollider
컴포넌트가 있습니다.
언리얼 엔진에서도 오브젝트의 생성자를 사용하여 동일한 작업을 수행할 수 있습니다.
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
UPROPERTY()
int32 MyIntProp;
UPROPERTY()
USphereComponent* MyCollisionComp;
AMyActor()
{
MyIntProp = 42;
MyCollisionComp = CreateDefaultSubobject<USphereComponent>(FName(TEXT("CollisionComponent"));
MyCollisionComp->RelativeLocation = FVector::ZeroVector;
MyCollisionComp->SphereRadius = 20.0f;
}
};
AMyActor
의 생성자에서 해당 클래스의 디폴트 프로퍼티 값을 설정합니다. 참고로, CreateDefaultSubobject
함수를 사용하여 컴포넌트를 생성하고 디폴트 프로퍼티를 할당할 수 있습니다. 이 함수를 사용하여 생성된 모든 서브 오브젝트는 디폴트 템플릿으로 작동하기 때문에 서브클래스 또는 블루프린트에서 수정할 수 있습니다.
한 타입에서 다른 타입으로 형변환하기
이 경우에는 이미 가지고 있는 컴포넌트를 구하여 특정 타입으로 형변환한 다음 조건부로 무언가를 수행합니다.
Unity C#
Collider collider = gameObject.GetComponent<Collider>;
SphereCollider sphereCollider = collider as SphereCollider;
if (sphereCollider != null)
{
// ...
}
언리얼 엔진 C++
UPrimitiveComponent* Primitive = MyActor->GetComponentByClass(UPrimitiveComponent::StaticClass());
USphereComponent* SphereCollider = Cast<USphereComponent>(Primitive);
if (SphereCollider != nullptr)
{
// ...
}
GameObject/액터 소멸하기
Unity
|
C++
|
블루프린트 ![]() |
GameObject/액터 소멸하기(1초 딜레이)
Unity
|
C++
|
블루프린트 ![]() |
GameObject/액터 비활성화하기
Unity
|
C++
|
블루프린트 ![]() |
컴포넌트에서 GameObject/액터 액세스하기
Unity
|
C++
|
블루프린트 ![]() |
GameObject/액터에서 컴포넌트 액세스하기
Unity C#
MyComponent MyComp = gameObject.GetComponent<MyComponent>();
언리얼 엔진 C++
UMyComponent* MyComp = MyActor->FindComponentByClass<UMyComponent>();
블루프린트

GameObject/액터 찾기
Unity C#
// 이름을 기준으로 GameObject를 찾습니다.
GameObject MyGO = GameObject.Find("MyNamedGameObject");
// 타입을 기준으로 오브젝트를 찾습니다.
MyComponent[] Components = Object.FindObjectsOfType(typeof(MyComponent)) as MyComponent[];
foreach (MyComponent Component in Components)
{
// ...
}
// 태그를 기준으로 GameObject를 찾습니다.
GameObject[] GameObjects = GameObject.FindGameObjectsWithTag("MyTag");
foreach (GameObject GO in GameObjects)
{
// ...
}
언리얼 엔진 C++
// 이름을 기준으로 액터를 찾습니다(UObject에서도 작동함).
AActor* MyActor = FindObject<AActor>(nullptr, TEXT("MyNamedActor"));
// 타입을 기준으로 액터를 찾습니다(UWorld 오브젝트를 필요로 함).
for (TActorIterator<AMyActor> It(GetWorld()); It; ++It)
{
AMyActor* MyActor = *It;
// ...
}
블루프린트

언리얼 엔진 C++
// 타입을 기준으로 UObjects를 찾습니다.
for (TObjectIterator<UMyObject> It; It; ++it)
{
UMyObject* MyObject = *It;
// ...
}
// 태그를 기준으로 액터를 찾습니다(ActorComponents에서도 작동함, 대신 TObjectIterator 사용).
for (TActorIterator<AActor> It(GetWorld()); It; ++It)
{
AActor* Actor = *It;
if (Actor->ActorHasTag(FName(TEXT("Mytag"))))
{
// ...
}
}
블루프린트

GameObject/액터에 태그 추가하기
Unity C#
MyGameObject.tag = "MyTag";
언리얼 엔진 C++
// 액터는 여러 태그를 가질 수 있습니다.
MyActor.Tags.AddUnique(TEXT("MyTag"));
블루프린트

MonoBehaviour/ActorComponent에 태그 추가하기
Unity C#
// 어태치된 GameObject의 태그를 변경합니다.
MyComponent.tag = "MyTag";
언리얼 엔진 C++
// 컴포넌트는 자체 태그 배열을 갖습니다.
MyComponent.ComponentTags.AddUnique(TEXT("MyTag"));
GameObject/액터와 MonoBehaviour/ActorComponent 간에 태그 비교하기
Unity C#
if (MyGameObject.CompareTag("MyTag"))
{
// ...
}
// 어태치된 GameObject의 태그를 확인합니다.
if (MyComponent.CompareTag("MyTag"))
{
// ...
}
언리얼 엔진 C++
// 액터에 이 태그가 있는지 확인합니다.
if (MyActor->ActorHasTag(FName(TEXT("MyTag"))))
{
// ...
}
블루프린트

언리얼 엔진 C++
// ActorComponent에 이 태그가 있는지 확인합니다.
if (MyComponent->ComponentHasTag(FName(TEXT("MyTag"))))
{
// ...
}
블루프린트

피직스: RigidBody vs. 프리미티브 컴포넌트
Unity에서는 어떤 GameObject 피직스 특성을 제공하든 먼저 RigidBody 컴포넌트를 제공해야 합니다.
언리얼 엔진에서는 어떤 프리미티브 컴포넌트(C++의 UPrimitiveComponent
)든 피지컬 오브젝트가 될 수 있습니다. 일반적인 프리미티브 컴포넌트에는 다음이 포함됩니다.
- 캡슐, 스피어, 박스 등의 셰이프(Shape) 컴포넌트
- 스태틱 메시(Static Mesh) 컴포넌트
- 스켈레탈 메시(Skeletal Mesh) 컴포넌트
Unity에서는 콜리전과 시각화 역할을 별도의 컴포넌트로 분리하는 것과 달리, 언리얼 엔진에서는 '잠재적 피지컬'과 '잠재적 표시' 개념을 단일 프리미티브(Primitive) 컴포넌트에 통합합니다. 월드에 지오메트리가 있고, 피지컬 렌더링 또는 인터랙션 가능한 모든 컴포넌트는 PrimitiveComponent
의 서브클래스입니다.
레이어 vs 채널
언리얼 엔진의 콜리전 채널은 Unity의 레이어에 해당합니다. 자세한 내용은 콜리전 필터링을 참고하세요.
레이캐스트 vs. 레이트레이스
Unity C#
GameObject FindGOCameraIsLookingAt()
{
Vector3 Start = Camera.main.transform.position;
Vector3 Direction = Camera.main.transform.forward;
float Distance = 100.0f;
int LayerBitMask = 1 << LayerMask.NameToLayer("Pawn");
RaycastHit Hit;
bool bHit = Physics.Raycast(Start, Direction, out Hit, Distance, LayerBitMask);
if (bHit)
{
return Hit.collider.gameObject;
}
return null;
}
언리얼 엔진 C++
APawn* AMyPlayerController::FindPawnCameraIsLookingAt()
{
// 이를 사용하여 트레이스와 관련된 다양한 프로퍼티를 커스터마이징할 수 있습니다.
FCollisionQueryParams Params;
// 플레이어의 폰을 무시합니다.
Params.AddIgnoredActor(GetPawn());
// 히트 결과가 라인 트레이스에 의해 채워집니다.
FHitResult Hit;
// 카메라로부터 레이캐스트하며, ECC_Pawn 콜리전 채널에 있는 폰과만 충돌합니다.
FVector Start = PlayerCameraManager->GetCameraLocation();
FVector End = Start + (PlayerCameraManager->GetCameraRotation().Vector() * 1000.0f);
bool bHit = GetWorld()->LineTraceSingle(Hit, Start, End, ECC_Pawn, Params);
if (bHit)
{
// Hit.Actor는 트레이스가 히트한 액터에 대한 위크 포인터를 포함합니다.
return Cast<APawn>(Hit.Actor.Get());
}
return nullptr;
}
블루프린트
이미지를 클릭하면 전체 크기로 표시됩니다.
트리거 볼륨
Unity C#
public class MyComponent : MonoBehaviour
{
void Start()
{
collider.isTrigger = true;
}
void OnTriggerEnter(Collider Other)
{
// ...
}
void OnTriggerExit(Collider Other)
{
// ...
}
}
언리얼 엔진 C++
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
// 내 트리거 컴포넌트
UPROPERTY()
UPrimitiveComponent* Trigger;
AMyActor()
{
Trigger = CreateDefaultSubobject<USphereComponent>(TEXT("TriggerCollider"));
// 이벤트가 발동하려면 두 콜라이더가 모두 이를 true로 설정해야 합니다.
Trigger.bGenerateOverlapEvents = true;
// 콜라이더의 콜리전 모드를 설정합니다.
// 이 모드는 레이캐스트와 스윕 및 오버랩에 대해서만 콜라이더를 활성화합니다.
Trigger.SetCollisionEnabled(ECollisionEnabled::QueryOnly);
}
virtual void NotifyActorBeginOverlap(AActor* Other) override;
virtual void NotifyActorEndOverlap(AActor* Other) override;
};
언리얼 엔진 블루프린트

콜리전 반응을 구성하는 것과 관련된 자세한 내용은 콜리전 페이지를 참고하세요.
키네마틱 리지드바디
Unity C#
public class MyComponent : MonoBehaviour
{
void Start()
{
rigidbody.isKinematic = true;
rigidbody.velocity = transform.forward * 10.0f;
}
}
언리얼 엔진에서는 콜리전 컴포넌트와 리지드바디 컴포넌트가 하나입니다. 이것의 베이스 클래스는 UPrimitiveComponent
로, USphereComponent
와 UCapsuleComponent
같은 필요한 서브클래스를 다수 갖추고 있습니다.
언리얼 엔진 C++
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
UPROPERTY()
UPrimitiveComponent* PhysicalComp;
AMyActor()
{
PhysicalComp = CreateDefaultSubobject<USphereComponent>(TEXT("CollisionAndPhysics"));
PhysicalComp->SetSimulatePhysics(false);
PhysicalComp->SetPhysicsLinearVelocity(GetActorRotation().Vector() * 100.0f);
}
};
입력 이벤트
Unity C#
public class MyPlayerController : MonoBehaviour
{
void Update()
{
if (Input.GetButtonDown("Fire"))
{
// ...
}
float Horiz = Input.GetAxis("Horizontal");
float Vert = Input.GetAxis("Vertical");
// ...
}
}
언리얼 엔진 C++:
UCLASS()
class AMyPlayerController : public APlayerController
{
GENERATED_BODY()
void SetupInputComponent()
{
Super::SetupInputComponent();
InputComponent->BindAction("Fire", IE_Pressed, this, &AMyPlayerController::HandleFireInputEvent);
InputComponent->BindAxis("Horizontal", this, &AMyPlayerController::HandleHorizontalAxisInputEvent);
InputComponent->BindAxis("Vertical", this, &AMyPlayerController::HandleVerticalAxisInputEvent);
}
void HandleFireInputEvent();
void HandleHorizontalAxisInputEvent(float Value);
void HandleVerticalAxisInputEvent(float Value);
};
블루프린트

'프로젝트 세팅(Project Settings)'의 입력 프로퍼티는 다음과 같습니다.

언리얼 엔진 프로젝트의 입력을 구성하는 방법에 대한 자세한 내용은 입력 페이지를 참고하세요.