# 使用随机性

> Use the Random class to generate various types of random values commonly required in game development.

Random 类为您提供了简便的方法，可生成各种常用类型的随机值。

此页面概述了 Random 类以及使用该类编写脚本时的常见用法。如需了解 Random 类各成员的详尽参考以及该类的技术细节，请参阅 [Random 脚本参考](/engine/6000.0/script-reference/unity-engine/random.md)。

请点击以下链接，了解这些有用方法的更多详细信息和示例。

## 简单的随机数

[`Random.value`](/engine/6000.0/script-reference/unity-engine/random/value.md) 可以随机生成一个介于 0.0 和 1.0 之间的**浮点数**。通常的用法是用乘法将其转换为一个介于 0 和您所选范围之间的数字。

[`Random.Range`](/engine/6000.0/script-reference/unity-engine/random/range.md) 可以在给定的**最小值和最大值**之间随机生成一个数字。结果可以是整数或浮点数，具体取决于提供的最小值和最大值是整数还是浮点数。

## 圆形或球体内的随机点

[`Random.insideUnitCircle`](/engine/6000.0/script-reference/unity-engine/random/inside-unit-circle.md) 会返回一个在半径为 1 的圆内随机选择的**圆内点**（同样可以对结果做乘法，从而获得任意大小的圆内的随机点）。

[`Random.insideUnitSphere`](/engine/6000.0/script-reference/unity-engine/random/inside-unit-sphere.md) 会返回一个在半径为 1 的球体内随机选择的**球内点**。

[`Random.onUnitSphere`](/engine/6000.0/script-reference/unity-engine/random/on-unit-sphere.md) 会返回一个在半径为 1 的球面上**随机选择的的\_球面\_点**。

## 其他类型的随机值

Unity 的 Random 类还提供了一些其他类型的随机值。

要生成随机的**旋转**，请使用 [`Random.rotation`](/engine/6000.0/script-reference/unity-engine/random/rotation.md)。

要生成随机的**颜色**，请使用 [`Random.ColorHSV`](/engine/6000.0/script-reference/unity-engine/random/color-hsv.md)。

## 从数组中选择一个随机项

随机选取一个数组元素归结为选择零和数组最大索引值（等于数组的长度减去 1）之间的一个随机整数。使用内置的 `Random.Range` 函数即可轻松实现这目的：

`var element = myArray[Random.Range(0, myArray.Length)];`

请注意，`Random.Range` 从包含第一个参数但不包含第二个参数的范围内返回一个值，因此在此处使用 `myArray.Length` 会得到正确的结果。

## 选择具有不同概率的项

有时需要随机选择项，但有些项比其他项被选中的几率更高。例如，NPC 在遇到玩家时可能会以几种不同的方式做出反应：

* 友好问候的几率为 50%
* 逃跑的几率为 25%
* 立即攻击的几率为 20%
* 提供金钱作为礼物的几率为 5%

可将这些不同的结果可视化为一张纸条，该纸条分成几个部分，每个部分占据纸条总长度的一部分。占据的比例等于选择结果的概率。选择行为相当于沿着纸条的长度选择一个随机点（例如通过投掷飞镖），然后查看该点处于哪个部分。


**Frame:**
![](/api/media?file=/engine/6000.0/media/images/ProbStrip.png)

在脚本中，纸条实际上是一个浮点数组，其中的浮点数按顺序包含项的不同概率。随机点是通过将 `Random.value` 乘以数组中所有浮点数的总和得到的（这些数值不需要加起来等于 1；重点是不同值的相对大小）。要找到该点“位于”哪个数组元素中，首先要检查它是否小于第一个元素中的值。如果是，则第一个元素便是选中的元素。否则，从该点值中减去第一个元素的值，然后将其与第二个元素进行比较，依此类推，直到找到正确的元素。在代码中表示为以下所示的内容：

