Lost in Space: Part 4 – Some code

This is a 4 part series on how the path finding works in Battlefleet. Please ask in the comments if something doesn’t make sense. Here are parts 1, 2, 3, 4.

So my code has two static functions, one called findPath, and one called reconstructPath.

public class PathFinder {

    public PathFinder() {
    }

    public static ArrayList<MoveOrder> findPath(ArrayList<ShipBase> ships, int shipId, float[] dest) {
    }

    public static ArrayList<MoveOrder> reconstructPath(ArrayList<Node> list) {
    }
}

You might be wondering what the classes MoveOrder, ShipBase and Node are. ShipBase is actually a very big class, with loads of stuff going on, however for this all you need to know is that it has an X, a Y, a Height, a Width and a rotation. MoveOrder, amongst other things, is basically just a X, Y coordinate. And Node we covered in part 2.

Lets look at findPath first, as it’s the interesting one. The first thing you need to add is the list of nodes. That is, the start, the end and one for each of the four corners of each ship.

ArrayList<Node> fullList = new ArrayList<Node>();

Node start = new Node(ships.get(shipId).getCoord());
start.setParentId(-1); // -1 to means "has no parent"
start.setG(0);
start.setF(Geometry.getDistAtoB(start.getCoord(), dest));
start.setOpen();
fullList.add(start);
fullList.add(new Node(dest));

for(int i = 0; i < ships.size(); ++i) {
    if(i != shipId) {
        float [] boundBox = Geometry.getRectFromDimensions(
            ships.get(i).getCoord(),
            ships.get(i).getHeight() + 2 * ships.get(shipId).getHeight(), 
            ships.get(i).getWidth() + 2 * ships.get(shipId).getWidth(),
            ships.get(i).getRotation()
        );

        fullList.add(new Node(new float [] {boundBox[0], boundBox[1]}));
        fullList.add(new Node(new float [] {boundBox[2], boundBox[3]}));
        fullList.add(new Node(new float [] {boundBox[4], boundBox[5]}));
        fullList.add(new Node(new float [] {boundBox[6], boundBox[7]}));
    }
}

The function Geometry.getRectFromDimensions pretty much does what it says, it works out the four corners of a rectangle from a centre point, a height, a width and a rotation. Next you want to go through and do the actual A star algorithm.

while(1) {
    int lowestFIndex = -1;
    int x = 0;
    while(lowestFIndex == -1) {
        if(x >= fullList.size()) {
            return null;
        } else if(fullList.get(x).isOpen()) {
            lowestFIndex = x;
        }
        x++;
    }

    for(int i = 0; i < fullList.size(); ++i) {
        if(fullList.get(i).isOpen() && 
           fullList.get(i).getF() < fullList.get(lowestFIndex).getF()) {
            lowestFIndex = i;
        }
    }

    if(fullList.get(lowestFIndex).isPoint(dest)) {
        return reconstructPath(fullList);
    }

    fullList.get(lowestFIndex).setClosed();

    for(int i = 0; i < fullList.size(); ++i) {
        boolean hasPathToNode = true;

        if(i != lowestFIndex) {
            for(int j = 0; j < ships.size(); ++j) {
                if(j != shipId && 
                   hasPathToNode &&
                   Geometry.lineIntersectsRect(
                       fullList.get(i).getCoord(), 
                       fullList.get(lowestFIndex).getCoord(), 
                       ships.get(j).getCoord(),
                       ships.get(j).getHeight() + ships.get(shipId).getHeight(),
                       ships.get(j).getWidth() + ships.get(shipId).getWidth(),
                       ships.get(j).getRotation())) {
                       hasPathToNode = false;
                }
            }

            if(hasPathToNode) {
                float tempG = fullList.get(lowestFIndex).getG() + Geometry.getDistAtoB(fullList.get(i).getCoord(), fullList.get(lowestFIndex).getCoord());
                float tempF = tempG + Geometry.getDistAtoB(fullList.get(i).getCoord(), dest);

                if(fullList.get(i).isClosed() && tempF >= fullList.get(i).getF()) {
                    continue;
                }

                if(!fullList.get(i).isOpen() || tempF < fullList.get(i).getF()) {
                    fullList.get(i).setParentId(lowestFIndex);
                    fullList.get(i).setG(tempG);
                    fullList.get(i).setF(tempF);

                    if(!fullList.get(i).isOpen()) {
                        fullList.get(i).setOpen();
                    }
                }
            }
        }
    }

    return null;
}

