我是一名网络游戏开发商,但我遇到了随机数问题。假设一位玩家有20%的机会用剑获得暴击。这意味着5个匹配中有1个至关重要。问题是我在现实生活中的结果非常糟糕-有时玩家在5次点击中获得3分,有时15次点击中没有得分。战斗时间很短(3-10次击中),因此获得良好的随机分布非常重要。
目前,我使用PHP mt_rand()
,但是我们只是将代码移至C ++,所以我想在游戏的新引擎中解决此问题。
我不知道解决方案是某种统一的随机生成器,还是想起以前的随机状态来强制正确分配。
我是一名网络游戏开发商,但我遇到了随机数问题。假设一位玩家有20%的机会用剑获得暴击。这意味着5个匹配中有1个至关重要。问题是我在现实生活中的结果非常糟糕-有时玩家在5次点击中获得3分,有时15次点击中没有得分。战斗时间很短(3-10次击中),因此获得良好的随机分布非常重要。
目前,我使用PHP mt_rand()
,但是我们只是将代码移至C ++,所以我想在游戏的新引擎中解决此问题。
我不知道解决方案是某种统一的随机生成器,还是想起以前的随机状态来强制正确分配。
Answers:
我同意较早的答案,即小规模游戏的真正随机性是不可取的-对于某些用例而言,这似乎太不公平了。
我用Ruby编写了一个类似于Shuffle Bag的简单实现,并做了一些测试。该实现做到了:
根据边界概率,这被认为是不公平的。例如,对于20%的概率,您可以将10%设置为下限,将40%设置为上限。
利用这些界限,我发现在运行10次命中后,真正的伪随机实现产生的结果超出这些界限的时间占14.2%。大约11%的时间,在10次尝试中得分为0。3.3%的时间中,有10次击中了5个或更多关键命中。自然地,使用此算法(最小掷骰数为5),很少(0.03%)的“公平”运行超出范围。即使下面的实现不合适(当然可以做一些更聪明的事情),值得注意的是,您的用户通常会感觉到使用真正的伪随机解决方案是不公平的。
这是我FairishBag
用Ruby编写的内容。完整的实现和快速的蒙特卡洛仿真可在此处找到(要点)。
def fire!
hit = if @rolls >= @min_rolls && observed_probability > @unfair_high
false
elsif @rolls >= @min_rolls && observed_probability < @unfair_low
true
else
rand <= @probability
end
@hits += 1 if hit
@rolls += 1
return hit
end
def observed_probability
@hits.to_f / @rolls
end
更新:使用此方法确实会增加受到严重打击的总体可能性,在上述范围内增加到大约22%。您可以通过将其“真实”概率设置得低一点来抵消它。进行了适度修改的概率为17.5%,观察到的长期概率约为20%,并使短期运行感觉良好。
这意味着5个匹配中有1个至关重要。问题是我的现实生活成绩很差-有时玩家在5次点击中获得3分,有时15次点击中没有得分。
您需要一个洗牌袋。它解决了真正随机性对于游戏来说过于随机的问题。
算法大致如下:将1个严重和4个非严重命中放入袋子。然后,您可以将它们的顺序随机放入购物袋中,然后一次挑选出来。当袋子是空的时,您再次用相同的值填充并随机化。这样,您平均每5次点击可获得1次严重点击,并且连续最多获得2次关键和8次非关键点击。增加袋子中物品的数量,以增加随机性。
您对随机意味着什么有误解。
其中哪个更随机?
尽管第二个图看起来分布更均匀,但实际上第一个图的随机性更高。人类的大脑通常会随机地看到图案,因此我们将第一幅图中的团块视为图案,但并非如此-它们只是随机选择的样本的一部分。
鉴于您要的行为,我认为您正在随机分配错误的变量。
与其随机化该命中是否关键,不如随机化转数直到出现下一个关键命中。例如,每次玩家获得关键球后,只需选择2到9之间的一个数字,然后在经过多轮之后再给他们下一个关键球。您还可以使用骰子方法来接近正态分布-例如,您将在2D4转弯中获得下一个临界点。
我相信这项技术也可以在在世界范围内遇到随机遭遇的RPG中使用-您将步数计数器随机化,然后经过许多步,您将再次受到打击。感觉要公平得多,因为您几乎永远不会被连续两次遭遇击中-即使发生一次,玩家也会变得烦躁。
首先,定义“适当的”分布。随机数是随机的-您看到的结果与(伪)随机性完全一致。
在此基础上,我假设您想要的是某种“公平”的感觉,因此用户不能成功走100转。如果是这样,我将跟踪自上次成功以来的失败次数,并对产生的结果进行加权。假设您希望五分之一的卷“成功”。因此,您会随机生成一个1到5的数字,如果它是5,则很好。
如果不是,则记录失败,然后再次生成一个从1到5的数字,然后加上floor(numFailures / 2)。所以这一次,他们再次有五分之一的机会。如果失败,则下一次获胜间隔为4 和 5;否则,则为5。五分之二的成功机会。有了这些选择,经过8次失败,他们肯定会成功。
希望本文对您有所帮助: http //web.archive.org/web/20090103063439/http //www.gamedev.net /
这种生成“随机数”的方法在rpg / mmorpg游戏中很常见。
它解决的问题是(提取):
刀片蜘蛛在你的喉咙。命中,你想念。它再次命中,您再次错过。一遍又一遍,直到你没有剩下的要打。您已经死了,尸体上有两吨重的蜘蛛蛛。不可能?不,不可能吗?是。但是,如果有足够的玩家和足够的时间,几乎不可能的事情就变得不可能了。不是说刀片蜘蛛很坚硬,那只是倒霉。真令人沮丧 这足以使玩家想退出。
您想要的不是随机数,而是对人类而言似乎是随机的数字。其他人已经建议了可以帮助您的个别算法,例如Shuffle Bad。
有关此领域的详细,详尽的分析,请参见AI Game Programming Wisdom 2。整本书对任何游戏开发者都值得一读,“看似随机数”的概念在以下章节中介绍:
人工智能决策和博弈逻辑的滤波随机性:
摘要:传统观点认为,随机数生成器越好,您的游戏就越难以预测。但是,根据心理学研究,短期内真正的随机性通常看起来对人类绝对是随机的。本文展示了如何使随机AI决策和游戏逻辑对玩家来说更加随机,同时仍保持强大的统计随机性。
您可能还会发现另一章有趣的内容:
随机数统计
摘要:一般来说,人工智能和游戏对随机数的使用最多。忽略它们的潜力就是使游戏变得可预测且无聊。错误地使用它们与完全忽略它们一样糟糕。了解随机数的生成方式,其局限性和功能可以消除在游戏中使用它们的许多困难。本文提供了对随机数,它们的生成以及区分好坏的方法的见解。
不幸的是,您实际上要求的是非随机数生成器-因为您希望在确定下一个数字时考虑先前的结果。恐怕这不是随机数生成器的工作方式。
如果您希望每5个匹配中有1个是关键,则只需选择1到5之间的一个数字,然后说该匹配将是关键。
mt_rand()基于Mersenne Twister实现,这意味着它会产生您可以得到的最佳随机分布之一。
显然,您想要的根本不是随机性,因此您应该一开始就明确指定您想要的。您可能会意识到自己有不同的期望-结果应该是真正随机的并且是不可预测的,但是同时它们不应该显示出所陈述的概率的局部变化-但随后就变得可预测了。如果您连续设置最多10个非得分,那么您刚刚告诉玩家“如果您连续有9个非得分,那么下一个将会以100%的确定性成为关键”。完全不用打扰。
我看到很多答案,建议您跟踪以前生成的数字或改组所有可能的值。
就我个人而言,我不同意,连续3次暴击是不好的。我也不同意连续15次非批评是不好的。
我可以通过在每个数字之后自行修改暴击几率来解决问题。示例(演示想法):
int base_chance = 20;
int current_chance = base_chance;
int hit = generate_random_number(0, 100) + 1; // anything from 1 to 100
if(hit < current_chance)//Or whatever method you use to check
{
//crit!
if(current_chance > base_chance)
current_chance = base_chance; // reset the chance.
else
current_chance *= 0.8; // decrease the crit chance for the NEXT hit.
}
else
{
//no crit.
if(current_chance < base_chance)
current_chance = base_chance; // reset the chance.
else
current_chance *= 1.1; // increase the crit chance for the NEXT hit.
//raise the current_chance
}
您没有暴击的时间越长-您下一次暴击的机会就越大。我提供的重置完全是可选的,需要进行测试以判断是否需要。在漫长的非暴击行动链之后,连续或多次行动都可能有更高的暴击概率。
只需投入我的2美分...
前几个答案是很好的解释,因此,我将仅关注一种算法,该算法可让您控制“不良条纹”的可能性,而不会变得确定性。我认为您应该这样做:
代替指定p,即伯努利分布的参数(可能会导致严重打击),指定a和b,即beta分布的参数,即贝努利分布的“共轭先验”。您需要跟踪到目前为止的关键命中数和非关键命中数A和B。
现在,要指定a和b,请确保a /(a + b)= p,这是重击的机会。整洁的事情是(a + b)量化了您通常希望A /(A + B)与p的接近程度。
您可以这样采样:
设p(x)
β分布的概率密度函数。它在许多地方都可用,但是您可以在GSL中找到它,形式为gsl_ran_beta_pdf。
S = A+B+1
p_1 = p((A+1)/S)
p_2 = p(A/S)
通过以概率为p_1 /(p_1 + p_2)的伯努利分布进行采样来选择关键命中
如果发现随机数有太多的“不良条纹”,请按比例放大a和b,但在极限情况下,随着a和b达到无穷大,您将拥有前面所述的无序袋方法。
如果您执行此操作,请告诉我它的进展!
如果希望使用不鼓励重复值的分布,则可以使用简单的重复拒绝算法。
例如
int GetRand(int nSize)
{
return 1 + (::rand() % nSize);
}
int GetDice()
{
static int nPrevious=-1;
while (1) {
int nValue = GetRand(6);
// only allow repeat 5% of the time
if (nValue==nPrevious && GetRand(100)<95)
continue;
nPrevious = nValue;
return nValue;
}
}
此代码95%的时间拒绝重复值,从而使重复不太可能但并非不可能。从统计上讲这有点丑陋,但可能会产生您想要的结果。当然,它不会阻止像“ 5 4 5 4 5”这样的分布。您可能会更喜欢并拒绝倒数第二个(例如)60%的时间和倒数第二个(例如)30%的时间。
我不建议您将其作为良好的游戏设计。只是建议如何实现您想要的。
还不清楚您想要什么。可以创建一个函数,以便在您调用它的前5次中,它以随机顺序返回数字1-5。
但这并不是真正随机的。玩家将知道在接下来的5次攻击中他将获得正5分。不过,这可能是您想要的,在这种情况下,您只需要自己编写即可。(创建一个包含数字的数组,然后将其洗牌)
另外,您可以继续使用当前方法,并假设当前结果是由不良的随机生成器引起的。请注意,您当前的号码可能没有问题。随机值是随机的。有时您会连续获得2、3或8个相同值的数据。因为它们是随机的。一个好的随机数生成器只能保证平均而言,所有数字将被平均返回。
当然,如果您使用的是错误的随机数发生器,那可能会使您的结果出现偏差,如果这样,只需切换到更好的随机数发生器即可解决此问题。(请查看Boost.Random库以获得更好的生成器)
或者,您可以记住随机函数返回的最后N个值,然后用这些值加权结果。(一个简单的示例是,“对于每次出现的新结果,我们都有50%的机会应该放弃该值并获得一个新结果”
如果我不得不猜测,我会说坚持“实际”随机性是您最好的选择。确保您使用了一个好的随机生成器,然后继续按照自己的方式进行操作。
您可以创建一个包含1到5的数字的列表,并按随机性对它们进行排序。然后只需浏览您创建的列表。您可以保证至少每个数字都会遇到一次...当您处理完前5个数字时,只需再创建5个数字...
我建议像暴雪使用的渐进百分比系统:http : //www.shacknews.com/onearticle.x/57886
通常,您滚动一个RNG,然后将其与一个值进行比较以确定是否成功。可能看起来像:
if ( randNumber <= .2 ) {
//Critical
} else {
//Normal
}
您需要做的就是逐渐增加基本机会...
if (randNumber <= .2 + progressiveChance ) {
progressiveChance = 0;
//Critical
} else {
progressiveChance += CHANCE_MODIFIER;
//Normal hit
}
如果您需要它更精美,可以添加更多内容。您可以设置progressiveChance可以避免的机会,以避免100%的暴击率或在某些事件中将其重置。您还可以通过每次进行逐次提升而使逐行增加的量较小,例如progressiveChance + =(1-progressChance)* SCALE,SCALE <1。
好吧,如果您对数学有所了解,则可以尝试指数分布
例如,如果lambda = 0.5,则期望值为2(请阅读该文章!),这意味着您很可能每2圈会击中/暴击/击中任何物体(例如50%,对吧?)。但是有了这样的概率分布,您将在第0圈(已经发生了一个事件,并且turn_counter被重置了)定义完全错失(或做任何相反的事情),大概有40%的机会击中下一回合,大约为65%有机会进行第二轮(下一轮下一轮),大约有80%的机会击中第三轮,依此类推。
这种分配的全部目的是,如果某人有50%的命中率,而他连续3次未命中,那么他肯定会命中(很好,超过80%的机率,并且每下回合都会增加)。它会导致更“公平”的结果,使总体50%的机会保持不变。
冒20%的暴击几率,你有
在接下来的5个回合中,它仍然有约0.2%的机率(相对于5%的机率)是3分+ 2分。并且有4次随之而来的非犯罪的可能性为14%,5、5、5%,6、0.3%,7、0.07%的机会是8次随之而来的非犯罪。我敢打赌它的“比较公平”超过41%,32%,26%,21%和16%。
希望您仍然不会感到无聊。
如何使危急机会取决于最近的N次攻击。一个简单的方案是某种马尔可夫链: http //en.wikipedia.org/wiki/Markov_chain,但是代码还是非常简单的。
IF turns_since_last_critical < M THEN
critial = false
turns_since_last_critical++;
ELSE
critial = IsCritical(chance);
IF Critial THEN
turns_since_last_critica = 0;
ELSE
turns_since_last_critica++;
END IF;
END IF;
当然,您必须进行数学运算,因为一旦您知道自从上一轮以来已经足够的转弯,一次暴击的机会就会比一次暴击的机会低
OP,
几乎,如果您希望它是公平的,那么它就不会是随机的。
您的游戏的问题是实际比赛时间。匹配时间越长,您看到的随机性就越少(临界值通常为20%),并且接近您的预期值。
您有两个选择,可以根据以前的结果预先计算攻击次数。每5次攻击您将获得一次暴击(基于您的20%),但是您可以使它随机发生。
listOfFollowingAttacks = {Hit,Hit,Hit,Miss,Crit};
那就是你想要的模式。因此,使其从该列表中随机选择,直到其为空为止,他们会重新创建它。
这是我为自己的游戏创建的模式,对于我想做的事情,它运行得很好。
第二种选择是增加暴击的机会,您可能会在所有攻击的末尾看到一个更均匀的数字(假设您的比赛很快结束)。机会百分比越少,您获得的RNG越高。
加权值怎么样?
例如,如果您有20%的几率爆击,请生成1到5之间的数字,其中一个数字表示爆击,或者生成1到100之间的数字,其中20个数字表示爆击。
但是,只要使用随机数或伪随机数,就无法避免出现当前看到的结果。这是随机性的本质。
关于以下问题的反应:“问题是我的现实生活成绩很差-有时玩家在5次点击中获得3分,有时在15次点击中没有获得3分。”
您有3到4%的几率在15次点击中什么都没有得到...
我将提出以下“随机延迟的回退死机”:
in-array
)最初填充了0到n-1的值,另一个数组(out-array
)为空in-array
in-array
移到out-array
out-array
后移到in-array
它具有以下特性:n越大,它“反应”越慢。例如,如果您希望获得20%的机会,则将n设置为5并命中0的概率要比将n设置为10并命中0或1的概率要小,而将其设置为0到199的概率几乎为零。与小样本的真实随机性无法区分。您必须将n调整为样本大小。
预计算每个玩家的随机暴击率。
// OBJECT
//...
// OnAttack()
//...
c_h = c_h -1;
if ( c_h == 0 ) {
// Yes, critical hit!
c_h = random(5) + 1 // for the next time
// ...
}