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

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

Оглавление

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

Добавляем текстуры и настраиваем материалы

В предыдущем уроке мы уже научились искать и скачивать бесплатные ассеты с сайта https://assetstore.unity.com/ и теперь нам не помешают какие-нибудь текстуры для окружающего мира. Мне приглянулись вот такие мультяшные текстурки и я добавил их в проект.

В этом наборе целая куча текстур!
В этом наборе целая куча текстур!

А еще вот эти на всякий случай:

158 мегабайт! Это очень много, но не волнуйтесь будем сжимать и удалять лишнее.
158 мегабайт! Это очень много, но не волнуйтесь будем сжимать и удалять лишнее.

Находим в проекте новую папку, находим в ней папку Textures и выбираем там там например вот такую лесную поверхность. В папке одной текстуры лежат сразу несколько текстур разных типов и один материал, который отображается в виде кружочка. Он то нам и нужен. Материал содержит в себе все эти текстуры, смешивает их между собой и имеет кучу настроек.

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

Теперь перетащим этот материал на нашу плоскость, которую кстати надо бы переименовать, например, в Ground.

Какая огромная трава!
Какая огромная трава!

Теперь давайте настроим материал. Кликаем на наш Ground, находим среди его компонентов наш материал в самом низу и первым делом подберем значения масштаба для Tiling. Остальные настройки по вкусу.

Масштаб текстуры зависит от масштаба объекта, но его всегда можно подкорректировать с помощью настройки Tiling
Масштаб текстуры зависит от масштаба объекта, но его всегда можно подкорректировать с помощью настройки Tiling

Можно сделать поменьше Smoothness, чтобы не так сильно отсвечивало. Можно смешать текстуру с каким-нибудь дополнительным цветом для придания нужного оттенка. Можно уменьшить глубину эффекта объемности с помощью настройки Normal Map.

Вот так уже получше
Вот так уже получше

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

Ну теперь, в красных труселях наш Михалыч просто неотразим! :)
Ну теперь, в красных труселях наш Михалыч просто неотразим! :)

Но что-то теперь эта поверхность кажется мрачноватой. По крайней мере для начала. Поищем в другом наборе что-нибудь больше похожее на летнюю траву.

По всем законам классических игр у нас должна быть локация с зелёной травой! :)
По всем законам классических игр у нас должна быть локация с зелёной травой! :)

Вот это мне нравится! Летом и выживается как-то приятнее!

Заполняем мир объектами и пробуем левел-дизайн

Теперь поищем какие-нибудь низкополигональные деревья, которые не съедят всю нашу оперативку. :) Вот эти вроде ничего!

Стараемся не перегрузить игру детализированными объектами, чтобы проще было оптимизировать под телефоны, поэтому для начала выбираем что попроще
Стараемся не перегрузить игру детализированными объектами, чтобы проще было оптимизировать под телефоны, поэтому для начала выбираем что попроще

Добавим их в своей проект. Найдем новую папку, а там найдем готовые префабы с этими деревьями.

Как правило во всех добавленных в проект пакетах (package) присутствует папка с готовыми префабами.
Как правило во всех добавленных в проект пакетах (package) присутствует папка с готовыми префабами.

Ну, а теперь немного творчества и левел-дизайна. Расставим понравившиеся префабы на нашей небольшой полянке на свой вкус. Чтобы было где развернуться, увеличим масштаб (Scale) полянки раза в 3. Цвета материалов будем также подстраивать под свой вкус. Масштабы (Scale) деревьев будем подгонять относительно нашего персонажа, а то они по умолчанию бывают слишком огромные. Выделенные объекты можно быстро дублировать с помощью сочетания клавиш CTRL+D.

Теперь это уже похоже на игру!
Теперь это уже похоже на игру!

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

Дело сделано! Теперь на нашей сцене красуется прекрасный сказочный лес, а в окне иерархии огромная куча объектов. Предлагаю навести порядок! Создадим пустой объект Empty Object и назовем его Forest. Если в иерархии уже нет места куда кликнуть, то можно сверху в меню выбрать GameObject - Create Empty. Перетащим все префабы деревьев и камней, выделив их с нажатой клавишей Shift, на этот объект Forest. Теперь все эти объекты стали вложены в объект Forest и стали для него дочерними.

Помести кучу однотипных объектов в какой-то один мы можем свернуть их список и избежать захламления окна иерархии. Одни объекты могут служить другим в качестве папок. Добавьте описание
Помести кучу однотипных объектов в какой-то один мы можем свернуть их список и избежать захламления окна иерархии. Одни объекты могут служить другим в качестве папок. Добавьте описание

