日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Unity-Behavior Designer详解

發(fā)布時間:2023/12/20 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Unity-Behavior Designer详解 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

Unity-Behavior Designer詳解


理論

基本概念

行為樹是一個包含邏輯節(jié)點和行為節(jié)點的樹結(jié)構(gòu),每次需要找出一個行為的時候,會從樹的根節(jié)點出發(fā),遍歷各個節(jié)點,找出第一個和當前數(shù)據(jù)相符合的行為。

如下圖,就是一個簡單的行為樹

當我們要決策一個AI要做什么樣的行為的時候, 我們就會自頂向下的,通過一些條件來搜索這顆樹,最終確定需要做的行為(葉節(jié)點),并且執(zhí)行它,這就是行為樹的基本原理。

Task&Status

有四種不同類型的 task(任務(wù)): 包括 action(行為),composite(復(fù)合),

conditional(條件),decorator(修飾符)

復(fù)合(Composites)

主要有三種:

  • Sequence
    • 順序執(zhí)行,會按照從左向右的順序依次執(zhí)行子節(jié)點
  • Selector
    • 選擇執(zhí)行,根據(jù)條件判斷,選擇一個子節(jié)點執(zhí)行
  • Parallel
    • 平行執(zhí)行,同時執(zhí)行所有子節(jié)點

修飾(Decorator)

這個類型的節(jié)點只能有一個子節(jié)點。它的功能是修改子任務(wù)的行為。在上面的例子中,我們沒有使用 decorator(修飾符),如果你需要類似于打斷操作的話會用得到這個 decorator(修飾符)類型!舉個例子:一個收集資源的操作,它可能有一個中斷節(jié)點,這個節(jié)點判斷是否被攻擊,如果被攻擊則中斷收集資源操作!decorator(修飾符)的另一個應(yīng)用場合是重復(fù)執(zhí)行子任務(wù) X 次,或

者執(zhí)行子任務(wù)直到完成

條件(Conditinals)

用來判斷某些游戲?qū)傩允欠窈线m!

行為(Action)

行為很容易理解,即為具體的動作,他們在某種程度上改變游戲的狀態(tài)和結(jié)果

返回狀態(tài)(status)

有時候一個 task(任務(wù))需要多幀才能完成。例如,大多數(shù)動畫不會在一幀開始并結(jié)束。此外有 conditional(條件)的任務(wù)需要一種方法來告訴他們的父任務(wù)條件是否正確,以便讓父節(jié)點確定子節(jié)點的執(zhí)行順序。這兩個問題都可以使用 status(狀態(tài))來解決。一個任務(wù)有三種不同狀態(tài):運行,成功或者失敗

行為樹插件

插件下載鏈接:https://download.csdn.net/download/qq_52324195/85400525

基本介紹

行為樹整體的編輯界面

  • Behavior Name,行為樹名稱
  • Behavior Description,行為樹簡介
  • Extenral Description,外部行為樹
    • 關(guān)于這一屬性,可以通過右上角的Export將行為樹導(dǎo)出,創(chuàng)建一個行為樹“Prefab”,他就成為了外部行為樹,然后將這個外部行為樹拖入該屬性,就會將外部行為樹應(yīng)用到當前行為樹
  • Group
    • 行為樹的分組編號,用來將行為樹分組!可以用來方便的查找到特定的行為樹
  • Start When Enabled
    • 如果設(shè)置為 true,那么當這個行為樹組件 enabled 的時候,這個行為樹就會被執(zhí)行
  • Asynchronous Load,異步加載
  • Pause When Disabled
    • 如果設(shè)置為 true,那么當這個行為樹組件 disabled 的時候,這個行為樹就會被暫停
  • Restart When Complete
    • 如果設(shè)置為 true,那么當這個行為樹組件執(zhí)行結(jié)束的時候,這個行為樹就會被重新執(zhí)行
  • Reset Values On Restart
    • 如果設(shè)置為 true,那么當這個行為樹組件 reset 的時候,這個行為樹就會被重新執(zhí)行
  • Log Task Changes
    • 當設(shè)置為 true 是,這個行為樹下只要 task 流程發(fā)生變化就會打印一條 log 日志到控制臺中

