UE Editor API 整理

2024-06-07 01:04
文章标签 整理 api ue editor

本文主要是介绍UE Editor API 整理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

UE Editor API 整理

过一下 https://github.com/20tab/UnrealEnginePython/blob/master/docs/,熟悉一下编辑器 API,方便后续编辑器脚本开发

后续的目标是所有编辑器操作应该都可以脚本化(自动化),这样把 GPT 接进 UE 里就可以 Chat UE 操作了

  • SM_ = static mesh, HISM = …
  • http 略
  • runtime 开发就用到的略

ICollectionManager

集合 是一种将资产集整理成组的方式。与文件夹不同,集合不包含资产本身,而仅包含对这些资产的引用。实际上,这意味着一个资产可以属于多个集合。

在这里插入图片描述

#include “Developer/CollectionManager/Public/ICollectionManager.h”

DataTable

#include “Runtime/Engine/Classes/Engine/DataTable.h”
#include “Editor/UnrealEd/Public/DataTableEditorUtils.h”

DT 内部是 FName => UScriptStruct 的映射
TMap<FName, uint8*> RowMap;

DT 转 json string

FString GetTableAsJSON(const EDataTableExportFlags InDTExportFlags = EDataTableExportFlags::None) const;

创建新 DT

dt_factory = DataTableFactory()
dt_factory.Struct = Transformdt = dt_factory.factory_create_new('/Game/TransformDataTable')

工厂模式,每个资源类型都有一个

检查下路径有没有冲突

class UDataTableFactory : public UFactory
{UPROPERTY(BlueprintReadWrite, Category = "Data Table Factory")TObjectPtr<const class UScriptStruct> Struct;
...void ue_factory_create_new(UFactory* factory, const FString& name)
{FString PackageName = UPackageTools::SanitizePackageName(name);UPackage* outer = CreatePackage(*PackageName);if (!outer){// Handle error: unable to create packagereturn;}TArray<UPackage*> TopLevelPackages;TopLevelPackages.Add(outer);if (!UPackageTools::HandleFullyLoadingPackages(TopLevelPackages, FText::FromString("Create a new object"))){// Handle error: unable to fully load packagereturn;}UClass* u_class = factory->GetSupportedClass();if (u_class->IsChildOf<UBlueprint>() && FindObject<UBlueprint>(outer, *name)){// Handle error: a blueprint with this name already exists in the packagereturn;}if (u_class->IsChildOf<UUserDefinedStruct>() && FindObject<UUserDefinedStruct>(outer, *name)){// Handle error: a structure with this name already exists in the packagereturn;}UObject* u_object =nullptr;u_object = factory->FactoryCreateNew(u_class, outer, FName(*name), RF_Public | RF_Standalone, nullptr, GWarn);if (u_object){FAssetRegistryModule::AssetCreated(u_object);outer->MarkPackageDirty();}else{// Handle error: unable to create new object from factoryreturn;}
}

Foliage

UE 自带的植被系统,专门有个刷植被的编辑模式,刷的结果存在 UWorld 的 AInstancedFoliageActor 里(单例,自动创建)

存储用了一个 Map,每一项是一个 SM_,渲染用 HISM

TMap<TObjectPtr<UFoliageType>, TUniqueObj<FFoliageInfo>> FoliageInfos;

? 某种资源 todo

factory = FoliageTypeFactory()
foliage_type = factory.factory_create_new('/Game/Foliage/FirstFoliageType')
foliage_type.Mesh = ue.load_object(StaticMesh, '/Game/Mesh/StaticMesh001')
foliage_type.save_package()

这个 Map 存 UWorld.AInstancedFoliageActor 里,即每个 .umap 都单独有一套植被表

foliage_actor = world.get_instanced_foliage_actor_for_current_level()
world.add_foliage_asset(foliage_type)
world.add_foliage_asset(ue.load_object(StaticMesh, '/Game/Mesh/StaticMesh001'))
void ue_add_foliage_asset(UObject* self, UObject* u_object)
{UWorld* world = ue_get_uworld(self);if (!world){// Handle error: unable to retrieve UWorld from uobjectreturn;}UFoliageType* foliage_type = nullptr;AInstancedFoliageActor* ifa = AInstancedFoliageActor::GetInstancedFoliageActorForCurrentLevel(world, true);if (u_object->IsA<UStaticMesh>()){foliage_type = ifa->GetLocalFoliageTypeForSource(u_object);if (!foliage_type){ifa->AddMesh(static_cast<UStaticMesh*>(u_object), &foliage_type);}}else if (u_object->IsA<UFoliageType>()){foliage_type = static_cast<UFoliageType*>(u_object);ifa->AddFoliageType(foliage_type);}if (!foliage_type){// Handle error: unable to add foliage assetreturn;}// Return foliage_type if needed
}

遍历植被表

import unreal_engine as uefoliage_actor = ue.get_editor_world().get_instanced_foliage_actor_for_current_level()for foliage_type in foliage_actor.get_foliage_types():print('Foliage Type: {0}'.format(foliage_type.get_name()))for foliage_instance in foliage_actor.get_foliage_instances(foliage_type):print(foliage_instance.location)print(foliage_instance.draw_scale3d)print(foliage_instance.pre_align_rotation)print(foliage_instance.rotation)print(foliage_instance.flags)print(foliage_instance.zoffset)print('*' * 20)

Add a ForEachLoop Macro node

蓝图编辑器的 API,非常恶心,添加节点,添加 pin 脚连线,保存

# for_each_loop = ue.load_object(EdGraph, '/Engine/EditorBlueprintResources/StandardMacros.StandardMacros:ForEachLoop')
for_each_loop = ue.find_object('ForEachLoop')# get a reference to your blueprint
blueprint = ...# add the node
node = blueprint.UberGraphPages[0].graph_add_node(K2Node_MacroInstance)
# assign the macro graph to the node
node.MacroGraphReference = GraphReference(MacroGraph=for_each_loop)
# allocate pins
node.node_allocate_default_pins()# update the blueprint
ue.blueprint_mark_as_structurally_modified(bp)

Landscape/Terrain

Landscape 也是特殊单例对象(和植被一样),用 heightmap 渲染

地形支持分块,实际以 ULandscapeComponent 为单位渲染,每个 ULandscapeComponent 存一小块 heightmap texture 引用

python 里 heightmap 对应到 bytearray 来操作

创建 heightmap,创建 landscape 并指定 heightmap,设置地形大小,分块粒度(存在 ULandscapeInfo)

其他导出 SM_ 等略

width = 1024
height = 1024
heightmap = []for y in range(0, height):for x in range(0, width):heightmap.append(random.randint(0, 65535))data = struct.pack('{0}H'.format(width * height), *heightmap)quads_per_section = 63
number_of_sections = 1
components_x = 8
components_y = 8fixed_data = ue.heightmap_expand(data, width, height, quads_per_section * number_of_sections * components_x + 1, quads_per_section * number_of_sections * components_y + 1)landscape = ue.get_editor_world().actor_spawn(Landscape)
landscape.landscape_import(quads_per_section, number_of_sections, components_x, components_y, fixed_data)
landscape.set_actor_scale(1,1,1)

Level & World

编辑器下理解 level 很重要

  • level 和 world 的区别,level 是静态容器,world 是动态容器,两个都是存 actor 列表

创建 .umap

factory = WorldFactory()
new_world = factory.factory_create_new('/Game/Maps/FooLevel')

spawn 一些 actor 到 world 里

# create a world (it will be the main one, the one you load into the editor by double clicking it)
main_world = factory.factory_create_new('/Game/MapsTest/MainWorld001')
# spawn actors in the world
actor0 = main_world.actor_spawn(PlayerStart)# create another world
child_world1 = factory.factory_create_new('/Game/MapsTest/ChildWorld001')
# spawn actors in the world
actor1 = child_world1.actor_spawn(Actor)
actor2 = child_world1.actor_spawn(Actor)
actor3 = child_world1.actor_spawn(Actor)# create another world
child_world2 = factory.factory_create_new('/Game/MapsTest/ChildWorld002')
# spawn actors in the world
actor4 = child_world2.actor_spawn(Actor)# now the important part, each UWorld, has a ULevel mapped to it (the PersistentLevel):
main_level = main_world.PersistentLevel
child_level1 = child_world1.PersistentLevel
child_level2 = child_world2.PersistentLevel# open main world in the editor
ue.open_editor_for_asset(main_world)

level 面板里,sub-level 和 level-streaming 的概念

added_streaming_level = ue.add_level_to_world(main_world, child_world1.get_path_name()[, always_loaded])

添加一个 sub-level(用 path),指定 ULevelStreaming StreamingMode

这个存在 main-level 的 TArray<TObjectPtr<ULevelStreaming>> StreamingLevels // todo double check

ULevelStreaming* ue_add_level_to_world(UWorld* u_world, const FString& name, bool isAlwaysLoaded)
{if (!u_world){// Handle error: argument is not a UWorldreturn nullptr;}if (!FPackageName::DoesPackageExist(*name, nullptr)){// Handle error: package does not existreturn nullptr;}UClass* streaming_mode_class = ULevelStreamingDynamic::StaticClass();if (isAlwaysLoaded){streaming_mode_class = ULevelStreamingAlwaysLoaded::StaticClass();}ULevelStreaming* level_streaming = nullptr;
#if ENGINE_MAJOR_VERSION == 5 || (ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 17)level_streaming = EditorLevelUtils::AddLevelToWorld(u_world, *name, streaming_mode_class);
#elselevel_streaming = EditorLevelUtils::AddLevelToWorld(u_world, *name, streaming_mode_class);
#endifif (!level_streaming){// Handle error: unable to add level to the worldreturn nullptr;}#if ENGINE_MAJOR_VERSION == 5 || (ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 16)FEditorDelegates::RefreshLevelBrowser.Broadcast();
#endifreturn level_streaming;
}

CurrentLevel 是编辑器下当前正在编辑的 level(focus),可以切 level,编辑器所有 actor 操作都是在这个 level 进行

# get a reference to the current level
current_level = ue.get_editor_world().get_current_level()# change the current level
ue.get_editor_world().set_current_level(child_level1)# spawn an actor in editor world, but effectively it will be spawned
# in a child level
actor001 = ue.get_editor_world().actor_spawn(actor001)# back to the original level
ue.get_editor_world().set_current_level(current_level)

Assets

  • UPackage 可以存若干 UObject(一般一个)
  • 路径 API,和 os 类似
  • asset import 时会记录本地 source path,用来支持 reimport
  • 每种 asset 对应 factory 类来进行 load
  • UObject 编辑器下 Outer 即 UPackage,SavePackage 写文件
  • AssetRegistryModule.Get().GetReferencersGetDependencies,editor 下全局缓存了引用关系图
  • // UE 的这套资源系统封的非常深度,相当于有一套傻瓜的自动模式,本质是 UE 存了引用关系配合 GC 来自动加载释放,(只有加载 UWorld,Spawn Actor & Component),做 Demo 完全够了;正式项目还是要对资源使用定下规范,使用"专家模式“,否则内存占用,以及 IO 加载造成游戏卡顿是后期优化很难偿还的技术债务
# get the Mat001.Foobar asset
material = ue.get_asset('/Game/Materials/Mat001.Foobar')
# print material repr
ue.log(material)
# print material properties
ue.log(material.properties())############################
materials = ue.get_assets('/Game/Materials')
materials = ue.get_assets('/Game/Materials', True)
materials = ue.get_assets_by_class('Material')ue.duplicate_asset('/Game/Materials/Mat001.Foobar', '/Game/NewMaterials/Mat001', 'FooBarUpdated')
ue.delete_asset('/Game/NewMaterials/Mat001.FooBarUpdated')asset = ue.import_asset('/Users/FooBar/Desktop/texture001.png', '/Game/Textures')if asset002.asset_can_reimport():asset002.asset_reimport()mesh.asset_import_data_set_sources('D:/sword.fbx')############################
particle_system = ParticleSystem()
particle_system.set_name('ParticleSystemForDummies')# special form of the constructor taking the object name
material = Material('FunnyMaterial')# this will save particle_system into /Game/Particles.ParticleSystemForDummies
particle_system.save_package('/Game/Particles')# this will save material into /Game/FunnyMaterials.FunnyMaterial
material.save_package('/Game/FunnyMaterials')list_of_referencers = ue.get_asset_referencers('/Game/FooBar')
list_of_dependencies = ue.get_asset_dependencies('/Game/FooBar')

Material

Material 本质是 shader function,走 shader compiler 那一套

实际上还有 Material Instance 和 Material Instance Dynamic 用来 override 参数值,前者是静态(存资源),后者是动态(运行时修改)

创建 MI_,指定 parent

material_instance = MaterialInstanceConstant()
material_instance.set_name('New Funny Material Instance')
material_instance.set_material_parent(new_material)
material_instance.save_package('/Game/Materials/instanced')

遍历参数

parent_material = material_instance.Parentfor expression in parent_material.Expressions:parameter_name = expression.ParameterNameparameter_group = expression.Group# retuns a float
material_instance.get_material_scalar_parameter('Parameter name')
# returns a FVector
material_instance.get_material_vector_parameter('Parameter name')
# returns a Texture
material_instance.get_material_texture_parameter('Parameter name')material_instance.set_material_scalar_parameter('Parameter name', float)
material_instance.set_material_vector_parameter('Parameter name', FVector)
material_instance.set_material_texture_parameter('Parameter name', Texture)

这篇关于UE Editor API 整理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/1037738

相关文章

基于Flask框架添加多个AI模型的API并进行交互

《基于Flask框架添加多个AI模型的API并进行交互》:本文主要介绍如何基于Flask框架开发AI模型API管理系统,允许用户添加、删除不同AI模型的API密钥,感兴趣的可以了解下... 目录1. 概述2. 后端代码说明2.1 依赖库导入2.2 应用初始化2.3 API 存储字典2.4 路由函数2.5 应

Mysql中深分页的五种常用方法整理

《Mysql中深分页的五种常用方法整理》在数据量非常大的情况下,深分页查询则变得很常见,这篇文章为大家整理了5个常用的方法,文中的示例代码讲解详细,大家可以根据自己的需求进行选择... 目录方案一:延迟关联 (Deferred Join)方案二:有序唯一键分页 (Cursor-based Paginatio

Mysql中InnoDB与MyISAM索引差异详解(最新整理)

《Mysql中InnoDB与MyISAM索引差异详解(最新整理)》InnoDB和MyISAM在索引实现和特性上有差异,包括聚集索引、非聚集索引、事务支持、并发控制、覆盖索引、主键约束、外键支持和物理存... 目录1. 索引类型与数据存储方式InnoDBMyISAM2. 事务与并发控制InnoDBMyISAM

StarRocks索引详解(最新整理)

《StarRocks索引详解(最新整理)》StarRocks支持多种索引类型,包括主键索引、前缀索引、Bitmap索引和Bloomfilter索引,这些索引类型适用于不同场景,如唯一性约束、减少索引空... 目录1. 主键索引(Primary Key Index)2. 前缀索引(Prefix Index /

C#集成DeepSeek模型实现AI私有化的流程步骤(本地部署与API调用教程)

《C#集成DeepSeek模型实现AI私有化的流程步骤(本地部署与API调用教程)》本文主要介绍了C#集成DeepSeek模型实现AI私有化的方法,包括搭建基础环境,如安装Ollama和下载DeepS... 目录前言搭建基础环境1、安装 Ollama2、下载 DeepSeek R1 模型客户端 ChatBo

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.

Deepseek R1模型本地化部署+API接口调用详细教程(释放AI生产力)

《DeepseekR1模型本地化部署+API接口调用详细教程(释放AI生产力)》本文介绍了本地部署DeepSeekR1模型和通过API调用将其集成到VSCode中的过程,作者详细步骤展示了如何下载和... 目录前言一、deepseek R1模型与chatGPT o1系列模型对比二、本地部署步骤1.安装oll

浅析如何使用Swagger生成带权限控制的API文档

《浅析如何使用Swagger生成带权限控制的API文档》当涉及到权限控制时,如何生成既安全又详细的API文档就成了一个关键问题,所以这篇文章小编就来和大家好好聊聊如何用Swagger来生成带有... 目录准备工作配置 Swagger权限控制给 API 加上权限注解查看文档注意事项在咱们的开发工作里,API

一分钟带你上手Python调用DeepSeek的API

《一分钟带你上手Python调用DeepSeek的API》最近DeepSeek非常火,作为一枚对前言技术非常关注的程序员来说,自然都想对接DeepSeek的API来体验一把,下面小编就来为大家介绍一下... 目录前言免费体验API-Key申请首次调用API基本概念最小单元推理模型智能体自定义界面总结前言最

JAVA调用Deepseek的api完成基本对话简单代码示例

《JAVA调用Deepseek的api完成基本对话简单代码示例》:本文主要介绍JAVA调用Deepseek的api完成基本对话的相关资料,文中详细讲解了如何获取DeepSeekAPI密钥、添加H... 获取API密钥首先,从DeepSeek平台获取API密钥,用于身份验证。添加HTTP客户端依赖使用Jav