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:

Re: Having Another Jab at a Camera System

Post by Sogona »

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: Select all

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?
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:I'm guessing I should buffer these as well, yes?
Yes, anything you need to write to the PPU must be buffered.
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.
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.
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.
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 »

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.
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 »

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.
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 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: Select all

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?
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: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: Select all

CameraX = PlayerX - 128;
if CameraX < 0 then CameraX = 0;
if CameraX > LastScreen then CameraX = LastScreen
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 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.
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 »

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.
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.
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.
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.
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 »

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.
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.
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.
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 »

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: Select all

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
User avatar
thefox
Posts: 3139
Joined: Mon Jan 03, 2005 10:36 am
Location: Tampere, Finland
Contact:

Re: Having Another Jab at a Camera System

Post by thefox »

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).
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
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 »

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: Select all

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: Select all

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: Select all

(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: Select all

(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.)
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 »

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.
User avatar
Myask
Posts: 965
Joined: Sat Jul 12, 2014 3:04 pm

Re: Having Another Jab at a Camera System

Post by Myask »

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).
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 »

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: Select all

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: Select all

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.
Post Reply