Page 1 of 1

Collision buffer with camera

Posted: Sat Feb 11, 2017 8:22 am
by zkip
Hi, again! lol I'm sorry to be making so many threads, but here I am again with another problem. I have a rule of I'll try to figure something out and if I can't after a couple of days I'll post a help thread. So here I am with about 4 days (about an hour per day) of trying to update a collision buffer correctly during screen scrolling. My idea is, once the screen scroll hits a multiple of 16 (which are the dimensions of the tiles) I need to update the collison buffer. Easy? Well, it doesn't seem to work the way I want it to. The collision seems to aways be a tile or two off. I've tried multiple instances, but this seems to be the correct way if I'm right about the idea. Something just isn't right though. By the way, it's just a simple two-screen setup.

Here's my code:

The camera move code:

Code: Select all

CameraMovement:
	LDA buttons
	AND #%00000001
	BEQ @ck_right
	LDA Player_X			;only scroll the screen if the player is at a fixed
	CMP #$90			; camera anchor
	BCC @ck_right
	LDA CameraX			;make sure that the add wont go over boundries
	CLC
	ADC #$01
	CMP #$FF
	BCS @r1
@move_camera
	LDA CameraX			;scroll the camera
	CLC
	ADC #$01			;note: change speed later
	STA CameraX
	JSR UpdateCollisionBuffer	;update the collision buffer
	LDA Player_X			;make the player not move faster than the scroll rate
	SEC
	SBC #$02
	STA Player_X
@r1
	RTS

@ck_right
	LDA buttons
	AND #%00000010
	BEQ @r
	LDA Player_X			;only scroll the screen if the player is at fixed achor
	CMP #$60
	BCS @r
	LDA CameraX			;make sure that the subtract wont go over boundries
	SEC
	SBC #$01
	BCC @r
	LDA CameraX			;scroll the camera
	SEC
	SBC #$01
	STA CameraX
	JSR UpdateCollisionBuffer	;update the collision buffer
	LDA Player_X			;make the player not move faster than the scroll rate
	CLC
	ADC #$02
	STA Player_X
@r
	RTS
The parts that update the collision buffer:

Code: Select all

UpdateCollisionBuffer:
	LDA CameraX
             ;xxxxxxxx
	AND #$0F			;every time a new 16x16 tile is scrolled on screen
	BEQ @getNewData			; we update the collision buffer
	RTS
@getNewData
	JSR RefreshCollisionTable
	RTS

RefreshCollisionTable:
	LDA CameraX			;get absolute
	AND #$10			;only take the multiple of 16
	LSR A				; shift into low nybble
	LSR A				; as index to coll. data
	LSR A
	LSR A
	STA Scratch			;index into scratch
	STA Temp			; monitor ram
	LDA LevelDataPtr
	PHA
	LDA LevelDataPtr+1		;preserve the pointer to the level data
	PHA				; because were fixin to mess it up

	LDA LevelDataPtr
	CLC
	ADC Scratch			;add the collison index we resolved earlier
	STA LevelDataPtr
	LDA LevelDataPtr+1
	ADC #$00
	STA LevelDataPtr+1

	TXA
	PHA

	LDY #$00			;copy the collision buffer from top to bottom
@coll_loop
	LDA (LevelDataPtr), y		; using the index as a start point
	TAX
	LDA TileCollisionBytes,x
	STA CollisionBuffer,y
	INY
	CPY #$F0
	BNE @coll_loop

	PLA
	TAX
	PLA
	STA LevelDataPtr+1
	PLA
	STA LevelDataPtr
	RTS

Re: Collision buffer with camera

Posted: Tue Feb 14, 2017 10:45 am
by zkip
Alright so after some time I can see why this doesn't work. Can someone rather than having to go through my code, give me an example of how this works? My idea is once the scroll reaches a multiple of tile size update this buffer. I'm having trouble wrapping my head around how to locate the right data and where to put it.

Re: Collision buffer with camera

Posted: Tue Feb 14, 2017 1:26 pm
by dougeff
Are you updating the entire buffer, or just a little bit?

Re: Collision buffer with camera

