스포너 Spawner
앞서 생성해 둔 C02_Mesh들을 스폰해 줄 스포너 액터를 생성해보자
신규 C++클래스 > Actor > C03_Spawner 생성
헤더
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "C03_Spawner.generated.h"
UCLASS()
class U03_BASIC_API AC03_Spawner : public AActor
{
GENERATED_BODY()
private:
// 블프에디터에서만 수정표시가능
UPROPERTY(EditDefaultsOnly, Category = "Settings")
TSubclassOf<class AC02_Mesh> SpawnClasses[3];
public:
AC03_Spawner();
protected:
virtual void BeginPlay() override;
};
TSubClassOf : UClass 유형의 안전성을 보장해 주는 템플릿 클래스 블루프린트 에디터에서
SpawnClasses배열에AC02_Mesh클래스 유형의 원소 3개를 넣어 줄 수 있게 선언
소스
#include "01_Spawn/C03_Spawner.h"
#include "Global.h"
#include "C02_Mesh.h"
AC03_Spawner::AC03_Spawner()
{
}
void AC03_Spawner::BeginPlay()
{
Super::BeginPlay();
for (int32 i = 0; i < 3; i++)
{
if (SpawnClasses[i] == nullptr)
continue;
FVector location = GetActorLocation();
FTransform transform;
transform.SetLocation(FVector(location.X, location.Y + i * 350, location.Z));
GetWorld()->SpawnActor<AC02_Mesh>(SpawnClasses[i], transform);
}
}
C03_Spawner의 Location에서 Y축 기준 350씩 떨어진 위치에 SpawnActor함수를 통해 액터들 스폰!
기존에 블프에서 해봤던 SpawnActorFromClass가 C++에선 SpawnActor로 활용된다
프로파일러 Profiler
C03_Spawner를 스폰관련 기본적인 작업을 마무리하고 두번째 C++클래스 디렉토리 02_Profiler를 생성해주자
프로젝트 작업 중 디버깅 오류확인용으로 PrintString이나 Log메세지 출력 함수를 편하게 사용할 수 있게 해줄 빈 C++ 클래스 CLog와 적용해 Test해 볼 액터 클래스C01_Log 생성
빈 C++ 클래스(직렬되지 않는 클래스) : 언리얼엔진에서 생성됐지만 에디터의 콘텐츠브라우저에 보이지 않는다
신규 C++클래스 생성 시 선택하는 부모클래스가 없는 상태이기 때문에 다른 액터나 컴포넌트클래스,등과 달리 최상위 클래스로부터 상속(직렬)되지 않는 클래스라 직렬되지 않는 클래스라고 한다
이게 시리얼라이즈 Serialize와 연관된 건지는 좀 더 알아봐야겠다
출력로그 Log
출력로그에 메세지를 출력해보자
Global.h에 CLog.h 포함
#include "Utilities/CLog.h"
- 기본 구현방법
- 헤더
#pragma once #include "CoreMinimal.h" class U03_BASIC_API CLog { public: static void Log(int32 InValue); } - 소스
#include "Utilities/CLog.h" void CLog::Log(int32 InValue) { GLog->Log("GamePlay", ELogVerbosity::Display, FString::FromInt(InValue)); }ELogVerbosity : 출력될 로그메세지의 특징 Enum - Fatal, Error, Warning, Display, Log, Verbose, VeryVerbose
Verbose는 ‘장황한’으로 해석되는데 로그메세지를 얼마나 장황하게 출력할지 선택하는것 같다ㅋㅋ - C01_Log 소스
void AC01_Log::BeginPlay() { Super::BeginPlay(); CLog::Log(10); }
- 헤더
- Macro 활용
출력할 InValue값을 여러 자료형으로 받을 수 있도록 각 자료형별로 함수 오버로딩을 해서 매크로 및static함수로 활용해보자- 헤더
#pragma once #include "CoreMinimal.h" #define LogLine() { CLog::Log(__FILE__, __FUNCTION__,__LINE__);} // __FILE__ : '현재'의 파일, 함수, 라인넘버 조회 가능 class U03_BASIC_API CLog { public: static void Log(int32 InValue); static void Log(float InValue); static void Log(const FString& InValue); // 인자를 const로 하면 전달하는 과정에서 수정편집이 불가하다 static void Log(const FVector& InValue); static void Log(const FRotator& InValue); static void Log(const UObject* InValue); // Object의 Name 출력 static void Log(const FString& InFileName, const FString& InFuncName, int32 InLineNo); // 디버깅 시 현재 파일, 함수, 라인 출력 }; - 소스
#include "Utilities/CLog.h" #include "Engine.h" DEFINE_LOG_CATEGORY_STATIC(GP,Display,All) void CLog::Log(int32 InValue) { //GLog->Log("GamePlay", ELogVerbosity::Display, FString::FromInt(InValue)); UE_LOG(GP, Display, L"%d", InValue); } void CLog::Log(float InValue) { UE_LOG(GP, Display, L"%f", InValue); } void CLog::Log(const FString& InValue) { UE_LOG(GP, Display, L"%s", *InValue); // string._cstr() = *str } void CLog::Log(const FVector& InValue) { UE_LOG(GP, Display, L"%s", *InValue.ToString()); } void CLog::Log(const FRotator& InValue) { UE_LOG(GP, Display, L"%s", *InValue.ToString()); } void CLog::Log(const UObject* InValue) { FString str; if (!!InValue) // !!: Is Not Nullptr) if(InValue != nullptr) 이거 대신 Unreal에선 !!를 활용한다 str.Append(InValue->GetName()); str.Append(!!InValue ? "Not Null" : "Null"); UE_LOG(GP, Display, L"%s", *str); } // 파일명, Function명 LineNo. 표기 void CLog::Log(const FString& InFileName, const FString & InFuncName, int32 InLineNo) { // " C:\Unreal\CPP\~CLog.cpp " 형식처럼 디렉토리 주소값 출력되기 때문에 파일명 이전 디렉토리는 삭제해줘야 함 int32 index = 0, length = 0; InFileName.FindLastChar(L'\\', index); length = InFileName.Len() - 1; FString fileName = InFileName.Right(length - index); // 파일명의 오른쪽을 index만큼 자르기 UE_LOG(GP, Display, L"%s, %s, %d", *fileName, *InFuncName, InLineNo); } - C01_Log.cpp
void AC01_Log::BeginPlay() { Super::BeginPlay(); CLog::Log(10); CLog::Log(PI); CLog::Log("C01_Log"); CLog::Log(GetActorLocation()); CLog::Log(GetActorRotation()); LogLine(); }
- 헤더
스크린메세지 Print
뷰포트에 출력될 스크린메세지도 매크로 및 static함수를 활용해 출력하기
- 헤더
#define PrintLine() { CLog::Print(__FILE__, __FUNCTION__,__LINE__);} { ... class U03_BASIC_API CLog 이어서 ... static void Print(int32 InValue, int32 InKey = -1, float InDuration = 10.0f, FColor InColor = FColor::Blue); static void Print(float InValue, int32 InKey = -1, float InDuration = 10.0f, FColor InColor = FColor::Blue); static void Print(const FString& InValue, int32 InKey = -1, float InDuration = 10.0f, FColor InColor = FColor::Blue); static void Print(const FVector& InValue, int32 InKey = -1, float InDuration = 10.0f, FColor InColor = FColor::Blue); static void Print(const FRotator& InValue, int32 InKey = -1, float InDuration = 10.0f, FColor InColor = FColor::Blue); static void Print(const UObject* InValue, int32 InKey = -1, float InDuration = 10.0f, FColor InColor = FColor::Blue); static void Print(const FString& InFileName, const FString& InFuncName, int32 InLineNo); } - 소스
#include "Engine.h" void CLog::Print(int32 InValue, int32 InKey, float InDuration, FColor InColor) { // InKey : 디버그 시 스크린에 출력될 메세지 고정 라인넘버 같은 것 ( -1 = 최근 출력된 메세지 다음) // Duration : 표출시간 GEngine->AddOnScreenDebugMessage(InKey, InDuration, InColor, FString::FromInt(InValue)); } void CLog::Print(float InValue, int32 InKey, float InDuration, FColor InColor) { GEngine->AddOnScreenDebugMessage(InKey, InDuration, InColor, FString::SanitizeFloat(InValue)); } void CLog::Print(const FString& InValue, int32 InKey, float InDuration, FColor InColor) { GEngine->AddOnScreenDebugMessage(InKey, InDuration, InColor, InValue); } void CLog::Print(const FVector& InValue, int32 InKey, float InDuration, FColor InColor) { GEngine->AddOnScreenDebugMessage(InKey, InDuration, InColor, InValue.ToString()); } void CLog::Print(const FRotator& InValue, int32 InKey , float InDuration, FColor InColor) { GEngine->AddOnScreenDebugMessage(InKey, InDuration, InColor, InValue.ToString()); } void CLog::Print(const UObject* InValue, int32 InKey, float InDuration, FColor InColor) { FString str; if (!!InValue) // !!: Is Not Nullptr) if(InValue != nullptr) 이거 대신 Unreal에선 !!를 활용한다 str.Append(InValue->GetName()); str.Append(!!InValue ? "Not Null" : "Null"); GEngine->AddOnScreenDebugMessage(InKey, InDuration, InColor, str); } void CLog::Print(const FString& InFileName, const FString& InFuncName, int32 InLineNo) { int32 index = 0, length = 0; InFileName.FindLastChar(L'\\', index); length = InFileName.Len() - 1; FString fileName = InFileName.Right(length - index); // 파일명의 오른쪽을 index만큼 자르기 FString str = FString::Printf(L"%s, %s, %d", *fileName, *InFuncName, InLineNo); GEngine->AddOnScreenDebugMessage(-1, 10, FColor::Blue, str); } - C01_Log.h
private: float TotalTime; - C01_Log.cpp
{ ... AC01_Log::BeginPlay() 이어서 ... CLog::Print(10); CLog::Print(PI); CLog::Print("C01_Log"); CLog::Print(GetActorLocation()); CLog::Print(GetActorRotation()); CLog::Print(this); PrintLine(); } void AC01_Log::Tick(float DeltaTime) { Super::Tick(DeltaTime); TotalTime += DeltaTime; CLog::Print(TotalTime, 10); }
DrawDebug
DrawDebug : 모양을 가지고 있는 Debug그리기 - Point, Line, Box, Circle, Sphere, Capsule 등등의 모양을 위치, 크기, 색깔 등 조건에 맞게 그려준다
새 C++ 액터 C02_DrawDebug클래스 생성해서 각각의 Debug들을 그려보자
- 헤더
#pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "C02_DrawDebug.generated.h" UCLASS() class U03_BASIC_API AC02_DrawDebug : public AActor { GENERATED_BODY() public: AC02_DrawDebug(); private: UPROPERTY(EditAnywhere, Category = "Settings") FVector InitLocation[5]; UPROPERTY(EditAnywhere, Category = "Settings") FBox Box; private: // 컴포넌트들은 대부분 보통 VisibleAnywhere를 사용 UPROPERTY(VisibleAnywhere) class UPointLightComponent* PointLight; protected: virtual void BeginPlay() override; public: virtual void Tick(float DeltaTime) override; private: float EaseOutBounce(float x); private: FVector Location[5]; }; - 소스
#include "02_Profiler/C02_DrawDebug.h" #include "Global.h" #include "Components/PointLightComponent.h" AC02_DrawDebug::AC02_DrawDebug() { PrimaryActorTick.bCanEverTick = true; CHelpers::CreateComponent<UPointLightComponent>(this, &PointLight, "Light"); InitLocation[0] = FVector(0, 0, 0); InitLocation[1] = FVector(0, 1000, 0); InitLocation[2] = FVector(0, 500, 0); InitLocation[3] = FVector(0, 1500, 0); InitLocation[4] = FVector(500, 0, 0); Box = FBox(FVector(-50, -100, -50), FVector(50, 100, 50)); } void AC02_DrawDebug::BeginPlay() { Super::BeginPlay(); PointLight->SetVisibility(false); } void AC02_DrawDebug::Tick(float DeltaTime) { Super::Tick(DeltaTime); // 현재 액터의 위치를 기준으로 좌표점 생성 for (int32 i = 0; i < 5; i++) Location[i] = InitLocation[i] + GetActorLocation(); DrawDebugSolidBox(GetWorld(), Location[0] + Box.GetCenter(), Box.GetExtent(), FColor::Red); DrawDebugPoint(GetWorld(), Location[1], 100, FColor::Green); // Point는 Controller가 있는 방향을 향한다 DrawDebugSphere(GetWorld(), Location[2], 100, 30, FColor::Blue); DrawDebugCircle(GetWorld(), Location[3], 100, 50, FColor::Magenta); DrawDebugLine(GetWorld(), Location[2], Location[3], FColor::Yellow,false,-1.0f,0,10); FVector location = Location[2]; location.X += 10; location.Y += 10; // location.Z += FMath::Sin(GetWorld()->GetTimeSeconds() * 5.0f) * 400.0f; // 위아래 같은 높낮이로 움직임 location.Z += EaseOutBounce(FMath::Sin(GetWorld()->GetTimeSeconds() * 3.0f)) * 300.0f; // 바운스되는 것처럼 점점 위아래움직임이 짧아짐 DrawDebugCapsule(GetWorld(), location, 200, 50, FQuat::Identity, FColor::White); DrawDebugDirectionalArrow(GetWorld(), Location[3], location, 400, FColor::Black, false, -1, 0, 20); bool b = FMath::Sign(FMath::Cos(GetWorld()->GetTimeSeconds() * 5.0f)) >= 0; PointLight->SetVisibility(b); } float AC02_DrawDebug::EaseOutBounce(float x) { // https://github.com/ai/easings.net 참조 const float n1 = 7.5625f; const float d1 = 2.75f; if (x < 1.0f / d1) { return n1 * x * x; } else if (x < 2.0f / d1) { return n1 * (x -= 1.5f / d1) * x + 0.75f; } else if (x < 2.5f / d1) { return n1 * (x -= 2.25f / d1) * x + 0.9375f; } else { return n1 * (x -= 2.625f / d1) * x + 0.984375f; } }
EaseOutBounce : 바운스되는 높이값을 비선형함수로 계산하는방식, 캐릭터 추락 시 카메라 흔들림효과에 활용하면 좋을 것 같다
선형움직임(EaseOutBounce() 사용안한 경우)
비선형움직임(EaseOutBounce() 사용)