第四章:创建《酒魂》项目与场景结构

「君子务本,本立而道生。」 ------《论语·学而》

这一章的核心任务只有一个:为《酒魂》搭建一个稳固、干净、可扩展的项目骨架。 这就像盖房子打地基、铺管道,虽然看不见摸不着,但决定了整栋房子能盖多高、住起来是否舒适。很多新手项目做到一半推倒重来,都是因为一开始的项目结构混乱,导致后期难以管理。


4.1 创建项目,规划文件夹结构

4.1.1 从图纸到工地:创建《酒魂》项目

首先,让我们打开Godot引擎,把《酒魂》项目的"工地"搭建起来。

  1. 打开Godot 4.6:双击运行我们在第一章安装好的Godot引擎。
  2. 新建项目 :在欢迎界面(项目管理器)点击右侧的"新建项目"按钮。
  3. 填写项目信息
    • 项目名称 :输入 WineSoul(《酒魂》的英文名,建议统一用这个,避免路径问题)。
    • 项目路径 :选择一个你容易找到的目录,例如 D:\GodotProjects\WineSoul
    • 渲染器 :选择默认的 Forward+(兼容性好,功能足够)。
    • 版本控制 :选择 Git。如果你已经按照第1章的指引安装了Git,这里会自动识别。勾选后,Godot会为你生成一个 .gitignore 文件,自动忽略那些不需要备份的临时文件,这是个非常好的习惯。
  4. 创建 :点击"创建并编辑"按钮。

图4-1-1:在Godot中创建"WineSoul"新项目,别忘了选择Git版本控制。

现在,你已经进入了一个空荡荡的Godot编辑器界面。这就是《酒魂》诞生的起点。

4.1.2 项目文件夹规划:对杂乱无章说不

一个清晰的目录结构,是项目长期维护的基础。想象一下,三个月后,你有一个上百个文件的工程,如果所有文件都堆在一个文件夹里,找到你需要的东西将是一场噩梦。

我们遵循业界通用的分类方式,为《酒魂》建立一套规范的文件夹体系。请在Godot编辑器的"文件系统"面板(左侧下方)中,完成以下操作:

  1. 右键点击 res:// 根目录,选择"新建 → 文件夹"。
  2. 依次创建以下文件夹(名称区分大小写,建议全部小写):
文件夹名称 用途说明 类比生活中的
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对话触发投壶时:

  1. GameManager 暂停 CurrentLevelprocess_mode = PROCESS_MODE_DISABLED)。
  2. 动态实例化 res://scenes/mini_games/pot_throw.tscn,作为 Main 的子节点叠加在画面上。
  3. 投壶结束,移除小游戏实例,恢复 CurrentLevel
    这样既不破坏原本的地图状态,又实现了独立玩法的切入。

4.3 全局自动加载:游戏的"中央指挥部"

在游戏中,总有一些数据或功能是全局的,无论在哪个场景、哪个角落都需要被访问。比如:游戏设置、玩家基本状态、全局事件总线、音乐管理器等。

Godot 提供了"自动加载"机制来完美解决这个问题。任何被添加到自动加载列表中的脚本或场景,都会在游戏启动时最先加载,并在整个游戏生命周期中作为单例存在,可以被任何脚本通过其名字直接访问。

4.3.1 配置《酒魂》的"中央指挥部"

对于《酒魂》,我们至少需要两个全局单例:

  1. GameManager (脚本):游戏流程的指挥中心。负责切换游戏状态(探索、战斗、酿造)、触发全局事件(如"游戏暂停"、"游戏结束")。
  2. EventBus (脚本) :全局事件总线。负责在各系统间传递消息,实现解耦。例如,"玩家获得物品"这个事件,背包UI需要更新,任务系统也需要检查,但玩家系统不需要知道背包和任务的存在,只需通过 EventBus 发出信号即可。
    动手配置:
  3. 创建全局脚本
    • scripts 文件夹下,右键"新建 → 脚本"。
    • 新建一个名为 game_manager.gd 的脚本。模板选择 Node
    • 再新建一个名为 event_bus.gd 的脚本。模板也选择 Node
  4. 添加为自动加载
    • 点击顶部菜单栏的"项目 → 项目设置"。
    • 在弹出的窗口中,切换到顶部的"全局 "标签页,自动加载标签。
    • 在"路径"一栏,选择你刚刚创建的 scripts/game_manager.gd
    • 节点名称会自动填充为 GameManager(区分大小写,我们保持默认)。点击"添加"。
    • 重复此步骤,将 scripts/event_bus.gd 也添加进去,节点名为 EventBus
    • 点击"关闭"按钮。

