6. 2.5D文件分类与结构设计
2.5D文件分类与结构设计
为什么要规划文件结构?
想象一下,你搬进了一个新家,把所有东西都随手扔在客厅地板上——衣服、书、厨具、工具全混在一起。刚开始可能还好,但过了一段时间,你想找一双袜子,却要翻遍整个客厅。
游戏开发也是一样。如果你把所有文件——场景、脚本、图片、音频——都堆在项目根目录,刚开始可能没问题,但随着项目越来越大,你会发现:
- 找一个文件要花很长时间
- 不知道哪个文件是干什么的
- 删错文件导致游戏崩溃
- 团队协作时互相干扰
好的文件结构就像整理好的房间:每样东西都有固定的位置,你知道去哪里找什么,也知道新东西应该放在哪里。
推荐的2.5D项目文件结构
下面是一个经过实践验证的2.5D游戏项目文件结构:
res://
├── assets/ # 所有素材资源
│ ├── models/ # 3D模型文件
│ │ ├── characters/ # 角色模型
│ │ ├── environment/ # 环境模型(树、石头、建筑等)
│ │ └── props/ # 道具模型
│ ├── textures/ # 贴图文件
│ │ ├── characters/ # 角色贴图
│ │ ├── environment/ # 环境贴图
│ │ ├── ui/ # UI贴图
│ │ └── effects/ # 特效贴图
│ ├── audio/ # 音频文件
│ │ ├── bgm/ # 背景音乐
│ │ ├── sfx/ # 音效
│ │ └── voice/ # 语音
│ ├── fonts/ # 字体文件
│ └── animations/ # 动画文件(如果单独存放)
│
├── scenes/ # 场景文件
│ ├── levels/ # 关卡场景
│ │ ├── level_01.tscn
│ │ ├── level_02.tscn
│ │ └── ...
│ ├── characters/ # 角色场景
│ │ ├── player.tscn
│ │ ├── enemy_goblin.tscn
│ │ └── npc_merchant.tscn
│ ├── ui/ # UI界面场景
│ │ ├── main_menu.tscn
│ │ ├── hud.tscn
│ │ ├── pause_menu.tscn
│ │ └── game_over.tscn
│ ├── effects/ # 特效场景
│ │ ├── explosion.tscn
│ │ └── hit_effect.tscn
│ └── world/ # 世界/环境场景
│ ├── main_world.tscn
│ └── dungeon.tscn
│
├── scripts/ # 脚本文件
│ ├── player/ # 玩家相关脚本
│ │ ├── player.gd
│ │ ├── player_movement.gd
│ │ └── player_combat.gd
│ ├── enemies/ # 敌人相关脚本
│ │ ├── enemy_base.gd
│ │ ├── enemy_goblin.gd
│ │ └── enemy_boss.gd
│ ├── managers/ # 管理器脚本
│ │ ├── game_manager.gd
│ │ ├── audio_manager.gd
│ │ └── save_manager.gd
│ ├── ui/ # UI脚本
│ │ ├── main_menu.gd
│ │ └── hud.gd
│ └── utils/ # 工具函数
│ ├── math_utils.gd
│ └── string_utils.gd
│
├── shaders/ # 着色器文件
│ ├── outline.gdshader
│ ├── dissolve.gdshader
│ └── water.gdshader
│
├── materials/ # 材质文件
│ ├── player_material.tres
│ └── ground_material.tres
│
└── data/ # 游戏数据文件
├── items.json # 物品数据
├── enemies.json # 敌人数据
└── levels.json # 关卡数据为什么这样组织?
这个结构遵循了"按类型分类"的原则:
assets/放所有外部资源(图片、音频、模型)scenes/放所有场景文件scripts/放所有代码shaders/放所有着色器
在每个大类下面,再按功能细分(角色、关卡、UI等)。这样你既能快速找到某类文件,也能快速找到某个功能的所有相关文件。
命名规范
好的命名就像给文件贴上清晰的标签,让你一眼就知道这个文件是什么。
基本规则
使用小写字母和下划线(snake_case):
- ✅
player_movement.gd - ✅
enemy_goblin.tscn - ✅
main_menu_bg.png - ❌
PlayerMovement.gd(驼峰命名,不推荐用于文件名) - ❌
Enemy Goblin.tscn(有空格,会导致问题) - ❌
主菜单背景.png(中文,可能导致问题)
为什么不用中文?
虽然Godot支持中文文件名,但在某些操作系统、版本控制工具或部署环境中,中文文件名可能导致乱码或错误。为了安全起见,文件名统一使用英文。
场景文件命名
场景文件使用 .tscn 扩展名,命名要体现场景的内容:
# 关卡场景
level_01.tscn # 第1关
level_02_boss.tscn # 第2关(Boss关)
# 角色场景
player.tscn # 玩家
enemy_goblin.tscn # 哥布林敌人
npc_blacksmith.tscn # 铁匠NPC
# UI场景
ui_main_menu.tscn # 主菜单
ui_hud.tscn # 游戏HUD
ui_inventory.tscn # 背包界面脚本文件命名
脚本文件通常和对应的场景同名:
player.tscn → player.gd
enemy_goblin.tscn → enemy_goblin.gd
ui_hud.tscn → ui_hud.gd资源文件命名
资源文件要体现内容和用途:
# 贴图
player_idle.png # 玩家待机贴图
player_run.png # 玩家跑步贴图
goblin_attack.png # 哥布林攻击贴图
bg_forest.jpg # 森林背景
# 音频
bgm_main_theme.ogg # 主题背景音乐
sfx_jump.wav # 跳跃音效
sfx_sword_hit.wav # 剑击音效类名和节点名命名
在Godot中,类名(脚本中的class_name)和节点名使用大驼峰命名(PascalCase):
// 类名使用大驼峰
public partial class PlayerMovement : CharacterBody3D
{
// 变量名使用小驼峰
private float moveSpeed = 5.0f;
private bool isGrounded = false;
// 常量使用全大写+下划线
private const float GRAVITY = -9.8f;
private const float JUMP_FORCE = 10.0f;
}# 类名使用大驼峰
class_name PlayerMovement
extends CharacterBody3D
# 变量名使用小写+下划线
var move_speed: float = 5.0
var is_grounded: bool = false
# 常量使用全大写+下划线
const GRAVITY: float = -9.8
const JUMP_FORCE: float = 10.0场景组织:预制体的概念
在Godot中,"预制体"(Prefab)这个概念来自Unity,在Godot里对应的是场景文件(.tscn)。
什么是预制体?
想象你在做一个游戏,里面有100个哥布林敌人。如果每个哥布林都是独立的,你要修改哥布林的外观,就需要修改100次。
预制体的思路是:先做一个"模板",然后复制出很多份。修改模板,所有复制品都会跟着改变。
在Godot中,你创建一个 enemy_goblin.tscn 场景,这就是你的"模板"。在关卡中,你可以把这个场景实例化(Instantiate)很多次,每次都会创建一个独立的哥布林,但它们都基于同一个模板。
如何创建和使用预制体
创建预制体:
- 创建一个新场景(Ctrl+N)
- 添加根节点(比如
CharacterBody3D) - 添加子节点(模型、碰撞体、脚本等)
- 保存场景(Ctrl+S),命名为
enemy_goblin.tscn
在关卡中使用预制体:
方法一:直接拖拽
- 在文件系统面板中,把
enemy_goblin.tscn拖到3D视口中
方法二:通过代码实例化
using Godot;
public partial class Level : Node3D
{
// 预加载预制体场景
[Export]
private PackedScene _goblinScene;
public override void _Ready()
{
// 在代码中实例化预制体
SpawnGoblin(new Vector3(5, 0, 0));
SpawnGoblin(new Vector3(10, 0, 0));
SpawnGoblin(new Vector3(15, 0, 0));
}
private void SpawnGoblin(Vector3 position)
{
// 创建预制体的实例
var goblin = _goblinScene.Instantiate<Node3D>();
// 设置位置
goblin.Position = position;
// 添加到场景树
AddChild(goblin);
}
}extends Node3D
# 预加载预制体场景
@export var goblin_scene: PackedScene
func _ready() -> void:
# 在代码中实例化预制体
spawn_goblin(Vector3(5, 0, 0))
spawn_goblin(Vector3(10, 0, 0))
spawn_goblin(Vector3(15, 0, 0))
func spawn_goblin(position: Vector3) -> void:
# 创建预制体的实例
var goblin = goblin_scene.instantiate()
# 设置位置
goblin.position = position
# 添加到场景树
add_child(goblin)预加载 vs 动态加载
上面的代码使用了 @export(GDScript)或 [Export](C#)来在编辑器中指定场景文件。
另一种方式是在代码中直接写路径:
// 动态加载(运行时加载)
var goblinScene = GD.Load<PackedScene>("res://scenes/characters/enemy_goblin.tscn");# 预加载(编译时加载,更快)
const GOBLIN_SCENE = preload("res://scenes/characters/enemy_goblin.tscn")
# 动态加载(运行时加载,更灵活)
var goblin_scene = load("res://scenes/characters/enemy_goblin.tscn")preload 在游戏启动时就加载,速度快但占内存;load 在需要时才加载,节省内存但可能有短暂卡顿。
:::
资源引用管理
在Godot中,所有资源都通过 res:// 路径引用,这是项目根目录的别名。
正确的资源引用方式
using Godot;
public partial class Player : CharacterBody3D
{
public override void _Ready()
{
// 正确:使用 res:// 路径
var texture = GD.Load<Texture2D>("res://assets/textures/characters/player_idle.png");
// 正确:使用 @export 在编辑器中指定
// [Export] private Texture2D _playerTexture;
}
}extends CharacterBody3D
func _ready() -> void:
# 正确:使用 res:// 路径
var texture = load("res://assets/textures/characters/player_idle.png")
# 更好:使用 preload(编译时检查路径是否正确)
var texture2 = preload("res://assets/textures/characters/player_idle.png")避免硬编码路径
如果你在很多地方都用到同一个资源路径,最好把它定义为常量:
using Godot;
public partial class GameConstants : Node
{
// 把常用路径定义为常量
public const string PLAYER_SCENE = "res://scenes/characters/player.tscn";
public const string GOBLIN_SCENE = "res://scenes/characters/enemy_goblin.tscn";
public const string MAIN_MENU_SCENE = "res://scenes/ui/main_menu.tscn";
// 音频路径
public const string BGM_MAIN = "res://assets/audio/bgm/main_theme.ogg";
public const string SFX_JUMP = "res://assets/audio/sfx/jump.wav";
}# game_constants.gd
class_name GameConstants
# 把常用路径定义为常量
const PLAYER_SCENE = "res://scenes/characters/player.tscn"
const GOBLIN_SCENE = "res://scenes/characters/enemy_goblin.tscn"
const MAIN_MENU_SCENE = "res://scenes/ui/main_menu.tscn"
# 音频路径
const BGM_MAIN = "res://assets/audio/bgm/main_theme.ogg"
const SFX_JUMP = "res://assets/audio/sfx/jump.wav"避免的坏习惯
坏习惯1:所有文件堆在根目录
res://
├── player.tscn
├── player.gd
├── enemy.tscn
├── enemy.gd
├── level1.tscn
├── background.png
├── player_idle.png
├── jump.wav
├── bgm.ogg
└── ...(几十个文件全在这里)这样做的问题:
- 文件越来越多,越来越难找
- 不知道哪些文件是相关的
- 容易误删文件
坏习惯2:命名不规范
res://
├── Scene1.tscn # 什么场景?
├── NewScript.gd # 什么脚本?
├── 图片1.png # 什么图片?
├── test.tscn # 测试用的?还是正式的?
└── final_v2_ok.tscn # 这是最终版吗?坏习惯3:不分类型混放
res://
├── player/
│ ├── player.tscn
│ ├── player.gd
│ ├── player_idle.png # 贴图放在角色文件夹里
│ └── jump.wav # 音效也放在角色文件夹里虽然这样"按角色分类"看起来合理,但当你需要找所有贴图或所有音效时,就需要翻遍每个角色文件夹。
推荐做法:按类型分类(所有贴图放一起,所有音效放一起),在类型下面再按功能分类。
坏习惯4:场景嵌套太深
World
└── Level
└── Area
└── Room
└── Platform
└── Player ← 太深了!场景嵌套太深会导致:
- 代码中获取节点的路径很长(
$World/Level/Area/Room/Platform/Player) - 性能下降
- 难以维护
推荐做法:保持场景层级在3-4层以内,使用信号(Signal)和自动加载(Autoload)来通信,而不是通过节点路径。
自动加载(Autoload)
自动加载是Godot中一个非常有用的功能,就像游戏的"全局管理员",在整个游戏运行期间都存在,任何场景都可以访问它。
适合放在自动加载中的内容:
- 游戏管理器(GameManager):管理游戏状态
- 音频管理器(AudioManager):播放音乐和音效
- 存档管理器(SaveManager):保存和加载游戏
- 事件总线(EventBus):场景间通信
设置自动加载:
- 打开"项目设置"(Project Settings)
- 点击"自动加载"(Autoload)标签
- 点击"添加",选择脚本文件
- 给它起一个名字(比如
GameManager)
设置后,你可以在任何脚本中直接访问:
using Godot;
public partial class Player : CharacterBody3D
{
public override void _Ready()
{
// 访问自动加载的单例
// 注意:C# 中需要通过 GetNode 或直接引用
var gameManager = GetNode<GameManager>("/root/GameManager");
gameManager.AddScore(100);
}
}extends CharacterBody3D
func _ready() -> void:
# 直接访问自动加载的单例(就像全局变量)
GameManager.add_score(100)
AudioManager.play_sfx("jump")项目结构模板
为了帮助你快速开始,这里提供一个可以直接使用的项目结构创建脚本:
小结
在这一章中,我们学习了:
- ✅ 为什么要规划文件结构(就像整理房间)
- ✅ 推荐的2.5D项目文件结构(按类型+功能分类)
- ✅ 命名规范(小写+下划线,有意义的名字)
- ✅ 预制体的概念(场景文件作为模板)
- ✅ 资源引用管理(使用
res://路径) - ✅ 避免的坏习惯(不要把文件堆在根目录)
- ✅ 自动加载的使用(全局管理器)
好的文件结构是游戏开发的基础,它能让你在整个开发过程中保持清晰的思路,避免很多不必要的麻烦。接下来,我们将学习2.5D游戏中最重要的组件之一:摄像机系统!