Variable

全局變量,如圖,你可以自己創(chuàng)建規(guī)定類型的變量

Inspector

點擊任意Task,可以在Inspector界面查看該節(jié)點的屬性

類似于Unity的編輯界面,出了前三個基本屬性外,其他的都是該Task的獨有屬性,我們可以通過每個屬性右邊的點按鈕,來使用我們的全局變量

Task

在整個任務(wù)樹的最高層的節(jié)點我們稱之為 Task(任務(wù))。這些 task 任務(wù)擁有類似于 MonoBehavior 那樣的接口用于實現(xiàn)和擴展。

基本屬性

Task 任務(wù)有三個基礎(chǔ)的公共屬性:name, comment, instant

如下圖,你會發(fā)現(xiàn)任意Task都有這三種屬性

前兩個很簡單,名稱與簡介。

不過instant可能并不好理解,解釋如下:

行為樹中,當一個 task 任務(wù)返回成功或者失敗后,行為樹會在同一幀中立刻移動到下一個 task 任務(wù)。如果,你沒有選擇 instant 選項,那么在當前 task 任務(wù)執(zhí)行完畢后,都會停留在當前節(jié)點中,直到收到了下一個 tick,才會移動到下一個 task 任務(wù)!

疑問:什么是tick?在解釋這個之前,我們來講解另外一個知識:BehaviorManager

其實,你會發(fā)現(xiàn),當運行一個行為樹的時候,會在場景中自動創(chuàng)建一個名稱為 BehaviorManager的 GameObject,并添 BehaviorManage.cs

最初你看到的是下圖這樣的:

第一行:Update Interval,這很好理解,行為樹的更新間隔,他有三種選擇

  • Every Frame 每幀更新
  • Specify Seconds 自定義時間更新

  • Manual 手動更新

如果你選擇了,這個選項。你需要通過腳本來手動調(diào)用以下函數(shù)來執(zhí)行所有行為樹的更新

BehaviorManager.instance.Tick()

它還有另外一種重載:BehaviorManager.instance.Tick(BehaviorTree);,很顯然,它傳入了一個行為樹,那么僅僅就是只對該行為樹進行更新,而不是全部。

第二行:Task Execution Type 它可以指定這次更新中行為樹的執(zhí)行次數(shù)

默認是No Dullicates,也就是無復(fù)制無重復(fù)的意思,也就是1次,每次你更新行為樹,行為樹會執(zhí)行一次。

如果你指定了5次,那么每次更新就會執(zhí)行5次

讓我們回到之前的話題,到這里我想你已經(jīng)明白了什么事Tick,簡單來說也就是行為樹更新指令

讓我們來看看執(zhí)行順序流程圖:

API

// 當行為樹被啟用時,OnAwake被調(diào)用一次。可以把它看作一個構(gòu)造函數(shù) public virtual void OnAwake(); // OnStart在執(zhí)行之前立即被調(diào)用。它用于設(shè)置需要在上次運行后重新設(shè)置的任何變量 public virtual void OnStart(); // OnUpdate運行實際的任務(wù) public virtual TaskStatus OnUpdate(); // 在執(zhí)行成功或失敗后調(diào)用OnEnd。 public virtual void OnEnd(); // 當行為暫停并恢復(fù)時,調(diào)用OnPause public virtual void OnPause(bool paused); // 優(yōu)先級選擇需要知道該任務(wù)的運行優(yōu)先級 public virtual float GetPriority(); // OnBehaviorComplete在行為樹完成執(zhí)行后被調(diào)用 public virtual void OnBehaviorComplete(); // 檢查器調(diào)用OnReset來重置公共屬性 public propertiespublic virtual void OnReset(); // 允許從任務(wù)中調(diào)用OnDrawGizmos public virtual void OnDrawGizmos(); // 保留對擁有此任務(wù)的行為的引用 public Behavior Owner;

父任務(wù) Parent Tasks

