Go to Top

10 scene tutorial in C# using quaternions to create billboards, aligned 3d text & target positions. Also includes examples of rotating about a single axis.

In This Tutorial

The Tutorial Project

You can download the tutorial project from here.

The project is organised so that each scene shows a different part of the tutorial.

Each scene and the new scripts and objects used with it are located in folders under Tutorial/Part1

You will see an indicator when the tutorial is moving to a new scene.

Tutorial Scene 1

The tutorial uses a number of amazing free assets from the Asset Store.


An Introduction to Quaternions

Quaternions are used to describe rotations in Unity, they are the internally used format rather than transformation matrices.

There is an immediate confusion with Quaternions for many users - and that is due to the fact that when you look at an object in the inspector the rotation is shown as a Vector3 representation of rotations around X, Y and Z axes.  This is in fact the eulerAngles of the rotation - not the Quaternion itself.  This confusion is compounded by the fact that Quaternions have properties called x, y and z - but these have nothing to do with the values specified in the inspector (or at least very little)!

There are many ways to make a Quaternion - to make one from a Vector3 of angles you use Quaternion.Euler with either the 3 rotational axes as parameters or a Vector3 describing the rotation.  To make a rotation that looks in a particular direction you use Quaternion.LookRotation(directionVector).  You will learn other ways to make Quaternions and how to combine them as you work through this tutorial.

Summary about eulerAngles and Quaternions:

EulerAngles:

Pros:

  • Easy to understand and visualize since they use 3D vectors.
  • Are computed faster since only 3 values to work with compared to 4 for Quaternions.

Cons:

  • One rotation can yield different result. Different engine use different orders (x,y,z) or (y,x,z) or else. The result is each time different
  • Gimbal lock effect. When one rotation aligns with another, the get locked together and one dimension is lost.

Quaternions:

Pros:

  • Avoid the gimbal lock effect.
  • Perform the rotation based on a target vector and not a set of rotation on axes.

Cons:

  • Are slighlty slower since there is one more value than eulerAngles
  • It requires you to be Stephen Hawking (or @Scribe) to fully understand how they work.

You can combine multiple rotations using the * operator (which will come in handy later in this tutorial) and you can also modify a Vector3 by multiplying a Quaternion by it, this returns a rotated version of the vector.  The order of Quaternion manipulation is significant.

var newVector = Quaternion.AngleAxis(90, Vector3.up) * Quaternion.LookRotation(someDirection) * someVector;

You don't add or combine Quaternions together using + (plus).  The * (multiplication) operator acts as a way of combining multiple rotations .

One reason for using Quaternions rather than Vector3s to describe rotations is that while a Vector3 can describe a rotation it is not always possible just to rotate an object by moving the three axis rotation values towards a set of target values.  This is due to an effect called Gimbal Lock that affects Euler Angles, if you are interested you can learn more about it on Wikipedia, but it occurs due to the need to move each value individually and in certain combinations this creates a situations where the target value cannot be reached in one dimension due to the movement in the others.  Quaternions remove this problem by making an entire rotation in one step around a single axis - basically using 4 dimensional space!

Euler angles are easy to think in, but are far less powerful than Quaternions.  Unity makes it easy to move between the two representations so you can choose the one that is best at each time.  If you want to clamp rotations for instance, you can get the .eulerAngles of a Quaternion and then apply your clamping operation with Mathf.Clamp and then turn the angles back into a quaternion by updating the .eulerAngles variable or creating a new Quaternion with Quaternion.Euler(yourAngles).

When you want to combine rotations, such as a head tilt and a body rotation you could use either approach, but as soon as those rotations are not around world axes, for example your character is also leaning over, then you want to use Quaternions.  It is very simple to use a Quaternion to create a simple rotation around each axis you want and then combine them.

Unity also provides the very useful axes transform.forward, transform.right and transform.up which can be used in conjunction with Quaternion.AngleAxis to create rotations that can be applied on top of an existing object rotation.

//Tilt as if this were a head, on a rotated body
transform.rotation = Quaternion.AngleAxis(degrees, transform.right) * transform.rotation;

Getting an Enemy to Look at the Player

One of the commonest things we need to do is get something to look at something else.  In a 3 dimensional space game where all angles look sensible this isn't a problem - but when enemies stand on the ground you can get into some trouble.

Tutorial Scene 1

The most obvious thing to do to make an enemy look at something is to use Transform.LookAt(somePosition).  In our first example we have a Player which is a capsule with a camera attached to it; it moves using standard FPS movement input and rotates the view with a MouseLook controller.

There are four enemies in our scene. Our enemies are pretty dumb in this first scene, they are just going to point at the player, by using BasicLookAtPlayer.cs

using UnityEngine;
using System.Collections;

public class BasicLookAtPlayer : MonoBehaviour {
	// Update is called once per frame
	void Update () {
		transform.LookAt(Camera.main.transform.position);
	}
}

As you can see this uses the standard LookAt command to point the enemy at the current main camera, in our case the player.  Unfortunately this has a pretty significant downside when the player or enemy are at different heights!  The enemy can end up lying on its back in an unlikely way...

Rotating Around the Y Axis Only

What we need to do is to rotate the enemy only around the Y axis so that it is facing the direction of the player, without rotating around the x and z axes which would make it lean in an unlikely way.

Tutorial Scene 2

Fortunately that's pretty easy to accomplish. In this scene we use the LookAtPlayerOnOneAxis script.

using UnityEngine;
using System.Collections;

public class LookAtPlayerOnOneAxis : MonoBehaviour {

	// Update is called once per frame
	void Update () {
		var newRotation = Quaternion.LookRotation(Camera.main.transform.position - transform.position).eulerAngles;
		newRotation.x = 0;
		newRotation.z = 0;
		transform.rotation = Quaternion.Euler(newRotation);
	}
}

First we make a new Quaternion that looks from the current position of the enemy towards the main camera (our player).  Quaternion.LookRotation takes a direction, which we can get easily by taking away the position of the enemy from the position of the camera.  When we've made the Quaternion we turn it back into the Vector3 angle representation using eulerAngles and store it in newRotation.

The rotation we created is exactly the same as we would have got using the LookAt method in the first scene.

To make this just a y-axis rotation we simply set the x and z elements to 0 - then turn the updated value back into a Quaternion using Quaternion.Euler.

Making the Rotation Smooth

At the moment the enemies just snap to a rotation facing the player - that's not very realistic and will start to look strange when we start having them move around the scene.

Tutorial Scene 3

We can use Quaternion.Slerp to interpolate between the current rotation of the character and the target rotation looking at the player.

using UnityEngine;
using System.Collections;

public class SmoothLookAtPlayerOnOneAxis : MonoBehaviour {

	// Update is called once per frame
	void Update () {
		var newRotation = Quaternion.LookRotation(Camera.main.transform.position - transform.position).eulerAngles;
		newRotation.x = 0;
		newRotation.z = 0;
		transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.Euler(newRotation), Time.deltaTime);
	}
}

Again we make the rotation using LookRotation and zero out the x and z angles.  Then we use Slerp to interpolate between the current rotation and the target.

Slerp takes three parameters. The first is the starting rotation, the second is the finishing rotation and the third is a number between 0 and 1 indicating how close to each end of the range the result will be.  A value of 0 will return the starting rotation, a value of 1 will return the ending rotation and a value of 0.5 will return a rotation half way between the two.

If you want to make the position between the start and end something that isn't a straight line, totally linear interpolation then you can apply a function that "eases" the value.  An easing function takes a parameter than is between 0 and 1 and returns a value between 0 and 1, but the returned value lies on a curve.  The practical upshot of this is that movement can be made to be slow to start with, be faster in the middle and slow again at the other end - or practically anything else you like - so long as you have a function for it.

In our case we want to update the potential rotation every frame, so we don't really have a good starting and ending value and a known amount of time to move between them.  To achieve our goal of making the rotation appear to be smooth what we do is take the current rotation and update it to be closer to the ending rotation by an amount every frame.  To do this we set the third parameter to Time.deltaTime.

The effect of this is that the character will start rotating quickly as there will be a large difference in the rotations - using Time.deltaTime means that we will move n% towards the target rotation every frame (where n% would be 10% if Time.deltaTime was 0.1s).  The next frame we start closer to the target rotation (presuming the player is not moving) and move n% of the difference.  As we approach the target rotation our rotation speed will slow giving us an attractive slowing rotation.

As the rotation approaches the target the difference will be very small, but we are still only closing that distance by n%, so it will be a very long time before they are exactly the same.  It will look right, but checking for an equality on the rotation would fail as it would be still moving imperceptibly.

Smooth Rotation with a Maximum Speed

The problem of our last example is that  should the rotations be very different the character may start rotating very quickly, perhaps unrealistically.  We probably want a way of making a smooth rotation that is limited to a maximum number of degrees per second.

Tutorial Scene 4

In the fourth scene we use the script RotateToPlayerWithAMaximumSpeed

using UnityEngine;
using System.Collections;

public class RotateToPlayerWithAMaximumSpeed : MonoBehaviour {

	public float maximumRotateSpeed = 40;
	public float minimumTimeToReachTarget = 0.5f;
	Transform _transform;
	Transform _cameraTransform;
	float _velocity;

	void Start()
	{
		_transform = transform;
		_cameraTransform = Camera.main.transform;
	}

	// Update is called once per frame
	void Update () {
		var newRotation = Quaternion.LookRotation(_cameraTransform.position - _transform.position).eulerAngles;
		var angles = _transform.rotation.eulerAngles;
		_transform.rotation = Quaternion.Euler(angles.x, Mathf.SmoothDampAngle(angles.y, newRotation.y, ref _velocity, minimumTimeToReachTarget, maximumRotateSpeed),
			angles.z);
	}
}

Starting in this example we will begin to think about performance optimisation.  We will optimise getting the Transforms of the camera and the enemy.  The highlighted lines show us caching these values to private variables.

Using transform may look like accessing a variable, but it isn't.  When you type transform.position you are effectively writing GetComponent<Transform>().position - this is a significant overhead when you do it many times and it is always a good idea to cache transforms and other similar variables.

So to perform our rotation with a maximum velocity we will call upon Mathf.SmoothDampAngle - this time we will also combine the existing eulerAngles of the rotation with a new value for the Y rotation.  SmoothDampXXXX functions (available on Vector3 and Mathf) take a current velocity, a maximum velocity and a target time to complete the movement.  The current velocity is just used by the routine and we have to provide a float to contain it - we don't set it ourselves.

Mathf.SmoothDampAngle(angles.y, newRotation.y, ref _velocity, minimumTimeToReachTarget, maximumRotateSpeed)

maximumRotateSpeed needs little explaining, but minimumTimeToReachTarget allows us to have a slower smoothed start and finish to our rotation.  When set to 0 the rotation will complete as fast as possible without exceeding the maximumSpeed, a larger value indicates that we are happy that the rotation be slower so that it will complete closing the continually updated rotation distance in the ideal time.  The practical upshot of that is that rotations will smoothly slow down as the target value is approached.

	void Update () {
		var newRotation = Quaternion.LookRotation(_cameraTransform.position - _transform.position).eulerAngles;
		var angles = _transform.rotation.eulerAngles;
		_transform.rotation = Quaternion.Euler(angles.x, Mathf.SmoothDampAngle(angles.y, newRotation.y, ref _velocity, minimumTimeToReachTarget, maximumRotateSpeed),
			angles.z);
	}

The whole update routine works out our target rotation, takes the current rotation of the enemy and replaces the Y axis rotation with the SmoothDampAngle to rotate the enemy towards our player.  A very useful effect.

Making a Text Mesh Face the Camera

Ok so lets move on with our test game a bit.  Our enemies are pretty boring and we are going to want to give them a bit of AI.  The first thing we will do is have them sleep until the player is close to them - we want to indicate that they are sleeping by having a few snorring Zzzzz appear above them when they are in this state.

Tutorial Scene 5

So we will start by adding the beginnings of a Finite State Machine to each enemy.  It can be found in MovementStateMachine.

using UnityEngine;
using System.Collections;

public class MovementStateMachine : MonoBehaviour {

	public bool sleeping = true;
	public Transform sleepingPrefab;

	// Use this for initialization
	IEnumerator Start () {
		var t = transform;
		while(true)
		{
			yield return new WaitForSeconds(Random.value * 3f + 2);
			if(sleeping)	
			{
				Instantiate(sleepingPrefab, t.position + Vector3.up * 3f, t.rotation);
			}
		}
	}
}

We have a very simple state machine that is either sleeping or not - not much happens when it wakes up yet!  And in fact nothing is there to wake it yet.  But it will fit our purposes of showing how to rotate items to look right facing the camera.

When the script starts it enters a coroutine that will instantiate a prefab above the enemy's head every few seconds if the script is sleeping at that moment.

Notice how we can cache components in local variables when we use coroutines

The prefab itself is simply a Text Mesh and Mesh Renderer with a script to allow it to rise up over time, and our BasicLookAtPlayer  script from scene 1.

The rising script is also straightforward.

using UnityEngine;
using System.Collections;

public class GentleRiseAndDestroy : MonoBehaviour {

	public float timeToLive = 2f;
	Transform _transform;

	// Use this for initialization
	void Start () {
		_transform = transform;
		Destroy(gameObject, timeToLive);
	}

	// Update is called once per frame
	void Update () {
		_transform.position += Vector3.up * Time.deltaTime;
	}
}

The script will destroy the object after a couple of seconds and make the object rise up slowly each frame.

The effect we are looking for is the Zzzzzz to appear to be straight on to the camera no matter where it is - rather than us being able to move around behind them.  But there are a couple of problems here.

Firstly the Zzzzzz appear backwards due to the fact that Unity wants the forward of a Text Mesh to point away from the camera in order for the text to read correctly left to right.

Unity has a couple of oddities when it comes to which way objects should point.  Planes generated by Unity normally face up rather than forward, while Text Meshes face -forward.

When you get close to the enemies you see that there is another problem, the Zzzzzz aren't pointing outwards from the screen.  A lot of people are confused by this (I know I was) - we have a script that says LookAt the camera - why isn't it looking at it?

Modifying Transform Vectors

The problem with LookAt is that it is actually looking at the location of the camera.  But the camera is located at a fixed point in the world, while the image it projects represents the things seen on its focal plane.  The objects are looking at the single point and, depending on where they are, that's not relative to where they are being rendered on the screen by the camera.

Tutorial Scene 6

There's a neat trick to doing this, although it's not advised in many circumstances and we will see another way of doing it in the next section.  You probably know that transform.forward represents the forward direction of an object in the same way as Vector3.forward represents forward in world coordinates.  What a lot of people don't know is that you can also set transform.forward and it will rotate the object!

Now what we actually want our Text Mesh to do is to point at the camera's focal plane - not look at it.  Basically we want it to adopt the camera's forward vector.

Remember that Test Meshes are reflected.  If this was a normal model we would want it to adopt -transform.forward in order for it to face the camera.

using UnityEngine;
using System.Collections;

public class ReversedCameraDirection : MonoBehaviour {

	Transform _transform;
	Transform _cameraTransform;

	// Use this for initialization
	void Start () {
		_transform = transform;
		_cameraTransform = Camera.main.transform;
	}

	// Update is called once per frame
	void Update () {
		_transform.forward = _cameraTransform.forward;
	}
}

We add this ReverseCameraDirection script to the Zzzzz Sleeping Text prefab in place of the BasicLookAtPlayer script and away we go.

Setting the transform vectors forward, up and right seems very powerful and lets you think in terms of vectors rather than rotations - but beware, when you set one of these values you are not controlling the other vectors and you will sometimes find things flip or rotate in unexpected ways.

Combining Rotations

So our Zzzzzz themselves aren't very interesting and they could give us an opportunity to demonstrate how we can use Quaternion combinations.  Let's have the Zzzzzz slowly rotate as they drift upwards - what I mean is, have them face the camera but spin slowly around on the plane of the screen.

Tutorial Scene 7

To achieve this rotation we create a new SpinAroundForward script.

using UnityEngine;
using System.Collections;

public class SpinAroundForward : MonoBehaviour {

	Transform _transform;
	float _rotatingFactor;

	void Start()
	{
		_transform = transform;
		_rotatingFactor = 0.2f + (Random.value * 0.8f);
	}

	// Update is called once per frame
	void Update () {
		_transform.rotation = _transform.rotation * Quaternion.AngleAxis(Time.deltaTime * 20 * _rotatingFactor, Vector3.forward); 
	}
}

This script creates a random rotating factor during Start and then applies it during Update.

You can see how we combine the current rotation of the object with a rotation around Vector3.forward.  Quaternion.AngleAxis makes a Quaternion that is a number of degrees rotation around a given axis.  We use the * operator to combine it with the current rotation of the Zzzzzz.

Now if you run this in the tutorial scene you will see that the text rotates, but doesn't face the camera.  There's a good reason for this: our ReverseCameraDirection script is turned off!  If you click on Sleeping Text 3 and enable ReverseCameraDirection you will then see that the Zzzzzz face the camera but don't rotate.  That there is one of the pitfalls of setting a Transform vector directly - it messes up your rotation combinations.  We need a different approach if we are going to achieve our objective.

Tutorial Scene 8

In Scene 8 we replace ReverseCameraDirection with a Quaternion version AdjustForwardToPointToCamera.

using UnityEngine;
using System.Collections;

public class AdjustForwardToPointToCamera : MonoBehaviour {

