Having Another Jab at a Camera System

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
Sogona
Posts: 186
Joined: Thu Jul 23, 2015 7:54 pm
Location: USA
Contact:

Having Another Jab at a Camera System

Post by Sogona »

Hey it's been a while. School started again, had to start applying for colleges and all that, so this had to kinda get pushed to the side. But I've decided I want to finally try and make a working camera system and scrolling engine, now that I'm a bit more comfortable with a bunch of different concepts (which I've so far been unable to integrate as a whole, but progress is progress.)


What I have so far:
-2x2 tile metatiles
-A simple RLE routine that can encode up to 16 bytes (All I'll need is 15 though, since I'll be drawing columns)
-A simple column buffer (2 actually, I explain why below)


I made this from BunnyBoy's scrolling engine on NA, which was designed to scroll automatically and only to the right. I feel comfortable with metatiles and buffers, but what I've been trying to do now is make it so that the screen can scroll left or right, and via the input of the controller.

Right now, whenever the shadow $2005 variable is at 0, the nametables swap, so at startup, if the d-pad isn't pressed, the screen flickers rapidly as the nametables switch every frame. I don't really understand how to fix this (All I've done is avoid it by making $2005 increment for the first 3 frames, to make it scroll slightly to the right.) I've also noticed that when you only tap the d-pad, rather than holding it down, it'll generate graphical errors every once and a while. I think this has to do with once the shadow $2005 variable wraps around to 0, the nametables flip back and forth rapidly, and if it goes to the wrong one, it produces the glitches; but I could be wrong. The buffer I have right now only draws the right tiles when scrolling to the right, and I don't know how I'd go about modifying it to handle both directions.

I'm sure the engine skeleton I have right now could probably be better, I'd love to hear ways it could be improved. The thing I'm the most "unsure" about is that I have 2 buffers - one for when the first half of a new metatile column is drawn, and the other for when the second one is. I also haven't implemented attribute columns yet, as all I'm really worrying about right now is getting the tiles to work.

Some other things I'm still not too sure on, but aren't really concerned with right now, are:
-How to make objects move in relation to the camera. (I think I heard somewhere that you have 1-byte variables for an object's actual position on the screen, and then a larger variable for their global position in the room. Whenever an object moves, its global position moves freely, but its local x position stays at 128 until its at the end of the room.)
-How to keep track of other objects (For instance, enemies), and make sure there are only enough objects on the screen at a time that the NES can handle. For example, if there are enemies that follow the player, what would happen when a spot was reached where new enemies spawn? Do some enemies just get erased?

Anyway, here's my source which I tried to comment thoroughly to remove any potential ambiguity.
sogona_camerasystem.zip
(33.99 KiB) Downloaded 130 times
Thanks for all the help you guys have been, and I'm sure will continue to be. :)
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Having Another Jab at a Camera System

Post by tokumaru »

From what I can see you're switching name tables whenever scroll is 0, regardless of the direction of the movement, and regardless of whether there was any movement at all, which is definitely not right. The correct behavior would be to switch name tables only when there's movement (i.e. under the ReadLeft and ReadRight labels), and not when scroll is exactly 0, but whenever it overflows (carry set after adding the displacement) or underflows (carry clear after subtracting the displacement).

Based on the above, my immediate advice is: replace the INC and DEC that move the camera by ADC and SBC, so that you can move the camera by distances other than a single pixel (if you ever want to), and so you can detect overflows and underflows. If there's an overflow (carry set) after moving right, swap name tables. If there's an underflow (carry clear) after moving left, swap name tables. This should fix things for now.

In the long term though, I suggest you make some more changes. What you have now is too low level, so I'm sure you could benefit from some abstraction. Manipulating copies of hardware registers in the middle of the logic is a very clumsy thing to do, and easy to get wrong. The camera is an actual entity, a living thing in your game, so it deserves its own state and dedicated logic, and all processing should happen in the context of this entity (movement, acceleration, tracking objects, etc.). A 16-bit variable should be used to keep track of where the camera is, and with this value you can easily do everything related to scrolling: calculate the position of the blocks to read from the level map, calculate the destination address for the blocks in VRAM, calculate the scroll value (including the NT bit) and calculate the positions of the sprites used to represent game entities. I mean, you could keep track of all those things separately, but keeping everything consistent that way is so much harder that way.
User avatar
Sogona
Posts: 186
Joined: Thu Jul 23, 2015 7:54 pm
Location: USA
Contact:

Re: Having Another Jab at a Camera System

Post by Sogona »

Well fixing Issue #1 was easy enough, thank you Tokumaru. The three routines in the main loop I made subroutines, and made them only get called in ReadLeft and ReadRight, and only swap nametables when the carry flag for scroll is cleared or set, respectively.
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Having Another Jab at a Camera System

Post by tokumaru »

Sogona wrote:The buffer I have right now only draws the right tiles when scrolling to the right, and I don't know how I'd go about modifying it to handle both directions.
From what I can see, column holds the number of the column to draw. It starts at 0, and after the initial screen is fully drawn, it points to the right edge of the screen. It seems that whenever the camera scrolls 16 pixels, column gets incremented, moving one column to the right. See, this is exactly one of the problems I described in my last message... you're separately updating scroll and column, hoping they'll remain in sync, while could keep track of only the camera, and calculate all other necessary values from it, knowing that everything is in sync because it all came from the same value.

Anyway, you can probably fix this if you use two column variables, one pointing to the left edge of the camera and another pointing at the right edge. Whenever the camera moves 16 pixels, update them both (increment when moving right, decrement when moving left) then, when drawing columns, use the appropriate column variable to read from the level map, depending on which direction the camera moved. The output address in VRAM also has to change accordingly.
The thing I'm the most "unsure" about is that I have 2 buffers - one for when the first half of a new metatile column is drawn, and the other for when the second one is.
It seems you're deciding a pair of columns every 16 pixels, and sending one of them to VRAM every 8 pixels. So yeah, unless you want to ruin the whole decoding routine every 8 pixels, yes, you need to buffer 2 columns.
-How to make objects move in relation to the camera. (I think I heard somewhere that you have 1-byte variables for an object's actual position on the screen, and then a larger variable for their global position in the room. Whenever an object moves, its global position moves freely, but its local x position stays at 128 until its at the end of the room.)
This is another reason you need to give you entities actual world coordinates. When you subtract the camera's X coordinte from an object's X coordinate, the result is the relative distance from the object to the left edge of the camera... in other words, the screen position for the sprites.
-How to keep track of other objects (For instance, enemies), and make sure there are only enough objects on the screen at a time that the NES can handle. For example, if there are enemies that follow the player, what would happen when a spot was reached where new enemies spawn? Do some enemies just get erased?
This depends on how your object engine works. The engine won't normally try to prevent a lot of enemies from gathering together, tha's usually the responsibility of the level design. If you don't want enemies to reach another area where more events will spawn, you put a wall preventing the older enemies from getting there. Another option is to put in their AI a condition that they can't go more than N pixels away from their spawn point, so they'll just bounce back or something.
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Having Another Jab at a Camera System

Post by tokumaru »

Honestly though, there are a lot of bad practices going on in this code, that will make it really hard to turn this into an actual functioning game. For example, the way the level map is read is hardcoded to reading only the column at the right of the camera. You not only have to change this so the left side can be read as well, you actually need to make the map reading process dynamic enough so you can have the objects collide against the background.

Another bad practice is that triggers are always at exact pixel coordinates. Before you changed it, the name table would only change if the scroll was exactly 0. A new column of metatiles is only decoded if the scroll is an exact multiple of 16. A column of tiles is only sent to VRAM if the scroll is an exact multiple of 8. This makes it impossible to scroll at speeds other than 1 pixel per frame, otherwise you'd miss the triggers... I know that not every game needs super fast characters, but why impose such a limitation?

Here's a better way to check for crossed boundaries:

Code: Select all

lda oldscroll
eor scroll
and #%00010000
beq BoundaryNotCrossed
EOR (Exclusive OR) is a bitwise operation that results in 0 when the bits are the same, and 1 when the bits are different, so basically we're checking if bit 4 changed by comparing the old scroll value against the new one. Look at what happens when 15 turns into 16:

15: %00001111
16: %00010000

Bit 4 changes. Now from 31 to 32:

31: %00011111
32: %00100000

Bit 4 changed again. And this still works if you move, say, 4 pixels at once, from 45 to 49:

45: %00101101
49: %00110001

But 4 changed. So, whenever a 16-pixel boundary is crossed, bit 4 changes. Whenever an 8-pixel boundary is crossed, bit 3 changes.

This way of checking for crossed boundaries is much safer and more versatile than checking for specific pixel coordinates, so, if you can, I highly recommend you change to this method.

The other urgent change I recommend is that you stop using these scroll and columb variables and start to actually keep track of where the camara is in level space. When the camera crosses a 16-pixel boundary, check the direction of the movement and calculate the number of the column that needs to be read from the map:

If moving left, column = camera / 16. If moving right, column = camera / 16 + 16. Similarly, you can use the lower 9 bits of the camera's coordinate to calculate the PPU address where to draw the tiles.
User avatar
Sogona
Posts: 186
Joined: Thu Jul 23, 2015 7:54 pm
Location: USA
Contact:

Re: Having Another Jab at a Camera System

Post by Sogona »

So how many bits should camera be? If I have 64 columns that make up 4 screens, 64 × 16 = 1,024, or 10 bits. If that's the case, how do i divide by 16 (LSRing 4 times) on a value that's more than 8 bits?

And so you're saying each frame I should save what the scroll value was last frame, XOR it with the scroll value from this frame, and if the 4th bit is set draw a new column, and if the third bit is set update the buffer?
User avatar
darryl.revok
Posts: 520
Joined: Sat Jul 25, 2015 1:22 pm

Re: Having Another Jab at a Camera System

Post by darryl.revok »

So how many bits should camera be?
16-bits, 2 bytes should suffice, for up to 256 screens.

Low byte is pixel position on a screen.

Every time that carries, you go up a screen, and the high byte increases.

Then you go back to pixel 0 (low byte) on screen 1 (high byte).

You'll need the same data length for 4 or 256 screens.

The only way you could need another byte is if you added a sub-pixel position, and I haven't seen a reason to do this for camera position.

(edit: The only situation I can think of is when you camera isn't related to character position. If your character has sub pixels and your camera movement is based on that, then your camera can still move as if it has sub-pixels.)
how do i divide by 16 (LSRing 4 times) on a value that's more than 8 bits?
It gets more interesting when you do the math for the actual scrolling because you have to account for a few different situations, but to answer the question you asked:

Code: Select all

LDA lowByte
LSR highByte         ; moves lowest bit into carry
ROR                      ; moves carry bit into bit 7
LSR highByte         ; moves lowest bit into carry
ROR                      ; moves carry bit into bit 7
LSR highByte         ; moves lowest bit into carry
ROR                      ; moves carry bit into bit 7
LSR highByte         ; moves lowest bit into carry
ROR                      ; moves carry bit into bit 7
STA lowByte
The thing is, that code changes the value in memory. That's not exactly what you want. I haven't gone over the code yet to fully see your map system. But, that's a 16 bit divide by 16.
And so you're saying each frame I should save what the scroll value was last frame, XOR it with the scroll value from this frame, and if the 4th bit is set draw a new column, and if the third bit is set update the buffer?
Minor thing that could get confusing, is that bit 4 is actually the 5th bit. Bit 0 is the first bit. When tokumaru says Bit 4 he means the 5th bit from the right.

edit: mis-typed a number
Last edited by darryl.revok on Thu Jan 14, 2016 11:10 pm, edited 1 time in total.
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Having Another Jab at a Camera System

Post by tokumaru »

Sogona wrote:So how many bits should camera be? If I have 64 columns that make up 4 screens, 64 × 16 = 1,024, or 10 bits.
Yeah, the variable must have enough bits to represent any position within the game world. You can't easily use 10 bits because they only come in 8's, so just use 2 bytes like darryl.revok said and you'll be covered for up to 256 screens, or 65536 pixels.
If that's the case, how do i divide by 16 (LSRing 4 times) on a value that's more than 8 bits?
Shift the high byte, then rotate the lower byte (so the bit shifted out from the high byte is shifted into the low byte). You might not need to actually divide by 16 though, depending on how the level map is stored. It looks like your level maps are just arrays of column pointers, right? So, with each column consuming 2 bytes from the map, the column index has to be multiplied by 2. This means that you actually only need to divide by 8. So you could do something like this:

Code: Select all

	lda camerax+0 ;get the low byte of the camera's position
	and #$f0 ;keep only the bits relevant to find the column
	sta column
	lda camerax+1 ;get the high byte of the camera's position
	lsr ;divide by 2
	ror column
	lsr ;divide by 4
	ror column
	lsr ;divide by 8
	ror column
	ldy column ;use the result as an index
Now you can load column pointers from the current room like you're already doing. Note that this is for reading a column at the left side of the camera. When scrolling right, you have to find the column that's 256 pixels ahead:

Code: Select all

	lda camerax+0 ;get the low byte of the camera's position
	and #$f0 ;keep only the bits relevant to find the column
	sta column
	lda camerax+1 ;get the high byte of the camera's position
	clc
	adc #$01 ;move 1 screen ahead
	lsr ;divide by 2
	ror column
	lsr ;divide by 4
	ror column
	lsr ;divide by 8
	ror column
	ldy column ;use the result as an index
You can also use this column index to calculate the target address for the tiles in VRAM. Conveniently enough, most bits are in the correct positions already, only the NT bit needs to be moved. Here's one way to do it:

Code: Select all

	lda column
	and #%00011110 ;keep only the index of he column within the screen
	sta ppuaddr_ptr+0 ;save the low byte of the target address
	lda column
	and #%00100000 ;keep only the NT selection bit
	lsr ;put it in the correct position
	lsr
	lsr
	ora #$20 ;combine with the high byte of the base NT address ($2000)
	sta ppuaddr_ptr+1 ;save the high byte of the target address
Now just use this to blast the buffers to VRAM.
And so you're saying each frame I should save what the scroll value was last frame, XOR it with the scroll value from this frame, and if the 4th bit is set draw a new column, and if the third bit is set update the buffer?
Like darryl.revok said, bit 4 is actually the 5th bit, that's the one that indicates when a 16-pixel boundary has been crossed. Other than that, what you said is correct (save a copy of the scroll/camerax before modifying it so you can XOR the old value and the new one).

The goal here is to eliminate the scroll and nametable variables (and change the way column is calculated, like in the code I wrote above), and use the camerax variable instead, which contains all the information you could possibly need for scrolling.
User avatar
Sogona
Posts: 186
Joined: Thu Jul 23, 2015 7:54 pm
Location: USA
Contact:

Re: Having Another Jab at a Camera System

Post by Sogona »

So Tokumaru, are you saying I should use the XOR method you described above to check when the low byte of the camera has passed 255? (aka when at a new "screen"?) In that case, I'd want to save the high byte of camerax to camerax_old, xor the high byte of camerax with that of camerax_old, and then check if bit 0 of the high byte is set to 1, right?
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Having Another Jab at a Camera System

Post by tokumaru »

Sogona wrote:So Tokumaru, are you saying I should use the XOR method you described above to check when the low byte of the camera has passed 255? (aka when at a new "screen"?) In that case, I'd want to save the high byte of camerax to camerax_old, xor the high byte of camerax with that of camerax_old, and then check if bit 0 of the high byte is set to 1, right?
That would work, but it's not necessary in this case, because you have the carry flag. When the low byte of the camera overflows after an addition and the carry is set (or underflows after a subtraction and the carry is clear), you already KNOW FOR A FACT that bit 8 (bit 0 of he next byte) will change, so it's pointless to look for changes afterwards. You not only know that the bit is going to change, but you also know if the camera is moving left or right, so you can immediately calculate the column number at the correct side of the camera. If you did this the XOR way you'd need yet another comparison to know the direction in which the camera moved. The reason we need the XOR trick in the other case is because there's no carry flag from from bit 3 to bit 4. I think some other CPUs (Z80?) actually have that though, it's called a half-carry.
tepples
Posts: 22345
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Having Another Jab at a Camera System

Post by tepples »

Half carry in Z80 is because of how it handles post-processing of binary-coded decimal (BCD) addition: add in binary then use half carry to correct it. The MOS 6502,* by contrast, had a single adder that could function in binary or BCD mode, with the BCD-related bit being an input to the adder rather than an output from it. That's why half carry was left out.


* The NES's second-source 6502 disables the BCD side of the adder to save on patent royalties.
User avatar
Sogona
Posts: 186
Joined: Thu Jul 23, 2015 7:54 pm
Location: USA
Contact:

Re: Having Another Jab at a Camera System

Post by Sogona »

Ok, I thought I could just use the carry flag, I guess I just confuzzled myself.

Going off on this tangent, Z80 seems like it'd be really fun to learn (as a lot of the arcade classics used the CPU, and it'd be cool to understand their source code. I came across the original Z80 source for pac-man a while back.) I think I'd wanna learn that as a next assembly language. Either that or MIPS.

Speaking of MIPS, were N64 games recent enough to be written in higher level languages like C or C++ (I think I'd read somewhere that they were) or all it still all in asm?
User avatar
rainwarrior
Posts: 8062
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Having Another Jab at a Camera System

Post by rainwarrior »

Sogona wrote:were N64 games recent enough to be written in higher level languages like C or C++ (I think I'd read somewhere that they were) or all it still all in asm?
I personally know of PSX games that were written in C. I'm certain people would have used C on the N64 too. PC platforms had been using C for games for years before consoles started doing it too.

The thing is, you can transition gradually. C coexists side by side with assembly code. How much needed to be in assembly has gradually decreased over the years. I would say that until relatively recently most games had at least some custom assembly in the project. (Probably more, the further back you go.) But now? I'd suggest that the majority of games don't have any, especially with the rise of indie development.

It took until about 2000 for pure-assembly game development to mostly die off (e.g. late holdout), though I think the retro homebrew scene is bringing it back in its small way.
Joe
Posts: 469
Joined: Mon Apr 01, 2013 11:17 pm

Re: Having Another Jab at a Camera System

Post by Joe »

Sogona wrote:Speaking of MIPS, were N64 games recent enough to be written in higher level languages like C or C++ (I think I'd read somewhere that they were) or all it still all in asm?
They were written primarily in C/C++ with occasional handwritten assembly. Licensed developers didn't need to write any assembly at all, since all of the direct hardware access was handled by Nintendo's development kit.
User avatar
rainwarrior
Posts: 8062
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Having Another Jab at a Camera System

Post by rainwarrior »

Joe wrote:Licensed developers didn't need to write any assembly at all, since all of the direct hardware access was handled by Nintendo's development kit.
Direct hardware access should generally be written in assembly, yes, but that's hardly the only place to use it.

Rewriting something in assembly is your last stand for optimization when the compiler isn't good enough (and it frequently isn't, especially back then and on that system). In projects I worked on for the PS3 and 360 we had a fair amount of hand-optimized assembly in the code, because we needed it to improve performance. I can only imagine the need was stronger when working on N64.
Post Reply