目前枪械系统的功能列表如下

  • 开枪
    • 底层系统层
    • 表现层
  • 填弹
    • 底层系统层
    • 表现层
  • 换枪
    • 底层系统层
    • 表现层
  • 捡枪
    • 底层系统层
    • 表现层
  • 弹药补给
    • 底层系统层
    • 表现层
  • 补给站补给
    • 底层系统层
    • 表现层

所有的底层系统层都完成了。

实现表现层父类

在这一节,我们改进一下架构的用法。

我们的表现层都会继承IController,实现这个接口时都要自主实现一遍返回框架入口的方法

1
2
3
4
public IArchitecture GetArchitecture()
{
return ShootingEditor2D.Interface;
}

显然这是重复的工作,在这里我们实现一个表现层父类

直接在ViewController文件夹内部新建一个ShootingEditor2DController文件

1
2
3
4
5
6
7
8
9
10
11
12
13
using UnityEngine;
using FrameWorkDesign;

namespace ShootingEditor2D
{
public abstract class ShootingEditor2DController : MonoBehaviour, IController
{
IArchitecture IBelongToArchitecture.GetArchitecture()
{
return ShootingEditor2D.Interface;
}
}
}

注意表现层父类每个项目是不一样的,都要像这样单独实现一遍。这是因为ShootingEditor2D这样的框架实现文件在每个项目是不同的。我们的AbstractSystemAbstractCommand等属于系统底层,是直接实现ICanSetArchitecture接口的,不依赖框架实现文件,而是在Architecture基类内部设置过了。记住规则:表现层——框架实现(ShootingEditor2D)——MakesureArchitecture()

之后写表现层,直接继承父类就可以,我们可以尝试修改现在的表现层

修改表现层

修改AttackPalyer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using UnityEngine;
using FrameWorkDesign;

namespace ShootingEditor2D
{
public class AttackPlayer : ShootingEditor2DController//
{

public int Hurt = 1;

private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Player"))
{
this.SendCommand(new HurtPlayerCommand(Hurt));
}
}
}
}

修改Bullet

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
34
35
36
using UnityEngine;
using FrameWorkDesign;

namespace ShootingEditor2D
{
public class Bullet : ShootingEditor2DController//
{
private Rigidbody2D m_Rigidbody;

private void Awake()
{
m_Rigidbody = GetComponent<Rigidbody2D>();

Destroy(gameObject, 5);
}

private void Start()
{
var isRight = Mathf.Sign(transform.lossyScale.x);
m_Rigidbody.velocity = Vector2.right * 10 * isRight;
}

private void OnCollisionEnter2D(Collision2D collision)
{

if (collision.gameObject.CompareTag("Enemy"))
{
Destroy(collision.gameObject);
this.SendCommand<KillEnemyCommand>();

Destroy(gameObject);
}
}

}
}

修改BulletPickItemGunGunPickItemPlayerSupplayStationUIController也是一样,这里就不列出了

枪械系统总结

在枪械系统的最开始,我们先进行了枪械系统的纸上设计。

在纸上设计的过程中,有一些需要权衡的事情,比如到底是要在纸上设计过程中把具体的实现都表达清楚还是要尽可能简化,这里笔者采用的是一个相对折中的方式,就是具体的实现用一两句话带过。

所有的枪都增加一个弹夹的子弹

在纸上设计的时候,我们可以确定下来有哪些模块(System、Model)以及如何交互(Command、Event、Query),除了这两件事情,还可以去做一个最核心的工作,就是设计数据类,比如我们开枪功能的实现,就是先设计好了数据结构(GunInfo),如下所示:

GunInfo

除了先设计数据结构的情况外,还可以等具体编码时再去考虑数据结构的方式,也就是捡枪功能的实现,如下:

捡枪功能

我们在编码是才选好了用Queue来作为缓存用的数据结构。

总之不管是先设计数据结构、还是编码时再考虑数据结构,纸上设计并没有规定标准的设计流程,最终目的是让功能快速稳定地实现的同时还要尽可能考虑周全,而考虑周全和快速、稳定实现这三个指标是互相矛盾的。

纸上设计时我们尽可能考虑周全,把命名、模块类、数据类、命令、事件等设计好,在编码时只考虑实现即可。纸上设计促进功能的周全性、稳定性和效率。

这就是通过枪械系统这个案例想给大家展示的纸上设计的方法。纸上设计易上手同时也能够设计复杂庞大的系统。

除了展示纸上设计,我们还对目前的软件架构做了进一步的改进,就是引入了Query这个概念,Query和Command是对等的概念,Command负责增删改,Query负责查。它们俩都是帮助Controller分担表现逻辑和交互逻辑,通过Query也很容易实现充血模型。