Введение


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

Теория


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

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

  • координаты начала вектора;
  • длина вектора;
  • угол наклона к осям координат.

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

$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);

	}

Заключение


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

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

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

Комментарии (6)


  1. rumyancevpavel
    30.01.2018 18:59
    +1

    Как ни старался, ничего не понял. Ни какую задачу пытается решить автор, ни то, зачем нужна вся история с осями и четвертями. Vector3.RotateTowards() не подойдёт для этого?


    1. Tutanhomon
      30.01.2018 19:10

      Еще есть замечательный Vector3.Cross, благодаря которому можно получить вектор, который заставляет один вектор (например, transform.forward), стремиться выровняться с другим вектором, (например, Direction, который вычисляется вычитаением вектора позиции цели из вектора позиции игрока, либо локально, для текущей и текущей + 1 точками пути).


    1. antiLOL Автор
      31.01.2018 14:29

      Не подойдет. Если отойти от математики, то в Lerp нужно два экземляра Vector3, один неизвестен, RotateTowards принимает 2 Vector3, один неизвестен.
      Изначально цель — найти точку, грубо говоря второй Vector3, если цель известна, то смысл распаляться при уже готовых методах Unity?

      Ну а Cross это вообще векторное произведение, я не знаю к чему оно здесь, но может так и надо.


  1. Tutanhomon
    31.01.2018 15:17

    Даже не поленился скачать проект. Так и не нашел применения там всей этой математике и бессмысленным IFам
    Вся реализация огранчивается transform.Translate и transform.Rotate. эти два метода в две строчки кода — все что нужно для реализации поведения, которое я увидел в проекте.
    Не берусь устверждать что в статье что-то не так, говорю лишь, что я вообще ничего не понял.


    1. antiLOL Автор
      31.01.2018 17:07

      Так, на примере. Образовательная игра для детей для обучения алгоритмированию. Ребенок пишет объекту: «Повернись на 20 и пройди вперед на 50». Что значит вперед на 50? Translate задает направление движения, а не конечную точку. В проекте пишется транслятор для драйвера двигателя. Тут мой косяк, я не предусмотрел руководство юзеров, добавлю вечером.


      1. Tutanhomon
        31.01.2018 17:42

        Ну для робота как раз все равно, что значит вперед на 50. Подаете питание на двигатель и он еде в новом локальном «прямо». что значит вперед на 50? 50 это скорость на время, опять не понимаю к чему вся алгебра.
        Но и в юнити то же самое. Если повенули трансформ, то и его «прямо» тоже повернулось.
        Про конечную точку.

        var targetPoint = transfrom.position + transform.forward * distance;

        Вот вам и конечная точка.
        осталость только лерпать:
        var start = transform.position;
        var end = targetPoint;
        // а теперь в корутине
        transform.position = Vector3.Lerp(start, end, time);
        time += Time.deltaTime;