Bir nesneyi diğerinin ofsetine dayanarak nasıl döndürebilirim?


25

Y ekseni etrafında dönen taretin 3B modeline sahibim. Bu taret, cismin merkezinden önemli ölçüde uzakta olan bir top atar. Topun, taretin değil, belirli bir hedefi hedeflemesini istiyorum. Ancak tareti yalnızca döndürebiliyorum ve böylece objektif olarak gerçekleştirmek için hangi denklemi uygulamam gerektiğini bilmiyorum.

Aşağıdaki resim sorunumu göstermektedir:görüntü tanımını buraya girin

Eğer hedef "LookAt ()" taretine sahipsem, toptan çıkan bir lazer, bahsedilen hedefi tamamen özleyecektir.

Bu tamamen yukarıdan aşağıya bir senaryo olsaydı ve top taretin tam olarak paralelindeydi, o zaman mantığım sahte hedefin gerçek hedefe eşit bir pozisyonda olması gerektiğini ve artı ile arasındakilere eşit bir dengenin bulunması gerektiğini söyler. taret ve savaş topu. Ancak, asıl senaryomda, kameram 60 ang açılı ve topun hafif bir dönüşü var.

Aşağıdaki görüntü senaryoyu göstermektedir: Örnek Senaryo

Tam olarak nedenini bilmiyorum ama aynı ofseti uygularsam, sadece taretten belirli mesafeleri hedeflerken işe yarar görünüyor.

Mantığım hatalı mı? Burada temel bir şeyi mi özlüyorum?

Son Düzenleme: @JohnHamilton tarafından sağlanan son güncelleme bu sorunu mükemmel bir şekilde çözüyor. Hatalı uygulamalarımı göstermek için kullandığım kod ve resimleri şimdi kaldırdım.


Silah tasarımı perspektifinden bakıldığında, silahını düzeltebilirsin ;)
Wayne Werner

@WayneWerner bu benim durumumda bir seçenek değil. Çarpık, ancak işlevsel olması için tasarım seçimi.
Franconstein

1
Ben bir çalışma örneği ekledik Cevabıma .
ens

Cevaplar mükemmel görünüyor ... tam olarak hangi ayrıntıya ihtiyacınız olduğunu söyler misiniz?
Seyed Morteza Kamali,

Yanıtlar:


31

Eğer matematik yaparsanız cevap aslında oldukça kolaydır. Sabit bir Y mesafeniz ve değişken bir X mesafeniz var (Bkz. Resim 1). Z ve X arasındaki açıyı bulmanız ve taretinizi daha fazla döndürmeniz gerekir. görüntü tanımını buraya girin

Adım 1 - Taret çizgisi (V) ile Y olan tabanca çizgisi (W) arasındaki mesafeyi alın (bu sabittir ancak hesaplanmasına zarar vermez). Taretten hedefe kadar olan mesafeyi alın (X).

Adım 2 - Y'yi X'e bölün ve sonra değerin Hiperbolik Sinüsünü elde edin

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.

Adım 3 - Tareti çok daha fazla çevirin (yukarıdan aşağıya doğru eksen etrafında, en çok yukarı eksen, ancak o kısmı sadece siz biliyorsunuz).

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

Elbette bu durumda, saat yönünün tersine çevirmeniz gerekir, böylece dönüşün önüne bir eksi eklemeniz gerekebilir -turnAngle.

Bazı parçalar düzenlendi. Mesafe farkını gösterdiği için @ens sayesinde.

OP silahının bir açısı olduğunu söyledi, işte başlıyoruz, önce resim, açıklama sonra: görüntü tanımını buraya girin

Kırmızı çizgiyi mavi çizgiye göre nereye hedefleyeceğinizi önceki hesaplamadan zaten biliyoruz. İlk önce mavi çizgiyi hedeflemek:

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

