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

歡迎光臨散文網 會員登陸 & 注冊

給貓看的游戲AI實戰(zhàn)(二)視覺感知初步

2017-09-12 17:45 作者:皮皮關做游戲  | 我要投稿

第一講的內容過于簡單,很多貓們做完還是不明所以。就當是熟悉一下Unity開發(fā)基礎好了。這次正式發(fā)車。

前言、AI行為綜述

作為一個稱職的游戲AI,要具有以下自我修養(yǎng),可以不包含全部:

  1. 明白自己能干什么——目前所有可以做的行為,即可能性圖Possibility Map。

  2. 認識到當前狀況,對一般的AI來說,直接讀取游戲數據即可;高級AI有視覺、聽覺來感知到當前狀況。

  3. 理解目標,分解目標,決策具體行動并執(zhí)行。尋路是一種典型的決策問題。

  4. 了解環(huán)境,思考互動策略,比如推箱子、觸發(fā)機關給玩家制造麻煩。

  5. 群體交互。理解其他伙伴的信息和隊伍的整體策略,做出配合、防止沖突。在復數敵人的游戲中多有體現,在足球游戲中更是體現得淋漓盡致。

AI的層次有高有低,但是行為層次的高低與編程難度有時并不成正比。比如AI的視覺、聽覺系統(tǒng)就屬于高級行為,但是在Unity中實現并不難;而如果現在就寫基本的可能性圖系統(tǒng),你會發(fā)現NPC的基本移動、攻擊功能還沒實現,最后只能寫出偽代碼而無法真正實現一個功能。

作為一篇想要盡可能淺顯的文章,我們從視覺系統(tǒng)出發(fā),把復雜問題解構,目的是讓大家有一種“不過如此”的感覺。( ̄y▽ ̄)~*

1、模擬視覺系統(tǒng)——原理和例子

人類的視覺系統(tǒng)有幾個特點,比如:

  1. 近的看得清,遠的看不清。

  2. 視角大約90度,視線正前方信息豐富(色彩和細節(jié)),視線外側的部分只有輪廓和運動信息。

  3. 注意力有限,當關注于某個具體的方位或者物體時,其它部分被忽略(比如魔術中的障眼法對絕大多數人有效)。

作為一個AI,可以模擬這種視覺系統(tǒng),有助于干掉玩家或者……取悅玩家ㄟ( ▔, ▔ )ㄏ 。



上圖是潛入類游戲里程碑式的作品《盟軍敢死隊》,紅圈標出的是一個敵方德國兵。這個游戲是俯視的,玩家具有上帝視角。玩家在游戲中可以隨意查看敵人的視野范圍(雖然這有點不符合實際),敵人的視野是一個巨大的三角形,視線角度約90度;視野分為兩段,近處是亮綠色,遠處是暗綠色,在亮綠色范圍內一定會引起注意,而暗綠色的部分由于敵人看不太清楚,所以我方的人員只要趴下就不會引起注意。

由于敵人眾多,視線錯綜復雜,這個游戲的難度頗高,后面幾關我實在打不過去(╯‵□′)╯︵┻━┻     。

不說老古董了,舉個更有人氣的例子:《合金裝備》系列。

合金裝備2中,室內場景更多一些,敵人視角也更窄,但是從技術面分析,敵人視野的實現方式和《盟軍敢死隊》并沒有什么不同。上圖中的主角正在利用墻角進行隱蔽,等待敵人轉過去時伺機擊殺他。

可以說所有潛入類游戲的AI都要依賴于視覺系統(tǒng),在Unity中實現這個效果并不難,我們來嘗試一下。

2、模擬視覺系統(tǒng)——實現

拿出前面一節(jié)做的小例子,換位思考一下——我們做的例子里的Player就是敵人。


1、先制造一點氛圍,把主光源Directional Light的強度調低,讓場景昏暗下來。

2、給Player加上一個探照燈。右鍵點擊Player,Light > Spotlight

3、以上兩步應該已經能看到效果了。下面調整一下探照燈的遠近、角度范圍、光線強度。讓它和人物的視野大概一致。

