본문 바로가기

Unreal Engine 4/C++

<Unreal C++> 4 - Actor Spawn

 

필요한 개념

 


*기본 Mesh 사용하기)
콘텐츠 브라우저에서 사용할 Mesh에 우클릭->레퍼런스 복사
ex) StaticMesh'/Game/Meshes/M_Cube.M_Cube'
자료형'경로'나온다.
StaticMesh앞에 U자나 A자 붙여서 쓰면된다.
UStaticMesh
Game/ : 콘텐츠 폴더를 의미
/Meshes/M_Cube
.M_Cube는 식별자
자료형과 식별자 빼고 나머지 부분만 써도 된다.
근데 모호하면 언리얼에서 에러를 내어서
StaticMesh'/Game/Meshes/M_Cube.M_Cube' 다 명시를 해줘야 한다.
ex)StaticMesh'/Engine/Meshes/M_Cube.M_Cube'이라면 Engine은 엔진 콘텐츠 폴더를 의미


콘텐츠 브라우저 -> StaticMesh-> 우클릭 -> 레퍼런스 복사
기본값을 불러온다.
StaticMesh'/Game/Meshes/M_Cube.M_Cube'

Constructor : 생성자를 의미(도움될 클래스를 모아놓은 구조체)
ConstructorHelpers -> 생성자에서만 사용가능
FObjectFinder : 오브젝트를 찾아주는 구조체
<> : 어떤 자료형을 찾을 것이냐?
레퍼런스 앞에 붙은 자료형을 찾는다.
ConstructorHelpers::FObjectFinder<UStaticMesh> 자료형이 됨
L"" -> 멀티바이트 코드 영문 1바이트 한글 2바이트 표기
TEXT() -> 유니코드, 영문이든, 한글이든 2바이트(너무길어서 L""을 씀)
원래 언리얼은 TEXT()를 쓰는 것이 정석
() 안에 있는 경로를 mesh 변수에 넣어준다.
구조체에 이너 구조체 형태의 템플릿이다.
ConstructorHelpers::FObjectFinder<UStaticMesh> mesh(L"StaticMesh'/Game/Meshes/M_Cube.M_Cube'");
부른 오브젝트는 .Object에 들어가있다.
BP에 있는 Mesh에 세팅하라
제대로 불려졌는지(Succeeded())
if (mesh.Succeeded())
Mesh->SetStaticMesh(mesh.Object);



적용해주면 C++ 클래스에도 기본으로 Cube StaticMesh가 적용되고 이를 상속받는 BP에도 적용된다.



월드에 3개의 구역으로 나누어
왼쪽은 건슈팅 모드 테스트용, 가운데는 충돌 관련, 오른쪽은 C작업한거 테스트용

코드에서만 사용할클래스(프로그래머만 사용할 내부 것들)은
UObject로 상속받지 않은 클래스로 제작
현재에서는 Cpp 파일은 필요없으므로 직접 헤더파일을 생성한다.(VisualStudio에서 생성)
C++ 작업하는 루트인(Source/프로젝트명)에서 저장해줘야 한다.(VisualStudio에서 생성시)
폴더 명과 필터명을 일치시킨다.(실무에서도 동일하게 한다.)

// 클래스 앞에는 언리얼 네임스페이스가 붙는다.(ex UONLINE_03_CPP_API)
// 언리얼 네임스페이스 : 솔루션 내에 많은 프로젝트들이 생기게 되므로
// 어느 프로젝트에 소속된 소스인지 구분해주기 위해 사용
// 어떤 프로젝트의 클래스냐
class UONLINE_03_CPP_API CHelpers
{
public:
// FString에는 ""넣어도 된다.
// 문제는 들어올때는 L""(문자열이고)
// 받는 거는 FString이다 그러면 *포인터를 붙여주면 된다.
// *InPath하면 FString -> 문자열이 된다.
// FString의 *를 붙이면 문자열 상수(L"" or TEXT(""))로 리턴된다.
// 마치 string a; a.c_str()(문자열의 시작 주소)과 같다.
// verifyf : assert와 동일, 테스트가 안되면 에디터가 죽고, 경고문자가뜸 -> 실제 게임 서비스시에 생략이 됨
// 값 정상 적 체크를 위해 사용(verifyf(에디터에서), check(게임 실행 내에서), anyshare? 도 있다.)
// T**인 이유는 들어오는 *의 주소가 들어감
// 보통 2차 포인터 사용하는 경우는 현재 값을 어떤 포인터 변수로 줘서 따로 넘겨서, 초기화 해서 리턴해주는 용도
template<typename T>
static void GetAsset(T** OutObject, FString InPath)
{
ConstructorHelpers::FObjectFinder<T> asset(*InPath);
// 제대로 만들어졌는지 체크
verifyf(asset.Succeeded(), L"asset.Succeeded()");

// OutObject가 가리키는 공간인 포인터 변수의 공간에 값을 넣음.
*OutObject = asset.Object;
}
};


요약
ConstructorHelpers::FObjectFinder
-Template
-생성자에서만 동작

ContentPath
-/Game : 게임의 컨턴츠
-/Engine : 엔진의 컨텐츠

상속 구조 쓸때 부모이름을 남겨놓음(그래야 누구로 부터 상속 받았는지 알 수 있다.)


#include "Global.h"하면 에러가 난다. 이유는?
언리얼 에진의 기본 설정은 상대로 헤더의 경로를 입력하도록 되어 있다.
우리는 편의상 언리얼 엔진의 기본 설정을 바꿔 루트 경로를 기준으로 입력할 수 있도록 바꿔났다.
(4.25에서 부터는 상대경로로 된다.)

<프로젝트명.Build.cs 수정하면 됨>
ModuleDirectiory 디렉터리는 Sources 폴더 밑 프로젝트 폴더
이 폴더를 기준으로 헤더를 불러들일 수 있도록 세팅
publicIncludePaths.Add(ModuleDirectiory);

Tip) template
template<>은 inline과 비슷해서 따로 점프해서 하지 않음, 그대로 붙여넣음


Tip) 블루프린트로 Material 만들시 용어 참고

Roughness (러프니스) : 입력은 말 그대로 머티리얼 표면의 거칠고 부드러운 정도를 제어합니다. 
거친 재질에 반사된 빛은 부드러운 재질보다 여러 방향으로 퍼지므로, 리플렉션이 얼마나 희미하거나 선명한지, 
아니면 스페큘러 하이라이트가 얼마나 퍼져있는지 모여있는지로 확인됩니다. 
러프니스가 0 인(부드러운) 경우 거울 반사이며, 1 인(거친) 경우 완전히 무광 또는 난반사(diffuse) 입니다.
러프니스라는 프로퍼티는 흔히 오브젝트에 매핑시켜 표면에 대부분 물리적 베리에이션을 더합니다.

Metallic (메탈릭) : 입력은 말 그대로 표면의 "금속성" 을 제어합니다. 비금속은 메탈릭 값이 0 이며, 금속은 1 입니다. 
순수한 금속이나 순수한 돌, 순수한 플라스틱처럼 순수 표면의 경우, 이 값은 0 아니면 1 이며, 그 중간 값은 아닙니다. 
부식되었거나 먼지 또는 녹슨 금속같은 혼합 표면을 만들 때는, 0 과 1 사이 값이 필요할 수도 있습니다.

만든 액터들을 스폰시킬 액터 클래스
SpawnClass 생성

공통적인 값을 가지도록
UClass* class;
UClass : 클래스 타입을 변수로 다루겠다.
UClass로 하니깐 모든 클래스들이 다 나타남
-> 지정한 클래스 타입에 대한 액터를 화면에 등장시키기 위해 사용
우리가 스폰시킬 거는 SpawnActor를 상속 받은 애들이다.
TSubclassOf : 특정 클래스 만을 제한시키는 것
<>안에 들어가는 것 이하로 제한해서 쓸 수 있음
C에서는 없는 기능인데, 언리얼에서는 리플렉션이라고 부름
리플렉션(reflection) : 자료형의 타입을 변수로 다룰 수 있도록 해주는 기능
ex) 열거형을 사용했을때, 그것을 문자열로 사용하고 싶다면...
C에서는 None(0), attack(1)을 문자열로 사용불가하지만, 언리얼에서는 가능 -> 리플렉션
UClass <-> TSubClassOf, TSubClassOf <-> UClass (서로간에 언제든지 바뀔 수 있다. 1 : 1)

UPROPERTY(EditDefaultsOnly)
TSubclassOf<class AC02_SpawnActor> SpawnClass;

이렇게 하면 BP변수에 AC02_SpawnActor를 상속 받은 BP와 해당 클래스만 보인다.

transform.SetLocation : 트랜스폼의 위치를 설정
SetActorLocation() : 액터의 위치를 설정