Burada farklılık gösteren tek hesaplama, "X Prime" (X ')' nin hesaplanmasıdır çünkü tabanca ile taret arasındaki açı ("a" açısı) çizgiler arasındaki mesafeyi değiştirmiştir.

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

Bu sonraki bölüm SADECE taret tabancalarını modüler yapıyorsanız gereklidir (yani kullanıcı taret üzerindeki tabancaları değiştirebilir ve farklı tabancaların farklı açıları vardır). Eğer bunu editörde yapıyorsanız, silah açısının tarete göre ne olduğunu zaten görebilirsiniz.

"A" açısını bulmak için iki yöntem vardır, biri transform.up yöntemidir:

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

Yukarıdaki teknik 3B olarak hesaplanacaktır, bu nedenle 2B sonuç istiyorsanız, Z ekseninden kurtulmanız gerekir (yerçekiminin nerede olduğunu varsayarım, ama hiçbir şeyi değiştirmediyseniz, Unity'nin yukarı ya da aşağı olduğu Y ekseni, yani, yerçekimi Y ekseni üzerinde olduğundan, bir şeyleri değiştirmek zorunda kalabilirsiniz):

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

İkincisi, rotasyon yöntemidir (bu durumda 2B olarak düşünüyorum):

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

Yine, tüm bu kodlar size pozitif değerler verecektir, bu nedenle açıya bağlı olarak miktarı eklemeniz veya çıkarmanız gerekebilir (bunun için de hesaplamalar vardır, ancak bu derinlemesine gitmeyeceğim). Bu konuda başlamak için iyi bir yerVector2.Dot Unity'deki yöntem .

Yaptıklarımızın ek açıklaması için son kod bloğu:

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

Her şeyi doğru yaptıysanız, böyle bir sahne elde etmelisiniz ( birliğin paketlenmesi için link ): görüntü tanımını buraya girin Ne demek istediğimi her zaman pozitif değerler:görüntü tanımını buraya girin

Z yöntemi negatif değerler verebilir:görüntü tanımını buraya girin

Örnek bir sahne için, bu bağlantıdan birliğin paketini alın .

İşte sahnede kullandığım kod (tarette):

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

2B düzlem olarak X ve Z ile 3B uyarlanmış kod:

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

İlk resimde küçük bir kusur var. Z, taretin kutuya olan uzunluğu. X, dönme işleminden sonra taretin kutuya uzunluğu ... x = z. Bu nedenle, y, dik bir üçgen olmayan hipotenüs olmadıkça ve günah geçerli olmaz.
Büyük Ördek

@GreatDuck Z, taret ile kutu arasındaki mesafe değil, bu taretin Vector2.forwardu (sonunda bir ok yerine sadece sonlu gösterilir). Z mesafe olsa bile, resmin birimleri vardır ve hesaplama yapmadan Z <X olduğunu görebilirsiniz.
John Hamilton

2
@ Franconstein ilk önce tareti çevirmek zorunda kalmaz, sonra bunları uygular. Önce bunları hesaplayabilir, daha sonra bu denklemlerden aldığınız dereceyi taretin dönüş derecesine ekleyebilirsiniz. (Böylece, tareti 20 dereceye kadar nesneye çevirmek yerine, ardından tabancayı ayarlamak için, tareti 20 dereceye kadar döndürürsünüz + silah için ayar).
John Hamilton

@ Franconstein Yeni ayarlanan koda bakınız. Uçakları değiştirdiğimizden beri kod diğer sürümdekinden farklı davranıyordu. Bunun neden olduğu hakkında hiçbir fikrim yok, ama şu anda benim için mükemmel çalışıyor. Bakınız: imgur.com/a/1scEH (taretlerinizi çıkarmak gerekli değildi, bu basit modeller sizinkiyle aynı şekilde davrandı).
John Hamilton

1
@JohnHamilton Yaptınız! Sonunda çözüldü ve aynı zamanda lazer benzeri bir hassasiyetle! Teşekkür ederim! Teşekkür ederim! Teşekkür ederim! Şimdi gönderimi başlangıçta nasıl olduğuna göre düzenleyeceğim, bu nedenle gelecekteki referans için daha kolay anlaşılabilir! Bir kez daha teşekkür ederim!
Franconstein

3

Daha genel bir yaklaşım da kullanabilirsiniz:

Sorununuz için matematik, scalarproduct (veya nokta ürün) biçiminde zaten var . Silahlarınızın yönlerini sadece eksenin ileri yönünde ve silahınızın hedefe olan yönünü almanız gerekir.

W, geminin ileri vektörü olsun.

D, silahınızdan hedefinize olan yön olsun. (Target.pos - Weapon.pos)

Nokta ürünün formülünü çözerseniz

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

Alfa için olsun:

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

Sadece radyanı dereceye çevirmek zorundasınız ve robotunuzu döndürmek için açınız var. (Silahın bahsettiğiniz gibi robotunuza açılı olduğu için açıyı alfaya eklemeniz gerekir)

görüntü tanımını buraya giringörüntü tanımını buraya girin


2

Şimdiye kadar gönderilen cevapların tümü (az ya da çok) yanlıştır, işte hızlı ve doğru bir çözüm:

görüntü tanımını buraya girin

Silahı hedefe doğru nişan almak için, taret ileri vektörünü hedefe çevirin ve açıyı θ ekleyin.

Öyleyse find bulalım:

   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.

Ne zaman δ' = 0bu basitleştiren için θ = asin(a / d)John Hamilton cevap ilk bölümünü eşleşir.

Düzenle:

Çalışan bir örnek ekledim.

JSFiddle'da açın veya aşağıdaki gömülü snippet'i kullanın:

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


Bu açıklama için çok teşekkür ederim. Anlayabilmem için yeterince basitti ve her durumu hesaba katıyor gibi görünüyor. Ancak, uyguladığımda elde ettiğim sonuçlar uygun olmadığında. Orijinal gönderimi kodumu, kurulumumu görselleştiren bir resmi ve her değişken için sonuçları içerecek şekilde düzenledim. Taretimin ileri vektörü daima hedefe bakıyor, ancak olmasa da sonuçlar neredeyse aynı kalıyor. Yanlış bir şey mi yapıyorum? Bu benim kodum mu?
Franconstein

Diğer cevaplar "az ya da çok yanlış" ise, bunları doğru şekilde anlamıyor / uygulamıyorsunuz. Her iki alternatif cevabı daha önce istenen davranışı oluşturmak için kullandım. @Franconstein, hatta çalıştığını doğruladığınızı söylemek için en az bir tane hakkındaki yorumlarınızı bile görebiliyorum . Bir çözümü doğruladıysanız, hala bir sorununuz mu var?
Gnemlock

@Gememlock, John Hamilton'un çözümü yanlıştı - Ben uygulamıştım ve çalıştı ve bu yüzden de çözümünü onaylandığı gibi doğruladım. Ancak uyguladıktan sonra, farklı statik olmayan senaryolar denemeye başladım ve çözüm dayanamadı. Ancak erken atmak istemedim, ben de bir meslektaşı ile aştım. Yapmadığını teyit ettik, ancak şimdi başka bir olası çözüm yayınladı ve John da dahil etmek için görevini düzenledi. Şu an itibariyle, ikisinin de düzgün çalıştığını onaylayamıyorum ve hala denemeye çalışıyorum. Yardımcı olup olmadığını görmek için kodumu gönderdim. Yanlış mı yaptım?
Franconstein,

@ Franconstein, bu formda aşırı kafa karıştırıcı. Bu örneğin bir Matematik ders kitabı okumayı beklediğiniz şeyin güzel bir örneği olduğunu söyleyebilirim , ancak genel oyun programlaması ile ilgili olarak şaşırtıcı bir şekilde kafa karıştırıcı. Tek önemli unsur açıdır (John Hamilton'un gönderdiği asıl cevabın sağladığı cevap). Ben belirli açılarla tarafından, sonuçta demek istediğini anlıyorum olabilir yanlış yapmış. Yanlış cevaplamak için bu cevapta çok yer var .
Gnemlock
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.