Sega Genesis Programming Part 14: Selections



Choosing a menu style

In this installment I'm going to attempt to implement some really basic menus where the player can select stuff and then other stuff happens. I know, that's all very specific. The first step is figuring out what style of menus to implement.

There are really two different menu questions... 1) What happens when the player interacts with a person or object (A button press)? and 2) What happens when the player opens the menu (C button press)? Right now I'm going to tackle the first one unless I'm especially productive.

I'm going to start by looking at a few games that are inspirations for this crazy project. Phantasy Star II has obviously been a big inspiration so it's first on the list. In it, the player interacts with NPCs by walking up to them and pressing A. The C button opens the menu, selecting an item opens another menu and so on until the screen is covered:

Phantasy Star II menus

The talking to NPCs part is fine but menu part is a little iffy. It's neat that the player is never taken off the overworld screen but it's easy to get lost in the menus. In this example there are three top level windows with active selections. Giving or using an item opens a fourth. It's kinda confusing.

The next game in the series uses the A button to talk to NPCs and the C button now opens a full-screen menu:

Phantasy Star III menus

I prefer this style even though it could require building character portraits which is something I'll be terrible at.

Phantasy Star IV went back to a Phantasy Star II menu system but swapped the A and C buttons just to mess with people:

Phantasy Star IV menus

The original Phantasy Star was a bit different. Walking into NPCs triggered a dialog screen. The menu took up the whole screen like Phantasy Star III but used a window system like II & IV.

Phantasy Star menus

I really like having the current scene background in the menu. Phantasy Star III would look amazing if it did that instead of the monochrome background for example. This fits into the "cool idea that's a bunch of work so I probably won't do it" category.

Jumping over to the Super Nintendo, Final Fantasy IV uses a system like Phantasy Star III with talk and menu buttons:

Final Fantasy IV menus

Of course it's worked this way since the beginning of the series:

Final Fantasy menus

Earthbound isn't a huge inspiration other than the setting. At the risk of making new enemies, I found Earthbound to be a mostly aggravating game. Now some of that could be based on when I played it. My biggest complaint is all the repetitive grinding required to not get killed constantly. Phantasy Star II also suffers this problem. The difference is I played Phantasy Star II when it was new-ish and I had endless free time. I played Earthbound just a couple years ago. I bet if I played Phantasy Star II for the first time tomorrow I'd hate it.

Anyway, the menus use a window style. Talking to people isn't automatic, it's one of the options on the menu if the player is facing someone:

Earthbound menus

Going back the Genesis (err... Sega CD) for a moment. Lunar: The Silver Star is a great game with a menu that's more style than function:

Lunar: The Silver Star menus

OK, so the last game we're going to look at is on the Super Nintendo and it's one of my favorites - Shadowrun. It handles this differently than every other game here. The one part that's similar is the full screen menu:

Shadowrun menu

Interacting with characters and items is where it gets interesting. The player selects something on the screen and is presented with a menu of options. From there they choose what action to take. Examining an NPC shows some text while talking to them might show a quick response from them:

Shadowrun talk menu

More often, talking to an NPC opens a dialog where the player can ask about keywords learned throughout the game:

Shadowrun dialog

This is really what I'd like to build in the long run but I have to start smaller. Character portraits, keywords, and a more advanced script are all great but if I over-complicate this I'll never finish. So I'm going to start small and maybe build-up to something grander once I get the basics working.

There are a few options to consider. One is showing a list of possible actions when the player presses A while facing an NPC:

Menu option #1

When facing an object the options could be slightly different:

Menu option #2

Pressing C could then open the menu:

Menu option #3

Hmm... I don't like that idea. I'll build a real full-screen menu later.

Another option is showing a brief object description above the options:

Menu option #4

Here's how that would look with an NPC:

Menu option #5

I'm leaning toward the last idea. There are some pros and cons, OK just one of each I can think of right now:

Pro: One button press to access to all actions you can perform against stuff.

Con: Two button presses to interact with stuff. It could be possible to just default to look or talk based on some rules.

And last up is a slightly more advanced option to enable dialog between the player and an NPC. This is less sophisticated than Shadowrun but still gives the player a way to choose how they interact with NPCs.

Menu option #6

I am also leaning toward building this because it's a simple interface to allow choices. Let's face it, every RPG/adventure game has a bit where the player talks to someone and it goes like "Here's this horrible problem I'm facing, can you help me?" followed by a yes/no option. Selecting "yes" advances the plot while selecting "no" gives the player a chance to go do other stuff before this new quest. It seems like something I'll need to have eventually anyway. This style of dialog could also be used for "There's a shiny red button here. Do you want to press it?"

If these last two ideas are used then there are something like 3 different dialog modes:

Dialog mode Usage A/C button action B button action D-pad action
Simple text Non-interactive messages Advance dialog, close dialog when on last page Advance dialog, close dialog when on last page Advance dialog, close dialog when on last page
Text with choice Interactive dialog Advance dialog, select items when on last page Advance dialog, cancel menu when on last page Advance dialog, move between selections when on last page
Overworld menu Interact with objects & NPCs Select items Cancel menu Move between selections

This doesn't seem all that difficult to tack on to the existing demo. I will likely regret this statement as I attempt to implement it...

Showing the menu

The first step to building interactive menus is just drawing them. Even before that let's define what our action codes are. The first three map to the buttons in the 'A' button menu. The captions on the menu change based on whether it's an NPC or object so these are mapping to the menu order. This makes life easier later. Also going back to the last article, limiting the number of actions limits the size of the action table which is good. The fourth action will be used for when the player responds to a "yes/no" type question.

ACTION_LOOK=$0000 ; look at an object or NPC

ACTION_USE_TALK=$0001 ; use an object or interact with an NPC

ACTION_TAKE_GIVE=$0002 ; try to take an object or give an item to an NPC

ACTION_RESPOND=$0003 ; respond to a selection in a conversation dialog

Next are some new dialog flags that effect how the dialog behaves. Three of these are based on the style of menu being shown while the last will be used to flag that the dialog should remain open until the player makes a selection.

DIALOG_FLAG_STYLE_MENU=$17 ; overworld menu style dialog

DIALOG_FLAG_STYLE_TEXT_CHOICE=$18 ; dialog has text with selection at the end

DIALOG_FLAG_STYLE_SIMPLE_TEXT=$19 ; dialog is a simple text dialog

DIALOG_FLAG_SELECTION_WAIT=$1A ; waiting for the player to make a selection

Now we need to create the text for the menus. The '{' character is being re-purposed to serve as an empty selector spot. Since '(', '{', '[', and '<' are all in the font it's not a big sacrifice.

NPCMenu:

 dc.b "{Look {Talk {Give",ETX

ObjectMenu:

 dc.b "{Look {Use {Take",ETX

The existing code that showed a general "look/talk" message will be replaced by a subroutine to show the selection dialog. This looks at the target object and references a lookup table to find the title of the menu.

ShowSelectionDialog:

 move.w (MEM_ACTION_TARGET_OBJID),d7 ;move target object ID to d7

 andi.w #$0FFF,d7 ; clear the base value

 mulu.w #$4,d7 ; multiply by 4 to get the offset

 move.w (MEM_ACTION_TARGET_OBJID),d6 ; copy action target to d6

 andi.w #OBJ_SCENE_BASE,d6 ; and against OBJ_SCENE_BASE

 beq.s .1 ; if the result is zero then this is not scenery

 lea ObjectNameTable,a6 ; point to default object text table

 bra.s .2 branch to next step

.1 ; target object is an NPC

 lea NPCNameTable,a6 ; point to default npc text table

.2

 adda.l d7,a6 ;add offset

 move.l (a6),(MEM_DIALOG_TEXT) ; copy value at a6 to MEM_DIALOG_TEXT

 ; set dialog flags to display the dialog

 move.l (MEM_DIALOG_FLAGS),d7 ; copy current dialog state to d7

 bset.l #DIALOG_FLAG_TEXT_OPENING,d7 ; change state to opening

 bset.l #DIALOG_FLAG_STYLE_MENU,d7 ; set style to overworld menu

 bset.l #DIALOG_FLAG_SELECTION_WAIT,d7 ; set flag to wait for selection

 move.l d7,(MEM_DIALOG_FLAGS) ; save changes made to the game state

 move.l (MEM_GAME_STATE),d7 ; copy current game state to d7

 bset.l #STATE_FLAG_DIALOG,d7 ; set the dialog bit

 move.l d7,(MEM_GAME_STATE) ; copy game state back to d7

 move.w #$0000,(MEM_MENU_SELECTION) ; default to first menu selection

 rts

