2ème jeu :tirer sur une cible en mouvement en prédisant l'intersection du projectile et de l'unité

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

  •  20-09-2019
  •  | 
  •  

Question

D'accord, tout cela se déroule dans un monde 2D simple et agréable...:)

Supposons que j'ai un objet statique A en position Apos, et un objet B en mouvement linéaire en Bpos avec bVelocity, et une cartouche de munitions avec vitesse Avelocity...

Comment puis-je connaître l'angle que A doit tirer pour frapper B, en tenant compte de la vitesse linéaire de B et de la vitesse des munitions de A ?

À l'heure actuelle, la visée est la position actuelle de l'objet, ce qui signifie qu'au moment où mon projectile y arrive, l'unité s'est déplacée vers des positions plus sûres :)

Était-ce utile?

La solution

Tout d'abord faire tourner les axes de telle sorte que AB est vertical (en faisant une rotation)

Maintenant, diviser le vecteur de vitesse de B dans les composantes x et y (dire Bx et By). Vous pouvez l'utiliser pour calculer les composantes x et y du vecteur dont vous avez besoin pour tirer.

B --> Bx
|
|
V

By


Vy
^
|
|
A ---> Vx

Vous avez besoin Vx = Bx et Sqrt(Vx*Vx + Vy*Vy) = Velocity of Ammo.

Cela devrait vous donner le vecteur dont vous avez besoin dans le nouveau système. Reprenez à l'ancien système et vous avez terminé (en faisant une rotation dans l'autre sens).

Autres conseils

J'ai écrit un sous-programme visant xtank un certain temps. Je vais essayer d'exposer comment je l'ai fait.

Disclaimer: je l'ai fait une ou plusieurs erreurs stupides partout ici; Je suis juste essayer de reconstituer le raisonnement avec mes compétences en mathématiques rouillées. Cependant, je vais couper à la chasse d'abord, puisque c'est une programmation Q & A au lieu d'une classe de mathématiques: -)

Comment faire

Il se résume à la résolution d'une équation du second degré de la forme:

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

Notez que par sqr Je veux dire la place, par opposition à la racine carrée. Utilisez les valeurs suivantes:

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)

Maintenant, nous pouvons regarder le discriminante pour déterminer si nous avons une solution possible.

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

Si le discriminant est inférieur à 0, oublier de frapper votre cible - votre projectile ne peut jamais arriver à temps. Sinon, regardez deux solutions candidats:

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

Notez que si disc == 0 puis t1 et t2 sont égaux.

S'il n'y a pas d'autres considérations telles que les obstacles intervenant, choisissez simplement la plus petite valeur positive. (Négatif t valeurs nécessiteraient feu arrière dans le temps à utiliser!)

Remplacez la valeur t choisie de nouveau dans les équations de position de la cible pour obtenir les coordonnées du point de premier plan que vous devriez viser à:

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

Dérivation

A l'instant T, le projectile doit être un (euclidienne) la distance du canon égal au temps écoulé, multiplié par la vitesse du projectile. Cela donne une équation pour un cercle, paramétrique dans le temps écoulé.

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

De même, la fin du temps T, l'objectif est déplacé le long de son vecteur par heure multiplié par sa vitesse:

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

Le projectile peut atteindre la cible lorsque la distance du canon correspond à la distance du projectile.

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

Magnifique! Les expressions pour substituer target.X et target.Y donne

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)

Substituer l'autre côté de l'équation donne ceci:

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

... soustrayant sqr(t * projectile_speed) des deux côtés et en retournant la situation:

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

... résoudre maintenant les résultats de la quadrature du sous-expressions ...

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

... et groupe termes similaires ...

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

... puis les combiner ...

