Specifically, I'm making a game for the Game Boy Advance... because of course I'm making it for that system :) (love that thing).
Here, I hope to document my process and progress as I go because I think it'll be interesting to look back at it all and see how I've grown. It'll also be a chance for me to document outside of comments and commits why I'm making the decisions that I am. This also means that I'm going to document everything here in the order that it happens, so not everything that relates to one topic or another will be grouped together, sorry.
If you want to check out the GitHub repo, here it is, and all commits that relate to sections in this log are linked at the beginning of their respective section.
This is just the most side-side-project for me right now, so it'll very likely take me forever to finish, if ever. Also, because there will likely be times that I'll have huge gaps between programming sessions, this document will make it nice for me to look back and see what I was doing (again, outside of commits), but hopefully I won't forget too much...
Also, note that I'm using my own assembler for this game, and I'll try to note when I update things. As I start off, I'll be using version 0.1.0.
For sprites and background tiles and stuff, I'm using my GBA OBJ Sprite Maker tool, so the link is here for you too if you wanted it.
For my primary testing, I use no$gba v3.06 debug, so if screenshots look a bit dark or desaturated, that's why: no$gba tries to mimic how the colors would look on a GBA.
Oh, and one more thing, if it wasn't obvious by now, but this will have a ton of spoilers in it, so... do with that as you will.
Something else I want to note so that you don't get confused (although it may still be a bit confusing at first), but I tend to intermix the use of the personal pronouns "me," "I," "we," etc. and related words--like "my" and "our"--to all refer to myself. In a way, I like to think of it as "me," "I," etc. being myself in the present time of writing or myself in the past and "we," etc. being me in all past, present, and future together, if you get what I mean (although, I might also use first-person plural pronouns for me with other folks, so I'll try to make it obvious when I'm using it this way).
This is, of course, going to change over time as I develop new ideas and learn that other ideas are bad, but here is my initial idea for the game exactly as I wrote when I first came up with the game (oh, and yes, I should mention that this game idea came from a dream):
Monster Game (name in-progress)
Game where you go into a haunted mansion or something with "evil" monsters. You are taught that you must capture and kill them (maybe they only fight back once you attack them, except for the boss, which initializes the fight itself). Capture being in like rope or cage or something. You hear or see them scream at you when you enter near or battle with them maybe? (Maybe only first see, then others hear only?) As you ascend the mansion, you reach a really hard boss (the "big boss") that, right as you're about to die, turns you into one of the monsters. Somehow, the player is meant to learn that all the monsters were people (cursed by the mansion trying to entice them to take cursed objects from it) and want to be saved. You come across your original human team, and they try to catch and kill you. You yell who you are, but all they hear are monster noises. You eventually find your way to turn back into a human (maybe by creating something in an abandoned research camp/lab and eating it). When the player comes across a monster now, they are either able to catch and kill it like normal (the team is like, "Where were you?" and you're like "Long story, I'll explain later" or something) or the player is able to cure it just like they had with themself (it shouldn't be super obvious because we want the player to figure it out on their own).
Maybe when the player respawns, they are actually being revived by the team members using their body parts to revive them, unless they were a monster, in which it will load the last save point instead. This means that on a normal death, you don't lose as much (maybe the player will drop some items on the ground where they died), but on a monster death, you lose a lot more. Maybe when you kill monsters, they drop parts that are meant to be given to the researchers, but later they can ask for them back and revive a monster to be able to heal (maybe not all monster parts are just out in the open to be taken because maybe some are being used for research, some taken as souvenirs, and some hidden away in storage. Maybe the player has to trade for some of them with other team members). The team originally came to collect and research the monster parts. Maybe if the player decides to keep killing, they might eventually get a good ranged weapon to be able to use on monsters more effectively because the researchers found a weakness (but think about how to stop players from just killing a bunch, getting the weapon, and then respawning the monsters to get the good ending).
The whole stage of monsters goes as follows: Human -> Youngo -> Boss -> Gonner; where "gonners" are so far gone that their souls have been so corroded away that they can no longer be turned back into humans. These will be the most common type of monster. Youngos and bosses can be turned back into humans (although the big boss requires a special recipe maybe and saving her is the good ending maybe?). At the end of the game, no matter if the player chose to save monsters or kill them, we can show how many monsters they killed, revived, and saved (with saving including themself).
Maybe for the "gonners" and the "youngos," they could have a color tint (or I guess palette set) to indicate difficulty level (okay, now that I think of it, that reminds me of Zelda...)
The crystal on the monsters is the primary way to kill monsters. They can be placed in all sorts of different areas (hard to reach and easy to reach). They correspond to the location of the cursed item from the mansion or town or area or something.
| Initial Monster Concept Art | Monster Name | Monster Type | Cursed Item | Notes |
|---|---|---|---|---|
![]() |
Eldor | Big Boss/Eldest | Necklace | Takes extra potent solution to turn her back into a human. Hairs can be used as whips and shields to push things away and throw things. Maybe can penetrate through the player? Maybe also throw the player. These hairs are kind of used as hands and are used to force the player to have a cursed hat so that they can turn into a monster. The blade legs can poke down and smash things. Can cut. Maybe they can be used a shields? Hairs and arms can be animated sprites, then turn into a dozen or so objects when attacking to have better arbitrary movement? Remember, this only appears in the one room, so there shouldn't be any issue with having a ton of objects like this for one "rope" thing. |
![]() |
Ebbo | Boss/Elder | Sandals | The "foot" part still contains the weak point but is used as a means to push itself around and whip around. The mouth can bite down and do big damage. |
![]() |
Malo-Malo | Boss/Elder | Crown | The legs attack and can be hit to disable the leg. Once all legs are down (maybe change the color when hit to darker or something), the monster falls and the player can jump on top and hit the crystal. After like 3-4 hits (and/or a timer), it launches the player away and gets up. |
![]() |
Thwop | Boss/Elder | Circlet | Explosive balls get shot out the top towards the player and explode after a few seconds. The mouth bites the player if they try to jump in. Spike balls get shot out from the holes on the bottom and roll towards the player (but don't change trajectory). |
![]() |
Stampo | Boss/Elder | Belt | Hitting its eyes coses them (hitting one closes one, hitting the other can close that one too). Once both eyes are closed, the player can walk under its body to hit the crystal. If the player does not close Stampo's eyes, it will drop its body down on the player to attack, knocking the player away before they can hit the crystal. In the room it is in, there should be platforms so that the player can get over to the other side to attack that eye too. The legs slide around to move. |
![]() |
King Gloub | Boss/Elder | Sack | The player can jump over it (with jump upgrade) and attack its crystal, but it turns around eventually (slowish). King Gloub is very slow. When King Gloub swallows the player, the player dies instantly. |
![]() |
Player Monster | Youngo | Hat | The big boss places the hat on the player's head once they get near dead (1 hp). This turns them into a "youngo." |
![]() |
Eb | Youngo | Shoes | Like an inchworm. |
![]() |
Zorb | Youngo | Bracelet | Scurries up to the player and attacks with arm things. |
![]() |
Gonk | Youngo | Mask | Charges forward. Maybe when it hits something, its mouth opens, so the way to feed it is to have it ram into the flask and it'll run into it, break it, and then eat it. (Should we make it so you can't jump over it unless you hit it first or get a jump upgrade or something?) |
![]() |
Gloub | Youngo | Sack | Jumps and/or slides. Bites do big damage, but they're slow. |
![]() |
Finger | Youngo | Ring | Scoots forwards with nail in front. Cuts with its nail to attack. |
![]() |
Stippo | Gonner | N/A | Can run around, jump, and attack; like jump forward, bite, and jump back sometimes. |
![]() |
Blop | Gonner | N/A | Slow but hits harder than other gonners. Maybe their body kind of goes up and down as it moves and can be annoying to hit because of it. |
![]() |
Stamper | Gonner | N/A | Walks normal speed and kicks to attack. |
I think one of the best ways to get to know the feel of a game (besides concept art, of course, which I'm pretty bad at right now) is with music, so I've written a few pieces that I can listen to to get a sense of the game. (I've heavilly compressed these audio files because I feel bad for putting large files willy-nilly on GitHub, so I hope I haven't compressed them too much.)
Title Theme:
Camp Theme:
Big Boss Theme:
I wrote a few more, but I hope these can give you a sense of what I'm going after, or at least for right now.
I know the GBA can do more than simple audio waves, but I'm not really sure what instruments I want to use right now. I'm thinking it might be nice to take advantage of audio channel 1 and 2's square waves in most of the songs just to make things a little easier, but also I like the square wave :]
I just want to create a simple sprite for the main character, if only to use as a placeholder for now; although, now that I've designed him, I might keep him, I don't know yet.
As I started to convert him over to bytes for the game using my OBJ sprite maker, I found things in the tool that I wanted to change/add: being able to remove the gaps and borders between pixels in the editor and being able to right click a pixel to set it to the first palette color (transparent), so I added these features; I'm guessing that I'll be doing a lot of that kind of thing. I'll probably also end up making more tools to help me along the way too.
In setting up the ROM to then display the player, I have to think about how I want the initial state of the game to be; of course, this doesn't have to be permanent and can always also be changed for different scenes in the game too. For now, I think I'll use background mode 0, 1-dimentional OBJ VRAM mapping, and, for now, I'll turn on the OBJ screen (later, I'll turn on some other background screens, but I'll leave that for then): %0001000001000000
For now, I'm just going to place the player idle sprite starting at char 1 and store the player as OBJ 0, but that might change later if I decide if anything should go before the player. I'm also going to set the player palette in palette 0.
So here he is:
...but because the background is black and his outline is black, it's hard to know if we got that right, so I'll set the first background palette color to white for now to make it a little more clear:
There we go, looks good; although, the white background makes the player sprite look a little dark, but in the actual game, I think I want the background to be generally pretty dark, so it shouldn't be an issue (though we'll find out later).
Now that I think about it, I should probably make the sprite a little more vibrant, but I'll do that as I start to design animations later. For now, it'll just feel a bit desaturated.
Of course, it isn't a game if there's no interaction, so I want to read player inputs; for now, I'll just read the d-pad left and right buttons and then flip the sprite based on that direction.
I really don't want to be reading and writing to OAM while the GBA is rendering the screen and I want it to have a little bit of pause between things, so I just wrote in (more like copied from my previous work) a set of instructions that will wait for V-blank ("vertical blank" for the uninitiated, but then, I probably won't explain every piece of jargon, so you guys will just have to pick things up, sorry) to end, and then wait for V-blank to start again. I do this because I want the main loop body to only run once in the vertical-blanking period, so once it runs, it should wait for V-blank to end and then wait for V-blank to start up again before running the body code again; however, this means that there is a chance that the first frame of the game won't be rendered... but, like, I can just make the title screen fade in or something and say it was intentional, sooo....
During that loop body, I have it read the player object's attribute that includes the horizontal flip flag (which happens to be in attribute 1), read the key input (located at $4000130), check the bits for d-pad left and d-pad right, clear or set the horizontal flip flag based on whether d-pad right or d-pad left is pressed respectively, and finally write back to OAM.
In the future, if we end up ever doing game logic while the screen is being drawn, we should should store a copy of some objects' data (or all of OAM depending on what we're doing) in EWRAM and then use DMA to copy that data over to OAM when V-blank starts because we shouldn't write to OAM while the PPU is using it to draw (same goes with palette data, but I don't think we'll be writing to that too often outside initialization).
Right now, it looks like our player is floating in an empty void, so let's add something to it. I drew up a quick floor tile that I'll stick on the bottom of the screen and then position the player to be standing on top of it.
As I'm copying over the palette, I'm remembering that one thing I wanted to implement in my assembler is an align bytes directive that will add a buffer of bytes if needed to make sure that the instructions that come after are aligned well. Right now, I have to manually align things in 4-byte blocks, but it would be nice to be able to do that automatically. I'm a bit busy right now in real life, though, so manual alignment will have to do for now.
Even though I've copied over the background palette and floor tile, they aren't going to show up until I map them on the screen, but it's getting late; I'll have to do that later.
What I want to do is just draw floor tiles on the bottom 3 rows of the screen (technically, I think, these are the top 3 because +y is down... but I'll just be saying "bottom" for this). I think it'll be best to just set them on the tilemap using a loop rather than using DMA and storing it in ROM because it'll be more efficient space-wise (at least for now).
The first thing to do is enable a background layer and set its parameters; for now, I'll use BG0, set the screen base block (tilemap) to block 1, and set character base block (tileset) to block 0. I already copied the tile into block 0 earlier, but I accidentally set it to tile 0, so now the entire screen is just floor tiles .-. easy fix though: just add $20 when copying it.
To map the tiles to the right places, I need to determine the location that those rows start in VRAM. Luckily, once I find the start of the first row to draw, I just need to keep drawing until I hit just past the last row, so I only need to know what the first row is that I want to map to--row 17 (there are 20 rows: row 0 to row 19)--and the last row--row 19. Actually, for the last row, I'm going to use the start of row 20 to test against (just test if the offset is less than that of row 20's start).
Block 1 (where my tilemap is going to be) is located at $6000800, and each row is $40 bytes wide (2 bytes per tile * (256 virtual screen pixel width / 8 pixels per tile) = 64 bytes = $40 bytes). This makes calculating the rows pretty easy: so row 17 starts at $6000C40 and row 20 starts at $6000D00. A simple loop does the trick for writing the tile id/number of the floor tile to the tilemap.
The problem now is that our player is still floating! After a quick repositioning, everything looks good:
Our character can turn, but he can't move... yet. Actually, I think the best thing to move is the background. This will allow us to dynamically add objects to be able to have larger "levels."
The horizontal and vertical background scrolling are controlled seperately, and for now, I just want to mess with horizontal scrolling. First, I wanted to read the value, modify it based on player input, and write the value back, but it seems to be resetting the value every time and doesn't scroll well...
If I just store the horizontal scroll in a register (I should store it in RAM at some point), I can just modify that and then write that to the horizontal scroll, it seems to work well! I think storing this value is also going to be useful for tracking object location offsets and whatnot; this means I should store "world position" seperately from OAM and then update OAM on frames.
It looks like it's scrolling well, but it's hard to tell if it's scrolling all the way. I think adding a block above the floor will let me see, so after setting a block at the beginning of the row 16 and running it, it looks like it scrolls all the way and wraps properly.
First, I want to draw up the player monster sprite to get a feel for designing monsters. While I'm at it, I might as well update the player's palette to be a bit more vibrant.
Here's my updated player sprite:![]()
and my new player monster sprite:![]()
I'm really bad at art right now (I'm trying to learn), so I just really want to thank my friend Caleb for giving me tips to make my terrible art not so terrible.
Anyway, I've also gone ahead and drawn in the Youngos and Gonners. Each one has three difficulty levels: cyan, magenta, and yellow (subtractive color :D ); I've made it so that the color change can be done with a simple palette swap.
| Monster Name | Lvl 0 Sprite | Lvl 1 Sprite | Lvl 2 Sprite |
|---|---|---|---|
| Eb | ![]() |
![]() |
![]() |
| Zorb | ![]() |
![]() |
![]() |
| Gonk | ![]() |
![]() |
![]() |
| Gloub | ![]() |
![]() |
![]() |
| Finger | ![]() |
![]() |
![]() |
| Stippo | ![]() |
![]() |
![]() |
| Blop | ![]() |
![]() |
![]() |
| Stamper | ![]() |
![]() |
![]() |
I haven't put them in the game yet, though; that's for another day.
I'm thinking of recruiting Caleb to join me in making this game because we've been thinking of making a game together for a while now. I'm not really sure how well this'll work out, but we can try!
He came up with a potential game idea while we were chatting: Human After All. I think it sounds amazing, but we'll have to think a little bit more about how the title will go along with the gameplay without spoiling anything.
I think a next logical step would be to add some gravity to the game; well, actually, I'll just add some downward acceleration. To do this, I'm just going to store a vertical/y velocity and add to that every frame (remember, add because down is +y).
Amazing! ...Except for that the player does not stop on the ground, he just goes through and loops around to the top--ever speeding up (just ignore overflow). Adding a simple "ground" as a hard-coded y-level is simple enough; although, later, I want to base this "grounding" on the tilemap, but that's for future me to deal with :)
Again, amazing, but now there's no good way to show it off because our player just stands there or walks horizontally; there's no vertical movement... yet! Adding in jumping is really simple: we just give a little impulse to the player's velocity, and voilà! (There's no animation yet, making it look a little goofy, but, again, that's for future me to handle!)
We don't want the player to be able to constantly apply that impulse, so we can check if the player is grounded. A naïve approach might be to just check if the player's velocity is zero, but one considers it futher, they'll realize that there are certain times when the player has a velocity of zero but is not grounded; for example, think back to physics class: let's say we have a positive velocity with a negative acceleration. The acceleration would decrease the velocity until, oop, we hit zero, then we go negative. This, in a position/time graph, would create an arc (our jump!), therefore our velocity would be zero at the top of the jump too! So, we can just track if the player is grounded seperately from velocity and just set the flag when the player is on the ground and clear it when they start to fall.
I'm thinking that now would be a good time to start storing things on the stack to free up the CPU registers just in case, so I moved the horizontal scroll, player y-velocity, and player grounded flag onto it (bytes at offset $0, $1, and $2 from $3007F00 (the initial address in the stack pointer, AKA sp or r13) respectively).
Oh, yes, and another thing for another day: there's no collision with that block we placed on the ground earlier! We can probably implement that around the same time that we implement using the tilemap for collision rather than hard-coded y-values.
In adding all of this stuff, I've realized that I could really do with some macros in my assembler, but that would require me to add a more sophisticated pre-processor pass, so I'll deal with that when I get around to it, seeing that it's not super important right now.
I guess this section is the start of a really messy branching "oh, I should do this later" thing, but that's just how software development goes, right?
Simply put: I don't want to have my floor be a hard-coded y-value; instead, I want the floor to be based on the tilemap. All I need to do is be able to calculate which tile offset in the tilemap corresponds with the player's position. Easy enough, I can make a formula:
offset = $40 [bytes per row] * floor((x [y-position] + h [sprite height] + 1 [adding one to find tile below]) / 8 [pixels per tile]) + $2 * floor(((x [horizontal scroll] + 112 [player sprite centering] + b [buffer for x-offset in sprite to look at]) & $FF) / 8 [pixels per tile])
This can be simplified to: offset = $40 * ((y + h + 1) >> 3) + ((((x + 112 + b) & $FF) >> 3) << 1)
Which can be simplified even more to: offset = $40 * ((y + h + 1) >> 3) + (((x + 112 + b) & $F8) >> 2)
(This last simplification's a bit trickier to see, but if you do the math, it all works out.)
First, I just tested the center of the sprite to see if it worked, and it did; however, just checking the middle of the sprite does not look so good when the player gets to the edge of a tile because they'll fall part-way across. Instead, the better thing to do would be to check both the left and right of the sprite for floor tiles.
After playing around with the x-offset buffer for both left and right, I think I got it pretty spot-on!
The floor works exactly how I want it to, so I can make platforms and structures that the player can stand on now; however, we did not implement collision with the underside of tiles nor, more importantly, collision with the sides of tiles. This means that our player is able to phase right through walls and ceilings, but not floors! I want to say now that it shouldn't be too hard because we can just do a similar thing there with what we did here, but knock on wood, you know?
First off, I just added an align bytes directive to my assembler (version 0.1.1), so now I can use that in my game.
Anyway, having only floor collision is kind of silly--how am I going to bang my head against the wall if my head goes right through it?
...I spent a long time not working in this, but did I not say this would happen, with this being a side-side-project and all?
I just did a bunch of refactoring to improve the structure of the code to make it easier to work with later; although, that "later" seems to be now. I refactored a lot of things to open up r0-r3 for use in functions, and now I've started pulling things out into functions.
With side collision, I really only need to calculate the offset of the bottom tile in the stack of four tiles that are going to need to be checked on either side of the player because, unlike with the x-axis, the player doesn't need to wrap around, so we can just subtract from the bottom tile's offset to get each one of the higher tiles' offsets (or at least for now; I don't know how I want to approach things later with vertical wrapping, if I even need to that is).
So, it looks like I'm going to need to update my assembler again because I made a silly mistake in shifting the upper nibble of immediate indexing... I accidentally shifted it over 8 bits instead of 4, so it would throw itself into Rt... oops. After a quick easy fix, we're updated to version 0.1.2.
Oh yeah, and collision from the sides now works perfectly! Now onto collision with the ceiling--all we have to do is just check if the block above the player is a floor tile (hmm, maybe I should not refer to it as a floor tile and instead as a collision tile? Eh, floor tile is easier, I guess). In doing this, I had to reorder some logic to be more efficient, but now it works perfectly!
Blocks--not tiles--you know, the blocks of VRAM that the background tilemap is stored in? Yeah, that. I've been considering later implementing something that builds out an entire room/block at once while the player walks around, and I think that'll be easier to do if I am able to build it all at once instead of in columns. I'm not sure yet if I want to build rooms using pre-defined structures or by storing maps in ROM and just straight copying them in, so I'll think about it. For the meantime, we need to add that whole new block buffer thing!
It's just a single bit to flip in screen size settings, right? Well, yes and no. Yes, it is just a single bit flipped to enable two blocks (I want horizontal, so I'll change %00 to %01), but no, our code for horizontal scrolling and collision relies on having only one screen. These are both simple fixes, those being using halfwords to store horizontal scrolling and checking which screen the player when calculating tile offsets respectively.
I feel like it's been a while since I've shown you what the game looks like, so here:
There are three things I want you to notice: 1. I have set the floor to be lower in the new block compared to the old, 2. Those funnily placed tiles are just tiles I placed earlier to test collision things, and 3. Some of the player sprite's colors are a little bit brighter than they were in previous screenshots because I updated the colors in a previous section (it might actually be kinda hard to tell, now that I'm looking at it).
I've been thinking for a bit on how I want to design the entire map, or at least part of it. I'm thinking it would be interesting to make the whole game a single giant dungeon (too large of scope?) where each floor (or set of floors) is a new puzzle area or something. I was also thinking maybe making the first floor be where the game starts and where the camp is, making the top floor be where the big boss is, and then having the player travel all the way back down (fighting researchers maybe?) to get to the basement (which maybe only now they are able to open the door because of an ability only the monster form has or maybe because they are technically wider now so can maybe press two things down at once?) where they'll be able to unlock the stuff to make things to turn monsters back into humans. (Maybe to show the player that they can now open doors by standing on two seperated buttons at once, the big boss room could have its only exit require this. We can also maybe stick it in a few other areas which will hopefully make the player recognize it as something they can do.)
I'm thinking that it'll also be nice for the player to have a map that unlocks rooms/blocks as you explore them. I think it would be interesting to start out with a completely blank map, then have it slowly fill in. To promote exploring connecting areas, I think it would be interesting to have greyed out rooms on the map to represent unexplored rooms that are adjacent to explored rooms.
You know, I'm just now realizing that one could extrapolate another meaning out of this game where inside every monster is a human being (or vice versa), and I think that's alright.