立游戲開(kāi)發(fā)入門(mén):從環(huán)境搭建到首個(gè)可交互場(chǎng)景)
1. 從零開(kāi)始為什么選擇 LibGDX 作為獨(dú)立游戲開(kāi)發(fā)的首個(gè)框架如果你剛開(kāi)始接觸獨(dú)立游戲開(kāi)發(fā)或者想從 Unity、Unreal 這類(lèi)重型引擎轉(zhuǎn)向更輕量、更可控的方案LibGDX 是一個(gè)繞不開(kāi)的名字。它不是一個(gè)完整的游戲引擎而是一個(gè)基于 Java 的跨平臺(tái)游戲開(kāi)發(fā)框架。這意味著它不提供拖拽式的編輯器但給了你從底層圖形渲染到上層游戲邏輯的完整控制權(quán)。對(duì)于想真正理解游戲如何運(yùn)行、希望代碼資產(chǎn)完全自主、或者目標(biāo)平臺(tái)是 PC、Android、iOS、Web 等多端的開(kāi)發(fā)者來(lái)說(shuō)LibGDX 提供了一個(gè)絕佳的起點(diǎn)。很多人被 Unity 的快速原型能力吸引但后期會(huì)遇到打包體積臃腫、定制化功能受限、版本升級(jí)“地震”等問(wèn)題。LibGDX 的核心價(jià)值在于“穩(wěn)定”和“透明”。它的 API 設(shè)計(jì)清晰底層基于 OpenGL性能開(kāi)銷(xiāo)小生成的桌面應(yīng)用可以小到幾 MB移動(dòng)端包體也極易控制。更重要的是你的游戲邏輯、資源管理、狀態(tài)機(jī)都是純代碼構(gòu)建的這強(qiáng)迫你建立更扎實(shí)的架構(gòu)思維這份能力在應(yīng)對(duì)復(fù)雜游戲邏輯時(shí)是無(wú)價(jià)的。所以這期內(nèi)容不是簡(jiǎn)單的“Hello World”教程。我會(huì)帶你走一遍從環(huán)境搭建到第一個(gè)可交互場(chǎng)景的完整流程重點(diǎn)不是復(fù)現(xiàn)代碼而是理解 LibGDX 項(xiàng)目的基本結(jié)構(gòu)、渲染循環(huán)、資源加載和輸入處理的“為什么”。我會(huì)假設(shè)你有一些 Java 基礎(chǔ)但即使沒(méi)有跟著步驟也能跑起來(lái)。我們的目標(biāo)是在結(jié)束時(shí)你不僅有一個(gè)能跑的窗口更清楚每一行代碼在 LibGDX 世界里的角色以及后續(xù)該從哪里擴(kuò)展。2. 環(huán)境準(zhǔn)備別在第一步就卡住開(kāi)始寫(xiě)代碼前先把路鋪平。LibGDX 的環(huán)境搭建比想象中簡(jiǎn)單但有幾個(gè)關(guān)鍵選擇點(diǎn)選錯(cuò)了后面會(huì)很麻煩。2.1 核心工具選擇構(gòu)建工具決定項(xiàng)目結(jié)構(gòu)LibGDX 官方推薦使用 Gradle 作為構(gòu)建工具。這是必須的不要嘗試手動(dòng)管理 JAR 包。Gradle 會(huì)幫你處理所有依賴LibGDX 自身、Box2D 物理引擎、音頻庫(kù)等、區(qū)分不同平臺(tái)桌面、安卓、iOS等的代碼和資源以及最終的打包任務(wù)。對(duì)于新手我建議直接使用官方提供的項(xiàng)目生成工具gdx-setup它能一鍵生成一個(gè)多模塊的 Gradle 項(xiàng)目這是最穩(wěn)妥的起點(diǎn)。你需要準(zhǔn)備Java Development Kit (JDK)版本至少是 JDK 8推薦 JDK 11 或 17LTS 長(zhǎng)期支持版。確保安裝后在命令行輸入java -version和javac -version都能正確顯示版本。集成開(kāi)發(fā)環(huán)境 (IDE)強(qiáng)烈推薦 IntelliJ IDEA社區(qū)版免費(fèi)。它對(duì) Gradle 和 Java 的支持是最好的能自動(dòng)識(shí)別 LibGDX 項(xiàng)目結(jié)構(gòu)。Eclipse 也可以但配置稍繁瑣。新手用 IDEA 能避開(kāi)很多環(huán)境問(wèn)題。項(xiàng)目生成器 (gdx-setup)這是一個(gè)在線的 JAR 文件。你不需要單獨(dú)安裝直接下載運(yùn)行即可。2.2 使用官方生成器創(chuàng)建你的第一個(gè)項(xiàng)目這是最關(guān)鍵的一步生成的項(xiàng)目結(jié)構(gòu)將伴隨整個(gè)開(kāi)發(fā)周期。下載生成器訪問(wèn) LibGDX 官網(wǎng)找到 “Download” 或 “Setup” 部分下載gdx-setup.jar文件。運(yùn)行生成器在命令行中進(jìn)入你存放gdx-setup.jar的目錄執(zhí)行java -jar gdx-setup.jar這會(huì)啟動(dòng)一個(gè)圖形界面窗口。填寫(xiě)項(xiàng)目配置這是容易出錯(cuò)的地方Name你的游戲名稱(chēng)例如MyFirstGame。這會(huì)用作項(xiàng)目根目錄名和包名的一部分。PackageJava 包名通常使用倒置的域名格式如com.myname.mygame。這決定了你的源代碼放在哪里。Game class你的主游戲類(lèi)名例如MyFirstGame。這個(gè)類(lèi)將包含游戲的核心邏輯。Destination項(xiàng)目要?jiǎng)?chuàng)建在哪個(gè)本地文件夾。確保路徑?jīng)]有中文和特殊字符。Android SDK如果你打算開(kāi)發(fā)安卓游戲需要提前安裝 Android SDK 并在此處指定路徑。如果只做桌面或?qū)W習(xí)可以取消勾選 Android這能大幅簡(jiǎn)化初始項(xiàng)目。iOS 開(kāi)發(fā)需要 Mac 和額外配置初期可以先忽略。選擇擴(kuò)展庫(kù)生成器會(huì)列出可選的擴(kuò)展如 Box2D2D物理、Bullet3D物理、Ashley實(shí)體組件系統(tǒng)等。對(duì)于第一期我建議一個(gè)都不要選。保持最純凈的 LibGDX 核心庫(kù)先理解基礎(chǔ)。后續(xù)需要時(shí)可以很方便地通過(guò)修改build.gradle文件來(lái)添加。高級(jí)設(shè)置通常保持默認(rèn)即可。注意IDE選項(xiàng)選擇你使用的 IDE如 IntelliJ。點(diǎn)擊 Generate等待生成器下載必要的依賴并創(chuàng)建項(xiàng)目文件。完成后會(huì)在你指定的Destination路徑下生成一個(gè)項(xiàng)目文件夾。2.3 導(dǎo)入項(xiàng)目到 IDE 并運(yùn)行項(xiàng)目生成后不要急著看代碼先讓它跑起來(lái)。用 IDE 打開(kāi)項(xiàng)目打開(kāi) IntelliJ IDEA選擇 “Open”然后導(dǎo)航到你剛才生成的項(xiàng)目根目錄包含build.gradle文件的文件夾點(diǎn)擊打開(kāi)。IDEA 會(huì)自動(dòng)識(shí)別為 Gradle 項(xiàng)目并開(kāi)始導(dǎo)入和索引依賴這可能需要幾分鐘請(qǐng)耐心等待。找到啟動(dòng)類(lèi)項(xiàng)目導(dǎo)入后在左側(cè)項(xiàng)目視圖中你會(huì)看到多個(gè)模塊core、desktop、android如果勾選了等。core模塊存放所有平臺(tái)共享的游戲邏輯和資源。desktop模塊是桌面版的啟動(dòng)入口。運(yùn)行桌面版展開(kāi)desktop模塊在src目錄下找到DesktopLauncher.java類(lèi)。右鍵點(diǎn)擊它選擇 “Run ‘DesktopLauncher.main()’”。如果一切順利你會(huì)看到一個(gè)深灰色的窗口彈出來(lái)標(biāo)題是你的游戲名。恭喜你的 LibGDX 項(xiàng)目已經(jīng)成功運(yùn)行了這個(gè)窗口目前什么都沒(méi)做但它意味著你的開(kāi)發(fā)環(huán)境、項(xiàng)目結(jié)構(gòu)和基礎(chǔ)依賴全部就緒。這是萬(wàn)里長(zhǎng)征的第一步也是最容易因?yàn)?JDK 版本、Gradle 網(wǎng)絡(luò)、路徑問(wèn)題而失敗的一步。如果卡在這里優(yōu)先檢查JDK 版本、Gradle 下載源可考慮配置國(guó)內(nèi)鏡像、以及項(xiàng)目路徑是否合規(guī)。3. 解剖項(xiàng)目理解 LibGDX 的基本骨架跑通之后我們停下來(lái)看看這個(gè)項(xiàng)目到底是怎么組織的。理解這個(gè)結(jié)構(gòu)比寫(xiě)十行代碼更重要。3.1 模塊化設(shè)計(jì)為什么要把代碼分開(kāi)LibGDX 采用模塊化設(shè)計(jì)這是它實(shí)現(xiàn)跨平臺(tái)的核心。core模塊這是游戲的“大腦”。所有的游戲邏輯、實(shí)體定義、資源管理、屏幕Screen切換代碼都應(yīng)該寫(xiě)在這里。core模塊不包含任何平臺(tái)特定的代碼因此可以被所有其他平臺(tái)模塊desktopandroid等共享和依賴。desktop模塊這是桌面版Windows, macOS, Linux的“外殼”。它主要包含一個(gè)啟動(dòng)類(lèi)DesktopLauncher負(fù)責(zé)初始化一個(gè)特定于桌面的應(yīng)用配置如窗口大小、標(biāo)題然后啟動(dòng)core模塊里的主游戲類(lèi)。它的assets文件夾通常為空或放一些桌面測(cè)試專(zhuān)用的資源因?yàn)楣蚕碣Y源應(yīng)該在core/assets里。android模塊這是 Android 版的“外殼”。結(jié)構(gòu)更復(fù)雜包含 Android 清單文件AndroidManifest.xml、活動(dòng)Activity和配置。它的assets文件夾同樣通常鏈接或依賴于core/assets。關(guān)鍵理解你 90% 的編碼工作都在core模塊里。當(dāng)你修改了core的代碼無(wú)論是桌面版還是安卓版都會(huì)生效。平臺(tái)模塊只是提供了不同的“包裝盒”和啟動(dòng)方式。3.2 核心入口ApplicationListener與游戲主類(lèi)打開(kāi)core/src下你命名的那個(gè)主游戲類(lèi)例如MyFirstGame.java。它實(shí)現(xiàn)了ApplicationListener接口。這個(gè)接口定義了游戲生命周期中 LibGDX 核心會(huì)回調(diào)的幾個(gè)最重要的方法public class MyFirstGame extends ApplicationAdapter { // ApplicationAdapter 是 ApplicationListener 的空實(shí)現(xiàn)更方便 Override public void create () { // 游戲啟動(dòng)時(shí)調(diào)用一次。用于初始化資源加載紋理、聲音、創(chuàng)建精靈、初始化游戲狀態(tài)。 } Override public void render () { // 每一幀都會(huì)被調(diào)用。這是游戲邏輯更新和畫(huà)面渲染的核心循環(huán)。 } Override public void resize (int width, int height) { // 當(dāng)窗口大小改變時(shí)調(diào)用。用于調(diào)整你的視口Viewport和 UI 布局。 } Override public void pause () { // 當(dāng)游戲失去焦點(diǎn)時(shí)調(diào)用如接到電話切換到其他應(yīng)用??梢员4媾R時(shí)狀態(tài)。 } Override public void resume () { // 當(dāng)游戲重新獲得焦點(diǎn)時(shí)調(diào)用??梢曰謴?fù)狀態(tài)。 } Override public void dispose () { // 游戲結(jié)束時(shí)調(diào)用。必須在這里釋放所有占用的原生資源紋理、聲音、字體等防止內(nèi)存泄漏。 } }create和render是你最常打交道的方法。記住一個(gè)原則加載放在create 循環(huán)放在render 清理放在dispose。3.3 資源管理assets文件夾的約定所有游戲資源圖片、聲音、字體、粒子效果、地圖文件等都應(yīng)該放在core/assets/目錄下。你可以在這個(gè)目錄下創(chuàng)建子文件夾來(lái)分類(lèi)管理例如images/sounds/fonts/。LibGDX 提供了一套資源管理類(lèi)AssetManager但初期我們可以使用更直接的方式。例如在create()方法中加載一張圖片Texture playerTexture; Override public void create () { playerTexture new Texture(Gdx.files.internal(images/player.png)); }這里Gdx.files.internal()就是用來(lái)訪問(wèn)assets目錄下的文件的。注意文件路徑是相對(duì)于assets的且不包含assets/前綴。4. 實(shí)現(xiàn)第一個(gè)可交互場(chǎng)景渲染、輸入與游戲循環(huán)現(xiàn)在我們來(lái)讓這個(gè)灰色窗口變得有趣一點(diǎn)。我們會(huì)創(chuàng)建一個(gè)簡(jiǎn)單的“方塊”用鍵盤(pán)控制它移動(dòng)。4.1 渲染一個(gè)精靈Sprite首先我們需要一些概念Texture代表加載到顯存中的圖像數(shù)據(jù)。SpriteBatch是 LibGDX 的 2D 渲染器。幾乎所有 2D 圖像的繪制都要通過(guò)它。它非常高效因?yàn)樗鼤?huì)盡可能地將多次繪制調(diào)用合并。坐標(biāo)系統(tǒng)LibGDX 的默認(rèn) 2D 坐標(biāo)原點(diǎn)(0, 0)在屏幕左下角。X 軸向右增長(zhǎng)Y 軸向上增長(zhǎng)。這個(gè)和很多圖形庫(kù)不同需要注意。我們來(lái)修改主游戲類(lèi)public class MyFirstGame extends ApplicationAdapter { private SpriteBatch batch; // 渲染器 private Texture img; // 紋理 private float x, y; // 方塊的位置 Override public void create () { batch new SpriteBatch(); // 在 assets 目錄下放一張名為 badlogic.jpg 的圖片生成項(xiàng)目時(shí)自帶 img new Texture(Gdx.files.internal(“badlogic.jpg”)); // 初始位置設(shè)在屏幕中心 x Gdx.graphics.getWidth() / 2 - img.getWidth() / 2; y Gdx.graphics.getHeight() / 2 - img.getHeight() / 2; } Override public void render () { // 1. 清屏用某種顏色填充整個(gè)屏幕否則上一幀的內(nèi)容會(huì)殘留 Gdx.gl.glClearColor(0.2f, 0.2f, 0.2f, 1); // 設(shè)置清屏顏色為深灰色 (R,G,B,A) Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); // 執(zhí)行清屏 // 2. 開(kāi)始繪制 batch.begin(); // 在 (x, y) 位置繪制紋理 batch.draw(img, x, y); // 結(jié)束繪制 batch.end(); } Override public void dispose () { // 釋放資源非常重要 batch.dispose(); img.dispose(); } }運(yùn)行一下你應(yīng)該能看到 LibGDX 的經(jīng)典紅色壞笑臉 Logo 出現(xiàn)在窗口中央。我們完成了靜態(tài)渲染。4.2 處理鍵盤(pán)輸入讓方塊動(dòng)起來(lái)LibGDX 的輸入處理非常直接。我們通過(guò)Gdx.input來(lái)查詢當(dāng)前幀的輸入狀態(tài)。在render()方法中清屏之后繪制之前加入輸入邏輯Override public void render () { // 處理輸入每幀檢查按鍵狀態(tài) float speed 200; // 每秒移動(dòng) 200 像素 float deltaTime Gdx.graphics.getDeltaTime(); // 獲取上一幀到這一幀的時(shí)間差秒 if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) { x - speed * deltaTime; } if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) { x speed * deltaTime; } if (Gdx.input.isKeyPressed(Input.Keys.DOWN)) { y - speed * deltaTime; } if (Gdx.input.isKeyPressed(Input.Keys.UP)) { y speed * deltaTime; } // 清屏和繪制代碼保持不變... Gdx.gl.glClearColor(0.2f, 0.2f, 0.2f, 1); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); batch.begin(); batch.draw(img, x, y); batch.end(); }關(guān)鍵點(diǎn)Gdx.graphics.getDeltaTime()。這是游戲編程的基石——基于時(shí)間的運(yùn)動(dòng)。不同電腦的render方法調(diào)用頻率幀率 FPS是不同的。如果你用x 5;這種每幀固定加5的方式移動(dòng)在 60 幀的電腦上會(huì)比在 30 幀的電腦上快一倍。乘以deltaTime后speed * deltaTime就表示“這一幀應(yīng)該移動(dòng)的距離”從而保證了在任何幀率下物體的移動(dòng)速度是恒定的?,F(xiàn)在運(yùn)行程序按住方向鍵方塊應(yīng)該能以恒定速度移動(dòng)了。你已經(jīng)實(shí)現(xiàn)了一個(gè)最基礎(chǔ)的游戲循環(huán)輸入 - 更新邏輯位置 - 渲染。4.3 引入簡(jiǎn)單的游戲?qū)ο蟾拍钅壳拔覀儼盐恢脁, y直接放在主類(lèi)里。當(dāng)游戲?qū)ο蠖嗥饋?lái)時(shí)這會(huì)變得混亂。我們來(lái)簡(jiǎn)單封裝一下// 一個(gè)簡(jiǎn)單的游戲?qū)ο箢?lèi) public class GameObject { public Texture texture; public float x, y; public float width, height; public GameObject(Texture texture, float x, float y) { this.texture texture; this.x x; this.y y; this.width texture.getWidth(); this.height texture.getHeight(); } public void draw(SpriteBatch batch) { batch.draw(texture, x, y); } }然后在主類(lèi)中使用它public class MyFirstGame extends ApplicationAdapter { private SpriteBatch batch; private GameObject player; // 使用 GameObject Override public void create () { batch new SpriteBatch(); Texture playerTex new Texture(Gdx.files.internal(“badlogic.jpg”)); float startX Gdx.graphics.getWidth() / 2 - playerTex.getWidth() / 2; float startY Gdx.graphics.getHeight() / 2 - playerTex.getHeight() / 2; player new GameObject(playerTex, startX, startY); } Override public void render () { // 輸入處理現(xiàn)在更新 player 對(duì)象 float speed 200; float deltaTime Gdx.graphics.getDeltaTime(); if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) { player.x - speed * deltaTime; } // ... 其他方向鍵處理 // 渲染 Gdx.gl.glClearColor(0.2f, 0.2f, 0.2f, 1); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); batch.begin(); player.draw(batch); // 通過(guò)對(duì)象方法繪制 batch.end(); } Override public void dispose () { batch.dispose(); player.texture.dispose(); // 記得釋放紋理 } }雖然這個(gè)GameObject還很簡(jiǎn)陋但它引入了“對(duì)象”的概念為后續(xù)添加更多屬性如速度、生命值和方法如更新、碰撞檢測(cè)打下了基礎(chǔ)。5. 從 Demo 到項(xiàng)目下一步該做什么以及如何避坑完成了第一個(gè)可交互場(chǎng)景你已經(jīng)跨過(guò)了 LibGDX 最初的門(mén)檻。但要把這變成一個(gè)真正的游戲項(xiàng)目還需要系統(tǒng)性地搭建更多東西。下面是我根據(jù)經(jīng)驗(yàn)梳理的后續(xù)步驟和常見(jiàn)陷阱。5.1 構(gòu)建游戲架構(gòu)屏幕Screen與狀態(tài)管理一個(gè)游戲通常有主菜單、游戲場(chǎng)景、暫停界面、游戲結(jié)束界面等。直接在render()里用if-else判斷狀態(tài)會(huì)很快變得難以維護(hù)。LibGDX 提供了Screen接口來(lái)管理不同的游戲“屏幕”。該怎么做為每個(gè)界面創(chuàng)建一個(gè)類(lèi)實(shí)現(xiàn)Screen接口或繼承ScreenAdapter。在主游戲類(lèi)實(shí)現(xiàn)ApplicationListener的那個(gè)中持有一個(gè)當(dāng)前Screen的引用。在render()中委托調(diào)用當(dāng)前屏幕的render(delta)方法。通過(guò)一個(gè)Game類(lèi)LibGDX 提供的它本身實(shí)現(xiàn)了ApplicationListener可以更方便地管理屏幕切換。官方生成的項(xiàng)目默認(rèn)使用Game類(lèi)它內(nèi)置了setScreen()方法。避坑點(diǎn)屏幕切換時(shí)舊屏幕的資源dispose和新屏幕的資源create要管理好避免內(nèi)存泄漏或資源沖突。5.2 高效資源管理使用 AssetManager在create()里用new Texture()直接加載資源對(duì)于小項(xiàng)目沒(méi)問(wèn)題。但資源一多尤其是需要異步加載如顯示加載條時(shí)就必須使用AssetManager。該怎么做創(chuàng)建一個(gè)全局的或每個(gè)屏幕獨(dú)有的AssetManager實(shí)例。在加載界面使用manager.load(“path/to/asset.png”, Texture.class)將資源加入隊(duì)列。調(diào)用manager.update()進(jìn)行漸進(jìn)式加載并可以通過(guò)manager.getProgress()獲取進(jìn)度來(lái)更新進(jìn)度條。加載完成后在任何地方使用manager.get(“path/to/asset.png”, Texture.class)獲取資源。在游戲退出或屏幕銷(xiāo)毀時(shí)調(diào)用manager.dispose()或manager.unload()。避坑點(diǎn)AssetManager根據(jù)文件名和類(lèi)型作為唯一鍵來(lái)存儲(chǔ)資源。確保用相同的路徑字符串去獲取。另外它默認(rèn)不是線程安全的。5.3 處理不同屏幕尺寸視口Viewport是救星你的游戲可能在 1920x1080 的電腦上開(kāi)發(fā)但也要能在 1280x720 或各種奇葩分辨率的手機(jī)上運(yùn)行。直接使用像素坐標(biāo)會(huì)導(dǎo)致畫(huà)面拉伸或布局錯(cuò)亂。Viewport視口就是用來(lái)解決這個(gè)問(wèn)題的。該怎么做定義虛擬屏幕大小為你的游戲設(shè)計(jì)一個(gè)固定的邏輯分辨率例如 800x480寬高比 5:3。所有的游戲邏輯、物體位置都基于這個(gè)虛擬坐標(biāo)。選擇合適的 ViewportFitViewport保持虛擬寬高比將游戲內(nèi)容完整顯示在屏幕上可能產(chǎn)生黑邊。最常用。FillViewport保持虛擬寬高比填滿屏幕可能裁剪內(nèi)容。StretchViewport拉伸內(nèi)容以填滿屏幕會(huì)導(dǎo)致變形不推薦。ScreenViewport不使用虛擬大小1 個(gè)邏輯單位等于 1 個(gè)屏幕像素適用于像素級(jí)精確的 UI。在create()中創(chuàng)建 Viewport在resize()中調(diào)用viewport.update(width, height)在render()中使用viewport.apply()來(lái)設(shè)置投影矩陣并使用viewport.getCamera()來(lái)獲取相機(jī)用于繪制。避坑點(diǎn)UI 元素如按鈕、文字通常需要另一套坐標(biāo)處理使用Stage和UiViewport不要和游戲世界的視口混用。5.4 輸入處理進(jìn)階事件監(jiān)聽(tīng)與多點(diǎn)觸控Gdx.input.isKeyPressed()是即時(shí)狀態(tài)查詢適合實(shí)時(shí)控制。但對(duì)于按鈕點(diǎn)擊、菜單選擇等“事件”使用事件監(jiān)聽(tīng)器更清晰。對(duì)于移動(dòng)端需要處理觸屏、手勢(shì)和多點(diǎn)觸控。該怎么做桌面輸入除了按鍵查詢還可以使用InputProcessor接口或InputAdapter類(lèi)來(lái)監(jiān)聽(tīng)keyDownkeyUptouchDown等事件。移動(dòng)端輸入Gdx.input提供了isTouched()getX()getY()等方法。對(duì)于復(fù)雜手勢(shì)可以考慮使用 LibGDX 的GestureDetector。UI 輸入如果你使用了 LibGDX 的 Scene2D UI 庫(kù)推薦那么按鈕等 UI 元素的點(diǎn)擊事件是通過(guò)addListener來(lái)注冊(cè)的與游戲世界的輸入是分離的。避坑點(diǎn)移動(dòng)端的坐標(biāo)原點(diǎn)在左上角與桌面版默認(rèn)的左下角不同。使用viewport或camera進(jìn)行坐標(biāo)轉(zhuǎn)換可以屏蔽這個(gè)差異。5.5 性能與調(diào)試早期就要養(yǎng)成的習(xí)慣性能探查L(zhǎng)ibGDX 提供了Graphics類(lèi)的getFramesPerSecond()方法。在屏幕角落顯示 FPS 是基礎(chǔ)操作。如果 FPS 過(guò)低檢查是否在render循環(huán)中創(chuàng)建了新對(duì)象導(dǎo)致頻繁垃圾回收或者紋理尺寸是否過(guò)大。內(nèi)存管理牢記dispose()。所有實(shí)現(xiàn)了Disposable接口的類(lèi)TextureSoundMusicSpriteBatchAssetManager等在使用完畢后都必須調(diào)用dispose()。一個(gè)常見(jiàn)的模式是在Screen的hide()或dispose()方法中釋放該屏幕獨(dú)有的資源。日志輸出使用Gdx.app.log(String tag, String message)而不是System.out.println()。LibGDX 的日志在不同平臺(tái)上有統(tǒng)一輸出且可以設(shè)置日志級(jí)別。調(diào)試?yán)L圖對(duì)于碰撞檢測(cè)、物理引擎調(diào)試可以使用Box2DDebugRenderer如果用了 Box2D或自己用ShapeRenderer在render循環(huán)中繪制簡(jiǎn)單的幾何形狀如矩形、圓形來(lái)可視化邊界框。從第一個(gè)移動(dòng)的方塊到一個(gè)完整的游戲中間隔著大量的工程實(shí)踐。我的建議是不要試圖一次性學(xué)會(huì)所有東西。定一個(gè)小目標(biāo)比如“做一個(gè)可以移動(dòng)、發(fā)射子彈、擊中敵人就消失的飛機(jī)”然后圍繞這個(gè)目標(biāo)去學(xué)習(xí)如何管理多個(gè)游戲?qū)ο?、處理碰撞、播放音效、顯示分?jǐn)?shù)。每完成一個(gè)功能你對(duì) LibGDX 的理解就會(huì)深一層。遇到問(wèn)題優(yōu)先查閱官方 Wiki 和 API 文檔社區(qū)的解決方案通常也很成熟。記住穩(wěn)定的框架加上清晰的代碼結(jié)構(gòu)是獨(dú)立游戲項(xiàng)目能走到最后的關(guān)鍵。