Использование случайности
Класс Random даёт простые способы получения самых востребованных типов случайных значений.
На этой странице дан обзор класса Random и типичных сценариев его использования в скриптах. Полный справочник по всем членам класса Random и дополнительные технические подробности см. в Случайная ссылка на скрипт.
Для получения более подробной информации и примеров этих полезных методов следуйте ссылкам ниже.
Простые случайные числа
Random.value даёт вам случайное число с плавающей точкой между 0.0 и 1.0. Обычно используется для преобразования его в число между нулем и диапазоном по вашему выбору путем умножения результата.
Random.Range возвращает случайное число между минимальным и максимальным значениями, которые вы указываете. Возвращается либо целое число, либо число с плавающей запятой, в зависимости от того, являются ли значения min и max целыми числами или числами с плавающей запятой.
Случайные точки в кругах или сферах
Random.insideUnitCircle возвращает случайно выбранную точку внутри окружности с радиусом 1 (опять же, вы можете умножить результат, чтобы получить случайную точку внутри окружности любого размера).
Random.insideUnitSphere возвращает случайно выбранную точку внутри сферы с радиусом 1.
Random.onUnitSphere возвращает случайно выбранный пункт на поверхность сферы радиусом 1.
Random.onUnitCircle возвращает случайно выбранную точку на окружности круга с радиусом 1.
Другие типы случайных значений
Класс random в Unity предлагает и несколько других типов случайных значений.
Чтобы создать случайное ротация, использование Random.rotation.
Чтобы создать случайное цвет, использование Random.ColorHSV.
Выбор случайного элемента из массива
Выбор элемента массива случайным образом сводится к выбору случайного целого числа между нулем и максимальным значением индекса массива (которое равно длине массива минус один). Это легко сделать с помощью встроенной функции Random.Range:
var element = myArray[Random.Range(0, myArray.Length)];
Обратите внимание, что Random.Range возвращает значение из диапазона, который включает первый параметр, но исключает второй, так что использование myArray.Length здесь дает правильный результат.
Выбор элементов с разными вероятностями
Иногда вам нужно выбирать предметы случайным образом, но некоторые предметы более вероятны, чем другие. Например, NPC может реагировать несколькими различными способами, когда он встречает игрока:-
- 50% шанса на дружеский привет
- 25% шанса на бегство
- 20% шанса на немедленную атаку
- 5% шанс подарить деньги
Вы можете визуализировать эти различные результаты как бумажную полоску, разделенную на секции, каждая из которых занимает долю от общей длины полоски. Занятая доля равна вероятности выбора этого результата. Выбор эквивалентен выбору случайной точки по длине полоски (скажем, бросив дартс), а затем посмотреть, в какой секции она находится.
В скрипте бумажная полоска на самом деле является массивом плавающих чисел, которые содержат различные вероятности для элементов в порядке. Случайная точка получается путем умножения 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])
... к тесту менее-чем-или-равным избежал бы дополнительного возврата, но также позволил бы элементу быть выбранным иногда, даже когда его вероятность равна нулю.
Взвешивание непрерывных случайных значений
Метод массива поплавков хорошо работает, если у вас есть дискретные результаты, но есть и ситуации, когда вы хотите получить более непрерывный результат — скажем, вы хотите произвольно распределить число золотых монет, найденных в сокровищнице, и вы хотите, чтобы было возможно получить любое число от 1 до 100, но сделать более вероятными низкие числа. Использование метода массива поплавков для этого потребовало бы создания массива из 100 поплавков (i.e. секций на бумажной полосе), что громоздко; и если вы не ограничиваетесь целыми числами, а хотите получить любое число в диапазоне, невозможно использовать этот подход.
Лучшим подходом для непрерывных результатов является использование AnimationCurve для преобразования «сырого» случайного значения в «взвешенное»; рисуя различные формы кривых, вы можете получить различные веса. Код также проще написать:
float CurveWeightedRandom(AnimationCurve curve) {
return curve.Evaluate(Random.value);
}
«Необработанное» случайное значение между 0 и 1 выбирается путем чтения из Random.value. Затем оно передается curve.Evaluate(), который рассматривает его как горизонтальную координату и возвращает соответствующую вертикальную координату кривой в этом горизонтальном положении. Мелкие части кривой имеют больший шанс быть выбранными, в то время как более крутые части имеют меньший шанс быть выбранными.
Обратите внимание, что эти кривы не являются кривой распределения вероятности, как вы можете найти в руководстве по теории вероятностей, но больше похожи на обратные кумулятивные кривы вероятности.
Объявив публичный 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;
}
}
Выбор из набора элементов без повторения
Общая задача состоит в том, чтобы случайно выбрать несколько предметов из набора, не выбирая один и тот же предмет более одного раза. Например, вы можете пожелать создать несколько NPCs в случайных точках появления, но убедитесь, что только один NPC будет создан в каждой точке. Это может быть сделано путем итерации через предметы в последовательности, делая случайное решение для каждого, добавляется ли он в выбранный набор или нет. По мере посещения каждого предмета, вероятность его выбора равна количеству необходимых предметов, разделенному на количество, оставшееся для выбора.
В качестве примера, предположим, что есть десять точек скопления, но только пять из них должны быть выбраны. Вероятность выбора первого элемента будет 5 / 10 или 0.5. Если он выбран, то вероятность для второго элемента будет 4 / 9 или 0.44 (то есть, четыре элемента все еще необходимы, девять осталось выбрать). Однако, если первый не был выбран, то вероятность для второго будет 5 / 9 или 0.56 (то есть, пять все еще необходимы, девять осталось выбрать). Это продолжается до тех пор, пока набор не содержит пять необходимых элементов. Вы можете выполнить это в коде следующим образом:-
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;