Прибавление очков при соприкосновении с объектом
Всех приветствую. Хочу попробовать написать простенькую игру. И на одном из этапов столкнулся с проблемой. При соприкосновении с объектом он исчезает и издаёт звук. Но мне ещё надо чтобы счётчик обновлялся. Как мне это сделать? Я создал Canvas и внутри него поместил текст. Прилагаю код для уничтожения объекта:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Destroys : MonoBehaviour < public AudioSource audio; private void Start() < audio = GetComponent(); > private void OnTriggerEnter(Collider other) < if (other.gameObject.tag == "bottle_green") < audio.Play(); Destroy(other.gameObject); >if(other.gameObject.tag == "coins") < audio.Play(); Destroy(other.gameObject); >> >
Отслеживать
задан 28 фев 2021 в 19:10
Bestprogrammer Bestprogrammer
15 5 5 бронзовых знаков
1 ответ 1
Сортировка: Сброс на вариант по умолчанию
Возможно, это то, что Вы хотите сделать. Объявите public переменную scoreText и счетчик scoreNum , изначально равный 0. При соприкосновении увеличивайте счетчик и обновляйте значение на экране. Не забудьте в Инспекторе перетащить Ваш Text со счетом на соответствующее поле скрипта.
using UnityEngine; using UnityEngine.UI; public class Destroys : MonoBehaviour < public AudioSource audio; public Text scoreText; private int scoreNum = 0; private void Start() < audio = GetComponent(); > private void OnTriggerEnter(Collider other) < if (other.gameObject.tag == "bottle_green") < audio.Play(); Destroy(other.gameObject); scoreNum++; scoreText.text = "Score: "+ scoreNum; >if (other.gameObject.tag == "coins") < audio.Play(); Destroy(other.gameObject); scoreNum++; scoreText.text = "Score: "+ scoreNum; >> >
И еще я заметила, что у Вас одинаковые действия при соприкосновении с bottle_green и coins . Возможно, это просто ещё недописанная часть, возможно, это для того, чтобы было два эффекта при одновременном соприкосновении, а возможно, это можно объединить и записать функцию OnTriggerEnter так:
private void OnTriggerEnter(Collider other) < if (other.gameObject.tag == "bottle_green" || other.gameObject.tag == "coins") < scoreNum++; scoreText.text = "Score: "+ scoreNum; audio.Play(); Destroy(other.gameObject); >>
Соответственно, этот счёт будет сбрасываться при перезапуске. Если Вам нужно постоянно хранить это значение, можно использовать базу данных или PlayerPrefs . Но это уже другой вопрос.
Создание системы бонусов в Unity

На что бы была похожа игра Sonic The Hedgehog без золотых колец и скоростных ботинок, Super Mario без грибов или Pac-Man без мигающих точек? Все эти игры стали бы намного скучнее!
Бонусы (power-ups) — это важнейший компонент игрового процесса, потому что они добавляют новые уровни комплексности и стратегии, побуждая игрока к действию.
В этом туториале вы научитесь следующему:
- Конструировать и собирать систему бонусов с возможностью многократного применения в других играх.
- Использовать в игре систему связи на основе сообщений.
- Реализовывать всё это в игре с видом сверху, использующей ваши собственные бонусы!
Примечание: в этом туториале подразумевается, что вы знакомы с Unity и обладаете хотя бы средними знаниями C#. Если вам нужно освежить свои знания, то изучите другие наши туториалы по Unity.
Для повторения действий туториала вам потребуется Unity 2017.1 или более новой версии, поэтому обновите свою версию Unity, если ещё этого не сделали.
GIF на 4МБ
Приступаем к работе
Игра, над которой мы будем работать — это двухмерная аркада с видом сверху, где игрок пытается увернуться от врагов; она немного похожа на Geometry Wars, но без стрельбы (и без коммерческого успеха). Наш герой в шлеме должен уворачиваться от врагов, чтобы добраться до выхода; при столкновении с врагами его здоровье уменьшается. Когда здоровье заканчивается, наступает game over.
Скачайте заготовку проекта для этого туториала и извлеките её в нужную папку. Откройте проект в Unity и изучите папки проекта:

- Audio: содержит файлы звуковых эффектов игры.
- Materials: материалы игры.
- Prefabs: содержит префабы (Prefabs) игры, в том числе игровое поле, игрока, врагов, частицы и бонусы.
- Scenes: здесь находится основная сцена игры.
- Scripts: содержит скрипты игры на C# с подробными комментариями. Можете исследовать эти скрипты, если хотите лучше освоиться в них перед началом работы.
- Textures: исходные изображения, используемые для игры и экрана заставки.

Вы увидите, что в игре пока нет бонусов. Поэтому уровень пройти сложно и игра кажется немного скучноватой. Наша задача — добавить бонусы и оживить игру. Когда игрок подбирает бонус, на экране появляется цитата из известной серии фильмов. Посмотрим, сможете ли вы узнать её. Ответ будет в конце туториала!
Цикл жизни бонуса
У бонуса есть цикл жизни, состоящий из нескольких отдельных состояний:

- Первая стадия — это создание, которое выполняется в игровом процессе или на этапе разработки, когда вы вручную располагаете GameObject бонусов в сцене.
- Далее идёт режим привлечения внимания, когда бонусы могут становиться анимированными или иным способом привлекать внимание игрока.
- Стадия сбора — это действие по подбиранию бонуса, которое вызывает срабатывание звуков, систем частиц или других спецэффектов.
- Подбор бонуса ведёт к выполнению полезной нагрузки, при которой бонус «делает своё дело». Полезной нагрузкой может быть всё, что угодно, от скромной прибавки здоровья до наделения игрока какими-то потрясающими сверхспособностями. Этап полезной нагрузки также приводит к срабатыванию проверки срока действия. Можно настроить бонус так, чтобы он переставал действовать после определённого времени, после того, как игрока заденет враг, после нескольких применений или после выполнения любого другого условия игрового процесса.
- Проверка срока действия приводит к стадии завершения. Стадия завершения уничтожает бонус и становится концом цикла.
Создание простого бонуса-звёздочки

Скорее всего, вам знакомы бонусы «начального уровня», представляющие собой золотые монеты, звёзды или кольца, дающие очки или прибавку к здоровью. Сейчас мы создадим в сцене бонус-звезду, которая даёт игроку мгновенную прибавку здоровья и увеличивает шансы героя на прохождение уровня живым.
Чтобы сбежать с уровня, недостаточно будет рассчитывать только на звёзды, так что позже мы добавим и другие бонусы, дающие нашему герою преимущества в бою.
Создайте новый Sprite, назовите его PowerUpStar и расположите прямо над героем в точке (X:6, Y:-1.3). Чтобы сцена была упорядоченной, сделайте спрайт дочерним элементом пустого GameObject PowerUps в сцене:

Теперь зададим внешний вид спрайта. Введите для Transform Scale значения (X:0.7, Y:0.7), в компоненте Sprite Renderer назначьте слоту Sprite звезду, а для Color выберите бледно-коричневый цвет (R:211, G:221, B:200).

Добавьте компонент Box Collider 2D, поставьте флажок Is Trigger и измените Size на (X:0.2, Y:0.2):

Мы только что создали первый бонус! Запустите игру, чтобы убедиться, что всё выглядит хорошо. Бонус появляется, но когда вы пытаетесь его поднять, то ничего не происходит. Чтобы исправить это, нам потребуются скрипты.

Отделяем игровую логику от иерархии классов
Будучи ответственными разработчиками, мы хотим оптимальнее тратить своё время и повторно использовать элементы из предыдущих проектов. Чтобы применить это к системе бонусов, нам нужно создавать её дизайн с иерархией классов. Иерархия классов разделяет логику бонусов на многоразовую часть движка и часть, относящуюся только к конкретной игре. Если вам неизвестна идея иерархий классов и наследования, то у нас есть видео, объясняющее все эти концепции.

На схеме выше показан класс PowerUp в качестве родительского класса. Он содержит независящую от игры логику, поэтому мы можем повторно использовать её «как есть» почти в любом другом проекте. В проекте туториала уже есть родительский класс. Родительский класс управляет циклом жизни бонуса, различными состояниями, которые может иметь бонус, обрабатывает коллизии, подбирание бонуса, полезные нагрузки, сообщения и завершение действия бонуса.
Родительский класс реализует простой конечный автомат, отслеживающий цикл жизни бонуса. Нам нужно реализовать подкласс и значения инспектора для каждого нового бонуса, и на этом всё!
Проверочный список кодирования бонусов
Примечание: для создания скрипта бонуса мы должны создать подкласс класса PowerUp и обеспечить выполнение всех пунктов списка. В туториале мы несколько раз будем обращаться к этому списку, так что держите его под рукой!
- Реализовать PowerUpPayload , чтобы запустить полезную нагрузку.
- Опционально: реализовать PowerUpHasExpired , чтобы убрать полезную нагрузку из предыдущего этапа.
- Вызвать PowerUpHasExpired при истечении срока действия бонуса. Если срок действия истекает сразу же, поставьте в инспекторе флажок ExpiresImmediately, потому что в этом случае нет необходимости вызывать PowerUpHasExpired .
Создаём первый скрипт для бонуса
Добавьте новый скрипт к GameObject PowerUpStar, назовите его PowerUpStar и откройте в редакторе.
Добавьте следующий код, замените бОльшую часть начального boilerplate-кода Unity, оставив только конструкции using в начале.
class PowerUpStar : PowerUp < public int healthBonus = 20; protected override void PowerUpPayload() // Пункт 1 контрольного списка < base.PowerUpPayload(); // Полезная нагрузка заключается в добавлении здоровья playerBrain.SetHealthAdjustment(healthBonus); >>
Код довольно короткий, но его достаточно, чтобы реализовать логику звезды! Скрипт соответствует всем пунктам контрольного списка:
- PowerUpPayload даёт игроку немного здоровья,
вызывая playerBrain.SetHealthAdjustment . Родительский класс PowerUp уже позаботился о получении ссылки на playerBrain . То, что у нас есть родительский класс, означает, что нам придётся вызывать base.PowerUpPayload , чтобы обеспечить выполнение всей базовой логики перед нашим кодом. - Нам не нужно реализовывать PowerUpHasExpired , потому что прибавление здоровья не отменяется.
- Срок действия этого бонуса завершается сразу же, поэтому нам снова не нужно ничего писать; достаточно поставить флажок ExpiresImmediately в инспекторе. Настало подходящее время, чтобы сохранить метод и вернуться в Unity для внесения изменений в инспекторе.
Создание первого бонуса в сцене
После сохранения и возврата в Unity GameObject StarPowerUp будет выглядеть в инспекторе следующим образом:

