The Light Racer 3D Development Journal

Android

As of the start of this journal in April, 2009, my first Android game, Light Racer, had over 250,000 downloads and was on version 1.0.1. That game was developed in about 3 weeks time in my spare time and was released right when the G1 was made available. I had over a quarter of a million downloads of that first game, so why did I feel like spending months rewriting it and developing a 3D version of it? Well, I wasn't totally happy with the old version. It had really bad AI. I wrote that quickly just so I could have a computer to play against when testing but never implemented a better version. It was a little slow and choppy on some phones. It lacked depth and the way it was designed makes it hard to continue to add new parts to the game. It didn't have multiplayer support. It didn't look very nice. Basically, it wasn't a game that I could personally be proud of developing and I felt that there was so much potential for a great multiplayer 3D game, but I wanted to bring the 2D game up-to-date in the process, so I ended up doing both. Because of the months of development effort documented in this journal, Light Racer v2 and Light Racer 3D are both complete and are worlds better and more advanced than the first title. In the following pages, I give away many tricks and code samples of how to do almost you need to develop a good Android game.

This journal was written from day one of development. The course of development took about 5 months in total. All of the entries from this point on are written from the perspective of the day that the development was happening. If you want to learn how to develop games, I recommend reading every article in the journal. Many have code examples and there are lots of lessons to learn from them. I started the project not knowing as much about game development as I know now. I hope that this journal helps you on your way and that you are able to learn something useful from it. If you are a Light Racer fan and not a developer, I hope that you have fun seeing what happened to make the game completely come together.

Today Light Racer 3D is on the Android market and is one of the most advanced games available on this platform. This journal documents how many of the game's features were developed.

And here it begins... Day 0 (pre-planning)

The next article will contain more on this, but for now I'll say that the reason I care about Light Racer with regards to LR3D is because I plan to use much of the code from that game in the next. Through clever design, Light Racer 3D will mostly be the same code as Light Racer but with an OpenGL front-end and if I do things right, I'll have a game engine that will make it really fast for me to develop brand new games in the future.

Android is none too easy to develop OpenGL apps for. The API is just OpenGL ES, but the performance isn't very good and there aren't any tools for Maya or 3DS import/export.

What I intend to have is an identical feature set between LightRacer and LightRacer3D. Some people will prefer the old-school 2D LR, but I believe most will prefer the much flashier and first-person-perspective LR3D.

Here are my current problems:

1) The game is poorly designed. One activity and one 1,400 line class handles nearly everything.
2) There is no networking code, so no multiplayer.
3) The AI is too easy.
4) The game lacks depth.
5) The game looks cheap.

Here's what I plan to do about that:

1) Redesign everything.

First I will start by refactoring all of the existing code. I believe that by simply moving much of the specific game component code from the main class, I will be able to achieve an architecture that will let me plug in new components, such as input from the network or a new type of AI character. The individual blocks of code are good, but it's really an organizational problem right now. Some things will need to be totally redone, and MUCH responsibility will need to be delegated to GameObject classes. More on that later.

2) Write networked multiplayer code.

This can impact the app in several ways. For the most part, I can assume that if you're playing a TCP/IP game, that it's local and WiFi. This means that there won't be any problems with network congestion, latency or packet loss. This does not take into account how people potentially could be connecting their devices in the future. I shouldn't assume that it will always be WiFi. If I wanted to be really safe, I would need to account for packet loss, which means that the physics would need to use interpolation, which they currently don't. Beyond that, the design needs to allow for a network player to be assigned as the entity controlling a player, which it currently does not support.

3) Develop easy, medium and hard Artificial Intelligence.

The current AI can stay as "Easy." For medium and hard, I intent to test with an A* algorithm and then an evaluation function that will determine the AIs path by predicting what you, the player will do and plan on that. Curbing CPU usage will be difficult with that because that approach is a bit like chess AI. If it works out well, it will be certainly be hard, if not unbeatable. Medium AI will just be the A* algorithm with a simple heuristic and no prediction. There is yet another design problem with the AI, though. Currently the main class of the game simply has "updateAI" which runs for all players that aren't the one human. It should be fairly simple to do, but a new design will be needed which will allow for assigning a specific implementation of AI to a player.

4) Add more interesting game elements

One of the most common suggestions I get for LR is that it could use a little more depth. What exactly does that mean? How do I make it more fun? I don't want to pollute the game with objects that will take away from it's core gameplay, but I agree that it could use something that makes it a bit more riveting. Perhaps a non-player character (NPC) that flies around on occasion, smashing the walls down? Maybe a beam down the center which only allows for occasional pass? Perhaps a boost feature which allows a player to speed up? All of these things could be fun. I am considering all of them and once I get the new design down, I'll be able to play with a few of these things to see which are the most fun and which ones make you want to play the game more.

5) Tweak the graphics, add more animation capability and hire a professional designer

When I wrote the game, I never intended for it to be anything more than a test application. All I wanted to do was learn the Android API. Now I want to have a really great game, which will be Light Racer 3D. I can do certain tweaks to the 2D version that will make it look a little better but ultimately I'm going to need the help of a professional graphic designer. Thank you but no portfolios, please. I already have someone that I trust and have a good relationship with. The other thing that is limited in the current design is the ability to add more animations without using up too many more CPU cycles. This will be covered in the Game Architecture article.

Light Racer's New Game Architecture

Android

Version 1.0 of Light Racer didn't have much to say for architecture. There was a single Activity which dealt with the buttons, showed instructions and displayed the main game View. I'll cover the way the menus are going to work in a different article but suffice to say for now that having a single Activity deal with multiple different things is not a good idea at all in Android. The main View was a SurfaceView and simply started up the main game thread. The Thread class was the main game and besides the Sound Manager and a very light Player class which held the state, path and animation information for any given player, there wasn't a whole lot to it. This made it easy to develop but the new requirements are to have NPCs (Non Player Characters), network multi-player, different 2D and 3D displays and more. How does one tackle such a problem? With a better design, of course.

The new design is much more organized and also more abstract. The following slides will show how I am changing the design to delegate far more responsibility into the Game Objects and Receivers and away from the main class. The main class's job will be simply to initialize the environment, load resources, initialize and configure the world, run the game and watch for changes to the Android environment.

The environment consists of the world (everything a game cares about and will display to the user), various managers like sound and notification managers, any object pools (because Android gets slow if you are creating and GCing lots of objects) and hooks into the Android environment.

Resources are things like graphics, sounds, fonts, and colors. Many parts of the game need to access the resource pools so it's always a good idea to have them managed by one object and pass that one object to anything requiring resources. For example, all GameObjects are responsible for drawing themselves so they will need to get their bitmaps from the resource manager. The main game class will have the resource manager release its resources when it's time to clean the game environment up.

Main Class, World and ResourcesMain Class, World and Resources

The next diagram is a bit more concrete and recognizable. Player is the only Game Object on here but you can imagine that there might be others, such as a barrier or a power-up item. All of these things are Game Objects and are responsible for drawing and animating themselves. The Player will be doing collision detection on itself to check for a crash. If I end up adding power-ups, it will also check to see if it has touched or passed over an item. A Player maintains its own position in the world. It also maintains it's state so it knows if it's still alive or crashed. The path is the trail of light. Animation means which frame of what kind of animation is currently being drawn.

Game Objects, Players, Receivers and AIGame Objects, Players, Receivers and AI

The big key element in this new design is the Receiver. I couldn't think of a better name at the time and this may be renamed when I do, but a Receiver is responsible for determining the next direction of the player. In LR 1.0, the Android button hooks were tied directly to a field that held the next direction of movement until the physics update could pick it up. It also had the AI code in the same big game class and assumed that any player that was not Player 1 was AI. That won't work for the new requirements so I'm designing this to make 2 policies: 1) The Receiver is entirely responsible for setting the next direction of a Player. 2) If there is no Receiver, the Player shall always continue (interpolate) on its trajectory.

Does policy 2 make sense? When would we not want a Receiver? Wouldn't that player always end up in the wall if it started North and continued without change?

Let me start by answering that there are several types of Receivers. The Player Receiver is directly tied to the buttons and touch. When a player hits up, that receiver sets the next direction to North. Simple enough. The AI Receiver is abstract and so there can be several different AI implementations. I'm doing 3, which are Easy, Medium and Hard. The different implementations may be better at playing than one another but they all end up outputting only one thing: The next direction for their Player. The network receivers are the most confusing. I'll explain the way multi-player games will work and then get back to them.

In Light Racer, you will be able to host a game or join a game. There can be up to 3 players for the regular game and 4 players for LR3D. The clients each "estimate" what is happening but the host is the real authority. One could say that the actual, authoritative game runs on the host. This means that when the host decides there was a crash, there was a crash. This is an easy strategy to follow because the rule is that the host's world always overrides anything on the client.

Let's discuss the following example:

3 Player Network Example3 Player Network Example

In this example, there are 3 players. One player is the host of the game, the second is a client and the third is an AI player. The configurations are different in that the host uses a Human Receiver for the person playing, a NetHost Receiver to pick up the input from the remote player and an AI Receiver to control the AI. Every time it ticks and updates the world, it packages the world up and sends it to the client. The client unpackages the world and overwrites its current world. This is because the host is always the authority. The client then processes input from its one Receiver, which is a NetClient Receiver. The NetClient Receiver is dual-purpose. It has a Human Receiver so it can take button presses and touch events from the real player and that will set the next direction, but then it also immediately sends that over to the host via the network connection.

If you can imagine that happening around 30 times per second, you can see why the Client World does not need Receivers. Its World is always being updated by the host and the Players in that world are located and on the trajectory that the World dictates. This means that the client simply tells the host what it would like to do and draws what it thinks is happening. The host processes this and tells the client what actually happened. So long as the updates are fast and consistent, this will work. Even if there are small stutters, the interpolation code will take care of it and things will still look mostly smooth.

In a few weeks I will address the issue of 2D and 3D code both running from the same codebase.

Light Racer 2.0 - Days 1 and 2 - Refactoring

Android

The first two days of actual coding have involved mostly refactoring. If you're curious as to why I'm doing all of this refactoring, I will recap on the new requirements which involve multiple kinds of AI, a new game object that I call the "decimator" and real-time multiplayer functionality. Many would think that the only way to do that would be to rewrite the game to support this, so why am I opting for refactoring to start? Some would argue that it's semantics but for the most part, refactoring is taking existing code without changing functionality and rewriting is completely scrapping a functional piece of code and developing it from scratch. Lots of the code in Light Racer was really good but just not organized to allow for new features to be added in without too much headache.

What changed?

I first started by creating two new activites: One for the game and one for the instructions. Before it was just one activity that had a hacked up layout. That hacked layout had all 3 different layouts all layered on top of each other and the activity would switch which ones were visible depending on the state of the whole game. This was very hard to manage and work on and certainly wouldn't have worked out well to add in the new multiplayer menus. Now there are three distinct Activities. One for the main menu, one for the instructions and one for the game itself. In the future there will be more to handle more of the new functionality.

My next goal was to break out the existing AI code so that I could make it more modular. What I wanted to do was be able to just pop a new instance of a MediumAI and be able to set that on any player. Here's the idea:

public void setupGame() {
Player p1 = new Player();
p1.isHuman = true;
Player p2 = new Player();
p2.setAI(new EasyAI());
Player p3 = new Player();
p3.setAI(new MediumAI());
startGame();
}

Sounds easy enough, right? Well that interface is in place now so I can have code that works almost exactly like that, but the hard part was working it out so that the AI code could work correctly on its own, which means working without access to all of the fields of the game. This was achieved by doing two things. First, I created a "World" class that contains everything that matters to the game. I moved all of the Players and state info into the World. Then I took the old updateAI() method which used to exist in the big monolithic game class and moved it into the AI interface, which all AI implementations implement, and added it as a parameter, so that every time the AI is updated, it has access to the entire world. To translate - the AI code is handed the positions of every player and game object as well as the paths of light, just like you see on the screen. This is necessary because those things are all used in the calculations for the AI to determine where to move its player to next. The most used method in that calculation is collision detection, which is some code that simply checks to see if the racer is going to hit a wall. That collision detection code is needed by AI and by the update() method in the Player class so I took a standard OO approach and created a base class called PlayerPhysics and made EasyAI and Player both extend it, thus allowing for them to share that common collision detection code.

Does this sound like a big jumble? It's actually really clean now. Here's how it looks:

public interface AI {
public void update(LightRacerWorld world);
}

public abstract class PlayerPhysics {
public boolean checkCollision(LightRacerWorld world, int fromX, int fromY, int toX, int toY) {
... collision detection code
}
}

public class EasyAI implements AI extends PlayerPhysics {
public void update(LightRacerWorld world) {
... all the old AI code is in here now
}
}

public class Player extends PlayerPhysics {
... tons of fields for position, next move, animation, color, etc
public void setAI(AI ai) {
this.ai = ai;
}

public void updateAI(LightRacerWorld world) {
ai.update(world);
}
}

Since a good, safe refactoring job moves things in small steps and doesn't break anything, this should be considered a few steps taken but not a finished job. The design in the architecture document uses Receivers, which we don't have implemented yet. For now, this is an improvement and takes steps to get there.

The other thing that was done is that resources were moved into their own class, also following the architecture document. This was necessary to allow for GameObjects to be able to draw themselves, which was an ability they did not previously possess. Did I mention I added GameObjects?

So far so good on the development. Here are screenshots of where the game is at now. Much of this work won't have any visible artifact but soon we'll get there. In fact, all that has changed aesthetically is that the game is now full screen and there are placeholders up on top for the game difficulty and level.

Main Menu Dev ShotMain Menu Dev Shot Light Racer 2.0 Dev Shot Game 4-7-2009Light Racer 2.0 Dev Shot Game 4-7-2009

Light Racer 2.0 - Days 3 and 4 - New Design Completion

Android

Much like the first two days of work, days 3 and 4 involved mostly refactoring the existing code to bring it all in to the new architecture. The end of the fourth day was much more exciting than the rest because nearly 100% of the design was completed and I was able to work on new code again. I implemented 2D coordinate interpolation for the racer physics and AI, which I had a few problems with but overcame. I also implemented visual AI debugging which is key in developing better AI.

Before I started modifying the game or adding anything, I was almost purely refactoring. This approach has paid off because now I have a great new design that will allow me to add all of the new features and the game is still in a 100% usable state. In fact, I could still package it up and put it on the market with little effort. I keep pushing this idea because I believe it's very fundamental to a successful developer. Notice how through the days of refactoring, I was able to keep the game working exactly as it had before. That's the big difference between refactoring and rewriting. Call it semantics if you wish, but I have not lost a single piece of functionality and I've been able to test it several times every day and verify that everything is still working exactly the way it was when I started the project.

The great thing about what I'm doing is that I'm so confident the entire way. I'm not scared that I'm going to make a huge mess that will be a major headache to clean up. It's one thing at a time and by dealing with only one small issue at a time as I go, I'm able to tackle every issue that comes up. In software, it's simply overwhelming to deal with tons of issues all in one go. Slow and steady wins the race here, no pun intended. At the end of the refactoring, the new design was fully implemented and the game was still working exactly as I left it. Now it all works and I'll be able to add in all of the new features I want. How cool is that?

Day 3: What was completed?

The LightRacerWorld is now used consistently and several things were added to the world. It has game state information, the pixel size of the game area of the screen, all of the players, the last, current and delta tick time, the level and the current movement rate (game speed). This was important because the world is the part that is passed into GameObject update() methods, which in turn will run AI algorithms. All of these things are needed for AI to process and for physics to interpolate.

I moved SoundManager into resources. This made it so that GameObjects can trigger and control their own sounds.

I combined AI and player physics updates into a single update. This improved the interface from the main game class to the players. Now the main loop just iterates through players and calls update(world) on each. Before it had some logic to determine if the player was AI and what to do.

I moved path updates into Player. This is just to further simplify the main game loop. The more simple, the better. Here's some game testing from the end of Day 3, just to confirm that the core game was still working correctly.

Day 4: What was completed?

Player now controls all of its own sounds. This wasn't complete before but now is 100%.

Player has one update() method. It was many updates before but all of that logic exists only in the Player class and the main loop has a very easy interface to update the Players.

Changed Receiver to PlayerInputSource. I renamed that class because I think that it is more appropriate this way. The PlayerInputSource is fundamental because it is either a network client, network host, human player or AI player. It is the keystone that will allow for different kinds of AI and network configurations.

LightRacerAI now implements PlayerInputSource. It's official. The AI is now a PlayerInputSource.

Created HumanInputSource. This was to finish implementing that part of the design.

Renamed PlayerPhysics to Collisions. All that was in the class was a method to check for collisions. I made that public and static, so it's really just a utility class now. I don't normally do that but it was needed by a few different classes and didn't fit well into the hierarchy.

GameObject created but can't standardize draw methods because of layering. I have a GameObject now but because this game is 2D, I can't have one draw method. Drawing order must occur like this: Background, light paths, racers, explosions, text. If I were to do one player at a time instead of one layer at a time for all players, you would end up with paths and racers on top of explosions, which would not look good at all. I don't have a good solution for this yet but it's not the worst thing ever to have one custom interface and everything else is looking very good.

Refactoring is complete! Now for the fun stuff...

Implementing Interpolation in a 2D World
I changed the physics of the game to use time distance interpolation instead of moving a set number of pixels per tic. The idea is that the game will run the exact same speed but at different FPS depending on how much load is already on the device. I found that for different phones with different numbers of services running, the game would run faster or slower and I wanted it to be consistent.

