Найти в Дзене
Сергей Эланд

Делаем свою игру на Unity и изучаем язык программирования C# (Часть 2)

Оглавление

В этом уроке займёмся нашим главным героем - сделаем ему визуальное воплощение, научим его передвигаться и сделаем поверхность, по которой он сможет ходить. Если вам интересна эта тема, то не забудьте подписаться на канал и давайте уже творить!

Модель для нашего главного героя

Чем особенно хорош Unity для начинающего разработчика игр, так это своим магазином "ассетов". Что такое asset? Самый подходящий для нас перевод этого слова - это "ресурс", т.е. то из чего будет строится наша игра. Поэтому поищем модель для нашего главного героя там, благо бесплатных моделей там предостаточно.

Заходим на сайт https://assetstore.unity.com/ и выбираем там критерии поиска 3D, ведь мы решили сделать 3D-проект. Выбираем поиск по персонажам Character и находим заветную галочку "Free", чтобы сайт показал нам всех бесплатных персонажей. Никакой особой идеи у нас нет, кроме того, что это казуальная выживалка, а значит, вот такой низкополигональный бородатый джентельмен вполне подойдёт)

Вполне классический для игр в жанре выживания "бородатый мужик"
Вполне классический для игр в жанре выживания "бородатый мужик"

Поскольку мы хотим сделать игру и для пк, и для телефона, то нам лучше не использовать крутые детально проработанные модели, чтобы не раздувать вес игры и чтобы проще было ее оптимизировать под слабые телефоны. Низкополигональные модели вполне подойдут.

Кликнув по иконке, мы видим, что в бесплатной версии у нас даже есть комплекты моделек одежды для этого персонажа и набор анимаций. Замечательно! Берём! Добавляем его к себе в ассеты и во всплывающем окошке выбираем "Открыть в Unity".

Когда мы добавим ассет себе, то он будет доступен в списке ассетов в Package Manager в Unity
Когда мы добавим ассет себе, то он будет доступен в списке ассетов в Package Manager в Unity

Если наш проект открыт, то мы сразу попадём в Package Manager, который откроет нам окошко с этой моделькой, где будет кнопка, чтобы её скачать. Если по каким-то причинам этого не произойдёт, то мы сами легко можем в Unity открыть этот Package Manager во вкладке Window. Главное потом выбрать в поле Packages именно наши ассеты (My Assets):

Помимо наших ассетов в проекте уже есть встроенные по-умолчанию (ассеты это необязательно модели, это могут быть и готовые скрипты) и они в отдельных категориях.
Помимо наших ассетов в проекте уже есть встроенные по-умолчанию (ассеты это необязательно модели, это могут быть и готовые скрипты) и они в отдельных категориях.

Жмём Download, чтобы скачать, после чего рядом появится кнопка Import. С ней поступаем аналогичным образом и импортируем всё, что нам сей ассет предлагает. Если там окажется что-то, что нам не нужно, то мы сможем потом легко это удалить из проекта.

Теперь в окошке файлов нашего проекта, которое внизу, появилась новая папка. Находим в ней префаб персонажа. А что такое префаб? Это слово можно перевести как "заготовка" и по смыслу так и есть: это игровой объект с заготовленным набором настроек (это могут быть размеры, добавленные компоненты или скрипты и т.д.). Ну и собственно перетаскиваем этот префаб прямо в основное окно редактора (окно сцены):

Мы можем перетаскивать модели и префабы из окна проекта в окно иерархии, либо в окно сцены и тем самым, добавлять эти объекты на нашу сцену.
Мы можем перетаскивать модели и префабы из окна проекта в окно иерархии, либо в окно сцены и тем самым, добавлять эти объекты на нашу сцену.

Ура! Наш "лунтик" родился и теперь мы его даже видим - вон болтается в пустоте в позе буквы Т. Но если мы нажмём кнопку Play, то мы увидим его в совсем другом месте, с опущенными руками, да еще и немного двигающегося.

В режиме Play редактор Unity автоматически переключается с окна Scene на окно Game
В режиме Play редактор Unity автоматически переключается с окна Scene на окно Game

Что за магия вне Хогвартса?! Эта магия вполне объяснима. Во-первых изначально в проекте уже присутствует объект MainCamera и это то, через что мы будем видеть игру. Именно через "объектив" этой камеры мы смотрим в режиме Play. А вот в окошке сцены уже как бы своя, "рабочая" камера, которая в игре никак не участвует, но позволяет нам видеть мир в режиме редактирования.

