4 个版本

使用旧的 Rust 2015

0.2.2 2019年6月7日
0.2.1 2019年6月6日
0.2.0 2019年6月6日
0.1.0 2019年6月4日

#103 in 模拟器

MIT 许可证

110KB
3K SLoC

crates.io docs.rs

ludus

Ludus 是一个提供 NES 模拟器核心逻辑的库。与其他库不同,Ludus 不是一个独立的应用程序。相反,Ludus 是一个设计来易于嵌入到应用程序中的库。有关使用 Ludus 创建 GUI 模拟器的示例,请参阅 ludus-emu

无头模式的优点是 Ludus 可以轻松地用于独立应用程序之外的环境。例如,这个库可以用来训练玩 NES 游戏的智能体,或者生成 NES 游戏的截图,或者生成 RAM 的图等。通过无头模式,Ludus 可以以任何你想要的方式在你的模拟器应用程序中使用。

特性

  • CPU 模拟
  • 视频模拟
  • 音频模拟
  • .ines 文件解析 ROM 数据。
  • 映射器 0、1 和 2,因此可以玩许多常见游戏。

用法

让我们首先导入 Ludus 中使用的常用类型

use ludus::*;

主要的模拟器类型是 Console。在我们创建一个 Console 之前,我们需要一个卡带来播放。我们可以通过读取一个 .ines 文件来创建一个 Cart 类型。

let bytes: &[u8] = read_ines_bytes();
let cart = Cart::from_bytes(bytes).unwrap();

如果 ROM 数据不有效,创建卡带将自然失败。

一旦我们有了卡带,我们就可以创建一个控制台来播放这个卡带

let console = Console::new(cart, sample_rate);

创建控制台需要卡带以及音频处理单元(APU)的采样率。通常,如果你使用允许你向设备播放音频的库,你应该可以访问这个采样率。

在任何时候,我们都可以这样重置控制台

console.reset();

我们还可以使用 ButtonState 结构更新按钮的状态

let mut buttons = ButtonState::default();
buttons.a = true;
console.update_controller(buttons);

现在,为了真正开始进行模拟,我们需要向前 step 控制台。然而,每次我们推进模拟时,APU 可能会生成音频样本,PPU 可能会生成视频帧。为了处理这些,我们需要提供一个可以处理音频样本的设备,以及一个可以处理视频帧的设备。

对于处理音频,我们有 AudioDevice 特性

trait AudioDevice {
    fn push_sample(&mut self, sample: f32)
}

实现此特质的设备应能够接收一个音频样本,范围为 [-1, 1] 并使用该信息处理音频内容。传递给控制台的样本率决定了APU生成样本并将其推送到此设备的频率。

处理视频,我们有 VideoDevice 特质

trait VideoDevice {
    fn blit_pixels(&mut self, pixels: &PixelBuffer)
}

该设备应能够接收一帧像素,并在屏幕上显示
或您可能想要处理视频数据的任何其他方式。像素缓冲区包含256x240 ARGB像素,按行主序格式排列。

如果您不想处理音频或视频,可以简单创建一个空的结构体,对这两个特质都不做任何操作

#[derive(Clone, Copy)]
pub struct NullDevice;

impl AudioDevice for NullDevice {
    fn push_sample(&mut self, sample: f32) {
    }
}

impl VideoDevice for NullDevice {
    fn blit_pixels(&mut self, pixels: &PixelBuffer) {
    }
}

现在我们已经设置了设备,我们可以开始进行一些仿真。

前进控制台的最简单方法是 step

pub fn step<'a, A, V>(&'a mut self, audio: &mut A, video: &mut V) -> i32 where
    A: AudioDevice,
    V: VideoDevice,

这将使 Console 前进一个CPU周期。这仅在您想要非常缓慢地看到事物发展时才有用。如果您是某种自动化程序,例如机器人,请使用 step_frame,因为大多数游戏甚至不会每帧查看一次输入。

下一个方法是 step_micros

pub fn step_micros<'a, A, V>(
    &'a mut self,
    audio: &mut A,
    video: &mut V,
    micros: u32
) where
    A: AudioDevice,
    V: VideoDevice, 

此方法将使模拟器前进一定数量的微秒。如果您在实现自己的GUI并希望在某种游戏循环中前进模拟器,这是一个非常有用的方法。

此类循环的一个示例可能如下所示

let mut old = Instant::now();
loop {
    let now = Instant::now();
    let duration = now.duration_since(old);
    old = now;
    console.step_micros(audio, video, duration.subsec_micros());
}

最后一个方法允许您通过完整帧前进模拟器

pub fn step_frame<'a, A, V>(&'a mut self, audio: &mut A, video: &mut V) where
    A: AudioDevice,
    V: VideoDevice,

这对于训练机器人很有用,因为游戏每帧只会查看一次输入。因此,您会为该帧设置输入,然后前进一次帧,然后设置输入等。请注意,这与其他方法不同,它不是基于 时间,而是等待ppu到达当前帧的末尾。

资源

我非常依赖这个非常出色的开源模拟器: https://github.com/fogleman/nes.

这个页面 https://wiki.nesdev.com/w/index.php/NES_reference_guide 一直是我工作的圣经;感谢多年来以某种方式做出贡献的许多人。

没有运行时依赖