Start Menu

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

Post Reply
User avatar
67726e
Posts: 129
Joined: Sat Apr 03, 2010 5:45 pm
Location: South Carolina
Contact:

Start Menu

Post by 67726e »

How do you typically place your start menu in your code? My start menu is just your typical 'Press Start' so I figured I would just put a small loop at the beginning that ends when the user presses start and from then on, the real code is done unless of course the game ends in which case it all just jumps back to that loop.

What are the negatives to this approach?
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Start Menu

Post by tokumaru »

67726e wrote:What are the negatives to this approach?
The downside is that this is not easily expandable. Most games have more screens/states than just a title screen and the main game, and not all of them are as simple as "press a button". Some screens are almost as complex as an actual game, requiring music, VBlank updates and such. It would be a good idea to implement a better "module" system, that allows you to navigate through screens without limitations.

In my games, each module (which is a separate ASM file) has an initialization area followed by a loop. To move from one module to the next I just jump to the initialization of the next module when required (in the case of a title screen that would be when "start" is pressed), but I also have time to some cleaning (e.g. fading out) before terminating the current module. I can also have sub-modules, which run inside a parent module (something like a menu that you can call during the game), in which case I JSR to it so I can return when it's done.

Since I want to be able to run crucial updates in case of lag frames, I want the possibility of running specialized NMIs for each module, but not all modules are CPU hogs, so these can get by with the traditional "wait for VBlank" technique. To accommodate both situations, my NMI routine can either call a specialized NMI routine if one is defined, or just signal that an NMI has happened, and each module is free to use the method that best suits it.
3gengames
Formerly 65024U
Posts: 2281
Joined: Sat Mar 27, 2010 12:57 pm

Post by 3gengames »

Just from thinking of this, have a screen uploaded to the PPU and then have a "select option" program running. I qould guess that a list of two-byte values for X-Y of the sprite that points to what your selecting would work, and then just loop through them from what the input is until it wraps around two the beginning again. You could even have it in a subroutine that returns a value in the A register to ave even more compact code, so the menus are from the subroutine, but the values returned get computed in the main games program. :D


Just an idea. I have never done this though. :)
User avatar
67726e
Posts: 129
Joined: Sat Apr 03, 2010 5:45 pm
Location: South Carolina
Contact:

Re: Start Menu

Post by 67726e »

tokumaru wrote:
67726e wrote:What are the negatives to this approach?
In my games, each module (which is a separate ASM file) has an initialization area followed by a loop.
That is actually what I was talking about doing.
User avatar
Memblers
Site Admin
Posts: 3901
Joined: Mon Sep 20, 2004 6:04 am
Location: Indianapolis
Contact:

Post by Memblers »

In some of my programs I supplied an NMI routine, in addition to the init routine and loop. You could change how your NMI routine operates, but instead I just point the vector to 3 bytes of RAM, putting $4C (JMP absolute) followed by the address, so it can be redirected easily.
User avatar
MetalSlime
Posts: 186
Joined: Tue Aug 19, 2008 11:01 pm
Location: Japan

Post by MetalSlime »

I use a system similar to Tokumaru's. The different sections of my game (title screen, gameplay, gameover, pause, etc) are divided into "gamestates". Each gamestate lives in its own file and has an init routine and a main routine (which calls the init routine). I store the addresses of all the main routines in a table and I have a subroutine set_gamestate which takes a gamestate id in A and uses it to grab an address from the table and stick it in a pointer variable (gamestate_ptr).

My main game loop just says:

Code: Select all

forever:
     jmp (gamestate_ptr)
and each gamestate ends with "jmp forever", completing the loop.

So for a start menu I might have:

Code: Select all

;table of addresses to main gamestate routines.
;  set_gamestate reads from this table to set gamestate_ptr
gamestate_table:
    .word startmenu
    .word gameplay
    ;other addresses

Code: Select all

;--------------startmenu.asm-----

startmenu_init:
    ;draw title screen
    ;turn screen on
    ;start music
    jmp gamestate_init_done  ;this routine adds 3 to gamestate_ptr.  To skip init for future iterations

startmenu:
    jsr startmenu_init

    ;wait a frame
    ;read the controller

    lda controller1
    and #START_BUTTON   ;bitmask that tests start button bit
    beq end             ;if not pressed, we're done.  loop back to forever

    ;cleanup code (e.g. play sfx, fadeout loop)
    ;change to gameplay gamestate

end:
    jmp forever
Having a gamestate handler like this has a little bit of overhead but it's easy for me to read and debug. Adding a new gamestate is as easy as adding a line to the address table.
Wave
Posts: 110
Joined: Mon Nov 16, 2009 5:59 am

Post by Wave »

Memblers wrote:In some of my programs I supplied an NMI routine, in addition to the init routine and loop. You could change how your NMI routine operates, but instead I just point the vector to 3 bytes of RAM, putting $4C (JMP absolute) followed by the address, so it can be redirected easily.
JMP absolute on RAM over JMP Indirect on ROM to gain 2 cycles (at 1 byte of ram cost)?
Just asking, might use it on my IRQ routine.
Ian A
Posts: 115
Joined: Sat Feb 27, 2010 8:32 am
Location: Maine

Post by Ian A »

I'm going to second what everyone else has said so far.

