Unity---巡邏兵

UML圖大致如下:


相關解釋:ObjectFactory生產巡邏兵Enemy堤如,Action是動作基類,EnemyLoop為巡邏動作,EnemyChase為追逐動作零蓉。在其中用了觀察者模式篮幢。
老師的要求:
1.巡邏兵是否為獨立模塊大刊?
有一部分耦合(與工廠和計分器),主要在于觀察者模式對這兩部分進行了消息傳遞和綁定事件三椿。
2.如果有多套AI缺菌,如何設計?
將AI擴展為Action的子類搜锰,需要換AI時只需要申請新的類型的Action即可伴郁。
下面是效果:



結構層次:

Paste_Image.png

下面分析代碼:
ObjectFactory.cs
一個簡單工廠,生成Enemy實例
包含了一個Enemy之間的觀察者互綁

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ObjectFactory : MonoBehaviour {
    public GameObject Enemy;
    private List<GameObject> UsingEnemy;

    private static ObjectFactory _instance;
    public static ObjectFactory Instance
    {
        get
        {
            _instance = FindObjectOfType<ObjectFactory>();
            if (_instance == null)
            {
                throw new System.NotImplementedException();
            }
            return _instance;
        }
    }


    // Use this for initialization
    void Start () {
        UsingEnemy = new List<GameObject>();
    }
    
    // Update is called once per frame
    void Update () {
        
    }

    public GameObject createEnemy(Vector3 pos)
    {
        UsingEnemy.Add(Instantiate(Enemy, pos, Quaternion.identity));
        return UsingEnemy[UsingEnemy.Count-1];
    }
    public void destroyEnemy()
    {
        for (int i = UsingEnemy.Count-1; i >= 0; i --)
        {
            GameObject temp = UsingEnemy[i];
            UsingEnemy.Remove(temp);
            Destroy(temp);
        }
    }
    public void bindObserver()
    {
        for (int i = 0; i < UsingEnemy.Count; i ++)
        {
            for (int j = 0; j < UsingEnemy.Count; j++)
            {
                UsingEnemy[i].GetComponent<EnemyScript>().OnGameOverEvent +=
                    UsingEnemy[j].GetComponent<EnemyScript>().setActionNull;
            }
        }
    }
}

SceneController.cs
場景管理器
在這里主要用于重新開始游戲的初始化設置

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SceneController : System.Object {
    public bool gameover = false;

    private static SceneController _instance;
    public static SceneController Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new SceneController();
            }
            return _instance;
        }
    }

    public string getScore()
    {
        return ScoreCounter.Score.ToString();
    }

    public bool isGameOver()
    {
        return gameover;
    }

    public void startGame()
    {
        gameover = false;
        ObjectFactory.Instance.destroyEnemy();

        GameObject temp = GameObject.Find("Player");
        temp.transform.position = new Vector3(0, 0.75f, 0);

        ObjectFactory.Instance.createEnemy(new Vector3(-5, 0.75f, -5));
        ObjectFactory.Instance.createEnemy(new Vector3(-5, 0.75f, 5));
        ObjectFactory.Instance.createEnemy(new Vector3(5, 0.75f, -5));

        // 對所有gameobject進行觀察者互綁
        ObjectFactory.Instance.bindObserver();
        ScoreCounter.Score = 0;

        // 設置Input模塊Active
        temp.GetComponent<InputModule>().setInputActive(true);
    }
}

