游戏逻辑系统化
1.游戏逻辑的目的
实现策划的设计文档
构建游戏系统
构建游戏玩法
2.什么是游戏逻辑
游戏逻辑是作为一个游戏项目里面最为复杂的系统之一,需要编写者在一开始的时候就尽可能的通过经验来构建一个比较完善并且扩展性强的逻辑架构,然后随后不断的进行迭代改进,完善整个系统。
3.游戏逻辑的作用
如果一款游戏对性能有很严格的要求,对应的游戏逻辑就必须要满足良好的运行性能需求,不能崩溃,有很好的拓展性,但是由于初学者时间周期紧张和基础的不扎实,所以不太会花过多的时间考虑架构与设计。
一个游戏根据功能可以划分为多个不同的模块,如金钱、背包、装备、技能、任务、成就等。按照软件工程的思想,我们希望分而治之单独实现不同的模块,再将这些模块组合在一起成为一份完整的游戏。但现实是残酷的,不同模块之间往往有千丝万缕的联系,比如购买背包物品会需要扣金币、打一个副本会完成任务,完成任务又会奖励金币和物品,金币的增加又导致一个成就达成。于是我们虽然在不同的类或不同的文件中来实现各个模块,却免不了模块间的交叉引用和互相调用,最后混杂不堪,任何一点小修改都可以导致牵一发而动全身。
为了后面说明方便,我们考虑这样一个小型游戏系统:总共有3个模块,分别是金钱、背包、任务。购买背包物品需要消耗金币,卖出背包物品可得到金币,金币增加到一定数额后会导致某个任务的状态变为完成,完成任务可获得物品和金币。这3个模块的调用关系如图。
首先我们把模块的数据和逻辑分离,借鉴经典的MVC模式,数据部分叫作Model,逻辑部分叫作Controller。如此一来,游戏功能部分就被划分出来了两个不同的层次,Controller处于较高的层次上,可以引用一个或者多个Model。Model层专心处理数据,对上层无感知。每个Model都是完全独立的模块,不引用任何Controller或Model,不依赖于其他任何对象,可以单拿出来进行单元测试。
对于我们的例子,每个模块提供的接口列举如下:
BagModel:获取物品数量,增加物品,扣除物品
MoneyModel:获取金币数量,增加金币,扣除金币
TaskModel:增加任务,删除任务,标记任务为完成
BagController:购买物品,卖出物品
TaskController:完成任务
购买或卖出物品时,由BagController进行或操作校验,随后调用BagModel和MoneyModel完成数据修改。完成任务时,由TaskController调用各个模块。
现在唯一的问题是,既然MoneyModel不引用其他模块,那么在金币增加时如何告知任务模块去完成任务呢?这里我们需要引入一个管理依赖的利器:观察者模式。
具体使用方式是把Model实现为一个Subject,对某个Model的数据变化感兴趣的Controller实现为对应的Observer。我们的例子中,MoneyModel是Subject,在金币数量变化时通知所有已注册的Observer;TaskController是MoneyModel的一个Observer,在初始化时向MoneyModel注册。
注意图中由MoneyModel指向TaskController的虚线箭头,代表MoneyModel数据变化时会去通知TaskController,用虚线是因为MoneyModel并不依赖于TaskController(只依赖于Observer接口)。同样BagModel也可以提供背包物品变化的Subject,如果新加一个任务是要求某物品的数量达某个值,那么TaskController可向BagModel注册,这样在物品变化时就能得到通知了,图中也画出了这条虚线。
对观察者模式不熟悉的读者朋友可以自行查阅资料, 本文的重点并不是介绍设计模式。这里简单提示一下观察者模式的精髓:当某模块调用其他模块时就产生了依赖,这时可以不直接去调用,而是转而实现一个机制,这个机制就是让其他模块告诉自己他们需要被调用。最后调用的流程没变,变化的是依赖关系。
在客户端情况要更复杂一些,实际上加入UI后,我们的模块设计就成经典的MVC,这也是我们为什么把数据模块和逻辑模块分别叫Model和Controller的原因。
这里只画出了背包模块。这里的System API指与游戏运行平台相关的一些接口,可能是操作系统API、引擎API、图形库API等等。View模块和Model模块地位相当,只处理显示而不管游戏功能,需要显示的数据都是由Controller提供的。对于能输入的View同样采用观察者模式,点击等事件发生时通知其他模块(而不是直接调用),注意图中由BagView指向BagController的虚线箭头。