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

前言

上个月的这个时候我写了一篇文章关于如何嵌入 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 因为开源,基本上就是让你直接看源码(:з」∠)

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

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

uasset python_Unreal Python 结合 C++ 开发蓝图库插件相关推荐

  1. python全栈开发中级班全程笔记(第二模块、第四章)(常用模块导入)

    python全栈开发笔记第二模块 第四章 :常用模块(第二部分)     一.os 模块的 详解 1.os.getcwd()    :得到当前工作目录,即当前python解释器所在目录路径 impor ...

  2. 学习 Python 之 Pygame 开发魂斗罗(一)

    学习 Python 之 Pygame 开发魂斗罗(一) Pygame 回忆Pygame 1. 使用pygame创建窗口 2. 设置窗口背景颜色 3. 获取窗口中的事件 4. 在窗口中展示图片 (1). ...

  3. 使用python爬虫爬取蓝调口琴网乐谱

    学习目标:使用python爬虫爬取蓝调口琴网乐谱 提示:这里可以添加学习目标 例如:一周掌握 Java 入门知识 学习内容: 使用爬虫爬取需要动态验证码(如手机短信验证码)登录的网站. 提示:这里可以 ...

  4. python 全栈开发之路 day1

    python 全栈开发之路 day1 本节内容 计算机发展介绍 计算机硬件组成 计算机基本原理 计算机 计算机(computer)俗称电脑,是一种用于高速计算的电子计算机器,可以进行数值计算,又可以进 ...

  5. python 全栈开发,Day45(html介绍和head标签,body标签中相关标签)

    python 全栈开发,Day45(html介绍和head标签,body标签中相关标签) 一.html介绍 1.web标准 web准备介绍: w3c:万维网联盟组织,用来制定web标准的机构(组织) ...

  6. Python 短信通知系统开发实战

    课程介绍 作为学生,你想不想要这样一种服务:教务系统更新成绩后,你的手机上会自动收到成绩通知? 作为白领,你想不想要这样一种服务:公司发布了晋升.放假等新闻时,你的手机上会第一时间收到新闻? 作为-- ...

  7. 鸽子学Python 之 Matplotlib数据绘图库

    本文来自鸽子学Python专栏系列文章,欢迎各位交流. 文章目录 Matplotlib介绍 第一部分 Matplotlib基础 1 基础知识 1.1 图形绘制 1.2 坐标轴刻度.标签.标题 1.3 ...

  8. HaaS学习笔记 | 最详细的HaaS Python轻应用开发快速入门教程

    [1]摘要  本教程主要讲述HaaS框架开发环境的搭建以及在ESP32开发板上进行HaaS Python轻应用开发的基本流程,结合小蜜蜂老师研制的蓝蜻蜓ESP32开发套件,通过"Hello ...

  9. python 全栈开发,Day137(爬虫系列之第4章-scrapy框架)

    python 全栈开发,Day137(爬虫系列之第4章-scrapy框架) 一.scrapy框架简介 1. 介绍 Scrapy一个开源和协作的框架,其最初是为了页面抓取 (更确切来说, 网络抓取 )所 ...

最新文章

  1. word标题自动编号
  2. Java编程思想学习录(连载之:内部类)
  3. Dirichlet分布与多项分布的共轭性
  4. java 之持久化和序列化(反序列化)
  5. vue 修改favicon
  6. python的Web编程
  7. [译] ASP.NET 生命周期 – ASP.NET 请求生命周期(三)
  8. AndroidVector初探
  9. JDK源码-ArrayList源码
  10. Java学习(二)Object
  11. java单线程共享,「Java并发编程实战」之对象的共享
  12. 语法的集合?协议可没那么简单
  13. 11-11 11:11
  14. 阿里云盘来袭,送几个福利码!手慢无!
  15. c语言里的除法运算定律,加法乘法运算,乘法需要打括号吗?
  16. 模N计数器-计数+使能信号
  17. 【CNN】——涨点模块SE,CBAM,CA对比
  18. 聚合数据左磊:不走寻常路 做国内最好的数据聚合平台
  19. 苹果在华一天收入可抵与唯冠6000万美元
  20. if-else练习:交换值

热门文章

  1. [JavaScript] 正则表达式
  2. find_first_of和find函数的区别
  3. 面向对象——三层架构(表现层、业务层、持久层)
  4. pmp思维导图 第六版_PMP考试技巧攻略(上)
  5. Day11-递归性能测试
  6. loewe测试软件,实测Loewe三角包 最轻的小包最贴心的设计
  7. java console 到文件
  8. 1.7.08:字符替换
  9. easyUI文本框textbox笔记
  10. web.config文件之自定义错误节