Issues with scrolling both directions

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Issues with scrolling both directions
by on (#111886)
I've been working at learning NES coding for several months now, and the incredible amount of information here and at NintendoAge have helped me out a lot. I'm stuck on this one though, so I decided to finally make a post and see if people can point me in the right direction.

I've been trying to put together a short demo for scrolling combined with collision detection, and while I've gotten everything to work well enough going right, I run into issues when I go left. It seems as if the collision map isn't being updated properly when I scroll to the right, as my character suddenly is colliding with invisible walls, falling through the floor, and all manner of other odd behaviors.

I tried to take a break from this for a bit and go back to it, but nothing I've done seems to be fixing it.

Anyway, I've included some relevant snippets of my (probably really sloppy and messy) code. Hopefully someone knowledgeable can look at it and point me in the direction of what I'm doing wrong and how I can fix this.

I have a feeling that the stuff I'm doing in @left here might be the cause of my problems.

Code:
Column:
   lda xscroll+1
   and #%00001111      ;check bottom 4 bits to see if we need to draw a new column
   bne @done2      
   lda drawn         ;check the 'drawn' value to see if we've already drawn this value
   bne @done2
   lda #$01         ;if we haven't drawn, set drawn value to #$01 and continue
   sta drawn
   
   lda xscroll+1      
   and #%11110000      
   lsr
   lsr
   lsr
   lsr
   sta ColRam_Index   ;check upper bits of xscroll, shift, store in ColRam_Index
   
   lda render_direction   
   bne @left            ;check which direction we're rendering in. If non zero, jump to @left
   jsr findcolumnR         
   jsr Loadcolumn         ;find right hand column and buffer it.
   jmp @done2
@left
   lda ColRam_Index   
   sec
   sbc #$01
   sta ColRam_Index      ;subtract $#01 from ColRam_Index
   lda ColRam_Index
   bpl @.               ;if the result is negative, we load $#0F and store that in ColRam_Index
   lda #$0F
   sta ColRam_Index
@.
   jsr findcolumnL         ;find left hand column and buffer it
   jsr Loadcolumn
@done2
   lda xscroll+1         
   and #%00001111         ;check lower bits of xscroll+1, if zero, go to @rts
   beq @rts
   lda #$00            ;reset "drawn" to #$00
   sta drawn
@rts   

  JSR Movement
  JSR Collision
  JSR UpdateSprites
  rts


I hope I'm providing enough information. If not, I'll be happy to clarify anything.
Re: Issues with scrolling both directions
by on (#111887)
You're code is written wrong, if xscroll is a variable/piece of RAM. You're trying to do a LDA,CLC,ADC #$01 with your xscroll+1, when it loads A with the LOCATION of xscroll+1, so if it's at $0123, it loads from $0124. That would probably do it.
Re: Issues with scrolling both directions
by on (#111889)
3gengames wrote:
You're code is written wrong, if xscroll is a variable/piece of RAM. You're trying to do a LDA,CLC,ADC #$01 with your xscroll+1, when it loads A with the LOCATION of xscroll+1, so if it's at $0123, it loads from $0124. That would probably do it.

No, that's intentional. Probably should've clarified before... xscroll is 3 bytes. One for subpixel stuff, the one that's manipulated here, and one that keeps track of which screen it's on (the lowest bit of xscroll+2 is used for the $2000 write to set the nametable arrangement thing.)

Besides, if it were that, I don't think it would work very well when scrolling to the right. I'm only having problems when going left.
Re: Issues with scrolling both directions
by on (#111912)
Quackula wrote:
Code:
@left
   lda ColRam_Index   
   sec
   sbc #$01
   sta ColRam_Index      ;subtract $#01 from ColRam_Index
   lda ColRam_Index
   bpl @.               ;if the result is negative, we load $#0F and store that in ColRam_Index
   lda #$0F
   sta ColRam_Index
@.


Just one thing I notice... wouldn't this also work?
Code:
@left
        dec ColRam_Index
        lda ColRam_Index
        bpl @.
        lda #$0F
        sta ColRam_Index
@.

I know dec is 5 cycles... but it's only 5 cycles... it replaces 2 + 2 + 2 + 2 = at least 6 8 cycles. FCEUX 2.1.5 likes simple code I'm learning too... :)

edit.
Re: Issues with scrolling both directions
by on (#111917)
It actually could be replaced with:

Code:
  DEC ColRam_Index
  BPL @.
  LDA #$0F
  STA ColRam_Index
@.


Simpler in assembly is always better...the first example...avoid that, especially since more line=more errors.
Re: Issues with scrolling both directions
by on (#111918)
Quackula, I am sorry I can't help you how you want to be helped. :( It's a problem I'll be facing too... my code almost works when scrolling right... doesn't scroll at all to the left. I guess all of your function calls... like findColumnR and Loadcolumn... are all of those guarenteed to work? Kasumi has helped me learn that it's important to start with small code and add to that a little bit and check it again. Just slowly guarentee each function... that would be my plan of attack. Hope someone else can help you with this. God bless your codeing. :)
Re: Issues with scrolling both directions
by on (#111919)
3gengames wrote:
Simpler in assembly is always better...the first example...avoid that, especially since more line=more errors.


I think I'm learning that the hard way... man this entire routine is a complete mess and barely works as it is. Trying to just redo it from the ground up now and going to read up some more, hopefully I can make something a bit easier to troubleshoot.
Re: Issues with scrolling both directions
by on (#112169)
I ran into an issue with my code that would happen upon switching directions. If you're using kind of a "rolling" window, where the columns of your level get decoded one column at a time as they are revealed, make sure that you decode the next column immediately as the scrolling direction is switched. What happened to me is that I would only decode after crossing a column boundary. So I'd be moving left, and it would decode a column on the left, but then I'd move right and it would only decode the next right column after crossing a column boundary. Seems fine, but as soon as I moved ahead a screen, the collision map in RAM contained the information for the last column that was decoded as I was moving left. It never got overwritten after switching directions.
Re: Issues with scrolling both directions
by on (#112173)
I think people usually overthink this. If you think in terms of "switching directions" things will end up being more complicated than they should be, because you have to interrupt the task of scrolling in one direction to start the task of scrolling in another (and this interruption is probably what causes the data inconsistencies), while they could very well be the same task, and the direction only really affects where the tiles/attributes are read from and where they are written to.

The logic for scrolling is pretty straight forward: when a metatile boundary is crossed, check the direction of the last movement to decide whether the new column comes from CameraX or CameraX + 256 in the level map, and whether it will be written to ScrollX or ScrollX + 256 in the name/attribute tables. This is all the direction should really affect, a couple of pointers. Once you have these pointers ready, the process should be exactly the same no matter the direction.

Even when scrolling horizontally and vertically at the same time it can still be this simple, you just need to fully process one axis (move camera and scroll, check source and destination addresses, fill buffers) before processing the other, otherwise one axis won't take into consideration all the changes made to the other axis and the output will often be shifted by 1 metatile, resulting in corrupted backgrounds. The only thing that really complicates multi-directional scrolling are the attribute tables, you often have to come up with a trick to handle the crossing of vertical boundaries to get the colors right.
Re: Issues with scrolling both directions
by on (#112174)
tokumaru wrote:
The logic for scrolling is pretty straight forward: when a metatile boundary is crossed, check the direction of the last movement to decide whether the new column comes from CameraX or CameraX + 256 in the level map, and whether it will be written to ScrollX or ScrollX + 256 in the name/attribute tables.

Yep. Just think of normal right-only scrolling. Scroll right one pixel. Any tile redrawing? If no, then you can still scroll left one pixel without needing to do anything. So there's an 8/16-pixel area where you can scroll freely back and forth without any tile drawing. Only going outside this do you need to redraw one edge.
Re: Issues with scrolling both directions
by on (#112373)
tokumaru wrote:
I think people usually overthink this. If you think in terms of "switching directions" things will end up being more complicated than they should be..


I'm pretty sure this is the case with everything; a golden rule is that the simplest explanation is often the most correct/accurate/reliable.

I had kind of a bad feeling about needing to check for the switch of directions, like something I was doing was wrong. It might just be something wrong with the order in which I do things in my code. But I have a tendency to make fencepost errors; I'm often off by 1. With my game, which only scrolls two directions, I have a collision map in RAM that's $200 bytes. It's sort of a rolling window where if you move left, it writes the collision data to RAM for -$80 pixels off the screen, and if you move right, it writes data for +$80 pixels off the screen. The way it was working was that if you moved left, it might copy data to $340-$34F, but it might share that same buffer the second you turn right. If that buffer doesn't get updated the second you turn to have "right" data, it would keep the "left" data and you'd run into a garbage column of collision data once you move right a fair bit.

Again, I'm sure there's some off by 1 logic error I overlooked. It works now, but I had a feeling there should be a way to simplify it.