Введите значения инспектора следующим образом:
- Power Up Name: Star
- Explanation: Recovered some health…
- Power Up Quote: (I will become more powerful than you can possibly imagine)
- Expires Immediately: флажок поставлен
- Special Effect: перетащите префаб из папки проекта Prefabs/Power Ups/ParticlesCollected
- Sound Effect: перетащите аудиоклип из папки проекта Audio/power_up_collect_01
- Health Bonus: 40

Завершив с параметрами PowerUpStar, перетащите его в папку дерева проекта Prefabs/Power Ups, чтобы создать префаб.
Используйте новый префаб, чтобы добавить несколько звёзд в правой части сцены.
Запустите сцену и проведите героя к первому бонусу. Подбор бонуса сопроводят замечательные звуковые эффекты и частицы. Отлично!

Связь на основе сообщений
Следующий создаваемый нами бонус требует фоновой информации. Чтобы получить эту информации, нам нужно, чтобы GameObjects научились обмениваться между собой данными.
Например, когда мы подбираем бонус, UI должен знать, какую информацию нужно отобразить. Когда здоровье игрока меняется, полоска энергии должна знать, каким стал обновлённый уровень здоровья. Это можно реализовать множеством способом, но в руководстве Unity перечислено несколько механизмов.
Каждый способ коммуникации имеет свои плюсы и минусы, и нельзя подобрать один, подходящий ко всем случаям. В нашей игре мы реализуем связь на основе сообщений, как она описана в руководстве Unity.
Мы можем сделать GameObjects на передающими сообщения, получающими сообщения, или и тем, и другим:

В левой части представленной выше схемы представлены передатчики сообщений. Можно считать их объектами, «кричащими», когда происходит что-то интересное. Например, если игрок передаёт сообщение «Меня задели». В правой части схемы показаны слушатели сообщений. Как понятно из названия, они слушают сообщения. Слушатели не обязаны слушать все сообщения; они слушают только те сообщения, на которые хотят реагировать.
Передатчики сообщений могут быть также и слушателями. Например, бонус передаёт сообщения, но и слушает сообщения игрока. Хорошим примером здесь может быть бонус, срок действия которого кончается, когда игрока задевают враги.
Можно понять, что при наличии множества передатчиков и множества слушателей, между левой и правой сторонами должно быть множество пересекающихся линий. Чтобы упростить нам жизнь, в Unity имеется компонент EventSystem, который располагается посередине:

Unity использует расширяемый компонент EventSystem для обработки ввода. Также этот компонент управляет большей частью логики событий отправки и получения.
Да, многовато теории, но подведём итог: система сообщений позволит бонусам удобно слушать игровой процесс и снизить количество жёстких соединений между объектами. Это позволит упростить добавление новых бонусов, особенно на поздних этапах разработки, потому что большинство сообщений уже будет передаваться.
Этапы создания связи на основе сообщений
Прежде чем приступить к созданию, нам нужно немного теории. Для вещания сообщения нам нужно выполнить следующие этапы:
- Передатчики определяют сообщение, которое они хотят передать, как интерфейс C#.
- Затем передатчики передают сообщения слушателям, хранящимся в списке слушателей.
Если вкратце, то интерфейс определяет сигнатуры метода. Любой класс, реализующий интерфейс, обещает предоставить функционал этим методам.
Более чётко это можно увидеть на примере. Посмотрите на код в файле IPlayerEvents.cs:
public interface IPlayerEvents : IEventSystemHandler
У этого интерфейса C# есть методы для OnPlayerHurt и OnPlayerReachedExit . Это сообщения, которые может отправить игрок. Теперь посмотрите на метод SendPlayerHurtMessages в файле PlayerBrain.cs. Строки, помеченные числами в следующем фрагменте кода, описаны ниже:
private void SendPlayerHurtMessages() < // Отправка сообщения всем слушателям foreach (GameObject go in EventSystemListeners.main.listeners) // 1 < ExecuteEvents.Execute// 2 (go, null, // 3 (x, y) => x.OnPlayerHurt(playerHitPoints) // 4 ); > >
Представленный выше метод обрабатывает отправку сообщения OnPlayerHurt . Цикл foreach обходит всех слушателей, хранящихся в списке EventSystemListeners.main.listeners и вызывает для каждого слушателя ExecuteEvents.Execute , который отправляет сообщения.
Пройдёмся по комментариям с числами:
- EventSystemListeners.main.listeners — это список GameObjects, глобально видимый в объекте синглтона EventSystemListeners . Любой GameObject, который хочет слушать все сообщения, должен находиться в этом списке. Добавлять GameObjects в этот список можно, присваивая GameObject метку Listener в инспекторе или вызывая EventSystemListeners.main.AddListener .
- ExecuteEvents.Execute — это предоставляемый Unity метод, отправляющий сообщение GameObject. Тип в угловых скобках — это имя интерфейса, содержащего сообщение, которое мы хотим отправить.
- Здесь определяется GameObject, которому нужно отправить сообщение и null для дополнительной информации события в соответствии с примером синтаксиса из руководства Unity.
- Лямбда-выражение. Это сложная концепция C#, которую мы не будем рассматривать в этом туториале. Если вкратце, то лямбда-выражение позволяет передавать методу код как параметр. В нашем случае код содержит сообщение, которое мы хотим отправить ( OnPlayerHurt ) вместе с необходимыми ему параметрами ( playerHitPoints ).

