纸上设计(二)点对点错计分实现
上一节完成了最佳分数功能,这一节完成点对点错计分功能,暂不实现倒计时计分
功能
规则
实现
最佳分数
存储并记录最佳分数
√
倒计时
游戏倒计时10秒
计分
点错一次扣5分,点对一次得10分,结束时倒计时每剩余1秒得10分
金币
每次点击正确的方块,有一定概率获得1~3金币
商店
游戏开始前可以在商店购买,能够抵消点击错误的次数
成就
百分成就、手残成就(分数为负数)、零失误成就、
重开
游戏结束后回到首页再来一次
点击计分纸上设计在游戏中添加一个背景墙,点击到方块则加分,点击到背景墙则减分
我们在Command中执行计分,点对的计分操作可以写在之前实现过的KillEnemyCommand里,点错的计分操作我们新实现一个MissCommand
计分的时候肯定是修改GameModel里面的Score,我们可以想到的有两种计分方法:
一种是直接在KillEnemyCommand和MissCommand修改GameModel
另一种是声明两个事件,在ScoreSystem里面监听这两个事件,在KillEnemyCommand发送Kill ...
纸上设计(一)《点点点》功能规划与最佳分数实现
在这一节,我们通过一些纸上设计,来说明框架的最佳实践方法。
《点点点》功能规划尝试把《点点点》这个游戏项目用纸上设计的方式给完善掉。
目前《点点点》我们预留了几个数据,代码如下
GameModel脚本
12345678910111213141516171819202122namespace FrameWorkDesign.Example{ public interface IGameModel : IModel { BindableProperty<int> KillCount { get; } BindableProperty<int> Gold { get; } BindableProperty<int> Score { get; } BindableProperty<int> BestScore { get; } } public class G ...
结构演化小结
架构演化顺序复盘对象之间的交互首先按照一次性项目的方法快速完成了《点点点》项目的制作。
分析了《点点点》项目后发现有三个问题,总体来说其实是一个问题:
对象之间的引用无规则
针对这一点,我们先使用树结构来整理了一下场景结构(UI、Game),然后引入了对象之间的交互这个概念。
对象之间的交互有三种,分别是:
方法
委托
事件
然后也简单说了下模块化的三种常规方式
单例
IOC
分层
接着,我们通过对象之间的三种交互方式,逐个修复了《点点点》遇到的问题。
最后总结得出
自底向上用事件或者委托
自顶向下用方法
在这个过程中,我们积累了一个工具类,即:Event基类。
再接着,我们学习了一些理论,包括:
表现和数据要分离
交互逻辑与表现逻辑
然后分别开始对《点点点》以及《CounterApp》的表现逻辑和交互逻辑进行优化。
表现逻辑优化时引入了一个工具类BindableProperty,它是数据+事件的组合。
交互逻辑优化时引入了Command模式,它是大部分MVC框架的选择。
Command是对象之间交互的第四种方式,不过它适用于交互逻辑。
到此,对象之间的交互这个话题 ...
增加事件的使用规则
到目前为止,我们使用接口+静态拓展的方式为每一个层级的接口增加了使用规则。还有一个规则设置还没完成,那就是事件的使用规则。
我们目前的事件基类如下
123456789101112131415161718192021222324using 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; } ...
使用拓展方法和接口阉割创建规则
在这一节,我们将通过接口阉割和拓展方法来给架构添加Rule(规则),防止各个层级都能随便互相调用
目前,Model层可以发送Command,因为调用GetArchitecture()之后便能够访问很多我们在IArchitecture接口中定义的方法,那么在Model层必然可以调用SendCommand方法。
阉割AbstractModelCommand层是表现层与底层系统层交互的方式,而在Model层使用Command是不合理的。在技术上讲,Model层是可以发送Command的,所以在实际开发中,尤其的多人开发,可能会有人写出不符合规范的代码,所以我们要限制Model层,让Model层不能发送Command。
首先,我们先使用接口阉割中的“接口——抽象类——实现类”的方式,把GetArchitecture方法和SetArchitecture方法从AbstractModel里面阉割掉
123456789101112131415161718192021222324252627namespace FrameWorkDesign{ public interface IMode ...
IUtility实现与ICommand完善
上一节我们实现了IController接口,并且拆分了IBelongToArchitecture引入ICanSetArchitecture从而重构了IModel和ISystem接口。
在这一堂课,我们接着完成剩下的接口,IUtility和ICommand
实现IUtility首先实现IUtility,Utility层和Model、System不一样,它作为一个工具层,我们使用时只能获取实例并使用它,而前面的两个需要有自己的初始化方法,需要能获取到其他层,而Utility不用获取其他层,所以不用继承IBelongToArchitecture和ICanSetArchitecture接口。
在FrameworkDesign——Framework——Architecture文件夹中新建IUtility脚本
1234567namespace FrameWorkDesign{ public interface IUtility { }}
所以目前我们的IUtility脚本仅仅是为了提供一层抽象,我们修改Architecture脚本中声明 ...
表现层IController接口定义与实现
目前已经定义的接口如下
ICommand
ISystem
IModel
我们还需要两个接口,一个是表现层的IController,一个是工具层的IUtility。并且目前的ICommand的接口还不够完善,我们需要Command接口能通过单例的方式获取Architecture对象。
实现IController时出现的问题IController与MVC中的Controller是一个意思。
表现层需要向底层发送Command,监听底层系统的事件,还有一个功能,就是查询Model或者System层的一些数据,这个功能我们用IController来定义。
在FrameworkDesign——Framework——Architecture中新建IController脚本
123456namespace FrameWorkDesign { public interface IController : IBelongToArchitecture { }}
由于表现层的对象时常进行创建和销毁,所以表现层的对象注册到Arch ...
引入System层
先梳理一下CounterApp的架构草图
目前从这张图中,我们可以将整个App分为三个层级:
表现层:即ViewControllor或者MonoBehaviour脚本等
Model层:管理数据,提供数据的增删改查
Utility层:工具层,提供一些必备的基础工具,比如存储数据、网络连接、蓝牙、序列化反序列化等
目前有三个层级了,而ATAOFramework系统设计架构最终有四个层级,而第四个层级其实叫做System层,即系统层。
架构层级演化回顾我们先来回顾一下到目前为止架构层级演化的历史
最开始只有表现层代码,即ViewController代码。
然后再多个ViewController可能会出现数据需要共享的情况,所以我们主张数据和表现要分离,所以就单独把数据部分提取了出来,这部分提取出来的代码我们放在了Model层。
而一旦有了Model层,就需要考虑对象之间的交互问题,也就是ViewController对象和Model对象如何交互。此时引入了两个概念,即表现逻辑和交互逻辑,表现逻辑就是:Model——View,交互逻辑就是:View——Model
我们经过比较,发现 ...
使用接口的显式实现来“阉割”
接口的“阉割”利用C#接口的显式实现,来达到接口方法在子类“阉割”的目的。
在FrameworkDesign——Example文件夹下新建文件夹InterfaceDesign,在其中新建InterfaceDesignExample场景以及InterfaceDesignExample脚本
12345678910111213141516171819202122232425262728using UnityEngine;namespace FrameWorkDesign.Example{ public interface ICanSayHello { void SayHello(); void SayOther(); } public class InterfaceDesignExample : MonoBehaviour,ICanSayHello { public void SayHello() { Debug.Log("H ...
解决递归调用、补丁重新构造
《CounterApp》支持数据存储我们尝试在CounterApp中引入数据存储功能,思路很简单,在CounterModel构造时写入PlayerPrefs即可
修改CounterViewController内的CounterModel
123456789101112131415public class CounterModel : ICounterModel{ public CounterModel()//添加构造函数 { Count.Value = PlayerPrefs.GetInt("COUNTER_COUNT", 0); Count.OnValueChanged += count => { PlayerPrefs.SetInt("COUNTER_COUNT", count); }; } public BindableProperty<int> Count { ...