Scrolling RPG maps

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

User avatar
zanto
Posts: 57
Joined: Sun Mar 07, 2021 11:15 pm
Location: Rio de Janeiro, Brazil

Re: Scrolling RPG maps

Post by zanto »

tokumaru wrote: Mon Apr 05, 2021 6:48 pm BTW, we can't watch the video without having to request access.
Sorry. I uploaded the video to youtube. It's also easier to see the glitching effect that happens while the hero is walking https://www.youtube.com/watch?v=sMkj9Mq5NO4

tokumaru wrote: Mon Apr 05, 2021 6:48 pm If all you want is to set the increment mode for doing updates, you don't really need to preserve the other bits during the update process itself, but you do need to restore all the bits *after* the update is done, before the rendering of the new frame starts.
Ah, I see. That makes sense!
tokumaru wrote: Mon Apr 05, 2021 6:48 pmThese problems are most likely related. You're probably taking more time than the length of vblank to do your updates, so the frame starts with a bad scroll and the final vram writes fail.

Do not decompress data or do any sort of slow processing during vblank. The vertical blank interval is really short, so you should use it for copying already processed and buffered data to vram, otherwise you won't be able to update much data at all before the time is up.
I don't think I understood what you meant. That routine is executed before the NMI. This is all I have in my NMI interrupt routine:

Code: Select all

NMI:
	pha         ; back up registers (important)
	txa
	pha
	tya
	pha
	; we'll copy the sprites in range $0200 into the PPU so the sprites will be displayed
	LDA #$00
	STA $2003 
	LDA #$02		; copy sprite data from $200 into PPU display
	STA OAMDMA
	LDA #$00
	STA PPUADDR        ; clean up PPU address registers
	STA PPUADDR
	
	bit PPUSTATUS
	lda camerax
	sta PPUSCROLL
	lda ntscrolly
	sta PPUSCROLL
	
	lda camerax+1
	lsr ;shift 9th bit of camerax into the carry flag
	lda ntscrolly+1
	rol ;put it alongside the 9th bit of ntscrolly
	and #%00000011 ;clear irrelevant bits
	ora ppuctrl_mirror ;combine with the other PPUCTRL settings
	sta PPUCTRL

	LDA #%00011110   ; enable sprites, enable background, no clipping on left side
	STA $2001
		
	LDA ppudrawcomplete
	EOR #$FF			; with this we toggle A between $00 and $FF
	STA ppudrawcomplete

	pla            ; restore regs and exit
	tay
	pla
	tax
	pla
	RTI	; this is the opcode used to return from an interrupt routine to the previous code (analogue to RTS)
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Scrolling RPG maps

Post by tokumaru »

zanto wrote: Mon Apr 05, 2021 9:05 pmThat routine is executed before the NMI.
Are you writing to vram (PPUDATA) outside of the NMI handler? If so, than that's definitely a problem. You should be running the decompression code and buffering the data, and then writing it to the PPU in the NMI handler. On the NES, it's only safe to write to vram during vblank or when rendering is off.
User avatar
zanto
Posts: 57
Joined: Sun Mar 07, 2021 11:15 pm
Location: Rio de Janeiro, Brazil

Re: Scrolling RPG maps

Post by zanto »

Okay, I made some progress. The scrolling right is working properly... almost. As you can see in the video below, when I move right, the tiles are loaded properly as I move right. But the problem is that when I move, the screen shakes twice, as you can see in this video. In the video it's not showing the screen shaking every time I move, but it is in the emulator.
https://www.youtube.com/watch?v=7CEkCADx3lo

