This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

Getting started again!! Romhacker to NES Game Programmer

Getting started again!! Romhacker to NES Game Programmer
by on (#178124)
I have made demos before but more to understand how the NES works rather than trying to make games. This was done to to increase my skills as a Romhacker. I am far better at reverse engineering games than I am making them which seems to require a new set of skills which I'm willing to learn.

I have also ripped a lot of NSFs which is also a long the lines of reverse engineering games to extract the sound code out of the game. I believe that I've done that for long enough and want to do some game programming.

One advantage I have from this is that I can rip a game apart to determine how it works, if I see something similar that I may need in my own game.

I have my assembler of choice and that's ASM6 because it is very easy to use and doesn't require too much setup to get started. In fact, if I wanted to, it would output even a small binary chunk which is very useful for Romhacking. So, I'm not really interested in another assembler. I'm also not interested in any of the C compilers for NES either because I am having such a difficult time learning C and it's associated IDE that I don't want to spend the years needed to learn both.

If I could learn C, I would just use it to make tools that I need. However I don't want to spend the time to learn it right now.

There are more than enough tile editors out there for my needs. There is a nice attribute table editor by Shiru that I really like a lot.

Plenty of sound drivers and Famitracker, although I've heard of people making the tunes in Famitracker and importing the data to a custom driver because Famitracker drivers are too expensive for a game to use.

I can also use that NES Music tracker NTRQ but again that is not really for games even though I can definitely use it well enough.

I've skimmed through the NESdev wiki and can use it again and gain as a reference when needed.

My plan so far is to take NROM and program various demos using the features from that configuration so that I can get the hang of and get started actually making games. For example, I would do background drawing, vertical or horizontal scrolling, either one but not both. Learning to use the IRQ, use CHR-ROM on some and CHR-RAM on some others to practice various techniques. I definitely want to do this to practice development. Once I build up code libraries I think it may help me.

Due to the fact that I haven't been around for quite awhile, I am not up to date on tools and other information that has been released in the past 2-3 years. So, I feel like I'm starting again and trying to refresh my memory about various things about the NES and programming.

I hope I have the right ideas what I'm doing to get started. If anyone has ideas I would like to hear them.
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178126)
Gil-Galad wrote:
I have made demos before but more to understand how the NES works rather than trying to make games. This was done to to increase my skills as a Romhacker. I am far better at reverse engineering games than I am making them which seems to require a new set of skills which I'm willing to learn.

I have also ripped a lot of NSFs which is also a long the lines of reverse engineering games to extract the sound code out of the game. I believe that I've done that for long enough and want to do some game programming.

Have you done much with reverse engineering the data formats of existing games' sound drivers? I know that used in a few Capcom games is documented, as are most of the major Super NES drivers and that of my own music engine Pently (concepts and macro definitions).

Quote:
I've heard of people making the tunes in Famitracker and importing the data to a custom driver because Famitracker drivers are too expensive for a game to use.

Drivers that play converted FamiTracker music include FamiTone2, GGSound, and now Pently. Each plays only a subset of FT features.
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178130)
I'll take a note of that for Famitone2, GG Sound and Pently. I hope I can use them. Thank you.

I have some disassembled drivers for various games and Final Fantasy I being more complete than the others but mostly I kept notes in my head because it slows you down when you have to document everything. I don't know where these files are right now. I think they are in my PC that I have in my closet.

What I usually do is rip a NSF as usual. Most of them are in the range of 8K to 32K so I can do it all in one bank. I disassemble them into readable format. That takes awhile by itself. Then I start trying to figure out what the code does and note them on the disassembly as well as creating readable labels. It's a slow process but it can get a driver RE'd eventually.
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178156)
Pently is currently geared for ca65. I tried making an asm6 version, but tepples has added features to pently since then, so the asm6 version should br considered incomplete.
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178186)
Well, I hope you consider updating the asm6 version when you have time and interest. That would be great.

I have been working on a basic template for NROM. Nothing more than getting the header in place and defining the interrupt vectors. I'm going to fine tune it some more until I am happy with it and then I will have a easy to use template from now on, I hope.

Code:
;****************************************
; NES Template 1.0
;****************************************



;****************************************
; iNES Header 1.0
;****************************************

.DB $4E,$45,$53,$1A    ; NES, MS-DOS EOF End of file

.DB $02                ; PRG-ROM size 32K - byte 04

.DB $01                ; CHR-ROM size 8K - byte 05

.DB %00000010          ; flags byte 06
                       ; horizontal mirroring bit 0
                       ; battery backed PRG RAM bit 1
                       ; No trainer bit 2
                       ; No 4 screen VRAM bit 3

.DB $00                ; flags byte 07
                       ; No Vs. Unisystem, PlayChoice-10

.DB %00000000          ; flags byte 08
                       ; NTSC mode bit 0, unused by most emulators

