views:

296

answers:

2

I'm trying to develop an oldschool NES-style video game, with sprite flickering and graphical slowdown. I've been thinking of what type of logic I should use to enable such effects.

I have to consider the following restrictions if I want to go old-school NES style:

  • No more than 64 sprites on the screen at a time
  • No more than 8 sprites per scanline, or for each line on the Y axis
  • If there is too much action going on the screen, the system freezes the image for a frame to let the processor catch up with the action

From what I've read up, if there were more than 64 sprites on the screen, the developer would only draw high-priority sprites while ignoring low-priority ones. They could also alternate, drawing each even numbered sprite on opposite frames from odd numbered ones.

The scanline issue is interesting. From my testing, it is impossible to get good speed on the XBOX 360 XNA framework by drawing sprites pixel-by-pixel, like the NES did. This is why in old-school games, if there were too many sprites on a single line, some would appear if they were cut in half. For all purposes for this project, I'm making scanlines be 8 pixels tall, and grouping the sprites together per scanline by their Y positioning.

To clarify, I'd be drawing sprites to the screen in batches of 8x8 pixels, not 1x1.

So, dumbed down I need to come up with a solution that....

  • 64 sprites on screen at once
  • 8 sprites per 'scanline'
  • Can draw sprites based on priority
  • Can alternate between sprites per frame
  • Emulate slowdown

Here is my current theory

First and foremost, a fundamental idea I came up with is addressing sprite priority. Assuming values between 0-255 (0 being low), I can assign sprites priority levels, for instance:

  • 0 to 63 being low
  • 63 to 127 being medium
  • 128 to 191 being high
  • 192 to 255 being maximum

Within my data files, I can assign each sprite to be a certain priority. When the parent object is created, the sprite would randomly get assigned a number between its designated range. I would then draw sprites in order from high to low, with the end goal of drawing every sprite.

Now, when a sprite gets drawn in a frame, I would then randomly generate it a new priority value within its initial priority level. However, if a sprite doesn't get drawn in a frame, I could add 32 to its current priority. For example, if the system can only draw sprites down to a priority level of 135, a sprite with an initial priority of 45 could then be drawn after 3 frames of not being drawn (45+32+32+32=141)

This would, in theory, allow sprites to alternate frames, allow priority levels, and limit sprites to 64 per screen.

Now, the interesting question is how do I limit sprites to only 8 per scanline?

I'm thinking that if I'm sorting the sprites high-priority to low-priority, iterate through the loop until I've hit 64 sprites drawn. However, I shouldn't just take the first 64 sprites in the list.

Before drawing each sprite, I could check to see how many sprites were drawn in it's respective scanline via counter variables . For example:

  • Y-values between 0 to 7 belong to Scanline 0, scanlineCount[0] = 0
  • Y-values between 8 to 15 belong to Scanline 1, scanlineCount[1] = 0
  • etc.

I could reset the values per scanline for every frame drawn. While going down the sprite list, add 1 to the scanline's respective counter if a sprite gets drawn in that scanline. If it equals 8, don't draw that sprite and go to the sprite with the next lowest priority.

SLOWDOWN

The last thing I need to do is emulate slowdown. My initial idea was that if I'm drawing 64 sprites per frame and there's still more sprites that need to be drawn, I could pause the rendering by 16ms or so. However, in the NES games I've played, sometimes there's slowdown if there's not any sprite flickering going on whereas the game moves beautifully even if there is some sprite flickering.

Perhaps give a value to each object that uses sprites on the screen (like the priority values above), and if the combined values of all objects w/ sprites surpass a threshold, introduce slowdown?

IN CONCLUSION...

Does everything I wrote actually sound legitimate and could work, or is it a pipe dream? What improvements can you all possibly think with this game programming theory of mine?

+2  A: 

Firstly, I love the idea. When making a retro game, it wouldn't be the same if you ignored the complexity limitations. By writing a framework that sort of enforced it is a great idea.

Can I just say, if you abstracted the engine as an open source "Retro Game" graphics engine, that would indeed be cool and I'd be sure to join in!

  • In regards to your highest priority sprites, maybe a Priority Queue would simplify this part in that you can just pull out the 64 highest priority sprites.

  • With the slow down, perhaps in the engine you could have a limitValue. Each sprite as an appropriate limitUse. Add up all the limitUse vars and if this is below limitValue, no slow down. Now, for slowDelay = f(limitUseTotal - limitValue) where f is a function that converts the excess amount of sprites into a calculated slow down value. Is this the general idea you are suggesting or have I misread it?

Dan McGrath
I'm guessing Priority Queue would basically be the same as me creating a custom sprite class and sorting it with a custom sorter? :) And for the slowdown, that is the idea right on the head. However, I personally would feel more comfortable capping it with a certain value, e.g. a delay no longer than 16ms. Cuz I could see a math error delaying the redraw cycle by 100ms or so haha. Thanks!
Jeffrey Kern
Yes, whatever you decide for your f(x) function could include an upper bound.
Dan McGrath
+3  A: 

For priority, just use the Z-index. That's what the GBA does, at least; priority 0 is rendered front-most thus has the weakest priority. So, render high to low, and if you've rendered too many sprites, stop.

A sprite's priority is determined by its index in the sprite table. The GBA had 128 entries [0,127] arranged as sequential four hwords (16 bytes each). For XNA, you'd probably use a List<Sprite> or similar instead. Maybe a Sprite[256] to simplify things a bit.

Limiting the sprite rendering falls naturally from this mechanism. Because you render from 255 to 0, you can keep track of what scanlines you have touched. So basically:

int[] numPixelsDrawnPerScanline = new int[Graphics.ScreenHeight];

foreach(Sprite sprite in SpriteTable.Reverse()) {
    int top = Math.Max(0, sprite.Y);
    int bottom = Math.Min(Graphics.ScreenHeight, sprite.Y + sprite.Height);

    // Could be rewritten to store number of pixels to draw per line, starting from the left.
    bool[] shouldDrawOnScanline = new bool[Graphics.ScreenHeight];

    for(int y = top; y < bottom; ++y) {
        bool shouldDraw = numPixelsDrawnPerScanline[y] + sprite.Width <= PixelsPerScanlineThreshold;

        shouldDrawOnScanline[y] = shouldDraw;

        if(shouldDraw) {
            numPixelsDrawnPerScanline[y] += sprite.Width;
        }
    }

    Graphics.Draw(sprite, shouldDrawOnScanline); // shouldDrawOnScanline defines clipping

    skipDrawingSprite:
}

I'm not quite sure what you mean by the slowdown, so I can't respond to that part of your question. Sorry.

Hope this helps.

strager
The GBA has an extra wrinkle regarding sprites with hardware rotation/scaling. Sprites that have hw scaling/rotation are more costly to render, so the hardware can render fewer of them. e.g. if you fill the screen by stretching 4 64x64 sprites (double size should cover 256x256 in theory), chances are the lower portion of the screen will not have its sprites drawn at all.
rq