산업 제조
산업용 사물 인터넷 | 산업자재 | 장비 유지 보수 및 수리 | 산업 프로그래밍 |
home  MfgRobots >> 산업 제조 >  >> Manufacturing Technology >> 제조공정

아두이노 플래피 버드 클론 만들기: TFT 터치스크린 게임 프로젝트 단계별 가이드

이 아두이노 프로젝트에서는 아두이노와 TFT 터치스크린을 사용해 인기 스마트폰 게임 '플래피 버드'의 복제품을 만들어보겠습니다. 아래 비디오를 시청하거나 작성된 글을 읽으며 작동 원리를 배워보세요.

개요

게임은 단순하지만 흥미롭고 중독적입니다. 터치스크린으로 새를 조종해 이동하는 기둥들을 피하며, 진행할수록 기둥 속도가 빨라집니다. 전원을 뽑아도 최고 점수가 저장됩니다.

아두이노 플래피 버드 클론 만들기: TFT 터치스크린 게임 프로젝트 단계별 가이드

이전 튜토리얼(아두이노 TFT 튜토리얼)에서 TFT 터치스크린을 아두이노와 사용하는 방법을 자세히 배웠고, 게임 예제는 이 튜토리얼에서 설명하기로 했습니다. 이전 튜토리얼처럼 단계별로 이 아두이노 게임의 코드를 설명하겠습니다.

소스 코드

코드가 다소 길고 이해를 돕기 위해 프로그램 소스 코드를 섹션별로 나누어 각 섹션 설명과 함께 게시합니다. 글 마지막에 전체 소스 코드를 올리겠습니다.

Henning Karlsen이 만든 UTFT와 URTouch 라이브러리를 사용합니다. 그의 웹사이트 www.RinkyDinkElectronics.com에서 다운로드할 수 있습니다. 최고 점수를 EEPROM에 저장하기 위해 EEPROM 라이브러리도 사용합니다. EEPROM은 보드 전원이 꺼져도 데이터를 저장할 수 있는 메모리입니다.

라이브러리를 포함시킨 후 UTFT와 URTouch 객체를 생성하고 게임에 필요한 변수들을 정의합니다. 설정 섹션에서는 디스플레이와 터치를 초기화하고, EEPROM에서 최고 점수를 읽고, initiateGame() 사용자 정의 함수로 게임을 초기화합니다.

#include <UTFT.h>
#include <URTouch.h>
#include <EEPROM.h>
//==== 객체 생성
UTFT myGLCD(SSD1289,38,39,40,41); // 파라미터는 사용 중인 디스플레이/실드 모델에 맞게 조정
URTouch myTouch(6, 5, 4, 3, 2);
//==== 폰트 정의
extern uint8_t SmallFont[];
extern uint8_t BigFont[];
extern uint8_t SevenSegNumFont[];
extern unsigned int bird01[0x41A]; // 새 비트맵
int x, y; // 디스플레이가 눌린 좌표 변수
// 플래피 버드
int xP = 319;
int yP = 100;
int yB = 50;
int movingRate = 3;
int fallRateInt = 0;
float fallRate = 0;
int score = 0;
int lastSpeedUpScore = 0;
int highestScore;
boolean screenPressed = false;
boolean gameStarted = false;
void setup() {
  // 디스플레이 초기화
  myGLCD.InitLCD();
  myGLCD.clrScr();
  myTouch.InitTouch();
  myTouch.setPrecision(PREC_MEDIUM);
  highestScore = EEPROM.read(0); // EEPROM에서 최고 점수 읽기
  initiateGame(); // 게임 초기화
}

initiateGame() 사용자 정의 함수로 게임의 초기 상태를 그립니다. 화면을 지우고 파란 배경, 바닥 부분 그리기, 텍스트 추가, drawBird() 함수로 새 그리기 순서입니다. 그런 다음 화면을 탭할 때까지 게임이 시작되지 않도록 while 루프를 사용합니다. 이 상태에서 오른쪽 상단을 누르면 최고 점수를 0으로 리셋하고, 화면 아무 곳이나 누르면 while 루프를 벗어나 메인 루프로 진입해 게임이 시작됩니다.

