다른 사람의 오프셋을 기준으로 개체를 회전시키는 방법은 무엇입니까?


25

Y 축을 중심으로 회전하는 포탑의 3D 모델이 있습니다. 이 포탑에는 대포가있어 물체의 중심에서 크게 벗어납니다. 포탑이 아닌 대포가 지정된 목표물을 겨냥하고 싶습니다. 그러나 터릿 만 회전시킬 수 있으므로 객관적으로 달성하기 위해 어떤 방정식을 적용해야하는지 알 수 없습니다.

다음 이미지는 내 문제를 보여줍니다.여기에 이미지 설명을 입력하십시오

터렛에 "LookAt ()"타겟이 있으면 대포에서 발생하는 레이저가 해당 타겟을 완전히 놓치게됩니다.

이것이 완전히 하향식 시나리오이고 대포가 포탑과 정확히 평행 한 경우, 나의 논리는 가짜 목표물이 실제 목표물과 같은 위치에 위치해야한다는 것을 알려줍니다. 포탑과 대포. 그러나 실제 시나리오에서 카메라의 각도는 60º이며 대포는 약간 회전합니다.

다음 이미지는 시나리오를 보여줍니다. 예시 시나리오

나는 왜 정확한지 잘 모르겠지만, 동일한 오프셋을 적용하면 터릿에서 특정 거리를 목표로 할 때만 작동하는 것 같습니다.

내 논리에 결함이 있습니까? 여기에 근본적인 것이 빠져 있습니까?

최종 편집 : @JohnHamilton 최신 업데이트에서 제공하는 솔루션은이 문제를 완벽한 정밀도로 해결합니다. 이제 잘못된 구현을 설명하는 데 사용한 코드와 이미지를 제거했습니다.


무기 디자인의 관점에서, 당신은 할 수 총을 수정 )
웨인 베르너

@ WayneWerner 이것은 내 경우에는 옵션이 아닙니다. 비뚤어 지지만 기능적인 디자인 선택입니다.
Franconstein

1
내 답변에 실제 예제를 추가했습니다 .
ens

답이 완벽 해 보입니다 ... 정확히 어떤 세부 사항이 필요한지 언급 할 수 있습니까?
Seyed Morteza Kamali

답변:


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 앞에 마이너스를 추가해야 할 수도 있습니다 -turnAngle.

일부 부품을 편집했습니다. 거리의 차이를 지적한 @ens에게 감사합니다.

OP는 그의 총에 각도가 있으므로 여기부터 먼저 설명하겠습니다. 여기에 이미지 설명을 입력하십시오

우리는 이전 계산에서 파란색 선에 따라 빨간색 선을 목표로하는 위치를 이미 알고 있습니다. 먼저 파란색 선을 목표로하십시오.

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

여기서 다른 유일한 계산은 건과 포탑 사이의 각도 (각 "a")가 선 사이의 거리를 변경했기 때문에 "X Prime"(X ')의 계산입니다.

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

모든 것을 올바르게했다면 다음과 같은 장면이 나타납니다 ( unitypackage 링크 ). 여기에 이미지 설명을 입력하십시오 항상 양수 값의 의미 :여기에 이미지 설명을 입력하십시오

Z 방법은 음수 값을 제공 할 수 있습니다.여기에 이미지 설명을 입력하십시오

장면의 예를 보려면 이 링크 에서 unitypackage를 가져 오십시오 .

장면에서 사용한 터렛은 다음과 같습니다.

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

X와 Z를 2D 평면으로 사용하는 3D 적응 코드 :

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. 그러므로 y가 빗변이 아닌 직각 삼각형이 아니라면 죄는 적용되지 않습니다.
그레이트 덕

@TheGreatDuck Z는 터렛과 상자 사이의 거리가 아니며 터릿의 Vector2입니다 (끝에 화살표 대신에 유한하게 표시됨). Z가 거리 였더라도 그림에는 단위가 있으며 계산하지 않고도 Z <X임을 알 수 있습니다.
John Hamilton

2
@Franconstein 먼저 터릿을 돌릴 필요가 없습니다. 먼저이를 계산 한 다음이 방정식에서 얻은 각도를 터릿의 회전 각도에 더할 수 있습니다. (따라서 포탑을 물체로 20도 돌리고 총을 조정하는 대신 포탑을 20도 + 총 조정하면 회전합니다).
John Hamilton

@Franconstein 새로 조정 된 코드를 참조하십시오. 우리가 비행기를 바꾸었기 때문에 코드는 다른 버전과 다르게 행동했습니다. 왜 이런 일이 일어 났는지 모르겠지만 지금은 완벽하게 작동하고 있습니다. imgur.com/a/1scEH를 참조하십시오 (포탑을 제거 할 필요는 없었습니다. 간단한 모델은 기존 모델과 동일한 방식으로 작동했습니다).
John Hamilton

1
@JohnHamilton 당신은 그것을했다! 최종적으로 해결되고 레이저와 같은 정밀성도 있습니다! 고맙습니다! 고맙습니다! 고맙습니다! 이제 게시물의 시작 부분을 편집하여 향후 참조를 위해 더 쉽게 이해할 수 있습니다! 다시 한번 감사합니다!
Franconstein

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)John Hamilton의 답변의 첫 부분과 일치합니다.

편집하다:

실제 예제를 추가했습니다.

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, 나는 적어도 당신이 그것이 효과가 있음을 확인했다고 말한 것을 봅니다. 솔루션을 확인한 후에도 여전히 문제가 있습니까?
Gnemlock

@Gnemlock, John Hamilton의 솔루션이 잘못되지 않았습니다. 구현 한 후 작동하여 승인 된 것으로 확인되었습니다. 그러나 그것을 구현 한 후, 나는 다른 비 정적 시나리오를 시도하기 시작했고 해결책은지지 않았습니다. 나는 그것을 조기에 버리고 싶지 않았으므로 동료와 함께 그것을 넘어갔습니다. 우리는 그것이 보류되지 않았 음을 확인하게되었지만, 이제 다른 가능한 해결책을 게시했으며 John은 자신의 게시물을 포함하도록 편집했습니다. 현재로서는 둘 중 하나가 올바르게 작동하는지 확인할 수 없으며 여전히 시도하고 있습니다. 도움이되는지 확인하기 위해 코드를 게시했습니다. 내가 잘못 했니?
Franconstein

@Franconstein,이 형태에서는 지나치게 혼란 스럽습니다. 이 예제는 Maths 교재를 읽는 것에 대한 좋은 예라고 말하지만 일반적인 게임 프로그래밍과 관련하여 매우 혼란 스럽습니다. 유일한 중요한 요소는 각도 (John Hamilton이 게시 한 원래 답변이 제공 한 것)입니다. 나는 당신이 특정 각도로 무엇을 의미하는지 알 있습니다. 결국 이 잘못했을 수도 있습니다. 이 답변 에는 잘못 할 여지가 많이 있습니다 .
Gnemlock
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.