Files
whale-town-front/docs/04-高级开发/场景设计规范.md
moyin 0edd1c740b docs: 完善项目文档和README,修复字符显示问题
- 修复README.md中的emoji字符显示问题
- 移除文档质量评级系统
- 添加贡献者致谢部分,创建详细的CONTRIBUTORS.md
- 创建核心系统文件EventNames.gd和ProjectPaths.gd
- 更新项目配置文件project.godot,添加输入映射
- 完善各模块文档,修正路径引用问题
- 创建文档更新日志CHANGELOG.md
- 优化文档结构和导航系统
2025-12-31 18:58:38 +08:00

16 KiB
Raw Blame History

场景设计规范

本文档定义了 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)               # 状态管理

场景脚本模板

# 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脚本模板

# 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. 批量处理 - 合并相似的渲染调用

🔧 场景管理

场景切换

# 使用SceneManager进行场景切换
SceneManager.change_scene("game_scene", {
	"level": 1,
	"player_data": player_data
})

# 带过渡效果的场景切换
SceneManager.change_scene_with_transition("battle_scene", {
	"enemy_data": enemy_data
}, "fade")

场景数据传递

# 发送场景数据
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)

🧪 场景测试

测试场景创建

# 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

性能测试

# 性能监控
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动画实现过渡效果。


记住:良好的场景设计是游戏体验的基础!