Loading levels
Moderator: Moderators
Loading levels
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!
-
- Posts: 1565
- Joined: Tue Feb 07, 2017 2:03 am
Re: Loading levels
There are no files. you don't load anything. So you load routine becomes
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)
Code: Select all
LDA LevelPtrLo,x
STA $02
LDA LevelPtrHi,x
STA $03
LDY #SizeOfLevelData
- LDA ($02),y
STA PlaceInRAMThatHoldsLevelData,y
DEY
BNE -
LevelPtrLo .byte <(Level1-1),<(Level2-1)
LevelPtrHi .byte >(Level1-1),>(Level2-1)
Re: Loading levels
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:
Do this:
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.
Code: Select all
lda Level1Collision, y
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
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.
Re: Loading levels
understood! awesome, thanks a lot! Great explanations
-
- Posts: 160
- Joined: Sat Apr 24, 2021 7:25 am
Re: Loading levels
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 wrongtokumaru 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:
Do this:Code: Select all
lda Level1Collision, y
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
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.Code: Select all
lda (LevelCollision), y
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.
Re: Loading levels
It doesn't matter which byte you copy first, as long as you copy both of them (and put them in the proper locations).puppydrum64 wrote: ↑Mon May 10, 2021 12:19 pmWouldn'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 wrongtokumaru wrote: ↑Sun May 09, 2021 11:02 pmCode: 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
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
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.
P.S. If you don't get this note, let me know and I'll write you another.
Re: Loading levels
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.
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.
Re: Loading levels
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?
Then to apply:
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!
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
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
Thanks!
Re: Loading levels
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.
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
-
- Posts: 1318
- Joined: Thu Apr 23, 2009 11:21 pm
- Location: cypress, texas
Re: Loading levels
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.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?Then to apply: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
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?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
Thanks!
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
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
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.
Re: Loading levels
Thanks, I applied this idea and worked very well, thanks a lot!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
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.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
Note: spriteTotal is a zeropage variable.
-
- Posts: 1318
- Joined: Thu Apr 23, 2009 11:21 pm
- Location: cypress, texas