I moved all the drawing code into the NMI, as shown below. It seems that if I comment the "STA PPUADDR" lines under the UpdateNTRight proc, the screen stops shaking (but it also doesn't draw anything to the right of the screen).
When I press a key, the player moves 16 pixels to the right. The routine that draws to the right of the screen is executed twice during this movement, each time to draw a 8 pixel column to the right of the screen. The screen shakes on the frame after the routine is executed, it seems. It's weird because when I look at the PPU viewer in Mesen, it looks like the scroll moves vertically one pixel twice every time I move, even though I checked and I'm sending the correct values to PPUSCROLL...

Code: Select all

NMI:
	pha         ; back up registers (important)
	txa
	pha
	tya
	pha
	; we'll copy the sprites in range $0200 into the PPU so the sprites will be displayed
	LDA #$00
	STA $2003 
	LDA #$02		; copy sprite data from $200 into PPU display
	STA OAMDMA
	LDA #$00
	STA PPUADDR        ; clean up PPU address registers
	STA PPUADDR
	
	; insane background scroll
	lda camerax
	sta PPUSCROLL
	lda ntscrolly
	sta PPUSCROLL
	
	lda camerax+1
	lsr ;shift 9th bit of camerax into the carry flag
	lda ntscrolly+1
	rol ;put it alongside the 9th bit of ntscrolly
	and #%00000011 ;clear irrelevant bits
	ora ppuctrl_mirror ;combine with the other PPUCTRL settings
	sta PPUCTRL
	

	lda ppuupdate
	and #%00000001
	cmp #%00000001	; do we have to update to the right of the current visible area?
					; 1st bit = right, 2nd bit = left etc....
	beq :+
	jmp UpdateNTLeft
:

.proc UpdateNTRight
	
	; set address in the PPU well write to
	LDA nametable	
	EOR #$01          ; invert low bit, A = $00 or $01
	ASL A             ; shift up, A = $00 or $02
	ASL A             ; $00 or $04
	CLC
	ADC #$20          ; add high byte of nametable base address ($2000)
	STA PPUADDR             ; write the high byte of column address

	LDA camerax
	LSR A
	LSR A
	LSR A            
	STA aux1
	STA PPUADDR

	LDA #%00000100        ; set to increment +32 mode, don't care about other bits
	ora ppuctrl_mirror
	STA PPUCTRL
	
	ldx #0
	
@UpdateNTLoop:
	ldy nametabletemp, X
	lda genericflags1
	and #%00000001
	
@GetTilesLeft:
	beq @GetTilesRight
	lda metatile_bytes_common, Y
	sta PPUDATA
	lda metatile_bytes_common+2, Y
	jmp @AfterGetTiles

@GetTilesRight:
	lda metatile_bytes_common+1, Y
	sta PPUDATA
	lda metatile_bytes_common+3, Y
	
@AfterGetTiles:
	sta PPUDATA
	inx
	cpx #15
	bne @UpdateNTLoop
	
@Done:

.endproc

.proc UpdateNTLeft
	
	
.endproc

	LDA #%00011100   ; enable sprites, enable background, no clipping on left side
	STA $2001
		
	lda #0
	sta ppuupdate
	ora ppuctrl_mirror
	sta PPUCTRL			; set back to increment +1 mode
	
	
	LDA ppudrawcomplete
	EOR #$FF			; with this we toggle A between $00 and $FF
	STA ppudrawcomplete

	pla            ; restore regs and exit
	tay
	pla
	tax
	pla
	RTI	; this is the opcode used to return from an interrupt routine to the previous code (analogue to RTS)
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Scrolling RPG maps

Post by tokumaru »

You're supposed to set the scroll *after* all VRAM updates. The usage of PPUADDR and PPUDATA messes up the scroll, so every time you use them you corrupt the scroll and the screen "jumps" for a frame. Move your PPUSCROLL and PPUCTRL writes so they happen after the VRAM updates, so that the scroll is always setup correctly before rendering starts.
User avatar
Quietust
Posts: 1920
Joined: Sun Sep 19, 2004 10:59 pm
Contact:

Re: Scrolling RPG maps

Post by Quietust »

tokumaru wrote: Tue Apr 06, 2021 7:00 pm You're supposed to set the scroll *after* all VRAM updates. The usage of PPUADDR and PPUDATA messes up the scroll, so every time you use them you corrupt the scroll and the screen "jumps" for a frame. Move your PPUSCROLL and PPUCTRL writes so they happen after the VRAM updates, so that the scroll is always setup correctly before rendering starts.
To be more specific, the VRAM address (updated via PPUADDR) and the Scrolling parameters (updated via PPUCTRL+PPUSCROLL) are the same thing inside the PPU, so updating one will cause the other one to change in unexpected ways.

The full details can be found on the wiki, and they can be used to perform special tricks (such as updating vertical scrolling mid-frame) that are otherwise not possible if you write to PPUCTRL+PPUSCROLL "normally".
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.
User avatar
zanto
Posts: 57
Joined: Sun Mar 07, 2021 11:15 pm
Location: Rio de Janeiro, Brazil

Re: Scrolling RPG maps

Post by zanto »

Oh, that's interesting to know! Thank you for the help, everyone.
I read the wiki pages whenever I can, but there are so many details that you have to be careful and it's easy to forget. Sometimes I get errors that make me go like "oh, I think I read about this on the wiki or the forums somewhere", and I can find the solution to those with relative ease.
Thank you for putting with me and my many problems :?
My next step is to make the screen scroll left, which should be relatively simple now that right scrolling seems to be working. Then vertical scroll, which will be more challenging... I can't wait I'm done with all the map and scrolling part so I can actually work on some game design :)