Geometry.lineIntersectsRect, again, does what you might think: tests to see if a line intersects a rectangle. To do this, it just treats the rectangle as four separate lines, and tests each to see if the line passes through them. I can post the code if anyone wants, just let me know in the comments. You might have noticed that H isn’t mentioned anywhere in my code. That’s because, as I said in part 3, I am just using the as-the-crow-files distance, which is given by the function Geometry.getDistAtoB. Comparatively, reconstructPath is a pretty simple function.

ArrayList<MoveOrder> path = new ArrayList<MoveOrder>();

int index = 1; // End node is always index 1
while(list.get(index).getParentId() != -1) {
    path.add(0, list.get(index).convertToMoveOrder());
    index = list.get(index).getParentId();
} 

return path;

It just runs through from the end, until it gets to the start.

So there you have it, hopefully you can make sense of all this. If you notice any errors, please let me know, so that other people don’t have to struggle with the same problems. Have fun not crashing into stuff.

< Part 3

Lost in Space: Part 3 – Distances

This is a 4 part series on how the path finding works in Battlefleet. Please ask in the comments if something doesn’t make sense. Here are parts 1234.

You may remember in part 1 we needed to decide what a node’s “distance values” were. Well in A star we need two distances to calculate the distance value, Wikipedia says:

  1. The past path-cost function, which is the known distance from the starting node to the current node x (usually denoted g(x))
  2. A future path-cost function, which is an admissible “heuristic estimate” of the distance from x to the goal (usually denoted h(x)).

So that’s G, which is the running total of all the distance travelled to get to that node, and H which is an estimate (that cannot be an overestimate) distance to the goal. The total value for each node is the “distance value”, F = G + H.

To calculate G, all we need to do is measure the as-the-crow-flies distance between our current node and it’s parent, PLUS it’s parent’s G.

To calculate H, we need to first decide on a way to estimate distance to the end, without overestimating it. I’m not sure if this applies to all situations (in fact I’m pretty sure it doesn’t if you have grid based movement), but in space it makes sense to just use the as-the-crow-flies distance from your current node to the end node.

Pathfinding_6

So referring back to my in-plain-english description in part 1 when I’m saying distance value, and closest, I mean F. The last thing you need for this implementation of path finding is way of deciding which nodes are next to which nodes. I was going to do a separate post for this, but it’s actually easy; just see if there is an obstacle between the two nodes you are considering, if there isn’t they are neighbours. In the next part you’ll finally get to see some code!

Let me know if any of that isn’t explained well enough in the comments.

< Part 2Part 4 >

Lost in space: Part 2 – Nodes

This is a 4 part series on how the path finding works in Battlefleet. Please ask in the comments if something doesn’t make sense. Here are parts 1234.

So, what is a node? In the general solution, a node is any point you wish to consider as part of the path from the start to the end. For a while I was thinking that I needed to have a grid superimposed over the whole map, and to block off the squares that had an obstacle in them. This probably works well in a game based in small areas with lots of obstacles, but as you can imagine, doing this in space would need quite a lot of squares… Of course this isn’t what you need to do, you just need to be clever when deciding where to put the nodes. In space there are basically 3 cases you need to consider:

  1. There’s nothing in the way
    Pathfinding_1
  2. There is one thing in the way
    Pathfinding_2
  3. There is loads of stuff in the way
    Pathfinding_3

In case 1 we clearly only need to consider the start, S, and the end, E, as nodes. In case 2 we also need to use S and E, but we will need more. The shortest path from S to E around X would be through a point just to the side of X. If you draw a line through X, perpendicular to the line SE, and then find points on it closest to X so that S could still fit past, you have your nodes.Pathfinding_4

In case 3, we just need to split up the journey into lots of case 2s. So, find lots of sensible nodes and then find a suitable route through them. Unfortunately every time you move to a new node, you need to recalculate a new perpendicular line through each obstacle. Luckily for me there is a further simplification that I can make as all of my spaceships/obstacles are going to be rectangular[0]. The most sticky out part of an obstacle is going to be it’s four corners, so I can use those as nodes. Actually, so that the ships don’t touch, I’m going to use a distance away from those corners, like this:

Pathfinding_5

Let me know if any of that isn’t explained well enough in the comments.

< Part 1Part 3 >

[0] At the moment at least I am using a rectangular sprite for each ship.

Lost in Space: Part 1 – The algorithm

This is a 4 part series on how the path finding works in Battlefleet. Please ask in the comments if something doesn’t make sense. Here are parts 1234.

Just as it is in real life, it is important that the spaceships in my game don’t crash into each other. Some 2D games allow units to stack on top of each other, sometimes this is desireable, but in an RTS I think it will only be irritating. You won’t be able to easily see who has the most units in any particular fight.

So, I need two things, firstly to never let units start on top of each other, and second for them not to move into each other. I will eventually write some tests to check that the first hasn’t happened, once I start designing levels. In this post I’m going to cover the second point. When ever a unit is told to move, it needs to be able to calculate a quick route to the destination that doesn’t involve flying through someone else. This is called path finding.

There are lots of different path finding algorithms. After some Googling, I decided to use the A star search algorithm. Here is what Wikipedia says:

function A*(start,goal)
    closedset := the empty set    // The set of nodes already evaluated.
    openset := {start}    // The set of tentative nodes to be evaluated, initially containing the start node
    came_from := the empty map    // The map of navigated nodes.

    g_score[start] := 0    // Cost from start along best known path.
    // Estimated total cost from start to goal through y.
    f_score[start] := g_score[start] + heuristic_cost_estimate(start, goal)

    while openset is not empty
        current := the node in openset having the lowest f_score[] value
        if current = goal
            return reconstruct_path(came_from, goal)

        remove current from openset
        add current to closedset
        for each neighbor in neighbor_nodes(current)
            if neighbor in closedset
                continue
            tentative_g_score := g_score[current] + dist_between(current,neighbor)

            if neighbor not in openset or tentative_g_score < g_score[neighbor] 
                came_from[neighbor] := current
                g_score[neighbor] := tentative_g_score
                f_score[neighbor] := g_score[neighbor] + heuristic_cost_estimate(neighbor, goal)
                if neighbor not in openset
                    add neighbor to openset

    return failure

function reconstruct_path(came_from, current_node)
    if current_node in came_from
        p := reconstruct_path(came_from, came_from[current_node])
        return (p + current_node)
    else
        return current_node

It’s not as complicated as it first looks. Basically it says:

  1. Start at the start.
  2. Are you at the end? If not go to 3, else go to 11
  3. Mark this node as checked
  4. Make a list of all the nodes that you haven’t already marked as checked, that are also next to the one you were just looking at.
  5. For each of those neighbour nodes, calculate the “distance” to it from you current node, plus the distance value of your current node.
  6. If that value is lower than the distance value already stored in that node, or the neighbour doesn’t have a parent, store the new value and mark it’s parent as the current node.
  7. Add the list you just made to any previous list you had
  8. Sort your whole list in order of which one is “closest” to the end
  9. Look at the node at the top of the list
  10. Go to 2
  11. Work backwards from the end, asking each node who it’s parent is

I have written “distance” and “closest” like that because you need to decide how to determine which paths are shorter, and which points are closer, I’ll cover that in a later part. However, before we go any further, we need to decide exactly what a node is.

Let me know if any of that isn’t explained well enough in the comments.

Moving, Shooting and Formations

Since last time I have added path finding, shooting and formation movement. All three new aspects are in still in alpha trials, so they will probably change a lot before the game is finished. Here’s a video:

As you can see I’ve added in some enemy ships in red. If you are ever flying a red spaceship and you are shooting at a green spaceship, you should think long and hard about who the bad guys are…

Irritatingly the video jumps at 0:06, again this isn’t what I see on the game, and is something to do with adb’s recording. Perhaps I’m using it wrong? This one has been recorded at 4Mbps.

The path finding algorithm I have used is called A*, I’m going to write an article or two on how it works and how I implemented it, so stay tuned. At the moment when more than one unit is moved at the same time, they will stay in the formation that they were in when they were given the move order.

