Wie kann ich ein Objekt basierend auf dem Versatz eines anderen Objektes drehen?

25

Ich habe ein 3D-Modell eines Turms, der sich um die Y-Achse dreht. Dieser Turm hat eine Kanone, die sich deutlich außerhalb der Mitte des Objekts befindet. Ich möchte, dass die Kanone und nicht der Turm auf ein bestimmtes Ziel zielen. Ich kann den Turm jedoch nur drehen und weiß daher nicht, welche Gleichung ich anwenden muss, um das Ziel zu erreichen.

Das folgende Bild zeigt mein Problem:Bildbeschreibung hier eingeben

Wenn ich den Revolver "LookAt ()" als Ziel habe, wird ein von der Kanone stammender Laser dieses Ziel vollständig verfehlen.

Wenn dies ein vollständig von oben nach unten gerichtetes Szenario wäre und die Kanone genau parallel zum Turm wäre, dann sagt mir meine Logik, dass sich das gefälschte Ziel an einer Position befinden sollte, die dem tatsächlichen Ziel entspricht, zuzüglich eines Versatzes, der dem zwischen den beiden entspricht Turm und die Kanone. In meinem aktuellen Szenario ist meine Kamera jedoch um 60 ° abgewinkelt, und die Kanone hat eine leichte Drehung.

Das folgende Bild zeigt das Szenario: Illustratives Szenario

Ich weiß nicht genau warum, aber wenn ich denselben Versatz anwende, scheint er nur zu funktionieren, wenn ich auf bestimmte Abstände vom Turm ziele.

Ist meine Logik fehlerhaft? Fehlt mir hier etwas Grundsätzliches?

Final Edit: Die Lösung des neuesten Updates von @JohnHamilton löst dieses Problem mit höchster Präzision. Ich habe jetzt den Code und die Bilder entfernt, die ich zur Veranschaulichung meiner falschen Implementierungen verwendet habe.

Franconstein
quelle
Aus Sicht des Waffendesigns können Sie einfach Ihre Waffe reparieren ;)
Wayne Werner
@WayneWerner das ist in meinem Fall keine Option. Es ist eine Designentscheidung, dass es schief, aber funktional ist.
Franconstein
1
Ich habe meiner Antwort ein funktionierendes Beispiel hinzugefügt .
ens
Offenbar sind die Antworten perfekt. Können Sie uns mitteilen, welche Details Sie genau benötigen?
Seyed Morteza Kamali

Antworten:

31

Die Antwort ist eigentlich ziemlich einfach, wenn Sie rechnen. Sie haben einen festen Abstand von Y und einen variablen Abstand von X (siehe Bild 1). Sie müssen den Winkel zwischen Z und X herausfinden und Ihren Revolver noch mehr drehen. Bildbeschreibung hier eingeben

Schritt 1 - Ermitteln Sie den Abstand zwischen der Revolverlinie (V) und der Kanonenlinie (W), der Y ist (dies ist konstant, schadet aber nicht bei der Berechnung). Ermitteln Sie die Entfernung vom Turm zum Ziel (X).

Schritt 2 - Teilen Sie Y durch X und erhalten Sie dann den Hyperbelsinus des Werts

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.

Schritt 3 - Drehen Sie den Turm noch weiter (um die Achse, die von oben nach unten verläuft, höchstwahrscheinlich um die obere Achse, aber nur Sie können diesen Teil erkennen).

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

In diesem Fall muss es natürlich gegen den Uhrzeigersinn gedreht werden, sodass möglicherweise ein Minus vor dem Drehwinkel hinzugefügt werden muss, wie in -turnAngle.

Einige Teile bearbeitet. Vielen Dank an @ens für den Hinweis auf den Unterschied in der Entfernung.

Der OP sagte, seine Waffe hat einen Winkel, also gehen wir, Bild zuerst, Erklärung später: Bildbeschreibung hier eingeben

Wir wissen bereits aus der vorherigen Berechnung, wohin die rote Linie mit der blauen Linie ausgerichtet werden soll. Also zuerst auf die blaue Linie zielen:

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

