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

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

給貓看的游戲AI實(shí)戰(zhàn)(五)忙碌的搬運(yùn)工與AI協(xié)作

2017-09-15 17:52 作者:皮皮關(guān)做游戲  | 我要投稿

上一節(jié)我們講解了AI行為中尋路的算法,比較特別的是我們是融合了算法可視化的理念,將尋路做出了有趣的動(dòng)態(tài)效果。

這一節(jié)我們?cè)俅无D(zhuǎn)向另一個(gè)問(wèn)題——多個(gè)AI協(xié)作的問(wèn)題。為了講清楚這個(gè)問(wèn)題,我特意做了這個(gè)例子:


上圖中,3個(gè)紅色的是物流機(jī)器人,綠色的是貨物。將貨物隨意地扔給他們,他們就能自發(fā)地將貨物依次擺放。如果覺(jué)得有趣的話,我們來(lái)試著實(shí)現(xiàn)一下。┌( ?_?)┘

1、實(shí)現(xiàn)一個(gè)單獨(dú)的物流機(jī)器人


對(duì)有一定基礎(chǔ)的讀者來(lái)說(shuō),這個(gè)例子已經(jīng)不需要細(xì)講了。

1、搭建場(chǎng)景。

如上圖,非常簡(jiǎn)單,場(chǎng)景包含地面和機(jī)器人,墻可要可不要。(為了開(kāi)發(fā)方便,一開(kāi)始可以把墻隱藏起來(lái))。

機(jī)器人自身非常精簡(jiǎn),就是一個(gè)不要碰撞體Collider、也不要Rigidbody的最普通的膠囊體即可。

另外做一個(gè)綠色方塊box代表貨物,box要有Rigidbody剛體組件。將box拖入工程目錄變成prefab以后用到,然后刪除方塊即可。


2、下面概覽一下用到的腳本:

1、攝像機(jī)掛載腳本PlayerInput.cs,功能:鼠標(biāo)點(diǎn)擊地面時(shí)生成貨物。

2、機(jī)器人掛載腳本RobotController.cs,功能:AI的所有邏輯。

可以猜到,其實(shí)箱子并不是由機(jī)器人通過(guò)物理推動(dòng)的,那樣實(shí)現(xiàn)會(huì)非常困難,因?yàn)楹茈y瞄準(zhǔn)推動(dòng)的角度,箱子會(huì)發(fā)生偏移和旋轉(zhuǎn)。


3、實(shí)現(xiàn)點(diǎn)擊地面,生成箱子。

這個(gè)功能對(duì)于看了本文好幾節(jié)的同學(xué)來(lái)說(shuō)應(yīng)該很簡(jiǎn)單了。代碼如下:


public class PlayerInput : MonoBehaviour {

    public GameObject box_prefeb;

    void OnClickGround()
    {
        Camera cam = Camera.main;       // 主攝像機(jī),這樣獲取很方便

        // 老規(guī)矩,從鼠標(biāo)點(diǎn)擊的地方,向屏幕內(nèi)打射線
        Ray ray = cam.ScreenPointToRay(Input.mousePosition);

        // 處理這條射線打到的那個(gè)GameObject
        RaycastHit hitt = new RaycastHit();
        Physics.Raycast(ray, out hitt, 100);
        Debug.DrawLine(cam.transform.position, ray.direction, Color.red);

        // 如果打到地面,就生成box(也就是貨物)
        if (hitt.transform!=null && hitt.transform.name=="Ground")
        {
            Vector3 p = new Vector3(hitt.point.x, 5, hitt.point.z);
            Instantiate(box_prefeb, p, Quaternion.Euler(0, 0, 0));
        }
    }

    void Update() {
        // 每幀檢測(cè)鼠標(biāo)點(diǎn)擊
        if (Input.GetMouseButtonDown(0))
        {
            OnClickGround();
        }
    }
}


4、實(shí)現(xiàn)貨物管理器。

由于我們要將散亂的貨物按順序碼好,這就需要給每個(gè)貨物編號(hào)。參考代碼如下:


public class RobotController : MonoBehaviour
{
    Dictionary<GameObject, int> boxes = new Dictionary<GameObject, int>();
    int id_counter = 1;

