Движение объекта к точке в Unity3D. Управление игровыми объектами (GameObjects) с помощью компонентов

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

Теория

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

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

  • координаты начала вектора;
  • длина вектора;
  • угол наклона к осям координат.
Предположим, что все это мы знаем. Тогда задача сводится к простейшим формулам теоремы синусов.

$inline$x/sin(α) = a/sin (90)$inline$
$inline$ x = a* sin(α)$inline$
$inline$y/sin (90 - α) = a/sin(90)$inline$
$inline$ y = a * sin (90 - α)$inline$

Где a - длина вектора, α - угол наклона к оси координат

Собственно, этих знаний нам пока достаточно для решения задачи на практике.

Практика

Итак, нам известно:
  • где фронтальная и тыльная сторона объекта;
  • текущее положение объекта;
  • угол, на который повернулся объект.
  • расстояние, которое должен преодолеть объект.
Почти вся информация у нас есть, но согласно нашему случаю, нам численно известен угол поворота объекта, но неизвестно относительно какой оси он повернулся. Необходимы дополнительные данные. Тогда вводим понятие четверти. Ни для кого не секрет, что в двумерной декартовой системе координат существуют 4 четверти, с 1 до 4 соответственно. В каждой четверти оси имеют разные знаки.

И в unity это тоже работает. Для начала, нам нужно определить четверти в сцене. Создаем куб в начале координат, перемещаем его и смотрим, какие координаты отрицательные, а какие положительные. В примере видно, что обе координаты отрицательные, значит куб находится в третьей четверти.

Теперь можно приступать непосредственно к скрипту. На вход мы принимаем Transform исходного объекта после поворота и Transform пустышки, к которой в последствии будем двигаться. Пустышка изначально имеет координаты объекта. Далее определяем четверть, в которой находится фронт объекта. Так как тригонометрическая окружность ограничена от 0 до 360 градусов, то труда это не составляет. Определив четверть, вычисляем углы наклона для каждой координаты. Потом делаем проверку, чтобы наши углы имели правильный знак. После этого отправляем углы наклона в конечную формулу вычисления координат. И наконец, задаем новые координаты пустышке, к которой будем интерполировать.