Ну ладно с видом теперь понятно, но почему этот джентельмен внезапно ожил и задвигался? А все потому что в этом префабе уже присутствует настроенный компонент Animator, который и отвечает за анимации персонажа. Мы видим справа в инспекторе этот компонент с его настройками. И первое его поле Controller содержит название файла этого контроллера. Если по нему кликнуть при открытом снизу окне проекта с файлами, то мы сразу перейдем к этому файлу в какой бы папке он не лежал. Теперь если кликнуть двойным кликом по этому файлу, то у нас откроется окно Аниматора и мы увидим, что у персонажа уже присутствует одна анимация Idle (состояние покоя).

По умолчанию в аниматоре присутствуют только блоки входа, перехода и выхода. Все блоки анимаций мы добавляем самостоятельно и соединяем их стрелками в нужной нам последовательности.
По умолчанию в аниматоре присутствуют только блоки входа, перехода и выхода. Все блоки анимаций мы добавляем самостоятельно и соединяем их стрелками в нужной нам последовательности.

Пока что, наш "лунтик" умеет только находиться в состоянии покоя. Всем бы так уметь! Где эта волшебная кнопка, чтобы перейти в это состояние?) А в окне аниматора мы видим цветные прямоугольники, соединяемые стрелками. Entry - это начало, точка входа. Как только персонаж появится на сцене в режиме игры, то из этой точки он автоматически попадает в состояние своей первой анимации Idle. Все остальное мы уже будем делать сами из кода.

Управление и передвижение нашего персонажа

Давайте переименуем префаб персонажа, например в Player, и с помощью кнопки под аниматором, добавим ему стандартный компонент Character Controller, легко его найдя там через поле поиска:

Компонент это по сути готовая программа с набором функций, которая наделяет наш игровой объект определенным функционалом
Компонент это по сути готовая программа с набором функций, которая наделяет наш игровой объект определенным функционалом

Character Controller нужен нам, чтобы не изобретая велосипед, сделать управление персонажем и научить его уже ходить. Компонент стандартный и для нас, как для новичков, вполне подойдёт. Вообще, в создании игр, использование готовых решений - это нормальная практика, т.к. это позволяет ускорить процесс разработки игры, который, порой, может растягиваться на годы, если делать все с нуля самостоятельно.

Раз уж в нашей игре появился новый игровой объект в лице персонажа, то давайте не будем запихивать весь наш код в один скрипт, чтобы не получить "простыню", в которой сложно "что либо найти, легко потерять и невозможно забыть". Никто не любит бесконечные "простыни" текста на Дзене, если они, конечно, не снабжены картинками. Но в коде картинки не предусмотрены, поэтому для удобства лучше разделять код по разным скриптам и классам в зависимости от того, к чему эти поля и методы относятся.

Создадим новый скрипт, кликнув в окне проекта с файлами и перетащим его на объект Player. Либо, как вариант, добавим на этот объект в качестве нового компонента новый скрипт (New Script).

Выбираем New Script, нам предложат его как то назвать и он сразу появится на этом объекте и в корневой папке проекта (но только надо будет распаковать префаб - об этом ниже)
Выбираем New Script, нам предложат его как то назвать и он сразу появится на этом объекте и в корневой папке проекта (но только надо будет распаковать префаб - об этом ниже)

Назовём этот новый скрипт Player. Будем писать в нём, только то, что относится непосредственно к игроку. Предлагаю пока оставить всё что касается параметров персонажа в скрипте Game, а в Player напишем методы управления игроком. Получается вполне логично: игрок может управлять персонажем, но следить за параметрами и принимать решение о проигрыше будет сама игра.

Но чтобы перетащить скрипт получилось, придётся сначала "распаковать" префаб кликнув на объекте Player и выбрав Unpack Completely (почему так - разберемся позже).

Кликаем по объекту с голубой иконкой в иерархии и в меню выбираем Prefab - Unpack Completely
Кликаем по объекту с голубой иконкой в иерархии и в меню выбираем Prefab - Unpack Completely

Итак, вот он наш новый скрипт:

Скрипт Player висит на объекте Player - удобно, не запутаемся)
Скрипт Player висит на объекте Player - удобно, не запутаемся)