    // 保存貨物到boxes容器中,會(huì)給貨物分配ID
    void SaveNewBox(GameObject box)
    {
        if (box.transform.position.y > 0.251f)
        {
            return;
        }
        if (boxes.ContainsKey(box))
        {
            return;
        }
        boxes[box] = id_counter;
        id_counter++;
    }

    void Update()
    {
        GameObject[] all = GameObject.FindGameObjectsWithTag("Box");
        foreach (GameObject box in all)
        {
            // 保存貨物到boxes中,這里會(huì)給貨物分配ID
            SaveNewBox(box);
        }
    }
}


管理貨物的方法很簡(jiǎn)單,每一幀都遍歷所有貨物,將沒(méi)有加入boxes字典的貨物加入字典,ID增加1。


5、實(shí)現(xiàn)機(jī)器人移動(dòng)和整理邏輯。

簡(jiǎn)單來(lái)說(shuō),機(jī)器人從boxes中找一個(gè)需要整理的貨物,然后將其設(shè)置為當(dāng)前工作的貨物,然后移動(dòng)它即可。

注意機(jī)器人搬運(yùn)時(shí),有兩種狀態(tài):1、正在跑向貨物。2、正在搬運(yùn)貨物。也就是說(shuō),要先跑到貨物旁邊才能搬運(yùn)它。用一個(gè)bool變量來(lái)標(biāo)識(shí)狀態(tài)。


   // 當(dāng)前正在搬運(yùn)的貨物
    GameObject working_box;
    // going_back表示了機(jī)器人的兩種狀態(tài):
    // true代表當(dāng)前箱子已處理完畢,可以去取下一個(gè)箱子
    // false代表正在推當(dāng)前的箱子
    bool going_back = true;


我一開(kāi)始做的例子也沒(méi)有g(shù)oing_back區(qū)分狀態(tài),機(jī)器人會(huì)瞬移到貨物旁邊直接開(kāi)始搬運(yùn)。我的例子代碼也是慢慢完善才得到的。

邏輯完善以后,代碼如下圖,加了幾個(gè)函數(shù),Update函數(shù)也要添加一些邏輯:


  // 整理貨物,即搬運(yùn)貨物到目標(biāo)位置
    bool CleanBox(GameObject box)
    {
        Vector3 clean_pos = BoxCleanPos(boxes[box]);
        if (Vector3.Distance(box.transform.position, clean_pos) > 0.05f)
        {
            //MoveTowards(current: Vector3, target: Vector3, maxDistanceDelta: float) : Vector3
            Vector3 to = clean_pos - box.transform.position;
            box.transform.position += to.normalized * Mathf.Min(0.1f, Vector3.Distance(box.transform.position, clean_pos));

            transform.position = box.transform.position + to.normalized * -0.5f;
            return false;
        }

        return true;
    }

    // 根據(jù)ID計(jì)算貨物對(duì)應(yīng)的位置
    Vector3 BoxCleanPos(int id)
    {
        int n = (id - 1) % 5;
        int row = (id - 1) / 5;
        Vector3 v = new Vector3(-5f + n * 1.0f, 0.25f, -5f + row * 1.0f);
        return v;
    }


    // Update is called once per frame
    void Update()
    {
        GameObject[] all = GameObject.FindGameObjectsWithTag("Box");
        foreach (GameObject box in all)
        {
            // 保存貨物到boxes中,這里會(huì)給貨物分配ID
            SaveNewBox(box);
        }

        // 如果當(dāng)前沒(méi)有正在搬運(yùn)的貨物,則從boxes中查找需要搬運(yùn)的貨物
        if (working_box == null)
        {
            foreach (var pair in boxes)
            {
                Vector3 clean_pos = BoxCleanPos(boxes[pair.Key]);
                if (Vector3.Distance(pair.Key.transform.position, clean_pos) > 0.05f)
                {
                    // 找到一個(gè)需要搬運(yùn)的貨物,設(shè)置為當(dāng)前正在搬的
                    working_box = pair.Key;
                    break;
                }
            }
        }

        // 如果當(dāng)前正在搬運(yùn)貨物
        if (working_box != null)
        {
            // 情況一:正在搬運(yùn)的狀態(tài)
            if (going_back == false)
            {
                if (CleanBox(working_box))
                {
                    working_box = null;
                    going_back = true;

                }
            }
            else
            {
            // 情況二:正在跑向貨物的狀態(tài)
                if (Vector3.Distance(working_box.transform.position, transform.position) > 0.05f)
                {
                    //MoveTowards(current: Vector3, target: Vector3, maxDistanceDelta: float) : Vector3
                    Vector3 to = working_box.transform.position - transform.position;
                    float f = to.magnitude / 0.1f;
                    to /= f;
                    transform.position += to;
                }
                else
                {
                    going_back = false;
                }
            }
        }
    }


