Files
whale-town-front/docs/scene_design.md
moyin 0b6b1c2040 docs:完善前端项目文档体系
- 重构README文档,参考后端结构优化内容组织
- 添加模块开发指南,详细说明模块化开发流程
- 创建场景设计规范,规范场景架构和最佳实践
- 建立贡献者名单,记录团队成员和贡献统计
- 完善技术栈介绍和功能特性说明
- 优化文档结构和导航,提升开发者体验
2025-12-24 20:50:31 +08:00

617 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 场景设计规范
本文档定义了 Whale Town 项目中场景设计的标准和最佳实践。
## 🎯 设计原则
### 核心原则
1. **功能独立** - 每个场景都是独立的功能单元
2. **职责单一** - 一个场景只负责一个主要功能
3. **可复用性** - 场景组件应该能够在其他场景中复用
4. **标准化** - 统一的场景结构和命名规范
5. **性能优先** - 优化场景性能,避免不必要的资源消耗
### 场景分类
- **主要场景** - 游戏的核心功能场景(主菜单、游戏场景、设置等)
- **UI场景** - 纯界面场景对话框、HUD、菜单等
- **游戏场景** - 包含游戏逻辑的场景(关卡、战斗、探索等)
- **工具场景** - 开发和测试用的场景
## 🏗️ 场景结构
### 标准目录结构
```
scenes/
├── main_scene.tscn # 主场景
├── auth_scene.tscn # 认证场景
├── game_scene.tscn # 游戏场景
├── settings_scene.tscn # 设置场景
└── prefabs/ # 预制体组件
├── 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
```
### 场景命名规范
- **主场景**: `scene_name.tscn` (snake_case)
- **预制体**: `component_name.tscn` (snake_case)
- **脚本文件**: `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 = "res://data/scenes/%s.json" % SCENE_NAME.to_lower()
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动画实现过渡效果。
---
**记住:良好的场景设计是游戏体验的基础!**