Die einzige Berechnung, die sich hier unterscheidet, ist die Berechnung von "X Prime" (X '), da der Winkel zwischen der Waffe und dem Turm (Winkel "a") den Abstand zwischen den Linien verändert hat.

//(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);

Dieser nächste Teil ist NUR erforderlich, wenn Sie die Revolverpistolen modular ausführen (dh der Benutzer kann die Pistolen auf einem Revolver wechseln und verschiedene Pistolen haben unterschiedliche Winkel). Wenn Sie dies im Editor tun, können Sie bereits sehen, welcher Schusswinkel dem Turm entspricht.

Es gibt zwei Methoden zum Ermitteln des Winkels "a", eine Methode ist die transform.up-Methode:

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

Die obige Technik wird in 3D berechnet. Wenn Sie also ein 2D-Ergebnis erhalten möchten, müssen Sie die Z-Achse entfernen (ich gehe davon aus, dass dort, wo sich die Schwerkraft befindet, aber wenn Sie nichts geändert haben, ist in Unity die Y-Achse nach oben oder unten gerichtet). Das heißt, die Schwerkraft liegt auf der Y-Achse.

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

Der zweite Weg ist die Rotationsmethode (ich denke in diesem Fall in 2D):

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

Alle diese Codes geben Ihnen wiederum positive Werte, so dass Sie möglicherweise den Betrag abhängig vom Winkel addieren oder subtrahieren müssen (es gibt auch Berechnungen dafür, aber ich werde nicht weiter darauf eingehen). Ein guter Ausgangspunkt hierfür wäre die Vector2.DotMethode in Unity.

Letzter Codeblock für zusätzliche Erklärungen:

//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);

Wenn du alles richtig gemacht hast, solltest du eine Szene wie diese bekommen ( Link für das Unitypackage ): Bildbeschreibung hier eingeben Was ich mit immer positiven Werten meine:Bildbeschreibung hier eingeben

Die Z-Methode kann negative Werte liefern:Bildbeschreibung hier eingeben

Für eine Beispielszene erhalten Sie das Unity-Paket über diesen Link .

Hier ist der Code, den ich in der Szene verwendet habe (auf dem Turm):

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

3D-angepasster Code mit X und Z als 2D-Ebene:

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.
    }
}
John Hamilton
quelle
Das erste Bild weist einen kleinen Fehler auf. Z ist die Länge des Revolvers zur Kiste. X ist die Länge des Revolvers zur Kiste nach der Drehung ... x = z. Es sei denn, y ist die Hypotenuse, die kein rechtwinkliges Dreieck ist, und Sünde gilt nicht.
The Great Duck
@TheGreatDuck Z ist nicht der Abstand zwischen dem Turm und der Kiste, sondern der Vektor2.vor diesem Turm (er ist nur endlich dargestellt, anstatt einen Pfeil am Ende zu haben). Selbst wenn Z der Abstand war, hat das Bild Einheiten und Sie können Z <X sehen, ohne auch nur zu berechnen.
John Hamilton
2
@Franconstein du musst nicht erst den Revolver drehen, dann diese anwenden. Sie können diese zuerst berechnen und dann den Grad, den Sie aus diesen Gleichungen erhalten, zum Umdrehungsgrad des Turms addieren. (Anstatt den Revolver um 20 Grad zum Objekt zu drehen und dann die Waffe einzustellen, drehen Sie den Revolver um 20 Grad + die Waffe).
John Hamilton
@Franconstein Siehe den neu eingestellten Code. Da wir die Flugzeuge gewechselt haben, verhielt sich der Code anders als in der anderen Version. Ich habe keine Ahnung, warum das passiert ist, aber es funktioniert jetzt perfekt auf meinem Ende. Siehe: imgur.com/a/1scEH (das Entfernen Ihrer Türme war nicht erforderlich, diese einfachen Modelle verhielten sich genauso wie Ihre).
John Hamilton
1
@JohnHamilton Du hast es geschafft! Es ist endlich gelöst, und das auch mit laserähnlicher Präzision! Vielen Dank! Vielen Dank! Vielen Dank! Ich werde meinen Beitrag jetzt so bearbeiten, wie er am Anfang war, damit er zum späteren Nachschlagen leichter verstanden werden kann! Nochmals vielen Dank!
Franconstein
3

Sie können auch einen allgemeineren Ansatz verwenden:

Die Mathematik für Ihr Problem existiert bereits in Form des Skalarprodukts (oder Punktprodukts) . Sie müssen nur die Richtung Ihrer Waffen-Vorwärtsachse und die Richtung von Ihrer Waffe zum Ziel ermitteln.

