直接上代码:
void FPutBuildingToLevelModule::CreateNewlevelAsset(FString AssetName,FString PackagePath)
{
UWorldFactory* Factory = NewObject<UWorldFactory>();
Factory->bCreateWorldPartition = false;
Factory->WorldType = EWorldType::Inactive;
FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked<FAssetToolsModule>("AssetTools");
AssetToolsModule.Get().CreateAsset(AssetName,PackagePath,UWorld::StaticClass(),Factory);
}
下面是此需求在数字孪生开发的一些分享,可以直接忽略直接看代码的部分
本人在公司数字孪生项目开发中,有个重复性人工操作,浪费大量的时间。
我目前公司基本上每个数字孪生项目都会有大量摄像头/门禁/消防点位 等设备的点位,
这些点位Actor需要准确的放置到对应楼层的对应位置。
并且使用流关卡的形式加载和卸载,当用户点击A栋B层的时候,在固定位置加载“A栋B层Level”,
并且让GameController 切换控制 固定位置的那个Pawn,使用这个Pawn负责楼栋内的控制 (因为楼栋内和大场景的操作逻辑是不同的,比如缩放速度,旋转速度,等等)
因此有个重复性的需求 :复制一份模型,新建一个关卡实例,讲模型放入到新关卡中
就是要将单层楼栋的模型复制一份到一个新关卡中(这样不影响大场景),并且复制后的模型移动到固定位置,这样就可以实现 上文中说的 当用户点击A栋B层的时候,在固定位置加载“A栋B层Level”。
使用一个球包住楼栋的位置,里面是单层楼栋的模型
这里代码只列出主要的部分
需求:复制一份模型,新建一个关卡实例,讲模型放入到新关卡中
1.拿到目前编辑器中选中的资源
这里我添加了自定义的右击菜单
void FPutBuildingToLevelModule::ShutdownModule()
{
}
void FPutBuildingToLevelModule::InitCBMenuExtention()
{
//获取Level Editor模块
FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
//获取Viewport菜单上下文拓展项(Viewport中选中物体的右击菜单拓展项),是一个委托数组
auto& LVCMExtenders = LevelEditorModule.GetAllLevelViewportContextMenuExtenders();
//在数组中添加一个新的拓展项,当右击选中Actor并弹出菜单时,将会遍历此数组并执行绑定的委托方法
LVCMExtenders.Add(FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors::CreateRaw(this, &FPutBuildingToLevelModule::CreatMenuExtension));
}
TSharedRef<FExtender> FPutBuildingToLevelModule::CreatMenuExtension(const TSharedRef<FUICommandList> CommandList ,
const TArray<AActor*> Actors)
{
//创建一个拓展项,与上一章中用法相似,用其拓展菜单项
TSharedPtr<FExtender> Extender = MakeShareable(new FExtender());
//设置菜单拓展项显示内容,绑定的委托是上章创建的委托方法
Extender->AddMenuExtension("GoHere", EExtensionHook::After, CommandList, FMenuExtensionDelegate::CreateRaw(this, &FPutBuildingToLevelModule::AddMenuExtension));
//auto& LVCMExtenders = LevelEditorModule.GetAllLevelViewportContextMenuExtenders();
//LVCMExtenders.Pop(); //如果将最后一个(我们刚刚创建的)Extender移除,则将会在第一显示菜单后,下次不会再显示此项
//返回拓展项,将会被显示在菜单栏
//当前选中的资源
SelectActors = Actors;
return Extender.ToSharedRef();
}
void FPutBuildingToLevelModule::AddMenuExtension(FMenuBuilder& MenuBuilder)
{
MenuBuilder.AddMenuEntry
(
//按钮名称
FText::FromString(TEXT("移动选择资源至关卡")),
//提示文本
FText::FromString(TEXT("移动选择资源至关卡")),
//按钮图标
FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Viewports"),
//绑定要执行的事件
FExecuteAction::CreateRaw(this, &FPutBuildingToLevelModule::OnClick),
"MoveBuilding"
);
}
void FPutBuildingToLevelModule::OnClick()
{
//具体实现
}
2.遍历拿到的资源,生成对应数量的关卡实例
//遍历选中的资源
for (AActor* Actor : SelectActors)
{
if (!Actor)
continue;
AStaticMeshActor* MeshActor = Cast<AStaticMeshActor>(Actor);
if (!MeshActor)
continue;
if (!MeshActor->GetStaticMeshComponent())
continue;
if (!MeshActor->GetStaticMeshComponent()->GetStaticMesh())
continue;
//新建关卡 并复制资源移动至新关卡内
//UKismetSystemLibrary::GetDisplayName(MeshActor);
FString DisplayName=MeshActor->GetActorLabel();
UWorld* World =CreateNewlevelAsset(DisplayName,PackagePath);
UWorld* FPutBuildingToLevelModule::CreateNewlevelAsset(FString AssetName,FString PackagePath)
{
UWorldFactory* Factory = NewObject<UWorldFactory>();
Factory->bCreateWorldPartition = false;
Factory->WorldType = EWorldType::Inactive;
FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked<FAssetToolsModule>("AssetTools");
UObject* WorldAsset =AssetToolsModule.Get().CreateAsset(AssetName,PackagePath,UWorld::StaticClass(),Factory);
if (!IsValid(WorldAsset))
{
return nullptr;
}
UWorld* World =WorldAsset->GetWorld();
return World;
}
3.复制一份到新关卡资源中
//遍历选中的资源
for (AActor* Actor : SelectActors)
{
if (!Actor)
continue;
AStaticMeshActor* MeshActor = Cast<AStaticMeshActor>(Actor);
if (!MeshActor)
continue;
if (!MeshActor->GetStaticMeshComponent())
continue;
if (!MeshActor->GetStaticMeshComponent()->GetStaticMesh())
continue;
//新建关卡 并复制资源移动至新关卡内
//UKismetSystemLibrary::GetDisplayName(MeshActor);
FString DisplayName=MeshActor->GetActorLabel();
UWorld* World =CreateNewlevelAsset(DisplayName,PackagePath);
if (!IsValid(World))continue;
//复制一份选中的Mesh
UEditorActorSubsystem* EditorActorSubsystem = GEditor->GetEditorSubsystem<UEditorActorSubsystem>();
//会将选中的复制一份到目标世界
//设置新位置
//将新建的关卡添加至当前关卡的子关卡
//这里先复制一个到当前关卡里 生成正确的偏移
//再二次复制到正确关卡里
AActor* DuplicateActorA = EditorActorSubsystem->DuplicateActor(Actor,nullptr,MoveLocation);
//修改世界大纲名称
FString NewName = Actor->GetActorLabel()+"_New";
DuplicateActorA->SetActorLabel(NewName);
AActor* DuplicateActorB = EditorActorSubsystem->DuplicateActor(DuplicateActorA,World,FVector(0,0,0));
DuplicateActorB->SetActorLabel(Actor->GetActorLabel());
//删除中转actor
EditorActorSubsystem->DestroyActor(DuplicateActorA);
}
AActor* DuplicateActor(
AActor* ActorToDuplicate,
UWorld* ToWorld = nullptr,
FVector Offset = FVector::ZeroVector)
DuplicateActor()这个函数有个大坑,当复制到新的World时,函数返回的总是我当前关卡里的actor,而不是我复制出来的那个actor。于是我设置复制出来的名字时总是把选中的actor名字给修改了。同关卡里返回值是新复制出来的,只是不同关卡时出现发现这个问题。没具体深入研究。
于是 - -
我在当前关卡右击A,复制一份B,将B名字修改为带_new字样的,再复制B到新关卡里,这样复制出来的C就带_new。再删掉B