아두이노 플래피 버드 클론 만들기: TFT 터치스크린 게임 프로젝트 단계별 가이드
// ===== initiateGame - 사용자 정의 함수
void initiateGame() {
  myGLCD.clrScr();
  // 파란 배경
  myGLCD.setColor(114, 198, 206);
  myGLCD.fillRect(0,0,319,239);
  // 바닥
  myGLCD.setColor(221,216,148);
  myGLCD.fillRect(0, 215, 319, 239);
  myGLCD.setColor(47,175,68);
  myGLCD.fillRect(0, 205, 319, 214);
  // 텍스트
  myGLCD.setColor(0, 0, 0);
  myGLCD.setBackColor(221, 216, 148);
  myGLCD.setFont(BigFont);
  myGLCD.print("Score:",5,220);
  myGLCD.setFont(SmallFont);
  myGLCD.print("HowToMechatronics.com", 140, 220);
  myGLCD.setColor(0, 0, 0);
  myGLCD.setBackColor(114, 198, 206);
  myGLCD.print("Highest Score: ",5,5);
  myGLCD.printNumI(highestScore, 120, 6);
  myGLCD.print(">RESET<",255,5);
  myGLCD.drawLine(0,23,319,23);
  myGLCD.print("TAP TO START",CENTER,100);
  drawBird(yB); // 새 그리기
  // 화면을 탭할 때까지 대기
  while (!gameStarted) {
    if (myTouch.dataAvailable()) {
      myTouch.read();
      x=myTouch.getX();
      y=myTouch.getY();
      // 최고 점수 리셋
      if ((x>=250) && (x<=319) &&(y>=0) && (y<=28)) {
        highestScore = 0;
        myGLCD.setColor(114, 198, 206);
        myGLCD.fillRect(120, 0, 150, 22);
        myGLCD.setColor(0, 0, 0);
        myGLCD.printNumI(highestScore, 120, 5);
      }
      if ((x>=0) && (x<=319) &&(y>=30) && (y<=239)) {
        gameStarted = true;
        myGLCD.setColor(114, 198, 206);
        myGLCD.fillRect(0, 0, 319, 32);
      }
    }
  }
  // 게임 시작 전 "TAP TO START" 텍스트 지우기
  myGLCD.setColor(114, 198, 206);
  myGLCD.fillRect(85, 100, 235, 116);
}

메인 루프 섹션에서 xP 변수는 기둥 그리기에 사용되며 yP 변수도 함께 쓰입니다. 처음에 xP는 화면 크기인 319, yP는 첫 번째 기둥 높이인 100입니다. 각 반복마다 xP 값이 movingRate 변수 값만큼 감소하며, movingRate는 처음에 3이고 게임 진행에 따라 증가합니다.

// 메인 루프 섹션
void loop() {
  xP = xP - movingRate; // xP - 기둥의 x 좌표; 범위: 319 ~ (-51)
  drawPilars(xP, yP); // 기둥 그리기
  // yB - 새의 y 좌표, fallingRate 값에 따라 결정
  yB += fallRateInt;
  fallRate = fallRate + 0.4; // 각 반복마다 낙하율 증가로 중력/가속도 효과
  fallRateInt = int(fallRate);
  // 충돌 체크
  if(yB >= 180 || yB <= 0) { // 위아래
    gameOver();
  }
  if((xP <= 85) && (xP >= 5) && (yB <= yP-2)) { // 위쪽 기둥
    gameOver();
  }
  if((xP <= 85) && (xP >= 5) && (yB >= yP+60)) { // 아래쪽 기둥
    gameOver();
  }
  // 새 그리기
  drawBird(yB);
  // 기둥이 화면을 통과한 후
  if (xP <= -51) {
    xP = 319; // xP를 319로 리셋
    yP = rand() % 100 + 20; // 기둥 높이에 대한 랜덤 숫자
    score++; // 점수 1 증가
  }
  //==== 새 조종
  if (myTouch.dataAvailable() && !screenPressed) {
    fallRate = -6; // 낙하율을 음수로 설정해 새가 점프
    screenPressed = true;
  }
  // 화면 누르기 방지 / 탭해야 함
  else if (!myTouch.dataAvailable() && screenPressed) {
    screenPressed = false;
  }
  // 5점마다 기둥 이동 속도 증가
  if ((score - lastSpeedUpScore) == 5) {
    lastSpeedUpScore = score;
    movingRate++;
  }
}

