動くシューターから動くターゲットを導く方法


7

私はこの質問を見ました:オブジェクトをそのターゲットに導くために敵の位置を予測しました。私の状況は少し異なりますが。

ターゲットが動き、射手が動きます。また、シューターの速度は弾丸の速度に追加されます。つまり、右にスライドしている間に発射された弾丸は、右に向かうほど大きな速度になります。

私がやろうとしていることは、敵に、プレイヤーを攻撃するために撃つ必要がある場所を決定させることです。リンクされたSOソリューションを使用すると、プレイヤーと敵が静止していない限り、速度の違いによりミスが発生します。どうすればそれを防ぐことができますか?


スタックオーバーフローの回答から提示されたソリューションは次のとおりです。要約すると、次の形式の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の場合、t1とt2は等しいことに注意してください。


3
質問を要約してみてください。あなたは本当に詳細な説明をする必要があると思いますが、実際には半分ほどのところまで行き、教科書を読んでいるように感じました
gardian06

1
sq()代わりになどを使用できますsqr()か?読みづらいです。
sam hocevar

@ gardian06説明のないコードは質問の最後にあります。
Azaral

@Sam Hocevar私はすべての変数*変数をstd :: pow(variable、2)に置き換えました。
Azaral

静止しているときにターゲットが動くが、両方が動くときは機能しない場合は、ターゲットの速度から速度を差し引いて相対速度を取得し、それを使用します。
George Duckett、2012年

回答:


5

さて、これに正気を入れましょう。私はあなたがまったく簡単にしていないと思います、あなたのコードはコンパイルせず、変数名に関して一貫性がなく(数行後にplayerVelocityXなりplayerXvelocityますか?何xVelocityですか?)、そして冗長すぎます。かなりの労力を費やさない限り、デバッグは基本的に不可能です。

だから、ここに修正するものがあります:

弾丸の速度

弾丸の速度は30、期間でなければなりません。計算を行う必要はありません。複雑さを回避するために、参照フレームの変更が正確に行われます。メインリファレンスフレームに戻ったときに、解決策を見つけた後にのみ敵の速度を追加します。

ソリューションの妥当性

timeソリューションがポジティブであることを確認していません。

多数のコーディングエラー

あなたはテストtime1してtime2いますが、常にtime1結果で使用しています。

あなたはplayerXvelocity - yVelocityどちらが矛盾していますか。

/ 2 * a代わりにやっています/ (2.f * a)。これは最悪のエラーであり、すべてがうまくいかない理由です。

あなたが計算shootxし、弾丸のshooty最終的な位置として、あなたが探しているのは弾丸の速度です。

固定コード

float const bulletSpeed = 30.f;
/* Relative player position */
float const dx = playerX - enemyX;
float const dy = playerY - enemyY;
/* Relative player velocity */
float const vx = playerVelocityX - enemyVelocityX;
float const vy = playerVelocityY - enemyVelocityY;

float const a = vx * vx + vy * vy - bulletSpeed * bulletSpeed;
float const b = 2.f * (vx * dx + vy * dy);
float const c = dx * dx + dy * dy;
float const disc = b * b - 4.f * a * c;

shouldShoot = false;

if (disc >= 0.f)
{
    float t0 = (-b - std::sqrt(disc)) / (2.f * a);
    float t1 = (-b + std::sqrt(disc)) / (2.f * a);
    /* If t0 is negative, or t1 is a better solution, use t1 */
    if (t0 < 0.f || (t1 < t0 && t1 >= 0.f))
        t0 = t1;
    if (t0 >= 0.f)
    {
        /* Compute the ship's heading */
        shootx = vx + dx / t0;
        shooty = vy + dy / t0;
        heading = std::atan2(shooty, shootx) * RAD2DEGREE;
        /* Compute the bullet's velocity by adding the enemy's velocity */
        bulletVelocityX = shootx + enemyVelocityX;
        bulletVelocityY = shooty + enemyVelocityY;

        shouldShoot = true;
    }
}

ええ、コードを再入力して、いくつかのことを台無しにしたことに気づきました。私の実際のコードではそれが矛盾していないわけではありません!「/(2.f * a)の代わりに/ 2 * aを実行しています」に気づきました。昨夜、それを修正しました。私はあなたのコードを試してみるつもりです。私はこれを理解しようと約4時間後にここに来て、かなりイライラした状態でした、そしてそれが私がコードをとても恐ろしく入力した理由だと思います。
Azaral

簡単なコンソール出力でこれを試しましたが、機能しません。コピーして新しいプロジェクトに貼り付けました。コードから宣言されていないプレーヤー変数と敵変数を追加しました。if(t0> = 0.f)領域内で、ラインが発射するはずの後にプレーヤーx、yと弾丸x、yの計算を追加しました= true; playerX += playerVelocityX*t0; playerY += playerVelocityY*t0; float bulletX = ((cos(heading * DEGREE2RAD)*bulletSpeed + enemyVelocityX) * t0) + enemyX; float bulletY = ((sin(heading * DEGREE2RAD) * bulletSpeed + enemyVelocityY) * t0) + enemyY; 出力はP =(84.8069,93.0386)E =(88.3793,105.132)
Azaral

この時点で、私がばかだったとしても、私は驚かないでしょう。私はそれを受け入れることができます。
Azaral

上記の例では、元のデータは次のとおりです。playerX= 50、playerY = 100、playerVelocityX = 20、playerVelocityY = -4、enemyX = 145、enemyY = 67、enemyVelocityX = -5、enemyVelocityY = 10
Azaral

bulletSpeedこの時点では使用しないでください。交換してくださいcos(heading * DEGREE2RAD) * bulletSpeedshootx。も同じですshooty。その理由は、敵の速度が弾の速度に追加されるためbulletSpeedです。その速度は正確ではなくなります。実際、heading変数はまったく必要ありません。デバッグに役立ちます。
sam hocevar

2

シューターを動かすことは、シューターを静止させることと同じです。ターゲットの動きベクトルから射手の動きベクトルを引くだけです。

Target [-5,0]
Shooter [4,1]
Target - Shooter = [-5,0] - [4,1] = [-9,-1]

発砲ベクトル/初期角度を計算してから、通常のようにターゲットの移動ベクトルを弾丸に追加します。


私の質問を読んでコードを見ると、私はすでにこれを行っていることがわかります。
Azaral

@azaralはい、そしていいえ。あなたはこれをあなたの値の1つを取得するために行って、それから速度で
何かを

@ gardian06より具体的にできますか?
Azaral

@Azaral私はあなたが提供したリンクで説明されているように、あなたがシューティングゲームが静止しているときに動作するアルゴリズムを持っているという印象を受けました。その場合、それが機能しない場合、移動するシューティングゲームを心配する必要がある前に並べ替えを必要とするアルゴリズムにバグがあります(違いは大きくありませんが、単純な方がデバッグが容易です)
Daniel Carlsson

@DanielCarlssonあなたはそれが私が考えたことのない良い点だと知っています。アルゴリズムが機能したと思いました。
Azaral

1

再帰アルゴリズムを使用した予測ターゲティングの問題の解決策を考案して実装した例を次に示します。http//www.newarteest.com/flash/targeting.html (私は静止したシューティングゲームを持っていますが、同じアプローチがムービングシューター)

1ステップで計算する方が効率的であるため、他のいくつかの解決策を試してみる必要がありますが、私が思いついた解決策は、目標位置を推定し、その結果をアルゴリズムにフィードバックして新しいものを作成することでしたより正確な推定、数回繰り返す。

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


1
この答えの利点の1つは、正確な解決策がない場合でも、シューターがプレーヤーの位置を中心に発砲することです。+1。
sam hocevar

ええ

0

2D物理演算(Z速度なし)のみを使用しているため、この問題は大幅に簡略化できます。これを行う簡単な方法は、ワールド座標に対して相対的に動くソースとターゲットの両方について考えるのをやめ、ソースに対して相対的に動くターゲットだけを考えることです(そしてソースを静止したままにします)。

Vector TargetInitialPosition = new Vector ( target.X - source.X ,
                                            target.Y - source.Y );
Vector TargetApparentVelocity = new Vector( target.velocityX - source.velocityX ,
                                            target.velocityY - source.velocityY );

通常、弾丸の速度は射手の速度よりもはるかに速いため、通常、弾丸は独立していると想定されますが、ヘリコプターや戦闘機から発砲するなど、これが当てはまらない場合もあります。

次に、弾丸の速度を計算する必要があります。

// Your directional vector MUST be normalized...
Vector BulletVelocity = new Vector( source.directionX * Bullet::StaticSpeed + source.velocityX ,
                                     source.directionY * Bullet::StaticSpeed + source.velocityY );

あなたが抱えている問題は、弾丸がそれらに到達するまでにターゲットが動いていることです。

TargetPosition = TargetInitialPosition + TargetApparentVelocity * t
BulletPosition = BulletInitialPosition + BulletVelocity * t
               = BulletVelocity * t

TargetPosition == BulletPositionを解くと、弾丸がターゲットに当たるはずです。これで、未知数は3つ、方程式は2つだけになりました。一次導関数を取ることで「t」を削除できます。

TargetInitialPosition + ( TargetApparentVelocity - BulletVelocity ) * t == 0
dV / dt = TargetApparentVelocity - BulletVelocity

目標を達成するために、あなたは望みdV/dt == -TargetInitialPosition * kます。定数はX座標とY座標で同じである必要があり、弾丸がターゲットに当たるまでにかかる秒数です。

TargetApparentVelocity.X - BulletVelocity.X == k * -TargetInitialPosition.X
k = ( BulletVelocity.X - TargetApparentVelocity.X ) / TargetInitialPosition.X
----------------------
TargetApparentVelocity.Y - BulletVelocity.Y == k * -TargetInitialPosition.Y
k = ( BulletVelocity.Y - TargetApparentVelocity.Y ) / TargetInitialPosition.Y

それらを等しくする:

( BulletVelocity.X - TargetApparentVelocity.X ) / TargetInitialPosition.X
= ( BulletVelocity.Y - TargetApparentVelocity.Y ) / TargetInitialPosition.Y

または変数を展開するには:

( source.directionX * Bullet::StaticSpeed + source.velocityX - target.velocityX + source.velocityX ) / ( target.X - source.X )
 == ( source.directionY * Bullet::StaticSpeed + source.velocityY - target.velocityY + source.velocityY ) / ( target.Y - source.Y )

次に代数はあなたにあなたの最終的な方程式を与えます:

source.directionY = ( target.velocityY * ( source.X - target.X ) - 2 * source.velocityY * ( source.X - target.X ) + ( Bullet::Speed * source.directionX + 2 * source.velocityX - target.velocityX ) * ( source.Y - target.Y ) ) / ( Bullet::Speed * ( source.X - target.X ) )

次の部分は乱雑で、コードにどのように実装するかはあなた次第ですが、これを代入してベクトルを正規化するだけです。

sqrt( source.directionX ^ 2 + source.directionY ^ 2 ) == 1

結局、未知数が1つだけの方程式(source.directionX)になり、directionXの代わりにそれを解いて、代入してdirectionYを取得できます。

私はこのコードのいずれもテストしていませんし、私が行った数学上の間違いを指摘していただいてもかまいませんが、理論は正しいはずです:)。

幸運を。


はい、2Dのみです。今読んでいます。
Azaral

なぜ弾丸の速度にソース速度を追加するのですか?
sam hocevar

@SamHocevarそれが物理学の仕組みです。銃から発射された弾丸は、発砲した銃と同じ速度で始まります。次に、発砲アクションによって生成されたベクトルをこのベクトルに追加します。アクションは空間で行われているため、弾丸の速度を変更する空気はありません。右に移動しているときに空間内で銃を発射すると、速度を変更するまで、常に銃が目の前にあります。
Azaral

1
申し訳ありませんが、例で動作します。一般的なケースに抽象化するのは難しいです。(dV / dt = TargetApparentVelocity-BulletVelocity)までのすべてが理論であり、必要なコードはこの最後のいくつかのボックスだけです。私が思いついた代数は複雑になる方法ですが、Mathematicaはそれを解くことができました。一階微分を採用する理由は、弾丸が命中するのにかかる時間を気にせず、単に弾を発射させたいからです。
tyler.daniels 2012年

1
@Azaralは物理学の仕組みではありません。均一に移動する参照フレームでは、弾丸の速度を含むすべての速度からソース速度を差し引く必要があります。ターゲットに見かけの速度を使用する場合、弾丸にも見かけの速度を使用する必要があります。
sam hocevar

0

これは3Dジオメトリの問題です。

まず、シューターとターゲットの相対位置と相対速度が必要です。

Pos = TargetPos - ShooterPos
Vel = TargetVel - ShooterVel

次に、方程式を解く必要があります。

Pos + t * Vel = t * FireSpeed * [x , +-sqrt(1-x^2)]

以下のためtx

それは:

x = ( PosX + t * VelX ) / ( t * FireSpeed )

( PosY + t * VelY ) / ( t * FireSpeed ) = +-sqrt(1 - (( PosX + t * VelX ) / ( t * FireSpeed ))^2 )
=>
( PosY + t * VelY )^2 / ( t * FireSpeed )^2 = 1 - (( PosX + t * VelX ) / ( t * FireSpeed ))^2
=>
( PosY + t * VelY )^2 + ( PosX + t * VelX )^2 = ( t * FireSpeed )^2
<=>
( Dot(Vel,Vel) - FireSpeed^2 ) * t^2 + 2 * Dot(Vel,Pos) * t + Dot(Pos,Pos) = 0

これらの方程式の最後は、簡単な2次方程式であり、解く必要があります。肯定的な結果ごとに、結果を方程式に挿入します。

FireVector = Vel + Pos / t

これにより、可能な射撃ベクトルがすべて得られるはずですt。は、ショットにかかる時間です。

注あればそれFireSpeedの大きさよりも大きいVelだけで一つの解決策があるだろうが、あればFireSpeedあるだけ小さいシングル、ダブルソリューション2つのソリューションまたはnoneすべてで、あるいは特殊なケースでがあるかもしれません。

編集:数学を正しく行うと、この答えはあまり良くありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.