代码之家  ›  专栏  ›  技术社区  ›  Jeffrey Kern

模仿老派精灵闪烁(理论和概念)

  •  20
  • Jeffrey Kern  · 技术社区  · 15 年前

    我正在尝试开发一款老式的Nes风格的电子游戏,它有精灵闪烁和图形减速。我一直在想我应该用什么样的逻辑来实现这种效果。

    如果我想选择传统的NES风格,我必须考虑以下限制:

    • 一次屏幕上的精灵不超过64个
    • 每个扫描线或Y轴上的每一行不超过8个精灵
    • 如果屏幕上有太多的动作,系统会将图像冻结为一帧,以便处理器跟上动作。

    根据我读到的,如果屏幕上有64个以上的精灵,开发人员只会绘制高优先级精灵,而忽略低优先级精灵。它们也可以交替使用,在奇数帧的相反帧上绘制每个偶数精灵。

    扫描线问题很有趣。在我的测试中,不可能像NES那样通过逐像素绘制精灵来在Xbox 360 XNA框架上获得良好的速度。这就是为什么在老式的游戏中,如果一条线上的精灵太多,如果它们被切成两半,就会出现一些精灵。对于本项目的所有目的,我都将扫描线设为8像素高,并通过它们的Y定位将精灵按扫描线分组。

    为了澄清这一点,我将以8x8像素而不是1x1像素的批量绘制精灵到屏幕上。

    所以,冷静下来,我需要想出一个解决办法……

    • 64个精灵同时出现在屏幕上
    • 每个“扫描线”8个精灵
    • 可以根据优先级绘制精灵
    • 每帧可以在精灵之间切换
    • 模拟减速

    这是我目前的理论

    首先,我提出的一个基本概念是解决sprite优先级问题。假设值在0-255之间(0为低),我可以分配精灵优先级,例如:

    • 0到63低
    • 63至127为中等
    • 128到191高
    • 最大192至255

    在我的数据文件中,我可以将每个sprite指定为特定的优先级。当创建父对象时,sprite会随机地在指定的范围内分配一个数字。然后,我将按照从高到低的顺序绘制精灵,最终目标是绘制每个精灵。

    现在, 当一个精灵被画进一个框架时 然后,我将在它的初始优先级级别内随机生成一个新的优先级值。然而, 如果一个雪碧没有画在一个框架里 ,我可以在当前优先级中添加32。例如,如果系统只能将精灵绘制到135的优先级,那么初始优先级为45的精灵可以在3帧未绘制之后绘制(45+32+32+32=141)。

    理论上,这将允许精灵交替帧,允许优先级,并将精灵限制为每屏64个。

    现在,有趣的问题是,如何将sprites限制为每个扫描线只有8个?

    我在想,如果我将sprites从高优先级排序到低优先级,那么循环迭代直到我画出64个sprites。不过,我不应该只考虑列表中的前64个精灵。

    在绘制每个sprite之前,我可以通过计数器变量检查在其各自的扫描线中绘制了多少个sprite。例如:

    • 0到7之间的Y值属于扫描线0,扫描线计数[0]=0
    • 8到15之间的Y值属于扫描线1,扫描线计数[1]=0
    • 等。

    我可以为绘制的每个帧重置每个扫描线的值。在浏览sprite列表的同时,如果sprite在scanline中绘制,则在scanline的相应计数器中添加1。如果它等于8,不要画出那个精灵,然后用下一个最低优先级的精灵。

    减速

    我要做的最后一件事就是模仿减速。我最初的想法是,如果我每帧绘制64个精灵,并且还有更多需要绘制的精灵,我可以将渲染暂停16毫秒左右。然而,在我玩过的NES游戏中,如果没有任何精灵闪烁,有时会出现减速,而即使有精灵闪烁,游戏也会运行得很漂亮。

    也许给每个在屏幕上使用精灵的对象一个值(如上面的优先级值),如果所有精灵对象的组合值超过一个阈值,会导致减速吗?

    总之…

    我写的每件事听起来都是合法的,可以工作吗,还是只是个白日梦?用我的游戏编程理论,你们都能想到哪些改进?

    2 回复  |  直到 15 年前
        1
  •  3
  •   Dan McGrath    15 年前

    首先,我喜欢这个主意。当你做一个复古游戏时,如果你忽略了复杂度的限制,它就不一样了。通过编写一个框架 强制执行 是一个 伟大的 想法。

    我可以说,如果你把这个引擎抽象成一个开源的“复古游戏”图形引擎,那就太酷了,我一定会加入进来的!

    • 关于你最优先的精灵,也许是 Priority Queue 可以简化这一部分,因为您只需拉出64个最高优先级的精灵。

    • 在减速的情况下,也许在发动机里你可以 limitValue . 每个雪碧都是合适的 limitUse . 把所有的东西都加起来 限量使用 变量,如果低于 极限值 不减速。现在,为了 slowDelay = f(limitUseTotal - limitValue) 其中f是一个函数,它将多余的sprite值转换为计算出的减速值。这是你建议的一般想法还是我误解了?

        2
  •  3
  •   strager    15 年前

    对于优先级,只需使用z索引。这就是GBA所做的,至少;优先级0被放在最前面,因此优先级最低。所以,从高到低渲染,如果渲染的精灵太多,请停止。

    sprite的优先级由其在sprite表中的索引决定。GBA有128个条目[0127]按顺序排列为四个hwords(每个16字节)。对于XNA,您可能会使用 List<Sprite> 或者类似的。也许是 Sprite[256] 把事情简化一点。

    限制sprite渲染自然就来自于这种机制。因为渲染的范围是从255到0,所以可以跟踪所接触到的扫描线。所以基本上:

    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:
    }
    

    我不太清楚你所说的减速是什么意思,所以我不能回答你的问题。对不起的。

    希望这有帮助。