Sega Genesis Programming Part 8: Game State and Pausing


Obligatory Cleanup Section

Each of these articles seems to start with a section where I cleanup baggage from previous iterations. In this round I'm doing some prep work to get this into a place where I'm comfortable uploading it to GitHub. This is nowhere near close to even being 1% complete but there are some issues I had to address before I was comfortable creating a project for it.

There are a lot of hobby projects that borrow assets, often heavily, from commercial games. People put a few years of part-time work into a game only for it to end with a take down letter. I'd hate to spend a couple hours a week on this project for the next 50 or so years just to receive a take down letter at the nursing home. In an attempt to prevent this I had to modify the sprites and music.

The two sprites I created were more or less direct copies of sprites from Phantasy Star II. I spent a little time adjusting them so they were more distinct. The challenge is I wanted to create sprites with real-world human proportions and with 16x32 to work with there aren't many options. The biggest changes I made were to the eyes, shoulder slope, and making the head slightly less exaggerated.

There was one other change to the female sprite I made after an observation from my oldest daughter. She recently got hooked on Final Fantasy X, enough so that recently started the sequel. It wasn't long into the game before she wondered why Yuna, the lead character, ran around with her arms looking all dainty.

Yuna running

My initial reaction was to rationalize this design choice. "No it's not a sexist portrayal of women, actually it's about eth.. ok, you're right." I've never once seen a woman walk around with her arms like that. It's a completely absurd and unjustifiable rendition. This was a character who in the previous game was single-handedly capable of defeating the final boss (not the actual final boss that you can't lose to but the one before). Now she's journeying the world and, in a flip of typical video game roles, trying to save the lead male character from the previous game. In spite of this she runs around like an antiquated stereotype.

This floppy female arm thing goes back a while. At least to the late 80s because it's the default style for female sprites in Phantasy Star II. As a result my initial sprite carried over this look.

Original female sprite

So I fixed this so she now has arms positioned like a real person. The new sprites aren't radically different but they're different enough to move on.

New sprites

The other part I had to cleanup was the music. The background music I've been using is based on an .xm file that was free to download but not free to use in other projects. So in keeping with the setting of the game, 1989 holiday shopping season, I butchered a public domain Christmas song for the new background.

I have no musical training or skills whatsoever. When I was a kid, like 10-11ish, I figured out how to read sheet music and convert it to Apple II sounds. I had a lot of free time and curiosity back then. My father would have preferred if I played Little League instead of sitting in front of a computer add day but which one is paying the bills now?

Armed with some sheet music, and a copy of MilkyTracker, I painfully transcribed a short song to a series of notes. These tutorials were a great help to get started.

MilkyTracker

The end result only slightly resembles the source song but more importantly, it sounds like a real song. Not a good song, but something that sounds like RPG shop music. I'm sure you're familiar with the perky music that plays in the background of shops all over the virtual universe. It sounds fine until about the 10th loop then you're sick of it. Next time around I'll have to expand it to something longer.

Now that I have a non-copyright-infringing song I need to fix something else from very early on...

Loading Instruments

A couple months ago I hooked up the Echo sound engine to this tiny demo. Once I heard music playing I declared victory and moved on. The mistake that wasn't obvious to me at the time was I failed to load all the included instruments. I was so happy to just get it working at all that I skipped the rest of the setup. Echo includes 20 instruments and you need to load the ones you want to use.

Here's where I ran into an exciting new problem. The samples for loading Echo instruments wouldn't compile in vasm. The author of Echo noted that they used asm68k to build it. Although there are a couple tools called asm68k, in the Genesis programming community this typically refers to asm68k.exe in the leaked Psygnosis development kit. When I started this crazy idea I tried this kit out and got it working just fine in FreeDOS and MS-DOS, the latter of which is almost certainly what it was designed for. I decided against using it because I was concerned about the legality of it. It's unlikely that Sony, who I believe owns all the Psygnosis assets, would decide to care about 25 year-old build tools. However, they could theoretically decide to crackdown on distribution of them at any time for any reason.

As far as I can tell there are some differences between the Psygnosis asm68k and vasm in how they handle relocation. I'm still not remotely close to being an expert on 68000 assembly so I'm not entirely sure what the difference is but here's an example...

In the Echo samples instruments are loaded like this:


Echo_ListEntry macro addr
 dc.b $80|((addr)>>8&$7F) ; High byte of address
 dc.b (addr)&$FF ; Low byte of address
 dc.b ((addr)>>15&$7F)|((addr)>>16&$80) ; Bank number
endm
[...]
PointerList:
Echo_ListEntry Instr_PSGFlat ; $00 [PSG] Flat PSG instrument
[...]
Instr_PSGFlat:
dc.b $FE,$00,$FF

In the vasm this produces an error:


illegal relocation
$80|((addr)>>8&$7F)

This could be some kind of ordering issue where vasm is applying the macro first but since the label doesn't have an address yet it can't continue. If the label already had an address then maybe it would be fine. Perhaps Psygnosis asm68k handles these in the opposite order as vasm. After some trial and error I resolved this in a very not elegant way by hard-coding the instrument addresses:


ROM_ADDR_INSTRUMENTS=$80000
[...]
INSTR_00=ROM_ADDR_INSTRUMENTS
INSTR_01=INSTR_00+(Instr_00_End-Instr_00_Start)
[...]

EchoPointerList:
 Echo_ListEntry INSTR_00 ; $00 [PSG] Flat PSG instrument
[...]
 org ROM_ADDR_INSTRUMENTS
[...]
Instr_00_Start:
 dc.b $FE,$00,$FF
Instr_00_End:

What I'm doing here is hard-coding where addresses are in the ROM. Right now my little demo is nowhere near this large so there's not a chance of the main program or graphics colliding. From there I'm setting the address of each instrument based on the size of the instrument before it. The Echo_ListEntry macro now compiles happily. After doing this the instruments are loading and even sound pretty nice. I'm mostly using the MIDI piano instrument right now, as I get a little better creating background music I'll experiment with the others.

Also, I think I finally fixed sprite collision detection for real this time, no code samples for this because the fix turned out to be mostly tweaking a couple values.

As for things that are still problems...

Sprite draw order: This might have something to do with the sprite link field which I don't completely understand. The documentation on it is a little head-spinning. The brute-force approach to this would be breaking the sprites into two sprites that occupy the high & low planes. I don't want to take that route yet though so I need to spend time researching this further.

Game State & Pausing

It's time to implement something new again. I started on a rather large bit of work, opening and closing dialogs that display text based on what the player sprite is facing, and decided to break that into a few smaller pieces. It's been a while since I posted something and didn't want to keep that trend going.

Introducing dialogs, menu screens, maps, and so on requires updating the main game loop to be aware of what state the game is in. I think the next few additions I make will be around managing the transition between game states.

Game state transitions

In this article we're going to build out the initial plumbing for this. Since the breakneck action in this game is too much for most to handle, let's implement pausing first. We'll start by adding some game state flags and a memory location to track it.


; game states
STATE_FLAG_PAUSED=$F ; game is paused
STATE_FLAG_EXPLORING=$0 ; player is controlling the sprite on the map
STATE_FLAG_DIALOG=$1 ; player is interacting with a dialog
[...]
InitGameState:
 move.l #$00000000,d0
 bset.l #STATE_FLAG_EXPLORING,d0
 move.l d0,(MEM_GAME_STATE)

In its simplest form pausing is just a matter of checking a flag and doing nothing if it is set. We should also pause and resume the new background music because it would be odd not too. We'll go a little further and also change the palette to grayscale just because I think it's nifty when games do that.


MainGameLoop:
 bsr.w WaitVBlank ; wait for vblank to complete
 addq #$1,(MEM_MAINLOOP_COUNTER) ; increment counter
;-------------------------------------------------------------------------------
; pause/unpause
;-------------------------------------------------------------------------------
 move.l (MEM_GAME_STATE),d7 ; copy current game state to d7
 move.b (MEM_CONTROL_PRESSED),d6 ; copy pressed buttons to d6
 andi.w #BUTTON_START_PRESSED,d6 ; test if the start button was pressed
 beq.s TestPause ; start button is not pressed test if still paused
 move.w #$2700,sr ; disable interrupts while changing the pause state
 ; clear MEM_CONTROL_PRESSED to prevent pause state from flipping in loop
 move.w #$0000,(MEM_CONTROL_PRESSED)
 btst.l #STATE_FLAG_PAUSED,d7 ; is the paused bit set?
 bne.s Unpause ; if so branch to unpause 
Pause:
 bset.l #STATE_FLAG_PAUSED,d7 ; set the paused bit
 ; flip to paused palette
 bsr Echo_StopBGM ; pause the background music
 lea PaletteStoreAPaused,a0 ; address of the starting palette to a0
 move.w #$0004,d0 ; number of palettes to load
 bsr.w LoadPalettes ; branch to LoadPalettes subroutine
 bra.s UpdatePause ; save the paused state
Unpause:
 bclr.l #STATE_FLAG_PAUSED,d7 ; clear the paused bit
 bsr Echo_ResumeBGM ; unpause the background music
 ; revert to normal palette
 lea PaletteStoreA,a0 ; address of the starting palette to a0
 move.w #$0004,d0 ; number of palettes to load
 bsr.w LoadPalettes ; branch to LoadPalettes subroutine
UpdatePause:
 move.l d7,(MEM_GAME_STATE) ; save the new paused state
 move.w #$2000,sr ; re-enable interrupts
TestPause:
 btst.l #STATE_FLAG_PAUSED,d7 ; test if paused
 beq.s TestExploring ; not paused, branch to update sprites
 bra.w MainGameLoop ; paused, return to start of game loop
;-------------------------------------------------------------------------------
; determine if sprites need to be moved and move them
;-------------------------------------------------------------------------------
TestExploring:
 btst.l #STATE_FLAG_EXPLORING,d7 ; test game state
 beq.w MainGameLoopEnd ; not exploring, branch to next test
MainGameLoopUpdateSprites:
[...]

This is what everything looks like when it's paused:

Paused game

I know this isn't the most impressive bit of work but with all the fixes I worked in, I'm mostly happy where this is at. Next time around I'm planning to start on interacting with objects via dialogs, and perhaps finally getting the sprite overlap fixed.

Download

Download the latest source code on GitHub




Related