8x16 and whatever else unreg wants to know

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

unregistered
Posts: 1071
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Post by unregistered » Wed Aug 31, 2011 2:24 pm

here is code from the scrolling tutorial

Code: Select all

NTSwapCheck:
  LDA scroll       ; check if the scroll just wrapped from 255 to 0
  BNE NTSwapCheckDone  ;if not, skip this NameTable Swap
  
NTSwap:
  LDA currNameTable    ; load current nametable number (0 or 1)
  EOR #$01         ; exclusive OR of bit 0 will flip that bit
  STA currNameTable    ; so if nametable was 0, now 1
                   ;    if nametable was 1, now 0
  inc CameraX+1
  
NTSwapCheckDone:

  LDA #$00
  STA $2003       
And I added the inc CameraX+1. After starting our game and then waiting for a while CameraX+1 becomes very incremented. That isn't good. If our character waits at the beginning of "the game" the scroll variable stays at 0. So each time it runs through this code it changes currNameTable... that's not good either, right? I'm unsure of changing the code because there may be a reason that bunnyboy kept it like this. :? Do you have a helpfull idea?

edit: And what if during the game one person stops traveling with scroll at exactly 0 and waits for a bit? It would mess up the same way, I think. :(
Last edited by unregistered on Wed Aug 31, 2011 3:06 pm, edited 1 time in total.

User avatar
tokumaru
Posts: 11755
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru » Wed Aug 31, 2011 3:01 pm

That code is terrible. The camera can only move 1 pixel at a time (otherwise you might miss the 0 that triggers the nametable switch), and the person who wrote it failed to realize that you don't need any sort of special checks to switch nametables: the lowest bit of CameraX+1 (i.e. the 9th bit of the overall coordinate) tells you which name table to display.

You can do something like this:

Code: Select all

	lda currNameTable
	and #$fe ;clear the lowest bit
	sta currNameTable

	lda CameraX+1 ;get the high byte of the camera
	and #$01 ;keep only the lowest bit
	ora currNameTable ;combine with the other value
	sta currNameTable ;this is what you'll write to $2000 when setting the scroll
And the low byte of CameraX (CameraX+0 or simply CameraX - I like the "+0" because it helps me identify multi-byte variables, even though in practice adding 0 is useless) is what you'll write to $2005 when setting the X scroll. There's no need for a "scroll" variable.

That way you can modify CameraX anyway you want, no need to change it pixel by pixel, which mean your character can move faster if you want.

unregistered
Posts: 1071
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Post by unregistered » Wed Aug 31, 2011 9:20 pm

tokumaru wrote:

Code: Select all

   lda currNameTable
   and #$fe ;clear the lowest bit
   sta currNameTable
Does currNameTable ever have a value other than 0 or 1? :)
After this code is run currNameTable is always 0 right?
tokumaru wrote:

Code: Select all

   lda CameraX+1 ;get the high byte of the camera
   and #$01 ;keep only the lowest bit
   ora currNameTable ;combine with the other value
   sta currNameTable ;this is what you'll write to $2000 when setting the scroll
What do you mean ora currNameTable to say? X OR False == X ? :)
Last edited by unregistered on Wed Aug 31, 2011 9:41 pm, edited 1 time in total.

User avatar
tokumaru
Posts: 11755
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru » Wed Aug 31, 2011 9:40 pm

unregistered wrote:Does currNameTable ever have a value other than 0 or 1? :)
Heh, I don't know, I haven't seen the rest of the code. Even if the name suggests it only specifies a nametable, I assumed it was the value that's written to $2000 (the lower 2 bits select the nametable, while the upper bits do other things).
After this code is run currNameTable is always 0 right?
The purpose is to clear bit 0, but keep all the others.
tokumaru wrote:Is the ora currNameTable a waste? X OR False == X ? :) (I'm sorry, this is me asking stupid questions.) :?
OR can be used to set bits. First I cleared the bit (AND 0 always results in 0) and then I OR'ed it with the nametable bit. 0 OR 0 = 0, while 0 OR 1 = 1, so I essentially transferred the bit from one variable (CameraX+1) to the other (currNameTable).

You can do this in many different ways, but the code I wrote is more "compatible" with the one you had before, so I thought it would be easier for you to understand. Honestly I'd rather just do this to set the scroll:

Code: Select all

	lda CameraX+1
	and #$01 ;keep only the first bit
	ora Cur2000Settings ;combine with the other PPU settings
	sta $2000
	lda CameraX+0
	sta $2005 ;set the horizontal scroll
	lda #$00
	sta $2005 ;set the vertical scroll
This takes care of everything. You just have to put all your $2000 configuration in "Cur2000Settings" and keep the lowest bit clear.

unregistered
Posts: 1071
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Post by unregistered » Wed Aug 31, 2011 10:09 pm

I'm sorry for saying some of the code that you spent valuable time typing out for me was a waste. My sister helped me understand my mistake but I asked her too late. Ok. Thank you for your response tokumaru. Have a good night. :)

User avatar
tokumaru
Posts: 11755
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru » Thu Sep 01, 2011 5:55 am

Maybe you should have your sister code the game then! XD

unregistered
Posts: 1071
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Post by unregistered » Thu Sep 01, 2011 10:16 am

haha! Thanks, your code works excellent! I can increase the speed of the scrolling like you said! :D Time for lunch... (I've been working on learning and using this code for almost 3 hours.)

3gengames
Formerly 65024U
Posts: 2276
Joined: Sat Mar 27, 2010 12:57 pm

Post by 3gengames » Thu Sep 01, 2011 10:51 am

Also, maybe make it so that you have 2 variables for the screen nametabale and other stuff, so that when you do a $2000 and $2001 store, you don't have to worry about your code running out of VBlank. :D

unregistered
Posts: 1071
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Post by unregistered » Thu Sep 01, 2011 1:00 pm

3gengames wrote:Also, maybe make it so that you have 2 variables for the screen nametabale and other stuff, so that when you do a $2000 and $2001 store, you don't have to worry about your code running out of VBlank. :D
I've never planned on doing a $2000 and $2001 store except the first one, but I read bunnyboys code and it says:

Code: Select all

  ;;This is the PPU clean up section, so rendering the next frame starts properly.
  LDA #%10010000   ; enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
  ORA nametable    ; select correct nametable for bit 0
  STA $2000
  
  LDA #%00011110   ; enable sprites, enable background, no clipping on left side
  STA $2001
    
So I added the $2000 store that tokumaru suggested. Maybe future $2000 stores would be good to have.? Thanks 3gengames. :) Writes to register $2000 are ignored for the first 30000 cycles! :shock: Well my first $2000 store didn't matter.....

3gengames
Formerly 65024U
Posts: 2276
Joined: Sat Mar 27, 2010 12:57 pm

Post by 3gengames » Thu Sep 01, 2011 1:13 pm

Yes, in your startup code you should have something like...

Setup stuff;
BIT $2002
-
BIT $2002
NPL -
Clear RAM/Stuff;
-
Bit $2002
BPL -
MainCode after

and then you need 2 variables in RAM, 1 for the $2000 write, and 1 for the $2001 write. After you wait the 2 frames with the BIT loops in the beginning, you can just write the statuses to RAM, and then turn on the NMI with a $2002 write outside of VBlank to start it, but at the end of your VBlank you can then write the writes from the 2 RAM spots to $2000 and $2001, because then other parts of your program that need to edit attributes of the screen like moving the nametable to another one or stuff, can do so without custom and bulky VBlank code to catch those and add them outside of the preprocessing. Make sense?

A bad example would be using the intensity bits to show the game's paused. [Bad idea to do something like that, but makes a good example.] If you don't use the method of storing $2000/$2001 write from RAM, then your program will have to set a flag and catch inside VBlank that a certain program needs to run and eat up precious cycles. But with the other method, you can over run your main game engine by making the pause turn on the intensity bits from the piece of RAM instead of trying to catch that it needs to happen in VBlank. Although the best way for this to seperate your NMI and your game code entirely and just do a INC FRAME/RTI, although there's still benefits from writing to $2000/$2001 inside VBlank from RAM though. :)

unregistered
Posts: 1071
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Post by unregistered » Thu Sep 01, 2011 2:28 pm

ok. : )
---
Before continueing scrolling beyond two screens... I think I should introduce our character to the level. Right now it doesn't rely on anything coded in the level. We want this lady to stand on the ground.... how should we do that? I have the tile codes for the ground parts/sprites.

3gengames
Formerly 65024U
Posts: 2276
Joined: Sat Mar 27, 2010 12:57 pm

Post by 3gengames » Thu Sep 01, 2011 2:30 pm

Well, start making parts of the entire system. Stuff like background collision [Possibly worth using 64 bytes as a buffer to do calculations outside of vblank for the buffer] and stuff like that. Hit detection, object creation/deletion, etc. :)

User avatar
tokumaru
Posts: 11755
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru » Thu Sep 01, 2011 3:36 pm

How are your levels stored? Just like you read them in order to draw to the nametables, you have to read them to check for collisions. Each block in the level must have its own solidity information (you can use a table for that).

You'll have to check key points around the character and make decisions depending on the solidity of the blocks that surround her. For gravity, for example, you should always check the block right below the character's feet, and if it's not solid the character must move down (i.e. fall).

Here are some posts where I talked about background collision, see if you can get something useful from them:

http://nesdev.com/bbs/viewtopic.php?p=4617#4617
http://nesdev.com/bbs/viewtopic.php?p=40918#40918
http://nesdev.com/bbs/viewtopic.php?p=59897#59897
http://nesdev.com/bbs/viewtopic.php?p=60374#60374

I'm not gonna lie to you, this will be hard to get right. This is the part where your little NES program stops being a playground and starts becoming a game. At this point, everything you program needs to be more robust and integrated, everything must be a "system", rather than just random blocks of code with immediate results on the screen. This is serious business.

3gengames
Formerly 65024U
Posts: 2276
Joined: Sat Mar 27, 2010 12:57 pm

Post by 3gengames » Thu Sep 01, 2011 3:49 pm

How good of practice is it to buffer a map with either 64 bytes [1 pit per tile] or 2 bits per tile and make it 128 bytes and use that to do all calculations out of vblank and such?

User avatar
tokumaru
Posts: 11755
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru » Thu Sep 01, 2011 5:18 pm

Considering that there are usually several objects that need collision tests against the background, the time taken to do this will easily be more than 20 scanlines, so that fact that this HAS to be done outside of VBlank is not even up for discussion.

Now, if you use only 1 bit per tile, you can only mark the tiles as either solid or empty. Forget about water, lava, blocks that hurt the player, and so on. This might be OK for some simple games, but definitely too limited for the majority of them. With 2 bits it gets a little better, but not much.

The ideal way to do it, in my opinion, is to use metatiles and associate all collision information to them, rather than to individual tiles. Also, instead of buffering whole screens of collision data, just read the metatiles from the level map (which may or may not be buffered) and then look for their collision information. I don't expect newbies to do this on their first try though.

Post Reply