Hello World

From Uzebox Wiki
Jump to navigation Jump to search

Hello, World (Uzebox style)

If you're just looking for how to setup AVR Studio to build a typical Hello, World application and instructions for how to program it to your Uzebox, then this tutorial is probably more suited to you.

If you're new to the Uzebox or you're just unsure how to get started, this tutorial will guide you through a simple game from start to finish. By the end, you should feel confident with each step required to build a retro game for the Uzebox from scratch and can start working on your favorite retro title conversion or even a brand new concept. You'll also have a fully functional Space Invaders clone and almost half the microchip free for expansion, should you wish to release Super Space Invaders.

The information in this tutorial refers to Video Mode 3. The Uzebox has numerous video modes that accommodate many different styles of games, and you can always create a new mode if those available don't quite meet your needs. You can read more about the currently available video modes here. We will be using GIMP for image manipulation and Tile Studio for converting our images into code for inclusion in our AVR Studio project. There are other tools available for these tasks, and many people in the Uzebox community would be only too happy to guide you as to their availability and usage.

Full, commented source code for Space Invaders is available here.

Source code, Tile Studio projects and artwork package is available here.

Graphics Assets

Preparing your graphical assets is typically a safe first move when considering a new game for the Uzebox. The Uzebox begins with very limited program space and we must squeeze all of our graphics and code into 61.4k bytes (or 64k if you don't plan on supporting the bootloader). So your main concern at the beginning is assessing the viability of your game for the Uzebox and deciding whether any compromises will be necessary and whether you are content to make them.

Tiles and Sprites

Before considering our Space Invaders graphics, let's look at the basic building blocks that the Uzebox affords us, and their restrictions. In order to support higher resolutions, mode 3 paritions the screen into groups of 8x8 pixels that are referred to as tiles. This allows for a 240x224 pixel resolution while only having to process 30x28 tiles. Tiles form your game's background and are typically static in nature. Tiles are also restricted to being drawn at the 30x28 precision level.

Sprites are your game's actors and dynamic elements. Sprites are also defined as 8x8 pixel blocks, but can be positioned with pixel-level precision. Within the Uzebox community, the word sprite is usually used to refer to both individual 8x8 pixel sprites and to a grouping of 8x8 pixel sprites that form a character (such as mario). This can be confusing to newcomers, but you should be aware that whenever the term is mentioned in Uzebox specs, it is referrring to the 8x8 pixel version.

Mario 2x5 mega-sprite

Here we see a mario sprite that is actually a 2x5 mega-sprite. When counting your runtime sprite limit, this will add 10 to it, rather than 1. So we can see that sprites should be used sparingly. Also note that I refer to this as the runtime limit as you may have up to 256 8x8 pixel sprites in your game, but far fewer on screen at once (roughly 20 sprites for mode 3). Consult the community forum for more information regarding RAM_TILE requirements for mode 3 sprites - we'll ignore RAM_TILES for now.

One final difference between sprites and tiles: sprites may have transparent regions that will not overwrite overlapping sprites or tiles. The value for a transparent pixel within a sprite is 0xFE. So wherever this value appears in your sprites, the background tiles (or underlying sprites) will show through.


Space Invaders Tiles

If you're anything like me, your art skills peaked at age 4 (and not in the Akiane age 4 sense). Luckily, there's plenty of art assets on the interweb for these retro games, and that's what we'll use here. Here's the original tileset upon which Space Invaders is based.

Expensive Space Invaders Tileset

This result is 350+ tiles, and is clearly out of the question. Yes, you can blow your tile budget very quickly with some poor choices. At the same time, the Uzebox can support games with hundreds of screens of very varied data as long as a few simple guidelines are followed.


1. Aim for re-use. Each tile's relative cost is inversely proportional to how often it appears within the game. If a tile is only used once or twice, ask yourself if it is really necessary or whether it could be approximated with a more generic tile or combination of tiles.

2. Align patterns within 8x8 squares. The below is an obvious example, but they can be far more subtle in complex images. The top cross costs 1 tile out of your 256 tile budget, whereas the bottom cross costs a staggering 12 tiles for the same effect. It doesn't take long for these inefficiencies to eat your entire tile budget.

Inefficient tile creation

3. Create your title screen from tiles that were used throughout your game. B.C. Dash has a fairly busy title screen, but costs practically nothing because each tile is extracted from those used to build the game world.


The revised version of our tiles removes many of the tiles that will only be used once and that provide minimal impact for their relative costs, coming in at 239 tiles. Typically, I would remove much more of the lunar landscape details and force the "Space Invaders" text to better align along 8x8 regions, but I did not need the extra tiles for other art assets in this case, so I used them liberally to achieve better presentation.

Cheaper Space Invaders Tileset

Space Invaders Sprites

Sprites usually cost a lot due to requiring animations. When your mario sprite uses up 10 sprites of your 256 sprite budget, you must consider that if his walking animation has 5 frames, mario has already taken a fifth of your budget without jumping or performing a variety of other actions. This is why it's generally advisable to keep your mega-sprites small. Any more than 6 sprites to a mega-sprite and it starts to get expensive (as it multiplies over the animation frames for every action). Note that you can flip sprites along the x-axis at runtime in mode 3 in order to save a lot of space.

Space Invaders has potential to be unsuitable for the Uzebox if one chooses to use sprites for the invaders - there's just too many on-screen at once. To avoid this problem, we will use tiles for the invaders and move them in half-tile increments. What's that you say...half-tile increments? I thought tiles had to be aligned within an 8x8 pixel grid? To solve this problem, we create 4 different versions of the invading aliens and keep track of their 4x4 pixel movement behind the scenes, displaying the appropriate tile pattern for their current positioning.


Space Invaders movement patterns

With the invaders accounted for through tiles, we only need sprites to represent the player's ship, the ufo and projectiles. Animations are restricted to the exploding player ship and to the invaders' lightning bolt projectile. Below is the very simple Space Invaders spritesheet.

Space Invaders Spriteset

GIMP

You may choose to edit your images with whatever tool you find most effective. For this tutorial, we will be using The GNU Image Manipulation Program. One of the first steps is to ensure we're using the correct colors for the Uzebox. Uzebox community member Jhysaun has details on how to setup the Uzebox palette in GIMP [here]. Ensure you uncheck "Remove unsued colors from colormap" if you want to be able to add other Uzebox palette colors in the future (ie otherwise you are restricted to whatever colors are currently used within your image even if future additions use new colors that are within the Uzebox palette range). If you choose a "Color dithering" other than "None", GIMP will try to approximate out-of-range colors via a dithering algorithm. With none, the closest Uzebox color will simply replace the out-of-range colors. With that done, we can be sure that the colors we see will be displayed similarly on the hardware.

Secondly, it can be helpful to turn on the grid to assist in aligning our tiles to an 8x8 pixel grid. This can be done through View->Show Grid. You can lock movement to the grid via View->Snap to Grid. If your grid is not set properly, you can do so via Image->Configure Grid. You can set the default grid via the GIMP main window: File->Preferences->Default Grid.

Make sure you have all tiles that will be used within your game represented at least once on the image you create. One method is to draw each image or map on the image as has been done in the image that appears earlier in this tutorial. Tile Studio will strip out any duplicates. At this point, you can call your tiles done and move on to importing them into Tile Studio. This is what I usually do, but recreating the images in Tile Studio can be a little more difficult this way, because the image is converted to a single-dimensional strip of tiles. Jhysaun has a method to get around this issue if it bothers you.

A similiar process can be used for your sprites with one caveat - make sure your transparent regions are 0xFE. If you're ensure which color this is, consider that 0x00 is pure black and 0xFF is pure white. You can access the GIMP Palette Editor via the main window's File->Dialogs->Palettes menu. Scrolling down to the Uzebox palette and double-clicking it's image will bring up the palette window that allows you to choose which color the brush should use. 0xFE is the extremely light aqua second from the right on the bottom row.

Now we're read for Tile Studio.

Tile Studio

If you don't like Tile Studio, or don't have access to it, Uzebox community member Pragma has created a Python script for generating tile data here.

For the rest of us, Uzebox community member Clay has made a fine two part Tile Studio Tutorial: Part 1 and Part 2.

If you open up the Space Invaders tiles.tsp file, you can see all the maps that were created. I tend to make my first map a simple copy of the entire tileset. This ensures that the generated code maintains the tile order from my tileset image. I placed a preceding black line because I wanted my first tile to be all black. The reason for this is that ClearVram uses the first tile to fill the entire screen and black is ideal for Space Invaders. We will simply cut this map out of the generated code as it is not needed within the game. The sprites.tsp file demonstrates the process for sprites. Note that I place animations all on the one map - this is not standard practice, but is one method of storing the maps for animations.

You'll also note that the script generates an extra tile at the end of the tileset. Feel free to remove this tile as it is not used and only takes up space. Note that for the sprites, I added a transparent tile as the first map. This used to be a requirement in earlier kernel versions, but may not be required anymore. I leave it here for compatibility.

Code

The Space Invaders source code has been commented, but here I will explain some of the typical Uzebox operations and some of the peculiarities of the Space Invaders implementation. The Uzebox API can be accessed here.

Kernel Initialization

There a three main areas that must be addressed for the kernel to play nice with your project.


1. The Makefile. In AVR Studio, select Project->Configuration Options. Check the box "Use External Makefile". Click the corresponding "..." button and select your project's Makefile.

The easiest option for your own projects is to re-use the Space Invaders Makefile with minor adjustments for your game. The only change required will be to search for the line "GAME= SpaceInvaders" and change it to match the name of your game. This name will be used for output files, so make sure it contains only valid filename chracters.

If your game requires more concurrently displaying sprites than Space Invaders, you may need to increase the values of the KERNEL_OPTIONS:

-DMAX_SPRITES=7 -DRAM_TILES_COUNT=14

See this topic for an idea of how to calculate how many ram tiles are required for your given sprite count.

If your game has more than one source file (other than the kernel files), you should add them under the "## Compile game sources" section of the Makefile. Finally be sure that you have informed the compiler where to find the kernel directory. Space Invaders places the kernel directory one directory level above the location of the Makefile, and indicates this as so:

KERNEL_DIR = ../kernel

You may need to alter this if you choose to store the kernel in a central location and reference it from multiple projects.


2. Include the uzebox header in your source files:

#include <uzebox.h>


3. Tell the kernel where your game's resources can be found.

Working with Tiles and Sprites

Firstly, don't forget to include your art assets in your game code. Here's Space Invaders' listing:

/****************************************
*                Data	                *
****************************************/
// Tiles
#include "data/tiles.pic.inc"
#include "data/tiles.map.inc"
// Sprites
#include "data/sprites.pic.inc"
#include "data/sprites.map.inc"
// Music/sfx
#include "data/patches.inc"

Drawing

There's really only one function you need to know about to manipulate tiles within your game: SetTile. This function sets the tile in the 30x28 display array (actually 32x32, but we're not scrolling so we can ignore this) located x tiles from the left on the y'th row from the top. The Fill convenience function will set a rectangular region of the screen to the same tile. The tile component of the function refers to the position of the tile within the tileset array output by Tile Studio.

You can directly access the "sprites" array that the kernel references (as Space Invaders does a couple of times) or you can use the kernel's MapSprite function. To set the mario mega-sprite as the first sprite referenced by the sprite index 0, we would do the following:

// This would be one of the outputs from Tile Studio
const char mapMario[2 * 5 + 2] PROGMEM = {
   2, 5, // Width/height that MapSprite reads to know how to map the sprite
   0, 1,
   2, 3,
   4, 5,
   6, 7,
   8, 9
};

// This would appear somewhere in our code
MapSprite(0, mapMario);

Now mario would be occupying sprite indexes 0 to 9 and can be easily moved as a single mega-sprite.

You can hide a sprite by moving it off-screen. Space Invaders provides its own convenience function for this called...HideSprite.

Moving

While moving a tile does not really make sense, you can make it appear to move by pointing neighbouring tiles to the same tile and then replacing the previous position with a background tile, all by using SetTile. In fact, the Platz toolset uses a similar method to make tiles appear to move as sprites for its moving platforms.

Space Invaders uses this method for making the invaders appear to edge back and forth across the screen and down towards the player.

Sprites can be moved with the MoveSprite function. The startSprite parameter for our mario sprite would be 0 and all 10 sprites would be moved to the specified position by setting the width and height parameters appropriately provided that the sprite has been mapped to consecutive sprite indexes (as MapSprite does). MoveSprite has pixel precision and thus can position sprites with much finer granularity than SetTile positions tiles.

Animating

Both tiles and sprites can be animated. Animation is simply the process of changing their display tile/sprite at whatever time interval suits their animation frames. Space Invaders stores animation details in flash and loads them when the current animation needs to change to another. An example of this is the ship being destroyed. Yes, it seems odd that the ship is an animation, but Space Invaders makes it an animation of frame count 1. This allows both the normal ship and the explosion animation to be handled by the same code. Space Invaders' animation structure is not the only means of storing animations, and is probably overdone for this game - it does the job, though.

Refer to Space Invaders' functions AnimateProjectiles and AnimatePlayer to see how the animation frames are incremented. These values are taken into account in DrawProjectiles and DrawPlayer to display the correct frame. Also, see SetPlayerState and SetProjectileState to see how their respective animations are loaded to match appropriate state changes.

Finite State Machines

Space Invaders uses very simple FSM's in order to simplfy game logic. These states are represented by enumerations in the "Type declarations" section of the source code. By providing specific functions to change state for each FSM, we centralize this logic and can be sure that initializations have been conducted prior to any changes in state. We can also ignore any logic that is not possible for a specific state, further simplifying the game. Furthermore, animations, events and abilities can be tied to specific states, making debugging your code a much easier task.

The result of using FSM's is typically a main function that is just one large select statement (possibly with other nested selects), making it very readable.

Collisions

While the display system has to abide by tile alignment, we can represent the perceived positions of our invaders however we wish. This allows us to have precise collision bounds for our invaders despite the fact that their graphical representation is occupying anywhere from 1 to 4 tiles.

Take a look at Space Invaders' ProcessCollisions function (and associated helper functions) to see exactly how it's determined if a projectile hits a shelter, player or goes out of bounds; and likewise with player projectiles.

Pseudo-random number generator

Space Invaders uses an 8-bit LFSR (Linear Feedback Shift Register) to generate pseudo-random numbers for use in many areas throughout the game. The LFSR is seeded based on the elapsed time between game startup and when the start button is pressed with a resolution of 1/60th of a second, so it's very unlikely that you'll regularly get the same sequence of numbers generated.

The PRNG (pseudo-random number generator) is used for determining when the ufo appears; setting the ufo bonus from between 1k-5k; and deciding which alien invader will shoot a projectile.

Music and sound effects

While Space Invaders doesn't have any music, it does employ several sound effects. Uzebox community member D3thAdd3r has created a great tutorial for generating sound effects for the Uzebox and another for music. Sound can be a confusing area to begin with, so please post questions to the community forums if you get stuck.

Loading and Saving Games

Accessing the eeprom for saving and loading of game data is handled with the EepromReadBlock and EepromWriteBlock functions and using the EepromBlockStruct data structure. Each game save "slot" is allocated 30 bytes and must have a unique ID. You can reserve eeprom ID's for your game here. See the Space Invaders functions LoadHighScore and SaveHighScore for an example of how to access this feature.

Finished Game

Space Invaders demonstrates most of the techniques required for simple Uzebox games. There are other more advanced techniques including Uzebox community member D3thAdd3r's ram tile effects and other resource management solutions for games where the screen should scroll to reveal much larger game worlds. There's also the possibility of creating your own Video Mode once you become familiar with the Uzebox kernel.

Best of luck with your game, and don't hesitate to post your progress on the community forums; we'd love to see it!

Media:SpaceInvaders.hex

Ideas for extending Space Invaders

Space Invaders only uses 56% of the available flash, so there's plenty of room for expanding on the game's complexity. You might want to extend a current game for your first effort rather than starting afresh. You could add powerups or ship upgrades or fancier alien attacks - the list is endless.