Void ChekingQuarterUp(Transform vectorAngle, ref Transform empty) { float zangle = 0; float xangle= 0; float zcoord = 0.0f; float xcoord = 0.0f; int normangle = Mathf.RoundToInt (vectorAngle.eulerAngles.y); if (normangle >= 0 && normangle <= 90) // 1-ая четверть { zangle = 90 - normangle; xangle = 0 - normangle; xangle = (xangle < 0) ? xangle * -1: xangle; zangle = (zangle < 0) ? zangle * -1: zangle; } if (normangle > 270 && normangle <= 360) // 2-ая четверть { xangle = 360 - normangle; zangle = 270 - normangle; xangle = (xangle > 0) ? xangle * -1: xangle; zangle = (zangle < 0) ? zangle * -1: zangle; } if (normangle > 180 && normangle <= 270) // 3-ая четверть { xangle = 180 - normangle; zangle = 270 - normangle; xangle = (xangle > 0) ? xangle * -1: xangle; zangle = (zangle > 0) ? zangle * -1: zangle; } if (normangle > 90 && normangle <= 180) // 4-ая четверть { zangle = 90 - normangle; xangle = 180 - normangle; zangle = (zangle > 0) ? zangle * -1: zangle; xangle = (xangle < 0) ? xangle * -1: xangle; } zcoord = path * Mathf.Sin (zangle *Mathf.PI/180); xcoord = path * Mathf.Sin (xangle * Mathf.PI/180); float newpathx = empty.position.x + xcoord; float newpathz = empty.position.z + zcoord; empty.position = new Vector3 (newpathx, empty.transform.position.y, newpathz); }

Заключение

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

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

Пример реализации метода можно взять

Всем привет, сегодня покажу как двигать персонаж в сторону курсора, например как в игре Diablo 2. В примере будет использоваться Unity3D 2.6. Сам урок очень старый, и делался для создания серии уроков по созданию РПГ (для другого ресурса, но ресурс накрылся медным тазом, по этому для пользователей gcup это будет более полезно, да и некоторые писали мне в лс "как двигать персонаж в сторону курсора"), но я не смог тягаться c BugZerg по этому забросил проект. Ну что, начнем.
Первое – запускаем Unity3D. Выбираем в окне «Project Wizard» где будем хранит проект, и выбираем импортируемый ассет «Standart Assets.unityPackage».
Ждем пока импортируется ассет. Это может занять некоторое время.
Импорт закончен? Это хорошо, давайте продолжим дальше. О, и не стоит пугается странного интерфейса, это не Blender.
Давайте сначала добавим пол для нашего уровня. Заходим в меню «Game Object», далее в «Create Other» и выбираем в выпавшем меню «Cube». В вкладке «Inspector» настройте объект как на изображение:


Импровизированный пол для нашего уровня готов.
Теперь добавим персонажа. Заходим в меню «Game Object», далее в «Create Other» и выбираем в выпавшем меню «Capsule». В вкладке «Inspector» настройте объект как на изображении:


Вау, главный персонаж готов. Но чего-то не хватает, кажется что не хватает источника света. Давайте решим эту проблему. Заходим снова в меню «Game Object», далее в «Create Other» и выбираем в выпавшем меню «Point Light». В вкладке «Inspector» настройте объект как на изображении:


Ну а теперь займемся камерой. Выбираем камеру «Main Camera» во вкладке «Hierarchy», и заходим в меню «Component», далее в «Camera Control» и выбираем «Smooth Follow». Вы теперь можете увидеть, что изменилась вкладка «Inspector», не переживайте все это хорошо. Теперь нам надо выполнить одно из самых трудных действий для новичков. Выберите «Main Camera» в вкладке «Hierarchy», теперь методом Drag’n’Drop попробуйте перенести из вкладки «Hierarchy» объект «Capsule» в вкладку «Inspector» на панель «Smooth Follow (Script)» на свойство «Target», там где написано «None».
Ну а теперь создадим скрипт для перемещения главного персонажа. Перемещать будем следующим способом – персонаж находится в центре экрана, двигается в сторону курсора, когда нажата правая кнопка мыши. Ну а теперь про то, как мы будем поворачивать персонажа в сторону курсора. Помните Я говорил что использую решение похожее на хитрозакрученую жопу? Щас вы это увидите. Так как игрок всегда находится в центре экрана, то мы найдем сначала расстояние по оси Х от центра до курсора, далее найдем расстояние от центра до курсора но уже по оси Y. В итоге мы имеем два катета, и мы можем спокойно найти угол между катетом и гипотенузой по формуле - Угол = Арктангенс(Растояние по оси У, Растояние по оси Х). Может плохо объяснил, но вот графическое объяснение:


Хотя оно тоже может быть кривым, и вы ничего не поймете, по крайней мере, те кому я показывал рисунок - нифига не поняли.
Давайте уже кодить.
Первое, во вкладке «Project» кликнем на кнопку «Create» и выберем «Folder», переименуем ее на «Scripts» клавишей «F2». Теперь опять кликаем на кнопку «Create» и выберем «JavaScript», в окне «Project» появится скрипт «NewBehaviourScript», переименуйте его на «Game_Player_Script» клавишей F2. В итоге должна быть следующая структура:


Двойным кликом на «Game_Player_Script» открываем его, удаляем все что там есть и вставляем следующий код:

200?"200px":""+(this.scrollHeight+5)+"px");">
public var speed: int = 10;

public var gravity: int = 1;

Function Update () {
if(play){
if(Input.GetKey(KeyCode.Escape)){
Application.Quit();
}

If(grounded){
moveDirection = transform.TransformDirection(Vector3.forward);
moveDirection *= speed;
}
moveDirection.y -= gravity * Time.deltaTime;
if(Input.GetMouseButton(1)){




}
}
}



Теперь перетягиваем «Game_Player_Script» на «Capsule», который находится на вкладке «Hierarchy». Теперь выбираем «Capsule» и заходим в меню «Component», далее в «Physics» и выбираем «Character Controller».
Вот и все, все готово, жмем на кнопку «Play» и смотрим на результат наших трудов.
Ну а теперь про сам код:

200?"200px":""+(this.scrollHeight+5)+"px");">public var play:boolean = true;
public var speed: int = 10;
public var moveDirection: Vector3;
public var grounded: boolean;
public var gravity: int = 1;


В переменной play хранится флаг состояния игры, в speed – скорость перемещения, в moveDirection – направление движения, grounded – флаг по которому определяем на плоскости персонаж или нет, gravity – гравитация.

200?"200px":""+(this.scrollHeight+5)+"px");">function Update () {


Это стандартная функция которая будет вызыватся каждый раз когда происходит обновление сцены

200?"200px":""+(this.scrollHeight+5)+"px");">if(Input.GetMouseButton(1)){
var dx: int = Input.mousePosition.x - Screen.width / 2.0;
var dy: int = Input.mousePosition.y - Screen.height / 2.0;
var strawRadians: float = Mathf.Atan2(dx,dy);
var strawDigrees:float = 360.0 * strawRadians/(2.0*Mathf.PI);
transform.rotation.eulerAngles.y = strawDigrees;