The interpolation works really well. It's really straight forward to write interpolation for a 3D coordinate system because you can actually move a floating point number away but in 2D land there are a set number of pixels and so you must move a nice round number. This was problematic at first because when I set the movement rate to 100 pixels per second, I ended up getting about 80. Why do you think that is?

I almost knew immediately what the problem was. Interpolation is calculated by using the the last known location of the object, the direction the object is moving in, the rate of movement and the last tic time and the current tick time. If you have that information, you simply take the difference in time, multiply that against the rate of movement and then move the object that far from the last position in the direction it is moving. The formula could be expressed as newPosition = oldPosition + (tickDelta * movementRate * direction).

Here is an example that also shows the problem:

Movement Rate = 100 pixels per second
Racer is located at (x,y) (100,100) and is facing North.
The last tic time was at 1000
The current tic time is 1078
Where should he be now?

The math looks like this: 78 (tick delta in ms) * movementRate (in seconds) / 1000 (to convert seconds to milliseconds) = 7 pixels.

So we should move 7 pixels in the north direction (subtract from Y axis) so that would put our racer at 100, 93. That works, but with a tic delta of 78, how did we end up with 7 pixels? What happened to the other .8 pixels? In a floating point world, we would have moved the racer 7.8 pixels. Since we can't do that we can either discard the remainder, which is what I was doing and was causing the game to run slower than it should have, or we can use a trick that I came up with.

Since there is usually a remainder, I changed the code so that it works like this:
movement = (tickDelta * movementRate + carryOver) / 1000
carryOver = (tickDelta * movementRate + carryOver) % 1000

carryOver must be an instance field on the GameObject you are interpolating, because it will need to be the remainder from the last physics update. This worked, because now that .8 pixels from the example is carried to the next update, and if that next update has a remainder that is at least .2 pixels, then an entire pixel will be added to the movement, thus making the movements add up to 100 pixels per second.

This worked ok but I was able to see the little jumps sometimes if there were small remainders for a few updates and then they finally added up to 1000 to add a pixel. I realized that only rounding down wasn't the most even way of approaching the movement and remainder problem so I also implemented a negative carry over for remainders over 500. Now the carry over value will, instead of being 0 to 900, be -400 to 500. This worked to smooth out the 2D interpolation. Here's some physics code from the game:

long targetMovementAmount = (world.tickDelta * world.movementRate) + movementCarryOver;
int pixelsToMove = (int) (targetMovementAmount / 1000);
movementCarryOver = (int) (targetMovementAmount % 1000);
if (movementCarryOver > 500) {
// round up and carry over a debt.
movementCarryOver = movementCarryOver - 1000;
pixelsToMove++;
}

All said and done, the game runs really well now. It's much smoother than before and it doesn't feel like there's any lag even when the phone slows down for a brief second.

Making AI work consistently in a variable update-time environment
After I added the interpolation to the Player physics, I got rid of the old cap I had placed on updates. Now the game will update as fast as the SurfaceView will allow for draws, so 60FPS. My emulator runs at around 45FPS so that's not too bad. I haven't tried it on the phone yet with my FPS text turned on but I'll have more on performance later in the book. For now, a new problem has come up. I used to run the game at a set 30 updates per second (or slower) and my AI was designed to run at that speed. The new AI has to be able to also interpolate or it runs totally different on one system to the next and also it takes up far more CPU cycles than are necessary. AI doesn't actually need to recalculate 45 times per second. 10 will do just fine. Is it even fair to play a computer player that can process everything faster than your eye registers motion?

I updated the AI to use interpolation similar to that of the Player. All the AI really needs to know is "How far will the racer move until I get to make another decision?" For that I decided to combine that with an AI update limit of 10 updates per second. This made the math simple: If the AI updates every 100 milliseconds, then the furthest the racers could go before getting to make another decision is 1/10th of the current movement rate. This doesn't take into account if the game runs slower than 10FPS, in which case there will be a problem here but I'm assuming that it is unplayable at that speed anyway. It works this way because the AI needs to be able to make sure that it's not going to run into a wall before getting to decide to turn again. That number is how many pixels it will go so it can pipe that through the collision detection code and see if there is something in the way.

Limiting the AI to 10FPS is a great thing because it works well and now I have some extra CPU cycles to use for even better AI. My Medium and Hard AI are going to be far more CPU intensive than the current Easy implementation. I knew I was going to need a better way to debug the algorithms so I added in a visual AI debugging feature. The PlayerAI interface has a drawDebug(canvas) method which is called by the main loop when the AI debugging is turned on. I implemented a little bit of it for the EasyAI to make sure it was all working. The EasyAI sometimes decides to "come after" a human opponent. Each time it makes this decision, I had it draw a "targeting" line that you can see in red in this screenshot.

Light Racer Easy AI debuggingLight Racer Easy AI debugging

It looks really cool to watch it make the decisions real-time, which is in the video below.

That's it for today. There have been so many changes and I'm getting really excited about adding all of the new features.

Light Racer 2.0 - Days 5 and 6 - Tiling the World for AI

Android

Most of what was done on days 5 and 6 was code to lay the foundation for the new medium and hard AI. Both AIs will make use of path-finding algorithms and because of that, using tiles to approximate coordinates will be far more CPU efficient, so long as creating the tiles themselves doesn't eat too many cycles. My strategy was to have each AI player manage its own tiles relative to itself. This makes it so that the point the player is at is at the center of a tile and that the tile size can be relative to the number of pixels a racer moves between AI updates.

On day 5, I developed a method to "tile" out the board and mark up each tile with a state: Open, Closed, Invalid, Self or Target. This is essentially all the A* algorithm needs to work. I also got a nice visual debugging of the tiles working and am able to show that they are being calculated correctly but I'm having performance problems. I think AI can consume 50% of the CPU time so that would be budgeted out as follows: Game Max AI players: 3. AI updates 10 times per second, so AI total must be under 500ms for all 30 updates, so 16ms max per update. 5ms for the basic tiling may be ok but I'd like it much better if I could keep it under 2. I'm worried that A* is going to really be a CPU killer. The problem I'm having right now is that it's taking 20ms additional to do collision detection on the tiles. I believe the method I'm using may be horribly inefficient so I'm going to take a look at how to write it totally differently.

My new approach will be to search from the world instead of from the tiles. This means that I'd start by assuming all tiles are open. Iterate each player, trace the path and mark each tile the path touches. Right now each tile is checking to see if a path touches it, which means that the full-game collision detection algorithm is run for every tile, or several hundred times.

