BehaviorDesigner 實(shí)現(xiàn)NPC 智能機(jī)器人

BehaviorDesigner 實(shí)現(xiàn)NPC 智能機(jī)器人

Created by miccall (轉(zhuǎn)載請(qǐng)注明出處 miccall.tech)

實(shí)現(xiàn)物體跟隨攝像機(jī)視野運(yùn)動(dòng)

1. VR實(shí)現(xiàn)機(jī)器人導(dǎo)航

  • 項(xiàng)目里要求一個(gè)機(jī)器人跟隨在角色旁邊嘴拢,一直飛著桩盲,就像一個(gè)精靈一樣,總在主角的身邊席吴,移動(dòng)赌结,轉(zhuǎn)頭,都要移動(dòng)到合適的位置 孝冒。還得讓他面向主角柬姚,今天就來(lái)實(shí)現(xiàn)這個(gè)樣例 。

2. 問(wèn)題思考

 - 1. 物體移動(dòng)到某個(gè)給定的位置(target)
 - 2. 物體轉(zhuǎn)動(dòng)到面向攝像機(jī) 
 - 3. 出現(xiàn)在相機(jī)的視野當(dāng)中 
 - 4. 自定義物體在攝影機(jī)的Screen中的位置 

3.實(shí)現(xiàn)以及方法

  • 移動(dòng)的話 迈倍,本來(lái)可以用動(dòng)畫(huà)來(lái)實(shí)現(xiàn) 伤靠,因?yàn)閯?dòng)畫(huà)還沒(méi)有做好,我就用一個(gè)cube當(dāng)作那個(gè)機(jī)器人做樣例了 啼染。
  • 首先有個(gè)cube之后 宴合,給他放一個(gè)移動(dòng)的腳本。這里我給他命名為PlayerTank 迹鹅。
  • 我們的目的就是讓他運(yùn)動(dòng)到某個(gè)target 卦洽,所以我們得給他指定一個(gè)followTransform 。 同時(shí)還有他的移動(dòng)速度和轉(zhuǎn)動(dòng)速度 斜棚。
  • 為了使他移動(dòng)不是很突兀阀蒂,我的思路是他先轉(zhuǎn)動(dòng)到面向follow物體,然后在直線移動(dòng)到給物體 弟蚀。所以算法很快寫(xiě)好了
        void LookTransform(Transform Mtransform)
            {
                Vector3 tarPos = Mtransform.position;
                Vector3 dirRot = tarPos - transform.position;
                Quaternion tarRot = Quaternion.LookRotation(dirRot);
                transform.rotation = Quaternion.Slerp(transform.rotation, tarRot, rotSpeed * Time.deltaTime);
            }

  • 簡(jiǎn)單解釋一下蚤霞,就是先確定物體的位置,然后求出指向他的方向义钉,并用插值的方法昧绣,
    讓物體轉(zhuǎn)動(dòng)到面向指定的物體 。
    好了捶闸,既然有了朝向的運(yùn)動(dòng)方向夜畴,那么走到這方向拖刃,就很簡(jiǎn)單了。
          transform.Translate(new Vector3(0, 0, movementSpeed * Time.deltaTime));
  • 那么什么時(shí)候停止運(yùn)動(dòng)呢 贪绘,我想了一下兑牡,決定用位置的差來(lái)判斷
    就是
        Vector3.Distance(transform.position, followTransform.position);

  • 好了,既然停止的方法也有了税灌,最后要解決的問(wèn)題就是朝向攝像機(jī)了均函。
    突然一想,這是問(wèn)題么垄琐,對(duì)边酒,這不是問(wèn)題 ,哈哈狸窘,剛剛寫(xiě)的那個(gè)算法墩朦,給一個(gè)攝像機(jī)就解決了嘛 。
    然后給出具體的判斷邏輯 翻擒。
        //該物體 接近要  到達(dá)的目標(biāo)   指定位置后就停止  
        if (Vector3.Distance(transform.position, followTransform.position) < 3f)
        {
            //當(dāng)物體道到位置時(shí) 讓物體面 向攝像機(jī) 
            LookTransform(Camre);
            return;
        }
        else
        {
            //讓物體轉(zhuǎn)向 將要運(yùn)動(dòng) 的方向 
            LookTransform(followTransform);
            transform.Translate(new Vector3(0, 0, movementSpeed * Time.deltaTime));
        }
  • 這樣就解決了物體移動(dòng)到target了氓涣,下一步就是固定target的位置,讓他在攝像機(jī)的固定位置了 陋气。
    新建一個(gè)腳本文件CameraView劳吠,掛在攝像機(jī)上。為了方便調(diào)試巩趁,我又用了FPS腳本痒玩,
    就是第一人稱(chēng)視角跟隨鼠標(biāo)轉(zhuǎn)動(dòng),就跟cs里面的玩法一樣议慰,(百度一大推代碼)蠢古。
    第二個(gè)調(diào)試算法是一個(gè)國(guó)外大牛寫(xiě)的 ,他可以給定一個(gè)距離别凹,畫(huà)出攝像機(jī)的視野范圍
