Loading levels

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
danton19
Posts: 4
Joined: Sun May 09, 2021 1:06 pm

Loading levels

Post by danton19 »

Hi! I'm newbie on this! I've followed the nerdy nights tutorials and certain articles and I managed to write, build and run an small game. I'm now working on creating the NES version of a game that I did in Unity for a Jam. This forum has been really helpful, thanks a lot! I had a question about loading levels (on my case is just fixed levels, no scrolling), what would be a good approach on this? Thanks for the help!
Oziphantom
Posts: 1565
Joined: Tue Feb 07, 2017 2:03 am

Re: Loading levels

Post by Oziphantom »

There are no files. you don't load anything. So you load routine becomes

Code: Select all

LDA LevelPtrLo,x
STA $02
LDA LevelPtrHi,x
STA $03
LDY #SizeOfLevelData
- LDA ($02),y
STA PlaceInRAMThatHoldsLevelData,y
DEY
BNE -
If your level data is over 256 bytes you will need to expand the loop. Also the pointers in this case need to be point to the start of the level data -1 ie.

LevelPtrLo .byte <(Level1-1),<(Level2-1)
LevelPtrHi .byte >(Level1-1),>(Level2-1)
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Loading levels

Post by tokumaru »

Basically, use pointers instead of absolute addresses to access the level data, and setup all the pointers when a new level starts. For example, instead of this:

Code: Select all

	lda Level1Collision, y
Do this:

Code: Select all

CollisionPointers:
	.dw Level1Collision
	.dw Level2Collision
	.dw Level3Collision
	.dw Level4Collision
	;(...)

Code: Select all

	lda CurrentLevel ;gets the number of the current level
	asl ;multiplies it by 2 since each pointer is 2 bytes
	tax ;use it as an index
	lda CollisionPointers+0, x ;copies the low byte to ZP
	sta LevelCollision+0
	lda CollisionPointers+1, x ;copies the high byte to ZP
	sta LevelCollision+1

Code: Select all

lda (LevelCollision), y
You need to do this for collision maps, background data, palettes, and anything else that's unique to each level. If you absolutely need to use absolute (no pun intended) addressing for whatever reason, then you can either copy the data to RAM beforehand (like in Oziphantom's example), or copy the code itself to RAM and manipulate the addresses before running it (i.e. self-modifying code, which is a moderately advanced topic!), but either way you're gonna need non-insignificant amounts of RAM.

EDIT: Since you're coming from Nerdy Nights, you're probably using NESASM, in which case the syntax for indirection uses [] instead of (), so you'd actually do lda [LevelCollision], y.
danton19
Posts: 4
Joined: Sun May 09, 2021 1:06 pm

Re: Loading levels

Post by danton19 »

understood! awesome, thanks a lot! Great explanations
puppydrum64
Posts: 160
Joined: Sat Apr 24, 2021 7:25 am

Re: Loading levels

Post by puppydrum64 »

tokumaru wrote: Sun May 09, 2021 11:02 pm Basically, use pointers instead of absolute addresses to access the level data, and setup all the pointers when a new level starts. For example, instead of this:

Code: Select all

	lda Level1Collision, y
Do this:

Code: Select all

CollisionPointers:
	.dw Level1Collision
	.dw Level2Collision
	.dw Level3Collision
	.dw Level4Collision
	;(...)

Code: Select all

	lda CurrentLevel ;gets the number of the current level
	asl ;multiplies it by 2 since each pointer is 2 bytes
	tax ;use it as an index
	lda CollisionPointers+0, x ;copies the low byte to ZP
	sta LevelCollision+0
	lda CollisionPointers+1, x ;copies the high byte to ZP
	sta LevelCollision+1

Code: Select all

lda (LevelCollision), y
You need to do this for collision maps, background data, palettes, and anything else that's unique to each level. If you absolutely need to use absolute (no pun intended) addressing for whatever reason, then you can either copy the data to RAM beforehand (like in Oziphantom's example), or copy the code itself to RAM and manipulate the addresses before running it (i.e. self-modifying code, which is a moderately advanced topic!), but either way you're gonna need non-insignificant amounts of RAM.