On Day 6 I tried the new approach and it worked much better. Each full-world tile update takes around 5ms now. When AI Debugging is on, the blue areas are "Open," the red are "Closed" and the regular graphics are underneath. Currently, the MediumAI (the one I'm working on) isn't actually calculating a direction to turn to so it just go straight north. That's ok because the goal of the day is to have a really fast tile generation algorithm working.

I would have posted some code but all in all it's about 150 lines of code that wouldn't really matter much without several other classes. Code like this is usually custom to the game engine so if anyone is needing help, please ask and I can help with math and a general approach.

Some of the things I dealt with were:

final int minMovement = (world.movementRate / LightRacerConstants.AI_FPS);
final int centerOffset = minMovement / 2;
final int firstX = (player.curX + centerOffset) % minMovement;
final int firstY = (player.curY + centerOffset) % minMovement;
AITile[][] tiles = new AITile[tileCountX][tileCountY];
for (int x = 0; x < tileCountX; x++) {
for (int y = 0; y < tileCountY; y++) {
// calculate bounds for tile
// determine if tile is closed due to outside wall
}
}
// iterate players trails and set colliding tiles as closed

AI_FPS is a constant, set at 10, so that the AI is updated at most every 100ms. The movementRate of the world is how fast the game is playing with a number like 100 pixels per second. This would mean that dividing 100/10 would give us 10 pixels per update, which is the minimum number of pixels the physics will move a racer before the next AI update. I'm using that for the size of the tiles.

centerOffset is simply how many pixels it is to the center of a tile.

firstX is the number of pixels offset of the left wall.
firstY is the number of pixels offset of the top wall.

Since the tiles are always moved to be centered on the AI player in question, they don't usually line up perfectly with 0, 0. Often times the rows and columns around the border are smaller than the center tiles. This makes calculations more reliable because we know that in 5 updates, the racer will probably end up close to the center of a tile 5 tiles away. If we had the tiles set statically, the racer might start 1 pixel off of one tile and end up 7 pixels in to another. That's not good for determining a sound path. The flip side is that other racers and trails end up marginally in some tiles. More on that later.

After that code, I have it iterate the full x and y axis, calculating the bounds for each Tile. Right now, the AITile class looks like this:

public class AITile {
public static final int OPEN = 0;
public static final int CLOSED = 1;
public static final int INVALID = 2;

public int state = OPEN;
public int topLeftX;
public int topLeftY;
public int width;
public int height;
public int tileX;
public int tileY;
}

Here are some screens and video:

Light Racer 2.0 - Days 7 and 8 - Medium AI

Android

Developing AI for an action/puzzle game can certainly be a challenge. What is the best approach? How do I want the computer to "feel?" How hard is too hard? What algorithms will be efficient enough to work well on a mobile device? These are all questions I had to ask myself when I was planning Light Racer 2.0. Light Racer 1.0 had just one AI implementation. 2.0 has 3: Easy, Medium and Hard. Easy will be the old Light Racer 1.0 AI. I made the decision to make Medium AI use a pathfinder algorithm and always target 1 tile in front of the opponent. A* seemed like an obvious choice for this task, but once I implemented it, I started to find that the AI is still lacking.

On Day 7, I did the first A* implementation. I found one that someone else had done in Java and modified it to make it work well with Android's limitations, which basically means that the AStar class never creates new objects and is very careful about referencing members and virtuals. This worked, but I had a few problems.

Here are some screen shots with AI debugging enabled. The white line from the yellow to the blue player is the path that A* has determined is the shortest path to the target tile. This works, except when the racer turns toward the existing trail and ends up colliding with it, as you will see in the video.

The path finding algorithm doesn't take into account how long the racer is. It is basically assuming that if you're in a tile, you can move into another adjacent tile without penalty. This is causing the Ai to run the racer into walls when turning into them while too close. Also the performance is good until A* has to do a really deep search. There are a few very inefficient pieces of code in my A* implementation that I believe I can speed up.

On Day 8, I did the following:

Tweaked the A* heuristic
Did more performance optimizations (long searches still cause a bit of slow-down)
Added better opponent selection to medium ai
tweaked collision prediction for medium ai

These changes have made the Medium AI work significantly better. I was originally planning on developing the Hard AI next, but the lack of game depth has been really bothering me throughout the last few weeks of development. I decided to switch directions and build the levels and items before going any further with the AI. Since I don't have any levels or items right now, I need to start designing the code to be able to handle them. I designed a speed-boost icon in photoshop and decided that the game should have 10 levels that can be played at 3 different difficulties. More on that tomorrow.

Light Racer 2.0 - Day 9 - Adding Items

Android

After working with the AI a bit, I realized that the game feels totally dull to me. I spent a little time thinking about game design and decided to go with levels and items. Items weren't designed in to the game at all so I have to add the whole concept in from scratch. The first item going in is the Speed Boost item. When a player picks it up, their racer will move a notch faster until the end of the round. I have a problem though, the game is coded to run at the speed of the current level, not of the current player.

Here's what I did today:

Built Project Plan
Designed Icon
Implemented basic non-working SpeedBoostItem that could just draw itself
Added SpeedBoostItem to the world
Changed the way motor sounds are handled so they will work at different speeds
Modified game to allow for different speed players
Added a method incrementSpeed() to Player
Added item collision detection
Added random item placement - needs refining
Animated SpeedBoostItem

The hardest thing was adding the concept of speed on to the Player class, which is necessary to have players run at independent speeds. The problem was that all of the AI code and collision detection was using the overall game's speed to calculate distances. I had to change several methods to use the specific player's speed in the calculations, which took about an hour. After that, it was fairly straight-forward.

I drew the images of the item in photoshop:

Speed Boost Item in PhotoshopSpeed Boost Item in Photoshop 3 Image Files3 Image Files

I created the item class and added it to the world. The main game is responsible for placing the item. After that, the item is responsible for detecting a collision and responding. When it's done, it will set a flag that it is finished and the game will remove it from the world. The collision detection is very simple. Here's how it works:

private void checkCollisions(World world) {
// check to see if a player is colliding with me, do something to that player
int top = y - (int) (height * .5) - PLAYER_ITEM_TOUCH_PADDING;
int bottom = y + (int) (height * .5) + PLAYER_ITEM_TOUCH_PADDING;
int left = x - (int) (width * .5) - PLAYER_ITEM_TOUCH_PADDING;
int right = x + (int) (width * .5) + PLAYER_ITEM_TOUCH_PADDING;
Player[] players = world.players;
Player player;
int lx, ly, cx, cy;
for (int playerIndex = 0; playerIndex < players.length; playerIndex++) {
player = players[playerIndex];
lx = player.lastX;
ly = player.lastY;
cx = player.curX;
cy = player.curY;
if (cx == lx && (cx >= left && cx <= right)) {
// moving vertical within horizontal bounds
if ((ly >= bottom && cy < bottom) || (cy > top && ly <= top)) {
// collision
doCollision(player);
}
} else if (cy == ly && (cy >= top && cy <= bottom)) {
// moving horizontal within vertical bounds
if ((lx >= right && cx < right) || (cx > left && lx <= left)) {
// collision
doCollision(player);
}
}
}
}

The animation code is the same code I always use for animations

For now the game is just randomly dropping the item all over but once I have all 3 items in, I'll work out the balance of the item drop. For now it really added a new dynamic element to the game. I can see the game getting much more fun in the next few weeks!

Here's a video of the new item working.

Light Racer 2.0 - Day 10 - Trail Drop Item

Android

Today I added a new item which, when picked up, causes the racer's trail at that point to fade to nothing. The idea is that the game will be more dynamic if new opportunities can open up while playing. My tests of the item show that it's going to be a good addition to the game as I've already enjoyed playing the game more with it. The code itself was a little annoying because the entire game was designed with the concept of 1 trail (List of Coordinates to represent the path) per player and now I had to change it to multiple trail segments for each player. To complicate matter more, each trail segment could be "dissolving" independently. I solved all of these problems though and by 2:15am, the item was done.

Here's what I did on Day 10:

Changing list of points for trail of player into a list of trail segments. Segments now handle their own removal state and redraw state
AI and drawing code has been updated to handle multiple segments
Added a method to the Player: dropTrail()
Added a working TrailDropItem

I added a very simple method to the Player class called "dropTrail()." This method is short and sweet. It works by "capping off" the current trail and then calling the remove method, which sets a flag for the update() method. Each trail segment has its own update method that controls if it's removing or removed. The Player still draws the trails, though I could probably change it so that the trail is responsible for drawing itself entirely.

In the Player class:

private void updateTrails(LightRacerWorld world) {
ArrayList trails = this.trails;
for (int i = 0; i < trails.size(); i++) {
TrailSegment trail = trails.get(i);
if (!trail.isPathActive) {
trails.remove(i);
}
trail.update(world);
}
}

public void dropTrail() {
currentTrail.path.add(new Coordinate(curX, curY));
currentTrail.remove(TRAIL_DROP_REMOVAL_DELAY);
currentTrail = new TrailSegment(gameResources, curX, curY);
trails.add(currentTrail);
}

Here's my new TrailSegment class

public class TrailSegment {
public ArrayList path = new ArrayList(100);
public boolean isPathActive = true;
public boolean redrawPaths = false;
public int pathRemovalFrameNumber = -1;

private long pathRemovalTime = 0;
private long nextPathRemovalFrameTime = 0;
private boolean remove = false;

private GameResources gameResources;

public TrailSegment(GameResources gameResources, int initialX, int initialY) {
this.gameResources = gameResources;
path.add(new Coordinate(initialX, initialY));
}

public void update(LightRacerWorld world) {
// if we're in a path-removing state
if (remove) {
// if we haven't yet begun the removal, keep taking time off the clock
if (pathRemovalTime > 0) {
pathRemovalTime -= world.tickDelta;
}
// if we've counted down to removal, start
if (pathRemovalTime <= 0) {
// if this is the first removal update, play the sound
if (pathRemovalFrameNumber == -1) {
gameResources.soundManager.playSound(SOUND_PATH_REMOVE);
}
// if there is still time left until the next frame, take time off the clock
if (nextPathRemovalFrameTime > 0) {
nextPathRemovalFrameTime -= world.tickDelta;
}
// if the time to next frame has counted down, increment or finish up.
if (nextPathRemovalFrameTime <= 0) {
if (pathRemovalFrameNumber == 4) {
pathRemovalFrameNumber = -1;
nextPathRemovalFrameTime = 0;
isPathActive = false;
remove = false;
} else {
pathRemovalFrameNumber++;
nextPathRemovalFrameTime = PATH_REMOVAL_FRAME_DELAY;
}
redrawPaths = true;
}
}
}
}

/**
* Causes this trail segment to remove after the delay
* @param delay
*/
public void remove(int delay) {
remove = true;
pathRemovalTime = delay;
}
}

I drew up the image in photoshop. I had imagined a sort of "overload" or electrical cut-off of some sort that would cause the trail to break, so I came up with this round-shaped arcing thingy. I think I'm going to make all of the items rounded and slightly 3d-looking, but for now, this will have to do.

I designed this in photoshop and created 3 frames with varying yellow "electrical" lines.

Trail Drop Item in PhotoshopTrail Drop Item in Photoshop

Here's how it looks in-game:

And finally a video demonstration:

Light Racer 2.0 - Day 11 - Invincibility

Android

Today I set out to develop the invincibility item. The idea is that when picked up, the player will be invincible for 3 seconds and be able to break through trails, but the outer walls will still kill the player. My plan was to develop the invincibility code, build the item graphics and then put it all together in the game as an item that can be picked up. I first got side-tracked by how ugly the items were and decided to build uniform items for the game. I spent a few hours in photoshop making animated, 3d, orb-looking items. After that I got the trail-breaking code working, with a few bugs.

In photoshop, I basically made a grey sphere that would be the basis for all items. I then created a lighting effect that would shade it. I applied the lighting effect to it and some blending options and called it my orb. For each item, I then made the graphic that would be on the front of the orb and then duplicated the layer for each animation frame. I colored the layers according to how I wanted them to animate. I then applied the same lighting effect to each layer so they would look like they are part of the whole orb. I then created each frame of animation and set the layers for those.

Here are some photos of what things looked like in photoshop:

Invincibility Item First Try: This was too bright in-gameInvincibility Item First Try: This was too bright in-game Uncolored Invincibility Item with Layers in Photoshop: This is what it looks like before any effects are applied.Uncolored Invincibility Item with Layers in Photoshop: This is what it looks like before any effects are applied.
Light Racer 2.0 - Invincibility Item with layers in photoshop after effects: With lighting effects but before the blending effects on the orbLight Racer 2.0 - Invincibility Item with layers in photoshop after effects: With lighting effects but before the blending effects on the orb Light Racer 2.0 - New Speed Boost Item before effectsLight Racer 2.0 - New Speed Boost Item before effects
Light Racer 2.0 - Final Items in Photoshop with layersLight Racer 2.0 - Final Items in Photoshop with layers All items in photoshop with animation framesAll items in photoshop with animation frames

There is an easy way to save all animation frames as separate PNGs in photoshop but I need them to be as small as possible so I had to save them all frame-by-frame using the save-for-web-and-devices option. This made each one 1k instead of 3-4 that the default save does. The animation was just to preview before I finally saved the PNGs.

I added a new collision check method and supporting methods that will break trails apart when collided with. This way any game object will be able to use it. There were a few bugs with it initially but I was able to get it all working correctly in short-order. I added an invincible flag to the player and set it to true by default to test. I think some sort of sound and graphic will be needed when the player goes through a wall, but for now just having the physics and trail management work correctly is making me happy.

Light Racer 2.0 - Days 12-14 - The Decimator NPC

Android

NPC stands for Non-Player Character. It's anything in the game that acts upon the world but is never directly controller by a player. Light Racer 2.0's only NPC is the Decimator. The Decimator is a menacing machine that flies in when the round is taking too long and starts smashing things up. Since it's the first object that can travel any direction, the physics math was a little bit different than for all of the existing code. Also, it needed some unique collision detection because it hits an area instead of hitting a straight line like the racers do. While the Decimator is an ugly purple pentagon at the end of the day, I'm happy with how it feels within the game. I think that with a little graphics and sound TLC, it'll be an excellent addition.

Day 12 actually started with the addition of the Invincibility Item, which was designed the day before. I'm slightly behind schedule because I've been taking extra time to work the graphics out to where I'm happy with them before moving on. I don't want to end up with a week of extra graphics work at the end of the project. Anyway, the invincibility item was fairly easy to implement. I just added a flag to the Player called "isInvincible" and added a timer, which is updated on update(), and some drawing code to show the invincibility. This way the item just "tells" the player "be invincible for 2 seconds" and the Player abides. Nice!

Day 13 didn't have very many hours in it so I was only able to start implementing the Decimator. I got the basic skeleton for the NPC added and some drawing code to draw a stationary purple pentagon sprite.

Day 14 is where all of the fun happened. I have 2 videos that show the progress. At first I had some bugs in the state-logic for the Decimator. Here's how it works:

States: None, Target, Drop, Rise, Flee

The update() method runs the block of state-management code which switches from one state to the next, depending on the variables. Here's the update() method from the Decimator:

public void update(LightRacerWorld world) {
// if no action or action is target and target no longer valid, determine target.
// if not over target, go to target
// if over target and not dropping, start dropping.
// if dropping but not at 0, keep dropping
// if dropping and at or beyond 0, impact and start rising
// if rising but not at 100, keep rising
// if rising and at or over 100, stop rising and choose next action (flee)
// actions are TARGET, DROP, RISE, FLEE
if (world.gameState == STATE_RUNNING) {
if (action == ACTION_NONE || (action == ACTION_TARGET && !isTargetValid(world))) {
// cycle begin
determineTarget(world);
}
if (action == ACTION_TARGET) {
if (x != targetX || y != targetY) {
moveTowardsTarget(world);
} else {
action = ACTION_DROP;
}
if (LightRacerWorld.RNG.nextFloat() < .1) {
updateTarget(world);
}
}
if (action == ACTION_DROP) {
if (z > BOTTOM_Z) {
drop(world);
} else {
attack(world);
}
}
if (action == ACTION_RISE) {
if (z < TOP_Z) {
rise(world);
} else {
flee(world);
}
}
if (action == ACTION_FLEE) {
if (x != targetX || y != targetY) {
moveTowardsTarget(world);
} else {
// Completed cycle.
isActive = false;
}
}
}
}

There's a trick in that code. Did you see it? It's the part where the Decimator only updates its target (that is to say, it changes its target xy to match the current xy of the thing it's chasing) 10% of the time. This makes for some intelligent-looking pseudo-random behavior. I'm sure I'll pull that .1 out and make it a constant for tweaking later.

The thing that I got stumbled-up on when coding this was the movement code. Since everything moves direct along the X or Y axis but not both at the same time, I haven't had to do any geometry until now. The decimator flies so I had to take the interpolated distance and use it as a hypotenuse on a right triangle at the angle to the target to figure out how many pixels to move x and y per tick. Since I'm weak on math right now, it was a pain to remember Arc Tangent and Radians. I finally came to after about an hour of reading basic Trig again and got everything working. I'm wondering if there isn't a more efficient way to do this, but my way is to get the angle using the Arc tangent, then change the length of the hypotenuse to be the number of pixels to move this tick and multiply that by sine of theta and cosine of theta to get the y and x movement, respectively. It seems fast enough and works well.

Watch these videos to see how the Decimator will behave. Imagine that the graphics are good. That's what we developers do until they are!

Light Racer 2.0 - Days 15-16 - Maps and MapObjects

Android

After getting a decent graphic in for the Decimator, I began working on design changes that would allow Light Racer to have 10 unique levels. I implemented a LevelBuilder class that is responsible for configuring the World for a given level number. I tried to take into account the idea of the skirmish game later for single and multi-player modes in this design. I created the base MapObject class. A MapObject is any obstacle or interactive GameObject that's considered part of the "level." My first MapObject is the PowerLine, which is an object spanning a distance either vertical or horizontal that periodically arcs tesla-like lightning across the coils. The important thing for now is that per level, different numbers of AI players can be placed in different starting locations facing different directions, MapObjects can created and placed anywhere and initial speeds can be set on the players.

Day 15 - What Was Done:

Added Decimator Art
Made it so that the decimator would turn the sprite to face which direction it is going.
Added a little attack delay.

I'm happy with how the Decimator looks and feels. Now it needs some sounds and animation, which will be added in later.

Day 16 - What Was Done:

Added a LevelBuilder class that configures the level. The game is now limited to 10 levels. I'm thinking about adding in a "speed-play" mode that lets you configure a game and play it as fast as you can, kinda like the old light racer. For now I'm focused on getting a really good 10-level game built. I'll add all the configurable stuff later.

I standardized the way GameObjects work and now have 3 main classes of things. The GameObject, which is the base class and then the Player, Item and MapObject, all of which extend GameObject. I added a bounds-based collision detection interface to the game object so that now players can just ask an object in the world if they are colliding with it. This will make implementing the map objects much easier, because they just need to do that basic bounds check.

Added basic graphics for the power emitter
Added lightning animation - problem - it's huge.
Got animation and drawing code in for the power line.
Here's what I have decided will be the levels for the game:

level 1 is plain map, 1 AI player. position directly across from each other, speed 1
level 2 is plain map, 2 AI players. human in center bottom, AIs both across, speed 2
level 3 is horiz power map, 1 AI player, directly across from each other, speed 3
level 4 is vert power map, 1 AI player, side to side across, speed 3
level 5 is power box map, 2 AI players, top and bottom, speed 3
level 6 is laser sweep map, 1 AI player, both at bottom, speed 4
level 7 is laser sweep map, 2 AI players, top and bottom, speed 4
level 8 is power-cross map, 1 AI player, both at bottom, speed 4
level 9 is power-cross map, 2 AI players, top and bottom, speed 5
level 10 is portals, 1 AI player, both at bottom, speed 5

I started by implementing a method in the LevelBuilder which is responsible for configuring each level. You will also see the applyMap method. This is what actually makes a map a map. It takes a MapObject that I have developed and puts it in the World.

public void buildLevel(int levelNumber, int difficulty, LightRacerWorld world) {
if (levelNumber == 1) {
// level 1 is plain map, 1 AI player. position directly across from each other, speed 1
world.players = createPlayers(2, difficulty, 1, false, world);
applyMap(MAP_VERT_POWER, world);
} else if (levelNumber == 2) {
// level 2 is plain map, 2 AI players. human in center bottom, AIs both across, speed 2
world.players = createPlayers(3, difficulty, 2, false, world);
applyMap(MAP_PLAIN, world);
} else if (levelNumber == 3) {
// level 3 is horiz power map, 1 AI player, directly across from each other, speed 3
world.players = createPlayers(2, difficulty, 3, false, world);
applyMap(MAP_HORIZ_POWER, world);
} else if (levelNumber == 4) {
// level 4 is vert power map, 1 AI player, side to side across, speed 3
world.players = createPlayers(2, difficulty, 3, false, world);
applyMap(MAP_VERT_POWER, world);
} else if (levelNumber == 5) {
// level 5 is power box map, 2 AI players, top and bottom, speed 3
world.players = createPlayers(3, difficulty, 3, false, world);
applyMap(MAP_POWER_BOX, world);
} else if (levelNumber == 6) {
// level 6 is laser sweep map, 1 AI player, both at bottom, speed 4
world.players = createPlayers(2, difficulty, 4, false, world);
applyMap(MAP_LASER_SWEEP, world);
} else if (levelNumber == 7) {
// level 7 is laser sweep map, 2 AI players, top and bottom, speed 4
world.players = createPlayers(3, difficulty, 4, false, world);
applyMap(MAP_LASER_SWEEP, world);
} else if (levelNumber == 8) {
// level 8 is power-cross map, 1 AI player, both at bottom, speed 4
world.players = createPlayers(2, difficulty, 4, false, world);
applyMap(MAP_POWER_CROSS, world);
} else if (levelNumber == 9) {
// level 9 is power-cross map, 2 AI players, top and bottom, speed 5
world.players = createPlayers(3, difficulty, 5, false, world);
applyMap(MAP_POWER_CROSS, world);
} else if (levelNumber == 10) {
// level 10 is portals, 1 AI player, both at bottom, speed 5
world.players = createPlayers(2, difficulty, 5, true, world);
applyMap(MAP_PORTALS, world);
}
}

public void applyMap(int map, LightRacerWorld world) {
if (map == MAP_VERT_POWER) {
world.mapObjects = new MapObject[1];
world.mapObjects[0] = new PowerLine(world.width / 2, world.height / 2, 300, true, gameResources);
}
}

The first level won't have this but for testing, I changing the configuration of the first level to be whatever map I'm currently working on. All in all there will be 6 maps that use about 3 totally unique features. This is kind of how the Power Line will work but instead of being on constantly it will have a visible charge period then it will let the power go for a second or two. I need to add collision detection to it so that it shocks the players that come too close wihle it's turned on.

To show you how the different levels might look, I took these screenshots:

Example Vertical Power LineExample Vertical Power Line Example Map PlainExample Map Plain

Light Racer 2.0 - Days 17-18 - Finished Power Lines

Android

Sometimes finishing the detail work can be the hardest part of completing any project. Games are certainly no exception! My project plan called for me to move forward but instead I decided to take a day to improve sound and finish collisions for everything done to-date. This means that after day 18, there are new sounds in for the decimator, power lines and item pick-ups. I process the sounds myself using audacity but usually the base sounds come from free sound banks online.

Day 17 - What was done:

Added cycling states to power line
Added collision detection to power line
Reduced power line graphic size
Implemented horizontal power line
Found some sounds for power line

The PowerLine class is stateful, in that the powerline always has some state and is counting down to its next state change. At the time of state-switching, the PowerLine picks a random amount of time within a range to switch to the next state. Here's how the update() code that handles states:

@Override
public void update(LightRacerWorld world) {
if (world.gameState == STATE_RUNNING) {
nextActionTimeLeft -= world.tickDelta;
// System.out.println("Action = " + action + ", nextActionTimeLeft = " + nextActionTimeLeft);
if (action == ACTION_NONE) {
// switch to action idle
// determine time to charge
if (nextActionTimeLeft <= 0) {
nextActionTimeLeft = LightRacerWorld.RNG.nextInt(MAX_ZAP_INTERVAL - MIN_ZAP_INTERVAL)
+ MIN_ZAP_INTERVAL;
action = ACTION_IDLE;
}
}
if (action == ACTION_IDLE) {
// on no time left, switch to charge
if (nextActionTimeLeft <= 0) {
action = ACTION_CHARGING;
nextActionTimeLeft = CHARGE_TIME;
gameResources.soundManager.playSound(LightRacerConstants.SOUND_CHARGE_UP);
}
}
if (action == ACTION_CHARGING) {
// animate
// on no time left, switch to zap
if (nextActionTimeLeft <= 0) {
action = ACTION_ZAPPING;
nextActionTimeLeft = LightRacerWorld.RNG.nextInt(MAX_ZAP_TIME - MIN_ZAP_TIME) + MIN_ZAP_TIME;
zapSoundStreamId = gameResources.soundManager.playLoopingSound(LightRacerConstants.SOUND_ZAP, -1);
}
}
if (action == ACTION_ZAPPING) {
if (nextActionTimeLeft <= 0) {
lightningFrameAdvance = 0;
lightningFrame = -1;
action = ACTION_NONE;
gameResources.soundManager.stopLoopingSound(zapSoundStreamId);
zapSoundStreamId = -1;
} else {
// update animation
lightningFrameAdvance -= world.tickDelta;
if (lightningFrameAdvance <= 0) {
lightningFrame++;
if (lightningFrame > gameResources.lightningFrames.length - 1) {
lightningFrame = 0;
}
lightningFrameAdvance = LIGHTNING_ANIMATION_DELAY;
}
}
}
} else {
if (zapSoundStreamId != -1) {
gameResources.soundManager.stopLoopingSound(zapSoundStreamId);
zapSoundStreamId = -1;
}
}
}

What that results in is a slightly unpredictable but comfortable feeling map object that gives a little warning before it zaps. The collision detection code checks the state and uses one big rectangle if its zapping but two smaller rectangles when its not. Here's that code:

public boolean checkCollision(int lastX, int lastY, int curX, int curY) {
int halfSize = size / 2;
int x = this.x;
int y = this.y;
if (action == ACTION_ZAPPING) {
// do big rect
if (isVertical) {
return Collisions.checkRectCollision(curX, curY, x - (HALF_MIN_BOUNDS), y - (halfSize), x
+ (HALF_MIN_BOUNDS), y + (halfSize));
} else {
return Collisions.checkRectCollision(curX, curY, x - (halfSize), y - (HALF_MIN_BOUNDS), x + (halfSize),
y + (HALF_MIN_BOUNDS));
}
} else {
// do two small rects
if (isVertical) {
if (!Collisions.checkRectCollision(curX, curY, x - (HALF_MIN_BOUNDS), y - (halfSize), x
+ (HALF_MIN_BOUNDS), y - (halfSize) + MIN_BOUNDS)) {
return Collisions.checkRectCollision(curX, curY, x - (HALF_MIN_BOUNDS),
y + (halfSize) - MIN_BOUNDS, x + (HALF_MIN_BOUNDS), y + (halfSize));
} else {
return true;
}
} else {
if (!Collisions.checkRectCollision(curX, curY, x - (halfSize), y - (HALF_MIN_BOUNDS), x - (halfSize)
+ MIN_BOUNDS, y + (HALF_MIN_BOUNDS))) {
return Collisions.checkRectCollision(curX, curY, x + (halfSize) - MIN_BOUNDS,
y - (HALF_MIN_BOUNDS), x + (halfSize), y + (HALF_MIN_BOUNDS));
} else {
return true;
}
}
}
}

Day 18 - What was done:

Edited and added sounds
Added better sound management
Sounds -
NPC (ominous hovering sound, lowers in pitch when close to the ground but you can hardly hear it now.)
Power line - nice charge up and electricity sound. I used a garage door and arc welder sample for this.
Item pick-up - standard rising-up sound.

Here's how things looked in the middle of day 18.

Light Racer 2.0 - Days 19-20 - Laser Sweep

Android

Implementing the laser sweep was one of the most fun parts of the development so far. I wanted the laser to feel like it was controlled by a conscious being and I believe I achieved that effect. The turret is menacing; it locks on to a player and tracks them before charging up and firing a sweeping blast of laser. I was originally planning on having the laser also decimate any trails it touched but after playing with it for a while, I decided that it was plenty effective just as a player killer. The art really worked out well for it too. I'll include what I drew in photoshop so you can see what I did to make it turn out nicely in the game.

Day 19 - What was done:

Graphics for Laser Sweep
Basic Laser Sweep Code, no collisions

Laser Sprites in PhotoshopLaser Sprites in PhotoshopI started by using the same "Base" as the power line. I then drew a little laser cannon and used some effects in photoshop to give it a shaded and sci-fi look. I made a few layers with different lighting on them so that I could have the regular state and the firing state. I also made a square laser beam image that will be stretched by the code. Yes, it could have been 1 pixel thick but it's easier for me to see it if its square.

In the coding side, I created my LaserSweep class which extends MapObject and I started putting in all of the fields and constants I know it will need. I copied the state management code from the PowerLine as a starting point. The laser will work mostly the same but will move around a bit before firing. I thought it would be really cool if it would "Target" players before charging and firing so I added some code to have it randomly choose a target and turn to face the target. This gives it the effect that it is "Watching" you and may fire on you. It worked out well.

The code to turn and face the target originally caused it to flip back and forth and basically move at an infinite speed. I didn't like that so I set a maximum number of degrees per second the laser could turn to and had the update code use the tick delta to adhere to it. Now it looks much more controlled.

I also added in a sweep in a random direction when it fires. It really looks cool now. I would like for the laser to "hit" the wall and also would like it if the beam itself had a little animation in it, but I'm worried that I simply won't have enough CPU to do all of these things with how much is happening in the game already. I think I may wait until the last few days of polishing to use up any extra CPU making it fancy.

My next task is collision detection, which will be my hardest yet. So far I have not had to deal with a thick angled line going through the board. What makes matters worse is that I want the laser to destroy any light trails that it touches. That's what I will spend much of the day tomorrow working on.

Day 20 - What was done:

Laser Sound
Laser Sweep Collisions
Started Portals

I was originally planning on having the laser sweep take out trails that it touched but once I finished with the basic collisions, I was happy with how it worked. I had a little problem with the math to detect collisions with the beam but after I read about a few tricks for checking that a point lies within a rotated rectangle, I got it all to work.

Here are my first attempts at checking my math for collision detection. I used some basic trig to come up with the points that would be affected by the laser and then drew it out to make sure the math was good. The "beam" in these screenshots and on the video is bigger than the real laser will be but I wanted to see it clearly to make sure things were right.

Laser Collision Detection 1Laser Collision Detection 1 Laser Collision Detection 2Laser Collision Detection 2

Here is a video showing it in action:

I didn't end up using that code because I found a different way to do it that is easier.

I was considering switching to vectors so I could use this - http://www.gamedev.net/community/forums/topic.asp?topic_id=483716 - but instead I found it easier to use this method - http://mathcentral.uregina.ca/QQ/database/QQ.09.06/h/graham1.html

When doing the collision checks, I pretend that the rectangle (laser beam) is straight up and down, starting at the point where the lase base is. I then find the relative position of the racer I'm checking for collisions on (think racerX - laserBaseX, racerY - laserBaseY) and that gives me coordinates in a space that I can rotate them in. I then rotate them exactly the opposite amount that the laser is rotated and then translate them back into coordinate relative to the world, since the collision detection code uses world coordinates. A regular rectangle check later and we're done.

Before this, I hadn't really dealt with line intersections at angles before so it was a slow process getting the collision stuff in and making sure it was as efficient as it could be. It seems quick and is working well though so I'm moving on to the portal map.

Here is the final product:

Light Racer Finished LaserLight Racer Finished Laser

Light Racer 2.0 - Days 21-22 - Portals

Android

The final stage of the game is called "Portals." When a player goes into a portal, he comes out facing a different direction on the other side of the map. It's simple but also risky. I randomized where the player comes out of so there's no way to guarantee safe exit when there are trails blocking parts of the other portal. Overall this map feature was fairly simple to implement and it makes for yet another interesting dynamic to the game. I'm very happy to be totally finished with map features because now I can go about assembling and tweaking everything to make the game really feel complete.

What was done - Day 21

Developed most of the portal code.

I thought that it might be cool if a portal would transport (or warp as the code calls it) a player to a random portal but I instead decided to code the portals up in pairs to make the game a little more predictable to play and easier to design. Portals can be positioned but will be created with two sides that exist on the edges of the world. When a player enters one side, their trail will be dropped and they will be moved to a random spot coming out of the opposite portal. This makes portals a bit of a gamble to use because the path may not be clear on the other end.

What was done - Day 22

I didn't get as much work done today as I was hoping to but I did get the code for the portals finished and I got the new graphic designed for it. I was thinking about animating it because I thought it could be cool to have a stargate-like thing but I ended up using a portal that looks a little more like a red ring.

I added a sound that is played when a player warps through but it doesn't sound as good as I'd like it to. I will have to search around or make a better sound.

I'm a little worried that there may be a problem with the player colliding with the back wall when it should be warping in the portal. My tests make it seem like everything is fine now but the code checks for wall collisions before portal ones so it could happen. The case would be when the player moves from in front to behind the portal in one tick (it would have to be 15 pixels, which could happen at higher speeds and if the game lags). The player collision code would see the wall first and kill the player before the portal code can see the player in the portal and move them. I'll come back to address this if it's an issue in play testing later.

This is the last level I'm designing. I'm very happy with the diversity of the different levels. The laser is so fun that I'm thinking about changing things a bit to have a level with 2 lasers. That could be challenging but really fun.

Here's what the portals look like:

PortalsPortals

And here's the video showing it in action:

Light Racer 2.0 - Days 23-24 - New Graphics

Android

My project plan called for the completion of the levels in the game and then on to do more work on the AI, but I decided instead to take a couple of days to finish up some odds and ends that I have been spending too much time thinking about, which came to be graphics improvements, zooming, player indicators and scoring. These two days have done more for the overall feel and polish of the game than anything I have done so far. The zooming got me to thinking about different ways to make 2D games interesting. I still have 6-8 weeks left of development to get Light Racer 2 and Light Racer 3D done but I'm already thinking about how I can use what I've learned on this project in my next game. Anyway, here is what was done on days 23 and 24.

Day 23 - what was done

Finished level configurations - Added power cross.
I positioned the power cross so that players could sneak behind them.
Tested all 10 levels a few times to make sure the game felt complete.
Created and put in a nicer looking sci-fi background
Started working on camera effects

I ended up slowing down the speeds for the last 5 levels because the game just felt too fast. The new map objects really add depth to the game and the extra speed is just unnecessary.

Here are some samples of how the completed levels look:

Light Racer 2 - Level 2Light Racer 2 - Level 2 Light Racer 2 - Level 3Light Racer 2 - Level 3 Light Racer 2 - Level 5Light Racer 2 - Level 5

Day 24 - what was done

I added camera effects and tweaked the background a little bit. I also implemented the score and level up on top. I wanted the score to render quickly so instead of using Android views for those text labels I made the entire screen the custom game view and have the main thread rendering text to the top. Since this increased the size of the surface view, I had to use a transform matrix on the canvas that the game renders to. That moved the game down 30 pixels and game me room to put the informational header up on top.

Here's how the zoom code works:

Matrix m = canvas.getMatrix();
// this moves the whole scene down to make room for the info header
m.postTranslate(0, INFO_HEADER_HEIGHT);
// this is the player currently controlled by the person playing the game
Player thisPlayer = this.thisPlayer;
// zoom is adjusted by the update() method and uses interpolation to get a constant rate.
float zoom = this.zoom;
if (thisPlayer != null && zoom > ZOOM_MIN) {
// zoom in on player
int centerX = world.width / 2;
int centerY = world.height / 2;
// center on player
// zoom in
// posFactor is a number between 0f and 1f that represents how completely we are zoomed. 0 is not at all.
float posFactor = (zoom - ZOOM_MIN) / (ZOOM_MAX - ZOOM_MIN);
// multiply the centering times the zoom factor to bring us back to 0,0 at the end of the zoom.
m.postTranslate((centerX - thisPlayer.curX) * posFactor, (centerY - thisPlayer.curY) * posFactor);
// scale to the zoom
m.postScale(zoom, zoom);
// compensate for the zoom throwing off the center.
m.postTranslate(centerX * (zoom - 1) * -1, centerY * (zoom - 1) * -1);
}
canvas.setMatrix(m);

I added a blinking player indicator showing who your player is and what direction they are starting in. I made one of each in blue, yellow and red.

I think the game looks much nicer now. The last 2 days of work have really made it come together and feel more polished and complete.

Light Racer 2 - Zoom with player indicatorLight Racer 2 - Zoom with player indicator

Light Racer 2.0 - Days 25-30 - More AI and Performance Tuning

Android

Wow, so much has happened in the past 6 days. My tasks were to finalize how items and NPCs spawned and finish Easy, Medium and Hard AI. Most of the time was spent optimizing algorithms, adding collision checks, changing little bits of design here and there and testing. Testing testing testing. This is the stage where it really gets tiring to play your own game. It just starts to lose some luster after the 100th time you've tried the same level against an Easy CPU. That's ok though because I pushed through and 6 days later, I've got most of the core game done. I also sped the game up by an average of 50% by optimizing my path finding algorithms and drawing things more efficiently.

Day 25 - What was done:

Performance Tweaks
Randomized Item Spawning
Finished all collision checks

To determine the correct locations of item spawns, I had to finish implementing several collision detection algorithms for various game objects that I had previously put off doing. This includes the ability for a game object to determine if it intersects with another rectangle, which is what we usually refer to as a bounds-based collision check. The algorithm itself is very simple for non-rotated rectangles, which is what this game uses for everything except the laser. The problem that I had is that performance slowed down a bit after I finished all of the implementations. I will need to work on that very soon.

The item spawner picks a random spot for the item and then loops to see if that spot is already occupied (bounds check) by something else. If it is, it adds 5 to both x and y and tries again. It will do this until it ends up out of the world, at which point a new random spot is chosen and the cycle repeats. This could potentially end up causing an infinite loop if there isn't a single 20x20 pixel spot in the world where the item can go, but I have yet to see that scenario.

Overall the item spawning works very nicely now. At any given point in time there can be 2 items in the world and they can be the same type of item.

Day 26 - What was done:

Began work optimizing pathfinding algorithms

I spent the entire day trying to speed up my A* path finding algorithm that is used to control the AI players. I received some help on gamedev.net and eventually settled on using a BinaryHeap with every little optimization I could think of that would make the code run faster.

The original implementation was based on a Java A-Star example I found online somewhere, but it was generic and was far too slow to run two AI players each at 10FPS (my AI update speed) on a phone. I was able to triple its speed by performing some very basic optimizations - I had ripped out any code that I didn't need, got rid of all the interfaces, coupled it tightly with my AITile class and minimized member references.

This ran fast enough to test the game with but I still needed to quadruple the speed of the algorithm to get a longer path in under 10ms. I first got rid of the closed list that the algorithm uses and instead use a closed flag on each AITile. that was a big boost because checking for the existance of an element in an ArrayList was a linear operation which was totally unnecessary since I had the element in-hand and could just keep a closed flag on it.

The next thing that I did was replace the sorted open array with a linked list, which substantially improved speeds once again. It's not necessarily optimal though and for the small space I have (1440 tiles max), a Binary Heap will be a better overall performer. I then switched to an optimized Binary Heap that is tightly coupled to my AITile and did a few more optimizations in an effort to reduce floating-point math and reduce indirection, index look-ups and member references.

The resulting algorithm can run 160 search iterations in 8-14ms, compared to the previous 40 iterations in about the same amount of time. I think that will work for what I want and I can use some tricks in the AI to reduce the number of path finding searches further.

Day 27 - What was done:

Tweaked A* algorithm more.
Began implementing ADA* Algorithm.
Stopped working on ADA* because it's looking like it will consume too much time. My A* is fairly fast now and can be modified to return a partial result, which will let me tighten down the CPU cap on it and still have some good playable AI.

Day 28 - What was done:

Performance testing and tweaking
AI enhancements

I found that drawing the background (static) as RGB_565 was a huge performance boost for the game.

Added game objects to AI tiling

Refactored Medium AI to prepare for Hard AI

Day 29 - What was done:

More AI enhancements. I finally have the code factored out so that I can set up rules and probabilities to get the right effect.

Medium and Hard AI will use the same class. The difference is that Hard will be item-aware, and medium will not. Medium also will go into random states of mindless navigation for 400-800ms. I found that with the 2-tile lead and having both AI players always go for the human, medium is a little more difficult than I'd like but it's also a little predictable. The random navigation state should take care of that.

Day 30 - What was done:

Finished implementing Medium and Hard AI.

I have to update the collision checks to account for invincibility. While invincibility works in the game, the AI code uses a different check to see if a move is valid. This check didn't support the collision detections.

I tested Medium with the seek and default mode switching and it works very well now. It's not quite as hard as it was, which is good, and it's a little unpredictable, which I like.

Hard AI is a lot of fun to play against. It is fairly strong and picks up items, using them against you. I like that.
'
I have to say that AI programming is fun but can be very meticulous and tedious to test. It's not my area of expertise and I just spent about a week working to tweak everything. I accomplished a lot in that week as I was able to significantly speed up the game and make the AI much more fun to play against.

While making the progress video, I saw that there are some really obvious bugs with the Hard AI so I'll need to address those but I'm going to wait until I'm doing the final testing to work on it. I'm thinking that I'll let the testers determine if the AI is hard enough. If it is, then I'm done. If not, then I have to work on the things I see as bugs. What's funny about AI programming is that some times things the developer sees as bugs, the user sees as just how the computer plays. They have no idea what the actual code is doing most of the time. To them, they are just playing the computer.

Light Racer 2.0 - Day 31 - Anywhere Touch Swipe Control

Android

I had previously written a little app that I was going to call "Fingerpaint" but I never released it because there already seemed to be other apps on the market that did that better. I did get it working and I'm glad I did because it made me very familiar with how to process sliding touches. I copied the code out of that sketch app and modified it to process touches for Light Racer. Now the user can touch anywhere on the screen and swipe in the direction they'd like the racer to go.

Day 31 - What was done:

I added anywhere touch swipe control.

Essentially what the code does is when the user swipes their finger, I turn the line segment from the start to the end in to a vector and then look at the angle of the vector. If the vector up is 0 degrees and down is 180 with the right half being positive and the left half being negative then from -45 to 45 degrees would be up, 45 to 135 would be right, 135 to 180,-180 to -135 is down and -135 to -45 is left. This worked very well. You can swipe anywhere you want on the screen as long or short as you like and it can be a little off-angle. So long as it's mostly in the right direction, the code will understand.

So how does one get a vector from two points? Easy! Say x1 and y1 are the starting points and x2 and y2 are the ending points.

First, to determine if this touch should be accepted, we need to set a certain threshold to get rid of over sensitivity.

int distance = Math.abs(x2 - x1) + Math.abs(y2 - y1);
if (distance > TOUCH_SENSITIVITY) {
float angleR = LightRacerUtil.fastatan2(x2 - x1, y2 - y1);
// process the angle, apply as input
}

That distance is just a simple manhattan distance. It is the total number of x+y points to get from the start to finish. It may make more sense to use euclidean distance but this is a little faster and works fine. Then I just use my fast atan2 to get the angle of the vector. My fast atan2 is about 50% faster than the regular one but is also much less accurate. It can be off by as much as 4 degrees. In all of my testing though, I didn't have any problems with the touch even with that margin of error so I stuck with it.

It's still better to use the trackball but I think users will enjoy being able to touch control, especially when it works well like this.

Light Racer 2.0 - Days 32-33 - Getting Great Game Performance

Android

Choppiness in game play can be an absolute make or break deal for the success of any game. I never thought that the first Light Racer would get downloaded and played as many times as it has. If I had realized that then, I probably would have invested a few more days smoothing it out. All of that is water under the bridge now though because the last 2 days of work have brought Light Racer 2 up between 45 and 60 frames per second on the G1 and no stutter in game from garbage collection or resource management. I covered all of the issues I faced and provided some solutions. This will probably apply to many other games as well.

Day 32 - What was done:

Added tilt sensing. I'm not happy with how it's working right now. I have the basic trig working for sensing the tilt of the two axis I'd like to use and I'm keeping a running average of how the user is holding the device, but it's still not working as well as I thought it would. This game isn't really well suited to tilt-control but since so many people think it's cool, I'm going to try to get it working well enough.

I did more performance optimization and the game is now running at nearly a full 60FPS on my G1. I'm having a few issues with garbage collection causing 240ms lags and I have an idea of what's causing it. I did as much as possible to ensure that no new memory was allocated after game initialization. Unfortunately there are a few arrays and a lot of Coordinate objects that are still being created in the loop. Once I find a way to pool those resources, the game should be free of GC jitters.

So how did I get from 20FPS to 60FPS in a few days time without cutting any features?

Changing the background bitmap to RGB_565 helped tremendously. That alone got me to 30-35fps. I was still drawing a full surface layer in ARGB_8888 that the trails were drawn on to the main canvas. I realized that if I could somehow get rid of that really expensive draw, I'd probably gain another 20fps. I thought about it and realized that if I keep a clean background bitmap at RGB_565 and a dynamic one at RGB_565 which has the same bg and also gets trails on it, which is "wiped" by copying the clean one to it, that it would be far more CPU efficient. I switched out the code and I was right. Now I'm drawing a dynamic background image at RGB_565 and then the individual sprites.

I optimized all of my floating point math to use FloatMath and then a few methods like my fast float atan2, toDegrees and toRadians. I now use no doubles anywhere in the code and while my arc tangents aren't totally accurate, you would never know that when playing the game. Accuracy isn't as important as the speed.

I also went through all of the code that runs in the update and draw loop and found ways to reuse objects instead of allocating new ones. For example, I was creating new Matrixes for each player for every draw call. Now each player has a member Matrix and each time it is simply reset().

The big changes probably doubled my framerate but all of those little changes have also helped to reduce CPU usage and keep the game super smooth. Besides the delay when GC is running, which I will solve, the game is very smooth now - much better than Light Racer 1. My FPS counter shows around 60FPS for most of the game play time, even with 2 AI players running path finders on a map with many animated obstacles. That's the level of playability I've been working towards with all of these days of optimizations. I want to make sure that when people get the game, it's so smooth that it seems like the phone was designed specifically to make it play that way.

Day 33 - What was done:

Started using DDMS to track allocations and figure out why I'm seeing so much garbage collection. http://android-developers.blogspot.com/2009/02/track-memory-allocations....

Found that canvas.getMatrix() allocates a new Matrix if there isn't one. This wasn't a big deal but I think it's strange that Canvas makes the assumption that a null matrix and a new matrix are the same thing. For performance sake, I'd prefer if it returned null if no matrix is set. Every class I have that uses a matrix to draw has just a single field called reusableMatrix. I used the same thing here to fix this.

Also found that the text I'm drawing on the info header is causing tons of new char[] and Strings. I put in some serious optimizations to stop that. The bigger problem is that doing something simple like Canvas.drawText(score + scoreLabel, x, y, paint) is crazy expensive in terms of allocations. Here's how that code actually executes:

1) score needs to be a String so Integer.toString(score) is implicitly called.
2) Integer.toString(int) creates a new char[] and a new String and returns it.
3) A new StringBuffer() is created to concatenate the score string and scoreLabel string.
4) StringBuffer.toString() is then called which creates another new String()

