Dreamcast Game Programming

Introduction

While programming for a PC is very accessible nothing beats seeing your code run on a games console which is why I've finally got round to developing for Sega's last but much loved console, the Dreamcast. For an introduction to the console you can check out my page here.

This page will cover techniques for getting code to run on both an emulator and a real Dreamcast, going into detail about the necessary steps along the way. You will see the growth of a simple game I've made for the Dreamcast that will introduce game mechanics and interaction with Dreamcast peripherals.

First steps

CD-R tests

Unfortunately not all Dreamcast consoles will be able to play CD-R discs so if you do plan to burn your software to disc then you need to check whether your Dreamcast can run CD-R's. As I already had a couple of burned games I tried them first and confirmed that they would run. You can burn your own discs (of games you already own) and test on your Dreamcast. For quickness, Planet ring is a good test as the file is very small in size although it's only good for testing booting since it requires online services that are no longer available to actually be able to play the game.

There is version of ImgBurn that supports writing Dreamcast discs. You can follow a tutorial at:

https://www.instructables.com/id/Dreamcast-Burn-Games-with-ImgBurn/

Annoyingly the link for the Dreamcast driver for ImgBurn is dead, however, I was able to find a different site hosting the files:

https://legacy.padus.com/downloads/cdi.php

Dreamcast disc images can be in either CDI or GDI format with GDI more closely resembles an original Dreamcast GD-ROM disc. The CDI format, however, is appropriate for burning to CD-R and may have had the game altered slightly (such as to force VGA mode) and likely the individually files will be accessible unlike on a GD-ROM disc.

Because ImgBurn supports writing Dreamcast discs from a CDI file you will need to obtain the CDI disc images only if you wish to burn to disc. The software we will be creating ourselves will be in CDI format so we will have no problem burning to disc.

I have had success using Tesco 700MB CD-R discs using an external optical drive, an Optiarc BD-5740L. In ImgBurn I set the write speed to 4x as instructed in the tutorial and although the drive doesn't support that speed and instead used 8x it still burned the disc correctly. It took 3 minutes and 26 seconds to write the image file to disc.

Emulator

It would be very tiring if you had to burn every revision of your software to disc to test and while you certainly should test frequently on a real Dreamcast it is a good idea to test even more frequently on an emulator. Emulators are not perfect and we will later look at why it's important to test on a real Dreamcast but most testing should be done on an emulator to speed up the development process and to take advantages of features such as save states.

Using my Surface Pro 5 running Windows 10 of which I do my Dreamcast development on I had quite the struggle to get a Dreamcast emulator to run. I was not able to get nullDC to run but I will link to it in case you would like to try on your system:

https://segaretro.org/NullDC

There is a link to download nullDC at the bottom of the page.

After downloading the file extract it using 7-Zip which will create 2 folders; one for Naomi (the arcade system based on the Dreamcast) and one for Dreamcast. For a Dreamcast emulator to work you need to download the Dreamcast BIOS files for the region you wish to play games from. Search Google for "dreamcast bios download". Once you have the BIOS file downloaded and extracted copy the dc_boot.bin and dc_flash.binto files into the data folder of the nullDC emulator you are using.

Before running nullDC you need to download and install Microsoft Visual C++ 2010 Redistributable Package from:

https://www.microsoft.com/en-us/download/details.aspx?id=5555

You must only use the 32-bit (x86) version.

However when I ran nullDC I got the error:

"unable to load nullpvr_win32.dll"

A suggested fix I found online was to the download DirectX 9.0c End-User Runtime:

https://www.microsoft.com/en-gb/download/details.aspx?id=34429

But the runtime cannot be installed on Windows 10 so I gave up on nullDC.

Next I tried another Dreamcast emulator, DEmul, which can be downloaded from:

http://demul.emulation64.com/

The emulator supports GDI, CHD and CDI formats but hasn't been updated since 2013.

After downloading and extracting the files from the site linked above you need to create a folder called "roms" within the main emulator folder and copy the BIOS files to the roms folder (you will need to create it yourself). When DEmul is run it will say the BIOS and plugins aren't configured and will bring up the Plugins, Maple & Paths window: simply click OK.

However after trying to run a game I got the error "unable to find mpr-21931.ic501' in romset 'dc'". The fix is to rename dc_boot.bin to 1_01d_01.bin and then when you launch DEmul go to Config->Dreamcast BIOS->Dreamcast v1.01d (World).

It is a good idea if you are using an emulator to get a Dreamcast controller to USB adapter so that when testing your software on an emulator you're using the same controller as on a real Dreamcast. For such purposes I bought an adapter from Amazon, a Mayflash Dreamcast controller adapter that supports 2 standard Dreamcast controllers and connects to a PC via USB. Although it will work with the Dreamcast arcade stick it will not detect the 'C' and 'Z' buttons as they are not present on a standard Dreamcast controller.

Supposedly the adapter only supports up to Windows 8 32/64-bit but I got it working no problem on my Surface pro running Windows 10. You simply connect the Dreamcast controller(s) to the adapter's Dreamcast ports and the adapter's USB to your computer. In Windows search for Set up USB game controllers and you should see two USB GamePad entries. You can select either, click on Properties and test the controller. If you only have 1 Dreamcast controller connected to the adapter then you may have to try the other GamePad entry.

For reference, the mapping between Dreamcast controller to PC is as follows:

(DC: PC)

A: 1

B: 2

X: 3

Y: 4

L: 5/Z

R: 6/Z

START: 10

Analog: X/Y

D-pad: 13 to 16/POV

The instructions for the controller adapter mention that the D-Pad and analog stick can be swapped by pressing and holding start and button A for 3 seconds.

The adapter is compatible with Demul; go to Config->Controls, click Joy 1 and then you can assign the controller inputs. However, as soon as you click on a button it will display 'JOY0_ANL2_KEY-'. This is not a problem; just hold the button/analog stick direction on the controller that you want to use and click the same button in the configuration window to register the DC input. Repeat for all buttons.

Setting up the development environment

Although I had programmed games and other types of software before programming for the Dreamcast was new to me when I started late 2018. I had a look online and came across the following tutorial which got me started:

http://www.racketboy.com/forum/viewtopic.php?t=50699

I worked my way through the instructions linked above which detailthe process of setting up the development software which takes some while to do. You are required to download and install Cygwin which sets up an Unix-like environment on Windows in order to run the Dreamcast tools that are to be used. Make sure when you install Cygwin that the packages mentioned in the tutorial are installed.

Apparently there is an alternative to using Cygwin which is described at:

https://brianpeek.com/dreamcast-gamedev-with-kos-and-bash-on-windows/

However I did opt to install Cygwin which turned out to be quite the pain to get working. When you get to the part in the tutorial (linked at the start of this section) where you issue the command:

./unpack.sh --no-deps

That takes a very long time as one of the files the script unpacks it very large. You can add debugging output to see what part of the script is being executed:

bash -x ./unpack.sh --no-deps

When you issue the command make during the tutorial that also takes a very long time to finish. Then when I ran make after source environ.sh as instructed I got a number of errors including complaining about jpeglib.h being missing.

I had to experiment a lot with running the Cygwin setup to install additional files such as python2. Now ./build-all.sh would run.

Here are some tips for when using the tutorial:

For editing .bash_profile you will find the file in the username folder of cygwin (e.g. C:\cygwin\home\username).

Don't forget to copy the scramble executable from C:\cygwin\home\username\dc\kos\utils\ to C:\cygwin\

IP.bin and Bootdreams are needed for the example project but there are no links on the tutorial page. Bootdreams can be downloaded from:

https://sourceforge.net/p/openbor/tools/3086/tree/tools/dc-tools/BootDreams-1.06c.7z

Bootdreams has the ability to create the IP.bin file for you. The file produced by Bootdreams can be burned to disc or run in an emulator.

To compile Lesson 3 you must run the following commands while in the lesson3 folder:

make

sh-elf-objcopy -R .stack -O binary main.elf output.bin

scramble output.bin 1st_read.bin

Then Bootdreams can be used to generate the cdi file.

Building a game

Now we will look at the game that I have been working on, going through each iteration in detail; this page will be updated as I progress. After I had worked through the tutorial on racketboy.com I built on the example code and it became clearer how the Dreamcast operates as well as how to use KOS, although there is still a lot to learn.

Starting with Playfield_with_text I will talk in detail about how the code works to give an idea how the program flows and how the important functions operate.

You can find the project files for each iteration attached at the bottom of the page with the file name matching the project heading. Simply download, extract, and build the same way as in the racketboy tutorial.

Please note that unless otherwise mentioned all screenshots in this section were taken using the Demul Dreamcast emulator.

You can view a video I did as an introduction on Dreamcast development which includes the iterations I have done from Move_obj_simple to Playfield_with_text:

You can also check out a playlist of all videos I have done on Dreamcast development:

https://www.youtube.com/playlist?list=PLujUolFWv4BG27UOdMPGrRGDz_POGfJV6

Move_obj_simple

While many people start with 'Hello world' I went straight into a move graphical version in which a green shaded box can be moved about the screen using the D-pad direction buttons. The block is created by drawing to the video memory directly and the movement is fast but there is some 'glitching' as the box moves. The background is black but it is a good start in responding to player input and updating a graphical object. I even put in boundary checking so the block cannot go off screen.

Move_obj_simple_2

This version is similar to the previous but has a textured background which has a black border around the edges as I wanted to check that the whole texture was being displayed although unfortunately on an emulator at least that is not so. The controls are the same as before but now the player controlled block is using a texture rather than a simple coloured block. Like with the background texture the block texture also has a black outline which unfortunately doesn't show. Whereas with the previous version the movable block was the result of writing directly to video memory in this version I had now switched to using a sprite which results in smoother movement.

Playfield_simple

Now the code is producing something that looks like a game; we have a simple animated player character consisting of 7 frames (I chose a ball as it was easier to animate than a person) that can be moved with D-pad left and right. The player can also jump using the A button although there is no debouncing so holding A will make the ball continually jump.

We also have a playfield consisting of tiles which are 80 x 80 pixel blocks; an array specifies which of the 2 blocks (background or solid block) is used in each fixed position. I chose large blocks mainly to keep down the size of playfield definition array but when I add loading and saving levels I'll reduce the size of the tiles.

There is simple collision between the player ball and the solid (brown/orange) blocks although it is very easy to get the ball through a block but doesn't really matter at this early stage.

Playfield_with_editor

The big change here aside from adding two additional solid type tiles is that as well as the controller for player input (which now has jump debouncing added), the Dreamcast keyboard and mouse are both supported for editing the level (although changes are only temporary). The controller must be plugged into port A, the keyboard in port B and the mouse in port C.

Pressing 'e' will toggle between editing the level and playing the game; any changes you make while in edit mode will be reflected as soon as you exit to the game. While in edit mode a pointer sprite will appear which can be moved with the Dreamcast mouse and cannot be made to go outside the screen but as it is treated as the same size as the player sprite it gets restricted a bit early toward the right and bottom of the screen.

In edit mode left click to pick up the tile under the mouse pointer and left click again to put it down; a picked up tile can be moved around with the mouse and has a selection sprite overlaid to show it has been picked up. Because the tiles are placed at set locations and the the mouse pointer position is take from the top left part of the sprite it can make selecting and putting tiles down a little tricky.

While a tile is picked up you can right click to cycle through the available tile types or side click (most likely middle mouse button if using an emulator) to return a tile that was picked up to its previous position.

There are other keyboard controls while in edit mode. Press 'u' to return a tile that was picked up to its previous position (same as mouse side button press). You can also pess 'd' to remove the currently picked up tile (actually converts it to a background tile).

Playfield_with_text

In this version we have a HUD which displays the player's current health and points and to make use of the HUD there are now three collectible items (which are tiles). The tile marked as '15' increases the player's points by 15, the heart item increases the player's health by 10, and the '!' item (which isn't really an item you'd want to collect) decreases the player's health by 10.

The game can now be paused by pressing start and while the game is paused text is displayed (see screenshot below) which in addition to saying 'PAUSED' the controls are also outlined.

The text of the HUD and the pause screen uses the Dreamcast's built-in font set copied to a texture but unfortunately the Western characters are thin and don't show up well but the Dreamcast symbols are slightly better looking. An improvement, as seen in the Dreamcast's dashboard, is to add shadowing to the text and place a translucent texture behind the text. But if you look at what I did as seen in this screenshot from Demul the text looks very clear, similar to what we would see using a VGA adapter on a real Dreamcast:

But if you were to compare to an actual Dreamcast with a composite video connection the text looks horrible. You can see exactly what I mean in the following image which was captured using my Elgato capture card:

Thus you need to consider how your game will look when viewed on a TV assuming at worse an RF or the slightly better composite option may be used. This is a good reason to test on a real Dreamcast to see how clearly the text can be read.

The editor can be accessed as before and the only real change is that a tile can now be changed to any of the item tiles as well as the previous tile types.

Now let's talk in detail about the code located in main.cpp.

Let's start with main() which first calls init() which uses a pvr_init_defaults() function call to set up the PVR with default settings. Next texInit() is called which creates a background texture, backTex, which holds the playfield tiles and items that are loaded into it from playfield_tiles.png. Next we make another texture, playerTex, that holds player animation frames and editor sprites loaded from player.png. After that we create another texture, fontTex, and then we copy into the texture the Western and Dreamcast special symbols so that they can be rendered to form words for the HUD and pause screen. It's a little bit complicated as the symbols don't fit exactly horizontally into the texture.

Next in init() we call vid_set_mode() to set the video mode and colour format and that is followed which two loops to copy the non-editable array containing the level definition (what tiles/items are placed in the level), playfieldTiles[][] to editPlayfieldTiles[][].

Back in main() we set up the quit variable which if set to non-zero value would cause the main loops to exit (non currently made use of). We also set up startMS variable so we can time when to call updateGame(), by checking if enough time has passed since the last check (waitTime determines how long to wait in milliseconds).

In the updateGame() function we first call updateObjs() and in this function we make sure that we are not in edit mode (editModeEn not true) and the game is not paused (gamePaused not true). If we don't exit from updateObjs() then we look to see if the player is jumping (playerJumpTimer is higher than zero) and if so we look to see if there is a tile of interest above the player by using getPlyTileType() to see the type of tile above the player. If the tile type isn't zero (i.e. not a background tile) and is another tile including an item tile then we remember that the tile type above is sold by setting tileAbove to true. Note that there is a slight oversight here as amn item tile will register as solid but can be collected anyway. If there isn't a solid tile above then we move tha plyer up at speed plyJumpSpeed unless at top of screen in which case we make the player fall by setting playerJumpTimer to 0. Note that there is a bug here; if the player is at the top of the screen playerJumpTimer will be set to 0 and then decreased by 1 but because we check for playerJumpTimer to be above 0 it won't cause any problems.

Next we test to see if there is a solid tile below, again by calling getPlyTileType() but with different values for the input parameters. If we do find a tile below we also check for a tile blow the player but to the right just to avoid a possibility of the player falling through a tile rather than landing on it. If we do find a tile below in either situation then we can log that the player has landed on a solid tile by setting playerLanded to true.

If we arrive at the conclusion that the player isn't jumping and the player is not on a solid surface then after making sure the player isn't at the bottom of the screen we move the player down using the speed value of plyFallSpeed. The last thing then to do after the previous test is to see if the player has touched an item tile by calling getObjTileType() and then the tileType returned value is passed to handleItem() which performs a set action based on the tile type and then removes the item tile.

Back to updateGame() the last thing to do is call checkForInpUpd() which in turn calls checkContInput(), checkKeyBoardInput() and checkMouseInput(). In function checkContInput() we look for a Dreamcast controller by using maple_enum_type() and if we find one we get its status by calling maple_dev_status(). If we were able to get the controller status we look to see if the start button was pressed and after checking it was previously released to peform switch debouncing (by testing startButReleased) we toggle pause on/off by updating the variable gamePaused. Note that we have to clear the keyboard queue using kbd_set_queue() so that if a key is pressed while the game is paused and the game was then unpaused the game doesn't respond to that key press.

If the game is not now in a paused state we can then check the controller D-pad left and right buttons which will move the player left/right by decreasing or increasing the player X position by 1 (it would be a good idea to use a speed value instead). We do check that there is no solid tile to the left or right of the player by getting the tile type to the left or right of the player by calling getPlyTileType().

After checking the direction buttons we must then test the A button on the controller which makes the player jump if the player has landed on a solid surface and isn't already jumping. Then we check to see if a new animation frame should be used for the player if the left or right direction button was pressed but we time the change using a frame timer, nextFrameTimer, comparing to nextFrameDelay. Depending on whether left or right direction button was pressed we either decrease of increase the animation frame index, playerFrameI, rolling over to the first frame.

Function checkKeyBoardInput() starts by looking for an attached Dreamcast keyboard and exits if there isn't one; it also leaves the function if the game is paused. Otherwise we get the currently pressed key by way of the return value from kbd_get_key() and then it is a matter of using a switch statement to check for the 'e', 'u' and 'd' keys. If the 'e' key was pressed we toggle edit mode on/off by updating the editModeEn variable. If 'u' was pressed we test that we are actually in edit mode and then we call editRetTile() which checks that a tile is currently picked up and if so puts the tile back by using the tile X, Y values previously stored when the tile was picked up by use of variables editTileOldX and editTileOldY. We do this by updating editPlayfieldTiles array with the current edit tile type stored in editTileType and then all we have to do is set editTilePickedUp to false, effectively dropping the tile back in its previous place.

