Всем привет! Скоро начнутся занятия в первой группе курса» Разработчик игр на Unity "В ожидании начала курса произошло открытый урок о создании зомби-шутера в Unity. Вебинар состоялся Николай Заполнов , старший разработчик игр из Rovio Entertainment Corporation. Он также написал подробную статью, которую мы предлагаем вашему вниманию.
В этой статье я хотел бы показать, как легко создавать игры в Unity. Если у вас есть базовые знания программирования, вы сможете быстро начать работать с этим движком и сделать свою первую игру.
Отказ от ответственности №1: Эта статья предназначена для новичков.
Если вы ели собаку в Unity, вам это может показаться скучным.
Отказ от ответственности №2: Чтобы прочитать эту статью, вам потребуются хотя бы базовые знания программирования.
Как минимум, слова «класс» и «метод» не должны вас пугать.
Будьте осторожны, под катом пробка!
Введение в Unity
Если вы уже знакомы с редактором Unity, можете пропустить введение и сразу перейти к разделу «Создание игрового мира».Основной структурной единицей в Unity является «сцена».
Сцена обычно представляет собой один уровень игры, хотя в некоторых случаях в одной сцене может быть сразу несколько уровней или, наоборот, один большой уровень может быть разбит на несколько сцен, загружаемых динамически.
Сцены наполнены игровыми объектами, а они, в свою очередь, наполнены компонентами.
Именно компоненты реализуют различные игровые функции: рисование объектов, анимацию, физику и т. д. Данная модель позволяет собирать функционал из простых блоков, как в игрушке Лего.
Вы можете писать компоненты самостоятельно; для этого вы используете язык программирования C#.
Именно так написана игровая логика.
Ниже мы увидим, как это делается, а пока давайте взглянем на сам движок.
Когда вы запустите движок и создадите новый проект, перед вами появится окно, в котором вы сможете выбрать четыре основных элемента:
В левом верхнем углу скриншота находится окно Иерархии.
Здесь мы можем увидеть иерархию игровых объектов в открытой в данный момент сцене.
Unity создала для нас два игровых объекта: камеру («Основная камера»), через которую игрок будет видеть наш игровой мир, и источник света («Направленный свет»), который будет освещать нашу сцену.
Без него мы бы увидели только черный квадрат. В центре находится окно редактирования сцены («Сцена»).
Здесь мы видим наш уровень и можем редактировать его визуально — перемещать и вращать объекты мышкой и смотреть, что из этого получится.
Рядом вы можете увидеть вкладку «Игра», которая на данный момент неактивна; если переключиться на него, то можно увидеть, как игра выглядит с камеры.
А если мы запустим игру (с помощью кнопки со значком воспроизведения на панели инструментов), то Unity переключится на эту вкладку, где мы и будем играть в запущенную игру.
В верхней правой части находится окно Инспектора.
В этом окне Unity показывает параметры выбранного объекта и мы можем их редактировать.
В частности, мы видим, что выбранная камера имеет два компонента — «Трансформация», задающая положение камеры в игровом мире и, собственно, «Камера», реализующая функционал камеры.
Кстати, все игровые объекты в Unity в том или ином виде имеют компонент Transform. И, наконец, внизу есть вкладка «Проект», где мы можем увидеть все так называемые активы, которые есть в нашем проекте.
Ресурсы — это файлы данных, такие как текстуры, спрайты, 3D-модели, анимация, звуки и музыка, файлы конфигурации.
То есть любые данные, которые мы можем использовать для создания уровней или пользовательских интерфейсов.
Unity понимает большое количество стандартных форматов (например, png и jpg для картинок или fbx для 3D-моделей), поэтому проблем с загрузкой данных в проект не возникнет. А если вы, как и я, не умеете рисовать, то ассеты можно скачать из Unity Asset Store, где собрана огромная коллекция всевозможных ресурсов: как бесплатных, так и продаваемых за деньги.
Справа от вкладки «Проект» вы можете увидеть неактивную вкладку «Консоль».
Unity выводит в консоль предупреждения и сообщения об ошибках, поэтому не забывайте периодически туда проверять.
Особенно если что-то не работает, скорее всего в консоли будет подсказка о причине проблемы.
Также вы можете выводить сообщения из кода игры в консоль для отладки.
Создание игрового мира
Так как я программист и рисую хуже куриной лапы, для графики я взял несколько бесплатных ассетов из Unity Asset Store. Ссылки на них вы найдете в конце этой статьи.
Из этих ассетов я собрал простой уровень, с которым мы будем работать:
Никакого волшебства, я просто перетащил понравившиеся объекты из окна Проекта и с помощью мыши расположил их так, как мне нравится:
Кстати, Unity позволяет добавлять в сцену стандартные объекты, такие как куб, сфера или плоскость, в один клик.
Для этого просто щелкните правой кнопкой мыши в окне Иерархии и выберите, например, 3D-объект⇨Плоскость.
Итак, асфальт на моем уровне собран из набора таких плоскостей, на которые я «натянул» текстуру из набора ассетов.
Н.
Б.
Если вам интересно, почему я использовал несколько плоскостей, а не одну с большим значением масштаба, ответ довольно прост: одна плоскость с большим масштабом будет иметь сильно увеличенную текстуру, что будет выглядеть неестественно по отношению к другим объектам сцены.
(это можно исправить с помощью параметров материала, но мы стараемся сделать все как можно проще, не так ли?)
Зомби ищут путь
Итак, у нас есть игровой уровень, но в нем пока ничего не происходит. В нашей игре зомби будут преследовать и атаковать игрока, и для этого они должны уметь двигаться к игроку и избегать препятствий.Для реализации этого мы воспользуемся инструментом Navigation Mesh. На основе данных сцены этот инструмент вычисляет области, по которым вы можете перемещаться, и генерирует набор данных, которые можно использовать для поиска оптимального маршрута из любой точки уровня в любую другую во время игры.
Эти данные хранятся в активе и не могут быть изменены в будущем — этот процесс называется «запеканием».
Если вам нужны динамически меняющиеся препятствия, вы можете использовать компонент NavMeshObstacle, но для нашей игры это не обязательно.
Важный момент: чтобы Unity знала, какие объекты необходимо включить в расчет, нужно для каждого объекта нажать на стрелку вниз рядом с опцией «Статический» в Инспекторе (вы можете выбрать их все сразу в окно «Иерархия») и отметьте пункт «Навигация статическая»:
В целом, остальные пункты тоже полезны и помогают Unity оптимизировать рендеринг сцены.
На них мы сегодня останавливаться не будем, но когда вы закончите изучать основы работы двигателя, очень рекомендую разобраться с остальными параметрами.
Иногда одна галочка может существенно увеличить частоту кадров.
Теперь воспользуемся пунктом меню «Окно⇨AI⇨Навигация» и в открывшемся окне выберем вкладку «Выпечка».
Здесь Unity предложит нам установить такие параметры, как высота и радиус персонажа, максимальный угол наклона земли, по которому еще можно ходить, максимальную высоту ступенек и так далее.
Мы пока ничего менять не будем и просто нажмем кнопку «Выпечка».
Unity выполнит необходимые вычисления и покажет нам результат:
Здесь синим цветом отмечена зона, где можно погулять.
Как видите, Unity оставила вокруг препятствий небольшую рамку — ширина этой границы как раз зависит от радиуса персонажа.
Таким образом, если центр персонажа находится в синей зоне, то он не будет «падать» в препятствия.
Имея рассчитанную навигационную сетку, мы можем использовать компонент NavMeshAgent для поиска маршрута и управления перемещением игровых объектов по нашему уровню.
Создадим игровой объект «Зомби», добавим к нему 3d модель зомби из ассетов, а также компонент NavMeshAgent:
Если вы сейчас запустите игру, ничего не произойдет. Нам нужно указать компоненту NavMeshAgent, куда идти.
Для этого мы создадим наш первый компонент на C#.
В окне проекта выберите корневой каталог (называемый «Активы») и щелкните правой кнопкой мыши в списке файлов, чтобы создать каталог «Сценарии».
Мы будем хранить в нем все наши скрипты, чтобы проект был организован.
Теперь внутри «Скриптов» создадим скрипт «Зомби» и добавим его в игровой объект зомби:
Двойной щелчок по скрипту откроет его в редакторе.
Давайте посмотрим, что Unity создал для нас
Это стандартная заготовка компонента.using System.Collections; using System.Collections.Generic; using UnityEngine; public class Zombie : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { } }
Как мы видим, Unity включила для нас библиотеки System.Collections и System.Collections.Generic (сейчас они не нужны, но часто нужны в коде Unity-игр, поэтому они были включены в стандартный шаблон), а также библиотеку UnityEngine, содержащую все основные API движка.
Также Unity создала для нас класс Zombie (имя совпадает с именем файла; это важно: если они не совпадают, Unity не сможет сопоставить скрипт компоненту в сцене).
Класс унаследован от MonoBehaviour — это базовый класс для компонентов, создаваемых пользователем.
Внутри класса Unity создала для нас два метода: Start и Update. Движок сам будет вызывать эти методы: Start — сразу после загрузки сцены и Update — каждый кадр.
На самом деле таких функций, вызываемых движком, очень много, но большинство из них нам сегодня не понадобится.
Полный список, а также последовательность их вызова всегда можно найти в документации: https://docs.unity3d.com/Manual/ExecutionOrder.html Давайте заставим зомби передвигаться по карте! Во-первых, нам нужно подключить библиотеку UnityEngine.AI. Он содержит класс NavMeshAgent и другие классы, связанные с навигационной сеткой.
Для этого добавьте директиву using UnityEngine.AI в начало файла.
Далее нам нужно получить доступ к компоненту NavMeshAgent. Для этого мы можем использовать стандартный метод GetComponent. Он позволяет получить ссылку на любой компонент в том же игровом объекте, который содержит компонент, из которого мы вызываем этот метод (в нашем случае это игровой объект «Зомби»).
Создадим в классе поле NavMeshAgent navMeshAgent, в методе Start мы получим ссылку на NavMeshAgent и попросим его переместиться в точку (0, 0, 0).
В итоге у нас должен получиться такой скрипт: using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class Zombie : MonoBehaviour
{
NavMeshAgent navMeshAgent;
// Start is called before the first frame update
void Start()
{
navMeshAgent = GetComponent<NavMeshAgent>();
navMeshAgent.SetDestination(Vector3.zero);
}
// Update is called once per frame
void Update()
{
}
}
После запуска игры мы увидим, как зомби движется к центру карты:
Зомби преследует жертву
Удивительный.Но нашему зомби скучно и одиноко, давайте добавим в игру жертву игрока ради него.
По аналогии с зомби создадим игровой объект «Игрок» (на этот раз мы выберем 3D-модель полицейского), а также добавим к нему компонент NavMeshAgent и свежесозданный скрипт Player. Содержимое скрипта Player мы пока трогать не будем, но нам нужно будет внести изменения в скрипт Zombie. Также я рекомендую установить значение свойства Priority игрока в компоненте NavMeshAgent равным 10 (или любому другому значению меньше стандартного 50, то есть дать игроку более высокий приоритет).
В этом случае, если игрок и зомби встретятся на карте, зомби не сможет сдвинуть игрока, в то время как игрок сможет оттолкнуть зомби.
Чтобы преследовать игрока, зомби должны знать свою позицию.
И для этого нам нужно получить ссылку на него в нашем классе Zombie с помощью стандартного метода FindObjectOfType. Как только мы запомним ссылку, мы можем перейти к компоненту преобразования игрока и запросить у него значение позиции.
А чтобы зомби преследовал игрока всегда, а не только в начале игры, мы зададим цель для NavMeshAgent в методе Update. Вы получите этот скрипт: using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class Zombie : MonoBehaviour
{
NavMeshAgent navMeshAgent;
Player player;
// Start is called before the first frame update
void Start()
{
navMeshAgent = GetComponent<NavMeshAgent>();
player = FindObjectOfType<Player>();
}
// Update is called once per frame
void Update()
{
navMeshAgent.SetDestination(player.transform.position);
}
}
Запустим игру и убедимся, что зомби нашел свою жертву:
Побег
Наш игрок все еще стоит как статуя.Это явно не поможет ему выжить в столь агрессивном мире, поэтому вам необходимо научить его передвигаться по карте.
Для этого нам необходимо получать информацию о нажатых клавишах из Unity. Метод GetKey стандартного класса Input предоставляет именно такую информацию! Н.
Б.
В целом этот способ получения ввода не совсем каноничен.
Лучше использовать Input.GetAxis и привязку через Настройки проекта⇨Диспетчер ввода.
А еще лучше - Новая система ввода .
Но эта статья и так слишком длинная, поэтому давайте упростим ее.
Давайте откроем скрипт Player и изменим его следующим образом: using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class Player : MonoBehaviour
{
NavMeshAgent navMeshAgent;
public float moveSpeed;
// Start is called before the first frame update
void Start()
{
navMeshAgent = GetComponent<NavMeshAgent>();
}
// Update is called once per frame
void Update()
{
Vector3 dir = Vector3.zero;
if (Input.GetKey(KeyCode.LeftArrow))
dir.z = -1.0f;
if (Input.GetKey(KeyCode.RightArrow))
dir.z = 1.0f;
if (Input.GetKey(KeyCode.UpArrow))
dir.x = -1.0f;
if (Input.GetKey(KeyCode.DownArrow))
dir.x = 1.0f;
navMeshAgent.velocity = dir.normalized * moveSpeed;
}
}
Как и в случае с зомби, в методе Start мы получаем ссылку на компонент NavMeshAgent игрока и сохраняем ее в поле класса.
Но теперь мы добавили еще и поле moveSpeed. Поскольку это поле является общедоступным, его значение можно редактировать непосредственно в Инспекторе Unity! Если в вашей команде есть гейм-дизайнер, он будет очень рад, что ему не придется лезть в код, чтобы редактировать параметры игрока.
Давайте установим 10 в качестве скорости:
В методе Update мы будем использовать Input.GetKey, чтобы проверить, нажата ли какая-либо стрелка на клавиатуре, и сгенерировать вектор направления движения игрока.
Обратите внимание, что мы используем координаты X и Z. Это связано с тем, что в Unity ось Y указывает на небо, а земля расположена в плоскости XZ. После того, как мы сгенерировали вектор направления движения dir, нормализуем его (иначе, если игрок захочет двигаться по диагонали, вектор будет чуть длиннее единицы и такое движение будет быстрее, чем прямолинейное движение) и умножим на заданную скорость движения.
.
Мы передаем результат в navMeshAgent.velocity, и агент сделает остальную работу.
После запуска игры мы наконец-то можем попытаться сбежать от зомби в безопасное место:
Чтобы камера двигалась вместе с игроком, напишем еще один простой скрипт. Назовем его «PlayerCamera»: using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerCamera : MonoBehaviour
{
Player player;
Vector3 offset;
// Start is called before the first frame update
void Start()
{
player = FindObjectOfType<Player>();
offset = transform.position - player.transform.position;
}
// Update is called once per frame
void LateUpdate()
{
transform.position = player.transform.position + offset;
}
}
Смысл этого сценария должен быть в основном ясен.
Одна из особенностей заключается в том, что здесь вместо Update мы используем LateUpdate. Этот метод похож на Update, но всегда вызывается строго после обработки Update для всех скриптов в сцене.
В данном случае мы используем LateUpdate, потому что хотим, чтобы NavMeshAgent вычислил новую позицию игрока, прежде чем мы переместим камеру.
В противном случае может возникнуть неприятный эффект «дергания».
Если теперь прикрепить этот компонент к игровому объекту «Основная камера» и запустить игру, персонаж игрока всегда будет в центре внимания!
Минута анимации
Давайте на минутку отвлечемся от проблем выживания в зомби-апокалипсисе и задумаемся о вечном – об искусстве.Наши персонажи теперь выглядят как ожившие статуи, движимые неизвестной силой (возможно, магнитами под асфальтом).
Но мне бы хотелось, чтобы они выглядели как настоящие, живые (и не очень) люди – двигают руками и ногами.
В этом нам поможет компонент Animator и инструмент под названием Animator Controller. Animator Controller — это конечный автомат (state Machine), в котором мы задаем определенные состояния (персонаж стоит, персонаж идет, персонаж умирает и т. д.), прикрепляем к ним анимации и задаем правила перехода из одного состояния.
другому.
Unity автоматически переключится с одной анимации на другую, как только сработает соответствующее правило.
Давайте создадим контроллер аниматора для зомби.
Для этого создайте в проекте директорию Animations (запомните порядок в проекте), а в ней — правой кнопкой — Animator Controller. И назовем его «Зомби».
Двойной клик и перед нами появится редактор:
Состояний пока нет, но есть две точки входа («Вход» и «Любое состояние») и одна точка выхода («Выход»).
Перетащим из ассетов пару анимаций:
Как видите, как только мы перетащили первую анимацию, Unity автоматически связала ее с точкой входа Entry. Это так называемая анимация по умолчанию.
Он будет сыгран сразу после начала уровня.
Чтобы перейти в другое состояние (и воспроизвести другую анимацию), нам нужно создать правила перехода.
И для этого, прежде всего, нам нужно добавить параметр, который мы зададим из кода для управления анимацией.
В левом верхнем углу окна редактора расположены две кнопки: «Слои» и «Параметры».
По умолчанию выбрана вкладка «Слои», но нам нужно переключиться на «Параметры».
Теперь мы можем добавить новый параметр типа float, используя кнопку «+».
Назовем это «скоростью»:
Теперь нам нужно указать Unity воспроизводить анимацию «Z_run», когда скорость больше 0, и «Z_idle_A», когда скорость равна нулю.
Для этого мы должны создать два перехода: один от «Z_idle_A» к «Z_run», а другой в обратном направлении.
Начнем с перехода из режима ожидания в режим работы.
Щелкните правой кнопкой мыши прямоугольник «Z_idle_A» и выберите «Сделать переход».
Появится стрелка, нажав на которую, вы сможете настроить его параметры.
Сначала вам нужно снять флажок «Имеет время выхода».
Если этого не сделать, анимация будет переключаться не по нашему условию, а когда закончится воспроизведение предыдущей.
Нам это вообще не нужно, поэтому снимаем галочку.
Во-вторых, внизу в списке условий («Условия») нужно нажать на «+» и Unity добавит нам условие.
Значения по умолчанию в данном случае именно такие, какие нам нужны: параметр «скорость» должен быть больше нуля для перехода из режима холостого хода в режим работы.
По аналогии создаем переход в обратную сторону, но теперь в качестве условия указываем «скорость» меньше 0,0001. Для параметров типа float проверка на равенство отсутствует; их можно сравнивать только по большему/меньшему:
Теперь вам нужно привязать контроллер к игровому объекту.
Выделим в сцене 3d модель зомби (это дочерний объект объекта «Зомби») и перетащим контроллер мышкой в соответствующее поле в компоненте Animator:
Остаётся только написать скрипт, который будет управлять параметром скорости!
Давайте создадим скрипт MovementAnimator со следующим содержимым: using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class MovementAnimator : MonoBehaviour
{
NavMeshAgent navMeshAgent;
Animator animator;
// Start is called before the first frame update
void Start()
{
navMeshAgent = GetComponent<NavMeshAgent>();
animator = GetComponentInChildren<Animator>();
}
// Update is called once per frame
void Update()
{
animator.SetFloat("speed", navMeshAgent.velocity.magnitude);
}
}
Здесь, как и в других скриптах, мы обращаемся к NavMeshAgent в методе Start. Мы также получаем доступ к компоненту Animator, но так как компонент MovementAnimator мы будем присоединять к игровому объекту «Зомби», а Аниматор находится в дочернем объекте, то вместо GetComponent нам нужно использовать стандартный метод GetComponentInChildren.
В методе Update мы запрашиваем у NavMeshAgent вектор скорости, вычисляем его длину и передаем аниматору в качестве параметра скорости.
Никакого волшебства, все по науке!
Теперь давайте добавим компонент MovementAnimator к игровому объекту Zombie, и если мы запустим игру, то увидим, что зомби теперь анимирован:
Обратите внимание: поскольку мы поместили код управления аниматором в отдельный компонент MovementAnimation, его можно легко добавить и в плеер.
Нам даже не нужно создавать контроллер с нуля — мы можем скопировать контроллер зомби (это можно сделать, выбрав файл «Zombie» и нажав Ctrl+D) и заменив анимации в прямоугольниках состояний на «m_idle_A» и «м_бег».
Все остальное похоже на зомби.
Я оставлю это вам в качестве упражнения (или скачайте код в конце статьи).
Одно небольшое дополнение, которое полезно сделать, — это добавить в класс Zombie следующие строки:
В методе Start: navMeshAgent.updateRotation = false;
В методе обновления: transform.rotation = Quaternion.LookRotation(navMeshAgent.velocity.normalized);
Первая строка сообщает NavMeshAgent, что он не должен управлять вращением персонажа, мы сделаем это сами.
Вторая строка задает персонажу поворот в том же направлении, в каком направлено его движение.
NavMeshAgent по умолчанию интерполирует угол поворота персонажа и выглядит это не очень красиво (зомби поворачивается медленнее, чем меняет направление).
Добавление этих строк убирает этот эффект. Н.
Б.
Чтобы установить вращение, мы используем кватернион.
В трехмерной графике основными способами задания вращения объекта являются углы, матрицы вращения и кватернионы.
Первые два не всегда удобны в использовании, а также подвержены такому неприятному эффекту, как «Gimbal Lock».
Кватернионы лишены этого недостатка и сейчас используются практически повсеместно.
Unity предоставляет удобный набор инструментов для работы с кватернионами (а также с матрицами и углами Эйлера), что позволяет не вдаваться в подробности структуры этого математического аппарата.
Я вижу цель
Отлично, теперь мы можем убегать от зомби.Но этого мало, рано или поздно появится второй зомби, потом третий, пятый, десятый.
и от толпы уже не сбежишь.
Чтобы выжить, нужно убивать.
Более того, в руке у игрока уже есть пистолет. Чтобы игрок мог стрелять, ему должна быть предоставлена возможность выбора цели.
Для этого поместите курсор, управляемый мышью, на землю.
На экране курсор мыши перемещается по двухмерному пространству – поверхности монитора.
При этом наша игровая сцена трехмерна.
Наблюдатель видит сцену своим глазом, где все лучи света сходятся в одной точке.
Объединив все эти лучи, получаем пирамиду видимости:
Глаз наблюдателя видит только то, что попадает в эту пирамиду.
Причем движок специально усекает эту пирамиду с двух сторон: во-первых, со стороны наблюдателя находится экран монитора, так называемая «ближняя плоскость» (на рисунке она окрашена в желтый цвет).
Монитор физически не может отображать объекты ближе экрана, поэтому движок их отсекает. Во-вторых, поскольку ресурсы компьютера ограничены, движок не может расширять лучи до бесконечности (например, буфер глубины должен быть установлен в определенный диапазон возможных значений; чем он шире, тем ниже будет точность) , поэтому пирамида срезана в задней части так называемой «дальней плоскости».
Поскольку курсор мыши перемещается по ближней плоскости, мы можем запустить луч из точки, где он находится, вглубь сцены.
Первым объектом, с которым он пересекается, будет объект, на который указывает курсор мыши с точки зрения наблюдателя.
Построить такой луч и найти его пересечение с объектами сцены можно с помощью стандартного метода Raycast из класса Physics. Но если мы воспользуемся этим методом, он найдет пересечение со всеми объектами сцены — землей, стенами, зомби.
Мы хотим, чтобы курсор двигался только по земле, поэтому нам нужно как-то объяснить Unity, что пересечение поиск должен быть ограничен только заданным набором объектов (в нашем случае только плоскостями Земли).
Если вы выберете любой игровой объект на сцене, вы увидите раскрывающийся список «Слой» в верхней части инспектора.
По умолчанию будет значение «По умолчанию».
Открыв раскрывающийся список, вы сможете найти в нем пункт «Добавить слой.
», который откроет окно редактора слоев.
В редакторе вам нужно добавить новый слой (назовем его «Земля»):
Теперь вы можете выбрать все плоскости земли в сцене и использовать этот раскрывающийся список, чтобы назначить им слой «Земля».
Это позволит вам позже в сценарии указать методу Physics.Raycast проверять пересечения лучей только с этими объектами.
Теперь давайте перетащим спрайт курсора из ресурсов в сцену (я использую Spags Assets⇨Textures⇨Demo⇨white_hip⇨white_hip_14):
Я добавил курсору поворот на 90 градусов вокруг оси X, чтобы он лежал на земле горизонтально, установил масштаб 0,25, чтобы он не был таким большим, и установил координату Y на 0,01. Последнее важно для того, чтобы не было эффекта под названием «Z-fighting».
Видеокарта использует вычисления с плавающей запятой, чтобы определить, какие объекты находятся ближе к камере.
Если установить курсор на 0 (т.е.
такой же, как и земляная плоскость), то из-за ошибок в этих вычислениях видеокарта для некоторых пикселей решит, что курсор ближе, а для других — это земля.
Более того, в разных кадрах наборы пикселей будут разными, что создаст неприятный эффект просвечивающих сквозь землю кусочков курсора и «мерцающих» при движении.
Значение 0,01 достаточно велико, чтобы нивелировать ошибки в расчетах видеокарты, но не настолько велико, чтобы глаз замечал, что курсор зависает. Теги: #Разработка игр #разработчик игр #C++ #unity
-
Существует Ли Зло?
19 Oct, 24 -
Модель «Хищник-Жертва» В Node.js
19 Oct, 24 -
Убогие И Грязные Комментарии, Темы И Т.д.
19 Oct, 24 -
Как Прикрепить Кнопку News2.Ru К Блогеру
19 Oct, 24 -
Корзина Для Каталога Товаров (Minibasket.js)
19 Oct, 24