	Transform _transform;
	Transform _cameraTransform;

	// Use this for initialization
	void Start () {
		_transform = transform;
		_cameraTransform = Camera.main.transform;
	}

	// Update is called once per frame
	void Update () {
		_transform.rotation = Quaternion.FromToRotation(_transform.forward, _cameraTransform.forward) * _transform.rotation;
	}
}

Quaternion.FromToRotation is very powerful when you have some vectors you need adjusting!  It can be used to have an object rotate to the surface of the thing its resting on by adjusting the up vector of the object to be the surface normal for instance.  In our case we are going to use it to rotate the transform.forward of our Zzzzzz to be the same as the camera's transform.forward.

Just a reminder, remember that Text Meshes are backwards - if this was a normal object we would be adjusting the transform.forward of the object to be -transform.forward of the camera so it pointed at it.

We combine the Quaternion we created from rotating forward to the correct angle with the current rotation (which is also being changed by our SpinAroundForward script) and that's it, we have a camera facing, rotating, Zzzzzz.  (Who would have thought it!)

Rotating a Plane to Face the Camera

Ok so we've suitably sorted out getting Text Meshes to face the camera - now let's concern ourselves with planes.  A Unity created plane is a good place to put a 2D texture to show it to the player.  Let's make a 2D billboard plane that always faces the screen.

To give ourselves a reason to do this we are going to give our enemies a "mood".  Basically they will start with one level of happiness and get gradually more unhappy over time.  In a game we might think of lots of reasons why this might be interesting.

The player is going to be able to tell the mood of the enemy when they look straight at them.  We will display one of a number of smilies that rises up from the enemy when the player looks directly at them - the colour of the smiley will also be a more accurate description of the mood from Green - happy to Red - angry.

This is going to introduce a few new scripts of course.

First let's think about how we will "look directly at" an enemy.  We need to attach a script to the "player" that will test what is directly infront of her.

using UnityEngine;
using System.Collections;

public class LookTrigger : MonoBehaviour {

	Transform _transform;

	// Use this for initialization
	void Start () {
		_transform = transform;
	}

	// Update is called once per frame
	void Update () {
		RaycastHit hit;
		if(Physics.SphereCast(_transform.position + _transform.forward * 0.5f, 3f, _transform.forward, out hit, 400))
		{
			hit.collider.SendMessage("LookedAt", SendMessageOptions.DontRequireReceiver);
		}
	}
}

We send out a SphereCast from just infront of the player and, if it hits something, we send the hit object a message that it has been "LookedAt".

We use a SphereCast because it allows for a bit more inaccuracy when looking at things that might be far away.

Ok, so that was easy - now we need the enemy to have it's mood.  We have a new script called EnemyMood.

using UnityEngine;
using System.Collections;

public class EnemyMood : MonoBehaviour {

	public MoodIndicator moodIndicatorPrefab;
	public float _mood;

	Transform _transform;
	MoodIndicator _currentIndicator;

	void Start () {
		_transform = transform;
		mood = Random.Range(30,99);
	}

	void Update () {
		mood -= Time.deltaTime/2;
	}

	void LookedAt()
	{
		if(_currentIndicator)
			return;

		_currentIndicator = Instantiate(moodIndicatorPrefab, _transform.position + Vector3.up * 3.5f,
			Quaternion.identity) as MoodIndicator;

		_currentIndicator.enemy = this;
	}

}

We start by initialising a mood variable to a value between 30 and 99 - then we decrement this slowly in the Update call.

We've added a prefab variable for the plane to display the mood and given that prefab a MoodIndicator script which we will examine momentarily.

When the EnemyMood receives a LookedAt message it checks whether there is already a current MoodIndicator and returns if there is.

Unity will automatically make this variable return null/false when the MoodIndicator is Destroyed.

If there wasn't a current MoodIndicator it makes one and places it just above the enemy - then it teaches the indicator about the enemy it represents.

The MoodIndicator handles displaying the plane and choosing the right graphic to appear on it.

using UnityEngine;
using System.Collections;

public class MoodIndicator : MonoBehaviour {

	public Texture2D[] moodIndicators;
	public Color happyColor = Color.green;
	public Color angryColor = Color.red;
	public float fadeTime = 4f;
	public EnemyMood enemy;

	Transform _transform;
	Transform _cameraTransform;
	Material _material;
	Color _color;
	Quaternion _pointUpAtForward;

	void Start () {

		_pointUpAtForward = Quaternion.FromToRotation(Vector3.up, Vector3.forward);

		_material = renderer.material;

		//Set the graphic
		_material.mainTexture = moodIndicators[
			Mathf.Clamp(
				Mathf.RoundToInt( enemy.mood/(100/moodIndicators.Length)),
				0,
				moodIndicators.Length-1)
			];

		//Calculate the color
		var moodRatio = enemy.mood/100;
		_material.color = _color = new Color(
			angryColor.r * (1 - moodRatio) + happyColor.r * moodRatio,
			angryColor.g * (1 - moodRatio) + happyColor.g * moodRatio,
			angryColor.b * (1 - moodRatio) + happyColor.b * moodRatio
		);

		Update();
	}

	void Awake()
	{
		_transform = transform;
		_cameraTransform = Camera.main.transform;
	}

	public void Update () {

		//Point a plane at the camera
		_transform.rotation = Quaternion.LookRotation(-_cameraTransform.forward, Vector3.up)
			* _pointUpAtForward;

		//Fade out the graphic
		_color.a -= Time.deltaTime/fadeTime;
		_material.color = _color;
	}
}

This is our first really substantial script so let's break it into parts.

	public Texture2D[] moodIndicators;
	public Color happyColor = Color.green;
	public Color angryColor = Color.red;
	public float fadeTime = 4f;
	public EnemyMood enemy;

Our public variables are an array of the images to use as mood indicators - these are smilies for sad, happy etc.  There are 4 in the example project.  We have a happy color and an angry color, then we want to fade out the indicator over time, so we have a variable that contains the number of seconds before the whole plane becomes transparent.  Finally we have the enemy to which this indicator relates.  All of these will be set by the time we get to Start.

	void Start () {

		_pointUpAtForward = Quaternion.FromToRotation(Vector3.up, Vector3.forward);

		_material = renderer.material;

		//Set the graphic
		_material.mainTexture = moodIndicators[
			Mathf.Clamp(
				Mathf.RoundToInt( enemy.mood/(100/moodIndicators.Length)),
				0,
				moodIndicators.Length-1)
			];

		//Calculate the color
		var moodRatio = enemy.mood/100;
		_material.color = _color = new Color(
			angryColor.r * (1 - moodRatio) + happyColor.r * moodRatio,
			angryColor.g * (1 - moodRatio) + happyColor.g * moodRatio,
			angryColor.b * (1 - moodRatio) + happyColor.b * moodRatio
		);

		Update();
	}

The first thing we do is create a cached Quaternion that can turn an object so that it's up vector points at it's forward vector.  If you recall this is what we need to make planes face the camera.

Next we select a texture that represents the mood by dividing the creatures current mood (a number between 0 and 98) by a factor that will put into the range of textures we've provided - while clamping to make sure we always get a valid texture.

Then we decide on a colour for the smiley by using a proportion of the angry and happy colours depending on the mood.

Finally we call Update so that the rotation code is called immediately.

The Update function looks like this:

public void Update () {

		//Point a plane at the camera
		_transform.rotation = Quaternion.LookRotation(-_cameraTransform.forward, Vector3.up)
			* _pointUpAtForward;

		//Fade out the graphic
		_color.a -= Time.deltaTime/fadeTime;
		_material.color = _color;
	}

First we work out the rotation that would point a normal object at the camera by using the Quaternion.LookRotation function, passing the -transform.forward of the camera, then we combine this with our cached rotation that points the up of an object at forward.  Our plane is now suitably aligned.

The second part of the routine just fades out the alpha of the image over the predefined fade time.

That's it - if you try it out you will see the indicators popping up when you center the enemy on the screen.  They get angry quickly!

Adding Some Movement

Ok so it's time for our enemies to go and attack the player.  We are going to use Quaternions again, to set the target position of the enemy to be a different rotation around the player in an attempt to have them sneak up, and space out when attacking.  That at the moment is in the future - let's have a look at our new MovementStateMachine3.  Warning - it's grown up a bit!

using UnityEngine;
using System.Collections;

public class MovementStateMachine3 : MonoBehaviour {

	public bool sleeping = true;
	public Transform sleepingPrefab;
	public float attackDistance = 5;
	public float sleepDistance = 30;
	public float speed = 2;
	public float health = 20;
	public float maximumAttackEffectRange = 1f;

	Transform _transform;
	Transform _player;
	public Transform target;

	public EnemyMood _mood;

	CharacterController _controller;
	AnimationState _attack;
	AnimationState _die;
	AnimationState _hit;
	Animation _animation;

	float _attackDistanceSquared;
	float _sleepDistanceSquared;
	float _attackRotation;
	float _maximumAttackEffectRangeSquared;
	float _angleToTarget;

	bool _busy;

	// Use this for initialization
	IEnumerator Start () {
		_transform = transform;
		_player = Camera.main.transform;

		_mood = GetComponent<EnemyMood>();

		_attackDistanceSquared = attackDistance * attackDistance;
		_sleepDistanceSquared = sleepDistance * sleepDistance;
		_maximumAttackEffectRangeSquared = maximumAttackEffectRange * maximumAttackEffectRange;

		_controller = GetComponent<CharacterController>();

		_animation = animation;
		_attack = _animation["attack"];
		_hit = _animation["gothit"];
		_die = _animation["die"];

		_attack.layer = 5;
		_hit.layer = 5;
		_die.layer = 5;

		_controller.Move(new Vector3(0,-20,0));

		while(true)
		{
			yield return new WaitForSeconds(Random.value * 6f + 3);
			if(sleeping)	
			{
				var newPrefab = Instantiate(sleepingPrefab, _transform.position + Vector3.up * 3f, Quaternion.identity) as Transform;
				newPrefab.forward = Camera.main.transform.forward;
			}
		}

	}

	void Update()
	{
		//Check if something else is in control
		if(_busy)
			return;

		if(sleeping)
		{
			if((_transform.position - _player.position).sqrMagnitude < _attackDistanceSquared)
			{
				sleeping = false;
				target = _player;
				//Where this enemy wants to stand to attack
				_attackRotation = Random.Range(60,310);
			}
		}
		else
		{
			//If the target is dead then go back to sleep
			if(!target)
			{
				sleeping = true;
				return;
			}

			var difference = (target.position - _transform.position);
			difference.y /= 6;
			var distanceSquared = difference.sqrMagnitude;

			//Too far away to care?
			if( distanceSquared > _sleepDistanceSquared)
			{
				sleeping = true;
			}
			//Close enough for an attack?
			else if( distanceSquared < _maximumAttackEffectRangeSquared && _angleToTarget < 40f)
			{
				StartCoroutine(Attack(target));
			}
			//Otherwise time to move
			else
			{

				//Decide target position
				var targetPosition = target.position + (Quaternion.AngleAxis(_attackRotation, Vector3.up) * target.forward * maximumAttackEffectRange * 0.8f);
				var basicMovement = (targetPosition - _transform.position).normalized * speed * Time.deltaTime;
				basicMovement.y = 0;
				//Only move when facing
				_angleToTarget = Vector3.Angle(basicMovement, _transform.forward);
				if( _angleToTarget < 70f)
				{
					basicMovement.y = -20 * Time.deltaTime;
					_controller.Move(basicMovement);
				}
			}
		}
	}

