[UE] Actor 복제 및 SRT 변경하는 Tool 만들기
언리얼 엔진 에디터에서 Actor를 배치하거나 위치를 조작하는 경우는 흔하다. 예를 들어, 맵에 몬스터 100마리를 배치한다고 가정했을때 몬스터 한마리 한마리 배치하기 시작하면 시간이 너무 많이 소요된다. 이 때, 몬스터 100마리를 각각 X, Y좌표로 1000 간격으로 배치하고 바라보는 방향으로 랜덤으로 돌리면 빠르게 맵에 배치할 수 있고 테스트하기도 좋다. 이런 기능을 담당하는 툴을 만들었다.
목차
Actor SRT Tool 만들기
UEditorSubsystem 기능을 사용할 것이다
UEditorActorSubsystem
- TArray<class > GetSelectedLevelActors()
- TArray<class > GetAllLevelActors()
QuickActorActionWidget 생성
새 C++ 클래스 - EditorUtilityWidget - QuickActorActionWidget 생성
QuickActorActionWidget.h
더보기
#pragma once
#include "CoreMinimal.h"
#include "EditorUtilityWidget.h"
#include "QuickActorActionsWidget.generated.h"
/** 아래 3가지의 기능들을 수행하는 Tool를 만드는 클래스
* 1. 비슷한 이름의 Actor들을 선택하는 기능
* 2. 선택한 Actor를 설정한 Offset간격으로 지정한 수만큼 복제하는 기능
* 3. Actor의 SRT(Scale, Rotate, Transform)을 조작하는 기능
* UEditorUtilityWidget 상속을 받음
*/
UENUM(BlueprintType)
enum class E_DuplicationAxis : uint8
{
EDA_XAxis UMETA(DisplayName = "X Axis"),
EDA_YAxis UMETA(DisplayName = "Y Axis"),
EDA_ZAxis UMETA(DisplayName = "Z Axis"),
EDA_MAX UMETA(DisplayName = "Default Max")
};
USTRUCT(BlueprintType)
struct FRandomActorRotation
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite)
bool bRandomizeRotYaw = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (EditCondition = "bRandomizeRotYaw")) // bRandomizeRotYaw == true일 때만 적용
float RotYawMin = -45.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (EditCondition = "bRandomizeRotYaw"))
float RotYawMax = 45.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
bool bRandomizeRotPitch = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (EditCondition = "bRandomizeRotPitch"))
float RotPitchMin = -45.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (EditCondition = "bRandomizeRotPitch"))
float RotPitchMax = 45.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
bool bRandomizeRotRoll = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (EditCondition = "bRandomizeRotRoll"))
float RotRollMin = -45.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (EditCondition = "bRandomizeRotRoll"))
float RotRollMax = 45.f;
};
UCLASS()
class SWMANAGER_API UQuickActorActionsWidget : public UEditorUtilityWidget
{
GENERATED_BODY()
public:
#pragma region ActorBatchSelection
UFUNCTION(BlueprintCallable) // WBP의 Graph에 노출시켜 버튼 클릭 시 실행되도록함
void SelectAllActorsWithSimilarName(); // 비슷한 이름의 모든 Actor를 선택
// "ActorBatchSelection"를 WBP의 ActorBatchSelectionDetailsView에 연결
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ActorBatchSelection")
TEnumAsByte<ESearchCase::Type> SearchCase = ESearchCase::IgnoreCase;
#pragma endregion
//** Actor 복제. WBP의 ActorBatchDuplicationDetailsView/Button에 아래변수 연결
#pragma region ActorBatchDuplication
UFUNCTION(BlueprintCallable)
void DuplicateActors(); // Actor 복제
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ActorBatchDuplication")
E_DuplicationAxis AxisForDuplication = E_DuplicationAxis::EDA_XAxis;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ActorBatchDuplication")
int32 NumberOfDuplicates = 5; // 복제 수
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ActorBatchDuplication")
float OffsetDist = 300.f; // 복제 시 Actor 간 간격
#pragma endregion
//** Actor를 랜덤으로 Transform
#pragma region RandomizeActorTransform
UFUNCTION(BlueprintCallable)
void RandomizeActorTransform(); // Actor를 랜덤으로 Transform
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RandomizeActorTransform")
FRandomActorRotation RandomActorRotation;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RandomizeActorTransform")
bool bRandomizeScale = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RandomizeActorTransform", meta = (EditCondition = "bRandomizeScale"))
float ScaleMin = 0.8f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RandomizeActorTransform", meta = (EditCondition = "bRandomizeScale"))
float ScaleMax = 1.2f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RandomizeActorTransform")
bool bRandomizeOffset = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RandomizeActorTransform", meta = (EditCondition = "bRandomizeOffset"))
float OffsetMin = -50.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RandomizeActorTransform", meta = (EditCondition = "bRandomizeOffset"))
float OffsetMax = 50.f;
#pragma endregion
private:
UPROPERTY()
TObjectPtr<class UEditorActorSubsystem> EditorActorSubsystem;
bool GetEditorActorSubsystem();
};
QuickActorActionWidget.cpp
더보기
#include "ActorActions/QuickActorActionsWidget.h"
#include "Subsystems/EditorActorSubsystem.h"
#include "DebugHeader.h"
void UQuickActorActionsWidget::SelectAllActorsWithSimilarName()
{
if (false == GetEditorActorSubsystem()) return;
TArray<AActor*> SelectedActors = EditorActorSubsystem->GetSelectedLevelActors();
uint32 SelectionCounter = 0; // 비슷한 이름으로 선택된 Actor의 수
//** 예외처리
if (SelectedActors.Num() == 0) // Actor를 선택하지 않은 경우
{
DebugHeader::ShowNotifyInfo(TEXT("선택된 Actor가 없습니다."));
return;
}
if (SelectedActors.Num() > 1) // Actor를 2개 이상 선택한 경우
{
DebugHeader::ShowNotifyInfo(TEXT("한가지 Actor만 선택할 수 있습니다. 다시 선택하십시오."));
return;
}
//** 제대로 선택한 경우. Actor를 1개 선택한 경우
FString SelectedActorName = SelectedActors[0]->GetActorLabel(); // 선택한 Actor가 WorldOutlinear에 노출되는 이름
const FString NameToSearch = SelectedActorName.LeftChop(4); // SelectedActorName에서 끝의 4글자를 제거하여 NameToSearch변수에 담음
TArray<AActor*> AllLeveActors = EditorActorSubsystem->GetAllLevelActors(); // Level에 있는 모든 Actor
for (AActor* ActorInLevel : AllLeveActors)
{
if (false == IsValid(ActorInLevel)) continue;
// Actor의 이름에 NameToSearch가 포함되어 있다면
if (ActorInLevel->GetActorLabel().Contains(NameToSearch, SearchCase))
{
// 해당 Actor를 선택하고 SelectionCounter를 증가시킨다
EditorActorSubsystem->SetActorSelectionState(ActorInLevel, true);
SelectionCounter++; // 비슷한 이름으로 선택된 Actor의 수++
}
}
if (SelectionCounter > 0)
{
DebugHeader::ShowNotifyInfo(TEXT("총 ") +
FString::FromInt(SelectionCounter) + TEXT(" 개의 Actor들이 성공적으로 선택되었습니다."));
}
else
{
DebugHeader::ShowNotifyInfo(TEXT("비슷한 이름의 Actor를 찾지 못했습니다."));
}
}
void UQuickActorActionsWidget::DuplicateActors() // Actor 복제
{
if (false == GetEditorActorSubsystem()) return;
TArray<AActor*> SelectedActors = EditorActorSubsystem->GetSelectedLevelActors(); // 선택된 Actor들을 담는 변수
uint32 Counter = 0; // 복제된 Actor 수 기록할 변수
if (SelectedActors.Num() == 0)
{
DebugHeader::ShowNotifyInfo(TEXT("선택된 Actor가 없습니다."));
return;
}
if (NumberOfDuplicates <= 0 || OffsetDist == 0)
{
DebugHeader::ShowNotifyInfo(TEXT("'복제를 희망하는 수'와 '복제 간격'을 올바르게 기입해주세요."));
return;
}
for (AActor* SelectedActor : SelectedActors) // 선택된 Actor들을 for문 돌아 복제
{
if (false == IsValid(SelectedActor)) continue; // 선택된 Actor가 없으면 continue
for (int32 i = 0; i < NumberOfDuplicates; i++) // NumberOfDuplicates 수 만큼 복제
{
//** Actor 복제
AActor* DuplicatedActor = EditorActorSubsystem->DuplicateActor(SelectedActor, SelectedActor->GetWorld());
if (false == IsValid(DuplicatedActor)) continue;
//** 복제 후 Offset 이동
const float DuplicationOffsetDist = (i + 1) * OffsetDist;
switch (AxisForDuplication)
{
case E_DuplicationAxis::EDA_XAxis:
DuplicatedActor->AddActorWorldOffset(FVector(DuplicationOffsetDist, 0.f, 0.f));
break;
case E_DuplicationAxis::EDA_YAxis:
DuplicatedActor->AddActorWorldOffset(FVector(0.f, DuplicationOffsetDist, 0.f));
break;
case E_DuplicationAxis::EDA_ZAxis:
DuplicatedActor->AddActorWorldOffset(FVector(0.f, 0.f, DuplicationOffsetDist));
break;
case E_DuplicationAxis::EDA_MAX:
break;
default:
break;
}
//** 복제된 Actor들 선택
EditorActorSubsystem->SetActorSelectionState(DuplicatedActor, true);
Counter++; // 복제된 Actor 수 기록
}
}
if (Counter > 0)
{
DebugHeader::ShowNotifyInfo(TEXT("총 ") +
FString::FromInt(Counter) + TEXT(" 개의 Actor가 성공적으로 복제되었습니다."));
}
}
void UQuickActorActionsWidget::RandomizeActorTransform() // Actor를 랜덤으로 Transform
{
const bool bConditionNotSet =
false == RandomActorRotation.bRandomizeRotYaw &&
false == RandomActorRotation.bRandomizeRotPitch &&
false == RandomActorRotation.bRandomizeRotRoll &&
false == bRandomizeScale &&
false == bRandomizeOffset;
if (bConditionNotSet)
{
DebugHeader::ShowNotifyInfo(TEXT("변경 조건이 설정되지 않았습니다."));
return;
}
if (false == GetEditorActorSubsystem()) return;
TArray<AActor*> SelectedActors = EditorActorSubsystem->GetSelectedLevelActors(); // 선택된 Actor들을 담음
uint32 Counter = 0; // Transform된 Actor 수를 기록할 변수
if (SelectedActors.Num() == 0)
{
DebugHeader::ShowNotifyInfo(TEXT("선택된 Actor가 없습니다."));
return;
}
for (AActor* SelectedActor : SelectedActors)
{
if (false == IsValid(SelectedActor)) continue;
if (RandomActorRotation.bRandomizeRotYaw) // Yaw
{
const float RandomRotYawValue = FMath::RandRange(RandomActorRotation.RotYawMin, RandomActorRotation.RotYawMax);
SelectedActor->AddActorWorldRotation(FRotator(0.f, RandomRotYawValue, 0.f));
}
if (RandomActorRotation.bRandomizeRotPitch) // Pitch
{
const float RandomRotPitchValue = FMath::RandRange(RandomActorRotation.RotPitchMin, RandomActorRotation.RotPitchMax);
SelectedActor->AddActorWorldRotation(FRotator(RandomRotPitchValue, 0.f, 0.f));
}
if (RandomActorRotation.bRandomizeRotRoll) // Roll
{
const float RandomRotRollValue = FMath::RandRange(RandomActorRotation.RotRollMin, RandomActorRotation.RotRollMax);
SelectedActor->AddActorWorldRotation(FRotator(0.f, 0.f, RandomRotRollValue));
}
if (bRandomizeScale) // Scale
{
SelectedActor->SetActorScale3D(FVector(FMath::RandRange(ScaleMin, ScaleMax)));
}
if (bRandomizeOffset) // Offset
{
const float RandomOffsetValue = FMath::RandRange(OffsetMin, OffsetMax);
SelectedActor->AddActorWorldOffset(FVector(RandomOffsetValue, RandomOffsetValue, 0.f));
}
Counter++; // Transform된 Actor 수++
}
if (Counter > 0)
{
DebugHeader::ShowNotifyInfo(TEXT("총 ") +
FString::FromInt(Counter) + TEXT(" 개의 Actor들이 Tranform 되었습니다."));
}
}
bool UQuickActorActionsWidget::GetEditorActorSubsystem()
{
if (false == IsValid(EditorActorSubsystem))
{
EditorActorSubsystem = GEditor->GetEditorSubsystem<UEditorActorSubsystem>(); // 할당
}
return IsValid(EditorActorSubsystem); // EditorActorSubsystem 존재한다면 true리턴
}
※ 참고: FString LeftChop(int32 Count)
- FString의 값에서 끝의 Count 숫자만큼 글자를 제거한다.
WBP_ QuickActorActionWidget 생성
실행화면
'⭐ Unreal Engine > UE Plugin - Custom Editor Tool' 카테고리의 다른 글
[UE] Texture로 Material 생성하기 (0) | 2023.12.07 |
---|---|
[UE] Content Browser Extender Menu에 아이콘 등록하기 (0) | 2023.12.07 |
[UE] Slate UI 2: 제거 마법사 - 에셋 리스트 띄우기 (0) | 2023.12.01 |
[UE] Slate UI 1: Tab 띄우기 (0) | 2023.12.01 |
[UE] ContentBrowser Menu: 사용하지 않는 에셋/빈 폴더 제거 (0) | 2023.11.25 |
댓글
이 글 공유하기
다른 글
-
[UE] Texture로 Material 생성하기
[UE] Texture로 Material 생성하기
2023.12.07 -
[UE] Content Browser Extender Menu에 아이콘 등록하기
[UE] Content Browser Extender Menu에 아이콘 등록하기
2023.12.07 -
[UE] Slate UI 2: 제거 마법사 - 에셋 리스트 띄우기
[UE] Slate UI 2: 제거 마법사 - 에셋 리스트 띄우기
2023.12.01 -
[UE] Slate UI 1: Tab 띄우기
[UE] Slate UI 1: Tab 띄우기
2023.12.01