Arduino X Vibe Coding 五分鐘做出小遊戲Part 1 ---- 做出 Chrome Dinosaur Game
專案資訊
前言 Vibe Coding 是什麼?
它是一種透過 自然語言與 AI 互動 來生成程式碼的軟體開發方式,而不是手寫每一行程式碼。由 OpenAI 前共同創辦人 Andrej Karpathy 首創這個概念,意在讓人專注在思路與需求,而非語法與細節。這降低了技術門檻,讓更多人能從想法直接實現功能出來。 現今大型語言模型(如Chat GPT、Gemini….等)百花齊放,寫程式的門檻也越來越低,筆者在看Nvidia 執行長黃仁勳接受 Podcast 訪問時,提到我們要學會如何與 AI 聊天和互動,並學習擅長提問,利用 AI 獲取知識,進而達到我們的目的。 黃仁勳訪談影片: https://www.instagram.com/reels/DUkRfywgseS/ 筆者將透過 Vibe Coding 搭配 《Arduino 首次接觸就上手》硬體教材,將設計多種小遊戲開發,讓各位可以透過用自然語言的方式向生成式 AI對話,進而將自己的想法能夠實現出來。以下筆者是使用 Chat GPT 來對話。內文開始 先來看 Demo 成果
[video width="1920" height="1080" mp4="https://cd.ainfinity.tw/media/2026/02/Vibe-Coding-五分鐘做出小遊戲-fin.mp4"][/video]硬體架構
首先開發小遊戲所使用的硬體是《Arduino 首次接觸就上手》,購買資訊請見上方專案資訊的材料表一欄。 《Arduino 首次接觸就上手》 好處就是感測器不需要額外接線,感測器是直接焊好的,而此專案使用的感測器為 OLED 0.96"、無源蜂鳴器及按鈕,感測器和腳位介紹見下方圖示
-
下載/開啟 Arduino IDE
下載後解壓縮,路徑位置在HanGeekduino\BlocklyDuino3\arduino-1.8.5,再開啟arduino.exe即可。
本次專案目標:做出 Chrome Dinosaur Game
有了硬體後,筆者來分享一下為何想做出這次的小遊戲。 相信許多人都玩過 Google Chrome 瀏覽器斷網時出現的小遊戲 —— Chrome Dinosaur Game(又稱 T-Rex Game)。 某次筆者在高中用《Arduino 首次接觸就上手》上課時發現,部分學生已開始利用 ChatGPT 或 Gemini 生成程式碼並做出類似像 Chrome 恐龍跳躍小遊戲。這也顯示生成式 AI 正逐步改變學習方式,使 Vibe Coding 成為新的學習模式。 所以本專題將以 Vibe Coding 方式,實作 Arduino 版 Chrome Dinosaur Game 。輸入提示詞 (Prompt)到 ChatGPT 產生遊戲程式
筆者是使用 ChatGPT 生成程式碼,當然讀者也可以使用其他生成式 AI 如: Gemini 、Claude...等,程式執行結果也許會與筆者不同,但這個 Vibe Coding 的過程是一樣的,請讀者開啟喜歡的生成式 AI來輸入提示詞。
Chrome Dinosaur Game 遊戲流程圖如下:
這是一個典型的「狀態機遊戲流程」,遊戲會在「開始 → 遊戲中 → 結束」三種狀態之間切換,筆者將狀態、功能連同使用的硬體都轉成提示詞,傳達給 ChatGPT 。
以下是我向 ChatGPT 提出的需求(初版):
以下是我向 ChatGPT 提出的需求(初版):
? 我現在要用 Arduino 程式做出來,我有按鈕和 0.96 吋 OLED。
? 開機顯示 "Game Start, PLS Press Button",按下按鈕開始遊戲。
? 用方塊代表角色與障礙物,按鈕控制跳躍,躲過得 10 分,撞到顯示 Game Over,10 秒後回到開始畫面。
將上述需求貼到ChatGPT,寫越詳細越好,這時 ChatGPT 就會生成基本功能的程式碼,但是我們的硬體是《Arduino 首次接觸就上手》,所以第一次生成的程式碼如果燒錄有錯誤是正常的,因為每個人或每個模型所產的程式都不一樣,所以要針對錯誤,必須進一步優化我們的Prompt。
程式執行遇到錯誤
筆者將第一次產生的程式碼燒錄到 Arduino ,但很顯然失敗遇到錯誤。
這時候不用緊張,將錯誤訊息複製,或按 Copy error message ,貼回 ChatGPT 即可得到解答。
ChatGPT 解釋因為 OLED 函示庫 U8X8lib 沒有繪圖功能,所以有錯,然而會再次生成修正過後的程式碼。
而筆者提供的 Arduino IDE 中含有 U8g2lib 的函示庫,所以就可以跳過安裝步驟。
再將生成過後的程式碼上傳至《Arduino 首次接觸就上手》中,但顯然還是會遇見問題。
優化 Prompt(修正 OLED 顯示方向與按鈕腳位)
筆者遇到 2 個問題,OLED 顯示方向有錯和按鈕腳位有錯(見下圖),
因為筆者之前有遇過一樣問題, OLED 顯示方向就是把 U8G2_R0 改成 U8G2_R2 即可,讀者若不知道的話可以詢問 ChatGPT ,並重新生成正確的程式碼。
那筆者除了錯誤之外,還想新增功能,便進一步向 ChatGPT下優化 Prompt:
修改2個地方,和新增功能:
? 改成 U8G2_R2
? 按鈕是 D6 腳位
另外要新增無源蜂鳴器聲音,跳躍和 Game Over 都要不同音效,無源蜂鳴器是 D5 腳位