4、開始寫代碼實現視野。我們用射線來模擬視野。先看最終效果,再來解釋代碼。

如圖,我們要發(fā)射一系列射線,從角色身上開始,發(fā)射到遠端,形成扇形分布。使用Debug.DrawLine函數顯示的射線只會出現在編輯窗口里,而不出現在Game窗口。像我這樣把兩個窗口并列排布可以很方便的看到效果。

給Player腳本增加兩個變量:


   public float viewRadius = 8.0f;      // 代表視野最遠的距離
    public float viewAngleStep = 30;     // 射線數量,越大就越密集,效果更好但硬件耗費越大。


增加一個函數:void DrawFieldOfView(),并在Update函數的最后面一句調用它。函數內容如下:


   void DrawFieldOfView()
    {
        // 獲得最左邊那條射線的向量,相對正前方,角度是-45
        Vector3 forward_left = Quaternion.Euler(0, -45, 0) * transform.forward * viewRadius;
        // 依次處理每一條射線
        for (int i = 0; i <= viewAngleStep; i++)
        {
            // 每條射線都在forward_left的基礎上偏轉一點,最后一個正好偏轉90度到視線最右側
            Vector3 v = Quaternion.Euler(0, (90.0f/viewAngleStep) * i, 0) * forward_left;
            // Player位置加v,就是射線終點pos
            Vector3 pos = transform.position + v;
            // 從玩家位置到pos畫線段,只會在編輯器里看到
            Debug.DrawLine(transform.position, pos, Color.red);
        }
    }


執(zhí)行游戲,已經可以看到效果了,截圖在上面可以返回去對比一下。

5、上面的射線在遇到盒子后,會傳過去?,F在處理一下,讓視線被物體、敵人阻擋,而不會穿透。

添加兩個Layer,一個是Enemy層,一個是Obstacle層。將那幾個大方塊設置為Obstacle層也就是障礙物層,敵人物體我們還沒做。前面介紹鼠標點擊地面的時候已經說明了添加、設置Layer的方法,不再贅述。

修改腳本,實際發(fā)出Ray與障礙物和敵人碰撞。


   void DrawFieldOfView()
    {
        // 獲得最左邊那條射線的向量,相對正前方,角度是-45
        Vector3 forward_left = Quaternion.Euler(0, -45, 0) * transform.forward * viewRadius;
        // 依次處理每一條射線
        for (int i = 0; i <= viewAngleStep; i++)
        {
            // 每條射線都在forward_left的基礎上偏轉一點,最后一個正好偏轉90度到視線最右側
            Vector3 v = Quaternion.Euler(0, (90.0f / viewAngleStep) * i, 0) * forward_left; ;

            // 創(chuàng)建射線
            Ray ray = new Ray(transform.position, v);
            RaycastHit hitt = new RaycastHit();
            // 射線只與兩種層碰撞,注意名字和你添加的layer一致,其他層忽略
            int mask = LayerMask.GetMask("Obstacle", "Enemy");
            Physics.Raycast(ray, out hitt, viewRadius, mask);

            // Player位置加v,就是射線終點pos
            Vector3 pos = transform.position + v;
            if (hitt.transform != null)
            {
                // 如果碰撞到什么東西,射線終點就變?yōu)榕鲎驳狞c了
                pos = hitt.point;
            }
            // 從玩家位置到pos畫線段,只會在編輯器里看到
            Debug.DrawLine(transform.position, pos, Color.red); ;

            // 如果真的碰撞到敵人,進一步處理
            if (hitt.transform!=null && hitt.transform.gameObject.layer == LayerMask.NameToLayer("Enemy"))
            {
                //OnEnemySpotted(hitt.transform.gameObject);
            }
        }
    }


成功的話應該是如下效果,視線被障礙物擋住了:

到這里效果已經有點像是盟軍敢死隊了……如果你覺得效果并不好,那只能先忍耐一下了。做游戲就是這樣,我們看到的成品游戲都是深入優(yōu)化的結果,無論程序還是美術都要做到80分才能得到好的結果。這里我們還是繼續(xù)研究視野問題本身吧。

