Как я могу повернуть объект на основе смещения другого к нему?

25

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

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

Если у меня есть турель «LookAt ()» цели, лазер из пушки полностью пропустит указанную цель.

Если бы это был полностью нисходящий сценарий, и пушка была точно параллельна башне, то моя логика подсказывает мне, что поддельная цель должна быть расположена в положении, равном фактической цели плюс смещение, равное расстоянию между Башенка и пушка. Однако в моем реальном сценарии моя камера наклонена на 60º, и у пушки есть небольшое вращение.

Следующее изображение иллюстрирует сценарий: Иллюстративный сценарий

Я не уверен точно, почему, но если я применю то же самое смещение, оно будет работать только при наведении на определенные расстояния от башни.

Моя логика ошибочна? Я что-то упустил здесь?

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

Franconstein
источник
С точки зрения дизайна оружия, вы можете просто починить свой пистолет ;)
Уэйн Вернер
@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, поэтому, если вы хотите 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);

Второй способ - это метод поворота (в данном случае я думаю о 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);

Если вы все сделали правильно, вы должны получить такую ​​сцену ( ссылка на пакет UnityPack ): введите описание изображения здесь Что я подразумеваю под всегда положительными значениями:введите описание изображения здесь

Метод Z может давать отрицательные значения:введите описание изображения здесь

Для примера сцены, получите пакет Unity по этой ссылке .

Вот код, который я использовал на сцене (на башне):

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.forward этой турели (он просто отображается конечным вместо стрелки в конце). Даже если Z было расстоянием, картинка имеет единицы измерения, и вы можете видеть, что Z <X, даже не вычисляя.
Джон Гамильтон
2
@ Франконштейн, тебе не нужно сначала поворачивать башню, а затем применять их. Вы можете сначала вычислить их, а затем добавить степень, полученную из этих уравнений, к степени поворота башни. (Таким образом, вместо того, чтобы поворачивать турель на 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|  )

Вам нужно только преобразовать радианы в градусы, и вы получили свой угол, чтобы повернуть робота. (Как вы упомянули, оружие находится под углом к ​​вашему роботу, поэтому вам нужно добавить угол к альфе)

введите описание изображения здесьвведите описание изображения здесь

RootMenu
источник
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, я даже вижу ваши комментарии, по крайней мере, один, чтобы сказать, что вы убедились, что это работает. Если вы проверили решение, у вас все еще есть проблема?
Гнемлок
@ Гнемлок, решение Джона Гамильтона не было неправильным - я реализовал его, и оно сработало, и, таким образом, я проверил его решение как одобренное. Но после его реализации я начал пробовать разные нестатические сценарии, и решение не выдержало. Однако я не хотел преждевременно отказываться от него, поэтому решил обсудить это с коллегой. Мы закончили тем, что подтвердили, что это не имеет места, но теперь опубликовал другое возможное решение, и Джон отредактировал свой пост, чтобы включить его. На данный момент я не могу подтвердить, что любой из них работает правильно, и все еще пытаюсь. Я разместил свой код, чтобы посмотреть, поможет ли это. Я не правильно сделал?
Франконштейн
@Franconstein, в этой форме это слишком запутанно. Я бы сказал, что этот пример является прекрасным примером того, что вы ожидаете, читая учебник по математике , но он властно запутан в отношении общего программирования игры. Единственный важный элемент - это угол (который дал первоначальный ответ Джона Гамильтона). Я понимаю, что вы имеете в виду под определенными углами, в конечном итоге вы, возможно , сделали это неправильно. Я считаю, что в этом ответе есть много возможностей сделать это неправильно .
Гнемлок