Как я могу запустить GameObject на цели, если мне дают все, кроме угла его запуска?

11

Я пытаюсь запустить объект по цели, учитывая его положение, положение цели, скорость запуска и гравитацию. Я следую этой формуле из Википедии :

θзнак равноaрсTaN(v2±v4-г(гИкс2+2Yv2)гИкс)

Я максимально упростил код, но до сих пор не могу последовательно достичь цели. Я рассматриваю только более высокую траекторию из двух, доступных из + - выбора в формуле.

Кто-нибудь знает, что я делаю не так?

using UnityEngine;

public class Launcher : MonoBehaviour
{
    public float speed = 10.0f;

    void Start()
    {
        Launch(GameObject.Find("Target").transform);
    }

    public void Launch(Transform target)
    {
        float angle = GetAngle(transform.position, target.position, speed, -Physics2D.gravity.y);
        var forceToAdd = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * speed;
        GetComponent<Rigidbody2D>().AddForce(forceToAdd, ForceMode2D.Impulse);
    }

    private float GetAngle(Vector2 origin, Vector2 destination, float speed, float gravity)
    {
        float angle = 0.0f;

        //Labeling variables to match formula
        float x = Mathf.Abs(destination.x - origin.x);
        float y = Mathf.Abs(destination.y - origin.y);
        float v = speed;
        float g = gravity;

        //Formula seen above
        float valueToBeSquareRooted = Mathf.Pow(v, 4) - g * (g * Mathf.Pow(x, 2) + 2 * y * Mathf.Pow(v, 2));
        if (valueToBeSquareRooted >= 0)
        {
            angle = Mathf.Atan((Mathf.Pow(v, 2) + Mathf.Sqrt(valueToBeSquareRooted)) / g * x);
        }
        else
        {
            //Destination is out of range
        }

        return angle;
    }
}
Evorlor
источник
Две вещи выделяются для меня. -Physics2D.gravity.y и angle = Mathf.Atan ((Mathf.Pow (v, 2) + Mathf.Sqrt (valueToBeSquareRooted)) / g * x); формула ожидает, что гравитация будет иметь положительное значение, например 9,81 второй знаменатель gx, то, как он у вас есть, вы делите на g, затем умножаете время x, у вас должен быть знаменатель (g * x), чтобы умножение происходило до деления.
Майк Уайт

Ответы:

14

Я немного скептически отношусь к использованию atanздесь, потому что отношение тангенсов отходит к бесконечности под определенными углами и может привести к ошибкам чисел (даже за пределами неопределенного случая / деления на ноль для стрельбы прямо вверх / вниз).

Используя формулы, разработанные в этом ответе , мы можем параметризовать это с точки зрения (первоначально неизвестного) времени воздействия T, используя начальную speedчасть снаряда:

// assuming x, y are the horizontal & vertical offsets from source to target,
// and g is the (positive) gravitational acceleration downwards
// and speed is the (maximum) launch speed of the projectile...

b = speed*speed - y * g
discriminant = b*b - g*g * (x*x + y*y)

if(discriminant < 0)
  return CANNOT_REACH_TARGET; // Out of range, need higher shot velocity.

discRoot = sqrt(discriminant);

// Impact time for the most direct shot that hits.
T_min = sqrt((b - discRoot) * 2 / (g * g));

// Impact time for the highest shot that hits.
T_max = sqrt((b + discRoot) * 2 / (g * g));

Вы можете выбрать либо T_min, либо T_max (или что-то промежуточное, если вы хотите стрелять со скоростями до, но не обязательно равными некоторому максимуму)

Пример траектории

( T_minэто мелкая красная траектория внизу и T_maxвысокая зеленая траектория. Любая траектория между ними жизнеспособна с некоторой возможной скоростью. Когда эти две сливаются в желтую траекторию, объект находится вне диапазона.)

Теперь, когда мы вычислили значение для T, все остальное просто:

vx = x/T;
vy = y/T + T*g/2;

velocity = (vx, vy);

Вы можете использовать эту скорость напрямую (она имеет длину, равную speedконструкции), или если вам действительно нужно знать угол, вы можете использоватьatan2(vy, vx)


Изменить: чтобы сделать это применимо к большему количеству случаев, вот 3D-версия:

Vector3 toTarget = target.position - transform.position;

// Set up the terms we need to solve the quadratic equations.
float gSquared = Physics.gravity.sqrMagnitude;
float b = speed * speed + Vector3.Dot(toTarget, Physics.gravity);    
float discriminant = b * b - gSquared * toTarget.sqrMagnitude;

