В третьем уроке давайте немного поработаем над оформлением игрового мира, займёмся левел-дизайном, проработаем механику движения и улучшим наш код. Если вам нравится эта тема, то не забудьте подписаться на канал и почитать предыдущие статьи из этой серии, чтобы лучше понимать, что мы делаем. Начнём!
Добавляем текстуры и настраиваем материалы
В предыдущем уроке мы уже научились искать и скачивать бесплатные ассеты с сайта https://assetstore.unity.com/ и теперь нам не помешают какие-нибудь текстуры для окружающего мира. Мне приглянулись вот такие мультяшные текстурки и я добавил их в проект.
А еще вот эти на всякий случай:
Находим в проекте новую папку, находим в ней папку Textures и выбираем там там например вот такую лесную поверхность. В папке одной текстуры лежат сразу несколько текстур разных типов и один материал, который отображается в виде кружочка. Он то нам и нужен. Материал содержит в себе все эти текстуры, смешивает их между собой и имеет кучу настроек.
Теперь перетащим этот материал на нашу плоскость, которую кстати надо бы переименовать, например, в Ground.
Теперь давайте настроим материал. Кликаем на наш Ground, находим среди его компонентов наш материал в самом низу и первым делом подберем значения масштаба для Tiling. Остальные настройки по вкусу.
Можно сделать поменьше Smoothness, чтобы не так сильно отсвечивало. Можно смешать текстуру с каким-нибудь дополнительным цветом для придания нужного оттенка. Можно уменьшить глубину эффекта объемности с помощью настройки Normal Map.
На нашем персонаже тоже есть материалы и их цвета легко можно изменить. Найдем в иерархии Player раскроем его вложения и увидим, что там по отдельности есть и части тела, и волосы, и борода, и труселя, и даже пока отключенная одежда. На каких-то элементах может быть один материал, например, на частях тела и изменяя его, мы изменим цвет всех частей тела сразу. А где то отдельный например на незаметных труселях персонажа. Давайте поиграемся с цветами материалов.
Но что-то теперь эта поверхность кажется мрачноватой. По крайней мере для начала. Поищем в другом наборе что-нибудь больше похожее на летнюю траву.
Вот это мне нравится! Летом и выживается как-то приятнее!
Заполняем мир объектами и пробуем левел-дизайн
Теперь поищем какие-нибудь низкополигональные деревья, которые не съедят всю нашу оперативку. :) Вот эти вроде ничего!
Добавим их в своей проект. Найдем новую папку, а там найдем готовые префабы с этими деревьями.
Ну, а теперь немного творчества и левел-дизайна. Расставим понравившиеся префабы на нашей небольшой полянке на свой вкус. Чтобы было где развернуться, увеличим масштаб (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" (вперёд-назад).
Если 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 тысяч! :) Я вроде стараюсь не лить воду и рассказываю только о самом необходимом, а объёмы получаются внушительные.
Поэтому я решил, что давайте я буду в каждой статье затрагивать меньше тем при той же подробности объяснения. Статьи (уроки) будут короче и чаще выходить. В общем, я перенёс часть уже написанного материала на следующую статью. В ней мы поговорим об анимации персонажа и заставим главного героя немного повыживать. :)
Если тебе интересна тема создания игр, то подпишись на канал, чтобы не пропустить продолжение. Если статья была интересна или полезна, то можно поставить лайк, а если есть вопросы и предложения, то напишите в комментарии.
Первая статья по этой теме:
Вторая статья: