1. 程式人生 > >手把手教你使用Behavior Designer

手把手教你使用Behavior Designer

首先科普下:

什麼是行為樹
如果瞭解過狀態機,會知道在行為樹之前,在實現AI用得比較多的技術是狀態機,狀態機理解起來是比較簡單的,即一個狀態過渡到另一個狀態,通過判斷將角色的狀態改變即可,如果學習過Unity的Mecanim動畫系統,會更加直觀的理解。


但是狀態機在狀態較多的情況下會使狀態之間的切換變得異常繁瑣,同時狀態之間很難複用。


在這種情況下,行為樹被髮明出來,行為樹的優點如下:

行為樹提供大量的流程控制方法,使得狀態之間的改變更加直觀;
整個遊戲AI使用樹型結構,方便檢視與編輯;
方便除錯和程式碼編寫;
最重要的:行為樹方便製作編輯器,可以交由策劃人員使用;

ai到底是啥子呢,玩過遊戲的應該都知道,那些小兵跟boss之所以會智慧得追著你打跟放招,其實那些就是ai啦,說高達上一點就是“人工智慧”

看看下面這個boss

你想知道boss是的ai怎麼做的嗎,來,跟著我一起學Behavior Designer

---------------------------------------------------------------------------------------------------------------------------------------------

Behavior Designer就是Unity的一個製作行為樹的外掛啦

Behavior Designer的使用教程網上比較少,今天我就來寫下教程好了,畢竟是個好東西,跟大家分享下。

匯入Behavior Designer後,可以在Tools/Behavior Designer/Editor開啟編輯器,如下圖所示

編輯器檢視如下

1部分:

行為樹的組織區域

2部分:

一堆Task,主要先掌握Composities(符合節點)、Decorators(裝飾節點)和Actions(行為節點),如何組織是難點

3部分:

Behavior標籤,可以設定行為樹的一些屬性

注意這個Restart When Complete,如果勾選了,則行為樹遍歷完後會再重新啟動一次,不斷迴圈

這些屬性也可以通過程式碼進行設定,一般我們編輯好行為樹後會導成asset檔案作為ExternalBehavior,這樣可以複用這個行為樹。

假設我們已經匯出在Resources目錄下,叫Behavio.asset,然後現在有一個cube,我們想給這個cube綁上行為樹,我們可以這樣做

var bt = cube.AddComponent<BehaviorTree> ();
var extBt = Resources.Load<ExternalBehaviorTree> ("Behavior");
bt.StartWhenEnabled = false;
bt.ExternalBehavior = extBt;

注意:以上用到了Resources.Load方法,所以行為樹資源務必放在工程目錄Resources中,如果沒有Resources目錄,則在Assets目錄中新建一個Resources目錄。

其中之所以把StartWhenEnabled置為false是因為我們不想讓行為樹立刻啟動,我們可能有一些初始化操作需要進行

如果無需做額外的初始化,也可直接設定bt.StartWhenEnabled=true,否則行為樹啟動後執行一次就不在迴圈運行了。

我們昨晚初始化操作後,可以通過bt.EnableBehavior ();來啟動行為樹。

Variables標籤,可以給行為樹新增變數,這些變數可以通過GetVariable跟SetVariableValue來讀取和賦值,並可以在各種Task節點裡進行傳遞和賦值,這個後面講下方法

Inspector標籤就是Task的檢視視窗啦,我們可以在這個標籤裡設定Task的屬性什麼的

4部分:

這裡是當前的行為樹物體和物件,可以點選進行切換不同的行為樹物體和行為樹物件,一個物體可以繫結多個行為樹,不過一般只綁一個就可以了,左邊的左右箭頭可以切換行為樹物體,右邊的+-好可以怎加和刪除行為樹。

5部分:

Export,就是把行為樹匯出成外部檔案,可以選擇二進位制檔案或者Json檔案,建議使用二進位制檔案,匯出的格式可以在6部分那裡進行設定

二進位制檔案的話匯出是一個xxxx.asset檔案,它是作為ExternalBehaviorTree儲存的,所以使用的時候要注意下。

如果是儲存成Json檔案,那就是一個xxxx.prefab檔案,可以理解為就是個EmptyObject上掛著個BehaviorTree

6部分:

主要就是那個匯出檔案的格式設定啦,如下圖:

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------

一條華麗麗的分割線

簡單說完了介面,我們現在開始講下具體使用吧。

第一步,給一個物體新增行為樹

方法1:開啟行為樹編輯器,然後選中要新增行為樹的物體,然後在編輯器編輯區右鍵Add Behavior Tree

方法2:直接給物體AddComponent一個BehaviorTree元件,然後點選Open開啟即可進行編輯

添加了行為樹,現在以一個簡單的例子講下樹的製作。

假設我們要做一個吃飯、睡覺、地震啦逃跑的ai,可以想象一下,我們需要有事件傳送和接受,比如我們告訴小a說你去吃飯吧,那麼他收到事件後就去執行吃飯,我們傳送時間告訴小a說去睡覺吧,他就去睡覺了,我們要需要中斷,比如正在睡覺,突然地震啦,趕緊逃命。

1 首先點選Unity頂部選單欄Tools->Behavior Designer->Editor,開啟行為樹編輯器

2 在Hierarchy視窗中滑鼠右鍵->Create Empty,建立一個GameObject

3 如果沒有選中任何物體,行為樹編輯器(上面說到的1區域)中會顯示“Select a GameObject”,我們要給上面的GameObject新增行為樹,所以選中GameObject,此時會顯示"Right Click, Add a Behavior Tree Component",我們在行為樹視窗中右鍵->Add Behavior Tree,此時會看到GameObject被添加了BehaviorTree元件,編輯器中顯示“Add a Task”,接下來我們就可以開始新增節點了

4 我們先新增一個Selector節點,找到上文說的2區域,在Composites標籤列表中找到Selector,點選,即可看到在組織區域中多了一個Entry節點和一個Selector節點,並且它們之間用連線連了起來。Selector節點就像or邏輯一樣,它從做到右知行,遇到success則立刻返回success,如果遇到failure,則繼續知行右邊的節點。

5 接下來新增Sequence節點,同理,它在Composites標籤中,點選Sequence,會在組織區域中建立一個Sequence節點,注意它可能被之前的節點擋住,可以稍微一動一下節點的位置。Sequence節點就像and邏輯,從做到右知行,遇到failure則立刻返回failure,如果遇到success則繼續執行下一個節點,直到從左到右所有子節點都返回success,它才返回success。

6 建立事件接收節點:Has Received Event,它在Conditionals標籤中,如果很難找,可以在2區域中上面的搜尋框裡直接輸入名字查詢,有個小小的放大鏡圖示那裡。Has Received Event用來做事件接收檢測,事件已字串作為標識,比如事件"Eat"這樣子,事件自己來定義。點中剛剛建立的Has Received Event節點,然後在3區域中點Inspector標籤,可以看到節點的屬性,我們會看到Event Name這個屬性,這就是事件標識,在這裡設定好Event Name,我這裡做了三個事件:"Eat","Sleep","EarthQuake"。

7 新增Log節點,它被執行的時候會輸出log,打log的內容可以在它的節點屬性Text設定

8 新建立兩個自定義的節點Eat和Sleep,一般我們在做特殊行為的時候,都需要自己拓展出一些新自定義的節點,來實現對應的需求。Eat和Sleep的程式碼看下文,它要繼承Action。

9 新建行為樹變數,在3區域中點選Variables標籤,會看到Name,Type和一個Add按鈕,在Name中輸入變數的名字,在Type中選擇變數的型別,然後點選Add按鈕即可新增變數。我們新增一個String型別的food變數,和一個Int型別的sleepTime變數。

最終如下:

行為樹的變數如下:

行為Eat和Sleep是另外寫的Action,程式碼如下:

Eat.cs

using UnityEngine;
using System.Collections;
using BehaviorDesigner.Runtime;
using BehaviorDesigner.Runtime.Tasks;

public class Eat : Action
{
	public SharedString food;

	public override TaskStatus OnUpdate ()
	{
		Debug.Log ("eat: " + food.GetValue());
		return TaskStatus.Success;
	}
}


Sleep.cs

using UnityEngine;
using System.Collections;
using BehaviorDesigner.Runtime;
using BehaviorDesigner.Runtime.Tasks;

public class Sleep : Action
{
	public SharedFloat sleepTime;
	private float m_sleepTime;
	private float m_startTime;

	public override void OnStart()
	{
		m_startTime = Time.time;
		m_sleepTime = (float)sleepTime.GetValue ();
	}


	public override TaskStatus OnUpdate()
	{
		if (m_startTime + m_sleepTime < Time.time) 
		{
			return TaskStatus.Success;
		}
		Debug.Log ("I am Sleeping");
		return TaskStatus.Running;
	}
}


其中Eat的屬性如下

同理Sleep的屬性如下:

需要注意一個點,就是Task中斷,中斷有3種類型:Self,Lower Priority,Both

怎麼理解中斷呢,行為樹是從上到下從左到右知行的,假設知行到右邊第二個節點,這個節點假設是一個持續執行的節點,比如“睡覺”,而此時突然收到一個“”地震”的訊息,就必須立刻中斷“睡覺”。

Self就是中斷自己的意思,

Lower Priority是中斷比自己低許可權的節點,在行為樹種,右邊的節點比左邊的節點許可權低。

Both就是中斷自己和比自己低許可權的節點。

只有複合節點(Composites標籤中的那些節點,比如上面的Selector和Sequence)有中斷屬性。我們選中Sequence,在它的Inspector中可以看到Abort Type屬性,我們設定成Loawr Priority。

我們地震的優先順序最高,所以放在最左邊,優先順序最低的是睡覺,地震要中斷吃飯跟睡覺,吃飯要中斷睡覺

我們把上面的行為樹導成二進位制檔案,放到Resources目錄下。(注:如何把行為樹導成二進位制檔案?上文的5區域有個Export按鈕,點它就可以匯出了,這個二進位制檔案怎麼用起來呢?可以通過程式碼動態繫結到物體上,也可以直接在物體上新增Behavior Tree元件然後把二進位制檔案拖到External Behavior屬性中)

接下來,現在我們開始寫控制程式碼:

我們建立一個Cube,然後新建一個Runner腳步掛在Cube上,程式碼如下:

Runner.cs

using UnityEngine;
using System.Collections;
using BehaviorDesigner.Runtime;


public class Runner : MonoBehaviour {

	private BehaviorTree m_bt;

	void Start () 
	{

		var bt = gameObject.AddComponent<BehaviorTree> ();
		var extBt = Resources.Load<ExternalBehaviorTree> ("Behavior");
		bt.StartWhenEnabled = false;
		bt.ExternalBehavior = extBt;
		bt.EnableBehavior ();
		m_bt = bt;
	}
	

	void Update () {
		if (Input.GetKeyDown (KeyCode.A)) 
		{
			//吃紅燒牛肉麵//
			m_bt.SetVariableValue ("food", "紅燒牛肉麵");
			m_bt.SendEvent ("Eat");
		}
		if (Input.GetKeyDown (KeyCode.B)) 
		{
			//誰10000秒//
			m_bt.SetVariableValue ("sleepTime", 10000f);
			m_bt.SendEvent ("Sleep");
		}
		if (Input.GetKeyDown (KeyCode.H))
		{
			//地震啦//
			m_bt.SendEvent ("EarthQuake");
		}
	}
}

測試效果如下:

這只是一個簡單的例子,還有很多複雜的ai,等後面有事件研究了再進行補充。

好啦,今天就先寫到這裡吧