本帖最后由 createskyblue 于 2020-4-21 15:50 编辑
演示视频 https://www.bilibili.com/video/av30637311/
状态 正常
游戏在文章最后 还有在线模拟器
大纲:0.逻辑框架 1.要求显示背景 2.要求控制小人移动和跳跃 3.要求火圈移动 人与火圈的碰撞箱
要求0 逻辑框架绘图->扫描按键->逻辑判断->绘图...
要求1 显示背景第一步:制作素材 FC中游戏的截图:
我重置后的背景(分辨率128*14)
原图是彩色的,但是把原图弄到我们普通单色128x64分辨率的OELD会导致丢失很多细节
第二步 导入素材 打开奈何的在线取模工具箱 http://tools.clz.me/ 要勾上颜色反转,不然在oled显示会相反
粘贴到代码中,我们重复这一步数次把所有素材导入代码中
第三步 编写代码 我们的需求是在游戏进行中显示,所以放到loop()结构体中 为了方便,我们新建一个名叫draw()的函数 用来显示游戏画面 并且使用函数arduboy.drawSlowXYBitmap(x轴, y轴, 位图名称, 位图长度, 位图高度, 白色); 显示在OLED上
[mw_shl_code=cpp,true]void draw() {
arduboy.clear(); //清屏
arduboy.drawSlowXYBitmap(0, 0, BG, 128, 14, 1); //显示背景
arduboy.display(); //显示到屏幕上
}[/mw_shl_code]
第四步 编译运行 以及 修改 不过有了一个新问题 游戏下半部分太暗了,我们再加个背景色以及一些黑色线条 大致的思路是下方先用白色实心长方形填充 然后再递归画几条黑色线 arduboy.fillRect(0, 15, 128, 49, 1); //在(0,15)的地方画一个颜色为白色的长方形 大小为128x49
再画几条黑线充实下背景 [mw_shl_code=cpp,true]for (byte i = 1; i <= 6; i++) {
arduboy.drawLine(0, i * i + 15, 128, i * i + 15, 0); //画背景黑线
}[/mw_shl_code]
要求2 控制小人移动和跳跃
第一步 按键扫描 扫描方向键
[mw_shl_code=cpp,true]byte KeyBack; //按键扫描返回
void key() {
/*
0 1 2 3 4 5
↑ ↓← → A B
*/
KeyBack = 255;
if (arduboy.pressed(UP_BUTTON)) KeyBack = 0;
if (arduboy.pressed(DOWN_BUTTON)) KeyBack = 1;
if (arduboy.pressed(LEFT_BUTTON)) KeyBack = 2;
if (arduboy.pressed(RIGHT_BUTTON)) KeyBack = 3;
if (arduboy.pressed(A_BUTTON)) KeyBack = 4;
if (arduboy.pressed(B_BUTTON)) KeyBack = 5;
}[/mw_shl_code]
第二步 逻辑判断
我们移动的不是小人而是背景 可以复制3个背景 小人夹在中间的背景 向左右移动的时候对应滚动背景,当背景被滚动到边缘的时候重置背景的x坐标复位
跳跃通过设置y轴的加速度实现,当到达”地面”重置加速度
小人移动动画:记录小人第一帧的时间,当超过每一帧设定的时间后播放下一帧画面,当没有连续按方向键的时候复位
[mw_shl_code=cpp,true]#define ManChangeTime 250 //移动的时候改变动画为500ms
unsigned long MCT; //记录改变帧的时间
byte MS; //小人状态 对应Man1 Man2 Man3
byte KeyBack; //按键扫描返回
int xy[6] = {20, 24, -128, 0}; //1-2 小人位置 3-4 背景位置 5-6 为火圈位置
float MYG;//小人Y方向加速度
byte ManCollision[8] = {11, 1, 14, 21, 0, 23, 34, 17}; //小人碰撞箱子 格式 在精灵中的x和y位置 以及长度和高度 一个碰撞箱4个数据
void logic() {
/*
移动
*/
switch (KeyBack) {
case 2:
xy[2] += 3;
break;
case 3:
xy[2] -= 3;
break;
case 4:
if (xy[1] == 24) MYG = -5;
break;
}
/*
跳跃加速度计算
*/
MYG += 0.5;
xy[1] += MYG;
if (xy[1] >= 24) { //落地归零
xy[1] = 24; //重置y坐标
MYG = 0; //重置加速度
}
/*
小人移动动画
*/
if (millis() >= MCT + ManChangeTime && KeyBack != 255) {
MCT = millis();
if (MS == 0) {
MS = 1;
} else if (MS == 1) MS = 0;
} else MS = 0;
/*
防止背景溢出
*/
if (xy[2] < -128) {
xy[2] += 128;
} else if (xy[2] > 0) xy[2] -= 128;
}[/mw_shl_code]
第三步 设置碰撞箱 byte ManCollision[8] = {11, 1, 14, 21, 0, 23, 34, 17}; //小人碰撞箱子 格式 在精灵中的x和y位置 以及长度和高度 一个碰撞箱4个数据
为什么要设置碰撞箱: 1. 为了方便后面计算是否于火圈有碰撞关系 2. 为了清除背景多余的黑线
第四步绘图
[mw_shl_code=cpp,true]void draw() {
arduboy.clear(); //清屏
/*
显示背景
*/
for (byte i = 0; i < 3; i++) {
arduboy.drawSlowXYBitmap(xy[2] + i * 128, xy[3], BG, 128, 14, 1); //显示背景
}
arduboy.fillRect(0, 15, 128, 49, 1); //在(0,15)的地方画一个颜色为白色的长方形 大小为128x49
for (byte i = 1; i <= 6; i++) {
arduboy.drawLine(0, i * i + 15, 128, i * i + 15, 0); //画背景黑线
}
/*
显示小人
*/
for (byte i = 0; i < (sizeof(ManCollision) / sizeof(ManCollision[0])) / 4; i++) {
arduboy.fillRect(xy[0] + ManCollision[i * 4], xy[1] + ManCollision[i * 4 + 1], +ManCollision[i * 4 + 2], +ManCollision[i * 4 + 3], 1); //清除多余的黑线
}
switch (MS) {
case 0:
arduboy.drawSlowXYBitmap(xy[0], xy[1], Man1, 34, 40, 0); //显示小人
break;
case 1:
arduboy.drawSlowXYBitmap(xy[0], xy[1], Man2, 34, 40, 0); //显示小人
break;
case 2:
arduboy.drawSlowXYBitmap(xy[0], xy[1], Man3, 34, 40, 0); //显示小人
break;
}
arduboy.display(); //显示到屏幕上
}[/mw_shl_code]
效果图: 现在按左右键可以实现小人移动 A键跳跃
要求3 火圈 和 碰撞箱
我们需要两个数组,一个数组来存储火圈的位置,一个为火圈的碰撞箱
[mw_shl_code=cpp,true]/*=========================================================
变量
=========================================================*/
#define ManChangeTime 250 //设置全局动画每帧时间 单位ms
unsigned long FCT; //记录火圈改变帧的时间
byte FS; //火圈对应状态
byte FireCollision[4] = {6, 38, 10, 3}; //火圈碰撞箱
int Firexy[20] = {160, 6, 240, 12, 356, 19, 424, 12, 480, 10, 542, 18, 608, 18, 704, 10, 798, 8, 866, 19}; //一共10个火圈
int GX; //游戏进度[/mw_shl_code]
并且当玩家越过火圈后火圈自动到队伍最后实现无限火圈 [mw_shl_code=cpp,true]/*
动态火焰
*/
if (millis() >= FCT + ManChangeTime) {
FCT = millis();
if (FS == 0) {
FS = 1;
} else FS = 0;
}[/mw_shl_code]
动态火焰是当火圈每一帧开始的时候记录时间,当超时后下一帧,原理和小丑跑起来是一样的,逻辑的最后到绘图。
[mw_shl_code=cpp,true]//显示火圈
for (byte i = 0; i < (sizeof(Firexy) / sizeof(Firexy[0])) / 2; i++) {
if (Firexy[i * 2] - GX >= -8 && Firexy[i * 2] - GX < 128) {
switch (FS) {
case 0:
arduboy.drawSlowXYBitmap(Firexy[i * 2] - GX, Firexy[i * 2 + 1] , Fire1, 22, 42, 0);
break;
case 1:
arduboy.drawSlowXYBitmap(Firexy[i * 2] - GX, Firexy[i * 2 + 1] , Fire2, 22, 42, 0);
break;
}
for (byte a = 0; a < (sizeof(ManCollision) / sizeof(ManCollision[0])) / 4; a++) { //计算碰撞箱
if (xy[0] + ManCollision[a * 4] + ManCollision[a * 4 + 2] > Firexy[i * 2] - GX + FireCollision[0] &&
Firexy[i * 2] - GX + FireCollision[0] + FireCollision[2] > xy[0] + ManCollision[a * 4] &&
xy[1] + ManCollision[a * 4 + 1] + ManCollision[a * 4 + 3] > Firexy[i * 2 + 1] + FireCollision[1] &&
Firexy[i * 2 + 1] + FireCollision[1] + FireCollision[3] > xy[1] + ManCollision[a * 4 + 1]) {
if (MS != 2) {
MS = 2; //状态为死亡 把这一行删掉即可开启无敌模式
MYG = -5; //跳跃
}
}
}
}
}
[/mw_shl_code] 碰撞箱的原理,以及火圈的碰撞箱范围 /* if (rc1.x + rc1.width > rc2.x && rc2.x + rc2.width > rc1.x && rc1.y + rc1.height > rc2.y && rc2.y + rc2.height > rc1.y )
rc1.x xy[0] + ManCollision[a * 4] rc1.width ManCollision[a * 4 + 2] rc1.y xy[1] + ManCollision[a * 4 + 1] rc1.high ManCollision[a * 4 + 3]
rc2.x Firexy[i * 2] - GX+FireCollision[0] rc2.width FireCollision[2] rc2.y Firexy[i * 2 + 1]+FireCollision[1] rc2.high FireCollision[3] */
最后完善我们还需要死亡动画和通过画面,这里比较简陋
[mw_shl_code=cpp,true]void loop() {
key(); //按键扫描
logic(); //逻辑
draw(); //绘图
if (MS == 2) fail(); //MS-2在显示火圈的时候会自动判断
if (GX >= 1000) win(); //当路程到达1000时游戏结束
}
/*=========================================================
胜利
=========================================================*/
void win() {
for (byte y = 0; y < 8; y++) {
for (byte x = 0; x < 7; x += 3) {
arduboy.setCursor(x * 6, y * 8); //设置光标
arduboy.print(F("WIN")); //打印 你赢了
arduboy.display(); //把画面显示在OLED上
}
}
while (1) {}
}[/mw_shl_code] 胜利就是满屏幕打印WIN [mw_shl_code=cpp,true]/*=========================================================
失败
=========================================================*/
void fail() {
while (xy[1] < 128) {
logic();
draw();
}
resetFunc();//重置游戏
}[/mw_shl_code]
并且设置新Y轴的加速度,碰到火圈会跳起来就像A键跳跃一样,不同的是玩家会落到屏幕下方并消失,此时游戏自动重置 void(* resetFunc) (void) = 0; //制造重启命令
如何正确运行游戏 传统的u8g系列的库包括u8g2 刷新有点慢,所以使用arduboy UNO移植库,刷新率比较快 但是会导致运行游戏没有这么容易 参考方法一和二 即使没有硬件,有手机或电脑也可以使用在线模拟器运行游戏,没关系的 看方法三
方法一 直接上传hex *Arduboy上传Ardubox.hex
|