Bagaimana saya bisa memutar objek berdasarkan offset orang lain?

25

Saya memiliki model 3D menara yang berputar di sekitar sumbu Y. Menara ini memiliki meriam yang jauh dari pusat objek. Saya ingin meriam, bukan menara, untuk membidik target yang ditentukan. Namun, saya hanya dapat memutar turet, dan karenanya saya tidak tahu persamaan apa yang perlu saya terapkan untuk menyelesaikan dengan objektif.

Gambar berikut menggambarkan masalah saya:masukkan deskripsi gambar di sini

Jika saya memiliki menara "LookAt ()" target, laser yang berasal dari meriam akan benar-benar kehilangan target kata.

Jika ini benar-benar skenario top-down, dan meriam itu persis sejajar dengan menara, maka logika saya memberi tahu saya bahwa target palsu harus ditempatkan pada posisi yang sama dengan target aktual ditambah offset yang sama dengan yang di antara menara dan meriam. Namun, dalam skenario saya yang sebenarnya, kamera saya miring 60º, dan meriam memiliki sedikit rotasi.

Gambar berikut menggambarkan skenario: Skenario Ilustrasi

Saya tidak yakin persis mengapa, tetapi jika saya menerapkan offset yang sama, itu hanya berfungsi saat membidik jarak tertentu dari turet.

Apakah logika saya cacat? Apakah saya melewatkan sesuatu yang mendasar di sini?

Edit Terakhir: solusi yang disediakan oleh @JohnHamilton, pembaruan terbaru memecahkan masalah ini dengan presisi sempurna. Saya sekarang telah menghapus kode dan gambar yang saya gunakan untuk menggambarkan implementasi yang salah saya.

Franconstein
sumber
Dari sudut pandang desain senjata, Anda hanya bisa memperbaiki senjatamu ;)
Wayne Werner
@WayneWerner ini bukan opsi dalam kasus saya. Ini adalah pilihan desain untuk membuatnya bengkok, tetapi fungsional.
Franconstein
1
Saya telah menambahkan contoh kerja untuk jawaban saya .
ens
Tampaknya jawabannya sempurna ... dapatkah Anda menyebutkan detail apa yang Anda butuhkan sebenarnya?
Seyed Morteza Kamali

Jawaban:

31

Jawabannya sebenarnya cukup mudah jika Anda melakukan perhitungan. Anda memiliki jarak tetap Y dan jarak variabel X (Lihat Gambar 1). Anda perlu mengetahui sudut antara Z dan X dan memutar menara Anda lebih dari itu. masukkan deskripsi gambar di sini

Langkah 1 - Dapatkan jarak antara garis menara (V) dan garis pistol (W) yang merupakan Y (ini konstan tetapi tidak ada salahnya untuk menghitung). Dapatkan jarak dari turet ke target (yaitu X).

Langkah 2 - Bagi Y dengan X dan kemudian dapatkan nilai Hyperbolic Sine

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.

Langkah 3 - Putar turret lebih dari itu (di sekitar sumbu yang bergerak dari atas ke bawah, kemungkinan besar sumbu atas tetapi hanya Anda yang dapat mengetahui bagian itu).

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

Tentu saja dalam kasus ini, Anda perlu memutar berlawanan arah jarum jam sehingga Anda mungkin perlu menambahkan minus di depan turnAngle di sana, seperti pada -turnAngle.

Mengedit beberapa bagian. Terima kasih kepada @ens untuk menunjukkan perbedaan jarak.

OP mengatakan senjatanya memiliki sudut jadi di sini kita pergi, gambar dulu, penjelasan nanti: masukkan deskripsi gambar di sini

Kita sudah tahu dari perhitungan sebelumnya di mana mengarahkan garis merah sesuai dengan garis biru. Jadi membidik garis biru pertama:

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

