打飞碟——基础版和物理版结合
游戏简介
打飞碟游戏是一款敏捷类游戏(也可能是我手残),我将游戏规则制定如下:
- MVC模式
MVC模式在之前的有关牧师与魔鬼的博客中有详细解释过,这里沿用的是牧师与魔鬼的MVC框架,在此基础上做了一些改动,如果有兴趣可以到我的相关博客查看,这次主要解释下面两个模式。 - 工厂模式
- 为什么需要工厂模式
游戏对象的创建与销毁成本较高,所以当游戏涉及大量游戏对象的创建与销毁时,必须考虑减少销毁次数,比如这次的打飞碟游戏,或者像其他类型的射击游戏,其中子弹或者中弹对象的创建与销毁是很频繁的。 - 工厂模式的定义
工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 - 工厂模式的结构
游戏对象的创建与销毁,分别对应工厂里面的两个List,这两个List分别存放正在使用的游戏对象,已失效的游戏对象。同时保存两个List的目的是,减少游戏对象的创建与销毁的性能开销。如果已失效的List里面还有item,而这时工厂又需要完成一个新的“订单”,便可以从这些回收的item中拿出来再加工(一些逻辑处理),再返回给用户(调用方)。 - 工厂模式的优缺点
- 优点
- 一个调用者想创建一个对象,只要知道其名称就可以了。 - 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
- 屏蔽产品的具体实现,调用者只关心产品的接口。
- 例子,如果一个客户需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现。
- 缺点
- 一开始实现会比较复杂,需要用心设计一下工厂的结构
- 每增加一个产品,可能都需要对工厂进行一次较大的“整改”
- 如果工厂太大,实时保存的所有游戏对象的开销可能要大于游戏对象直接创建与销毁
- 优点
- 工厂模式的意义(以本次代码为例)
体现了面向对象设计的核心——抽象、包装、隐藏。道具工厂通过场景单实例,构建了可以被方便获取的disk类。包装了复杂的disk生产和回收逻辑,易于使用。包装了disk的产生规则,有利于应对未来游戏规则的变化,易于维护。
- 适配器模式
- 为什么需要适配器模式
就像只有一个usb接口的电脑上想要插多个usb外设,就需要通过一个usb适配器来对这些接口进行扩展。在本游戏实例中,由于我们想要继续沿用上次的基础版ActionManager,并且新增一个管理物理运动的PhysicsActionManager,这时在FirstControl里就需要同时有两个变量分别指向这两个动作管理器了。而这样会造成FirstControl需要管理多个功能相同或类似的组件,如果未来还想加多几个动作管理器,就会显得越来越臃肿,可扩展性很差。所以我们希望FirstControl里只保留一个可扩展的变量,那就是适配器! - 适配器模式的定义
在计算机编程中,适配器模式(有时候也称包装样式或者包装)将一个类的接口适配成用户所期待的。一个适配允许通常因为接口不兼容而不能在一起工作的类工作在一起,做法是将类自己的接口包裹在一个已存在的类中。 - 适配器模式的结构
- Target:目标抽象类(USB接口)
- Adapter:适配器类(USB扩展器)
- Adaptee:适配者类(鼠标、键盘、U盘)
- Client:客户类(平板电脑)
- 适配器模式的优缺点
- 优点
- 可以让任何两个没有关联的类一起运行。
- 提高了类的复用。
- 增加了类的透明度。
- 灵活性好。
- 缺点
- 过多地使用适配器,会让系统非常零乱,不易整体进行把握。
- 容易误导程序员或用户,比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现。
- 优点
- 适配器模式的意义
适配器的主要作用,就是将多个类接入同一个接口,或者转接“不兼容”接口。有动机地修改一个正常运行的系统的接口,将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
实现效果
- Round1
- Round2
- Round3
重要代码实现
- 文件框架结构
- 脚本挂载情况
ActionManager,基础动作管理器
public class ActionManager : SSActionManager { public FirstControl sceneController; public DiskFactory diskFactory; public RecordControl scoreRecorder; public Emit EmitDisk; public GameObject Disk; // Use this for initialization protected void Start() { sceneController = (FirstControl)Director.getInstance().sceneCtrl; diskFactory = sceneController.factory; scoreRecorder = sceneController.scoreRecorder; //sceneController.MyActionManager = this; sceneController.myAdapter.SetNormalAM(this); //修改的地方 } // Update is called once per frame protected new void Update() { base.Update(); } public void playDisk(int round) { //Debug.Log(diskFactory); EmitDisk = Emit.GetSSAction(); Disk = diskFactory.getDisk(round); this.AddAction(Disk, EmitDisk, this); Disk.GetComponent<DiskControl>().action = EmitDisk; } public void SSActionEvent(SSAction source) { if (!source.GameObject.GetComponent<DiskControl>().hit) scoreRecorder.miss(); diskFactory.freeDisk(source.GameObject); source.GameObject.GetComponent<DiskControl>().hit = false; } }
PhysicsManager,物理动作管理器
public class PhysicsActionManager : SSActionManager { public FirstControl sceneController; public DiskFactory diskFactory; public RecordControl scoreRecorder; public PhysicsEmit EmitDisk; public GameObject Disk; int count = 0; // Use this for initialization protected void Start() { sceneController = (FirstControl)Director.getInstance().sceneCtrl; diskFactory = sceneController.factory; scoreRecorder = sceneController.scoreRecorder; //sceneController.MyActionManager = this; sceneController.myAdapter.SetPhysicsAM(this); //修改的地方 } // Update is called once per frame protected new void Update() { base.Update(); } public void playDisk(int round) { EmitDisk = PhysicsEmit.GetSSAction(); Disk = diskFactory.getDisk(round); this.AddAction(Disk, EmitDisk, this); Disk.GetComponent<DiskControl>().action = EmitDisk; } public void SSActionEvent(SSAction source) { if (!source.GameObject.GetComponent<DiskControl>().hit) scoreRecorder.miss(); diskFactory.freeDisk(source.GameObject); source.GameObject.GetComponent<DiskControl>().hit = false; } }
ActionManagerAdapter,适配器类,封装多个动作管理器
public class ActionManagerAdapter { ActionManager normalAM; PhysicsActionManager PhysicsAM; public int mode = 0; // 0->normal, 1->physics public ActionManagerAdapter(GameObject main) { normalAM = main.AddComponent<ActionManager>(); PhysicsAM = main.AddComponent<PhysicsActionManager>(); mode = 0; } public void SwitchActionMode() { mode = 1 - mode; } public void PlayDisk(int round) { if (mode == 0) { Debug.Log("normalAM"); normalAM.playDisk(round); } else { Debug.Log("physicsAM"); PhysicsAM.playDisk(round); } } public void SetNormalAM(ActionManager am) { normalAM = am; } public void SetPhysicsAM(PhysicsActionManager pam) { PhysicsAM = pam; } public ActionManager GetNormalAM() { return normalAM; } public PhysicsActionManager GetPhysicsAM() { return PhysicsAM; } }
FirstControl,控制第一个场景的类
public class FirstControl : MonoBehaviour, ISceneControl, IUserAction { //public ActionManager MyActionManager { get; set; } public ActionManagerAdapter myAdapter; // 修改的地方 public DiskFactory factory { get; set; } public RecordControl scoreRecorder; public UserGUI user; public static float time = 0; void Awake() { Director diretor = Director.getInstance(); diretor.sceneCtrl = this; } // Use this for initialization void Start() { Begin(); } void FixedUpdate() { time += Time.deltaTime; if (time < 1) return; time = 0; // if round <= 3 and is playing, if (user.round <= 3 && user.game == 0) { PlayDisk(); user.num++; } } public void Begin() { LoadPrefabs(); //MyActionManager = gameObject.AddComponent<ActionManager>() as ActionManager; myAdapter = new ActionManagerAdapter(gameObject); // 修改的地方 scoreRecorder = gameObject.AddComponent<RecordControl>(); user = gameObject.AddComponent<UserGUI>(); user.Begin(); } public void Hit(DiskControl diskCtrl) { if (user.game == 0) { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if (Physics.Raycast(ray, out hit)) { hit.collider.gameObject.SetActive(false); Debug.Log("Hit"); hit.collider.gameObject.GetComponent<DiskControl>().hit = true; scoreRecorder.add(); } else { Debug.Log("Miss"); scoreRecorder.miss(); } } } public void PlayDisk() { //MyActionManager.playDisk(user.round); myAdapter.PlayDisk(user.round); // 修改的地方 } public void Restart() { SceneManager.LoadScene("scene"); } // 切换模式 public void SwitchMode() { Debug.Log("Switch Mode"); myAdapter.SwitchActionMode(); } public int Check() { return 0; } }
DiskFactory,飞碟工厂,管理飞碟的创建和回收
public class DiskFactory : MonoBehaviour { private static DiskFactory _instance; public FirstControl sceneControler { get; set; } GameObject diskPrefab; public DiskControl diskData; public List<GameObject> used; public List<GameObject> free; // Use this for initialization public static DiskFactory getInstance() { return _instance; } private void Awake() { if (_instance == null) { _instance = Singleton<DiskFactory>.Instance; _instance.used = new List<GameObject>(); _instance.free = new List<GameObject>(); diskPrefab = Instantiate(Resources.Load<GameObject>("Prefabs/disk"), new Vector3(40, 0, 0), Quaternion.identity); } Debug.Log("instance: " + _instance); } public void Start() { sceneControler = (FirstControl)Director.getInstance().sceneCtrl; sceneControler.factory = _instance; } public GameObject getDisk(int round) { if (sceneControler.scoreRecorder.Score >= round * 10) { if (sceneControler.user.round < 3) { sceneControler.user.round++; sceneControler.user.num = 0; sceneControler.scoreRecorder.Score = 0; } else { sceneControler.user.game = 2; } } else { if (sceneControler.user.num >= 10) { sceneControler.user.game = 1; } } GameObject newDisk; RoundControl diskOfCurrentRound = new RoundControl(round); if (free.Count == 0) // if no free disk, then create a new disk { newDisk = GameObject.Instantiate(diskPrefab) as GameObject; newDisk.AddComponent<ClickGUI>(); diskData = newDisk.AddComponent<DiskControl>(); } else // else let the first free disk be the newDisk { newDisk = free[0]; free.Remove(free[0]); newDisk.SetActive(true); Debug.Log("get from free"); } diskData = newDisk.GetComponent<DiskControl>(); diskData.color = diskOfCurrentRound.color; newDisk.transform.localScale = diskOfCurrentRound.scale * diskPrefab.transform.localScale; newDisk.GetComponent<Renderer>().material.color = diskData.color; used.Add(newDisk); return newDisk; } public void freeDisk(GameObject disk1) { used.Remove(disk1); disk1.SetActive(false); free.Add(disk1); Debug.Log("free: " + free.Count); return; } public void Restart() { used.Clear(); free.Clear(); } }