A resposta é realmente muito fácil se você fizer as contas. Você tem uma distância fixa de Y e uma distância variável de X (veja a figura 1). Você precisa descobrir o ângulo entre Z e X e transformar sua torre muito mais.
Etapa 1 - Obtenha a distância entre a linha da torre (V) e a linha da arma (W), que é Y (isso é constante, mas não custa calcular). Distância da torre ao alvo (que é X).
Etapa 2 - Divida Y por X e obtenha o Seno Hiperbólico do valor
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.
Etapa 3 - Gire a torre muito mais (em torno do eixo que vai de cima para baixo, provavelmente acima do eixo, mas somente você pode conhecer essa parte).
gameObject.transform.Rotate(Vector3.up, turnAngle);
É claro que, nesse caso, você precisa girar no sentido anti-horário para adicionar um sinal de menos na frente do turnAngle, como em -turnAngle
.
Editou algumas partes. Agradecemos a @ens por apontar a diferença de distância.
O OP disse que sua arma tinha um ângulo, então aqui vamos nós, primeiro a imagem, a explicação depois:
Já sabemos no cálculo anterior onde apontar a linha vermelha de acordo com a linha azul. Então, visando a linha azul primeiro:
float turnAngle = angleBetweenTurretAndTarget - angleBetweenTurretAndGun;
turret.transform.Rotate(Vector3.up, turnAngle);
O único cálculo que difere aqui é o cálculo de "X Prime" (X ') porque o ângulo entre a pistola e a torre (ângulo "a") alterou a distância entre as linhas.
//(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);
Esta próxima parte é SOMENTE necessária se você estiver usando as armas de torre modulares (ou seja, o usuário pode mudar as armas em uma torre e armas diferentes têm ângulos diferentes). Se você estiver fazendo isso no editor, já poderá ver qual é o ângulo da pistola de acordo com a torre.
Existem dois métodos para encontrar o ângulo "a", um é o método transform.up:
float angleBetween = Vector3.Angle(turret.transform.up, gun.transform.up);
A técnica acima será calculada em 3D. Portanto, se você deseja um resultado 2D, precisa se livrar do eixo Z (é isso que eu suponho onde está a gravidade, mas se você não mudou nada, no Unity é o eixo Y que está para cima ou para baixo, ou seja, a gravidade está no eixo Y, então você pode precisar mudar as coisas):
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);
A segunda maneira é o método de rotação (estou pensando em 2D neste caso):
double angleRadians = Mathf.Asin(turret.transform.rotation.z - gun.transform.rotation.z);
double angle = 2 * Mathf.Rad2Deg * angleRadians;
Novamente, todos esses códigos fornecerão valores positivos, portanto, você pode adicionar ou subtrair a quantidade dependendo do ângulo (também existem cálculos para isso, mas não vou detalhar isso). Um bom lugar para começar com isso seria o Vector2.Dot
método no Unity.
Bloco final de código para explicação adicional do que estamos fazendo:
//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);
Se você fez tudo certo, deve ter uma cena como esta ( link para o unitypackage ):
O que quero dizer com valores sempre positivos:
O método Z pode dar valores negativos:
Para uma cena de exemplo, obtenha o unitypackage neste link .
Aqui está o código que usei na cena (na torre):
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.
}
}
Código 3D adaptado com X e Z como plano 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.
}
}