게임 작동 원리: 50픽셀 폭의 기둥이 오른쪽에서 왼쪽으로 이동하며, 각 기둥마다 랜덤한 높이를 가집니다. 논리적으로 각 반복마다 화면을 지우고 기둥을 새 위치에 다시 그려야 하지만, 화면 재생률이 낮아 그래픽이 깜빡입니다. 화면이 모든 픽셀을 활성화하는 데 시간이 더 필요하므로, 움직이는 것만 다시 그리는 방식으로 타협합니다.

아두이노 플래피 버드 클론 만들기: TFT 터치스크린 게임 프로젝트 단계별 가이드

drawPilars() 사용자 정의 함수가 어떻게 이를 수행하는지 봅시다. xP와 yP 변수를 받아 fillRect() 함수로 기둥을 그립니다. 각 반복마다 기둥을 새 위치에 그리고 좌우에 추가 파란 직사각형을 그려 이전 기둥을 지웁니다. 이렇게 움직이는 기둥만 다시 그리는 타협을 합니다. fillRect() 함수의 'x2' 매개변수가 화면 크기를 벗어나는 값일 때 작동하지 않는 이유로 if 문이 추가되었습니다. 이 함수 마지막에 도달한 점수 값을 출력합니다.

// ===== drawPilars - 사용자 정의 함수
void drawPilars(int x, int y) {
  if (x >= 270) {
    myGLCD.setColor(0, 200, 20);
    myGLCD.fillRect(318, 0, x, y-1);
    myGLCD.setColor(0, 0, 0);
    myGLCD.drawRect(319, 0, x-1, y);
    myGLCD.setColor(0, 200, 20);
    myGLCD.fillRect(318, y+81, x, 203);
    myGLCD.setColor(0, 0, 0);
    myGLCD.drawRect(319, y+80, x-1, 204);
  } else if (x <= 268) {
    // 기둥 오른쪽 파란 직사각형
    myGLCD.setColor(114, 198, 206);
    myGLCD.fillRect(x+51, 0, x+60, y);
    // 기둥 그리기
    myGLCD.setColor(0, 200, 20);
    myGLCD.fillRect(x+49, 1, x+1, y-1);
    // 기둥 검은 테두리
    myGLCD.setColor(0, 0, 0);
    myGLCD.drawRect(x+50, 0, x, y);
    // 기둥 왼쪽 파란 직사각형
    myGLCD.setColor(114, 198, 206);
    myGLCD.fillRect(x-1, 0, x-3, y);
    // 아래쪽 기둥
    myGLCD.setColor(114, 198, 206);
    myGLCD.fillRect(x+51, y+80, x+60, 204);
    myGLCD.setColor(0, 200, 20);
    myGLCD.fillRect(x+49, y+81, x+1, 203);
    myGLCD.setColor(0, 0, 0);
    myGLCD.drawRect(x+50, y+80, x, 204);
    myGLCD.setColor(114, 198, 206);
    myGLCD.fillRect(x-1, y+80, x-3, 204);
  }
  // 점수 그리기
  myGLCD.setColor(0, 0, 0);
  myGLCD.setBackColor(221, 216, 148);
  myGLCD.setFont(BigFont);
  myGLCD.printNumI(score, 100, 220);
}

루프 섹션으로 돌아가면 yB 변수는 새의 y 위치이며 낙하율에 따라 결정됩니다. 각 반복마다 낙하율이 증가해 중력/가속도 효과를 냅니다. 충돌 체크로 새가 위, 바닥, 기둥에 부딪히면 게임 오버 처리합니다.

아두이노 플래피 버드 클론 만들기: TFT 터치스크린 게임 프로젝트 단계별 가이드

drawBird() 사용자 정의 함수입니다. 새는 Henning Karlsen의 ImageConverter565 도구로 비트맵으로 변환한 사진입니다. 생성된 ".c" 파일을 디렉토리에 포함시켜 스케치 실행 시 로드되게 합니다. 비트맵을 이렇게 정의하고 drawBitmap() 함수로 화면에 그립니다. 새는 고정된 X 좌표와 yB 변수를 Y 좌표로 가집니다. 기둥과 비슷하게 새 위아래에 파란 직사각형을 그려 이전 상태를 지웁니다.

