Работа с движением снаряда в Unity

Один из самых захватывающих аспектов работы консультантом - это то, что можно закончить всевозможными интересными проектами. Начав как веб-разработчик, я никогда не думал, что у меня будет возможность использовать игровой движок Unity на работе, но время показало, что я ошибался. Когда я начал увлекаться миром 3D, я обнаружил несколько совершенно новых аспектов программирования. Впервые я действительно увидел, как мир геометрии и физики разыгрывается в коде.

Этот опыт вдохновил меня на попытку привести пример проблемы кодирования, с которой можно столкнуться при работе с объектами в трехмерном пространстве. Я также попытаюсь описать, как я подошел к этим проблемам и попытался найти для них красивое решение.

Создание движения

В Unity элементы сцены представлены игровыми объектами. Например, декорации, игроки и предметы создаются как игровые объекты с прикрепленными к ним скриптами. Эти сценарии используются для управления поведением и свойствами объектов. Преобразование объекта используется для доступа и изменения положения и поворота объектов. Изменение преобразования в цикле обновления также является способом перемещения объекта.

// Example 1:
transform.Translate(x, y, z);
transform.Rotate(xAngle, yAngle, zAngle);

Можно указать значения с плавающей запятой как x, y и z или использовать Vector3 для определения новой позиции преобразования и поворота. Обычно движение определяется в Update () или LateUpdate () скрипта MonoBehaviour Game Object. В примере 2 мы используем положение игрового объекта игрока и смещение, чтобы установить положение игрового объекта, которым мы управляем. Это можно использовать, например, для того, чтобы камера следовала за игроком по сцене.

// Example 2:
void LateUpdate(){
transform.position = player.transform.position + offset;
}

Другие способы перемещения игрового объекта - использовать Lerp или Slerp. Lerp означает линейную интерполяцию между двумя векторами и позволяет нам, например, перемещать определенную часть расстояния от начальной до конечной позиции. Пример 3 показывает пример этого.

// Example 3:
    
public Transform startPoint;
public Transform endPoint;

// Speed of lemon in units/sec
public float speed = 1.0F;
// Time when the lemon was thrown
private float startTime;

// Total distance of the flight
private float flightDistance;

void Start()
{
    // Keep a note of the time the lemon was thrown
    startTime = Time.time;

    // Calculate the length of the flight
    flightDistance = Vector3.Distance(startMarker.position, endMarker.position);
}

void Update()
{
    // Distance flied = time * speed
    float distanceFlied = (Time.time - startTime) * speed;

    // Fraction of flight completed
    float flightFraction = distanceFlied / flightDistance;

    // Update the lemons position
    transform.position = Vector3.Lerp(startPoint.position, endPoint.position, flightFraction);
    }
}

Разница между Slerp и Lerp в том, что в Slerp точки рассматриваются как направления, а не как точки в пространстве. Также можно использовать сопрограммы для смещения положения преобразований и создания движения.

Снаряды в 2D

Хотя многие инструменты, предлагаемые библиотеками Unitys, упрощают разработку, иногда возникает проблема, которая, по-видимому, не имеет готовой функциональности. Одним из таких камней преткновения может быть проблема расчета движения объекта, брошенного в воздух. Мы знаем, что брошенный объект будет двигаться по кривой в зависимости от угла и скорости, с которой он запущен. Если мы хотим переместить игровой объект таким образом, нам нужна более конкретная информация о том, какие значения x и y преобразования Game Objects должны быть установлены в каждый момент времени.

Самый простой способ справиться с двумерным движением снаряда - это рассматривать объекты с горизонтальной и вертикальной скоростями отдельно. Когда мы движемся по кривой движения, горизонтальная скорость остается постоянной, тогда как вертикальная скорость изменяется. В верхней части параболы вертикальная скорость равна 0. Таким образом, мы упрощаем эту двумерную задачу до двух более простых одномерных задач.

Решение для горизонтальной скорости легко, поскольку (если не принимать во внимание объем и сопротивление воздуха) горизонтальная скорость остается постоянной. Таким образом, изменение горизонтального положения равно скорости, умноженной на изменение во времени. Для упрощения я использую v для описания горизонтальной скорости.

Δx = vt

Использование четырех кинематических формул

Решение относительно вертикальной скорости намного сложнее. Находясь в воздухе, наш снаряд испытывает притяжение силы тяжести при массе, умноженной на 9,8 м / с², что дает ему ускорение вниз на 9,8 м над своей массой.

a = F/m

Вертикальные переменные могут быть включены в четыре кинематические формулы для определения вертикальной скорости в данный момент. Здесь я использую v для описания вертикальной скорости.

Кодирование

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

// Example 4: 
float initialYVelocity = initialVelocity*Math.Cos(angle);
float initialXVelocity = initialVelocity*Math.Sin(angle);
var Yvalues = new float[];
var landed = false;
var Yacceleration = -9.81;
var time = 0;
Yvalues[0] = 0f;

while (!landed) {
    var deltaY = initialYVelocity*t + 1/2*acceleration*time^2;
    Yvalues.append(Yvalues[Yvalues.count-1] + deltaY)
    if (deltaY =< 0) {
        landed = true
    }
    time++;
}

Это создает массив значений для Y, содержащий вертикальное смещение за каждую секунду. Горизонтальное смещение просто рассчитывается по формуле

// Example 5:
var currentX = initialXVelocity*seconds;

Альтернативные решения

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

Как синусоида поможет нам создать движение лимона, брошенного в воздух? В 16 веке Галилео Галилей обнаружил, что кривая, по которой снаряд движется по воздуху, действительно является параболой. Благодаря этому можно использовать синусоидальную кривую для вычисления позиций в заданное время, как в следующем примере Python.

// Example 6:
original_y = 0
max_y = 250
distance_x = 1000
x_projectile = np.arange(distance_x)
y_projectile = np.sin(np.pi*x_projectile/distance_x)

Этот синусоидальный подход, несомненно, менее научен, но в некоторых случаях он служит нашим целям лучше, чем полное физическое решение.

Перемещение по позициям

После того, как у нас есть положения x и y в заданные моменты времени, мы можем легко перемещаться по кривой, которую они образуют,

// Example 7:
void Update(){
    // If there are no more waypoints, don't do anything
    if (YValues.length < 1)
        break;
    // Update the lemons position to the next waypoint
    transform.position = new Vector3(XValues[0], YValues[0],    transform.position.z);    
    // Delete first items in arrays    
    XValues = arr.Skip(1).ToArray();    
    YValues = arr.Skip(1).ToArray();
   }
}

Это даст нам более или менее неуклюжее движение по нашей кривой. Чтобы сделать движение более плавным, увеличьте количество путевых точек. Скорость теперь также зависит от количества путевых точек (одна путевая точка на единицу). С этим можно было поиграться в обновлении.

Ресурсы

Физика Ханской Академии

Документация Unity

(… И мои классные коллеги)