├── .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.md ├── README_en.md ├── docs ├── .markdownlint.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── changelogs │ ├── unreleased.md │ └── v0.0.2.md ├── systems │ ├── audio_system.md │ ├── audio_system_zh.md │ ├── config_system.md │ ├── config_system_zh.md │ ├── input_system.md │ ├── input_system │ │ ├── README.md │ │ ├── README_zh.md │ │ ├── input_buffer.md │ │ ├── input_buffer_zh.md │ │ ├── input_event_processor.md │ │ ├── input_event_processor_zh.md │ │ ├── input_recorder.md │ │ ├── input_recorder_zh.md │ │ ├── input_state.md │ │ ├── input_state_zh.md │ │ ├── virtual_axis.md │ │ └── virtual_axis_zh.md │ ├── input_system_zh.md │ ├── logger_system.md │ ├── logger_system_zh.md │ ├── resource_system.md │ ├── resource_system_zh.md │ ├── save_system.md │ ├── save_system_zh.md │ ├── scene_system.md │ ├── scene_system_zh.md │ ├── serialization_system.md │ ├── serialization_system_zh.md │ ├── state_machine_system.md │ ├── state_machine_system_zh.md │ ├── tag_system.md │ ├── tag_system_zh.md │ ├── trigger_system.md │ └── trigger_system_zh.md └── utils │ ├── async_io_manager.md │ ├── async_io_manager_zh.md │ ├── frame_splitter.md │ ├── frame_splitter_zh.md │ ├── random_picker.md │ ├── random_picker_zh.md │ ├── threading_system.md │ └── threading_system_zh.md ├── examples ├── audio_demo │ ├── assets │ │ ├── music │ │ │ └── bgm.ogg │ │ ├── sfx │ │ │ └── click.ogg │ │ └── voice │ │ │ └── congratulations.ogg │ ├── audio_demo.gd │ ├── audio_demo.gd.uid │ └── audio_demo.tscn ├── config_demo │ ├── config_demo.gd │ ├── config_demo.gd.uid │ └── config_demo.tscn ├── event_bus_demo │ ├── event_bus_dem.tscn │ ├── event_bus_demo.gd │ └── event_bus_demo.gd.uid ├── input_demo │ ├── README.md │ ├── basic │ │ ├── basic_demo.gd │ │ ├── basic_demo.gd.uid │ │ └── basic_demo.tscn │ ├── event_processor │ │ ├── processor_demo.gd │ │ ├── processor_demo.gd.uid │ │ └── processor_demo.tscn │ ├── input_buffer │ │ ├── buffer_demo.gd │ │ ├── buffer_demo.gd.uid │ │ └── buffer_demo.tscn │ ├── input_recorder │ │ ├── recorder_demo.gd │ │ ├── recorder_demo.gd.uid │ │ └── recorder_demo.tscn │ ├── input_state │ │ ├── combo_demo.gd │ │ ├── combo_demo.gd.uid │ │ └── combo_demo.tscn │ ├── settings_ui.gd │ ├── settings_ui.gd.uid │ └── virtual_axis │ │ ├── axis_visualizer.gd │ │ ├── axis_visualizer.gd.uid │ │ ├── character.tscn │ │ ├── movement_demo.gd │ │ ├── movement_demo.gd.uid │ │ └── movement_demo.tscn ├── random_picker_demo │ ├── test_random_picker.gd │ ├── test_random_picker.gd.uid │ └── test_random_picker_demo.tscn ├── save_demo │ ├── player.gd │ ├── player.gd.uid │ ├── readme.md │ ├── save_demo.gd │ ├── save_demo.gd.uid │ └── save_demo.tscn ├── scene_demo │ ├── scene_demo.gd │ ├── scene_demo.gd.uid │ ├── scene_demo.tscn │ └── scenes │ │ ├── demo_scene.gd │ │ ├── demo_scene.gd.uid │ │ ├── scene1.tscn │ │ ├── scene2.tscn │ │ └── scene3.tscn ├── state_machine │ ├── example.gd │ ├── example.gd.uid │ ├── example.tscn │ ├── example_game_state_machine.gd │ └── example_game_state_machine.gd.uid ├── tag_demo │ ├── tag_character.gd │ ├── tag_character.gd.uid │ ├── tag_demo.gd │ ├── tag_demo.gd.uid │ └── tag_demo.tscn ├── time_demo │ ├── time_demo.gd │ ├── time_demo.gd.uid │ └── time_demo.tscn └── trigger_demo │ ├── trigger_demo.gd │ ├── trigger_demo.gd.uid │ └── trigger_demo.tscn ├── git ├── plugin.cfg ├── plugin.gd ├── plugin.gd.uid ├── setting.gd ├── setting.gd.uid ├── source ├── audio_system │ ├── audio_manager.gd │ └── audio_manager.gd.uid ├── config_system │ ├── config_manager.gd │ └── config_manager.gd.uid ├── core_system.gd ├── core_system.gd.uid ├── entity_system │ ├── entity_manager.gd │ └── entity_manager.gd.uid ├── event_system │ ├── event_bus.gd │ └── event_bus.gd.uid ├── input_system │ ├── config │ │ ├── input_config.gd │ │ ├── input_config.gd.uid │ │ ├── input_config_adapter.gd │ │ └── input_config_adapter.gd.uid │ ├── features │ │ ├── input_buffer.gd │ │ ├── input_buffer.gd.uid │ │ ├── input_event_processor.gd │ │ ├── input_event_processor.gd.uid │ │ ├── input_recorder.gd │ │ ├── input_recorder.gd.uid │ │ ├── input_virtual_axis.gd │ │ └── input_virtual_axis.gd.uid │ ├── input_manager.gd │ ├── input_manager.gd.uid │ ├── input_state.gd │ └── input_state.gd.uid ├── logger │ ├── logger.gd │ └── logger.gd.uid ├── resource_system │ ├── resource_manager.gd │ └── resource_manager.gd.uid ├── save_system │ ├── game_state_data.gd │ ├── game_state_data.gd.uid │ ├── save_format_strategy │ │ ├── async_io_strategy.gd │ │ ├── async_io_strategy.gd.uid │ │ ├── binary_save_strategy.gd │ │ ├── binary_save_strategy.gd.uid │ │ ├── json_save_strategy.gd │ │ ├── json_save_strategy.gd.uid │ │ ├── resource_save_strategy.gd │ │ ├── resource_save_strategy.gd.uid │ │ ├── save_format_strategy.gd │ │ └── save_format_strategy.gd.uid │ ├── save_manager.gd │ └── save_manager.gd.uid ├── scene_system │ ├── scene_base.gd │ ├── scene_base.gd.uid │ ├── scene_manager.gd │ ├── scene_manager.gd.uid │ └── transitions │ │ ├── base_transition.gd │ │ ├── base_transition.gd.uid │ │ ├── dissolve_transition.gd │ │ ├── dissolve_transition.gd.uid │ │ ├── fade_transition.gd │ │ ├── fade_transition.gd.uid │ │ ├── slide_transition.gd │ │ └── slide_transition.gd.uid ├── state_machine │ ├── base_state.gd │ ├── base_state.gd.uid │ ├── base_state_machine.gd │ ├── base_state_machine.gd.uid │ ├── state_machine_manager.gd │ └── state_machine_manager.gd.uid ├── tag_system │ ├── gameplay_tag.gd │ ├── gameplay_tag.gd.uid │ ├── gameplay_tag_container.gd │ ├── gameplay_tag_container.gd.uid │ ├── gameplay_tag_manager.gd │ └── gameplay_tag_manager.gd.uid ├── time_system │ ├── time_manager.gd │ └── time_manager.gd.uid ├── trigger_system │ ├── conditions │ │ ├── composite_trigger_condition.gd │ │ ├── composite_trigger_condition.gd.uid │ │ ├── event_type_trigger_condition.gd │ │ ├── event_type_trigger_condition.gd.uid │ │ ├── state_trigger_condition.gd │ │ └── state_trigger_condition.gd.uid │ ├── gameplay_trigger.gd │ ├── gameplay_trigger.gd.uid │ ├── trigger.gd.uid │ ├── trigger_confition.gd │ ├── trigger_confition.gd.uid │ ├── trigger_manager.gd │ └── trigger_manager.gd.uid └── utils │ ├── async_io_manager.gd │ ├── async_io_manager.gd.uid │ ├── file_dir_handler.gd │ ├── file_dir_handler.gd.uid │ ├── frame_splitter.gd │ ├── frame_splitter.gd.uid │ ├── io_strategies │ ├── compression │ │ ├── compression_strategy.gd │ │ ├── compression_strategy.gd.uid │ │ ├── gzip_compression_strategy.gd │ │ ├── gzip_compression_strategy.gd.uid │ │ ├── no_compression_strategy.gd │ │ └── no_compression_strategy.gd.uid │ ├── encryption │ │ ├── encryption_strategy.gd │ │ ├── encryption_strategy.gd.uid │ │ ├── no_encryption_strategy.gd │ │ ├── no_encryption_strategy.gd.uid │ │ ├── xor_encryption_strategy.gd │ │ └── xor_encryption_strategy.gd.uid │ └── serialization │ │ ├── json_serialization_strategy.gd │ │ ├── json_serialization_strategy.gd.uid │ │ ├── serialization_strategy.gd │ │ └── serialization_strategy.gd.uid │ ├── random_picker.gd │ ├── random_picker.gd.uid │ └── threading │ ├── module_thread.gd │ ├── module_thread.gd.uid │ ├── single_thread.gd │ └── single_thread.gd.uid └── test ├── async_io ├── async_io_test.gd ├── async_io_test.gd.uid └── async_io_test.tscn ├── event_bus_test.gd ├── event_bus_test.gd.uid ├── event_bus_test.tscn ├── frame_splitter ├── frame_splitter_test.gd ├── frame_splitter_test.gd.uid └── frame_splitter_test.tscn ├── random_picker ├── test_random_picker.gd ├── test_random_picker.gd.uid └── test_random_picker.tscn ├── threading ├── threading_test.gd ├── threading_test.gd.uid └── threading_test.tscn └── unit ├── test_event_bus.gd └── test_event_bus.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.com/channels/1255664864593186889/1338049403558035486 5 | about: 请在这里讨论项目相关话题 6 | - name: 项目文档 7 | url: https://gitee.com/Giab/godot_core_system/wikis 8 | about: 查看项目文档 -------------------------------------------------------------------------------- /.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.com/channels/1255664864593186889/1338049403558035486 5 | about: 请在这里进行一般性讨论 -------------------------------------------------------------------------------- /.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 | *.import 2 | *.tmp -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 GodotUIFramework Contributors 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 | -------------------------------------------------------------------------------- /docs/.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "MD041": false, 3 | "MD003": false, 4 | "MD001": false, 5 | "MD022": false, 6 | "MD024": false, 7 | "MD026": false, 8 | "MD033": false, 9 | "no-hard-tabs": false, 10 | "line-length": false 11 | } 12 | -------------------------------------------------------------------------------- /docs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 更新日志 2 | 3 | 所有重要的更改都会记录在这个文件中。 4 | 5 | 本文档格式基于[Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/), 6 | 并且本项目遵循[语义化版本](https://semver.org/lang/zh-CN/)。 7 | 8 | 详细的更新历史记录已迁移至 [changelogs](./changelogs/) 目录下的单独文件。 9 | 10 | ## 主要版本 11 | 12 | - **[待发布]** - 查看 [unreleased.md](./changelogs/unreleased.md) 13 | - **[0.0.2]** - 查看 [v0.0.2.md](./changelogs/v0.0.2.md) 14 | -------------------------------------------------------------------------------- /docs/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # 贡献者公约 2 | 3 | ## 我们的承诺 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 | 项目维护者有权利及责任去删除、编辑、拒绝与本行为准则有所违背的评论、提交、代码、wiki 编辑、问题和其他贡献,以及项目维护者可暂时或永久地禁止任何他们认为有不当、威胁、冒犯、有害行为的贡献者。 30 | 31 | ## 适用范围 32 | 33 | 当一个人代表该项目或其社区时,本行为准则适用于其所有的项目空间和公共空间。代表项目或社区的情况包括:使用官方项目电子邮件地址、通过官方社交媒体帐户发布、在线上或线下活动中担任指定代表。项目维护者可以进一步定义和解释代表项目的其他情况。 34 | 35 | ## 强制执行 36 | 37 | 可以通过[在此输入联系方式],向项目团队举报滥用、骚扰和其他不可接受的行为。 38 | 39 | 所有的投诉都将经过审查和调查,并做出必要的回应。项目团队有义务为举报者保密。具体执行的方针近一步细节可能会单独公布。 40 | 41 | 对于善意遵循或执行本行为准则的项目维护者,不会受到报复性的行为,比如虚假的投诉、敌对的行为、威胁或其他伤害。 42 | 43 | ## 来源 44 | 45 | 本行为准则改编自[贡献者公约][homepage],版本 1.4 46 | 可在此查看:https://www.contributor-covenant.org/zh-cn/version/1/4/code-of-conduct.html 47 | 48 | [homepage]: https://www.contributor-covenant.org 49 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # 贡献指南 2 | 3 | 感谢你考虑为 godot_core_system 做出贡献! 4 | 5 | ## 行为准则 6 | 7 | 本项目采用[贡献者公约](CODE_OF_CONDUCT.md)。参与本项目即表示你同意遵守其条款。 8 | 9 | ## 如何贡献 10 | 11 | ### 报告 Bug 12 | 13 | 1. 使用 GitHub 的[issue tracker](https://github.com/Liweimin0512/godot_core_system/issues) 14 | 2. 检查是否已经存在相同的 issue 15 | 3. 如果没有,创建一个新的 issue,包含: 16 | - 清晰的标题和描述 17 | - 重现步骤 18 | - 期望的行为 19 | - 实际的行为 20 | - Godot 版本和操作系统信息 21 | - 相关的代码片段或截图 22 | 23 | ### 提出新功能 24 | 25 | 1. 先在 issues 中提出建议 26 | 2. 说明这个功能解决什么问题 27 | 3. 描述你设想的实现方式 28 | 4. 等待社区反馈和讨论 29 | 30 | ### 提交代码 31 | 32 | 1. Fork 本仓库 33 | 2. 创建你的特性分支:`git checkout -b feature/AmazingFeature` 34 | 3. 提交你的修改:`git commit -m 'Add some AmazingFeature'` 35 | 4. 推送到分支:`git push origin feature/AmazingFeature` 36 | 5. 创建 Pull Request 37 | 38 | ### 代码风格 39 | 40 | - 遵循[GDScript 风格指南](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_styleguide.html) 41 | - 使用 Tab 进行缩进 42 | - 类名使用 PascalCase 43 | - 函数和变量名使用 snake_case 44 | - 常量使用 SCREAMING_SNAKE_CASE 45 | - 添加适当的注释和文档字符串 46 | 47 | ### 提交信息规范 48 | 49 | 使用清晰的提交信息,格式如下: 50 | 51 | ```xml 52 | 53 | <类型>: <描述> 54 | 55 | [可选的详细描述] 56 | 57 | [可选的关闭issue引用] 58 | ``` 59 | 60 | 类型包括: 61 | 62 | - feat: 新功能 63 | - fix: Bug 修复 64 | - docs: 文档更新 65 | - style: 代码风格修改 66 | - refactor: 代码重构 67 | - test: 测试相关 68 | - chore: 构建过程或辅助工具的变动 69 | 70 | 例如: 71 | 72 | ```xml 73 | 74 | feat: 添加主题切换动画 75 | 76 | 添加了在切换主题时的渐变动画效果,提升用户体验。 77 | 78 | Closes #123 79 | ``` 80 | 81 | ### 文档 82 | 83 | - 为新功能编写文档 84 | - 更新受影响的文档 85 | - 使用清晰的中文描述 86 | - 包含代码示例 87 | 88 | ### 测试 89 | 90 | - 添加适当的测试用例 91 | - 确保所有测试通过 92 | - 测试覆盖主要功能点 93 | 94 | ## 发布流程 95 | 96 | 1. 版本号遵循[语义化版本](https://semver.org/lang/zh-CN/) 97 | 2. 更新 CHANGELOG.md 98 | 3. 创建新的发布标签 99 | 4. 编写发布说明 100 | 101 | ## 问题讨论 102 | 103 | - 使用 GitHub Discussions 进行讨论 104 | - 保持友善和专业 105 | - 尊重他人观点 106 | - 聚焦于技术讨论 107 | 108 | ## 审查标准 109 | 110 | Pull Request 在合并前需要满足: 111 | 112 | 1. 通过所有测试 113 | 2. 符合代码规范 114 | 3. 有适当的文档 115 | 4. 至少一个维护者审查通过 116 | 5. 没有未解决的问题 117 | 118 | ## 许可 119 | 120 | 贡献的代码将采用 MIT 许可证。 121 | -------------------------------------------------------------------------------- /docs/changelogs/unreleased.md: -------------------------------------------------------------------------------- 1 | # [待发布] 2 | 3 | ### ✨ 新增 (Added) 4 | 5 | * **线程实用工具集 (`Threading Utilities`)**: 6 | * 引入 `SingleThread` 和 `ModuleThread`,简化后台任务和多线程管理。 7 | * 提供线程安全的任务提交和信号反馈。 8 | * **加权随机选择器 (`RandomPicker`)**: 新增工具类,方便实现基于权重的随机选择。 9 | 10 | ### 🔄 变更 (Changed) 11 | 12 | * **异步 IO 管理器 (`AsyncIOManager`)**: 重构为更灵活的工具类 (`RefCounted`),不再是单例模块,解耦其使用。 13 | * **配置系统 (`ConfigManager`)**: 14 | * **核心重构**: 切换为使用 Godot 内置的 `ConfigFile` 进行**同步**读写 `.cfg` 文件,移除异步操作和 `AsyncIOManager` 依赖。 15 | * **默认值处理**: 框架不再加载硬编码默认值,`get_value` 需提供默认参数,`reset_config` 清空内存并发送信号,由游戏项目负责应用默认值。 16 | * **API 简化**: 利用 `ConfigFile` 的原生类型支持简化了 API。 17 | * **存档系统 (`SaveManager`)**: 18 | * **核心重构**: 采用**策略模式 (Strategy Pattern)**,支持多种存档格式(Resource, JSON, Binary)。 19 | * **异步**: 全面使用 `async/await` 进行文件操作。 20 | * **功能增强**: 改进了配置管理、错误处理、接口统一性,并添加了加密支持。 21 | * **事件总线 (`EventBus`)**: 重构为使用 Godot 内置信号系统,更符合原生习惯。 22 | * **场景系统 (`SceneSystem`)**: 重构以更贴近 Godot 原生的场景管理方式。 23 | * **日志系统 (`Logger`)**: 添加颜色标识,使日志输出更清晰。 24 | * **开发流程**: 创建 `release` 分支用于发布,主开发流程移至 `dev` 分支。 25 | 26 | ### 🗑️ 移除 (Removed) 27 | 28 | * `release` 分支不再包含 `test` 目录。 29 | * 移除了旧的、单一格式的存档系统实现。 30 | 31 | ### 🛠️ 修复 (Fixed) 32 | 33 | * 重构路径处理以适应不同安装方式 (#28)。 34 | * 修复 2D 场景切换后相机缩放未更新的问题 (#28)。 35 | * 修复部分资源 UID 问题 (#28)。 36 | 37 | ### 🔒 安全 (Security) 38 | 39 | * 为存档系统添加了文件加密支持(主要通过 Binary 策略)。 40 | * (规划中) 添加存档数据验证机制 -------------------------------------------------------------------------------- /docs/changelogs/v0.0.2.md: -------------------------------------------------------------------------------- 1 | # [0.0.2] 2 | 3 | ### 新增 4 | 5 | - 新增 `FrameSplitter` 工具类,用于将耗时操作分散到多帧执行,避免卡顿 6 | - 支持数组、范围、迭代器和自定义处理模式 7 | - 提供进度反馈和完成信号 8 | - 动态调整每帧处理量 9 | - 详细的使用文档和示例 10 | 11 | ### 变更 12 | 13 | - 重构 `InputManager` 输入系统 14 | - 添加输入缓冲系统,提升游戏手感 15 | - 集成 `ConfigManager` 进行输入配置管理 16 | - 添加输入重映射功能和事件通知 17 | - 优化虚拟轴和动作处理 18 | - 添加输入系统配置管理 19 | - 网络输入系统改进 20 | - 重构`SceneSystem`场景系统 21 | - 更接近Godot原生场景系统的使用体验 -------------------------------------------------------------------------------- /docs/systems/audio_system.md: -------------------------------------------------------------------------------- 1 | # Audio System 2 | 3 | The Audio System provides a comprehensive solution for managing game audio, including sound effects, music, and ambient sounds with advanced features like audio pooling and transitions. 4 | 5 | ## Features 6 | 7 | - 🎵 **Audio Categories**: Organize sounds by type (Music, SFX, Voice, etc.) 8 | - 🔊 **Audio Pool**: Efficient sound instance management 9 | - 🎼 **Music Management**: Smooth transitions between tracks 10 | - 📊 **Volume Control**: Category and master volume control 11 | - 🔄 **Audio States**: Support for different audio states (e.g., Menu, Game) 12 | - 📱 **Project Settings**: Configure through Godot's project settings 13 | 14 | ## Core Components 15 | 16 | ### AudioManager 17 | 18 | Central manager for all audio operations: 19 | - Category-based audio management 20 | - Volume control 21 | - Audio state handling 22 | 23 | ```gdscript 24 | # Configure through project settings 25 | core_system/audio_system/master_volume = 1.0 26 | core_system/audio_system/music_volume = 0.8 27 | core_system/audio_system/sfx_volume = 1.0 28 | core_system/audio_system/voice_volume = 1.0 29 | 30 | # Usage example 31 | func _ready() -> void: 32 | var audio = CoreSystem.audio_manager 33 | 34 | # Play music with fade 35 | audio.play_music("res://assets/music/battle_theme.ogg", 2.0) 36 | 37 | # Play sound effect 38 | audio.play_sound("res://assets/sfx/explosion.wav") 39 | 40 | # Set category volume 41 | audio.set_category_volume("music", 0.8) 42 | ``` 43 | 44 | ## Usage Examples 45 | 46 | ### Playing Audio 47 | 48 | ```gdscript 49 | # Play background music (default looping). Note: If audio is imported with loop enabled, it will always loop regardless of loop parameter 50 | func play_background_music() -> void: 51 | CoreSystem.audio_manager.play_music("res://assets/music/main_theme.ogg") 52 | 53 | # Play sound effect 54 | func play_attack_sound() -> void: 55 | CoreSystem.audio_manager.play_sound("res://assets/sfx/attack.wav") 56 | 57 | # Play voice 58 | func play_character_voice() -> void: 59 | CoreSystem.audio_manager.play_voice("res://assets/voice/greeting.ogg") 60 | ``` 61 | 62 | ### Volume Control 63 | 64 | ```gdscript 65 | # Set music volume 66 | func set_music_volume(volume: float) -> void: 67 | CoreSystem.audio_manager.set_volume(CoreSystem.AudioManager.AudioType.MUSIC, volume) 68 | ``` 69 | 70 | ## Best Practices 71 | 72 | 1. **Audio Organization** 73 | - Use clear category names 74 | - Maintain consistent file naming conventions 75 | - Organize audio resources in dedicated folders 76 | 77 | 2. **Performance** 78 | - Use audio pool for frequently played sounds 79 | - Limit simultaneous audio streams 80 | - Clean up unused audio resources 81 | 82 | 3. **User Experience** 83 | - Implement smooth volume transitions 84 | - Provide independent volume controls per category 85 | - Remember user audio preferences 86 | 87 | ## API Reference 88 | 89 | ### AudioManager 90 | - `play_music(path: String, fade_duration: float = 1.0, loop: bool = true) -> void`: Play background music 91 | - Note: Default looping. If audio is imported with loop enabled, it will always loop regardless of loop parameter 92 | - `play_sound(path: String, volume: float = 1.0)`: Play sound effect 93 | - `play_voice(path: String, volume: float = 1.0)`: Play voice 94 | - `set_volume(type: AudioType, volume: float)`: Set volume by type 95 | - `stop_all() -> void`: Stop all audio 96 | - `preload_audio(path: String, type: AudioType) -> void`: Preload audio resources 97 | -------------------------------------------------------------------------------- /docs/systems/audio_system_zh.md: -------------------------------------------------------------------------------- 1 | # 音频系统 2 | 3 | 音频系统为游戏音频管理提供了一个全面的解决方案,包括音效、音乐和环境音,并支持音频池和过渡等高级功能。 4 | 5 | ## 特性 6 | 7 | - 🎵 **音频分类**:按类型组织声音(音乐、音效、语音等) 8 | - 🔊 **音频池**:高效的声音实例管理 9 | - 🎼 **音乐管理**:音轨之间的平滑过渡 10 | - 📊 **音量控制**:分类和主音量控制 11 | - 🔄 **音频状态**:支持不同的音频状态(如菜单、游戏等) 12 | - 📱 **项目设置**:通过 Godot 的项目设置进行配置 13 | 14 | ## 核心组件 15 | 16 | ### AudioManager(音频管理器) 17 | 18 | 所有音频操作的中央管理器: 19 | 20 | - 基于分类的音频管理 21 | - 音量控制 22 | - 音频状态处理 23 | 24 | ```gdscript 25 | # 通过项目设置配置 26 | core_system/audio_system/master_volume = 1.0 27 | core_system/audio_system/music_volume = 0.8 28 | core_system/audio_system/sfx_volume = 1.0 29 | core_system/audio_system/voice_volume = 1.0 30 | 31 | # 使用示例 32 | func _ready() -> void: 33 | var audio = CoreSystem.audio_manager 34 | 35 | # 播放音乐(带淡入淡出) 36 | audio.play_music("res://assets/music/battle_theme.ogg", 2.0) 37 | 38 | # 播放音效 39 | audio.play_sound("res://assets/sfx/explosion.wav") 40 | 41 | # 设置分类音量 42 | audio.set_category_volume("music", 0.8) 43 | ``` 44 | 45 | ## 使用示例 46 | 47 | ### 播放音频 48 | 49 | ```gdscript 50 | # 播放背景音乐 默认循环播放 如果音频导入时设置为循环播放,则将一直循环,哪怕loop参数设置为false 51 | func play_background_music() -> void: 52 | CoreSystem.audio_manager.play_music("res://assets/music/main_theme.ogg") 53 | 54 | # 播放音效 55 | func play_attack_sound() -> void: 56 | CoreSystem.audio_manager.play_sound("res://assets/sfx/attack.wav") 57 | 58 | # 播放语音 59 | func play_character_voice() -> void: 60 | CoreSystem.audio_manager.play_voice("res://assets/voice/greeting.ogg") 61 | ``` 62 | 63 | ### 音量控制 64 | 65 | ```gdscript 66 | # 设置背景音乐音量 67 | func set_music_volume(volume: float) -> void: 68 | CoreSystem.audio_manager.set_volume(CoreSystem.AudioManager.AudioType.MUSIC,volume) 69 | 70 | ``` 71 | 72 | ## 最佳实践 73 | 74 | 1. **音频组织** 75 | 76 | - 使用清晰的分类名称 77 | - 保持一致的文件命名约定 78 | - 在专用文件夹中组织音频资源 79 | 80 | 2. **性能** 81 | 82 | - 对频繁播放的声音使用音频池 83 | - 限制同时播放的音频流 84 | - 清理未使用的音频资源 85 | 86 | 3. **用户体验** 87 | - 实现平滑的音量过渡 88 | - 为不同分类提供独立的音量控制 89 | - 记住用户的音频偏好 90 | 91 | ## API 参考 92 | 93 | ### 音频管理器 AudioManager 94 | 95 | - `play_music(path: String, fade_duration: float = 1.0, loop: bool = true) -> void`: 播放背景音乐 96 | - 注意:默认循环播放 如果音频导入时设置为循环播放,则将一直循环,哪怕loop参数设置为false 97 | - `play_sound(path: String, volume: float = 1.0)`: 播放音效 98 | - `play_voice(path: String, volume: float = 1.0)`: 播放语音 99 | - `set_volume(type: AudioType, volume: float)`: 按类型设置音量 100 | - `stop_all() -> void`: 停止所有音频 101 | - `preload_audio(path: String, type: AudioType) -> void`: 预加载音频资源 102 | -------------------------------------------------------------------------------- /docs/systems/input_system.md: -------------------------------------------------------------------------------- 1 | # 输入系统 (Input System) 2 | 3 | ## 简介 4 | 输入系统是一个全面的输入处理框架,提供了高级的输入管理、状态跟踪、缓冲处理和事件记录功能。它由多个专门的子系统组成,每个子系统都专注于特定的输入处理方面。 5 | 6 | ## 系统架构 7 | 输入系统由以下核心组件组成: 8 | 9 | ### 输入管理器 (InputManager) 10 | 中央控制器,协调各个子系统的工作。主要职责包括: 11 | - 初始化和管理所有子系统 12 | - 处理原始输入事件 13 | - 更新输入状态 14 | - 分发处理后的事件 15 | 16 | ### 子系统 17 | 1. [虚拟轴系统](input_system/virtual_axis.md) 18 | - 处理基于轴的输入(如角色移动) 19 | - 支持死区和灵敏度调节 20 | - 提供实时轴值更新 21 | 22 | 2. [输入状态系统](input_system/input_state.md) 23 | - 跟踪所有输入动作的状态 24 | - 提供精确的时间戳记录 25 | - 支持输入强度检测 26 | 27 | 3. [输入缓冲系统](input_system/input_buffer.md) 28 | - 实现输入缓冲机制 29 | - 支持可配置的缓冲窗口 30 | - 自动管理缓冲优先级 31 | 32 | 4. [输入记录器](input_system/input_recorder.md) 33 | - 记录输入序列 34 | - 支持回放功能 35 | - 提供存档和加载功能 36 | 37 | 5. [事件处理器](input_system/input_event_processor.md) 38 | - 处理和过滤输入事件 39 | - 管理事件优先级 40 | - 提供事件转换功能 41 | 42 | ## 使用示例 43 | ### 基本设置 44 | ```gdscript 45 | # 获取输入管理器实例 46 | @onready var input_manager = CoreSystem.input_manager 47 | 48 | # 设置虚拟轴 49 | input_manager.virtual_axis.register_axis( 50 | "movement", 51 | "ui_right", 52 | "ui_left", 53 | "ui_up", 54 | "ui_down" 55 | ) 56 | 57 | # 检查输入状态 58 | if input_manager.input_state.is_just_pressed("jump"): 59 | character.jump() 60 | ``` 61 | 62 | ### 高级功能 63 | ```gdscript 64 | # 使用输入缓冲 65 | input_manager.input_buffer.add_buffer("attack", 1.0) 66 | 67 | # 记录输入序列 68 | input_manager.input_recorder.start_recording() 69 | 70 | # 处理自定义事件 71 | input_manager.event_processor.process_event(event) 72 | ``` 73 | 74 | ## 配置和自定义 75 | ### 系统配置 76 | ```gdscript 77 | # 配置虚拟轴 78 | virtual_axis.set_sensitivity(1.0) 79 | virtual_axis.set_deadzone(0.2) 80 | 81 | # 设置缓冲窗口 82 | input_buffer.set_buffer_window(0.15) 83 | ``` 84 | 85 | ### 信号连接 86 | ```gdscript 87 | # 监听动作触发 88 | input_manager.action_triggered.connect(_on_action_triggered) 89 | 90 | # 监听轴值变化 91 | input_manager.axis_changed.connect(_on_axis_changed) 92 | ``` 93 | 94 | ## 最佳实践 95 | 1. 状态管理 96 | - 使用输入状态系统而不是直接检查输入 97 | - 在适当的时机更新和重置状态 98 | 99 | 2. 事件处理 100 | - 使用事件处理器过滤无关事件 101 | - 遵循事件优先级规则 102 | 103 | 3. 性能优化 104 | - 合理使用缓冲机制 105 | - 及时清理过期数据 106 | - 避免过度记录 107 | 108 | ## 调试支持 109 | - 详细的状态日志 110 | - 输入序列记录和回放 111 | - 性能监控工具 112 | 113 | ## 扩展性 114 | 系统支持通过以下方式进行扩展: 115 | 1. 添加新的子系统 116 | 2. 自定义事件处理规则 117 | 3. 实现特定游戏类型的输入处理 118 | 119 | ## 注意事项 120 | 1. 初始化顺序 121 | - 确保在使用前正确初始化所有子系统 122 | - 遵循依赖关系 123 | 124 | 2. 资源管理 125 | - 及时清理不需要的记录 126 | - 控制缓冲区大小 127 | 128 | 3. 线程安全 129 | - 在主线程处理输入 130 | - 注意异步操作的影响 131 | 132 | ## API 参考 133 | 请参考各个子系统的详细文档: 134 | - [虚拟轴系统](input_system/virtual_axis.md) 135 | - [输入状态系统](input_system/input_state.md) 136 | - [输入缓冲系统](input_system/input_buffer.md) 137 | - [输入记录器](input_system/input_recorder.md) 138 | - [事件处理器](input_system/input_event_processor.md) 139 | -------------------------------------------------------------------------------- /docs/systems/input_system/README.md: -------------------------------------------------------------------------------- 1 | # 输入系统文档目录 2 | 3 | 本目录包含了输入系统的所有相关文档: 4 | 5 | ## 核心文档 6 | - [输入系统概述](../input_system.md) - 系统整体架构和使用指南 7 | 8 | ## 子系统文档 9 | - [虚拟轴系统](virtual_axis.md) - 处理基于轴的输入 10 | - [输入状态系统](input_state.md) - 跟踪输入状态 11 | - [输入缓冲系统](input_buffer.md) - 处理输入缓冲 12 | - [输入记录器](input_recorder.md) - 记录和回放输入 13 | - [事件处理器](input_event_processor.md) - 处理输入事件 14 | 15 | ## 使用说明 16 | 每个子系统文档都包含: 17 | - 功能介绍 18 | - API 参考 19 | - 使用示例 20 | - 最佳实践 21 | - 注意事项 22 | -------------------------------------------------------------------------------- /docs/systems/input_system/README_zh.md: -------------------------------------------------------------------------------- 1 | # 输入系统文档目录 2 | 3 | 本目录包含了输入系统的所有相关文档: 4 | 5 | ## 核心文档 6 | - [输入系统概述](../input_system_zh.md) - 系统整体架构和使用指南 7 | 8 | ## 子系统文档 9 | - [虚拟轴系统](virtual_axis_zh.md) - 处理基于轴的输入,如角色移动和摄像机控制 10 | - [输入状态系统](input_state_zh.md) - 跟踪和管理输入状态,支持时间戳和输入强度 11 | - [输入缓冲系统](input_buffer_zh.md) - 提供输入缓冲机制,优化游戏手感 12 | - [输入记录器](input_recorder_zh.md) - 记录和回放输入序列,用于游戏回放和调试 13 | - [事件处理器](input_event_processor_zh.md) - 处理和过滤输入事件,管理事件优先级 14 | 15 | ## 文档说明 16 | 每个子系统文档都包含: 17 | - 功能介绍和特性说明 18 | - API 参考和使用示例 19 | - 高级用法和最佳实践 20 | - 性能优化建议 21 | - 注意事项和常见问题 22 | 23 | ## 版本信息 24 | - 最后更新:2025-03-26 25 | - 版本:1.0.0 26 | 27 | ## 反馈和贡献 28 | 如果您发现文档中的问题或有改进建议,请通过以下方式反馈: 29 | 1. 提交 Issue 30 | 2. 创建 Pull Request 31 | 3. 联系项目维护者 32 | -------------------------------------------------------------------------------- /docs/systems/input_system/input_buffer.md: -------------------------------------------------------------------------------- 1 | # 输入缓冲系统 (Input Buffer System) 2 | 3 | ## 简介 4 | 输入缓冲系统为游戏提供了一个灵活的输入缓冲机制,可以在一定时间窗口内记住玩家的输入,提高游戏的操作手感,特别适用于格斗游戏、动作游戏等需要精确输入的场景。 5 | 6 | ## 功能特性 7 | - 可配置的缓冲时间窗口 8 | - 支持多个动作的并行缓冲 9 | - 自动清理过期缓冲 10 | - 优先级队列管理 11 | 12 | ## 核心类 13 | ### InputBuffer 14 | 主要的缓冲管理类,负责处理输入缓冲的添加、检查和清理。 15 | 16 | #### 主要属性 17 | ```gdscript 18 | var _buffer_window: float = 0.1 # 默认缓冲窗口为100ms 19 | var _input_buffers: Dictionary # 存储各个动作的缓冲 20 | ``` 21 | 22 | #### 主要方法 23 | - `add_buffer(action_name, strength)`: 添加一个输入缓冲 24 | - `has_buffer(action_name)`: 检查是否存在有效的缓冲 25 | - `get_buffer_strength(action_name)`: 获取缓冲的输入强度 26 | - `clean_expired_buffers()`: 清理过期的缓冲 27 | - `set_buffer_window(window)`: 设置缓冲时间窗口 28 | - `clear_buffers()`: 清除所有缓冲 29 | 30 | ## 使用示例 31 | ```gdscript 32 | # 设置缓冲窗口 33 | input_buffer.set_buffer_window(0.15) # 150ms的缓冲窗口 34 | 35 | # 添加输入缓冲 36 | input_buffer.add_buffer("attack", 1.0) 37 | 38 | # 检查是否有有效缓冲 39 | if input_buffer.has_buffer("attack"): 40 | character.perform_attack() 41 | ``` 42 | 43 | ## 高级用法 44 | ### 连招系统集成 45 | ```gdscript 46 | # 在连招系统中使用输入缓冲 47 | func check_combo(): 48 | if input_buffer.has_buffer("punch"): 49 | if _last_move == "kick": 50 | perform_special_move() 51 | else: 52 | perform_normal_punch() 53 | ``` 54 | 55 | ### 优先级处理 56 | 当多个输入在缓冲窗口内时,系统会: 57 | 1. 优先处理最新的输入 58 | 2. 考虑输入强度 59 | 3. 根据游戏逻辑决定最终行为 60 | 61 | ## 实现细节 62 | ### 缓冲数据结构 63 | ```gdscript 64 | class BufferData: 65 | var timestamp: float # 输入时间戳 66 | var strength: float # 输入强度 67 | var consumed: bool # 是否已消费 68 | ``` 69 | 70 | ### 缓冲清理 71 | - 自动清理过期缓冲 72 | - 手动清理已消费的缓冲 73 | - 场景切换时清理所有缓冲 74 | 75 | ## 最佳实践 76 | 1. 根据游戏类型调整缓冲窗口 77 | - 格斗游戏:较短窗口(50-100ms) 78 | - 动作游戏:中等窗口(100-200ms) 79 | - 休闲游戏:较长窗口(200-300ms) 80 | 81 | 2. 合理使用缓冲检查 82 | ```gdscript 83 | # 好的做法:在合适的时机检查缓冲 84 | func _physics_process(delta): 85 | update_movement() 86 | check_action_buffers() 87 | ``` 88 | 89 | 3. 及时清理缓冲 90 | ```gdscript 91 | # 在状态切换时清理相关缓冲 92 | func change_state(new_state): 93 | clear_related_buffers() 94 | current_state = new_state 95 | ``` 96 | 97 | ## 注意事项 98 | - 定期调用 clean_expired_buffers() 以防止内存泄漏 99 | - 避免过长的缓冲窗口,可能导致不期望的行为 100 | - 在状态切换或场景转换时适当清理缓冲 101 | -------------------------------------------------------------------------------- /docs/systems/input_system/input_buffer_zh.md: -------------------------------------------------------------------------------- 1 | # 输入缓冲系统 (Input Buffer System) 2 | 3 | ## 简介 4 | 输入缓冲系统为游戏提供了一个灵活的输入缓冲机制,可以在一定时间窗口内记住玩家的输入,提高游戏的操作手感,特别适用于格斗游戏、动作游戏等需要精确输入的场景。 5 | 6 | ## 功能特性 7 | - 可配置的缓冲时间窗口 8 | - 支持多个动作的并行缓冲 9 | - 自动清理过期缓冲 10 | - 优先级队列管理 11 | 12 | ## 核心类 13 | ### InputBuffer 14 | 主要的缓冲管理类,负责处理输入缓冲的添加、检查和清理。 15 | 16 | #### 主要属性 17 | ```gdscript 18 | var _buffer_window: float = 0.1 # 默认缓冲窗口为100ms 19 | var _input_buffers: Dictionary # 存储各个动作的缓冲 20 | ``` 21 | 22 | #### 主要方法 23 | - `add_buffer(action_name, strength)`: 添加一个输入缓冲 24 | - `has_buffer(action_name)`: 检查是否存在有效的缓冲 25 | - `get_buffer_strength(action_name)`: 获取缓冲的输入强度 26 | - `clean_expired_buffers()`: 清理过期的缓冲 27 | - `set_buffer_window(window)`: 设置缓冲时间窗口 28 | - `clear_buffers()`: 清除所有缓冲 29 | 30 | ## 使用示例 31 | ### 基础用法 32 | ```gdscript 33 | # 设置缓冲窗口 34 | input_buffer.set_buffer_window(0.15) # 150ms的缓冲窗口 35 | 36 | # 添加输入缓冲 37 | input_buffer.add_buffer("attack", 1.0) 38 | 39 | # 检查是否有有效缓冲 40 | if input_buffer.has_buffer("attack"): 41 | character.perform_attack() 42 | ``` 43 | 44 | ### 连招系统集成 45 | ```gdscript 46 | # 在连招系统中使用输入缓冲 47 | func check_combo(): 48 | if input_buffer.has_buffer("punch"): 49 | if _last_move == "kick": 50 | perform_special_move() 51 | else: 52 | perform_normal_punch() 53 | ``` 54 | 55 | ## 高级特性 56 | ### 缓冲数据结构 57 | ```gdscript 58 | class BufferData: 59 | var timestamp: float # 输入时间戳 60 | var strength: float # 输入强度 61 | var consumed: bool # 是否已消费 62 | ``` 63 | 64 | ### 优先级处理 65 | 当多个输入在缓冲窗口内时,系统会: 66 | 1. 优先处理最新的输入 67 | 2. 考虑输入强度 68 | 3. 根据游戏逻辑决定最终行为 69 | 70 | ## 最佳实践 71 | ### 缓冲窗口设置 72 | 根据游戏类型调整缓冲窗口: 73 | - 格斗游戏:较短窗口(50-100ms) 74 | - 动作游戏:中等窗口(100-200ms) 75 | - 休闲游戏:较长窗口(200-300ms) 76 | 77 | ### 缓冲检查时机 78 | ```gdscript 79 | # 在物理更新中检查缓冲 80 | func _physics_process(delta): 81 | update_movement() 82 | check_action_buffers() 83 | ``` 84 | 85 | ### 状态切换处理 86 | ```gdscript 87 | # 在状态切换时清理相关缓冲 88 | func change_state(new_state): 89 | clear_related_buffers() 90 | current_state = new_state 91 | ``` 92 | 93 | ## 性能优化 94 | 1. 定期清理过期缓冲 95 | 2. 限制缓冲队列大小 96 | 3. 及时消费已使用的缓冲 97 | 98 | ## 注意事项 99 | - 定期调用 clean_expired_buffers() 以防止内存泄漏 100 | - 避免过长的缓冲窗口,可能导致不期望的行为 101 | - 在状态切换或场景转换时适当清理缓冲 102 | - 合理使用缓冲强度来实现更精确的输入控制 103 | -------------------------------------------------------------------------------- /docs/systems/input_system/input_event_processor.md: -------------------------------------------------------------------------------- 1 | # 输入事件处理器 (Input Event Processor) 2 | 3 | ## 简介 4 | 输入事件处理器提供了一个统一的接口来处理和过滤各种输入事件,支持自定义事件处理规则和优先级管理。它是输入系统的核心组件之一,负责对原始输入事件进行预处理和分发。 5 | 6 | ## 功能特性 7 | - 输入事件过滤 8 | - 事件优先级管理 9 | - 自定义处理规则 10 | - 事件转换和规范化 11 | - 事件队列管理 12 | 13 | ## 核心类 14 | ### InputEventProcessor 15 | 主要的事件处理器类,负责处理和分发输入事件。 16 | 17 | #### 主要方法 18 | ```gdscript 19 | class_name InputEventProcessor 20 | 21 | func process_event(event: InputEvent) -> bool: 22 | # 处理输入事件,返回是否应该继续处理 23 | if _should_ignore_event(event): 24 | return false 25 | 26 | # 处理事件并返回结果 27 | return _process_event_internal(event) 28 | ``` 29 | 30 | ## 事件处理流程 31 | 1. 事件接收 32 | 2. 事件验证 33 | 3. 事件过滤 34 | 4. 事件转换 35 | 5. 事件分发 36 | 37 | ### 示例代码 38 | ```gdscript 39 | # 在输入管理器中使用事件处理器 40 | func _input(event: InputEvent) -> void: 41 | if not event_processor.process_event(event): 42 | return 43 | _process_action_input(event) 44 | ``` 45 | 46 | ## 事件过滤 47 | ### 内置过滤规则 48 | - 重复事件过滤 49 | - 无效事件过滤 50 | - 优先级过滤 51 | 52 | ### 自定义过滤规则 53 | ```gdscript 54 | func _should_ignore_event(event: InputEvent) -> bool: 55 | # 忽略未知类型的事件 56 | if not (event is InputEventKey or 57 | event is InputEventMouseButton or 58 | event is InputEventJoypadButton): 59 | return true 60 | return false 61 | ``` 62 | 63 | ## 事件转换 64 | ### 输入规范化 65 | 将不同类型的输入事件转换为统一格式: 66 | ```gdscript 67 | func _normalize_event(event: InputEvent) -> Dictionary: 68 | return { 69 | "type": _get_event_type(event), 70 | "action": _get_event_action(event), 71 | "strength": event.get_action_strength(), 72 | "timestamp": Time.get_ticks_msec() 73 | } 74 | ``` 75 | 76 | ### 事件合并 77 | 处理可能的重复或冲突事件: 78 | ```gdscript 79 | func _merge_events(event1: Dictionary, event2: Dictionary) -> Dictionary: 80 | # 根据规则合并事件 81 | return { 82 | "type": event1.type, 83 | "action": event1.action, 84 | "strength": max(event1.strength, event2.strength), 85 | "timestamp": event1.timestamp 86 | } 87 | ``` 88 | 89 | ## 优先级管理 90 | ### 优先级规则 91 | 1. 系统级事件 (最高优先级) 92 | 2. UI事件 93 | 3. 游戏输入事件 94 | 4. 调试事件 (最低优先级) 95 | 96 | ### 优先级实现 97 | ```gdscript 98 | enum Priority { 99 | SYSTEM = 0, 100 | UI = 1, 101 | GAMEPLAY = 2, 102 | DEBUG = 3 103 | } 104 | 105 | func _get_event_priority(event: InputEvent) -> int: 106 | if event.is_action("ui_cancel"): 107 | return Priority.SYSTEM 108 | elif event is InputEventMouseButton: 109 | return Priority.UI 110 | return Priority.GAMEPLAY 111 | ``` 112 | 113 | ## 事件队列 114 | ### 队列管理 115 | ```gdscript 116 | var _event_queue: Array = [] 117 | var _max_queue_size: int = 32 118 | 119 | func queue_event(event: InputEvent) -> void: 120 | if _event_queue.size() >= _max_queue_size: 121 | _event_queue.pop_front() 122 | _event_queue.push_back(event) 123 | ``` 124 | 125 | ### 队列处理 126 | ```gdscript 127 | func process_queued_events() -> void: 128 | while not _event_queue.is_empty(): 129 | var event = _event_queue.pop_front() 130 | process_event(event) 131 | ``` 132 | 133 | ## 调试支持 134 | ### 事件日志 135 | ```gdscript 136 | func _log_event(event: InputEvent) -> void: 137 | if _debug_mode: 138 | print("处理事件: ", _get_event_description(event)) 139 | ``` 140 | 141 | ### 性能监控 142 | ```gdscript 143 | var _processing_times: Array = [] 144 | 145 | func _monitor_performance(event: InputEvent) -> void: 146 | var start_time = Time.get_ticks_usec() 147 | process_event(event) 148 | var end_time = Time.get_ticks_usec() 149 | _processing_times.append(end_time - start_time) 150 | ``` 151 | 152 | ## 最佳实践 153 | 1. 事件处理要快速高效 154 | 2. 避免在事件处理中进行耗时操作 155 | 3. 合理使用事件过滤减少不必要的处理 156 | 4. 保持处理逻辑的模块化和可测试性 157 | 158 | ## 注意事项 159 | - 确保事件处理的线程安全 160 | - 避免事件处理循环 161 | - 合理设置队列大小限制 162 | - 注意事件处理的性能开销 163 | -------------------------------------------------------------------------------- /docs/systems/input_system/input_event_processor_zh.md: -------------------------------------------------------------------------------- 1 | # 输入事件处理器 (Input Event Processor) 2 | 3 | ## 简介 4 | 输入事件处理器提供了一个统一的接口来处理和过滤各种输入事件,支持自定义事件处理规则和优先级管理。它是输入系统的核心组件之一,负责对原始输入事件进行预处理和分发。 5 | 6 | ## 功能特性 7 | - 输入事件过滤 8 | - 事件优先级管理 9 | - 自定义处理规则 10 | - 事件转换和规范化 11 | - 事件队列管理 12 | 13 | ## 核心类 14 | ### InputEventProcessor 15 | 主要的事件处理器类,负责处理和分发输入事件。 16 | 17 | #### 主要方法 18 | ```gdscript 19 | class_name InputEventProcessor 20 | 21 | func process_event(event: InputEvent) -> bool: 22 | # 处理输入事件,返回是否应该继续处理 23 | if _should_ignore_event(event): 24 | return false 25 | 26 | # 处理事件并返回结果 27 | return _process_event_internal(event) 28 | ``` 29 | 30 | ## 事件处理流程 31 | 1. 事件接收 32 | 2. 事件验证 33 | 3. 事件过滤 34 | 4. 事件转换 35 | 5. 事件分发 36 | 37 | ### 使用示例 38 | ```gdscript 39 | # 在输入管理器中使用事件处理器 40 | func _input(event: InputEvent) -> void: 41 | if not event_processor.process_event(event): 42 | return 43 | _process_action_input(event) 44 | ``` 45 | 46 | ## 事件过滤 47 | ### 内置过滤规则 48 | - 重复事件过滤 49 | - 无效事件过滤 50 | - 优先级过滤 51 | 52 | ### 自定义过滤规则 53 | ```gdscript 54 | func _should_ignore_event(event: InputEvent) -> bool: 55 | # 忽略未知类型的事件 56 | if not (event is InputEventKey or 57 | event is InputEventMouseButton or 58 | event is InputEventJoypadButton): 59 | return true 60 | return false 61 | ``` 62 | 63 | ## 事件转换 64 | ### 输入规范化 65 | ```gdscript 66 | func _normalize_event(event: InputEvent) -> Dictionary: 67 | return { 68 | "type": _get_event_type(event), 69 | "action": _get_event_action(event), 70 | "strength": event.get_action_strength(), 71 | "timestamp": Time.get_ticks_msec() 72 | } 73 | ``` 74 | 75 | ### 事件合并 76 | ```gdscript 77 | func _merge_events(event1: Dictionary, event2: Dictionary) -> Dictionary: 78 | # 根据规则合并事件 79 | return { 80 | "type": event1.type, 81 | "action": event1.action, 82 | "strength": max(event1.strength, event2.strength), 83 | "timestamp": event1.timestamp 84 | } 85 | ``` 86 | 87 | ## 优先级管理 88 | ### 优先级规则 89 | 1. 系统级事件 (最高优先级) 90 | 2. UI事件 91 | 3. 游戏输入事件 92 | 4. 调试事件 (最低优先级) 93 | 94 | ### 优先级实现 95 | ```gdscript 96 | enum Priority { 97 | SYSTEM = 0, 98 | UI = 1, 99 | GAMEPLAY = 2, 100 | DEBUG = 3 101 | } 102 | 103 | func _get_event_priority(event: InputEvent) -> int: 104 | if event.is_action("ui_cancel"): 105 | return Priority.SYSTEM 106 | elif event is InputEventMouseButton: 107 | return Priority.UI 108 | return Priority.GAMEPLAY 109 | ``` 110 | 111 | ## 事件队列 112 | ### 队列管理 113 | ```gdscript 114 | var _event_queue: Array = [] 115 | var _max_queue_size: int = 32 116 | 117 | func queue_event(event: InputEvent) -> void: 118 | if _event_queue.size() >= _max_queue_size: 119 | _event_queue.pop_front() 120 | _event_queue.push_back(event) 121 | ``` 122 | 123 | ### 队列处理 124 | ```gdscript 125 | func process_queued_events() -> void: 126 | while not _event_queue.is_empty(): 127 | var event = _event_queue.pop_front() 128 | process_event(event) 129 | ``` 130 | 131 | ## 调试支持 132 | ### 事件日志 133 | ```gdscript 134 | func _log_event(event: InputEvent) -> void: 135 | if _debug_mode: 136 | print("处理事件: ", _get_event_description(event)) 137 | ``` 138 | 139 | ### 性能监控 140 | ```gdscript 141 | var _processing_times: Array = [] 142 | 143 | func _monitor_performance(event: InputEvent) -> void: 144 | var start_time = Time.get_ticks_usec() 145 | process_event(event) 146 | var end_time = Time.get_ticks_usec() 147 | _processing_times.append(end_time - start_time) 148 | ``` 149 | 150 | ## 最佳实践 151 | 1. 事件处理要快速高效 152 | 2. 避免在事件处理中进行耗时操作 153 | 3. 合理使用事件过滤减少不必要的处理 154 | 4. 保持处理逻辑的模块化和可测试性 155 | 156 | ## 注意事项 157 | - 确保事件处理的线程安全 158 | - 避免事件处理循环 159 | - 合理设置队列大小限制 160 | - 注意事件处理的性能开销 161 | - 正确处理事件的优先级关系 162 | - 避免在事件处理中修改事件队列 163 | -------------------------------------------------------------------------------- /docs/systems/input_system/input_recorder.md: -------------------------------------------------------------------------------- 1 | # 输入记录器 (Input Recorder) 2 | 3 | ## 简介 4 | 输入记录器系统提供了一种记录和回放玩家输入序列的机制。这对于游戏回放、调试、录制演示和创建游戏教程特别有用。 5 | 6 | ## 功能特性 7 | - 实时输入记录 8 | - 输入序列回放 9 | - 时间戳精确记录 10 | - 支持保存和加载记录数据 11 | - 可配置的记录缓冲区大小 12 | 13 | ## 核心类 14 | ### InputRecorder 15 | 主要的记录器类,负责记录和管理输入序列。 16 | 17 | #### 主要属性 18 | ```gdscript 19 | class InputRecord: 20 | var timestamp: float # 记录时间戳 21 | var action: String # 动作名称 22 | var pressed: bool # 是否按下 23 | var strength: float # 输入强度 24 | ``` 25 | 26 | #### 主要方法 27 | - `start_recording()`: 开始记录输入 28 | - `stop_recording()`: 停止记录 29 | - `record_input(action, pressed, strength)`: 记录一个输入事件 30 | - `start_playback()`: 开始回放 31 | - `stop_playback()`: 停止回放 32 | - `save_recording(path)`: 保存记录到文件 33 | - `load_recording(path)`: 从文件加载记录 34 | 35 | ## 使用示例 36 | ### 基本记录和回放 37 | ```gdscript 38 | # 开始记录 39 | input_recorder.start_recording() 40 | 41 | # 记录输入 42 | func _on_input_event(action: String, pressed: bool, strength: float): 43 | input_recorder.record_input(action, pressed, strength) 44 | 45 | # 停止记录并保存 46 | input_recorder.stop_recording() 47 | input_recorder.save_recording("user://demo.rec") 48 | 49 | # 加载和回放 50 | input_recorder.load_recording("user://demo.rec") 51 | input_recorder.start_playback() 52 | ``` 53 | 54 | ### 游戏回放系统集成 55 | ```gdscript 56 | # 回放管理器示例 57 | class ReplayManager: 58 | var recorder: InputRecorder 59 | var game_state: Dictionary 60 | 61 | func start_replay(): 62 | game_state = save_game_state() 63 | recorder.start_playback() 64 | 65 | func on_replay_input(action: String, pressed: bool): 66 | process_input(action, pressed) 67 | ``` 68 | 69 | ## 高级特性 70 | ### 时间同步 71 | 系统使用精确的时间戳来确保回放的时序准确: 72 | ```gdscript 73 | func _process(delta: float): 74 | if _is_playing: 75 | _playback_time += delta 76 | _process_pending_inputs() 77 | ``` 78 | 79 | ### 压缩存储 80 | 记录数据使用高效的存储格式: 81 | ```gdscript 82 | func _serialize_record(record: InputRecord) -> Dictionary: 83 | return { 84 | "t": record.timestamp, 85 | "a": record.action, 86 | "p": record.pressed, 87 | "s": record.strength 88 | } 89 | ``` 90 | 91 | ### 回放控制 92 | 提供详细的回放控制选项: 93 | - 暂停/继续 94 | - 速度调节 95 | - 跳转到特定时间点 96 | - 循环回放 97 | 98 | ## 应用场景 99 | 1. 游戏回放系统 100 | - 比赛回放 101 | - 精彩时刻回放 102 | - 死亡回放 103 | 104 | 2. 调试工具 105 | - 问题复现 106 | - 输入序列测试 107 | - 性能分析 108 | 109 | 3. 教程系统 110 | - 操作示范 111 | - 引导教学 112 | - 动作展示 113 | 114 | ## 最佳实践 115 | 1. 记录管理 116 | ```gdscript 117 | # 定期清理旧记录 118 | func manage_recordings(): 119 | var max_recordings = 10 120 | clean_old_recordings(max_recordings) 121 | ``` 122 | 123 | 2. 性能优化 124 | ```gdscript 125 | # 使用缓冲区限制记录大小 126 | var _record_buffer: Array 127 | var _max_buffer_size = 1000 128 | 129 | func add_record(record: InputRecord): 130 | if _record_buffer.size() >= _max_buffer_size: 131 | _record_buffer.pop_front() 132 | _record_buffer.append(record) 133 | ``` 134 | 135 | 3. 错误处理 136 | ```gdscript 137 | # 处理回放过程中的异常 138 | func safe_playback(): 139 | try: 140 | start_playback() 141 | catch: 142 | handle_playback_error() 143 | ``` 144 | 145 | ## 注意事项 146 | - 确保记录数据的版本兼容性 147 | - 考虑记录文件的大小限制 148 | - 在状态切换时正确处理记录器状态 149 | - 注意回放时的游戏状态同步 150 | -------------------------------------------------------------------------------- /docs/systems/input_system/input_recorder_zh.md: -------------------------------------------------------------------------------- 1 | # 输入记录器 (Input Recorder) 2 | 3 | ## 简介 4 | 输入记录器系统提供了一种记录和回放玩家输入序列的机制。这对于游戏回放、调试、录制演示和创建游戏教程特别有用。 5 | 6 | ## 功能特性 7 | - 实时输入记录 8 | - 输入序列回放 9 | - 时间戳精确记录 10 | - 支持保存和加载记录数据 11 | - 可配置的记录缓冲区大小 12 | 13 | ## 核心类 14 | ### InputRecorder 15 | 主要的记录器类,负责记录和管理输入序列。 16 | 17 | #### 主要属性 18 | ```gdscript 19 | class InputRecord: 20 | var timestamp: float # 记录时间戳 21 | var action: String # 动作名称 22 | var pressed: bool # 是否按下 23 | var strength: float # 输入强度 24 | ``` 25 | 26 | #### 主要方法 27 | - `start_recording()`: 开始记录输入 28 | - `stop_recording()`: 停止记录 29 | - `record_input(action, pressed, strength)`: 记录一个输入事件 30 | - `start_playback()`: 开始回放 31 | - `stop_playback()`: 停止回放 32 | - `save_recording(path)`: 保存记录到文件 33 | - `load_recording(path)`: 从文件加载记录 34 | 35 | ## 使用示例 36 | ### 基本记录和回放 37 | ```gdscript 38 | # 开始记录 39 | input_recorder.start_recording() 40 | 41 | # 记录输入 42 | func _on_input_event(action: String, pressed: bool, strength: float): 43 | input_recorder.record_input(action, pressed, strength) 44 | 45 | # 停止记录并保存 46 | input_recorder.stop_recording() 47 | input_recorder.save_recording("user://demo.rec") 48 | 49 | # 加载和回放 50 | input_recorder.load_recording("user://demo.rec") 51 | input_recorder.start_playback() 52 | ``` 53 | 54 | ### 回放系统集成 55 | ```gdscript 56 | # 回放管理器示例 57 | class ReplayManager: 58 | var recorder: InputRecorder 59 | var game_state: Dictionary 60 | 61 | func start_replay(): 62 | game_state = save_game_state() 63 | recorder.start_playback() 64 | 65 | func on_replay_input(action: String, pressed: bool): 66 | process_input(action, pressed) 67 | ``` 68 | 69 | ## 高级特性 70 | ### 时间同步 71 | 系统使用精确的时间戳来确保回放的时序准确: 72 | ```gdscript 73 | func _process(delta: float): 74 | if _is_playing: 75 | _playback_time += delta 76 | _process_pending_inputs() 77 | ``` 78 | 79 | ### 数据压缩 80 | 记录数据使用高效的存储格式: 81 | ```gdscript 82 | func _serialize_record(record: InputRecord) -> Dictionary: 83 | return { 84 | "t": record.timestamp, 85 | "a": record.action, 86 | "p": record.pressed, 87 | "s": record.strength 88 | } 89 | ``` 90 | 91 | ### 回放控制 92 | 提供详细的回放控制选项: 93 | - 暂停/继续 94 | - 速度调节 95 | - 跳转到特定时间点 96 | - 循环回放 97 | 98 | ## 应用场景 99 | 1. 游戏回放系统 100 | - 比赛回放 101 | - 精彩时刻回放 102 | - 死亡回放 103 | 104 | 2. 调试工具 105 | - 问题复现 106 | - 输入序列测试 107 | - 性能分析 108 | 109 | 3. 教程系统 110 | - 操作示范 111 | - 引导教学 112 | - 动作展示 113 | 114 | ## 最佳实践 115 | ### 记录管理 116 | ```gdscript 117 | # 定期清理旧记录 118 | func manage_recordings(): 119 | var max_recordings = 10 120 | clean_old_recordings(max_recordings) 121 | ``` 122 | 123 | ### 性能优化 124 | ```gdscript 125 | # 使用缓冲区限制记录大小 126 | var _record_buffer: Array 127 | var _max_buffer_size = 1000 128 | 129 | func add_record(record: InputRecord): 130 | if _record_buffer.size() >= _max_buffer_size: 131 | _record_buffer.pop_front() 132 | _record_buffer.append(record) 133 | ``` 134 | 135 | ### 错误处理 136 | ```gdscript 137 | # 处理回放过程中的异常 138 | func safe_playback(): 139 | try: 140 | start_playback() 141 | catch: 142 | handle_playback_error() 143 | ``` 144 | 145 | ## 注意事项 146 | - 确保记录数据的版本兼容性 147 | - 考虑记录文件的大小限制 148 | - 在状态切换时正确处理记录器状态 149 | - 注意回放时的游戏状态同步 150 | - 处理回放过程中可能出现的异常情况 151 | - 定期清理和管理记录文件 152 | -------------------------------------------------------------------------------- /docs/systems/input_system/input_state.md: -------------------------------------------------------------------------------- 1 | # 输入状态系统 (Input State System) 2 | 3 | ## 简介 4 | 输入状态系统提供了一个强大的输入状态管理机制,用于跟踪和管理所有输入动作的状态。它不仅记录基本的按下/释放状态,还包括时间戳和输入强度等高级特性。 5 | 6 | ## 功能特性 7 | - 完整的输入状态跟踪 8 | - 精确的时间戳记录 9 | - 输入强度支持 10 | - 状态持久化和重置功能 11 | 12 | ## 核心类 13 | ### InputState 14 | 主要的状态管理类,负责跟踪所有注册动作的状态。 15 | 16 | #### ActionState 数据结构 17 | ```gdscript 18 | class ActionState: 19 | var pressed: bool # 当前是否按下 20 | var just_pressed: bool # 是否刚刚按下 21 | var just_released: bool # 是否刚刚释放 22 | var strength: float # 输入强度 23 | var press_time: float # 当前按下时间 24 | var last_press_time: float # 上次按下时间 25 | var last_release_time: float # 上次释放时间 26 | ``` 27 | 28 | #### 主要方法 29 | - `update_action(action_name, is_pressed, strength)`: 更新动作状态 30 | - `is_pressed(action_name)`: 检查动作是否按下 31 | - `is_just_pressed(action_name)`: 检查动作是否刚按下 32 | - `is_just_released(action_name)`: 检查动作是否刚释放 33 | - `get_strength(action_name)`: 获取动作强度 34 | - `get_press_time(action_name)`: 获取按下时间 35 | - `reset_action(action_name)`: 重置动作状态 36 | 37 | ## 使用示例 38 | ```gdscript 39 | # 更新动作状态 40 | input_state.update_action("jump", true, 1.0) 41 | 42 | # 检查动作状态 43 | if input_state.is_just_pressed("jump"): 44 | character.jump() 45 | 46 | # 获取按下持续时间 47 | var hold_time = input_state.get_press_time("crouch") 48 | ``` 49 | 50 | ## 高级特性 51 | ### 时间戳跟踪 52 | 系统自动记录以下时间戳: 53 | - 当前按下时间 54 | - 上次按下时间 55 | - 上次释放时间 56 | 57 | 这些时间戳可用于实现: 58 | - 连击检测 59 | - 长按判定 60 | - 输入缓冲 61 | 62 | ### 输入强度 63 | 支持模拟输入的强度值,范围从0到1: 64 | - 对于键盘输入,通常是0或1 65 | - 对于游戏手柄扳机键,可以是0到1之间的任意值 66 | - 对于触摸输入,可以基于压力感应 67 | 68 | ## 注意事项 69 | - 状态更新应该在每帧进行 70 | - 使用 just_pressed 和 just_released 进行精确的输入检测 71 | - 合理使用时间戳来实现高级输入功能 72 | -------------------------------------------------------------------------------- /docs/systems/input_system/input_state_zh.md: -------------------------------------------------------------------------------- 1 | # 输入状态系统 (Input State System) 2 | 3 | ## 简介 4 | 输入状态系统提供了一个强大的输入状态管理机制,用于跟踪和管理所有输入动作的状态。它不仅记录基本的按下/释放状态,还包括时间戳和输入强度等高级特性。 5 | 6 | ## 功能特性 7 | - 完整的输入状态跟踪 8 | - 精确的时间戳记录 9 | - 输入强度支持 10 | - 状态持久化和重置功能 11 | 12 | ## 核心类 13 | ### InputState 14 | 主要的状态管理类,负责跟踪所有注册动作的状态。 15 | 16 | #### ActionState 数据结构 17 | ```gdscript 18 | class ActionState: 19 | var pressed: bool # 当前是否按下 20 | var just_pressed: bool # 是否刚刚按下 21 | var just_released: bool # 是否刚刚释放 22 | var strength: float # 输入强度 23 | var press_time: float # 当前按下时间 24 | var last_press_time: float # 上次按下时间 25 | var last_release_time: float # 上次释放时间 26 | ``` 27 | 28 | #### 主要方法 29 | - `update_action(action_name, is_pressed, strength)`: 更新动作状态 30 | - `is_pressed(action_name)`: 检查动作是否按下 31 | - `is_just_pressed(action_name)`: 检查动作是否刚按下 32 | - `is_just_released(action_name)`: 检查动作是否刚释放 33 | - `get_strength(action_name)`: 获取动作强度 34 | - `get_press_time(action_name)`: 获取按下时间 35 | - `reset_action(action_name)`: 重置动作状态 36 | 37 | ## 使用示例 38 | ```gdscript 39 | # 更新动作状态 40 | input_state.update_action("jump", true, 1.0) 41 | 42 | # 检查动作状态 43 | if input_state.is_just_pressed("jump"): 44 | character.jump() 45 | 46 | # 获取按下持续时间 47 | var hold_time = input_state.get_press_time("crouch") 48 | ``` 49 | 50 | ## 高级特性 51 | ### 时间戳跟踪 52 | 系统自动记录以下时间戳: 53 | - 当前按下时间 54 | - 上次按下时间 55 | - 上次释放时间 56 | 57 | 这些时间戳可用于实现: 58 | - 连击检测 59 | - 长按判定 60 | - 输入缓冲 61 | 62 | ### 输入强度 63 | 支持模拟输入的强度值,范围从0到1: 64 | - 对于键盘输入,通常是0或1 65 | - 对于游戏手柄扳机键,可以是0到1之间的任意值 66 | - 对于触摸输入,可以基于压力感应 67 | 68 | ## 高级用法 69 | ### 连击检测示例 70 | ```gdscript 71 | func check_double_tap(action_name: String) -> bool: 72 | var state = input_state.get_action_state(action_name) 73 | if not state.just_pressed: 74 | return false 75 | 76 | var time_since_last = state.press_time - state.last_press_time 77 | return time_since_last < 0.3 # 300ms内的两次按下视为连击 78 | ``` 79 | 80 | ### 长按检测示例 81 | ```gdscript 82 | func check_long_press(action_name: String) -> bool: 83 | var state = input_state.get_action_state(action_name) 84 | if not state.pressed: 85 | return false 86 | 87 | var hold_duration = Time.get_ticks_msec() / 1000.0 - state.press_time 88 | return hold_duration >= 1.0 # 按住1秒以上视为长按 89 | ``` 90 | 91 | ## 注意事项 92 | - 状态更新应该在每帧进行 93 | - 使用 just_pressed 和 just_released 进行精确的输入检测 94 | - 合理使用时间戳来实现高级输入功能 95 | - 注意及时重置不再使用的动作状态 96 | 97 | ## 性能优化 98 | - 只跟踪必要的动作状态 99 | - 定期清理未使用的状态 100 | - 避免在每帧重复获取状态信息 101 | -------------------------------------------------------------------------------- /docs/systems/input_system/virtual_axis.md: -------------------------------------------------------------------------------- 1 | # 虚拟轴系统 (Virtual Axis System) 2 | 3 | ## 简介 4 | 虚拟轴系统提供了一种灵活的方式来处理基于轴的输入,如角色移动、摄像机控制等。它支持组合多个输入动作形成一个虚拟轴,并提供死区和灵敏度调节。 5 | 6 | ## 功能特性 7 | - 支持2D轴输入(X和Y轴) 8 | - 可配置的死区和灵敏度 9 | - 实时轴值更新 10 | - 轴值变化信号通知 11 | 12 | ## 核心类 13 | ### InputVirtualAxis 14 | 主要的虚拟轴处理类,负责管理和更新轴状态。 15 | 16 | #### 主要属性 17 | - `_sensitivity`: 轴灵敏度 18 | - `_deadzone`: 轴死区 19 | 20 | #### 主要方法 21 | - `register_axis(axis_name, positive_x, negative_x, positive_y, negative_y)`: 注册一个新的虚拟轴 22 | - `update_axis(axis_name)`: 更新指定轴的状态 23 | - `get_axis_value(axis_name)`: 获取轴的当前值 24 | - `set_sensitivity(value)`: 设置轴灵敏度 25 | - `set_deadzone(value)`: 设置轴死区 26 | 27 | ## 使用示例 28 | ```gdscript 29 | # 注册一个用于角色移动的虚拟轴 30 | virtual_axis.register_axis( 31 | "movement", 32 | "ui_right", # X轴正向 33 | "ui_left", # X轴负向 34 | "ui_up", # Y轴正向 35 | "ui_down" # Y轴负向 36 | ) 37 | 38 | # 设置轴参数 39 | virtual_axis.set_sensitivity(1.0) 40 | virtual_axis.set_deadzone(0.2) 41 | 42 | # 获取轴值 43 | var movement = virtual_axis.get_axis_value("movement") 44 | ``` 45 | 46 | ## 信号 47 | - `axis_changed(axis_name: String, value: Vector2)`: 当轴值发生变化时触发 48 | 49 | ## 注意事项 50 | - 确保在使用轴值之前已经注册了相应的轴 51 | - 合理设置死区可以过滤掉不必要的小幅度输入 52 | - 灵敏度值影响输入响应的速度 53 | -------------------------------------------------------------------------------- /docs/systems/input_system/virtual_axis_zh.md: -------------------------------------------------------------------------------- 1 | # 虚拟轴系统 (Virtual Axis System) 2 | 3 | ## 简介 4 | 虚拟轴系统提供了一种灵活的方式来处理基于轴的输入,如角色移动、摄像机控制等。它支持组合多个输入动作形成一个虚拟轴,并提供死区和灵敏度调节。 5 | 6 | ## 功能特性 7 | - 支持2D轴输入(X和Y轴) 8 | - 可配置的死区和灵敏度 9 | - 实时轴值更新 10 | - 轴值变化信号通知 11 | 12 | ## 核心类 13 | ### InputVirtualAxis 14 | 主要的虚拟轴处理类,负责管理和更新轴状态。 15 | 16 | #### 主要属性 17 | - `_sensitivity`: 轴灵敏度 18 | - `_deadzone`: 轴死区 19 | 20 | #### 主要方法 21 | - `register_axis(axis_name, positive_x, negative_x, positive_y, negative_y)`: 注册一个新的虚拟轴 22 | - `update_axis(axis_name)`: 更新指定轴的状态 23 | - `get_axis_value(axis_name)`: 获取轴的当前值 24 | - `set_sensitivity(value)`: 设置轴灵敏度 25 | - `set_deadzone(value)`: 设置轴死区 26 | 27 | ## 使用示例 28 | ```gdscript 29 | # 注册一个用于角色移动的虚拟轴 30 | virtual_axis.register_axis( 31 | "movement", 32 | "ui_right", # X轴正向 33 | "ui_left", # X轴负向 34 | "ui_up", # Y轴正向 35 | "ui_down" # Y轴负向 36 | ) 37 | 38 | # 设置轴参数 39 | virtual_axis.set_sensitivity(1.0) 40 | virtual_axis.set_deadzone(0.2) 41 | 42 | # 获取轴值 43 | var movement = virtual_axis.get_axis_value("movement") 44 | ``` 45 | 46 | ## 信号 47 | - `axis_changed(axis_name: String, value: Vector2)`: 当轴值发生变化时触发 48 | 49 | ## 注意事项 50 | - 确保在使用轴值之前已经注册了相应的轴 51 | - 合理设置死区可以过滤掉不必要的小幅度输入 52 | - 灵敏度值影响输入响应的速度 53 | 54 | ## 高级用法 55 | ### 轴值处理 56 | ```gdscript 57 | # 处理带有死区的轴值 58 | func process_axis_value(value: float) -> float: 59 | if abs(value) < _deadzone: 60 | return 0.0 61 | 62 | # 应用死区后的值映射 63 | var sign = sign(value) 64 | return sign * (abs(value) - _deadzone) / (1.0 - _deadzone) 65 | ``` 66 | 67 | ### 自定义轴配置 68 | ```gdscript 69 | # 配置特定游戏类型的轴设置 70 | func setup_platformer_controls(): 71 | # 平台游戏通常只需要水平移动 72 | virtual_axis.register_axis( 73 | "movement", 74 | "move_right", 75 | "move_left" 76 | ) 77 | 78 | # 设置较小的死区以获得精确控制 79 | virtual_axis.set_deadzone(0.1) 80 | ``` 81 | 82 | ## 性能考虑 83 | - 只在必要时更新轴状态 84 | - 避免每帧重复注册轴 85 | - 及时注销不再使用的轴 86 | -------------------------------------------------------------------------------- /docs/systems/logger_system_zh.md: -------------------------------------------------------------------------------- 1 | # 日志系统 2 | 3 | 日志系统通过结构化日志提供了一种灵活而强大的方式来跟踪和调试游戏行为。 4 | 5 | ## 特性 6 | 7 | - 📝 **日志级别**:不同严重程度的消息 8 | - 📊 **多个通道**:输出到控制台、文件和自定义通道 9 | - 🔍 **过滤**:按级别和类别过滤日志 10 | - 📱 **项目设置**:通过 Godot 的项目设置进行配置 11 | - 🔄 **日志轮转**:自动日志文件管理 12 | - 🎯 **上下文跟踪**:为日志消息添加上下文 13 | 14 | ## 核心组件 15 | 16 | ### Logger(日志记录器) 17 | 18 | 中央日志设施: 19 | 20 | - 日志级别管理 21 | - 通道配置 22 | - 消息格式化 23 | 24 | ```gdscript 25 | # 通过项目设置配置 26 | core_system/logger/log_level = "info" 27 | core_system/logger/file_logging = true 28 | core_system/logger/log_directory = "user://logs" 29 | core_system/logger/max_log_files = 5 30 | 31 | # 使用示例 32 | func _ready() -> void: 33 | var logger = CoreSystem.logger 34 | 35 | # 记录日志 36 | logger.debug("正在初始化游戏...") 37 | logger.info("游戏已启动") 38 | logger.warning("内存不足警告") 39 | logger.error("加载资源失败") 40 | ``` 41 | 42 | ## 使用示例 43 | 44 | ### 基本日志记录 45 | 46 | ```gdscript 47 | # 不同日志级别 48 | func example_logging() -> void: 49 | var logger = CoreSystem.logger 50 | 51 | logger.debug("调试消息") # 用于调试的详细信息 52 | logger.info("信息消息") # 一般信息 53 | logger.warning("警告消息") # 不会停止执行的警告 54 | logger.error("错误消息") # 影响功能的错误 55 | logger.fatal("致命消息") # 导致停止执行的严重错误 56 | ``` 57 | 58 | ### 上下文日志记录 59 | 60 | ```gdscript 61 | # 为日志添加上下文 62 | func player_action() -> void: 63 | var logger = CoreSystem.logger 64 | 65 | logger.with_context({ 66 | "player_id": "player_1", 67 | "position": Vector2(100, 100), 68 | "health": 100 69 | }).info("玩家执行动作") 70 | 71 | # 带类别的日志 72 | logger.category("combat").info("玩家攻击敌人") 73 | ``` 74 | 75 | ### 日志配置 76 | 77 | ```gdscript 78 | # 配置日志记录器 79 | func setup_logger() -> void: 80 | var logger = CoreSystem.logger 81 | 82 | # 设置日志级别 83 | logger.set_level(Logger.LEVEL.DEBUG) 84 | 85 | # 添加自定义通道 86 | logger.add_channel("analytics", func(message): 87 | send_to_analytics_service(message) 88 | ) 89 | 90 | # 配置文件日志 91 | logger.configure_file_logging("user://logs", 5) 92 | ``` 93 | 94 | ## 最佳实践 95 | 96 | 1. **日志组织** 97 | 98 | - 使用适当的日志级别 99 | - 添加相关上下文 100 | - 使用类别进行过滤 101 | 102 | 2. **性能** 103 | 104 | - 在生产环境中避免过多的调试日志 105 | - 对复杂的日志消息使用延迟求值 106 | - 配置适当的日志轮转 107 | 108 | 3. **调试信息** 109 | - 为错误包含堆栈跟踪 110 | - 记录状态变化 111 | - 为消息添加时间戳 112 | 113 | ## API 参考 114 | 115 | ### Logger(日志记录器)(中文名称:日志记录器 Logger) 116 | 117 | - `debug(message: String) -> void`: 记录调试消息 118 | - `info(message: String) -> void`: 记录信息消息 119 | - `warning(message: String) -> void`: 记录警告消息 120 | - `error(message: String) -> void`: 记录错误消息 121 | - `fatal(message: String) -> void`: 记录致命消息 122 | - `with_context(context: Dictionary) -> Logger`: 为下一条日志添加上下文 123 | - `category(name: String) -> Logger`: 为下一条日志设置类别 124 | - `set_level(level: int) -> void`: 设置最小日志级别 125 | - `add_channel(name: String, callback: Callable) -> void`: 添加自定义日志通道 126 | - `remove_channel(name: String) -> void`: 移除日志通道 127 | - `configure_file_logging(directory: String, max_files: int) -> void`: 配置文件日志 128 | - `flush() -> void`: 刷新日志缓冲区 129 | 130 | ### 日志级别 131 | 132 | ```gdscript 133 | enum LEVEL { 134 | DEBUG = 0, # 调试 135 | INFO = 1, # 信息 136 | WARNING = 2, # 警告 137 | ERROR = 3, # 错误 138 | FATAL = 4 # 致命 139 | } 140 | ``` 141 | -------------------------------------------------------------------------------- /docs/systems/resource_system_zh.md: -------------------------------------------------------------------------------- 1 | # 资源系统 2 | 3 | 资源系统提供了一种高效的方式来管理和加载游戏资源,具有异步加载和资源缓存等功能。 4 | 5 | ## 特性 6 | 7 | - 🔄 **异步加载**:非阻塞资源加载 8 | - 📦 **资源缓存**:智能缓存以提高性能 9 | - 🎯 **资源引用**:管理资源依赖关系 10 | - 🔍 **资源验证**:验证资源完整性 11 | - 📱 **项目设置**:通过 Godot 的项目设置进行配置 12 | - 🗑️ **自动清理**:智能资源卸载 13 | 14 | ## 核心组件 15 | 16 | ### ResourceManager(资源管理器) 17 | 18 | 所有资源操作的中央管理器: 19 | 20 | - 资源加载和缓存 21 | - 引用计数 22 | - 内存管理 23 | 24 | ```gdscript 25 | # 通过项目设置配置 26 | core_system/resource_system/cache_size = 100 27 | core_system/resource_system/preload_resources = true 28 | core_system/resource_system/cleanup_interval = 300 29 | 30 | # 使用示例 31 | func _ready() -> void: 32 | var resource_manager = CoreSystem.resource_manager 33 | 34 | # 加载资源 35 | var texture = resource_manager.load("res://assets/textures/player.png") 36 | 37 | # 异步加载 38 | resource_manager.load_async("res://assets/models/enemy.tscn", 39 | func(resource): setup_enemy(resource) 40 | ) 41 | ``` 42 | 43 | ## 使用示例 44 | 45 | ### 基本资源加载 46 | 47 | ```gdscript 48 | # 同步加载 49 | func load_player_resources() -> void: 50 | var resource_manager = CoreSystem.resource_manager 51 | 52 | var texture = resource_manager.load("res://assets/textures/player.png") 53 | var animation = resource_manager.load("res://assets/animations/player.tres") 54 | var sound = resource_manager.load("res://assets/sounds/footstep.wav") 55 | 56 | setup_player(texture, animation, sound) 57 | ``` 58 | 59 | ### 异步加载 60 | 61 | ```gdscript 62 | # 带回调的异步加载 63 | func load_level_async() -> void: 64 | var resource_manager = CoreSystem.resource_manager 65 | 66 | # 加载多个资源 67 | resource_manager.load_multiple_async([ 68 | "res://assets/levels/level1.tscn", 69 | "res://assets/textures/background.png", 70 | "res://assets/music/level1_theme.ogg" 71 | ], func(resources): setup_level(resources)) 72 | 73 | # 加载单个资源 74 | resource_manager.load_async("res://assets/models/enemy.tscn", 75 | func(resource): spawn_enemy(resource) 76 | ) 77 | ``` 78 | 79 | ### 资源管理 80 | 81 | ```gdscript 82 | # 资源引用管理 83 | func manage_resources() -> void: 84 | var resource_manager = CoreSystem.resource_manager 85 | 86 | # 添加引用 87 | resource_manager.add_reference("res://assets/textures/player.png") 88 | 89 | # 移除引用 90 | resource_manager.remove_reference("res://assets/textures/player.png") 91 | 92 | # 清理未使用的资源 93 | resource_manager.cleanup_unused() 94 | ``` 95 | 96 | ## 最佳实践 97 | 98 | 1. **资源组织** 99 | 100 | - 使用清晰的文件夹结构 101 | - 遵循一致的命名约定 102 | - 对相关资源进行分组 103 | 104 | 2. **性能** 105 | 106 | - 预加载常用资源 107 | - 对大型资源使用异步加载 108 | - 实现适当的引用计数 109 | 110 | 3. **内存管理** 111 | - 清理未使用的资源 112 | - 监控内存使用 113 | - 对频繁实例化的对象使用资源池 114 | 115 | ## API 参考 116 | 117 | ### 资源管理器 ResourceManager 118 | 119 | - `load(path: String) -> Resource`: 同步加载资源 120 | - `load_async(path: String, callback: Callable) -> void`: 异步加载资源 121 | - `load_multiple_async(paths: Array, callback: Callable) -> void`: 加载多个资源 122 | - `add_reference(path: String) -> void`: 添加资源引用 123 | - `remove_reference(path: String) -> void`: 移除资源引用 124 | - `cleanup_unused() -> void`: 清理未引用的资源 125 | - `preload_resources(paths: Array) -> void`: 预加载资源 126 | - `unload_resource(path: String) -> void`: 卸载特定资源 127 | - `get_resource(path: String) -> Resource`: 获取缓存的资源 128 | - `has_resource(path: String) -> bool`: 检查资源是否已缓存 129 | - `clear_cache() -> void`: 清理资源缓存 130 | -------------------------------------------------------------------------------- /docs/systems/scene_system_zh.md: -------------------------------------------------------------------------------- 1 | # 场景系统 2 | 3 | 场景系统提供了一个完整的场景管理解决方案,包括场景转场效果、场景状态管理和异步加载等功能。 4 | 5 | ## 功能特性 6 | 7 | - 🔄 **转场效果**: 内置和自定义场景转场效果 8 | - 📚 **场景栈**: 保存和恢复场景状态 9 | - ⚡ **异步加载**: 非阻塞的场景加载操作 10 | - 🔍 **场景预加载**: 预加载场景以加快切换速度 11 | - 🎯 **子场景管理**: 添加和管理子场景 12 | 13 | ## 核心组件 14 | 15 | ### 场景管理器 (SceneManager) 16 | 17 | 所有场景操作的中央管理器: 18 | 19 | - 场景转场管理 20 | - 场景状态处理 21 | - 场景栈操作 22 | 23 | ```gdscript 24 | # 基本场景切换 25 | scene_manager.change_scene_async( 26 | "res://scenes/game.tscn", # 场景路径 27 | {"level": 1}, # 场景数据 28 | true, # 保存到栈 29 | SceneManager.TransitionEffect.FADE # 转场效果 30 | ) 31 | 32 | # 返回上一个场景 33 | scene_manager.pop_scene_async( 34 | SceneManager.TransitionEffect.FADE 35 | ) 36 | ``` 37 | 38 | ### 转场基类 (BaseTransition) 39 | 40 | 创建自定义转场效果的基类: 41 | 42 | ```gdscript 43 | extends BaseTransition 44 | class_name CustomTransition 45 | 46 | func _do_start(duration: float) -> void: 47 | # 实现开始转场效果 48 | pass 49 | 50 | func _do_end(duration: float) -> void: 51 | # 实现结束转场效果 52 | pass 53 | ``` 54 | 55 | ## API 参考 56 | 57 | ### 场景管理器 58 | 59 | #### 属性 60 | 61 | ```gdscript 62 | enum TransitionEffect { 63 | NONE, # 无转场效果 64 | FADE, # 淡入淡出 65 | SLIDE, # 滑动 66 | DISSOLVE, # 溶解 67 | CUSTOM # 自定义 68 | } 69 | ``` 70 | 71 | #### 方法 72 | 73 | ##### 场景管理 74 | 75 | ```gdscript 76 | # 异步切换场景 77 | func change_scene_async( 78 | scene_path: String, # 场景路径 79 | scene_data: Dictionary = {}, # 场景数据 80 | push_to_stack: bool = false, # 保存当前场景 81 | effect: TransitionEffect = NONE, # 效果类型 82 | duration: float = 0.5, # 持续时间 83 | callback: Callable = Callable(), # 回调函数 84 | custom_transition: BaseTransition = null # 自定义效果 85 | ) -> void 86 | 87 | # 返回上一个场景 88 | func pop_scene_async( 89 | effect: TransitionEffect = NONE, 90 | duration: float = 0.5, 91 | callback: Callable = Callable(), 92 | custom_transition: BaseTransition = null 93 | ) -> void 94 | 95 | # 添加子场景 96 | func add_sub_scene( 97 | parent_node: Node, 98 | scene_path: String, 99 | scene_data: Dictionary = {} 100 | ) -> Node 101 | 102 | # 获取当前场景 103 | func get_current_scene() -> Node 104 | ``` 105 | 106 | ##### 场景预加载 107 | 108 | ```gdscript 109 | # 预加载场景 110 | func preload_scene(scene_path: String) -> void 111 | 112 | # 清除预加载的场景 113 | func clear_preloaded_scenes() -> void 114 | ``` 115 | 116 | ##### 转场效果 117 | 118 | ```gdscript 119 | # 注册自定义转场效果 120 | func register_transition( 121 | effect: TransitionEffect, 122 | transition: BaseTransition 123 | ) -> void 124 | ``` 125 | 126 | #### 信号 127 | 128 | ```gdscript 129 | signal scene_loading_started(scene_path: String) # 场景开始加载 130 | signal scene_changed(old_scene: Node, new_scene: Node) # 场景已切换 131 | signal scene_loading_finished() # 场景加载完成 132 | signal scene_preloaded(scene_path: String) # 场景预加载完成 133 | ``` 134 | 135 | ### 转场基类 (BaseTransition) 136 | 137 | #### 方法 138 | 139 | ```gdscript 140 | # 初始化转场效果 141 | func init(transition_rect: ColorRect) -> void 142 | 143 | # 开始转场 144 | func start(duration: float) -> void 145 | 146 | # 结束转场 147 | func end(duration: float) -> void 148 | ``` 149 | 150 | #### 保护方法 151 | 152 | ```gdscript 153 | # 重写以实现自定义开始效果 154 | func _do_start(duration: float) -> void 155 | 156 | # 重写以实现自定义结束效果 157 | func _do_end(duration: float) -> void 158 | ``` 159 | 160 | ## 最佳实践 161 | 162 | 1. 始终使用 `change_scene_async()` 进行场景切换 163 | 2. 为需要保持状态的场景实现状态管理 164 | 3. 预加载常用场景 165 | 4. 使用适当的转场效果 166 | 5. 优雅地处理场景加载错误 167 | 168 | ## 示例 169 | 170 | 查看 `examples/scene_demo` 目录获取完整示例: 171 | 172 | - 基本场景切换 173 | - 自定义转场效果 174 | - 场景状态管理 175 | - 场景预加载 176 | -------------------------------------------------------------------------------- /docs/systems/tag_system_zh.md: -------------------------------------------------------------------------------- 1 | # 标签系统 2 | 3 | 标签系统是一个轻量级的、灵活的标签管理系统,用于为游戏对象添加和管理标签。它支持层级标签结构,可以用于实现各种游戏功能,如状态标记、分类系统等。 4 | 5 | ## 特性 6 | 7 | - 🏷️ **层级标签**: 支持通过点号分隔的多层级标签(如 "character.player.state.idle") 8 | - 🔍 **灵活查询**: 支持精确和模糊匹配标签 9 | - 📦 **标签容器**: 为游戏对象提供独立的标签管理 10 | - 🎯 **事件通知**: 标签变化时发送信号 11 | - 💾 **持久化**: 支持标签的序列化和反序列化 12 | 13 | ## 快速开始 14 | 15 | ### 1. 访问标签管理器 16 | 17 | ```gdscript 18 | var tag_manager = CoreSystem.tag_manager 19 | ``` 20 | 21 | ### 2. 创建标签容器 22 | 23 | ```gdscript 24 | # 在场景中添加标签容器节点 25 | var tag_container = GameplayTagContainer.new() 26 | add_child(tag_container) 27 | 28 | # 或者通过场景树引用已有的标签容器 29 | @onready var tag_container = $TagContainer 30 | ``` 31 | 32 | ### 3. 基本使用 33 | 34 | ```gdscript 35 | # 添加标签 36 | tag_container.add_tag("character.player") 37 | tag_container.add_tag("state.idle") 38 | 39 | # 检查标签 40 | if tag_container.has_tag("character.player"): 41 | print("This is a player!") 42 | 43 | # 移除标签 44 | tag_container.remove_tag("state.idle") 45 | 46 | # 获取所有标签 47 | var all_tags = tag_container.get_tags() 48 | print("Current tags:", all_tags) 49 | ``` 50 | 51 | ### 4. 高级功能 52 | 53 | ```gdscript 54 | # 检查多个标签 55 | var required_tags = ["character.player", "state.idle"] 56 | if tag_container.has_all_tags(required_tags): 57 | print("Player is idle!") 58 | 59 | # 模糊匹配 60 | if tag_container.has_tag("character", false): 61 | print("This is any character!") 62 | 63 | # 监听标签变化 64 | tag_container.tag_added.connect(_on_tag_added) 65 | tag_container.tag_removed.connect(_on_tag_removed) 66 | ``` 67 | 68 | ## 示例 69 | 70 | 查看 [tag_demo](../examples/tag_demo/) 目录获取完整的示例项目。 71 | 72 | ### 玩家状态示例 73 | 74 | ```gdscript 75 | # 给玩家添加初始标签 76 | player_tags.add_tag("character.player") 77 | player_tags.add_tag("state.idle") 78 | 79 | # 切换玩家状态 80 | func _on_player_move(): 81 | player_tags.remove_tag("state.idle") 82 | player_tags.add_tag("state.moving") 83 | 84 | # 添加buff 85 | func _on_buff_acquired(buff_name: String): 86 | player_tags.add_tag("buff." + buff_name) 87 | ``` 88 | 89 | ## API 参考 90 | 91 | ### GameplayTagManager 92 | 93 | 全局标签管理器,负责标签的注册和管理。 94 | 95 | - `register_tag(tag_name: String) -> void`: 注册新标签 96 | - `get_tag(tag_name: String) -> GameplayTag`: 获取标签对象 97 | - `has_tag(tag_name: String) -> bool`: 检查标签是否已注册 98 | 99 | ### GameplayTagContainer 100 | 101 | 标签容器节点,用于管理单个对象的标签集合。 102 | 103 | - `add_tag(tag) -> void`: 添加标签 104 | - `remove_tag(tag) -> void`: 移除标签 105 | - `has_tag(tag, exact: bool = true) -> bool`: 检查是否有指定标签 106 | - `has_all_tags(required_tags: Array, exact: bool = true) -> bool`: 检查是否有所有指定标签 107 | - `has_any_tags(required_tags: Array, exact: bool = true) -> bool`: 检查是否有任意指定标签 108 | - `get_tags() -> Array`: 获取所有标签名称 109 | - `get_all_tags() -> Array[GameplayTag]`: 获取所有标签对象(包括子标签) 110 | 111 | ### GameplayTag 112 | 113 | 标签对象,表示单个标签。 114 | 115 | - `name: StringName`: 标签名称 116 | - `parent: GameplayTag`: 父标签 117 | - `children: Array[GameplayTag]`: 子标签列表 118 | - `matches(other: GameplayTag, exact: bool) -> bool`: 检查是否匹配另一个标签 119 | 120 | ## 最佳实践 121 | 122 | 1. **使用层级结构** 123 | - 使用点号分隔不同层级 124 | - 例如:`character.player.state.idle` 125 | 126 | 2. **标签命名规范** 127 | - 使用小写字母 128 | - 使用点号分隔层级 129 | - 避免特殊字符 130 | - 使用描述性名称 131 | 132 | 3. **性能考虑** 133 | - 避免过度使用标签 134 | - 及时移除不需要的标签 135 | - 使用精确匹配而不是模糊匹配 136 | 137 | 4. **组织标签** 138 | - 按功能分类(如 state、buff、character) 139 | - 保持层级结构清晰 140 | - 记录标签的用途和含义 141 | 142 | ## 常见问题 143 | 144 | 1. **标签不存在** 145 | - 确保在使用前已注册标签 146 | - 检查标签名称拼写是否正确 147 | - 查看日志输出的错误信息 148 | 149 | 2. **标签匹配失败** 150 | - 检查是否使用了正确的匹配模式(精确/模糊) 151 | - 确认标签的层级结构是否正确 152 | - 检查标签名称的大小写 153 | 154 | 3. **性能问题** 155 | - 减少不必要的标签 156 | - 使用精确匹配代替模糊匹配 157 | - 避免频繁添加/移除标签 158 | -------------------------------------------------------------------------------- /docs/utils/random_picker_zh.md: -------------------------------------------------------------------------------- 1 | # 随机选择池(RandomPicker) 2 | 3 | ## 功能特性 4 | 5 | - **高效算法**: 6 | - 使用别名算法(Alias Method)实现`O(1)`时间复杂度的基于权重的随机选择 7 | - 提供手动/自动重建概率表的参数控制,优化高频操作场景性能 8 | - **动态操作**: 9 | - 支持添加、删除、更新物品及权重,支持批量操作 10 | - 物品变动后自动或手动重建概率表(通过方法参数选择) 11 | - **灵活配置**: 12 | - 支持多种输入格式(数组、字典),自动转换为统一结构(参考示例代码) 13 | - 可选择是否检查重复项、是否自动重建表 14 | - **安全机制**: 15 | - 自动过滤无效权重(非正数) 16 | - 提供池状态监控(空池检测、剩余数量查询) 17 | - 支持重复项合并与清理 18 | 19 | --- 20 | 21 | ## 使用示例 22 | 23 | ### 基础用法 24 | ```gdscript 25 | # 初始化池(关闭自动重建和重复检查以优化性能) 26 | var pool = CoreSystem.RandomPicker.new([ 27 | ["sword", 10], 28 | {"data": "gem", "weight": 5} 29 | ], check_repeat=false) 30 | 31 | # 添加单个物品(手动控制重建) 32 | pool.add_item("shield", 8.0, rebuild=false, check_repeat=false) 33 | 34 | # 批量添加并手动重建 35 | var new_items = [ 36 | ["potion", 15], 37 | {"data": "key", "weight": 1} 38 | ] 39 | pool.add_items(new_items, rebuild=false) 40 | pool.rebuild_alias_table() # 显式重建 41 | 42 | # 获取随机物品(不移除) 43 | var loot = pool.get_random_item() 44 | 45 | # 批量移除并自动重建 46 | pool.remove_items(["sword", "gem"], rebuild=true) 47 | ``` 48 | 49 | ### 动态操作与信号 50 | ```gdscript 51 | # 连接信号 52 | pool.item_picked.connect(func(item): 53 | print("选中物品:", item) 54 | ) 55 | pool.pool_emptied.connect(func(): 56 | print("★ 池已清空!") 57 | ) 58 | 59 | # 动态更新权重并触发重建 60 | pool.update_item_weight("potion", 20.0) 61 | pool.add_item("dragon_sword", 5.0) 62 | 63 | # 清空池(触发信号) 64 | while not pool.is_empty(): 65 | pool.get_random_item(true) # 移除选中项 66 | ``` 67 | 68 | --- 69 | ## 最佳实践 70 | 71 | 1. **数据有效性**: 72 | - 权重必须为正数,否则添加/更新操作会被拒绝 73 | - 总权重为`0`时,无法构建别名表,`get_random_item`返回`null` 74 | 75 | 2. **性能优化**: 76 | - 高频操作(如批量添加)建议关闭`rebuild`和`check_repeat`,最后手动调用`rebuild_alias_table()`。 77 | - 压力测试表明,10万级物品池的单次随机访问平均耗时低于`0.001ms`,但初始化时间约`580ms`(轻薄本简单测试),因此大数据量需要审慎清理重复项或重建概率表和别名表 78 | - 如果可以确保数据无重复,可以在随机池初始化时将`check_repeat`参数设为`false`以优化性能(10万数据下大概快`100ms`) 79 | 80 | --- 81 | 82 | ## API 参考 83 | 84 | ### 核心方法 85 | | 方法 | 参数 | 返回值 | 说明 | 86 | |------|------|--------|------| 87 | | **`add_item`**
`(item_data, item_weight, rebuild:=true, check_repeat:=true)` | `item_data`: 任意数据
`item_weight`: 正浮点数
`rebuild`: 是否重建表
`check_repeat`: 是否检查重复 | `bool` | 添加单个物品,返回成功状态 | 88 | | **`add_items`**
`(items, rebuild:=true, check_repeat:=true)` | `items`: 数组(混合格式)
`rebuild`: 是否重建表
`check_repeat`: 是否检查重复 | `int` | 返回成功添加数量 | 89 | | **`remove_item`**
`(item_data, rebuild:=true)` | `item_data`: 要删除的数据
`rebuild`: 是否重建表 | `bool` | 返回是否删除成功 | 90 | | **`remove_items`**
`(item_datas, rebuild:=true)` | `item_datas`: 要删除的数据数组
`rebuild`: 是否重建表 | `int` | 返回成功删除数量 | 91 | | **`update_item_weight`**
`(item_data, new_weight, rebuild:=true)` | `item_data`: 目标数据
`new_weight`: 新权重
`rebuild`: 是否重建表 | `bool` | 返回是否更新成功 | 92 | | **`update_items_weights`**
`(updates, rebuild:=true)` | `updates`: 更新项数组(混合格式)
`rebuild`: 是否重建表 | `int` | 返回成功更新数量 | 93 | | **`get_random_item`**
`(should_remove:=false)` | `should_remove`: 是否移除选中项 | `Variant` | 返回随机数据(空池返回`null`) | 94 | 95 | ### 辅助方法 96 | | 方法 | 说明 | 97 | |------|------| 98 | | `get_item_weight(item_data: Variant) -> float` | 查询指定物品的权重(未找到返回`-1`) | 99 | | `get_items_weights(item_datas: Array) -> Dictionary` | 返回存在的物品权重字典(`{数据: 权重}`) | 100 | | `remove_duplicates()` | 清理重复项(保留第一项,返回移除数量) | 101 | | `get_remaining_count() -> int` | 获取池中物品总数 | 102 | | `is_empty() -> bool` | 检查池是否为空 | 103 | | `rebuild_alias_table()` | 手动重建别名表和概率表 | 104 | | `clear()` | 清空所有物品 | 105 | | `has_item(item_data: Variant) -> bool` | 判断是否存在物品 | 106 | 107 | ### 信号 108 | | 信号 | 说明 | 109 | |------|------| 110 | | `item_picked(item_data: Variant)` | 当`get_random_item`选中物品时触发 | 111 | | `pool_emptied` | 当池中物品被清空时触发 | 112 | 113 | --- 114 | -------------------------------------------------------------------------------- /examples/audio_demo/assets/music/bgm.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiGameAcademy/godot_core_system/0c689a1b78164e981c7a361b650f26e37134e99b/examples/audio_demo/assets/music/bgm.ogg -------------------------------------------------------------------------------- /examples/audio_demo/assets/sfx/click.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiGameAcademy/godot_core_system/0c689a1b78164e981c7a361b650f26e37134e99b/examples/audio_demo/assets/sfx/click.ogg -------------------------------------------------------------------------------- /examples/audio_demo/assets/voice/congratulations.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiGameAcademy/godot_core_system/0c689a1b78164e981c7a361b650f26e37134e99b/examples/audio_demo/assets/voice/congratulations.ogg -------------------------------------------------------------------------------- /examples/audio_demo/audio_demo.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | const AudioManager = CoreSystem.AudioManager 4 | 5 | @onready var audio_manager : AudioManager = CoreSystem.audio_manager 6 | 7 | # 预加载的音频资源路径 8 | var AUDIO_PATHS := { 9 | "bgm": FileDirHandler.get_object_script_dir(self) + "/assets/music/bgm.ogg", 10 | "click": FileDirHandler.get_object_script_dir(self) + "/assets/sfx/click.ogg", 11 | "voice": FileDirHandler.get_object_script_dir(self) + "/assets/voice/congratulations.ogg" 12 | } 13 | 14 | func _ready(): 15 | # 预加载音频资源 16 | audio_manager.preload_audio(AUDIO_PATHS.bgm, AudioManager.AudioType.MUSIC) 17 | audio_manager.preload_audio(AUDIO_PATHS.click, AudioManager.AudioType.SOUND_EFFECT) 18 | audio_manager.preload_audio(AUDIO_PATHS.voice, AudioManager.AudioType.VOICE) 19 | 20 | # 延迟1秒后开始演示 21 | await get_tree().create_timer(1.0).timeout 22 | _start_demo() 23 | 24 | ## 开始演示 25 | func _start_demo(): 26 | print("\n=== 开始音频系统演示 ===") 27 | 28 | # 1. 播放背景音乐(带淡入效果) 29 | print("\n1. 播放背景音乐(2秒淡入):") 30 | audio_manager.play_music(AUDIO_PATHS.bgm, 2.0) 31 | 32 | await get_tree().create_timer(3.0).timeout 33 | 34 | # 2. 调整音乐音量 35 | print("\n2. 调整音乐音量至50%:") 36 | audio_manager.set_volume(AudioManager.AudioType.MUSIC, 0.5) 37 | 38 | await get_tree().create_timer(2.0).timeout 39 | 40 | # 3. 播放音效 41 | print("\n3. 播放音效:") 42 | for i in range(3): 43 | audio_manager.play_sound(AUDIO_PATHS.click, 1.0) 44 | await get_tree().create_timer(0.5).timeout 45 | 46 | # 4. 播放语音 47 | print("\n4. 播放语音:") 48 | audio_manager.play_voice(AUDIO_PATHS.voice, 1.0) 49 | 50 | await get_tree().create_timer(2.0).timeout 51 | 52 | # 5. 切换背景音乐(带淡出淡入效果) 53 | print("\n5. 切换背景音乐(1秒淡出淡入):") 54 | audio_manager.play_music(AUDIO_PATHS.bgm, 1.0) 55 | 56 | await get_tree().create_timer(2.0).timeout 57 | 58 | # 6. 停止所有音频 59 | print("\n6. 停止所有音频:") 60 | audio_manager.stop_all() 61 | 62 | print("\n=== 音频系统演示结束 ===") 63 | 64 | func _input(event): 65 | if event is InputEventKey and event.pressed: 66 | match event.keycode: 67 | # 按空格键重新开始演示 68 | KEY_SPACE: 69 | print("\n按下空格键,重新开始演示") 70 | audio_manager.stop_all() 71 | _start_demo() 72 | # 按ESC键停止所有音频 73 | KEY_ESCAPE: 74 | print("\n按下ESC键,停止所有音频") 75 | audio_manager.stop_all() 76 | -------------------------------------------------------------------------------- /examples/audio_demo/audio_demo.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bb5k3hm8rshbl 2 | -------------------------------------------------------------------------------- /examples/audio_demo/audio_demo.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://b0jtmsl52j5r6"] 2 | 3 | [ext_resource type="Script" uid="uid://bb5k3hm8rshbl" path="res://addons/godot_core_system/examples/audio_demo/audio_demo.gd" id="1_4s84c"] 4 | 5 | [node name="AudioDemo" type="Node2D"] 6 | script = ExtResource("1_4s84c") 7 | -------------------------------------------------------------------------------- /examples/config_demo/config_demo.gd.uid: -------------------------------------------------------------------------------- 1 | uid://b5rrdsdnckdyh 2 | -------------------------------------------------------------------------------- /examples/config_demo/config_demo.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://dtcbl62vfohhe"] 2 | 3 | [ext_resource type="Script" uid="uid://b5rrdsdnckdyh" path="res://addons/godot_core_system/examples/config_demo/config_demo.gd" id="1_x8g4f"] 4 | 5 | [node name="ConfigDemo" type="Node2D"] 6 | script = ExtResource("1_x8g4f") 7 | 8 | [node name="UI" type="CanvasLayer" parent="."] 9 | 10 | [node name="StatusLabel" type="Label" parent="UI"] 11 | anchors_preset = 5 12 | anchor_left = 0.5 13 | anchor_right = 0.5 14 | offset_left = -200.0 15 | offset_top = 20.0 16 | offset_right = 200.0 17 | offset_bottom = 46.0 18 | grow_horizontal = 2 19 | horizontal_alignment = 1 20 | 21 | [node name="ConfigTree" type="Tree" parent="UI"] 22 | anchors_preset = 8 23 | anchor_left = 0.5 24 | anchor_top = 0.5 25 | anchor_right = 0.5 26 | anchor_bottom = 0.5 27 | offset_left = -200.0 28 | offset_top = -150.0 29 | offset_right = 200.0 30 | offset_bottom = 150.0 31 | grow_horizontal = 2 32 | grow_vertical = 2 33 | 34 | [node name="Buttons" type="HBoxContainer" parent="UI"] 35 | anchors_preset = 7 36 | anchor_left = 0.5 37 | anchor_top = 1.0 38 | anchor_right = 0.5 39 | anchor_bottom = 1.0 40 | offset_left = -200.0 41 | offset_top = -100.0 42 | offset_right = 200.0 43 | offset_bottom = -60.0 44 | grow_horizontal = 2 45 | grow_vertical = 0 46 | alignment = 1 47 | 48 | [node name="AutoSaveLabel" type="Label" parent="UI/Buttons"] 49 | unique_name_in_owner = true 50 | layout_mode = 2 51 | text = "自动保存:是" 52 | 53 | [node name="ModifyButton" type="Button" parent="UI/Buttons"] 54 | layout_mode = 2 55 | text = "修改配置" 56 | 57 | [node name="SaveButton" type="Button" parent="UI/Buttons"] 58 | layout_mode = 2 59 | text = "保存配置" 60 | 61 | [node name="ResetButton" type="Button" parent="UI/Buttons"] 62 | layout_mode = 2 63 | text = "重置配置" 64 | 65 | [connection signal="pressed" from="UI/Buttons/ModifyButton" to="." method="_on_modify_button_pressed"] 66 | [connection signal="pressed" from="UI/Buttons/SaveButton" to="." method="_on_save_button_pressed"] 67 | [connection signal="pressed" from="UI/Buttons/ResetButton" to="." method="_on_reset_button_pressed"] 68 | -------------------------------------------------------------------------------- /examples/event_bus_demo/event_bus_dem.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://c78l34csftot7"] 2 | 3 | [ext_resource type="Script" uid="uid://ek0dop40lat7" path="res://addons/godot_core_system/examples/event_bus_demo/event_bus_demo.gd" id="1_tn4av"] 4 | 5 | [node name="EventBusDem" type="Node2D"] 6 | script = ExtResource("1_tn4av") 7 | -------------------------------------------------------------------------------- /examples/event_bus_demo/event_bus_demo.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | const EventBus = CoreSystem.EventBus 4 | 5 | var event_bus : EventBus = CoreSystem.event_bus 6 | 7 | func _ready(): 8 | # 启用调试模式和历史记录 9 | event_bus.debug_mode = true 10 | event_bus.enable_history = true 11 | 12 | # 1. 基本订阅(普通优先级) 13 | event_bus.subscribe("player_move", _on_player_move) 14 | 15 | # 2. 高优先级订阅 16 | event_bus.subscribe("player_move", _on_player_move_high_priority, 17 | event_bus.Priority.HIGH) 18 | 19 | # 3. 低优先级订阅 20 | event_bus.subscribe("player_move", _on_player_move_low_priority, 21 | event_bus.Priority.LOW) 22 | 23 | # 4. 一次性订阅 24 | event_bus.subscribe_once("player_attack", _on_player_attack_once) 25 | 26 | # 5. 带过滤器的订阅(只处理向右移动) 27 | event_bus.subscribe("player_move", _on_player_move_right, 28 | event_bus.Priority.NORMAL, 29 | false, 30 | func(payload): return payload[0] == "right" 31 | ) 32 | 33 | # 延迟1秒后开始演示 34 | await get_tree().create_timer(1.0).timeout 35 | _start_demo() 36 | 37 | ## 开始演示 38 | func _start_demo(): 39 | print("\n=== 开始EventBus演示 ===") 40 | 41 | # 1. 测试不同优先级的事件处理 42 | print("\n1. 测试事件优先级:") 43 | event_bus.push_event("player_move", ["left", 100]) 44 | 45 | # 2. 测试一次性订阅 46 | print("\n2. 测试一次性订阅:") 47 | event_bus.push_event("player_attack", ["sword", 50]) 48 | print("再次触发player_attack事件(不会有响应):") 49 | event_bus.push_event("player_attack", ["sword", 30]) 50 | 51 | # 3. 测试事件过滤器 52 | print("\n3. 测试事件过滤器:") 53 | print("向左移动(过滤器不会响应):") 54 | event_bus.push_event("player_move", ["left", 50]) 55 | print("向右移动(过滤器会响应):") 56 | event_bus.push_event("player_move", ["right", 50]) 57 | 58 | # 4. 测试延迟事件 59 | print("\n4. 测试延迟事件:") 60 | event_bus.push_event("player_move", ["up", 100], false) 61 | 62 | # 5. 测试历史记录 63 | await get_tree().create_timer(0.5).timeout 64 | print("\n5. 显示事件历史:") 65 | var history = event_bus.get_event_history() 66 | for event in history: 67 | print("事件:%s,参数:%s" % [event.event_name, event.payload]) 68 | 69 | print("\n=== EventBus演示结束 ===") 70 | 71 | ## 玩家移动事件回调 72 | func _on_player_move(direction, distance): 73 | print("普通优先级:玩家向%s移动了%d单位" % [direction, distance]) 74 | 75 | ## 高优先级玩家移动事件回调 76 | func _on_player_move_high_priority(direction, distance): 77 | print("高优先级:玩家向%s移动了%d单位" % [direction, distance]) 78 | 79 | ## 低优先级玩家移动事件回调 80 | func _on_player_move_low_priority(direction, distance): 81 | print("低优先级:玩家向%s移动了%d单位" % [direction, distance]) 82 | 83 | ## 一次性订阅事件回调 84 | func _on_player_attack_once(weapon, damage): 85 | print("一次性订阅:玩家使用%s造成了%d点伤害" % [weapon, damage]) 86 | 87 | 88 | ## 过滤器订阅事件回调 89 | func _on_player_move_right(direction, distance): 90 | print("过滤器订阅:玩家向右移动了%d单位" % [distance]) 91 | -------------------------------------------------------------------------------- /examples/event_bus_demo/event_bus_demo.gd.uid: -------------------------------------------------------------------------------- 1 | uid://ek0dop40lat7 2 | -------------------------------------------------------------------------------- /examples/input_demo/README.md: -------------------------------------------------------------------------------- 1 | # 输入系统演示 (Input System Demo) 2 | 3 | 本目录包含了输入系统各个子模块的演示示例。 4 | 5 | ## 目录结构 6 | ``` 7 | input_demo/ 8 | ├── basic/ # 基础功能演示 9 | │ ├── basic_demo.tscn # 基础演示场景 10 | │ └── basic_demo.gd # 基础演示脚本 11 | ├── virtual_axis/ # 虚拟轴系统演示 12 | │ ├── character.tscn # 角色场景 13 | │ └── movement_demo.gd # 移动演示脚本 14 | ├── input_state/ # 输入状态系统演示 15 | │ ├── combo_demo.tscn # 连击演示场景 16 | │ └── combo_demo.gd # 连击演示脚本 17 | ├── input_buffer/ # 输入缓冲系统演示 18 | │ ├── buffer_demo.tscn # 缓冲演示场景 19 | │ └── buffer_demo.gd # 缓冲演示脚本 20 | ├── input_recorder/ # 输入记录器演示 21 | │ ├── recorder_demo.tscn # 记录演示场景 22 | │ └── recorder_demo.gd # 记录演示脚本 23 | └── event_processor/ # 事件处理器演示 24 | ├── processor_demo.tscn # 处理器演示场景 25 | └── processor_demo.gd # 处理器演示脚本 26 | ``` 27 | 28 | ## 演示内容 29 | 1. 基础功能演示 30 | - 输入系统初始化 31 | - 基本输入检测 32 | - 配置管理 33 | 34 | 2. 虚拟轴系统演示 35 | - 角色移动控制 36 | - 死区和灵敏度调节 37 | - 轴值可视化 38 | 39 | 3. 输入状态系统演示 40 | - 连击检测 41 | - 长按判定 42 | - 状态可视化 43 | 44 | 4. 输入缓冲系统演示 45 | - 技能释放 46 | - 缓冲窗口配置 47 | - 缓冲可视化 48 | 49 | 5. 输入记录器演示 50 | - 输入序列记录 51 | - 回放功能 52 | - 存档和加载 53 | 54 | 6. 事件处理器演示 55 | - 事件过滤 56 | - 优先级处理 57 | - 事件队列管理 58 | -------------------------------------------------------------------------------- /examples/input_demo/basic/basic_demo.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | ## 输入系统基础演示 4 | ## 展示输入系统的基本功能,包括: 5 | ## 1. 输入系统初始化 6 | ## 2. 基本输入检测 7 | ## 3. 配置管理 8 | 9 | @onready var input_manager = CoreSystem.input_manager 10 | @onready var status_label = $UI/StatusLabel 11 | @onready var input_log = $UI/InputLog 12 | 13 | # 定义输入动作 14 | const INPUT_ACTIONS = { 15 | "move_right": "ui_right", 16 | "move_left": "ui_left", 17 | "move_up": "ui_up", 18 | "move_down": "ui_down", 19 | "jump": "ui_accept", 20 | "attack": "ui_select" 21 | } 22 | 23 | func _ready() -> void: 24 | # 初始化UI 25 | status_label.text = "输入系统就绪" 26 | input_log.text = "" 27 | 28 | # 连接信号 29 | input_manager.action_triggered.connect(_on_action_triggered) 30 | 31 | # 初始化输入配置 32 | _setup_input() 33 | 34 | func _setup_input() -> void: 35 | # 注册虚拟轴 36 | input_manager.virtual_axis.register_axis( 37 | "movement", 38 | INPUT_ACTIONS.move_right, 39 | INPUT_ACTIONS.move_left, 40 | INPUT_ACTIONS.move_down, 41 | INPUT_ACTIONS.move_up 42 | ) 43 | 44 | # 设置轴参数 45 | input_manager.virtual_axis.set_sensitivity(1.0) 46 | input_manager.virtual_axis.set_deadzone(0.2) 47 | 48 | func _process(_delta: float) -> void: 49 | # 更新状态显示 50 | var status_text = "输入状态:\n" 51 | 52 | # 检查移动轴 53 | var movement = input_manager.virtual_axis.get_axis_value("movement") 54 | status_text += "移动轴: " + str(movement) + "\n" 55 | 56 | # 检查跳跃状态 57 | if input_manager.input_state.is_pressed(INPUT_ACTIONS.jump): 58 | status_text += "跳跃按下中\n" 59 | if input_manager.input_state.is_just_pressed(INPUT_ACTIONS.jump): 60 | status_text += "刚刚跳跃\n" 61 | 62 | # 检查攻击状态 63 | if input_manager.input_state.is_pressed(INPUT_ACTIONS.attack): 64 | status_text += "攻击按下中\n" 65 | if input_manager.input_state.is_just_pressed(INPUT_ACTIONS.attack): 66 | status_text += "刚刚攻击\n" 67 | 68 | status_label.text = status_text 69 | 70 | func _on_action_triggered(action_name: String, event: InputEvent) -> void: 71 | # 记录输入事件 72 | var log_text = "动作触发: %s (%s)\n" % [action_name, event.as_text()] 73 | input_log.text = log_text + input_log.text 74 | 75 | # 限制日志长度 76 | if input_log.text.length() > 500: 77 | input_log.text = input_log.text.substr(0, 500) 78 | 79 | func _on_clear_log_pressed() -> void: 80 | input_log.text = "" 81 | 82 | func _on_reset_config_pressed() -> void: 83 | # 重置输入配置 84 | input_manager.virtual_axis.set_sensitivity(1.0) 85 | input_manager.virtual_axis.set_deadzone(0.2) 86 | status_label.text = "配置已重置" 87 | 88 | func _on_sensitivity_value_changed(value: float) -> void: 89 | input_manager.virtual_axis.set_sensitivity(value) 90 | status_label.text = "灵敏度已更新: " + str(value) 91 | 92 | func _on_deadzone_value_changed(value: float) -> void: 93 | input_manager.virtual_axis.set_deadzone(value) 94 | status_label.text = "死区已更新: " + str(value) 95 | -------------------------------------------------------------------------------- /examples/input_demo/basic/basic_demo.gd.uid: -------------------------------------------------------------------------------- 1 | uid://clc4t6p561yth 2 | -------------------------------------------------------------------------------- /examples/input_demo/event_processor/processor_demo.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dfdgghw16y273 2 | -------------------------------------------------------------------------------- /examples/input_demo/input_buffer/buffer_demo.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dlvrm55417gx8 2 | -------------------------------------------------------------------------------- /examples/input_demo/input_buffer/buffer_demo.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://c313sxjomemxr"] 2 | 3 | [ext_resource type="Script" uid="uid://dlvrm55417gx8" path="res://addons/godot_core_system/examples/input_demo/input_buffer/buffer_demo.gd" id="1_46qi6"] 4 | 5 | [node name="BufferDemo" type="CanvasLayer"] 6 | script = ExtResource("1_46qi6") 7 | 8 | [node name="UI" type="Control" parent="."] 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 | 16 | [node name="Title" type="Label" parent="UI"] 17 | layout_mode = 1 18 | anchors_preset = 5 19 | anchor_left = 0.5 20 | anchor_right = 0.5 21 | offset_left = -100.0 22 | offset_top = 20.0 23 | offset_right = 100.0 24 | offset_bottom = 46.0 25 | grow_horizontal = 2 26 | theme_override_font_sizes/font_size = 24 27 | text = "输入缓冲系统演示" 28 | horizontal_alignment = 1 29 | 30 | [node name="StatusLabel" type="Label" parent="UI"] 31 | layout_mode = 0 32 | offset_left = 20.0 33 | offset_top = 20.0 34 | offset_right = 220.0 35 | offset_bottom = 120.0 36 | text = "输入缓冲系统就绪" 37 | 38 | [node name="BufferDisplay" type="Label" parent="UI"] 39 | layout_mode = 1 40 | anchors_preset = 5 41 | anchor_left = 0.5 42 | anchor_right = 0.5 43 | offset_left = 120.0 44 | offset_top = 136.0 45 | offset_right = 320.0 46 | offset_bottom = 236.0 47 | grow_horizontal = 2 48 | text = "当前缓冲:" 49 | horizontal_alignment = 1 50 | 51 | [node name="InputLog" type="TextEdit" parent="UI"] 52 | layout_mode = 1 53 | anchors_preset = 8 54 | anchor_left = 0.5 55 | anchor_top = 0.5 56 | anchor_right = 0.5 57 | anchor_bottom = 0.5 58 | offset_left = -56.0 59 | offset_top = -106.0 60 | offset_right = 112.0 61 | offset_bottom = 142.0 62 | grow_horizontal = 2 63 | grow_vertical = 2 64 | text = "输入日志" 65 | editable = false 66 | 67 | [node name="Controls" type="VBoxContainer" parent="UI"] 68 | layout_mode = 1 69 | anchors_preset = 2 70 | anchor_top = 1.0 71 | anchor_bottom = 1.0 72 | offset_left = 16.0 73 | offset_top = -148.0 74 | offset_right = 216.0 75 | offset_bottom = -28.0 76 | grow_vertical = 0 77 | metadata/_edit_group_ = true 78 | 79 | [node name="BufferWindow" type="VBoxContainer" parent="UI/Controls"] 80 | layout_mode = 2 81 | 82 | [node name="Label" type="Label" parent="UI/Controls/BufferWindow"] 83 | layout_mode = 2 84 | text = "缓冲窗口 (秒)" 85 | 86 | [node name="Slider" type="HSlider" parent="UI/Controls/BufferWindow"] 87 | layout_mode = 2 88 | max_value = 0.5 89 | step = 0.01 90 | value = 0.15 91 | 92 | [node name="Value" type="Label" parent="UI/Controls/BufferWindow"] 93 | layout_mode = 2 94 | text = "0.15" 95 | horizontal_alignment = 1 96 | 97 | [node name="Buttons" type="HBoxContainer" parent="UI/Controls"] 98 | layout_mode = 2 99 | alignment = 1 100 | 101 | [node name="ClearLogButton" type="Button" parent="UI/Controls/Buttons"] 102 | layout_mode = 2 103 | text = "清除日志" 104 | 105 | [node name="ResetButton" type="Button" parent="UI/Controls/Buttons"] 106 | layout_mode = 2 107 | text = "重置参数" 108 | 109 | [node name="Instructions" type="Label" parent="UI"] 110 | layout_mode = 0 111 | offset_left = 424.0 112 | offset_top = 8.0 113 | offset_right = 568.0 114 | offset_bottom = 187.0 115 | theme_override_font_sizes/font_size = 10 116 | text = "操作说明: 117 | 空格键:技能1 (红色) 118 | 回车键:技能2 (蓝色) 119 | Tab键:技能3 (绿色) 120 | 121 | 技能有0.5秒冷却时间 122 | 在冷却结束时会检查缓冲" 123 | 124 | [connection signal="value_changed" from="UI/Controls/BufferWindow/Slider" to="." method="_on_buffer_window_value_changed"] 125 | [connection signal="pressed" from="UI/Controls/Buttons/ClearLogButton" to="." method="_on_clear_log_pressed"] 126 | [connection signal="pressed" from="UI/Controls/Buttons/ResetButton" to="." method="_on_reset_pressed"] 127 | -------------------------------------------------------------------------------- /examples/input_demo/input_recorder/recorder_demo.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dx2xud5fu64d2 2 | -------------------------------------------------------------------------------- /examples/input_demo/input_state/combo_demo.gd: -------------------------------------------------------------------------------- 1 | extends CanvasLayer 2 | 3 | ## 输入状态系统演示 4 | ## 展示输入状态系统的功能,包括: 5 | ## 1. 连击检测 6 | ## 2. 长按判定 7 | ## 3. 状态可视化 8 | 9 | @onready var input_manager = CoreSystem.input_manager 10 | @onready var status_label = $UI/StatusLabel 11 | @onready var combo_display = $UI/ComboDisplay 12 | @onready var hold_indicator = $UI/HoldIndicator 13 | @onready var input_log = $UI/InputLog 14 | 15 | # 输入配置 16 | const INPUT_ACTIONS = { 17 | "punch": "ui_accept", # 空格键 18 | "kick": "ui_select", # 回车键 19 | "special": "ui_focus_next" # Tab键 20 | } 21 | 22 | # 连击配置 23 | const COMBO_WINDOW = 0.5 # 连击窗口(秒) 24 | const HOLD_THRESHOLD = 0.5 # 长按阈值(秒) 25 | 26 | var combo_count := 0 27 | var last_hit_time := 0.0 28 | var current_hold_time := 0.0 29 | 30 | func _ready() -> void: 31 | # 初始化显示 32 | status_label.text = "输入状态系统就绪" 33 | combo_display.text = "连击数:0" 34 | input_log.text = "" 35 | 36 | # 初始化长按指示器 37 | hold_indicator.max_value = HOLD_THRESHOLD 38 | hold_indicator.value = 0 39 | 40 | func _process(delta: float) -> void: 41 | _update_combo_system() 42 | _update_hold_system(delta) 43 | _update_status_display() 44 | 45 | func _update_combo_system() -> void: 46 | var current_time = Time.get_ticks_msec() / 1000.0 47 | 48 | # 检查连击超时 49 | if current_time - last_hit_time > COMBO_WINDOW and combo_count > 0: 50 | _reset_combo() 51 | 52 | # 检测新的攻击输入 53 | if input_manager.input_state.is_just_pressed(INPUT_ACTIONS.punch): 54 | _handle_punch() 55 | elif input_manager.input_state.is_just_pressed(INPUT_ACTIONS.kick): 56 | _handle_kick() 57 | 58 | func _update_hold_system(delta: float) -> void: 59 | # 更新长按状态 60 | if input_manager.input_state.is_pressed(INPUT_ACTIONS.special): 61 | current_hold_time += delta 62 | hold_indicator.value = current_hold_time 63 | 64 | # 检查是否达到长按阈值 65 | if current_hold_time >= HOLD_THRESHOLD: 66 | _handle_special_move() 67 | else: 68 | current_hold_time = 0 69 | hold_indicator.value = 0 70 | 71 | func _handle_punch() -> void: 72 | var current_time = Time.get_ticks_msec() / 1000.0 73 | 74 | if current_time - last_hit_time <= COMBO_WINDOW: 75 | combo_count += 1 76 | else: 77 | combo_count = 1 78 | 79 | last_hit_time = current_time 80 | _log_input("拳击") 81 | _update_combo_display() 82 | 83 | func _handle_kick() -> void: 84 | var current_time = Time.get_ticks_msec() / 1000.0 85 | 86 | if current_time - last_hit_time <= COMBO_WINDOW: 87 | combo_count += 1 88 | else: 89 | combo_count = 1 90 | 91 | last_hit_time = current_time 92 | _log_input("踢腿") 93 | _update_combo_display() 94 | 95 | func _handle_special_move() -> void: 96 | _log_input("特殊技能!") 97 | # 重置长按状态 98 | current_hold_time = 0 99 | hold_indicator.value = 0 100 | 101 | func _reset_combo() -> void: 102 | combo_count = 0 103 | _update_combo_display() 104 | _log_input("连击重置") 105 | 106 | func _update_combo_display() -> void: 107 | combo_display.text = "连击数:%d" % combo_count 108 | 109 | func _update_status_display() -> void: 110 | var status_text = "输入状态:\n" 111 | 112 | # 显示按键状态 113 | for action_name in INPUT_ACTIONS.values(): 114 | if input_manager.input_state.is_pressed(action_name): 115 | status_text += "%s: 按下\n" % action_name 116 | elif input_manager.input_state.is_just_released(action_name): 117 | status_text += "%s: 刚释放\n" % action_name 118 | 119 | status_label.text = status_text 120 | 121 | func _log_input(action: String) -> void: 122 | var time = Time.get_ticks_msec() / 1000.0 123 | var log_text = "[%.2f] %s\n" % [time, action] 124 | input_log.text = log_text + input_log.text 125 | 126 | # 限制日志长度 127 | if input_log.text.length() > 500: 128 | input_log.text = input_log.text.substr(0, 500) 129 | 130 | func _on_clear_log_pressed() -> void: 131 | input_log.text = "" 132 | 133 | func _on_reset_combo_pressed() -> void: 134 | _reset_combo() 135 | -------------------------------------------------------------------------------- /examples/input_demo/input_state/combo_demo.gd.uid: -------------------------------------------------------------------------------- 1 | uid://df0qapeeoahmq 2 | -------------------------------------------------------------------------------- /examples/input_demo/input_state/combo_demo.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://cw1k6bjqeyk61"] 2 | 3 | [ext_resource type="Script" uid="uid://df0qapeeoahmq" path="res://addons/godot_core_system/examples/input_demo/input_state/combo_demo.gd" id="1_ttwky"] 4 | 5 | [node name="ComboDemo" type="CanvasLayer"] 6 | script = ExtResource("1_ttwky") 7 | 8 | [node name="UI" type="Control" parent="."] 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 | 16 | [node name="Title" type="Label" parent="UI"] 17 | layout_mode = 1 18 | anchors_preset = 5 19 | anchor_left = 0.5 20 | anchor_right = 0.5 21 | offset_left = -100.0 22 | offset_top = 20.0 23 | offset_right = 100.0 24 | offset_bottom = 46.0 25 | grow_horizontal = 2 26 | theme_override_font_sizes/font_size = 24 27 | text = "输入状态系统演示" 28 | horizontal_alignment = 1 29 | 30 | [node name="StatusLabel" type="Label" parent="UI"] 31 | layout_mode = 0 32 | offset_left = 20.0 33 | offset_top = 20.0 34 | offset_right = 220.0 35 | offset_bottom = 120.0 36 | text = "输入状态系统就绪" 37 | 38 | [node name="ComboDisplay" type="Label" parent="UI"] 39 | layout_mode = 1 40 | anchors_preset = 5 41 | anchor_left = 0.5 42 | anchor_right = 0.5 43 | offset_left = -100.0 44 | offset_top = 60.0 45 | offset_right = 100.0 46 | offset_bottom = 86.0 47 | grow_horizontal = 2 48 | theme_override_font_sizes/font_size = 20 49 | text = "连击数:0" 50 | horizontal_alignment = 1 51 | 52 | [node name="HoldIndicator" type="ProgressBar" parent="UI"] 53 | layout_mode = 1 54 | anchors_preset = 5 55 | anchor_left = 0.5 56 | anchor_right = 0.5 57 | offset_left = -272.0 58 | offset_top = 96.0 59 | offset_right = -72.0 60 | offset_bottom = 123.0 61 | grow_horizontal = 2 62 | 63 | [node name="HoldLabel" type="Label" parent="UI/HoldIndicator"] 64 | layout_mode = 1 65 | anchors_preset = 8 66 | anchor_left = 0.5 67 | anchor_top = 0.5 68 | anchor_right = 0.5 69 | anchor_bottom = 0.5 70 | offset_left = -108.0 71 | offset_top = -29.5 72 | offset_right = -12.0 73 | offset_bottom = -3.5 74 | grow_horizontal = 2 75 | grow_vertical = 2 76 | text = "长按进度" 77 | horizontal_alignment = 1 78 | 79 | [node name="InputLog" type="TextEdit" parent="UI"] 80 | layout_mode = 1 81 | anchors_preset = 8 82 | anchor_left = 0.5 83 | anchor_top = 0.5 84 | anchor_right = 0.5 85 | anchor_bottom = 0.5 86 | offset_left = -136.0 87 | offset_top = -66.0 88 | offset_right = 264.0 89 | offset_bottom = 134.0 90 | grow_horizontal = 2 91 | grow_vertical = 2 92 | text = "输入日志" 93 | editable = false 94 | 95 | [node name="Controls" type="HBoxContainer" parent="UI"] 96 | layout_mode = 1 97 | anchors_preset = 7 98 | anchor_left = 0.5 99 | anchor_top = 1.0 100 | anchor_right = 0.5 101 | anchor_bottom = 1.0 102 | offset_left = 72.0 103 | offset_top = -260.0 104 | offset_right = 272.0 105 | offset_bottom = -229.0 106 | grow_horizontal = 2 107 | grow_vertical = 0 108 | alignment = 1 109 | 110 | [node name="ClearLogButton" type="Button" parent="UI/Controls"] 111 | layout_mode = 2 112 | text = "清除日志" 113 | 114 | [node name="ResetComboButton" type="Button" parent="UI/Controls"] 115 | layout_mode = 2 116 | text = "重置连击" 117 | 118 | [node name="Instructions" type="Label" parent="UI"] 119 | layout_mode = 0 120 | offset_left = 20.0 121 | offset_top = 140.0 122 | offset_right = 220.0 123 | offset_bottom = 240.0 124 | text = "操作说明: 125 | 空格键:拳击 126 | 回车键:踢腿 127 | Tab键:长按蓄力 128 | 连击窗口:0.5秒 129 | 长按阈值:0.5秒" 130 | 131 | [connection signal="pressed" from="UI/Controls/ClearLogButton" to="." method="_on_clear_log_pressed"] 132 | [connection signal="pressed" from="UI/Controls/ResetComboButton" to="." method="_on_reset_combo_pressed"] 133 | -------------------------------------------------------------------------------- /examples/input_demo/settings_ui.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | # 信号 4 | signal remap_requested(action: String) 5 | signal sensitivity_changed(value: float) 6 | signal deadzone_changed(value: float) 7 | signal reset_requested 8 | 9 | # 节点引用 10 | @onready var jump_button = $VBoxContainer/InputSettings/JumpButton 11 | @onready var attack_button = $VBoxContainer/InputSettings/AttackButton 12 | @onready var sensitivity_slider = $VBoxContainer/InputSettings/SensitivitySlider 13 | @onready var deadzone_slider = $VBoxContainer/InputSettings/DeadzoneSlider 14 | @onready var reset_button = $VBoxContainer/ResetButton 15 | 16 | func _ready(): 17 | # 连接按钮信号 18 | jump_button.pressed.connect(func(): remap_requested.emit("player_jump")) 19 | attack_button.pressed.connect(func(): remap_requested.emit("player_attack")) 20 | 21 | # 连接滑块信号 22 | sensitivity_slider.value_changed.connect(func(value: float): sensitivity_changed.emit(value)) 23 | deadzone_slider.value_changed.connect(func(value: float): deadzone_changed.emit(value)) 24 | 25 | # 连接重置按钮信号 26 | reset_button.pressed.connect(func(): reset_requested.emit()) 27 | 28 | # 设置初始值 29 | sensitivity_slider.value = 1.0 30 | deadzone_slider.value = 0.2 31 | 32 | ## 更新按钮文本 33 | func update_button_text(action: String, event: InputEvent) -> void: 34 | var button_text = "" 35 | match event.get_class(): 36 | "InputEventKey": 37 | button_text = OS.get_keycode_string(event.keycode) 38 | "InputEventJoypadButton": 39 | button_text = "手柄按钮 " + str(event.button_index) 40 | "InputEventMouseButton": 41 | button_text = "鼠标按钮 " + str(event.button_index) 42 | 43 | match action: 44 | "player_jump": 45 | jump_button.text = "跳跃: " + button_text 46 | "player_attack": 47 | attack_button.text = "攻击: " + button_text 48 | 49 | ## 更新滑块值 50 | func update_sensitivity(value: float) -> void: 51 | sensitivity_slider.value = value 52 | 53 | func update_deadzone(value: float) -> void: 54 | deadzone_slider.value = value 55 | -------------------------------------------------------------------------------- /examples/input_demo/settings_ui.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dbjbjqhogs3e2 2 | -------------------------------------------------------------------------------- /examples/input_demo/virtual_axis/axis_visualizer.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | ## 虚拟轴可视化器 4 | ## 用于可视化显示虚拟轴的当前值和方向 5 | 6 | var current_axis := Vector2.ZERO 7 | var axis_color := Color.GREEN 8 | var deadzone_color := Color.RED 9 | var background_color := Color(0.2, 0.2, 0.2, 0.5) 10 | 11 | # 可视化参数 12 | const CIRCLE_RADIUS = 100.0 13 | const DEADZONE_RADIUS = 20.0 14 | const INDICATOR_SIZE = 10.0 15 | 16 | func _ready() -> void: 17 | # 确保控件能接收绘制调用 18 | custom_minimum_size = Vector2(CIRCLE_RADIUS * 2, CIRCLE_RADIUS * 2) 19 | queue_redraw() 20 | 21 | func update_axis(axis_value: Vector2) -> void: 22 | current_axis = axis_value 23 | queue_redraw() 24 | 25 | func _draw() -> void: 26 | var center = size / 2 27 | 28 | # 绘制背景圆 29 | draw_circle(center, CIRCLE_RADIUS, background_color) 30 | 31 | # 绘制死区圆 32 | draw_circle(center, DEADZONE_RADIUS, deadzone_color) 33 | 34 | # 绘制坐标轴 35 | draw_line(center + Vector2(-CIRCLE_RADIUS, 0), 36 | center + Vector2(CIRCLE_RADIUS, 0), 37 | Color.WHITE_SMOKE) 38 | draw_line(center + Vector2(0, -CIRCLE_RADIUS), 39 | center + Vector2(0, CIRCLE_RADIUS), 40 | Color.WHITE_SMOKE) 41 | 42 | # 绘制当前轴值指示器 43 | var indicator_pos = center + current_axis * CIRCLE_RADIUS 44 | draw_circle(indicator_pos, INDICATOR_SIZE, axis_color) 45 | 46 | # 绘制从中心到指示器的线 47 | if current_axis.length() > 0: 48 | draw_line(center, indicator_pos, axis_color) 49 | 50 | # 绘制轴值文本 51 | var text = "X: %.2f\nY: %.2f" % [current_axis.x, current_axis.y] 52 | draw_string(ThemeDB.fallback_font, 53 | center + Vector2(CIRCLE_RADIUS + 10, 0), 54 | text) 55 | 56 | func set_deadzone(value: float) -> void: 57 | # 更新死区可视化 58 | queue_redraw() 59 | 60 | func set_sensitivity(value: float) -> void: 61 | # 更新灵敏度可视化 62 | queue_redraw() 63 | -------------------------------------------------------------------------------- /examples/input_demo/virtual_axis/axis_visualizer.gd.uid: -------------------------------------------------------------------------------- 1 | uid://ea77c7332d4m 2 | -------------------------------------------------------------------------------- /examples/input_demo/virtual_axis/character.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene format=3 uid="uid://b8p4u5yqjbqx4"] 2 | 3 | [node name="Character" type="CharacterBody2D"] 4 | 5 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."] 6 | 7 | [node name="Sprite2D" type="Sprite2D" parent="."] 8 | 9 | [node name="AnimationPlayer" type="AnimationPlayer" parent="."] 10 | -------------------------------------------------------------------------------- /examples/input_demo/virtual_axis/movement_demo.gd: -------------------------------------------------------------------------------- 1 | extends CanvasLayer 2 | 3 | ## 虚拟轴系统演示 4 | ## 展示虚拟轴系统的功能,包括: 5 | ## 1. 角色移动控制 6 | ## 2. 死区和灵敏度调节 7 | ## 3. 轴值可视化 8 | 9 | @onready var input_manager = CoreSystem.input_manager 10 | @onready var character = $Character 11 | @onready var axis_visualizer = $UI/AxisVisualizer 12 | @onready var status_label = $UI/StatusLabel 13 | 14 | # 移动参数 15 | const MOVE_SPEED = 300.0 16 | const ACCELERATION = 1500.0 17 | const FRICTION = 2000.0 18 | 19 | # 虚拟轴配置 20 | const MOVEMENT_AXIS = "movement" 21 | const INPUT_ACTIONS = { 22 | "right": "ui_right", 23 | "left": "ui_left", 24 | "up": "ui_up", 25 | "down": "ui_down" 26 | } 27 | 28 | func _ready() -> void: 29 | # 初始化虚拟轴 30 | _setup_virtual_axis() 31 | 32 | # 设置初始状态显示 33 | status_label.text = "虚拟轴系统就绪" 34 | 35 | func _setup_virtual_axis() -> void: 36 | # 注册移动轴 37 | input_manager.virtual_axis.register_axis( 38 | MOVEMENT_AXIS, 39 | INPUT_ACTIONS.right, 40 | INPUT_ACTIONS.left, 41 | INPUT_ACTIONS.down, 42 | INPUT_ACTIONS.up 43 | ) 44 | 45 | # 设置默认参数 46 | input_manager.virtual_axis.set_sensitivity(1.0) 47 | input_manager.virtual_axis.set_deadzone(0.2) 48 | 49 | func _physics_process(delta: float) -> void: 50 | # 获取移动输入 51 | var movement = input_manager.virtual_axis.get_axis_value(MOVEMENT_AXIS) 52 | 53 | # 更新角色移动 54 | _update_character_movement(movement, delta) 55 | 56 | # 更新轴值可视化 57 | _update_axis_visualizer(movement) 58 | 59 | # 更新状态显示 60 | _update_status_display(movement) 61 | 62 | func _update_character_movement(movement: Vector2, delta: float) -> void: 63 | var velocity = character.velocity 64 | 65 | # 应用加速度 66 | if movement.length() > 0: 67 | velocity = velocity.move_toward(movement * MOVE_SPEED, ACCELERATION * delta) 68 | else: 69 | # 应用摩擦力 70 | velocity = velocity.move_toward(Vector2.ZERO, FRICTION * delta) 71 | 72 | character.velocity = velocity 73 | character.move_and_slide() 74 | 75 | func _update_axis_visualizer(movement: Vector2) -> void: 76 | # 更新可视化显示 77 | axis_visualizer.update_axis(movement) 78 | 79 | func _update_status_display(movement: Vector2) -> void: 80 | var status_text = "轴值: " + str(movement) + "\n" 81 | status_text += "速度: " + str(character.velocity.length()) + "\n" 82 | status_text += "灵敏度: " + str(input_manager.virtual_axis.get_sensitivity()) + "\n" 83 | status_text += "死区: " + str(input_manager.virtual_axis.get_deadzone()) 84 | status_label.text = status_text 85 | 86 | func _on_sensitivity_value_changed(value: float) -> void: 87 | input_manager.virtual_axis.set_sensitivity(value) 88 | 89 | func _on_deadzone_value_changed(value: float) -> void: 90 | input_manager.virtual_axis.set_deadzone(value) 91 | 92 | func _on_reset_pressed() -> void: 93 | input_manager.virtual_axis.set_sensitivity(1.0) 94 | input_manager.virtual_axis.set_deadzone(0.2) 95 | -------------------------------------------------------------------------------- /examples/input_demo/virtual_axis/movement_demo.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dtyovc5pap37o 2 | -------------------------------------------------------------------------------- /examples/input_demo/virtual_axis/movement_demo.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=3 uid="uid://c8p4u5yqjbqx4"] 2 | 3 | [ext_resource type="Script" uid="uid://dtyovc5pap37o" path="res://addons/godot_core_system/examples/input_demo/virtual_axis/movement_demo.gd" id="1_yvxu8"] 4 | [ext_resource type="PackedScene" uid="uid://b8p4u5yqjbqx4" path="res://addons/godot_core_system/examples/input_demo/virtual_axis/character.tscn" id="2_n3k4r"] 5 | [ext_resource type="Script" uid="uid://ea77c7332d4m" path="res://addons/godot_core_system/examples/input_demo/virtual_axis/axis_visualizer.gd" id="3_f8m2p"] 6 | 7 | [node name="MovementDemo" type="CanvasLayer"] 8 | script = ExtResource("1_yvxu8") 9 | 10 | [node name="Character" parent="." instance=ExtResource("2_n3k4r")] 11 | position = Vector2(512, 300) 12 | 13 | [node name="UI" type="Control" parent="."] 14 | layout_mode = 3 15 | anchors_preset = 15 16 | anchor_right = 1.0 17 | anchor_bottom = 1.0 18 | grow_horizontal = 2 19 | grow_vertical = 2 20 | 21 | [node name="Title" type="Label" parent="UI"] 22 | layout_mode = 1 23 | anchors_preset = 5 24 | anchor_left = 0.5 25 | anchor_right = 0.5 26 | offset_left = -100.0 27 | offset_top = 20.0 28 | offset_right = 100.0 29 | offset_bottom = 46.0 30 | grow_horizontal = 2 31 | theme_override_font_sizes/font_size = 24 32 | text = "虚拟轴系统演示" 33 | horizontal_alignment = 1 34 | 35 | [node name="StatusLabel" type="Label" parent="UI"] 36 | layout_mode = 0 37 | offset_left = 20.0 38 | offset_top = 20.0 39 | offset_right = 220.0 40 | offset_bottom = 120.0 41 | text = "虚拟轴系统就绪" 42 | 43 | [node name="AxisVisualizer" type="Control" parent="UI"] 44 | layout_mode = 1 45 | anchors_preset = 3 46 | anchor_left = 1.0 47 | anchor_top = 1.0 48 | anchor_right = 1.0 49 | anchor_bottom = 1.0 50 | offset_left = -352.0 51 | offset_top = -236.0 52 | offset_right = -132.0 53 | offset_bottom = -16.0 54 | grow_horizontal = 0 55 | grow_vertical = 0 56 | script = ExtResource("3_f8m2p") 57 | 58 | [node name="Controls" type="VBoxContainer" parent="UI"] 59 | layout_mode = 1 60 | anchors_preset = 2 61 | anchor_top = 1.0 62 | anchor_bottom = 1.0 63 | offset_left = 20.0 64 | offset_top = -160.0 65 | offset_right = 220.0 66 | offset_bottom = -20.0 67 | grow_vertical = 0 68 | 69 | [node name="SensitivityControl" type="VBoxContainer" parent="UI/Controls"] 70 | layout_mode = 2 71 | 72 | [node name="Label" type="Label" parent="UI/Controls/SensitivityControl"] 73 | layout_mode = 2 74 | text = "灵敏度" 75 | 76 | [node name="SensitivitySlider" type="HSlider" parent="UI/Controls/SensitivityControl"] 77 | layout_mode = 2 78 | max_value = 2.0 79 | step = 0.1 80 | value = 1.0 81 | 82 | [node name="DeadzoneControl" type="VBoxContainer" parent="UI/Controls"] 83 | layout_mode = 2 84 | 85 | [node name="Label" type="Label" parent="UI/Controls/DeadzoneControl"] 86 | layout_mode = 2 87 | text = "死区" 88 | 89 | [node name="DeadzoneSlider" type="HSlider" parent="UI/Controls/DeadzoneControl"] 90 | layout_mode = 2 91 | max_value = 0.5 92 | step = 0.05 93 | value = 0.2 94 | 95 | [node name="ResetButton" type="Button" parent="UI/Controls"] 96 | layout_mode = 2 97 | text = "重置参数" 98 | 99 | [node name="Instructions" type="Label" parent="UI"] 100 | layout_mode = 0 101 | offset_left = 424.0 102 | offset_right = 624.0 103 | offset_bottom = 101.0 104 | text = "操作说明: 105 | 方向键:移动角色 106 | 右下角:轴值可视化 107 | 左下角:参数调节" 108 | 109 | [connection signal="value_changed" from="UI/Controls/SensitivityControl/SensitivitySlider" to="." method="_on_sensitivity_value_changed"] 110 | [connection signal="value_changed" from="UI/Controls/DeadzoneControl/DeadzoneSlider" to="." method="_on_deadzone_value_changed"] 111 | [connection signal="pressed" from="UI/Controls/ResetButton" to="." method="_on_reset_pressed"] 112 | -------------------------------------------------------------------------------- /examples/random_picker_demo/test_random_picker.gd.uid: -------------------------------------------------------------------------------- 1 | uid://t16fw2403mb6 2 | -------------------------------------------------------------------------------- /examples/random_picker_demo/test_random_picker_demo.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://ctgi1xyqby3x1"] 2 | 3 | [ext_resource type="Script" uid="uid://t16fw2403mb6" path="res://addons/godot_core_system/examples/random_picker_demo/test_random_picker.gd" id="1_qdguv"] 4 | 5 | [node name="TestRandomPickerDemo" type="Node2D"] 6 | script = ExtResource("1_qdguv") 7 | -------------------------------------------------------------------------------- /examples/save_demo/player.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | ## 玩家基本属性 4 | @export var player_name: String = "Player" 5 | @export var player_level: int = 1 6 | @export var player_exp: int = 0 7 | @export var player_position: Vector2 = Vector2.ZERO 8 | 9 | func _ready() -> void: 10 | # 注册需要序列化的属性 11 | CoreSystem.save_manager.register_saveable_node(self) 12 | 13 | func save() -> Dictionary: 14 | return { 15 | "player_name": player_name, 16 | "player_level": player_level, 17 | "player_exp": player_exp, 18 | "player_position": player_position, 19 | } 20 | 21 | func load_data(data: Dictionary) -> void: 22 | player_name = data.get("player_name", player_name) 23 | player_level = data.get("player_level", player_level) 24 | player_exp = data.get("player_exp", player_exp) 25 | player_position = data.get("player_position", player_position) 26 | -------------------------------------------------------------------------------- /examples/save_demo/player.gd.uid: -------------------------------------------------------------------------------- 1 | uid://tp7fgwa0ysst 2 | -------------------------------------------------------------------------------- /examples/save_demo/readme.md: -------------------------------------------------------------------------------- 1 | # save_demo.gd 需要展示的功能: 2 | 3 | # 1. 基础存档功能 4 | - 手动保存/加载 5 | - 自动保存(定时器触发) 6 | - 存档列表显示 7 | - 存档删除 8 | 9 | # 2. 不同存档格式 10 | - Resource 格式(展示 Godot 原生资源保存) 11 | - JSON 格式(展示可读性) 12 | - Binary 格式(展示加密和压缩) 13 | 14 | # 3. 存档数据展示 15 | - 元数据显示(时间戳、版本等) 16 | - 游戏状态显示(玩家位置、分数等) 17 | - 存档文件大小对比 18 | 19 | # 4. 错误处理 20 | - 存档失败处理 21 | - 加载失败处理 22 | - 文件不存在处理 -------------------------------------------------------------------------------- /examples/save_demo/save_demo.gd.uid: -------------------------------------------------------------------------------- 1 | uid://d2i3gjmx5dc4d 2 | -------------------------------------------------------------------------------- /examples/scene_demo/scene_demo.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | const SceneManager = CoreSystem.SceneManager 4 | 5 | @onready var scene_manager : SceneManager = CoreSystem.scene_manager 6 | @onready var status_label = $UI/StatusLabel 7 | @onready var buttons = $UI/Buttons 8 | @onready var preload_status = $UI/PreloadStatus 9 | 10 | # 场景路径 11 | var SCENE_PATHS = [ 12 | FileDirHandler.get_object_script_dir(self) + "/scenes/scene1.tscn", 13 | FileDirHandler.get_object_script_dir(self) + "/scenes/scene2.tscn", 14 | FileDirHandler.get_object_script_dir(self) + "/scenes/scene3.tscn", 15 | ] 16 | 17 | # 预加载状态 18 | var _preloaded_scenes : Dictionary = {} 19 | 20 | func _ready() -> void: 21 | # 连接信号 22 | scene_manager.scene_loading_started.connect(_on_scene_loading_started) 23 | scene_manager.scene_changed.connect(_on_scene_changed) 24 | scene_manager.scene_loading_finished.connect(_on_scene_loading_finished) 25 | scene_manager.scene_preloaded.connect(_on_scene_preloaded) 26 | 27 | # 预加载所有场景 28 | for scene_path in SCENE_PATHS: 29 | _preloaded_scenes[scene_path] = false 30 | scene_manager.preload_scene(scene_path) 31 | 32 | # 设置状态标签 33 | status_label.text = "正在预加载场景..." 34 | buttons.visible = false 35 | 36 | ## 切换到场景1(无转场效果) 37 | func _on_scene1_pressed() -> void: 38 | scene_manager.change_scene_async( 39 | SCENE_PATHS[0], 40 | { 41 | "message": "这是场景1", 42 | "source_scene": name 43 | }, 44 | true, 45 | SceneManager.TransitionEffect.NONE 46 | ) 47 | 48 | ## 切换到场景2(淡入淡出) 49 | func _on_scene2_pressed() -> void: 50 | scene_manager.change_scene_async( 51 | SCENE_PATHS[1], 52 | { 53 | "message": "这是场景2", 54 | "source_scene": name 55 | }, 56 | true, 57 | SceneManager.TransitionEffect.FADE 58 | ) 59 | 60 | ## 切换到场景3(滑动) 61 | func _on_scene3_pressed() -> void: 62 | scene_manager.change_scene_async( 63 | SCENE_PATHS[2], 64 | { 65 | "message": "这是场景3", 66 | "source_scene": name 67 | }, 68 | true, 69 | SceneManager.TransitionEffect.SLIDE 70 | ) 71 | 72 | ## 场景加载开始回调 73 | func _on_scene_loading_started(scene_path: String): 74 | status_label.text = "开始加载场景:" + scene_path 75 | buttons.visible = false 76 | 77 | ## 场景切换回调 78 | func _on_scene_changed(_old_scene: Node, _new_scene: Node): 79 | status_label.text = "场景切换完成" 80 | 81 | ## 场景加载完成回调 82 | func _on_scene_loading_finished(): 83 | if scene_manager.get_current_scene() == self: 84 | status_label.text = "加载完成" 85 | buttons.visible = true 86 | 87 | ## 场景预加载完成回调 88 | func _on_scene_preloaded(scene_path: String): 89 | _preloaded_scenes[scene_path] = true 90 | var loaded_count = _preloaded_scenes.values().count(true) 91 | 92 | # 所有场景预加载完成 93 | if loaded_count == SCENE_PATHS.size(): 94 | status_label.text = "选择一个转场效果和目标场景" 95 | buttons.visible = true 96 | -------------------------------------------------------------------------------- /examples/scene_demo/scene_demo.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c6apg2ynhay8p 2 | -------------------------------------------------------------------------------- /examples/scene_demo/scene_demo.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://dwqi32p3ht1by"] 2 | 3 | [ext_resource type="Script" uid="uid://c6apg2ynhay8p" path="res://addons/godot_core_system/examples/scene_demo/scene_demo.gd" id="1_v8g4f"] 4 | 5 | [node name="SceneDemo" type="Node2D"] 6 | script = ExtResource("1_v8g4f") 7 | 8 | [node name="UI" type="CanvasLayer" parent="."] 9 | 10 | [node name="StatusLabel" type="Label" parent="UI"] 11 | anchors_preset = 5 12 | anchor_left = 0.5 13 | anchor_right = 0.5 14 | offset_left = -200.0 15 | offset_top = 53.0 16 | offset_right = 200.0 17 | offset_bottom = 79.0 18 | grow_horizontal = 2 19 | horizontal_alignment = 1 20 | 21 | [node name="PreloadStatus" type="Label" parent="UI"] 22 | anchors_preset = 5 23 | anchor_left = 0.5 24 | anchor_right = 0.5 25 | offset_left = -200.0 26 | offset_top = 20.0 27 | offset_right = 200.0 28 | offset_bottom = 46.0 29 | grow_horizontal = 2 30 | horizontal_alignment = 1 31 | 32 | [node name="Buttons" type="VBoxContainer" parent="UI"] 33 | anchors_preset = 8 34 | anchor_left = 0.5 35 | anchor_top = 0.5 36 | anchor_right = 0.5 37 | anchor_bottom = 0.5 38 | offset_left = -100.0 39 | offset_top = -100.0 40 | offset_right = 100.0 41 | offset_bottom = 100.0 42 | grow_horizontal = 2 43 | grow_vertical = 2 44 | 45 | [node name="Scene1Button" type="Button" parent="UI/Buttons"] 46 | layout_mode = 2 47 | text = "场景1 (无转场)" 48 | 49 | [node name="Scene2Button" type="Button" parent="UI/Buttons"] 50 | layout_mode = 2 51 | text = "场景2 (淡入淡出)" 52 | 53 | [node name="Scene3Button" type="Button" parent="UI/Buttons"] 54 | layout_mode = 2 55 | text = "场景3 (滑动)" 56 | 57 | [connection signal="pressed" from="UI/Buttons/Scene1Button" to="." method="_on_scene1_pressed"] 58 | [connection signal="pressed" from="UI/Buttons/Scene2Button" to="." method="_on_scene2_pressed"] 59 | [connection signal="pressed" from="UI/Buttons/Scene3Button" to="." method="_on_scene3_pressed"] 60 | -------------------------------------------------------------------------------- /examples/scene_demo/scenes/demo_scene.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | const SceneManager = CoreSystem.SceneManager 4 | 5 | @onready var scene_manager : SceneManager = CoreSystem.scene_manager 6 | @onready var label = $Label 7 | @onready var source_label = $SourceLabel 8 | 9 | var _message: String = "" 10 | var _source_scene: String = "" 11 | 12 | 13 | ## 初始化场景状态 14 | func init_state(data: Dictionary) -> void: 15 | if not is_node_ready(): 16 | await ready 17 | if data.has("message"): 18 | _message = data.message 19 | label.text = _message 20 | else: 21 | label.text = name 22 | 23 | if data.has("source_scene"): 24 | _source_scene = data.source_scene 25 | source_label.text = "从场景 " + _source_scene + " 切换而来" 26 | else: 27 | source_label.text = "初始场景" 28 | 29 | ## 保存场景状态 30 | func save_state() -> Dictionary: 31 | return { 32 | "message": _message if not _message.is_empty() else name, 33 | "source_scene": _source_scene 34 | } 35 | 36 | ## 恢复场景状态 37 | func restore_state(data: Dictionary) -> void: 38 | if not is_node_ready(): 39 | await ready 40 | if data.has("message"): 41 | _message = data.message 42 | label.text = _message 43 | if data.has("source_scene"): 44 | _source_scene = data.source_scene 45 | source_label.text = "从场景 " + _source_scene + " 切换而来" 46 | 47 | ## 返回上一个场景 48 | func _on_back_button_pressed() -> void: 49 | if scene_manager: 50 | scene_manager.pop_scene_async( 51 | SceneManager.TransitionEffect.NONE, 52 | 0.5 53 | ) 54 | -------------------------------------------------------------------------------- /examples/scene_demo/scenes/demo_scene.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cve56a01fxb1q 2 | -------------------------------------------------------------------------------- /examples/scene_demo/scenes/scene1.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://kbxdoilv60o8"] 2 | 3 | [ext_resource type="Script" uid="uid://cve56a01fxb1q" path="res://addons/godot_core_system/examples/scene_demo/scenes/demo_scene.gd" id="1_j2on2"] 4 | 5 | [node name="Scene1" type="Control"] 6 | layout_mode = 3 7 | anchors_preset = 15 8 | anchor_right = 1.0 9 | anchor_bottom = 1.0 10 | grow_horizontal = 2 11 | grow_vertical = 2 12 | script = ExtResource("1_j2on2") 13 | 14 | [node name="ColorRect" type="ColorRect" parent="."] 15 | layout_mode = 1 16 | anchors_preset = 15 17 | anchor_right = 1.0 18 | anchor_bottom = 1.0 19 | grow_horizontal = 2 20 | grow_vertical = 2 21 | color = Color(0.2, 0.4, 0.8, 1) 22 | 23 | [node name="Label" type="Label" parent="."] 24 | layout_mode = 1 25 | anchors_preset = 8 26 | anchor_left = 0.5 27 | anchor_top = 0.5 28 | anchor_right = 0.5 29 | anchor_bottom = 0.5 30 | offset_left = -200.0 31 | offset_top = -13.0 32 | offset_right = 200.0 33 | offset_bottom = 13.0 34 | grow_horizontal = 2 35 | grow_vertical = 2 36 | theme_override_font_sizes/font_size = 24 37 | text = "场景 1" 38 | horizontal_alignment = 1 39 | 40 | [node name="SourceLabel" type="Label" parent="."] 41 | layout_mode = 1 42 | anchors_preset = 8 43 | anchor_left = 0.5 44 | anchor_top = 0.5 45 | anchor_right = 0.5 46 | anchor_bottom = 0.5 47 | offset_left = -200.0 48 | offset_top = 20.0 49 | offset_right = 200.0 50 | offset_bottom = 46.0 51 | grow_horizontal = 2 52 | grow_vertical = 2 53 | theme_override_font_sizes/font_size = 16 54 | text = "初始场景" 55 | horizontal_alignment = 1 56 | 57 | [node name="BackButton" type="Button" parent="."] 58 | layout_mode = 1 59 | anchors_preset = 7 60 | anchor_left = 0.5 61 | anchor_top = 1.0 62 | anchor_right = 0.5 63 | anchor_bottom = 1.0 64 | offset_left = -48.0 65 | offset_top = -100.0 66 | offset_right = 48.0 67 | offset_bottom = -69.0 68 | grow_horizontal = 2 69 | grow_vertical = 0 70 | text = "返回" 71 | 72 | [connection signal="pressed" from="BackButton" to="." method="_on_back_button_pressed"] 73 | -------------------------------------------------------------------------------- /examples/scene_demo/scenes/scene2.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://dbw53xlo38m0o"] 2 | 3 | [ext_resource type="Script" uid="uid://cve56a01fxb1q" path="res://addons/godot_core_system/examples/scene_demo/scenes/demo_scene.gd" id="1_hvav5"] 4 | 5 | [node name="Scene2" type="Control"] 6 | layout_mode = 3 7 | anchors_preset = 15 8 | anchor_right = 1.0 9 | anchor_bottom = 1.0 10 | grow_horizontal = 2 11 | grow_vertical = 2 12 | script = ExtResource("1_hvav5") 13 | 14 | [node name="ColorRect" type="ColorRect" parent="."] 15 | layout_mode = 1 16 | anchors_preset = 15 17 | anchor_right = 1.0 18 | anchor_bottom = 1.0 19 | grow_horizontal = 2 20 | grow_vertical = 2 21 | color = Color(0.4, 0.8, 0.2, 1) 22 | 23 | [node name="Label" type="Label" parent="."] 24 | layout_mode = 1 25 | anchors_preset = 8 26 | anchor_left = 0.5 27 | anchor_top = 0.5 28 | anchor_right = 0.5 29 | anchor_bottom = 0.5 30 | offset_left = -200.0 31 | offset_top = -13.0 32 | offset_right = 200.0 33 | offset_bottom = 13.0 34 | grow_horizontal = 2 35 | grow_vertical = 2 36 | theme_override_font_sizes/font_size = 24 37 | text = "场景 2" 38 | horizontal_alignment = 1 39 | 40 | [node name="SourceLabel" type="Label" parent="."] 41 | layout_mode = 1 42 | anchors_preset = 8 43 | anchor_left = 0.5 44 | anchor_top = 0.5 45 | anchor_right = 0.5 46 | anchor_bottom = 0.5 47 | offset_left = -200.0 48 | offset_top = 20.0 49 | offset_right = 200.0 50 | offset_bottom = 46.0 51 | grow_horizontal = 2 52 | grow_vertical = 2 53 | theme_override_font_sizes/font_size = 16 54 | text = "初始场景" 55 | horizontal_alignment = 1 56 | 57 | [node name="BackButton" type="Button" parent="."] 58 | layout_mode = 1 59 | anchors_preset = 7 60 | anchor_left = 0.5 61 | anchor_top = 1.0 62 | anchor_right = 0.5 63 | anchor_bottom = 1.0 64 | offset_left = -48.0 65 | offset_top = -100.0 66 | offset_right = 48.0 67 | offset_bottom = -69.0 68 | grow_horizontal = 2 69 | grow_vertical = 0 70 | text = "返回" 71 | 72 | [connection signal="pressed" from="BackButton" to="." method="_on_back_button_pressed"] 73 | -------------------------------------------------------------------------------- /examples/scene_demo/scenes/scene3.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://dg85aa1sht7in"] 2 | 3 | [ext_resource type="Script" uid="uid://cve56a01fxb1q" path="res://addons/godot_core_system/examples/scene_demo/scenes/demo_scene.gd" id="1_dgxna"] 4 | 5 | [node name="Scene3" type="Control"] 6 | layout_mode = 3 7 | anchors_preset = 15 8 | anchor_right = 1.0 9 | anchor_bottom = 1.0 10 | grow_horizontal = 2 11 | grow_vertical = 2 12 | script = ExtResource("1_dgxna") 13 | 14 | [node name="ColorRect" type="ColorRect" parent="."] 15 | layout_mode = 1 16 | anchors_preset = 15 17 | anchor_right = 1.0 18 | anchor_bottom = 1.0 19 | grow_horizontal = 2 20 | grow_vertical = 2 21 | color = Color(0.8, 0.2, 0.4, 1) 22 | 23 | [node name="Label" type="Label" parent="."] 24 | layout_mode = 1 25 | anchors_preset = 8 26 | anchor_left = 0.5 27 | anchor_top = 0.5 28 | anchor_right = 0.5 29 | anchor_bottom = 0.5 30 | offset_left = -200.0 31 | offset_top = -13.0 32 | offset_right = 200.0 33 | offset_bottom = 13.0 34 | grow_horizontal = 2 35 | grow_vertical = 2 36 | theme_override_font_sizes/font_size = 24 37 | text = "场景 3" 38 | horizontal_alignment = 1 39 | 40 | [node name="SourceLabel" type="Label" parent="."] 41 | layout_mode = 1 42 | anchors_preset = 8 43 | anchor_left = 0.5 44 | anchor_top = 0.5 45 | anchor_right = 0.5 46 | anchor_bottom = 0.5 47 | offset_left = -200.0 48 | offset_top = 20.0 49 | offset_right = 200.0 50 | offset_bottom = 46.0 51 | grow_horizontal = 2 52 | grow_vertical = 2 53 | theme_override_font_sizes/font_size = 16 54 | text = "初始场景" 55 | horizontal_alignment = 1 56 | 57 | [node name="BackButton" type="Button" parent="."] 58 | layout_mode = 1 59 | anchors_preset = 7 60 | anchor_left = 0.5 61 | anchor_top = 1.0 62 | anchor_right = 0.5 63 | anchor_bottom = 1.0 64 | offset_left = -48.0 65 | offset_top = -100.0 66 | offset_right = 48.0 67 | offset_bottom = -69.0 68 | grow_horizontal = 2 69 | grow_vertical = 0 70 | text = "返回" 71 | 72 | [connection signal="pressed" from="BackButton" to="." method="_on_back_button_pressed"] 73 | -------------------------------------------------------------------------------- /examples/state_machine/example.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | ## 分层状态机示例 4 | ## 这个示例展示了一个简单的游戏状态机系统,包含主状态机和子状态机 5 | var state_machine_manager : CoreSystem.StateMachineManager = CoreSystem.state_machine_manager 6 | var state_label : Label 7 | 8 | func _ready() -> void: 9 | # 创建并注册主状态机 10 | var game_state_machine = ExampleGameStateMachine.new() 11 | state_machine_manager.register_state_machine(&"game", game_state_machine, self, &"menu") 12 | 13 | # 获取状态显示标签 14 | state_label = $StateLabel 15 | 16 | func _process(_delta: float) -> void: 17 | # 更新状态显示 18 | update_state_display() 19 | 20 | func update_state_display() -> void: 21 | var current_state_text = "当前状态: " 22 | var game_state_machine = state_machine_manager.get_state_machine(&"game") 23 | 24 | if game_state_machine and game_state_machine.is_active: 25 | var main_state_name = game_state_machine.get_current_state_name() 26 | current_state_text += main_state_name 27 | 28 | # 如果是游戏状态,还要显示子状态 29 | if main_state_name == &"gameplay" and game_state_machine.current_state is BaseStateMachine: 30 | var gameplay_state = game_state_machine.current_state as BaseStateMachine 31 | if gameplay_state.current_state: 32 | current_state_text += " > " + gameplay_state.get_current_state_name() 33 | else: 34 | current_state_text += "无" 35 | 36 | state_label.text = current_state_text 37 | -------------------------------------------------------------------------------- /examples/state_machine/example.gd.uid: -------------------------------------------------------------------------------- 1 | uid://mmwadm71pi7m 2 | -------------------------------------------------------------------------------- /examples/state_machine/example.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://bwgvla1fa1o42"] 2 | 3 | [ext_resource type="Script" uid="uid://mmwadm71pi7m" path="res://addons/godot_core_system/examples/state_machine/example.gd" id="1_spsyk"] 4 | 5 | [node name="Example" type="Node2D"] 6 | script = ExtResource("1_spsyk") 7 | 8 | [node name="Label" type="Label" parent="."] 9 | offset_left = 8.0 10 | offset_top = 32.0 11 | offset_right = 608.0 12 | offset_bottom = 341.0 13 | theme_override_font_sizes/font_size = 10 14 | text = "按键操作: 15 | - Enter (ui_accept): 16 | - 在菜单中:开始游戏 17 | - 在探索中:进入战斗 18 | - Tab (ui_focus_next): 19 | - 在探索中:开始对话 20 | - Esc (ui_cancel): 21 | - 在游戏中:暂停(将保存当前游戏状态) 22 | - 在暂停中:继续(将恢复到之前的游戏状态) 23 | - 在战斗/对话中:返回探索 24 | - Home (ui_home): 25 | - 在暂停中:返回主菜单" 26 | 27 | [node name="StateLabel" type="Label" parent="."] 28 | offset_right = 576.0 29 | offset_bottom = 40.0 30 | theme_override_font_sizes/font_size = 18 31 | text = "当前状态: 无" 32 | -------------------------------------------------------------------------------- /examples/state_machine/example_game_state_machine.gd: -------------------------------------------------------------------------------- 1 | extends BaseStateMachine 2 | class_name ExampleGameStateMachine 3 | 4 | 5 | func _ready() -> void: 6 | # 添加主要状态 7 | add_state(&"menu", MenuState.new()) 8 | add_state(&"gameplay", GameplayState.new()) 9 | add_state(&"pause", PauseState.new()) 10 | 11 | ## 菜单状态 12 | class MenuState extends BaseState: 13 | func _enter(_msg := {}) -> void: 14 | print("进入菜单状态") 15 | 16 | func _handle_input(event: InputEvent) -> void: 17 | if event.is_action_pressed("ui_accept"): 18 | switch_to(&"gameplay") 19 | 20 | ## 游戏状态(包含子状态机) 21 | class GameplayState extends BaseStateMachine: 22 | func _ready() -> void: 23 | # 添加子状态 24 | add_state(&"explore", ExploreState.new()) # 添加探索状态 25 | add_state(&"battle", BattleState.new()) # 添加战斗状态 26 | add_state(&"dialog", DialogState.new()) # 添加对话状态 27 | print("gameplay state ready") 28 | 29 | func _enter(msg := {}) -> void: 30 | print("进入游戏状态") 31 | # 如果msg中指定了resume,则恢复到上一个状态 32 | if current_state == null: # 只在没有当前状态时才启动 33 | start(&"explore", {}, msg.get("resume", false)) 34 | 35 | func _handle_input(event: InputEvent) -> void: 36 | if event.is_action_pressed("ui_cancel"): 37 | print("退出游戏状态 ui_cancel") 38 | switch_to(&"pause", {"resume": true}) # 传递resume标记 39 | 40 | func _exit() -> void: 41 | print("退出游戏状态") 42 | stop() 43 | 44 | ## 探索状态 45 | class ExploreState extends BaseState: 46 | func _enter(_msg := {}) -> void: 47 | print("进入探索状态") 48 | 49 | func _handle_input(event: InputEvent) -> void: 50 | if event.is_action_pressed("ui_accept"): 51 | switch_to(&"battle") 52 | elif event.is_action_pressed("ui_focus_next"): 53 | switch_to(&"dialog") 54 | 55 | ## 战斗状态 56 | class BattleState extends BaseState: 57 | func _enter(_msg := {}) -> void: 58 | print("进入战斗状态") 59 | 60 | func _exit() -> void: 61 | print("退出战斗状态") 62 | 63 | func _handle_input(event: InputEvent) -> void: 64 | if event.is_action_pressed("ui_cancel"): 65 | print("退出战斗状态 ui_cancel") 66 | switch_to(&"explore") 67 | 68 | ## 对话状态 69 | class DialogState extends BaseState: 70 | func _enter(_msg := {}) -> void: 71 | print("进入对话状态") 72 | 73 | func _handle_input(event: InputEvent) -> void: 74 | if event.is_action_pressed("ui_cancel"): 75 | switch_to(&"explore") 76 | 77 | ## 暂停状态 78 | class PauseState extends BaseState: 79 | func _enter(_msg := {}) -> void: 80 | print("进入暂停状态") 81 | 82 | func _handle_input(event: InputEvent) -> void: 83 | if event.is_action_pressed("ui_cancel"): 84 | switch_to(&"gameplay", {"resume": true}) # 传递resume标记 85 | elif event.is_action_pressed("ui_home"): 86 | switch_to(&"menu") -------------------------------------------------------------------------------- /examples/state_machine/example_game_state_machine.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cr67ucqdecqol 2 | -------------------------------------------------------------------------------- /examples/tag_demo/tag_character.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | ## 角色的标签容器 4 | var tag_container : GameplayTagContainer: 5 | get: 6 | if not tag_container: 7 | tag_container = GameplayTagContainer.new() 8 | return tag_container 9 | 10 | ## 添加标签 11 | func add_tag(tag: String) -> void: 12 | tag_container.add_tag(tag) 13 | 14 | ## 移除标签 15 | func remove_tag(tag: String) -> void: 16 | tag_container.remove_tag(tag) 17 | 18 | ## 检查是否有标签 19 | func has_tag(tag: String, exact: bool = true) -> bool: 20 | return tag_container.has_tag(tag, exact) 21 | 22 | ## 获取所有标签 23 | func get_tags() -> Array: 24 | return tag_container.get_tags() -------------------------------------------------------------------------------- /examples/tag_demo/tag_character.gd.uid: -------------------------------------------------------------------------------- 1 | uid://b261f8dk00dww 2 | -------------------------------------------------------------------------------- /examples/tag_demo/tag_demo.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | ## 标签系统示例 4 | 5 | @onready var tag_manager : CoreSystem.GameplayTagManager = CoreSystem.tag_manager 6 | @onready var status_label = $UI/StatusLabel 7 | @onready var player: Node2D = $Player 8 | @onready var enemy: Node2D = $Enemy 9 | 10 | func _ready() -> void: 11 | # 给玩家添加标签 12 | player.add_tag("character") 13 | player.add_tag("character.player") 14 | player.add_tag("state.idle") 15 | 16 | # 给敌人添加标签 17 | enemy.add_tag("character") 18 | enemy.add_tag("character.enemy") 19 | enemy.add_tag("state.idle") 20 | 21 | 22 | func _on_player_move_button_pressed() -> void: 23 | # 切换玩家移动状态 24 | if player.has_tag("state.moving"): 25 | player.remove_tag("state.moving") 26 | player.add_tag("state.idle") 27 | $Player.modulate = Color.WHITE 28 | _update_status("Player stopped moving") 29 | else: 30 | player.remove_tag("state.idle") 31 | player.add_tag("state.moving") 32 | $Player.modulate = Color.GREEN 33 | _update_status("Player started moving") 34 | 35 | 36 | func _on_player_attack_button_pressed() -> void: 37 | # 玩家攻击状态 38 | if player.has_tag("state.attacking"): 39 | return 40 | 41 | player.add_tag("state.attacking") 42 | $Player.modulate = Color.RED 43 | _update_status("Player is attacking!") 44 | 45 | # 2秒后移除攻击状态 46 | await get_tree().create_timer(2.0).timeout 47 | player.remove_tag("state.attacking") 48 | $Player.modulate = Color.WHITE if player.has_tag("state.idle") else Color.GREEN 49 | _update_status("Player finished attacking") 50 | 51 | 52 | func _on_buff_button_pressed() -> void: 53 | # 给玩家添加随机增益 54 | var buffs = ["buff.speed_up", "buff.attack_up"] 55 | var random_buff = buffs[randi() % buffs.size()] 56 | 57 | if player.has_tag(random_buff): 58 | player.remove_tag(random_buff) 59 | _update_status("Removed buff: " + random_buff.split(".")[-1]) 60 | else: 61 | player.add_tag(random_buff) 62 | _update_status("Added buff: " + random_buff.split(".")[-1]) 63 | 64 | # 更新buff显示 65 | _update_buff_display() 66 | 67 | 68 | func _on_query_button_pressed() -> void: 69 | # 查询并显示所有标签 70 | var player_tag_list = player.get_tags() 71 | var enemy_tag_list = enemy.get_tags() 72 | 73 | var status_text = "Player tags: %s\nEnemy tags: %s" % [player_tag_list, enemy_tag_list] 74 | _update_status(status_text) 75 | 76 | func _update_status(text: String) -> void: 77 | status_label.text = text 78 | 79 | 80 | func _update_buff_display() -> void: 81 | var buff_text = "" 82 | if player.has_tag("buff.speed_up"): 83 | buff_text += "[Speed Up] " 84 | if player.has_tag("buff.attack_up"): 85 | buff_text += "[Attack Up] " 86 | 87 | $UI/BuffLabel.text = "Active Buffs: " + (buff_text if not buff_text.is_empty() else "None") 88 | -------------------------------------------------------------------------------- /examples/tag_demo/tag_demo.gd.uid: -------------------------------------------------------------------------------- 1 | uid://xonpuf6mmtlm 2 | -------------------------------------------------------------------------------- /examples/tag_demo/tag_demo.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=3 uid="uid://ndhkmmfbele4"] 2 | 3 | [ext_resource type="Script" uid="uid://b261f8dk00dww" path="res://addons/godot_core_system/examples/tag_demo/tag_character.gd" id="1_5lwci"] 4 | [ext_resource type="Script" uid="uid://xonpuf6mmtlm" path="res://addons/godot_core_system/examples/tag_demo/tag_demo.gd" id="1_mwg5h"] 5 | 6 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_zzzzz"] 7 | bg_color = Color(0.2, 0.2, 0.2, 1) 8 | corner_radius_top_left = 8 9 | corner_radius_top_right = 8 10 | corner_radius_bottom_right = 8 11 | corner_radius_bottom_left = 8 12 | 13 | [node name="TagDemo" type="Node2D"] 14 | script = ExtResource("1_mwg5h") 15 | 16 | [node name="Player" type="Node2D" parent="."] 17 | position = Vector2(300, 300) 18 | script = ExtResource("1_5lwci") 19 | 20 | [node name="ColorRect" type="ColorRect" parent="Player"] 21 | offset_left = -20.0 22 | offset_top = -20.0 23 | offset_right = 20.0 24 | offset_bottom = 20.0 25 | color = Color(0.2, 0.6, 1, 1) 26 | 27 | [node name="Label" type="Label" parent="Player"] 28 | offset_left = -20.0 29 | offset_top = 25.0 30 | offset_right = 20.0 31 | offset_bottom = 51.0 32 | text = "Player" 33 | horizontal_alignment = 1 34 | 35 | [node name="Enemy" type="Node2D" parent="."] 36 | position = Vector2(500, 300) 37 | script = ExtResource("1_5lwci") 38 | 39 | [node name="ColorRect" type="ColorRect" parent="Enemy"] 40 | offset_left = -20.0 41 | offset_top = -20.0 42 | offset_right = 20.0 43 | offset_bottom = 20.0 44 | color = Color(1, 0.2, 0.2, 1) 45 | 46 | [node name="Label" type="Label" parent="Enemy"] 47 | offset_left = -20.0 48 | offset_top = 25.0 49 | offset_right = 20.0 50 | offset_bottom = 51.0 51 | text = "Enemy" 52 | horizontal_alignment = 1 53 | 54 | [node name="UI" type="Control" parent="."] 55 | layout_mode = 3 56 | anchors_preset = 15 57 | anchor_right = 1.0 58 | anchor_bottom = 1.0 59 | grow_horizontal = 2 60 | grow_vertical = 2 61 | 62 | [node name="Panel" type="Panel" parent="UI"] 63 | layout_mode = 0 64 | offset_left = 20.0 65 | offset_top = 20.0 66 | offset_right = 230.0 67 | offset_bottom = 221.0 68 | theme_override_styles/panel = SubResource("StyleBoxFlat_zzzzz") 69 | 70 | [node name="VBoxContainer" type="VBoxContainer" parent="UI/Panel"] 71 | layout_mode = 1 72 | anchors_preset = 15 73 | anchor_right = 1.0 74 | anchor_bottom = 1.0 75 | offset_left = 10.0 76 | offset_top = 10.0 77 | offset_right = -10.0 78 | offset_bottom = -10.0 79 | grow_horizontal = 2 80 | grow_vertical = 2 81 | 82 | [node name="MoveButton" type="Button" parent="UI/Panel/VBoxContainer"] 83 | layout_mode = 2 84 | text = "Toggle Move" 85 | 86 | [node name="AttackButton" type="Button" parent="UI/Panel/VBoxContainer"] 87 | layout_mode = 2 88 | text = "Attack" 89 | 90 | [node name="BuffButton" type="Button" parent="UI/Panel/VBoxContainer"] 91 | layout_mode = 2 92 | text = "Toggle Buff" 93 | 94 | [node name="QueryButton" type="Button" parent="UI/Panel/VBoxContainer"] 95 | layout_mode = 2 96 | text = "Query Tags" 97 | 98 | [node name="StatusLabel" type="Label" parent="UI"] 99 | layout_mode = 0 100 | offset_left = 20.0 101 | offset_top = 240.0 102 | offset_right = 395.0 103 | offset_bottom = 310.0 104 | text = "Status: Ready" 105 | autowrap_mode = 3 106 | 107 | [node name="BuffLabel" type="Label" parent="UI"] 108 | layout_mode = 0 109 | offset_left = 20.0 110 | offset_top = 352.0 111 | offset_right = 391.0 112 | offset_bottom = 424.0 113 | text = "Active Buffs: None" 114 | 115 | [connection signal="pressed" from="UI/Panel/VBoxContainer/MoveButton" to="." method="_on_player_move_button_pressed"] 116 | [connection signal="pressed" from="UI/Panel/VBoxContainer/AttackButton" to="." method="_on_player_attack_button_pressed"] 117 | [connection signal="pressed" from="UI/Panel/VBoxContainer/BuffButton" to="." method="_on_buff_button_pressed"] 118 | [connection signal="pressed" from="UI/Panel/VBoxContainer/QueryButton" to="." method="_on_query_button_pressed"] 119 | -------------------------------------------------------------------------------- /examples/time_demo/time_demo.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | const TimeManager = CoreSystem.TimeManager 4 | 5 | @onready var time_manager : TimeManager = CoreSystem.time_manager 6 | @onready var game_time_label = $UI/GameTimeLabel 7 | @onready var timer_label = $UI/TimerLabel 8 | @onready var status_label = $UI/StatusLabel 9 | 10 | # 计时器ID 11 | const TIMER_IDS = { 12 | "countdown": "demo_countdown", 13 | "loop": "demo_loop" 14 | } 15 | 16 | func _ready(): 17 | # 连接信号 18 | time_manager.time_scale_changed.connect(_on_time_scale_changed) 19 | time_manager.timer_completed.connect(_on_timer_completed) 20 | time_manager.time_state_changed.connect(_on_time_state_changed) 21 | 22 | # 创建一个5秒倒计时 23 | time_manager.create_timer(TIMER_IDS.countdown, 5.0, false, 24 | func(): print("倒计时结束!")) 25 | # 创建一个2秒循环计时器 26 | time_manager.create_timer(TIMER_IDS.loop, 2.0, true, 27 | func(): print("循环计时器触发!")) 28 | 29 | # 设置状态标签 30 | status_label.text = "时间系统演示" 31 | 32 | func _process(_delta): 33 | # 更新游戏时间显示 34 | game_time_label.text = "游戏时间:%.2f 秒" % time_manager.get_game_time() 35 | 36 | # 更新计时器显示 37 | var countdown_time = time_manager.get_timer_remaining(TIMER_IDS.countdown) 38 | if countdown_time > 0: 39 | timer_label.text = "倒计时:%.1f 秒" % countdown_time 40 | else: 41 | timer_label.text = "倒计时已结束" 42 | 43 | ## 时间缩放按钮回调 44 | func _on_speed_button_pressed(scale: float): 45 | time_manager.set_time_scale(scale) 46 | status_label.text = "时间缩放:%.1fx" % scale 47 | 48 | ## 暂停按钮回调 49 | func _on_pause_button_pressed(): 50 | var new_state = !time_manager.is_paused() 51 | time_manager.set_paused(new_state) 52 | 53 | ## 重置倒计时按钮回调 54 | func _on_reset_button_pressed(): 55 | var success = time_manager.reset_timer(TIMER_IDS.countdown) 56 | if success: 57 | status_label.text = "倒计时已重置" 58 | else: 59 | time_manager.create_timer(TIMER_IDS.countdown, 5.0, false, 60 | func(): print("倒计时结束!")) 61 | ## 时间缩放变化回调 62 | func _on_time_scale_changed(new_scale: float): 63 | status_label.text = "时间缩放已更改为:%.1fx" % new_scale 64 | 65 | ## 计时器完成回调 66 | func _on_timer_completed(timer_id: String): 67 | if timer_id == TIMER_IDS.countdown: 68 | status_label.text = "倒计时完成!" 69 | 70 | ## 时间状态变化回调 71 | func _on_time_state_changed(is_paused: bool): 72 | status_label.text = "时间已%s" % ("暂停" if is_paused else "恢复") 73 | -------------------------------------------------------------------------------- /examples/time_demo/time_demo.gd.uid: -------------------------------------------------------------------------------- 1 | uid://k8w826kr0445 2 | -------------------------------------------------------------------------------- /examples/time_demo/time_demo.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://dyj07da8tde3x"] 2 | 3 | [ext_resource type="Script" uid="uid://k8w826kr0445" path="res://addons/godot_core_system/examples/time_demo/time_demo.gd" id="1_w8g4f"] 4 | 5 | [node name="TimeDemo" type="Node2D"] 6 | script = ExtResource("1_w8g4f") 7 | 8 | [node name="UI" type="CanvasLayer" parent="."] 9 | 10 | [node name="GameTimeLabel" type="Label" parent="UI"] 11 | anchors_preset = 5 12 | anchor_left = 0.5 13 | anchor_right = 0.5 14 | offset_left = -100.0 15 | offset_top = 20.0 16 | offset_right = 100.0 17 | offset_bottom = 46.0 18 | grow_horizontal = 2 19 | text = "游戏时间:0.00 秒" 20 | horizontal_alignment = 1 21 | 22 | [node name="TimerLabel" type="Label" parent="UI"] 23 | anchors_preset = 5 24 | anchor_left = 0.5 25 | anchor_right = 0.5 26 | offset_left = -100.0 27 | offset_top = 46.0 28 | offset_right = 100.0 29 | offset_bottom = 72.0 30 | grow_horizontal = 2 31 | text = "倒计时:5.0 秒" 32 | horizontal_alignment = 1 33 | 34 | [node name="StatusLabel" type="Label" parent="UI"] 35 | anchors_preset = 5 36 | anchor_left = 0.5 37 | anchor_right = 0.5 38 | offset_left = -100.0 39 | offset_right = 100.0 40 | offset_bottom = 26.0 41 | grow_horizontal = 2 42 | text = "时间系统演示" 43 | horizontal_alignment = 1 44 | 45 | [node name="Controls" type="VBoxContainer" parent="UI"] 46 | anchors_preset = 8 47 | anchor_left = 0.5 48 | anchor_top = 0.5 49 | anchor_right = 0.5 50 | anchor_bottom = 0.5 51 | offset_left = -100.0 52 | offset_top = -84.0 53 | offset_right = 100.0 54 | offset_bottom = 84.0 55 | grow_horizontal = 2 56 | grow_vertical = 2 57 | 58 | [node name="Speed05Button" type="Button" parent="UI/Controls"] 59 | layout_mode = 2 60 | text = "0.5x 速度" 61 | 62 | [node name="Speed10Button" type="Button" parent="UI/Controls"] 63 | layout_mode = 2 64 | text = "1.0x 速度" 65 | 66 | [node name="Speed20Button" type="Button" parent="UI/Controls"] 67 | layout_mode = 2 68 | text = "2.0x 速度" 69 | 70 | [node name="PauseButton" type="Button" parent="UI/Controls"] 71 | layout_mode = 2 72 | text = "暂停/继续" 73 | 74 | [node name="ResetButton" type="Button" parent="UI/Controls"] 75 | layout_mode = 2 76 | text = "重置倒计时" 77 | 78 | [connection signal="pressed" from="UI/Controls/Speed05Button" to="." method="_on_speed_button_pressed" binds= [0.5]] 79 | [connection signal="pressed" from="UI/Controls/Speed10Button" to="." method="_on_speed_button_pressed" binds= [1.0]] 80 | [connection signal="pressed" from="UI/Controls/Speed20Button" to="." method="_on_speed_button_pressed" binds= [2.0]] 81 | [connection signal="pressed" from="UI/Controls/PauseButton" to="." method="_on_pause_button_pressed"] 82 | [connection signal="pressed" from="UI/Controls/ResetButton" to="." method="_on_reset_button_pressed"] 83 | -------------------------------------------------------------------------------- /examples/trigger_demo/trigger_demo.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | ## 触发器系统示例 4 | 5 | @onready var trigger_manager : CoreSystem.TriggerManager = CoreSystem.trigger_manager 6 | @onready var player = $Player 7 | @onready var status_label = $UI/StatusLabel 8 | 9 | func _ready() -> void: 10 | # 创建进入区域的触发器 11 | var enter_area_trigger = GameplayTrigger.new({ 12 | "trigger_type": GameplayTrigger.TRIGGER_TYPE.ON_EVENT, 13 | "trigger_event": "enter_area", 14 | "conditions": [ 15 | { 16 | "type": "state_trigger_condition", 17 | "state_name": "player_in_area", 18 | "required_state": "true" 19 | } 20 | ] 21 | }) 22 | enter_area_trigger.triggered.connect(_on_enter_area) 23 | enter_area_trigger.activate() 24 | 25 | # 创建点击按钮的触发器 26 | var click_button_trigger = GameplayTrigger.new({ 27 | "trigger_type": GameplayTrigger.TRIGGER_TYPE.ON_EVENT, 28 | "trigger_event": "button_click", 29 | "conditions": [ 30 | { 31 | "type": "state_trigger_condition", 32 | "state_name": "button_clicked", 33 | "required_state": "true" 34 | } 35 | ] 36 | }) 37 | click_button_trigger.triggered.connect(_on_button_clicked) 38 | click_button_trigger.max_triggers = 1 39 | click_button_trigger.activate() 40 | 41 | # 创建周期性触发器 42 | var periodic_trigger = GameplayTrigger.new({ 43 | "trigger_type": GameplayTrigger.TRIGGER_TYPE.PERIODIC, 44 | "period": 2.0, # 每2秒触发一次 45 | "trigger_chance": 0.5 # 50%触发概率 46 | }) 47 | periodic_trigger.triggered.connect(_on_periodic_trigger) 48 | periodic_trigger.activate() 49 | 50 | 51 | func _on_area_2d_body_entered(_body: Node2D) -> void: 52 | trigger_manager.handle_event("enter_area", { 53 | "state_name": "player_in_area", 54 | "state_value": "true" 55 | }) 56 | 57 | func _on_area_2d_body_exited(_body: Node2D) -> void: 58 | trigger_manager.handle_event("enter_area", { 59 | "state_name": "player_in_area", 60 | "state_value": "false" 61 | }) 62 | 63 | func _on_button_pressed() -> void: 64 | trigger_manager.handle_event("button_click", { 65 | "state_name": "button_clicked", 66 | "state_value": "true" 67 | }) 68 | 69 | func _on_enter_area(context: Dictionary) -> void: 70 | status_label.text = "Player entered area!" 71 | status_label.modulate.a = 1.0 72 | create_tween().tween_property(status_label, "modulate:a", 0.0, 1.0) 73 | 74 | func _on_button_clicked(context: Dictionary) -> void: 75 | status_label.text = "Button clicked!" 76 | status_label.modulate.a = 1.0 77 | create_tween().tween_property(status_label, "modulate:a", 0.0, 1.0) 78 | 79 | func _on_periodic_trigger(context: Dictionary) -> void: 80 | status_label.text = "Periodic trigger activated!" 81 | status_label.modulate.a = 1.0 82 | create_tween().tween_property(status_label, "modulate:a", 0.0, 1.0) 83 | 84 | func _physics_process(delta: float) -> void: 85 | # 简单的玩家移动控制 86 | var input := Vector2.ZERO 87 | input.x = Input.get_axis("ui_left", "ui_right") 88 | input.y = Input.get_axis("ui_up", "ui_down") 89 | player.position += input * 200 * delta 90 | -------------------------------------------------------------------------------- /examples/trigger_demo/trigger_demo.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dn7fakmeblec6 2 | -------------------------------------------------------------------------------- /examples/trigger_demo/trigger_demo.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=3 uid="uid://ddyomssbamjd0"] 2 | 3 | [ext_resource type="Script" uid="uid://dn7fakmeblec6" path="res://addons/godot_core_system/examples/trigger_demo/trigger_demo.gd" id="1_lejic"] 4 | 5 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_xxxxx"] 6 | size = Vector2(32, 32) 7 | 8 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_yyyyy"] 9 | size = Vector2(200, 200) 10 | 11 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_zzzzz"] 12 | bg_color = Color(0.2, 0.2, 0.2, 1) 13 | corner_radius_top_left = 8 14 | corner_radius_top_right = 8 15 | corner_radius_bottom_right = 8 16 | corner_radius_bottom_left = 8 17 | 18 | [node name="TriggerDemo" type="Node2D"] 19 | script = ExtResource("1_lejic") 20 | 21 | [node name="Player" type="CharacterBody2D" parent="."] 22 | position = Vector2(100, 300) 23 | collision_layer = 2 24 | 25 | [node name="ColorRect" type="ColorRect" parent="Player"] 26 | offset_left = -16.0 27 | offset_top = -16.0 28 | offset_right = 16.0 29 | offset_bottom = 16.0 30 | color = Color(0.2, 0.6, 1, 1) 31 | 32 | [node name="CollisionShape2D" type="CollisionShape2D" parent="Player"] 33 | shape = SubResource("RectangleShape2D_xxxxx") 34 | 35 | [node name="TriggerArea" type="Area2D" parent="."] 36 | position = Vector2(500, 300) 37 | collision_mask = 2 38 | 39 | [node name="ColorRect" type="ColorRect" parent="TriggerArea"] 40 | offset_left = -100.0 41 | offset_top = -100.0 42 | offset_right = 100.0 43 | offset_bottom = 100.0 44 | color = Color(1, 0.5, 0.2, 0.3) 45 | 46 | [node name="CollisionShape2D" type="CollisionShape2D" parent="TriggerArea"] 47 | shape = SubResource("RectangleShape2D_yyyyy") 48 | 49 | [node name="UI" type="Control" parent="."] 50 | layout_mode = 3 51 | anchors_preset = 15 52 | anchor_right = 1.0 53 | anchor_bottom = 1.0 54 | grow_horizontal = 2 55 | grow_vertical = 2 56 | 57 | [node name="Panel" type="Panel" parent="UI"] 58 | layout_mode = 0 59 | offset_left = 20.0 60 | offset_top = 20.0 61 | offset_right = 220.0 62 | offset_bottom = 120.0 63 | theme_override_styles/panel = SubResource("StyleBoxFlat_zzzzz") 64 | 65 | [node name="VBoxContainer" type="VBoxContainer" parent="UI/Panel"] 66 | layout_mode = 1 67 | anchors_preset = 15 68 | anchor_right = 1.0 69 | anchor_bottom = 1.0 70 | offset_left = 10.0 71 | offset_top = 10.0 72 | offset_right = -10.0 73 | offset_bottom = -10.0 74 | grow_horizontal = 2 75 | grow_vertical = 2 76 | 77 | [node name="TriggerButton" type="Button" parent="UI/Panel/VBoxContainer"] 78 | layout_mode = 2 79 | text = "Trigger Button" 80 | 81 | [node name="StatusLabel" type="Label" parent="UI"] 82 | layout_mode = 0 83 | offset_left = 20.0 84 | offset_top = 140.0 85 | offset_right = 220.0 86 | offset_bottom = 166.0 87 | text = "Status: Ready" 88 | 89 | [connection signal="body_entered" from="TriggerArea" to="." method="_on_area_2d_body_entered"] 90 | [connection signal="body_exited" from="TriggerArea" to="." method="_on_area_2d_body_exited"] 91 | [connection signal="pressed" from="UI/Panel/VBoxContainer/TriggerButton" to="." method="_on_button_pressed"] 92 | -------------------------------------------------------------------------------- /git: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiGameAcademy/godot_core_system/0c689a1b78164e981c7a361b650f26e37134e99b/git -------------------------------------------------------------------------------- /plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="godot_core_system" 4 | description="基于godot4.2的,一些常用的的单例和可复用的框架结构、功能模块等。" 5 | author="laoli" 6 | version="0.0.1" 7 | script="plugin.gd" 8 | -------------------------------------------------------------------------------- /plugin.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorPlugin 3 | 4 | 5 | const SYSTEM_NAME: String = "CoreSystem" 6 | var SYSTEM_PATH: String = FileDirHandler.get_object_script_dir(self) + "/source/core_system.gd" 7 | 8 | const SETTING_SCRIPT: Script = preload("./setting.gd") 9 | const SETTING_INFO_DICT: Dictionary[StringName, Dictionary] = SETTING_SCRIPT.SETTING_INFO_DICT 10 | 11 | ## 在插件运行时添加项目设置 12 | func _enter_tree() -> void: 13 | _add_project_settings() 14 | add_autoload_singleton(SYSTEM_NAME, SYSTEM_PATH) 15 | ProjectSettings.save() 16 | 17 | ## 在禁用插件时恢复项目配置 18 | func _disable_plugin() -> void: 19 | remove_autoload_singleton(SYSTEM_NAME) 20 | _remove_project_settings() 21 | ProjectSettings.save() 22 | 23 | ## 添加配置脚本中的设置项 24 | func _add_project_settings() -> void: 25 | for setting_dict in SETTING_INFO_DICT.values(): 26 | _add_setting_dict(setting_dict) 27 | 28 | ## 移除配置脚本中的设置项 29 | func _remove_project_settings() -> void: 30 | for setting_dict in SETTING_INFO_DICT.values(): 31 | _remove_setting_dict(setting_dict) 32 | 33 | ## 使用hint_dictionary添加选项 34 | func _add_setting_dict(info_dict: Dictionary) -> void: 35 | var setting_name: String = info_dict["name"] 36 | if not ProjectSettings.has_setting(setting_name): 37 | ProjectSettings.set_setting(setting_name, info_dict["default"]) 38 | 39 | ProjectSettings.set_as_basic(setting_name, info_dict["basic"]) 40 | ProjectSettings.set_initial_value(setting_name, info_dict["default"]) 41 | ProjectSettings.add_property_info(info_dict) 42 | 43 | ## 使用hint_dictionary移除选项 44 | func _remove_setting_dict(info_dict: Dictionary) -> void: 45 | var setting_name: String = info_dict["name"] 46 | if ProjectSettings.has_setting(setting_name): 47 | ProjectSettings.set_setting(setting_name, null) 48 | 49 | ## 添加单个设置项 50 | func _add_setting(name: String, default_value, type: int, hint: int, hint_string: String = "") -> void: 51 | if not ProjectSettings.has_setting(name): 52 | ProjectSettings.set_setting(name, default_value) 53 | 54 | ProjectSettings.set_initial_value(name, default_value) 55 | ProjectSettings.add_property_info({ 56 | "name": name, 57 | "type": type, 58 | "hint": hint, 59 | "hint_string": hint_string 60 | }) 61 | 62 | 63 | ## 确保项目设置中有我们的分类 64 | func _ensure_project_settings_category() -> void: 65 | if not ProjectSettings.has_setting("godot_core_system/modules"): 66 | ProjectSettings.set_setting("godot_core_system/modules", {}) 67 | ProjectSettings.set_as_basic("godot_core_system/modules", true) 68 | ProjectSettings.add_property_info({ 69 | "name": "godot_core_system/modules", 70 | "type": TYPE_DICTIONARY, 71 | "hint": PROPERTY_HINT_NONE, 72 | "hint_string": "Core System Modules" 73 | }) 74 | -------------------------------------------------------------------------------- /plugin.gd.uid: -------------------------------------------------------------------------------- 1 | uid://kldnjh164od2 2 | -------------------------------------------------------------------------------- /setting.gd.uid: -------------------------------------------------------------------------------- 1 | uid://b76e18f06idfs 2 | -------------------------------------------------------------------------------- /source/audio_system/audio_manager.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cjnqmbledetxf 2 | -------------------------------------------------------------------------------- /source/config_system/config_manager.gd.uid: -------------------------------------------------------------------------------- 1 | uid://ky1vvk1ilt0f 2 | -------------------------------------------------------------------------------- /source/core_system.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cbspmadydncps 2 | -------------------------------------------------------------------------------- /source/entity_system/entity_manager.gd.uid: -------------------------------------------------------------------------------- 1 | uid://0x1gdv5qu3y6 2 | -------------------------------------------------------------------------------- /source/event_system/event_bus.gd.uid: -------------------------------------------------------------------------------- 1 | uid://da6j211gtwk1d 2 | -------------------------------------------------------------------------------- /source/input_system/config/input_config.gd.uid: -------------------------------------------------------------------------------- 1 | uid://co8v7qjuur7jn 2 | -------------------------------------------------------------------------------- /source/input_system/config/input_config_adapter.gd.uid: -------------------------------------------------------------------------------- 1 | uid://b0c2kflu76gvn 2 | -------------------------------------------------------------------------------- /source/input_system/features/input_buffer.gd: -------------------------------------------------------------------------------- 1 | extends RefCounted 2 | class_name InputBuffer 3 | 4 | ## 缓冲数据结构 5 | class BufferData: 6 | ## 动作名称 7 | var action: String 8 | ## 输入强度 9 | var strength: float 10 | ## 创建时间 11 | var creation_time: float 12 | ## 缓冲持续时间 13 | var duration: float 14 | 15 | func _init(p_action: String, p_strength: float, p_duration: float) -> void: 16 | action = p_action 17 | strength = p_strength 18 | creation_time = Time.get_ticks_msec() / 1000.0 19 | duration = p_duration 20 | 21 | ## 缓冲列表 22 | var _buffers: Array[BufferData] = [] 23 | ## 默认缓冲时间(秒) 24 | var _default_buffer_duration: float = 0.1 25 | 26 | ## 设置默认缓冲时间 27 | ## [param duration] 缓冲时间(秒) 28 | func set_buffer_duration(duration: float) -> void: 29 | _default_buffer_duration = duration 30 | 31 | ## 获取默认缓冲时间 32 | ## [return] 缓冲时间(秒) 33 | func get_buffer_duration() -> float: 34 | return _default_buffer_duration 35 | 36 | ## 添加输入缓冲 37 | ## [param action] 动作名称 38 | ## [param strength] 输入强度 39 | ## [param duration] 缓冲时间(可选,默认使用默认缓冲时间) 40 | func add_buffer(action: String, strength: float = 1.0, duration: float = -1.0) -> void: 41 | if duration < 0: 42 | duration = _default_buffer_duration 43 | _buffers.append(BufferData.new(action, strength, duration)) 44 | 45 | ## 检查动作是否在缓冲中 46 | ## [param action] 动作名称 47 | ## [return] 是否在缓冲中 48 | func has_buffer(action: String) -> bool: 49 | clean_expired_buffers() 50 | for buffer in _buffers: 51 | if buffer.action == action: 52 | return true 53 | return false 54 | 55 | ## 获取缓冲的输入强度 56 | ## [param action] 动作名称 57 | ## [return] 输入强度,如果不在缓冲中则返回0 58 | func get_buffer_strength(action: String) -> float: 59 | clean_expired_buffers() 60 | for buffer in _buffers: 61 | if buffer.action == action: 62 | return buffer.strength 63 | return 0.0 64 | 65 | ## 清除指定动作的缓冲 66 | ## [param action] 动作名称 67 | func clear_buffer(action: String) -> void: 68 | for i in range(_buffers.size() - 1, -1, -1): 69 | if _buffers[i].action == action: 70 | _buffers.remove_at(i) 71 | 72 | ## 清除所有缓冲 73 | func clear_all_buffers() -> void: 74 | _buffers.clear() 75 | 76 | ## 清理过期的缓冲 77 | func clean_expired_buffers() -> void: 78 | var current_time = Time.get_ticks_msec() / 1000.0 79 | for i in range(_buffers.size() - 1, -1, -1): 80 | var buffer = _buffers[i] 81 | if current_time - buffer.creation_time > buffer.duration: 82 | _buffers.remove_at(i) 83 | 84 | ## 获取所有缓冲数据 85 | ## [return] 缓冲数据字典 86 | func get_all_buffers() -> Dictionary: 87 | clean_expired_buffers() 88 | var buffers = {} 89 | for buffer in _buffers: 90 | buffers[buffer.action] = { 91 | "strength": buffer.strength, 92 | "creation_time": buffer.creation_time, 93 | "duration": buffer.duration, 94 | "remaining_time": buffer.duration - (Time.get_ticks_msec() / 1000.0 - buffer.creation_time) 95 | } 96 | return buffers 97 | -------------------------------------------------------------------------------- /source/input_system/features/input_buffer.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cv2k54hd0xqy0 2 | -------------------------------------------------------------------------------- /source/input_system/features/input_event_processor.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cphydgfanykig 2 | -------------------------------------------------------------------------------- /source/input_system/features/input_recorder.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c7hp20ojgcjrd 2 | -------------------------------------------------------------------------------- /source/input_system/features/input_virtual_axis.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dvuawsotigm1p 2 | -------------------------------------------------------------------------------- /source/input_system/input_manager.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | ## 动作触发信号 4 | signal action_triggered(action_name: String, event: InputEvent) 5 | ## 轴值变化信号 6 | signal axis_changed(axis_name: String, value: Vector2) 7 | ## 重映射完成信号 8 | signal remap_completed(action: String, event: InputEvent) 9 | 10 | ## 虚拟轴系统 11 | @onready var virtual_axis: InputVirtualAxis = InputVirtualAxis.new() 12 | ## 输入缓冲系统 13 | @onready var input_buffer: InputBuffer = InputBuffer.new() 14 | ## 输入记录器 15 | @onready var input_recorder: InputRecorder = InputRecorder.new() 16 | ## 输入状态管理器 17 | @onready var input_state: InputState = InputState.new() 18 | ## 事件处理器 19 | @onready var event_processor: InputEventProcessor = InputEventProcessor.new() 20 | ## 配置管理器 21 | @onready var config_manager: Node = CoreSystem.config_manager 22 | 23 | func _ready() -> void: 24 | process_mode = Node.PROCESS_MODE_ALWAYS 25 | _setup_input_handling() 26 | virtual_axis.axis_changed.connect(_on_axis_changed) 27 | 28 | func _process(delta: float) -> void: 29 | _update_input_state(delta) 30 | 31 | func _input(event: InputEvent) -> void: 32 | if not event_processor.process_event(event): 33 | return 34 | _process_action_input(event) 35 | 36 | #region 私有方法 - 初始化 37 | func _setup_input_handling() -> void: 38 | set_process_input(true) 39 | #endregion 40 | 41 | #region 私有方法 - 输入处理 42 | ## 更新输入状态 43 | ## [param delta] 时间增量 44 | func _update_input_state(delta: float) -> void: 45 | input_buffer.clean_expired_buffers() 46 | 47 | # 更新轴状态 48 | for axis_name in virtual_axis.get_registered_axes(): 49 | virtual_axis.update_axis(axis_name) 50 | 51 | # 更新所有动作的状态 52 | for action in InputMap.get_actions(): 53 | var is_pressed = Input.is_action_pressed(action) 54 | var strength = Input.get_action_strength(action) 55 | input_state.update_action(action, is_pressed, strength) 56 | 57 | ## 处理动作输入 58 | ## [param event] 输入事件 59 | func _process_action_input(event: InputEvent) -> void: 60 | if not event.is_action_type(): 61 | return 62 | 63 | for action in InputMap.get_actions(): 64 | if event.is_action(action): 65 | var just_pressed = event.is_action_pressed(action) 66 | var just_released = event.is_action_released(action) 67 | var strength = event.get_action_strength(action) 68 | 69 | if just_pressed or just_released: 70 | # 更新输入状态 71 | input_state.update_action(action, just_pressed, strength) 72 | 73 | # 处理输入缓冲 74 | if just_pressed: 75 | input_buffer.add_buffer(action, strength) 76 | 77 | # 记录输入 78 | input_recorder.record_input(action, just_pressed, strength) 79 | 80 | # 发送信号 81 | action_triggered.emit(action, event) 82 | #endregion 83 | 84 | #region 私有方法 - 回调函数 85 | ## 轴值变化回调 86 | ## [param axis_name] 轴名称 87 | ## [param value] 轴值 88 | func _on_axis_changed(axis_name: String, value: Vector2) -> void: 89 | axis_changed.emit(axis_name, value) 90 | #endregion 91 | -------------------------------------------------------------------------------- /source/input_system/input_manager.gd.uid: -------------------------------------------------------------------------------- 1 | uid://b2i7i7nju1hxc 2 | -------------------------------------------------------------------------------- /source/input_system/input_state.gd: -------------------------------------------------------------------------------- 1 | extends RefCounted 2 | class_name InputState 3 | 4 | ## 动作状态数据结构 5 | class ActionState: 6 | ## 是否按下 7 | var pressed: bool = false 8 | ## 是否刚按下 9 | var just_pressed: bool = false 10 | ## 是否刚释放 11 | var just_released: bool = false 12 | ## 输入强度 13 | var strength: float = 0.0 14 | ## 按下时间 15 | var press_time: float = 0.0 16 | ## 上次按下时间 17 | var last_press_time: float = 0.0 18 | ## 上次释放时间 19 | var last_release_time: float = 0.0 20 | 21 | func _init() -> void: 22 | reset() 23 | 24 | ## 重置状态 25 | func reset() -> void: 26 | pressed = false 27 | just_pressed = false 28 | just_released = false 29 | strength = 0.0 30 | press_time = 0.0 31 | last_press_time = 0.0 32 | last_release_time = 0.0 33 | 34 | ## 更新状态 35 | ## [param is_pressed] 是否按下 36 | ## [param input_strength] 输入强度 37 | func update(is_pressed: bool, input_strength: float) -> void: 38 | var current_time = Time.get_ticks_msec() / 1000.0 39 | 40 | # 更新按下/释放状态 41 | just_pressed = is_pressed and not pressed 42 | just_released = not is_pressed and pressed 43 | pressed = is_pressed 44 | 45 | # 更新强度 46 | strength = input_strength if pressed else 0.0 47 | 48 | # 更新时间 49 | if just_pressed: 50 | last_press_time = press_time 51 | press_time = current_time 52 | elif just_released: 53 | last_release_time = current_time 54 | 55 | ## 动作状态字典 56 | var _action_states: Dictionary = {} 57 | 58 | ## 更新动作状态 59 | ## [param action_name] 动作名称 60 | ## [param is_pressed] 是否按下 61 | ## [param strength] 输入强度 62 | func update_action(action_name: String, is_pressed: bool, strength: float) -> void: 63 | if not _action_states.has(action_name): 64 | _action_states[action_name] = ActionState.new() 65 | 66 | _action_states[action_name].update(is_pressed, strength) 67 | 68 | ## 获取动作状态 69 | ## [param action_name] 动作名称 70 | ## [return] 动作状态 71 | func get_action_state(action_name: String) -> ActionState: 72 | if not _action_states.has(action_name): 73 | _action_states[action_name] = ActionState.new() 74 | 75 | return _action_states[action_name] 76 | 77 | ## 检查动作是否按下 78 | ## [param action_name] 动作名称 79 | ## [return] 是否按下 80 | func is_pressed(action_name: String) -> bool: 81 | return get_action_state(action_name).pressed 82 | 83 | ## 检查动作是否刚按下 84 | ## [param action_name] 动作名称 85 | ## [return] 是否刚按下 86 | func is_just_pressed(action_name: String) -> bool: 87 | return get_action_state(action_name).just_pressed 88 | 89 | ## 检查动作是否刚释放 90 | ## [param action_name] 动作名称 91 | ## [return] 是否刚释放 92 | func is_just_released(action_name: String) -> bool: 93 | return get_action_state(action_name).just_released 94 | 95 | ## 获取动作强度 96 | ## [param action_name] 动作名称 97 | ## [return] 动作强度 98 | func get_strength(action_name: String) -> float: 99 | return get_action_state(action_name).strength 100 | 101 | ## 获取动作按下时间 102 | ## [param action_name] 动作名称 103 | ## [return] 按下时间 104 | func get_press_time(action_name: String) -> float: 105 | return get_action_state(action_name).press_time 106 | 107 | ## 获取动作上次按下时间 108 | ## [param action_name] 动作名称 109 | ## [return] 上次按下时间 110 | func get_last_press_time(action_name: String) -> float: 111 | return get_action_state(action_name).last_press_time 112 | 113 | ## 获取动作上次释放时间 114 | ## [param action_name] 动作名称 115 | ## [return] 上次释放时间 116 | func get_last_release_time(action_name: String) -> float: 117 | return get_action_state(action_name).last_release_time 118 | 119 | ## 重置动作状态 120 | ## [param action_name] 动作名称,如果为空则重置所有 121 | func reset_action(action_name: String = "") -> void: 122 | if action_name.is_empty(): 123 | for state in _action_states.values(): 124 | state.reset() 125 | elif _action_states.has(action_name): 126 | _action_states[action_name].reset() 127 | 128 | ## 获取所有动作状态 129 | ## [return] 动作状态字典 130 | func get_all_states() -> Dictionary: 131 | var states = {} 132 | for action_name in _action_states: 133 | states[action_name] = get_action_state(action_name) 134 | return states 135 | -------------------------------------------------------------------------------- /source/input_system/input_state.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c7iem5u2n3itr 2 | -------------------------------------------------------------------------------- /source/logger/logger.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cp1ae8br1ohwe 2 | -------------------------------------------------------------------------------- /source/resource_system/resource_manager.gd.uid: -------------------------------------------------------------------------------- 1 | uid://viqd0nprluu0 2 | -------------------------------------------------------------------------------- /source/save_system/game_state_data.gd: -------------------------------------------------------------------------------- 1 | extends Resource 2 | 3 | ## 游戏状态数据 4 | 5 | ## 存档元数据 6 | @export var metadata : Dictionary = { 7 | "save_id": "", 8 | "timestamp": 0, 9 | "save_date": "", 10 | "game_version": "", 11 | "playtime": 0.0, 12 | } 13 | ## 节点状态 14 | @export var nodes_state : Array[Dictionary] = [] 15 | 16 | func _init( 17 | p_save_id: StringName = "", 18 | p_timestamp: int = 0, 19 | p_save_date: String = "", 20 | p_game_version: String = "", 21 | p_playtime: float = 0.0, 22 | ) -> void: 23 | metadata.save_id = p_save_id 24 | metadata.timestamp = p_timestamp 25 | metadata.save_date = p_save_date 26 | metadata.game_version = p_game_version 27 | metadata.playtime = p_playtime 28 | -------------------------------------------------------------------------------- /source/save_system/game_state_data.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bl7nf0xvrmwt0 2 | -------------------------------------------------------------------------------- /source/save_system/save_format_strategy/async_io_strategy.gd.uid: -------------------------------------------------------------------------------- 1 | uid://djx25ps14ydrk 2 | -------------------------------------------------------------------------------- /source/save_system/save_format_strategy/binary_save_strategy.gd: -------------------------------------------------------------------------------- 1 | extends "./async_io_strategy.gd" 2 | 3 | func _init() -> void: 4 | _io_manager = CoreSystem.AsyncIOManager.new( 5 | CoreSystem.AsyncIOManager.JSONSerializationStrategy.new(), 6 | CoreSystem.AsyncIOManager.GzipCompressionStrategy.new(), 7 | CoreSystem.AsyncIOManager.XOREncryptionStrategy.new() 8 | ) 9 | 10 | ## 是否为有效存档 11 | func is_valid_save_file(file_name: String) -> bool: 12 | return file_name.ends_with(".save") 13 | 14 | ## 获取存档ID 15 | func get_save_id_from_file(file_name: String) -> String: 16 | return file_name.trim_suffix(".save") 17 | 18 | ## 获取存档路径 19 | func get_save_path(directory: String, save_id: String) -> String: 20 | return directory.path_join("%s.save" % save_id) 21 | -------------------------------------------------------------------------------- /source/save_system/save_format_strategy/binary_save_strategy.gd.uid: -------------------------------------------------------------------------------- 1 | uid://w8wexmxm2ddn 2 | -------------------------------------------------------------------------------- /source/save_system/save_format_strategy/json_save_strategy.gd: -------------------------------------------------------------------------------- 1 | extends "./async_io_strategy.gd" 2 | 3 | func _init() -> void: 4 | _io_manager = CoreSystem.AsyncIOManager.new(CoreSystem.AsyncIOManager.JSONSerializationStrategy.new()) 5 | 6 | ## 是否为有效的存档文件 7 | func is_valid_save_file(file_name: String) -> bool: 8 | return file_name.ends_with(".json") 9 | 10 | ## 获取存档名 11 | func get_save_id_from_file(file_name: String) -> String: 12 | return file_name.trim_suffix(".json") 13 | 14 | ## 获取存档路径 15 | func get_save_path(directory: String, save_id: String) -> String: 16 | return directory.path_join("%s.json" % save_id) 17 | 18 | -------------------------------------------------------------------------------- /source/save_system/save_format_strategy/json_save_strategy.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c1t3vhqrhlyag 2 | -------------------------------------------------------------------------------- /source/save_system/save_format_strategy/resource_save_strategy.gd: -------------------------------------------------------------------------------- 1 | extends "./save_format_strategy.gd" 2 | 3 | const GameStateData = CoreSystem.SaveManager.GameStateData 4 | 5 | ## 文件名是否有效 6 | func is_valid_save_file(file_name: String) -> bool: 7 | return file_name.ends_with(".tres") 8 | 9 | ## 获取存档ID 10 | func get_save_id_from_file(file_name: String) -> String: 11 | return file_name.trim_suffix(".tres") 12 | 13 | ## 获取存档路径 14 | func get_save_path(directory: String, save_id: String) -> String: 15 | return directory.path_join("%s.tres" % save_id) 16 | 17 | ## 保存存档 18 | func save(path: String, data: Dictionary) -> bool: 19 | var save_data = GameStateData.new( 20 | data.metadata.save_id, 21 | data.metadata.timestamp, 22 | data.metadata.save_date, 23 | data.metadata.game_version, 24 | data.metadata.playtime 25 | ) 26 | # 设置节点状态 27 | save_data.nodes_state = data.nodes 28 | 29 | # 保存资源 30 | var error = ResourceSaver.save(save_data, path) 31 | return error == OK 32 | 33 | ## 加载存档数据 34 | func load_save(path: String) -> Dictionary: 35 | if not FileAccess.file_exists(path): 36 | return {} 37 | 38 | var resource = ResourceLoader.load(path) 39 | if not resource: 40 | return {} 41 | 42 | var result = { 43 | "metadata": resource.metadata, 44 | "nodes": resource.nodes_state 45 | } 46 | 47 | return result 48 | 49 | ## 加载元数据 50 | func load_metadata(path: String) -> Dictionary: 51 | if not FileAccess.file_exists(path): 52 | return {} 53 | 54 | var resource = ResourceLoader.load(path) 55 | return resource.metadata if resource else {} 56 | -------------------------------------------------------------------------------- /source/save_system/save_format_strategy/resource_save_strategy.gd.uid: -------------------------------------------------------------------------------- 1 | uid://djkkaa2lw6i2h 2 | -------------------------------------------------------------------------------- /source/save_system/save_format_strategy/save_format_strategy.gd: -------------------------------------------------------------------------------- 1 | extends RefCounted 2 | 3 | ## 存档格式策略接口 4 | 5 | ## 是否为有效的存档文件 6 | ## [param file_name] 文件名称 7 | ## [return] 是否存在有效的存档文件 8 | func is_valid_save_file(file_name: String) -> bool: 9 | return false 10 | 11 | ## 从文件名获取存档ID 12 | ## [param file_name] 文件名称 13 | ## [return] 存档ID 14 | func get_save_id_from_file(file_name: String) -> String: 15 | return "" 16 | 17 | ## 获取存档路径 18 | ## [param directory] 19 | ## [param save_id] 存档ID 20 | ## [return] 存档路径 21 | func get_save_path(directory: String, save_id: String) -> String: 22 | return "" 23 | 24 | ## 保存数据 25 | ## [param path] 存档路径 26 | ## [param data] 存储数据 27 | ## [param callback] 完成回调,参数bool是否完成 28 | func save(path: String, data: Dictionary) -> bool: 29 | return false 30 | 31 | ## 加载数据 32 | ## [param path] 存档路径 33 | ## [param callback] 完成回调,参数:bool是否完成,Dictionary存档数据 34 | func load_save(path: String) -> Dictionary: 35 | return {} 36 | 37 | ## 加载元数据 38 | ## [param path] 存档路径 39 | ## [param callback] 完成回调,参数:bool是否完成,Dictionary存档元数据 40 | func load_metadata(path: String) -> Dictionary: 41 | return {} 42 | 43 | ## 删除文件 44 | ## [param path] 存档路径 45 | ## [return] 是否删除成功 46 | func delete_file(path: String) -> bool: 47 | var dir = DirAccess.open(path.get_base_dir()) 48 | if not dir: 49 | return false 50 | return dir.remove(path.get_file()) == OK 51 | 52 | ## 列出文件 53 | ## [param directory] 存档目录 54 | ## [return] 文件列表 55 | func list_files(directory: String) -> Array: 56 | var files := [] 57 | var dir := DirAccess.open(directory) 58 | if not dir: 59 | return files 60 | 61 | dir.list_dir_begin() 62 | var file_name := dir.get_next() 63 | while not file_name.is_empty(): 64 | if file_name != "." and file_name != ".." and not dir.current_is_dir(): 65 | if is_valid_save_file(file_name): 66 | files.append(file_name) 67 | file_name = dir.get_next() 68 | dir.list_dir_end() 69 | return files 70 | -------------------------------------------------------------------------------- /source/save_system/save_format_strategy/save_format_strategy.gd.uid: -------------------------------------------------------------------------------- 1 | uid://b8mgxiidia6xm 2 | -------------------------------------------------------------------------------- /source/save_system/save_manager.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c6sa8ilucacgd 2 | -------------------------------------------------------------------------------- /source/scene_system/scene_base.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | class_name SceneBase 3 | 4 | ## 场景基类,提供状态保存和恢复功能 5 | ## 注意:core_system使用has_method确认场景是否有对应方法 6 | ## 注意:因此继承SceneBase并不是必须的 7 | 8 | ## 初始化场景状态 9 | func init_state(_data: Dictionary) -> void: 10 | pass 11 | 12 | ## 保存场景状态 13 | func save_state() -> Dictionary: 14 | return {} 15 | 16 | ## 恢复场景状态 17 | func restore_state(_data: Dictionary) -> void: 18 | pass 19 | -------------------------------------------------------------------------------- /source/scene_system/scene_base.gd.uid: -------------------------------------------------------------------------------- 1 | uid://corsuxwbsbjas 2 | -------------------------------------------------------------------------------- /source/scene_system/scene_manager.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dkreu5wicfg16 2 | -------------------------------------------------------------------------------- /source/scene_system/transitions/base_transition.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends Resource 3 | class_name BaseTransition 4 | 5 | ## 转场效果基类 6 | ## 所有自定义转场效果都应该继承这个类 7 | 8 | ## 转场矩形 9 | var _transition_rect: ColorRect 10 | 11 | ## 初始化转场效果 12 | ## @param transition_rect 转场矩形 13 | func init(transition_rect: ColorRect) -> void: 14 | _transition_rect = transition_rect 15 | 16 | ## 开始转场效果 17 | ## @param duration 转场持续时间 18 | func start(duration: float) -> void: 19 | if not _transition_rect: 20 | return 21 | _reset_state() 22 | await _do_start(duration) 23 | 24 | ## 结束转场效果 25 | ## @param duration 转场持续时间 26 | func end(duration: float) -> void: 27 | if not _transition_rect: 28 | return 29 | await _do_end(duration) 30 | _reset_state() 31 | 32 | ## 重置状态 33 | ## 在开始和结束转场时都会调用 34 | func _reset_state() -> void: 35 | if not _transition_rect: 36 | return 37 | _transition_rect.position = Vector2.ZERO 38 | _transition_rect.color = Color.BLACK 39 | if _transition_rect.material: 40 | _transition_rect.material.set_shader_parameter("progress", 0.0) 41 | 42 | ## 执行开始转场 43 | ## 子类必须实现这个方法 44 | ## @param duration 转场持续时间 45 | func _do_start(_duration: float) -> void: 46 | push_error("BaseTransition._do_start() not implemented!") 47 | 48 | ## 执行结束转场 49 | ## 子类必须实现这个方法 50 | ## @param duration 转场持续时间 51 | func _do_end(_duration: float) -> void: 52 | push_error("BaseTransition._do_end() not implemented!") 53 | 54 | ## 清理资源 55 | func _notification(what: int) -> void: 56 | if what == NOTIFICATION_PREDELETE: 57 | if _transition_rect and _transition_rect.material: 58 | _transition_rect.material = null 59 | _transition_rect = null 60 | -------------------------------------------------------------------------------- /source/scene_system/transitions/base_transition.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c51kfsgi55y2b 2 | -------------------------------------------------------------------------------- /source/scene_system/transitions/dissolve_transition.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends BaseTransition 3 | class_name DissolveTransition 4 | 5 | ## 溶解转场效果 6 | 7 | ## 执行开始转场 8 | ## @param duration 转场持续时间 9 | func _do_start(duration: float) -> void: 10 | var tween = _transition_rect.create_tween() 11 | tween.tween_property(_transition_rect, "color:a", 1.0, duration) 12 | await tween.finished 13 | 14 | ## 执行结束转场 15 | ## @param duration 转场持续时间 16 | func _do_end(duration: float) -> void: 17 | var tween = _transition_rect.create_tween() 18 | tween.tween_property(_transition_rect, "color:a", 0.0, duration) 19 | await tween.finished 20 | -------------------------------------------------------------------------------- /source/scene_system/transitions/dissolve_transition.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dsse3wrd8my7q 2 | -------------------------------------------------------------------------------- /source/scene_system/transitions/fade_transition.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends BaseTransition 3 | class_name FadeTransition 4 | 5 | ## 淡入淡出转场效果 6 | 7 | ## 执行开始转场 8 | ## @param duration 转场持续时间 9 | func _do_start(duration: float) -> void: 10 | _transition_rect.color.a = 0.0 11 | var tween = _transition_rect.create_tween() 12 | tween.tween_property(_transition_rect, "color:a", 1.0, duration) 13 | await tween.finished 14 | 15 | ## 执行结束转场 16 | ## @param duration 转场持续时间 17 | func _do_end(duration: float) -> void: 18 | var tween = _transition_rect.create_tween() 19 | tween.tween_property(_transition_rect, "color:a", 0.0, duration) 20 | await tween.finished 21 | -------------------------------------------------------------------------------- /source/scene_system/transitions/fade_transition.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c6e6j20o03jwy 2 | -------------------------------------------------------------------------------- /source/scene_system/transitions/slide_transition.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends BaseTransition 3 | class_name SlideTransition 4 | 5 | ## 滑动转场效果 6 | 7 | ## 执行开始转场 8 | ## @param duration 转场持续时间 9 | func _do_start(duration: float) -> void: 10 | _transition_rect.color.a = 1.0 11 | _transition_rect.position.x = -_transition_rect.size.x 12 | 13 | var tween = _transition_rect.create_tween() 14 | tween.tween_property(_transition_rect, "position:x", 0, duration) 15 | await tween.finished 16 | 17 | ## 执行结束转场 18 | ## @param duration 转场持续时间 19 | func _do_end(duration: float) -> void: 20 | var tween = _transition_rect.create_tween() 21 | tween.tween_property(_transition_rect, "position:x", _transition_rect.size.x, duration) 22 | await tween.finished 23 | -------------------------------------------------------------------------------- /source/scene_system/transitions/slide_transition.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dcmvuakeqgqbq 2 | -------------------------------------------------------------------------------- /source/state_machine/base_state.gd: -------------------------------------------------------------------------------- 1 | extends RefCounted 2 | class_name BaseState 3 | 4 | ## 基础状态类 5 | 6 | # 信号 7 | ## 状态进入 8 | signal state_entered(msg: Dictionary) 9 | ## 状态退出 10 | signal state_exited 11 | 12 | var state_id : StringName = &"" 13 | ## 状态机引用 14 | var state_machine : BaseStateMachine = null 15 | ## 代理者 16 | var agent: Object = null : set = _agent_setter 17 | 18 | ## 是否活跃 19 | var is_active: bool = false 20 | var _is_ready 21 | 22 | var is_debug: bool = false 23 | var _logger : CoreSystem.Logger = CoreSystem.logger 24 | 25 | func ready() -> void: 26 | if _is_ready: 27 | _logger.warning("State is already ready!") 28 | return 29 | _ready() 30 | _is_ready = true 31 | 32 | func dispose() -> void: 33 | _dispose() 34 | _is_ready = false 35 | 36 | ## 进入状态 37 | func enter(msg: Dictionary = {}) -> bool: 38 | if is_active: 39 | _logger.warning("State is already active!") 40 | return false 41 | is_active = true 42 | _enter(msg) 43 | _debug("Entering state: %s" % state_id) 44 | state_entered.emit(msg) 45 | return true 46 | 47 | ## 退出状态 48 | func exit() -> bool: 49 | if not is_active: 50 | _logger.warning("State is not active!") 51 | return false 52 | is_active = false 53 | _exit() 54 | _debug("Exiting state: %s" % state_id) 55 | state_exited.emit() 56 | return true 57 | 58 | ## 更新 59 | func update(delta: float) -> void: 60 | if not is_active: 61 | return 62 | _update(delta) 63 | 64 | ## 物理更新 65 | func physics_update(delta: float) -> void: 66 | if not is_active: 67 | return 68 | _physics_update(delta) 69 | 70 | ## 处理事件 71 | func handle_input(event: InputEvent) -> void: 72 | if not is_active: 73 | return 74 | _handle_input(event) 75 | 76 | ## 切换状态 77 | func switch_to(state_id: StringName, msg: Dictionary = {}) -> void: 78 | if not state_machine: 79 | _logger.error("State machine is not set!") 80 | return 81 | 82 | if not state_machine.has_state(state_id): 83 | _logger.error("State %s does not exist!" % state_id) 84 | return 85 | 86 | if state_machine: 87 | state_machine.switch(state_id, msg) 88 | 89 | ## 获取变量 90 | func get_variable(key: StringName) -> Variant: 91 | if state_machine: 92 | return state_machine.get_variable(key) 93 | return null 94 | 95 | ## 设置变量 96 | func set_variable(key: StringName, value: Variant) -> void: 97 | if state_machine: 98 | state_machine.set_variable(key, value) 99 | 100 | ## 检查变量是否存在 101 | func has_variable(key: StringName) -> bool: 102 | if state_machine: 103 | return state_machine.has_variable(key) 104 | return false 105 | 106 | ## 虚函数 - 准备 107 | func _ready() -> void: 108 | pass 109 | 110 | ## 虚函数 - 清理 111 | func _dispose() -> void: 112 | pass 113 | 114 | ## 虚函数 - 进入状态 115 | func _enter(_msg: Dictionary = {}) -> void: 116 | pass 117 | 118 | ## 虚函数 - 退出状态 119 | func _exit() -> void: 120 | pass 121 | 122 | ## 虚函数 - 更新 123 | func _update(_delta: float) -> void: 124 | pass 125 | 126 | ## 虚函数 - 物理更新 127 | func _physics_update(_delta: float) -> void: 128 | pass 129 | 130 | ## 虚函数 - 处理输入 131 | func _handle_input(_event: InputEvent) -> void: 132 | pass 133 | 134 | 135 | func _debug(debug: String) -> void: 136 | if not is_debug: 137 | return 138 | _logger.debug("[State] " + state_id + ": " + debug) 139 | 140 | 141 | func _agent_setter(value: Object) -> void: 142 | agent = value 143 | -------------------------------------------------------------------------------- /source/state_machine/base_state.gd.uid: -------------------------------------------------------------------------------- 1 | uid://eo76ewqsdiev 2 | -------------------------------------------------------------------------------- /source/state_machine/base_state_machine.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bugidns56c08x 2 | -------------------------------------------------------------------------------- /source/state_machine/state_machine_manager.gd.uid: -------------------------------------------------------------------------------- 1 | uid://lfi5erupb0gr 2 | -------------------------------------------------------------------------------- /source/tag_system/gameplay_tag.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends RefCounted 3 | class_name GameplayTag 4 | 5 | ## 标签名称 6 | var name: String 7 | 8 | ## 父标签 9 | var parent: GameplayTag 10 | 11 | ## 子标签 12 | var children: Array[GameplayTag] = [] 13 | 14 | static func create(tag_name: String) -> GameplayTag: 15 | var tag := GameplayTag.new() 16 | tag.name = tag_name 17 | return tag 18 | 19 | ## 添加子标签 20 | func add_child(child: GameplayTag) -> void: 21 | if not child in children: 22 | children.append(child) 23 | child.parent = self 24 | 25 | ## 移除子标签 26 | func remove_child(child: GameplayTag) -> void: 27 | if child in children: 28 | children.erase(child) 29 | child.parent = null 30 | 31 | ## 获取完整路径 32 | func get_full_path() -> String: 33 | if parent: 34 | return parent.get_full_path() + "." + name 35 | return name 36 | 37 | ## 获取所有子标签(递归) 38 | func get_all_children() -> Array[GameplayTag]: 39 | var result: Array[GameplayTag] = [] 40 | for child in children: 41 | result.append(child) 42 | result.append_array(child.get_all_children()) 43 | return result 44 | 45 | ## 检查是否匹配目标标签 46 | ## exact: 是否精确匹配。如果为false,则会检查层级关系 47 | func matches(other: GameplayTag, exact: bool = true) -> bool: 48 | if exact: 49 | return get_full_path() == other.get_full_path() 50 | 51 | # 检查是否是目标标签的父标签 52 | var current := other 53 | while current: 54 | if current == self: 55 | return true 56 | current = current.parent 57 | 58 | # 检查是否是目标标签的子标签 59 | current = self 60 | while current: 61 | if current == other: 62 | return true 63 | current = current.parent 64 | 65 | return false 66 | -------------------------------------------------------------------------------- /source/tag_system/gameplay_tag.gd.uid: -------------------------------------------------------------------------------- 1 | uid://lm4rcy7bhuhh 2 | -------------------------------------------------------------------------------- /source/tag_system/gameplay_tag_container.gd: -------------------------------------------------------------------------------- 1 | extends Resource 2 | class_name GameplayTagContainer 3 | 4 | ## 标签列表 5 | var _tags: Array[GameplayTag] = [] 6 | 7 | ## 当添加标签时发出 8 | signal tag_added(tag: GameplayTag) 9 | ## 当移除标签时发出 10 | signal tag_removed(tag: GameplayTag) 11 | 12 | var _tag_manager : CoreSystem.GameplayTagManager: 13 | get: 14 | if not _tag_manager: 15 | _tag_manager = CoreSystem.tag_manager 16 | return _tag_manager 17 | 18 | 19 | ## 添加标签 20 | ## 可以直接传入标签路径字符串或GameplayTag对象 21 | func add_tag(tag) -> void: 22 | var tag_obj: GameplayTag 23 | if tag is String: 24 | tag_obj = _tag_manager.get_tag(tag) 25 | if not tag_obj: 26 | push_error("Tag not found: %s" % tag) 27 | return 28 | else: 29 | tag_obj = tag 30 | 31 | if not has_tag(tag_obj): 32 | _tags.append(tag_obj) 33 | tag_added.emit(tag_obj) 34 | 35 | 36 | ## 移除标签 37 | ## 可以直接传入标签路径字符串或GameplayTag对象 38 | func remove_tag(tag) -> void: 39 | var tag_obj: GameplayTag 40 | if tag is String: 41 | tag_obj = _tag_manager.get_tag(tag) 42 | if not tag_obj: 43 | push_error("Tag not found: %s" % tag) 44 | return 45 | else: 46 | tag_obj = tag 47 | 48 | if _tags.has(tag_obj): 49 | _tags.erase(tag_obj) 50 | tag_removed.emit(tag_obj) 51 | 52 | 53 | ## 是否有指定标签 54 | ## 可以直接传入标签路径字符串或GameplayTag对象 55 | func has_tag(tag, exact: bool = true) -> bool: 56 | var tag_obj: GameplayTag 57 | if tag is String: 58 | tag_obj = _tag_manager.get_tag(tag) 59 | if not tag_obj: 60 | push_error("Tag not found: %s" % tag) 61 | return false 62 | else: 63 | tag_obj = tag 64 | 65 | for existing_tag in _tags: 66 | if existing_tag.matches(tag_obj, exact): 67 | return true 68 | return false 69 | 70 | 71 | ## 是否有所有指定标签 72 | ## 可以直接传入标签路径字符串数组或GameplayTag数组 73 | func has_all_tags(required_tags: Array, exact: bool = true) -> bool: 74 | for tag in required_tags: 75 | if not has_tag(tag, exact): 76 | return false 77 | return true 78 | 79 | 80 | ## 是否有任意指定标签 81 | ## 可以直接传入标签路径字符串数组或GameplayTag数组 82 | func has_any_tags(required_tags: Array, exact: bool = true) -> bool: 83 | for tag in required_tags: 84 | if has_tag(tag, exact): 85 | return true 86 | return false 87 | 88 | 89 | ## 获取所有标签 90 | func get_tags() -> Array: 91 | return _tags.map(func(tag: GameplayTag): return tag.name) 92 | 93 | 94 | ## 获取所有标签(包括子标签) 95 | func get_all_tags() -> Array[GameplayTag]: 96 | var result: Array[GameplayTag] = [] 97 | for tag in _tags: 98 | result.append(tag) 99 | result.append_array(tag.get_all_children()) 100 | return result 101 | -------------------------------------------------------------------------------- /source/tag_system/gameplay_tag_container.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c7ucg4nah1eqg 2 | -------------------------------------------------------------------------------- /source/tag_system/gameplay_tag_manager.gd.uid: -------------------------------------------------------------------------------- 1 | uid://byimshwm0ryxe 2 | -------------------------------------------------------------------------------- /source/time_system/time_manager.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | signal time_scale_changed(new_scale: float) 4 | signal timer_completed(timer_id: String) 5 | signal time_state_changed(is_paused: bool) 6 | 7 | ## 计时器数据结构 8 | class GameTimer: 9 | var id: String 10 | var duration: float 11 | var elapsed: float 12 | var loop: bool 13 | var paused: bool 14 | var callback: Callable 15 | 16 | func _init(p_id: String, p_duration: float, p_loop: bool = false, p_callback: Callable = Callable()): 17 | id = p_id 18 | duration = p_duration 19 | elapsed = 0.0 20 | loop = p_loop 21 | paused = false 22 | callback = p_callback 23 | 24 | var _time_scale: float = 1.0 25 | var _paused: bool = false 26 | var _timers: Dictionary = {} 27 | var _game_time: float = 0.0 28 | 29 | func _process(delta: float): 30 | if _paused: 31 | return 32 | 33 | var scaled_delta = delta * _time_scale 34 | _game_time += scaled_delta 35 | 36 | # 更新所有计时器 37 | var completed_timers = [] 38 | 39 | for timer in _timers.values(): 40 | if timer.paused: 41 | continue 42 | 43 | timer.elapsed += scaled_delta 44 | if timer.elapsed >= timer.duration: 45 | if timer.callback.is_valid(): 46 | timer.callback.call() 47 | 48 | timer_completed.emit(timer.id) 49 | 50 | if timer.loop: 51 | timer.elapsed = 0.0 52 | else: 53 | completed_timers.append(timer.id) 54 | 55 | # 移除已完成的非循环计时器 56 | for timer_id in completed_timers: 57 | _timers.erase(timer_id) 58 | 59 | ## 设置时间缩放 60 | func set_time_scale(scale: float) -> void: 61 | _time_scale = max(0.0, scale) 62 | time_scale_changed.emit(_time_scale) 63 | 64 | ## 获取时间缩放 65 | func get_time_scale() -> float: 66 | return _time_scale 67 | 68 | ## 暂停/恢复时间 69 | func set_paused(paused: bool) -> void: 70 | _paused = paused 71 | time_state_changed.emit(_paused) 72 | 73 | ## 检查是否暂停 74 | func is_paused() -> bool: 75 | return _paused 76 | 77 | ## 获取游戏运行时间 78 | func get_game_time() -> float: 79 | return _game_time 80 | 81 | ## 创建计时器 82 | func create_timer(id: String, duration: float, loop: bool = false, 83 | callback: Callable = Callable()) -> void: 84 | if has_timer(id): 85 | push_warning("Timer with id '%s' already exists. Overwriting..." % id) 86 | 87 | _timers[id] = GameTimer.new(id, duration, loop, callback) 88 | 89 | ## 暂停计时器 90 | func pause_timer(timer_id: String) -> bool: 91 | if has_timer(timer_id): 92 | _timers[timer_id].paused = true 93 | return true 94 | return false 95 | 96 | ## 恢复计时器 97 | func resume_timer(timer_id: String) -> bool: 98 | if has_timer(timer_id): 99 | _timers[timer_id].paused = false 100 | return true 101 | return false 102 | 103 | ## 重置计时器 104 | func reset_timer(timer_id: String) -> bool: 105 | if has_timer(timer_id): 106 | _timers[timer_id].elapsed = 0.0 107 | return true 108 | return false 109 | 110 | ## 移除计时器 111 | func remove_timer(timer_id: String) -> void: 112 | _timers.erase(timer_id) 113 | 114 | ## 获取计时器剩余时间 115 | func get_timer_remaining(timer_id: String) -> float: 116 | if has_timer(timer_id): 117 | return max(0.0, _timers[timer_id].duration - _timers[timer_id].elapsed) 118 | return 0.0 119 | 120 | ## 检查计时器是否存在 121 | func has_timer(timer_id: String) -> bool: 122 | return _timers.has(timer_id) 123 | 124 | ## 清除所有计时器 125 | func clear_all_timers() -> void: 126 | _timers.clear() 127 | -------------------------------------------------------------------------------- /source/time_system/time_manager.gd.uid: -------------------------------------------------------------------------------- 1 | uid://clkevf5c4fbbx 2 | -------------------------------------------------------------------------------- /source/trigger_system/conditions/composite_trigger_condition.gd: -------------------------------------------------------------------------------- 1 | extends TriggerCondition 2 | class_name CompositeTriggerCondition 3 | 4 | ## 组合触发条件 5 | 6 | @export var conditions : Array[TriggerCondition] 7 | @export_enum("AND", "OR", "XOR", "NAND", "NOR") var operator : String = "AND" 8 | 9 | func _init(config : Dictionary = {}) -> void: 10 | for condition_config in config.get("conditions", {}): 11 | conditions.append(TriggerCondition.new(condition_config)) 12 | operator = config.get("operator", "AND") 13 | 14 | func evaluate(context: Dictionary) -> bool: 15 | match operator: 16 | "AND": 17 | return conditions.all(func(condition : TriggerCondition) -> bool: return condition.evaluate(context)) 18 | "OR": 19 | return conditions.any(func(condition : TriggerCondition) -> bool: return condition.evaluate(context)) 20 | "XOR": 21 | # XOR is equivalent to having an odd number of true values 22 | var result : Array[bool] = conditions.map(func(condition : TriggerCondition) -> bool: return condition.evaluate(context)) 23 | return result.count(true) % 2 == 1 24 | "NAND": 25 | # NAND is equivalent to not (AND) 26 | return not conditions.all(func(condition : TriggerCondition) -> bool: return condition.evaluate(context)) 27 | "NOR": 28 | # NOR is equivalent to not (OR) 29 | return not conditions.any(func(condition : TriggerCondition) -> bool: return condition.evaluate(context)) 30 | _: 31 | return false 32 | -------------------------------------------------------------------------------- /source/trigger_system/conditions/composite_trigger_condition.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dpdesd2u3cqpq 2 | -------------------------------------------------------------------------------- /source/trigger_system/conditions/event_type_trigger_condition.gd: -------------------------------------------------------------------------------- 1 | extends TriggerCondition 2 | class_name EventTypeTriggerCondition 3 | 4 | var event_type : StringName 5 | 6 | func _init(config : Dictionary = {}) -> void: 7 | event_type = config.get("event_type", "") 8 | 9 | func evaluate(context: Dictionary) -> bool: 10 | return context.get("event_type", "") == event_type 11 | -------------------------------------------------------------------------------- /source/trigger_system/conditions/event_type_trigger_condition.gd.uid: -------------------------------------------------------------------------------- 1 | uid://d1b2andsf37c6 2 | -------------------------------------------------------------------------------- /source/trigger_system/conditions/state_trigger_condition.gd: -------------------------------------------------------------------------------- 1 | extends TriggerCondition 2 | class_name StateTriggerCondition 3 | 4 | ## 状态触发条件,条件示例 5 | 6 | ## 状态名称 7 | @export var state_name : StringName 8 | ## 状态值 9 | @export var required_state : StringName 10 | 11 | func _init(config : Dictionary = {}) -> void: 12 | state_name = config.get("state_name", "") 13 | required_state = config.get("required_state", "") 14 | 15 | func evaluate(context: Dictionary) -> bool: 16 | var p_state_name = context.get("state_name", "") 17 | var p_state_value = context.get("state_value", "") 18 | if state_name != p_state_name: 19 | return true 20 | return p_state_value == required_state 21 | -------------------------------------------------------------------------------- /source/trigger_system/conditions/state_trigger_condition.gd.uid: -------------------------------------------------------------------------------- 1 | uid://2q3mmslqdfp0 2 | -------------------------------------------------------------------------------- /source/trigger_system/gameplay_trigger.gd.uid: -------------------------------------------------------------------------------- 1 | uid://hcekyj1acusw 2 | -------------------------------------------------------------------------------- /source/trigger_system/trigger.gd.uid: -------------------------------------------------------------------------------- 1 | uid://b6pymbtaivkp4 2 | -------------------------------------------------------------------------------- /source/trigger_system/trigger_confition.gd: -------------------------------------------------------------------------------- 1 | extends Resource 2 | class_name TriggerCondition 3 | 4 | ## 触发条件 5 | func _init(config : Dictionary = {}) -> void: 6 | pass 7 | 8 | func evaluate(context: Dictionary) -> bool: 9 | return true 10 | -------------------------------------------------------------------------------- /source/trigger_system/trigger_confition.gd.uid: -------------------------------------------------------------------------------- 1 | uid://gs8ohgiqrg81 2 | -------------------------------------------------------------------------------- /source/trigger_system/trigger_manager.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | const SETTING_SCRIPT: Script = preload("../../setting.gd") 4 | const SETTING_TRIGGER_SYSTEM := SETTING_SCRIPT.SETTING_TRIGGER_SYSTEM 5 | const SETTING_SUBSCRIBE_EVENT_BUS := SETTING_TRIGGER_SYSTEM + "subscribe_event_bus" 6 | 7 | ## 触发器集 8 | @export_storage var _event_triggers : Dictionary[StringName, Array] ## 事件触发器 9 | @export_storage var _periodic_triggers : Array[GameplayTrigger] ## 周期触发器 10 | var _condition_types : Dictionary = { 11 | "composite_trigger_condition": CompositeTriggerCondition, 12 | "event_type_trigger_condition": EventTypeTriggerCondition, 13 | "state_trigger_condition": StateTriggerCondition, 14 | } 15 | 16 | var _event_bus : Node = CoreSystem.event_bus 17 | ## 是否订阅事件总线的事件 18 | var subscribe_event_bus : bool = false: 19 | get: 20 | return ProjectSettings.get_setting(SETTING_SUBSCRIBE_EVENT_BUS, true) 21 | 22 | signal triggered(trigger: GameplayTrigger, context: Dictionary) 23 | 24 | 25 | func _process(delta: float) -> void: 26 | for trigger : GameplayTrigger in _periodic_triggers: 27 | trigger.update(delta) 28 | 29 | 30 | ## 触发 31 | func handle_event(trigger_type: StringName, context: Dictionary) -> void: 32 | var triggers : Array = _event_triggers.get(trigger_type, []) 33 | if triggers.is_empty(): 34 | return 35 | for trigger : GameplayTrigger in triggers: 36 | trigger.execute(context) 37 | 38 | 39 | ## 添加触发器 40 | func register_event_trigger(trigger_type: StringName, trigger: GameplayTrigger) -> void: 41 | trigger.triggered.connect(_on_trigger_triggered.bind(trigger)) 42 | if not _event_triggers.has(trigger_type): 43 | if subscribe_event_bus: 44 | # 这里可以订阅事件总线的事件 45 | _event_bus.subscribe(trigger_type, _on_event_bus_trigger.bind(trigger_type)) 46 | _event_triggers[trigger_type] = [] 47 | _event_triggers[trigger_type].append(trigger) 48 | 49 | 50 | ## 移除触发器 51 | func unregister_event_trigger(trigger_type: StringName, trigger: GameplayTrigger) -> void: 52 | trigger.triggered.disconnect(_on_trigger_triggered.bind(trigger)) 53 | var triggers : Array[GameplayTrigger] = _event_triggers.get(trigger_type, []) 54 | if triggers.has(trigger): 55 | triggers.erase(trigger) 56 | if triggers.is_empty() and subscribe_event_bus: 57 | # 这里可以取消订阅事件总线的事件 58 | _event_bus.unsubscribe(trigger_type, _on_event_bus_trigger.bind(trigger_type)) 59 | 60 | 61 | ## 添加周期触发器 62 | func register_periodic_trigger(trigger: GameplayTrigger) -> void: 63 | _periodic_triggers.append(trigger) 64 | 65 | 66 | ## 移除周期触发器 67 | func unregister_periodic_trigger(trigger: GameplayTrigger) -> void: 68 | _periodic_triggers.erase(trigger) 69 | 70 | 71 | ## 注册限制器类型 72 | ## [param type] 限制器类型 73 | ## [param condition_class] 限制器类 74 | func register_condition_type(type: StringName, condition_class: GDScript) -> void: 75 | _condition_types[type] = condition_class 76 | 77 | 78 | ## 卸载限制器类型 79 | func unregister_condition_type(type: StringName) -> void: 80 | _condition_types.erase(type) 81 | 82 | 83 | ## 创建限制器 84 | func create_condition(config: Dictionary) -> TriggerCondition: 85 | var condition_type : StringName = config.get("type") 86 | if not _condition_types.has(condition_type): 87 | CoreSystem.logger.error("create condition by type: %s" % condition_type) 88 | return null 89 | return _condition_types[condition_type].new(config) 90 | 91 | 92 | func _on_trigger_triggered(context: Dictionary, trigger: GameplayTrigger) -> void: 93 | triggered.emit(trigger, context) 94 | 95 | 96 | func _on_event_bus_trigger(context: Dictionary, trigger_type: StringName) -> void: 97 | handle_event(trigger_type, context) 98 | -------------------------------------------------------------------------------- /source/trigger_system/trigger_manager.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cue22mxufbm23 2 | -------------------------------------------------------------------------------- /source/utils/async_io_manager.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bxou788k1eo5r 2 | -------------------------------------------------------------------------------- /source/utils/file_dir_handler.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | class_name FileDirHandler 3 | 4 | static func get_object_script_dir(object:Object)->String: 5 | return object.get_script().resource_path.get_base_dir() 6 | -------------------------------------------------------------------------------- /source/utils/file_dir_handler.gd.uid: -------------------------------------------------------------------------------- 1 | uid://byqbqmqj4l3k3 2 | -------------------------------------------------------------------------------- /source/utils/frame_splitter.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cr4dgwvg3xenx 2 | -------------------------------------------------------------------------------- /source/utils/io_strategies/compression/compression_strategy.gd: -------------------------------------------------------------------------------- 1 | extends RefCounted 2 | 3 | ## Abstract method to compress byte data. 4 | func compress(bytes: PackedByteArray) -> PackedByteArray: 5 | CoreSystem.logger.error("CompressionStrategy.compress() must be implemented by subclasses.") 6 | return PackedByteArray() 7 | 8 | ## Abstract method to decompress byte data. 9 | func decompress(bytes: PackedByteArray) -> PackedByteArray: 10 | CoreSystem.logger.error("CompressionStrategy.decompress() must be implemented by subclasses.") 11 | return PackedByteArray() -------------------------------------------------------------------------------- /source/utils/io_strategies/compression/compression_strategy.gd.uid: -------------------------------------------------------------------------------- 1 | uid://b87o2fbg3f6nl 2 | -------------------------------------------------------------------------------- /source/utils/io_strategies/compression/gzip_compression_strategy.gd: -------------------------------------------------------------------------------- 1 | extends "./compression_strategy.gd" 2 | class_name GzipCompressionStrategy 3 | 4 | # 压缩模式常量 5 | const COMPRESSION_MODE = FileAccess.COMPRESSION_GZIP 6 | 7 | # 估算解压缩缓冲区大小的乘数 (如果经常出错,需要调整) 8 | const DECOMPRESSION_BUFFER_MULTIPLIER = 10 9 | # 解压缩缓冲区最小大小 (防止估算为0) 10 | const MIN_DECOMPRESSION_BUFFER_SIZE = 1024 11 | 12 | 13 | 14 | ## 使用 Gzip 压缩字节数据 15 | func compress(bytes: PackedByteArray) -> PackedByteArray: 16 | if bytes.is_empty(): 17 | return bytes 18 | return bytes.compress(COMPRESSION_MODE) 19 | 20 | ## 使用 Gzip 解压缩字节数据 21 | func decompress(bytes: PackedByteArray) -> PackedByteArray: 22 | if bytes.is_empty(): 23 | return bytes 24 | 25 | # 估算解压缩后的大小。需要足够大,否则解压会失败或不完整。 26 | var estimated_size = bytes.size() * DECOMPRESSION_BUFFER_MULTIPLIER 27 | # 确保缓冲区大小至少为最小值 28 | estimated_size = max(estimated_size, MIN_DECOMPRESSION_BUFFER_SIZE) 29 | 30 | var decompressed_bytes = bytes.decompress(estimated_size, COMPRESSION_MODE) 31 | 32 | # 注意:Godot 3.x/4.x 的 decompress 在缓冲区不足或其他错误时 33 | # 可能只返回部分数据或空数组,而不会抛出异常。 34 | # 无法完美检测解压是否完全成功,只能依赖后续数据校验。 35 | if decompressed_bytes.is_empty() and not bytes.is_empty(): 36 | push_warning("Gzip Decompression resulted in empty bytes. Buffer size (%d) might be insufficient or data corrupted." % estimated_size) 37 | 38 | return decompressed_bytes 39 | 40 | -------------------------------------------------------------------------------- /source/utils/io_strategies/compression/gzip_compression_strategy.gd.uid: -------------------------------------------------------------------------------- 1 | uid://8pjkg08a13yv 2 | -------------------------------------------------------------------------------- /source/utils/io_strategies/compression/no_compression_strategy.gd: -------------------------------------------------------------------------------- 1 | extends "./compression_strategy.gd" 2 | 3 | ## Returns the input bytes without modification. 4 | func compress(bytes: PackedByteArray) -> PackedByteArray: 5 | return bytes 6 | 7 | ## Returns the input bytes without modification. 8 | func decompress(bytes: PackedByteArray) -> PackedByteArray: 9 | return bytes -------------------------------------------------------------------------------- /source/utils/io_strategies/compression/no_compression_strategy.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bxg6k8ovvg6f8 2 | -------------------------------------------------------------------------------- /source/utils/io_strategies/encryption/encryption_strategy.gd: -------------------------------------------------------------------------------- 1 | extends RefCounted 2 | 3 | ## Abstract method to encrypt byte data. 4 | ## [param bytes] The data to encrypt. 5 | ## [param key] The encryption key. 6 | func encrypt(bytes: PackedByteArray, key: PackedByteArray) -> PackedByteArray: 7 | CoreSystem.logger.error("EncryptionStrategy.encrypt() must be implemented by subclasses.") 8 | return PackedByteArray() 9 | 10 | ## Abstract method to decrypt byte data. 11 | ## [param bytes] The data to decrypt. 12 | ## [param key] The encryption key. 13 | func decrypt(bytes: PackedByteArray, key: PackedByteArray) -> PackedByteArray: 14 | CoreSystem.logger.error("EncryptionStrategy.decrypt() must be implemented by subclasses.") 15 | return PackedByteArray() 16 | -------------------------------------------------------------------------------- /source/utils/io_strategies/encryption/encryption_strategy.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c8jfrchmgdpxx 2 | -------------------------------------------------------------------------------- /source/utils/io_strategies/encryption/no_encryption_strategy.gd: -------------------------------------------------------------------------------- 1 | extends "./encryption_strategy.gd" 2 | 3 | ## Returns the input bytes without modification. 4 | func encrypt(bytes: PackedByteArray, _key: PackedByteArray) -> PackedByteArray: 5 | return bytes 6 | 7 | ## Returns the input bytes without modification. 8 | func decrypt(bytes: PackedByteArray, _key: PackedByteArray) -> PackedByteArray: 9 | return bytes -------------------------------------------------------------------------------- /source/utils/io_strategies/encryption/no_encryption_strategy.gd.uid: -------------------------------------------------------------------------------- 1 | uid://d4cnmiinsgqke 2 | -------------------------------------------------------------------------------- /source/utils/io_strategies/encryption/xor_encryption_strategy.gd: -------------------------------------------------------------------------------- 1 | extends "./encryption_strategy.gd" 2 | 3 | ## Simple XOR encryption/decryption. 4 | ## NOTE: This is NOT cryptographically secure and is for demonstration/basic obfuscation only. 5 | 6 | var _logger : CoreSystem.Logger = CoreSystem.logger 7 | 8 | func encrypt(bytes: PackedByteArray, key: PackedByteArray) -> PackedByteArray: 9 | if key.is_empty(): 10 | _logger.error("XOR encrypt: Empty key provided, returning original data.") 11 | return bytes 12 | 13 | var encrypted_bytes := PackedByteArray() 14 | encrypted_bytes.resize(bytes.size()) 15 | for i in range(bytes.size()): 16 | encrypted_bytes[i] = bytes[i] ^ key[i % key.size()] # XOR with cycling key 17 | return encrypted_bytes 18 | 19 | func decrypt(bytes: PackedByteArray, key: PackedByteArray) -> PackedByteArray: 20 | if key.is_empty(): 21 | _logger.error("XOR decrypt: Empty key provided, returning original data.") 22 | return bytes 23 | 24 | # XOR decryption is the same operation as encryption 25 | return encrypt(bytes, key) 26 | -------------------------------------------------------------------------------- /source/utils/io_strategies/encryption/xor_encryption_strategy.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dg6jyh4neowtv 2 | -------------------------------------------------------------------------------- /source/utils/io_strategies/serialization/json_serialization_strategy.gd: -------------------------------------------------------------------------------- 1 | extends "./serialization_strategy.gd" 2 | 3 | func serialize(data: Variant) -> PackedByteArray: 4 | var json_str := JSON.stringify(data, "\t", false) 5 | if json_str.is_empty() and data != null: 6 | CoreSystem.logger.error("Failed to serialize data to JSON: %s" % str(data)) 7 | return json_str.to_utf8_buffer() 8 | 9 | func deserialize(bytes: PackedByteArray) -> Variant: 10 | var json_str := bytes.get_string_from_utf8() 11 | if json_str.is_empty() and bytes.size() > 0: 12 | # Handle cases where non-utf8 bytes might result in empty string 13 | CoreSystem.logger.error("Could not decode bytes as UTF8 string for JSON deserialization.") 14 | return null 15 | if json_str.is_empty() and bytes.size() == 0: 16 | # Handle empty input gracefully (e.g., return empty dict or null) 17 | return null 18 | 19 | var json := JSON.new() 20 | var error := json.parse(json_str) 21 | if error == OK: 22 | return json.get_data() 23 | else: 24 | CoreSystem.logger.error("JSON parsing error: %s (Line: %d)" % [json.get_error_message(), json.get_error_line()]) 25 | return null 26 | -------------------------------------------------------------------------------- /source/utils/io_strategies/serialization/json_serialization_strategy.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c82hhuii62036 2 | -------------------------------------------------------------------------------- /source/utils/io_strategies/serialization/serialization_strategy.gd: -------------------------------------------------------------------------------- 1 | extends RefCounted 2 | 3 | ## Abstract method to serialize data into bytes. 4 | func serialize(data: Variant) -> PackedByteArray: 5 | CoreSystem.logger.error("SerializationStrategy.serialize() must be implemented by subclasses.") 6 | return PackedByteArray() 7 | 8 | ## Abstract method to deserialize bytes into data. 9 | func deserialize(bytes: PackedByteArray) -> Variant: 10 | CoreSystem.logger.error("SerializationStrategy.deserialize() must be implemented by subclasses.") 11 | return null -------------------------------------------------------------------------------- /source/utils/io_strategies/serialization/serialization_strategy.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cb4p47xeopkj5 2 | -------------------------------------------------------------------------------- /source/utils/random_picker.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bmhh1f08bk3d6 2 | -------------------------------------------------------------------------------- /source/utils/threading/module_thread.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c85n8ldaffjk6 2 | -------------------------------------------------------------------------------- /source/utils/threading/single_thread.gd: -------------------------------------------------------------------------------- 1 | extends RefCounted 2 | 3 | ## 简化的单线程工作器 4 | ## 实现基本的异步任务执行功能,用于验证线程测试 5 | 6 | signal task_completed(result, task_id) 7 | signal thread_finished() 8 | 9 | var _thread: Thread 10 | var _mutex: Mutex 11 | var _semaphore: Semaphore 12 | var _is_running: bool = true 13 | var _task_queue = [] 14 | var _task_id_counter = 0 15 | 16 | func _init(): 17 | _mutex = Mutex.new() 18 | _semaphore = Semaphore.new() 19 | _thread = Thread.new() 20 | _thread.start(_thread_function) 21 | 22 | func _notification(what): 23 | if what == NOTIFICATION_PREDELETE and is_instance_valid(self): 24 | stop() 25 | 26 | ## 添加任务到队列 27 | func add_task(task_func: Callable) -> int: 28 | _mutex.lock() 29 | var task_id = _task_id_counter 30 | _task_id_counter += 1 31 | _task_queue.append({"id": task_id, "func": task_func}) 32 | _mutex.unlock() 33 | 34 | _semaphore.post() 35 | return task_id 36 | 37 | ## 获取待处理任务数 38 | func get_pending_task_count() -> int: 39 | _mutex.lock() 40 | var count = _task_queue.size() 41 | _mutex.unlock() 42 | return count 43 | 44 | ## 停止线程 45 | func stop(): 46 | if not _thread or not _thread.is_alive(): 47 | return 48 | 49 | _mutex.lock() 50 | _is_running = false 51 | _mutex.unlock() 52 | 53 | _semaphore.post() # 唤醒线程以处理终止信号 54 | _thread.wait_to_finish() 55 | 56 | ## 工作线程主函数 57 | func _thread_function(): 58 | while _is_running: 59 | _semaphore.wait() 60 | 61 | if not _is_running: 62 | break 63 | 64 | var task = null 65 | _mutex.lock() 66 | if not _task_queue.is_empty(): 67 | task = _task_queue.pop_front() 68 | _mutex.unlock() 69 | 70 | if task: 71 | var result = null 72 | # 执行任务并获取结果 73 | result = task.func.call() 74 | 75 | # 使用 CallDeferred 安全地从主线程发出信号 76 | call_deferred("_emit_task_completed", result, task.id) 77 | 78 | CoreSystem.logger.info("线程已停止") 79 | 80 | ## 从主线程发出任务完成信号 81 | func _emit_task_completed(result, task_id): 82 | task_completed.emit(result, task_id) 83 | 84 | # 检查是否所有任务已完成 85 | _mutex.lock() 86 | var queue_empty : bool = _task_queue.is_empty() 87 | _mutex.unlock() 88 | 89 | if queue_empty: 90 | thread_finished.emit() 91 | -------------------------------------------------------------------------------- /source/utils/threading/single_thread.gd.uid: -------------------------------------------------------------------------------- 1 | uid://b1e514uc1thrq 2 | -------------------------------------------------------------------------------- /test/async_io/async_io_test.gd.uid: -------------------------------------------------------------------------------- 1 | uid://b08y8uiry6ouw 2 | -------------------------------------------------------------------------------- /test/async_io/async_io_test.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://bne3wj44oyn1n"] 2 | 3 | [ext_resource type="Script" uid="uid://b08y8uiry6ouw" path="res://addons/godot_core_system/test/async_io/async_io_test.gd" id="1_l8kbg"] 4 | 5 | [node name="AsyncIoTest" type="Control"] 6 | layout_mode = 3 7 | anchors_preset = 15 8 | anchor_right = 1.0 9 | anchor_bottom = 1.0 10 | grow_horizontal = 2 11 | grow_vertical = 2 12 | script = ExtResource("1_l8kbg") 13 | 14 | [node name="VBoxContainer" type="VBoxContainer" parent="."] 15 | layout_mode = 1 16 | anchors_preset = 15 17 | anchor_right = 1.0 18 | anchor_bottom = 1.0 19 | grow_horizontal = 2 20 | grow_vertical = 2 21 | 22 | [node name="LogLabel" type="RichTextLabel" parent="VBoxContainer"] 23 | custom_minimum_size = Vector2(0, 120) 24 | layout_mode = 2 25 | bbcode_enabled = true 26 | fit_content = true 27 | 28 | [node name="TestBasicBtn" type="Button" parent="VBoxContainer"] 29 | layout_mode = 2 30 | size_flags_horizontal = 4 31 | text = "基础测试 (读/写/删)" 32 | 33 | [node name="TestCompressBtn" type="Button" parent="VBoxContainer"] 34 | layout_mode = 2 35 | size_flags_horizontal = 4 36 | text = "压缩测试 (读/写/删)" 37 | 38 | [node name="TestEncryptBtn" type="Button" parent="VBoxContainer"] 39 | layout_mode = 2 40 | size_flags_horizontal = 4 41 | text = "加密测试 (读/写/删)" 42 | 43 | [node name="TestAllBtn" type="Button" parent="VBoxContainer"] 44 | layout_mode = 2 45 | size_flags_horizontal = 4 46 | text = "组合策略测试 (读/写/删)" 47 | 48 | [node name="ClearLogBtn" type="Button" parent="VBoxContainer"] 49 | layout_mode = 2 50 | size_flags_horizontal = 4 51 | text = "清除日志" 52 | -------------------------------------------------------------------------------- /test/event_bus_test.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dnso6ea5sux7x 2 | -------------------------------------------------------------------------------- /test/event_bus_test.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://ca3f45fq1yg3k"] 2 | 3 | [ext_resource type="Script" uid="uid://dnso6ea5sux7x" path="res://addons/godot_core_system/test/event_bus_test.gd" id="1_oc8wp"] 4 | 5 | [node name="EventBusTest" type="Node2D"] 6 | script = ExtResource("1_oc8wp") 7 | -------------------------------------------------------------------------------- /test/frame_splitter/frame_splitter_test.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | @onready var progress_bar = $ProgressBar 4 | @onready var fps_label = $FPSLabel 5 | @onready var time_label = $TimeLabel 6 | 7 | var frame_counter = 0 8 | var fps_update_time = 0.0 9 | var current_fps = 0 10 | var test_start_time = 0 11 | 12 | func _ready(): 13 | $NormalBtn.pressed.connect(_on_normal_test_pressed) 14 | $FrameSplitBtn.pressed.connect(_on_frame_split_test_pressed) 15 | 16 | func _process(delta): 17 | # 更新FPS显示 18 | frame_counter += 1 19 | fps_update_time += delta 20 | if fps_update_time >= 1.0: 21 | current_fps = frame_counter 22 | fps_label.text = "FPS: %d" % current_fps 23 | frame_counter = 0 24 | fps_update_time = 0.0 25 | 26 | ## 创建测试数据 27 | func _create_test_data() -> Array: 28 | var items = [] 29 | for i in range(1000000): # 100万个测试项 30 | items.append({ 31 | "id": i, 32 | "value": randf(), 33 | "name": "Item_%d" % i 34 | }) 35 | return items 36 | 37 | ## 处理单个测试项 38 | func _process_test_item(item: Dictionary) -> void: 39 | # 模拟复杂的处理逻辑 40 | var result = 0.0 41 | for j in range(5): # 增加每项的处理复杂度 42 | result += pow(sin(item.value * j), 2) + pow(cos(item.value * j), 2) 43 | result *= pow(2.0, sin(j)) + log(max(1, j)) 44 | 45 | # 字符串操作 46 | var str_result = "%s_%f" % [item.name, result] 47 | str_result = str_result.pad_zeros(20) 48 | 49 | ## 普通执行测试(不使用分帧) 50 | func _on_normal_test_pressed() -> void: 51 | progress_bar.value = 0 52 | time_label.text = "Time: 0ms" 53 | test_start_time = Time.get_ticks_msec() 54 | 55 | var items = _create_test_data() 56 | 57 | # 直接在一帧内处理所有数据 58 | for i in range(items.size()): 59 | _process_test_item(items[i]) 60 | if i % 1000 == 0: # 更新进度 61 | progress_bar.value = (float(i) / items.size()) * 100 62 | 63 | progress_bar.value = 100 64 | var total_time = Time.get_ticks_msec() - test_start_time 65 | time_label.text = "Time: %dms" % total_time 66 | 67 | ## 分帧执行测试 68 | func _on_frame_split_test_pressed() -> void: 69 | progress_bar.value = 0 70 | time_label.text = "Time: 0ms" 71 | test_start_time = Time.get_ticks_msec() 72 | 73 | var items = _create_test_data() 74 | 75 | # 创建分帧执行器(每帧最多执行10ms) 76 | var splitter = CoreSystem.FrameSplitter.new(100, 10.0) 77 | 78 | # 连接进度信号 79 | splitter.progress_changed.connect(func(p): progress_bar.value = p * 100) 80 | 81 | # 使用分帧执行器处理数据 82 | await splitter.process_array(items, _process_test_item) 83 | 84 | var total_time = Time.get_ticks_msec() - test_start_time 85 | time_label.text = "Time: %dms" % total_time 86 | -------------------------------------------------------------------------------- /test/frame_splitter/frame_splitter_test.gd.uid: -------------------------------------------------------------------------------- 1 | uid://darcwvrwiemfg 2 | -------------------------------------------------------------------------------- /test/frame_splitter/frame_splitter_test.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://riqfmggkqpfd"] 2 | 3 | [ext_resource type="Script" uid="uid://darcwvrwiemfg" path="res://addons/godot_core_system/test/frame_splitter/frame_splitter_test.gd" id="1_abcde"] 4 | 5 | [node name="FrameSplitterTest" type="Node2D"] 6 | script = ExtResource("1_abcde") 7 | 8 | [node name="NormalBtn" type="Button" parent="."] 9 | offset_left = 50.0 10 | offset_top = 50.0 11 | offset_right = 250.0 12 | offset_bottom = 100.0 13 | text = "普通执行测试" 14 | 15 | [node name="FrameSplitBtn" type="Button" parent="."] 16 | offset_left = 300.0 17 | offset_top = 50.0 18 | offset_right = 500.0 19 | offset_bottom = 100.0 20 | text = "分帧执行测试" 21 | 22 | [node name="ProgressBar" type="ProgressBar" parent="."] 23 | offset_left = 50.0 24 | offset_top = 150.0 25 | offset_right = 500.0 26 | offset_bottom = 180.0 27 | 28 | [node name="FPSLabel" type="Label" parent="."] 29 | offset_left = 50.0 30 | offset_top = 200.0 31 | offset_right = 200.0 32 | offset_bottom = 230.0 33 | text = "FPS: 60" 34 | 35 | [node name="TimeLabel" type="Label" parent="."] 36 | offset_left = 250.0 37 | offset_top = 200.0 38 | offset_right = 500.0 39 | offset_bottom = 230.0 40 | text = "Time: 0ms" 41 | -------------------------------------------------------------------------------- /test/random_picker/test_random_picker.gd.uid: -------------------------------------------------------------------------------- 1 | uid://btaff43gum3uu 2 | -------------------------------------------------------------------------------- /test/random_picker/test_random_picker.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://dhpwdvmwg41dx"] 2 | 3 | [ext_resource type="Script" uid="uid://btaff43gum3uu" path="res://addons/godot_core_system/test/random_picker/test_random_picker.gd" id="1_nscw4"] 4 | 5 | [node name="TestRandomPicker" type="Node2D"] 6 | script = ExtResource("1_nscw4") 7 | -------------------------------------------------------------------------------- /test/threading/threading_test.gd.uid: -------------------------------------------------------------------------------- 1 | uid://bop8d7pno8wt0 2 | -------------------------------------------------------------------------------- /test/threading/threading_test.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://cpxvndrjkspev"] 2 | 3 | [ext_resource type="Script" uid="uid://bop8d7pno8wt0" path="res://addons/godot_core_system/test/threading/threading_test.gd" id="1_n8wov"] 4 | 5 | [node name="ThreadingTest" type="Control"] 6 | layout_mode = 3 7 | anchors_preset = 15 8 | anchor_right = 1.0 9 | anchor_bottom = 1.0 10 | grow_horizontal = 2 11 | grow_vertical = 2 12 | script = ExtResource("1_n8wov") 13 | 14 | [node name="MarginContainer" type="MarginContainer" parent="."] 15 | layout_mode = 1 16 | anchors_preset = 15 17 | anchor_right = 1.0 18 | anchor_bottom = 1.0 19 | grow_horizontal = 2 20 | grow_vertical = 2 21 | theme_override_constants/margin_left = 20 22 | theme_override_constants/margin_top = 20 23 | theme_override_constants/margin_right = 20 24 | theme_override_constants/margin_bottom = 20 25 | 26 | [node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"] 27 | layout_mode = 2 28 | theme_override_constants/separation = 15 29 | 30 | [node name="Title" type="Label" parent="MarginContainer/VBoxContainer"] 31 | layout_mode = 2 32 | theme_override_font_sizes/font_size = 24 33 | text = "线程系统性能测试" 34 | horizontal_alignment = 1 35 | 36 | [node name="InfoLabel" type="Label" parent="MarginContainer/VBoxContainer"] 37 | layout_mode = 2 38 | text = "本测试比较在处理大量数据时,不同线程策略的性能差异 39 | 特别关注主线程的响应性和整体处理时间" 40 | horizontal_alignment = 1 41 | 42 | [node name="ButtonsContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] 43 | layout_mode = 2 44 | theme_override_constants/separation = 10 45 | alignment = 1 46 | 47 | [node name="SyncBtn" type="Button" parent="MarginContainer/VBoxContainer/ButtonsContainer"] 48 | custom_minimum_size = Vector2(150, 40) 49 | layout_mode = 2 50 | text = "同步执行测试" 51 | 52 | [node name="SingleThreadBtn" type="Button" parent="MarginContainer/VBoxContainer/ButtonsContainer"] 53 | custom_minimum_size = Vector2(150, 40) 54 | layout_mode = 2 55 | text = "单线程异步测试" 56 | 57 | [node name="ModuleThreadBtn" type="Button" parent="MarginContainer/VBoxContainer/ButtonsContainer"] 58 | custom_minimum_size = Vector2(150, 40) 59 | layout_mode = 2 60 | text = "模块线程多任务测试" 61 | 62 | [node name="ProgressContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer"] 63 | layout_mode = 2 64 | theme_override_constants/separation = 10 65 | 66 | [node name="ProgressBar" type="ProgressBar" parent="MarginContainer/VBoxContainer/ProgressContainer"] 67 | custom_minimum_size = Vector2(0, 25) 68 | layout_mode = 2 69 | size_flags_vertical = 4 70 | 71 | [node name="StatusContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] 72 | layout_mode = 2 73 | theme_override_constants/separation = 15 74 | alignment = 1 75 | 76 | [node name="FPSLabel" type="Label" parent="MarginContainer/VBoxContainer/StatusContainer"] 77 | layout_mode = 2 78 | text = "FPS: 0" 79 | 80 | [node name="TimeLabel" type="Label" parent="MarginContainer/VBoxContainer/StatusContainer"] 81 | layout_mode = 2 82 | text = "时间: 0ms" 83 | 84 | [node name="StatusLabel" type="Label" parent="MarginContainer/VBoxContainer/StatusContainer"] 85 | layout_mode = 2 86 | text = "状态: 空闲" 87 | 88 | [node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/VBoxContainer"] 89 | layout_mode = 2 90 | size_flags_vertical = 3 91 | 92 | [node name="ResultsContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/ScrollContainer"] 93 | layout_mode = 2 94 | size_flags_horizontal = 3 95 | size_flags_vertical = 3 96 | 97 | [connection signal="pressed" from="MarginContainer/VBoxContainer/ButtonsContainer/SingleThreadBtn" to="." method="_on_single_thread_btn_pressed"] 98 | -------------------------------------------------------------------------------- /test/unit/test_event_bus.gd.uid: -------------------------------------------------------------------------------- 1 | uid://ty3jhb0t60wq 2 | --------------------------------------------------------------------------------