//====== drawBird() - 사용자 정의 함수
void drawBird(int y) {
  // 새 그리기 - 비트맵
  myGLCD.drawBitmap(50, y, 35, 30, bird01);
  // 새 이전 상태 지우기 위해 위아래 파란 직사각형 그리기
  myGLCD.setColor(114, 198, 206);
  myGLCD.fillRoundRect(50, y, 85, y-6);
  myGLCD.fillRoundRect(50, y+30, 85, y+36);
}

루프에서 기둥이 화면을 통과하면 xP를 319로 리셋, yP에 20~100 사이 랜덤 값으로 새 높이 지정, 점수 1 증가. 다음 if 문으로 새 조종: 화면 탭 시 낙하율을 음수로 설정해 점프, else if로 화면 누르기 방지. 마지막 if 문은 게임 난이도로 5점마다 기둥 이동 속도 증가.

남은 건 gameOver() 사용자 정의 함수 작동 원리입니다. 1초 지연 후 화면 지우고 점수와 텍스트 출력, 점수가 최고 점수보다 높으면 EEPROM에 기록, 모든 변수를 시작 위치 값으로 리셋, initiateGame() 호출로 게임 재시작.

아두이노 플래피 버드 클론 만들기: TFT 터치스크린 게임 프로젝트 단계별 가이드
//======== gameOver() - 사용자 정의 함수
void gameOver() {
  delay(1000); // 1초
  // 화면 지우고 텍스트 출력
  myGLCD.clrScr();
  myGLCD.setColor(255, 255, 255);
  myGLCD.setBackColor(0, 0, 0);
  myGLCD.setFont(BigFont);
  myGLCD.print("GAME OVER", CENTER, 40);
  myGLCD.print("Score:", 100, 80);
  myGLCD.printNumI(score, 200, 80);
  myGLCD.print("Restarting...", CENTER, 120);
  myGLCD.setFont(SevenSegNumFont);
  myGLCD.printNumI(2, CENTER, 150);
  delay(1000);
  myGLCD.printNumI(1, CENTER, 150);
  delay(1000);
  // 최고 점수 EEPROM에 기록
  if (score > highestScore) {
    highestScore = score;
    EEPROM.write(0, highestScore);
  }
  // 변수를 시작 위치 값으로 리셋
  xP = 319;
  yB = 50;
  fallRate = 0;
  score = 0;
  lastSpeedUpScore = 0;
  movingRate = 3;
  gameStarted = false;
  // 게임 재시작
  initiateGame();
}

이상으로 코드 설명을 마칩니다. 설명이 충분히 명확했길 바랍니다. 질문 있으면 댓글 섹션에 남겨주세요.

게임 전체 코드:

/* 아두이노 게임 프로젝트
 * Program made by Dejan Nedelkovski,
 * www.HowToMechatronics.com
 */
/* 이 프로그램은 Henning Karlsen이 만든 UTFT와 URTouch 라이브러리를 사용합니다.
 * www.RinkyDinkElectronics.com에서 찾고 다운로드할 수 있습니다.
 */
