unity 智慧巡邏兵

detecting……發表於2020-11-18

unity 智慧巡邏兵

在這裡插入圖片描述

一、遊戲要求

  • 遊戲設計要求:
    建立一個地圖和若干巡邏兵(使用動畫);
    每個巡邏兵走一個3~5個邊的凸多邊型,位置資料是相對地址。即每次確定下一個目標位置,用自己當前位置為原點計算;
    巡邏兵碰撞到障礙物,則會自動選下一個點為目標;
    巡邏兵在設定範圍內感知到玩家,會自動追擊玩家;
    失去玩家目標後,繼續巡邏;
    計分:玩家每次甩掉一個巡邏兵計一分,與巡邏兵碰撞遊戲結束;

  • 程式設計要求:
    必須使用訂閱與釋出模式傳訊息
    subject:OnLostGoal
    Publisher: ?
    Subscriber: ?
    工廠模式生產巡邏兵

二、遊戲效果

在這裡插入圖片描述

視訊演示

三、實現過程

本次專案參考自師兄的專案https://blog.csdn.net/C486C/article/details/80153548
並在此基礎上增加了自己的設計。

整體的設計思路在師兄的部落格中已經講得很清楚了,我就挑部分比較重要以及我做了修改的程式碼進行闡述。

PatrolData

儲存了巡邏兵的資料。

public class PatrolData : MonoBehaviour
{
    public int sign;                      //標誌巡邏兵在哪一塊區域
    public bool follow_player = false;    //是否跟隨玩家
    public int wall_sign = -1;            //當前玩家所在區域標誌
    public GameObject player;             //玩家遊戲物件
    public Vector3 start_position;        //當前巡邏兵初始位置     
    public bool alive = true;
}

GoPatrolAction

和之前的程式碼結構一樣,Action繼承自SSAction。GoPatrolAction是控制士兵進行巡邏的動作。士兵預設按四角矩形的軌跡運動,如果跟隨玩家的時機被觸發,則銷燬巡邏動作,增加跟隨玩家動作。

public class GoPatrolAction : SSAction
{
    private enum Dirction { EAST, NORTH, WEST, SOUTH };
    private float pos_x, pos_z;                 //移動前的初始x和z方向座標
    private float move_length;                  //移動的長度
    private float move_speed = 1.5f;            //移動速度
    private bool move_sign = true;              //是否到達目的地
    private Dirction dirction = Dirction.EAST;  //移動的方向
    private PatrolData data;                    //偵察兵的資料
    

    private GoPatrolAction() { }
    public static GoPatrolAction GetSSAction(Vector3 location)
    {
        GoPatrolAction action = CreateInstance<GoPatrolAction>();
        action.pos_x = location.x;
        action.pos_z = location.z;
        //設定移動矩形的邊長
        action.move_length = Random.Range(7, 10);
        return action;
    }
    public override void Update()
    {
        if (data.alive == false) {
            this.destroy = true;
            this.callback.SSActionEvent(this, 2, this.gameobject);
            return;
        }
        
        //偵察移動
        Gopatrol();
        //如果偵察兵需要跟隨玩家並且玩家就在偵察兵所在的區域,偵查動作結束
        if (data.follow_player && data.wall_sign == data.sign)
        {
            this.destroy = true;
            this.callback.SSActionEvent(this,0,this.gameobject);
        }
    }
    public override void Start()
    {
        this.gameobject.GetComponent<Animator>().SetBool("run", true);
        data  = this.gameobject.GetComponent<PatrolData>();
    }

    void Gopatrol()
    {
        if (move_sign)
        {
            //不需要轉向則設定一個目的地,按照矩形移動
            switch (dirction)
            {
                case Dirction.EAST:
                    pos_x -= move_length;
                    break;
                case Dirction.NORTH:
                    pos_z += move_length;
                    break;
                case Dirction.WEST:
                    pos_x += move_length;
                    break;
                case Dirction.SOUTH:
                    pos_z -= move_length;
                    break;
            }
            move_sign = false;
        }
        this.transform.LookAt(new Vector3(pos_x, 0, pos_z));
        float distance = Vector3.Distance(transform.position, new Vector3(pos_x, 0, pos_z));
        //當前位置與目的地距離浮點數的比較
        if (distance > 0.9)
        {
            transform.position = Vector3.MoveTowards(this.transform.position, new Vector3(pos_x, 0, pos_z), move_speed * Time.deltaTime);
        }
        else
        {
            dirction = dirction + 1;
            if(dirction > Dirction.SOUTH)
            {
                dirction = Dirction.EAST;
            }
            move_sign = true;
        }
    }
}

