游戏开发中游戏对象分组的一些经验分享
分享一下我自己在开发游戏过程中,对游戏对象分组的一些实践经验。
先看需求,再谈封装
首先说明:一切封装都要看情况,看你是否有大量需求。这个分享的内容从我从自己实践中遇到的,对于一些有复杂分组需求的情况才要做封装使用。
如果只是偶尔出现一两个这种需求,你完全可以想怎么写就怎么写,不需要特意搞什么封装。封装是为了解决重复问题,不是为了封装而封装。
配置分组
分组,我在写类时通常命名为 Group。它不是什么复杂概念,本质上就是对数组的一层封装。
最开始,这种分组操作是用在配置上的。
比如一个手游卡牌游戏,里面有大量卡牌配置,经常会有某个功能需要从中取出部分配置。
例如,抽卡功能有 a% 概率抽出 SR。这时就应该把 SR 专门分一个卡池,而不是每次都遍历全部配置再筛选。
配置分组其实比较简单,因为配置是静态的,不会动态改变。不过即使是这么简单的系统,有时也没人去处理。我见过一些项目里堆了不少“屎山代码”,每次获取配置都是一串手写遍历,实在让人头大。
游戏对象分组
更复杂一点的是游戏对象的分组。
例如一个卡牌游戏,如果有一张卡牌,它的效果是让场上所有白卡攻击力 +1。这种时候,你需要筛选获取场上卡牌,再筛选获取白卡。
你可以设计一个数组,存下场上所有白卡。但问题在于:每次场上卡牌登场、离场,你都需要维护和更新这个数组。相关代码写起来会很麻烦,尤其是这类数组多的时候。
这类数组,我的封装方式是这样的:
首先,对象需要具有数据变更时的监听响应,这是我已经封装好的。例如:
card.zone = "field" // 表示卡牌在场上
当卡牌离场,例如进入墓地时,我会直接修改:
card.zone = "grave"
有了这个基础之后,我建立了数据对象 Group 类和管理器。
管理器中默认会创建一个键值为 all 的 Group,所有数据对象都会自动加入 all。然后开发者可以编写 Group 的子类。
例如 CardGroup:
// 示意
CardGroup:
1. 设定源分组为 all:
source_group = 'all'
2. 设置筛选方法:
function(dataObject) {
return dataObject.obj_type == "card"
}
3. 设置监听依赖属性键值:
'obj_type'
这样就搞定了“所有卡牌”这个分组。所有加入到 all 的对象,会自动根据 obj_type 判断是否加入 CardGroup。
当数据对象的 obj_type 变更时,也会自动检测判断是否要从 CardGroup 中移除。当然,这个例子里的 obj_type 在实际场景中一般不会变,但后面的 zone 在实际场景中就会变。
再写一个 FieldCardGroup,表示场上的卡牌:
// 示意
FieldCardGroup:
1. 设定源分组为 CardGroup:
source_group = "card_group"
2. 设置筛选方法:
function(card) {
return card.zone == "field"
}
3. 设置监听依赖属性键值:
'zone'
组合分组:筛选、合并、交集
实际使用时,我发现光靠筛选不太够用,于是又增加了一些合并、交集类型的自动组合方式。
例如现在有了 FieldCardGroup 和 GraveCardGroup。
然后有一些卡牌效果,会对场上和墓地中的卡起效。游戏王中常见这类效果,例如根据场上 + 墓地中龙族怪物的数量增加攻击力。
所以我再写一个 FieldAndGraveCardGroup:
// 示意
FieldAndGraveCardGroup:
1. 设置源分组为 FieldCardGroup 和 GraveCardGroup:
source_groups = ['field_card_group', 'grave_card_group']
2. 设置 group 类型为 OR:
group_type = OR
// 上面那种筛选类型为 FILTER
这就行了。这个 Group 会自动将上面两个 Group 中的对象都放进自己体内。
分组统计与响应式接口
这是分组的处理方式。然后还可以为分组加上一些常用接口方法,例如统计某些数据。
实际场景例如:这张卡会根据自己场上 + 墓地中的龙族怪物的等级,每一级提升 3 点攻击力。
这个接口,不仅要做成一个单纯获取数据的接口,还要考虑做成响应式的。这样在写逻辑时,只要设置好这个统计值,再监听统计值的变化即可:
- 对“己方场上与墓地中的龙族怪物分组”设置一个统计数值,键值为
level。 - 使用逻辑中,对这个分组的统计
level进行监听。
除了统计外,还可以封装:
- 分组中是否存在某类数据;
- 某个对象的某项数据是否达到某个阈值;
- 其他类似的常用查询接口。
小结
以上属于对逻辑比较复杂的游戏中的封装经验。有实际需求的可以看一下,参考一下。封装起来其实没有太大难度,关键还是看需求是否足够多、是否值得抽象。