Let's count this up. For EACH call (every frame, remember) to drawText(int + string, ...) a new char array, new StringBuffer (with its char array) and 2 new Strings, each with their own char arrays are created. All in all, the garbage collector has to collect 7 short-term objects for this one draw. That may not seem like a lot, but imagine you are drawing 2 of these labels like I was 60 times per second. That's 60 * 14 = 840 objects created per second or 50,400 objects per minute, which will max out Android garbage pile at around 1.7MB, causing a 100ms GC to run and interrupt your game.

The fix isn't _that_ hard but it is a serious optimization which means serious unreadability of the code if you don't understand it. What you need to do to stop this is to create a char[] to hold your stringed integer in. Then create a StringBuffer that you will reuse to do your concatenations. There are other ways of solving this problem, like caching the string if the actual text does not change often, but my scores change like the national debt rises so I have to do it this way.

Ready?

Here's the discussion thread where I worked out what to do with a bunch of other android developers. These guys are very helpful.

This is about as efficient as I will ever care to make such a thing.
If you want to _never_ allocate, you could make a char[][] where the
first dimension is the length of the second dimension arrays. That
would be char[1], char[2], char[3], char[4] and so on. That would
make it so that if your string size were 5 chars, you would say char[]
correctArray = myArrays[5]. I didn't both with that because a few
allocations are ok, just not one every tick of the loop.

