Unity3D - 巡邏兵

巡邏兵

視頻地址
Github地址

效果圖

場景和人物使用現有素材製做,人物動畫控制器是本身製做。
開始遊戲
遊戲進行

遊戲組織結構

此次依然是使用了動做分離,MVC模式和工廠模式,以及新加了訂閱與發表模式。

新設計

文件組織結構

遊戲對象製做

  1. 玩家對象,添加了剛體,膠囊碰撞器以及動畫:
    玩家對象
    玩家動畫控制器
  2. 巡邏兵對象,添加了剛體,膠囊碰撞器,動畫以及碰撞事件處理腳本:
    巡邏兵對象
    巡邏兵動畫控制器
  3. 遊戲地圖由一系列組件製做:
    遊戲地圖

代碼組織結構

接口,遊戲場景控制器以及GUI

  1. 接口類聲明在命名空間Interface中,UserAction類中主要爲GUI和場景控制器交互的的方法,SSActionCallback中則爲運動控制器的回調函數。html

    namespace Interfaces
    {
        public interface ISceneController
        {
            void LoadResources();
        }
    
        public interface UserAction
        {
            int GetScore();
            void Restart();
            bool GetGameState();
            //移動玩家
            void MovePlayer(float translationX, float translationZ);
        }
    
        public enum SSActionEventType : int { Started, Completed }
    
        public interface SSActionCallback
        {
            void SSActionCallback(SSAction source);
        }
    }
  2. 遊戲場景控制器FirstSceneController類繼承了接口ISceneController和UserAction,而且在其中實現了接口聲明的函數。場景控制器仍是訂閱者,在初始化時將自身相應的事件處理函數提交給消息處理器,在相應事件發生時被自動調用。git

    public class FirstSceneController : MonoBehaviour, ISceneController, UserAction
    {
        GameObject player = null;
        PropFactory PF;
        int score = 0;
        int PlayerArea = 4;
        bool gameState = false;
        Dictionary<int, GameObject> allProp = null;
        CCActionManager CCManager = null;
    
        void Awake()
        {
            SSDirector director = SSDirector.getInstance();
            director.currentScenceController = this;
            PF = PropFactory.PF;
            if(CCManager == null) CCManager = gameObject.AddComponent<CCActionManager>();
            if (player == null && allProp == null)
            {
                Instantiate(Resources.Load<GameObject>("Prefabs/Plane"), new Vector3(0, 0, 0), Quaternion.identity);
                player = Instantiate(Resources.Load("Prefabs/Player"), new Vector3(0, 0, 0), Quaternion.identity) as GameObject;
                allProp = PF.GetProp();
            }
            if (player.GetComponent<Rigidbody>())
            {
                player.GetComponent<Rigidbody>().freezeRotation = true;
            }
        }
    
        // Update is called once per frame
        void Update () {
            //防止碰撞帶來的移動
            if (player.transform.localEulerAngles.x != 0 || player.transform.localEulerAngles.z != 0)
            {
                player.transform.localEulerAngles = new Vector3(0, player.transform.localEulerAngles.y, 0);
            }
            if (player.transform.position.y <= 0)
            {
                player.transform.position = new Vector3(player.transform.position.x, 0, player.transform.position.z);
            }
        }
    
        void OnEnable()
        {
            GameEventManager.ScoreChange += AddScore;
            GameEventManager.GameoverChange += Gameover;
        }
    
        void OnDisable()
        {
            GameEventManager.ScoreChange -= AddScore;
            GameEventManager.GameoverChange -= Gameover;
        }
    
        public void LoadResources()
        {
            
        }
    
        public int GetScore()
        {
            return score;
        }
    
        public void Restart()
        {
            player.GetComponent<Animator>().Play("New State");
            PF.StopPatrol();
            gameState = true;
            score = 0;
            player.transform.position = new Vector3(0, 0, 0);
            allProp[PlayerArea].GetComponent<Prop>().follow_player = true;
            CCManager.Tracert(allProp[PlayerArea], player);
            foreach (GameObject x in allProp.Values)
            {
                if (!x.GetComponent<Prop>().follow_player)
                {
                    CCManager.GoAround(x);
                }
            }
        }
    
        public bool GetGameState()
        {
            return gameState;
        }
        public void SetPlayerArea(int x)
        {
            if (PlayerArea != x && gameState)
            {
                allProp[PlayerArea].GetComponent<Animator>().SetBool("run", false);
                allProp[PlayerArea].GetComponent<Prop>().follow_player = false;
                PlayerArea = x;
            }
        }
    
        void AddScore()
        {
            if (gameState)
            {
                ++score;
                allProp[PlayerArea].GetComponent<Prop>().follow_player = true;
                CCManager.Tracert(allProp[PlayerArea], player);
                allProp[PlayerArea].GetComponent<Animator>().SetBool("run", true);
            }
        }
    
        void Gameover()
        {
            CCManager.StopAll();
            allProp[PlayerArea].GetComponent<Prop>().follow_player = false;
            player.GetComponent<Animator>().SetTrigger("death");
            gameState = false;
        }
    
        //玩家移動
        public void MovePlayer(float translationX, float translationZ)
        {
            if (gameState&&player!=null)
            {
                if (translationX != 0 || translationZ != 0)
                {
                    player.GetComponent<Animator>().SetBool("run", true);
                }
                else
                {
                    player.GetComponent<Animator>().SetBool("run", false);
                }
                //移動和旋轉
                player.transform.Translate(0, 0, translationZ * 4f * Time.deltaTime);
                player.transform.Rotate(0, translationX * 50f * Time.deltaTime, 0);
            }
        }
    }
  3. GUI界面主要是實現顯示分數和計時,而且在遊戲結束的時候顯示開始按鈕以重開遊戲。github

    public class InterfaceGUI : MonoBehaviour {
        UserAction UserActionController;
        ISceneController SceneController;
        public GameObject t;
        bool ss = false;
        float S;
        // Use this for initialization
        void Start () {
            UserActionController = SSDirector.getInstance().currentScenceController as UserAction;
            SceneController = SSDirector.getInstance().currentScenceController as ISceneController;
            S = Time.time;
        }
    
        private void OnGUI()
        {
            if(!ss) S = Time.time;
            GUI.Label(new Rect(Screen.width -160, 30, 150, 30),"Score: " + UserActionController.GetScore().ToString() + "  Time:  " + ((int)(Time.time - S)).ToString());
            if (ss)
            {
                if (!UserActionController.GetGameState())
                {
                    ss = false;
                }
            }
            else
            {
                if (GUI.Button(new Rect(Screen.width / 2 - 30, Screen.height / 2 - 30, 100, 50), "Start"))
                {
                    ss = true;
                    SceneController.LoadResources();
                    S = Time.time;
                    UserActionController.Restart();
                }
            }
        }
    
        private void Update()
        {
            //獲取方向鍵的偏移量
            float translationX = Input.GetAxis("Horizontal");
            float translationZ = Input.GetAxis("Vertical");
            //移動玩家
            UserActionController.MovePlayer(translationX, translationZ);
        }
    }