這里寫(xiě)圖片描述
  • 這里我畫(huà)了兩個(gè)邊 草讶,一個(gè)是距離攝像機(jī)8.5米 用黃色表示,距離攝像機(jī)12米的用紅色表示炉菲。
    應(yīng)為篇幅問(wèn)題和詳略問(wèn)題堕战,這里不多解釋這個(gè)算法,有興趣的可以去研究一下拍霜,這里我們引用一下就行了嘱丢。
      Vector3[] GetCorners(float distance)
          {
        Vector3[] corners = new Vector3[4];

        float halfFOV = (theCamera.fieldOfView * 0.5f) * Mathf.Deg2Rad;
        float aspect = theCamera.aspect;

        float height = distance * Mathf.Tan(halfFOV);
        float width = height * aspect;

        // UpperLeft
        corners[0] = tx.position - (tx.right * width);
        corners[0] += tx.up * height;
        corners[0] += tx.forward * distance;

        // UpperRight
        corners[1] = tx.position + (tx.right * width);
        corners[1] += tx.up * height;
        corners[1] += tx.forward * distance;

        // LowerLeft
        corners[2] = tx.position - (tx.right * width);
        corners[2] -= tx.up * height;
        corners[2] += tx.forward * distance;

        // LowerRight
        corners[3] = tx.position + (tx.right * width);
        corners[3] -= tx.up * height;
        corners[3] += tx.forward * distance;

        return corners;
    }
      void FindUpperCorners()
      {
        Vector3[] corners = GetCorners(upperDistance);
        // for debugging
        Debug.DrawLine(corners[0], corners[1], Color.yellow); // UpperLeft -> UpperRight
        Debug.DrawLine(corners[1], corners[3], Color.yellow); // UpperRight -> LowerRight
        Debug.DrawLine(corners[3], corners[2], Color.yellow); // LowerRight -> LowerLeft
        Debug.DrawLine(corners[2], corners[0], Color.yellow); // LowerLeft -> UpperLeft
}
  • debug的時(shí)候敲才,直接調(diào)用FindUpperCorners()就可以了 坦辟。
    剛開(kāi)始的時(shí)候 ,我就用的這個(gè)調(diào)試 贿讹,給出一個(gè)位置,然后計(jì)算他的偏移量伐谈,調(diào)試了很久,沒(méi)有一個(gè)良好的效果试疙,我決定換個(gè)思路了 诵棵,為了普遍大眾 ,我還是把這個(gè)調(diào)試方法貼出來(lái)了祝旷,有需要的可以試試 履澳。
    第二個(gè)我就去翻api了 ,因?yàn)槲颐菜朴浀糜袀€(gè)屏幕坐標(biāo)和世界坐標(biāo)轉(zhuǎn)化的什么鬼方法來(lái)著怀跛。果然不出我所料距贷,這個(gè)方法的確是相當(dāng)?shù)暮糜玫难?。
    試了一下官方給的調(diào)試方法吻谋,畫(huà)了一個(gè)點(diǎn)出來(lái) 忠蝗。
        void OnDrawGizmosSelected()
        {
            Vector3 p = theCamera.ScreenToWorldPoint(new Vector3(100, 200, 8));
            Gizmos.color = Color.blue;
            //target.position = p;
            Gizmos.DrawSphere(p, 1F);
        }
  • 好了,就連我最后決定用的位置也標(biāo)明了漓拾。
    然后阁最,我就寫(xiě)了一個(gè)很簡(jiǎn)單的方法來(lái)達(dá)到目的。
          void maketarget()
          {
              Vector3 p = theCamera.ScreenToWorldPoint(new Vector3(RH, RV, upperDistance));
              target.position = p;
          }
  • 寫(xiě)完我都嚇了一跳 骇两,竟然如此簡(jiǎn)單速种。還是簡(jiǎn)單解釋一下 ,RH 是水平偏移量低千,RV是垂直偏移量配阵,upperDistance是距離攝像機(jī)的一個(gè)平面位置 。
    接下來(lái)就是運(yùn)行看效果了 示血。

