Unity3D学习——牧师与恶魔过河游戏(组合模式、单实例模式)
2017-04-10 21:50
218 查看
成品展示
通过鼠标点击实现对象移动原游戏Priests and Devils传送门,有兴趣的可以去玩下:
http://www.flash-game.net/game/2535/priests-and-devils.html
游戏制作
如果需要,可以按如下步骤先设置好预设后,再把文章最后的代码复制进脚本,即可运行察看游戏效果,所用unity版本为5.5预制与脚本的挂载
游戏设计以及代码分析
采用MVC结构,单实例模式,类的UML图如下组合模式产生动作,单实例模式方便管理
各类说明
导演类:管理游戏全局状态,获取当前游戏的场景,协调各个类之间的通讯,是单实例类,不被Unity内存管理所管理
界面:负责与用户交互
场记:管理本次场景所有的游戏对象,协调游戏对象(预制件级别)之间的通讯,响应外部输入事件,管理本场次的规则,杂务
动作管理者:被场记调用,为场景中的对象设计具体动作并执行
红色方框中为基本的动作类
各类具体代码
导演类 SSDirector
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public enum State { WIN, LOSE, PAUSE, CONTINUE , START }; public class SSDirector : System.Object { //计时器 public int totalSeconds = 60; public int leaveSeconds; public bool onCountDown = false; public string countDownTitle = "Start"; public State state { get; set; } public static SSDirector _instance; public ISceneController currentScenceController { get; set; } public bool running { get; set; } public static SSDirector getInstance() { if (_instance == null) { _instance = new SSDirector(); } return _instance; } public int getFPS() { return Application.targetFrameRate; } public void setFPS(int fps) { Application.targetFrameRate = fps; } public void NextScene() { Debug.Log("抱歉,没下一个场景了"); } public IEnumerator DoCountDown() { while (leaveSeconds > 0) { yield return new WaitForSeconds(1f); leaveSeconds--; } } }
界面交互 UserGUI
using System.Collections; using System.Collections.Generic; using UnityEngine; /** * 玩家动作接口,鼠标点击是唯一动作 */ public interface IUserAction { void clickOne(); } public class UserGUI : MonoBehaviour { private IUserAction action; float width, height; void Start() { action = SSDirector.getInstance().currentScenceController as IUserAction; } float castw(float scale) { return (Screen.width - width) / scale; } float casth(float scale) { return (Screen.height - height) / scale; } void OnGUI() { width = Screen.width / 12; height = Screen.height / 12; GUI.Label(new Rect(castw(2f)+20, casth(6f) - 20, 50, 50), SSDirector.getInstance().leaveSeconds.ToString());// 倒计时秒数 // if (SSDirector.getInstance().state != State.WIN && SSDirector.getInstance().state != State.LOSE && GUI.Button(new Rect(10, 10, 80, 30), SSDirector.getInstance().countDownTitle)) { if (SSDirector.getInstance().countDownTitle == "Start") { SSDirector.getInstance().currentScenceController.Resume(); SSDirector.getInstance().countDownTitle = "Pause"; SSDirector.getInstance().onCountDown = true; StartCoroutine(SSDirector.getInstance().DoCountDown()); } else { SSDirector.getInstance().currentScenceController.Pause(); SSDirector.getInstance().countDownTitle = "Start"; SSDirector.getInstance().onCountDown = false; StopAllCoroutines(); } } if (SSDirector.getInstance().state == State.WIN)//胜利 { StopAllCoroutines(); if (GUI.Button(new Rect(castw(2f), casth(6f), Screen.width / 8, height), "Win!")) { SSDirector.getInstance().currentScenceController.Restart(); } } else if (SSDirector.getInstance().state == State.LOSE)//失败 { StopAllCoroutines(); if (GUI.Button(new Rect(castw(2f), casth(6f), Screen.width / 7, height), "Lose!")) { SSDirector.getInstance().currentScenceController.Restart(); } } } /** * 检测用户的点击动作 */ void Update() { action.clickOne(); } }
场景管理器 FirstSceneController
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; /** * 场记的接口,每个场记都要实现 * 加载场景资源、场景的暂停、恢复和重新布置 */ public interface ISceneController { void LoadResources(); void Pause(); void Resume(); void Restart(); } public class FirstSceneController : MonoBehaviour, IUserAction, ISceneController { public FirstSSActionManager actionManager; List<GameObject> LeftObjList = new List<GameObject>();//存储在左岸的对象 List<GameObject> RightObjList = new List<GameObject>();//存储在右岸的对象 GameObject[] boat = new GameObject[2]; GameObject boat_obj, leftShore_obj, rightShore_obj; Vector3 LeftShorePos = new Vector3(-12, 0, 0); Vector3 RightShorePos = new Vector3(12, 0, 0); Vector3 BoatLeftPos = new Vector3(-4, 0, 0); Vector3 BoatRightPos = new Vector3(4, 0, 0); void Awake() { SSDirector director = SSDirector.getInstance(); director.setFPS(60); director.currentScenceController = this; director.currentScenceController.LoadResources(); director.leaveSeconds = director.totalSeconds; } void Start () { SSDirector.getInstance().state = State.PAUSE; SSDirector.getInstance().countDownTitle = "Start"; actionManager = GetComponent<FirstSSActionManager>() as FirstSSActionManager; } void Update() { Judge(); } public void LoadResources() { GameObject priest_obj, devil_obj; Camera.main.transform.position = new Vector3(0, 0, -20); /** * 加载两岸 * 加载船 */ leftShore_obj = Instantiate(Resources.Load("Prefabs/Shore"), LeftShorePos, Quaternion.identity) as GameObject; rightShore_obj = Instantiate(Resources.Load("Prefabs/Shore"), RightShorePos, Quaternion.identity) as GameObject; leftShore_obj.name = "left_shore"; rightShore_obj.name = "right_shore"; boat_obj = Instantiate(Resources.Load("Prefabs/Boat"), BoatLeftPos, Quaternion.identity) as GameObject; boat_obj.name = "boat"; /** * 一开始牧师、魔鬼、船都在左岸,注意:我是通过判断父对象决定物体在两岸还是船上的 */ boat_obj.transform.parent = leftShore_obj.transform; /** * 把牧师与恶魔摆在左岸初始位置 */ for (int i = 0; i < 3; ++i) { priest_obj = Instantiate(Resources.Load("Prefabs/Priest")) as GameObject; priest_obj.name = i.ToString();//牧师编号0 1 2 priest_obj.transform.position = new Vector3(-16f + 1.5f * Convert.ToInt32(priest_obj.name), 2.7f, 0); priest_obj.transform.parent = leftShore_obj.transform; LeftObjList.Add(priest_obj); devil_obj = Instantiate(Resources.Load("Prefabs/Devil")) as GameObject; devil_obj.name = (i + 3).ToString();//魔鬼编号3 4 5 devil_obj.transform.position = new Vector3(-16f + 1.5f * Convert.ToInt32(devil_obj.name), 2.7f, 0); devil_obj.transform.parent = leftShore_obj.transform; LeftObjList.Add(devil_obj); } } /** * 实现用户动作接口中的点击对象函数 * 利用射线获取对象 * 对象名字 0—2代表牧师 * 3—5代表魔鬼 * boat代表船 * 若对象名字为其他,或者没点中对象则不处理 */ public void clickOne() { GameObject gameObj = null; if (Input.GetMouseButtonDown(0) && (SSDirector.getInstance().state == State.START || SSDirector.getInstance().state == State.CONTINUE)) { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if (Physics.Raycast(ray, out hit)) { gameObj = hit.transform.gameObject; } } if (gameObj == null) return; else if (gameObj.name == "0" || gameObj.name == "1" || gameObj.name == "2" || gameObj.name == "3" || gameObj.name == "4" || gameObj.name == "5") { MovePeople(gameObj); } else if(gameObj.name == "boat") { MoveBoat(); } } /** * 人的移动分四种情况 * 上船:左岸上左船,右岸上右船 * 上岸:左船上左岸、右船上右岸 * 其他情况人都不会动 */ void MovePeople(GameObject people) { int shoreNum, seatNum;//0为左,1为右,作为参数传给动作管理员 if (people.transform.parent == boat_obj.transform.parent && (boat[0] == null || boat[1] == null))//物体和船都在同一个岸且船有空位才能上船 { seatNum = boat[0] == null ? 0 : 1; if (people.transform.parent == leftShore_obj.transform) { shoreNum = 0; for (int i = 0; i < LeftObjList.Count; i++) { if (people.name == LeftObjList[i].name) { actionManager.getOnBoat(people, shoreNum, seatNum);//让动作管理员执行上船动作 LeftObjList.Remove(LeftObjList[i]); } } } else { shoreNum = 1; for (int i = 0; i < RightObjList.Count; i++) { if (people.name == RightObjList[i].name) { actionManager.getOnBoat(people, shoreNum, seatNum); RightObjList.Remove(RightObjList[i]); } } } boat[seatNum] = people; people.transform.parent = boat_obj.transform; } else if (people.transform.parent == boat_obj.transform)//若物体在船上就选择上岸 { shoreNum = boat_obj.transform.parent == leftShore_obj.transform ? 0 : 1; seatNum = (boat[0] != null && boat[0].name == people.name) ? 0 : 1; actionManager.getOffBoat(people, shoreNum);//动作管理员去执行上岸动作 boat[seatNum] = null; if(shoreNum == 0) { people.transform.parent = leftShore_obj.transform; LeftObjList.Add(people); } else { people.transform.parent = rightShore_obj.transform; RightObjList.Add(people); } } } /** * 若船上有人,则船可驶向对岸 * 让动作管理器去负责移动小船 */ void MoveBoat() { if(boat[0]!=null || boat[1] != null) { actionManager.moveBoat(boat_obj); boat_obj.transform.parent = boat_obj.transform.parent == leftShore_obj.transform ? rightShore_obj.transform : leftShore_obj.transform; } } /** * 判断游戏输赢状态并告知导演 * 计算分三步,先统计左岸的牧师、恶魔 * 然后统计右岸的牧师、恶魔 * 再判断船在左岸还是右岸,把船上恶魔与牧师加到对应岸的数量上 * 若某一岸恶魔 > 牧师,则游戏失败 * 若全到右岸,则胜利 * 否则游戏继续 */ public void Judge() { int left_d = 0, left_p = 0, right_d = 0, right_p = 0; foreach (GameObject element in LeftObjList) { if (element.tag == "Priest") left_p++; if (element.tag == "Devil") left_d++; } foreach (GameObject element in RightObjList) { if (element.tag == "Priest") right_p++; if (element.tag == "Devil") right_d++; } for (int i = 0; i < 2; i++) { if (boat[i] != null && boat_obj.transform.parent == leftShore_obj.transform)//船在左岸 { if (boat[i].tag == "Priest") left_p++; else left_d++; } if (boat[i] != null && boat_obj.transform.parent == rightShore_obj.transform)//船在右岸 { if (boat[i].tag == "Priest") right_p++; else right_d++; } } if ((left_d > left_p && left_p != 0) || (right_d > right_p && right_p != 0) || SSDirector.getInstance().leaveSeconds == 0) { SSDirector.getInstance().state = State.LOSE; } else if (right_d == right_p && right_d == 3)//全过河,赢了 { SSDirector.getInstance().state = State.WIN; } } public void Pause() { SSDirector.getInstance().state = State.PAUSE; } public void Resume() { SSDirector.getInstance().state = State.CONTINUE; } public void Restart() { Application.LoadLevel(Application.loadedLevelName); SSDirector.getInstance().state = State.START; } }
动作管理者 FirstSSActionManager
我把三个基本动作的类SSAction、MoveToAction、 SequenceAction也一起放到了这里,然后SSActionManager是对基本动作的管理。
再写一个FirstSSActionManager继承SSActionManager,专门实现对FirstSceneController场景里对象的动作设计,这样场记可以更方便地让FirstActonManager执行动作。
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public interface ISSActionCallback { void actionDone(SSAction source); } /** * SSAction是动作的基类,保存要移动的游戏对象属性 */ public class SSAction : ScriptableObject { public bool enable = true; public bool destroy = false; public GameObject gameObject { get; set; } public Transform transform { get; set; } public ISSActionCallback callback { get; set; } public virtual void Start() { throw new System.NotImplementedException(); } public virtual void Update() { throw new System.NotImplementedException(); } } /** * MoveToAction是SSAction的一个子类,代表平移的动作。 */ public class MoveToAction : SSAction { public Vector3 target; public float speed; private MoveToAction() { } public static MoveToAction getAction(Vector3 target, float speed) { MoveToAction action = ScriptableObject.CreateInstance<MoveToAction>(); action.target = target; action.speed = speed; return action; } public override void Update() { this.transform.position = Vector3.MoveTowards(this.transform.position, target, speed * Time.deltaTime); if (this.transform.position == target) { this.destroy = true; this.callback.actionDone(this); } } public override void Start() {} } /** * SequenceAction是SSAction的另一个子类,它代表一系列组合动作。 */ public class SequenceAction : SSAction, ISSActionCallback { public List<SSAction> sequence; public int repeat = -1; //-1表示无限循环,0表示只执行一遍,repeat> 0 表示重复repeat遍 public int currentAction = 0;//当前动作列表里,执行到的动作序号 public static SequenceAction getAction(int repeat, int currentActionIndex, List<SSAction> sequence) { SequenceAction action = ScriptableObject.CreateInstance<SequenceAction>(); action.sequence = sequence; action.repeat = repeat; action.currentAction = currentActionIndex; return action; } public override void Update() { if (sequence.Count == 0) return; if (currentAction < sequence.Count) { sequence[currentAction].Update(); } } public void actionDone(SSAction source) { source.destroy = false; this.currentAction++; if (this.currentAction >= sequence.Count) { this.currentAction = 0; if (repeat > 0) repeat--; if (repeat == 0) { this.destroy = true; this.callback.actionDone(this); } } } public override void Start() { foreach (SSAction action in sequence) { action.gameObject = this.gameObject; action.transform = this.transform; action.callback = this; action.Start(); } } void OnDestroy() { foreach (SSAction action in sequence) { DestroyObject(action); } } } /** * SSActionManager统筹上面三个动作类 */ public class SSActionManager : MonoBehaviour { private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>(); private List<SSAction> waitingToAdd = new List<SSAction>(); private List<int> watingToDelete = new List<int>(); protected void Update() { foreach (SSAction ac in waitingToAdd) { actions[ac.GetInstanceID()] = ac; } waitingToAdd.Clear(); foreach (KeyValuePair<int, SSAction> kv in actions) { SSAction ac = kv.Value; if (ac.destroy) { watingToDelete.Add(ac.GetInstanceID()); } else if (ac.enable) { ac.Update(); } } foreach (int key in watingToDelete) { SSAction ac = actions[key]; actions.Remove(key); DestroyObject(ac); } watingToDelete.Clear(); } public void RunAction(GameObject gameObject, SSAction action, ISSActionCallback whoToNotify) { action.gameObject = gameObject; action.transform = gameObject.transform; action.callback = whoToNotify; waitingToAdd.Add(action); action.Start(); } } /** * 专门设计动作并执行 */ public class FirstSSActionManager : SSActionManager, ISSActionCallback { public FirstSceneController scene; public MoveToAction action1, action2; public SequenceAction saction; float speed = 30f; /** * 为小船设置水平移动动作 */ public void moveBoat(GameObject boat) { action1 = MoveToAction.getAction((boat.transform.position == new Vector3(4, 0, 0)? new Vector3(-4, 0, 0) : new Vector3(4, 0, 0)), speed); this.RunAction(boat, action1, this); } /** * 人的上船动作 * 为人设置水平和垂直两个分解动作,然后组合起来执行 */ public void getOnBoat(GameObject people, int shore, int seat) { if (shore == 0 && seat == 0) { action1 = MoveToAction.getAction(new Vector3(-5f, 2.7f, 0), speed);//右移 action2 = MoveToAction.getAction(new Vector3(-5f, 1.2f, 0), speed);//下移 } else if(shore==0 && seat == 1) { action1 = MoveToAction.getAction(new Vector3(-3f, 2.7f, 0), speed); action2 = MoveToAction.getAction(new Vector3(-3f, 1.2f, 0), speed); } else if (shore == 1 && seat == 0) { action1 = MoveToAction.getAction(new Vector3(3f, 2.7f, 0), speed); action2 = MoveToAction.getAction(new Vector3(3f, 1.2f, 0), speed); } else if (shore == 1 && seat == 1) { action1 = MoveToAction.getAction(new Vector3(5f, 2.7f, 0), speed); action2 = MoveToAction.getAction(new Vector3(5f, 1.2f, 0), speed); } SequenceAction saction = SequenceAction.getAction(0, 0, new List<SSAction> { action1, action2 });//组合动作 this.RunAction(people, saction, this); } /** * 人的下船动作 */ public void getOffBoat(GameObject people, int shoreNum) { action1 = MoveToAction.getAction(new Vector3(people.transform.position.x, 2.7f, 0), speed);//上移 if (shoreNum == 0) action2 = MoveToAction.getAction(new Vector3(-16f + 1.5f * Convert.ToInt32(people.name), 2.7f, 0), speed);//左移 else action2 = MoveToAction.getAction(new Vector3(16f - 1.5f * Convert.ToInt32(people.name), 2.7f, 0), speed);//右移 SequenceAction saction = SequenceAction.getAction(0, 0, new List < SSAction >{ action1, action2});//组合动作 this.RunAction(people, saction, this); } protected void Start() { scene = (FirstSceneController)SSDirector.getInstance().currentScenceController; scene.actionManager = this; } protected new void Update() { base.Update(); } public void actionDone(SSAction source) { Debug.Log("Done"); } }
对类之间的操作还不熟练,自己设计得很粗糙,有出错的地方请大家指正TAT。
相关文章推荐
- Unity3D学习(5)——牧师与魔鬼游戏
- 组合模式设计购物车价格计算实例(仅供学习使用)
- 牧师与恶魔过河游戏
- 迭代器模式与组合模式(二) —— Head First设计模式学习
- C#面向对象设计模式学习笔记(8) - Composite 组合模式(结构型模式)
- (Head First 设计模式)学习笔记(2) --观察者模式(气象站实例)
- (Head First 设计模式)学习笔记(2) --观察者模式(气象站实例)
- 组合模式(composite)学习
- 大话设计模式学习(十四)——组合模式
- 设计模式学习(七)----迭代和组合
- WCF学习之:实例上下文模式和并发模式的性能影响
- Net设计模式实例之组合模式(Composite Pattern)(2)
- 关于Singleton设计模式的计数器代码实例(拷贝粘贴即可学习)
- Net设计模式实例之组合模式(Composite Pattern)(3)
- net设计模式实例之组合模式(Composite Pattern)
- C# 设计模式学习三 Abstract Factory 抽象工厂(实例)
- 懒羊学习《深入浅出设计模式》C#代码手札之组合模式
- j2me手机游戏学习实例50个
- (Head First 设计模式)学习笔记(3) --装饰者模式(StarBuzz咖啡店实例)
- Net设计模式实例之组合模式(Composite Pattern)(1) 推荐