「君子务本,本立而道生。」 ------《论语·学而》
这一章的核心任务只有一个:为《酒魂》搭建一个稳固、干净、可扩展的项目骨架。 这就像盖房子打地基、铺管道,虽然看不见摸不着,但决定了整栋房子能盖多高、住起来是否舒适。很多新手项目做到一半推倒重来,都是因为一开始的项目结构混乱,导致后期难以管理。
4.1 创建项目,规划文件夹结构
4.1.1 从图纸到工地:创建《酒魂》项目
首先,让我们打开Godot引擎,把《酒魂》项目的"工地"搭建起来。
- 打开Godot 4.6:双击运行我们在第一章安装好的Godot引擎。
- 新建项目 :在欢迎界面(项目管理器)点击右侧的"新建项目"按钮。
- 填写项目信息 :
- 项目名称 :输入
WineSoul(《酒魂》的英文名,建议统一用这个,避免路径问题)。 - 项目路径 :选择一个你容易找到的目录,例如
D:\GodotProjects\WineSoul。 - 渲染器 :选择默认的
Forward+(兼容性好,功能足够)。 - 版本控制 :选择
Git。如果你已经按照第1章的指引安装了Git,这里会自动识别。勾选后,Godot会为你生成一个.gitignore文件,自动忽略那些不需要备份的临时文件,这是个非常好的习惯。
- 项目名称 :输入
- 创建 :点击"创建并编辑"按钮。

图4-1-1:在Godot中创建"WineSoul"新项目,别忘了选择Git版本控制。
现在,你已经进入了一个空荡荡的Godot编辑器界面。这就是《酒魂》诞生的起点。
4.1.2 项目文件夹规划:对杂乱无章说不
一个清晰的目录结构,是项目长期维护的基础。想象一下,三个月后,你有一个上百个文件的工程,如果所有文件都堆在一个文件夹里,找到你需要的东西将是一场噩梦。
我们遵循业界通用的分类方式,为《酒魂》建立一套规范的文件夹体系。请在Godot编辑器的"文件系统"面板(左侧下方)中,完成以下操作:
- 右键点击
res://根目录,选择"新建 → 文件夹"。 - 依次创建以下文件夹(名称区分大小写,建议全部小写):
| 文件夹名称 | 用途说明 | 类比生活中的 |
|---|---|---|
assets |
存放所有外部导入的原始资源。 | 建材市场买回来的原料。 |
├─ sprites |
2D图片、精灵图、序列帧。 | 油漆、壁纸、瓷砖。 |
├─ tilesets |
地图用的瓦片集。 | 铺地板的木料。 |
├─ fonts |
字体文件(.ttf, .otf)。 | 装饰用的字画。 |
├─ sounds |
音效、音乐文件。 | 背景音乐播放器。 |
scenes |
存放构成游戏的各个场景(.tscn文件)。 | 房子的各个房间(卧室、客厅)。 |
├─ characters |
玩家、敌人、NPC的场景。 | 房间里的人。 |
├─ levels |
游戏关卡/地图场景。 | 整栋房子的户型图。 |
ui |
存放所有用户界面(UI)相关的场景和脚本。 | 房子的电路开关面板、水管阀门。 |
scripts |
存放所有GDScript脚本(.gd文件)。 | 各种功能的控制电路和管道系统。 |
resources |
存放自定义的Godot资源文件(如配方数据、物品数据)。 | 定制的智能家居配置文件。 |
addons |
存放从外部导入的Godot插件。 | 额外的智能家电。 |
关键认知: 把场景、脚本、资源分开放,而不是按功能(比如"酿造系统"一个文件夹),好处是当项目变大时,你和引擎都能更快地定位和加载文件。
动手创建: 在你的WineSoul项目中,按照表格结构,在res://下创建所有这些文件夹。创建完成后,你的"文件系统"面板应该看起来像这样:
res://
├─ assets/
│ ├─ sprites/
│ ├─ tilesets/
│ ├─ fonts/
│ └─ sounds/
├─ scenes/
│ ├─ characters/
│ └─ levels/
├─ ui/
├─ scripts/
├─ resources/
└─ addons/

这就是《酒魂》项目的"地基"。一个干净、分门别类的文件夹,能让你在后续数百个文件中心平气和,而不是在文件堆里内耗。
4.2 《酒魂》的场景树设计
如果说文件夹结构是项目的静态骨架,那么场景树(Scene Tree)就是项目的动态骨架。它决定了游戏运行时,各个部分(如玩家、地图、UI)是如何组织和协作的。
4.2.1 理解"一切都是节点"与场景的层次
Godot的核心哲学之一是"一切皆节点"。一个场景是由一棵节点树组成的,每个节点都是一个独立的功能模块。
Node:所有东西的基础。可以把它想象成一个"看不见的积木块"。Node2D:所有2D东西的基础,有位置、旋转、缩放。Control:所有UI的基础,有锚点、边距等用于界面布局。CharacterBody2D:一个专门的2D物理体,适合做玩家和敌人。
场景(.tscn)是节点的集合 ,它保存了一棵带有特定配置的节点树。场景可以实例化到另一个场景中,形成父子关系。例如,你的"玩家"场景,可以被实例化到"主游戏"场景中作为子节点。
4.2.2 设计《酒魂》的主场景
《酒魂》是一款ARPG游戏,核心体验是"探索中遇敌,拔刀就砍"的无缝动作感。因此,我们绝不能把"探索"和"战斗"拆成两个独立切换的场景。正确的做法是:探索即战斗,按需动态加载地图,UI覆盖交互。
我们的根场景将作为游戏主世界的容器,它负责管理和切换不同的关卡,同时处理所有的界面交互。让我们画出主场景的节点结构图:
text
Main (Node2D) 主场景根节点
├── CurrentLevel (Node2D) 当前活跃关卡容器(动态替换)
│ ├── TileMapLayer 地图瓦片与碰撞(来自第6章)
│ ├── Player (CharacterBody2D) 我们的主角(来自第5章,独立场景实例化)
│ ├── Enemies (Node2D) 本地敌人容器
│ │ ├── Slime (CharacterBody2D)
│ │ └── Archer (CharacterBody2D)
│ └── NPCs (Node2D) 本地NPC容器
│ └── LiBai (CharacterBody2D)
└── UI (CanvasLayer) 全局用户界面层(永远在最上方)
├── MainHUD (Control) 主界面HUD(血条、醉意条等,常驻)
├── PauseMenu (Control) 暂停菜单(初始隐藏)
├── BreweryPanel (Control) 酿造面板(初始隐藏,按F弹出)
└── DialoguePanel (Control) 对话面板(初始隐藏,交互时弹出)

设计意图解析:
Main作为根 :一个简单的Node2D,它是整个游戏世界坐标系的原点。CurrentLevel动态关卡容器:这是ARPG的灵魂。玩家在地图上走动,遇敌直接在这里开打,没有"切入战斗"的过程。当玩家走到地图边缘(如进入酒坊或下一张地图)时,我们只需卸载当前关卡,将新关卡场景实例化到这个节点下,即可实现无缝换图。UI层独立 :使用CanvasLayer节点,UI将永远显示在游戏世界的上方,不受摄像机移动影响。- 酿造与对话是UI,不是场景 :打开酿造界面,不是切换到独立的
brewery.tscn,而是弹出UI节点下的BreweryPanel,同时暂停游戏世界进程。这样玩家关闭面板后立刻恢复动作,丝滑无违和感。
4.2.3 细分场景:为特殊玩法预留空间
根据第三章的设计,第7章我们要做"投壶战斗"。它属于独立规则的小游戏,不适合和主线ARPG战斗混在一起。正确的做法是叠加场景 :
当玩家与NPC对话触发投壶时:
GameManager暂停CurrentLevel(process_mode = PROCESS_MODE_DISABLED)。- 动态实例化
res://scenes/mini_games/pot_throw.tscn,作为Main的子节点叠加在画面上。 - 投壶结束,移除小游戏实例,恢复
CurrentLevel。
这样既不破坏原本的地图状态,又实现了独立玩法的切入。
4.3 全局自动加载:游戏的"中央指挥部"
在游戏中,总有一些数据或功能是全局的,无论在哪个场景、哪个角落都需要被访问。比如:游戏设置、玩家基本状态、全局事件总线、音乐管理器等。
Godot 提供了"自动加载"机制来完美解决这个问题。任何被添加到自动加载列表中的脚本或场景,都会在游戏启动时最先加载,并在整个游戏生命周期中作为单例存在,可以被任何脚本通过其名字直接访问。
4.3.1 配置《酒魂》的"中央指挥部"
对于《酒魂》,我们至少需要两个全局单例:
GameManager(脚本):游戏流程的指挥中心。负责切换游戏状态(探索、战斗、酿造)、触发全局事件(如"游戏暂停"、"游戏结束")。EventBus(脚本) :全局事件总线。负责在各系统间传递消息,实现解耦。例如,"玩家获得物品"这个事件,背包UI需要更新,任务系统也需要检查,但玩家系统不需要知道背包和任务的存在,只需通过EventBus发出信号即可。
动手配置:- 创建全局脚本 :
- 在
scripts文件夹下,右键"新建 → 脚本"。 - 新建一个名为
game_manager.gd的脚本。模板选择Node。 - 再新建一个名为
event_bus.gd的脚本。模板也选择Node。
- 在
- 添加为自动加载 :
- 点击顶部菜单栏的"项目 → 项目设置"。
- 在弹出的窗口中,切换到顶部的"全局 "标签页,自动加载标签。
- 在"路径"一栏,选择你刚刚创建的
scripts/game_manager.gd。 - 节点名称会自动填充为
GameManager(区分大小写,我们保持默认)。点击"添加"。 - 重复此步骤,将
scripts/event_bus.gd也添加进去,节点名为EventBus。 - 点击"关闭"按钮。