上述的提示語,除了跟ChatGPT說 OLED 的顯示角度有誤,也進一步說明按鈕的腳位是D6,補足第一次沒有完整說明腳位編號,最後也提出需要有音效的產生。
這時候產出來的程式碼,已經是可以運行了,但有些細節和程式執行上有問題,筆者遇到的問題是《Arduino 首次接觸就上手》一接電就開始遊戲,和想改變按鈕的觸發動作。
於是向 ChatGPT 再次下 Prompt:
- 當OLED 顯示 "Game Start ,PLS Press Button twice",需要按按鈕兩下才能進遊戲畫面。
- 讓Arduino 重新燒錄/重新供電時,不會直接進到遊戲畫面。
產生最終可運行程式碼
經歷了千辛萬苦,終於為最終優化後,可直接運行於《Arduino 首次接觸就上手》的完整程式碼。#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>
// ===== OLED(旋轉 R2)=====
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R2, U8X8_PIN_NONE);
// ===== 腳位 =====
const int buttonPin = 6;
const int buzzerPin = 5;
// ===== 遊戲狀態 =====
enum GameState { WAIT_START, PLAYING, GAME_OVER };
GameState gameState = WAIT_START;
// ===== 遊戲參數 =====
const int groundY = 54;
const int playerSize = 8;
const int obsSize = 8;
int playerX = 16;
int playerY = groundY - playerSize;
int vy = 0;
bool isJumping = false;
int obsX = 128;
int obsY = groundY - obsSize;
int obsSpeed = 3;
long score = 0;
bool scoredThisObstacle = false;
unsigned long gameOverAt = 0;
// ===== Start 畫面控制 =====
unsigned long bootTime = 0;
bool startScreenReady = false;
int startPressCount = 0; // 需按兩下
// ===== 音效 =====
bool tonePlaying = false;
unsigned long toneStart = 0;
unsigned long toneDuration = 0;
void playTone(int f,int d){
tone(buzzerPin,f);
tonePlaying=true;
toneStart=millis();
toneDuration=d;
}
void updateTone(){
if(tonePlaying && millis()-toneStart>=toneDuration){
noTone(buzzerPin);
tonePlaying=false;
}
}
void soundJump(){ playTone(1200,80); }
// Game Over 音效
int goStep=0;
unsigned long goLast=0;
bool gameOverSoundActive=false;
void startGameOverSound(){
gameOverSoundActive=true;
goStep=0;
goLast=0;
}
void updateGameOverSound(){
if(!gameOverSoundActive) return;
unsigned long now=millis();
if(goStep==0){ playTone(600,200); goLast=now; goStep=1; }
else if(goStep==1 && now-goLast>220){ playTone(300,300); goLast=now; goStep=2; }
else if(goStep==2 && now-goLast>320){ gameOverSoundActive=false; }
}
// ===== 按鈕(邊緣觸發)=====
bool lastBtn=HIGH;
unsigned long lastDebounceAt=0;
const unsigned long debounceMs=25;
bool buttonPressed(){
bool cur=digitalRead(buttonPin);
if(cur!=lastBtn){
lastDebounceAt=millis();
lastBtn=cur;
}
if(millis()-lastDebounceAt<debounceMs) return false;
static bool prevStable=HIGH;
bool pressed=(prevStable==HIGH && cur==LOW);
prevStable=cur;
return pressed;
}
// ===== 畫面 =====
void drawStartScreen(){
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_6x12_tf);
u8g2.drawStr(4,24,"Game Start");
u8g2.drawStr(4,44,"PLS Press Button twice");
u8g2.sendBuffer();
}
void drawGameOver(){
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_6x12_tf);
u8g2.drawStr(10,24,"Game Over");
char buf[24];
snprintf(buf,sizeof(buf),"Score:%ld",score);
u8g2.drawStr(10,44,buf);
u8g2.sendBuffer();
}
void drawGame(){
u8g2.clearBuffer();
char buf[24];
snprintf(buf,sizeof(buf),"Score:%ld",score);
u8g2.drawStr(0,12,buf);
u8g2.drawHLine(0,groundY,128);
u8g2.drawBox(playerX,playerY,playerSize,playerSize);
u8g2.drawBox(obsX,obsY,obsSize,obsSize);
u8g2.sendBuffer();
}
bool checkCollision(){
return (playerX < obsX+obsSize &&
playerX+playerSize > obsX &&
playerY < obsY+obsSize &&
playerY+playerSize > obsY);
}
void resetGame(){
playerY=groundY-playerSize;
vy=0;
isJumping=false;
obsX=128;
obsSpeed=3;
score=0;
scoredThisObstacle=false;
}
// ===== Setup =====
void setup(){
pinMode(buttonPin,INPUT_PULLUP);
pinMode(buzzerPin,OUTPUT);
u8g2.begin();
u8g2.setContrast(200);
bootTime=millis();
gameState=WAIT_START;
startScreenReady=false;
startPressCount=0;
drawStartScreen(); // 上電立即顯示
}
// ===== Loop =====
void loop(){
updateTone();
updateGameOverSound();
switch(gameState){
// ===== Start 畫面(需按兩下)=====
case WAIT_START:
// 上電後先等待,避免誤觸
if(!startScreenReady){
if(millis()-bootTime>800 && digitalRead(buttonPin)==HIGH){
startScreenReady=true;
}
delay(20);
return;
}
if(buttonPressed()){
startPressCount++;
// 第二次按下才開始遊戲
if(startPressCount>=2){
startPressCount=0;
resetGame();
gameState=PLAYING;
}
}
delay(20);
break;
// ===== 遊戲中 =====
case PLAYING:
if(buttonPressed() && !isJumping){
vy=-10;
isJumping=true;
soundJump();
}
if(isJumping){
playerY+=vy;
vy+=1;
if(playerY>=groundY-playerSize){
playerY=groundY-playerSize;
vy=0;
isJumping=false;
}
}
obsX-=obsSpeed;
if(!scoredThisObstacle && (obsX+obsSize)<playerX){
score+=10;
scoredThisObstacle=true;
}
if(obsX<-obsSize){
obsX=128;
scoredThisObstacle=false;
if(obsSpeed<8) obsSpeed++;
}
if(checkCollision()){
gameState=GAME_OVER;
gameOverAt=millis();
startGameOverSound();
}
drawGame();
delay(30);
break;
// ===== Game Over =====
case GAME_OVER:
drawGameOver();
if(millis()-gameOverAt>=10000UL){
gameState=WAIT_START;
startScreenReady=false;
startPressCount=0;
bootTime=millis();
drawStartScreen();
}
delay(20);
break;
}
}
恭喜你完成本專題
完成此遊戲,你已掌握:
? 使用 Prompt 與 AI 生成程式
? 遊戲狀態流程設計(State Machine)
? OLED 畫面顯示與互動控制
? 按鈕輸入與跳躍邏輯
? 音效回饋設計
? 透過優化程式行為
下一步,嘗試新增功能
你可以試著新增的內容:
?反應速度加快
?最後顯示最高分
?設計不同關卡
立即洽詢與報名
CAVEDU 教育團隊 (翰尼斯企業) 是國內執行課程經驗最豐富的 NVIDIA 教育合作夥伴。 歡迎洽詢企業包班、技術顧問與客製化課程:
我們的專業團隊(2025 全球第一白金級大使領軍)將為您提供專屬的 NVIDIA DLI 原廠認證課程 規劃。
留言 💬 (0)
還沒有留言,來當第一個。