6、這個例子最后一大步是添加虛擬的敵人。其實我們控制的Player是敵人,準確來說現在要添幾個虛擬的玩家,我們要去發(fā)現他們。別忘了,角色互換 (????)?  ?( ̄??)

如上圖,添加幾個敵人,可以在障礙前、障礙物后面都放一個,方便測試。注意圓柱體要有一定高度,也要粗一些。因為我們的射線是有高度的,我一開始放的圓柱體很矮,導致射線打不到它。另外如果圓柱太細,會從射線之間漏過去,也不好。

把這些敵人的Layer設置為Enemy層,以便和射線碰撞。

播放游戲。如圖,確保射線與圓柱體也能碰撞。

7、還沒完,我們要做出一種效果,讓敵人被看到時才顯形,平時沒被發(fā)現時是隱形的。先給敵人添加腳本,內容如下:


public class Enemy : MonoBehaviour {

    MeshRenderer meshRenderer;
    // 代表被發(fā)現時的幀數(這里用幀數代表時間)
    public int spottedFrame = -100;
    void Start () {
        meshRenderer = GetComponent<MeshRenderer>();
    }
    void Update () {
        // 通過設置 spottedFrame,就可以實現隱藏或顯現
        if (spottedFrame >= Time.frameCount-10)
        {
            meshRenderer.enabled = true;
        }
        else
        {
            meshRenderer.enabled = false;
        }
    }
}

如上圖,敵人只有兩個屬性,meshRenderer和spottedFrame,看注釋可以大致理解spottedFrame的用途,不理解沒關系,我們先做完。修改Player的腳本,剛才Update最后面射線碰撞到敵人的部分我們注釋掉了,把注釋去掉并添加函數OnEnemySpotted


   // Player.cs的Update函數……省略上面的代碼
            // 如果真的碰撞到敵人,進一步處理
            if (hitt.transform!=null && hitt.transform.gameObject.layer == LayerMask.NameToLayer("Enemy"))
            {
                OnEnemySpotted(hitt.transform.gameObject);
            }
        }
    }

    void OnEnemySpotted(GameObject enemy)
    {
        enemy.GetComponent<Enemy>().spottedFrame = Time.frameCount;
    }


對照這里spottedFrame 的設置方法,理解一下。當敵人被發(fā)現的時候,他會保持顯形10幀,一直在視線內就一直顯形。一旦它離開視線,10幀之后他就會再次隱形。


8、完成!接下來測試和修正問題吧。




總結

本專欄受到一本書《Practical Game AI Programming》的影響,例子會講的很淺顯易懂。我覺得游戲教程就應該如此,新手能跟著一步一步學習,老手可以看個思路。希望我能和大家一起實踐,對游戲AI有更系統(tǒng)更深入的理解,肯定可以超出那本書所講的范疇。(有一句話偷偷說:AI是國產游戲的明顯短板 (/ω\)。)

注意本文最后的控制敵人隱形、顯形的算法。AI實現時會有很多游戲特有的小算法,初學者學習時要注意思考哦。這些小算法屬于編程的核心能力,而核心能力在AI編程中極其重要,這也是為什么國外的游戲制作團隊會非常重視AI設計、重視培養(yǎng)AI設計師的原因。


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

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

我們的官網地址:levelpp.com/

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

我們的微信公眾號:皮皮關


給貓看的游戲AI實戰(zhàn)(二)視覺感知初步的評論 (共 條)

分享到微博請遵守國家法律
石台县| 于都县| 徐水县| 原阳县| 亳州市| 特克斯县| 大荔县| 普安县| 当涂县| 青岛市| 容城县| 锡林浩特市| 江油市| 克东县| 自贡市| 武城县| 玉溪市| 新乡县| 仁怀市| 阳新县| 巴中市| 保靖县| 凤冈县| 阳山县| 齐河县| 清苑县| 高唐县| 威信县| 永德县| 无极县| 浠水县| 榕江县| 万全县| 沁水县| 常德市| 黔西| 宜春市| 舞钢市| 禄劝| 新田县| 长乐市|