
這次我們來看一個面向游戲開發初學者的實戰項目使用 Godot 引擎復刻一款《土豆兄弟》風格的 2D 生存射擊游戲。這個項目不是空談理論而是直接帶你從零開始一步步實現核心玩法。如果你對獨立游戲開發、Roguelike 元素、Godot 的 GDScript 編程感興趣或者想找一個能跑起來的完整案例來學習這篇文章可以直接跟著做。《土豆兄弟》是一款典型的俯視角 Roguelike 生存游戲玩家控制角色在潮水般的敵人中生存通過擊敗敵人獲取資源在每波間隙升級、購買武器和道具形成獨特的 Build。我們將使用 Godot 4 來實現其核心循環角色移動與射擊、敵人波次生成、武器系統、升級商店以及永久成長機制。本文會重點拆解如何用 Godot 高效構建這些模塊。我們將從項目結構搭建開始到玩家控制器、敵人 AI、武器數據驅動、UI 交互最后整合成完整的游戲循環。整個過程會關注 Godot 4 在 2D 開發中的特性如CharacterBody2D、信號Signals、資源Resource系統、場景實例化Instancing以及如何組織代碼以支持 Roguelike 的隨機性和成長性。1. 核心能力速覽能力項說明項目類型Godot 4 2D 游戲開發實戰教程目標成果一個可運行的、包含核心玩法的《土豆兄弟》風格游戲原型技術棧Godot Engine 4.x, GDScript, 2D 物理與渲染核心功能玩家移動與射擊、敵人波次生成與 AI、多種武器系統、升級與商店、永久成長Meta Progression開發門檻基礎編程邏輯理解無需深厚游戲開發經驗輸出平臺Windows / macOS / Linux / Web (通過 Godot 導出)適合讀者想通過完整項目學習 Godot 的初學者對 Roguelike 游戲機制實現感興趣的開發者2. 適用場景與使用邊界這個項目主要服務于學習和原型驗證。適合誰Godot 初學者希望通過一個中等復雜度的完整項目系統學習 Godot 的節點、場景、腳本和資源管理。獨立游戲開發者需要快速驗證一個俯視角射擊 Roguelike 升級玩法的原型。游戲設計學習者想了解《土豆兄弟》這類游戲的核心循環戰斗-資源-成長是如何通過代碼實現的。能解決什么問題Godot 2D 工作流實踐從空項目到可發布原型的完整路徑。游戲系統模塊化設計學習如何將復雜的游戲功能移動、戰斗、經濟、成長拆分為獨立的、可復用的場景和腳本。數據驅動設計入門使用 Godot 的Resource來定義武器、敵人、升級等數據使游戲內容易于調整和擴展。不適合什么場景追求 AAA 級畫面本文聚焦玩法原型美術資源使用簡單形狀或免費素材不涉及復雜著色器或高級渲染。直接商用這是一個教學項目核心玩法靈感來源于《土豆兄弟》直接用于商業發布需注意玩法差異化和版權風險應作為學習基礎進行深度創新。替代系統學習建議先了解 Godot 界面和 GDScript 基礎語法再通過本項目深化理解。3. 環境準備與前置條件開始前請確保你的開發環境就緒。1. 操作系統Windows 10/11, macOS 10.14, 或主流 Linux 發行版。Godot 跨平臺支持良好。2. Godot 引擎下載并安裝Godot 4.2或更高版本穩定版。建議從官方網站或 Steam 獲取。版本確認打開 Godot在編輯器右下角查看版本號。3. 磁盤空間Godot 編輯器本身約 100MB。項目文件代碼、簡單資源預計需要 50-200MB。確保有足夠空間存放導出后的游戲包。4. 基礎知識準備GDScript 基礎了解變量、函數、控制流if/for、類的基本概念。Godot 文檔有優秀的中文教程。Godot 編輯器界面熟悉場景樹Scene Tree、檢查器Inspector、文件系統FileSystem面板。2D 坐標概念了解 2D 坐標系X, Y、位置Position、旋轉Rotation等。5. 可選輔助工具圖像編輯軟件如 Aseprite, GIMP, Krita用于制作或編輯簡單的精靈Sprite。音頻編輯軟件如 Audacity用于處理音效。代碼編輯器Godot 內置腳本編輯器足夠也可配置外部編輯器如 VSCode。4. 項目結構與核心場景搭建一個清晰的項目結構是成功的一半。我們先在 Godot 中創建文件夾并搭建核心場景。步驟 1創建新項目打開 Godot點擊“新建項目”。輸入項目名稱例如PotatoBrosClone。選擇項目路徑。渲染器選擇對于 2D 項目選擇“兼容性”渲染器即可它對硬件要求更低兼容性更好。點擊“創建并編輯”。步驟 2創建文件夾結構在“文件系統”面板中創建以下文件夾讓資源管理井井有條res:// ├── actors/ # 角色相關場景和腳本 │ ├── player/ │ └── enemies/ ├── weapons/ # 武器場景、腳本和數據 ├── ui/ # 用戶界面場景 ├── scenes/ # 管理場景如主游戲、菜單 ├── resources/ # 資源文件如武器定義、升級定義 ├── scripts/ # 通用工具腳本 ├── audio/ # 音效和音樂 └── textures/ # 圖片素材步驟 3創建主游戲場景 (Main.tscn)這是游戲運行的核心容器。右鍵scenes/文件夾選擇“新建場景”。添加一個Node2D節點作為根節點重命名為Main。保存場景為scenes/Main.tscn。在Main節點下添加以下子節點它們將作為不同功能的“層”YSort節點重命名為World用于管理玩家、敵人等游戲實體YSort能根據節點的 Y 坐標自動排序渲染順序實現角色互相遮擋的效果。CanvasLayer節點重命名為UI用于存放所有用戶界面元素確保 UI 始終顯示在最上層。Node2D節點重命名為Projectiles專門管理所有子彈、飛行物。Node2D節點重命名為Items管理地上掉落的資源、經驗球等。將scenes/Main.tscn設為項目的主場景項目設置 - 應用 - 運行 - 主場景。5. 玩家角色實現移動與基礎屬性玩家是游戲的核心。我們來創建可移動、有生命值和經驗的玩家角色。步驟 1創建玩家場景 (Player.tscn)在actors/player/文件夾中新建場景根節點選擇CharacterBody2D命名為Player。為Player節點添加子節點Sprite2D用于顯示玩家形象。可以先放一個簡單的ColorRect矩形或從textures/導入一個圖片。CollisionShape2D賦予碰撞形狀如CircleShape2D半徑根據精靈大小調整。Timer節點重命名為ShootTimer用于控制射擊間隔。步驟 2編寫玩家腳本 (Player.gd)為Player根節點附加新腳本保存為Player.gd。extends CharacterBody2D # 玩家屬性 - 通過導出變量方便在編輯器中調整 export var speed: float 300.0 export var max_health: float 100.0 export var health: float max_health: set(value): health clamp(value, 0, max_health) health_changed.emit(health, max_health) # 發出信號更新UI if health 0: die() # 經驗與等級 var experience: float 0.0 var level: int 1 var exp_to_next_level: float 100.0 # 信號用于與其他系統通信 signal health_changed(current_health, max_health) signal experience_changed(current_exp, exp_to_next) signal player_died func _physics_process(delta): # 1. 獲取輸入 var input_direction Input.get_vector(move_left, move_right, move_up, move_down) # 2. 計算速度 velocity input_direction * speed # 3. 移動并處理碰撞 move_and_slide() # 4. 朝向鼠標用于射擊方向 look_at(get_global_mouse_position()) func take_damage(amount: float): health - amount func gain_experience(amount: float): experience amount experience_changed.emit(experience, exp_to_next_level) # 檢查升級 if experience exp_to_next_level: level_up() func level_up(): experience - exp_to_next_level level 1 exp_to_next_level * 1.5 # 下一級所需經驗增加 experience_changed.emit(experience, exp_to_next_level) # 這里可以觸發升級選擇UI print(Level Up! Now level: , level) func die(): player_died.emit() # 例如顯示游戲結束畫面重置游戲 queue_free()步驟 3配置輸入映射在項目設置 - 輸入映射中添加以下動作Actionmove_left 按鍵 A / 左箭頭move_right按鍵 D / 右箭頭move_up 按鍵 W / 上箭頭move_down 按鍵 S / 下箭頭shoot 鼠標左鍵步驟 4將玩家實例化到主場景打開scenes/Main.tscn。將actors/player/Player.tscn拖拽到World(YSort) 節點下。運行游戲現在你應該可以用 WASD 移動矩形并且矩形會旋轉朝向鼠標。6. 武器系統數據驅動與射擊邏輯武器是游戲玩法的關鍵。我們將設計一個數據驅動的武器系統便于添加新武器。步驟 1創建武器資源WeaponResource.gd)使用 Godot 的Resource類來定義武器屬性。在scripts/文件夾右鍵創建新腳本繼承自Resource。# WeaponResource.gd extends Resource class_name WeaponResource export var weapon_name: String Pistol export var damage: float 10.0 export var attack_speed: float 1.0 # 每秒攻擊次數 export_range(1, 1000) var range: float 500.0 export var projectile_scene: PackedScene # 子彈場景 export var texture: Texture2D # 武器圖標保存后你可以在 Inspector 中創建新的.tres資源文件來定義具體武器。步驟 2創建子彈場景 (BasicProjectile.tscn)在weapons/文件夾新建場景根節點為Area2D用于檢測碰撞命名為BasicProjectile。添加子節點Sprite2D顯示子彈、CollisionShape2D圓形或矩形。附加腳本# BasicProjectile.gd extends Area2D export var speed: float 800.0 export var damage: float 10.0 var direction: Vector2 Vector2.RIGHT func _physics_process(delta): position direction * speed * delta # 簡單邊界檢查超出屏幕后刪除 var viewport_rect get_viewport_rect() if !viewport_rect.has_point(global_position): queue_free() func _on_body_entered(body): if body.is_in_group(enemies): body.take_damage(damage) queue_free() # 擊中后消失連接Area2D的body_entered信號到_on_body_entered函數。步驟 3修改玩家腳本以支持武器射擊在Player.gd中添加武器相關邏輯# Player.gd 新增部分 # ... export var current_weapon: WeaponResource var can_shoot: bool true func _process(delta): if Input.is_action_pressed(shoot) and can_shoot and current_weapon: shoot() can_shoot false get_node(ShootTimer).wait_time 1.0 / current_weapon.attack_speed get_node(ShootTimer).start() func shoot(): if not current_weapon or not current_weapon.projectile_scene: return var projectile_instance current_weapon.projectile_scene.instantiate() # 設置子彈初始位置和方向 projectile_instance.global_position global_position projectile_instance.direction (get_global_mouse_position() - global_position).normalized() # 將子彈添加到主場景的 Projectiles 層 get_tree().root.get_node(Main/Projectiles).add_child(projectile_instance) func _on_shoot_timer_timeout(): can_shoot true記得將ShootTimer的timeout信號連接到_on_shoot_timer_timeout函數。步驟 4創建并配置一把武器在resources/文件夾右鍵選擇“新建資源”搜索并創建WeaponResource。將其命名為pistol.tres。在 Inspector 中設置屬性damage15,attack_speed2,range600。將projectile_scene指向你創建的BasicProjectile.tscn。回到Player場景在 Inspector 中將Current Weapon屬性設置為剛創建的pistol.tres。運行游戲按住鼠標左鍵玩家應該會朝鼠標方向發射子彈。7. 敵人系統波次生成與簡單AI敵人需要自動移動并攻擊玩家。我們將實現一個簡單的波次管理系統。步驟 1創建敵人場景 (Enemy.tscn)在actors/enemies/中新建場景根節點為CharacterBody2D命名Enemy。添加Sprite2D,CollisionShape2D。附加腳本# Enemy.gd extends CharacterBody2D export var speed: float 150.0 export var health: float 30.0 export var damage_to_player: float 10.0 export var experience_value: float 10.0 var player: Node2D func _ready(): # 嘗試獲取玩家引用 player get_tree().get_first_node_in_group(player) add_to_group(enemies) # 添加到“敵人”組便于子彈識別 func _physics_process(delta): if not player: return # 簡單AI朝玩家移動 var direction (player.global_position - global_position).normalized() velocity direction * speed move_and_slide() func take_damage(amount: float): health - amount if health 0: die() func die(): if player and player.has_method(gain_experience): player.gain_experience(experience_value) queue_free()步驟 2創建波次生成器 (Spawner.gd)這是一個控制敵人生成邏輯的腳本可以附加到Main場景的一個Node2D上。# Spawner.gd extends Node2D export var enemy_scenes: Array[PackedScene] [] export var spawn_radius: float 400.0 export var time_between_waves: float 30.0 export var enemies_per_wave_base: int 5 export var wave_increase_factor: float 1.2 var current_wave: int 0 var enemies_alive: int 0 var spawn_timer: Timer var wave_timer: Timer func _ready(): spawn_timer Timer.new() add_child(spawn_timer) spawn_timer.one_shot true wave_timer Timer.new() add_child(wave_timer) wave_timer.wait_time time_between_waves wave_timer.timeout.connect(start_next_wave) wave_timer.start() start_next_wave() func start_next_wave(): current_wave 1 var enemy_count ceil(enemies_per_wave_base * pow(wave_increase_factor, current_wave - 1)) print(Wave %s started. Spawning %s enemies. % [current_wave, enemy_count]) for i in range(enemy_count): spawn_timer.wait_time i * 0.5 # 每隔0.5秒生成一個避免同時出現 spawn_timer.timeout.connect(_spawn_single_enemy.bind()) spawn_timer.start() await spawn_timer.timeout spawn_timer.timeout.disconnect(_spawn_single_enemy) func _spawn_single_enemy(): if enemy_scenes.is_empty(): return var enemy_scene enemy_scenes.pick_random() var enemy_instance enemy_scene.instantiate() # 在玩家周圍隨機位置生成 var player get_tree().get_first_node_in_group(player) if player: var spawn_direction Vector2.RIGHT.rotated(randf_range(0, TAU)) var spawn_distance randf_range(spawn_radius * 0.8, spawn_radius) enemy_instance.global_position player.global_position (spawn_direction * spawn_distance) get_parent().get_node(World).add_child(enemy_instance) # 添加到World層 enemies_alive 1 enemy_instance.tree_exiting.connect(_on_enemy_died) func _on_enemy_died(): enemies_alive - 1 if enemies_alive 0: print(Wave %s cleared! % current_wave) # 所有敵人都被消滅開始下一波倒計時 wave_timer.start()步驟 3集成到主場景打開Main.tscn在根節點下添加一個Node2D重命名為Spawner并將Spawner.gd腳本附加給它。在Spawner節點的 Inspector 中將Enemy Scenes數組添加一個元素并指向你創建的Enemy.tscn。運行游戲。你會看到敵人在玩家周圍生成并朝玩家移動。擊敗敵人后玩家會獲得經驗。8. UI 系統生命值、經驗條與升級選擇UI 是玩家與游戲交互的窗口。我們將創建顯示生命值、經驗的常駐 UI以及一個升級選擇界面。步驟 1創建 HUD 場景 (HUD.tscn)在ui/文件夾新建場景根節點為CanvasLayer命名HUD。添加控件節點TextureRect作為背景可選。Label節點重命名為HealthLabel用于顯示生命值文本。ProgressBar節點重命名為HealthBar用于顯示生命值進度。Label節點重命名為LevelLabel顯示當前等級。ProgressBar節點重命名為ExpBar顯示經驗進度。附加腳本# HUD.gd extends CanvasLayer onready var health_label: Label $HealthLabel onready var health_bar: ProgressBar $HealthBar onready var level_label: Label $LevelLabel onready var exp_bar: ProgressBar $ExpBar func _ready(): # 初始隱藏等待玩家連接 hide() # 假設玩家節點有“player”組 var player get_tree().get_first_node_in_group(player) if player: connect_to_player(player) func connect_to_player(player: Node): player.health_changed.connect(update_health) player.experience_changed.connect(update_experience) # 初始化顯示 update_health(player.health, player.max_health) update_experience(player.experience, player.exp_to_next_level) show() func update_health(current: float, max_health: float): health_label.text HP: %d/%d % [current, max_health] health_bar.max_value max_health health_bar.value current func update_experience(current: float, to_next: float): exp_bar.max_value to_next exp_bar.value current level_label.text Lv. %d % get_tree().get_first_node_in_group(player).level將HUD.tscn實例化為Main.tscn中UI節點的子節點。步驟 2創建升級選擇界面 (UpgradeSelection.tscn)這是一個彈窗當玩家升級時出現提供多個升級選項。在ui/文件夾新建場景根節點為PanelContainer命名UpgradeSelection。添加VBoxContainer內部添加一個Label顯示“選擇升級”和三個Button代表三個升級選項。附加腳本# UpgradeSelection.gd extends PanelContainer signal upgrade_selected(upgrade_id: String) onready var option_buttons: Array[Button] [ $VBoxContainer/Option1, $VBoxContainer/Option2, $VBoxContainer/Option3 ] func display_upgrades(upgrades: Array): hide() # 默認隱藏 # 這里假設upgrades是一個包含升級ID和描述的數組 for i in range(min(upgrades.size(), option_buttons.size())): option_buttons[i].text upgrades[i].description option_buttons[i].pressed.connect(_on_option_selected.bind(upgrades[i].id)) show() func _on_option_selected(upgrade_id: String): upgrade_selected.emit(upgrade_id) hide() # 選擇后隱藏在Main.gd需要為Main節點創建腳本中監聽玩家的升級事件并彈出此界面。9. 游戲循環整合與狀態管理現在我們需要一個“大腦”來協調所有系統游戲開始、結束、暫停、波次狀態等。步驟 1創建游戲狀態管理器 (GameManager.gd)為Main節點創建腳本。# Main.gd (附加給Main場景根節點) extends Node2D onready var player $World/Player onready var hud $UI/HUD onready var upgrade_ui $UI/UpgradeSelection enum GameState { PLAYING, PAUSED, GAME_OVER } var current_state: GameState GameState.PLAYING func _ready(): # 連接信號 player.player_died.connect(_on_player_died) if hud.has_method(connect_to_player): hud.connect_to_player(player) # 初始化升級UI隱藏 upgrade_ui.hide() func _on_player_died(): current_state GameState.GAME_OVER print(Game Over!) # 顯示游戲結束畫面這里可以實例化一個GameOver場景 # get_tree().paused true func _input(event): if event.is_action_pressed(pause): match current_state: GameState.PLAYING: current_state GameState.PAUSED get_tree().paused true # 顯示暫停菜單 GameState.PAUSED: current_state GameState.PLAYING get_tree().paused false # 隱藏暫停菜單步驟 2定義升級系統在resources/中創建UpgradeResource.gd類似武器資源定義升級效果如增加傷害、生命值、速度等。當玩家升級時從預定義的升級池中隨機選取幾個顯示在UpgradeSelection界面玩家選擇后將效果應用到玩家屬性上。10. 資源占用、性能觀察與導出性能觀察Godot 內置了性能監控工具。在編輯器運行時點擊底部“調試器”面板切換到“監視器”選項卡。重點關注FPS確保穩定在60左右。如果過低檢查物理對象數量、粒子效果或復雜腳本邏輯。2D 節點數大量敵人、子彈同時存在時節點數會上升。考慮使用對象池Object Pooling回收子彈和敵人。內存觀察是否有持續增長防止內存泄漏。優化建議對象池為子彈和敵人創建對象池避免頻繁instantiate()和queue_free()造成的性能開銷。精靈批處理確保使用相同紋理的精靈Sprite2D啟用了“紋理重復”或使用MultiMeshInstance2D進行批量渲染。簡化碰撞使用簡單的CircleShape2D或RectangleShape2D避免復雜多邊形碰撞。按需生成敵人只在玩家視野附近生成。項目導出點擊頂部菜單“項目” - “導出”。點擊“添加…”選擇目標平臺如“Windows 桌面”。配置導出選項通常默認即可。點擊“導出項目”選擇輸出路徑和文件名如PotatoBrosClone.exe。導出的可執行文件可以獨立運行分享給他人測試。11. 常見問題與排查方法問題現象可能原因排查方式解決方案運行游戲后黑屏只有背景色主場景未設置或玩家/相機未正確放置檢查“項目設置 - 應用 - 運行 - 主場景”路徑是否正確檢查主場景中是否有Camera2D節點。正確設置主場景確保玩家或某個節點下有Camera2D并設置為“當前”。玩家無法移動輸入映射未正確設置腳本中獲取輸入的動作名寫錯_physics_process未調用move_and_slide檢查項目設置中的輸入映射檢查腳本中Input.get_vector使用的動作名是否與映射一致確認在_physics_process中調用了move_and_slide()。核對并修正輸入動作名確保移動邏輯在_physics_process中。子彈不發射或方向錯誤ShootTimer未啟動或連接子彈實例化后未添加到場景樹子彈方向計算錯誤打印can_shoot變量狀態檢查子彈實例化后其global_position和direction使用print調試方向向量。確保計時器信號正確連接確認將子彈添加到了正確的父節點如Main/Projectiles檢查方向計算邏輯。敵人不生成或生成位置不對Spawner腳本未正確附加或啟用未找到玩家節點生成坐標計算有誤檢查Spawner節點是否在場景中且腳本已附加打印player變量是否為空打印生成的坐標。確保玩家被添加到“player”組檢查生成半徑和方向計算邏輯。升級界面不彈出升級信號未連接UI 場景未實例化或路徑錯誤檢查玩家level_up函數中是否發出了信號檢查Main場景中UpgradeSelection節點的路徑是否正確。正確連接信號使用get_node()或onready確保獲取到正確的 UI 節點引用。游戲運行越來越卡節點數量過多子彈、敵人未及時清理內存泄漏使用調試器的“監視器”查看節點數和內存使用趨勢檢查是否有節點不斷創建但未釋放。實現對象池確保子彈超出屏幕或擊中后、敵人死亡后調用queue_free()。導出后無法運行或缺少資源導出時未包含所有必要文件使用了絕對路徑檢查導出對話框的“資源”選項卡確保所有用到的場景、腳本、圖片、字體都被包含。在導出設置中確保“過濾器”為非排除模式或手動添加遺漏的資源文件夾。12. 最佳實踐與擴展方向最佳實踐模塊化設計保持場景和腳本的單一職責。玩家管移動和戰斗生成器管刷怪UI 管顯示。使用信號通信減少節點間的直接引用使用 Godot 強大的信號系統進行松耦合通信。數據驅動將武器、敵人、升級的數值定義在Resource文件中便于平衡和調整無需修改代碼。版本控制使用 Git 管理項目特別是.gd腳本和.tscn場景文件。忽略.import文件夾和二進制導出文件。定期測試每實現一個功能就運行測試盡早發現問題。擴展方向更多武器與道具利用WeaponResource系統輕松添加霰彈槍、激光、導彈等并設計被動道具如增加生命恢復、投射物速度。敵人種類與行為樹創建不同的敵人資源并實現更復雜的 AI如巡邏、逃跑、遠程攻擊可以使用StateMachine模式或BehaviorTree插件。地圖與障礙物設計可破壞的障礙物、提供加成的神龕、隨機房間等增加游戲層次。音效與音樂為射擊、受傷、升級、敵人死亡等事件添加音效并配置背景音樂。本地化與存檔實現多語言支持并使用Resource或文件來保存玩家的永久升級Meta Progression。發布與優化針對目標平臺如 Web、移動端進行控件適配和性能優化考慮使用 Godot 的TextureAtlas和AudioStreamPool等高級功能。通過這個項目你不僅學會了 Godot 2D 開發的基本流程更重要的是掌握了構建一個完整游戲循環的思維方法。從移動射擊到 Roguelike 成長每一個模塊都可以作為你未來更大項目的基石。建議將代碼和資源整理好嘗試添加你自己的創意把它變成獨一無二的游戲。