【Unity/QFramework】QFramework学习笔记
文章目录
- 基础流程
- 示例程序【PointGame】
- Architecture
- Storage
- PlayerPrefsStorage : IStorage
- Model
- GameModel : AbstractModel, IGameModel
- Controller
- Game : MonoBehaviour, IController
- Enemy : MonoBehaviour, IController
- MissArea : MonoBehaviour, IController
- Command
- StartGameCommand : AbstractCommand
- KillEnemyCommand : AbstractCommand
- MissCommand : AbstractCommand
- BuyLifeCommand : AbstractCommand
- Event
- System
- ScoreSystem : AbstractSystem, IScoreSystem
- CountDownSystem : AbstractSystem, ICountDownSystem
- AchievementSystem : AbstractSystem, IAchievementSystem
- UI
QFramework是一个轻量级的游戏框架,并且整合了许多有用工具。源码只有800行不到,这既意味着其源代码足够凝练,不会束手束脚,也意味着在制作自己的游戏的时候,需要扩展和增加一下框架的功能。
QFramework整体基于MVC架构,实现了数据,操作,视图的解耦分层。在中间穿插了Command,Event,Architecture ,System这样的类作为扩展。
基础流程
- 全局单例:Architecture类是一个抽象类,使用时使用泛型继承,并重载其Init方法,在里面进行各个模块的注册(System,Model,Utility等)。这里即体现了IOC的思想。
- 底层存储:负责进行具体存储的存储类继承自IStorage接口,这是为了方便后续扩展不同的存储类,即面向接口编程(依赖倒置原则)。IStorage接口继承了IUtility接口,这是因为注册和获取时都需要通过RegisterUtility和GetUtility函数。
- 上层存储(Model):实现IModel接口,这一层是MVC层中的Model层,Storage层就是从这一层中分离出来的,目的是能够应用各种不同的存储方式和策略。该层会被Command,Controller等层广泛获取,用于存储和修改全局变量。
- 控制器(Controller):实现IController接口,该层负责发送Command,触发即使用Monobehavior的函数,如玩家输入,鼠标事件,UI按钮等,具体执行的操作在Command中定义,Event也在Command中进行触发。除此之外该层还负责绑定Event事件到视图更新上。
- 视图(View):该层通过绑定Event事件来触发更新
- 命令(Command):该类是Controller层发出的,每个Command都继承自AbstractCommand,并重写OnExecute来执行业务逻辑,并按条件触发不同的事件。
- 事件(Event):事件是一个什么也不继承的纯结构体,使用扩展泛型方法RegisterEvent进行绑定,CancelEvent进行取消绑定,或者使用链式编程在绑定时即使用CancelOnDestroy(gameObject)简化这一过程。
- 系统(System):系统继承自具体的系统接口IxxxSystem和AbstractSystem,这些具体的系统接口继承自ISystem,系统层用于扩展不同的业务功能,比如输入,成就,任务,计时等等高级逻辑。系统层基本基于Event事件触发。
示例程序【PointGame】
该示例程序可以在QFramework的官方Github页面找到,链接如下
github.com/onehundredlines/FrameworkDesign
Architecture
PointGame : Architecture
该层负责注册模块:
- AchievementSystem : AbstractSystem, IAchievementSystem
- CountDownSystem : AbstractSystem, ICountDownSystem
- ScoreSystem : AbstractSystem, IScoreSystem
- GameModel : AbstractModel, IGameModel
- PlayerPrefsStorage : IStorage
Storage
PlayerPrefsStorage : IStorage
该层负责具体的存储策略,Demo使用最简单的PlayerPrefs的方法存储数据,并且只提供了按Key读取和存储int类型的接口
Model
GameModel : AbstractModel, IGameModel
该层负责提供全局变量,负责在数据变化时将需要持久化存储的数据通过IStorage进行存储。
内部数据不需要存储的包括:KillCount,Score,HighScore
需要存储的包括:BestScore,Life,Gold
Controller
该层主要负责发送Command,接收Event。
Game : MonoBehaviour, IController
该类负责管理点击的方块的整体显隐和初始化每个方块的显隐状态,基本就相当于游戏场景的开始流程。控制显隐的时机通过注册对应的Event事件来触发。
Enemy : MonoBehaviour, IController
使用OnMouseDown,隐藏自身并发送一个KillEnemyCommand,很合理。
MissArea : MonoBehaviour, IController
使用OnMouseDown,隐藏自身并发送一个MissCommand,很合理。
Command
该层是从Controller层级中抽离出来的具体业务逻辑,其会对Model层进行更改,并发送Event事件供System,View,Controller层响应
StartGameCommand : AbstractCommand
清空GameModel中的KillCount和Score,发送OnGameStartEvent事件。
KillEnemyCommand : AbstractCommand
增加GameModel中的KillCount。并有30%概率为GameModel中的Gold随机增加1~3
发送OnKillEnemyEvent事件,并在KillCount大于等于9时发生OnGamePassEvent事件。
MissCommand : AbstractCommand
减少Gamemodel中的Life值,若该值小于等于0,发送OnMissEvent。
BuyLifeCommand : AbstractCommand
减少Gold值,增加Life值,汇率1:1
Event
该层可被Controller,View,System层进行监听,该Demo所有Event都为struct并且都没有参数,仅做触发使用,并都在上述Command中提及。
System
该层负责较为高级的业务逻辑,具体到该Demo中,包括得分系统,计时系统,成就系统。
ScoreSystem : AbstractSystem, IScoreSystem
重写OnInit(),监听OnKillEnemyEvent和OnMissEvent,增加和减少GameModel的Score值。
监听OnGamePassEvent,更新GameModel的BestScore
CountDownSystem : AbstractSystem, ICountDownSystem
重写OnInit(),监听OnGameStartEvent和OnGamePassEvent,用于设置mStarted这个Flag值以及记录开始游戏时的时间。
成员函数Update,内部在mStarted为true的情况下循环判断游戏时长是否超过十秒,超过则发送OnCountDownEndEvent事件并将mStarted设为false。该Update依赖于Monobehavior的生命周期函数,Demo里直接在UI部分的GamePanel中的Update中调用此函数,有那么一丝丝的简单粗暴。
AchievementSystem : AbstractSystem, IAchievementSystem
这个成就系统是以局为单位的,每局都会重置,也没有持久化存储。就是通过定义了一个AchievementItem类,内部有名称,是否解锁,检查是否完成的函数委托(Func)。
在Init时动态添加到成就列表中,成就条件基本就是把Model中的值掏出来判断一下是否达到,或者绑定事件记录一些flag辅助检查成就。
监听OnGamePassEvent,异步等待0.1f秒,然后遍历成就列表确认哪些满足条件且未解锁过,将其设置为解锁并打印出来
UI
这部分的类都是Monobehavior类,因为需要对UI进行初始化,找到某些按钮组件等,需要借助transform,并且没有使用框架,因此写的有点抽象,但是it just works。
总的来说这一块和Controller没什么不同,寻找按钮然后绑定发送Command,然后监听Event寻找文本进行更新(Demo里有的通过Event有的通过BindableProperty的方式来监听更新,都行)