When I first started programming I made title screens/ intros as the outer most level of a loop per your example. However, this scales HORRIBLY and it kinda makes the code more difficult to understand. Later, when I tired to add more to the start screen, because cool intros kick ass, the loop was a huge mess of code surrounding a much smaller, better structured chunk of code.

It certainly makes sense to break it off into a separate parts or states. Even if your title screen is a typically simple, you could reuse the code with very little effort. Or later, if you want something more, you can alter it with ease.

I tend to rewrite parts of my programs often, so I find it better to break them up into smaller isolated chunks, so if I change a section I don't have to worry about how it effects the rest of the code too much.
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

Wave wrote:
Memblers wrote:In some of my programs I supplied an NMI routine, in addition to the init routine and loop. You could change how your NMI routine operates, but instead I just point the vector to 3 bytes of RAM, putting $4C (JMP absolute) followed by the address, so it can be redirected easily.
JMP absolute on RAM over JMP Indirect on ROM to gain 2 cycles (at 1 byte of ram cost)?
If you had an indirect JMP on ROM and an NMI fired between you setting the first and the second bytes of the address in RAM, the program would jump to an invalid location and most likely crash. To avoid that you would have to disable NMIs every time you modified the address.

The other option is to keep the whole instruction in RAM, so that you can temporally change the JMP to RTI before you modify the address, and finally change the RTI back to JMP when the address is valid. Modifying the opcode is faster than disabling/enabling NMIs through the PPU.
Wave
Posts: 110
Joined: Mon Nov 16, 2009 5:59 am

Post by Wave »

tokumaru wrote:
Wave wrote:
Memblers wrote:In some of my programs I supplied an NMI routine, in addition to the init routine and loop. You could change how your NMI routine operates, but instead I just point the vector to 3 bytes of RAM, putting $4C (JMP absolute) followed by the address, so it can be redirected easily.
JMP absolute on RAM over JMP Indirect on ROM to gain 2 cycles (at 1 byte of ram cost)?
If you had an indirect JMP on ROM and an NMI fired between you setting the first and the second bytes of the address in RAM, the program would jump to an invalid location and most likely crash. To avoid that you would have to disable NMIs every time you modified the address.

The other option is to keep the whole instruction in RAM, so that you can temporally change the JMP to RTI before you modify the address, and finally change the RTI back to JMP when the address is valid. Modifying the opcode is faster than disabling/enabling NMIs through the PPU.
Well, I'm thinking about using it on IRQs, that will only be fired if loaded properly.
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

Wave wrote:Well, I'm thinking about using it on IRQs, that will only be fired if loaded properly.
Yeah, for IRQs you can safely have just the address in RAM. Even if they fired at unpredictable times they are easily disabled and enabled with SEI and CLI.

Since you are planning on using IRQs for various types of effects, it makes sense to give each effect its own routine so that they can do their thing as quickly as possible.
User avatar
Memblers
Site Admin
Posts: 3901
Joined: Mon Sep 20, 2004 6:04 am
Location: Indianapolis
Contact:

Post by Memblers »

Yeah, that redirection trick is especially helpful with IRQ (where overhead starts to matter more, if it's triggering often enough). It makes other optimizations easier too, like in many IRQ routines you only need to save/restore the accumulator.

Even if there's only one condition to check (there's usually more than that), with only one NMI/IRQ routine in the best case it's still 7 cycles minimum to load/compare/branch. Compared to just burning up 3 cycles with a JMP to a dedicated routine.
tepples
Posts: 22345
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

Here's what the outer loop of something like Concentration Room looks like:

Code: Select all

callTitleScreenProc:
  asl a
  tax
  lda titleScreenProcs+1,x
  pha
  lda titleScreenProcs,x
  pha
  php
  rti

titleScreenProcs: .addr start1PlayerGame, start2PlayerGame, startOptions

reset:
  sei
  ldx #0
  stx $2000
  stx $2001
  dex
  txs
  ; Omitted: initialize APU ports, then wait for $2002 to be negative twice

titleloop:
  jsr showTitleScreen  ; this returns number of selection in A
  jsr callTitleScreenProc
  jmp titleloop
User avatar
Bregalad
Posts: 8036
Joined: Fri Nov 12, 2004 2:49 pm
Location: Caen, France

Post by Bregalad »

I'm unsure of the original quesiton, but my main loop looks like this :

Code: Select all

stageLoop
  jsr ResetStage

-  jsr WaitNMI
  jsr RunPlayer
  jsr RunObjects      ; Objects are enemies, items, etc...
  jsr DisplaySprites
  lda GameOverFlag
  beq _gameOver     ;Those flags breaks the main loop
  lda StageBeatenFlag
  bne _stageBeaten
  jmp -

_gameOver
   jsr FadeOut
   jsr DisplayGameOverScr
   jmp Reset

_stageBeaten
   jsr PlayVictoryMusic
   jsr FadeOut
   inc StageNmr
   jmp _stageLoop
Pretty straightforward. The "start menu" is handled in the "Run Player" routine, which, if the menu is to be opened, don't exit but simply executes until the menu is closed again and then the game continues normally.
I like this natural way of coding things in the order they execute.
Some people prefer having a "jmp *" style main loop (i.e. everything in NMI), and do their logic based on a series on numbers which tells the NMI routine what to do. This is more complicated for me, as you have to carefully manage that series of numbers, and this is slower/less efficient. I might be wrong though.
Useless, lumbering half-wits don't scare us.
Post Reply