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<UActorComponent>(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<USceneComponent>(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<UAssetEditorSubsystem>();

    TArray <UObject*> assets = sub->GetAllEditedAssets();


    for (UObject* asset : assets)
    {
        IAssetEditorInstance* AssetEditor = sub->FindEditorForAsset(asset, false);

        FLevelSequenceEditorToolkit* LevelSequenceEditor = (FLevelSequenceEditorToolkit*)AssetEditor;

        if (LevelSequenceEditor != nullptr)
        {
            ULevelSequence* LevelSeq = Cast<ULevelSequence>(asset);

            return LevelSeq;
        }
    }
    return nullptr;
}

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

  好在 Sequencer 提供了 GetSelectedObjects 的方法

  通过 LevelSequence 可以获取到 Sequencer

TArray<FGuid> URedArtToolkitBPLibrary::GetFocusBindings(ULevelSequence* LevelSeq)

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


    FLevelSequenceEditorToolkit* LevelSequenceEditor = (FLevelSequenceEditorToolkit*)AssetEditor;
    TArray<FGuid> 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 因为开源,基本上就是让你直接看源码(:з」∠)

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

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

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!
### 回答1: Unreal C是一种用于开发Unreal Engine的游戏编程语言,也称为蓝图图表和蓝图脚本。 Unreal C通过可视化编程方式来设计游戏逻辑,使得开发人员可以不需要了解编程语言的复杂性就能创建复杂的游戏逻辑和行为。 Unreal C结合了传统的程序语言和可视化脚本语言的优势,使得开发人员可以更快速的构建游戏,并且实时预览游戏效果。 对于想要学习Unreal C的人来说,官方提供了详细的教程和实例练习,包括基础语法、流程控制、函数调用、事件绑定等等。同时,由于Unreal C是基于C++开发的,因此有一定的编程基础的人也能较快地上手学习。 此外,开发者还可以在Unreal Marketplace中寻找和购买已经开发好的插件和功能模块,这些插件与Unreal C结合使用可以快速高效地开发出高品质的游戏玩法。 总之,Unreal C是一个功能强大的可视化编程工具,通过学习掌握它,可以让游戏开发者更容易快捷地构建复杂的游戏内容,从而让游戏玩家获得更好的游戏体验。 ### 回答2: Unreal C++是一种高性能的游戏编程语言,提供了丰富的游戏开发工具类库和API,可以满足各种游戏开发者的需求。该语言嵌入了Unreal Engine 4引擎中,支持跨平台游戏开发。想要学习Unreal C++,需要掌握C++语法和面向对象编程的相关知识,同时熟悉Unreal Engine 4的相关操作和API使用。为了方便学习和开发,Unreal Engine 4提供了丰富的游戏开发文档,包括各种主题的详细教程、API参考文档以及社区支持。此外,Unreal Engine 4还提供了强大的脚本工具Blueprint,可以让开发者在不需要编写代码的情况下快速构建功能丰富的游戏。总之,Unreal C++是一个非常强大和灵活的游戏编程语言,有着广泛的应用和巨大的学习和发展空间。 ### 回答3: Unreal C++是一种基于C++的语言,用于编写Unreal Engine 4的游戏程序。它是UE4框架中最核心的组成之一,提供了直接访问游戏引擎的代码和功能的能力。 Unreal C++是一个功能强大的编程语言,支持面向对象思想和模块化开发。相比于其他游戏引擎,它的语法更加易读、易懂,程序员可以更加方便地进行开发。 Unreal C++除了拥有C++的所有功能和特性外,还有许多自己的API和功能,比如基于UE4框架的蓝图系统,它允许非程序员基于可视化界面进行游戏的逻辑设计和开发,实现具有交互性的游戏场景和元素。除此之外还有底层的控制台命令、实时游戏调试、完整的物理引擎、二维和三维图形渲染引擎等等。 无论是开发大型的3D游戏还是小巧的独立游戏,Unreal C++都是一款非常出色的引擎语言,它可以帮助游戏程序员轻松构建全平台、高效率、高品质的游戏项目。如果你正在寻找一个强大而易用的游戏引擎语言,那么Unreal C++绝对是一个值得了解和使用的工具。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值