Oh, I forgot about attributes too :x
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Scrolling RPG maps

Post by tokumaru »

zanto wrote: Tue Apr 06, 2021 11:17 pmThen vertical scroll, which will be more challenging...
The complexity of adding the vertical scroll is that it will affect the horizontal scroll too, since the columns you update when scrolling sideways will not be neatly aligned to the name tables anymore, they'll have to be split according to the vertical alignment. It's not too complex, but not trivial either.
I can't wait I'm done with all the map and scrolling part so I can actually work on some game design :)
Don't work, they're are plenty of complicated systems for you to worry about besides scrolling before you can get an actual game working, such as object management, bankswitching and data organization, collision detection...
Oh, I forgot about attributes too :x
If you want full control over the attributes, the most complete solution is to mirror the attribute tables in RAM and manipulate that copy using bit masks to address individual 16x16-pixel areas, and upload to VRAM the bytes that overlap the rows and columns being updated by the scrolling engine.
Pokun
Posts: 2681
Joined: Tue May 28, 2013 5:49 am
Location: Hokkaido, Japan

Re: Scrolling RPG maps

Post by Pokun »

Ouch, that means more RAM is needed for attributes.

Collision detection should be easy enough in an RPG though, since you don't have pixel-based or acceleration-based movement and basically only passable tiles, non-passable tiles and a few special ones like stairs, shop counters and damage floors.

BTW I just found this pretty cool DW map building visualizer.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Scrolling RPG maps

Post by tokumaru »

Pokun wrote: Wed Apr 07, 2021 10:46 amOuch, that means more RAM is needed for attributes.
Mirroring the entire tables is only necessary if you want full control over the attributes. If you don't need to change the attributes mid-screen you can get away with using buffers only for the edges of the scroll area.

You can also pre-bake the attributes into the map and avoid messing with attributes altogether, but that usually complicates interactions between objects and the map, due to having to skip lines 240-255 of every screen when doing collisions.
User avatar
zanto
Posts: 57
Joined: Sun Mar 07, 2021 11:15 pm
Location: Rio de Janeiro, Brazil

Re: Scrolling RPG maps

Post by zanto »

Pokun wrote: Wed Apr 07, 2021 10:46 am BTW I just found this pretty cool DW map building visualizer.
I saw that! I was very impressed they managed to store maps using instructions like that! I wonder if they load the entire map into RAM for scrolling or if they know which specific instructions they need to run to load part of the map when scrolling happens...
User avatar
zanto
Posts: 57
Joined: Sun Mar 07, 2021 11:15 pm
Location: Rio de Janeiro, Brazil

Re: Scrolling RPG maps

Post by zanto »

So I made left and right scrolling work. Yay! Now I need to get vertical scrolling to work. My game uses vertical mirroring, btw. So the situation below is what I'm trying to deal with.
The player is positioned in a way that makes the scroll takes up space in all 4 nametables.
img1.png
img1.png (5.38 KiB) Viewed 4337 times
I'm trying to think of how to do two things:
1) Calculate what's the starting PPU address I need to draw to. Since for now I only have right/left scrolling, this is what I'm doing:

Code: Select all

LDA nametable
	EOR #$01 
	ASL A 
	ASL A 
	CLC
	ADC #$20   
	STA PPUADDR             ; write the high byte of column address

	lda camerax
	LSR A
	LSR A
	LSR A            
	STA PPUADDR
My guess is that if I scroll the camera down, I'd have to add $20 to the lower byte of PPUADDR. However, that may end up making the PPU writing garbage into the attribute tables, right? Do I have to make it so that the PPU it loops back to the first row of the nametable?


2) How to make it so that all nametables are correctly updated to draw everything on screen correctly. Again, since scroll is taking space in all nametables, how do I make it so that the NES knows which memory areas it needs to draw tiles into?
Pokun
Posts: 2681
Joined: Tue May 28, 2013 5:49 am
Location: Hokkaido, Japan

Re: Scrolling RPG maps

Post by Pokun »

I'm not entirely sure what you are doing. I think you can ignore nametable 2 and 3 as they are just mirrors of nametable 0 and 1 respectively. Anything you draw to the mirrors will appear on 0 and 1 as well, so it doesn't matter which of them the vertical scroll coordinate is at.

I guess vertical scrolling is the same as horizontal scrolling with the one difference that you don't have an extra adjacent screen to draw on. You have to draw on the same screen that you are currently displaying (either top row or bottom row), and depending on the TV the seam where you flip the tiles might be more or less visible. There are various techniques to hide it but I don't really know as I haven't done this type of scrolling myself.
One thing I think games do is to change mirroring so that vertical scrolling becomes seamless and horizontal scrolling will have a seam instead, then hide that seam by setting the left-edge background blanking flag in PPUMASK.


zanto wrote: Thu Apr 08, 2021 7:03 pm
Pokun wrote: Wed Apr 07, 2021 10:46 am BTW I just found this pretty cool DW map building visualizer.
I saw that! I was very impressed they managed to store maps using instructions like that! I wonder if they load the entire map into RAM for scrolling or if they know which specific instructions they need to run to load part of the map when scrolling happens...
This puzzles me as I don't think there is enough RAM to hold the entire uncompressed map in DQ2 since you only have the 2 kB internal RAM in the Famicom to work with. Especially not the world map. DQ3 and DQ4 uses cartridge SRAM and are probably able to do that though.
User avatar
zanto
Posts: 57
Joined: Sun Mar 07, 2021 11:15 pm
Location: Rio de Janeiro, Brazil

Re: Scrolling RPG maps

Post by zanto »

