Как создать контроллер для телефона на unity 2021
Я ни в коем случае не могу назвать себя хорошим программистом или опытным разработчиком игр, но все же хотел бы предложить мой вариант реализации перемещения персонажа.
Данный способ подойдет для реализации игр с видом сверху, при небольших переделках можно использовать и для игры с видом от третьего лица. Можно использовать как для персонажей, управляемых игроком, так и для ботов.
Здесь показаны основные элементы проекта, но лучше детально его изучить, если захотите использовать у себя.
Предлагаю минимальный набор скриптов и материалов для реализации перемещения, если нужно что-то более сложное, то придется доработать скрипты или можно у меня спросить, я помогу :)
Аниматор состоит из двух BlendTree, одно для перемещения персонажа, а второе для поворотов на месте.
BlendTree для поворотов очень простой, состоит из двух анимаций. Одна для поворота направо, а вторая для поворота налево.
BlendTree для перемещения куда сложнее, он состоит из 9 анимаций, 8 отвечают за направление движения и 1 для неподвижного положения
BlendTree для перемещения. Все анимации зависят от двух чисел (от -1 до 1), Vert отвечает за перемещение вперед и назад, а Hor за перемещение вправо и влевоСкрипты - самое главное. Я же программистом работаю :)
Всего используется два скрипта, один отвечает за перемещение, а второй передает первому скрипту инфу о нажатых кнопках.
Не буду показывать здесь полностью весь код, покажу основные методы. Их всего 3.
Метод, отвечающий за перемещение персонажа вызывается каждый кадр, в него передаются координаты цели, вектор, содержащий инфу о том, куда идти персонажу (x - движение вправо или влево, 1 - вправо, -1 - влево, 0 - на месте; y - не используется, но можно использовать для переключения на ходьбу например; z - перемещение вперед или назад, 1 - вперед, -1 - назад, 0 - на месте) и время между кадрами.
public void Move(Vector3 targetTransPos, Vector3 moveAxis, float deltaTime) < //подстановка нужных данных в аниматор (скорость движения, поворота и т.п.) _anim.SetFloat(_inputParams.MoveSpeed, _moveSpeed); _anim.SetFloat(_inputParams.RotSpeed, _rotSpeed); _anim.SetFloat(_inputParams.Vert, moveAxis.z, 1f / _movingSens, deltaTime); _anim.SetFloat(_inputParams.Hor, moveAxis.x, 1f / _movingSens, deltaTime); //направление взгляда персонажа _anim.SetLookAtWeight(_inverseKinematicWeights.Weight, _inverseKinematicWeights.BodyWeight, _inverseKinematicWeights.HeadWeight, _inverseKinematicWeights.EyesWeight, _inverseKinematicWeights.ClampWeight); _targetTransPos = Vector3.Lerp(_targetTransPos, targetTransPos, _targetSens * deltaTime); _anim.SetLookAtPosition(_targetTransPos); //поворот тела персонажа, вызывается метод, отдельно отвечающий за это var isRot = _isSimpleRot ? false : Mathf.Abs(moveAxis.x) <= Mathf.Epsilon && Mathf.Abs(moveAxis.z) <= Mathf.Epsilon; Rot(_targetTransPos, isRot, Time.deltaTime); >Метод, отвечающий за поворот персонажа, в него передаются координаты цели, флаг, отвечающий за необходимость поворота с помощью анимации и время между кадрами. Это довольно сложный код, если интересно в нем разобраться, то пишите, я помогу.
private void Rot(Vector3 targetTransPos, bool isRot, float deltaTime) < //запоминаем старый угол поворота персонажа var oldRot = _anim.transform.eulerAngles; //поворачиваем персонажа на цель (потом обратно вернем, тут нам нужны данные) _anim.transform.LookAt(targetTransPos); //находим угол между старым поворотом персонажа и новым, при котором персонаж развернут на цель var angleBetween = Mathf.DeltaAngle(_anim.transform.eulerAngles.y, oldRot.y); //выбираем направление поворота у персонажа _anim.SetFloat(_inputParams.Rot, (angleBetween < 0) ? 1 : -1, 1f / _movingSens, deltaTime); //убираем знак у угла поворота angleBetween = Mathf.Abs(angleBetween); //экономим на спичках и переиспользуем deltaTime, теперь это скорость разворота персонажа, когда он не стоит на месте deltaTime *= _movingSens; // если поворот по анимации не требуется, то плавно меняем старый угол персонажа if (!isRot) oldRot.y = Mathf.LerpAngle(oldRot.y, _anim.transform.eulerAngles.y, deltaTime); //если поворот по анимации нужен, проверяем не повернут ли уже персонаж на цель else if (angleBetween > _luft) _isRotation = true; //возвращаем персонажа к старому углу поворота _anim.transform.eulerAngles = oldRot; //если персонаж на цель повернут, то ничего больше не делаем if (!_isRotation) return; //проверяем достаточно ли повернулись на цель, если так, то прекращаем разворот if (angleBetween * Mathf.Deg2Rad <= deltaTime) _isRotation = isRot = false; /включаем или выключаем анимацию разворота в аниматоре _anim.SetBool(_inputParams.IsRot, isRot); >Метод, который проверяет нажатия игрока, и на основе этих данных возвращает нужные значения для контроллера, который отвечает за перемещение игрока. Принимает время между кадрами, а возвращает нужный вектор для перемещения персонажа и координаты его цели
public void SetInputs(float deltaTime, out Vector3 moveInput, out Vector3 targetPos) < //угол наклона камеры относительно персонажа var angle = (_bodyTrans.localEulerAngles.y - _camera.transform.localEulerAngles.y) * Mathf.Deg2Rad; //косинус этого угла var cosAngle = Mathf.Cos(angle); //синус этого угла var sinAngle = Mathf.Sin(angle); //вертикальный инпут игрока var vertInput = Input.GetAxis(_moveVertInput); //горизонтальный инпут игрока var horInput = Input.GetAxis(_moveHorInput); //высчитанные данные для аниматора игрока var hor = cosAngle * horInput - sinAngle * vertInput; var vert = cosAngle * vertInput + sinAngle * horInput; //подставляем полученные данные _moveInput.x = hor; _moveInput.y = 0; _moveInput.z = vert; //находим точку, куда наведена мышка и считаем ее целью if (Physics.Raycast(_camera.ScreenPointToRay(Input.mousePosition), out var hit)) _targetPos = hit.point; moveInput = _moveInput; targetPos = _targetPos; >Это совсем легко, на персонаже висит два скрипта, один ловит нажатия на кнопки, а второй заставляет двигаться. Все просто.
Для того, чтобы персонаж не проходил сквозь стены, я использую NavMesh, если на персонаже висит агент, то он не сможет пройти туда, куда нельзя, но для этого надо запечь карту путей :)
Персонаж состоит из пары скриптов, аниматора и агента для поиска путиЯ очень давно использую именно этот скрипт для реализации перемещения в своих играх, если кому-то еще они пригодятся, то это было бы здорово.
Если такой формат кому-то интересен или что-то лучше поменять, то напишите в комментах - это позволит сделать более полезный контент в будущем, а если у вас есть какой-то конкретный вопрос, то пишите тоже, постараюсь разобрать :)
Отличный материал, особенно круто построчное комментирование — более чем полезно начинающим. Хвалю за вложенный труд!,)
Мне так же нравится, что персонаж правильно переступает при вращении. Не дай боги какая инновация, но всё чаще замечаю, что в ряде не самых дешёвых игр нет (или сделано так себе) и этого базового момента, особенно, у NPС такое встречается чаще.
Спасибо, могу показать как научить ботов так же чётко ходить по навмешу)
Было бы круто, я бы почитал и такую статью.
Так же первой мыслью была об необходимости научить ботов / персонажа игрока менять цель во время уже исполнения движения к точке. Полагаю, тут нужны дополнительные анимации разворота во время уже идущего бега/ходьбы, а так же пачка точек.
Просто, меня всегда раздражало, когда в играх не оказывается гибкости в этом аспекте. В конце концов в реал тайм игре многое может поменяться пока персонаж/NPC идут куда-то. Не только новая цель, но и новые вводные в мире игры — обнаруженная ловушка/враг на пути, закрывшаяся дверь или поднявшийся мост. Мало ли что,)
Не нужно анимаций больше, все можно сделать на основе этих ассетов, просто скрипт для бота написать. Я покажу немного попозже)
Вот бежит у тебя перц в одну сторону, надрывается, спешит, разогнался не на шутку, и тут ему резко становится нужно в обратную сторону. Он же не может мгновенно встать на место, развернуться и начать бежать в обратку? Это долго и это странно, так как не естественно. Так же он не может дать кругаля по широкой траектории, так как пространства для этого очень даже может не оказаться.
Лучшее решение — это анимация когда он отыгрывает анимацию резкого торможения и разворота. Причём разворот может быть как на 180, так и на, скажем, 90, а может и на промежуточное значение. И вот под такие случаи, имхо, всё же отдельный наборчик анимаций, в идеале, стоило бы заводить. Как это было, например, в старых Ассасинах.
Поймите правильно, я большой фанат продуманного и отзывчивого передвижения в играх. И поэтому это одна из двух основных моих болей в вышедшей недавно CP77. А жаль,(
А, ну если резкая смена направления, то конечно, но для большинства ботов этого хватает)
В этой статье мы пошагово разберем создание просто джойстика под Android, а также рассмотрим работу контроллера персонажа игры.
Подготовим сцену и элементы джойстика
Camera. Выбираем в сцене камеру, по умолчанию она названа Main Camera. И переключаем Clear Flags на Solid Color. Теперь камера будет отображать фон сплошным цветом заданным в поле Background.
Теперь займемся основой джойстика
Для основы используем элемент UI под названием Image. В нем нам важна возможность отрисовывать фон джойстика и компонент Rect Transform. Создадим Image и переименуем его Joystic. В Rect Transform устанавливаем значения width и height равными 300. Это высота и ширина картинки. Далее обратим внимание на компонент Image.
Далее нам потребуется еще один Image. Создадим его и назовем “Touch_marker”. Его мы используем для отображения точки нажатия на поле джойстика. На этом можно сказать что подготовка элементов джойстика закончена.
Написание управляющего скрипта
Следующая переменная, приватная типа Vector3. В ней мы будем хранить вектор направленный из центра джойстика в координаты касания экрана. Vector3 tаrget_vector;
Ниже идет метод Start, который вызывается при старте сцены. В него мы добавим строку, перемещающую touch_marker в центр гейм объекта, к которому прикреплен скрипт. Это на случай, если при сборке сцены touch_marker находится не в центре джойстика.
if (Input.GetMouseButton(0))
Проверяем. Если нажата левая кнопка мыши или произошло касание экрана, Модуль Unity Input позволяет для отслеживания touch использовать мышку. То есть Input.mouse будет работать и на телефоне. И если кнопка (касание) нажата (произошло), записываем координаты касания в локальную переменную Vector3.
Vector3 touch_pos = Input.mousePosition;
Чтобы полностью переключится на мобильные платформы, достаточно поменять Input.GetMouseButton(0) на Input.touchCount >0 и Input.mousePosition на Input.GetTouch (0).position, где 0 означает, что мы записываем координаты первого касания. Следующее что мы делаем, это получаем вектор направления.
Делается это путем вычитания одной точки в пространстве из координат другой, и получается вектор, который “выходит” из второй точки и “заканчивается” в первой. Так же модуль данного вектора равен расстоянию между двумя позициями. Чем мы и воспользуемся для ограничения зоны действия джойстика. if (tаrget_vector.magnitude < 100)
Ограничим радиус действия джойстика равным 100. Если расстояние между позицией джойстика и точки касания экрана больше этого значения, то target_marker перемещается в центр джойстика, если меньше, то в точку касания экрана.
Далее передаем вектор направления классу, управляющему персонажем: sg_controller.target_move = tаrget_vector;
Затем проверяем, если нет нажатий на экран, то возвращаем target_marker в центр джойстика и второй строкой делаем вектор направления в классе персонажа нулевым, останавливая движение.
sg_controller.target_move= new Vector3(0, 0, 0);
Полностью скрипт выглядит так:
На этом контроллер джойстика можно считать законченным.
Создаем персонажа и скрипт управления для него
Опять создаем Image. Переименовываем в Square_green и в компоненте Image, в поле Color, выбираем зеленый. Height и width в компоненте Rect Transform выставим 50.
В нем объявляем две переменные. Скорость движения. Ее мы зададим в инспекторе. public float speed;
Направление движения, которое передаст нам джойстик public Vector3 target_move;
В Update первым делом ограничиваем движение персонажа границами экрана. Для этого используем Mathf.Clamp. функцию ограничения значения, то есть заключение его в определённый диапазон. От нуля которых находится в нижнем левом углу до противоположной границе экрана.
transform.position = new Vector3( Mathf.Clamp(transform.position.x, 0, Screen.width), transform.position.y, transform.position.z);
transform.position = new Vector3(transform.position.x, Mathf.Clamp(transform.position.y,0, Screen.height), transform.position.z);
И зададим движение через Translate где вектор направления движения задает джойстик.
transform.Translate(target_move * speed * Time.deltaTime);
Полностью скрипт выглядит так:
Контроллер персонажа готов. Осталось назначить скрипты гейм объектам и скриптам некоторые переменные.
Скрипт Joystic_controller мы назначаем Joystic. А Square_green_controller назначаем Square_green. Теперь в компоненте Joystic_controller мы назначим путем простого перетаскивания из окна иерархии: в поле touch_marker назначим Touch_marker, а в поле sg_controller назначим Square_green.
И конечно не забудем Square_green >Square_green_controller выставить значение Speed. Например 5.
Опишем процесс сборки проекта под платформу Android, которая поддерживается большинством смартфонов.
Для начала сборки необходимо открыть окно Build Settings из пункта меню File -> Build Settings… (или нажать комбинацию клавиш Ctrl + Shift + B):
Окно Build Settings
Шаг 1. Выбор платформы
В окне Build Settings выбрать платформу Android и нажать на кнопку Switch Platform:
Выбор платформы Android
Шаг 2. Проверить и отредактировать (если надо) настройки в в окне Project Settings, в разделе Player:
1) заполнить поля:
Company Name (писать по-английски и лучше без знаков препинания и пробелов),
Product Name (аналогично – по-английски и без специальных символов и пробелов),
Version (можно оставить значение по умолчанию, но если приложение собирается повторно, то значение надо менять на большее; тогда при установке новой версии приложения на смартфон существующее приложение обновится. Если это число оставить прежним, потребуется сначала удалить установленное ранее приложение).
2) задать изображение для иконки приложения, добавив его в Default Icon.
3) если необходимо, в разделе Resolution and Presentation можно зафиксировать ориентацию приложения: горизонтальное (Landscape) или вертикальное (Portrait):
Ориентация приложения
4) в разделе Other Settings проверить правильность сформированного идентификатора в поле Package Name:
Это минимальный набор настроек, которые стоит отредактировать. После этого окно Project Settings можно закрыть.
Шаг 3. Перечислить в окне Build Settings сцены, которые должны попасть в сборку (build) проекта:
Если сцена всего одна, и она открыта в редакторе, можно просто нажать на кнопку Add Open Scenes.
Дополнительные сцены можно перетащить мышью из нужной папки окна Project.
Если в окне Scenes in Build указана не та сцена, её можно выделить мышью и удалить, нажав на клавишу Delete на клавиатуре компьютера.
Шаг 4. Нажать на кнопку Build в правом нижнем углу окна Build Settings, указать папку и имя собираемого файла в формате .apk и нажать на кнопку Сохранить (Save):
Собранный файл .apk переписать на смартфон, открыть его на смартфоне и установить приложение. После этого можно начать тестировать свою мобильную игру или приложение на смартфоне.
Гироскоп дает разработчикам широкий спектр интересных вариаций управления. Но когда доходит дело до его интеграции и реализации контроллера камеры, просходит много странных и волшебных вещей. Разработчики имеют дело с различными ориентациями устройств, исходными позициями камеры и кватернионами. Я буду рад поделиться своим опытом и описать, как мы решили эти проблемы. Будем надеяться, что это позволит сэкономить усилия и нервы тех, которые работают с гироскопом впервые.
В моем примере я хочу показать, как реализовать следующие функции контроллера камеры:
1. Экран устройства должен работать как "окно" в виртуальный мир. Поворачивая девайс, пользователь должен осматривать этот мир.
2. Контроллер должен поддерживать функцию автоповорота и поддерживать все ориентации устройства.
3. Вы сможете отделить контроллер от камеры, изменить ее вращение и положение из кода (например проиграть анимацию камеры), а затем прикрепить контроллер обратно без заметных заминок.
4. Верхняя ось виртуального мира будет соотвествовать верхней оси в реальном мире и будет осуществлена рекалибровка горизонтального вращения.
Первая проблема с которой мы столкнемся это разный тип систем координат, использующихся в iOS устройствах и Unity3d: левосторонняя и правосторонняя. Для того чтобы преобразовать кватернионы из одной системы в другую, используем следующую функцию:
Следующая строка рассчитывает вращение камеры:
Следующая проблема - поддержка различных ориентаций устройства (была решена разработчиками Unity в четвертой версии). Фиксируем кватернионы для каждой ориентации:
Далее - обновленная функция для расчета вращения камеры:
Контроллер должен обновлять вращение камеры, ссылаясь на базовое вращение девайса и сбрасывать базовое вращение в горизонтальную плоскость. Это означает, что если пользователь запускает приложение, указывая устройством на север, игровая камера будет поворачиватся относительно "виртуального" севера. Камера будет показывать данное направление. Ниже приводится новая функция, которая принимает во внимание базовое вращение камеры и устройства.
Расчет cameraBase и referanceRotation немного сложнее. Два основных препятствия:
1. При расчет referenceRotation нужно принять во внимание текущую ориентацию устройства.
2. При расчете cameraBase нужно принять во внимание только вращение вокруг верхней оси.
Точные функции вы найдете в прикрепленном примере.
И последнее, сглаживание вращения камеры:
Теперь добавим две функции: AttachGyro и DetachGyro. Первая позволит включить контроллер и пересчитать все базовые повороты. Вторая - отключает контроллер.
Читайте также: