Juego en 2d :fuego a un objetivo en movimiento mediante la predicción de la intersección de proyectil y de la unidad de

StackOverflow https://stackoverflow.com/questions/2248876

  •  20-09-2019
  •  | 
  •  

Pregunta

Bueno, todo esto tiene lugar en un bonito y simple en 2D mundo...:)

Supongamos que tengo un objeto estático a en la posición Opa, y un linealmente objeto en movimiento B en Bpos con bVelocity, y una munición ronda con velocidad de Avelocity...

¿Cómo puedo averiguar el ángulo que tiene que disparar, golpear B, teniendo en cuenta B de la velocidad lineal y la velocidad de la munición ?

Ahora el objetivo en la posición actual del objeto, lo que significa que por el momento mi proyectil llega la unidad ha pasado a posiciones más seguras :)

¿Fue útil?

Solución

Primero, girar los ejes de modo que AB es vertical (haciendo una rotación)

Ahora, dividir el vector de velocidad de B en los componentes X e Y (decir Bx y By). Puede usar esto para calcular las componentes x e y del vector que necesita para disparar.

B --> Bx
|
|
V

By


Vy
^
|
|
A ---> Vx

Es necesario Vx = Bx y Sqrt(Vx*Vx + Vy*Vy) = Velocity of Ammo.

Esto debe darle el vector que necesita en el nuevo sistema. Transformar de nuevo a viejo sistema y ya está (haciendo un giro en la otra dirección).

Otros consejos

escribí una subrutina con el objetivo de xtank un tiempo atrás. Voy a tratar de establecer cómo lo hice.

Renuncia: que pude haber hecho una o más tontos errores en cualquier parte aquí; Sólo estoy tratando de reconstruir el razonamiento con mis habilidades matemáticas oxidados. Sin embargo, voy a ir al grano en primer lugar, ya que es una programación de Q y A en lugar de una clase de matemáticas: -)

¿Cómo hacerlo?

Se reduce a resolver una ecuación cuadrática de la forma:

a * sqr(x) + b * x + c == 0

Tenga en cuenta que por sqr quiero decir cuadrado, en lugar de la raíz cuadrada. Utilice los siguientes valores:

a := sqr(target.velocityX) + sqr(target.velocityY) - sqr(projectile_speed)
b := 2 * (target.velocityX * (target.startX - cannon.X)
          + target.velocityY * (target.startY - cannon.Y))
c := sqr(target.startX - cannon.X) + sqr(target.startY - cannon.Y)

Ahora podemos mirar el discriminante para determinar si tenemos una posible solución.

disc := sqr(b) - 4 * a * c

Si el discriminante es menor que 0, olvidarse de golpear a su objetivo - el proyectil nunca puede llegar a tiempo. De lo contrario, tener en cuenta dos soluciones candidatas:

t1 := (-b + sqrt(disc)) / (2 * a)
t2 := (-b - sqrt(disc)) / (2 * a)

Tenga en cuenta que si disc == 0 continuación t1 y t2 son iguales.

Si no hay otras consideraciones tales como intervenir obstáculos, sólo tiene que elegir el valor positivo más pequeño. (Negativo t valores requerirían disparar hacia atrás en el tiempo para usar!)

Sustituir el valor t elegido de nuevo en las ecuaciones de la posición del objetivo para obtener las coordenadas del punto principal que usted debe apuntar a:

aim.X := t * target.velocityX + target.startX
aim.Y := t * target.velocityY + target.startY

Derivación

en el tiempo t, el proyectil debe ser un (euclidiana) distancia desde el cañón igual al tiempo transcurrido multiplicado por la velocidad del proyectil. Esto da una ecuación para un círculo, paramétrico en el tiempo transcurrido.

sqr(projectile.X - cannon.X) + sqr(projectile.Y - cannon.Y)
  == sqr(t * projectile_speed)

Del mismo modo, en el tiempo T, el objetivo se ha movido a lo largo de su vector de tiempo multiplicado por su velocidad:

target.X == t * target.velocityX + target.startX
target.Y == t * target.velocityY + target.startY

El proyectil puede dar en el blanco cuando su distancia desde el cañón coincide con la distancia del proyectil.

sqr(projectile.X - cannon.X) + sqr(projectile.Y - cannon.Y)
  == sqr(target.X - cannon.X) + sqr(target.Y - cannon.Y)

maravilloso! Sustituyendo las expresiones para target.X y target.Y da

sqr(projectile.X - cannon.X) + sqr(projectile.Y - cannon.Y)
  == sqr((t * target.velocityX + target.startX) - cannon.X)
   + sqr((t * target.velocityY + target.startY) - cannon.Y)

Sustituyendo el otro lado de la ecuación da esto:

sqr(t * projectile_speed)
  == sqr((t * target.velocityX + target.startX) - cannon.X)
   + sqr((t * target.velocityY + target.startY) - cannon.Y)

... restando sqr(t * projectile_speed) de ambos lados y darle la vuelta en torno a:

sqr((t * target.velocityX) + (target.startX - cannon.X))
  + sqr((t * target.velocityY) + (target.startY - cannon.Y))
  - sqr(t * projectile_speed)
  == 0

... ahora resolver los resultados de la cuadratura del subexpresiones ...

sqr(target.velocityX) * sqr(t)
    + 2 * t * target.velocityX * (target.startX - cannon.X)
    + sqr(target.startX - cannon.X)
+ sqr(target.velocityY) * sqr(t)
    + 2 * t * target.velocityY * (target.startY - cannon.Y)
    + sqr(target.startY - cannon.Y)
- sqr(projectile_speed) * sqr(t)
  == 0

... y términos similares en grupo ...

sqr(target.velocityX) * sqr(t)
    + sqr(target.velocityY) * sqr(t)
    - sqr(projectile_speed) * sqr(t)
+ 2 * t * target.velocityX * (target.startX - cannon.X)
    + 2 * t * target.velocityY * (target.startY - cannon.Y)
+ sqr(target.startX - cannon.X)
    + sqr(target.startY - cannon.Y)
  == 0

... luego combinarlas ...

(sqr(target.velocityX) + sqr(target.velocityY) - sqr(projectile_speed)) * sqr(t)
  + 2 * (target.velocityX * (target.startX - cannon.X)
       + target.velocityY * (target.startY - cannon.Y)) * t
  + sqr(target.startX - cannon.X) + sqr(target.startY - cannon.Y)
  == 0

... dando una ecuación cuadrática estándar en t . Encontrar los ceros reales positivos de esta ecuación da la (cero, uno, o dos) posibles ubicaciones de golpe, que se puede hacer con la fórmula cuadrática:

a * sqr(x) + b * x + c == 0
x == (-b ± sqrt(sqr(b) - 4 * a * c)) / (2 * a)

1 en la excelente respuesta de Jeffrey Hantin aquí. I googled alrededor y encontró soluciones que eran demasiado complejo o no específicamente sobre el caso que estaba interesado en (simple proyectil de velocidad constante en el espacio 2D.) Su era exactamente lo que necesitaba para producir la solución JavaScript autónomo a continuación.

El único punto que quiero añadir es que hay un par de casos especiales que tienen que vigilar además del discriminante es negativo:

  • "a == 0": se produce si el objetivo y el proyectil están viajando a la misma velocidad. (Solución es lineal, no cuadrática)
  • "a == 0 y b == 0": si tanto objetivo y proyectil son estacionarios. (Sin solución a menos c == 0, es decir, src y dst son mismo punto.)

Código:

/**
 * Return the firing solution for a projectile starting at 'src' with
 * velocity 'v', to hit a target, 'dst'.
 *
 * @param Object src position of shooter
 * @param Object dst position & velocity of target
 * @param Number v   speed of projectile
 * @return Object Coordinate at which to fire (and where intercept occurs)
 *
 * E.g.
 * >>> intercept({x:2, y:4}, {x:5, y:7, vx: 2, vy:1}, 5)
 * = {x: 8, y: 8.5}
 */
function intercept(src, dst, v) {
  var tx = dst.x - src.x,
      ty = dst.y - src.y,
      tvx = dst.vx,
      tvy = dst.vy;

  // Get quadratic equation components
  var a = tvx*tvx + tvy*tvy - v*v;
  var b = 2 * (tvx * tx + tvy * ty);
  var c = tx*tx + ty*ty;    

  // Solve quadratic
  var ts = quad(a, b, c); // See quad(), below

  // Find smallest positive solution
  var sol = null;
  if (ts) {
    var t0 = ts[0], t1 = ts[1];
    var t = Math.min(t0, t1);
    if (t < 0) t = Math.max(t0, t1);    
    if (t > 0) {
      sol = {
        x: dst.x + dst.vx*t,
        y: dst.y + dst.vy*t
      };
    }
  }

  return sol;
}


/**
 * Return solutions for quadratic
 */
function quad(a,b,c) {
  var sol = null;
  if (Math.abs(a) < 1e-6) {
    if (Math.abs(b) < 1e-6) {
      sol = Math.abs(c) < 1e-6 ? [0,0] : null;
    } else {
      sol = [-c/b, -c/b];
    }
  } else {
    var disc = b*b - 4*a*c;
    if (disc >= 0) {
      disc = Math.sqrt(disc);
      a = 2*a;
      sol = [(-b-disc)/a, (-b+disc)/a];
    }
  }
  return sol;
}

Jeffrey Hantin tiene una buena solución para este problema, aunque su derivación se complica demasiado. Aquí está una manera más limpia de derivar con una parte del código resultante en la parte inferior.

Voy a estar utilizando x.y para representar el vector de productos de punto, y si se eleva al cuadrado una cantidad vectorial, que significa que estoy que salpican con sí mismo.

origpos = initial position of shooter
origvel = initial velocity of shooter

targpos = initial position of target
targvel = initial velocity of target

projvel = velocity of the projectile relative to the origin (cause ur shooting from there)
speed   = the magnitude of projvel
t       = time

Sabemos que la posición del proyectil y el blanco con respecto a t tiempo se puede describir con algunas ecuaciones.

curprojpos(t) = origpos + t*origvel + t*projvel
curtargpos(t) = targpos + t*targvel

Queremos que éstos sean iguales entre sí en algún punto (el punto de intersección), por lo que vamos a establecer ellos iguales entre sí y resolver para la variable libre, projvel.

origpos + t*origvel + t*projvel = targpos + t*targvel
    turns into ->
projvel = (targpos - origpos)/t + targvel - origvel

Vamos a olvidarnos de la noción de posición de origen y de destino / velocidad. En su lugar, vamos a trabajar en términos relativos desde el movimiento de una cosa es relativa a otro. En este caso, lo que tenemos ahora es relpos = targetpos - originpos y relvel = targetvel - originvel

projvel = relpos/t + relvel

No sabemos lo que es projvel, pero sí sabemos que queremos projvel.projvel sea igual a speed^2, por lo que vamos al cuadrado ambos lados y obtenemos

projvel^2 = (relpos/t + relvel)^2
    expands into ->
speed^2 = relvel.relvel + 2*relpos.relvel/t + relpos.relpos/t^2

Ahora podemos ver que la única variable libre es el tiempo, t, y luego vamos a utilizar para resolver t projvel. Vamos a resolver para t con la fórmula cuadrática. En primer lugar separar hacia fuera en a, b y c, a continuación, resolver las raíces.

Antes de resolver, sin embargo, recordar que queremos la mejor solución en la que t es el más pequeño, pero tenemos que asegurarnos de que t no es negativo (no se puede golpear algo en el pasado)

a  = relvel.relvel - speed^2
b  = 2*relpos.relvel
c  = relpos.relpos

h  = -b/(2*a)
k2  = h*h - c/a

if k2 < 0, then there are no roots and there is no solution
if k2 = 0, then there is one root at h
    if 0 < h then t = h
    else, no solution
if k2 > 0, then there are two roots at h - k and h + k, we also know r0 is less than r1.
    k  = sqrt(k2)
    r0 = h - k
    r1 = h + k
    we have the roots, we must now solve for the smallest positive one
    if 0<r0 then t = r0
    elseif 0<r1 then t = r1
    else, no solution

Ahora, si tenemos un valor t, podemos conectar t de nuevo en la ecuación original y resolver para el projvel

 projvel = relpos/t + relvel

Ahora, para el rodaje del proyectil, la posición global resultante y la velocidad para el proyectil es

globalpos = origpos
globalvel = origvel + projvel

Y ya está!

Mi aplicación de mi solución en Lua, donde vec * vec representa el producto escalar de vectores:

local function lineartrajectory(origpos,origvel,speed,targpos,targvel)
    local relpos=targpos-origpos
    local relvel=targvel-origvel
    local a=relvel*relvel-speed*speed
    local b=2*relpos*relvel
    local c=relpos*relpos
    if a*a<1e-32 then--code translation for a==0
        if b*b<1e-32 then
            return false,"no solution"
        else
            local h=-c/b
            if 0<h then
                return origpos,relpos/h+targvel,h
            else
                return false,"no solution"
            end
        end
    else
        local h=-b/(2*a)
        local k2=h*h-c/a
        if k2<-1e-16 then
            return false,"no solution"
        elseif k2<1e-16 then--code translation for k2==0
            if 0<h then
                return origpos,relpos/h+targvel,h
            else
                return false,"no solution"
            end
        else
            local k=k2^0.5
            if k<h then
                return origpos,relpos/(h-k)+targvel,h-k
            elseif -k<h then
                return origpos,relpos/(h+k)+targvel,h+k
            else
                return false,"no solution"
            end
        end
    end
