最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

UPBGE Logic Nodes (源代碼分析)

2023-04-24 10:03 作者:緊果唄  | 我要投稿

UPBGE 游戲開發(fā)系列教程:

#????UPBGE?- Blender 游戲引擎繼承者

## ???UPBGE?Python?Scripting?

## ?? Logic Nodes (源代碼分析)

## ?? Script Lifecycle (源代碼分析)

## ?? UPBGE Python API (源代碼分析)


與邏輯塊、腳本組件不同,UPBGE Logic Nodes 以插件的形式集成在 UPBGE 開發(fā)環(huán)境,完全以 Python 腳本實(shí)現(xiàn)。邏輯塊和腳本組件則基于導(dǎo)出到腳本環(huán)境中的 C++ API 開發(fā)。


源代碼位置對應(yīng)如下,Logic Nodes 是一個獨(dú)立插件項(xiàng)目,可以在 Github 上下載:


1. **Logic Nodes**? ? ? ?scripts\addons\bge_netlogic\basicnodes\__init__.py

2. **Logic Bricks**? ? ? upbge-0.30\source\gameengine\GameLogic

3. **Python Components** upbge-0.30\source\gameengine\Ketsji\KX_PythonComponent.h

4. Uchronian Logic: UPBGE Logic Nodes https://github.com/UPBGE/UPBGE-logicnodes

Blender Python Console 執(zhí)行以下腳本可以獲取邏輯節(jié)點(diǎn)分類與運(yùn)行時類型對照表,Blender 環(huán)境不支持 stdout 重定義,只好將輸出文件 nodes.md 設(shè)置到打印函數(shù)參數(shù)中:


注意,在腳本組件中,有些方法不能直接調(diào)用,比如只有在 Logic Bricks 下有效的當(dāng)前控制器獲取方法就不能在腳本組件中直接調(diào)用,除非是在 python controllers namespace:

? ? SystemError: bge.logic.getCurrentController(), this function is being run?outside the python controllers context, or blenders internal state is corrupt.

你應(yīng)該知道腳本會用什么方式調(diào)用,至少有以下四種不同的運(yùn)行方式:


1. Logic Bricks - Python Controller

2. Logic Nodes - Python - Run Python Code

3. Game Object Properties - Python Component

4. Game Object Properties - Game Object


Controllers 是腳本編程中的橋梁一樣的對象,它上連傳感器 Sensors,下連執(zhí)行器 Actuators。在源代碼中,控制器的實(shí)現(xiàn)代碼很少量,因?yàn)樗旧淼倪壿嫴粡?fù)雜。他的父類 `SCA_ILogicBrick`, 涉及整個 BGE 邏輯塊的實(shí)現(xiàn),邏輯復(fù)雜,代碼也相對多。


? ? upbge-0.30\doc\python_api\rst\bge.types.rst

? ? upbge-0.30\doc\python_api\rst\bge.logic.rst

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

Logic Nodes 編輯器側(cè)欄面板也可以操作邏輯節(jié)點(diǎn)代碼生成:`Administration -> Compile All`,注意使用 `Apply As Bricks` 方式,如果使用 `Apply As Component` 方式下編譯,生成代碼則會內(nèi)嵌在 Blender 文件,可使用自帶的腳本編輯器查看。


工程 `bgelogic` 目錄下生成代碼中的邏輯節(jié)點(diǎn)樹并不是一個具體的類型,它只是一個 Python 腳本文件,也是 Python 的腳本模塊,這個模塊中定義了:


1. 一個 `_initialize(owner)` 初始化函數(shù);

2. 一個 `pulse_network(controller)` 控制器觸發(fā)函數(shù);


控制器對象 `owner` 屬性引用一個當(dāng)前調(diào)用這個控制器的游戲?qū)ο?GameObject。因?yàn)橐粋€控制器可以掛載到多個對象上,不同的控制器掛載到不同對像,使用所用的游戲?qū)ο笠膊煌?/p>



一個名為 **NewTree.001** 的邏輯節(jié)點(diǎn)樹,用 `On Init` -> `Once` -> `Print` 去執(zhí)行

打印 `Get Property` 獲取的屬性數(shù)據(jù)。由代碼生成工具 tree_code_generator.py 生成的邏輯

節(jié)點(diǎn)樹代碼如下:

代碼中會導(dǎo)入 uplogic\nodes.py 模塊,創(chuàng)建 `LogicNetwork` 實(shí)例,它代表了邏輯節(jié)點(diǎn)樹,節(jié)點(diǎn)類型定義為稱為 Cell。變量命名按 CON - Condition Nodes, ACT - Action Nodes 這樣的規(guī)則。網(wǎng)絡(luò)執(zhí)行時,`pulse_network()` 被調(diào)用,傳入控制器所攜帶的 owner 游戲?qū)ο蠼o初始化方法,初始化好連接關(guān)系,再通過 `setup()` 方法配置所有連接的節(jié)點(diǎn)。


`StatefulValueProducer` 定義一個有狀態(tài)節(jié)點(diǎn),兩個接口函數(shù) `get_value()` `has_status()`。`LogicNetworkCell` 定義一個有運(yùn)行控制方法的節(jié)點(diǎn)(Cell),定義多個接口函數(shù),還有多個屬性:


1. **_uid** 私有屬性,節(jié)點(diǎn)標(biāo)識;

2. **_status** 私有屬性,存儲節(jié)點(diǎn)狀態(tài);

3. **_value** 私有屬性,存儲節(jié)點(diǎn)值;

4. **_children** 私有屬性,子節(jié)點(diǎn)集合;

5. **network** 所屬節(jié)點(diǎn)網(wǎng)絡(luò);

6. **is_waiting** 是否處于等待狀態(tài);


接口方法包括了 `setup()` `stop()` `reset()` 以及 `evaluate()` `deactivate()` 等等。`LogicNetwork` 作為節(jié)點(diǎn)樹的實(shí)現(xiàn),它實(shí)現(xiàn) `setup()` 方法設(shè)置樹內(nèi)連接的所有節(jié)點(diǎn)的 network 屬性,以及調(diào)用其 `setup()` 方法完成配置,如果節(jié)點(diǎn)有配置需要。


