Concealed Intent is on Steam Greenlight! Please vote for it here!
I’ll be GDC this year. If you are there too and watch to meet up, feel free to send me a message.
In Concealed Intent I wanted enemies to circle the players, and not just in a predictable flat y=? plane. No, instead they should fly all sorts of curved paths above and below their targets. The game is fully 3D and the enemy AI should demonstrate this. So began my affair with Unity3D’s Transform.RotateAround. If that function and its uses are second nature to you then this blog post will be old news, if not then read on…
So what exactly does this
Transform.RotateAround(Vector3 point, Vector3 axis, float angle); function do? It moves the
angle degrees around the
point given. That is,
point is the center of a circle with the
Transform on its edge, which moves
angle around this circle.
So what does the
axis vector do? In maths, it is the normal of the plane on which the circle is drawn. Imagine walking on flat ground in a circle and looking straight up into the sky, that is the
axis vector. Now imagine walking in a circle on the side of a steep hill and looking up directly away from the ground. You are no longer looking straight up into the sky, but at an angle because the circle in which you are walking on is itself on an angle. The “up” vector points away from the slope, and again this is the
axis. The circle the sun makes as it crosses the sky would have the
Vector3.left, depending on which way around the circle you wanted it to move.
Now let’s say you wanted to have a spaceship move around another object (its target) in a circle. Although in space it doesn’t matter which way is up as long as the ship continues in a nice circle. So you have a
Transform for the ship and a vector for the target, what is “up”. The
axis could be fixed as
Vector3.right, but then the enemy ships will always circle their targets in the same way – too easy to predict. Instead, each ship should work out its own “up”, but a random vector by itself can’t be used.
point and the
axis parameters define a plane. If the
Transform is not in this plane then rather than rotate around the
point, Unity will shift the plane along the
axis (“up”) until it is in the plane and then rotate around that point. This is shown in the image above. This situation maybe what is desired. Imagine a camera looking down on a player from an angle. To get this camera to rotate around the player while remaining at the same height use
cameraTransform.RotateAround(playerPosition, Vector3.up, angle) (assuming
Vector3.up is the right axis for the game).
If this shifting of the center of the circle is not what is required (as in Concealed Intent) then the
axis provided must define a plane that includes the
Transform. Luckily there is a handy way to calculate such an axis – the cross product, already provided in Unity by Vector3.Cross.
Cross takes two vectors and uses them to define a plane, returning the normal of that plane. Since we know the
Transform to rotate and the
point it should rotate around, one of these vectors is
Transform.position - point. The other vector can be anything, so I just use a random vector. The code I use to get the
axis is below, where
Me is the the enemy, and
center is the point it is circling:
Two extra points to note here. First is that if the vector
Transform.position - point is parallel to the random vector, then the cross product won’t work (it will be the zero vector). This is the reason for the check
normal.magnitude < 0.01f and recalculating the normal if this is
true. A number slightly greater than 0 is used to handle any numerical errors in the calculation. The second line to note is
Vector3.Dot(normal, Vector3.up) > 0; – this checks that the normal always points in expected direction and thus always goes clockwise or counterclockwise depending on the value of
It can also sometimes be useful not to use a random vector when crossing. If you know something special about the situation a specific second vector may be appropriate. For instance, if the direction in which the target is moving is known this could be used as the vector to cross with. Then depending on the orientation of the resulting normal, the
Transform will either rotate towards the direction or movement, or away from it.
Now with the appropriate axis,
Transform.RotateAround(Vector3 point, Vector3 axis, float angle); can be called to get the expected result. However, Concealed Intent does two extra things. Below is the code it uses:
Concealed Intent is turn based – it needs to know the enemy ships’ plans before they actually move. Thus rather than call
RotateAround on the enemy
Transform itself, it uses a dummy transform on an empty
Gameobject instead and stores its position at various (
steps) points in the circle. Also, the enemy may want to spiral into, or away from, the target. This is achieved with the
... * (1+(radius-1)*ratio), if radius is less than 1 it will spiral in; greater than 1 it will spiral away.
So that is nearly all I’ve learnt about moving objects in circles in Unity. RotateAround is a powerful tool, use it wisely.