```
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 作为结果。这时，搜索将无法在任何地方找到随机点。将以下行

`if (randomPoint < probs[i])`

...更改为“小于或等于”测试将避免额外的 return 语句，但也会允许偶尔选择某个项，即使其概率为零也是如此。

## 加权连续随机值

如果结果是不连续的，那么浮点数组方法会很有效，但在某些情况下希望产生更连续的结果；比如说，希望随机化一个宝箱中发现的金块数量，并希望能够出现 1 到 100 之间的任何数字，但让更小数字的概率更高。使用浮点数组方法来执行此算法将需要设置一个包含 100 个浮点数（即纸条上的部分）的数组，这是很不实用的方法；如果不局限于整数而是想要在该范围内的任何数字，则不可能使用这种方法。

一种更适合连续结果的方法是使用 `AnimationCurve` 将“原始”随机值转换为“加权”值；绘制不同的曲线形状即可产生不同的权重。代码编写起来也更简单：

```
float CurveWeightedRandom(AnimationCurve curve) {
    return curve.Evaluate(Random.value);
}
```

此算法从 ``Random.value` 读取值来选择 0 到 1 之间的“原始”随机值。然后，该值传递给 \`curve.Evaluate()``，在此处将其视为水平坐标，并返回曲线在该水平位置处的相应垂直坐标。曲线较平缓的部分被选取的几率较高，而较陡峭的部分被选取的几率较低。


**线性曲线对值不进行任何加权；曲线上每个点的横坐标都等于纵坐标。:**
![](/api/media?file=/engine/6000.0/media/images/WeightedRandomCurve-linear.png)


**这条曲线在开始时较平缓，然后在结束时变得越来越陡峭，因此较低值的几率较高，而较高值的几率较低。此处可以看到 x=0.5 时曲线的高度约为 0.25，这意味着有 50% 的几率得到 0 到 0.25 之间的值。:**
![](/api/media?file=/engine/6000.0/media/images/WeightedRandomCurve-bias-low.png)


**这条曲线在开始和结束时都很平缓，因此这些值接近极值的几率较高，而中间的陡峭部分表示得到这些值的几率较低。另外请注意，使用此曲线时，高度值会向上移动：曲线底部为 1，而曲线顶部为 10，这意味着曲线产生的值将在 1-10 范围内，而不是像之前的曲线那样在 0-1 的范围内。:**
![](/api/media?file=/engine/6000.0/media/images/WeightedRandomCurve-bias-lowhigh.png)

请注意，这些曲线并非概率论指南中可能介绍的概率分布曲线，而更像是反向累积概率曲线。

通过在任一脚本中定义一个公共 `AnimationCurve` 变量，就可以使用检视器 (Inspector) 窗口直观地查看并编辑曲线，而无需计算值。

这种方法会产生浮点数。如果要计算整数结果（例如，需要 82 个金块，而不是 82.1214 个金块），可将计算值传递给 `Mathf.RoundToInt()` 之类的函数。

## 列表洗牌

一种常见的游戏机制是从一组已知的项中进行选择，但让这些项以随机顺序到达。例如，一副纸牌通常需要洗牌，因此不会以可预测的顺序绘制。为了对数组中的项进行随机洗牌，可访问每个元素，然后将其与数组中位于随机索引处的另一个元素进行交换：

```
void Shuffle (int[] deck) {
	for (int i = 0; i < deck.Length; i++) {
		int temp = deck[i];
		int randomIndex = Random.Range(i, deck.Length);
		deck[i] = deck[randomIndex];
		deck[randomIndex] = temp;
	}
}
```

## 从一组无重复的项中选择

一种常见的任务是从一组中随机选取一些项，但不可多次选取同一项。例如，可能希望在一些随机生成点生成多个 NPC，但要确保每个点只生成一个 NPC。为实现此目的，可按顺序遍历这些项，随机决定是否将每一项添加到所选集合中。当访问每一项时，该项被选取的概率等于仍然需要的项数除以仍然可供选择的项数。

例如，假设有 10 个可用的生成点，但只能选择其中 5 个。选择第一项的概率为 5/10，即 0.5。如果选择了该项，那么第二项的概率将是 4/9，即 0.44（即仍然需要 4 项，还剩下 9 项可供选择）。但是，如果未选择第一项，那么第二项的概率将是 5/9，即 0.56（即仍然需要 5 项，还剩下 9 项可供选择）。这一直持续到该集合包含所需的 5 项为止。可使用如下所示的代码实现此算法：

```
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;
}
```

请注意，虽然选择是随机的，但所选集合中的项与原始数组中的项具有相同的顺序。如果要按顺序一次使用一项，那么这种排序可能使它们在一定程度上可预测，因此在使用之前可能需要对数组进行洗牌。

## 空间中的随机点

通过将 Vector3 的各个分量设置为 `Random.value` 返回的值，就可以选择立方体体积中的随机点：

`var randVec = Vector3(Random.value, Random.value, Random.value);`

这种算法可在边长为一个单位的立方体内部给出一个点。只需将矢量的 X、Y 和 Z 分量乘以期望的边长即可缩放该立方体。如果其中一个轴设置为零，则该点将始终位于单个平面内。例如，要在“地面”上选取随机点，通常只需随机设置 X 和 Z 分量，并将 Y 分量设为零。

当体积为球体时（即需要一个从原点出发、在给定半径内选取的随机点），则可用 `Random.insideUnitSphere` 乘以所需的半径：

`var randWithinRadius = Random.insideUnitSphere * radius;`

请注意，如果将结果矢量的某个分量设置为零，则*无法*在圆内获得正确的随机点。尽管该点确实是随机点且位于正确的半径范围内，但是概率严重偏向于圆心，因此点的分布将非常不均匀。对于此任务，应改用 `Random.insideUnitCircle`：

`var randWithinCircle = Random.insideUnitCircle * radius;`
