0b0000-写个 NES emulator
标签搜索

0b0000-写个 NES emulator

limit
2021-02-23 / 0 评论 / 84 阅读 / 正在检测是否收录...
温馨提示:
本文最后更新于2022年10月03日,已超过361天没有更新,若内容或图片失效,请留言反馈。

日常讲屁话

吃得饱,打算写个 NES emulator,语言使用 Swift(一边看文档一边学),目标是做个 macOS App,如果有人有想法,把它移植到 iOS 上也可以。macOS App 开发也是临时看文档学,代码质量估计不高,先这样吧。

NES:是 FC 主机欧美地区发售的称呼。1985年10月,NES 正式在西方国家推出,除了名称由 Family Computer 改为 Nintendo Entertainment System,造型设计也大幅改变。我们 80/90 后都玩过的小霸王就是内置了一个 NES,比较出名的游戏如超级马里奥,我们的大目标就是可以仿真运行一个超级马里奥初代。

设备结构

开发一个 NES emulator 之前,我们来过一遍 NES 的主要组成部分。基本上有这些模块

achitecture
  • CPU NES 的处理器是 MOS 6502 的衍生产品,譬如 Apple II 的处理器就是它这一系的,这是我在网上找到的一篇关于该处理器的文章 MOS Technology 6502 CPU。我读中学的时期,国内有一些低端学习机(譬如电子词典这类)用得也是这个 CPU,具体可以查一下,此处不赘述。它是一个 8 位的处理器,频率是 1.7897725MHz(通常是 NTSC 机型, PAL 机型下频率只有 1.773447MHz),中断模式有 RESETNMIIRQ
  • PPU 这个全称是 Picture Processing Unit,这玩意是用来处理图形的。它会把图像用 256 * 240 的分辨率输出(受限于 NTSC 上下各 8 行显示不了,其实只能显示 256 * 224),调色盘可显示 48 色 5 个灰阶(现在看起来好像蛮弱的),NES 中图像分 BackgroundSprite,通过组合两者来显示完整的图像。如果你尝试开发过 Game Boy 的小游戏, 你会发现这两个概念好眼熟,因为受限于那个年代游戏机平台的硬件机能,很多 2D 游戏机是通过类似方式处理图像的(SpriteWeb 前端应用或小游戏开发里也有相应的应用)。
  • RAM NESCPUPPU 都有 2KiB 大小的内存
  • APU 是一个音频处理器,全称 Audio Processing Unit,这个模块其实集成在 CPU 中, 所以它并不是独立的芯片,可以称它为 p(seudo)APU,它提供了五个通道用来处理音频,分别是两个矩形波通道,一个三角波通道, 一个噪声波通道(譬如爆炸声),一个音频采样通道(主要用于处理背景音)。
  • 卡带 由于 NES 没有操作系统,所以内容由卡带提供,每个卡带至少包含两个东西,一个是 CHR ROM 主要用于存储游戏图形的数据,另一个是 PRG ROM,主要是用来存储游戏的指令。现实中卡带插入到机器后,CHR ROM 直接连接到 PPU,PRG 连接到 CPU,NES 后续的机器还用卡带提供其他的扩展,不过目前我们先不太深究这块内容
  • 手柄 这就是个输入设备就不细讲了

其实这些就是我们的目标,我们要把这些模块都用代码描述出来,由于我们用得是高级语言,所以很多东西都变得简单起来了。

创建项目

现在来启个新项目,具体怎么创建 macOS 项目就不截图描述了,我就不用 SwiftUI 模板创建项目,因为我只想要显示一个窗口,输入相关也是通过键盘处理,界面上也没有太复杂的 UI,然后把 AppDelegate 类改一下

import Cocoa

@main
class AppDelegate: NSObject, NSApplicationDelegate {
  var window: NSWindow?

  func applicationDidFinishLaunching(_ aNotification: Notification) {
    window = NSApplication.shared.windows.first
    window?.title = "NES emulator"
    window?.setFrame(CGRect(x: 0, y: 0, width: 960, height: 544), display: true)
    window?.center()
  }

  func applicationWillTerminate(_ aNotification: Notification) {
  }

  func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
    if !flag {
      for a in sender.windows {
        if let w = a as NSWindow? {
          w.makeKeyAndOrderFront(self)
        }
      }
    }
    return true
  }
}

现在第一步已经处理好,后续水文主要专注写 emulator 的逻辑,再把仿真后的内容展示在窗口上。

0

评论 (0)

取消