PatrolFollowAction

控制士兵跟隨玩家的動作,如果玩家脫離了追捕範圍或者出了士兵的巡邏區域,則跟隨動作結束並變回巡邏動作。

public class PatrolFollowAction : SSAction
{
    private float speed = 2.5f;            //跟隨玩家的速度
    private GameObject player;           //玩家
    private PatrolData data;             //偵查兵資料

    private PatrolFollowAction() { }
    public static PatrolFollowAction GetSSAction(GameObject player)
    {
        PatrolFollowAction action = CreateInstance<PatrolFollowAction>();
        action.player = player;
        return action;
    }

    public override void Update()
    {
        if (data.alive == false) {
            this.destroy = true;
            this.callback.SSActionEvent(this, 2, this.gameobject);
            return;
        }
        Follow();
        //如果偵察兵沒有跟隨物件,或者需要跟隨的玩家不在偵查兵的區域內
        if (!data.follow_player || data.wall_sign != data.sign)
        {
            this.destroy = true;
            this.callback.SSActionEvent(this,1,this.gameobject);
        }
    }
    public override void Start()
    {
        data = this.gameobject.GetComponent<PatrolData>();
    }
    void Follow()
    {
        transform.position = Vector3.MoveTowards(this.transform.position, player.transform.position, speed * Time.deltaTime);
        this.transform.LookAt(player.transform.position);
    }
}

上述兩個動作的切換是通過callBack函式實現的

public override void SSActionEvent(SSAction source, int intParam = 0, GameObject objectParam = null)
{
    if(intParam == 0)
    {
        //偵查兵跟隨玩家
        PatrolFollowAction follow = PatrolFollowAction.GetSSAction(objectParam.gameObject.GetComponent<PatrolData>().player);
        this.RunAction(objectParam, follow, this);
    }
    else if (intParam == 1) 
    {
        //偵察兵按照初始位置開始繼續巡邏
        GoPatrolAction move = GoPatrolAction.GetSSAction(objectParam.gameObject.GetComponent<PatrolData>().start_position);
        this.RunAction(objectParam, move, this);
        //玩家逃脫
        Singleton<GameEventManager>.Instance.PlayerEscape();
    }
    else {
        
    }
}
GameEventManager

與事件的釋出相關的管理器。

public class GameEventManager : MonoBehaviour
{
    //分數變化
    public delegate void ScoreEvent();
    public static event ScoreEvent ScoreChange;
    //遊戲結束變化
    public delegate void GameoverEvent();
    public static event GameoverEvent GameoverChange;
    //水晶數量變化
    public delegate void CrystalEvent();
    public static event CrystalEvent CrystalChange;
    //玩家射擊
    public delegate void Shoot();
    public static event Shoot ShootChange;
    //玩家逃脫
    public void PlayerEscape()
    {
        if (ScoreChange != null)
        {
            ScoreChange();
        }
    }
    //玩家被捕
    public void PlayerGameover()
    {
        if (GameoverChange != null)
        {
            GameoverChange();
        }
    }
    //減少水晶數量
    public void ReduceCrystalNum()
    {
        if (CrystalChange != null)
        {
            CrystalChange();
        }
    }

    public void shoot() {
        if (ShootChange != null) {
            ShootChange();
        }
    }
}

當某一個事件發生時,通過呼叫GameEventManager中的某一個函式來發布這個事件,訂閱這個事件的其他地方將會接收到此次事件,並作出相應操作。
比如控制器檢測到滑鼠點選時通過GameEventManage的shoot釋出射擊事件,訂閱這個事件的音訊管理器將會播放一段射擊的音訊。
可以通過以下方法訂閱:

public AudioClip shootClip;
void OnEnable()
{
    GameEventManager.ShootChange += Shoot;
}
void OnDisable()
{
    GameEventManager.ShootChange -= Shoot;
}

void Shoot() {
    FirstSceneController scene = SSDirector.GetInstance().CurrentScenceController as FirstSceneController;
    //在一個玩家的位置播放音樂
    AudioSource.PlayClipAtPoint(shootClip, scene.player.transform.position);
}
IUserAction

使用者介面,定義了玩家操作

public interface IUserAction                          
{
    //移動玩家
    void MovePlayer(float translationX, float translationZ, Vector3 mousePosition);
    //得到分數
    int GetScore();
    //得到水晶數量
    int GetCrystalNumber();
    //得到遊戲結束標誌
    bool GetGameover();
    //重新開始
    void Restart();
    //射擊
    void shoot();
}
碰撞檢測

