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 >

Lolbug

Sometimes when programming you get bugs that are worth sharing with the world. Sometimes it’s because they are technically interesting, or thought provoking, other times it’s because they are stupid. This one was caused by me trying to add more realistic ship movements. As you have seen in previous videos, turning 180° on the spot doesn’t quite look right, but this doesn’t look much better…

On a side note, I wish YouTube would stop fucking around with names and profiles. I have no idea what’s going on. I have had to upload that video as a new account, but I don’t know why or where the old videos have gone. Perhaps I’ll choose a better video site in future.

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.

Recording what I’ve done

So here are the first videos of what I have achieved so far. I’ve got the “Temp Art” Spaceship flying around with a photo of a galaxy in the background. I can’t remember which galaxy it is… Thanks anyway NASA. When the spaceship is moving its thrusters fire, and when it stops, they stop. I know this isn’t how moving in space actually works, but it’s how it usually works in films and computer games.

There is a bit around 7 seconds in where it lags/jumps. This isn’t the fault of the game, but of the recording. I used Android 4.4’s new screenrecord feature. I played around with the setting a bit, but I didn’t manage to get the recording completely lag free. Here’s another video with half the bit rate (2Mbps) of the first.

This one doesn’t jump quite so obviously…
Anyway I hope you get the idea.

The command I used was:

adb shell screenrecord --time-limit 10 --bit-rate 2000000 /sdcard/demo.mp4

Temp Art

I am not the most terrible person at art, though I may well be the slowest, I once managed to blag 7 extra hours in an 8 hour art exam, and I still didn’t finish… (Got a C, lame). One of the problems with trying to make a brilliant game is you either need to be good at everything, or you need to own a large wallet. I suppose you can be somewhere in between , but when you are just starting out, you don’t want to be spending money on concept art. So that means, for now at least, I have created my own filler art. This is just a stand in until I get something better.

nshipbase
It’s not nearly as grim and gritty as I would like…

Here’s the first sprite sheet I’ve attempted:

fighter1
Could be a lot worse, right?

Battlefleet

Hello,

I am starting the process of making my first ever Android game. The plan is to make a 2D strategy game set in space, a bit like a cross between Homeworld and Battlefleet Gothic. It’s working title at the moment is ‘Battlefleet’.

I want the feel of the game to be serious, grim and cold, and I want to the action to be intense, but not fast paced. I would quite like this game to require constant thinking, rather than requiring a high APM. Something else I would like this game to have, is a huge sense of scale, so maybe the biggest ships are over 100 times bigger than the smallest ships. Future revisions of the game will hopefully have multiplayer, maybe even with an RPG element. But that’s probably going to be a permanent year away… Most of the other details aren’t there yet, but they will materialise as I get further into the project.

I have done a bit of Android programming before, but never a game. I have chosen to use AndEngine as the game engine as I saw it recommended on StackOverflow quite a few times, and there is only so much research into which engine to use that I can be bothered to do… Let’s hope it’s as well documented as I have read it is.

I plan to keep this blog updated with what I manage to do, there will be code, screenshots, music samples, maybe even storyline if it ever gets one… Please write feedback or questions or whatever you like in the comments, I’ll get back to you when I can think of something witty to say.

Right, let’s go…