Back to checkKeyBoardInput() if we find that the 'd' key was pressed then all we have to do is set editTilePickedUp to false which actually removes the picked up tile since we don't place it anywhere and the next time we pick up a tile the variables holding the picked up tile type and position of the now deleted tile will be overwritten.

Lastly we will look at checkMouseInput() as called from checkForInpUpd(); in checkMouseInput() if in edit mode the function will exit but if not we try to find an attached Dreamcast mouse which if not found will cause the function to terminate. If a mouse was found and we were able to get its current state we update mouseX and mouseY with the current mouse position (think of it as an offset) so the mouse cursor moves with the mouse. Next we make sure the mouse pointer doesn't go beyond the screen although as currently it's treated as a square of fixed size the cursor stops too early at the left and bottom of the screen.

After updating the mouse position we test to see if any of the mouse buttons have been pressed starting with the left button which is debounced using mouseLeftClickReleased. If we determined that the left button was pressed we toggle editTilePickedUp on/off to pick up/put down a tile and if we need to pick up a tile then we get the tile information from getObjTileType() and store the tile position in editTileOldX and editTileOldY, and store the tile type in editTileType. We remove the picked up tile (which will now be attached to the mouse cursor) by updating the editPlayfieldTiles array by setting the tile to the backhground type (0). Note that you can pick up background tiles. If we are, however, to put down a tile we get the tile that the mouse cursor is on by calling getObjTileType() and then we update editPlayfieldTiles[] with the current edit tile type as stored in editTileType.

Another mouse button we must check is the right button, debounced by mouseRightClickReleased, and if we find that it was pressed we cycle through the various tile types (which includes item tiles) by updating the editTileType variable, going back to 0 (first tile type) or tileTypeTotal-1 (last tile type) as needed. The last mouse button to check is the side button which causes editRetTile() to be called, which we have already called.

Returning to main() we only have drawFrame() left to call which always runs regardless of how often the game objects and variables are updated. In drawFrame(), we get the PVR ready and then render the opaque objects which are the tiles that make up the playfield (by calling drawPlayfield()) and we also render the current tile that was picked up (through calling drawEditTile()). In drawPlayfield() it is a relatively simple matter of looping through the editPlayfieldTiles array using X and Y loops to get each tile type and using the tile type to index the texture backTex. Once we have that index (frameY) we calculate the UV values by calling calc_UVs_from_XY() and then we have enough information to render the tile by using drawSprite().

Back in drawFrame() we have to do the translucent objects next which are the player (using drawPlayer()), the edit mode mouse pointer (drawMousePoint()) and the HUD (drawHUD()). The drawPlayer() function is not too difficult as it's a matter of getting the player UV values using calc_UVs_from_XY() and passing the current player fram index (playerFrameI) and then rendering the player using drawSprite(). As for the drawMousePoint() function it is somewhat like drawPlayer() but we use the mouse pointer frame index, mousePointFrameI, which is first passed to render the mouse pointer and then if a tile has been picked up we render using mousePointFrameI+1 which selects the second mouse pointer frame which is actually the tile selection graphic. Finally, in drawHUD() we check we are not in edit mode and if so if the game is paused we calculate the position of the pause screen text. We render each text using drawText() by passing our text string in strOut which can either contain standard ASCII characters but can also include Dreamcast special symbols by using one of the macros I have set up (DC_COPYSYM, DC_REGSYM, and so on). These macros let us insert a hex value into the string by using the escape character '\x' followed by the hex values. This way we can use meaningful Dreamcast symbol names as part of the main string.

In drawHUD() again, if the game is not currently paused then we just use drawText() to display the player's currently health and points. We use the string stream, strStream, so we can include variable values in the string, strOut.

Playfield_with_editor_saving

To view a video I did on this iteration please see below:

The big new additions to this version includes being able to save and load the edited level, slightly improved text, a better looking animated character, and new tiles.

Let's talk about the new controls. While in edit mode:

Press 's' to save the edited level to a connected VMU/memory card. During saving the game will be unresponsive to the keyboard and mouse.

Press 'l' to load the edited level from a connected VMU/memory card. During loading the game will be unresponsive to the keyboard and mouse.

Press 'c' to create the level that is being edited (make it live) and exit edit mode. Previously there was no difference between the level being edited and the level being played and any changes made in edit mode had an affect as soon as edit mode was exited.

Press 'p' to toggle between editing the player and the other tiles. Pressing 'p' automatically picks up the player which will move with the mouse pointer and the player can be placed like any other tile. Pressing 'p' again without putting the player down will cause the player to return to the previous position.

Press 'g' to toggle the grid on/off.

While playing the game:

When the player falls after jumping or moving off an edge she gradually (floats) and this is so the player can maneuver over obstacles despite having a fast jump. When falling hold D-pad down to fall faster.

When the game first starts an attempt will be made to load the level from an attached VMU/memory card in slot A-1. Whether successful or not when you enter edit mode you will see a message indicating success/failure. If the level cannot be loaded then the level will be blank with the player in the default position.

VMU/memory card support is based on code from:

https://github.com/losinggeneration/kos/blob/master/examples/dreamcast/vmu/vmu_pkg/vmu.c

Standard VMU's/memory cards are limited to 128KB (1Mb) which is divided into 200 blocks, with each block taking up 512 bytes. Files on a VMU/memory card must be multiples of 512 bytes in size, that is, even if you just wanted to store, for e.g. 400 bytes, you would see need a whole block. On top of that, for a file to show up in the Dreamcast BIOS menu (file manager) the file needs a header which ues 1 block.

In addition to the data our game needs to store the edited level information a single icon (32x32 pixel, 16 colour maximum) is saved also. You can generate C code for the icon palette and image using the utility at:

http://vmu.sega-dc.de/vmu/vmuimage.php

Which lets you upload a PNG of the icon. The resulting data can then be copied to the VMU package file for saving. However, I couldn't generate a suitable icon using Paint/Paint.NET as the palette would always end up wrong when converted; this is to do with not supporting 16 colour PNGs. However I found a suitable Paint.NET plugin that does work:

https://forums.getpaint.net/topic/15317-low-color-filetype-bmp-gif-png-tiff-20091204/

When saving in Paint.NET select PNG (Low color) and then select the 16 color option in the dialog box that pops up.

Icon palette and image is stored in icon.h and linked into main.cpp. The file saveIcon.png is the icon image used to generate the icon values (the file has been provided for this project for reference but is not needed for compilation).

The format of how file information appears in the Dreamcast file manager with maximum character values:

Long description (32 chars)

Filename (12 chars) Short description (20 chars)

If you use a filename longer that 12 characters KOS will limit to 12 characters.

Supposedly the long description can be a maximum of 36 characters which is the maximum that KOS allows for but the actual maximum that can be saved is 32 (KOS uses an internal structure to actually save file header information).

Our game saves the level definition filename information as:

The Adventures of Kitty Wong

levelDef Level definition

Following the same format that commercial games typically use.

The edited level can be saved to a VMU or memory card whether you are using an emulator or real Dreamcast (note that Demul incorrectly treats a VMS in its Maple device manager as a plain memory card - no screen etc. - even though in reality VMS is just another name for VMU). In the screenshot that follows you can see the save file selected which has its own icon representing the player character. The information that is displayed when the file is selected was chosen to try to follow the same format of commercial Dreamcast games. The save file to the right is from an earlier version of the game which did not include icon data which causes the Dreamcast to use an 'X' icon instead.

Now to look at the game showing a very simple level I put together. Because the level doesn't scroll and the tiles are large in comparison to the player (which was an aim as the player character is short) it's difficult to put together anything more than a simple prototype level.

The aim of the level is to collect the key (bottom right) without falling into the lava (red tile) because of the crumbling block below the key which breaks if the player spends too much time on it. Once the player has the key all that is left to do is go to the door (bottom left) and the level will end.

The ball character has been replaced with a catgirl which has some basic movements (walking, jumping, etc.); the design is based on some sprites I did some while ago. The player character animations frames are stored in player.png and are loaded into a texture; to animate the player it's a matter of selecting the correct frames to use in sequence but currently some of the 'animations' only have 1 frame. The flipping of the frames is handled by the drawSprite() routine which swaps the U1 and U2 texture coordinates.

Looking at player.png you can see on the top row the idle frames, and below them are the walking frames. The first frame on the new row is the jumping frame, to the right of that is the falling slow frame (default when falling), and to the right of that is the falling fast frame (the player can be made to fall fast by pressing D-pad down while falling). On the next row there is a blank space for an attacking frame (to be added later), to the right of that is the stunned frame (when player is hit), and to the right of that is the died frame.

Whereas before the background (blue tile) was part of playfield_tiles.png it is now in its own texture which is background_tile.png. The reason for the change is that the background never has transparency but the tiles now can as you can see with the key, spikes, heart, and points item., and will show the background tile where there is transparency. Tiles that are not transparent (the lava, solid block, crumbling block, and door parts) are still rendered as if transparent as the texture as a whole has transparency. A downside of the texture having transparency is that there is a smaller range of colours available: ARGB4444 vs. RGB565.

The heart item increases the player's health by 10, the points item increases the player's points by 15, and the key as mentioned is required to use the door and complete the level. The orange tile is solid and can be stood on, whereas the version with cracks is the crumbling block that if the player stands on too long breaks. The spikes take away 10 points if the player comes into contact with it; if the player falls on the points the player bounces off but any other contact causes the player to be stunned and pushed away (the player cannot be controller while stunned). The lava kills the player instantly if fallen on to but only if the player is completely on the lava (so not half on the lava, for e.g.). The reason the door is split into 2 parts is because the player is short but is the same height as the player but the door should be taller and only the bottom half the player can interact with. If the player approaches the door without the key a message comes up 'DOOR UNLOCKED!' as you can see in the image above. If the player has the key then the level ends. Any text displayed - the door message, HUD, editor text, and paused text - is rendered twice; once in the requested colour and position and then a second time in black and one pixel down and to the right. This helps the text to stand out better although still further steps will need to be taken to better the readability of the text.

As mentioned, once the key is taken to the door the level ends and a message is displayed which you can see below:

The text still needs improving so it stands out more (a custom font will probably the best thing to use as the BIOS font has very thin characters) but I've used an overlay texture with transparency rendered above the level tiles but behind the text just to help the text stand out better. The overlay image is overlay.png and is completely transparent but is rendered with orange-like colour of the vertices to contrast with the 'LEVEL COMPLETE!' text.

Currently if the level is completed or the game ends because the player has been killed (see below) the level cannot be played again without going into the editor and re-creating the level (press 'c'); new editor features will be covered later on in this section.

The game over screen is shown above and it also uses a translucent overlay but coloured cyan to complement the game over text colour so it can be seen clearer.

When the game is paused the translucent overlay (coloured white) is also used to make the help text more apparent, as can be seen in the screenshot that follows:

Again, the text still is a bit difficult to read so I will need to experiment with the overlay transparency value or perhaps put a patterned texture behind the text.

While in edit mode it is possible to toggle grid lines on and off to make it easier to place objects since a tile when placed will snap to the tile position where the tip of the mouse pointer is. The grid lines are drawn by rendering a black outlined box stored in editor_icons.png (which also contains the mouse pointer and tile selection icon which used to be in player.png) in each tile position. I chose this method as I thought it would give a better result and take up less memory than using a texture full of grid lines which would then have to be scaled and may not look so good. The grid lines can be seen in the image below:

Because we can now save and load the edited level while in edit mode the top left of the screen reports whether loading/saving was successful. In the future I may have the message time out so that after the text has been displayed for a certain amount of time it vanishes.

Next we will talk about the major changes or additions to the code in main.cpp since the previous iteration.

In texInit() we reserve memory and create a texture for the editor icons (editIconsTex), the background texture (backTex) and overlay (overlayTex). Function init() sets up the default position for the player in array editPlayfieldTiles should it not be possible to load the level definition file which is the save file that stores the location and type of all the tiles as well as the player start position. We try to load the level definition file by editLoadLevel() and if that is successful VMU_file_state is set to FILE_LOAD_SUCCESS which drawHUD() uses to display editor related messages (when the game first starts you will not see any error message until you enter the editor). If the level file was loaded then createLevel() is called which first copies the contents of array editPlayfieldTiles (load with values from editLoadLevel()) and then calls initVars() to initialize important variables, thus restarting the level. Back in init(), if editLoadLevel() fails then VMU_file_state is set to FILE_LOAD_FAIL so that in the editor you can see the load fail message.

Going into detail more about editLoadLevel(), we try to open the level save file using fs_open() with the filename stored in editLvlFilename which is '/vmu/a1/levelDef'; the filename refers to file 'levelDef' stored on a VMU/memory card in slot A1. If the load was successful we create a converted VMU package structure, VMU_package_conv, and then try to read in the data into the package using fs_read(). We also create another VMU package, VMU_package, which is loaded with data using vmu_pkg_parse() using the information from VMU_package_conv. All that is then left to do is use the information from VMU_package to populate array editPlayfieldTiles. We do load a level definition version number, lvl_def_ver, so that if in the future we save additional data or change things round we can maintain backward compatibility (the current level definition version is always saved).

For more information about vmu_pkg_parse() please see:

http://gamedev.allusion.net/docs/kos-2.0.0/vmu__pkg_8h.html

As for the vmu_pkg_t structure see:

http://gamedev.allusion.net/docs/kos-2.0.0/structvmu__pkg.html

Function editLoadLevel() is also called from checkForInpUpd() if editLoadLvlPend is true which gets set in checkKeyBoardInput() when 'l' is pressed. The reason for setting a flag rather than directly calling editLoadLevel() is to make sure drawHUD() has chance to display the 'Loading level...' message.

Just as we can load a level definition file we can also save it and that is handled by editSaveLevel() which gets called from checkForInpUpd() when editSaveLvlPend becomes true. This happens when 's' is pressed and again is to give the HUD chance to display the 'Saving level...' message.

In editSaveLevel(), we are basically doing the opposite of loading the save file so we need an array to hold the data to be written, which is VMU_Data. We also need a VMU package type, VMU_package, and converted VMU package, VMU_package_conv. We set up our description giving information about the save file (desc_short, desc_long, and app_id), set the icon count to 1 (icon_cnt) and assign the icon data (icon_data) which comes from icon.h. The palette data comes from icon.h also and is assigned to icon_pal by copying it over. We set up some other icon related information as well as assigning the data and data size. Then we copy to array VMU_Data the values from editPlayfieldTiles. We then try to build the package so it can be saved to file by calling vmu_pkg_build() and if that is successful we try to open the save file by using fs_open(). If the file doesn't exist fs_open() will still succeed and we will end up creating the file anyway when it comes to writing the data to the VMU/memory card by calling fs_write().

As the player now has multiple animations we use a function, changePlyAnimType(), to change the player's current animation. As its only input parameter it takes the new animation type to use (newAnimType) and provided the new animation type is different to what is currently being used, the player's animation will change. The reason for checking the animation type is to simplify coding so that changePlyAnimType() can be called multiple times such as when, for e.g., it's detected that the player is still falling, but we don't want to reset the player's current animation frame (playerFrameI) which would otherwise cause the animation to keep restarting.

Because some of the 'animations' are actually different states (jumping, falling slow, falling fast) that only have 1 animation frame we can change the animation type using changePlyAnimType(), then set playerFrameI to a particular frame in the player texture and we can just use the single frame. Function plyUpdateAnim() will otherwise look up the number of frames for animations that have multiple frames using plyAnimFrameTotal[]. This is why, for e.g. jumping/falling is listed as only having one frame as the 3 frames are more like individual states than an actually animation as such.

In plyGravityUpdate(), called from updateObjs(), we check to see if there is a tile directly below the player or below the player to the right and update playerLanded accordingly. If we find that the player has landed on a solid surface then we check if the tile the player has landed on is a crumbling block and if so we test to see if the player is on the crumbling block enough that we should time how long the player has been on the block using crumbleBlkTileTimer. If the timer has reached its maximum value it is reset and the crumbling tile is removed which would cause the player to fall if not on another solid tile also. Should the player no longer be on a crumbling tile then crumbleBlkTileTimer is set back to 0.

If otherwise we find that the player is on a lava tile then we first determine if there are 2 lava tiles next to each other and whether the other lava tile is to the left or right of the main one we are testing. The reason we have to do that check is because the player is only killed by the lava if completely on the lava tile but that would mean that the player would not be killed if standing on 2 lava tiles next to each other. After check for 2 lava tiles next to each other the program can do the necessary checks to work out if the player is actually enough on the lava to be killed and if so the player's health is reduced to 0, the player's animation is changed to the dead frame (ANIM_ATTACK_ATTACKED_DEAD), and plyGravityEn is set to false to make the tile seem solid.

Also in plyGravityUpdate(), we determine if the player is currently falling by checking that the player is not on a solid surface (playerLanded is false) and the player isn't jumping (playerJumpTimer is 0). If the player is indeed falling then we calculate the player's current fall speed, which is plyFallSpeedMul * playerFallTimer, and store it in plyFallSpeed. This has the effect that the player will increasingly fall faster up to a cut off point to simulate terminal velocity. The player always starts of falling slowly so by default we make sure plyFallSpeed doesn't exceed plyFallMaxSpeedSlow but if the player is falling fast by pressing D-pad down then plyFallSpeed becomes plyFallMaxSpeedFast regardless for how long the player has been falling.

To actually move the player down the screen we check that the player is not at the bottom of the screen but if that has happened we just place the player at the bottom of the screen; this is actually a mistake as that will have the effect of preventing the player from being able to jump. If the player isn't at the bottom of the screen we calculate if when we move the player down at the current speed will the player overlap a tile below (the bottom of the player passes the top of a tile) and if so by how many pixels. If we find there will be overlap then we move the player down by a reduced amount otherwise we move the player down at the previously calculated speed. To keep track of how long the player has been falling we increase playerFallTimer and as well as affecting the player's fall speed it could be used to determine whether the player should loose health upon landing on a solid surface.

Lastly in plyGravityUpdate() we have to do a number of checks to make sure we can set the player to either of the falling animations (which are actually each only a single frame). If the player has health and is not stunned then we are safe to change the player's animation to ANIM_JUMP_FALL and by setting playerFrameI to either ANIM_FRAME_FALL_FAST or ANIM_FRAME_FALL_SLOW we can show the player falling fast or slow.

In plyHandleItem(), called from updateObjs(), we look for a tile below right, below left, above right and above left, by using function getObjTileType() to see what tile is near the player. Regardless of whether the tile is an item or not we call handleItem() which uses a switch() block to go through the various item types and take the necessary action. Item EDIT_ITEM_HEART is the heart item which increases the player's health and is the same as the previous game iteration. Another item which is similar to before is EDIT_ITEM_POINTS_BLK which increases the player's points if the player has overlapped the points item enough.

Next, on to EDIT_ITEM_SPIKES, we see if the player has landed on a solid surface and if so, we check if the bottom of the player has overlapped the top of the spikes by amount spikesTouchOffset and if so the player is hurt by calling doPlyHurtReact(). Note that if the player is jumping of falling (playerLanded is false) the same action is taken and this is a mistake as originally I had different code in place, but the overall outcome for now is acceptable. If the player was hurt due to touching the spikes (handledItem is false) and the player is falling we check to see if the player has overlapped the spikes horizontally by amount spikesFallOffset and if so we cause the player to jump (by calling startSimplePlyJump() instead of startPlyJump() so the player is forced to jump even though the player has not landed) and then hurt the player.

The door item (lower half), EDIT_ITEM_DOOR, works by finding out first if the player has overlapped the door enough horizontally and then a check is made to see if the key item was collected (keyCollected is true). If the key was collected and the player is on a solid surface then the level becomes completed (lvlFinish becomes true) and the player's animation is set to idle. If the key was not collected when the door was touched then a message is displayed to notify the player that the door is locked. This is done by setting the door message position (doorMsgX and doorMsgY) to above the door and by enabling the door message (enDoorMsg set to true). In drawHUD() enDoorMsg is checked and if true the door message will be rendered using positions doorMsgX and doorMsgY.

Lastly, in handleItem(), the key item must be handled and it is relatively easy to handle. Because the key visually is small we ensure the player has overlapped the key both horizontally and vertically by amount keyHorizOffset and if so keyCollected is set to true and the key item is removed from the level.

To go into more detail about doPlyHurtReact(): if the player is already stunned (plyStunned) then the function exits. Otherwise the player's health is reduced by amount decHealthAmount and made sure to not go below 0. If the player's health is now 0 then the player's animation is set to ANIM_ATTACK_ATTACKED_DEAD and the animation frame (playerFrameI) is set to ANIM_FRAME_DEAD to show the player is dead. If the player otherwise still has some health then we make the player stunned by setting the stunned time (plyStunnedtimer) and enabling the stunned state (plyStunned set to true). We find out if whatever hurt the player was on the player's left or right side so that we can set plyStunMoveRight so the player gets pushed away in the correct direction. Lastly we change the player's animation to ANIM_ATTACK_ATTACKED_DEAD and frame to ANIM_FRAME_ATTACKED to show the player is stunned.

The player's stunned state will be updated in plyDoStunned() (called from updateObjs()) and will check whether the player is stunned (plyStunned is true) and if so will decrease plyStunnedTimer until it reaches 0. If the player is still stunned (plyStunnedTimer > 0) then the player will be moved right/left by amount plyStunMoveAmnt but note currently there is no collision detection so it is possible for the player while stunned to go through tiles. If the player is no longer stunned then the player's animation is set back to idle.

A few last things to mention: in drawHUD() near the bottom of the function there is some debug code which can be uncommented and will cause a number of variable values to be displayed while the game is being played. These variables - plyFallSpeed, playerFallTimer, crumbleBlkTileTimer, playerLanded and playerJumpTimer - I needed to monitor to make sure they were behaving as expected. Sometimes we will have a bug which we really can't work out why it exists but once you see the state of a variable it becomes clear what is happening.

In checkKeyBoardInput() we look to see if 'p' was pressed and if that is the case we check that we are in edit mode and that a tile has been picked up. If the tile picked up is the player (editTileType is same as editPlyTileType) then pressing 'p' will cause the player to become a non-player tile, in particular the background tile. This action also returns the player to the previous position so we don't end up deleting the player. Regardless of whether the currently picked up tile was the player we toggle editTileIsPlayer on/off if a tile has actually been picked up.

Adventures_of_Kitty_Wong_1V0

For the video I did on this iteration please see below:

Moving forward the iterations will be named using the game's title (Adventures of Kitty Wong) and version number. As such, this is V1.0 which has the big addition of supporting horizontal and vertical scrolling, as well as other changes and additions that will be specified in the following paragraphs.

The texture playfield_tiles.png has now been increased to 512x512 (previously 128x128) and the various types of tiles have been grouped together. The groups are:

Tiles that act as a solid block to the player.

Tiles that act as items that the player can collect or have other special qualities.

Tiles that appear behind the player (but in front of background tiles) and cannot act on the player.

Tiles that appear in front of the player and cannot act on the player.

Each group appears in the order listed above and because each tile is 32x32 each group contains 64 tiles. This means there are 255 unique tiles in total that a level can use (0 represents no tile which shows through the background), represented by the values 1 to 255 in the level definition array.

In the previous version, when the level is completed or the player dies, the editor had to be entered and 'c' pressed to create the level again in order to play again. Now, when the player dies or the level is completed, a message is displayed and you can press start to play again from beginning. In checkContInput(): when checking the start button, it calls createLevel() to restart the level if the player is dead or the level has been completed, otherwise usual logic follows to pause/unpause game.

Let's talk about the new controls:

In edit mode, press 'b' to select the previous tile group or 'n' to select the next tile group. Note that when a tile group is selected the first tile of the group is shown; if the solid tile group is selected then the first tile is actually blank (will show the sky texture) but the tiles that follow are the solid tiles. When you change the tile type by pressing right mouse click you can easily move through the tiles of each group in sequence. Note that if the player has been picked up you cannot change the tile type or group type.

Additionally, in editor mode you can now use the right mouse button to change the tile type of the tile under the mouse pointer just as you can with a picked up tile.

If you pick up a tile and press 'p' it becomes the player but if you press 'p' again you get the tile back that you had previously picked up and the player returns to the previous position. If you have not picked up a tile and press 'p', if the player not already at that location, the player gets placed there. Pressing 'p' again on the same location doesn't do anything. Because we must have the player in the level we make sure it's not possible to lose the player using 'p' which is why the 'p' key behaves the way it does. This may be changed in the future since we may want levels where there is no player, such as for cutscenes.

Previously, debug information could be displayed by un-commenting various lines in drawHUD() but now the debug information can be toggled on/off by pressing 'q' either when the game is being played or when using the editor. The debug information that is displayed is dependent on what mode you are in; for e.g., when editing, editor related values are shown. One debug value to highlight that is shown when in edit mode is the 'levelDef load(save)' value. This is the level definition save value that was loaded from VMU and in brackets the save version that will be used when the level is saved. This is so that it can be seen what the save version of the level loaded in case it's later discovered there are backward compatibility problems.

Variable dbgDisplayOn when true enables the display of debug values is updated by checkKeyBoardInput() in response to the 'q' key. In drawHUD() dbgDisplayOn and editModeEn are checked to see whether and what type of debug info to display.

Some improvements have been made to the editor: the grid lines are now grey so they don't clash with the tile selector/tiles so much. As well as the selector shown around the tile that is picked up, the selector is also shown on the tile under the mouse pointer when a tile has not been picked up, which makes it clearer what tile will be selected when picked up and that certain actions can be performed without picking up the tile highlighted. Also, to make the tile selector clearer against different tiles, its colour changes over time so it appears to pulse.

A hand icon has been added to editor_icons and is displayed instead of the mouse pointer in edit mode to help show when a tile has been picked up. In drawMousePoint(), if editTilePickedUp is true (tile has been picked up) then frameX is set to EDIT_TILE_GRAB as to render the hand icon.

The mouse pointer looks better now as it previously looked bad because of being scaled up so now it's rendered the same size as from the texture it's contained in; mousePointWidth and mousePointHeight have been reduced from 80 to 64. The grid and tile selector graphics, however, are still rendered at the larger size (same as for the tiles) as they don't look too bad scaled up.

New function drawTileSelector() draws the tile selector so have a single function called from drawPlayfield() and drawMousePoint(). Variable editTileSelColVal is the current tile selector colour value and editTileSelColInc remembers whether to increase or decrease the tile selector colour value (true means increase, false decrease). Function updateEditMode() handles changing the colour of the tile selector, using editTileSelColIncVal to increase/decrease edit mode tile selector colour and editTileSelColMinVal as the lowest colour value.

File editor_icons.png has been updated so the tile selector and grid are white so we can modify their colour.

The image that follows shows the grid lines, the selector around the tile that has been picked up and the hand icon.

In previous iterations scrolling wasn't implemented and the player's position was a screen position. Now the player's position is a level position which is converted to a screen position when rendering the player by determining if the screen has scrolled. A screen worth of tiles are rendered with the player's position determining what tiles are rendered. Smooth scrolling is achieved by determining the scroll offset amount which is used to shift the tiles horizontally and vertically as needed. The DC/KOS seems to handle clipping of tiles partly off screen but we do have to render an extra tile horizontally and vertically to take into account when the first tile is partly off screen.

Currently the level size is fixed measuring 1280 x 960 which gives us 16 x 12 (192 tiles in total), since tiles are rendered as 80x80. The level size was chosen to give a decent size level with primarily horizontal scroll and be able to save within the single block of 512 bytes saved to the memory card/VMU.

In edit mode the level scrolls whenever the mouse pointer is in the middle of the screen unless the beginning or end of the level is reached, similar to how the level scrolls based on the player's position when playing the game. Grid lines now are rendered with scrolling in mind and the tile selector snaps to the grid lines (even when not displayed) if a tile has not been picked up.

The player is no longer stored as part of the level definition and thus the player can now occupy the same place as a tile when using the editor. To pick up the player place the mouse pointer on the player; to pick up the tile behind the player move the mouse pointer to the right of the player but still on the tile you want to pick up.

There is now a new level definition save version (V2) to support the level being able to scroll but the old version (V1) can still be loaded but if then saved it will save as V2. In the previous version (V1) we first saved the level definition number (VMU_lvl_def_ver) and then every tile type was saved from array editPlayfieldTiles. In V2 after saving VMU_lvl_def_ver we save the number of tiles horizontally (playfieldTileTotalX) and number of tiles vertically (playfieldTileTotalY), then we save the player start index X,Y, and lastly we save all tile values to file.

By saving the number of tiles horizontally and vertically rather than the level width and height we can retain the correct size of the level even if we were to change the tile size. Note that editLoadLevel() does not do anything with the values for the number of tiles horizontally and vertically as playfieldTileTotalX and playfieldTileTotalY are currently constants.

Next we will look over what has changed in main.cpp for this new iteration, starting with constants and variables.

With playfield_tiles increased to 512x512 in pixels, up from previous size of 128x128, playfieldTexSize now has the value 512.

Rather than being a constant, tileTypeTotal (total number of different types of tiles) is now calculated as: playfieldTexTileTotalXY*playfieldTexTileTotalXY

So tile type total is now determined based on texture size and individual tile size rather than seemingly a random value.

We introduce a constant, tileGroupTypeTotal, which is the total number of tile groups (solid/item/behind player/front of player) and the array tileGroupTileTotals stores the total number of tiles in each group as currently no group has 64 tiles and we wouldn't want the user cycling through loads of unused tile slots.

Another constant, tileGroupTileMaxTotal, stores the total number of tiles a tile group can have (64) and this in turn sets the value for itemTileTypeI, the tile index for the first item, since items are in the second group. Similarly, solidTileTypeI is given the value 1 as the solid tile group is the first group but the background tile having index 0 is not solid even though it's in the solid tile group.

Even with scrolling, we keep scrnWidth and scrnWidth the same as before (640 and 480 respectively) since the player is still seeing a screen's worth of a level at a time and we still use halfScrnWidth and halfScrnHeight but they are now declared near the top of the program as they are used more than in just one function.

As mentioned, the player only sees a small amount of the level at once and the dimensions of the complete level are stored in levelWidth and levelHeight, with the level size set to 1280 * 960. I have got rid of playfieldWidth and playfieldHeight, replaced by levelWidth and levelHeight.

Constants screenTileTotalX and screenTileTotalY give us the number of tile positions that make up the screen horizontally and vertically with playfieldTileTotalX and playfieldTileTotalY now storing the number of tiles in the entire level horizontally and vertically.

As already stated, this new iteration saves data in V2 format and such VMU_lvl_def_ver now has the value of 2 instead of 1. Because we now have more data to save, constant VMU_lvl_def_lvl_tile_XY_i sets the offset for where we store the number of tiles horizontally and vertically that make up the level. I have also added VMU_lvl_def_ply_start_XY_i for the offset to store the player's start position as tile index values. The code in functions editSaveLevel() has been updated to save the new data version and editLoadLevel() checks the loaded in save version to accommodate V1 save format.

To remember what tile group we are editing in edit mode we use editTileGroupType which defaults to 0 (first tile group) and can have any value up to tileGroupTypeTotal (total number of tile group types) less one. Since tileGroupTypeTotal currently has the value 4, editTileGroupType can range from 0 to 3 to signify tile group 1 to 4 has been selected.

Variable dbgKeyPressVal is assigned the value of the key that was pressed, which comes from keyValue, updated in function checkKeyBoardInput(). The purpose of dbgKeyPressVal is for displaying the key pressed value for debug purposes but note that the key value will only be displayed very briefly when a key is pressed.

Next, looking at the functions we have a new one, convLvlPosToScrnPos() which converts level position to screen position taking into account scrolling. This function is used so that based on the level position (lvlX, lvlY) of an object (player/tile/mouse pointer) we can work out where exactly on the screen (scrnX, scrnY) it needs to be rendered. The function calculates the horizontal and vertical screen positions independently but the logic is the same. I will use the example of calculating the horizontal screen position which is found in the first half of convLvlPosToScrnPos():

We start by setting the default which is that the object is at the start of the level and has not moved at least halfway across the screen horizontally in which case the screen position will be the same as the level position. Next, we check to see if actually the case is that the level position is greater than half screen width but less than the level width minus half screen width, that is, the screen has scrolled but not so much that the end of the level has reached. If this is so then the screen position is simply the position of half the screen width because when there is scrolling the object is actually always at half screen width position.

The last check we do is to see if the level position has passed the level width minus half screen width such that there is no more scrolling, since there is no more of the level to show horizontally. If this is true, the screen position is calculated as:

screen width - (level width - object level position X)

Function drawPlayer() uses convLvlPosToScrnPos() to get the player's current screen position form the current level position and convLvlPosToScrnPos() is also used in drawEditTile() for rendering the tile be edited in edit mode, and in drawMousePoint() for drawing the mouse pointer.

We don't need to change drawPlayfieldBack(), which renders the background tile to every tile position, since we are only rendering a screen's worth of tiles. For rendering the other tiles, however, drawPlayfield() has grown quite a bit since the previous iteration. Which tiles we render to the screen is based on the player's position when not editing the level and the mouse's position when in edit mode. Function calcScrollVal() takes the player/mouse pointer level position and converts in into scroll values scrollX, scrollY. This function takes in a level position (posX, posY) and returns how much the level has scrolled (scrollX, scrollY) and the index of the first tile to be rendered horizontally and vertically (tileStartIndX, tileStartIndY).

The basic idea of drawing the tiles is the same, using the vertical and horizontal loops but due to scrolling we may have to render part of an extra tile at either the very right of the screen or the very bottom of the screen. This is because when the screen has scrolled, for e.g. horizontally, the first tile rendered at the very left of the screen may be partly offscreen and the last tile to be rendered at the right edge of the screen would have moved left away from the screen's right edge. Thus we need to render part of the next tile to fill the gap at the right edge of the screen. Because of having to render an extra tile, in drawPlayfield() we use screenTileTotalY+1 and screenTileTotalX+1 for the loops that handle the drawing of the tiles but we check that we do not access outside arrays editPlayfieldTiles and playfieldTiles.

Variable tileStartX determines the index of the first tile to render at the left of the screen taking into account horizontal scrolling and tileStartY is the index of the first tile to render at the top of screen taking into account vertical scrolling. Function calcScrollVal() will calculate tileStartX and tileStartY based on either the player or mouse position.

For drawing each tile we calculate its position, posX and posY, by multiplying the current tile loop value (tileX and tileY) by playfieldTileSize (tile render size X/Y) and then offsetting the position value by subtracting the modulus of the screen position (scrollX/scrollY) with playfieldTileSize.

Function drawEditGrid() has been removed and the code has been moved to drawPlayfield() to draw the optional grid in edit mode but now the grid takes into account scrolling. The only other main thing to add with regards to drawPlayfield() is that a tile selector graphic is rendered under the mouse pointer when editing a level.

In the drawPlayer() function that calls the main drawPlayer() routine we use convLvlPosToScrnPos() to get the screen position of the mouse pointer's level position so that we can render the player with the mouse pointer in edit mode if the player has been picked up. If the player object has not been picked up while editing the level then the player will be rendered in drawPlayfield(). If we need to render the player while not in edit mode then the player's current level position will be converted to a screen position using convLvlPosToScrnPos().

Changes to function drawMousePoint(): we now use convLvlPosToScrnPos() to get the mouse pointer's screen position (scrnX, scrnY) from its level position (mouseX, mouseY) before rendering the mouse pointer. Variables scrnX, scrnY are then used to render the tile selector if a tile has been picked up.

Another new function is editGetNextTileType() which takes the current tile type (currentTileType) and current group type (currentGroupType) and will increase the input params as necessary to move to the next tile and (if necessary) group so the user can move through all tiles in sequence without having to manually move to the next tile group. Function editGetNextTileType() is called from checkMouseInput() whenever the right mouse button is pressed.

New function editUpdateGroupTileType() is called from checkKeyBoardInput() so that when select next or previous tile group either the selected tile type (editTileType) is updated if a tile was picked up or the tile under the mouse pointer is updated (array editPlayfieldTiles).

In function checkKeyBoardInput() we allow for selecting of the previous/next tile group when in edit mode, by checking the 'b' and 'n' keys and then modifying editTileGroupType before calling editUpdateGroupTileType(). The code for toggling between editing the player and the other tiles (the 'p' key) has been updated to reflect the previously mentioned behaviour.

In the prior version the player only moved one pixel left/right which felt sluggish but now the player will move up to a maximum speed. Player's current speed for left/right movement is stored in plyMoveSpeed and the amount of speed increase each frame is determined by plyMoveIncVal, with plyMoveSpeed limited to the maximum speed plyMoveMaxSpeed while the player is moving along the ground. If the player is falling and moving left/right then plyMoveSpeed is limited to plyFallMoveMaxSpeed. Updating of plyMoveSpeed is handled in checkContInput().

When the player slows down, that is when the player is not pressing d-pad left or d-pad right, plyMoveSpeed is decreased by plyMoveDecVal until 0 is reached. When the player hits a solid tile to the left or right of the player, plyMoveSpeed is set instantly to 0.

Some changes have been made for the player's health: function updatePlayerHealth() must always be used when modifying the player's health, by either passing a positive value to increase health or negative to decrease it. The function makes sure that the player's health never goes below zero or above the maximum.

Whereas in the previous version both the player's health and the points accumulated where simply displayed in text form, now the HUD has been improved (player's health in numerical form is shown in the debug display). Starting with the player's health, instead of showing a number, one of three icons will be shown reflecting the player's current health (the icons have been added to editor_icons.png) and if the player has very low health the icon will flash. The health ranges are:

0-19 1st icon (low health)

20-39 2nd icon (medium health)

40-60 3rd icon (near or full health)

In drawHUD() we render the player health icon using HUD_ply_health_X=HUD_X and HUD_ply_health_Y for the icon position and HUD_ply_health_frame_total for the number of different icons. Function set_HUD_ply_health_icon_index(), called when level starts and whenever player's health changes, works out plyHealthIconFrameI (which health icon to display) by first dividing player's maximum health, plyMaxHealth, by HUD_ply_health_frame_total. Once we have that value we can divide the player's current health, plyHealth, by it so we get the index of the player health icon to be displayed. The only problem with this calculation is if the player has exactly full health the index value will be one too high so we just check for and correct that. With this calculation, regardless of what the maximum health value is only 1 of 3 icons are displayed.

As mentioned, when the player's health is low the health icon flashes to help highlight the situation and this is done in update_HUD_effects(). Variable HUD_ply_health_col_change_count is a counter that handles the delay before changing the player health icon colour when the player is low on health. It is increased until it reaches HUD_ply_health_col_change_delay which then causes HUD_ply_health_col_change_count to be reset to 0 and HUD_ply_health_col_change to be inverted. Variable HUD_ply_health_col_change, which when false doesn't change the icon colour and when true the icon colour is changed.

Function set_HUD_ply_health_icon_index() checks if plyHealthIconFrameI is different to previously and if so we reset HUD_ply_health_col_change_count to 0 and HUD_ply_health_col_change to false to ensure the flashing of the health icon always starts the same.

Lastly, in regards to player health, plyStartHealth has the value of health the player starts on and purposely it is less than the maximum health so that the player starts at a disadvantage.

The number of collected points is now only shown in the debug display and now the HUD shows points collected as a jewel icon with the number of jewels collected (obtained by dividing player points by jewel worth). The '15' item has been changed to a jewel which is also used but scaled down in the HUD.

Added to drawHUD() is the code to display the jewel icon along with how many jewels (calculated from points) have been collected. Constants HUD_ply_jewel_X and HUD_ply_jewel_Y hold the position of the jewel icon in the HUD and in turn the position of the text that specifies how many jewels have been collected.

There is another change to the HUD and that is when the key item is picked up a key is rendered in the HUD, using the same key sprite but scaled down.

When key collected and before it's used, the key sprite is rendered in the HUD as a smaller version. Function drawHUD() renders the key icon if keyCollected is true, using HUD_ply_key_X and HUD_ply_key_Y for the screen position of the key.

Jumping has been improved: startSimplePlyJump(): now returns bool and before starting a jump it checks that there is space above to jump (will not go beyond level top and no solid tile above). Before, the player would always start jumping when told to even if would soon hit something above.

We have a new tile, a lava bubble (added in playfield_tiles), which when touched by the player will cause a jump. However, if touched at the very last moment of the lava bubble appearing, the player will continually ascend by doing a continuous jump and only stop when the player hits a solid object above; to help with the timing you can use d-pad down since that alters the player's falling speed.

To make the player continually jump it's a matter of setting plyJumpAgain to true and in plyDoJump() the variable is checked and if true startSimplePlyJump() is called to make the player jump again. The player has a new animation frame, accessed with ANIM_FRAME_FALL_LONG, which shows that the player is continually ascending (this frame has been added to player.PNG. If, in startSimplePlyJump(), plyJumpAgain is true then playerFrameI (current player animation frame index) is set to ANIM_FRAME_FALL_LONG.

In handleItem(), if an item is detected to be of type EDIT_ITEM_LAVA_BUBBLE and the player has collided with the bubble (using lavaBubbleTileOffset because of the small size of the visible part of the bubble), if lavaBubbleTileSize is at least 0.6 then plyJumpAgain is set to true. Variable lavaBubbleTileSize, the bubble size modifier, is not actually used to modify the bubble's size but that was the original intention. The variable is updated in updateObjAnim(), increasing it by lavaBubbleTileIncVal until it reaches 1 and then starting again from 0, so that the bubble fades in and then disappears.

In drawPlayfield(), if it's detected we're not in edit mode and the lava bubble is to be rendered then lavaBubbleTileSize is used for the tile's transparency and to cause it to move upward somewhat. If lavaBubbleTileSize is too low (0.05 or less) then the bubble is not rendered.

In the prior version there was an issue with the lava tiles in that the player could stand between two lava tiles and not be killed. This has been changed so that any contact with lava kills the player; lava should be within a pit enclosed with non-lava tiles so player has to fall on to lava rather than just be able to walk on to it. Function plyGravityUpdate() checks for EDIT_TILE_LAVA tile and causes the player to be killed by calling updatePlayerHealth() with negative plyMaxHealth.

The lava isn't currently animated but I wanted to make it at least look a bit better so in drawPlayfield() if we detect a lava tile is to be rendered we increase its render size, tileRenderSize, just on the Y axis by lavaTileOverlapIncY but also move it up on the Y axis by the same amount since scaling happens from top left corner. So that the lava tile appears in front of any non-lava tile that may be above it we set its z value, posZ, to DRAW_LVL_4 which is higher than for most other tiles which have Z position of DRAW_LVL_2.

In the image below the lava can be seen as well as the lava bubble that the player is about to land on:

Previously, the background tile was rendered wherever a background tile was placed using the editor, so the tile was rendered multiple times, which limited it to being a plain coloured tile (otherwise you would see obvious patterns). However, I have increased the background tile texture from 32x32 to 512x512 and coloured it to look like a simple sky. Now, in drawPlayfieldBack(), the background tile is rendered once to act as the complete background and will show through where objects are transparent. This will result in a sort of parallax effect since the background isn't moving but the foreground tiles do, although ideally the sky should also scroll but as the level is fairly small it's not so much a problem. There is some banding in the sky image since it's stretched from 512x512 to meet the screen size of 640x480 but I'll be changing how the sky is handled in a future version.

I did originally try having clouds added as tiles, and there is still a tile graphic in playfield_tiles.PNG, but it didn't quite look right. If you look at old games, such as Super Mario Bros., it looks quite convincing (aside from when Mario as at the same height as a cloud) that the clouds are in the distance even though they are on the same plane as Mario. It works since the clouds are tiles with lower priority than Mario and as part of the level they scroll with Mario. Because we don't have such level height as SMB and currently the tiles are scaled up a lot it's harder to fit clouds anywhere but on a separate layer. In a future version tiles will be scaled less or not at all; tiles were scaled up so there were less tiles that needed to be saved since fewer tiles are need to fill the level.

In the screenshot below the very start of the level can be seen with the better looking tiles, the sky in the background, and the player health and jewel icons.

Just a few more things to mention:

To make text stand out (aside from debug text) we now render a portion of the overlay behind the text with colour. We reuse function drawOverlaySetSize() to draw an 'overlay' (as an underlay) at a specified position, size and colour. In a future version, better looking text needs to be used and perhaps a patterned texture behind the text.

I've improved the look of the playfield tiles and tried to make them look more uniform, i.e. all 3D looking, although the player is still 'plain' looking.

The editor can now be entered even when the game is paused. In checkKeyBoardInput(), keys 'e' and 'q' are checked before the editor keys that only respond when in edit mode.

As already previously mentioned the tiles in playfield_tiles have been updated in terms of their look. Another new tile to mention is the horizontal spikes, which are essentially just a rotated version of the up-pointing sprites.

Programming tips, tricks and errors

Compiling

I had trouble trying to get C++ files to compile when using the C++ string class. In my makefile I changed KOS_CC to KOS_CCPLUS and KOS_CFLAGS to KOS_CPPFLAGS and now g++ would be called but still I got errors about an undefined reference to std::basic_string... I put KOS_CCPLUS back to KOS_CC and KOS_CPPFLAGS to KOS_CFLAGS but added -lstdc++:

$(KOS_CC) $(KOS_CFLAGS) $(KOS_LDFLAGS) -o $@ $(KOS_START) $^ -lpng -lz -lm -lstdc++ $(KOS_LIBS)

Now the file compiled correctly even though g++ should have passed -lstdc++ to gcc to force use of the C++ libraries.

Debugging

If you find you have bugs in your software that are very hard to fix and you are sure it's not down to memory leaks and are testing on an emulator try closing and opening it. I had an issue where not only Demul would not respond to the mouse but there were also issues with rendering text.

Emulator

I found the Demul emulator has an issue with graphic update smoothness; enabling vsync helps a bit.

Files

If you want to view the contents of a CDI file you can either burn it to disk and then view the CD contents using your computer's disc drive or you can use a program such as Daemon tools which allows you to mount a virtual CD drive to which you can place the CDI file to view the contents. You can get Daemon tools from:

https://www.daemon-tools.cc/downloads

There are free and paid for versions.

The romdisk facility in KOS is for storing information, files, etc that your program needs without requiring the use of the CD, VMU, etc. to load the information from. It's especially useful if you need a base directory of files to run your program, but you need to swap CDs, for e.g.

One thing to note is the romdisk is linked into your executable, increasing the size of the elf file accordingly. In other words, it's not a good place to store large numbers of files. One piece it's often needed for is to store the stream.drv file for any SPU access.

Graphics

You can use transparent textures with transparency encoded in them, for e.g. by using Paint.NET and save as PNG using the option to auto detect bit detect. Using KOS, when you create the texture using png_to_texture() you must specify PNG_FULL_ALPHA for the alpha type. For pvr_poly_cxt_txr() you can use PVR_TXRFMT_ARGB4444 for the alpha type. Opaque objects must be rendered first followed by translucent ones. This is because the Dreamcast's PVR processes three separate lists of polygons each frame which are opaque first, then those with varying levels of transparency and lastly the polygons have a simple on/off transparency and are called punchthru. You cannot submit a mixture of opaque and transparent polygons during a single frame. Note that transparent polygon processing is slower than processing opaque polygons.

If when using KOS you create a texture with pvr_mem_malloc() and write to it directly and then display the texture using the PVR you will probably see it doesn't look as expected. This is because unless you specify PVR_TXRFMT_NONTWIDDLED as an additional option for the texture format for pvr_poly_cxt_txr() the texture will be twiddled and the memory will not be laid out in a linear way. This caused problems for me as I was trying to use bfont_draw_str() to write text to a texture. The solution is to specify PVR_TXRFMT_NONTWIDDLED for any texture that you or someone else's function expects to write to in a linear way just as you would with video memory. Note that I haven't tried with any filtering turned on which is what twiddling is supposed to help with (as well as boosting performance) but you could turn filtering off (PVR_FILTER_NONE) just for the texture you write to.

KOS has built-in KGL (KallistiOS Graphics Library), based on OpenGL for 3D graphics.

When using KOS, if you don't do a video_set_mode(), you'll get 640x480 with RGB565 automatically.

The Dreamcast PVR does not support indexed geometry.

The Dreamcast supposedly can render 4 million polygons per second but in a typical game which has logic, AI, etc. you're more like to hit 150K polygons per second.

Dreamcast PVR sprites are normal plain quads and can be rendered either textured or non-textured. Because the polygon is a triangle strip in order to render a quad four vertices must be used, forming two triangles. Each vertex is a 32-bit structure containing X, Y, Z coordinates as well as colour values A, R, G, B, texture U, V coordinates and a PVR vertex command.

There is an optimized hardware sprite structure supported by the PVR. Rather than submitting four 32-bit vertices to render a single sprite, you submit a single 64-bit vertex and the hardware is able to extrapolate the rest of the data. The biggest key differences in the data structure is that there is no floating point Z coordinate for the fourth vertex and the colour of the sprite is submitted in the header (rather than individually for each vertex). This means that you must resubmit a polygon header to change colours and that the hardware sprites must be coloured uniformly; no gouraud shading. This does mean, however, you are submitting half of the amount of data to the PVR using this method, so you should be able to get higher throughput.

The Dreamcast BIOS has a built-in font set which can be viewed at:

http://submarine.org.uk/info/biosfont/

There are English and Japanese symbols, as well as Dreamcast and VMU specific symbols. If using KOS, the default text colour is white with black background (the background colour can be made transparent when using KOS text drawing routines).

Input

The Dreamcast keyboard has no LED indicators even though internally the molding has support for three lights and KallistiOS supports LED status lights. If the keyboard doesn't work at all try cleaning port contacts and check inside the keyboard in case the cable has come loose.

Although using KOS you can use MAPLE_FOREACH_BEGIN() and MAPLE_FOREACH_END() to check for any connected controller you have to be careful. For e.g. if you wanted to do switch debouncing, which requires checking if a button has been released, other connected controllers would interfere with that test.

It seems at one point there was a maple_first_controller() and similar functions to return the ID of the first connected controller but those functions have been removed even though they are still referenced on the main site. However, if want to access a device in a specific port then can use maple_enum_type() and maple_dev_status(). However, I found maple_enum_type() to be confusing but after looking at the source code (located in maple_enum.c) it made sense. The function is supposed to 'return the Nth device of the requested type (where N is zero-indexed).' The function can find devices attached directly to the Dreamcast (controller, keyboard, etc.) as well as attachments (vibration pack, rumble pak, etc.). The input parameter 'n' should be 0 if looking for a controller, keyboard, etc. and will search from port A. If checking for an attachment then 'n' specifies which number attachment starting from port A. For e.g., passing 1 for 'n' when looking for a memory card will find the second memory card starting from port A regardless of what controller the second memory card is in. You can check what port a device is connected to by checking maple_device_t.unit.

When using KOS, if you have the keyboard queue on this can cause problems such as if a key is pressed while your game is paused and you then unpause, the game will respond to the key press even though the game was paused. To prevent that happening, when you unpause the game flush the keyboard queue by doing the following:

kbd_set_queue(0);

kbd_set_queue(1);

Which turns the keyboard queue off and on and clears the keyboard queue by nature of changing whether the queue is active or not.

Make

If you get the error when run make:

*** missing separator. Stop.

This is because make expects tabs in the makefile rather than pressing space multiple times (this can happen when copying code from websites, for example).

If you get this error when run make:

*** No rule to make target 'main.o', needed by 'main.elf'. Stop.

This is possibly because the source file is spelled wrong or has a capital letter when it shouldn't.

Use a -DDEBUG option in your makefile so it will only compile in the printf's when you need them.

Memory

If using KOS, use malloc_stats() to get memory status or pvr_get_stats() for PVR stats. Also you can use pvr_mem_available().

Resources

Deamcast technical specification:

https://segaretro.org/Sega_Dreamcast/Technical_specifications

KOS (KallistiOS) main documentation page:

http://cadcdev.sourceforge.net/docs/kos-2.0.0/index.html

The following site has some useful downloads for Dreamcast programmers:

http://sizious.com/download/dreamcast/

It is possible to get the Dreamcast online with a modern Internet connection using DreamPi:

https://segaretro.org/DreamPi

More information on getting the Dreamcast online:

http://www.dreamcastlive.net/connection-guide.html

All content of this and related pages is copyright (c) James S. 2019-2020