Unity中的随机数
在许多Unity开发的游戏中,去添加随机游戏元素,其中随机选择的items或values是很重要的,本篇文章就和大家介绍下如何使用Unity的内置随机函数来实现一些常见的游戏机制。
选择一个随机Item游戏元素从数组中:
在随机选择一个数组元素时,可以选择一个零和数组的最大值(这是等于数组的长度减去 1 )的随机整数。这是很容易使用内置的随机函数:—
var element = myArray[Random.Range(0, myArray.Length)];
注意,Random.Range 从一个范围(包括第一个参数但不包括第二)返回一个值,所以使用 myArray.Length。在这里能给出正确的结果。
选择不同概率的Items:
有时,你需要选择随机的物品,但有些物品更容易被选上。例如,NPC可能会反应在几个不同的方式,当它遇到一个玩家:
50% 友好的问候的机会
25% 逃跑的机会
20% 直接攻击的机会
5% 提供金钱作为礼物的机会
你能想象这些不同的结果作为一条纸分成几部分分别占总长度的一小部分的条。所占比例等于被选择的结果的概率。作出选择是相当于挑选一个随机点沿带状的长度(例如投掷一个飞镖),然后看到它是在。
在脚本中,该文件条实际上是一个float 的数组,该数组中包含的Item的不同的概率。随机点则是由float数组总大小乘以 Random.value 值来获得(他们不需要总计达1;重要的是不同的值和 相对大小)。为了找出随机点元素落在哪个点数组,首先检查看它是否是小于第一个元素的值。如果是的话,那么第一个元素将被选择。否则,从数组中减去第一个元素的值,比较第二个元素,直到找到正确的元素。类似于下面这个代码:
//C# float Choose (float[] probs) { float total = 0; foreach (float elem in probs) { total = elem; } float randomPoint = Random.value * total; for (int i= 0; i < probs.Length; i ) { if (randomPoint < probs[i]) { return i; } else { randomPoint -= probs[i]; } } return probs.Length - 1; }
需要注意的是最后一个return语句非常必要,因为Random.value可以返回1(注:有些地方random.value都是不能返回1,可以返回0)。在这种情况下,我们会无法获得随机点,更改代码行:
if (randomPoint < probs[i])
一个小于或等于的if测试语句将避免额外的return语句。因为即便选中它的概率是零。它也有可能被选中。
加权连续随机值
floats数组方法 工作如果你有离散的结果,但也存在的情况下,要产生一个更连续的结果,你想随机金币数 在打开一个宝箱的时候,你希望它可以结束与1和100之间的任何数字,但使较低的数字更可能。使用array-of-floats 要求你建立一个数组(即纸条上的100部分)这是笨拙的;如果你不限于整数,而是要在范围内的任何数,使用这种方法是不可能的。
连续的结果,更好的方法是使用一个‘raw’ AnimationCurve随机值变换成一个加权的;通过绘制不同的曲线形状,可以产生不同的权重。写的代码也简单:
//C# float CurveWeightedRandom(AnimationCurve curve) { return curve.Evaluate(Random.value); }
一个 ‘raw’ 的随机值在0和1之间,是通过阅读从random.value选择。然后通过 curve.Evaluate() ,以它为横坐标,并返回到水平位置的曲线对应的垂直坐标。曲线的浅部分有更大的机会被选中,而陡峭的部分有一个较低的机会被选中。
线性曲线不等于权重值,水平坐标等于曲线上每个点的垂直坐标。
这条曲线在开始时是较浅的,在结束时更为陡峭,因此它有更大的机会,低值和高值的机会减少。你可以看到,在直线上的曲线的高度,在x= 0.5是y在约0.25,这意味着有50%的机会得到一个值在0和0.25之间。
这条曲线在开始和结束时都很浅,使得值接近于极端更为普遍,在中间,这将使这些值稀少。还注意到这个曲线,高度的值已经被移了:曲线的底部是1,并且曲线的顶部是在10,这意味着所产生的曲线将在1 - 10范围,而不是0 - 1像以前的曲线。
注意,这些曲线不是概率分布曲线,就像你在概率论中找到的,但更像是逆累积概率曲线。
通过一个脚本定义一个公共AnimationCurve变量,你将能够看到和编辑曲线通过Inspector窗口形象,而不需要计算值。
此技术产生浮点数。如果你想计算例如整数结果,你需要82个金币,而不是82.1214枚金币----你可以通过计算值为Mathf.RoundToInt().一样的功能。
随机排序(扑克牌的洗牌!):
一个普遍的游戏机制是从一个已经进行随机顺序的数组中选择一个需要的数据。例如,一副牌通常会被打乱,所以你不会预见到它们的排列顺序。同样这你可以通过随机交换数组中的元素或改变它们的索引值来打乱它们之间的顺序。
//C# void Shuffle (int[] deck) { for (int i = 0; i < deck.Length; i ) { int temp = deck[i]; int randomIndex = Random.Range(0, deck.Length); deck[i] = deck[randomIndex]; deck[randomIndex] = temp; } }
不重复选取:
法1:
一个简单的要求是在选择数组中,同一个元素最多只能被随机选中一次。例如,您可能要在随机点的位置生成NPC,但可以肯定,在每一个点只能有一个NPC产生。这可以通过遍历序列中的项目,为每一个随机点决定是否被添加到选择集。剩下的元素被选中的概率等于需要选择的元素数量除以剩下的所有元素数量。
例如,假设有十个刷新复活点可以用,但是必须只能选择五个。第一个物体被选择的概率将是5/ 10或0.5。如果选中第一个之后,第二个的概率为4 /9或0.44(即,四个物体仍然需要从剩下的九个选择)。然而,如果第一个没有选中,第二个选中的概率为5/ 9(即5/ 9或0.56,仍然需要从剩下的九个中选择)。这一直持续到要求的5个都被选上。完成的代码如下:
//C# Transform[] spawnPoints; Transform[] ChooseSet (int numRequired) { Transform[] result = new Transform[numRequired]; int numToChoose = numRequired; for (int numLeft = spawnPoints.Length; numLeft > 0; numLeft--) { float prob = (float)numToChoose/(float)numLeft; if (Random.value <= prob) { numToChoose--; result[numToChoose] = spawnPoints[numLeft - 1]; if (numToChoose == 0) { break; } } } return result; }
请注意,虽然选择是随机的,在选择集中的数据在原始数组的排列顺序依然相同。如果数据排序是一次性的话,部分随机的数据有可能是可预见到的,所以它有必要在使用前就打乱数组的顺序。
法2:
使用List , 选择了一个之后,移除那个, 然后继续随机
法3:
添加另外一个数组,标识每个元素是否被选中了,如果选中就重新随机啊!
空间中的随机点:
一个三维空间中的点,可以通过random.value函数中随机返回一个为Vector3的xyz值
var randVec = Vector3(Random.value, Random.value, Random.value);
上述函数所给的这个立方体中的点会分布在同一条边上。而将所需的边长乘以vector3的X,Y和Z分量就可以简单的进行比例缩放。另外,如果其中一个轴的值被设置为零,这一点始终分布在一个单独的平面。例如,选择"地面"上的一个随机点通常是把Y分量设为零,然后随机设置X和Z分量值。
当它是一个球体时(即:当你想从给定的半径内获取一个随机点),您可以使用所需的半径大小乘以Random.insideUnitSphere:
var randWithinRadius = Random.insideUnitSphere * radius;
注意:如果你设定了Vector中的某个向量值为零,你获得的随机点将"不会"围成一个圆。虽然这一点的获取确实是随机的且半径也是正确的。这个点分布的概率在很大程度上偏向圆的边缘且极不均匀。要让它分布在圆上的话可以使用Random.insideUnitCircle做到。
var randWithinCircle = Random.insideUnitCircle * radius;