- IPlayerEvents : используется для сообщений, когда игрока задевают или он добирается до выхода.
- IPowerUpEvents : используется для сообщений, когда подбирается бонус или его действие заканчивается.
- IMainGameEvents : используется для сообщений, когда игрок побеждает или проигрывает.
Бонус увеличения скорости

Теперь, когда мы знаем о связи на основе сообщений, мы воспользуемся ею и будем слушать сообщение!
Мы создадим бонус, дающий игроку дополнительную скорость до момента, пока он с чем-нибудь не столкнётся. Бонус будет распознавать столкновение игрока «прослушивая» игровой процесс. А конкретнее, бонус будет слушать игрока, передающего сообщение «I am hurt».
Чтобы слушать сообщение, нам нужно выполнить следующие шаги:
- Реализовать соответствующий интерфейс C#, чтобы указать, что должен слушать слушающий GameObject.
- Сделать так, чтобы сам слушающий GameObjects находился в списке EventSystemListeners.main.listeners .
Добавьте Box Collider 2D и измените его Size на (X:0.2, Y:0.2). В компоненте Sprite Renderer назначьте для Sprite значение fast и измените его цвет так, как мы делали со звездой. Не забудьте также поставить флажок Is Trigger. После этого GameObject должен выглядеть примерно так:

Добавьте этому GameObject новый скрипт PowerUpSpeed и вставьте в скрипт следующий код:
class PowerUpSpeed : PowerUp < [Range(1.0f, 4.0f)] public float speedMultiplier = 2.0f; protected override void PowerUpPayload() // Пункт 1 контрольного списка < base.PowerUpPayload(); playerBrain.SetSpeedBoostOn(speedMultiplier); >protected override void PowerUpHasExpired() // Пункт 2 контрольного списка < playerBrain.SetSpeedBoostOff(); base.PowerUpHasExpired(); >>
Проверим пункты контрольного списка. Скрипт выполняет каждый из пунктов следующим образом:
- PowerUpPayload . Вызывает метод base , чтобы обеспечить вызов родительского класса, затем придаёт игроку ускорение. Заметьте, что родительский класс определяет playerBrain , в котором содержится ссылка на игрока, собравшего бонус.
- PowerUpHasExpired. Нам нужно убрать данное игроку ускорение, а затем вызвать метод base .
- Последний пункт контрольного списка — вызов PowerUpHasExpired после завершения срока действия бонуса. Позже мы реализуем это с помощью прислушивания к сообщениям игрока.
class PowerUpSpeed : PowerUp, IPlayerEvents
Примечание: если вы работаете в Visual Studio, то можете навести мышь на элемент IPlayerEvents после его ввода и выбрать опцию меню Implement interface explicitly. При этом создастся заготовка метода.

Добавляйте или изменяйте методы, пока они не будут выглядеть следующим образом, и убедитесь, что они всё ещё являются частью класса PowerUpSpeed:
void IPlayerEvents.OnPlayerHurt(int newHealth) < // Мы хотим реагировать только при подборе бонуса if (powerUpState != PowerUpState.IsCollected) < return; >// Срок действия истекает, когда игрока задевают PowerUpHasExpired(); // Пункт 3 контрольного списка > ////// Нам нужно реализовать весь интерфейс IPlayerEvents, но нам не нужно реагировать на это сообщение /// void IPlayerEvents.OnPlayerReachedExit(GameObject exit)
Метод IPlayerEvents.OnPlayerHurt вызывается каждый раз, когда игрок получает урон. Это часть «прислушивания к вещаемым сообщениям». В этом методе мы сначала делаем так, чтобы бонус реагировал только после подбора. Затем код вызывает PowerUpHasExpired в родительском классе, который будет обрабатывать логику истечения срока действия.
Сохраните этот метод и вернитесь в Unity, чтобы внести нужные изменения в инспекторе.
Создание бонуса увеличения скорости в сцене
GameObject SpeedPowerUp теперь будет выглядеть в инспекторе следующим образом:

