UPBGE Python API (源代碼分析)

UPBGE 游戲開發(fā)系列教程:
#????UPBGE?- Blender 游戲引擎繼承者
## ???UPBGE?Python?Scripting?
## ?? Logic Nodes (源代碼分析)
## ?? Script Lifecycle (源代碼分析)
## ?? UPBGE Python API (源代碼分析)
源文檔:https://github.com/Jeangowhy/opendocs/blob/main/upbge.md
內(nèi)部模塊導(dǎo)致的對(duì)象相關(guān)接口可以從 UPBGE API 文檔查詢,另一個(gè)信息獲取途徑則是直接參考源代碼:
1. https://github.com/UPBGE/UPBGE-API
2. https://github.com/UPBGE/upbge/releases
* Blender for Developers
? * Bledner Code Layout https://www.blender.org/bf/codelayout.jpg
? * Bledner Code Layout of modules https://download.blender.org/ftp/ideasman42/pics/code_layout.webp
API 文檔本身是通過(guò) doxygen 工具從源代碼中內(nèi)容生成的摘要信息,所以閱讀源代碼有不一樣的效果,關(guān)鍵是找對(duì)閱讀方法(心態(tài))和代碼閱讀工具,高效定位文件、符號(hào)的工具即為好。閱讀代碼至少有兩點(diǎn)實(shí)用主義的好處:一是學(xué)習(xí)引擎更底層的工作原理,二是為今后可能的深入開發(fā)打下一個(gè)基礎(chǔ)。
源代碼閱讀工具配置參考:https://github.com/Jeangowhy/opendocs
Code Map and Live Dependency Validation on Visual Studio
https://learn.microsoft.com/en-us/visualstudio/modeling/map-dependencies-across-your-solutions
當(dāng)然,除了生成的摘要信息外,官方還在文檔中添加額外的資源,比如示范程序:
? ? upbge-0.30\doc\python_api\examples\aud
? ? upbge-0.30\doc\python_api\examples\bpy
? ? upbge-0.30\doc\python_api\examples\gpu
? ? upbge-0.30\doc\python_api\examples\matah
另外,還可以使用 Python 內(nèi)置的對(duì)象信息查詢方法,如 `help('tuple')` 或者 `dir('tuple')`。
根據(jù) Bledner Code Layout 文檔顯示,Blender 源代碼主要分布在以下幾個(gè)目錄:
對(duì)于 UPBGE 游戲引擎部分則集中在 gameengine 20 個(gè)子目錄下的將近 500 個(gè)文件。就當(dāng)下而言,將可以閱讀重點(diǎn)聚焦到包含 `KX_GameObject` 和 `KX_PythonComponent` 兩個(gè)類型的 Ketsji 這個(gè)子目錄下:
Python 模塊開發(fā)接口中定義了一套接口類型,如 `PyObject`、`PyCFunction`、`PyMethodDef` 等等用于向腳本環(huán)境導(dǎo)出類型定義、函數(shù)符號(hào)、成員方法定義。Ketsji Engine 基于這些接口類型定義了一系列宏函數(shù)用來(lái)向 Python 模塊導(dǎo)出各種符號(hào)定義。`EXP_PyObjectPlus` 這個(gè)抽象類代表了一個(gè)導(dǎo)出出 Python 腳本環(huán)境中的類型,它定義了一套通用方法,支撐起 UPBGE 腳本環(huán)境下的基類結(jié)構(gòu)。
另一方面,Python 接口規(guī)則使用 `PyTypeObject` 來(lái)定義一個(gè)導(dǎo)出到腳本環(huán)境的類型定義結(jié)構(gòu),這個(gè)接口導(dǎo)出的類型,在腳本看來(lái)才是“真正”的類型定義。相對(duì)于 `EXP_PyObjectPlus`,它是 C++ 類型。
源代碼條件編譯使用 `WITH_PYTHON`,以上這些符號(hào)都可以用來(lái)搜索那些導(dǎo)出到腳本環(huán)境的 API,名稱中帶有 DOC 表示這是一個(gè)導(dǎo)出 API 幫助文檔信息的宏定義:
KX_PythonInit.cpp 代碼有一系列核心模塊的導(dǎo)出,`initBGE()` 方法初始化的模塊包括:
1. Application Data (bge.app)
2. Game Types (bge.types)
3. Physics Constraints (bge.constraints)
4. 游戲邏輯模塊的方法導(dǎo)出 Game Logic (bge.logic)
5. 光柵化模塊的方法導(dǎo)出 Rasterizer (bge.render)
6. Game Keys (bge.events)
7. Video Texture (bge.texture)
引擎中定義了兩個(gè)鏈表數(shù)據(jù)結(jié)構(gòu),它們都是雙向循環(huán)鏈表,和雙向鏈表 (Doubly Linked List) 不同,增加首尾銜接功能,`SG_DList` 搜索動(dòng)作不會(huì)以有到尾端的情況出現(xiàn)。重雙向循環(huán)鏈表 `SG_QList`,同時(shí)存儲(chǔ)了兩條 `SG_DList` 雙向循環(huán)鏈表。鏈表是一種插入算法時(shí)間為常數(shù)的數(shù)據(jù)結(jié)構(gòu),因?yàn)殒湵碇械墓?jié)點(diǎn)使用指針記錄前后節(jié)點(diǎn)的位置,所以只需要斷開原鏈接,將新的數(shù)據(jù)鏈接前后節(jié)點(diǎn)即完成數(shù)據(jù)插入。但是數(shù)據(jù)搜索速度隨著節(jié)點(diǎn)數(shù)量增加而下降,因?yàn)橐鸸?jié)點(diǎn)線性查找。
以下兩個(gè)類型定義本身作為鏈表中的節(jié)點(diǎn)使用,不同的是 QList 存儲(chǔ)雙鏈,DList 存儲(chǔ)單鏈,每條循環(huán)鏈?zhǔn)褂?m_current 指針記錄當(dāng)前位置:
1. `SG_DList` Double circular linked list
2. `SG_QList` Double-Double circular linked list. For storing an object is two lists simultaneously
鏈表的一種改進(jìn)算法是跳表,以下用一個(gè) [1,9] 的區(qū)間數(shù)據(jù)來(lái)解析 SkipList 結(jié)構(gòu),因?yàn)閿?shù)據(jù)量少,只需要 2 級(jí)索引即可以實(shí)現(xiàn)二分法查找的效率:
`SCA_ILogicBrick` 是邏輯塊的基類,傳感器、控制器、執(zhí)行器等等都派生自此,它些類因?yàn)楣?jié)點(diǎn)連接關(guān)系的處理,就需要使用鏈表這樣的數(shù)據(jù)結(jié)構(gòu)。
`SCA_IObject` 是游戲?qū)ο蟮母割悾琒CA 前綴表明這是一個(gè)場(chǎng)景相關(guān)類型,這個(gè)類形提供一些常用方法。
`KX_Scene` 場(chǎng)景對(duì)象可以訪問(wèn)到場(chǎng)景內(nèi)的所有對(duì)象,它不僅繼承了 Python Proxy,還從第二父類 `SCA_IScene` 繼承一系統(tǒng)調(diào)試方法,C++/Python 支持多繼承,但這一特性帶來(lái)的復(fù)雜度多于便利。
多承繼引入的一個(gè)問(wèn)題就是同名方法查找問(wèn)題:如果繼承多個(gè)父類中,都定了相同的函數(shù),那么選用哪一個(gè)?這就是 Python MRO - Method Resolution Order 機(jī)制中 C3 算法要做的事,C3 即三個(gè)約束條件。
C3 算法保證了三件事情:
1. 單調(diào)性:任意兩個(gè)類的相對(duì)順序和自己所有父類的 MRO 順序一致,即父集與子集關(guān)系。
2. 一致性:任意兩個(gè)類的順序和繼承圖里所有直接繼承自這兩個(gè)類的聲明順序一致。
3. 有序性:如果兩個(gè)類不具有直接的繼承關(guān)系,那么找到兩個(gè)類的最小公共子類,其多繼承順序靠前的分支上的類具有高優(yōu)先級(jí)。
基本的樹狀數(shù)據(jù)結(jié)構(gòu)搜索方法有兩種:
- 【經(jīng)典類】 Depth-First Search (DFS) 深度優(yōu)先搜索算法;
- 【新式類】 Breadth First Search (BFS) 廣度優(yōu)先搜索算法;
Python 舊式類的算法使用從左往右(繼承順序),采用深度優(yōu)先搜索(DFS)的算法,稱為舊式類的 MRO。單純使用這兩種方法都不是最佳選擇,Python 最后選擇了 C3 算法。多繼承必然會(huì)遇到棱形法則關(guān)系處理 Multiple Inheritance: The Diamond Rule,以下示意圖摘自 Python 創(chuàng)始人的論文:
PEP 253 -- Subtyping Built-in Types by Guido van Rossum
C3 算法又稱為超類線性化 (superclass linearization)。Python 會(huì)計(jì)算出每一個(gè)類的 MRO 列表。一個(gè)類的 MRO 列表是一個(gè)包含了其繼承鏈上所有基類的線性順序列,并且繼承列表中的每一項(xiàng)均保持唯一。當(dāng)需要在繼承鏈中尋找某個(gè)屬性時(shí),Python 會(huì)在 MRO 列表中從左到右開始查找各個(gè)基類,直到找到第一個(gè)匹配這個(gè)屬性的類為止。
我們不必深究這個(gè)算法的數(shù)學(xué)原理,它實(shí)際上就是合并所有父類的 MRO 列表并遵循如下三條準(zhǔn)則:
- 先子類、后父類的順序進(jìn)行符號(hào)匹配檢查;
- 根據(jù)父類在列表中的從左到右順序進(jìn)行檢查;
- 如果對(duì)下一個(gè)類存在兩個(gè)合法的選擇,選擇第一個(gè)父類;
其實(shí)我們只需要知道 MRO 列表中類的順序代表著類層次結(jié)構(gòu)間的關(guān)系即可。使用類型的 `__mro__` 或者 `mro()` 可以獲取其 MRO 解析結(jié)果數(shù)據(jù)。
`KX_PythonProxyManager` 是游戲?qū)ο蟮淖?cè)中心,負(fù)責(zé)調(diào)用所有游戲?qū)ο蟮?`Update()` 方法。
以下是 KetsjiEngine 一些主要類型關(guān)系示意圖: