Showing

[Unreal] 액터 C++ 클래스와 블루프린트 호환 및 컴포넌트의 이해 본문

Unreal

[Unreal] 액터 C++ 클래스와 블루프린트 호환 및 컴포넌트의 이해

RabbitCode 2023. 11. 24. 16:03

 

컴포넌트와 액터

언리얼에서 월드는 아래 4가지를 제공한다.

  • 레벨
  • 액터 : 각종 컴포넌트를 붙여 다양한 기능의 구현체가 될 수 있다(시각, 물리...etc)
  • 시간(가상 공간에서 흐르는 시간이므로 조절 가능)
  • 물리(액터들 사이의 작용, 콜리전 정보 이용)

언리얼은 컴포넌트 구조를 채택하여 액터는 여러 개의 컴포넌트를 가질 수 있고, 대표되는 하나의 컴포넌트를 루트 컴포넌트라고 한다.(스태틱메쉬, 스켈레탈 메쉬, 박스(콜리전), 무브먼트, 카메라, 파티클, 라이트 컴포넌트 등..)

내가 소유한 언리얼 오브젝트 : 서브 오브젝트

나의 주인이 되는 언리얼 오브젝트 : 아우

 

블루프린트로 만든 컴포넌트 구조와 똑같이 C++로 작성할 수 있다.

 

우선 액터로 c++ 클래스를 생성해준다.

헤더파일

#pragma once

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

UCLASS()
class TRACESOFTHEFOX_API ASTorch : public AActor
{
	GENERATED_BODY()
	
public:	
	ASTorch();
private:
	UPROPERTY(VisibleAnywhere, Category="Troch")
	TObjectPtr<class UBoxComponent> BoxComponent;

	UPROPERTY(VisibleAnywhere, Category = "Troch")
	TObjectPtr<class UStaticMeshComponent> BodyStaticMeshComponent;

	UPROPERTY(VisibleAnywhere, Category = "Troch")
	TObjectPtr<class UPointLightComponent> PointLightComponent;

	UPROPERTY(VisibleAnywhere, Category = "Troch")
	TObjectPtr<class UParticleSystemComponent> ParticleSystemComponent;
};

 

필요한 컴포넌트들을 전부 달아준 코드 상황

UPROPERTY()는 언리얼 오브젝트 클래스의 속성이라는 뜻

VisibleAnywhere은 에디터에서 수정할 수 있다는 뜻으로, 사실 TObjectPtr 포인터는 주소값을 알면 값을 바꿀 수 있다는 사실을 이용한 예약어이다. (단, 포인터 타입은 VisibleAnywhere로 충분하지만 값 타입은 EditAnywhere로 해주어야 한다)

BP_class에서만 바꾸고 싶다면 EditDefaultOnly 인스턴스에서만 바꾸고 싶다면 EditInstanceOnly

참고로 UE4 스타일

UE5 스타일 : 언리얼 측에서 TObjectPtr를 쓸 것을 권장했기 때문이다.(기능 추가와 안전성이 올라갔다고 한다)

 

cpp


#include "WorldStatics/STorch.h"
#include "Components/BoxComponent.h"
#include "Components/PointLightComponent.h"
#include "Particles/ParticleSystemComponent.h"

ASTorch::ASTorch()
{
	PrimaryActorTick.bCanEverTick = false;

	BoxComponent = CreateDefaultSubobject<UBoxComponent>(TEXT("RootBoxComponent"));
	SetRootComponent(BoxComponent);

	BodyStaticMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("My Static Mesh"));
	BodyStaticMeshComponent->SetupAttachment(GetRootComponent());
	BodyStaticMeshComponent->SetRelativeLocation(FVector(0.f, 0.f, -30.f));

	PointLightComponent = CreateDefaultSubobject<UPointLightComponent>(TEXT("PointLight"));
	PointLightComponent->SetupAttachment(GetRootComponent());
	PointLightComponent->SetRelativeLocation(FVector(0.f, 0.f, 500.f));

	ParticleSystemComponent = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("ParticleSystem"));
	ParticleSystemComponent->SetupAttachment(GetRootComponent());
	ParticleSystemComponent->SetRelativeLocation(FVector(0.f, 0.f, 500.f));
}

 

PrimaryActorTick.bCanEverTick = false; tick 함수는 부하가 많기 때문에 최대한 로직을 안 넣기 위해 노력해야 하며, 아예 tick 함수를 쓰지 않을 경우에는 false로 바꾸어준다.

 

