CC65 开发FC/NES 游戏(三)使用摇杆
Contents
起因
这是 CC65 开发 FC/NES 游戏系列的第三篇:环境搭好了、Hello World 也能显示了,但游戏的核心是"响应玩家输入"。FC 的输入就是那两个手柄,这一篇解决"怎么把手柄按键读进程序"。
需求
- 读出 1P 手柄的按键状态,按什么键就在屏幕上显示对应的数值
- 弄清每个按键的数值编码、多键同按时数值怎么组合——这是下一篇"方向键控制精灵"的基础
技术实现原理
手柄的本质:一个 8 位移位寄存器
FC 主机通过 $4016/$4017 端口读手柄:手柄内部是 8 位移位寄存器,8 个键各占一位,先锁存再逐位移出。cc65 把这整个过程封装进 joy_read(),返回一个 8 位无符号整数——每一位代表一个按键,该位为 1 就是按下。
这就是下面数值表的由来:A 是 bit0(0x01)、B 是 bit1(0x02)……右是 bit7(0x80)。位掩码设计自然带来几个性质:
- 多键同按 = 按位或:A+B = 0x01|0x02 = 3;左+下 = 0x40|0x20 = 96(文末截图实测);
- 全部按下 = 0xFF = 255 = 二进制 11111111;
- 连发键(Turbo)就是电路按固定频率自动通断,读到的值在 0 和键值之间频闪。
为什么代码要记住上一次的按键值
主循环一秒跑成千上万次,按住一个键不放,joy_read() 每次返回的值都相同——不加判断直接打印,cprintf 会不停重绘同一个数字,屏幕闪烁。代码里 if(p1==pt1) continue; 的思路是边沿检测:值变了才处理,是裸机程序防"按一下响应一万次"的标准手法。
注意:这个"变化才响应"适合显示按键值;下一篇要方向键连续移动精灵,就不能跳过相同值了,得每帧(或每 N 帧)响应一次——边沿触发还是电平触发,按业务选。
打印数字前为什么要先打三个空格
cprintf 只往光标处写字符、不会清行:上次显示 3 位数、这次变 2 位数,最后一位会残留。所以先用三个空格覆盖,再写新值。
环境与编译
编译器:CC65
下载地址:https://cc65.github.io/
这次的代码来自cc65的samples目录下的hello.c 并做了修改和注释
通过查看源码可以看到里面支持的机器类型
|
|
通过 cmd 命令将 c 编译成 nes 文件。-t nes 指定目标机型——cc65 是多目标编译器,头文件、启动代码、运行库都按目标链接,目标写错(比如 c64)编出来的文件模拟器认不出:
|
|
|
|
通过这个demo我们可以获取到按键对应的数值
| 按键 | 二进制 | 十进制 | 16进制 |
|---|---|---|---|
| A | 00000001 | 1 | 0x01 |
| B | 00000010 | 2 | 0x02 |
| select | 00000100 | 4 | 0x04 |
| start | 00001000 | 8 | 0x08 |
| 上 | 00010000 | 16 | 0x10 |
| 下 | 00100000 | 32 | 0x20 |
| 左 | 01000000 | 64 | 0x40 |
| 右 | 10000000 | 128 | 0x80 |
按键数值怎么来的(位掩码)
其实这个按键的取值是从手柄端口地址读取的一个 8 位值,每个键占一位。比如右这个值最大对应的:
- 十进制 128
- 16进制 0x80
- 二进制 10000000
同理左的二进制是 01000000;如果全按了就会显示 11111111(=255)。
当多个按键一起按的时候相当于按位或(累加),比如 AB 一起按输出的就是 3;连发 A 和 B 就是这个数字在 0 和对应的 1、2 两个数字之间频闪。
截图是我用qq的 ctrl+alt+a 同时按着下(s) 相当于同时按了 左和下 显示的就是 96(0x40 + 0x20)

踩坑记录与注意事项
- joy_install 忘了调用:
joy_read()会一直返回 0,读不到任何键——main 里第一件事必须是joy_install(joy_static_stddrv); - 组合键判断用位与,不要用等于:
if(p1 == 12)只在"恰好 start+select 同时按"时成立,多按一个键就失效;工程写法是if(p1 & JOY_START),demo 里的==12只是退出演示的偷懒写法(start 8 + select 4 = 12); - 数字残留:cprintf 不清行,覆盖打印(先打空格);
- 边沿/电平触发按业务选:显示按键值要"变化才打印"防闪烁;连续移动精灵要每帧响应——这是本篇和(四)篇最关键的区别。
Author 软件开发大郭
LastMod 2022-01-13