There is an awesome article on Gamasutra about Coordinated Unit Movement. They also have a companion post about implementing said movement. However, the posts are from 1999, and they deal mainly in pseudocode. Also, I’m convinced there is absolutely nothing else on the entire internet explaining the topic. So, an experienced developer may be able to read the ideas and know how to implement them, but it has been a bit of a struggle for me as a new developer. I’m going to try to document a step by step implementation of formations, squads, and movement for someone who hasn’t done it before. I’ll be writing in Swift / Sprite Kit, but the code should be easy enough for anyone to follow. I also welcome corrections / improvements from experts.
Arrange Units in Rows and Columns
Before reading anything, I started out by trying to make a Formation class that would be smart enough to handle any size or shape. Wrong approach. Instead, we’ll start with a
Squad class that contains all of the units, and a
Formation strategy that describes the layout of those units. It’s important to note that each arrangement (4×3, 5×2, wedge, etc) would be a new strategy, but for simplicity in this article I’m only going to use one class.
So, that’s the empty
Squad container class. Here is the
Interestingly, a formation is just an array of vectors. Each vector is an offset from
0,0, which is the center of our formation. I place the center row first in the array so that those spots are filled first. As of now, I’m matching the
units array and the
The last step is to take the units and form them up into the appropriate positions. For that, we’ll add a
moveIntoFormation() function to the
And that’s it. With this code, we’ve recreated the screenshot at the top of this section.
Rotating the Formation
Next up, rotation. I won’t cover wheeling just yet, but the first step to wheeling is finding out what the final position should look like. This seems like it should be difficult. How can you rotate the vector
0,64 by 13 degrees and convert it to new coordinates? Well, thanks to a little bit of linear algebra and a lotta bit of internet help, we can apply a simple equation on each vector to adjust it to the new coordinate space. Here is what our new
moveIntoFormation() function looks like.
In particular, look at our
newY variables. They’ve been modified by a hard coded radian. We could easily make that a parameter in the function, and control the units rotation. And, to give credit where it’s due, I pulled the code from this Stack Overflow post.
Improving Unit’s Position Selection
Up until now, each unit has been taking the vector that corresponds to their position in the
units array. That causes people to overlap when they move into formation, as shown in the gif below.
This overlap looks a bit chunky, so let’s add some logic to the
Two import changes to point out:
- We’re looping over a count now instead of the `units` array.
- The unit that gets moved is now determined by `findClosestUnitToPosition()`
Let’s have a look at our new function to determine the closest unit.
With all of that applied, the gif below shows our new
moveIntoFormation() code executing. Notice that the units walk in a smarter fashion now.
We’ve got a lot of the core formation working, so all that is left is polish. Here are a few ideas:
- The `findClosestUnitToPosition()` function is just one of the many modifiers you could run. For example, if we wanted the healer to always be in the middle, we could make a function that moves him first, and then surrounds him. There are a ton of possibilities.
- We could store a vector called `direction` on the `Formation` class. That way, units can face the correct direction once they reach their final position.
- To take direction a step further, we could have an array of directional vectors. Each wall of the formation could face outwards instead of everyone facing the same direction.
- When people assemble, place a random delay between each iteration of the loop. That way, it won’t look like *snap*, everyone moved. It’ll feel more natural.
I’ve implemented a couple of those in the gif below. One note I would leave with is to be careful of doing too much at this step. For example, in my gif, one guy travels a bit too far to get to his spot. I could optimize that, but it would take more loops. I’d prefer to get everything acceptably functional, and then see what room is left for even more polish.