Rotate bone on 1 axis to look at another object - Unity 3D

I'm new to game development and I'm trying to create a basic 3D Tower Defence game as my first project. I've created a basic turret in Blender and I'm now trying to rotate 2 different bones to make the turret follow the enemy. The first bone (which I have working) rotates the turret to face the direction of the enemy. The second which I can't get right needs to rotate the gun at the top of the turret to look down at the enemy. I need it to keep facing the same direction as the rest of the turret and don't want it to tilt to either side. So it needs to move on 1 axis only.

I have tried various things, including examples from the Internet but I can't figure it out. So far I've managed to get it to look down at its base, up at the sky and spin like a wheel. Basically everything except where it's supposed to be looking.

Here is the code I have at the moment:

if (enemiesInRange.Count > 0)
{
//Rotate Platform //#### This section works fine
Transform platform = transform.root.Find("Armature/BaseBone/PlatformBone");

Quaternion targetRotation; //Final rotation (I.e. facing the enemy)

Vector3 turretPosition = platform.position; //Store the turrets current position
turretPosition.y = 0; //0 the Y axis to stop the turret tilting up or down

Vector3 targetPosition = enemyManager.activeEnemies[enemiesInRange[0]].transform.position;

targetRotation = Quaternion.LookRotation(targetPosition - turretPosition);

float rotateSpeed = 2.0f * Time.deltaTime;
platform.transform.rotation = Quaternion.Lerp(platform.transform.rotation, targetRotation, rotateSpeed);

//Rotate GunBox //####TO DO: This part is not working
Transform gunBox = transform.root.FindChild("Armature/BaseBone/PlatformBone/LowerArmBone/UpperArmBone/GunBoxBone");
Vector3 gunBoxPosition = gunBox.position;

float angle = Vector3.Angle(gunBoxPosition, targetPosition);

gunBox.RotateAround(gunBox.position, Vector3.left, angle);

Debug.Log(angle);

}


Here's a picture of the turret to give you an idea what I'm working with. You can see in the screenshot that the bone's default X rotation is roughly -50.

Enemies walk through the trench around the turret. The PlatformBone rotates everything from the silver platform upwards.

I also don't understand why the line below makes the turret rotate instead of tilting it. I would expect to rotate the turret around the Y axis to keep it flat on the floor and face another direction. Yet I seem to have to do the opposite (If I select the PlatformBone its Y axis is facing up as expected):

turretPosition.y = 0; //0 the Y axis to stop the turret tilting up or down

• In Unity, there's an important aspect you need to know about: Local vs Global. In your case, a local rotation is the rotation of an object relative to its parent. A global rotation is the rotation of the object in world space, i.e relative to the point (0, 0, 0) with vectors Right, Up and Forward. The reason it rotates instead of tilts is because you are rotating its global rotation instead of its local rotation. – DisturbedNeo Sep 16 '16 at 11:51
• Thank you, I understand what you are saying about global vs local but if I use Blender as an example, If I wanted to rotate the whole turret I would lock the rotation to the upward facing axis (which in Blenders case is Z, but would be the equivalent of Unity's Y axis). In Unity's global axis Y is facing upwards so I would want to rotate around Y, but by setting turretPosition.y = 0 I think I'm stopping Y from changing (which is working). Shouldn't I need to set x and z to 0 instead of y? Unfortunately I don't yet understand quaternions which might be why I'm confused. – Daniel Sep 16 '16 at 13:02
• To be honest, I think you'd be better off using transform.LookAt to achieve what you want. This Unity answer, answers.unity3d.com/questions/36255/…, shows how to use it so that it only rotates in the Y-axis, but this can of course be adapted so that it rotates only in whichever axes you want. – DisturbedNeo Sep 16 '16 at 14:12
• Unfortunately locking the rotation to one axis wasn't working as intended because the gun had to follow the rotation of it's parents (I.e. the turret's arm and platform). But I was able to get it very close to what I wanted by allowing all axis to change using LookAt. So thank you for your help. – Daniel Sep 18 '16 at 15:03

To make an object face another object in Unity you can simply use

https://docs.unity3d.com/ScriptReference/Transform.LookAt.html

public void LookAt(Vector3 worldPosition, Vector3 worldUp = Vector3.up);

Rotates the transform so the forward vector points at worldPosition.

The interesting thing here is that if you have 2 objects (let's say A to rotate and B to look at) which have the same position.y, and you call A's transform.LookAt(B's position), A will be rotated to face B with rotation.y unchanged.

With that logic we can write a function that will rotate our object having the same rotation.y as before, it would look like:

public void LookAtXZ (Transform rotatingPart, Vector3 target )
{
var origin = rotatingPart.position; // get origin point of rotation
var targetXZ = new Vector3(target.x, origin.y, target.z); // "neutralize" target's position so that it has the same y as origin
rotatingPart.LookAt(targetXZ);

}


Analogically we can write function that rotates only on X, Y or Z axis without difficulty.

public void LookAtY (Transform rotatingPart, Vector3 target )
{
var origin = rotatingPart.position;
var targetY = new Vector3(origin.x, target.y, origin.z);
rotatingPart.LookAt(targetY);
}


Besides, transform.Find is not really a good way to get transform of an object, especially when you must do it every frame. Just create a public field in your class, attach the transform of part that needs to be rotated in Inspector and refer to it by reference in code, it would be way more performance-wise.

List<Transform> enemies;
Transform platform;
Transform gunBox;

void UpdateRotation ()
{
foreach( var e in enemies )
{
var target = e.position;
// For the sake of simplicity i'm going to take the use of 2 function above
LookAtXZ(platform, target );
LookAtY(gunBox, target );
}
}


Hope this helps :)

p.s: how to make turret prefab with references to its parts

After setting up, save the object as a prefab and next time when you instantiate your turret from prefab, it will already have the references to platform and gunStation

• Thank you for the extra info :) Would the public field work with a prefab? The turrets will be created by the player so I was using transform.Find in order to reference the correct instance of the turret. – Daniel Sep 21 '16 at 20:04
• Public field in Inspector is meant to be used with prefab or references. About the transform.Find, I dont think it matters if its the player who creates the turrets or not, you are the one who define where turret's parts are located anyway. You can make a script with no funtionality, just public fields to store references. I added image to show how to do it in my answer – tc07 Sep 21 '16 at 20:57
• Thanks, I've used them before in a tutorial I just didn't know if it would work when the object is instantiated multiple times. All I meant by the player creating them is that they will be instantiated randomly. I wasn't sure if it would only reference the actual object linked to the field rather than an instance of it. As for performance this is my first game ever so still getting to grips with it. I appreciate the advice :) – Daniel Sep 21 '16 at 21:12
• It will work as expected, each copy will have references to their owns objects. – tc07 Sep 21 '16 at 23:43