Saturday, March 17, 2018

devkitSMS Programming Sample II

In 2014, we checked out z88dk Programming Setup + Sample to build 3D City for the Sega Master System. While this worked on Fusion + Meka emulators unfortunately 3D City encountered graphics glitches on real hardware.

In 2017 we checked out devkitSMS Programming Setup + Sample to build CandyKid Demo for the SMS also. As this demo worked on Emulicious emulator with VDP constraints enabled CandyKid Demo now ran on real hardware.

Building upon knowledge + experience shared by the SMS Power community, plus gaining access to source code, we have now streamlined the SMS development process to build Simpsons Trivia using the devkitSMS.
Let’s check it out!

Software
Follow all instructions from the previous post: this documents how to setup the pre-requisite software.
Note: ensure you have downloaded and installed the devkitSMS and Small Device C Compiler [SDCC].

Folders
The devkitSMS requires certain utilities to work such as ihx2sms, assets2banks and/or folder2c. Plus, it is common to use utilities like BMP2Tile for graphics, VGM2PSG for sound, assemblers like WLA-DX etc. etc.

Therefore, it makes sense to centralize these utilities in a parent folder C:\SEGA\Utilities. Also, all emulators such as Fusion, Meka + Emulicious can be re-located to C:\SEGA and added to PATH Environment variable. System | Advanced system settings | Environment Variables | PATH. Add all corresponding C:\SEGA folders

Graphics
Previous work on SMS graphics simply used basic tools like MS Paint to save sprite and tile data in 16 Color Bitmap format to conform to the 4bpp (bits per pixel) constraints. However, graphics here were very simple!

In order to achieve a more authentic 8-bit look and feel, and still comply with 4bpp, let's use another tool: Download Gimp. Load typical 256x192 BMP image as before but save as Indexed mode with max 16 colors. Image menu | Mode | Indexed... Convert Image to Indexed Colors | Generate optimum palette | Maximum number of colors: 16 Click Convert button. Finally, File menu | Export As... simpsons.bmp | Export

BMP2Tile
Previous work using BMP2Tile was a very manual process: Launch BMP2Tile | drag n' drop the 4bpp image into BMP2Tile and manually configure options and save out the Tiles, Tilemap and Palette files one by one.

After inspecting the source code for Astro Force, we see that eruiz00 automates this process using batch scripts. Let's replicate this process to produce Tiles, Tilemap and Palette files for simpsons.bmp [above].

Image.bat
bmp2tile.exe raw\simpsons.bmp -savetiles "simpsons (tiles).psgcompr" -removedupes -nomirror -planar -tileoffset 96
bmp2tile.exe raw\simpsons.bmp -savetilemap "simpsons (tilemap).stmcompr" -exit
bmp2tile.exe raw\simpsons.bmp -savepalette "simpsons (palette).bin" -exit
Tiles Tilemap Palette IMPORTANT: for multiple images loaded and rendered at the same time ensure the palette file is the same!
Note: folder2c ..\gfx gfx is used as before to convert files to code. Here 96 is the tile offset used in code:

ContentManager.c
#define SIMPSONS_TILES 96
void engine_content_manager_title()
{
  SMS_loadPSGaidencompressedTiles( simpsons__tiles__psgcompr, SIMPSONS_TILES );
  SMS_loadSTMcompressedTileMap( 0, 0, simpsons__tilemap__stmcompr );
  SMS_loadBGPalette( simpsons__palette__bin );
}
Finally, see here for a complete list of all Command line mode arguments that can be used with BMP2Tile.

ROM Banking
Simpsons Trivia is 128KB in size thus uses ROM banking to allow for more that 48KB in the game ROM. Again Astro, Force gives a real world example on how bank switching can be achieved for more content.

For example, let's load the Splash screen from bank 2 as this is rendered once during the entire game:

Image.bat
bmp2tile.exe raw\splash.bmp -savetiles "splash (tiles).psgcompr" -removedupes -nomirror -planar -tileoffset 112 
bmp2tile.exe raw\splash.bmp -savetilemap "splash (tilemap).stmcompr" -exit
bmp2tile.exe raw\splash.bmp -savepalette "splash (palette).bin" -exit
Build.bat
REM echo Build banks.c and banks.h from banks folder
cd banks
folder2c bank2 bank6 2

REM echo Build banks
sdcc -c --no-std-crt0 -mz80 --Werror --opt-code-speed --constseg BANK2 bank2.c

REM echo Build main
sdcc -c -mz80 --opt-code-speed --peep-file peep-rules.txt --std-c99 main.c

REM echo Linking
sdcc -o output.ihx --Werror --opt-code-speed -mz80 --no-std-crt0 --data-loc 0xC000 ^
-Wl-b_BANK2=0x8000 ^
..\crt0\crt0_sms.rel ^main.rel ^
..\lib\SMSlib.lib ^
..\lib\PSGlib.rel ^
banks\bank2.rel

ContentManager.c
#define GRAPHICS_TILES 112
void engine_content_manager_splash()
{
  SMS_mapROMBank( 2 );
  SMS_loadPSGaidencompressedTiles( splash__tiles__psgcompr, GRAPHICS_TILES );
  SMS_loadSTMcompressedTileMap( 0, 0, splash__tilemap__stmcompr );
  SMS_loadBGPalette( splash__palette__bin );
}

Function Pointers
Game development is renown for using the State design pattern. Therefore, previous SMS titles like 3D City or CandyKid demo have a main loop that switches load and update methods depending on the current state.

For example, CandyKid demo has 4x states: Splash, Title, Ready, and Play thus the main loop is as follows:

Main.c
for ( ;; )
{
  if ( enum_curr_screen_type != enum_next_screen_type )
  {
    enum_curr_screen_type = enum_next_screen_type;
    custom_screen_manager_load( enum_curr_screen_type );
  }
  // ...
  custom_screen_manager_update( &enum_next_screen_type, curr_joypad1, prev_joypad1 );
}
void custom_screen_manager_load( unsigned char screen_type )
{
  switch( screen_type )
  {
  case SCREEN_TYPE_SPLASH:
    screen_splash_screen_load(); break;
  case SCREEN_TYPE_TITLE:
    screen_title_screen_load(); break;
  case SCREEN_TYPE_READY:
    screen_ready_screen_load(); break;
  case SCREEN_TYPE_PLAY:
    screen_play_screen_load(); break;
  }
}
void custom_screen_manager_update( unsigned char *screen_type, const int curr_joypad1, const int prev_joypad1 )
{
  switch ( *screen_type )
  {
  case SCREEN_TYPE_SPLASH:
    screen_splash_screen_update( screen_type, curr_joypad1, prev_joypad1 ); break;
  case SCREEN_TYPE_TITLE:
    screen_title_screen_update( screen_type, curr_joypad1, prev_joypad1 ); break;
  case SCREEN_TYPE_READY:
    screen_ready_screen_update( screen_type, curr_joypad1, prev_joypad1 ); break;
  case SCREEN_TYPE_PLAY:
    screen_play_screen_update( screen_type, curr_joypad1, prev_joypad1 ); break;
  }
}

Consequently, this solution does not scale! Instead, opt for an array of function pointers: for each state add to an array of load and update function pointers; the main loop invokes the correct function based on state.

Main.c
void ( *load_method[ MAX_STATES ] )();
void ( *update_method[ MAX_STATES ] )( unsigned char *screen_type, const int curr_joypad1, const int prev_joypad1 );

for ( ;; )
{
  if ( enum_curr_screen_type != enum_next_screen_type )
  {
    enum_curr_screen_type = enum_next_screen_type;
    load_method[ enum_curr_screen_type ]();
  }
  // ...
  update_method[ enum_curr_screen_type ]( &enum_next_screen_type, curr_joypad1, prev_joypad1 );
}

void custom_initialize()
{
// Set load methods
load_method[ SCREEN_TYPE_SPLASH ] = screen_splash_screen_load;
load_method[ SCREEN_TYPE_TITLE ] = screen_title_screen_load;
load_method[ SCREEN_TYPE_READY ] = screen_ready_screen_load;
load_method[ SCREEN_TYPE_PLAY ] = screen_play_screen_load;
// ...
load_method[ SCREEN_TYPE_OVER ] = screen_over_screen_load;

// Set update methods
update_method[ SCREEN_TYPE_SPLASH ] = screen_splash_screen_update;
update_method[ SCREEN_TYPE_TITLE ] = screen_title_screen_update;
update_method[ SCREEN_TYPE_READY ] = screen_ready_screen_update;
update_method[ SCREEN_TYPE_PLAY ] = screen_play_screen_update;
// ...
update_method[ SCREEN_TYPE_OVER ] = screen_over_screen_update;
}

Emulation
In the past, Fusion emulator was used during development especially to test code changes quickly. However, more developers prefer Emulicious as this has an option to Emulate VDP constraints for real SMS hardware.

Also, Emulicious has numerous built-in tools to view memory, tiles, tilemaps, palettes, sprites etc. plus it has an awesome debugger! Therefore, let's integrate Emulicious into our build batch script development process:

First, launch Emulicious. Tools menu open all windows. Ensure the "Reopen Tools" option is checked. Launch the debugger. Run menu | ensure "Suspend On Open" is checked. Close Emulicious. Update the build script:

Build.bat
REM output.sms
java -jar C:\SEGA\Emulicious\emulicious.jar output.sms
Finally, Ctrl+1 in Visual Studio 2008 will now launch in Emulicious with debug step through Z80 assembler!

Summary
To summarize, the SMS Power community has been awesome and by members sharing especially their code, for example like Astro Force then this really does help develop ideas for new projects like Simpsons Trivia.

Recently, eruiz00 has shared the source code for Galactic Revenge. This game uses an up-to-date version of z88dk with devkitSMS [instead of sdcc] as the libraries are optimized in ASM + produce smaller object code.

Therefore, 3D City and future games could be written using z88dk with devkitSMS without graphics glitches!

No comments:

Post a Comment