图4-3-1:在"项目设置"的"自动加载"页签中添加全局脚本,这样它们就会在游戏启动时最先加载并成为单例。

现在,无论在哪个脚本里,你都可以通过 GameManager.xxxEventBus.xxx 来调用它们的方法和信号了。它们是整个《酒魂》的"中央指挥部"和"广播站"。

4.4 动手实操:创建《酒魂》项目根场景

"纸上得来终觉浅,绝知此事要躬行。"现在,让我们把上面的设计实现,亲手搭建出《酒魂》的根场景骨架。这是本章最重要的动手环节。

4.4.1 创建主场景和基本节点
  1. 新建主场景
    • 点击"文件系统"面板上方的"新建场景"按钮(或使用快捷键 Ctrl+N)。
    • 选中 Node2D 作为根节点,点击"创建"。
    • 将这个根节点重命名为 Main(在左侧"场景"面板中双击节点名即可修改)。
  2. 保存主场景
    • Ctrl+S 保存。
    • 将场景名保存为 main.tscn,存放在 scenes/ 文件夹下。这是我们的游戏入口。
  3. 搭建场景骨架
    • Main 节点下,创建以下子节点(确保在Main选中时点击"+"号添加):
      • 一个 Node2D 节点,命名 CurrentLevel。这是当前关卡的容器。
      • 一个 CanvasLayer 节点,命名 UI。这是全局UI层。
    • 选中 UI 节点,在其下创建一个 Control 节点,命名 MainHUD
      此时,你的场景树应该如下图:
text 复制代码
Main (Node2D)
├── CurrentLevel (Node2D)
└── UI (CanvasLayer)
    └── MainHUD (Control)
  1. 设置游戏入口
    • 点击顶部菜单"项目 → 项目设置"。
    • 在"通用 "标签页下,找到左侧"应用 → 运行"。
    • 在右侧的"主场景 "设置中,点击文件夹图标,选择我们刚刚创建的 scenes/main.tscn
    • 关闭窗口。这样,每次你按下F5运行游戏,都会从这个场景开始。

图4-4-1:将main.tscn设置为项目的主场景,这是游戏运行的起点。

4.4.2 编写 GameManager 与 EventBus 脚本

在之前创建的 game_manager.gdevent_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也会被冻结,导致按钮点不动。

  1. 在场景树中选中 UI (CanvasLayer) 节点。
  2. 在右侧检查器中找到 Process -> Mode 属性。
  3. 将其从 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。深入了解自动加载的最佳实践和注意事项。
相关推荐
mxwin6 小时前
Unity Shader URP 使用模板测试 · 深度测试实现秘境空间效果
unity·游戏引擎·shader
hhcgchpspk6 小时前
easyx按键游戏
c++·stm32·单片机·游戏·easyx
魔法阵维护师6 小时前
从零开发游戏需要学习的c#模块,第二十一章(精灵动画 —— 让角色走起来)
学习·游戏·c#
好想写代码7 小时前
Java:猜数字游戏
游戏
2501_940041747 小时前
挖掘前端交互潜力的五款创意游戏原型
前端·游戏
Kurisu5757 小时前
博德之门3 2026最新免费下载 一键转存 永久更新 (看到速转存 资源随时走丢)
游戏·游戏程序·动画·游戏美术·友善·爱国
^—app5668668 小时前
多款游戏vivo小游戏Oppo小游戏开发定制游戏流量主广告联盟成品搭建部署
游戏
richard_yuu20 小时前
鸿蒙治愈游戏模块实战|四大轻量解压游戏、ArkTS动画交互与低功耗落地
游戏·交互·harmonyos
魔法阵维护师20 小时前
从零开发游戏需要学习的c#模块,第十四章(保存和加载)
学习·游戏·c#