This is ugly but if you need something like this, it does work:

I have a class called Util and I put this in it:

/* Most of this code is copied from the Integer class in Java 6 SDK. It's slightly modified here but the original copyrights should apply. */
private final static int[] intSizeTable = { 9, 99, 999, 9999, 99999,
999999, 9999999, 99999999, 999999999,
Integer.MAX_VALUE };

private final static char[] DigitTens = { '0', '0', '0', '0', '0',
'0', '0', '0', '0', '0', '1', '1', '1', '1',
'1', '1', '1', '1', '1', '1', '2', '2', '2', '2', '2', '2', '2',
'2', '2', '2', '3', '3', '3', '3', '3',
'3', '3', '3', '3', '3', '4', '4', '4', '4', '4', '4', '4', '4',
'4', '4', '5', '5', '5', '5', '5', '5',
'5', '5', '5', '5', '6', '6', '6', '6', '6', '6', '6', '6', '6',
'6', '7', '7', '7', '7', '7', '7', '7',
'7', '7', '7', '8', '8', '8', '8', '8', '8', '8', '8', '8', '8',
'9', '9', '9', '9', '9', '9', '9', '9',
'9', '9', };

private final static char[] DigitOnes = { '0', '1', '2', '3', '4',
'5', '6', '7', '8', '9', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6',
'7', '8', '9', '0', '1', '2', '3', '4',
'5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', '0', '1', '2', '3', '4', '5', '6',
'7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', };

private final static char[] digits = { '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e',
'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };

// Requires positive x
private static int stringSize(int x) {
for (int i = 0;; i++) {
if (x <= intSizeTable[i]) {
return i + 1;
}
}
}

public static void getChars(int i, int index, char[] buf) {
if (i == Integer.MIN_VALUE) {
System.arraycopy("-2147483648".toCharArray(), 0, buf, 0,
buf.length);
}
int q, r;
int charPos = index;
char sign = 0;

if (i < 0) {
sign = '-';
i = -i;
}

// Generate two digits per iteration
while (i >= 65536) {
q = i / 100;
// really: r = i - (q * 100);
r = i - ((q << 6) + (q << 5) + (q << 2));
i = q;
buf[--charPos] = DigitOnes[r];
buf[--charPos] = DigitTens[r];
}

// Fall thru to fast mode for smaller numbers
// assert(i <= 65536, i);
for (;;) {
q = (i * 52429) >>> (16 + 3);
r = i - ((q << 3) + (q << 1)); // r = i-(q*10) ...
buf[--charPos] = digits[r];
i = q;
if (i == 0)
break;
}
if (sign != 0) {
buf[--charPos] = sign;
}
}

Then in my game code it looks like this (for my FPS counter):

private char[] fpsChars = new char[2];
private StringBuffer fpsText = new StringBuffer(7);

private void drawFPS(Canvas canvas) {
int fps = this.fps;
int fpsStringLength = Util.getSize(fps);
if (fpsChars.length != fpsStringLength) {
// re-allocate
fpsChars = new char[fpsStringLength];
}
char[] fpsChars = this.fpsChars;
//copy the chars into the array
Util.getChars(fps, fpsStringLength, fpsChars);
StringBuffer fpsText = this.fpsText;
fpsText.delete(0, fpsText.length());
fpsText.append(fpsChars).append(FPS_TEXT);
canvas.drawText(fpsText, 0, fpsText.length(), worldWidth - 60,
worldHeight + INFO_HEADER_HEIGHT - 20,
gameResources.fpsPaint);
}

That is SO MUCH more code than I ever wanted there but it's
ridiculously more efficient than it was before so I'm going to call it
good and move on.

So, with all of this said and done, I now have a game that runs at a great framerate (45-60fps on the G1) and never garbage collects while playing. I'd say that's about all I could ask for. It's finally time to quit messing around with performance and get some new screens done!

Update 8/28/2009 - Timothy F has sent me an easier way to do a counter update with no allocations:

static final char c[] = new c[100];
static final StringBuilder sb = new StringBuilder(100);
private void drawFPS(Canvas canvas) {
sb.setLength(0);
sb.append(fps);
sb.append(FPS_TEXT);
sb.getChars(0, sb.length(), c, 0);
canvas.drawText(c, 0, sb.length(), worldWidth - 60, worldHeight +
INFO_HEADER_HEIGHT - 20, gameResources.fpsPaint);
}

Light Racer 2.0 - Day 34 - New Menus, Modes and Continue

Android

After letting several people play the game, I've taken their feedback and incorporated it. I spent a lot of time debating how I was going to handle the continue screen. It was frustrating me because it was confusing to people and it wasn't going to work well to try to overlay an Android UI on top of it since it's still in-game. I also finally added in the new solo menus and added a new game mode based on the original light racer - Speed Trial mode.

Day 34 - What was done:

New menus
3 Lives, automatic replay (no prompt)
No zooming on continue
Speed Trial mode
Started testing on 1.5

Play testing is so important, I can't even emphasize it enough. I get the best feedback from the people who try the game out. When I was first asked, "What do I do?" I realized that the game simply wasn't obvious enough and so I added a player indicator and a zoom to show who you are controlling. That seriously helped and now very few people ask what to do. Instead, they start trying things.

The continue screen bugged me so bad that I ended up just removing it. I had planned on having a finite number of continues and with that, I was able to remove the continue screen altogether and just show how many lives are left. This makes the game much smoother to interact with because players know exactly what the goal of the game is - don't die!

The game was running too fast so I slowed it down from 100 pixels/second to 80 pixels/second standard movement rate. It's much nicer now on the phone.

I also just switched my development environment to Android 1.5 (cupcake). It seems as though they've tweaked the emulator to perform almost exactly like the G1. This is nice because the 1.1 SDK had an emulator that would run much faster so I would have to guess at how the performance would be on the device itself, then find out that the device was too slow and make changes. Now I'll know more of what to expect from the start.

I also added new menus but I'm still waiting on the new graphics. They'll be in soon.

Light Racer 2.0 - Days 35-40 - Multiplayer Host and Join

Android

Developing a couple of screens with some networking code that can make them talk to each other sounds like a simple enough task on the surface, but when you set out to develop multiplayer menus that need to handle every kind of situation that can happen on a mobile device as well as be able to understand and respond to future protocol versions, it's a much more complicated job. I definitely underestimated this task, giving myself just two days to get it done. It ended up taking closer to six, and there are still a few problems that will need to be fixed before the final release. I believe that the design I used is sound and this code will hold up against the test of time.

Day 35 - What was done:

Added screens for multiplayer join and host
Added host spinner options
Started working on first-level netcode/service

Day 36 - What was done:

Created Service definitions in AIDL
Implemented basic Host and Join activities
Started defining control protocol
Implemented much of service

Day 37 - What was done:

Got broadcast working for hosted game
Got join-activity to see the broadcasts and maintain a list of hosts

Day 38 - What was done:

Made both hosting and joining solid with orientation changes, cancels and rebinds.
Added notification for hosting service.
Designed a simple control protocol.
Implements most of the client and host protocol, interactions and callbacks.

Day 39 - What was done:

Added multiplayer client screen - hooked it up with join screen
Got control protocol working
Ran into issue with UDP / firewall - unresolved, using hack for testing
Got preferences working with the host screen, live updates work over control stream

Day 40 - What was done:

Fixed most networking issues.
Got host-kick to work.
Got client disconnect to work.

Summary

The basic design approach I took was to have a MultiplayerService that would handle all network communication with itself, running on another device. This means that in a 3-way situation, there will be 3 devices, each with a MultiplayerService. One service will be running in a host mode and the other two will be running as clients. Since I put all of the heavy lifting network code into the service, the activities are very clean and it's easy to find and debug problems, because they are generally only in one spot.

Here's the AIDL for my service:

interface IMultiplayerService {

void registerCallback(IMultiplayerServiceCallback cb);
void unregisterCallback(IMultiplayerServiceCallback cb);
MultiplayerGameConfiguration getGameConfiguration();
// host side
void updateGameConfiguration(in MultiplayerGameConfiguration mpGameConfig);
void endHosting();
void startGame();
// client side
void listenForHosts();
void joinGame(String url, String userName);
void disconnect();
}

And for the callback AIDL:

oneway interface IMultiplayerServiceCallback {
void onError(String errorText);
void onJoinSuccess(out MultiplayerGameConfiguration mpGameConfig);
void gameConfigurationUpdated(out MultiplayerGameConfiguration mpGameConfig);
void hostFound(String ipAddress, int version, String gameName);
void hostConnectionLost();
void clientConnected(out MultiplayerClientInfo clientInfo);
void clientConnectionLost(out MultiplayerClientInfo clientInfo);
void gameStarting();
}

Since my activities only have to deal with these methods, working with them was easy and straight forward. Getting the network code to work was much more difficult.

I ended up using UDP broadcasts to 255.255.255.255 that announce when a game is being hosted. The join activity, which lists games it sees, listens on 0.0.0.0 for the broadcasts. It runs a thread that prunes the list if it hasn't seen a broadcast in over 3 seconds. Since broadcasts are to be made every second, this works fairly well, although I may have to increase it to 5 to account for udp packet loss. To make that work, I had to use my actual phone as the broadcaster and the emulator as the client (listener). On the emulator, I had to telnet to the local emulator port and run redir add udp:
:
where
is my broadcasting port. I wasn't able to get this to work with 2 emulators.

The client knows what server it is by looking at the address on the DatagramPacket, but that presents a problem with the emulator. Since the emulator is using a bridge for networking, it is natting everything and the source host gets rewritten to be the nat source, which is 10.0.2.2. I don't think this will be a problem in the real world but for now I have hard coded the source address to make things work. I'm sure I'll find another solution for this later.

The host is broadcasting its hosted game but also listening for client control connections on a TCP port. The client connects to the TCP port at the address it saw the UDP packet coming from and the protocol begins. I used Object streams (serialization) for the control protocol because speed and efficiency do not matter, which is why it gets its own port. The data for the game will run on another port because it needs to be a smaller, faster protocol.

The host has the ability to change any parameter of the game. Clients can join or quit. The host can kick a client. When the client joins, it is taken from the join activity and brought to the client activity, which displays live information about the configuration of the game. It is here that the game itself will launch, clients will connect to the host's data port and the fun begins.

That's where I start coding tomorrow.

Sorry, no screenshots or videos at this time. There will be more as the game gets closer to completion. I have no specific date at this point.

Light Racer 2.0 - Days 41-44 - Real-time Multiplayer Protocol Implementation

Android

According to my 2nd revised project plan for Light Racer 2, I was to have a release done on June 5. The core game has been done for a few weeks but the complexity and difficulty of getting the multiplayer system to work well has thrown that date right out the door. The last 4 days of work have been focused specifically on getting the basic multiplayer code to work. This means connecting two phones together, starting a game and playing it. Today I had a breakthrough, after weeks of work I was finally able to play against myself using my phone and emulator. There are tons of bugs and it's not yet complete but it was good to actually see it all working.

Day 41 - What was done:

Talked to a friend - he suggested I try google's protobuffer
Started implementing the world in protobuffer
Protobuf won't work. I'll have GC nightmares and it's too slow.
I need to roll my own protocol, found naga which will make things a little nicer with handling connections but I still need a buncha bytes.

Day 42 - What was done:

Found thorough byte conversion code snippet at http://www.daniweb.com/code/snippet644.html#
Developed basics of a custom protocol
Started work on RealtimeNetworkHost class using Naga NIO

Day 43 - What was done:

Naga NIO didn't work correctly on Android so I reimplemented the data host and client using standard sockets.
Developed Data Protocol, wrote positional byte converter (NetworkDataObject) with header structure.
Wired up the RealtimeNetworkHost and RealtimeNetworkClient completely with the main game thread

Day 44 - What was done:

Reworked state system so that things would work on the network client
Debugged much of the new data networking code
Finally played myself for the first time, though there are tons of little problems

Summary

It took a while to figure out the best approach for the data protocol. I have dedicated one port to control and one port to data. The control port is used to negotiate the multiplayer clients. It's kind of like a "chat room," where people can join and the host can change game settings. Once the game is started, everything moves to the data port.

Unlike the control protocol, which is handled by the multiplayer service, the data protocol is handled by the game thread directly. This means that when the host starts, it opens up a server socket to listen for clients. The clients start and immediately connect to the host. Once all clients are connected, the host begins sending data and the clients begin sending their input.

I tried my best to avoid rolling my own data protocol but as it seems with every game, it's much more efficient to do it yourself. I first tried google's protobuf which is a really nice library and toolset for creating a protocol that can interoperate between languages, but the limitations are that I can not control when it allocates new memory and it's also slower than a custom protocol. I spent a day working on a proof of concept for it but had to throw that out the door.

For connectivity, I almost used Java's NIO but then tried Naga NIO. It didn't work correctly for some reason. I never got callbacks. That was frustrating and cost me almost another day of time as I had to rework all of my socket code to use basic Sockets.

The interesting part - The game's network protocol design

Light racer uses a World class to hold all relevant game information. This was done specifically so that it would be easier to network for multiplayer. The World has GameObjects which are Players, MapObjects and Items. The low level network interface has write(byte[]) and read(byte[]) so I needed to convert the world and its game objects into a flattened-out byte array.

I first came up with a class that I called NetworkDataObject. A NetworkDataObject is a flat, networkable representation of the World or a GameObject. I then added to GameObject that makes the class look like this:

public abstract class GameObject {
/** set if it's possible to collide with this object */
public boolean isCollideable;
/** set if this object is alive */
public boolean isAlive;
/** object's x location */
public int x;
/** object's y location */
public int y;

// Non-Networked fields
/** increment this and use it for assigning IDs out. */
public static int nextId = 0;
/** the id assigned by nextId; */
public int id;
/** set if this object has been initialized (used by netcode) */
public boolean isInitialized;

public abstract void update(LightRacerWorld world);
public abstract void draw(Canvas canvas);
public abstract void restartSound();
public abstract void release();
public abstract boolean checkCollision(int lastX, int lastY, int curX, int curY);
public abstract boolean checkRectCollision(int left, int top, int right, int bottom);
public abstract NetworkObjectData getNetworkObjectData();
public abstract void update(NetworkObjectData data);
}

See the getNetworkObjectData() and update(NetworkObjectData)? Those make it so that on the host, getNetworkObjectData() will encode the object into a NOD (NetworkObjectData) and then when the NOD is received on the client, it will update the existing object.

Here's what the NOD looks like:

public class NetworkObjectData {
public static final int HEADER_DATA_SIZE = 12;

public static final int OBJECT_TYPE_WORLD = 0;
public static final int OBJECT_TYPE_PLAYER = 1;
public static final int OBJECT_TYPE_TRAIL_SEGMENT = 2;
public static final int OBJECT_TYPE_COORDINATE = 3;

public int objectType;
public int objectId;
public byte[] objectData;
private int dataPosition = 0;

public NetworkObjectData(int objectType, int objectId, int dataSize) {
this.objectType = objectType;
this.objectId = objectId;
objectData = new byte[dataSize];
}

public void reset() {
dataPosition = 0;
}

public void putInt(int value) {
int offset = dataPosition;
byte[] data = objectData;
data[offset] = (byte)((value >> 24) & 0xff);
data[offset + 1] = (byte)((value >> 16) & 0xff);
data[offset + 2] = (byte)((value >> 8) & 0xff);
data[offset + 3] = (byte)((value >> 0) & 0xff);
dataPosition += 4;
}

public int getInt() {
int offset = dataPosition;
byte[] data = objectData;
int ret = (int)(
(0xff & data[offset]) << 24 |
(0xff & data[offset + 1]) << 16 |
(0xff & data[offset + 2]) << 8 |
(0xff & data[offset + 3]) << 0
);
dataPosition += 4;
return ret;
}

public static void putInt(int value, byte[] array, int position) {
array[position] = (byte)((value >> 24) & 0xff);
array[position + 1] = (byte)((value >> 16) & 0xff);
array[position + 2] = (byte)((value >> 8) & 0xff);
array[position + 3] = (byte)((value >> 0) & 0xff);
}

//... more types of gets and puts and a few utility methods...
}

The NOD is awesome to use because the interface is so easy. It's just like a parcelable in that you call puts in a certain order on the encode and gets in the same order on the decode. It's about as efficient as you can possibly get while still being easy to read and use.

Here's an example of how it works on Player

@Override
public NetworkObjectData getNetworkObjectData() {
//TODO - Reuse, don't reallocate
NetworkObjectData data = new NetworkObjectData(NetworkObjectData.OBJECT_TYPE_PLAYER, id, MAX_DATA_LENGTH);
data.putInt(x);
data.putInt(y);
//...
}

@Override
public void update(NetworkObjectData data) {
x = data.getInt();
y = data.getInt();
//...
}

That part was easy and elegant. The next difficult part is getting all of the NODs into a single byte array for transmission from the host to the clients. This brings me to the next part, which is the RealtimeNetworkHost and the RealtimeNetworkClient.

I put all of the hard stuff into these two classes. They manage connections and handle all of the tricky parts of the protocol. They are created by the activity and handed down to the thread. The can call back with status updates and drive the client/server updates.

When the host updates the client, it starts by writing a single header int (4 bytes), which is the number of NODs to expect. Each NOD has the exact same structure, 12 bytes for the header (object type, object id and data length) and then the data which matches the data length exactly. This makes it so that the client can read the header, then loop the number of times specified in the header, each time reading another 3 int header which tells it how much data to read for each NOD. It assembles the NODs back into an array and holds them until the main loop is ready to accept the update. The main loop passes in the World and it is updated with the contents of the NODs.

An example of how the protocol looks would be:

// Packet Header
Bytes 0-3 - (int) 3 // number of objects

// NOD 1
Bytes 4-7 - (int) 0 // object type 0 means World
Bytes 8-11 - (int) 0 // object id 0 means World
Bytes 12-15 - (int) 50 // The World's data is 50 bytes long
Bytes 16-65 - (byte[]) ... // World Data

// NOD 2
Bytes 66-69 - (int) 1 // object type 1 means Player
Bytes 70-73 - (int) 1 // object id 1
Bytes 74-77 - (int) 22 // Player data is 22 bytes long
Bytes 78-97 - (byte[]) ... // Player Data

... and so on.

The difficult part is determining when things have changed (AKA when do new objects get created and previous ones deleted?), which will be done using the IDs but is still a tedious task. Also just managing memory is hard. Since you really shouldn't be allocating any new memory during the game loop on an Android game, you will want to reuse the NODs and their byte arrays. This means using fixed-size arrays whenever possible.

I did get everything working today but the client update rate is currently horrible and I'm not yet reusing NODs or arrays. I'm also missing state information and haven't figured out how to manage the trail data. These will be the challenges over the next few days as I inch toward a polished multiplayer game that will really be one of the defining elements of this game.

Light Racer 2.0 - Days 45-52 - More Real-Time Multiplayer Coding

Android

The past 7 days of coding has possibly been the most frustrating in the entire project. The fact of the matter is that writing really good, efficient real-time multiplayer code is hard if you've never done it before. I spent days ironing out the kinks and ended up reworking the protocol more than once. There were several times where I just threw my hands up and walked away from the computer. Major hurdles included: having to ditch TCP because of latency, finally getting around to learning NIO but having NIO problems on Android. All in all it was a tough week but at the end of it, I have a game that's at least playable at 30-60fps over WiFi without too many problems.

I originally tried to implement the data protocol using TCP. I figured that since the players are always on a LAN because of WiFi, that they will always be low-latency and therefor TCP should work. I was wrong. The round-trip ACK/SYN/whatever that TCP uses to ensure delivery adds a noticable amount of latency. I got everything working with it but wasn't happy, so I switched to UDP.

I had always heard that Java's NIO was better but had never implemented anything in it. I didn't need the concurrency stuff since this is fairly simple and straightforward so fortunately I didn't have to deal with selectors at all. I switched to UDP and implemented it using NIO channels. I had a major performance problem using DatagramChannel.receive() because Android does some DNS operation of some sort and logs it. I think it was maybe just the logging but suffice to say that logging 60 times per second is enough to start screwing things up when running a game. I had to instead use reciprocal connections - both the client and server "connect" to each other so that they can use read() and write() instead of send() and receive(). No, UDP does not truly "connect" but it's how Java's Datagram NIO works. This means that I need a unique port for each client but since this is a 3 player max game, that's not a problem.

Once I switched to UDP, I had to implement a heartbeat from the clients so that the host could track lost connections and such. Overall it works satisfactory. I'm surprised by the network lag sometimes because I wouldn't think it would be an issue on a LAN but since this is my first Real-time MP protocol, I feel like I can cut myself a little slack.

One thing I REALLY like about NIO is the ByteBuffer class. It makes it extremely easy to write simple, small, fast protocols. The only thing it's missing is a 3-byte int, but those are rarely used so it's not a huge deal.

Most of the days were spent testing and tweaking through much trial and error. It was very frustrating. I did eventually get things ironed out well enough to where by the end of the day today, I was playing several multiplayer games in a row against my fiancee. She's currently pretty bad at the game (don't tell her I said that) but the testing went well and I was happy with the playability of the multiplayer. I think people are going to enjoy it.

