欢迎光临散文网 会员登陆 & 注册

UPBGE - Blender 游戏引擎继承者

2023-04-23 00:38 作者:紧果呗  | 我要投稿

UPBGE 游戏开发系列教程:

# 🥚 UPBGE - Blender 游戏引擎继承者

## 🎨 UPBGE Python Scripting 

## 🎨 Logic Nodes (源代码分析)

## 🎨 Script Lifecycle (源代码分析)

## 🎨 UPBGE Python API (源代码分析)

UPBGE 作为 Bledner BGE 引擎的继承者,直接基于 Blender 源代码开发,集成度更高。Armory3D 开发团队自从 ArmoryPaint 工具获 EPIC Games 大奖后,重心似乎也偏向它了:

简而言之:

1. Armory3D 基于 Haxe 语言和 Kha 底层 HAL 硬件抽象层实现跨平台!

2. UPBGE 与 Blender 一体,Python 加逻辑节点编程,所见即所得,Grease Pencil,支持的!

源文档:https://github.com/Jeangowhy/opendocs/blob/main/upbge.md

Armory3D 使用 Armory2D + Zui 制作用户界面。UPBGE 有 Game GUI (bgui) 模块,需要另行安装。UPBGE + Blender 完全融合根本不需要这样的工具,直接使用场景建模、蜡笔绘画都是可以直接使用的工具,再有 UPBGE 提供的逻辑节点编程工具。或者直接使用 Python 组件扩展,甚至是 C/C++ 开发扩展库。


UPBGE 开发中有些不太便利的功能,就是看一个脚本组件却无法直接了解到它会被挂载到哪个对象上。通过 Game object 属性面板中查看脚本组件时,只能看到组件名称,如果组件命名不规范,那么也可能找不到相应的脚本,因为不能直接修改或编辑。所以,脚本起名就是个考验,不要让自己在场景中迷失。如果脚本有更新,那么还需要在已经挂载的对象上刷新脚本内容,否则依然使用旧的代码实现的功能,即使重新打开工程也一样不会自动更新。


下载安装 UPBGE 并运行,``Edit -> Preferences -> Add-ons`` 启用以下以下插件:

#. Game Engine: `Logic Nodes` 逻辑节点编辑器。

#. Game Engine: `Easy Online` 多人连网游戏插件。

#. Improt-Export: `Save As Game Engine Runtime` 游戏项目导出。

导出独立程序:File --> Export --> Save As Game Engine Runtime

`Easy Online` 会向脚本编辑器复制两个脚本 serevr.py 和 client.py,以及一个说明文档。


注意:在 UPBGE 程序崩溃后,插件可能被自动禁用,这会导致逻辑节点出现 Undefined 的节点和插槽。


UPBGE 有三大编程工具,选择 Blender 场景中的对象就可以添加这些设置:

1. Logic Bricks 逻辑砖,如其名,提供了一套现成的逻辑供调用,逻辑砖有三类,依次连接起来使用:

    1. **传感器** 用来接收硬件输入、或抽象信号产生游戏控制流。 sensors/index.rst

    2. **控制器** 用来做逻辑判定,满足逻辑条件就调用执行器节点。 controllers/index.rst

    3. **执行器** 通过控制器触发执行特定的操作,比如移动几何体。 actuators/index.rst

2. Logic Nodes 逻辑节点编程工具。

3. Python Components 编写脚本组件扩展游戏引擎中的逻辑,**args** 参数列表添加面板选项。


这三块内容可以互相转换,或者相互调用,除了 Logic Bricks 本身完全是导出 C++ 的接口内容,Logic Nodes 是完全 Python 脚本实现的功能,而脚本组件则是基于 Blender Python API 环境下的脚本开发,当然它们使用的类型基本上都是导出到脚本环境的由 UPBGE 源代码定义的类型。


除了这三块内容,还状态数据和游戏属性数据,Game Properites 是保存全局数据的一种方式,可以在以下位置添加:

1. `Logic Bricks - Sibar - Properties -> Game Properites`

2. `Logic Nodes - Sibar - Dashboard -> Game Properites`

3. `Game Object Properties -> Game Properites`

游戏对象,Game objects,或者是 C/C++ 定义的 `KX_GameObject` 类型是整个游戏引擎的核心,UPBGE 游戏编程构架将围绕**游戏对象**开展,场景中的几何体在脚本中就是`KX_GameObject` 类型,相机就是它的派生类型 `KX_GameObject` 类型等等,在开始深入引擎内部之间,非常有必要对引擎的类型系统的设计有一个大概的印象。场景对象使用 objects 属性引用场景中所有的游戏对象,游戏对象作为一个核心类型,它提供了大量 API 用于操作对象,如几何体变换、移动、旋转等,物理引擎方法等等。游戏对象一个主要功能是作为数据结构类型,记录游戏中的各种物体的状态属性,这一功能将始终与以上三大编程工具相结合。


UPBGE 编程环境中常用对象类型层次结构示意图,后续内容中再深入:

Logic Nodes 编辑器中侧栏面板 Globals 中可以添加全局数据字典,`Add Global Category` 即是添加一个字典用于存储数据,配合 `Value -> Globals` 节点使用,`Python -> Dictonary` 字典节点可以设置数据值,使用 `Init Empty` 还直接在节点中创建字典对象。


配合渲染器属性面板激活 `Game Debug -> Debug Properties` 可以在游戏窗口左上角显示属性数据。


游戏属性数据使用 5 类数据:


1. `Timer` 计时器对象存在时长,模拟时间(或帧时间),不是实际时间,帧率相同时两个时间才是相等。

2. `Float` 浮点数据,范围在 -10000.000 到 10000.000。

3. `Integer` 保存整数,范围在 -10000 到 10000,用于计算弹药等不需要小数的物品。

4. `String` 保存 128 个字符。

5. `Boolean` 保存``TRUE`` 和 ``FALSE`` 两个值。

有以下两种基本使用方式,通过 `GameObject["propname"]` 字典对象获取数据:


1. the `Property Sensor` (/manual/logic/sensors/types/property)