(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

... donnant une équation quadratique standard dans t . Trouver les zéros réels positifs de cette équation donne la (zéro, un ou deux) possibles lieux de vie, ce qui peut être fait avec la formule quadratique:

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

1 sur l'excellente réponse de Jeffrey Hantin ici. J'ai googlé et trouvé des solutions qui étaient trop complexes ou pas spécifiquement sur le cas, je me suis intéressé à (projectile simple vitesse constante dans l'espace 2D.) Son était exactement ce que je devais produire la solution JavaScript autonome ci-dessous.

Le seul point que je voudrais ajouter est qu'il ya quelques cas particuliers, vous devez surveiller en plus du discriminante étant négatif:

  • « a == 0 »: se produit si la cible et le projectile se déplacent à la même vitesse. (Solution est linéaire, non quadratique)
  • "a == 0 et b == 0": si à la fois la cible et projectile sont stationnaires. (Pas de solution à moins que c == 0, c.-à-src et dst sont même point.)

Code:

/**
 * 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 a une belle solution pour ce problème, bien que sa dérivation est trop compliquée. Voici un moyen plus propre de tirer avec une partie du code qui en résulte au fond.

Je vais utiliser x.y pour représenter vecteur produit scalaire, et si une quantité de vecteur est carré, cela signifie que je suis avec lui-même qui parsèment ce.

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

Nous savons que la position du projectile et de la cible par rapport à t temps peut être décrit avec des équations.

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

Nous voulons que ces soient égaux entre eux à un moment donné (point d'intersection), donc nous allons les régler égaux entre eux et à résoudre pour la variable libre, projvel.

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

Oublions la notion d'origine et la position cible / vitesse. Au lieu de cela, nous allons travailler en termes relatifs puisque le mouvement d'une chose est par rapport à une autre. Dans ce cas, ce que nous avons maintenant est relpos = targetpos - originpos et relvel = targetvel - originvel

projvel = relpos/t + relvel

Nous ne savons pas ce que projvel est, mais nous savons que nous voulons projvel.projvel soit égale à speed^2, nous allons donc au carré les deux côtés et nous obtenons

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

On peut maintenant voir que la seule variable libre est temps, t, puis nous allons utiliser t pour résoudre pour projvel. Nous résoudrons pour t avec la formule quadratique. Tout d'abord séparer dehors dans a, b et c, puis résoudre les racines.

Avant de résoudre, si, rappelez-vous que nous voulons la meilleure solution où t est le plus petit, mais nous devons nous assurer que t n'est pas négatif (vous ne pouvez pas frapper quelque chose dans le passé)

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

Maintenant, si nous avons une valeur t, nous pouvons brancher t dans l'équation d'origine et résoudre le projvel

 projvel = relpos/t + relvel

Maintenant, pour le tournage du projectile, la position globale résultante et la vitesse du projectile est

globalpos = origpos
globalvel = origvel + projvel

Et vous avez terminé!

Ma mise en œuvre de ma solution dans Lua, où VEC * représente VEC produit vecteur de points:

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

Voici le code de visée basé sur les coordonnées polaires en C++.

Pour utiliser avec des coordonnées rectangulaires, vous devez d'abord convertir les coordonnées relatives de la cible en angle/distance, et la vitesse x/y de la cible en angle/vitesse.

L'entrée « vitesse » est la vitesse du projectile.Les unités de vitesse et de targetSpeed ​​n'ont pas d'importance, car seul le rapport des vitesses est utilisé dans le calcul.Le résultat est l’angle auquel le projectile doit être tiré et la distance jusqu’au point de collision.

L'algorithme provient du code source disponible sur 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;
}

Voici un exemple où je conçu et mis en une solution au problème de la prédiction de ciblage en utilisant un algorithme récursif: http://www.newarteest.com/flash/targeting.html

Je vais devoir essayer quelques-unes des autres solutions présentées, car il semble plus efficace de calculer en une seule étape, mais la solution que je suis venu avec était d'estimer la position cible et alimentation qui résultat dans l'algorithme faire une nouvelle estimation plus précise, en répétant plusieurs fois.

Pour la première estimation que je « feu » à la position actuelle de la cible, puis utiliser la trigonométrie pour déterminer où la cible sera quand le tir atteint la position de tir à. Puis, dans la prochaine itération I « feu » à cette nouvelle position et déterminer où la cible sera cette fois-ci. Après environ 4 répétitions, je reçois dans un pixel de précision.

Je viens piraté cette version pour viser dans l'espace 2d, je ne l'ai pas testé encore très bien, mais il semble fonctionner. L'idée sous-jacente est la suivante:

Insérer un vecteur perpendiculaire au vecteur pointant de la bouche du canon à la cible. En cas de collision se produise, les vitesses de la cible et le projectile le long de ce vecteur (axe) doivent être identiques! L'utilisation des choses cosinus assez simple je suis arrivé à ce code:

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;
}

Je l'ai vu plusieurs façons de résoudre ce problème mathématique, mais cela a été un élément pertinent pour un projet de ma classe était tenu de le faire à l'école secondaire, et pas tout le monde dans cette classe de programmation avait un arrière-plan avec le calcul, ou même des vecteurs pour cette question, donc je créé un moyen de résoudre ce problème avec plus d'une approche de programmation. Le point d'intersection sera exacte, même si elle peut frapper 1 cadre plus tard que dans les calculs mathématiques.

Considérez:

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

Dans l'implémentation standard de ce problème [S, E, P, Es, D] sont tous Givens et vous résolvent soit pour trouver T ou l'angle auquel tirer de sorte que vous frappez T au moment approprié.

L'aspect principal de cette méthode de résolution du problème est de considérer la portée du tireur comme un cercle qui englobe tous les points possibles qui peuvent être prises à tout moment. Le rayon de ce cercle est égal à:

Sr = P*time

Lorsque le temps est calculée comme une itération d'une boucle.

Ainsi, pour trouver la distance un voyage ennemi étant donné le temps itération, nous créons le vecteur:

V = D*Es*time

Maintenant, pour résoudre effectivement le problème que nous voulons trouver un point où la distance de la cible (T) à notre jeu de tir (S) est inférieure à la plage de notre jeu de tir (Sr). Voici un peu d'une mise en œuvre de pseudo-code de cette équation.

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

J'ai fait un domaine public Unité fonction C # ici:
http://ringofblades.com/Blades/Code/PredictiveAim.cs

Il est pour la 3D, mais vous pouvez facilement modifier ce 2D en remplaçant le Vector3s avec Vector2s et en utilisant votre axe vers le bas de choix pour la gravité s'il y a gravité.

Dans le cas où les intérêts de la théorie que vous, je marche dans la dérivation des mathématiques ici:
http://www.gamasutra.com/blogs/KainShin/20090515/83954 /Predictive_Aim_Mathematics_for_AI_Targeting.php

En fait, le concept d'intersection est pas vraiment nécessaire ici, autant que vous utilisez le mouvement du projectile, il vous suffit de frapper à un angle particulier et instancier au moment de la prise de vue afin d'obtenir la distance exacte de votre cible de la Source et une fois que vous avez la distance, vous pouvez calculer la vitesse appropriée avec laquelle il devrait tourné afin de frapper la cible.

Le lien suivant fait Teh concept clair et est considéré comme utile, pourrait aider: mouvement Projectile toujours à atteindre une cible mouvante

Je saisis l'une des solutions d'ici, mais aucun d'entre eux prennent en compte le mouvement du tireur. Si votre jeu de tir se déplace, vous voudrez peut-être tenir compte (comme cela devrait être ajouté au moment où vous tirez la vitesse de votre balle de vitesse du tireur). Vraiment tout ce que vous devez faire est de soustraire votre vitesse de tir de la vitesse de la cible. Donc, si vous utilisez le code de broofa ci-dessus (que je recommande), changer les lignes

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

à

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

et vous devriez être tous ensemble.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top