查看: 2478|回复: 10

教你用Arduino编写FC马戏团

[复制链接]
  • TA的每日心情
    奋斗
    2019-3-22 20:44
  • 签到天数: 6 天

    [LV.2]偶尔看看I

    发表于 2018-8-28 19:45 | 显示全部楼层 |阅读模式
    本帖最后由 createskyblue 于 2018-10-3 21:29 编辑

    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

    kittenblock中小学创客名师推荐的图形化编程软件

    void draw() {
      arduboy.clear();   //清屏
     arduboy.drawSlowXYBitmap(0, 0, BG, 128, 14, 1); //显示背景
      arduboy.display(); //显示到屏幕上
    }

    第四步 编译运行 以及 修改
    4.jpg
    不过有了一个新问题 游戏下半部分太暗了,我们再加个背景色以及一些黑色线条
    大致的思路是下方先用白色实心长方形填充 然后再递归画几条黑色线
    arduboy.fillRect(0, 15, 128, 49, 1);  //(0,15)的地方画一个颜色为白色的长方形 大小为128x49

    再画几条黑线充实下背景

    kittenblock中小学创客名师推荐的图形化编程软件

    for (byte i = 1; i <= 6; i++) {
        arduboy.drawLine(0, i * i + 15, 128, i * i + 15, 0); //画背景黑线
      }

    5 - 副本.jpg


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

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

    kittenblock中小学创客名师推荐的图形化编程软件

    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;
    }

    第二步 逻辑判断

    我们移动的不是小人而是背景
    可以复制3个背景 小人夹在中间的背景
    向左右移动的时候对应滚动背景,当背景被滚动到边缘的时候重置背景的x坐标复位

    跳跃通过设置y轴的加速度实现,当到达”地面”重置加速度

    小人移动动画:记录小人第一帧的时间,当超过每一帧设定的时间后播放下一帧画面,当没有连续按方向键的时候复位

    kittenblock中小学创客名师推荐的图形化编程软件

    #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;
    }

    第三步 设置碰撞箱
    byte ManCollision[8] = {11, 1, 14, 21, 0, 23, 34, 17}; //小人碰撞箱子 格式 在精灵中的xy位置 以及长度和高度 一个碰撞箱4个数据

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

    6.png

    第四步绘图

    kittenblock中小学创客名师推荐的图形化编程软件

    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(); //显示到屏幕上
    }

    效果图:
    现在按左右键可以实现小人移动 A键跳跃

    7.jpg

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


    kittenblock中小学创客名师推荐的图形化编程软件

    /*=========================================================
      变量
      =========================================================*/
    #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; //游戏进度


    并且当玩家越过火圈后火圈自动到队伍最后实现无限火圈

    kittenblock中小学创客名师推荐的图形化编程软件

    /*
         动态火焰
      */
      if (millis() >= FCT + ManChangeTime) {
        FCT = millis();
        if (FS == 0) {
          FS = 1;
        } else FS = 0;
      }

    动态火焰是当火圈每一帧开始的时候记录时间,当超时后下一帧,原理和小丑跑起来是一样的,逻辑的最后到绘图。

    11.png

    kittenblock中小学创客名师推荐的图形化编程软件

    //显示火圈
      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; //跳跃
              }
            }
          }
        }
      }
    
    碰撞箱的原理,以及火圈的碰撞箱范围
    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]
            */

    最后完善
    我们还需要死亡动画和通过画面,这里比较简陋

    kittenblock中小学创客名师推荐的图形化编程软件

    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
    13.png

    kittenblock中小学创客名师推荐的图形化编程软件

    /*=========================================================
                      失败
      =========================================================*/
    void fail() {
      while (xy[1] < 128) {
        logic();
        draw();
      }
      resetFunc();//重置游戏
    }
    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



    有帮助的话,可以赞助小编一支笔芯促进学业吗

    1人打赏

  • TA的每日心情
    无聊
    2019-4-6 10:04
  • 签到天数: 74 天

    [LV.6]常住居民II

    发表于 2018-8-28 20:35 | 显示全部楼层
    666,大佬,多发点这种教程
    打赏作者鼓励一下!
  • TA的每日心情
    奋斗
    2019-3-22 20:44
  • 签到天数: 6 天

    [LV.2]偶尔看看I

     楼主| 发表于 2018-8-28 20:52 | 显示全部楼层
    新手之帆 发表于 2018-8-28 20:35
    666,大佬,多发点这种教程

    未来可能会有更多fc经典游戏移植,不过开学后可能要很久才能一更
    有帮助的话,可以赞助小编一支笔芯促进学业吗
  • TA的每日心情
    开心
    2019-4-25 11:17
  • 签到天数: 649 天

    [LV.9]以坛为家II

    发表于 2018-8-28 22:29 | 显示全部楼层
    大佬 666 给你顶一下帖
    打赏作者鼓励一下!
  • TA的每日心情
    开心
    2018-9-21 15:12
  • 签到天数: 8 天

    [LV.3]偶尔看看II

    发表于 2018-9-18 08:59 | 显示全部楼层
    好游戏绑定
    PY让世界更美好~

    该用户从未签到

    发表于 2018-10-29 17:02 | 显示全部楼层
    666,大佬,学习了
  • TA的每日心情
    开心
    2018-11-15 21:07
  • 签到天数: 1 天

    [LV.1]初来乍到

    发表于 2018-12-24 09:14 | 显示全部楼层
    大佬真厉害!
  • TA的每日心情
    慵懒
    2019-2-15 15:01
  • 签到天数: 5 天

    [LV.2]偶尔看看I

    发表于 2018-12-24 10:30 | 显示全部楼层
    感谢楼主,手上的OLED 12864有用武之地了。

    该用户从未签到

    发表于 2019-1-1 17:10 | 显示全部楼层
    太棒啦拉拉阿拉拉了
    您需要登录后才可以回帖 登录 | 立即注册  

    本版积分规则

    热门推荐

    学习记录2,Blinker读取温湿度
    学习记录2,Blinker读取温
    开关现在是使用得很6了,准备进行下一步,读取数据。 找了下官方的例程,发现用的是w
    学生智能打卡系统(接入blinker)
    学生智能打卡系统(接入bl
    【项目名称】学生智能打卡系统(接入blinker) 一.感谢各位大佬 首先感谢社区的管理员
    关于红外控制空调的模块选型
    关于红外控制空调的模块选
    想问问这中模块能支持arduino吗
    LCD1602只亮 不显示
    LCD1602只亮 不显示
    #include // initialize the library by associating any needed LCD interface pin
    18脚的8x8LED点阵如何使用??
    18脚的8x8LED点阵如何使用
    本帖最后由 Creeper666 于 2018-8-14 12:24 编辑 这个点阵模块上面一排有12个脚,
    Copyright   ©2015-2016  Arduino中文社区  Powered by©Discuz!   ( 蜀ICP备14017632号-3 )
    快速回复 返回顶部 返回列表