作业内容:
- 游戏设计要求:
- 创建一个地图和若干巡逻兵(使用动画);
- 每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次确定下一个目标位置,用自己当前位置为原点计算;
- 巡逻兵碰撞到障碍物,则会自动选下一个点为目标;
- 巡逻兵在设定范围内感知到玩家,会自动追击玩家;
- 失去玩家目标后,继续巡逻;
- 计分:玩家每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束;
- 程序设计要求:
- 必须使用订阅与发布模式传消息
- subject:OnLostGoal
- Publisher: ?
- Subscriber: ?
- 工厂模式生产巡逻兵
- 必须使用订阅与发布模式传消息
本次作业主要掌握模型和动画。
以下是游戏代码:
using System.Collections; using System.Collections.Generic; using UnityEngine; [RequireComponent(typeof(Animator))] [RequireComponent(typeof(CapsuleCollider))] [RequireComponent(typeof(Rigidbody))] public class ActorController : MonoBehaviour { private Animator ani; private AnimatorStateInfo currentBaseState; private Rigidbody rig; private Vector3 velocity; // 我实在不知道怎么用力学表示匀速,不如用运动学 private float rotateSpeed = 15f; private float runSpeed = 5f; // 旋转速度,奔跑速度 // Use this for initialization void Start () { ani = GetComponent<Animator>(); rig = GetComponent<Rigidbody>(); } // Update is called once per frame void FixedUpdate () { if (!ani.GetBool("isLive")) return; // 如果死亡,不执行所有动作 float x = Input.GetAxis("Horizontal"); float z = Input.GetAxis("Vertical"); ani.SetFloat("Speed", Mathf.Max(Mathf.Abs(x), Mathf.Abs(z))); // 设置速度 ani.speed = 1 + ani.GetFloat("Speed") / 3; // 调整跑步的时候的动画速度 velocity = new Vector3(x, 0, z); // 如果处于运动,则转向 if (x != 0 || z != 0) { Quaternion rotation = Quaternion.LookRotation(velocity); if (transform.rotation != rotation) transform.rotation = Quaternion.Slerp(transform.rotation, rotation, Time.fixedDeltaTime * rotateSpeed); } this.transform.position += velocity * Time.fixedDeltaTime * runSpeed; // 主角移动 } /// <summary> /// 用于检测Actor进入某个区域 /// </summary> /// <param name="other"></param> private void OnTriggerEnter(Collider other) { if (other.gameObject.CompareTag("Area")) { Publish publish = Publisher.getInstance(); int patrolType = other.gameObject.name[other.gameObject.name.Length - 1] - '0'; publish.notify(ActorState.ENTER_AREA, patrolType, this.gameObject); // 进入区域后,发布消息 } } /// <summary> /// 用于检测Actor与Patrol碰撞后死亡 /// </summary> /// <param name="collision"></param> private void OnCollisionEnter(Collision collision) { if (collision.gameObject.CompareTag("Patrol") && ani.GetBool("isLive")) { ani.SetBool("isLive", false); ani.SetTrigger("toDie"); // 执行死亡动作 Publish publish = Publisher.getInstance(); publish.notify(ActorState.DEATH, 0, null); // 碰撞后,发布死亡信息 } } }
BaseActions.cs:
using System.Collections; using System.Collections.Generic; using UnityEngine; namespace Tem.Action { public enum SSActionEventType : int { STARTED, COMPLETED } public interface ISSActionCallback { void SSEventAction(SSAction source, SSActionEventType events = SSActionEventType.COMPLETED, int intParam = 0, string strParam = null, Object objParam = null); } public class SSAction : ScriptableObject // 动作的基类 { public bool enable = true; public bool destory = false; public GameObject gameObject { get; set; } public Transform transform { get; set; } public ISSActionCallback callback { get; set; } public virtual void Start() { throw new System.NotImplementedException("Action Start Error!"); } public virtual void FixedUpdate() { throw new System.NotImplementedException("Physics Action Start Error!"); } public virtual void Update() { throw new System.NotImplementedException("Action Update Error!"); } } public class CCSequenceAction : SSAction, ISSActionCallback { public List<SSAction> sequence; public int repeat = -1; public int start = 0; public static CCSequenceAction GetSSAction(List<SSAction> _sequence, int _start = 0, int _repead = 1) { CCSequenceAction actions = ScriptableObject.CreateInstance<CCSequenceAction>(); actions.sequence = _sequence; actions.start = _start; actions.repeat = _repead; return actions; } public override void Start() { foreach (SSAction ac in sequence) { ac.gameObject = this.gameObject; ac.transform = this.transform; ac.callback = this; ac.Start(); } } public override void Update() { if (sequence.Count == 0) return; if (start < sequence.Count) sequence[start].Update(); } public void SSEventAction(SSAction source, SSActionEventType events = SSActionEventType.COMPLETED, int intParam = 0, string strParam = null, Object objParam = null) //通过对callback函数的调用执行下个动作 { source.destory = false; // 当前动作不能销毁(有可能执行下一次) this.start++; if (this.start >= this.sequence.Count) { this.start = 0; if (this.repeat > 0) repeat--; if (this.repeat == 0) { this.destory = true; this.callback.SSEventAction(this); } } } private void OnDestroy() { this.destory = true; } } public class IdleAction : SSAction { private float time; private Animator ani; // 站立持续时间 public static IdleAction GetIdleAction(float time, Animator ani) { IdleAction currentAction = ScriptableObject.CreateInstance<IdleAction>(); currentAction.time = time; currentAction.ani = ani; return currentAction; } public override void Start() { ani.SetFloat("Speed", 0); // 进入站立状态 } public override void Update() { if (time == -1) return; // 永久站立 time -= Time.deltaTime; // 减去时间 if (time < 0) { this.destory = true; this.callback.SSEventAction(this); } } } public class WalkAction : SSAction { private float speed; private Vector3 target; private Animator ani; // 移动速度和目标的地点 public static WalkAction GetWalkAction(Vector3 target, float speed, Animator ani) { WalkAction currentAction = ScriptableObject.CreateInstance<WalkAction>(); currentAction.speed = speed; currentAction.target = target; currentAction.ani = ani; return currentAction; } public override void Start() { ani.SetFloat("Speed", 0.5f); // 进入走路状态 } public override void Update() { Quaternion rotation = Quaternion.LookRotation(target - transform.position); if (transform.rotation != rotation) transform.rotation = Quaternion.Slerp(transform.rotation, rotation, Time.deltaTime * speed * 5); // 进行转向,转向目标方向 this.transform.position = Vector3.MoveTowards(this.transform.position, target, speed * Time.deltaTime); if (this.transform.position == target) { this.destory = true; this.callback.SSEventAction(this); } } } public class RunAction : SSAction { private float speed; private Transform target; private Animator ani; // 移动速度和人物的transform public static RunAction GetRunAction(Transform target, float speed, Animator ani) { RunAction currentAction = ScriptableObject.CreateInstance<RunAction>(); currentAction.speed = speed; currentAction.target = target; currentAction.ani = ani; return currentAction; } public override void Start() { ani.SetFloat("Speed", 1); // 进入跑步状态 } public override void Update() { Quaternion rotation = Quaternion.LookRotation(target.position - transform.position); if (transform.rotation != rotation) transform.rotation = Quaternion.Slerp(transform.rotation, rotation, Time.deltaTime * speed * 5); // 转向 this.transform.position = Vector3.MoveTowards(this.transform.position, target.position, speed * Time.deltaTime); if (Vector3.Distance(this.transform.position, target.position) < 0.5) { this.destory = true; this.callback.SSEventAction(this); } } } public class SSActionManager : MonoBehaviour { private Dictionary<int, SSAction> dictionary = new Dictionary<int, SSAction>(); private List<SSAction> watingAddAction = new List<SSAction>(); private List<int> watingDelete = new List<int>(); protected void Start() { } protected void Update() { foreach (SSAction ac in watingAddAction) dictionary[ac.GetInstanceID()] = ac; watingAddAction.Clear(); // 将待加入动作加入dictionary执行 foreach (KeyValuePair<int, SSAction> dic in dictionary) { SSAction ac = dic.Value; if (ac.destory) watingDelete.Add(ac.GetInstanceID()); else if (ac.enable) ac.Update(); } // 如果要删除,加入要删除的list,否则更新 foreach (int id in watingDelete) { SSAction ac = dictionary[id]; dictionary.Remove(id); DestroyObject(ac); } watingDelete.Clear(); // 将deletelist中的动作删除 } public void runAction(GameObject gameObject, SSAction action, ISSActionCallback callback) { action.gameObject = gameObject; action.transform = gameObject.transform; action.callback = callback; watingAddAction.Add(action); action.Start(); } } public class PYActionManager : MonoBehaviour { private Dictionary<int, SSAction> dictionary = new Dictionary<int, SSAction>(); private List<SSAction> watingAddAction = new List<SSAction>(); private List<int> watingDelete = new List<int>(); protected void Start() { } protected void FixedUpdate() { foreach (SSAction ac in watingAddAction) dictionary[ac.GetInstanceID()] = ac; watingAddAction.Clear(); // 将待加入动作加入dictionary执行 foreach (KeyValuePair<int, SSAction> dic in dictionary) { SSAction ac = dic.Value; if (ac.destory) watingDelete.Add(ac.GetInstanceID()); else if (ac.enable) ac.FixedUpdate(); } // 如果要删除,加入要删除的list,否则更新 foreach (int id in watingDelete) { SSAction ac = dictionary[id]; dictionary.Remove(id); DestroyObject(ac); } watingDelete.Clear(); // 将deletelist中的动作删除 } public void runAction(GameObject gameObject, SSAction action, ISSActionCallback callback) { action.gameObject = gameObject; action.transform = gameObject.transform; action.callback = callback; watingAddAction.Add(action); action.Start(); } } }Singletone.cs:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Singleton<T> : MonoBehaviour where T : MonoBehaviour { // 私有static变量 protected static T instance; public static T Instance { get { if (instance == null) { // 在场景里寻找该类 instance = (T)FindObjectOfType(typeof(T)); if (instance == null) { Debug.LogError("An instance of " + typeof(T) + " is needed in the scene, but it not!"); } } return instance; } } }
ScoreRecords.cs:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class ScoreRecorder { public Text scoreText; // 计分板 private int score = -1; // 纪录分数 public void resetScore() { score = -1; } // 飞碟点击中加分 public void addScore(int addscore) { score += addscore; scoreText.text = "Score:" + score; } public void setDisActive() { scoreText.text = ""; } public void setActive() { scoreText.text = "Score:" + score; } }
SceneController.cs:
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class SceneController : MonoBehaviour, Observer { public Text scoreText; public Text centerText; private ScoreRecorder record; //private UIController UI; private ObjectFactory fac; private float[] posx = { -5, 7, -5, 5 }; private float[] posz = { -5, -7, 5, 5 }; // 开始位置 void Start() { record = new ScoreRecorder(); record.scoreText = scoreText; //UI = new UIController(); //DUI.centerText = centerText; fac = Singleton<ObjectFactory>.Instance; Publish publisher = Publisher.getInstance(); publisher.add(this); // 添加事件 LoadResources(); } private void LoadResources() { Instantiate(Resources.Load("prefabs/skeleton_tom_angry"), new Vector3(2, 0, -2), Quaternion.Euler(new Vector3(0, 180, 0))); // 初始化主角 ObjectFactory fac = Singleton<ObjectFactory>.Instance; for (int i = 0; i < posx.Length; i++) { GameObject patrol = fac.setObjectOnPos(new Vector3(posx[i], 0, posz[i]), Quaternion.Euler(new Vector3(0, 180, 0))); patrol.name = "skeleton_mage_purple" + (i + 1); // 初始化巡逻兵 } } /// <summary> /// 如果角色死亡,显示LOSE /// </summary> /// <param name="state">订阅状态</param> /// <param name="pos"></param> public void notified(ActorState state, int pos, GameObject actor) { if (state == ActorState.ENTER_AREA) record.addScore(1); //else UI.loseGame(); } }
PublisherAndObserver.cs:
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public interface Publish { void notify(ActorState state, int pos, GameObject actor); // 发布函数 void add(Observer observer); // 委托添加事件 void delete(Observer observer); // 委托取消事件 } public interface Observer { void notified(ActorState state, int pos, GameObject actor); // 实现接收函数 } public enum ActorState { ENTER_AREA, DEATH } public class Publisher : Publish { private delegate void ActionUpdate(ActorState state, int pos, GameObject actor); private ActionUpdate updatelist; // 委托定义 /// <summary> /// 单实例模式 /// </summary> private static Publish _instance; public static Publish getInstance() { if (_instance == null) _instance = new Publisher(); return _instance; } public void notify(ActorState state, int pos, GameObject actor) { if (updatelist != null) updatelist(state, pos, actor); // 发布信息 } public void add(Observer observer) { updatelist += observer.notified; } public void delete(Observer observer) { updatelist -= observer.notified; } }
PatrolUI.cs:
using System.Collections; using System.Collections.Generic; using UnityEngine; using Tem.Action; [RequireComponent(typeof(Animator))] [RequireComponent(typeof(CapsuleCollider))] [RequireComponent(typeof(Rigidbody))] public class PatrolUI : SSActionManager, ISSActionCallback, Observer { public enum ActionState : int { IDLE, WALKLEFT, WALKFORWARD, WALKRIGHT, WALKBACK } // 各种动作 private Animator ani; // 动作 private SSAction currentAction; private ActionState currentState; // 保证当前只有一个动作 private const float walkSpeed = 1f; private const float runSpeed = 3f; // 跑步和走路的速度 // Use this for initialization new void Start () { ani = this.gameObject.GetComponent<Animator>(); Publish publisher = Publisher.getInstance(); publisher.add(this); // 添加事件 currentState = ActionState.IDLE; idle(); // 开始时,静止状态 } // Update is called once per frame new void Update () { base.Update(); } public void SSEventAction(SSAction source, SSActionEventType events = SSActionEventType.COMPLETED, int intParam = 0, string strParam = null, Object objParam = null) { currentState = currentState > ActionState.WALKBACK ? ActionState.IDLE : (ActionState)((int)currentState + 1); // 改变当前状态 switch (currentState) { case ActionState.WALKLEFT: walkLeft(); break; case ActionState.WALKRIGHT: walkRight(); break; case ActionState.WALKFORWARD: walkForward(); break; case ActionState.WALKBACK: walkBack(); break; default: idle(); break; } // 执行下个动作 } public void idle() { currentAction = IdleAction.GetIdleAction(Random.Range(1, 1.5f), ani); this.runAction(this.gameObject, currentAction, this); } public void walkLeft() { Vector3 target = Vector3.left * Random.Range(3, 5) + this.transform.position; currentAction = WalkAction.GetWalkAction(target, walkSpeed, ani); this.runAction(this.gameObject, currentAction, this); } public void walkRight() { Vector3 target = Vector3.right * Random.Range(3, 5) + this.transform.position; currentAction = WalkAction.GetWalkAction(target, walkSpeed, ani); this.runAction(this.gameObject, currentAction, this); } public void walkForward() { Vector3 target = Vector3.forward * Random.Range(3, 5) + this.transform.position; currentAction = WalkAction.GetWalkAction(target, walkSpeed, ani); this.runAction(this.gameObject, currentAction, this); } public void walkBack() { Vector3 target = Vector3.back * Random.Range(3, 5) + this.transform.position; currentAction = WalkAction.GetWalkAction(target, walkSpeed, ani); this.runAction(this.gameObject, currentAction, this); } /// <summary> /// 当碰到墙壁或者出了巡逻区域后,向反方向走 /// </summary> public void turnNextDirection() { currentAction.destory = true; // 销毁当前动作 switch (currentState) { case ActionState.WALKLEFT: currentState = ActionState.WALKRIGHT; walkRight(); break; case ActionState.WALKRIGHT: currentState = ActionState.WALKLEFT; walkLeft(); break; case ActionState.WALKFORWARD: currentState = ActionState.WALKBACK; walkBack(); break; case ActionState.WALKBACK: currentState = ActionState.WALKFORWARD; walkForward(); break; } // 执行相反动作 } public void getGoal(GameObject gameobject) { currentAction.destory = true; // 销毁当前动作 currentAction = RunAction.GetRunAction(gameobject.transform, runSpeed, ani); this.runAction(this.gameObject, currentAction, this); // 跑向目标方向 } public void loseGoal() { currentAction.destory = true; // 销毁当前动作 idle(); // 重新进行动作循环 } public void stop() { currentAction.destory = true; currentAction = IdleAction.GetIdleAction(-1f, ani); this.runAction(this.gameObject, currentAction, this); // 永久站立 } private void OnCollisionEnter(Collision collision) { Debug.Log(collision.gameObject. name); Transform parent = collision.gameObject.transform.parent; if (parent != null && parent.CompareTag("Wall")) turnNextDirection(); // 撞到墙 } private void OnTriggerEnter(Collider other) { if (other.gameObject.CompareTag("Door")) turnNextDirection(); // 走出巡逻区域 } /// <summary> /// 接受信息后执行的动作,判断角色在哪个区域内,并判断角色死亡 /// </summary> /// <param name="state">角色状态</param> /// <param name="pos">角色所在区域</param> /// <param name="actor">角色</param> public void notified(ActorState state, int pos, GameObject actor) { if (state == ActorState.ENTER_AREA) { if (pos == this.gameObject.name[this.gameObject.name.Length - 1] - '0') getGoal(actor); // 如果进入自己的区域,进行追击 else loseGoal(); // 如果离开自己的区域,放弃追击 } else stop(); // 角色死亡,结束动作 } }
ObjectFactory.cs:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class ObjectFactory : MonoBehaviour { private static List<GameObject> used = new List<GameObject>(); // 正在使用的对象链表 private static List<GameObject> free = new List<GameObject>(); // 正在空闲的对象链表 // 此函数表示将物体放到一个指定位置,并且面向方向指定 public GameObject setObjectOnPos(Vector3 targetposition, Quaternion faceposition) { if (free.Count == 0) { GameObject aGameObject = Instantiate(Resources.Load("prefabs/skeleton_mage_purple") , targetposition, faceposition) as GameObject; // 新建实例,将位置设置成为targetposition,将面向方向设置成faceposition used.Add(aGameObject); } else { used.Add(free[0]); free.RemoveAt(0); used[used.Count - 1].SetActive(true); used[used.Count - 1].transform.position = targetposition; used[used.Count - 1].transform.localRotation = faceposition; } return used[used.Count - 1]; } public void freeObject(GameObject oj) { oj.SetActive(false); used.Remove(oj); free.Add(oj); } }
大部分能代码与之前做过的一样的,不用改什么的。这类我使用了外部模型。 把模型放到prefabs中,用代码生成巡逻兵。
玩家与巡逻兵接触时游戏自动会停,Score表示甩掉过巡逻兵的数量。