Управление персонажем, с помощью мыши в Unity
Стандартные Asset управления в Unity имею несколько скриптов, перемещение только с помощью клавиатуры и клавиатуры и мыши, но отсутствует скрипт перемещения с использованием мыши («Diablo-style»). Скрипты будет написаны на C#.

Для проверки всего, что будет написано, понадобятся сцена и размещенные на ней объекты. Их всего 4, это: Персонаж (я использовал: Standard Assets -> Character Controllers -> 3rd Person Controller), Камера, Объект выполняющий роль Цели, я использовал капсулу, можно использовать просто пустой объект, если вы не хотите видеть куда идет ваш персонаж, и Terrain по которому мы и будем перемещаться. Рассмотрим с начало скрипт Камеры.
public class MouseCamera : MonoBehaviour <
// Позиция объекта Target
public Transform target;
// Слой(и) которые реагируют на клик
public LayerMask mask;
// Персонаж которым управляем
public MousePerson player;
// Вектор перемещения
private Vector3 direction;
// Информация о луче
RaycastHit hit;
void Update() <
if (Input.GetMouseButtonUp(0)) <
// Получаем направление луча
Ray ray = camera.ScreenPointToRay(Input.mousePosition);
// Кидаем луч бесконечной длинны и проверяем пересечение слоев
if (Physics.Raycast(ray, out hit, Mathf.Infinity, mask)) <
// Проверяем то, что вернулось и перемещаем туда наш Target
target.position = hit.point;
// Сообщаем персонажу о новом «задание»
player.GetTarget(target.position);
>
>
>
>
Скрипт проверяет пересекает ли место клика объект(ы) указанные в слое, перемещает в это место объект цели и «говорит» персонажу двигаться к этому месту.
Скрипт персонажа немного больше, но не сильно сложнее.
using UnityEngine;
public class MousePerson : MonoBehaviour <
// Персонаж
CharacterController player;
// Радиус в котором персонаж считает что он у цели
public float radiusNoClick = 3f;
// Координаты Target
public Vector3 target = Vector3.zero;
// Вектор перемещения
private Vector3 direction;
// Скорость поворота
public float speedRotation = 10f;
// Скорость передвижения
public float speedMove = 45f;
// Маркер персонажа, на месте или нет
private bool onPlace = true;
// Анимации
private Animation _animation;
// Состояния
enum CharacterState <
Idle = 0,
Walking = 1,
>
// Состояние
private CharacterState _characterState;
void Start() <
// Получаем анимации
_animation = GetComponent();
// Получаем персонажа
player = (CharacterController)gameObject.GetComponent(typeof(CharacterController));
>
void Update() <
if (!onPlace) <
// Вычисляем вектор перемещения
direction = target — this.transform.position;
// Нормализуем, что бы персонаж смотрел не в землю, а прямо
direction = new Vector3(direction.x, 0, direction.z);
direction.Normalize();
// Поворачиваемся
Quaternion look = Quaternion.LookRotation(direction);
this.transform.rotation = Quaternion.Slerp(this.transform.rotation, look, Time.deltaTime * speedRotation);
// Двигаемся
player.Move(direction * Time.deltaTime * speedMove);
// Персонаж в движении
_characterState = CharacterState.Walking;
>
else
// Персонаж в состоянии «покоя»
_characterState = CharacterState.Idle;
if (_animation) <
// Включаем нужную анимацию в зависимости от состояния
if (_characterState == CharacterState.Walking)
_animation.Play(«walk», PlayMode.StopAll);
else if (_characterState == CharacterState.Idle)
_animation.Play(«idle», PlayMode.StopAll);
>
>
public void GetTarget(Vector3 target) <
// Проверим позицию Target
if ((Mathf.Abs(transform.position.x — target.x) >= radiusNoClick) &&
(Mathf.Abs(transform.position.z — target.z) >= radiusNoClick))
<
this.target = target;
// Скажем что персонаж не на месте
onPlace = false;
>
>
public void OnPlaceTrue() <
onPlace = true;
>
>
Метод GetTarget() принимает из скрипта MouseCamera позицию цели и проверяется не находится ли он около персонажа. Если все хорошо и цель далеко, персонаж начинает движение в сторону цели, путем вычислений проводящихся в Update(). Метод OnPlaceTrue() служит для указания персонажу, что он на месте. Он используется в небольшом триггере который размещается на объект Цели.
public class TriggerOnTarget : MonoBehaviour <
public MousePerson player;
void OnTriggerEnter(Collider onPlace) <
player.OnPlaceTrue();
>
>
При пересечении зоны триггера, сообщается что персонаж прибыл в место назначения.

В коллайдере необходимо указать что «он» триггер, установить радиус (зависит от размеров вашего персонажа) и указать в скрипте персонажа, от которого он зависит.
У персонажа в скрипте все настройки оставлены по умолчанию, и ничего дополнительного указывать нет необходимости.
На камере в скрипте необходимо указать объект который выполняет роль цели, игрока которым управляет и слой который будет «ловить» клики, и по которому будет ходить персонаж. Для этой цели я завел отдельный слой Terrain и присвоил его объекту выполняющему роль земли.
Вот и все, пробуйте и экспериментируйте.
Правильная реализация передвижения персонажа
Почему один обьект проходит сквозь другой хотя у меня есть коллайдеры на обоих обьектах?
Почему мой персонаж во время движения проходит сквозь другой обьект, а потом его откидывает назад?
Как реализовать передвижение персонажа в Unity3d правильно?
Почему так часто используется передвижение через transform.position и почему это неправильно?
Почему мой персонаж движется с разной скоростью если проседает FPS?
Почему используя присвоение в transform.position используют множитель Time.deltaTime ?
Почему двигать персонажа через смену transform.position неправильно?
Почему при использовании Velocity или .AddForce() не используется множитель Time.deltaTime ?
Как сделать прыжок от пола, но так что бы персонаж не мог бесконечно взлетать
Почему когда платформа движется, персонаж стоящий на платформе остается на месте?
Почему пуля не всегда наносит урон?
Все эти вопросы, фактически, являются одним единым вопросом, который слишком уж часто встречается у начинающих.
Заодно создал тэг unity3d-faq
![]()
Перед прочтением важно знать
В любом случае на персонаж должен быть навешан RigidBody — скрипт отвечающий за физику персонажа (силу притяжения, силу трения и т.д.)
Хоть я здесь и разбираю в т.ч. нефизическое движение, я настоятельно рекомендую использовать ФИЗИЧЕСКОЕ движение. И переходить на нефизическое только в исключительных ситуациях.
В коде не должно быть прямой привязки к кнопкам. Должна быть привязка к параметрам Input Manager . Которые можно найти в: Edit -> Project Settings -> Input . Нужно принять это как аксиому и не отходить, несмотря, на то что вы там нагуглите.
Я буду использовать здесь 2 термина: "телепортация" и "плавное движение". В моем понимании:
Плавное движение — перерасчет позиции обьекта в рамках физики или паралельно физике на вызове FixedUpdate() .
Телепортация — перерасчет позиции обьекта на промежутке времени большем чем fixedDeltaTime .
Есть люди у которых мнение отличается.
- Плавное движение — исключительно физическое движение
- Телепортация — изменение позиции вручную или использование .Translate() метода.
- UPD: и кстате они правы — но мне влом править весь текст — Translate внутри использует телепортацию на каждом кадреhttps://github.com/Unity-Technologies/UnityCsReference/blob/master/Runtime/Transform/ScriptBindings/Transform.bindings.cs
Учтите, что все что написано ниже упирается в верхние значения терминов, а не эти.
Двигать обьекты в игровых движках можно следующими способами:
используя физический движок (движение обусловленное физической моделью игрового движка)
движение НЕфизическое. Неправильный подход — подход телепортации на каждом кадре. (в Update() )
движение НЕфизическое. Правильный подход — плавное передвижение обьекта между кадрами (паралельно каждому просчету физики) (все равно желательно не использовать)
Движение реализуемое через CharacterController (здесь пока что не рассматривается т.к. новички в его сторону вообще не смотрят, может, позже распишу)
На практике метод передвижения подбирается под конкретного персонажа[персонажа — не буквально. Это может быть и автомобиль]. В одном случае лучше будет физическое перемещение. В другом — нефизическое. В третьем случае будет лучше всего CharacterController. Понимание что лучше в каком случае прийдет с практикой.
Новички очень часто использую телепортацию на каждом кадре, что есть критически неправильным подходом. Потом на SO появляются кучи клонов вопросов вроде "почему персонажа дергает возле стены?" или "почему он проходит сквозь стену?" или "почему пуля не всегда наносит урон?" и подобные.
Нужно запомнить всего одно правило: Двигать/поворачивать через присвоение transform.position / transform.rotation нельзя. Это порождает проблемы. В любом случае это вам вылезет боком.
Все для чего нужно это — телепортация в другое место обьекта, но никак не его движения.
Пример правильной реализации движения:
( на примере обьекта-шара )
Обратите внимание что за основу взят код из официальной документации/туториалов по юнити. Если есть несколько источников информации по какому-то мелкому но часто задаваемому вопросу (например движение персонажа ) — выбирайте официальную документацию! Там точно фигни не посоветуют,
в отличии от пестрящих дичью форумов, в т.ч. сервисе вопросов/ответов от юнити. Там в таких темах слишком часто пишут ответы те люди, которые понятия не имеют о правильном подходе.
Связанные с темой понятия:
Есть Update() — этот метод вызывается на каждой прорисовке кадра. Time.DeltaTime — это расчетное время между прорисовкой двух кадров. Если FPS проседает на компьютере, то этот параметр возрастает пропорционально проседанию.
Есть FixedUpdate() — это метод который вызывается при перепросчете физики. Time.FixedDeltaTime , как вы уже догадались, это время между вызовами FixedUpdate() . Оно может изменятся вручную через настройки, но упирается в физические возможности машины на которой игра будет запущена.
Если обьект не обладает физическими свойствами (не имеет RigidBody) эти параметры и методы можно использовать для НЕфизического передвижения.
Например поворот камеры.
Или крутящийся куб на небосводе.
Или движущийся изображение поезда где-то далеко, к которому нельзя подойти близко. Физика такому обьекту просто ни к чему — это просто лишняя трата ресурсов
но, даже, в этом случае предпочтительно использовать Transform.Translate , но про это позже
Мы не получим дергающуюся картинку при проседании кадров если сделаем НЕФИЗИЧЕСКОЕ движение правильно:
ВАЖНО: . ПРИМЕР ВРЕДНОГО КОДА. Не делайте так!
мы присваиваем в новую позицию:
- старую позицию
- направление движения
- скорость передвижения умноженную на Time.deltaTime .
Поэтому, даже, если, у нас было 60 кадров и случилось проседание до 10 кадров — скорость вращения/движения обьекта не будет изменятся. Ведь, мы ее учитываем вместе с проседанием кадров.
Про физические свойства движения.
Допустим мы двигаем обьект через rb.Velocity или через AddForce() , то это физическое движение обьекта. То есть она может изменятся во времени сама под действием неких физических законов. Например, мы задумали сделать прыжок персонажа:
мы разово задаем вектор скачка. Только 1 долю секунды. Но он будет изменятся во времени автоматически равномерно уменьшаясь под силой тяжения. Пока не станет нулевым (верхняя точка прыжка), а потом не пойдет в минус по Y (падение), а потом не упадет на землю и не отскочит от нее (снова плюс по Y ) и так до полной остановки физической скорости обьекта на земле.
Допустим, мы двигаем изменением transform.Positon нашего плеера вперед по нажатию клавиши "пробел". В какой-то момент мы перестаем нажимать кнопку — движение резко остановится и замрет. Это потому, что наше движение НЕ является физическим. Допустим мы подойдем к стенке и попробуем пройти на нее. Т.к. мы занимаемся телепортацией обьекта, то наш персонаж сначала дойдет до стенки, а потом телепортируется ВНУТРЬ нее, после чего Collider ее вытолкнет из себя. Как глубоко телепортируется внутрь зависит лишь от того, на какое расстояние мы телепортируем нашего персонажа за кадр. То есть это "Bad Practice" так реализовать перемещение персонажа.
Но в то же время есть и допустимое не-физическое перемещение. Это использование метода Transform.Translate() . Это (вроде как) тоже телепортация, но с попыткой плавного нефизического перемещения обьекта. Но использование этого метода не освобождает нас от использования deltaTime/fixedDeltaTime, как в примере оф.документации.
( Снова таки — если на вашем обьекте есть RigidBody — наверняка нужно использовать физическое перемещение все равно! )
Если девайс с игрой сильно загружен, вызов методов Update() / FixedUpdate() тоже может просесть в скорости. И если в физике это учтено и без нас, то сейчас мы делаем НЕ физическое движение и именно по-этому это нужно учитывать добавлением даного множителя.
Но и без использования даного множителя у нас не появится проблем с провалами сквозь стены. Это просто фикс скорости.
Пример простой но хорошей НЕФИЗИЧЕСКОЙ реализации кода движения на примере персонажа.
Если в прошлом примере мы двигали шар, то было допустимо его толкать используя физ.модель. То есть мы использовали AddForce() для этих целей.
Допустим у нас персонаж — человек, а не шар. Давай создадим вместо человека его подобие — высокий куб 0.8х1.8х0.3 и попробуем нацепить на него наш скрипт движения шара. Выйдет следующее:
То есть когда мы пытаемся подвигать, наш персонаж падает (мы ж его толкаем, логично!). Когда он упал — он не может двигатся из-за силы трения. Зато мы можем двигать его в прыжке. 🙂
Давайте актуализируем этот код под даного персонажа. Мы заменим физический толчек обьекта на не-физическое, но ПЛАВНОЕ перемещение обьекта в пространстве:
С этим кодом мы получим такой результат:
С такой реализацией у нас не будет проблем вроде скачков скорости на проседании или повышении количества FPS, проваливаний, дерганости, прохождения сквозь стены или других неожиданностей.
Теперь мы можем занятся украшательствами — например повороты тела. Довольно приятно реализованы повороты вот здесь: Как сделать управление, как в игре "Overcooked"?
Так же можно добавить анимацию бега на нашего персонажа (ну если бы это был не куб).
Но как же реализация на физике?
Да, можно подобное реализовать и на физике.
Наша прошлая версия скрипта имела несколько недостатков. А именно:
- нужно было вручную отмечать каждый из предметов от которого мы можем прыгать. То есть добавив ящик на пол, нам нужно еще и его отметить тэгом Ground.
- если поставить кучу ящиков вертикально, присвоить каждому из них тэг "Ground", то просто подойдя к вертикальной стене из ящиков мы сможем взлететь вверх). То есть нам не важно к чему мы дотрагиваемся — к полу или к стене — оно давало нам возможность прыгать.
- наше движение все так же было НЕ физическим. То есть если мы начнем двигать игрока влево-вправо то он будет резко останавливатся а потом резко двигатся в противоположную сторону. В живом мире так не бывает.
Вспомните уроки физкультуры, когда нужно было пробежать 30 метров вперед, взять палочку, пробежать 30 метров назад, положить палочку и еще раз 30 метров в другую сторону. Что случалось с бегуном в этот момент если посмотреть сбоку? Сначала скорость растет, потом достигает пика, а потом торможение, взятие палочки, бег в другую сторону — снова возрастание скорости. Никаких резких скачков. Этого можно добится именно передвижением при помощи физики.
Давайте поместим на наш куб CapsuleCollider (минимальное торможение из-за силы трения) и заблочим в rigidBody rotateX и rotateZ (что б наш персонаж не падал на бок).
А потом нацепим на него вот этот скрипт:
Вы видите эту плавность, как будто человек бежит, останавливается, бежит в другую сторону? Красота!
А теперь вернитесь к прошлой гифке и присмотритесь. Движение совсем не такое 🙂 Там как буд-то рукой двигают шахматную фигуру по доске.
Ну и описанные выше баги поведения были пофикшены с такой реализацией.
Можно добавить еще физический материал нашему персонажу и откоректировать его поведение.
Вообще улучшать реализацию можно до бесконечности. Но, думаю, основные проблемы СПОСОБОВ ПЕРЕДВИЖЕНИЯ с которыми вы столкнетесь, я затронул 🙂
Оптимально использовать именно передвижение на базе физики.
Пытайтесь использовать исключительно физическое передвижение.
Реализация нестандартной физики движений.
Одним из моих любимейших примеров нестандартной физики движения является игра Ori and the Blind Forest
Такое перемещение/такие прыжки невозможно сделать на основе стандартной физики. Вероятнее всего, это делалось через физическое перемещение + костыли для получения нужных эфектов которые противоречат стандартной физике.
Сначала разрабатываются концепты движения. Они делаются в любом видеоредакторе с примитивными фигурами. Вот пример (если станет недоступным искать можно по Ori and the blind forest Enemy Concepts ) :
Обратите внимание на то, то здесь прорисовано не только перемещение обьекта, но и его вытягивания/сжатия. Изменения формы во время любого взаимодействия с внешним миром. В т.ч. выстрелы так же влияют на форму. А так же что указываются радиусы опознавания главного героя каждым отдельным врагом.
Костыли для каждого персонажа/врага свои собственные. Это делается что бы каждый из них обладал своей уникальной физикой. Сделать это на общей физике навряд ли возможно.
Движение реализовано "правильно" но предмет все равно пролетает сквозь стену
ДАЖЕ если вы реализовали физическое передвижение вашего персонажа, все равно может случится такое, что просчет CollisionDetect может проходить с ошибками. Такое бывает.
Для таких случаев есть настройки отвечающие за обработку CollisionDetect в настройках самого RigidBody.

