├── .gitee └── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ ├── documentation.yml │ └── feature_request.yml ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ ├── documentation.md │ └── feature_request.md ├── .gitignore ├── LICENSE ├── README-EN.md ├── README.md ├── docs ├── CODE_OF_CONDUCT.CN.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING CN.md └── CONTRIBUTING.md ├── examples ├── assets │ ├── sword.svg │ └── sword.svg.import ├── data_table │ ├── item_data.json │ ├── player_data.csv │ └── player_data.csv.import ├── example_scene.gd ├── example_scene.gd.uid ├── example_scene.tscn ├── resources │ ├── model_types │ │ ├── item_model_type.tres │ │ └── player_model_type.tres │ └── table_types │ │ ├── item_table_type.tres │ │ └── player_table_type.tres └── scripts │ └── resource │ ├── item_model.gd │ ├── item_model.gd.uid │ ├── player_model.gd │ └── player_model.gd.uid ├── plugin.cfg ├── plugin.cfg.uid ├── plugin.gd ├── plugin.gd.uid └── source ├── data_loader.gd ├── data_loader.gd.uid ├── data_loader ├── csv_loader.gd ├── csv_loader.gd.uid ├── json_loader.gd └── json_loader.gd.uid ├── data_manager.gd ├── data_manager.gd.uid ├── resource ├── model_type.gd ├── model_type.gd.uid ├── table_type.gd └── table_type.gd.uid ├── thread_pool.gd └── thread_pool.gd.uid /.gitee/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug 报告 2 | description: 创建一个 Bug 报告以帮助我们改进 3 | title: '[BUG] ' 4 | labels: bug 5 | assignees: '' 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | 感谢对项目的支持与关注。在提出问题之前,请确保你已查看相关开发或使用文档: 11 | - type: textarea 12 | attributes: 13 | label: 问题描述 14 | description: 请清晰简洁地描述这个 Bug 15 | placeholder: [简洁说明异常现象,如"旋转地牢时角色卡入不可达区域"] 16 | validations: 17 | required: true 18 | - type: textarea 19 | attributes: 20 | label: 💻复现步骤 21 | description: 请详细描述复现这个 Bug 的步骤 22 | placeholder: | 23 | - 1. 进入晶洞层第三区 24 | - 2. 连续逆时针旋转红轴3次 25 | - 3. 尝试向西北方向移动 26 | validations: 27 | required: true 28 | - type: textarea 29 | attributes: 30 | label: 📝 预期行为 31 | description: 请描述你期望发生的事情 32 | placeholder: [角色应抵达隐藏房间] 33 | validations: 34 | required: false 35 | - type: textarea 36 | attributes: 37 | label: 📝 实际行为 38 | description: 请描述实际发生的事情 39 | placeholder: [角色模型卡在岩壁中,摄像机视角错位] 40 | - type: textarea 41 | attributes: 42 | label: 📎附加信息 43 | description: 请提供额外的信息以帮助我们更好地理解这个 Bug 44 | placeholder: | 45 | [可选] 你可以在这里添加任何你认为有用的附加信息 46 | validations: 47 | required: false 48 | - type: textarea 49 | attributes: 50 | label: 截图或视频 51 | description: 如果可以的话,上传任何关于 bug 的截图。 52 | value: | 53 | ![异常截图](URL) 54 | - type: textarea 55 | attributes: 56 | label: 🔍 环境信息 57 | description: 请提供额外的信息以帮助我们更好地理解这个 Bug 58 | placeholder: | 59 | - 游戏版本:v0.3.2 60 | - 项目版本/分支:[例如 main] 61 | - Godot 版本:[例如 4.3] 62 | - 操作系统:[例如 Windows 11] 63 | validations: 64 | required: false 65 | - type: checkboxes 66 | attributes: 67 | label: 确认 68 | options: 69 | - label: 我已确认该问题未被重复报告 70 | default: false 71 | required: false -------------------------------------------------------------------------------- /.gitee/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 项目讨论区 4 | url: https://discord.gg/6GFtSjfdF7 5 | about: 请在这里讨论项目相关话题 6 | - name: 项目文档 7 | url: https://gitee.com/Giab/GDDataForge/wikis 8 | about: 查看项目文档 9 | -------------------------------------------------------------------------------- /.gitee/ISSUE_TEMPLATE/documentation.yml: -------------------------------------------------------------------------------- 1 | name: 文档改进 2 | description: 帮助我们改进项目文档 3 | title: '[文档] ' 4 | labels: ['documentation'] 5 | assignees: [] 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | 感谢您帮助我们改进文档。清晰的文档对项目的发展至关重要。 11 | 12 | - type: input 13 | attributes: 14 | label: 文档位置 15 | description: 指出需要改进的文档位置 16 | placeholder: 例如:README.md 的安装说明部分 17 | validations: 18 | required: true 19 | 20 | - type: dropdown 21 | attributes: 22 | label: 改进类型 23 | description: 选择文档改进的类型 24 | options: 25 | - 错误修正 26 | - 内容补充 27 | - 格式优化 28 | - 翻译改进 29 | - 示例添加 30 | - 其他 31 | validations: 32 | required: true 33 | 34 | - type: textarea 35 | attributes: 36 | label: 现有问题 37 | description: 描述当前文档存在的问题 38 | placeholder: 例如:安装说明缺少某些重要步骤... 39 | validations: 40 | required: true 41 | 42 | - type: textarea 43 | attributes: 44 | label: 改进建议 45 | description: 提供具体的改进建议 46 | placeholder: 建议添加/修改的具体内容... 47 | validations: 48 | required: true 49 | 50 | - type: checkboxes 51 | attributes: 52 | label: 确认事项 53 | options: 54 | - label: 我已确认这不是重复的建议 55 | required: true 56 | - label: 我愿意协助实施这些改进 57 | required: false -------------------------------------------------------------------------------- /.gitee/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: 功能请求 2 | description: 建议一个新功能或改进 3 | title: '[功能] ' 4 | labels: ['enhancement'] 5 | assignees: [] 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | 感谢您的功能建议。请确保您的建议符合项目的发展方向。 11 | 12 | - type: textarea 13 | attributes: 14 | label: 相关问题 15 | description: 这个功能是否与某个问题相关?请描述。 16 | placeholder: 例如:当我...的时候,总是感觉很不方便... 17 | validations: 18 | required: true 19 | 20 | - type: textarea 21 | attributes: 22 | label: 解决方案 23 | description: 描述您期望的解决方案 24 | placeholder: 希望能够添加...功能来解决这个问题 25 | validations: 26 | required: true 27 | 28 | - type: textarea 29 | attributes: 30 | label: 替代方案 31 | description: 描述您考虑过的替代解决方案 32 | placeholder: 也可以通过...方式来解决,但是... 33 | 34 | - type: checkboxes 35 | attributes: 36 | label: 功能类型 37 | description: 这个功能属于哪些类别? 38 | options: 39 | - label: UI/UX改进 40 | required: false 41 | - label: 性能优化 42 | required: false 43 | - label: 新玩法机制 44 | required: false 45 | - label: Bug修复 46 | required: false 47 | - label: 其他 48 | required: false 49 | 50 | - type: textarea 51 | attributes: 52 | label: 补充信息 53 | description: 添加任何其他相关的信息 54 | placeholder: 例如:参考截图、相似游戏的实现等 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug 报告 3 | about: 创建一个 Bug 报告以帮助我们改进 4 | title: '[BUG] ' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | ## 🐛 Bug 描述 10 | 11 | [简洁说明异常现象,如"旋转地牢时角色卡入不可达区域"] 12 | 13 | ## 💻 复现步骤 14 | 15 | 1. 进入晶洞层第三区 16 | 2. 连续逆时针旋转红轴3次 17 | 3. 尝试向西北方向移动 18 | 19 | ## 📝 预期行为 20 | 21 | 角色应抵达隐藏房间 22 | 23 | ## 实际结果 24 | 角色模型卡在岩壁中,摄像机视角错位 25 | 26 | ## 📷 截图/日志 27 | 28 | [粘贴最近一次错误日志片段] 29 | ![异常截图](URL) 30 | 31 | ## 🔍 环境信息 32 | 33 | - 游戏版本:v0.3.2 34 | - 项目版本/分支:[例如 main] 35 | - Godot 版本:[例如 4.3] 36 | - 操作系统:[例如 Windows 11] 37 | 38 | ## 📎 补充信息 39 | 40 | - [ ] 首次出现 41 | - [ ] 100%复现 42 | - [ ] 影响存档安全 43 | 44 | > 我已确认该问题未被重复报告 [检查结果] 45 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 项目讨论 4 | url: https://discord.gg/6GFtSjfdF7 5 | about: 请在这里进行一般性讨论 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 文档改进 3 | about: 帮助我们改进文档 4 | title: '[DOCS] ' 5 | labels: documentation 6 | assignees: '' 7 | --- 8 | 9 | ## 📚 文档问题 10 | 11 | 12 | ## 📝 建议的改进 13 | 14 | 15 | ## 📎 补充信息 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 功能建议 3 | about: 为这个项目提出一个想法 4 | title: '[FEATURE] ' 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | ## 🚀 功能描述 10 | 11 | 12 | ## 💡 解决方案 13 | 14 | 15 | ## 🔄 替代方案 16 | 17 | 18 | ## 📎 补充信息 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot 4+ specific ignores 2 | .godot/ 3 | /android/ 4 | .idea/ 5 | *.tmp 6 | .vscode/ 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 李维民 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README-EN.md: -------------------------------------------------------------------------------- 1 | # Godot Data Manager Plugin 2 | 3 | English | [简体中文](README.md) 4 | 5 | [![Godot v4.4](https://img.shields.io/badge/Godot-v4.4-%23478cbf)](https://godotengine.org/) 6 | [![MIT license](https://img.shields.io/badge/license-MIT-brightgreen.svg)](../../LICENSE) 7 | [![GitHub](https://img.shields.io/badge/GitHub-Repository-black?logo=github)](https://github.com/Liweimin0512/GDDataForge) 8 | [![Gitee](https://img.shields.io/badge/Gitee-Repository-red?logo=gitee)](https://gitee.com/Giab/GDDataForge) 9 | 10 | ## 💡 Introduction 11 | 12 | A flexible and efficient data management plugin designed for Godot 4.4, helping you easily manage and load game data from various file formats (CSV, JSON, etc.). Supports asynchronous loading based on thread pools, perfect for handling large amounts of game data without impacting performance. Supports direct construction of custom Resource type objects from data by defining DataType and ModelType to implement more complex data table and data model features. 13 | 14 | ## ✨ Features 15 | 16 | - **Multiple File Format Support** 17 | 18 | - Support for CSV files 19 | - Support for JSON files 20 | - Extensible loader system for adding new formats 21 | 22 | - **Flexible Data Loading** 23 | 24 | - Synchronous loading for simple scenarios 25 | - Asynchronous loading for better performance 26 | - Support for progress tracking and callbacks 27 | 28 | - **Type Safety** 29 | 30 | - Strong type checking 31 | - Automatic type conversion 32 | - Data integrity validation system 33 | 34 | - **Memory Efficiency** 35 | - Data caching system 36 | - Shared resource reference counting 37 | - Memory-optimized data structures 38 | 39 | ## 🚀 Quick Start 40 | 41 | ### Installation 42 | 43 | 1. Download or clone this repository 44 | 2. Copy the repository to your project's `addons` folder 45 | 3. Enable the plugin in Project Settings -> Plugins 46 | 47 | ### Basic Usage 48 | 49 | #### 1. **Define Data Table Type** 50 | 51 | ```gdscript 52 | # Create table type resource 53 | var item_type = TableType.new( 54 | "item", 55 | ["res://data/items.csv"] 56 | ) 57 | ``` 58 | 59 | #### 2. **Model Data Mapping** 60 | 61 | ```gdscript 62 | # Create model type resource 63 | class ItemModel: 64 | extends Resource 65 | var id: String 66 | var name: String 67 | 68 | var item_model_type = ModelType.new( 69 | "item", 70 | "res://scripts/item_model.gd", 71 | item_type, 72 | ) 73 | ``` 74 | 75 | #### 3. **Load Data** 76 | 77 | ```gdscript 78 | # Synchronous loading 79 | DataManager.load_data_tables([table_type]) 80 | 81 | # Asynchronous loading with callback 82 | DataManager.load_data_tables_async([table_type], 83 | func(results): print("Loading complete!"), 84 | func(current, total): print("Progress: %d/%d" % [current, total]) 85 | ) 86 | ``` 87 | 88 | #### 4. **Access Data** 89 | 90 | ```gdscript 91 | # Get item data 92 | var item_datas = DataManager.get_table_data("items") 93 | # Get single item data 94 | var item_data = DataManager.get_table_item("items", "sword_1") 95 | # Get item data model 96 | var item : ItemModel = DataManager.get_data_model("item", "sword_1") 97 | ``` 98 | 99 | ### Example Scenes 100 | 101 | Check out the example scenes in `addons/li_data_manager/examples` to see the plugin in action: 102 | 103 | - Data loading demonstration 104 | - Type conversion examples 105 | - Progress tracking 106 | - Error handling 107 | 108 | ## 🗺️ Development Plan 109 | 110 | - [x] Basic functionality implementation 111 | 112 | - [x] Extensible loader system 113 | - [x] Synchronous and asynchronous loading 114 | - [x] Data type safety 115 | - [x] Memory optimization 116 | 117 | - [ ] Visual Data Editor 118 | 119 | - [ ] Table structure editing 120 | - [ ] Data entry and modification 121 | - [ ] Import/Export functionality 122 | - [ ] Preview and validation tools 123 | 124 | - [ ] Other Features 125 | - [ ] Support for more file formats 126 | - [ ] Support for more complex data types in JSON files 127 | - [ ] Configurable data validation rules 128 | - [ ] Data compression options 129 | - [ ] Data encryption support 130 | - [ ] Data hot reloading 131 | - [ ] Network synchronization 132 | 133 | ## 🤝 Contributing 134 | 135 | We welcome contributions! Please see our [Contributing Guidelines](docs/CONTRIBUTING.md) for details on how to submit pull requests, report issues, and contribute to the project. 136 | 137 | ## 📋 Code of Conduct 138 | 139 | Please note that this project follows a [Code of Conduct](docs/CODE_OF_CONDUCT.md). By participating in this project, you agree to abide by its terms. 140 | 141 | ## 📄 License 142 | 143 | This project is licensed under the MIT License - see the [LICENSE](/LICENSE) file for details. 144 | 145 | ## 📬 Contact 146 | 147 | - GitHub Issues: [Issues](https://github.com/Liweimin0512/GDDataForge/issues) 148 | - Email: [liwemin0284@gmail.com](liwemin0284@gmail.com) 149 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Godot 数据管理器插件 2 | 3 | [English](README-EN.md) | 简体中文 4 | 5 | [![Godot v4.4](https://img.shields.io/badge/Godot-v4.4-%23478cbf)](https://godotengine.org/) 6 | [![MIT license](https://img.shields.io/badge/license-MIT-brightgreen.svg)](../../LICENSE) 7 | [![GitHub](https://img.shields.io/badge/GitHub-仓库-black?logo=github)](https://github.com/Liweimin0512/GDDataForge) 8 | [![Gitee](https://img.shields.io/badge/Gitee-仓库-red?logo=gitee)](https://gitee.com/Giab/GDDataForge) 9 | 10 | ## 💡 简介 11 | 12 | 一个为 Godot 4.4 设计的灵活高效的数据管理插件,帮助您轻松管理和加载来自各种文件格式(CSV、JSON 等)的游戏数据。支持基于线程池的异步加载,非常适合处理大量游戏数据而不影响性能。支持从数据直接构造自定义 Resource 类型的对象,通过定义 DataType 和 ModelType 来实现更复杂的数据表和数据模型特性。 13 | 14 | ## ✨ 特性 15 | 16 | - **多文件格式支持** 17 | 18 | - 支持 CSV 文件 19 | - 支持 JSON 文件 20 | - 可扩展的加载器系统,方便添加新格式 21 | 22 | - **灵活的数据加载** 23 | 24 | - 同步加载用于简单场景 25 | - 异步加载提供更好性能 26 | - 支持进度跟踪和回调 27 | 28 | - **类型安全** 29 | 30 | - 强类型检查 31 | - 自动类型转换 32 | - 数据完整性验证系统 33 | 34 | - **内存效率** 35 | - 数据缓存系统 36 | - 共享资源引用计数 37 | - 内存优化的数据结构 38 | 39 | ## 🚀 快速开始 40 | 41 | ### 安装 42 | 43 | 1. 下载或克隆此仓库 44 | 2. 将仓库复制到你项目的 `addons` 文件夹中 45 | 3. 确保插件项目文件夹命名为`GDDataForge` 46 | 4. 在项目设置 -> 插件中启用此插件 47 | 48 | ### 基本用法 49 | 50 | #### 1. **定义数据表类型** 51 | 52 | ```gdscript 53 | # 创建表格类型资源 54 | var item_type = TableType.new( 55 | "item", 56 | ["res://data/items.csv"] 57 | ) 58 | ``` 59 | 60 | #### 2. **模型数据映射** 61 | 62 | ```gdscript 63 | # 创建模型类型资源 64 | class ItemModel: 65 | extends Resource 66 | var id: String 67 | var name: String 68 | 69 | var item_model_type = ModelType.new( 70 | "item", 71 | "res://scripts/item_model.gd", 72 | item_type, 73 | ) 74 | ``` 75 | 76 | #### 3. **加载数据** 77 | 78 | ```gdscript 79 | 80 | DataManager.load_data_tables([table_type], 81 | func(results): print("加载完成!"), 82 | func(current, total): print("进度:%d/%d" % [current, total]) 83 | ) 84 | ``` 85 | 86 | #### 4. **访问数据** 87 | 88 | ```gdscript 89 | # 获取物品数据 90 | var item_datas = DataManager.get_table_data("items") 91 | # 获取单个物品数据 92 | var item_data = DataManager.get_table_item("items", "sword_1") 93 | # 获取物品数据模型 94 | var item : ItemModel = DataManager.get_data_model("item", "sword_1") 95 | ``` 96 | 97 | ### 示例场景 98 | 99 | 查看 `addons/li_data_manager/examples` 中的示例场景,了解插件的实际应用: 100 | 101 | - 数据加载演示 102 | - 类型转换示例 103 | - 进度跟踪 104 | - 错误处理 105 | 106 | ## 🗺️ 开发计划 107 | 108 | - [x] 基本功能实现 109 | 110 | - [x] 可拓展的加载器系统 111 | - [x] 同步加载和异步加载 112 | - [x] 数据类型安全 113 | - [x] 内存优化 114 | 115 | - [ ] 可视化数据编辑器 116 | 117 | - [ ] 表格结构编辑 118 | - [ ] 数据录入和修改 119 | - [ ] 导入导出功能 120 | - [ ] 预览和验证工具 121 | 122 | - [ ] 其他功能 123 | - [ ] 更多文件格式支持 124 | - [ ] json 文件支持更多复杂数据类型 125 | - [ ] 可配置的数据校验规则 126 | - [ ] 数据压缩选项 127 | - [ ] 数据加密支持 128 | - [ ] 数据热加载 129 | - [ ] 网络同步 130 | 131 | ## 🤝 参与贡献 132 | 133 | 欢迎参与贡献!您可以: 134 | 135 | 1. Fork 这个仓库 136 | 2. 创建您的特性分支 (`git checkout -b feature/AmazingFeature`) 137 | 3. 提交您的更改 (`git commit -m '添加一些很棒的功能'`) 138 | 4. 推送到分支 (`git push origin feature/AmazingFeature`) 139 | 5. 开启一个 Pull Request 140 | 141 | ## 📄 许可证 142 | 143 | 本项目采用 MIT 许可证 - 查看 [LICENSE](/LICENSE) 文件了解详情。 144 | 145 | ## 📬 联系方式 146 | 147 | - GitHub Issue 追踪:[Issues](https://github.com/Liweimin0512/GDDataForge/issues) 148 | - 邮箱:[liwemin0284@gmail.com](liwemin0284@gmail.com) 149 | -------------------------------------------------------------------------------- /docs/CODE_OF_CONDUCT.CN.md: -------------------------------------------------------------------------------- 1 | # 行为准则 | Code of Conduct 2 | 3 | [English](#contributor-covenant-code-of-conduct) | [简体中文](#贡献者公约) 4 | 5 | ## 贡献者公约 6 | 7 | ### 我们的承诺 8 | 9 | 为了营造一个开放和友好的环境,我们作为贡献者和维护者承诺:无论年龄、体型、身体健全与否、民族、性别认同和表达、经验水平、教育程度、社会地位、国籍、相貌、种族、宗教信仰、性别取向如何,我们项目和社区的参与者都有权利免于骚扰。 10 | 11 | ### 我们的准则 12 | 13 | 有助于创造积极环境的行为包括: 14 | 15 | - 使用友好和包容性的语言 16 | - 尊重不同的观点和经历 17 | - 耐心地接受建设性批评 18 | - 关注对社区最有利的事情 19 | - 友善对待其他社区成员 20 | 21 | 不当行为包括: 22 | 23 | - 使用与性有关的言语或是图像,以及不受欢迎的性骚扰 24 | - 发表挑衅、侮辱/贬损的评论,进行人身攻击或政治攻击 25 | - 公开或私下的骚扰 26 | - 未经明确许可,发布他人的私人信息 27 | - 其他可能被合理地认定为不恰当或者违反职业操守的行为 28 | 29 | ### 我们的责任 30 | 31 | 项目维护者有责任为可接受的行为标准做出诠释,并采取恰当且公平的纠正措施来应对任何不可接受的行为。 32 | 33 | 项目维护者有权利和责任删除、编辑或拒绝违反本行为准则的评论、提交、代码、维基编辑、问题和其他贡献,并暂时或永久地禁止任何贡献者进行违反行为。 34 | 35 | ### 适用范围 36 | 37 | 本行为准则适用于项目空间以及个人代表项目或其社区的公共空间中。 38 | 39 | ### 强制执行 40 | 41 | 可以通过 [discord 服务器](https://discord.gg/6GFtSjfdF7) 联系项目团队来举报滥用、骚扰或其他不可接受的行为。 42 | 43 | 所有投诉都将被审查和调查,并作出必要的回应。项目团队有义务为事件举报者保密。 44 | 45 | ### Attribution 46 | 47 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 48 | available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html) 49 | 50 | [homepage]: https://www.contributor-covenant.org 51 | -------------------------------------------------------------------------------- /docs/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # 行为准则 | Code of Conduct 2 | 3 | [English](#contributor-covenant-code-of-conduct) | [简体中文](#贡献者公约) 4 | 5 | ## Contributor Covenant Code of Conduct 6 | 7 | ### Our Pledge 8 | 9 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 10 | 11 | ### Our Standards 12 | 13 | Examples of behavior that contributes to creating a positive environment include: 14 | 15 | * Using welcoming and inclusive language 16 | * Being respectful of differing viewpoints and experiences 17 | * Gracefully accepting constructive criticism 18 | * Focusing on what is best for the community 19 | * Showing empathy towards other community members 20 | 21 | Examples of unacceptable behavior include: 22 | 23 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 24 | * Trolling, insulting/derogatory comments, and personal or political attacks 25 | * Public or private harassment 26 | * Publishing others' private information without explicit permission 27 | * Other conduct which could reasonably be considered inappropriate in a professional setting 28 | 29 | ### Our Responsibilities 30 | 31 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 32 | 33 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 34 | 35 | ### Scope 36 | 37 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. 38 | 39 | ### Enforcement 40 | 41 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [ADD CONTACT METHOD]. 42 | 43 | All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. 44 | 45 | ### Attribution 46 | 47 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 48 | available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html) 49 | 50 | [homepage]: https://www.contributor-covenant.org 51 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING CN.md: -------------------------------------------------------------------------------- 1 | # 贡献指南 2 | 3 | [English](CONTRIBUTING.md) | [简体中文](CONTRIBUTING.CN.md) 4 | 5 | 感谢你考虑为 GDDataForge 做出贡献! 6 | 7 | ## 🌟 贡献方式 8 | 9 | 你可以通过以下方式参与项目: 10 | 11 | ### 1. 提交问题(Issues) 12 | 13 | - 报告 Bug 14 | - 提出新功能建议 15 | - 改进文档 16 | 17 | ### 2. 提交代码(Pull Requests) 18 | 19 | - 修复 Bug 20 | - 实现新功能 21 | - 改进代码质量 22 | - 优化性能 23 | 24 | ## 📝 贡献流程 25 | 26 | ### 提交 Issue 27 | 28 | 1. 使用 Issue 模板 29 | 2. 清晰描述问题或建议 30 | 3. 提供复现步骤(如果是 Bug) 31 | 4. 添加相关标签 32 | 33 | ### 提交 Pull Request 34 | 35 | 1. Fork 本仓库 36 | 2. 创建特性分支:`git checkout -b feature/your-feature-name` 37 | 3. 提交修改:`git commit -am '添加新特性'` 38 | 4. 推送分支:`git push origin feature/your-feature-name` 39 | 5. 提交 Pull Request 40 | 41 | ## 💻 开发指南 42 | 43 | ### 代码风格 44 | 45 | - 遵循 [GDScript 风格指南](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_styleguide.html) 46 | - 使用有意义的变量和函数名 47 | - 添加必要的注释 48 | - 保持代码简洁清晰 49 | 50 | ### 提交信息规范 51 | 52 | <类型>: <描述> 53 | [可选的详细描述] 54 | [可选的相关 Issue] 55 | 56 | 类型包括: 57 | 58 | - feat: 新功能 59 | - fix: Bug 修复 60 | - docs: 文档更新 61 | - style: 代码格式调整 62 | - refactor: 代码重构 63 | - test: 测试相关 64 | - chore: 构建过程或辅助工具的变动 65 | 66 | ### 示例 67 | 68 | feat: 添加新的技能效果系统 69 | 实现基础效果组件 70 | 添加效果组合器 71 | 更新相关文档 72 | Closes #123 73 | 74 | ## ⚠️ 注意事项 75 | 76 | 1. 确保你的代码符合项目的 MIT 许可证要求 77 | 2. 不要提交与项目无关的文件 78 | 3. 保护敏感信息,不要提交密码等隐私数据 79 | 4. 遵守社区行为准则 80 | 81 | ## 🔍 代码审查 82 | 83 | 所有的 Pull Request 都会经过审查。审查重点包括: 84 | 85 | - 代码质量和风格 86 | - 功能完整性 87 | - 测试覆盖 88 | - 文档完整性 89 | 90 | ## 📋 行为准则 91 | 92 | 请参阅我们的 [行为准则](CODE_OF_CONDUCT.CN.md),共同维护一个友好的社区环境。 93 | 94 | ## 🙏 致谢 95 | 96 | 再次感谢你的贡献! 97 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | [English](CONTRIBUTING.md) | [简体中文](CONTRIBUTING.CN.md) 4 | 5 | Thank you for considering contributing to GDDataForge! 6 | 7 | ## 🌟 Ways to Contribute 8 | 9 | You can participate in the project through: 10 | 11 | ### 1. Submit Issues 12 | 13 | - Report bugs 14 | - Suggest new features 15 | - Improve documentation 16 | 17 | ### 2. Submit Pull Requests 18 | 19 | - Fix bugs 20 | - Implement new features 21 | - Improve code quality 22 | - Optimize performance 23 | 24 | ## 📝 Contribution Process 25 | 26 | ### Submitting an Issue 27 | 28 | 1. Use the Issue template 29 | 2. Clearly describe the problem or suggestion 30 | 3. Provide reproduction steps (if it's a bug) 31 | 4. Add relevant labels 32 | 33 | ### Submitting a Pull Request 34 | 35 | 1. Fork this repository 36 | 2. Create a feature branch: `git checkout -b feature/your-feature-name` 37 | 3. Commit changes: `git commit -am 'Add new feature'` 38 | 4. Push branch: `git push origin feature/your-feature-name` 39 | 5. Submit Pull Request 40 | 41 | ## 💻 Development Guidelines 42 | 43 | ### Code Style 44 | 45 | - Follow the [GDScript Style Guide](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_styleguide.html) 46 | - Use meaningful variable and function names 47 | - Add necessary comments 48 | - Keep code concise and clear 49 | 50 | ### Commit Message Convention 51 | 52 | : 53 | [optional body] 54 | [optional footer] 55 | 56 | Types include: 57 | 58 | - feat: New feature 59 | - fix: Bug fix 60 | - docs: Documentation updates 61 | - style: Code formatting 62 | - refactor: Code refactoring 63 | - test: Test-related 64 | - chore: Build process or auxiliary tool changes 65 | 66 | ### Example 67 | 68 | feat: Add new skill effect system 69 | Implement base effect components 70 | Add effect combinator 71 | Update related documentation 72 | Closes #123 73 | 74 | ## ⚠️ Important Notes 75 | 76 | 1. Ensure your code complies with the project's MIT license requirements 77 | 2. Don't submit files unrelated to the project 78 | 3. Protect sensitive information, don't submit passwords or private data 79 | 4. Follow the community code of conduct 80 | 81 | ## 🔍 Code Review 82 | 83 | All Pull Requests will undergo review. Review focuses include: 84 | 85 | - Code quality and style 86 | - Feature completeness 87 | - Test coverage 88 | - Documentation completeness 89 | 90 | ## 📋 Code of Conduct 91 | 92 | Please refer to our [Code of Conduct](CODE_OF_CONDUCT.md) to help maintain a friendly community environment. 93 | 94 | ## 🙏 Acknowledgments 95 | 96 | Thank you again for your contribution! 97 | -------------------------------------------------------------------------------- /examples/assets/sword.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/assets/sword.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://caewaveax5oe" 6 | path="res://.godot/imported/sword.svg-e43fe66aebbd3b1287d880eb928d7709.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/GDDataForge/examples/assets/sword.svg" 14 | dest_files=["res://.godot/imported/sword.svg-e43fe66aebbd3b1287d880eb928d7709.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /examples/data_table/item_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "sword_1": { 3 | "id": "sword_1", 4 | "name": "炎龙剑", 5 | "type": "weapon", 6 | "damage": 100, 7 | "defense": 0, 8 | "position": "@vector2:(10, 20)", 9 | "icon": "@resource://addons/GDDataForge/examples/assets/sword.svg", 10 | "properties": { 11 | "fire_damage": 50, 12 | "critical_rate": 0.2 13 | }, 14 | "tags": ["武器", "剑", "火属性"] 15 | }, 16 | "shield_1": { 17 | "id": "shield_1", 18 | "name": "寒冰盾", 19 | "type": "shield", 20 | "damage": 0, 21 | "defense": 80, 22 | "position": "@vector2:(30, 40)", 23 | "icon": "@resource://addons/GDDataForge/examples/assets/shield.png", 24 | "properties": { 25 | "ice_defense": 40, 26 | "block_rate": 0.3 27 | }, 28 | "tags": ["防具", "盾", "冰属性"] 29 | }, 30 | "staff_1": { 31 | "id": "staff_1", 32 | "name": "雷霆法杖", 33 | "type": "weapon", 34 | "damage": 150, 35 | "defense": 20, 36 | "position": "@vector2:(50, 60)", 37 | "icon": "@resource://addons/GDDataForge/examples/assets/staff.png", 38 | "properties": { 39 | "lightning_damage": 80, 40 | "magic_boost": 0.4 41 | }, 42 | "tags": ["武器", "法杖", "雷属性"] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/data_table/player_data.csv: -------------------------------------------------------------------------------- 1 | ID,name,level,position,skills,properties,is_active,icon 2 | ID,名称,等级,位置,技能列表,属性值列表,是否激活,图标 3 | string,string,int,vector2,string[],dictionary,bool,resource 4 | player_1,英雄,10,"100, 200",剑刃*盾牌*闪电,"attack:100,defense:50,speed:60",1,"res://addons/li_data_manager/examples/assets/sword.svg" 5 | player_2,法师,8,"300, 400",火球*冰箭*闪电,"attack:50,defense:30,speed:60",0, 6 | player_3,战士,12,"500, 600",冲锋*旋风斩*格挡,"attack:150,defense:100,speed:70",1, -------------------------------------------------------------------------------- /examples/data_table/player_data.csv.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="skip" 4 | -------------------------------------------------------------------------------- /examples/example_scene.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | ## 数据模型 4 | const ItemModel = preload("res://addons/GDDataForge/examples/scripts/resource/item_model.gd") 5 | const PlayerModel = preload("res://addons/GDDataForge/examples/scripts/resource/player_model.gd") 6 | 7 | ## UI 组件 8 | @onready var test_output : RichTextLabel = %TestOutput 9 | 10 | @export var _model_types : Array[ModelType] 11 | 12 | var _data_manager : Node 13 | 14 | func _ready() -> void: 15 | if has_node(^"/root/DataManager"): 16 | _data_manager = get_node(^"/root/DataManager") 17 | else: 18 | assert(false, "请开启GDDataForge插件,并检查DataManager单例是否存在!") 19 | if _data_manager: 20 | # 连接信号 21 | _data_manager.load_completed.connect(_on_load_completed) 22 | _data_manager.batch_load_completed.connect(_on_batch_load_completed) 23 | 24 | # 输出初始信息 25 | _print_test_info("数据管理器测试") 26 | _print_test_info("点击按钮开始测试...") 27 | 28 | ## 加载完成回调 29 | func _on_load_completed(table_name: String) -> void: 30 | _print_test_info("\n表格加载完成: %s" % table_name) 31 | var data = _data_manager.get_table_data(table_name) 32 | _print_table_data(table_name, data) 33 | 34 | ## 批量加载完成回调 35 | func _on_batch_load_completed(loaded_tables: Array[String]) -> void: 36 | _print_test_info("\n批量加载完成!") 37 | _print_test_info("已加载表格: {0}".format([loaded_tables])) 38 | 39 | ## 输出测试信息 40 | func _print_test_info(text: String) -> void: 41 | if test_output: 42 | test_output.append_text("[{0}]{1}\n".format([Time.get_unix_time_from_system(), text])) 43 | 44 | ## 打印表格数据 45 | func _print_table_data(table_name: String, data: Dictionary) -> void: 46 | if data == null: 47 | _print_test_info("[color=yellow]警告: 表格数据为空: %s[/color]" % table_name) 48 | return 49 | 50 | _print_test_info("\n=== 表格内容: %s ===" % table_name) 51 | for key in data: 52 | var value = data[key] 53 | if typeof(value) == TYPE_ARRAY: 54 | value = "" 55 | for v in data[key]: 56 | value += v + " " 57 | _print_test_info("{0}: {1}".format([key, value])) 58 | 59 | ## 测试数据模型的方法 60 | func _test_model_methods() -> void: 61 | _print_test_info("\n=== 测试数据模型方法 ===") 62 | 63 | # 测试玩家模型方法 64 | var player_models : Array = _data_manager.get_all_data_models("player") 65 | for player : PlayerModel in player_models: 66 | _print_test_info("\n玩家模型方法测试 : %s" %player.name) 67 | _print_test_info("- 攻击力: %d" % player.get_attack()) 68 | _print_test_info("- 防御力: %d" % player.get_defense()) 69 | _print_test_info("- 速度: %d" % player.get_speed()) 70 | 71 | # 测试物品模型方法 72 | var item_models : Array = _data_manager.get_all_data_models("item") 73 | var sword : ItemModel = _data_manager.get_data_model("item", "sword_1") 74 | if sword: 75 | _print_test_info("\n物品模型方法测试 (sword_1):") 76 | _print_test_info("- 是否武器: %s" % sword.is_weapon()) 77 | _print_test_info("- 是否防具: %s" % sword.is_shield()) 78 | _print_test_info("- 主属性值: %d" % sword.get_main_property()) 79 | _print_test_info("- 是否有'武器'标签: %s" % sword.has_tag("武器")) 80 | 81 | var shield : ItemModel = _data_manager.get_data_model("item", "shield_1") 82 | if shield: 83 | _print_test_info("\n物品模型方法测试 (shield_1):") 84 | _print_test_info("- 是否武器: %s" % shield.is_weapon()) 85 | _print_test_info("- 是否防具: %s" % shield.is_shield()) 86 | _print_test_info("- 主属性值: %d" % shield.get_main_property()) 87 | _print_test_info("- 是否有'防具'标签: %s" % shield.has_tag("防具")) 88 | 89 | print("table shield :", DataManager.get_table_item("item", "shield_1") ) 90 | 91 | func _on_load_btn_pressed() -> void: 92 | _print_test_info("\n=== 开始异步加载测试 ===") 93 | 94 | # 异步加载 95 | _data_manager.load_models(_model_types, func(loaded_tables: Array[String]): 96 | _print_test_info("\n异步加载完成回调!") 97 | _print_test_info("已加载表格:{0}".format([loaded_tables])) 98 | # 测试数据模型方法 99 | _test_model_methods() 100 | ) 101 | 102 | 103 | func _on_clear_btn_pressed() -> void: 104 | _print_test_info("\n=== 清理测试数据 ===") 105 | _data_manager.clear_model_types() 106 | _data_manager.clear_table_types() 107 | test_output.text = "" 108 | _print_test_info("数据已清理!") 109 | _print_test_info("点击按钮开始新的测试...") 110 | -------------------------------------------------------------------------------- /examples/example_scene.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bt5firv3f8hwj 2 | -------------------------------------------------------------------------------- /examples/example_scene.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=3 uid="uid://br3jamwkx21cy"] 2 | 3 | [ext_resource type="Script" uid="uid://bt5firv3f8hwj" path="res://addons/GDDataForge/examples/example_scene.gd" id="1_of7mr"] 4 | [ext_resource type="Script" uid="uid://bf3cohk1etxvp" path="res://addons/GDDataForge/source/resource/model_type.gd" id="2_etxah"] 5 | [ext_resource type="Resource" uid="uid://bnsbjerbe0i0t" path="res://addons/GDDataForge/examples/resources/model_types/item_model_type.tres" id="3_esdcf"] 6 | [ext_resource type="Resource" uid="uid://b556wun2wqhoe" path="res://addons/GDDataForge/examples/resources/model_types/player_model_type.tres" id="4_q78k6"] 7 | 8 | [node name="ExampleScene" type="Control"] 9 | layout_mode = 3 10 | anchors_preset = 15 11 | anchor_right = 1.0 12 | anchor_bottom = 1.0 13 | grow_horizontal = 2 14 | grow_vertical = 2 15 | script = ExtResource("1_of7mr") 16 | _model_types = Array[ExtResource("2_etxah")]([ExtResource("3_esdcf"), ExtResource("4_q78k6")]) 17 | 18 | [node name="VBoxContainer" type="VBoxContainer" parent="."] 19 | layout_mode = 1 20 | anchors_preset = 15 21 | anchor_right = 1.0 22 | anchor_bottom = 1.0 23 | grow_horizontal = 2 24 | grow_vertical = 2 25 | 26 | [node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] 27 | layout_mode = 2 28 | 29 | [node name="LoadBtn" type="Button" parent="VBoxContainer/HBoxContainer"] 30 | unique_name_in_owner = true 31 | layout_mode = 2 32 | text = "加载数据" 33 | 34 | [node name="ClearBtn" type="Button" parent="VBoxContainer/HBoxContainer"] 35 | unique_name_in_owner = true 36 | layout_mode = 2 37 | text = "清除" 38 | 39 | [node name="TestOutput" type="RichTextLabel" parent="VBoxContainer"] 40 | unique_name_in_owner = true 41 | layout_mode = 2 42 | size_flags_vertical = 3 43 | text = "数据输出窗口 44 | " 45 | 46 | [connection signal="pressed" from="VBoxContainer/HBoxContainer/LoadBtn" to="." method="_on_load_btn_pressed"] 47 | [connection signal="pressed" from="VBoxContainer/HBoxContainer/ClearBtn" to="." method="_on_clear_btn_pressed"] 48 | -------------------------------------------------------------------------------- /examples/resources/model_types/item_model_type.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Resource" script_class="ModelType" load_steps=4 format=3 uid="uid://bnsbjerbe0i0t"] 2 | 3 | [ext_resource type="Script" uid="uid://djpgu0hpdqk7m" path="res://addons/GDDataForge/examples/scripts/resource/item_model.gd" id="1_h70pn"] 4 | [ext_resource type="Script" uid="uid://bf3cohk1etxvp" path="res://addons/GDDataForge/source/resource/model_type.gd" id="2_j0iad"] 5 | [ext_resource type="Resource" uid="uid://dvhv2b2tc2sej" path="res://addons/GDDataForge/examples/resources/table_types/item_table_type.tres" id="4_j0iad"] 6 | 7 | [resource] 8 | script = ExtResource("2_j0iad") 9 | model_name = &"item" 10 | model_script = ExtResource("1_h70pn") 11 | table = ExtResource("4_j0iad") 12 | description = "" 13 | field_mappings = Dictionary[String, String]({}) 14 | default_values = Dictionary[String, Variant]({}) 15 | -------------------------------------------------------------------------------- /examples/resources/model_types/player_model_type.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Resource" script_class="ModelType" load_steps=4 format=3 uid="uid://b556wun2wqhoe"] 2 | 3 | [ext_resource type="Script" uid="uid://c5cctkkcygkj8" path="res://addons/GDDataForge/examples/scripts/resource/player_model.gd" id="1_8be26"] 4 | [ext_resource type="Script" uid="uid://bf3cohk1etxvp" path="res://addons/GDDataForge/source/resource/model_type.gd" id="2_ktwej"] 5 | [ext_resource type="Resource" uid="uid://pv58vkywjgs8" path="res://addons/GDDataForge/examples/resources/table_types/player_table_type.tres" id="4_na36g"] 6 | 7 | [resource] 8 | script = ExtResource("2_ktwej") 9 | model_name = &"player" 10 | model_script = ExtResource("1_8be26") 11 | table = ExtResource("4_na36g") 12 | description = "" 13 | field_mappings = Dictionary[String, String]({ 14 | "ID": "id" 15 | }) 16 | default_values = Dictionary[String, Variant]({}) 17 | -------------------------------------------------------------------------------- /examples/resources/table_types/item_table_type.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Resource" script_class="TableType" load_steps=2 format=3 uid="uid://dvhv2b2tc2sej"] 2 | 3 | [ext_resource type="Script" uid="uid://c65rwndbbr15i" path="res://addons/GDDataForge/source/resource/table_type.gd" id="1_3cjjf"] 4 | 5 | [resource] 6 | script = ExtResource("1_3cjjf") 7 | table_name = &"item" 8 | table_paths = PackedStringArray("res://addons/GDDataForge/examples/data_table/item_data.json") 9 | description = "" 10 | primary_key = "ID" 11 | enable_cache = true 12 | validation_rules = {} 13 | cache = {} 14 | is_loaded = false 15 | metadata/_custom_type_script = "uid://c65rwndbbr15i" 16 | -------------------------------------------------------------------------------- /examples/resources/table_types/player_table_type.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Resource" script_class="TableType" load_steps=2 format=3 uid="uid://pv58vkywjgs8"] 2 | 3 | [ext_resource type="Script" uid="uid://c65rwndbbr15i" path="res://addons/GDDataForge/source/resource/table_type.gd" id="1_33j5m"] 4 | 5 | [resource] 6 | script = ExtResource("1_33j5m") 7 | table_name = &"player" 8 | table_paths = PackedStringArray("res://addons/GDDataForge/examples/data_table/player_data.csv") 9 | description = "" 10 | primary_key = "ID" 11 | enable_cache = true 12 | validation_rules = {} 13 | cache = {} 14 | is_loaded = false 15 | metadata/_custom_type_script = "uid://c65rwndbbr15i" 16 | -------------------------------------------------------------------------------- /examples/scripts/resource/item_model.gd: -------------------------------------------------------------------------------- 1 | extends Resource 2 | 3 | ## 物品ID 4 | @export var id: String 5 | ## 物品名称 6 | @export var name: String 7 | ## 物品类型 8 | @export var type: String 9 | ## 伤害值 10 | @export var damage: int 11 | ## 防御值 12 | @export var defense: int 13 | ## 位置 14 | @export var position: Vector2 15 | ## 图标 16 | @export var icon: Resource 17 | ## 属性列表 18 | @export var properties: Dictionary 19 | ## 标签列表 20 | @export var tags: Array 21 | 22 | ## 从数据初始化 23 | func _init_from_data(data: Dictionary) -> void: 24 | # 可以在这里添加额外的初始化逻辑 25 | pass 26 | 27 | ## 是否是武器 28 | func is_weapon() -> bool: 29 | return type == "weapon" 30 | 31 | ## 是否是防具 32 | func is_shield() -> bool: 33 | return type == "shield" 34 | 35 | ## 获取主属性值 36 | func get_main_property() -> float: 37 | return properties.values()[0] if not properties.is_empty() else 0.0 38 | 39 | ## 检查是否有指定标签 40 | func has_tag(tag: String) -> bool: 41 | return tags.has(tag) 42 | 43 | func _to_string() -> String: 44 | return "Item: %s" % name + " (%s)" % type + " (%s)" % id 45 | -------------------------------------------------------------------------------- /examples/scripts/resource/item_model.gd.uid: -------------------------------------------------------------------------------- 1 | uid://djpgu0hpdqk7m 2 | -------------------------------------------------------------------------------- /examples/scripts/resource/player_model.gd: -------------------------------------------------------------------------------- 1 | extends Resource 2 | 3 | ## 玩家ID 4 | @export var id: String 5 | ## 玩家名称 6 | @export var name: String 7 | ## 玩家图标 8 | @export var icon : Texture2D 9 | ## 等级 10 | @export var level: int 11 | ## 位置 12 | @export var position: Vector2 13 | ## 技能列表 14 | @export var skills: Array 15 | ## 属性值列表 16 | @export var properties: Dictionary[String, float] 17 | ## 是否激活 18 | @export var is_active: bool 19 | 20 | ## 从数据初始化 21 | func _init_from_data(data: Dictionary) -> void: 22 | # 可以在这里添加额外的初始化逻辑 23 | print("player init form data, do something here") 24 | for key in data.properties: 25 | var value = data.properties[key] 26 | properties[key] = float(value) 27 | 28 | ## 获取攻击力 29 | func get_attack() -> float: 30 | return properties["attack"] 31 | 32 | ## 获取防御力 33 | func get_defense() -> float: 34 | return properties["defense"] 35 | 36 | ## 获取速度 37 | func get_speed() -> float: 38 | return properties["speed"] 39 | -------------------------------------------------------------------------------- /examples/scripts/resource/player_model.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c5cctkkcygkj8 2 | -------------------------------------------------------------------------------- /plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="GDDataForge" 4 | description="A flexible and efficient data management plugin for Godot 4.4, designed to help you easily manage and load game data from various file formats (CSV, JSON, etc.). It supports both synchronous and asynchronous loading, making it perfect for handling large amounts of game data without impacting performance." 5 | author="godot_li" 6 | version="0.0.2" 7 | script="plugin.gd" 8 | -------------------------------------------------------------------------------- /plugin.cfg.uid: -------------------------------------------------------------------------------- 1 | uid://ml7p4bdiseew 2 | -------------------------------------------------------------------------------- /plugin.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorPlugin 3 | 4 | func _enter_tree() -> void: 5 | add_autoload_singleton("DataManager", "res://addons/GDDataForge/source/data_manager.gd") 6 | 7 | func _exit_tree() -> void: 8 | remove_autoload_singleton("DataManager") 9 | -------------------------------------------------------------------------------- /plugin.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bdb7kjfyypm78 2 | -------------------------------------------------------------------------------- /source/data_loader.gd: -------------------------------------------------------------------------------- 1 | extends RefCounted 2 | 3 | ## 加载数据表 4 | func load_datatable(table_path: StringName) -> Dictionary: 5 | return {} 6 | 7 | ## 数据格式化 8 | func _parse_value(value: String, type: String) -> Variant: 9 | return null 10 | -------------------------------------------------------------------------------- /source/data_loader.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bdlu5emk2dfj6 2 | -------------------------------------------------------------------------------- /source/data_loader/csv_loader.gd: -------------------------------------------------------------------------------- 1 | extends "res://addons/GDDataForge/source/data_loader.gd" 2 | 3 | ## 数据类型处理器 4 | var _parsers : Dictionary[String, Callable] = { 5 | "string": _parse_string, 6 | "int": _parse_int, 7 | "float": _parse_float, 8 | "bool": _parse_bool, 9 | "vector2": _parse_vector2, 10 | "resource": _parse_resource, 11 | "dictionary": _parse_dictionary, 12 | } 13 | 14 | ## 重写加载单一数据表,这里对CSV文件数据行格式化 15 | func load_datatable(table_path: StringName) -> Dictionary: 16 | var file := FileAccess.open(table_path, FileAccess.READ) 17 | var ok := FileAccess.get_open_error() 18 | if ok != OK: 19 | push_error("未能正确打开文件!") 20 | var _file_data : Dictionary = {} 21 | # CSV数据解析 22 | var data_names : PackedStringArray = file.get_csv_line(",") # 第1行是数据名称字段 23 | var _dec : PackedStringArray = file.get_csv_line(",") # 第2行是数据注释字段 24 | var data_types : PackedStringArray = file.get_csv_line(",") # 第3行是数据类型字段 25 | while not file.eof_reached(): 26 | var row : PackedStringArray = file.get_csv_line(",") # 每行的数据,如果为空则跳过 27 | if row.is_empty(): continue 28 | var row_data := {} 29 | for index: int in row.size(): 30 | # 遍历当前行的每一列 31 | var data_name : StringName = data_names[index] 32 | if data_name.is_empty(): continue 33 | var data_type : StringName = data_types[index] 34 | if data_type.is_empty(): continue 35 | # row_data[data_name] = row[index] 36 | row_data[data_name] = _parse_value(row[index], data_type) 37 | if not row_data.is_empty() and not row_data.ID.is_empty(): 38 | _file_data[StringName(row_data.ID)] = row_data 39 | return _file_data 40 | 41 | ## 注册数据类型处理器 42 | func register_parser(type: StringName, parser: Callable) -> void: 43 | _parsers[type] = parser 44 | 45 | ## 注销数据类型处理器 46 | func unregister_parser(type: StringName) -> void: 47 | _parsers.erase(type) 48 | 49 | ## 清空数据类型处理器 50 | func clear_parsers() -> void: 51 | _parsers.clear() 52 | 53 | ## 数据格式化 54 | func _parse_value(value: String, type: String) -> Variant: 55 | # 检查是否是数组类型 56 | if type.ends_with("[]"): 57 | var base_type = type.substr(0, type.length() - 2) # 移除 "[]" 后缀 58 | return _parse_array(value, base_type) 59 | elif _parsers.has(type): 60 | return _parsers[type].call(value) 61 | else: 62 | push_error("未知的数据类型:{0} in _parsers: {1}".format([type, _parsers])) 63 | return null 64 | 65 | ## 通用数组处理函数 66 | func _parse_array(value: String, base_type: String) -> Variant: 67 | if value.is_empty(): 68 | return [] 69 | 70 | var elements = value.split("*") 71 | var result = [] 72 | 73 | # 根据基础类型处理每个元素 74 | if _parsers.has(base_type): 75 | for element in elements: 76 | result.append(_parsers[base_type].call(element)) 77 | else: 78 | push_error("未知的数组基础类型:" + base_type) 79 | return [] 80 | 81 | return result 82 | 83 | ## 处理字符串 84 | func _parse_string(v: String) -> String: 85 | return v 86 | 87 | ## 处理整数 88 | func _parse_int(v: String) -> int: 89 | return v.to_int() 90 | 91 | ## 处理浮点数 92 | func _parse_float(v: String) -> float: 93 | return v.to_float() 94 | 95 | ## 处理布尔值 96 | func _parse_bool(v: String) -> bool: 97 | return bool(v.to_int()) 98 | 99 | ## 处理向量 100 | func _parse_vector2(v: String) -> Vector2: 101 | var vs : Array[float] 102 | for element in v.split(","): 103 | vs.append(element.to_float()) 104 | return Vector2(vs[0], vs[1]) 105 | 106 | ## 处理资源 107 | func _parse_resource(v: String) -> Resource: 108 | if v.is_empty(): 109 | return null 110 | if ResourceLoader.exists(v): 111 | return ResourceLoader.load(v) 112 | else: 113 | var error_info: String = "未知的资源类型:" + v 114 | if OS.has_feature("release"): 115 | push_error(error_info) 116 | return null 117 | 118 | ## 处理字典 119 | func _parse_dictionary(v: String) -> Dictionary: 120 | var dict : Dictionary 121 | var kv_pairs = v.split(",") 122 | for pair in kv_pairs: 123 | var kv = pair.split(":") 124 | if kv.size() == 2: 125 | dict[kv[0].strip_edges()] = kv[1].strip_edges() 126 | return dict 127 | -------------------------------------------------------------------------------- /source/data_loader/csv_loader.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dsjfjivoclsb4 2 | -------------------------------------------------------------------------------- /source/data_loader/json_loader.gd: -------------------------------------------------------------------------------- 1 | extends "res://addons/GDDataForge/source/data_loader.gd" 2 | class_name JSONDatatableHelper 3 | 4 | ## 数据类型标记 5 | var _parse_types: Dictionary[String, String] = { 6 | "json": "@json://", # 嵌套的JSON配置 7 | "scene": "@scene://", # 场景资源 8 | "texture": "@texture://", # 贴图资源 9 | "audio": "@audio://", # 音频资源 10 | "resource": "@resource://", # 资源 11 | "vector2" : "@vector2:", # 2D向量 12 | } 13 | 14 | ## 加载单个JSON文件 15 | ## [param table_path] JSON文件路径 16 | ## [return] 加载结果 17 | func load_datatable(table_path: StringName) -> Dictionary: 18 | if not FileAccess.file_exists(table_path): 19 | push_error("JSON file not found: %s" % table_path) 20 | return {} 21 | var json_text = FileAccess.get_file_as_string(table_path) 22 | var json = JSON.parse_string(json_text) 23 | if not json is Dictionary: 24 | push_error("Invalid JSON format: %s" % table_path) 25 | return {} 26 | # 处理资源引用 27 | return _process_resource_references(json) 28 | 29 | ## 处理JSON中的资源引用 30 | ## [param data] JSON数据 31 | ## [return] 处理后的数据 32 | func _process_resource_references(data: Variant) -> Variant: 33 | match typeof(data): 34 | TYPE_DICTIONARY: 35 | var result = {} 36 | for key in data: 37 | result[key] = _process_resource_references(data[key]) 38 | return result 39 | TYPE_ARRAY: 40 | var result = [] 41 | for item in data: 42 | result.append(_process_resource_references(item)) 43 | return result 44 | TYPE_STRING: 45 | return _resolve_resource_path(data) 46 | _: 47 | return data 48 | 49 | ## 解析资源路径 50 | ## [param value] 资源路径 51 | ## [return] 加载结果 52 | func _resolve_resource_path(value: String) -> Variant: 53 | for marker in _parse_types: 54 | var prefix = _parse_types[marker] 55 | if value.begins_with(prefix): 56 | var path = value.substr(prefix.length()) 57 | if marker == "json": 58 | return load_datatable(path) 59 | elif marker == "vector2": 60 | return _load_vector2(path) 61 | else: 62 | return _load_resource(path, marker) 63 | return _load_resource(path, marker) 64 | return value 65 | 66 | ## 加载vector2 67 | ## [param value] 向量字符串 68 | ## [return] 向量 69 | func _load_vector2(value: String) -> Vector2: 70 | value = value.replace("(", "").replace(")", "") 71 | var vs: Array[float] 72 | for v in value.split(", "): 73 | vs.append(v.to_float()) 74 | return Vector2(vs[0], vs[1]) 75 | 76 | ## 加载资源 77 | ## [param path] 资源路径 78 | ## [param type] 资源类型 79 | ## [return] 资源 80 | func _load_resource(path: String, type: String) -> Variant: 81 | if ResourceLoader.exists(path): 82 | return ResourceLoader.load(path) 83 | else: 84 | push_error("资源地址无效: ", path) 85 | return null 86 | -------------------------------------------------------------------------------- /source/data_loader/json_loader.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c1iyqajk1s5tv 2 | -------------------------------------------------------------------------------- /source/data_manager.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | ## 数据表管理器 4 | 5 | const DataLoader = preload("res://addons/GDDataForge/source/data_loader.gd") 6 | const CsvLoader = preload("res://addons/GDDataForge/source/data_loader/csv_loader.gd") 7 | const JsonLoader = preload("res://addons/GDDataForge/source/data_loader/json_loader.gd") 8 | 9 | ## 数据表加载器 10 | var _data_loader_factory : Dictionary[String, DataLoader] = { 11 | "csv": CsvLoader.new(), 12 | "json": JsonLoader.new() 13 | } 14 | 15 | ## 已注册的模型类型 16 | var _model_types: Dictionary[String, ModelType] = {} 17 | var _table_types: Dictionary[String, TableType] = {} 18 | 19 | ## 线程池 20 | var _thread_pool: ThreadPool 21 | ## 加载中的模型 22 | var _loading_types: Dictionary = {} 23 | ## 加载中的表格 24 | var _batch_results: Dictionary = {} 25 | 26 | ## 加载完成信号 27 | signal batch_load_completed(results: Dictionary) 28 | signal load_completed(table_name: String) 29 | 30 | func _init() -> void: 31 | _thread_pool = ThreadPool.new() 32 | add_child(_thread_pool) 33 | _thread_pool.task_completed.connect(_on_table_loaded) 34 | _thread_pool.all_tasks_completed.connect(_on_batch_completed) 35 | 36 | func _notification(what: int) -> void: 37 | if what == NOTIFICATION_PREDELETE: 38 | _thread_pool.shutdown() 39 | 40 | ## 注册数据表加载器 41 | ## [param type] 加载器类型 42 | ## [param loader] 加载器 43 | func register_data_loader(type: String, loader: DataLoader) -> void: 44 | _data_loader_factory[type] = loader 45 | 46 | ## 移除数据表加载器 47 | ## [param type] 加载器类型 48 | func unregister_data_loader(type: String) -> void: 49 | _data_loader_factory.erase(type) 50 | 51 | ## 获取数据表加载器 52 | ## [param type] 加载器类型 53 | ## [return] 加载器 54 | func get_data_loader(type: String) -> DataLoader: 55 | return _data_loader_factory.get(type, null) 56 | 57 | ## 清除模型 58 | func clear_model_types() -> void: 59 | _model_types.clear() 60 | 61 | ## 清除表格 62 | func clear_table_types() -> void: 63 | _table_types.clear() 64 | 65 | ## 加载数据表 66 | ## [param table_type] 数据表类型 67 | ## [param completed_callback] 完成回调 68 | ## [return] 返回加载的数据表名字数组 69 | func load_data_table(table_type: TableType, completed_callback: Callable = Callable()) -> Dictionary: 70 | if table_type.is_loaded: 71 | if completed_callback.is_valid(): 72 | completed_callback.call(table_type.table_name) 73 | return table_type.cache 74 | 75 | # 创建加载任务 76 | var task_id = "load_table_%s" % table_type.table_name 77 | _loading_types[task_id] = { 78 | "table_type": table_type, 79 | "completed_callback": completed_callback 80 | } 81 | 82 | _thread_pool.add_task( 83 | task_id, 84 | _load_data_type, 85 | [table_type], 86 | 1) 87 | return {} 88 | 89 | ## 批量加载数据表 90 | ## [param table_types] 数据表类型数组 91 | ## [param callback] 完成回调,返回加载的数据表 92 | ## [param progress_callback] 进度回调,返回当前加载的数据表数和总数据表数 93 | ## [return] 返回加载的数据表名字数组 94 | func load_data_tables( 95 | table_types: Array[TableType], 96 | callback: Callable = Callable(), 97 | progress_callback: Callable = Callable()) -> Array[String]: 98 | # 异步加载 99 | var total : int = table_types.size() 100 | var results : Array[String] 101 | 102 | if total == 0: 103 | # 没有需要加载的数据表,立刻返回 104 | if callback.is_valid(): 105 | callback.call(results) 106 | batch_load_completed.emit(results) 107 | return results 108 | 109 | # 记录加载进度 110 | var progress_data = {"current": 0, "total": total} 111 | 112 | for table_type in table_types: 113 | if table_type.is_loaded: 114 | progress_data.current += 1 115 | results.append(table_type.table_name) 116 | continue 117 | 118 | # 创建加载任务 119 | load_data_table(table_type, 120 | func(table_name: String) -> void: 121 | progress_data.current += 1 # 使用共享计数器 122 | results.append(table_name) 123 | 124 | if progress_callback.is_valid(): 125 | progress_callback.call(progress_data.current, progress_data.total) 126 | 127 | call_deferred("emit_signal", "load_completed", table_type.table_name) 128 | 129 | if progress_data.current >= total: 130 | print("所有数据表加载完成,结果数:", results.size()) 131 | if callback.is_valid(): 132 | call_deferred("_emit_callback", callback, results) 133 | call_deferred("emit_signal", "batch_load_completed", results) 134 | ) 135 | return results 136 | 137 | ## 检查某个路径的数据表是否已缓存 138 | ## [param table_name] 数据表名称 139 | ## [return] 是否已缓存 140 | func has_data_table_cached(table_name: String) -> bool: 141 | if not _table_types.has(table_name): return false 142 | if not _table_types[table_name].is_loaded: return false 143 | return true 144 | 145 | ## 获取已缓存的数据表 146 | ## [param table_name] 数据表名称 147 | ## [return] 数据表配置 148 | func get_table_data(table_name: String) -> Dictionary: 149 | var config : Dictionary 150 | if _table_types.has(table_name): 151 | config = _table_types[table_name].cache 152 | return config 153 | 154 | ## 获取数据表项 155 | ## [param table_name] 数据表名称 156 | ## [param item_id] 项ID 157 | ## [return] 项配置 158 | func get_table_item(table_name: String, item_id: String) -> Dictionary: 159 | var data : Dictionary = get_table_data(table_name) 160 | if data.is_empty(): 161 | push_error("数据表 %s 不存在" % table_name) 162 | return {} 163 | if not data.has(item_id): 164 | push_error("数据表 %s 中不存在项 %s" % [table_name, item_id]) 165 | return {} 166 | return data.get(item_id) 167 | 168 | 169 | ## 是否存在数据表项 170 | func has_table_item(table_name : String, item_id: String) -> bool: 171 | var data : Dictionary = get_table_data(table_name) 172 | return data.has(item_id) 173 | 174 | 175 | ## 加载模型 176 | ## [param model] 模型配置 177 | ## [param completed_callback] 完成回调, 返回模型对象 178 | func load_model(model: ModelType, completed_callback: Callable = Callable()) -> void: 179 | if _model_types.has(model.model_name): 180 | push_warning("模型 %s 已存在" % model.model_name) 181 | return 182 | _model_types[model.model_name] = model 183 | load_data_table(model.table, completed_callback) 184 | 185 | ## 批量加载模型 186 | ## [param models] 模型数组 187 | ## [param completed_callback] 完成回调 188 | ## [param progress_callback] 进度回调 189 | func load_models(models: Array[ModelType], 190 | completed_callback: Callable = Callable(), 191 | progress_callback: Callable = Callable()) -> void: 192 | # 注册所有模型 193 | for model in models: 194 | _model_types[model.model_name] = model 195 | var tables : Array[TableType] = [] 196 | for model in models: 197 | if model.table and not model.table.is_loaded: 198 | tables.append(model.table) 199 | load_data_tables(tables, completed_callback, progress_callback) 200 | 201 | ## 获取模型 202 | ## [param model_name] 模型名称 203 | ## [return] 模型配置 204 | func get_model_type(model_name: String) -> ModelType: 205 | return _model_types.get(model_name, null) 206 | 207 | ## 获取数据模型 208 | ## [param model_name] 模型名称 209 | ## [param item_id] 项ID 210 | ## [return] 数据模型 211 | func get_data_model(model_name: String, item_id: String) -> Resource: 212 | var model_type : ModelType = get_model_type(model_name) 213 | if not model_type: 214 | push_error("模型 %s 不存在" % model_name) 215 | return null 216 | var table_type : TableType = model_type.table 217 | var data : Dictionary = get_table_item(table_type.table_name, item_id) 218 | var model : Resource = model_type.create_instance(data) 219 | return model 220 | 221 | ## 获取所有数据模型 222 | ## [param model_name] 模型名称 223 | ## [return] 数据模型数组 224 | func get_all_data_models(model_name: String) -> Array[Resource]: 225 | var models : Array[Resource] = [] 226 | var model_type : ModelType = get_model_type(model_name) 227 | if not model_type: 228 | push_error("模型 %s 不存在" % model_name) 229 | var table_type : TableType = model_type.table 230 | for item_id in table_type.cache: 231 | var data : Dictionary = get_table_item(table_type.table_name, item_id) 232 | var model : Resource = model_type.create_instance(data) 233 | models.append(model) 234 | return models 235 | 236 | ## 加载数据表对象 237 | ## [param table_type] 数据表类型 238 | ## [return] 数据表对象 239 | func _load_data_type(table_type: TableType) -> Dictionary: 240 | for path in table_type.table_paths: 241 | var data : Dictionary = _load_data_file(path) 242 | table_type.cache.merge(data, true) 243 | _table_types[table_type.table_name] = table_type 244 | return table_type.cache 245 | 246 | ## 加载数据表文件 247 | ## [param file_path] 文件路径 248 | ## [return] 数据表 249 | func _load_data_file(file_path: String) -> Dictionary: 250 | if not FileAccess.file_exists(file_path): 251 | push_error("file not found: %s" % file_path) 252 | return {} 253 | var loader := _get_file_loader(file_path) 254 | if not loader: 255 | push_error("无法加载文件:%s" % file_path) 256 | return {} 257 | var data = loader.load_datatable(file_path) 258 | return data 259 | 260 | ## 根据数据表文件路径后缀名选择加载器 261 | ## [param path] 文件路径 262 | ## [return] 加载器 263 | func _get_file_loader(path: String) -> DataLoader: 264 | var ext = path.get_extension().to_lower() 265 | var loader : DataLoader 266 | if _data_loader_factory.has(ext): 267 | loader = _data_loader_factory[ext] 268 | else: 269 | push_error("未找到合适的数据表加载器:%s" % ext) 270 | return loader 271 | 272 | ## 发送进度回调 273 | ## [param callback] 回调函数 274 | ## [param current] 当前进度 275 | ## [param total] 总进度 276 | func _emit_progress(callback: Callable, current: int, total: int) -> void: 277 | if callback.is_valid(): 278 | callback.call(current, total) 279 | 280 | ## 发送完成回调 281 | ## [param callback] 回调函数 282 | ## [param results] 结果 283 | func _emit_callback(callback: Callable, results: Array) -> void: 284 | if callback.is_valid(): 285 | callback.call(results) 286 | 287 | ## 表格加载完成回调 288 | ## [param task_id] 任务ID 289 | ## [param result] 结果 290 | func _on_table_loaded(task_id: String, result: Variant) -> void: 291 | var task_info := _loading_types.get(task_id) 292 | if not task_info: 293 | push_error("数据表 %s 加载失败" % task_id) 294 | return 295 | 296 | var table_type : TableType = task_info.get("table_type") 297 | if not table_type: 298 | push_error("数据表 %s 加载失败" % table_type.table_name) 299 | return 300 | var callback = task_info.get("completed_callback") 301 | 302 | # 更新缓存 303 | table_type.cache = result 304 | table_type.is_loaded = true 305 | _table_types[table_type.table_name] = table_type 306 | _batch_results[table_type.table_name] = result 307 | 308 | print("数据表 %s 加载完成" % table_type.table_name) 309 | 310 | # 发送完成回调 311 | if callback and callback.is_valid(): 312 | callback.call(table_type.table_name) 313 | 314 | _loading_types.erase(task_id) 315 | 316 | func _on_batch_completed() -> void: 317 | if not _batch_results.is_empty(): 318 | batch_load_completed.emit(_batch_results) 319 | _batch_results.clear() 320 | -------------------------------------------------------------------------------- /source/data_manager.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dfrqlkx1ckgnm 2 | -------------------------------------------------------------------------------- /source/resource/model_type.gd: -------------------------------------------------------------------------------- 1 | extends Resource 2 | class_name ModelType 3 | 4 | ## 模型名称 5 | @export var model_name: StringName 6 | ## 模型脚本 7 | @export var model_script: GDScript 8 | ## 关联的数据表 9 | @export var table: TableType 10 | ## 模型描述 11 | @export_multiline var description: String 12 | ## 字段映射规则 13 | @export var field_mappings: Dictionary[String, String] = {} 14 | ## 默认值 15 | @export var default_values: Dictionary[String, Variant] = {} 16 | 17 | ## 构造函数 18 | func _init( 19 | p_model_name: String = "", 20 | p_model_script: GDScript = null, 21 | p_table: TableType = null, 22 | p_description: String = "", 23 | p_field_mappings: Dictionary[String, String] = {}, 24 | p_default_values: Dictionary[String, Variant] = {}) -> void: 25 | model_name = p_model_name 26 | model_script = p_model_script 27 | table = p_table 28 | description = p_description 29 | field_mappings = p_field_mappings 30 | default_values = p_default_values 31 | 32 | ## 验证模型配置 33 | func validate() -> bool: 34 | if model_name.is_empty(): 35 | push_error("模型名称为空") 36 | return false 37 | 38 | if not is_instance_valid(model_script): 39 | push_error("无效的模型脚本") 40 | return false 41 | 42 | # 验证每个表格配置 43 | if not is_instance_valid(table): 44 | push_error("无效的表格配置") 45 | return false 46 | 47 | return true 48 | 49 | ## 创建模型实例 50 | func create_instance(data: Dictionary = {}) -> Resource: 51 | if not validate(): 52 | return null 53 | 54 | var instance = model_script.new() 55 | if not is_instance_valid(instance): 56 | push_error("模型实例创建失败") 57 | return null 58 | 59 | # 应用默认值 60 | for field in default_values: 61 | instance.set(field, default_values[field]) 62 | 63 | # 应用数据映射 64 | for field in data: 65 | var target_field = field_mappings.get(field, field) 66 | if instance.get_property_list().any(func(p): return p["name"] == target_field): 67 | instance.set(target_field, data[field]) 68 | 69 | # 调用初始化方法 70 | if instance.has_method("_init_from_data"): 71 | instance.call("_init_from_data", data) 72 | 73 | return instance 74 | 75 | ## 获取模型的所有属性 76 | func get_properties() -> Array[Dictionary]: 77 | if not is_instance_valid(model_script): 78 | return [] 79 | 80 | var instance = model_script.new() 81 | if not is_instance_valid(instance): 82 | return [] 83 | 84 | var properties: Array[Dictionary] = [] 85 | for property in instance.get_property_list(): 86 | if property["usage"] & PROPERTY_USAGE_SCRIPT_VARIABLE: 87 | properties.append(property) 88 | 89 | instance.free() 90 | return properties 91 | 92 | ## 获取字段映射 93 | func get_field_mapping(field: String = "") -> String: 94 | return field_mappings.get(field, field) 95 | 96 | ## 获取默认值 97 | func get_default_value(field: String) -> Variant: 98 | return default_values.get(field, null) 99 | -------------------------------------------------------------------------------- /source/resource/model_type.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bf3cohk1etxvp 2 | -------------------------------------------------------------------------------- /source/resource/table_type.gd: -------------------------------------------------------------------------------- 1 | extends Resource 2 | class_name TableType 3 | 4 | ## 表格名称 5 | @export var table_name: StringName 6 | ## 表格文件路径列表 7 | @export_file var table_paths: PackedStringArray 8 | ## 表格描述 9 | @export_multiline var description: String 10 | ## 主键字段名 11 | @export var primary_key: String = "ID" 12 | ## 是否启用缓存 13 | @export var enable_cache: bool = true 14 | ## 表格验证规则 15 | @export var validation_rules: Dictionary = {} 16 | ## 表格缓存 17 | @export_storage var cache: Dictionary = {} 18 | ## 加载状态 19 | @export_storage var is_loaded: bool = false 20 | 21 | ## 初始化函数 22 | func _init( 23 | p_table_name: String = "", 24 | p_table_paths: Variant = [], 25 | p_description: String = "", 26 | p_primary_key: String = "ID", 27 | p_enable_cache: bool = true) -> void: 28 | table_name = p_table_name 29 | description = p_description 30 | primary_key = p_primary_key 31 | enable_cache = p_enable_cache 32 | 33 | # 处理表格路径参数 34 | if p_table_paths is String: 35 | # 如果是单个路径字符串,转换为数组 36 | table_paths = [p_table_paths] 37 | elif p_table_paths is Array: 38 | # 如果是数组,确保所有元素都是字符串 39 | table_paths = [] 40 | for path in p_table_paths: 41 | if path is String: 42 | table_paths.append(path) 43 | else: 44 | table_paths = [] 45 | 46 | ## 获取缓存数据表行 47 | func get_cache_item(item_id : String) -> Dictionary: 48 | if cache.has(item_id): 49 | return cache[item_id] 50 | push_warning("未找到缓存数据表行:%s" % item_id) 51 | return {} 52 | 53 | ## 验证表格数据是否有效 54 | func validate_data(data: Dictionary) -> bool: 55 | if data.is_empty(): 56 | push_error("数据为空") 57 | return false 58 | if not data.has(primary_key): 59 | push_error("缺少主键:%s" % primary_key) 60 | return false 61 | # 应用验证规则 62 | for field in validation_rules: 63 | var rule = validation_rules[field] 64 | if not _validate_field(data, field, rule): 65 | return false 66 | return true 67 | 68 | ## 验证字段 69 | func _validate_field(data: Dictionary, field: String, rule: Dictionary) -> bool: 70 | if not data.has(field): 71 | if rule.get("required", false): 72 | push_error("缺少必需字段:%s" % field) 73 | return false 74 | return true 75 | 76 | var value = data[field] 77 | 78 | # 类型检查 79 | if rule.has("type"): 80 | var type_name = rule["type"] 81 | if not _validate_type(value, type_name): 82 | push_error("字段类型错误:%s,期望类型:%s" % [field, type_name]) 83 | return false 84 | 85 | # 范围检查 86 | if rule.has("range"): 87 | var range_value = rule["range"] 88 | if not _validate_range(value, range_value): 89 | push_error("字段值超出范围:%s" % field) 90 | return false 91 | 92 | # 枚举检查 93 | if rule.has("enum"): 94 | var enum_values = rule["enum"] 95 | if not enum_values.has(value): 96 | push_error("字段值不在枚举范围内:%s" % field) 97 | return false 98 | 99 | return true 100 | 101 | ## 验证类型 102 | func _validate_type(value: Variant, type_name: String) -> bool: 103 | match type_name: 104 | "int": return value is int 105 | "float": return value is float 106 | "string": return value is String 107 | "bool": return value is bool 108 | "array": return value is Array 109 | "dictionary": return value is Dictionary 110 | "vector2": return value is Vector2 111 | "vector3": return value is Vector3 112 | _: return true 113 | 114 | ## 验证范围 115 | func _validate_range(value: Variant, range_value: Dictionary) -> bool: 116 | if range_value.has("min") and value < range_value["min"]: 117 | return false 118 | if range_value.has("max") and value > range_value["max"]: 119 | return false 120 | return true 121 | -------------------------------------------------------------------------------- /source/resource/table_type.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c65rwndbbr15i 2 | -------------------------------------------------------------------------------- /source/thread_pool.gd: -------------------------------------------------------------------------------- 1 | # addons/GDDataForge/source/thread_pool.gd 2 | extends Node 3 | class_name ThreadPool 4 | 5 | signal task_completed(task_id: String, result: Variant) 6 | signal all_tasks_completed 7 | 8 | # 线程池配置 9 | const MAX_THREADS := 4 # 最大线程数 10 | const QUEUE_SIZE := 100 # 任务队列大小 11 | 12 | # 线程状态 13 | enum ThreadState { 14 | IDLE, 15 | BUSY 16 | } 17 | 18 | # 任务结构 19 | class Task: 20 | ## 任务ID 21 | var id: String 22 | ## 任务回调 23 | var callable: Callable 24 | ## 任务参数 25 | var args: Array 26 | ## 任务优先级 27 | var priority: int 28 | 29 | func _init(p_id: String, p_callable: Callable, p_args: Array = [], p_priority: int = 0) -> void: 30 | id = p_id 31 | callable = p_callable 32 | args = p_args 33 | priority = p_priority 34 | 35 | # 线程结构 36 | class WorkerThread: 37 | var thread: Thread 38 | var state: ThreadState 39 | var current_task: Task 40 | 41 | func _init() -> void: 42 | thread = Thread.new() 43 | state = ThreadState.IDLE 44 | current_task = null 45 | 46 | var _threads: Array[WorkerThread] 47 | var _task_queue: Array[Task] 48 | var _mutex: Mutex 49 | var _semaphore: Semaphore 50 | var _exit_thread := false 51 | var _active_tasks := 0 52 | 53 | func _init() -> void: 54 | _mutex = Mutex.new() 55 | _semaphore = Semaphore.new() 56 | _task_queue = [] 57 | _threads = [] 58 | 59 | # 创建工作线程 60 | for i in MAX_THREADS: 61 | var worker := WorkerThread.new() 62 | worker.thread.start(_thread_function.bind(worker)) 63 | _threads.append(worker) 64 | 65 | func _notification(what: int) -> void: 66 | if what == NOTIFICATION_PREDELETE: 67 | shutdown() 68 | 69 | ## 添加任务到队列 70 | func add_task(id: String, callable: Callable, args: Array = [], priority: int = 0) -> void: 71 | var task := Task.new(id, callable, args, priority) 72 | 73 | _mutex.lock() 74 | # 根据优先级插入任务 75 | var inserted := false 76 | for i in _task_queue.size(): 77 | if _task_queue[i].priority < priority: 78 | _task_queue.insert(i, task) 79 | inserted = true 80 | break 81 | 82 | if not inserted: 83 | _task_queue.append(task) 84 | 85 | _mutex.unlock() 86 | _semaphore.post() 87 | 88 | ## 等待所有任务完成 89 | func wait_all() -> void: 90 | while true: 91 | _mutex.lock() 92 | var all_done := _task_queue.is_empty() and _active_tasks == 0 93 | _mutex.unlock() 94 | 95 | if all_done: 96 | break 97 | 98 | await get_tree().process_frame 99 | 100 | ## 关闭线程池 101 | func shutdown() -> void: 102 | _exit_thread = true 103 | 104 | # 通知所有等待的线程 105 | for i in _threads.size(): 106 | _semaphore.post() 107 | 108 | # 等待所有线程结束 109 | for worker in _threads: 110 | worker.thread.wait_to_finish() 111 | 112 | _threads.clear() 113 | 114 | ## 线程函数 115 | func _thread_function(worker: WorkerThread) -> void: 116 | while not _exit_thread: 117 | _semaphore.wait() 118 | 119 | if _exit_thread: 120 | break 121 | 122 | _mutex.lock() 123 | if _task_queue.is_empty(): 124 | _mutex.unlock() 125 | continue 126 | 127 | worker.current_task = _task_queue.pop_front() 128 | worker.state = ThreadState.BUSY 129 | _active_tasks += 1 130 | _mutex.unlock() 131 | 132 | # 执行任务 133 | var result = worker.current_task.callable.callv(worker.current_task.args) 134 | 135 | # 通知主线程任务完成 136 | call_deferred("_on_task_completed", worker.current_task.id, result) 137 | 138 | _mutex.lock() 139 | worker.state = ThreadState.IDLE 140 | worker.current_task = null 141 | _active_tasks -= 1 142 | 143 | if _task_queue.is_empty() and _active_tasks == 0: 144 | call_deferred("_on_all_tasks_completed") 145 | _mutex.unlock() 146 | 147 | ## 任务完成回调 148 | func _on_task_completed(task_id: String, result: Variant) -> void: 149 | task_completed.emit(task_id, result) 150 | 151 | ## 所有任务完成回调 152 | func _on_all_tasks_completed() -> void: 153 | all_tasks_completed.emit() 154 | -------------------------------------------------------------------------------- /source/thread_pool.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bjf37ajg3do60 2 | --------------------------------------------------------------------------------