Создадим систему появления грибов в случайных местах и придумаем разные виды грибов. Бегать по всему лесу и сажать грибы для нашего героя, дело хлопотное и неблагодарное - пускай матушка-природа сама занимается воспроизведением собственных ресурсов, ну а мы ей в этом поможем! Не забудьте подписаться на канал и вперёд в сказку! Водку купим по дороге!
Создаём список игровых объектов
Списки и массивы - это очень нужные элементы в программировании. Хороший программист - это ленивый программист, он не будет заниматься каждым объектом отдельно, а объединит всё в какие-нибудь группы и сделает так, чтобы эта группа обрабатывалась целиком и желательно автоматически, вообще без его участия.
Вы уже возможно догадываетесь на что я намекаю) Если мы хотим, чтоб в нашей игре было много грибов, они появлялись в случайных местах и будучи съеденными, со временем опять где-то вырастали, то нам нужен их список!
Давайте создадим скрипт ItemHandler, который будет отвечать за появление грибов и вообще их всячески контролировать. Объявим в нём список объектов типа Mushroom (List<Mushroom>), под очень говорящим названием mushrooms.
Давайте специально для скрипта ItemHandler создадим отдельный пустой объект внутри объекта Game и повесим его туда. Поскольку мы сделали список публичным, то мы можем видеть его в инспекторе. Если тыкнуть на стрелочку рядом с названием списка в инспекторе, то он раскроется и мы увидим все "ноль" грибов, которые в нём содержаться. Ну то есть ничего не увидим, т.к. изначально он пустой.
Поскольку в треугольных скобках при объявлении списка мы указали тип Mushroom, то ничего кроме экземпляров "грибного" скрипта в этом списке хранить не получится. Но грибов сколько угодно! Давайте перетащим из окна иерархии грибы прямо на название списка в инспекторе. По одному. Если хотим перетащить все грибы сразу, то прежде чем их выделить с помощью клик+CTRL, нам нужно заблокировать окно инспектора с помощью иконки замка. И не забыть потом разблокировать, а то окно инспектора перестанет меняться и это может вызвать замешательство и даже панику! :)
Теперь когда все наши грибы помещены в список мы можем обращаться к ним через этот список по номерам элементов: mushrooms[0], mushrooms[1], mushrooms[2]. Очень удобно! Если не считать, что счёт идёт с нуля - к этому надо привыкнуть.
Появление грибов в случайных местах
Чтобы грибы в начале игры появлялись в случайных местах нам понадобится полезная функция Random.Range(). В качестве аргументов этой функции мы указываем диапазон в виде 2 чисел через запятую и каждый раз будем получать случайное число из этого диапазона. Зададим каждую координату позиции гриба с помощью рандома, а чтобы не делать это для каждого гриба отдельно нам понадобится наш список mushrooms и цикл for. Создадим метод SpawnMushrooms(), который будет рандомно расставлять грибы в начале игры.
Цикл for будет выполнять всё, что мы в него пропишем, для каждого значения i, которое меньше, чем количество элементов в нашем списке mushrooms. Значение i должно быть меньше, чем количество элементов потому-что счёт в списке идёт от 0 и третьего элемента там не будет, хотя количество элементов 3.
Границы для случайных координат я выбрал такие, чтобы грибы не появлялись за пределами имеющейся карты - получилось плюс-минус 20 метров от начала координат, которое совпадает с центром нашей карты и с первоначальной позицией игрока. А вот высоту я выбрал просто для визуального разнообразия - какие то грибы будут пониже, какие-то повыше торчать из земли.
Теперь пропишем вызов нашего нового метода в методе Start() и давайте проверим, как оно работает.
Запускаем режим Play и чтобы долго грибы не искать, переходим в окно сцены, выделяем все грибы в иерархии, отдаляем камеру и сразу видим где они все находятся. Работает - грибы появились в случайных местах!
Автоматическое появление новых объектов
Точнее, в нашем случае это будет появление уже съеденных грибов в новых местах через какое-то время. Предлагаю сделать так, чтобы каждый раз когда главный герой съедает гриб, то запускался специальный таймер, который бы отсчитывал время respawnTime и включал этот гриб, переместив его на новое случайное место.
Чтобы не изобретать велосипед, я предлагаю сделать таймер с помощью асинхронного метода. Это такие методы, которые выполняются сами по себе, в своём темпе, с любыми задержками. Вот как раз в качестве задержки у нас и будет время респауна.
Создадим в скрипе ItemHandler асинхронный метод для респауна, который будет еще и таймером. Для этого перед методом нужно написать async, а внутри использовать хотя бы одну асинхронную функцию, например await Task.Delay(). И снова VisualStudio не понимает, что мы от него хотим, но догадливо предлагает в качестве возможного решения библиотеку System.Threading.Tasks. Берём! Кто мы такие, чтоб отказываться! :)
Добавим переменную respawnTime, а в методе пропишем её в качестве аргумента для асинхронного метода задержки Task.Delay(). После того как время задержки истечёт, включаем объект гриба.
Остаётся добавить перемещение гриба на новое случайное место. У нас уже есть логика случайного размещения грибов в начале игры - давайте ей воспользуемся и вынесем её в отдельный метод RandomPosition(), который на вход будет получать ссылку на гриб, который нужно переместить. А еще надо учесть, что Task.Delay() на вход принимает время в миллисекундах, поэтому respawnTime надо умножить на 1000, иначе грибы будут появляться практически моментально.
Ну, а в методе RespawnTimer() вызовем метод RandomPosition для собранного гриба сразу как только асинхронная задержка равная respawnTime закончится. Теперь осталось только в скрипте самого гриба получить ссылку на ItemHandler и вместо отключения гриба вызвать RespawnTimer, который его и отключит, и переместит, и включит. Для начала сделаем ссылку на ItemHandler в скрипте Game.
Теперь учитывая, что в скрипте Mushroom мы в момент взаимодействия с игроком, получаем на него ссылку, а сам игрок уже имеет ссылку на скрипт Game, то мы можем из скрипта Mushroom обратится к нужному методу через вот такую цепочку ссылок: player.game.itemHandler.RespawnTimer()
Это не очень красиво и не особо правильно, но пока пойдёт. Указать скриптом ссылку на самого себя можно с помощью оператора this.
Не забывая в инспекторе проставить ссылку скрипта Game на скрипт ItemHandler, запускаем и проверяем. Если все ссылки везде указаны, то все заработает и грибы на 5 секунд будут выключаться и появляться в другом месте. Теперь наш главный герой сможет практически бесконечно ходить по лесу и искать новые (или хорошо забытые старые) грибы.
Разные версии одного вида объектов
Как я и обещал, давайте сделаем разные виды грибов, дабы разнообразить флору нашего сказочного леса и сделать выживание главного героя более интересным. Предлагаю сделать ядовитый гриб и МегаГриб-3000 Дефинитив Эдишн Плюс. Второй для удобства будем называть МегаГриб и это будет отсылка к крутой аптечке из Quake 2 под названием MegaHealth.
Применим 2 разных подхода для их создания. Ядовитость сделаем в качестве еще одного параметра гриба, а МегаСвойства мы сделаем уникальными для конкретного вида гриба.
Ядовитый гриб
Ядовитость как свойство сделаем присущей всем грибам. У обычных грибов значение poison сделаем 0, а у ядовитых например 20. При съедании ядовитого гриба будем вычитать из здоровья персонажа значение poison.
Зайдём в нашу папку префабов и создадим дубликат имеющегося префаба гриба с помощью CTRL+D. Два раза щелкнем на него, чтобы отредактировать. Назовём его PoisonedMushroom. Пропишем ему значение poison 20. А в настройках материала шляпки гриба сделаем цвет более красным.
Только вот незадача - обычные грибы тоже поменяли цвет. Это потому, что они используют один и тот же материал, а мы просто меняем его настройки. Давайте найдем на грибе компонент MeshRenderer, в нём найдём список материалов Materials и тыкнем на первый из них. Это материал шляпки и он отобразился в окне проекта. Давайте сделаем два его дубликата - один для ядовитого гриба, другой для МегаГриба.
Теперь давайте выберем цвета для разных грибов какие захотим. Я пожалуй обычные грибы оставлю коричневыми, ядовитые сделаю красными, а МегаГриб синим. Для удобства я создам в нашей папке Textures папку для материалов грибов под названием Mushrooms и перетащу их туда.
Теперь надо убедится, что нужные материалы на нужных грибах, а если нет, то выбрать нужные. Материалы можно даже просто перетащить на нужную часть объекта. В нашем случае это надо делать в режим редактирования префаба.
В общем-то ядовитый гриб готов - перетаскиваем его на сцену и проверяем.
Работает. Съедаем грибы и здоровье уменьшается на 20. Но если сразу выключить режим Play, то мы получаем ошибку.
Она возникает потому-что асинхронный метод работает даже после выключения игры и пытается заспавнить гриб даже когда игра уже выключена. Чтобы этого не было давайте добавим проверку, что объект не равен null (т.е. существует и не был уничтожен).
Это не обязательно в нашем случае, но вообще лучше стараться, чтобы ошибок вообще никаких в игре не происходило.
МегаГриб и наследование логики
Третий вариант гриба мы создадим с помощью наследования. Он будет содержать в себе весь функционал обычных грибов, но иметь свои уникальные фишки. А уникальной фишкой у него будет восполнение здоровья и сытости на максимум, а также увеличение максимального значения здоровья на 1.
Добавим в скрипте Game новую переменную maxHealth и подправим метод ChangeHealth() с учётом переменного значения максимального здоровья.
Добавим также метод для изменения максимального здоровья, чтобы не менять его напрямую и с определенными условиями.
Теперь самое интересно! Создадим новый скрипт MegaMushroom и сделаем ему наследование от Mushroom.
Новый скрипт MegaMushroom хоть и выглядит пустым, но содержит все поля и методы скрипта Mushroom. А вот скрипт Mushroom немного подредактируем - вынесем реакцию на взаимодействие с игроком в отдельный метод, который сделаем виртуальным (virtual).
Теперь этот виртуальный метод наш МегаГриб сможет выполнять по своему, либо с какими-то своими дополнениями - нам нужно только переписать в нём этот метод с приставкой override.
В методе с приставкой override мы можем обратится к родительскому классу Mushroom с помощью обращения base и выполнить всю его логику. Но в дополнение к этому пропишем увеличение максимального здоровья и восполнения здоровья до максимума. После этого остаётся только в параметрах гриба выставить его сытость равно 100, т.к. пока это максимальный предел, а poison равной 0. Ну и давайте сделаем его побольше это же МегаГриб!
Главное не забыть убрать с префаба МегаГриба скрипт Mushroom, а вместо него добавить MegaMushroom. Проверим! Всё работает. МегаГриб восполняет здоровье до 101, потом респавнится, можно еще раз его собрать и будет уже 102. Остаётся добавить новые грибы в список грибов и тогда в начале игры они тоже появятся в случайном месте. Вот такие пока у нас приключения в грибном царстве! :)
В следующем уроке попробуем добавить какую-нибудь живность и сделать выживание нашего грибника более суровым и непредсказуемым! Ну, а пока подписывайтесь на канал, чтобы его не пропустить и всегда иметь возможность изучить предыдущие статьи, чтобы понять откуда что взялось! Если хотите меня поддержать в этом нелегком деле, то лайки и комментарии неплохо мотивирую поскорее выпускать продолжение. :)
Предыдущая статья (6 урок):
Первая статья из этой серии:
А вот тут вся подборка статей-уроков этой серии: