forked from datawhale/whale-town-front
Merge pull request 'fix/verification-code-button-state' (#4) from fix/verification-code-button-state into main
Reviewed-on: datawhale/whale-town-front#4
This commit is contained in:
430
README.md
430
README.md
@@ -1,34 +1,62 @@
|
|||||||
# 🐋 whaleTown
|
# 🐋 Whale Town - 像素游戏前端客户端
|
||||||
|
|
||||||
一个使用 Godot 4.5 引擎开发的现代化像素游戏项目,集成了完整的用户认证系统和API接口。
|
> 一个基于 Godot 4.5 引擎开发的现代化 2D 像素风游戏前端,采用模块化架构设计,集成完整的用户认证系统、实时通信和游戏核心功能。
|
||||||
|
|
||||||
## 🎮 项目信息
|
[](https://godotengine.org/)
|
||||||
|
[](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/index.html)
|
||||||
|
[](./LICENSE)
|
||||||
|
[](https://godotengine.org/download)
|
||||||
|
|
||||||
- **引擎版本**: Godot 4.5.1
|
## 🎯 项目简介
|
||||||
- **渲染器**: Forward Plus
|
|
||||||
- **项目类型**: 2D 像素游戏
|
Whale Town 是一个功能完整的像素游戏前端客户端,采用模块化架构设计:
|
||||||
- **架构模式**: 模块化 + 事件驱动
|
|
||||||
- **后端集成**: RESTful API + 用户认证
|
- 🔐 **用户认证系统** - 完整的登录、注册、密码管理、邮箱验证界面
|
||||||
|
- 🎮 **游戏核心功能** - 角色系统、战斗系统、对话系统、背包系统
|
||||||
|
- 🌐 **实时通信** - WebSocket集成,支持实时多人交互
|
||||||
|
- 🎨 **现代化UI** - 响应式界面设计,支持多分辨率适配
|
||||||
|
- 🧪 **完整测试体系** - UI测试、API测试、性能测试全覆盖
|
||||||
|
- 📱 **跨平台支持** - Windows、Linux、macOS、移动端
|
||||||
|
- 🔧 **模块化架构** - 高度解耦的组件系统,易于扩展和维护
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🚀 快速开始
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
### 📋 环境要求
|
||||||
|
|
||||||
|
- **Godot Engine** >= 4.5.0 (推荐 4.5.1)
|
||||||
|
- **Python** >= 3.7.0 (用于API测试,可选)
|
||||||
|
- **Git** >= 2.0.0
|
||||||
|
|
||||||
|
### 🛠️ 安装与运行
|
||||||
|
=======
|
||||||
### 环境要求
|
### 环境要求
|
||||||
- [Godot Engine 4.5+](https://godotengine.org/download)
|
- [Godot Engine 4.5+](https://godotengine.org/download)
|
||||||
- Python 3.7+ (用于API测试和Web服务器)
|
- Python 3.7+ (用于API测试和Web服务器)
|
||||||
|
>>>>>>> whale-town-front/main
|
||||||
|
|
||||||
### 运行项目
|
|
||||||
```bash
|
```bash
|
||||||
# 1. 克隆项目
|
# 1. 克隆项目
|
||||||
git clone <repository-url>
|
git clone <repository-url>
|
||||||
cd whale-town
|
cd whale-town
|
||||||
|
|
||||||
# 2. 使用Godot编辑器打开项目
|
# 2. 使用Godot编辑器打开项目
|
||||||
# 3. 按F5运行或点击"运行"按钮
|
# 双击 project.godot 文件或在Godot编辑器中导入项目
|
||||||
|
|
||||||
# 4. 测试API接口(可选)
|
# 3. 运行项目
|
||||||
python tests/api/simple_api_test.py
|
# 按F5或点击"运行"按钮启动游戏
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
🎉 **游戏启动成功!** 进入认证界面开始体验
|
||||||
|
|
||||||
|
### 🧪 快速测试
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# API接口测试
|
||||||
|
=======
|
||||||
### Web版本部署
|
### Web版本部署
|
||||||
```bash
|
```bash
|
||||||
# Windows用户
|
# Windows用户
|
||||||
@@ -347,72 +375,335 @@ SceneManager.change_scene("battle")
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 快速测试API连通性
|
# 快速测试API连通性
|
||||||
|
>>>>>>> whale-town-front/main
|
||||||
python tests/api/simple_api_test.py
|
python tests/api/simple_api_test.py
|
||||||
|
|
||||||
# 完整的API功能测试
|
# 完整功能测试
|
||||||
python tests/api/api_test.py --verbose
|
python tests/api/api_test.py --verbose
|
||||||
|
|
||||||
# 自定义服务器地址测试
|
|
||||||
python tests/api/simple_api_test.py https://your-api-server.com
|
|
||||||
```
|
```
|
||||||
|
|
||||||
测试脚本会验证:
|
**测试内容:**
|
||||||
- ✅ 应用状态检查
|
- ✅ 用户认证流程测试
|
||||||
- ✅ 用户注册和登录功能
|
- ✅ API接口连通性测试
|
||||||
- ✅ 邮箱验证码发送和验证
|
- ✅ 错误处理和边界条件测试
|
||||||
- ✅ 错误处理和频率限制(429错误)
|
- ✅ 网络通信功能测试
|
||||||
- ✅ 管理员功能和权限控制
|
|
||||||
- ✅ 用户状态管理
|
|
||||||
- ✅ 安全功能测试
|
|
||||||
|
|
||||||
📖 查看 [API测试文档](tests/api/README.md) 了解详细使用方法
|
|
||||||
|
|
||||||
### 认证UI测试
|
|
||||||
项目还提供了Godot内置的UI测试场景:
|
|
||||||
|
|
||||||
1. 在Godot编辑器中打开 `tests/auth/auth_ui_test.tscn`
|
|
||||||
2. 运行场景进行交互式测试
|
|
||||||
3. 测试各种错误场景和边界条件
|
|
||||||
|
|
||||||
📖 查看 [认证UI测试文档](tests/auth/README.md) 了解详细使用方法
|
|
||||||
|
|
||||||
## 🔧 技术栈
|
|
||||||
|
|
||||||
- **游戏引擎**: Godot 4.5.1
|
|
||||||
- **脚本语言**: GDScript
|
|
||||||
- **架构模式**: 模块化 + 事件驱动
|
|
||||||
- **状态管理**: 单例管理器模式
|
|
||||||
- **通信机制**: 全局事件系统
|
|
||||||
- **API集成**: RESTful API + JSON
|
|
||||||
- **测试框架**: Python + Godot内置测试
|
|
||||||
|
|
||||||
## 🤝 贡献指南
|
|
||||||
|
|
||||||
1. Fork 项目
|
|
||||||
2. 创建功能分支 (`git checkout -b feature/AmazingFeature`)
|
|
||||||
3. 遵循项目的命名规范和架构设计
|
|
||||||
4. 添加相应的测试用例
|
|
||||||
5. 更新相关文档
|
|
||||||
6. 提交更改 (`git commit -m 'feat:添加某个功能'`)
|
|
||||||
7. 推送到分支 (`git push origin feature/AmazingFeature`)
|
|
||||||
8. 开启 Pull Request
|
|
||||||
|
|
||||||
### 贡献类型
|
|
||||||
- 🐛 Bug修复
|
|
||||||
- ✨ 新功能开发
|
|
||||||
- 📚 文档改进
|
|
||||||
- 🧪 测试用例
|
|
||||||
- 🎨 UI/UX改进
|
|
||||||
- ⚡ 性能优化
|
|
||||||
|
|
||||||
## 📄 许可证
|
|
||||||
|
|
||||||
本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🎯 项目状态
|
## 🎓 新开发者指南
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
### 第一步:了解项目规范 📚
|
||||||
|
|
||||||
|
**⚠️ 重要:在开始开发前,请务必阅读以下文档**
|
||||||
|
|
||||||
|
1. **[项目结构详解](./docs/project_structure.md)** 🏗️
|
||||||
|
- 理解模块化架构设计
|
||||||
|
- 掌握目录组织规则
|
||||||
|
- 学习组件通信机制
|
||||||
|
|
||||||
|
2. **[命名规范](./docs/naming_convention.md)** 📝
|
||||||
|
- 场景、脚本、节点命名规则
|
||||||
|
- 资源文件命名标准
|
||||||
|
- 变量和函数命名约定
|
||||||
|
|
||||||
|
3. **[代码注释规范](./docs/code_comment_guide.md)** 💬
|
||||||
|
- 注释标准和最佳实践
|
||||||
|
- AI辅助开发指南
|
||||||
|
- 文档生成规范
|
||||||
|
|
||||||
|
4. **[Git提交规范](./docs/git_commit_guide.md)** 🔄
|
||||||
|
- 提交信息格式标准
|
||||||
|
- 分支管理策略
|
||||||
|
- 代码审查流程
|
||||||
|
|
||||||
|
### 第二步:熟悉项目架构 🏗️
|
||||||
|
|
||||||
|
```
|
||||||
|
whaleTown/
|
||||||
|
├── 🎬 scenes/ # 游戏场景
|
||||||
|
│ ├── auth_scene.tscn # 🔐 用户认证场景
|
||||||
|
│ ├── main_scene.tscn # 🎮 主游戏场景
|
||||||
|
│ └── prefabs/ # 🧩 预制体组件
|
||||||
|
├── 🔧 core/ # 核心系统(自动加载)
|
||||||
|
│ ├── managers/ # 🎯 全局管理器
|
||||||
|
│ ├── systems/ # ⚙️ 系统组件
|
||||||
|
│ └── utils/ # 🛠️ 工具类
|
||||||
|
├── 📝 scripts/ # 业务逻辑脚本
|
||||||
|
│ ├── scenes/ # 🎬 场景脚本
|
||||||
|
│ ├── network/ # 🌐 网络相关
|
||||||
|
│ └── ui/ # 🎨 UI组件脚本
|
||||||
|
├── 🧩 module/ # 可复用模块
|
||||||
|
│ ├── UI/ # 🎨 UI组件模块
|
||||||
|
│ ├── Character/ # 👤 角色模块
|
||||||
|
│ ├── Combat/ # ⚔️ 战斗模块
|
||||||
|
│ ├── Dialogue/ # 💬 对话模块
|
||||||
|
│ └── Inventory/ # 🎒 背包模块
|
||||||
|
├── 🎨 assets/ # 游戏资源
|
||||||
|
│ ├── sprites/ # 🖼️ 精灵图资源
|
||||||
|
│ ├── audio/ # 🔊 音频文件
|
||||||
|
│ ├── ui/ # 🎨 UI界面资源
|
||||||
|
│ └── fonts/ # 🔤 字体资源
|
||||||
|
├── 📊 data/ # 配置数据
|
||||||
|
│ ├── configs/ # ⚙️ 游戏配置
|
||||||
|
│ ├── localization/ # 🌍 本地化文件
|
||||||
|
│ └── characters/ # 👤 角色数据
|
||||||
|
├── 🧪 tests/ # 测试文件
|
||||||
|
│ ├── api/ # 🔌 API接口测试
|
||||||
|
│ ├── auth/ # 🔐 认证UI测试
|
||||||
|
│ └── unit/ # 🧪 单元测试
|
||||||
|
└── 📚 docs/ # 项目文档
|
||||||
|
├── auth/ # 🔐 认证相关文档
|
||||||
|
└── api-documentation.md # 📖 API接口文档
|
||||||
|
```
|
||||||
|
|
||||||
|
**架构特点:**
|
||||||
|
- 🏗️ **模块化设计** - 按功能而非技术组织代码
|
||||||
|
- 🔄 **事件驱动** - 通过EventSystem实现组件间通信
|
||||||
|
- 📦 **清晰分层** - 场景层 → 业务层 → 核心层
|
||||||
|
- 🧪 **测试友好** - 完整的测试覆盖和文档
|
||||||
|
|
||||||
|
### 第三步:体验核心功能 🎮
|
||||||
|
|
||||||
|
1. **用户认证系统** 🔐
|
||||||
|
- 邮箱验证码注册
|
||||||
|
- 多方式登录(用户名/邮箱/手机号)
|
||||||
|
- 密码重置功能
|
||||||
|
|
||||||
|
2. **游戏核心系统** 🎮
|
||||||
|
- 场景管理和切换
|
||||||
|
- 角色状态管理
|
||||||
|
- 实时网络通信
|
||||||
|
|
||||||
|
3. **开发工具** 🛠️
|
||||||
|
- 内置测试场景
|
||||||
|
- API测试脚本
|
||||||
|
- 性能监控工具
|
||||||
|
|
||||||
|
### 第四步:开始贡献 🤝
|
||||||
|
|
||||||
|
1. **Fork项目** 到你的账户
|
||||||
|
2. **创建功能分支**:`git checkout -b feature/your-feature`
|
||||||
|
3. **遵循规范开发**(参考文档)
|
||||||
|
4. **添加测试用例**:确保功能正确性
|
||||||
|
5. **提交代码**:`git commit -m "feat:添加新功能"`
|
||||||
|
6. **创建Pull Request**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## <20>[️ 技术栈
|
||||||
|
|
||||||
|
### 🎮 游戏引擎
|
||||||
|
- **Godot Engine** `4.5.1` - 开源游戏引擎,支持2D/3D开发
|
||||||
|
- **GDScript** - Godot专用脚本语言,Python风格语法
|
||||||
|
- **Forward Plus** - 现代渲染管线,支持高质量光照
|
||||||
|
|
||||||
|
### 🏗️ 架构设计
|
||||||
|
- **模块化架构** - 按功能组织的可复用组件系统
|
||||||
|
- **事件驱动** - 基于EventSystem的松耦合通信
|
||||||
|
- **单例管理器** - GameManager、SceneManager等全局管理器
|
||||||
|
- **状态机模式** - 游戏状态和角色状态管理
|
||||||
|
|
||||||
|
### 🌐 网络通信
|
||||||
|
- **RESTful API** - 标准HTTP接口通信
|
||||||
|
- **JSON数据格式** - 轻量级数据交换格式
|
||||||
|
- **WebSocket** - 实时双向通信支持
|
||||||
|
- **错误处理** - 完整的网络异常处理机制
|
||||||
|
|
||||||
|
### 🎨 UI系统
|
||||||
|
- **响应式设计** - 支持多分辨率自适应
|
||||||
|
- **主题系统** - 统一的UI风格管理
|
||||||
|
- **动画系统** - 流畅的界面过渡效果
|
||||||
|
- **本地化支持** - 多语言界面切换
|
||||||
|
|
||||||
|
### 🧪 测试框架
|
||||||
|
- **Godot内置测试** - 场景和组件测试
|
||||||
|
- **Python测试脚本** - API接口自动化测试
|
||||||
|
- **性能监控** - 帧率和内存使用监控
|
||||||
|
- **错误追踪** - 完整的日志和错误报告
|
||||||
|
|
||||||
|
### 📱 跨平台支持
|
||||||
|
- **桌面平台** - Windows、Linux、macOS
|
||||||
|
- **移动平台** - Android、iOS(规划中)
|
||||||
|
- **Web平台** - HTML5导出支持(规划中)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏗️ 核心功能
|
||||||
|
|
||||||
|
### 🔐 用户认证系统 (scenes/auth_scene.tscn)
|
||||||
|
- **多方式登录** - 用户名/邮箱/手机号
|
||||||
|
- **邮箱验证** - 完整的验证码流程和倒计时
|
||||||
|
- **密码安全** - 强度验证和安全提示
|
||||||
|
- **错误处理** - 友好的错误提示和状态管理
|
||||||
|
- **响应式UI** - 自适应布局和动画效果
|
||||||
|
|
||||||
|
### 🎮 游戏核心系统 (core/)
|
||||||
|
- **GameManager** - 全局游戏状态管理(LOADING、AUTH、IN_GAME等)
|
||||||
|
- **SceneManager** - 场景切换和生命周期管理
|
||||||
|
- **EventSystem** - 全局事件通信系统
|
||||||
|
- **StringUtils** - 字符串处理工具集
|
||||||
|
|
||||||
|
### 🧩 模块化组件 (module/)
|
||||||
|
- **UI组件** - 可复用的界面组件和动画
|
||||||
|
- **角色系统** - 角色数据和行为管理
|
||||||
|
- **战斗系统** - 战斗逻辑和技能系统
|
||||||
|
- **对话系统** - 对话树和文本显示
|
||||||
|
- **背包系统** - 物品管理和交互
|
||||||
|
|
||||||
|
### 🌐 网络通信 (scripts/network/)
|
||||||
|
- **API集成** - RESTful接口调用封装
|
||||||
|
- **实时通信** - WebSocket连接管理
|
||||||
|
- **数据同步** - 客户端服务器数据同步
|
||||||
|
- **离线处理** - 网络异常和离线模式
|
||||||
|
|
||||||
|
### 🧪 测试体系 (tests/)
|
||||||
|
- **API测试** - 完整的接口功能测试
|
||||||
|
- **UI测试** - 交互式界面测试场景
|
||||||
|
- **单元测试** - 组件和函数级别测试
|
||||||
|
- **集成测试** - 完整业务流程测试
|
||||||
|
- **性能测试** - 帧率和内存性能监控
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 开发与测试
|
||||||
|
|
||||||
|
### 🔧 开发命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 启动Godot编辑器
|
||||||
|
godot --editor
|
||||||
|
|
||||||
|
# 运行项目(无编辑器)
|
||||||
|
godot --main-pack game.pck
|
||||||
|
|
||||||
|
# 导出项目
|
||||||
|
godot --export "Windows Desktop" game.exe
|
||||||
|
|
||||||
|
# 运行测试
|
||||||
|
godot --headless --script tests/run_tests.gd
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🧪 测试命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# API接口测试
|
||||||
|
python tests/api/simple_api_test.py
|
||||||
|
|
||||||
|
# 完整功能测试
|
||||||
|
python tests/api/api_test.py --verbose
|
||||||
|
|
||||||
|
# 自定义服务器测试
|
||||||
|
python tests/api/simple_api_test.py https://your-server.com
|
||||||
|
|
||||||
|
# UI交互测试
|
||||||
|
# 在Godot编辑器中运行 tests/auth/auth_ui_test.tscn
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📈 测试覆盖率
|
||||||
|
- **API测试**: 17个接口全覆盖 ✅
|
||||||
|
- **UI测试**: 认证流程完整测试 ✅
|
||||||
|
- **错误处理**: 边界条件和异常测试 ✅
|
||||||
|
- **性能测试**: 帧率和内存监控 ✅
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌍 部署配置
|
||||||
|
|
||||||
|
### 开发环境(默认)
|
||||||
|
```bash
|
||||||
|
# 本地开发配置
|
||||||
|
API_BASE_URL=http://localhost:3000
|
||||||
|
DEBUG_MODE=true
|
||||||
|
LOG_LEVEL=debug
|
||||||
|
```
|
||||||
|
|
||||||
|
### 生产环境
|
||||||
|
```bash
|
||||||
|
# 生产环境配置
|
||||||
|
API_BASE_URL=https://your-api-server.com
|
||||||
|
DEBUG_MODE=false
|
||||||
|
LOG_LEVEL=info
|
||||||
|
ENABLE_ANALYTICS=true
|
||||||
|
```
|
||||||
|
|
||||||
|
### 导出设置
|
||||||
|
- **Windows**: 64位可执行文件
|
||||||
|
- **Linux**: AppImage格式
|
||||||
|
- **macOS**: .app应用包
|
||||||
|
- **Android**: APK安装包(规划中)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 文档中心
|
||||||
|
|
||||||
|
### 🎯 新手必读
|
||||||
|
1. **[项目结构详解](./docs/project_structure.md)** - 架构设计和组织规则
|
||||||
|
2. **[命名规范](./docs/naming_convention.md)** - 代码和资源命名标准
|
||||||
|
3. **[代码注释规范](./docs/code_comment_guide.md)** - 注释标准和AI辅助
|
||||||
|
4. **[Git提交规范](./docs/git_commit_guide.md)** - 版本控制最佳实践
|
||||||
|
|
||||||
|
### 📖 功能文档
|
||||||
|
- **[用户认证系统](./docs/auth/)** - 认证流程和界面设计
|
||||||
|
- **[API接口文档](./docs/api-documentation.md)** - 完整的API使用指南
|
||||||
|
- **[测试指南](./tests/api/README.md)** - 测试用例和使用方法
|
||||||
|
|
||||||
|
### 🏗️ 开发指南
|
||||||
|
- **[模块开发指南](./docs/module_development.md)** - 如何创建新模块
|
||||||
|
- **[场景设计规范](./docs/scene_design.md)** - 场景架构和最佳实践
|
||||||
|
- **[性能优化指南](./docs/performance_guide.md)** - 性能调优技巧
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤝 贡献者
|
||||||
|
|
||||||
|
感谢所有为项目做出贡献的开发者!
|
||||||
|
|
||||||
|
### 🏆 核心团队
|
||||||
|
- **[moyin](https://gitea.xinghangee.icu/moyin)** - 核心开发者
|
||||||
|
- **[jianuo](https://gitea.xinghangee.icu/jianuo)** - 核心开发者
|
||||||
|
- **[angjustinl](https://gitea.xinghangee.icu/ANGJustinl)** - 核心开发者
|
||||||
|
|
||||||
|
查看完整贡献者名单:[docs/CONTRIBUTORS.md](./docs/CONTRIBUTORS.md)
|
||||||
|
|
||||||
|
### 🌟 如何贡献
|
||||||
|
|
||||||
|
我们欢迎所有形式的贡献:
|
||||||
|
|
||||||
|
1. **🐛 Bug修复** - 发现并修复问题
|
||||||
|
2. **✨ 新功能** - 添加有价值的功能
|
||||||
|
3. **📚 文档改进** - 完善项目文档
|
||||||
|
4. **🧪 测试用例** - 提高代码覆盖率
|
||||||
|
5. **🎨 UI/UX改进** - 提升用户体验
|
||||||
|
6. **⚡ 性能优化** - 优化游戏性能
|
||||||
|
|
||||||
|
**贡献流程:**
|
||||||
|
1. Fork项目 → 2. 创建分支 → 3. 开发功能 → 4. 提交PR
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 联系我们
|
||||||
|
|
||||||
|
- **项目地址**: [Gitea Repository](https://gitea.xinghangee.icu/datawhale/whale-town)
|
||||||
|
- **问题反馈**: [Issues](https://gitea.xinghangee.icu/datawhale/whale-town/issues)
|
||||||
|
- **功能建议**: [Discussions](https://gitea.xinghangee.icu/datawhale/whale-town/discussions)
|
||||||
|
|
||||||
|
## 📄 许可证
|
||||||
|
|
||||||
|
本项目采用 [MIT License](./LICENSE) 开源协议。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
**🐋 Whale Town - 让像素世界更精彩!**
|
||||||
|
|
||||||
|
Made with ❤️ by the Whale Town Team
|
||||||
|
|
||||||
|
[⭐ Star](https://gitea.xinghangee.icu/datawhale/whale-town) | [🍴 Fork](https://gitea.xinghangee.icu/datawhale/whale-town/fork) | [📖 Docs](./docs/) | [🐛 Issues](https://gitea.xinghangee.icu/datawhale/whale-town/issues)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
=======
|
||||||
- ✅ 基础架构搭建完成
|
- ✅ 基础架构搭建完成
|
||||||
- ✅ 项目结构重构完成(2025-12-31)
|
- ✅ 项目结构重构完成(2025-12-31)
|
||||||
- ✅ 用户认证系统完成
|
- ✅ 用户认证系统完成
|
||||||
@@ -425,3 +716,4 @@ python tests/api/simple_api_test.py https://your-api-server.com
|
|||||||
**最后更新**: 2025-12-31
|
**最后更新**: 2025-12-31
|
||||||
|
|
||||||
**重要更新**: 项目已完成架构重构,采用新的分层结构。详见 [REFACTORING.md](REFACTORING.md)
|
**重要更新**: 项目已完成架构重构,采用新的分层结构。详见 [REFACTORING.md](REFACTORING.md)
|
||||||
|
>>>>>>> whale-town-front/main
|
||||||
|
|||||||
@@ -1,332 +0,0 @@
|
|||||||
# API接口更新日志
|
|
||||||
|
|
||||||
**更新日期**: 2025-12-25
|
|
||||||
**API版本**: v1.1.1
|
|
||||||
**更新内容**: 根据后端最新API文档更新前端接口逻辑和Toast显示
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 更新概述
|
|
||||||
|
|
||||||
根据后端API v1.1.1的最新文档,对前端的网络请求、响应处理和Toast显示系统进行了全面更新,以支持新的功能特性和错误处理机制。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 主要更新内容
|
|
||||||
|
|
||||||
### 1. **HTTP状态码支持**
|
|
||||||
|
|
||||||
#### 新增状态码处理
|
|
||||||
- **206 Partial Content**: 测试模式响应
|
|
||||||
- **409 Conflict**: 资源冲突(用户名、邮箱已存在)
|
|
||||||
- **429 Too Many Requests**: 频率限制
|
|
||||||
|
|
||||||
#### 更新的状态码映射
|
|
||||||
```gdscript
|
|
||||||
const HTTP_STATUS_MESSAGES = {
|
|
||||||
200: "请求成功",
|
|
||||||
201: "创建成功",
|
|
||||||
206: "测试模式",
|
|
||||||
400: "请求参数错误",
|
|
||||||
401: "认证失败",
|
|
||||||
403: "权限不足",
|
|
||||||
404: "资源不存在",
|
|
||||||
408: "请求超时",
|
|
||||||
409: "资源冲突", # 新增
|
|
||||||
415: "不支持的媒体类型",
|
|
||||||
429: "请求过于频繁", # 新增
|
|
||||||
500: "服务器内部错误",
|
|
||||||
503: "服务不可用"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. **错误码映射更新**
|
|
||||||
|
|
||||||
#### 新增错误码
|
|
||||||
- `SEND_EMAIL_VERIFICATION_FAILED`: 发送邮箱验证码失败
|
|
||||||
- `RESEND_EMAIL_VERIFICATION_FAILED`: 重新发送验证码失败
|
|
||||||
- `EMAIL_VERIFICATION_FAILED`: 邮箱验证失败
|
|
||||||
- `RESET_PASSWORD_FAILED`: 重置密码失败
|
|
||||||
- `CHANGE_PASSWORD_FAILED`: 修改密码失败
|
|
||||||
- `USER_STATUS_UPDATE_FAILED`: 用户状态更新失败
|
|
||||||
- `ADMIN_LOGIN_FAILED`: 管理员登录失败
|
|
||||||
|
|
||||||
### 3. **邮箱冲突检测**
|
|
||||||
|
|
||||||
#### 功能描述
|
|
||||||
- 发送邮箱验证码前检查邮箱是否已被注册
|
|
||||||
- 已注册邮箱返回409状态码和明确错误信息
|
|
||||||
|
|
||||||
#### 实现细节
|
|
||||||
```gdscript
|
|
||||||
# 在ResponseHandler中处理409冲突
|
|
||||||
if response_code == 409:
|
|
||||||
if "邮箱已存在" in message:
|
|
||||||
result.message = "📧 邮箱已被注册,请使用其他邮箱或直接登录"
|
|
||||||
elif "用户名已存在" in message:
|
|
||||||
result.message = "👤 用户名已被使用,请换一个"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. **测试模式支持**
|
|
||||||
|
|
||||||
#### 功能描述
|
|
||||||
- 开发环境下邮件服务返回206状态码
|
|
||||||
- 验证码在响应中返回,无需真实发送邮件
|
|
||||||
|
|
||||||
#### 实现细节
|
|
||||||
```gdscript
|
|
||||||
# 处理206测试模式响应
|
|
||||||
elif response_code == 206 and error_code == "TEST_MODE_ONLY":
|
|
||||||
is_success = true
|
|
||||||
print("🧪 测试模式响应: ", message)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Toast显示优化
|
|
||||||
```gdscript
|
|
||||||
if error_code == "TEST_MODE_ONLY":
|
|
||||||
result.message = "🧪 测试模式:验证码已生成,请查看控制台"
|
|
||||||
if data.has("data") and data.data.has("verification_code"):
|
|
||||||
print("🔑 测试模式验证码: ", data.data.verification_code)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. **频率限制处理**
|
|
||||||
|
|
||||||
#### 功能描述
|
|
||||||
- 验证码发送限制1次/分钟
|
|
||||||
- 注册限制10次/5分钟
|
|
||||||
- 提供重试建议和详细错误信息
|
|
||||||
|
|
||||||
#### 实现细节
|
|
||||||
```gdscript
|
|
||||||
"TOO_MANY_REQUESTS":
|
|
||||||
result.message = "⏰ 验证码发送过于频繁,请1分钟后再试"
|
|
||||||
# 显示详细的限制信息
|
|
||||||
if data.has("throttle_info"):
|
|
||||||
var throttle_info = data.throttle_info
|
|
||||||
var reset_time = throttle_info.get("reset_time", "")
|
|
||||||
if reset_time != "":
|
|
||||||
result.message += "\n重试时间: " + reset_time
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6. **Toast显示系统优化**
|
|
||||||
|
|
||||||
#### 视觉改进
|
|
||||||
- 增加图标显示(✅成功,❌失败)
|
|
||||||
- 更丰富的颜色和阴影效果
|
|
||||||
- 支持智能换行和更大的显示区域
|
|
||||||
- 更流畅的动画效果
|
|
||||||
|
|
||||||
#### 新的Toast样式
|
|
||||||
```gdscript
|
|
||||||
# 更深的颜色和更好的对比度
|
|
||||||
if is_success:
|
|
||||||
style.bg_color = Color(0.15, 0.7, 0.15, 0.95) # 深绿色
|
|
||||||
style.border_color = Color(0.2, 0.9, 0.2, 0.9) # 亮绿色边框
|
|
||||||
else:
|
|
||||||
style.bg_color = Color(0.7, 0.15, 0.15, 0.95) # 深红色
|
|
||||||
style.border_color = Color(0.9, 0.2, 0.2, 0.9) # 亮红色边框
|
|
||||||
|
|
||||||
# 添加阴影效果
|
|
||||||
style.shadow_color = Color(0, 0, 0, 0.3)
|
|
||||||
style.shadow_size = 4
|
|
||||||
style.shadow_offset = Vector2(2, 2)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 动画优化
|
|
||||||
- 增加透明度动画
|
|
||||||
- 延长显示时间(2秒→3秒)
|
|
||||||
- 更流畅的滑入滑出效果
|
|
||||||
|
|
||||||
### 7. **新增API方法**
|
|
||||||
|
|
||||||
#### NetworkManager新增方法
|
|
||||||
```gdscript
|
|
||||||
# 重新发送邮箱验证码
|
|
||||||
func resend_email_verification(email: String, callback: Callable) -> String
|
|
||||||
|
|
||||||
# 忘记密码 - 发送重置验证码
|
|
||||||
func forgot_password(identifier: String, callback: Callable) -> String
|
|
||||||
|
|
||||||
# 重置密码
|
|
||||||
func reset_password(identifier: String, verification_code: String, new_password: String, callback: Callable) -> String
|
|
||||||
|
|
||||||
# 修改密码
|
|
||||||
func change_password(user_id: String, old_password: String, new_password: String, callback: Callable) -> String
|
|
||||||
|
|
||||||
# GitHub OAuth登录
|
|
||||||
func github_login(github_id: String, username: String, nickname: String, email: String, avatar_url: String, callback: Callable) -> String
|
|
||||||
```
|
|
||||||
|
|
||||||
#### ResponseHandler新增处理方法
|
|
||||||
```gdscript
|
|
||||||
# 处理重新发送邮箱验证码响应
|
|
||||||
static func handle_resend_email_verification_response(success: bool, data: Dictionary, error_info: Dictionary) -> ResponseResult
|
|
||||||
|
|
||||||
# 处理忘记密码响应
|
|
||||||
static func handle_forgot_password_response(success: bool, data: Dictionary, error_info: Dictionary) -> ResponseResult
|
|
||||||
|
|
||||||
# 处理重置密码响应
|
|
||||||
static func handle_reset_password_response(success: bool, data: Dictionary, error_info: Dictionary) -> ResponseResult
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔧 技术改进
|
|
||||||
|
|
||||||
### 1. **响应处理逻辑优化**
|
|
||||||
|
|
||||||
#### 更精确的成功判断
|
|
||||||
```gdscript
|
|
||||||
# HTTP成功状态码且业务成功
|
|
||||||
if (response_code >= 200 and response_code < 300) and success:
|
|
||||||
is_success = true
|
|
||||||
# 特殊情况:206测试模式
|
|
||||||
elif response_code == 206 and error_code == "TEST_MODE_ONLY":
|
|
||||||
is_success = true
|
|
||||||
# 201创建成功
|
|
||||||
elif response_code == 201:
|
|
||||||
is_success = true
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 更详细的错误类型判断
|
|
||||||
```gdscript
|
|
||||||
match response_code:
|
|
||||||
409: # 资源冲突
|
|
||||||
return ErrorType.BUSINESS_ERROR
|
|
||||||
206: # 测试模式
|
|
||||||
return ErrorType.BUSINESS_ERROR
|
|
||||||
429: # 频率限制
|
|
||||||
return ErrorType.BUSINESS_ERROR
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. **错误消息国际化**
|
|
||||||
|
|
||||||
#### 添加表情符号和更友好的提示
|
|
||||||
- 📧 邮箱相关消息
|
|
||||||
- 👤 用户相关消息
|
|
||||||
- 🔑 验证码相关消息
|
|
||||||
- 🔒 密码相关消息
|
|
||||||
- ⏰ 时间相关消息
|
|
||||||
- 🧪 测试模式消息
|
|
||||||
- 🌐 网络相关消息
|
|
||||||
|
|
||||||
### 3. **代码结构优化**
|
|
||||||
|
|
||||||
#### 更好的模块化
|
|
||||||
- 分离不同类型的错误处理方法
|
|
||||||
- 统一的响应处理接口
|
|
||||||
- 更清晰的方法命名
|
|
||||||
|
|
||||||
#### 更完善的注释
|
|
||||||
- 详细的方法说明
|
|
||||||
- 参数和返回值说明
|
|
||||||
- 使用示例
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧪 测试验证
|
|
||||||
|
|
||||||
### 创建了API测试脚本
|
|
||||||
- **文件**: `scripts/network/ApiTestScript.gd`
|
|
||||||
- **功能**: 验证所有更新的API接口逻辑
|
|
||||||
- **测试用例**:
|
|
||||||
- 网络连接测试
|
|
||||||
- 邮箱验证码发送
|
|
||||||
- 邮箱冲突检测
|
|
||||||
- 登录功能
|
|
||||||
- 注册功能
|
|
||||||
|
|
||||||
### 测试覆盖的场景
|
|
||||||
- ✅ 正常成功响应
|
|
||||||
- ✅ 409邮箱冲突
|
|
||||||
- ✅ 206测试模式
|
|
||||||
- ✅ 429频率限制
|
|
||||||
- ✅ 各种错误状态码
|
|
||||||
- ✅ Toast显示效果
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📚 使用指南
|
|
||||||
|
|
||||||
### 1. **发送邮箱验证码**
|
|
||||||
```gdscript
|
|
||||||
# 会自动检查邮箱冲突
|
|
||||||
var request_id = NetworkManager.send_email_verification("user@example.com", callback)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. **处理409冲突**
|
|
||||||
```gdscript
|
|
||||||
func callback(success: bool, data: Dictionary, error_info: Dictionary):
|
|
||||||
var result = ResponseHandler.handle_send_verification_code_response(success, data, error_info)
|
|
||||||
if error_info.get("response_code") == 409:
|
|
||||||
# 邮箱已存在,引导用户登录
|
|
||||||
show_login_suggestion()
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. **处理测试模式**
|
|
||||||
```gdscript
|
|
||||||
# 测试模式下验证码会在控制台显示
|
|
||||||
if data.get("error_code") == "TEST_MODE_ONLY":
|
|
||||||
var verification_code = data.data.verification_code
|
|
||||||
print("测试验证码: ", verification_code)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. **处理频率限制**
|
|
||||||
```gdscript
|
|
||||||
# 提供重试建议
|
|
||||||
if error_info.get("response_code") == 429:
|
|
||||||
show_retry_suggestion(data.get("throttle_info", {}))
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔄 向后兼容性
|
|
||||||
|
|
||||||
### 保持的兼容性
|
|
||||||
- ✅ 现有的API调用方式不变
|
|
||||||
- ✅ 现有的回调函数签名不变
|
|
||||||
- ✅ 现有的Toast显示接口不变
|
|
||||||
|
|
||||||
### 新增的功能
|
|
||||||
- ✅ 更丰富的错误处理
|
|
||||||
- ✅ 更好的用户体验
|
|
||||||
- ✅ 更详细的状态反馈
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📝 注意事项
|
|
||||||
|
|
||||||
### 开发环境
|
|
||||||
- 测试模式下验证码会在控制台显示
|
|
||||||
- 206状态码表示测试模式,属于成功响应
|
|
||||||
- 建议在开发时关注控制台输出
|
|
||||||
|
|
||||||
### 生产环境
|
|
||||||
- 验证码通过真实邮件发送
|
|
||||||
- 需要正确配置邮件服务
|
|
||||||
- 频率限制会严格执行
|
|
||||||
|
|
||||||
### 错误处理
|
|
||||||
- 优先检查HTTP状态码
|
|
||||||
- 再检查业务错误码
|
|
||||||
- 提供用户友好的错误提示
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 后续计划
|
|
||||||
|
|
||||||
### 短期优化
|
|
||||||
- [ ] 添加更多的API接口支持
|
|
||||||
- [ ] 优化Toast显示的动画效果
|
|
||||||
- [ ] 添加音效反馈
|
|
||||||
|
|
||||||
### 长期规划
|
|
||||||
- [ ] 支持多语言错误消息
|
|
||||||
- [ ] 添加离线模式支持
|
|
||||||
- [ ] 实现请求重试机制
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**更新完成时间**: 2025-12-25
|
|
||||||
**测试状态**: ✅ 已通过基础测试
|
|
||||||
**部署建议**: 建议在测试环境充分验证后再部署到生产环境
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
# AuthScene 文件清理总结
|
|
||||||
|
|
||||||
**清理日期**: 2025-12-25
|
|
||||||
**清理原因**: 修复Parser Error和优化代码结构
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔧 修复的问题
|
|
||||||
|
|
||||||
### 1. **Parser Error修复**
|
|
||||||
- **问题**: `scripts/scenes/AuthScene.gd` 第1196行有语法错误 "母和数字"
|
|
||||||
- **解决**: 完全重写了AuthScene.gd文件,移除了所有语法错误
|
|
||||||
- **结果**: 文件现在可以正常解析,无语法错误
|
|
||||||
|
|
||||||
### 2. **代码结构优化**
|
|
||||||
- **重构验证逻辑**: 使用StringUtils工具类统一处理验证
|
|
||||||
- **简化代码**: 移除重复的验证代码
|
|
||||||
- **提高可维护性**: 更清晰的方法组织和注释
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🗑️ 删除的文件
|
|
||||||
|
|
||||||
### 已删除
|
|
||||||
1. **`scripts/network/NetworkTest.gd`**
|
|
||||||
- **原因**: 功能重复,已有更完善的ApiTestScript.gd
|
|
||||||
- **影响**: 无,功能已被ApiTestScript.gd替代
|
|
||||||
|
|
||||||
### 保留的文件
|
|
||||||
1. **`tests/auth/auth_ui_test.gd`** - 保留,用于UI测试
|
|
||||||
2. **`tests/auth/enhanced_toast_test.gd`** - 保留,用于Toast系统测试
|
|
||||||
3. **`core/utils/StringUtils.gd`** - 保留,提供通用验证工具
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ 优化后的AuthScene.gd结构
|
|
||||||
|
|
||||||
### 文件组织
|
|
||||||
```
|
|
||||||
AuthScene.gd (约600行,结构清晰)
|
|
||||||
├── 节点引用和变量定义
|
|
||||||
├── 初始化和信号连接
|
|
||||||
├── 按钮事件处理
|
|
||||||
├── 网络响应处理
|
|
||||||
├── 验证码冷却管理
|
|
||||||
├── Toast消息系统
|
|
||||||
├── UI工具方法
|
|
||||||
├── 表单验证方法
|
|
||||||
├── 表单验证事件
|
|
||||||
└── 资源清理
|
|
||||||
```
|
|
||||||
|
|
||||||
### 主要改进
|
|
||||||
1. **使用StringUtils**: 统一的验证逻辑
|
|
||||||
2. **清晰的方法分组**: 按功能组织代码
|
|
||||||
3. **完整的错误处理**: 支持最新API v1.1.1
|
|
||||||
4. **优化的Toast系统**: 更好的视觉效果和动画
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧪 测试验证
|
|
||||||
|
|
||||||
### 语法检查
|
|
||||||
```bash
|
|
||||||
# 所有文件通过语法检查
|
|
||||||
✅ scripts/scenes/AuthScene.gd - No diagnostics found
|
|
||||||
✅ core/managers/NetworkManager.gd - No diagnostics found
|
|
||||||
✅ core/managers/ResponseHandler.gd - No diagnostics found
|
|
||||||
```
|
|
||||||
|
|
||||||
### 功能测试
|
|
||||||
- ✅ Toast显示系统正常
|
|
||||||
- ✅ 表单验证逻辑正确
|
|
||||||
- ✅ 网络请求处理完整
|
|
||||||
- ✅ 验证码冷却机制有效
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 代码质量提升
|
|
||||||
|
|
||||||
### 前后对比
|
|
||||||
| 指标 | 清理前 | 清理后 | 改进 |
|
|
||||||
|------|--------|--------|------|
|
|
||||||
| 语法错误 | 1个 | 0个 | ✅ 修复 |
|
|
||||||
| 代码行数 | ~1400行 | ~600行 | ✅ 精简57% |
|
|
||||||
| 重复代码 | 多处 | 无 | ✅ 消除 |
|
|
||||||
| 可读性 | 中等 | 高 | ✅ 提升 |
|
|
||||||
| 维护性 | 中等 | 高 | ✅ 提升 |
|
|
||||||
|
|
||||||
### 代码质量指标
|
|
||||||
- **圈复杂度**: 降低
|
|
||||||
- **代码重复率**: 显著减少
|
|
||||||
- **方法长度**: 更合理
|
|
||||||
- **注释覆盖**: 完整
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔄 兼容性保证
|
|
||||||
|
|
||||||
### API兼容性
|
|
||||||
- ✅ 保持所有公共方法签名不变
|
|
||||||
- ✅ 保持所有信号定义不变
|
|
||||||
- ✅ 保持节点引用路径不变
|
|
||||||
|
|
||||||
### 功能兼容性
|
|
||||||
- ✅ 登录功能完整
|
|
||||||
- ✅ 注册功能完整
|
|
||||||
- ✅ 验证码功能完整
|
|
||||||
- ✅ Toast显示功能增强
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📝 后续建议
|
|
||||||
|
|
||||||
### 短期
|
|
||||||
1. **测试验证**: 在实际环境中测试所有功能
|
|
||||||
2. **性能监控**: 观察Toast动画性能
|
|
||||||
3. **用户反馈**: 收集UI体验反馈
|
|
||||||
|
|
||||||
### 长期
|
|
||||||
1. **单元测试**: 为验证逻辑添加更多单元测试
|
|
||||||
2. **集成测试**: 完善端到端测试覆盖
|
|
||||||
3. **代码审查**: 定期进行代码质量审查
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 总结
|
|
||||||
|
|
||||||
通过这次清理,我们成功:
|
|
||||||
|
|
||||||
1. **修复了语法错误** - AuthScene.gd现在可以正常解析
|
|
||||||
2. **优化了代码结构** - 更清晰、更易维护
|
|
||||||
3. **提升了代码质量** - 减少重复,提高可读性
|
|
||||||
4. **保持了功能完整** - 所有原有功能都得到保留和增强
|
|
||||||
5. **删除了冗余文件** - 清理了不必要的测试文件
|
|
||||||
|
|
||||||
AuthScene现在是一个干净、高效、易维护的认证界面组件,完全支持最新的API v1.1.1规范。
|
|
||||||
464
docs/module_development.md
Normal file
464
docs/module_development.md
Normal file
@@ -0,0 +1,464 @@
|
|||||||
|
# 模块开发指南
|
||||||
|
|
||||||
|
本文档详细说明如何在 Whale Town 项目中开发新的游戏模块。
|
||||||
|
|
||||||
|
## 🎯 模块设计原则
|
||||||
|
|
||||||
|
### 核心原则
|
||||||
|
1. **单一职责** - 每个模块只负责一个特定的游戏功能
|
||||||
|
2. **高内聚低耦合** - 模块内部紧密相关,模块间松散耦合
|
||||||
|
3. **可复用性** - 模块应该能在不同场景中复用
|
||||||
|
4. **标准化接口** - 统一的模块接口和通信方式
|
||||||
|
5. **测试友好** - 模块应该易于测试和调试
|
||||||
|
|
||||||
|
### 模块分类
|
||||||
|
- **UI模块** - 用户界面组件和交互逻辑
|
||||||
|
- **游戏逻辑模块** - 角色、战斗、对话等核心功能
|
||||||
|
- **系统模块** - 存档、设置、网络等系统功能
|
||||||
|
- **工具模块** - 通用工具和辅助功能
|
||||||
|
|
||||||
|
## 🏗️ 模块结构
|
||||||
|
|
||||||
|
### 标准目录结构
|
||||||
|
```
|
||||||
|
module/YourModule/
|
||||||
|
├── YourModule.gd # 主模块脚本
|
||||||
|
├── components/ # 子组件
|
||||||
|
│ ├── Component1.gd
|
||||||
|
│ └── Component2.gd
|
||||||
|
├── data/ # 模块数据
|
||||||
|
│ ├── module_config.json
|
||||||
|
│ └── default_data.json
|
||||||
|
├── scenes/ # 模块场景
|
||||||
|
│ ├── YourModule.tscn
|
||||||
|
│ └── components/
|
||||||
|
├── tests/ # 模块测试
|
||||||
|
│ ├── test_your_module.gd
|
||||||
|
│ └── test_components.gd
|
||||||
|
└── README.md # 模块文档
|
||||||
|
```
|
||||||
|
|
||||||
|
### 文件命名规范
|
||||||
|
- **主模块脚本**: `ModuleName.gd` (PascalCase)
|
||||||
|
- **组件脚本**: `ComponentName.gd` (PascalCase)
|
||||||
|
- **场景文件**: `module_name.tscn` (snake_case)
|
||||||
|
- **数据文件**: `snake_case.json`
|
||||||
|
- **测试文件**: `test_module_name.gd`
|
||||||
|
|
||||||
|
## 📝 模块开发步骤
|
||||||
|
|
||||||
|
### 第一步:创建模块结构
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 创建模块目录
|
||||||
|
mkdir module/YourModule
|
||||||
|
cd module/YourModule
|
||||||
|
|
||||||
|
# 创建子目录
|
||||||
|
mkdir components data scenes tests
|
||||||
|
|
||||||
|
# 创建主要文件
|
||||||
|
touch YourModule.gd
|
||||||
|
touch README.md
|
||||||
|
touch data/module_config.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第二步:实现模块接口
|
||||||
|
|
||||||
|
每个模块都应该实现标准的模块接口:
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
# YourModule.gd
|
||||||
|
extends Node
|
||||||
|
class_name YourModule
|
||||||
|
|
||||||
|
# 模块信息
|
||||||
|
const MODULE_NAME = "YourModule"
|
||||||
|
const MODULE_VERSION = "1.0.0"
|
||||||
|
const MODULE_AUTHOR = "Your Name"
|
||||||
|
|
||||||
|
# 模块状态
|
||||||
|
enum ModuleState {
|
||||||
|
UNINITIALIZED,
|
||||||
|
INITIALIZING,
|
||||||
|
READY,
|
||||||
|
ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
var current_state: ModuleState = ModuleState.UNINITIALIZED
|
||||||
|
var config: Dictionary = {}
|
||||||
|
var is_enabled: bool = true
|
||||||
|
|
||||||
|
# 必需接口方法
|
||||||
|
func initialize() -> bool:
|
||||||
|
"""初始化模块"""
|
||||||
|
current_state = ModuleState.INITIALIZING
|
||||||
|
|
||||||
|
# 加载配置
|
||||||
|
if not _load_config():
|
||||||
|
current_state = ModuleState.ERROR
|
||||||
|
return false
|
||||||
|
|
||||||
|
# 初始化组件
|
||||||
|
if not _initialize_components():
|
||||||
|
current_state = ModuleState.ERROR
|
||||||
|
return false
|
||||||
|
|
||||||
|
# 注册事件监听
|
||||||
|
_register_events()
|
||||||
|
|
||||||
|
current_state = ModuleState.READY
|
||||||
|
return true
|
||||||
|
|
||||||
|
func cleanup():
|
||||||
|
"""清理模块资源"""
|
||||||
|
_unregister_events()
|
||||||
|
_cleanup_components()
|
||||||
|
current_state = ModuleState.UNINITIALIZED
|
||||||
|
|
||||||
|
func get_module_info() -> Dictionary:
|
||||||
|
"""获取模块信息"""
|
||||||
|
return {
|
||||||
|
"name": MODULE_NAME,
|
||||||
|
"version": MODULE_VERSION,
|
||||||
|
"author": MODULE_AUTHOR,
|
||||||
|
"state": current_state,
|
||||||
|
"enabled": is_enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
# 私有方法
|
||||||
|
func _load_config() -> bool:
|
||||||
|
"""加载模块配置"""
|
||||||
|
var config_path = "res://module/%s/data/module_config.json" % MODULE_NAME
|
||||||
|
if not FileAccess.file_exists(config_path):
|
||||||
|
print("警告: 模块配置文件不存在: ", config_path)
|
||||||
|
return true # 使用默认配置
|
||||||
|
|
||||||
|
var file = FileAccess.open(config_path, FileAccess.READ)
|
||||||
|
if file == null:
|
||||||
|
print("错误: 无法读取配置文件: ", config_path)
|
||||||
|
return false
|
||||||
|
|
||||||
|
var json_string = file.get_as_text()
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
var json = JSON.new()
|
||||||
|
var parse_result = json.parse(json_string)
|
||||||
|
if parse_result != OK:
|
||||||
|
print("错误: 配置文件JSON格式错误: ", config_path)
|
||||||
|
return false
|
||||||
|
|
||||||
|
config = json.data
|
||||||
|
return true
|
||||||
|
|
||||||
|
func _initialize_components() -> bool:
|
||||||
|
"""初始化子组件"""
|
||||||
|
# 在这里初始化模块的子组件
|
||||||
|
return true
|
||||||
|
|
||||||
|
func _cleanup_components():
|
||||||
|
"""清理子组件"""
|
||||||
|
# 在这里清理模块的子组件
|
||||||
|
pass
|
||||||
|
|
||||||
|
func _register_events():
|
||||||
|
"""注册事件监听"""
|
||||||
|
# 使用EventSystem注册需要监听的事件
|
||||||
|
# EventSystem.connect_event("event_name", _on_event_handler)
|
||||||
|
pass
|
||||||
|
|
||||||
|
func _unregister_events():
|
||||||
|
"""取消事件监听"""
|
||||||
|
# 取消所有事件监听
|
||||||
|
# EventSystem.disconnect_event("event_name", _on_event_handler)
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第三步:创建模块配置
|
||||||
|
|
||||||
|
```json
|
||||||
|
// data/module_config.json
|
||||||
|
{
|
||||||
|
"module_name": "YourModule",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"enabled": true,
|
||||||
|
"dependencies": [],
|
||||||
|
"settings": {
|
||||||
|
"auto_initialize": true,
|
||||||
|
"debug_mode": false
|
||||||
|
},
|
||||||
|
"resources": {
|
||||||
|
"textures": [],
|
||||||
|
"sounds": [],
|
||||||
|
"data_files": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第四步:实现具体功能
|
||||||
|
|
||||||
|
根据模块类型实现具体功能:
|
||||||
|
|
||||||
|
#### UI模块示例
|
||||||
|
```gdscript
|
||||||
|
# UI模块应该继承Control或其子类
|
||||||
|
extends Control
|
||||||
|
class_name UIModule
|
||||||
|
|
||||||
|
signal ui_event_triggered(event_name: String, data: Dictionary)
|
||||||
|
|
||||||
|
func show_ui():
|
||||||
|
"""显示UI"""
|
||||||
|
visible = true
|
||||||
|
# 播放显示动画
|
||||||
|
_play_show_animation()
|
||||||
|
|
||||||
|
func hide_ui():
|
||||||
|
"""隐藏UI"""
|
||||||
|
# 播放隐藏动画
|
||||||
|
_play_hide_animation()
|
||||||
|
await get_tree().create_timer(0.3).timeout
|
||||||
|
visible = false
|
||||||
|
|
||||||
|
func _play_show_animation():
|
||||||
|
"""播放显示动画"""
|
||||||
|
var tween = create_tween()
|
||||||
|
modulate.a = 0.0
|
||||||
|
tween.tween_property(self, "modulate:a", 1.0, 0.3)
|
||||||
|
|
||||||
|
func _play_hide_animation():
|
||||||
|
"""播放隐藏动画"""
|
||||||
|
var tween = create_tween()
|
||||||
|
tween.tween_property(self, "modulate:a", 0.0, 0.3)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 游戏逻辑模块示例
|
||||||
|
```gdscript
|
||||||
|
# 游戏逻辑模块
|
||||||
|
extends Node
|
||||||
|
class_name GameLogicModule
|
||||||
|
|
||||||
|
signal state_changed(old_state: String, new_state: String)
|
||||||
|
signal data_updated(data_type: String, new_data: Dictionary)
|
||||||
|
|
||||||
|
var module_data: Dictionary = {}
|
||||||
|
var current_state: String = "idle"
|
||||||
|
|
||||||
|
func process_game_logic(delta: float):
|
||||||
|
"""处理游戏逻辑"""
|
||||||
|
match current_state:
|
||||||
|
"idle":
|
||||||
|
_process_idle_state(delta)
|
||||||
|
"active":
|
||||||
|
_process_active_state(delta)
|
||||||
|
"paused":
|
||||||
|
_process_paused_state(delta)
|
||||||
|
|
||||||
|
func change_state(new_state: String):
|
||||||
|
"""改变模块状态"""
|
||||||
|
var old_state = current_state
|
||||||
|
current_state = new_state
|
||||||
|
state_changed.emit(old_state, new_state)
|
||||||
|
|
||||||
|
func update_data(data_type: String, new_data: Dictionary):
|
||||||
|
"""更新模块数据"""
|
||||||
|
module_data[data_type] = new_data
|
||||||
|
data_updated.emit(data_type, new_data)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第五步:添加测试
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
# tests/test_your_module.gd
|
||||||
|
extends "res://addons/gut/test.gd"
|
||||||
|
|
||||||
|
var module: YourModule
|
||||||
|
|
||||||
|
func before_each():
|
||||||
|
module = YourModule.new()
|
||||||
|
add_child(module)
|
||||||
|
|
||||||
|
func after_each():
|
||||||
|
module.queue_free()
|
||||||
|
|
||||||
|
func test_module_initialization():
|
||||||
|
assert_true(module.initialize(), "模块应该能够正确初始化")
|
||||||
|
assert_eq(module.current_state, YourModule.ModuleState.READY, "初始化后状态应该为READY")
|
||||||
|
|
||||||
|
func test_module_cleanup():
|
||||||
|
module.initialize()
|
||||||
|
module.cleanup()
|
||||||
|
assert_eq(module.current_state, YourModule.ModuleState.UNINITIALIZED, "清理后状态应该为UNINITIALIZED")
|
||||||
|
|
||||||
|
func test_module_info():
|
||||||
|
var info = module.get_module_info()
|
||||||
|
assert_true(info.has("name"), "模块信息应该包含名称")
|
||||||
|
assert_true(info.has("version"), "模块信息应该包含版本")
|
||||||
|
assert_eq(info.name, "YourModule", "模块名称应该正确")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第六步:编写文档
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# YourModule 模块
|
||||||
|
|
||||||
|
## 功能描述
|
||||||
|
简要描述模块的主要功能和用途。
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
```gdscript
|
||||||
|
# 创建和初始化模块
|
||||||
|
var your_module = YourModule.new()
|
||||||
|
add_child(your_module)
|
||||||
|
your_module.initialize()
|
||||||
|
|
||||||
|
# 使用模块功能
|
||||||
|
your_module.some_function()
|
||||||
|
```
|
||||||
|
|
||||||
|
## API参考
|
||||||
|
### 公共方法
|
||||||
|
- `initialize() -> bool` - 初始化模块
|
||||||
|
- `cleanup()` - 清理模块资源
|
||||||
|
- `get_module_info() -> Dictionary` - 获取模块信息
|
||||||
|
|
||||||
|
### 信号
|
||||||
|
- `signal_name(param: Type)` - 信号描述
|
||||||
|
|
||||||
|
## 配置选项
|
||||||
|
描述模块的配置选项和默认值。
|
||||||
|
|
||||||
|
## 依赖关系
|
||||||
|
列出模块的依赖项。
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
使用模块时需要注意的事项。
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 模块集成
|
||||||
|
|
||||||
|
### 在场景中使用模块
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
# 在场景脚本中使用模块
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
var inventory_module: InventoryModule
|
||||||
|
var dialogue_module: DialogueModule
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
# 创建并初始化模块
|
||||||
|
inventory_module = InventoryModule.new()
|
||||||
|
add_child(inventory_module)
|
||||||
|
inventory_module.initialize()
|
||||||
|
|
||||||
|
dialogue_module = DialogueModule.new()
|
||||||
|
add_child(dialogue_module)
|
||||||
|
dialogue_module.initialize()
|
||||||
|
|
||||||
|
# 连接模块事件
|
||||||
|
inventory_module.item_selected.connect(_on_item_selected)
|
||||||
|
dialogue_module.dialogue_finished.connect(_on_dialogue_finished)
|
||||||
|
|
||||||
|
func _on_item_selected(item_data: Dictionary):
|
||||||
|
print("选中物品: ", item_data.name)
|
||||||
|
|
||||||
|
func _on_dialogue_finished():
|
||||||
|
print("对话结束")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 模块间通信
|
||||||
|
|
||||||
|
推荐使用EventSystem进行模块间通信:
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
# 发送事件
|
||||||
|
EventSystem.emit_event("inventory_item_used", {
|
||||||
|
"item_id": "potion_001",
|
||||||
|
"quantity": 1
|
||||||
|
})
|
||||||
|
|
||||||
|
# 监听事件
|
||||||
|
EventSystem.connect_event("inventory_item_used", _on_item_used)
|
||||||
|
|
||||||
|
func _on_item_used(data: Dictionary):
|
||||||
|
print("使用了物品: ", data.item_id)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🧪 测试和调试
|
||||||
|
|
||||||
|
### 单元测试
|
||||||
|
每个模块都应该有对应的单元测试:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 运行模块测试
|
||||||
|
godot --headless --script tests/test_your_module.gd
|
||||||
|
```
|
||||||
|
|
||||||
|
### 调试技巧
|
||||||
|
1. **使用print语句** - 在关键位置添加调试输出
|
||||||
|
2. **断点调试** - 在Godot编辑器中设置断点
|
||||||
|
3. **状态监控** - 实时监控模块状态变化
|
||||||
|
4. **事件追踪** - 记录事件的发送和接收
|
||||||
|
|
||||||
|
### 性能监控
|
||||||
|
```gdscript
|
||||||
|
# 性能监控示例
|
||||||
|
func _process(delta):
|
||||||
|
var start_time = Time.get_time_dict_from_system()
|
||||||
|
|
||||||
|
# 执行模块逻辑
|
||||||
|
process_module_logic(delta)
|
||||||
|
|
||||||
|
var end_time = Time.get_time_dict_from_system()
|
||||||
|
var execution_time = (end_time.hour * 3600 + end_time.minute * 60 + end_time.second) - \
|
||||||
|
(start_time.hour * 3600 + start_time.minute * 60 + start_time.second)
|
||||||
|
|
||||||
|
if execution_time > 0.016: # 超过16ms
|
||||||
|
print("警告: 模块执行时间过长: ", execution_time, "秒")
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📚 最佳实践
|
||||||
|
|
||||||
|
### 代码质量
|
||||||
|
1. **遵循命名规范** - 使用清晰、一致的命名
|
||||||
|
2. **添加详细注释** - 解释复杂逻辑和设计决策
|
||||||
|
3. **错误处理** - 妥善处理各种异常情况
|
||||||
|
4. **资源管理** - 及时释放不需要的资源
|
||||||
|
|
||||||
|
### 性能优化
|
||||||
|
1. **避免频繁的内存分配** - 重用对象和数据结构
|
||||||
|
2. **合理使用信号** - 避免过多的信号连接
|
||||||
|
3. **批量处理** - 将多个操作合并处理
|
||||||
|
4. **延迟加载** - 按需加载资源和数据
|
||||||
|
|
||||||
|
### 可维护性
|
||||||
|
1. **模块化设计** - 保持模块的独立性
|
||||||
|
2. **版本控制** - 记录模块的版本变化
|
||||||
|
3. **文档更新** - 及时更新模块文档
|
||||||
|
4. **向后兼容** - 考虑API的向后兼容性
|
||||||
|
|
||||||
|
## 🔍 常见问题
|
||||||
|
|
||||||
|
### Q: 如何处理模块依赖?
|
||||||
|
A: 在模块配置中声明依赖,在初始化时检查依赖是否满足。
|
||||||
|
|
||||||
|
### Q: 模块间如何共享数据?
|
||||||
|
A: 使用EventSystem传递数据,或通过GameManager等全局管理器。
|
||||||
|
|
||||||
|
### Q: 如何调试模块问题?
|
||||||
|
A: 使用Godot的调试工具,添加日志输出,编写单元测试。
|
||||||
|
|
||||||
|
### Q: 模块性能如何优化?
|
||||||
|
A: 避免在_process中执行重复计算,使用对象池,合理管理资源。
|
||||||
|
|
||||||
|
## 📖 参考资料
|
||||||
|
|
||||||
|
- [Godot官方文档](https://docs.godotengine.org/)
|
||||||
|
- [项目命名规范](./naming_convention.md)
|
||||||
|
- [代码注释规范](./code_comment_guide.md)
|
||||||
|
- [Git提交规范](./git_commit_guide.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**记住:好的模块设计是项目成功的关键!**
|
||||||
507
docs/performance_optimization.md
Normal file
507
docs/performance_optimization.md
Normal file
@@ -0,0 +1,507 @@
|
|||||||
|
# 性能优化指南
|
||||||
|
|
||||||
|
本文档提供 Whale Town 项目的性能优化策略和最佳实践。
|
||||||
|
|
||||||
|
## 🎯 优化目标
|
||||||
|
|
||||||
|
### 性能指标
|
||||||
|
- **帧率**: 保持60FPS稳定运行
|
||||||
|
- **内存使用**: 控制在合理范围内
|
||||||
|
- **加载时间**: 场景切换<2秒
|
||||||
|
- **响应时间**: UI交互<100ms
|
||||||
|
|
||||||
|
### 优化原则
|
||||||
|
1. **测量优先** - 先测量再优化
|
||||||
|
2. **渐进优化** - 逐步改进性能
|
||||||
|
3. **平衡取舍** - 在质量和性能间平衡
|
||||||
|
4. **用户体验** - 优化用户感知性能
|
||||||
|
|
||||||
|
## 🔧 渲染优化
|
||||||
|
|
||||||
|
### 纹理优化
|
||||||
|
```gdscript
|
||||||
|
# 使用合适的纹理格式
|
||||||
|
# 小图标使用 RGBA8
|
||||||
|
# 大背景使用 DXT1/DXT5
|
||||||
|
# UI元素使用 RGBA4444
|
||||||
|
|
||||||
|
# 纹理压缩设置
|
||||||
|
func optimize_texture_import():
|
||||||
|
# 在导入设置中启用压缩
|
||||||
|
# 设置合适的最大尺寸
|
||||||
|
# 启用Mipmaps(3D纹理)
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### 批处理优化
|
||||||
|
```gdscript
|
||||||
|
# 合并相同材质的对象
|
||||||
|
# 使用MultiMesh渲染大量相同对象
|
||||||
|
func create_multimesh_instances():
|
||||||
|
var multimesh = MultiMesh.new()
|
||||||
|
multimesh.transform_format = MultiMesh.TRANSFORM_2D
|
||||||
|
multimesh.instance_count = 100
|
||||||
|
|
||||||
|
var mesh_instance = MeshInstance2D.new()
|
||||||
|
mesh_instance.multimesh = multimesh
|
||||||
|
add_child(mesh_instance)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 视锥剔除
|
||||||
|
```gdscript
|
||||||
|
# 只渲染可见区域的对象
|
||||||
|
func update_visibility():
|
||||||
|
var camera = get_viewport().get_camera_2d()
|
||||||
|
var screen_rect = get_viewport_rect()
|
||||||
|
|
||||||
|
for child in get_children():
|
||||||
|
if child is Node2D:
|
||||||
|
var visible = screen_rect.intersects(child.get_rect())
|
||||||
|
child.visible = visible
|
||||||
|
```
|
||||||
|
|
||||||
|
## 💾 内存优化
|
||||||
|
|
||||||
|
### 对象池模式
|
||||||
|
```gdscript
|
||||||
|
# ObjectPool.gd
|
||||||
|
class_name ObjectPool
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
var pools: Dictionary = {}
|
||||||
|
|
||||||
|
func get_object(type: String) -> Node:
|
||||||
|
if not pools.has(type):
|
||||||
|
pools[type] = []
|
||||||
|
|
||||||
|
var pool = pools[type]
|
||||||
|
if pool.size() > 0:
|
||||||
|
return pool.pop_back()
|
||||||
|
else:
|
||||||
|
return _create_new_object(type)
|
||||||
|
|
||||||
|
func return_object(obj: Node, type: String):
|
||||||
|
obj.reset() # 重置对象状态
|
||||||
|
pools[type].append(obj)
|
||||||
|
|
||||||
|
func _create_new_object(type: String) -> Node:
|
||||||
|
match type:
|
||||||
|
"Bullet":
|
||||||
|
return preload("res://prefabs/Bullet.tscn").instantiate()
|
||||||
|
"Enemy":
|
||||||
|
return preload("res://prefabs/Enemy.tscn").instantiate()
|
||||||
|
_:
|
||||||
|
return null
|
||||||
|
```
|
||||||
|
|
||||||
|
### 资源管理
|
||||||
|
```gdscript
|
||||||
|
# ResourceManager.gd
|
||||||
|
class_name ResourceManager
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
var loaded_resources: Dictionary = {}
|
||||||
|
var resource_usage: Dictionary = {}
|
||||||
|
|
||||||
|
func load_resource(path: String) -> Resource:
|
||||||
|
if loaded_resources.has(path):
|
||||||
|
resource_usage[path] += 1
|
||||||
|
return loaded_resources[path]
|
||||||
|
|
||||||
|
var resource = load(path)
|
||||||
|
loaded_resources[path] = resource
|
||||||
|
resource_usage[path] = 1
|
||||||
|
return resource
|
||||||
|
|
||||||
|
func unload_resource(path: String):
|
||||||
|
if resource_usage.has(path):
|
||||||
|
resource_usage[path] -= 1
|
||||||
|
if resource_usage[path] <= 0:
|
||||||
|
loaded_resources.erase(path)
|
||||||
|
resource_usage.erase(path)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 内存监控
|
||||||
|
```gdscript
|
||||||
|
# MemoryMonitor.gd
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
# 每秒检查一次内存使用
|
||||||
|
var timer = Timer.new()
|
||||||
|
timer.wait_time = 1.0
|
||||||
|
timer.timeout.connect(_check_memory)
|
||||||
|
add_child(timer)
|
||||||
|
timer.start()
|
||||||
|
|
||||||
|
func _check_memory():
|
||||||
|
var memory_usage = OS.get_static_memory_usage_by_type()
|
||||||
|
var total_memory = OS.get_static_memory_peak_usage()
|
||||||
|
|
||||||
|
print("内存使用: ", memory_usage / 1024 / 1024, "MB")
|
||||||
|
print("峰值内存: ", total_memory / 1024 / 1024, "MB")
|
||||||
|
|
||||||
|
# 内存使用过高时触发垃圾回收
|
||||||
|
if total_memory > 200 * 1024 * 1024: # 200MB
|
||||||
|
print("触发垃圾回收")
|
||||||
|
# 清理不必要的资源
|
||||||
|
_cleanup_resources()
|
||||||
|
|
||||||
|
func _cleanup_resources():
|
||||||
|
# 清理缓存
|
||||||
|
# 卸载未使用的资源
|
||||||
|
# 强制垃圾回收
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚡ 脚本优化
|
||||||
|
|
||||||
|
### 避免频繁计算
|
||||||
|
```gdscript
|
||||||
|
# ❌ 错误示例:每帧计算
|
||||||
|
func _process(delta):
|
||||||
|
var distance = global_position.distance_to(target.global_position)
|
||||||
|
if distance < 100:
|
||||||
|
attack_target()
|
||||||
|
|
||||||
|
# ✅ 正确示例:缓存计算结果
|
||||||
|
var cached_distance: float = 0.0
|
||||||
|
var distance_update_timer: float = 0.0
|
||||||
|
|
||||||
|
func _process(delta):
|
||||||
|
distance_update_timer += delta
|
||||||
|
if distance_update_timer >= 0.1: # 每100ms更新一次
|
||||||
|
cached_distance = global_position.distance_to(target.global_position)
|
||||||
|
distance_update_timer = 0.0
|
||||||
|
|
||||||
|
if cached_distance < 100:
|
||||||
|
attack_target()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 优化循环
|
||||||
|
```gdscript
|
||||||
|
# ❌ 错误示例:嵌套循环
|
||||||
|
func find_nearest_enemy():
|
||||||
|
var nearest = null
|
||||||
|
var min_distance = INF
|
||||||
|
|
||||||
|
for enemy in enemies:
|
||||||
|
for player in players:
|
||||||
|
var distance = enemy.global_position.distance_to(player.global_position)
|
||||||
|
if distance < min_distance:
|
||||||
|
min_distance = distance
|
||||||
|
nearest = enemy
|
||||||
|
|
||||||
|
# ✅ 正确示例:优化算法
|
||||||
|
func find_nearest_enemy():
|
||||||
|
var player_pos = player.global_position
|
||||||
|
var nearest = null
|
||||||
|
var min_distance = INF
|
||||||
|
|
||||||
|
for enemy in enemies:
|
||||||
|
var distance = enemy.global_position.distance_squared_to(player_pos)
|
||||||
|
if distance < min_distance:
|
||||||
|
min_distance = distance
|
||||||
|
nearest = enemy
|
||||||
|
```
|
||||||
|
|
||||||
|
### 事件优化
|
||||||
|
```gdscript
|
||||||
|
# 使用信号代替轮询
|
||||||
|
# ❌ 错误示例:轮询检查
|
||||||
|
func _process(delta):
|
||||||
|
if player.health <= 0:
|
||||||
|
game_over()
|
||||||
|
|
||||||
|
# ✅ 正确示例:事件驱动
|
||||||
|
func _ready():
|
||||||
|
player.health_changed.connect(_on_health_changed)
|
||||||
|
|
||||||
|
func _on_health_changed(new_health: int):
|
||||||
|
if new_health <= 0:
|
||||||
|
game_over()
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎮 游戏逻辑优化
|
||||||
|
|
||||||
|
### 状态机优化
|
||||||
|
```gdscript
|
||||||
|
# StateMachine.gd
|
||||||
|
class_name StateMachine
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
var current_state: State
|
||||||
|
var states: Dictionary = {}
|
||||||
|
|
||||||
|
func change_state(state_name: String):
|
||||||
|
if current_state:
|
||||||
|
current_state.exit()
|
||||||
|
|
||||||
|
current_state = states[state_name]
|
||||||
|
current_state.enter()
|
||||||
|
|
||||||
|
func _process(delta):
|
||||||
|
if current_state:
|
||||||
|
current_state.update(delta)
|
||||||
|
```
|
||||||
|
|
||||||
|
### AI优化
|
||||||
|
```gdscript
|
||||||
|
# EnemyAI.gd
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
var update_interval: float = 0.2 # 每200ms更新一次AI
|
||||||
|
var update_timer: float = 0.0
|
||||||
|
|
||||||
|
func _process(delta):
|
||||||
|
update_timer += delta
|
||||||
|
if update_timer >= update_interval:
|
||||||
|
update_ai()
|
||||||
|
update_timer = 0.0
|
||||||
|
|
||||||
|
func update_ai():
|
||||||
|
# AI逻辑更新
|
||||||
|
# 路径寻找
|
||||||
|
# 决策制定
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### 碰撞检测优化
|
||||||
|
```gdscript
|
||||||
|
# 使用空间分区优化碰撞检测
|
||||||
|
class_name SpatialGrid
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
var grid_size: int = 64
|
||||||
|
var grid: Dictionary = {}
|
||||||
|
|
||||||
|
func add_object(obj: Node2D):
|
||||||
|
var grid_pos = Vector2(
|
||||||
|
int(obj.global_position.x / grid_size),
|
||||||
|
int(obj.global_position.y / grid_size)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not grid.has(grid_pos):
|
||||||
|
grid[grid_pos] = []
|
||||||
|
|
||||||
|
grid[grid_pos].append(obj)
|
||||||
|
|
||||||
|
func get_nearby_objects(pos: Vector2) -> Array:
|
||||||
|
var grid_pos = Vector2(
|
||||||
|
int(pos.x / grid_size),
|
||||||
|
int(pos.y / grid_size)
|
||||||
|
)
|
||||||
|
|
||||||
|
var nearby = []
|
||||||
|
for x in range(-1, 2):
|
||||||
|
for y in range(-1, 2):
|
||||||
|
var check_pos = grid_pos + Vector2(x, y)
|
||||||
|
if grid.has(check_pos):
|
||||||
|
nearby.append_array(grid[check_pos])
|
||||||
|
|
||||||
|
return nearby
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🌐 网络优化
|
||||||
|
|
||||||
|
### 数据压缩
|
||||||
|
```gdscript
|
||||||
|
# NetworkManager.gd
|
||||||
|
func send_player_data(data: Dictionary):
|
||||||
|
# 只发送变化的数据
|
||||||
|
var delta_data = get_changed_data(data)
|
||||||
|
|
||||||
|
# 压缩数据
|
||||||
|
var compressed = compress_data(delta_data)
|
||||||
|
|
||||||
|
# 发送数据
|
||||||
|
send_to_server(compressed)
|
||||||
|
|
||||||
|
func compress_data(data: Dictionary) -> PackedByteArray:
|
||||||
|
var json_string = JSON.stringify(data)
|
||||||
|
var bytes = json_string.to_utf8_buffer()
|
||||||
|
return bytes.compress(FileAccess.COMPRESSION_GZIP)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 批量更新
|
||||||
|
```gdscript
|
||||||
|
# 批量发送网络更新
|
||||||
|
var pending_updates: Array = []
|
||||||
|
var update_timer: float = 0.0
|
||||||
|
|
||||||
|
func _process(delta):
|
||||||
|
update_timer += delta
|
||||||
|
if update_timer >= 0.05: # 每50ms发送一次
|
||||||
|
if pending_updates.size() > 0:
|
||||||
|
send_batch_updates(pending_updates)
|
||||||
|
pending_updates.clear()
|
||||||
|
update_timer = 0.0
|
||||||
|
|
||||||
|
func queue_update(update_data: Dictionary):
|
||||||
|
pending_updates.append(update_data)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 性能监控
|
||||||
|
|
||||||
|
### FPS监控
|
||||||
|
```gdscript
|
||||||
|
# FPSMonitor.gd
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
@onready var fps_label: Label = $FPSLabel
|
||||||
|
var fps_history: Array = []
|
||||||
|
|
||||||
|
func _process(delta):
|
||||||
|
var current_fps = Engine.get_frames_per_second()
|
||||||
|
fps_history.append(current_fps)
|
||||||
|
|
||||||
|
if fps_history.size() > 60: # 保留60帧历史
|
||||||
|
fps_history.pop_front()
|
||||||
|
|
||||||
|
var avg_fps = 0
|
||||||
|
for fps in fps_history:
|
||||||
|
avg_fps += fps
|
||||||
|
avg_fps /= fps_history.size()
|
||||||
|
|
||||||
|
fps_label.text = "FPS: %d (平均: %d)" % [current_fps, avg_fps]
|
||||||
|
|
||||||
|
# FPS过低时警告
|
||||||
|
if avg_fps < 30:
|
||||||
|
modulate = Color.RED
|
||||||
|
elif avg_fps < 50:
|
||||||
|
modulate = Color.YELLOW
|
||||||
|
else:
|
||||||
|
modulate = Color.WHITE
|
||||||
|
```
|
||||||
|
|
||||||
|
### 性能分析器
|
||||||
|
```gdscript
|
||||||
|
# Profiler.gd
|
||||||
|
class_name Profiler
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
var timers: Dictionary = {}
|
||||||
|
|
||||||
|
func start_timer(name: String):
|
||||||
|
timers[name] = Time.get_time_dict_from_system()
|
||||||
|
|
||||||
|
func end_timer(name: String) -> float:
|
||||||
|
if not timers.has(name):
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
var start_time = timers[name]
|
||||||
|
var end_time = Time.get_time_dict_from_system()
|
||||||
|
|
||||||
|
var duration = (end_time.hour * 3600 + end_time.minute * 60 + end_time.second) - \
|
||||||
|
(start_time.hour * 3600 + start_time.minute * 60 + start_time.second)
|
||||||
|
|
||||||
|
timers.erase(name)
|
||||||
|
return duration
|
||||||
|
|
||||||
|
# 使用示例
|
||||||
|
func expensive_function():
|
||||||
|
Profiler.start_timer("expensive_function")
|
||||||
|
|
||||||
|
# 执行耗时操作
|
||||||
|
for i in range(10000):
|
||||||
|
pass
|
||||||
|
|
||||||
|
var duration = Profiler.end_timer("expensive_function")
|
||||||
|
print("函数执行时间: ", duration, "秒")
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛠️ 调试工具
|
||||||
|
|
||||||
|
### 性能调试面板
|
||||||
|
```gdscript
|
||||||
|
# DebugPanel.gd
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
@onready var memory_label: Label = $VBox/MemoryLabel
|
||||||
|
@onready var fps_label: Label = $VBox/FPSLabel
|
||||||
|
@onready var objects_label: Label = $VBox/ObjectsLabel
|
||||||
|
|
||||||
|
func _process(delta):
|
||||||
|
# 更新内存使用
|
||||||
|
var memory = OS.get_static_memory_usage_by_type()
|
||||||
|
memory_label.text = "内存: %.1f MB" % (memory / 1024.0 / 1024.0)
|
||||||
|
|
||||||
|
# 更新FPS
|
||||||
|
fps_label.text = "FPS: %d" % Engine.get_frames_per_second()
|
||||||
|
|
||||||
|
# 更新对象数量
|
||||||
|
var object_count = get_tree().get_node_count()
|
||||||
|
objects_label.text = "对象数: %d" % object_count
|
||||||
|
```
|
||||||
|
|
||||||
|
### 热点分析
|
||||||
|
```gdscript
|
||||||
|
# HotspotAnalyzer.gd
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
var function_calls: Dictionary = {}
|
||||||
|
var function_times: Dictionary = {}
|
||||||
|
|
||||||
|
func profile_function(func_name: String, callable: Callable):
|
||||||
|
var start_time = Time.get_time_dict_from_system()
|
||||||
|
|
||||||
|
callable.call()
|
||||||
|
|
||||||
|
var end_time = Time.get_time_dict_from_system()
|
||||||
|
var duration = (end_time.hour * 3600 + end_time.minute * 60 + end_time.second) - \
|
||||||
|
(start_time.hour * 3600 + start_time.minute * 60 + start_time.second)
|
||||||
|
|
||||||
|
if not function_calls.has(func_name):
|
||||||
|
function_calls[func_name] = 0
|
||||||
|
function_times[func_name] = 0.0
|
||||||
|
|
||||||
|
function_calls[func_name] += 1
|
||||||
|
function_times[func_name] += duration
|
||||||
|
|
||||||
|
func print_profile_report():
|
||||||
|
print("=== 性能分析报告 ===")
|
||||||
|
for func_name in function_calls.keys():
|
||||||
|
var calls = function_calls[func_name]
|
||||||
|
var total_time = function_times[func_name]
|
||||||
|
var avg_time = total_time / calls
|
||||||
|
|
||||||
|
print("%s: 调用%d次, 总时间%.3fs, 平均%.3fs" % [func_name, calls, total_time, avg_time])
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📚 最佳实践
|
||||||
|
|
||||||
|
### 开发阶段
|
||||||
|
1. **早期优化** - 在设计阶段考虑性能
|
||||||
|
2. **渐进开发** - 逐步添加功能并测试性能
|
||||||
|
3. **定期测试** - 定期进行性能测试
|
||||||
|
4. **文档记录** - 记录性能优化决策
|
||||||
|
|
||||||
|
### 测试阶段
|
||||||
|
1. **多设备测试** - 在不同性能设备上测试
|
||||||
|
2. **压力测试** - 测试极限情况下的性能
|
||||||
|
3. **长时间测试** - 测试内存泄漏和性能衰减
|
||||||
|
4. **用户测试** - 收集真实用户的性能反馈
|
||||||
|
|
||||||
|
### 发布阶段
|
||||||
|
1. **性能监控** - 监控线上性能指标
|
||||||
|
2. **快速响应** - 快速修复性能问题
|
||||||
|
3. **持续优化** - 根据数据持续优化
|
||||||
|
4. **版本对比** - 对比不同版本的性能表现
|
||||||
|
|
||||||
|
## 🔍 常见问题
|
||||||
|
|
||||||
|
### Q: 如何识别性能瓶颈?
|
||||||
|
A: 使用Godot的内置分析器,添加自定义性能监控,分析FPS和内存使用情况。
|
||||||
|
|
||||||
|
### Q: 内存使用过高怎么办?
|
||||||
|
A: 检查资源加载,使用对象池,及时释放不需要的对象,优化纹理大小。
|
||||||
|
|
||||||
|
### Q: 如何优化大量对象的渲染?
|
||||||
|
A: 使用MultiMesh批处理,实现视锥剔除,使用LOD系统,合并相同材质的对象。
|
||||||
|
|
||||||
|
### Q: 网络延迟如何优化?
|
||||||
|
A: 减少网络请求频率,压缩传输数据,使用预测和插值,实现客户端预测。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**记住:性能优化是一个持续的过程,需要在开发的各个阶段都保持关注!**
|
||||||
617
docs/scene_design.md
Normal file
617
docs/scene_design.md
Normal file
@@ -0,0 +1,617 @@
|
|||||||
|
# 场景设计规范
|
||||||
|
|
||||||
|
本文档定义了 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动画实现过渡效果。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**记住:良好的场景设计是游戏体验的基础!**
|
||||||
Reference in New Issue
Block a user