EnemyLoop.cs
這段代碼描述了巡邏兵的巡邏AI
具體行為為在一個正方形中走多邊形

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyLoop : Action {
    // 在一個為 loopsize 大小的四邊形中走多邊形蛋叼,
    // 隨機從任意一條邊上選取任意一個點作為下一個target
    public const float loopsize = 7.0f;
    public const float speed = 2.0f;
    public Vector3 target;
    // 在四邊形中的相對位置
    public Vector3 position;
    // 誤差允許
    public const float EPS = 0.0001f;

    public static EnemyLoop getEnemyLoop(GameObject go)
    {
        EnemyLoop action = ScriptableObject.CreateInstance<EnemyLoop>();
        action.gameobject = go;
        // 初始化在四邊形的左下角
        action.position = new Vector3(0, 0, 0);
        // 計算首個target
        action.calcTarget();
        return action;
    }

    // Use this for initialization
    public override void Start () {
        
    }
    
    // Update is called once per frame
    public override void Update () {
        // 進行巡邏前進
        gameobject.transform.position = Vector3.MoveTowards(gameobject.transform.position, target, speed * Time.deltaTime);
        // 測試是否走到目標位置
        if (testArrived())
        {
            // 計算下一個巡邏目標
            calcTarget();
        }
    }

    public void calcTarget()
    {
        // 隨機選擇一條邊:上方為0焊傅, 順時針遞增
        int randomedge = Random.Range(0, 4);
        // 隨機選擇邊上一個點
        float randompoint = Random.Range(0, loopsize);
        Vector3 newposition = position;

        switch (randomedge)
        {
            case 0: newposition = new Vector3(randompoint, 0, loopsize); break;
            case 1: newposition = new Vector3(loopsize, 0, randompoint); break;
            case 2: newposition = new Vector3(randompoint, 0, 0); break;
            case 3: newposition = new Vector3(0, 0, randompoint); break;
        }
        // 從四邊形坐標映射到世界坐標
        target = newposition - position + gameobject.transform.position;
        position = newposition;
    }

    public bool testArrived()
    {
        return Vector3.Distance(gameobject.transform.position, target) < EPS;
    }
}

EnemyChase.cs
這段描述了巡邏兵的追蹤AI
簡單的向著玩家當前位置走
加入了一個AILevel剂陡,作用后面會提到

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyChase : Action
{
    public GameObject player;
    int AILevel;

    public static EnemyChase getEnemyChase(GameObject go, GameObject player, int AILevel)
    {
        EnemyChase action = ScriptableObject.CreateInstance<EnemyChase>();
        action.gameobject = go;
        action.player = player;
        action.AILevel = AILevel;
        return action;
    }

    // Use this for initialization
    public override void Start()
    {

    }

    // Update is called once per frame
    public override void Update()
    {
        // 追蹤玩家
        gameobject.transform.position = Vector3.MoveTowards(gameobject.transform.position, player.transform.position, AILevel * Time.deltaTime);
        // Debug.Log(gameobject.transform.position);
        // Debug.Log(player.transform.position);
    }

}

EnemyScript.cs
這段是掛載在Enemy上的腳本
具體實現(xiàn)細節(jié)請看注釋

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyScript : MonoBehaviour {
    // 需要運行的動作
    Action action;
    // AI層級(模擬為追蹤速度,每被甩掉一次自動增加AI層級)
    int AILevel = 2;
    // 玩家是否在偵測范圍內
    GameObject Player;
    bool PlayerDetected = false;
    float DetectedRadius = 5.0f;

    // 觀察者模式:當玩家離開Enemy的偵測范圍時,向計分器發(fā)送基于當前AILevel的計分信息
    public delegate void LeaveEvent(float AILevel);
    public event LeaveEvent OnLeaveEvent;

    // 觀察者模式:當玩家與其中一個Enemy碰撞時狐胎,通知其他Enemy滅活(綁定在SceneController中進行鹏倘,不需解綁,直接Destroy)
    public delegate void gameOverEvent();
    public event gameOverEvent OnGameOverEvent;

    // 觀察者模式:當玩家與其中一個Enemy碰撞時顽爹,通知玩家Input模塊滅活
    public delegate void forbidInputEvent(bool isActive);
    public event forbidInputEvent OnForbidInputEvent;

    // Use this for initialization
    void Start () {
        Player = GameObject.Find("Player");
        action = EnemyLoop.getEnemyLoop(gameObject);
        // 綁定計分
        OnLeaveEvent += ScoreCounter.CountScore;
        // 綁定玩家
        OnForbidInputEvent += Player.GetComponent<InputModule>().setInputActive;
    }
    
    // Update is called once per frame
    void Update () {
        // 在偵測范圍內尋找Player
        FindPlayer();
        if (action != null) {
            action.Update();
        }
        
    }

    void FindPlayer()
    {
        if (Vector3.Distance(Player.transform.position, gameObject.transform.position) < DetectedRadius)
        {
            // player第一次進入偵測范圍
            if (!PlayerDetected)
            {
                // 申請追蹤動作
                action = EnemyChase.getEnemyChase(gameObject, Player, AILevel);
                PlayerDetected = true;
            }
        }
        else
        {
            // Player離開偵測范圍
            if (PlayerDetected)
            {
                // 申請巡邏動作
                action = EnemyLoop.getEnemyLoop(gameObject);
                // 傳遞信息給觀察者
                OnLeaveEvent(AILevel);
                PlayerDetected = false;
                // 增加AI層級
                AILevel++;
            }
        }
    }

    // 與物體碰撞(判斷持續(xù))
    private void OnTriggerStay(Collider collider)
    {
        string Tag = collider.tag;
        // 與障礙物碰撞
        if (Tag == "RockTag" || Tag == "FenceTag" || Tag == "EnemyTag")
        {
            if (action is EnemyLoop)
            {
                // 重新計算下一個target
                (action as EnemyLoop).calcTarget();
            }
            // 追逐玩家過程中碰到障礙物(臨時解決方法:重設動作為巡邏)(效果很差哈哈哈)
            else if (action is EnemyChase)
            {
                action = EnemyLoop.getEnemyLoop(gameObject);
            }
        }
    }
    // 與物體碰撞(判斷初次)
    private void OnTriggerEnter(Collider collider)
    {
        string Tag = collider.tag;
        // 與玩家碰撞
        if (Tag == "PlayerTag")
        {
            // 游戲結束
            SceneController.Instance.gameover = true;
            action = null;
            // 通知其他Enemy停止動作(不使用SceneController的gameOver變量纤泵,降低耦合)
            OnGameOverEvent();
            // 滅活Input模塊
            OnForbidInputEvent(false);
        }
    }

    public void setActionNull()
    {
        action = null;
    }
}

其他的UI,ScoreCounter之類的都是很簡單的實現(xiàn)镜粤,不再贅述

相關還未解決的問題:
1.判斷碰撞時(比如玩家撞墻)會發(fā)生抖動捏题,使得玩家不再處于平面上,而會產生漂浮肉渴。
2.巡邏兵速度過快時會卡入墻內
3.巡邏兵模塊未完全解耦公荧,不能單獨一個預制即可拿來使用。
4.一些AI很丑同规,比如追蹤玩家時如果遇到障礙物應該怎么做循狰?
5.對于觀察者模式存在的意義還不甚明了,使用得還不夠規(guī)范券勺。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末绪钥,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子关炼,更是在濱河造成了極大的恐慌程腹,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件儒拂,死亡現(xiàn)場離奇詭異寸潦,居然都是意外死亡,警方通過查閱死者的電腦和手機社痛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門见转,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蒜哀,你說我怎么就攤上這事斩箫。” “怎么了凡怎?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵校焦,是天一觀的道長赊抖。 經(jīng)常有香客問我统倒,道長,這世上最難降的妖魔是什么氛雪? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任房匆,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘浴鸿。我一直安慰自己井氢,他們只是感情好,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布岳链。 她就那樣靜靜地躺著花竞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪掸哑。 梳的紋絲不亂的頭發(fā)上约急,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天,我揣著相機與錄音苗分,去河邊找鬼厌蔽。 笑死,一個胖子當著我的面吹牛摔癣,可吹牛的內容都是我干的奴饮。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼择浊,長吁一口氣:“原來是場噩夢啊……” “哼戴卜!你這毒婦竟也來了?” 一聲冷哼從身側響起琢岩,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤叉瘩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后粘捎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體薇缅,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年攒磨,在試婚紗的時候發(fā)現(xiàn)自己被綠了泳桦。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡娩缰,死狀恐怖灸撰,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情拼坎,我是刑警寧澤浮毯,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站泰鸡,受9級特大地震影響债蓝,放射性物質發(fā)生泄漏。R本人自食惡果不足惜盛龄,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一饰迹、第九天 我趴在偏房一處隱蔽的房頂上張望芳誓。 院中可真熱鬧,春花似錦啊鸭、人聲如沸锹淌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赂摆。三九已至,卻和暖如春钟些,著一層夾襖步出監(jiān)牢的瞬間库正,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工厘唾, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留褥符,地道東北人。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓抚垃,卻偏偏與公主長得像喷楣,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鹤树,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內容