# 场景设计规范 本文档定义了 Whale Town 项目中场景设计的标准和最佳实践。 ## 🎯 设计原则 ### 核心原则 1. **功能独立** - 每个场景都是独立的功能单元 2. **职责单一** - 一个场景只负责一个主要功能 3. **可复用性** - 场景组件应该能够在其他场景中复用 4. **标准化** - 统一的场景结构和命名规范 5. **性能优先** - 优化场景性能,避免不必要的资源消耗 ### 场景分类 - **主要场景** - 游戏的核心功能场景(主菜单、游戏场景、设置等) - **UI场景** - 纯界面场景(对话框、HUD、菜单等) - **游戏场景** - 包含游戏逻辑的场景(关卡、战斗、探索等) - **工具场景** - 开发和测试用的场景 ## 🏗️ 场景结构 ### 标准目录结构 ``` scenes/ ├── Maps/ # 地图场景 │ ├── main_scene.tscn # 主场景 │ └── game_scene.tscn # 游戏场景 ├── Components/ # 组件预制体 │ ├── ui/ # UI组件 │ │ ├── button.tscn │ │ ├── dialog.tscn │ │ └── menu.tscn │ ├── characters/ # 角色组件 │ │ ├── player.tscn │ │ └── npc.tscn │ ├── effects/ # 特效组件 │ │ ├── particle_effect.tscn │ │ └── animation_effect.tscn │ └── items/ # 物品组件 │ ├── collectible.tscn │ └── interactive.tscn └── auth_scene.tscn # 认证场景 ``` ### 场景命名规范 - **主场景**: `SceneName.tscn` (PascalCase) - **组件预制体**: `ComponentName.tscn` (PascalCase) - **脚本文件**: `SceneName.gd` (PascalCase) - **节点名称**: `NodeName` (PascalCase) 或 `nodeName` (camelCase) ## 📝 场景设计模板 ### 主场景结构模板 ``` SceneName (Control/Node2D) ├── Background (TextureRect/Sprite2D) # 背景 ├── UI (CanvasLayer) # UI层 │ ├── HUD (Control) # 游戏HUD │ ├── Menu (Control) # 菜单界面 │ └── Dialog (Control) # 对话框 ├── Game (Node2D) # 游戏内容层 │ ├── Player (CharacterBody2D) # 玩家 │ ├── NPCs (Node2D) # NPC容器 │ ├── Items (Node2D) # 物品容器 │ └── Effects (Node2D) # 特效容器 ├── Audio (Node) # 音频管理 │ ├── BGM (AudioStreamPlayer) # 背景音乐 │ └── SFX (AudioStreamPlayer2D) # 音效 └── Systems (Node) # 系统组件 ├── CameraController (Node) # 相机控制 ├── InputHandler (Node) # 输入处理 └── StateManager (Node) # 状态管理 ``` ### 场景脚本模板 ```gdscript # SceneName.gd extends Control # 或 Node2D,根据场景类型选择 class_name SceneName # 场景信息 const SCENE_NAME = "SceneName" const SCENE_VERSION = "1.0.0" # 场景状态 enum SceneState { LOADING, READY, ACTIVE, PAUSED, TRANSITIONING } var current_state: SceneState = SceneState.LOADING # 节点引用 @onready var background: TextureRect = $Background @onready var ui_layer: CanvasLayer = $UI @onready var game_layer: Node2D = $Game @onready var audio_manager: Node = $Audio @onready var systems: Node = $Systems # 场景数据 var scene_data: Dictionary = {} var is_initialized: bool = false # 信号定义 signal scene_ready signal scene_state_changed(old_state: SceneState, new_state: SceneState) signal scene_data_updated(key: String, value) func _ready(): """场景初始化""" print("初始化场景: ", SCENE_NAME) # 初始化场景 await initialize_scene() # 设置场景状态 change_state(SceneState.READY) # 发送场景就绪信号 scene_ready.emit() func initialize_scene(): """初始化场景组件""" # 加载场景数据 await load_scene_data() # 初始化UI initialize_ui() # 初始化游戏组件 initialize_game_components() # 初始化音频 initialize_audio() # 初始化系统 initialize_systems() # 连接信号 connect_signals() is_initialized = true func load_scene_data(): """加载场景数据""" # 从配置文件或网络加载场景数据 var data_path = ProjectPaths.get_scene_data_path(SCENE_NAME) if FileAccess.file_exists(data_path): var file = FileAccess.open(data_path, FileAccess.READ) if file: var json_string = file.get_as_text() file.close() var json = JSON.new() if json.parse(json_string) == OK: scene_data = json.data else: print("警告: 场景数据JSON格式错误: ", data_path) # 等待一帧确保所有节点都已初始化 await get_tree().process_frame func initialize_ui(): """初始化UI组件""" if ui_layer: # 初始化UI组件 for child in ui_layer.get_children(): if child.has_method("initialize"): child.initialize() func initialize_game_components(): """初始化游戏组件""" if game_layer: # 初始化游戏组件 for child in game_layer.get_children(): if child.has_method("initialize"): child.initialize() func initialize_audio(): """初始化音频""" if audio_manager: # 设置背景音乐 if scene_data.has("bgm"): play_bgm(scene_data.bgm) func initialize_systems(): """初始化系统组件""" if systems: # 初始化系统组件 for child in systems.get_children(): if child.has_method("initialize"): child.initialize() func connect_signals(): """连接信号""" # 连接场景内部信号 # 连接全局事件 EventSystem.connect_event("game_paused", _on_game_paused) EventSystem.connect_event("game_resumed", _on_game_resumed) func change_state(new_state: SceneState): """改变场景状态""" if current_state == new_state: return var old_state = current_state current_state = new_state print("场景状态变更: %s -> %s" % [SceneState.keys()[old_state], SceneState.keys()[new_state]]) # 处理状态变更 _handle_state_change(old_state, new_state) # 发送状态变更信号 scene_state_changed.emit(old_state, new_state) func _handle_state_change(old_state: SceneState, new_state: SceneState): """处理状态变更""" match new_state: SceneState.LOADING: _on_enter_loading_state() SceneState.READY: _on_enter_ready_state() SceneState.ACTIVE: _on_enter_active_state() SceneState.PAUSED: _on_enter_paused_state() SceneState.TRANSITIONING: _on_enter_transitioning_state() func _on_enter_loading_state(): """进入加载状态""" # 显示加载界面 pass func _on_enter_ready_state(): """进入就绪状态""" # 场景准备完成 pass func _on_enter_active_state(): """进入活跃状态""" # 开始游戏逻辑 pass func _on_enter_paused_state(): """进入暂停状态""" # 暂停游戏逻辑 get_tree().paused = true func _on_enter_transitioning_state(): """进入转换状态""" # 场景转换中 pass func play_bgm(bgm_path: String): """播放背景音乐""" var bgm_player = audio_manager.get_node("BGM") as AudioStreamPlayer if bgm_player and FileAccess.file_exists(bgm_path): var audio_stream = load(bgm_path) bgm_player.stream = audio_stream bgm_player.play() func play_sfx(sfx_path: String, position: Vector2 = Vector2.ZERO): """播放音效""" var sfx_player = audio_manager.get_node("SFX") as AudioStreamPlayer2D if sfx_player and FileAccess.file_exists(sfx_path): var audio_stream = load(sfx_path) sfx_player.stream = audio_stream if position != Vector2.ZERO: sfx_player.global_position = position sfx_player.play() func update_scene_data(key: String, value): """更新场景数据""" scene_data[key] = value scene_data_updated.emit(key, value) func get_scene_data(key: String, default_value = null): """获取场景数据""" return scene_data.get(key, default_value) func cleanup(): """清理场景资源""" print("清理场景: ", SCENE_NAME) # 断开信号连接 EventSystem.disconnect_event("game_paused", _on_game_paused) EventSystem.disconnect_event("game_resumed", _on_game_resumed) # 清理组件 if ui_layer: for child in ui_layer.get_children(): if child.has_method("cleanup"): child.cleanup() if game_layer: for child in game_layer.get_children(): if child.has_method("cleanup"): child.cleanup() if systems: for child in systems.get_children(): if child.has_method("cleanup"): child.cleanup() # 停止音频 if audio_manager: var bgm_player = audio_manager.get_node("BGM") as AudioStreamPlayer if bgm_player: bgm_player.stop() # 事件处理 func _on_game_paused(): """游戏暂停事件""" if current_state == SceneState.ACTIVE: change_state(SceneState.PAUSED) func _on_game_resumed(): """游戏恢复事件""" if current_state == SceneState.PAUSED: change_state(SceneState.ACTIVE) get_tree().paused = false # 输入处理 func _input(event): """处理输入事件""" if current_state != SceneState.ACTIVE: return # 处理场景特定的输入 _handle_scene_input(event) func _handle_scene_input(event): """处理场景特定输入""" # 在子类中重写此方法 pass # 生命周期方法 func _process(delta): """每帧更新""" if current_state == SceneState.ACTIVE: _update_scene(delta) func _update_scene(delta): """更新场景逻辑""" # 在子类中重写此方法 pass func _physics_process(delta): """物理更新""" if current_state == SceneState.ACTIVE: _physics_update_scene(delta) func _physics_update_scene(delta): """物理更新场景逻辑""" # 在子类中重写此方法 pass ``` ## 🎨 UI设计规范 ### UI层级结构 ``` UI (CanvasLayer) ├── Background (Control) # UI背景 ├── MainContent (Control) # 主要内容 │ ├── Header (Control) # 头部区域 │ ├── Body (Control) # 主体区域 │ └── Footer (Control) # 底部区域 ├── Overlay (Control) # 覆盖层 │ ├── Loading (Control) # 加载界面 │ ├── Dialog (Control) # 对话框 │ └── Toast (Control) # 提示消息 └── Debug (Control) # 调试信息 ``` ### UI组件规范 1. **响应式设计** - 使用Anchor和Margin实现自适应布局 2. **主题统一** - 使用统一的主题资源 3. **动画效果** - 添加适当的过渡动画 4. **无障碍支持** - 考虑键盘导航和屏幕阅读器 ### UI脚本模板 ```gdscript # UIComponent.gd extends Control class_name UIComponent signal ui_action(action_name: String, data: Dictionary) @export var auto_initialize: bool = true @export var animation_duration: float = 0.3 var is_visible: bool = false var is_initialized: bool = false func _ready(): if auto_initialize: initialize() func initialize(): """初始化UI组件""" if is_initialized: return # 设置初始状态 modulate.a = 0.0 visible = false # 连接信号 _connect_signals() is_initialized = true func show_ui(animated: bool = true): """显示UI""" if is_visible: return visible = true is_visible = true if animated: var tween = create_tween() tween.tween_property(self, "modulate:a", 1.0, animation_duration) else: modulate.a = 1.0 func hide_ui(animated: bool = true): """隐藏UI""" if not is_visible: return is_visible = false if animated: var tween = create_tween() tween.tween_property(self, "modulate:a", 0.0, animation_duration) await tween.finished visible = false else: modulate.a = 0.0 visible = false func _connect_signals(): """连接信号""" # 在子类中重写 pass ``` ## 🎮 游戏场景规范 ### 游戏场景结构 ``` GameScene (Node2D) ├── Background (ParallaxBackground) # 背景层 ├── Environment (Node2D) # 环境层 │ ├── Terrain (TileMap) # 地形 │ ├── Props (Node2D) # 道具 │ └── Decorations (Node2D) # 装饰 ├── Entities (Node2D) # 实体层 │ ├── Player (CharacterBody2D) # 玩家 │ ├── NPCs (Node2D) # NPC │ ├── Enemies (Node2D) # 敌人 │ └── Items (Node2D) # 物品 ├── Effects (Node2D) # 特效层 │ ├── Particles (Node2D) # 粒子效果 │ └── Animations (Node2D) # 动画效果 └── Camera (Camera2D) # 相机 ``` ### 性能优化 1. **对象池** - 重用频繁创建销毁的对象 2. **视锥剔除** - 只渲染可见区域的对象 3. **LOD系统** - 根据距离调整细节级别 4. **批量处理** - 合并相似的渲染调用 ## 🔧 场景管理 ### 场景切换 ```gdscript # 使用SceneManager进行场景切换 SceneManager.change_scene("game_scene", { "level": 1, "player_data": player_data }) # 带过渡效果的场景切换 SceneManager.change_scene_with_transition("battle_scene", { "enemy_data": enemy_data }, "fade") ``` ### 场景数据传递 ```gdscript # 发送场景数据 var scene_data = { "player_level": 10, "inventory": player_inventory, "quest_progress": quest_data } SceneManager.change_scene("next_scene", scene_data) # 接收场景数据 func _on_scene_data_received(data: Dictionary): if data.has("player_level"): player_level = data.player_level if data.has("inventory"): load_inventory(data.inventory) ``` ## 🧪 场景测试 ### 测试场景创建 ```gdscript # TestScene.gd extends "res://scenes/BaseScene.gd" func _ready(): super._ready() # 设置测试环境 setup_test_environment() # 运行测试用例 run_test_cases() func setup_test_environment(): """设置测试环境""" # 创建测试数据 # 设置测试状态 pass func run_test_cases(): """运行测试用例""" test_scene_initialization() test_ui_interactions() test_game_logic() func test_scene_initialization(): """测试场景初始化""" assert(is_initialized, "场景应该已初始化") assert(current_state == SceneState.READY, "场景状态应该为READY") func test_ui_interactions(): """测试UI交互""" # 模拟用户输入 # 验证UI响应 pass func test_game_logic(): """测试游戏逻辑""" # 测试游戏规则 # 验证状态变化 pass ``` ### 性能测试 ```gdscript # 性能监控 func _process(delta): super._process(delta) # 监控帧率 var fps = Engine.get_frames_per_second() if fps < 30: print("警告: 帧率过低: ", fps) # 监控内存使用 var memory_usage = OS.get_static_memory_usage_by_type() if memory_usage > 100 * 1024 * 1024: # 100MB print("警告: 内存使用过高: ", memory_usage / 1024 / 1024, "MB") ``` ## 📚 最佳实践 ### 代码组织 1. **单一职责** - 每个场景只负责一个主要功能 2. **模块化** - 将复杂功能拆分为独立组件 3. **可测试** - 设计易于测试的场景结构 4. **文档化** - 为场景添加详细的文档说明 ### 性能优化 1. **延迟加载** - 按需加载资源和组件 2. **对象复用** - 使用对象池管理频繁创建的对象 3. **批量操作** - 合并相似的操作减少开销 4. **内存管理** - 及时释放不需要的资源 ### 用户体验 1. **响应式设计** - 支持不同分辨率和设备 2. **流畅动画** - 添加适当的过渡效果 3. **错误处理** - 优雅处理异常情况 4. **加载提示** - 为长时间操作提供进度反馈 ## 🔍 常见问题 ### Q: 如何处理场景间的数据传递? A: 使用SceneManager的场景切换方法传递数据,或通过全局单例存储共享数据。 ### Q: 场景性能如何优化? A: 使用对象池、视锥剔除、LOD系统,避免在_process中执行重复计算。 ### Q: 如何调试场景问题? A: 使用Godot的远程调试器,添加性能监控,编写场景测试用例。 ### Q: 场景切换如何添加过渡效果? A: 使用SceneManager的过渡系统,或自定义Tween动画实现过渡效果。 --- **记住:良好的场景设计是游戏体验的基础!**