dustpg / BlogFM

Blog for Me
MIT License
156 stars 23 forks source link

Re: 从零开始的红白机模拟 - [12]基础输入 #16

Open dustpg opened 6 years ago

dustpg commented 6 years ago

STEP5: 手柄输入

为了让这个测试ROM能够通过, 我们加入手柄输入试试吧.

一般来说最少得有1个手柄, 不然只能看着马大叔被板栗弄死.

最高可支持4个手柄输入, 比如这个款游戏: 油管 - 4 Player Madness - RC PRO-AM 2 (NES)

但是是通过特殊装置接驳上去的.

标准输入

先支持普通双手柄吧, 最后完善测试再说其他情况.

Controller port registers

根据文档, NES(欧美版) 和 Famicom(日版) 存在区别:

地址 比特位 功能
N: $4016 (写) ----, ---A 为全手柄写入选通(strobe)
N: $4016 (写) ----, -CBA 为扩展端口写入数据
N: $4016 (读) ---4, 3--0 从手柄端口#1读到的数据
N: $4016 (读) ---4, 3210 从扩展端口读到的数据
N: $4017 (读) ---4, 3--0 从手柄端口#2读到的数据
N: $4017 (读) ---4, 3210 从扩展端口读到的数据
F: $4016 (写) ----, ---A 为全手柄写入选通(strobe)
F: $4016 (写) ----, -CBA 为扩展端口写入数据
F: $4016 (读) ----, -M-0 从手柄端口#1读到的数据, 以及手柄端口2读取到的麦克风(M)数据
F: $4016 (读) ----, --1- 从扩展端口读到的数据
F: $4017 (读) ----, ---0 从手柄端口#2读到的数据
F: $4017 (读) ---4, 3210 从扩展端口读到的数据

同4人手柄, 扩展端口就直接...所以实现基本输入就行, 反正绝大数游戏都是支持的 —— 也就是最低位.

strobe(选通)乍一看! 还以为是string.h的一个函数... 对于这里输入来说, 是一种重置手段:

  1. 为0时表示按钮状态会被像字节流一样被连续读取.
  2. 为一表示重置按钮读取序列状态, 会被一直'重置'直到被设为0

也就是说读取按钮状态应该这样:

读取大概就是这样:

case 0x16:
    // 手柄端口#1
    data = (famicom->button_states+0)[famicom->button_index_1 & famicom->button_index_mask];
    ++famicom->button_index_1;
    break;
case 0x17:
    // 手柄端口#2
    data = (famicom->button_states+8)[famicom->button_index_2 & famicom->button_index_mask];
    ++famicom->button_index_2;
    break;

写入大概这样:

case 0x16:
    // 手柄端口
    famicom->button_index_mask = (data & 1) ? 0x0 : 0x7;
    if (data & 1) {
        famicom->button_index_1 = 0;
        famicom->button_index_2 = 0;
    }
    break;

黑匣子

之前提到用户输入的黑匣子函数, 现在详细说说.

void user_input(int index, unsigned char data);

用户输入时调用, index是0-15, 前面8个是手柄1, 后面8个是手柄2. 不过, 我记得二号手柄没有select/start键? 键盘映射如下:

static const unsigned sc_key_map[16] = {
    // A, B, Select, Start, Up, Down, Left, Right
    'J', 'K', 'U', 'I', 'W', 'S', 'A', 'D',
    // A, B, Select, Start, Up, Down, Left, Right
    VK_NUMPAD2, VK_NUMPAD3, VK_NUMPAD5, VK_NUMPAD6, 
    VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT,
};

输出效果

first-pass

按下'I'键(Start)就能测试了. 历时这么久, 终于把基础指令测试通过了! 即便没有实现BRK和CLI!

同时, 也宣告着这个ROM的使命完成了. 感谢ROM作者kevtris.

这一节很简单! 主要当然只是实现了基本的输入而已. 项目地址Github-StepFC-Step5

作业

REF

buzai commented 4 years ago

为什么我只能按一次,按完了就不能继续按了呢

dustpg commented 4 years ago

@buzai

为什么我只能按一次,按完了就不能继续按了呢

这说法太笼统了,不过猜测是没有复位。简单地说就是按下去是 true, 弹起来是 false, 可以从这个方法着手检查

buzai commented 4 years ago

@dustpg 我是在osx,重新用 sdl2 实现了图像部分,其他部分都是直接复制的你的,但是我用clion 通过cmake build的

监控到按钮事件就调用的 user_input 函数,打印也有,不懂为啥第二次按下就不动了。 if (event.key.keysym.sym == SDLK_UP) { printf("SDLK_UP\n"); user_input(4, 1); }

dustpg commented 4 years ago

@buzai

if (event.key.keysym.sym == SDLK_UP) { printf("SDLK_UP\n"); user_input(4, 1); }

这个只有1啊, 项目中默认 user_input(index, msg == WM_KEYDOWN);, windows中WM_KEYDOWN是按下, WM_KEYUP是弹起,

这里来看 应该user_input(index, event.type == SDL_KEYDOWN);

buzai commented 4 years ago

@dustpg 弄好了,谢谢🙏,果然是这里的问题。