CreateDefaultSubobject<자료형> 내가 가질 특정 <자료형>인  Subobject를 만든다. 바로 뒤에 붙는 해시값 (TEXT("RootBoxComponent")); 등으로 유니크한 id가 만들어지는데 웬만하면 변수명으로 쓰면 된다.

 

참고로 루트 컴포넌트가 된 컴포넌트는 액터의 방향, 위치와 동일하므로 블루프린트 상에서 볼 수 없다. 따라서 컴포넌트의 방향과 위치를 보고 싶다면 Get Actor Rotation() Get Actor Location() 하면 된다! 액터를 수정하는 것은 곧 루트 컴포넌트를 수정하는 것과 같다.

 

BodyStaticMeshComponent->SetupAttachment(GetRootComponent()); GetRootComponent의 자식으로 들어간다

 

또한 C++에서 Location을 작성해놓으면 에디터 상에서의 디폴트 값이 된다.(뒤로가기 버튼이 생기지 않는다)

 

기존에 만들어둔 블루프린트의 부모를 방금 만든 c++ 클래스로 바꾸어줄 수 있다!

넣자마자 컴포넌트창에 변화가 생긴다. Edit in C++이라는 표시와 함께 c++에서 작성했던 컴포넌트들이 추가되었다.

따라서 블루프린트에서 만든 것을 작성하면 아래와 같이 초기화된다.

에셋 디폴트 지정

에셋 경로를 하드코딩으로 지정해줄 수 있다.

아트 팀에서 경로를 많이 변경할 수 있기 때문에 소속 컨벤션에 따라 선택 사항이다.

#include "WorldStatics/STorch.h"
#include "Components/BoxComponent.h"
#include "Components/PointLightComponent.h"
#include "Particles/ParticleSystemComponent.h"

ASTorch::ASTorch()
{
	PrimaryActorTick.bCanEverTick = false;

	BoxComponent = CreateDefaultSubobject<UBoxComponent>(TEXT("RootBoxComponent"));
	SetRootComponent(BoxComponent);

	BodyStaticMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("My Static Mesh"));
	BodyStaticMeshComponent->SetupAttachment(GetRootComponent());
	BodyStaticMeshComponent->SetRelativeLocation(FVector(0.f, 0.f, -30.f));
	static ConstructorHelpers::FObjectFinder<UStaticMesh> BodyStaticMesh(TEXT("/Script/Engine.StaticMesh'/Game/StarterContent/Architecture/Pillar_50x500.Pillar_50x500'"));

	if (BodyStaticMesh.Succeeded())
	{
		BodyStaticMeshComponent->SetStaticMesh(BodyStaticMesh.Object);
	}

	PointLightComponent = CreateDefaultSubobject<UPointLightComponent>(TEXT("PointLight"));
	PointLightComponent->SetupAttachment(GetRootComponent());
	PointLightComponent->SetRelativeLocation(FVector(0.f, 0.f, 500.f));

	ParticleSystemComponent = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("ParticleSystem"));
	ParticleSystemComponent->SetupAttachment(GetRootComponent());
	ParticleSystemComponent->SetRelativeLocation(FVector(0.f, 0.f, 500.f));
}

아래와 같이 다양한 변주를 넣어줄 수도 있을 것이다.(디폴트로)

결국 코드로 작성한다는 것은 어떠한 설정을 디폴트화 한다는 것이다.

	meshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("static meshComp"));
	meshComp->SetupAttachment(RootComponent);

	static ConstructorHelpers::FObjectFinder<UStaticMesh> SphereMeshAsset(TEXT("/Game/StarterContent/Shapes/Shape_Sphere.Shape_Sphere"));//추가

	if (SphereMeshAsset.Succeeded())
	{
		meshComp->SetStaticMesh(SphereMeshAsset.Object);
		meshComp->SetRelativeScale3D(FVector(0.1f, 0.1f, 0.1f));
		meshComp->SetRelativeLocation(FVector(0.0f, 0.0f, -5.0f));
	}

 

파티클도 마찬가지로 처리해줄 수 있는데 함수명이 다른 점만 염두에 두면 될 것이다.

#include "WorldStatics/STorch.h"
#include "Components/BoxComponent.h"
#include "Components/PointLightComponent.h"
#include "Particles/ParticleSystemComponent.h"