4.中途出現(xiàn)的小BUG

  •  莫名其妙的做圓周運(yùn)動(dòng) 棋傍,然后我分析了線速度,角速度和半徑的關(guān)系 矾芙,
     然后總結(jié)出一個(gè)基本的規(guī)律舍沙,他應(yīng)該是當(dāng)運(yùn)動(dòng)到某個(gè)特定的位置 ,正好滿足了
     圓周運(yùn)動(dòng)的關(guān)系剔宪,然后我們調(diào)整movementSpeed 和rotSpeed 的值拂铡,讓他么盡可能
     的和Distance消除乘積關(guān)系,這樣出現(xiàn)的幾率就微乎其微了 葱绒。
     感想 -- 其實(shí)unity和現(xiàn)實(shí)物理感帅,理論物理 還是有很大的不同。
    

BehaviorDesigner 介紹以及使用方法

  • Behavior Designer 是一個(gè)行為樹(shù)插件 他提供了可視化編輯器 和強(qiáng)大的 API 可以輕松的創(chuàng)建 tasks(任務(wù))通過(guò)決策樹(shù)的方式判斷行為地淀,耦合度更高失球,更加方便的打造AI系統(tǒng) 。

  • 本教程不是入門(mén)教程 ,而是通過(guò)一個(gè)引導(dǎo)來(lái)實(shí)現(xiàn)我們的主題 实苞。

    1. Sequence 隊(duì)列節(jié)點(diǎn)
      -- 表示順序執(zhí)行的節(jié)點(diǎn) 此節(jié)點(diǎn)下屬所有節(jié)點(diǎn)依次執(zhí)行直到返回false
    1. Selector 選擇節(jié)點(diǎn)
      -- 表示在此節(jié)點(diǎn)下選擇一個(gè)執(zhí)行 此節(jié)點(diǎn)下屬所有節(jié)點(diǎn)依次執(zhí)行直到返回true
  • 我們暫且就用這兩個(gè) 想要了解更多的 豺撑,請(qǐng)參考別的教程

基本任務(wù)

  • 判斷是否到達(dá)目的地
  • 移動(dòng)到目的地(包含起飛和停止動(dòng)畫(huà))
  • 判斷是否面向攝像機(jī)
  • 判斷是否執(zhí)相應(yīng)的動(dòng)畫(huà)( 原地浮動(dòng) )
  • 判斷是否要面向UI物體
  • 面向UI之后 如果操作 執(zhí)行相應(yīng)的動(dòng)畫(huà)(點(diǎn)頭 搖頭 攤手)

