ATtiny85 미니 아케이드: 스네이크 게임 - 휴대용 OLED 게임기 만들기
부품 및 소모품
| 부품 |
수량 |
| 마이크로칩 ATtiny85 |
1 |
| 그래픽 OLED 128x64 |
1 |
| 스파크펀 마이크로B USB 브레이크아웃 |
1 |
| 아두이노 나노 R3 (프로그래밍용) |
1 |
필요한 도구 및 장비
앱 및 온라인 서비스
- 오토데스크 퓨전 360 (케이스 설계)
- 아두이노 IDE
프로젝트 개요
영감과 이전 프로젝트
2017년 12월, 아두이노 나노와 OLED, 버튼 2개로 휴대용 퐁(Pong) 콘솔을 만들었습니다.
당시엔 괜찮았지만 크고 번거로웠습니다. 최근 과거 프로젝트를 재구성하며 스네이크 게임이 돌아가는 초소형 콘솔을 만들기로 했습니다.
선정된 부품
소형화를 위해 아두이노 개발 보드 대신 ATtiny85를 선택했습니다.
충분한 I/O(ADC 2개, I2C 포트, GPIO 1개)를 제공합니다. 조작은 심플한 2축 조이스틱/스위치 모듈로, 신호 핀 3개만 필요합니다.
디스플레이는 DF로봇의 128x64 OLED로, 크기는 작지만 해상도가 충분합니다.
시스템 납땜 조립
ATtiny85를 SOP-8 to DIP-8 브레이크아웃 PCB에 장착해 소형 퍼프보드에 납땜. 프로그래밍용 헤더와 화면용 헤더를 달고, 조이스틱 핀을 연결한 뒤 마이크로 USB 브레이크아웃 보드로 전원 공급선을 연결했습니다.
인클로저 설계 (퓨전 360)
퓨전 360에서 실제 부품들을 배치하고 주변을 감싸는 인클로저를 설계했습니다. 80년대 아케이드 박스 느낌을 내면서도 초소형 크기 유지.
게임 프로그래밍
스네이크는 프로그래밍하기 쉬운 게임입니다. RAM 절약을 위해 최대 뱀 길이를 30으로 제한(먹이 29개 먹으면 승리). 뱀 몸통 좌표를 저장하는 2차원 배열 사용.
머리가 이동하면 이전 위치들이 연쇄적으로 아래로 전달됩니다. 먹이 섭취 시 새 세그먼트가 랜덤 위치에 생성. 충돌 검사는 각 세그먼트 좌표와 머리 좌표 비교, 벽 충돌도 감지.
스네이크 플레이
전원 켜고 화면 로드 대기 후 조이스틱으로 뱀 조종하며 먹이 먹기. 작은 패키지에 담긴 재미있는 시간 때우기 게임입니다.
코드
ATTiny85 Code 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];
uint8_t 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]){
if(segmentPositions[i][Y] == headPosition[Y]) return 1;
}
}
if(headPosition[X] <= 0 || headPosition[X] >= 128) return 1;
else if(headPosition[Y] <= 0 || headPosition[Y] >= 64) return 1;
return false;
}
void spawnFood(){
int randomX = random(5, 123);
int randomY = random(5, 60);
foodPosition[X] = randomX;
foodPosition[Y] = randomY;
}
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 segment=0; segment < segmentLength; segment++){
u8g.drawPixel(segmentPositions[segment][X], segmentPositions[segment][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 segment=1; segment < segmentLength; segment+= 2){
tempPosition1[X] = segmentPositions[segment][X];
tempPosition1[Y] = segmentPositions[segment][Y];
segmentPositions[segment][X] = tempPosition0[X];
segmentPositions[segment][Y] = tempPosition0[Y];
tempPosition0[X] = segmentPositions[segment+1][X];
tempPosition0[Y] = segmentPositions[segment+1][Y];
segmentPositions[segment+1][X] = tempPosition1[X];
segmentPositions[segment+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();
}
커스텀 파트 및 인클로저
(파일 별도 제공)
회로도