他のオブジェクトのオフセットに基づいてオブジェクトを回転するにはどうすればよいですか?


25

Y軸を中心に回転するタレットの3Dモデルがあります。この砲塔には、オブジェクトの中心から大きく外れた大砲があります。砲塔ではなく大砲で指定されたターゲットを狙います。ただし、タレットを回転させることしかできないため、目的ごとに達成するためにどの方程式を適用する必要があるかわかりません。

次の画像は私の問題を示しています。ここに画像の説明を入力してください

ターゲットに砲塔「LookAt()」がある場合、大砲から発するレーザーは、そのターゲットを完全に見逃してしまいます。

これが完全にトップダウンのシナリオであり、大砲が砲塔と正確に平行である場合、私のロジックは、偽のターゲットは実際のターゲットに等しい位置に加えて、砲塔と大砲。しかし、実際のシナリオでは、カメラの角度は60度であり、大砲はわずかに回転しています。

次の図は、シナリオを示しています。 例示的なシナリオ

正確な理由はわかりませんが、同じオフセットを適用すると、タレットから特定の距離を狙っているときにしか機能しないようです。

ロジックに欠陥がありますか?ここに基本的なものがありませんか?

最終編集:@JohnHamiltonの最新アップデートが提供するソリューションは、この問題を完全に正確に解決します。これで、誤った実装を説明するために使用したコードと画像を削除しました。


武器の設計の観点から、あなたはあなたの銃をただ修理することができました;)
ウェインワーナー

@WayneWernerこれは私の場合、オプションではありません。曲がっているが機能的であることが設計上の選択です。
フランコンシュタイン16

1
私は答えに実用的な例を追加しました。
ens

答えは完璧だと思われます...正確にどの詳細が必要ですか?
セイドモルテザカマリ

回答:


31

あなたが数学をすれば、答えは実際には非常に簡単です。Yの固定距離とXの可変距離があります(図1を参照)。ZとXの間の角度を見つけて、砲台をさらに回転させる必要があります。 ここに画像の説明を入力してください

ステップ1-タレットライン(V)とガンライン(W)の間の距離を取得します。これはYです(これは一定ですが、計算しても害はありません)。砲塔からターゲット(X)までの距離を取得します。

ステップ2-YをXで除算し、値の双曲線正弦を取得します

double turnRadians = Mathf.Asin(Y/X);
double angle = Mathf.Rad2Deg * turnRadians;

//where B is the red dot, A is a point on the X line and C is a point on the Z line.

ステップ3-タレットをそれ以上回転させます(上部から下部に向かう軸の周り、最も可能性の高い軸ですが、あなただけがその部分を知ることができます)。

gameObject.transform.Rotate(Vector3.up, turnAngle);

もちろん、この場合は、反時計回りに回転する必要があるため、のように、そこでturnAngleの前にマイナスを追加する必要があります-turnAngle

一部を編集しました。距離の違いを指摘してくれた@ensに感謝します。

OPは、彼の銃には角度があると言ったので、ここで最初に説明します。 ここに画像の説明を入力してください

前の計算から、赤い線を青い線に合わせてどこに向けるかがすでにわかっています。だから、最初に青い線を目指して:

float turnAngle = angleBetweenTurretAndTarget - angleBetweenTurretAndGun;
turret.transform.Rotate(Vector3.up, turnAngle);

