實戰(zhàn):Swing與JavaFX雙版本核心機制與避坑指南)
1. 項目緣起與整體設(shè)計思路1.1 為什么選《黃金礦工》作為Java練手項目做Java桌面開發(fā)練手最怕的就是選了一個“看起來簡單、寫起來全是坑”的題目?!饵S金礦工》這個題材我前前后后寫過兩版第一版用Swing硬擼第二版換成了JavaFX中間踩的坑足夠?qū)懸黄L文。先說為什么推薦它這個游戲的核心機制極其干凈——鉤子擺動、發(fā)射抓取、回收判定、計分過關(guān)四個狀態(tài)循環(huán)沒有復(fù)雜的AI尋路也沒有網(wǎng)絡(luò)同步問題但麻雀雖小五臟俱全游戲循環(huán)、碰撞檢測、狀態(tài)機、資源管理、音效播放這些游戲開發(fā)的基礎(chǔ)模塊一個不少。從技術(shù)選型上講市面上的Java游戲教程要么是貪吃蛇、俄羅斯方塊這種過于簡單的要么直接上LibGDX、Unity這種重型引擎對剛學(xué)完Java SE、想鞏固面向?qū)ο蠛褪录?qū)動編程的人來說中間缺了一個過渡層?!饵S金礦工》恰好卡在這個位置用純Java標準庫就能完成不需要額外依賴但又能讓你真正理解“一個游戲是怎么跑起來的”。這個項目適合誰我總結(jié)了三類人第一類是大二大三剛學(xué)完Java、想做點看得見摸得著的東西的學(xué)生第二類是想從Web開發(fā)轉(zhuǎn)游戲開發(fā)、需要補底層邏輯的開發(fā)者第三類是純粹想找個周末項目放松一下的老手。不管哪一類只要你能看懂基本的類、繼承、接口和線程這個項目就能吃下來。1.2 技術(shù)棧選型Swing還是JavaFX這是第一個要做的決策也是很多人卡住的地方。我兩版都寫過直接給結(jié)論新手用Swing想做得好看用JavaFX。Swing的優(yōu)勢在于資料多、API簡單、和AWT無縫銜接JPanel重寫paintComponent就能畫一切Timer做游戲循環(huán)也夠用。缺點是默認外觀老舊做動畫容易閃爍需要手動開雙緩沖。JavaFX的優(yōu)勢是自帶硬件加速、支持CSS樣式、動畫API完善AnimationTimer做游戲循環(huán)比Swing的Timer精準得多但學(xué)習(xí)曲線陡一些而且打包成可執(zhí)行jar的時候模塊化配置容易出問題。我第一版用Swing代碼量大概800行跑起來沒問題但畫面確實糙。第二版用JavaFX代碼量漲到1200行左右但畫面流暢度和視覺效果提升明顯。如果你只是想理解游戲邏輯Swing足夠如果你打算把這個項目放進作品集建議直接上JavaFX。提示不要一上來就想著用LibGDX或者jMonkeyEngine那些引擎會幫你把游戲循環(huán)、資源加載、碰撞檢測全封裝好你反而學(xué)不到東西。先用標準庫把輪子造一遍再去用引擎理解完全不一樣。1.3 整體架構(gòu)四個核心模塊的劃分不管用Swing還是JavaFX架構(gòu)思路是一致的。我把整個項目拆成四個模塊游戲主循環(huán)模塊負責(zé)驅(qū)動整個游戲的時間軸控制幀率、更新邏輯、觸發(fā)重繪。這是游戲的心臟所有狀態(tài)變化都由它驅(qū)動。實體模塊包含礦工、鉤子、金塊、石頭、鉆石、炸藥桶等所有游戲?qū)ο蟆C總€實體有自己的位置、速度、狀態(tài)和繪制方法。這里的關(guān)鍵是設(shè)計一個合理的基類把公共屬性抽出來。狀態(tài)管理模塊管理游戲的各個階段準備中、鉤子擺動、發(fā)射中、抓取中、回收中、結(jié)算中。用狀態(tài)機模式來寫比一堆if-else清晰一百倍。資源與配置模塊負責(zé)圖片、音效的加載以及關(guān)卡數(shù)據(jù)的配置。關(guān)卡數(shù)據(jù)建議用外部文件JSON或properties存儲方便調(diào)整難度。這四個模塊之間的依賴關(guān)系是主循環(huán)調(diào)用狀態(tài)管理狀態(tài)管理操作實體實體從資源模塊取素材。層次清晰改起來不會牽一發(fā)動全身。2. 核心機制拆解與關(guān)鍵實現(xiàn)細節(jié)2.1 鉤子擺動從三角函數(shù)到實際手感調(diào)優(yōu)鉤子擺動是整個游戲最核心的視覺表現(xiàn)也是最容易做砸的地方。原理很簡單鉤子繞礦工頭頂?shù)闹c做鐘擺運動角度在-75度到75度之間來回擺動。用Math.sin()或者直接角度累加都能實現(xiàn)但手感差別很大。我第一版用的是角度線性累加每幀angle speed到邊界反向。跑起來發(fā)現(xiàn)鉤子在兩端會“卡頓”因為線性運動在端點沒有減速感。第二版改成用正弦函數(shù)驅(qū)動angle maxAngle * Math.sin(time * speed)這樣鉤子在兩端自然減速、中間加速看起來就像真實的鐘擺。具體參數(shù)上maxAngle取75度比較合適太小了抓取范圍不夠太大了鉤子會擺到屏幕外面。擺動速度我試過好幾組最終定在每秒1.2個周期左右也就是time的系數(shù)取2 * Math.PI * 1.2。這個速度下玩家有足夠的反應(yīng)時間判斷發(fā)射時機又不會覺得拖沓。// 鉤子擺動角度計算 double maxAngle Math.toRadians(75); double swingSpeed 2 * Math.PI * 1.2; // 每秒1.2個周期 double angle maxAngle * Math.sin(elapsedTime * swingSpeed);這里有個細節(jié)elapsedTime必須用游戲運行的總時間不能用幀計數(shù)。因為不同機器的幀率可能不一樣用幀計數(shù)會導(dǎo)致快機器上鉤子擺得飛快。用System.nanoTime()計算時間差保證跨平臺一致性。2.2 發(fā)射與回收狀態(tài)切換的時機把控鉤子的狀態(tài)切換是這個游戲最容易出bug的地方。我見過不少人的實現(xiàn)是“按下空格就發(fā)射”結(jié)果鉤子還在回收途中又按了一下直接狀態(tài)錯亂。正確的做法是用一個明確的狀態(tài)枚舉public enum HookState { SWINGING, // 擺動中 SHOOTING, // 發(fā)射中 RETRACTING, // 回收中 CAUGHT // 抓到東西回收中 }狀態(tài)切換的規(guī)則必須嚴格只有SWINGING狀態(tài)才能響應(yīng)發(fā)射指令發(fā)射后立即切到SHOOTING鉤子到達最大長度或碰到物體后切到RETRACTING或CAUGHT回收完成后切回SWINGING。每個狀態(tài)下的更新邏輯和繪制邏輯分開寫不要混在一起。發(fā)射速度我建議設(shè)為每秒600像素左右回收速度根據(jù)抓到的物體重量動態(tài)調(diào)整。空鉤回收快一些每秒800像素抓到金塊后慢一些每秒300-500像素抓到石頭最慢每秒200像素。這個速度差就是游戲策略的核心——你得判斷哪些東西值得抓。2.3 碰撞檢測矩形碰撞夠用嗎很多人一上來就想用像素級碰撞檢測覺得矩形碰撞太粗糙。我的建議是這個游戲用矩形碰撞完全夠用而且更好。原因有兩個第一游戲里的物體都是近似矩形或圓形的矩形包圍盒的誤差在可接受范圍內(nèi)第二矩形碰撞計算量小代碼簡單不容易出bug。像素級碰撞需要讀取圖片的alpha通道性能開銷大而且對于金塊這種不規(guī)則形狀反而容易出現(xiàn)“看起來碰到了但沒抓到”的挫敗感。具體實現(xiàn)上給每個實體維護一個Rectangle2D或者自定義的Bounds對象鉤子頭部用一個小的圓形區(qū)域做檢測。每幀更新完位置后遍歷所有可抓取物體判斷鉤子頭部是否與物體包圍盒相交。相交則觸發(fā)抓取邏輯。// 矩形碰撞檢測示例 public boolean intersects(Entity other) { return this.x other.x other.width this.x this.width other.x this.y other.y other.height this.y this.height other.y; }注意鉤子頭部的碰撞區(qū)域要比視覺上的鉤子稍大一點大概放大1.2倍。這是為了補償玩家的操作延遲讓抓取判定更“寬容”手感更好。這個技巧在動作游戲里很常見叫“碰撞盒寬容度”。2.4 物體生成隨機性與關(guān)卡設(shè)計的平衡關(guān)卡里的金塊、石頭、鉆石不能完全隨機生成否則會出現(xiàn)“好東西全擠在一起”或者“一關(guān)全是石頭”的極端情況。我的做法是分層隨機第一層確定本關(guān)的物體總數(shù)和各類物體的比例。比如第一關(guān)總共8個物體金塊5個、石頭2個、鉆石1個。第二層在屏幕下半部分隨機生成位置但加入最小間距約束避免物體重疊。第三層對金塊的大小做隨機大金塊價值高但重量大小金塊價值低但回收快。這個分層隨機的思路來自我玩過的很多roguelike游戲核心思想是“受控的隨機性”——既保證每局不一樣又保證不會出現(xiàn)無法通關(guān)的極端情況。關(guān)卡數(shù)據(jù)我建議用JSON存儲結(jié)構(gòu)大概是這樣{ level: 1, targetScore: 500, timeLimit: 60, objects: [ {type: gold_large, count: 2}, {type: gold_small, count: 3}, {type: stone, count: 2}, {type: diamond, count: 1} ] }這樣調(diào)整難度只需要改配置文件不用重新編譯代碼。3. 完整實操流程與核心代碼實現(xiàn)3.1 項目結(jié)構(gòu)搭建與依賴配置先建一個標準的Maven項目目錄結(jié)構(gòu)如下gold-miner/ ├── pom.xml ├── src/main/java/ │ └── com/example/goldminer/ │ ├── Main.java │ ├── core/ │ │ ├── GameLoop.java │ │ ├── GameState.java │ │ └── InputHandler.java │ ├── entity/ │ │ ├── Entity.java │ │ ├── Miner.java │ │ ├── Hook.java │ │ ├── Gold.java │ │ ├── Stone.java │ │ └── Diamond.java │ ├── level/ │ │ ├── LevelConfig.java │ │ └── LevelLoader.java │ └── util/ │ ├── ResourceManager.java │ └── SoundPlayer.java └── src/main/resources/ ├── images/ ├── sounds/ └── levels/如果不用Maven手動管理jar包也行但這個項目其實不需要任何第三方依賴純JDK就能跑。用Maven主要是為了方便打包和資源管理。pom.xml里只需要配置Java版本和主類properties maven.compiler.source17/maven.compiler.source maven.compiler.target17/maven.compiler.target /propertiesJava 17是目前的長期支持版本用新版本的語言特性比如record、switch表達式能讓代碼更簡潔。3.2 游戲主循環(huán)固定時間步長的實現(xiàn)游戲循環(huán)有兩種常見寫法固定時間步長和可變時間步長。我強烈建議用固定時間步長因為物理更新和碰撞檢測在固定步長下更穩(wěn)定。核心思路是不管實際幀率是多少邏輯更新固定每秒60次渲染盡可能快。這樣在不同性能的機器上游戲邏輯的速度是一致的。public class GameLoop implements Runnable { private static final double UPDATE_RATE 60.0; private static final double UPDATE_INTERVAL 1_000_000_000 / UPDATE_RATE; private boolean running true; private long lastUpdateTime; private double delta; Override public void run() { lastUpdateTime System.nanoTime(); while (running) { long now System.nanoTime(); delta (now - lastUpdateTime) / UPDATE_INTERVAL; lastUpdateTime now; while (delta 1) { update(); // 邏輯更新 delta--; } render(); // 渲染 } } }這個寫法叫“累加器模式”是游戲開發(fā)里的經(jīng)典模式。delta累積時間每夠一個更新周期就執(zhí)行一次update()多出來的時間留到下一幀。這樣即使某一幀卡了邏輯更新也不會丟幀。提示render()方法里不要做任何邏輯計算只負責(zé)把當(dāng)前狀態(tài)畫出來。邏輯和渲染分離是游戲開發(fā)的基本原則混在一起后期改起來會非常痛苦。3.3 鉤子類的完整實現(xiàn)鉤子是整個項目最復(fù)雜的類我把關(guān)鍵代碼拆開講。首先是屬性定義public class Hook extends Entity { private HookState state HookState.SWINGING; private double angle; // 當(dāng)前擺動角度 private double length; // 當(dāng)前伸出長度 private double maxLength; // 最大伸出長度 private double swingSpeed; // 擺動速度 private double shootSpeed; // 發(fā)射速度 private double retractSpeed; // 回收速度 private Entity caughtEntity; // 抓到的物體 private double pivotX, pivotY; // 支點坐標 }然后是更新邏輯按狀態(tài)分支public void update(double deltaTime) { switch (state) { case SWINGING - { elapsedTime deltaTime; angle maxAngle * Math.sin(elapsedTime * swingSpeed); updateTipPosition(); } case SHOOTING - { length shootSpeed * deltaTime; if (length maxLength) { length maxLength; state HookState.RETRACTING; } updateTipPosition(); checkCollision(); } case RETRACTING - { length - retractSpeed * deltaTime; if (length 0) { length 0; state HookState.SWINGING; caughtEntity null; } updateTipPosition(); if (caughtEntity ! null) { caughtEntity.setPosition(getTipX(), getTipY()); } } } }updateTipPosition()根據(jù)角度和長度計算鉤子頭部的坐標private void updateTipPosition() { tipX pivotX Math.sin(angle) * length; tipY pivotY Math.cos(angle) * length; }這里用sin算x、cos算y是因為角度0度時鉤子垂直向下。如果你想讓0度對應(yīng)其他方向調(diào)整一下就行。碰撞檢測在SHOOTING狀態(tài)下每幀執(zhí)行private void checkCollision() { for (Entity entity : level.getGrabbableEntities()) { if (getTipBounds().intersects(entity.getBounds())) { caughtEntity entity; state HookState.RETRACTING; retractSpeed calculateRetractSpeed(entity); break; } } }calculateRetractSpeed根據(jù)物體重量返回不同的回收速度這是游戲策略性的來源。3.4 關(guān)卡數(shù)據(jù)加載與難度曲線設(shè)計關(guān)卡數(shù)據(jù)用JSON存儲加載用簡單的文件讀取加手動解析。如果不想引入JSON庫用properties格式也行但JSON結(jié)構(gòu)更清晰。難度曲線的設(shè)計我參考了原版《黃金礦工》的節(jié)奏前3關(guān)是教學(xué)關(guān)物體少、目標低、時間充裕4-8關(guān)開始加入石頭和炸藥桶考驗取舍9關(guān)以后加入移動的金塊和限時鉆石增加操作難度。具體參數(shù)上目標分數(shù)按關(guān)卡數(shù)遞增targetScore 300 level * 150。時間限制從60秒逐步降到40秒。物體總數(shù)從8個增加到15個但好東西的比例逐漸降低。public class LevelConfig { private int level; private int targetScore; private int timeLimit; private ListObjectSpawn spawns; public static LevelConfig generate(int level) { LevelConfig config new LevelConfig(); config.level level; config.targetScore 300 level * 150; config.timeLimit Math.max(40, 60 - level * 2); // ... 生成物體配置 return config; } }注意難度曲線不要做得太陡。我第一版把第5關(guān)的目標分數(shù)設(shè)得太高結(jié)果測試的時候自己都過不去。后來改成每關(guān)遞增150分配合物體價值的微調(diào)節(jié)奏就舒服多了。測試的時候一定要自己從頭玩到尾不要只測單關(guān)。3.5 音效與視覺反饋提升手感的關(guān)鍵音效不是可有可無的東西它直接影響游戲手感。鉤子發(fā)射時的“嗖”聲、抓到金塊時的“?!甭?、抓到石頭時的沉悶聲、過關(guān)時的歡呼聲這些反饋讓玩家明確知道發(fā)生了什么。Java標準庫播放音效用javax.sound.sampled包支持WAV格式。MP3需要額外庫建議直接用WAV文件大一點但兼容性好。public class SoundPlayer { private static final MapString, Clip clipCache new HashMap(); public static void play(String name) { try { Clip clip clipCache.computeIfAbsent(name, n - { AudioInputStream ais AudioSystem.getAudioInputStream( SoundPlayer.class.getResourceAsStream(/sounds/ n .wav)); Clip c AudioSystem.getClip(); c.open(ais); return c; }); clip.setFramePosition(0); clip.start(); } catch (Exception e) { // 音效播放失敗不影響游戲運行 } } }視覺反饋方面抓到物體時在鉤子位置彈出一個分數(shù)飄字過關(guān)時屏幕閃一下白光這些細節(jié)能讓游戲品質(zhì)提升一個檔次。飄字用簡單的粒子系統(tǒng)實現(xiàn)維護一個飄字列表每幀更新位置和透明度畫在頂層。4. 常見問題排查與避坑經(jīng)驗實錄4.1 畫面閃爍與雙緩沖Swing下最常見的抱怨就是“畫面一閃一閃的”。原因是paintComponent每次重繪時先清空背景再畫內(nèi)容清空和繪制之間有時間差人眼就能看到閃爍。解決方案是開啟雙緩沖。Swing的JPanel默認是雙緩沖的但如果你重寫了paintComponent并且調(diào)用了super.paintComponent(g)有時候會出問題。正確的做法是Override protected void paintComponent(Graphics g) { // 不要調(diào)用super.paintComponent(g)自己控制清屏 Graphics2D g2d (Graphics2D) g; g2d.setColor(Color.BLACK); g2d.fillRect(0, 0, getWidth(), getHeight()); // 繪制游戲內(nèi)容 render(g2d); }如果還是閃檢查是不是在Timer的回調(diào)里直接調(diào)用了repaint()而沒有做邏輯更新。正確的順序是Timer觸發(fā) - 更新邏輯 - 調(diào)用repaint() - 系統(tǒng)調(diào)度paintComponent。JavaFX下基本不會遇到閃爍問題因為它的渲染管線自帶垂直同步和雙緩沖。4.2 鉤子抓取判定不準的排查思路“明明碰到了卻沒抓到”是反饋最多的問題。排查思路按優(yōu)先級來第一檢查碰撞檢測的調(diào)用時機。碰撞檢測必須在鉤子位置更新之后執(zhí)行如果順序反了檢測的是上一幀的位置自然不準。第二檢查碰撞盒的大小。視覺上的鉤子圖片可能只有20x20像素但碰撞盒應(yīng)該稍大一些。我一般設(shè)成圖片尺寸的1.2倍并且以鉤子頭部中心為基準。第三檢查物體的包圍盒是否包含了透明區(qū)域。如果金塊圖片周圍有大量透明像素包圍盒會偏大導(dǎo)致“看起來沒碰到卻抓到了”。解決辦法是手動調(diào)整包圍盒或者在圖片制作階段就把透明邊裁掉。第四檢查幀率。如果幀率太低鉤子移動速度快的時候會“跳過”物體。固定時間步長能緩解這個問題但如果物體很小、鉤子很快還是可能穿透。終極方案是做連續(xù)碰撞檢測但在這個游戲里沒必要把鉤子速度控制在合理范圍就行。4.3 資源加載路徑的坑打包成jar之后圖片加載失敗是Java桌面開發(fā)的經(jīng)典坑。原因是開發(fā)時用相對路徑new File(images/gold.png)能讀到但jar里的資源不能用File訪問必須用getResourceAsStream。// 錯誤寫法打包后失效 Image img ImageIO.read(new File(images/gold.png)); // 正確寫法從classpath讀取 Image img ImageIO.read( getClass().getResourceAsStream(/images/gold.png));資源文件放在src/main/resources目錄下編譯后會復(fù)制到classpath根目錄。路徑以/開頭表示從classpath根開始找。提示如果getResourceAsStream返回null先檢查文件是否真的在classpath里。用getClass().getResource(/)打印一下classpath根路徑看看文件在不在。4.4 常見問題速查表問題現(xiàn)象可能原因排查方法解決方案畫面閃爍未開啟雙緩沖觀察重繪時是否先清屏重寫paintComponent手動控制清屏鉤子擺動速度異常用了幀計數(shù)而非時間在不同機器上測試改用System.nanoTime()計算時間差抓取判定不準碰撞盒與視覺不匹配開啟調(diào)試模式畫出碰撞盒調(diào)整碰撞盒大小和位置打包后圖片加載失敗用了File而非getResourceAsStream檢查異常堆棧改用classpath資源加載音效播放延遲Clip未預(yù)熱首次播放時觀察啟動時預(yù)加載所有音效游戲速度隨幀率變化可變時間步長限制幀率后觀察改用固定時間步長物體重疊生成隨機位置無約束多次開局觀察加入最小間距約束內(nèi)存持續(xù)增長資源未釋放長時間運行觀察復(fù)用對象避免每幀new4.5 性能優(yōu)化的幾個實用技巧這個游戲本身不重但在低配機器上還是可以做些優(yōu)化。第一圖片資源只加載一次緩存起來復(fù)用不要每次繪制都讀文件。第二碰撞檢測只對屏幕內(nèi)的物體執(zhí)行屏幕外的直接跳過。第三飄字粒子數(shù)量控制在20個以內(nèi)多了就回收最舊的。第四用Graphics2D的setRenderingHint開啟抗鋸齒但不要開太多否則低配機器會卡。g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);抗鋸齒讓邊緣更平滑雙線性插值讓縮放后的圖片不那么糊。這兩個設(shè)置對畫面品質(zhì)提升明顯性能開銷在現(xiàn)代機器上可以忽略。5. 項目擴展方向與個人實操體會5.1 從單機到聯(lián)機的擴展思路單機版做完之后如果想繼續(xù)深入可以嘗試加聯(lián)機對戰(zhàn)。思路是兩個玩家在同一關(guān)卡里競爭誰先達到目標分數(shù)誰贏。技術(shù)上加一個Socket通信層同步雙方的鉤子狀態(tài)和抓取結(jié)果。但要注意聯(lián)機游戲的狀態(tài)同步是個大坑建議先用本地雙人模式練手兩個鉤子在同一屏幕上操作確認邏輯沒問題再上網(wǎng)絡(luò)。另一個擴展方向是加道具系統(tǒng)。比如“強力鉤子”道具使用后10秒內(nèi)回收速度翻倍“透視眼鏡”道具顯示所有物體的價值。道具系統(tǒng)能大幅增加游戲的可玩性實現(xiàn)上也不復(fù)雜加一個道具欄和計時器就行。5.2 我在這個項目里踩過的三個坑第一個坑是狀態(tài)機沒設(shè)計好。第一版我用布爾變量isShooting、isRetracting來控制鉤子狀態(tài)結(jié)果出現(xiàn)了“既在發(fā)射又在回收”的詭異情況。后來改成枚舉狀態(tài)機每個狀態(tài)只允許特定的轉(zhuǎn)換問題迎刃而解。這個教訓(xùn)讓我在后來的所有項目里都優(yōu)先設(shè)計狀態(tài)機。第二個坑是資源沒做緩存。第一版每次繪制金塊都重新讀圖片文件跑起來CPU占用率飆到80%。后來加了資源緩存降到5%以下。這個坑很典型新手容易忽略IO操作的代價。第三個坑是難度曲線沒測試。第一版做完之后自己只玩了前3關(guān)覺得挺簡單結(jié)果朋友玩到第6關(guān)直接罵人——目標分數(shù)太高時間太短根本過不去。后來我老老實實從頭玩到第15關(guān)調(diào)整了每一關(guān)的參數(shù)。自己不做通關(guān)測試的游戲不要拿給別人玩這是血淚教訓(xùn)。5.3 給想動手的人的幾點建議如果你打算動手寫這個項目我的建議是先別急著寫代碼拿張紙把狀態(tài)轉(zhuǎn)換圖和各模塊的職責(zé)畫出來。狀態(tài)機清晰了代碼自然就順了。然后從最小的可運行版本開始——先讓鉤子能擺動再加發(fā)射再加抓取再加計分每一步都確保能跑起來再往下做。不要一次性把所有功能寫完再調(diào)試那樣出了問題很難定位。素材方面如果不想自己畫可以用簡單的幾何圖形代替圓形代表金塊、方形代表石頭、三角形代表鉆石。等邏輯全部跑通了再替換成正式素材。這樣能把精力集中在邏輯上不被美術(shù)資源分散注意力。最后這個項目最大的價值不在于做出來的游戲本身而在于你通過它理解了游戲循環(huán)、狀態(tài)機、碰撞檢測、資源管理這些通用概念。這些概念在任何一個游戲引擎里都是相通的學(xué)會了之后換到Unity或者Godot你會發(fā)現(xiàn)底層邏輯一模一樣只是API換了而已。