Добавление случайных элементов в игру
Случайно выбираемые значения или предметы являются важной частью множества игр. В этих разделах показаны способы использования встроенного в Unity функционала генерации случайных значений для реализации некоторых основных игровых механик.
Выбор случайного элемента в массиве
Выбор случайного элемента массива водится к выбору случайного значения в диапазоне от нуля до максимального значения индекса в массиве (который на 1 меньше длины массива). Это сделать довольно просто, используя встроенный метод Random.Range:-
var element = myArray[Random.Range(0, myArray.Length)];
Учтите, что диапазон, из которого метод Random.Range возвращает значение, включает первый аргумент, но не включает второй аргумент. Так что, если в качестве второго аргумента передавать myArray.Length, вы получите правильный результат.
Выбор элементов с разной вероятностью
Иногда, вам требуется случайно выбрать элементы, но при этом некоторые из них должны выбираться с большей вероятностью, чем другие. Например, NPC может реагировать по-разному при встрече с игроком:-
- с вероятностью 50% он дружелюбно поприветствует игрока
- с вероятностью 25% он убежит
- с вероятностью 20% он немедленно начнёт атаковать
- с вероятностью 5% он предложит деньги в качестве подарка
Вы можете представить эти разные реакции в качестве отрезков, каждый из которых занимает определённую площадь на кусочке бумажной ленты, в сумме занимая всю площадь кусочка. Занимаемая отрезком площадь эквивалентна вероятности реакции, соответствующей данному отрезку. Совершение выбора в таком случае эквивалентно указанию на случайную точку на протяжении всего кусочка бумажной ленты (так сказать, бросанию дротика) с последующим выяснением того, в какой секции находится эта точка.
В коде, кусочек бумажной ленты — это на самом деле массив float чисел, содержащий упорядоченный список вероятностей различных элементов. Случайная точка получается с помощью умножения Random.value на сумму всех float значений в массиве (в сумме они не должны превышать 1; нам важен относительный размер различных значений). Чтобы определить, в какой элемент массива “попала” точка, сперва проверьте не меньше ли она значения первого элемента. Если меньше, тогда первый элемент и выбирается. Иначе, вычтите значение первого элемента массива из значения полученной точки и сравните результат со вторым элементом и так далее, до тех пор, пока не найдётся правильный элемент. В коде это может выглядеть как-то так:-
//JS function Choose(probs: float[]) < var total = 0; for (elem in probs) < total += elem; >var randomPoint = Random.value * total; for (i = 0; i < probs.Length; i++) < if (randomPoint < probs[i]) return i; else randomPoint -= probs[i]; >return probs.Length - 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; >
Заметьте, что в данном случае необходимо наличие последнего оператора возврата, т.к. Random.value может вернуть 1 и тогда поиск никогда не найдёт случайно выбранную точку. Изменение строки
if (randomPoint < probs[i])
Weighting continuous random values
The array of floats method works well if you have discrete outcomes, but there are also situations where you want to produce a more continuous result - say, you want to randomize the number of gold pieces found in a treasure chest, and you want it to be possible to end up with any number between 1 and 100, but to make lower numbers more likely. Using the array-of-floats method to do this would require that you set up an array of 100 floats (i.e. sections on the paper strip) which is unwieldy; and if you aren’t limited to whole numbers but instead want any number in the range, it’s impossible to use that approach.
A better approach for continuous results is to use an AnimationCurve to transform a ‘raw’ random value into a ‘weighted’ one; by drawing different curve shapes, you can produce different weightings. The code is also simpler to write:
//JS function CurveWeightedRandom(curve: AnimationCurve) < return curve.Evaluate(Random.value); >//C# float CurveWeightedRandom(AnimationCurve curve)
A ‘raw’ random value between 0 and 1 is chosen by reading from Random.value. It is then passed to curve.Evaluate(), which treats it as a horizontal coordinate, and returns the corresponding vertical coordinate of the curve at that horizontal position. Shallow parts of the curve have a greater chance of being picked, while steeper parts have a lower chance of being picked.
Notice that these curves are not probability distribution curves like you might find in a guide to probability theory, but are more like inverse cumulative probability curves.
By defining a public AnimationCurve variable on one of your scripts, you will be able to see and edit the curve through the Inspector window visually, instead of needing to calculate values.
This technique produces floating-point numbers. If you want to calculate an integer result - for example, you want 82 gold pieces, rather than 82.1214 gold pieces - you can just pass the calculated value to a function like Mathf.RoundToInt().
Перемешивание списка
Довольно часто встречающаяся в играх механика - выбор из известного набора элементов, но со случайным порядком. Например, колода карт обычно перемешана, поэтому их не выбрать в предсказуемой последовательности. Вы можете перемешивать элементы в массиве путём “посещения” каждого из элементов и его обмена местами с другим элементом, случайно выбранным из массива:-
//JS function Shuffle(deck: int[]) < for (i = 0; i < deck.Length; i++) < var temp = deck[i]; var randomIndex = Random.Range(0, deck.Length); deck[i] = deck[randomIndex]; deck[randomIndex] = temp; >> //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; >>
Выбор элементов из набора без повторений
Распространённая задача - случайно выбрать какое-то количество элементов из массива без повторного выбора одного и того же значения. Например, вы можете захотеть сгенерировать какое-то количество NPC в случайных точках генерации, но при этом вы желаете, чтобы только один NPC генерировался в каждой из точек. Это можно реализовать с помощью перебора последовательности элементов, решая для каждого случайным образом - быть ему добавленным в выбранный набор или нет. После “посещения” каждого элемента, вероятность того, что он будет выбран равна числу ещё требующихся элементов, разделённому на число оставшихся для выбора элементов.
В качестве примера, представьте, что существует десять точек генерации, но выбрать можно только пять. Вероятность выбора первого элемента будет равна 5 / 10 или 0.5. Если он выбран, то вероятность выбора второго элемента - 4 / 9, или 0.44 (то есть требуется ещё 4 элемента и ещё 9 доступно для выбора). Однако, если первый элемент не был выбран, то вероятность выбора второго элемента - 5 / 9, или 0.56 (то есть ещё требуется выбрать 5 элементов и ещё 9 доступно для выбора). Это продолжается до тех пор, пока набор не будет состоять из требуемых пяти элементов. Вы можете реализовать это в коде таким образом:-
//JS var spawnPoints: Transform[]; function ChooseSet(numRequired: int) < var result = new Transform[numRequired]; var numToChoose = numRequired; for (numLeft = spawnPoints.Length; numLeft >0; numLeft--) < // Adding 0.0 is simply to cast the integers to float for the division. var prob = (numToChoose + 0.0) / (numLeft + 0.0); if (Random.value > return result; > //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 > > 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;
Создаём рандомный спавн объектов
Бывают случаи, когда при определённых условиях нам необходимо создать случайный объект на игровой сцене. Например, при нажатии на кнопку, или при уничтожении ящика, из него должно выпасть случайный предмет. Давайте же это сейчас реализуем.
Для начала в окне Hierarchy создадим пустой объект(Create Empty), и назовём его SpawnArea. Этот объект будет являться нашим спавнером, и именно в нём будет спавниться случайный объект. Так же необходимо создать несколько префабов для спавна. У меня всего три префаба: арбуз, груша и яблоко.
Спавнер вместе с прифабами готов. Осталось написать скрипт. Создаём C# скрипт с названием Spawner, и заранее присвойте его к объекту SpawnArea. Впишите в него следующий код:
using UnityEngine; public class Spawner : MonoBehaviour < public GameObject[] obj; private void Start() < Spawn(); >public void Spawn() < int random = Random.Range(0, obj.Length - 1); Instantiate(obj[random], transform); >>
Предлагаю вкратце разобрать этот код:
- В строке #5 мы создали массив объектов для спавна. Там будут хранится ссылки на префабы моего арбуза, яблока и груши.
- Так же мы создали метод Spawn() в котором будет генерироваться случайное число от нуля, до длины нашего массива - 1. В нашем случае получается от 0 до 2.
- Ну а запускать метод Spawn() мы будем в методе Start(), который сработает сразу же при старте игры.
И остался последний штрих. Необходимо в окне Inspector для нашего скрипта Spawner в поле Obj перетащить все наши префабы.
Можете запустить свой проект, и сразу же при старте игры на игровой сцене появится случайный объект.
Хочу обратить ваше внимание, что запуск Spawn() в методе Start() был всего лишь демонстративным. Его мы можете запускать когда угодно, например, при клике на определённую кнопку, или сразу же после уничтожения того самого ящика, или врага.
Бывают случаи, когда нам необходимо создавать объекты не единожды, а многократно, например, каждые 5 секунд, и не в одной точке пространства, а в случайных, то советую посмотреть мой предыдущий гайд, который называется Как создать спавн объектов в Unity.
А на этом всё. Если есть вопросы - пишите в комментариях, и не забывайте о лайках 🙂
Как создать спавн объектов в Unity
Приветствую начинающих разработчиков! В данной статье мы научимся спавнить объекты на игровой сцене на примере 2D игры. Хотя он подойдёт и для 3д, но с минимальными изменениями в коде. Долго затягивать не будем, а приступим сразу к делу.
Для начала с помощью окна Hierarchy создадим пустой игровой объект(Create Empty), и назовём его SpawnArea. Этот объект будет являться нашим спавнером. Так же создайте префаб того объекта, который будет спавниться. Я создал свой префаб на основе обычного круглого 2д объекта (2D Object - Sprites - Circle). Назвал данный префаб Circle.
Спавнер вместе с префабом готов! Но чтобы этот спавнер работал, необходимо написать скрипт. С помощью окна Project создадим C# скрипт с названием Spawner, и заранее присвойте его нашему объекту с названием SpawnArea. В скрипт пропишите следующие строчки кода:
using UnityEngine; public class Spawner : MonoBehaviour < public GameObject enemyPrefab; public float timeSpawn = 2f; private float timer; private void Start() < timer = timeSpawn; >private void Update() < timer -= Time.deltaTime; if (timer > >
Давайте вкратце разберём код:
- В строке #5 мы создали переменную enemyPrefab, в которой будет храниться ссылка на наш префаб, который будет спавниться.
- В строках #7-8 мы создали две переменные отвечающие за время. В переменной timeSpawn хранится информация о том, сколько необходимо времени, для создания объекта на сцене. А в timer будет хранится сам таймер, по истечении которого будет создаваться объект на игровой сцене. Чуть позже вы поймёте разницу.
- В методе Start() мы в переменную timer заносим значение из timeSpawn.
- В методе Update() мы каждый кадр уменьшаем время нашего таймера timer, и как только оно закончится, то значение timer обновляется, а на игровой сцене, с помощью метода Instantiate() создаётся новый объект.
И в завершении необходимо настроить наш скрипт в окне Inspector:
- В поле Enemy Prefab перетащите префаб Вашего объекта Circle.
- В поле Time Spawn укажите время спавна для таймера. Я указал значение 2.
Готово! На этом этапе можете запустить свой проект, и убедиться, что всё работает успешно! Каждые 2 секунды происходит создание нового игрового объекта.
Спавнер созданный выше хорошо работает, но предлагаю его немного усовершенствовать, поскольку мне не нравятся в нём две вещи. Во-первых, все объекты создаются в одной точке, а хотелось бы, чтобы они создавались в случайной области в пределах определённого радиуса. А во-вторых, хотелось бы, чтобы создание объектов не производилось в тех случаях, когда в игре уже присутствует 10 объектов.
Предлагаю чуть чуть переписать наш код следующим образом:
using UnityEngine; public class Spawner : MonoBehaviour < public GameObject enemyPrefab; public int maxEnemy = 5; public float timeSpawn = 2f; private float timer; public float distance = 3; private void Start() < timer = timeSpawn; >private void Update() < timer -= Time.deltaTime; if (timer > > >
Что же мы тут добавили? В строке #6 мы добавили переменную maxEnemy, которая отвечает за максимальное количество созданных объектов на игровой сцене. А в строке #11 в переменной distance храниться множитель для нашего радиуса спавнера, в пределах которого будут создаваться объекты. А сам радиус прописывается в строке #26 через Random.insideUnitCircle.
В строке #24 с помощью свойства childCount мы проверяем, сколько у нас имеется созданных объектов. И если их число меньше 10, то происходит создание нового объекта, иначе - ничего не делать.
Наш скрипт готов! Теперь запустим его, и увидим следующую картину. Каждые 2 секунды в нашем проекте создаётся по одному объекту, в случайной точке, радиусом в 3, от нашего спавнера. И как только было создано 10 объектов, спавнер прекращает свою работу.
Но и этот спавн можно усовершенствовать. Мы можем спавнить не один и тот же объект, а разные объекты, которые будут спавниться случайным образом. Но это уже совсем другая история. О том, как это можно реализовать, можете прочитать наш отдельный гайд, под названием «Создаём рандомный спавн объектов».
На этом наш гайд подошёл к концу. Если остались вопросы, задавайте их в комментариях. А так же не забывайте ставить свои лайки.
Случайный спавн обьектов
Создаешь объект спавна и в него расчитываешь случайный спавн, а дальше пользуешься уже им:
var spawn = spawnsArray[Random.Range(0, spawnsArray.Lenght)]; //spawn.SpawnEnemy();
Отслеживать
ответ дан 7 июн 2020 в 1:14
Psyxoz0x13 Psyxoz0x13
578 3 3 серебряных знака 7 7 бронзовых знаков
-
Важное на Мете
Похожие
Подписаться на ленту
Лента вопроса
Для подписки на ленту скопируйте и вставьте эту ссылку в вашу программу для чтения RSS.
Дизайн сайта / логотип © 2024 Stack Exchange Inc; пользовательские материалы лицензированы в соответствии с CC BY-SA . rev 2024.4.30.8420