Day 45 - What was done:

Optimized object fields and related netcode to use bytes and shorts where appropriate.
Changed network client from blocking update to interpolation between updates.
Fixed Tie-game result
Changed lose/win states into a single result state
Added scores to world
Changed state update code to work better for the net client

Day 46 - What was done:

Made RealtimeNetworkClient and Game Objects netcode more memory efficient
Fixed stability problems when quitting multiplayer - was causing major testing headaches, found that always using read timeouts is essential with android sockets.
Spent hours finding a thread synchronization issue stemming from cached net objects
Flattened out trail data and wrote a very efficient object model updater on the client side

Day 47 - What was done:

Refactored items to remove obvious code duplication
Implemented item netcode
Got items tested and working correctly w/very efficient code

Day 48 - What was done:

Implemented all netcode for all map objects
All levels tested and working
Decimator working but a little buggy

Day 49 - What was done:

Added variable-length data size for player and for MP packets
Now the multiplayer game shouldn't crash because of running out of space on the stream.
Show scores between rounds
Show final results
End game after correct # of rounds
Stated are synced up better

Here's how packets are laid out, assuming 1024 bytes available:

byte[] packet
-- Header
short packet length (2)
byte net objects (1)

-- Net Object
- Header (7)
byte obj type (1)
int obj id (4)
short data length (2)

-- World max data length (93)

-- item data length (5)

data length totals with header data for largest case scenario

world total = 100
items total (12 * 3) = 36
map objects max total (powerbox) = 67
187 before players = 837 for all players or
279 per player for 3 players or
418 per player for 2 players

It will take over 50 trail segments before increasing the packet size beyond 1024 bytes

Day 50 - What was done:

Tried using running average for host latency - didn't work well
Switched to NIO-based UDP for real time protocol

Day 51 - What was done:

Implemented much of the new menu styles
Switched to full use of ByteBuffers for real time multiplayer
Tried to add connection gained/lost detection

Day 52 - What was done:

Finished NIO UDP implementation
Fixed multiplayer sound issues
Fixed decimator
Got 3 player MP game working
Fixed client heartbeat
Fixed MP default input
Added MP host detection to main menu
Play tested several games with 2 and 3 players

Summary

3 player games work, though there are still a few bugs and it does sometimes get a little bit jittery and laggy. 2 player games are fairly smooth, though and since that's what most of the games are going to be, I'm happy with that for now. I just want to get the game finished since it's over a month delayed and I can come back to work on the multiplayer code later.

Here's a video of the multiplayer code at the end of Day 45

Things are MUCH better than that at day 52.

I haven't made a more recent video because it's really a pain to set up the camera, but I'll do one for 3-player action when Multiplayer is totally complete.

Light Racer 2.0 - Days 53-55 - Accessorizing the Game

Android

After becoming slightly demoralized at the number of hours it was requiring to get through this game, I decided to ask my brother to help out. He is in college right now for CS and we both thought it would be a good thing for us to work together on this. It turns out that it's been a great choice because he's really been doing a lot of great work and in turn, I'm getting more energized and motivated to finish this title off. The past 3 days have been spent working out the accessories of the game which include ending screens, opening screens, achievements, settings, pop-up notifications and other details. That's the vast majority of what remains of development, but I believe it's a very important part for having a well-polished product.

Day 53 - What was done:

- Start button is now disabled until at least one client is connected
- Fixed trail break on client (causes weirdness)
- Fixed item animation (not working well on client)
- Fixed MP start locations

Day 54 - What was done:

- Designed 3D racer for winning game screen
- Added status popups (connecting, connected, lost connection to player n, host quit)
- If no Wifi - pop up a message or add something to layout saying, "MAKE SURE WI-FI is connected!"
- Added MP game results at end of match (You won the match, You lost the match)

Day 55 - What was done:

- Polished up Multiplayer end-game flow
- Fixed memory issues
- Host game back button ends hosting and returns to main screen
- made screen stay on during game
- Speed trials gets submit score online dialog after level 10
- added default application preferences (sound, music, vibration, splash screen, show how to play)
- attached app prefs to settings menu option
- Made shared preferences for achievements
- Added achievements state infrastructure
- Added ending sequence state infrastructure

Summary

As you can see by the list, it has been a lot of little things that we've been working on. I'm only listing things that I've been doing but a lot of what we've done together has been design work to make my brother's job easier. The goal is to get the end game sequence, achievements, leaderboard, music and test plan all put together and working well in the next week and a half. I think that together, we may actually be able to do it.

Here's a video of how things are looking right now. I had a little problem with getting the Theme to work on the Dialog but it has since been fixed.

By the way - the best way I've found to have a general app Theme in Android and then to customize the Dialog's themes is to have a theme for each, one that extends the base theme, the other that extends the base dialog theme, and then to use the dialog constructor which takes a theme id and simply set your theme there. For instance, my MultiplayerDialog has a constructor which takes a context, but then calls super(context, R.styles.LightRacerDialogTheme);

Light Racer 2.0 - Days 56-60 - Achievements and Leaderboards

Android

The past several days of work have been focused on two things: Achievements and Leaderboards. We implemented an AchievementsCalculator that was called after every round and each multiplayer match. The achievements themselves are simply stored as a set of SharedPreferences. I decided to go that route because of how much simpler the implementation is than using an SQLLite Provider. I used a Provider to do the stats on Wuzzle and I believe it's simply overkill for storing 15 booleans. The leaderboard was a little more complex. Fortunately I had written most of the code once before when doing the Online Multiplayer code for Wuzzle so it was a matter of copy, paste and modify. The client uses REST/JSON to communicate with the leaderboard server which is Ruby on Rails deployed using Phusion Passenger on Apache.

Day 56 - what was done:

Added fields to player for achievements - collisionType, itemPickupCount, invincibleCount
Created ending sequence

Day 57 - What was done:

Added Check for achievements even on loss
Got domain and web server configured for BPG leaderboard server
Created SVN repository for leaderboard server
Created default project in Rails 2.3.2
Created partial leaderboard screens
Copied over much of the json/leaderboard code from Wuzzle

Day 58 - What was done:

Created the db migrations
Created the scaffolds
Customized the routes and controllers and implemented this_Week, this_month and all_time sql.

Day 59 - What was done:

Implemented score submissions on server
Copied lots of code from Wuzzle
Implemented leaderboard client code
Implemented leaderboard screens
Tied it all together
Deployed on server
Finished Achievements for multiplayer

Day 60 - What was done:

Made leaderboard pretty
Tested leaderboard score submissions
Made achievements pretty

Summary

The leaderboard and achievements add a certain level of depth to the game that just makes you want to keep playing it to try to win everything and beat other people. The technology was fairly simple and straight forward to implement. I'd recommend the Ruby on Rails with JSON combo to anyone looking to implement a quick and easy web service for their android app. I have a class I used for both Wuzzle and Light Racer called OnlineGateway that handles all of the calls to the server. I used an asynchronous mechanism, creating a thread for every call that calls back to a handler and delivers a message. If there is an exception, it delivers that, otherwise it delivers the result. This works very well with the exception of activity lifecycle, which is why I wrapped Wuzzle's online gateway in a service. Since Light Racer only submits scores and then retrieves leaderboard scores, I decided it wasn't intense enough to need a service.

I decided to have a running-leaderboard, that is, a leaderboard that isn't always dominated by the long-term high scores. There are several methods to do this, but I chose using time-periods. Light Racer's leaderboards have an all-time, this-month and this-week category. This was easy enough to do. Here's my code in Ruby on Rails to do it:

In the routes:

map.connect 'lightracer/speed_trial_scores/all_time.:format', :controller => 'lightracer_speed_trial_scores', :action => 'all_time'
map.connect 'lightracer/speed_trial_scores/this_week.:format', :controller => 'lightracer_speed_trial_scores', :action => 'this_week'
map.connect 'lightracer/speed_trial_scores/this_month.:format', :controller => 'lightracer_speed_trial_scores', :action => 'this_month'

In the speed trials controller:

# GET /lightracer/speed_trial_scores/this_week.json
def this_week
scores = LightracerSpeedTrialScore.find(:all, :conditions => "YEARWEEK(created_at) = YEARWEEK(CURDATE())", :order => "score DESC, speed DESC", :limit => 20);
respond_to do |format|
format.json { render :json => scores }
end
end

# GET /lightracer/speed_trial_scores/this_month.json
def this_month
scores = LightracerSpeedTrialScore.find(:all, :conditions => "YEAR(created_at) = YEAR(CURDATE()) AND MONTH(created_at) = MONTH(CURDATE())", :order => "score DESC, speed DESC", :limit => 20);
respond_to do |format|
format.json { render :json => scores }
end
end

# GET /lightracer/speed_trial_scores/all_time.json
def all_time
scores = LightracerSpeedTrialScore.find(:all, :order => "score DESC, speed DESC", :limit => 20);
respond_to do |format|
format.json { render :json => scores }
end
end

I made a video to show how things are going. In this video, I have 3 devices playing a multiplayer game. After the match, you can see that one of the phones has achieved something.

Light Racer 2.0 - Days 61-64 - Completion

Android

The last 4 days of work were some of the most intense. Finishing up a project always is. The majority of the work was completing all of the little things like music, sounds, preferences, menu options, layout and UI tweaks and vibration. What's really interesting about the last few days is how some of the most critical bugs tend to pop up right then. For instance, when we added a how-to-play dialog to the game just today (on the release day), it brought up a massive bug where the user couldn't input any control at all. I figured out why that was happening but am still having a few problems with that now and then. Perhaps someone could explain why a thread waiting on a synchronized block gets stuck waiting even though the mutex is bring locked and freed by another thread many times over? Even with that, we managed to get everything together and get the game onto the market!

Day 61 - What was done:

Clear Achievements menu option added
Settings menu option added to all activities
Fixed pausing

Day 62 - What was done:

Added landscape layouts for any screens that need text input.
Moved the submit score dialog into its own activity for speed trial score submission
Added splash screen with animations
Created "battery powered" voice and added to splash screen
Created new game icon - used my sloppy 3ds model and snippets from my graphic designer
Added music to the menu activities - created and used a MenuMusicPlayer
** show code for MenuMusicPlayer and give examples for cross-activity music
Added music to the main game - used an old song I wrote in 2004
Started optimizing for size - it's difficult because everything is already very small.