LogicNetworkCell 接口定義了三個邏輯狀態(tài),初始狀態(tài)就是 WAITING

節(jié)點(diǎn)求值完成后,調(diào)用 `_set_ready()` 切換到 READY 狀態(tài),或者 `_set_status()` 直接設(shè)置狀態(tài)。

NO_VALUE 在 On Value Changed To `ConditionValueTrigger` 這些需要通過求值結(jié)果來決定是否輸出觸發(fā)條件的節(jié)點(diǎn)上使用。求值方法通過判斷最后一次更新的值來決定如何調(diào)用 `_set_value()`,初始狀態(tài)下的 last_value 初始值就是 NO_VALUE。


`LogicNetwork` 作為邏輯樹的實(shí)現(xiàn),是邏輯節(jié)點(diǎn)編程的核心節(jié)點(diǎn),也是邏輯節(jié)點(diǎn)的管理器。它繼承了以上的狀態(tài)之外,還要實(shí)現(xiàn)其它節(jié)點(diǎn)的功能,比如給 Ray Casts 節(jié)點(diǎn)提供 `rayCast()` 投射方法,所以邏輯樹在實(shí)現(xiàn)上也是一個 Caster,這么多功能使用得它本身的結(jié)構(gòu)有點(diǎn)大。射線投射方法還會在邏輯樹上設(shè)置私有屬性,`_NL_ray_cast_data` 用于緩存數(shù)據(jù)。


? 邏輯節(jié)點(diǎn)使用教程

UPBGE 屏幕空間大小標(biāo)準(zhǔn)化為 Vec(1,1),要獲取窗口原像素單位大小,可以使用渲染器屬性。相機(jī)對象 `KX_Camera` 提供了 API 用于獲取場景中的 3D Vector 坐標(biāo),或者獲取與指定視線方向相交的對象。又或者反過來,將指定對象的原點(diǎn)坐標(biāo)轉(zhuǎn)換為相應(yīng)的屏幕坐標(biāo)。這里涉及的變換矩陣可以通過相機(jī) API 獲取。


upbge-0.30\doc\python_api\rst\bge_types\bge.types.KX_GameObject.rst

upbge-0.30\doc\python_api\rst\bge_types\bge.types.KX_Camera.rst


相機(jī)對象的父類,`KX_GameObject` 提供兩個更基礎(chǔ)的方法,其中 `rayCast()` 在邏輯樹中調(diào)用:從一個坐標(biāo)點(diǎn)或物體觀察另一個點(diǎn)或物體,在限定 **dist** 距離內(nèi)找到具有 **prop** 屬性匹配的,射線碰撞到的第一個物體。


1. **objto** 參數(shù)接收一個目標(biāo)坐標(biāo)或?qū)ο螅壿嫎渲惺褂?ray_target 變量。

2. **face** 參數(shù)用于確定返回法線的朝向,0 表示總是朝向射線原點(diǎn),1 表示碰撞點(diǎn)的曲面法線。

3. **xray** 是否使用 X 光穿透,配合 **prop** 屬性,默認(rèn) 0 表示不穿透,有效目標(biāo)被遮擋就不能拾取。

4. **poly** 根據(jù)不同的值 0/1/2 值返回 3 ~ 5 個數(shù)據(jù),(KX_GameObject, hitpoint, hitnormal, KX_PolyProxy, hituv)。

5. **mask** 使用 16-bit 數(shù)據(jù)用于碰撞層的對象過濾,`collisionGroup & mask`,同層才進(jìn)行檢測。

用戶在游戲世界中需要使用鼠標(biāo)、屏幕等基礎(chǔ)設(shè)備進(jìn)行交互,即就是輸入、輸出的數(shù)據(jù)處理,當(dāng)用于在屏幕上一點(diǎn)進(jìn)行操作,而這個點(diǎn)輸入時對應(yīng)的時屏幕空間的二維坐標(biāo),根據(jù)引擎中的相機(jī)成像過程,進(jìn)行逆運(yùn)算才能得到相應(yīng)的三維空間的坐標(biāo),`getScreenVect()`。反過來,3D 場景中的對象坐標(biāo)要通過成像過程的數(shù)學(xué)運(yùn)算,才得到相應(yīng)的坐標(biāo),`getScreenPosition()`。


軟件中的相機(jī)只是抽象的概念,本質(zhì)上是數(shù)學(xué)關(guān)系,光線傳播或者是光學(xué)成像原理。那么,求解 3D 空間坐標(biāo)參照的相機(jī)位置就是一個關(guān)鍵信息,不同相機(jī)成像不同,也就是對應(yīng)不同空間坐標(biāo)中的物體、表面的信息。相當(dāng)在屏幕各個像素發(fā)射一條射線,沿著相機(jī)的正前方傳播,光線與物體碰撞時的坐標(biāo)位置就是要求解的屏幕坐標(biāo)對應(yīng)的 3D Vector。


以下射線工具需要使用 `KX_Camera` 對象的 API,因此相機(jī)屬性只能選擇相機(jī),不能使用其它對象替代。并且,射線可以設(shè)置一個有效距離,超過距離就不再進(jìn)行碰撞檢測。另外,待檢測的物體可以設(shè)置特定的屬性,**Game Properties** 面板中設(shè)置一個數(shù)據(jù)屬性,然后射線節(jié)點(diǎn) Property 屬性中填寫相同的名稱,那些沒有設(shè)置相應(yīng)屬性的對象就不會參與射線的碰撞檢測。


參考 `ActionMousePick` 節(jié)點(diǎn)調(diào)用邏輯樹定義的光線投射方法獲取數(shù)據(jù),**ray_origin** 就是光線傳播的起點(diǎn),留空表示默認(rèn)使用相機(jī)成像平面發(fā)射光線,光線從起點(diǎn)向 **ray_target** 坐標(biāo)傳遞,兩個坐標(biāo)向量相減得到的向量就表示光線傳播方向。


? ? raw_target = camera.worldPosition - camera.getScreenVect(x, y)

射線投射邏輯節(jié)點(diǎn)功能 Ray Casts:

1. **Mouse Ray** 按照指定相機(jī)視角,從光標(biāo)位置發(fā)出射線,并獲取射線首次碰撞到對象信息。

2. **Camera Ray** 按照指定相機(jī)視角,在相機(jī)坐標(biāo)空間原點(diǎn)向 Aim 坐標(biāo)投射射線,默認(rèn)拾取屏幕中心目標(biāo)對象。

3. **Ray** 直接指定射線起點(diǎn) Origin 和目標(biāo)點(diǎn) Aim,拾取兩點(diǎn)之間首個碰撞目標(biāo),Visualize 可查看射線。

4. **Projectile Ray** 按拋物線投射光線,Power 能量越大線條越直,可以激活 Visualize 查看效果。


射線投射邏輯節(jié)點(diǎn)設(shè)計(jì)時、運(yùn)行時類型對照:


????UPBGE-Docs\source\manual\logic\sensors\types\ray.rst

射線投身節(jié)點(diǎn)通用屬性說明:

  1. Distance 指定射線有效距離,在此距離內(nèi)的物理對象才可能被拾取。

  2. Property 指定一個名稱,只有設(shè)置了相應(yīng) Game Properties 的對象才可能被拾取。

  3. 啟用 X-Ray 可以拾取被遮擋的有效目標(biāo),即設(shè)置了 Property 中指定的屬性的游戲?qū)ο蟆?/p>

Game Properties and Logic Nodes


**Camera Ray** 節(jié)點(diǎn)中的 Aim 坐標(biāo)是以相機(jī)坐標(biāo)空間計(jì)算的,也就是笛卡爾坐標(biāo)系統(tǒng),以屏幕中心為原點(diǎn) (0,0)。這個原點(diǎn)與鼠標(biāo)默認(rèn)的坐標(biāo)原點(diǎn)(屏幕左上角)不重疊,并且相機(jī)空間與模型使用相同的長度單位,而鼠標(biāo)使用的是規(guī)范值,[1,1],所以鼠標(biāo)的坐標(biāo)數(shù)據(jù)需要根據(jù)窗口大小進(jìn)行轉(zhuǎn)換:

? ? ????mouse_position - 0.5 * window_size

注意,Aim 輸入可以是 Vec2 也可以是 Vec3,前者會觸發(fā)屏幕空間轉(zhuǎn)換,而 **Status** 節(jié)點(diǎn)獲取的坐標(biāo)數(shù)據(jù)已經(jīng)將二維轉(zhuǎn)換為三維向量,所以不會觸發(fā)這個過程。同樣 **Vectory XY** 也是輸出 3 維。所以要么直接設(shè)置 Aim = [0,0] 拾取屏幕中心目標(biāo)對象,要么自行處理目標(biāo)坐標(biāo)數(shù)據(jù):


使用 **Get Resolution** 節(jié)點(diǎn)可以獲取全屏幕的像素大小,但是不能直接用來將鼠標(biāo)坐標(biāo)轉(zhuǎn)換成相機(jī)畫面中的世界尺寸。畫面的大小與相機(jī) lensfov 等參數(shù)密切相關(guān)。代碼中乘 10 是一個放大系數(shù)。


在處理數(shù)據(jù)過程中,Math 運(yùn)算節(jié)點(diǎn)可以對向量進(jìn)行數(shù)值的運(yùn)算,而 Vector Math 節(jié)點(diǎn)則進(jìn)行向量運(yùn)算,例如點(diǎn)積,叉積乖乖。其中 Vector XY,雖然只有兩個分量,但其實(shí)它是三維的輸出。


還有 RunPythonCode,雖然它可以運(yùn)行腳本,但也只是調(diào)用函數(shù),并且參數(shù)只能有一個,當(dāng)然,可以通過字典對象傳遞多個值。


另外,Object -> Data -> Get Position 等節(jié)點(diǎn)可以獲取游戲?qū)ο蟮目臻g坐標(biāo)等信息,和專用的 Get Property 節(jié)點(diǎn)不同,它專用于 Game Properties 屬性數(shù)據(jù)的獲取,等價于游戲?qū)ο蟮?`get()`。


相比 **Mouse Ray** 以屏幕中心為 (0.5,0.5),這和鼠標(biāo)輸入的坐標(biāo)系統(tǒng)原點(diǎn)重疊在左上角,

**Status** 節(jié)點(diǎn)獲取到的光標(biāo)坐標(biāo)數(shù)據(jù)可以使用直接。



鼠標(biāo)邏輯節(jié)點(diǎn)功能 Input -> Mouse

01. **Look** 指定的 Main 對象視角跟隨鼠標(biāo)移動面轉(zhuǎn)動,建議指定“頭部”對象。

02. **Set Position** 設(shè)置光標(biāo)位置,左上角到右下角 (0, 0) ~ (1,1),屏幕中心為 (0.5,0.5)。

03. **Cursor Visibility**? 設(shè)置光標(biāo)是否顯示。

04. **Status** 獲取光標(biāo)的屏幕坐標(biāo)、移動、滾輪數(shù)據(jù)。

05. **Button** 鼠標(biāo)點(diǎn)擊時觸發(fā),可以指定 L/M/R 按鈕,以及是否每幀都觸發(fā)。

06. **Moved** 鼠標(biāo)移動時觸發(fā)。

07. **Button Up** 鼠標(biāo)按鈕釋放時觸發(fā),可以指定 L/M/R 按鈕,以及是否每幀都觸發(fā)。

08. **Button Over**? 鼠標(biāo)在物理體上懸停時并點(diǎn)擊時觸發(fā),可以指定 L/M/R 按鈕,非物理體沒有效果。

09. **Wheel** 滾輪事件觸發(fā),Scroll Up/Down 或者 Up and Down 三種條件。

10. **Over** 鼠標(biāo)與物體體產(chǎn)生的 Enter/Over/Exit 事件輸出,還有相應(yīng)的碰撞點(diǎn)以及法線。


注意,**Status** 獲取的 Movement 是實(shí)時的鼠標(biāo)移動距離數(shù)據(jù),不移動就為 0 值,而且數(shù)值是標(biāo)準(zhǔn)化的大小 [1,1],表示整個屏幕空間,移動的像素距離換算成比例值。


**Look** 節(jié)點(diǎn)是使對象跟隨鼠標(biāo)移動的快速方法,可以指定“軀干”和“頭部”對象,當(dāng)鼠標(biāo)偏移屏幕中心時,就根據(jù)屏幕空間的 X/Y 偏移量分別調(diào)整 Main ObjectHead 的旋轉(zhuǎn)角度,如果沒有指定 Head 對象,則將兩軸偏移量都應(yīng)用到主體的旋轉(zhuǎn)。勾選 Smooth 可以使用旋轉(zhuǎn)動作的起止運(yùn)動更平緩。可以指定敏感度 Sensitivity,這是一個乘數(shù),設(shè)置為 0 則不會產(chǎn)生旋轉(zhuǎn)量。


旋轉(zhuǎn)角度可以控制在一個范圍,使用 Vec2 表示:


1. Cap Left/Right 約束 local Z 旋轉(zhuǎn)軸的角度范圍,對應(yīng)主體對象的偏轉(zhuǎn)角,注意要求:x > y;

2. Cap Up/Down 約束 local X/Y axis 旋轉(zhuǎn)軸的角度范圍,對應(yīng)頭對象的俯仰角;


當(dāng)前 UPBGE 0.3 版本源代碼應(yīng)該有邏輯錯誤,出現(xiàn) use_cap_z 屬性的重復(fù),另一個應(yīng)該是 use_cap_y。


鼠標(biāo)邏輯節(jié)點(diǎn)設(shè)計(jì)時、運(yùn)行時類型對照:

變換節(jié)點(diǎn)功能 Objects -> TransformationApply 類型施加物理參數(shù),配合 Game Physics 物理系統(tǒng)屬性使用,Bullet 物理引擎。節(jié)點(diǎn)激活 Local (藍(lán)色) 和 Global 分別表示相對局部、全局坐標(biāo)系統(tǒng):


01. **Align Axis to Vector** 將物體指定的 Axis 軸向與指定的向量方向?qū)R。

02. **Apply Force** 向物理剛體施加力的作用,輸入一個向量指定力度和方向。

03. **Apply Impulse** 施加沖量作用,兩個向量輸入分別是沖擊點(diǎn)、沖量向量。

04. **Apply Movement** 施加移動量,輸入向量指定偏移量。

05. **Apply Rotation** 施加旋轉(zhuǎn)量,輸入向量指定偏移量,使用角度為單位。

06. **Apply Torque** 施加扭矩,向量指定旋轉(zhuǎn)軸及力度,比如 [0,0,1] 以 Z 軸為旋轉(zhuǎn)中心,力度 1。

07. **Follow Path** 沿曲線路徑移動對象。

08. **Move To** 向量 Target 目標(biāo)點(diǎn)坐標(biāo)勻速移動,受重力影響,無法向 Z 軸上方移動。

09. **Move To with Navmesh** 在指定導(dǎo)航網(wǎng)格上導(dǎo)航到指定目標(biāo)點(diǎn)。

10. **Rotate To** 以 Rot Axis 為旋轉(zhuǎn)軸,F(xiàn)ront Axis 為正面旋轉(zhuǎn)到目標(biāo)角度。

11. **Translate** 以指定速度向目標(biāo)位置平移,


**Rotate To** 僅在世界空間的單個軸和固定角度上應(yīng)用旋轉(zhuǎn),可以瞬時或指定 Speed。如果旋轉(zhuǎn)軸與正面同軸,則無法旋轉(zhuǎn)(軸向鎖定)。


沖量模擬的受力分析復(fù)雜,很容易出現(xiàn)旋轉(zhuǎn)效果,特別是在沖擊點(diǎn)不在物體的中心軸,越容易使用物體產(chǎn)生旋轉(zhuǎn)。沖擊點(diǎn)與沖量方向搭配不正確也影響模擬結(jié)果,比如物體已經(jīng)在地面,使用 [0,0,-1] 這個沖量就可能不產(chǎn)生效果,因?yàn)闆_擊方向指向地面。沖擊點(diǎn)可以超出物體幾何空間,這相當(dāng)旋轉(zhuǎn)扭矩旋轉(zhuǎn) Torque。


沖量解算方法定義在 Bullet 引擎的移植代碼中:`CcdPhysicsController::ApplyImpulse()`

upbge-0.30\source\gameengine\Physics\Bullet\CcdPhysicsController.cpp

https://upbge.org/docs/latest/manual/manual/logic_nodes/scene/objects/transformation/index.html


**Follow Path** 是復(fù)雜的變換邏輯節(jié)點(diǎn),某些情況下似乎無法完全運(yùn)行,可以用來模擬四處走動的 NPC。路徑曲線使用 Nurbs Curve,在其中的點(diǎn)之間移動對象。僅與獲取曲線點(diǎn)配合使用,Get Curve Points。


**Move To with Navmesh** 比較好用一點(diǎn),只是使用起來需要構(gòu)建 Navigation Mesh,好在 UPBGE 在場景屬性面板提供了導(dǎo)航網(wǎng)格構(gòu)建工具,只需要依據(jù) Mesh 對象構(gòu)建出 Navigation Mesh。前期工作就是創(chuàng)建網(wǎng)格空間結(jié)構(gòu)。使用 Blender 的各種建模工具也很方便,以下提供一個參考思路:


1. 創(chuàng)建一個 `Curve -> Nurbs Path` 對象,按 Tab 進(jìn)行編輯模式,按路徑走向需要調(diào)整控制點(diǎn);

2. 切換回對象模式,找到 Path 對象數(shù)據(jù)屬性面板 `Geometry -> Bevel`;

3. 就使用 Round 倒角方式,將路徑倒角出一個管道形狀,Depth 指定深度,相當(dāng)于控制管道半徑;

4. 在對象模式下,找到菜單 `Object -> Convert -> Mesh` 將路徑對象轉(zhuǎn)換為網(wǎng)格體;

5. 切換到編輯模式,選擇所有頂點(diǎn),依次按 s z 0 將頂點(diǎn)沿 Z 軸縮放到 0 值,即壓平網(wǎng)格體;

6. 選保持選中所有頂點(diǎn),`Mesh -> Merge -> By Distance` 將頂點(diǎn)按就近距離合并以簡化;

7. 找到場景屬性面板 `Navigation Mesh -> Build Navigation Mesh` 按網(wǎng)格體生成導(dǎo)航路徑;

Nurbs Path - Convert to?Mesh 間接創(chuàng)建導(dǎo)航網(wǎng)格


導(dǎo)航網(wǎng)格只在尋路算法中表示 AI 角色可以觸達(dá)的區(qū)域,像陡坡或直立物體所覆蓋的區(qū)域都不算是游戲角色可觸達(dá)的區(qū)域,可以根據(jù)導(dǎo)航網(wǎng)格面板中指定的參數(shù)設(shè)置,使用方法參考 Bullet 引擎的文檔。當(dāng)前算法可能會在轉(zhuǎn)角位置產(chǎn)生卡住的不動的問題,可以適當(dāng)調(diào)整一個稍大的 Cell Size,避免與障礙物接觸。

Move To with Navmesh 導(dǎo)航邏輯節(jié)點(diǎn)演示


生成導(dǎo)航網(wǎng)格后,直接在**Move To with Navmesh**中的 Navmesh Object 屬性列表中選擇指定,然后 Destination 指定導(dǎo)航目標(biāo)位置,勾選 Visualize 可以運(yùn)行時看到一條紅線指示導(dǎo)航路徑。變換邏輯節(jié)點(diǎn)設(shè)計(jì)時、運(yùn)行時類型對照:


Animation 動畫節(jié)點(diǎn)分為三類,Timeline、Armature、Constraints,功能參考:

  1. **Animation Status** 獲取指定對象上的動畫播放狀態(tài),輸出兩個控制流和 Action Name/Frame 等。

  2. **Play Animation** 在指定對象上播放指定時間軸動畫數(shù)據(jù),可以指定幀區(qū)間 Start/End,速度等等。

  3. **Set Animation Frame** 將指定對象的動畫播放狀態(tài)移動到指定 Frame/Layer。

  4. **Stop Animation** 停止在指定對象、以及指定動畫層上播放動畫。

**Set Animation Frame** 使用?Freeze?模式,要在停止?fàn)顟B(tài)下才有效,會將動畫“冰凍”在指定幀位置,此時執(zhí)行播放命令無效,需要先停止動畫,解除冰凍狀態(tài)才可以繼續(xù)播放。

Timeline Animation

時間軸動畫,就是記錄在以幀為單位的屬性數(shù)據(jù)的重放到指定對象上。例如,最簡單的位移動畫,數(shù)據(jù)記錄的是 Position 屬性在不同關(guān)鍵幀的數(shù)值。關(guān)鍵幀之間應(yīng)該取什么什值,取決于插值算法生成的中間值。


Blender 編程模型中,基本單位是數(shù)據(jù)塊,Datablocks。所有對象都具有數(shù)據(jù),這些數(shù)據(jù)塊包括 meshes, objects, materials, textures, node trees, scenes, texts, brushes ... 所有數(shù)據(jù)塊都可以通過 Outliner -> blend-files 列表查看,刪除,管理。


????Datablocks https://docs.blender.org/manual/en/latest/files/data_blocks.html


而時間軸動畫記錄下來的數(shù)據(jù)就是 `Action` 數(shù)據(jù)塊,打開 Timeline 編輯器,可以通過 Keying(關(guān)鍵設(shè)置工具)或直接按快捷鍵 I 來添加關(guān)鍵幀,將當(dāng)前的狀態(tài)數(shù)據(jù)記錄下來。不同的屬性在時間軸上顯示為不同 Channels,選擇需要記錄的屬性,就會產(chǎn)生相應(yīng)的軌道記錄。


關(guān)鍵鍵有不同的類型:


1. **Keyframe**? (白/黃色菱形) 常規(guī)關(guān)鍵幀,如果之間有灰色塊連接表示記錄的狀態(tài)數(shù)據(jù)相等,沒變化。

2. **Breakdown** (青色小菱形) 間斷狀態(tài),如用于不同關(guān)鍵姿態(tài)間的過渡。

3. **Move Hold** (深灰色/橙色菱形) 慣性延續(xù),一個可以在一個保持姿勢附近添加少量動作的關(guān)鍵幀。在動畫攝影表中,它還會在它們之間顯示一個條塊。

4. **Extreme** (紅色大菱形) 極端狀態(tài),或者其他需要的用途。

5. **Jitter** (綠色小菱形) 抖動,填充或烘焙關(guān)鍵幀,用于在其他幀上插幀,或用于其他所需目的。

Keying (關(guān)鍵設(shè)置工具)

`Keying -> Active Keying Set` 列表中可以選擇當(dāng)前活動的通道,然后點(diǎn)擊帶 + 號的鑰匙圖標(biāo)就可以在相應(yīng)的屬性通道添加一個關(guān)鍵幀,不需要的關(guān)鍵幀也可以隨時刪除,可以直接在時間軸框選關(guān)鍵幀,移動它們到指定幀位置,或者直接刪除它們。每個時間軸動畫對應(yīng)的數(shù)據(jù)塊都有一個名字,比如 CubeAction 就表明這是 Cube 對象上的一個動畫數(shù)據(jù)塊。**Dope Sheet** 編輯器還可以切換為**Action Editor**以編輯時間軸動畫數(shù)據(jù),包括當(dāng)前幀的插值,通過修改左側(cè)顯示在綠色背景中的插值數(shù)據(jù),就只可以自動創(chuàng)建新的關(guān)鍵幀。

