Making a Simple 2600 Game
Creating games is not the easiest of undertakings. Doing so in 6502 assembly language is much harder. One technique that is often used is to write pseudo code before writing the real code. With assembly language, however, the pseudo code is pretty similar to what you would write in a higher level language. For that reason, I will often work out the more difficult parts of a problem using a higher level language. As it is also nice to have a game that is playable in a browser, using a language like JavaScript for prototyping a game before writing the real game makes some sense. To aid in doing that, I have created a simple TIA library that lets you work in a similar fashion to what you would do on 2600 hardware.
I have written a simple game that demonstrates how this works. As it is written in JavaScript using the simple TIA simulation it is much easier to understand what is going on. Play Game Demo
The full game code for this game can be found by looking at the source for this page or by downloading my TIALib0_01.zip file.
The initialization section just sets up variables for the game so will
not be described here. The main loop of the game starts by calling my
rendering routine. As the position of the sprites do not change mid-stream
for this game, the current positions of the sprite are set at the top of
display.
scanline.setPlayer0SpriteQuick(1, false, 0, enemyX);
scanline.setPlayer1SpriteQuick(1, false, 0, playerX);
scanline.setMissile0Quick(1, false, enemyShotX);
scanline.setMissile1Quick(1, false, playerShotX);
We next run through all the scan lines, using simple logic to determine
what to draw. When porting to assembly language, in order to keep the number
of cycles down, the display code might break the display into three sections.
The first where we know we are drawing the enemy so just fill in it's data
and draw the player shot if in that range. The second section would be the
middle of the screen and just draw the missiles if visible. The bottom range
would always draw the player and the enemy missle if it is visible.
// loop through the scan-lines
for (var cntr = 0; cntr < 192; ++cntr) {
var temp;
// player rendering
if ( (cntr >= PLAYER_START_Y) && (cntr <= PLAYER_END_Y) ) {
temp = Math.floor((cntr - PLAYER_START_Y) / 2)
if (playerDead) {
scanline.setPlayer1SpriteData(DEAD_SPRITE[temp]);
} else {
scanline.setPlayer1SpriteData(PLAYER_SPRITE[temp]);
}
} else {
scanline.setPlayer1SpriteData(0);
}
// enemy rendering
if ( (cntr >= ENEMY_START_Y) && (cntr <= ENEMY_END_Y) ) {
temp = Math.floor((cntr - ENEMY_START_Y) / 2)
if (enemyDead) {
scanline.setPlayer0SpriteData(DEAD_SPRITE[temp]);
} else {
scanline.setPlayer0SpriteData(PLAYER_SPRITE[temp]);
}
} else {
scanline.setPlayer0SpriteData(0);
}
// bullet
var showMissile1 = ((cntr >= playerShotY) && (cntr <= (playerShotY+8)));
var showMissile0 = ((cntr >= enemyShotY) && (cntr <= (enemyShotY+8)));
scanline.enableMissiles(showMissile0, showMissile1);
// api routine to actually draw line to screen
scanline.renderToCanvas(ctx, 0,cntr*2,4,2);
Normally the game logic will be placed both before and after the rendering routines (as there are large blocks of cycles available in both sections) but for simplicity I am having this code as a single block. When actually writing the assembly language this code may end up being split into two chunks if it is too large to run in either the syncing section or the overdraw section.
The game has four states, with the appropriate code being selected (via a switch statement in JavaScript, but using a series of CMP and branching in assembly language.
The waiting for game state simply does nothing until the fire button is
pressed.
case WAITING_FOR_GAME :
if (firing)
gameState = STARTING_GAME;
break;
Starting game simply waits for the button pressed above to be released
and then sets the game to its starting state.
case STARTING_GAME :
if (firing == false) {
enemyX = PLAYER_X_START;
enemyShotY = SHOT_INACTIVE_Y;
playerX = PLAYER_X_START
playerShotY = SHOT_INACTIVE_Y;
enemyDead = false;
playerDead = false;
gameState = PLAYING_GAME;
}
break;
The most complex state is playing game. It first adjusts the player position based on the joystick input. If the player's bullet is active, it moves the bullet and checks if it hits the enemy which ends the game, otherwise it checks to see if the player is pressing the fire button in which case it launches the bullet above the player. Once the player work is done, it is time for the enemy logic.
The enemy simply moves toward a target spot. If it has reached that
target spot it sees if it can fire and does so if it can otherwise just
waits. The Enemy bullet is moved and checked to see if it hits the
player in which case it ends the game.
case PLAYING_GAME:
//adjust player position based on keys being pressed
if (movingLeft)
playerX = Math.max(1, playerX - 1);
if (movingRight)
playerX = Math.min(160-8, playerX + 1);
// player bullet movement
if (playerShotY < SHOT_INACTIVE_Y) {
playerShotY -= 4;
if (playerShotY < 0)
playerShotY = SHOT_INACTIVE_Y;
if ((playerShotY >= ENEMY_START_Y) && (playerShotY <= ENEMY_END_Y)
&& (playerShotX >= enemyX) && (playerShotX < (enemyX+8))) {
enemyDead = true;
playerShotY = SHOT_INACTIVE_Y;
gameState = GAME_OVER;
}
//else if player pressing shoot button
} else if (firing == true) {
// set player shot to just above player position
playerShotY = PLAYER_START_Y - 8;
playerShotX = playerX + 4;
}
// Enemy AI
if (enemyX == enemyTarget) {
if (enemyShotY >= SHOT_INACTIVE_Y) {
enemyShotY = ENEMY_END_Y;
enemyShotX = enemyX + 4;
enemyTarget = Math.floor(Math.random() * 152);
}
} else {
if (enemyX < enemyTarget)
++enemyX;
else
--enemyX;
}
// Enemy shot hanling
if (enemyShotY < SHOT_INACTIVE_Y) {
enemyShotY += 4;
if ((enemyShotY >= PLAYER_START_Y) && (enemyShotY <= PLAYER_END_Y) &&
(enemyShotX > playerX) && (enemyShotX < (playerX+8))) {
playerDead = true;
gameState = GAME_OVER;
enemyShotY = SHOT_INACTIVE_Y;
}
}
break;
Finally, game over simply waits until the fire button is released in the
case where the player was holding down the fire button when the game ended.
case GAME_OVER:
if (firing == false)
gameState = WAITING_FOR_GAME;
break;
And that is all there is to a simple game. Converting the above game to assembly language will be interesting and I will post additional articles once that has been done.