图4-3-1:在"项目设置"的"自动加载"页签中添加全局脚本,这样它们就会在游戏启动时最先加载并成为单例。
现在,无论在哪个脚本里,你都可以通过 GameManager.xxx 或 EventBus.xxx 来调用它们的方法和信号了。它们是整个《酒魂》的"中央指挥部"和"广播站"。
4.4 动手实操:创建《酒魂》项目根场景
"纸上得来终觉浅,绝知此事要躬行。"现在,让我们把上面的设计实现,亲手搭建出《酒魂》的根场景骨架。这是本章最重要的动手环节。
4.4.1 创建主场景和基本节点
- 新建主场景 :
- 点击"文件系统"面板上方的"新建场景"按钮(或使用快捷键 Ctrl+N)。
- 选中
Node2D作为根节点,点击"创建"。 - 将这个根节点重命名为
Main(在左侧"场景"面板中双击节点名即可修改)。
- 保存主场景 :
- 按
Ctrl+S保存。 - 将场景名保存为
main.tscn,存放在scenes/文件夹下。这是我们的游戏入口。
- 按
- 搭建场景骨架 :
- 在
Main节点下,创建以下子节点(确保在Main选中时点击"+"号添加):- 一个
Node2D节点,命名CurrentLevel。这是当前关卡的容器。 - 一个
CanvasLayer节点,命名UI。这是全局UI层。
- 一个
- 选中
UI节点,在其下创建一个Control节点,命名MainHUD。
此时,你的场景树应该如下图:
- 在
text
Main (Node2D)
├── CurrentLevel (Node2D)
└── UI (CanvasLayer)
└── MainHUD (Control)
- 设置游戏入口 :
- 点击顶部菜单"项目 → 项目设置"。
- 在"通用 "标签页下,找到左侧"应用 → 运行"。
- 在右侧的"主场景 "设置中,点击文件夹图标,选择我们刚刚创建的
scenes/main.tscn。 - 关闭窗口。这样,每次你按下F5运行游戏,都会从这个场景开始。