Posted: Tue Feb 14, 2017 1:34 pm
by tokumaru
It'd also help if you explained how you set up things, since there's no standard for this kind of thing. A lot of people don't even use collision maps, and instead get their collision information straight from the level map.

Anyway, what are the dimensions of your vision buffer? What information does it contain? How is it stored in RAM? How is it stored in ROM? How does your camera system work? Do you use 8 or 16 bit coordinates?

Re: Collision buffer with camera

Posted: Tue Feb 14, 2017 5:51 pm
by zkip
I'd like to update the entire buffer. I don't have a map for collision. I have a table setup for each tile. 0 for empty, 1 for solid. That's where the TileCollisionBytes label in the earlier posted code are. I have the idea that I only need xF0 bytes as the length of the buffer, one for each tile. It's stored in RAM one tile right after the other. I explained earlier how it's read from ROM. The whole camera system code is posted above in the first post. Like I said, I'm just looking for a way to understand this and maybe just need some direction other than trying to debug and fit tie the code I've already written? There is no nametable swapping. At the beginning of the level both nametables are copied and left alone. The camera starts out at x=0, y=0 at the beginning and x is increased once the player reaches a certain point. I think that's all..

Thanks, zkip.

Re: Collision buffer with camera

Posted: Tue Feb 14, 2017 7:06 pm
by tokumaru
So your collision buffer is 240 bytes, 1 byte per 16x16-pixel metatile, right? The rest of my post is based on this assumption.

I noticed a few things wrong with your code. The first thing I noticed is that you load new data when the camera is in a position that's a multiple of 16. One problem with this is that you can't scroll at speeds larger than 1 pixel per frame, or you'll miss updates. The second problem is that if the camera stops at a position that's a multiple of 16, the same data will be loaded every frame, wasting CPU time. To avoid these problems, the correct way to detect a metatile boundary crossing is to compare the previous and the current values of CameraX, looking for changes in bit 4:

Code: Select all

	lda CameraX
	eor OldCameraX
	and #%00010000
	bne @getNewData
All you need for this to work is to copy CameraX to OldCameraX before modifying it. This way you can scroll up to 16 pixels per frame without problems, and the CPU will only spend time loading new data once.

Here's another possible problem:

Code: Select all

	LDA CameraX         ;get absolute
	AND #$10         ;only take the multiple of 16
Shouldn't you be ANDing with $f0, to keep the 4 highest bits of the camera? ANDing with $10 only preserves bit 4, so the index you create from this will only ever be 0 or 1, which I believe is not what you want. To tell the truth, you don't even need an AND here at all, since the lower bits will be discarded anyway due to the 4 LSRs. Also note that since CameraX is only 8 bits, you can't fully scroll into the second screen. Since the highest possible value for CameraX is $FF (1 pixel short of entering the second screen), the farthest you can start from is the last column of the first screen, meaning you'll never reach the last column of the second screen. Fixing this would require extending CameraX to 16-bits, so the index is created like this:

Code: Select all

	lda CameraX+1
	lsr
	lda CameraX+0
	ror
	lsr
	lsr
	lsr
This uses only the lowest bit of CameraX+1, supporting levels up to 512 pixels wide. For longer levels you'll need to take more bits from CameraX+1.

Now, the biggest problem is probably the actual data transfer. You can't just transfer 240 contiguous bytes, since the level is longer than 16 metatiles. The buffer is 16 metatiles wide, so when the index goes from 15 to 16, that's an automatic wrap to the next row, but since the map in the ROM is longer, this index will just keep reading from the same row, wrapping at the wrong position, and causing your buffer to be filled with misaligned garbage. What you need to do is adjust the pointer (LevelDataPtr) after each run of 16 bytes copied, so the data is correctly copied from the row below.

Since your maps are apparently 32 metatiles long, you can get away with adding 16 to LevelDataPtr after every 16 bytes copied, to force a wrap to the next row, while still using the same unmodified index for the destination. This is a quick hack for when the length of the level is constant, but if you ever switch to levels of arbitrary lengths you will need a more versatile solution. This is one way to implement the hack:

Code: Select all

	iny
	tya
	and #$0F
	bne @test_end
	clc
	lda LevelDataPtr+0
	adc #$10
	sta LevelDataPtr+0
	lda LevelDataPtr+1
	adc #$00
	sta LevelDataPtr+1