.DB $00                ; flags byte 09
                       ; Unoffical TV System byte. 0 NTSC, 1 PAL bit 0, bit 1 set dual compatible
                       ; Bus conflict flag for mapper bit 5

.DSB 6                 ; padding unused space 00

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

;**********************************************
; CONSTANTS
;**********************************************


;**********************************************
; VARIABLES
;**********************************************

;Zero Page

.enum $0000

.ende

sprendisflag=$0100

;**********************************************
; PRG-ROM Bank 0 NROM
;**********************************************

.ORG $8000

RESET:

SEI            ; disable IRQs
CLD            ; disable decimal mode
LDA #$40
STA $4017      ; disable APU frame IRQ
LDX #$FF
TXS            ; set up the stack
INX            ; X=0
STX $2000      ; disable NMI
STX $2001      ; disable rendering
STX $4010      ; disable DMC IRQs

JSR vblankwait ; wait for vertical blank #1

JSR clrmem1    ; clear out WRAM on boot

JSR clrmem2    ; clear out SPR-RAM

JSR vblankwait ; wait for vertical blank #2

; Bootup sequence ended.

LDA $2002         ; set PPU palette address
LDA #$3F
STA $2006
LDA #$10
STA $2006

LDX #$00

PaletteLoop:

LDA Palette, x    ; load palette data table sprite and background colors
STA $2007
INX
CPX #$20          ; 32 bytes
BNE PaletteLoop

LDA #%10010000
STA $2000
LDA #%0001000
STA $2001

startloop:

JMP startloop

vblankwait:

BIT $2002
BPL vblankwait
RTS

clrmem1:

LDA #$00
LDX #$00
LDY #$00

clrmem1a:

STA $00,x      ; lower page 1 will be used for reset variables
STA $0300,x
STA $0400,x
STA $0500,x
STA $0600,x
STA $0700,x
INX
BNE clrmem1a
RTS

clrmem2:       ; clear out SPR-RAM

LDA #$EF
clrmem2a
STA $0200,x
INX
BNE clrmem2a
RTS

Palette:

.DB $0F,$31,$32,$33,$0F,$35,$36,$37,$0F,$39,$3A,$3B,$0F,$3D,$3E,$0F
.DB $0F,$1C,$15,$14,$0F,$02,$38,$3C,$0F,$1C,$15,$14,$0F,$02,$38,$3C

;*************************************************
; NMI Handler Code
;*************************************************

NMI:

PHA
TXA
PHA
TYA
PHA

JSR sprdmarun

PLA
TAY
PLA
TAX
PLA

RTI

sprdmarun:

LDA sprendisflag  ; 0=enable sprite DMA 1=disable sprite DMA
BNE skipsprdma

LDA #$00
STA $2003
LDA #$02
STA $4014    ; allocate page 2 for sprite ram +513 cycles

skipsprdma:

RTS
;------------------------------------------------

;************************************************
; IRQ Handler Code
;************************************************

IRQ:

PHA
TXA
PHA
TYA
PHA

NOP

PLA
TAY
PLA
TAX
PLA

RTI

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


;***********************************************
; INTERRUPT VECTOR TABLE
;***********************************************

.ORG $FFFA

.DW NMI
.DW RESET
.DW IRQ

;***********************************************
; CHR-ROM Bank 0
;***********************************************

.incbin "chr.chr"

           
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178199)
Try to declare your variables by reserving space, not assigning hardcoded addresses. Instead of this:

Code:
sprendisflag = $0100
othervariable = $0101
yetanothervar = $0103

Do this:

Code:
.enum $0100
   sprendisflag .dsb 1
   othervariable .dsb 2
   yetanothervar .dsb 1
.ende

This you you won't risk declaring overlapping variables or leaving gaps, and you can quickly rearrange and resize the variables if necessary, without having to go through the tedious and error-prone task of manually adjusting dozens of addresses.
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178200)
FWIW, I think the concern about Famitracker's replay engine can appear a bit overblown for most cases. If you want to make something that's going to push the NES to it's limits, sure. But if you're making a game for the first time it's really advisable to keep it simple as much as possible. Unless it's a pretty intense program, you probably won't be in danger of running out of CPU cycles or RAM. I may be wrong, but I seem to recall that even quite a few commercial game sound engines were bigger hogs than Famitracker's.
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178201)
Does FamiTracker's sound engine have sound effect support?
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178202)
Memblers wrote:
FWIW, I think the concern about Famitracker's replay engine can appear a bit overblown for most cases. If you want to make something that's going to push the NES to it's limits, sure. But if you're making a game for the first time it's really advisable to keep it simple as much as possible. Unless it's a pretty intense program, you probably won't be in danger of running out of CPU cycles or RAM. I may be wrong, but I seem to recall that even quite a few commercial game sound engines were bigger hogs than Famitracker's.