*Tip) 에러가 걸리면 에디터가 꺼지는 증상이 있다.
그래서 가끔 많이 바뀌었다고 느끼면 컴파일 후에 에디터에서 플레이 클릭하면 독립형 게임 플레이를 가끔 해보는 게 좋다.
그다음에 선택된 뷰포트로 하는게 좋다.
이유는? 별도의 프로그램으로 실행되었으니까 에러가 걸려도 독립형 게임 창만 꺼진다. 에디터가 꺼지지는 않음

 

 

 

 

 

C02_SpawnActor.h


더보기
#pragma once

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

UCLASS()
class UONLINE_03_CPP_API AC02_SpawnActor : public AActor
{
	GENERATED_BODY()

protected:

	// BP에 컴포넌트 추가->StaticMesh가 UStaticMeshComponent이다.
	// U는 UObject에서 상속 받은 애다.(AActor를 거치지 않고 상속 받은 클래스)
	UPROPERTY(VisibleDefaultsOnly)
		class UStaticMeshComponent* Mesh;
	
public:	
	AC02_SpawnActor();

protected:
	virtual void BeginPlay() override;
};

 

 

 

 

 

 

 

C02_SpawnActor.cpp


더보기
#include "C02_SpawnActor.h"
#include "Global.h"
#include "Components/StaticMeshComponent.h"

AC02_SpawnActor::AC02_SpawnActor()
{
	// CreateDefaultSubobject : 실제 생성 함수
	// <어떤 자료형 생성> 1: 컴포넌트이름(변수명과 일치해야 관리편함)
	Mesh = CreateDefaultSubobject<UStaticMeshComponent>("Mesh");
	// BP는 자동으로 SceneComponent라고 Root컴포넌트가 생겨있다.
	// C에서는 지정해줘야 한다.
	// 루트로 쓰겠다.
	RootComponent = Mesh;

	// 콘텐츠 브라우저 -> StaticMesh-> 우클릭 -> 레퍼런스 복사
	// 기본값을 불러온다.
	//StaticMesh'/Game/Meshes/M_Cube.M_Cube'
	
	// Constructor : 생성자를 의미(도움될 클래스를 모아놓은 구조체)
	// ConstructorHelpers -> 생성자에서만 사용가능
	// FObjectFinder : 오브젝트를 찾아주는 구조체
	// <> : 어떤 자료형을 찾을 것이냐?
	// 레퍼런스 앞에 붙은 자료형을 찾는다.
	// ConstructorHelpers::FObjectFinder<UStaticMesh> 자료형이 됨
	// L"" -> 멀티바이트 코드 영문 1바이트 한글 2바이트 표기
	// TEXT() -> 유니코드, 영문이든, 한글이든 2바이트(너무길어서 L""을 씀)
	// 원래 언리얼은 TEXT()를 쓰는 것이 정석
	// () 안에 있는 경로를 mesh 변수에 넣어준다.
	// 구조체에 이너 구조체 형태의 템플릿이다.
	//ConstructorHelpers::FObjectFinder<UStaticMesh> mesh(L"StaticMesh'/Game/Meshes/M_Cube.M_Cube'");
	// 부른 오브젝트는 .Object에 들어가있다.
	// BP에 있는 Mesh에 세팅하라
	// 제대로 불려졌는지(Succeeded())
	//if (mesh.Succeeded())
	//	Mesh->SetStaticMesh(mesh.Object);

	UStaticMesh* mesh;
	// 받는 것이 String형이어서 L자 안붙여도 된다.
	CHelpers::GetAsset<UStaticMesh>(&mesh, "StaticMesh'/Game/Meshes/M_Cube.M_Cube'");
	Mesh->SetStaticMesh(mesh);

}

void AC02_SpawnActor::BeginPlay()
{
	Super::BeginPlay();
}

 

 

C02_SpawnActor_Sphere.h


더보기
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "01_Actor/C02_SpawnActor.h"
#include "C02_SpawnActor_Sphere.generated.h"


UCLASS()
class UONLINE_03_CPP_API AC02_SpawnActor_Sphere : public AC02_SpawnActor
{
	GENERATED_BODY()

public:
	AC02_SpawnActor_Sphere();
	
};

 

 

 

 

 

 

 

C02_SpawnActor_Sphere.cpp


더보기
#include "C02_SpawnActor_Sphere.h"
#include "Global.h"


