亿迅智能制造网
工业4.0先进制造技术信息网站!
首页 | 制造技术 | 制造设备 | 工业物联网 | 工业材料 | 设备保养维修 | 工业编程 |
home  MfgRobots >> 亿迅智能制造网 >  >> Manufacturing Technology >> 制造工艺

ATtiny85 迷你街机:贪吃蛇 — 打造掌上 OLED 游戏机

组件与物料

Microchip ATtiny85×1
128×64 图形 OLED 屏 (I2C)×1
SparkFun microB USB 电源板×1
Arduino Nano R3 (用于烧录 ATtiny85)×1

必需工具

应用与在线服务

项目简介

灵感与前作

2017 年底我曾制作过基于 Arduino Nano 与 OLED 的手持 Pong 游戏机,但体积笨重。近期重制旧项目,目标是做一个极致小巧的贪吃蛇掌机。

ATtiny85 迷你街机:贪吃蛇 — 打造掌上 OLED 游戏机
ATtiny85 迷你街机:贪吃蛇 — 打造掌上 OLED 游戏机

核心元件选型

为实现微型化,放弃 Arduino 开发板,改用 ATtiny85 裸片。它提供足够 I/O:两路 ADC、一路 I2C、一路 GPIO。输入采用简易 2 轴摇杆/按键模块,仅需 3 根信号线。显示选用 DFRobot 128×64 OLED,尺寸小巧分辨率足够。

ATtiny85 迷你街机:贪吃蛇 — 打造掌上 OLED 游戏机
ATtiny85 迷你街机:贪吃蛇 — 打造掌上 OLED 游戏机
ATtiny85 迷你街机:贪吃蛇 — 打造掌上 OLED 游戏机

焊接系统

将 ATtiny85 焊接在 SOP-8 转 DIP-8 转接板上,再焊到千孔板。焊接两排排针:一排用于编程烧录,一排连 OLED。接着接摇杆信号线,最后接 micro USB 电源板供电。

ATtiny85 迷你街机:贪吃蛇 — 打造掌上 OLED 游戏机
ATtiny85 迷你街机:贪吃蛇 — 打造掌上 OLED 游戏机

外壳设计

使用 Fusion 360 建模:先建立实物元件模型并布局,再包裹设计外壳。造型致敬 80 年代街机,同时保持极小体积。

ATtiny85 迷你街机:贪吃蛇 — 打造掌上 OLED 游戏机
ATtiny85 迷你街机:贪吃蛇 — 打造掌上 OLED 游戏机
ATtiny85 迷你街机:贪吃蛇 — 打造掌上 OLED 游戏机
ATtiny85 迷你街机:贪吃蛇 — 打造掌上 OLED 游戏机

游戏编程

贪吃蛇逻辑简明:蛇身最大长度限 30 节(节省 RAM),吃满 29 个食物即胜利。用二维数组存储各节段坐标。蛇头移动时,前序坐标依次后移。吃食后在随机位置生成新食物。碰撞检测遍历所有节段坐标与蛇头比较,撞墙或撞身均判负。

ATtiny85 迷你街机:贪吃蛇 — 打造掌上 OLED 游戏机
ATtiny85 迷你街机:贪吃蛇 — 打造掌上 OLED 游戏机

游戏体验

上电等待画面初始化蛇头与食物,拨动摇杆控制移动吞食。游戏节奏紧凑,极适合碎片时间消遣,包装在指尖大小的复古街机盒中。

ATtiny85 迷你街机:贪吃蛇 — 打造掌上 OLED 游戏机

代码

ATtiny85 代码 (C/C++)

需先安装 U8g2lib

//#include <TinyWireM.h>
#include <U8g2lib.h>
U8G2_SSD1306_128X64_NONAME_1_SW_I2C u8g(U8G2_R0, /* clock=*/ 2, /* data=*/ 0, /* reset=*/ U8X8_PIN_NONE);
#define MAX_LENGTH 30
#define X 0
#define Y 1
#define JOYSTICK_X 2
#define JOYSTICK_Y 3
#define DIR_THRESH 300
uint8_t segmentPositions[MAX_LENGTH][2];
uint8_t headPosition[2] = {63, 31};
uint8_t foodPosition[2];
uint8_t tempPosition0[2], tempPosition1[2];
int segmentLength = 1;
void gameUpdate();
enum DIRECTIONS { RIGHT, DOWN, LEFT, UP } currentDirection;

