Having Another Jab at a Camera System

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Having Another Jab at a Camera System
by on (#162571)
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.
Attachment:
sogona_camerasystem.zip [33.99 KiB]
Downloaded 117 times


Thanks for all the help you guys have been, and I'm sure will continue to be. :)
Re: Having Another Jab at a Camera System
by on (#162574)
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.
Re: Having Another Jab at a Camera System
by on (#162576)
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.
Re: Having Another Jab at a Camera System
by on (#162587)
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.

Quote:
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.

Quote:
-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.

Quote:
-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.
Re: Having Another Jab at a Camera System
by on (#162589)
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:
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.
Re: Having Another Jab at a Camera System
by on (#162591)
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?
Re: Having Another Jab at a Camera System
by on (#162592)
Quote:
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.)

Quote:
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:
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.

Quote:
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
Re: Having Another Jab at a Camera System
by on (#162594)
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.

Quote:
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:
   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:
   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:
   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.

Quote:
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.
Re: Having Another Jab at a Camera System
by on (#162655)
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?
Re: Having Another Jab at a Camera System
by on (#162656)
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.
Re: Having Another Jab at a Camera System
by on (#162659)
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.
Re: Having Another Jab at a Camera System
by on (#162660)
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?
Re: Having Another Jab at a Camera System
by on (#162663)
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.
Re: Having Another Jab at a Camera System
by on (#162748)
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.
Re: Having Another Jab at a Camera System
by on (#162753)
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.
Re: Having Another Jab at a Camera System
by on (#162762)
Well I can now scroll in both directions! Thank you guys so much for your help. Just to make sure I have everything right, here are my routines: http://pastebin.com/ZyY91VVQ
DrawNewColumn is the same, only the subroutine CameraCalculatePPUAddr is called from within it.

Now before I dive into working with objects, I want to quickly and (hopefully painlessly) add attributes. I'm guessing I should buffer these as well, yes?

The data format I think would be similar to what I'm already using with columns. Something like:
Code:
RoomAttributes:
     .dw Room0Attributes, Room1Attributes...

Room0Attributes:
     .dw Room0Attributes0, Room0Attributes1, Room0Attributes2, Room0Attributes3, ... ,Room0Attributes7

Room0Attributes0:
     .db %00100101, %10010110, ....
and so on.

So I'll need a new routine to check when a boundary of 32 pixels have been crossed, and since column is already keeping track of increments of 16, will I need a new variable that keeps track of increments of 32?
Also, my buffer flag variable gets set whenever 8 pixels are crossed, to signal that whatever's in the buffer needs to be drawn to the PPU, and a pointer that says where in the PPU to draw them. Will I need another flag variable and a new pointer?
Re: Having Another Jab at a Camera System
by on (#162767)
Sogona wrote:
I'm guessing I should buffer these as well, yes?

Yes, anything you need to write to the PPU must be buffered.

Quote:
The data format I think would be similar to what I'm already using with columns.

This is a good way to handle attributes painlessly. It's a little wasteful, storage-wise, but easier to handle. The more complicated approach would be to store the attribute as properties of the metatiles, and dynamically form each attribute byte by combining bits from 4 different metatiles.

Quote:
since column is already keeping track of increments of 16, will I need a new variable that keeps track of increments of 32?

Yes, you have to look at bit 5 of camerax to detect when a 32-pixel boundary is crossed, but you don't need more pointers to read the attributes, you can reuse the ones you use for metatiles, just divide them by 2.

Quote:
Also, my buffer flag variable gets set whenever 8 pixels are crossed, to signal that whatever's in the buffer needs to be drawn to the PPU, and a pointer that says where in the PPU to draw them. Will I need another flag variable and a new pointer?

Yes.
Re: Having Another Jab at a Camera System
by on (#162951)
Since attributes need to be drawn a column at a time, how exactly do I go about drawing them vertically? Increment-32 of course only works for nametables.
Re: Having Another Jab at a Camera System
by on (#162971)
An 8-byte attribute column covers a 32-pixel-wide portion of the map, so make sure you've prepared it with that taken into account.

  1. Set +32 increment mode.
  2. Set the VRAM address to $23C0+x.
  3. Write bytes 0 and 4 of the attribute column.
  4. Set the VRAM address to $23C8+x.
  5. Write bytes 1 and 5 of the attribute column.
  6. Set the VRAM address to $23D0+x.
  7. Write bytes 2 and 6 of the attribute column.
  8. Set the VRAM address to $23D8+x.
  9. Write bytes 3 and 7 of the attribute column.
Re: Having Another Jab at a Camera System
by on (#163025)
So I've gotten attributes to be working nicely and smoothly. I can scroll back and forth at will without any graphical errors.

Before I start trying to work with multiple entities, I want to be able to make sure that the camera can "follow" a particular sprite, which will become the player. So each object has coordinates for their position in the entire room, and for their position on the screen. I've just been referring to these as "global" and "local" coordinates. (And since I'm only worrying about scrolling in one dimension, I won't need to have a global Y coordinate.)

The idea I have in mind is that, unless the camera is at the end of a room (The first screen or the last screen), the main entity should be in the middle of the screen and not moving. (Except for of course vertically) Otherwise, their local position should move until it reaches 128, or the center of the screen, in which case it also stops moving.

The pseudocode plan I have for the logic is this:
Code:
MoveLeft {
   /*
   all the camera updating code
   */

   //move the sprite's global position in the room
   sprite_globalx--;

   //see if sprite needs to be moved on the actual screen
   if (camerax+1 == minscreen) {   //if at screen 0 (the left edge of the room)
      if (lowbyte of sprite_globalx < 128)
         sprite_localx--;
         //do the metasprite updates and all that
   }
   else if (camerax+1 == maxscreen) {   //if at the edge of the room
      if (sprite_localx >= 128)   //if the sprite is right of the center of the screen
         //move the sprite until it reaches the center of the screen, after that keep it there
         sprite_localx--;
         //do the metasprsite updates and all that
   }
}

MoveRight {
   /*
   all the camera updating code
   */

   //move the sprite's global position in the room
   sprite_globalx++;

   //see if sprite needs to be moved on the actual screen
   if (camerax+1 == minscreen) {   //if at screen 0 (the left edge of the room)
      if (sprite_localx < 128)   //if the sprite is left of the center of the screen
         //move the sprite until it reaches the center of the screen, after that keep it there
         sprite_localx++;
         //do the metasprite updates and all that
   }
   else if (camerax+1 == maxscreen) {   //if at the right edge of the room
      if (lowbyte of sprite_globalx >= 128)
         sprite_localx++;
         //do the metasprite updates and all that
   }
}


Is this how it's normally done, or is there a simpler way?
Re: Having Another Jab at a Camera System
by on (#163036)
Sogona wrote:
Is this how it's normally done, or is there a simpler way?

As usual, I advise against maintaining multiple copies of what is essentially the same data. If you go the global/local way, you'll need more logic than what you have there. For example, what happens when an object is off-screen? Will the local coordinate stop being updated and lose sync with the global coordinate?

My suggestion is you keep only the global coordinate, since that's what really describes the game world. Screen coordinates are just a hardware-specific detail you need in order to draw sprites, it doesn't say anything meaningful about the game state. Update only the global coordinates of all objects, and once they're all in their final positions, you can subtract the camera's coordinate from the objects' coordinates to find their screen positions.

Then you only need a few very simple rules for the camera to follow the player:

Code:
CameraX = PlayerX - 128;
if CameraX < 0 then CameraX = 0;
if CameraX > LastScreen then CameraX = LastScreen
Re: Having Another Jab at a Camera System
by on (#163115)
So I've gotten the camera to correctly follow the player and stop moving once it gets to the edge of the room. Thanks.

So now, other objects and how they work in relation to the camera is a concept I'm pretty much entirely unfamiliar with. So do rooms keep track of certain points where objects are?

I don't really know how other games go about doing it, but one idea that came to me is that for each room, there's a table that contains the 16-bit coordinates of where all the objects would spawn. When the player moves, the game checks each of these positions, and if the absolute value of the distance between the object and the player is less than 128, the object should be instantiated and drawn to the screen.
Then, when each object's code is run, a routine is called that checks if the absolute value of the distance between the player and it is greater than 128. If it is, the object is then deactivated, and its sprites are moved off the screen. Otherwise, it goes on to calculate where on the screen it should be drawn.

One thing that confuses me is, once an object is deactivated, and its OAM erased, should the OAM of sprites further ahead be moved up, so that new objects can be drawn? I don't really know how to explain it, but I'll try to go more in depth if that doesn't make any sense.
Re: Having Another Jab at a Camera System
by on (#163120)
Object spawning is one of those things that varies greatly from game to game, and you probably should design a system that works well with your game's design.

Sogona wrote:
there's a table that contains the 16-bit coordinates of where all the objects would spawn.

I believe it's common for games to have a table containing the coordinates, the object type, and sometimes an attribute byte, which can be used for different things depending on the object's type. You don't need to have this if you can setup everything up using just the type.

Quote:
When the player moves, the game checks each of these positions, and if the absolute value of the distance between the object and the player is less than 128, the object should be instantiated and drawn to the screen.

Object spawning should be relative to the camera, not the player, since there are cases when the player isn't centered. If the player is at the very start of the level, near the left edge of the screen, an object that's 200 pixels ahead of it won't be loaded if you check for a constant distance of 128 pixel from the player, even though that point is on-screen.

Quote:
Then, when each object's code is run, a routine is called that checks if the absolute value of the distance between the player and it is greater than 128. If it is, the object is then deactivated, and its sprites are moved off the screen.

The same happens at the end of the level, if the player is near the right edge of the screen... objects on the far left will be deactivated while still on-screen. To avoid these cases, just base the spawning on the camera instead.

Also, you should add some breathing room for the objects, instead of activating them right before their hotspots enter the screen, otherwise half of the objects will "pop" on the screen, instead of smoothly scrolling into view, when they're activated. The same goes for when they are deactivated. Not only it's weird for an object to disappear when parts of it are still visible, but it feels really weird for the player if he tries to follow an enemy that just went off-screen only to find there's nothing there. Personally I'd give at least 64 pixels of padding between the edges of the camera and the spawning/despawning spots.

Quote:
One thing that confuses me is, once an object is deactivated, and its OAM erased, should the OAM of sprites further ahead be moved up, so that new objects can be drawn? I don't really know how to explain it, but I'll try to go more in depth if that doesn't make any sense.

Normally, objects don't use constant OAM positions, they normally pick OAM slots at random each frame, so that priorities are cycled every frame. With this setup you don't have to do anything about objects that die, they simply won't be there to claim any OAM slots.
Re: Having Another Jab at a Camera System
by on (#163136)
Sogona wrote:
one idea that came to me is that for each room, there's a table that contains the 16-bit coordinates of where all the objects would spawn. When the player moves, the game checks each of these positions, and if the absolute value of the distance between the object and the player is less than 128, the object should be instantiated and drawn to the screen.

Yes. Having the object spawn table sorted by X position makes this search easier to do each frame, as the expected range can move to the left or right as the camera moves to the left or right.

Quote:
Then, when each object's code is run, a routine is called that checks if the absolute value of the distance between the player and it is greater than 128. If it is, the object is then deactivated, and its sprites are moved off the screen.

But you need to be careful that when a sprite moves offscreen, it doesn't get respawned until the player moves out of the area and back into the area. There are a couple ways to do that, usually involving storing each active object's index into the object spawn table.

Quote:
One thing that confuses me is, once an object is deactivated, and its OAM erased, should the OAM of sprites further ahead be moved up, so that new objects can be drawn?

On NES, where sprite cycling (that is, flicker to avoid dropout) is the norm, it's usual to rebuild OAM entirely from scratch every frame by rescanning the list of active objects.

tokumaru wrote:
Object spawning should be relative to the camera, not the player, since there are cases when the player isn't centered.

Doing it relative to the camera is a good policy in general for reasons that you mentioned. But it has a couple exploits, especially when you change the camera policy not to keep the player strictly in the center. If you have Super Mario World for Super NES, press the R button to change the scroll and trigger enemies early.
Re: Having Another Jab at a Camera System
by on (#163165)
tepples wrote:
On NES, where sprite cycling (that is, flicker to avoid dropout) is the norm, it's usual to rebuild OAM entirely from scratch every frame by rescanning the list of active objects.

I've been looking through the various topics on sprite cycling, but I'm still pretty confused as to how it works. So it's custom to clear your OAM shadow page each frame, update where to start drawing by a random (Prime?) number, and then just draw from there?

Here's a quick demo I typed up, which implements what I described above, and does cause sprites to cycle, but not very well. By the way, I'm not really concerned with priority, at least not right now.

Code:
InitSomeSprites:
   lda #9
   sta activesprites
   
        ;put 9 sprites on a scanline
   lda #$80
   sta object_y+0
   sta object_y+1
   sta object_y+2
   sta object_y+3
   sta object_y+4
   sta object_y+5
   sta object_y+6
   sta object_y+7
   sta object_y+8
   lda #$60
   sta object_x+0
   lda #$68
   sta object_x+1
   lda #$70
   sta object_x+2
   lda #$78
   sta object_x+3
   lda #$80
   sta object_x+4
   lda #$88
   sta object_x+5
   lda #$90
   sta object_x+6
   lda #$98
   sta object_x+7
   lda #$A0
   sta object_x+8


MainLoop:
   inc sleeping
@waitframe:
   lda sleeping
   bne @waitframe
   
   lda oamstart
   clc
   adc #76                ;76 / 4 = 19, a prime number
   sta oamstart
   
   jsr ClearOAM
   ;draw objects to the screen
   ldy oamstart
   ;x should be 0, from the above subroutine
-   lda object_y,x
   sta $0200,y
   iny
   lda object_tile,x
   sta $0200,y
   iny
   lda #$00
   sta $0200,y
   iny
   lda object_x,x
   sta $0200,y
   iny
   inx
   cpx activesprites
   bne -
   
   jmp MainLoop
Re: Having Another Jab at a Camera System
by on (#163166)
Sogona wrote:
So it's custom to clear your OAM shadow page each frame, update where to start drawing by a random (Prime?) number, and then just draw from there?

Better to draw first, and then clear what's left. That way you don't waste time processing the same sprites twice. Also note that you only need to "clear" the Y coordinate (to $FF).
Re: Having Another Jab at a Camera System
by on (#163971)
Okay, so now I'm in the process of integrating my object system with my camera system. I haven't written any code yet, but I've taken your guys' advice and have a basic outline of how I think objects would spawn.

I think it is a good idea to have object data in spawn tables sorted by X coordinate, so that compares with the camera can happen in a linear fashion, and simply stop once an object's x position is greater than that of the camera's, since every object after that will be as well.

My spawn table looks like this:
Code:
ROOM00Objects:
     .db $00,$00,$C0,$40     ;ID, X hi byte, X lo byte, Y
     .db $01,$01,$20,$20
     .db $01,$01,$40,$70
     ...
     .db $FF                            ;No more objects


So when a new room is loaded, the high byte of camerax (the screen number) is set to either 0 (if the player exited the previous room from the right), or how many screens wide the room is - 1 (if the player exited the previous room from the left.) The low byte is simply set to 0. In case there are objects on the screen at the start, these should be loaded doing something like this:
Code:
FOREACH object IN SpawnTable
     if (object.x < camerax)     //16-bit compares, if object is to the left of the camera
          go to next object
     else
          if (object.x - camerax < 255)
               initialize object
               objectcount++
          else
               exit loop


And then, when the player moves right:
Code:
(after moving camera)
FOREACH object IN SpawnTable
     if (object.x < (camerax + 255))
          go to next object
     else
          if (object.x - (camerax + 255) < 64)
               initialize object
               objectcount++
          else
               exit loop, since every other object will be too far away


And when moving left:
Code:
(after moving camera)
FOREACH object IN SpawnTable
     if (object.x > camerax)
          exit loop, since every other object will either already be on the screen or too far to the right
     else
          if (camerax - object.x < 64)
               initialize object
               objectcount++
          else
               go to next object


This seems feasible, but before I translate it all into ASM, I'd like to know if it would work (And of course, if I'm overcomplicating things.)
Re: Having Another Jab at a Camera System
by on (#163977)
That's the general idea. My only criticism is that FOREACH looks like a waste of processing time, because you might spend a lot of time scanning through objects that are way to far from the camera before getting to the ones that matter. Depending on how long the levels are and how many objects they have, this might not be such a big problem, but there are ways reduce the amount of objects you have to check.

The solution I use is to have 2 pointers that slide over the list of objects. These pointers delimit the range of objects that are active, with a resolution of 128 pixels, and they indicate the next candidates for activation on both sides of the camera. Every time the camera moves 128 pixels, the pointer on the leading side checks if the object it points to (the candidate) is part of the column that just entered the active area, by checking if the relevant bits of its X coordinate match those of the new column. If it's a match, the object is loaded and the pointer moves one position. This repeats until an object that does not belong in the new column is found, and this object becomes the new candidate.

On the trailing side, the pointer moves to exclude objects from the active area, and stops in the last object that belongs in the column that was just left behind, so this object is now the candidate for loading in that direction, in case the camera switches directions. Objects excluded from the active area aren't immediately deactivated though, since they could be still on screen. Each object is responsible for keeping track of whether their respawn coordinates got excluded from the active area, and whether they are off screen. Only when both these conditions are met the object will deactivate itself.

The advantage of doing it this way is that there are always pointers near the objects that are to be included in or excluded from the active area, so there's no need to make comparisons with objects that are nowhere near the areas of interest. I'm not saying you should do it exactly like I do it, I'm just throwing the idea out there in case you find it interesting.

EDIT: BTW, you need some mechanism to prevent already loaded objects from being loaded again, in case the camera moves back and forth. What I do is use a list of flags (1 bit for each object) indicating whether objects should be loaded or not. When the object is activated, this bit is set, when it's deactivated, the bit is cleared. When an object is destroyed or dies and is not supposed to appear again, the bit is left permanently set.
Re: Having Another Jab at a Camera System
by on (#164238)
A more naïve way to do it (that will get you more NES-era-like results) is to use a more precise, sorted list of spawnpoints, keeping a pointer to which next to spawn in either direction, and only to invoke each as it enters the region and its object is not active. If you don't include the latter, you can wiggle the camera and get a torrent of spawns, as in Rygar. (Each of those apparent enemies is actually a stack of four or five, leading to quick experience-gain.)

This lightweight combination would prevent an enemy immediately respawning on death, but allows "scrolling them back onscreen", a thing that some games have started to treat as a design element (consider Galaxy Man's stage from Megaman 9).
Re: Having Another Jab at a Camera System
by on (#164526)
After a bit of headache, I've gotten objects to spawn and be deactivated pretty nicely. Right now I have it so that there can be 8 objects that are active at a time, not counting the player, or things that are made by other objects (For instance, projectiles.) For now I've just kept things simple and stuck with my linear search, and when all 8 objects are active, their code running, and the camera's moving, around 20% of frame time is used; and at most times, I feel like there'll be less objects than that. It's usually only around 10% frame use.

So I think the next thing I want to try is background collision with scrolling. I already asked about this in another thread, but from what others have said it seems to be more involved when there's scrolling. After thinking about it for a bit, though, isn't the only difference really that you're working with 16-bit values instead of 8-bit?

I think I want to try and have collision data work like attribute data, where there can be four different types of collisions (Nothing, solid, death, something else) and 4 16x16 pixel blocks can be packed into one byte. So for each room there could be a pointer table for the collision data for each screen. The high byte of each object's position, as it corresponds to the screen number, could be multiplied by 2 to get the right pointer, and then the low byte is treated as the X position in the table, and from there on it's just like normal background collision detection, right?

Something like:
Code:
RoomCollisionData:
        .dw Room0CollisionData, Room1CollisionData, ...

Room0CollisionData:
        .dw Room0Screen0CollisionData, Room0Screen1CollisionData, ...

Room0Screen0CollisionData:
        .db %01010101,%01010101,%01010101,%01010101        ;first 16 blocks - solid
        .db %01000000,%00000000,%00000000,%00000001        ;first and last block solid, blocks 1-14 non-solid
        .db %01000101,%01010101,%01010101,%01010001
        .db %01000101,%01010101,%01010101,%01010001
        .db %01000101,%01010101,%01010101,%01010001
        .db %01000101,%01010101,%01010101,%01010001
        .db %01000101,%01010101,%01010101,%01010001
        .db %01000101,%01010101,%01010101,%01010001
        .db %01000101,%01010101,%01010101,%01010001
        .db %01000101,%01010101,%01010101,%01010001
        .db %01000101,%01010101,%01010101,%01010001
        .db %01000101,%01010101,%01010101,%01010001
        .db %01000101,%01010101,%01010101,%01010001
        .db %01000000,%00000000,%00000000,%00000001
        .db %01010101,%01010101,%01010101,%01010101


And then I think something like this would get the correct type of collision (0-3) of wherever the object is:
Code:
int[] AND_Table = {0xC0, 0x30, 0x0C, 0x03};

bgcol_y = obj_y / 4;
bgcol_x = obj_xlo / 64;
bgcol_x_block = (obj_xlo / 16) & 0x03;

bgcol_index = bgcol_y + bgcol_x;

bgcol = Room0Screen0CollisionData[bgcol_index] & AND_Table[bgcol_x_block];

if (bgcol_x_block > 0) {
        for (int i = 0; i <= bgcol_x_block - 1; i++)
                bgcol /= 4;
}


Sorry, I'm kinda dumping ideas. Time to close my eyes.
Re: Having Another Jab at a Camera System
by on (#164527)
Sogona wrote:
I already asked about this in another thread, but from what others have said it seems to be more involved when there's scrolling. After thinking about it for a bit, though, isn't the only difference really that you're working with 16-bit values instead of 8-bit?

Yeah, the size of the level shouldn't matter much, you just have to know WHERE to find the collision data for any given block, based on level coordinates. As long as you can manipulate level coordinates in a way that allows you to traverse the data structures you need to in order to find the collision attributes for the block under those coordinates, it'll work.

Quote:
I think I want to try and have collision data work like attribute data, where there can be four different types of collisions (Nothing, solid, death, something else) and 4 16x16 pixel blocks can be packed into one byte.

Could "something else" be water? Personally, I think that 4 types is too little. What if in the future you decide to add ice, lava, conveyor belts, spikes (only one side hurts), slopes, and so on? I'd reserve AT LEAST 4 bits for the type. You can get by with 2 bits in a basic game, but it's not very expansible.

Quote:
So for each room there could be a pointer table for the collision data for each screen.

Here's another thing I'd do differently... I'd much rather have the collision information be an attribute of the metatile. Think about it: a metatile is likely to ALWAYS have the same solidity ANYWHERE it's used. If it has rocks on the bottom and grass on the top, it'll probably ALWAYS be solid, so think about all the ROM space you'll waste specifying that - EVERY - SINGLE - INSTANCE - of that metatile is solid. Each time you use any metatile, anywhere, BAM, 2 bits lost. Use the same metatile 4 times and you already wasted 8 bits on it, enough to specify 256 different types if you stored that information in the metatile itself rather than in a separate map. And I bet that most metatiles will be used way more than just 4 times!

If you actually needed a metatile to be solid in some places but not others for some reason, you'd waste much less space by creating a duplicate metatile that looks the same but has different collision information, but I honestly doubt that would happen much. For consistence, you generally want things that look the same to behave the same, otherwise you'll confuse the player.

Another advantage of making the solidity an attribute of the metatile, besides not having to painstakingly draw/type an overlay collision map for every room of every level, is that you can reuse the code you already wrote to fetch tiles for scrolling. You already have the working logic to locate metatiles in the level map, so you can use the exact same logic (if not the exact same routine(s), depending on how exactly you implemented the scrolling) to find them, the only difference is that when you do, you'll not read its tiles or palette information, but its collision information.
Re: Having Another Jab at a Camera System
by on (#164528)
I guess this means I'll have yo get rid of my RLE format :|
Re: Having Another Jab at a Camera System
by on (#164531)
It was just a suggestion, you shouldn't do anything you're not comfortable doing. :) I didn't realize you were using RLE for the level data... Are you decompressing the RLE on the fly to fill the tile and attribute buffers without buffering small sections of the level first? Still, independent collision maps for the whole game would take so much space that giving up on the RLE might still result in lower ROM consumption overall... You'd have to check how much ROM the RLE compression is saving you versus how much ROM the raw collision maps take.
Re: Having Another Jab at a Camera System
by on (#164537)
Haunted: Halloween '85 ended up using separate collision maps consisting of a floor heightmap and a horizontally sorted list of rectangular slabs, both at 8x4 pixel resolution, and there isn't really a concept of "metatiles" because of the desire to hide the grid David Crane-style. Instead of capturing spatial correlation using metatiles, its map compression instead predicts the most common tile below each tile with a table that changes for each map. I'd have to check, but I doubt that the collision maps are larger than 8 KiB, compared to close to 384 KiB for compressed tiles and compressed maps combined. I seem to remember seeing a screenshot of some of the tools used to make Braid, which builds the floor with rotated rectangles.

But I agree that a more traditional metatile-based approach would have collision as a property of the metatile for the sake of normalization, just as the top left, top right, bottom left, and bottom right tiles are.
Re: Having Another Jab at a Camera System
by on (#164538)
I don't think 2 bits per block is THAT restrictive if you can select from sets of four meanings based on the current stage or whatever. You'd only be restricted to using 4 block types at a time, but you could switch to a set of "Nothing/Solid/Death/Ice" when you need those, and switch to a set of "Nothing/Solid/Conveyor Belt Right/Conveyor Belt Left" for another level, and so on... I did the same thing with my controller routine to be able to select different button masks so that I can require certain buttons to be released before they count as being pressed again, because you don't want the player to be able to hold the D-pad to move a menu cursor, but during gameplay they should be able to.
Re: Having Another Jab at a Camera System
by on (#164545)
za909 wrote:
I don't think 2 bits per block is THAT restrictive if you can select from sets of four meanings based on the current stage or whatever. You'd only be restricted to using 4 block types at a time, but you could switch to a set of "Nothing/Solid/Death/Ice" when you need those, and switch to a set of "Nothing/Solid/Conveyor Belt Right/Conveyor Belt Left" for another level, and so on...

I think that's still pretty restrictive, considering that "nothing" and "solid" are mandatory for every level, so you actually only have 2 dynamic slots. And it makes the engine needlessly complicated by requiring it to indirectly look up the correct response to each type of block instead of reacting right away. It's not bad to the point of being prohibitive, but hardly justifiable if you can save space through other means.

Quote:
I did the same thing with my controller routine to be able to select different button masks so that I can require certain buttons to be released before they count as being pressed again, because you don't want the player to be able to hold the D-pad to move a menu cursor, but during gameplay they should be able to.

That's... not the same thing at all. For the controller you just need a couple of bitwise operations to interpret button presses differently, while configurable collision responses would need a much more complex system, with jump tables and the like. In fact, interpreting button presses differently is so quick that most games I checked just read the controllers all possible ways all the time: there's a byte indicating the buttons that are currently pressed, another indicating which were just pressed, and yet another indicating which were just released. This is useful because even during gameplay you want some buttons to work continuously, like the arrows that move left and right, and others to work only on transitions, such as the action buttons for jumping and shooting. If you have all the different states available, it's just a matter of having the code that handles each action check the appropriate byte.
Re: Having Another Jab at a Camera System
by on (#164687)
Thank you all so much for your help, I really do appreciate it. The engine's certainly come a long way from what it originally was. :wink:

I've gotten background collision detection to work with scrolling, but I decided "screw it" and made it so that objects can only have a maximum hitbox width or height of 32 pixels (For objects that have background collision, that is.), so I can just check both corners and have a hard-coded midpoint by adding both corners and dividing by two. I know, not the most versatile decision, but it's just easy. Maybe one day I'll try working with bigger, weirder hitboxes, but for now, this'll work fine.

Now it's time to work on graphics and music!