前言
万恶的抽卡系统到底怎么回事?为啥我什么都抽不到?抽到都是蓝天白云,什么时候是闪光最佳抽卡时机,SSR,SP为何与我无缘?一位从业多年,头发渐无的老者来解析其中缘由。
随机机制
所有的抽卡逻辑都是写在服务端,服务端一般会有全服统一的一个随机数生成器,我们这里的随机数生成器本身就是伪随机。开服时,一般以时间作为随机种子。策划会对所有可以抽出来的道具设置权重,比如某个池子一共只有A,B,C三个道具,权重为10,20,70,先计算总权重为100,然后随机一个[0,100)的数,[0,10)区间是A道具,[10,30)区间为B道具,[30,100)为C道具。参考lua代码如下:
math.randomseed(os.time())
local function draw_card(item_libs,probs,count)
if not count then
count = 1
end
assert(#item_libs == #probs, "item list length must equal to probs length")
local sum = 0
for _, prob in pairs(probs) do
sum = sum + prob
end
local result = {}
for _ = 1, count do
local rand_value = math.random() * sum -- [0,sum)
local s = 0
local select_index = 1
for index, prob in pairs(probs) do
s = s + prob
if rand_value < s then
select_index = index
break
end
end
table.insert(result, item_libs[select_index])
end
return result
end
local result = draw_card({"A","B","C"},{10,20,70},10)
大致的流程是这样,不过,里面的花样繁多,最终的实际概率基本和游戏中显示的抽卡概率不一致。
根据不同的花样,我大致分为以下几类:
- 限量抽取
- 分库抽取
- 保底机制
- 老虎机
- 限时UP
- 全凭人品
- 诺亚幻想大建
限量抽取
什么是限量抽取?比如某个道具A,策划限定每天全服务器最多出10个,每个抽出来概率为0.1%,那么玩家把10个都抽完之后,每个抽出来概率就为0了,后面的玩家怎么抽都不会抽到。这种情况下,如果想抽到,那么必须在刷新后马上抽。一般刷新时间为凌晨0点,或者早上6点,不同的游戏不一样,一个游戏玩的久了大家就都清楚刷新时间了。一些限量的周边或者极其稀有的道具抽取活动会采用此类抽卡方式。限量抽取参考lua代码如下:
local item_limit = {A = 1} -- 全服当天只能抽取一个A
math.randomseed(os.time())
local function draw_card(item_libs,probs,count)
if not count then
count = 1
end
assert(#item_libs == #probs, "item list length must equal to probs length")
local sum = 0
for _, prob in pairs(probs) do
sum = sum + prob
end
local result = {}
for _ = 1, count do
local rand_value = math.random() * sum -- [0,sum)
local s = 0
local select_index = 1
for index, prob in pairs(probs) do
s = s + prob
if rand_value < s then
select_index = index
break
end
end
local item = item_libs[select_index]
if item_limit[item] > 0 then -- 判断是否抽完了
item_limit[item] = item_limit[item] - 1
else
item = draw_card(item_libs, probs, 1) -- 重新抽取
end
table.insert(result, item)
end
return result
end
local result = draw_card({"A","B","C"},{10,20,70},10)
分库抽取
这里的库,就是英雄池了,拿《剑与远征》举例,有种族库,紫卡库,神魔库,先随机一次,根据随机结果决定去哪个库,然后再随机一次,决定随出库中的哪个英雄。不同的英雄在库中设计的权重也不一样,还有可能随时调整,调整了玩家也不知道。所以,强力的英雄最终抽到的概率会很低。分库抽取参考lua代码如下:
local all_libs = {
lib1 = {A = 10,B = 20,C = 70},
lib2 = {D = 10,E = 20,F = 70},
lib3 = {G = 10,H = 20,I = 70},
}
local libs_probs = {lib1 = 5,lib2 = 10,lib3 = 85}
math.randomseed(os.time())
local function draw(libs,probs,count)
if not count then
count = 1
end
assert(#libs == #probs, "item list length must equal to probs length")
local sum = 0
for _, prob in pairs(probs) do
sum = sum + prob
end
local result = {}
for _ = 1, count do
local rand_value = math.random() * sum -- [0,sum)
local s = 0
local select_index = 1
for index, prob in pairs(probs) do
s = s + prob
if rand_value < s then
select_index = index
break
end
end
local item = libs[select_index]
table.insert(result, item)
end
return result
end
local function get_lib_probs(lib_probs)
local libs = {}
local probs = {}
for k,v in pairs(lib_probs) do
table.insert(libs,k)
table.insert(probs,v)
end
return libs, probs
end
local function draw_with_lib(lib_probs)
local libs,probs = get_lib_probs(lib_probs)
local lib = draw(libs,probs)
local result = {}
for _, v in pairs(lib) do
local item_lib = all_libs[v]
local item_libs, item_probs = get_lib_probs(item_lib)
local single_result = draw(item_libs, item_probs)
table.insert(result, single_result[1])
end
return result
end
local result = draw_with_lib(libs_probs)
保底机制
保底我相信大家都很熟悉了,这个是专为非酋玩家准备的,脸比较黑的玩家真的是抽不到好东西,为了优化玩家整体抽卡体验,很多游戏都推出了保底机制,比如50连保底,解释为连续49次未抽中,则第50次必定出。这里必定出,是出什么呢,有可能是出固定的英雄库,也有可能是必出某个英雄。在程序中,会有一个计数器,计数玩家当前的抽卡未中次数,一般很有可能有多个计数器,每个英雄池子单独计数,并且,只要抽中了,那么计数就会清零。现在,问题来了。
- 正常抽中概率1%,50次保底,假设玩家抽无限次,那么抽到的平均概率是多少?
上代码:
math.randomseed(os.time())
local LUCKY_LIMIT = 50
local lucky_count_map = { A = 0} -- 保底计数
local bingle_count = 0 -- 抽到A的总次数
local draw_count = 10000000 -- 总共抽1千万次
local function draw_card(item_libs,probs,count)
if not count then
count = 1
end
assert(#item_libs == #probs, "item list length must equal to probs length")
local sum = 0
for _, prob in pairs(probs) do
sum = sum + prob
end
-- local result = {}
for _ = 1, count do
local rand_value = math.random() * sum -- [0,sum)
local s = 0
local select_index = 1
for index, prob in pairs(probs) do
s = s + prob
if rand_value < s then
select_index = index
break
end
end
local item = item_libs[select_index]
if item == "A" then
lucky_count_map.A = 0
bingle_count = bingle_count + 1
else
lucky_count_map.A = lucky_count_map.A + 1
if lucky_count_map.A == LUCKY_LIMIT then
item = "A"
bingle_count = bingle_count + 1
lucky_count_map.A = 0
end
end
-- table.insert(result, item)
end
-- return result
end
draw_card({"A","B","C"},{1,29,70},draw_count)
print(bingle_count / draw_count)
运行结果 0.025303
,加上保底后平均概率约为 2.53%
,由此可见,保底机制大大增加了抽卡抽到的概率,有保底机制的游戏大大的良心。再拿《剑与远征》举例,游戏内公示的概率为:
注意,这里是卡池的概率,是上一小节介绍的分库抽取的模式,不过不影响保底的计算。我们可以用程序反推其英雄卡池的权重。已知精英级英雄30连保底,精英级英雄卡池概率4.61%,稀有级43.70%,普通级英雄51.69%,求所有卡池的整数权重(策划一般都是配置的整数)。我的计算结果为:1587 1342 71
,怎么得出来的呢?我试出来的,用牛顿逼近法。。这个概率用公式我还不知道怎么计算。如果有朋友知道,麻烦在评论区留言,非常感谢。
老虎机
老虎机是一种赌博的机器,这种机器有一种机制,它会慢慢吃掉你的筹码,然后累计到一定量,然后吐出来一部分。这一部分占多少比例呢?每个老虎机可以自己设置,比如吃掉1000,吐50。稳赚不赔。
在游戏里面,抽卡也有老虎机模式:全服玩家抽卡花掉的钻石总数达到一个配置的上限,才会吐出来SSR,SP,在这之前,不好意思,抽出来概率为0.
这种机制虽然比较恶心,但是初期一般都看不出来,需要比较多的抽之后,慢慢才会被玩家发现。玩家发现之后,一般都是,看到系统公告,有人出SSR了,就马上去抽。错过了就充当垫子了。
玩家还是比较讨厌这种机制的,那为啥很多游戏还是用老虎机模式呢?我个人认为,老虎机可以保证稳定的收益计算,策划设计起来更省事。
老虎机模式可以看作是动态版本的限量抽取,收到一定钻石之后,限量值会动态变化一次,这时候就是限量放送,限量值为0后,又需要等待下一次的钻石累计,如此循环。
参考lua代码如下:
local item_limit = {A = 0}
local diamond_sum = 0 -- 钻石累计
local diamond_limit = 100000 -- 钻石收到10万后放送
local bingle_count = 0
math.randomseed(os.time())
local function draw_card(item_libs,probs,count)
if not count then
count = 1
end
assert(#item_libs == #probs, "item list length must equal to probs length")
local sum = 0
for _, prob in pairs(probs) do
sum = sum + prob
end
local result = {}
for _ = 1, count do
diamond_sum = diamond_sum + 300
if diamond_sum > diamond_limit then
item_limit.A = 10
diamond_sum = 0
end
local rand_value = math.random() * sum -- [0,sum)
local s = 0
local select_index = 1
for index, prob in pairs(probs) do
s = s + prob
if rand_value < s then
select_index = index
break
end
end
local item = item_libs[select_index]
if item_limit[item] then
if item_limit[item] > 0 then -- 判断是否抽完了
item_limit[item] = item_limit[item] - 1
else
item = draw_card(item_libs, probs, 1) -- 重新抽取
end
end
if item == "A" then
bingle_count = bingle_count + 1
end
table.insert(result, item)
end
return result
end
draw_card({"A","B","C"},{10,20,70},1000)
print(bingle_count)
限时UP
限时UP一般出现在活动中,分两种,一种是卡池的概率UP,一种是卡池内部特定英雄的概率UP。两种都是在原有的权重基础上乘以一个参数。一般活动描述的XXX英雄概率提升100%,那么就是权重提高2倍。这个很好理解,代码上修改也比较简单,这里就不贴代码了。
全凭人品
有些游戏,一点花样都没有,非常干净,这就是全凭人品。不过我这个标题还有另一个意思,只要不是老虎机,欧皇永远是欧皇,这个非酋真没法比。还有就是,网上流传的伪随机初始号,每个号的人品都不一样,这种说法。是谣言,在服务器程序里,每个号都是平等的。
诺亚幻想大建
这个标题有点奇怪,因为诺亚幻想的抽卡机制是另类的,特殊的,居然还受玩家控制的!!详情请查看诺亚幻想抽卡系统
总结
很多游戏里面都是以上各种花样的结合体,希望对大家有所帮助。如果还有我没见过的花样,欢迎大家留言告知。祝大家抽卡顺利,游戏愉快。