Carving the A-10 Thunderbolt II game into 10 stages of development, I explained the pieces over a 3 hour period. In hindsight, that example was too complicated for an introduction. Or, at the very least, it is too specialized, since most games don’t handle the ground in a way that collapses under explosions.
That A-10 project involves some performance optimization hacks, rudimentary AI, some trigonometry and vector geometry – things that don’t need to be the first topics considered for an introductory session.
This is slightly more complicated than it looks.
In light of that, I’m putting the blog post version of that talk on hold, and instead I created a platformer game last night to use as an example. It’s simple enough that comments in the code are (hopefully?) enough to handle most of the explanation.
The platformer uses a grid for the ground and collision detection, which is a very common method. The character is a side view, jumping, animated character, which is a much more typical player avatar. The goal of the game is to collect spinning coins, without falling into or bumping dangerous areas, which is a traditional objective. No trigonometry is needed, and the only vector geometry used to add x and y components together to move the character. No speed hacks, no AI, and the player is the only thing that moves.
Source Code Link
With the explanatory comments in the source, anyone with conceptual and fundamental programming familiarity should be able to make sense of it, or at least well enough to make changes. That second link, on Game Programming Fundamentals, may be particularly helpful if you’re new to reading, modifying, and writing code.
As a few helpful hints to get started: Platformer.pde is the primary file, setup() in that file happens automatically when the program starts, and draw() in that file happens every frame (24 times per second).
If you run into questions trying to make sense of it though, please let me know! Others are probably hitting the same dead end, so by asking me a question you may also be doing a favor for a bunch of strangers.
Things to try with the source, to better understand how it works:
- Remove my name from the top. (Leave Kevin’s somewhere visible, until you change the music.)
- Change the color of the coins.
- Make the coins spin faster.
- Change the level layout.
- The light grid lines help visually connect the level in-game to the 2D array that fills it, but they don’t look nice. Remove those lines.
- Extend the horizontal length of the world. (Remember to update both the number for GRID_UNITS_WIDE as well as the start_Grid in World.pde)
- Extend the vertical height of the world, adding vertical camera movement to follow the player up/down. (Base it on how the horizontal camera movement is handled.)
- Draw a new character to replace the stickman (look in the data folder). Give the character additional animation frames so that running looks smoother.
- Make your own coin and jump sound effects to replace mine (also in the data folder). Add and implement new sound effects for bumping into red killblocks and collecting the last coin.
- Rather than drawing tiles as solid squares, create new tileable gif/jpg images of the correct size and use those instead to render the world.
- Find some suitable replacement music. Swap it in, then remove Kevin’s music credit from the top.
- The instant reset upon bumping into a red killblock is jarring. Add a “Game Over” screen with new text asking the user to press R to reset the game, and adjust keyboard input to handle this.
- Add a second player character, controlled by W/A/S/D as arrows. Design and implement a solution for dealing with camera position. (Average between player positions? Move player 2 to where player 1 is if that character leaves player 1′s screen? Change the level render routine to support split-screen?)
- Feeling ambitious and bored? Add moving enemies.
Visualizing the Level Grid
From the earlier article about Math for Videogame Development, here’s an illustration about 2D arrays being used for collision detection and level rendering:
That same method is being used here in World.pde – a 2D array contains numbers, and in that same class we’ve stored that, for example, 1 is the number used for a solid tile (TILE_SOLID). Details of the code are a bit different, since Processing isn’t the language used in that illustrated example, but it’s similar and the pattern is consistent.
Here are the tile types we use in the Platformer game:
static final int TILE_EMPTY = 0; static final int TILE_SOLID = 1; static final int TILE_COIN = 2; static final int TILE_KILLBLOCK = 3; static final int TILE_START = 4;
When drawing the grid each frame, code iterates over every tile in the grid, and checks if the number there is equal to TILE_SOLID (meaning there’s a 1 in the grid there), and if so, draws a black square to represent a solid section of wall. To check if the player has moved inside a tile that’s solid, we divide the player’s coordinates by the pixel dimensions of each tile, to then check whether the value for that part of the grid is TILE_SOLID – which would mean the player bumped into or is standing on a solid piece.
Visualizing the Collision Detection
It’s a simplification in the world grid section above to say that we detect collision by checking the player’s “position” – we actually need to check six positions, of the sort shown in the image above. One for the top, one for the bottom, two for the left side (one high, one low), two for the right side (one high, one low).
In the code those extra positions can be found in checkForWallBumping() of the Player.pde file, and are referred to as topSide, position (we’re keeping the player’s coordinate at the center bottom), leftSideHigh, leftSideLow, rightSideHigh, rightSideLow.
If the point we check on top goes inside a solid block, we move the player downward so that the top point is just below the block it bumped into. If either right point goes inside a solid block, we move the player left until the offending point is just left of the block it bumped into, and so on. By treating the player as a hexagon, we’re able to keep the player from standing half inside a wall, going halfway through the ceiling, squeezing through a waist-high tunnel that’s too short, etc. Those kind of weird behaviors would happen if we instead treated the player’s character as a single point in space.
Another benefit of detecting player collision using six points in that hexagon configuration is that if the player is jumping horizontally and the feet hit a corner, the player is automatically bumped up onto the surface; if the player falling vertically hits a corner off-center, or steps off a ledge, the player slides off away from the wall. (Try this! It feels much better than it would if the player behaved as a boxy rectangle.)
This 6-point method is more complicated than how coins are collected, which is done in the checkForCoinGetting() function of that same file. For coins, the code just checks whether a point at the center of the player (half the player’s height above the foot origin point “position”) is inside a tile that is of type TILE_COIN – which if it is, then sets that tile’s value to TILE_EMPTY, triggers a coin collection sound, and adds one to the player’s coin counter.
Processing is a programming language and development environment from MIT’s Media Lab. It’s primarily used for real-time image effects, interactive prototypes, and research projects. It’s also suitable for learning introductory videogame development.
Processing for Learning and Prototyping
Processing was designed to make learning programming more straightforward. For that, Processing is very successful.
In particular, Processing provides an easy environment to learn programming that does not (as many introductory languages and environments do) dilute or change the type of programming learned. Meanwhile, in contrast to more advanced platforms, it entirely removes any hassle or troubleshooting involved in setting up the environment to get code running – no libraries to recompile, no compiler settings, no mysterious project settings or makefiles to grapple with.
Much of what’s learned in Processing in terms of code structuring carries over seamlessly into writing C/C++ or ActionScript 3 code. Although moving on to those full featured, industrial languages and platform configurations involve some additional conventions and quirks to be picked up, that sort of material can be learned later atop what’s brought in from programming experience from Processing.
High Performance Games in Processing
Even though Processing isn’t the best choice for making videogames, performance isn’t the issue. Processing can be used to build computationally intensive web games.
Check out the per-pixel, real-time terrain crumbling and particles in A-10 Thunderbolt II.
Speaking of which: I’ll still be finishing the write up about how that A-10 example works [update: finished and available here]. In the meantime, experimenting with changes to this Platformer example can provide a head start on making sense out of the A-10 source, which will be available as soon as I complete its corresponding article sometime soon.
Looking for a more advanced collision detection example? Check out 2D Platformer Advanced Collision Detection!