#include <UTFT.h>
#include <URTouch.h>
#include <EEPROM.h>
//==== 객체 생성
UTFT myGLCD(SSD1289,38,39,40,41); // 파라미터는 사용 중인 디스플레이/실드 모델에 맞게 조정
URTouch myTouch(6, 5, 4, 3, 2);
//==== 폰트 정의
extern uint8_t SmallFont[];
extern uint8_t BigFont[];
extern uint8_t SevenSegNumFont[];
extern unsigned int bird01[0x41A]; // 새 비트맵
int x, y; // 디스플레이가 눌린 좌표 변수
// 플래피 버드
int xP = 319;
int yP = 100;
int yB = 50;
int movingRate = 3;
int fallRateInt = 0;
float fallRate = 0;
int score = 0;
int lastSpeedUpScore = 0;
int highestScore;
boolean screenPressed = false;
boolean gameStarted = false;
void setup() {
  // 디스플레이 초기화
  myGLCD.InitLCD();
  myGLCD.clrScr();
  myTouch.InitTouch();
  myTouch.setPrecision(PREC_MEDIUM);
  highestScore = EEPROM.read(0); // EEPROM에서 최고 점수 읽기
  initiateGame(); // 게임 초기화
}
void loop() {
  xP = xP - movingRate; // xP - 기둥의 x 좌표; 범위: 319 ~ (-51)
  drawPilars(xP, yP); // 기둥 그리기
  // yB - 새의 y 좌표, fallingRate 값에 따라 결정
  yB += fallRateInt;
  fallRate = fallRate + 0.4; // 각 반복마다 낙하율 증가로 중력/가속도 효과
  fallRateInt = int(fallRate);
  // 충돌 체크
  if(yB >= 180 || yB <= 0) { // 위아래
    gameOver();
  }
  if((xP <= 85) && (xP >= 5) && (yB <= yP-2)) { // 위쪽 기둥
    gameOver();
  }
  if((xP <= 85) && (xP >= 5) && (yB >= yP+60)) { // 아래쪽 기둥
    gameOver();
  }
  // 새 그리기
  drawBird(yB);
  // 기둥이 화면을 통과한 후
  if (xP <= -51) {
    xP = 319; // xP를 319로 리셋
    yP = rand() % 100 + 20; // 기둥 높이에 대한 랜덤 숫자
    score++; // 점수 1 증가
  }
  //==== 새 조종
  if (myTouch.dataAvailable() && !screenPressed) {
    fallRate = -6; // 낙하율을 음수로 설정해 새가 점프
    screenPressed = true;
  }
  // 화면 누르기 방지 / 탭해야 함
  else if (!myTouch.dataAvailable() && screenPressed) {
    screenPressed = false;
  }
  // 5점마다 기둥 이동 속도 증가
  if ((score - lastSpeedUpScore) == 5) {
    lastSpeedUpScore = score;
    movingRate++;
  }
}
// ===== initiateGame - 사용자 정의 함수
void initiateGame() {
  myGLCD.clrScr();
  // 파란 배경
  myGLCD.setColor(114, 198, 206);
  myGLCD.fillRect(0,0,319,239);
  // 바닥
  myGLCD.setColor(221,216,148);
  myGLCD.fillRect(0, 215, 319, 239);
  myGLCD.setColor(47,175,68);
  myGLCD.fillRect(0, 205, 319, 214);
  // 텍스트
  myGLCD.setColor(0, 0, 0);
  myGLCD.setBackColor(221, 216, 148);
  myGLCD.setFont(BigFont);
  myGLCD.print("Score:",5,220);
  myGLCD.setFont(SmallFont);
  myGLCD.print("HowToMechatronics.com", 140, 220);
  myGLCD.setColor(0, 0, 0);
  myGLCD.setBackColor(114, 198, 206);
  myGLCD.print("Highest Score: ",5,5);
  myGLCD.printNumI(highestScore, 120, 6);
  myGLCD.print(">RESET<",255,5);
  myGLCD.drawLine(0,23,319,23);
  myGLCD.print("TAP TO START",CENTER,100);
  drawBird(yB); // 새 그리기
  // 화면을 탭할 때까지 대기
  while (!gameStarted) {
    if (myTouch.dataAvailable()) {
      myTouch.read();
      x = myTouch.getX();
      y = myTouch.getY();
      // 최고 점수 리셋
      if ((x>=250) && (x<=319) &&(y>=0) && (y<=28)) {
        highestScore = 0;
        myGLCD.setColor(114, 198, 206);
        myGLCD.fillRect(120, 0, 150, 22);
        myGLCD.setColor(0, 0, 0);
        myGLCD.printNumI(highestScore, 120, 5);
      }
      if ((x>=0) && (x<=319) &&(y>=30) && (y<=239)) {
        gameStarted = true;
        myGLCD.setColor(114, 198, 206);
        myGLCD.fillRect(0, 0, 319, 32);
      }
    }
  }
  // 게임 시작 전 "TAP TO START" 텍스트 지우기
  myGLCD.setColor(114, 198, 206);
  myGLCD.fillRect(85, 100, 235, 116);
}
// ===== drawPilars - 사용자 정의 함수
void drawPilars(int x, int y) {
  if (x >= 270) {
    myGLCD.setColor(0, 200, 20);
    myGLCD.fillRect(318, 0, x, y-1);
    myGLCD.setColor(0, 0, 0);
    myGLCD.drawRect(319, 0, x-1, y);
    myGLCD.setColor(0, 200, 20);
    myGLCD.fillRect(318, y+81, x, 203);
    myGLCD.setColor(0, 0, 0);
    myGLCD.drawRect(319, y+80, x-1, 204);
  } else if (x <= 268) {
    // 기둥 오른쪽 파란 직사각형
    myGLCD.setColor(114, 198, 206);
    myGLCD.fillRect(x+51, 0, x+60, y);
    // 기둥 그리기
    myGLCD.setColor(0, 200, 20);
    myGLCD.fillRect(x+49, 1, x+1, y-1);
    // 기둥 검은 테두리
    myGLCD.setColor(0, 0, 0);
    myGLCD.drawRect(x+50, 0, x, y);
    // 기둥 왼쪽 파란 직사각형
    myGLCD.setColor(114, 198, 206);
    myGLCD.fillRect(x-1, 0, x-3, y);
    // 아래쪽 기둥
    myGLCD.setColor(114, 198, 206);
    myGLCD.fillRect(x+51, y+80, x+60, 204);
    myGLCD.setColor(0, 200, 20);
    myGLCD.fillRect(x+49, y+81, x+1, 203);
    myGLCD.setColor(0, 0, 0);
    myGLCD.drawRect(x+50, y+80, x, 204);
    myGLCD.setColor(114, 198, 206);
    myGLCD.fillRect(x-1, y+80, x-3, 204);
  }
  // 점수 그리기
  myGLCD.setColor(0, 0, 0);
  myGLCD.setBackColor(221, 216, 148);
  myGLCD.setFont(BigFont);
  myGLCD.printNumI(score, 100, 220);
}
//====== drawBird() - 사용자 정의 함수
void drawBird(int y) {
  // 새 그리기 - 비트맵
  myGLCD.drawBitmap(50, y, 35, 30, bird01);
  // 새 이전 상태 지우기 위해 위아래 파란 직사각형 그리기
  myGLCD.setColor(114, 198, 206);
  myGLCD.fillRoundRect(50, y, 85, y-6);
  myGLCD.fillRoundRect(50, y+30, 85, y+36);
}
//======== gameOver() - 사용자 정의 함수
void gameOver() {
  delay(3000); // 1초
  // 화면 지우고 텍스트 출력
  myGLCD.clrScr();
  myGLCD.setColor(255, 255, 255);
  myGLCD.setBackColor(0, 0, 0);
  myGLCD.setFont(BigFont);
  myGLCD.print("GAME OVER", CENTER, 40);
  myGLCD.print("Score:", 100, 80);
  myGLCD.printNumI(score, 200, 80);
  myGLCD.print("Restarting...", CENTER, 120);
  myGLCD.setFont(SevenSegNumFont);
  myGLCD.printNumI(2, CENTER, 150);
  delay(1000);
  myGLCD.printNumI(1, CENTER, 150);
  delay(1000);
  // 최고 점수 EEPROM에 기록
  if (score > highestScore) {
    highestScore = score;
    EEPROM.write(0, highestScore);
  }
  // 변수를 시작 위치 값으로 리셋
  xP = 319;
  yB = 50;
  fallRate = 0;
  score = 0;
  lastSpeedUpScore = 0;
  movingRate = 3;
  gameStarted = false;
  // 게임 재시작
  initiateGame();
}

