查看: 23561|回复: 75

Arduino教你制作 FC炸弹人游戏

  [复制链接]

该用户从未签到

发表于 2018-9-17 19:30 | 显示全部楼层 |阅读模式
本帖最后由 createskyblue 于 2018-9-17 20:20 编辑

0 - 副本.png 1.png
前排提示,游戏文件、在线模拟在2楼
1楼为制作教程
视频: https://www.bilibili.com/video/av31933044/

第一步 导入素材

11.png



第二步 地图生成器


[mw_shl_code=cpp,true]void BuildMap() {
  /*
     0  空气
     1  不可摧毁墙
     2  普通墙
     3  TNT
     4  爆炸3
     5  爆炸2
     6  爆炸1
  */
  //生成墙和怪物
  LIFE = 3; //开局3条命
  byte MN = 0; //重置怪物计数器
  PP = 2; //设置玩家方向头朝下[/mw_shl_code]
首先要生成墙壁,先生成边框防止玩家跑出地图,最后生成边框里面墙壁阵列

5.png

[mw_shl_code=cpp,true]for (byte y = 0; y < 15; y++) {
    for (byte x = 0; x < 31; x++) {
      if (y == 0 || y == 14) {
        MAP[x][y] = 1;        生成y轴墙壁外围 下图橙色区域
      } else if (x == 0 || x == 30) {
        MAP[x][y] = 1;        生成x轴墙壁外围 下图蓝色区域
      } else {
        if (x % 2 == 0 && y % 2 == 0) {
          MAP[x][y] = 1;      判断当前x,y坐标是否为偶数,如果是则生成内部墙壁
        } else if (random(0, 4) == 0) {
          MAP[x][y] = 2;      1/4概率生成可以破坏的墙
        } else if (random(0, 28) == 0) {   1/28 的概率生成怪物
          //生成怪物
          if (MN < LEVEL) {        确保怪物数量少于当前关卡数
            monster[MN][0] = x;    记录当前编号怪物的x,y坐标
            monster[MN][1] = y;
            MLRUD[MN] = 2; //设置怪物方向为头朝下 0上 1右 2下 3左
            MN++;            下一个怪物编号
          }
        } else MAP[x][y] = 0;    假若不满足上边的条件则生成空气
      }
    }
  }[/mw_shl_code]

4.png
[mw_shl_code=cpp,true]//设置玩家出生点
  for (byte py = 0; py < 3; py++) {
    for (byte px = 0; px < 3; px++) {
      //清空出生点附近的普通墙和怪物,确保出生点安全
      MAP[15 - 1 + px][7 - 1 + py] = 0;
    }
  }

清除玩家出生点附近3*3区域内所有方块,确保有较大的空间让玩家开一条路出去

  PX = 15;  设置玩家在地图上的出生点
  PY = 7;
}[/mw_shl_code]


12.png

上图为生成器生成的初始游戏地图
第三步 实现玩家移动 绘图部分
[mw_shl_code=cpp,true]void loop() {
if (LIFE == 0) FAIL();  //如果生命为0 游戏结束
  key();     //扫描按键
  Draw();    //绘图
  logic();   //逻辑处理
}

/*=========================================================
  按键扫描
  =========================================================*/
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;
}

/*===================================================================
                             绘图
===================================================================*/
void Draw() {
  DrawMap(); //渲染地图
  DrawEntity(); //渲染实体
  arduboy.display(); //发送画面到屏幕
arduboy.fillRect(0, 56, 128, 8, 0);
  for (byte ni = 0; ni < LIFE; ni++) {
    arduboy.drawSlowXYBitmap(ni * 9, 56, LOVE, 8, 8, 1); //生命条
  }
  arduboy.display(); //发送画面到屏幕
}
/*=================================================================
                             渲染地图
==================================================================*/
void DrawMap() {
  arduboy.fillRect(0, 0, 128, 64, 1);
  for (char y = PY - 4; y < PY + 5; y++) {
    for (char x = PX - 8; x < PX + 10; x++) {
      if (x >= 0 && y >= 0 && x <= 30 && y <= 14) {[/mw_shl_code]

用两个嵌套的for语句来扫描玩家视线内的方块,然后用switch语句显示出对应方块的位图
下图灰色区域为玩家所能看到的区域 黑框内为128x64OLED的显示范围
游戏中,无论怎样移动移动的都是地图而不是玩家本身,玩家是被固定在屏幕中心位置的,而且地图方块对应的位置不代表实际显示的位置,为了正确显示我们需要减去地图方块与玩家相对坐标 参考下图紫色箭头
为了直观告诉大家哪里做了处理,下列显示代码会把要坐标处理的部分染为紫色

6.png
[mw_shl_code=cpp,true] switch (MAP[x][y]) {
          case 1:
            arduboy.drawSlowXYBitmap(x * 8 - (PX - 15) * 8 - 64 + CSX, y * 8 - (PY - 7) * 8 - 32 + CSY, WALL_1, 8, 8, 0);
            break;
          case 2:
            arduboy.drawSlowXYBitmap(x * 8 - (PX - 15) * 8 - 64 + CSX, y * 8 - (PY - 7) * 8 - 32 + CSY, WALL_2, 8, 8, 0);
            break;
          case 3:
            arduboy.drawSlowXYBitmap(x * 8 - (PX - 15) * 8 - 64 + CSX, y * 8 - (PY - 7) * 8 - 32 + CSY, TNT_table[TNTS], 8, 8, 0);
            break;
          case 4:
            arduboy.drawSlowXYBitmap(x * 8 - (PX - 15) * 8 - 64 + CSX, y * 8 - (PY - 7) * 8 - 32 + CSY, BOOM_1, 8, 8, 0);
            break;
          case 5:
            arduboy.drawSlowXYBitmap(x * 8 - (PX - 15) * 8 - 64 + CSX, y * 8 - (PY - 7) * 8 - 32 + CSY, BOOM_2, 8, 8, 0);
            break;
          case 6:
            arduboy.drawSlowXYBitmap(x * 8 - (PX - 15) * 8 - 64 + CSX, y * 8 - (PY - 7) * 8 - 32 + CSY, BOOM_3, 8, 8, 0);
            break;
        }
      }
    }
  }
  TNTS++;
  if (TNTS >= 2) TNTS = 0;
}
/*====================================================================
                             渲染实体
  ====================================================================*/
void DrawEntity() {
  if (LIFE > 0) {
    //渲染怪物
    for (byte n = 0; n < 10; n++) {
      if (MLRUD[n] != 255) arduboy.drawSlowXYBitmap(monster[n][0] * 8 - (PX - 15) * 8 - 64 + CSX, monster[n][1] * 8  - (PY - 7) * 8 - 32 + CSY, M_table[byte(MLRUD[n])], 8, 8, 0);
    }[/mw_shl_code]

Man_table 存储的是炸弹人动作列表:{ 炸弹人_朝上_1, 炸弹人_朝上_2, 炸弹人_朝上_3, 炸弹人_朝右_1, 炸弹人_朝右_2, 炸弹人_朝右_3, 炸弹人_朝下_1, 炸弹人_朝下_2, 炸弹人_朝下_3, 炸弹人_朝左_1, 炸弹人_朝左_2, 炸弹人_朝左_3}
炸弹人有4个方向的贴图,每个方向拥有3个动作,加起来一共12幅图
变量PP指的是炸弹人的方向,决定选择哪一组贴图
变量PS为炸弹人当前动作帧,当每一帧动作显示后自加或者复位 实现炸弹人移动动画
因此得出Man_table[PP * 3 + PS]这条式子
当炸弹人受到伤害后将会被扣血,扣血后会进入一段时间的无敌状态避免短时间重复受到伤害
而无敌模式中只会显示炸弹人某个方向的第一帧 忽略掉2、3帧,因此在无敌模式中炸弹人会有闪烁的效果

[mw_shl_code=cpp,true]//渲染玩家
    if (millis() >= PIT + Invincible_Time) {
      arduboy.drawSlowXYBitmap(56, 24, Man_table[PP * 3 + PS] , 8, 8, 0); //玩家图像为方向*3+动画帧
    } else if (PS == 0) arduboy.drawSlowXYBitmap(56, 24, Man_table[PP * 3 + PS] , 8, 8, 0); //无敌模式的时候闪烁效果
    if (PMove == true || millis() < PIT + Invincible_Time) { //只有在玩家移动的时候或者无敌模式 才会有移动动画
      PS++;
      if (PS > 2) PS = 0;
    } else PS = 0;
  }
}[/mw_shl_code]


7.png

[mw_shl_code=cpp,true]/*=========================================================
  逻辑
  =========================================================*/
