24

さて、これはすべて素晴らしくシンプルな2Dの世界で行われます... :)

位置Aposに静的オブジェクトAがあり、BposにbVelocityの直線的に移動するオブジェクトBがあり、速度Avelocityの弾薬ラウンドがあるとします。

Bの線速度とAの弾薬の速度を考慮して、AがBを打つために撃たなければならない角度をどのように知ることができますか?

現在、照準はオブジェクトの現在の位置にあります。つまり、発射物がそこに到達するまでに、ユニットはより安全な位置に移動しています:)

4

11 に答える 11

47

しばらく前に、 xtankの照準サブルーチンを作成しました。私はそれをどのようにしたかをレイアウトしようとします。

免責事項:私はここのどこかで1つ以上のばかげた間違いをしたかもしれません。さびた数学のスキルで推論を再構築しようとしています。ただし、これは数学の授業ではなくプログラミングのQ&Aであるため、最初に追いかけます:-)

どうやってするの

つまり、次の形式の2次方程式を解くことになります。

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

sqr平方根ではなく、平方を意味することに注意してください。次の値を使用します。

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)

これで、判別式を調べて、可能な解決策があるかどうかを判断できます。

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

判別式が0未満の場合は、ターゲットに命中することを忘れてください。発射物が時間内に到達することはありません。それ以外の場合は、2つの候補ソリューションを検討してください。

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

disc == 0その場合t1t2が等しいことに注意してください。

障害物の介在など、他に考慮事項がない場合は、小さい方の正の値を選択してください。(負のt値を使用するには、時間を逆方向に発射する必要があります!)

選択したt値をターゲットの位置方程式に代入して、狙うべきリーディングポイントの座標を取得します。

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

導出

時間Tで、発射体は、経過時間に発射体の速度を掛けたものに等しい、大砲からの(ユークリッド)距離でなければなりません。これにより、経過時間のパラメトリックな円の方程式が得られます。

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

同様に、時間Tで、ターゲットはそのベクトルに沿って時間×速度を掛けて移動しました。

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

大砲からの距離が発射体の距離と一致すると、発射体はターゲットに命中する可能性があります。

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

素晴らしい!target.Xとtarget.Yの式を代入すると、次のようになります。

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)

方程式の反対側を代入すると、次のようになります。

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

sqr(t * projectile_speed)...両側から減算し、それを裏返します:

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

...部分式を二乗した結果を解決します...

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

...そして同様の用語をグループ化する..。

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

...次にそれらを組み合わせる...

