uasset python_Unreal Python 结合 C++ 开发蓝图库插件

本文章转载自 智伤帝的个人博客 - 原文链接

前言

上个月的这个时候我写了一篇文章关于如何嵌入 PySide 调用 Qt 的 GUI 开发。 链接

Python 虽然很好,但是有些功能,并没有从 C++ 里面暴露出来。

这种情况就需要通过 C++ 的蓝图开发来将这部分的功能进行暴露。

这样 Python 基本上可以做任何 Unreal 的事情。

如何开发蓝图库也基本可以参照上篇文章提到的 Unreal Python 教程。 链接

为什么需要开发 C++ 蓝图

上面的视频链接有很详细如何通过 Unreal C++ API 开发一个 Unreal 的蓝图,暴露给 Python 调用。

Unreal 的 Python 插件其实已经将 Unreal 内置的所有蓝图暴露给了 Python。

因此 蓝图 能够做到的事情, Python 是绝对可以做到的。

而且经过一个多月的使用来看, Python 的 API 文档做得比 蓝图 的 文档要好很多。

有时候直接查 Python 的 API 反而更有效率,甚至会发现一些其他插件所引入的蓝图。

那么 Python 相较于 蓝图 的有什么异同?

我目前的使用感受来看,除了失去图形节点编程的可视化之外,基本上碾压蓝图,当然运行效率上没有测试过。

蓝图 和 Python 的定位有很大的不一样。

蓝图可以作为游戏运行逻辑的一部分, Python 只能当做编辑器的自动化工具。(Python 效率太低了,运行脚本宁愿用 lua 调 C++)

蓝图自身有它的优缺点,效率比不过 C++ 链接

但是图形化编程,对于非 coding 人员很友好,而且一些简单的逻辑也比较直观。

但是复杂蓝图的连线还是太让人劝退了。

Python 对于像我这种工具向开发的 TA 来说太友好了,毕竟很多 DCC 都使用 Python 。

Unreal 的 Python 插件大部分是对 蓝图 的分装,基本上蓝图有的功能都可以通过 Python 来调。

同时 Python 还可以实现一些神奇的功能,比如说通过 Python 开发一个蓝图节点 ,调用 Python 的第三方库诸如 PySide 包,或者调用系统的 cmd 或者 shell 命令。

因此从引擎的提升来说, Python 的确在这方面更胜一筹,复杂逻辑通过代码看也比较直观。

当然很明显, 蓝图不能实现的引擎操作,基本上也不用指望 Python 能够调用什么 API 来实现了。

这种情况下就需要 C++ 来扩展蓝图,实现 Python 调用。

C++ 开发蓝图库插件

我们目前的需求并不是开发游戏调用的蓝图,因此我们可以开发一个蓝图库插件。

这样可以轻易将这些蓝图迁移到不同的项目里面去。

Unreal 搭建蓝图库开发其实并不难,按照官方的指引去做即可。

首先需要创建一个 C++ 工程,如果是蓝图工程是无法写 C++ 代码的。

然后打开插件面板,选择 New Plugin

然后引擎就会自动创建一个基础插件的模板出来。

后续就是在这个基础模板上调用 C++ 的 API 实现一些特殊的功能。

unreal C++ 插件注意事项

插件的默认结构是 .uplugin 文件加 Resource 和 Source 文件夹。

uplugin 就是一个 Json 配置,配置了插件在引擎的插件列表的显示,以及加载方式。

Resource 存放插件显示的图标。

Source 存放的是 C++ 源码了。

前面两个不需要太过关注,重点的 Source 文件夹的东西。

里面会有 *.Build.cs 文件以及 Public 和 Private 文件夹。

*.Build.cs是 C# 代码,通过虚幻的 Reflect 机制生成 Intermediate 的中间代码用来编译生成 dll。

Public 默认存放头文件

Private 默认存放cpp源码

引用了引擎内部的一些库,需要在 build.cs 文件里面添加上。

否则编译的时候回报某些类型无法识别的错误。

试过排查这种小错误花了我大半天。

前面两个部分是添加路径的,用来缩短头文件索引的路径长度。

后面的 Private 和 Public Module 则是最重要的索引头文件的,必须要在这里配置才能在 c++ 里面调用。

这里怎么填写可以参考引擎 Source 源码下的文件夹名称。

cs 里面配置就可以找 Source 源码的一些头文件进行引用了。

因为虚幻开源了,所以内部 Private 和 Public 没有什么区别,也可能是我的 C++ 造诣还不够。

配置头文件就可以愉快地使用官方提供的一些 C++ 了。

C++ 实现 Add Component 蓝图功能

这个功能看似非常简单,奈何 Python 就是实现不了。

就是给现有 Actor 添加新的 Component 组件而已。

但是查了 API 文档,即便使用 Attach 相关的方法,也无法新的 Component 添加到 Actor 上。

应该说 Python 的操作没有问题, Component 也加上了,可以通过 Python 获取到,但是 Component 没有注册,无法在 UI 上显示出来。

经过我查阅大量网上的资料之后,只在论坛上找到了一个通过 C++ 实现的方案。 链接

这段代码里面有很关键的 RegisterComponent 的操作。

而这些操作并没有暴露到 Python 或者说 蓝图 里面。

当然这个添加 Component 的方法估计也和 Unreal 的机制有关,我对 Unreal 引擎还不是很熟,就不做无关的揣测了。

Python 的文档在 Actor 的部分有所涉及。

不过这个问题就非常蛋疼,

unreal.EditorLevelLibrary.get_all_level_actors_components 可以获取所有注册的 Component

Actor 也可以删除现有的 Component ,偏偏无法添加新的 Component

C++ 的部分我简化了上面回答的代码。

如果没有传入具体的 Component 类型就返回 None 给 Python 就好了。

UActorComponent* URedArtToolkitBPLibrary::AddComponent(AActor* a, USceneComponent *future_parent, FName name, UClass* NewComponentClass)

{

UActorComponent* retComponent = nullptr;

if (NewComponentClass)

{

UActorComponent* NewComponent = NewObject(a, NewComponentClass, name);

FTransform CmpTransform;// = dup_source->GetComponentToWorld();

//NewComponent->AttachToComponent(sc, FAttachmentTransformRules::KeepWorldTransform);

// Do Scene Attachment if this new Comnponent is a USceneComponent

if (USceneComponent* NewSceneComponent = Cast(NewComponent))

{

if (future_parent != 0)

NewSceneComponent->AttachToComponent(future_parent, FAttachmentTransformRules::KeepWorldTransform);

else

NewSceneComponent->AttachToComponent(a->GetRootComponent(), FAttachmentTransformRules::KeepWorldTransform);

NewSceneComponent->SetComponentToWorld(CmpTransform);

}

a->AddInstanceComponent(NewComponent);

NewComponent->OnComponentCreated();

NewComponent->RegisterComponent();

a->RerunConstructionScripts();

retComponent = NewComponent;

}

return retComponent;

}

头文件怎么去 #include ,我基本就是用 VScode 搜索引擎源码,查找头文件的位置,然后逐个添加。

C++有点麻烦的地方就是 cpp 代码写完之后还要将函数注册到 头文件 里面

不过基本上复制 cpp 的函数第一行就可以了,只需要把 :: 前面的类名删除掉而已。

下面就是点击 VS 上面的 本地 windows 调试,编译插件并启动项目。

我用 VS2017 编译经常遇到 clxx.dll 命令行过长 的错误。

网上了查了要将项目的编译改为 Release 版本,或者升级到 VS2019 才可以解决。(网上查到这个是 VS 的 BUG)

后来我是随便将一些 Intermediate 文件夹删除,然后重新调用 UnrealHeaderTool 生成反射代码就不会有这个编译报错了。