AC02_SpawnActor_Sphere::AC02_SpawnActor_Sphere()
{
	UStaticMesh* mesh;
	CHelpers::GetAsset<UStaticMesh>(&mesh, "StaticMesh'/Game/Meshes/M_Sphere.M_Sphere'");
	Mesh->SetStaticMesh(mesh);
}

 

 

 

 

C02_SpawnActor_Cone.h


더보기
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "01_Actor/C02_SpawnActor.h"
#include "C02_SpawnActor_Cone.generated.h"


UCLASS()
class UONLINE_03_CPP_API AC02_SpawnActor_Cone : public AC02_SpawnActor
{
	GENERATED_BODY()

	// 언리얼은 아무것도 붙이지 않으면 접근 지정자가 public이 됨
public:
	AC02_SpawnActor_Cone();
	
};

 

 

 

 

C02_SpawnActor_Cone.cpp


더보기
#include "C02_SpawnActor_Cone.h"
#include "Global.h"


AC02_SpawnActor_Cone::AC02_SpawnActor_Cone()
{
	UStaticMesh* mesh;
	CHelpers::GetAsset<UStaticMesh>(&mesh, "StaticMesh'/Game/Meshes/M_Cone.M_Cone'");
	Mesh->SetStaticMesh(mesh);
}

 

 

 

C03_Spawner.h


더보기
#pragma once

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

// 만든 Actor들을 Spawn을 시킴

UCLASS()
class UONLINE_03_CPP_API AC03_Spawner : public AActor
{
	GENERATED_BODY()

private:
	// 공통적인 값을 가지도록
	// UClass* class;
	// UClass : 클래스 타입을 변수로 다루겠다.
	// UClass로 하니깐 모든 클래스들이 다 나타남
	// -> 지정한 클래스 타입에 대한 액터를 화면에 등장시키기 위해 사용
	// 우리가 스폰시킬 거는 SpawnActor를 상속 받은 애들이다.
	// TSubclassOf : 특정 클래스 만을 제한시키는 것
	// <>안에 들어가는 것 이하로 제한해서 쓸 수 있음
	// C에서는 없는 기능인데, 언리얼에서는 리플렉션이라고 부름
	// 리플렉션(reflection) : 자료형의 타입을 변수로 다룰 수 있도록 해주는 기능
	// ex) 열거형을 사용했을때, 그것을 문자열로 사용하고 싶다면...
	// C에서는 None(0), attack(1)을 문자열로 사용불가하지만, 언리얼에서는 가능 -> 리플렉션
	// UClass <-> TSubClassOf, TSubClassOf <-> UClass (서로간에 언제든지 바뀔 수 있다. 1 : 1)
	UPROPERTY(EditDefaultsOnly)
		TSubclassOf<class AC02_SpawnActor> SpawnClass[3];
	
public:	
	AC03_Spawner();

protected:
	virtual void BeginPlay() override;


	
private:
	// 스폰된 결과를 저장할 클래스
	// 전방선언 하는 이유는 헤더를 여기다 추가시키게되어서
	// 인클루드된 헤더를 무는애는 전파가 되어서 컴파일 속도가 느려짐(의미없는 게 물림)
	// 상속은 상관없음(무조건 물어야해서)
	// 그러나 외부 클래스들은 헤더를 여기다 추가하면 Spawner를 인클루드하면 그쪽에서 느려짐 
	// 그쪽에서는 이 헤더에서 인클루드한 것을 또 물게된다.
	class AC02_SpawnActor* SpawnActors[3];
};

 

C03_Spawner.cpp


더보기
#include "C03_Spawner.h"
#include "Global.h"
#include "C02_SpawnActor.h"

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

}