Предлагаю сразу же познакомить два наших скрипта между собой, чтобы они могли хотя бы узнавать о состоянии друг друга (например, достаточно ли персонаж жив, чтобы передвигаться). Для этого в скрипте Player (удалив ненужные нам комментарии) добавим публичное поле, чтобы иметь доступ к нему из инспектора (пока не будем заморачиваться с "правильной" приватностью всего и вся). Заодно сделаем ссылку на компонент CharacterController.

Сами по себе созданные нами поля game и controller пустые. Присвоить им нужный элемент можно из кода, либо явным образом в инспекторе Unity.
Сами по себе созданные нами поля game и controller пустые. Присвоить им нужный элемент можно из кода, либо явным образом в инспекторе Unity.

Не забывая после всех изменений сохранять скрипт, добавляем публичное поле типа Game. А что это за тип такой? А это тот скрипт, который мы с вами создали и который следит за здоровьем персонажа. Прелесть в том, что мы можем теперь сколько угодно его вешать, на какие угодно объекты и создавать его независимо работающие копии (экземпляры).

Но нам хватит одного и ссылку на него мы, как раз, получим, перетащив в окошко значения сам объект на котором висит скрипт Game. А CharacterController можно перетащить в соответствующее окно, просто потянув за название компонента, которое идёт после галочки ниже.

Ага, в поле Game помещается экземпляр класса Game, который висит на объекте Game - точно не запутаемся?) Это совершенно разные сущности и случайно заменить одно другим не получится.
Ага, в поле Game помещается экземпляр класса Game, который висит на объекте Game - точно не запутаемся?) Это совершенно разные сущности и случайно заменить одно другим не получится.

А теперь давайте наконец-то научим нашего героя передвигаться. Для этого воспользуемся простейшей системой ввода и доставшимся нам в комплекте со всем остальным классом Input (ввод), у которого есть метод GetKey() проверяющий нажата ли та или иная клавиша на клавиатуре. В качестве аргумента в этот метод вписываем одно из значений перечисления KeyCode, которые Visual Studio нам дружелюбно подсказывает, стоит только начать набирать внутри скобок название этого перечисления.

При наших попытках что-то написать VisualStudio будет показывать подсказки с доступными нам вариантами, будь то названия классов, перечислений, полей или методов. Если этого не происходит, то убедитесь, что VisualStudio выбрана в настройках Unity (Edit -> Preferences -> External Tools) в качестве внешнего редактора скриптов (External Script Editor).
При наших попытках что-то написать VisualStudio будет показывать подсказки с доступными нам вариантами, будь то названия классов, перечислений, полей или методов. Если этого не происходит, то убедитесь, что VisualStudio выбрана в настройках Unity (Edit -> Preferences -> External Tools) в качестве внешнего редактора скриптов (External Script Editor).

В методе Update() каждый кадр будем проверять нажата ли выбранная нами клавиша и если да, то в метод Move() контроллера, который мы повесили на персонажа (CharacterController), впишем вектор желаемого направления.

Что за вектор? В нашем 3D-проекте присутствует 3 измерения, которые легко представить в виде осей X, Y и Z. Как видно на примере нашего персонажа движение вперёд-назад будет осуществляться по оси Z (синяя стрелка), вверх-вниз по оси Y (зелёная стрелка), а влево вправо по оси X (красная стрелка).

Получается если мы хотим научить нашего героя ходить, то он должен двигаться по осям X и Z. А вот прыгать и падать придется по оси Y.
Получается если мы хотим научить нашего героя ходить, то он должен двигаться по осям X и Z. А вот прыгать и падать придется по оси Y.

Вектор в Unity задаётся тремя координатами, например Vector3(1, 1, 1). Но те кто помнит геометрию знаю, что тремя координатами можно, разве что, описать точку в пространстве, но уж никак не вектор. В Unity же имеется ввиду луч направленный из начала координат в указанную нами точку, а это уже вектор. Например, вектор представленный синей стрелкой в записи будет выглядеть как Vector3(0, 0, 1).

Есть глобальные координаты направления в которых неизменны (на юг это всегда на юг) и локальные для каждого объекта, направления которых меняются в зависимости от того, куда объект повернут (повернись и иди вперёд). Для удобства давайте решим, что глобальная ось Z (синяя стрелка) указывает на север, а ось X (красная стрелка) указывает на восток. Теперь с нашей легкой руки в нашей игре появились стороны света!