完成到这里基本可以参照老外的教程,使用 Python 可以在 unreal 库下找到刚才蓝图扩展的类的,类下面就由刚才扩展的 函数 了。

行数名称自动将 C++ 的驼峰转为 Python pep8 规范的 sneak 写法。

C++ 蓝图获取当前 Sequencer 选择的元素

上面介绍了 C++ 的编写的流程,就不再追溯,这里着重看蓝图的实现。

我最近有一个需求是要获取当前打开的 Sequencer 里面的元素,然后进行 FBX 导出。

但是查遍了 Unreal 的 Python 文档也没有找到这个方法。

对了这里记录一个天坑,之前被坑惨了的。

Unreal Python 的老外教程里面也记录一些使用 Sequencer 处理的 Python 方案。

但是我发现到我调用这些 API 的时候, Unreal 居然报错找不到这些 API。

然后我就以为是我当前 Unreal 版本出 BUG 了,或者是官方删除了这个功能。

后来折腾了好久之后才发现,我没有开启 Sequencer Scripting 插件,所以那些调用蓝图没有加载(:з」∠)

我当时还不知道 Python 调用的就是蓝图, 踩了这个坑才有了深刻的认识。

回到这里要实现的功能,我查了 C++ 相关的问题,总算是找到了一个比较可靠的回复。 链接

于是我就抄了这里的代码。

不过上面的代码有点旧,其中 IAssetEditorInstance* AssetEditor = UAssetEditorSubsystem().FindEditorForAsset(LevelSeq, true); 编译会报错

修改为 IAssetEditorInstance* AssetEditor = GEditor->GetEditorSubsystem()->FindEditorForAsset(LevelSeq, true); 解决问题。

经过修改之后上面的代码可以获取到当前 Sequencer 打开的 LevelSequence

原理也不复杂,就是遍历项目所有的 LevelSequence 然后找到那个开启了 Editor 的 LevelSequence

然后在从这个 LevelSequence 里获取 Editor 再从 Editor 获取 Sequencer。

虽然这个遍历有点不太合理,但是我在测试的项目上还是很奏效的。

但是当我将代码编译放到我们正在开发的项目上之后,出现了大问题。

项目有大量的 LevelSequence ,遍历需要很长的时间,并且遍历之后大量的材质启动了编译,导致电脑很卡。

于是我又查了一下 C++ API 文档,发现有个很有用的函数 GetAllEditedAssets。

这个函数可以获取当前打开在编辑器里面的 Assets ,能打开的 Asset 肯定就那么几个。

这样找 Editor 的速度可就快多了。

ULevelSequence* URedArtToolkitBPLibrary::GetFocusSequence()

{

UAssetEditorSubsystem* sub = GEditor->GetEditorSubsystem();

TArray assets = sub->GetAllEditedAssets();

for (UObject* asset : assets)

{

IAssetEditorInstance* AssetEditor = sub->FindEditorForAsset(asset, false);

FLevelSequenceEditorToolkit* LevelSequenceEditor = (FLevelSequenceEditorToolkit*)AssetEditor;

if (LevelSequenceEditor != nullptr)

{

ULevelSequence* LevelSeq = Cast(asset);

return LevelSeq;

}

}

return nullptr;

}

上面只是找 LevelSequence ,还需要找当前 LevelSequence 里面选择的元素。

好在 Sequencer 提供了 GetSelectedObjects 的方法

通过 LevelSequence 可以获取到 Sequencer

TArray URedArtToolkitBPLibrary::GetFocusBindings(ULevelSequence* LevelSeq)

{

IAssetEditorInstance* AssetEditor = GEditor->GetEditorSubsystem()->FindEditorForAsset(LevelSeq, false);

FLevelSequenceEditorToolkit* LevelSequenceEditor = (FLevelSequenceEditorToolkit*)AssetEditor;

TArray SelectedGuid;

if (LevelSequenceEditor != nullptr)

{

ISequencer* Sequencer = LevelSequenceEditor->GetSequencer().Get();

Sequencer->GetSelectedObjects(SelectedGuid);

return SelectedGuid;

}

return SelectedGuid;

}

这样获取返回的是 Guid , Python 有 Guid 类。

可以通过 LevelSequence 的 get_bindings 方法获取 sequence 相关的 binding

再调用 get_id 方法获取 guid ,然后通过 C++ 的蓝图将获取到的 id 筛选一遍。

# NOTE 获取当前 Sequencer 中的 LevelSequence

sequence = unreal.RedArtToolkitBPLibrary.get_focus_sequence()

# NOTE 获取当前 Sequencer 中选中的 Bindings

id_list = unreal.RedArtToolkitBPLibrary.get_focus_bindings(sequence)

bindings_list = [binding for binding in sequence.get_bindings() if binding.get_id() in id_list]

这样就获取到了当前选择的 SequencerBindingProxy 类。

通过 unreal.SequencerTools.export_fbx 就可以将选择的元素导出 FBX 了。

import unreal

from Qt import QtCore, QtWidgets, QtGui

def alert(msg=u"msg", title=u"警告", button_text=u"确定"):

# NOTE 生成 Qt 警告窗口

msg_box = QtWidgets.QMessageBox()

msg_box.setIcon(QtWidgets.QMessageBox.Warning)

msg_box.setWindowTitle(title)

msg_box.setText(msg)

msg_box.addButton(button_text, QtWidgets.QMessageBox.AcceptRole)

unreal.parent_external_window_to_slate(msg_box.winId())

msg_box.exec_()

def unreal_export_fbx(fbx_file):

# NOTE 获取当前 Sequencer 中的 LevelSequence

sequence = unreal.RedArtToolkitBPLibrary.get_focus_sequence()

if not sequence:

alert(u"请打开定序器")

return

# NOTE 获取当前 Sequencer 中选中的 Bindings

id_list = unreal.RedArtToolkitBPLibrary.get_focus_bindings(sequence)

bindings_list = [binding for binding in sequence.get_bindings() if binding.get_id() in id_list]

if bindings_list:

# NOTE 导出 FBX 文件

option = unreal.FbxExportOption()

option.set_editor_property("collision",False)

world = unreal.EditorLevelLibrary.get_editor_world()

unreal.SequencerTools.export_fbx(world,sequence,bindings_list,option,fbx_file)

else:

alert(u"请选择定序器的元素进行 FBX 导出")

return

上面就是完整的示例代码。

当然导出的 FBX 是带动画的,还需要将动画处理成带 蒙皮骨骼 的 FBX 。

这个操作我是通过 FBX Python SDK 实现的。

官方的 ExportScene01 包含了蒙皮创建,关键帧处理等等的操作,绝大部分的代码可以照抄。

这里蒙皮转换的需求很简单,因此稍微修改一下就可以用了。

处理完成之后将 FBX 输出到临时目录,然后用 Python 调 windows 命令打开路径。

总结

其实调用 C++ API 并不难,这种程度的操作还没有修改到 Unreal 的底层,很多机制也没有用到,我作为个外行还是可以应付的。

而且 Unreal C++ 本身做了很多工作,比如实现了 垃圾回收,含有智能指针,都降低了开发难度(同时增加了学习的难度)

Unreal 开发比较难受的地方时教程文档各方面都不全, Unity 文档还有代码示例,Unreal 因为开源,基本上就是让你直接看源码(:з」∠)

有时候遇到的一些奇奇怪怪的问题还找不到任何网上的提问,就很难受了。

最后引擎编译非常耗时,如果要搞这一块的研究,一定一定要配台好电脑。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

设计模式_2_单例模式

单例模式 单例模式(创建型模式): 涉及到的单一的类,该类只负责自己对象的创建,并且只有单个对象被创建,提供唯一的对象访问方式,可直接访问 注: 只能有一个实例作为全局的访问点, 构造函数私有单例类只能自己创建自己唯一的实例, 必须给所有其他对象提供这一实例; !!!使用syn…

送书送书送书,(包邮)送,仅此而已!

亲爱的各位fans们,大家好,这次小编联合5个公众号朋友一起送书,一共送出 6本,当然包邮!感谢本次活动的赞助商北京大学出版社。6个公众号联合送书,一共6本书,每个公众号送1本(包邮)。 6个公众号同…

Java多线程_1_Java内存模型_内存模型的3大特性

Java内存模型: 内存分布情况及其关系: 主内存:Java内存模型规定所有的变量都保存在主内存中 工作内存:每个线程都有自己的工作内存,保存了该线程使用到的变量的主内存副本拷贝 主内存与工作内存的关系: 线程对变量的所有操作都必须在自己的工作内存中进行,不能直接读写主内存…

requests 获取div_爬虫系列第五篇 使用requests与BeautifulSoup爬取豆瓣图书Top250

上一篇我们学习了BeautifulSoup的基本用法,本节我们使用它来爬取豆瓣图书Top250。一、网页分析我们爬取的网页的url是https://book.douban.com/top250?icnindex-book250-all。首页如图与豆瓣电影Top250差不多,将页面拉到最底部,可以看到分页…

python--socket套接字/TCP

socket套接字/TCP 一 客户端/服务器架构 C/S架构,包括 硬件C/S架构(打印机)软件C/S 架构(web服务)C/S架构的软件(软件属于应用层)是基于网络进行通信的Server端要: 1.力求一直提供服…

uniapp封装网络请求_八张图带你走进“通过一个完美请求封装一个网络模块”

本文提供视频课程讲解,需要的小伙伴可以点赞私信‘’网络模块‘’前往领取学习大纲1、网络模块在应用中的地位1.1当今占统治地位的网络组件OKHttp OkHttp 是一个相对成熟的解决方案,据说 Android4.4 的源码中可以看到 HttpURLConnection 已经替换成 OkHt…

luogu P1046 陶陶摘苹果

二次联通门 : luoguP1046 /*这个题好难.....由苹果树可知这应该是个树结构的题所以很自然的想到了用树链剖分来搞一下连边 最后查询以1为根节点的子树的权值和...从前闲的没事写着玩... */ #include <cstdio> #define Max 3300void read (int &now) {now 0;char wor…

毕业的这0111年

1.2004年&#xff0c;不知道大家对这个时间有没有感觉&#xff0c;那几年我正在读高中&#xff0c;韩寒的《三重门》席卷校园&#xff0c;同样还有郭敬明的《夏至未至》。那时候的我&#xff0c;还挣扎在温饱阶段&#xff0c;我每天吃饭的时候都想上食堂的三楼吃风味&#xff0…

Struts2_4_ActionMap与ValueStack详解_Struct2的EL及常用标签_防止表单数据重复提交

接着Struts2_3_day的讲 注:使用Struts2的< s:debug>< /s:debug>就可获取数据储存的分布图 StrutsPrepareAndExecuteFilter都会创建一个ActionContext和ValueStack对象, 所以Struts2的数据存储分为两类: ActionMap(contextMap)以及ValueStack; ActionMap中都是以m…

python快递代取系统_代取快递的变现方式,校园跑腿的经营范围有多大?

原标题&#xff1a;代取快递的变现方式&#xff0c;校园跑腿的经营范围有多大&#xff1f;进入大学&#xff0c;随着越来越多的学生加入到网购的行列。快递在学校也是堆积成山&#xff0c;高校校园快递市场也日渐红火。但往往带来的也有更多的麻烦&#xff0c;学生取快递时间变…

java 空指针异常之一。 新建的一个对象没有NEW 导致其SET属性时报错