void logic() {
    /*
      控制移动 以及移动相关动画
    */
    switch (KeyBack) {
      case 0:
        PP = 0;
        SBDP(PP, PX, PY);
        if (PY > 1 && BMove == true) {
          PMove = true;
          for (CSY = 1; CSY <= 7; CSY += 3) Draw();
          PY--;
        }
        break;
      case 1:
        PP = 2;
        SBDP(PP, PX, PY);
        if (PY < 13 && BMove == true) {
          PMove = true;
          for (CSY = -1; CSY >= -7; CSY -= 3) Draw();
          PY++;
        }
        break;
      case 2:
        PP = 3;
        SBDP(PP, PX, PY);
        if (PX > 1 && BMove == true) {
          PMove = true;
          for (CSX = 1; CSX <= 7; CSX += 3) Draw();
          PX--;
        }
        break;
      case 3:
        PP = 1;
        SBDP(PP, PX, PY);
        if (PX < 29 && BMove == true) {
          PMove = true;
          for (CSX = -1; CSX >= -7; CSX -= 3) Draw();
          PX++;
        }
        break;
    }
    if (PMove == true) {
      CSX = 0;
      CSY = 0;
      PMove = false;
    }
}[/mw_shl_code]

接下来研究炸弹人平滑移动,而不是直接瞬移到下一方块
方法是按下方向键后先让背景平滑移动,当移动整整一格方块后背景复位并且玩家坐标移动到对应位置。给人一种平滑走动的感觉!
上面移动代码中反复出现了以下类似的结构,我们取向右移动的代码来研究:


1   PP = 1;
设置炸弹人朝向右边
   2 SBDP(PP, PX, PY);
调用SBDP障碍物判断函数,判断在炸弹人位置对应方向前是否有障碍物,如果没有障碍物判断函数会把变量BMove设置为true
   3 if (PX < 29 && BMove == true) {
   判断障碍物判断函数返回值 前方是否没障碍物,并且前面是否小于x轴地图最大范围
   4 PMove = true;
   允许移动
5 for (CSX = -1; CSX >= -7; CSX -= 3) Draw();
   让背景往左边方向平滑移动 每次移动3个像素 如果想要更加平滑可以把-3改为-1
   并且调用绘图函数背景显示部分,这里拿墙壁显示片段举例
arduboy.drawSlowXYBitmap(x * 8 - (PX - 15) * 8 - 64 + CSX, y * 8 - (PY - 7) * 8 - 32 + CSY, WALL_1, 8, 8, 0);
   6 PX++;
   让炸弹人坐标真正往前一步,不过不用马上调用绘图函数刷新
   }
   6步 背景复位   if (PMove == true) {
             CSX = 0;
               CSY = 0;
               PMove = false;
            }
这一步也没有立即调用绘图函数来刷新,尽管背景位置复位了,不过玩家位置已经向前,所以在下一次刷新的时候画面不会变化


/*===================================================================
                              障碍物判断===================================================================*/
void SBDP(byte SBP, byte sx, byte sy) {
1  BMove = true;
       char SX, SY;
初始化变量
2步 判断返回值
调用障碍物判断函数的时候还要传递3个数据 分别是 方向 和玩家位置x和y
用一个SWITCH语句判断要检测的坐标

(X,Y-1)
(X-1,Y)
玩家位置
(X,Y)
(X+1,Y)
(X,Y+1)


[mw_shl_code=cpp,true]switch (SBP) {
    case 3:
      SX = -1;
      SY = 0;
      break;
    case 1:
      SX = +1;
      SY = 0;
      break;
    case 0:
      SX = 0;
      SY = -1;
      break;
    case 2:
      SX = 0;
      SY = +1;
      break;
  }[/mw_shl_code]

3步 获取列表长度
获取障碍物列表最大的长度 默认障碍物列表有以下ID(1,2,3) 分别为坚固墙壁 可以摧毁的墙壁以及TNT
[mw_shl_code=cpp,true]byte length = sizeof(SBDPL) / sizeof(SBDPL[0]);[/mw_shl_code]
4步 检测是否为障碍物
获用for逐个检测目标位置ID是否为障碍物,如果是则设定变量BMOVE为false
[mw_shl_code=cpp,true]for (byte i = 0; i < length; i++) {  
    if (MAP[sx + SX][sy + SY] == SBDPL) BMove = false;
  }
}[/mw_shl_code]

第四步 TNT放置 爆炸效果 爆炸伤害

在上边第二步中检测按键返回值控制移动的switch语句中加多以下内容
case 4:
        //TNT