I don't think CPU or RAM are the biggest problems with it.

Famitracker has no sound effect support. This is probably the most important reason to use something else. (Unless you want to write your own separate sound effect engine and learn how to patch that on top of Famitracker's music.)

The second biggest reason I think is its ROM footprint. It's more than 5k. The common alternatives are more like 1k.

It does use a lot more RAM than the alternatives too, but it thankfully keeps that mostly off the ZP, so I think this is not too bad, really.

The CPU usage isn't too bad, either. Maybe on average you lose 1000 cycles per frame compared to something lighter? Probably not the dealbreaker.
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178215)
That's good information to know about Famitracker. Although, I definitely need a sound driver that can use sound effects and be somewhat efficient memory and cycle wise to keep my options open with as much time as I can get. Although, it's not a big concern but the sound effects are.

I've been working on my template again and changed some variables to .enum. I also added two hard coded variables for controller ports. I don't see a good reason to change the ports to .enum.

I also added controller code that is for one player. It's the controller code from the Wiki. As a matter of fact I have been using stuff from the Wiki and template ideas from that ASM6 template thread. And other tutorials just to refresh my memory.

The controller code I'm running in NMI to run once per frame to keep it well timed with the game. I don't know if this is the best way to do it. I've seen games do it and run controller code elsewhere.

I've also been debugging the code as I'm going along by trial and error. It hasn't taken me long to track down some blunders I made. I'm sure it will be much more difficult later on to track down bugs.

Code:
;****************************************
; NES Template 1.0
;****************************************



;****************************************
; iNES Header 1.0
;****************************************

.DB $4E,$45,$53,$1A    ; NES, MS-DOS EOF End of file

.DB $02                ; PRG-ROM size 32K - byte 04

.DB $01                ; CHR-ROM size 8K - byte 05

.DB %00000010          ; flags byte 06
                       ; horizontal mirroring bit 0
                       ; battery backed PRG RAM bit 1
                       ; No trainer bit 2
                       ; No 4 screen VRAM bit 3

.DB $00                ; flags byte 07
                       ; No Vs. Unisystem, PlayChoice-10

.DB %00000000          ; flags byte 08
                       ; NTSC mode bit 0, unused by most emulators

.DB $00                ; flags byte 09
                       ; Unoffical TV System byte. 0 NTSC, 1 PAL bit 0, bit 1 set dual compatible
                       ; Bus conflict flag for mapper bit 5

.DSB 6                 ; padding unused space 00

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

;**********************************************
; CONSTANTS
;**********************************************


;**********************************************
; VARIABLES
;**********************************************

; Controller Ports

JOYPAD1=$4016
JOYPAD2=$4017

; Zero Page

.enum $0000

.ende

; Page 1 flag variables

.enum $0100

sprendisflag .DSB 1
ctrlendis    .DSB 1
buttons      .DSB 1


.ende

;**********************************************
; PRG-ROM Bank 0 NROM
;**********************************************

.ORG $8000

RESET:

SEI            ; disable IRQs
CLD            ; disable decimal mode
LDA #$40
STA $4017      ; disable APU frame IRQ
LDX #$FF
TXS            ; set up the stack
INX            ; X=0
STX $2000      ; disable NMI
STX $2001      ; disable rendering
STX $4010      ; disable DMC IRQs

JSR vblankwait ; wait for vertical blank #1

JSR clrmem1    ; clear out WRAM on boot

JSR clrmem2    ; clear out SPR-RAM

JSR vblankwait ; wait for vertical blank #2

; Bootup sequence ended.

LDA $2002         ; set PPU palette address $3F10
LDA #$3F
STA $2006
LDA #$10
STA $2006

LDX #$00

PaletteLoop:

LDA Palette, x    ; load palette data table sprite and background colors
STA $2007
INX
CPX #$20          ; 32 bytes
BNE PaletteLoop

LDA #%10010000    ; enable NMI, background $1000 VRAM, sprites $0000 VRAM
STA $2000         ; sprite size 8x8, PPU increment 1, name table $2000 VRAM
LDA #%0011000     ; background visible, sprites visible, background & sprites clipped
STA $2001         ; color display

startloop:

JMP startloop

vblankwait:

BIT $2002
BPL vblankwait
RTS

clrmem1:

LDA #$00
TAX
TAY

clrmem1a:

STA $00,x      ; lower page 1 will be used for reset variables
STA $0300,x
STA $0400,x
STA $0500,x
STA $0600,x
STA $0700,x
INX
BNE clrmem1a
RTS

clrmem2:       ; clear out SPR-RAM

LDA #$EF
clrmem2a
STA $0200,x
INX
BNE clrmem2a
RTS

Palette:

.DB $0F,$31,$32,$33,$0F,$35,$36,$37,$0F,$39,$3A,$3B,$0F,$3D,$3E,$0F
.DB $0F,$1C,$15,$14,$0F,$02,$38,$3C,$0F,$1C,$15,$14,$0F,$02,$38,$3C

;*************************************************
; NMI Handler Code
;*************************************************

NMI:

PHA
TXA
PHA
TYA
PHA

JSR sprdmarun     ; run sprite DMA       513
JSR joycontrol    ; run controller code  132

PLA
TAY
PLA
TAX
PLA

RTI

sprdmarun:

LDA sprendisflag  ; 0=enable sprite DMA 1=disable sprite DMA
BNE skipsprdma

LDA #$00
STA $2003
LDA #$02
STA $4014    ; allocate page 2 for sprite ram +513 cycles

skipsprdma:

RTS

joycontrol:

LDA ctrlendis    ; 0=enable 1=disable controller port 1 code
BNE skipctrl     ; skip controller code if 1

LDA #$01         ; 132 cycle controller port 1 ring counter
STA JOYPAD1
STA buttons
LSR A
STA JOYPAD1

joyloop:

LDA JOYPAD1
LSR A
ROL buttons
BCC joyloop

skipctrl

RTS

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

;************************************************
; IRQ Handler Code
;************************************************

IRQ:

PHA
TXA
PHA
TYA
PHA

NOP

PLA
TAY
PLA
TAX
PLA

RTI

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


;***********************************************
; INTERRUPT VECTOR TABLE
;***********************************************

.ORG $FFFA

.DW NMI
.DW RESET
.DW IRQ

;***********************************************
; CHR-ROM Bank 0
;***********************************************

.incbin "chr.chr"

           
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178276)
Gil-Galad: Looks good so far. I see the main loop is an infinite JMP loop, that's one of those eternally debated program structures (vs having a minimal NMI routine and most code in the main loop). Either way can work just fine if you're careful, I tend to go for the minimal NMI, myself. The thing to consider is what happens if/when your code takes longer than a frame. With the "big NMI" setup, one easy way out is to allow recursive NMIs, but at the start of NMI you check to see if the last NMI had completed or not, and if it hasn't you can play the music and skip all the other processing. Then the game will slow down, but not the music. In that case, hopefully the music code is not what was being interrupted, heheh. :P

rainwarrior wrote:
Memblers wrote:
FWIW, I think the concern about Famitracker's replay engine can appear a bit overblown for most cases. If you want to make something that's going to push the NES to it's limits, sure. But if you're making a game for the first time it's really advisable to keep it simple as much as possible. Unless it's a pretty intense program, you probably won't be in danger of running out of CPU cycles or RAM. I may be wrong, but I seem to recall that even quite a few commercial game sound engines were bigger hogs than Famitracker's.

I don't think CPU or RAM are the biggest problems with it.

Famitracker has no sound effect support. This is probably the most important reason to use something else. (Unless you want to write your own separate sound effect engine and learn how to patch that on top of Famitracker's music.)

The second biggest reason I think is its ROM footprint. It's more than 5k. The common alternatives are more like 1k.

It does use a lot more RAM than the alternatives too, but it thankfully keeps that mostly off the ZP, so I think this is not too bad, really.

The CPU usage isn't too bad, either. Maybe on average you lose 1000 cycles per frame compared to something lighter? Probably not the dealbreaker.


I did this little bit of music engine benchmarking, I was having trouble with Famitone that day so that's why it didn't make onto that short list. Though anyone can check it with NintendulatorDX, I was hoping to build them all with the same example song.
http://forums.nesdev.com/viewtopic.php?f=6&t=13580
It's worth noting that max CPU per frame usage is critical when using sprite zero hit, otherwise average CPU usage is probably what matters.

Yeah sound effects are a big deal. I've been doing a soundtrack for a game I'm making and I haven't even started considering sound effects yet, but I've had my old Nerdtracker 2 habits in mind. For NT2 I once made a sound effects engine with this simple mod: redirect all the sound register writes to RAM, then my own sound effects code overrides them in RAM, finally in the end all those buffered registers gets dumped to the actual registers. So I figure I'll probably end up doing the same thing in Famitracker. In the case of my current game project I have the luxury of it being a turn-based game, tons of ROM, and the spartan channel effects support in other engines is a deal-breaker for me. Anymore I can't live without the Gxx effect, duty-cycle envelopes, and often enough the volume column. Though those can be mostly worked around the hard way, I'm just taking the easy way out because this game has a lot of music (35 tracks so far..) that I don't want to optimize unless I have to.

ROM usage I can see being a concern for some people, definitely not for me though since I make my own hardware. 512kB ROM now costs 75 cents (edit: checking again it seems to be 97 cents, not sure what the deal was with 75 cent price I saw), and I've pretty much adopted that as my entry-level cartridge.
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178277)
Memblers wrote:
The thing to consider is what happens if/when your code takes longer than a frame. With the "big NMI" setup, one easy way out is to allow recursive NMIs, but at the start of NMI you check to see if the last NMI had completed or not, and if it hasn't you can play the music and skip all the other processing.

Don't forget about raster effects. If you have a sprite 0 status bar, effects based on IRQs, and stuff like that, you also need to reset them for the new frame, otherwise you'll get glitched frames every time there's slowdown.
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178283)
Memblers wrote:
I've been doing a soundtrack for a game I'm making and I haven't even started considering sound effects yet, but I've had my old Nerdtracker 2 habits in mind.

As far as I know, of the current game-oriented homebrew NES sound drivers, only Pently supports NT2-style decaying envelopes.

Quote:
Anymore I can't live without the Gxx effect, duty-cycle envelopes

Pently supports both, though envelopes can't loop.

Quote:
and often enough the volume column.

Pently doesn't support this yet, but it's the next thing that I plan to put in after The Curse of Possum Hollow wraps.
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178285)
That's one thing I've been trying to figure out is how to layout my code between NMI or in the Reset code. In other demos I've put most of the code in reset. I've had problems doing that before.

I'm trying a bulk of the code in NMI but I am setting up flags to enable and disable code routines. Not sure how well this is going to work out but I'm going to try and make sure the timing is right by putting in DMA and controller code so far to run once per frame. I think I need to set up more flag variables later on to pick and choose which code I'm going to run. Not really sure what I should do different.
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178287)
Gil-Galad wrote:
That's one thing I've been trying to figure out is how to layout my code between NMI or in the Reset code. In other demos I've put most of the code in reset. I've had problems doing that before.

It's basically this (the example is pseudo code and done from my head):

In reset:
Code:
start:

if (waitForNmi == false)
{
    DoGameLogic();
    waitForNmi = true;
    goto start;
}


And then in NMI:
Code:
SaveRegistersToStack();

if (waitForNmi == true)
{
    waitForNmi = false;
    DoNmiStuff();
}

DoMusicStuff();
RestoreRegistersFromStack();


This should be it.
Did I remember anything incorrectly?
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178288)
Controller reading in the NMI and game logic in the main thread could cause your game logic to miss presses, especially if the NMI handler also calculates the "pressed or released since last frame" flag.
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178354)
tokumaru wrote:
Don't forget about raster effects. If you have a sprite 0 status bar, effects based on IRQs, and stuff like that, you also need to reset them for the new frame, otherwise you'll get glitched frames every time there's slowdown.

Ah yeah. On that topic too, I think it's crazy that so many games have the sprite 0 status bar on the bottom of the screen (looking at you, Rare!), that trick is a lot fancier than most (non-dev) people would think. You can't even have a frame-time overflow in the case. I mean, you can without a complete disaster, but it'll look like Konami's first TMNT game - maybe the glitchiest status bar ever?

tepples wrote:
Memblers wrote:
I've been doing a soundtrack for a game I'm making and I haven't even started considering sound effects yet, but I've had my old Nerdtracker 2 habits in mind.

As far as I know, of the current game-oriented homebrew NES sound drivers, only Pently supports NT2-style decaying envelopes.

Oh man, kill it with fire.. :lol: I'll always love NT2 but I don't make new stuff in it. I spent so much effort working around that to make it sound better without knowing why exactly, didn't realize at the time that because the NES volume control is linear, a linear fade sounds terrible. That's why the NES's hardware decay sounds so distinctive and weird, it's just.. wrong. A built-in logarithmic decay could be useful perhaps, I suppose everyone will just build their own envelopes to do it themselves, though. But, being able to scale that (like the FT2's volume control channel) is something I really like, I'm glad you're considering that for the future. I suppose it can be quickly done with a 4-bit x 4-bit look-up table?

I had it in my head that Pently only accepted MML-type input, but looking into it again I see that NovaSquirrel had made a Famitracker converter for it. With that, I am immediately intrigued. The little testing I did previously seemed to have Pently appearing to be CPU efficient to an impressive degree, that's definitely something I will be exploring!
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178356)
Memblers wrote:
Ah yeah. On that topic too, I think it's crazy that so many games have the sprite 0 status bar on the bottom of the screen (looking at you, Rare!), that trick is a lot fancier than most (non-dev) people would think. You can't even have a frame-time overflow in the case. I mean, you can without a complete disaster, but it'll look like Konami's first TMNT game - maybe the glitchiest status bar ever?

In some earlier discussions we figured that this is probably because it's much more difficult to set the scroll arbitrarily mid-screen than in VBlank. Few developers seemed to manage it back in the day (like Rare in Battletoads). But it is indeed a PITA to have the status bar at the bottom without mapper IRQs.
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178359)
thefox wrote:
In some earlier discussions we figured that this is probably because it's much more difficult to set the scroll arbitrarily mid-screen than in VBlank.

What's so difficult about it? My game is mapper 0 without any fancy stuff and yet I easily managed clean parallax scrolling.*
Just make sure that there's one pixel line that consists of all the same color. Put the sprite 0 there. And then set the scrolling. The scrolling change will be done before rendering the line is over and since the line consists of pixels of the same color, the player won't notice any artifacts.

* In fact, I do three scrolling changes per frame:
First one in NMI that goes to scrolling position 0.
Then the status bar is drawn. While this is done, I do some game logic whose duration is always pretty constant.
Then I wait for the nine sprites per scanline flag because I positioned nine empty sprites below the status bar. There, I set the scrolling for the background.
Then the background is drawn. In the meantime, I do the majority of the game logic.
Then sprite 0 split.
Then the foreground is drawn and I use the remaining time for some other constant-time logic.
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178361)
DRW wrote:
thefox wrote:
In some earlier discussions we figured that this is probably because it's much more difficult to set the scroll arbitrarily mid-screen than in VBlank.

What's so difficult about it? My game is mapper 0 without any fancy stuff and yet I easily managed clean parallax scrolling.

Setting the X scroll is trivial (they managed that even in SMB1), but setting the Y scroll with pixel precision is rather involved. See http://wiki.nesdev.com/w/index.php/PPU_ ... g#Examples
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178362)
Oh, you're talking about vertical scrolling. Sorry, I thought it was just about status bar on top vs. status bar at the botton.
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178374)
tepples wrote:
Controller reading in the NMI and game logic in the main thread could cause your game logic to miss presses, especially if the NMI handler also calculates the "pressed or released since last frame" flag.


It sounds like I need to move the controller code out of the NMI then if it's going to be buggy.

Now I'm back to the beginning. Should I make a loop in the reset to run the controller and logic code?

For example.

Code:

LDA #%10010000    ; enable NMI, background $1000 VRAM, sprites $0000 VRAM
STA $2000         ; sprite size 8x8, PPU increment 1, name table $2000 VRAM
LDA #%0011000     ; background visible, sprites visible, background & sprites clipped
STA $2001         ; color display

startloop:

JMP startloop



Where I have the JMP startloop is where I should put logic code instead? One time I tried putting code there and it was running way faster than it should be. I also ran the sound driver init here once and the NTSC music driver I was using sped up big time, sounded like PAL. So then I tried keeping code out of there because at the time I didn't have the skill to work out some type of timing or I just had the wrong idea. This has always been a problem for me.
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178375)
You don't need to move the controller code, unless you're making a zapper game. :)

Wait, no, tepples is right..IF your game logic goes longer than 1 frame, the logic will miss a change in button presses event. (Events like, Title Screen, exit if Start wasn't pressed last frame && Start is pressed this frame)

Move the jsr button_check to the infinite loop.

EDIT...
Have a flag for when the game logic is done, and only check buttons if it is set, or something like that.
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178380)
Gil-Galad wrote:
Now I'm back to the beginning. Should I make a loop in the reset to run the controller and logic code?


I'm writing you a shortened version of my own code. This works without problems and the NMI only does stuff that needs to be done during vblank.

Code:
Reset:
   ; All the initialization stuff.
   ; ...

@gameLogic:
   LDA WaitForNmi
   BNE @end
   JSR ControllerInput
   JSR GameLogic
   LDA #true
   STA WaitForNmi
@end:
   JMP @gameLogic

Nmi:
   PHA
   TXA
   PHA
   TYA
   PHA
   LDA WaitForNmi
   BEQ @end
@nmiStart:
   LDA #false
   STA WaitForNmi
   LDA PpuMaskValue
   STA PpuMask
   BEQ @end
   LDA #<Sprites
   STA OamAddr
   LDA #>Sprites
   STA OamDma
   JSR UpdatePpu
   JSR SetScrolling
@end:
   JSR SoundUpdate
   PLA
   TAY
   PLA
   TAX
   PLA
   RTI
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178384)
dougeff wrote:
IF your game logic goes longer than 1 frame, the logic will miss a change in button presses event.

Why is this a bad thing? Of course, if your game lags, the controller won't be updated until the logic is done. But that's what it's supposed to do.

If I read the controller in every NMI interrupt call, even if my game logic isn't finished yet, then this means that half of the logic will do status updates based on another input than the other half:

- Read Controller: Player presses right.
- Start move function: Update vertical movement based on player pressing right.
- Continue move function: Update weapon attack based on player pressing right.
- Oops! Lag. NMI interrupts the program and reads the controller again: Player presses left.
- Continue move function: Update horizontal movement based on player pressing left.
--> Inconsistency! The value changed in the middle of a function that was never meant to handle a change of the value.

You want that?

This one is better in my opinion:

- Read Controller: Player presses right.
- Start move function: Update vertical movement based on player pressing right.
- Continue move function: Update weapon attack based on player pressing right.
- Oops! Lag. NMI interrupts the program, but doesn't read the controller again: Player presses left, but the value is still set to right.
- Continue move function: Update horizontal movement based on player pressing right.
--> No inconsistency at all. The game logic will recognize the new button press at the start of the next frame when the game logic starts again.
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178388)
Quote:
Why is this a bad thing? Of course, if your game lags, the controller won't be updated until the logic is done. But that's what it's supposed to do.

You're misunderstanding. It's not about the a change in button state during lag not being read. That is indeed expected and normal. It's about a change in button state during lag being read, and then having an undesired effect.

NMI happens at ~60 FPS while it's enabled regardless of anything. I am reading my controller in the NMI, and so by extension it's happening at ~60FPS regardless of anything.

My game loop takes too long.

Code:
GAMELOOP STARTS
NMI OCCURS: I have pressed A.
GAMELOOP CONTINUES
NMI OCCURS: I am still holding A. My "press" of A has become a hold.
GAMELOOP CHECKS FOR A PRESS OF A TO JUMP. But I am now holding A because the NMI hit twice during the game loop.
The jump fails.

This is the case people are saying he should look out for when reading from the NMI.

Yes, you can read the joypad but not update whatever controls presses vs holds in your game in the NMI, and yes, you can conditionally not read/update the joypad in the NMI if the game loop has taken too long. But you wouldn't need those extra conditions at all if you just read the joypad not in your NMI.
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178396)
Kasumi wrote:
Code:
GAMELOOP STARTS
NMI OCCURS: I have pressed A.
GAMELOOP CONTINUES
NMI OCCURS: I am still holding A. My "press" of A has become a hold.
GAMELOOP CHECKS FOR A PRESS OF A TO JUMP. But I am now holding A because the NMI hit twice during the game loop.
The jump fails.

This is the case people are saying he should look out for when reading from the NMI.

That's why I think you should never read the controller during NMI, but at the start of the game logic:

Code:
CONTROLLER READING: I have pressed A.
GAMELOOP STARTS
NMI OCCURS: I am still holding A. But it isn't read. So, it doesn't become a hold.
GAMELOOP CONTINUES
NMI OCCURS: I am still holding A. But it isn't read. So, it doesn't become a hold.
GAMELOOP CHECKS FOR A PRESS OF A TO JUMP. Everything is fine. It's not a hold.


Kasumi wrote:
But you wouldn't need those extra conditions at all if you just read the joypad not in your NMI.

Exactly. So, why is reading the controller in the NMI an issue at all?

Even if I absolutely have to read the controller in NMI for some reason, it would still be trivial to avoid this problem: You don't need to implement any special conditions.
Since you need to have a condition to leave the NMI early anyway:
Code:
Nmi:
   PHA
   TXA
   PHA
   TYA
   PHA
   LDA WaitForNmi
   BEQ @end
@nmiStart:
   LDA #false
   STA WaitForNmi
   LDA PpuMaskValue
   STA PpuMask
   BEQ @end
   LDA #<Sprites
   STA OamAddr
   LDA #>Sprites
   STA OamDma
   JSR UpdatePpu
   JSR SetScrolling
@end:
   JSR SoundUpdate
   PLA
   TAY
   PLA
   TAX
   PLA
   RTI

you would just add this:
Code:
Nmi:
   PHA
   TXA
   PHA
   TYA
   PHA
   LDA WaitForNmi
   BEQ @end
@nmiStart:
   LDA #false
   STA WaitForNmi
   LDA PpuMaskValue
   STA PpuMask
   BEQ @end
   JSR ReadController ; --> New code.
   LDA #<Sprites
   STA OamAddr
   LDA #>Sprites
   STA OamDma
   JSR UpdatePpu
   JSR SetScrolling
@end:
   JSR SoundUpdate
   PLA
   TAY
   PLA
   TAX
   PLA
   RTI

That's it. Controller reading in NMI without any issues and without any additional dedicated code to check for lag in the controller routine.
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178398)
Quote:
That's why I think you should never read the controller during NMI, but at the start of the game logic:

I'm not sure we're reading the same things. That's what everyone is saying!
Quote:
Exactly. So, why is reading the controller in the NMI an issue at all?

For that reason I said? Also note that I said this too:
Quote:
Yes, you can read the joypad but not update whatever controls presses vs holds in your game in the NMI, and yes, you can conditionally not read/update the joypad in the NMI if the game loop has taken too long. But you wouldn't need those extra conditions at all if you just read the joypad not in your NMI.

It's like... so we're warning him about a thing that can happen. And you're posting ways to fix that thing that can happen to say it's not a problem. And yeah, it's not a problem if you know about it. But... we warned him because he may not know about it, but also because there are better ways to do it. See the first thing I quoted from you in this post. Does that make sense?

Maybe you don't need an extra condition, but it depends on your setup. And again: It's more about, "Hey, know about this thing." It's not, "Never do this thing, because there's no way around it." It's, "Here's a better way so you don't have to even think about it."
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178399)
DRW's example would work fine. That's basically what I do....

The first example that starts off like
Quote:
I'm writing you a shortened...
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178401)
Has anybody done measurements on what is a typical minimum "pulse width" on button presses (or releases) that one can expect? That is, if a player quickly taps the button. Or, holds it down, quickly releases it and then holds it down again.

I guess I might throw together a small test ROM for it.
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178403)
Kasumi wrote:
I'm not sure we're reading the same things. That's what everyone is saying!


I guess I misunderstood this one:
dougeff wrote:
IF your game logic goes longer than 1 frame, the logic will miss a change in button presses event.
[...]
Have a flag for when the game logic is done, and only check buttons if it is set, or something like that.

The first statement sounded like the following to me:
"The logic might miss a button press if it goes longer than one frame [therefore don't use the controller reading in the game logic but in NMI because then you will never miss controller updates]."

And the second sentence and its talking about a flag sounded like proposing NMI as well. Because when you have controller readings in the game logic anyway, then you don't need any flag specifically for the controller. If you're in the game logic, it's time to update the controller, end.
But the NMI is an interrupt, so controller reading in NMI might require a flag to decide if the controller values should be updated or ignored, based on outside conditions.

That's where I got the idea that this post suggested moving the controller reading to NMI.
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178405)
I don't think you misunderstood that post. He was suggesting a way to fix the problem, as you were.

For what it's worth: My reply with the "jump miss" example was only because I wasn't sure you saw that case. Because you did ask. You asked why it could be a problem, and I answered with that.

I wasn't trying to say, "You can't fix this." or "This can't be fixed", just "This is the case people are talking about."
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178407)
Yeah, I guess I understood it incorrectly. But we can summarize:

Controller reading in game logic: No problem.

Controller reading in NMI: No problem and no overhead either.
You need a check to prematurely exit the NMI anyway. So, you just put the controller reading after that check. No need for any flags, other than the general one that you need in NMI to begin with.
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178410)
One reason you might want to read the controller in NMI is if you're using even number of cycles since OAM upload as a means of DMC glitch avoidance, as Rahsennor discovered. Perhaps use the "is a new sprite list ready" flag as a signal for whether the main thread is ready to accept a new set of keypresses.
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178411)
thefox: There's this thing: viewtopic.php?f=22&t=14532&hilit=buttons

Edit: I guess you might have meant one that measures times less than a frame.
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178418)
If you have to read the controllers in the NMI because of DMC or whatever, you can save the result to a temporary location, and have the main loop read this location instead of using the hardware registers. The main loop would still be responsible for calculating holds, releases, etc., the NMI code would just abstract the hardware part.
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178423)
One game (maybe Silver Surfer) rolls each button into a different address, so it can tell how many frames a button has been pressed, up to 8 frames.
Not pressed = 00000000
First frame = 00000001
Then = 00000011
00000111
00001111
00011111
00111111
01111111
11111111
Stays like that till button released...

11111110
11111100
11111000
Etc, back to zero
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178430)
thefox wrote:
Has anybody done measurements on what is a typical minimum "pulse width" on button presses (or releases) that one can expect? That is, if a player quickly taps the button. Or, holds it down, quickly releases it and then holds it down again.

I guess I might throw together a small test ROM for it.

I seem to recall reading a study on button pressing a while back (can't remember the source, sorry), and I think something like 10 (edit: 16?) times per second was the upper limit for human rapid fire.

As for whether people can generate single frame input, you can if you try. A normal "tap" is going to be a few frames, but it's not hard to do a glancing tap of the button that only connects very briefly.
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178433)
I think I can do about 14 on a standard controller. I can make a video if you want.
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178435)
Maybe the number was 16.
http://indie-games-ichiban.wonderhowto. ... r-0127642/
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178446)
It depends on how long you sample the input for. I made a hack for Tecmo Super Bowl that displays the number of taps when two (man controlled) players collide.

The game takes 64 frames worth of input (~ one second). The fastest tappers in our community hit about 16 on average. But in any one sample it ranges ~ +/-10%.

So a guy who averages 16 could have a high of 18 and a low of 14.
Re: Getting started again!! Romhacker to NES Game Programmer
by on (#178467)
That's 6.66…% more than a second. How many buttons does Tecmo Super Bowl sample?

Mario Party 4's "Domination" minigame does a ten-second sample of A. It also, apparently, caps out at 160, possibly a subtle nod to Takahashi.

I can(could?) do ~14 as well, one-button.