Мы можем легко управлять дочерними (вложенными в Forest) объектами с помощью родительского (Forest). Например, перемещая родительский объект мы будем одновременно перемещать все дочерние. Тоже самое с вращением и масштабированием. Мы можем, выключив родительский объект в инспекторе, с помощью галочки напротив названия, выключить и все что в нём. Выключить это значит убрать из игры, но не удалить. Т.е. все просто исчезнет до тех пор, пока не будет снова включено.

Выключая родительский объект дочерние тоже выключаются
Выключая родительский объект дочерние тоже выключаются

Порядок неплохо бы навести и в окне проекта. А то скоро папки со скачанными моделями и текстурами все заполонят! Поэтому давайте создадим папки Models и Textures, чтобы раскидать все по папкам. Скриптов пока не много, оставим их в корне для наглядности, но позже и для них потребуется отдельная папка.

Вот так выглядит уже поаккуратнее и стало проще ориентироваться в проекте.
Вот так выглядит уже поаккуратнее и стало проще ориентироваться в проекте.

Красоту навели, давайте теперь вспомним про C# и займёмся программированием.

Поворот персонажа в сторону движения

Было бы неплохо чтоб наш отважный герой все таки смотрел куда шел, а то так и об ёлку какую-нибудь убиться можно. А для этого нам понадобиться знать направление движения. Его гораздо проще будет вычислить, если немного по другому начнем считывать нажатые клавиши и вместо GetKey() будем использовать GetAxis(). По умолчанию в Unity уже прописаны клавиши управления на WSAD и мы можем считать их нажатие с помощью аргументов "Horizontal" (лево-право) и "Vertical" (вперёд-назад).

Добавим поле типа Vector3, которое будет называться direction и отвечать за направление движения персонажа. В него каждый кадр будем записывать новый вектор собранный из значений осей движения.
Добавим поле типа Vector3, которое будет называться direction и отвечать за направление движения персонажа. В него каждый кадр будем записывать новый вектор собранный из значений осей движения.

Если Input.GetAxis("Vertical") равно 1, то это равносильно нажатой клавише W, а если -1, то клавише S. Если 0, то ни одна из них не нажата, либо нажаты обе. С горизонтальной осью тоже самое.

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

Теперь мы можем использовать этот вектор направления движения direction как для движения, так и для поворота. Вынесем движение и поворот в отдельный метод Move(). Передвигать персонажа будем также через controller.Move(), в котором в качестве вектора будет полученный direction.

А вот для поворота нам придётся обратится из скрипта Player к игровому объекту Player. Поскольку первый висит на втором, то сделать это можно напрямую с помощью обращения gameObject. За вращение отвечает компонент Transform и его поле Rotation. А чтобы развернуть объект по направлению вектора direction нам потребуется специально обученная функция Quaternion.LookRotation().

gameObject.transform.rotation = Quaternion.LookRotation(direction);

В итоге наш скрипт Player преобразуется вот так:

Изменение кода для оптимизации его работы называется рефакторинг и мы с вами сейчас его проделали
Изменение кода для оптимизации его работы называется рефакторинг и мы с вами сейчас его проделали

Нам понадобится проверка, что направление движения не нулевое, иначе персонажа будет всегда поворачивать вперёд, как только мы отпустим клавиши движения. Как и во многих других языках, в языке C# знак "не равно" - это !=.

А вектор нельзя сравнить с 0, его нужно сравнивать с нулевым вектором Vector3.Zero.

В качестве бонуса мы получили еще и плавное изменение направления движения, что круто. И плавную остановку персонажа, что спорно, но может и подойдёт.

Теперь наш персонаж двигается в любую сторону, в том числе по диагонали и смотрит в сторону движения.
Теперь наш персонаж двигается в любую сторону, в том числе по диагонали и смотрит в сторону движения.

Объём статей и уроков

Пока пишу эту статью, решил замерить объём статей этой серии в символах и я немного в замешательстве. Первая статья 13 тысяч символов, вторая 15 тысяч, а третья к моменту замера была уже 18 тысяч! :) Я вроде стараюсь не лить воду и рассказываю только о самом необходимом, а объёмы получаются внушительные.

Поэтому я решил, что давайте я буду в каждой статье затрагивать меньше тем при той же подробности объяснения. Статьи (уроки) будут короче и чаще выходить. В общем, я перенёс часть уже написанного материала на следующую статью. В ней мы поговорим об анимации персонажа и заставим главного героя немного повыживать. :)

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

Первая статья по этой теме:

Вторая статья:

Обложка для статьи
Обложка для статьи