到此為止,我們已經(jīng)實(shí)現(xiàn)了一個(gè)單獨(dú)的物流機(jī)器人了,試試看吧。效果見(jiàn)本段開(kāi)頭只有一個(gè)機(jī)器人的那個(gè)動(dòng)圖。

回想一下前幾節(jié)介紹的狀態(tài)機(jī)AI的例子,會(huì)發(fā)現(xiàn)AI邏輯基本都是這樣的形式,只要寫(xiě)過(guò)一個(gè)復(fù)雜一點(diǎn)的狀態(tài)機(jī),再寫(xiě)大部分小游戲AI都會(huì)比較有信心了 (? ??_??)? ~~


2、多機(jī)器人協(xié)作

可以試驗(yàn)一下,在場(chǎng)景里多復(fù)制幾個(gè)機(jī)器人,也不會(huì)報(bào)錯(cuò)哦~~機(jī)器人可以正常搬運(yùn),只不過(guò)多人同時(shí)搬運(yùn)一個(gè)貨物,移動(dòng)會(huì)加快。

這是因?yàn)槎鄠€(gè)機(jī)器人的邏輯是相同的,他們會(huì)同時(shí)奔向同一個(gè)貨物,然后一起搬運(yùn)。他們的這種行為就好像不知道隊(duì)友的存在一樣,毫無(wú)計(jì)劃性,純粹的個(gè)人主義 ?ω?  ?ω?  ?ω?  ?ω?

要想讓多人協(xié)作起來(lái),他們之間就必須通過(guò)某種方式做信息的交流。

  • A:我要搬1號(hào)貨物哦,不要和我搶。

  • B:那我搬2號(hào)貨物。

  • 過(guò)了一陣:

  • A:1號(hào)貨物已搬運(yùn)完畢。

這里,我們通過(guò)在貨物上面做標(biāo)記的方法實(shí)現(xiàn)消息通信,為貨物創(chuàng)建一個(gè)腳本BoxData.cs,并掛在貨物的prefab上面:


// BoxData.cs
public class BoxData : MonoBehaviour {
    public GameObject working_robot = null;
}


我這里直接用機(jī)器人變量本身作為標(biāo)記,比較方便。

機(jī)器人打算搬某個(gè)貨物時(shí),要在貨物上面標(biāo)記好自己。別的機(jī)器人看到這個(gè)貨物已經(jīng)被人占用了,就不會(huì)處理這個(gè)貨物了。


   // 修改RobotController.cs
    // 給貨物加鎖,也就是打上自己的標(biāo)記
    bool LockBox(GameObject box)
    {
        BoxData d = box.GetComponent<BoxData>();
        if (d == null)
        {
            return false;
        }
        if (d.working_robot == null)
        {
            d.working_robot = gameObject;
        }

        if (d.working_robot != gameObject)
        {
            return false;
        }
        return true;
    }

    // 釋放鎖,也就是刪除貨物的標(biāo)記
    bool FreeBoxLock(GameObject box)
    {
        BoxData d = box.GetComponent<BoxData>();
        if (d == null)
        {
            return false;
        }
        if (d.working_robot == null)
        {
            return true;
        }
        if (d.working_robot != gameObject)
        {
            return false;
        }
        d.working_robot = null;
        return true;
    }


在機(jī)器人處理貨物時(shí)做一點(diǎn)改動(dòng),用到了面兩個(gè)函數(shù)。下面的代碼關(guān)鍵看LockBox和FreeBoxLock兩處:


