If you just want to rotate the turret and the gun around their local Y and X axes in order to aim to some target point, there’s a much easier alternative: project the target position into the turret’s local horizontal plane and use LookAt to rotate the turret to this point, then simply use LookAt(target) to control the gun direction (does exactly the same effect that a gun childed to the turret and rotating only around its local X axis).
This approach requires that you child the turret and the gun to an intermediate empty object - the Base object - which in turn is childed to the tank, like below:
The script gets references to Gun and Turret, and control both:
var target: Transform;
var damping: float = 5.0;
private var turret: Transform;
private var gun: Transform;
private var trf: Transform;
function Start () { // gun and turret are childed to Base:
trf = transform;
gun = trf.Find("Gun");
turret = trf.Find("Turret");
}
var aimPos: Vector3; // position the gun must aim at
var elevationAngle: float = 30; // fixed elevation angle
var projectileVel: Vector3; // velocity needed to reach the target
function AimAtTarget(euler: Vector3, distance: float){
// get vector Base->target:
var dir = Quaternion.Euler(euler) * Vector3.forward * distance;
var h = dir.y; // get height difference
dir.y = 0; // retain only the horizontal direction
var dist = dir.magnitude ; // get horizontal distance
var a = elevationAngle * Mathf.Deg2Rad; // convert elevation angle to radians
dir.y = dist * Mathf.Tan(a); // set dir to the elevation angle
dist += h / Mathf.Tan(a); // correct for small height differences
// calculate the velocity magnitude
var vel = Mathf.Sqrt(dist * Physics.gravity.magnitude / Mathf.Sin(2 * a));
projectileVel = vel * dir.normalized; // save projectile velocity vector
aimPos = gun.position + dir; // save position to aim at
}
function Update () {
// convert target position to local space:
var pos = trf.InverseTransformPoint(aimPos);
pos.y = 0; // project it in the horizontal plane
// convert pos back to world space:
pos = trf.TransformPoint(pos);
// find the desired turret rotation and Slerp to it:
var rot = Quaternion.LookRotation(pos-trf.position, trf.up);
turret.rotation = Quaternion.Slerp(turret.rotation, rot, damping*Time.deltaTime);
// just Slerp the gun to the target:
rot = Quaternion.LookRotation(aimPos-gun.position, trf.up);
gun.rotation = Quaternion.Slerp(gun.rotation, rot, damping*Time.deltaTime);
}
// how to use:
var projPrefab: Rigidbody; // projectile prefab
function Shoot(eulerAngles: Vector3, distance){
AimAtTarget(eulerAngles, distance);
yield WaitForSeconds(3); // give some time for gun positioning
var proj: Rigidbody = Instantiate(projPrefab, gun.position, gun.rotation);
proj.velocity = projectileVel; // shoot with the calculated velocity
}
This code actually shoots the projectile according to the calculated trajectory - the turret/gun orientation is just a visual aid. By the way, I’m assuming here that the Euler angles define the target direction based on the Gun object. If they are relative to the tank position, replace gun by the transform.parent (assuming Base is childed to the tank) in the Instantiate instruction.
NOTE: You must use some trick to avoid collisions between the projectile and the tank, turret or gun. An alternative is to create two new layers, Tank and Projectile, disable collisions between them in the Physics Manager and move the tank/turret/gun and projectile to the appropriate layers. Another alternative: make the projectile collider be a trigger during a few cycles, so that it can ignore collisions until it’s at some distance from the tank - like this:
...
var proj: Rigidbody = Instantiate(projPrefab, gun.position, gun.rotation);
proj.collider.isTrigger = true; // projectile is a trigger, initially
proj.velocity = projectileVel; // shoot with the calculated velocity
yield WaitForSeconds(0.2); // let it reach a safe distance...
proj.collider.isTrigger = false; // and make it a regular collider again
}