裂石响惊弦
这样岂不是所有老游戏都可以用这个思路做震动了
不是,GB的声音硬件比较特殊,模拟器作者使用了一种取巧的方法。类似的方法或许可以用到FC上,但是PS1这种的使用波形的机型肯定是不行的。
这段代码展示了 SameBoy 模拟器如何实现所有 Game Boy 游戏都支持震动功能的逻辑。具体来说,通过 `GB_handle_rumble` 函数处理震动效果。
### 代码分析
1. **检查回调函数是否存在**
```c
if (gb->rumble_callback) {
```
首先检查是否存在震动回调函数 `rumble_callback`。
2. **判断震动模式是否禁用**
```c
if (gb->rumble_mode == GB_RUMBLE_DISABLED) {
return;
}
```
如果震动模式被禁用,则直接返回,不执行震动逻辑。
3. **特定游戏卡带震动逻辑**
```c
if (gb->cartridge_type->has_rumble &&
(gb->cartridge_type->mbc_type != GB_TPP1 || (gb->rom[0x153] & 1))) {
if (gb->rumble_on_cycles + gb->rumble_off_cycles) {
gb->rumble_callback(gb, gb->rumble_on_cycles / (double)(gb->rumble_on_cycles + gb->rumble_off_cycles));
gb->rumble_on_cycles = gb->rumble_off_cycles = 0;
}
}
```
如果游戏卡带支持震动功能,并且满足特定条件(非 GB_TPP1 类型或 ROM 地址 0x153 处的第 0 位为 1),则根据 `rumble_on_cycles` 和 `rumble_off_cycles` 的比例计算震动强度并执行震动回调。
4. **所有游戏的震动逻辑**
```c
else if (gb->rumble_mode == GB_RUMBLE_ALL_GAMES) {
```
如果设置为所有游戏都支持震动,则通过音频寄存器的值来计算震动强度。
- **音量计算**
```c
unsigned volume = (gb->io_registers[GB_IO_NR50] & 7) + 1 + ((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1;
unsigned ch4_volume = volume * (!!(gb->io_registers[GB_IO_NR51] & 8) + !!(gb->io_registers[GB_IO_NR51] & 0x80));
unsigned ch1_volume = volume * (!!(gb->io_registers[GB_IO_NR51] & 1) + !!(gb->io_registers[GB_IO_NR51] & 0x10));
```
计算总音量和第 4 声道(噪声通道)及第 1 声道的音量。
- **第 4 声道震动计算**
```c
unsigned ch4_divisor = (gb->io_registers[GB_IO_NR43] & 0x07) << 1;
if (!ch4_divisor) ch4_divisor = 1;
unsigned ch4_sample_length = (ch4_divisor << (gb->io_registers[GB_IO_NR43] >> 4)) - 1;
double ch4_rumble = (MIN(ch4_sample_length * (gb->apu.noise_channel.narrow? 8 : 1) , 4096) * ((signed) gb->apu.noise_channel.current_volume * gb->apu.noise_channel.current_volume * ch4_volume / 32.0 - 50) - 2048) / 2048.0;
ch4_rumble = MIN(ch4_rumble, 1.0);
ch4_rumble = MAX(ch4_rumble, 0.0);
```
根据噪声通道的相关寄存器值计算震动强度,并进行范围限制。
- **第 1 声道震动计算**
```c
double ch1_rumble = 0;
if ((gb->io_registers[GB_IO_NR10] & 0x7) && (gb->io_registers[GB_IO_NR10] & 0x70)) {
double sweep_speed = (gb->io_registers[GB_IO_NR10] & 7) / (double)((gb->io_registers[GB_IO_NR10] >> 4) & 7);
ch1_rumble = gb->apu.square_channels[GB_SQUARE_1].current_volume * ch1_volume / 32.0 * sweep_speed / 8.0 - 0.5;
ch1_rumble = MIN(ch1_rumble, 1.0);
ch1_rumble = MAX(ch1_rumble, 0.0);
}
```
根据方波通道(第 1 声道)的相关寄存器值计算震动强度,并进行范围限制。
- **检查通道是否激活**
```c
if (!gb->apu.is_active[GB_NOISE]) {
ch4_rumble = 0;
}
if (!gb->apu.is_active[GB_SQUARE_1]) {
ch1_rumble = 0;
}
```
如果噪声通道或方波通道未激活,则相应的震动强度设为 0。
- **执行震动回调**
```c
gb->rumble_callback(gb, MIN(MAX(ch1_rumble / 2 + ch4_rumble, 0.0), 1.0));
```
最终,执行震动回调函数,并将震动强度限制在 0 到 1 的范围内。
### 总结
通过上述逻辑,SameBoy 模拟器可以根据特定游戏卡带的震动支持情况,或通过音频寄存器的值为所有游戏模拟震动效果。