梦开始的地方——FC游戏开发指南(3)引擎循环与手柄输入
(本系列是一个回归电子游戏原点的特别系列,作者 @马遥 )
本节介绍FC的游戏主循环编写方法。
首先说明,任何实时刷新画面的电子游戏,都有一个亘古不变的循环流程:
无论什么游戏引擎,UE也好,Unity也好,都需要按上图进行每一帧的流程。
咱们的FC当然也不例外,也必须符合上图的原则。
1、FC主循环
有趣的地方在于——FC本身没有引擎层,就像一张白纸没有任何要求,我们可以随意搞一个while循环来代替引擎循环。返璞归真的感觉。
我改动了示例工程《移动》并加了一些注释。
// FC的C语言开发基础包,包含cprintf等功能
#include <conio.h>
// 程序代码会以极快速度执行
// 理论速度:电视机每扫描一行,执行一条CPU指令
// 程序入口
void main()
// 局部变量
unsigned char var1 = 1; // 取值范围0~255
int t = 1;
// ★ 游戏主循环
while (1)
// cprintf的写法直接修改游戏背景,实际游戏开发极少这样写
// 本程序为了教学+演示才这么做
gotoxy(var1, 10);
cprintf("12");
gotoxy(var1, 11);
cprintf("AB");
var1 = var1 + 1;
t = 1;
// 简易延迟方法
while (t++ < 800);
// 程序不需要退出,也无法暂停,如果没事干死循环就可以
while (1);
}
对速度很快的现代电脑来说,在循环中应该有某种时间控制机制。
FC的CPU很慢,恰好是每扫描一行执行一条指令,所以可以用更简单的方式控制游戏速度,比如写个空循环。
有了引擎循环,循环中要考虑几件事: 输入,逻辑,渲染,帧时间控制 。
输入需要研究FC手柄原理,逻辑可以直接写C语言代码,渲染是半自动的暂时不用考虑,时间控制也需要研究FC计时机制。
好在不用那么麻烦,“FC游戏大师”提供的项目里已经写好了大部分基础功能。
2、引入常用代码库
“FC游戏大师”里的《迷宫不迷》示例工程已经做出了很好的示范,所有基本功能全写到了"mylib.c"源文件中,将该工程的"mylib.c"文件拷贝到移动工程里就能直接用了。
拷贝到工程目录以后,在"移动.c"源码中包含它,之前的<conio.h>不需要了。
#include "mylib.c"
3、手柄输入逻辑
接下来演示手柄输入怎么写。
#include "mylib.c"
// 手柄输入变量,变量名不要改,与mylib.c保持一致
u8 key, okey; // 当前输入值,上一帧输入值
u8 x, y;
// 程序入口
void main()
int t = 0;
// 初始化
key = 0;
okey = 0;
x = 1;
y = 1;
// ★ 主游戏循环
while (1)
key = read_joy(); // 读取手柄1的输入值
if (presskey(button_LEFT)) x--;
if (presskey(button_RIGHT)) x++;
if (downkey(button_UP)) y--;
if (downkey(button_DOWN)) y++;
// cprintf的写法直接修改游戏背景,实际游戏开发极少这样写
// 本程序为了教学+演示才这么做
gotoxy(x, y);
cprintf("12");
gotoxy(x, y+1);
cprintf("AB");
// 保存上一帧输入值,固定写法
okey = key;
// CPU延迟一帧的正规写法,依据PPU状态进行延迟
wait_VBlank();
运行后可以按方向键移动文字,但是由于旧的位置不清除,试验效果如图:
代码重点:
- u8就是unsigned char,8位,取值范围0~255。
- 输入相关的两个变量名为key和okey,它们的用法都是固定的。
- 上面的代码中,X轴是连续输入,Y轴是按一下动一下,看看写法有什么不同。(和Unity写法是类似的)
- 每一帧结尾okey被赋值,如果okey忘了赋值那么判断downkey的功能会出问题。
- 相对正确的延迟一帧方法:wait_VBlank()。 它一样是让CPU进行死循环,但是当PPU状态变化(代表一帧结束)就跳出循环。
这样,一个麻雀虽小,五脏俱全的游戏循环就写好了。
当然,一般的游戏会有一个开始界面、一个GameOver界面,一般做成独立场景。
有多个场景,想切换场景怎么办呢?最简单的办法:再加一个while循环就行,游戏主循环未必只能有一个,怎么方便怎么写。
只不过切换场景的时候要关闭显示、切换背景。这块的内容以后再说。
4、补充:二进制与手柄输入
FC手柄有8个按钮:上下左右,选择暂停,AB。为什么上面只用一个u8 key 变量就够了呢?
因为FC的硬件设计就是用8个bit,也就是8位来表示这8个按键的,正好用一个u8变量装下。判断某个键要用二进制技巧。如果想深入了解,可以看mylib.c中的具体写法:
//手柄相关:
#define joystick_1 0x4016 //两个手柄的端口地址
#define joystick_2 0x4017
// 读取手柄状态,参数_joystick为手柄端口地址,
u8 read_joystick(u16 _joystick)
u8 n=8,joy_state=0;
address(_joystick)=01; //先对端口_joystick写01
address(_joystick)=00; //再对_joystick写00,代表开始读取
// 循环8次读取端口状态
while(n){