用Arduino制作类FC马戏团-Arduino中文社区 - Powered by Discuz! Archiver

createskyblue 发表于 2018-8-28 19:45

用Arduino制作类FC马戏团

本帖最后由 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上
void draw() {
arduboy.clear();   //清屏
arduboy.drawSlowXYBitmap(0, 0, BG, 128, 14, 1); //显示背景
arduboy.display(); //显示到屏幕上
}
第四步 编译运行 以及 修改不过有了一个新问题 游戏下半部分太暗了,我们再加个背景色以及一些黑色线条大致的思路是下方先用白色实心长方形填充 然后再递归画几条黑色线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); //画背景黑线
}


要求2 控制小人移动和跳跃

第一步 按键扫描扫描方向键
byte KeyBack; //按键扫描返回
void key() {
/*
      012345
      ↑ ↓← →AB
*/
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;
}
第二步 逻辑判断
我们移动的不是小人而是背景可以复制3个背景 小人夹在中间的背景向左右移动的时候对应滚动背景,当背景被滚动到边缘的时候重置背景的x坐标复位
跳跃通过设置y轴的加速度实现,当到达”地面”重置加速度
小人移动动画:记录小人第一帧的时间,当超过每一帧设定的时间后播放下一帧画面,当没有连续按方向键的时候复位
#define ManChangeTime 250 //移动的时候改变动画为500ms
unsigned long MCT; //记录改变帧的时间
byte MS; //小人状态 对应Man1 Man2 Man3
byte KeyBack; //按键扫描返回
int xy = {20, 24, -128, 0}; //1-2 小人位置 3-4 背景位置 5-6 为火圈位置
float MYG;//小人Y方向加速度
byte ManCollision = {11, 1, 14, 21, 0, 23, 34, 17}; //小人碰撞箱子 格式 在精灵中的x和y位置 以及长度和高度 一个碰撞箱4个数据
void logic() {
/*
   移动
*/
switch (KeyBack) {
    case 2:
      xy += 3;
      break;
    case 3:
      xy -= 3;
      break;
    case 4:
      if (xy == 24) MYG = -5;
      break;
}
/*
   跳跃加速度计算
*/
MYG += 0.5;
xy += MYG;
if (xy >= 24) {//落地归零
    xy = 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 < -128) {
   xy += 128;
} else if (xy > 0) xy -= 128;
}
第三步 设置碰撞箱byte ManCollision = {11, 1, 14, 21, 0, 23, 34, 17}; //小人碰撞箱子 格式 在精灵中的x和y位置 以及长度和高度 一个碰撞箱4个数据
为什么要设置碰撞箱:1. 为了方便后面计算是否于火圈有碰撞关系2. 为了清除背景多余的黑线

第四步绘图
void draw() {
arduboy.clear();   //清屏
/*
   显示背景
*/
for (byte i = 0; i < 3; i++) {
    arduboy.drawSlowXYBitmap(xy + i * 128, xy, 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)) / 4; i++) {
    arduboy.fillRect(xy + ManCollision, xy + ManCollision, +ManCollision, +ManCollision, 1); //清除多余的黑线
}
switch (MS) {
    case 0:
      arduboy.drawSlowXYBitmap(xy, xy, Man1, 34, 40, 0); //显示小人
      break;
    case 1:
      arduboy.drawSlowXYBitmap(xy, xy, Man2, 34, 40, 0); //显示小人
      break;
    case 2:
      arduboy.drawSlowXYBitmap(xy, xy, Man3, 34, 40, 0); //显示小人
      break;
}

arduboy.display(); //显示到屏幕上
}
效果图:现在按左右键可以实现小人移动 A键跳跃

要求3 火圈 和 碰撞箱

我们需要两个数组,一个数组来存储火圈的位置,一个为火圈的碰撞箱

/*=========================================================
变量
=========================================================*/
#define ManChangeTime 250 //设置全局动画每帧时间 单位ms
unsigned long FCT; //记录火圈改变帧的时间
byte FS; //火圈对应状态
byte FireCollision = {6, 38, 10, 3}; //火圈碰撞箱
int Firexy = {160, 6, 240, 12, 356, 19, 424, 12, 480, 10, 542, 18, 608, 18, 704, 10, 798, 8, 866, 19}; //一共10个火圈
int GX; //游戏进度

并且当玩家越过火圈后火圈自动到队伍最后实现无限火圈/*
   动态火焰
*/
if (millis() >= FCT + ManChangeTime) {
    FCT = millis();
    if (FS == 0) {
      FS = 1;
    } else FS = 0;
}
动态火焰是当火圈每一帧开始的时候记录时间,当超时后下一帧,原理和小丑跑起来是一样的,逻辑的最后到绘图。