Pokun wrote: Sat Apr 10, 2021 5:09 am I guess vertical scrolling is the same as horizontal scrolling with the one difference that you don't have an extra adjacent screen to draw on. You have to draw on the same screen that you are currently displaying (either top row or bottom row), and depending on the TV the seam where you flip the tiles might be more or less visible. There are various techniques to hide it but I don't really know as I haven't done this type of scrolling myself.
One thing I think games do is to change mirroring so that vertical scrolling becomes seamless and horizontal scrolling will have a seam instead, then hide that seam by setting the left-edge background blanking flag in PPUMASK.
I'm not really concerned with the graphical glitches that will appear as you scroll up or down, at least not for now. But I don't know how I can write in nametables 1 and 2 at the same time if the scroll is between those 2 nametables like the image below. So for example, if the hero moved 4 tiles to the right, the first 28 tiles of the map that is shown on screen would have to be drawn to the right of nametable 1 and the last 4 tiles would have to be drawn on the right of nametable 2. (in my case, I'm using metatiles, which makes things a little bit harder, since a metatile can be split between two nametables)
img1.png
img1.png (6.58 KiB) Viewed 4278 times


Pokun wrote: Wed Apr 07, 2021 10:46 am This puzzles me as I don't think there is enough RAM to hold the entire uncompressed map in DQ2 since you only have the 2 kB internal RAM in the Famicom to work with. Especially not the world map. DQ3 and DQ4 uses cartridge SRAM and are probably able to do that though.
I think the world maps uses a variation of RLE compression instead of using those instructions. Dwedit made a post on this thread describing it.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Scrolling RPG maps

Post by tokumaru »

zanto wrote: Sat Apr 10, 2021 10:48 amBut I don't know how I can write in nametables 1 and 2 at the same time if the scroll is between those 2 nametables like the image below.
You wouldn't write at the same time, it would be two separate updates one after the other. You need to calculate how far from the edge the column is (use the value of NTCameraY to calculate this) and buffer an update of that many blocks, then another update for the remaining blocks.
So for example, if the hero moved 4 tiles to the right, the first 28 tiles of the map that is shown on screen would have to be drawn to the right of nametable 1 and the last 4 tiles would have to be drawn on the right of nametable 2.
I'm a little confused by your name table numbering... If you scroll to the right in that situation you'd have to write part of the new column on name table 1 (top right) and the other part on name table 3 (bottom right), isn't that right?

However, name tables 1 and 3 are actually the same name table, because of the vertical mirroring. Since this is the case, if you prefer, you can still do this as a single update to a single name table, but start filling the buffer from the middle (depending on the value of NTCameraY) and wrap around to the top of the buffer. Then, in vblank, just copy the buffer contents to the name table like you're already doing, no changes required there.

This trick won't work for the vertical scroll though, since the left and right name tables are in fact different. If the scroll is split between two name tables horizontally when you scroll vertically, the new row of metatiles will have to be split in two.

Alternatively, you could actually write a full row of metatiles to each name table, which is easier to calculate, but you'll be wasting time processing and updating tiles that aren't even visible (you'd be updating 32 metatiles when only 17 would be enough), but that's up to you.
User avatar
zanto
Posts: 57
Joined: Sun Mar 07, 2021 11:15 pm
Location: Rio de Janeiro, Brazil

Re: Scrolling RPG maps

Post by zanto »

tokumaru wrote: Sat Apr 10, 2021 3:15 pm I'm a little confused by your name table numbering... If you scroll to the right in that situation you'd have to write part of the new column on name table 1 (top right) and the other part on name table 3 (bottom right), isn't that right?
Oh geez... I'm sorry. I made a mistake when explaining my problem. It's not when I move right. It's when I move down (or up). Since the game has vertical mirroring, just updating nametables 1 and 2 work fine for those directions.

tokumaru wrote: Sat Apr 10, 2021 3:15 pm This trick won't work for the vertical scroll though, since the left and right name tables are in fact different. If the scroll is split between two name tables horizontally when you scroll vertically, the new row of metatiles will have to be split in two.
Yeah, that's my issue...

tokumaru wrote: Sat Apr 10, 2021 3:15 pm Alternatively, you could actually write a full row of metatiles to each name table, which is easier to calculate, but you'll be wasting time processing and updating tiles that aren't even visible (you'd be updating 32 metatiles when only 17 would be enough), but that's up to you.
So should I just calculate the VRAM address I need to start in both nametables 1 and 2 and write only the tiles that I need?

So, if the top left of the scroll is on NT1, I'd calculate the VRAM address the following way:
NT1: $2000 + low byte of scrollX + 32 * scrollY
NT2: $2400 + 32 * scrollY

And if top left of the scroll is on NT2, it'd be similar to that
NT2: $2400 + low byte of scrollX + 32 * scrollY
NT1: $2000 + 32 * scrollY

Am I on the right track? Is there any way to make this in an optimized way (considering this code will be in NMI)?
Post Reply