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

Arduino中文社区

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 16359|回复: 28

用Arduino制作类FC马戏团

[复制链接]
发表于 2018-8-28 19:45 | 显示全部楼层 |阅读模式
本帖最后由 createskyblue 于 2020-4-21 15:50 编辑

11.png
演示视频 https://www.bilibili.com/video/av30637311/
状态        正常

游戏在文章最后 还有在线模拟器
大纲:
0.逻辑框架
1.要求显示背景
2.要求控制小人移动和跳跃
3.要求火圈移动 人与火圈的碰撞箱

要求0 逻辑框架
绘图->扫描按键->逻辑判断->绘图...

要求1 显示背景
第一步:制作素材
FC中游戏的截图:
1.png

我重置后的背景(分辨率128*14)
BG - 高清.png

原图是彩色的,但是把原图弄到我们普通单色128x64分辨率的OELD会导致丢失很多细节

第二步 导入素材
打开奈何的在线取模工具箱 http://tools.clz.me/
要勾上颜色反转,不然在oled显示会相反

2.png
3.png


粘贴到代码中,我们重复这一步数次把所有素材导入代码中

第三步 编写代码
我们的需求是在游戏进行中显示,所以放到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]

第四步 编译运行 以及 修改
4.jpg
不过有了一个新问题 游戏下半部分太暗了,我们再加个背景色以及一些黑色线条
大致的思路是下方先用白色实心长方形填充 然后再递归画几条黑色线
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]

5 - 副本.jpg


要求2 控制小人移动和跳跃 高清.png

第一步 按键扫描
扫描方向键

[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}; //小人碰撞箱子 格式 在精灵中的xy位置 以及长度和高度 一个碰撞箱4个数据

为什么要设置碰撞箱:
1. 为了方便后面计算是否于火圈有碰撞关系
2. 为了清除背景多余的黑线

6.png

第四步绘图

[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键跳跃

7.jpg

要求3 火圈 和 碰撞箱
火圈 - 副本.png
我们需要两个数组,一个数组来存储火圈的位置,一个为火圈的碰撞箱


[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]
动态火焰是当火圈每一帧开始的时候记录时间,当超时后下一帧,原理和小丑跑起来是一样的,逻辑的最后到绘图。

11.png

[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]
碰撞箱的原理,以及火圈的碰撞箱范围
8.png
/*
          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
13.png
[mw_shl_code=cpp,true]/*=========================================================
                  失败
  =========================================================*/
void fail() {
  while (xy[1] < 128) {
    logic();
    draw();
  }
  resetFunc();//重置游戏
}[/mw_shl_code]
12.png

并且设置新Y轴的加速度,碰到火圈会跳起来就像A键跳跃一样,不同的是玩家会落到屏幕下方并消失,此时游戏自动重置
void(* resetFunc) (void) = 0; //制造重启命令

如何正确运行游戏
游戏文件:
游客,如果您要查看本帖隐藏内容请回复
传统的u8g系列的库包括u8g2 刷新有点慢,所以使用arduboy UNO移植库,刷新率比较快
但是会导致运行游戏没有这么容易 参考方法一和二
即使没有硬件,有手机或电脑也可以使用在线模拟器运行游戏,没关系的 看方法三
方法一 直接上传hex
*Arduboy上传Ardubox.hex
*UNO上传UNO.hex
1.先连接硬件
上键               ===>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

游戏hex文件在上面的游戏下载附件的压缩包里

该画面为成功上传,现在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,大佬,多发点这种教程
 楼主| 发表于 2018-8-28 20:52 | 显示全部楼层
新手之帆 发表于 2018-8-28 20:35
666,大佬,多发点这种教程

未来可能会有更多fc经典游戏移植,不过开学后可能要很久才能一更
发表于 2018-8-28 22:29 | 显示全部楼层
大佬 666 给你顶一下帖
发表于 2018-9-18 08:59 | 显示全部楼层
好游戏绑定
发表于 2018-10-29 17:02 | 显示全部楼层
666,大佬,学习了
发表于 2018-12-24 09:14 | 显示全部楼层
大佬真厉害!
发表于 2018-12-24 10:30 | 显示全部楼层
感谢楼主,手上的OLED 12864有用武之地了。
发表于 2019-1-1 17:10 | 显示全部楼层
太棒啦拉拉阿拉拉了
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|Archiver|手机版|Arduino中文社区

GMT+8, 2024-11-28 09:32 , Processed in 0.101173 second(s), 18 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表