1

巡逻兵

视频地址
Github地址

效果图

场景和人物使用现有素材制作,人物动画控制器是自己制作。
开始游戏
游戏进行

游戏组织结构

这次依然是使用了动作分离,MVC模式和工厂模式,以及新加了订阅与发表模式。

新设计

文件组织结构

游戏对象制作

  1. 玩家对象,添加了刚体,胶囊碰撞器以及动画:
    玩家对象
    玩家动画控制器
  2. 巡逻兵对象,添加了刚体,胶囊碰撞器,动画以及碰撞事件处理脚本:
    巡逻兵对象
    巡逻兵动画控制器
  3. 游戏地图由一系列组件制作:
    游戏地图

代码组织结构

接口,游戏场景控制器以及GUI

  1. 接口类声明在命名空间Interface中,UserAction类中主要为GUI和场景控制器交互的的方法,SSActionCallback中则为运动控制器的回调函数。

    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,并且在其中实现了接口声明的函数。场景控制器还是订阅者,在初始化时将自身相应的事件处理函数提交给消息处理器,在相应事件发生时被自动调用。

    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界面主要是实现显示分数和计时,并且在游戏结束的时候显示开始按钮以重开游戏。

    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”。如果是玩家,区域将调用事件管理器发布玩家进入新区域的事件。

    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. 当巡逻兵发生碰撞时,判断碰撞对象是否为玩家。如果是玩家,调用事件管理器发表游戏结束的消息。

    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. 巡逻动作则是选取一个在合理范围的位置,朝该位置移动,到达后调用回调函数继续巡逻动作。

    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);
    }


刘一
36 声望10 粉丝

引用和评论

0 条评论