Gamasutra is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.   Latest Jobs
December 5, 2020 Latest Blogs
December 5, 2020 Press Releases
December 5, 2020
Games Press About Gama Network
If you enjoy reading this site, you might also want to check out these UBM Tech sites: Blogs

# Circling in Unity3D with RotateAround

by Charles Cordingley on 02/23/15 12:25:00 pm The following blog post, unless otherwise noted, was written by a member of Gamasutra’s community.
The thoughts and opinions expressed are those of the writer and not Gamasutra or its parent company.

Concealed Intent is on Steam Greenlight! Please vote for it here: http://steamcommunity.com/sharedfiles/filedetails/?id=394572500

I'll be GDC this year. If you are there too and watch to meet up, feel free to send me a message.

This post was first published on the Concealed Intent blog

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 thisTransform.RotateAround(Vector3 point, Vector3 axis, float angle) function do? It moves the Transform through 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 axis as Vector3.right or 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.up or 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. The 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. This function 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:

do {
Vector3 perturb = new Vector3(Random.Range(-1, 1), Random.Range(-1, 1), Random.Range(-1, 1)).normalized;
normal = Vector3.Cross(me.Position - center, perturb).normalized;
bool newOrientation = Vector3.Dot(normal, Vector3.up) > 0;
if (orientation != newOrientation) {
normal = -normal;
}
} while (count++ < RETRIES && normal.magnitude < 0.01f);


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 orientation.

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:

Vector3[] CreateCircle(Vector3 self, Vector3 center, Vector3 up, float radius) {
Vector3[] circle = new Vector3[steps];
dummyTrans.position = self;
for (int i = 0; i < circle.Length; i++) {
float ratio = (i + 1) / (float)steps;
dummyTrans.RotateAround(center, up, 180*ratio);
}
return circle;
}


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.

Contact Details: 