區域碰撞和巡邏兵碰撞

  1. 有元素進入區域時,判斷進入區域的對象是否爲玩家「Player」。若是是玩家,區域將調用事件管理器發佈玩家進入新區域的事件。dom

    public class AreaCollide : MonoBehaviour
    {
        public int sign = 0;
        FirstSceneController sceneController;
        private void Start()
        {
            sceneController = SSDirector.getInstance().currentScenceController as FirstSceneController;
        }
        void OnTriggerEnter(Collider collider)
        {
            //標記玩家進入本身的區域
            if (collider.gameObject.tag == "Player")
            {
                sceneController.SetPlayerArea(sign);
                GameEventManager.Instance.PlayerEscape();
            }
        }
    }
  2. 當巡邏兵發生碰撞時,判斷碰撞對象是否爲玩家。若是是玩家,調用事件管理器發表遊戲結束的消息。ide

    public class PlayerCollide : MonoBehaviour
    {
    
        void OnCollisionEnter(Collision other)
        {
            //當玩家與偵察兵相撞
            if (other.gameObject.tag == "Player")
            {
                GameEventManager.Instance.PlayerGameover();
            }
        }
    }

遊戲事件管理器

遊戲事件管理器是訂閱與發佈模式中的中繼者,消息的訂閱者經過與管理器中相應的事件委託綁定,在管理器相應的函數被髮布者調用(也就是發佈者發佈相應消息時),訂閱者綁定的相應事件處理函數也會被調用。訂閱與發佈模式實現了一部分消息的發佈者和訂閱者之間的解耦,讓發佈者和訂閱者沒必要產生直接聯繫。
public class GameEventManager
{
    public static GameEventManager Instance = new GameEventManager();
    //計分委託
    public delegate void ScoreEvent();
    public static event ScoreEvent ScoreChange;
    //遊戲結束委託
    public delegate void GameoverEvent();
    public static event GameoverEvent GameoverChange;

    private GameEventManager() { }

