An RPG on Uzebox
Posted: Tue Jul 17, 2018 10:32 pm
Check out the game page!
https://nicksen782.net/r_uzerpg.php
... There are some short demo videos there too.
// OVERVIEW
I have been building tool and doing tests for this since April. This is not intended to be a clone of an existing game. Not a faithful clone anyway.
It is a merging of several game ideas that I liked. One of the more important parts to an RPG is the story. Story means dialog and dialog means bytes.
A whole fontset (64 font tiles) is 4096 bytes. Right now the Uzebox API Print functions work with a FLASH-based fontset.
In fact, for development/debugging I am going to continue to use a FLASH-based tileset. However, I would like to be able to change colors which means I need ram tiles.
I have roughly 200 bytes free RAM right now with 36 ram tiles. I only have 4 sprites configured but that is due to the way the game will work. It was easier if the player character is a sprite.
// RAM TILE SPRITES
I did experiment with ram tile sprites and was successful. The cost is the cost of blitting that sprite plus however many tiles are needed for the source of that sprite.
For example, a 2x2 sprite would need 4 sprites to blit and 4 sprites to source. So, if a 2x2 can take a max of 9 to blit (overlap on both x and y) then the cost is 12 tiles.
However, if you draw the same sprite somewhere else also then the cost is just the extra blitting cost since you only pay the source cost once.
This is expensive but does allow for a possibility of sprite tricks where you change the sprite source to ram and then work with it.
This would be an impromptu and quick thing since even though I have so few sprites I really am using those ram tiles.
// PSEUDO SPRITES
To save on the cost of the ram tile source and blit I came up with a ram tile solution that doesn't use sprites but appear like sprites.
These sprites (as of now and likely to stay this way) remain aligned on x and y just like any other tile. In fact, I'm just drawing ram tiles with SetRamTile().
The trick is the blitting. Blitting is replacing a "translucent" color with the color whatever is directly "behind" it.
This is why I needed to know what the active tileset was since I needed to directly read tile data in order to do this. The result is a ram tile pseudo-sprite.
My pseudo-sprites come in two-frame and one-frame types. The two-frame ones are just two frames. The one-frame ones use a software x-flipping.
In many games a character appears to "dance". They aren't dancing. They are being drawn and then being drawn again mirrored. This is a common trick to reduce the tile count in games. It looks like the character is moving. It isn't of course but it looks that way.
A pseudo-sprite cannot take advantage of the kernel's built-in x-flip functions (PLEASE CORRECT ME IF I AM WRONG.) So, I wrote one in C.
You would think that this would be terribly slow and it likely is but the game did not slow down. I didn't' notice additional lag.
The x-flipping happens a couple times per second so if it were to slow things down I think I would have noticed (PLEASE FEEL FREE TO CORRECT MY UNDERSTANDING ON THIS.)
So, for the ram tile pseudo sprites I have fixed tilemaps and disabled tile de-duplication when creating the tileset for these.
Why? Because de-duplication leads to some unpredictability in tile ids.
I actually have a method of drawing tiles AND the tilemap where the map itself is in SPIRAM and read from for each draw. I may consider this for the pseudo ram tile sprites in the future.
// SPI RAM
This uses the SPI RAM heavily. Not only is much of the graphics in SPI RAM but music is streamed from there as well. Thus far this is working out. I have a 16 byte music streaming buffer.
The SPI RAM is plenty fast. If you reduce the number of rendered video lines then it is even faster (just do it for a quick moment while reading in the more heavy cases.)
The SD CARD is just used as mass storage and contains the file that gets loaded into SPI RAM. I intend to do game saves to the SD CARD and I may move dialog there too.
// RAM TILES, RAMTILE MAPS, FLASHTILE MAPS STORED IN SPIRAM GRAPHICS
Due to the speed of the SPI RAM I can store both the tileset AND the tilemap in SPIRAM. I only need to cache the ram tiles. I actually keep the map in SPI RAM too.
So, a screen could use FLASH tiles and be huge. For instance, a conservative 28x28 tile screen takes 784+2 bytes for the tilemap.
Screen maps should not be stored in FLASH. I store them in SPIRAM in binary. Using a system of offsets I can point to any of those maps and then draw them into VRAM.
I can do the same thing where both the TILE and the MAP are stored in SPIRAM. In this case I can use up to my ram tile limit in unique tiles and draw whatever I want.
In fact, I have experimented with combining FLASHTILE and RAMTILE maps together (draw one and then the other) and can have both on screen.
It just works well. The only hard part is creating the system to manage it all.
// SCREEN DRAWING AND "SCROLLING"
I am not using smooth scrolling. This removes some kernel complexity and it seems fine this way.
I still do want to make the screen look like it is shaking (for example upon an attack.)
// DIALOG
RPGs are usually big on dialog and story and I knew that this would likely be the most critical feature of the game.
Each dialog has a "Chathead" available. It is paletted on the fly so there is a base tileset for a particular chathead and then a palette is used to replace the source colors.
This really is a seek out and replace operation. I change the ramtile itself which is how the Chathead is brought it.
Upon changing the source no further repaletting needs to happen (although it could if you wanted.)
A chathead is a 2x3 tilemap. A map would take 8 bytes and each Chathead frame would need 6 tiles. However, these tiles ARE de-duplicated. So far, for three 2x3 frames I only need 10 ram tiles.
An 11th tile is reserved but interpreted like data. The first 32 bytes are reserved for palette data and the last 32 bytes actually store the tilemaps for each frame.
I actually still have some bytes left over too!
So, a loaded Chathead with 3 frames takes 11 ram tiles. That leaves me with 25 ram tiles remaining. I really wanted to have the fontset in ram tiles so that I could adjust colors and to save the 4k of FLASH.
Most English phrases typically use 18 - 23 unique letters. I have 25 available. The trick is to make sure that you limit the unique characters in whatever message you create.
The length of the message is not important here other than a length choice (of which I have alloted up to 256 characters currently.)
So, for each separate dialog you need to load a custom fontset based on the unique letters of the dialog and change the colors of each tile on the fly.
This does not take as much time as it sounds like it would. Take a look at the demos.
The next step is handling the drawing of the text. I wanted text to be drawn out quickly letter by letter (not a page at once).
So, I keep track of x and y. Once y gets low enough I quit changing it and just shift vram up instead. It looks like scrolling.
Each separate dialog can load custom font colors and a customized Chathead. Currently, I am thinking of doing this game WITHOUT a "silent protagonist".
Perhaps the player can choose a Chathead for themselves at the beginning of the game.
// HELP?
Size of a tile:
I like mode 3 and I have written a large number of tools for it. However, if there was one thing that I would change it would be the overall color palette.
There are lots of colors available and many look very similar. I'm not doing a high-resolution game here.
I would assume that this would make the tile size (in bytes) smaller. That means more unique tiles and more ram tiles. I am really pushing my ram tiles.
A few more at the cost of colors I won't be using anyway would be awesome.
// VERSIONS:
Presently there are only two versions. V1 and the in-progress V2.
V1 is largely a demo of several early key parts of the game engine:
Load/draw the screen.
Scroll the view (like a pseudo camera)
Load/draw NPC graphics that are specific to a screen.
Animate those graphics.
Handle the transistion between screens (border exits.)
Handle the transitions into "doors". I refer to these as "teleports". Basically a square on the map that you walk on and then end up somewhere else.
Streaming music and changing (or not) the currently playing song based on which screen you are on.
Be sure to check out the Tavern.
Be sure to check out the flower screen found two screens down from the start screen.
V2 is still in progress but has additional features:
Improved NPC graphics system.
Improved screendata system (has a GUI now. It was a nightmare in V1 with all the manual data editing, especially for border exits and teleports!)
Full RAM-based animations.
Screen maps can be sourced from binary in SPIRAM.
The initial and subsequent data loads are faster.
Dialog. You can now "speak" to NPCs. Each side of the conversation (can be more than 1 side) can have a custom colored font and custom colored avator ("Chathead").
Dialog can be displayed normal, fast, or immediate.
https://nicksen782.net/r_uzerpg.php
... There are some short demo videos there too.
// OVERVIEW
I have been building tool and doing tests for this since April. This is not intended to be a clone of an existing game. Not a faithful clone anyway.
It is a merging of several game ideas that I liked. One of the more important parts to an RPG is the story. Story means dialog and dialog means bytes.
A whole fontset (64 font tiles) is 4096 bytes. Right now the Uzebox API Print functions work with a FLASH-based fontset.
In fact, for development/debugging I am going to continue to use a FLASH-based tileset. However, I would like to be able to change colors which means I need ram tiles.
I have roughly 200 bytes free RAM right now with 36 ram tiles. I only have 4 sprites configured but that is due to the way the game will work. It was easier if the player character is a sprite.
// RAM TILE SPRITES
I did experiment with ram tile sprites and was successful. The cost is the cost of blitting that sprite plus however many tiles are needed for the source of that sprite.
For example, a 2x2 sprite would need 4 sprites to blit and 4 sprites to source. So, if a 2x2 can take a max of 9 to blit (overlap on both x and y) then the cost is 12 tiles.
However, if you draw the same sprite somewhere else also then the cost is just the extra blitting cost since you only pay the source cost once.
This is expensive but does allow for a possibility of sprite tricks where you change the sprite source to ram and then work with it.
This would be an impromptu and quick thing since even though I have so few sprites I really am using those ram tiles.
// PSEUDO SPRITES
To save on the cost of the ram tile source and blit I came up with a ram tile solution that doesn't use sprites but appear like sprites.
These sprites (as of now and likely to stay this way) remain aligned on x and y just like any other tile. In fact, I'm just drawing ram tiles with SetRamTile().
The trick is the blitting. Blitting is replacing a "translucent" color with the color whatever is directly "behind" it.
This is why I needed to know what the active tileset was since I needed to directly read tile data in order to do this. The result is a ram tile pseudo-sprite.
My pseudo-sprites come in two-frame and one-frame types. The two-frame ones are just two frames. The one-frame ones use a software x-flipping.
In many games a character appears to "dance". They aren't dancing. They are being drawn and then being drawn again mirrored. This is a common trick to reduce the tile count in games. It looks like the character is moving. It isn't of course but it looks that way.
A pseudo-sprite cannot take advantage of the kernel's built-in x-flip functions (PLEASE CORRECT ME IF I AM WRONG.) So, I wrote one in C.
You would think that this would be terribly slow and it likely is but the game did not slow down. I didn't' notice additional lag.
The x-flipping happens a couple times per second so if it were to slow things down I think I would have noticed (PLEASE FEEL FREE TO CORRECT MY UNDERSTANDING ON THIS.)
So, for the ram tile pseudo sprites I have fixed tilemaps and disabled tile de-duplication when creating the tileset for these.
Why? Because de-duplication leads to some unpredictability in tile ids.
I actually have a method of drawing tiles AND the tilemap where the map itself is in SPIRAM and read from for each draw. I may consider this for the pseudo ram tile sprites in the future.
// SPI RAM
This uses the SPI RAM heavily. Not only is much of the graphics in SPI RAM but music is streamed from there as well. Thus far this is working out. I have a 16 byte music streaming buffer.
The SPI RAM is plenty fast. If you reduce the number of rendered video lines then it is even faster (just do it for a quick moment while reading in the more heavy cases.)
The SD CARD is just used as mass storage and contains the file that gets loaded into SPI RAM. I intend to do game saves to the SD CARD and I may move dialog there too.
// RAM TILES, RAMTILE MAPS, FLASHTILE MAPS STORED IN SPIRAM GRAPHICS
Due to the speed of the SPI RAM I can store both the tileset AND the tilemap in SPIRAM. I only need to cache the ram tiles. I actually keep the map in SPI RAM too.
So, a screen could use FLASH tiles and be huge. For instance, a conservative 28x28 tile screen takes 784+2 bytes for the tilemap.
Screen maps should not be stored in FLASH. I store them in SPIRAM in binary. Using a system of offsets I can point to any of those maps and then draw them into VRAM.
I can do the same thing where both the TILE and the MAP are stored in SPIRAM. In this case I can use up to my ram tile limit in unique tiles and draw whatever I want.
In fact, I have experimented with combining FLASHTILE and RAMTILE maps together (draw one and then the other) and can have both on screen.
It just works well. The only hard part is creating the system to manage it all.
// SCREEN DRAWING AND "SCROLLING"
I am not using smooth scrolling. This removes some kernel complexity and it seems fine this way.
I still do want to make the screen look like it is shaking (for example upon an attack.)
// DIALOG
RPGs are usually big on dialog and story and I knew that this would likely be the most critical feature of the game.
Each dialog has a "Chathead" available. It is paletted on the fly so there is a base tileset for a particular chathead and then a palette is used to replace the source colors.
This really is a seek out and replace operation. I change the ramtile itself which is how the Chathead is brought it.
Upon changing the source no further repaletting needs to happen (although it could if you wanted.)
A chathead is a 2x3 tilemap. A map would take 8 bytes and each Chathead frame would need 6 tiles. However, these tiles ARE de-duplicated. So far, for three 2x3 frames I only need 10 ram tiles.
An 11th tile is reserved but interpreted like data. The first 32 bytes are reserved for palette data and the last 32 bytes actually store the tilemaps for each frame.
I actually still have some bytes left over too!
So, a loaded Chathead with 3 frames takes 11 ram tiles. That leaves me with 25 ram tiles remaining. I really wanted to have the fontset in ram tiles so that I could adjust colors and to save the 4k of FLASH.
Most English phrases typically use 18 - 23 unique letters. I have 25 available. The trick is to make sure that you limit the unique characters in whatever message you create.
The length of the message is not important here other than a length choice (of which I have alloted up to 256 characters currently.)
So, for each separate dialog you need to load a custom fontset based on the unique letters of the dialog and change the colors of each tile on the fly.
This does not take as much time as it sounds like it would. Take a look at the demos.
The next step is handling the drawing of the text. I wanted text to be drawn out quickly letter by letter (not a page at once).
So, I keep track of x and y. Once y gets low enough I quit changing it and just shift vram up instead. It looks like scrolling.
Each separate dialog can load custom font colors and a customized Chathead. Currently, I am thinking of doing this game WITHOUT a "silent protagonist".
Perhaps the player can choose a Chathead for themselves at the beginning of the game.
// HELP?
Size of a tile:
I like mode 3 and I have written a large number of tools for it. However, if there was one thing that I would change it would be the overall color palette.
There are lots of colors available and many look very similar. I'm not doing a high-resolution game here.
I would assume that this would make the tile size (in bytes) smaller. That means more unique tiles and more ram tiles. I am really pushing my ram tiles.
A few more at the cost of colors I won't be using anyway would be awesome.
// VERSIONS:
Presently there are only two versions. V1 and the in-progress V2.
V1 is largely a demo of several early key parts of the game engine:
Load/draw the screen.
Scroll the view (like a pseudo camera)
Load/draw NPC graphics that are specific to a screen.
Animate those graphics.
Handle the transistion between screens (border exits.)
Handle the transitions into "doors". I refer to these as "teleports". Basically a square on the map that you walk on and then end up somewhere else.
Streaming music and changing (or not) the currently playing song based on which screen you are on.
Be sure to check out the Tavern.
Be sure to check out the flower screen found two screens down from the start screen.
V2 is still in progress but has additional features:
Improved NPC graphics system.
Improved screendata system (has a GUI now. It was a nightmare in V1 with all the manual data editing, especially for border exits and teleports!)
Full RAM-based animations.
Screen maps can be sourced from binary in SPIRAM.
The initial and subsequent data loads are faster.
Dialog. You can now "speak" to NPCs. Each side of the conversation (can be more than 1 side) can have a custom colored font and custom colored avator ("Chathead").
Dialog can be displayed normal, fast, or immediate.