*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 `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. `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:

```
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);
circle[i] = dummyTrans.position * (1+(radius-1)*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:**

- Concealed Intent website & blog
- Steam Greenlight
- Twitter: @JarrahTech
- IndieDB entry
- TIGSource DevBlog
- Facebook: JarrahTechnology
- Instagram: @JarrahTech
- Mailing List
- Email: charles at jarrahtechnology dot com

Tags: ConcealedIntent, Syndicated, and Unity