Here are the lookup tables and their values:

ObjectNameTable:

 dc.l $00000000

 ; OBJ_SCENE_VB_8BIT

 dc.l ObjectName8Bit

 ; OBJ_SCENE_VB_HARDWARE

 dc.l ObjectNameComputerHardware

 ; OBJ_SCENE_VB_16BIT

 dc.l ObjectName16Bit

 ; OBJ_SCENE_VB_MAGS

 dc.l ObjectNameMagazines

 ; OBJ_SCENE_VB_COUNTER

 dc.l ObjectNameCounter

 ; OBJ_SCENE_VB_REGISTER

 dc.l ObjectNameRegister

NPCNameTable:

 dc.l $00000000

 ; OBJ_NPC_DANI

 dc.l NPCNameDani

 ; OBJ_NPC_MALE_SHOPPER0

 dc.l NPCNameMapperShopper0

[...]

ObjectName8Bit:

 dc.b "Wall of 8-bit games",OBJMENU

ObjectNameComputerHardware:

 dc.b "Computer hardware",OBJMENU

ObjectName16Bit:

 dc.b "Rack of 16-bit games",OBJMENU

ObjectNameMagazines:

 dc.b "Magazine rack",OBJMENU

ObjectNameCounter:

 dc.b "Counter",OBJMENU

ObjectNameRegister:

 dc.b "Cash register",OBJMENU

NPCNameDani:

 dc.b "Dani (your sister)",NPCMENU

NPCNameMapperShopper0:

 dc.b "Business casual guy",NPCMENU

You're probably wondering? What's the deal with those new OBJMENU and NPCMENU constants? Those are used to tell the routine that draws the dialog that it's the end of the line and to insert a menu in the next line. That turned out to be a smaller change than I expected:

ProcessDialogTextDrawing:

[...]

TestObjMenu:

 cmpi.b #OBJMENU,d6 ; draw the object menu?

 bne.s TestNPCMenu ; not the OBJMENU character, continue to next test

 move.l #(VDP_VRAM_WRITE_A+DIALOG_ROWCOL),(MEM_DIALOG_VPD) ; base address

 add.l #$01020000,(MEM_DIALOG_VPD) ; add 258 tomove 2 rows and column

 lea ObjectMenu,a6 ; load object menu text

 move.l a6,(MEM_DIALOG_TEXT) ; copy address to MEM_DIALOG_TEXT

 bra.w ExitProcessDialog

TestNPCMenu:

 cmpi.b #NPCMENU,d6 ; draw the npc menu?

 bne.s TestLF ; not the NPCMENU character, continue to next test

 move.l #(VDP_VRAM_WRITE_A+DIALOG_ROWCOL),(MEM_DIALOG_VPD) ; base address

 add.l #$01020000,(MEM_DIALOG_VPD) ; add 258 tomove 2 rows and column

 lea NPCMenu,a6 ; load npc menu text

 move.l a6,(MEM_DIALOG_TEXT) ; copy address to MEM_DIALOG_TEXT

 bra.w ExitProcessDialog

[...]

This gets us up to drawing an empty menu with a caption.

Empty menu

This isn't interesting enough to stop so let's continue with...

Showing selections

Now for the difficult part - actually selecting menu options. We need some sort of visual indicator for which menu item is selected. I was curious how Phantasy Star II did this and it turns out they used a high layer sprite:

Phantasy Star II menu sprite

Hmm.. so a while back I created a off-screen "sprite zero" that exists only to get sprite ordering to work. Maybe I can re-purpose it to also act as a menu selector.

So after creating an 8x8 pattern for sprite zero we need some code to move it around and hide it:

MoveSelectorSprite: ; this also sets DIALOG_FLAG_SELECTION_WAIT if appropriate

 ; store sprite x in d4, sprite y in d5

 move.w #$0000,d4 ; set to 0 by default

 move.w #$0000,d5 ; set to 0 by default

 btst.l #DIALOG_FLAG_TEXT_CLOSING,d7 ; test if the dialog is closing

 bne.s .2 ; flag is set,move to update VDP section

 btst.l #DIALOG_FLAG_STYLE_MENU,d7 ; test if this is a menu

 beq.s .1 ; flag is not set, try next test

 bset.l #DIALOG_FLAG_SELECTION_WAIT,d7 ; set waiting for selection flag

 move.w #DIALOG_MENU_INIT_SELECTION_X,d4 ; x value

 ; move x value based on which item is selected

 move.w #$0038,d6 ; the selections are 38 apart

 mulu.w (MEM_MENU_SELECTION),d6 ; multiply by selection number

 add.w d6,d4 ; add result to x

 move.w #DIALOG_MENU_INIT_SELECTION_Y,d5 ; y value

 addq #$8,d5 ; adjust for options being on 2nd row of dialog

 bra.s .2 ; move to update VDP section

.1

 btst.l #DIALOG_FLAG_STYLE_TEXT_CHOICE,d7 ; test if this is a menu

 beq.s .2 ; flag is not set, use default values

 bset.l #DIALOG_FLAG_SELECTION_WAIT,d7 ; set waiting for selection flag

 move.w #DIALOG_MENU_INIT_SELECTION_X,d4 ; x value

 move.w #DIALOG_MENU_INIT_SELECTION_Y,d5 ; y value

 ; move y value based on which item is selected

 move.w #$0008,d6 ; the selections are 8 apart

 mulu.w (MEM_MENU_SELECTION),d6 ; multiply by selection number

 add.w d6,d5 ; add result to y

.2

 ;---------------------------------------------------------------------------

 ; update y

 ;---------------------------------------------------------------------------

 move.l #VDP_VRAM_WRITE_SPRITE,d6 ; add to sprite table address

 move.l d6,(VDP_CONTROL) ; set write location in VDP

 move.w d5,(VDP_DATA) ; copy the new y-coordinate

 ;---------------------------------------------------------------------------

 ; update x

 ;---------------------------------------------------------------------------

 add.l #$00060000,d6 ;move to x-coordinate

 move.l d6,(VDP_CONTROL) ; set write location in VDP

 move.w d4,(VDP_DATA) ; copy the new x-coordinate

ExitMoveSelectorSprite:

 rts

HideSelectorSprite:

 ;---------------------------------------------------------------------------

 ; update y

 ;---------------------------------------------------------------------------

 move.l #VDP_VRAM_WRITE_SPRITE,d6 ; add to sprite table address

 move.l d6,(VDP_CONTROL) ; set write location in VDP

 move.w #$0000,(VDP_DATA) ; copy the new y-coordinate

 ;---------------------------------------------------------------------------

 ; update x

 ;---------------------------------------------------------------------------

 add.l #$00060000,d6 ;move to x-coordinate

 move.l d6,(VDP_CONTROL) ; set write location in VDP

 move.w #$0000,(VDP_DATA) ; copy the new x-coordinate

ExitHideSelectorSprite:

 rts

Also needed is code to increment & decrement the selected menu item. This needs to work for both horizontal menus with three options and vertical choice menus with two options:

IncrementMenuSelection:

 btst.l #DIALOG_FLAG_STYLE_MENU,d7 ; test if this is a menu

 beq.s .2 ; flag is not set

 addq #$1,(MEM_MENU_SELECTION) ; increment menu selection

 cmpi.w #$0003,(MEM_MENU_SELECTION) ; at the last menu item?

 bge.s .1 ; at the last menu item, branch

 rts

.1

 move.w #$0000,(MEM_MENU_SELECTION) ; rollover to first menu item

 rts

.2 ; not DIALOG_FLAG_STYLE_MENU - must be DIALOG_FLAG_STYLE_TEXT_CHOICE

 addq #$1,(MEM_MENU_SELECTION) ; increment menu selection

 cmpi.w #$0002,(MEM_MENU_SELECTION) ; at the last menu item?

 bge.s .3 ; at the last menu item, branch

 rts

.3

 move.w #$0000,(MEM_MENU_SELECTION) ; rollover to first menu item

 rts

DecrementMenuSelection:

 btst.l #DIALOG_FLAG_STYLE_MENU,d7 ; test if this is a menu

 beq.s .2 ; flag is not set

 cmpi.w #$0000,(MEM_MENU_SELECTION) ; at the first menu item?

 ble.s .1 ; at the first menu item, branch

 subq #$1,(MEM_MENU_SELECTION) ; decrement menu selection

 rts

.1

 move.w #$0002,(MEM_MENU_SELECTION) ; rollover to first menu item

 rts

.2 ; not DIALOG_FLAG_STYLE_MENU - must be DIALOG_FLAG_STYLE_TEXT_CHOICE

 cmpi.w #$0000,(MEM_MENU_SELECTION) ; at the first menu item?

 ble.s .3 ; at the first menu item, branch

 subq #$1,(MEM_MENU_SELECTION) ; decrement menu selection

 rts

.3

 move.w #$0001,(MEM_MENU_SELECTION) ; rollover to first menu item

 rts

Next up is calling these methods when a menu or choice is displaying. The d-pad should move the indicator, B should cancel out of the menu, while A & C should accept the current selection.

[...]

ProcessDialogTestButtonPress:

 ; wait until a button is pressed to clear the dialog

 move.b (MEM_CONTROL_PRESSED),d6 ; copy pressed buttons to d6

 cmpi.w #$0000,d6 ; are any buttons pressed?

 beq.w ExitProcessDialog ; no buttons are pressed, exit

 ; start button shouldn't close the dialog

 cmpi.w #BUTTON_START_PRESSED,d6 ; test if the start button is held

 beq.w ExitProcessDialog ; exit if start button is held

 btst.l #DIALOG_FLAG_TEXT_NEW_PAGE,d7 ; test if there is another page

 beq.s .4 ; branch if new text page is not set

 ;---------------------------------------------------------------------------

 ; moving to a new page of text

 ;---------------------------------------------------------------------------

 add.l #$0001,(MEM_DIALOG_TEXT) ;move to the next character

 ; reset the drawing location for the dialog text

 move.l #(VDP_VRAM_WRITE_A+DIALOG_ROWCOL),(MEM_DIALOG_VPD) ; base address

 add.l #$00820000,(MEM_DIALOG_VPD) ; add 132 tomove 1 row and column

 ; clear out the dialog

 movea.l #PatternDialogFull,a0 ; point a0 to start of dialog patterns

 move.w #DIALOG_BASE_TILE,d0 ; base pattern

 move.w #$0000,d1 ; repeat

 movea.l #(VDP_VRAM_WRITE_A+DIALOG_ROWCOL),a1 ; initial drawing location

 bsr.w DrawTileset ; branch to DrawTileset subroutine

 ; reset flags to force text to start re-drawing

 bset.l #DIALOG_FLAG_TEXT_DRAWING,d7 ; set drawing flag

 bclr.l #DIALOG_FLAG_TEXT_OPEN,d7 ; clear open flag

 bclr.l #DIALOG_FLAG_TEXT_NEW_PAGE,d7 ; clear new page flag

 bra.w ExitProcessDialog ; exit

.4

 btst.l #DIALOG_FLAG_SELECTION_WAIT,d7 ; test if waiting for selection

 beq.w ProcessDialogSetClosing ; branch if waiting selection flag not set

 cmpi.w #BUTTON_B_PRESSED,d6 ; is the b button pressed?

 beq.w ProcessDialogSetClosing ; close if b button is pressed

 cmpi.w #BUTTON_RIGHT_PRESSED,d6 ; is the right button pressed?

 bne.s .5 ; right is not pressed,move to next test

 bsr.w IncrementMenuSelection ; increment the menu selection

 move.w #$0000,(MEM_CONTROL_PRESSED) ; clear pressed buttons

 bsr.w moveSelectorSprite ;move the selector sprite

 bra.w ExitProcessDialog ; exit

.5 ; down

 cmpi.w #BUTTON_DOWN_PRESSED,d6 ; is the down button pressed?

 bne.s .6 ; down is not pressed,move to next test

 bsr.w IncrementMenuSelection ; increment the menu selection

 move.w #$0000,(MEM_CONTROL_PRESSED) ; clear pressed buttons

 bsr.w moveSelectorSprite ;move the selector sprite

 bra.w ExitProcessDialog ; exit

.6 ; left

 cmpi.w #BUTTON_LEFT_PRESSED,d6 ; is the left button pressed?

 bne.s .7 ; left is not pressed,move to next test

 bsr.w DecrementMenuSelection ; increment the menu selection

 move.w #$0000,(MEM_CONTROL_PRESSED) ; clear pressed buttons

 bsr.w moveSelectorSprite ;move the selector sprite

 bra.w ExitProcessDialog ; exit

.7 ; up

 cmpi.w #BUTTON_UP_PRESSED,d6 ; is the up button pressed?

 bne.s .8 ; up is not pressed,move to next test

 bsr.w DecrementMenuSelection ; increment the menu selection

 move.w #$0000,(MEM_CONTROL_PRESSED) ; clear pressed buttons

 bsr.w moveSelectorSprite ;move the selector sprite

 bra.s ExitProcessDialog ; exit

.8 ; a

 cmpi.w #BUTTON_A_PRESSED,d6 ; is the a button pressed?

 bne.s .9 ; a is not pressed,move to next test

 bsr.w ConfirmMenuSelection ; confirm the menu selection

 move.w #$0000,(MEM_CONTROL_PRESSED) ; clear pressed buttons

 bra.s ExitProcessDialog ; exit

.9 ; c

 cmpi.w #BUTTON_C_PRESSED,d6 ; is the c button pressed?

 bne.s ExitProcessDialog ; c is not pressed, exit

 bsr.w ConfirmMenuSelection ; confirm the menu selection

 move.w #$0000,(MEM_CONTROL_PRESSED) ; clear pressed buttons

 bra.s ExitProcessDialog ; exit

[...]

I think that routine could be shortened a bit. Rather than tackle that now let's proceed to...

Responding to selections

When the A or C button is pressed the first thing that happens is calling a sub-routine that saves the selection and updates the dialog state:

ConfirmMenuSelection:

 btst.l #DIALOG_FLAG_STYLE_MENU,d7 ; test if this is a menu

 beq.s .1 ; flag is not set

 move.w (MEM_MENU_SELECTION),(MEM_ACTION_ID) ; selection->action

 bra.s ExitConfirmMenuSelection ; exit

.1 ; not DIALOG_FLAG_STYLE_MENU - must be DIALOG_FLAG_STYLE_TEXT_CHOICE

 move.w #ACTION_RESPOND,(MEM_ACTION_ID) ; action is respond

 move.w (MEM_MENU_SELECTION),(MEM_MENU_RESPONSE) ; selection->response

ExitConfirmMenuSelection:

 move.l (MEM_GAME_STATE),d6 ; copy current game state to d6

 bset.l #STATE_FLAG_ACTION,d6 ; clear the dialog bit

 move.l d6,(MEM_GAME_STATE) ; copy it back

 rts

Back in the main loop, when STATE_FLAG_ACTION is set it redirects to a new routine that processes the queued action. This builds off the last article and adds code to clear the dialog so it can be reused:

ProcessAction:

 bsr.w ResetDialog ; reset the dialog

 bsr.w BuildActionTableOffset ; build action table offset

 lea ActionTable,a5 ; point to action table

 adda.w (MEM_ACTION_TABLE_OFFSET),a5 ;move to offset location

 move.l (a5),a6 ; a5 has the address of the subroutine to jump to

 jsr (a6) ; jump to location of code to process this event

 move.l (MEM_GAME_STATE),d7 ; copy current game state to d7

 bclr.l #STATE_FLAG_ACTION,d7 ; clear action flag

 move.l d7,(MEM_GAME_STATE) ; save it back

 rts

If the action that was just invoked needs the dialog it would look weird to close and re-open it. I'm having a hard time thinking of when an action wouldn't re-use the dialog. If the player did something and the dialog just closed with no message that would be really confusing. Although I suppose there are cases where the "use" command might fire off an animation or something. Anyway, reseting the dialog is a matter of switching flags so it stays open and starts re-drawing text at the top.

; resets the dialog so new text can be drawn without closing & reopening

ResetDialog:

 move.l (MEM_DIALOG_FLAGS),d7 ; copy current dialog state to d7

 ; reset the drawing location for the dialog text

 move.l #(VDP_VRAM_WRITE_A+DIALOG_ROWCOL),(MEM_DIALOG_VPD) ; base address

 add.l #$00820000,(MEM_DIALOG_VPD) ; add 132 to move 1 row and column

 ; clear out the dialog

 movea.l #PatternDialogFull,a0 ; point a0 to start of dialog patterns

 move.w #DIALOG_BASE_TILE,d0 ; base pattern

 move.w #$0000,d1 ; repeat

 movea.l #(VDP_VRAM_WRITE_A+DIALOG_ROWCOL),a1 ; initial drawing location

 bsr.w DrawTileset ; branch to DrawTileset subroutine

 ; reset flags to force text to start re-drawing

 bset.l #DIALOG_FLAG_TEXT_DRAWING,d7 ; set text drawing flag

 bclr.l #DIALOG_FLAG_TEXT_OPEN,d7 ; clear open flag

 bclr.l #DIALOG_FLAG_TEXT_NEW_PAGE,d7 ; clear new page flag

 bclr.l #DIALOG_FLAG_STYLE_TEXT_CHOICE,d7 ; clear menu style choice flag

 bclr.l #DIALOG_FLAG_STYLE_MENU,d7 ; clear menu style flag

 bset.l #DIALOG_FLAG_STYLE_SIMPLE_TEXT,d7 ; set simple text style flag

 bclr.l #DIALOG_FLAG_SELECTION_WAIT,d7 ; clear waiting for selection flag

 move.l d7,(MEM_DIALOG_FLAGS) ; copy game state back to d7

 move.w #$0000,(MEM_MENU_SELECTION) ; reset menu selection

 bsr.w HideSelectorSprite ; hide the selection icon

 rts

Somewhere in this article you might be wondering "That's an awful lot of dialog flags, what is this dude thinking?" I think this whole dialog flag thing I'm doing is the direct result of doing Windows UI development for a couple decades. In the Windows world, and presumably similar GUIs, creating dialogs involves setting a bunch of flags then calling a show dialog routine. The dialog appearance & behavior can change quite a bit based on the flag combinations. All that implementation logic is handled by Windows. I'm sort of copying that by creating one large method that handles all dialog behavior and is controlled by a set of dialog flags.

The last steps are updating the action script and adding new text. In this iteration the player can talk to the shopper in the white shirt. At the end of that dialog the player is presented a choice. If the player selects either choice then they (a) receive a message based on the choice and (b) receive a different message next time they talk to the shopper.

Day00Scene00Action01: ; ACTION_USE_TALK

 move.l (MEM_DAY_EVENT_FLAGS),d7 ; copy event flags for the day to d7

 move.w (MEM_ACTION_TARGET_OBJID),d6 ; copy action target to d6

 cmpi.w #OBJ_NPC_DANI,d6 ; test target

 bne.s .2 ; branch to next NPC

 ;---------------------------------------------------------------------------

 ; handle dialog with NPC Dani

 ;---------------------------------------------------------------------------

 btst.l #$1,d7 ; test if flag 1 is set

 bne .1 ; branch if it is

 bset.l #$1,d7 ; set flag 1

 move.l d7,(MEM_DAY_EVENT_FLAGS) ; save updated event flags for the day

 lea DialogTextDaniScene0Day0Flag0,a6 ; load dialog text

 bra.s .5 ; branch to setup dialog display

.1

 lea DialogTextDaniScene0Day0Flag1,a6 ; load dialog text

 bra.s .5 ; branch to setup dialog display

.2

 ;---------------------------------------------------------------------------

 ; handle dialog with shopper

 ;---------------------------------------------------------------------------

 cmpi.w #OBJ_NPC_MALE_SHOPPER0,d6 ; test target

 bne.s .4 ; branch to display default text

 btst.l #$2,d7 ; test if flag 2 is set

 bne .3 ; branch if it is set

 move.l (MEM_DIALOG_FLAGS),d7 ; copy dialog flags to d7

 bset.l #DIALOG_FLAG_STYLE_TEXT_CHOICE,d7 ; set text choice flag

 move.l d7,(MEM_DIALOG_FLAGS) ; save updated dialog flags

 lea DialogTextMaleShopper0Scene0Day0Flag0,a6 ; load dialog text

 bra.s .5 ; branch to setup dialog display

.3 ; flag 2 is set

 lea DialogTextMaleShopper0Scene0Day0Flag2,a6 ; load dialog text

 bra.s .5 ; branch to setup dialog display

.4

 ; default

 lea DialogTextNothingHappens,a6 ; load default text

.5

 move.l a6,MEM_DIALOG_TEXT ; copy address to MEM_DIALOG_TEXT

ExitDay00Scene00Action01:

 rts

[...]

Day00Scene00Action03: ; ACTION_RESPOND

 move.l (MEM_DAY_EVENT_FLAGS),d7 ; copy event flags for the day to d7

 move.w (MEM_ACTION_TARGET_OBJID),d6 ; copy action target to d6

 cmpi.w #OBJ_NPC_MALE_SHOPPER0,d6 ; test target

 bne.s .2 ; branch to display default text

 bset.l #$2,d7 ; set flag 2

 move.l d7,(MEM_DAY_EVENT_FLAGS) ; save the updated flags

 cmpi.w #$0000,(MEM_MENU_RESPONSE) ; is the menu selection 0?

 bne.s .1 ; branch if not equal to zero (there are only two options)

 lea DialogTextMaleShopper0Scene0Day0Flag0R0,a6 ; load response text

 move.l a6,MEM_DIALOG_TEXT ; copy address to MEM_DIALOG_TEXT

 bra.s ExitDay00Scene00Action03 ; exit

.1

 lea DialogTextMaleShopper0Scene0Day0Flag0R1,a6 ; load response text

 move.l a6,MEM_DIALOG_TEXT ; copy address to MEM_DIALOG_TEXT

 bra.s ExitDay00Scene00Action03 ; exit

.2

 lea DialogTextNothingHappens,a6 ; load default text

 move.l a6,MEM_DIALOG_TEXT ; copy address to MEM_DIALOG_TEXT

ExitDay00Scene00Action03:

 bsr.w ResetDialog ; reset the dialog

 rts

[...]

DialogTextMaleShopper0Scene0Day0Flag0:

 ; "1234567890123456789012"

 dc.b "`Do you know if I have",LF

 dc.b "a mouse on COM2 using^",FF

 dc.b "IRQ3 can I hookup one",LF

 dc.b "of these modems ^",FF

 dc.b "on COM4 using IRQ4?'",LF

 dc.b " ^",FF

 dc.b "{Sure, why not?",LF

 dc.b "{I make $3.35 an hour.",ETX

DialogTextMaleShopper0Scene0Day0Flag0R0:

 ; "1234567890123456789012"

 dc.b "`You chose response",LF

 dc.b "zero, neat.'",ETX

DialogTextMaleShopper0Scene0Day0Flag0R1:

 ; "1234567890123456789012"

 dc.b "`You chose response",LF

 dc.b "one, alright.'",ETX

DialogTextMaleShopper0Scene0Day0Flag2:

 ; "1234567890123456789012"

 dc.b "`You're talking to me",LF

 dc.b "again now.'",ETX

Time to test it all out by talking to the shopper:

First dialog with NPC

Pressing B at the selection dialog will cause this to repeat next time he's talked to. Pressing A or C changes the next dialog with him:

Second dialog with NPC

This was a little painful to get working the first time. You don't see the parts where I'm trying to debug why instead of the dialog a bunch of gibberish prints. However, having gone through this adding new dialog is now reasonably easy. The only tricky part is having to update things in multiple places: dialog text file, dialog table, and the action script which may need updates in multiple action handlers. I don't think there's an easy way to get around this. Perhaps in a couple months I'll think of a better way to handle actions.

What's next?

Let's go back to the "plan" from two articles ago:

1) Dialog between characters - meaning an NPC says something to you and you respond. Maybe you respond with more dialog, maybe you give them an item. That leads to...

2) Inventory management - along with basic stuff like taking and giving items.

3) Game event tracking - we need a way to say "if [X] happened and you talk to character [Y] then do [Z]", I'll probably over-complicate this. I won't call this 100% complete but it's functional enough.

4) Scripted sprite movements - if the ultimate goal is to get a customer to leave a store then there needs to be an animation where they walk away.

5) Adding & removing NPCs from the current scene - the one NPC is hard-coded, there needs to be a way to move NPCs in and out of scenes. The removing part hasn't been needed yet but it's small and will be addressed with #4.

6) To make this look like a real demo I should really create a title screen and ending message.

7) Dozens of things I didn't think of that will all be painful to work out.

Looks like inventory is next.

Download

Download the latest source code on GitHub




Tweet