Dope Sheet 動畫編輯器


還可以使用 **Graph Editor** 曲線動畫工具改變插值規(guī)律,通過給屬性通道添加 F-Curves 函數(shù)曲線、修改器,用于設(shè)置關(guān)鍵幀間的插值函數(shù)。選擇好關(guān)鍵幀,通過以下菜單操作就可以改變插值方式:

- 菜單 Key -> Interpolation Mode 選擇插值函數(shù)類型;

- 側(cè)欄面板 F-Curve - Active Keyframe - Interpolation 設(shè)置插值類型和緩動類型 Easing Type。

- 側(cè)欄面板 Modifiers - Add Modifier 添加動畫曲線修改器。

F-Curve 曲線動畫工具

將動畫數(shù)據(jù)重現(xiàn)(播放)到指定對象的屬性,就可以還原關(guān)鍵幀記錄下的狀態(tài),并且 animation layering 動畫分層概念可以將多個動畫在同一個對象上播放,結(jié)合 Blend 選項(xiàng)在不同動畫層之間按權(quán)重計(jì)算重疊屬性的數(shù)據(jù),最終得到一個混合好的動畫效果。



Animation 動畫節(jié)點(diǎn)設(shè)計(jì)時、運(yùn)行時類型對照:


骨骼動畫涉及的內(nèi)容比較多,以后再深入探討,大概操作流程是:


1. 創(chuàng)建與模型相適應(yīng)的骨骼系統(tǒng),按關(guān)節(jié)位置連接骨骼;

2. 將模型與骨骼綁定:通過頂點(diǎn)組管理模型中的頂點(diǎn)與對應(yīng)骨骼的權(quán)重值,以確定每塊骨骼對指定頂點(diǎn)的影響程度;

3. 然后調(diào)整骨骼狀態(tài)以改變模型的形態(tài),因?yàn)橛猩弦徊降慕壎ú僮鳎P途W(wǎng)格會按權(quán)重分配給對應(yīng)的骷髏進(jìn)行變形;

4. 蒙皮,將材質(zhì)賦予模型,使用模型在姿態(tài)控制下呈現(xiàn)特定的動畫效果;


? 邏輯節(jié)點(diǎn)原理


邏輯節(jié)點(diǎn)的連接關(guān)系固定在由生成器輸出的邏輯樹配置代碼中,保存在項(xiàng)目的 bgelogic 目錄下,運(yùn)行游戲時或主動通過邏輯編輯側(cè)欄面板 `Administration -> Compile All` 生成代碼。比如,上面代碼中的 `ACT001.condition = CON0000` 就是將一個條件節(jié)點(diǎn)連接到一個動作節(jié)點(diǎn)的 condition 端口上。


注意,在 Apply As Logic Bricks 模式下編譯才會生成外部腳本模塊,如果是 Component 模式則會內(nèi)嵌在 Blender 文件,使用自帶的腳本編輯器查看。


Blender 提供的節(jié)點(diǎn)編輯器最基礎(chǔ)的兩個組件就是:


1. `bpy.types.NodeSocket` 節(jié)點(diǎn)插槽基類,所有節(jié)點(diǎn)的輸入、輸出端口都是插槽類型的實(shí)例;

2. `bpy.types.Node` 節(jié)點(diǎn)基類,邏輯樹中定義的節(jié)點(diǎn)之間,通過關(guān)聯(lián)插槽類型到輸入、輸出端口連接;


UPBGE 邏輯節(jié)點(diǎn)實(shí)現(xiàn)插件,bge_netlogic 插件代碼主要分成四塊:


- **uplogic** 邏輯節(jié)點(diǎn)運(yùn)行時的實(shí)現(xiàn),由邏輯節(jié)點(diǎn)生成器根據(jù)邏輯節(jié)點(diǎn)樹的節(jié)點(diǎn)連接信息生成的代碼調(diào)用。

- **basicnodes** 邏輯節(jié)點(diǎn)編輯器中節(jié)點(diǎn) UI 的實(shí)現(xiàn),最終子類屬于 bpy.types.Node 或 NodeSocket。

- **nodeutils** 節(jié)點(diǎn)編輯器中的節(jié)點(diǎn)分類目錄,使用了 `nodeitems_utils` 插件模塊。

- **ops** 包括代碼生成器,操作組件,bpy.types.Operator,對應(yīng)邏輯節(jié)點(diǎn)編輯器中的按鈕等 UI。


每個節(jié)點(diǎn)每個端口的設(shè)計(jì)時代碼,basicnodes 目錄下定義,主要是提供繪制出相應(yīng)的圖形界面的邏輯。并且向生成的運(yùn)行時節(jié)點(diǎn)類型實(shí)現(xiàn)提供連接關(guān)系信息數(shù)據(jù)。


而運(yùn)行時的實(shí)現(xiàn)代碼,uplogic 目錄下定義的各種 Cell 類型對應(yīng)邏輯節(jié)點(diǎn),SubCell 對應(yīng)插槽功能:


1. `ActionCell` 行為節(jié)點(diǎn)執(zhí)行各種動作,比如 `ActionApplyRotation` 旋轉(zhuǎn)指定游戲?qū)ο螅?/p>

2. `ConditionCell` 條件節(jié)點(diǎn)根據(jù)求值函數(shù)輸出邏輯條件,供行為節(jié)點(diǎn)的條件使用;

3. `ParameterCell` 參數(shù)節(jié)點(diǎn)主要是向其它節(jié)點(diǎn)提供數(shù)據(jù);

4. `LogicNetworkSubCell` 插槽類型,也是唯一的運(yùn)行時插槽類型實(shí)現(xiàn);


`LogicNetworkSubCell` 插槽類型記錄了上游節(jié)點(diǎn)(owner)和其數(shù)據(jù)讀取 API,`get_value()`方法一般由父類 `get_socket_value()` 方法間接調(diào)用。Get Owner 這樣的節(jié)點(diǎn)用來獲取游戲?qū)ο螅倪\(yùn)行時實(shí)現(xiàn) `ParamOwnerObject` 通過 `get_owner()` 獲取邏輯樹上的游戲?qū)ο筝敵鼋o下游。


下游節(jié)點(diǎn)連接到一個輸出端口,就可以根據(jù)端口 owner 屬性獲取引用上游節(jié)點(diǎn),注意這個 owner 表示 Socket 對象歸屬的節(jié)點(diǎn),并不是游戲?qū)ο?。通常一個邏輯節(jié)點(diǎn)中 Object 選項(xiàng)有一個 **Use Owner** 圖標(biāo),激活此選項(xiàng)就表示使用邏輯樹當(dāng)前掛載的游戲?qū)ο蟆?/p>

Use Owner 引用當(dāng)前邏輯樹所掛載的對象

Get Property 這樣的節(jié)點(diǎn),運(yùn)行時實(shí)現(xiàn)為 `ParameterObjectProperty`,它的輸入端口可以指定場景中的對象,也就是 GameObject,根據(jù)不同設(shè)置,在生成邏輯樹的代碼有不同的屬性值配置:


1. Object 屬性留空,生成代碼:`game_object = None`

2. Object 指定列表中的對象,比如場景中的 Plane 對象:`game_object = "NLO:Plane"`

3. Object 從其它節(jié)點(diǎn)輸入,比如 Get Owner:`game_object = nodes.ParamOwnerObject()`


如果是第二種,可以直接在屬性列表中看到這個指定對象的 Game Properties 數(shù)據(jù)屬性列表。但是節(jié)點(diǎn)需要激活 **Free Edit** 模式才能自由指定需要訪問的屬性數(shù)據(jù)。而在生成代碼中,Object 屬性值中前綴 `NLO:` 表示它真正需要獲取的是一個游戲?qū)ο?。`get_socket_value()` 方法包含前綴值的處理,會將參數(shù)值截掉 'NLO:' 的部分作為對象名稱匹配場景中的對象,Scene.objects 保存所有對象的引用。`get_value()` 則沒有這個前綴的處理。


還有一種情況,屬性值設(shè)置為 'NLO:U_O' 則返回 `LogicNetwork` 私有成員 `_owner` 引用的游戲?qū)ο?,它在生成的邏輯樹代碼定義在初始化方法中,和傳入節(jié)點(diǎn)的控制器引用相同的游戲?qū)ο蟆?/p>


在邏輯節(jié)點(diǎn)執(zhí)行求值時,還會調(diào)用 `is_invalid()` 方法驗(yàn)證屬性的名稱的有效性,以及判斷節(jié)點(diǎn)是否還處于等待狀態(tài),或者游戲?qū)ο蟊旧硪呀?jīng)定義了 invalid 屬性并且值不為 False,這都是處于無效狀態(tài),會導(dǎo)致節(jié)點(diǎn)路過求值操作,或者說求值未完成:

編寫 GameObject 對象時注意 `property_name not in game_object` 這個屬性存在性判斷條件。而這個 in 運(yùn)算符的使用,就涉及多個魔術(shù)方法的定義與使用,才能決定一個屬性數(shù)據(jù)是否存在。即就是說,即使用 GmaeObject `getPropertyNames()` 方法可以看到屬性的定義,但是通過 not-in 運(yùn)算符返回的值可能又是表明屬性不存在。


Python 腳本中,`obj.attr` 和 `obj['attr']` 兩種訪問方法涉及了不同執(zhí)行邏輯,它們大多數(shù)情況下都不是指向相同的數(shù)據(jù)。同時,UPBGE 引擎內(nèi)還會整合 Game Properties 數(shù)據(jù),以及邏輯節(jié)點(diǎn)樹生成代碼中設(shè)置 GameObject 屬性數(shù)據(jù),比如一個名為 **ArchitectureBasic** 的邏輯樹就會在生成代碼中包含以下游戲?qū)ο髮傩栽O(shè)置:

? ? owner["IGNLTree_ArchitectureBasic"] = network


這些在不同執(zhí)行階段混入的數(shù)據(jù),如果不清楚什么數(shù)據(jù)在什么時間可用,無疑會讓程序邏輯變得非常復(fù)雜,并且可能導(dǎo)致一些怪異的現(xiàn)象。這些在不同執(zhí)行階段混入的數(shù)據(jù),如果不清楚什么數(shù)據(jù)在什么時間可用,無疑會讓程序邏輯變得非常復(fù)雜,并且可能導(dǎo)致一些怪異的現(xiàn)象。比如,在邏輯節(jié)點(diǎn)求值方法中,無法通過 `get()` 方法獲取游戲?qū)ο髮傩砸酝獾膶傩灾担茨切傩?Python 原生導(dǎo)出的符號。

`getPropertyNames()` 函數(shù)導(dǎo)出自 C++ 方法 `PyGetPropertyNames()`,它只是從導(dǎo)出符號中獲取列表,在腳本中可能訪問不到真實(shí)的值。列表中存在的屬性,也就是說,`KX_GameObject::sPyget()` 源代碼中導(dǎo)出的這個方法是 Game Properties 屬性數(shù)據(jù)獲取的專用方法。根據(jù)其導(dǎo)出符號用途的宏定義規(guī)則,可以知道,這是一個由`EXP_PYMETHOD_VARARGS` 宏函數(shù)生成的方法,頭文件中可找到相應(yīng)定義。


Python 2.x 升級到 Python 3.x,所有類型的底層類型系統(tǒng)完全重新設(shè)計(jì),舊版本的類型稱為舊式類型,新版本的類型,只要是繼承自 `object` 或者其子類型,那么就是新式類型,默認(rèn)都是新式類。舊式類和新式類的區(qū)別,old-style vs. new-style,主要體現(xiàn)在多重繼承、屬性訪問以及特殊方法等方面。

在新式類中,一個類可以直接繼承自內(nèi)置類型(比如 list、dict 等),同時也支持使用 super() 函數(shù)調(diào)用指定父類方法,例如 `super(A, obj).m` 或者 `super().__init__()`。還能夠使用 slots 插槽屬性限制實(shí)例屬性的數(shù)量,以及使用 `getattribute()` 方法控制屬性訪問等高級特性。還涉及類型成員解釋順序算法 Method Resolution Order (MRO) 等等。?

