It is currently Fri Dec 15, 2017 11:11 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 15 posts ] 
Author Message
PostPosted: Wed Jun 21, 2017 2:17 pm 
Offline
User avatar

Joined: Wed Jun 21, 2017 1:51 pm
Posts: 19
Hi there fellow NES Development Newbies!

I'm Rychan and I've started to get into NES development recently after having spent a year or two coding up Gameboy games in GBDK and C. There's a link to a few of them here if you're interested BTW: http://gamejolt.com/@refreshgames/games

I've never played with assembly until about a month ago and have finally plucked up the cpourage to take on the beast that is NESASM.

So far I've been able to get the basics of a game working well inside the FCEUX NES Emulator. Wrote some collision detection, faux randomness, played with sprites and moving from the title screen to the main screen and you can probably tell where this is going :)

Before we get there though, here's a link to an animation of the main game screen in it's current state via emulation:

https://twitter.com/refreshgamesuk/status/872585476661805057

So, I purchased a flash cart and with the help of a friend got my old NES up and running with the thing. Which is when I noticed a few fundamental flaws with my code haha! Yes joypad button detection and also running out of vblank time by putting EVERYTHING into NMI, like a noob which is where I am with ASM I guess :)

I've updated my joypad handling code which now works on device and understand that I now need to separate my game logic out into the main game loop and minimise the amount of stuff in NMI but I'm a little unsure how to both perform large background loads safely and how to inform my code that it needs to wait until vBlank is done for safe looping.

I mean, is it possible to OAM a whole background over in a single vblank amount of time? I don't know the cycle count for this but I imagine it's a lot less flexible then the gameboy :)

Any advice would be really handy, I'm still sorta dreading moving my code from NMI into the main game loop but understand that it needs to be done in order to persue my dreams of owning a NES cart which I made, 8 year old me would be so happy also :)


Top
 Profile  
 
PostPosted: Wed Jun 21, 2017 2:58 pm 
Offline
User avatar

Joined: Thu Apr 23, 2009 11:21 pm
Posts: 806
Location: cypress, texas
rychan wrote:
I mean, is it possible to OAM a whole background over in a single vblank amount of time? I don't know the cycle count for this but I imagine it's a lot less flexible then the gameboy :)

Any advice would be really handy, I'm still sorta dreading moving my code from NMI into the main game loop but understand that it needs to be done in order to persue my dreams of owning a NES cart which I made, 8 year old me would be so happy also :)
OAM is Object Attribute Memory, I think, and I also believe that deals with sprites. OAM a whole background? :? For me, writing nametables 00 and 01 requires disabling rendering and it takes multiple frames, so I doubt it's possible to draw an entire background in one vblank (NMI).

You are 8 years old and interested in the NES? That's pretty fantastic! :D


Top
 Profile  
 
PostPosted: Wed Jun 21, 2017 3:07 pm 
Offline
User avatar

Joined: Wed Jun 21, 2017 1:51 pm
Posts: 19
Eh heh, nope, I'm way over 8 years old unregistered (more like 35 currently, although my son, who happens to be 8 years old loves the NES now!) :) But I remember my 8 year old self wishing to make a game that runs on the NES :)

I take it that's why my game crashes to black on actual hardware then? when I'm disabling rendering then attempting to restart it later on, I'm still inside NMI, perhaps it just isn't making it to the point (due to NMI being re-run)w here it's requesting it to be turned back on then! That would make sense now I think about it.


Top
 Profile  
 
PostPosted: Wed Jun 21, 2017 3:14 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10164
Location: Rio de Janeiro - Brazil
Short reply because I can't be here long: No, you can't update a full background in a single vblank. The fastest you can copy a byte (from ROM or normal RAM) is 8 cycles (LDA + STA), and a full background is 1024 bytes, meaning you'd need at least 8192 cycles (not considering the overhead of loops or the NMI handler) but vblank only lasts ~2273 cycles. In the best case, you're looking at 4 frames for a full background.


Top
 Profile  
 
PostPosted: Wed Jun 21, 2017 3:22 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10164
Location: Rio de Janeiro - Brazil
Is the structure of your program copied from the Nerdy Nights tutorial? The problem there isn't exactly that everything is in the NMI handler (which although not optimal, is a valid program structure - SMB for example uses it), but the fact that the game logic is placed before the PPU updates in said NMI handler. Simply inverting this order should allow you to make full use of a frame's time, with PPU updates using the first 20 scanlines (vblank) and the game logic using the remaining 240 scanlines.


Top
 Profile  
 
PostPosted: Wed Jun 21, 2017 3:25 pm 
Offline
User avatar

Joined: Thu Apr 23, 2009 11:21 pm
Posts: 806
Location: cypress, texas
rychan wrote:
Eh heh, nope, I'm way over 8 years old unregistered (more like 35 currently, although my son, who happens to be 8 years old loves the NES now!) :) But I remember my 8 year old self wishing to make a game that runs on the NES :)

I take it that's why my game crashes to black on actual hardware then? when I'm disabling rendering then attempting to restart it later on, I'm still inside NMI, perhaps it just isn't making it to the point (due to NMI being re-run)w here it's requesting it to be turned back on then! That would make sense now I think about it.
Ah ok, I'm about the same age as you. :) Sorry for misunderstanding.

Yes, that sounds about right since tokumaru said it takes ~ 2273 cycles for a vblank; it's not at all.


Top
 Profile  
 
PostPosted: Wed Jun 21, 2017 4:41 pm 
Offline
User avatar

Joined: Wed Jun 21, 2017 1:51 pm
Posts: 19
Thanks for the tips everyone. Going to look into re-writing my code into the main loop tomorrow night now. I'm assuming that so long as my code per frame doesn't go over the start of vblank if can BIT test $2002 at the end of the main loop to prevent execution from looping too many times.

I'm thinking I need to pre-prepare the screen to be off then use frames to load in the new data over 4-5 frames worth of time before re-enabling it again, should be fun!

Will go through the rewrites tomorrow anyhoo, thanks once again, I'll post my results when I have them!


Top
 Profile  
 
PostPosted: Wed Jun 21, 2017 5:12 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10164
Location: Rio de Janeiro - Brazil
rychan wrote:
I'm assuming that so long as my code per frame doesn't go over the start of vblank if can BIT test $2002 at the end of the main loop to prevent execution from looping too many times.

Not really. Reading $2002 at the exact time the vblank flag gets set cancels the NMI (and the flag returns clear). But even if this quirk didn't exist, the vblank flag is cleared when vblank ends, so if your NMI handler used all of the vblank time you'd miss the flag anyway. You have to implement a flag in software to properly detect vblanks.

Quote:
I'm thinking I need to pre-prepare the screen to be off then use frames to load in the new data over 4-5 frames worth of time before re-enabling it again, should be fun!

Most games do indeed turn rendering off between levels or any screens that require large video updates, to gain unrestricted access to VRAM. If a blank screen isn't a problem, that's way easier than spreading large updates across several vblanks.


Top
 Profile  
 
PostPosted: Wed Jun 21, 2017 5:26 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 5898
Location: Canada
As tokumaru said, polling $2002 is unreliable for a vblank wait; even in the best conditions it misses a frame occasionally and you will wait a whole extra frame.

If you want all your code outside the NMI, the simplest way to poll for the next vblank is something like this:
Code:
nmi:
   inc nmi_counter
   rti

wait_for_nmi:
   lda nmi_counter
@loop:
   cmp nmi_counter
   beq @loop
   rts

; ...

main_loop:
   ; ...
   jsr wait_for_nmi
   ; ... do all PPU updates right here, immediately after the wait
   ; ...
   jmp main_loop


In this case you have an NMI handler that does nothing except change a value (nmi), and when you want to wait for it you can just spin testing that value until it changes (wait_for_nmi). Immediately following the wait test you'll be close to the start of vblank and can freely use the PPU for a short time.

Edit: fixed a mistake spotted by tepples.


Last edited by rainwarrior on Wed Jun 21, 2017 5:50 pm, edited 1 time in total.

Top
 Profile  
 
PostPosted: Wed Jun 21, 2017 5:38 pm 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 19347
Location: NE Indiana, USA (NTSC)
rainwarrior wrote:
Code:
nmi:
   inc nmi_counter
   rti

wait_for_nmi:
   lda nmi_counter
@loop:
   cmp nmi_counter
   bne @loop
   rts

Shouldn't that be beq @loop? You want to keep waiting while the frame count stays the same.


Top
 Profile  
 
PostPosted: Wed Jun 21, 2017 8:44 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10164
Location: Rio de Janeiro - Brazil
rainwarrior wrote:
Code:
nmi:
   inc nmi_counter
   rti

wait_for_nmi:
   lda nmi_counter
@loop:
   cmp nmi_counter
   beq @loop
   rts

; ...

main_loop:
   ; ...
   jsr wait_for_nmi
   ; ... do all PPU updates right here, immediately after the wait
   ; ...
   jmp main_loop

The simplicity of this approach is great, but I want to take this opportunity to say that in practice, this offers no advantage over the all-in-NMI method, since it doesn't provide any means to deal with lag frames. If the game logic takes too long, the music will slow down, and raster effects (e.g. status bars, parallax effects) on the next frame might glitch. If lag frames aren't an issue, this is probably the simplest structure you can use, and also versatile in the sense that you can easily have different vblank handlers for different parts of the program, or you can simply JSR to a common one if that works best for you.

The all-in-NMI method, when done the correct way (i.e. PPU updates first, game logic second - not how Nerdy Nights presents it), at least offers the possibility of doing something about lag frames, since the NMI will interrupt the game logic, at which point you can choose to handle some high priority tasks such as music and raster effects before the new frame begins, even if the logic for the previous frame hasn't finished yet.


Top
 Profile  
 
PostPosted: Thu Jun 22, 2017 1:20 am 
Offline

Joined: Tue Oct 06, 2015 10:16 am
Posts: 607
Just in case you're not aware, you can program for the nes in C just fine. For simpler games you don't need to touch asm at all, for more complex ones you only need to write performance-sensitive parts in it.


Top
 Profile  
 
PostPosted: Thu Jun 22, 2017 1:33 am 
Offline
User avatar

Joined: Wed Jun 21, 2017 1:51 pm
Posts: 19
Oh, there's a C compiler out there? I honestly wasn't aware of that. I might fall back to that should I need to, but I feel that, having only ever used high level languages, it's one of those things which after many years of coding I should at least have a go at making something in. Even though, yup, simplistic game using mapper 0, it's one of those challenges I want to beat :)

Does the @ prefix change anything about a label? Or is it one of those good practises to help organise code better.

Got a couple of hours now to play around with things in code again, yay!


Top
 Profile  
 
PostPosted: Thu Jun 22, 2017 1:46 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10164
Location: Rio de Janeiro - Brazil
rychan wrote:
Does the @ prefix change anything about a label?

It depends on the assembler, but that normally means the label is local, so you can reuse names if there's a global label between them:

Code:
@LocalLabel:
   jmp @LocalLabel

GlobalLabel:

@LocalLabel:
   jmp @LocalLabel

EDIT: You mentioned you're using NESASM, and looking up its documentation reveals that local labels in that assembler begin with a dot instead. Just one of the many things NESASM does differently from everything else.


Top
 Profile  
 
PostPosted: Thu Jun 22, 2017 2:20 am 
Offline
User avatar

Joined: Wed Jun 21, 2017 1:51 pm
Posts: 19
Cool, will investigate into using those later on, I'll stick to global labels for the moment to keep things simpler for me :)

Also, this happened! Yay!

https://youtu.be/gc1q4Ph1-x8

and there's even an hour left for me this morning to re-enable those missing features, so psyched!


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 15 posts ] 

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 4 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group