Day 63 - What was done:

Added music volume adjustments
Fixed music transitions for preferences screen and activity lifecycle
Fixed several bugs
Added how-to-play screen
Fixed CPU pegged / unresponsive controls problems sometimes when starting the game problem
Added vibrations
Fixed sound enable/disable in-game

Day 64 - What was done:

Added end game music
Added some tab icons for the leaderboard
Cleaned up some resources
Designed the Light Racer Elite Logo
Switched accounting over to Battery Powered Games
Created new project for Light Racer Elite
Copied all source over to LRE project
Tested a ton
Ripped out extras from LR adding buy Elite hooks in instead
Tested more
Release!

Summary

One of the things I'm really proud of is getting the inter-menu music working really well. I've been writing electronic music for years and while I haven't done any recently, I have a whole library of stuff that I've done that works really well for video games. I sifted through my collection and found a few loops that I thought would work well in the game. I wanted one loop for the menus, one for the main game and one for the end game screen. I edited them in Audacity, like always, and kept tweaking the data rates and options until I was able to get something that sounds somewhat ok into a tiny OGG.

If you've ever tried to implement music across activities that plays well with the multitasking of Android, you'll know what I mean when I say that it's a major pain. I found a good way of dealing with it and so I'm going to post the code that I use to do the music for Light Racer.

public class MusicManager {
private static final String TAG = "MusicManager";

public static final int MUSIC_PREVIOUS = -1;
public static final int MUSIC_MENU = 0;
public static final int MUSIC_GAME = 1;
public static final int MUSIC_END_GAME = 2;

private static HashMap players = new HashMap();
private static int currentMusic = -1;
private static int previousMusic = -1;

public static float getMusicVolume(Context context) {
String[] volumes = context.getResources().getStringArray(R.array.volume_values);
String volumeString = PreferenceManager.getDefaultSharedPreferences(context).getString(
context.getString(R.string.key_pref_music_volume), volumes[PREF_DEFAULT_MUSIC_VOLUME_ITEM]);
return new Float(volumeString).floatValue();
}

public static void start(Context context, int music) {
start(context, music, false);
}

public static void start(Context context, int music, boolean force) {
if (!force && currentMusic > -1) {
// already playing some music and not forced to change
return;
}
if (music == MUSIC_PREVIOUS) {
Log.d(TAG, "Using previous music [" + previousMusic + "]");
music = previousMusic;
}
if (currentMusic == music) {
// already playing this music
return;
}
if (currentMusic != -1) {
previousMusic = currentMusic;
Log.d(TAG, "Previous music was [" + previousMusic + "]");
// playing some other music, pause it and change
pause();
}
currentMusic = music;
Log.d(TAG, "Current music is now [" + currentMusic + "]");
MediaPlayer mp = players.get(music);
if (mp != null) {
if (!mp.isPlaying()) {
mp.start();
}
} else {
if (music == MUSIC_MENU) {
mp = MediaPlayer.create(context, R.raw.menu_music);
} else if (music == MUSIC_GAME) {
mp = MediaPlayer.create(context, R.raw.game_music);
} else if (music == MUSIC_END_GAME) {
mp = MediaPlayer.create(context, R.raw.end_game_music);
} else {
Log.e(TAG, "unsupported music number - " + music);
return;
}
players.put(music, mp);
float volume = getMusicVolume(context);
Log.d(TAG, "Setting music volume to " + volume);
mp.setVolume(volume, volume);
if (mp == null) {
Log.e(TAG, "player was not created successfully");
} else {
try {
mp.setLooping(true);
mp.start();
} catch (Exception e) {
Log.e(TAG, e.getMessage(), e);
}
}
}
}

public static void pause() {
Collection mps = players.values();
for (MediaPlayer p : mps) {
if (p.isPlaying()) {
p.pause();
}
}
// previousMusic should always be something valid
if (currentMusic != -1) {
previousMusic = currentMusic;
Log.d(TAG, "Previous music was [" + previousMusic + "]");
}
currentMusic = -1;
Log.d(TAG, "Current music is now [" + currentMusic + "]");
}

public static void updateVolumeFromPrefs(Context context) {
try {
float volume = getMusicVolume(context);
Log.d(TAG, "Setting music volume to " + volume);
Collection mps = players.values();
for (MediaPlayer p : mps) {
p.setVolume(volume, volume);
}
} catch (Exception e) {
Log.e(TAG, e.getMessage(), e);
}
}

public static void release() {
Log.d(TAG, "Releasing media players");
Collection mps = players.values();
for (MediaPlayer mp : mps) {
try {
if (mp != null) {
if (mp.isPlaying()) {
mp.stop();
}
mp.release();
}
} catch (Exception e) {
Log.e(TAG, e.getMessage(), e);
}
}
mps.clear();
if (currentMusic != -1) {
previousMusic = currentMusic;
Log.d(TAG, "Previous music was [" + previousMusic + "]");
}
currentMusic = -1;
Log.d(TAG, "Current music is now [" + currentMusic + "]");
}
}

To use this, each activity must have a boolean field called continueMusic or whatever you want to call it. For any activity that isn't the root activity (launcher), override onKeyDown and set continueMusic to true if the key is the "back" key. Also set continueMusic to true when launching any activities in which you want to keep playing this music in with no interruption.

Then the magic is in the onPause and onResume:

@Override
protected void onPause() {
super.onPause();
if (!continueMusic) {
MusicManager.pause();
}
}

@Override
protected void onResume() {
super.onResume();
continueMusic = false;
MusicManager.start(this, MusicManager.MUSIC_MENU);
}

See how that works? You simply opt-in on keeping the music playing for when the activity will be pausing. It works really well except for a little studder on orientation changes.

I can't believe Light Racer Elite is actually done. I've been working on it for so long now! I started putting together a product page here - http://www.rbgrn.net/lightracer - but that will be moved to the Battery Powered Games site as soon as it is ready.

If you haven't tried Light Racer yet, please do. I'd love to hear your feedback! If you'd like to support my development, please purchase a copy of Light Racer Elite or Wixel. I really appreciate the support!

The next few articles will deal with getting started in 3D development. It may be a few days until the next update as I have to tie up some loose ends with Light Racer and finish transitioning Wixel to Battery Powered Games.

Light Racer 2.0 - Deployment and User Acceptance

Android

The initial update (2.0.0) of Light Racer was very shakey. I'm not entirely surprised, though. When I put out a game or a new app, I like to watch for feedback or signs of acceptance over the first few days. With this update, I got a lot of negative feedback about how the update ruined the original game. I was confused by that because I specifically put in the time trial mode to try to follow up the original version. I had underestimated how much many people enjoyed the simplicity of Light Racer 1. The first update addressed that issue by adding in classic modes and response to the update has significantly improved.

If there's one thing that I understand about running a successful game development company, it is that you never want to betray or upset your loyal fans. Sometimes it is hard because you can think you know best but when you're writing games for other people and not just yourself, you have to go with what works for them. I was so proud of the campaign modes that I put in to version 2 that I forgot about why the game was popular in the first place. In the first v2 update, I put the easy, medium and hard modes up at the top of the menu and followed them with the time trials. I thought this was the way to go because those modes were what I was really wanting in the game and I had received much feedback from the first version asking for more "depth." I was wrong.

To a user who had been playing the original Light Racer for 6 months or so, this new menu was totally foreign. They were used to seeing a "vs 1 CPU" and "vs 2 CPU" option and that's it. Now they tried the game on Easy, thinking that it was the same game. Nope! It's a totally different game. It even looks different with the new background - which needs to be replaced in the next update. I think most people didn't even try speed trials because they were immediately upset with what they found in the new game. It wasn't what they expected.

I immediately put together an update which has a new menu structure. Now, prominently displayed at the top of the solo menu is "Classic vs 1 CPU" and "Classic vs 2 CPU" modes. I also added descriptions, and put the old green grid back on for those 2 and the speed trials mode. Response dramatically improved by those upgrading. I think I made the right choice by adding these back in and supporting the fans. After all, I'm not writing games for myself - they are for everyone.

I will be continuing with the updates over time to Light Racer and Light Racer Elite. I currently don't like how the multiplayer feels in LRE and would also like to address input pipeline problems with both. I'm planning on taking a look at these issues while working on LR3D and retrofitting the original games to use the new code.

I'd like to thank everyone who has purchased Light Racer Elite. Starting a new business is very difficult and expensive. Purchasing games you like, like Light Racer Elite and Wixel is what enables developers like myself to be able to continue developing new and better games.

Light Racer 3D - Days 1-2 - Learning OpenGL ES

Android

I feel good about the game engine that I've developed while working on Light Racer. The only big thing that will be different about Light Racer 3D is that instead of drawing 2D sprites, each object will need to be rendered as 3D. To me, this sounds very simple. I plan on modeling everything in the game in 3D Studio Max, developing custom bits of rendering code then merging the 3D world renderer with the existing code base and tweaking to perfection. I'm confident that this will work, yet I'm pretty bad with 3DS, I've never developed anything in OpenGL, much less ES, and I have no existing tools to help me on my way. There has been a lot of reading and experimenting done, but at the end of the day, I have a working scene and a working OBJ file importer that maps textures correctly.

Here are some of the resources I've found most helpful in getting started with OpenGL ES:

The OpenGL ES 1.1 Reference Manual
This intro/tutorial on OpenGL Coordinate systems
This collection of tutorials of various OpenGL functions
The Google IO Presentation - Writing Real-Time Games in Android

Starting an OpenGL ES project from scratch can be seemingly impossible if it's your first time. Most likely, you will just end up with a black screen. I started with an example from the API demos. I believe I used the rotating cube. I started learning by just messing around with the cube. I'd add more cubes, make some bigger and some smaller, move some around, change colors, change rotations, change camera angles, etc. These are things you can do just to learn how the different transformations affect the scene.

Once I started to get the hang of that, I figured that I could create an Arena object and render it. I copied the cube to Arena and worked out the difference of Geometry. I pass in the width, height and depth on the constructor and build geometry to match. I also had to flip around the order of indices to make the triangles for the faces because I wanted the inside faces showing, not the outside. I removed the top and voila, I have a working arena!

A Note on Fixed Point conversions - 1.0 in floating point = 1 << 16 in fixed point. You can easily convert from float to fixed point by multiplying by 0x10000, since that is 16 bits. Try to do everything you can in fixed point. Floats will be slower!

Applying textures:

This is where I had the most problems. I found so many tutorials on OpenGL that would show how to put in texture coordinates for every vertex but OpenGL ES doesn't allow for point-by-point definition of primitives. We, instead, must define an array of vertices, an array of texture coordinates and an array of indices. If you're not sure how this works, think of it like this:

A vertex is a 3 dimensional point in space (x,y,z). 3 vertices can make a triangle.
OpenGL ES draws triangles using the indices. Each index points to a vertex. The idea is that you can re-use vertexes for multiple faces (triangles).
Texture coordinates are mapped 1-to-1 with vertices, NOT indices. The value of a texture coordinate is 0 to 1 and should always be a floating point number. The number represents a percentage of distance, starting in the upper left at 0,0 down to 1.0, 1.0 in the lower right, of the texture image to be applied.

The problem is that when applying textures, you basically need to define the same vertex more than once if two different faces connected to it are to be textured starting at two different locations on the texture. My arena texture is like this. I ended up defining 4 vertices and 6 indices for each side. This allowed me to map a specific square of texture for each face, using 4 texture coordinates. It worked very well this way.

Here is the texture I used and here are some screenshots of how things currently look.

Basic arena texture (128x128)Basic arena texture (128x128) Rectangle Racer in 3DSRectangle Racer in 3DS First arena w/rectangle racerFirst arena w/rectangle racer

The first image is the actual texture that is being applied to both the arena and the rectangle racer. The second image is a screenshot of the rectangle in 3DStudio. The arena is 440 in size, so I will make the racer 30x8x8 so that it will be the right size and I won't have to scale in the game. The third is a screenshot of how the "game" currently looks. The arena is rendered with the racer rectangle sitting in the middle. You can see how that texture applies.

Importing OBJ Files

If you're not lost in 3D land at this point, you may be asking yourself, "How did you get that 3D Studio rectangle into Android OpenGL ES?" Great question. I exported the .obj file and wrote a tool for my game that can import the obj, aggregate the geometry and output a file that looks like this:

public class ModelData3D {
public int[] vertices;
public float[] tex;
public short[] indices;
public int vertexCount;
public void print() {
System.out.println("vertices=" + Arrays.toString(vertices));
System.out.println("tex=" + Arrays.toString(tex));
System.out.println("indices=" + Arrays.toString(indices));
}
}

Once you have the model data in those arrays, you can easily create byte buffers, set your texture, transformations, etc and render it using GL10.glDrawElements.

Here's the obj importer I wrote. Depending on what you use to export and what options you have set, you may need to tweak it. It's written specifically for the settings I've chosen to use for my models. It may not work right off the bat for you.

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.StringTokenizer;
public class ObjImporter {
private static final String SPACE = " ";
private static final String SLASH = "/";
public static ModelData3D importObj(InputStream in) throws Exception {
ModelData3D data = new ModelData3D();
ArrayList<VertexF> verticies = new ArrayList<VertexF>();
ArrayList<UVCoord> uvs = new ArrayList<UVCoord>();
ArrayList<Face> faces = new ArrayList<Face>();
// 1) read in verticies,
// 2) read in uvs
// 3) create faces which are verticies and uvs expanded
// 4) unroll faces into ModelData3D using sequential indicies
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringTokenizer st;
String line = reader.readLine();
System.out.println("Loading obj data");
while (line != null) {
st = new StringTokenizer(line, SPACE);
if (st.countTokens() > 1) {
String lineType = st.nextToken(SPACE);
if (lineType.equals("v")) {
// vertex
VertexF vert = new VertexF();
vert.x = Float.valueOf(st.nextToken());
vert.y = Float.valueOf(st.nextToken());
vert.z = Float.valueOf(st.nextToken());
verticies.add(vert);
} else if (lineType.equals("vt")) {
// texture mapping
UVCoord uv = new UVCoord();
uv.u = Float.valueOf(st.nextToken());
uv.v = Float.valueOf(st.nextToken());
uvs.add(uv);
} else if (lineType.equals("f")) {
// face
Face face = new Face();
face.v1 = verticies.get(Integer.valueOf(st.nextToken(SLASH).trim()) - 1);
face.uv1 = uvs.get(Integer.valueOf(st.nextToken(SPACE).substring(1)) - 1);
face.v2 = verticies.get(Integer.valueOf(st.nextToken(SLASH).trim()) - 1);
face.uv2 = uvs.get(Integer.valueOf(st.nextToken(SPACE).substring(1)) - 1);
face.v3 = verticies.get(Integer.valueOf(st.nextToken(SLASH).trim()) - 1);
face.uv3 = uvs.get(Integer.valueOf(st.nextToken(SPACE).substring(1)) - 1);
faces.add(face);
}
}
line = reader.readLine();
}
//printFaces(faces);
int facesSize = faces.size();
System.out.println(facesSize + " polys");
data.vertexCount = facesSize * 3;
data.vertices = new int[facesSize * 3 * 3];
data.tex = new float[facesSize * 3 * 2];
data.indices = new short[facesSize * 3];
for (int i = 0; i < facesSize; i++) {
Face face = faces.get(i);
data.vertices[i * 9] = toFP(face.v1.x);
data.vertices[i * 9 + 1] = toFP(face.v1.y);
data.vertices[i * 9 + 2] = toFP(face.v1.z);
data.vertices[i * 9 + 3] = toFP(face.v2.x);
data.vertices[i * 9 + 4] = toFP(face.v2.y);
data.vertices[i * 9 + 5] = toFP(face.v2.z);
data.vertices[i * 9 + 6] = toFP(face.v3.x);
data.vertices[i * 9 + 7] = toFP(face.v3.y);
data.vertices[i * 9 + 8] = toFP(face.v3.z);
data.tex[i * 6] = face.uv1.u;
data.tex[i * 6 + 1] = face.uv1.v;
data.tex[i * 6 + 2] = face.uv2.u;
data.tex[i * 6 + 3] = face.uv2.v;
data.tex[i * 6 + 4] = face.uv3.u;
data.tex[i * 6 + 5] = face.uv3.v;
data.indices[i * 3] = (short) (i * 3);
data.indices[i * 3 + 1] = (short) (i * 3 + 1);
data.indices[i * 3 + 2] = (short) (i * 3 + 2);
}
reader.close();
return data;
}
private static int toFP(float f) {
// normally you'd << 16 but um, can't do that with a float so we multiply by 16 bits.
return (int)((double)f * 0x10000);
}
private static void printFaces(ArrayList faces) {
for (Face f : faces) {
System.out.println("Face uv1 " + f.uv1.u + " " + f.uv1.v);
System.out.println("Face uv2 " + f.uv2.u + " " + f.uv2.v);
System.out.println("Face uv3 " + f.uv3.u + " " + f.uv3.v);
System.out.println("Face v1 " + f.v1.x + " " + f.v1.y + " " + f.v1.z);
System.out.println("Face v2 " + f.v2.x + " " + f.v2.y + " " + f.v2.z);
System.out.println("Face v3 " + f.v3.x + " " + f.v3.y + " " + f.v3.z);
}
}
private static class VertexF {
public float x;
public float y;
public float z;
}
private static class UVCoord {
public float u;
public float v;
}
private static class Face {
public UVCoord uv1, uv2, uv3;
public VertexF v1, v2, v3;
}
public static void main(String[] args) {
try {
ModelData3D data = importObj(new FileInputStream("racer.obj"));
data.print();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}

For more help working with .obj files, check out Roy Rigg's OBJ File Format page

Light Racer 3D - Days 3-4 - Textured Racer Model

Android

Every 3D game has a process defined for creating models, texturing and importing into the game. In these two days, I've come up with a process that works for Light Racer 3D and all of the games we will be developing in the future. This process will be a little different from project to project, depending on the requirements of the game and which tools you are using to create your art. I wanted to use Maya but was most familiar with 3D Studio Max so I went forward with it for Light Racer 3D.

Day 3 - What was done:

Added normals to obj import
Added groups to obj import
Created basic racer model of around 550 polys
Tested on G1 - Initial failure
Switched out code to use GLSurfaceView
Tested on G1 - 60FPS with arena and 3 racers all in view. (awesome!!)

Day 4 - What was done:

Optimized racer model
Created texture map for racer
Created basic texture for blue racer
Added Flip V to import for textures (texture coordinates were upside down)

Summary

The emulator runs OpenGL far slower than a G1. I was really upset on the night of day 3 because I couldn't get the app to run at all on the phone and on the emulator it was dipping down to around 10-15FPS, which basically indicated complete disaster. I originally set up this project about a year ago and used some much older EGL configuration code that came from the Android API Demos. It didn't work correctly on the device at all. Since I'm targeting Android 1.5 as the release platform, I can use the new GLSurfaceView class that they provide. I retrofitted my project to use that, which allowed for almost all of the gross initialization code to be removed. Now the "game" runs at 60FPS on the G1 with 3 Racers each with 550 polygons. I can't image that I'll ever need many more than 2000 polygons to do a mobile game so this is great news.

I spent all of day 4 optimizing the model by removing additional polys and faces. I also spent some time lining everything up very carefully because I didn't want to have to edit the geometry again. For you 3D studio users - you probably already know how to do all of this, but I'll outline the process simply as a matter of documentation for myself.

1) Create the model geometry. This is where you draw splines, extrude sides and faces, draw tubes, circles, or whatever else you want. Just make all of your new geometry and edit until you have it all looking the way you want.
2) Convert everything into an editable mesh and attach objects together into the correct groups.
3) Select all of the elements, go to materials and assign material 1.
4) Select the editable mesh and add an Unwrap UVW modifier
5) Clear the UVs and edit - creating a nice map.
6) Export the map as a template and edit, then save as a 512x512 or whatever size PNG.
7) Go back into the materials, edit material 1, choose a map for the diffuse, pick your PNG as the file. Also hit the little checkered box that shows the maps in the viewport - this makes working on your texture easier because all you need to do is hit reload when you've changed the file.
8) When everything looks good, export as an OBJ. Don't flip YZ like poser. Export normals and optimize everything.

