使用泛型+继承重构代码

新建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>//T被BaseEvent约束,规定了子Event类必须以BaseEvent为基类,在本例中,此约束可写可不写
{
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();
}
}
}

然后将GameStartEventGameEndEvent继承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);//
}
}
}

更新引用关系