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 模拟器
110KB
3K SLoC
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 一直是我工作的圣经;感谢多年来以某种方式做出贡献的许多人。