void AC03_Spawner::BeginPlay()
{
	Super::BeginPlay();

	// 언리얼에서는 int -> int32라고 씀(32비트->4바이트)
	// 왜 사용하는지? 언리얼은 PC게임용으로만 제작하는 것이 아닌
	// 멀티플랫폼을 지원하는 엔진이므로 해당 플랫폼의 자료형 크기로 자동으로
	// 변환해서 사용할 수 있도록 해준다.(ex)플스, 안드로이드, xBox)
	// 어떤 콘솔에서는 얘를 16비트로 취급되기도 한다.(CPU, 운영체제에 의해 결정)
	// 어떤 콘솔게임기는 int로 쓰면 16비트로 취급받을 수 있다.
	// 즉, int32는 어떤 플랫폼이든 int를 32비트로 사용하겠단 의미
	for (int32 i = 0; i < 3; i++)
	{
		FTransform  transform;
		FVector location = GetActorLocation();

		// 방법 1.(생성할때 동시에 세팅) -> 이게 좋음
		// 이렇게 바로 세팅도 가능
		// transform.SetLocation : 트랜스폼의 위치를 설정
		transform.SetLocation(FVector(location.X + i * 400, location.Y, location.Z));

		// SpawnActor() -> BP에 있는 SpawnActorFromClass() 함수와 같다.
		// 생성 시킴
		SpawnActors[i] = GetWorld()->SpawnActor<AC02_SpawnActor>(SpawnClass[i], transform);


		// 방법 2.(생성 한 후에 세팅)
		//// 400만큼 위치 옮겨줌
		//// SetActorLocation() : 액터의 위치를 설정
		//SpawnActors[i]->SetActorLocation(FVector(location.X + i * 400, location.Y, location.Z));
	}
}

 

 

 

 

 

CHelper.h


더보기
#pragma once

#include "CoreMinimal.h"
// ConstructorHelpers가 이 파일.h에 있다.
#include "UObject/ConstructorHelpers.h"

// 클래스 앞에는 언리얼 네임스페이스가 붙는다.(ex UONLINE_03_CPP_API)
// 언리얼 네임스페이스 : 솔루션 내에 많은 프로젝트들이 생기게 되므로
// 어느 프로젝트에 소속된 소스인지 구분해주기 위해 사용
// 어떤 프로젝트의 클래스냐
class UONLINE_03_CPP_API CHelpers
{
public:
	// FString에는 ""넣어도 된다.
	// 문제는 들어올때는 L""(문자열이고)
	// 받는 거는 FString이다 그러면 *포인터를 붙여주면 된다.
	// *InPath하면 FString -> 문자열이 된다.
	// FString의 *를 붙이면 문자열 상수(L"" or TEXT(""))로 리턴된다.
	// 마치 string a; a.c_str()(문자열의 시작 주소)과 같다.
	// verifyf : assert와 동일, 테스트가 안되면 에디터가 죽고, 경고문자가뜸 -> 실제 게임 서비스시에 생략이 됨
	// 값 정상 적 체크를 위해 사용(verifyf(에디터에서), check(게임 실행 내에서), anyshare? 도 있다.)
	// T**인 이유는 들어오는 *의 주소가 들어감
	// 보통 2차 포인터 사용하는 경우는 현재 값을 어떤 포인터 변수로 줘서 따로 넘겨서, 초기화 해서 리턴해주는 용도
	template<typename T>
	static void GetAsset(T** OutObject, FString InPath)
	{
		ConstructorHelpers::FObjectFinder<T> asset(*InPath);
		// 제대로 만들어졌는지 체크
		// verify : 검증만함
		// verifyf : 검증한다음에 포맷(에러가 났을때 우리가 원하는 형태로 값을 출력)
		verifyf(asset.Succeeded(), L"asset.Succeeded()");

		// OutObject가 가리키는 공간인 포인터 변수의 공간에 값을 넣음.
		*OutObject = asset.Object;
	}
};

 

 

Global.h


더보기
#pragma once

// 모아둠

#include "Utillites/CHelpers.h"

 

 

 

 

 

 

UOnline_03_Cpp.Build.cs


더보기
// Copyright Epic Games, Inc. All Rights Reserved.

using UnrealBuildTool;

public class UOnline_03_Cpp : ModuleRules
{
	public UOnline_03_Cpp(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
	
		// 모듈 라이브러리들
		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });

		PrivateDependencyModuleNames.AddRange(new string[] {  });

		// ModuleDirectiory 디렉터리는 Sources 폴더 밑 프로젝트 폴더
		// 이 폴더를 기준으로 헤더를 불러들일 수 있도록 세팅
		PublicIncludePaths.Add(ModuleDirectory);
	}
}

 

 

 

 

 

결과


스폰할 액터 설정
원하는 클래스의 BP들을 세팅해서 생성했다.

'Unreal Engine 4 > C++' 카테고리의 다른 글

<Unreal C++> 9 - PlayerInputComponent  (0) 2022.04.04
<Unreal C++> 8 - Player  (0) 2022.04.04
<Unreal C++> 7 - Material  (0) 2022.04.04
<Unreal C++> 2 - C++과 BP 통신  (0) 2022.03.30
<Unreal C++> 1 - 언리얼 설명 및 환경 구축  (0) 2022.03.30