Желательно такого не делать т.к. это негативно сказывается на производительности. Чем на большем количестве обьектов вы меняете эти настройки, тем более вероятно что вы делаете какую-то дичь, которую делать совсем не нужно. Считайте это спасательным кругом, а не панацеей. А если вы так будете делать, то рано или поздно вы прийдете на SO с вопросом почему игра тормозит, вас попросят показать код и ничего не найдут просто потому, что проблема тормозов не в коде. И намучаетесь вы с оптимизациями ой как сильно.
Так делать — не является ошибкой(!). Но чем меньше вы так будете делать — тем лучше. Подходите к изменению этих настроек с умом!
Разработка вашей первой игры с помощью Unity и C#. Часть 3
Вы все еще со мной в этом цикле статей? Хорошо. В первой статье я рассказал о некоторых основах Unity (msdn.microsoft.com/magazine/dn759441). Во второй статье я сосредоточился на двухмерной графике (2D) в Unity (msdn.microsoft.com/magazine/dn781360). Теперь я перейду к своей любимой части разработки игр — к трехмерной графике (3D). Трехмерный мир — это воистину волшебное место: потрясающие среды, создающие эффект полного погружения в мир игры, богатые звуковые эффекты и красивые визуальные средства; даже простая игра-головоломка с реалистичной физикой может зацепить так, что вы часами будете в нее играть.
3D-игры определенно добавляют уровень сложности по сравнению с 2D, но шаг за шагом вы постепенно создаете интересную 3D-игру. Новые параметры проектов как для 2D, так и для 3D, в Unity поддерживают 3D. В 2D-игре могут быть 3D-объекты и наоборот.
Из чего состоит трехмерная сцена?
Трехмерная (3D) сцена состоит главным образом из трех основных визуальных компонентов: источников света (lights), рендеров мешей (mesh renderers) и шейдеров. Unity поддерживает четыре типа источников света. Вы найдете их в меню GameObject. Поэкспериментируйте, добавляя разные типы и изменяя их свойства. Самый простой из них — источник направленного света (directional light), который подобен солнцу на небе.
Меш (mesh), или модель, — это набор вершин, образующие многоугольники, из которых состоит какой-либо объект. Шейдер является скомпилированной подпрограммой, содержащей код для управления тем, как будет отображаться ваш объект или как он будет взаимодействовать с источником света. Некоторые шейдеры просто берут освещение и отражают его подобно зеркалу, другие — принимают текстуру (изображение, применяемое к мешу) и могут создавать тени и глубину, а некоторые даже позволяют прорезать визуальные дыры в ваших моделях (пример — изгородь).
Модели обычно содержатся в файлах .fbx или .obj, экспортируемых из другого программного пакета для моделирования. FBX-файлы также могут хранить данные анимаций, поэтому вы можете получить один FBX-файл для своей модели и еще один, который содержит несколько анимаций. Также поддерживается ряд сторонних файловых форматов, таких как Autodesk Maya (.ma) и Blender. Как правило, сторонняя программа должна быть установлена в той же системе, если вы хотите, чтобы Unity импортировал эти файлы, а затем предоставлял возможность их простого перетаскивания в проект Unity подобно любым другим файлам. «За кулисами» Unity будет преобразовывать другие файловые форматы (при импорте или обнаружении изменений в файлах) в формат .fbx.
Asset Store
Я вкратце рассказывал об Asset Store (магазине ресурсов) в своей первой статье, но по-настоящему полезным он становится при разработке 3D-игр. Я не художник и, поскольку это технический журнал, полагаю, что большинство из вас тоже не являются художниками. (Если же вы художник, пожалуйста, примите мои поздравления — это редкий дар.) Но, если вы хотите создать игру, например, с буйной растительностью и старыми полуразрушенными зданиями, это не проблема. Я могу купить то, что мне нужно, в Asset Store. Если мне нужны 15 видов зомби, я могу приобрести пакет от Mixamo в Asset Store. Потенциально возможные комбинации почти бесконечны, так что не волнуйтесь о том, что чья-то игра будет выглядеть похожей на вашу. А самое главное в том, что Asset Store интегрирован с Unity. Вы можете обновлять свои пакеты, щелкая Window | Asset Store, а затем значок корзины. Вы также можете просматривать рецензии и комментарии, чтобы быстрее понять, подойдет ли конкретный элемент для вашего проекта, например оптимизирован ли он для мобильного устройства. Настольные игры обычно используют гораздо больше объектов, текстур, вершин, памяти, чем мобильная игра, хотя некоторые из более новых чипов делают сегодня мобильные устройства близкими по своим возможностям к Xbox 360.
В типичной 3D-игре применяются многие из тех же концепций, что и в 2D-играх: коллайдеры, триггеры, абсолютно твердые тела (rigid bodies), игровые объекты/преобразования, компоненты и др. Независимо от типа 3D-игры обычно вам нужно контролировать ввод, перемещения и персонажей, использовать анимации и эффекты частиц, а также выстраивать воображаемый мир так, чтобы он был и фантастическим, и реалистическим. Мы обсудим некоторые способы, которыми Unity может помочь добиться всего этого.
Контроллеры ввода, перемещения и персонажей
Чтение ввода для перемещения в 3D немного усложняется, потому что вместо простого движения в плоскостях X и Y теперь можно двигаться в трех измерениях: X, Y и Z. Варианты 3D-перемещения включают (но не исчерпываются ими) движение сверху вниз, где персонаж двигается только горизонтально и вертикально; поворачивание камеры или персонажа при считывании ввода от мыши, как это делается во многих шутерах от первого лица (first-person shooter, FPS); смещение влево и вправо при чтении ввода по горизонтали; вращение вокруг себя при чтении ввода по горизонтали или простое движение в обратном направлении. Вариантов перемещения очень много, так что вам есть из чего выбирать.
Перемещая объект, вы не сообщаете ему позицию, в которую он должен переместиться, как вы, возможно, ожидали. Вспомните, что вы выполняете код на каждом кадре, поэтому должны смещать объект с небольшим приращением. Можно либо отдать это на откуп ядру поддержки физики, приложив силу к абсолютно твердому телу, либо создать переходную анимацию (tween) объекта. Последнее, по сути, означает переход между значениями, т. е. движение из точки A в точку B. Есть разные способы создания соответствующего набора значений в Unity, в том числе применение бесплатных сторонних библиотек, таких как iTween. На рис. 1 показаны некоторые ручные способы перемещения объекта в Unity. Заметьте, что для простоты они не оптимизированы (для оптимизации мне пришлось бы хранить ссылку на преобразование в переменной, чтобы избежать частого переключения между управляемым и «родным» кодом).
Рис. 1. Различные методы перемещения объектов
У каждого подхода есть свои плюсы и минусы. При перемещении только с помощью преобразования производительность может пострадать (методы 1–2), хотя это очень простой способ перемещения. Unity предполагает: если у объекта нет компонента rigidbody, он, вероятно, не является перемещаемым объектом. Она создает на внутреннем уровне статическую матрицу столкновений (static collision matrix), чтобы знать, где находятся объекты; это повышает производительность. Когда вы перемещаете объекты с помощью преобразования, эту матрицу нужно пересчитывать, что приводит к уменьшению производительности. В простых играх вы никогда не заметите этого падения производительности, и такие варианты могут оказаться для вас самыми простыми, хотя по мере усложнения ваших игр важно перемещать само абсолютно твердое тело, как в методах 4–6.
Поворачивание объектов
Поворачивание объектов — операция сравнительно несложная, во многом аналогичная перемещению объектов, но теперь векторы представляют градусы, а не позицию или нормализованный вектор. Нормализованный вектор — это просто вектор с максимальным значением 1 для любого значения, и он может использоваться, когда вам нужно лишь ссылаться на направление по вектору. Вам доступны некоторые ключевые слова, относящиеся к векторам, например Vector3.right, back, forward, down, up, left, right, zero и one. Все, что будет перемещаться или поворачиваться в положительном горизонтальном направлении, может использовать Vector.right, под которым подразумевается (1,0,0), или одна единица вправо, а в случае поворачиваемого объекта это один градус. На рис. 2 я просто понемногу поворачиваю объект в каждом кадре.
Рис. 2. Метод для поворачивания объекта
У каждого из этих методов есть свои нюансы. Какой из них следует использовать вам? Я попытался бы по возможности приложить силы к rigidbody. Наверное, я просто немного запутал вас этим вариантом. Хорошая новость в том, что уже имеется код, способный делать за вас практически все из этого.
Вы обратили внимание на Quaternion в методе 3? Unity использует на внутреннем уровне эти Quaternion для представления всех поворачиваний. Quaternion — эффективные структуры, предотвращающие эффект, который называется шарнирной блокировкой (gimbal lock). Он возможен, если для поворачивания вы используете обычные углы Эйлера (Euler angles). Шарнирная блокировка происходит, когда две поворачиваемые оси оказываются в одной плоскости, после чего их нельзя разделить. (Наглядную демонстрацию см. в видеоролике по ссылке bit.ly/1mKgdFI.) Чтобы избежать этой проблемы, Unity использует структуры Quaternion вместо углов Эйлера, хотя вы можете задавать углы Эйлера в Unity Editor и он в конечном счете будет выполнять их преобразование в Quaternion. Многие никогда не сталкиваются с шарнирной блокировкой, но я хотел указать на то, что, если вы хотите напрямую задавать поворачивание в коде, то должны делать это через Quaternion; кроме того, вы можете сами преобразовывать углы Эйлера с помощью Quaternion.Euler.
Теперь, когда вы увидели множество вариантов, должен отметить, что самым простым методом я нахожу использование rigidbody и простое применение .AddForce к персонажу. По возможности я предпочитаю повторно использовать код, и, к счастью, Unity предоставляет целый ряд заготовок (prefabs).
Давайте не будем изобретать колесо
Unity предоставляет пакет Sample Assets в Asset Store (bit.ly/1twX0Kr), который содержит кросс-платформенный диспетчер ввода, элементы управления для контроля джойстиков на мобильных устройствах, некоторые анимации, эффекты частиц и, что самое важное, ряд заранее скомпилированных контроллеров персонажей.
В Unity (версии 4.6 на момент написания этой статьи) также включены некоторые более старые ресурсы. Эти ресурсы теперь распространяются как отдельный пакет, который Unity может обновлять индивидуально. Вместо написания всего кода для создания в игре персонажа от первого или третьего лица или даже самостоятельно движущегося автомобиля вы можете просто использовать заготовки (prefabs) из ресурсов-образцов. Перетащите заготовку в свою сцену и вы моментально получите персонаж с видом от третьего лица со множеством анимаций и полным доступом к исходному коду, как показано на рис. 3.
Рис. 3. Заготовка персонажа от третьего лица
Анимации
Системе анимации Mecanim в Unity можно было бы посвятить целую книгу (и такая книга уже написана, кстати). Анимации в 3D, как правило, сложнее, чем в 2D. В 2D файл анимации обычно изменяет спрайт для рендеринга в каждом ключевом кадре. В 3D данные, связанные с анимацией, гораздо сложнее. Вспомните: в предыдущей статье я говорил, что файлы анимации содержат ключевые кадры. В 3D может быть много ключевых кадров, каждый из которых содержит массу точек данных для движений пальцев, руки или ноги или для выполнения любого количества и типа перемещений. Кроме того, меши могут определять кости в них и использовать компоненты, называемые рендерами мешей со скинами (skinned mesh renderers), которые деформируют меши на основе того, как движутся кости; это во многом напоминает то, как движутся живые существа.
Файлы анимации обычно создаются в сторонней системе моделирования/анимации, хотя их можно создавать и в Unity.
Базовая поза персонажа в системе 3D-анимации — в виде буквы «T» (T-pose), и понимать это следует буквально: персонаж стоит вертикально с вытянутыми в стороны руками. Такой вариант применяется практически ко всем моделям, имеющим гуманоидную форму. Затем вы можете вдохнуть жизнь в этот базовый персонаж, назначив ему с помощью Mecanim практически любой файл анимации. Можно сделать так, чтобы зомби, эльф и человек танцевали совершенно одинаково. Также можно смешивать и подбирать файлы анимации любым подходящим для вас способом и назначать их через состояния во многом по аналогии с тем, что вы делали в 2D. Для этого применяется контроллер анимации, подобный показанному на рис. 4.
Рис. 4. Контроллер анимации для управления состояниями анимации персонажа
Помните, что персонажи и анимации можно получать из Unity Asset Store, создавать с помощью средств моделирования и использовать сторонние продукты вроде Fuse от Mixamo, которые позволяют быстро генерировать адаптированные под ваши потребности персонажи. Просмотрите мои видеоролики на Channel 9 — они дадут вам начальное представление об анимации в Unity.
Создание мира
В Unity встроена система террейнов для генерации миров. Можно создать террейн, а затем использовать имеющиеся инструменты для приданию террейну нужных форм, создания гор, размещения деревьев и травы, рисования текстур и др. Можно добавить в мир небо, импортировав пакет скайбоксов (Assets | Import Package | Skyboxes) и назначив их в Edit | Render Settings | Skybox Material. У меня ушло около пары минут на создание террейна с динамической отражающей водой, деревьями, песком, горами и травой (рис. 5).
Рис. 5. Быстро созданный террейн
Системы координат в Unity
В Unity есть четыре метода для ссылки на какую-либо точку в игре или на экране (рис. 6). Существует экранное пространство, диапазон которого простирается от 0 до количества пикселей; оно обычно используется для получения местоположения, где пользователь коснулся экрана или щелкнул мышью.
Пространство области просмотра (viewport space) — просто значение от 0 до 1, благодаря которому можно сообщить, что полпути — это .5, а не делить пиксели на 2. Поэтому я могу разместить объект посреди экрана, указав в качестве его позиции координаты (.5, .5).
Мировое пространство (world space) относится к абсолютному позиционированию объекта в игре на основе трех координат: (0, 0, 0). Все игровые объекты верхнего уровня в сцене имеют свои координаты, перечисленные в мировом пространстве.
Наконец, локальное пространство (local space) всегда относительно родительскому игровому объекту. В случае игрового объекта верхнего уровня оно идентично мировому пространству. Все дочерние игровые объекты перечисляются в Editor с координатами относительно их предку, поэтому, например, в вашей модели у дома могут быть мировые координаты (200, 0, 35), тогда как у его передней двери (предполагая, что это дочерний игровой объект, принадлежащий дому) — только (1.5, 0, 0), поскольку они относительны предку. В коде, когда вы ссылаетесь на transform.position, всегда используются мировые координаты, даже если это дочерний объект. В данном примере у двери были бы мировые координаты (201.5, 0, 35), но, если вместо этого вы ссылаетесь на transform.localPosition, вы получили бы (1.5, 0, 0). В Unity есть функции для преобразований между различными системами координат.
Рис. 6. Координаты в Unity
| Screen space: | Экранное пространство: |
| Viewport space: | Пространство области просмотра: |
| World space: | Мировое пространство: |
В предыдущих примерах перемещения я выполнял эти операции, используя в основном мировое пространство, но в некоторых случаях применял локальное. Вернитесь к методу 7 на рис. 1. В этом примере я принимаю локальный нормализованный вектор Vector.forward, который представляет собой (0,0,1). Само по себе это ничего особенно не значит. Однако это показывает намерение что-то переместить по оси Z, т. е. вперед. А как быть, если объект поворачивается на 90 градусов от (0,0,0)? Слово «вперед» теперь может иметь два значения: оригинальную абсолютную ось Z (в мировом пространстве) или ось Z, относительную поворачиваемому объекту, что всегда указывает вперед для объекта. Если нужно, чтобы объект всегда двигался вперед независимо от угла поворота, можно просто транслировать значения между локальным forward и мировым вектором forward, используя transform.TransformDirection(Vector3.forward * speed), как и показано в том примере.
Потоки и сопрограммы
Unity использует систему сопрограмм (coroutine system) для управления своими потоками. Если вам нужно, чтобы что-то происходило в другом потоке, вы запускаете сопрограмму вместо создания нового потока. Unity управляем всем «за кулисами». Сопрограмма приостанавливается, встречая метод yield. В примере на рис. 7 проигрывается анимация атаки, потом следует пауза в течение случайно выбранного интервала, а затем анимация атаки воспроизводится снова.
Рис. 7. Применение сопрограммы для приостановки действия
Физика и обнаружение коллизий
Физика и средства обнаружения коллизий в 3D почти такие же, как в 2D, с тем исключением, что коллайдеры имеют другую форму и у компонента rigidbody есть несколько других свойств, таких как способность свободного вращения или перемещения по осям X, Y и Z. В 3D теперь имеется коллайдер меша, который обертывает всю фигуру модели как зону распознавания коллизии. Звучит грандиозно, и для коллизий это все весьма хорошо, но плохо для производительности. В идеале, нужно упростить формы коллайдеров и ограничить процессорные ресурсы, необходимые для их использования. У вас есть зомби? Нет проблем — используйте коллайдер капсулы (capsule collider). Сложный объект? Используйте несколько коллайдеров. По возможности избегайте коллайдера меша.
Unity предоставляет ряд методов, позволяющих узнавать, когда происходит коллизия или срабатывает триггер. Ниже показан базовый пример:
Методов гораздо больше, чем перечислено здесь, в частности есть методы OnTriggerExit и OnCollisionExit, почти идентичные своим аналогам в 2D.
Создание объектов
Когда вам нужно создать в период выполнения новые элементы на основе GameObject, не пользуйтесь конструкторами. Вместо них применяйте Instantiate. У вас определенно могут быть классы с конструкторами, которые в скриптах неявно наследуются от MonoBehavior; это происходит во всех скриптах верхнего уровня, назначенных любому GameObject. Однако эти скрипты могут вызывать конструкторы для других объектов:
Эффекты частиц
Если вы хотите, чтобы у вас были мерцающие звезды, пыль, снег, взрывы, огонь, туман, поднимающийся от водопада, всякие кровавые и другие эффекты, нужно использовать эффект частиц (particle effect). В Unity есть старая система частиц и более новая, лучше оптимизированная — Shuriken. С помощью Shuriken в Unity можно делать массу потрясающий вещей, в том числе заставить падающие частицы поддерживать коллизии. Поскольку на этот счет существует множество учебных пособий, например по ссылке bit.ly/1pZ71it, и эффекты, как правило, создаются в Editor с дизайнером, здесь я просто покажу, как можно создавать их экземпляры, когда, скажем, некий персонаж попадает в область триггера монеты, которую он должен подобрать.
Чтобы начать работу с частицами, выберите Game Object | Particle System и вы сразу же увидите, что в вашу сцену добавлен один из эффектов, как на рис. 8.
Рис. 8. Эффект частиц
Я предпочитаю создавать из своих систем частиц заготовки (о которых я рассказывал во второй статье), чтобы их можно было легко использовать повторно. Достаточно создать их экземпляры в коде, сначала назначив скрипт игровому объекту (предполагая, что его класс наследует от MonoBehavior, как и все компоненты script игровых объектов), а затем перетащив нужный эффект частиц в Editor из сцены или заготовки в проект, например, на предоставляемое свойство SmokeEffect (рис. 9).
Рис. 9. Предоставляемое свойство SmokeEffect
Создание UI
В Unity 4.6 добавили совершенно новую систему UI для создания элементов HUD (heads-up display) в игре с использованием текста, панелей, виджетов и др. Добавление текста в HUD вашей игры сводится к выбору GameObject | UI | Text и заданию шрифта и текста. Если вам нужно позднее управлять текстом в коде, например для обновления счета, вы просто используете:
Если мне требуется некое изображение в UI, я открываю GameObject | UI | Image и назначаю спрайтовое 2D-изображение этому новому компоненту. Значения задаются, как и в случае любого другого игрового объекта. Надеюсь, что к этому моменту вы уже заметили шаблон. Чтобы создать простой GUI, создайте UI-объекты через меню GameObject | UI, укажите начальные значения в Editor и позже контролируйте их, получая ссылки на данные UI-компоненты и изменяя значения или даже анимируя их. Я сформировал базовый GUI, показанный на рис. 10, создав элементы под новым компонентом Canvas. Новая UI-система в Unity 4.6 содержит ряд базовых типов объектов, таких как Panel, Button, Text, Image, Slider, Scrollbar и Toggle, и с ними невероятно легко работать при создании UI.
Рис. 10. UI с изображением и текстов в HUD
Искусственный интеллект в вашей игре
Было бы нечестно не упомянуть об искусственном интеллекте (AI), хотя здесь я не стану вдаваться в детали создания AI (хотя строительные блоки для него присутствуют в более ранних примерах кода для find/move/rotate). Но я кратко расскажу о нескольких доступных вам вариантах. Не уверен, что игровой AI правильно называть искусственным интеллектом, потому что весь этот интеллект сводится не более чем к самым базовым действиям. Я показывал, как поворачивать преобразование в сторону другого объекта и перемещать этот объект. Это базовый AI во многих играх. В Unity встроены некоторые средства нахождения пути за счет поддержки NavMesh, который заранее рассчитывает все пути вокруг объектов. NavMesh работает весьма хорошо и теперь включен в бесплатную редакцию Unity, хотя многие выбирают вместо него A* Pathfinding Project (arongranberg.com/astar) — алгоритм, который можно реализовать самостоятельно или, экономя время, приобрести соответствующий пакет ресурсов. На момент написания этой статьи 2D-поддержка поиска путей (pathfinding) не была встроена в Unity — только для 3D, хотя A* имеет такую поддержку. Есть также Behave 2.0 от AngryAnt — популярный плагин AI для Unity с некоторыми по-настоящему впечатляющими средствами и RAIN — бесплатный инструментальный набор для создания AI от rivaltheory.com, который тоже весьма неплох и имеет встроенные поведения для follow, find, интеграции с Mecanim и др.
Заключение
3D-мир вносит дополнительный уровень сложности по сравнению с 2D, так как в нем вы имеете дело с полноценными мешами и еще одним измерением. Asset Store — важнейший источник ресурсов как для начинающих, так и для продвинутых разработчиков игр, и вы действительно можете значительно ускорить создание своей игры, используя готовые ресурсы.
Когда я только начинал разрабатывать игры, я едва не сошел с ума в поисках множества моделей и текстур в Интернете. В Интернете есть некоторые великолепные торговые площадки, предлагающие такие ресурсы, но вы быстро поймете, что не все они годятся для игр. Как-то раз я скачал модель небольшого валуна, в которой оказалось почти 100 000 вершин! Ищите ресурсы, оптимизированные под мобильные устройства, или проверяйте количество вершин/многоугольников, чтобы они гарантированно работали в ваших играх. А иначе они значительно замедлят вашу игру. Существуют средства оптимизации моделей, в том числе Cruncher, включенный в Unity. В следующей статье я рассмотрю, как перенести игру или приложение из Unity на платформу Windows. Заходите в мой блог на Channel 9 (aka.ms/AdamChannel9), где я периодически выкладываю свои учебные видеоролики и ссылки на различный контент для скачивания.
Адам Тьюлипер (Adam Tuliper) — старший идеолог по технологиям в Microsoft; живет в солнечной Южной Калифорнии. Разработчик инди-игр, один из администраторов Orange County Unity Meetup и автор на pluralsight.com. Скоро в его семье появится третий ребенок, так что связывайтесь с ним, пока у него еще есть свободные минуты, по адресу adamt@microsoft.com или через twitter.com/AdamTuliper.
Выражаю благодарность за рецензирование статьи экспертам Мэтту Ньюмену (Matt Newman) из Subscience Studios и Тоутвидасу Цилису (Tautvydas Žilys) из Unity.