2. the `Property Actuator` (/manual/logic/actuators/types/property>


游戏中可以存在多个 `GameObject` 数据对象,在对应对象属性面板中设置。

`Game Object Properties -> Game Properites`


游戏中的每个 `GameObject` 可以存储控逻辑组件的集合(Logic Bricks),可以组合逻辑块来执行用户定义的动作,这些动作决定游戏模拟的进度。

逻辑节点 `Logic Nodes` 是 Blender 内建的一套可以视化节点编排系统,Armory3D 和 UPBGE 都基于这套系统开发了逻辑节点编程工具,UnrealEngine 蓝图 Blueprints 是同类可视化编程工具。逻辑节点树设计好后,必需挂载到场景中的对象上运行,Logic Nodes 编辑器侧栏面板也可以进行设置:`Administration -> Apply to Selected`,`Apply As` 指定挂载方式,点它击切换方式,Logic BricksPython Component


逻辑节点编程的基本思想是:控制流(事件)串连数据流节点,事件节点提供程序运行逻辑关系的组织信息,数据节点提供了相关环节的数据读写功能。编译时,会在工程 `bgelogic` 目录下生成 Python 实现脚本。


事件流或控制流决定了节点在什么条件下可以被执行,所有节点的 `Condition` 条件输入端口都可以接收控制流或事件流,也可以勾选激活端口,条件就设置为 True。


按以下几个个节点说明逻辑节点的基本使用流程:


1. On Init `GE_OnInit` 游戏运行初始化时,无条件地执行此事件。

2. On Update `ConditionOnUpdate` 持续触发执行。

3. On Next Tick `OnNextFrame` 将输入的触发条件延后一帧再触发。

4. Once `ConditionOnce` 单次触发,启用 `Repeat` 并达到延时复位时间即可以继承输入条件。

5. Print `ActionPrint` 打印信息到控制台,可以不连接控制流,勾选激活 `Condition` 端口即可。

UPBGE 和 Armory3D 的逻辑节点编程是两种不同的实现思维,后者有通过节点完成控制流连接关系的调用,前者则是集中在节点树上执行求值方法来实现整个逻辑的执行,即是 LogicNetwork 的 `evaluate()`。也是因为这样,逻辑树类形的求值方法变显得更复杂,远不及 Armory3D 逻辑节点树简洁明了。


求值函数需要对评估整个逻辑树挂载的节点,即各种 Cell 类型,其执行条件是否满足,是否要执行它,调用它的求值方法以准备好状态数据,可潜在的下游节点使用。也因为这种实现思路,`ActionPrint` 这样的逻辑节点就不需要连接控制流,直接激活 `condition` 端口就表示其满足执行条件。


如果有需要,可以直接向 UPGBGE 脚本源代码中添加调试代码以了解背后到底发生了什么。比如,在某个节点的配置方法中添加调试代码,查看一样当前逻辑树的类型以及所有者(GameObject)的类型信息:

逻辑节点编辑器中编辑的就是逻辑树 Logic Tree,逻辑节点的连接关系构建整棵逻辑树。树的结构可以嵌套执行,`Logic - Trees - Execute Logic Tree` 添加节点,并指定要执行的逻辑树。逻辑树也是节点组,它们没有本质区别,可以在侧栏面板中将当前选中的节点打包为新的节点树:

`Sidebar -> Dashboard -> Tree Prefabs and Substress -> Pack Into New Tree`


面板中提供了一个 WASD 4 键运行的节点预置,Tree Prefabs,点击 `4 Key Movement` 即可以自动按预置的节点连接添加到编辑器中。在其预置文件中可以查看所用节点信息,这些预置节点就是写好固定的功能,例如 WASD 四个节点是 `NLKeyPressedCondition`,按什么键盘都写好了,不能修改,除非是添加新的 `Key Down` 节点替换它们:


Blender 提供的节点编辑器最基础的两个组件就是:


1. `bpy.types.NodeSocket` 节点插槽基类;

2. `bpy.types.Node` 节点基类;


UPBGE 逻辑节点实现插件,bge_netlogic 插件代码主要分成四块:


- **uplogic** 逻辑节点运行时的实现。

- **basicnodes** 逻辑节点编辑器中节点 UI 的实现,最终子类属于 bpy.types.NodeNodeSocket

- **nodeutils** 节点编辑器中的节点分类目录,使用了 `nodeitems_utils` 插件模块。

- **ops** 包括代码生成器,操作组件,bpy.types.Operator,对应逻辑节点编辑器中的按钮等 UI。


UI 实现与运行时实现,有此基本类型的对应关系:

1. ParameterCell -> NLConditionNode -> 参数化节点;

2. ActionCell    -> NLActionNode    -> 动作节点;

3. ConditionCell -> NLParameterNode -> 条件节点;


除了按在 3D View 按下快捷键 `P` 运行游戏,还可以在渲染器属性面板运行,并自动生成逻辑节点代码:

`Render -> Game Resolution -> Embedded Game` or `Standalone Game`


Logic Nodes 编辑器侧栏面板也可以操作逻辑节点代码生成:`Administration -> Compile All`,或者点击 `Apply to Selected` 将逻辑节点树挂载到当前选中对象上,`Apply As` 指定挂载方式,可以是 Logic BricksPython Component,点击切换方式。如果是挂载为脚本组件,就生成相应脚本模块,模块名称使用节点树名称并且前缀 `LN_` 以表示逻辑节点树生成的脚本组件,Blender 脚本编辑器的列表中可以查看。每个逻辑节点树挂载为脚本组件,对应创建一个和节点树同名的类型定义,比如 `NodeTree`,并且继承自 `bge.types.KX_PythonComponent`。


两种挂载方式设置方式不一样,脚本组件方式挂载的逻辑树,脚本组件面板提供 `Only Run At Startup` 选项,要勾选它才表示在游戏开始时执行逻辑树。或者使用 `Execution Condition`,指定一个逻辑条件,它就是一个字符串,相当于是逻辑树的 condition 条件输入端口。但是它需要经过一次映射转换,即读取 self.objcet 对应字段的值使用执行条件,参考 bgelogic 目录下的生成代码:

注意,因为作为脚本组件挂载,所以 self 指的就是 `KX_PythonComponent` 实例,object 即是脚本组件的所有者,也就是组件所挂载的那个游戏对象,也就是场景中的对象。


注意,在 Apply As Logic Bricks 模式下编译才会生成外部脚本模块,如果是 Component 模式则会内嵌在 Blender 文件,使用自带的脚本编辑器查看。


工程 `bgelogic` 目录下生成代码中的逻辑节点树并不是一个具体的类型,它只是一个 Python 脚本文件,也是 Python 的脚本模块,这个模块中定义了:


1. 一个 `_initialize(owner)` 初始化函数;

2. 一个 `pulse_network(controller)` 控制器触发函数;


Logic Bricks 操作上和 Logic Nodes 基本没多大差别,都是节点之间的连接。

比如,`Keyboard` 和 `Always` 两个传感器连接到一个 `And` 控制器,用来触发一个 `Motion` 执行器,以实现对象的移动、旋转等等。尽管 `Always` 是执行活动中,但是因为 `And` 逻辑需要两个传感器都活动时才会执行,所以只有在按下键盘动作配合才能执行后续逻辑块,换成 `Or` 则不需要键盘。


`Always` 作为一种持续激活状态的传感器,它没其它额外的选项,只继承了 `SCA_ISensor` 类型中定义的最基本的传感器设置,如下:


https://upbge.org/docs/latest/manual/_images/logic-sensors-common-options.png

UPBGE-Docs\source\manual\logic\introduction.rst

UPBGE-Docs\source\manual\logic\properties.rst

UPBGE-Docs\source\manual\logic\states.rst

UPBGE-Docs\source\manual\logic\sensors\introduction.rst

1. **Pulse True Level** 正值脉冲模式,激活时输出的 `True` 状态才会发送给控制器;

2. **Pulse False Level** 负值脉冲模式,激活时输出的 `False` 状态才会发送给控制器;

3. **Skipped Ticks** 指定跳过多少个脉冲周期,0 表示不跳过任何脉冲,有信号就传递给控制器;

4. **Level** 触发模式:逻辑块内置状态机的状态改变时触发连接的控制器;

5. **Tap** 触发模式:在一帧后将传感器的状态更改为 negative 状态,即使传感器求值为 positive。

6. **Invert** 反转输出的状,`True` 和 `False` 反转为 `False` 和 `True` 输出;


传感器默认是 True 脉冲,比如,`Always` 激活 **Pulse True Level** 模式时就可以驱动控制器,如果激活 **Invert** 反转输出,那么就需要激活 **Pulse False Level** 才能驱动控制器。


其中的 **Level** 和 **Tap** 是两种互斥的模式或者都不激活,是不同的控制器触发逻辑。Level,也是状态数据的一种,当状态改变另一种状态,negative 与 positive,对应两种相反的状态,就如 True 与 False 对应。而 Tap 可以理解为滴水龙头,水滴未滴落的状态就是 positive,水滴掉落对应 negative,所以 Tap 模式下,状态总是在一帧后变为 negative。


Logic Ticks 和游戏运行的帧率等同,每帧就是一个逻辑时间周期,Skipped 多少逻辑周期,逻辑块就跳过相应的触发机会。


以一个 `Keyboard` 传感器为例,在以上功能都不激活的情况下,按下按键、和释放按键,对应的是两个触发控制器的机会,也就是控制器相应功能被执行,这种状态和激活了 Level 模式相同,因为键盘状态改变就触发连接的控制器。Tap 模式激活后,那么键盘的释放动作就被忽略,释放对应 negative 状态。


如果只激活 **Pulse False Level** 负值脉冲模式,那么键盘没有按键时就会触发控制器执行,并且会持续触发,因为此时键盘一直牌 negative 状态,控下按钮反而会停止触发。如果配合 Tap 模式,那么就只会在按下按钮时触发。


如果只激活 **Pulse True Level** 正值脉冲模式,那么键盘按下时就会一直触发控制器执行,直到释放按键。如果配合 Tap 模式,那么触发会更快速,因为 Tap 增加了触发脉冲。Pulse False Level 配合 Tap 模式的表现就完全不同。



Logic Bricks 编辑器界面分为三列,分别是:Sensors,Controllers、Actuators。每一列默认都有现行,首行设置 Logic Bricks 显示过滤条件,第二行设置逻辑砖的显示状态:


- **Sel** 显示选中对象的上设置的 Logic Bricks;

- **Act** 显示选中对象的上设置的 Logic Bricks;

- **Link** 显示左右两侧有连接线的 Logic Bricks;

- **State** 显示连着处于活动状态的 Logic Bricks;


第一行弹出菜单相等点击逻辑砖左侧的箭头图标,用于展开、收起面板:


1. Show Objects 显示当前栏的 Logic Bricks 对象;

2. Hide Objects 隐藏当前栏的 Logic Bricks 对象,只显示场景对象对应的一个基本状态位设置;

3. Show Sensors/Controllers/Actuators 显示当前栏的 Logic Bricks 对象上的具体设置;

4. Hide Sensors/Controllers/Actuators 隐藏当前栏的 Logic Bricks 对象上的具体设置;


游戏中所有对象都有状态信息,比如走动、站立、攻击等等,控制器逻辑砖面板中最基本的也是控制器的两种基本状态的数据面板 State Panel,可见性控制那个通道的控制器可见、有效:

- **Visible States** 提供 30 个状态位,设置 Controller 可见或不可见,游戏中是否生效;

- **Initial States** 提供 30 个状态位设置,激活其中的状态作为游戏开始的状态;


亮灰色方块表示不在活动状态,亮蓝色表示活动状态,Active,点击 `All` 按钮激活所有 30 个状态位。按住 `Shift` 拖动可以快速切换相应位置的状态。方块内有圆点,表示此状态数据挂载了 Logic Bricks,激活这个状态位就可以显示这些控制器。通过 **Controller visible at** 列表修改控制器所在通道。控制器作为 `EXP_Value` 的子类型,当然也继承它的 name 属性,控制器类型列表左侧的文本框设置。


激活 i 信息图标,并且打开调试属性选项就可以在游戏运行时,在左上角显示状态信息。

`Render properties --> Game Debug panel --> Debug Properties checkbox`


要在相应栏添加 Logic Bricks,点击相应的 Add Sensors、Add Controller 或 Add Actuator。

鼠标在两个逻辑砖的插槽 Link socket 之间拖动完成两个 Logic Bricks 的连接。要切开连接:按 `CTRL-RMB` 在连线上划过,切断连线。要删除逻辑砖,点击其右上角的 X 图标。也可以临时切换启用状态(Active),或者执行优先级(Priority),还可以在多个逻辑砖之间调整先后执行顺序。


侧栏属性面板 *Add Game Property*  可以向游戏引擎添加属性定义保存相关数据。

`Properties` (manual/logic/properties).


侧栏面板 *Python Components* 区域用来添加脚本扩展组件,`Register Component` 或者`Create Component`,这是 UPBGE 主要的编程手段。这是与逻辑节点、逻辑砖相互独立的模块,可以将脚本模块挂载到模型对象上。

see `Python Components` (manual/python_components/introduction).


打开 Blender 脚本编辑器,可以从 `Templates - Python Component templates` 菜单找到脚本组件模板,以供学习。


逻辑控制器除了常用的逻辑运算外,还可以使用两个特别的控制器,表达式和脚本模块:

1. **And**  逻辑与运算,输入条件同时为 `True` 时才执行 **Actuator**。

2. **Or**   逻辑或运算,输入条件只要有一个为 `True` 就运行 **Actuator**。

3. **Nand** 与非逻辑运算,Not And,输入条件只要有一个不为 `True` 就执行 **Actuator**。

4. **Nor**  或非逻辑运算,Not Or,输入条件全部为 `True` 才执行 **Actuator**。

5. **Xor**  异或逻辑运算⊕,eXclusive Or,当输入两条件相反时就执行 **Actuator**。

6. **Xnor** 同或逻辑运算,eXclusive Not Or,当输入两条件相同时就执行 **Actuator**。

7. **Expression** 只有在表达式求值结果为 `True` 时才执行 **Actuator**。

8. **Python** 就执行 **Actuator**。


UPBGE-Docs\source\manual\logic\controllers\types\python.rst

UPBGE-Docs\source\manual\logic\controllers\types\expression.rst

https://www.howtogeek.com/wp-content/uploads/csit/2021/05/22e2d43d.png

How Logic Gates Work: OR, AND, XOR, NOR, NAND, XNOR, and NOT

求值表达式中可以使用变量、常量以及各种运算符号,还可以使用 sensors 名称和 Game Properties 设置的属性数据。比如 `3 > 2` (True) 或者 `1 AND 0` (False)。假设设置 Game Properties  `coins` 属性数据为数值 30。同时又有一个传感器名称为 `Key_Inserted`,其值为 `True`,那么可以使用以下这样的表达式:

使用 **Python** 控制器就可以加载脚本模块,Python 脚本就是一个模块,它可以和 .blend 保存在同级目录中,或者子目录中,也可以使用 Blender 内嵌的脚本。比如同级目录有scripts/myscript.py 脚本定义了以下这样一个函数,那么就可以使用脚本控制器加载:`scripts.myscript.reload_me`,这个点路径中,目录可以称之为包 package,脚本文件称为模块 module,函数或类型称为导入的符号。控制器中导入的脚本,调用其函数时,会将当前的控制器作为参数传入。


导入外部脚本时,可以使用多级目录,但是在 Game Components 面板中创建脚本组件时就只能使用模块名加符号名的组合形式。即使指定多级点路径,UPBGE 也只会按前面两部分创建相应的模块和脚本组件类型。

UPBGE-Docs\source\blends\Python_Scripting\001_reloadme\reload.py

如果使用 Script 模式,将使用 Blender 内嵌脚本,这时,整个脚本用作控制器来执行,而不是其中的函数。这种执行方式下,需要借助 bge 模块出的各种功能与场景中的各种对象交互。使用全局函数 `globals()` 可以获取当前模块的全局符号表,通过返回的字典数据来观察属性是否存在。也可以使用内置函数 `dir()` 来打印对象的成员信息字典,使用 `type()` 获取类型信息。UPBGE 引擎的类型信息都在 `bge.types` 命名空间下,为了直接使用这些类型避免输入点路径,可以导入它们。


以下代码,假设逻辑块中已经给控制器连接了 Random 传感器和 Mouse 执行器等等,那么就可以使用脚本控制器来获取这些相连接的逻辑块对象。未连接到控制器的逻辑块对象不会记录在 sensors 或 actuators 集合中。注意,使用下标访问 `sens['Random']` 在对象不存在时引发异常,而使用 `hasattr()` 方法不能判断集合中的元素,只能用于判断属性,应该使用 `get()` 查询集合:


UPBGE - Blender 游戏引擎继承者的评论 (共 条)

分享到微博请遵守国家法律