@test_end
	cpy #$F0
	bne @coll_loop
EDIT: I edited the post a few times. Please be sure you didn't miss anything.

Re: Collision buffer with camera

Posted: Wed Feb 15, 2017 9:53 am
by zkip
Ah! I see, so it's essentially done just like how I was loading the nametables. Thanks for explaining that. However, I still have one problem. It seems to only work when the screen is perfectly aligned to a 16x16 tile. Does the player X or the camera anchor (how far the player goes before scrolling starts) need to be sorted into this index? It doesn't seem like it should. Also, is it proper how I decrease the players x in the code above to stop from going farther than the speed of the scroll?

Thanks.

Re: Collision buffer with camera

Posted: Wed Feb 15, 2017 10:37 am
by tokumaru
Just in case there are still any doubts about the alignment issue, I made a couple of pictures to illustrate the problem. Here's what happen when you copy contiguous bytes from a 32-metatile wide map to a 16-metatile wide buffer:
alignment-00.png
alignment-00.png (11.21 KiB) Viewed 3360 times
The layouts don't match, so you end up with useless data.

To properly advance to the next row of the level map at the same time the index advances to the next row of the buffer, you have to skip the MapWidth - BufferWidth (in this case, 32 - 16 = 16) blocks:
alignment-01.png
alignment-01.png (11.05 KiB) Viewed 3360 times
Notice how the data we're reading from the map is now properly aligned with the buffer. Like I said in the previous post, a quick way to do this is add the amount of metatiles to skip to the pointer you're using to read the map data.
zkip wrote:Ah! I see, so it's essentially done just like how I was loading the nametables.
I don't know how you're loading name tables, but I imagine that if you're drawing a 16-metatile wide screen from a 32-metatile wide map you also need to skip 16 extra metatiles to advance to the correct point of the next row.
It seems to only work when the screen is perfectly aligned to a 16x16 tile.
What exactly doesn't work? Is the collision buggy? Have you checked if that's due to the buffer having the wrong contents or due to the collision code not reading from the collision buffer properly? You can use an emulator that lets you inspect memory in real time (FCEUX, Mesen) to see if the collision buffer is being updated properly. If you can get the window to display 16 bytes per line you should easily eyeball the 16x15 map to see if it looks correct as you scroll.
Does the player X or the camera anchor (how far the player goes before scrolling starts) need to be sorted into this index? It doesn't seem like it should. Also, is it proper how I decrease the players x in the code above to stop from going farther than the speed of the scroll?
The player position shouldn't affect how the collision data is loaded, since the loading is based on the position of the camera. The position of the player will affect how you test collisions against the buffer though. Since you're loading an entire screen worth of collision data, I assume you're keeping player coordinates as 8-bit coordinates relative to the screen (something I personally would never recommend, but let's keep going), but it seems you're not accounting for the fine scroll when using the player coordinate to read from the collision map.

Think about this: if the player is at screen position 9, but the screen has scrolled 10 pixels to the right, that means that 10 pixels of the first column of metatiles are off the left of the screen, and only the rightmost 6 pixels remain visible. If you simply take the player's position and divide by 16 to calculate the collision column, you'll get index 0, but position 9 is already past the 6 visible pixels of the first column, so position 9 is actually overlapping the SECOND column. To fix that, you have to take the fine scroll (CameraX AND $0F) into consideration when reading from the map. If the fine scroll is 10 pixels, you have to add that to player's coordinates before dividing by 16, so 9 + 10 = 19, which divided by 16 will give you the correct index of 1, so you can read from the second column.

Do you think this could be your problem?

Re: Collision buffer with camera

Posted: Wed Feb 15, 2017 1:06 pm
by zkip
Yeah, the collision map is right in RAM, that's why I went to the players coordinates as being the culprit.

You're exactly right, I wasn't considering the scroll in the collision checking code. Setting that up (camerax and #$0f) made it better, but it still seems a pixel or two off. At this point, I'm not sure where I went wrong or what to even ask here. Ever had a problem you can't think of a question for?

edit: The problem is fixed. The problem was I forgot to add the fine scroll to the code that ejects the player from a tile. Thanks. :D