【游戏编程扯淡精粹】UE5 蓝图
最近新学蓝图编程。。还没看蓝图VM代码,有些点不保证准确
本文主要是从使用角度,分析你为什么需要学习蓝图,蓝图适合做什么,不适合做什么
最后 Bonus,跟一下 Blueprint Pipeline,梳理一下蓝图的知识结构
大纲
- 蓝图是什么
- 蓝图对非程序
- 蓝图对程序
- 蓝图适合用于
- 蓝图不适合用于
- 蓝图管线
- 蓝图版本管理
- 蓝图迁移 Lua
蓝图是什么
Blueprint - Wikipedia
蓝图(英语:Blueprint),港澳地区又称“蓝本”或“蓝纸”,是工程制图的原图经过描图、晒图和薰图后生成的复制品,因为图纸是蓝色的,所以被称为“蓝图”。蓝图类似照相用的相纸,可以反复复制新图,而且易于保存,不会模糊,不会掉色,不易玷污。
蓝图这个词的本义很接近 Prefab,预制体,蓝图描述一个东西的结构和行为,可以多次拷贝,实例化为具体的实体 Entity
UE 蓝图是一种资源,把引擎暴功能以可视化脚本的形式暴露出来
另外,UE3 时代的可视化脚本叫做 Kismet
蓝图实现原理
实现原理,基本综合了常见脚本方案的做法
- Lua,一般的脚本语言一样,跑解释器执行
- IL2Cpp,编译到C++
- GDScript,和引擎内核紧密结合
说下第三点,主要是和 Lua 有区别
Lua 是通用的,有自己的对象模型,但是接入每个引擎,都需要做一个类型映射,记录额外的类型信息,这是有开销的,想一想各种 SDK 的 XXXVector3 类
蓝图是直接在 UE 内核上实现的,因此只能 UE 用,没有类型映射的开销
如果需要 UE 蓝图编辑,在自家引擎跑,就需要实现一个 Compiler,把蓝图翻译成自己引擎的格式,工作量和坑不小
蓝图对非程序
脚本对非程序的游戏开发技能提升是有积极意义的
脚本本质是把 Compiler 和 Linker 集成到 Engine Runtime,这样只要下载游戏就可以编程了,不需要装Visual Studio 或者某种编译工具链(一般很重型),对于UE蓝图,则是把 IDE 集成到 UE Editor
蓝图目前的功能几乎覆盖了 Gameplay 开发,还包含联网,理论上开发玩法可以靠纯蓝图完成
有一些纯蓝图开发的案例,作者是非程序,比如谌嘉诚的《死寂》
总的来说,蓝图降低了编程的门槛
蓝图对程序
对程序员,理论上掌握 C++ 和 Lua,不需要学蓝图
所以其实问题是,没时间学蓝图,怎么用最小成本,学到能看懂别人的蓝图的程度
笔者采取的策略,是把蓝图翻译成熟悉的 Lua
学习一门语言关键在于理解语言的语义,或者说VM是如何实现的
蓝图适合用于
- 策划实现流程图,高阶的的逻辑设计,流程整合
- 单数据流,不要存数据,把 Input / Output 串联起来 // 局部变量,不要全局变量
- 单机流程,不要联网
说白了,就是策划才写蓝图,蓝图节点由程序实现,不要搞得很复杂
让策划写脚本,本质是程序和策划工作的解耦,策划提需求,策划自己实现,自己维护和迭代
这个边界定在哪,其实取决于团队策划的平均水平,没有经验可以从简单做起,如前所说,这是一个机会
蓝图不适合用于
说白了,复杂的东西都不适合。。这里针对的是大型项目工程,需要 scale,需要快速定位问题,后期低成本解决性能问题
反过来说,适合做 Demo,但是 Demo 无法 scale 到大项目
选择脚本语言需要优先考虑的是性能上的 scale,VM 的性能决定了能承载多复杂的代码(指令吞吐量),开发写得很爽,性能不行,最后还是要 C++ 化
然后是工程上的 scale,不解释了。。
蓝图在这两点上和 Lua 基本没法比,所以蓝图 VM 也没有深入理解的必要,别用蓝图就完了
蓝图问题案例1
重构的问题,拿到 Lyra 的代码,把 C++ 代码的 “Lyra” 全局替换成自己项目前缀,Boom,蓝图爆炸了,因为蓝图里都还是引用的 LyraXXX 类型
移植的问题,这和 UE 插件本身也有关系,但是可以看出,即使是很简单的情形,也很麻烦
Lyra 要接入一个背包插件,要把插件中的 ExamplePlayerController(蓝图实现) 移植到 LyraPlayerController(C++实现) 上
如果都是 C++,把 ExamplePlayerController 代码复制粘贴,合并到 LyraPlayerController 下面,再全局文本替换 ExamplePlayerController 为 LyraPlayerController,ez,10min 搞定
蓝图呢?蓝图的属性和函数要拷贝过去吧,直接CV,Boom,编辑器 Crash,最后找了个插件拷贝过去了
拷贝过去还是编译不了,为什么呢,因为蓝图是有类型的,ExamplePlayerController.MyMethod 节点和 ExamplePlayerController 类绑定了,要把原来的节点删掉,再拖出来新节点 LyraPlayerController.MyMethod ,所有连线重新连一遍,有的节点要连五根线。。
这样的操作,需要搜索引用,然后一个一个节点去改
蓝图问题案例2
项目用蓝图开发一个月了,补充一下多人开发的问题
项目起点是一些 Demo 的混合,有大量蓝图脚本,多人在一个模块上开发时非常痛苦
多人开发阻塞
- 拉新版本,点击 Play 发现蓝图编译不过
- 其他人在修改一个蓝图文件,我就不能修改了,UI 美术改控件蓝图基本半天以上
Crash 率
- 不低,其他项目反馈到的,包括编辑时和运行时
编辑效率
- 无法看 Diff,不知道某个提交改了什么
- 无法搜索某个符号的引用,哪里引用了这个变量 / 函数
- 没有文件列表,需要搜索+鼠标点击1-2次,才能跳转到某个蓝图
这里说无法,确切的说是不可用,有蓝图 Diff,有全局搜索,效率低得没法用
对比Lua的文件列表
蓝图问题小结
- 蓝图是二进制格式
- 版本管理,多人协作开发,分支并行开发不可用
- 重构困难,移植别人的代码困难
- 信息密度低,占据屏幕面积大
- 蓝图是有类型系统的,并且依赖于 UE Editor 打开加载整个项目
- 蓝图不是通用编程语言,缺乏其他脚本语言的基本特性
- 蓝图是为了解决可视化编程的问题,并为易用性做了妥协和阉割
- 蓝图编辑依赖于 UE Editor 作为 IDE,而这个 IDE,显然干不过 Visual Studio 和 JetBrain
- 蓝图编辑依赖于频繁的鼠标点击,编辑效率低
- 蓝图代码散落在工程各个地方,你需要一个一个用鼠标点开
- BUG 率 / ,不会比 Lua 更好
说白了,可用性低,开发效率低
语言 BUG 率要求很高,是程序员的 trust base,出了问题老是需要怀疑是不是语言出了 BUG,定位问题的效率会大打折扣
蓝图管线
BP 用于扩展
- BP Actor
- BP Component
- BP EventGraph
Actor和Component其实也属于继承C++类,BP Actor对标Unity Prefab
EventGraph对标Flow,是BP相比于C++和Lua最大的优势
BP属性
- Variable
- Function
- Macro
- Event Dispatcher // BP <=> BP
BP类别
- Actor
- Level
- Anim
- Sequencer
- UMG
- Niagara,特效
C++ <=> BP
- 继承,BP可以继承C++类
- Event/Function/Var,C++定义,暴露给BP
BP <=> BP
- 存BP类型的变量,然后调用
- 接口,类似Go
- 走C++做桥接
- Event Dispatcher
标准库
- FuncLib,有C++实现的,也有BP实现的
- MacroLib,BP Macro
蓝图版本管理
- 本质是因为蓝图是二进制格式,同一文件同一时刻只能一个人修改,导致版本管理的并行开发失效
- 所有人用 UE 集成的版本管理来 checkout 蓝图
- 不要用 P4V 或者 P4 命令行绕开,UE checkout 并不会 p4 lock,只是 UE 里警告拦一下
- 程序不要用蓝图编程,用 C++ 或者 Lua
蓝图本地化生成 C++,不可用蓝图 Merge & Diff,不可用
UE 里修改蓝图前点一下保存,有其他人 checkout 了就别改了(你被阻塞了)
蓝图本地化
- UE5 不支持
- 生成代码勉强可读,但是不能用于继续在C++上开发
这个生成代码,更像是 IL2CPP,只是打包时用,开发还是蓝图编程
蓝图 Merge
经过测试,不好用,并不能自动 Merge,所以只是一个 Diff,而且 Diff 也很糟糕
蓝图迁移 Lua / C++
UnLua 可以完全替代蓝图,可以增量地把蓝图 Lua 化
- 控件蓝图,因为使用蓝图编程,导致 UI 程序和 UI 美术无法并行开发,优先迁移
- 团队里很多人都涉及修改的,容易冲突和编译不过的蓝图,优先迁移
----- 增加一些后续日常开发中的案例和问题
节点和类型绑定导致难以重构
一个Widget变量类型从UniformGrid 改成 Grid
然后就break了,要全部重新连,哪怕两个类型都有ClearChildren接口(以及AddChildToUniformGrid
AddChildToGrid),但是ClearChildren不是两个类的基类里的,就会break
语言有类型系统本身是帮助重构的,但是这里却反而很麻烦
UE编辑器崩溃+蓝图没保存=寄
实际开发中,编辑器崩溃率不低,一部分是UE引擎问题,一部分是项目人多了,用法可能超出UE的考虑
蓝图搜索很慢,还有漏
使用上,建立索引很慢,搜索相当于全局搜索字符串
曾经查一个偶现的鼠标Cursor没了的问题,在蓝图里搜 PlayerController.bShowMouseCursor,非常痛苦,最后还是没有定位问题
没有看过底层实现机制