	void OnTriggerEnter(Collider hit) 
	{
		if(hit.transform == _transform)
			return;

		if(hit.transform == _player)
		{
			StartCoroutine(Attack(_player));
		}
		else
		{
			var rival = hit.transform.GetComponent<EnemyMood>();
			if(rival)
			{
				if(Random.value > _mood.mood/100)
				{
					StartCoroutine("Attack",rival.transform);
				}
			}
		}
	}

	IEnumerator Attack(Transform victim)
	{
		sleeping = false;
		_busy = true;
		target = victim;
		_attack.enabled = true;
		_attack.time = 0;
		_attack.weight = 1;
		//Wait for half way through the animation
		yield return StartCoroutine(WaitForAnimation(_attack, 0.5f));
		//Check if still in range
		if(victim && (victim.position - _transform.position).sqrMagnitude < _maximumAttackEffectRangeSquared)
		{
			//Apply the damage
			victim.SendMessage("TakeDamage", 1 + Random.value * 5, SendMessageOptions.DontRequireReceiver);
		}
		//Wait for the end of the animation
		yield return StartCoroutine(WaitForAnimation(_attack, 1f));
		_attack.weight = 0;
		_busy = false;
	}

	void TakeDamage(float amount)
	{
		StopCoroutine("Attack");
		health -= amount;
		if(health < 0)
			StartCoroutine(Die());
		else
			StartCoroutine(Hit());
	}

	IEnumerator Die()
	{
		_busy = true;
		_animation.Stop();
		yield return StartCoroutine(PlayAnimation(_die));
		Destroy(gameObject);
	}

	IEnumerator Hit()
	{
		_busy = true;
		_animation.Stop();
		yield return StartCoroutine(PlayAnimation(_hit));
		_busy = false;
	}

	public static IEnumerator WaitForAnimation(AnimationState state, float ratio)
	{
		state.wrapMode = WrapMode.ClampForever;
		state.enabled = true;
		state.speed = state.speed == 0 ? 1 : state.speed;
		while(state.normalizedTime < ratio-float.Epsilon)
		{
			yield return null;
		}
	}

	public static IEnumerator PlayAnimation(AnimationState state)
	{
		state.time = 0;
		state.weight = 1;
		state.speed = 1;
		state.enabled = true;
		var wait = WaitForAnimation(state, 1f);
		while(wait.MoveNext())
			yield return null;
		state.weight = 0;

	}

}

Ok so let's examine the interesting bits - here's the new stuff in Start.

       _mood = GetComponent<EnemyMood>();

		_attackDistanceSquared = attackDistance * attackDistance;
		_sleepDistanceSquared = sleepDistance * sleepDistance;
		_maximumAttackEffectRangeSquared = maximumAttackEffectRange * maximumAttackEffectRange;

		_controller = GetComponent<CharacterController>();

		_animation = animation;
		_attack = _animation["attack"];
		_hit = _animation["gothit"];
		_die = _animation["die"];

		_attack.layer = 5;
		_hit.layer = 5;
		_die.layer = 5;

Clearly we are doing a bunch of caching for performance reasons - we are also squaring all of the distances.  This is so we can avoid square roots when working out how far away things are from the enemy.

Calculating the distance between two things uses Pythagoras's theory and requires a square root.  Square roots are recursive functions that are very expensive.  We can avoid the square root of calculating whether something is in range by squaring the distance we want to check.

We put several of the animations on a high layer so that they will override the normal idle animation.

Ok so the Update function is pretty big - it starts like this:

                //Check if something else is in control
		if(_busy)
			return;

For attacking and being hit we are going to allow something else to control the character - when that happens _busy will be true and Update will be ignored.

Next we need the logic for when the enemy is sleeping.

                if(sleeping)
		{
			if((_transform.position - _player.position).sqrMagnitude < _attackDistanceSquared)
			{
				sleeping = false;
				target = _player;
				//Where this enemy wants to stand to attack
				_attackRotation = Random.Range(60,310);
			}
		}

When the enemy is sleeping we just check whether the player is inside it's attackDistance, if it is - we switch off sleeping mode (yay!) and set the target to te the player.  We also allocate a particular rotation around the player we want to target (sneaky fellows try to approach from the side or behind!)  - currently this is just stored in a variable, we'll see where it is used next.  This is what happens when the enemy isn't sleeping:

			//If the target is dead then go back to sleep
			if(!target)
			{
				sleeping = true;
				return;
			}

			var difference = (target.position - _transform.position);
			difference.y /= 6;
			var distanceSquared = difference.sqrMagnitude;

			//Too far away to care?
			if( distanceSquared > _sleepDistanceSquared)
			{
				sleeping = true;
			}
			//Close enough for an attack?
			else if( distanceSquared < _maximumAttackEffectRangeSquared && _angleToTarget < 40f)
			{
				StartCoroutine(Attack(target));
			}
			//Otherwise time to move
			else
			{

				//Decide target position
				var targetPosition = target.position + (Quaternion.AngleAxis(_attackRotation, Vector3.up) * target.forward * maximumAttackEffectRange * 0.8f);
				var basicMovement = (targetPosition - _transform.position).normalized * speed * Time.deltaTime;
				basicMovement.y = 0;
				//Only move when facing
				_angleToTarget = Vector3.Angle(basicMovement, _transform.forward);
				if( _angleToTarget < 70f)
				{
					basicMovement.y = -20 * Time.deltaTime;
					_controller.Move(basicMovement);
				}
			}

We put the enemy to sleep if it's target has gone or it is too far away.  Then if that isn't the case, if we are within striking distance we start a coroutine to perform an attack.  If we aren't asleep and we aren't close enough to attack then we consider moving towards the target.

The first part of that is working out where we want to stand.  This is a combination of the target's position, the angle we decided on when we targeted it and the striking distance (which we want to be at 80% of).  We use a Quaternion based on the angle to modify a vector based on the way the target is facing and scale that vector by the 80% of striking distance.  All of that gets called targetPosition. For example if the angle we picked was 180 and the striking distance was 2 then the target position would be 1.6 world units directly behind the camera.

Next we work out the angle between where we are facing and where we want to go - if there's too much of a discrepancy we don't move yet (and let the rotation script sort out the angle).  If it was ok then we move towards the target position  having added a bit of gravity to keep the enemy on the ground. Phew!

Using a Coroutine to Wait for an Animation

Ok so we know that if we were in range then an attack coroutine was started.  What we want to do is pause the normal behaviour, play an attack animation and apply damage half way through the animation as the blow is landed.  This is how we achieve that:

First - to make the routine more useful, we always wake up an enemy when Attack is called - that's because you can also trigger an attack by getting enemies to run into each other!

	IEnumerator Attack(Transform victim)
	{
		sleeping = false;
		_busy = true;
		target = victim;
		_attack.enabled = true;
		_attack.time = 0;
		_attack.weight = 1;
		//Wait for half way through the animation
		yield return StartCoroutine(WaitForAnimation(_attack, 0.5f));
		//Check if still in range
		if(victim && (victim.position - _transform.position).sqrMagnitude < _maximumAttackEffectRangeSquared)
		{
			//Apply the damage
			victim.SendMessage("TakeDamage", 1 + Random.value * 5, SendMessageOptions.DontRequireReceiver);
		}
		//Wait for the end of the animation
		yield return StartCoroutine(WaitForAnimation(_attack, 1f));
		_attack.weight = 0;
		_busy = false;
	}

Setting _busy = true turns off Update while this routine runs.  _attack is the attack animation which we enable, set to the start and give it full weight.  The animation will now start playing.  Next comes a cool bit - we start another coroutine that will monitor the animation, in this case waiting for it to be 50% complete.  We'll look at that routine in a moment.

When the animation is 50% complete we check that the intended victim isn't already dead, and that it is still in range and if those conditions are met we send it a TakeDamage message passing a random amount of damage to be inflicted.

We then wait for the animation to complete before removing our busy status and disabling the animation.

So let's take a look at the animation waiting coroutine:

	public static IEnumerator WaitForAnimation(AnimationState state, float ratio)
	{
		state.wrapMode = WrapMode.ClampForever;
		state.enabled = true;
		state.speed = state.speed == 0 ? 1 : state.speed;
		while(state.normalizedTime < ratio-float.Epsilon)
		{
			yield return null;
		}
	}

As you can see it's pretty simple!  First we just make sure the animation isn't going to loop or reset as that might mean that we missed the end point.  We also make sure that it is enabled and has some speed, otherwise this routine would never end.

Then the loop just waits for the .normalizedTime (the ratio of the clip) to be greater than or equal to the value we passed in (- a floating point fudge factor).

More States

Ok so now we've attacked and sent a damage message we should look at how that message is handled.  In this demo the player is invincible (haha!) but enemies can inflict damage on each other (it's just not fair is it).

When the enemy receives a TakeDamage message it needs to react:

	void TakeDamage(float amount)
	{
		StopCoroutine("Attack");
		health -= amount;
		if(health < 0)
			StartCoroutine(Die());
		else
			StartCoroutine(Hit());
	}

First we stop any current attack, then decrement the health and either start a Hit or a Die coroutine.

	IEnumerator Die()
	{
		_busy = true;
		_animation.Stop();
		yield return StartCoroutine(PlayAnimation(_die));
		Destroy(gameObject);
	}

	IEnumerator Hit()
	{
		_busy = true;
		_animation.Stop();
		yield return StartCoroutine(PlayAnimation(_hit));
		_busy = false;
	}

Both coroutines play an animation to completion, Die destroys the object, Hit re-enables it.

And that's pretty much it for this tutorial.  You should check out RotateToFaceTarget which replaces our earlier rotation script, using the target variable rather than always facing the player - this let's enemies target each other.  The only other script of interest is WalkingAnimation:

using UnityEngine;
using System.Collections;

public class WalkingAnimation : MonoBehaviour {

	Transform _transform;
	Vector3 _lastPosition;
	AnimationState _walk;

	public float minimumDistance = 0.01f;

	// Use this for initialization
	void Start () {
		_transform = transform;
		_lastPosition = _transform.position;
		_walk = animation["walk"];
		_walk.layer = 2;
	}

	// Update is called once per frame
	void Update () {
		var moved = (_transform.position - _lastPosition).magnitude;
		_lastPosition = _transform.position;
		if(moved < minimumDistance)		
		{
			_walk.weight = 0;
		}
		else
		{
			_walk.weight = moved * 100;
			_walk.enabled = true;
			_walk.speed = 1;
		}
	}
}

This script blends in the walking animation based on how fast the enemy is moving.

Quaternion Inner Workings

 


Courtesy of @Scribe on UA:

Firstly I should point out that you will probably never need to know any of this to successfully code in Unity and, unless you become very knowledgeable about Quaternions, should probably never set them directly. That is not to say you can't, but it is generally advised against unless you know what you are doing!

Secondly, there is a heck of a lot of information out there on the internet on the subject of quaternions and/or converting them to Euler angles (and other rotation representations).

Thirdly, for anyone who is looking for just the function to handle all this for you checkout:

1. Quaternion.eulerAngles
2.Quaternion.Euler

The Basics

A quaternion rotation is made up of 4 numbers, whose values all have a minimum of -1 and a maximum of 1, i.e (0, 0, 0, 1) is a quaternion rotation that is equivalent to 'no rotation' or a rotation of 0 around all axis.

Quaternions are widely used in coding as:

1. They are almost always unique, as opposed to Euler angles where 360 degrees around an axis is the same as 0 around the same axis, is the same as 720.. etc, this confusion does not exist with quaternions, where every orientation is related to 2 quaternion, q and -q.
2. They are simple to create/compose
3. They are 'easy' to interpolate between (as they are not commutative i.e a*b =/= b*a), whereas Euler Angle representation suffers from 'gimbal lock'.

Note:

All rotation quaternions are unit quaternions (their length is 1).