Введите в инспекторе следующие значения:
- Power Up Name: Speed
- Explanation: Super fast movement until enemy contact
- Power Up Quote: (Make the Kessel run in less than 12 parsecs)
- Expires Immediately: снять флажок
- Special Effect: ParticlesCollected (тот же, что и для звезды)
- Sound Effect power_up_collect_01 (тот же, что и для звезды)
- Speed Multiplier: 2

Настроив параметры бонуса Speed нужным вам образом, перетащите его в папку дерева проекта Prefabs/Power Ups, чтобы создать префаб. Мы не будем использовать его в демо-проекте, но для завершённости стоит это сделать.
Запустите сцену и переместите героя, чтобы собрать бонус скорости, после чего он получит дополнительную скорость, которая будет сохраняться до контакта с врагом.

Отталкивающий бонус

Следующий бонус позволяет игроку отталкивать объекты со своего пути нажатием клавиши P; количество его применений ограничено. Вы уже знакомы с этапами создания бонуса, поэтому чтобы не загромождать текст, я рассмотрю только самые интересные части кода, а затем создам префаб. Этому бонусу не нужно слушать никакие сообщения.
Найдите и изучите в иерархии проекта префаб PowerUpPush. Откройте его скрипт под названием PowerUpPush и изучите код.

Вы увидите знакомые методы, которые мы уже рассматривали. Все интересные действия отталкивающего бонуса происходят в методе Update:
private void Update () < if (powerUpState == PowerUpState.IsCollected && //1 numberOfUsesRemaining >0) //2 < if (Input.GetKeyDown ("p")) //3 < PushSpecialEffects (); //4 PushPhysics (); //5 numberOfUsesRemaining--; if (numberOfUsesRemaining > > >
Вот, что происходит в этом коде:
- Скрипт должен выполняться только для собранных бонусов.
- Скрипт должен выполняться, только если осталось количество использований.
- Скрипт должен выполняться, только когда игрок нажимает на P.
- Скрипт запускает выполнение красивого эффекта частиц вокруг игрока.
- Скрипт отталкивает врагов от игрока.
- Бонус используется ещё раз.
- Если число использований закончилось, то срок действия бонуса заканчивается.

Дополнительное задание: бонус неуязвимости

Этот раздел необязателен, но довольно интересен. Вооружённые полученными знаниями, создайте бонус, на какое-то время делающий игрока неуязвимым.
Советы и рекомендации для выполнения этого задания:
- Sprite: найдите спрайт в папке проекта Textures/shield
- Power Up Name: Invulnerable
- Power Up Explanation: You are Invulnerable for a time
- Power Up Quote: (Great kid, don’t get cocky)
- Coding: пройдитесь по тому же контрольному списку, который мы использовали для бонуса-звезды и увеличения скорости. Вам понадобится таймер, контролирующий срок действия бонуса. Метод SetInvulnerability в PlayerBrain будет включать и отключать неуязвимость.
- Effects: проект содержит эффект частиц для красивого эффекта пульсации вокруг игрока, пока он остаётся неуязвимым. Префаб находится в Prefabs/Power Ups/ParticlesInvuln. Можно сделать его дочерним экземпляром игрока, пока он неуязвим.
Решение внутри
Создайте новый Sprite, назовите его PowerUpInvuln и поместите в (X:-0.76, Y:1.29). Для Scale задайте значения X:0.7, Y:0.7. Этот GameObject не будет никого слушать, срок его действия будет просто истекать после заданного времени, поэтому нет необходимости давать ему метку в инспекторе.
Добавьте Box Collider 2D и измените Size на X = 0.2, Y = 0.2. В компоненте Sprite Renderer назначьте Sprite значение shield и выберите цвет, как мы это раньше делали с другими бонусами. Убедитесь, что флажок Is Trigger поставлен. После этого GameObject должен выглядеть примерно так:

Добавьте к этому GameObject новый скрипт PowerUpInvuln и вставьте в него следующий код:
class PowerUpInvuln : PowerUp < public float invulnDurationSeconds = 5f; public GameObject invulnParticles; private GameObject invulnParticlesInstance; protected override void PowerUpPayload () // Пункт 1 контрольного списка < base.PowerUpPayload (); playerBrain.SetInvulnerability (true); if (invulnParticles != null) < invulnParticlesInstance = Instantiate (invulnParticles, playerBrain.gameObject.transform.position, playerBrain.gameObject.transform.rotation, transform); >> protected override void PowerUpHasExpired () // Пункт 2 контрольного списка < if (powerUpState == PowerUpState.IsExpiring) < return; >playerBrain.SetInvulnerability (false); if (invulnParticlesInstance != null) < Destroy (invulnParticlesInstance); >base.PowerUpHasExpired (); > private void Update () // Пункт 3 контрольного списка < if (powerUpState == PowerUpState.IsCollected) < invulnDurationSeconds -= Time.deltaTime; if (invulnDurationSeconds < 0) < PowerUpHasExpired (); >> > >
Снова проверьте пункты контрольного списка кода. Скрипт выполняет эти пункты следующим образом:
- PowerUpPayload : вызывает метод base для вызова родительского класса, затем даёт игроку бонус неуязвимости. Также добавляет пульсирующий эффект частиц.
- PowerUpHasExpired : необходимо удалить полученный бонус неуязвимости, а затем вызвать метод base .
- Последний пункт контрольного списка — вызов PowerUpHasExpired при завершении срока действия бонуса. В нашем случае мы используем Update() для обратного отсчёта прошедшего времени.
Надеюсь, вы попробовали решить задание, прежде чем посмотрели решение!
Куда двигаться дальше?

Готовый проект для этого туториала можно скачать здесь.
Если вы хотите дальше развивать проект, то можно сделать следующее:
- Добавить больше бонусов. Как насчёт бонуса, стреляющего убивающими врагов лучами?
- Создать класс-фабрику для случайного создания бонусов на арене в процессе игры.
- Если вы хотите дальше изучать Unity, то оцените Unity Games by Tutorials book из нашего магазина.
Ура! Наверно, вы единственный человек в Интернете, нажавший сюда! Ответ: это цитаты из серии «Звёздные войны».
Игра на Unity в стиле Jetpack Joyride. Монеты
Создание префаба монеты легко и похоже на создание лазера, так что вы должны попробовать сделать это самостоятельно. Просто используйте спрайт с монетой и следуйте за нашими советами:
- Не создавайте скрипты для монеты.
- используйте Circle Collider 2D вместо Box Collider 2D.
- Включите Trigger option для коллайдера, так как мы не хотим, чтобы монеты останавливали движение мыши.
Если вам все еще непонятно, читайте далее.
- Откройте папку Sprites во вкладке Project.
- Перетащите спрайт coin на сцену и выберите его в Иерархии.
- В инспекторе установите его Sorting Layer в Objects.
- Добавьте компонент Circle Collider 2D
- Включите свойство Is Trigger в коллайдере
- Установите Radius коллайдера 0.23
Вот изображение, показывающее все необходимые шаги:

После создания игрового объекта с монетой (coin), перетащите его из Иерархии в папку Prefabs, что во вкладке Project, для создания префаба.

Теперь добавьте несколько монет на сцену с помощью перетаскивания префаба coin на вкладку Scene. Сделайте нечто вроде этого:


Подождите секунду — почему мышь умирает сразу же, как только касается монеты? Неужели они ядовитые?
Нет, с монетами все в порядке. Мышь умирает из-за кода в скрипте MouseController, который обрабатывает любое столкновение.
Использование тегов, чтобы отличать монеты от лазеров
Чтобы отличить монеты от лазеров вы будете использовать теги, которые сделаны именно для этой цели.
Выберите coin Prefab прямо в папке Prefabs во вкладке Project. Это откроет свойства префаба в Инспекторе. Найдите выпадающий список Tag прямо под полем name, откройте его и выберите Add Tag. (Добавить тег).

Это откроет уже знакомый редактор Tags & Layers в Инспекторе. В секции Tags добавьте тег по имени Coins.
Это автоматически увеличит Size до 2 и добавит Element 1, но это нормально.
Теперь выберите coin Prefab во вкладке Project еще раз, и установите его Tag в Coins в Инспекторе.

Конечно просто установив свойство Tag не получить скрипт, отличающий монеты от лазеров. Вам все равно придется править код.
Обновление скрипта MouseController
Откройте скрипт MouseController и добавьте переменную счетчика coins:
private uint coins = 0;
Здесь вы будете хранить число собранных монет. Теперь добавьте метод CollectCoin:
void CollectCoin(Collider2D coinCollider)
Этот метод увеличивает количество монет и удаляет монету со сцены, чтобы вы не столкнулись с ней во второй раз. Теперь внесите следующие изменения в OnTriggerEnter2D:
void OnTriggerEnter2D(Collider2D collider)
С этими изменениями вы вызываете CollectCoin при сборе монет и HitByLaser во всех остальных случаях.
В этой игре есть только два типа объектов, поэтому можно использовать оператор else для лазеров. В реальной игре, вы должны назначить метки для всех типов объектов и проверить их неявно.

В настоящее время у вас есть префаб, который состоит только из одной монеты, так что, если вы пишете код генерации монет, вы просто создать только одину монету на уровне. Это очень скучно! Как насчет создания различных фигур из монет и генерации нескольких монет сразу?
Создание префаба из кучи монет
Откройте папку Prefabs во вкладке Project и создайте 9 монеток на сцене, используя префаб coin. Это должно выглядеть примерно так:

Выберите любую монету и установить ее Position в (0, 0, 0). Это будет центральная монета. Вы добавите все монеты в пустой игровой объект, поэтому вам надо будет добавлять их рядом с началом координат.
После размещения центральной монеты, , создайте вокруг монеты фигуру в виде перевернутого треугольника. Помните, что вы можете воспользоваться Vertex Snapping, если зажмете клавишу V.

Теперь создайте пустой игровой объект выбрав GameObject\Create Empty. Выберите его в Иерархии и переименуйте в coins_v.
Установите его Position в (0, 0, 0), так что у него будет тоже положение, что и у центральной монеты. После этого выберите все монеты в Иерархии и добавьте их в coins_v. Вы должны получить что-то вроде этого в Иерархии:

Выберите coins_v в Иерархиии и перетащите ее в папку Prefabs во вкладке Project для создания префаба.

Вы можете создать столько различных фигур из монет, сколько захотите. Как и в случае с комнатами, скрипт генератора создаст свойство, в котором можно будет обозначить все объекты, которые можно сгенерировать.
Дело сделано. Теперь удалите все монеты и лазеры со сцены, так как теперь они будут генерироваться скриптом.
Добавление новых параметров в GeneratorScript
Откройте GeneratorScript и добавьте следующие переменные экземпляра:
public GameObject[] availableObjects; public List objects; public float objectsMinDistance = 5.0f; public float objectsMaxDistance = 10.0f; public float objectsMinY = -1.4f; public float objectsMaxY = 1.4f; public float objectsMinRotation = -45.0f; public float objectsMaxRotation = 45.0f;
Массив availableObjects, будет содержать все объекты, которые могут генерироваться скриптом (т.е. различные группы монет и лазеров). Список objects будет хранить созданные объекты, так, чтобы вы могли проверить, нужно ли добавлять что-то перед игроком, либо удалять их, если они за пределами экрана.
Так же, как участки комнаты, вы можете создать несколько лазеров, или монет в начале уровня, уровня там, где вы не хотите полагаться на генератор случайных чисел. Только не забудьте добавить их в список объектов.
Переменные objectsMinDistance и objectsMaxDistance используются для определения случайного расстояния между последним объектом и добавляемым, так что объекты не появляются с постоянным интервалом.
Используя objectsMinY и objectsMaxY вы можете настроить максимальную и минимальную высоту, на которой размещены объекты, а с помощью objectsMinRotation и objectsMaxRotation вы можете настроить диапазон поворота.
Добавление метода для добавления нового объекта
Новые объекты добавляются в AddObject подобно тому, как добавляются участки комнаты. Добавьте следующее:
void AddObject(float lastObjectX) < //1 int randomIndex = Random.Range(0, availableObjects.Length); //2 GameObject obj = (GameObject)Instantiate(availableObjects[randomIndex]); //3 float objectPositionX = lastObjectX + Random.Range(objectsMinDistance, objectsMaxDistance); float randomY = Random.Range(objectsMinY, objectsMaxY); obj.transform.position = new Vector3(objectPositionX,randomY,0); //4 float rotation = Random.Range(objectsMinRotation, objectsMaxRotation); obj.transform.rotation = Quaternion.Euler(Vector3.forward * rotation); //5 objects.Add(obj); >
Этот метод tметод занимает позицию самого крайнего правого объекта и создает новый объект в случайном месте (в пределах заданного интервала) после него. Вызывая этот метод каждый раз, когда объект должен появиться на экране, вы создаете новый объект вне экрана и создаете бесконечный поток новых монеток и лазеров.
Вот описание каждого блока кода:
- Генерирует случайный индекс для генерируемого объекта. Это может быть лазер, или кучка монет.
- Создает экземпляр объекта, который выбирается случайно.
- Установка позиции объекта с использованием случайного интервала и случайной высоты. Это контролируется с помощью параметров скрипта.
- Добавление случайного вращения для вновь размещенных объектов.
- Добавляет только что созданный объект в список объектов для отслеживания и, в конечном счете, удаления (когда объект покидает экран).
Генерирование и удаление объектов в случае необходимости
Добавьте следующее в GeneratorScript:
void GenerateObjectsIfRequired() < //1 float playerX = transform.position.x; float removeObjectsX = playerX - screenWidthInPoints; float addObjectX = playerX + screenWidthInPoints; float farthestObjectX = 0; //2 ListobjectsToRemove = new List(); foreach (var obj in objects) < //3 float objX = obj.transform.position.x; //4 farthestObjectX = Mathf.Max(farthestObjectX, objX); //5 if (objX < removeObjectsX) objectsToRemove.Add(obj); >//6 foreach (var obj in objectsToRemove) < objects.Remove(obj); Destroy(obj); >//7 if (farthestObjectX
Этот метод проверяет, нужно ли добавить или удалить объект где-то в пределах класса GeneratorScript Вот последовательность его действий:
- Расчет ключевых точек впереди и позади игрока. Если лазер, или монеты находятся левее removeObjectsX, то они уже вне экрана и их придется удалить. Если нет объекта после AddObject точка, то вам нужно добавить больше объектов, так как последний из созданных объектов скоро появится на экране. Переменная farthestObjectX используется, чтобы найти позицию последнего (крайнего справа) объекта для сравнения ее с addObjectX.
- Поскольку вы не можете удалить объекты из текущей итерации, вы позмещаете объекты, которые нужно удалить, в отдельный массив, который будет удален после полного круга.
- Это положение объекта (группы монет, или лазера).
- При выполнении этого кода для каждого objX Вы получаете максимальное значение objX в farthestObjectX в конце цикла (или начальное нулевое значение, если все объекты расположены левее начала координат, хотя это не наш случай).
- Если текущий объект далеко позади, — это весомый аргумент, чтобы освободить некоторые ресурсы.
- Удаляет объекты, помеченные для удаления.
- Если игрок скоро дойдет до последнего объекта, скрипт автоматически добавит новые.
Для того, чтобы этот метод работал, добавьте вызов GenerateObjectsIfRequired в конце FixedUpdate:
GenerateObjectsIfRequired();
Этот метод вызывается каждый раз, гарантируя, что всегда будут существовать объекты впереди игрока.
Настройка параметров скрипта
Для того, чтобы GeneratorScript работал, вам нужно установить несколько параметров. Вернитесь обратно в Unity и выберите Игровой объект mouse в Иерархии.
найдите компонент Generator Script в Инспекторе и убедитесь, что папка Prefabs открыта во вкладке Project.
Перетащите префаб coins_v из вкладки Project в список Available Objects компонента GeneratorScript. Проделайте те же действия с префабом laser.

Это оно! Запустите сцену.
В приведенном выше анимированном GIF лазеры не вращаются, потому что я установил rotationSpeed параметра LaserScript в 0 (с вращающимися лазерами довольно трудно записать хорошее видео игрового процесса).
Теперь, то, что мы видим, выглядит как почти законченная игра! В следующем уроке мы поработаем с GUI, чтобы добавить счетчик очков.
Простой способ: PlayerPrefs
Для начала можно закомментировать или удалить методы Start и Update , так как они не потребуются для демонстрации сохранения данных. Затем нам понадобятся несколько переменных.

Загрузка
Загрузка сохраненных данных – это, по сути, сохранение наоборот. Необходимо взять значения, хранящиеся в PlayerPrefs и записать их в переменные.
Хорошей практикой является предварительная проверка наличия нужных ключей. Для этого предназначен метод HasKey . Достаточно проверить хотя бы один ключ из установленных в методе SaveGame , чтобы понимать, есть ли у пользователя сохраненные данные.
Если данных нет, выведем в консоль сообщение об ошибке.

Теперь запустите игру и начните взаимодействовать с GUI-элементами. Изменяйте переменные, нажимая на кнопки и заполняя текстовое поле. Когда будете готовы, сохраните данные кнопкой Save Your Game . После этого остановите и перезапустите игру и нажмите на кнопку Load Your Game . Если вы всё сделали правильно, значения переменных немедленно изменятся на те, что вы сохранили в предыдущем запуске.
Чтобы очистить PlayerPrefs , кликните Reset Save Data .

Недостатки
Этот способ кажется простым и эффективным. Почему бы всегда не использовать PlayerPrefs для сохранения пользовательских данных?
Из-за его невысокой безопасности. Вы точно не захотите сохранять таким способом данные, в которые не должен вмешиваться игрок: например, количество доступной игроку игровой валюты, или статистика.
Из названия понятно, что PlayerPrefs предназначен для хранения пользовательских предпочтений и других неигровых данных. Например, этот метод идеально подходит для записи настроек интерфейса: цветовой темы, размера элементов и т. п.
Другая проблема – в недостаточной гибкости. В PlayerPrefs вы можете сохранять только числа и строки, поэтому он не подходит для данных, имеющих сложную структуру.
К счастью, у нас есть еще один способ, более гибкий и безопасный.
Сложный способ: Сериализация
Для демонстрации сложного способа сохранения данных в Unity откроем скрипт SaveSerial .
Снова определим переменные и создадим интерфейс для управления ими. Метод OnGUI похож на тот, что мы только что писали:

Обратите внимание, три переменные в классе SaveData соответствуют переменным из класса SaveSerial . Для сохранения мы будем передавать значения из SaveSerial в SaveData , а затем сериализовать последний.
Добавим в класс SaveSerial метод SaveGame :

После запуска вы увидите тот же самый интерфейс, что и раньше. Попробуйте изменить переменные и сохранить игру.
В этот раз файл будет сохранен по «постоянному пути данных» игры. В Windows это C:\Users\username\AppData\LocalLow\project name , в Mac – ~/Library/Application Support/companyname/productname согласно документации.
Перезапустите игру и загрузите данные, нажав на кнопку Load Your Game . Значения переменных должны измениться на те, что вы сохранили ранее.
Также вы можете удалить все сохраненные данные кнопкой Reset Save Data .

Заключение
Сохранение данных – важная часть большинства игр, за исключением лишь некоторых специфических исключений (например, игры с очень короткими игровыми сессиями или механикой перманентной смерти, в которой после каждого проигрыша вся игра сбрасывается к началу).
Помните о том, что PlayerPrefs – это простой способ сохранить настройки и предпочтения пользователя, но его не следует использовать для записи важных игровых данных.
Сериализация и запись в файл – более сложный путь, но он дает вам бóльшую гибкость и безопасность.
Какие именно данные сохранять и каким способом – зависит от особенностей проекта.
Больше полезной информации вы найдете на наших телеграм-каналах «Библиотека программиста» и «Книги для программистов».