end

Siguiente es polar de coordenadas basado en el apuntamiento de código en C++.

Para utilizar con las coordenadas rectangulares que usted necesita para convertir los objetivos de coordenada relativa al ángulo/distancia, y los objetivos de x/y de la velocidad de ángulo/velocidad.

La "velocidad" de entrada es la velocidad del proyectil.Las unidades de la velocidad y targetSpeed son irrelevent, como sólo la relación de las velocidades que se utilizan en el cálculo.La salida es el ángulo que el proyectil debe ser despedido y la distancia al punto de colisión.

El algoritmo es desde el código fuente disponible en http://www.turtlewar.org/ .


// C++
static const double pi = 3.14159265358979323846;
inline double Sin(double a) { return sin(a*(pi/180)); }
inline double Asin(double y) { return asin(y)*(180/pi); }

bool/*ok*/ Rendezvous(double speed,double targetAngle,double targetRange,
   double targetDirection,double targetSpeed,double* courseAngle,
   double* courseRange)
{
   // Use trig to calculate coordinate of future collision with target.
   //             c
   //
   //       B        A
   //
   // a        C        b
   //
   // Known:
   //    C = distance to target
   //    b = direction of target travel, relative to it's coordinate
   //    A/B = ratio of speed and target speed
   //
   // Use rule of sines to find unknowns.
   //  sin(a)/A = sin(b)/B = sin(c)/C
   //
   //  a = asin((A/B)*sin(b))
   //  c = 180-a-b
   //  B = C*(sin(b)/sin(c))

   bool ok = 0;
   double b = 180-(targetDirection-targetAngle);
   double A_div_B = targetSpeed/speed;
   double C = targetRange;
   double sin_b = Sin(b);
   double sin_a = A_div_B*sin_b;
   // If sin of a is greater than one it means a triangle cannot be
   // constructed with the given angles that have sides with the given
   // ratio.
   if(fabs(sin_a) <= 1)
   {
      double a = Asin(sin_a);
      double c = 180-a-b;
      double sin_c = Sin(c);
      double B;
      if(fabs(sin_c) > .0001)
      {
         B = C*(sin_b/sin_c);
      }
      else
      {
         // Sin of small angles approach zero causing overflow in
         // calculation. For nearly flat triangles just treat as
         // flat.
         B = C/(A_div_B+1);
      }
      // double A = C*(sin_a/sin_c);
      ok = 1;
      *courseAngle = targetAngle+a;
      *courseRange = B;
   }
   return ok;
}

Este es un ejemplo en el que elaboren y apliquen una solución al problema de la predicción de orientación usando un algoritmo recursivo: http://www.newarteest.com/flash/targeting.html

Voy a tener que probar algunas de las otras soluciones presentadas, ya que parece más eficiente para calcular en un solo paso, pero la solución que se me ocurrió fue estimar la posición de destino y piensos que resultan de nuevo en el algoritmo para hacer una nueva estimación más precisa, repitiendo varias veces.

Por primera estimación de I "fuego" en la posición actual del objetivo y luego usar la trigonometría para determinar donde el objetivo será cuando el disparo alcanza la posición disparado. Luego, en la siguiente iteración I "fuego" en esa nueva posición y determinar dónde está el objetivo será esta vez. Después de unos 4 repeticiones consigo dentro de un píxel de precisión.

Yo sólo hackeado esta versión para apuntar en el espacio 2D, no he probado muy bien todavía, pero parece que funciona. La idea detrás de esto es la siguiente:

Crea un vector perpendicular al vector que apunta desde la boca del cañón hacia el objetivo. Para una colisión que se produzca, las velocidades del objetivo y el proyectil a lo largo de este vector (eje) deben ser los mismos! El uso de material coseno bastante simple llegué a este código:

private Vector3 CalculateProjectileDirection(Vector3 a_MuzzlePosition, float a_ProjectileSpeed, Vector3 a_TargetPosition, Vector3 a_TargetVelocity)
{
    // make sure it's all in the horizontal plane:
    a_TargetPosition.y = 0.0f;
    a_MuzzlePosition.y = 0.0f;
    a_TargetVelocity.y = 0.0f;

    // create a normalized vector that is perpendicular to the vector pointing from the muzzle to the target's current position (a localized x-axis):
    Vector3 perpendicularVector = Vector3.Cross(a_TargetPosition - a_MuzzlePosition, -Vector3.up).normalized;

    // project the target's velocity vector onto that localized x-axis:
    Vector3 projectedTargetVelocity = Vector3.Project(a_TargetVelocity, perpendicularVector);

    // calculate the angle that the projectile velocity should make with the localized x-axis using the consine:
    float angle = Mathf.Acos(projectedTargetVelocity.magnitude / a_ProjectileSpeed) / Mathf.PI * 180;

    if (Vector3.Angle(perpendicularVector, a_TargetVelocity) > 90.0f)
    {
        angle = 180.0f - angle;
    }

    // rotate the x-axis so that is points in the desired velocity direction of the projectile:
    Vector3 returnValue = Quaternion.AngleAxis(angle, -Vector3.up) * perpendicularVector;

    // give the projectile the correct speed:
    returnValue *= a_ProjectileSpeed;

    return returnValue;
}

He visto muchas formas de resolver este problema matemáticamente, pero este fue un componente relevante para un proyecto de mi clase estaba obligado a hacer en la escuela secundaria, y no todo el mundo en esta clase de programación tenía un fondo con el cálculo, o incluso los vectores, así que he creado una manera de resolver este problema con más de un enfoque de programación.El punto de intersección será exacta, aunque se puede dar 1 marco más tarde que en los cálculos matemáticos.

Considere la posibilidad de:

S = shooterPos, E = enemyPos, T = targetPos, Sr = shooter range, D = enemyDir
V = distance from E to T, P = projectile speed, Es = enemy speed

En la implementación estándar de este problema [S,E,P,E,D], son todos dados y que se van a resolver, ya sea para encontrar T o el ángulo en el que se va a disparar por lo que de llegar a T en el tiempo adecuado.

El aspecto principal de este método de resolver el problema es considerar el rango de la pistola como un círculo que abarca todos los posibles puntos que pueden ser tomadas en cualquier momento dado.El radio de este círculo es igual a:

Sr = P*time

Donde el tiempo se calcula como una iteración de un bucle.

Por lo tanto para encontrar la distancia a un enemigo viajes, dado el tiempo de iteración creamos el vector:

V = D*Es*time

Ahora, para resolver el problema que se quiere encontrar un punto en el que la distancia desde el objetivo (T) a nuestro tirador (S) es menor que el rango de nuestro tirador (Sr).Aquí es un poco de un pseudocódigo aplicación de esta ecuación.

iteration = 0;
while(TargetPoint.hasNotPassedShooter)
{
    TargetPoint = EnemyPos + (EnemyMovementVector)
    if(distanceFrom(TargetPoint,ShooterPos) < (ShooterRange))
        return TargetPoint;
    iteration++
}

Hice una Unidad C función de dominio público # aquí:
http://ringofblades.com/Blades/Code/PredictiveAim.cs

Es para 3D, pero se puede modificar fácilmente esto para 2D mediante la sustitución de la Vector3s con Vector2s y el uso de sus ejes abajo de elección para la gravedad si hay gravedad.

En el caso de los intereses de la teoría de que, camino a través de la derivación de la matemáticas aquí:
http://www.gamasutra.com/blogs/KainShin/20090515/83954 /Predictive_Aim_Mathematics_for_AI_Targeting.php

Básicamente, el concepto de intersección no es realmente necesario aquí, Por lo que usted está utilizando el movimiento de proyectiles, sólo tiene que golpear en un ángulo particular y cree una instancia en el momento de la toma de modo que se obtiene la distancia exacta de su destino desde el fuente y luego una vez que tenga la distancia, se puede calcular la velocidad apropiada con la cual debe disparado con el fin de dar en el blanco.

En el siguiente enlace hace teh concepto claro y se considera útil, podría ayudar: Movimiento de proyectiles que siempre dan en un blanco móvil

Me agarró una de las soluciones de aquí, pero ninguno de ellos toman en cuenta el movimiento del tirador. Si el tirador está en movimiento, es posible que desee tener esto en cuenta (como la velocidad del tirador debe añadirse a la velocidad de su bala cuando el fuego). Realmente todo lo que necesita hacer es restar la velocidad de su juego de disparos desde la velocidad del objetivo. Así que si usted está utilizando el código de broofa anterior (que recomiendo), cambiar las líneas

  tvx = dst.vx;
  tvy = dst.vy;

a

  tvx = dst.vx - shooter.vx;
  tvy = dst.vy - shooter.vy;

y usted debe estar todo listo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top