代码&#xff1a; //会报错 Lendbook lbk;//不会报错 Lendbook lbknew Lendbook();//在这里报错lbk.setLenddate(new Date());lbk.setPersoncode(personcode);lbk.setPersonname(personname); 转载于:https://www.cnblogs.com/Ychao/p/6811648.html

我的互助小蜜圈

写在前面 从不久之前到现在&#xff0c;我的微信公众号从一个小小小阶段到了另一个小小阶段&#xff0c;非常感谢各位读者对我的信任和支持&#xff0c;当然我也是非常用心的维护这这群大佬&#xff0c;时刻想着怎样给各位大佬做好服务 &#xff0c;比如我正在溜娃&#xff0c…

设计模式_3_建造者模式

建造者模式(BuilderPattern) 内在的核心问题就是: 使用多个简单对象根据一种组合关系构造一个我们想要的复杂对象, 属于创建型模式 建造者模式的角色关系 建造者模式中总共有两种角色: 1.建造者: 负责建造每个基本组件 2.指挥者: 将建造者建造好的基本组件按照某种组合关系进…

比较两个表格的不同_两表数据的核对,WPS表格似乎更加方便容易

在EXCEL中的两个表格的数据对比&#xff0c;可能使用的方法会采用到查询语句&#xff0c;VBA之类的。显得在使用的过程显得有些复杂。在不经间发现WPS表格的功能处理此类的问题显得就比较的简单的多。查看并标识出分数相同的内容如下面的分数表&#xff0c;选择分数栏的范围&am…

6递归

1 /*2 递归函数:3 函数自己调用自己就叫递归函数4 递归可以实现循环5 递归是神,迭代是人(迭代循环)6 缺点:需要暂存大量的数据,递归次数太多会占用大量内存空间7 8 递归和迭代的区别9 迭代是循环结构 10 递归是选择结…

我的知识小密圈

写在前面从不久之前到现在&#xff0c;我的微信公众号从一个小小小阶段到了另一个小小阶段&#xff0c;非常感谢各位读者对我的信任和支持&#xff0c;当然我也是非常用心的维护这这群朋友&#xff0c;时刻想着怎样给大家做好服务 &#xff0c;比如我正在溜娃&#xff0c;隔断时…

设计模式_4_原型模式(对象的拷贝)

原形模式(PrototypePattern, 创建型模式,创建重复对象且保证性能, 对象的克隆) 通常使用原型模式创建一个原型接口, 用于获取创建对象的克隆, 对于浅拷贝与深拷贝不用纠结, 他们二者的区别就在于重写Clonable的clone方法 浅拷贝与深拷贝 浅拷贝: 直接调用Object的clone pub…

vk_down 每次下翻丙行 c++_笔记本接口不够用?不妨试试这款Type-C拓展坞,给你7个接口用...

随着笔记本电脑越来越轻薄化&#xff0c;已经很难再布局较多的数据接口了&#xff0c;但是在办公等环境下总是需要这些接口来满足需求。就拿小新个人来说&#xff0c;使用的是小米笔记本12.5寸款的&#xff0c;该款笔记本仅有三个接口&#xff0c;分别是全功能 USB-C 接口 x 1、…

学习,才是最好的投资~

推荐语&#xff1a;我因为王小波常说而喜欢的英国哲学家罗素的一句话&#xff1a; 参差多态乃是幸福的本源。正是因为有了多种多样的行业&#xff0c;才使得我们的职业也是多种多样&#xff0c;行行出大牛&#xff01;这个世界天生就注定有人搞互联网的&#xff0c;Linux&#…

第二百四十八节,Bootstrap轮播插件

Bootstrap轮播插件 学习要点&#xff1a; 1.轮播插件 本节课我们主要学习一下 Bootstrap 中的轮播插件。 一&#xff0e;轮播 轮播插件就是将几张同等大小的大图&#xff0c;按照顺序依次播放。 基本实例。 第一步&#xff0c;给轮播器区域div设置一个id给轮播器区域div设置样…