    //玩家逃脫進入新區域
    public void PlayerEscape()
    {
        if (ScoreChange != null)
        {
            ScoreChange();
        }
    }
    //玩家被捕,遊戲結束
    public void PlayerGameover()
    {
        if (GameoverChange != null)
        {
            GameoverChange();
        }
    }
}

追蹤與巡邏動做

  1. 場記經過動做管理器CCActionManager管理對象的移動,CCActionManager實現了追蹤Tracert,巡邏GoAround方法,並經過回調函數來循環執行巡邏動做或者在追蹤結束時繼續巡邏動做。函數

    public class CCActionManager : SSActionManager, SSActionCallback
    {
        public SSActionEventType Complete = SSActionEventType.Completed;
        Dictionary<int,CCMoveToAction> actionList = new Dictionary<int, CCMoveToAction>();
    
        public void Tracert(GameObject p,GameObject player)
        {
            if (actionList.ContainsKey(p.GetComponent<Prop>().block)) actionList[p.GetComponent<Prop>().block].destroy = true;
            CCTracertAction action = CCTracertAction.getAction(player, 0.8f);
            addAction(p.gameObject, action, this);
        }
    
        public void GoAround(GameObject p)
        {
            CCMoveToAction action = CCMoveToAction.getAction(p.GetComponent<Prop>().block,0.6f,GetNewTarget(p));
            actionList.Add(p.GetComponent<Prop>().block, action);
            addAction(p.gameObject, action, this);
        }
    
        private Vector3 GetNewTarget(GameObject p)
        {
            Vector3 pos = p.transform.position;
            int block = p.GetComponent<Prop>().block;
            float ZUp = 13.2f - (block / 3) * 9.65f;
            float ZDown = 5.5f - (block / 3) * 9.44f;
            float XUp = -4.7f + (block % 3) * 8.8f;
            float XDown = -13.3f + (block % 3) * 10.1f;
            Vector3 Move = new Vector3(Random.Range(-2f, 2f), 0, Random.Range(-2f, 2f));
            Vector3 Next = pos + Move;
            while (!(Next.x<XUp && Next.x>XDown && Next.z<ZUp && Next.z > ZDown))
            {
                Move = new Vector3(Random.Range(-1f, 1f), 0, Random.Range(-1f, 1f));
                Next = pos + Move;
            }
            return Next;
        }
    
        public void StopAll()
        {
            foreach(CCMoveToAction x in actionList.Values)
            {
                x.destroy = true;
            }
            actionList.Clear();
        }
    
        public void SSActionCallback(SSAction source)
        {
            if(actionList.ContainsKey(source.gameObject.GetComponent<Prop>().block)) actionList.Remove(source.gameObject.GetComponent<Prop>().block);
            GoAround(source.gameObject);
        }
    }
  2. 追蹤動做在動做管理器CCActionManager類中實現了Tracert函數,傳入了追蹤者和被追蹤的對象也就是玩家對象。建立了追蹤事件,在追上玩家或者追蹤標誌follow_player被置爲false前一直追着玩家(當碰撞事件發生時追蹤者的追蹤標誌會被場記設置爲false)。動畫

    public class CCTracertAction : SSAction
    {
        public GameObject target;
        public float speed;
    
        private CCTracertAction() { }
        public static CCTracertAction getAction(GameObject target, float speed)
        {
            CCTracertAction action = ScriptableObject.CreateInstance<CCTracertAction>();
            action.target = target;
            action.speed = speed;
            return action;
        }
    
        public override void Update()
        {
            this.transform.position = Vector3.MoveTowards(transform.position, target.transform.position, speed * Time.deltaTime);
            Quaternion rotation = Quaternion.LookRotation(target.transform.position - gameObject.transform.position, Vector3.up);
            gameObject.transform.rotation = rotation;
            if (gameObject.GetComponent<Prop>().follow_player == false||transform.position == target.transform.position)
            {
                destroy = true;
                CallBack.SSActionCallback(this);
            }
        }
    
        public override void Start()
        {
    
        }
    }
  3. 巡邏動做則是選取一個在合理範圍的位置,朝該位置移動,到達後調用回調函數繼續巡邏動做。this

    public void GoAround(GameObject p)
    {
        CCMoveToAction action = CCMoveToAction.getAction(p.GetComponent<Prop>().block,0.6f,GetNewTarget(p));
        actionList.Add(p.GetComponent<Prop>().block, action);
        addAction(p.gameObject, action, this);
    }
    
    //回調函數
    public void SSActionCallback(SSAction source)
    {
        if(actionList.ContainsKey(source.gameObject.GetComponent<Prop>().block)) actionList.Remove(source.gameObject.GetComponent<Prop>().block);
        GoAround(source.gameObject);
    }
相關文章
相關標籤/搜索