(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

... tに標準の2次方程式を与えます。この方程式の正の実数ゼロを見つけると、(ゼロ、1、または2つの)可能なヒット位置が得られます。これは、2次方程式で実行できます。

a * sqr(x) + b * x + c == 0
x == (-b ± sqrt(sqr(b) - 4 * a * c)) / (2 * a)
于 2010-02-12T02:11:44.873 に答える
24

ここでジェフリーハンティンの優れた答えに+1。私はグーグルで調べて、私が興味を持ったケース(2D空間での単純な定速発射体)について複雑すぎるか、具体的にはないソリューションを見つけました。彼はまさに、以下の自己完結型JavaScriptソリューションを作成するために必要なものでした。

私が付け加える1つのポイントは、判別式が否定的であることに加えて、注意しなければならないいくつかの特別なケースがあるということです。

  • "a == 0":ターゲットと発射物が同じ速度で移動している場合に発生します。(解は二次ではなく線形です)
  • "a==0およびb==0":ターゲットと発射物の両方が静止している場合。(c == 0でない限り、解決策はありません。つまり、srcとdstが同じ点です。)

コード:

/**
 * Return the firing solution for a projectile starting at 'src' with
 * velocity 'v', to hit a target, 'dst'.
 *
 * @param ({x, y}) src position of shooter
 * @param ({x, y, vx, vy}) dst position & velocity of target
 * @param (Number) v   speed of projectile
 *
 * @return ({x, y}) Coordinate at which to fire (and where intercept occurs). Or `null` if target cannot be hit.
 */
function intercept(src, dst, v) {
  const tx = dst.x - src.x;
  const ty = dst.y - src.y;
  const tvx = dst.vx;
  const tvy = dst.vy;

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

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

  // Find smallest positive solution
  let sol = null;
  if (ts) {
    const t0 = ts[0];
    const t1 = ts[1];
    let 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) {
  let 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 {
    let 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;
}

// For example ...
const sol = intercept(
  {x:2, y:4},              // Starting coord
  {x:5, y:7, vx: 2, vy:1}, // Target coord and velocity
  5                        // Projectile velocity
)

console.log('Fire at', sol)

于 2010-08-15T14:23:45.027 に答える
10

まず、ABが垂直になるように軸を回転させます(回転させることにより)

ここで、Bの速度ベクトルをx成分とy成分(たとえば、BxとBy)に分割します。これを使用して、撮影する必要のあるベクトルのx成分とy成分を計算できます。

B --> Bx
|
|
V

By


Vy
^
|
|
A ---> Vx

あなたが必要Vx = BxSqrt(Vx*Vx + Vy*Vy) = Velocity of Ammo

これにより、新しいシステムで必要なベクトルが得られます。古いシステムに変換して戻すと、完了です(反対方向に回転することによって)。

于 2010-02-12T00:55:00.490 に答える
8

Jeffrey Hantinは、この問題に対する優れた解決策を持っていますが、彼の導出は非常に複雑です。下部に結果のコードのいくつかを使用して、それを導出するためのよりクリーンな方法を次に示します。

ベクトルドット積を表すためにxyを使用します。ベクトル量が二乗されている場合、それはそれ自体でドットを付けていることを意味します。

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

時間に対する発射体とターゲットの位置は、tいくつかの方程式で記述できることがわかっています。

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

これらをある点(交点)で互いに等しくしたいので、それらを互いに等しく設定して、自由変数を解きましょうprojvel

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

原点とターゲットの位置/速度の概念を忘れましょう。代わりに、あるものの動きは別のものに対して相対的であるため、相対的な用語で作業しましょう。この場合、私たちが今持っているrelpos = targetpos - originposのはrelvel = targetvel - originvel

projvel = relpos/t + relvel

何であるかはわかりませんが、に等しくprojvelなりたいことはわかっているので、両側を2乗して次のようになります。projvel.projvelspeed^2

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

これで、自由変数は時間、、だけであることがtわかります。次に、を使用tしてを解きますprojvelt二次方程式で解きます。最初にそれを、、に分離しab次にc根を解きます。

tただし、解決する前に、最小の最良の解決策が必要であることを忘れないでください。ただし、それtが否定的でないことを確認する必要があります(過去に何かを打つことはできません)。

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

これで、値があれば、元の方程式にtプラグインして、tprojvel

 projvel = relpos/t + relvel

ここで、発射体を発射するために、発射体の結果として得られるグローバルな位置と速度は次のようになります。

globalpos = origpos
globalvel = origvel + projvel

そして、あなたは完了です!

Luaでのソリューションの実装。vec*vecはベクトル内積を表します。

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
于 2015-09-01T22:40:24.670 に答える
2

以下は、C++の極座標ベースの照準コードです。

長方形の座標で使用するには、最初にターゲットの相対座標を角度/距離に変換し、ターゲットのx/y速度を角度/速度に変換する必要があります。

「速度」入力は、発射体の速度です。計算には速度の比率のみが使用されるため、速度とtargetSpeedの単位は関係ありません。出力は、発射物が発射される角度と衝突点までの距離です。

アルゴリズムは、 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;
}

于 2010-02-12T06:40:08.937 に答える
1

これは、再帰的アルゴリズムを使用して予測ターゲティングの問題の解決策を考案して実装した例です。http: //www.newarteest.com/flash/targeting.html

1つのステップで計算する方が効率的であるため、提示された他のソリューションのいくつかを試してみる必要がありますが、私が思いついたソリューションは、ターゲット位置を推定し、その結果をアルゴリズムにフィードバックして新しいものを作成することでした。より正確な見積もり、数回繰り返す。

最初の見積もりでは、ターゲットの現在の位置で「発射」し、三角法を使用して、ショットが発射された位置に到達したときにターゲットがどこにあるかを決定します。次に、次の反復で、その新しい位置で「発砲」し、今回のターゲットがどこになるかを決定します。約4回繰り返すと、1ピクセル以内の精度になります。

于 2012-05-04T12:49:56.840 に答える
1

2D空間を狙うためにこのバージョンをハックしたばかりで、まだ十分にテストしていませんが、機能しているようです。その背後にある考え方はこれです:

銃口からターゲットを指すベクトルに垂直なベクトルを作成します。衝突が発生するためには、このベクトル(軸)に沿ったターゲットと発射体の速度が同じである必要があります!かなり単純なコサインのものを使用して、私はこのコードに到達しました:

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;
}
于 2013-02-13T22:17:04.273 に答える
1

ここでパブリックドメインのUnityC#関数を作成しました:http:
//ringofblades.com/Blades/Code/PredictiveAim.cs

これは3D用ですが、Vector3をVector2に置き換え、重力がある場合は重力に選択した下軸を使用することで、これを2D用に簡単に変更できます。

理論に興味がある場合は、ここで数学の導出について説明します:http:
//www.gamasutra.com/blogs/KainShin/20090515/83954/Predictive_Aim_Mathematics_for_AI_Targeting.php

于 2014-04-03T10:19:42.850 に答える
0

この問題を数学的に解決する方法はたくさんありますが、これは私のクラスが高校で行う必要のあるプロジェクトに関連するコンポーネントであり、このプログラミングクラスの全員が微積分やベクトルのバックグラウンドを持っているわけではありません。 、それで私はこの問題をより多くのプログラミングアプローチで解決する方法を作成しました。交点は正確ですが、数学的な計算よりも1フレーム遅れてヒットする可能性があります。

検討:

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

この問題の標準的な実装では、[S、E、P、Es、D]がすべて与えられ、適切なタイミングでTを打つように、Tまたは射撃する角度のいずれかを見つけるために解決しています。

この問題を解決する方法の主な側面は、射手の範囲を、任意の時点で射撃できるすべての可能なポイントを含む円と見なすことです。この円の半径は次のとおりです。

Sr = P*time

ここで、時間はループの反復として計算されます。

したがって、時間の反復を前提として敵が移動する距離を見つけるために、ベクトルを作成します。

V = D*Es*time

ここで、実際に問題を解決するために、ターゲット(T)からシューター(S)までの距離がシューター(Sr)の範囲よりも小さいポイントを見つけたいと思います。これは、この方程式の擬似コードの実装です。

iteration = 0;
while(TargetPoint.hasNotPassedShooter)
{
    TargetPoint = EnemyPos + (EnemyMovementVector)
    if(distanceFrom(TargetPoint,ShooterPos) < (ShooterRange))
        return TargetPoint;
    iteration++
}
于 2013-11-06T14:35:56.333 に答える
0

基本的に、ここでは交差点の概念は実際には必要ありません。発射体の動きを使用している限り、特定の角度でヒットし、射撃時にインスタンス化するだけで、ソースからターゲットまでの正確な距離を取得できます。距離が決まったら、ターゲットに命中するために撃つべき適切な速度を計算できます。

次のリンクは、概念を明確にし、役立つと考えられており、役立つ可能性があります。 常に移動するターゲットに当たる投射物の動き

于 2014-08-06T20:16:12.043 に答える
0

私はここから解決策の1つをつかみましたが、それらのどれも射手の動きを考慮に入れていません。射手が動いている場合は、それを考慮に入れることをお勧めします(射撃時の弾丸の速度に射手の速度を追加する必要があるため)。本当にあなたがする必要があるのは、ターゲットの速度からあなたの射手の速度を引くことです。したがって、上記のbroofaのコード(私がお勧めします)を使用している場合は、行を変更してください

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

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

そして、あなたはすべて設定されている必要があります。

于 2015-03-18T23:52:02.857 に答える