[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
댓글을 사용할 수 없습니다.