Conversions

Angle-Axis to Quaternion

Given a normalized (length 1) axis representation (x, y, z) and an angle A.

The corresponding quaternion is equal to:

Q = [sin(A/2)*x, sin(A/2)*y, sin(A/2)*z, cos(A/2)] (corresponding to [x, y, z, w])

Euler angle to Quaternion
Given an Euler rotation (X, Y, Z) [using orthogonal axes]

x = sin(Y)sin(Z)cos(X)+cos(Y)cos(Z)sin(X)
y = sin(Y)cos(Z)cos(X)+cos(Y)sin(Z)sin(X)
z = cos(Y)sin(Z)cos(X)-sin(Y)cos(Z)sin(X)
w = cos(Y)cos(Z)cos(X)-sin(Y)sin(Z)sin(X)
Q = [x, y, z, w]

Slerp (Spherical linear interpolation) 

To interpolate between two quaternions is very simple:

Let t be a value that increases from 0 to 1.

The angle (A) between the two quaternions can be found using the dot product:

Q1*Q2 = Q1.x*Q2.x+Q1.y*Q2.y+Q1.z*Q2.z+Q1.w*Q2.w = A

then the **un-normalized** interpolation (Q3) is `(sin((1-u)A)/sin(A))*Q1 + (sin((uA)/sin(A))*Q2`

to find the normalized quaternion you need to divide by Q3's length.

Q3/|Q3| = Q4

Hence, Q4 is a Quaternion rotation between Q1 and Q2 based on t.

Links/Extra reading

1. Euler to Quaternion
2. Quaternion To Euler
3. AngleAxis to Quaternion
4. Quaternion to AngleAxis

On these pages heading is the rotation around y, attidue around z, and bank around the x axis.

Disclaimer

I am nowhere close to an expert at quaternions, so I apologise if anything I have written is incorrect. I marking this as a community wiki, so feel free to change something if its incorrect!

Scribe

Conclusion

We hope you've found this tutorial useful.  Please feel free to leave comments - especially useful would be comments regarding what future features you'd like to see explained.

Mike(whydoidoit)

Never manipulate the x, y, z and w parameters of a Quaternion if you do not completely understand the complex mathematics used.  These values are not directly related to the rotation that you see in the inspector.

 
 
 
 
, , , , , ,

9 Responses to "Quaternions & Rotations – C#"

  • Joseph
    October 26, 2012 - 1:09 pm Reply

    Great work! I’ve learned a lot in this tutorial. I’d like to see more about game structures and algorithms using C# in Unity environment.

  • Jason
    November 7, 2012 - 6:26 am Reply

    Lovely tutorial! Would be great to have embedded videos or even live demos to further illustrate each section.

    • fafase
      November 7, 2012 - 7:05 am Reply

      Hi Jason. Slowly, videos are making their way into the articles. Lately, we added some for the common mistake section but definitely, we will add some for this one too.
      EDIT: In the meanwhile, the memory management article has a video with little monsters that are using the advice from the Quaternion article.

  • mick
    November 28, 2012 - 12:54 am Reply

    The spherecast in scene 9 doesn’t seem consistent. For instance, with the group of 3 enemies next to the house, if you try to target the one all the way on the right the collision seems to only happen you’re ray/sphere intersects above the enemies’ head. I put a Debug.DrawRay in there to be precise about it. Any ideas?

    Added to this:
    hit.collider.SendMessage(“LookedAt”, SendMessageOptions.DontRequireReceiver); // sphere cast code
    Debug.DrawRay(_transform.position, _transform.forward * 100.0f); // debug visualization

  • Dizzy
    January 29, 2013 - 10:06 pm Reply

    This is great, I learned alot. Thanks, man. You are making more competent programmers out of us all.

  • Tom
    July 18, 2013 - 10:18 pm Reply

    The project unitypackage causes unity to crash when I try to open it?

  • Tim
    October 17, 2013 - 5:53 pm Reply

    Very Very informative tutorial. Well done and thank you for sharing.

    I started to play with Quaternion after the article and have a question.

    What is the best way to rotate and object around an arbitrary line that does not run through the world origin.
    Is this something that Quaternion could be used for?

    • whydoidoit
      October 17, 2013 - 6:57 pm Reply

      A line as an Axis? Or do you mean an orbit?

      Quaternion.AngleAxis(someDegress, someVector); will rotate around an arbitrary axis

  • Parth Patel
    December 23, 2013 - 4:13 pm Reply

    very good tutorial. Cleared a lot of things about Quaternion.
    One question though,
    What is best way to rotate a circle for more than 180 degrees. I want to rotate a circle for specific degree like 260 or 300 and right now quaternion doesn’t rotate more than 180.
    Right now m doing kinda of hack like if I want to rotate for 240 degree then I subtract that number by 180 and calling Quaternion.slerp for first rotation for 180 and again calling for rest of 60 degrees.
    Here is the code

    float angle = Vector3.Angle( v1, v2 );
    float remainingAngle = 0.0f;
    int sign = Vector3.Cross(v1, v2).y < 0 ? -1 : 1;
    Debug.Log( "Angle: " + angle * sign );

    if( sign 180.0f )
    {
    remainingAngle = Mathf.Abs(angle) – 180.0f;
    angle = 180.0f * sign;
    Debug.Log( “Remaining Angle: ” + remainingAngle * sign );
    }

    Quaternion rotationAxis = Quaternion.AngleAxis( angle , Vector3.up ) * this.transform.rotation;
    float elapsedTime = 0.0f;
    float totalTime = 4.0f;

    while( elapsedTime totalTime )
    elapsedTime = totalTime;

    this.transform.rotation = Quaternion.RotateTowards( this.transform.rotation, rotationAxis, 50.0f * Time.deltaTime );
    yield return null;
    }

    StartCoroutine( RemainingRotation( remainingAngle ) );

    So is there a way to rotate in one shot instead of calling it 2 times?

Leave a Reply