1.判斷是否到達(dá)目的地

  • 這個(gè)方法在上面的教程已經(jīng)介紹了解決思路以及代碼實(shí)現(xiàn) 簡(jiǎn)單重新陳述一下,首先在攝像機(jī)上掛一個(gè)腳本 這個(gè)腳本用來(lái)測(cè)量攝像機(jī)的范圍黔牵,在范圍內(nèi)畫(huà)一個(gè)點(diǎn)聪轿,讓一個(gè)target(transform)覆蓋這個(gè)點(diǎn) 。
  • VR中攝像機(jī)可以根據(jù)頭盔的傳感器猾浦,來(lái)旋轉(zhuǎn)第一人稱(chēng)視角 這個(gè)target就固定在視角的一個(gè)地方隨意移動(dòng) 陆错。
  • 那么,機(jī)器人的z軸 (forward) 就一直面向這個(gè)target金赦。然后使用translate移動(dòng)到這個(gè)target附近音瓷。
  • 我們要判斷的就是這個(gè)target和機(jī)器人的distance
  • 我們自己寫(xiě)一個(gè)腳本來(lái)實(shí)現(xiàn)這個(gè)小功能(Task)
        using UnityEngine;

        namespace BehaviorDesigner.Runtime.Tasks.Basic.UnityVector3
        {
            [TaskCategory("Basic/Vector3")]
            [TaskDescription("Returns the distance between two Vector3s.")]

            public class dis_tance : Action
            {

                [Tooltip("target Vector3")]
                public SharedVector3 firstVector3;
                [Tooltip("The distance")]
                [RequiredField]
                public SharedFloat storeResult;
                public SharedGameObject action;
                //看代碼就應(yīng)該看到 我們需要一個(gè)target的位置向量 一個(gè)action的GameObject 
                //還有一個(gè)距離的返回值 


                public override TaskStatus OnUpdate()
                {
                    storeResult.Value = Vector3.Distance(firstVector3.Value, action.Value.transform.position);
                    return TaskStatus.Success;
                }

                public override void OnReset()
                {
                    storeResult = 0;
                }
            }
        }
  • 隨后,我們就要判斷這個(gè)距離的值 夹抗,來(lái)給定這個(gè)機(jī)器人 是否是移動(dòng)绳慎,還是靜止
  • 我們還是來(lái)寫(xiě)一個(gè)自定義Task 來(lái)實(shí)現(xiàn)
        using System.Collections;
        using System.Collections.Generic;
        using UnityEngine;
        namespace BehaviorDesigner.Runtime.Tasks.Basic.UnityVector3 { 
            public class comparedis : Action {

                public SharedFloat distance;
                public SharedFloat compare_dis ;
                /*compare_dis 表示一個(gè)范圍 以distance 減去這個(gè)范圍 
                表示以target為圓心 畫(huà)一個(gè)compare_dis的球 只要進(jìn)入這個(gè)球 ,說(shuō)明已經(jīng)到達(dá)目的地  */
                public SharedBool storeResult;
                /*我們返回一個(gè)bool值 他的意思是機(jī)器人是否靜止 
                  如果和target的計(jì)算距離大于0 那么就不是禁止 他就應(yīng)該移動(dòng)到target 否則 他是靜止的漠烧,就應(yīng)該做相應(yīng)的動(dòng)畫(huà) 
                */
                public override TaskStatus OnUpdate()
                {
                    storeResult.Value = distance.Value - compare_dis.Value > 0 ? false : true ; 
                    return TaskStatus.Success;
                }

                public override void OnReset()
                {
                    
                }
            }
        }
  • 好了 我們這兩個(gè)Task已經(jīng)寫(xiě)好了 然后用一個(gè)Sequence 連接兩個(gè)任務(wù)


    start
  • 做完了這個(gè)偷线,我們又要分情況討論了 一個(gè)是移動(dòng) 一個(gè)是移動(dòng)結(jié)束

2.移動(dòng)到target

  • 移動(dòng)到target我們要用一個(gè)判斷來(lái)做相應(yīng)的事件 首先 我們判斷第一步的變量 isstop 看看是否靜止 如果是false的話,執(zhí)行下面的步驟 否則的話 沽甥,跳轉(zhuǎn)到面向攝像機(jī)

  • 下面一層 我們用一個(gè)selector節(jié)點(diǎn) 來(lái)選擇一個(gè)節(jié)點(diǎn)執(zhí)行
    選擇是播放起飛動(dòng)畫(huà)呢 声邦,還是move呢

  • 首先播放起飛動(dòng)畫(huà)的條件是 isfirstfly 初始值是true 那么開(kāi)始執(zhí)行飛行動(dòng)畫(huà) ,執(zhí)行結(jié)束 把這個(gè)值設(shè)置false false就不進(jìn)入這個(gè)節(jié)點(diǎn) 而是去選擇執(zhí)行move 節(jié)點(diǎn) 摆舟。

  • 動(dòng)畫(huà)這個(gè)也是一個(gè)坑 我用比較長(zhǎng)的一段詳細(xì)講一下亥曹。收悉的朋友可以跳過(guò)這一段 。

move

-looktarget前面也講過(guò)了 movetotarget是封裝以后的translate 可以自己動(dòng)腦去實(shí)現(xiàn)一下

3.停止邏輯

stop

4.第一次停止動(dòng)畫(huà)

firststop

5.面向攝像機(jī)

lookcam
  • 面向攝像機(jī)以后 要判斷是否已經(jīng)結(jié)束 我們要判斷向量的方向來(lái)決定
  • 首先先計(jì)算機(jī)器人的forward向量 然后機(jī)器人位置減攝像機(jī)的位置 恨诱,得到一個(gè)方向向量 最后判斷兩個(gè)向量的單位向量是否相同 就可以了

6.面向UI

  • 我規(guī)定 當(dāng)外界代碼控制修改一個(gè)全局變量 就把他設(shè)置成true 然后指定一個(gè)UI物體 那么下一幀就可以面向UI了
lookUI

7.面向UI之后的動(dòng)作

UIaction
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末媳瞪,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子照宝,更是在濱河造成了極大的恐慌蛇受,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件厕鹃,死亡現(xiàn)場(chǎng)離奇詭異兢仰,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)剂碴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)把将,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人忆矛,你說(shuō)我怎么就攤上這事察蹲。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵洽议,是天一觀的道長(zhǎng)宗收。 經(jīng)常有香客問(wèn)我,道長(zhǎng)亚兄,這世上最難降的妖魔是什么镜雨? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮儿捧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘挑宠。我一直安慰自己菲盾,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布各淀。 她就那樣靜靜地躺著懒鉴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪碎浇。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天苟穆,我揣著相機(jī)與錄音跟磨,去河邊找鬼型豁。 笑死墩瞳,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的般妙。 我是一名探鬼主播突诬,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼垄提,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼妥粟!你這毒婦竟也來(lái)了锦秒?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤旅择,失蹤者是張志新(化名)和其女友劉穎惭笑,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體生真,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡沉噩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了柱蟀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片川蒙。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖长已,靈堂內(nèi)的尸體忽然破棺而出畜眨,到底是詐尸還是另有隱情昼牛,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布康聂,位于F島的核電站贰健,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏恬汁。R本人自食惡果不足惜伶椿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望氓侧。 院中可真熱鬧脊另,春花似錦、人聲如沸约巷。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)载庭。三九已至,卻和暖如春廊佩,著一層夾襖步出監(jiān)牢的瞬間囚聚,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工标锄, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留顽铸,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓料皇,卻偏偏與公主長(zhǎng)得像谓松,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子践剂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,077評(píng)論 25 707
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)鬼譬、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,094評(píng)論 4 62
  • 在前不久舉行的E3游戲展上逊脯,首款國(guó)產(chǎn)游戲主機(jī)戰(zhàn)斧F1雖然未能參展优质,但是依然引起了國(guó)外眾多玩家和媒體的關(guān)注,全球最著...
    斧子科技閱讀 324評(píng)論 0 0
  • 深夜 我失眠了 為了買(mǎi)房子的事情 其實(shí)是我們要求太多 又太稚嫩 沒(méi)弄清楚需求 走了太多的彎路 親愛(ài)的 我想做個(gè)好老...
    曲奇年糕閱讀 331評(píng)論 1 1
  • 才可以讓自己看開(kāi)一切世凡雜事 一次一次 想著逃離人群军洼,逃離自己生活過(guò)得地方 逃離那些纏繞在身旁陌生的氣味 一切的一...
    羅昕?jī)?/span>閱讀 182評(píng)論 0 0