아두이노 스케치, 새 이미지, 새 비트맵 파일이 포함된 다운로드 파일을 제공합니다.

제조공정

  1. 아두이노로 모니터 앰비라이트(Ambilight) 시스템 직접 만들기
  2. 아두이노 우노와 OLED 디스플레이로 클래식 퐁(Pong) 게임 만들기: 단계별 튜토리얼
  3. DIY 아두이노 USB 게임 컨트롤러 - 나만의 고성능 게임패드 만들기
  4. 아두이노 터치 브레이크아웃 게임: 2.4" TFT LCD로 클래식 아케이드 구현
  5. 거대 레고 미니피겨 아두이노 아나마트로닉스 오퍼레이션 게임 제작기
  6. 아두이노 오디오 주파수 감지기 - 고감도 사운드 모듈로 최대 음량 피크 주파수 측정
  7. 아두이노 틱택토 게임 - MAX7219 LED 매트릭스와 골판지 케이스
  8. 스파크펀 7-세그먼트 디스플레이로 신뢰할 수 있는 아두이노 카운트다운 타이머 만들기
  9. 픽셀 체이서: 아두이노 나노로 만든 인터랙티브 원탭 LED 게임
  10. 아두이노로 직접 만드는 RC 포르쉐 자동차: 단계별 가이드