摘要:经过两星期的动画的学习,这一次我们要完成一个有动画的小游戏---智能巡逻兵,并且必须使用订阅与发布模式传消息,其中巡逻兵和场景可以由工厂生成。并且最好能够将我们之前学习的模式一起整合一下,完成一个游戏制品。
完成的过程:
首先是完成一些预制品,其中就包括了我们的人物(Player),巡逻兵,凸建筑(要包含门),前两个我们直接从应用商店导入,可以导一些有动画的,建筑的话我们自己设置自己想要的模型,
这是我们预想设计出来的大概图案,其中我们等一下可以在场景里面增加巡逻兵和玩家角色,这里还有注意的是因为我们边缘开了门,所以为了防止从边缘掉落,我们要拓展一下四周的范围,增加一个平板。
其中大概的预制品有:
这里是我们导入的玩家(女角色)还有巡逻兵(丑东西)。
接下去是动画逻辑的实现还有代码的编写。
首先完成导入场景实例和资源:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityEngine.SceneManagement; public class SceneController : MonoBehaviour, ISceneController, IUserAction { public ScoreRecorder scoreRecorder; public GameObject player; public Text FinalText; public Text GameText; public int game = 0; void Awake() //创建导演实例并载入资源 设置60帧 { SSDirector director = SSDirector.getInstance(); director.setFPS(60); director.currentScenceController = this; director.currentScenceController.LoadResources(); } public void LoadResources() //载入资源 { player = Instantiate(Resources.Load("Prefabs/role")) as GameObject; if(player == null) { Debug.Log("null"); } Instantiate(Resources.Load("Prefabs/start"), new Vector3(2.5f, 0, 2f), Quaternion.identity); for(int i = 0; i < 4; i++) { Instantiate(Resources.Load("Prefabs/maze"), new Vector3(-4 * i, 0, 0), Quaternion.identity); Instantiate(Resources.Load("Prefabs/maze"), new Vector3(-4 * i, 0, 4), Quaternion.identity); Instantiate(Resources.Load("Prefabs/Zombie"), new Vector3(-1.6f - 4 * i, 0.5f,-1.6f), Quaternion.identity); Instantiate(Resources.Load("Prefabs/Zombie"), new Vector3(-1.6f - 4 * i, 0.5f, -1.6f + 4 ), Quaternion.identity); } Instantiate(Resources.Load("Prefabs/start"), new Vector3(-14.5f, 0, 2f), Quaternion.identity); Instantiate(Resources.Load("Prefabs/beside"), new Vector3(-6f, 0, -2.5f), Quaternion.identity); Instantiate(Resources.Load("Prefabs/beside"), new Vector3(-6f, 0, 6.5f), Quaternion.identity); } // Use this for initialization void Start() { game = 1; RoleTrigger.gameOver += GameOver; } // Update is called once per frame void Update() { if (player.transform.position.y < -10) { GameOver(); } } void GameOver() { game = 0; FinalText.text = "YOU LOSE!"; } public void ShowRule() { GUIStyle fontstyle1 = new GUIStyle(); fontstyle1.fontSize = 20; fontstyle1.normal.textColor = Color.blue; GUI.Label(new Rect(220, 100, 500, 200), "过门得分,注意躲避怪物,如果被发现,你将会被杀死!还有注意不要从边缘掉落", fontstyle1); } public void ReStart() { SceneManager.LoadScene("Task1"); } }
其中由于我们的巡逻兵要工厂模式且必须使用订阅与发布模式传消息,并且有以下要求实现的功能:
- 巡逻兵碰撞到障碍物,则会自动选下一个点为目标;
- 巡逻兵在设定范围内感知到玩家,会自动追击玩家;
- 失去玩家目标后,继续巡逻;
实现我们的巡逻兵的控制:
GuardController.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class GuardController : MonoBehaviour { private float pos_X, pos_Z; public float speed = 0.5f; private float dis = 0; private bool flag = true; public int state = 0; public GameObject role; int n = 0; public SceneController sceneController; // Use this for initialization void Start() { sceneController = (SceneController)SSDirector.getInstance().currentScenceController; role = sceneController.player; pos_X = this.transform.position.x; pos_Z = this.transform.position.z; } // Update is called once per frame void FixedUpdate() { if (state == 0) { patrol(); } else if (state == 1) { chase(role); } } void patrol() { if (flag) { switch (n) { case 0: pos_Z += 3.2f; break; case 1: pos_X += 3.2f; break; case 2: pos_Z -= 3.2f; break; case 3: pos_X -= 3.2f; break; } flag = false; } speed = 0.5f; this.transform.LookAt(new Vector3(pos_X, 0.5f, pos_Z)); dis = Vector3.Distance(transform.position, new Vector3(pos_X, 0.5f, pos_Z)); if (dis > 0.1) { transform.position = Vector3.MoveTowards(this.transform.position, new Vector3(pos_X, 0.5f, pos_Z), speed * Time.deltaTime); } else { n++; n %= 4; flag = true; this.transform.Rotate(new Vector3(0, 90, 0)); } } void chase(GameObject role) { speed = 1.0f * (1 + sceneController.scoreRecorder.Score / 5); transform.position = Vector3.MoveTowards(this.transform.position, role.transform.position, 0.5f * speed * Time.deltaTime); this.transform.LookAt(role.transform.position); } }
然后考虑下如何什么情况下触发巡逻兵状态,根据距离其实也不是不可以,但是我们实际上不选择让怪物出门,所以最后我改用触发器。在各个入口设置触发器。但是要让触发器触发的恰好是我们这个地图上的巡逻兵而不是其他巡逻兵。我在每个地图上挂载脚本获得当前Guard。具体代码如下:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class GetGuard : MonoBehaviour { public GameObject guard; void OnCollisionEnter(Collision collider) { Debug.Log(collider.gameObject.tag); if (collider.gameObject.tag == "Guard") { guard = collider.gameObject; } } }
然后在触发器写脚本,当然这里用到订阅与发布模式,进来然后能够出去即成功逃掉一次,发布事件,让记分员去订阅它,加分。
这部分是抓捕和行走控制状态切换:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Gate : MonoBehaviour { public delegate void AddScore(); public static event AddScore addScore; // Use this for initialization void OnTriggerEnter(Collider collider) { if (collider.gameObject.tag == "Player") { if (this.transform.parent.GetComponent<GetGuard>().guard.GetComponent<GuardController>().state == 0) { this.transform.parent.GetComponent<GetGuard>().guard.GetComponent<Animator>().SetInteger("state1", 1); this.transform.parent.GetComponent<GetGuard>().guard.GetComponent<GuardController>().state = 1; } else { this.transform.parent.GetComponent<GetGuard>().guard.GetComponent<Animator>().SetInteger("state1", 0); this.transform.parent.GetComponent<GetGuard>().guard.GetComponent<GuardController>().state = 0; escape(); } } if (collider.gameObject.tag == "Guard") { this.transform.parent.GetComponent<GetGuard>().guard.GetComponent<GuardController>().state = 0; } } void escape() { if (addScore != null) { addScore(); } } }
这一部分实现计分:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class ScoreRecorder : MonoBehaviour { public Text ScoreText; public SceneController sceneController; public int Score = 0; void AddScore() { Score++; } // Use this for initialization void Start() { sceneController = (SceneController)SSDirector.getInstance().currentScenceController; sceneController.scoreRecorder = this; Gate.addScore += AddScore; } // Update is called once per frame void Update() { ScoreText.text = "Score:" + Score.ToString();//更新分数 } }
using System.Collections; using System.Collections.Generic; using UnityEngine; public class RoleTrigger : MonoBehaviour { public delegate void GameOver();//委托 public static event GameOver gameOver;//事件 void OnCollisionEnter(Collision other) { if (other.gameObject.tag == "Guard") { if (gameOver != null) { gameOver(); } } } }
那大体的游戏架构其实架起来了,我们实现导演类就差不多基本可以运行实现了,但是我们有可能觉得可以丰富一下镜头感和游戏体验,所以我们采用第一人称视角,给我们的主角挂上了摄像机,并且摄像机随着他移动。还有我们增加了鼠标功能,从而能够是实现360度无死角,更好的躲避怪物。
其中给主角挂摄像机的代码如下:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class CameraRotate : MonoBehaviour { public SceneController sceneController; public GameObject role; void Start() { sceneController = (SceneController)SSDirector.getInstance().currentScenceController; role = sceneController.player; this.transform.parent = role.transform; } }
增加鼠标转动角度(视角幅度)的代码如下:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class RoleRotate : MonoBehaviour { public float roate_Speed = 2.0f;//旋转速度 public SceneController sceneController; void Start() { sceneController = (SceneController)SSDirector.getInstance().currentScenceController; } void Update() { if (sceneController.game == 1) { float mousX = Input.GetAxis("Mouse X") * roate_Speed;//得到鼠标移动距离 this.transform.Rotate(new Vector3(0, mousX, 0)); } } }
接下去我们完成和实现导演类和UI界面就好了,这里代码就不贴了,不是主要的逻辑部分。
接下去就是我们完整的视屏展示:
完整的视屏展示的链接是:视屏链接地址
其中完整的视屏大概1分20秒,保存到百度云即可查看完整版。