巡邏兵需要做兩個碰撞檢測:
與玩家直接接觸;檢測玩家進入巡邏範圍。

由於有兩個檢測,所以需要設定兩個碰撞體,一個放在巡邏兵物件上,另一個放在巡邏兵的子物件上,然後分別附加上不同的碰撞處理程式碼。
對於直接接觸的碰撞,則遊戲結束,如果是進入巡邏範圍,則觸發追趕玩家的條件。

工廠模式產生巡邏兵

以及生產了水晶。

private GameObject patrol = null;                              //巡邏兵
    private List<GameObject> used = new List<GameObject>();        //正在被使用的巡邏兵
    private GameObject crystal = null;                             //水晶
    private List<GameObject> usedcrystal = new List<GameObject>();      //正在被使用的水晶
    private float range = 18;                                      //水晶生成的座標範圍
    private Vector3[] vec = new Vector3[9];                        //儲存每個巡邏兵的初始位置

    public FirstSceneController sceneControler;                    //場景控制器

    public List<GameObject> GetPatrols()
    {
        int[] pos_x = { -12, 4, 20 };
        int[] pos_z = { -20, -3, 12 };
        int index = 0;
        //生成不同的巡邏兵初始位置
        for(int i=0;i < 3;i++)
        {
            for(int j=0;j < 3;j++)
            {
                vec[index] = new Vector3(pos_x[i], 0, pos_z[j]);
                index++;
            }
        }
        for(int i=0; i < 9; i++)
        {
            patrol = Instantiate(Resources.Load<GameObject>("Prefabs/Patrol"));
            patrol.transform.position = vec[i];
            patrol.GetComponent<PatrolData>().sign = i + 1;
            patrol.GetComponent<PatrolData>().start_position = vec[i];
            used.Add(patrol);
        }   
        return used;
    }


    public List<GameObject> GetCrystal()
    {
        for(int i=0;i<12;i++)
        {
            crystal = Instantiate(Resources.Load<GameObject>("Prefabs/Crystal"));
            float ranx = Random.Range(-range, range);
            float ranz = Random.Range(-range, range);
            crystal.transform.position = new Vector3(ranx, 0, ranz);
            usedcrystal.Add(crystal);
        }

        return usedcrystal;
    }
   
玩家跟隨滑鼠轉動、鍵盤移動
//玩家移動
    public void MovePlayer(float translationX, float translationZ, Vector3 mousePosition)
    {
        if(!game_over)
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            //Debug.Log(ray);
            RaycastHit hitInfo;
            if(Physics.Raycast(ray, out hitInfo)){
                Vector3 target = hitInfo.point;
                //Debug.Log(target);
                target.y = player.transform.position.y;
                player.transform.LookAt(target);
            }

            if (translationX != 0 || translationZ != 0)
            {
                player.GetComponent<Animator>().SetBool("run", true);
            }
            else
            {
                player.GetComponent<Animator>().SetBool("run", false);
            }
            //移動和旋轉
            player.transform.Translate(translationX * player_speed * Time.deltaTime, 0, translationZ * player_speed * Time.deltaTime);
            
        }
點選滑鼠射擊
public void shoot() {
        player.GetComponent<Animator>().SetBool("run", false);
        player.GetComponent<Animator>().SetTrigger("shoot");
        Singleton<GameEventManager>.Instance.shoot();
        Ray ray = new Ray(player.transform.position, player.transform.forward);

        RaycastHit hit;
        if (Physics.Raycast(ray, out hit)) {
            GameObject obj = hit.transform.gameObject;
            if (obj.tag == "Patrol") {
                obj.GetComponent<PatrolData>().alive = false;
                obj.GetComponent<Animator>().SetBool("run", false);
                obj.GetComponent<Animator>().SetBool("dead", true);
            }
        }
    }
相機跟隨玩家移動
public class CameraFlow : MonoBehaviour
{
    public GameObject follow;            //跟隨的物體
    public float smoothing = 5f;          //相機跟隨的速度
    Vector3 offset;                      //相機與物體相對偏移位置
    public Transform player;
    void Start()
    {
        offset = transform.position - follow.transform.position;
    }

    void FixedUpdate()
    {
        Vector3 target = follow.transform.position + offset;
        //攝像機自身位置到目標位置平滑過渡
        transform.position = Vector3.Lerp(transform.position, target, smoothing * Time.deltaTime);
    }
}

專案地址

github地址

相關文章