添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
梦开始的地方——FC游戏开发指南(3)引擎循环与手柄输入

梦开始的地方——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();

运行后可以按方向键移动文字,但是由于旧的位置不清除,试验效果如图:

代码重点:

  1. u8就是unsigned char,8位,取值范围0~255。
  2. 输入相关的两个变量名为key和okey,它们的用法都是固定的。
  3. 上面的代码中,X轴是连续输入,Y轴是按一下动一下,看看写法有什么不同。(和Unity写法是类似的)
  4. 每一帧结尾okey被赋值,如果okey忘了赋值那么判断downkey的功能会出问题。
  5. 相对正确的延迟一帧方法: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){