使用泛型+继承重构代码
新建Framework文件夹,在里面新建Event文件夹,再在里面新建BaseEvent
脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| using System;
namespace FrameWorkDesign { public class BaseEvent<T> where T : BaseEvent<T> { private static Action m_OnEvent;
public static void Register(Action onEvent) { m_OnEvent += onEvent; }
public static void Unregister(Action onEvent) { m_OnEvent -= onEvent; }
public static void Trigger() { m_OnEvent?.Invoke(); } } }
|
然后将GameStartEvent
和GameEndEvent
继承BaseEvent
即可,并将之前的代码删除
1 2 3 4 5 6 7
| namespace FrameWorkDesign.Example { public class GameStartEvent : BaseEvent<GameStartEvent> { } }
|
1 2 3 4 5 6 7
| namespace FrameWorkDesign.Example { public class GameEndEvent : BaseEvent<GameEndEvent> { } }
|
再运行游戏,逻辑并没有改变。
继承解决拓展问题,那么泛型是为了什么呢,如果不使用泛型
1 2 3
| public class BaseEvent{...} public class GameStartEvent : BaseEvent{} public class GameEndEvent : BaseEvent{}
|
BaseEvent
里面的变量都成了唯一的了,我们点击开始游戏,直接触发了游戏通关的事件,所以泛型就是解决实现代码一致,类不一致的问题,注意继承泛型基类创建的是两个新的类,不是同一个类的两个实例,一定要记好。
表现与数据分离
目前游戏的通关逻辑是点击9次小方块,这个通关逻辑放在了Enemy脚本中
1 2 3 4 5 6 7 8 9 10
| static int count; private void OnMouseDown() { count++; if(count == 9) { GameEndEvent.Trigger(); } Destroy(gameObject); }
|
在未来,游戏要加上分数、最高分、金币、拥有的道具等功能,这些功能所需的数据在大多数情况下需要在多个场景、界面、游戏物体之间共享,这些数据不但需要在空间上共享,还需要在时间上共享(需要储存起来),所以在这里,开发者的共识就是把数据的部分剥离出来,单独放在一个地方进行维护,而比较常见的开发架构就是使用MVC架构,我们先只用其中一个概念,Model
Model就是管理数据、存储数据,管理数据就是可以通过Model对象或类可以对数据进行增删改查,有的时候还可以进行存储。
在Scripts文件夹内新建Model文件夹,并在其中新建GameModel
脚本。

1 2 3 4 5 6 7 8 9 10
| namespace FrameWorkDesign.Example { public class GameModel { public static int KillCount = 0; public static int Gold = 0; public static int Score = 0; public static int BestScore = 0; } }
|
在Enemy
脚本中引用这个Model
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| using UnityEngine;
namespace FrameWorkDesign.Example { public class Enemy : MonoBehaviour { private void OnMouseDown() { GameModel.KillCount++; if(GameModel.KillCount == 9) { GameEndEvent.Trigger(); } Destroy(gameObject); } } }
|

单一职责
到目前,游戏的通关逻辑判断还是放在的Enemy里,作为游戏中的一个基础小单位,将通关逻辑放在这里面是不符合设计的,通关逻辑属于游戏中的规则,规则是由玩家认识并判断,按照规范设计,游戏的规则应该放在一些大类中,这里我们把它放在GameRoot
脚本里
Enemy每次被点击的时候,需要告诉Game类,Enemy被点击了,而Game和Enemy隔着一个Enemies,而且Game和Enemy是1对10的关系,这是一种子节点向父节点通信的例子,这种情况下可以用委托或者事件来实现,但是用委托不现实,如果想用委托Game或者Enemies就需要维护一个Enemy数组(???),所以用事件是最佳选择
增加一个事件,名字叫做KilledOneEnemyEvent
1 2 3 4 5 6 7
| namespace FrameWorkDesign.Example { public class KilledOneEnemyEvent : BaseEvent<KilledOneEnemyEvent> { } }
|
当Enemy被点击时,触发这个事件,修改Enemy
脚本如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| using UnityEngine;
namespace FrameWorkDesign.Example { public class Enemy : MonoBehaviour { private void OnMouseDown() { KilledOneEnemyEvent.Trigger(); Destroy(gameObject); } } }
|
在GameRoot
脚本中注册(监听)这个事件,将通关逻辑加入进去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| using UnityEngine;
namespace FrameWorkDesign.Example { public class GameRoot : MonoBehaviour { private void Awake() { GameStartEvent.Register(OnGameStart); KilledOneEnemyEvent.Register(OnKilledOne); }
private void OnKilledOne() { GameModel.KillCount++; if (GameModel.KillCount == 9) { GameEndEvent.Trigger(); } }
private void OnGameStart() { transform.Find("Enemies").gameObject.SetActive(true); }
void OnDestroy() { GameStartEvent.Unregister(OnGameStart); KilledOneEnemyEvent.Unregister(OnKilledOne); } } }
|