1步 检查要放TNT的位置是否已经有TNT 并且这一刻全地图内是否少于10TNT
        if (TNTN < 10 && MAP[PX][PY] != 3) {
          //注意0为没有TNT 范围1-10
2步 可以放置TNT 让全地图TNT数量+1
          TNTN++;

3步  在TNT列表中设置好当前编号的TNT位置
          TntList[TNTN - 1][0] = PX;
          TntList[TNTN - 1][1] = PY;

4步  在地图对应位置写入TNTID
          MAP[PX][PY] = 3;

5步 记录下放置TNT的时间 用于计算什么时候起爆
          TntTime[TNTN - 1] = millis();
        }
        break;
现在TNT已经放置了,可是还需要让它到时间后起爆,在逻辑语句中加入以下部分
/*
       计算TNT爆炸
  */

1步 判断地图内是否存在TNT,有的话继续
    if (TNTN != 0) {
      //存在炸弹
2步 检查TNT列表第一个也就是最接近起爆时间的TNT是否到时间
      if (millis() >= TntTime[0] + BOOMTime) {

3步 如果到了起爆时间那么在地图TNT所在的位置上把ID-3 准备爆炸的TNT 替换为ID-4爆炸第一阶段

        MAP[TntList[0][0]][TntList[0][1]] = 4; //引爆
        //摧毁附近的非坚固实体或者方块

4步 检查起爆的TNT十字范围内是否有可以摧毁的东西,有的话把ID替换为ID-4 爆炸的第一阶段

(X,Y-1)
(X-1,Y)
TNT位置
(X,Y)
(X+1,Y)
(X,Y+1)

先用for检查X轴是否有可以摧毁的方块,这里可以修改代码扩大摧毁范围
[mw_shl_code=cpp,true] for (byte BOOMx = 0; BOOMx < 3; BOOMx++) {
if (MAP[TntList[0][0] - 1 + BOOMx][TntList[0][1]] != 1 && MAP[TntList[0][0] - 1 + BOOMx][TntList[0][1]] != 3) {
MAP[TntList[0][0] - 1 + BOOMx][TntList[0][1]] = 4;
}
}[/mw_shl_code]
最后for检查y轴是否有可以摧毁的方块

[mw_shl_code=cpp,true]for (byte BOOMy = 0; BOOMy < 3; BOOMy++) {
          if (MAP[TntList[0][0]][TntList[0][1] - 1 + BOOMy] != 1 && MAP[TntList[0][0] - 1 + BOOMy][TntList[0][1]] != 3) {
            MAP[TntList[0][0]][TntList[0][1] - 1 + BOOMy] = 4;
          }
        }[/mw_shl_code]
5步 在TNT列表注销以及爆炸的TNT
[mw_shl_code=cpp,true] //让TNT列表向前移位
        TNTN--; //减少一枚TNT
        for (byte TNTi = 0; TNTi < TNTN; TNTi++) {
让列表后边的TNT坐标和TNT放置时间移动到前面一位
TntList[TNT编号][0] 对应编号TNT的X轴
TntList[TNT编号][1] 对应编号TNT的Y轴
TntTime[编号]         对应编号TNT的放置时间

          TntList[TNTi][0] = TntList[TNTi + 1][0];
          TntList[TNTi][1] = TntList[TNTi + 1][1];
          TntTime[TNTi] = TntTime[TNTi + 1];
        }
      }
}[/mw_shl_code]
现在TNT可以爆炸,接下来是爆炸动画

8.png

[mw_shl_code=cpp,true]第1步 两个嵌套的for遍历地图
for (byte y = 0; y < 15; y++) {
for (byte x = 0; x < 31; x++) {


第2步 如果是4替换为5 5替换为6 6替换为0(空气)
      if (MAP[x][y] == 4) MAP[x][y] = 5; else if (MAP[x][y] == 5) MAP[x][y] = 6; else if (MAP[x][y] == 6) MAP[x][y] = 0; //让爆炸切换下一帧
    }
  }

有动画还不够,TNT存在的意义是破坏和伤害

