帧率与按键¶
更多指令 一节实现了 Chip8 的所有指令。但是遗留了定时器相关的逻辑。导致 俄罗斯方块 的程序卡死不动。下面将会增加定时器的处理逻辑。
帧率¶
Chip8 有两个定时器。delay timer 和 sound timer。它们两者的工作频率都是60HZ。即每 1/60 秒,如果当前的值大于0,就减少 1。对于 sound timer 而言,当其值大于 0, 则播放声音。
def ticker(self):
if self._delay_timer > 0:
self._delay_timer -= 1
if self._sound_timer > 0:
self._sound_timer -= 1
# pygame.mixer.music.play()
除了 定时器的频率之外,模拟器的CPU的时钟频率也需要配置。现在的处理器主频都很高。我的机器是 2.6 GHz 。因此运行一次 cpu.cycle 方法的时间几乎可以忽略不计。
为了控制帧率,让古老的 Chip8 在高速处理器上渲染正常一点。可以调整一下Chip8的时钟频率。通常 700Hz 的频率对大多数 Chip 都比较友好。新增两个常量
CLOCK_SPEED = 700 # cpu 主频 700Hz
TIMER_SPEED = 60 # 定时器频率 60Hz
主循环的方法,模拟 700 Hz的主频。可以直接 time.sleep(1/700) 就可以了。
def run(self):
cycles = 0
while True:
self.cpu.cycle()
self.keyboard.poll_event(self.cpu._keys_pressed_buf)
if self.cpu.draw_flag:
self.display.render(self.cpu.screen_buf)
self.cpu.draw_flag = False
cycles += 1
time.sleep(1 / CLOCK_SPEED) # 1 / 700
if cycles >= CLOCK_SPEED / TIMER_SPEED: # 每 60Hz ,定时器减少 1
cycles = 0
self.cpu.ticker()
按键控制¶
指令 EX9E EXA1 FX0A 都是需要对键盘按键响应的读操作。当用户运行模拟器的时候,输入的按键需要被主循环的 pygame 捕获。因此我们需要在 Keyboard 的poll_event 方法处理按键。
按键的结果就是更新 CPU的按键缓存属性key_pressed_buf
。这个过程好比键盘响应了按键之后,给CPU发中断信号,然后CPU 更新按键标记。
Keyboard 的方法修改如下
class Keyboard:
def poll_event(self, keys_pressed_buf: List[int]):
for event in pygame.event.get():
if event.type == pygame.KEYDOWN or event.type == pygame.KEYUP:
self.key_press(event, keys_pressed_buf)
elif event.type == pygame.QUIT:
pygame.quit()
sys.exit()
def key_press(self, event, keys_pressed_buf: List[int]):
if event.key in _KEYS:
key = _KEYS[event.key]
if event.type == pygame.KEYDOWN:
keys_pressed_buf[key] = 1
elif event.type == pygame.KEYUP:
keys_pressed_buf[key] = 0
增加声音¶
chip8 也支持声音,声音我们只需要使用pygame提供的原生功能。
总结¶
现代处理器都很快,相比之下,需要模拟 Chip8 比较低的主频。 键盘处理的原理就是监听键盘事件,将按键结果响应到 CPU 的键盘缓存结构里。
Well Done。 Chip8 模拟器完成了。可以使用更多的 rom 测试效果。