Satu-satunya perhitungan yang berbeda di sini, adalah perhitungan "X Prime" (X ') karena sudut antara pistol dan turret (sudut "a") mengubah jarak antara garis.

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

Bagian selanjutnya ini HANYA diperlukan jika Anda melakukan modular senjata meriam (yaitu pengguna dapat mengubah senjata pada menara dan senjata yang berbeda memiliki sudut pandang yang berbeda). Jika Anda melakukan ini di editor, Anda sudah bisa melihat apa sudut pistol sesuai dengan menara.

Ada dua metode untuk menemukan sudut "a", satu adalah metode transform.up:

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

Teknik di atas akan menghitung dalam 3D, jadi jika Anda menginginkan hasil 2D, Anda harus menyingkirkan sumbu Z (itulah yang saya asumsikan di mana gravitasi berada, tetapi jika Anda tidak mengubah apa pun, di Unity itu sumbu Y yang naik atau turun, yaitu gravitasi ada di sumbu Y, jadi Anda mungkin harus mengubah keadaan):

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

Cara kedua adalah metode rotasi (saya berpikir dalam 2D ​​dalam kasus ini):

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

Sekali lagi, semua kode ini akan memberi Anda nilai-nilai yang positif, jadi Anda mungkin harus menambah atau mengurangi jumlah tergantung pada sudut (ada perhitungan untuk itu juga, tapi saya tidak akan pergi yang mendalam). Tempat yang baik untuk memulai ini adalah Vector2.Dotmetode di Unity.

Blok kode terakhir untuk penjelasan tambahan tentang apa yang kami lakukan:

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

Jika Anda melakukan semuanya dengan benar, Anda harus mendapatkan adegan seperti ini ( tautan untuk paket unity ): masukkan deskripsi gambar di sini Yang saya maksud dengan selalu nilai positif:masukkan deskripsi gambar di sini

Metode Z dapat memberikan nilai negatif:masukkan deskripsi gambar di sini

Untuk contoh adegan, dapatkan paket unity dari tautan ini .

Berikut kode yang saya gunakan dalam adegan (di menara):

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

Kode yang diadaptasi 3D dengan X dan Z sebagai bidang 2D:

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
sumber
Ada sedikit cacat pada gambar pertama. Z adalah panjang turret ke kotak. X adalah panjang turret ke kotak setelah rotasi ... x = z. Karena itu, kecuali y adalah sisi miring yang bukan segitiga siku-siku dan dosa tidak berlaku.
The Great Duck
@TheGreatDuck Z bukan jarak antara turret dan kotak itu adalah Vector2.forward dari turret itu (itu hanya ditunjukkan hingga bukannya memiliki panah di akhir). Bahkan jika Z adalah jarak, gambar memiliki satuan dan Anda dapat melihat bahwa Z <X bahkan tanpa menghitung.
John Hamilton
2
@ Franconstein Anda tidak harus pertama-tama memutar menara, lalu terapkan ini. Pertama-tama Anda dapat menghitung ini, lalu menambahkan derajat yang Anda dapatkan dari persamaan ini ke tingkat putaran menara. (Jadi, alih-alih memutar menara 20 derajat ke objek, lalu menyesuaikan untuk pistol, Anda akan memutar menara dengan 20 derajat + penyesuaian untuk pistol).
John Hamilton
@ Franconstein Lihat kode yang baru disesuaikan. Karena kami mengubah pesawat, kodenya bertindak berbeda dari pada versi lainnya. Saya tidak tahu mengapa ini terjadi tetapi itu bekerja dengan baik pada saya sekarang. Lihat: imgur.com/a/1scEH (menghapus menara Anda tidak diperlukan, model-model sederhana itu bertindak dengan cara yang sama seperti milik Anda).
John Hamilton
1
@ JohnHamilton Anda berhasil! Akhirnya diselesaikan, dan dengan presisi seperti laser juga! Terima kasih! Terima kasih! Terima kasih! Sekarang saya akan mengedit posting saya seperti di awal, sehingga dapat lebih mudah dipahami untuk referensi di masa mendatang! Sekali lagi terima kasih!
Franconstein
3

Anda juga bisa menggunakan pendekatan yang lebih umum:

Matematika untuk masalah Anda sudah ada dalam bentuk skalarproduk (atau produk titik) . Anda hanya perlu mendapatkan arah sumbu senjata Anda ke depan dan arah dari senjata Anda ke target.

Biarkan W menjadi vektor maju senjata Anda.

Biarkan D menjadi arah dari senjata Anda ke target Anda. (Target.pos - Weapon.pos)

Jika Anda memecahkan formula produk titik

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

untuk alpha, Anda mendapatkan:

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

Anda hanya perlu mengubah radian ke derajat dan Anda punya sudut untuk memutar robot Anda. (Seperti yang Anda sebutkan senjata berada pada sudut ke robot Anda, jadi Anda perlu menambahkan sudut ke alpha)

masukkan deskripsi gambar di sinimasukkan deskripsi gambar di sini

rootmenu
sumber
2

Semua jawaban yang diposting sejauh ini (kurang lebih) salah, jadi inilah solusi cepat yang benar:

masukkan deskripsi gambar di sini

Untuk mengarahkan pistol ke target, putar vektor forward turret ke target dan tambahkan sudut θ.

Jadi mari kita cari θ:

   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.

Ketika δ' = 0ini disederhanakan θ = asin(a / d), yang cocok dengan bagian pertama dari jawaban John Hamilton.

Edit:

Saya telah menambahkan contoh yang berfungsi.

Buka di JSFiddle atau gunakan cuplikan tertanam di bawah:

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

kemudian
sumber
Terima kasih banyak atas penjelasannya. Itu cukup sederhana untuk saya pahami, dan tampaknya memperhitungkan setiap situasi. Namun, ketika saya menerapkannya, hasil yang saya peroleh tidak menguntungkan. Saya telah mengedit posting asli saya untuk memasukkan kode saya, gambar memvisualisasikan pengaturan saya, dan hasil untuk setiap variabel. Vektor maju menara saya selalu melihat target, tetapi bahkan jika tidak, hasilnya tetap hampir sama. Apakah saya melakukan sesuatu yang salah? Apakah ini kode saya?
Franconstein
Jika jawaban lain "kurang lebih salah", Anda tidak mengerti / menerapkannya dengan benar. Saya telah menggunakan kedua jawaban alternatif, sebelumnya, untuk membuat perilaku yang diinginkan. @ Franconstein, saya bahkan melihat komentar Anda di atleast satu untuk mengatakan Anda telah memverifikasi bahwa itu berfungsi. Jika Anda telah memverifikasi solusi, apakah Anda masih memiliki masalah?
Gnemlock
@ Gnemlock, solusi John Hamilton tidak salah - saya menerapkannya, dan itu berhasil, dan dengan demikian saya memverifikasi solusinya sebagai disetujui. Tetapi setelah menerapkannya, saya mulai mencoba berbagai skenario non-statis, dan solusinya tidak bertahan. Saya tidak ingin membuangnya terlalu dini, jadi saya membahasnya dengan seorang rekan. Kami akhirnya memastikan itu tidak berlaku, tetapi sekarang kami memposting solusi lain yang mungkin, dan John mengedit postingnya untuk memasukkannya. Sampai saat ini, saya tidak dapat mengkonfirmasi keduanya bekerja dengan benar, dan saya masih berusaha. Saya memposting kode saya untuk melihat apakah itu membantu. Apakah saya salah?
Franconstein
@ Franconstein, dalam bentuk ini terlalu membingungkan. Saya akan mengatakan contoh ini adalah contoh yang baik dari apa yang Anda harapkan membaca buku teks Matematika , tetapi terlalu membingungkan dalam kaitannya dengan pemrograman game umum. Satu-satunya elemen penting adalah sudut (yang diberikan oleh jawaban asli Hamilton Hamilton). Saya melihat apa yang Anda maksud dengan sudut tertentu, pada akhirnya Anda mungkin telah melakukan ini dengan salah. Saya menemukan ada banyak ruang, dalam jawaban ini, untuk melakukannya secara tidak benar .
Gnemlock