// Copyright 2019-Present LexLiu. All Rights Reserved. #include "LGUIEditorTools.h" #include "Core/LGUIManager.h" #include "Widgets/Docking/SDockTab.h" #include "Misc/FileHelper.h" #include "Misc/MessageDialog.h" #include "PropertyCustomizationHelpers.h" #include "DesktopPlatformModule.h" #include "AssetRegistry/AssetRegistryModule.h" #include "Engine/EngineTypes.h" #include "Kismet2/ComponentEditorUtils.h" #include "Widgets/SViewport.h" #include "Layout/LGUICanvasScaler.h" #include "EditorViewportClient.h" #include "Engine/Selection.h" #include "EngineUtils.h" #include "DataFactory/LGUIPrefabActorFactory.h" #include "PrefabSystem/LGUIPrefabHelperObject.h" #include LGUIPREFAB_SERIALIZER_NEWEST_INCLUDE #include "LGUIEditorModule.h" #include "PrefabEditor/LGUIPrefabEditor.h" #include "LGUIHeaders.h" #include "Settings/LevelEditorMiscSettings.h" #include "Layers/LayersSubsystem.h" #include "ActorEditorUtils.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Serialization/ArchiveReplaceObjectRef.h" #include "Logging/MessageLog.h" #define LOCTEXT_NAMESPACE "LGUIEditorTools" UE_DISABLE_OPTIMIZATION FEditingPrefabChangedDelegate LGUIEditorTools::OnEditingPrefabChanged; FBeforeApplyPrefabDelegate LGUIEditorTools::OnBeforeApplyPrefab; namespace ReattachActorsHelper { /** Holds the actor and socket name for attaching. */ struct FActorAttachmentInfo { AActor* Actor; FName SocketName; }; /** Used to cache the attachment info for an actor. */ struct FActorAttachmentCache { public: /** The post-conversion actor. */ AActor* NewActor; /** The parent actor and socket. */ FActorAttachmentInfo ParentActor; /** Children actors and the sockets they were attached to. */ TArray AttachedActors; }; /** * Caches the attachment info for the actors being converted. * * @param InActorsToReattach List of actors to reattach. * @param InOutAttachmentInfo List of attachment info for the list of actors. */ void CacheAttachments(const TArray& InActorsToReattach, TArray& InOutAttachmentInfo) { for (int32 ActorIdx = 0; ActorIdx < InActorsToReattach.Num(); ++ActorIdx) { AActor* ActorToReattach = InActorsToReattach[ActorIdx]; InOutAttachmentInfo.AddZeroed(); FActorAttachmentCache& CurrentAttachmentInfo = InOutAttachmentInfo[ActorIdx]; // Retrieve the list of attached actors. TArray AttachedActors; ActorToReattach->GetAttachedActors(AttachedActors); // Cache the parent actor and socket name. CurrentAttachmentInfo.ParentActor.Actor = ActorToReattach->GetAttachParentActor(); CurrentAttachmentInfo.ParentActor.SocketName = ActorToReattach->GetAttachParentSocketName(); // Required to restore attachments properly. for (int32 AttachedActorIdx = 0; AttachedActorIdx < AttachedActors.Num(); ++AttachedActorIdx) { // Store the attached actor and socket name in the cache. CurrentAttachmentInfo.AttachedActors.AddZeroed(); CurrentAttachmentInfo.AttachedActors[AttachedActorIdx].Actor = AttachedActors[AttachedActorIdx]; CurrentAttachmentInfo.AttachedActors[AttachedActorIdx].SocketName = AttachedActors[AttachedActorIdx]->GetAttachParentSocketName(); AActor* ChildActor = CurrentAttachmentInfo.AttachedActors[AttachedActorIdx].Actor; ChildActor->Modify(); ChildActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); } // Modify the actor so undo will reattach it. ActorToReattach->Modify(); ActorToReattach->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); } } /** * Caches the actor old/new information, mapping the old actor to the new version for easy look-up and matching. * * @param InOldActor The old version of the actor. * @param InNewActor The new version of the actor. * @param InOutReattachmentMap Map object for placing these in. * @param InOutAttachmentInfo Update the required attachment info to hold the Converted Actor. */ void CacheActorConvert(AActor* InOldActor, AActor* InNewActor, TMap& InOutReattachmentMap, FActorAttachmentCache& InOutAttachmentInfo) { // Add mapping data for the old actor to the new actor. InOutReattachmentMap.Add(InOldActor, InNewActor); // Set the converted actor so re-attachment can occur. InOutAttachmentInfo.NewActor = InNewActor; } /** * Checks if two actors can be attached, creates Message Log messages if there are issues. * * @param InParentActor The parent actor. * @param InChildActor The child actor. * @param InOutErrorMessages Errors with attaching the two actors are stored in this array. * * @return Returns true if the actors can be attached, false if they cannot. */ bool CanParentActors(AActor* InParentActor, AActor* InChildActor) { FText ReasonText; if (GEditor->CanParentActors(InParentActor, InChildActor, &ReasonText)) { return true; } else { FMessageLog("EditorErrors").Error(ReasonText); return false; } } /** * Reattaches actors to maintain the hierarchy they had previously using a conversion map and an array of attachment info. All errors displayed in Message Log along with notifications. * * @param InReattachmentMap Used to find the corresponding new versions of actors using an old actor pointer. * @param InAttachmentInfo Holds parent and child attachment data. */ void ReattachActors(TMap& InReattachmentMap, TArray& InAttachmentInfo) { // Holds the errors for the message log. FMessageLog EditorErrors("EditorErrors"); EditorErrors.NewPage(LOCTEXT("AttachmentLogPage", "Actor Reattachment")); for (int32 ActorIdx = 0; ActorIdx < InAttachmentInfo.Num(); ++ActorIdx) { FActorAttachmentCache& CurrentAttachment = InAttachmentInfo[ActorIdx]; // Need to reattach all of the actors that were previously attached. for (int32 AttachedIdx = 0; AttachedIdx < CurrentAttachment.AttachedActors.Num(); ++AttachedIdx) { // Check if the attached actor was converted. If it was it will be in the TMap. AActor** CheckIfConverted = InReattachmentMap.Find(CurrentAttachment.AttachedActors[AttachedIdx].Actor); if (CheckIfConverted) { // This should always be valid. if (*CheckIfConverted) { AActor* ParentActor = CurrentAttachment.NewActor; AActor* ChildActor = *CheckIfConverted; if (CanParentActors(ParentActor, ChildActor)) { // Attach the previously attached and newly converted actor to the current converted actor. ChildActor->AttachToActor(ParentActor, FAttachmentTransformRules::KeepWorldTransform, CurrentAttachment.AttachedActors[AttachedIdx].SocketName); } } } else { AActor* ParentActor = CurrentAttachment.NewActor; AActor* ChildActor = CurrentAttachment.AttachedActors[AttachedIdx].Actor; if (CanParentActors(ParentActor, ChildActor)) { // Since the actor was not converted, reattach the unconverted actor. ChildActor->AttachToActor(ParentActor, FAttachmentTransformRules::KeepWorldTransform, CurrentAttachment.AttachedActors[AttachedIdx].SocketName); } } } // Check if the parent was converted. AActor** CheckIfNewActor = InReattachmentMap.Find(CurrentAttachment.ParentActor.Actor); if (CheckIfNewActor) { // Since the actor was converted, attach the current actor to it. if (*CheckIfNewActor) { AActor* ParentActor = *CheckIfNewActor; AActor* ChildActor = CurrentAttachment.NewActor; if (CanParentActors(ParentActor, ChildActor)) { ChildActor->AttachToActor(ParentActor, FAttachmentTransformRules::KeepWorldTransform, CurrentAttachment.ParentActor.SocketName); } } } else { AActor* ParentActor = CurrentAttachment.ParentActor.Actor; AActor* ChildActor = CurrentAttachment.NewActor; // Verify the parent is valid, the actor may not have actually been attached before. if (ParentActor && CanParentActors(ParentActor, ChildActor)) { // The parent was not converted, attach to the unconverted parent. ChildActor->AttachToActor(ParentActor, FAttachmentTransformRules::KeepWorldTransform, CurrentAttachment.ParentActor.SocketName); } } } // Add the errors to the message log, notifications will also be displayed as needed. EditorErrors.Notify(NSLOCTEXT("ActorAttachmentError", "AttachmentsFailed", "Attachments Failed!")); } } struct LGUIEditorToolsHelperFunctionHolder { public: static TArray ConvertSelectionToActors(USelection* InSelection) { TArray result; auto count = InSelection->Num(); for (int i = 0; i < count; i++) { auto obj = (AActor*)(InSelection->GetSelectedObject(i)); if (obj != nullptr) { result.Add(obj); } } return result; } static FString GetLabelPrefixForCopy(const FString& srcActorLabel, FString& outNumetricSuffix) { int rightCount = 1; while (rightCount <= srcActorLabel.Len() && srcActorLabel.Right(rightCount).IsNumeric()) { rightCount++; } rightCount--; outNumetricSuffix = srcActorLabel.Right(rightCount); return srcActorLabel.Left(srcActorLabel.Len() - rightCount); } public: static FString GetCopiedActorLabel(AActor* Parent, FString OriginActorLabel, UWorld* World) { TArray SameParentActorList;//all actors attached at same parent actor. if parent is null then get all actors for (TActorIterator ActorItr(World); ActorItr; ++ActorItr) { if (AActor* itemActor = *ActorItr) { if (IsValid(itemActor)) { if (IsValid(Parent)) { if (itemActor->GetAttachParentActor() == Parent) { SameParentActorList.Add(itemActor); } } else { if (itemActor->GetAttachParentActor() == nullptr) { SameParentActorList.Add(itemActor); } } } } } FString MaxNumetricSuffixStr = TEXT("");//numetric suffix OriginActorLabel = GetLabelPrefixForCopy(OriginActorLabel, MaxNumetricSuffixStr); int MaxNumetricSuffixStrLength = MaxNumetricSuffixStr.Len(); int SameNameActorCount = 0;//if actor name is same with source name, then collect it for (int i = 0; i < SameParentActorList.Num(); i ++)//search from same level actors, and get the right suffix { auto item = SameParentActorList[i]; auto itemActorLabel = item->GetActorLabel(); if (itemActorLabel == OriginActorLabel)SameNameActorCount++; if (OriginActorLabel.Len() == 0 || itemActorLabel.StartsWith(OriginActorLabel)) { auto itemRightStr = itemActorLabel.Right(itemActorLabel.Len() - OriginActorLabel.Len()); if (!itemRightStr.IsNumeric())//if rest is not numetric { continue; } FString itemNumetrixSuffixStr = itemRightStr; int itemNumetrix = FCString::Atoi(*itemNumetrixSuffixStr); int maxNumetrixSuffix = FCString::Atoi(*MaxNumetricSuffixStr); if (itemNumetrix > maxNumetrixSuffix) { maxNumetrixSuffix = itemNumetrix; MaxNumetricSuffixStr = FString::Printf(TEXT("%d"), maxNumetrixSuffix); } } } FString CopiedActorLabel = OriginActorLabel; if (!MaxNumetricSuffixStr.IsEmpty() || SameNameActorCount > 0) { int MaxNumtrixSuffix = FCString::Atoi(*MaxNumetricSuffixStr); MaxNumtrixSuffix++; FString NumetrixSuffixStr = FString::Printf(TEXT("%d"), MaxNumtrixSuffix); while (NumetrixSuffixStr.Len() < MaxNumetricSuffixStrLength) { NumetrixSuffixStr = TEXT("0") + NumetrixSuffixStr; } CopiedActorLabel += NumetrixSuffixStr; } return CopiedActorLabel; } public: static TArray ConvertSelectionToComponents(USelection* InSelection) { TArray result; auto count = InSelection->Num(); for (int i = 0; i < count; i++) { auto obj = (UActorComponent*)(InSelection->GetSelectedObject(i)); if (obj != nullptr) { result.Add(obj); } } return result; } //this function mostly copied from UnrealED/Private/EditorEngine.cpp::ReplaceActors static TArray ReplaceActor(const TArray& ActorsToReplace, TSubclassOf NewActorClass) { TArray Result; // Cache for attachment info of all actors being converted. TArray AttachmentInfo; // Maps actors from old to new for quick look-up. TMap ConvertedMap; // Cache the current attachment states. ReattachActorsHelper::CacheAttachments(ActorsToReplace, AttachmentInfo); USelection* SelectedActors = GEditor->GetSelectedActors(); SelectedActors->BeginBatchSelectOperation(); SelectedActors->Modify(); for (int32 ActorIdx = 0; ActorIdx < ActorsToReplace.Num(); ++ActorIdx) { AActor* OldActor = ActorsToReplace[ActorIdx];//.Pop(); check(OldActor); UWorld* World = OldActor->GetWorld(); ULevel* Level = OldActor->GetLevel(); AActor* NewActor = NULL; // Unregister this actors components because we are effectively replacing it with an actor sharing the same ActorGuid. // This allows it to be unregistered before a new actor with the same guid gets registered avoiding conflicts. OldActor->UnregisterAllComponents(); const FName OldActorName = OldActor->GetFName(); const FName OldActorReplacedNamed = MakeUniqueObjectName(OldActor->GetOuter(), OldActor->GetClass(), *FString::Printf(TEXT("%s_REPLACED"), *OldActorName.ToString())); FActorSpawnParameters SpawnParams; SpawnParams.Name = OldActorName; SpawnParams.bCreateActorPackage = false; SpawnParams.OverridePackage = OldActor->GetExternalPackage(); SpawnParams.OverrideActorGuid = OldActor->GetActorGuid(); // Don't go through AActor::Rename here because we aren't changing outers (the actor's level) and we also don't want to reset loaders // if the actor is using an external package. We really just want to rename that actor out of the way so we can spawn the new one in // the exact same package, keeping the package name intact. OldActor->UObject::Rename(*OldActorReplacedNamed.ToString(), OldActor->GetOuter(), REN_DoNotDirty | REN_DontCreateRedirectors | REN_ForceNoResetLoaders); const FTransform OldTransform = OldActor->ActorToWorld(); // create the actor NewActor = OldActor->GetWorld()->SpawnActor(NewActorClass, &OldTransform, SpawnParams); //added by liuf, if no root component then add one { auto RootComponent = NewActor->GetRootComponent(); if (!RootComponent) { RootComponent = NewObject(NewActor, USceneComponent::GetDefaultSceneRootVariableName(), RF_Transactional); RootComponent->Mobility = EComponentMobility::Movable; RootComponent->bVisualizeComponent = false; NewActor->SetRootComponent(RootComponent); RootComponent->RegisterComponent(); NewActor->AddInstanceComponent(RootComponent); } } // try to copy over properties NewActor->UnregisterAllComponents(); UEngine::FCopyPropertiesForUnrelatedObjectsParams Options; Options.bNotifyObjectReplacement = true; UEditorEngine::CopyPropertiesForUnrelatedObjects(OldActor, NewActor, Options); if (OldActor->GetRootComponent() != nullptr && NewActor->GetRootComponent() != nullptr) { UEditorEngine::CopyPropertiesForUnrelatedObjects(OldActor->GetRootComponent(), NewActor->GetRootComponent(), Options); } NewActor->RegisterAllComponents(); Result.Add(NewActor); if (NewActor) { // The new actor might not have a root component USceneComponent* const NewActorRootComponent = NewActor->GetRootComponent(); if (NewActorRootComponent) { if (!GetDefault()->bReplaceRespectsScale || OldActor->GetRootComponent() == NULL) { NewActorRootComponent->SetRelativeScale3D(FVector(1.0f, 1.0f, 1.0f)); } else { NewActorRootComponent->SetRelativeScale3D(OldActor->GetRootComponent()->GetRelativeScale3D()); } if (OldActor->GetRootComponent() != NULL) { NewActorRootComponent->SetMobility(OldActor->GetRootComponent()->Mobility); } } NewActor->Layers.Empty(); ULayersSubsystem* LayersSubsystem = GEditor->GetEditorSubsystem(); LayersSubsystem->AddActorToLayers(NewActor, OldActor->Layers); // Preserve the label and tags from the old actor NewActor->SetActorLabel(OldActor->GetActorLabel()); NewActor->Tags = OldActor->Tags; // Allow actor derived classes a chance to replace properties. NewActor->EditorReplacedActor(OldActor); // Caches information for finding the new actor using the pre-converted actor. ReattachActorsHelper::CacheActorConvert(OldActor, NewActor, ConvertedMap, AttachmentInfo[ActorIdx]); if (SelectedActors->IsSelected(OldActor)) { GEditor->SelectActor(OldActor, false, true); GEditor->SelectActor(NewActor, true, true); } // Find compatible static mesh components and copy instance colors between them. UStaticMeshComponent* NewActorStaticMeshComponent = NewActor->FindComponentByClass(); UStaticMeshComponent* OldActorStaticMeshComponent = OldActor->FindComponentByClass(); if (NewActorStaticMeshComponent != NULL && OldActorStaticMeshComponent != NULL) { NewActorStaticMeshComponent->CopyInstanceVertexColorsIfCompatible(OldActorStaticMeshComponent); } NewActor->InvalidateLightingCache(); NewActor->PostEditMove(true); NewActor->MarkPackageDirty(); TSet LevelsToRebuildBSP; ABrush* Brush = Cast(OldActor); if (Brush && !FActorEditorUtils::IsABuilderBrush(Brush)) // Track whether or not a brush actor was deleted. { ULevel* BrushLevel = OldActor->GetLevel(); if (BrushLevel && !Brush->IsVolumeBrush()) { BrushLevel->Model->Modify(); LevelsToRebuildBSP.Add(BrushLevel); } } // Replace references in the level script Blueprint with the new Actor const bool bDontCreate = true; ULevelScriptBlueprint* LSB = NewActor->GetLevel()->GetLevelScriptBlueprint(bDontCreate); if (LSB) { // Only if the level script blueprint exists would there be references. FBlueprintEditorUtils::ReplaceAllActorRefrences(LSB, OldActor, NewActor); } LayersSubsystem->DisassociateActorFromLayers(OldActor); World->EditorDestroyActor(OldActor, true); // If any brush actors were modified, update the BSP in the appropriate levels if (LevelsToRebuildBSP.Num()) { FlushRenderingCommands(); for (ULevel* LevelToRebuild : LevelsToRebuildBSP) { GEditor->RebuildLevel(*LevelToRebuild); } } } else { // If creating the new Actor failed, put the old Actor's name back OldActor->UObject::Rename(*OldActorName.ToString(), OldActor->GetOuter(), REN_DoNotDirty | REN_DontCreateRedirectors | REN_ForceNoResetLoaders); OldActor->RegisterAllComponents(); } } SelectedActors->EndBatchSelectOperation(); // Reattaches actors based on their previous parent child relationship. ReattachActorsHelper::ReattachActors(ConvertedMap, AttachmentInfo); // Perform reference replacement on all Actors referenced by World TArray ReferencedLevels; for (const TPair& ReplacedObj : ConvertedMap) { ReferencedLevels.AddUnique(ReplacedObj.Value->GetLevel()); } for (UObject* Referencer : ReferencedLevels) { constexpr EArchiveReplaceObjectFlags ArFlags = (EArchiveReplaceObjectFlags::IgnoreOuterRef | EArchiveReplaceObjectFlags::TrackReplacedReferences); FArchiveReplaceObjectRef Ar(Referencer, ConvertedMap, ArFlags); for (const TPair>& MapItem : Ar.GetReplacedReferences()) { UObject* ModifiedObject = MapItem.Key; if (!ModifiedObject->HasAnyFlags(RF_Transient) && ModifiedObject->GetOutermost() != GetTransientPackage() && !ModifiedObject->RootPackageHasAnyFlags(PKG_CompiledIn)) { ModifiedObject->MarkPackageDirty(); } for (FProperty* Property : MapItem.Value) { FPropertyChangedEvent PropertyEvent(Property); ModifiedObject->PostEditChangeProperty(PropertyEvent); } } } GEditor->RedrawLevelEditingViewports(); ULevel::LevelDirtiedEvent.Broadcast(); return Result; } }; TMap> LGUIEditorTools::CopiedActorPrefabMap; TWeakObjectPtr LGUIEditorTools::CopiedComponent; FString LGUIEditorTools::LGUIPresetPrefabPath = TEXT("/LGUI/Prefabs/"); FString LGUIEditorTools::GetUniqueNumetricName(const FString& InPrefix, const TArray& InExistNames) { auto ExtractNumetric = [](const FString& InString, int32& Num) { int NumetricStringIndex = -1; FString SubNumetricString; int NumetricStringCharCount = 0; for (int i = InString.Len() - 1; i >= 0; i--) { auto SubChar = InString[i]; if (SubChar >= '0' && SubChar <= '9') { NumetricStringIndex = i; NumetricStringCharCount++; if (NumetricStringCharCount >= 4) { break; } } else { break; } } if (NumetricStringIndex != -1) { auto NumetricSubString = InString.Right(InString.Len() - NumetricStringIndex); Num = FCString::Atoi(*NumetricSubString); return true; } else { return false; } }; int MaxNumSuffix = 0; for (int i = 0; i < InExistNames.Num(); i++)//search from same level actors, and get the right suffix { auto& Item = InExistNames[i]; if (Item.Len() == 0)continue; int Num; if (ExtractNumetric(Item, Num)) { if (Num > MaxNumSuffix) { MaxNumSuffix = Num; } } } return FString::Printf(TEXT("%s_%d"), *InPrefix, MaxNumSuffix + 1); } TArray LGUIEditorTools::GetRootActorListFromSelection(const TArray& selectedActors) { TArray RootActorList; auto count = selectedActors.Num(); //search upward find parent and put into list, only root actor can add to list for (int i = 0; i < count; i++) { auto obj = selectedActors[i]; auto parent = obj->GetAttachParentActor(); bool isRootActor = false; while (true) { if (parent == nullptr)//top level { isRootActor = true; break; } if (selectedActors.Contains(parent))//if parent is already in list, skip it { isRootActor = false; break; } else//if not in list, keep search upward { parent = parent->GetAttachParentActor(); continue; } } if (isRootActor) { RootActorList.Add(obj); } } return RootActorList; } UWorld* LGUIEditorTools::GetWorldFromSelection() { if (auto selectedActor = GetFirstSelectedActor()) { return selectedActor->GetWorld(); } return GWorld; } void LGUIEditorTools::CreateActorByClass(UClass* ActorClass, TFunction Callback) { auto selectedActor = GetFirstSelectedActor(); if (selectedActor == nullptr)return; GEditor->BeginTransaction(LOCTEXT("CreateActor_Transaction", "LGUI Create Actor")); MakeCurrentLevel(selectedActor); AActor* newActor = GetWorldFromSelection()->SpawnActor(ActorClass, FTransform::Identity, FActorSpawnParameters()); if (IsValid(newActor)) { if (Callback != nullptr) { Callback(newActor); } if (selectedActor != nullptr) { auto SelectedRootComp = selectedActor->GetRootComponent(); auto NewRootComp = newActor->GetRootComponent(); if (SelectedRootComp && NewRootComp) { NewRootComp->SetMobility(SelectedRootComp->Mobility); newActor->AttachToActor(selectedActor, FAttachmentTransformRules::KeepRelativeTransform); } GEditor->SelectActor(selectedActor, false, true); } GEditor->SelectActor(newActor, true, true); SetTraceChannelToParent(newActor); } GEditor->EndTransaction(); } void LGUIEditorTools::CreateEmptyActor() { auto selectedActor = GetFirstSelectedActor(); if (selectedActor == nullptr)return; GEditor->BeginTransaction(LOCTEXT("CreateEmptyActor_Transaction", "LGUI create empty actor")); MakeCurrentLevel(selectedActor); AActor* newActor = GetWorldFromSelection()->SpawnActor(AActor::StaticClass(), FTransform::Identity, FActorSpawnParameters()); if (IsValid(newActor)) { //create SceneComponent { USceneComponent* RootComponent = NewObject(newActor, USceneComponent::GetDefaultSceneRootVariableName(), RF_Transactional); RootComponent->Mobility = EComponentMobility::Movable; RootComponent->bVisualizeComponent = false; newActor->SetRootComponent(RootComponent); RootComponent->RegisterComponent(); newActor->AddInstanceComponent(RootComponent); } if (selectedActor != nullptr) { newActor->AttachToActor(selectedActor, FAttachmentTransformRules::KeepRelativeTransform); GEditor->SelectActor(selectedActor, false, true); } GEditor->SelectActor(newActor, true, true); } GEditor->EndTransaction(); } AActor* LGUIEditorTools::GetFirstSelectedActor() { auto selectedActors = LGUIEditorToolsHelperFunctionHolder::ConvertSelectionToActors(GEditor->GetSelectedActors()); auto count = selectedActors.Num(); if (count == 0) { //UE_LOG(LGUIEditor, Error, TEXT("NothingSelected")); return nullptr; } else if (count > 1) { //UE_LOG(LGUIEditor, Error, TEXT("Only support one component")); return nullptr; } return selectedActors[0]; } TArray LGUIEditorTools::GetSelectedActors() { return LGUIEditorToolsHelperFunctionHolder::ConvertSelectionToActors(GEditor->GetSelectedActors()); } void LGUIEditorTools::CreateUIControls(FString InPrefabPath) { auto selectedActor = GetFirstSelectedActor(); if (selectedActor == nullptr)return; GEditor->BeginTransaction(LOCTEXT("CreateUIControl_Transaction", "LGUI Create UI Control")); MakeCurrentLevel(selectedActor); auto prefab = LoadObject(NULL, *InPrefabPath); if (prefab) { auto actor = prefab->LoadPrefabInEditor(GetWorldFromSelection() , selectedActor == nullptr ? nullptr : selectedActor->GetRootComponent()); GEditor->SelectActor(selectedActor, false, true); GEditor->SelectActor(actor, true, true); SetTraceChannelToParent_Recursive(actor); } else { UE_LOG(LGUIEditor, Error, TEXT("[LGUIEditorToolsAgentObject::CreateUIControls] Load control prefab error! Path:%s. Missing some content of LGUI plugin, reinstall this plugin may fix the issue."), *InPrefabPath); } GEditor->EndTransaction(); } void LGUIEditorTools::ReplaceActorByClass(UClass* ActorClass) { auto selectedActors = LGUIEditorToolsHelperFunctionHolder::ConvertSelectionToActors(GEditor->GetSelectedActors()); auto count = selectedActors.Num(); if (count == 0) { UE_LOG(LGUIEditor, Error, TEXT("NothingSelected")); return; } auto RootActorList = LGUIEditorTools::GetRootActorListFromSelection(selectedActors); GEditor->BeginTransaction(LOCTEXT("ReplaceUIElement_Transaction", "LGUI Replace UI Element")); for (auto& Actor : RootActorList) { MakeCurrentLevel(Actor); int HierarchyIndex = 0; if (auto SourceUIItem = Cast(Actor->GetRootComponent())) { HierarchyIndex = SourceUIItem->GetHierarchyIndex(); } AActor* ReplacedActor = nullptr; TArray ChildrenActors; Actor->GetAttachedActors(ChildrenActors); TMap> ChildrenOriginPositionArray; for (auto& ChildActor : ChildrenActors) { if (auto UIComp = Cast(ChildActor->GetRootComponent())) { ChildrenOriginPositionArray.Add(UIComp, { UIComp->GetAnchorData(), UIComp->GetHierarchyIndex(), UIComp->GetRelativeLocation()}); } } if (auto PrefabHelperObject = LGUIEditorTools::GetPrefabHelperObject_WhichManageThisActor(Actor)) { if (PrefabHelperObject->CleanupInvalidSubPrefab())//do cleanup before everything else { PrefabHelperObject->Modify(); } bool bIsRootActor = PrefabHelperObject->LoadedRootActor == Actor; if (bIsRootActor) { auto confirmMsg = LOCTEXT("Warning_ReplaceRootActorOfPrefab", "Trying to replace root actor of a prefab, this could cause unexpected error if other prefab or level is referencing this prefab!\ \nDo you want to continue."); auto confirmResult = FMessageDialog::Open(EAppMsgType::YesNo, confirmMsg); if (confirmResult == EAppReturnType::Yes) { auto FindGuid = [&](UObject* Obj) { for (auto& KeyValue : PrefabHelperObject->MapGuidToObject) { if (Obj == KeyValue.Value) { return KeyValue.Key; } } return FGuid(); }; TArray OriginObjects; GetObjectsWithOuter(PrefabHelperObject->LoadedRootActor, OriginObjects); TMap MapObjectNameToGuid; for (auto& Object : OriginObjects) { auto FoundGuid = FindGuid(Object); if (FoundGuid.IsValid()) { MapObjectNameToGuid.Add(Object->GetFName(), FoundGuid); } } FGuid RootActorGuid = FindGuid(PrefabHelperObject->LoadedRootActor); FGuid RootCompGuid = FindGuid(PrefabHelperObject->LoadedRootActor->GetRootComponent()); PrefabHelperObject->SetCanNotifyAttachment(false); ReplacedActor = LGUIEditorToolsHelperFunctionHolder::ReplaceActor({ Actor }, ActorClass)[0]; if (bIsRootActor) { PrefabHelperObject->LoadedRootActor = ReplacedActor; } TArray NewObjects; GetObjectsWithOuter(ReplacedActor, NewObjects); for (auto& KeyValue : MapObjectNameToGuid) { auto FoundIndex = NewObjects.IndexOfByPredicate([=](const UObject* Item) { return Item->GetFName() == KeyValue.Key; }); if (FoundIndex != INDEX_NONE) { PrefabHelperObject->MapGuidToObject[KeyValue.Value] = NewObjects[FoundIndex]; } } PrefabHelperObject->MapGuidToObject[RootActorGuid] = ReplacedActor; PrefabHelperObject->MapGuidToObject[RootCompGuid] = ReplacedActor->GetRootComponent(); PrefabHelperObject->SetCanNotifyAttachment(true); } } else { ReplacedActor = LGUIEditorToolsHelperFunctionHolder::ReplaceActor({ Actor }, ActorClass)[0]; } PrefabHelperObject->SetAnythingDirty(); } else { ReplacedActor = LGUIEditorToolsHelperFunctionHolder::ReplaceActor({ Actor }, ActorClass)[0]; } if (IsValid(ReplacedActor)) { if (auto ReplaceUIItem = Cast(ReplacedActor->GetRootComponent())) { ReplaceUIItem->SetHierarchyIndex(HierarchyIndex); } for (auto& KeyValue : ChildrenOriginPositionArray) { auto UIItem = KeyValue.Key; auto& Value = KeyValue.Value; UIItem->SetRelativeLocation(Value.Get<2>()); UIItem->SetAnchorData(Value.Get<0>()); UIItem->SetHierarchyIndex(Value.Get<1>()); } } } GEditor->EndTransaction(); } void LGUIEditorTools::DuplicateSelectedActors_Impl()//@todo: fix bug: duplicate subprefab then undo, this operation will revert the source copied prefab to orignal state { auto selectedActors = LGUIEditorToolsHelperFunctionHolder::ConvertSelectionToActors(GEditor->GetSelectedActors()); auto count = selectedActors.Num(); if (count == 0) { UE_LOG(LGUIEditor, Error, TEXT("NothingSelected")); return; } auto RootActorList = LGUIEditorTools::GetRootActorListFromSelection(selectedActors); GEditor->BeginTransaction(LOCTEXT("DuplicateActor_Transaction", "LGUI Duplicate Actors")); for (auto Actor : RootActorList) { MakeCurrentLevel(Actor); Actor->GetLevel()->Modify(); auto copiedActorLabel = LGUIEditorToolsHelperFunctionHolder::GetCopiedActorLabel(Actor->GetAttachParentActor(), Actor->GetActorLabel(), Actor->GetWorld()); AActor* copiedActor; USceneComponent* Parent = nullptr; if (Actor->GetAttachParentActor()) { Parent = Actor->GetAttachParentActor()->GetRootComponent(); } TMap, FLGUISubPrefabData> DuplicatedSubPrefabMap; TMap> OutMapGuidToObject; TMap InMapObjectToGuid; if (auto PrefabHelperObject = LGUIEditorTools::GetPrefabHelperObject_WhichManageThisActor(Actor)) { PrefabHelperObject->CleanupInvalidSubPrefab();//do cleanup before everything else PrefabHelperObject->Modify(); PrefabHelperObject->SetCanNotifyAttachment(false); struct LOCAL { static void CollectSubPrefabActors(AActor* InActor, const TMap, FLGUISubPrefabData>& InSubPrefabMap, TArray& OutSubPrefabRootActors) { if (InSubPrefabMap.Contains(InActor)) { OutSubPrefabRootActors.Add(InActor); } else { TArray ChildrenActors; InActor->GetAttachedActors(ChildrenActors); for (auto& ChildActor : ChildrenActors) { CollectSubPrefabActors(ChildActor, InSubPrefabMap, OutSubPrefabRootActors); } } } }; TArray SubPrefabRootActors; LOCAL::CollectSubPrefabActors(Actor, PrefabHelperObject->SubPrefabMap, SubPrefabRootActors);//collect sub prefabs that is attached to this Actor for (auto& SubPrefabKeyValue : PrefabHelperObject->SubPrefabMap)//generate MapObjectToGuid { auto SubPrefabRootActor = SubPrefabKeyValue.Key; if (SubPrefabRootActors.Contains(SubPrefabRootActor)) { auto& SubPrefabData = SubPrefabKeyValue.Value; PrefabHelperObject->RefreshOnSubPrefabDirty(SubPrefabData.PrefabAsset, SubPrefabRootActor);//need to update subprefab to latest before duplicate auto FindObjectGuidInParentPrefab = [&](FGuid InGuidInSubPrefab) { for (auto& KeyValue : SubPrefabData.MapObjectGuidFromParentPrefabToSubPrefab) { if (KeyValue.Value == InGuidInSubPrefab) { return KeyValue.Key; } } UE_LOG(LGUIEditor, Error, TEXT("[LGUIEditorTools::DuplicateSelectedActors_Impl] Should never reach this point!")); FDebug::DumpStackTraceToLog(ELogVerbosity::Warning); return FGuid::NewGuid(); }; for (auto& MapGuidToObjectKeyValue : SubPrefabData.MapGuidToObject) { InMapObjectToGuid.Add(MapGuidToObjectKeyValue.Value, FindObjectGuidInParentPrefab(MapGuidToObjectKeyValue.Key)); } } } copiedActor = LGUIPREFAB_SERIALIZER_NEWEST_NAMESPACE::ActorSerializer::DuplicateActorForEditor(Actor, Parent, PrefabHelperObject->SubPrefabMap, InMapObjectToGuid, DuplicatedSubPrefabMap, OutMapGuidToObject); if (auto UIItem = Cast(copiedActor->GetRootComponent())) { if (auto UIParent = Cast(Parent)) { UIItem->SetAsLastHierarchy(); } } for (auto& KeyValue : DuplicatedSubPrefabMap) { TMap> SubMapGuidToObject; for (auto& MapGuidItem : KeyValue.Value.MapObjectGuidFromParentPrefabToSubPrefab) { SubMapGuidToObject.Add(MapGuidItem.Value, OutMapGuidToObject[MapGuidItem.Key]); } PrefabHelperObject->MakePrefabAsSubPrefab(KeyValue.Value.PrefabAsset, KeyValue.Key, SubMapGuidToObject, KeyValue.Value.ObjectOverrideParameterArray); } PrefabHelperObject->SetCanNotifyAttachment(true); } else { copiedActor = LGUIPREFAB_SERIALIZER_NEWEST_NAMESPACE::ActorSerializer::DuplicateActorForEditor(Actor, Parent, {}, InMapObjectToGuid, DuplicatedSubPrefabMap, OutMapGuidToObject); } copiedActor->SetActorLabel(copiedActorLabel); GEditor->SelectActor(Actor, false, true); GEditor->SelectActor(copiedActor, true, true); } GEditor->EndTransaction(); ULGUIManagerWorldSubsystem::RefreshAllUI(); } void LGUIEditorTools::CopySelectedActors_Impl() { auto selectedActors = LGUIEditorToolsHelperFunctionHolder::ConvertSelectionToActors(GEditor->GetSelectedActors()); auto count = selectedActors.Num(); if (count == 0) { UE_LOG(LGUIEditor, Error, TEXT("NothingSelected")); return; } for (auto KeyValuePair : CopiedActorPrefabMap) { KeyValuePair.Value->RemoveFromRoot(); KeyValuePair.Value->ConditionalBeginDestroy(); } auto CopyActorList = LGUIEditorTools::GetRootActorListFromSelection(selectedActors); CopiedActorPrefabMap.Reset(); for (auto Actor : CopyActorList) { auto prefab = NewObject(); prefab->AddToRoot(); TMap MapObjectToGuid; TMap, FLGUISubPrefabData> SubPrefabMap; if (auto PrefabHelperObject = LGUIEditorTools::GetPrefabHelperObject_WhichManageThisActor(Actor)) { SubPrefabMap = PrefabHelperObject->SubPrefabMap; if (PrefabHelperObject->CleanupInvalidSubPrefab())//do cleanup before everything else { PrefabHelperObject->Modify(); } struct LOCAL { static void CollectSubPrefabActors(AActor* InActor, const TMap, FLGUISubPrefabData>& InSubPrefabMap, TArray& OutSubPrefabRootActors) { if (InSubPrefabMap.Contains(InActor)) { OutSubPrefabRootActors.Add(InActor); } else { TArray ChildrenActors; InActor->GetAttachedActors(ChildrenActors); for (auto& ChildActor : ChildrenActors) { CollectSubPrefabActors(ChildActor, InSubPrefabMap, OutSubPrefabRootActors); } } } }; TArray SubPrefabRootActors; LOCAL::CollectSubPrefabActors(Actor, PrefabHelperObject->SubPrefabMap, SubPrefabRootActors);//collect sub prefabs that is attached to this Actor for (auto& SubPrefabKeyValue : PrefabHelperObject->SubPrefabMap)//generate MapObjectToGuid { auto SubPrefabRootActor = SubPrefabKeyValue.Key; if (SubPrefabRootActors.Contains(SubPrefabRootActor)) { auto& SubPrefabData = SubPrefabKeyValue.Value; PrefabHelperObject->RefreshOnSubPrefabDirty(SubPrefabData.PrefabAsset, SubPrefabRootActor);//need to update subprefab to latest before duplicate auto FindObjectGuidInParentPrefab = [&](FGuid InGuidInSubPrefab) { for (auto& KeyValue : SubPrefabData.MapObjectGuidFromParentPrefabToSubPrefab) { if (KeyValue.Value == InGuidInSubPrefab) { return KeyValue.Key; } } UE_LOG(LGUIEditor, Error, TEXT("[LGUIEditorTools::CopySelectedActors_Impl] Should never reach this point!")); FDebug::DumpStackTraceToLog(ELogVerbosity::Warning); return FGuid::NewGuid(); }; for (auto& MapGuidToObjectKeyValue : SubPrefabData.MapGuidToObject) { MapObjectToGuid.Add(MapGuidToObjectKeyValue.Value, FindObjectGuidInParentPrefab(MapGuidToObjectKeyValue.Key)); } } } } TMap, FLGUISubPrefabData> TempSubPrefabMap; for (auto& SubPrefabKeyValue : SubPrefabMap) { if (SubPrefabKeyValue.Key->IsAttachedTo(Actor) || SubPrefabKeyValue.Key == Actor) { TempSubPrefabMap = SubPrefabMap; break; } } prefab->SavePrefab(Actor, MapObjectToGuid, TempSubPrefabMap); CopiedActorPrefabMap.Add(Actor->GetActorLabel(), prefab); } } void LGUIEditorTools::PasteSelectedActors_Impl() { auto selectedActors = LGUIEditorToolsHelperFunctionHolder::ConvertSelectionToActors(GEditor->GetSelectedActors()); USceneComponent* parentComp = nullptr; if (selectedActors.Num() > 0) { parentComp = selectedActors[0]->GetRootComponent(); } ULGUIPrefabHelperObject* PrefabHelperObject = nullptr; if (parentComp) { PrefabHelperObject = LGUIEditorTools::GetPrefabHelperObject_WhichManageThisActor(parentComp->GetOwner()); } if (!PrefabHelperObject) { UWorld* World = nullptr; if (parentComp != nullptr) { World = parentComp->GetWorld(); } else { World = GWorld; } if (World) { if (auto Level = World->GetCurrentLevel()) { if (auto ManagerActor = ALGUIPrefabLevelManagerActor::GetInstance(Level)) { PrefabHelperObject = ManagerActor->PrefabHelperObject; } } } } if (PrefabHelperObject == nullptr)return; PrefabHelperObject->SetCanNotifyAttachment(false); GEditor->BeginTransaction(LOCTEXT("PasteActor_Transaction", "LGUI Paste Actors")); for (auto item : selectedActors) { GEditor->SelectActor(item, false, true); } if (IsValid(parentComp)) { MakeCurrentLevel(parentComp->GetOwner()); } for (auto KeyValuePair : CopiedActorPrefabMap) { if (KeyValuePair.Value.IsValid()) { TMap> OutMapGuidToObject; TMap, FLGUISubPrefabData> LoadedSubPrefabMap; auto copiedActorLabel = LGUIEditorToolsHelperFunctionHolder::GetCopiedActorLabel(parentComp->GetOwner(), KeyValuePair.Key, parentComp->GetWorld()); auto copiedActor = KeyValuePair.Value->LoadPrefabInEditor(parentComp->GetWorld(), parentComp, LoadedSubPrefabMap, OutMapGuidToObject, false); for (auto& KeyValue : LoadedSubPrefabMap) { TMap> SubMapGuidToObject; for (auto& MapGuidItem : KeyValue.Value.MapObjectGuidFromParentPrefabToSubPrefab) { SubMapGuidToObject.Add(MapGuidItem.Value, OutMapGuidToObject[MapGuidItem.Key]); } PrefabHelperObject->MakePrefabAsSubPrefab(KeyValue.Value.PrefabAsset, KeyValue.Key, SubMapGuidToObject, KeyValue.Value.ObjectOverrideParameterArray); } copiedActor->SetActorLabel(copiedActorLabel); GEditor->SelectActor(copiedActor, true, true, true); } else { UE_LOG(LGUIEditor, Error, TEXT("Source copied actor is missing!")); } } PrefabHelperObject->SetCanNotifyAttachment(true); GEditor->EndTransaction(); ULGUIManagerWorldSubsystem::RefreshAllUI(); } void LGUIEditorTools::DeleteSelectedActors_Impl() { auto selectedActors = LGUIEditorToolsHelperFunctionHolder::ConvertSelectionToActors(GEditor->GetSelectedActors()); DeleteActors_Impl(selectedActors); } void LGUIEditorTools::CutSelectedActors_Impl() { CopySelectedActors_Impl(); DeleteSelectedActors_Impl(); } void LGUIEditorTools::ToggleSelectedActorsSpatiallyLoaded_Impl() { auto selectedActors = LGUIEditorToolsHelperFunctionHolder::ConvertSelectionToActors(GEditor->GetSelectedActors()); auto count = selectedActors.Num(); if (count == 0) { UE_LOG(LGUIEditor, Error, TEXT("NothingSelected")); return; } struct LOCAL { static void SetSpatiallyLoadedValue_Recursive(AActor* Actor, bool value) { if (Actor->CanChangeIsSpatiallyLoadedFlag()) { if (Actor->GetIsSpatiallyLoaded() != value) { Actor->SetIsSpatiallyLoaded(value); LGUIUtils::NotifyPropertyChanged(Actor, AActor::GetIsSpatiallyLoadedPropertyName()); } } TArray ChildActors; Actor->GetAttachedActors(ChildActors); for (auto ChildActor : ChildActors) { if (IsValid(ChildActor)) { SetSpatiallyLoadedValue_Recursive(ChildActor, value); } } } }; GEditor->BeginTransaction(LOCTEXT("ToggleSpatiallyLoaded_Transaction", "LGUI Toggle Actors IsSpatiallyLoaded")); auto ActorList = LGUIEditorTools::GetRootActorListFromSelection(selectedActors); for (auto Actor : ActorList) { Actor->Modify(); auto bIsSpatiallyLoadedValue = !Actor->GetIsSpatiallyLoaded(); LOCAL::SetSpatiallyLoadedValue_Recursive(Actor, bIsSpatiallyLoadedValue); } GEditor->EndTransaction(); } ECheckBoxState LGUIEditorTools::GetActorSpatiallyLoadedProperty() { auto selectedActors = LGUIEditorToolsHelperFunctionHolder::ConvertSelectionToActors(GEditor->GetSelectedActors()); auto count = selectedActors.Num(); if (count == 0) { return ECheckBoxState::Undetermined; } auto ActorList = LGUIEditorTools::GetRootActorListFromSelection(selectedActors); bool bIsSpatiallyLoadedValue = ActorList[0]->GetIsSpatiallyLoaded(); for (int i = 1; i < ActorList.Num(); i++) { if (bIsSpatiallyLoadedValue != ActorList[i]->GetIsSpatiallyLoaded()) { return ECheckBoxState::Undetermined; } } return bIsSpatiallyLoadedValue ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } void LGUIEditorTools::DeleteActors_Impl(const TArray& InActors) { auto count = InActors.Num(); if (count == 0) { UE_LOG(LGUIEditor, Error, TEXT("NothingSelected")); return; } auto confirmMsg = FString::Printf(TEXT("Destroy selected actors? This will also destroy the children attached to selected actors.")); auto confirmResult = FMessageDialog::Open(EAppMsgType::YesNo, FText::FromString(confirmMsg)); if (confirmResult == EAppReturnType::No)return; ULGUIPrefabManagerObject::GetInstance(true)->bIsProcessingDelete = true; auto RootActorList = LGUIEditorTools::GetRootActorListFromSelection(InActors); GEditor->BeginTransaction(LOCTEXT("DestroyActor_Transaction", "LGUI Destroy Actor")); GEditor->GetSelectedActors()->DeselectAll(); for (auto Actor : RootActorList) { bool shouldDeletePrefab = false; auto PrefabHelperObject = LGUIEditorTools::GetPrefabHelperObject_WhichManageThisActor(Actor); if (PrefabHelperObject != nullptr) { PrefabHelperObject->Modify(); PrefabHelperObject->SetAnythingDirty(); TArray ChildrenActors; LGUIUtils::CollectChildrenActors(Actor, ChildrenActors); for (auto ChildActor : ChildrenActors) { PrefabHelperObject->RemoveSubPrefabByAnyActorOfSubPrefab(ChildActor); } LGUIUtils::DestroyActorWithHierarchy(Actor); } else//common actor { LGUIUtils::DestroyActorWithHierarchy(Actor); } } GEditor->EndTransaction(); CleanupPrefabsInWorld(RootActorList[0]->GetWorld()); ULGUIPrefabManagerObject::GetInstance(true)->bIsProcessingDelete = false; } bool LGUIEditorTools::CanDuplicateActor() { auto SelectedActor = LGUIEditorTools::GetFirstSelectedActor(); if (SelectedActor == nullptr)return false; if (!LGUIEditorTools::IsActorCompatibleWithLGUIToolsMenu(SelectedActor))return false; return true; } bool LGUIEditorTools::CanCopyActor() { auto SelectedActors = LGUIEditorTools::GetSelectedActors(); if (SelectedActors.Num() <= 0)return false; return true; } bool LGUIEditorTools::CanPasteActor() { if (LGUIEditorTools::CopiedActorPrefabMap.Num() == 0)return false; auto SelectedActor = LGUIEditorTools::GetFirstSelectedActor(); if (SelectedActor == nullptr)return false; if (!LGUIEditorTools::IsActorCompatibleWithLGUIToolsMenu(SelectedActor))return false; return true; } bool LGUIEditorTools::CanCutActor() { return CanDeleteActor(); } bool LGUIEditorTools::CanDeleteActor() { auto SelectedActors = LGUIEditorTools::GetSelectedActors(); if (SelectedActors.Num() == 0)return false; for (auto Actor : SelectedActors) { if (auto PrefabHelperObject = LGUIEditorTools::GetPrefabHelperObject_WhichManageThisActor(Actor)) { if (!PrefabHelperObject->ActorIsSubPrefabRootActor(Actor)//allowed to delete sub prefab's root actor && PrefabHelperObject->IsActorBelongsToSubPrefab(Actor))//not allowed to delete sub prefab's actor { return false; } } } return true; } bool LGUIEditorTools::CanToggleActorSpatiallyLoaded() { auto selectedActors = LGUIEditorToolsHelperFunctionHolder::ConvertSelectionToActors(GEditor->GetSelectedActors()); if (selectedActors.Num() <= 0)return false; auto ActorList = LGUIEditorTools::GetRootActorListFromSelection(selectedActors); for (auto Actor : ActorList) { if (!Actor->CanChangeIsSpatiallyLoadedFlag()) { return false; } } return true; } void LGUIEditorTools::CopyComponentValues_Impl() { auto selectedComponents = LGUIEditorToolsHelperFunctionHolder::ConvertSelectionToComponents(GEditor->GetSelectedComponents()); auto count = selectedComponents.Num(); if (count == 0) { UE_LOG(LGUIEditor, Error, TEXT("NothingSelected")); return; } else if (count > 1) { UE_LOG(LGUIEditor, Error, TEXT("Only support one component")); return; } CopiedComponent = selectedComponents[0]; } void LGUIEditorTools::PasteComponentValues_Impl() { auto selectedComponents = LGUIEditorToolsHelperFunctionHolder::ConvertSelectionToComponents(GEditor->GetSelectedComponents()); auto count = selectedComponents.Num(); if (count == 0) { UE_LOG(LGUIEditor, Error, TEXT("NothingSelected")); return; } if (CopiedComponent.IsValid()) { GEditor->BeginTransaction(LOCTEXT("PasteComponentValues_Transaction", "LGUI Paste Component Proeprties")); UEngine::FCopyPropertiesForUnrelatedObjectsParams Options; Options.bNotifyObjectReplacement = true; for (UActorComponent* SelectedComp : selectedComponents) { if (SelectedComp->IsRegistered() && SelectedComp->AllowReregistration()) { SelectedComp->UnregisterComponent(); } UEditorEngine::CopyPropertiesForUnrelatedObjects(CopiedComponent.Get(), SelectedComp, Options); if (!SelectedComp->IsRegistered()) { SelectedComp->RegisterComponent(); } } GEditor->EndTransaction(); ULGUIManagerWorldSubsystem::RefreshAllUI(); } else { UE_LOG(LGUIEditor, Error, TEXT("Selected component is missing!")); } } void LGUIEditorTools::OpenAtlasViewer_Impl() { FGlobalTabmanager::Get()->TryInvokeTab(FLGUIEditorModule::LGUIDynamicSpriteAtlasViewerName); } void LGUIEditorTools::ChangeTraceChannel_Impl(ETraceTypeQuery InTraceTypeQuery) { auto selectedActors = LGUIEditorToolsHelperFunctionHolder::ConvertSelectionToActors(GEditor->GetSelectedActors()); auto count = selectedActors.Num(); if (count == 0) { UE_LOG(LGUIEditor, Error, TEXT("NothingSelected")); return; } struct FunctionContainer { static void ChangeTraceChannel(USceneComponent* InSceneComp, ETraceTypeQuery InChannel) { if (IsValid(InSceneComp)) { if (auto uiItemComp = Cast(InSceneComp)) { uiItemComp->SetTraceChannel(InChannel); } auto& children = InSceneComp->GetAttachChildren(); for (auto itemComp : children) { ChangeTraceChannel(itemComp, InChannel); } } } }; auto RootActorList = LGUIEditorTools::GetRootActorListFromSelection(selectedActors); GEditor->BeginTransaction(LOCTEXT("ChangeTraceChannel_Transaction", "LGUI Change Trace Channel")); for (auto item : RootActorList) { FunctionContainer::ChangeTraceChannel(item->GetRootComponent(), InTraceTypeQuery); } GEditor->EndTransaction(); } void LGUIEditorTools::CreateScreenSpaceUI_BasicSetup() { FString prefabPath(TEXT("/LGUI/Prefabs/ScreenSpaceUI")); auto prefab = LoadObject(NULL, *prefabPath); if (prefab) { ETraceTypeQuery LGUITraceTypeQuery; auto bIsTraceTypeQueryValid = CreateTraceChannel_BasicSetup(LGUITraceTypeQuery); GEditor->BeginTransaction(FText::FromString(TEXT("LGUI Create Screen Space UI"))); auto actor = prefab->LoadPrefabInEditor(GetWorldFromSelection(), nullptr, true); actor->GetRootComponent()->SetRelativeScale3D(FVector::OneVector); actor->GetRootComponent()->SetRelativeLocation(FVector(0, 0, 250)); if (bIsTraceTypeQueryValid) { SetTraceChannel(actor, LGUITraceTypeQuery); SetTraceChannelToParent_Recursive(actor); } if (auto selectedActor = GetFirstSelectedActor()) { GEditor->SelectActor(selectedActor, false, true); } GEditor->SelectActor(actor, true, true); CreatePresetEventSystem_BasicSetup(); GEditor->EndTransaction(); ULGUIManagerWorldSubsystem::RefreshAllUI(); } else { UE_LOG(LGUIEditor, Error, TEXT("[LGUIEditorToolsAgentObject::CreateScreenSpaceUI_BasicSetup]Load control prefab error! Path:%s. Missing some content of LGUI plugin, reinstall this plugin may fix the issue."), *prefabPath); } } void LGUIEditorTools::CreateWorldSpaceUIUERenderer_BasicSetup() { FString prefabPath(TEXT("/LGUI/Prefabs/WorldSpaceUI_UERenderer")); auto prefab = LoadObject(NULL, *prefabPath); if (prefab) { ETraceTypeQuery LGUITraceTypeQuery; auto bIsTraceTypeQueryValid = CreateTraceChannel_BasicSetup(LGUITraceTypeQuery); GEditor->BeginTransaction(FText::FromString(TEXT("LGUI Create World Space UI - UE Renderer"))); auto actor = prefab->LoadPrefabInEditor(GetWorldFromSelection(), nullptr, true); actor->GetRootComponent()->SetRelativeLocation(FVector(0, 0, 250)); actor->GetRootComponent()->SetWorldScale3D(FVector::OneVector); if (bIsTraceTypeQueryValid) { SetTraceChannel(actor, LGUITraceTypeQuery); SetTraceChannelToParent_Recursive(actor); } if (auto selectedActor = GetFirstSelectedActor()) { GEditor->SelectActor(selectedActor, false, true); } GEditor->SelectActor(actor, true, true); CreatePresetEventSystem_BasicSetup(); GEditor->EndTransaction(); ULGUIManagerWorldSubsystem::RefreshAllUI(); } else { UE_LOG(LGUIEditor, Error, TEXT("[LGUIEditorToolsAgentObject::CreateWorldSpaceUIUERenderer_BasicSetup]Load control prefab error! Path:%s. Missing some content of LGUI plugin, reinstall this plugin may fix the issue."), *prefabPath); } } void LGUIEditorTools::CreateWorldSpaceUILGUIRenderer_BasicSetup() { FString prefabPath(TEXT("/LGUI/Prefabs/WorldSpaceUI_LGUIRenderer")); auto prefab = LoadObject(NULL, *prefabPath); if (prefab) { ETraceTypeQuery LGUITraceTypeQuery; auto bIsTraceTypeQueryValid = CreateTraceChannel_BasicSetup(LGUITraceTypeQuery); GEditor->BeginTransaction(FText::FromString(TEXT("LGUI Create World Space UI - LGUI Renderer"))); auto actor = prefab->LoadPrefabInEditor(GetWorldFromSelection(), nullptr, true); actor->GetRootComponent()->SetRelativeLocation(FVector(0, 0, 250)); actor->GetRootComponent()->SetWorldScale3D(FVector::OneVector); if (bIsTraceTypeQueryValid) { SetTraceChannel(actor, LGUITraceTypeQuery); SetTraceChannelToParent_Recursive(actor); } if (auto selectedActor = GetFirstSelectedActor()) { GEditor->SelectActor(selectedActor, false, true); } GEditor->SelectActor(actor, true, true); CreatePresetEventSystem_BasicSetup(); GEditor->EndTransaction(); ULGUIManagerWorldSubsystem::RefreshAllUI(); } else { UE_LOG(LGUIEditor, Error, TEXT("[LGUIEditorToolsAgentObject::CreateWorldSpaceUILGUIRenderer_BasicSetup]Load control prefab error! Path:%s. Missing some content of LGUI plugin, reinstall this plugin may fix the issue."), *prefabPath); } } void LGUIEditorTools::CreatePresetEventSystem_BasicSetup() { bool haveEventSystem = false; for (TActorIterator eventSysActorItr(GetWorldFromSelection()); eventSysActorItr; ++eventSysActorItr) { haveEventSystem = true; break; } if (!haveEventSystem) { if (auto presetEventSystemActorClass = LoadObject(NULL, TEXT("/LGUI/Blueprints/PresetEventSystemActor.PresetEventSystemActor_C"))) { GetWorldFromSelection()->SpawnActor(presetEventSystemActorClass); } else { UE_LOG(LGUIEditor, Error, TEXT("[ULGUIEditorToolsAgentObject::CreateWorldSpaceUILGUIRenderer_BasicSetup]Load PresetEventSystemActor error! Missing some content of LGUI plugin, reinstall this plugin may fix the issue.")); } } } bool LGUIEditorTools::CreateTraceChannel_BasicSetup(ETraceTypeQuery& OutTraceTypeQuery) { enum class ELGUIChannelErrorType { NoError, NoLGUIChannel, ChannelIsNotTrace, }; const auto DefaultChannelResponsesName = FName(TEXT("DefaultChannelResponses")); auto OnLGUIChannel = [=](const TFunction& OnDefaultResponseProperty, ECollisionChannel& OutChannel) { auto DefaultChannelResponses_Property = FindFProperty(UCollisionProfile::StaticClass(), DefaultChannelResponsesName); auto CollisionProfile = UCollisionProfile::Get(); FScriptArrayHelper ArrayHelper(DefaultChannelResponses_Property, DefaultChannelResponses_Property->ContainerPtrToValuePtr(CollisionProfile)); for (int i = 0; i < ArrayHelper.Num(); i++) { if (auto StructProperty = CastField(DefaultChannelResponses_Property->Inner)) { auto StructPtr = StructProperty->ContainerPtrToValuePtr(ArrayHelper.GetRawPtr(i)); FByteProperty* ChannelProperty = nullptr; FNameProperty* DisplayNameProperty = nullptr; FByteProperty* DefaultResponseProperty = nullptr; FBoolProperty* TraceTypeProperty = nullptr; for (TFieldIterator It(StructProperty->Struct); It; ++It) { if (It->GetFName() == TEXT("Name")) { if (auto NameProperty = CastField(*It)) { DisplayNameProperty = NameProperty; } } else if (It->GetFName() == TEXT("Channel")) { if (auto ByteProperty = CastField(*It)) { ChannelProperty = ByteProperty; } } else if (It->GetFName() == TEXT("DefaultResponse")) { if (auto ByteProperty = CastField(*It)) { DefaultResponseProperty = ByteProperty; } } else if (It->GetFName() == TEXT("bTraceType")) { if (auto BoolProperty = CastField(*It)) { TraceTypeProperty = BoolProperty; } } } if (DisplayNameProperty != nullptr && DefaultResponseProperty != nullptr && TraceTypeProperty != nullptr && ChannelProperty != nullptr) { if (DisplayNameProperty->GetPropertyValue_InContainer(StructPtr) == TEXT("LGUI")) { if (TraceTypeProperty->GetPropertyValue_InContainer(StructPtr) == true) { OnDefaultResponseProperty(DefaultResponseProperty, StructPtr); OutChannel = (ECollisionChannel)ChannelProperty->GetPropertyValue_InContainer(StructPtr); return ELGUIChannelErrorType::NoError; } else { return ELGUIChannelErrorType::ChannelIsNotTrace; } } } } } return ELGUIChannelErrorType::NoLGUIChannel; }; auto GetLGUIChannelResponse = [=](ECollisionResponse& Response, ECollisionChannel& OutChannelIndex) { return OnLGUIChannel([&](FByteProperty* DefaultResponseProperty, void* StructPtr) { DefaultResponseProperty->GetValue_InContainer(StructPtr, (uint8*)&Response); }, OutChannelIndex); }; auto SetLGUIChannelResponse = [=](ECollisionChannel& OutChannelIndex) { return OnLGUIChannel([](FByteProperty* DefaultResponseProperty, void* StructPtr) { auto Response = ECollisionResponse::ECR_Ignore; DefaultResponseProperty->SetValue_InContainer(StructPtr, (uint8)Response); }, OutChannelIndex); }; ECollisionResponse Response = ECollisionResponse::ECR_MAX; ECollisionChannel TraceChannel = ECollisionChannel::ECC_MAX; auto ChannelErrorType = GetLGUIChannelResponse(Response, TraceChannel); switch (ChannelErrorType) { case ELGUIChannelErrorType::NoError: { if (Response != ECollisionResponse::ECR_Ignore) { auto Message = LOCTEXT("RecommandLGUITraceChannelSettings", "It is recommanded to set \"Default Response\" of LGUI trace channel to \"Ignore\"."); FMessageDialog::Open(EAppMsgType::Ok, Message); auto CollisionProfile = UCollisionProfile::Get(); OutTraceTypeQuery = CollisionProfile->ConvertToTraceType(TraceChannel); return true; } else { auto CollisionProfile = UCollisionProfile::Get(); OutTraceTypeQuery = CollisionProfile->ConvertToTraceType(TraceChannel); return true; } } break; case ELGUIChannelErrorType::NoLGUIChannel: { auto Message = LOCTEXT("RecommandCreateLGUITraceChannel", "It is recommanded to create a specific trace channel for LGUI, with name \"LGUI\", and default response \"Ignore\"."); FMessageDialog::Open(EAppMsgType::Ok, Message); } break; case ELGUIChannelErrorType::ChannelIsNotTrace: { auto Message = LOCTEXT("LGUIChannelIsNotTraceType", "\ Trying to use \"LGUI\" as trace channel, but detect a collision channel with name \"LGUI\"!\n\ It is recommanded to create a specific trace channel for LGUI, with name \"LGUI\", and default response \"Ignore\".\ "); FMessageDialog::Open(EAppMsgType::Ok, Message); } break; } return false; } void LGUIEditorTools::AttachComponentToSelectedActor(TSubclassOf InComponentClass) { GEditor->BeginTransaction(FText::FromString(TEXT("LGUI Attach Component to Actor"))); auto selectedActors = LGUIEditorToolsHelperFunctionHolder::ConvertSelectionToActors(GEditor->GetSelectedActors()); auto count = selectedActors.Num(); if (count == 0) { UE_LOG(LGUIEditor, Error, TEXT("NothingSelected")); return; } UActorComponent* lastCreatedComponent = nullptr; for (auto actor : selectedActors) { if (IsValid(actor)) { auto comp = NewObject(actor, InComponentClass, *FComponentEditorUtils::GenerateValidVariableName(InComponentClass, actor), RF_Transactional); actor->AddInstanceComponent(comp); comp->RegisterComponent(); lastCreatedComponent = comp; } } GEditor->EndTransaction(); if (selectedActors.Num() == 1) { GEditor->SelectNone(true, true); GEditor->SelectActor(lastCreatedComponent->GetOwner(), true, true, false, true); GEditor->SelectComponent(lastCreatedComponent, true, true, false); } } bool LGUIEditorTools::HaveValidCopiedActors() { if (CopiedActorPrefabMap.Num() == 0)return false; for (auto KeyValuePair : CopiedActorPrefabMap) { if (!KeyValuePair.Value.IsValid()) { return false; } } return true; } bool LGUIEditorTools::HaveValidCopiedComponent() { return CopiedComponent.IsValid(); } FString LGUIEditorTools::PrevSavePrafabFolder = TEXT(""); void LGUIEditorTools::CreatePrefabAsset()//@todo: make some referenced parameter as override parameter(eg: Actor parameter reference other actor that is not belongs to prefab hierarchy) { auto selectedActor = GetFirstSelectedActor(); if (!IsValid(selectedActor)) { return; } if (Cast(selectedActor) != nullptr || Cast(selectedActor) != nullptr) { auto Message = LOCTEXT("CreatePrefabError_PrefabActor", "Cannot create prefab on a LGUIPrefabLoadHelperActor or LGUIPrefabLevelManagerActor!"); FMessageDialog::Open(EAppMsgType::Ok, Message); return; } auto OldPrefabHelperObject = GetPrefabHelperObject_WhichManageThisActor(selectedActor); if (IsValid(OldPrefabHelperObject) && OldPrefabHelperObject->LoadedRootActor == selectedActor)//If create prefab from an existing prefab's root actor, this is not allowed { auto Message = LOCTEXT("CreatePrefabError_BelongToOtherPrefab", "This actor is a root actor of another prefab, this is not allowed! Instead you can duplicate the prefab asset."); FMessageDialog::Open(EAppMsgType::Ok, Message); return; } IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); if (DesktopPlatform) { TArray OutFileNames; DesktopPlatform->SaveFileDialog( FSlateApplication::Get().FindBestParentWindowHandleForDialogs(FSlateApplication::Get().GetGameViewport()), TEXT("Choose a path to save prefab asset, must inside Content folder"), PrevSavePrafabFolder.IsEmpty() ? FPaths::ProjectContentDir() : PrevSavePrafabFolder, selectedActor->GetActorLabel() + TEXT("_Prefab"), TEXT("*.*"), EFileDialogFlags::None, OutFileNames ); if (OutFileNames.Num() > 0) { FString selectedFilePath = OutFileNames[0]; if (selectedFilePath.StartsWith(FPaths::ProjectContentDir())) { PrevSavePrafabFolder = FPaths::GetPath(selectedFilePath); if (FPaths::FileExists(selectedFilePath + TEXT(".uasset"))) { auto returnValue = FMessageDialog::Open(EAppMsgType::YesNo , FText::Format(LOCTEXT("Error_AssetAlreadyExist", "Asset already exist at path: \"{0}\" !\nReplace it?"), FText::FromString(selectedFilePath))); if (returnValue == EAppReturnType::No) { return; } } selectedFilePath.RemoveFromStart(FPaths::ProjectContentDir(), ESearchCase::CaseSensitive); FString packageName = TEXT("/Game/") + selectedFilePath; UPackage* package = CreatePackage(*packageName); if (package == nullptr) { FMessageDialog::Open(EAppMsgType::Ok , LOCTEXT("Error_NotValidPathForSavePrefab", "Selected path not valid, please choose another path to save prefab.")); return; } package->FullyLoad(); FString fileName = FPaths::GetBaseFilename(selectedFilePath); auto OutPrefab = NewObject(package, ULGUIPrefab::StaticClass(), *fileName, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone); FAssetRegistryModule::AssetCreated(OutPrefab); auto PrefabHelperObjectWhichManageThisActor = LGUIEditorTools::GetPrefabHelperObject_WhichManageThisActor(selectedActor); if (PrefabHelperObjectWhichManageThisActor == nullptr)//not exist, means in level editor and not create PrefabManagerActor yet, so create it { auto ManagerActor = ALGUIPrefabLevelManagerActor::GetInstance(selectedActor->GetLevel()); if (ManagerActor != nullptr) { PrefabHelperObjectWhichManageThisActor = ManagerActor->PrefabHelperObject; } } check(PrefabHelperObjectWhichManageThisActor != nullptr) { struct LOCAL { static auto Make_MapGuidFromParentToSub(const TMap& InNewParentMapObjectToGuid, ULGUIPrefabHelperObject* InPrefabHelperObject, const FLGUISubPrefabData& InOriginSubPrefabData) { TMap Result; for (auto& KeyValue : InOriginSubPrefabData.MapObjectGuidFromParentPrefabToSubPrefab) { auto Object = InPrefabHelperObject->MapGuidToObject[KeyValue.Key]; if (IsValid(Object)) { auto Guid = InNewParentMapObjectToGuid[Object]; if (!Result.Contains(Guid)) { Result.Add(Guid, KeyValue.Value); } } } return Result; } static void CollectSubPrefab(AActor* InActor, TMap, FLGUISubPrefabData>& InOutSubPrefabMap, ULGUIPrefabHelperObject* InPrefabHelperObject, const TMap& InMapObjectToGuid) { if (InPrefabHelperObject->IsActorBelongsToSubPrefab(InActor)) { auto OriginSubPrefabData = InPrefabHelperObject->GetSubPrefabData(InActor); FLGUISubPrefabData SubPrefabData; SubPrefabData.PrefabAsset = OriginSubPrefabData.PrefabAsset; SubPrefabData.ObjectOverrideParameterArray = OriginSubPrefabData.ObjectOverrideParameterArray; SubPrefabData.MapObjectGuidFromParentPrefabToSubPrefab = Make_MapGuidFromParentToSub(InMapObjectToGuid, InPrefabHelperObject, OriginSubPrefabData); InOutSubPrefabMap.Add(InActor, SubPrefabData); return; } TArray ChildrenActors; InActor->GetAttachedActors(ChildrenActors); for (auto ChildActor : ChildrenActors) { CollectSubPrefab(ChildActor, InOutSubPrefabMap, InPrefabHelperObject, InMapObjectToGuid);//collect all actor, include subprefab's actor } } }; TMap, FLGUISubPrefabData> SubPrefabMap; TMap MapObjectToGuid; OutPrefab->SavePrefab(selectedActor, MapObjectToGuid, SubPrefabMap);//save prefab first step, just collect guid and sub prefab LOCAL::CollectSubPrefab(selectedActor, SubPrefabMap, PrefabHelperObjectWhichManageThisActor, MapObjectToGuid); for (auto& KeyValue : SubPrefabMap) { PrefabHelperObjectWhichManageThisActor->RemoveSubPrefabByAnyActorOfSubPrefab(KeyValue.Key);//remove prefab from origin PrefabHelperObject } OutPrefab->SavePrefab(selectedActor, MapObjectToGuid, SubPrefabMap);//save prefab second step, store sub prefab data OutPrefab->RefreshAgentObjectsInPreviewWorld(); //make it as subprefab TMap> MapGuidToObject; for (auto KeyValue : MapObjectToGuid) { MapGuidToObject.Add(KeyValue.Value, KeyValue.Key); } PrefabHelperObjectWhichManageThisActor->MakePrefabAsSubPrefab(OutPrefab, selectedActor, MapGuidToObject, {}); if (auto PrefabManagerActor = ALGUIPrefabLevelManagerActor::GetInstanceByPrefabHelperObject(PrefabHelperObjectWhichManageThisActor)) { PrefabManagerActor->MarkPackageDirty(); } if (OldPrefabHelperObject != nullptr && OldPrefabHelperObject->PrefabAsset != nullptr) { if (auto PrefabEditor = FLGUIPrefabEditor::GetEditorForPrefabIfValid(OldPrefabHelperObject->PrefabAsset))//if is create prefab inside a prefab editor, then apply the prefab editor { PrefabEditor->ApplyPrefab(); } } } CleanupPrefabsInWorld(selectedActor->GetWorld()); } else { FMessageDialog::Open(EAppMsgType::Ok , LOCTEXT("Error_PrefabSaveLocation", "Prefab should only save inside Content folder!")); } } } } void LGUIEditorTools::RefreshLevelLoadedPrefab(ULGUIPrefab* InPrefab) { for (TObjectIterator Itr; Itr; ++Itr) { if (Itr->GetIsManagerObject()) { if (!Itr->IsInsidePrefabEditor()) { Itr->CheckPrefabVersion(); } } } } void LGUIEditorTools::RefreshOpenedPrefabEditor(ULGUIPrefab* InPrefab) { if (auto PrefabEditor = FLGUIPrefabEditor::GetEditorForPrefabIfValid(InPrefab))//refresh opened prefab { if (PrefabEditor->GetAnythingDirty()) { auto Msg = LOCTEXT("PrefabEditorChangedDataWillLose", "Prefab editor will automaticallly refresh changed prefab, but detect some data changed in prefab editor, refresh the prefab editor will lose these data, do you want to continue?"); auto Result = FMessageDialog::Open(EAppMsgType::YesNo, Msg); if (Result == EAppReturnType::Yes) { //reopen this prefab editor PrefabEditor->CloseWithoutCheckDataDirty(); UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); AssetEditorSubsystem->OpenEditorForAsset(InPrefab); } } else { //reopen this prefab editor PrefabEditor->CloseWithoutCheckDataDirty(); UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); AssetEditorSubsystem->OpenEditorForAsset(InPrefab); } } } void LGUIEditorTools::RefreshOnSubPrefabChange(ULGUIPrefab* InSubPrefab) { auto AllPrefabs = GetAllPrefabArray(); struct Local { public: static void RefreshAllPrefabsOnSubPrefabChange(const TArray& InPrefabs, ULGUIPrefab* InSubPrefab) { for (auto& Prefab : InPrefabs) { if (Prefab->IsPrefabBelongsToThisSubPrefab(InSubPrefab, false)) { //check if is opened by prefab editor if (auto PrefabEditor = FLGUIPrefabEditor::GetEditorForPrefabIfValid(Prefab))//refresh opened prefab { PrefabEditor->RefreshOnSubPrefabDirty(InSubPrefab); } else { //Why comment this? Because we don't need to refresh un-opened prefab, because prefab will reload all sub prefab when open //if (Prefab->RefreshOnSubPrefabDirty(InSubPrefab)) //{ // RefreshAllPrefabsOnSubPrefabChange(InPrefabs, Prefab); //} } RefreshAllPrefabsOnSubPrefabChange(InPrefabs, Prefab); } } } }; Local::RefreshAllPrefabsOnSubPrefabChange(AllPrefabs, InSubPrefab); } TArray LGUIEditorTools::GetAllPrefabArray() { FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(FName("AssetRegistry")); IAssetRegistry& AssetRegistry = AssetRegistryModule.Get(); // Need to do this if running in the editor with -game to make sure that the assets in the following path are available TArray PathsToScan; PathsToScan.Add(TEXT("/Game/")); AssetRegistry.ScanPathsSynchronous(PathsToScan); // Get asset in path TArray ScriptAssetList; AssetRegistry.GetAssetsByPath(FName("/Game/"), ScriptAssetList, /*bRecursive=*/true); TArray AllPrefabs; auto PrefabClassName = ULGUIPrefab::StaticClass()->GetClassPathName(); // Ensure all assets are loaded for (const FAssetData& Asset : ScriptAssetList) { // Gets the loaded asset, loads it if necessary if (Asset.AssetClassPath == PrefabClassName) { auto AssetObject = Asset.GetAsset(); if (auto Prefab = Cast(AssetObject)) { Prefab->MakeAgentObjectsInPreviewWorld(); AllPrefabs.Add(Prefab); } } } //collect prefabs that are not saved to disc yet for (TObjectIterator Itr; Itr; ++Itr) { if (!AllPrefabs.Contains(*Itr)) { AllPrefabs.Add(*Itr); } } return AllPrefabs; } void LGUIEditorTools::UnpackPrefab() { GEditor->BeginTransaction(FText::FromString(TEXT("LGUI UnpackPrefab"))); auto SelectedActor = GetFirstSelectedActor(); if (SelectedActor == nullptr)return; auto PrefabHelperObject = LGUIEditorTools::GetPrefabHelperObject_WhichManageThisActor(SelectedActor); if (PrefabHelperObject != nullptr) { check(PrefabHelperObject->SubPrefabMap.Contains(SelectedActor) || PrefabHelperObject->MissingPrefab.Contains(SelectedActor));//should already filtered by menu PrefabHelperObject->Modify(); PrefabHelperObject->RemoveSubPrefabByRootActor(SelectedActor);//the SelectedActor must be root actor, should already filtered by menu } GEditor->EndTransaction(); CleanupPrefabsInWorld(SelectedActor->GetWorld()); } void LGUIEditorTools::SelectPrefabAsset() { GEditor->BeginTransaction(FText::FromString(TEXT("LGUI SelectPrefabAsset"))); auto SelectedActor = GetFirstSelectedActor(); if (SelectedActor == nullptr)return; auto PrefabHelperObject = LGUIEditorTools::GetPrefabHelperObject_WhichManageThisActor(SelectedActor); if (PrefabHelperObject != nullptr) { check(PrefabHelperObject->SubPrefabMap.Contains(SelectedActor));//should have being checked in Browse button auto PrefabAsset = PrefabHelperObject->GetSubPrefabAsset(SelectedActor); if (IsValid(PrefabAsset)) { TArray ObjectsToSync; ObjectsToSync.Add(PrefabAsset); GEditor->SyncBrowserToObjects(ObjectsToSync); } } GEditor->EndTransaction(); } void LGUIEditorTools::OpenPrefabAsset() { auto SelectedActor = GetFirstSelectedActor(); if (SelectedActor == nullptr)return; auto PrefabHelperObject = LGUIEditorTools::GetPrefabHelperObject_WhichManageThisActor(SelectedActor); if (PrefabHelperObject != nullptr) { check(PrefabHelperObject->SubPrefabMap.Contains(SelectedActor));//should have being check in menu auto PrefabAsset = PrefabHelperObject->GetSubPrefabAsset(SelectedActor); if (IsValid(PrefabAsset)) { UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); AssetEditorSubsystem->OpenEditorForAsset(PrefabAsset); } } } void LGUIEditorTools::UpdateLevelPrefab() { auto SelectedActor = GetFirstSelectedActor(); if (SelectedActor == nullptr)return; if (auto PrefabHelperObject = LGUIEditorTools::GetPrefabHelperObject_WhichManageThisActor(SelectedActor)) { if (auto SubPrefabDataPtr = PrefabHelperObject->SubPrefabMap.Find(SelectedActor)) { PrefabHelperObject->RefreshOnSubPrefabDirty(SubPrefabDataPtr->PrefabAsset, SelectedActor); } } } void LGUIEditorTools::ToggleLevelPrefabAutoUpdate() { auto SelectedActor = GetFirstSelectedActor(); if (SelectedActor == nullptr)return; if (auto PrefabHelperObject = LGUIEditorTools::GetPrefabHelperObject_WhichManageThisActor(SelectedActor)) { if (auto SubPrefabDataPtr = PrefabHelperObject->SubPrefabMap.Find(SelectedActor)) { SubPrefabDataPtr->bAutoUpdate = !SubPrefabDataPtr->bAutoUpdate; } } } ULGUIPrefabHelperObject* LGUIEditorTools::GetPrefabHelperObject_WhichManageThisActor(AActor* InActor) { if (!IsValid(InActor))return nullptr; for (TObjectIterator Itr; Itr; ++Itr) { if (Itr->IsActorBelongsToThis(InActor)) { return *Itr; } } return nullptr; } void LGUIEditorTools::CleanupPrefabsInWorld(UWorld* World) { for (TObjectIterator Itr; Itr; ++Itr) { Itr->CleanupInvalidSubPrefab(); } } bool LGUIEditorTools::IsCanvasActor(AActor* InActor) { if (auto rootComp = InActor->GetRootComponent()) { if (auto rootUIItem = Cast(rootComp)) { if (rootUIItem->IsCanvasUIItem()) { return true; } } } return false; } bool LGUIEditorTools::IsSelectUIActor() { auto selectedActors = LGUIEditorToolsHelperFunctionHolder::ConvertSelectionToActors(GEditor->GetSelectedActors()); if (selectedActors.Num() > 0) { bool allIsUI = true; for (auto actor : selectedActors) { if (IsValid(actor)) { if (auto rootComp = actor->GetRootComponent()) { auto uiRootComp = Cast(rootComp); if (uiRootComp == nullptr) { allIsUI = false; } } } } return allIsUI; } return false; } int LGUIEditorTools::GetDrawcallCount(AActor* InActor) { if (auto rootComp = InActor->GetRootComponent()) { if (auto rootUIItem = Cast(rootComp)) { if (auto canvas = InActor->FindComponentByClass()) { return canvas->GetDrawcallCount(); } } } return 0; } void LGUIEditorTools::MakeCurrentLevel(AActor* InActor) { if (IsValid(InActor) && InActor->GetWorld() && InActor->GetLevel()) { if (InActor->GetWorld()->GetCurrentLevel() != InActor->GetLevel()) { if (!InActor->GetWorld()->GetCurrentLevel()->bLocked) { if (!InActor->GetLevel()->IsCurrentLevel()) { InActor->GetWorld()->SetCurrentLevel(InActor->GetLevel()); } } else { LGUIUtils::EditorNotification(FText::FromString(FString::Printf(TEXT("The level of selected actor:%s is locked!"), *(InActor->GetActorLabel())))); } } } } void LGUIEditorTools::SetTraceChannelToParent(AActor* InActor) { //change trace channel to same as parent if (auto parentActor = InActor->GetAttachParentActor()) { if (auto parentComp = parentActor->GetRootComponent()) { if (auto parentUIComp = Cast(parentComp)) { SetTraceChannel(InActor, parentUIComp->GetTraceChannel()); } } } } void LGUIEditorTools::SetTraceChannelToParent_Recursive(AActor* InActor) { SetTraceChannelToParent(InActor); TArray childrenActors; InActor->GetAttachedActors(childrenActors); for (auto itemActor : childrenActors) { SetTraceChannelToParent_Recursive(itemActor); } } void LGUIEditorTools::SetTraceChannel(AActor* InActor, ETraceTypeQuery InTraceTypeQuery) { TArray Components; InActor->GetComponents(Components); for (auto CompItem : Components) { CompItem->Modify(); CompItem->SetTraceChannel(InTraceTypeQuery); LGUIUtils::NotifyPropertyChanged(CompItem, UUIItem::GetTraceChannelPropertyName()); } } void LGUIEditorTools::FocusToScreenSpaceUI() { if (!GWorld)return; if (!GEditor)return; if (auto activeViewport = GEditor->GetActiveViewport()) { if (auto viewportClient = activeViewport->GetClient()) { auto editorViewportClient = (FEditorViewportClient*)viewportClient; for (TActorIterator ActorItr(GWorld); ActorItr; ++ActorItr) { auto canvasScaler = ActorItr->FindComponentByClass(); if (canvasScaler != nullptr) { auto canvas = ActorItr->FindComponentByClass(); if (canvas != nullptr && canvas->IsRenderToScreenSpace())//make sure is screen space UI root { auto viewDistance = FVector::Distance(canvas->GetViewLocation(), canvas->GetUIItem()->GetComponentLocation()); auto halfViewWidth = viewDistance * FMath::Tan(FMath::DegreesToRadians(canvasScaler->GetFovAngle() * 0.5f)); auto editorViewDistance = halfViewWidth / FMath::Tan(FMath::DegreesToRadians(editorViewportClient->FOVAngle * 0.5f)); auto viewRotation = canvas->GetViewRotator().Quaternion(); editorViewportClient->SetViewLocation(canvas->GetUIItem()->GetComponentLocation() - viewRotation.GetForwardVector() * editorViewDistance); editorViewportClient->SetViewRotation(viewRotation.Rotator()); editorViewportClient->SetLookAtLocation(canvas->GetUIItem()->GetComponentLocation()); break; } } } } } } void LGUIEditorTools::FocusToSelectedUI() { if (!GEditor)return; if (auto activeViewport = GEditor->GetActiveViewport()) { if (auto viewportClient = activeViewport->GetClient()) { auto editorViewportClient = (FEditorViewportClient*)viewportClient; if (auto selectedActor = GetFirstSelectedActor()) { if (auto selectedUIItem = Cast(selectedActor)) { if (auto renderCavnas = selectedUIItem->GetUIItem()->GetRenderCanvas()) { if (auto canvas = renderCavnas->GetRootCanvas()) { if (canvas != nullptr) { editorViewportClient->SetViewLocation(canvas->GetViewLocation()); auto viewRotation = canvas->GetViewRotator().Quaternion(); editorViewportClient->SetViewRotation(viewRotation.Rotator()); editorViewportClient->SetLookAtLocation(canvas->GetUIItem()->GetComponentLocation()); } } } } } } } } bool LGUIEditorTools::IsActorCompatibleWithLGUIToolsMenu(AActor* InActor) { auto ActorClassName = InActor->GetClass()->GetFName(); if ( ActorClassName == TEXT("Landscape") || ActorClassName == TEXT("LandscapeStreamingProxy") || ActorClassName == TEXT("WorldDataLayers") || ActorClassName == TEXT("WorldPartitionMiniMap") ) { return false; } return true; } void LGUIEditorTools::ForceGC() { GEngine->ForceGarbageCollection(); } UE_ENABLE_OPTIMIZATION #undef LOCTEXT_NAMESPACE