ASTorch::ASTorch()
{
	PrimaryActorTick.bCanEverTick = false;

	BoxComponent = CreateDefaultSubobject<UBoxComponent>(TEXT("RootBoxComponent"));
	SetRootComponent(BoxComponent);

	BodyStaticMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("My Static Mesh"));
	BodyStaticMeshComponent->SetupAttachment(GetRootComponent());
	BodyStaticMeshComponent->SetRelativeLocation(FVector(0.f, 0.f, -30.f));
	static ConstructorHelpers::FObjectFinder<UStaticMesh> BodyStaticMesh(TEXT("/Script/Engine.StaticMesh'/Game/StarterContent/Architecture/Pillar_50x500.Pillar_50x500'"));

	if (BodyStaticMesh.Succeeded())
	{
		BodyStaticMeshComponent->SetStaticMesh(BodyStaticMesh.Object);
	}

	PointLightComponent = CreateDefaultSubobject<UPointLightComponent>(TEXT("PointLight"));
	PointLightComponent->SetupAttachment(GetRootComponent());
	PointLightComponent->SetRelativeLocation(FVector(0.f, 0.f, 500.f));

	ParticleSystemComponent = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("ParticleSystem"));
	ParticleSystemComponent->SetupAttachment(GetRootComponent());
	ParticleSystemComponent->SetRelativeLocation(FVector(0.f, 0.f, 500.f));
	static ConstructorHelpers::FObjectFinder<UParticleSystem> FireParticle(TEXT("/Script/Engine.ParticleSystem'/Game/StarterContent/Particles/P_Fire.P_Fire'"));

	if (FireParticle.Succeeded())
	{
		ParticleSystemComponent->SetTemplate(FireParticle.Object);
	}
}

초속 회전 시키기

#pragma once

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

UCLASS()
class TRACESOFTHEFOX_API ASTorch : public AActor
{
	GENERATED_BODY()
protected:
	virtual void BeginPlay() override;
public:
	virtual void Tick(float DeltaSeconds) override;
	
public:	
	ASTorch();
private:
	UPROPERTY(VisibleAnywhere, Category="Troch")
	TObjectPtr<class UBoxComponent> BoxComponent;

	UPROPERTY(VisibleAnywhere, Category = "Troch")
	TObjectPtr<class UStaticMeshComponent> BodyStaticMeshComponent;

	UPROPERTY(VisibleAnywhere, Category = "Troch")
	TObjectPtr<class UPointLightComponent> PointLightComponent;

	UPROPERTY(VisibleAnywhere, Category = "Troch")
	TObjectPtr<class UParticleSystemComponent> ParticleSystemComponent;

	UPROPERTY(EditDefaultsOnly, Category = "Troch")
	float RotationSpeed;
};

#include "WorldStatics/STorch.h"
#include "Components/BoxComponent.h"
#include "Components/PointLightComponent.h"
#include "Particles/ParticleSystemComponent.h"

void ASTorch::BeginPlay()
{
	Super::BeginPlay();
	RotationSpeed = 100.0f;
}

void ASTorch::Tick(float DeltaSeconds)
{
	Super::Tick(DeltaSeconds);
	AddActorWorldRotation(FRotator(0.f, RotationSpeed * DeltaSeconds, 0.f));
}

ASTorch::ASTorch()
{
	PrimaryActorTick.bCanEverTick = true;

	BoxComponent = CreateDefaultSubobject<UBoxComponent>(TEXT("RootBoxComponent"));
	SetRootComponent(BoxComponent);

	BodyStaticMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("My Static Mesh"));
	BodyStaticMeshComponent->SetupAttachment(GetRootComponent());
	BodyStaticMeshComponent->SetRelativeLocation(FVector(0.f, 0.f, -30.f));
	static ConstructorHelpers::FObjectFinder<UStaticMesh> BodyStaticMesh(TEXT("/Script/Engine.StaticMesh'/Game/StarterContent/Architecture/Pillar_50x500.Pillar_50x500'"));

	if (BodyStaticMesh.Succeeded())
	{
		BodyStaticMeshComponent->SetStaticMesh(BodyStaticMesh.Object);
	}

	PointLightComponent = CreateDefaultSubobject<UPointLightComponent>(TEXT("PointLight"));
	PointLightComponent->SetupAttachment(GetRootComponent());
	PointLightComponent->SetRelativeLocation(FVector(0.f, 0.f, 500.f));

	ParticleSystemComponent = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("ParticleSystem"));
	ParticleSystemComponent->SetupAttachment(GetRootComponent());
	ParticleSystemComponent->SetRelativeLocation(FVector(0.f, 0.f, 500.f));
	static ConstructorHelpers::FObjectFinder<UParticleSystem> FireParticle(TEXT("/Script/Engine.ParticleSystem'/Game/StarterContent/Particles/P_Fire.P_Fire'"));

	if (FireParticle.Succeeded())
	{
		ParticleSystemComponent->SetTemplate(FireParticle.Object);
	}
}

 

