From fca3eb79ddf844cae2f2f0984b00c2805640a033 Mon Sep 17 00:00:00 2001 From: moyin <2443444649@qq.com> Date: Fri, 2 Jan 2026 00:57:53 +0800 Subject: [PATCH 1/6] =?UTF-8?q?chore=EF=BC=9A=E6=B8=85=E7=90=86=E4=B8=B4?= =?UTF-8?q?=E6=97=B6=E6=96=87=E6=A1=A3=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除迁移完成标记文件 MIGRATION_COMPLETE.md - 删除重构说明文件 REFACTORING.md - 删除结构对比文件 STRUCTURE_COMPARISON.md - 删除临时文档 cloude.md --- MIGRATION_COMPLETE.md | 235 ---------------------------------------- REFACTORING.md | 222 ------------------------------------- STRUCTURE_COMPARISON.md | 213 ------------------------------------ cloude.md | 123 --------------------- 4 files changed, 793 deletions(-) delete mode 100644 MIGRATION_COMPLETE.md delete mode 100644 REFACTORING.md delete mode 100644 STRUCTURE_COMPARISON.md delete mode 100644 cloude.md diff --git a/MIGRATION_COMPLETE.md b/MIGRATION_COMPLETE.md deleted file mode 100644 index c4b7123..0000000 --- a/MIGRATION_COMPLETE.md +++ /dev/null @@ -1,235 +0,0 @@ -# ✅ 项目结构重构完成报告 - -## 📅 完成时间 -2025-12-31 - -## 🎉 重构成功 - -项目已成功从混乱的文件结构重构为清晰、模块化的架构! - ---- - -## 📊 最终结构总览 - -``` -whale-town-front/ -├── _Core/ ✅ [框架层] 全局单例和系统 -├── Scenes/ ✅ [玩法层] 游戏世界实体 -├── UI/ ✅ [界面层] 所有UI界面 -├── Assets/ ✅ [资源层] 美术资源 -├── Config/ ✅ [配置层] 静态数据 -├── Utils/ ✅ [工具层] 工具类 -├── Tests/ ✅ [测试层] 测试脚本 -└── docs/ 📄 项目文档 -``` - ---- - -## ✅ 已完成的迁移 - -### 1️⃣ 框架层 (_Core/) -- ✅ `GameManager.gd` → `_Core/managers/` -- ✅ `SceneManager.gd` → `_Core/managers/` (已更新路径) -- ✅ `NetworkManager.gd` → `_Core/managers/` -- ✅ `ResponseHandler.gd` → `_Core/managers/` -- ✅ `EventSystem.gd` → `_Core/systems/` - -### 2️⃣ 场景层 (Scenes/) -- ✅ `scenes/main_scene.tscn` → `Scenes/Maps/main_scene.tscn` -- ✅ `scripts/scenes/MainScene.gd` → `Scenes/Maps/MainScene.gd` -- ✅ `scenes/prefabs/` → `Scenes/Components/` - -### 3️⃣ 界面层 (UI/) -- ✅ `scenes/auth_scene.tscn` → `UI/Windows/LoginWindow.tscn` -- ✅ `scripts/scenes/AuthScene.gd` → `UI/Windows/AuthScene.gd` -- ✅ `assets/ui/chinese_theme.tres` → `UI/Theme/MainTheme.tres` -- ✅ `assets/fonts/` → `UI/Theme/Fonts/` - -### 4️⃣ 配置层 (Config/) -- ✅ `data/configs/game_config.json` → `Config/game_config.json` -- ✅ `data/localization/zh_CN.json` → `Config/zh_CN.json` - -### 5️⃣ 工具层 (Utils/) -- ✅ `core/utils/StringUtils.gd` → `Utils/StringUtils.gd` - -### 6️⃣ 资源层 (Assets/) -- ✅ `assets/sprites/` → `Assets/Sprites/` -- ✅ `assets/audio/` → `Assets/Audio/` -- ✅ 其他资源文件保留在 `assets/`(待后续整理) - -### 7️⃣ 构建脚本 -- ✅ `scripts/build_web.sh` → `./build_web.sh` -- ✅ `scripts/serve_web.sh` → `./serve_web.sh` - ---- - -## 🗑️ 已删除的旧目录 - -- ❌ `core/` - 已迁移到 `_Core/` -- ❌ `module/` - 空目录,未使用 -- ❌ `scripts/` - 脚本已内联到场景目录 -- ❌ `scenes/` - 已迁移到 `Scenes/` 和 `UI/` -- ❌ `data/` - 配置已移至 `Config/` - ---- - -## 🔧 已更新的配置 - -### project.godot -```ini -✅ run/main_scene="res://Scenes/Maps/main_scene.tscn" -✅ GameManager="*res://_Core/managers/GameManager.gd" -✅ SceneManager="*res://_Core/managers/SceneManager.gd" -✅ EventSystem="*res://_Core/systems/EventSystem.gd" -✅ NetworkManager="*res://_Core/managers/NetworkManager.gd" -✅ ResponseHandler="*res://_Core/managers/ResponseHandler.gd" -``` - -### SceneManager.gd -```gdscript -✅ "main": "res://Scenes/Maps/main_scene.tscn" -✅ "auth": "res://UI/Windows/LoginWindow.tscn" -``` - -### 测试文件 -```gdscript -✅ tests/auth/enhanced_toast_test.gd - 已更新脚本路径 -✅ tests/auth/auth_ui_test.tscn - 已更新场景路径 -``` - ---- - -## 📚 创建的文档 - -1. **REFACTORING.md** - 详细的重构文档 - - 迁移映射表 - - 设计原则 - - 注意事项 - - 后续建议 - -2. **STRUCTURE_COMPARISON.md** - 结构对比分析 - - 旧结构问题分析 - - 新结构优势说明 - - 对比表格 - - 团队协作改进 - ---- - -## 🎯 关键改进 - -### 清晰的分层 -- **_Core**: 框架代码,全局系统 -- **Scenes**: 游戏世界,地图和实体 -- **UI**: 所有界面,HUD和弹窗 -- **Config**: 静态数据,策划可编辑 -- **Utils**: 通用工具函数库 - -### 组件化设计 -```gdscript -Scenes/Components/ // 可复用组件 -├── characters/ // 角色预制体 -├── effects/ // 特效预制体 -├── items/ // 物品预制体 -└── ui/ // UI预制体 -``` - -### 场景内聚 -- 每个 `.tscn` 配套一个 `.gd` -- 脚本紧邻场景文件 -- 符合 Godot 原生习惯 - -### UI 独立化 -``` -UI/ -├── Windows/ // 模态窗口(登录、设置) -├── HUD/ // 常驻界面(聊天框) -├── Dialog/ // 对话系统 -└── Theme/ // 全局样式 -``` - ---- - -## ⚠️ 后续步骤 - -### 必做事项 -- [ ] 在 Godot 编辑器中打开项目,让编辑器重新索引文件 -- [ ] 测试主场景加载: `Scenes/Maps/main_scene.tscn` -- [ ] 验证登录窗口: `UI/Windows/LoginWindow.tscn` -- [ ] 测试所有自动加载脚本 -- [ ] 运行完整测试套件 - -### 建议优化 -- [ ] 补充 `Scenes/Components/` 下的可复用组件 -- [ ] 完善 `UI/HUD/` 和 `UI/Dialog/` -- [ ] 添加 `Scenes/Entities/Player/` 玩家实体 -- [ ] 将硬编码数值移至 `Config/` - -### 代码审查 -- [ ] 检查是否还有硬编码的旧路径 -- [ ] 验证所有 `.import` 文件正常 -- [ ] 确认网络连接功能正常 -- [ ] 验证 UI 主题显示正确 - ---- - -## 🎓 团队协作指南 - -### 工作目录划分 -``` -🎨 美术组 → Assets/Sprites/, Assets/Audio/ -📋 策划组 → Config/ -💻 前端程序 → UI/, Scenes/Entities/ -⚙️ 后端程序 → _Core/, Utils/ -🧪 测试组 → Tests/ -``` - -### Git 提交建议 -```bash -# 按目录分类提交 -git add _Core/ -git commit -m "refactor: 迁移核心框架代码到 _Core/" - -git add Scenes/ -git commit -m "refactor: 重组场景文件到 Scenes/" - -git add UI/ -git commit -m "refactor: 独立 UI 界面到 UI/" -``` - ---- - -## 📈 预期收益 - -### 可维护性提升 -- 🟢 目录职责清晰,降低认知负担 -- 🟢 新人快速定位文件 -- 🟢 减少代码冲突 - -### 开发效率提升 -- 🟢 组件复用更容易 -- 🟢 团队协作更流畅 -- 🟢 代码审查更高效 - -### 符合最佳实践 -- ✅ Godot 官方推荐结构 -- ✅ 场景内聚原则 -- ✅ 组件化设计思想 -- ✅ 配置与代码分离 - ---- - -## 🎉 总结 - -**重构完成!** 项目现在拥有: -- ✅ 清晰的 6 层架构 -- ✅ 符合 Godot 最佳实践 -- ✅ 易于维护和扩展 -- ✅ 团队协作友好 - -感谢您的耐心!如有问题,请查看详细文档: -- [REFACTORING.md](./REFACTORING.md) - 重构详情 -- [STRUCTURE_COMPARISON.md](./STRUCTURE_COMPARISON.md) - 结构对比 - ---- - -**下一步:在 Godot 编辑器中打开项目并测试!** 🚀 diff --git a/REFACTORING.md b/REFACTORING.md deleted file mode 100644 index ecad018..0000000 --- a/REFACTORING.md +++ /dev/null @@ -1,222 +0,0 @@ -# 项目结构重构文档 - -## 📅 重构时间 -2025-12-31 - -## 🎯 重构目标 -将项目从混乱的文件结构重构为清晰、模块化的架构,采用 Godot 最佳实践。 - -## 📊 重构前后对比 - -### 旧结构问题 -``` -❌ core/ - 概念模糊,混合了框架和业务逻辑 -❌ module/ - 空壳目录,设计未落地 -❌ scripts/ - 与 scenes/ 重复,脚本分散 -❌ scenes/ - 场景、预制体、脚本混在一起 -❌ data/ - 配置和运行时数据不分 -``` - -### 新结构优势 -``` -✅ _Core/ - 框架层:全局单例和系统 -✅ Scenes/ - 玩法层:按游戏世界组织 -✅ UI/ - 界面层:独立的UI管理 -✅ Assets/ - 资源层:纯美术资源 -✅ Config/ - 配置层:静态数据 -✅ Utils/ - 工具层:通用函数库 -``` - -## 🏗️ 新的目录结构 - -``` -res:// -├── _Core/ # [框架层] 游戏的底层框架,单例,全局管理器 -│ ├── managers/ # 游戏管理器 -│ │ ├── GameManager.gd # 游戏状态管理 -│ │ ├── SceneManager.gd # 场景管理(已更新路径) -│ │ ├── NetworkManager.gd # 网络通信 -│ │ └── ResponseHandler.gd # API响应处理 -│ ├── systems/ # 核心系统 -│ │ └── EventSystem.gd # 事件系统 -│ └── singletons/ # 其他单例(待扩展) -│ -├── Scenes/ # [玩法层] 具体的游戏场景、实体、地图 -│ ├── Maps/ # 地图场景 -│ │ └── main_scene.tscn # 主游戏场景 -│ ├── Entities/ # 游戏实体 -│ │ ├── Player/ # 玩家实体 -│ │ ├── NPC/ # NPC实体 -│ │ └── Interactables/ # 交互物 -│ └── Components/ # 可复用组件 -│ -├── UI/ # [界面层] 所有UI相关的预制体和逻辑 -│ ├── HUD/ # 抬头显示(常驻) -│ ├── Windows/ # 模态窗口 -│ │ └── LoginWindow.tscn # 登录窗口(原auth_scene.tscn) -│ ├── Dialog/ # 对话系统 -│ └── Theme/ # 全局样式 -│ ├── MainTheme.tres # 主主题 -│ └── Fonts/ # 字体文件 -│ -├── Assets/ # [资源层] 美术、音频、字体 -│ ├── Sprites/ # 精灵图 -│ ├── Audio/ # 音频 -│ └── Fonts/ # 字体 -│ -├── Config/ # [配置层] 游戏配置文件 -│ ├── game_config.json # 游戏配置 -│ └── zh_CN.json # 中文本地化 -│ -├── Utils/ # [工具层] 通用辅助脚本 -│ └── StringUtils.gd # 字符串工具 -│ -└── Tests/ # [测试层] 单元测试脚本 - ├── api/ # API测试 - ├── auth/ # 认证测试 - ├── integration/ # 集成测试 - ├── performance/ # 性能测试 - └── unit/ # 单元测试 -``` - -## 🔄 迁移映射表 - -| 旧路径 | 新路径 | 说明 | -|--------|--------|------| -| `core/managers/*` | `_Core/managers/` | 框架层管理器 | -| `core/systems/*` | `_Core/systems/` | 框架层系统 | -| `core/utils/*` | `Utils/` | 工具类 | -| `scenes/main_scene.tscn` | `Scenes/Maps/main_scene.tscn` | 主游戏场景 | -| `scenes/auth_scene.tscn` | `UI/Windows/LoginWindow.tscn` | 登录窗口 | -| `data/configs/*.json` | `Config/` | 配置文件 | -| `data/localization/*.json` | `Config/` | 本地化配置 | -| `assets/ui/chinese_theme.tres` | `UI/Theme/MainTheme.tres` | UI主题 | -| `assets/fonts/*` | `UI/Theme/Fonts/` | 字体文件 | - -## ✂️ 已删除的目录 - -- ❌ `core/` - 已迁移到 `_Core/` -- ❌ `module/` - 空目录,未使用 -- ❌ `scripts/` - 脚本应内联到场景目录 -- ❌ `scenes/` - 已迁移到 `Scenes/` 和 `UI/` -- ❌ `data/` - 配置已移至 `Config/` - -## 🔧 已更新的配置文件 - -### project.godot -```ini -# 更新主场景路径 -run/main_scene="res://Scenes/Maps/main_scene.tscn" - -# 更新自动加载路径 -GameManager="*res://_Core/managers/GameManager.gd" -SceneManager="*res://_Core/managers/SceneManager.gd" -EventSystem="*res://_Core/systems/EventSystem.gd" -NetworkManager="*res://_Core/managers/NetworkManager.gd" -ResponseHandler="*res://_Core/managers/ResponseHandler.gd" -``` - -### SceneManager.gd -```gdscript -# 更新场景路径映射 -var scene_paths: Dictionary = { - "main": "res://Scenes/Maps/main_scene.tscn", - "auth": "res://UI/Windows/LoginWindow.tscn", - # ... 其他场景路径 -} -``` - -### 测试文件 -- `tests/auth/enhanced_toast_test.gd` - 已更新脚本路径 -- `tests/auth/auth_ui_test.tscn` - 已更新场景路径 - -## 🎨 设计原则 - -### 1. 清晰的分层 -- **_Core**: 框架代码,与具体游戏逻辑无关 -- **Scenes**: 游戏世界,地图和实体 -- **UI**: 所有界面,HUD和弹窗 -- **Config**: 静态数据,策划可编辑 - -### 2. 组件化设计 -```gdscript -# 可复用组件放在 Scenes/Components/ -Scenes/Components/ -├── InteractableArea.tscn # 让任何物体"可交互" -├── MovementSync.gd # 网络位置同步 -└── NameTag3D.tscn # 头顶名字条 -``` - -### 3. 场景内聚 -- 每个 .tscn 配套一个 .gd -- 脚本紧邻场景文件存放 -- 符合 Godot 原生开发习惯 - -### 4. 职责单一 -``` -UI/Windows/ - 模态窗口(登录、设置、商店) -UI/HUD/ - 常驻界面(聊天框、状态栏) -UI/Dialog/ - 对话系统 -``` - -## 🚀 后续建议 - -### 待完善的功能 -1. **组件化开发** - - 创建 `Scenes/Components/` 下的可复用组件 - - 使用组合优于继承的设计模式 - -2. **UI 独立化** - - 补充 `UI/HUD/` 下的常驻界面 - - 创建 `UI/Dialog/` 对话系统 - -3. **场景管理** - - 补充更多地图场景到 `Scenes/Maps/` - - 添加玩家实体到 `Scenes/Entities/Player/` - -4. **配置驱动** - - 将硬编码的数值移到 `Config/` - - 使用 Resource 文件管理游戏数据 - -### 团队协作 -- **美术组**: 主要在 `Assets/` 工作 -- **策划组**: 主要在 `Config/` 工作 -- **程序组**: 主要在 `_Core/`, `Scenes/`, `UI/` 工作 -- **测试组**: 主要在 `Tests/` 工作 - -## 📝 迁移检查清单 - -- [x] 创建新的目录结构 -- [x] 迁移核心管理器 -- [x] 迁移工具类 -- [x] 迁移场景文件 -- [x] 分离 UI 界面 -- [x] 迁移配置文件 -- [x] 重组资源文件 -- [x] 更新 project.godot -- [x] 更新路径引用 -- [x] 清理旧目录 -- [ ] 在 Godot 编辑器中测试场景加载 -- [ ] 验证所有自动加载脚本正常工作 -- [ ] 测试网络连接功能 -- [ ] 验证 UI 主题显示 - -## ⚠️ 注意事项 - -1. **场景引用更新**: 所有旧场景的引用都已更新,但建议在 Godot 编辑器中重新打开项目,让编辑器重新索引文件 - -2. **.import 文件**: 移动资源文件后,Godot 可能会重新生成 .import 文件,这是正常的 - -3. **版本控制**: 如果使用 Git,旧文件的删除会在下次提交时体现 - -4. **测试覆盖**: 迁移后建议运行完整的测试套件确保功能正常 - -## 🎓 参考资料 - -- [Godot 官方项目组织建议](https://docs.godotengine.org/en/stable/tutorials/best_practices/project_organization.html) -- [GDScript 场景组织](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_basics.html#scenes-and-scripts) -- [ECS 架构模式](https://github.com/SmitUS/Pine-Tree-ECS-For-Godot-4) - ---- - -**重构完成!项目现在拥有清晰的架构,易于维护和扩展。** 🎉 diff --git a/STRUCTURE_COMPARISON.md b/STRUCTURE_COMPARISON.md deleted file mode 100644 index 37001d3..0000000 --- a/STRUCTURE_COMPARISON.md +++ /dev/null @@ -1,213 +0,0 @@ -# 🏗️ 项目结构对比 - -## 旧结构 ❌ -``` -whale-town-front/ -├── core/ # ❌ 概念模糊 -│ ├── managers/ # - 框架代码? -│ ├── systems/ # - 还是业务逻辑? -│ └── utils/ # - 边界不清 -├── module/ # ❌ 空壳目录(无 .gd 文件) -│ ├── Character/ -│ ├── Combat/ -│ ├── Dialogue/ -│ ├── Inventory/ -│ └── UI/ -├── scenes/ # ❌ 混乱的组织 -│ ├── auth_scene.tscn -│ ├── main_scene.tscn -│ ├── Components/ -│ ├── Entities/ -│ ├── Maps/ -│ └── prefabs/ -├── scripts/ # ❌ 与 scenes/ 重复 -│ ├── characters/ -│ ├── scenes/ -│ ├── ui/ -│ └── network/ -├── data/ # ❌ 配置和数据混在一起 -│ ├── configs/ -│ ├── characters/ -│ ├── dialogues/ -│ └── localization/ -├── assets/ # ✅ 相对清晰 -│ ├── audio/ -│ ├── fonts/ -│ ├── icon/ -│ ├── materials/ -│ ├── shaders/ -│ ├── sprites/ -│ └── ui/ -├── tests/ # ✅ 结构良好 -│ ├── api/ -│ ├── auth/ -│ ├── integration/ -│ ├── performance/ -│ └── unit/ -└── docs/ -``` - -**问题总结:** -1. 🔴 脚本分散:`scripts/` 和 `scenes/` 都有脚本,职责不清 -2. 🔴 空壳模块:`module/` 目录存在但无实际代码 -3. 🔴 场景混乱:场景文件、预制体、脚本平级放置 -4. 🔴 分层不明:`core/`, `module/`, `scripts/` 三层交叉 -5. 🔴 数据混杂:`data/` 既包含配置也包含运行时数据 - ---- - -## 新结构 ✅ -``` -whale-town-front/ -├── _Core/ # ✅ 框架层 - 清晰的单例和系统 -│ ├── managers/ # - 全局管理器 -│ ├── systems/ # - 核心系统 -│ └── singletons/ # - 其他单例 -│ -├── Scenes/ # ✅ 玩法层 - 按游戏世界组织 -│ ├── Maps/ # - 地图场景 -│ │ └── main_scene.tscn -│ ├── Entities/ # - 游戏实体 -│ │ ├── Player/ # - 玩家 -│ │ ├── NPC/ # - NPC -│ │ └── Interactables/ # - 交互物 -│ └── Components/ # - 可复用组件 -│ -├── UI/ # ✅ 界面层 - 独立的UI管理 -│ ├── HUD/ # - 抬头显示(常驻) -│ ├── Windows/ # - 模态窗口 -│ │ └── LoginWindow.tscn # (原 auth_scene) -│ ├── Dialog/ # - 对话系统 -│ └── Theme/ # - 全局样式 -│ ├── MainTheme.tres -│ └── Fonts/ -│ -├── Assets/ # ✅ 资源层 - 纯美术资源 -│ ├── Sprites/ # - 精灵图 -│ ├── Audio/ # - 音频 -│ └── Fonts/ # - 字体 -│ -├── Config/ # ✅ 配置层 - 静态数据 -│ ├── game_config.json -│ └── zh_CN.json -│ -├── Utils/ # ✅ 工具层 - 通用函数库 -│ └── StringUtils.gd -│ -├── Tests/ # ✅ 测试层 - 完整的测试覆盖 -│ ├── api/ -│ ├── auth/ -│ ├── integration/ -│ ├── performance/ -│ └── unit/ -│ -└── docs/ # 📄 项目文档 - └── web_deployment_guide.md -``` - -**改进总结:** -1. 🟢 **分层清晰**: 框架、玩法、界面、资源、配置、工具各司其职 -2. 🟢 **场景内聚**: .tscn 和 .gd 成对出现,逻辑紧耦合场景 -3. 🟢 **UI 独立**: 所有界面统一管理,避免和游戏场景混淆 -4. 🟢 **配置分离**: Config 只存放静态数据,策划可直接编辑 -5. 🟢 **组件化**: Scenes/Components/ 提供可复用的逻辑组件 - ---- - -## 📊 核心改进对比表 - -| 维度 | 旧结构 | 新结构 | 改进效果 | -|:---:|:---:|:---:|:---:| -| **目录层级** | 8层 | 6层 | ✅ 更扁平 | -| **脚本管理** | 分散在2处 | 集中在场景内 | ✅ 内聚性高 | -| **UI 组织** | 混在 scenes/ | 独立 UI/ | ✅ 职责清晰 | -| **框架代码** | core/ 概念模糊 | _Core/ 明确 | ✅ 边界清楚 | -| **配置管理** | data/ 混杂 | Config/ 专职 | ✅ 策划友好 | -| **可维护性** | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ✅ 提升67% | -| **学习曲线** | ⭐⭐ | ⭐⭐⭐⭐ | ✅ 新人友好 | - ---- - -## 🎯 Godot 最佳实践对照 - -### ✅ 符合 Godot 规范 -- [x] 场景脚本内聚(.tscn + .gd 相邻) -- [x] 使用 autoload 全局单例 -- [x] 组件化设计(可复用的 Components/) -- [x] 资源独立管理(Assets/) -- [x] 配置与代码分离(Config/) - -### 🔧 待完善项 -- [ ] 补充 Scenes/Components/ 下的可复用组件 -- [ ] 完善事件系统的使用 -- [ ] 添加 SaveSystem 到 _Core/systems/ -- [ ] 实现资源热重载机制 - ---- - -## 📈 团队协作改进 - -### 角色与目录对应 -``` -┌─────────────────┬─────────────────────────────────┐ -│ 角色 │ 主要工作目录 │ -├─────────────────┼─────────────────────────────────┤ -│ 🎨 美术组 │ Assets/Sprites/, Assets/Audio/ │ -│ 📋 策划组 │ Config/ │ -│ 💻 前端程序 │ UI/, Scenes/Entities/ │ -│ ⚙️ 后端程序 │ _Core/, Utils/ │ -│ 🧪 测试组 │ Tests/ │ -└─────────────────┴─────────────────────────────────┘ -``` - -### 协作优势 -1. **减少冲突**: 不同角色在不同目录工作 -2. **职责清晰**: 每个目录有明确的负责人 -3. **易于审查**: PR 可以按目录分类评审 -4. **快速定位**: 新人快速找到相关文件 - ---- - -## 🚀 扩展性对比 - -### 旧结构的扩展问题 -```gdscript -// 添加新功能时需要在多个地方修改 -module/FeatureName/ // ❌ 需要创建 -scenes/feature_scene/ // ❌ 需要创建 -scripts/feature_logic/ // ❌ 需要创建 -data/feature_config/ // ❌ 需要创建 -``` - -### 新结构的扩展方式 -```gdscript -// 添加新功能只需要: -Scenes/Entities/NewFeature/ // ✅ 场景+逻辑一体 -Config/feature_config.json // ✅ 配置独立 -``` - ---- - -## 📚 参考架构 - -这个新结构参考了业界最佳实践: -- **Godot 官方**: [Project Organization](https://docs.godotengine.org/en/stable/tutorials/best_practices/project_organization.html) -- **Unity 模式**: Assets/Scenes/Scripts 分离 -- **ECS 架构**: Entities + Components 思想 -- **微服务思维**: 按功能域而非技术分层 - ---- - -## 🎓 学习资源 - -如果你是新人,这里有一些学习路径: - -1. **先熟悉目录** → 查看 [REFACTORING.md](./REFACTORING.md) -2. **理解核心系统** → 阅读 `_Core/systems/EventSystem.gd` -3. **学习场景管理** → 查看 `_Core/managers/SceneManager.gd` -4. **研究 UI 结构** → 打开 `UI/Windows/LoginWindow.tscn` -5. **运行测试** → 执行 `Tests/` 下的测试用例 - ---- - -**结论:新结构更加清晰、模块化、易于维护,符合 Godot 最佳实践!** 🎉 diff --git a/cloude.md b/cloude.md deleted file mode 100644 index 77d72b9..0000000 --- a/cloude.md +++ /dev/null @@ -1,123 +0,0 @@ -# 🎯 WhaleTown 项目开发规范 - -## 1. 项目愿景与背景 -- **项目名称**: "WhaleTown" - 一个2D俯视角像素风RPG游戏 -- **引擎**: Godot 4.2+ (严格禁止使用Godot 3.x语法) -- **架构**: 严格分层架构:`_Core`(框架层)、`Scenes`(游戏层)、`UI`(界面层) -- **核心原则**: "信号向上,调用向下"。通过`EventSystem`实现高度解耦 - -## 2. 🛠 命令参考与设置 -- **输入映射 (必需配置)**: - - `move_left`, `move_right`, `move_up`, `move_down` (WASD / 方向键) - - `interact` (E键 / 空格键) - - `pause` (ESC键) -- **运行游戏**: `godot --path .` -- **运行测试 (GUT)**: `godot --headless -s addons/gut/gut_cmdline.gd -gdir=res://tests/ -ginclude_subdirs` -- **初始化结构**: `mkdir -p _Core/managers _Core/systems Scenes/Maps Scenes/Entities Scenes/Components UI/Windows UI/HUD Assets/Sprites tests/unit tests/integration` - -## 3. 📂 文件路径规则 (严格小写) -*注意:根目录文件夹必须小写。脚本和场景必须放在一起。* -- **核心管理器**: `_Core/managers/[Name].gd` -- **核心系统**: `_Core/systems/[Name].gd` -- **实体**: `Scenes/Entities/[EntityName]/[EntityName].tscn` (脚本`.gd`放在同一文件夹) -- **地图**: `Scenes/Maps/[map_name].tscn` -- **组件**: `Scenes/Components/[ComponentName].gd` (可复用逻辑节点) -- **UI窗口**: `UI/Windows/[WindowName].tscn` -- **测试**: `tests/[unit|integration]/test_[name].gd` (文件夹名为小写`tests`) - -## 4. 📋 编码标准 (必须遵守) -- **类型安全**: 始终使用严格静态类型:`var speed: float = 100.0`, `func _ready() -> void` -- **命名约定**: - - 每个脚本顶部必须有`class_name PascalCase` - - 变量/函数:`snake_case`。常量:`SCREAMING_SNAKE_CASE` - - 私有成员:使用下划线前缀`_` (例如:`var _health: int`) -- **节点访问**: 对UI和内部场景组件使用`%UniqueName` -- **信号**: 使用"信号向上,调用向下"原则。父节点调用子节点方法;子节点发出信号 -- **禁止模式**: - - ❌ 禁止使用`yield()` -> 使用`await` - - ❌ 禁止在`_process`中使用`get_node()` -> 使用`@onready`缓存 - - ❌ 禁止线性过滤 -> 所有Sprite2D/TileMap资源必须使用**最近邻**过滤 - -## 5. 🏛 架构与通信 -- **事件系统**: 使用`_Core/systems/EventSystem.gd`进行跨模块消息传递 -- **事件注册**: 在`_Core/EventNames.gd`中使用`class_name EventNames` - ```gdscript - class_name EventNames - const PLAYER_MOVED = "player_moved" - const INTERACT_PRESSED = "interact_pressed" - const NPC_TALKED = "npc_talked" - ``` -- **单例**: 只允许GameManager、SceneManager、EventSystem作为自动加载 -- **解耦**: 底层实体不得直接引用GameManager。使用事件系统 - -## 6. 🏗 实现细节 -- **玩家**: 使用CharacterBody2D。必须包含Camera2D,设置`position_smoothing_enabled = true` -- **NPC/交互物**: 使用名为InteractionArea的Area2D。通过EventSystem触发 -- **TileMap图层**: - - 图层0:地面 (无碰撞) - - 图层1:障碍物 (启用物理层) - - 图层2:装饰 (启用Y排序) -- **相机**: 必须通过`TileMap.get_used_rect()`自动计算边界 - -## 7. 🧪 测试要求 (强制性) -- **覆盖率**: `_Core/`中的每个管理器/系统都必须有GUT测试 -- **命名**: 测试文件必须以`test_`开头并继承`GutTest` -- **示例**: - ```gdscript - extends GutTest - func test_event_emission(): - var sender = Node.new() - watch_signals(EventSystem) - EventSystem.emit_event(EventNames.PLAYER_MOVED, {}) - assert_signal_emitted(EventSystem, "event_raised") - ``` - -## 8. 🧘 开发哲学 -- **流畅体验**: 每个交互(UI弹窗、NPC对话)都必须有Tween动画占位符 -- **零魔法数字**: 所有速度/计时器必须使用`@export`或在`Config/`中定义 -- **简洁性**: 如果一个函数做两件事,就拆分它 -- **隐藏逻辑**: 隐藏的逻辑(如ResponseHandler.gd)必须和HUD一样干净 - -## 9. 📝 代码模板 (实体模式) -```gdscript -extends CharacterBody2D -class_name Player - -# 1. 导出变量与常量 -@export var move_speed: float = 200.0 - -# 2. 节点引用 -@onready var sprite: Sprite2D = %Sprite2D - -# 3. 生命周期 -func _physics_process(delta: float) -> void: - _move(delta) - -# 4. 私有方法 -func _move(_delta: float) -> void: - var dir := Input.get_vector("move_left", "move_right", "move_up", "move_down") - velocity = dir * move_speed - move_and_slide() -``` - -## 10. 🎨 UI设计规范 -- **响应式布局**: 使用Anchor和Margin实现自适应 -- **主题统一**: 所有UI组件使用统一主题资源 -- **动画效果**: 界面切换必须有过渡动画 -- **无障碍支持**: 支持键盘导航 - -## 11. 🔧 性能优化 -- **对象池**: 频繁创建销毁的对象使用对象池 -- **视锥剔除**: 只渲染可见区域的对象 -- **批量处理**: 合并相似的渲染调用 -- **内存管理**: 及时释放不需要的资源 - -## 12. 📚 最佳实践 -- **模块化**: 功能拆分为独立模块 -- **可测试**: 设计易于测试的代码结构 -- **文档化**: 为复杂逻辑添加详细注释 -- **版本控制**: 遵循Git提交规范 - ---- - -**记住:代码质量是游戏成功的基础!** \ No newline at end of file From a18c7a54b158c6a9cd1bd4b93dcaaf2fb682317a Mon Sep 17 00:00:00 2001 From: moyin <2443444649@qq.com> Date: Fri, 2 Jan 2026 00:58:16 +0800 Subject: [PATCH 2/6] =?UTF-8?q?refactor=EF=BC=9A=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E7=BB=93=E6=9E=84=EF=BC=8C=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E6=97=A7=E7=9A=84=E6=96=87=E4=BB=B6=E7=BB=84=E7=BB=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除旧的 UI/Windows/ 目录下的认证相关文件 - 删除旧的 Utils/ 目录下的工具类文件 - 删除旧的 scenes/Components/ 目录结构 - 为新的目录结构让路 --- UI/Windows/AuthScene.gd | 1032 ------------------------- UI/Windows/AuthScene.gd.uid | 1 - UI/Windows/LoginWindow.tscn | 561 -------------- Utils/StringUtils.gd | 199 ----- Utils/StringUtils.gd.uid | 1 - scenes/Components/characters/.gitkeep | 1 - scenes/Components/effects/.gitkeep | 1 - scenes/Components/items/.gitkeep | 1 - scenes/Components/ui/.gitkeep | 1 - 9 files changed, 1798 deletions(-) delete mode 100644 UI/Windows/AuthScene.gd delete mode 100644 UI/Windows/AuthScene.gd.uid delete mode 100644 UI/Windows/LoginWindow.tscn delete mode 100644 Utils/StringUtils.gd delete mode 100644 Utils/StringUtils.gd.uid delete mode 100644 scenes/Components/characters/.gitkeep delete mode 100644 scenes/Components/effects/.gitkeep delete mode 100644 scenes/Components/items/.gitkeep delete mode 100644 scenes/Components/ui/.gitkeep diff --git a/UI/Windows/AuthScene.gd b/UI/Windows/AuthScene.gd deleted file mode 100644 index 4c235c0..0000000 --- a/UI/Windows/AuthScene.gd +++ /dev/null @@ -1,1032 +0,0 @@ -extends Control - -# 信号定义 -signal login_success(username: String) - -# UI节点引用 -@onready var background_image: TextureRect = $BackgroundImage -@onready var login_panel: Panel = $CenterContainer/LoginPanel -@onready var register_panel: Panel = $CenterContainer/RegisterPanel -@onready var title_label: Label = $CenterContainer/LoginPanel/VBoxContainer/TitleLabel -@onready var subtitle_label: Label = $CenterContainer/LoginPanel/VBoxContainer/SubtitleLabel -@onready var whale_frame: TextureRect = $WhaleFrame - -# 登录表单 -@onready var login_username: LineEdit = $CenterContainer/LoginPanel/VBoxContainer/LoginForm/UsernameContainer/UsernameInput -@onready var login_password: LineEdit = $CenterContainer/LoginPanel/VBoxContainer/LoginForm/PasswordContainer/PasswordInput -@onready var login_verification: LineEdit = $CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationInputContainer/VerificationInput -@onready var login_username_error: Label = $CenterContainer/LoginPanel/VBoxContainer/LoginForm/UsernameContainer/UsernameLabelContainer/UsernameError -@onready var login_password_error: Label = $CenterContainer/LoginPanel/VBoxContainer/LoginForm/PasswordContainer/PasswordLabelContainer/PasswordError -@onready var login_verification_error: Label = $CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationLabelContainer/VerificationError -@onready var password_container: VBoxContainer = $CenterContainer/LoginPanel/VBoxContainer/LoginForm/PasswordContainer -@onready var verification_container: VBoxContainer = $CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer -@onready var get_code_btn: Button = $CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationInputContainer/GetCodeBtn -@onready var main_btn: Button = $CenterContainer/LoginPanel/VBoxContainer/MainButton -@onready var login_btn: Button = $CenterContainer/LoginPanel/VBoxContainer/ButtonContainer/LoginBtn -@onready var forgot_password_btn: Button = $CenterContainer/LoginPanel/VBoxContainer/BottomLinks/ForgotPassword -@onready var register_link_btn: Button = $CenterContainer/LoginPanel/VBoxContainer/BottomLinks/RegisterLink - -# 注册表单 -@onready var register_username: LineEdit = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/UsernameContainer/UsernameInput -@onready var register_email: LineEdit = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/EmailContainer/EmailInput -@onready var register_password: LineEdit = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/PasswordContainer/PasswordInput -@onready var register_confirm: LineEdit = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/ConfirmContainer/ConfirmInput -@onready var verification_input: LineEdit = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer/VerificationInputContainer/VerificationInput -@onready var send_code_btn: Button = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer/VerificationInputContainer/SendCodeBtn -@onready var register_btn: Button = $CenterContainer/RegisterPanel/VBoxContainer/ButtonContainer/RegisterBtn -@onready var to_login_btn: Button = $CenterContainer/RegisterPanel/VBoxContainer/ButtonContainer/ToLoginBtn - -# 错误提示标签 -@onready var register_username_error: Label = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/UsernameContainer/UsernameLabelContainer/UsernameError -@onready var register_email_error: Label = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/EmailContainer/EmailLabelContainer/EmailError -@onready var register_password_error: Label = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/PasswordContainer/PasswordLabelContainer/PasswordError -@onready var register_confirm_error: Label = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/ConfirmContainer/ConfirmLabelContainer/ConfirmError -@onready var verification_error: Label = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer/VerificationLabelContainer/VerificationError - -# Toast消息节点 -@onready var toast_container: Control = $ToastContainer - -# Toast管理 -var active_toasts: Array = [] -var toast_counter: int = 0 - -# 验证码状态 -var verification_codes_sent: Dictionary = {} -var code_cooldown: float = 60.0 -var cooldown_timer: Timer = null -var current_email: String = "" - -# 登录模式枚举 -enum LoginMode { - PASSWORD, # 密码登录模式 - VERIFICATION # 验证码登录模式 -} - -# 当前登录模式 -var current_login_mode: LoginMode = LoginMode.PASSWORD - -# 网络请求管理 -var active_request_ids: Array = [] - -func _ready(): - connect_signals() - show_login_panel() - update_login_mode_ui() # 初始化登录模式UI - await get_tree().process_frame - print("认证系统已加载") - test_network_connection() - -func test_network_connection(): - print("=== 测试网络连接 ===") - - var request_id = NetworkManager.get_app_status(_on_network_test_response) - if request_id != "": - active_request_ids.append(request_id) - print("网络测试请求已发送,ID: ", request_id) - else: - print("网络连接测试失败") -func connect_signals(): - # 主要按钮 - main_btn.pressed.connect(_on_main_button_pressed) - - # 登录界面按钮 - login_btn.pressed.connect(_on_login_pressed) - forgot_password_btn.pressed.connect(_on_forgot_password_pressed) - register_link_btn.pressed.connect(_on_register_link_pressed) - get_code_btn.pressed.connect(_on_get_login_code_pressed) - - # 注册界面按钮 - register_btn.pressed.connect(_on_register_pressed) - to_login_btn.pressed.connect(_on_to_login_pressed) - send_code_btn.pressed.connect(_on_send_code_pressed) - - # 回车键登录 - login_password.text_submitted.connect(_on_login_enter) - - # 登录表单失焦验证 - login_username.focus_exited.connect(_on_login_username_focus_exited) - login_password.focus_exited.connect(_on_login_password_focus_exited) - login_verification.focus_exited.connect(_on_login_verification_focus_exited) - - # 注册表单失焦验证 - register_username.focus_exited.connect(_on_register_username_focus_exited) - register_email.focus_exited.connect(_on_register_email_focus_exited) - register_password.focus_exited.connect(_on_register_password_focus_exited) - register_confirm.focus_exited.connect(_on_register_confirm_focus_exited) - verification_input.focus_exited.connect(_on_verification_focus_exited) - - # 实时输入验证 - register_username.text_changed.connect(_on_register_username_text_changed) - register_email.text_changed.connect(_on_register_email_text_changed) - register_password.text_changed.connect(_on_register_password_text_changed) - register_confirm.text_changed.connect(_on_register_confirm_text_changed) - verification_input.text_changed.connect(_on_verification_text_changed) - -func show_login_panel(): - login_panel.visible = true - register_panel.visible = false - login_username.grab_focus() - -func show_register_panel(): - login_panel.visible = false - register_panel.visible = true - register_username.grab_focus() - -# 更新登录模式UI -func update_login_mode_ui(): - if current_login_mode == LoginMode.PASSWORD: - # 密码登录模式 - login_btn.text = "验证码登录" - forgot_password_btn.text = "忘记密码" - - # 显示密码输入框,隐藏验证码输入框 - password_container.visible = true - verification_container.visible = false - - # 清空验证码输入框和错误提示 - login_verification.text = "" - hide_field_error(login_verification_error) - - else: # VERIFICATION mode - # 验证码登录模式 - login_btn.text = "密码登录" - forgot_password_btn.text = "获取验证码" - - # 隐藏密码输入框,显示验证码输入框 - password_container.visible = false - verification_container.visible = true - - # 清空密码输入框和错误提示 - login_password.text = "" - hide_field_error(login_password_error) - # 这里需要根据实际UI结构调整 - -# 切换登录模式 -func toggle_login_mode(): - if current_login_mode == LoginMode.PASSWORD: - current_login_mode = LoginMode.VERIFICATION - else: - current_login_mode = LoginMode.PASSWORD - - update_login_mode_ui() - - # 清空输入框 - login_username.text = "" - login_password.text = "" - hide_field_error(login_username_error) - hide_field_error(login_password_error) - -# ============ 按钮事件处理 ============ - -func _on_main_button_pressed(): - # 根据当前登录模式执行不同的登录逻辑 - if current_login_mode == LoginMode.PASSWORD: - _execute_password_login() - else: - _execute_verification_login() - -func _execute_password_login(): - if not validate_login_form(): - return - - var username = login_username.text.strip_edges() - var password = login_password.text - - show_loading(main_btn, "登录中...") - show_toast('正在验证登录信息...', true) - - var request_id = NetworkManager.login(username, password, _on_login_response) - if request_id != "": - active_request_ids.append(request_id) - else: - restore_button(main_btn, "进入小镇") - show_toast('网络请求失败', false) - -func _execute_verification_login(): - var identifier = login_username.text.strip_edges() - var verification_code = login_verification.text.strip_edges() - - if identifier.is_empty(): - show_field_error(login_username_error, "请输入用户名/手机/邮箱") - login_username.grab_focus() - return - - if verification_code.is_empty(): - show_field_error(login_verification_error, "请输入验证码") - login_verification.grab_focus() - return - - show_loading(main_btn, "登录中...") - show_toast('正在验证验证码...', true) - - var request_id = NetworkManager.verification_code_login(identifier, verification_code, _on_verification_login_response) - if request_id != "": - active_request_ids.append(request_id) - else: - restore_button(main_btn, "进入小镇") - show_toast('网络请求失败', false) - -func _on_login_pressed(): - # 现在这个按钮用于切换登录模式 - toggle_login_mode() - -func _on_register_pressed(): - print("注册按钮被点击") - - if not validate_register_form(): - print("注册表单验证失败") - show_toast('请检查并完善注册信息', false) - return - - print("注册表单验证通过,开始注册流程") - - var username = register_username.text.strip_edges() - var email = register_email.text.strip_edges() - var password = register_password.text - var verification_code = verification_input.text.strip_edges() - - show_loading(register_btn, "注册中...") - show_toast('正在创建账户...', true) - - # 直接调用注册接口,让服务器端处理验证码验证 - send_register_request(username, email, password, verification_code) - -func _on_send_code_pressed(): - var email = register_email.text.strip_edges() - - var email_validation = validate_email(email) - if not email_validation.valid: - show_toast(email_validation.message, false) - register_email.grab_focus() - return - - hide_field_error(register_email_error) - - # 检查冷却时间 - var current_time = Time.get_time_dict_from_system() - var current_timestamp = current_time.hour * 3600 + current_time.minute * 60 + current_time.second - - if verification_codes_sent.has(email): - var email_data = verification_codes_sent[email] - if email_data.sent and (current_timestamp - email_data.time) < code_cooldown: - var remaining = code_cooldown - (current_timestamp - email_data.time) - show_toast('该邮箱请等待 %d 秒后再次发送' % remaining, false) - return - - if current_email != email: - stop_current_cooldown() - current_email = email - - if not verification_codes_sent.has(email): - verification_codes_sent[email] = {} - - verification_codes_sent[email].sent = true - verification_codes_sent[email].time = current_timestamp - start_cooldown_timer(email) - - var request_id = NetworkManager.send_email_verification(email, _on_send_code_response) - if request_id != "": - active_request_ids.append(request_id) - else: - show_toast('网络请求失败', false) - reset_verification_button() - -func _on_register_link_pressed(): - show_register_panel() - -func _on_get_login_code_pressed(): - var identifier = login_username.text.strip_edges() - - if identifier.is_empty(): - show_field_error(login_username_error, "请先输入用户名/手机/邮箱") - login_username.grab_focus() - return - - show_loading(get_code_btn, "发送中...") - show_toast('正在发送登录验证码...', true) - - var request_id = NetworkManager.send_login_verification_code(identifier, _on_send_login_code_response) - if request_id != "": - active_request_ids.append(request_id) - else: - restore_button(get_code_btn, "获取验证码") - show_toast('网络请求失败', false) - -func _on_to_login_pressed(): - show_login_panel() - -func _on_login_enter(_text: String): - _on_login_pressed() - -func _on_forgot_password_pressed(): - var identifier = login_username.text.strip_edges() - - if identifier.is_empty(): - show_toast('请先输入邮箱或手机号', false) - login_username.grab_focus() - return - - if not is_valid_email(identifier) and not is_valid_phone(identifier): - show_toast('请输入有效的邮箱或手机号', false) - login_username.grab_focus() - return - - if current_login_mode == LoginMode.PASSWORD: - # 密码登录模式:发送密码重置验证码 - show_loading(forgot_password_btn, "发送中...") - show_toast('正在发送密码重置验证码...', true) - - var request_id = NetworkManager.forgot_password(identifier, _on_forgot_password_response) - if request_id != "": - active_request_ids.append(request_id) - else: - restore_button(forgot_password_btn, "忘记密码") - show_toast('网络请求失败', false) - else: - # 验证码登录模式:发送登录验证码 - show_loading(forgot_password_btn, "发送中...") - show_toast('正在发送登录验证码...', true) - - var request_id = NetworkManager.send_login_verification_code(identifier, _on_send_login_code_response) - if request_id != "": - active_request_ids.append(request_id) - else: - restore_button(forgot_password_btn, "获取验证码") - show_toast('网络请求失败', false) -# ============ 网络响应处理 ============ - -func send_register_request(username: String, email: String, password: String, verification_code: String = ""): - var request_id = NetworkManager.register(username, password, username, email, verification_code, _on_register_response) - if request_id != "": - active_request_ids.append(request_id) - else: - show_toast('网络请求失败', false) - restore_button(register_btn, "注册") - -func _on_network_test_response(success: bool, data: Dictionary, error_info: Dictionary): - print("=== 网络测试响应处理 ===") - print("成功: ", success) - print("数据: ", data) - - var result = ResponseHandler.handle_network_test_response(success, data, error_info) - - if result.should_show_toast: - show_toast(result.message, result.success) - -func _on_login_response(success: bool, data: Dictionary, error_info: Dictionary): - print("=== 登录响应处理 ===") - print("成功: ", success) - print("数据: ", data) - print("错误信息: ", error_info) - - restore_button(main_btn, "进入小镇") - - var result = ResponseHandler.handle_login_response(success, data, error_info) - - if result.should_show_toast: - show_toast(result.message, result.success) - - if result.success: - var username = login_username.text.strip_edges() - if data.has("data") and data.data.has("user") and data.data.user.has("username"): - username = data.data.user.username - - login_username.text = "" - login_password.text = "" - hide_field_error(login_username_error) - hide_field_error(login_password_error) - - await get_tree().create_timer(1.0).timeout - login_success.emit(username) - -func _on_send_code_response(success: bool, data: Dictionary, error_info: Dictionary): - print("=== 发送验证码响应处理 ===") - print("成功: ", success) - print("数据: ", data) - - var result = ResponseHandler.handle_send_verification_code_response(success, data, error_info) - - if result.should_show_toast: - show_toast(result.message, result.success) - - if not result.success: - reset_verification_button() - -func _on_send_login_code_response(success: bool, data: Dictionary, error_info: Dictionary): - print("=== 发送登录验证码响应处理 ===") - print("成功: ", success) - print("数据: ", data) - - # 恢复按钮状态 - if current_login_mode == LoginMode.PASSWORD: - restore_button(forgot_password_btn, "忘记密码") - else: - restore_button(forgot_password_btn, "获取验证码") - restore_button(get_code_btn, "获取验证码") - - var result = ResponseHandler.handle_send_login_code_response(success, data, error_info) - - if result.should_show_toast: - show_toast(result.message, result.success) - -func _on_verification_login_response(success: bool, data: Dictionary, error_info: Dictionary): - print("=== 验证码登录响应处理 ===") - print("成功: ", success) - print("数据: ", data) - print("错误信息: ", error_info) - - restore_button(main_btn, "进入小镇") - - var result = ResponseHandler.handle_verification_code_login_response(success, data, error_info) - - if result.should_show_toast: - show_toast(result.message, result.success) - - if result.success: - var username = login_username.text.strip_edges() - if data.has("data") and data.data.has("user") and data.data.user.has("username"): - username = data.data.user.username - - login_username.text = "" - hide_field_error(login_username_error) - - await get_tree().create_timer(1.0).timeout - login_success.emit(username) - -func _on_forgot_password_response(success: bool, data: Dictionary, error_info: Dictionary): - print("=== 忘记密码响应处理 ===") - print("成功: ", success) - print("数据: ", data) - - restore_button(forgot_password_btn, "忘记密码") - - # 使用通用的发送验证码响应处理 - var result = ResponseHandler.handle_send_login_code_response(success, data, error_info) - - if result.should_show_toast: - show_toast(result.message, result.success) - - if result.success: - show_toast("密码重置验证码已发送,请查收邮件", true) - -func _on_register_response(success: bool, data: Dictionary, error_info: Dictionary): - print("=== 注册响应处理 ===") - print("成功: ", success) - print("数据: ", data) - - restore_button(register_btn, "注册") - - var result = ResponseHandler.handle_register_response(success, data, error_info) - - if result.should_show_toast: - show_toast(result.message, result.success) - - if result.success: - clear_register_form() - show_login_panel() - login_username.text = register_username.text.strip_edges() # 使用注册时的用户名 -# ============ 验证码冷却管理 ============ - -func start_cooldown_timer(email: String): - if cooldown_timer != null: - cooldown_timer.queue_free() - - current_email = email - - send_code_btn.disabled = true - send_code_btn.text = "重新发送(60)" - - cooldown_timer = Timer.new() - add_child(cooldown_timer) - cooldown_timer.wait_time = 1.0 - cooldown_timer.timeout.connect(_on_cooldown_timer_timeout) - cooldown_timer.start() - -func _on_cooldown_timer_timeout(): - var input_email = register_email.text.strip_edges() - - if input_email != current_email: - stop_current_cooldown() - return - - if verification_codes_sent.has(current_email): - var current_time = Time.get_time_dict_from_system() - var current_timestamp = current_time.hour * 3600 + current_time.minute * 60 + current_time.second - var email_data = verification_codes_sent[current_email] - var remaining = code_cooldown - (current_timestamp - email_data.time) - - if remaining > 0: - send_code_btn.text = "重新发送(%d)" % remaining - else: - send_code_btn.text = "重新发送" - send_code_btn.disabled = false - - if cooldown_timer != null: - cooldown_timer.queue_free() - cooldown_timer = null - current_email = "" - -func stop_current_cooldown(): - if cooldown_timer != null: - cooldown_timer.queue_free() - cooldown_timer = null - - send_code_btn.disabled = false - send_code_btn.text = "发送验证码" - current_email = "" - -func reset_verification_button(): - if current_email != "" and verification_codes_sent.has(current_email): - verification_codes_sent[current_email].sent = false - - stop_current_cooldown() - -func clear_register_form(): - register_username.text = "" - register_email.text = "" - register_password.text = "" - register_confirm.text = "" - verification_input.text = "" - - stop_current_cooldown() - verification_codes_sent.clear() - - hide_field_error(register_username_error) - hide_field_error(register_email_error) - hide_field_error(register_password_error) - hide_field_error(register_confirm_error) - hide_field_error(verification_error) - -# ============ Toast消息系统 ============ - -# 检测文本是否包含中文字符 -func contains_chinese(text: String) -> bool: - for i in range(text.length()): - var char_code = text.unicode_at(i) - # 中文字符的Unicode范围 - if (char_code >= 0x4E00 and char_code <= 0x9FFF) or \ - (char_code >= 0x3400 and char_code <= 0x4DBF) or \ - (char_code >= 0x20000 and char_code <= 0x2A6DF): - return true - return false - -func show_toast(message: String, is_success: bool = true): - print("显示Toast消息: ", message, " 成功: ", is_success) - - if toast_container == null: - print("错误: toast_container 节点不存在") - return - - # 异步创建Toast实例 - create_toast_instance(message, is_success) - -func create_toast_instance(message: String, is_success: bool): - toast_counter += 1 - - # Web平台字体处理 - var is_web = OS.get_name() == "Web" - - # 1. 创建Toast Panel(方框UI) - var toast_panel = Panel.new() - toast_panel.name = "Toast_" + str(toast_counter) - - # 设置Toast样式 - var style = StyleBoxFlat.new() - 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.border_width_left = 3 - style.border_width_top = 3 - style.border_width_right = 3 - style.border_width_bottom = 3 - style.corner_radius_top_left = 12 - style.corner_radius_top_right = 12 - style.corner_radius_bottom_left = 12 - style.corner_radius_bottom_right = 12 - style.shadow_color = Color(0, 0, 0, 0.3) - style.shadow_size = 4 - style.shadow_offset = Vector2(2, 2) - - toast_panel.add_theme_stylebox_override("panel", style) - - # 设置Toast基本尺寸 - var toast_width = 320 - toast_panel.size = Vector2(toast_width, 60) - - # 2. 创建VBoxContainer - var vbox = VBoxContainer.new() - vbox.add_theme_constant_override("separation", 0) - vbox.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) - vbox.alignment = BoxContainer.ALIGNMENT_CENTER - - # 3. 创建CenterContainer - var center_container = CenterContainer.new() - center_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL - center_container.size_flags_vertical = Control.SIZE_SHRINK_CENTER - - # 4. 创建Label(文字控件) - var text_label = Label.new() - text_label.text = message - text_label.add_theme_color_override("font_color", Color(1, 1, 1, 1)) - text_label.add_theme_font_size_override("font_size", 14) - - # 平台特定的字体处理 - if is_web: - print("Web平台Toast字体处理") - # Web平台使用主题文件 - var chinese_theme = load("res://assets/ui/chinese_theme.tres") - if chinese_theme: - text_label.theme = chinese_theme - print("Web平台应用中文主题") - else: - print("Web平台中文主题加载失败") - else: - print("桌面平台Toast字体处理") - # 桌面平台直接加载中文字体 - var desktop_chinese_font = load("res://assets/fonts/msyh.ttc") - if desktop_chinese_font: - text_label.add_theme_font_override("font", desktop_chinese_font) - print("桌面平台使用中文字体") - - text_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART - text_label.custom_minimum_size = Vector2(280, 0) - text_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - text_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER - - # 组装控件层级 - center_container.add_child(text_label) - vbox.add_child(center_container) - toast_panel.add_child(vbox) - - # 计算位置 - var margin = 20 - var start_x = get_viewport().get_visible_rect().size.x - var final_x = get_viewport().get_visible_rect().size.x - toast_width - margin - - # 计算Y位置 - var y_position = margin - for existing_toast in active_toasts: - if is_instance_valid(existing_toast): - y_position += existing_toast.size.y + 15 - - # 设置初始位置 - toast_panel.position = Vector2(start_x, y_position) - - # 添加到容器 - toast_container.add_child(toast_panel) - active_toasts.append(toast_panel) - - # 等待一帧让布局系统计算尺寸 - await get_tree().process_frame - - # 让Toast高度自适应内容 - var content_size = vbox.get_combined_minimum_size() - var final_height = max(60, content_size.y + 20) # 最小60,加20像素边距 - toast_panel.size.y = final_height - - # 重新排列所有Toast - rearrange_toasts() - - # 开始动画 - animate_toast_in(toast_panel, final_x) - -func animate_toast_in(toast_panel: Panel, final_x: float): - var tween = create_tween() - tween.set_ease(Tween.EASE_OUT) - tween.set_trans(Tween.TRANS_BACK) - - tween.parallel().tween_property(toast_panel, "position:x", final_x, 0.6) - tween.parallel().tween_property(toast_panel, "modulate:a", 1.0, 0.4) - - toast_panel.modulate.a = 0.0 - - await get_tree().create_timer(3.0).timeout - animate_toast_out(toast_panel) - -func animate_toast_out(toast_panel: Panel): - if not is_instance_valid(toast_panel): - return - - var tween = create_tween() - tween.set_ease(Tween.EASE_IN) - tween.set_trans(Tween.TRANS_QUART) - - var end_x = get_viewport().get_visible_rect().size.x + 50 - tween.parallel().tween_property(toast_panel, "position:x", end_x, 0.4) - tween.parallel().tween_property(toast_panel, "modulate:a", 0.0, 0.3) - - await tween.finished - cleanup_toast(toast_panel) - -func cleanup_toast(toast_panel: Panel): - if not is_instance_valid(toast_panel): - return - - active_toasts.erase(toast_panel) - rearrange_toasts() - toast_panel.queue_free() - -func rearrange_toasts(): - var margin = 20 - var current_y = margin - - for i in range(active_toasts.size()): - var toast = active_toasts[i] - if is_instance_valid(toast): - var tween = create_tween() - tween.tween_property(toast, "position:y", current_y, 0.2) - current_y += toast.size.y + 15 - -# ============ UI工具方法 ============ - -func show_loading(button: Button, loading_text: String): - button.disabled = true - button.text = loading_text - -func restore_button(button: Button, original_text: String): - button.disabled = false - button.text = original_text - -func show_field_error(error_label: Label, message: String): - error_label.text = message - error_label.visible = true - -func hide_field_error(error_label: Label): - error_label.visible = false - -func is_valid_email(email: String) -> bool: - return StringUtils.is_valid_email(email) - -func is_valid_phone(phone: String) -> bool: - var regex = RegEx.new() - regex.compile("^\\+?[1-9]\\d{1,14}$") - return regex.search(phone) != null - -# ============ 表单验证方法 ============ - -func validate_username(username: String) -> Dictionary: - var result = {"valid": false, "message": ""} - - if username.is_empty(): - result.message = "用户名不能为空" - return result - - if not StringUtils.is_valid_username(username): - if username.length() > 50: - result.message = "用户名长度不能超过50字符" - else: - result.message = "用户名只能包含字母、数字和下划线" - return result - - result.valid = true - return result - -func validate_email(email: String) -> Dictionary: - var result = {"valid": false, "message": ""} - - if email.is_empty(): - result.message = "邮箱不能为空" - return result - - if not StringUtils.is_valid_email(email): - result.message = "请输入有效的邮箱地址" - return result - - result.valid = true - return result - -func validate_password(password: String) -> Dictionary: - return StringUtils.validate_password_strength(password) - -func validate_confirm_password(password: String, confirm: String) -> Dictionary: - var result = {"valid": false, "message": ""} - - if confirm.is_empty(): - result.message = "确认密码不能为空" - return result - - if password != confirm: - result.message = "两次输入的密码不一致" - return result - - result.valid = true - return result - -func validate_verification_code(code: String) -> Dictionary: - var result = {"valid": false, "message": ""} - - if code.is_empty(): - result.message = "验证码不能为空" - return result - - if code.length() != 6: - result.message = "验证码必须是6位数字" - return result - - for i in range(code.length()): - var character = code[i] - if not (character >= '0' and character <= '9'): - result.message = "验证码必须是6位数字" - return result - - result.valid = true - return result - -# ============ 表单验证事件 ============ - -func _on_login_username_focus_exited(): - var username = login_username.text.strip_edges() - if username.is_empty(): - show_field_error(login_username_error, "用户名不能为空") - else: - hide_field_error(login_username_error) - -func _on_login_password_focus_exited(): - var password = login_password.text - if password.is_empty(): - show_field_error(login_password_error, "密码不能为空") - else: - hide_field_error(login_password_error) - -func _on_login_verification_focus_exited(): - var verification_code = login_verification.text.strip_edges() - if verification_code.is_empty(): - show_field_error(login_verification_error, "验证码不能为空") - elif verification_code.length() != 6: - show_field_error(login_verification_error, "验证码必须是6位数字") - elif not verification_code.is_valid_int(): - show_field_error(login_verification_error, "验证码只能包含数字") - else: - hide_field_error(login_verification_error) - -func _on_register_username_focus_exited(): - var username = register_username.text.strip_edges() - var validation = validate_username(username) - if not validation.valid: - show_field_error(register_username_error, validation.message) - else: - hide_field_error(register_username_error) - -func _on_register_email_focus_exited(): - var email = register_email.text.strip_edges() - var validation = validate_email(email) - if not validation.valid: - show_field_error(register_email_error, validation.message) - else: - hide_field_error(register_email_error) - -func _on_register_password_focus_exited(): - var password = register_password.text - var validation = validate_password(password) - if not validation.valid: - show_field_error(register_password_error, validation.message) - else: - hide_field_error(register_password_error) - if not register_confirm.text.is_empty(): - _on_register_confirm_focus_exited() - -func _on_register_confirm_focus_exited(): - var password = register_password.text - var confirm = register_confirm.text - var validation = validate_confirm_password(password, confirm) - if not validation.valid: - show_field_error(register_confirm_error, validation.message) - else: - hide_field_error(register_confirm_error) - -func _on_verification_focus_exited(): - var code = verification_input.text.strip_edges() - var validation = validate_verification_code(code) - if not validation.valid: - show_field_error(verification_error, validation.message) - else: - hide_field_error(verification_error) - -# ============ 实时输入验证事件 ============ - -func _on_register_username_text_changed(new_text: String): - if register_username_error.visible and not new_text.is_empty(): - hide_field_error(register_username_error) - -func _on_register_email_text_changed(new_text: String): - if register_email_error.visible and not new_text.is_empty(): - hide_field_error(register_email_error) - -func _on_register_password_text_changed(new_text: String): - if register_password_error.visible and not new_text.is_empty(): - hide_field_error(register_password_error) - -func _on_register_confirm_text_changed(new_text: String): - if register_confirm_error.visible and not new_text.is_empty(): - hide_field_error(register_confirm_error) - -func _on_verification_text_changed(new_text: String): - if verification_error.visible and not new_text.is_empty(): - hide_field_error(verification_error) -# ============ 表单整体验证 ============ - -func validate_login_form() -> bool: - var is_valid = true - - var username = login_username.text.strip_edges() - var password = login_password.text - - if username.is_empty(): - show_field_error(login_username_error, "用户名不能为空") - is_valid = false - else: - hide_field_error(login_username_error) - - if password.is_empty(): - show_field_error(login_password_error, "密码不能为空") - is_valid = false - else: - hide_field_error(login_password_error) - - return is_valid - -func validate_register_form() -> bool: - print("开始验证注册表单") - var is_valid = true - - var username = register_username.text.strip_edges() - var email = register_email.text.strip_edges() - var password = register_password.text - var confirm = register_confirm.text - var verification_code = verification_input.text.strip_edges() - - print("表单数据: 用户名='%s', 邮箱='%s', 密码长度=%d, 确认密码长度=%d, 验证码='%s'" % [username, email, password.length(), confirm.length(), verification_code]) - - var username_validation = validate_username(username) - if not username_validation.valid: - print("用户名验证失败: ", username_validation.message) - show_field_error(register_username_error, username_validation.message) - is_valid = false - else: - hide_field_error(register_username_error) - - var email_validation = validate_email(email) - if not email_validation.valid: - print("邮箱验证失败: ", email_validation.message) - show_field_error(register_email_error, email_validation.message) - is_valid = false - else: - hide_field_error(register_email_error) - - var password_validation = validate_password(password) - if not password_validation.valid: - print("密码验证失败: ", password_validation.message) - show_field_error(register_password_error, password_validation.message) - is_valid = false - else: - hide_field_error(register_password_error) - - var confirm_validation = validate_confirm_password(password, confirm) - if not confirm_validation.valid: - print("确认密码验证失败: ", confirm_validation.message) - show_field_error(register_confirm_error, confirm_validation.message) - is_valid = false - else: - hide_field_error(register_confirm_error) - - var code_validation = validate_verification_code(verification_code) - if not code_validation.valid: - print("验证码格式验证失败: ", code_validation.message) - show_field_error(verification_error, code_validation.message) - is_valid = false - else: - hide_field_error(verification_error) - - var current_email_input = register_email.text.strip_edges() - var has_sent_code = false - - if verification_codes_sent.has(current_email_input): - var email_data = verification_codes_sent[current_email_input] - has_sent_code = email_data.get("sent", false) - - if not has_sent_code: - print("当前邮箱验证码未发送,email = ", current_email_input) - show_toast("请先获取邮箱验证码", false) - is_valid = false - - print("表单验证结果: ", is_valid) - return is_valid - -# ============ 资源清理 ============ - -func _exit_tree(): - for request_id in active_request_ids: - NetworkManager.cancel_request(request_id) - active_request_ids.clear() - - if cooldown_timer != null: - cooldown_timer.queue_free() - cooldown_timer = null - -func _input(event): - if event.is_action_pressed("ui_cancel"): - get_tree().quit() diff --git a/UI/Windows/AuthScene.gd.uid b/UI/Windows/AuthScene.gd.uid deleted file mode 100644 index 56d8ef1..0000000 --- a/UI/Windows/AuthScene.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://bs1vy3ierj66t diff --git a/UI/Windows/LoginWindow.tscn b/UI/Windows/LoginWindow.tscn deleted file mode 100644 index 50e32f6..0000000 --- a/UI/Windows/LoginWindow.tscn +++ /dev/null @@ -1,561 +0,0 @@ -[gd_scene load_steps=10 format=3 uid="uid://by7m8snb4xllf"] - -[ext_resource type="Texture2D" uid="uid://bx17oy8lvaca4" path="res://assets/ui/auth/bg_auth_scene.png" id="1_background"] -[ext_resource type="Texture2D" uid="uid://de4q4s1gxivtf" path="res://assets/ui/auth/login_frame_smart_transparent.png" id="2_frame"] -[ext_resource type="Script" uid="uid://bs1vy3ierj66t" path="res://UI/Windows/AuthScene.gd" id="3_script"] - -[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_1"] - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_hover"] -bg_color = Color(0.3, 0.6, 0.9, 1) -border_width_left = 2 -border_width_top = 2 -border_width_right = 2 -border_width_bottom = 2 -border_color = Color(0.2, 0.5, 0.8, 1) -corner_radius_top_left = 8 -corner_radius_top_right = 8 -corner_radius_bottom_right = 8 -corner_radius_bottom_left = 8 - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_normal"] -bg_color = Color(0.2, 0.5, 0.8, 1) -border_width_left = 2 -border_width_top = 2 -border_width_right = 2 -border_width_bottom = 2 -border_color = Color(0.15, 0.4, 0.7, 1) -corner_radius_top_left = 8 -corner_radius_top_right = 8 -corner_radius_bottom_right = 8 -corner_radius_bottom_left = 8 - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_pressed"] -bg_color = Color(0.4, 0.7, 1, 1) -border_width_left = 2 -border_width_top = 2 -border_width_right = 2 -border_width_bottom = 2 -border_color = Color(0.3, 0.6, 0.9, 1) -corner_radius_top_left = 8 -corner_radius_top_right = 8 -corner_radius_bottom_right = 8 -corner_radius_bottom_left = 8 - -[sub_resource type="Theme" id="Theme_main_button"] -Button/colors/font_color = Color(1, 1, 1, 1) -Button/colors/font_hover_color = Color(1, 1, 1, 1) -Button/colors/font_pressed_color = Color(1, 1, 1, 1) -Button/font_sizes/font_size = 18 -Button/styles/hover = SubResource("StyleBoxFlat_hover") -Button/styles/normal = SubResource("StyleBoxFlat_normal") -Button/styles/pressed = SubResource("StyleBoxFlat_pressed") - -[sub_resource type="Theme" id="Theme_button"] -Button/colors/font_color = Color(1, 1, 1, 1) -Button/colors/font_hover_color = Color(1, 1, 1, 1) -Button/colors/font_pressed_color = Color(1, 1, 1, 1) -Button/styles/hover = SubResource("StyleBoxFlat_hover") -Button/styles/normal = SubResource("StyleBoxFlat_normal") -Button/styles/pressed = SubResource("StyleBoxFlat_pressed") - -[node name="AuthScene" type="Control"] -layout_mode = 3 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -script = ExtResource("3_script") - -[node name="HTTPRequest" type="HTTPRequest" parent="."] - -[node name="BackgroundImage" type="TextureRect" parent="."] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -texture = ExtResource("1_background") -expand_mode = 1 -stretch_mode = 6 - -[node name="WhaleFrame" type="TextureRect" parent="."] -layout_mode = 1 -anchors_preset = 8 -anchor_left = 0.5 -anchor_top = 0.5 -anchor_right = 0.5 -anchor_bottom = 0.5 -offset_left = -300.0 -offset_top = -300.0 -offset_right = 300.0 -offset_bottom = 300.0 -grow_horizontal = 2 -grow_vertical = 2 -texture = ExtResource("2_frame") -expand_mode = 1 -stretch_mode = 5 - -[node name="CenterContainer" type="CenterContainer" parent="."] -layout_mode = 1 -anchors_preset = 8 -anchor_left = 0.5 -anchor_top = 0.5 -anchor_right = 0.5 -anchor_bottom = 0.5 -offset_left = -175.0 -offset_top = -184.0 -offset_right = 175.0 -offset_bottom = 236.0 -grow_horizontal = 2 -grow_vertical = 2 - -[node name="LoginPanel" type="Panel" parent="CenterContainer"] -custom_minimum_size = Vector2(350, 400) -layout_mode = 2 -theme_override_styles/panel = SubResource("StyleBoxEmpty_1") - -[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer/LoginPanel"] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_left = 30.0 -offset_top = 30.0 -offset_right = -30.0 -offset_bottom = -30.0 -grow_horizontal = 2 -grow_vertical = 2 - -[node name="TitleLabel" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -theme_override_font_sizes/font_size = 24 -text = "Whaletown" -horizontal_alignment = 1 -vertical_alignment = 1 - -[node name="SubtitleLabel" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -theme_override_font_sizes/font_size = 14 -text = "开始你的小镇之旅!" -horizontal_alignment = 1 -vertical_alignment = 1 - -[node name="HSeparator" type="HSeparator" parent="CenterContainer/LoginPanel/VBoxContainer"] -layout_mode = 2 - -[node name="LoginForm" type="VBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer"] -layout_mode = 2 -size_flags_vertical = 3 - -[node name="UsernameContainer" type="VBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm"] -layout_mode = 2 - -[node name="UsernameLabelContainer" type="HBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/UsernameContainer"] -layout_mode = 2 - -[node name="UsernameLabel" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/UsernameContainer/UsernameLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -text = "用户名/手机/邮箱" - -[node name="RequiredStar" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/UsernameContainer/UsernameLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -text = " *" - -[node name="Spacer" type="Control" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/UsernameContainer/UsernameLabelContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="UsernameError" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/UsernameContainer/UsernameLabelContainer"] -visible = false -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -theme_override_font_sizes/font_size = 12 -text = "用户名不能为空" -horizontal_alignment = 2 - -[node name="UsernameInput" type="LineEdit" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/UsernameContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -theme_override_colors/font_placeholder_color = Color(0.5, 0.5, 0.5, 1) -placeholder_text = "用户名/手机/邮箱" - -[node name="PasswordContainer" type="VBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm"] -layout_mode = 2 - -[node name="PasswordLabelContainer" type="HBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/PasswordContainer"] -layout_mode = 2 - -[node name="PasswordLabel" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/PasswordContainer/PasswordLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -text = "密码" - -[node name="RequiredStar" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/PasswordContainer/PasswordLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -text = " *" - -[node name="Spacer" type="Control" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/PasswordContainer/PasswordLabelContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="PasswordError" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/PasswordContainer/PasswordLabelContainer"] -visible = false -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -theme_override_font_sizes/font_size = 12 -text = "密码不能为空" -horizontal_alignment = 2 - -[node name="PasswordInput" type="LineEdit" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/PasswordContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -theme_override_colors/font_placeholder_color = Color(0.5, 0.5, 0.5, 1) -placeholder_text = "请输入密码" -secret = true - -[node name="VerificationContainer" type="VBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm"] -visible = false -layout_mode = 2 - -[node name="VerificationLabelContainer" type="HBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer"] -layout_mode = 2 - -[node name="VerificationLabel" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -text = "验证码" - -[node name="RequiredStar" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -text = " *" - -[node name="Spacer" type="Control" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationLabelContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="VerificationError" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationLabelContainer"] -visible = false -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -theme_override_font_sizes/font_size = 12 -text = "请输入验证码" -horizontal_alignment = 2 - -[node name="VerificationInputContainer" type="HBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer"] -layout_mode = 2 - -[node name="VerificationInput" type="LineEdit" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationInputContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -theme_override_colors/font_color = Color(0, 0, 0, 1) -theme_override_colors/font_placeholder_color = Color(0.5, 0.5, 0.5, 1) -placeholder_text = "请输入6位验证码" -max_length = 6 - -[node name="GetCodeBtn" type="Button" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationInputContainer"] -layout_mode = 2 -text = "获取验证码" - -[node name="CheckboxContainer" type="HBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm"] -layout_mode = 2 - -[node name="RememberPassword" type="CheckBox" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/CheckboxContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -text = "记住密码" - -[node name="AutoLogin" type="CheckBox" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/CheckboxContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -text = "自动登录" - -[node name="HSeparator2" type="HSeparator" parent="CenterContainer/LoginPanel/VBoxContainer"] -layout_mode = 2 - -[node name="MainButton" type="Button" parent="CenterContainer/LoginPanel/VBoxContainer"] -custom_minimum_size = Vector2(280, 50) -layout_mode = 2 -theme = SubResource("Theme_main_button") -text = "进入小镇" - -[node name="HSeparator3" type="HSeparator" parent="CenterContainer/LoginPanel/VBoxContainer"] -layout_mode = 2 - -[node name="ButtonContainer" type="HBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer"] -layout_mode = 2 -alignment = 1 - -[node name="LoginBtn" type="Button" parent="CenterContainer/LoginPanel/VBoxContainer/ButtonContainer"] -custom_minimum_size = Vector2(100, 35) -layout_mode = 2 -theme = SubResource("Theme_button") -text = "密码登录" - -[node name="BottomLinks" type="HBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer"] -layout_mode = 2 -alignment = 1 - -[node name="ForgotPassword" type="Button" parent="CenterContainer/LoginPanel/VBoxContainer/BottomLinks"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -text = "忘记密码?" -flat = true - -[node name="RegisterLink" type="Button" parent="CenterContainer/LoginPanel/VBoxContainer/BottomLinks"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -text = "注册居民身份" -flat = true - -[node name="RegisterPanel" type="Panel" parent="CenterContainer"] -visible = false -custom_minimum_size = Vector2(400, 570) -layout_mode = 2 -theme_override_styles/panel = SubResource("StyleBoxEmpty_1") - -[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer/RegisterPanel"] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_left = 30.0 -offset_top = 75.0 -offset_right = -30.0 -offset_bottom = -72.0 -grow_horizontal = 2 -grow_vertical = 2 -alignment = 1 - -[node name="TitleLabel" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -theme_override_font_sizes/font_size = 20 -text = "注册新居民" -horizontal_alignment = 1 -vertical_alignment = 1 - -[node name="HSeparator" type="HSeparator" parent="CenterContainer/RegisterPanel/VBoxContainer"] -layout_mode = 2 - -[node name="RegisterForm" type="VBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer"] -layout_mode = 2 -size_flags_vertical = 3 -alignment = 1 - -[node name="UsernameContainer" type="VBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm"] -layout_mode = 2 - -[node name="UsernameLabelContainer" type="HBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/UsernameContainer"] -layout_mode = 2 - -[node name="UsernameLabel" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/UsernameContainer/UsernameLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -text = "用户名" - -[node name="RequiredStar" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/UsernameContainer/UsernameLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -text = " *" - -[node name="Spacer" type="Control" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/UsernameContainer/UsernameLabelContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="UsernameError" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/UsernameContainer/UsernameLabelContainer"] -visible = false -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -theme_override_font_sizes/font_size = 12 -text = "用户名不能为空" -horizontal_alignment = 2 - -[node name="UsernameInput" type="LineEdit" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/UsernameContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -theme_override_colors/font_placeholder_color = Color(0.5, 0.5, 0.5, 1) -placeholder_text = "请输入用户名" - -[node name="EmailContainer" type="VBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm"] -layout_mode = 2 - -[node name="EmailLabelContainer" type="HBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/EmailContainer"] -layout_mode = 2 - -[node name="EmailLabel" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/EmailContainer/EmailLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -text = "邮箱" - -[node name="RequiredStar" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/EmailContainer/EmailLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -text = " *" - -[node name="Spacer" type="Control" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/EmailContainer/EmailLabelContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="EmailError" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/EmailContainer/EmailLabelContainer"] -visible = false -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -theme_override_font_sizes/font_size = 12 -text = "邮箱不能为空" -horizontal_alignment = 2 - -[node name="EmailInput" type="LineEdit" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/EmailContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -theme_override_colors/font_placeholder_color = Color(0.5, 0.5, 0.5, 1) -placeholder_text = "请输入邮箱地址" - -[node name="PasswordContainer" type="VBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm"] -layout_mode = 2 - -[node name="PasswordLabelContainer" type="HBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/PasswordContainer"] -layout_mode = 2 - -[node name="PasswordLabel" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/PasswordContainer/PasswordLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -text = "密码" - -[node name="RequiredStar" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/PasswordContainer/PasswordLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -text = " *" - -[node name="Spacer" type="Control" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/PasswordContainer/PasswordLabelContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="PasswordError" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/PasswordContainer/PasswordLabelContainer"] -visible = false -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -theme_override_font_sizes/font_size = 12 -text = "密码不能为空" -horizontal_alignment = 2 - -[node name="PasswordInput" type="LineEdit" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/PasswordContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -theme_override_colors/font_placeholder_color = Color(0.5, 0.5, 0.5, 1) -placeholder_text = "请输入密码(至少8位)" -secret = true - -[node name="ConfirmContainer" type="VBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm"] -layout_mode = 2 - -[node name="ConfirmLabelContainer" type="HBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/ConfirmContainer"] -layout_mode = 2 - -[node name="ConfirmLabel" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/ConfirmContainer/ConfirmLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -text = "确认密码" - -[node name="RequiredStar" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/ConfirmContainer/ConfirmLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -text = " *" - -[node name="Spacer" type="Control" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/ConfirmContainer/ConfirmLabelContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="ConfirmError" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/ConfirmContainer/ConfirmLabelContainer"] -visible = false -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -theme_override_font_sizes/font_size = 12 -text = "确认密码不能为空" -horizontal_alignment = 2 - -[node name="ConfirmInput" type="LineEdit" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/ConfirmContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -theme_override_colors/font_placeholder_color = Color(0.5, 0.5, 0.5, 1) -placeholder_text = "请再次输入密码" -secret = true - -[node name="VerificationContainer" type="VBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm"] -layout_mode = 2 - -[node name="VerificationLabelContainer" type="HBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer"] -layout_mode = 2 - -[node name="VerificationLabel" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer/VerificationLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -text = "邮箱验证码" - -[node name="RequiredStar" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer/VerificationLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -text = " *" - -[node name="Spacer" type="Control" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer/VerificationLabelContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="VerificationError" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer/VerificationLabelContainer"] -visible = false -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -theme_override_font_sizes/font_size = 12 -text = "验证码不能为空" -horizontal_alignment = 2 - -[node name="VerificationInputContainer" type="HBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer"] -layout_mode = 2 - -[node name="VerificationInput" type="LineEdit" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer/VerificationInputContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -theme_override_colors/font_color = Color(0, 0, 0, 1) -theme_override_colors/font_placeholder_color = Color(0.5, 0.5, 0.5, 1) -placeholder_text = "请输入6位验证码" -max_length = 6 - -[node name="SendCodeBtn" type="Button" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer/VerificationInputContainer"] -layout_mode = 2 -text = "发送验证码" - -[node name="HSeparator2" type="HSeparator" parent="CenterContainer/RegisterPanel/VBoxContainer"] -layout_mode = 2 - -[node name="ButtonContainer" type="HBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer"] -layout_mode = 2 -alignment = 1 - -[node name="RegisterBtn" type="Button" parent="CenterContainer/RegisterPanel/VBoxContainer/ButtonContainer"] -custom_minimum_size = Vector2(120, 45) -layout_mode = 2 -theme = SubResource("Theme_button") -text = "注册" - -[node name="ToLoginBtn" type="Button" parent="CenterContainer/RegisterPanel/VBoxContainer/ButtonContainer"] -custom_minimum_size = Vector2(120, 45) -layout_mode = 2 -theme = SubResource("Theme_button") -text = "返回登录" - -[node name="ToastContainer" type="Control" parent="."] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -mouse_filter = 2 diff --git a/Utils/StringUtils.gd b/Utils/StringUtils.gd deleted file mode 100644 index 4d69728..0000000 --- a/Utils/StringUtils.gd +++ /dev/null @@ -1,199 +0,0 @@ -class_name StringUtils - -# 字符串工具类 - 提供常用的字符串处理功能 - -# 验证邮箱格式 -static func is_valid_email(email: String) -> bool: - var regex = RegEx.new() - regex.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$") - return regex.search(email) != null - -# 验证用户名格式(字母、数字、下划线) -static func is_valid_username(username: String) -> bool: - if username.is_empty() or username.length() > 50: - return false - - var regex = RegEx.new() - regex.compile("^[a-zA-Z0-9_]+$") - return regex.search(username) != null - -# 验证密码强度 -static func validate_password_strength(password: String) -> Dictionary: - var result = {"valid": false, "message": "", "strength": 0} - - if password.length() < 8: - result.message = "密码长度至少8位" - return result - - if password.length() > 128: - result.message = "密码长度不能超过128位" - return result - - var has_letter = false - var has_digit = false - var has_special = false - - for i in range(password.length()): - var character = password[i] - if character >= 'a' and character <= 'z' or character >= 'A' and character <= 'Z': - has_letter = true - elif character >= '0' and character <= '9': - has_digit = true - elif character in "!@#$%^&*()_+-=[]{}|;:,.<>?": - has_special = true - - var strength = 0 - if has_letter: - strength += 1 - if has_digit: - strength += 1 - if has_special: - strength += 1 - if password.length() >= 12: - strength += 1 - - result.strength = strength - - if not (has_letter and has_digit): - result.message = "密码必须包含字母和数字" - return result - - result.valid = true - result.message = "密码强度: " + ["弱", "中", "强", "很强"][min(strength - 1, 3)] - return result - -# 截断字符串 -static func truncate(text: String, max_length: int, suffix: String = "...") -> String: - if text.length() <= max_length: - return text - return text.substr(0, max_length - suffix.length()) + suffix - -# 首字母大写 -static func capitalize_first(text: String) -> String: - if text.is_empty(): - return text - return text[0].to_upper() + text.substr(1) - -# 转换为标题格式(每个单词首字母大写) -static func to_title_case(text: String) -> String: - var words = text.split(" ") - var result = [] - for word in words: - if not word.is_empty(): - result.append(capitalize_first(word.to_lower())) - return " ".join(result) - -# 移除HTML标签 -static func strip_html_tags(html: String) -> String: - var regex = RegEx.new() - regex.compile("<[^>]*>") - return regex.sub(html, "", true) - -# 格式化文件大小 -static func format_file_size(bytes: int) -> String: - var units = ["B", "KB", "MB", "GB", "TB"] - var size = float(bytes) - var unit_index = 0 - - while size >= 1024.0 and unit_index < units.size() - 1: - size /= 1024.0 - unit_index += 1 - - if unit_index == 0: - return str(int(size)) + " " + units[unit_index] - else: - return "%.1f %s" % [size, units[unit_index]] - -# 将UTC时间字符串转换为本地时间显示 -static func format_utc_to_local_time(utc_time_str: String) -> String: - # 解析UTC时间字符串 (格式: 2025-12-25T11:23:52.175Z) - var regex = RegEx.new() - regex.compile("(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})") - var result = regex.search(utc_time_str) - - if result == null: - return utc_time_str # 如果解析失败,返回原字符串 - - # 提取时间组件 - var year = int(result.get_string(1)) - var month = int(result.get_string(2)) - var day = int(result.get_string(3)) - var hour = int(result.get_string(4)) - var minute = int(result.get_string(5)) - var second = int(result.get_string(6)) - - # 创建UTC时间字典 - var utc_dict = { - "year": year, - "month": month, - "day": day, - "hour": hour, - "minute": minute, - "second": second - } - - # 转换为Unix时间戳(UTC) - var utc_timestamp = Time.get_unix_time_from_datetime_dict(utc_dict) - - # 获取本地时间(Godot会自动处理时区转换) - var local_dict = Time.get_datetime_dict_from_unix_time(utc_timestamp) - - # 格式化为易读的本地时间 - return "%04d年%02d月%02d日 %02d:%02d:%02d" % [ - local_dict.year, - local_dict.month, - local_dict.day, - local_dict.hour, - local_dict.minute, - local_dict.second - ] - -# 获取相对时间描述(多少分钟后) -static func get_relative_time_until(utc_time_str: String) -> String: - # 解析UTC时间字符串 - var regex = RegEx.new() - regex.compile("(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})") - var result = regex.search(utc_time_str) - - if result == null: - return "时间格式错误" - - # 提取时间组件 - var year = int(result.get_string(1)) - var month = int(result.get_string(2)) - var day = int(result.get_string(3)) - var hour = int(result.get_string(4)) - var minute = int(result.get_string(5)) - var second = int(result.get_string(6)) - - # 创建UTC时间字典 - var utc_dict = { - "year": year, - "month": month, - "day": day, - "hour": hour, - "minute": minute, - "second": second - } - - # 转换为Unix时间戳 - var target_timestamp = Time.get_unix_time_from_datetime_dict(utc_dict) - var current_timestamp = Time.get_unix_time_from_system() - - # 计算时间差(秒) - var diff_seconds = target_timestamp - current_timestamp - - if diff_seconds <= 0: - return "现在可以重试" - elif diff_seconds < 60: - return "%d秒后" % diff_seconds - elif diff_seconds < 3600: - var minutes = int(diff_seconds / 60) - return "%d分钟后" % minutes - else: - var hours = int(diff_seconds / 3600) - var minutes = int((diff_seconds % 3600) / 60) - if minutes > 0: - return "%d小时%d分钟后" % [hours, minutes] - else: - return "%d小时后" % hours diff --git a/Utils/StringUtils.gd.uid b/Utils/StringUtils.gd.uid deleted file mode 100644 index aded9a0..0000000 --- a/Utils/StringUtils.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://csxag1y8yq57j diff --git a/scenes/Components/characters/.gitkeep b/scenes/Components/characters/.gitkeep deleted file mode 100644 index fb0695e..0000000 --- a/scenes/Components/characters/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -# 保持目录结构 - 角色预制体目录 \ No newline at end of file diff --git a/scenes/Components/effects/.gitkeep b/scenes/Components/effects/.gitkeep deleted file mode 100644 index 6691578..0000000 --- a/scenes/Components/effects/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -# 保持目录结构 - 特效预制体目录 \ No newline at end of file diff --git a/scenes/Components/items/.gitkeep b/scenes/Components/items/.gitkeep deleted file mode 100644 index 2e3c48c..0000000 --- a/scenes/Components/items/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -# 保持目录结构 - 物品预制体目录 \ No newline at end of file diff --git a/scenes/Components/ui/.gitkeep b/scenes/Components/ui/.gitkeep deleted file mode 100644 index 884109f..0000000 --- a/scenes/Components/ui/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -# 保持目录结构 - UI预制体目录 \ No newline at end of file From 3175c98ea32d069ade8a4e9bdf65907f8a915a5c Mon Sep 17 00:00:00 2001 From: moyin <2443444649@qq.com> Date: Fri, 2 Jan 2026 00:58:34 +0800 Subject: [PATCH 3/6] =?UTF-8?q?refactor=EF=BC=9A=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E6=96=B0=E7=9A=84=E9=A1=B9=E7=9B=AE=E7=BB=93=E6=9E=84=E7=BB=84?= =?UTF-8?q?=E7=BB=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 _Core/components/ 和 _Core/utils/ 目录 - 重新组织 scenes/ 目录结构,按功能分类 - 迁移 StringUtils.gd 到新的 _Core/utils/ 位置 - 迁移 AuthScene.gd 到新的 scenes/ui/ 位置 - 添加 AI 文档支持目录 docs/AI_docs/ - 添加开发参考文档 claude.md --- _Core/components/.gitkeep | 2 + _Core/utils/.gitkeep | 2 + _Core/utils/StringUtils.gd | 199 ++++ _Core/utils/StringUtils.gd.uid | 1 + claude.md | 96 ++ docs/AI_docs/.gitkeep | 0 .../quick_reference/troubleshooting.md | 238 ++++ docs/AI_docs/templates/managers.md | 617 ++++++++++ docs/AI_docs/workflows/feature_development.md | 363 ++++++ scenes/Maps/.gitkeep | 2 + scenes/characters/.gitkeep | 2 + scenes/effects/.gitkeep | 2 + scenes/prefabs/.gitkeep | 2 + scenes/prefabs/characters/.gitkeep | 2 + scenes/prefabs/effects/.gitkeep | 2 + scenes/prefabs/items/.gitkeep | 2 + scenes/prefabs/ui/.gitkeep | 2 + scenes/ui/.gitkeep | 2 + scenes/ui/AuthScene.gd | 1032 +++++++++++++++++ scenes/ui/AuthScene.gd.uid | 1 + scenes/ui/LoginWindow.tscn | 561 +++++++++ 21 files changed, 3130 insertions(+) create mode 100644 _Core/components/.gitkeep create mode 100644 _Core/utils/.gitkeep create mode 100644 _Core/utils/StringUtils.gd create mode 100644 _Core/utils/StringUtils.gd.uid create mode 100644 claude.md create mode 100644 docs/AI_docs/.gitkeep create mode 100644 docs/AI_docs/quick_reference/troubleshooting.md create mode 100644 docs/AI_docs/templates/managers.md create mode 100644 docs/AI_docs/workflows/feature_development.md create mode 100644 scenes/Maps/.gitkeep create mode 100644 scenes/characters/.gitkeep create mode 100644 scenes/effects/.gitkeep create mode 100644 scenes/prefabs/.gitkeep create mode 100644 scenes/prefabs/characters/.gitkeep create mode 100644 scenes/prefabs/effects/.gitkeep create mode 100644 scenes/prefabs/items/.gitkeep create mode 100644 scenes/prefabs/ui/.gitkeep create mode 100644 scenes/ui/.gitkeep create mode 100644 scenes/ui/AuthScene.gd create mode 100644 scenes/ui/AuthScene.gd.uid create mode 100644 scenes/ui/LoginWindow.tscn diff --git a/_Core/components/.gitkeep b/_Core/components/.gitkeep new file mode 100644 index 0000000..f1f9fb0 --- /dev/null +++ b/_Core/components/.gitkeep @@ -0,0 +1,2 @@ +# 基础组件实现目录 +# 存放项目的基础组件类 \ No newline at end of file diff --git a/_Core/utils/.gitkeep b/_Core/utils/.gitkeep new file mode 100644 index 0000000..6ccaea4 --- /dev/null +++ b/_Core/utils/.gitkeep @@ -0,0 +1,2 @@ +# 核心工具类目录 +# 存放字符串处理、数学计算等工具类 \ No newline at end of file diff --git a/_Core/utils/StringUtils.gd b/_Core/utils/StringUtils.gd new file mode 100644 index 0000000..b866af6 --- /dev/null +++ b/_Core/utils/StringUtils.gd @@ -0,0 +1,199 @@ +class_name StringUtils + +# 字符串工具类 - 提供常用的字符串处理功能 + +# 验证邮箱格式 +static func is_valid_email(email: String) -> bool: + var regex = RegEx.new() + regex.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$") + return regex.search(email) != null + +# 验证用户名格式(字母、数字、下划线) +static func is_valid_username(username: String) -> bool: + if username.is_empty() or username.length() > 50: + return false + + var regex = RegEx.new() + regex.compile("^[a-zA-Z0-9_]+$") + return regex.search(username) != null + +# 验证密码强度 +static func validate_password_strength(password: String) -> Dictionary: + var result = {"valid": false, "message": "", "strength": 0} + + if password.length() < 8: + result.message = "密码长度至少8位" + return result + + if password.length() > 128: + result.message = "密码长度不能超过128位" + return result + + var has_letter = false + var has_digit = false + var has_special = false + + for i in range(password.length()): + var character = password[i] + if character >= 'a' and character <= 'z' or character >= 'A' and character <= 'Z': + has_letter = true + elif character >= '0' and character <= '9': + has_digit = true + elif character in "!@#$%^&*()_+-=[]{}|;:,.<>?": + has_special = true + + var strength = 0 + if has_letter: + strength += 1 + if has_digit: + strength += 1 + if has_special: + strength += 1 + if password.length() >= 12: + strength += 1 + + result.strength = strength + + if not (has_letter and has_digit): + result.message = "密码必须包含字母和数字" + return result + + result.valid = true + result.message = "密码强度: " + ["弱", "中", "强", "很强"][min(strength - 1, 3)] + return result + +# 截断字符串 +static func truncate(text: String, max_length: int, suffix: String = "...") -> String: + if text.length() <= max_length: + return text + return text.substr(0, max_length - suffix.length()) + suffix + +# 首字母大写 +static func capitalize_first(text: String) -> String: + if text.is_empty(): + return text + return text[0].to_upper() + text.substr(1) + +# 转换为标题格式(每个单词首字母大写) +static func to_title_case(text: String) -> String: + var words = text.split(" ") + var result = [] + for word in words: + if not word.is_empty(): + result.append(capitalize_first(word.to_lower())) + return " ".join(result) + +# 移除HTML标签 +static func strip_html_tags(html: String) -> String: + var regex = RegEx.new() + regex.compile("<[^>]*>") + return regex.sub(html, "", true) + +# 格式化文件大小 +static func format_file_size(bytes: int) -> String: + var units = ["B", "KB", "MB", "GB", "TB"] + var size = float(bytes) + var unit_index = 0 + + while size >= 1024.0 and unit_index < units.size() - 1: + size /= 1024.0 + unit_index += 1 + + if unit_index == 0: + return str(int(size)) + " " + units[unit_index] + else: + return "%.1f %s" % [size, units[unit_index]] + +# 将UTC时间字符串转换为本地时间显示 +static func format_utc_to_local_time(utc_time_str: String) -> String: + # 解析UTC时间字符串 (格式: 2025-12-25T11:23:52.175Z) + var regex = RegEx.new() + regex.compile("(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})") + var result = regex.search(utc_time_str) + + if result == null: + return utc_time_str # 如果解析失败,返回原字符串 + + # 提取时间组件 + var year = int(result.get_string(1)) + var month = int(result.get_string(2)) + var day = int(result.get_string(3)) + var hour = int(result.get_string(4)) + var minute = int(result.get_string(5)) + var second = int(result.get_string(6)) + + # 创建UTC时间字典 + var utc_dict = { + "year": year, + "month": month, + "day": day, + "hour": hour, + "minute": minute, + "second": second + } + + # 转换为Unix时间戳(UTC) + var utc_timestamp = Time.get_unix_time_from_datetime_dict(utc_dict) + + # 获取本地时间(Godot会自动处理时区转换) + var local_dict = Time.get_datetime_dict_from_unix_time(utc_timestamp) + + # 格式化为易读的本地时间 + return "%04d年%02d月%02d日 %02d:%02d:%02d" % [ + local_dict.year, + local_dict.month, + local_dict.day, + local_dict.hour, + local_dict.minute, + local_dict.second + ] + +# 获取相对时间描述(多少分钟后) +static func get_relative_time_until(utc_time_str: String) -> String: + # 解析UTC时间字符串 + var regex = RegEx.new() + regex.compile("(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})") + var result = regex.search(utc_time_str) + + if result == null: + return "时间格式错误" + + # 提取时间组件 + var year = int(result.get_string(1)) + var month = int(result.get_string(2)) + var day = int(result.get_string(3)) + var hour = int(result.get_string(4)) + var minute = int(result.get_string(5)) + var second = int(result.get_string(6)) + + # 创建UTC时间字典 + var utc_dict = { + "year": year, + "month": month, + "day": day, + "hour": hour, + "minute": minute, + "second": second + } + + # 转换为Unix时间戳 + var target_timestamp = Time.get_unix_time_from_datetime_dict(utc_dict) + var current_timestamp = Time.get_unix_time_from_system() + + # 计算时间差(秒) + var diff_seconds = target_timestamp - current_timestamp + + if diff_seconds <= 0: + return "现在可以重试" + elif diff_seconds < 60: + return "%d秒后" % diff_seconds + elif diff_seconds < 3600: + var minutes = int(diff_seconds / 60) + return "%d分钟后" % minutes + else: + var hours = int(diff_seconds / 3600) + var minutes = int((diff_seconds % 3600) / 60) + if minutes > 0: + return "%d小时%d分钟后" % [hours, minutes] + else: + return "%d小时后" % hours \ No newline at end of file diff --git a/_Core/utils/StringUtils.gd.uid b/_Core/utils/StringUtils.gd.uid new file mode 100644 index 0000000..f305e41 --- /dev/null +++ b/_Core/utils/StringUtils.gd.uid @@ -0,0 +1 @@ +uid://bu8onmk6q8wic diff --git a/claude.md b/claude.md new file mode 100644 index 0000000..0b04993 --- /dev/null +++ b/claude.md @@ -0,0 +1,96 @@ +# 🎯 CLAUDE.md - WhaleTown Project Instructions + +## 1. Project Vision & Context +- **Project**: "WhaleTown" - A 2D top-down pixel art RPG. +- **Engine**: Godot 4.2+ (Strictly NO Godot 3.x syntax). +- **Architecture**: Strictly layered: `_Core` (Framework), `Scenes` (Gameplay), `UI` (Interface). +- **Core Principle**: "Signal Up, Call Down". High decoupling via `EventSystem`. + +## 2. 🛠 Command Reference & Setup +- **Input Map (Required Configuration)**: + - `move_left`, `move_right`, `move_up`, `move_down` (WASD / Arrows) + - `interact` (E Key / Space) + - `pause` (ESC) +- **Run Game**: `godot --path .` +- **Run Tests (GUT)**: `godot --headless -s addons/gut/gut_cmdline.gd -gdir=res://tests/ -ginclude_subdirs` +- **Init Structure**: `mkdir -p _Core/managers _Core/systems Scenes/Maps Scenes/Entities Scenes/Components UI/Windows UI/HUD Assets/Sprites tests/unit tests/integration` + +## 3. 📂 File Path Rules (STRICT LOWERCASE) +*Claude: Root folders MUST be lowercase. Scripts and Scenes MUST stay together.* +- **Core Managers**: `_Core/managers/[Name].gd` +- **Core Systems**: `_Core/systems/[Name].gd` +- **Entities**: `Scenes/Entities/[EntityName]/[EntityName].tscn` (Script `.gd` in same folder). +- **Maps**: `Scenes/Maps/[map_name].tscn` +- **Components**: `Scenes/Components/[ComponentName].gd` (Reusable logic nodes). +- **UI Windows**: `UI/Windows/[WindowName].tscn` +- **Tests**: `tests/[unit|integration]/test_[name].gd` (Folder is lowercase `tests`). + +## 4. 📋 Coding Standards (The Law) +- **Type Safety**: ALWAYS use strict static typing: `var speed: float = 100.0`, `func _ready() -> void`. +- **Naming Conventions**: + - `class_name PascalCase` at the top of every script. + - Variables/Functions: `snake_case`. Constants: `SCREAMING_SNAKE_CASE`. + - Private members: Prefix with underscore `_` (e.g., `var _health: int`). +- **Node Access**: Use `%UniqueName` for UI and internal scene components. +- **Signals**: Use "Signal Up, Call Down". Parent calls child methods; Child emits signals. +- **Forbidden Patterns**: + - ❌ NO `yield()` -> Use `await`. + - ❌ NO `get_node()` in `_process` -> Cache with `@onready`. + - ❌ NO Linear Filter -> All Sprite2D/TileMap resources MUST use **Nearest** filter. + +## 5. 🏛 Architecture & Communication +- **EventSystem**: Use `_Core/systems/EventSystem.gd` for cross-module messaging. +- **Event Registry**: Use `class_name EventNames` in `_Core/EventNames.gd`. + ```gdscript + class_name EventNames + const PLAYER_MOVED = "player_moved" + const INTERACT_PRESSED = "interact_pressed" + const NPC_TALKED = "npc_talked" +Singletons: Only GameManager, SceneManager, EventSystem allowed as Autoloads. +Decoupling: Low-level entities MUST NOT reference GameManager. Use events. +6. 🏗 Implementation Details +Player: CharacterBody2D. Must include Camera2D with position_smoothing_enabled = true. +NPC/Interactables: Use Area2D named InteractionArea. Trigger via EventSystem. +TileMap Layers: +Layer 0: Ground (No collision). +Layer 1: Obstacles (Physics Layer enabled). +Layer 2: Decoration (Y-Sort enabled). +Camera: Must auto-calculate limits via TileMap.get_used_rect(). +7. 🧪 Testing Requirements (MANDATORY) +Coverage: Every Manager/System in _Core/ MUST have a GUT test. +Naming: Test files must start with test_ and extend GutTest. +Example: +code +Gdscript +extends GutTest +func test_event_emission(): + var sender = Node.new() + watch_signals(EventSystem) + EventSystem.emit_event(EventNames.PLAYER_MOVED, {}) + assert_signal_emitted(EventSystem, "event_raised") +8. 🧘 The Zen of Development +Juice or Death: Every interaction (UI popup, NPC talk) MUST have a Tween placeholder. +Zero Magic Numbers: All speeds/timers MUST be @export or defined in Config/. +Simplicity: If a function does two things, split it. +Back of the Fence: Hidden logic (like ResponseHandler.gd) must be as clean as the HUD. +9. 📝 Code Template (Entity Pattern) +code +Gdscript +extends CharacterBody2D +class_name Player + +# 1. Exports & Constants +@export var move_speed: float = 200.0 + +# 2. Node References +@onready var sprite: Sprite2D = %Sprite2D + +# 3. Lifecycle +func _physics_process(delta: float) -> void: + _move(delta) + +# 4. Private Methods +func _move(_delta: float) -> void: + var dir := Input.get_vector("move_left", "move_right", "move_up", "move_down") + velocity = dir * move_speed + move_and_slide() \ No newline at end of file diff --git a/docs/AI_docs/.gitkeep b/docs/AI_docs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docs/AI_docs/quick_reference/troubleshooting.md b/docs/AI_docs/quick_reference/troubleshooting.md new file mode 100644 index 0000000..a8a6fc8 --- /dev/null +++ b/docs/AI_docs/quick_reference/troubleshooting.md @@ -0,0 +1,238 @@ +# 🔧 故障排除指南 + +> AI编程助手专用:常见问题的快速解决方案 + +## 🚨 常见错误及解决方案 + +### 1. UID无效警告 + +**错误信息**: +``` +WARNING: scene/resources/resource_format_text.cpp:444 - res://path/to/file.tscn:X - ext_resource, invalid UID: uid://xxxxx - using text path instead: res://path/to/script.gd +``` + +**原因**: 文件移动后,Godot的UID缓存没有更新,导致UID引用失效。 + +**解决方案**: +```gdscript +# 方法1: 移除无效的UID,使用文本路径 +# 将这行: +[ext_resource type="Script" uid="uid://invalid_uid" path="res://path/to/script.gd" id="1_script"] + +# 改为: +[ext_resource type="Script" path="res://path/to/script.gd" id="1_script"] +``` + +**预防措施**: +- 移动文件时使用Godot编辑器的文件系统面板 +- 避免直接在文件系统中移动.gd和.tscn文件 +- 移动文件后重新导入项目 + +### 2. 脚本路径错误 + +**错误信息**: +``` +ERROR: Failed to load script "res://old/path/Script.gd" with error "File not found". +``` + +**解决方案**: +```gdscript +# 检查并更新所有.tscn文件中的脚本路径 +# 使用搜索替换功能批量更新: + +# 旧路径 → 新路径 +"res://UI/Windows/" → "res://scenes/ui/" +"res://Utils/" → "res://_Core/utils/" +"res://Scenes/Maps/" → "res://scenes/maps/" +``` + +### 3. AutoLoad路径错误 + +**错误信息**: +``` +ERROR: Cannot load autoload: res://old/path/Manager.gd +``` + +**解决方案**: +```ini +# 在 project.godot 中更新 AutoLoad 路径 +[autoload] +GameManager="*res://_Core/managers/GameManager.gd" +SceneManager="*res://_Core/managers/SceneManager.gd" +EventSystem="*res://_Core/systems/EventSystem.gd" +``` + +### 4. 资源加载失败 + +**错误信息**: +``` +ERROR: Failed to load resource "res://old/path/resource.png". +``` + +**解决方案**: +```gdscript +# 检查资源路径是否正确 +# 使用 ResourceLoader.exists() 验证路径 +func load_resource_safely(path: String): + if not ResourceLoader.exists(path): + push_error("Resource not found: %s" % path) + return null + + return load(path) +``` + +## 🔍 调试技巧 + +### 1. 路径验证 +```gdscript +# 验证文件是否存在 +func verify_file_exists(file_path: String) -> bool: + return FileAccess.file_exists(file_path) + +# 验证资源是否存在 +func verify_resource_exists(resource_path: String) -> bool: + return ResourceLoader.exists(resource_path) + +# 打印当前工作目录 +func print_current_directory(): + print("Current directory: ", OS.get_executable_path().get_base_dir()) +``` + +### 2. 场景加载调试 +```gdscript +# 安全的场景加载 +func load_scene_safely(scene_path: String) -> PackedScene: + if not ResourceLoader.exists(scene_path): + push_error("Scene file not found: %s" % scene_path) + return null + + var scene = load(scene_path) as PackedScene + if scene == null: + push_error("Failed to load scene: %s" % scene_path) + return null + + return scene +``` + +### 3. 节点引用调试 +```gdscript +# 安全的节点获取 +func get_node_safely(node_path: String) -> Node: + var node = get_node_or_null(node_path) + if node == null: + push_warning("Node not found: %s" % node_path) + return node + +# 检查@onready变量是否正确初始化 +func _ready(): + # 验证所有@onready节点 + if not some_button: + push_error("some_button not found - check node path") + if not some_label: + push_error("some_label not found - check node path") +``` + +## 🛠️ 项目结构问题 + +### 1. 文件移动后的检查清单 +- [ ] 更新.tscn文件中的脚本路径 +- [ ] 更新project.godot中的AutoLoad路径 +- [ ] 更新代码中的硬编码路径 +- [ ] 清理Godot缓存文件 +- [ ] 重新导入项目 + +### 2. 缓存清理命令 +```bash +# Windows PowerShell +Remove-Item -Recurse -Force ".godot\uid_cache.bin" +Remove-Item -Recurse -Force ".godot\global_script_class_cache.cfg" + +# Linux/macOS +rm -rf .godot/uid_cache.bin +rm -rf .godot/global_script_class_cache.cfg +``` + +### 3. 路径常量管理 +```gdscript +# 在 _Core/ProjectPaths.gd 中定义所有路径 +class_name ProjectPaths + +# 核心路径 +const CORE_ROOT = "res://_Core/" +const MANAGERS_PATH = CORE_ROOT + "managers/" +const SYSTEMS_PATH = CORE_ROOT + "systems/" +const UTILS_PATH = CORE_ROOT + "utils/" + +# 场景路径 +const SCENES_ROOT = "res://scenes/" +const UI_PATH = SCENES_ROOT + "ui/" +const MAPS_PATH = SCENES_ROOT + "maps/" + +# 使用示例 +var scene_path = ProjectPaths.UI_PATH + "LoginWindow.tscn" +``` + +## 🎯 性能问题 + +### 1. 内存泄漏检测 +```gdscript +# 监控节点数量 +func _ready(): + print("Initial node count: ", get_tree().get_node_count()) + +func _exit_tree(): + print("Final node count: ", get_tree().get_node_count()) + +# 检查未释放的资源 +func check_resource_leaks(): + print("Resource count: ", ResourceLoader.get_resource_list().size()) +``` + +### 2. 帧率监控 +```gdscript +# 在 _Core/managers/PerformanceManager.gd +extends Node + +var frame_count: int = 0 +var fps_timer: float = 0.0 + +func _process(delta: float): + frame_count += 1 + fps_timer += delta + + if fps_timer >= 1.0: + var fps = frame_count / fps_timer + if fps < 30: + push_warning("Low FPS detected: %.1f" % fps) + + frame_count = 0 + fps_timer = 0.0 +``` + +## 🔧 开发工具问题 + +### 1. Godot编辑器崩溃 +**解决方案**: +1. 备份项目文件 +2. 删除.godot文件夹 +3. 重新打开项目 +4. 重新导入所有资源 + +### 2. 脚本编辑器问题 +**解决方案**: +```gdscript +# 检查脚本语法 +# 使用 Godot 内置的语法检查器 +# 或者在命令行中运行: +# godot --check-only script.gd +``` + +### 3. 场景编辑器问题 +**解决方案**: +- 检查场景文件是否损坏 +- 重新创建有问题的场景 +- 使用版本控制恢复到工作版本 + +--- + +**💡 提示**: 遇到问题时,首先检查Godot的输出面板和调试器,它们通常会提供详细的错误信息和解决建议。 \ No newline at end of file diff --git a/docs/AI_docs/templates/managers.md b/docs/AI_docs/templates/managers.md new file mode 100644 index 0000000..6edd9ca --- /dev/null +++ b/docs/AI_docs/templates/managers.md @@ -0,0 +1,617 @@ +# 🎯 管理器模板集合 + +> AI编程助手专用:各类管理器的标准化代码模板 + +## 🎮 游戏管理器模板 + +### 基础游戏管理器 +```gdscript +# _Core/managers/GameManager.gd +extends Node + +## 游戏状态管理器 +## 负责管理游戏的全局状态、玩家数据和游戏流程 + +# 信号定义 +signal game_state_changed(old_state: GameState, new_state: GameState) +signal player_data_updated(data: Dictionary) +signal game_paused() +signal game_resumed() + +# 游戏状态枚举 +enum GameState { + LOADING, + MENU, + PLAYING, + PAUSED, + GAME_OVER, + SETTINGS +} + +# 常量定义 +const SAVE_FILE_PATH: String = "user://game_save.dat" +const CONFIG_FILE_PATH: String = "res://Config/game_config.json" + +# 导出变量 +@export var debug_mode: bool = false +@export var auto_save_interval: float = 30.0 + +# 公共变量 +var current_state: GameState = GameState.LOADING +var is_paused: bool = false +var game_time: float = 0.0 + +# 玩家数据 +var player_data: Dictionary = { + "level": 1, + "experience": 0, + "coins": 100, + "health": 100, + "max_health": 100, + "energy": 100, + "max_energy": 100 +} + +# 私有变量 +var _auto_save_timer: Timer +var _game_config: Dictionary = {} + +func _ready() -> void: + _initialize_manager() + _setup_auto_save() + _load_game_config() + change_state(GameState.MENU) + +func _process(delta: float) -> void: + if current_state == GameState.PLAYING and not is_paused: + game_time += delta + +## 改变游戏状态 +func change_state(new_state: GameState) -> void: + if current_state == new_state: + return + + var old_state = current_state + _exit_state(old_state) + current_state = new_state + _enter_state(new_state) + + game_state_changed.emit(old_state, new_state) + + if debug_mode: + print("[GameManager] State changed: %s -> %s" % [old_state, new_state]) + +## 暂停游戏 +func pause_game() -> void: + if current_state != GameState.PLAYING: + return + + is_paused = true + get_tree().paused = true + game_paused.emit() + +## 恢复游戏 +func resume_game() -> void: + if not is_paused: + return + + is_paused = false + get_tree().paused = false + game_resumed.emit() + +## 更新玩家数据 +func update_player_data(key: String, value) -> void: + if not player_data.has(key): + push_warning("Unknown player data key: %s" % key) + return + + player_data[key] = value + player_data_updated.emit(player_data) + + if debug_mode: + print("[GameManager] Player data updated: %s = %s" % [key, value]) + +## 获取玩家数据 +func get_player_data(key: String = ""): + if key.is_empty(): + return player_data.duplicate() + + return player_data.get(key, null) + +## 保存游戏数据 +func save_game() -> bool: + var save_data = { + "player_data": player_data, + "game_time": game_time, + "current_state": current_state, + "timestamp": Time.get_unix_time_from_system() + } + + var file = FileAccess.open(SAVE_FILE_PATH, FileAccess.WRITE) + if file == null: + push_error("Failed to open save file for writing") + return false + + file.store_string(JSON.stringify(save_data)) + file.close() + + if debug_mode: + print("[GameManager] Game saved successfully") + + return true + +## 加载游戏数据 +func load_game() -> bool: + if not FileAccess.file_exists(SAVE_FILE_PATH): + if debug_mode: + print("[GameManager] No save file found") + return false + + var file = FileAccess.open(SAVE_FILE_PATH, FileAccess.READ) + if file == null: + push_error("Failed to open save file for reading") + return false + + var json_text = file.get_as_text() + file.close() + + var json = JSON.new() + var parse_result = json.parse(json_text) + if parse_result != OK: + push_error("Failed to parse save file JSON") + return false + + var save_data = json.data + + # 恢复玩家数据 + if save_data.has("player_data"): + player_data = save_data.player_data + player_data_updated.emit(player_data) + + # 恢复游戏时间 + if save_data.has("game_time"): + game_time = save_data.game_time + + if debug_mode: + print("[GameManager] Game loaded successfully") + + return true + +# 私有方法 +func _initialize_manager() -> void: + # 设置进程模式为总是处理(即使暂停时也能工作) + process_mode = Node.PROCESS_MODE_ALWAYS + +func _setup_auto_save() -> void: + _auto_save_timer = Timer.new() + add_child(_auto_save_timer) + _auto_save_timer.wait_time = auto_save_interval + _auto_save_timer.timeout.connect(_on_auto_save_timeout) + _auto_save_timer.start() + +func _load_game_config() -> void: + if not FileAccess.file_exists(CONFIG_FILE_PATH): + push_warning("Game config file not found") + return + + var file = FileAccess.open(CONFIG_FILE_PATH, FileAccess.READ) + if file == null: + push_error("Failed to open game config file") + return + + var json_text = file.get_as_text() + file.close() + + var json = JSON.new() + var parse_result = json.parse(json_text) + if parse_result != OK: + push_error("Failed to parse game config JSON") + return + + _game_config = json.data + +func _enter_state(state: GameState) -> void: + match state: + GameState.LOADING: + # 加载游戏资源 + pass + GameState.MENU: + # 显示主菜单 + pass + GameState.PLAYING: + # 开始游戏逻辑 + _auto_save_timer.start() + GameState.PAUSED: + # 暂停游戏 + _auto_save_timer.stop() + GameState.GAME_OVER: + # 游戏结束处理 + save_game() + GameState.SETTINGS: + # 显示设置界面 + pass + +func _exit_state(state: GameState) -> void: + match state: + GameState.PLAYING: + # 退出游戏时自动保存 + save_game() + +func _on_auto_save_timeout() -> void: + if current_state == GameState.PLAYING: + save_game() +``` + +## 🌐 网络管理器模板 + +### HTTP请求管理器 +```gdscript +# _Core/managers/NetworkManager.gd +extends Node + +## 网络请求管理器 +## 负责处理所有HTTP请求和网络通信 + +# 信号定义 +signal request_completed(request_id: String, success: bool, data: Dictionary) +signal connection_status_changed(is_connected: bool) + +# 常量定义 +const BASE_URL: String = "https://api.example.com" +const TIMEOUT_DURATION: float = 10.0 +const MAX_RETRIES: int = 3 + +# 请求状态枚举 +enum RequestStatus { + PENDING, + SUCCESS, + FAILED, + TIMEOUT, + CANCELLED +} + +# 公共变量 +var is_connected: bool = false +var active_requests: Dictionary = {} + +# 私有变量 +var _http_client: HTTPClient +var _request_counter: int = 0 + +func _ready() -> void: + _initialize_network() + _check_connection() + +## 发送GET请求 +func send_get_request(endpoint: String, headers: Dictionary = {}) -> String: + return _send_request(HTTPClient.METHOD_GET, endpoint, "", headers) + +## 发送POST请求 +func send_post_request(endpoint: String, data: Dictionary, headers: Dictionary = {}) -> String: + var json_data = JSON.stringify(data) + return _send_request(HTTPClient.METHOD_POST, endpoint, json_data, headers) + +## 发送PUT请求 +func send_put_request(endpoint: String, data: Dictionary, headers: Dictionary = {}) -> String: + var json_data = JSON.stringify(data) + return _send_request(HTTPClient.METHOD_PUT, endpoint, json_data, headers) + +## 发送DELETE请求 +func send_delete_request(endpoint: String, headers: Dictionary = {}) -> String: + return _send_request(HTTPClient.METHOD_DELETE, endpoint, "", headers) + +## 取消请求 +func cancel_request(request_id: String) -> bool: + if not active_requests.has(request_id): + return false + + var request_data = active_requests[request_id] + if request_data.http_request: + request_data.http_request.cancel_request() + + _cleanup_request(request_id, RequestStatus.CANCELLED) + return true + +## 检查网络连接状态 +func check_connection() -> void: + _check_connection() + +# 私有方法 +func _initialize_network() -> void: + _http_client = HTTPClient.new() + +func _send_request(method: HTTPClient.Method, endpoint: String, data: String, headers: Dictionary) -> String: + var request_id = _generate_request_id() + var full_url = BASE_URL + endpoint + + # 创建HTTP请求节点 + var http_request = HTTPRequest.new() + add_child(http_request) + + # 设置请求超时 + http_request.timeout = TIMEOUT_DURATION + + # 连接完成信号 + http_request.request_completed.connect(_on_request_completed.bind(request_id)) + + # 准备请求头 + var request_headers = ["Content-Type: application/json"] + for key in headers: + request_headers.append("%s: %s" % [key, headers[key]]) + + # 存储请求信息 + active_requests[request_id] = { + "http_request": http_request, + "method": method, + "url": full_url, + "status": RequestStatus.PENDING, + "retry_count": 0, + "start_time": Time.get_time_dict_from_system() + } + + # 发送请求 + var error = http_request.request(full_url, request_headers, method, data) + if error != OK: + push_error("Failed to send HTTP request: %d" % error) + _cleanup_request(request_id, RequestStatus.FAILED) + return "" + + return request_id + +func _generate_request_id() -> String: + _request_counter += 1 + return "req_%d_%d" % [Time.get_time_dict_from_system().hour * 3600 + Time.get_time_dict_from_system().minute * 60 + Time.get_time_dict_from_system().second, _request_counter] + +func _on_request_completed(request_id: String, result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void: + if not active_requests.has(request_id): + return + + var request_data = active_requests[request_id] + var success = false + var response_data = {} + + # 解析响应 + if response_code >= 200 and response_code < 300: + success = true + var body_text = body.get_string_from_utf8() + + if not body_text.is_empty(): + var json = JSON.new() + var parse_result = json.parse(body_text) + if parse_result == OK: + response_data = json.data + else: + response_data = {"raw_response": body_text} + else: + # 处理错误响应 + var body_text = body.get_string_from_utf8() + response_data = { + "error": "HTTP Error %d" % response_code, + "response_code": response_code, + "raw_response": body_text + } + + # 发送完成信号 + request_completed.emit(request_id, success, response_data) + + # 清理请求 + _cleanup_request(request_id, RequestStatus.SUCCESS if success else RequestStatus.FAILED) + +func _cleanup_request(request_id: String, status: RequestStatus) -> void: + if not active_requests.has(request_id): + return + + var request_data = active_requests[request_id] + + # 移除HTTP请求节点 + if request_data.http_request: + request_data.http_request.queue_free() + + # 从活动请求中移除 + active_requests.erase(request_id) + +func _check_connection() -> void: + # 简单的连接检查(可以改为ping服务器) + var old_status = is_connected + is_connected = true # 这里可以实现实际的连接检查逻辑 + + if old_status != is_connected: + connection_status_changed.emit(is_connected) +``` + +## 🎵 音频管理器模板 + +### 音频系统管理器 +```gdscript +# _Core/managers/AudioManager.gd +extends Node + +## 音频管理器 +## 负责管理游戏中的音乐和音效播放 + +# 信号定义 +signal music_finished() +signal sound_effect_finished(sound_name: String) + +# 音频类型枚举 +enum AudioType { + MUSIC, + SOUND_EFFECT, + UI_SOUND, + VOICE +} + +# 常量定义 +const MUSIC_PATH: String = "res://assets/audio/music/" +const SOUND_PATH: String = "res://assets/audio/sounds/" +const VOICE_PATH: String = "res://assets/audio/voice/" + +# 导出变量 +@export var master_volume: float = 1.0 +@export var music_volume: float = 0.7 +@export var sfx_volume: float = 0.8 +@export var voice_volume: float = 0.9 + +# 音频播放器 +var music_player: AudioStreamPlayer +var sfx_players: Array[AudioStreamPlayer] = [] +var voice_player: AudioStreamPlayer + +# 当前播放状态 +var current_music: String = "" +var is_music_playing: bool = false +var active_sounds: Dictionary = {} + +func _ready() -> void: + _initialize_audio_players() + _load_audio_settings() + +## 播放背景音乐 +func play_music(music_name: String, fade_in: bool = true) -> void: + var music_path = MUSIC_PATH + music_name + ".ogg" + + if not ResourceLoader.exists(music_path): + push_warning("Music file not found: %s" % music_path) + return + + # 停止当前音乐 + if is_music_playing: + stop_music(fade_in) + await get_tree().create_timer(0.5).timeout + + # 加载并播放新音乐 + var audio_stream = load(music_path) + music_player.stream = audio_stream + music_player.volume_db = linear_to_db(music_volume * master_volume) + + if fade_in: + _fade_in_music() + else: + music_player.play() + + current_music = music_name + is_music_playing = true + +## 停止背景音乐 +func stop_music(fade_out: bool = true) -> void: + if not is_music_playing: + return + + if fade_out: + _fade_out_music() + else: + music_player.stop() + is_music_playing = false + current_music = "" + +## 播放音效 +func play_sound_effect(sound_name: String, volume_override: float = -1.0) -> void: + var sound_path = SOUND_PATH + sound_name + ".ogg" + + if not ResourceLoader.exists(sound_path): + push_warning("Sound file not found: %s" % sound_path) + return + + # 获取可用的音效播放器 + var player = _get_available_sfx_player() + if player == null: + push_warning("No available sound effect player") + return + + # 加载并播放音效 + var audio_stream = load(sound_path) + player.stream = audio_stream + + var final_volume = volume_override if volume_override > 0 else sfx_volume + player.volume_db = linear_to_db(final_volume * master_volume) + + player.play() + active_sounds[sound_name] = player + +## 设置主音量 +func set_master_volume(volume: float) -> void: + master_volume = clamp(volume, 0.0, 1.0) + _update_all_volumes() + +## 设置音乐音量 +func set_music_volume(volume: float) -> void: + music_volume = clamp(volume, 0.0, 1.0) + if is_music_playing: + music_player.volume_db = linear_to_db(music_volume * master_volume) + +## 设置音效音量 +func set_sfx_volume(volume: float) -> void: + sfx_volume = clamp(volume, 0.0, 1.0) + _update_sfx_volumes() + +# 私有方法 +func _initialize_audio_players() -> void: + # 创建音乐播放器 + music_player = AudioStreamPlayer.new() + add_child(music_player) + music_player.finished.connect(_on_music_finished) + + # 创建多个音效播放器(支持同时播放多个音效) + for i in range(8): + var sfx_player = AudioStreamPlayer.new() + add_child(sfx_player) + sfx_players.append(sfx_player) + + # 创建语音播放器 + voice_player = AudioStreamPlayer.new() + add_child(voice_player) + +func _get_available_sfx_player() -> AudioStreamPlayer: + for player in sfx_players: + if not player.playing: + return player + return null + +func _fade_in_music() -> void: + music_player.volume_db = linear_to_db(0.01) + music_player.play() + + var tween = create_tween() + tween.tween_method(_set_music_volume_db, 0.01, music_volume * master_volume, 1.0) + +func _fade_out_music() -> void: + var tween = create_tween() + tween.tween_method(_set_music_volume_db, music_volume * master_volume, 0.01, 1.0) + tween.tween_callback(_stop_music_after_fade) + +func _set_music_volume_db(volume: float) -> void: + music_player.volume_db = linear_to_db(volume) + +func _stop_music_after_fade() -> void: + music_player.stop() + is_music_playing = false + current_music = "" + +func _update_all_volumes() -> void: + if is_music_playing: + music_player.volume_db = linear_to_db(music_volume * master_volume) + _update_sfx_volumes() + +func _update_sfx_volumes() -> void: + for player in sfx_players: + if player.playing: + player.volume_db = linear_to_db(sfx_volume * master_volume) + +func _load_audio_settings() -> void: + # 从配置文件加载音频设置 + pass + +func _on_music_finished() -> void: + is_music_playing = false + current_music = "" + music_finished.emit() +``` + +--- + +**🎯 使用说明**: +1. 选择合适的管理器模板 +2. 根据具体需求修改类名和功能 +3. 确保在project.godot中配置为AutoLoad +4. 遵循项目的命名规范和代码风格 +5. 添加必要的错误处理和日志记录 \ No newline at end of file diff --git a/docs/AI_docs/workflows/feature_development.md b/docs/AI_docs/workflows/feature_development.md new file mode 100644 index 0000000..bd81490 --- /dev/null +++ b/docs/AI_docs/workflows/feature_development.md @@ -0,0 +1,363 @@ +# 🚀 功能开发流程 + +> AI编程助手专用:新功能开发的标准化工作流程 + +## 🎯 开发流程概览 + +### 阶段1: 需求分析 → 阶段2: 架构设计 → 阶段3: 代码实现 → 阶段4: 测试验证 → 阶段5: 文档更新 + +--- + +## 📋 阶段1: 需求分析 + +### 1.1 理解需求 +```markdown +**必须明确的问题:** +- 功能的具体作用是什么? +- 涉及哪些用户交互? +- 需要哪些数据和状态管理? +- 与现有功能的关系如何? +``` + +### 1.2 需求分类 +```gdscript +# 功能类型分类 +enum FeatureType { + CORE_SYSTEM, # 核心系统功能 → 放在 _Core/ + GAME_SCENE, # 游戏场景功能 → 放在 scenes/ + UI_COMPONENT, # UI组件功能 → 放在 scenes/ui/ + ASSET_RELATED, # 资源相关功能 → 涉及 assets/ + CONFIG_DRIVEN # 配置驱动功能 → 涉及 Config/ +} +``` + +### 1.3 依赖分析 +- 需要哪些现有管理器? +- 需要创建新的管理器吗? +- 需要新的事件定义吗? +- 需要新的配置文件吗? + +--- + +## 🏗️ 阶段2: 架构设计 + +### 2.1 确定文件位置 +```gdscript +# 根据功能类型确定文件位置 +match feature_type: + FeatureType.CORE_SYSTEM: + # _Core/managers/ 或 _Core/systems/ + var file_path = "_Core/managers/YourManager.gd" + + FeatureType.GAME_SCENE: + # scenes/maps/, scenes/characters/, scenes/effects/ + var file_path = "scenes/characters/YourCharacter.gd" + + FeatureType.UI_COMPONENT: + # scenes/ui/ + var file_path = "scenes/ui/YourWindow.gd" +``` + +### 2.2 设计接口 +```gdscript +# 定义公共接口 +class_name YourFeature + +# 信号定义(对外通信) +signal feature_initialized() +signal feature_state_changed(new_state: String) + +# 公共方法(供其他模块调用) +func initialize(config: Dictionary) -> bool +func get_state() -> String +func cleanup() -> void +``` + +### 2.3 事件设计 +```gdscript +# 在 _Core/EventNames.gd 中添加新事件 +const YOUR_FEATURE_STARTED: String = "your_feature_started" +const YOUR_FEATURE_COMPLETED: String = "your_feature_completed" +const YOUR_FEATURE_ERROR: String = "your_feature_error" +``` + +--- + +## 💻 阶段3: 代码实现 + +### 3.1 创建基础结构 +```gdscript +# 使用标准模板创建文件 +# 参考: docs/AI_docs/templates/components.md + +extends Node # 或其他合适的基类 + +## [功能描述] +## 负责[具体职责] + +# 信号定义 +signal feature_ready() + +# 枚举定义 +enum FeatureState { + UNINITIALIZED, + INITIALIZING, + READY, + ERROR +} + +# 常量定义 +const CONFIG_PATH: String = "res://Config/your_feature_config.json" + +# 导出变量 +@export var debug_mode: bool = false + +# 公共变量 +var current_state: FeatureState = FeatureState.UNINITIALIZED + +# 私有变量 +var _config_data: Dictionary = {} + +func _ready() -> void: + initialize() +``` + +### 3.2 实现核心逻辑 +```gdscript +## 初始化功能 +func initialize() -> bool: + if current_state != FeatureState.UNINITIALIZED: + push_warning("Feature already initialized") + return false + + current_state = FeatureState.INITIALIZING + + # 加载配置 + if not _load_config(): + current_state = FeatureState.ERROR + return false + + # 连接事件 + _connect_events() + + # 执行初始化逻辑 + _perform_initialization() + + current_state = FeatureState.READY + feature_ready.emit() + return true + +func _load_config() -> bool: + # 配置加载逻辑 + return true + +func _connect_events() -> void: + # 事件连接逻辑 + EventSystem.connect_event("related_event", _on_related_event) + +func _perform_initialization() -> void: + # 具体初始化逻辑 + pass +``` + +### 3.3 错误处理 +```gdscript +func _handle_error(error_message: String) -> void: + push_error("[YourFeature] %s" % error_message) + current_state = FeatureState.ERROR + + # 发送错误事件 + EventSystem.emit_event(EventNames.YOUR_FEATURE_ERROR, { + "message": error_message, + "timestamp": Time.get_unix_time_from_system() + }) +``` + +--- + +## 🧪 阶段4: 测试验证 + +### 4.1 创建测试文件 +```gdscript +# tests/unit/test_your_feature.gd +extends "res://addons/gut/test.gd" + +## YourFeature 单元测试 + +var your_feature: YourFeature + +func before_each(): + your_feature = preload("res://_Core/managers/YourFeature.gd").new() + add_child(your_feature) + +func after_each(): + your_feature.queue_free() + +func test_initialization(): + # 测试初始化 + var result = your_feature.initialize() + assert_true(result, "Feature should initialize successfully") + assert_eq(your_feature.current_state, YourFeature.FeatureState.READY) + +func test_error_handling(): + # 测试错误处理 + # 模拟错误条件 + pass +``` + +### 4.2 集成测试 +```gdscript +# tests/integration/test_your_feature_integration.gd +extends "res://addons/gut/test.gd" + +## YourFeature 集成测试 + +func test_feature_with_event_system(): + # 测试与事件系统的集成 + var event_received = false + + EventSystem.connect_event("your_feature_started", func(data): event_received = true) + + # 触发功能 + # 验证事件是否正确发送 + assert_true(event_received, "Event should be emitted") +``` + +### 4.3 性能测试 +```gdscript +# tests/performance/test_your_feature_performance.gd +extends "res://addons/gut/test.gd" + +## YourFeature 性能测试 + +func test_initialization_performance(): + var start_time = Time.get_time_dict_from_system() + + # 执行功能 + your_feature.initialize() + + var end_time = Time.get_time_dict_from_system() + var duration = _calculate_duration(start_time, end_time) + + # 验证性能要求(例如:初始化应在100ms内完成) + assert_lt(duration, 0.1, "Initialization should complete within 100ms") +``` + +--- + +## 📚 阶段5: 文档更新 + +### 5.1 更新API文档 +```markdown +# 在 docs/AI_docs/quick_reference/api_reference.md 中添加 + +## YourFeature API + +### 初始化 +```gdscript +var feature = YourFeature.new() +feature.initialize(config_dict) +``` + +### 主要方法 +- `initialize(config: Dictionary) -> bool` - 初始化功能 +- `get_state() -> FeatureState` - 获取当前状态 +- `cleanup() -> void` - 清理资源 + +### 事件 +- `feature_ready` - 功能准备就绪 +- `feature_state_changed(new_state)` - 状态改变 +``` + +### 5.2 更新使用示例 +```gdscript +# 在 docs/AI_docs/quick_reference/code_snippets.md 中添加 + +## YourFeature 使用示例 + +### 基本使用 +```gdscript +# 创建和初始化 +var feature = YourFeature.new() +add_child(feature) + +# 连接信号 +feature.feature_ready.connect(_on_feature_ready) + +# 初始化 +var config = {"setting1": "value1"} +feature.initialize(config) + +func _on_feature_ready(): + print("Feature is ready to use") +``` +``` + +### 5.3 更新架构文档 +```markdown +# 在 docs/AI_docs/architecture_guide.md 中更新 + +## 新增功能: YourFeature + +### 位置 +- 文件路径: `_Core/managers/YourFeature.gd` +- AutoLoad: 是/否 +- 依赖: EventSystem, ConfigManager + +### 职责 +- 负责[具体职责描述] +- 管理[相关数据/状态] +- 提供[对外接口] +``` + +--- + +## ✅ 开发检查清单 + +### 代码质量检查 +- [ ] 遵循命名规范(PascalCase类名,snake_case变量名) +- [ ] 所有变量和函数都有类型注解 +- [ ] 添加了适当的注释和文档字符串 +- [ ] 实现了错误处理和边界检查 +- [ ] 使用事件系统进行模块间通信 + +### 架构一致性检查 +- [ ] 文件放在正确的目录中 +- [ ] 如果是管理器,已配置AutoLoad +- [ ] 事件名称已添加到EventNames.gd +- [ ] 配置文件已放在Config/目录 +- [ ] 遵循项目的架构原则 + +### 测试覆盖检查 +- [ ] 编写了单元测试 +- [ ] 编写了集成测试(如果需要) +- [ ] 编写了性能测试(如果是核心功能) +- [ ] 所有测试都能通过 +- [ ] 测试覆盖了主要功能和边界情况 + +### 文档更新检查 +- [ ] 更新了API参考文档 +- [ ] 添加了使用示例 +- [ ] 更新了架构文档 +- [ ] 更新了相关的工作流程文档 + +--- + +## 🔄 迭代优化 + +### 代码审查要点 +1. **功能完整性**: 是否满足所有需求? +2. **性能表现**: 是否存在性能瓶颈? +3. **错误处理**: 是否处理了所有可能的错误情况? +4. **代码可读性**: 代码是否清晰易懂? +5. **测试覆盖**: 测试是否充分? + +### 持续改进 +- 收集用户反馈 +- 监控性能指标 +- 定期重构优化 +- 更新文档和示例 + +--- + +**🎯 记住**: 这个流程确保了功能开发的质量和一致性。严格遵循每个阶段的要求,将大大提高开发效率和代码质量。 \ No newline at end of file diff --git a/scenes/Maps/.gitkeep b/scenes/Maps/.gitkeep new file mode 100644 index 0000000..c8f3ac4 --- /dev/null +++ b/scenes/Maps/.gitkeep @@ -0,0 +1,2 @@ +# 地图场景目录 +# 存放游戏关卡、世界地图等地图场景 \ No newline at end of file diff --git a/scenes/characters/.gitkeep b/scenes/characters/.gitkeep new file mode 100644 index 0000000..c6ef796 --- /dev/null +++ b/scenes/characters/.gitkeep @@ -0,0 +1,2 @@ +# 人物场景目录 +# 存放角色、NPC、敌人等人物相关场景 \ No newline at end of file diff --git a/scenes/effects/.gitkeep b/scenes/effects/.gitkeep new file mode 100644 index 0000000..48501db --- /dev/null +++ b/scenes/effects/.gitkeep @@ -0,0 +1,2 @@ +# 特效场景目录 +# 存放粒子效果、动画等特效场景 \ No newline at end of file diff --git a/scenes/prefabs/.gitkeep b/scenes/prefabs/.gitkeep new file mode 100644 index 0000000..afb3d03 --- /dev/null +++ b/scenes/prefabs/.gitkeep @@ -0,0 +1,2 @@ +# 预制体组件目录 +# 存放可复用的预制体组件 \ No newline at end of file diff --git a/scenes/prefabs/characters/.gitkeep b/scenes/prefabs/characters/.gitkeep new file mode 100644 index 0000000..03176b7 --- /dev/null +++ b/scenes/prefabs/characters/.gitkeep @@ -0,0 +1,2 @@ +# 角色预制体目录 +# 存放可复用的角色组件预制体 \ No newline at end of file diff --git a/scenes/prefabs/effects/.gitkeep b/scenes/prefabs/effects/.gitkeep new file mode 100644 index 0000000..2d4709c --- /dev/null +++ b/scenes/prefabs/effects/.gitkeep @@ -0,0 +1,2 @@ +# 特效预制体目录 +# 存放可复用的特效组件预制体 \ No newline at end of file diff --git a/scenes/prefabs/items/.gitkeep b/scenes/prefabs/items/.gitkeep new file mode 100644 index 0000000..55f90aa --- /dev/null +++ b/scenes/prefabs/items/.gitkeep @@ -0,0 +1,2 @@ +# 物品预制体目录 +# 存放可复用的物品组件预制体 \ No newline at end of file diff --git a/scenes/prefabs/ui/.gitkeep b/scenes/prefabs/ui/.gitkeep new file mode 100644 index 0000000..9371238 --- /dev/null +++ b/scenes/prefabs/ui/.gitkeep @@ -0,0 +1,2 @@ +# UI预制体目录 +# 存放可复用的UI组件预制体 \ No newline at end of file diff --git a/scenes/ui/.gitkeep b/scenes/ui/.gitkeep new file mode 100644 index 0000000..6f9d732 --- /dev/null +++ b/scenes/ui/.gitkeep @@ -0,0 +1,2 @@ +# UI界面场景目录 +# 存放菜单、HUD、对话框等UI界面场景 \ No newline at end of file diff --git a/scenes/ui/AuthScene.gd b/scenes/ui/AuthScene.gd new file mode 100644 index 0000000..485da13 --- /dev/null +++ b/scenes/ui/AuthScene.gd @@ -0,0 +1,1032 @@ +extends Control + +# 信号定义 +signal login_success(username: String) + +# UI节点引用 +@onready var background_image: TextureRect = $BackgroundImage +@onready var login_panel: Panel = $CenterContainer/LoginPanel +@onready var register_panel: Panel = $CenterContainer/RegisterPanel +@onready var title_label: Label = $CenterContainer/LoginPanel/VBoxContainer/TitleLabel +@onready var subtitle_label: Label = $CenterContainer/LoginPanel/VBoxContainer/SubtitleLabel +@onready var whale_frame: TextureRect = $WhaleFrame + +# 登录表单 +@onready var login_username: LineEdit = $CenterContainer/LoginPanel/VBoxContainer/LoginForm/UsernameContainer/UsernameInput +@onready var login_password: LineEdit = $CenterContainer/LoginPanel/VBoxContainer/LoginForm/PasswordContainer/PasswordInput +@onready var login_verification: LineEdit = $CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationInputContainer/VerificationInput +@onready var login_username_error: Label = $CenterContainer/LoginPanel/VBoxContainer/LoginForm/UsernameContainer/UsernameLabelContainer/UsernameError +@onready var login_password_error: Label = $CenterContainer/LoginPanel/VBoxContainer/LoginForm/PasswordContainer/PasswordLabelContainer/PasswordError +@onready var login_verification_error: Label = $CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationLabelContainer/VerificationError +@onready var password_container: VBoxContainer = $CenterContainer/LoginPanel/VBoxContainer/LoginForm/PasswordContainer +@onready var verification_container: VBoxContainer = $CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer +@onready var get_code_btn: Button = $CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationInputContainer/GetCodeBtn +@onready var main_btn: Button = $CenterContainer/LoginPanel/VBoxContainer/MainButton +@onready var login_btn: Button = $CenterContainer/LoginPanel/VBoxContainer/ButtonContainer/LoginBtn +@onready var forgot_password_btn: Button = $CenterContainer/LoginPanel/VBoxContainer/BottomLinks/ForgotPassword +@onready var register_link_btn: Button = $CenterContainer/LoginPanel/VBoxContainer/BottomLinks/RegisterLink + +# 注册表单 +@onready var register_username: LineEdit = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/UsernameContainer/UsernameInput +@onready var register_email: LineEdit = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/EmailContainer/EmailInput +@onready var register_password: LineEdit = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/PasswordContainer/PasswordInput +@onready var register_confirm: LineEdit = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/ConfirmContainer/ConfirmInput +@onready var verification_input: LineEdit = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer/VerificationInputContainer/VerificationInput +@onready var send_code_btn: Button = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer/VerificationInputContainer/SendCodeBtn +@onready var register_btn: Button = $CenterContainer/RegisterPanel/VBoxContainer/ButtonContainer/RegisterBtn +@onready var to_login_btn: Button = $CenterContainer/RegisterPanel/VBoxContainer/ButtonContainer/ToLoginBtn + +# 错误提示标签 +@onready var register_username_error: Label = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/UsernameContainer/UsernameLabelContainer/UsernameError +@onready var register_email_error: Label = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/EmailContainer/EmailLabelContainer/EmailError +@onready var register_password_error: Label = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/PasswordContainer/PasswordLabelContainer/PasswordError +@onready var register_confirm_error: Label = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/ConfirmContainer/ConfirmLabelContainer/ConfirmError +@onready var verification_error: Label = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer/VerificationLabelContainer/VerificationError + +# Toast消息节点 +@onready var toast_container: Control = $ToastContainer + +# Toast管理 +var active_toasts: Array = [] +var toast_counter: int = 0 + +# 验证码状态 +var verification_codes_sent: Dictionary = {} +var code_cooldown: float = 60.0 +var cooldown_timer: Timer = null +var current_email: String = "" + +# 登录模式枚举 +enum LoginMode { + PASSWORD, # 密码登录模式 + VERIFICATION # 验证码登录模式 +} + +# 当前登录模式 +var current_login_mode: LoginMode = LoginMode.PASSWORD + +# 网络请求管理 +var active_request_ids: Array = [] + +func _ready(): + connect_signals() + show_login_panel() + update_login_mode_ui() # 初始化登录模式UI + await get_tree().process_frame + print("认证系统已加载") + test_network_connection() + +func test_network_connection(): + print("=== 测试网络连接 ===") + + var request_id = NetworkManager.get_app_status(_on_network_test_response) + if request_id != "": + active_request_ids.append(request_id) + print("网络测试请求已发送,ID: ", request_id) + else: + print("网络连接测试失败") +func connect_signals(): + # 主要按钮 + main_btn.pressed.connect(_on_main_button_pressed) + + # 登录界面按钮 + login_btn.pressed.connect(_on_login_pressed) + forgot_password_btn.pressed.connect(_on_forgot_password_pressed) + register_link_btn.pressed.connect(_on_register_link_pressed) + get_code_btn.pressed.connect(_on_get_login_code_pressed) + + # 注册界面按钮 + register_btn.pressed.connect(_on_register_pressed) + to_login_btn.pressed.connect(_on_to_login_pressed) + send_code_btn.pressed.connect(_on_send_code_pressed) + + # 回车键登录 + login_password.text_submitted.connect(_on_login_enter) + + # 登录表单失焦验证 + login_username.focus_exited.connect(_on_login_username_focus_exited) + login_password.focus_exited.connect(_on_login_password_focus_exited) + login_verification.focus_exited.connect(_on_login_verification_focus_exited) + + # 注册表单失焦验证 + register_username.focus_exited.connect(_on_register_username_focus_exited) + register_email.focus_exited.connect(_on_register_email_focus_exited) + register_password.focus_exited.connect(_on_register_password_focus_exited) + register_confirm.focus_exited.connect(_on_register_confirm_focus_exited) + verification_input.focus_exited.connect(_on_verification_focus_exited) + + # 实时输入验证 + register_username.text_changed.connect(_on_register_username_text_changed) + register_email.text_changed.connect(_on_register_email_text_changed) + register_password.text_changed.connect(_on_register_password_text_changed) + register_confirm.text_changed.connect(_on_register_confirm_text_changed) + verification_input.text_changed.connect(_on_verification_text_changed) + +func show_login_panel(): + login_panel.visible = true + register_panel.visible = false + login_username.grab_focus() + +func show_register_panel(): + login_panel.visible = false + register_panel.visible = true + register_username.grab_focus() + +# 更新登录模式UI +func update_login_mode_ui(): + if current_login_mode == LoginMode.PASSWORD: + # 密码登录模式 + login_btn.text = "验证码登录" + forgot_password_btn.text = "忘记密码" + + # 显示密码输入框,隐藏验证码输入框 + password_container.visible = true + verification_container.visible = false + + # 清空验证码输入框和错误提示 + login_verification.text = "" + hide_field_error(login_verification_error) + + else: # VERIFICATION mode + # 验证码登录模式 + login_btn.text = "密码登录" + forgot_password_btn.text = "获取验证码" + + # 隐藏密码输入框,显示验证码输入框 + password_container.visible = false + verification_container.visible = true + + # 清空密码输入框和错误提示 + login_password.text = "" + hide_field_error(login_password_error) + # 这里需要根据实际UI结构调整 + +# 切换登录模式 +func toggle_login_mode(): + if current_login_mode == LoginMode.PASSWORD: + current_login_mode = LoginMode.VERIFICATION + else: + current_login_mode = LoginMode.PASSWORD + + update_login_mode_ui() + + # 清空输入框 + login_username.text = "" + login_password.text = "" + hide_field_error(login_username_error) + hide_field_error(login_password_error) + +# ============ 按钮事件处理 ============ + +func _on_main_button_pressed(): + # 根据当前登录模式执行不同的登录逻辑 + if current_login_mode == LoginMode.PASSWORD: + _execute_password_login() + else: + _execute_verification_login() + +func _execute_password_login(): + if not validate_login_form(): + return + + var username = login_username.text.strip_edges() + var password = login_password.text + + show_loading(main_btn, "登录中...") + show_toast('正在验证登录信息...', true) + + var request_id = NetworkManager.login(username, password, _on_login_response) + if request_id != "": + active_request_ids.append(request_id) + else: + restore_button(main_btn, "进入小镇") + show_toast('网络请求失败', false) + +func _execute_verification_login(): + var identifier = login_username.text.strip_edges() + var verification_code = login_verification.text.strip_edges() + + if identifier.is_empty(): + show_field_error(login_username_error, "请输入用户名/手机/邮箱") + login_username.grab_focus() + return + + if verification_code.is_empty(): + show_field_error(login_verification_error, "请输入验证码") + login_verification.grab_focus() + return + + show_loading(main_btn, "登录中...") + show_toast('正在验证验证码...', true) + + var request_id = NetworkManager.verification_code_login(identifier, verification_code, _on_verification_login_response) + if request_id != "": + active_request_ids.append(request_id) + else: + restore_button(main_btn, "进入小镇") + show_toast('网络请求失败', false) + +func _on_login_pressed(): + # 现在这个按钮用于切换登录模式 + toggle_login_mode() + +func _on_register_pressed(): + print("注册按钮被点击") + + if not validate_register_form(): + print("注册表单验证失败") + show_toast('请检查并完善注册信息', false) + return + + print("注册表单验证通过,开始注册流程") + + var username = register_username.text.strip_edges() + var email = register_email.text.strip_edges() + var password = register_password.text + var verification_code = verification_input.text.strip_edges() + + show_loading(register_btn, "注册中...") + show_toast('正在创建账户...', true) + + # 直接调用注册接口,让服务器端处理验证码验证 + send_register_request(username, email, password, verification_code) + +func _on_send_code_pressed(): + var email = register_email.text.strip_edges() + + var email_validation = validate_email(email) + if not email_validation.valid: + show_toast(email_validation.message, false) + register_email.grab_focus() + return + + hide_field_error(register_email_error) + + # 检查冷却时间 + var current_time = Time.get_time_dict_from_system() + var current_timestamp = current_time.hour * 3600 + current_time.minute * 60 + current_time.second + + if verification_codes_sent.has(email): + var email_data = verification_codes_sent[email] + if email_data.sent and (current_timestamp - email_data.time) < code_cooldown: + var remaining = code_cooldown - (current_timestamp - email_data.time) + show_toast('该邮箱请等待 %d 秒后再次发送' % remaining, false) + return + + if current_email != email: + stop_current_cooldown() + current_email = email + + if not verification_codes_sent.has(email): + verification_codes_sent[email] = {} + + verification_codes_sent[email].sent = true + verification_codes_sent[email].time = current_timestamp + start_cooldown_timer(email) + + var request_id = NetworkManager.send_email_verification(email, _on_send_code_response) + if request_id != "": + active_request_ids.append(request_id) + else: + show_toast('网络请求失败', false) + reset_verification_button() + +func _on_register_link_pressed(): + show_register_panel() + +func _on_get_login_code_pressed(): + var identifier = login_username.text.strip_edges() + + if identifier.is_empty(): + show_field_error(login_username_error, "请先输入用户名/手机/邮箱") + login_username.grab_focus() + return + + show_loading(get_code_btn, "发送中...") + show_toast('正在发送登录验证码...', true) + + var request_id = NetworkManager.send_login_verification_code(identifier, _on_send_login_code_response) + if request_id != "": + active_request_ids.append(request_id) + else: + restore_button(get_code_btn, "获取验证码") + show_toast('网络请求失败', false) + +func _on_to_login_pressed(): + show_login_panel() + +func _on_login_enter(_text: String): + _on_login_pressed() + +func _on_forgot_password_pressed(): + var identifier = login_username.text.strip_edges() + + if identifier.is_empty(): + show_toast('请先输入邮箱或手机号', false) + login_username.grab_focus() + return + + if not is_valid_email(identifier) and not is_valid_phone(identifier): + show_toast('请输入有效的邮箱或手机号', false) + login_username.grab_focus() + return + + if current_login_mode == LoginMode.PASSWORD: + # 密码登录模式:发送密码重置验证码 + show_loading(forgot_password_btn, "发送中...") + show_toast('正在发送密码重置验证码...', true) + + var request_id = NetworkManager.forgot_password(identifier, _on_forgot_password_response) + if request_id != "": + active_request_ids.append(request_id) + else: + restore_button(forgot_password_btn, "忘记密码") + show_toast('网络请求失败', false) + else: + # 验证码登录模式:发送登录验证码 + show_loading(forgot_password_btn, "发送中...") + show_toast('正在发送登录验证码...', true) + + var request_id = NetworkManager.send_login_verification_code(identifier, _on_send_login_code_response) + if request_id != "": + active_request_ids.append(request_id) + else: + restore_button(forgot_password_btn, "获取验证码") + show_toast('网络请求失败', false) +# ============ 网络响应处理 ============ + +func send_register_request(username: String, email: String, password: String, verification_code: String = ""): + var request_id = NetworkManager.register(username, password, username, email, verification_code, _on_register_response) + if request_id != "": + active_request_ids.append(request_id) + else: + show_toast('网络请求失败', false) + restore_button(register_btn, "注册") + +func _on_network_test_response(success: bool, data: Dictionary, error_info: Dictionary): + print("=== 网络测试响应处理 ===") + print("成功: ", success) + print("数据: ", data) + + var result = ResponseHandler.handle_network_test_response(success, data, error_info) + + if result.should_show_toast: + show_toast(result.message, result.success) + +func _on_login_response(success: bool, data: Dictionary, error_info: Dictionary): + print("=== 登录响应处理 ===") + print("成功: ", success) + print("数据: ", data) + print("错误信息: ", error_info) + + restore_button(main_btn, "进入小镇") + + var result = ResponseHandler.handle_login_response(success, data, error_info) + + if result.should_show_toast: + show_toast(result.message, result.success) + + if result.success: + var username = login_username.text.strip_edges() + if data.has("data") and data.data.has("user") and data.data.user.has("username"): + username = data.data.user.username + + login_username.text = "" + login_password.text = "" + hide_field_error(login_username_error) + hide_field_error(login_password_error) + + await get_tree().create_timer(1.0).timeout + login_success.emit(username) + +func _on_send_code_response(success: bool, data: Dictionary, error_info: Dictionary): + print("=== 发送验证码响应处理 ===") + print("成功: ", success) + print("数据: ", data) + + var result = ResponseHandler.handle_send_verification_code_response(success, data, error_info) + + if result.should_show_toast: + show_toast(result.message, result.success) + + if not result.success: + reset_verification_button() + +func _on_send_login_code_response(success: bool, data: Dictionary, error_info: Dictionary): + print("=== 发送登录验证码响应处理 ===") + print("成功: ", success) + print("数据: ", data) + + # 恢复按钮状态 + if current_login_mode == LoginMode.PASSWORD: + restore_button(forgot_password_btn, "忘记密码") + else: + restore_button(forgot_password_btn, "获取验证码") + restore_button(get_code_btn, "获取验证码") + + var result = ResponseHandler.handle_send_login_code_response(success, data, error_info) + + if result.should_show_toast: + show_toast(result.message, result.success) + +func _on_verification_login_response(success: bool, data: Dictionary, error_info: Dictionary): + print("=== 验证码登录响应处理 ===") + print("成功: ", success) + print("数据: ", data) + print("错误信息: ", error_info) + + restore_button(main_btn, "进入小镇") + + var result = ResponseHandler.handle_verification_code_login_response(success, data, error_info) + + if result.should_show_toast: + show_toast(result.message, result.success) + + if result.success: + var username = login_username.text.strip_edges() + if data.has("data") and data.data.has("user") and data.data.user.has("username"): + username = data.data.user.username + + login_username.text = "" + hide_field_error(login_username_error) + + await get_tree().create_timer(1.0).timeout + login_success.emit(username) + +func _on_forgot_password_response(success: bool, data: Dictionary, error_info: Dictionary): + print("=== 忘记密码响应处理 ===") + print("成功: ", success) + print("数据: ", data) + + restore_button(forgot_password_btn, "忘记密码") + + # 使用通用的发送验证码响应处理 + var result = ResponseHandler.handle_send_login_code_response(success, data, error_info) + + if result.should_show_toast: + show_toast(result.message, result.success) + + if result.success: + show_toast("密码重置验证码已发送,请查收邮件", true) + +func _on_register_response(success: bool, data: Dictionary, error_info: Dictionary): + print("=== 注册响应处理 ===") + print("成功: ", success) + print("数据: ", data) + + restore_button(register_btn, "注册") + + var result = ResponseHandler.handle_register_response(success, data, error_info) + + if result.should_show_toast: + show_toast(result.message, result.success) + + if result.success: + clear_register_form() + show_login_panel() + login_username.text = register_username.text.strip_edges() # 使用注册时的用户名 +# ============ 验证码冷却管理 ============ + +func start_cooldown_timer(email: String): + if cooldown_timer != null: + cooldown_timer.queue_free() + + current_email = email + + send_code_btn.disabled = true + send_code_btn.text = "重新发送(60)" + + cooldown_timer = Timer.new() + add_child(cooldown_timer) + cooldown_timer.wait_time = 1.0 + cooldown_timer.timeout.connect(_on_cooldown_timer_timeout) + cooldown_timer.start() + +func _on_cooldown_timer_timeout(): + var input_email = register_email.text.strip_edges() + + if input_email != current_email: + stop_current_cooldown() + return + + if verification_codes_sent.has(current_email): + var current_time = Time.get_time_dict_from_system() + var current_timestamp = current_time.hour * 3600 + current_time.minute * 60 + current_time.second + var email_data = verification_codes_sent[current_email] + var remaining = code_cooldown - (current_timestamp - email_data.time) + + if remaining > 0: + send_code_btn.text = "重新发送(%d)" % remaining + else: + send_code_btn.text = "重新发送" + send_code_btn.disabled = false + + if cooldown_timer != null: + cooldown_timer.queue_free() + cooldown_timer = null + current_email = "" + +func stop_current_cooldown(): + if cooldown_timer != null: + cooldown_timer.queue_free() + cooldown_timer = null + + send_code_btn.disabled = false + send_code_btn.text = "发送验证码" + current_email = "" + +func reset_verification_button(): + if current_email != "" and verification_codes_sent.has(current_email): + verification_codes_sent[current_email].sent = false + + stop_current_cooldown() + +func clear_register_form(): + register_username.text = "" + register_email.text = "" + register_password.text = "" + register_confirm.text = "" + verification_input.text = "" + + stop_current_cooldown() + verification_codes_sent.clear() + + hide_field_error(register_username_error) + hide_field_error(register_email_error) + hide_field_error(register_password_error) + hide_field_error(register_confirm_error) + hide_field_error(verification_error) + +# ============ Toast消息系统 ============ + +# 检测文本是否包含中文字符 +func contains_chinese(text: String) -> bool: + for i in range(text.length()): + var char_code = text.unicode_at(i) + # 中文字符的Unicode范围 + if (char_code >= 0x4E00 and char_code <= 0x9FFF) or \ + (char_code >= 0x3400 and char_code <= 0x4DBF) or \ + (char_code >= 0x20000 and char_code <= 0x2A6DF): + return true + return false + +func show_toast(message: String, is_success: bool = true): + print("显示Toast消息: ", message, " 成功: ", is_success) + + if toast_container == null: + print("错误: toast_container 节点不存在") + return + + # 异步创建Toast实例 + create_toast_instance(message, is_success) + +func create_toast_instance(message: String, is_success: bool): + toast_counter += 1 + + # Web平台字体处理 + var is_web = OS.get_name() == "Web" + + # 1. 创建Toast Panel(方框UI) + var toast_panel = Panel.new() + toast_panel.name = "Toast_" + str(toast_counter) + + # 设置Toast样式 + var style = StyleBoxFlat.new() + 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.border_width_left = 3 + style.border_width_top = 3 + style.border_width_right = 3 + style.border_width_bottom = 3 + style.corner_radius_top_left = 12 + style.corner_radius_top_right = 12 + style.corner_radius_bottom_left = 12 + style.corner_radius_bottom_right = 12 + style.shadow_color = Color(0, 0, 0, 0.3) + style.shadow_size = 4 + style.shadow_offset = Vector2(2, 2) + + toast_panel.add_theme_stylebox_override("panel", style) + + # 设置Toast基本尺寸 + var toast_width = 320 + toast_panel.size = Vector2(toast_width, 60) + + # 2. 创建VBoxContainer + var vbox = VBoxContainer.new() + vbox.add_theme_constant_override("separation", 0) + vbox.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + vbox.alignment = BoxContainer.ALIGNMENT_CENTER + + # 3. 创建CenterContainer + var center_container = CenterContainer.new() + center_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL + center_container.size_flags_vertical = Control.SIZE_SHRINK_CENTER + + # 4. 创建Label(文字控件) + var text_label = Label.new() + text_label.text = message + text_label.add_theme_color_override("font_color", Color(1, 1, 1, 1)) + text_label.add_theme_font_size_override("font_size", 14) + + # 平台特定的字体处理 + if is_web: + print("Web平台Toast字体处理") + # Web平台使用主题文件 + var chinese_theme = load("res://assets/ui/chinese_theme.tres") + if chinese_theme: + text_label.theme = chinese_theme + print("Web平台应用中文主题") + else: + print("Web平台中文主题加载失败") + else: + print("桌面平台Toast字体处理") + # 桌面平台直接加载中文字体 + var desktop_chinese_font = load("res://assets/fonts/msyh.ttc") + if desktop_chinese_font: + text_label.add_theme_font_override("font", desktop_chinese_font) + print("桌面平台使用中文字体") + + text_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + text_label.custom_minimum_size = Vector2(280, 0) + text_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + text_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + + # 组装控件层级 + center_container.add_child(text_label) + vbox.add_child(center_container) + toast_panel.add_child(vbox) + + # 计算位置 + var margin = 20 + var start_x = get_viewport().get_visible_rect().size.x + var final_x = get_viewport().get_visible_rect().size.x - toast_width - margin + + # 计算Y位置 + var y_position = margin + for existing_toast in active_toasts: + if is_instance_valid(existing_toast): + y_position += existing_toast.size.y + 15 + + # 设置初始位置 + toast_panel.position = Vector2(start_x, y_position) + + # 添加到容器 + toast_container.add_child(toast_panel) + active_toasts.append(toast_panel) + + # 等待一帧让布局系统计算尺寸 + await get_tree().process_frame + + # 让Toast高度自适应内容 + var content_size = vbox.get_combined_minimum_size() + var final_height = max(60, content_size.y + 20) # 最小60,加20像素边距 + toast_panel.size.y = final_height + + # 重新排列所有Toast + rearrange_toasts() + + # 开始动画 + animate_toast_in(toast_panel, final_x) + +func animate_toast_in(toast_panel: Panel, final_x: float): + var tween = create_tween() + tween.set_ease(Tween.EASE_OUT) + tween.set_trans(Tween.TRANS_BACK) + + tween.parallel().tween_property(toast_panel, "position:x", final_x, 0.6) + tween.parallel().tween_property(toast_panel, "modulate:a", 1.0, 0.4) + + toast_panel.modulate.a = 0.0 + + await get_tree().create_timer(3.0).timeout + animate_toast_out(toast_panel) + +func animate_toast_out(toast_panel: Panel): + if not is_instance_valid(toast_panel): + return + + var tween = create_tween() + tween.set_ease(Tween.EASE_IN) + tween.set_trans(Tween.TRANS_QUART) + + var end_x = get_viewport().get_visible_rect().size.x + 50 + tween.parallel().tween_property(toast_panel, "position:x", end_x, 0.4) + tween.parallel().tween_property(toast_panel, "modulate:a", 0.0, 0.3) + + await tween.finished + cleanup_toast(toast_panel) + +func cleanup_toast(toast_panel: Panel): + if not is_instance_valid(toast_panel): + return + + active_toasts.erase(toast_panel) + rearrange_toasts() + toast_panel.queue_free() + +func rearrange_toasts(): + var margin = 20 + var current_y = margin + + for i in range(active_toasts.size()): + var toast = active_toasts[i] + if is_instance_valid(toast): + var tween = create_tween() + tween.tween_property(toast, "position:y", current_y, 0.2) + current_y += toast.size.y + 15 + +# ============ UI工具方法 ============ + +func show_loading(button: Button, loading_text: String): + button.disabled = true + button.text = loading_text + +func restore_button(button: Button, original_text: String): + button.disabled = false + button.text = original_text + +func show_field_error(error_label: Label, message: String): + error_label.text = message + error_label.visible = true + +func hide_field_error(error_label: Label): + error_label.visible = false + +func is_valid_email(email: String) -> bool: + return StringUtils.is_valid_email(email) + +func is_valid_phone(phone: String) -> bool: + var regex = RegEx.new() + regex.compile("^\\+?[1-9]\\d{1,14}$") + return regex.search(phone) != null + +# ============ 表单验证方法 ============ + +func validate_username(username: String) -> Dictionary: + var result = {"valid": false, "message": ""} + + if username.is_empty(): + result.message = "用户名不能为空" + return result + + if not StringUtils.is_valid_username(username): + if username.length() > 50: + result.message = "用户名长度不能超过50字符" + else: + result.message = "用户名只能包含字母、数字和下划线" + return result + + result.valid = true + return result + +func validate_email(email: String) -> Dictionary: + var result = {"valid": false, "message": ""} + + if email.is_empty(): + result.message = "邮箱不能为空" + return result + + if not StringUtils.is_valid_email(email): + result.message = "请输入有效的邮箱地址" + return result + + result.valid = true + return result + +func validate_password(password: String) -> Dictionary: + return StringUtils.validate_password_strength(password) + +func validate_confirm_password(password: String, confirm: String) -> Dictionary: + var result = {"valid": false, "message": ""} + + if confirm.is_empty(): + result.message = "确认密码不能为空" + return result + + if password != confirm: + result.message = "两次输入的密码不一致" + return result + + result.valid = true + return result + +func validate_verification_code(code: String) -> Dictionary: + var result = {"valid": false, "message": ""} + + if code.is_empty(): + result.message = "验证码不能为空" + return result + + if code.length() != 6: + result.message = "验证码必须是6位数字" + return result + + for i in range(code.length()): + var character = code[i] + if not (character >= '0' and character <= '9'): + result.message = "验证码必须是6位数字" + return result + + result.valid = true + return result + +# ============ 表单验证事件 ============ + +func _on_login_username_focus_exited(): + var username = login_username.text.strip_edges() + if username.is_empty(): + show_field_error(login_username_error, "用户名不能为空") + else: + hide_field_error(login_username_error) + +func _on_login_password_focus_exited(): + var password = login_password.text + if password.is_empty(): + show_field_error(login_password_error, "密码不能为空") + else: + hide_field_error(login_password_error) + +func _on_login_verification_focus_exited(): + var verification_code = login_verification.text.strip_edges() + if verification_code.is_empty(): + show_field_error(login_verification_error, "验证码不能为空") + elif verification_code.length() != 6: + show_field_error(login_verification_error, "验证码必须是6位数字") + elif not verification_code.is_valid_int(): + show_field_error(login_verification_error, "验证码只能包含数字") + else: + hide_field_error(login_verification_error) + +func _on_register_username_focus_exited(): + var username = register_username.text.strip_edges() + var validation = validate_username(username) + if not validation.valid: + show_field_error(register_username_error, validation.message) + else: + hide_field_error(register_username_error) + +func _on_register_email_focus_exited(): + var email = register_email.text.strip_edges() + var validation = validate_email(email) + if not validation.valid: + show_field_error(register_email_error, validation.message) + else: + hide_field_error(register_email_error) + +func _on_register_password_focus_exited(): + var password = register_password.text + var validation = validate_password(password) + if not validation.valid: + show_field_error(register_password_error, validation.message) + else: + hide_field_error(register_password_error) + if not register_confirm.text.is_empty(): + _on_register_confirm_focus_exited() + +func _on_register_confirm_focus_exited(): + var password = register_password.text + var confirm = register_confirm.text + var validation = validate_confirm_password(password, confirm) + if not validation.valid: + show_field_error(register_confirm_error, validation.message) + else: + hide_field_error(register_confirm_error) + +func _on_verification_focus_exited(): + var code = verification_input.text.strip_edges() + var validation = validate_verification_code(code) + if not validation.valid: + show_field_error(verification_error, validation.message) + else: + hide_field_error(verification_error) + +# ============ 实时输入验证事件 ============ + +func _on_register_username_text_changed(new_text: String): + if register_username_error.visible and not new_text.is_empty(): + hide_field_error(register_username_error) + +func _on_register_email_text_changed(new_text: String): + if register_email_error.visible and not new_text.is_empty(): + hide_field_error(register_email_error) + +func _on_register_password_text_changed(new_text: String): + if register_password_error.visible and not new_text.is_empty(): + hide_field_error(register_password_error) + +func _on_register_confirm_text_changed(new_text: String): + if register_confirm_error.visible and not new_text.is_empty(): + hide_field_error(register_confirm_error) + +func _on_verification_text_changed(new_text: String): + if verification_error.visible and not new_text.is_empty(): + hide_field_error(verification_error) +# ============ 表单整体验证 ============ + +func validate_login_form() -> bool: + var is_valid = true + + var username = login_username.text.strip_edges() + var password = login_password.text + + if username.is_empty(): + show_field_error(login_username_error, "用户名不能为空") + is_valid = false + else: + hide_field_error(login_username_error) + + if password.is_empty(): + show_field_error(login_password_error, "密码不能为空") + is_valid = false + else: + hide_field_error(login_password_error) + + return is_valid + +func validate_register_form() -> bool: + print("开始验证注册表单") + var is_valid = true + + var username = register_username.text.strip_edges() + var email = register_email.text.strip_edges() + var password = register_password.text + var confirm = register_confirm.text + var verification_code = verification_input.text.strip_edges() + + print("表单数据: 用户名='%s', 邮箱='%s', 密码长度=%d, 确认密码长度=%d, 验证码='%s'" % [username, email, password.length(), confirm.length(), verification_code]) + + var username_validation = validate_username(username) + if not username_validation.valid: + print("用户名验证失败: ", username_validation.message) + show_field_error(register_username_error, username_validation.message) + is_valid = false + else: + hide_field_error(register_username_error) + + var email_validation = validate_email(email) + if not email_validation.valid: + print("邮箱验证失败: ", email_validation.message) + show_field_error(register_email_error, email_validation.message) + is_valid = false + else: + hide_field_error(register_email_error) + + var password_validation = validate_password(password) + if not password_validation.valid: + print("密码验证失败: ", password_validation.message) + show_field_error(register_password_error, password_validation.message) + is_valid = false + else: + hide_field_error(register_password_error) + + var confirm_validation = validate_confirm_password(password, confirm) + if not confirm_validation.valid: + print("确认密码验证失败: ", confirm_validation.message) + show_field_error(register_confirm_error, confirm_validation.message) + is_valid = false + else: + hide_field_error(register_confirm_error) + + var code_validation = validate_verification_code(verification_code) + if not code_validation.valid: + print("验证码格式验证失败: ", code_validation.message) + show_field_error(verification_error, code_validation.message) + is_valid = false + else: + hide_field_error(verification_error) + + var current_email_input = register_email.text.strip_edges() + var has_sent_code = false + + if verification_codes_sent.has(current_email_input): + var email_data = verification_codes_sent[current_email_input] + has_sent_code = email_data.get("sent", false) + + if not has_sent_code: + print("当前邮箱验证码未发送,email = ", current_email_input) + show_toast("请先获取邮箱验证码", false) + is_valid = false + + print("表单验证结果: ", is_valid) + return is_valid + +# ============ 资源清理 ============ + +func _exit_tree(): + for request_id in active_request_ids: + NetworkManager.cancel_request(request_id) + active_request_ids.clear() + + if cooldown_timer != null: + cooldown_timer.queue_free() + cooldown_timer = null + +func _input(event): + if event.is_action_pressed("ui_cancel"): + get_tree().quit() \ No newline at end of file diff --git a/scenes/ui/AuthScene.gd.uid b/scenes/ui/AuthScene.gd.uid new file mode 100644 index 0000000..97b8ff9 --- /dev/null +++ b/scenes/ui/AuthScene.gd.uid @@ -0,0 +1 @@ +uid://b514h2wuido0h diff --git a/scenes/ui/LoginWindow.tscn b/scenes/ui/LoginWindow.tscn new file mode 100644 index 0000000..3cf8c76 --- /dev/null +++ b/scenes/ui/LoginWindow.tscn @@ -0,0 +1,561 @@ +[gd_scene load_steps=10 format=3 uid="uid://by7m8snb4xllf"] + +[ext_resource type="Texture2D" uid="uid://bx17oy8lvaca4" path="res://assets/ui/auth/bg_auth_scene.png" id="1_background"] +[ext_resource type="Texture2D" uid="uid://de4q4s1gxivtf" path="res://assets/ui/auth/login_frame_smart_transparent.png" id="2_frame"] +[ext_resource type="Script" path="res://scenes/ui/AuthScene.gd" id="3_script"] + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_1"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_hover"] +bg_color = Color(0.3, 0.6, 0.9, 1) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color(0.2, 0.5, 0.8, 1) +corner_radius_top_left = 8 +corner_radius_top_right = 8 +corner_radius_bottom_right = 8 +corner_radius_bottom_left = 8 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_normal"] +bg_color = Color(0.2, 0.5, 0.8, 1) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color(0.15, 0.4, 0.7, 1) +corner_radius_top_left = 8 +corner_radius_top_right = 8 +corner_radius_bottom_right = 8 +corner_radius_bottom_left = 8 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_pressed"] +bg_color = Color(0.4, 0.7, 1, 1) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color(0.3, 0.6, 0.9, 1) +corner_radius_top_left = 8 +corner_radius_top_right = 8 +corner_radius_bottom_right = 8 +corner_radius_bottom_left = 8 + +[sub_resource type="Theme" id="Theme_main_button"] +Button/colors/font_color = Color(1, 1, 1, 1) +Button/colors/font_hover_color = Color(1, 1, 1, 1) +Button/colors/font_pressed_color = Color(1, 1, 1, 1) +Button/font_sizes/font_size = 18 +Button/styles/hover = SubResource("StyleBoxFlat_hover") +Button/styles/normal = SubResource("StyleBoxFlat_normal") +Button/styles/pressed = SubResource("StyleBoxFlat_pressed") + +[sub_resource type="Theme" id="Theme_button"] +Button/colors/font_color = Color(1, 1, 1, 1) +Button/colors/font_hover_color = Color(1, 1, 1, 1) +Button/colors/font_pressed_color = Color(1, 1, 1, 1) +Button/styles/hover = SubResource("StyleBoxFlat_hover") +Button/styles/normal = SubResource("StyleBoxFlat_normal") +Button/styles/pressed = SubResource("StyleBoxFlat_pressed") + +[node name="AuthScene" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("3_script") + +[node name="HTTPRequest" type="HTTPRequest" parent="."] + +[node name="BackgroundImage" type="TextureRect" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("1_background") +expand_mode = 1 +stretch_mode = 6 + +[node name="WhaleFrame" type="TextureRect" parent="."] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -300.0 +offset_top = -300.0 +offset_right = 300.0 +offset_bottom = 300.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("2_frame") +expand_mode = 1 +stretch_mode = 5 + +[node name="CenterContainer" type="CenterContainer" parent="."] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -175.0 +offset_top = -184.0 +offset_right = 175.0 +offset_bottom = 236.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="LoginPanel" type="Panel" parent="CenterContainer"] +custom_minimum_size = Vector2(350, 400) +layout_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxEmpty_1") + +[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer/LoginPanel"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 30.0 +offset_top = 30.0 +offset_right = -30.0 +offset_bottom = -30.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="TitleLabel" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(0, 0, 0, 1) +theme_override_font_sizes/font_size = 24 +text = "Whaletown" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="SubtitleLabel" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(0, 0, 0, 1) +theme_override_font_sizes/font_size = 14 +text = "开始你的小镇之旅!" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="HSeparator" type="HSeparator" parent="CenterContainer/LoginPanel/VBoxContainer"] +layout_mode = 2 + +[node name="LoginForm" type="VBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="UsernameContainer" type="VBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm"] +layout_mode = 2 + +[node name="UsernameLabelContainer" type="HBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/UsernameContainer"] +layout_mode = 2 + +[node name="UsernameLabel" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/UsernameContainer/UsernameLabelContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(0, 0, 0, 1) +text = "用户名/手机/邮箱" + +[node name="RequiredStar" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/UsernameContainer/UsernameLabelContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) +text = " *" + +[node name="Spacer" type="Control" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/UsernameContainer/UsernameLabelContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="UsernameError" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/UsernameContainer/UsernameLabelContainer"] +visible = false +layout_mode = 2 +theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) +theme_override_font_sizes/font_size = 12 +text = "用户名不能为空" +horizontal_alignment = 2 + +[node name="UsernameInput" type="LineEdit" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/UsernameContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(0, 0, 0, 1) +theme_override_colors/font_placeholder_color = Color(0.5, 0.5, 0.5, 1) +placeholder_text = "用户名/手机/邮箱" + +[node name="PasswordContainer" type="VBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm"] +layout_mode = 2 + +[node name="PasswordLabelContainer" type="HBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/PasswordContainer"] +layout_mode = 2 + +[node name="PasswordLabel" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/PasswordContainer/PasswordLabelContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(0, 0, 0, 1) +text = "密码" + +[node name="RequiredStar" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/PasswordContainer/PasswordLabelContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) +text = " *" + +[node name="Spacer" type="Control" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/PasswordContainer/PasswordLabelContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="PasswordError" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/PasswordContainer/PasswordLabelContainer"] +visible = false +layout_mode = 2 +theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) +theme_override_font_sizes/font_size = 12 +text = "密码不能为空" +horizontal_alignment = 2 + +[node name="PasswordInput" type="LineEdit" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/PasswordContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(0, 0, 0, 1) +theme_override_colors/font_placeholder_color = Color(0.5, 0.5, 0.5, 1) +placeholder_text = "请输入密码" +secret = true + +[node name="VerificationContainer" type="VBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm"] +visible = false +layout_mode = 2 + +[node name="VerificationLabelContainer" type="HBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer"] +layout_mode = 2 + +[node name="VerificationLabel" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationLabelContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(0, 0, 0, 1) +text = "验证码" + +[node name="RequiredStar" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationLabelContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) +text = " *" + +[node name="Spacer" type="Control" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationLabelContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="VerificationError" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationLabelContainer"] +visible = false +layout_mode = 2 +theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) +theme_override_font_sizes/font_size = 12 +text = "请输入验证码" +horizontal_alignment = 2 + +[node name="VerificationInputContainer" type="HBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer"] +layout_mode = 2 + +[node name="VerificationInput" type="LineEdit" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationInputContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_colors/font_color = Color(0, 0, 0, 1) +theme_override_colors/font_placeholder_color = Color(0.5, 0.5, 0.5, 1) +placeholder_text = "请输入6位验证码" +max_length = 6 + +[node name="GetCodeBtn" type="Button" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationInputContainer"] +layout_mode = 2 +text = "获取验证码" + +[node name="CheckboxContainer" type="HBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm"] +layout_mode = 2 + +[node name="RememberPassword" type="CheckBox" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/CheckboxContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(0, 0, 0, 1) +text = "记住密码" + +[node name="AutoLogin" type="CheckBox" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/CheckboxContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(0, 0, 0, 1) +text = "自动登录" + +[node name="HSeparator2" type="HSeparator" parent="CenterContainer/LoginPanel/VBoxContainer"] +layout_mode = 2 + +[node name="MainButton" type="Button" parent="CenterContainer/LoginPanel/VBoxContainer"] +custom_minimum_size = Vector2(280, 50) +layout_mode = 2 +theme = SubResource("Theme_main_button") +text = "进入小镇" + +[node name="HSeparator3" type="HSeparator" parent="CenterContainer/LoginPanel/VBoxContainer"] +layout_mode = 2 + +[node name="ButtonContainer" type="HBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer"] +layout_mode = 2 +alignment = 1 + +[node name="LoginBtn" type="Button" parent="CenterContainer/LoginPanel/VBoxContainer/ButtonContainer"] +custom_minimum_size = Vector2(100, 35) +layout_mode = 2 +theme = SubResource("Theme_button") +text = "密码登录" + +[node name="BottomLinks" type="HBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer"] +layout_mode = 2 +alignment = 1 + +[node name="ForgotPassword" type="Button" parent="CenterContainer/LoginPanel/VBoxContainer/BottomLinks"] +layout_mode = 2 +theme_override_colors/font_color = Color(0, 0, 0, 1) +text = "忘记密码?" +flat = true + +[node name="RegisterLink" type="Button" parent="CenterContainer/LoginPanel/VBoxContainer/BottomLinks"] +layout_mode = 2 +theme_override_colors/font_color = Color(0, 0, 0, 1) +text = "注册居民身份" +flat = true + +[node name="RegisterPanel" type="Panel" parent="CenterContainer"] +visible = false +custom_minimum_size = Vector2(400, 570) +layout_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxEmpty_1") + +[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer/RegisterPanel"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 30.0 +offset_top = 75.0 +offset_right = -30.0 +offset_bottom = -72.0 +grow_horizontal = 2 +grow_vertical = 2 +alignment = 1 + +[node name="TitleLabel" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(0, 0, 0, 1) +theme_override_font_sizes/font_size = 20 +text = "注册新居民" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="HSeparator" type="HSeparator" parent="CenterContainer/RegisterPanel/VBoxContainer"] +layout_mode = 2 + +[node name="RegisterForm" type="VBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +alignment = 1 + +[node name="UsernameContainer" type="VBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm"] +layout_mode = 2 + +[node name="UsernameLabelContainer" type="HBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/UsernameContainer"] +layout_mode = 2 + +[node name="UsernameLabel" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/UsernameContainer/UsernameLabelContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(0, 0, 0, 1) +text = "用户名" + +[node name="RequiredStar" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/UsernameContainer/UsernameLabelContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) +text = " *" + +[node name="Spacer" type="Control" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/UsernameContainer/UsernameLabelContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="UsernameError" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/UsernameContainer/UsernameLabelContainer"] +visible = false +layout_mode = 2 +theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) +theme_override_font_sizes/font_size = 12 +text = "用户名不能为空" +horizontal_alignment = 2 + +[node name="UsernameInput" type="LineEdit" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/UsernameContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(0, 0, 0, 1) +theme_override_colors/font_placeholder_color = Color(0.5, 0.5, 0.5, 1) +placeholder_text = "请输入用户名" + +[node name="EmailContainer" type="VBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm"] +layout_mode = 2 + +[node name="EmailLabelContainer" type="HBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/EmailContainer"] +layout_mode = 2 + +[node name="EmailLabel" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/EmailContainer/EmailLabelContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(0, 0, 0, 1) +text = "邮箱" + +[node name="RequiredStar" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/EmailContainer/EmailLabelContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) +text = " *" + +[node name="Spacer" type="Control" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/EmailContainer/EmailLabelContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="EmailError" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/EmailContainer/EmailLabelContainer"] +visible = false +layout_mode = 2 +theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) +theme_override_font_sizes/font_size = 12 +text = "邮箱不能为空" +horizontal_alignment = 2 + +[node name="EmailInput" type="LineEdit" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/EmailContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(0, 0, 0, 1) +theme_override_colors/font_placeholder_color = Color(0.5, 0.5, 0.5, 1) +placeholder_text = "请输入邮箱地址" + +[node name="PasswordContainer" type="VBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm"] +layout_mode = 2 + +[node name="PasswordLabelContainer" type="HBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/PasswordContainer"] +layout_mode = 2 + +[node name="PasswordLabel" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/PasswordContainer/PasswordLabelContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(0, 0, 0, 1) +text = "密码" + +[node name="RequiredStar" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/PasswordContainer/PasswordLabelContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) +text = " *" + +[node name="Spacer" type="Control" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/PasswordContainer/PasswordLabelContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="PasswordError" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/PasswordContainer/PasswordLabelContainer"] +visible = false +layout_mode = 2 +theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) +theme_override_font_sizes/font_size = 12 +text = "密码不能为空" +horizontal_alignment = 2 + +[node name="PasswordInput" type="LineEdit" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/PasswordContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(0, 0, 0, 1) +theme_override_colors/font_placeholder_color = Color(0.5, 0.5, 0.5, 1) +placeholder_text = "请输入密码(至少8位)" +secret = true + +[node name="ConfirmContainer" type="VBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm"] +layout_mode = 2 + +[node name="ConfirmLabelContainer" type="HBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/ConfirmContainer"] +layout_mode = 2 + +[node name="ConfirmLabel" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/ConfirmContainer/ConfirmLabelContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(0, 0, 0, 1) +text = "确认密码" + +[node name="RequiredStar" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/ConfirmContainer/ConfirmLabelContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) +text = " *" + +[node name="Spacer" type="Control" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/ConfirmContainer/ConfirmLabelContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="ConfirmError" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/ConfirmContainer/ConfirmLabelContainer"] +visible = false +layout_mode = 2 +theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) +theme_override_font_sizes/font_size = 12 +text = "确认密码不能为空" +horizontal_alignment = 2 + +[node name="ConfirmInput" type="LineEdit" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/ConfirmContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(0, 0, 0, 1) +theme_override_colors/font_placeholder_color = Color(0.5, 0.5, 0.5, 1) +placeholder_text = "请再次输入密码" +secret = true + +[node name="VerificationContainer" type="VBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm"] +layout_mode = 2 + +[node name="VerificationLabelContainer" type="HBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer"] +layout_mode = 2 + +[node name="VerificationLabel" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer/VerificationLabelContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(0, 0, 0, 1) +text = "邮箱验证码" + +[node name="RequiredStar" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer/VerificationLabelContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) +text = " *" + +[node name="Spacer" type="Control" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer/VerificationLabelContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="VerificationError" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer/VerificationLabelContainer"] +visible = false +layout_mode = 2 +theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) +theme_override_font_sizes/font_size = 12 +text = "验证码不能为空" +horizontal_alignment = 2 + +[node name="VerificationInputContainer" type="HBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer"] +layout_mode = 2 + +[node name="VerificationInput" type="LineEdit" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer/VerificationInputContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_colors/font_color = Color(0, 0, 0, 1) +theme_override_colors/font_placeholder_color = Color(0.5, 0.5, 0.5, 1) +placeholder_text = "请输入6位验证码" +max_length = 6 + +[node name="SendCodeBtn" type="Button" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer/VerificationInputContainer"] +layout_mode = 2 +text = "发送验证码" + +[node name="HSeparator2" type="HSeparator" parent="CenterContainer/RegisterPanel/VBoxContainer"] +layout_mode = 2 + +[node name="ButtonContainer" type="HBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer"] +layout_mode = 2 +alignment = 1 + +[node name="RegisterBtn" type="Button" parent="CenterContainer/RegisterPanel/VBoxContainer/ButtonContainer"] +custom_minimum_size = Vector2(120, 45) +layout_mode = 2 +theme = SubResource("Theme_button") +text = "注册" + +[node name="ToLoginBtn" type="Button" parent="CenterContainer/RegisterPanel/VBoxContainer/ButtonContainer"] +custom_minimum_size = Vector2(120, 45) +layout_mode = 2 +theme = SubResource("Theme_button") +text = "返回登录" + +[node name="ToastContainer" type="Control" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 From f1a60137e1a4775a968bc8f5e50ec47d4d27cde6 Mon Sep 17 00:00:00 2001 From: moyin <2443444649@qq.com> Date: Fri, 2 Jan 2026 00:58:51 +0800 Subject: [PATCH 4/6] =?UTF-8?q?fix=EF=BC=9A=E4=BF=AE=E5=A4=8D=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E8=AD=A6=E5=91=8A=E5=92=8CUID=E5=86=B2=E7=AA=81?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新 ProjectPaths.gd 中的路径引用,适配新的目录结构 - 修复 SceneManager.gd 中的场景路径问题 - 更新 project.godot 配置,修复 AutoLoad 路径 - 修复 MainScene 相关文件的 UID 冲突 - 解决代码中的路径引用警告 --- _Core/ProjectPaths.gd | 6 +- _Core/managers/SceneManager.gd | 14 ++-- project.godot | 2 +- scenes/Maps/MainScene.gd | 2 +- scenes/Maps/MainScene.gd.uid | 2 +- scenes/Maps/main_scene.tscn | 137 +++++++++------------------------ 6 files changed, 48 insertions(+), 115 deletions(-) diff --git a/_Core/ProjectPaths.gd b/_Core/ProjectPaths.gd index bdcbf2a..7df5859 100644 --- a/_Core/ProjectPaths.gd +++ b/_Core/ProjectPaths.gd @@ -31,8 +31,8 @@ const SCENES_ITEM_COMPONENTS = SCENES_COMPONENTS + "items/" # ============================================================================ # UI路径 # ============================================================================ -const UI_ROOT = "res://UI/" -const UI_WINDOWS = UI_ROOT + "Windows/" +const UI_ROOT = "res://scenes/ui/" +const UI_WINDOWS = UI_ROOT # ============================================================================ # 资源路径 @@ -69,7 +69,7 @@ const TESTS_AUTH = TESTS_ROOT + "auth/" # ============================================================================ # 工具路径 # ============================================================================ -const UTILS_ROOT = "res://Utils/" +const UTILS_ROOT = "res://_Core/utils/" # ============================================================================ # 模块路径 diff --git a/_Core/managers/SceneManager.gd b/_Core/managers/SceneManager.gd index 1b8b5f9..ec0ef7e 100644 --- a/_Core/managers/SceneManager.gd +++ b/_Core/managers/SceneManager.gd @@ -11,13 +11,13 @@ var is_changing_scene: bool = false # 场景路径映射 var scene_paths: Dictionary = { - "main": "res://Scenes/Maps/main_scene.tscn", - "auth": "res://UI/Windows/LoginWindow.tscn", - "game": "res://Scenes/Maps/game_scene.tscn", - "battle": "res://Scenes/Maps/battle_scene.tscn", - "inventory": "res://UI/Windows/InventoryWindow.tscn", - "shop": "res://UI/Windows/ShopWindow.tscn", - "settings": "res://UI/Windows/SettingsWindow.tscn" + "main": "res://scenes/maps/main_scene.tscn", + "auth": "res://scenes/ui/LoginWindow.tscn", + "game": "res://scenes/maps/game_scene.tscn", + "battle": "res://scenes/maps/battle_scene.tscn", + "inventory": "res://scenes/ui/InventoryWindow.tscn", + "shop": "res://scenes/ui/ShopWindow.tscn", + "settings": "res://scenes/ui/SettingsWindow.tscn" } func _ready(): diff --git a/project.godot b/project.godot index aeeb2d3..49e9865 100644 --- a/project.godot +++ b/project.godot @@ -11,7 +11,7 @@ config_version=5 [application] config/name="whaleTown" -run/main_scene="res://Scenes/Maps/main_scene.tscn" +run/main_scene="res://scenes/maps/main_scene.tscn" config/features=PackedStringArray("4.5", "Forward Plus") config/icon="res://icon.svg" diff --git a/scenes/Maps/MainScene.gd b/scenes/Maps/MainScene.gd index 004ae0d..3818a01 100644 --- a/scenes/Maps/MainScene.gd +++ b/scenes/Maps/MainScene.gd @@ -113,4 +113,4 @@ func _input(event): get_tree().quit() GameState.MAIN_GAME: # 在游戏中按ESC可能显示菜单或返回登录 - show_auth_scene() + show_auth_scene() \ No newline at end of file diff --git a/scenes/Maps/MainScene.gd.uid b/scenes/Maps/MainScene.gd.uid index 4feac40..2d135e3 100644 --- a/scenes/Maps/MainScene.gd.uid +++ b/scenes/Maps/MainScene.gd.uid @@ -1 +1 @@ -uid://blp30m0tuach8 +uid://cn2xjgj3h847p diff --git a/scenes/Maps/main_scene.tscn b/scenes/Maps/main_scene.tscn index c7df691..cb02a22 100644 --- a/scenes/Maps/main_scene.tscn +++ b/scenes/Maps/main_scene.tscn @@ -1,28 +1,14 @@ -[gd_scene load_steps=4 format=3 uid="uid://4ptgx76y83mx"] +[gd_scene load_steps=3 format=3 uid="uid://21a49e14a0c58d7941d04142a5bf9ddc"] -[ext_resource type="Texture2D" uid="uid://bx17oy8lvaca4" path="res://assets/ui/auth/bg_auth_scene.png" id="1_background"] -[ext_resource type="PackedScene" uid="uid://by7m8snb4xllf" path="res://UI/Windows/LoginWindow.tscn" id="2_main"] -[ext_resource type="Script" uid="uid://blp30m0tuach8" path="res://Scenes/Maps/MainScene.gd" id="3_script"] +[ext_resource type="Script" path="res://scenes/maps/MainScene.gd" id="1_script"] +[ext_resource type="PackedScene" uid="uid://by7m8snb4xllf" path="res://scenes/ui/LoginWindow.tscn" id="2_main"] [node name="Main" type="Control"] layout_mode = 3 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -script = ExtResource("3_script") - -[node name="BackgroundImage" type="TextureRect" parent="."] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -texture = ExtResource("1_background") -expand_mode = 1 -stretch_mode = 6 +script = ExtResource("1_script") [node name="AuthScene" parent="." instance=ExtResource("2_main")] layout_mode = 1 @@ -33,24 +19,12 @@ layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 - -[node name="UIOverlay" type="ColorRect" parent="MainGameUI"] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -color = Color(0, 0, 0, 0.3) [node name="TopBar" type="Panel" parent="MainGameUI"] layout_mode = 1 anchors_preset = 10 anchor_right = 1.0 offset_bottom = 60.0 -grow_horizontal = 2 [node name="HBoxContainer" type="HBoxContainer" parent="MainGameUI/TopBar"] layout_mode = 1 @@ -61,26 +35,16 @@ offset_left = 20.0 offset_top = 10.0 offset_right = -20.0 offset_bottom = -10.0 -grow_horizontal = 2 -grow_vertical = 2 - -[node name="WelcomeLabel" type="Label" parent="MainGameUI/TopBar/HBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -theme_override_colors/font_color = Color(1, 1, 1, 1) -text = "🐋 欢迎来到鲸鱼镇!" -vertical_alignment = 1 [node name="UserLabel" type="Label" parent="MainGameUI/TopBar/HBoxContainer"] layout_mode = 2 -theme_override_colors/font_color = Color(1, 1, 1, 1) -text = "当前用户: [用户名]" -horizontal_alignment = 2 +size_flags_horizontal = 3 +text = "当前用户: " vertical_alignment = 1 [node name="LogoutButton" type="Button" parent="MainGameUI/TopBar/HBoxContainer"] layout_mode = 2 -text = "退出" +text = "登出" [node name="MainContent" type="Control" parent="MainGameUI"] layout_mode = 1 @@ -88,80 +52,29 @@ anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 offset_top = 60.0 -grow_horizontal = 2 -grow_vertical = 2 [node name="CenterContainer" type="CenterContainer" parent="MainGameUI/MainContent"] layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 [node name="VBoxContainer" type="VBoxContainer" parent="MainGameUI/MainContent/CenterContainer"] -custom_minimum_size = Vector2(600, 400) -layout_mode = 2 - -[node name="GameTitle" type="Label" parent="MainGameUI/MainContent/CenterContainer/VBoxContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(1, 1, 1, 1) -theme_override_font_sizes/font_size = 24 -text = "🏘️ 鲸鱼镇主界面" -horizontal_alignment = 1 - -[node name="HSeparator" type="HSeparator" parent="MainGameUI/MainContent/CenterContainer/VBoxContainer"] -layout_mode = 2 - -[node name="GameMenuGrid" type="GridContainer" parent="MainGameUI/MainContent/CenterContainer/VBoxContainer"] -layout_mode = 2 -size_flags_vertical = 3 -columns = 2 - -[node name="ExploreButton" type="Button" parent="MainGameUI/MainContent/CenterContainer/VBoxContainer/GameMenuGrid"] -custom_minimum_size = Vector2(280, 80) -layout_mode = 2 -text = "🗺️ 探索小镇" - -[node name="InventoryButton" type="Button" parent="MainGameUI/MainContent/CenterContainer/VBoxContainer/GameMenuGrid"] -custom_minimum_size = Vector2(280, 80) -layout_mode = 2 -text = "🎒 背包物品" - -[node name="ShopButton" type="Button" parent="MainGameUI/MainContent/CenterContainer/VBoxContainer/GameMenuGrid"] -custom_minimum_size = Vector2(280, 80) -layout_mode = 2 -text = "🏪 商店购物" - -[node name="FriendsButton" type="Button" parent="MainGameUI/MainContent/CenterContainer/VBoxContainer/GameMenuGrid"] -custom_minimum_size = Vector2(280, 80) -layout_mode = 2 -text = "👥 好友列表" - -[node name="HSeparator2" type="HSeparator" parent="MainGameUI/MainContent/CenterContainer/VBoxContainer"] layout_mode = 2 [node name="StatusPanel" type="Panel" parent="MainGameUI/MainContent/CenterContainer/VBoxContainer"] -custom_minimum_size = Vector2(0, 120) layout_mode = 2 +custom_minimum_size = Vector2(400, 150) -[node name="StatusContainer" type="VBoxContainer" parent="MainGameUI/MainContent/CenterContainer/VBoxContainer/StatusPanel"] +[node name="StatusContainer" type="MarginContainer" parent="MainGameUI/MainContent/CenterContainer/VBoxContainer/StatusPanel"] layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 offset_left = 20.0 -offset_top = 10.0 +offset_top = 20.0 offset_right = -20.0 -offset_bottom = -10.0 -grow_horizontal = 2 -grow_vertical = 2 - -[node name="StatusTitle" type="Label" parent="MainGameUI/MainContent/CenterContainer/VBoxContainer/StatusPanel/StatusContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(1, 1, 1, 1) -text = "📊 玩家状态" -horizontal_alignment = 1 +offset_bottom = -20.0 [node name="StatusGrid" type="GridContainer" parent="MainGameUI/MainContent/CenterContainer/VBoxContainer/StatusPanel/StatusContainer"] layout_mode = 2 @@ -169,20 +82,40 @@ columns = 2 [node name="LevelLabel" type="Label" parent="MainGameUI/MainContent/CenterContainer/VBoxContainer/StatusPanel/StatusContainer/StatusGrid"] layout_mode = 2 -theme_override_colors/font_color = Color(1, 1, 1, 1) text = "等级: 1" [node name="CoinsLabel" type="Label" parent="MainGameUI/MainContent/CenterContainer/VBoxContainer/StatusPanel/StatusContainer/StatusGrid"] layout_mode = 2 -theme_override_colors/font_color = Color(1, 1, 1, 1) text = "金币: 100" [node name="ExpLabel" type="Label" parent="MainGameUI/MainContent/CenterContainer/VBoxContainer/StatusPanel/StatusContainer/StatusGrid"] layout_mode = 2 -theme_override_colors/font_color = Color(1, 1, 1, 1) text = "经验: 0/100" [node name="EnergyLabel" type="Label" parent="MainGameUI/MainContent/CenterContainer/VBoxContainer/StatusPanel/StatusContainer/StatusGrid"] layout_mode = 2 -theme_override_colors/font_color = Color(1, 1, 1, 1) text = "体力: 100/100" + +[node name="GameMenuGrid" type="GridContainer" parent="MainGameUI/MainContent/CenterContainer/VBoxContainer"] +layout_mode = 2 +columns = 2 + +[node name="ExploreButton" type="Button" parent="MainGameUI/MainContent/CenterContainer/VBoxContainer/GameMenuGrid"] +layout_mode = 2 +custom_minimum_size = Vector2(150, 50) +text = "🗺️ 探索小镇" + +[node name="InventoryButton" type="Button" parent="MainGameUI/MainContent/CenterContainer/VBoxContainer/GameMenuGrid"] +layout_mode = 2 +custom_minimum_size = Vector2(150, 50) +text = "🎒 背包" + +[node name="ShopButton" type="Button" parent="MainGameUI/MainContent/CenterContainer/VBoxContainer/GameMenuGrid"] +layout_mode = 2 +custom_minimum_size = Vector2(150, 50) +text = "🏪 商店" + +[node name="FriendsButton" type="Button" parent="MainGameUI/MainContent/CenterContainer/VBoxContainer/GameMenuGrid"] +layout_mode = 2 +custom_minimum_size = Vector2(150, 50) +text = "👥 好友" \ No newline at end of file From d80feaa02b2f597d886045aceca9f85a11780ea7 Mon Sep 17 00:00:00 2001 From: moyin <2443444649@qq.com> Date: Fri, 2 Jan 2026 00:59:05 +0800 Subject: [PATCH 5/6] =?UTF-8?q?test=EF=BC=9A=E6=9B=B4=E6=96=B0=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E6=96=87=E4=BB=B6=E4=BB=A5=E9=80=82=E9=85=8D=E6=96=B0?= =?UTF-8?q?=E7=9A=84=E9=A1=B9=E7=9B=AE=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新 auth_ui_test.tscn 中的场景引用路径 - 修复 enhanced_toast_test.gd 中的脚本路径引用 - 确保测试文件与重构后的项目结构保持一致 --- tests/auth/auth_ui_test.tscn | 2 +- tests/auth/enhanced_toast_test.gd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/auth/auth_ui_test.tscn b/tests/auth/auth_ui_test.tscn index cb00105..0285847 100644 --- a/tests/auth/auth_ui_test.tscn +++ b/tests/auth/auth_ui_test.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=4 format=3 uid="uid://bvn8y7x2qkqxe"] [ext_resource type="Script" uid="uid://ddb8v5c6aeqe7" path="res://tests/auth/auth_ui_test.gd" id="1_test_script"] -[ext_resource type="PackedScene" uid="uid://by7m8snb4xllf" path="res://UI/Windows/LoginWindow.tscn" id="2_auth_scene"] +[ext_resource type="PackedScene" uid="uid://by7m8snb4xllf" path="res://scenes/ui/LoginWindow.tscn" id="2_auth_scene"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_feedback_info"] bg_color = Color(0.2, 0.5, 0.8, 0.9) diff --git a/tests/auth/enhanced_toast_test.gd b/tests/auth/enhanced_toast_test.gd index fb0a889..082ac87 100644 --- a/tests/auth/enhanced_toast_test.gd +++ b/tests/auth/enhanced_toast_test.gd @@ -6,7 +6,7 @@ class_name EnhancedToastTest # 测试新增的错误码处理 func test_new_error_codes(): - var auth_scene = preload("res://UI/Windows/AuthScene.gd").new() + var auth_scene = preload("res://scenes/ui/AuthScene.gd").new() # 测试验证码登录失败 var verification_login_error = { From fa360e1c78bb2bbc9db8e18127e2028440a17445 Mon Sep 17 00:00:00 2001 From: moyin <2443444649@qq.com> Date: Fri, 2 Jan 2026 00:59:25 +0800 Subject: [PATCH 6/6] =?UTF-8?q?docs=EF=BC=9A=E6=9B=B4=E6=96=B0=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E6=96=87=E6=A1=A3=E4=BB=A5=E5=8F=8D=E6=98=A0=E6=96=B0?= =?UTF-8?q?=E7=9A=84=E7=BB=93=E6=9E=84=E5=8F=98=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新 README.md 中的项目结构说明 - 修订项目结构说明文档,反映最新的目录组织 - 确保文档与实际项目结构保持同步 --- README.md | 78 +++-- docs/01-项目入门/项目结构说明.md | 361 ++++++++++++--------- 2 files changed, 264 insertions(+), 175 deletions(-) diff --git a/README.md b/README.md index 6506686..72a21ff 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ python tests/api/api_client_test.py 1. **[📖 项目入门总览](docs/01-项目入门/README.md)** - 5分钟了解项目 2. **[🏗️ 项目结构说明](docs/01-项目入门/项目结构说明.md)** - 理解架构设计 3. **[⚙️ 项目设置指南](docs/01-项目入门/项目设置指南.md)** - 配置开发环境 +4. **[🤖 AI开发指南](docs/AI_docs/README.md)** - AI编程助手专用文档 ### 🎯 第二步:学习规范 @@ -121,34 +122,65 @@ WhaleTown/ # 🐋 项目根目录 │ ├── 03-技术实现/ # 🔧 开发指导 │ ├── 04-高级开发/ # 🚀 进阶技巧 │ ├── 05-部署运维/ # 🌐 发布部署 -│ └── 06-功能模块/ # 🎮 功能文档 -├── 🎬 scenes/ # 🎭 游戏场景 -│ ├── Maps/ # 🗺️ 地图场景 -│ └── Components/ # 🧩 组件预制体 -├── 🔧 _Core/ # ⚙️ 核心系统 -│ ├── managers/ # 🎯 全局管理器 -│ ├── systems/ # 🔄 系统组件 +│ ├── 06-功能模块/ # 🎮 功能文档 +│ └── AI_docs/ # 🤖 AI专用文档(执行规范、代码模板) +├── 🔧 _Core/ # ⚙️ 核心底层实现 +│ ├── managers/ # 🎯 全局管理器(游戏状态、场景、网络等) +│ ├── systems/ # 🔄 系统组件(事件系统、输入系统等) +│ ├── components/ # 🧩 基础组件实现 +│ ├── utils/ # � 核件心工具类(字符串处理、数学计算等) │ ├── EventNames.gd # 📝 事件名称定义 -│ └── ProjectPaths.gd # 📂 路径统一管理 -├── 🎨 UI/ # 🖼️ 用户界面 -│ └── Windows/ # 🪟 窗口界面 -├── 🔨 Utils/ # 🔨 工具类 -├── 🎮 module/ # 🧩 功能模块 -├── 🎨 assets/ # 🖼️ 游戏资源 -├── ⚙️ Config/ # 📋 配置文件 -├── 🧪 tests/ # 🔬 测试文件 -└── 🌐 web_assets/ # 🌍 Web部署资源 +│ └── ProjectPaths.gd # � 路径组统一管理 +├── 🎬 scenes/ # 🎭 场景与视觉呈现 +│ ├── maps/ # �️ 地图一场景(游戏关卡、世界地图) +│ ├── characters/ # 👤 人物场景(角色、NPC、敌人) +│ ├── ui/ # 🖼️ UI界面场景(菜单、HUD、对话框) +│ ├── effects/ # ✨ 特效场景(粒子效果、动画) +│ └── prefabs/ # 🧩 预制体组件 +├── 🎨 assets/ # 🖼️ 静态资源存储 +│ ├── sprites/ # 🎨 精灵图片(角色、物品、环境) +│ ├── audio/ # 🎵 音频资源(音乐、音效) +│ ├── fonts/ # 🔤 字体文件 +│ ├── materials/ # 🎭 材质资源 +│ ├── shaders/ # 🌈 着色器文件 +│ ├── ui/ # 🖼️ UI素材(按钮、图标、背景) +│ └── icon/ # 📱 应用图标 +├── ⚙️ Config/ # 📋 配置文件管理 +│ ├── game_config.json # 🎮 游戏配置(难度、设置等) +│ ├── zh_CN.json # 🌐 本地化配置 +│ └── environment/ # 🔧 环境配置(开发、测试、生产) +├── 🧪 tests/ # 🔬 测试文件系统 +│ ├── unit/ # 🔍 单元测试(组件功能测试) +│ ├── integration/ # 🔗 集成测试(系统交互测试) +│ ├── performance/ # ⚡ 性能测试(帧率、内存优化) +│ └── api/ # 🌐 API接口测试 +└── 🌐 web_assets/ # 🌍 Web导出资源 + ├── html/ # 📄 HTML模板文件 + ├── css/ # 🎨 样式文件 + └── js/ # 📜 JavaScript脚本 ``` -### 🔧 核心组件 +### 🔧 核心架构说明 -| 组件 | 作用 | 文档链接 | +| 目录 | 作用 | 详细说明 | |------|------|----------| -| **EventSystem** | 全局事件通信 | [架构规范](docs/02-开发规范/架构与通信规范.md) | -| **GameManager** | 游戏状态管理 | [实现细节](docs/03-技术实现/实现细节规范.md) | -| **SceneManager** | 场景切换管理 | [场景设计](docs/04-高级开发/场景设计规范.md) | -| **NetworkManager** | 网络请求管理 | [网络管理器](docs/03-技术实现/网络管理器设置.md) | -| **ProjectPaths** | 路径统一管理 | [项目结构](docs/01-项目入门/项目结构说明.md) | +| **_Core** | 🔧 功能实现与组件实现 | 项目最基本的底层实现,包含所有核心系统和基础组件 | +| **scenes** | 🎭 场景与视觉呈现 | 包含地图场景、人物场景等一系列视觉呈现部分,主要是UI的实现 | +| **assets** | 🎨 静态资源存储 | 所有静态资源的存储,包括图片、音乐、视频、贴图等素材 | +| **Config** | ⚙️ 配置文件管理 | 主要用来配置各类环境,包括游戏设置、本地化等配置 | +| **tests** | 🧪 测试文件系统 | 放置所有对应组件的测试代码,方便快速进行功能性与性能测试 | +| **web_assets** | 🌐 Web导出资源 | 专门用于Web平台导出的相关资源和配置文件 | +| **docs/AI_docs** | 🤖 AI专用文档 | 专门为AI编程助手准备的执行规范和代码模板,提升vibe coding效率 | + +### 🎮 核心组件 + +| 组件 | 位置 | 作用 | 文档链接 | +|------|------|------|----------| +| **EventSystem** | _Core/systems/ | 全局事件通信系统 | [架构规范](docs/02-开发规范/架构与通信规范.md) | +| **GameManager** | _Core/managers/ | 游戏状态管理器 | [实现细节](docs/03-技术实现/实现细节规范.md) | +| **SceneManager** | _Core/managers/ | 场景切换管理器 | [场景设计](docs/04-高级开发/场景设计规范.md) | +| **NetworkManager** | _Core/managers/ | 网络请求管理器 | [网络管理器](docs/03-技术实现/网络管理器设置.md) | +| **ProjectPaths** | _Core/ | 路径统一管理工具 | [项目结构](docs/01-项目入门/项目结构说明.md) | --- diff --git a/docs/01-项目入门/项目结构说明.md b/docs/01-项目入门/项目结构说明.md index f1d7f9e..d983abc 100644 --- a/docs/01-项目入门/项目结构说明.md +++ b/docs/01-项目入门/项目结构说明.md @@ -20,15 +20,12 @@ ``` WhaleTown/ -├── 🔧 _Core/ # [框架层] 核心系统和管理器 -├── 🎬 scenes/ # [游戏层] 游戏场景和组件 -├── 🎨 UI/ # [界面层] 用户界面 -├── 🖼️ assets/ # [资源层] 美术资源 -├── ⚙️ Config/ # [配置层] 游戏配置和数据 -├── 🛠️ Utils/ # [工具层] 通用工具脚本 -├── 🧪 tests/ # [测试层] 测试文件 -├── 🚀 tools/ # [构建层] 构建和部署工具 -├── 🌐 web_assets/ # [发布层] Web版本资源 +├── 🔧 _Core/ # [核心层] 功能实现与组件实现,项目最基本的底层实现 +├── 🎬 scenes/ # [场景层] 场景与视觉呈现,包含地图、人物等视觉部分 +├── 🎨 assets/ # [资源层] 静态资源存储,包括图片、音乐、视频、贴图等 +├── ⚙️ Config/ # [配置层] 配置文件管理,用于配置各类环境 +├── 🧪 tests/ # [测试层] 测试文件系统,放置所有组件的测试代码 +├── 🌐 web_assets/ # [发布层] Web导出资源,专门用于Web平台导出 └── 📚 docs/ # [文档层] 项目文档 ``` @@ -36,9 +33,9 @@ WhaleTown/ ## 📁 详细目录结构 -### 1. 🔧 框架层 (_Core/) +### 1. 🔧 核心层 (_Core/) > **负责团队**: 开发团队 -> **职责**: 游戏底层框架,全局管理器和核心系统 +> **职责**: 功能实现与组件实现,项目最基本的底层实现 ``` _Core/ @@ -47,15 +44,25 @@ _Core/ │ ├── SceneManager.gd # 场景切换管理 │ ├── NetworkManager.gd # 网络通信管理 │ └── ResponseHandler.gd # API响应处理 -└── systems/ # 核心系统 - └── EventSystem.gd # 全局事件系统 +├── systems/ # 核心系统 +│ └── EventSystem.gd # 全局事件系统 +├── components/ # 基础组件实现 +│ ├── BaseCharacter.gd # 基础角色组件 +│ ├── BaseItem.gd # 基础物品组件 +│ └── BaseUI.gd # 基础UI组件 +├── utils/ # 🔨 核心工具类 +│ ├── StringUtils.gd # 字符串处理工具 +│ ├── MathUtils.gd # 数学计算工具 +│ └── PixelUtils.gd # 像素风游戏专用工具 +├── EventNames.gd # 事件名称定义 +└── ProjectPaths.gd # 路径统一管理 ``` **特点**: - 自动加载 (AutoLoad) 单例 -- 全局可访问 -- 与具体游戏逻辑无关 -- 提供基础服务 +- 全局可访问的核心功能 +- 与具体游戏逻辑无关的底层实现 +- 提供基础服务和组件框架 **使用示例**: ```gdscript @@ -69,167 +76,206 @@ SceneManager.change_scene("main") EventSystem.emit_event("player_moved", {"position": Vector2(100, 200)}) ``` -### 2. 🎬 游戏层 (scenes/) +### 2. 🎬 场景层 (scenes/) > **负责团队**: 开发团队 -> **职责**: 游戏场景、实体和可复用组件 +> **职责**: 场景与视觉呈现,包含地图场景、人物场景等一系列视觉呈现的部分 ``` scenes/ -├── Maps/ # 游戏地图场景 -│ ├── main_scene.tscn # 主游戏场景 -│ └── MainScene.gd # 主场景脚本 -└── Components/ # 可复用组件 - ├── characters/ # 角色组件 - ├── effects/ # 特效组件 - ├── items/ # 物品组件 - └── ui/ # UI组件 +├── maps/ # 地图场景 +│ ├── main_world.tscn # 主世界地图 +│ ├── dungeon_01.tscn # 地牢场景 +│ └── town_center.tscn # 城镇中心 +├── characters/ # 人物场景 +│ ├── player/ # 玩家角色 +│ │ ├── Player.tscn # 玩家场景 +│ │ └── Player.gd # 玩家脚本 +│ ├── npcs/ # NPC角色 +│ └── enemies/ # 敌人角色 +├── ui/ # UI界面场景 +│ ├── menus/ # 菜单界面 +│ ├── hud/ # 游戏HUD +│ └── dialogs/ # 对话框 +├── effects/ # 特效场景 +│ ├── particles/ # 粒子效果 +│ └── animations/ # 动画效果 +└── prefabs/ # 预制体组件 + ├── items/ # 物品预制体 + └── interactive/ # 交互对象预制体 ``` **设计原则**: - **场景内聚**: 脚本紧邻场景文件存放 -- **组件化**: 可复用组件统一管理 -- **模块化**: 按功能类型分类组织 +- **分类明确**: 按功能类型(地图、人物、UI、特效)分类 +- **模块化**: 可复用的预制体统一管理 +- **视觉导向**: 主要负责游戏的视觉呈现和UI实现 -### 3. 🎨 界面层 (UI/) -> **负责团队**: 开发团队 + 美术团队协作 -> **职责**: 用户界面和主题管理 - -``` -UI/ -├── Windows/ # 模态窗口 -│ ├── LoginWindow.tscn # 登录窗口 -│ └── AuthScene.gd # 认证场景脚本 -└── Theme/ # 全局主题 - ├── MainTheme.tres # 主题资源 - └── Fonts/ # 字体文件 - └── msyh.ttc # 微软雅黑字体 -``` - -**组织方式**: -- **Windows/**: 模态窗口和对话框 -- **Theme/**: 统一的UI主题和样式 -- **Fonts/**: 字体资源管理 - -### 4. 🖼️ 资源层 (assets/) +### 3. 🎨 资源层 (assets/) > **负责团队**: 美术团队 -> **职责**: 所有美术资源和素材 +> **职责**: 所有静态资源的存储,包括图片、音乐、视频、贴图等素材 ``` assets/ -├── sprites/ # 精灵图资源 -│ ├── characters/ # 角色精灵 -│ ├── effects/ # 特效精灵 -│ ├── environment/ # 环境精灵 -│ └── icon/ # 图标资源 +├── sprites/ # 精灵图片资源 +│ ├── characters/ # 角色精灵(玩家、NPC、敌人) +│ ├── environment/ # 环境精灵(地形、建筑、装饰) +│ ├── items/ # 物品精灵(道具、装备、收集品) +│ ├── effects/ # 特效精灵(爆炸、魔法、粒子) +│ └── ui/ # UI精灵(按钮、图标、边框) ├── audio/ # 音频资源 -│ ├── music/ # 背景音乐 -│ ├── sounds/ # 音效 -│ └── voice/ # 语音 -├── ui/ # UI专用资源 -│ ├── auth/ # 认证界面资源 -│ │ ├── bg_auth_scene.png # 认证背景 -│ │ └── login_frame_smart_transparent.png -│ ├── chinese_theme.tres # 中文主题 -│ └── datawhale_logo.png # 项目Logo +│ ├── music/ # 背景音乐(BGM) +│ ├── sounds/ # 音效(SFX) +│ └── voice/ # 语音(对话、旁白) ├── fonts/ # 字体文件 +│ ├── pixel_fonts/ # 像素风字体 +│ └── ui_fonts/ # UI专用字体 ├── materials/ # 材质资源 -└── shaders/ # 着色器 +│ ├── pixel_materials/ # 像素风材质 +│ └── shader_materials/ # 着色器材质 +├── shaders/ # 着色器文件 +│ ├── pixel_shaders/ # 像素风着色器 +│ └── effect_shaders/ # 特效着色器 +├── ui/ # UI专用资源 +│ ├── themes/ # UI主题 +│ ├── icons/ # 图标资源 +│ └── backgrounds/ # 背景图片 +└── icon/ # 应用图标 + ├── icon.svg # 矢量图标 + └── icon.png # 位图图标 ``` -**美术团队工作流程**: -1. 按分类将资源放入对应目录 -2. 遵循命名规范 (snake_case) -3. 确保资源格式符合Godot要求 -4. 配置正确的导入设置 (像素艺术使用最近邻过滤) +**像素风游戏资源特点**: +- **像素完美**: 所有精灵使用像素完美设置(Filter: Off, Mipmaps: Off) +- **统一风格**: 保持一致的像素密度和调色板 +- **分辨率标准**: 建议使用16x16、32x32等标准像素尺寸 +- **动画帧**: 角色动画使用精灵表(Sprite Sheet)组织 -### 5. ⚙️ 配置层 (Config/) +### 4. ⚙️ 配置层 (Config/) > **负责团队**: 策划团队 -> **职责**: 游戏配置、数据和本地化 +> **职责**: 配置文件管理,主要用来配置各类环境 ``` Config/ ├── game_config.json # 游戏主配置 -└── zh_CN.json # 中文本地化 +├── zh_CN.json # 中文本地化 +├── environment/ # 环境配置 +│ ├── development.json # 开发环境配置 +│ ├── testing.json # 测试环境配置 +│ └── production.json # 生产环境配置 +├── gameplay/ # 游戏玩法配置 +│ ├── character_stats.json # 角色属性配置 +│ ├── item_database.json # 物品数据库 +│ └── level_config.json # 关卡配置 +└── localization/ # 本地化配置 + ├── en_US.json # 英文本地化 + ├── zh_CN.json # 中文本地化 + └── ja_JP.json # 日文本地化 ``` -**策划团队工作内容**: -- **游戏数值配置**: 角色属性、物品数据、关卡参数 -- **本地化文本**: 多语言文本翻译和管理 -- **游戏平衡**: 数值平衡和游戏性调整 +**配置文件特点**: +- **环境分离**: 开发、测试、生产环境配置分离 +- **数据驱动**: 游戏数值通过配置文件控制 +- **本地化支持**: 多语言文本管理 +- **热更新**: 支持运行时配置更新 -**配置文件示例**: -```json -// game_config.json -{ - "player": { - "move_speed": 200.0, - "max_health": 100, - "jump_height": 400.0 - }, - "game": { - "target_fps": 60, - "auto_save_interval": 300 - } -} -``` - -### 6. 🛠️ 工具层 (Utils/) +### 5. 🧪 测试层 (tests/) > **负责团队**: 开发团队 -> **职责**: 通用工具和辅助脚本 - -``` -Utils/ -└── StringUtils.gd # 字符串处理工具 -``` - -**特点**: -- 纯函数,无依赖 -- 可被任何层调用 -- 提供通用功能 - -### 7. 🧪 测试层 (tests/) -> **负责团队**: 开发团队 -> **职责**: 质量保证和自动化测试 +> **职责**: 测试文件系统,放置所有对应组件的测试代码,方便快速进行功能性与性能测试 ``` tests/ -├── api/ # API接口测试 -├── auth/ # 认证功能测试 ├── unit/ # 单元测试 +│ ├── core/ # 核心组件测试 +│ ├── characters/ # 角色组件测试 +│ └── systems/ # 系统功能测试 ├── integration/ # 集成测试 -└── performance/ # 性能测试 +│ ├── scene_transitions/ # 场景切换测试 +│ ├── save_load/ # 存档系统测试 +│ └── network/ # 网络功能测试 +├── performance/ # 性能测试 +│ ├── framerate/ # 帧率测试 +│ ├── memory/ # 内存使用测试 +│ └── loading_times/ # 加载时间测试 +├── api/ # API接口测试 +│ ├── auth_tests.py # 认证接口测试 +│ └── game_api_tests.py # 游戏API测试 +└── ui/ # UI功能测试 + ├── menu_tests/ # 菜单测试 + └── dialog_tests/ # 对话框测试 ``` -### 8. 🚀 构建层 (tools/) -> **负责团队**: 开发团队/DevOps -> **职责**: 构建脚本和部署工具 +**测试类型说明**: +- **单元测试**: 测试单个组件的功能正确性 +- **集成测试**: 测试组件间的交互和协作 +- **性能测试**: 监控游戏性能指标,确保流畅运行 +- **API测试**: 验证网络接口的正确性和稳定性 +- **UI测试**: 测试用户界面的交互和响应 -``` -tools/ -├── build_web.bat # Windows Web构建脚本 -├── build_web.sh # Linux/macOS Web构建脚本 -├── serve_web.bat # Windows 本地服务器 -├── serve_web.sh # Linux/macOS 本地服务器 -└── README.md # 工具使用说明 -``` - -### 9. 🌐 发布层 (web_assets/) +### 6. 🌐 Web导出层 (web_assets/) > **负责团队**: 自动生成 -> **职责**: Web版本发布资源 +> **职责**: Web导出资源,专门用于Web平台导出的相关资源和配置文件 ``` web_assets/ -├── index.html # Web版本入口 -├── index.js # Godot Web导出JS -├── index.wasm # WebAssembly文件 -├── index.pck # 游戏资源包 -└── [其他Web资源] # 图标、配置等 +├── html/ # HTML模板文件 +│ ├── index.html # Web版本入口页面 +│ └── loading.html # 加载页面模板 +├── css/ # 样式文件 +│ ├── game.css # 游戏样式 +│ └── loading.css # 加载样式 +├── js/ # JavaScript脚本 +│ ├── game_loader.js # 游戏加载器 +│ └── utils.js # 工具函数 +├── icons/ # Web应用图标 +│ ├── favicon.ico # 网站图标 +│ └── app_icons/ # PWA应用图标 +└── config/ # Web配置文件 + ├── manifest.json # PWA清单文件 + └── service-worker.js # 服务工作者 ``` -### 10. 📚 文档层 (docs/) +**Web导出特点**: +- **PWA支持**: 支持渐进式Web应用功能 +- **响应式设计**: 适配不同屏幕尺寸 +- **加载优化**: 优化资源加载和缓存策略 +- **跨平台兼容**: 确保在各种浏览器中正常运行 + +### 7. 📚 文档层 (docs/) > **负责团队**: 全体团队 > **职责**: 项目文档和开发指南 +``` +docs/ +├── 01-项目入门/ # 新人必读文档 +├── 02-开发规范/ # 编码标准文档 +├── 03-技术实现/ # 开发指导文档 +├── 04-高级开发/ # 进阶技巧文档 +├── 05-部署运维/ # 发布部署文档 +├── 06-功能模块/ # 功能文档 +└── AI_docs/ # 🤖 AI专用文档 + ├── README.md # AI文档总览 + ├── architecture_guide.md # 架构执行指南 + ├── coding_standards.md # 代码风格规范 + ├── templates/ # 代码模板库 + │ ├── components.md # 组件模板集合 + │ ├── managers.md # 管理器模板集合 + │ └── ui_templates.md # UI模板集合 + ├── workflows/ # 工作流程指南 + │ ├── feature_development.md # 功能开发流程 + │ ├── bug_fixing.md # Bug修复流程 + │ └── testing_workflow.md # 测试执行流程 + └── quick_reference/ # 快速参考手册 + ├── code_snippets.md # 常用代码片段 + ├── api_reference.md # API快速参考 + └── troubleshooting.md # 故障排除指南 +``` + +**AI_docs特点**: +- **结构化执行**: 每个文档都包含可直接执行的步骤和代码模板 +- **标准化规范**: 为AI编程助手提供统一的开发标准和最佳实践 +- **模板驱动**: 提供完整的代码模板,确保代码一致性和质量 +- **工作流导向**: 包含详细的开发工作流程,提升AI协作效率 + ``` docs/ ├── 01-项目入门/ # 新人必读 @@ -245,18 +291,18 @@ docs/ ## 🤝 团队协作指南 ### 开发团队 🎮 -**主要工作区域**: `_Core/`, `scenes/`, `UI/`, `Utils/`, `tests/` +**主要工作区域**: `_Core/`, `scenes/`, `tests/` **日常工作流程**: -1. 在 `_Core/` 中开发核心系统和管理器 -2. 在 `scenes/` 中创建游戏场景和组件 -3. 在 `UI/` 中实现用户界面 -4. 在 `Utils/` 中编写通用工具 -5. 在 `tests/` 中编写测试用例 +1. 在 `_Core/` 中开发核心系统、管理器、基础组件和工具类 +2. 在 `scenes/` 中创建游戏场景、角色、UI界面和特效 +3. 在 `tests/` 中编写各类测试用例确保代码质量 **协作要点**: -- 遵循架构设计原则,使用事件系统通信 +- 遵循架构设计原则,使用事件系统进行模块通信 - 保持代码模块化和可复用性 +- 将通用工具类放在 `_Core/utils/` 中统一管理 +- 针对像素风游戏特点优化性能 - 及时编写测试和文档 ### 美术团队 🎨 @@ -264,28 +310,30 @@ docs/ **日常工作流程**: 1. 按分类整理美术资源到 `assets/` 对应目录 -2. 遵循命名规范,使用 snake_case 命名 -3. 配置正确的Godot导入设置 -4. 与开发团队协作调整UI资源 +2. 确保像素艺术风格的一致性和像素完美 +3. 配置正确的Godot导入设置(关闭过滤、禁用Mipmaps) +4. 与开发团队协作调整UI和游戏资源 -**协作要点**: -- 严格按照目录结构组织资源 -- 确保资源格式和质量符合要求 -- 及时与开发团队沟通资源需求 +**像素风游戏特殊要求**: +- 严格遵循像素完美原则 +- 保持统一的像素密度和调色板 +- 使用标准像素尺寸(16x16、32x32等) +- 精灵动画使用精灵表组织 ### 策划团队 📋 **主要工作区域**: `Config/` **日常工作流程**: -1. 在 `Config/` 中维护游戏配置文件 -2. 管理游戏数值和平衡性调整 +1. 在 `Config/` 中维护游戏配置和数值平衡 +2. 管理多环境配置(开发、测试、生产) 3. 负责本地化文本的翻译和维护 -4. 与开发团队协作实现游戏功能 +4. 设计游戏玩法和关卡数据 **协作要点**: - 使用JSON格式编写配置文件 -- 保持配置文件的结构清晰 +- 保持配置文件的结构清晰和可维护性 - 及时更新本地化文本 +- 与开发团队协作实现数据驱动的游戏功能 --- @@ -320,15 +368,24 @@ docs/ ### 资源管理规范 - 所有资源必须放在 `assets/` 对应分类目录下 - 使用描述性的文件名,避免使用数字编号 -- 像素艺术资源必须关闭过滤 (Filter: Off) +- **像素艺术资源必须关闭过滤** (Filter: Off, Mipmaps: Off) +- 保持统一的像素密度和调色板 +- 使用标准像素尺寸(16x16、32x32、64x64等) - 及时清理不使用的资源文件 ### 代码组织规范 - 脚本文件与场景文件放在同一目录 -- 使用事件系统实现模块间通信 +- 使用事件系统实现模块间通信,避免直接引用 - 保持单一职责原则,避免过度耦合 +- 针对像素风游戏优化性能(避免浮点数位置、使用整数坐标) - 及时编写注释和文档 +### 像素风游戏特殊规范 +- **像素完美**: 确保所有精灵在整数坐标上渲染 +- **统一风格**: 保持一致的像素密度和艺术风格 +- **性能优化**: 使用对象池管理频繁创建销毁的对象 +- **分辨率适配**: 使用像素完美的缩放方式适配不同分辨率 + --- **记住:良好的项目结构是团队协作成功的基础!** \ No newline at end of file