EDIT: Since you're coming from Nerdy Nights, you're probably using NESASM, in which case the syntax for indirection uses [] instead of (), so you'd actually do lda [LevelCollision], y.
Wouldn't CollisionPointers+1 come first because .dw stores the two bytes little-endian? I tend to mix this up a lot so I could be wrong
User avatar
Quietust
Posts: 1918
Joined: Sun Sep 19, 2004 10:59 pm
Contact:

Re: Loading levels

Post by Quietust »

puppydrum64 wrote: Mon May 10, 2021 12:19 pm
tokumaru wrote: Sun May 09, 2021 11:02 pm

Code: Select all

	lda CurrentLevel ;gets the number of the current level
	asl ;multiplies it by 2 since each pointer is 2 bytes
	tax ;use it as an index
	lda CollisionPointers+0, x ;copies the low byte to ZP
	sta LevelCollision+0
	lda CollisionPointers+1, x ;copies the high byte to ZP
	sta LevelCollision+1
Wouldn't CollisionPointers+1 come first because .dw stores the two bytes little-endian? I tend to mix this up a lot so I could be wrong
It doesn't matter which byte you copy first, as long as you copy both of them (and put them in the proper locations).

Since the 6502 is little endian, all assemblers will (or at least should) output addresses (via .dw or dc.w or whatever directive they use) in little endian. Thus, if you copy the first byte of the source pointer to the first byte of the destination pointer and copy the second byte of the source pointer to the second byte of the destination pointer, the end result will be the same.

In other words, the following pieces of code are functionally identical:

Code: Select all

	LDA CollisionPointers+0,X
	STA LevelCollision+0
	LDA CollisionPointers+1,X
	STA LevelCollision+1
and

Code: Select all

	LDA CollisionPointers+1,X
	STA LevelCollision+1
	LDA CollisionPointers+0,X
	STA LevelCollision+0
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Loading levels

Post by tokumaru »

What Quietust said. Endianness only matters when the CPU is handling 16-bit values by itself. If you're manually handling the individual bytes of a multi-byte value, the order doesn't matter.

In this particular case, the CPU will be using the 16-bit value after we set it up, so we do need to make sure that the lower byte comes first in memory, then the high byte, but it doesn't matter what bye you copy first. I did copy them in that order because we typically manipulate multi-byte values from least-significant to most-significant.
danton19
Posts: 4
Joined: Sun May 09, 2021 1:06 pm

Re: Loading levels

Post by danton19 »

People, I've managed to apply this approach, thanks a lot for the help with pointers. I've started with the sprites, this is the code:
Setting up the sprites for each lvl, is it correct to use .db?

Code: Select all

spritesLvl1:
     ;vert tile attr horiz
  .db $80, $40, $00, $80   ;sprite 0
  .db $83, $41, $00, $6C   ;sprite 1
  .db $73, $41, $00, $8C   ;sprite 1
  .db $8B, $41, $00, $9C   ;sprite 1

spritesLvl2:
     ;vert tile attr horiz
  .db $80, $40, $00, $80   ;sprite 0
  .db $83, $41, $00, $6C   ;sprite 1
  .db $8B, $41, $00, $9C   ;sprite 1

spritesPointers:
  .dw spritesLvl1
  .dw spritesLvl2
Then to apply:

Code: Select all

LoadLevel:
  	lda levelNumber 
	asl A 
	tax 
	lda spritesPointers+0, x 
	sta levelSprites+0
	lda spritesPointers+1, x 
	sta levelSprites+1


LoadSprites:
  LDY #$00              
LoadSpritesLoop:
  LDA [levelSprites], y        
  STA $0200, y          
  INY                   
  CPY #$20              
  BNE LoadSpritesLoop  
It's working. Question, how to limit this loop? at the moment I have a random #$20 there but how to limit this loop to avoid iterate to next pointer, this case, spritesLvl2?

Thanks!
User avatar
dougeff
Posts: 3078
Joined: Fri May 08, 2015 7:17 pm

Re: Loading levels

Post by dougeff »

Think about sprites as an array of objects that have x and y positions, and a set of tiles (animation frames).

Write some code that goes one by one through the array and fetches their tiles, and builds the shadow OAM dynamically.

The start of the level, then, would just be to initialize those object array values.
nesdoug.com -- blog/tutorial on programming for the NES
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re: Loading levels

Post by unregistered »

danton19 wrote: Mon May 17, 2021 1:32 pm I've started with the sprites, this is the code:
Setting up the sprites for each lvl, is it correct to use .db?

Code: Select all

spritesLvl1:
     ;vert tile attr horiz
  .db $80, $40, $00, $80   ;sprite 0
  .db $83, $41, $00, $6C   ;sprite 1
  .db $73, $41, $00, $8C   ;sprite 1
  .db $8B, $41, $00, $9C   ;sprite 1

spritesLvl2:
     ;vert tile attr horiz
  .db $80, $40, $00, $80   ;sprite 0
  .db $83, $41, $00, $6C   ;sprite 1
  .db $8B, $41, $00, $9C   ;sprite 1

spritesPointers:
  .dw spritesLvl1
  .dw spritesLvl2
Then to apply:

Code: Select all

LoadLevel:
  	lda levelNumber 
	asl A 
	tax 
	lda spritesPointers+0, x 
	sta levelSprites+0
	lda spritesPointers+1, x 
	sta levelSprites+1


LoadSprites:
  LDY #$00              
LoadSpritesLoop:
  LDA [levelSprites], y        
  STA $0200, y          
  INY                   
  CPY #$20              
  BNE LoadSpritesLoop  
It's working. Question, how to limit this loop? at the moment I have a random #$20 there but how to limit this loop to avoid iterate to next pointer, this case, spritesLvl2?

Thanks!
Ummm, if each level has #$10 sprite bytes (yes, .db works how you are using it), then put “cmp #$10” to stop the loop after writing 16 bytes.

However, if, like your example, you don’t have 16 sprite bytes per level:

Code: Select all

SpriteTotalBox:
.db $10, 12 ;holds sprite bytes total for each lvl

Code: Select all

LoadSprites:
ldy levelNumber
lda [SpriteTotalBox], y
sta spriteTotal

ldy #$00
LoadSpritesLoop:
lda [levelSprites], y
sta $0200, y
iny
cpy spriteTotal

bne LoadSpritesLoop
Therefore, your compared value is no longer an immediate value AND you can easily adjust the sprite bytes per level, they’ll all be under SpriteTotalBox.


Note: spriteTotal is a zeropage variable. :)

You could also make it more digestible:

Code: Select all

SpriteTotalBox:
.db 4, 3 ;holds sprite number for each level

Code: Select all

ldy levelNumber
lda [SpriteBoxTotal], y
asl ;*2
asl ;*2
;now the accumulator holds 4*sprites
sta spriteTotal
now your compare provided above will work as before :)


EDIT: Oooh, I realize that level 0 has sprite 0 and sprite 1.
In my examples, it’s important to realize that sprite 1 consists of 3 sprites… so, you have actually defined 4 sprites for lvl 0. :)
danton19
Posts: 4
Joined: Sun May 09, 2021 1:06 pm

Re: Loading levels

Post by danton19 »

unregistered wrote: Mon May 17, 2021 6:49 pm However, if, like your example, you don’t have 16 sprite bytes per level:

Code: Select all

SpriteTotalBox:
.db $10, 12 ;holds sprite bytes total for each lvl

Code: Select all

LoadSprites:
ldy levelNumber
lda [SpriteTotalBox], y
sta spriteTotal

ldy #$00
LoadSpritesLoop:
lda [levelSprites], y
sta $0200, y
iny
cpy spriteTotal

bne LoadSpritesLoop
Therefore, your compared value is no longer an immediate value AND you can easily adjust the sprite bytes per level, they’ll all be under SpriteTotalBox.


Note: spriteTotal is a zeropage variable. :)
Thanks, I applied this idea and worked very well, thanks a lot! :D
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re: Loading levels

Post by unregistered »

danton19 wrote: Thu May 20, 2021 12:29 pm Thanks, I applied this idea and worked very well, thanks a lot! :D
You’re welcome sir! 😀

Just please take time to digest and understand how the code works… that way you are learning; improving your coding skills. :)
Post Reply