URotatingMovementComponent 안에는 이미 Tick이 있기 때문에 Tick을 제거 해도 무방하다

#pragma once

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

UCLASS()
class TRACESOFTHEFOX_API ASTorch : public AActor
{
	GENERATED_BODY()
protected:
	virtual void BeginPlay() override;
/*
public:
	virtual void Tick(float DeltaSeconds) override;
*/
public:	
	ASTorch();
private:
	UPROPERTY(VisibleAnywhere, Category="Troch")
	TObjectPtr<class UBoxComponent> BoxComponent;

	UPROPERTY(VisibleAnywhere, Category = "Troch")
	TObjectPtr<class UStaticMeshComponent> BodyStaticMeshComponent;

	UPROPERTY(VisibleAnywhere, Category = "Troch")
	TObjectPtr<class UPointLightComponent> PointLightComponent;

	UPROPERTY(VisibleAnywhere, Category = "Troch")
	TObjectPtr<class UParticleSystemComponent> ParticleSystemComponent;

	UPROPERTY(EditDefaultsOnly, Category = "Troch")
	float RotationSpeed;

	UPROPERTY(VisibleAnywhere, Category = "Troch")
	TObjectPtr<class URotatingMovementComponent> RotationMovementComponent;
};
#include "WorldStatics/STorch.h"
#include "Components/BoxComponent.h"
#include "Components/PointLightComponent.h"
#include "Particles/ParticleSystemComponent.h"
#include "GameFramework/RotatingMovementComponent.h"


/*
void ASTorch::Tick(float DeltaSeconds)
{
	Super::Tick(DeltaSeconds);
	AddActorWorldRotation(FRotator(0.f, RotationSpeed * DeltaSeconds, 0.f));
}
*/
ASTorch::ASTorch()
{
	PrimaryActorTick.bCanEverTick = false;

	BoxComponent = CreateDefaultSubobject<UBoxComponent>(TEXT("RootBoxComponent"));
	SetRootComponent(BoxComponent);

	BodyStaticMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("My Static Mesh"));
	BodyStaticMeshComponent->SetupAttachment(GetRootComponent());
	BodyStaticMeshComponent->SetRelativeLocation(FVector(0.f, 0.f, -30.f));
	static ConstructorHelpers::FObjectFinder<UStaticMesh> BodyStaticMesh(TEXT("/Script/Engine.StaticMesh'/Game/StarterContent/Architecture/Pillar_50x500.Pillar_50x500'"));

	if (BodyStaticMesh.Succeeded())
	{
		BodyStaticMeshComponent->SetStaticMesh(BodyStaticMesh.Object);
	}

	PointLightComponent = CreateDefaultSubobject<UPointLightComponent>(TEXT("PointLight"));
	PointLightComponent->SetupAttachment(GetRootComponent());
	PointLightComponent->SetRelativeLocation(FVector(0.f, 0.f, 500.f));

	ParticleSystemComponent = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("ParticleSystem"));
	ParticleSystemComponent->SetupAttachment(GetRootComponent());
	ParticleSystemComponent->SetRelativeLocation(FVector(0.f, 0.f, 500.f));
	static ConstructorHelpers::FObjectFinder<UParticleSystem> FireParticle(TEXT("/Script/Engine.ParticleSystem'/Game/StarterContent/Particles/P_Fire.P_Fire'"));

	if (FireParticle.Succeeded())
	{
		ParticleSystemComponent->SetTemplate(FireParticle.Object);
	}
	RotationMovementComponent = CreateDefaultSubobject<URotatingMovementComponent>(TEXT("RotatingMovementComponent"));
}

void ASTorch::BeginPlay()
{
	Super::BeginPlay();
	RotationSpeed = 100.0f;
	RotationMovementComponent->RotationRate = FRotator(0.f, RotationSpeed, 0.f);
}