I spent a while with the shooting, and I think that I have already changed my mind on how I’m going to do it. I think I will reserve visible projectiles, like the ones seen in the video above, for torpedoes and other large ordinance. For the kind of guns/lasers that fighters and the like have, I think I’ll just animate the guns and the effects. We’ll see. One of the reasons the shooting took me so long, is that my first implementation was hideously slow, it was drastically reducing the frame rate. At first, as I had made no attempt to do any optimisation, I thought this might be due to the large number of loops inside loops. However, looking at LogCat, I could see that the Garbage Collector (GC) was running several times a second.

12-18 08:59:35.022: D/dalvikvm(1005): GC_CONCURRENT freed 487K, 11% free 8828K/9860K, paused 4ms+6ms, total 38ms
12-18 09:32:43.682: D/dalvikvm(10131): GC_FOR_ALLOC freed 512K, 7% free 8082K/8636K, paused 26ms, total 26ms

I have read that the GC is often the cause of performance issues, so before any other optimisation, I thought I’d attack from that angle.

It turns out the cause of the problem was that I was returning float arrays from several static methods, and each call was creating a new float array, that was then subsequently GCd. Here’s an example with a function that rotates an arbitrary number of points about a point:

public static float [] rotatePointsAboutAPoint(float [] points, float [] rotateCenter, float angle) {

   float s = (float) Math.sin(Math.toRadians(angle));
   float c = (float) Math.cos(Math.toRadians(angle));

   float [] out = new float [points.length];

   int round = 0;

   for(int i = 0; i < points.length / 2; ++i) {
       round = i * 2;

       out[round + X] = c * (points[round + X] - rotateCenter[X]) - s * (points[round + Y] - rotateCenter[Y]) + rotateCenter[X];
       out[round + Y] = s * (points[round + X] - rotateCenter[X]) + c * (points[round + Y] - rotateCenter[Y]) + rotateCenter[Y];
   }

   return out;
}

This function is called when checking if one ship is within range of another, which happens ALL THE TIME. So you can see how a stupid number of temp variables would be created. My solution at this point, is to make those variables static, like this:

/*
 * WARNING
 * 
 * The following static variables are global to this class, but should be treated as uninitialised
 * They will be in the state that they were in before, which could cause some difficult errors
 * 
 * They are like this to prevent the GC from running, which drastically decreased performance
 * 
 */
// rotatePointsAboutAPoint
private static float [] OUT_COORDS_1 = new float [2];
private static float [] OUT_COORDS_4 = new float [8];
private static float SIN_THETA, COS_THETA;
private static int ROUND;

public static float [] rotatePointsAboutAPoint(float [] points, float [] rotateCenter, float angle) {

    SIN_THETA = (float) Math.sin(Math.toRadians(angle));
    COS_THETA = (float) Math.cos(Math.toRadians(angle));

    ROUND = 0;
    for(int i = 0; i < points.length / 2; ++i) {
        ROUND = i * 2;
        OUT_COORDS_4[ROUND + X] = COS_THETA * (points[ROUND + X] - rotateCenter[X]) - SIN_THETA * (points[ROUND + Y] - rotateCenter[Y]) + rotateCenter[X];
        OUT_COORDS_4[ROUND + Y] = SIN_THETA * (points[ROUND + X] - rotateCenter[X]) + COS_THETA * (points[ROUND + Y] - rotateCenter[Y]) + rotateCenter[Y];
    }

    switch(points.length) {
    case 2:
        OUT_COORDS_1[0] = OUT_COORDS_4[0];
        OUT_COORDS_1[1] = OUT_COORDS_4[1];
        return OUT_COORDS_1;
    case 8:
        return OUT_COORDS_4;
    default:
        return null;
    }
}

This doesn’t look nice does it? Firstly if I ever forgot to reset any of those variables for any reason, they would be stuck at the value from the last calculation, which would cause some wacky, deceptive errors. Also the function is less versatile as it no longer works with an arbitrary number of points. That’s not so bad as so far I only use this for rotating points and rectangles, but still…

Have you encountered a better way of doing this?

After doing that, the game runs smoothly, as you can see (excluding the jumps…). Lots of people complain about Java’s GC, lots of other languages, including C++, don’t have one. At the moment I am glad it exists, as otherwise I would be spending my time battling memory leaks as well, but perhaps it will annoy me in the future.