La risposta è in realtà abbastanza semplice se fai la matematica. Hai una distanza fissa di Y e una distanza variabile di X (vedi figura 1). Devi scoprire l'angolo tra Z e X e girare la torretta molto di più.
Passaggio 1: ottenere la distanza tra la linea della torretta (V) e la linea della pistola (W) che è Y (questo è costante ma non fa male per il calcolo). Prendi la distanza dalla torretta al bersaglio (che è X).
Passaggio 2: Dividi Y per X e ottieni il seno iperbolico del valore
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.
Passaggio 3: ruota la torretta molto di più (attorno all'asse che va dall'alto verso il basso, molto probabilmente verso l'alto ma solo tu puoi conoscere quella parte).
gameObject.transform.Rotate(Vector3.up, turnAngle);
Ovviamente in questo caso, è necessario che giri in senso antiorario, quindi potrebbe essere necessario aggiungere un segno negativo di fronte al turno, come in -turnAngle
.
Ho modificato alcune parti. Grazie a @ens per aver sottolineato la differenza di distanza.
L'OP ha detto che la sua pistola ha un angolo, quindi eccoci qui, prima l'immagine, la spiegazione più tardi:
Sappiamo già dal calcolo precedente dove puntare la linea rossa secondo la linea blu. Quindi puntando prima alla linea blu:
float turnAngle = angleBetweenTurretAndTarget - angleBetweenTurretAndGun;
turret.transform.Rotate(Vector3.up, turnAngle);
L'unico calcolo che qui differisce, è il calcolo di "X Prime" (X ') perché l'angolo tra la pistola e la torretta (angolo "a") ha cambiato la distanza tra le linee.
//(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);
Questa parte successiva è SOLO necessaria se stai facendo le pistole a torretta modulari (cioè l'utente può cambiare le pistole su una torretta e pistole diverse hanno angoli diversi). Se lo stai facendo nell'editor, puoi già vedere qual è l'angolo della pistola in base alla torretta.
Esistono due metodi per trovare l'angolo "a", uno è il metodo transform.up:
float angleBetween = Vector3.Angle(turret.transform.up, gun.transform.up);
La tecnica sopra calcolerà in 3D, quindi se vuoi un risultato 2D, devi sbarazzarti dell'asse Z (è quello che presumo dov'è la gravità, ma se non hai cambiato nulla, in Unity è l'asse Y che è su o giù, cioè la gravità è sull'asse Y, quindi potresti dover cambiare le cose):
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);
Il secondo modo è il metodo di rotazione (sto pensando in 2D in questo caso):
double angleRadians = Mathf.Asin(turret.transform.rotation.z - gun.transform.rotation.z);
double angle = 2 * Mathf.Rad2Deg * angleRadians;
Ancora una volta, tutti questi codici ti daranno valori positivi, quindi potresti dover aggiungere o sottrarre la quantità in base all'angolo (ci sono anche calcoli per quello, ma non ho intenzione di approfondirlo). Un buon punto di partenza sarebbe il Vector2.Dot
metodo in Unity.
Blocco finale di codice per una spiegazione aggiuntiva di ciò che stiamo facendo:
//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 hai fatto tutto nel modo giusto, dovresti ottenere una scena come questa ( link per il pacchetto di unità ):
cosa intendo per valori sempre positivi:
Il metodo Z può dare valori negativi:
Per una scena di esempio, ottieni il pacchetto di unità da questo link .
Ecco il codice che ho usato nella scena (sulla torretta):
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.
}
}
Codice adattato 3D con X e Z come piano 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.
}
}