現(xiàn)Flappy Bird物理交互游戲)
1. 項(xiàng)目緣起當(dāng)經(jīng)典游戲遇上物理世界幾年前當(dāng)那個(gè)像素風(fēng)的小鳥(niǎo)在屏幕上蹦蹦跳跳讓無(wú)數(shù)人摔手機(jī)、砸鍵盤(pán)的時(shí)候我就在想如果這只鳥(niǎo)能跳出屏幕變成一個(gè)我可以用手按動(dòng)的實(shí)體按鈕讓游戲過(guò)程更有“觸感”會(huì)不會(huì)更有趣這就是我做這個(gè)“Arduino Processing版Flappy Bird”項(xiàng)目的初衷。它不是一個(gè)簡(jiǎn)單的軟件復(fù)刻而是一個(gè)軟硬件結(jié)合的交互實(shí)驗(yàn)用Arduino Uno讀取物理按鈕的信號(hào)用Processing在電腦屏幕上渲染游戲畫(huà)面和邏輯讓虛擬的游戲世界通過(guò)一根USB線與真實(shí)的物理按鍵連接起來(lái)。這個(gè)項(xiàng)目的核心價(jià)值在于它為你打開(kāi)了一扇門(mén)讓你看到如何用最低的成本一塊幾十塊的Arduino板子、一個(gè)按鈕、幾根杜邦線和最簡(jiǎn)單的代碼去構(gòu)建一個(gè)屬于自己的、可觸摸的交互系統(tǒng)。無(wú)論你是剛接觸Arduino和Processing的新手想找一個(gè)有趣的項(xiàng)目練手還是有一定經(jīng)驗(yàn)的開(kāi)發(fā)者想了解串口通信和游戲邏輯如何結(jié)合這個(gè)項(xiàng)目都能提供一條清晰的路徑。你會(huì)發(fā)現(xiàn)從“燈閃一下”到“做一個(gè)能玩的游戲”中間的距離并沒(méi)有想象中那么遙遠(yuǎn)。2. 核心架構(gòu)解析軟硬件如何“握手”整個(gè)系統(tǒng)的架構(gòu)非常清晰可以概括為“一個(gè)硬件輸入一個(gè)軟件處理一條數(shù)據(jù)通道”。理解這個(gè)架構(gòu)是后續(xù)一切操作的基礎(chǔ)。2.1 硬件端Arduino的角色與電路搭建Arduino在這里扮演了一個(gè)純粹的“輸入采集器”角色。它不負(fù)責(zé)復(fù)雜的游戲邏輯計(jì)算也不負(fù)責(zé)圖形渲染它的任務(wù)只有一個(gè)實(shí)時(shí)監(jiān)測(cè)連接在它上面的物理按鈕的狀態(tài)并將這個(gè)狀態(tài)變化通過(guò)串口Serial Port發(fā)送給電腦。硬件清單與選型理由Arduino Uno或兼容板這是最經(jīng)典、資料最豐富的入門(mén)級(jí)開(kāi)發(fā)板。它的ATmega328P芯片性能足夠處理按鍵檢測(cè)和串口通信USB接口供電和編程都非常方便。選擇它意味著你遇到的絕大多數(shù)問(wèn)題都能在網(wǎng)上找到解決方案。按鍵模塊或輕觸開(kāi)關(guān)這是游戲的“靈魂”。我推薦使用那種四腳帶帽的輕觸開(kāi)關(guān)或者已經(jīng)集成了上拉電阻的按鍵模塊。前者更便宜但需要自己處理電路后者即插即用對(duì)新手更友好。選擇哪種取決于你想在電路知識(shí)上深入到什么程度。杜邦線公對(duì)公若干用于連接。10kΩ電阻如果使用裸開(kāi)關(guān)這是實(shí)現(xiàn)“上拉”的關(guān)鍵。后面會(huì)詳細(xì)解釋為什么需要它。電路連接原理以使用裸輕觸開(kāi)關(guān)為例這是整個(gè)硬件部分最容易出錯(cuò)的地方務(wù)必理解其原理。按鍵一端連接到Arduino的某個(gè)數(shù)字引腳例如我常用D2引腳。按鍵另一端連接到GND地。在D2引腳和5V引腳之間連接一個(gè)10kΩ的上拉電阻。這個(gè)連接方式構(gòu)成了一個(gè)經(jīng)典的“上拉電阻”電路。其工作原理是當(dāng)按鍵沒(méi)有被按下時(shí)D2引腳通過(guò)10kΩ電阻被“拉”到了高電平5VArduino讀取到的值是HIGH或數(shù)字1。當(dāng)按鍵被按下時(shí)D2引腳通過(guò)按鍵直接與GND相連由于導(dǎo)線的電阻遠(yuǎn)小于10kΩ此時(shí)D2引腳被“拉”到了低電平0VArduino讀取到的值是LOW或數(shù)字0。這個(gè)電阻的作用是防止引腳懸空既不接高也不接低導(dǎo)致電平不確定從而產(chǎn)生誤觸發(fā)。注意很多Arduino教程會(huì)教你使用板子內(nèi)部的上拉電阻通過(guò)pinMode(pin, INPUT_PULLUP)來(lái)設(shè)置。這種方法更簡(jiǎn)單無(wú)需外接電阻。此時(shí)你的按鍵連接需要改變按鍵一端接D2另一端接5V。按下時(shí)D2從內(nèi)部被拉高的狀態(tài)HIGH變?yōu)榻拥?V還是HIGH不這里有個(gè)關(guān)鍵點(diǎn)。實(shí)際上當(dāng)使用內(nèi)部上拉時(shí)按鍵應(yīng)該連接在引腳和GND之間。按下按鍵引腳從HIGH被拉低到LOW。我個(gè)人的經(jīng)驗(yàn)是對(duì)于新手先從外接上拉電阻的電路開(kāi)始理解更能建立清晰的物理概念。后續(xù)優(yōu)化時(shí)可以改用內(nèi)部上拉以簡(jiǎn)化電路。2.2 軟件端Processing的游戲世界Processing在這個(gè)項(xiàng)目中承擔(dān)了所有“高級(jí)”任務(wù)游戲畫(huà)面繪制、物理碰撞檢測(cè)、分?jǐn)?shù)計(jì)算、關(guān)卡難度遞增邏輯以及最重要的——通過(guò)串口與Arduino對(duì)話。為什么選擇Processing為視覺(jué)交互而生Processing的語(yǔ)法極其簡(jiǎn)潔專(zhuān)為圖形、動(dòng)畫(huà)和交互設(shè)計(jì)。用它來(lái)畫(huà)一只小鳥(niǎo)、幾根管道比用其他語(yǔ)言要直觀和快速得多。與Arduino“天生一對(duì)”P(pán)rocessing有一個(gè)官方的Serial庫(kù)專(zhuān)門(mén)用于串口通信其API設(shè)計(jì)得非常容易上手幾乎是為Arduino通信量身定做??缙脚_(tái)Windows, macOS, Linux全支持確保你的項(xiàng)目可以在任何電腦上運(yùn)行。Processing程序的結(jié)構(gòu)通常圍繞setup()和draw()兩個(gè)主函數(shù)。setup()在程序開(kāi)始時(shí)運(yùn)行一次用于初始化串口、設(shè)置窗口大小、加載資源等。draw()則每秒運(yùn)行很多次默認(rèn)60次形成60FPS的動(dòng)畫(huà)在這里更新小鳥(niǎo)的位置、繪制每一幀的畫(huà)面、檢測(cè)碰撞。2.3 通信橋梁串口協(xié)議設(shè)計(jì)硬件和軟件之間需要一種共同的語(yǔ)言這就是串口通信協(xié)議。我們的協(xié)議可以設(shè)計(jì)得非常簡(jiǎn)單。Arduino發(fā)送什么Arduino只需要在按鍵狀態(tài)發(fā)生變化時(shí)發(fā)送一個(gè)簡(jiǎn)單的字符。例如當(dāng)檢測(cè)到按鍵從“松開(kāi)”變?yōu)椤鞍聪隆毕陆笛貢r(shí)發(fā)送字符p代表 press。或者更簡(jiǎn)單粗暴且常用的方式每次在loop()中讀取引腳狀態(tài)如果當(dāng)前是按下?tīng)顟B(tài)LOW就持續(xù)發(fā)送字符1如果是松開(kāi)狀態(tài)HIGH就發(fā)送0。Processing端則根據(jù)連續(xù)收到的1來(lái)判斷按鍵被持續(xù)按著。Processing接收與解析Processing的Serial庫(kù)會(huì)監(jiān)聽(tīng)指定串口如 COM3, /dev/ttyUSB0。當(dāng)有數(shù)據(jù)到達(dá)時(shí)我們可以在draw()循環(huán)中調(diào)用serial.read()讀取。如果讀到我們約定的字符如p就觸發(fā)一次“小鳥(niǎo)跳躍”的動(dòng)作給小鳥(niǎo)一個(gè)向上的速度。這種“事件驅(qū)動(dòng)”的通信方式效率很高Arduino只在有輸入變化時(shí)才通信避免了不必要的串口數(shù)據(jù)擁堵。3. 分步實(shí)現(xiàn)從零到一的完整流程理解了架構(gòu)我們開(kāi)始動(dòng)手。我會(huì)先完成Arduino端的“固件”程序確保硬件信號(hào)能正確發(fā)出然后再編寫(xiě)Processing端的游戲邏輯。3.1 Arduino端程序簡(jiǎn)潔的輸入哨兵首先用Arduino IDE編寫(xiě)并上傳以下代碼。假設(shè)我們使用D2引腳并采用了外部上拉電阻的電路即按鍵按下時(shí)D2讀為L(zhǎng)OW。// Flappy Bird - Arduino Controller // 引腳定義 const int buttonPin 2; // 變量記錄上一次按鍵狀態(tài)用于檢測(cè)變化 int lastButtonState HIGH; int currentButtonState HIGH; void setup() { // 初始化串口通信波特率設(shè)為9600。這個(gè)值必須與Processing端一致。 Serial.begin(9600); // 初始化按鍵引腳為輸入模式 pinMode(buttonPin, INPUT); // 注意這里用的是INPUT因?yàn)槲覀冇昧送獠可侠娮琛?// 如果用內(nèi)部上拉應(yīng)改為 INPUT_PULLUP同時(shí)按鍵另一端接GND。 } void loop() { // 讀取當(dāng)前按鍵狀態(tài) currentButtonState digitalRead(buttonPin); // 檢測(cè)狀態(tài)是否發(fā)生變化從高到低即按下瞬間 if (lastButtonState HIGH currentButtonState LOW) { // 按鍵被按下的瞬間通過(guò)串口發(fā)送字符 p Serial.write(p); // 也可以打印到串口監(jiān)視器查看調(diào)試時(shí)有用正式運(yùn)行時(shí)可以注釋掉 // Serial.println(Button Pressed!); } // 更新上一次的狀態(tài) lastButtonState currentButtonState; // 一個(gè)短暫的延時(shí)防止檢測(cè)過(guò)于頻繁去抖動(dòng)。10-20毫秒通常足夠。 delay(10); }上傳與測(cè)試將代碼上傳到你的Arduino Uno。打開(kāi)Arduino IDE的“串口監(jiān)視器”工具 - 串口監(jiān)視器。確保右下角波特率設(shè)置為9600。按下連接好的物理按鍵你應(yīng)該能在串口監(jiān)視器中看到不斷的p字符輸出或者你取消注釋的“Button Pressed!”文字。這說(shuō)明你的硬件電路和Arduino程序工作正常已經(jīng)能夠可靠地發(fā)送按鍵事件。實(shí)操心得按鍵去抖動(dòng)。機(jī)械按鍵在按下和松開(kāi)的瞬間內(nèi)部的金屬觸點(diǎn)會(huì)發(fā)生物理彈跳導(dǎo)致在幾毫秒內(nèi)電平快速變化多次。如果不處理一次按壓會(huì)被誤判為多次。上面的代碼通過(guò)兩個(gè)方式處理一是delay(10)在每次檢測(cè)后等待一段時(shí)間避開(kāi)抖動(dòng)期二是檢測(cè)“下降沿”從HIGH到LOW而不是單純的低電平。對(duì)于要求更高的場(chǎng)景可以使用更精確的“狀態(tài)機(jī)”或庫(kù)來(lái)處理但對(duì)我們這個(gè)游戲delay(10)邊沿檢測(cè)已經(jīng)足夠穩(wěn)定。3.2 Processing端程序構(gòu)建游戲的核心邏輯接下來(lái)是重頭戲。打開(kāi)Processing創(chuàng)建一個(gè)新項(xiàng)目。你需要先導(dǎo)入串口庫(kù)Sketch - Import Library... - Serial。然后開(kāi)始編寫(xiě)代碼。為了清晰我將代碼分塊解釋。第一部分變量聲明與初始化 (setup)import processing.serial.*; // 導(dǎo)入串口庫(kù) Serial myPort; // 串口對(duì)象 String portName; // 串口名稱(chēng) // 游戲角色小鳥(niǎo) float birdX, birdY; // 小鳥(niǎo)位置 float birdVelocity 0; // 小鳥(niǎo)垂直速度 float gravity 0.5; // 重力加速度 float jumpForce -10; // 跳躍力度向上為負(fù) // 游戲障礙物管道 ArrayListPipe pipes new ArrayListPipe(); // 使用動(dòng)態(tài)數(shù)組存儲(chǔ)管道 int pipeWidth 60; // 管道寬度 int pipeGap 150; // 管道上下之間的空隙小鳥(niǎo)能通過(guò)的高度 int pipeInterval 180; // 管道出現(xiàn)的幀間隔 int pipeTimer 0; // 計(jì)時(shí)器用于控制管道生成頻率 // 游戲狀態(tài) boolean gameRunning true; int score 0; PFont font; // 字體 // 管道類(lèi)用于管理單個(gè)管道對(duì) class Pipe { float x; float topHeight; // 上管道的高度 float bottomY; // 下管道的Y坐標(biāo) Pipe(float tempX) { x tempX; // 隨機(jī)生成上管道的長(zhǎng)度留出中間空隙 topHeight random(50, height - pipeGap - 50); bottomY topHeight pipeGap; } void update() { x - 2; // 管道向左移動(dòng)的速度 } void display() { fill(70, 200, 70); // 綠色管道 // 繪制上管道 rect(x, 0, pipeWidth, topHeight); // 繪制下管道 rect(x, bottomY, pipeWidth, height - bottomY); } // 檢測(cè)小鳥(niǎo)是否與當(dāng)前管道碰撞 boolean hits(float bx, float by, float bw) { if (by topHeight || by 20 bottomY) { // 小鳥(niǎo)高度假設(shè)為20 if (bx 20 x bx x pipeWidth) { return true; } } return false; } // 檢測(cè)小鳥(niǎo)是否已通過(guò)管道用于計(jì)分 boolean passed(float bx) { // 如果小鳥(niǎo)的x坐標(biāo)超過(guò)了管道的x坐標(biāo)寬度且之前未計(jì)分則計(jì)分 // 這里簡(jiǎn)化處理每對(duì)管道只計(jì)分一次的邏輯需要在主循環(huán)中額外維護(hù)一個(gè)布爾變量 return (bx x pipeWidth); } }setup()函數(shù)void setup() { size(800, 600); // 設(shè)置游戲窗口大小 birdX width / 4; // 小鳥(niǎo)初始水平位置在屏幕左側(cè)1/4處 birdY height / 2; // 小鳥(niǎo)初始垂直位置在屏幕中央 // 初始化字體 font createFont(Arial, 32); textFont(font); // 串口初始化這里需要你手動(dòng)修改 // 打印可用的串口列表找到你的Arduino對(duì)應(yīng)的端口 printArray(Serial.list()); // 根據(jù)打印的結(jié)果選擇正確的端口索引。例如Windows可能是COM3macOS/Linux可能是/dev/tty.usbmodem14101 // portName Serial.list()[0]; // 通常第一個(gè)是當(dāng)前連接的但不絕對(duì) portName COM3; // 請(qǐng)?zhí)鎿Q為你的實(shí)際端口名 myPort new Serial(this, portName, 9600); // 波特率必須與Arduino端匹配 // 生成第一對(duì)管道 pipes.add(new Pipe(width)); }第二部分游戲主循環(huán) (draw) 與串口讀取draw()函數(shù)每秒執(zhí)行60次是游戲的心臟。void draw() { background(135, 206, 235); // 繪制天藍(lán)色背景 // --- 1. 處理串口輸入 --- while (myPort.available() 0) { char inChar myPort.readChar(); // 讀取一個(gè)字符 if (inChar p gameRunning) { // 收到Arduino發(fā)來(lái)的跳躍指令 birdVelocity jumpForce; // 給小鳥(niǎo)一個(gè)向上的速度 } } if (!gameRunning) { // 游戲結(jié)束畫(huà)面 fill(255, 0, 0); textAlign(CENTER); text(Game Over! Score: score, width/2, height/2 - 40); text(Press R to Restart, width/2, height/2 20); return; // 游戲結(jié)束不再更新游戲邏輯 } // --- 2. 更新小鳥(niǎo)狀態(tài) --- birdVelocity gravity; // 應(yīng)用重力 birdY birdVelocity; // 更新位置 // 邊界檢測(cè)碰到頂部或底部則游戲結(jié)束 if (birdY height || birdY 0) { gameRunning false; } // --- 3. 更新與繪制管道 --- pipeTimer; if (pipeTimer pipeInterval) { pipes.add(new Pipe(width)); // 在屏幕右側(cè)生成新管道 pipeTimer 0; } // 遍歷所有管道更新、繪制并檢測(cè)碰撞 // 注意從后往前遍歷方便安全地刪除已移出屏幕的管道 for (int i pipes.size() - 1; i 0; i--) { Pipe p pipes.get(i); p.update(); p.display(); // 碰撞檢測(cè) if (p.hits(birdX, birdY, 20)) { gameRunning false; } // 計(jì)分檢測(cè)簡(jiǎn)化版小鳥(niǎo)中心通過(guò)管道中心即算分 // 更嚴(yán)謹(jǐn)?shù)淖龇ㄊ菫槊總€(gè)Pipe對(duì)象添加一個(gè)isScored布爾標(biāo)記 if (!p.passed(birdX) birdX p.x pipeWidth/2) { // 這里是一個(gè)簡(jiǎn)單的計(jì)分觸發(fā)點(diǎn)實(shí)際需要更精確的邏輯避免重復(fù)計(jì)分 // 為了示例我們用一個(gè)全局變量記錄上一幀是否在管道右側(cè)本幀是否在左側(cè)來(lái)判斷 // 此處省略精確邏輯用一個(gè)粗略的每對(duì)管道計(jì)分一次的方法示意 } // 如果管道移出屏幕左側(cè)則刪除它以釋放內(nèi)存 if (p.x -pipeWidth) { pipes.remove(i); score; // 每成功移除一對(duì)管道即小鳥(niǎo)通過(guò)了一對(duì)得分加一 } } // --- 4. 繪制小鳥(niǎo) --- fill(255, 255, 0); // 黃色小鳥(niǎo) ellipse(birdX, birdY, 20, 20); // 用圓形代表小鳥(niǎo) // --- 5. 繪制當(dāng)前分?jǐn)?shù) --- fill(0); textAlign(LEFT); text(Score: score, 20, 40); }第三部分添加鍵盤(pán)重啟功能為了讓游戲在結(jié)束后可以重新開(kāi)始我們添加一個(gè)鍵盤(pán)事件處理。void keyPressed() { if (key r || key R) { // 重置游戲狀態(tài) gameRunning true; birdY height / 2; birdVelocity 0; score 0; pipes.clear(); // 清空所有管道 pipeTimer 0; pipes.add(new Pipe(width)); // 添加第一對(duì)管道 } }3.3 連接與運(yùn)行修改端口號(hào)在Processing代碼的setup()函數(shù)里找到portName COM3;這一行。運(yùn)行一次程序查看控制臺(tái)輸出的串口列表將COM3替換成你的Arduino實(shí)際連接的端口名如/dev/ttyUSB0。確保Arduino已連接并通電且已上傳了之前的按鍵檢測(cè)程序。點(diǎn)擊Processing的“運(yùn)行”按鈕。如果一切正常你將看到一個(gè)游戲窗口黃色的小鳥(niǎo)開(kāi)始下墜。此時(shí)按下連接在Arduino上的物理按鈕小鳥(niǎo)應(yīng)該會(huì)向上跳躍。試著控制它穿過(guò)移動(dòng)的綠色管道吧4. 調(diào)試與優(yōu)化讓游戲更“跟手”第一次運(yùn)行很可能不會(huì)完美。以下是幾個(gè)常見(jiàn)的坑和優(yōu)化點(diǎn)。問(wèn)題1按鍵無(wú)反應(yīng)或反應(yīng)延遲。檢查串口連接確認(rèn)Processing代碼中的端口號(hào)是否正確。這是最常見(jiàn)的問(wèn)題。檢查波特率確保Arduino代碼的Serial.begin(9600)和Processing代碼的new Serial(this, portName, 9600)波特率一致。檢查電路用萬(wàn)用表測(cè)量按鍵按下時(shí)D2引腳對(duì)GND的電壓是否接近0V?;蛘咴贏rduino IDE的串口監(jiān)視器里觀察按下按鍵時(shí)是否有數(shù)據(jù)輸出。Processing讀取方式我們使用了while (myPort.available() 0)來(lái)讀取所有緩沖區(qū)的數(shù)據(jù)。如果游戲卡頓可能是draw()循環(huán)太慢導(dǎo)致串口緩沖區(qū)累積了大量數(shù)據(jù)處理不過(guò)來(lái)。可以嘗試改為if (myPort.available() 0)只讀一個(gè)字符但這可能在快速連按時(shí)丟失指令。對(duì)于這個(gè)游戲while循環(huán)通常是安全的。問(wèn)題2游戲畫(huà)面卡頓。簡(jiǎn)化繪圖我們的管道和小鳥(niǎo)都是簡(jiǎn)單圖形卡頓可能性不大。但如果后期想添加背景、動(dòng)畫(huà)要注意Processing的繪圖效率。檢查碰撞檢測(cè)效率我們的碰撞檢測(cè)hits函數(shù)是每幀對(duì)每個(gè)管道進(jìn)行的矩形檢測(cè)在管道數(shù)量不多時(shí)20完全沒(méi)問(wèn)題。如果卡頓可能是其他原因。問(wèn)題3按鍵感覺(jué)“不跟手”。這是輸入延遲問(wèn)題是硬件交互游戲的關(guān)鍵體驗(yàn)。延遲來(lái)自幾個(gè)方面Arduino端采樣延遲我們的loop()中有一個(gè)delay(10)。這意味著Arduino最快每10毫秒檢測(cè)一次按鍵。對(duì)于需要快速響應(yīng)的游戲這個(gè)延遲有點(diǎn)大。優(yōu)化方案移除delay(10)改用非阻塞的時(shí)間戳方式檢測(cè)去抖動(dòng)。例如記錄上次按下時(shí)間只有當(dāng)按鍵狀態(tài)變化且距離上次變化時(shí)間大于5毫秒時(shí)才認(rèn)為是有效動(dòng)作并發(fā)送數(shù)據(jù)。串口通信延遲字符傳輸本身很快但系統(tǒng)串口緩沖和調(diào)度可能有微小延遲。通常這不是主因。Processing幀處理延遲draw()函數(shù)一幀內(nèi)的所有計(jì)算和繪圖必須在下一次draw()調(diào)用前完成。如果一幀計(jì)算量太大導(dǎo)致幀率從60FPS降到30FPS那么從按下按鍵到畫(huà)面響應(yīng)延遲就從約16ms增加到33ms手感差異會(huì)很明顯。優(yōu)化后的Arduino代碼去抖動(dòng)優(yōu)化版const int buttonPin 2; int lastButtonState HIGH; int currentButtonState HIGH; unsigned long lastDebounceTime 0; // 上次狀態(tài)穩(wěn)定時(shí)間 unsigned long debounceDelay 5; // 去抖動(dòng)延時(shí)毫秒 void setup() { Serial.begin(9600); pinMode(buttonPin, INPUT_PULLUP); // 這次使用內(nèi)部上拉電阻按鍵另一端接GND。 } void loop() { int reading digitalRead(buttonPin); // 如果讀數(shù)與上次穩(wěn)定狀態(tài)不同則重置去抖動(dòng)計(jì)時(shí)器 if (reading ! lastButtonState) { lastDebounceTime millis(); } // 如果經(jīng)過(guò)去抖動(dòng)延時(shí)后狀態(tài)仍然與當(dāng)前穩(wěn)定狀態(tài)不同則認(rèn)為是有效變化 if ((millis() - lastDebounceTime) debounceDelay) { if (reading ! currentButtonState) { currentButtonState reading; // 只有從高到低的變化按下瞬間才發(fā)送 if (currentButtonState LOW) { Serial.write(p); } } } lastButtonState reading; // 移除了 delay(10)讓 loop() 跑得更快 }這個(gè)版本使用了更精確的毫秒級(jí)計(jì)時(shí)去抖動(dòng)并且去除了固定的delay使得Arduino能以更高的頻率檢測(cè)引腳響應(yīng)速度顯著提升。你會(huì)立刻感覺(jué)到按鍵“跟手”了很多。5. 擴(kuò)展思路不止于“能玩”一個(gè)基礎(chǔ)版本完成后你可以從這個(gè)核心出發(fā)進(jìn)行無(wú)限擴(kuò)展讓這個(gè)項(xiàng)目成為你的創(chuàng)意試驗(yàn)場(chǎng)。硬件擴(kuò)展多種輸入方式除了按鈕可以嘗試使用超聲波傳感器HC-SR04來(lái)控制小鳥(niǎo)。用手在傳感器前上下移動(dòng)通過(guò)測(cè)量距離來(lái)映射小鳥(niǎo)的升降。這需要修改Arduino代碼讀取傳感器數(shù)據(jù)并將其映射為一個(gè)范圍值發(fā)送給Processing。增加反饋在Arduino上連接一個(gè)蜂鳴器。當(dāng)小鳥(niǎo)碰撞時(shí)Arduino收到Processing發(fā)回的指令需要建立雙向通信讓蜂鳴器發(fā)出“悲鳴”?;蛘哌B接一個(gè)振動(dòng)電機(jī)碰撞時(shí)產(chǎn)生觸覺(jué)反饋。實(shí)體化游戲角色用舵機(jī)、LED和紙板做一個(gè)物理小鳥(niǎo)Processing通過(guò)串口控制舵機(jī)上下移動(dòng)讓游戲角色真正“活”在桌面上。軟件與游戲性擴(kuò)展雙向通信目前是Arduino單向發(fā)送。可以改為Arduino發(fā)送按鍵信號(hào)Processing判斷碰撞后通過(guò)串口發(fā)送一個(gè)字符如c回Arduino觸發(fā)蜂鳴器或LED。圖形美化在Processing中加載真正的Flappy Bird精靈圖片繪制帶有漸變色的背景讓管道有紋理。游戲邏輯豐富增加地面移動(dòng)、多種障礙物類(lèi)型如移動(dòng)的方塊、道具系統(tǒng)如護(hù)盾、本地高分記錄等。數(shù)據(jù)可視化在游戲界面旁邊用曲線圖實(shí)時(shí)繪制小鳥(niǎo)的飛行高度、速度或者記錄每次按鍵的力度和時(shí)機(jī)用于分析自己的操作。這個(gè)項(xiàng)目的魅力在于它像一塊積木。你掌握了“Arduino采集輸入 - 串口發(fā)送 - Processing接收并處理 - 圖形化輸出”這個(gè)核心流程后就可以替換掉其中任何一個(gè)環(huán)節(jié)。把按鈕換成搖桿把Processing換成Unity游戲引擎把Flappy Bird換成你自己的創(chuàng)意游戲。這條連接物理世界和數(shù)字世界的通道一旦打通你會(huì)發(fā)現(xiàn)無(wú)數(shù)可能性。