void setup() {
  //TinyWireM.begin();
  u8g.begin();
  u8g.setPowerSave(0);
  pinMode(JOYSTICK_X, INPUT);
  pinMode(JOYSTICK_Y, INPUT);
  randomSeed(analogRead(0));
  beginGame();
}

void loop() {
  u8g.firstPage();
  do { gameUpdate(); u8g.setColorIndex(1); } while(u8g.nextPage());
}

void beginGame(){
  currentDirection = RIGHT;
  spawnFood();
  delay(1000);
}

bool checkCollisions(){
  for(int i=1; i<segmentLength; i++){
    if(segmentPositions[i][X] == headPosition[X] && segmentPositions[i][Y] == headPosition[Y]) return 1;
  }
  if(headPosition[X] <= 0 || headPosition[X] >= 128) return 1;
  if(headPosition[Y] <= 0 || headPosition[Y] >= 64) return 1;
  return false;
}

void spawnFood(){
  foodPosition[X] = random(5, 123);
  foodPosition[Y] = random(5, 60);
}

void checkFoodEaten(){
  if(headPosition[X] == foodPosition[X] || headPosition[Y] == foodPosition[Y]){
    segmentLength += 1;
    spawnFood();
  }
}

void updateDirection(){
  int joy_x_val = analogRead(JOYSTICK_X);
  int joy_y_val = analogRead(JOYSTICK_Y);
  if(joy_x_val <= DIR_THRESH) currentDirection = LEFT;
  else if(joy_x_val >= 1023-DIR_THRESH) currentDirection = RIGHT;
  else if(joy_y_val <= DIR_THRESH) currentDirection = UP;
  else if(joy_y_val >= 1023-DIR_THRESH) currentDirection = DOWN;
}

void displaySegments(){
  for(int s=0; s<segmentLength; s++) u8g.drawPixel(segmentPositions[s][X], segmentPositions[s][Y]);
  u8g.drawPixel(foodPosition[X], foodPosition[Y]);
}

void gameUpdate(){
  updateDirection();
  tempPosition0[X] = headPosition[X]; tempPosition0[Y] = headPosition[Y];
  switch(currentDirection){
    case UP: headPosition[Y] -= 1; break;
    case DOWN: headPosition[Y] += 1; break;
    case LEFT: headPosition[X] -= 1; break;
    case RIGHT: headPosition[X] += 1; break;
  }
  for(int seg=1; seg<segmentLength; seg+=2){
    tempPosition1[X] = segmentPositions[seg][X]; tempPosition1[Y] = segmentPositions[seg][Y];
    segmentPositions[seg][X] = tempPosition0[X]; segmentPositions[seg][Y] = tempPosition0[Y];
    tempPosition0[X] = segmentPositions[seg+1][X]; tempPosition0[Y] = segmentPositions[seg+1][Y];
    segmentPositions[seg+1][X] = tempPosition1[X]; segmentPositions[seg+1][Y] = tempPosition1[Y];
  }
  displaySegments();
  checkFoodEaten();
  if(checkCollisions()) endGame();
  if(segmentLength >= MAX_LENGTH) endGame();
  delay(50);
}

void endGame(){
  segmentLength = 1;
  headPosition[0] = 63; headPosition[1] = 31;
  beginGame();
}

自定义零件与外壳

(STL 文件见项目页面)

原理图

ATtiny85 迷你街机:贪吃蛇 — 打造掌上 OLED 游戏机

制造工艺

  1. 基于 Edge Impulse 的 TinyML 语言检测器:Arduino Nano 33 BLE Sense 实战
  2. Arduino Nano 与 MPU-6050 构建实时陀螺仪游戏
  3. Arduino 数字骰子:LCD 显示随机点数的电子骰子
  4. 用 Arduino Nano 自制 37 灯珠轮盘赌游戏机
  5. 便携测距仪 DIY 套件:Arduino Nano + HC-SR04 + 18650 电池 + 3D 打印外壳
  6. 声控氛围灯:Arduino Nano + 麦克风模块 + WS2812B 灯珠
  7. 铜电镀智能系统:基于 Arduino UNO 的可控电解装置
  8. Arduino Nano 驱动 NeoPixel 矩阵:自制霓虹乒乓游戏
  9. 使用 Arduino 移位寄存器创建绚丽灯光序列
  10. 智能插座:基于 Arduino 与 RTC 的 120V 定时开关插座
  11. Arduino 智能安防系统:超声波测距 + 舵机扫描 + 声光报警
  12. 用蜂鸣器和摇杆制作经典 Arduino LCD 街机游戏