Ну а тут проверяется, нажата ли правая кнопка мыши, если да то, рассчитываем угол поворота.

200?"200px":""+(this.scrollHeight+5)+"px");">var controller: CharacterController = GetComponent(CharacterController);
var flags = controller.Move(moveDirection * Time.deltaTime);
grounded = (flags & CollisionFlags.CollidedBelow) != 0;


Эта самая трудная часть кода. Тут мы получаем ссылку на компонент «Charactrer Controller», который и управляет персонажем. Его методом «Move» двигаем персонажа, а рассчитываем флаг «grounded».
Ну вот и все. Как видно игрок двигается в сторону мыши прям как в игре Diablo.
Всем пока, до встречи. Удачи в геймдеве. Итак. Всем привет. И сегодня я расскажу, как сделать простое движение персонажа. Сейчас только от третьего лица... Приступим...
Начнём, пожалуй, с создания персоажа. У меня это будет куб. Кто не знает, как создавать кубы или круги, поясняю - "GameObject" => "CreateOther" => "Cube". Создаём таким же образом камеру и привязываем к кубу (то бишь просто в иерархии перетаскиваем камеру на куб).
Так... Теперь создадим поверхность, по которой персонаж будет ходить. Пусть это будет просто "Plane". Ах, да... В конце урока будет ссылка с исходником по туториалу для тех, кто не понял.
Итак. Теперь создадим скрипт "Move". Добавим переменную игрока и переменную скорости.

Public GameObject player;
public int speed = 5;


Теперь укажем в методе старта, что это объект, на котором висит скрипт.

Void Start () {
player = (GameObject)this.gameObject;
}


Теперь сделаем само передвижение игрока вперёд при нажатии на "W" или стрелку вверх. Это делаем в методе void Update()! Для этого мы будем прибавлять позицию. Например вперёд.

If (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow))
{
}


Мы прибавили позицию вперёд (forward) и умножили на скорость, а точнее её переменную. И обязательно надо умножить на кадры в секунду (deltaTime).
Таким же образом сделаем движение назад. Только будем отнимать позицию.

If (Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.DownArrow))
{
player.transform.position -= player.transform.forward * speed * Time.deltaTime;
}


Таким же образом можем сделать и вправо и влево (right, left), но я сделаю просто поворот игрока, при нажатии на "A" или "D".
Я буду использовать "Rotate()". Чтобы поворачивать по оси "Y", я буду использовать "up" и "down". Кстати, для этого ещё надо объявить переменную "public int speedRotation = 3". И пишем в условиях.

If (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow))
{
player.transform.Rotate(Vector3.down * speedRotation);
}
if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow))
{
player.transform.Rotate(Vector3.up * speedRotation);
}


Ну... Сейчас пришло время анимировать. Я записываю анимацию в самой юнити. Это можно открыть в "Window" => "Animation". В этом окне мы можем анимировать куб. Итак... Пропустим момент создания анимации. Давайте теперь создадим переменную анимации.

Public AnimationClip anima;


Теперь в старте добавим клип.

Animation.AddClip(anima, "animCube");


Теперь мы будем его воспроизводить через "CrossFade". Воспроизводить буду в условиях ходьбы вперёд и назад. Чтобы воспроизвести, нужно написать.



Итак... У нас получился хороший код. Сейчас мы сделаем прыжок. Всё так же просто. Опять мы будем прибавлять позицию. Только вверх (up).
И так же с новой переменной анимации "public AnimationClip anima2;"? так же добавим и переменной "public int jumpSpeed = 50;". И мы получаем условие.

If (Input.GetKeyDown(KeyCode.Space))
{
player.transform.position += player.transform.up * jumpSpeed * Time.deltaTime;
}


Всё... Наш код готов.

using UnityEngine;
using System.Collections;
public class Move: MonoBehaviour {
public GameObject player;
public int speedRotation = 3;
public int speed = 5;
public AnimationClip anima;
public int jumpSpeed = 50;

Void Start () {
player = (GameObject)this.gameObject;
animation.AddClip(anima, "animCube");
}
void Update(){
if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow))
{
player.transform.position += player.transform.forward * speed * Time.deltaTime;
animation.CrossFade("animCube");
}
if (Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.DownArrow))
{
player.transform.position -= player.transform.forward * speed * Time.deltaTime;
}
if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow))
{
player.transform.Rotate(Vector3.down * speedRotation);
}
if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow))
{
player.transform.Rotate(Vector3.up * speedRotation);
}
if (Input.GetKeyDown(KeyCode.Space))
{
player.transform.position += player.transform.up * jumpSpeed * Time.deltaTime;
}