Як я можу обертати об'єкт на основі чужого зміщення до нього?


25

У мене є 3D-модель башти, яка обертається навколо осі Y. Ця вежа має гармати, які знаходяться значно від центру предмета. Я хочу, щоб гармата, а не башта, націлилася на визначену ціль. Однак я можу лише обертати башту, і, таким чином, я не знаю, яке рівняння мені потрібно застосувати для досягнення цілі.

Наступне зображення ілюструє мою проблему:введіть тут опис зображення

Якщо у мене є башта "LookAt ()" цілі, лазер, що виходить із гармати, повністю пропустить вказану ціль.

Якщо це був повністю сценарій зверху вниз, і гармати були точно паралельні башточках, то моя логіка говорить мені, що фальшива ціль повинна розташовуватися в положенні, рівному фактичній цілі плюс зміщенню, рівному тому, що знаходиться між башточка і гармата. Однак у моєму реальному сценарії моя камера знаходиться під кутом 60 °, а гармата має незначне обертання.

Наведене нижче зображення ілюструє сценарій: Ілюстративний сценарій

Я точно не впевнений, чому, але якщо я застосую те саме зміщення, він, здається, спрацює лише при націлюванні на певні відстані від башти.

Чи є моя логіка хибною? Чи пропускаю тут щось фундаментальне?

Остаточне редагування: рішення, надане останнім оновленням @JohnHamilton, вирішує цю проблему з ідеальною точністю. Зараз я видалив код і зображення, які використовував для ілюстрації моїх неправильних реалізацій.


З точки зору дизайну зброї, ви могли просто виправити свій пістолет ;)
Уейн Вернер

@WayneWerner в моєму випадку це не варіант. Це вибір дизайну, щоб він був кривим, але функціональним.
Франкоштейн

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.

Редагував деякі частини. Завдяки @ens за вказівку на різницю відстані.

ОП заявив, що його пістолет має кут, тому ось ми переходимо до зображення, спочатку пояснення: введіть тут опис зображення

Ми вже знаємо з попереднього розрахунку, куди слід орієнтуватися на червону лінію відповідно до синьої. Тож спочатку орієнтуючись на синю лінію:

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

Єдиний розрахунок, який тут відрізняється, - це обчислення "X Prime" (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", один - метод transform.up:

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

Наведена вище техніка буде обчислена в 3D, тож якщо ви хочете отримати двовимірний результат, вам потрібно позбутися осі 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);

Другий спосіб - це метод обертання (я думаю, що в цьому випадку є 2D):

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

Знову ж таки, всі ці коди дадуть вам позитивні значення, тому вам, можливо, доведеться додавати або віднімати суму залежно від кута (для цього теж є розрахунки, але я не збираюся це робити глибоко). Хорошим місцем для початку став би Vector2.Dotметод в Єдності.

Заключний блок коду для додаткового пояснення того, що ми робимо:

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

Якщо ви зробили все правильно, вам слід отримати подібну сцену ( посилання на папір єдності ): введіть тут опис зображення Що я маю на увазі під завжди позитивними значеннями:введіть тут опис зображення

Метод Z може дати негативні значення:введіть тут опис зображення

Для прикладу сцени знайдіть пакунок єдності за цим посиланням .

Ось код, який я використав у сцені (на башті):

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 адаптований код з X і Z як 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.
    }
}

У першому зображенні незначний недолік. Z - довжина башти до коробки. X - довжина башти до коробки після обертання ... x = z. Тому, якщо у не є гіпотенуза, яка не є правильним трикутником, і гріх не застосовується.
Велика качка

@TheGreatDuck Z - це не відстань між баштою та коробкою, це вектор2.наперед цієї башти (вона просто показана кінцевою, а не стрілкою в кінці). Навіть якщо Z була відстань, на малюнку є одиниці, і ви можете бачити, що Z <X, навіть не розраховуючи.
Джон Гамільтон

2
@Franconstein вам не потрібно спочатку повертати башту, а потім застосувати їх. Ви можете спочатку обчислити їх, а потім додати ступінь, отриманий з цих рівнянь, до ступеня повороту башти. (Отже, замість того, щоб повернути башту на 20 градусів до об'єкта, потім налаштувавши гармати, ви б повернули башту на 20 градусів + регулювання для гармати).
Джон Гамільтон

@Franconstein Дивіться щойно відрегульований код. Оскільки ми змінили літаки, код діяв інакше, ніж це було в іншій версії. Я поняття не маю, чому це сталося, але зараз він ідеально працює на моєму кінці. Дивіться: imgur.com/a/1scEH (видаляти башточки не потрібно, ці прості моделі діяли так само, як і ваша).
Джон Гамільтон

1
@JohnHamilton Ви це зробили! Це остаточно вирішено, і з лазерною точністю теж! Дякую! Дякую! Дякую! Зараз я відредагую свій пост тим, яким він був на початку, тому його можна буде легше зрозуміти для подальшої довідки! Ще раз дякую!
Франкоштейн

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


Дуже дякую за це пояснення. Мені це було досить просто зрозуміти, і він, здається, враховує кожну ситуацію. Однак, коли я його реалізував, результати, які я отримав, не сприятливі. Я відредагував свій початковий пост, щоб включити код, зображення, що візуалізує мою настройку, та результати для кожної змінної. Передній вектор моєї башточки завжди дивиться на ціль, але навіть якщо це не так, результати залишаються майже однаковими. Я щось роблю не так? Це мій код?
Франкоштейн

Якщо інші відповіді "більш-менш помилкові", ви не розумієте / не реалізуєте їх правильно. Раніше я використовував обидві альтернативні відповіді, щоб створити бажану поведінку. @Franconstein, я навіть бачу ваші коментарі принаймні, щоб сказати, що ви перевірили, що це працює. Якщо ви перевірили рішення, чи все ще виникає проблема?
Gnemlock

@Gnemlock, рішення Джона Гамільтона не було помилковим - я його реалізував, і він спрацював, і таким чином я перевірив його рішення як схвалено. Але після його втілення я почав пробувати різні нестатичні сценарії, і рішення не витримало. Я не хотів передчасно відкидати це, тому перейшов це разом із колегою. Ми нарешті підтвердили, що це не відповідає, але тепер опублікував ще одне можливе рішення, і Джон відредагував свою посаду, щоб включити її. На цей момент я не можу підтвердити, що жоден з них працює правильно, і я все ще намагаюся. Я опублікував свій код, щоб побачити, чи допомагає він. Я помилявся?
Франкоштейн

@Franconstein, в такому вигляді це занадто заплутано. Я б сказав, що цей приклад є прекрасним прикладом того, що ви очікуєте, читаючи текстову книгу з математики , але це надмірно заплутано стосовно загального ігрового програмування. Єдиний важливий елемент - це кут (який в оригінальній відповіді Джон Гамільтон розмістив). Я бачу, що ви маєте на увазі під певними кутами, зрештою, ви могли це зробити неправильно. Я вважаю, що в цій відповіді багато місця, щоб зробити це неправильно .
Gnemlock
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.