WIP: Wizard of Wor
Moderator: Moderators
Re: WIP: Wizard of Wor
You have about 2200 cycles per vblank (113⅔ · 20) for everything that needs to be sent to the PPU. Updating sprites (OAM DMA) takes roughly 1/4 of that. CC65 is probably adding some overhead, too.
Almost every game I've looked at has some kind of scheduler for PPU updates. Sometimes they're really minimal (e.g. some flip-screen games just change between "PPU is off, uploading new data" and "game is running")
Almost every game I've looked at has some kind of scheduler for PPU updates. Sometimes they're really minimal (e.g. some flip-screen games just change between "PPU is off, uploading new data" and "game is running")
- rainwarrior
- Posts: 8735
- Joined: Sun Jan 22, 2012 12:03 pm
- Location: Canada
- Contact:
Re: WIP: Wizard of Wor
I think you can call set_vram_update whenever you want, that's just setting a pointer and the actual PPU update is done by neslib's NMI handler (which will run at the next vblank). The problem is just that the NMI handler has no way of safeguarding against or helping to diagnose the "too much data takes too long" case.tschak909 wrote:Yup, looks like that's the problem. The question I have is, is it safe to call set_vram_update(); when the PPU is on?
I need to literally update different areas of the screen, depending on different events, score change, worrior doors opening, teleport happening, etc. If I can't call set_vram_update() while ppu is active, will I need to roll my own little queuing and scheduler code for this?
I believe all the neslib functions you can't call while rendering is on are labelled as such in the header.
If you need all of those updates to happen in one frame, you could write a custom assembly NMI handler that can get that much data through in a single vblank, but the library's generic one is not efficient enough to do it.
Otherwise you can break up your updates so you only have to do some of it each frame.
Re: WIP: Wizard of Wor
The solution is making your vram_update array dynamic, fill it with what you need to change for that frame, and let the NMI handler do its magic. I'm sure you don't need to update everythign every frame, so this just works.
Reserve a big enough update buffer in BSS, such as:
Here, UPDATE_LIST_SIZE = 32 works for me (more than enough!). ul is a pointer we'll use to update the buffer.
Then your main loop would do something in the lines of:
In your game logic, whenever you need to update something, just write everything to the update buffer through *ul, for example:
I'm sure just a number of things will fill the buffer each frame, so this will work and you won't run out of VBlank time.
Reserve a big enough update buffer in BSS, such as:
Code: Select all
unsigned char update_buffer [UPDATE_LIST_SIZE*3+1];
unsigned char *ul;
Then your main loop would do something in the lines of:
Code: Select all
set_vram_update (update_buffer);
while (game_on) {
ul = update_buffer; // Point to the beginning of the buffer
... do your game logic, write to ul, increment ul.
*ul = NT_UPD_EOF;
ppu_wait_frame ();
}
set_vram_update (0);
...
Code: Select all
*ul ++ = MSB(NTADR_A(1,19))|NT_UPD_HORZ,LSB(NTADR_A(1,19));
*ul ++ = 3;
*ul ++ = 0x74;
*ul ++ = 0x63;
*ul ++ = 0x75;
Re: WIP: Wizard of Wor
Ok. I'm already basically doing this, I'll just not try to do it in one big buffer. (I was doing 256 bytes in BSS and just creating update records for every single thing to update, and simply just changing the data...)
-Thom
-Thom
Re: WIP: Wizard of Wor
It depends on how the tube is calibrated. It's often joked that NTSC stands for Never The Same Color, but it can also mean Never The Same Crop. There's a diagram at "Overscan" on the wiki. For convenience and in case something happens to the wiki:tschak909 wrote:I'm just curious as to how the result frames on a tube.
- Danger Zone (outside Action Safe Area)
- Action Safe Area, 256x224, (0, 8)-(255, 231)
- PocketNES Safe Area, 240x212, (8, 16)-(247, 227)
- Title Safe Area, 224x192, (16, 24)-(223, 215)
Re: WIP: Wizard of Wor
oh I know the joke, all too well. It's especially poignant on the VCS, because you're literally generating half of the video signal yourself! (I have literally made black and white pixels artifact into color on the VCS on accident.)
I just wanted to see how tight my tolerances are, since I do not have a toaster or a tube anymore.
(and yes, I have the overscan chart for the NES, I'm really bunching up against it. I just want to see how close to the cliff I actually am.)
-Thom
I just wanted to see how tight my tolerances are, since I do not have a toaster or a tube anymore.
(and yes, I have the overscan chart for the NES, I'm really bunching up against it. I just want to see how close to the cliff I actually am.)
-Thom
Re: WIP: Wizard of Wor
Code that currently opens/closes the worrior doors
Code: Select all
/**
* set_door(player, openclose)
* player = Player 0 (blue) or Player 1 (yellow) door.
* openclose = 0 for open, 1 for close.
*/
void set_door(unsigned char player, unsigned char openclose)
{
// Clear the update buffer
clear_update_buffer();
// Set the addresses for the two rows of tiles that make up the door
update_buffer[0]=(player==0?MSB(NTADR_A(1,18))|NT_UPD_HORZ:MSB(NTADR_A(28,18))|NT_UPD_HORZ);
update_buffer[1]=(player==0?LSB(NTADR_A(1,18)):LSB(NTADR_A(28,18)));
update_buffer[2]=3;
update_buffer[6]=(player==0?MSB(NTADR_A(1,19))|NT_UPD_HORZ:MSB(NTADR_A(28,19))|NT_UPD_HORZ);
update_buffer[7]=(player==0?LSB(NTADR_A(1,19)):LSB(NTADR_A(28,19)));
update_buffer[8]=3;
// And then set the tiles for each update depending on desired door state.
if (openclose==0 && player==0)
{
update_buffer[3]=0x65;
update_buffer[4]=0x00;
update_buffer[5]=0x00;
update_buffer[9]=0x65;
update_buffer[10]=0x00;
update_buffer[11]=0x00;
}
else if (openclose==1 && player==0)
{
update_buffer[3]=0x76;
update_buffer[4]=0x64;
update_buffer[5]=0x64;
update_buffer[9]=0x74;
update_buffer[10]=0x63;
update_buffer[11]=0x63;
}
else if (openclose==0 && player==1)
{
update_buffer[3]=0x00;
update_buffer[4]=0x00;
update_buffer[5]=0x66;
update_buffer[9]=0x00;
update_buffer[10]=0x00;
update_buffer[11]=0x66;
}
else if (openclose==1 && player==1)
{
update_buffer[3]=0x64;
update_buffer[4]=0x64;
update_buffer[5]=0x77;
update_buffer[9]=0x63;
update_buffer[10]=0x63;
update_buffer[11]=0x75;
}
}
Re: WIP: Wizard of Wor
Doors and teleports working, next, scores... but I need to make a little queuing mechanism and make the functions use it... still thinking... in the mean time, a bit of silliness:
Re: WIP: Wizard of Wor
Scoring now implemented. I had a bit of a trip trying to be a bit too clever with the algorithm,
https://www.youtube.com/watch?v=tsEfDJwiPNY
but it's there, and now I'm implementing the sprite structure, a simple array of 8 entries with 5 elements each (40 bytes) containing:
So, onward and upward. (and yes, this structure will change, i'm slowly adding what I need and then rewriting it if I need to tighten it up)
-Thom
https://www.youtube.com/watch?v=tsEfDJwiPNY
but it's there, and now I'm implementing the sprite structure, a simple array of 8 entries with 5 elements each (40 bytes) containing:
Code: Select all
/**
* 8 objects on screen, two players and 6 enemies.
*
* [0] - Sprite X position
* [1] - Sprite Y position
* [2] - Sprite Type - (worrior, burwor, thurwor, gorwor... Bit 7 means empty.)
* [3] - Dungeon X coordinate
* [4] - Dungeon Y coordinate
*/
-Thom
Re: WIP: Wizard of Wor
Needed more room in my sprite chr bank, so I used NESST's remove duplicates feature. Fantastic feature indeed..in fact, both YYCHR and NESST are fantastically indispensable tools, hats off to Shiru and YY.
Now, assembling metasprites is like a twisted jigsaw puzzle. yay.
Now, assembling metasprites is like a twisted jigsaw puzzle. yay.
Re: WIP: Wizard of Wor
Now status is slowing down a bit, as I slog through entering tables and data for animation and start implementing the frame loop for the main part of the game.
I am currently using a very naive vram update scheduler, interleaving parts of the vram to be updated across four different frames, as it isn't critical that all of these updates happen with hair-pin precision:
I will spread the radar update into 4 sections, so that I can have enough cycles to update the radar screen (really not much to do other than just read the dungeon x and y boxes for the 6 enemy state slots, and set the tile to use accordingly, but there are 10x6 tiles to update, so I have to slice it up a bit)
If I need something better, i'll write it.
I've gone through and implemented almost all of the sprites, save for the wizard, as I am needing to decode the bitplane format used on the Astrocade, to grab the blitter object for the wizard from the ROM, as I have tried to frame grab it, like the other character objects, and as it happens, the blitter object is munged a bit to blur it, making extraction of anything other than the one visible object on the character description page difficult. Worst case, I will just #@($#@ draw it based on the blurry (#@$#@ images.
I've also implemented the animation state tables which will be used to select which metasprites to use for a given state, and have enough to where I can do a naive first pass at filling in the OAM on each frame. I believe the game uses four frames for each animation state, with two of those frames duplicated to create a sort of slight cyclical pause that is visible at slower speeds...it certainly makes it easy to cycle through the frames in the state list, as I just have to AND it off.
I've also implemented the red/blue dungeon palette swap that is needed to differentiate certain states (worluk/wizard visible, or you're now in a Worlord dungeon), as well as the Double Score win color cycling.
Slowly but surely...
-Thom
I am currently using a very naive vram update scheduler, interleaving parts of the vram to be updated across four different frames, as it isn't critical that all of these updates happen with hair-pin precision:
Code: Select all
// VRAM update scheduler
a=frame_cnt&0x03;
switch (a)
{
case 0:
update_doors();
break;
case 1:
update_scores();
break;
case 2:
set_teleport(teleport_state);
break;
case 3:
break;
}
// End VRAM update scheduler
If I need something better, i'll write it.
I've gone through and implemented almost all of the sprites, save for the wizard, as I am needing to decode the bitplane format used on the Astrocade, to grab the blitter object for the wizard from the ROM, as I have tried to frame grab it, like the other character objects, and as it happens, the blitter object is munged a bit to blur it, making extraction of anything other than the one visible object on the character description page difficult. Worst case, I will just #@($#@ draw it based on the blurry (#@$#@ images.
I've also implemented the animation state tables which will be used to select which metasprites to use for a given state, and have enough to where I can do a naive first pass at filling in the OAM on each frame. I believe the game uses four frames for each animation state, with two of those frames duplicated to create a sort of slight cyclical pause that is visible at slower speeds...it certainly makes it easy to cycle through the frames in the state list, as I just have to AND it off.
I've also implemented the red/blue dungeon palette swap that is needed to differentiate certain states (worluk/wizard visible, or you're now in a Worlord dungeon), as well as the Double Score win color cycling.
Slowly but surely...
-Thom
Re: WIP: Wizard of Wor
Implemented first passes of initial enemy placement, and radar. (need to re-order radar bits a bit to make it easier to select the correct tile for display.)
Radar is implemented as background tiles, saw no need to waste perfectly good sprites for it.
-Thom
Radar is implemented as background tiles, saw no need to waste perfectly good sprites for it.
-Thom
Re: WIP: Wizard of Wor
Looks like enemy collision detection needs a bit of work (I need to add a bit of fudging to allow the monsters to go into the box, as well as some bias correction, but if you look at the radar output, you'll see the collision code is acting as expected)
https://www.youtube.com/watch?v=QV_Tf5Mdmf4
-Thom
https://www.youtube.com/watch?v=QV_Tf5Mdmf4
-Thom
Re: WIP: Wizard of Wor
A little refactoring of my macros to make them saner, and the collision code became simpler to write, and the bugs went poof:
and the relevant bit of collision code:
This implements a very simple "patrol my space" AI, which I may want to change to alter direction when there are no walls in a box, or something similar.
Have posted a quick build for others to enjoy looking @, code is on github, and am continuing on.
-Thom
Code: Select all
#define STAMP_NUM_FIELDS 8 // Number of fields in each stamp slot
#define STAMP_NUM_SLOTS 8 // Number of slots in stamp structure
#define STAMP_CENTER_BIAS_X 12 // Offset to apply to box multiply to center sprite (X)
#define STAMP_CENTER_BIAS_Y 10 // Offset to apply to box multiply to center sprite (Y)
#define STAMP_NUM(x) (x*STAMP_NUM_FIELDS) // Stamp Number
#define STAMP_X(x) (STAMP_NUM(x)+0) // Stamp Field: X pixel position
#define STAMP_Y(x) (STAMP_NUM(x)+1) // Stamp Field: Y pixel position
#define STAMP_TYPE(x) (STAMP_NUM(x)+2) // Stamp Field: Type
#define STAMP_STATE(x) (STAMP_NUM(x)+3) // Stamp Field: state (which frames to use).
#define STAMP_FRAME(x) (STAMP_NUM(x)+4) // Stamp Field: Current frame
#define STAMP_DELAY(x) (STAMP_NUM(x)+5) // Stamp Field: Delay
#define STAMP_FINE_X(x) (STAMP_NUM(x)+6) // Stamp Field: Fine X offset relative to box (not used, will be repurposed)
#define STAMP_FINE_Y(x) (STAMP_NUM(x)+7) // Stamp Field: Fine Y offset relative to box (not used, will be repurposed)
#define PIXEL_BOX_X(x) ((x*24)+STAMP_CENTER_BIAS_X) // Convert Box X coordinates to pixels
#define PIXEL_BOX_Y(x) ((x*24)+STAMP_CENTER_BIAS_Y) // Convert Box Y coordinates to pixels
#define BOX_PIXEL_X(x) (div24(x-STAMP_CENTER_BIAS_X)) // Convert Stamp X coordinates to Box X
#define BOX_PIXEL_Y(y) (div24(x-STAMP_CENTER_BIAS_Y)) // Convert Stamp Y coordinates to Box Y
Code: Select all
/**
* move_monsters()
* Move the monsters
*/
void move_monsters()
{
for (i=2;i<STAMP_NUM_SLOTS;++i)
{
// Get the box in a&b
a=div24(stamps[STAMP_X(i)]-8);
b=div24(stamps[STAMP_Y(i)]-8);
c=(b*10)+a; // C is now the box #
d=dungeon[c];
if (stamps[STAMP_STATE(i)] == STATE_MONSTER_RIGHT)
{
if (d&1<<4)
{
if (stamps[STAMP_X(i)]==PIXEL_BOX_X(a))
{
stamps[STAMP_STATE(i)]=rand8()&0x03;
}
else
{
stamps[STAMP_X(i)]++;
}
}
else
{
stamps[STAMP_X(i)]++;
}
}
else if (stamps[STAMP_STATE(i)] == STATE_MONSTER_LEFT)
{
if (d&1<<6)
{
if (stamps[STAMP_X(i)]==PIXEL_BOX_X(a))
{
stamps[STAMP_STATE(i)]=rand8()&0x03;
}
else
{
stamps[STAMP_X(i)]--;
}
}
else
{
stamps[STAMP_X(i)]--;
}
}
else if (stamps[STAMP_STATE(i)] == STATE_MONSTER_UP)
{
if (d&1<<7)
{
if (stamps[STAMP_Y(i)]==PIXEL_BOX_Y(b))
{
stamps[STAMP_STATE(i)]=rand8()&0x03;
}
else
{
stamps[STAMP_Y(i)]--;
}
}
else
{
stamps[STAMP_Y(i)]--;
}
}
else if (stamps[STAMP_STATE(i)] == STATE_MONSTER_DOWN)
{
if (d&1<<5)
{
if (stamps[STAMP_Y(i)]==PIXEL_BOX_Y(b))
{
stamps[STAMP_STATE(i)]=rand8()&0x03;
}
else
{
stamps[STAMP_Y(i)]++;
}
}
else
{
stamps[STAMP_Y(i)]++;
}
}
}
}
Have posted a quick build for others to enjoy looking @, code is on github, and am continuing on.
-Thom