behavior tree 行為樹中的父任務(wù) task 包括:composite(復(fù)合),decorator(修飾符)!

以下是他的可擴展API,雖然 Monobehaviour 沒有類似的 API,但是并不難去理解這些功能

//一個父任務(wù)可以擁有的子任務(wù)的最大數(shù)量。通常為1或int。MaxValue public virtual int MaxChildren(); //布爾值,以確定當前任務(wù)是否為并行任務(wù) public virtual bool CanRunParallelChildren(); //當前活動子節(jié)點的索引 public virtual int CurrentChildIndex(); //布爾值,以確定當前任務(wù)是否可以執(zhí)行 public virtual bool CanExecute(); //為執(zhí)行狀態(tài)應(yīng)用裝飾器,輸入?yún)?shù)為被修飾節(jié)點的狀態(tài) public virtual TaskStatus Decorate(TaskStatus status); //通知parenttask子任務(wù)已被執(zhí)行,其狀態(tài)為childStatus public virtual void OnChildExecuted(TaskStatus childStatus); //通知父任務(wù),其子任務(wù)childIndex已被執(zhí)行,其狀態(tài)為childStatus public virtual void OnChildExecuted(int childIndex, TaskStatus childStatus); //通知任務(wù)子進程已經(jīng)開始運行 public virtual void OnChildStarted(); //通知并行任務(wù),索引為childIndex的子任務(wù)已開始運行 public virtual void OnChildStarted(int childIndex); //一些父任務(wù)需要能夠覆蓋狀態(tài),例如并行任務(wù) public virtual TaskStatus OverrideStatus(TaskStatus status); //如果中斷節(jié)點被中斷,它將覆蓋狀態(tài)。 public virtual TaskStatus OverrideStatus(); //通知復(fù)合任務(wù),條件中止已被觸發(fā),子索引應(yīng)重置 public virtual void OnConditionalAbort(int childIndex);

條件節(jié)點的終止

一共有四種中斷類型的 abort types: None, Self, Lower Priority, and Both.

如上圖,所有的條件節(jié)點都有一個屬性:Abort Type,也就是中止類型

  • None
    • 無中止
  • Self
    • 這是一種自包含中斷類型。也就是會檢測此節(jié)點下所有條件判斷節(jié)點,即便是被執(zhí)行過的節(jié)點,如果判斷條件不滿足則打斷當前執(zhí)行順序從新回到判斷節(jié)點判斷,并返回判斷結(jié)果!
  • Lower Priority
    • 當運行到后續(xù)節(jié)點時,本節(jié)點的判斷條件生效了的話則打斷當前執(zhí)行順序,返回本節(jié)點執(zhí)行!
  • Both
    • 同時包含Self與LowerPriority

如果你是剛剛接觸行為樹,我相信,到這里你一定還不明白,接下來,我們結(jié)合例子進行講解

行為樹應(yīng)用簡單例子

如圖:

這里我們首先說明Selector選擇節(jié)點,這是系統(tǒng)默認的選擇節(jié)點,你可以理解為if else,從左向右依次選擇,如果能執(zhí)行就選擇該子樹執(zhí)行,如果不能執(zhí)行則選擇下一個子樹。注意,我們的用詞,子樹,也就是說,如果要執(zhí)行下去,那么左邊這個子樹的條件節(jié)點必須返回Success,否則,那么就會執(zhí)行下一個子樹。

當前的條件節(jié)點是兩個Int變量的比較,其中一個我們使用了全局變量 A=100,另外一個則賦值初始值10。

其次,左邊的Sequence我們賦予了Lower Priority中止

現(xiàn)在,我們執(zhí)行它看看結(jié)果,最初是這樣的:

方框呈現(xiàn)綠色,說明該節(jié)點正在運行,X則表示返回Failure或者不能執(zhí)行,?表示該狀態(tài)本身返回Success

當我們修改第二個Int值為1000,使條件節(jié)點返回Success,行為樹就變成下面這樣:

這里有一點需要說明:中止只會發(fā)生在節(jié)點運行過程中,如果節(jié)點已經(jīng)運行完畢,也就是出現(xiàn)了綠色的勾,此時說明執(zhí)行完畢,行為樹已經(jīng)結(jié)束,中止就無效了。

如果你之前使用的都是狀態(tài)機,我想你需要轉(zhuǎn)變過來,行為樹并非一種狀態(tài),它僅僅包含了一系列行為的邏輯模式,一旦它執(zhí)行完畢,不會向狀態(tài)機一樣執(zhí)行完就回到Idle,行為樹就結(jié)束了。如果你想要再次執(zhí)行它,就需要再次通過腳本來啟用他。

當我們的條件節(jié)點通過了,此時中止就生效了,他會中止第二個Sequence,然后執(zhí)行本節(jié)點

此外,你可以通過點擊節(jié)點左上角的X,來禁用該子樹

自定義Task

自定義行為節(jié)點

這是代碼

首先,需要引入命名空間:

using BehaviorDesigner.Runtime; using BehaviorDesigner.Runtime.Tasks;

對于行為節(jié)點,我們僅僅需要讓該類繼承Action,然后重寫OnUpdate函數(shù)即可,你想要實現(xiàn)的功能就寫在這個函數(shù)中,我這里實現(xiàn)的就是讓對象朝向目標移動

using System.Collections; using System.Collections.Generic; using UnityEngine; using BehaviorDesigner.Runtime; using BehaviorDesigner.Runtime.Tasks;public class MyMoveTo : Action {public GameObject enemy;public override TaskStatus OnUpdate(){if (GetComponent<Player>()){if (Vector3.Distance(transform.position, enemy.transform.position) < 0.1f){return TaskStatus.Success;}else{transform.position = Vector3.MoveTowards(transform.position,enemy.transform.position,Time.deltaTime);return TaskStatus.Running;}}else{return TaskStatus.Failure;}} }

自定義條件節(jié)點

同樣引入命名空間,然后繼承Conditional

判斷敵人是否存活,如果否,那么返回Failure

public class MyConditional : Conditional {public GameObject enemy;public override TaskStatus OnUpdate(){if (enemy.GetComponent<EnemyState>().alive){return TaskStatus.Success;}else{return TaskStatus.Failure;}} }

自定義修飾節(jié)點

繼承Decorator

修飾節(jié)點比較特殊,我這里重寫了四個函數(shù)

第一個函數(shù)是CanExecute(),這個函數(shù)的返回值表明該節(jié)點能否通過

第二個函數(shù)是OnChildExecuted(TaskStatus childStatus),輸入?yún)?shù)為孩子節(jié)點狀態(tài),該函數(shù)會在孩子節(jié)點執(zhí)行的時候調(diào)用

第三個函數(shù)為Decorate(TaskStatus status),輸入?yún)?shù)仍然是孩子節(jié)點狀態(tài),該函數(shù)的返回值會改變孩子節(jié)點的返回值

最后一個函數(shù)就沒什么可說的了,這里我使用的行為樹如下:

這里我仔細講解一下,行為樹調(diào)用過程,首先通過Selector嘗試調(diào)用Sequence,當他調(diào)用到MyBreak的時候,由于我們一開始的CanExecute通過條件為

executionStatus == TaskStatus.Inactive || executionStatus == TaskStatus.Running

也就是未激活或者正在運行時都允許通過,此時,通過MyBreak,走到MyConditional,然后判斷MyConditional返回的狀態(tài),如果返回Success,此時通過OnChildExecuted檢測到孩子節(jié)點狀態(tài)為Success,我們定義的變量executionStatus被賦予Success,這導(dǎo)致CanExecute返回false,此時行為樹從這條路中退出,能夠繼續(xù)運行,接下來繼續(xù)執(zhí)行MyMoveTo,這里有一點要注意,因為我們已經(jīng)執(zhí)行了條件判斷,因此才可以執(zhí)行MyMoveTo,我們之所以要在執(zhí)行完條件判斷后將該修飾節(jié)點的CanExecute返回false,是為了能夠繼續(xù)執(zhí)行下去,如果這個函數(shù)一直返回true,這會導(dǎo)致一直執(zhí)行條件判斷,就無法繼續(xù)執(zhí)行MyMoveTo了!;如果條件節(jié)點返回Failure那么該Sequence執(zhí)行失敗,Selector選擇執(zhí)行第二個Sequence。

這里要注意一點,中止導(dǎo)致的更新只有條件節(jié)點才能觸發(fā),并且在觸發(fā)時只會遵循那時刻的所有節(jié)點的變量,如果你嘗試更改其他節(jié)點使其無法執(zhí)行,這是沒有用的。只有條件節(jié)點才能觸發(fā)此類行為

using BehaviorDesigner.Runtime; using BehaviorDesigner.Runtime.Tasks;public class MyBreak : Decorator {private TaskStatus executionStatus = TaskStatus.Inactive;public override bool CanExecute(){Debug.Log(executionStatus == TaskStatus.Inactive || executionStatus == TaskStatus.Running);return executionStatus == TaskStatus.Inactive || executionStatus == TaskStatus.Running;}public override void OnChildExecuted(TaskStatus childStatus){// Update the execution status after a child has finished running.executionStatus = childStatus;}public override TaskStatus Decorate(TaskStatus status){if (GetComponent<Player>() && GetComponent<Player>().ready){return TaskStatus.Success;}else{return TaskStatus.Failure;}}public override void OnEnd(){executionStatus = TaskStatus.Inactive;} }

使用腳本創(chuàng)建一個行為樹

在某些情況下,你可能想要通過腳本在運行時創(chuàng)建一個行為樹,而不是直接使用拖拽或者面板操作去創(chuàng)建!例如:如果你已經(jīng)導(dǎo)

出了一個外部行為樹,并想通過腳本創(chuàng)建它的話,可以如下這么做:

behaviorTree.StartWhenEnabled = false;使得行為樹不要再一開始的時候執(zhí)行

后續(xù)當你想要調(diào)用他的時候,使用behaviorTree.EnableBehavior();來主動執(zhí)行行為樹

using System.Collections; using System.Collections.Generic; using UnityEngine; using BehaviorDesigner.Runtime; using BehaviorDesigner.Runtime.Tasks;public class BT_Test : MonoBehaviour {public bool on;public BehaviorTree behaviorTree;public void Start(){behaviorTree = GetComponent<BehaviorTree>();behaviorTree.StartWhenEnabled = false;}public void Update(){if (on){behaviorTree.EnableBehavior();on = false;}} }

Event事件

為了說明事件,我們使用下面這個行為樹

這里我們使用了一個條件節(jié)點:Has Received Event

通過該條件節(jié)點的Inspector界面,我們可以看到,它有一個屬性和三個變量

  • Event 事件名稱
  • 參數(shù)

我們來看看他的源碼的一個函數(shù)

很明顯,在行為樹啟動時,他會自動以給定的名稱來注冊事件,一共有四種注冊事件(0個參數(shù)到3個參數(shù))

public override void OnStart() {// Let the behavior tree know that we are interested in receiving the event specifiedif (!registered) {Owner.RegisterEvent(eventName.Value, ReceivedEvent);Owner.RegisterEvent<object>(eventName.Value, ReceivedEvent);Owner.RegisterEvent<object, object>(eventName.Value, ReceivedEvent);Owner.RegisterEvent<object, object, object>(eventName.Value, ReceivedEvent);registered = true;} }

我們再來看看注冊的函數(shù)

首先將eventReceived變量賦為true,然后設(shè)置對應(yīng)的參數(shù),即獲得參數(shù)

private void ReceivedEvent() {eventReceived = true; }private void ReceivedEvent(object arg1) {ReceivedEvent();if (storedValue1 != null && !storedValue1.IsNone) {storedValue1.SetValue(arg1);} }private void ReceivedEvent(object arg1, object arg2) {ReceivedEvent();if (storedValue1 != null && !storedValue1.IsNone) {storedValue1.SetValue(arg1);}if (storedValue2 != null && !storedValue2.IsNone) {storedValue2.SetValue(arg2);} }

通過Update函數(shù),我們可以知道,當注冊事件被調(diào)用時,eventReceived為true,該條件節(jié)點就會返回Success

public override TaskStatus OnUpdate() {return eventReceived ? TaskStatus.Success : TaskStatus.Failure; }

我們通過一個腳本來調(diào)用函數(shù),調(diào)用對應(yīng)的事件十分簡單,因為我們使用了一個名字來注冊事件,想要調(diào)用只需要使用behaviorTree.SendEvent()這個API即可調(diào)用,也就是發(fā)送,調(diào)用了它之后,Has Received Event條件節(jié)點就會返回Success,因此條件能夠通過,你可以把這個函數(shù)當作對應(yīng)名字事件的觸發(fā)器,觸發(fā)即可使得該條件節(jié)點返回Success。

另外,再次強調(diào)只有在行為樹運行的時候才有效,如果你的行為樹結(jié)束了,也就是出現(xiàn)綠色的勾了,說明行為樹已經(jīng)結(jié)束,此時再發(fā)送就無效了

using System.Collections; using System.Collections.Generic; using UnityEngine; using BehaviorDesigner.Runtime; using BehaviorDesigner.Runtime.Tasks;public class TaskA : MonoBehaviour {public bool send;public bool on;public BehaviorTree behaviorTree;public void Start(){behaviorTree = GetComponent<BehaviorTree>();behaviorTree.StartWhenEnabled = false;}public void Update(){if (on){behaviorTree.EnableBehavior();on = false;}if (send){behaviorTree.SendEvent<object>("MyEvent",1);send = false;}} }

行為樹運行后,還未收到事件調(diào)用信息,此時我們使用Wait防止行為樹結(jié)束,保持運行

我們發(fā)送信息后,條件節(jié)點收到信息后,通過執(zhí)行

Task的引用

我們先創(chuàng)建兩個行為節(jié)點,并在一個節(jié)點中創(chuàng)建第二個節(jié)點公共變量,并嘗試打印信息

public class TaskF : Action {public TaskS referencedTask;public override void OnAwake(){Debug.Log(referencedTask.some);} } public class TaskS : Action {public float some; }

我們來看看Inspector界面

在屬性界面,它顯示出了一個引用任務(wù)的選擇按鈕,因為對于行為節(jié)點我們無法直接賦值,因此采取選擇的方式

點擊Select,然后點擊行為樹中對應(yīng)類型的行為節(jié)點,此時就是將選中行為節(jié)點賦予給本節(jié)點的此變量

通過點擊X,可以取消引用

在引用后,我們啟動行為樹就會正常打印,如果不引用就會報出空指針異常。

變量同步器(Variable Synchronizer)

同步全局變量,箭頭表示同步方向,下圖中就是將A的值同步給B,點按Add添加同步操作

添加之后,仍然可以改變同步方向,點按箭頭按鈕即可,啟動后才能看到同步效果

Task可用特性

HelpURL : web 連接

[HelpURL("http://www.opsive.com/assets/BehaviorDesigner/documentation.php?id=27")] public class Parallel : Composite{ }

TaskIcon :任務(wù)的圖標

[TaskIcon("Assets/Path/To/{SkinColor}Icon.png")] public class MyTask : Action{ }

TaskCategory:任務(wù)的顯示位置(在 Task 任務(wù)面板中的顯示位置)

[TaskCategory("Common")] public class Seek : Action{ }[TaskCategory("RTS/Harvester")] public class HarvestGold : Action{ }

TaskDescription:功能描述的文本內(nèi)容,顯示在編輯器布局區(qū)域的左下角

[TaskDescription("The sequence task is similar to an \"and\" operation. ..."] public class Sequence : Composite{ }

LinkedTask:應(yīng)用其他的 Task 任務(wù)

[LinkedTask] public TaskGuard[] linkedTaskGuards = **null**;

InheritedField : 繼承屬性

[InheritedField] public float moveSpeed;

總結(jié)

以上是生活随笔為你收集整理的Unity-Behavior Designer详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。