Как сделать Bomberman в Unity
Вы когда-нибудь мечтали создать игру в стиле Bomberman?
С Unity 3D и нашим пошаговым руководством, вы легко это сделаете!
Bomberman — это игра, в которой четыре игрока сражаются на одной локации, стратегически размещая бомбы на поле битвы с целью взорвать друг друга.
У каждой бомбы есть задержка в несколько секунд, прежде чем она взорвется и извергнет взрывную волну в четырех направлениях.
Оригинальный Bomberman вышел в начале 80-х, и с тех пор были опубликованы десятки, если не сотни его вариаций.
Можно сказать, что это идеальная формула игры как для новичков, так и для профи.
В этом руководстве вы узнаете следующее:
►Как устанавливать бомбы и привязывать их к определенному месту в локации.
►Как создается взрывная волна.
►Обработку события взрыва при столкновении с игроком.
►Обработка взрывов при взаимодействии с другими бомбами.
►Условия для определения выигрыша или ничьей.
Начало работы
Загрузите Starter Project для этого руководства и распакуйте его в любую папку, по вашему выбору.
Откройте Starter Project в Unity и запустите Bomberman.
Обратите внимание, что ресурсы сортируются в нескольких папках:
?Animation Controllers: содержат контроллер анимации игрока, включая логику для анимации конечностей во время движения перков.
?Materials: содержит материал блоков для уровня.
?Models: содержит все модели локации (игрока, уровня, бомб), а также их материалы.
?Music: содержит звуковые файлы, в данном случае саундтрек.
?Physics Materials: содержит физический материал персонажей — это особые виды материалов, которые добавляют физические свойства поверхностям.
?Prefabs: содержит информацию о бомбах и типах взрывов.
?Scenes: тут находится игровая сцена.
?Scripts: содержит стартовые скрипты — каждый из них сопровождается поясняющими кмментариями для более легкого понимания.
?Sound Effects:содержит звуковые эффекты для бомбы и взрыва.
?Textures: содержит текстуры для игровых персонажей.
Сбрасывание бомб
Для начала откройте и запустите сцену Game:
В данный момент оба персонажа могут перемещаться по карте с помощью клавиш WASD и клавиш со стрелками.
Нам нужно сделать так, чтобы игрок 1 (красный) нажимая пробел, мог оставить бомбу у своих ног, а игрок 2 делал то же самое с помощью Enter/Return.
Для этого вам нужно реализовать код для установки бомб, поэтому откройте скрипт Player.cs в любом редакторе, которым вы пользуетесь обычно.
Этот сценарий обрабатывает все движения игрока и логику анимации. Он также включает метод DropBomb, который просто проверяет, прикреплен ли GameObject bombPrefab:
private void DropBomb() { if (bombPrefab) { //Check if bomb prefab is assigned first } }
Чтобы бомба установилась в нужном месте, добавьте в оператор if следующую строку:
Instantiate(bombPrefab, myTransform.position, bombPrefab.transform.rotation);
Сохраните изменения в скрипте, а затем запустите игровую сцену, чтобы проверить, как все работает:
Привязка
Следующая задача — убедиться, что бомбы встали на нужное место при падении и чтобы они точно совпадали с сеткой на полу.
Каждая плитка в этой сетке имеет размер 1 × 1, поэтому внести это изменение довольно легко.
В Player.cs отредактируйте Instantiate (), который вы только что добавили в DropBomb (), следующим образом:
Instantiate(bombPrefab, new Vector3(Mathf.RoundToInt(myTransform.position.x), bombPrefab.transform.position.y, Mathf.RoundToInt(myTransform.position.z)), bombPrefab.transform.rotation);
Mathf.RoundToInt вызывает значения x и z позиции игрока, округляет любое число до значения int, которое затем привязывает бомбы к позициям плитки:
Сохраните изменения и снова воспроизведите сцену.
Обратите вниманеи на то, как падают бомбы теперь — каждая на свое место.
Создание эффекта взрыва
Для начала вам понадобится новый скрипт:
►Выберите папку Scripts в меню Project.
►Нажмите кнопку Create.
►Выберите C # Script.
►Назовите новый скрипт Bomb.
Теперь прикрепите скрипт Bomb к префабу Bomb:
►В папке Prefabs выберите GameObject Bomb.
►В окне «Инспектор» нажмите кнопку «Добавить компонент».
►Введите бомбу в поле поиска.
Выберите только что созданный сценарий Bomb.
Наконец, откройте сценарий Bomb в редакторе кода. Внутри Start () добавьте следующую строку кода:
Invoke("Explode", 3f);
Добавьте следующее в Update ():
void Explode() { }
Прежде чем вы сможете создать какие-либо игровые объекты Explosion, вам понадобится общедоступная переменная типа GameObject, чтобы вы могли назначить Explosionprefab в редакторе. Для этого необходимо добавить еще один скриптсправа над Start ():
public GameObject explosionPrefab;
Сохраните файл и вернитесь в редактор. Выберите префаб Bomb в папке Prefabs и перетащите префаб Explosion в слот Explosion Prefab:
Как только вы это сделаете, вернитесь в редактор кода. Наконец-то вы можете написать код, который заставит все врываться!
Внутри Explode () добавьте следующие строки:
Instantiate(explosionPrefab, transform.position, Quaternion.identity); //1 GetComponent<MeshRenderer>().enabled = false; //2 transform.Find("Collider").gameObject.SetActive(false); //3 Destroy(gameObject, .3f); //4
Этот фрагмент кода выполняет следующие действия:
►Создает взрыв на месте бомбы.
►Отключает рендерер меша, делая бомбу невидимой.
►Отключает коллайдер, позволяя игрокам проходить сквозь взрыв.
►Уничтожает бомбу через 0,3 секунды; это гарантирует, что все взрывы появятся до того, как GameObject ►будет уничтожен.
Сохраните сценарий бомбы, вернитесь в редактор и попробуйте поиграть в игру. Бросьте несколько бомб и наслаждайтесь огненными взрывами!
Добавление LayerMask
LayerMask выборочно отфильтровывает определенные слои и обычно используется с Raycast. В этом случае вам нужно отфильтровать только блоки, чтобы взрыв действовал на выбранные вами объекты.
В редакторе Unity нажмите кнопку «Layers» в правом верхнем углу и выберите «Edit Layers…»
При необходимости щелкните треугольник перед словом «Layers», чтобы развернуть список слоев, если он не виден.
Щелкните текстовое поле рядом с User Layer 8 и введите «Blocks». Это создаст новый слой, который вы можете использовать.
В иерархии выберите GameObject Blocks внутри объекта Map.
Измените слой на только что созданный слой Blocks:
Когда появится диалоговое окно «Change Layer», нажмите кнопку «Yes, change children», чтобы применить действие ко всем желтым блокам, разбросанным по карте.
Наконец, добавьте общедоступную ссылку на LayerMask, чтобы сценарий Bomb мог получить доступ к слою, добавив следующую строку сразу под ссылкой на explosionPrefab.
public LayerMask levelMask;
Не забудьте сохранить свой код.
Следующим шагом будет добавление знаковых штрихов расширяющихся рядов взрывов. Для этого вам нужно создать сопрограмму.
Примечание: Сопрограмма — это, по сути, функция, которая позволяет приостанавливать выполнение и возвращать управление Unity. Позже выполнение этой функции действие возобновится с того места, где оно было остановлено в последний раз. Люди часто путают сопрограммы с многопоточностью. Это не одно и то же: сопрограммы выполняются в одном потоке и возобновляются в промежуточные моменты времени.
Чтобы узнать больше о сопрограммах и о том, как их определять, ознакомьтесь с документацией Unity.
Вернитесь в редактор кода и отредактируйте сценарий Bomb. В разделе Explode () добавьте IEnumerator с именем CreateExplosions:
private IEnumerator CreateExplosions(Vector3 direction) { return null; // placeholder for now }
Для создания сопрограммы добавьте следующие четыре строки кода между Instantiate и MeshRenderer в Explode ():
StartCoroutine(CreateExplosions(Vector3.forward)); StartCoroutine(CreateExplosions(Vector3.right)); StartCoroutine(CreateExplosions(Vector3.back)); StartCoroutine(CreateExplosions(Vector3.left));
StartCoroutine запускает IEnumerator CreateExplosions один раз для каждого направления.
Теперь самое интересное. Внутри CreateExplosions () замените return null; // следующим фрагментом кода:
//1 for (int i = 1; i < 3; i++) { //2 RaycastHit hit; //3 Physics.Raycast(transform.position + new Vector3(0,.5f,0), direction, out hit, i, levelMask); //4 if (!hit.collider) { Instantiate(explosionPrefab, transform.position + (i * direction), //5 explosionPrefab.transform.rotation); //6 } else { //7 break; } //8 yield return new WaitForSeconds(.05f); }
Небольшое пояснение к каждому разделу:
- Итерирует цикл for для каждой единицы расстояния, где должны происходить взрывы. В этом случае взрыв достигнет двух метров.
- Объект RaycastHit содержит всю информацию о том, что и в какой позиции Raycast попадает — или не попадает.
- Эта важная строка кода отправляет raycast из центра бомбы в направлении, указанном через StartCoroutine. Затем он выводит результат в объект RaycastHit. Параметр i определяет расстояние, которое должен пройти луч.
- Если raycast ни с чем не взоимодействует, то это бесплатный тайл.
- Создает взрыв в позиции, проверенной лучом.
- Raycast попадает в блок.
- Как только raycast попадает в блок, он выходит из цикла for. Это гарантирует, что взрыв не будет действовать на объекты за стеной.
- Ожидает 0,05 секунды перед выполнением следующей итерации цикла for. Это делает взрыв более убедительным, создавая впечатление, будто он увеличивается.
Вот как это выглядит в действии:
Красная линия — это raycast. Он проверяет плитки вокруг бомбы на предмет наличия свободного места и, если находит его, вызывает взрыв. Когда он попадает в блок, он ничего не порождает и прекращает проверку в этом направлении.
Теперь вы можете увидеть причину, по которой бомбы нужно прикреплять к центру плиток. Если бы бомбы могли падать куда угодно, то в некоторых случаях raycast попадл бы в блок и не вызывал взрывов, потому что он не выровнен должным образом с уровнем:
Выберите префаб Bomb в папке Prefabs и измените значение Level Mask на Blocks.
Запустите игровую сцену и сбросьте несколько бомб. Посмотрите, как теперь выглядят взрывы:
Создание цепных реакций
Когда взрыв одной бомбы взаимодействует с другой, следующая бомба также должна взрываться — эта функция сделает игру более стратегической, захватывающей и яркой.
Для этого откройте сценарий Bomb.cs в редакторе кода. Добавьте новый метод с именем OnTriggerEnter под CreateExplosions ():
public void OnTriggerEnter(Collider other) { }
OnTriggerEnter — это предопределенный метод в MonoBehaviour, который вызывается при столкновении триггерного коллайдера и твердого тела. Параметр Collider с именем other — это коллайдер GameObject, который вошел в триггер.
В этом случае вам нужно проверить сталкивающийся объект и заставить бомбу взорваться при взаимодействии с другим взрывом.
Для начала нужно знать, взорвалась ли бомба. Для этого необходимо определить разнесенную переменную, поэтому добавьте следующее значение под переменной levelMask:
private bool exploded = false;
Теперь внутри OnTriggerEnter () добавьте этот фрагмент:
if (!exploded && other.CompareTag("Explosion")) { // 1 & 2 CancelInvoke("Explode"); // 2 Explode(); // 3 }
Он будет выполнять сразу несколько функций:
- Проверяет, не взорвалась ли бомба.
- Проверяет, назначен ли коллайдеру триггера тег взрыва.
- Отменяет уже вызванный Explode — если вы этого не сделаете, бомба может взорваться дважды.
- Реализует взрыв
Теперь у вас есть переменная, и наиболее логичное место для установки этого параметра — внутри Explode (), сразу после отключения компонента MeshRenderer:
... GetComponent<MeshRenderer>().enabled = false; exploded = true; ...
Сохраните файл и снова запустите игровую сцену. Бросьте несколько бомб рядом друг с другом и посмотрите, что произойдет:
Теперь вам необходимо обработать реакцию игроков на взрывы и то, как игра переводит в состояние победы или ничьей.
Смерть игрока и как с этим справиться
Откройте скрипт Player.cs в редакторе кода.
Сейчас у вас нет переменной, указывающей, жив ли игровой персонаж или мертв, поэтому добавьте логическую переменную вверху скрипта, прямо под переменной canMove:
public bool dead = false;
Эта переменная используется для отслеживания смерти игрока в результате взрыва.
Затем добавьте следующее значение выше всех других переменных:
public GlobalStateManager globalManager;
Это ссылка на GlobalStateManager, скрипт, который уведомляет о гибели игрового персонажа и определяет, какой игрок выиграл.
Внутри OnTriggerEnter () уже есть значение, определяющее был ли перк поражен взрывом, но все, что он делает сейчас, это записывает данные в окно консоли.
Добавьте этот фрагмент в вызов Debug.Log:
dead = true; // 1
globalManager.PlayerDied(playerNumber); // 2
Destroy(gameObject); // 3
Этот фрагмент кода выполняет следующие действия:
- Устанавливает переменную, чтобы вы могли отслеживать смерть игрока.
- Уведомляет о смерти игрока.
- Уничтожает GameObject игрока.
Сохраните этот файл и вернитесь в редактор Unity. Вам нужно будет связать GlobalStateManager с обоими игроками:
В окне иерархии выберите оба GameObject Player.
Перетащите GameObject Global State Manager в слоты Global Manager, как показано на изображении ниже:
Снова запустите сцену и убедитесь, что один из игроков уничтожается взрывом.
Теперь каждый игрок, оказавшийся на пути взрыва, мгновенно умирает.
Определение победителя и проигравшего
Откройте GlobalStateManager.cs в редакторе кода.
Чтобы GlobalStateManager отслеживал, какой игрок умер, вам нужны две переменные. Добавьте их вверху скрипта над PlayerDied ():
private int deadPlayers = 0; private int deadPlayerNumber = -1;
Во-первых, deadPlayers будет содержать количество погибших игроков. DeadPlayerNumber устанавливается после смерти первого игрока и указывает, каким именно он был.
Теперь, когда у вас есть эти настройки, вы можете использовать фактическую логику. В PlayerDied () добавьте следующий фрагмент кода:
deadPlayers ++; // 1 если (deadPlayers == 1) {// 2 deadPlayerNumber = playerNumber; // 3 Invoke ("CheckPlayersDeath", .3f); // 4 }
Этот фрагмент отвечает за действия:
- Добавляет одного мертвого игрока.
- Если это первый погибший игрок …
- Устанавливает номер мертвого игрока для игрока, который умер первым.
- Проверяет, умер ли другой игрок или попал ли он в зону взрыва через 0,3 секунды.
Последняя задержка имеет решающее значение для проверки раунда на ничью. Если вы проверите сразу, вы можете не увидеть, что все умерли. Достаточно 0,3 секунды, чтобы определить, все ли погибли.
Теперь создайте новый метод с именем CheckPlayersDeath в скрипте GlobalStateManager:
void CheckPlayersDeath() { // 1 if (deadPlayers == 1) { // 2 if (deadPlayerNumber == 1) { Debug.Log("Player 2 is the winner!"); // 3 } else { Debug.Log("Player 1 is the winner!"); } // 4 } else { Debug.Log("The game ended in a draw!"); } }
Это логика, лежащая в основе различных операторов if в этом методе:
- Один игрок умер, и он проиграл.
- Игрок 1 умер, поэтому победителем становится Игрок 2.
- Игрок 2 умер, поэтому победителем становится Игрок 1.
- Оба игрока погибли, так что ничья.
Сохраните свой код, затем последний раз проверьте вашу игру на наличие ошибок.
Что делать дальше?
Если у вас возникли проблемы, загрузите готовый Final Project по Bomberman.
Теперь вы знаете, как создать простую игру, похожую на Bomberman, с помощью Unity.
Если вы хотите узнать больше о работе с движком, ознакомьтесь с нашим руководством «Введение в Unity».
Я настоятельно рекомендую вам продолжить работу над этой игрой — сделайте ее своей, добавляя новые функции! Вот некоторые предложения:
?Ограничьте количество сбрасываемых бомб
?Добавьте хрупкие блоки, которые разрушаются взрывами
?Создавайте привлекательные для игроков бонусы
?Добавьте жизни или способ их заработать
?Элементы пользовательского интерфейса, указывающие, какой игрок выиграл
Обязательно поделитесь здесь своими творениями, мы бы хотели посмотреть, что вы, ребята, можете придумать!
Если у вас есть какие-либо замечания или вопросы, вы можете озвучить их в разделе комментариев.