第1步 检查怪物列表
用for遍历怪物列表,每个地图怪物上限为10 很轻松就可以遍历一次
for (byte i = 0; i < 10; i++) {
      if (MAP[monster[0]][monster[1]] >= 4 && MLRUD != 255) {
假若当前列表编号的怪物不是死亡状态并且脚下为ID-4 ~ ID-6 不同阶段的爆炸则继续执行
        

第2步 让怪物死亡
MLRUD = 255;
      }

第3步 检查玩家是否在无敌状态
      if (millis() >= PIT + Invincible_Time) { //玩家不在无敌状态


第4步 若不是在无敌状态下检查脚下是否为为ID-4 ~ ID-6 不同阶段的爆炸
        if (PX == monster[0] && PY == monster[1] || MAP[PX][PY] >= 4) { //怪物伤害 或者 TNT伤害

第5步 扣生命 以及设置无敌状态开始的时间
          LIFE--;
          PIT = millis();
        }
      }[/mw_shl_code]

第五步 怪物AI
要怪物有可用,为了被玩家攻击和攻击玩家给玩家带来难度,所以我们要会动的怪物而不是呆住不动的木头脑袋
在逻辑语句插入以下内容

[mw_shl_code=cpp,true] /*
     怪物AI
  */
第1步 假设游戏通关 怪物被消灭
bool PWIN = true;

第2步 是否到了刷新时间 若是继续执行
if (millis() >= MMTime + MMTimeOut) {

重置刷新时间
      MMTime = millis();

第3步 遍历怪物列表
      for (byte n = 0; n < 10; n++) {

假若对应列表编号的怪物不是死亡状态
        if (MLRUD[n] != 255) {
很遗憾,游戏还没有结束 设置游戏胜利状态为假
          PWIN = false;

第4步  通过SBDP障碍物判断函数判断怪物坐标的对应方向是否有障碍物
          SBDP(MLRUD[n], monster[n][0], monster[n][1]);

第5步 没有障碍物 进行移动
在对应方向的坐标走一步
          if (BMove == true) {
            //移动合法
            switch (MLRUD[n]) {
              case 0:
                monster[n][1]--;
                break;
              case 1:
                monster[n][0]++;
                break;
              case 2:
                monster[n][1]++;
                break;
              case 3:
                monster[n][0]--;
                break;
            }
假若有障碍物,那么怪物方向随机更改(通过无数次尝试发现越简单的越好用,穷举法在这种情况下很好用)
          } else MLRUD[n] = random(0, 4);
        } else if (PWIN == true) WIN(); 假若通过为真,调用通关函数
      }
    }[/mw_shl_code]

2.png

第六步 游戏菜单 以及 游戏结束


[mw_shl_code=cpp,true]/*=========================================================
                     通关
  =========================================================*/
void WIN() {

第1步 假若关卡为10 则通关画面
  if (LEVEL == 10) {
第2步 清屏并显示文字
    arduboy.clear();
    arduboy.setCursor(16, 0);
    arduboy.println(F("CONGRATULATIONS"));
    arduboy.println(F(" BOMBER MAN BECOMES"));
    arduboy.println(F("       RUNNER"));
    arduboy.println(F("SEE YOU AGAIN IN LODE"));
arduboy.println(F("       RUNNER"));


第3步 显示一个面向右面的炸弹人
arduboy.drawSlowXYBitmap(56, 48, Man_table[3] , 8, 8, 1);


第4步 在最下方砖块显示的区域画一个实心长方体,用于画布底层
arduboy.fillRect(0, 56, 128, 8, 1);
通过for显示16个转头
    for (byte x = 0; x < 128; x += 8) {
      arduboy.drawSlowXYBitmap(x, 56, WALL_2, 8, 8, 0);
}

第5步 在屏幕上显示
    arduboy.display();
    while (1) {}
  } else {
假若关卡小于10
    LEVEL++;   //关卡+1
    BuildMap();  //构建地图
    ShowLevel(); //显示第几关
  }
}[/mw_shl_code]

3.png

[mw_shl_code=cpp,true]/*=========================================================
                     显示关卡
  =========================================================*/
void ShowLevel() {
  arduboy.clear();
  arduboy.setCursor(52, 16);
  arduboy.println(F("LEVEL"));
  arduboy.setCursor(64, 32);
  arduboy.println(LEVEL);
  arduboy.display();
  delay(1000);
}
/*=========================================================
                     玩家死亡
  =========================================================*/
void FAIL() {
  for (byte y = 0; y < 15; y++) {
    for (byte x = 0; x < 31; x++) {
      MAP[x][y] = 4;
把整个地图设置为爆炸
    }
  }

  while (MAP[0][0] >= 3) {
    Draw();
    logic();
delay(500);
切换爆炸下一帧,直到爆炸结束
  }[/mw_shl_code]

9.png

[mw_shl_code=cpp,true] Draw();
  arduboy.drawSlowXYBitmap(56, 24, Man_table[3] , 8, 8, 0);
在画面中间显示孤独的主角[/mw_shl_code]
10.png


[mw_shl_code=cpp,true]arduboy.display();
  delay(5000);
  resetFunc(); //重启游戏
}
/*=========================================================
                     主菜单
  =========================================================*/
void MENU() {
  bool POA = false;
  while (POA == true || KeyBack != 4) {
    key();
    switch (KeyBack) {
      case 0:
        POA = false;
        break;
      case 1:
        POA = true;
        break;
      case 4:
        if (POA == true) {
          KeyBack = 255;
          arduboy.clear();
          arduboy.setCursor(0, 0);
          arduboy.println(F(" >About"));
          arduboy.println(F(""));
          arduboy.println(F("LHW programming"));
          arduboy.println(F("LHW Art"));
          arduboy.println(F("E-mail"));
          arduboy.println(F("1281702594@qq.com"));
          arduboy.println(F(""));
          arduboy.println(F("Any key back..."));
          arduboy.display();
          delay(200);
          while (KeyBack == 255) key();
          delay(200);
        }
        break;
    }
    arduboy.clear();
    arduboy.drawSlowXYBitmap(39, 1, START_TITLE , 87, 39, 1);  //大标题
    arduboy.drawSlowXYBitmap(0, 23, TITLE_TNT , 37, 41, 1);    //TNT图标
    arduboy.drawSlowXYBitmap(65, 58, LHW , 39, 5, 1);          //作者信息
    arduboy.setCursor(70, 39);
    arduboy.println(F("PLAY"));
    arduboy.setCursor(70, 47);
    arduboy.println(F("ABOUT"));
    if (POA == false) arduboy.setCursor(62, 39); else arduboy.setCursor(62, 47);
    arduboy.println(F("*"));
    arduboy.display();
  }
}[/mw_shl_code]



0.png

该用户从未签到

 楼主| 发表于 2018-9-17 19:41 | 显示全部楼层
本帖最后由 createskyblue 于 2018-10-3 21:31 编辑

更新:2018/9/21 怪物伤害玩家代码中,忘记判断怪物是否死亡,导致死亡并且隐藏的怪物伤害玩家,无缘无故扣血 GITHUB将会发布推送更新


如何正确运行游戏
游戏文件:
游客,如果您要查看本帖隐藏内容请回复

传统的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
3.打开Arduloader
游戏hex文件在上面的游戏下载附件的压缩包里
该画面为成功上传,现在oled上应该有画面

方法二 编译安装    可以参考下面   假若有Arduboy可以直接编译
https://www.arduino.cn/forum.php?mod=viewthread&tid=80103&highlight=arduboy
方法三 在线模拟器
游客,如果您要查看本帖隐藏内容请回复

该用户从未签到

发表于 2018-10-9 14:07 | 显示全部楼层
太棒了 终于找到了

该用户从未签到

发表于 2018-10-29 15:50 | 显示全部楼层

太棒了 终于找到了

该用户从未签到

发表于 2018-11-3 09:41 | 显示全部楼层
棒,学习到了!!!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

热门推荐

点灯科技的blink库有没有设备到设备间分享数据功能啊
点灯科技的blink库有没有
请问下,点灯科技的blink库里有没有接受其他设备的函数程序啊? 我想设计4个8266设
OLED 128*64自制可达10000000个选项的菜单(已更新)
OLED 128*64自制可达10000
OLED 128*64自制可达10000000个选项的菜单 温馨提示: 建议占个楼再食用本帖子
DIY炫彩灯带,竟如此简单,更有硬件开发工具免费领!
DIY炫彩灯带,竟如此简单
什么是涂鸦Arduino SDK? Arduino 是全球最流行的开源硬件平台,涂鸦官方推出的 Ardui
ESP8266利用Blinker、小爱同学和本地按钮控制4路开关
ESP8266利用Blinker、小爱
案例介绍:小爱同学(安卓、苹果、小爱音响均可)控制4路开关 硬件需求:ESP8266 node
Seeeduion XIAO
Seeeduion XIAO
请问有那位大神用Seeeduion XIAO做过HID设备吗 求教程 网上有些说要用 TinyUSB来做 不
Copyright   ©2015-2016  Arduino中文社区  Powered by©Discuz!   
快速回复 返回顶部 返回列表