From 9742bf21fa22742873451a04812a9a9ae5f7d774 Mon Sep 17 00:00:00 2001 From: janing <1175861874@qq.com> Date: Fri, 28 Nov 2025 18:10:10 +0800 Subject: [PATCH] =?UTF-8?q?cocos=E5=9F=BA=E7=A1=80=E5=B7=A5=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...tom Script Template Help Documentation.url | 2 + client-cocos/.creator/default-meta.json | 5 + client-cocos/.gitignore | 24 + client-cocos/BUILD_PHYSICS_GUIDE.md | 94 ++++ client-cocos/assets/res.meta | 9 + client-cocos/assets/res/Box.prefab | 3 + client-cocos/assets/res/Box.prefab.meta | 13 + client-cocos/assets/res/Circle.prefab | 3 + client-cocos/assets/res/Circle.prefab.meta | 13 + client-cocos/assets/res/Table.prefab | 3 + client-cocos/assets/res/Table.prefab.meta | 13 + client-cocos/assets/scenes.meta | 9 + .../assets/scenes/client-multiplayer.scene | 3 + .../scenes/client-multiplayer.scene.meta | 11 + .../assets/scenes/client-standalone.scene | 3 + .../scenes/client-standalone.scene.meta | 11 + .../assets/scenes/server-multiplayer.scene | 3 + .../scenes/server-multiplayer.scene.meta | 11 + client-cocos/assets/scripts.meta | 9 + client-cocos/assets/scripts/App.meta | 9 + .../assets/scripts/App/BasicGeometry.ts | 81 ++++ .../assets/scripts/App/BasicGeometry.ts.meta | 9 + .../assets/scripts/App/ClientRunner.ts | 218 ++++++++++ .../assets/scripts/App/ClientRunner.ts.meta | 9 + client-cocos/assets/scripts/Modules.meta | 9 + .../assets/scripts/Modules/Pinball.meta | 9 + .../assets/scripts/Modules/Pinball/Boot.meta | 9 + .../Modules/Pinball/Boot/BaseBooter.ts | 84 ++++ .../Modules/Pinball/Boot/BaseBooter.ts.meta | 9 + .../scripts/Modules/Pinball/Boot/BootTypes.ts | 70 +++ .../Modules/Pinball/Boot/BootTypes.ts.meta | 9 + .../scripts/Modules/Pinball/Boot/Mode.meta | 9 + .../Boot/Mode/ClientMultiplayerBooter.ts | 22 + .../Boot/Mode/ClientMultiplayerBooter.ts.meta | 9 + .../Boot/Mode/ServerMultiplayerBooter.ts | 22 + .../Boot/Mode/ServerMultiplayerBooter.ts.meta | 9 + .../Pinball/Boot/Mode/StandaloneBooter.ts | 42 ++ .../Boot/Mode/StandaloneBooter.ts.meta | 9 + .../Modules/Pinball/Boot/PinballBootUtils.ts | 186 ++++++++ .../Pinball/Boot/PinballBootUtils.ts.meta | 9 + .../Modules/Pinball/Boot/PinballBootstrap.ts | 116 +++++ .../Pinball/Boot/PinballBootstrap.ts.meta | 9 + .../scripts/Modules/Pinball/Boot/index.ts | 12 + .../Modules/Pinball/Boot/index.ts.meta | 9 + .../assets/scripts/Modules/Pinball/Core.meta | 9 + .../scripts/Modules/Pinball/Core/EventBus.ts | 88 ++++ .../Modules/Pinball/Core/EventBus.ts.meta | 9 + .../scripts/Modules/Pinball/Core/GameData.ts | 74 ++++ .../Modules/Pinball/Core/GameData.ts.meta | 9 + .../Modules/Pinball/Core/IPhysicsEngine.ts | 116 +++++ .../Pinball/Core/IPhysicsEngine.ts.meta | 9 + .../scripts/Modules/Pinball/Core/IRenderer.ts | 86 ++++ .../Modules/Pinball/Core/IRenderer.ts.meta | 9 + .../scripts/Modules/Pinball/GameModes.meta | 9 + .../Pinball/GameModes/StandaloneMode.ts | 358 +++++++++++++++ .../Pinball/GameModes/StandaloneMode.ts.meta | 9 + .../assets/scripts/Modules/Pinball/Input.meta | 9 + .../Modules/Pinball/Input/InputManager.ts | 385 ++++++++++++++++ .../Pinball/Input/InputManager.ts.meta | 9 + .../Modules/Pinball/Input/InputTypes.ts | 71 +++ .../Modules/Pinball/Input/InputTypes.ts.meta | 9 + .../scripts/Modules/Pinball/Network.meta | 9 + .../scripts/Modules/Pinball/Physics.meta | 9 + .../Modules/Pinball/Physics/PhysicsTypes.ts | 75 ++++ .../Pinball/Physics/PhysicsTypes.ts.meta | 9 + .../Pinball/Physics/WasmPhysicsEngine.ts | 411 ++++++++++++++++++ .../scripts/Modules/Pinball/PinballManager.ts | 400 +++++++++++++++++ .../Modules/Pinball/PinballManager.ts.meta | 9 + .../scripts/Modules/Pinball/Renderer.meta | 9 + .../Pinball/Renderer/PinballRenderer.ts | 292 +++++++++++++ .../Pinball/Renderer/PinballRenderer.ts.meta | 9 + client-cocos/assets/scripts/Utils.meta | 9 + .../assets/scripts/Utils/WasmLoader.ts | 321 ++++++++++++++ .../assets/scripts/Utils/WasmLoader.ts.meta | 9 + .../assets/scripts/Utils/WasmLoaderExample.ts | 128 ++++++ .../scripts/Utils/WasmLoaderExample.ts.meta | 9 + client-cocos/assets/wasm.meta | 11 + client-cocos/assets/wasm/pinball_physics.wasm | 3 + .../assets/wasm/pinball_physics.wasm.meta | 12 + client-cocos/build-physics.bat | 193 ++++++++ client-cocos/package.json | 7 + .../settings/v2/packages/builder.json | 3 + client-cocos/settings/v2/packages/device.json | 3 + client-cocos/settings/v2/packages/engine.json | 150 +++++++ .../settings/v2/packages/information.json | 23 + .../settings/v2/packages/program.json | 3 + .../settings/v2/packages/project.json | 3 + client-cocos/tsconfig.json | 9 + 88 files changed, 4626 insertions(+) create mode 100644 client-cocos/.creator/asset-template/typescript/Custom Script Template Help Documentation.url create mode 100644 client-cocos/.creator/default-meta.json create mode 100644 client-cocos/.gitignore create mode 100644 client-cocos/BUILD_PHYSICS_GUIDE.md create mode 100644 client-cocos/assets/res.meta create mode 100644 client-cocos/assets/res/Box.prefab create mode 100644 client-cocos/assets/res/Box.prefab.meta create mode 100644 client-cocos/assets/res/Circle.prefab create mode 100644 client-cocos/assets/res/Circle.prefab.meta create mode 100644 client-cocos/assets/res/Table.prefab create mode 100644 client-cocos/assets/res/Table.prefab.meta create mode 100644 client-cocos/assets/scenes.meta create mode 100644 client-cocos/assets/scenes/client-multiplayer.scene create mode 100644 client-cocos/assets/scenes/client-multiplayer.scene.meta create mode 100644 client-cocos/assets/scenes/client-standalone.scene create mode 100644 client-cocos/assets/scenes/client-standalone.scene.meta create mode 100644 client-cocos/assets/scenes/server-multiplayer.scene create mode 100644 client-cocos/assets/scenes/server-multiplayer.scene.meta create mode 100644 client-cocos/assets/scripts.meta create mode 100644 client-cocos/assets/scripts/App.meta create mode 100644 client-cocos/assets/scripts/App/BasicGeometry.ts create mode 100644 client-cocos/assets/scripts/App/BasicGeometry.ts.meta create mode 100644 client-cocos/assets/scripts/App/ClientRunner.ts create mode 100644 client-cocos/assets/scripts/App/ClientRunner.ts.meta create mode 100644 client-cocos/assets/scripts/Modules.meta create mode 100644 client-cocos/assets/scripts/Modules/Pinball.meta create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Boot.meta create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Boot/BaseBooter.ts create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Boot/BaseBooter.ts.meta create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Boot/BootTypes.ts create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Boot/BootTypes.ts.meta create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Boot/Mode.meta create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Boot/Mode/ClientMultiplayerBooter.ts create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Boot/Mode/ClientMultiplayerBooter.ts.meta create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Boot/Mode/ServerMultiplayerBooter.ts create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Boot/Mode/ServerMultiplayerBooter.ts.meta create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Boot/Mode/StandaloneBooter.ts create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Boot/Mode/StandaloneBooter.ts.meta create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Boot/PinballBootUtils.ts create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Boot/PinballBootUtils.ts.meta create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Boot/PinballBootstrap.ts create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Boot/PinballBootstrap.ts.meta create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Boot/index.ts create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Boot/index.ts.meta create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Core.meta create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Core/EventBus.ts create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Core/EventBus.ts.meta create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Core/GameData.ts create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Core/GameData.ts.meta create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Core/IPhysicsEngine.ts create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Core/IPhysicsEngine.ts.meta create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Core/IRenderer.ts create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Core/IRenderer.ts.meta create mode 100644 client-cocos/assets/scripts/Modules/Pinball/GameModes.meta create mode 100644 client-cocos/assets/scripts/Modules/Pinball/GameModes/StandaloneMode.ts create mode 100644 client-cocos/assets/scripts/Modules/Pinball/GameModes/StandaloneMode.ts.meta create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Input.meta create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Input/InputManager.ts create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Input/InputManager.ts.meta create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Input/InputTypes.ts create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Input/InputTypes.ts.meta create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Network.meta create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Physics.meta create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Physics/PhysicsTypes.ts create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Physics/PhysicsTypes.ts.meta create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Physics/WasmPhysicsEngine.ts create mode 100644 client-cocos/assets/scripts/Modules/Pinball/PinballManager.ts create mode 100644 client-cocos/assets/scripts/Modules/Pinball/PinballManager.ts.meta create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Renderer.meta create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Renderer/PinballRenderer.ts create mode 100644 client-cocos/assets/scripts/Modules/Pinball/Renderer/PinballRenderer.ts.meta create mode 100644 client-cocos/assets/scripts/Utils.meta create mode 100644 client-cocos/assets/scripts/Utils/WasmLoader.ts create mode 100644 client-cocos/assets/scripts/Utils/WasmLoader.ts.meta create mode 100644 client-cocos/assets/scripts/Utils/WasmLoaderExample.ts create mode 100644 client-cocos/assets/scripts/Utils/WasmLoaderExample.ts.meta create mode 100644 client-cocos/assets/wasm.meta create mode 100644 client-cocos/assets/wasm/pinball_physics.wasm create mode 100644 client-cocos/assets/wasm/pinball_physics.wasm.meta create mode 100644 client-cocos/build-physics.bat create mode 100644 client-cocos/package.json create mode 100644 client-cocos/settings/v2/packages/builder.json create mode 100644 client-cocos/settings/v2/packages/device.json create mode 100644 client-cocos/settings/v2/packages/engine.json create mode 100644 client-cocos/settings/v2/packages/information.json create mode 100644 client-cocos/settings/v2/packages/program.json create mode 100644 client-cocos/settings/v2/packages/project.json create mode 100644 client-cocos/tsconfig.json diff --git a/client-cocos/.creator/asset-template/typescript/Custom Script Template Help Documentation.url b/client-cocos/.creator/asset-template/typescript/Custom Script Template Help Documentation.url new file mode 100644 index 0000000..7606df0 --- /dev/null +++ b/client-cocos/.creator/asset-template/typescript/Custom Script Template Help Documentation.url @@ -0,0 +1,2 @@ +[InternetShortcut] +URL=https://docs.cocos.com/creator/manual/en/scripting/setup.html#custom-script-template \ No newline at end of file diff --git a/client-cocos/.creator/default-meta.json b/client-cocos/.creator/default-meta.json new file mode 100644 index 0000000..b94d8fd --- /dev/null +++ b/client-cocos/.creator/default-meta.json @@ -0,0 +1,5 @@ +{ + "image": { + "type": "sprite-frame" + } +} diff --git a/client-cocos/.gitignore b/client-cocos/.gitignore new file mode 100644 index 0000000..a231b3f --- /dev/null +++ b/client-cocos/.gitignore @@ -0,0 +1,24 @@ + +#/////////////////////////// +# Cocos Creator 3D Project +#/////////////////////////// +library/ +temp/ +local/ +build/ +profiles/ +native +#////////////////////////// +# NPM +#////////////////////////// +node_modules/ + +#////////////////////////// +# VSCode +#////////////////////////// +.vscode/ + +#////////////////////////// +# WebStorm +#////////////////////////// +.idea/ \ No newline at end of file diff --git a/client-cocos/BUILD_PHYSICS_GUIDE.md b/client-cocos/BUILD_PHYSICS_GUIDE.md new file mode 100644 index 0000000..3e42fba --- /dev/null +++ b/client-cocos/BUILD_PHYSICS_GUIDE.md @@ -0,0 +1,94 @@ +# Pinball Physics 构建指南 + +这个脚本用于构建物理引擎并生成客户端所需的 WASM 和 asm.js 文件。 + +## 使用方法 + +```batch +# 在 client-cocos 目录下运行 + +# 构建 debug 版本(默认) +build-physics.bat + +# 或显式指定 debug +build-physics.bat debug + +# 构建 release 版本(推荐用于生产环境) +build-physics.bat release +``` + +## 构建流程 + +1. **编译物理引擎** + - 进入 `../pinball-physics` 目录 + - 执行 `cargo build --target wasm32-unknown-unknown [--release]` + +2. **拷贝 WASM 文件** + - 源文件: `../pinball-physics/target/wasm32-unknown-unknown/[模式]/pinball_physics.wasm` + - 目标文件: `assets/wasm/pinball_physics.bin` + +3. **生成 asm.js** + - 使用 `../tools/emscripten/gen-asm.bat` 工具 + - 输出: `assets/wasm/pinball_physics.js` + +## 输出文件 + +构建完成后,会在 `assets/wasm/` 目录下生成: + +- **pinball_physics.bin** - WebAssembly 二进制文件 +- **pinball_physics.js** - asm.js 文件(用于兼容性) + +## 前置要求 + +1. **Rust 环境** + ```batch + # 安装 Rust + # 访问 https://rustup.rs/ + + # 添加 WebAssembly 目标 + rustup target add wasm32-unknown-unknown + ``` + +2. **Emscripten 环境** + ```batch + # 安装 Emscripten + tools\emscripten\install.bat + ``` + +## 故障排除 + +### 常见错误 + +1. **未找到 Rust 编译器** + - 确保已安装 Rust + - 检查 PATH 环境变量 + +2. **未找到物理引擎项目** + - 确保在正确的目录运行脚本 + - 检查 `../pinball-physics/Cargo.toml` 是否存在 + +3. **Emscripten 转换失败** + - 确保已运行 `tools\emscripten\install.bat` + - 检查 Emscripten 环境是否正确设置 + +4. **权限错误** + - 确保对输出目录有写权限 + - 尝试以管理员身份运行 + +### 调试技巧 + +- 查看详细的错误信息和文件路径 +- 检查各个步骤的输出文件是否正确生成 +- 验证文件大小是否合理 + +## 使用建议 + +1. **开发阶段**: 使用 `debug` 模式,编译速度更快 +2. **生产环境**: 使用 `release` 模式,性能更好,文件更小 +3. **兼容性**: asm.js 文件主要用于不支持 WebAssembly 的老旧浏览器 + +## 相关文件 + +- `../pinball-physics/` - 物理引擎 Rust 项目 +- `../tools/emscripten/` - Emscripten 工具集 +- `assets/wasm/` - 输出目录 \ No newline at end of file diff --git a/client-cocos/assets/res.meta b/client-cocos/assets/res.meta new file mode 100644 index 0000000..51dc089 --- /dev/null +++ b/client-cocos/assets/res.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.2.0", + "importer": "directory", + "imported": true, + "uuid": "8435224a-7b06-4067-8889-ef1344d30105", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/res/Box.prefab b/client-cocos/assets/res/Box.prefab new file mode 100644 index 0000000..d9f05f9 --- /dev/null +++ b/client-cocos/assets/res/Box.prefab @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd0e25f70b9cecfe4e1aaf74125c2b6e1d48eca01fcef8c8d72e22f7646cb7e8 +size 4471 diff --git a/client-cocos/assets/res/Box.prefab.meta b/client-cocos/assets/res/Box.prefab.meta new file mode 100644 index 0000000..292730e --- /dev/null +++ b/client-cocos/assets/res/Box.prefab.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.49", + "importer": "prefab", + "imported": true, + "uuid": "eb6934d9-5e1d-45c5-92f1-2b10e983ed24", + "files": [ + ".json" + ], + "subMetas": {}, + "userData": { + "syncNodeName": "Box" + } +} diff --git a/client-cocos/assets/res/Circle.prefab b/client-cocos/assets/res/Circle.prefab new file mode 100644 index 0000000..794d7b2 --- /dev/null +++ b/client-cocos/assets/res/Circle.prefab @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba4f12c5e5e2adc2005809f4378de61565ff035503e914fcddcbaa019e43c040 +size 4477 diff --git a/client-cocos/assets/res/Circle.prefab.meta b/client-cocos/assets/res/Circle.prefab.meta new file mode 100644 index 0000000..0edf3a5 --- /dev/null +++ b/client-cocos/assets/res/Circle.prefab.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.49", + "importer": "prefab", + "imported": true, + "uuid": "28d6731f-90bb-49d6-a041-f8648d28269c", + "files": [ + ".json" + ], + "subMetas": {}, + "userData": { + "syncNodeName": "Circle" + } +} diff --git a/client-cocos/assets/res/Table.prefab b/client-cocos/assets/res/Table.prefab new file mode 100644 index 0000000..5ec46c6 --- /dev/null +++ b/client-cocos/assets/res/Table.prefab @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:52b42a87ee726832e1b6223f172513ead5444113410ba98418514341ba93afc4 +size 4475 diff --git a/client-cocos/assets/res/Table.prefab.meta b/client-cocos/assets/res/Table.prefab.meta new file mode 100644 index 0000000..a8550d0 --- /dev/null +++ b/client-cocos/assets/res/Table.prefab.meta @@ -0,0 +1,13 @@ +{ + "ver": "1.1.49", + "importer": "prefab", + "imported": true, + "uuid": "70ac2e4f-8335-49ee-8f56-d7e5478988b8", + "files": [ + ".json" + ], + "subMetas": {}, + "userData": { + "syncNodeName": "Table" + } +} diff --git a/client-cocos/assets/scenes.meta b/client-cocos/assets/scenes.meta new file mode 100644 index 0000000..d99ff1e --- /dev/null +++ b/client-cocos/assets/scenes.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.2.0", + "importer": "directory", + "imported": true, + "uuid": "d93688f7-8f37-4210-b0cf-5e14068a2856", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scenes/client-multiplayer.scene b/client-cocos/assets/scenes/client-multiplayer.scene new file mode 100644 index 0000000..30c09ae --- /dev/null +++ b/client-cocos/assets/scenes/client-multiplayer.scene @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:816471aba2282afc546b1d3d150087851e2ef6334e33447c964a123517f2ec65 +size 11649 diff --git a/client-cocos/assets/scenes/client-multiplayer.scene.meta b/client-cocos/assets/scenes/client-multiplayer.scene.meta new file mode 100644 index 0000000..daa47cd --- /dev/null +++ b/client-cocos/assets/scenes/client-multiplayer.scene.meta @@ -0,0 +1,11 @@ +{ + "ver": "1.1.49", + "importer": "scene", + "imported": true, + "uuid": "104ca2d7-7d95-41ab-a6a9-9218131c1c0e", + "files": [ + ".json" + ], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scenes/client-standalone.scene b/client-cocos/assets/scenes/client-standalone.scene new file mode 100644 index 0000000..ebf065d --- /dev/null +++ b/client-cocos/assets/scenes/client-standalone.scene @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65e4a5c0330b08ccab94d2bf742855c70f279dc8f84c52f754f53e2b8cec0bf0 +size 11647 diff --git a/client-cocos/assets/scenes/client-standalone.scene.meta b/client-cocos/assets/scenes/client-standalone.scene.meta new file mode 100644 index 0000000..0cac8aa --- /dev/null +++ b/client-cocos/assets/scenes/client-standalone.scene.meta @@ -0,0 +1,11 @@ +{ + "ver": "1.1.49", + "importer": "scene", + "imported": true, + "uuid": "d40eeb4c-286e-45ba-8c1e-b566914a4c0f", + "files": [ + ".json" + ], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scenes/server-multiplayer.scene b/client-cocos/assets/scenes/server-multiplayer.scene new file mode 100644 index 0000000..bf02637 --- /dev/null +++ b/client-cocos/assets/scenes/server-multiplayer.scene @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b28ab664a2d5fadf805017d4e8789c990b87687f462d8d1a254df68c4e1c3511 +size 11649 diff --git a/client-cocos/assets/scenes/server-multiplayer.scene.meta b/client-cocos/assets/scenes/server-multiplayer.scene.meta new file mode 100644 index 0000000..cb50e20 --- /dev/null +++ b/client-cocos/assets/scenes/server-multiplayer.scene.meta @@ -0,0 +1,11 @@ +{ + "ver": "1.1.49", + "importer": "scene", + "imported": true, + "uuid": "7b9f26e0-5d5a-4124-bc21-087790bc8aac", + "files": [ + ".json" + ], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts.meta b/client-cocos/assets/scripts.meta new file mode 100644 index 0000000..e761261 --- /dev/null +++ b/client-cocos/assets/scripts.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.2.0", + "importer": "directory", + "imported": true, + "uuid": "67d29da8-68a1-4b50-9e7c-e2883d977ac6", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/App.meta b/client-cocos/assets/scripts/App.meta new file mode 100644 index 0000000..d45ceb2 --- /dev/null +++ b/client-cocos/assets/scripts/App.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.2.0", + "importer": "directory", + "imported": true, + "uuid": "bab444c8-af0d-4a8b-ac9b-967b84f9f626", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/App/BasicGeometry.ts b/client-cocos/assets/scripts/App/BasicGeometry.ts new file mode 100644 index 0000000..1dfef17 --- /dev/null +++ b/client-cocos/assets/scripts/App/BasicGeometry.ts @@ -0,0 +1,81 @@ +import { _decorator, Color, Component, Node, Size, Sprite, UITransform } from 'cc'; +const { ccclass, property } = _decorator; + +@ccclass('BasicGeometry') +export class BasicGeometry extends Component { + private spriteNode: Node = null; + private spriteComponent: Sprite = null; + private uiTransform: UITransform = null; + + start() { + // 获取名为"Sprite"的子节点 + this.spriteNode = this.node.getChildByName("Sprite"); + if (this.spriteNode) { + this.spriteComponent = this.spriteNode.getComponent(Sprite); + this.uiTransform = this.spriteNode.getComponent(UITransform); + } + + if (!this.spriteNode || !this.spriteComponent) { + console.warn("BasicGeometry: 未找到名为'Sprite'的子节点或Sprite组件"); + } + + if (!this.uiTransform) { + console.warn("BasicGeometry: 未找到UITransform组件"); + } + } + + update(deltaTime: number) { + + } + + /** + * 设置精灵的大小 + * @param width 宽度 + * @param height 高度 + */ + public SetSize(width: number, height: number): void { + if (!this.uiTransform) { + console.warn("BasicGeometry: UITransform组件不存在,无法设置大小"); + return; + } + + this.uiTransform.setContentSize(new Size(width, height)); + } + + /** + * 设置精灵的颜色 + * @param color 颜色值 + */ + public SetColor(color: Color): void { + if (!this.spriteComponent) { + console.warn("BasicGeometry: Sprite组件不存在,无法设置颜色"); + return; + } + + this.spriteComponent.color = color; + } + + /** + * 获取当前精灵的大小 + * @returns 返回当前大小,如果组件不存在则返回null + */ + public GetSize(): Size | null { + if (!this.uiTransform) { + return null; + } + return this.uiTransform.contentSize; + } + + /** + * 获取当前精灵的颜色 + * @returns 返回当前颜色,如果组件不存在则返回null + */ + public GetColor(): Color | null { + if (!this.spriteComponent) { + return null; + } + return this.spriteComponent.color; + } +} + + diff --git a/client-cocos/assets/scripts/App/BasicGeometry.ts.meta b/client-cocos/assets/scripts/App/BasicGeometry.ts.meta new file mode 100644 index 0000000..5d9e3c4 --- /dev/null +++ b/client-cocos/assets/scripts/App/BasicGeometry.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "257b98d4-8ead-4135-bf08-c0b7e099b1b5", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/App/ClientRunner.ts b/client-cocos/assets/scripts/App/ClientRunner.ts new file mode 100644 index 0000000..ae1e824 --- /dev/null +++ b/client-cocos/assets/scripts/App/ClientRunner.ts @@ -0,0 +1,218 @@ +import { _decorator, Component, Enum, Node } from 'cc'; +import { PinballBootConfig, PinballBootMode, PinballBootResult, PinballBootstrap } from '../Modules/Pinball/Boot'; +import { PinballManager } from '../Modules/Pinball/PinballManager'; +const { ccclass, property } = _decorator; + +// 定义模式枚举 +export enum RunMode { + STANDALONE = 0, + CLIENT_MULTIPLAYER = 1, + SERVER_MULTIPLAYER = 2 +} + +// 将枚举注册到Cocos Creator +Enum(RunMode); + +// RunMode 到字符串的映射 +const RunModeStrings = { + [RunMode.STANDALONE]: 'standalone', + [RunMode.CLIENT_MULTIPLAYER]: 'client-multiplayer', + [RunMode.SERVER_MULTIPLAYER]: 'server-multiplayer' +} as const; + +// RunMode 到 PinballBootMode 的映射 +const RunModeToPinballBootMode = { + [RunMode.STANDALONE]: PinballBootMode.STANDALONE, + [RunMode.CLIENT_MULTIPLAYER]: PinballBootMode.CLIENT_MULTIPLAYER, + [RunMode.SERVER_MULTIPLAYER]: PinballBootMode.SERVER_MULTIPLAYER +} as const; + +@ccclass('ClientRunner') +export class ClientRunner extends Component { + + @property({ + type: Enum(RunMode), + displayName: "运行模式", + tooltip: '选择运行模式:单机/客户端多人/服务器多人' + }) + public mode: RunMode = RunMode.STANDALONE; + + @property({ + type: Node, + displayName: "主相机节点", + tooltip: '主相机节点,用于渲染游戏画面' + }) + public cameraNode: Node = null; + + @property({ + type: Node, + displayName: "渲染容器", + tooltip: '渲染容器节点,所有游戏对象将在此节点下渲染' + }) + public renderContainer: Node = null; + + @property({ + type: Node, + displayName: "UI容器", + tooltip: 'UI容器节点,用于显示游戏UI界面' + }) + public uiContainer: Node = null; + + private pinballManager: PinballManager = null; + private bootResult: PinballBootResult = null; + + async start() { + console.log(`[ClientRunner] 开始启动,运行模式: ${this.getModeString()}`); + + // 使用 Bootstrap 启动游戏 + await this.bootWithBootstrap(); + } + + /** + * 使用 Bootstrap 启动游戏 + */ + private async bootWithBootstrap(): Promise { + try { + // 创建启动配置 + const config = this.createBootConfig(); + + // 使用 Bootstrap 启动 + const bootstrap = PinballBootstrap.getInstance(); + this.bootResult = await bootstrap.boot(this, config); + + if (this.bootResult.success) { + this.pinballManager = this.bootResult.pinballManager; + console.log(`[ClientRunner] 使用 Bootstrap 启动成功: ${this.bootResult.mode}`); + } else { + console.error(`[ClientRunner] Bootstrap 启动失败: ${this.bootResult.error}`); + this.handleBootFailure(this.bootResult); + } + + } catch (error) { + console.error('[ClientRunner] Bootstrap 启动异常:', error); + } + } + + /** + * 创建启动配置 + */ + private createBootConfig(): PinballBootConfig { + // 将 RunMode 转换为 PinballBootMode + const bootMode = this.convertRunModeToPinballBootMode(this.mode); + + // 使用默认配置并设置节点引用 + const config = PinballBootstrap.createDefaultConfig(bootMode); + config.cameraNode = this.cameraNode; + config.renderContainer = this.renderContainer; + config.uiContainer = this.uiContainer; + + // 根据模式添加特定配置 + if (bootMode === PinballBootMode.CLIENT_MULTIPLAYER || + bootMode === PinballBootMode.SERVER_MULTIPLAYER) { + config.multiplayerConfig = { + serverAddress: 'localhost:3000', // 默认服务器地址 + playerName: 'Player1', + roomId: 'default' + }; + } + + return config; + } + + /** + * 转换 RunMode 到 PinballBootMode + */ + private convertRunModeToPinballBootMode(runMode: RunMode): PinballBootMode { + return RunModeToPinballBootMode[runMode] ?? PinballBootMode.STANDALONE; + } + + /** + * 处理启动失败 + */ + private handleBootFailure(result: PinballBootResult): void { + console.error(`[ClientRunner] 启动失败 - 模式: ${result.mode}, 错误: ${result.error}`); + + // 可以在这里添加错误恢复逻辑,比如回退到 Standalone 模式 + if (result.mode !== PinballBootMode.STANDALONE) { + console.log('[ClientRunner] 尝试回退到 Standalone 模式...'); + setTimeout(async () => { + this.mode = RunMode.STANDALONE; + await this.bootWithBootstrap(); + }, 1000); + } + } + + update(deltaTime: number) { + + } + + onDestroy() { + // 清理 PinballManager + if (this.pinballManager) { + this.pinballManager = null; + } + } + + /** + * 获取 PinballManager 实例 + */ + public getPinballManager(): PinballManager | null { + return this.pinballManager; + } + + /** + * 重启当前模式 + */ + public async restart(): Promise { + console.log('[ClientRunner] 重启游戏...'); + + if (this.pinballManager) { + await this.pinballManager.restartGame(); + } else { + // 如果没有 PinballManager,重新执行启动流程 + await this.bootWithBootstrap(); + } + } + + /** + * 切换模式并重启 + */ + public async switchMode(newMode: RunMode): Promise { + console.log(`[ClientRunner] 切换模式从 ${this.getModeString()} 到 ${this.getModeStringForMode(newMode)}`); + + // 停止当前游戏 + if (this.pinballManager) { + await this.pinballManager.stopCurrentGame(); + } + + // 设置新模式 + this.mode = newMode; + + // 重新启动 + await this.bootWithBootstrap(); + } + + /** + * 获取启动结果 + */ + public getBootResult(): PinballBootResult | null { + return this.bootResult; + } + + /** + * 获取指定模式的字符串值 + */ + private getModeStringForMode(mode: RunMode): string { + return RunModeStrings[mode] ?? 'standalone'; + } + + /** + * 获取当前模式的字符串值 + * @returns 模式字符串 + */ + public getModeString(): string { + return this.getModeStringForMode(this.mode); + } +} + + diff --git a/client-cocos/assets/scripts/App/ClientRunner.ts.meta b/client-cocos/assets/scripts/App/ClientRunner.ts.meta new file mode 100644 index 0000000..784ccbc --- /dev/null +++ b/client-cocos/assets/scripts/App/ClientRunner.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "594d765c-8674-48a1-a60f-06e438cdfb47", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Modules.meta b/client-cocos/assets/scripts/Modules.meta new file mode 100644 index 0000000..0b43958 --- /dev/null +++ b/client-cocos/assets/scripts/Modules.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.2.0", + "importer": "directory", + "imported": true, + "uuid": "cd3c8566-25ad-4c45-acb5-d4622f704aa6", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Modules/Pinball.meta b/client-cocos/assets/scripts/Modules/Pinball.meta new file mode 100644 index 0000000..1435a0b --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.2.0", + "importer": "directory", + "imported": true, + "uuid": "c4fb263a-8354-4597-9436-e72c9e9f189d", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Modules/Pinball/Boot.meta b/client-cocos/assets/scripts/Modules/Pinball/Boot.meta new file mode 100644 index 0000000..adfe6af --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Boot.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.2.0", + "importer": "directory", + "imported": true, + "uuid": "03edfca7-426e-4a79-aa6f-9bd259a9ead3", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Modules/Pinball/Boot/BaseBooter.ts b/client-cocos/assets/scripts/Modules/Pinball/Boot/BaseBooter.ts new file mode 100644 index 0000000..20d2ec0 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Boot/BaseBooter.ts @@ -0,0 +1,84 @@ +/** + * 启动器基类 + * 定义不同启动模式的统一接口 + */ + +import { Component } from 'cc'; +import { PinballManager } from '../PinballManager'; +import { PinballBootConfig } from './BootTypes'; + +export abstract class BaseBooter { + + /** + * 启动特定模式的游戏 + * @param hostComponent 宿主组件 + * @param config 启动配置 + */ + abstract boot(hostComponent: Component, config: PinballBootConfig): Promise; + + /** + * 应用配置到 PinballManager + * @param pinballManager PinballManager 实例 + * @param config 启动配置 + */ + protected applyConfiguration(pinballManager: PinballManager, config: PinballBootConfig): void { + // 设置节点引用 + if (config.cameraNode) { + pinballManager.cameraNode = config.cameraNode; + } + + if (config.renderContainer) { + pinballManager.renderContainer = config.renderContainer; + } + + if (config.uiContainer) { + pinballManager.uiContainer = config.uiContainer; + } + + // 设置基础配置 + pinballManager.autoStart = config.autoStart !== false; // 默认为 true + pinballManager.debugMode = config.debugMode || false; + + // 应用自定义配置 + if (config.physicsConfig || config.renderConfig || config.wasmPath) { + const pinballConfig = pinballManager.getConfig(); + + if (!pinballConfig) { + pinballManager.updateConfig(config); + } else { + if (config.physicsConfig) { + pinballConfig.physicsSettings = { + ...pinballConfig.physicsSettings, + gravity: config.physicsConfig.gravity || { x: 0, y: -9.81 }, + timeStep: config.physicsConfig.timeStep || 1 / 60 + }; + } + + if (config.renderConfig) { + pinballConfig.renderSettings = { + ...pinballConfig.renderSettings, + enableEffects: config.renderConfig.enableEffects !== false, + maxParticles: config.renderConfig.maxParticles || 500 + }; + } + + if (config.wasmPath) { + pinballConfig.wasmPath = config.wasmPath; + } + + pinballManager.updateConfig(pinballConfig); + } + + + } + } + + /** + * 条件日志输出 + */ + protected log(message: string, enabled?: boolean): void { + if (enabled !== false) { + console.log(message); + } + } +} \ No newline at end of file diff --git a/client-cocos/assets/scripts/Modules/Pinball/Boot/BaseBooter.ts.meta b/client-cocos/assets/scripts/Modules/Pinball/Boot/BaseBooter.ts.meta new file mode 100644 index 0000000..24bcf4c --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Boot/BaseBooter.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "73bc9ff5-962b-4dd1-801f-0671a621bc9b", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Modules/Pinball/Boot/BootTypes.ts b/client-cocos/assets/scripts/Modules/Pinball/Boot/BootTypes.ts new file mode 100644 index 0000000..be96806 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Boot/BootTypes.ts @@ -0,0 +1,70 @@ +/** + * Pinball 启动配置接口 + */ + +import { Node } from 'cc'; + +export enum PinballBootMode { + STANDALONE = 'standalone', + CLIENT_MULTIPLAYER = 'client-multiplayer', + SERVER_MULTIPLAYER = 'server-multiplayer' +} + +export interface PinballBootConfig { + /** 启动模式 */ + mode: PinballBootMode; + + /** 主相机节点 */ + cameraNode?: Node; + + /** 渲染容器节点 */ + renderContainer?: Node; + + /** UI容器节点 */ + uiContainer?: Node; + + /** 是否启用调试模式 */ + debugMode?: boolean; + + /** 是否自动启动 */ + autoStart?: boolean; + + /** 物理引擎配置 */ + physicsConfig?: { + gravity?: { x: number; y: number }; + timeStep?: number; + }; + + /** 渲染配置 */ + renderConfig?: { + enableEffects?: boolean; + maxParticles?: number; + }; + + /** 多人模式配置 */ + multiplayerConfig?: { + serverAddress?: string; + playerName?: string; + roomId?: string; + }; + + /** WASM文件路径 */ + wasmPath?: string; +} + +export interface PinballBootResult { + /** 启动是否成功 */ + success: boolean; + + /** 错误信息(如果失败) */ + error?: string; + + /** 启动的模式 */ + mode: PinballBootMode; + + /** PinballManager 实例 */ + pinballManager?: any; + + /** 启动时间戳 */ + timestamp: number; +} \ No newline at end of file diff --git a/client-cocos/assets/scripts/Modules/Pinball/Boot/BootTypes.ts.meta b/client-cocos/assets/scripts/Modules/Pinball/Boot/BootTypes.ts.meta new file mode 100644 index 0000000..82828bb --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Boot/BootTypes.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "d101ed2a-e68c-4b3b-831b-04d84f0596f0", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Modules/Pinball/Boot/Mode.meta b/client-cocos/assets/scripts/Modules/Pinball/Boot/Mode.meta new file mode 100644 index 0000000..2b75f53 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Boot/Mode.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.2.0", + "importer": "directory", + "imported": true, + "uuid": "3efea1af-73f2-404f-a948-e059b92c65c1", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Modules/Pinball/Boot/Mode/ClientMultiplayerBooter.ts b/client-cocos/assets/scripts/Modules/Pinball/Boot/Mode/ClientMultiplayerBooter.ts new file mode 100644 index 0000000..f2b8cb0 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Boot/Mode/ClientMultiplayerBooter.ts @@ -0,0 +1,22 @@ +/** + * Client Multiplayer 模式启动器 + */ + +import { Component } from 'cc'; +import { PinballManager } from '../../PinballManager'; +import { BaseBooter } from '../BaseBooter'; +import { PinballBootConfig } from '../BootTypes'; + +export class ClientMultiplayerBooter extends BaseBooter { + + async boot(hostComponent: Component, config: PinballBootConfig): Promise { + this.log('[ClientMultiplayerBooter] 正在启动客户端多人模式...', config.debugMode); + + // TODO: 实现客户端多人模式启动逻辑 + // 1. 连接到游戏服务器 + // 2. 加入游戏房间 + // 3. 设置网络同步和状态管理 + + throw new Error('客户端多人模式尚未实现'); + } +} \ No newline at end of file diff --git a/client-cocos/assets/scripts/Modules/Pinball/Boot/Mode/ClientMultiplayerBooter.ts.meta b/client-cocos/assets/scripts/Modules/Pinball/Boot/Mode/ClientMultiplayerBooter.ts.meta new file mode 100644 index 0000000..b12f1a7 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Boot/Mode/ClientMultiplayerBooter.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "57dd41d5-1108-443c-8888-9aa695b552ce", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Modules/Pinball/Boot/Mode/ServerMultiplayerBooter.ts b/client-cocos/assets/scripts/Modules/Pinball/Boot/Mode/ServerMultiplayerBooter.ts new file mode 100644 index 0000000..48ab4ef --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Boot/Mode/ServerMultiplayerBooter.ts @@ -0,0 +1,22 @@ +/** + * Server Multiplayer 模式启动器 + */ + +import { Component } from 'cc'; +import { PinballManager } from '../../PinballManager'; +import { BaseBooter } from '../BaseBooter'; +import { PinballBootConfig } from '../BootTypes'; + +export class ServerMultiplayerBooter extends BaseBooter { + + async boot(hostComponent: Component, config: PinballBootConfig): Promise { + this.log('[ServerMultiplayerBooter] 正在启动服务器多人模式...', config.debugMode); + + // TODO: 实现服务器多人模式启动逻辑 + // 1. 连接到 SpacetimeDB + // 2. 创建房间或加入现有房间 + // 3. 设置网络同步 + + throw new Error('服务器多人模式尚未实现'); + } +} \ No newline at end of file diff --git a/client-cocos/assets/scripts/Modules/Pinball/Boot/Mode/ServerMultiplayerBooter.ts.meta b/client-cocos/assets/scripts/Modules/Pinball/Boot/Mode/ServerMultiplayerBooter.ts.meta new file mode 100644 index 0000000..759c265 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Boot/Mode/ServerMultiplayerBooter.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "6f237677-8531-40fc-af0a-c1e9f020528a", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Modules/Pinball/Boot/Mode/StandaloneBooter.ts b/client-cocos/assets/scripts/Modules/Pinball/Boot/Mode/StandaloneBooter.ts new file mode 100644 index 0000000..8d7f1c1 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Boot/Mode/StandaloneBooter.ts @@ -0,0 +1,42 @@ +/** + * Standalone 模式启动器 + */ + +import { Component } from 'cc'; +import { PinballManager, PinballMode } from '../../PinballManager'; +import { BaseBooter } from '../BaseBooter'; +import { PinballBootConfig } from '../BootTypes'; + +export class StandaloneBooter extends BaseBooter { + + async boot(hostComponent: Component, config: PinballBootConfig): Promise { + this.log('[StandaloneBooter] 正在启动 Standalone 模式...', config.debugMode); + + // 创建或获取 PinballManager 组件 + let pinballManager = hostComponent.getComponent(PinballManager); + if (!pinballManager) { + pinballManager = hostComponent.addComponent(PinballManager); + } + + // 应用配置 + this.applyConfiguration(pinballManager, config); + + // 设置默认模式 + pinballManager.defaultMode = PinballMode.STANDALONE; + + // 启动 PinballManager(在配置应用后) + const startSuccess = await pinballManager.Start(); + if (!startSuccess) { + throw new Error('PinballManager 启动失败'); + } + + // 启动 Standalone 模式 + const success = await pinballManager.startGame(PinballMode.STANDALONE); + if (!success) { + throw new Error('Standalone 模式启动失败'); + } + + this.log('[StandaloneBooter] Standalone 模式启动完成', config.debugMode); + return pinballManager; + } +} \ No newline at end of file diff --git a/client-cocos/assets/scripts/Modules/Pinball/Boot/Mode/StandaloneBooter.ts.meta b/client-cocos/assets/scripts/Modules/Pinball/Boot/Mode/StandaloneBooter.ts.meta new file mode 100644 index 0000000..7256858 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Boot/Mode/StandaloneBooter.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "b1ed032a-5ed8-4dee-b821-8d4a4558ae00", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Modules/Pinball/Boot/PinballBootUtils.ts b/client-cocos/assets/scripts/Modules/Pinball/Boot/PinballBootUtils.ts new file mode 100644 index 0000000..ba2510e --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Boot/PinballBootUtils.ts @@ -0,0 +1,186 @@ +/** + * Pinball 启动工具类 + * 提供常用的启动配置和便捷方法 + */ + +import { Component } from 'cc'; +import { PinballBootConfig, PinballBootMode, PinballBootResult, PinballBootstrap } from './index'; + +export class PinballBootUtils { + + + /** + * 创建高性能配置(适合移动设备) + */ + public static createMobileConfig(mode: PinballBootMode): PinballBootConfig { + const config = PinballBootstrap.createDefaultConfig(mode); + + // 移动设备优化配置 + config.renderConfig = { + enableEffects: false, // 关闭粒子效果以提升性能 + maxParticles: 100 // 降低粒子数量 + }; + + config.physicsConfig = { + gravity: { x: 0, y: -9.81 }, + timeStep: 1 / 30 // 降低物理步进频率 + }; + + return config; + } + + /** + * 创建高质量配置(适合桌面设备) + */ + public static createDesktopConfig(mode: PinballBootMode): PinballBootConfig { + const config = PinballBootstrap.createDefaultConfig(mode); + + // 桌面设备高质量配置 + config.renderConfig = { + enableEffects: true, // 启用所有粒子效果 + maxParticles: 1000 // 更多粒子数量 + }; + + config.physicsConfig = { + gravity: { x: 0, y: -9.81 }, + timeStep: 1 / 120 // 更高的物理步进频率 + }; + + return config; + } + + /** + * 创建调试配置 + */ + public static createDebugConfig(mode: PinballBootMode): PinballBootConfig { + const config = PinballBootstrap.createDefaultConfig(mode); + config.debugMode = true; + return config; + } + + /** + * 创建生产配置 + */ + public static createProductionConfig(mode: PinballBootMode): PinballBootConfig { + const config = PinballBootstrap.createDefaultConfig(mode); + config.debugMode = false; + return config; + } + + /** + * 批量启动多个模式(用于测试) + */ + public static async batchBoot( + hostComponent: Component, + configs: PinballBootConfig[] + ): Promise { + const bootstrap = PinballBootstrap.getInstance(); + const results: PinballBootResult[] = []; + + for (const config of configs) { + console.log(`[PinballBootUtils] 批量启动: ${config.mode}`); + const result = await bootstrap.boot(hostComponent, config); + results.push(result); + + // 如果启动失败,停止批量启动 + if (!result.success) { + console.error(`[PinballBootUtils] 批量启动在 ${config.mode} 模式失败: ${result.error}`); + break; + } + } + + return results; + } + + /** + * 性能基准测试启动 + */ + public static async benchmarkBoot( + hostComponent: Component, + config: PinballBootConfig, + iterations: number = 5 + ): Promise<{ averageTime: number; results: PinballBootResult[] }> { + const bootstrap = PinballBootstrap.getInstance(); + const results: PinballBootResult[] = []; + let totalTime = 0; + + console.log(`[PinballBootUtils] 开始性能基准测试,迭代次数: ${iterations}`); + + for (let i = 0; i < iterations; i++) { + const startTime = performance.now(); + const result = await bootstrap.boot(hostComponent, config); + const endTime = performance.now(); + + const duration = endTime - startTime; + totalTime += duration; + + results.push(result); + + console.log(`[PinballBootUtils] 第 ${i + 1} 次启动耗时: ${duration.toFixed(2)}ms`); + + // 清理资源准备下一次测试 + if (result.success && result.pinballManager) { + await result.pinballManager.stopCurrentGame(); + } + } + + const averageTime = totalTime / iterations; + console.log(`[PinballBootUtils] 平均启动时间: ${averageTime.toFixed(2)}ms`); + + return { averageTime, results }; + } + + /** + * 验证启动结果 + */ + public static validateBootResult(result: PinballBootResult): { + isValid: boolean; + issues: string[]; + } { + const issues: string[] = []; + + if (!result.success) { + issues.push(`启动失败: ${result.error}`); + } + + if (!result.pinballManager) { + issues.push('PinballManager 实例不存在'); + } + + if (!result.mode) { + issues.push('启动模式未定义'); + } + + if (!result.timestamp || result.timestamp <= 0) { + issues.push('时间戳无效'); + } + + return { + isValid: issues.length === 0, + issues + }; + } + + /** + * 生成启动报告 + */ + public static generateBootReport(results: PinballBootResult[]): string { + const successCount = results.filter(r => r.success).length; + const failureCount = results.length - successCount; + + let report = '=== Pinball 启动报告 ===\n'; + report += `总启动次数: ${results.length}\n`; + report += `成功启动: ${successCount}\n`; + report += `启动失败: ${failureCount}\n`; + report += `成功率: ${((successCount / results.length) * 100).toFixed(1)}%\n\n`; + + if (failureCount > 0) { + report += '失败详情:\n'; + results.filter(r => !r.success).forEach((result, index) => { + report += `${index + 1}. ${result.mode}: ${result.error}\n`; + }); + } + + return report; + } +} \ No newline at end of file diff --git a/client-cocos/assets/scripts/Modules/Pinball/Boot/PinballBootUtils.ts.meta b/client-cocos/assets/scripts/Modules/Pinball/Boot/PinballBootUtils.ts.meta new file mode 100644 index 0000000..8403eb6 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Boot/PinballBootUtils.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "e796606d-57a8-45e7-b700-66f4c28ccaed", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Modules/Pinball/Boot/PinballBootstrap.ts b/client-cocos/assets/scripts/Modules/Pinball/Boot/PinballBootstrap.ts new file mode 100644 index 0000000..b3469ee --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Boot/PinballBootstrap.ts @@ -0,0 +1,116 @@ +/** + * Pinball 启动管理器 + * 负责维护游戏启动状态和调度具体启动器 + */ + +import { Component } from 'cc'; +import { PinballBootConfig, PinballBootMode, PinballBootResult } from './BootTypes'; +import { ClientMultiplayerBooter } from './Mode/ClientMultiplayerBooter'; +import { ServerMultiplayerBooter } from './Mode/ServerMultiplayerBooter'; +import { StandaloneBooter } from './Mode/StandaloneBooter'; + +export class PinballBootstrap { + private static instance: PinballBootstrap; + private currentResult: PinballBootResult | null = null; + + /** 获取单例实例 */ + public static getInstance(): PinballBootstrap { + if (!PinballBootstrap.instance) { + PinballBootstrap.instance = new PinballBootstrap(); + } + return PinballBootstrap.instance; + } + + private constructor() { } + + /** + * 启动 Pinball 游戏 + */ + public async boot(hostComponent: Component, config: PinballBootConfig): Promise { + const startTime = Date.now(); + + try { + console.log(`[PinballBootstrap] 开始启动 ${config.mode} 模式`); + + // 获取对应的启动器 + const booter = this.getBooter(config.mode); + + // 执行启动 + const pinballManager = await booter.boot(hostComponent, config); + + // 记录启动结果 + this.currentResult = { + success: true, + mode: config.mode, + pinballManager, + timestamp: startTime + }; + + console.log(`[PinballBootstrap] ${config.mode} 模式启动成功`); + return this.currentResult; + + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + console.error(`[PinballBootstrap] 启动失败:`, error); + + this.currentResult = { + success: false, + error: errorMessage, + mode: config.mode, + timestamp: startTime + }; + + return this.currentResult; + } + } + + /** + * 获取启动器实例 + */ + private getBooter(mode: PinballBootMode) { + switch (mode) { + case PinballBootMode.STANDALONE: + return new StandaloneBooter(); + case PinballBootMode.CLIENT_MULTIPLAYER: + return new ClientMultiplayerBooter(); + case PinballBootMode.SERVER_MULTIPLAYER: + return new ServerMultiplayerBooter(); + default: + throw new Error(`不支持的启动模式: ${mode}`); + } + } + + /** + * 获取当前启动结果 + */ + public getCurrentResult(): PinballBootResult | null { + return this.currentResult; + } + + /** + * 清除启动状态 + */ + public clearState(): void { + this.currentResult = null; + } + + /** + * 创建默认配置 + */ + public static createDefaultConfig(mode: PinballBootMode): PinballBootConfig { + return { + mode, + debugMode: true, + autoStart: true, + physicsConfig: { + gravity: { x: 0, y: -9.81 }, + timeStep: 1 / 60 + }, + renderConfig: { + enableEffects: true, + maxParticles: 500 + }, + wasmPath: 'assets/wasm/pinball_physics.wasm' + }; + } +} \ No newline at end of file diff --git a/client-cocos/assets/scripts/Modules/Pinball/Boot/PinballBootstrap.ts.meta b/client-cocos/assets/scripts/Modules/Pinball/Boot/PinballBootstrap.ts.meta new file mode 100644 index 0000000..d918d86 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Boot/PinballBootstrap.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "9cdad6b2-b0b6-4223-b44f-fbdf0f37197c", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Modules/Pinball/Boot/index.ts b/client-cocos/assets/scripts/Modules/Pinball/Boot/index.ts new file mode 100644 index 0000000..93b700f --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Boot/index.ts @@ -0,0 +1,12 @@ +/** + * Pinball Boot 模块入口 + * 导出启动相关的所有类型和类 + */ + +export { PinballBootMode } from './BootTypes'; +export type { + PinballBootConfig, + PinballBootResult +} from './BootTypes'; +export { PinballBootstrap } from './PinballBootstrap'; +export { PinballBootUtils } from './PinballBootUtils'; diff --git a/client-cocos/assets/scripts/Modules/Pinball/Boot/index.ts.meta b/client-cocos/assets/scripts/Modules/Pinball/Boot/index.ts.meta new file mode 100644 index 0000000..17fdac0 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Boot/index.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "618d7e5f-842b-4506-a4ad-f55fc18f102b", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Modules/Pinball/Core.meta b/client-cocos/assets/scripts/Modules/Pinball/Core.meta new file mode 100644 index 0000000..35b5016 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Core.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.2.0", + "importer": "directory", + "imported": true, + "uuid": "af23f3f9-fa61-4053-9a39-a4fccd83f14e", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Modules/Pinball/Core/EventBus.ts b/client-cocos/assets/scripts/Modules/Pinball/Core/EventBus.ts new file mode 100644 index 0000000..2e941c5 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Core/EventBus.ts @@ -0,0 +1,88 @@ +/** + * 简单的事件总线实现 + * 用于 Pinball 模块内部组件间通信 + */ + +export type EventCallback = (data: T) => void; + +export class EventBus { + private static instance: EventBus; + private events: Map = new Map(); + + private constructor() { } + + /** + * 获取单例实例 + */ + static getInstance(): EventBus { + if (!EventBus.instance) { + EventBus.instance = new EventBus(); + } + return EventBus.instance; + } + + /** + * 订阅事件 + */ + on(event: string, callback: EventCallback): void { + if (!this.events.has(event)) { + this.events.set(event, []); + } + this.events.get(event)!.push(callback); + } + + /** + * 取消订阅事件 + */ + off(event: string, callback: EventCallback): void { + const callbacks = this.events.get(event); + if (callbacks) { + const index = callbacks.indexOf(callback); + if (index > -1) { + callbacks.splice(index, 1); + } + } + } + + /** + * 发射事件 + */ + emit(event: string, data?: T): void { + const callbacks = this.events.get(event); + if (callbacks) { + callbacks.forEach(callback => { + try { + callback(data); + } catch (error) { + console.error(`Error in event callback for event '${event}':`, error); + } + }); + } + } + + /** + * 只订阅一次事件 + */ + once(event: string, callback: EventCallback): void { + const onceCallback: EventCallback = (data: T) => { + callback(data); + this.off(event, onceCallback); + }; + this.on(event, onceCallback); + } + + /** + * 清理所有事件监听器 + */ + clear(): void { + this.events.clear(); + } + + /** + * 获取事件监听器数量 + */ + listenerCount(event: string): number { + const callbacks = this.events.get(event); + return callbacks ? callbacks.length : 0; + } +} \ No newline at end of file diff --git a/client-cocos/assets/scripts/Modules/Pinball/Core/EventBus.ts.meta b/client-cocos/assets/scripts/Modules/Pinball/Core/EventBus.ts.meta new file mode 100644 index 0000000..6172fc5 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Core/EventBus.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "1dc34aed-d9d7-4cd7-8bf7-5d4a1564882f", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Modules/Pinball/Core/GameData.ts b/client-cocos/assets/scripts/Modules/Pinball/Core/GameData.ts new file mode 100644 index 0000000..9410cf6 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Core/GameData.ts @@ -0,0 +1,74 @@ +/** + * Pinball 游戏核心数据结构定义 + */ + +/** 2D 向量 */ +export interface Vector2 { + x: number; + y: number; +} + +/** 物理体 ID */ +export type BodyId = number; + +/** 世界 ID */ +export type WorldId = number; + +/** 物理体数据 */ +export interface PhysicsBodyData { + id: BodyId; + position: Vector2; + velocity: Vector2; + rotation: number; + angularVelocity: number; + bodyType: 'circle' | 'box'; + radius?: number; + size?: Vector2; + isStatic: boolean; +} + +/** 游戏状态 */ +export interface GameState { + worldId: WorldId; + bodies: Map; + isPaused: boolean; + timeStep: number; +} + +/** Pinball 配置 */ +export interface PinballConfig { + mode: 'standalone' | 'client-multiplayer' | 'server-multiplayer'; + serverAddress?: string; + wasmPath?: string; + physicsSettings?: { + gravity: Vector2; + timeStep: number; + }; + renderSettings?: { + enableEffects: boolean; + maxParticles: number; + }; +} + +/** 物理设置 */ +export interface PhysicsSettings { + gravity: Vector2; + timeStep: number; + maxBodies: number; +} + +/** 游戏事件类型 */ +export enum GameEventType { + PHYSICS_STEP = 'physics_step', + BODY_CREATED = 'body_created', + BODY_DESTROYED = 'body_destroyed', + WORLD_RESET = 'world_reset', + INPUT_RECEIVED = 'input_received' +} + +/** 游戏事件数据 */ +export interface GameEvent { + type: GameEventType; + data?: any; + timestamp: number; +} \ No newline at end of file diff --git a/client-cocos/assets/scripts/Modules/Pinball/Core/GameData.ts.meta b/client-cocos/assets/scripts/Modules/Pinball/Core/GameData.ts.meta new file mode 100644 index 0000000..f342069 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Core/GameData.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "ff4e5c1d-69eb-4234-9087-1b84b837d708", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Modules/Pinball/Core/IPhysicsEngine.ts b/client-cocos/assets/scripts/Modules/Pinball/Core/IPhysicsEngine.ts new file mode 100644 index 0000000..801b1c6 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Core/IPhysicsEngine.ts @@ -0,0 +1,116 @@ +/** + * 物理引擎接口 + * 定义了不同物理引擎实现需要遵循的契约 + */ + +import { BodyId, PhysicsBodyData, PhysicsSettings, Vector2, WorldId } from './GameData'; + +export interface CreateCircleOptions { + position: Vector2; + radius: number; + isStatic: boolean; + density?: number; + restitution?: number; + friction?: number; +} + +export interface CreateBoxOptions { + position: Vector2; + size: Vector2; + isStatic: boolean; + density?: number; + restitution?: number; + friction?: number; +} + +export interface IPhysicsEngine { + /** + * 初始化物理引擎 + */ + initialize(settings?: PhysicsSettings): Promise; + + /** + * 创建物理世界 + */ + createWorld(gravity: Vector2): Promise; + + /** + * 销毁物理世界 + */ + destroyWorld(worldId: WorldId): Promise; + + /** + * 执行物理步进 + */ + step(deltaTime: number, worldId?: WorldId): Promise; + + /** + * 创建圆形刚体 + */ + createCircle(options: CreateCircleOptions): BodyId; + + /** + * 创建矩形刚体 + */ + createBox(options: CreateBoxOptions): BodyId; + + /** + * 创建动态刚体 + */ + createDynamicBody(worldId: WorldId, position: Vector2, radius: number): Promise; + + /** + * 创建静态刚体 + */ + createStaticBody(worldId: WorldId, position: Vector2, radius: number): Promise; + + /** + * 销毁刚体 + */ + destroyBody(worldId: WorldId, bodyId: BodyId): Promise; + + /** + * 移除刚体 + */ + removeBody(bodyId: BodyId): void; + + /** + * 获取刚体位置 + */ + getBodyPosition(worldId: WorldId, bodyId: BodyId): Promise; + + /** + * 设置刚体位置 + */ + setBodyPosition(worldId: WorldId, bodyId: BodyId, position: Vector2): Promise; + + /** + * 获取刚体速度 + */ + getBodyVelocity(worldId: WorldId, bodyId: BodyId): Promise; + + /** + * 设置刚体速度 + */ + setBodyVelocity(worldId: WorldId, bodyId: BodyId, velocity: Vector2): Promise; + + /** + * 获取刚体数据 + */ + getBodyData(bodyId: BodyId): PhysicsBodyData | null; + + /** + * 获取所有物理体数据 + */ + getAllBodies(worldId: WorldId): Promise; + + /** + * 清理资源 + */ + cleanup(): void; + + /** + * 清理资源 (别名) + */ + dispose(): Promise; +} \ No newline at end of file diff --git a/client-cocos/assets/scripts/Modules/Pinball/Core/IPhysicsEngine.ts.meta b/client-cocos/assets/scripts/Modules/Pinball/Core/IPhysicsEngine.ts.meta new file mode 100644 index 0000000..ea02842 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Core/IPhysicsEngine.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "b15a76bd-406d-4463-8a9c-1e82229ab995", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Modules/Pinball/Core/IRenderer.ts b/client-cocos/assets/scripts/Modules/Pinball/Core/IRenderer.ts new file mode 100644 index 0000000..57c1f61 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Core/IRenderer.ts @@ -0,0 +1,86 @@ +/** + * 渲染器接口 + * 定义了渲染系统需要实现的方法 + */ + +import { PhysicsBodyData, Vector2 } from './GameData'; + +/** 渲染对象数据 */ +export interface RenderObject { + id: string; + position: Vector2; + radius: number; + color: { r: number; g: number; b: number; a: number }; + layer: number; +} + +/** 粒子效果数据 */ +export interface ParticleEffect { + position: Vector2; + velocity: Vector2; + color: { r: number; g: number; b: number; a: number }; + lifetime: number; + size: number; +} + +export interface IRenderer { + /** + * 初始化渲染器 + */ + initialize(parentNode: any): Promise; + + /** + * 渲染物理体 + */ + renderBodies(bodies: PhysicsBodyData[]): void; + + /** + * 创建渲染对象 + */ + createRenderObject(body: PhysicsBodyData): RenderObject; + + /** + * 更新渲染对象 + */ + updateRenderObject(renderObject: RenderObject, body: PhysicsBodyData): void; + + /** + * 移除渲染对象 + */ + removeRenderObject(bodyId: string): void; + + /** + * 播放粒子效果 + */ + playParticleEffect(effect: ParticleEffect): void; + + /** + * 清除所有渲染对象 + */ + clear(): void; + + /** + * 设置相机 + */ + setCamera(camera: any): void; + + /** + * 设置世界边界 + */ + setWorldBounds(width: number, height: number): void; + + /** + * 设置相机位置 + */ + setCameraPosition(position: Vector2): void; + + /** + * 设置相机缩放 + */ + setCameraZoom(zoom: number): void; + + /** + * 销毁渲染器 + */ + dispose(): void; +} \ No newline at end of file diff --git a/client-cocos/assets/scripts/Modules/Pinball/Core/IRenderer.ts.meta b/client-cocos/assets/scripts/Modules/Pinball/Core/IRenderer.ts.meta new file mode 100644 index 0000000..bdfe463 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Core/IRenderer.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "afa401be-4482-4d0a-ae6c-1b1b94b7b992", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Modules/Pinball/GameModes.meta b/client-cocos/assets/scripts/Modules/Pinball/GameModes.meta new file mode 100644 index 0000000..b05a577 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/GameModes.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.2.0", + "importer": "directory", + "imported": true, + "uuid": "c0d94965-94bc-44ed-b580-d2b93cdb93a1", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Modules/Pinball/GameModes/StandaloneMode.ts b/client-cocos/assets/scripts/Modules/Pinball/GameModes/StandaloneMode.ts new file mode 100644 index 0000000..1e0e70b --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/GameModes/StandaloneMode.ts @@ -0,0 +1,358 @@ +import { _decorator, Camera, Component, Node } from 'cc'; +import { EventBus } from '../Core/EventBus'; +import { PhysicsBodyData, Vector2 } from '../Core/GameData'; +import { IPhysicsEngine } from '../Core/IPhysicsEngine'; +import { IRenderer } from '../Core/IRenderer'; +import { InputManager } from '../Input/InputManager'; +import { MouseInputEvent, TouchInputEvent } from '../Input/InputTypes'; +import { WasmPhysicsEngine } from '../Physics/WasmPhysicsEngine'; +import { PinballRenderer } from '../Renderer/PinballRenderer'; + +const { ccclass, property } = _decorator; + +/** + * Standalone模式 - 单机弹珠物理游戏模式 + * 整合物理引擎、渲染器和输入管理器,提供完整的单机游戏体验 + */ +@ccclass('StandaloneMode') +export class StandaloneMode extends Component { + + @property(Camera) + gameCamera: Camera = null; + + @property(Node) + renderNode: Node = null; + + @property({ type: Node, tooltip: "用于显示游戏边界的节点" }) + boundsNode: Node = null; + + // 核心系统 + private physicsEngine: IPhysicsEngine = null; + private renderer: IRenderer = null; + private inputManager: InputManager = null; + private eventBus: EventBus = null; + + // 游戏配置 + @property({ tooltip: "游戏世界宽度" }) + worldWidth: number = 800; + + @property({ tooltip: "游戏世界高度" }) + worldHeight: number = 600; + + @property({ tooltip: "重力加速度" }) + gravity: number = -9.81; + + @property({ tooltip: "弹珠默认半径" }) + ballRadius: number = 10; + + @property({ tooltip: "弹珠默认密度" }) + ballDensity: number = 1.0; + + @property({ tooltip: "弹珠默认弹性系数" }) + ballRestitution: number = 0.8; + + // 游戏状态 + private isInitialized: boolean = false; + private ballCount: number = 0; + private activeBalls: Map = new Map(); + + async onLoad() { + // 初始化事件总线 + this.eventBus = EventBus.getInstance(); + + // 初始化物理引擎 + await this.initializePhysics(); + + // 初始化渲染器 + this.initializeRenderer(); + + // 初始化输入管理器 + this.initializeInput(); + + // 注册事件监听 + this.registerEventHandlers(); + + console.log('[StandaloneMode] 初始化完成'); + } + + onEnable() { + if (this.isInitialized) { + this.startGameLoop(); + } + } + + onDisable() { + this.stopGameLoop(); + } + + onDestroy() { + this.cleanup(); + } + + /** + * 初始化物理引擎 + */ + private async initializePhysics(): Promise { + this.physicsEngine = new WasmPhysicsEngine(); + await this.physicsEngine.initialize({ + gravity: { x: 0, y: this.gravity }, + timeStep: 1 / 60, + maxBodies: 1000 + }); + + // 创建物理世界 + await this.physicsEngine.createWorld({ x: 0, y: this.gravity }); + + // 创建世界边界 + this.createWorldBounds(); + + console.log('[StandaloneMode] 物理引擎初始化完成'); + } + + /** + * 初始化渲染器 + */ + private initializeRenderer(): void { + if (!this.renderNode) { + console.error('[StandaloneMode] renderNode 未设置'); + return; + } + + this.renderer = this.renderNode.getComponent(PinballRenderer); + if (!this.renderer) { + this.renderer = this.renderNode.addComponent(PinballRenderer); + } + + // 设置渲染器参数 + if (this.gameCamera) { + this.renderer.setCamera(this.gameCamera); + } + + this.renderer.setWorldBounds(this.worldWidth, this.worldHeight); + + console.log('[StandaloneMode] 渲染器初始化完成'); + } + + /** + * 初始化输入管理器 + */ + private initializeInput(): void { + this.inputManager = this.getComponent(InputManager); + if (!this.inputManager) { + this.inputManager = this.addComponent(InputManager); + } + + // 设置输入管理器参数 + if (this.gameCamera) { + this.inputManager.setCamera(this.gameCamera); + } + + console.log('[StandaloneMode] 输入管理器初始化完成'); + } + + /** + * 注册事件处理器 + */ + private registerEventHandlers(): void { + // 监听输入事件 + this.eventBus.on('input.mouse.click', (event: MouseInputEvent) => this.onMouseClick(event)); + this.eventBus.on('input.touch.start', (event: TouchInputEvent) => this.onTouchStart(event)); + + // 监听物理事件 + this.eventBus.on('physics.collision', (collisionData: any) => this.onPhysicsCollision(collisionData)); + + console.log('[StandaloneMode] 事件处理器注册完成'); + + this.isInitialized = true; + } + + /** + * 创建世界边界 + */ + private createWorldBounds(): void { + const halfWidth = this.worldWidth / 2; + const halfHeight = this.worldHeight / 2; + const wallThickness = 10; + + // 创建四面墙壁 + const walls = [ + // 底部墙 + { x: 0, y: -halfHeight - wallThickness / 2, width: this.worldWidth + wallThickness, height: wallThickness }, + // 顶部墙 + { x: 0, y: halfHeight + wallThickness / 2, width: this.worldWidth + wallThickness, height: wallThickness }, + // 左侧墙 + { x: -halfWidth - wallThickness / 2, y: 0, width: wallThickness, height: this.worldHeight + wallThickness }, + // 右侧墙 + { x: halfWidth + wallThickness / 2, y: 0, width: wallThickness, height: this.worldHeight + wallThickness } + ]; + + for (const wall of walls) { + this.physicsEngine.createBox({ + position: { x: wall.x, y: wall.y }, + size: { x: wall.width, y: wall.height }, + isStatic: true, + restitution: 0.8, + friction: 0.3 + }); + } + + console.log('[StandaloneMode] 世界边界创建完成'); + } + + /** + * 鼠标点击事件处理 + */ + private onMouseClick(event: MouseInputEvent): void { + this.createBallAtPosition(event.position); + } + + /** + * 触摸开始事件处理 + */ + private onTouchStart(event: TouchInputEvent): void { + this.createBallAtPosition(event.position); + } + + /** + * 在指定位置创建弹珠 + */ + private createBallAtPosition(position: Vector2): void { + const ballId = this.physicsEngine.createCircle({ + position: position, + radius: this.ballRadius, + isStatic: false, + density: this.ballDensity, + restitution: this.ballRestitution, + friction: 0.3 + }); + + // 创建球体数据 + const ballData: PhysicsBodyData = { + id: ballId, + position: position, + rotation: 0, + velocity: { x: 0, y: 0 }, + angularVelocity: 0, + bodyType: 'circle', + radius: this.ballRadius, + isStatic: false + }; + + this.activeBalls.set(ballId, ballData); + this.ballCount++; + + // 通知渲染器 + this.eventBus.emit('ball.created', { + id: ballId, + position: position, + radius: this.ballRadius + }); + + console.log(`[StandaloneMode] 创建弹珠 #${ballId} 在位置 (${position.x}, ${position.y})`); + } + + /** + * 物理碰撞事件处理 + */ + private onPhysicsCollision(collisionData: any): void { + // 播放碰撞音效或效果 + console.log('[StandaloneMode] 物理碰撞:', collisionData); + } + + /** + * 开始游戏循环 + */ + private startGameLoop(): void { + // 在update中已经自动运行物理和渲染循环 + console.log('[StandaloneMode] 游戏循环已开始'); + } + + /** + * 停止游戏循环 + */ + private stopGameLoop(): void { + console.log('[StandaloneMode] 游戏循环已停止'); + } + + /** + * 游戏更新循环 + */ + update(deltaTime: number) { + if (!this.isInitialized || !this.physicsEngine) { + return; + } + + // 步进物理引擎 + this.physicsEngine.step(deltaTime); + + // 更新所有活动球体的状态 + for (const [ballId, ballData] of this.activeBalls) { + const updatedData = this.physicsEngine.getBodyData(ballId); + if (updatedData) { + // 更新本地数据 + ballData.position = updatedData.position; + ballData.rotation = updatedData.rotation; + ballData.velocity = updatedData.velocity; + ballData.angularVelocity = updatedData.angularVelocity; + + // 发送更新事件给渲染器 + this.eventBus.emit('ball.updated', { + id: ballId, + position: ballData.position, + rotation: ballData.rotation + }); + } + } + } + + /** + * 清理资源 + */ + private cleanup(): void { + // 清理事件监听器 + this.eventBus.off('input.mouse.click', this.onMouseClick); + this.eventBus.off('input.touch.start', this.onTouchStart); + this.eventBus.off('physics.collision', this.onPhysicsCollision); + + // 清理物理引擎 + if (this.physicsEngine) { + this.physicsEngine.cleanup(); + this.physicsEngine = null; + } + + // 清理数据 + this.activeBalls.clear(); + this.ballCount = 0; + this.isInitialized = false; + + console.log('[StandaloneMode] 资源清理完成'); + } + + /** + * 获取游戏统计信息 + */ + public getGameStats() { + return { + ballCount: this.ballCount, + activeBalls: this.activeBalls.size, + worldSize: { width: this.worldWidth, height: this.worldHeight }, + isInitialized: this.isInitialized + }; + } + + /** + * 重置游戏 + */ + public resetGame(): void { + // 移除所有球体 + for (const ballId of this.activeBalls.keys()) { + this.physicsEngine.removeBody(ballId); + this.eventBus.emit('ball.removed', { id: ballId }); + } + + this.activeBalls.clear(); + this.ballCount = 0; + + console.log('[StandaloneMode] 游戏重置完成'); + } +} \ No newline at end of file diff --git a/client-cocos/assets/scripts/Modules/Pinball/GameModes/StandaloneMode.ts.meta b/client-cocos/assets/scripts/Modules/Pinball/GameModes/StandaloneMode.ts.meta new file mode 100644 index 0000000..f50cb65 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/GameModes/StandaloneMode.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "f1a5b7ea-5782-43d3-9eea-8b5909a93dbd", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Modules/Pinball/Input.meta b/client-cocos/assets/scripts/Modules/Pinball/Input.meta new file mode 100644 index 0000000..49505b2 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Input.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.2.0", + "importer": "directory", + "imported": true, + "uuid": "2121ed3d-9094-4dde-97cb-7ba9081cbd9d", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Modules/Pinball/Input/InputManager.ts b/client-cocos/assets/scripts/Modules/Pinball/Input/InputManager.ts new file mode 100644 index 0000000..d460fde --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Input/InputManager.ts @@ -0,0 +1,385 @@ +/** + * 输入管理器实现 + * 处理鼠标和触摸输入,转换为游戏事件 + */ + +import { Camera, Component, EventMouse, EventTouch, Vec2, Vec3, _decorator } from 'cc'; +import { EventBus } from '../Core/EventBus'; +import { Vector2 } from '../Core/GameData'; +import { + BaseInputEvent, + InputButton, + InputCallback, + InputConfig, + InputEventType, + InputState, + MouseInputEvent, + TouchInputEvent +} from './InputTypes'; + +const { ccclass, property } = _decorator; + +@ccclass +export class InputManager extends Component { + + @property({ tooltip: '启用鼠标输入' }) + enableMouse: boolean = true; + + @property({ tooltip: '启用触摸输入' }) + enableTouch: boolean = true; + + @property({ tooltip: '双击时间间隔(ms)' }) + doubleClickTime: number = 300; + + @property({ tooltip: '长按时间(ms)' }) + longPressTime: number = 500; + + @property({ tooltip: '拖拽阈值(像素)' }) + dragThreshold: number = 10; + + private eventBus: EventBus; + private camera: Camera | null = null; + private inputState: InputState; + private config: InputConfig; + private callbacks: Map = new Map(); + + /** + * 组件初始化 + */ + onLoad(): void { + this.eventBus = EventBus.getInstance(); + this.initializeInputState(); + this.initializeConfig(); + this.setupInputEvents(); + + console.log('InputManager initialized'); + } + + /** + * 初始化输入状态 + */ + private initializeInputState(): void { + this.inputState = { + isMouseDown: false, + isTouchActive: false, + lastClickTime: 0, + lastClickPosition: { x: 0, y: 0 }, + isDragging: false, + dragStartPosition: { x: 0, y: 0 }, + activeTouches: new Map() + }; + } + + /** + * 初始化配置 + */ + private initializeConfig(): void { + this.config = { + enableMouse: this.enableMouse, + enableTouch: this.enableTouch, + doubleClickTime: this.doubleClickTime, + longPressTime: this.longPressTime, + dragThreshold: this.dragThreshold + }; + } + + /** + * 设置相机引用 + */ + setCamera(camera: Camera): void { + this.camera = camera; + } + + /** + * 设置事件回调 + */ + on(eventType: InputEventType, callback: InputCallback): void { + if (!this.callbacks.has(eventType)) { + this.callbacks.set(eventType, []); + } + this.callbacks.get(eventType)!.push(callback as InputCallback); + } + + /** + * 移除事件回调 + */ + off(eventType: InputEventType, callback: InputCallback): void { + const callbacks = this.callbacks.get(eventType); + if (callbacks) { + const index = callbacks.indexOf(callback as InputCallback); + if (index > -1) { + callbacks.splice(index, 1); + } + } + } + + /** + * 设置输入事件监听 + */ + private setupInputEvents(): void { + if (this.config.enableMouse) { + this.node.on('mousedown', this.onMouseDown, this); + this.node.on('mouseup', this.onMouseUp, this); + this.node.on('mousemove', this.onMouseMove, this); + } + + if (this.config.enableTouch) { + this.node.on('touchstart', this.onTouchStart, this); + this.node.on('touchend', this.onTouchEnd, this); + this.node.on('touchmove', this.onTouchMove, this); + } + } + + /** + * 鼠标按下事件 + */ + private onMouseDown(event: EventMouse): void { + const position = this.screenToWorldPosition(event.getUILocation()); + const inputEvent: MouseInputEvent = { + type: InputEventType.MOUSE_DOWN, + position, + screenPosition: { x: event.getUILocation().x, y: event.getUILocation().y }, + timestamp: Date.now(), + button: event.getButton() as InputButton, + ctrlKey: false, // Cocos Creator 3.x 中需要通过其他方式获取 + shiftKey: false, + altKey: false + }; + + this.inputState.isMouseDown = true; + this.inputState.dragStartPosition = position; + + // 检测双击 + const timeSinceLastClick = inputEvent.timestamp - this.inputState.lastClickTime; + if (timeSinceLastClick < this.config.doubleClickTime) { + const distance = this.calculateDistance(position, this.inputState.lastClickPosition); + if (distance < this.config.dragThreshold) { + this.emitDoubleClickEvent(inputEvent); + } + } + + this.inputState.lastClickTime = inputEvent.timestamp; + this.inputState.lastClickPosition = position; + + this.emitEvent(inputEvent); + } + + /** + * 鼠标释放事件 + */ + private onMouseUp(event: EventMouse): void { + const position = this.screenToWorldPosition(event.getUILocation()); + const inputEvent: MouseInputEvent = { + type: InputEventType.MOUSE_UP, + position, + screenPosition: { x: event.getUILocation().x, y: event.getUILocation().y }, + timestamp: Date.now(), + button: event.getButton() as InputButton, + ctrlKey: false, + shiftKey: false, + altKey: false + }; + + this.inputState.isMouseDown = false; + this.inputState.isDragging = false; + + this.emitEvent(inputEvent); + } + + /** + * 鼠标移动事件 + */ + private onMouseMove(event: EventMouse): void { + const position = this.screenToWorldPosition(event.getUILocation()); + const inputEvent: MouseInputEvent = { + type: InputEventType.MOUSE_MOVE, + position, + screenPosition: { x: event.getUILocation().x, y: event.getUILocation().y }, + timestamp: Date.now(), + button: event.getButton() as InputButton, + ctrlKey: false, + shiftKey: false, + altKey: false + }; + + // 检测拖拽开始 + if (this.inputState.isMouseDown && !this.inputState.isDragging) { + const distance = this.calculateDistance(position, this.inputState.dragStartPosition); + if (distance > this.config.dragThreshold) { + this.inputState.isDragging = true; + this.emitDragStartEvent(inputEvent); + } + } + + this.emitEvent(inputEvent); + } + + /** + * 触摸开始事件 + */ + private onTouchStart(event: EventTouch): void { + const touches = event.getAllTouches(); + + for (const touch of touches) { + const position = this.screenToWorldPosition(touch.getUILocation()); + const touchEvent: TouchInputEvent = { + type: InputEventType.TOUCH_START, + position, + screenPosition: { x: touch.getUILocation().x, y: touch.getUILocation().y }, + timestamp: Date.now(), + touchId: touch.getID(), + force: undefined // Cocos Creator 3.x 中 getForce 方法可能不可用 + }; + + this.inputState.activeTouches.set(touch.getID(), touchEvent); + this.inputState.isTouchActive = true; + + this.emitEvent(touchEvent); + } + } + + /** + * 触摸结束事件 + */ + private onTouchEnd(event: EventTouch): void { + const touches = event.getAllTouches(); + + for (const touch of touches) { + const position = this.screenToWorldPosition(touch.getUILocation()); + const touchEvent: TouchInputEvent = { + type: InputEventType.TOUCH_END, + position, + screenPosition: { x: touch.getUILocation().x, y: touch.getUILocation().y }, + timestamp: Date.now(), + touchId: touch.getID(), + force: undefined // Cocos Creator 3.x 中 getForce 方法可能不可用 + }; + + this.inputState.activeTouches.delete(touch.getID()); + if (this.inputState.activeTouches.size === 0) { + this.inputState.isTouchActive = false; + } + + this.emitEvent(touchEvent); + } + } + + /** + * 触摸移动事件 + */ + private onTouchMove(event: EventTouch): void { + const touches = event.getAllTouches(); + + for (const touch of touches) { + const position = this.screenToWorldPosition(touch.getUILocation()); + const touchEvent: TouchInputEvent = { + type: InputEventType.TOUCH_MOVE, + position, + screenPosition: { x: touch.getUILocation().x, y: touch.getUILocation().y }, + timestamp: Date.now(), + touchId: touch.getID(), + force: undefined // Cocos Creator 3.x 中 getForce 方法可能不可用 + }; + + this.emitEvent(touchEvent); + } + } + + /** + * 屏幕坐标转世界坐标 + */ + private screenToWorldPosition(screenPos: Vec2): Vector2 { + if (!this.camera) { + // 如果没有相机,使用简单的坐标转换 + return { + x: (screenPos.x - 400) / 100, // 假设屏幕中心为 (400, 300),缩放 100倍 + y: (300 - screenPos.y) / 100 + }; + } + + // 使用相机进行坐标转换 - 转换为 Vec3 + const screenPos3 = new Vec3(screenPos.x, screenPos.y, 0); + const worldPos = this.camera.screenToWorld(screenPos3); + return { + x: worldPos.x / 100, // 转换为物理世界坐标 + y: worldPos.y / 100 + }; + } + + /** + * 计算两点距离 + */ + private calculateDistance(pos1: Vector2, pos2: Vector2): number { + const dx = pos1.x - pos2.x; + const dy = pos1.y - pos2.y; + return Math.sqrt(dx * dx + dy * dy); + } + + /** + * 发射事件 + */ + private emitEvent(event: BaseInputEvent): void { + const callbacks = this.callbacks.get(event.type); + if (callbacks) { + callbacks.forEach(callback => { + try { + callback(event); + } catch (error) { + console.error(`Error in input callback for ${event.type}:`, error); + } + }); + } + + // 同时通过事件总线发射 + this.eventBus.emit(event.type, event); + } + + /** + * 发射双击事件 + */ + private emitDoubleClickEvent(event: MouseInputEvent): void { + this.eventBus.emit('double_click', event); + } + + /** + * 发射拖拽开始事件 + */ + private emitDragStartEvent(event: MouseInputEvent): void { + this.eventBus.emit('drag_start', event); + } + + /** + * 获取当前输入状态 + */ + getInputState(): InputState { + return { ...this.inputState }; + } + + /** + * 获取事件总线 + */ + getEventBus(): EventBus { + return this.eventBus; + } + + /** + * 清理资源 + */ + onDestroy(): void { + // 移除事件监听 + this.node.off('mousedown', this.onMouseDown, this); + this.node.off('mouseup', this.onMouseUp, this); + this.node.off('mousemove', this.onMouseMove, this); + this.node.off('touchstart', this.onTouchStart, this); + this.node.off('touchend', this.onTouchEnd, this); + this.node.off('touchmove', this.onTouchMove, this); + + // 清理回调 + this.callbacks.clear(); + + // 清理事件总线 + this.eventBus.clear(); + + console.log('InputManager destroyed'); + } +} \ No newline at end of file diff --git a/client-cocos/assets/scripts/Modules/Pinball/Input/InputManager.ts.meta b/client-cocos/assets/scripts/Modules/Pinball/Input/InputManager.ts.meta new file mode 100644 index 0000000..e0fcd1a --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Input/InputManager.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "1a3cf9f0-ddcf-4175-9dc8-632e89c6941e", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Modules/Pinball/Input/InputTypes.ts b/client-cocos/assets/scripts/Modules/Pinball/Input/InputTypes.ts new file mode 100644 index 0000000..7767874 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Input/InputTypes.ts @@ -0,0 +1,71 @@ +/** + * 输入相关类型定义 + */ + +import { Vector2 } from '../Core/GameData'; + +/** 输入事件类型 */ +export enum InputEventType { + MOUSE_DOWN = 'mouse_down', + MOUSE_UP = 'mouse_up', + MOUSE_MOVE = 'mouse_move', + TOUCH_START = 'touch_start', + TOUCH_END = 'touch_end', + TOUCH_MOVE = 'touch_move' +} + +/** 输入按键枚举 */ +export enum InputButton { + LEFT_MOUSE = 0, + RIGHT_MOUSE = 1, + MIDDLE_MOUSE = 2, + TOUCH = 99 +} + +/** 基础输入事件数据 */ +export interface BaseInputEvent { + type: InputEventType; + position: Vector2; // 世界坐标 + screenPosition: Vector2; // 屏幕坐标 + timestamp: number; + button?: InputButton; +} + +/** 鼠标输入事件 */ +export interface MouseInputEvent extends BaseInputEvent { + type: InputEventType.MOUSE_DOWN | InputEventType.MOUSE_UP | InputEventType.MOUSE_MOVE; + button: InputButton; + ctrlKey: boolean; + shiftKey: boolean; + altKey: boolean; +} + +/** 触摸输入事件 */ +export interface TouchInputEvent extends BaseInputEvent { + type: InputEventType.TOUCH_START | InputEventType.TOUCH_END | InputEventType.TOUCH_MOVE; + touchId: number; + force?: number; +} + +/** 输入配置 */ +export interface InputConfig { + enableMouse: boolean; + enableTouch: boolean; + doubleClickTime: number; // 双击时间间隔 (ms) + longPressTime: number; // 长按时间 (ms) + dragThreshold: number; // 拖拽阈值 (像素) +} + +/** 输入回调函数类型 */ +export type InputCallback = (event: T) => void; + +/** 输入状态 */ +export interface InputState { + isMouseDown: boolean; + isTouchActive: boolean; + lastClickTime: number; + lastClickPosition: Vector2; + isDragging: boolean; + dragStartPosition: Vector2; + activeTouches: Map; +} \ No newline at end of file diff --git a/client-cocos/assets/scripts/Modules/Pinball/Input/InputTypes.ts.meta b/client-cocos/assets/scripts/Modules/Pinball/Input/InputTypes.ts.meta new file mode 100644 index 0000000..028b7ed --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Input/InputTypes.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "b07a5b72-454a-4fb1-8dd6-9b39141b85d8", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Modules/Pinball/Network.meta b/client-cocos/assets/scripts/Modules/Pinball/Network.meta new file mode 100644 index 0000000..3b48d24 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Network.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.2.0", + "importer": "directory", + "imported": true, + "uuid": "3cc0736e-7d1c-4771-b861-bfd3525e90d1", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Modules/Pinball/Physics.meta b/client-cocos/assets/scripts/Modules/Pinball/Physics.meta new file mode 100644 index 0000000..5d203b6 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Physics.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.2.0", + "importer": "directory", + "imported": true, + "uuid": "0d17157f-5edf-479c-9296-4008cd31be83", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Modules/Pinball/Physics/PhysicsTypes.ts b/client-cocos/assets/scripts/Modules/Pinball/Physics/PhysicsTypes.ts new file mode 100644 index 0000000..f181b76 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Physics/PhysicsTypes.ts @@ -0,0 +1,75 @@ +/** + * 物理引擎相关类型定义 + */ + +import { Vector2 } from '../Core/GameData'; + +/** WASM 函数签名定义 */ +export interface WasmExports { + // 世界管理 + pinball_create_world(gravity_x: number, gravity_y: number): number; + pinball_step_world(world_id: number): void; + + // 刚体管理 + pinball_create_dynamic_body(world_id: number, x: number, y: number): number; + + // 位置获取 + pinball_get_body_x(world_id: number, body_id: number): number; + pinball_get_body_y(world_id: number, body_id: number): number; + + // 内存管理 + memory: WebAssembly.Memory; +} + +/** WASM 模块状态 */ +export enum WasmModuleState { + UNLOADED = 'unloaded', + LOADING = 'loading', + LOADED = 'loaded', + ERROR = 'error' +} + +/** WASM 配置 */ +export interface WasmConfig { + wasmPath: string; + memoryPages?: number; + importObject?: WebAssembly.Imports; +} + +/** 物理世界配置 */ +export interface PhysicsWorldConfig { + gravity: Vector2; + timeStep: number; + velocityIterations: number; + positionIterations: number; +} + +/** 碰撞体类型 */ +export enum ColliderType { + CIRCLE = 'circle', + BOX = 'box' +} + +/** 碰撞体定义 */ +export interface ColliderDef { + type: ColliderType; + radius?: number; + width?: number; + height?: number; + offset?: Vector2; +} + +/** 刚体类型 */ +export enum BodyType { + STATIC = 'static', + DYNAMIC = 'dynamic', + KINEMATIC = 'kinematic' +} + +/** 刚体定义 */ +export interface BodyDef { + type: BodyType; + position: Vector2; + velocity?: Vector2; + colliders: ColliderDef[]; +} \ No newline at end of file diff --git a/client-cocos/assets/scripts/Modules/Pinball/Physics/PhysicsTypes.ts.meta b/client-cocos/assets/scripts/Modules/Pinball/Physics/PhysicsTypes.ts.meta new file mode 100644 index 0000000..d52eb02 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Physics/PhysicsTypes.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "6a80be32-a5d9-4b97-ace6-ac2c417a91b3", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Modules/Pinball/Physics/WasmPhysicsEngine.ts b/client-cocos/assets/scripts/Modules/Pinball/Physics/WasmPhysicsEngine.ts new file mode 100644 index 0000000..e2b8f2d --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Physics/WasmPhysicsEngine.ts @@ -0,0 +1,411 @@ +/** + * WASM 物理引擎实现 + * 提供与 pinball-physics WASM 模块的接口 + */ + +import { wasmLoader } from '../../../Utils/WasmLoader'; +import { BodyId, PhysicsBodyData, PhysicsSettings, Vector2, WorldId } from '../Core/GameData'; +import { IPhysicsEngine } from '../Core/IPhysicsEngine'; +import { WasmExports, WasmModuleState } from './PhysicsTypes'; + +export class WasmPhysicsEngine implements IPhysicsEngine { + private wasmModule: WebAssembly.Module | null = null; + private wasmInstance: WebAssembly.Instance | null = null; + private wasmExports: WasmExports | null = null; + private state: WasmModuleState = WasmModuleState.UNLOADED; + private worlds: Map = new Map(); + private bodies: Map> = new Map(); + + /** + * 初始化 WASM 物理引擎 + * @param settings 物理设置 + * @param wasmFactory WASM工厂函数(可选,推荐使用) + */ + async initialize(settings: PhysicsSettings, wasmFactory?: any): Promise { + if (this.state === WasmModuleState.LOADED) { + return; + } + + this.state = WasmModuleState.LOADING; + + try { + if (wasmFactory) { + // 使用新的 WasmLoader(推荐方式) + await this.initializeWithWasmLoader(wasmFactory); + } else { + // 回退到旧的加载方式(保持向后兼容) + console.warn('使用旧的 WASM 加载方式,推荐提供 wasmFactory 参数使用 WasmLoader'); + await this.initializeLegacy(); + } + + this.state = WasmModuleState.LOADED; + console.log('WASM Physics Engine initialized successfully'); + + } catch (error) { + this.state = WasmModuleState.ERROR; + console.error('Failed to initialize WASM Physics Engine:', error); + throw error; + } + } + + /** + * 创建 WASM 导入对象 + */ + private createImportObject(): WebAssembly.Imports { + return { + env: { + // 内存管理 + memory: new WebAssembly.Memory({ + initial: 10, + maximum: 100 + }), + + // 日志函数 + console_log: (ptr: number, len: number) => { + // 可以实现 WASM 的日志输出 + }, + + // 错误处理 + abort: (msg: number, file: number, line: number, col: number) => { + console.error('WASM abort:', { msg, file, line, col }); + } + } + }; + } + + /** + * 使用 WasmLoader 初始化(推荐方式) + */ + private async initializeWithWasmLoader(wasmFactory: any): Promise { + // 初始化 WASM 加载器 + wasmLoader.initialize(); + + // 检查平台支持 + if (!wasmLoader.isWasmSupported()) { + throw new Error('当前平台不支持 WASM,需要提供 ASM 回退选项'); + } + + // 加载 WASM 模块 + // 注意:在实际使用时需要提供正确的 editorUuid + const instance = await wasmLoader.loadSimpleWasm( + wasmFactory, + 'pinball_physics.wasm', + undefined, // editorUuid,需要在实际使用时提供 + 'wasm' // bundleName + ); + + this.wasmExports = instance as WasmExports; + } + + /** + * 旧的初始化方法(保持向后兼容) + */ + private async initializeLegacy(): Promise { + // 加载 WASM 文件 + const wasmPath = 'assets/wasm/pinball_physics.wasm'; + const wasmResponse = await fetch(wasmPath); + + if (!wasmResponse.ok) { + throw new Error(`Failed to fetch WASM file: ${wasmResponse.statusText}`); + } + + const wasmBytes = await wasmResponse.arrayBuffer(); + + // 编译 WASM 模块 + this.wasmModule = await WebAssembly.compile(wasmBytes); + + // 创建实例 + const importObject = this.createImportObject(); + this.wasmInstance = await WebAssembly.instantiate(this.wasmModule, importObject); + this.wasmExports = this.wasmInstance.exports as unknown as WasmExports; + } + + /** + * 检查 WASM 是否已加载 + */ + private ensureLoaded(): void { + if (this.state !== WasmModuleState.LOADED || !this.wasmExports) { + throw new Error('WASM Physics Engine not loaded. Call initialize() first.'); + } + } + + /** + * 创建物理世界 + */ + async createWorld(gravity: Vector2): Promise { + this.ensureLoaded(); + + const worldId = this.wasmExports!.pinball_create_world(gravity.x, gravity.y); + this.worlds.set(worldId, true); + this.bodies.set(worldId, new Map()); + + console.log(`Created physics world ${worldId} with gravity (${gravity.x}, ${gravity.y})`); + return worldId; + } + + /** + * 销毁物理世界 + */ + async destroyWorld(worldId: WorldId): Promise { + if (this.worlds.has(worldId)) { + this.worlds.delete(worldId); + this.bodies.delete(worldId); + console.log(`Destroyed physics world ${worldId}`); + } + } + + /** + * 执行物理步进 + */ + async step(worldId: WorldId): Promise { + this.ensureLoaded(); + + if (!this.worlds.has(worldId)) { + throw new Error(`World ${worldId} does not exist`); + } + + this.wasmExports!.pinball_step_world(worldId); + } + + /** + * 创建动态刚体 + */ + async createDynamicBody(worldId: WorldId, position: Vector2, radius: number): Promise { + this.ensureLoaded(); + + if (!this.worlds.has(worldId)) { + throw new Error(`World ${worldId} does not exist`); + } + + const bodyId = this.wasmExports!.pinball_create_dynamic_body(worldId, position.x, position.y); + + // 记录刚体 + const worldBodies = this.bodies.get(worldId)!; + worldBodies.set(bodyId, true); + + console.log(`Created dynamic body ${bodyId} at (${position.x}, ${position.y}) with radius ${radius}`); + return bodyId; + } + + /** + * 创建静态刚体(暂时使用动态刚体实现) + */ + async createStaticBody(worldId: WorldId, position: Vector2, radius: number): Promise { + // 目前 WASM 中只有 pinball_create_dynamic_body,后续可以扩展 + return this.createDynamicBody(worldId, position, radius); + } + + /** + * 销毁刚体(目前 WASM 中没有此函数,仅从记录中移除) + */ + async destroyBody(worldId: WorldId, bodyId: BodyId): Promise { + const worldBodies = this.bodies.get(worldId); + if (worldBodies && worldBodies.has(bodyId)) { + worldBodies.delete(bodyId); + console.log(`Removed body ${bodyId} from tracking (WASM destroy not implemented yet)`); + } + } + + /** + * 获取刚体位置 + */ + async getBodyPosition(worldId: WorldId, bodyId: BodyId): Promise { + this.ensureLoaded(); + + const worldBodies = this.bodies.get(worldId); + if (!worldBodies || !worldBodies.has(bodyId)) { + return null; + } + + const x = this.wasmExports!.pinball_get_body_x(worldId, bodyId); + const y = this.wasmExports!.pinball_get_body_y(worldId, bodyId); + + return { x, y }; + } + + /** + * 设置刚体位置(目前 WASM 中没有此函数) + */ + async setBodyPosition(worldId: WorldId, bodyId: BodyId, position: Vector2): Promise { + // 目前 WASM 中没有 pinball_set_body_position 函数 + console.warn('setBodyPosition not implemented in WASM yet'); + } + + /** + * 获取刚体速度(目前 WASM 中没有此函数) + */ + async getBodyVelocity(worldId: WorldId, bodyId: BodyId): Promise { + // 目前 WASM 中没有速度获取函数 + console.warn('getBodyVelocity not implemented in WASM yet'); + return { x: 0, y: 0 }; + } + + /** + * 设置刚体速度(目前 WASM 中没有此函数) + */ + async setBodyVelocity(worldId: WorldId, bodyId: BodyId, velocity: Vector2): Promise { + // 目前 WASM 中没有 pinball_set_body_velocity 函数 + console.warn('setBodyVelocity not implemented in WASM yet'); + } + + /** + * 获取所有物理体数据 + */ + async getAllBodies(worldId: WorldId): Promise { + const worldBodies = this.bodies.get(worldId); + if (!worldBodies) { + return []; + } + + const bodiesData: PhysicsBodyData[] = []; + + for (const bodyId of worldBodies.keys()) { + const position = await this.getBodyPosition(worldId, bodyId); + const velocity = await this.getBodyVelocity(worldId, bodyId); + + if (position && velocity) { + bodiesData.push({ + id: bodyId, + position, + velocity, + rotation: 0, + angularVelocity: 0, + bodyType: 'circle', + radius: 0.5, // 默认半径,可以后续改进 + isStatic: false // 可以根据需要区分静态和动态 + }); + } + } + + return bodiesData; + } + + /** + * 创建圆形刚体 (新接口方法) + */ + createCircle(options: import('../Core/IPhysicsEngine').CreateCircleOptions): BodyId { + this.ensureLoaded(); + + // 由于目前使用第一个世界,获取第一个世界ID + const firstWorldId = this.worlds.keys().next().value; + if (firstWorldId === undefined) { + throw new Error('No physics world created'); + } + + // 目前WASM只支持动态刚体创建 + const bodyId = this.wasmExports!.pinball_create_dynamic_body(firstWorldId, options.position.x, options.position.y); + + this.bodies.get(firstWorldId)!.set(bodyId, true); + return bodyId; + } + + /** + * 创建矩形刚体 (新接口方法) + */ + createBox(options: import('../Core/IPhysicsEngine').CreateBoxOptions): BodyId { + this.ensureLoaded(); + + const firstWorldId = this.worlds.keys().next().value; + if (firstWorldId === undefined) { + throw new Error('No physics world created'); + } + + // 目前WASM只支持动态刚体创建,矩形用动态刚体模拟 + const bodyId = this.wasmExports!.pinball_create_dynamic_body(firstWorldId, options.position.x, options.position.y); + + this.bodies.get(firstWorldId)!.set(bodyId, true); + return bodyId; + } + + /** + * 移除刚体 (新接口方法) + */ + removeBody(bodyId: BodyId): void { + // 查找包含此bodyId的世界 + for (const [worldId, worldBodies] of this.bodies) { + if (worldBodies.has(bodyId)) { + worldBodies.delete(bodyId); + // 注意:这里需要WASM支持删除函数 + // this.wasmExports!.pinball_remove_body(worldId, bodyId); + console.log(`Body ${bodyId} removed from world ${worldId}`); + return; + } + } + } + + /** + * 获取刚体数据 (新接口方法) + */ + getBodyData(bodyId: BodyId): PhysicsBodyData | null { + // 查找包含此bodyId的世界 + for (const [worldId, worldBodies] of this.bodies) { + if (worldBodies.has(bodyId)) { + // 这里需要同步调用WASM函数获取数据 + try { + const x = this.wasmExports!.pinball_get_body_x(worldId, bodyId); + const y = this.wasmExports!.pinball_get_body_y(worldId, bodyId); + // WASM暂时不支持速度获取,使用默认值 + const vx = 0; + const vy = 0; + + return { + id: bodyId, + position: { x, y }, + velocity: { x: vx, y: vy }, + rotation: 0, // 暂时固定值 + angularVelocity: 0, // 暂时固定值 + bodyType: 'circle', + radius: 0.5, // 暂时固定值 + isStatic: false // 暂时固定值 + }; + } catch (error) { + console.error(`Failed to get body data for body ${bodyId}:`, error); + return null; + } + } + } + return null; + } + + /** + * 清理资源 (新接口方法) + */ + cleanup(): void { + this.dispose(); // 异步转同步 + } + + /** + * 清理资源 + */ + async dispose(): Promise { + this.worlds.clear(); + this.bodies.clear(); + this.wasmInstance = null; + this.wasmExports = null; + this.wasmModule = null; + this.state = WasmModuleState.UNLOADED; + + console.log('WASM Physics Engine disposed'); + } + + /** + * 获取当前状态 + */ + getState(): WasmModuleState { + return this.state; + } + + /** + * 检查平台是否支持WASM(使用WasmLoader) + */ + isWasmSupported(): boolean { + return wasmLoader.isWasmSupported(); + } + + /** + * 获取推荐的加载策略 + */ + getRecommendedStrategy(): 'wasm' | 'asm' | 'unsupported' { + return wasmLoader.getRecommendedStrategy(); + } +} \ No newline at end of file diff --git a/client-cocos/assets/scripts/Modules/Pinball/PinballManager.ts b/client-cocos/assets/scripts/Modules/Pinball/PinballManager.ts new file mode 100644 index 0000000..7ade0a1 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/PinballManager.ts @@ -0,0 +1,400 @@ +/** + * PinballManager - Pinball模块主控制器 + * 负责整个Pinball模块的生命周期管理和模式切换 + */ + +import { _decorator, Camera, Component, director, Director, game, Game, Node, Scene } from 'cc'; +import { EventBus } from './Core/EventBus'; +import { GameState, PinballConfig } from './Core/GameData'; +import { StandaloneMode } from './GameModes/StandaloneMode'; + +const { ccclass, property } = _decorator; + +export enum PinballMode { + STANDALONE = 'standalone', + CLIENT_MULTIPLAYER = 'client-multiplayer', + SERVER_MULTIPLAYER = 'server-multiplayer' +} + +@ccclass('PinballManager') +export class PinballManager extends Component { + + @property({ + type: Node, + tooltip: "主相机节点,用于渲染游戏画面" + }) + cameraNode: Node = null; + + @property({ + type: Node, + tooltip: "渲染容器节点,所有游戏对象将在此节点下渲染" + }) + renderContainer: Node = null; + + @property({ + type: Node, + tooltip: "UI容器节点,用于显示游戏UI界面" + }) + uiContainer: Node = null; + + @property({ + tooltip: "启动时的默认游戏模式" + }) + defaultMode: PinballMode = PinballMode.STANDALONE; + + @property({ + tooltip: "是否在启动时自动开始游戏" + }) + autoStart: boolean = true; + + @property({ + tooltip: "调试模式,启用详细日志" + }) + debugMode: boolean = true; + + // 核心系统 + private eventBus: EventBus = null; + private currentMode: PinballMode = null; + private currentGameMode: Component = null; + private mainCamera: Camera = null; + + // 游戏状态 + private isInitialized: boolean = false; + private isPaused: boolean = false; + private gameConfig: PinballConfig = null; + + /** + * 启动 PinballManager + * 必须在配置应用后调用 + */ + async Start(): Promise { + this.log('[PinballManager] Start 开始'); + + try { + // 初始化事件总线 + this.eventBus = EventBus.getInstance(); + + // 验证必需的节点 + if (!this.validateRequiredNodes()) { + console.error('[PinballManager] 必需节点验证失败'); + return false; + } + + // 获取主相机 + this.initializeCamera(); + + // 创建默认配置 + this.createDefaultConfig(); + + // 注册全局事件监听器 + this.registerGlobalEvents(); + + // 初始化完成 + this.isInitialized = true; + this.log('[PinballManager] 初始化完成'); + + this.log('[PinballManager] Start 完成'); + return true; + } catch (error) { + console.error('[PinballManager] Start 过程中发生错误:', error); + return false; + } + } + + /** + * 停止 PinballManager + */ + Stop(): void { + this.log('[PinballManager] Stop 开始'); + this.cleanup(); + this.isInitialized = false; + this.log('[PinballManager] Stop 完成'); + } + + // 保留 onDestroy 作为安全清理 + onDestroy() { + this.Stop(); + } + + /** + * 验证必需的节点 + */ + private validateRequiredNodes(): boolean { + if (!this.cameraNode) { + console.error('[PinballManager] cameraNode 未设置'); + return false; + } + + if (!this.renderContainer) { + console.error('[PinballManager] renderContainer 未设置'); + return false; + } + + return true; + } + + /** + * 初始化相机 + */ + private initializeCamera(): void { + this.mainCamera = this.cameraNode.getComponent(Camera); + if (!this.mainCamera) { + console.error('[PinballManager] 主相机组件未找到'); + return; + } + + this.log('主相机初始化完成'); + } + + /** + * 创建默认配置 + */ + private createDefaultConfig(): void { + this.gameConfig = { + mode: this.defaultMode, + physicsSettings: { + gravity: { x: 0, y: -9.81 }, + timeStep: 1 / 60 + }, + renderSettings: { + enableEffects: true, + maxParticles: 500 + }, + wasmPath: 'assets/wasm/pinball_physics.wasm' + }; + + this.log('默认配置创建完成', this.gameConfig); + } + + /** + * 注册全局事件监听器 + */ + private registerGlobalEvents(): void { + // 监听场景切换事件 - Cocos Creator 3.x 中使用静态常量 + director.on(Director.EVENT_BEFORE_SCENE_LAUNCH, this.onSceneChange, this); + + // 监听游戏暂停/恢复 - Cocos Creator 3.x 中使用game事件 + game.on(Game.EVENT_HIDE, this.onGamePause, this); + game.on(Game.EVENT_SHOW, this.onGameResume, this); + + this.log('全局事件监听器注册完成'); + } + + /** + * 启动游戏 + */ + public async startGame(mode: PinballMode): Promise { + if (!this.isInitialized) { + console.error('[PinballManager] 尚未初始化'); + return false; + } + + if (this.currentGameMode) { + await this.stopCurrentGame(); + } + + this.log(`开始启动游戏模式: ${mode}`); + + try { + switch (mode) { + case PinballMode.STANDALONE: + await this.startStandaloneMode(); + break; + + case PinballMode.CLIENT_MULTIPLAYER: + console.warn('[PinballManager] Client Multiplayer 模式尚未实现'); + return false; + + case PinballMode.SERVER_MULTIPLAYER: + console.warn('[PinballManager] Server Multiplayer 模式尚未实现'); + return false; + + default: + console.error(`[PinballManager] 未知游戏模式: ${mode}`); + return false; + } + + this.currentMode = mode; + this.gameConfig.mode = mode; + + this.log(`游戏模式 ${mode} 启动成功`); + + // 发送游戏开始事件 + this.eventBus.emit('game.started', { mode }); + + return true; + + } catch (error) { + console.error(`[PinballManager] 启动游戏模式 ${mode} 失败:`, error); + return false; + } + } + + /** + * 启动Standalone模式 + */ + private async startStandaloneMode(): Promise { + // 添加StandaloneMode组件 + const standaloneMode = this.node.addComponent(StandaloneMode); + + // 设置所需的引用 + standaloneMode.gameCamera = this.mainCamera; + standaloneMode.renderNode = this.renderContainer; + standaloneMode.boundsNode = this.renderContainer; // 简化设置 + + // StandaloneMode 会在 onLoad 中自动初始化 + this.currentGameMode = standaloneMode; + + this.log('Standalone模式启动完成'); + } + + /** + * 停止当前游戏模式 + */ + public async stopCurrentGame(): Promise { + if (!this.currentGameMode) { + return; + } + + this.log(`停止当前游戏模式: ${this.currentMode}`); + + // 销毁当前游戏模式组件 + this.currentGameMode.destroy(); + this.currentGameMode = null; + this.currentMode = null; + + // 发送游戏停止事件 + this.eventBus.emit('game.stopped', {}); + + this.log('当前游戏模式已停止'); + } + + /** + * 暂停游戏 + */ + public pauseGame(): void { + if (this.isPaused) { + return; + } + + this.isPaused = true; + this.eventBus.emit('game.paused', {}); + this.log('游戏已暂停'); + } + + /** + * 恢复游戏 + */ + public resumeGame(): void { + if (!this.isPaused) { + return; + } + + this.isPaused = false; + this.eventBus.emit('game.resumed', {}); + this.log('游戏已恢复'); + } + + /** + * 重启当前游戏 + */ + public async restartGame(): Promise { + const currentMode = this.currentMode; + + if (currentMode) { + await this.stopCurrentGame(); + return await this.startGame(currentMode); + } + + return false; + } + + /** + * 切换游戏模式 + */ + public async switchMode(newMode: PinballMode): Promise { + if (this.currentMode === newMode) { + this.log(`已经是 ${newMode} 模式`); + return true; + } + + return await this.startGame(newMode); + } + + /** + * 场景切换事件处理 + */ + private onSceneChange(scene: Scene): void { + this.log('场景切换事件'); + // 可以在此处理场景切换时的清理工作 + } + + /** + * 游戏暂停事件处理 + */ + private onGamePause(): void { + this.pauseGame(); + } + + /** + * 游戏恢复事件处理 + */ + private onGameResume(): void { + this.resumeGame(); + } + + /** + * 清理资源 + */ + private cleanup(): void { + // 清理全局事件监听器 + director.off(Director.EVENT_BEFORE_SCENE_LAUNCH, this.onSceneChange, this); + game.off(Game.EVENT_HIDE, this.onGamePause, this); + game.off(Game.EVENT_SHOW, this.onGameResume, this); + + // 停止当前游戏 + if (this.currentGameMode) { + this.stopCurrentGame(); + } + + this.log('PinballManager 资源清理完成'); + } + + /** + * 获取当前游戏状态 + */ + public getGameState(): GameState | null { + if (this.currentGameMode && this.currentGameMode instanceof StandaloneMode) { + return this.currentGameMode.getGameStats() as any; + } + return null; + } + + /** + * 获取游戏配置 + */ + public getConfig(): PinballConfig { + return this.gameConfig; + } + + /** + * 更新游戏配置 + */ + public updateConfig(newConfig: Partial): void { + this.gameConfig = { ...this.gameConfig, ...newConfig }; + this.log('游戏配置已更新', this.gameConfig); + } + + /** + * 调试日志输出 + */ + private log(message: string, data?: any): void { + if (this.debugMode) { + if (data) { + console.log(`[PinballManager] ${message}`, data); + } else { + console.log(`[PinballManager] ${message}`); + } + } + } +} \ No newline at end of file diff --git a/client-cocos/assets/scripts/Modules/Pinball/PinballManager.ts.meta b/client-cocos/assets/scripts/Modules/Pinball/PinballManager.ts.meta new file mode 100644 index 0000000..e61f70f --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/PinballManager.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "03de2ced-4b54-4dac-8773-b078be08a33f", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Modules/Pinball/Renderer.meta b/client-cocos/assets/scripts/Modules/Pinball/Renderer.meta new file mode 100644 index 0000000..13f523e --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Renderer.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.2.0", + "importer": "directory", + "imported": true, + "uuid": "27270c02-6c7f-448a-922d-12d7734844de", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Modules/Pinball/Renderer/PinballRenderer.ts b/client-cocos/assets/scripts/Modules/Pinball/Renderer/PinballRenderer.ts new file mode 100644 index 0000000..3510fc0 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Renderer/PinballRenderer.ts @@ -0,0 +1,292 @@ +/** + * Pinball 渲染器实现 + * 负责渲染物理对象、粒子效果等 + */ + +import { _decorator, Camera, Color, Component, Graphics, Node, Prefab, Sprite, tween, Vec3 } from 'cc'; +import { PhysicsBodyData, Vector2 } from '../Core/GameData'; +import { IRenderer, ParticleEffect, RenderObject } from '../Core/IRenderer'; + +const { ccclass, property } = _decorator; + + +@ccclass +export class PinballRenderer extends Component implements IRenderer { + + @property(Node) + private renderContainer: Node = null!; + + @property(Prefab) + private ballPrefab: Prefab = null!; + + private renderObjects: Map = new Map(); + private particlePool: Node[] = []; + private camera: Camera = null!; + + /** + * 设置相机 + */ + setCamera(camera: Camera): void { + this.camera = camera; + console.log('Camera set for PinballRenderer'); + } + + /** + * 设置世界边界 + */ + setWorldBounds(width: number, height: number): void { + console.log(`World bounds set to ${width} x ${height}`); + // 可以在此处设置渲染边界或背景 + } + + /** + * 初始化渲染器 + */ + async initialize(parentNode: any): Promise { + console.log('Initializing PinballRenderer...'); + + // 设置渲染容器 + if (parentNode) { + this.renderContainer = parentNode; + } else { + this.renderContainer = this.node; + } + + // 获取相机 - 在 Cocos Creator 3.x 中需要通过场景查找 + const cameraNode = this.renderContainer.scene?.getChildByName('Main Camera'); + if (cameraNode) { + this.camera = cameraNode.getComponent(Camera); + } + + if (!this.camera) { + console.warn('Main camera not found, creating default camera'); + const cameraNode = new Node('PinballCamera'); + this.camera = cameraNode.addComponent(Camera); + this.renderContainer.addChild(cameraNode); + } + + // 创建默认球体预制件(如果没有提供) + if (!this.ballPrefab) { + await this.createDefaultBallPrefab(); + } + + console.log('PinballRenderer initialized successfully'); + } + + /** + * 创建默认的球体预制件 + */ + private async createDefaultBallPrefab(): Promise { + // 创建一个简单的圆形节点作为默认球体 + const ballNode = new Node('DefaultBall'); + + // 添加 Sprite 组件 + const sprite = ballNode.addComponent(Sprite); + + // 创建圆形材质 + const graphics = ballNode.addComponent(Graphics); + graphics.fillColor = Color.WHITE; + graphics.circle(0, 0, 25); // 半径 25 像素 + graphics.fill(); + + console.log('Created default ball prefab'); + } + + /** + * 渲染物理体 + */ + renderBodies(bodies: PhysicsBodyData[]): void { + // 清理不存在的渲染对象 + const currentBodyIds = new Set(bodies.map(body => body.id.toString())); + for (const [id, node] of this.renderObjects) { + if (!currentBodyIds.has(id)) { + this.removeRenderObject(id); + } + } + + // 更新或创建渲染对象 + for (const body of bodies) { + const bodyIdStr = body.id.toString(); + let renderNode = this.renderObjects.get(bodyIdStr); + + if (!renderNode) { + // 创建新的渲染对象 + const renderObject = this.createRenderObject(body); + renderNode = this.createRenderNode(renderObject); + this.renderObjects.set(bodyIdStr, renderNode); + this.renderContainer.addChild(renderNode); + } + + // 更新位置 + renderNode.setPosition(body.position.x * 100, body.position.y * 100); // 放大显示 + } + } + + /** + * 创建渲染对象数据 + */ + createRenderObject(body: PhysicsBodyData): RenderObject { + return { + id: body.id.toString(), + position: body.position, + radius: body.radius, + color: body.isStatic ? + { r: 128, g: 128, b: 128, a: 255 } : // 静态物体:灰色 + { r: 255, g: 100, b: 100, a: 255 }, // 动态物体:红色 + layer: 0 + }; + } + + /** + * 创建渲染节点 + */ + private createRenderNode(renderObject: RenderObject): Node { + const node = new Node(`Ball_${renderObject.id}`); + + // 添加 Graphics 组件用于绘制圆形 + const graphics = node.addComponent(Graphics); + graphics.fillColor = new Color( + renderObject.color.r, + renderObject.color.g, + renderObject.color.b, + renderObject.color.a + ); + + const radius = Math.max(renderObject.radius * 100, 10); // 最小半径 10 像素 + graphics.circle(0, 0, radius); + graphics.fill(); + + // 添加边框 + graphics.strokeColor = Color.BLACK; + graphics.lineWidth = 2; + graphics.circle(0, 0, radius); + graphics.stroke(); + + return node; + } + + /** + * 更新渲染对象 + */ + updateRenderObject(renderObject: RenderObject, body: PhysicsBodyData): void { + const node = this.renderObjects.get(renderObject.id); + if (node) { + // 更新位置 + node.setPosition(body.position.x * 100, body.position.y * 100); + + // 可以在此添加更多属性的更新,如颜色、大小等 + } + } + + /** + * 移除渲染对象 + */ + removeRenderObject(bodyId: string): void { + const node = this.renderObjects.get(bodyId); + if (node) { + node.removeFromParent(); + this.renderObjects.delete(bodyId); + console.log(`Removed render object ${bodyId}`); + } + } + + /** + * 播放粒子效果 + */ + playParticleEffect(effect: ParticleEffect): void { + // 创建简单的粒子效果节点 + const particleNode = this.getParticleNode(); + particleNode.setPosition(effect.position.x * 100, effect.position.y * 100); + + // 使用 Cocos Creator 3.x 的 tween 系统 + tween(particleNode) + .parallel( + tween().to(effect.lifetime, { opacity: 0 }), + tween().by(effect.lifetime, { + position: new Vec3(effect.velocity.x * 50, effect.velocity.y * 50, 0) + }) + ) + .call(() => { + this.recycleParticleNode(particleNode); + }) + .start(); + + this.renderContainer.addChild(particleNode); + } + + /** + * 获取粒子节点(对象池) + */ + private getParticleNode(): Node { + if (this.particlePool.length > 0) { + return this.particlePool.pop()!; + } + + const node = new Node('Particle'); + const graphics = node.addComponent(Graphics); + graphics.fillColor = Color.YELLOW; + graphics.circle(0, 0, 3); + graphics.fill(); + + return node; + } + + /** + * 回收粒子节点 + */ + private recycleParticleNode(node: Node): void { + node.removeFromParent(); + // 重置透明度 - 在 Cocos Creator 3.x 中通过 UIOpacity 组件 + const uiOpacity = node.getComponent('cc.UIOpacity') as any; + if (uiOpacity) { + uiOpacity.opacity = 255; + } + node.setPosition(0, 0, 0); // 重置位置 + this.particlePool.push(node); + } + + /** + * 清除所有渲染对象 + */ + clear(): void { + for (const [id, node] of this.renderObjects) { + node.removeFromParent(); + } + this.renderObjects.clear(); + + // 清理粒子池 + for (const particle of this.particlePool) { + particle.removeFromParent(); + } + this.particlePool = []; + + console.log('Cleared all render objects'); + } + + /** + * 设置相机位置 + */ + setCameraPosition(position: Vector2): void { + if (this.camera) { + this.camera.node.setPosition(position.x * 100, position.y * 100); + } + } + + /** + * 设置相机缩放 + */ + setCameraZoom(zoom: number): void { + if (this.camera) { + // 在 Cocos Creator 3.x 中使用 orthoHeight 控制缩放 + this.camera.orthoHeight = 600 / zoom; // 基础高度 600 + } + } + + /** + * 销毁渲染器 + */ + dispose(): void { + this.clear(); + console.log('PinballRenderer disposed'); + } +} \ No newline at end of file diff --git a/client-cocos/assets/scripts/Modules/Pinball/Renderer/PinballRenderer.ts.meta b/client-cocos/assets/scripts/Modules/Pinball/Renderer/PinballRenderer.ts.meta new file mode 100644 index 0000000..660a6c2 --- /dev/null +++ b/client-cocos/assets/scripts/Modules/Pinball/Renderer/PinballRenderer.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "da8038e2-7c90-4e3e-a3f6-4f8b88b3a11d", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Utils.meta b/client-cocos/assets/scripts/Utils.meta new file mode 100644 index 0000000..4c8befa --- /dev/null +++ b/client-cocos/assets/scripts/Utils.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.2.0", + "importer": "directory", + "imported": true, + "uuid": "8d22d76a-17c1-48bd-a151-d2ed14a97029", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Utils/WasmLoader.ts b/client-cocos/assets/scripts/Utils/WasmLoader.ts new file mode 100644 index 0000000..2b82380 --- /dev/null +++ b/client-cocos/assets/scripts/Utils/WasmLoader.ts @@ -0,0 +1,321 @@ +/** + * WASM加载器 + * 基于Cocos Creator官方文档的WASM/ASM加载最佳实践 + * https://docs.cocos.com/creator/3.8/manual/zh/advanced-topics/wasm-asm-load.html + */ + +import { Asset, assetManager, sys } from 'cc'; +import { EDITOR } from 'cc/env'; + +// ASM模块内存配置常量 +const PAGESIZE = 65536; // 64KiB +const PAGECOUNT = 32 * 16; // 可根据需要调整 +const MEMORYSIZE = PAGESIZE * PAGECOUNT; // 32 MiB + + +export interface WasmLoadOptions { + bundleName?: string; + fileName: string; + editorUuid?: string; // 编辑器内的资源UUID + supportAsm?: boolean; // 是否支持ASM回退 + asmFileName?: string; // ASM文件名 + asmEditorUuid?: string; // ASM文件的编辑器UUID + wasmFactory?: any; // WASM工厂函数 + asmFactory?: any; // ASM工厂函数 +} + +export interface WasmLoadResult { + instance: any; + isWasm: boolean; // true为WASM,false为ASM +} + +export class WasmLoader { + private static instance: WasmLoader; + private initialized = false; + + private constructor() { } + + public static getInstance(): WasmLoader { + if (!WasmLoader.instance) { + WasmLoader.instance = new WasmLoader(); + } + return WasmLoader.instance; + } + + /** + * 初始化WASM加载器 + * 注册WASM/ASM文件的下载器和解析器 + */ + public initialize(): void { + if (this.initialized) { + return; + } + + // 判断当前平台是否支持加载WASM文件 + // Cocos引擎目前暂不支持在iOS平台加载WASM文件 + if (sys.hasFeature(sys.Feature.WASM) || (sys.isNative && sys.os !== sys.OS.IOS)) { + if (sys.isNative) { + // 原生平台 + //@ts-ignore + assetManager.downloader.register('.wasm', assetManager.downloader._downloaders[".bin"]); + //@ts-ignore + assetManager.parser.register('.wasm', assetManager.parser._parsers[".bin"]); + } else if (sys.isBrowser || sys.platform === sys.Platform.WECHAT_GAME) { + // 浏览器或微信小游戏平台 + //@ts-ignore + assetManager.downloader.register('.wasm', assetManager.downloader._downloadArrayBuffer); + //@ts-ignore + assetManager.downloader.register('.mem', assetManager.downloader._downloadArrayBuffer); + } + } else { + // 不支持WASM的平台,注册ASM相关文件 + if (sys.isNative) { + //@ts-ignore + assetManager.downloader.register('.mem', assetManager.downloader._downloaders[".bin"]); + //@ts-ignore + assetManager.parser.register('.mem', assetManager.parser._parsers[".bin"]); + } + } + + this.initialized = true; + console.log('[WasmLoader] 初始化完成'); + } + + /** + * 加载WASM模块 + * @param options 加载配置选项(必须包含工厂函数) + * @returns Promise + */ + public async loadWasmModule(options: WasmLoadOptions): Promise { + if (!this.initialized) { + this.initialize(); + } + + // 判断是否支持WASM + if (sys.hasFeature(sys.Feature.WASM) || (sys.isNative && sys.os !== sys.OS.IOS)) { + // 加载WASM + return this.loadWasm(options); + } else if (options.supportAsm && options.asmFileName && options.asmFactory) { + // 回退到ASM + return this.loadAsm(options); + } else { + throw new Error('当前平台不支持WASM,且未配置ASM回退选项'); + } + } + + /** + * 加载WASM + */ + private async loadWasm(options: WasmLoadOptions): Promise { + if (!options.wasmFactory) { + throw new Error('WASM工厂函数未提供'); + } + + try { + const wasmFactory = options.wasmFactory; + + // 加载WASM二进制文件 + const wasmFile = await this.loadWasmFile( + options.bundleName || '', + options.fileName, + options.editorUuid + ); + + // 初始化WASM + const instance = await this.initWasm(wasmFactory, wasmFile); + + return { + instance, + isWasm: true + }; + } catch (error) { + console.error('[WasmLoader] WASM加载失败:', error); + throw error; + } + } + + /** + * 加载ASM + */ + private async loadAsm(options: WasmLoadOptions): Promise { + if (!options.asmFactory) { + throw new Error('ASM工厂函数未提供'); + } + + try { + const asmFactory = options.asmFactory; + + // 加载ASM内存文件 + const asmFile = await this.loadWasmFile( + options.bundleName || '', + options.asmFileName || options.fileName, + options.asmEditorUuid + ); + + // 初始化ASM + const instance = await this.initAsm(asmFactory, asmFile); + + return { + instance, + isWasm: false + }; + } catch (error) { + console.error('[WasmLoader] ASM加载失败:', error); + throw error; + } + } + + /** + * 加载WASM/ASM二进制文件 + */ + private loadWasmFile(bundleName: string, fileName: string, editorUuid?: string): Promise { + return new Promise((resolve, reject) => { + if (EDITOR) { + // 编辑器内通过UUID加载资源 + if (editorUuid) { + assetManager.loadAny(editorUuid, (err, file: Asset) => { + if (!err) { + resolve(file); + } else { + reject(err); + } + }); + } else { + reject(new Error('编辑器环境下需要提供editorUuid')); + } + } else { + // 运行时通过Bundle加载 + if (bundleName && fileName) { + assetManager.loadBundle(bundleName, (err, bundle) => { + if (!err) { + bundle.load(fileName, Asset, (err2: any, file: Asset) => { + if (!err2) { + resolve(file); + } else { + reject(err2); + } + }); + } else { + reject(err); + } + }); + } else { + reject(new Error('运行时环境下需要提供bundleName和fileName')); + } + } + }); + } + + /** + * 初始化WASM模块 + */ + private initWasm(wasmFactory: any, wasmFile: Asset): Promise { + const self = this; + return new Promise((resolve, reject) => { + wasmFactory({ + instantiateWasm(importObject: WebAssembly.Imports, receiveInstance: any) { + self.instantiateWasm(wasmFile, importObject).then((result) => { + receiveInstance(result.instance, result.module); + }).catch((err) => reject(err)); + } + }).then((instance: any) => { + resolve(instance); + }).catch((err: any) => reject(err)); + }); + } + + /** + * 实例化WASM + */ + private instantiateWasm(wasmFile: Asset, importObject: WebAssembly.Imports): Promise { + if (sys.isBrowser || sys.isNative) { + //@ts-ignore + return WebAssembly.instantiate(wasmFile._file, importObject); + } else if (sys.platform === sys.Platform.WECHAT_GAME) { + //@ts-ignore + return CCWebAssembly.instantiate(wasmFile.nativeUrl, importObject); + } else { + return Promise.reject(new Error('不支持的平台')); + } + } + + /** + * 初始化ASM模块 + */ + private initAsm(asmFactory: any, asmFile: Asset): Promise { + const asmMemory: any = {}; + asmMemory.buffer = new ArrayBuffer(MEMORYSIZE); + + const module = { + asmMemory, + memoryInitializerRequest: { + //@ts-ignore + response: asmFile._file, + status: 200, + } as Partial, + }; + + return asmFactory(module); + } + + /** + * 便捷方法:加载简单的WASM模块(仅WASM,不支持ASM回退) + */ + public async loadSimpleWasm(wasmFactory: any, fileName: string, editorUuid?: string, bundleName?: string): Promise { + const result = await this.loadWasmModule({ + fileName, + editorUuid, + bundleName, + supportAsm: false, + wasmFactory + }); + return result.instance; + } + + /** + * 便捷方法:加载支持ASM回退的WASM模块 + */ + public async loadWasmWithAsmFallback( + wasmFactory: any, + asmFactory: any, + fileName: string, + asmFileName: string, + editorUuid?: string, + asmEditorUuid?: string, + bundleName?: string + ): Promise { + return this.loadWasmModule({ + fileName, + editorUuid, + bundleName, + supportAsm: true, + asmFileName, + asmEditorUuid, + wasmFactory, + asmFactory + }); + } + + /** + * 检查当前平台是否支持WASM + */ + public isWasmSupported(): boolean { + return sys.hasFeature(sys.Feature.WASM) || (sys.isNative && sys.os !== sys.OS.IOS); + } + + /** + * 获取推荐的加载策略 + */ + public getRecommendedStrategy(): 'wasm' | 'asm' | 'unsupported' { + if (this.isWasmSupported()) { + return 'wasm'; + } else if (sys.isNative || sys.isBrowser) { + return 'asm'; + } else { + return 'unsupported'; + } + } +} + +// 导出单例实例 +export const wasmLoader = WasmLoader.getInstance(); \ No newline at end of file diff --git a/client-cocos/assets/scripts/Utils/WasmLoader.ts.meta b/client-cocos/assets/scripts/Utils/WasmLoader.ts.meta new file mode 100644 index 0000000..abb94bd --- /dev/null +++ b/client-cocos/assets/scripts/Utils/WasmLoader.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "1a752554-ae7a-4ac3-b468-31ad25757d52", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/scripts/Utils/WasmLoaderExample.ts b/client-cocos/assets/scripts/Utils/WasmLoaderExample.ts new file mode 100644 index 0000000..ab59350 --- /dev/null +++ b/client-cocos/assets/scripts/Utils/WasmLoaderExample.ts @@ -0,0 +1,128 @@ +/** + * WasmLoader 使用示例 + * + * 这个文件展示如何使用 WasmLoader 来加载 WASM 模块 + */ + +import { wasmLoader } from './WasmLoader'; + +// 假设我们有一个 WASM 工厂函数(需要从 JS 胶水代码中导入) +// import wasmFactory from '../path/to/your-wasm-module.js'; + +export class WasmLoaderExample { + + /** + * 示例1: 加载简单的 WASM 模块 + */ + async loadSimpleWasmExample(wasmFactory: any) { + try { + // 首先需要初始化加载器(通常在应用启动时执行一次) + wasmLoader.initialize(); + + // 假设你有一个 WASM 工厂函数 + // const wasmFactory = (await import('../wasm/pinball_physics.js')).default; + + // 加载 WASM 模块 + const instance = await wasmLoader.loadSimpleWasm( + wasmFactory, // WASM 工厂函数 + 'pinball_physics.wasm', // WASM 文件名 + '44cacb3c-e901-455d-b3e1-1c38a69718e1', // 编辑器中的 UUID(可选) + 'wasmFiles' // Bundle 名称(可选) + ); + + // 现在可以使用 WASM 模块的函数 + console.log('WASM 模块加载成功:', instance); + + return instance; + } catch (error) { + console.error('WASM 加载失败:', error); + throw error; + } + } + + /** + * 示例2: 加载支持 ASM 回退的 WASM 模块 + */ + async loadWasmWithFallbackExample(wasmFactory: any, asmFactory: any) { + try { + wasmLoader.initialize(); + + // 假设你有 WASM 和 ASM 工厂函数 + // const wasmFactory = (await import('../wasm/pinball_physics.js')).default; + // const asmFactory = (await import('../wasm/pinball_physics.asm.js')).default; + + const result = await wasmLoader.loadWasmWithAsmFallback( + wasmFactory, // WASM 工厂函数 + asmFactory, // ASM 工厂函数 + 'pinball_physics.wasm', // WASM 文件名 + 'pinball_physics.asm.mem', // ASM 内存文件名 + '44cacb3c-e901-455d-b3e1-1c38a69718e1', // WASM UUID + '3400003e-dc3c-43c1-8757-3e082429125a', // ASM UUID + 'wasmFiles' // Bundle 名称 + ); + + console.log('模块加载成功,使用的是:', result.isWasm ? 'WASM' : 'ASM'); + console.log('模块实例:', result.instance); + + return result; + } catch (error) { + console.error('模块加载失败:', error); + throw error; + } + } + + /** + * 示例3: 检查平台支持情况 + */ + checkPlatformSupport() { + const isWasmSupported = wasmLoader.isWasmSupported(); + const strategy = wasmLoader.getRecommendedStrategy(); + + console.log('WASM 支持:', isWasmSupported); + console.log('推荐策略:', strategy); + + return { isWasmSupported, strategy }; + } + + /** + * 示例4: 高级用法 - 自定义配置 + */ + async loadCustomWasm(wasmFactory: any, asmFactory: any) { + try { + wasmLoader.initialize(); + + // 使用完整配置 + const result = await wasmLoader.loadWasmModule({ + fileName: 'pinball_physics.wasm', + bundleName: 'wasmFiles', + editorUuid: '44cacb3c-e901-455d-b3e1-1c38a69718e1', + supportAsm: true, + asmFileName: 'pinball_physics.asm.mem', + asmEditorUuid: '3400003e-dc3c-43c1-8757-3e082429125a', + wasmFactory: wasmFactory, + asmFactory: asmFactory + }); + + return result; + } catch (error) { + console.error('自定义 WASM 加载失败:', error); + throw error; + } + } +} + +/** + * 使用说明: + * + * 1. 首先将 WASM 文件和对应的 JS 胶水代码放到项目中 + * 2. 将 .wasm 文件导入到编辑器的资源管理器中 + * 3. 在编辑器中获取 .wasm 文件的 UUID + * 4. 导入 JS 胶水代码并获取工厂函数 + * 5. 调用 wasmLoader 的方法加载 WASM 模块 + * + * 注意事项: + * - 在编辑器环境下需要提供资源的 UUID + * - 在运行时环境下需要提供 Bundle 名称和文件名 + * - iOS 平台不支持 WASM,需要提供 ASM 回退 + * - 确保在使用前调用 initialize() 方法 + */ \ No newline at end of file diff --git a/client-cocos/assets/scripts/Utils/WasmLoaderExample.ts.meta b/client-cocos/assets/scripts/Utils/WasmLoaderExample.ts.meta new file mode 100644 index 0000000..b010777 --- /dev/null +++ b/client-cocos/assets/scripts/Utils/WasmLoaderExample.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "2ecfafcc-6d3c-443e-8e24-3207988f4742", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/assets/wasm.meta b/client-cocos/assets/wasm.meta new file mode 100644 index 0000000..bcb795a --- /dev/null +++ b/client-cocos/assets/wasm.meta @@ -0,0 +1,11 @@ +{ + "ver": "1.2.0", + "importer": "directory", + "imported": true, + "uuid": "812832c8-4a7e-482a-b607-a64b8f91ae2a", + "files": [], + "subMetas": {}, + "userData": { + "isBundle": true + } +} diff --git a/client-cocos/assets/wasm/pinball_physics.wasm b/client-cocos/assets/wasm/pinball_physics.wasm new file mode 100644 index 0000000..6c62a4e --- /dev/null +++ b/client-cocos/assets/wasm/pinball_physics.wasm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:798d25f96cbd922e18d230c67c4ce0db3e76ea83f97526e6723057f1d14c4801 +size 666186 diff --git a/client-cocos/assets/wasm/pinball_physics.wasm.meta b/client-cocos/assets/wasm/pinball_physics.wasm.meta new file mode 100644 index 0000000..cd665f4 --- /dev/null +++ b/client-cocos/assets/wasm/pinball_physics.wasm.meta @@ -0,0 +1,12 @@ +{ + "ver": "1.0.1", + "importer": "*", + "imported": true, + "uuid": "401aec55-42ae-48c1-9160-22c930389b53", + "files": [ + ".json", + ".wasm" + ], + "subMetas": {}, + "userData": {} +} diff --git a/client-cocos/build-physics.bat b/client-cocos/build-physics.bat new file mode 100644 index 0000000..e8eb806 --- /dev/null +++ b/client-cocos/build-physics.bat @@ -0,0 +1,193 @@ +@echo off +setlocal enabledelayedexpansion + +echo ======================================== +echo Pinball Physics 构建脚本 +echo ======================================== +echo. + +:: 设置构建模式,默认为 debug +set BUILD_MODE=%1 +if "%BUILD_MODE%"=="" set BUILD_MODE=debug + +:: 验证构建模式 +if /i "%BUILD_MODE%" neq "debug" if /i "%BUILD_MODE%" neq "release" ( + echo 错误: 无效的构建模式 "%BUILD_MODE%" + echo 支持的模式: debug ^| release + echo. + echo 用法: build-physics.bat [debug^|release] + echo 示例: build-physics.bat release + goto :error +) + +echo 构建模式: %BUILD_MODE% +echo. + +:: 设置路径变量 +set SCRIPT_DIR=%~dp0 +set PROJECT_ROOT=%SCRIPT_DIR%..\ +set PHYSICS_DIR=%PROJECT_ROOT%pinball-physics +set TARGET_DIR=%PHYSICS_DIR%\target\wasm32-unknown-unknown\%BUILD_MODE% +set WASM_SOURCE=%TARGET_DIR%\pinball_physics.wasm +set WASM_OUTPUT=%SCRIPT_DIR%assets\wasm\pinball_physics.bin +set ASM_OUTPUT=%SCRIPT_DIR%assets\wasm\pinball_physics.js +set EMSCRIPTEN_TOOL=%PROJECT_ROOT%tools\emscripten\gen-asm.bat + +echo 路径配置: +echo 物理引擎目录: %PHYSICS_DIR% +echo WASM 源文件: %WASM_SOURCE% +echo WASM 输出: %WASM_OUTPUT% +echo asm.js 输出: %ASM_OUTPUT% +echo. + +:: 检查物理引擎项目是否存在 +if not exist "%PHYSICS_DIR%\Cargo.toml" ( + echo 错误: 未找到物理引擎项目 + echo 路径: %PHYSICS_DIR% + goto :error +) + +:: 检查 Rust 是否安装 +rustc --version >nul 2>&1 +if %errorlevel% neq 0 ( + echo 错误: 未找到 Rust 编译器 + echo 请先安装 Rust: https://rustup.rs/ + goto :error +) + +:: 检查 wasm32-unknown-unknown 目标是否安装 +rustup target list --installed | findstr "wasm32-unknown-unknown" >nul 2>&1 +if %errorlevel% neq 0 ( + echo 安装 wasm32-unknown-unknown 目标... + rustup target add wasm32-unknown-unknown + if %errorlevel% neq 0 ( + echo 错误: 安装 wasm32-unknown-unknown 目标失败 + goto :error + ) +) + +:: 创建输出目录 +if not exist "%SCRIPT_DIR%assets\wasm" ( + echo 创建输出目录: assets\wasm + mkdir "%SCRIPT_DIR%assets\wasm" +) + +echo ---------------------------------------- +echo 第1步: 编译物理引擎 (模式: %BUILD_MODE%) +echo ---------------------------------------- +echo. + +:: 切换到物理引擎目录并编译 +cd /d "%PHYSICS_DIR%" + +if /i "%BUILD_MODE%"=="release" ( + echo 执行命令: cargo build --target wasm32-unknown-unknown --release + cargo build --target wasm32-unknown-unknown --release +) else ( + echo 执行命令: cargo build --target wasm32-unknown-unknown + cargo build --target wasm32-unknown-unknown +) + +if %errorlevel% neq 0 ( + echo 错误: 物理引擎编译失败 + goto :error +) + +:: 检查编译输出是否存在 +if not exist "%WASM_SOURCE%" ( + echo 错误: 编译输出文件不存在 + echo 期望位置: %WASM_SOURCE% + goto :error +) + +:: 显示编译结果信息 +for %%A in ("%WASM_SOURCE%") do ( + set source_size=%%~zA + set /a size_kb=!source_size!/1024 + echo 编译成功! 文件大小: !size_kb! KB +) + +echo. +echo ---------------------------------------- +echo 第2步: 拷贝 WASM 文件 +echo ---------------------------------------- +echo. + +:: 拷贝 WASM 文件到客户端资源目录 +echo 拷贝: %WASM_SOURCE% +echo 到: %WASM_OUTPUT% + +copy "%WASM_SOURCE%" "%WASM_OUTPUT%" >nul +if %errorlevel% neq 0 ( + echo 错误: 拷贝 WASM 文件失败 + goto :error +) + +echo 拷贝成功! + +echo. +echo ---------------------------------------- +echo 第3步: 生成 asm.js 文件 +echo ---------------------------------------- +echo. + +:: 检查 Emscripten 转换工具是否存在 +if not exist "%EMSCRIPTEN_TOOL%" ( + echo 错误: 未找到 Emscripten 转换工具 + echo 路径: %EMSCRIPTEN_TOOL% + echo 请先运行: tools\emscripten\install.bat + goto :error +) + +:: 切换回项目根目录 +cd /d "%PROJECT_ROOT%" + +:: 执行 WASM 到 asm.js 转换 +echo 执行转换: WASM → asm.js +echo 命令: %EMSCRIPTEN_TOOL% "%WASM_SOURCE%" "%ASM_OUTPUT%" +echo. + +call "%EMSCRIPTEN_TOOL%" "%WASM_SOURCE%" "%ASM_OUTPUT%" +if %errorlevel% neq 0 ( + echo 错误: asm.js 转换失败 + goto :error +) + +echo. +echo ======================================== +echo 构建完成! +echo ======================================== +echo. +echo 输出文件: +echo WASM: %WASM_OUTPUT% +if exist "%ASM_OUTPUT%" ( + for %%A in ("%WASM_OUTPUT%") do ( + set wasm_size=%%~zA + set /a wasm_kb=!wasm_size!/1024 + ) + for %%A in ("%ASM_OUTPUT%") do ( + set asm_size=%%~zA + set /a asm_kb=!asm_size!/1024 + ) + echo asm.js: %ASM_OUTPUT% + echo. + echo 文件大小: + echo WASM: !wasm_kb! KB + echo asm.js: !asm_kb! KB +) +echo. +echo 构建模式: %BUILD_MODE% +echo 可以在 Cocos Creator 中使用这些文件了! + +goto :end + +:error +echo. +echo ======================================== +echo 构建失败! +echo ======================================== +exit /b 1 + +:end +echo. +pause \ No newline at end of file diff --git a/client-cocos/package.json b/client-cocos/package.json new file mode 100644 index 0000000..a0caf5c --- /dev/null +++ b/client-cocos/package.json @@ -0,0 +1,7 @@ +{ + "name": "client-cocos", + "uuid": "20f4ca2c-2427-4801-805a-4b81e119fc54", + "creator": { + "version": "3.8.1" + } +} diff --git a/client-cocos/settings/v2/packages/builder.json b/client-cocos/settings/v2/packages/builder.json new file mode 100644 index 0000000..12a1ffc --- /dev/null +++ b/client-cocos/settings/v2/packages/builder.json @@ -0,0 +1,3 @@ +{ + "__version__": "1.3.7" +} diff --git a/client-cocos/settings/v2/packages/device.json b/client-cocos/settings/v2/packages/device.json new file mode 100644 index 0000000..70e599e --- /dev/null +++ b/client-cocos/settings/v2/packages/device.json @@ -0,0 +1,3 @@ +{ + "__version__": "1.0.1" +} diff --git a/client-cocos/settings/v2/packages/engine.json b/client-cocos/settings/v2/packages/engine.json new file mode 100644 index 0000000..d8f9996 --- /dev/null +++ b/client-cocos/settings/v2/packages/engine.json @@ -0,0 +1,150 @@ +{ + "__version__": "1.0.7", + "modules": { + "cache": { + "base": { + "_value": true + }, + "graphcis": { + "_value": true + }, + "gfx-webgl": { + "_value": true + }, + "gfx-webgl2": { + "_value": true + }, + "animation": { + "_value": true + }, + "skeletal-animation": { + "_value": false + }, + "3d": { + "_value": false + }, + "2d": { + "_value": true + }, + "xr": { + "_value": false + }, + "ui": { + "_value": true + }, + "particle": { + "_value": false + }, + "physics": { + "_value": false, + "_option": "physics-ammo" + }, + "physics-ammo": { + "_value": false + }, + "physics-cannon": { + "_value": false + }, + "physics-physx": { + "_value": false + }, + "physics-builtin": { + "_value": false + }, + "physics-2d": { + "_value": true, + "_option": "physics-2d-box2d" + }, + "physics-2d-box2d": { + "_value": false + }, + "physics-2d-builtin": { + "_value": false + }, + "intersection-2d": { + "_value": true + }, + "primitive": { + "_value": false + }, + "profiler": { + "_value": true + }, + "occlusion-query": { + "_value": false + }, + "geometry-renderer": { + "_value": false + }, + "debug-renderer": { + "_value": false + }, + "particle-2d": { + "_value": true + }, + "audio": { + "_value": true + }, + "video": { + "_value": true + }, + "webview": { + "_value": true + }, + "tween": { + "_value": true + }, + "websocket": { + "_value": false + }, + "websocket-server": { + "_value": false + }, + "terrain": { + "_value": false + }, + "light-probe": { + "_value": false + }, + "tiled-map": { + "_value": true + }, + "spine": { + "_value": true + }, + "dragon-bones": { + "_value": true + }, + "marionette": { + "_value": false + }, + "custom-pipeline": { + "_value": false + } + }, + "includeModules": [ + "2d", + "animation", + "audio", + "base", + "dragon-bones", + "gfx-webgl", + "gfx-webgl2", + "intersection-2d", + "particle-2d", + "physics-2d-box2d", + "profiler", + "spine", + "tiled-map", + "tween", + "ui", + "video", + "webview" + ], + "noDeprecatedFeatures": { + "value": false, + "version": "" + }, + "flags": {} + } +} diff --git a/client-cocos/settings/v2/packages/information.json b/client-cocos/settings/v2/packages/information.json new file mode 100644 index 0000000..4aadcd9 --- /dev/null +++ b/client-cocos/settings/v2/packages/information.json @@ -0,0 +1,23 @@ +{ + "__version__": "1.0.0", + "information": { + "customSplash": { + "id": "customSplash", + "label": "customSplash", + "enable": true, + "customSplash": { + "complete": false, + "form": "https://creator-api.cocos.com/api/form/show?sid=73288e660382f608298d6ba44c576519" + } + }, + "removeSplash": { + "id": "removeSplash", + "label": "removeSplash", + "enable": true, + "removeSplash": { + "complete": false, + "form": "https://creator-api.cocos.com/api/form/show?sid=73288e660382f608298d6ba44c576519" + } + } + } +} diff --git a/client-cocos/settings/v2/packages/program.json b/client-cocos/settings/v2/packages/program.json new file mode 100644 index 0000000..356db6b --- /dev/null +++ b/client-cocos/settings/v2/packages/program.json @@ -0,0 +1,3 @@ +{ + "__version__": "1.0.3" +} diff --git a/client-cocos/settings/v2/packages/project.json b/client-cocos/settings/v2/packages/project.json new file mode 100644 index 0000000..4129dde --- /dev/null +++ b/client-cocos/settings/v2/packages/project.json @@ -0,0 +1,3 @@ +{ + "__version__": "1.0.6" +} diff --git a/client-cocos/tsconfig.json b/client-cocos/tsconfig.json new file mode 100644 index 0000000..88abbfe --- /dev/null +++ b/client-cocos/tsconfig.json @@ -0,0 +1,9 @@ +{ + /* Base configuration. Do not edit this field. */ + "extends": "./temp/tsconfig.cocos.json", + /* Add your custom configuration here. */ + "compilerOptions": { + "strict": false, + "module": "CommonJS" + } +} \ No newline at end of file