Python-3.10.2\Doc\library\functions.rst



獲取屬性數(shù)據(jù)時,不能在沒有實(shí)現(xiàn)下標(biāo)操作方法使用 `obj['attr']` 這樣的索引法取值,可以實(shí)現(xiàn)`__getitem__` 魔術(shù)方法,或者使用 `getattr()` 內(nèi)置函數(shù)。for-in 循環(huán)會優(yōu)先使用 `__iter__` 魔術(shù)方法枚舉數(shù)據(jù),如果沒有定義,才使用 `__getitem__`。


數(shù)據(jù)枚舉完成,就應(yīng)該發(fā)出停止檢舉信息,`StopIteration` 或者 `IndexError` 異常都可以停止循環(huán)。如果沒有終止信息發(fā)出,這就是一個死循環(huán)。


另外,if-in 或 not-in 這種帶 in 運(yùn)算符的搭配會觸發(fā) `__getitem__` 枚舉行為,但是 in 運(yùn)算符優(yōu)先使用 `__contains__` 方法,一個布爾值就能解決所查詢的數(shù)據(jù)是否存在的問題。但是 `__getitem__` 方法則需要返回一個值,供 in 運(yùn)算符進(jìn)行比較,getitem 不能返回 True 或 False 決定數(shù)據(jù)是否存在。


Python 作為動態(tài)類型語言,它的類型標(biāo)注只是給人看的,不對于編譯器的編譯處理,所以以下代碼展示了一個代碼一致性的反面示范,因?yàn)?`__getitem__` 會被多個方法調(diào)用,id 不一定是數(shù)值。和 `__getitem__` 方法配對的還有 `__setitem__`,用于下標(biāo)索引方式的賦值操作。

所有設(shè)計(jì)時實(shí)現(xiàn)實(shí)現(xiàn),名稱上基本都使用 LN 前綴,而運(yùn)行時實(shí)現(xiàn)則沒有這樣的前綴,這是代碼約定,這是非常好的編碼習(xí)慣,一方面邏輯更清晰,另外更方便定位。


`Python -> Dictionary -> Init Empty` 節(jié)點(diǎn)為例,它創(chuàng)建一個空字典以保存數(shù)據(jù)。在邏輯節(jié)點(diǎn)編輯器中添加此節(jié)點(diǎn),就會執(zhí)行 `init()` 初始化,添加輸入、輸出端口,以及相關(guān)的端口類型。同時,節(jié)點(diǎn)實(shí)現(xiàn)類型中還定義了三個方法,分別返回輸入、輸出端口對應(yīng)的字段或變量名稱,以及實(shí)現(xiàn)運(yùn)行時的類型 `InitEmptyDict(ActionCell)`,所有節(jié)點(diǎn)運(yùn)行時實(shí)現(xiàn)都是 `LogicNetworkCell` 子類。


節(jié)點(diǎn)樹進(jìn)行運(yùn)行時,節(jié)點(diǎn)得到控件流執(zhí)行,就會調(diào)用求值方法 `evaluate()`,并將數(shù)據(jù)暫存起來,等待相應(yīng)的輸出端口連接的下游節(jié)點(diǎn)調(diào)用已經(jīng)為各個端口注冊好的 API 獲取暫存數(shù)據(jù)。這里就是指注冊在輸出端口 **Dictionary** 的接口函數(shù),此端口對應(yīng)的變量名是 `DICT`,此值對應(yīng)節(jié)點(diǎn)類型的一個同名成員,也就是在 self.DICT 這個成員上注冊的 API `get_dict()`,下游節(jié)點(diǎn)需要獲取數(shù)據(jù)時就會根據(jù)以上信息調(diào)用 `get_dict()`。


Init EmptyInit From Item 節(jié)點(diǎn)的主要差別在于 `InitEmptyDict` 和 `InitNewDict`

兩個實(shí)現(xiàn)類型的功能差別。不同節(jié)點(diǎn)的這個求值方法有所不同,前者直接創(chuàng)建空字典,`dict = {}`,后者則是根據(jù)輸入的鍵值對數(shù)據(jù)來初始化一個字典,`dict = {str(key): value}`。求值完成后,父類定義的內(nèi)部函數(shù) `set_ready()` 被執(zhí)行,告知節(jié)點(diǎn)樹當(dāng)前節(jié)點(diǎn)已經(jīng)完成求值。


由于 UPBGE 邏輯節(jié)點(diǎn)編程設(shè)計(jì)思路與 Armory3D 完全不一樣,在使用節(jié)點(diǎn)時的思維也幾乎完全不一樣。定時器 Time - Timer `ConditionTimeElapsed` 就是這樣一個典型:When Elapsed 持續(xù)輸出觸發(fā)信號,當(dāng)輸入 Set Timer 條件時,定時器開始計(jì)時,停止輸出觸發(fā)信號。計(jì)時到達(dá)后,恢復(fù)信號。可以使用 `ConditionNot` 返回這個邏輯,即沒有輸入 Set Timer 時也不輸出觸發(fā),有信號輸入時就在計(jì)時這段時間持續(xù)輸出觸發(fā)信號。


UPBGE Logic Nodes (源代碼分析)的評論 (共 條)

分享到微博請遵守國家法律
五家渠市| 桦川县| 开原市| 峨边| 呈贡县| 乌拉特中旗| 基隆市| 武定县| 增城市| 洛宁县| 呼伦贝尔市| 如东县| 莎车县| 苍梧县| 鄂尔多斯市| 伊川县| 天等县| 衢州市| 四川省| 许昌市| 南康市| 高雄县| 翁源县| 虞城县| 光山县| 湖口县| 长泰县| 太湖县| 象州县| 大姚县| 滦平县| 新龙县| 格尔木市| 固原市| 鄯善县| 广宁县| 新蔡县| 沂南县| 开化县| 屏边| 巢湖市|