//显示火圈
for (byte i = 0; i < (sizeof(Firexy) / sizeof(Firexy)) / 2; i++) {
    if (Firexy - GX >= -8 && Firexy - GX < 128) {
      switch (FS) {
      case 0:
          arduboy.drawSlowXYBitmap(Firexy - GX, Firexy , Fire1, 22, 42, 0);
          break;
      case 1:
          arduboy.drawSlowXYBitmap(Firexy - GX, Firexy , Fire2, 22, 42, 0);
          break;
      }
      for (byte a = 0; a < (sizeof(ManCollision) / sizeof(ManCollision)) / 4; a++) {//计算碰撞箱
      if (xy + ManCollision + ManCollision> Firexy - GX + FireCollision &&
            Firexy - GX + FireCollision + FireCollision> xy + ManCollision &&
            xy + ManCollision + ManCollision > Firexy + FireCollision &&
            Firexy + FireCollision + FireCollision > xy + ManCollision) {
          if (MS != 2) {
            MS = 2; //状态为死亡把这一行删掉即可开启无敌模式
            MYG = -5; //跳跃
          }
      }
      }
    }
}
碰撞箱的原理,以及火圈的碰撞箱范围/*          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 + ManCollision         rc1.widthManCollision         rc1.y      xy + ManCollision         rc1.high   ManCollision
         rc2.x      Firexy - GX+FireCollision         rc2.width FireCollision         rc2.y      Firexy+FireCollision         rc2.high   FireCollision      */
最后完善我们还需要死亡动画和通过画面,这里比较简陋
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) {}
}胜利就是满屏幕打印WIN/*=========================================================
                  失败
=========================================================*/
void fail() {
while (xy < 128) {
    logic();
    draw();
}
resetFunc();//重置游戏
}
并且设置新Y轴的加速度,碰到火圈会跳起来就像A键跳跃一样,不同的是玩家会落到屏幕下方并消失,此时游戏自动重置void(* resetFunc) (void) = 0; //制造重启命令
如何正确运行游戏游戏文件:**** Hidden Message *****传统的u8g系列的库包括u8g2 刷新有点慢,所以使用arduboy UNO移植库,刷新率比较快但是会导致运行游戏没有这么容易 参考方法一和二即使没有硬件,有手机或电脑也可以使用在线模拟器运行游戏,没关系的 看方法三
方法一 直接上传hex*Arduboy上传Ardubox.hex *UNO上传UNO.hex
1.先连接硬件https://www.arduino.cn/data/attachment/forum/201807/04/212143rw1nj0on1w53201z.png上键               ===>17 A3
下键               ===>2
左键               ===>15 A1
右键               ===>3
A键                ===>4
B键                ===>16 A2
OLED_SCL   ===>19 A5
OLED_SDA    ===>18 A4
按键一端接地 一端接到UNO上
2.下载Arduloader[附件过大无法下载,请自行百度]我的博客下载:http://hwtime.free.idcfengye.com/blog/zb_users/upload/2018/08/201808221759048320995.zip
(不保证下载稳定)


3.打开Arduloader
https://www.arduino.cn/data/attachment/forum/201808/22/181145o3tyo637yx7vddzv.png
游戏hex文件在上面的游戏下载附件的压缩包里
https://www.arduino.cn/data/attachment/forum/201808/22/181145fliywn1lwsk5n1ko.png
该画面为成功上传,现在oled上应该有画面

方法二 编译安装    可以参考下面   假若有Arduboy可以直接编译
https://www.arduino.cn/forum.php?mod=viewthread&tid=80103&highlight=arduboy
方法三 在线模拟器
https://felipemanga.github.io/ProjectABE/?url=https://raw.githubusercontent.com/createskyblue/circus/master/ARDUBOY.hex



新手之帆 发表于 2018-8-28 20:35

666,大佬,多发点这种教程

createskyblue 发表于 2018-8-28 20:52

新手之帆 发表于 2018-8-28 20:35
666,大佬,多发点这种教程

未来可能会有更多fc经典游戏移植,不过开学后可能要很久才能一更

单片机菜鸟 发表于 2018-8-28 22:29

大佬 666 给你顶一下帖

明娃子 发表于 2018-9-18 08:59

好游戏绑定

明娃子 发表于 2018-9-18 09:00

谢谢分享

Momo_HxJ 发表于 2018-10-29 17:02

666,大佬,学习了

h1216398255 发表于 2018-12-24 09:14

大佬真厉害!

arduino月色无声 发表于 2018-12-24 10:30

感谢楼主,手上的OLED 12864有用武之地了。

h1997l1997 发表于 2019-1-1 17:10

太棒啦拉拉阿拉拉了
页: [1] 2 3
查看完整版本: 用Arduino制作类FC马戏团