图4-4-1:将main.tscn设置为项目的主场景,这是游戏运行的起点。
4.4.2 编写 GameManager 与 EventBus 脚本
在之前创建的 game_manager.gd 和 event_bus.gd 中,我们填入适配的基础代码。
event_bus.gd (全局事件总线)
gdscript
extends Node
# 这是一个全局信号总线,所有系统间的通信都通过这里广播
# 例如:玩家获得物品,背包UI和任务系统都需要知道,但它们不需要互相认识
signal player_drunk_changed(drunk_level: float) # 玩家醉意值变化
signal item_obtained(item_data: Resource) # 玩家获得物品
signal battle_started # 战斗开始
signal battle_ended(is_victory: bool) # 战斗结束
game_manager.gd (游戏管理器)
gdscript
extends Node
# 《酒魂》的游戏流程指挥中心
# 定义游戏可能的状态枚举:探索、战斗、酿酒、对话、暂停
enum GameState { EXPLORE, BATTLE, BREWING, DIALOGUE, PAUSED }
# 当前游戏状态,初始为探索状态
var current_state: GameState = GameState.EXPLORE
var player_drunk_level: float = 0.0 # 全局缓存一份醉意值,方便其他系统查询
func _ready() -> void:
print("[GameManager] 游戏启动,当前状态: ", GameState.keys()[current_state])
# 切换游戏状态的方法
func change_state(new_state: GameState) -> void:
if current_state == new_state:
return # 避免重复切换
print("[GameManager] 状态变更: ", GameState.keys()[current_state], " -> ", GameState.keys()[new_state])
current_state = new_state
match new_state:
GameState.EXPLORE:
get_tree().paused = false # 恢复游戏
GameState.PAUSED:
get_tree().paused = true # 暂停游戏
GameState.BREWING, GameState.DIALOGUE:
# 弹出UI时暂停游戏世界,但UI层(CanvasLayer)需要设置不受暂停影响
get_tree().paused = true
GameState.BATTLE:
# ARPG中战斗无需暂停世界,敌人与玩家同场
pass
关键认知: 我们这里的
GameState枚举和change_state函数,就是后续所有系统状态同步的核心。当你需要判断"玩家在对话中能否打开背包?"时,答案就是:
if GameManager.current_state != GameManager.GameState.DIALOGUE。提前定义好状态,能避免大量的逻辑冲突。
动手练习4.1:让UI层不受暂停影响当使用
get_tree().paused = true暂停游戏来打开酿造或暂停菜单时,UI也会被冻结,导致按钮点不动。
- 在场景树中选中
UI (CanvasLayer)节点。- 在右侧检查器中找到 Process -> Mode 属性。
- 将其从
Inherit修改为When Paused(暂停时仍可处理)。
这样,即使游戏世界暂停了,你的UI面板依然可以点击关闭。
4.5 本章产出与小结:一个稳固的起点
现已经成功完成了《酒魂》项目的奠基工作。不再是一张白纸,而是拥有了一个结构清晰、配置完整的工程骨架。
4.5.1 本章为你产出了什么?
回顾一下,你在Godot中完成了以下实实在在的成果:
- 一个干净的
WineSoul项目,并配置了Git进行版本控制。 - 一套规范、可扩展的文件夹结构,所有文件今后都各得其所。
- 一张专为ARPG设计的场景树蓝图 ,采用
CurrentLevel动态加载架构,并创建了main.tscn作为游戏入口。 - 两个全局自动加载单例 :
GameManager(指挥中心)和EventBus(广播站),并编写了适配ARPG状态的基础代码。 - 一个可以运行的空壳项目,没有报错,随时可以进入第5章的开发。
4.5.2 本章小结
| 我们学了什么 | 为什么它很重要 |
|---|---|
| 项目文件夹规划 | 从源头避免混乱,让项目像图书馆一样井井有条。 |
| 场景树分层设计 | 探索即战斗,动态加载关卡,UI覆盖交互,保证了动作游戏的流畅性。 |
CanvasLayer的用途 |
确保UI永远在游戏世界之上,且可以独立于游戏暂停状态运行。 |
| 自动加载 | 创建全局唯一的"管理器"和"通讯器",实现跨系统的解耦和通信。 |
| 游戏状态枚举 | 用清晰的状态管理游戏流程,避免逻辑混乱的灵魂Bug。 |
"千里之行,始于足下。" ------老子《道德经》
这一章,就是我们《酒魂》千里之行的坚实第一步。一个稳固的基础,比一百个炫酷但摇摇欲坠的功能都重要。你现在拥有的不仅是一个空项目,更是一个可以承载你所有设计蓝图的专业工程。
下一步: 框架已经搭好,是时候让它活起来了。在下一章,我们将深入Player场景,创造出属于《酒魂》的主角,并让她在我们的江南小镇上自由行走。
延伸阅读
- Godot官方文档《场景与节点》 :
https://docs.godotengine.org/zh_CN/4.x/tutorials/scripting/scenes_and_nodes.html。必读。这是理解Godot核心哲学的基础。 - Godot官方文档《单例(自动加载)》 :
https://docs.godotengine.org/zh_CN/4.x/tutorials/scripting/singletons_autoload.html。深入了解自动加载的最佳实践和注意事项。