Sei W der Vorwärtsvektor deiner Waffe.

Lass D die Richtung von deiner Waffe zu deinem Ziel sein. (Target.pos - Weapon.pos)

Wenn Sie die Formel des Skalarprodukts lösen

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

Für Alpha erhalten Sie:

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

Sie müssen nur das Bogenmaß in Grad umrechnen und haben Ihren Winkel, um Ihren Roboter zu drehen. (Wie Sie bereits erwähnt haben, befindet sich die Waffe in einem Winkel zu Ihrem Roboter. Daher müssen Sie den Winkel zu Alpha hinzufügen.)

Bildbeschreibung hier eingebenBildbeschreibung hier eingeben

Wurzelmenü
quelle
2

Alle bisher veröffentlichten Antworten sind (mehr oder weniger) falsch, daher hier eine schnelle, korrekte Lösung:

Bildbeschreibung hier eingeben

Um die Waffe auf das Ziel zu richten, drehen Sie den Turmvektor vorwärts zum Ziel und addieren Sie den Winkel θ.

Also lasst uns θ finden:

   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.

Wenn sich δ' = 0dies vereinfacht θ = asin(a / d), entspricht das dem ersten Teil von John Hamiltons Antwort.

Bearbeiten:

Ich habe ein funktionierendes Beispiel hinzugefügt.

Öffnen Sie in JSFiddle oder verwenden Sie das folgende eingebettete Snippet:

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" />

ens
quelle
Vielen Dank für diese Erklärung. Für mich war es einfach genug zu verstehen, und es scheint jede Situation zu berücksichtigen. Als ich es jedoch implementierte, waren die Ergebnisse, die ich erhielt, nicht günstig. Ich habe meinen ursprünglichen Beitrag so bearbeitet, dass er meinen Code, ein Bild, das mein Setup darstellt, und die Ergebnisse für jede Variable enthält. Der Vorwärtsvektor meines Geschützturms zeigt immer auf das Ziel, aber auch wenn dies nicht der Fall ist, bleiben die Ergebnisse nahezu gleich. Mache ich etwas falsch? Ist es mein Code?
Franconstein
Wenn die anderen Antworten "mehr oder weniger falsch sind", verstehen / implementieren Sie sie nicht richtig. Ich habe zuvor beide alternativen Antworten verwendet, um das gewünschte Verhalten zu erstellen. @Franconstein, ich sehe in deinen Kommentaren zu mindestens einem sogar, dass du überprüft hast, ob es funktioniert. Wenn Sie eine Lösung überprüft haben, haben Sie noch ein Problem?
Gnemlock
@ Gnemlock, John Hamiltons Lösung war nicht falsch - ich habe sie implementiert und sie hat funktioniert, und daher habe ich seine Lösung als genehmigt bestätigt. Aber nach der Implementierung habe ich verschiedene nicht statische Szenarien ausprobiert, und die Lösung hat sich nicht bewährt. Ich wollte es aber nicht vorzeitig wegwerfen, also ging ich es mit einem Kollegen durch. Am Ende haben wir bestätigt, dass es nicht zutrifft, aber jetzt hat ens eine andere mögliche Lösung veröffentlicht und John hat seinen Beitrag bearbeitet, um ihn aufzunehmen. Derzeit kann ich nicht bestätigen, dass eine der beiden Funktionen ordnungsgemäß funktioniert, und ich versuche es immer noch. Ich habe meinen Code gepostet, um zu sehen, ob er hilft. Habe ich falsch gemacht
Franconstein
@Franconstein, in dieser Form ist es zu verwirrend. Ich würde sagen, dieses Beispiel ist ein gutes Beispiel für das, was man erwartet, wenn man ein Mathematik- Lehrbuch liest , aber es ist in Bezug auf die allgemeine Spielprogrammierung unerträglich verwirrend. Das einzig wichtige Element ist der Winkel (den John Hamilton in seiner ursprünglichen Antwort angegeben hat). Ich verstehe, was Sie unter bestimmten Blickwinkeln verstehen. Letztendlich haben Sie dies möglicherweise falsch gemacht. Ich finde, dass es in dieser Antwort viel Raum gibt, es falsch zu machen .
Gnemlock