
這次我們來看一個面向游戲開發初學者的實戰項目使用 Godot 引擎復刻一款《土豆兄弟》Brotato風格的 2D 俯視角生存游戲。這個項目的重點不是讓你從零開始設計復雜的游戲系統而是通過一個具體的、可運行的案例快速掌握 Godot 在 2D 游戲開發中的核心工作流和關鍵模塊。如果你對獨立游戲開發感興趣想了解如何用 Godot 實現角色控制、敵人 AI、武器系統、升級機制和 Roguelike 元素這篇文章可以直接收藏。我們將從環境搭建開始一步步拆解游戲的核心功能實現并最終得到一個可以運行、可以擴展的游戲原型。整個過程不涉及復雜的 3D 渲染或底層圖形學重點關注 2D 游戲開發的實用技巧和 Godot 引擎的高效用法。1. 核心能力速覽能力項說明項目類型2D 俯視角生存 Roguelike 游戲原型技術棧Godot 4.x (GDScript)核心功能玩家移動與射擊、敵人 AI 與波次生成、武器與升級系統、UI 與狀態管理開發門檻無需深厚圖形學基礎具備基礎編程邏輯即可上手運行環境Windows / macOS / LinuxGodot 4.x 編輯器輸出目標可獨立運行的 PC 端游戲原型可導出為 exe, app 等適合場景Godot 初學者實戰、2D 游戲原型快速驗證、Roguelike 機制學習2. 適用場景與使用邊界這個項目主要適合以下幾類開發者Godot 初學者通過一個完整的項目快速理解場景Scene、節點Node、腳本GDScript和信號Signal等核心概念的實際應用。2D 游戲愛好者希望了解如何構建一個類似《吸血鬼幸存者》、《土豆兄弟》的俯視角生存游戲框架。獨立游戲原型師需要快速驗證一個包含移動、戰斗、成長系統的游戲核心循環是否有趣。它能解決什么問題提供一個結構清晰、代碼可讀的 Godot 4 項目模板。演示如何組織 2D 游戲中的常見模塊玩家、敵人、子彈、UI。展示 Godot 內置的物理系統、動畫系統、UI 系統在實戰中的用法。實現一個可玩的、具備基本 Roguelike 元素波次、升級選擇的游戲原型。它不適合什么場景大型商業項目直接復用這是一個教學原型代碼結構和資源管理未做深度優化。追求極致性能的硬核動作游戲物理碰撞、敵人數量極大時可能需要性能優化。需要復雜 3D 效果或網絡聯機的游戲本項目完全基于 2D 模塊。版權與合規提醒本項目為學習目的復刻的是《土豆兄弟》的游戲玩法機制而非美術資源、劇情或 IP。所有使用的美術、音效素材應為原創或使用明確允許商用的資源如 Kenney.nl, Itch.io 上的免費資源包。開發完成后若計劃發布請確保所有素材擁有合法授權。3. 環境準備與前置條件開始之前請確保你的開發環境滿足以下要求操作系統Windows 10/11, macOS 10.14, 或主流 Linux 發行版。Godot 引擎跨平臺支持良好。Godot 引擎下載并安裝Godot 4.x穩定版。建議從官方網站或 Steam 獲取。官網下載訪問 Godot 官網選擇 “Download” - “Godot 4.x Stable”。版本選擇對于本 2D 項目標準版.NET 版本可選即可無需渲染功能更強的版本。磁盤空間預留至少 500MB 空間用于安裝引擎和存放項目資源。基礎技能對編程有基本了解任何語言能理解變量、函數、循環等概念。GDScript 語法類似 Python上手很快。檢查清單[ ] Godot 4.x 已安裝并可正常啟動。[ ] 在電腦上創建一個專門用于存放 Godot 項目的文件夾例如D:\Godot_Projects。[ ] 準備一些占位用的美術資源如 16x16 或 32x32 的 PNG 圖片用于快速原型開發。可以使用簡單的幾何圖形方塊、圓形代替。4. 項目創建與基礎結構啟動 Godot 引擎我們將從零開始創建項目。4.1 創建新項目點擊 “New Project” 按鈕。輸入項目名稱例如BrotatoStyleDemo。選擇項目存儲路徑指向你剛才創建的文件夾。渲染器選擇對于 2D 項目選擇 “Forward” 或 “Mobile” 均可性能差異不大。點擊 “Create Edit”。4.2 規劃項目文件夾結構一個清晰的結構有助于管理資源。在 Godot 的文件系統面板中創建以下文件夾res:// ├── actors/ # 活動實體 │ ├── player/ # 玩家相關場景和腳本 │ └── enemies/ # 敵人相關 ├── weapons/ # 武器和子彈場景 ├── ui/ # 用戶界面場景 ├── scenes/ # 主場景、房間場景 ├── scripts/ # 全局或工具腳本 ├── assets/ # 美術、音效資源 │ ├── sprites/ │ ├── sounds/ │ └── fonts/ └── autoloads/ # 自動加載的單例腳本如游戲管理器你可以通過右鍵點擊res://- “New Folder” 來創建。4.3 設置窗口與項目設置窗口尺寸進入Project - Project Settings...。在Display - Window下設置Width: 1152Height: 648Test Width/Height: 同上可選Stretch Mode:canvas_itemsStretch Aspect:keep這樣設置是為了一個固定的 16:9 游戲視圖并允許在不同分辨率下縮放。輸入映射在Project Settings中切換到Input Map標簽頁。我們需要添加游戲所需的輸入動作。點擊 “Add New Action”輸入move_right然后點擊 “Add”。同樣方法添加move_left,move_up,move_down,shoot。為move_right分配鍵盤按鍵D或Right。其他方向鍵同理。為shoot分配鼠標左鍵Mouse Button Left或鍵盤按鍵如Space。5. 核心模塊實現玩家角色玩家是游戲的核心。我們將創建一個可移動、可射擊的玩家角色。5.1 創建玩家場景右鍵點擊actors/player文件夾選擇 “New Scene”。選擇CharacterBody2D作為根節點命名為Player。為Player節點添加子節點Sprite2D用于顯示玩家形象。暫時可以給它一個默認的icon.svg或你自己的精靈圖。CollisionShape2D用于物理碰撞。為其Shape屬性選擇一個CircleShape2D或RectangleShape2D并調整大小匹配精靈。Timer命名為ShootTimer用于控制射擊間隔。選中根節點Player點擊右側 “Node” 標簽頁下的 “Script” 按鈕為它創建一個新腳本保存為player.gd。5.2 編寫玩家移動腳本 (player.gd)extends CharacterBody2D # 導出變量方便在編輯器中調整 export var speed: float 300.0 export var bullet_scene: PackedScene # 將在編輯器中關聯子彈場景 export var fire_rate: float 0.3 # 每秒發射次數 onready var shoot_timer: Timer $ShootTimer func _ready(): # 設置射擊計時器 shoot_timer.wait_time 1.0 / fire_rate shoot_timer.start() func _physics_process(delta): # 1. 獲取輸入向量 var input_vector : Vector2.ZERO input_vector.x Input.get_action_strength(move_right) - Input.get_action_strength(move_left) input_vector.y Input.get_action_strength(move_down) - Input.get_action_strength(move_up) input_vector input_vector.normalized() # 標準化防止斜向移動更快 # 2. 計算速度并移動 velocity input_vector * speed move_and_slide() func _process(delta): # 朝向鼠標 look_at(get_global_mouse_position()) func _on_shoot_timer_timeout(): if bullet_scene and Input.is_action_pressed(shoot): shoot() func shoot(): var bullet_instance bullet_scene.instantiate() # 設置子彈的初始位置和方向 bullet_instance.global_position global_position bullet_instance.rotation rotation # 子彈朝向與玩家相同 # 將子彈添加到當前場景樹中 get_parent().add_child(bullet_instance)代碼解析_physics_process每幀調用用于處理物理相關的邏輯如移動。我們使用CharacterBody2D的move_and_slide()方法處理碰撞和移動。_process每幀調用用于處理非物理邏輯這里讓玩家精靈始終面向鼠標。_on_shoot_timer_timeout連接到ShootTimer的timeout信號。當計時器到期時如果按住射擊鍵則調用shoot()。shoot()實例化子彈場景設置其位置和方向并添加到場景中。5.3 創建子彈場景在weapons/文件夾下新建場景根節點選擇Area2D命名為Bullet。添加子節點Sprite2D,CollisionShape2D。為根節點Bullet添加腳本bullet.gd。extends Area2D export var speed: float 600.0 export var damage: int 1 func _ready(): # 子彈在2秒后自動銷毀防止內存泄漏 await get_tree().create_timer(2.0).timeout queue_free() func _physics_process(delta): # 向前移動 position transform.x * speed * delta func _on_body_entered(body): # 如果碰撞到敵人這里假設敵人有“enemy”組 if body.is_in_group(enemy): # 調用敵人受傷函數后續實現 if body.has_method(take_damage): body.take_damage(damage) # 子彈命中后消失 queue_free()回到Player場景在編輯器中將bullet_scene屬性關聯到你剛創建的Bullet.tscn。6. 核心模塊實現敵人與波次系統敵人需要自動尋路攻擊玩家并按波次生成。6.1 創建基礎敵人場景在actors/enemies/下新建場景根節點為CharacterBody2D命名為BasicEnemy。添加Sprite2D,CollisionShape2D,HealthBar一個ProgressBar節點需調整方向等子節點。為根節點添加腳本basic_enemy.gd。extends CharacterBody2D class_name BasicEnemy # 給個類名方便識別 export var max_health: int 10 export var speed: float 150.0 export var damage: int 1 var current_health: int var player_node: Node2D onready var health_bar: ProgressBar $HealthBar func _ready(): current_health max_health health_bar.max_value max_health health_bar.value current_health # 查找玩家節點假設玩家在場景中且名為“Player” player_node get_tree().get_first_node_in_group(player) if not player_node: print(Warning: Player node not found in group player) func _physics_process(delta): if not player_node: return # 簡單的追逐AI朝向玩家移動 var direction (player_node.global_position - global_position).normalized() velocity direction * speed move_and_slide() # 也可以添加簡單的避障邏輯 func take_damage(amount: int): current_health - amount health_bar.value current_health if current_health 0: die() func die(): # 死亡效果播放動畫、產生經驗球、發出死亡信號等 # 例如通知游戲管理器敵人死亡 GameManager.enemy_died() queue_free()記得將玩家節點添加到 “player” 組中。在Player場景的根節點上在 “Node” 面板的 “Groups” 選項卡中添加組名 “player”。6.2 實現波次生成器我們需要一個管理器來控制敵人何時生成、生成多少。在scenes/下創建新場景根節點為Node2D命名為WaveSpawner。為其添加腳本wave_spawner.gd。這個腳本將負責定義波次數據每波敵人數量、類型、生成間隔。在場景中預設的生成點Marker2D節點生成敵人。控制波次之間的等待時間。extends Node2D export var enemy_scenes: Array[PackedScene] [] export var spawn_points: Array[Marker2D] [] export var wave_data: Array[Dictionary] [ {enemy_count: 5, spawn_delay: 1.0}, {enemy_count: 8, spawn_delay: 0.8}, # ... 更多波次 ] var current_wave_index: int 0 var enemies_alive: int 0 onready var game_manager get_node(/root/GameManager) # 假設有全局管理器 func _ready(): start_next_wave() func start_next_wave(): if current_wave_index wave_data.size(): print(All waves completed!) return var wave wave_data[current_wave_index] spawn_wave(wave[enemy_count], wave[spawn_delay]) current_wave_index 1 func spawn_wave(count: int, delay: float): for i in range(count): if enemy_scenes.size() 0 and spawn_points.size() 0: var enemy_scene enemy_scenes[0] # 簡單起見使用第一種敵人 var spawn_point spawn_points.pick_random() var enemy_instance enemy_scene.instantiate() enemy_instance.global_position spawn_point.global_position get_parent().add_child(enemy_instance) enemies_alive 1 # 連接敵人的死亡信號以便計數 if enemy_instance.has_signal(died): enemy_instance.died.connect(_on_enemy_died) await get_tree().create_timer(delay).timeout func _on_enemy_died(): enemies_alive - 1 if enemies_alive 0: # 所有敵人都被消滅可以開始下一波或顯示升級選擇 game_manager.wave_cleared()在主場景中放置幾個Marker2D節點作為生成點并將它們拖入WaveSpawner節點的spawn_points屬性數組中。7. 核心模塊實現游戲管理器與 UI游戲管理器GameManager作為全局單例負責管理游戲狀態分數、波次、玩家狀態、控制游戲流程開始、結束、暫停和協調其他系統。7.1 創建自動加載的單例在autoloads/文件夾下創建腳本game_manager.gd。進入Project - Project Settings... - Autoload。點擊 “Add”在 “Path” 中選擇你剛創建的game_manager.gd在 “Name” 中輸入GameManager注意大小寫。點擊 “Add”。這樣GameManager就會在游戲啟動時自動加載并可以在任何腳本中通過GameManager直接訪問。extends Node # 游戲狀態信號 signal score_changed(new_score) signal wave_changed(new_wave) signal player_health_changed(new_health) signal game_over var score: int 0: set(value): score value score_changed.emit(score) var current_wave: int 1: set(value): current_wave value wave_changed.emit(current_wave) var player_health: int 100: set(value): player_health value player_health_changed.emit(player_health) if player_health 0: game_over.emit() func _ready(): # 初始化 pass func add_score(points: int): score points func enemy_died(): add_score(10) # 每殺死一個敵人得10分 # 其他邏輯... func wave_cleared(): current_wave 1 # 顯示升級選擇UI等 show_upgrade_selection() func show_upgrade_selection(): # 這里可以暫停游戲并顯示一個包含多個升級選項的UI get_tree().paused true # 實例化并顯示升級UI場景 var upgrade_ui preload(res://ui/upgrade_selection.tscn).instantiate() get_tree().root.add_child(upgrade_ui)7.2 創建游戲主界面 UIGodot 的 UI 系統基于 Control 節點。我們將創建一個簡單的 HUD。在ui/文件夾下創建新場景根節點為CanvasLayer命名為HUD。CanvasLayer可以確保 UI 始終繪制在最上層。在CanvasLayer下添加一個Control節點作為容器然后添加以下子節點Label命名為ScoreLabel用于顯示分數。Label命名為WaveLabel用于顯示當前波次。TextureProgressBar或ProgressBar命名為HealthBar用于顯示玩家生命值。為HUD根節點添加腳本hud.gd。extends CanvasLayer onready var score_label: Label $Control/ScoreLabel onready var wave_label: Label $Control/WaveLabel onready var health_bar: ProgressBar $Control/HealthBar func _ready(): # 連接全局管理器的信號 GameManager.score_changed.connect(update_score) GameManager.wave_changed.connect(update_wave) GameManager.player_health_changed.connect(update_health) # 初始化顯示 update_score(GameManager.score) update_wave(GameManager.current_wave) update_health(GameManager.player_health) func update_score(new_score: int): score_label.text Score: %d % new_score func update_wave(new_wave: int): wave_label.text Wave: %d % new_wave func update_health(new_health: int): health_bar.value new_health在你的主游戲場景中實例化這個HUD.tscn。8. 功能整合與效果驗證現在我們將所有模塊組合起來創建一個可玩的游戲原型。8.1 構建主游戲場景創建一個新場景作為主場景根節點可以是Node2D命名為Main。將以下場景實例化拖入為子節點Player.tscn(放在場景中央)WaveSpawner.tscnHUD.tscn你可能還需要添加一個TileMap節點作為背景地板以及一些StaticBody2D節點作為障礙物。在Project Settings - Application - Run中將主場景設置為Main.tscn。8.2 運行與基礎測試點擊編輯器頂部的 “運行” 按鈕或按 F5。你應該能看到玩家角色出現在場景中。使用WASD或方向鍵可以移動角色。角色始終面向鼠標。按住鼠標左鍵或射擊鍵角色會按固定頻率朝鼠標方向發射子彈。WaveSpawner開始生成敵人如果設置了波次數據。敵人生成后會朝玩家移動。子彈擊中敵人敵人血量減少血條更新生命值歸零后敵人消失。屏幕左上角的 UI 會更新分數、波次和玩家生命值。測試要點移動與射擊是否流暢射擊方向是否正確碰撞檢測子彈是否能正確擊中敵人并觸發take_damage敵人碰撞體是否合理AI 行為敵人是否持續追蹤玩家遇到障礙物時行為如何目前是直接穿過后續可加導航網格波次系統一波敵人被消滅后是否會觸發wave_cleared并進入下一波UI 響應分數、波次、生命值變化時UI 是否實時更新性能觀察打開 Godot 編輯器底部的 “調試器” 面板切換到 “監視器” 標簽頁。觀察 “幀時間ms” 和 “物理幀時間ms”。在數十個敵人和子彈同時存在時幀率是否保持穩定60 FPS 對應約 16.6ms8.3 擴展功能驗證升級系統《土豆兄弟》的核心樂趣之一在于每波結束后的升級選擇。我們來簡單實現一個。創建升級選擇 UI 場景 (ui/upgrade_selection.tscn)。它是一個覆蓋全屏的半透明面板上面有 3 個按鈕每個按鈕代表一種升級如最大生命值、射擊速度、移動速度。在game_manager.gd的show_upgrade_selection方法中我們已經暫停游戲并顯示這個 UI。為每個升級按鈕連接pressed信號在回調函數中應用升級效果然后關閉 UI 并恢復游戲。# 在 upgrade_selection.gd 中示例 func _on_health_upgrade_pressed(): GameManager.player_max_health 20 # 需要先在GameManager定義這個變量 GameManager.player_health GameManager.player_max_health close_upgrade_ui() func close_upgrade_ui(): get_tree().paused false queue_free()再次運行游戲消滅第一波敵人后游戲應暫停并彈出升級選擇界面。選擇一項后游戲繼續玩家屬性得到增強。9. 資源管理與性能優化建議當項目逐漸復雜時需要注意以下方面對象池Object Pooling頻繁創建和銷毀子彈、敵人會帶來內存分配開銷。可以實現一個簡單的對象池預先實例化一定數量的對象使用時激活不用時禁用并放回池中。信號Signals解耦大量使用全局管理器GameManager作為中介傳遞消息可以減少節點之間的直接引用使代碼更清晰。例如敵人死亡時發出died信號WaveSpawner和GameManager都可以連接這個信號而不是敵人直接調用它們的方法。資源預加載Preloading對于常用的場景如子彈、敵人可以在游戲開始時預加載到內存中。# 在 GameManager 中 var bullet_scene preload(res://weapons/bullet.tscn)使用圖層Layer和蒙版Mask在CollisionObject2D如Area2D,CharacterBody2D的屬性中合理設置Collision Layer和Collision Mask。例如讓子彈只與敵人層碰撞敵人只與玩家和墻壁層碰撞可以避免不必要的碰撞計算。簡化物理形狀為精靈使用的碰撞形狀應盡可能簡單圓形、矩形、膠囊形避免使用復雜的凹多邊形以提升物理性能。10. 常見問題與排查方法問題現象可能原因排查方式解決方案運行后黑屏只有背景色主場景未設置或攝像機缺失檢查Project Settings - Application - Run - Main Scene路徑是否正確。在主場景中確保有一個Camera2D節點。正確設置主場景并添加Camera2D節點。玩家無法移動輸入映射未正確設置或腳本未處理輸入1. 檢查Project Settings - Input Map中動作名是否與腳本中Input.get_action_strength使用的名字完全一致。2. 在Player腳本的_physics_process中打印input_vector查看是否有值。修正輸入映射名稱或腳本中的動作名。確保_physics_process被調用。子彈不發射bullet_scene屬性未關聯或ShootTimer未啟動1. 在Player場景編輯器中檢查bullet_scene屬性是否已拖入Bullet.tscn。2. 檢查ShootTimer節點的Autostart是否勾選或在_ready中是否調用了start()。正確關聯場景確保計時器啟動。在_ready中打印bullet_scene檢查是否為null。敵人不追蹤玩家未找到玩家節點或player_node為null在敵人腳本的_physics_process中打印player_node。確保玩家根節點加入了 “player” 組。將玩家節點添加到 “player” 組。或在敵人腳本中使用其他可靠方式查找玩家如通過標簽。碰撞未觸發碰撞層/蒙版未設置或信號未連接1. 檢查發生碰撞的兩個節點如Bullet(Area2D) 和Enemy(CharacterBody2D)的Collision Layer/Mask是否有交集。2. 檢查Bullet的_on_body_entered信號是否連接到其自身的body_entered信號。在編輯器或代碼中正確設置碰撞層。確保信號已連接藍色圖標。UI 不更新信號未正確連接或路徑錯誤1. 檢查HUD.gd中_ready函數里的GameManager.signal_name.connect(...)是否拼寫正確。2. 檢查GameManager中定義的信號是否使用signal關鍵字聲明了。確保信號名稱、連接函數名完全一致。使用編輯器的信號連接工具可視化操作更可靠。游戲運行卡頓同時存在大量活動節點敵人、子彈打開調試器“監視器”觀察“對象計數”和“幀時間”。可能是沒有及時銷毀節點如子彈未設置生命周期。為子彈、特效等臨時對象設置Timer自動銷毀。考慮實現對象池。簡化碰撞形狀和精靈圖。11. 總結與下一步通過這個項目我們快速實現了一個《土豆兄弟》風格的 2D 生存游戲核心框架。你不僅學會了 Godot 的基本操作更重要的是掌握了如何將想法分解為可執行的模塊玩家控制、敵人 AI、武器系統、波次邏輯、UI 交互和全局狀態管理。最值得嘗試的擴展方向更多武器與攻擊模式實現散射槍、激光、近戰武器、自動追蹤導彈等。為武器設計不同的數據結構傷害、射速、子彈速度、特殊效果。豐富的升級系統設計一個包含數十種升級的池子每輪隨機提供 3-4 個選擇。升級可以影響玩家屬性、武器屬性甚至增加新的能力如召喚物、光環。敵人多樣性創建不同行為模式的敵人快速近戰、遠程射擊、自爆、分裂、BOSS。使用狀態機State Machine來管理敵人的不同行為狀態巡邏、追逐、攻擊、死亡。地圖與障礙物使用TileMap創建更復雜的地圖添加可破壞的障礙物增加游戲的策略性。音效與視覺反饋為射擊、擊中、升級、敵人死亡等事件添加音效和簡單的粒子效果GPUParticles2D能極大提升游戲體驗。數據持久化使用Resource來定義武器、敵人、升級的數值方便平衡和調整。實現一個簡單的存檔系統記錄最高分、解鎖內容等。建議的后續學習路徑深入 GDScript學習更高級的語言特性如信號、協程await/yield、自定義資源Resource。學習 Godot 動畫系統使用AnimationPlayer和AnimationTree為角色制作流暢的骨骼動畫或幀動畫。了解 Shader嘗試編寫簡單的 2D Shader 來實現溶解、閃爍、像素化等視覺效果。探索 Godot 插件在 AssetLib 中尋找 UI 框架、調試工具、地圖編輯器等插件來提升開發效率。這個原型項目代碼結構清晰每個模塊都相對獨立非常適合作為你第一個 Godot 項目的起點。動手修改它添加你自己的創意是學習游戲開發最有效的方式。建議收藏本文在遇到具體問題時回來查閱對應的模塊實現和排查方法。