睿智的唯物主义者+40
1. 一个现实的需求
在某个游戏中,会根据每一局玩家遇到的内容决定游戏结束后的提示(tips)。例如:
- 当玩家玩得很差时(这一般说明他是个新手),显示最基础的提示
- 当玩家遇到了特定敌人时,显示这类敌人的提示
- 当玩家获得了某些道具时,显示这些道具的提示
- 当玩家使用了某些技能时,显示这些技能的提示
- 当玩家因为某种原因死亡时,显示避免这种死亡的提示
- 等等……
玩家结束一局时可能会满足多个提示的显示条件,这时就在可以显示的提示里随机选择一个。
选择的实现方式是给各种种类的提示做一个权重表。根据玩家行为的不同调整不同类型提示的权重,然后选择一个结果。比如说:
local tipsWeightList = {
[TIP_GROUP.TYPE_1] = 10,
[TIP_GROUP.TYPE_2] = 0, -- 不可能出现
[TIP_GROUP.TYPE_3] = 50,
[TIP_GROUP.TYPE_4] = 1000, -- 出现概率最高
[TIP_GROUP.TYPE_5] = 10,
}
睿智的策划为权重的调整加入了很多条件,希望能给玩家更好的体验,例如:
- 当玩家还处在新手阶段时,大幅提升基础类提示的权重
- 当一局游戏中出现了很多类型的道具时,之前出现提示较少的道具类型获得更大的权重
- 某些死亡方式和某些敌人的搭配会大幅影响提示的权重
- 随着一部分提示的出现,还没有出现过的提示的权重会有所变化(例如一些提示只有当一定数量的提示解锁后才可能出现)
- 等等……
我们需要根据各种规则反复修改之前的权重表。当规则多起来之后,我们在测试中遇到了问题:这个权重表的每一项到底对不对?如果不对是哪里不对了?
回想一下P社游戏会把大量影响因素罗列出来,非常便于分析:
我们希望自己的游戏一局游戏结束后能够将权重表罗列成这样:
基础组 10
基础 +10
敌人1组 160
发现了敌人 +10
敌人1组出现率<10% (0/6) +150
敌人2组 0
道具1组 10
发现道具 +10
道具2组 0
道具3组 110
发现道具 +10
内容组出现率<10% (0/6) +100
后期组 0
全体出现率 (10/100) +0
这样我们可以容易地看出权重计算得对不对,以及以此估算可能出现的提示。
为了适应睿智的策划的需求,这个记录肯定要是动态的。
2. 先说一个简单的例子
游戏中玩家拥有的金币的数量,最简单的实现是直接放在一个全局的单例里:
PlayerData:getInstance().coin = 10
如果再讲究一点,可以稍微封装一下:
PlayerData:getInstance():addCoin(20) -- 收入
PlayerData:getInstance():addCoin(-5) -- 支出
这样在赋值的时候可以干更多的事,比如做个安全检查、发个事件什么的。
在某个时刻,我们发现玩家拥有的金币和我们期望的不一致——肯定是哪里出现bug了。然后我们失望地发现游戏中赚钱和花钱的途径有十几种。为了找到问题出在哪里,我们要么在每处打log,要么加断点然后反复尝试,即便这样也不一定能很快定位到问题所在。
如果我们能像查询银行账单一样查询这个值的修改历史该有多好啊!
所以我们真的来做一个类似日志的系统。实现非常简单:
coin_history = {}
function addCoin(value, tag)
coin_history[#coin_history+1] = {value = value, tag = tag}
end
function getCoin()
local coin = 0
for k, v in pairs(coin_history) do
coin = coin + v.value
end
return coin
end
用起来并没有更麻烦,但是我们现在可以很容易地获得操作历史了:
function listCoinLog()
print("------ Coin ------")
local coin = 0
for k, v in pairs(coin_history) do
coin = coin + v.value
if v.value >= 0 then
print(coin .. "(+" .. v.value .. ") Comment: " .. v.tag)
else
print(coin .. "(" .. v.value .. ") Comment: " .. v.tag)
end
end
end
测试一下:
addCoin(10, "income 1")
addCoin(20, "income 2")
addCoin(-5, "expend 1")
listCoinLog()
输出:
------ Coin ------
10(+10) Comment: income 1
30(+20) Comment: income 2
25(-5) Comment: expend 1
就像查询银行账单。
这样做的一大好处是,我们可能在游戏开发的某一时刻突然有了对数据的统计需求,比如:
- 今天有10个工人在工作,每个工人贡献了多少?
- 今天生产了20种产品,每种产品创造了多少收入?
为了这种需求加记录和统计的代码太可怕了,但是我们可以把这些信息全都变成tag,然后在需要的时候统计这些tag。
3. 扩展这个系统
我们有很多事可以做。
- 我们可以把这个标记系统单独抽出来做一个类,然后每个数值型的用户数据都是它的实例。
- 我们在持久化数据时可以把这些标记一起序列化。这种做法:
- 简单
- 可以保持全部的原始数据(可以在下次运行时de上次的bug了)
- 可以让篡改数据变得稍微更困难些。
- 我们可以直接在原始数据的基础上分析数据,例如按照tag分类排序,或者统计单一tag的结果。
- 虽然现在只有一个tag字段,但可以扩充更多的信息,比如来自谁、时间戳等等。
- 当记录过多时,可以加一个merge方法,归并所有的记录,只保留一条归并后的结果。
- 如果比较讲究的话,可以在归并数据时把记录全都转移到另一个archived列表里。但我们毕竟是做游戏不是记账软件,这个有点过于讲究了。
4. 回到最初的问题
有了这个系统之后最开始的问题变得很平凡了。
比如有两个条件都会增加『敌人1组』tips的出现权重:
- 发现这种敌人
- 敌人1组总体出现率极低
那么当条件1触发时我们就记录:
{group = 1, value = 10, tag = "发现了敌人"}
条件2触发时记录:
{group = 1, value = 150, tag = "敌人1组出现率低"}
在游戏结束时我们就可以获得类似这样的输出:
敌人1组 160
发现了敌人 +10
敌人1组出现率低 +150
当睿智的策划增加新的条件时,我们就加入一小段条件判断,然后把权重的改变打上tag。例如一个降低『敌人1组』出现率的条件是敌人2出现,我们就可以在敌人2出现时加入记录:
{group = 1, value = -100, tag = "敌人2出现"}
在最后获得的结果是:
敌人1组 60
发现了敌人 +10
敌人1组出现率低 +150
敌人2出现 -100