Итак, проверяем нажата ли кнопка и передвигаем нашего героя в нужном направлении. Для удобства в Unity есть готовые векторы глобальных направлений (например, Vector3.forward будет направление по оси Z и на север) - по ним и будем двигать.

Как и в случае с голодом сделаем зависимость от времени, а не от кадров, умножив на Time.deltaTime, чтобы персонаж двигался метр в секунду, а не метр в один кадр.
Как и в случае с голодом сделаем зависимость от времени, а не от кадров, умножив на Time.deltaTime, чтобы персонаж двигался метр в секунду, а не метр в один кадр.

Давайте проверять. Возвращаемся в Unity, жмём Play, нажимаем W. Ура! Наш персонаж поплыл в пустоте куда-то вперёд, куда глаза глядят. Но очень лениво и не торопясь, надо бы как то его ускорить.

В Unity единицы координат для удобства приравниваются к единицам метров и получается, что наш персонаж перемещается на метр в секунду. Это довольно медленно, поэтому давайте создадим поле speed, в котором через инспектор сможем менять скорость движения персонажа. Ну и умножим эту переменную speed на вектор движения. А заодно добавим ему возможность двигаться вправо и влево.

Есть более изящные способы сделать движение персонажа, но для начала и для понимания подойдёт.
Есть более изящные способы сделать движение персонажа, но для начала и для понимания подойдёт.

Вот это другое дело. Два метра в секунду уже лучше. К тому же теперь можно играться, меняя скорость в инспекторе, сколько угодно. Наш выживальщик, где то с краю экрана, парит в пространстве во всех направлениях не зная преград.

Расстановка и настройка игровых объектов

Давайте поставим нашего героя в центр кадра, обнулив координаты его позиции.

Position - это положение в пространстве, Rotation отвечает за поворот объекта, а Scale за его масштаб
Position - это положение в пространстве, Rotation отвечает за поворот объекта, а Scale за его масштаб

Поскольку мы решили делать вид сверху, то давайте поднимем камеру повыше и зададим ей угол наклона где-то 45 градусов.

У каждого игрового объекта в Unity есть компонент Transform и у камеры в том числе, так что мы можем перемещать и поворачивать её как захотим
У каждого игрового объекта в Unity есть компонент Transform и у камеры в том числе, так что мы можем перемещать и поворачивать её как захотим

Теперь другое дело, но неплохо бы добавить, какую то поверхность по которой будем ходить. Правой клавишей кликнем в окне иерархии и выберем 3D Object - Plane. На нашей сцене появилась плоскость. Поиграем с её настройками, обнулив координаты и увеличив масштаб.

-19

В режиме Play мы видим даже тень главного героя и судя по ней, сей почтенный джентельмен во время движения решил позволить себе левитацию вне Хогвартса! Предлагаю его немного приземлить. Это произошло потому-что в компоненте CharacterController его коллайдер отвечающий за столкновения оказался смещён относительно модели персонажа.

Бывает, что дух выходит из тела, но здесь кажется наоборот)
Бывает, что дух выходит из тела, но здесь кажется наоборот)

Давайте это исправим. Выбрав игрока, найдем на нём компонент CharacterController и в поле Center - Y зададим значение 1 - это будет примерно центр персонажа по высоте. Точнее настроим потом.

Координаты и размеры коллайдера нужно подобрать соответственно используемой модели
Координаты и размеры коллайдера нужно подобрать соответственно используемой модели

Теперь в режиме Play персонаж больше не ливитирует над поверхностью. Но что-то его эти скольжения боком и задом, без передвижения ног, выглядят не особо реалистично, да и ходить босиком по снегу дело небезопасное - было бы неплохо для начала дать ему погулять по какой-нибудь траве или песку. Этим, и не только, займёмся в следующем нашем уроке! В этом возможно мы сделали и не так много, зато сколько полезной информации узнали без которой дальше двигаться было бы просто невозможно! Так что все самое интересное еще впереди!

А пока если тебе интересна эта тема, то обязательно подпишись на канал, чтобы не пропустить продолжение. Если тебе нравится как я подаю материал, то поставь лайк - мне будет приятно, ведь я стараюсь) Ну, а если остались какие-нибудь вопросы или есть какие-нибудь предложения, то смело пиши в комментариях - обязательно отвечу!

Начало разработки нашей игры в первой статье, где мы на практике разбираем основы программирования:

Создаём свою игру на Unity и изучаем язык программирования C#
Создаём свою игру на Unity и изучаем язык программирования C#