ここで異なる唯一の計算は、「Xプライム」(X ')の計算です。これは、銃と砲塔間の角度(角度「a」)がライン間の距離を変更したためです。

//(this part had a mistake of using previous code inside new variable names, YPrime and Y are shown as X' and X in the 2nd picture.
float YPrime = Cos(a)*Y; //this part is what @ens is doing in his answer
double turnRadians = Mathf.Asin(YPrime/X);
double angle = Mathf.Rad2Deg * turnRadians;
turret.transform.Rotate(Vector3.up, angle);

この次の部分は、砲塔ガンをモジュラーで行う場合にのみ必要です(つまり、ユーザーは砲塔の銃を変更でき、異なる銃は異なる角度を持っています)。エディターでこれを行っている場合、砲塔に応じて銃の角度が何であるかをすでに確認できます。

角度「a」を見つけるには2つの方法があります。1つはtransform.upメソッドです。

float angleBetween = Vector3.Angle(turret.transform.up, gun.transform.up);

上記の手法は3Dで計算されるため、2Dの結果が必要な場合は、Z軸を取り除く必要があります(重力がどこにあるかを想定しますが、何も変更しない場合、UnityではY軸が上下し、すなわち、重力はY軸上にあるので、物事を変更する必要があるかもしれません):

Vector2 turretVector = new Vector2(turret.transform.up.x, turret.transform.up.y);
Vector2 gunVector = new Vector2(gun.transform.up.x, gun.transform.up.y);
float angleBetween = Vector2.Angle(turretVector, gunVector);

2番目の方法は回転方法です(この場合は2Dで考えています)。

double angleRadians = Mathf.Asin(turret.transform.rotation.z - gun.transform.rotation.z);
double angle = 2 * Mathf.Rad2Deg * angleRadians;

繰り返しますが、これらのすべてのコードは正の値を提供するため、角度に応じて量を加算または減算する必要がある場合があります(そのための計算もありますが、詳細に説明するつもりはありません)。これから始めるのに適した場所Vector2.Dotは、Unity のメソッドです。

私たちがやっていることの追加説明のためのコードの最終ブロック:

//turn turret towards target
turretTransform.up = targetTransform.position - turretTransform.position;
//adjust for gun angle
if (weaponTransform.localEulerAngles.z <180) //if the value is over 180 it's actually a negative for us
    turretTransform.Rotate(Vector3.forward, 90 - b - a);
else
    turretTransform.Rotate(Vector3.forward, 90 - b + a);

すべてを正しく行った場合、次のようなシーンが得られるはずです(unitypackageのリンク): ここに画像の説明を入力してください 常に正の値が意味すること:ここに画像の説明を入力してください

Zメソッドは負の値を与えることができます:ここに画像の説明を入力してください

サンプルシーンについては、このリンクからunitypackageを取得してください

シーンで使用したコードは次のとおりです(タレット上)。

public class TurretAimCorrection : MonoBehaviour
{
    public Transform targetTransform;
    public Transform turretTransform;
    public Transform weaponTransform;

    private float f, d, x, y, h, b, a, weaponAngle, turnAngle;
    private void Start()
    {
        TurnCorrection();
    }

    private void Update()
    {
        TurnCorrection();
    }
    void TurnCorrection()
    {
        //find distances and angles
        d = Vector2.Distance(new Vector2(targetTransform.position.x, targetTransform.position.y), new Vector2(turretTransform.position.x, turretTransform.position.y));
        x = Vector2.Distance(new Vector2(turretTransform.position.x, turretTransform.position.y), new Vector2(weaponTransform.position.x, weaponTransform.position.y));
        weaponAngle = weaponTransform.localEulerAngles.z;
        weaponAngle = weaponAngle * Mathf.Deg2Rad;
        y = Mathf.Abs(Mathf.Cos(weaponAngle) * x);
        b = Mathf.Rad2Deg * Mathf.Acos(y / d);
        a = Mathf.Rad2Deg * Mathf.Acos(y / x);
        //turn turret towards target
        turretTransform.up = targetTransform.position - turretTransform.position;
        //adjust for gun angle
        if (weaponTransform.localEulerAngles.z < 180)
            turretTransform.Rotate(Vector3.forward, 90 - b - a);
        else
            turretTransform.Rotate(Vector3.forward, 90 - b + a);
        //Please leave this comment in the code. This code was made by 
        //http://gamedev.stackexchange.com/users/93538/john-hamilton a.k.a. CrazyIvanTR. 
        //This code is provided as is, with no guarantees. It has worked in local tests on Unity 5.5.0f3.
    }
}

XとZを2D平面とする3D適応コード:

public class TurretAimCorrection : MonoBehaviour
{
    public Transform targetTransform; //drag target here
    public Transform turretTransform; //drag turret base or turret top part here
    public Transform weaponTransform; //drag the attached weapon here

    private float d, x, y, b, a, weaponAngle, turnAngle;
    private void Start()
    {
        TurnAdjustment();
    }

    private void Update()
    {
        TurnAdjustment();
    }
    void TurnAdjustment()
    {

        d = Vector2.Distance(new Vector2(targetTransform.position.x, targetTransform.position.z), new Vector2(turretTransform.position.x, turretTransform.position.z));
        x = Vector2.Distance(new Vector2(turretTransform.position.x, turretTransform.position.z), new Vector2(weaponTransform.position.x, weaponTransform.position.z));
        weaponAngle = weaponTransform.localEulerAngles.y;
        weaponAngle = weaponAngle * Mathf.Deg2Rad;
        y = Mathf.Abs(Mathf.Cos(weaponAngle) * x);
        b = Mathf.Rad2Deg * Mathf.Acos(y / d);
        a = Mathf.Rad2Deg * Mathf.Acos(y / x);
        //turn turret towards target
        turretTransform.forward = new Vector3(targetTransform.position.x, 0, targetTransform.position.z) - new Vector3(turretTransform.position.x, 0, turretTransform.position.z);
        //adjust for gun angle
        if (weaponTransform.localEulerAngles.y < 180)
            turretTransform.Rotate(Vector3.up, - a +b-90);
        else
            turretTransform.Rotate(Vector3.up, + a+ b - 90);
        //Please leave this comment in the code. This code was made by 
        //http://gamedev.stackexchange.com/users/93538/john-hamilton a.k.a. CrazyIvanTR. 
        //This code is provided as is, with no guarantees. It has worked in local tests on Unity 5.5.0f3.
    }
}

最初の画像にはわずかな欠陥があります。Zは、ボックスまでのタレットの長さです。Xは、回転後のボックスまでのタレットの長さです... x = z。したがって、yが直角三角形ではない斜辺であり、sinが適用されない場合を除きます。
グレートダック

@TheGreatDuck Zは、タレットとボックスの間の距離ではなく、そのタレットのVector2.forwardです(最後に矢印が表示される代わりに、有限で表示されます)。Zが距離であったとしても、写真には単位があり、計算することなくZ <Xであることがわかります。
ジョンハミルトン

2
@Franconsteinでは、最初に砲塔を回してから適用する必要はありません。最初にこれらを計算してから、これらの方程式から得られる次数をタレットの回転次数に追加できます。(つまり、砲塔をオブジェクトに対して20度回転させてから、銃を調整する代わりに、砲塔を20度回転させ、銃を調整します)。
ジョンハミルトン

@Franconstein新しく調整されたコードを参照してください。プレーンを変更したため、コードは他のバージョンとは異なる動作をしていました。なぜこれが起こったのか分かりませんが、今では私の目的に完全に取り組んでいます。imgur.com/a/1scEHを参照してください(タレットを取り外す必要はありませんでした。これらの単純なモデルは、あなたのものと同じように機能しました)。
ジョンハミルトン

1
@JohnHamiltonやりました!最終的に解決され、レーザーのような精度もあります!ありがとうございました!ありがとうございました!ありがとうございました!投稿を最初の状態に編集しますので、今後の参考のためにより簡単に理解できます!もう一度、ありがとう!
フランコンシュタイン16

3

より一般的なアプローチも使用できます。

問題の数学は、スカラー積(またはドット積)の形で既に存在します。武器の進行方向と、武器からターゲットへの方向のみを取得する必要があります。

Wを武器の前方ベクトルにします。

Dを武器からターゲットへの方向にします。(Target.pos-Weapon.pos)

内積の公式を解くと

dot(A,B) = |A|*|B|*cos(alpha) with alpha = the angle between A and B

アルファの場合、以下が得られます。

              ( dot(W,D) )
alpha = arccos(--------- )
              ( |W|*|D|  )

ラジアンを度に変換するだけで、ロボットを回転させる角度が得られます。(先ほど述べたように、武器はロボットに対して斜めになっているため、アルファに角度を追加する必要があります)

ここに画像の説明を入力してくださいここに画像の説明を入力してください


2

これまでに投稿されたすべての回答は(多かれ少なかれ)間違っているので、ここに簡単な正しい解決策があります:

ここに画像の説明を入力してください

銃をターゲットに向けるには、ターレットの前方ベクトルをターゲットに回転させ、角度θを追加します。

それでは、θを見つけましょう。

   d / sin(δ) = a / sin(α) # By the Law of Sines
=> α = asin(a * sin(δ) / d)

   β = 180° - α - δ
   θ = 90° - β
=> θ = α + δ - 90°
     = asin(a * sin(δ) / d) + δ - 90°
     = asin(a * cos') / d) - δ' # Where (δ' = 90° - δ) is the angle between 
                                  # the gun and the turret forward vector.

ときにδ' = 0、これは簡素化しθ = asin(a / d)、ジョン・ハミルトンの答えの最初の部分と一致しています。

編集:

実用的な例を追加しました。

JSFiddleで開くか、以下の埋め込みスニペットを使用します。

var Degree = Math.PI / 180;
var showDebugOverlays = false;

var Turret = {
    gunAngle: -10 * Degree,
    maxGunAngle: 85 * Degree,
    adaptedGunXOffset: null,

    rotateToTarget: function (target) {
        var delta = Vec.subtract(this.position, target.position);
        var dist = Vec.length(delta);
        var angle = Vec.angle(delta);

        theta = Math.asin(this.adaptedGunXOffset / dist) + this.gunAngle;
        this.rotation = -(angle + theta);

        this.updateGunRay(target);
    },

    setGunAngle: function (angle) {
        var angle = this.clampGunAngle(angle);
        this.gunAngle = angle;
        this.gun.rotation = angle;
        // Account for the fact that the origin of the gun also has an y offset
        // relative to the turret origin
        var extraXOffset = this.gun.position.y * Math.tan(angle);
        var gunXOffset = this.gun.position.x + extraXOffset;
        // This equals "a * cos(δ')" in the angle formula
        this.adaptedGunXOffset = gunXOffset * Math.cos(-angle);

        if (showDebugOverlays) {
            // Show x offsets
            this.removeChild(this.xOffsetOverlay);
            this.removeChild(this.extraXOffsetOverlay);
            this.xOffsetOverlay = addRect(this, 0, 0, this.gun.position.x, 1, 0xf6ff00);
            this.extraXOffsetOverlay = addRect(this, this.gun.position.x, 0, extraXOffset, 1, 0xff00ae);
        }
    },

    rotateGun: function (angleDelta) {
        this.setGunAngle(this.gunAngle + angleDelta);
    },

    updateGunRay: function (target) {
        var delta = this.gun.toLocal(target.position);
        var dist = Vec.length(delta);
        this.gun.removeChild(this.gun.ray);
        this.gun.ray = makeLine(0, 0, 0, -dist);
        this.gun.addChildAt(this.gun.ray, 0);
    },

    clampGunAngle: function (angle) {
        if (angle > this.maxGunAngle) {
            return this.maxGunAngle;
        }
        if (angle < -this.maxGunAngle) {
            return -this.maxGunAngle;
        }
        return angle;
    }
}

function makeTurret() {
    var turret = new PIXI.Sprite.fromImage('http://i.imgur.com/gPtlPJh.png');
    var gunPos = new PIXI.Point(25, -25)

    turret.anchor.set(0.5, 0.5);

    var gun = new PIXI.Container();
    var gunImg = new PIXI.Sprite.fromImage('http://i.imgur.com/RE45GEY.png');
    gun.ray = makeLine(0, 0, 0, -250);
    gun.addChild(gun.ray);

    gunImg.anchor.set(0.5, 0.6);
    gun.addChild(gunImg);
    gun.position = gunPos;

    // Turret forward vector
    turret.addChild(makeLine(0, -38, 0, -90, 0x38ce2c));
    turret.addChild(gun);
    turret.gun = gun;

    Object.setPrototypeOf(Turret, Object.getPrototypeOf(turret));
    Object.setPrototypeOf(turret, Turret);

    turret.setGunAngle(turret.gunAngle);

    if (showDebugOverlays) {
        addRect(turret, 0, 0, 1, 1); // Show turret origin
        addRect(gun, -1, 1, 2, 2, 0xff0096); // Show gun origin
    }

    return turret;
}

function makeTarget() {
    var target = new PIXI.Graphics();
    target.beginFill(0xd92f8f);
    target.drawCircle(0, 0, 9);
    target.endFill();
    return target;
}

var CursorKeys = {
    map: { ArrowLeft: -1, ArrowRight: 1 },
    pressedKeyDirection: null,

    onKeyDown: function (keyEvent) {
        var key = this.map[keyEvent.key];
        if (key) {
            this.pressedKeyDirection = key;
        }
    },
    onKeyUp: function (keyEvent) {
        var key = this.map[keyEvent.key];
        if (key) {
            if (this.pressedKeyDirection == key) {
                this.pressedKeyDirection = null;
            }
        }
    }
}

document.body.addEventListener("keydown", CursorKeys.onKeyDown.bind(CursorKeys));
document.body.addEventListener("keyup", CursorKeys.onKeyUp.bind(CursorKeys));

function makeLine(x1, y1, x2, y2, color) {
    if (color == undefined) {
        color = 0x66CCFF;
    }
    var line = new PIXI.Graphics();
    line.lineStyle(1.5, color, 1);
    line.moveTo(x1, y1);
    line.lineTo(x2, y2);
    return line;
}

function addRect(parent, x, y, w, h, color) {
    if (color == undefined) {
        color = 0x66CCFF;
    }
    var rectangle = new PIXI.Graphics();
    rectangle.beginFill(color);
    rectangle.drawRect(x, y, w, h);
    rectangle.endFill();
    parent.addChild(rectangle);
    return rectangle;
}

var Vec = {
    subtract: function (a, b) {
        return { x: a.x - b.x,
                 y: a.y - b.y };
    },
    length: function (v) {
        return Math.sqrt(v.x * v.x + v.y * v.y);
    },
    angle: function (v) {
        return Math.atan2(v.x, v.y)
    }
}

Math.clamp = function(n, min, max) {
    return Math.max(min, Math.min(n, max));
}

var renderer;
var stage;
var turret;
var target;

function run() {
    renderer = PIXI.autoDetectRenderer(600, 300, { antialias: true });
    renderer.backgroundColor = 0x2a2f34;
    document.body.appendChild(renderer.view);
    stage = new PIXI.Container();
    stage.interactive = true;

    target = makeTarget();
    target.position = { x: renderer.width * 0.2, y: renderer.height * 0.3 };

    turret = makeTurret();
    turret.position = { x: renderer.width * 0.65, y: renderer.height * 0.3 };
    turret.rotateToTarget(target);

    stage.addChild(turret);
    stage.addChild(target);

    var message = new PIXI.Text(
        "Controls: Mouse, left/right cursor keys",
        {font: "18px Arial", fill: "#7c7c7c"}
    );
    message.position.set(10, 10);
    stage.addChild(message);

    stage.on('mousemove', function(e) {
        var pos = e.data.global;
        target.position.x = Math.clamp(pos.x, 0, renderer.width);
        target.position.y = Math.clamp(pos.y, 0, renderer.height);
        turret.rotateToTarget(target);
    })

    animate();
}

function animate() {
    requestAnimationFrame(animate);

    if (CursorKeys.pressedKeyDirection) {
        turret.rotateGun(3 * Degree * CursorKeys.pressedKeyDirection);
        turret.rotateToTarget(target);
    }

    renderer.render(stage);
}

run();
body {
    padding: 0;
    margin: 0;
}

#capture_focus {
  position: absolute;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.2.2/pixi.min.js"></script>
<div id="capture_focus" />


この説明をありがとうございます。それは私が理解するのに十分簡単で、あらゆる状況を考慮に入れているようです。しかし、それを実装したとき、得られた結果は好ましくありませんでした。元の投稿を編集して、コード、セットアップを視覚化した画像、各変数の結果を含めました。私の砲塔の前方ベクトルは常にターゲットを見ていますが、たとえそうでなくても、結果はほぼ同じままです。私は何か間違っていますか?それは私のコードですか?
フランコンシュタイン16

他の回答が「多かれ少なかれ間違っている」場合、それらを正しく理解/実装していません。希望する動作を作成するために、以前は両方の代替回答を使用しました。@Franconstein、私はあなたがそれが機能していることを確認したと言って、少なくとも 1つにあなたのコメントを見ます。ソリューションを検証した場合、まだ問題がありますか?
Gnemlock 16

@Gnemlock、ジョン・ハミルトンのソリューションは間違っていませんでした-私はそれを実装し、それが機能したので、彼のソリューションが承認されていることを検証しました。しかし、それを実装した後、私はさまざまな非静的なシナリオを試し始めましたが、ソリューションは持ちこたえませんでした。しかし、私は時期尚早にそれを破棄したくなかったので、同僚と一緒に調べました。最終的にはそれが成り立たないことを確認しましたが、今度はensが別の可能な解決策を投稿し、Johnは彼の投稿を編集してそれを含めました。この時点で、どちらも正常に動作することを確認できず、現在も試みています。役立つかどうかを確認するためにコードを投稿しました。私は間違っていましたか?
フランコンシュタイン16

@Franconstein、この形式では非常に混乱しています。この例は、数学の教科書を読むことを期待することの良い例ですが、一般的なゲームプログラミングに関しては圧倒的に紛らわしいです。唯一の重要な要素は角度です(John Hamiltonが投稿した元の回答はそれを提供しました)。あなたが特定の角度で何を意味しているのかがわかります、最終的にあなたこれを間違ってしているかもしれません。この答えには、それを間違って行う余地がたくさんあると思います。
Gnemlock
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.