Как создать 2D-шутер в Unity
Создание шутеров позволяет научиться многим вещам, которые пригодятся в разработке игр других жанров.
На примере шутеров можно научиться нескольким очень полезным приемам работы с Unity: инстанцированию префабов, созданию логики для NPC, изменению здоровья персонажей и так далее.
Перед чтением статьи рекомендуем ознакомиться с другими нашими материалами о базовых навыках работы с Unity, которые пригодятся для создания шутера:
Пишет о разработке сайтов, в свободное время создает игры. Мечтает открыть свою студию и выпускать ламповые RPG.
Что делает шутер шутером
Кроме стрельбы, также можно реализовать и другие возможности:
Стрельба
Есть два основных способа реализовать стрельбу:
Каждый стоит разобрать более подробно.
Начало выстрела
Затем нужно написать код, который позволит персонажу вращаться вместе с этой точкой. Создайте скрипт Controller.cs и прикрепите его к персонажу:
Теперь персонаж сможет двигаться и вращаться вместе с объектом FirePoint:
Теперь можно разобрать оба способа стрельбы.
Стрельба снарядами
Добавьте коллайдер с триггером и создайте скрипт Bullet.cs, в котором будут обрабатываться попадания (он будет рассмотрен чуть позже). Сохраните объект в качестве префаба, а потом перетащите его в компонент Shooting.cs. Туда же перетащите FirePoint:
Теперь нужно написать метод, который будет создавать (инстанцировать) новые снаряды на карте:
Продолжаем делать наш Unity for dummies платформер.
Полные версии кода будут находиться в конце поста.
Если вы не в курсе, как работают хуки жизненного цикла в Unity, вы можете ознакомиться с данной статьёй.
Создаём в панели ассетов C# cкрипт PlayerBehaviour, и запихиваем его в папку Scripts (её можно создать в той же панели ассетов).
Затем выбираем наш объект Player слева, в меню иерархии, и закрепляем инспектор (меню справа), нажав на замок в верхнем правом углу.
Пока закрываем все ненужные компоненты (Box Collider 2D, etc), нажимая на стрелочку у каждого из них.
Теперь перетаскиваем наш скрипт в инспектор, и он прикрепляется к объекту Player (если вы не выполнили предыдщуий пункт с закрепением меню, то вы этого сделать не сможете).
Если зайти в Edit => Project Settings => Input Manager, то мы можём увидеть настройки управления. Например, прыжок (Jump) назначен на пробел, в то время, как движение по горизонтали назначено на a, d, ←,→.
Запомним это для того, чтобы затем использовать в коде (названия инпутов Jump, Horizontal).
Пора покодить. Два раза нажимаем на наш скрипт PlayerBehaviour, и Unity отдаст его в руки Visual Studio.
Сохраняем файл, и переходим обратно в Unity. После того, как Unity проверит код (линтинг, ошибки, пустые переменные). Если что то неправильно, то вы увидите сообщение об ошибке.
Исправляем код, и страшные красные буквы исчезают. Можно запустить игру, и проверить, как все работает.
Мне не нравится, что персонажа переворачивает. Исправим.
Заходим в инспектор, открываем Rigidbody2D и ставим галочку на Freeze Rotation Z.
Есть два основных способа научить камеру двигаться за персонажем.
1. Подвинуть камеру внутри сцены, чтобы она была на персонаже, а затем в привязать камеру к объекту персонажа через меню иерархии.
2. Задать движение камеры за персонажем програмно.
Создаём скрипт CameraBehaviour, и привязываем его к объекту Main Camera (добавляем через инспектор).
Открываем наш новый скрипт, и начинаем кодить (да, мне на работе не хватает, продолжаю и после).
Фиксируем объект камеры на замок в инспекторе, и перетаскиваем наш объект Player в GameObject.
Теперь камера двигается за игроком.
Однако камера постоянно вылезает за пределы уровня, показывая синий фон.
Я не уверен, что моё решение правильное. Буду рад, если кто-нибудь поделится своими соображениями на данный счёт.
Добавляем в наш скрипт CameraBehaviour следующий код:
Двигаем камеру, и снимаем наши измерения ее положений.
На скрине ниже камера стоит в левом нижнем углу сцены.
Проставляем снятые позиции для камеры (мои и ваши позиции будут отличаться).
Теперь обновим метод UpdateCameraPosition в скрипте CameraBehaviour
Отлично, камера перестала «гулять». Работаем дальше.
Выбираем наш Foreground, и создаём новый слой ‘Ground’ в инспекторе.
Снова тыкаем на Foreground, и выбираем в поле Layer наш только что созданный слой.
Добавим в PlayerBehaviour такую строчку.
Теперь в объекте Player мы можем выбрать наш свежесозданный слой.
Обновляем наш PlayerBehaviour. Добавим коллайдер (rigidBody мы создавали ранее).
Теперь мы можем обновить обработку перемещения персонажа в методе updatePlayerPosition
Летать мы разучились, а по платформе больше не скользим. Победа!
Я не уверен, что моё решение правильное. Буду рад, если кто-нибудь поделится своими соображениями на данный счёт
Привет.
Я, конечно, дохуя вовремя, когда у тебя уже пять статей и рефакторинг, но вот щас иду за тобой пока вот на этом месте.
Короче, в ассет-сторе есть проект 2D Game Kit от разрабов движка. Там движение камеры сделано буквально следующим образом, если помню (давно открывал). Уровень завёрнут в коллайдер, ещё один коллайдер на камере. Соответственно, когда она о него стукается, то расслабляется и не двигается. И ещё там угарный камера-лаг стоит, чтоб камера не жёстко следовала за персонажем, а как бы за верёвочку.
Привет.
Да, я изначально так и пробовал.
Но персонаж, почему-то, попросту исчезает при старте игры.
Попробую ради эксперимента новый проект создать.
Надо разобраться куда исчезает. Самое простое, поставить на паузу, перейти из окна Game в Scene, и просто посмотреть. А потом уже делать выводы.
Разработка космической стрелялки для android с использованием игрового движка Unity3D
Доброго времени суток! В этой статье я хочу поделиться опытом разработки своей игры с использованием игрового движка Unity.
Разработка
Графика игры состоит из следующих текстур:
На основе этих текстур были созданы следующие префабы
asteroidrotate — метеорит который нужно уничтожать
enemy — вражеский звездолет
explosionasteroid, explosionenemy, explosionplayer — это анимации взрыва созданные с использованием particle system
Игра будет включать в себя 2 сцены: главное меню и игровая сцена.
Главное меню
Где «menu» это главное меню а «1» это игровая сцена.
В качестве фона я использовал спрайт с именем «space», на нем нарисован космос.
Далее создаем скрипт «menu.cs» (Щелкаем правой кнопкой → выбираем Create → C# Script) и «вешаем» его на background. background это спрайт со 100% прозрачностью на котором создаются основные элементы управления (START/EXIT) а space служит просто для декорации.
Содержимое скрипта activemenu:
Должно получиться примерно вот так:
Создание игровой сцены
Игровая сцена состоит из следующих ключевых объектов:
космос (спрайт «space»)
игрок
метеориты
вражеские корабли
прочее(лазеры и взрывы).
Космос организован также как в главном меню. Здесь можно ничего не трогать.
Далее нужно обратить внимание на то что в игре будут постоянно генерироваться из префабов, такие объекты как метеориты, выстрелы и враги. И те объекты которые упустил игрок нужно удалять, чтобы в лишний раз не нагружать память.
Это можно сделать следующим образом. Создаем новый игровой объект на сцене(у меня это «controlcountobjects»), добавляем к нему компонент boxcollider и растягиваем его вокруг игровой зоны.
Далее добавляем на него скрипт со следующим содержимым:
В этом скрипте происходит удаление элементов при выходе из box collider объекта controlcountobject. Таким образом получается некая зона при выходе из которой происходит удаление объектов.
Генерация метеоритов
Добавляем данный скрипт на камеру:
Метеорит
Выше можно заметить, то что метеориты которые появляются на сцене разного размера и еще они вращаются. Это все потому что метеорит реализован с помощью двух GameObject, где один находится внутри другого (матрешка).
Внешний объект «asteroidrotate» описывает движение метеорита, содержит circle collider и запускает эффект взрыва, в случае столкновения, а «aster» рандомно при своем создании задает скорость направления вращения и размер.
Скрипт для asteroidrotate:
Игрок
Кидаем спрайт с звездолетом на игровую сцену, добавляем на него box collider(не забываем отмечать «Is Triger»), добавляем rigibody(нужно заморозить Y координату). Все это необходимо чтобы игрок мог взаимодействовать с другими игровыми объектами.
Можно еще добавить компонент particle system, который будет изображать струю из двигателя.
Результат работы скрипта.
Когда игрок будет удален а в файл будет записан новый рекорд уничтоженных метеоритов, то тогда нужно переходить на главное меню.
Создаем скрипт со следующим содержимым и подключаем его на камеру:
Данный скрипт наблюдает за состоянием игрока.
Взрывы
Эффект взрыва можно создать с помощью Particle System. Но тогда он будет зацикленным и вечно повторяться. Чтобы этого не было, на префаб взрыва нужно добавить следующий скрипт.
Выстрелы
Выстрелы генерируются из места нахождения игрока во время его движения.
В скрипте для выстрела нужно задать только скорость и направление.
Генерация противников
Генерация противников устроена аналогично генерации метеоритов.
Содержимое скрипта Enemygenerator:
Логика противника
Поведение противника организовано таким образом, что он просто летит по прямой, ведет огонь и в случае своего поражения может оставить капсулу для активации игроком нового оружия
Выглядит это вот так:
Как создать игру на Unity
Эксперт в медицинских тренажерах VR на Unity, физических симуляциях и сетевых играх.
Сделать игру на Unity сможет даже начинающий кодер, который не знает С#. Большое количество обучающих материалов и активное комьюнити значительно облегчают первые этапы знакомства с движком и создание первого проекта. Вместе с ведущим разработчиком Unity Владимиром Филимоновым и преподавателем на курсе «Разработчик игр» разбираемся, почему писать код — не главное в геймдеве и какие базовые инструменты нужно освоить, чтобы соорудить свою первую игру.
Что такое Unity
Unity — это и среда разработки, и игровой движок, с помощью которого создаются проекты для разных платформ: ПК, мобильных устройств, игровых консолей и интернет-платформ, — поэтому он называется кроссплатформенным. В Unity есть инструменты для создания объектов, их перемещения, работы с графикой, текстурами и звуком, поэтому сделать полноценную игру с его помощью можно даже в одиночку.
Наглядный пример игры, созданной на Unity, которая поддерживает разные устройства, — Genshin Impact, успешный мультиплатформенный проект китайской студии miHoYo Limited. Более популярной стала ее мобильная версия, но пользователи могут войти в аккаунт, например, с компьютера и продолжить играть с того же момента, на котором остановились в мобильной версии. Кроме Genshin Impact, на Unity созданы такие известные проекты, как Hearthstone, Outlast, Cuphead, Pokemon GO и многие другие.
В игровой индустрии существуют десятки разных движков. Одни разработаны под конкретную игру, на других можно делать игры конкретного жанра (шутеры от первого лица, платформеры, гонки), а есть универсальные, вроде Unity, которые открывают разработчикам больше возможностей. Уникальность Unity заключается в сочетании нескольких факторов. Кроме того, что этот движок позволяет создавать проекты под разные устройства и не ограничивает разработчика конкретным жанром, он:
Как создать простую игру
При создании собственного проекта важно помнить, что разработка кода — это примерно 20% игры; гораздо большее значение в ней имеют другие аспекты:
Разработчик игр на Unity
Освойте C#, Unity и основы гейм-дизайна и начните делать собственные игры. Дополнительная скидка 5% по промокоду BLOG.
Перед созданием игры важно продумать все эти моменты и представить общую картину, а также найти референсы, на которые можно ориентироваться, продумать опорные точки сюжета и механики. Для создания игры именно на Unity также пригодится понимание некоторых базовых терминов, с которыми постоянно придется сталкиваться в процессе разработки:
Установка Unity занимает 5–10 минут. Для этого нужно зайти на официальный сайт проекта и выбрать бесплатный тариф для физических лиц — Personal. Его будет достаточно для самостоятельного изучения Unity и создания первой игры. Еще один бесплатный тариф — студенческий, но он подойдет тем, кто зарегистрирован на GitHub и может подтвердить свой ученический статус.
После нажатия кнопки «Начать» откроется Unity Store. Для новых пользователей в нем есть вариант установки движка с обучающим руководством. С помощью кнопки «Начните здесь» скачивается установщик UnityHubSetup.exe, который запускается как любой другой установщик: нужно просто открыть файл, принять условия пользовательского соглашения и нажать кнопку «Установить».
Русского языка в настройках нет, так что придется совершенствовать технический английский. Всего Unity занимает 11,3 Гб,поэтому перед установкой лучше проверить свободное место на диске и почистить его при необходимости.
Следующий шаг — создание Unity ID. Можно регистрироваться с помощью почты или использовать предложенные аккаунты, например Google, Facebook или Apple. Важно поставить первые две галочки: согласие с условиями использования Unity и признание политики конфиденциальности. Третья галочка — это согласие на маркетинговые рассылки, ее ставить не обязательно.
После регистрации Unity предложит создать тестовый проект Microgame. На выбор предлагается пять шаблонов:
Можно выбрать любой из них и посмотреть, как работает создание игры в конкретном жанре. Обучающий материал пошагово демонстрирует назначение различных окон в интерфейсе и принцип работы с элементами игры: как заставить двигаться персонажей, поменять текстуру объекта или его форму. В обучении окно Scene, в котором происходит вся работа с элементами, уже заполнено различными объектами, но при создании проекта с нуля оно будет пустым.
Создание проекта
После обучения можно перейти к созданию своей первой игры на Unity с помощью кнопки NEW в меню проектов.
Новому проекту присваивается имя, выбираются место хранения на диске и темплейт — то есть шаблон для разработки, внешний вид и функционал которого зависит от количества измерений в игре. Проще начинать с 2D-проектов, так как для этого формата создано больше готовых ассетов. Конечно, можно сразу начать делать 3D-игры, но в этом случае многие элементы и анимации придется самостоятельно создавать с нуля или выделять бюджет на то, чтобы делегировать эту часть работы другим специалистам.
Настройка интерфейса
В стандартном интерфейсе проекта шесть элементов рабочей области:
Добавление объекта
Объекты на экран Scene можно добавить из Asset Store. Для этого на панели инструментов нужно кликнуть на вкладку Window –> General –> Asset Store.
В строке поиска можно по названиям найти нужные компоненты, например, сет Free Platform Game Assets.
Как и другие ассеты, он загружается с помощью кнопки Import.
Перед загрузкой появится список всех компонентов, которые содержит этот пакет; некоторые из них можно исключить. Если в списке есть персонажи, текстуры или другие элементы, которые вам не нужны, можно просто снять галочки, и пакет загрузится без них.
После установки все ассеты будут доступны в окне Project. Теперь можно комбинировать и перемещать эти объекты, менять их форму, причем сделать это можно с помощью мыши или горячих клавиш, не написав ни одной строчки кода. Например, из перечня платформ самых разных видов можно выбрать одну и мышкой перетащить ее в рабочую область.
Шаг 2. Перенести в область Scene
Работа со скриптами
За поведение игровых объектов отвечают присоединенные к ним компоненты (Components). Базовый компонент любого объекта — Transform, он отвечает за положение элемента в окне Scene, возможность поворачивать и масштабировать его. К базовому компоненту можно добавить, например, Renderer, который меняет цвет, или RigidBody, который отвечает за массу и физику объекта. Но кроме базовых компонентов, объектам можно задавать особые условия, и для этого как раз используются скрипты.
Создать новый скрипт можно в окне Project, кликнув мышкой на Assets –> Create –> C# Script.
Двойным кликом мыши скрипт открывается в текстовом редакторе. Скрипты, как и все остальное в Unity, прописываются на С#, так что для создания сложных проектов разработчикам все же придется освоить этот язык.
Базовые элементы скриптов — это:
Рассмотрим, например, функцию start. Любое действие в ней произойдет только один раз, когда запустится игра. Пропишем здесь print (“Hi”).
И можно заметить, что в консоли это слово выводится один раз.
Функция update — повторяющаяся, ее можно использовать, например, для передвижения объекта. Для этого в скрипте задается переменная int i = 0, она выводится на экран с помощью функции print (i) и увеличивается на одну единицу за каждый шаг с помощью i++.
В консоли можно будет заметить, что апдейт действительно срабатывает каждый фрейм и объект, к которому применен этот скрипт, плавно движется.
Настройка триггеров
Для понимания сути триггеров важно усвоить, что такое коллайдер (Collider). Это компонент, который присваивается объекту в пространстве игры, задает форму и делает его твердым, недоступным для прохождения сквозь него. Например, если мы разместим монетку в 2D-пространстве и захотим сделать так, чтобы она упала на платформу, то без использования компонента Collider ничего не получится — монетка пролетит сквозь платформу.
Поэтому обоим объектам необходимо присвоить компонент Box Collider 2D — это тонкая зеленая линия, которая обводит элементы по контуру, и за счет этой рамки они становятся твердыми, то есть один не может пройти сквозь другой.
Так объекты обязательно соприкоснутся и монета встанет на платформу.
Триггер (Trigger) — это пространство на карте, при попадании объекта в которое происходит действие; он тоже обводит объект или область в пространстве по краям. По сути, это тот же коллайдер, только триггер позволяет объектам проходить внутрь этой области. Представьте, что на ту же самую платформу вместе с коллайдером наброшен триггер, и при попадании персонажа внутрь триггерной области активируется телепорт — персонажа перебрасывает в другую точку карты.
Чтобы создать триггер, нужно накинуть тот же самый компонент коллайдера, но поставить галочку Is Trigger.
Триггеры распознают три варианта взаимодействия области на карте и объекта:
Что дальше?
Освоить интерфейс Unity довольно легко, в интернете есть разные гайды на тему того, как сделать это действительно быстро. Например, видео «Я сделал игру на Unity за неделю» или обучающий мини-курс «Как создать RPG за час». Можно изучать геймдев самостоятельно или начать обучение на курсе — это ускорит процесс и даст более заметные результаты.
Для работы со скриптами и создания более сложных механик разработчикам в любом случае понадобится С#, так что к его изучению вы тоже рано или поздно придете. Но в отличие от Unity, по C# нет в свободном доступе такого большого количества актуальных и понятных гайдов, так что в целях экономии времени и сил лучше записаться на курс «Разработчик игр на Unity», где все знания упакованы в структурированные блоки и сбалансированы с практикой на тренажерах.
Разработчик игр на Unity
Уже во время обучения вы создадите себе портфолио, сможете брать подработки и откликаться на вакансии.
Создаём простой зомби-шутер на Unity
Авторизуйтесь
Создаём простой зомби-шутер на Unity
В преддверии старта нового потока курса «Unity Game Developer. Professional», подготовили статью по мотивам урока от преподавателя курса — Николая Запольнова, где он пошагово разъяснил процесс создания простейшего шутера на Unity.
Статья нацелена в первую очередь на новичков с базовыми знаниями терминологии программирования. Главной её целью является желание показать, что при некотором багаже знаний вы сможете довольно скоро освоить работу с движком и создадите свои первые проекты.
Последующие разделы могут показаться простыми и не дадут ничего нового, если вы уже опытный специалист в разработке игр на Unity.
Введение в Unity
Можете пропустить раздел, если вы уже знакомы с этим редактором. Переходите к разделу «Игровой мир: начало».
Базовым блоком при создании шутера на Unity является «сцена», представляет, обычно, 1 уровень игры. Однако, бывает так, что одна сцена включает несколько уровней, или один объёмный уровень дробится на ряд сцен. Сцена, как матрёшка, состоит из объектов, наполненных компонентами.
Именно компоненты делают интерфейс живым и привлекательным, обеспечивают такие функции как, например, визуализация, анимация, реалистичность. С помощью C# разработчик может сам написать их, сформировать нужную ему логику будущей игры. Перейдём непосредственно к движку.
После запуска редактора и создания нового файла, перед вами появится окно, состоящее из 4-х основных областей:
Слева вверху расположено окно Hierarchy, в котором отображается иерархия игровых объектов сцены: камеру (Main Camera), отображающую виртуальный мир глазами играющего, и источник света (Directional Light), определяющий направление освещённости сцены, без которого она выглядела бы как чёрное поле.
По центру расположено окно Scene, где, собственно, можно визуально редактировать уровень — вращать и передвигать с помощью мыши, получая наглядный результат. Справа от него находится вкладка Game (на скриншоте она неактивна). При переключении на неё кнопкой со значком воспроизведения сцена отображается через Main Camera.
В правой части экрана можно увидеть фрэйм Inspector — представляет собой набор полей с параметрами отмеченного объекта с возможностью их редактирования. На картинке видны 2 компонента: Transform, отвечающий за положение камеры, и, непосредственно, Camera — воплощает её функционал. Компонент Transform свойственен всем игровым объектам в Unity.
Внизу окна находится вкладка Project, содержащая цифровые объекты проекта (ассеты). По сути, это файл с данными, которые можно применить для создания сцен и интерфейса: текстуры, двумерные и трёхмерные элементы, звуковое сопровождение, анимация и конфигурации. Разработчики, не обладающие выдающимися навыками графического дизайна, могут загружать ассеты из Unity Asset Store в том числе и бесплатно. Также в Unity поддерживается опция загрузки файлов в стандартных форматах, например, png, jpg, fbx.
Справа от Project можете наблюдать неактивную вкладку Console. Здесь отображаются баги (ошибки), которые стоит периодически отслеживать, и сюда можно вводить части кода с целью отладки.
Игровой мир: начало
Неумение рисовать для разработчика игр — не приговор. В представленном примере взята графика из бесплатного раздела Unity Asset Store (ссылки внизу статьи), с помощью которой был собран элементарный уровень:
Выбранные элементы можно просто перетащить в окно проекта при помощи мыши и расположить по желанию:
Помимо этого, в Unity можно в один клик наполнять сцену типовыми объектами: сфера, куб, плоскость. Это делается нажатием правой кнопки мыши в области Hierarchy, из предложенного списка можно выбрать нужный элемент. В качестве примера — 3DObject⇨Plane. В представленной сцене из этих плоскостей с наложением текстуры собран асфальт. Текстура выбрана также в Unity Asset Store.
Важно!
Наземное покрытие состоит именно из нескольких областей, а не из одной с высоким значением scale, поскольку при такой схеме покрытие выглядит более естественно, без чрезмерного увеличения текстуры. Можно, конечно, поиграть с параметрами материала, но представленный вариант является более простым и подходит для новичков.
И всё-таки оно движется!
Игровой мир уже вырисовывается, но на уровне пока отсутствует динамика. Согласно концепции, игрок будет противостоять атакам зомби, для этого они должны не только «видеть» персонажа игрока, двигаться в его сторону, но и огибать преграды на пути.
Для реализации этих функций используется инструмент «навигационная сетка» (Navigation Mesh). По конфигурации уровня он вычисляет пространства, где можно двигаться, группирует их и даёт возможность поиска оптимального расстояния между двумя точками сцены. Информация сохраняется в ассет с возможностью последующего редактирования, это называется «запекание» (baking). Для более сложных сцен с динамичными преградами используется компонент NavMeshObstacle.
Обратите внимание, чтобы Unity мог распознать объекты верно, следует в области “Inspector” напротив объектов отметить Navigation Static и кликнуть стрелку вниз для параметра Static:
И хоть пока мы не будем обращаться к остальным опциям в данной области, они также важны в процессе качественной визуализации сцены. Вам будет полезно с ними разораться позже, поскольку даже одна галочка может серьёзно улучшить внешний вид.
В меню нужно выбрать Window⇨AI⇨Navigation⇨Bake. В открывшейся вкладке Unity попросить обозначить физические параметры объектов: радиус персонажа, высоту ступеней и так далее. Оставляем предустановленные значения, нажимаем кнопку Bake.
После проведения нужных расчётов Unity выдаст результат:
Область для передвижений обозначается синим цветом. Вокруг препятствий автоматически устанавливается небольшое дополнительное расстояние, которое зависит от радиуса героя. Благодаря этому персонаж не будет застревать в текстурах.
При наличии навигационной сетки можно использовать NavMeshAgent для поиска и управления траекторией перемещения игровых объектов в сцене.
Перейдём к персонажу зомби. Для его создания нужно добавить объёмную модель из ассетов и компонент NavMeshAgent:
На языке C# разработаем компонент, чтобы персонаж начал движение, а NavMeshAgent знал направление.
В Unity нужно выбрать корневую папку Assets, где нажатием правой кнопки мыши будет создана папка Scripts. Именно в ней будут находится все скрипты для облегчения поиска. В созданной папке создаём файл C# с именем Zombie и добавим в соответствующий игровой объект:
Скрипт открывается двойным щелчком левой кнопки мыши. Далее представлен результат в Unity:
Unity подключил библиотеки для типовой заготовки System.Collections и System.Collections.Generic, которые часто необходимы в коде игр, а также UnityEngine с интерфейсом прикладного программирования движка.
Автоматически был создан класс под названием Zombie, которое совпадает с именем файла. Это важно, поскольку так Unity соотносит компонент и относящийся к нему скрипт.
Класс является производным от MonoBehaviour (базового класса для пользовательских компонентов). Он содержит 2 метода, которые будут вызываться автоматически: Start — после загрузки сцены, Update — покадрово. Это одни из множества аналогичных функций, которые вызывает движок. С полным перечнем можно ознакомиться здесь: https://docs.unity3d.com/Manual/ExecutionOrder.html.
Пришло время заставить зомби перемещаться по карте. Следует в начало файла вставить директиву using UnityEngine.AI, чтобы подключить библиотеку UnityEngine.AI, где хранятся NavMeshAgent и иные классы, относящиеся к сетке. Для этого добавим в начало файла директиву using UnityEngine.AI.
Доступ к нужному классу можно получить, применив метод GetComponent (метод вызывается из компонента и возвращает значение в виде ссылки на любой другой компонент того же объекта). Создадим в классе поле NavMeshAgent navMeshAgent, с помощью Start получим ссылку на NavMeshAgent, и зададим движение в точку с координатами (0;0;0). Итоговый код должен иметь вид:
После запуска игры зомби двинется в центр сцены:
Зомби в погоне за жертвой
Уровень стал динамичнее, но не хватает противостояния. Нужно добавить игрока, который самоотверженно будет сражаться с мертвяками.
Игровой объект Player создаётся так же, как и для зомби — на основе 3D-модели. Выберем теперь полицейского, дополним компонентом NavMeshAgent и скриптом с соответствующим названием.
Файл с кодом Player пока остаётся без изменений, при этом потребуется внести исправления в скрипт Zombie. Игрока лучше наделить более высоким приоритетом, указав для свойства Priority в компоненте NavMeshAgent число меньше 50. Таким образом, при встрече на карте игрок будет иметь более крепкую позицию и сможет оттолкнуть зомби.
Чтобы преследовать игрока, зомби нужно видеть его местоположение. С помощью стандартного метода FindObjectOfType необходимо получить ссылку на игрока в классе Zombie. После этого обратимся к компоненту transform игрока с запросом значения position. Чтобы шутер был захватывающим, важно, чтобы зомби нападали в течение всей игры, а не только в начале, поэтому надо задать цель для NavMeshAgent в Update. Скрипт выглядит так:
Проверяем, находит ли зомби цель для нападения:
Итак, всё готово для зомби-апокалипсиса. Но пока главный герой неподвижен, у него нет шансов на спасение, поэтому надо научить его уворачиваться от атаки.
Игрок управляется с помощью определённых комбинаций нажатия клавиш, значения которых в Unity возвращает метод GetKey, входящий в стандартный класс Input.
Важно!
Такое решение задачи принято для упрощения кода. Однако, общепринятым считается использование Input.GetAxis и биндинг через Project Settings⇨Input Manager.
Настало время внести корректировки в скрипт Player:
По примеру персонажа зомби при помощи Start получена ссылка на NavMeshAgent игрока, зафиксированная в поле класса. Также её следует добавить в поле moveSpeed. Его значение доступно для редактирования сразу в области Inspector в Unity, поскольку это общедоступное поле. Такая опция будет по достоинству оценена гейм-дизайнером, если он задействован в работе над проектом.
Устанавливаем значение скорости равное 10:
Для проверки нажатия стрелок на клавиатуре и управления траекторией движения персонажа следует использовать Input.GetKey из метода Update. В Unity использованы координаты X и Z для обозначения земли, а ось Y направлена вверх, перпендикулярно им.
Вектор движения dir сформирован, теперь его нужно нормализовать и умножить на ранее заданную скорость. Результат итерации отправляется в navMeshAgent.velocity, нужное значение будет добавлено автоматически. Нормализация нужна, чтобы вектор не был длиннее единичного, а объект двигался с равной скоростью как по диагонали, так и по прямой.
Теперь персонажи имеют одинаковую подвижность, что можно проверить при запуске игры:
Важно, чтобы камера двигалась вместе с персонажем. В этом поможет несложный скрипт, назовём PlayerCamera:
Главное, чтобы он был понятен. В качестве особенностей стоит отметить замену Update на LateUpdate — схожий по логике, но выполняется только после всех скриптов уровня. Эта замена используется, поскольку NavMeshAgent должен рассчитать новое расположение персонажа до перемещения камеры. В противном случае глаз будет резать эффект «подёргивания». После закрепления этого компонента за объектом Main Camera, при запуске игры, камера будет следовать за главным персонажем.
Во-первых, анимация — это красиво
После того, как персонажи игры стали подвижные, настал момент сделать их реалистичнее и привлекательнее. В этом поможет компонент Animator и инструмент Animator Controller.
Последний задаёт разные автоматически сменяемые состояния объекта, к которым привязывается определённая анимация. Чтобы применить этот инструмент к объекту, надо создать вложенную папку Animator Controller в директории Animations. Переименуем её в Zombie. Редактор примет вид:
На данном этапе состояния отсутствуют. Есть точки входа и выхода: Entry и Any State, Exit. Можно добавить несколько анимацией из ассетов:
Первая анимация, которую перетащили в область, привязана к точке начала, и будет проигрываться на старте уровня. Для перехода к другим состояниям нужно обозначить правило.
Нужно выбрать кнопку Parameters в левом верхнем углу, далее нажать на «+», выбрав параметр типа float. Новый параметр назовём speed:
Следует создать 2 перехода: Z_idle_A в Z_run и в обратную сторону, чтобы воспроизводилась анимация при значении speed больше 0.
Чтобы перейти из idle в run нужен двойной щелчок мыши по прямоугольнику Z_idle_A, выбираем Make Transition. Далее настраиваем параметры появившейся стрелки. Нужно отжать чекбокс Has Exit Time, в противном случае анимация будет воспроизводиться не по нашему условию. Также в списке Conditions необходимо выбрать «+», чтобы значение speed было больше 0.
Аналогичный алгоритм при переходе в обратную сторону. Теперь условие speed принимает значение меньше 0.0001. Параметр float пока не проверен на равенство:
Контроллер переместим мышью в соответствующее поле компонента Animator к объекту Zombie:
Поскольку MovementAnimator относится к игровому объекту Zombie, Animator расположен в дочернем объекте, таким образом, для получения доступа к Animator необходимо использовать стандартный метод GetComponentInChildren.
Далее необходимо отправить запрос о векторе скорости у NavMeshAgent в Update, рассчитать длину и передать результат в параметр скорости speed.
Чтобы анимировать зомби, надо добавить MovementAnimator в объект Zombie:
Код управления анимацией, помещённый в отдельный компонент MovementAnimation, даёт возможность не создавать контроллер игрока заново, а скопировать уже созданный для зомби. Чтобы скопировать, следует выбрать файл Zombie и нажать горячие клавиши Ctrl+D. Нужно внести изменить анимации на m_idle_А и m_run.
Потребуется ещё ряд дополнений
В класс Zombie добавим несколько строк.
В NavMeshAgent задан такой угол поворота, что персонаж с задержкой во времени реагирует на изменение траектории движения. Добавив эти 2 строчки, можно избавиться от такого недочёта: первая строка передаёт NavMeshAgent сигнал о снятии управления поворотом (сделаем это сами); вторая — назначает поворот в сторону по вектору движения.
Важно!
Чтобы назначить поворот объекта, в 3D-графике используются эйлеровы углы, матрицы поворота или кватернионы. Первые 2 подвержены эффекту шарнирного замка (Gimbal Lock) и часто не удобны в работе, поэтому в данном примере использован кватернион. Даже при использовании матриц и углов Эйлера в Unity, они всё равно хранятся в кватернионах. В остальном, инструментарий движка настолько удобен, что позволяет разработчику не вникать в математические тонкости.
Вижу цель, вижу препятствия
Размещаем на плоскости земли курсор под управлением мыши, чтобы игрок мог выбирать цель перед выстрелом. На мониторе курсор перемещается в 2D-пространстве, а в игровой сцене трёхмерное пространство. При этом играющий видит уровень через глаз, где в одной точке собираются все лучи света. После их объединения получается пирамида видимости:
Человеческое зрение улавливает только элементы, попавшие в эту пирамиду. Система автоматически усекает пирамиду ближней и дальней плоскостями. Ближняя плоскость (окрашена жёлтым) находится со стороны экрана, который физически не может отображать элементы, расположенные к наблюдателю ближе него. Дальняя плоскость отсекается, поскольку ресурсы компьютера ограничены — невозможно продлить луч в бесконечность.
Чтобы определить объект, на который указывает курсор, нужно выпустить из точки его расположения луч от ближней плоскости к дальней. С точки зрения играющего, тем самым объектом станет первый, с которым пересечётся луч.
Задействуя метод Raycast из класса Physics, можно выстроить луч, найти его пересечения с объектами на уровне. Он определит пересечения со всеми объектами, когда для нас важно, чтобы курсор передвигался по земле. Нужно задать в Unity ограниченный набор объектов для поиска (например, только плоскость земли).
Выделив игровой объект, можно увидеть вверху инспектора выпадающий список Layer, начально значение которого Default. В списке нам интересен пункт Add layer…, предназначенный для редактирования слоёв. Нужно добавить слой с
названием Ground:
Всем плоскостям земли в сцене нужно назначить слой Ground, что позволит указать в скрипте метод Physics.Raycast для реализации проверок пересечений.
Далее перетащим спрайт курсора из ассетов в сцену (пример: Spags Assets⇨Textures⇨Demo⇨white_hip⇨white_hip_14):
Курсору добавлен поворот вокруг оси X на 90° (пусть лежит на земле), масштаб: 0.25, Y = 0.01. Значение Y задано с целью избежать эффект Z-fighting. Для определения близости объектов к камере, видеокартой производятся расчёты с плавающей точкой, и если задать значение равное 0 для курсора и для земли, то они начнут «спорить». В итоге курсор будет частично просвечиваться и мерцать при перемещении. Значение 0.01 оптимально, чтобы сократить оплошности в расчетах видеокарты, при этом визуально не даёт ощущение, что курсор парит над землёй.
Далее назовём объект Cursor, добавим скрипт с аналогичным именем и следующим кодом:
Редактор Unity для изображения курсора применяет компонент SpriteRenderer, поскольку это двумерный рисунок (спрайт). Чтобы управлять включением компонента, получаем ссылку на него в Start.
В этом же методе надо преобразовать ранее созданное имя слоя Ground в битовую маску, поскольку Unity в процессе поиска пересечений пользуется битовыми масками для фильтрации объектов, а метод LayerMask.GetMask возвращает битовую маску, соответствующую указанному слою.
Получив доступ к главной камере уровня через Camera.main в методе Update, даём команду преобразовать координаты мыши (которые получены с помощью Input.mousePosition) в 3D-луч. Этот трёхмерный объект надо передать в метод Physics.Raycast и проверить пересечения с объектами в сцене. Unity просит обозначить максимальное расстояние, значение 1000 подойдёт.
Если луч и объект не пересеклись, то отключаем курсор с помощью SpriteRenderer. В противоположной ситуации перемещаем курсор в место пересечения. От точки пересечения перенимаем значения координат X и Z, а значение координаты Y не стоит менять, чтобы не получить эффект Z-fighting.
Далее к объекту Cursor добавим компонент Cursor.
Самое время дополнить скрипт Player. Сперва нужно добавить поле Cursor cursor. После допишем несколько строк в Start:
В метод Update нужно добавить код, чтобы персонаж игрока поворачивал в нужную сторону вслед за курсором:
Координата Y по-прежнему 0.01.
Стрельба по зомби как призвание
Нужно создать новый объект Shot и добавить к нему LineRenderer. В поле Width обозначается ширина — 0.04 будет достаточно. На изображении видно, что объект имеет ярко-фиолетовый цвет — так в Unity обозначаются объекты без материала.
Материал играет важную роль в каждом трёхмерном движке, с его помощью полностью описывается наружность объекта, в том числе освещённость, текстура, шейдер.
В папку Materials положим материал под названием Yellow. Для него выберем стандартный шейдер Unlit/Color. Он не учитывает освещённость, поэтому пулю будет хорошо видно.
Применяем жёлтый цвет к объекту:
Добавим скрипт Shot:
Этот скрипт добавляем к объекту Shot.
Чтобы отображать 1 выстрел на кадр с минимумом кода, была применена хитрость. Во-первых, применён не Update, а FixedUpdate, которому присуща определённая периодичность вызова (по умолчанию её значение равно 60 кадров в секунду). Во-вторых, введена переменная visible с значением true во время выведения выстрела на экран. В следующем цикле FixedUpdate значение переменной меняется на false, и соответственно объект выстрела перестаёт отображаться. Эта логическая переменная работает как счётчик от 1 до 0.
Используем метод gameObject.SetActive, чтобы выключить объект. Он включает или выключает игровой объект, на котором расположен интересующий нас компонент. Отключенные объекты не выводятся на экран, а для их компонентов не вызываются методы. Благодаря этому методу действия персонажа выглядят логично — выстрел не видно, когда герой не стреляет.
Теперь скрипт Player дополним следующими полями:
Подобно moveSpeed, публичное поле gunBarrel доступно также в Инспекторе. Пора назначить полю ранее созданный игровой объект:
Теперь при запуске игры персонаж может отстреливаться от зомби.
Но выстрелы не уничтожают зомби, а пролетают сквозь него. Это предсказуемо, поскольку в коде выстрела не отслеживается попадание в цель.
Исправить это довольно просто — в классе Player между строками var to = … и shot.Show(…) допишем строки:
Чтобы луч вылетал из дула пистолета, и можно было определить факт его пересечение с другими объектами в сцене, используется Physics.Raycast.
Однако, создатели ассета добавили объектам сцены коллайдер, кроме персонажей, поэтому пуля по-прежнему будет пролетать через зомби. Исправим это упущение.
Коллайдер является компонентом для определения столкновений объектов в сцене, обычно имеет простую геометрическую форму (куб, сфера, например). Точность столкновений при этом невысока, зато формулы пересечений простые и не требуют сложных вычислений. В случаях, когда точность важнее производительности, можно использовать MeshCollider. Нам подойдёт пока стандартный компонент CapsuleCollider:
Пуля больше не пролетает сквозь зомби, но всё ещё не убивает его.
Зомби — сдохни или умри!
Дополним скрипт Zombie следующими полями:
В Start класса Zombie внесём:
Добавим проверку в начале метода Update:
В классе Zombie укажем метод Kill:
Необходимость создания новых полей очевидна. В методе Kill ставится флаг гибели зомби, удаляются компоненты CapsuleCollider, MovementAnimator и NavMeshAgent из игрового объекта и активируется проигрывание анимации смерти в контроллере.
Компоненты нужно удалить, чтобы зомби после поражения не перемещался и перестал быть препятствием для пуль. В идеале, после воспроизведения анимации смерти зомби было бы хорошо как-то изящно избавляться от тела, чтобы поддерживать производительность на достаточно хорошем уровне. Простейший способ — добавить вызов Destroy(gameObject, 3), и в течение 3-х секунд Unity удалит этот игровой объект.
В качестве финального аккорда в класс Player, метод Update, в месте вызова Physics.Raycast, к описанию случая обнаружения пересечения, необходимо добавить проверку:
Вызов Physics.Raycast передаёт данные о пресечении в переменную hit. А именно, появится ссылка на компонент Transform объекта, с которым пересекся луч, в поле transform. Зомби будет убит, если в объекте обнаружен компонент Zombie.
Кликаем правой кнопкой мыши по объекту Zombie, чтобы добавить систему частиц (Effects⇨Particle System):