在简单游戏中设置计时器(附帧规则)

Lobsters Hottest 工具

摘要

一篇博客文章,介绍了PICO-8游戏中使用单张表和循环的简单计时器系统,并附带说明了类似计时器在《超级马力欧兄弟》中如何导致了速通中的帧规则。

<p><a href="https://lobste.rs/s/1efcss/setting_timers_simple_games_feat_frame">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/07/29 17:57

# 在简单游戏中设置计时器(附:帧规则) 来源:https://lynn.github.io/blog/pico-timers/ 2026年7月24日 最近我一直在阅读《超级马力欧兄弟》的反汇编源码(https://6502disassembly.com/nes-smb/SuperMarioBros.html),并且了解到,在它的代码中,未来的事件调度方式与我用 Lua 制作快速游戏 Jam 作品(比如 o moku e mun(https://rose.systems/omokuemun/))时的方式非常相似。 这简单得几乎算不上一个系统!但我喜欢它既容易理解又灵活的特点。我将用 Lua 来描述它,用于 PICO-8 或 Picotron 游戏。只需两行代码!读吧,然后尽情流泪吧。 ## PICO-8 中的计时器 首先,定义一个表 `T`。我想添加一点 Lua 魔法,使得 `T.foo` 在未设置时返回 0。这有点奇怪,但在游戏 Jam 中可以节省宝贵的时间,不用来回跳到文件顶部添加 `T.foo = 0` 来防止 `nil` 错误。代码如下: ```lua local T = setmetatable({}, {__index = function() return 0 end}) ``` 然后,在 `_update` 函数(每帧执行一次)中,将所有计时器减一: ```lua for k,v in pairs(T) do T[k]=max(0,v-1) end ``` 就是这样!在任何时候,`T.foo` 都表示 `foo` 计时器剩余的帧数。 ### 1. 让某些事情持续发生 假设你想让玩家发光一秒钟,也就是 60 帧。 你只需写 `T.glow = 60` 来启动计时器,然后在其他地方检查 `T.glow > 0` 来决定是否绘制发光精灵。 要停止发光,写 `T.glow = 0`。要延长发光时间,再次触发 `T.glow = 60`。简单! ### 2. 让某些事情稍后发生 一只青蛙在左边空白处打盹。假设你想在两秒后安排一个 *嘀!* 音效。 你设置 `T.blip = 120` 来启动计时器,然后在更新函数中写:`if (T.blip == 1) sfx(2)`。 要取消计时器,只需设置 `T.blip = 0`。 你也可以写像 `if (T.blip % 10 == 1) sfx(2)` 这样的代码,以便在计时器运行时重复发出嘀声。 ### 3. 非全局计时器 你也可以拥有多个 `T`。也许你的 `Enemy` 对象各自都有 `self.T`。只需确保你的 `Enemy:update` 将表中的所有项都减一即可。 ## 超级马力欧兄弟中的计时器 现在,在编写 NES 游戏时,没有花哨的键值对表。所有计时器都连续存储在 RAM 中,然后由这个循环(https://6502disassembly.com/nes-smb/SuperMarioBros.html#SymDecTimersLoop)统一递减。 每个计时器只能保存一个字节的值,而 NES 以 60 fps 运行,因此我们无法通过这种方式设置很长时间的计时器。`0xff` 帧只有 4.25 秒。为了解决这个问题,大约一半的计时器被编程为只有在一种称为 *IntervalTimerControl*(在反汇编中这样称呼)的“上级计时器”指示时才会递减。 翻译成伪 Lua 代码,就像这样: ```lua itc = 0 -- 每帧调用 function DecTimers() last_timer = 20 itc = itc - 1 if itc < 0 then itc = 20 -- 20,19,18...0 = 21 帧时钟 last_timer = 35 end -- 递减这些计时器! for i = 0, last_timer do if timers[i] > 0 then timers[i] = timers[i] - 1 end end end ``` 大多数时候,`last_timer` 是 20,因此这个函数只递减“快速”计时器 `timer[0]` 到 `timer[20]`。但每 21 帧,*IntervalTimerControl* 到期,此时*所有*计时器 `timer[0]` 到 `timer[35]` 都会被递减。 特别地,*IntervalTimerControl* 在每个关卡开始时重置,而一个“慢速”计时器用于控制关卡结束时的动画。这就是超级马力欧兄弟速通中著名的帧规则(https://en.wikipedia.org/wiki/Super_Mario_Bros._speedrunning#Frame_rules)的起源!你的游戏操作无法影响 *IntervalTimerControl* 的值,而关卡结束动画计时器与之同步,因此你通常只能以 21 帧的增量来提升关卡时间。

相似文章

在PICO-8上模拟进化

Hacker News Top

一篇关于在PICO-8幻想游戏机上模拟进化过程的文章,可能展示了一个创意编码项目或教育工具。

基础振荡器的快速合成

Lobsters Hottest

一篇技术博客文章,详细介绍了面向GBA和rp2040的快速整数振荡器实现,涵盖锯齿波、方波、三角波和脉冲波,重点在于速度而非减少混叠。

The Art of CHIP-8

Lobsters Hottest

A deep-dive technical article exploring the CHIP-8 programming language, its instruction set, interpreter quirks, and strategies for writing portable CHIP-8 programs.

用RP2350监视Z80

Hacker News Top

一篇博客文章,探讨如何使用带PIO的Raspberry Pi Pico RP2350来监视Z80微处理器的地址和数据总线,包括时序考虑和时钟速度限制。