Make copies of your MAX file or save your UVW after you've made it! It's too easy to accidentally destroy it and 3D Studio can only go back 10 or so steps of undo. Remember - if you change your geometry, you have to redo your UVW map. Make sure you've got the right geometry and that it's totally optimized before texturing.

I have a problem with the texture going in to OpenGL ES upside down. For whatever reason, 3DS's V coordinates and Android's OpenGL ES texture V coordinates are reversed. I changed my OBJ importer to flip them by doing one minus the coordinate, and all is well.

Here are some screenshots of where I'm at:

3 Racers with wrong texture3 Racers with wrong texture Low-Poly Racer in 3D StudioLow-Poly Racer in 3D Studio
Textured Racer in 3DSTextured Racer in 3DS 3 Blue Textured Racers In-Game3 Blue Textured Racers In-Game
Blue Racer TextureBlue Racer Texture

The racer model is final but the texture is not. Once I can see everything in-game while playing, I'll start working more on detailing textures.

Light Racer 3D - Days 5-7 - A Working Scene

Android

Days 5-7 have brought on a dramatic difference to the new game. It went from being a test bed for 3D objects and experimentation to an actual working scene. I laughed out loud when I first finished the merge of the 2D game engine with the new 3D rendering system because the problems were so bad but I was still able to see the racers and control them. It didn't take me much over an hour to fix up the issues and move the camera behind the player for that 3rd person following view. There are no trails, explosions or text yet but this will give you an idea of what the game will look like.

Day 5 - What was done:

Created Red and Yellow racer textures
Created basic item and power emitter meshes
Started implementing better multiple texture loading
Started implementing 2D labels

Day 6 - What was done:

Changed all code to fixed point (importer and renderable objects)
Got basic orthographic heads up display working with dynamic labels
Cleaned up much of the opengl code
Checked FPS - running around 40FPS, am slightly concerned about performance.
Started on trail segment renderer

Day 7 - What was done:

Refactored all rendering code to prepare for the big merge with the 2D game engine
Merged LRE code - deleted all 2D drawing code, commented much functionality out
Rigged up the renderer with the main thread, got basic racer rendering in
Changed camera to follow racer - will put in toggle for overview
Changed controls to left-right turning

Summary:
What I've discovered (which is not news to most people) is that the emulator can be very different than the target device. I recommend using a device for most or all Android 3D development. The videos for this entry are from the emulator and as such, are running at a slow framerate and have some visible issues.

The code merge was nearly as straightforward as I was hoping it would be. I ended up having to set up a synchronization system between the renderer thread and the main game thread. I wanted to hand snapshots of the world to the renderer but because of the dynamic nature of the trails and segments, I was afraid of memory allocations so I'm just going to try a system like this:

Game Engine Locks World
Game Engine Updates World
Game Engine Unlocks World
Renderer Locks World
Renderer Draws World
Renderer Unlocks World

Repeat.

It seems to work fairly well on the device so far. Its not ideal, as I'd like to be rendering the previous snapshot while ticking the next. I'll see what the final performance numbers are like and go from there.

Here are videos of Day 7 mid-day, and Day 7 end of day. What a difference!

Light Racer 3D - Days 8-9 - Core Gameplay Elements

Android

Light Racer 3D is an exciting project because every day of development adds substantial improvements to the game. The last 2 days have been no exception. Make sure to watch the video at the end of this post. It shows how totally playable the game is. If I were to stop right now, the game would be at least as good as a 3D counterpart to the original Light Racer. As it stands right now, I've got a list of about 20 things I'd like to do for this game to take it from cool to amazing. Because of memory allocation sensitivity on Android, I had to employ a few tricks to deal with the dynamic geometry of the trails. I also ran into a problem getting the explosions properly billboarded.

Day 8 - What was done:

Got text working
synced up game start with OGL init
Serialized ModelData3D into .s3d files - added utilities for developers
Finished all trail rendering including the "falling" trail

Day 9 - What was done:

Got billboard-hacked explosion in (had problems getting bb to work correctly)
Get score text working efficiently
Get level text working
Change touch input to be left-right zone
Added camera turn rate

Summary

OBJ file parsing was taking several seconds per file, especially on the racer model. This is because of string parsing, which was hard to do without allocating lots of strings. As you may or may not know, Android is very slow with many memory allocations so reducing those is key to a quick app. I wrote a little tool that parses the obj file and serializes the result to another file. I then added a little utility that reads that serialized file back into the same result. The game now just uses that serialized file and in my tests, it's been 7-8 times as fast. I know I can make something faster but this will do for now.

The trail geometry required a little hack that made it quick to work with. Each segment of trail is held in a class called a TrailSegment. The TrailSegment is simply a list of coordinates to make the path and some state information about the segment, such as, if it is active and how high it's currently standing. Since trail segments are modified, I added the direct buffers to them so that they can hold their own geometry. They have a flag indicating if their geometry needs to be updated, and when the trails are drawn for a player, if that flag is true, the geometry is updated before drawing happens. The last bit of trail, which is from the last turn to the current location of the player, reuses the same direct buffers for all players. The geometry is simple and always uses the same indices, so I simply update the 4 vertices for every draw for every player. It's quick and easy.

I spent about a day trying to get billboarding perfect for the explosion. If you aren't familiar with what billboarding is, imagine a piece of paper lying on the ground. If it's billboarded, no matter where you move to or what angle you look at, it's always facing you. That would be creepy in real life but for games, it's really nice because you can do things like put in a texture-animated explosion, like LR3D has. I'm still bad at linear algebra so I had problems getting the billboard to work correctly, but at the end of the day I figured out a quick hack that would make it work well enough. I'm happy with the hack because, while it's not perfect, it's much less CPU intensive than actual billboarding. My hack is simply to track the rotation and angle of the camera and counter-rotate the explosion quad to match. If the camera is at 30 degrees, the explosion sits at 60. If the camera rotates left, the explosion rotates right. This makes the explosion look fairly convincing from the majority of angles.

There is point sprite function in GL11, but I'm doing the whole game in GL10 to ensure compatibility amongst all devices.

If you're wondering how the nice camera rotation works, here's a bit of code for you:

switch (player.curDirection) {
case EAST: {
targetZRot = 90;
break;
}
case WEST: {
targetZRot = 270;
break;
}
case NORTH: {
targetZRot = 0;
break;
}
case SOUTH: {
targetZRot = 180;
break;
}
}
if (cameraZRotation != targetZRot) {
// rotate shortest way always
long targetMovementAmount = (renderFrameDelta * CAMERA_TURN_RATE) + zRotCarryOver;
int rotAmount = (int) targetMovementAmount / 1000;
zRotCarryOver = (int) (targetMovementAmount % 1000);
//Log.d(TAG, "currentZRot=" + cameraZRotation + ", targetZRot=" + targetZRot + ", amount=" + amount);
if (targetZRot < cameraZRotation) {
if (cameraZRotation - targetZRot > 180) {
cameraZRotation -= 360;
cameraZRotation += rotAmount;
} else {
cameraZRotation -= rotAmount;
if (cameraZRotation < targetZRot) {
cameraZRotation = targetZRot;
}
}
} else {
if (targetZRot - cameraZRotation > 180) {
cameraZRotation += 360;
cameraZRotation -= rotAmount;
} else {
cameraZRotation += rotAmount;
if (cameraZRotation > targetZRot) {
cameraZRotation = targetZRot;
}
}
}
}
if (cameraZRotation != 0) {
Matrix.rotateM(cameraMatrix, 0, cameraZRotation, 0, 0, 1);
}

And finally, here's a video of me playing the game on my G1.

Light Racer 3D - Days 10-11 - Camera work and modeling

Android

Turning 90 degreees in an instant is hard on human eyes. After the game was working, I decided that the camera needs to be active and quick but also needs to be limited to a certain amount of movement. I added 4 camera modes to the world renderer: Above, Behind player, Beside player and Spinning. I then added the same code that I use to control player movement to the camera. Now, for every frame rendered, the camera checks where it is supposed to be, then tries to move there at the rate that I defined. I also really liked the way that the original F-Zero for SNES did the overhead view and zoom down to behind the player, so I added a similar sort of effect to the level intros. Besides camera work, there was a lot of modeling and texturing happening. None of us are very good at 3D modelling but by the end of the 2 days, we had already learned several tricks. All it takes is an ADC2 deadline to make you learn fast!

Day 10 - What was done:

Moved camera to behind player
Made user perspective adjustable with up/down
Add triangle to front of trail (to match racer geometry better)
Added top/bottom colors to trails for shading (looks way better)
Zoom works
Added camera modes (follow behind player, rotate_z, follow player side)
Added clockwise spin at slow rate on results
Explosion depth issue kinda fixed, tilt corrected
Fixed up HUD
Items are textured and rendering correctly w/ a slow rotation.
Created white racer texture and showed blink

Day 11 - What was done:

Make a better texture for the grid w/closer lines - blue for campaign, green for speed trials
Added Tilt view control - looks 60 degrees to the side when phone is tilted 25 degrees or more. Tried variable tilt but had problems with speaker interference.
Modeled and textured the decimator
Added rendering code in for the decimator
Modeled and textured the emitter map object
Modeled and partially textured the laser cannon
Began experimenting with portal models

Summary

The laser cannon model took the longest of any. It is simple but is still the most complex thing in the game. Below is a photo of the cannon from before we had textured it at all. It just has a default wood grain on it. There is also the first attempt at a decimator. We ended up changing the texture but the geometry stayed the same (very simple - gotta keep that triangle count down for android!).

Wooden Laser CannonWooden Laser Cannon Decimator first textureDecimator first texture

For the lightning emitters we preshaded two textures. One is normal, the other is "on." Here is the "on" one:

Glowing Lightning EmitterGlowing Lightning Emitter

And finally here is a video of the game after day 10:

Light Racer 3D - Days 12-14 - A Frantic Race to the Finish

Android

Light Racer 3D is complete! So much happened in the past 3 days that it's very difficult to write about one thing. I had decided on about day 9 to try to get Light Racer 3D finished in time for the Android Developer's Contest 2. It was a very ambitious goal, especially because that game was only partially working with about 25% of its total content just a day before. For 6 days I slept only about 4 hours per night and only took breaks when I absolutely had to. The hard work paid off because on Monday, Aug 31, we finished the game, submitted a trial version to the ADC and put the full version online on the Android market. I'm happy with how the game turned out, despite our lack of real artistic capability. While there are still a few bugs which will be fixed in a future version, the overall feel is good and the game is really fun.

Day 12 - What was done:

Added code in to render power emitters
Modeled electricity
Rendered electricity for basic power lines
Modeled/textured the portal
Rendered power box
Rendered laser cannon
Rendered laser
Render portals
Made explosion rendering much quicker using 512x512 tiled texture for all frames of animation
Rendered BigExplosion, spin around during them then view from side and drive off.
Added loading screen
Fixed resource releasing issues
Shaded racer textures and reduced size to 256
Changed decimator texture

Day 13 - What was done:

Stopped screen dimming on game activity
Fixed bug w/only human player losing
handles activity lifecycle changes better (currently getting black screen)
Created new logo
Added LR3D to leaderboard server
Updated OnlineGateway to use LR3D leaderboard
Updated instructions
Fixed bugs in multiplayer, tested all configurations
Limited game to 50 speeds.
Switched soundpool to actually stop looping streams and no longer re-init (was hacked for Android 1.0/1.1)
Configured levels for campaign modes
Fixed performance issues with Medium and Hard AI
Added tilt-view as a preference with a warning
Tested leaderboard server for both LRE and LR3D
Added a little depth to frustum (looked weird when blacked out triangles on back wall on rotate)
Designed new main menu logo and background
Designed new app icon
Changed all menus to landscape

Day 14 - What was done:

Changed button border colors and button text color to match new aesthetics
Put in New Music
Finished custom backgrounds for result screens
Put in yet another new icon
Created trial version of the game for ADC2
Did final tests of both the regular and trial version
Deployed!

Summary

I apologize for not documenting these days better. We were in such a hurry and were totally sleep deprived so writing about the project was just not a priority. We weren't sure if we were going to have time to do all new menu graphics or music but by Monday morning we were done with all critical components so we decided to go for it. The music is all original by me, written a few years ago. My brother and I worked on the menu graphics for a few hours and he came up with the idea for the app icon. He said, "Make the racer coming out of the portal," so that's what it is.

The electricity is 7 models. I made the models, put each one as an object, which translated to a group in the obj file, so they can all be in one file. They are all centered on 0,0 but are at the same Z as the emitter tops so that makes it easy to render because I only have to transform them to the same x,y.

here's how all of the electricity models look when rendered at the same time:

Power Model MessPower Model Mess

I also had to do something really quick to make the laser work because there wasn't time to do anything right. The model has 3 objects, a base, cannon and laser. The renderer transforms to the base location, renders the base, then rotates and renders the cannon and the laser if the laser is on. Here's how the model looks in the tool:

Laser Cannon FiringLaser Cannon Firing

Once we got all of that working, the portals were fairly easy. Figuring out what to make them look like was the hard part but Dan came up with an idea to make them look like there are portal emitters floating over them and the portals themselves are a weird plasma looking thing. I think it worked out really well. I like how they look and it's fun to drive in to them and come out somewhere else.

Finally there were the menu graphics. Here's the first concept for the main menu:

Main Menu Concept 1Main Menu Concept 1

But that didn't look right. I messed around with perspectives, then settled on one that goes up toward the top center but sits squared up more with the buttons. That still didn't look right and I didn't like the gap on the upper left so I came up with an idea for putting the racer on there. Here's the next iteration:

Main Menu Trial 2Main Menu Trial 2

The perspective didn't seem right and the colors were off so once that was corrected, we ended up with this - which is the final menu that is in the game now:

Main Menu FinalMain Menu Final

Here's a video of the first 6 levels of an easy campaign and then 6 or so of speed mode.

Thanks to everyone for your support. Game development is hard and doesn't pay well. We do it because we love making games. I hope you try and enjoy Light Racer 3D. It will be in the ADC2 so if you like it, please vote accordingly!

We'd also love your feedback. We had no time to do any kind of a testing process so there may be some parts of that game that people just don't like. It's hard to please everyone but we try so if you send us your feedback and we get an overwhelming response in one direction or another, we'll make adjustments for an update.

Update - 10-16-2009:

6 weeks after the 1.0 release, we've finally finished version 1.2. Details can be found here - http://www.batterypoweredgames.com/content/50-light-racer-3d-v12-released

Here's the v1.2 demo video.