[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.07Content Browser Extender Menu에 아이콘 등록하기 아이콘 등록하기 구현방법 1. Custom FSlateStyle Set에서 할 일 Custom FSlateStyle Set으로 사용할 SWManageStyle 클래스를 만들고 아래 작업들을 수행한다. Style set 이름 설정하기 static FName StyleSetName; Custom Style set 생성하기: static TSharedRef CreateSlateStyleSet(); 파일 경로를 가져와서 Icon 이미지를 로드 TSharedRef CustomStyleSet = MakeShareable(new FSlateStyleSet(StyleSetName)); const FString IconDirectory = IPlug… -
[UE] Slate UI 2: 제거 마법사 - 에셋 리스트 띄우기
[UE] Slate UI 2: 제거 마법사 - 에셋 리스트 띄우기
2023.12.01 -
[UE] Slate UI 1: Tab 띄우기
[UE] Slate UI 1: Tab 띄우기
2023.12.01Slate UI를 사용하여 탭을 생성하고 폴더 내에 있는 에셋 목록을 한번에 보여주게 만들 것이다. 에셋 목록을 조작하여 필요시 선택한 에셋들을 지우는 기능을 추가할 것이다. 일단, 탭을 생성하고 띄워주는 것부터 하겠다. 목차 Smart Pointer Smart Pointer 스마트 포인터 객체를 소유 삭제 예방 Unique Property TSharedPtr O O Reference Counting 메서드 사용 NULL을 가리킬 수 있음 TSharedRef O O NULL을 가리킬 수 없음 무조건유효한 객체를 가르켜야 함 TWeakPtr X X Reference cycle을 부순다 TSharedRef타입의 함수는 return 값이 무조건 NULL이 아닌 유효한 값이다. 이 점을 알고 있으면 코드 파악이…
댓글을 사용할 수 없습니다.