// Check whether the target is reachable at max speed or less.
if(discriminant < 0) {
    // Target is too far away to hit at this speed.
    // Abort, or fire at max speed in its general direction?
}

float discRoot = Mathf.Sqrt(discriminant);

// Highest shot with the given max speed:
float T_max = Mathf.Sqrt((b + discRoot) * 2f / gSquared);

// Most direct shot with the given max speed:
float T_min = Mathf.Sqrt((b - discRoot) * 2f / gSquared);

// Lowest-speed arc available:
float T_lowEnergy = Mathf.Sqrt(Mathf.Sqrt(toTarget.sqrMagnitude * 4f/gSquared));

float T = // choose T_max, T_min, or some T in-between like T_lowEnergy

// Convert from time-to-hit to a launch velocity:
Vector3 velocity = toTarget / T - Physics.gravity * T / 2f;

// Apply the calculated velocity (do not use force, acceleration, or impulse modes)
projectileBody.AddForce(velocity, ForceMode.VelocityChange);
Д.М.Григорий
источник
Да, я нашел решения, используя время как известное, но я хочу, чтобы сила была известной.
Evorlor
1
Да, Jost Petrie и @DMGregory - это прибыль на этом форуме. :) нет сомнений
Хамза Хасан
1
Оу, чёрт, спасибо вам обоим! :) @Evorlor discRoot- это квадратный корень дискриминанта , который является частью, которая появляется под знаком квадратного корня в квадратной формуле . bв -1 раз превышает переменную b в квадратной формуле. К сожалению, я не знаю более описательного названия для этого. (Я умножил его на -1 при назначении на более поздние шаги, так как ведущий минус уже запечен и не влияет на квадрат). Смотрите другой ответ для полного вывода, хотя пара квадратов отсутствует (будет исправлено в ближайшее время)
DMGregory
1
Что представляют сине-желтая кривая?
Слипп Д. Томпсон
3
@ SlippD. Thompson желтая кривая - самая эффективная траектория (наименьшая необходимая скорость запуска), а синяя кривая - самая высокая траектория в пределах фиксированного потолка (полезно, если вам нужно избегать границ игрового поля или дуг за пределами экрана). Уравнения для этих значений времени находятся в связанном ответе
DMGregory
3

Благодаря DMGregory у меня теперь есть скрипт расширения C #, который можно использовать для этого. Самую последнюю версию можно найти на GitHub .

using UnityEngine;

public static class Rigidbody2DExtensions
{
    /// <summary>
    /// Applies the force to the Rigidbody2D such that it will land, if unobstructed, at the target position.  The arch [0, 1] determines the percent of arch to provide between the minimum and maximum arch.  If target is out of range, it will fail to launch and return false; otherwise, it will launch and return true.  This only takes the Y gravity into account, and X gravity will not affect the trajectory.
    /// </summary>
    public static bool SetTrajectory(this Rigidbody2D rigidbody2D, Vector2 target, float force, float arch = 0.5f)
    {
        Mathf.Clamp(arch, 0, 1);
        var origin = rigidbody2D.position;
        float x = target.x - origin.x;
        float y = target.y - origin.y;
        float gravity = -Physics2D.gravity.y;
        float b = force * force - y * gravity;
        float discriminant = b * b - gravity * gravity * (x * x + y * y);
        if (discriminant < 0)
        {
            return false;
        }
        float discriminantSquareRoot = Mathf.Sqrt(discriminant);
        float minTime = Mathf.Sqrt((b - discriminantSquareRoot) * 2) / Mathf.Abs(gravity);
        float maxTime = Mathf.Sqrt((b + discriminantSquareRoot) * 2) / Mathf.Abs(gravity);
        float time = (maxTime - minTime) * arch + minTime;
        float vx = x / time;
        float vy = y / time + time * gravity / 2;
        var trajectory = new Vector2(vx, vy);
        rigidbody2D.AddForce(trajectory, ForceMode2D.Impulse);
        return true;
    }
}
Evorlor
источник
-6

Лично я бы даже не стал использовать какую-либо сложную формулу.

GetComponent<Rigidbody2D>.AddForce((target.transform.position - transform.position) * someSortOfMultiplier());

Это просто стреляет в направлении цели. И если вы хотите компенсировать гравитацию, расстояние и т. Д., Установите someSortOfMultiplier()функцию, которая возвращает число с плавающей запятой, которое будет компенсировать при умножении на приведенный выше код.

jonathanhuo11
источник