void Update () {
        GameObject[] all = GameObject.FindGameObjectsWithTag("Box");
        foreach (GameObject box in all)
        {
            SaveNewBox(box);
        }

        if (working_box == null)
        {
            foreach (var pair in boxes)
            {
                // 如果鎖定失敗,就代表貨物已經(jīng)被別人占用了
                if (!LockBox(pair.Key))
                {
                    continue;
                }
                Vector3 clean_pos = BoxCleanPos(boxes[pair.Key]);
                if (Vector3.Distance(pair.Key.transform.position, clean_pos) > 0.05f)
                {
                    working_box = pair.Key;
                    break;
                }
            }
        }

        if (working_box != null)
        {
            if (going_back == false)
            {
                if(CleanBox(working_box))
                {
                    // 運(yùn)送到位后即可釋放鎖
                    FreeBoxLock(working_box);
                    working_box = null;
                    going_back = true;

                }
            }


這樣就OK了。

什么???這么簡(jiǎn)單!?是的,無(wú)論多少機(jī)器人,都能井井有條的協(xié)作!ヽ(??ω?? )ゝ

復(fù)制10個(gè)試一試!

如螞蟻一樣一擁而上的效果,你也可以實(shí)現(xiàn)。ヽ(??ω?? )ゝ。看起來(lái)炫酷的效果卻是用一個(gè)非常簡(jiǎn)單的方法做到的,這就是算法的魅力啊~~~


注意,有一種特殊情況,也已經(jīng)被解決了,不需要更多考慮,可以想想是為什么:

  • A:1號(hào)貨物已搬運(yùn)完畢。

  • 過(guò)了一會(huì)兒

  • C:1號(hào)貨物被擠到了其他位置,需要再搬運(yùn)一下

  • 過(guò)了一會(huì)兒

  • C:1號(hào)貨物搬運(yùn)完畢

代碼就不貼了,工程地址會(huì)放在文末。下載即可。


3、總結(jié)

本節(jié)我們介紹了一種模擬整理箱子的Demo,有很大篇幅在制作這個(gè)Demo本身,但是重點(diǎn)是第2段。在第2段我們用一種非常簡(jiǎn)單的方法實(shí)現(xiàn)了一種自發(fā)性的任務(wù)規(guī)劃。

這有點(diǎn)像公司制度,在制度合理的情況下,每個(gè)人只要按制度干活,就能實(shí)現(xiàn)良好的協(xié)作,事情就能自動(dòng)處理好??墒翘斓紫虏欢际沁@么簡(jiǎn)單的事,比如現(xiàn)在IT、金融等知識(shí)密集型的領(lǐng)域,制度的作用就不像在工廠、車間里那么有效了。這時(shí)候需要更復(fù)雜的協(xié)作機(jī)制,將計(jì)劃和管理的工作獨(dú)立出來(lái),而且同時(shí)讓工作者們保持一定自主性,才能達(dá)到良好效果。

在很多重視AI的游戲中,上面說(shuō)的這些也都是可以做到的。比如一些MOBA或者RTS游戲里的高智能電腦,就既懂得自己發(fā)展,又懂得和友軍協(xié)作。

作為AI設(shè)計(jì)的入門級(jí)專欄,本文沒(méi)有把問(wèn)題講得很深入。但是只要引起讀者的興趣,就已經(jīng)達(dá)到本文的目的了。 (????)?


工程地址:

https://github.com/mayao11/PracticalGameAI/tree/master/AIBlock


————————————————————————————————————

對(duì)游戲開(kāi)發(fā)感興趣的同學(xué),歡迎圍觀我們:【皮皮關(guān)游戲開(kāi)發(fā)教育】 ,會(huì)定期更新各種教程干貨,更有別具一格的線下小班教育~

我們的官網(wǎng)地址:http://levelpp.com/

我們的游戲開(kāi)發(fā)技術(shù)交流群:610475807

我們的微信公眾號(hào):皮皮關(guān)

給貓看的游戲AI實(shí)戰(zhàn)(五)忙碌的搬運(yùn)工與AI協(xié)作的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
千阳县| 古浪县| 双峰县| 汉寿县| 马山县| 陇川县| 成安县| 滦南县| 余姚市| 九江市| 陇南市| 敦煌市| 塔城市| 军事| 南和县| 宁阳县| 政和县| 济宁市| 彰化市| 隆安县| 铜川市| 武强县| 石台县| 扬州市| 永靖县| 临西县| 延边| 达拉特旗| 颍上县| 额敏县| 平罗县| 凉城县| 阳朔县| 襄樊市| 永修县| 双流县| 德保县| 随州市| 都江堰市| 海门市| 视频|