├── .gitattributes ├── .github └── workflows │ ├── build-release-manual.yml │ ├── build-release.yml │ ├── jekyll-gh-pages.yml │ ├── npm-gulp.yml │ └── publish-npm.yml ├── .gitignore ├── README.md ├── SECURITY.md ├── client-lib-copy-test.bat ├── client-test ├── dist │ ├── gs-client.d.ts │ └── gs-client.js ├── index.html ├── lib │ ├── gs.d.ts │ ├── gs.js │ └── gs.min.js ├── package-lock.json ├── package.json ├── src │ ├── ColorComponent.ts │ ├── DrawSystem.ts │ ├── InputSystem.ts │ ├── MoveSystem.ts │ ├── MyInputAdapter.ts │ ├── Player.ts │ ├── PositionComponent.ts │ ├── VelocityComponent.ts │ └── index.ts ├── tsconfig.json └── webpack.config.js ├── debug ├── debugViewer.js └── index.html ├── docs ├── custom-input-adapter.md ├── emitter.md ├── entity-prefab.md ├── input-manager.md ├── interpolation.md ├── network-adapter.md ├── object-pool.md ├── state-machine.md ├── state-snapshop.md ├── sync-strategy.md └── time-manager.md └── source ├── .npmignore ├── bin ├── gs.d.ts ├── gs.js └── gs.min.js ├── gulpfile.js ├── package-lock.json ├── package.json ├── src ├── Core.ts ├── Core │ ├── Component.ts │ ├── Entity.ts │ ├── Interpolatable.ts │ └── System.ts ├── Debug │ ├── Debug.ts │ └── PerformanceProfiler.ts ├── Event │ ├── EventEmitter.ts │ ├── EventListener.ts │ └── GEvent.ts ├── GlobalEventEmitter.ts ├── Input │ ├── InputAdapter.ts │ ├── InputBuffer.ts │ ├── InputEvent.ts │ └── InputManager.ts ├── Manager │ ├── ComponentManager.ts │ ├── ComponentTypeInfo.ts │ ├── ComponentTypeManager.ts │ ├── EntityManager.ts │ ├── SystemManager.ts │ └── TimeManager.ts ├── Network │ ├── Authentication.ts │ ├── Connection.ts │ ├── ErrorCode.ts │ ├── GApi │ │ ├── MessageHandler.ts │ │ ├── Player.ts │ │ ├── Room.ts │ │ └── RoomApi.ts │ ├── GNetworkAdapter.ts │ ├── Message.ts │ ├── NetworkAdapter.ts │ ├── NetworkManager.ts │ ├── Strategy │ │ ├── ISyncStrategy.ts │ │ ├── InterpolationStrategy.ts │ │ ├── LinearInterpolationStrategy.ts │ │ ├── SnapshotInterpolationStrategy.ts │ │ ├── SplineInterpolationStrategy.ts │ │ ├── StateCompressionStrategy.ts │ │ └── SyncStrategyManager.ts │ └── WebSocketUtils.ts ├── Plugins │ └── IPlugin.ts ├── Pool │ ├── EventPool.ts │ └── ObjectPool.ts ├── State │ ├── StateMachine.ts │ ├── StateMachineComponent.ts │ └── StateMachineSystem.ts └── Utils │ ├── Bits.ts │ ├── EntityIdAllocator.ts │ ├── LinkedList.ts │ ├── Matcher.ts │ ├── Random.ts │ └── SparseSet.ts └── tsconfig.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/build-release-manual.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release Manual 2 | on: 3 | workflow_dispatch: 4 | jobs: 5 | build-and-release: 6 | name: Build and Release 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v2 11 | - name: Use Node.js 10.20.1 12 | uses: actions/setup-node@v3 13 | with: 14 | node-version: 10.20.1 15 | - name: Install zip 16 | run: sudo apt-get install -y zip 17 | - name: Create zip 18 | run: zip -r bin.zip source/bin/ 19 | - name: Get Current Tag 20 | id: current_tag 21 | uses: actions/github-script@v4 22 | with: 23 | script: | 24 | const { data: tags } = await github.repos.listTags({ 25 | owner: context.repo.owner, 26 | repo: context.repo.repo, 27 | per_page: 1 28 | }); 29 | return tags.length ? tags[0].name : 'v0.0.0'; 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | - name: Increment Version 33 | id: increment_version 34 | run: echo ::set-output name=tag::$(echo ${{ steps.current_tag.outputs.result }} | awk -F. '{$NF = $NF + 1;} 1' | sed 's/ /./g') 35 | - name: Get Latest Commit 36 | id: latest_commit 37 | uses: actions/github-script@v4 38 | with: 39 | script: | 40 | const { data: commit } = await github.repos.getCommit({ 41 | owner: context.repo.owner, 42 | repo: context.repo.repo, 43 | ref: context.sha, 44 | }); 45 | return commit.commit.message; 46 | env: 47 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 48 | - name: Create Release 49 | id: create_release 50 | uses: actions/create-release@v1.0.1 51 | env: 52 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 53 | with: 54 | tag_name: ${{ steps.increment_version.outputs.tag }} 55 | release_name: Release ${{ steps.increment_version.outputs.tag }} 56 | body: | 57 | # Release ${{ steps.increment_version.outputs.tag }} 58 | 最近的提交记录: ${{ steps.latest_commit.outputs.result }} # 添加最近的提交记录 59 | 这是一个包含 bin 文件夹内容的 release。 60 | draft: false 61 | prerelease: false 62 | - name: Upload Release Asset 63 | uses: actions/upload-release-asset@v1 64 | env: 65 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 66 | with: 67 | asset_path: ./bin.zip 68 | asset_name: bin.zip 69 | asset_content_type: application/zip 70 | upload_url: ${{ steps.create_release.outputs.upload_url }} 71 | -------------------------------------------------------------------------------- /.github/workflows/build-release.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | build-and-release: 8 | name: Build and Release 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | - name: Check Changes 14 | id: check_changes 15 | run: | 16 | set -e 17 | last_commit=$(git log -1 --format="%H") 18 | changed_files=$(git diff --name-only $last_commit HEAD) 19 | echo "Changed files: $changed_files" 20 | if echo "$changed_files" | grep -qE '^(source\/bin|source\\bin)\/.*\.(js|ts)$'; then 21 | echo "Changes in source/bin detected. Proceeding with release." 22 | else 23 | echo "No changes in source/bin detected. Skipping release." 24 | echo ::set-output name=skip::true 25 | fi 26 | - name: Use Node.js 10.20.1 27 | if: ${{ !steps.check_changes.outputs.skip }} 28 | uses: actions/setup-node@v3 29 | with: 30 | node-version: 10.20.1 31 | - name: Install zip 32 | if: ${{ !steps.check_changes.outputs.skip }} 33 | run: sudo apt-get install -y zip 34 | - name: Create zip 35 | if: ${{ !steps.check_changes.outputs.skip }} 36 | run: zip -r bin.zip source/bin/ 37 | - name: Get Current Tag 38 | if: ${{ !steps.check_changes.outputs.skip }} 39 | id: current_tag 40 | uses: actions/github-script@v4 41 | with: 42 | script: | 43 | const { data: tags } = await github.repos.listTags({ 44 | owner: context.repo.owner, 45 | repo: context.repo.repo, 46 | per_page: 1 47 | }); 48 | return tags.length ? tags[0].name : 'v0.0.0'; 49 | env: 50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 51 | - name: Increment Version 52 | if: ${{ !steps.check_changes.outputs.skip }} 53 | id: increment_version 54 | run: echo ::set-output name=tag::$(echo ${{ steps.current_tag.outputs.result }} | awk -F. '{$NF = $NF + 1;} 1' | sed 's/ /./g') 55 | - name: Get Latest Commit 56 | if: ${{ !steps.check_changes.outputs.skip }} 57 | id: latest_commit 58 | uses: actions/github-script@v4 59 | with: 60 | script: | 61 | const { data: commit } = await github.repos.getCommit({ 62 | owner: context.repo.owner, 63 | repo: context.repo.repo, 64 | ref: context.sha, 65 | }); 66 | return commit.commit.message; 67 | env: 68 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 69 | - name: Create Release 70 | if: ${{ !steps.check_changes.outputs.skip }} 71 | id: create_release 72 | uses: actions/create-release@v1.0.1 73 | env: 74 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 75 | with: 76 | tag_name: ${{ steps.increment_version.outputs.tag }} 77 | release_name: Release ${{ steps.increment_version.outputs.tag }} 78 | body: | 79 | # Release ${{ steps.increment_version.outputs.tag }} 80 | 最近的提交记录: ${{ steps.latest_commit.outputs.result }} # 添加最近的提交记录 81 | 这是一个包含 bin 文件夹内容的 release。 82 | draft: false 83 | prerelease: false 84 | - name: Upload Release Asset 85 | if: ${{ !steps.check_changes.outputs.skip }} 86 | uses: actions/upload-release-asset@v1 87 | env: 88 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 89 | with: 90 | asset_path: ./bin.zip 91 | asset_name: bin.zip 92 | asset_content_type: application/zip 93 | upload_url: ${{ steps.create_release.outputs.upload_url }} 94 | -------------------------------------------------------------------------------- /.github/workflows/jekyll-gh-pages.yml: -------------------------------------------------------------------------------- 1 | # Sample workflow for building and deploying a Jekyll site to GitHub Pages 2 | name: Deploy Jekyll with GitHub Pages dependencies preinstalled 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["main"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 20 | concurrency: 21 | group: "pages" 22 | cancel-in-progress: false 23 | 24 | jobs: 25 | # Build job 26 | build: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v3 31 | - name: Setup Pages 32 | uses: actions/configure-pages@v3 33 | - name: Build with Jekyll 34 | uses: actions/jekyll-build-pages@v1 35 | with: 36 | source: ./ 37 | destination: ./_site 38 | - name: Upload artifact 39 | uses: actions/upload-pages-artifact@v1 40 | 41 | # Deployment job 42 | deploy: 43 | environment: 44 | name: github-pages 45 | url: ${{ steps.deployment.outputs.page_url }} 46 | runs-on: ubuntu-latest 47 | needs: build 48 | steps: 49 | - name: Deploy to GitHub Pages 50 | id: deployment 51 | uses: actions/deploy-pages@v2 52 | -------------------------------------------------------------------------------- /.github/workflows/npm-gulp.yml: -------------------------------------------------------------------------------- 1 | name: NodeJS with Gulp 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [10.20.1] 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | 25 | - name: Build 26 | run: | 27 | cd source 28 | npm install 29 | gulp build 30 | -------------------------------------------------------------------------------- /.github/workflows/publish-npm.yml: -------------------------------------------------------------------------------- 1 | name: Publish NPM 2 | on: 3 | workflow_dispatch: 4 | jobs: 5 | build-and-release-framework: 6 | name: Build and Release Framework 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout repository 10 | uses: actions/checkout@v2 11 | - name: Use Node.js 18.0.0 12 | uses: actions/setup-node@v3 13 | with: 14 | node-version: 18.0.0 15 | registry-url: https://npm.pkg.github.com 16 | - name: Install compatible TypeScript version 17 | run: npm install typescript@4.9.5 18 | working-directory: source 19 | - name: Install dependencies 20 | run: npm install 21 | working-directory: source 22 | - name: Update version number 23 | run: npm version patch 24 | working-directory: source 25 | - name: Publish Framework to npm 26 | run: npm publish --access public 27 | env: 28 | NODE_AUTH_TOKEN: ${{secrets.NODE_AUTH_TOKEN}} 29 | working-directory: source 30 | build-and-release-server: 31 | name: Build and Release Server 32 | runs-on: ubuntu-latest 33 | steps: 34 | - name: Checkout repository 35 | uses: actions/checkout@v2 36 | - name: Use Node.js 18.0.0 37 | uses: actions/setup-node@v3 38 | with: 39 | node-version: 18.0.0 40 | registry-url: https://npm.pkg.github.com 41 | - name: Install compatible TypeScript version 42 | run: npm install typescript@5.1.3 43 | working-directory: server 44 | - name: Install dependencies 45 | run: npm install 46 | working-directory: server 47 | - name: Update version number 48 | run: npm version patch 49 | working-directory: server 50 | - name: Publish Server to npm 51 | run: npm publish --access public 52 | env: 53 | NODE_AUTH_TOKEN: ${{secrets.NODE_AUTH_TOKEN}} 54 | working-directory: server 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /source/node_modules 2 | /demo/bin-debug 3 | /demo/bin-release 4 | /.idea 5 | /.vscode 6 | /demo_wxgame 7 | /demo/.wing 8 | /demo/.idea 9 | /demo/.vscode 10 | /source/docs 11 | /test/node_modules 12 | /physics/node_modules 13 | /server/node_modules 14 | /server/dist 15 | /server-test/node_modules 16 | /server-test/error.log 17 | /server-test/combined.log 18 | /client-test/node_modules 19 | /server/combined.log 20 | /server/error.log 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # G-Framework 2 | 3 | G-Framework 是一个基于 TypeScript 编写的实体组件系统(ECS)框架,旨在为游戏开发者提供一个高性能、易用、可扩展的游戏逻辑框架。G-Framework 可以与多种游戏引擎无缝集成,支持逻辑并发执行,让你的游戏开发变得更加高效。 4 | 5 | # 核心优势 6 | 7 | - 高性能:G-Framework 专为游戏开发场景优化,具备高性能数据处理能力,有效降低游戏性能损耗。 8 | 9 | - 纯逻辑实现:无需依赖其他框架,G-Framework 让您专注于游戏逻辑,减少额外学习成本。 10 | 11 | - 易用性:简洁的 API 和明确的组件和系统隔离设计,降低上手难度,提高开发效率。 12 | 13 | - 跨平台支持:无论您使用哪种游戏引擎,G-Framework 都可以轻松集成,提供一致的开发体验。 14 | 15 | - 可扩展性:通过模块化设计,方便开发者自由添加自定义模块,快速满足项目需求。 16 | 17 | - 实时并发执行支持:G-Framework 支持逻辑并发执行,确保游戏中各个模块的实时响应。 18 | 19 | - 详细的文档支持:我们提供了详细的文档和教程,助您更快地了解和上手 G-Framework。 20 | 21 | # 交流群 22 | 23 | 点击链接加入群聊【ecs游戏框架交流】:https://jq.qq.com/?_wv=1027&k=29w1Nud6 24 | 25 | # 功能丰富的模块 26 | 27 | G-Framework 不仅提供了优秀的实体组件系统(ECS)核心,还结合多种专门针对帧同步优化策略和快速上手的实用模块,让您的游戏开发过程更加高效和轻松。以下是 G-Framework 提供的一些模块,助您在游戏开发中快速实现所需功能: 28 | 29 | - [事件总线](docs/emitter.md) 用于在系统之间传递事件的模块,方便在不同系统间解耦地通信 30 | - [时间管理器](docs/time-manager.md) 管理游戏中的计时器和帧率,确保各种定时任务的精确执行 31 | - [状态机](docs/state-machine.md) 实现有限状态机的逻辑,帮助您管理游戏角色和对象的状态切换 32 | - [输入适配器](docs/custom-input-adapter.md) 根据您使用的游戏引擎,为您提供便捷的输入事件抽象,简化输入控制 33 | - [网络适配器](docs/network-adapter.md) 提供网络通信的抽象接口,可以与各种网络库无缝对接 34 | - [状态快照](docs/state-snapshop.md) 用于记录和操作游戏状态的快照,方便在帧同步中处理状态变更 35 | - [客户端插值](docs/interpolation.md) 提供插值算法,优化客户端帧同步下的状态表现,如平滑移动效果 36 | - [输入管理器](docs/input-manager.md) 管理游戏输入,如键盘、鼠标和触摸事件,并在合适的时机同步输入状态 37 | - [游戏同步策略](docs/sync-strategy.md) 针对游戏同步提供了多种策略,如状态插值、状态压缩等,按需选择使用。 38 | - [对象池](docs/object-pool.md) 提供了一种高效的对象管理方式,通过重用对象来避免频繁的对象创建和销毁,可以用于管理游戏中的实体,如子弹、角色等。 39 | - [实体预制件](docs/entity-prefab.md) 提供了对预制体的管理功能,包括注册、注销以及从预制体创建新实体等功能。它是一个事先配置好的实体模板,我们可以从中快速复制(实例化)出新的实体 40 | 41 | G-Framework 通过提供这些实用模块,让您在游戏开发过程中专注于逻辑实现,更快速地满足项目需求。这些模块针对帧同步做了大量优化,确保您的项目在采用帧同步时获得较佳的性能表现。 42 | 43 | # G-Framework ECS 框架入门 44 | 45 | ## 核心 46 | 47 | 核心是该框架的重要目标,它包含了实体、组件、系统的管理。 48 | 49 | ```ts 50 | const core = gs.Core.instance; 51 | ``` 52 | 53 | ## 实体 54 | 55 | 实体是游戏中的基本对象,每个实体由一个唯一的 ID 标识,并包含一组组件。你可以通过 G-Framework 的 Entity 类来创建和管理实体。 56 | 57 | ```typescript 58 | // 创建实体 59 | const entity = this.core.entityManager.createEntity(); 60 | ``` 61 | 62 | ### 创建自定义实体 63 | 64 | 使用createEntity你可以轻松的创建自定义实体 65 | 66 | ```ts 67 | class Player extends gs.Entity { 68 | onCreate(): void { 69 | console.log('player 实体创建'); 70 | } 71 | 72 | onDestroy(): void { 73 | console.log('player 实体销毁'); 74 | } 75 | } 76 | 77 | 78 | const playerEntity = entityManager.createEntity(Player); 79 | ``` 80 | 81 | > onCreate方法和onDestroy方法由框架调用,分别再实体的创建和销毁时触发 82 | 83 | ## 组件 84 | 85 | 组件是实体的数据属性,用于描述实体的状态和行为。每个组件都是一个类,继承自 G-Framework 的 Component 类。 86 | 87 | ```typescript 88 | // 创建组件 89 | class PositionComponent extends gs.Component { 90 | public x: number = 0; 91 | public y: number = 0; 92 | 93 | public reset() { 94 | this.x = 0; 95 | this.y = 0; 96 | } 97 | } 98 | 99 | class VelocityComponent extends gs.Component { 100 | public x: number = 0; 101 | public y: number = 0; 102 | 103 | public reset() { 104 | this.x = 0; 105 | this.y = 0; 106 | } 107 | } 108 | 109 | 110 | ``` 111 | 112 | > 注意,需要实现reset方法,当组件回收时会调用该方法 113 | 114 | 现在,你可以将组件附加到实体上: 115 | 116 | ```typescript 117 | // 为实体添加组件 118 | entity.addComponent(PositionComponent); 119 | entity.addComponent(VelocityComponent); 120 | ``` 121 | 122 | ## 系统 123 | 124 | 系统是用于处理实体和组件的逻辑的核心部分,通过继承 G-Framework 的 System 类创建系统。 125 | 126 | ```typescript 127 | // 创建系统 128 | class MoveSystem extends gs.System { 129 | entityFilter(entity: gs.Entity): boolean { 130 | return entity.hasComponent(PositionComponent) && entity.hasComponent(VelocityComponent); 131 | } 132 | 133 | update(entities: gs.Entity[]): void { 134 | const deltaTime = gs.TimeManager.getInstance().deltaTime; 135 | for (const entity of entities) { 136 | const position = entity.getComponent(PositionComponent); 137 | const velocity = entity.getComponent(VelocityComponent); 138 | 139 | position.x += velocity.x * deltaTime; 140 | position.y += velocity.y * deltaTime; 141 | } 142 | } 143 | } 144 | 145 | // 注册系统到系统管理器中 146 | const moveSystem = new MoveSystem(this.core.entityManager, 0); 147 | this.core.systemManager.registerSystem(moveSystem); 148 | ``` 149 | 150 | ## 循环更新系统 151 | 152 | 在每个游戏循环中,你可以调用 核心中的 的 update() 方法来更新所有系统: 153 | 154 | ```typescript 155 | function gameLoop(deltaTime: number) { 156 | this.core.update(deltaTime); 157 | } 158 | ``` 159 | 160 | # 性能测试报告 161 | 162 | | 测试项 | 耗时 | 163 | | ------------------------------------------ | --------- | 164 | | 创建 1000 个实体并分配组件 | 0.80 ms | 165 | | 更新 1000 次系统 | 0.40 ms | 166 | | 创建并删除 1000 个实体 | 0.80 ms | 167 | | 筛选 5000 个实体中具有位置和速度组件的实体 | 0.00 ms | 168 | | 添加和删除 1000 个实体的组件 | 1.40 ms | 169 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /client-lib-copy-test.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | set sourceDir=source\bin 3 | set destinationDir=client-test\lib 4 | 5 | xcopy "%sourceDir%\*" "%destinationDir%\" /s /i /y -------------------------------------------------------------------------------- /client-test/dist/gs-client.d.ts: -------------------------------------------------------------------------------- 1 | declare class ColorComponent extends gs.Component { 2 | color: string; 3 | onInitialize(): void; 4 | private getRandomColor; 5 | } 6 | declare class DrawSystem extends gs.System { 7 | entityFilter(entity: gs.Entity): boolean; 8 | update(entities: gs.Entity[]): void; 9 | } 10 | declare class InputSystem extends gs.System { 11 | entityFilter(entity: gs.Entity): boolean; 12 | update(entities: gs.Entity[]): void; 13 | private applyInputToEntity; 14 | } 15 | declare class MoveSystem extends gs.System { 16 | entityFilter(entity: gs.Entity): boolean; 17 | update(entities: gs.Entity[]): void; 18 | } 19 | declare class MyInputAdapter extends gs.InputAdapter { 20 | private networkAdapter; 21 | constructor(inputManager: gs.InputManager, networkAdapter: gs.GNetworkAdapter); 22 | handleInputEvent(inputEvent: gs.InputEvent[]): void; 23 | sendInputEvent(event: any, playerId: string): void; 24 | private convertEngineEventToInputEvent; 25 | } 26 | declare class Player extends gs.Entity { 27 | playerId: string; 28 | onCreate(): void; 29 | onDestroy(): void; 30 | } 31 | declare class PositionComponent extends gs.Component { 32 | x: number; 33 | y: number; 34 | reset(): void; 35 | } 36 | declare class VelocityComponent extends gs.Component { 37 | x: number; 38 | y: number; 39 | reset(): void; 40 | } 41 | declare const core: gs.Core; 42 | declare const moveSystem: MoveSystem; 43 | declare const inputSystem: InputSystem; 44 | declare const drawSystem: DrawSystem; 45 | declare const userName = "test"; 46 | declare const password = "test"; 47 | declare let networkAdapter: gs.GNetworkAdapter; 48 | declare let playerId: any; 49 | declare let roomId: any; 50 | declare let playerNet: {}; 51 | declare let lastSnapshotVersion: number; 52 | declare function createPlayer(id: string): void; 53 | declare function deletePlayer(id: string): void; 54 | declare function deleteAllPlayer(): void; 55 | declare const syncStrategy: gs.SnapshotInterpolationStrategy; 56 | declare const strategyManager: gs.SyncStrategyManager; 57 | declare let lastTime: number; 58 | declare function update(timestamp: any): void; 59 | -------------------------------------------------------------------------------- /client-test/dist/gs-client.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __extends = (this && this.__extends) || (function () { 3 | var extendStatics = function (d, b) { 4 | extendStatics = Object.setPrototypeOf || 5 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 6 | function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; 7 | return extendStatics(d, b); 8 | }; 9 | return function (d, b) { 10 | if (typeof b !== "function" && b !== null) 11 | throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); 12 | extendStatics(d, b); 13 | function __() { this.constructor = d; } 14 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 15 | }; 16 | })(); 17 | var __values = (this && this.__values) || function(o) { 18 | var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; 19 | if (m) return m.call(o); 20 | if (o && typeof o.length === "number") return { 21 | next: function () { 22 | if (o && i >= o.length) o = void 0; 23 | return { value: o && o[i++], done: !o }; 24 | } 25 | }; 26 | throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); 27 | }; 28 | var ColorComponent = /** @class */ (function (_super) { 29 | __extends(ColorComponent, _super); 30 | function ColorComponent() { 31 | return _super !== null && _super.apply(this, arguments) || this; 32 | } 33 | ColorComponent.prototype.onInitialize = function () { 34 | this.color = this.getRandomColor(); 35 | this.markUpdated(lastSnapshotVersion + 1); 36 | }; 37 | ColorComponent.prototype.getRandomColor = function () { 38 | var letters = "0123456789ABCDEF"; 39 | var color = "#"; 40 | for (var i = 0; i < 6; i++) { 41 | color += letters[Math.floor(Math.random() * 16)]; 42 | } 43 | return color; 44 | }; 45 | return ColorComponent; 46 | }(gs.Component)); 47 | var DrawSystem = /** @class */ (function (_super) { 48 | __extends(DrawSystem, _super); 49 | function DrawSystem() { 50 | return _super !== null && _super.apply(this, arguments) || this; 51 | } 52 | DrawSystem.prototype.entityFilter = function (entity) { 53 | return entity.hasTag("player"); 54 | }; 55 | DrawSystem.prototype.update = function (entities) { 56 | var e_1, _a; 57 | var canvas = document.getElementById("game-canvas"); 58 | var ctx = canvas.getContext("2d"); 59 | ctx.clearRect(0, 0, canvas.width, canvas.height); 60 | try { 61 | for (var entities_1 = __values(entities), entities_1_1 = entities_1.next(); !entities_1_1.done; entities_1_1 = entities_1.next()) { 62 | var entity = entities_1_1.value; 63 | var color = entity.getComponent(ColorComponent); 64 | var position = entity.getComponent(PositionComponent); 65 | ctx.fillStyle = color.color; 66 | ctx.fillRect(position.x, position.y, 50, 50); 67 | } 68 | } 69 | catch (e_1_1) { e_1 = { error: e_1_1 }; } 70 | finally { 71 | try { 72 | if (entities_1_1 && !entities_1_1.done && (_a = entities_1.return)) _a.call(entities_1); 73 | } 74 | finally { if (e_1) throw e_1.error; } 75 | } 76 | }; 77 | return DrawSystem; 78 | }(gs.System)); 79 | var InputSystem = /** @class */ (function (_super) { 80 | __extends(InputSystem, _super); 81 | function InputSystem() { 82 | return _super !== null && _super.apply(this, arguments) || this; 83 | } 84 | InputSystem.prototype.entityFilter = function (entity) { 85 | return entity.hasTag("player"); 86 | }; 87 | InputSystem.prototype.update = function (entities) { 88 | var e_2, _a; 89 | var inputBuffer = this.entityManager.getInputManager().getInputBuffer(); 90 | // 处理输入缓冲区中的事件 91 | while (inputBuffer.hasEvents()) { 92 | var inputEvent = inputBuffer.consumeEvent(); 93 | try { 94 | // 遍历实体并根据输入事件更新它们 95 | for (var entities_2 = (e_2 = void 0, __values(entities)), entities_2_1 = entities_2.next(); !entities_2_1.done; entities_2_1 = entities_2.next()) { 96 | var entity = entities_2_1.value; 97 | if (entity instanceof Player && entity.playerId == inputEvent.data.playerId) { 98 | this.applyInputToEntity(entity, inputEvent); 99 | break; 100 | } 101 | } 102 | } 103 | catch (e_2_1) { e_2 = { error: e_2_1 }; } 104 | finally { 105 | try { 106 | if (entities_2_1 && !entities_2_1.done && (_a = entities_2.return)) _a.call(entities_2); 107 | } 108 | finally { if (e_2) throw e_2.error; } 109 | } 110 | } 111 | }; 112 | InputSystem.prototype.applyInputToEntity = function (entity, inputEvent) { 113 | if (inputEvent.type == 'keyboard') { 114 | var velocity = entity.getComponent(VelocityComponent); 115 | velocity.x = 0; 116 | velocity.y = 0; 117 | if (inputEvent.data.isKeyDown) { 118 | switch (inputEvent.data.key.toLowerCase()) { 119 | case 'w': 120 | velocity.y = -100; 121 | break; 122 | case 's': 123 | velocity.y = 100; 124 | break; 125 | case 'a': 126 | velocity.x = -100; 127 | break; 128 | case 'd': 129 | velocity.x = 100; 130 | break; 131 | } 132 | } 133 | } 134 | }; 135 | return InputSystem; 136 | }(gs.System)); 137 | // 创建系统 138 | var MoveSystem = /** @class */ (function (_super) { 139 | __extends(MoveSystem, _super); 140 | function MoveSystem() { 141 | return _super !== null && _super.apply(this, arguments) || this; 142 | } 143 | MoveSystem.prototype.entityFilter = function (entity) { 144 | return entity.hasComponent(PositionComponent) && entity.hasComponent(VelocityComponent); 145 | }; 146 | MoveSystem.prototype.update = function (entities) { 147 | var e_3, _a; 148 | var deltaTime = gs.TimeManager.getInstance().deltaTime; 149 | try { 150 | for (var entities_3 = __values(entities), entities_3_1 = entities_3.next(); !entities_3_1.done; entities_3_1 = entities_3.next()) { 151 | var entity = entities_3_1.value; 152 | var position = entity.getComponent(PositionComponent); 153 | var velocity = entity.getComponent(VelocityComponent); 154 | var xIn = velocity.x * deltaTime; 155 | var yIn = velocity.y * deltaTime; 156 | position.x += xIn; 157 | position.y += yIn; 158 | if (xIn != 0 || yIn != 0) 159 | position.markUpdated(lastSnapshotVersion + 1); 160 | } 161 | } 162 | catch (e_3_1) { e_3 = { error: e_3_1 }; } 163 | finally { 164 | try { 165 | if (entities_3_1 && !entities_3_1.done && (_a = entities_3.return)) _a.call(entities_3); 166 | } 167 | finally { if (e_3) throw e_3.error; } 168 | } 169 | }; 170 | return MoveSystem; 171 | }(gs.System)); 172 | var MyInputAdapter = /** @class */ (function (_super) { 173 | __extends(MyInputAdapter, _super); 174 | function MyInputAdapter(inputManager, networkAdapter) { 175 | var _this = _super.call(this, inputManager) || this; 176 | _this.networkAdapter = networkAdapter; 177 | return _this; 178 | } 179 | // 这个方法将处理游戏引擎或平台特定的输入事件,并将它们转换为通用的 InputEvent 对象 180 | MyInputAdapter.prototype.handleInputEvent = function (inputEvent) { 181 | var e_4, _a; 182 | try { 183 | // 将转换后的 InputEvent 发送到 InputManager 184 | for (var inputEvent_1 = __values(inputEvent), inputEvent_1_1 = inputEvent_1.next(); !inputEvent_1_1.done; inputEvent_1_1 = inputEvent_1.next()) { 185 | var event_1 = inputEvent_1_1.value; 186 | this.sendInputToManager(event_1); 187 | } 188 | } 189 | catch (e_4_1) { e_4 = { error: e_4_1 }; } 190 | finally { 191 | try { 192 | if (inputEvent_1_1 && !inputEvent_1_1.done && (_a = inputEvent_1.return)) _a.call(inputEvent_1); 193 | } 194 | finally { if (e_4) throw e_4.error; } 195 | } 196 | }; 197 | MyInputAdapter.prototype.sendInputEvent = function (event, playerId) { 198 | // 处理特定的输入事件,例如将它们转换为通用的 InputEvent 对象 199 | var inputEvent = this.convertEngineEventToInputEvent(event, playerId); 200 | this.sendInputToManager(inputEvent); 201 | }; 202 | // 将游戏引擎的输入事件转换为 InputEvent 203 | MyInputAdapter.prototype.convertEngineEventToInputEvent = function (event, playerId) { 204 | var inputEvent = { 205 | type: 'unknown', 206 | data: null // 初始化为null,根据需要设置 207 | }; 208 | if (event.type === 'keydown' || event.type === 'keyup') { 209 | // 如果事件类型是按键按下或按键松开 210 | inputEvent.type = 'keyboard'; // 设置为键盘事件类型 211 | inputEvent.data = { 212 | key: event.key, 213 | isKeyDown: event.type === 'keydown', 214 | playerId: playerId 215 | }; 216 | } 217 | return inputEvent; 218 | }; 219 | return MyInputAdapter; 220 | }(gs.InputAdapter)); 221 | var Player = /** @class */ (function (_super) { 222 | __extends(Player, _super); 223 | function Player() { 224 | return _super !== null && _super.apply(this, arguments) || this; 225 | } 226 | Player.prototype.onCreate = function () { 227 | console.log('player 实体创建'); 228 | }; 229 | Player.prototype.onDestroy = function () { 230 | console.log('player 实体销毁'); 231 | }; 232 | return Player; 233 | }(gs.Entity)); 234 | var PositionComponent = /** @class */ (function (_super) { 235 | __extends(PositionComponent, _super); 236 | function PositionComponent() { 237 | var _this = _super !== null && _super.apply(this, arguments) || this; 238 | _this.x = 0; 239 | _this.y = 0; 240 | return _this; 241 | } 242 | PositionComponent.prototype.reset = function () { 243 | this.x = 0; 244 | this.y = 0; 245 | }; 246 | return PositionComponent; 247 | }(gs.Component)); 248 | var VelocityComponent = /** @class */ (function (_super) { 249 | __extends(VelocityComponent, _super); 250 | function VelocityComponent() { 251 | var _this = _super !== null && _super.apply(this, arguments) || this; 252 | _this.x = 0; 253 | _this.y = 0; 254 | return _this; 255 | } 256 | VelocityComponent.prototype.reset = function () { 257 | this.x = 0; 258 | this.y = 0; 259 | }; 260 | return VelocityComponent; 261 | }(gs.Component)); 262 | /// 263 | /// 264 | /// 265 | var core = gs.Core.instance; 266 | // 注册系统到系统管理器中 267 | var moveSystem = new MoveSystem(core.entityManager, 0); 268 | core.systemManager.registerSystem(moveSystem); 269 | var inputSystem = new InputSystem(core.entityManager, 0); 270 | core.systemManager.registerSystem(inputSystem); 271 | var drawSystem = new DrawSystem(core.entityManager, 0); 272 | var userName = 'test'; 273 | var password = 'test'; 274 | // 使用你的服务器URL实例化网络适配器 275 | var networkAdapter = new gs.GNetworkAdapter('ws://127.0.0.1:8080', userName, password); 276 | // 添加网络适配器到EntityManager 277 | core.entityManager.getNetworkManager().setNetworkAdapter(networkAdapter); 278 | var playerId; 279 | var roomId; 280 | var playerNet = {}; 281 | var lastSnapshotVersion = 0; 282 | // 监听服务器更新 283 | core.entityManager.getNetworkManager().getNetworkAdapter().onServerUpdate(function (serverState) { 284 | if (serverState.type == "authentication") { 285 | playerId = serverState.payload.id; 286 | document.getElementById('player-session').textContent = "SESSION:" + serverState.payload.sessionId; 287 | document.getElementById('player-id').textContent = 'ID:' + playerId; 288 | document.getElementById('player-name').textContent = "Name:" + userName; 289 | } 290 | else if (serverState.type == 'sessionId') { 291 | document.getElementById('player-session').textContent = "SESSION:" + serverState.payload; 292 | } 293 | if (serverState.type != 'heartbeat' && serverState.type != 'frameSync') { 294 | document.getElementById('loggerArea').textContent += ("[".concat(serverState.type, "]: ").concat(JSON.stringify(serverState.payload), "\n")); 295 | console.warn('更新游戏状态', serverState); 296 | } 297 | }); 298 | function createPlayer(id) { 299 | var playerEntity = core.entityManager.createEntity(Player); 300 | playerEntity.addComponent(PositionComponent); 301 | playerEntity.addComponent(VelocityComponent); 302 | playerEntity.addComponent(ColorComponent); 303 | playerEntity.playerId = id; 304 | playerEntity.addTag("player"); 305 | playerNet[id] = playerEntity.getId(); 306 | } 307 | function deletePlayer(id) { 308 | core.entityManager.deleteEntity(playerNet[id]); 309 | delete playerNet[id]; 310 | core.systemManager.invalidateEntityCacheForSystem(drawSystem); 311 | } 312 | function deleteAllPlayer() { 313 | var players = core.entityManager.getEntitiesWithTag('player'); 314 | for (var i = players.length - 1; i >= 0; i--) { 315 | var playerId_1 = players[i].playerId; 316 | core.entityManager.deleteEntity(playerNet[playerId_1]); 317 | } 318 | playerNet = {}; 319 | core.systemManager.invalidateEntityCacheForSystem(drawSystem); 320 | } 321 | var syncStrategy = new gs.SnapshotInterpolationStrategy(); 322 | syncStrategy.setInterpolationCallback(function (prevSnapshot, nextSnapshot, progress) { 323 | console.log("".concat(prevSnapshot, " ").concat(nextSnapshot, " ").concat(progress)); 324 | }); 325 | var strategyManager = new gs.SyncStrategyManager(syncStrategy); // 发送状态 326 | document.addEventListener("DOMContentLoaded", function () { 327 | // 获取加入房间按钮 328 | var joinRoomButton = document.getElementById("join-room-btn"); 329 | var createRoomButton = document.getElementById("create-room-btn"); 330 | var leaveRoomButton = document.getElementById('leave-room-btn'); 331 | var startFrameBtn = document.getElementById('start-frame'); 332 | var endFrameBtn = document.getElementById('end-frame'); 333 | var clearLogBtn = document.getElementById('clear-log'); 334 | joinRoomButton.onclick = function (ev) { 335 | console.log("发送加入房间指令"); 336 | // 获取输入框元素 337 | var roomInputElement = document.getElementById("room-input"); 338 | // 获取输入框中的房间号 339 | var roomInput = roomInputElement.value; 340 | networkAdapter.RoomAPI.joinRoom(roomInput); 341 | }; 342 | createRoomButton.onclick = function (ev) { 343 | console.log("发送创建房间指令"); 344 | networkAdapter.RoomAPI.createRoom(10, function (room) { 345 | roomId = room.id; 346 | document.getElementById('room-id').textContent = "ID: " + roomId; 347 | createPlayer(playerId); 348 | }); 349 | }; 350 | clearLogBtn.onclick = function (ev) { 351 | document.getElementById('loggerArea').textContent = ''; 352 | }; 353 | leaveRoomButton.onclick = function (ev) { 354 | console.log("发送退出房间指令"); 355 | networkAdapter.RoomAPI.leaveRoom(roomId); 356 | }; 357 | startFrameBtn.onclick = function (ev) { 358 | console.log('开始帧同步'); 359 | networkAdapter.RoomAPI.startGame(roomId); 360 | }; 361 | endFrameBtn.onclick = function (ev) { 362 | console.log('结束帧同步'); 363 | networkAdapter.RoomAPI.endGame(roomId); 364 | }; 365 | networkAdapter.RoomAPI.setPlayerLeftCallback(function (leftPlayerId) { 366 | if (leftPlayerId == playerId) { 367 | // 自己离开房间 368 | document.getElementById('room-id').textContent = ""; 369 | deleteAllPlayer(); 370 | } 371 | else { 372 | console.log("\u6709\u73A9\u5BB6\u79BB\u5F00: ".concat(leftPlayerId)); 373 | deletePlayer(leftPlayerId); 374 | } 375 | }); 376 | networkAdapter.RoomAPI.setPlayerJoinedCallback(function (joinPlayerId, room) { 377 | var e_5, _a; 378 | if (joinPlayerId == playerId) { 379 | roomId = room.id; 380 | // 自己加入房间 381 | document.getElementById('room-id').textContent = "ID: ".concat(room.id); 382 | try { 383 | for (var _b = __values(room.players), _c = _b.next(); !_c.done; _c = _b.next()) { 384 | var player = _c.value; 385 | createPlayer(player.id); 386 | } 387 | } 388 | catch (e_5_1) { e_5 = { error: e_5_1 }; } 389 | finally { 390 | try { 391 | if (_c && !_c.done && (_a = _b.return)) _a.call(_b); 392 | } 393 | finally { if (e_5) throw e_5.error; } 394 | } 395 | } 396 | else { 397 | console.log("\u6709\u73A9\u5BB6\u52A0\u5165: ".concat(joinPlayerId)); 398 | createPlayer(joinPlayerId); 399 | } 400 | }); 401 | networkAdapter.RoomAPI.setFrameSync(function (payload) { 402 | if (payload.actions) { 403 | console.log(payload.frame, payload.actions); 404 | } 405 | document.getElementById('frame').textContent = "\u5E27: ".concat(payload.frame); 406 | var snapshot = core.entityManager.createIncrementalStateSnapshot(lastSnapshotVersion); 407 | // 如果有增量数据 408 | if (snapshot.entities.length > 0) { 409 | // 向服务器发送增量数据 410 | networkAdapter.RoomAPI.snapShot(roomId, snapshot, lastSnapshotVersion); 411 | // 输出增量帧 412 | console.log("version: ".concat(lastSnapshotVersion, " snapshot: ").concat(snapshot)); 413 | } 414 | }); 415 | networkAdapter.RoomAPI.setSnapshotCallback(function (snapshot) { 416 | console.log("receive snapshot: ".concat(snapshot)); 417 | lastSnapshotVersion = snapshot.lastSnapVersion; 418 | strategyManager.receiveState(snapshot); 419 | // core.entityManager.applyIncrementalSnapshot(snapshot.snapshot); 420 | }); 421 | var inputAdapter = new MyInputAdapter(core.entityManager.getInputManager(), networkAdapter); 422 | document.addEventListener("keydown", function (event) { 423 | inputAdapter.sendInputEvent(event, playerId); 424 | }); 425 | document.addEventListener("keyup", function (event) { 426 | inputAdapter.sendInputEvent(event, playerId); 427 | }); 428 | core.systemManager.registerSystem(drawSystem); 429 | }); 430 | var lastTime = 0; 431 | function update(timestamp) { 432 | var deltaTime = (timestamp - lastTime) / 1000; // 将时间戳转换为秒 433 | // 这里写你的更新逻辑 434 | core.update(deltaTime); 435 | lastTime = timestamp; 436 | // 请求下一帧 437 | requestAnimationFrame(update); 438 | // 处理状态更新 439 | strategyManager.handleStateUpdate(deltaTime); 440 | } 441 | // 开始更新循环 442 | requestAnimationFrame(update); 443 | -------------------------------------------------------------------------------- /client-test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 10 | 11 |
12 |

玩家信息

13 |

Name:

14 |

ID:

15 |

SESSION:

16 |
17 | 18 |
19 |

房间信息

20 |

ID:

21 | 22 | 23 | 24 | 25 | 26 |

帧:

27 | 28 | 29 |
30 | 31 |
32 |

日志信息

33 | 34 | 35 |
36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /client-test/lib/gs.d.ts: -------------------------------------------------------------------------------- 1 | declare module gs { 2 | class Core { 3 | private _entityManager; 4 | private _systemManager; 5 | private _timeManager; 6 | private _plugins; 7 | private _performanceProfiler; 8 | readonly entityManager: EntityManager; 9 | readonly systemManager: SystemManager; 10 | private static _instance; 11 | static readonly instance: Core; 12 | private constructor(); 13 | private onInit; 14 | registerPlugin(plugin: IPlugin): void; 15 | update(deltaTime: number): void; 16 | } 17 | } 18 | declare module gs { 19 | class ObjectPool { 20 | private createFn; 21 | private resetFn; 22 | private pool; 23 | constructor(createFn: () => T, resetFn: (obj: T) => void); 24 | acquire(): T; 25 | release(obj: T): void; 26 | } 27 | } 28 | declare module gs { 29 | class EventPool extends ObjectPool { 30 | constructor(); 31 | } 32 | } 33 | declare module gs { 34 | class EventEmitter { 35 | private listeners; 36 | private eventPool; 37 | constructor(); 38 | /** 39 | * 用于订阅特定事件类型的侦听器。当事件类型不存在时,将创建一个新的侦听器数组 40 | * @param eventType 41 | * @param listener 42 | */ 43 | on(eventType: string, listener: EventListener): void; 44 | /** 45 | * 用于订阅特定事件类型的侦听器。当事件类型不存在时,将创建一个新的侦听器数组。该方法只会在回调函数被执行后,移除监听器 46 | * @param eventType 47 | * @param callback 48 | */ 49 | once(eventType: string, callback: (event: GEvent) => void): void; 50 | /** 51 | * 用于取消订阅特定事件类型的侦听器。如果找到侦听器,则将其从数组中移除 52 | * @param eventType 53 | * @param listener 54 | */ 55 | off(eventType: string, listener: EventListener): void; 56 | /** 57 | * 用于触发事件。该方法将遍历所有订阅给定事件类型的侦听器,并调用它们 58 | * @param event 59 | */ 60 | emitEvent(event: GEvent): void; 61 | } 62 | } 63 | declare module gs { 64 | const GlobalEventEmitter: EventEmitter; 65 | } 66 | declare module gs { 67 | /** 68 | * 组件 69 | */ 70 | abstract class Component { 71 | private _entity; 72 | private _version; 73 | private _entityManager; 74 | dependencies: ComponentConstructor[]; 75 | setEntity(entity: Entity, entityManager: EntityManager): void; 76 | readonly entityId: number; 77 | readonly entity: Entity; 78 | readonly version: number; 79 | /** 80 | * 标记组件已更新的方法 81 | * 通过增加 _version 的值来表示组件已更新 82 | */ 83 | markUpdated(version: number): void; 84 | /** 85 | * 重置组件的状态并进行必要的初始化 86 | * @param entity 87 | * @param entityManager 88 | */ 89 | reinitialize(entity: Entity, entityManager: EntityManager): void; 90 | /** 91 | * 当组件初始化的时候调用 92 | * @param args 93 | */ 94 | onInitialize(...args: any[]): void; 95 | serialize(): any; 96 | deserialize(data: any): void; 97 | /** 98 | * 判断是否需要序列化的方法 99 | * @returns 默认返回 true,表示需要序列化 100 | */ 101 | shouldSerialize(): boolean; 102 | /** 103 | * 清除数据方法,用于组件池在重用时 104 | */ 105 | reset(): void; 106 | /** 107 | * 默认的浅复制方法 108 | * @returns 克隆的组件实例 109 | */ 110 | cloneShallow(): Component; 111 | /** 112 | * 默认的深复制方法 113 | * 不处理循环引用 114 | * 如果需要循环引用请重写该方法 115 | * @returns 克隆的组件实例 116 | */ 117 | cloneDeep(): Component; 118 | /** 119 | * 深复制辅助函数 120 | * @param obj 121 | * @returns 122 | */ 123 | private deepCopy; 124 | } 125 | } 126 | declare module gs { 127 | /** 128 | * 实体类,用于管理实体的组件和标签。 129 | */ 130 | class Entity { 131 | private id; 132 | private componentManagers; 133 | private tags; 134 | private eventEmitter; 135 | private entityManager; 136 | componentBits: Bits; 137 | private _parent?; 138 | private _children; 139 | private _childNode?; 140 | private componentCache; 141 | constructor(id: number, entityManager: EntityManager, componentManagers: Map Component, ComponentManager>); 142 | getId(): number; 143 | readonly parent: Entity | undefined; 144 | readonly children: Entity[]; 145 | setParent(parent: Entity): void; 146 | removeParent(): void; 147 | addChild(child: Entity): void; 148 | removeChild(child: Entity): void; 149 | /** 150 | * 添加组件 151 | * @param componentType 152 | * @param args 153 | * @returns 154 | */ 155 | addComponent(componentType: ComponentConstructor, ...args: any[]): T; 156 | /** 157 | * 获取组件 158 | * @param componentType 159 | * @returns 160 | */ 161 | getComponent(componentType: ComponentConstructor): T | null; 162 | /** 163 | * 获取所有组件 164 | * @returns 165 | */ 166 | getAllComponents(): Component[]; 167 | /** 168 | * 移除组件 169 | * @param componentType 170 | * @returns 171 | */ 172 | removeComponent(componentType: ComponentConstructor): void; 173 | /** 174 | * 是否有组件 175 | * @param componentType 176 | * @returns 177 | */ 178 | hasComponent(componentType: new (entityId: number) => T): boolean; 179 | /** 180 | * 清除组件缓存 181 | */ 182 | clearComponentCache(): void; 183 | /** 184 | * 添加标签 185 | * @param tag 186 | */ 187 | addTag(tag: string): void; 188 | /** 189 | * 获取标签 190 | * @returns 191 | */ 192 | getTags(): Set; 193 | /** 194 | * 移除标签 195 | * @param tag 196 | */ 197 | removeTag(tag: string): void; 198 | /** 199 | * 检查是否具有指定标签 200 | * @param tag 201 | * @returns 202 | */ 203 | hasTag(tag: string): boolean; 204 | /** 205 | * 序列化 206 | * @returns 207 | */ 208 | serialize(): any; 209 | /** 210 | * 增量序列化 211 | * @param lastSnapshotVersion 上一次快照版本 212 | * @returns 返回增量序列化后的实体对象,如果没有更新的组件,则返回null 213 | */ 214 | serializeIncremental(lastSnapshotVersion: number): any | null; 215 | /** 216 | * 反序列化 217 | * @param data 218 | */ 219 | deserialize(data: any): void; 220 | /** 221 | * 实体创建时的逻辑 222 | */ 223 | onCreate(): void; 224 | /** 225 | * 实体销毁时的逻辑 226 | */ 227 | onDestroy(): void; 228 | [Symbol.iterator](): Iterator; 229 | on(eventType: string, listener: EventListener): void; 230 | once(eventType: string, callback: (event: GEvent) => void): void; 231 | off(eventType: string, listener: EventListener): void; 232 | emit(event: GEvent): void; 233 | } 234 | } 235 | declare module gs { 236 | interface Interpolatable { 237 | savePreviousState(): void; 238 | applyInterpolation(factor: number): void; 239 | } 240 | } 241 | declare module gs { 242 | /** 243 | * ECS 框架中的系统接口,定义了系统需要实现的方法。 244 | */ 245 | interface ISystem { 246 | update(entities: Entity[]): void; 247 | filterEntities(entities: Entity[]): Entity[]; 248 | onRegister(): void; 249 | onUnregister(): void; 250 | } 251 | /** 252 | * 系统基类 253 | */ 254 | abstract class System implements ISystem { 255 | protected entityManager: EntityManager; 256 | protected matcher: Matcher; 257 | protected lastUpdateTime: number; 258 | protected updateInterval: number; 259 | /** 260 | * 系统优先级,优先级越高,越先执行 261 | */ 262 | readonly priority: number; 263 | /** 264 | * 系统所在的worker脚本 265 | */ 266 | readonly workerScript?: string; 267 | constructor(entityManager: EntityManager, priority: number, matcher?: Matcher, workerScript?: string, updateInterval?: number); 268 | protected _paused: boolean; 269 | protected _enabled: boolean; 270 | paused: boolean; 271 | enabled: boolean; 272 | isPaused(): boolean; 273 | isEnabled(): boolean; 274 | /** 275 | * 更新系统 276 | * @param entities 277 | */ 278 | performUpdate(entities: Entity[]): void; 279 | /** 280 | * 更新系统 281 | * @param entities 282 | */ 283 | abstract update(entities: Entity[]): void; 284 | /** 285 | * 筛选实体 286 | * @param entity 287 | */ 288 | entityFilter(entity: Entity): boolean; 289 | filterEntities(entities: Entity[]): Entity[]; 290 | handleComponentChange(entity: Entity, component: Component, added: boolean): void; 291 | protected onComponentAdded(entity: Entity, component: Component): void; 292 | protected onComponentRemoved(entity: Entity, component: Component): void; 293 | /** 294 | * 系统注册时的逻辑 295 | */ 296 | onRegister(): void; 297 | /** 298 | * 系统注销时的逻辑 299 | */ 300 | onUnregister(): void; 301 | } 302 | } 303 | declare module gs { 304 | class Debug { 305 | static isEnabled: boolean; 306 | static enable(): void; 307 | static disable(): void; 308 | } 309 | } 310 | declare module gs { 311 | class PerformanceProfiler { 312 | private static instance; 313 | private performanceData; 314 | private frameCount; 315 | private totalTime; 316 | private maxFrameTime; 317 | private minFrameTime; 318 | private constructor(); 319 | static getInstance(): PerformanceProfiler; 320 | startFrame(): void; 321 | endFrame(): void; 322 | reportPerformance(): void; 323 | } 324 | } 325 | declare module gs { 326 | interface EventListener { 327 | (event: GEvent): void; 328 | } 329 | } 330 | declare module gs { 331 | class GEvent { 332 | type: string; 333 | data: any; 334 | constructor(type: string, data?: any); 335 | reset(): void; 336 | getType(): string; 337 | getData(): any; 338 | } 339 | } 340 | declare module gs { 341 | abstract class InputAdapter { 342 | protected inputManager: InputManager; 343 | constructor(inputManager: InputManager); 344 | /** 345 | * 需要实现此方法以适应使用的游戏引擎 346 | * @param event 347 | */ 348 | abstract handleInputEvent(event: any): void; 349 | protected sendInputToManager(inputEvent: InputEvent): void; 350 | } 351 | } 352 | declare module gs { 353 | /** 354 | * 输入缓冲区 355 | */ 356 | class InputBuffer { 357 | private buffer; 358 | constructor(); 359 | addEvent(event: InputEvent): void; 360 | hasEvents(): boolean; 361 | getEvents(): InputEvent[]; 362 | consumeEvent(): InputEvent | null; 363 | clear(): void; 364 | } 365 | } 366 | declare module gs { 367 | interface InputEvent { 368 | type: string; 369 | data: any; 370 | } 371 | } 372 | declare module gs { 373 | class InputManager { 374 | private entityManager; 375 | private adapter?; 376 | private inputBuffer; 377 | /** 输入历史记录队列 */ 378 | private inputHistory; 379 | private historySizeThreshold; 380 | private eventListeners; 381 | constructor(entityManager: EntityManager); 382 | setHistorySizeThreshold(threshold: number): void; 383 | addEventListener(callback: (event: InputEvent) => void): void; 384 | setAdapter(adapter: InputAdapter): void; 385 | sendInput(event: InputEvent): void; 386 | private handleInput; 387 | /** 388 | * 获取当前帧编号的方法 389 | * @returns 390 | */ 391 | private getCurrentFrameNumber; 392 | getInputBuffer(): InputBuffer; 393 | getInputHistory(): Array<{ 394 | frameNumber: number; 395 | input: InputEvent; 396 | }>; 397 | } 398 | } 399 | declare module gs { 400 | type ComponentConstructor = new (...args: any[]) => T; 401 | /** 402 | * 组件管理器 403 | */ 404 | class ComponentManager { 405 | private components; 406 | private componentPool; 407 | componentType: ComponentConstructor; 408 | /** 409 | * ComponentManager 构造函数 410 | * @param componentType - 用于创建和管理的组件类型。 411 | * 412 | * 用法示例: 413 | * const positionManager = new ComponentManager(PositionComponent); 414 | */ 415 | constructor(componentType: ComponentConstructor); 416 | create(entity: Entity, entityManager: EntityManager): T; 417 | /** 418 | * 获取组件数据 419 | * @param entityId 实体ID 420 | * @returns 组件数据 421 | */ 422 | get(entityId: number): T | null; 423 | /** 424 | * 425 | * @param entityId 426 | * @returns 427 | */ 428 | has(entityId: number): boolean; 429 | /** 430 | * 431 | * @param entityId 432 | * @returns 433 | */ 434 | remove(entityId: number): void; 435 | /** 436 | * 预先创建指定数量的组件实例,并将它们放入对象池 437 | * @param count 要预先创建的组件数量 438 | */ 439 | preallocate(count: number, resetComponents?: boolean): void; 440 | } 441 | } 442 | declare module gs { 443 | class ComponentTypeInfo { 444 | readonly index: number; 445 | readonly type: new (...args: any[]) => Component; 446 | readonly parents: ComponentTypeInfo[]; 447 | readonly allAncestors: number[]; 448 | constructor(index: number, type: new (...args: any[]) => Component); 449 | } 450 | } 451 | declare module gs { 452 | class ComponentTypeManager { 453 | private static componentTypes; 454 | private static indexToComponentTypes; 455 | private static nextIndex; 456 | static getIndexFor(componentType: new (...args: any[]) => Component): ComponentTypeInfo; 457 | static getComponentTypeFor(index: number): Function | undefined; 458 | } 459 | } 460 | declare module gs { 461 | interface StateSnapshot { 462 | entities: any[]; 463 | } 464 | class EntityManager { 465 | private entities; 466 | private entityIdAllocator; 467 | componentManagers: Map, ComponentManager>; 468 | /** 当前帧编号属性 */ 469 | private currentFrameNumber; 470 | private inputManager; 471 | private networkManager; 472 | private queryCache; 473 | private tagToEntities; 474 | private prefabs; 475 | systemManager?: SystemManager; 476 | constructor(systemManager?: SystemManager); 477 | setSystemManager(systemManager: SystemManager): void; 478 | /** 479 | * 添加组件管理器 480 | * @param componentClass 要添加的组件类 481 | */ 482 | addComponentManager(componentClass: ComponentConstructor): ComponentManager; 483 | updateFrameNumber(): void; 484 | getCurrentFrameNumber(): number; 485 | getInputManager(): InputManager; 486 | getNetworkManager(): NetworkManager; 487 | /** 488 | * 创建实体 489 | * @returns customEntityClass 可选的自定义实体类 490 | */ 491 | createEntity(customEntityClass?: new (entityId: number, entityManager: EntityManager, componentManagers: Map, ComponentManager>) => Entity): Entity; 492 | /** 493 | * 删除实体 494 | * @param entityId 495 | */ 496 | deleteEntity(entityId: number): void; 497 | /** 498 | * 从预制件创建实体 499 | * @param name 500 | * @param deepCopy 501 | * @returns 502 | */ 503 | createEntityFromPrefab(name: string, deepCopy?: boolean): Entity | null; 504 | /** 505 | * 注册预制件 506 | * @param name 507 | * @param entity 508 | */ 509 | registerPrefab(name: string, entity: Entity): void; 510 | /** 511 | * 注销预制件 512 | * @param name 513 | */ 514 | unregisterPrefab(name: string): void; 515 | /** 516 | * 获取实体 517 | * @param entityId 实体id 518 | * @returns 实体 519 | */ 520 | getEntity(entityId: number): Entity | null; 521 | /** 522 | * 获取具有特定组件的所有实体 523 | * @param componentClass 要检查的组件类 524 | * @returns 具有指定组件的实体数组 525 | */ 526 | getEntitiesWithComponent(componentClass: ComponentConstructor): Entity[]; 527 | /** 528 | * 查找具有指定组件的实体 529 | * @param componentClasses 530 | * @returns 531 | */ 532 | getEntitiesWithComponents(componentClasses: ComponentConstructor[]): Entity[]; 533 | /** 534 | * 获取所有实体 535 | * @returns 536 | */ 537 | getEntities(): Entity[]; 538 | /** 539 | * 获取具有特定标签的所有实体 540 | * @param tag 要检查的标签 541 | * @returns 具有指定标签的实体数组 542 | */ 543 | getEntitiesWithTag(tag: string): Entity[]; 544 | /** 545 | * 检查实体是否具有指定类型的组件 546 | * @param entityId 要检查的实体的ID 547 | * @param componentClass 要检查的组件类型 548 | * @returns 如果实体具有指定类型的组件,则返回 true,否则返回 false 549 | */ 550 | hasComponent(entityId: number, componentClass: ComponentConstructor): boolean; 551 | /** 552 | * 根据提供的组件数组查询实体 553 | * @param components 要查询的组件数组 554 | * @returns 符合查询条件的实体数组 555 | */ 556 | queryComponents(components: ComponentConstructor[]): Entity[]; 557 | private performQuery; 558 | /** 559 | * 创建当前游戏状态的快照 560 | * @returns 561 | */ 562 | createStateSnapshot(): StateSnapshot; 563 | /** 564 | * 创建增量状态快照 565 | * @param lastSnapshotVersion 上一个快照的版本号 566 | * @returns 返回一个包含实体增量数据的快照对象 567 | */ 568 | createIncrementalStateSnapshot(lastSnapshotVersion: number): any; 569 | /** 570 | * 使用给定的增量状态快照更新游戏状态 571 | * @param incrementalSnapshot 增量状态快照 572 | */ 573 | applyIncrementalSnapshot(incrementalSnapshot: any): void; 574 | /** 575 | * 使用给定的状态快照更新游戏状态 576 | * @param stateSnapshot 577 | */ 578 | updateStateFromSnapshot(stateSnapshot: any): void; 579 | /** 580 | * 应用插值 581 | * @param factor 582 | */ 583 | applyInterpolation(factor: number): void; 584 | /** 585 | * 克隆实体并返回新创建的实体 586 | * @param entity 要克隆的实体 587 | * @param deepCopy 是否使用深拷贝 588 | * @returns 新创建的实体 589 | */ 590 | cloneEntity(entity: Entity, deepCopy?: boolean): Entity; 591 | /** 592 | * 将实体添加到指定标签的缓存 593 | * @param tag 594 | * @param entity 595 | */ 596 | addToTagCache(tag: string, entity: Entity): void; 597 | /** 598 | * 将实体从指定标签的缓存中删除 599 | * @param tag 600 | * @param entity 601 | */ 602 | removeFromTagCache(tag: string, entity: Entity): void; 603 | toJSON(): {}; 604 | } 605 | } 606 | declare module gs { 607 | /** 608 | * ECS 框架中的系统管理器类,负责管理系统的注册、注销以及更新。 609 | */ 610 | class SystemManager { 611 | private systems; 612 | private entityManager; 613 | private systemWorkers; 614 | private entityCache; 615 | private dependencies; 616 | private workerWarningDisplayed; 617 | constructor(entityManager: EntityManager); 618 | /** 619 | * 注册系统 620 | * @param system 系统 621 | * @param dependsOn 可选的依赖系统数组 622 | */ 623 | registerSystem(system: System, dependsOn?: System[]): void; 624 | /** 625 | * 注销系统 626 | * @param system 627 | */ 628 | unregisterSystem(system: System): void; 629 | /** 630 | * 通知所有系统组件已添加 631 | * @param entity 632 | * @param component 633 | */ 634 | notifyComponentAdded(entity: Entity, component: Component): void; 635 | /** 636 | * 通知所有系统组件已删除 637 | * @param entity 638 | * @param component 639 | */ 640 | notifyComponentRemoved(entity: Entity, component: Component): void; 641 | /** 642 | * 使特定系统的实体缓存无效。 643 | * @param system 要使其实体缓存无效的系统 644 | */ 645 | invalidateEntityCacheForSystem(system: System): void; 646 | /** 647 | * 更新系统 648 | */ 649 | update(): void; 650 | /** 651 | * 按优先级和依赖关系对系统进行排序 652 | */ 653 | private sortSystemsByPriorityAndDependencies; 654 | /** 655 | * 确定系统 a 是否依赖于系统 b 656 | * @param a 系统 a 657 | * @param b 系统 b 658 | * @returns 如果系统 a 依赖于系统 b,则为 true,否则为 false 659 | */ 660 | private dependsOn; 661 | dispose(): void; 662 | } 663 | } 664 | declare module gs { 665 | /** 666 | * 时间管理器 667 | */ 668 | class TimeManager { 669 | private static instance; 670 | /** 671 | * 上一帧到这一帧的时间间隔 672 | */ 673 | deltaTime: number; 674 | /** 675 | * 时间缩放 676 | */ 677 | timeScale: number; 678 | /** 679 | * 游戏运行的总时间 680 | */ 681 | totalTime: number; 682 | /** 683 | * 固定时间步长 684 | */ 685 | fixedDeltaTime: number; 686 | accumulatedTime: number; 687 | isPaused: boolean; 688 | private fixedUpdateCallbacks; 689 | private constructor(); 690 | static getInstance(): TimeManager; 691 | update(deltaTime: number): void; 692 | fixedUpdate(deltaTime: number): void; 693 | registerFixedUpdate(callback: (deltaTime: number) => void): void; 694 | pause(): void; 695 | resume(): void; 696 | isGamePaused(): boolean; 697 | } 698 | } 699 | declare module gs { 700 | class Authentication { 701 | private connection; 702 | constructor(connection: Connection); 703 | /** 704 | * 启动身份验证过程。 705 | * @param username - 用户名。 706 | * @param password - 密码。 707 | */ 708 | startAuthentication(username: string, password: string): void; 709 | /** 710 | * 处理服务器端发来的身份验证消息。 711 | * @param message - 身份验证消息对象。 712 | */ 713 | handleAuthenticationMessage(message: Message): void; 714 | /** 715 | * 在身份验证完成后执行一些操作。 716 | */ 717 | private afterAuthenticated; 718 | } 719 | } 720 | declare module gs { 721 | class Connection { 722 | private socket; 723 | isAuthenticated: boolean; 724 | token: string | null; 725 | verificationCode: string | null; 726 | readonly Socket: WebSocket; 727 | constructor(url: string); 728 | send(message: any): void; 729 | } 730 | } 731 | declare module gs { 732 | const ErrorCodes: { 733 | SUCCESS: string; 734 | AUTH_FAIL: string; 735 | WRONG_PASSWORD: string; 736 | REGISTRATION_FAILED: string; 737 | RECONNECT_FAIL: string; 738 | }; 739 | } 740 | declare module gs { 741 | class GNetworkAdapter implements NetworkAdapter { 742 | private serverUrl; 743 | private socket; 744 | private reconnectionAttempts; 745 | private maxReconnectionAttempts; 746 | private connection; 747 | private authentication; 748 | private sessionId; 749 | private lastKnownState; 750 | private messageHandler; 751 | private roomAPI; 752 | readonly RoomAPI: RoomApi; 753 | constructor(serverUrl: string, username: string, password: string); 754 | private connect; 755 | sendInput(frameNumber: number, inputData: any): void; 756 | send(message: Message): void; 757 | onServerUpdate(callback: (serverState: any) => void): void; 758 | private getReconnectDelay; 759 | } 760 | } 761 | declare module gs { 762 | interface Message { 763 | type: string; 764 | subtype?: string; 765 | payload: any; 766 | } 767 | } 768 | declare module gs { 769 | interface NetworkAdapter { 770 | send(message: Message): void; 771 | /** 772 | * 将输入数据发送到服务器 773 | * @param frameNumber 客户端帧编号 774 | * @param inputData 输入数据 775 | */ 776 | sendInput(frameNumber: number, inputData: any): void; 777 | /** 778 | * 从服务器接收状态更新 779 | * @param callback 处理服务器状态更新的回调函数 780 | */ 781 | onServerUpdate(callback: (serverState: any) => void): void; 782 | } 783 | } 784 | declare module gs { 785 | class NetworkManager { 786 | private networkAdapter; 787 | /** 788 | * 设置网络适配器 789 | * @param adapter 用户实现的NetworkAdapter接口 790 | */ 791 | setNetworkAdapter(adapter: NetworkAdapter): void; 792 | /** 793 | * 获取网络适配器 794 | * @returns 795 | */ 796 | getNetworkAdapter(): NetworkAdapter | null; 797 | } 798 | } 799 | declare module gs { 800 | class WebSocketUtils { 801 | static hashPassword(password: string): string; 802 | static sendToConnection(connection: Connection, message: Message): void; 803 | } 804 | } 805 | declare module gs { 806 | class MessageHandler { 807 | private messageHandlers; 808 | emit(message: Message): void; 809 | on(type: string, handler: (msg: Message) => void): void; 810 | private handleMessage; 811 | } 812 | } 813 | declare module gs { 814 | interface Player { 815 | id: string; 816 | } 817 | } 818 | declare module gs { 819 | interface Room { 820 | id: string; 821 | owner: string; 822 | maxPlayers: number; 823 | players: Player[]; 824 | } 825 | } 826 | declare module gs { 827 | class RoomApi { 828 | adapter: NetworkAdapter; 829 | private createRoomCallback; 830 | private playerLeftCallback; 831 | private playerJoinedCallback; 832 | private frameSyncCallback; 833 | private snapshotCallback; 834 | constructor(adapter: NetworkAdapter); 835 | createRoom(maxPlayers: number, callback: (room: Room) => void): void; 836 | joinRoom(roomId: string): void; 837 | /** 838 | * 离开房间 839 | * @param roomId - 房间ID 840 | */ 841 | leaveRoom(roomId: string): void; 842 | /** 843 | * 开始房间帧同步 844 | * @param roomId - 房间ID 845 | */ 846 | startGame(roomId: string): void; 847 | /** 848 | * 结束房间帧同步 849 | * @param roomId 850 | */ 851 | endGame(roomId: string): void; 852 | action(act: any): void; 853 | snapShot(roomId: string, snapshot: any, lastSnapVersion: number): void; 854 | /** 855 | * 设置玩家离开回调 856 | * @param callback 857 | */ 858 | setPlayerLeftCallback(callback: (playerId: string) => void): void; 859 | /** 860 | * 861 | * @param callback 862 | */ 863 | setFrameSync(callback: (payload: any) => void): void; 864 | /** 865 | * 设置玩家加入回调 866 | * @param callback 867 | */ 868 | setPlayerJoinedCallback(callback: (playerId: string, room: Room) => void): void; 869 | setSnapshotCallback(callback: (snapshot: any) => void): void; 870 | /** 871 | * 当房间创建成功时被调用 872 | * @param room - 房间信息 873 | */ 874 | onRoomCreated(room: Room): void; 875 | /** 876 | * 当有玩家离开房间时调用 877 | * @param playerId 878 | */ 879 | onPlayerLeft(playerId: string): void; 880 | /** 881 | * 882 | * @param playerId 883 | * @param room 884 | */ 885 | onPlayerJoined(playerId: string, room: Room): void; 886 | /** 887 | * 888 | * @param payload 889 | */ 890 | onFrameSync(payload: any): void; 891 | onSnapShot(snapshot: any): void; 892 | } 893 | } 894 | declare module gs { 895 | interface ISyncStrategy { 896 | sendState(state: any): void; 897 | receiveState(state: any): any; 898 | handleStateUpdate(deltaTime: number): void; 899 | } 900 | } 901 | declare module gs { 902 | interface InterpolationStrategy { 903 | interpolate(prevSnapshot: any, nextSnapshot: any, progress: number): any; 904 | } 905 | } 906 | declare module gs { 907 | class LinearInterpolationStrategy implements InterpolationStrategy { 908 | interpolate(prevSnapshot: any, nextSnapshot: any, progress: number): any; 909 | } 910 | } 911 | declare module gs { 912 | /** 913 | * 快照插值策略 914 | */ 915 | class SnapshotInterpolationStrategy implements ISyncStrategy { 916 | private snapshotQueue; 917 | private onInterpolation; 918 | setInterpolationCallback(callback: (prevSnapshot: any, nextSnapshot: any) => void): void; 919 | /** 920 | * 发送游戏状态 921 | * @param state 922 | */ 923 | sendState(state: any): void; 924 | /** 925 | * 在收到新的快照时将其添加到快照队列中 926 | * @param state 927 | */ 928 | receiveState(state: any): void; 929 | handleStateUpdate(state: any): void; 930 | } 931 | } 932 | declare module gs { 933 | class SplineInterpolationStrategy implements InterpolationStrategy { 934 | interpolate(prevSnapshot: any, nextSnapshot: any, progress: number): any; 935 | } 936 | } 937 | declare module gs { 938 | /** 939 | * 状态压缩策略 940 | */ 941 | class StateCompressionStrategy implements ISyncStrategy { 942 | onCompressState: (state: any) => any; 943 | onDecompressState: (compressedState: any) => any; 944 | onSendState: (compressedState: any) => void; 945 | onReceiveState: (decompressedState: any) => void; 946 | handleStateUpdate: (state: any) => void; 947 | /** 948 | * 发送游戏状态时,将游戏状态压缩 949 | * @param state 950 | */ 951 | sendState(state: any): void; 952 | /** 953 | * 接收服务器或客户端发送的压缩后的游戏状态,并解压缩更新 954 | */ 955 | receiveState(compressedState: any): void; 956 | } 957 | } 958 | declare module gs { 959 | /** 960 | * 同步策略管理器类 961 | */ 962 | class SyncStrategyManager { 963 | private strategy; 964 | /** 965 | * 构造函数 966 | * @param strategy - 同步策略实现 967 | */ 968 | constructor(strategy: ISyncStrategy); 969 | /** 970 | * 发送状态方法 971 | * @param state - 需要发送的状态对象 972 | */ 973 | sendState(state: any): void; 974 | /** 975 | * 接收状态方法 976 | * @param state - 接收到的状态对象 977 | */ 978 | receiveState(state: any): void; 979 | /** 980 | * 处理状态更新方法 981 | * @param deltaTime - 时间增量 982 | */ 983 | handleStateUpdate(deltaTime: number): void; 984 | /** 985 | * 设置策略方法 986 | * @param strategy - 新的同步策略实现 987 | */ 988 | setStrategy(strategy: ISyncStrategy): void; 989 | } 990 | } 991 | declare module gs { 992 | interface IPlugin { 993 | name: string; 994 | onInit(core: Core): void; 995 | onUpdate(deltaTime: number): void; 996 | } 997 | } 998 | declare module gs { 999 | interface State { 1000 | enter?(): void; 1001 | exit?(): void; 1002 | update?(): void; 1003 | } 1004 | class StateMachine { 1005 | private currentState; 1006 | private states; 1007 | constructor(); 1008 | addState(name: string, state: State): void; 1009 | changeState(name: string): void; 1010 | update(): void; 1011 | } 1012 | } 1013 | declare module gs { 1014 | class StateMachineComponent extends Component { 1015 | stateMachine: StateMachine; 1016 | constructor(); 1017 | reset(): void; 1018 | } 1019 | } 1020 | declare module gs { 1021 | class StateMachineSystem extends System { 1022 | constructor(entityManager: EntityManager); 1023 | entityFilter(entity: Entity): boolean; 1024 | update(entities: Entity[]): void; 1025 | } 1026 | } 1027 | declare module gs { 1028 | class Bits { 1029 | private data; 1030 | constructor(size?: number); 1031 | set(index: number): void; 1032 | clear(index: number): void; 1033 | get(index: number): boolean; 1034 | resize(newSize: number): void; 1035 | } 1036 | } 1037 | declare module gs { 1038 | class EntityIdAllocator { 1039 | private nextId; 1040 | constructor(); 1041 | allocate(): number; 1042 | } 1043 | } 1044 | declare module gs { 1045 | class Node { 1046 | value: T; 1047 | next: Node | null; 1048 | prev: Node | null; 1049 | constructor(value: T); 1050 | } 1051 | /** 1052 | * 双向链表 1053 | */ 1054 | class LinkedList { 1055 | head: Node | null; 1056 | tail: Node | null; 1057 | append(value: T): Node; 1058 | remove(node: Node): void; 1059 | toArray(): T[]; 1060 | } 1061 | } 1062 | declare module gs { 1063 | /** 1064 | * 定义一个实体匹配器类。 1065 | */ 1066 | class Matcher { 1067 | protected allSet: (new (...args: any[]) => Component)[]; 1068 | protected exclusionSet: (new (...args: any[]) => Component)[]; 1069 | protected oneSet: (new (...args: any[]) => Component)[]; 1070 | static empty(): Matcher; 1071 | getAllSet(): (new (...args: any[]) => Component)[]; 1072 | getExclusionSet(): (new (...args: any[]) => Component)[]; 1073 | getOneSet(): (new (...args: any[]) => Component)[]; 1074 | isInterestedEntity(e: Entity): boolean; 1075 | isInterested(components: Bits): boolean; 1076 | private checkAllSet; 1077 | private checkExclusionSet; 1078 | private checkOneSet; 1079 | /** 1080 | * 添加所有包含的组件类型。 1081 | * @param types 所有包含的组件类型列表 1082 | */ 1083 | all(...types: (new (...args: any[]) => Component)[]): Matcher; 1084 | /** 1085 | * 添加排除包含的组件类型。 1086 | * @param types 排除包含的组件类型列表 1087 | */ 1088 | exclude(...types: (new (...args: any[]) => Component)[]): Matcher; 1089 | /** 1090 | * 添加至少包含其中之一的组件类型。 1091 | * @param types 至少包含其中之一的组件类型列表 1092 | */ 1093 | one(...types: (new (...args: any[]) => Component)[]): Matcher; 1094 | } 1095 | } 1096 | declare module gs { 1097 | class Random { 1098 | private seed; 1099 | constructor(seed: number); 1100 | /** 1101 | * 生成 [0, 1) 范围内的随机浮点数 1102 | * @returns 1103 | */ 1104 | next(): number; 1105 | /** 1106 | * 生成 [min, max) 范围内的随机整数 1107 | * @param min 1108 | * @param max 1109 | * @returns 1110 | */ 1111 | nextInt(min: number, max: number): number; 1112 | /** 1113 | * 生成 [min, max) 范围内的随机浮点数 1114 | * @param min 1115 | * @param max 1116 | * @returns 1117 | */ 1118 | nextFloat(min: number, max: number): number; 1119 | /** 1120 | * 从数组中随机选择一个元素 1121 | * @param array 1122 | * @returns 1123 | */ 1124 | choose(array: T[]): T; 1125 | } 1126 | } 1127 | declare module gs { 1128 | /** 1129 | * SparseSet数据结构 1130 | */ 1131 | class SparseSet { 1132 | private sparse; 1133 | private dense; 1134 | private items; 1135 | private count; 1136 | constructor(); 1137 | add(index: number, item: T): void; 1138 | remove(index: number): void; 1139 | has(index: number): boolean; 1140 | get(index: number): T; 1141 | getCount(): number; 1142 | } 1143 | } 1144 | -------------------------------------------------------------------------------- /client-test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "tsc" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "@types/node": "^20.3.2", 14 | "ts-loader": "^9.4.3", 15 | "ts-node": "^10.9.1", 16 | "typescript": "^5.1.5", 17 | "webpack": "^5.88.0", 18 | "webpack-cli": "^5.1.4" 19 | }, 20 | "dependencies": { 21 | "systemjs": "^6.14.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client-test/src/ColorComponent.ts: -------------------------------------------------------------------------------- 1 | class ColorComponent extends gs.Component { 2 | public color: string; 3 | 4 | onInitialize() { 5 | this.color = this.getRandomColor(); 6 | this.markUpdated(lastSnapshotVersion + 1); 7 | } 8 | 9 | private getRandomColor() { 10 | const letters = "0123456789ABCDEF"; 11 | let color = "#"; 12 | for (let i = 0; i < 6; i++) { 13 | color += letters[Math.floor(Math.random() * 16)]; 14 | } 15 | return color; 16 | } 17 | } -------------------------------------------------------------------------------- /client-test/src/DrawSystem.ts: -------------------------------------------------------------------------------- 1 | class DrawSystem extends gs.System { 2 | entityFilter(entity: gs.Entity): boolean { 3 | return entity.hasTag("player"); 4 | } 5 | 6 | update(entities: gs.Entity[]) { 7 | const canvas = document.getElementById("game-canvas") as HTMLCanvasElement; 8 | const ctx = canvas.getContext("2d"); 9 | ctx.clearRect(0, 0, canvas.width, canvas.height); 10 | 11 | for (let entity of entities) { 12 | let color = entity.getComponent(ColorComponent); 13 | let position = entity.getComponent(PositionComponent); 14 | ctx.fillStyle = color.color; 15 | ctx.fillRect(position.x, position.y, 50, 50); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /client-test/src/InputSystem.ts: -------------------------------------------------------------------------------- 1 | class InputSystem extends gs.System { 2 | 3 | entityFilter(entity: gs.Entity): boolean { 4 | return entity.hasTag("player"); 5 | } 6 | 7 | update(entities: gs.Entity[]): void { 8 | const inputBuffer = this.entityManager.getInputManager().getInputBuffer(); 9 | 10 | // 处理输入缓冲区中的事件 11 | while (inputBuffer.hasEvents()) { 12 | const inputEvent = inputBuffer.consumeEvent(); 13 | 14 | // 遍历实体并根据输入事件更新它们 15 | for (const entity of entities) { 16 | if (entity instanceof Player && entity.playerId == inputEvent.data.playerId) { 17 | this.applyInputToEntity(entity, inputEvent); 18 | break; 19 | } 20 | } 21 | } 22 | } 23 | 24 | private applyInputToEntity(entity: gs.Entity, inputEvent: gs.InputEvent): void { 25 | if (inputEvent.type == 'keyboard') { 26 | let velocity = entity.getComponent(VelocityComponent); 27 | velocity.x = 0; 28 | velocity.y = 0; 29 | if (inputEvent.data.isKeyDown) { 30 | switch (inputEvent.data.key.toLowerCase()) { 31 | case 'w': 32 | velocity.y = -100; 33 | break; 34 | case 's': 35 | velocity.y = 100; 36 | break; 37 | case 'a': 38 | velocity.x = -100; 39 | break; 40 | case 'd': 41 | velocity.x = 100; 42 | break; 43 | } 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /client-test/src/MoveSystem.ts: -------------------------------------------------------------------------------- 1 | // 创建系统 2 | class MoveSystem extends gs.System { 3 | entityFilter(entity: gs.Entity): boolean { 4 | return entity.hasComponent(PositionComponent) && entity.hasComponent(VelocityComponent); 5 | } 6 | 7 | update(entities: gs.Entity[]): void { 8 | const deltaTime = gs.TimeManager.getInstance().deltaTime; 9 | for (const entity of entities) { 10 | const position = entity.getComponent(PositionComponent); 11 | const velocity = entity.getComponent(VelocityComponent); 12 | 13 | const xIn = velocity.x * deltaTime; 14 | const yIn = velocity.y * deltaTime; 15 | position.x += xIn; 16 | position.y += yIn; 17 | 18 | if (xIn != 0 || yIn != 0) 19 | position.markUpdated(lastSnapshotVersion + 1); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /client-test/src/MyInputAdapter.ts: -------------------------------------------------------------------------------- 1 | class MyInputAdapter extends gs.InputAdapter { 2 | constructor(inputManager: gs.InputManager, private networkAdapter: gs.GNetworkAdapter) { 3 | super(inputManager); 4 | } 5 | 6 | // 这个方法将处理游戏引擎或平台特定的输入事件,并将它们转换为通用的 InputEvent 对象 7 | handleInputEvent(inputEvent: gs.InputEvent[]): void { 8 | // 将转换后的 InputEvent 发送到 InputManager 9 | for (let event of inputEvent) { 10 | this.sendInputToManager(event); 11 | } 12 | } 13 | 14 | sendInputEvent(event: any, playerId: string): void { 15 | // 处理特定的输入事件,例如将它们转换为通用的 InputEvent 对象 16 | const inputEvent = this.convertEngineEventToInputEvent(event, playerId); 17 | 18 | this.sendInputToManager(inputEvent); 19 | } 20 | 21 | // 将游戏引擎的输入事件转换为 InputEvent 22 | private convertEngineEventToInputEvent(event: any, playerId: string): gs.InputEvent { 23 | let inputEvent: gs.InputEvent = { 24 | type: 'unknown', // 默认为未知类型 25 | data: null // 初始化为null,根据需要设置 26 | }; 27 | 28 | if (event.type === 'keydown' || event.type === 'keyup') { 29 | // 如果事件类型是按键按下或按键松开 30 | inputEvent.type = 'keyboard'; // 设置为键盘事件类型 31 | 32 | inputEvent.data = { 33 | key: event.key, // 使用 keyCode 属性获取按下的键码 34 | isKeyDown: event.type === 'keydown', // 标识按键按下或松开 35 | playerId: playerId 36 | }; 37 | } 38 | 39 | return inputEvent; 40 | } 41 | } -------------------------------------------------------------------------------- /client-test/src/Player.ts: -------------------------------------------------------------------------------- 1 | class Player extends gs.Entity { 2 | public playerId: string; 3 | 4 | onCreate(): void { 5 | console.log('player 实体创建'); 6 | } 7 | 8 | onDestroy(): void { 9 | console.log('player 实体销毁'); 10 | 11 | } 12 | } -------------------------------------------------------------------------------- /client-test/src/PositionComponent.ts: -------------------------------------------------------------------------------- 1 | class PositionComponent extends gs.Component { 2 | public x: number = 0; 3 | public y: number = 0; 4 | 5 | public reset() { 6 | this.x = 0; 7 | this.y = 0; 8 | } 9 | } -------------------------------------------------------------------------------- /client-test/src/VelocityComponent.ts: -------------------------------------------------------------------------------- 1 | class VelocityComponent extends gs.Component { 2 | public x: number = 0; 3 | public y: number = 0; 4 | 5 | public reset() { 6 | this.x = 0; 7 | this.y = 0; 8 | } 9 | } -------------------------------------------------------------------------------- /client-test/src/index.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | const core = gs.Core.instance; 5 | 6 | // 注册系统到系统管理器中 7 | const moveSystem = new MoveSystem(core.entityManager, 0); 8 | core.systemManager.registerSystem(moveSystem); 9 | 10 | const inputSystem = new InputSystem(core.entityManager, 0); 11 | core.systemManager.registerSystem(inputSystem); 12 | 13 | const drawSystem = new DrawSystem(core.entityManager, 0); 14 | 15 | const userName = 'test'; 16 | const password = 'test'; 17 | 18 | 19 | // 使用你的服务器URL实例化网络适配器 20 | let networkAdapter = new gs.GNetworkAdapter('ws://127.0.0.1:8080', userName, password); 21 | // 添加网络适配器到EntityManager 22 | core.entityManager.getNetworkManager().setNetworkAdapter(networkAdapter); 23 | 24 | let playerId; 25 | let roomId; 26 | let playerNet = {}; 27 | let lastSnapshotVersion = 0; 28 | 29 | // 监听服务器更新 30 | core.entityManager.getNetworkManager().getNetworkAdapter().onServerUpdate((serverState: gs.Message) => { 31 | if (serverState.type == "authentication") { 32 | playerId = serverState.payload.id; 33 | 34 | document.getElementById('player-session').textContent = "SESSION:" + serverState.payload.sessionId; 35 | document.getElementById('player-id').textContent = 'ID:' + playerId; 36 | document.getElementById('player-name').textContent = "Name:" + userName; 37 | } else if(serverState.type == 'sessionId') { 38 | document.getElementById('player-session').textContent = "SESSION:" + serverState.payload; 39 | } 40 | 41 | if (serverState.type != 'heartbeat' && serverState.type != 'frameSync') { 42 | document.getElementById('loggerArea').textContent += (`[${serverState.type}]: ${JSON.stringify(serverState.payload)}\n`); 43 | 44 | console.warn('更新游戏状态', serverState); 45 | } 46 | }); 47 | 48 | 49 | function createPlayer(id: string) { 50 | const playerEntity = core.entityManager.createEntity(Player) as Player; 51 | playerEntity.addComponent(PositionComponent); 52 | playerEntity.addComponent(VelocityComponent); 53 | playerEntity.addComponent(ColorComponent); 54 | playerEntity.playerId = id; 55 | playerEntity.addTag("player"); 56 | playerNet[id] = playerEntity.getId(); 57 | } 58 | 59 | function deletePlayer(id: string) { 60 | core.entityManager.deleteEntity(playerNet[id]); 61 | delete playerNet[id]; 62 | 63 | core.systemManager.invalidateEntityCacheForSystem(drawSystem); 64 | } 65 | 66 | function deleteAllPlayer() { 67 | const players = core.entityManager.getEntitiesWithTag('player'); 68 | for (let i = players.length - 1; i >= 0; i --) { 69 | let playerId = (players[i] as Player).playerId; 70 | core.entityManager.deleteEntity(playerNet[playerId]); 71 | } 72 | 73 | playerNet = {}; 74 | 75 | core.systemManager.invalidateEntityCacheForSystem(drawSystem); 76 | } 77 | 78 | const syncStrategy = new gs.SnapshotInterpolationStrategy(); 79 | syncStrategy.setInterpolationCallback((prevSnapshot, nextSnapshot)=>{ 80 | 81 | console.log(`${prevSnapshot} ${nextSnapshot}`); 82 | }); 83 | const strategyManager = new gs.SyncStrategyManager(syncStrategy);// 发送状态 84 | 85 | document.addEventListener("DOMContentLoaded", function() { 86 | // 获取加入房间按钮 87 | const joinRoomButton = document.getElementById("join-room-btn"); 88 | const createRoomButton = document.getElementById("create-room-btn"); 89 | const leaveRoomButton = document.getElementById('leave-room-btn'); 90 | 91 | const startFrameBtn = document.getElementById('start-frame'); 92 | const endFrameBtn = document.getElementById('end-frame'); 93 | 94 | const clearLogBtn = document.getElementById('clear-log'); 95 | 96 | joinRoomButton.onclick = ( ev: MouseEvent)=> { 97 | console.log("发送加入房间指令"); 98 | // 获取输入框元素 99 | const roomInputElement = document.getElementById("room-input") as HTMLInputElement; 100 | // 获取输入框中的房间号 101 | const roomInput = roomInputElement.value; 102 | 103 | networkAdapter.RoomAPI.joinRoom(roomInput); 104 | } 105 | 106 | createRoomButton.onclick = (ev: MouseEvent) => { 107 | console.log("发送创建房间指令"); 108 | 109 | networkAdapter.RoomAPI.createRoom(10, room => { 110 | roomId = room.id; 111 | document.getElementById('room-id').textContent = "ID: " + roomId; 112 | 113 | createPlayer(playerId); 114 | }); 115 | }; 116 | 117 | clearLogBtn.onclick = (ev: MouseEvent) => { 118 | document.getElementById('loggerArea').textContent = ''; 119 | }; 120 | 121 | leaveRoomButton.onclick = (ev: MouseEvent) => { 122 | console.log("发送退出房间指令"); 123 | 124 | networkAdapter.RoomAPI.leaveRoom(roomId); 125 | }; 126 | 127 | startFrameBtn.onclick = (ev: MouseEvent) => { 128 | console.log('开始帧同步'); 129 | 130 | networkAdapter.RoomAPI.startGame(roomId); 131 | }; 132 | 133 | endFrameBtn.onclick = (ev: MouseEvent) => { 134 | console.log('结束帧同步'); 135 | 136 | networkAdapter.RoomAPI.endGame(roomId); 137 | }; 138 | 139 | networkAdapter.RoomAPI.setPlayerLeftCallback(leftPlayerId => { 140 | if (leftPlayerId == playerId) { 141 | // 自己离开房间 142 | document.getElementById('room-id').textContent = ""; 143 | 144 | deleteAllPlayer(); 145 | } else { 146 | console.log(`有玩家离开: ${leftPlayerId}`); 147 | 148 | deletePlayer(leftPlayerId); 149 | } 150 | }); 151 | 152 | networkAdapter.RoomAPI.setPlayerJoinedCallback((joinPlayerId, room) => { 153 | if (joinPlayerId == playerId) { 154 | roomId = room.id; 155 | // 自己加入房间 156 | document.getElementById('room-id').textContent = `ID: ${room.id}`; 157 | for (let player of room.players) { 158 | createPlayer(player.id); 159 | } 160 | }else { 161 | console.log(`有玩家加入: ${joinPlayerId}`); 162 | createPlayer(joinPlayerId); 163 | } 164 | }); 165 | 166 | networkAdapter.RoomAPI.setFrameSync((payload) =>{ 167 | if (payload.actions) { 168 | console.log(payload.frame, payload.actions); 169 | } 170 | 171 | document.getElementById('frame').textContent = `帧: ${payload.frame}`; 172 | 173 | const snapshot = core.entityManager.createIncrementalStateSnapshot(lastSnapshotVersion); 174 | 175 | // 如果有增量数据 176 | if (snapshot.entities.length > 0) { 177 | // 向服务器发送增量数据 178 | networkAdapter.RoomAPI.snapShot(roomId, snapshot, lastSnapshotVersion); 179 | // 输出增量帧 180 | console.log(`version: ${lastSnapshotVersion} snapshot: ${snapshot}`); 181 | } 182 | }); 183 | 184 | networkAdapter.RoomAPI.setSnapshotCallback((snapshot) => { 185 | console.log(`receive snapshot: ${snapshot}`); 186 | 187 | 188 | lastSnapshotVersion = snapshot.lastSnapVersion; 189 | strategyManager.receiveState(snapshot); 190 | // core.entityManager.applyIncrementalSnapshot(snapshot.snapshot); 191 | }); 192 | 193 | 194 | const inputAdapter = new MyInputAdapter(core.entityManager.getInputManager(), networkAdapter); 195 | document.addEventListener("keydown", function(event) { 196 | inputAdapter.sendInputEvent(event, playerId); 197 | }); 198 | 199 | document.addEventListener("keyup", function (event) { 200 | inputAdapter.sendInputEvent(event, playerId); 201 | }); 202 | 203 | core.systemManager.registerSystem(drawSystem); 204 | }); 205 | 206 | let lastTime = 0; 207 | 208 | function update(timestamp) { 209 | const deltaTime = (timestamp - lastTime) / 1000; // 将时间戳转换为秒 210 | 211 | // 这里写你的更新逻辑 212 | core.update(deltaTime); 213 | 214 | lastTime = timestamp; 215 | 216 | // 请求下一帧 217 | requestAnimationFrame(update); 218 | 219 | // 处理状态更新 220 | strategyManager.handleStateUpdate(deltaTime); 221 | 222 | } 223 | 224 | // 开始更新循环 225 | requestAnimationFrame(update); 226 | -------------------------------------------------------------------------------- /client-test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "downlevelIteration": true, 4 | "importHelpers": true, 5 | "pretty": true, 6 | "preserveConstEnums": true, 7 | "alwaysStrict": true, 8 | "module": "system", 9 | "target": "es5", 10 | "declaration": true, 11 | "sourceMap": false, 12 | "removeComments": false, 13 | "experimentalDecorators": true, 14 | "outFile": "./dist/gs-client.js", 15 | "lib": [ 16 | "es5", 17 | "dom", 18 | "es2015.promise", 19 | "es6" 20 | ] 21 | }, 22 | "include": [ 23 | "src", 24 | "lib" 25 | ], 26 | "exclude": [ 27 | "node_modules" 28 | ] 29 | } -------------------------------------------------------------------------------- /client-test/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: './src/index.ts', 5 | module: { 6 | rules: [ 7 | { 8 | test: /\.tsx?$/, 9 | use: 'ts-loader', 10 | exclude: /node_modules/, 11 | }, 12 | ], 13 | }, 14 | mode: 'development', 15 | resolve: { 16 | extensions: [ '.tsx', '.ts', '.js' ], 17 | alias: { 18 | 'gs': path.resolve(__dirname, 'libs/gs/gs'), 19 | } 20 | }, 21 | output: { 22 | filename: 'bundle.js', 23 | path: path.resolve(__dirname, 'dist'), 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /debug/debugViewer.js: -------------------------------------------------------------------------------- 1 | class DebugViewer { 2 | constructor() { 3 | this.gui = new dat.GUI(); 4 | this.entitiesFolder = this.gui.addFolder('Entities'); 5 | this.systemsFolder = this.gui.addFolder('Systems'); 6 | 7 | this.gui.add(this, 'createEntity').name('Create Entity'); 8 | } 9 | 10 | createEntity() { 11 | const positionManager = new gs.ComponentManager(PositionComponent); 12 | const entityManager = new gs.EntityManager([positionManager]); 13 | const entity = entityManager.createEntity(); 14 | entity.name = 'New Entity'; 15 | entity.creationTime = new Date(); 16 | 17 | const entityFolder = this.addEntity(entity); 18 | entityFolder.add(entity, 'creationTime').name('Creation Time'); 19 | } 20 | 21 | addEntity(entity) { 22 | const entityFolder = this.entitiesFolder.addFolder(entity.name); 23 | entityFolder.open(); 24 | return entityFolder; 25 | } 26 | 27 | addComponent(folder, component) { 28 | const componentFolder = folder.addFolder(component.constructor.name); 29 | for (const key in component) { 30 | if (typeof component[key] !== 'function') { 31 | componentFolder.add(component, key); 32 | } 33 | } 34 | componentFolder.open(); 35 | return componentFolder; 36 | } 37 | 38 | addSystem(system) { 39 | const systemFolder = this.systemsFolder.addFolder(system.constructor.name); 40 | systemFolder.open(); 41 | return systemFolder; 42 | } 43 | 44 | init() { 45 | // 创建一个 PositionComponentManager 46 | const positionManager = new gs.ComponentManager(PositionComponent); 47 | gs.Component.registerComponent(PositionComponent, positionManager); 48 | 49 | // 创建一个实体管理器,传入 ComponentManager 数组 50 | const entityManager = new gs.EntityManager([positionManager]); 51 | 52 | // 创建一个实体 53 | const entity = entityManager.createEntity(); 54 | entity.name = 'Player'; 55 | 56 | // 为实体添加一个 PositionComponent 57 | const positionComponent = entity.addComponent(PositionComponent); 58 | 59 | // 添加实体到 DebugViewer 60 | const entityFolder = this.addEntity(entity); 61 | 62 | // 添加 PositionComponent 到 DebugViewer 63 | this.addComponent(entityFolder, positionComponent); 64 | 65 | const movementSystem = new MovementSystem(); 66 | 67 | // 添加系统到 DebugViewer 68 | this.addSystem(movementSystem); 69 | } 70 | } 71 | 72 | class PositionComponent { 73 | constructor(x = 0, y = 0, z = 0) { 74 | this.x = x; 75 | this.y = y; 76 | this.z = z; 77 | } 78 | } 79 | 80 | 81 | // 创建一个系统 82 | class MovementSystem extends gs.System { 83 | constructor() { 84 | super([gs.PositionComponent]); 85 | } 86 | 87 | update() { 88 | // 更新逻辑 89 | } 90 | } -------------------------------------------------------------------------------- /debug/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Debug Viewer 8 | 9 | 10 | 11 | 32 | 33 | 34 | 35 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /docs/custom-input-adapter.md: -------------------------------------------------------------------------------- 1 | # InputAdapter 和输入缓冲区使用指南 2 | 3 | 在本文中,我们将介绍如何使用 `InputAdapter` 和输入缓冲区来处理游戏中的用户输入事件。首先,我们将详细讲解 `InputAdapter` 的概念及其用途,然后我们将通过一个实际的例子展示如何使用输入缓冲区来处理游戏输入。 4 | 5 | ## InputAdapter 理解与使用 6 | 7 | `InputAdapter` 是一个抽象类,它为处理游戏引擎或平台特定的输入事件提供了基础。InputAdapter 的主要目的是将这些特定的输入事件转换为通用的 InputEvent 对象,并将它们添加到输入缓冲区中。要实现自定义的输入适配器,您需要继承 `InputAdapter` 类并实现以下方法: 8 | 9 | ```ts 10 | class MyInputAdapter extends gs.InputAdapter { 11 | constructor(inputManager: gs.InputManager) { 12 | super(inputManager); 13 | } 14 | 15 | // 这个方法将处理游戏引擎或平台特定的输入事件,并将它们转换为通用的 InputEvent 对象 16 | handleInputEvent(event: any): void { 17 | // 处理特定的输入事件,例如将它们转换为通用的 InputEvent 对象 18 | const inputEvent = this.convertEngineEventToInputEvent(event); 19 | 20 | // 将转换后的 InputEvent 发送到 InputManager 21 | this.sendInputToManager(inputEvent); 22 | } 23 | 24 | // 将游戏引擎的输入事件转换为 InputEvent 25 | private convertEngineEventToInputEvent(event: any): gs.InputEvent { 26 | // 根据您使用的游戏引擎实现相应的转换逻辑 27 | // ... 28 | return { 29 | type: event.type, // 示例:游戏引擎事件类型(如按键按下或鼠标移动) 30 | data: event.data, // 示例:事件相关数据(如按键的键码或鼠标坐标) 31 | }; 32 | } 33 | } 34 | ``` 35 | 36 | 在这个例子中,我们创建了一个名为 `MyInputAdapter` 的自定义输入适配器。我们实现了 `handleInputEvent` 方法,该方法负责处理游戏引擎或平台特定的输入事件,并将它们转换为通用的 `InputEvent` 对象。转换后的事件会被发送到 `InputManager`。 37 | 38 | ## 使用输入缓冲区处理游戏输入 39 | 40 | 输入缓冲区是一种数据结构,用于存储处理过的输入事件。在游戏的更新循环中,我们可以访问输入缓冲区并根据存储的事件更新游戏实体的状态。下面是一个简单的例子,展示了如何在游戏更新循环中使用输入缓冲区: 41 | 42 | ```ts 43 | class InputSystem extends gs.System { 44 | // 构造函数和其他方法... 45 | 46 | update(entities: gs.Entity[]): void { 47 | const inputBuffer = this.entityManager.getInputManager().getInputBuffer(); 48 | 49 | // 处理输入缓冲区中的事件 50 | while (inputBuffer.hasEvents()) { 51 | const inputEvent = inputBuffer.consumeEvent(); 52 | 53 | // 遍历实体并根据输入事件更新它们 54 | for (const entity of entities) { 55 | this.applyInputToEntity(entity, inputEvent); 56 | } 57 | } 58 | } 59 | 60 | // 将输入事件应用到游戏实体 61 | private applyInputToEntity(entity: gs.Entity, inputEvent: gs.InputEvent): void { 62 | // 示例:如果实体具有Movable组件,则处理移动输入 63 | if (entity.hasComponent(Movable)) { 64 | const movable = entity.getComponent(Movable); 65 | 66 | switch (inputEvent.type) { 67 | case 'moveLeft': 68 | movable.velocity.x = -1; 69 | break; 70 | case 'moveRight': 71 | movable.velocity.x = 1; 72 | break; 73 | case 'jump': 74 | if (movable.isOnGround) { 75 | movable.velocity.y = -1; 76 | } 77 | break; 78 | default: 79 | break; 80 | } 81 | } 82 | 83 | // 示例:如果实体具有Shooter组件,处理射击输入 84 | if (entity.hasComponent(Shooter)) { 85 | const shooter = entity.getComponent(Shooter); 86 | 87 | if (inputEvent.type === 'shoot') { 88 | shooter.shoot(); 89 | } 90 | } 91 | } 92 | } 93 | ``` 94 | 95 | 在这个示例中,我们定义了 `applyInputToEntity` 方法,该方法根据输入事件更新游戏实体的状态。首先,我们检查实体是否具有 `Movable` 组件。如果实体具有该组件,我们根据输入事件的类型更新实体的速度。接下来,我们检查实体是否具有 `Shooter` 组件。如果实体具有该组件,我们在输入事件类型为 'shoot' 时调用 `shooter.shoot()` 方法。 96 | 97 | 这样,我们就能根据输入缓冲区中的事件来更新游戏实体的状态。通过使用 `InputAdapter` 和输入缓冲区,我们可以确保游戏输入的处理是与游戏引擎或平台无关的,从而使我们的 ECS 框架更具通用性和可扩展性。 -------------------------------------------------------------------------------- /docs/emitter.md: -------------------------------------------------------------------------------- 1 | # G-Framework 事件系统 - GlobalEventEmitter 2 | G-Framework 的事件系统提供了一个全局的事件发射器(GlobalEventEmitter),用于在组件和系统之间传递消息,降低耦合度。GlobalEventEmitter 使用了对象池技术,以减少垃圾回收的压力。 3 | 4 | # 使用方法 5 | 监听事件 6 | 要监听一个事件,你需要使用 GlobalEventEmitter.on 方法,传入事件类型(字符串)和回调函数: 7 | 8 | ```typescript 9 | GlobalEventEmitter.on('someEvent', (event: Event) => { 10 | console.log('someEvent occurred:', event.data); 11 | }); 12 | ``` 13 | ## 发射事件 14 | 要发射一个事件,使用 GlobalEventEmitter.emit 方法,传入事件类型(字符串)和事件数据: 15 | 16 | ```typescript 17 | const eventData = { key: 'value' }; 18 | GlobalEventEmitter.emit('someEvent', eventData); 19 | ``` 20 | ## 取消监听事件 21 | 要取消监听一个事件,你需要使用 GlobalEventEmitter.off 方法,传入事件类型(字符串)和回调函数: 22 | 23 | ```typescript 24 | const callback = (event: Event) => { 25 | console.log('someEvent occurred:', event.data); 26 | }; 27 | 28 | GlobalEventEmitter.on('someEvent', callback); 29 | // 当你不再需要监听事件时 30 | GlobalEventEmitter.off('someEvent', callback); 31 | ``` 32 | > 注意:取消监听事件时,请确保传入的回调函数与 on 方法中使用的回调函数相同。 33 | 34 | ## 一次性监听事件 35 | 如果你只想监听一个事件的第一次发生,可以使用 GlobalEventEmitter.once 方法: 36 | 37 | ```typescript 38 | GlobalEventEmitter.once('someEvent', (event: Event) => { 39 | console.log('someEvent occurred for the first time:', event.data); 40 | }); 41 | ``` 42 | 这样,回调函数将在事件第一次发生时被调用,然后自动从监听列表中移除。 43 | 44 | # 示例 45 | 以下是一个使用 GlobalEventEmitter 的简单示例: 46 | 47 | ```typescript 48 | class Player { 49 | constructor() { 50 | GlobalEventEmitter.on('playerJump', this.onPlayerJump.bind(this)); 51 | } 52 | 53 | onPlayerJump(event: Event): void { 54 | console.log('Player jumped:', event.data); 55 | } 56 | 57 | jump(): void { 58 | const eventData = { jumpHeight: 5 }; 59 | GlobalEventEmitter.emit('playerJump', eventData); 60 | } 61 | } 62 | 63 | const player = new Player(); 64 | player.jump(); // 输出:Player jumped: { jumpHeight: 5 } 65 | ``` -------------------------------------------------------------------------------- /docs/entity-prefab.md: -------------------------------------------------------------------------------- 1 | # 实体预制件 2 | 在g-framework中,我们知道游戏中的许多对象(例如:敌人,道具,特效等)可能需要反复创建和销毁。如果每次需要这些对象时都手动创建一个全新的实例,这不仅编程繁琐,而且可能对性能产生负面影响,特别是在大量对象频繁创建和销毁的情况下。 3 | 4 | 实体预制体(Entity Prefabs)提供了一个高效的方式来解决这个问题。预制体是一个事先配置好的实体模板,我们可以从中快速复制(实例化)出新的实体,这样,我们只需要在游戏初始化的时候创建并配置好预制体,之后就可以快速地从预制体创建新的实体,这不仅简化了代码,也提高了性能。 5 | 6 | EntityManager类提供了对预制体的管理功能,包括注册、注销以及从预制体创建新实体等功能。 7 | 8 | ## createEntityFromPrefab 9 | 从预制件创建实体。 10 | 11 | ```typescript 12 | public createEntityFromPrefab(name: string, deepCopy: boolean = false): Entity | null; 13 | ``` 14 | 参数 15 | - name - 预制件的名称。 16 | - deepCopy - 是否进行深复制,默认为 false。 17 | 18 | 返回值 19 | - 如果找到预制件,返回新创建的实体; 20 | - 如果没有找到对应名称的预制件,返回 null。 21 | 22 | ### 使用示例 23 | ```typescript 24 | let entityManager = gs.Core.instnace.entityManager; 25 | let entity = entityManager.createEntityFromPrefab("monster", true); 26 | ``` 27 | 28 | ## registerPrefab 29 | 注册预制件。 30 | 31 | ```typescript 32 | public registerPrefab(name: string, entity: Entity): void; 33 | ``` 34 | 35 | 参数 36 | - name - 预制件的名称。 37 | - entity - 实体对象。 38 | 使用示例 39 | ```typescript 40 | let entityManager = gs.Core.instnace.entityManager; 41 | let monster = entityManager.createEntity(); 42 | entityManager.registerPrefab("monster", monster); 43 | ``` 44 | 45 | ### unregisterPrefab 46 | 注销预制件。 47 | 48 | ```typescript 49 | public unregisterPrefab(name: string): void; 50 | ``` 51 | 52 | 参数 53 | - name - 预制件的名称。 54 | 55 | 使用示例 56 | ```typescript 57 | let entityManager = gs.Core.instnace.entityManager; 58 | entityManager.unregisterPrefab("monster"); 59 | ``` 60 | 61 | ## 为什么要使用预制体 62 | 63 | 预制体的一个主要优点是可以将实体的创建和配置从使用它的地方分离出来。如果不使用预制体,每次需要一个新的实体,都需要创建一个新的实体,并对其进行一系列的配置,这不仅使得代码冗余,而且降低了效率。 64 | 65 | 使用预制体,你只需要创建并配置一次实体,之后就可以多次从预制体创建新的实体,这极大地简化了代码,提高了代码的复用性,同时也提高了运行时的性能。 66 | 67 | 除此之外,预制体还支持深复制和浅复制两种方式。浅复制是共享同一份资源,这意味着所有的实体都会共享同样的属性和状态, 68 | 69 | ## 示例 70 | 71 | 首先,我们假设有一个基础的Entity类,它可以是一个游戏中的角色,敌人,道具等,每个实体都有自己的特性和属性。为了示例的目的,我们简单定义一个带有名字的Entity类。 72 | 73 | 我们的游戏需要大量的“monster”实体。手动创建大量相同的实体会非常繁琐且效率低下,因此我们会创建一个“monster”实体的预制件,并将其注册到EntityManager中。 74 | 75 | ```typescript 76 | let entityManager = gs.Core.instnace.entityManager; 77 | let monsterPrefab = entityManager.createEntity(); 78 | // 你可以再这里添加一些组件给这个实体,例如: 79 | // monsterPrefab.addComponent(PositionComponent); 80 | entityManager.registerPrefab("monster", monsterPrefab); 81 | ``` 82 | 83 | 在这个阶段,我们创建了一个monster的实体,并注册为预制件。接下来,我们想创建更多的monster实体,我们就可以通过预制件来创建: 84 | 85 | ```typescript 86 | let monster1 = entityManager.createEntityFromPrefab("monster"); 87 | let monster2 = entityManager.createEntityFromPrefab("monster"); 88 | ``` 89 | 90 | 在这个阶段,我们成功创建了两个新的monster实体,它们都是基于monster预制件创建的。如果我们不再需要monster预制件,我们可以将其从EntityManager中注销: 91 | 92 | ```typescript 93 | entityManager.unregisterPrefab("monster"); 94 | ``` 95 | 96 | 这样一来,monster预制件就被成功移除了。如果我们再尝试从这个预制件创建实体,将会返回null,因为没有找到monster预制件: 97 | 98 | ```typescript 99 | let monster3 = entityManager.createEntityFromPrefab("monster"); 100 | ``` 101 | -------------------------------------------------------------------------------- /docs/input-manager.md: -------------------------------------------------------------------------------- 1 | # 输入管理器 2 | 3 | `InputManager` 是游戏同步框架的一个核心组件,它负责处理和管理输入事件。通过使用适配器模式,您可以轻松地将 `InputManager` 与您选择的游戏引擎或平台集成在一起。 4 | 5 | ## 用途 6 | 7 | InputManager 的主要功能包括: 8 | 9 | - 管理输入缓冲区 10 | - 处理输入事件 11 | - 存储输入历史记录 12 | 13 | ## 自定义输入适配器 14 | 15 | 要将 InputManager 集成到您的游戏引擎中,需要创建一个自定义的 `InputAdapter`,使得游戏引擎的输入事件可以转换为框架所需的 InputEvent 格式。下面是一个示例,演示如何为一个游戏引擎创建自定义适配器: 16 | 17 | 18 | ```ts 19 | class CustomInputAdapter extends gs.InputAdapter { 20 | constructor(inputManager: gs.InputManager) { 21 | super(inputManager); 22 | } 23 | 24 | handleInputEvent(event: any): void { 25 | // 转换游戏引擎的输入事件为框架需要的 InputEvent 26 | const inputEvent: gs.InputEvent = { 27 | type: event.type, // 用户自定义的输入类型,如键盘按键或鼠标移动 28 | data: event.data // 输入事件的相关数据,例如按键的键码或鼠标的坐标 29 | }; 30 | 31 | // 将转换后的 InputEvent 传递给 InputManager 32 | this.sendInputToManager(inputEvent); 33 | } 34 | } 35 | ``` 36 | 37 | ## 使用自定义输入适配器和 InputManager 38 | 39 | 接下来,实例化 InputManager 并将自定义的 InputAdapter 设置为其适配器。下面的示例展示了如何在游戏中实现这一过程: 40 | 41 | ```ts 42 | // 创建 EntityManager 实例 43 | const entityManager = new gs.EntityManager(/* 传入您的 ComponentManager 实例列表 */); 44 | 45 | // 从 EntityManager 获取 InputManager 46 | const inputManager = entityManager.getInputManager(); 47 | 48 | // 创建自定义输入适配器实例 49 | const customInputAdapter = new CustomInputAdapter(inputManager); 50 | 51 | // 将自定义输入适配器设置为 InputManager 的适配器 52 | inputManager.setAdapter(customInputAdapter); 53 | ``` 54 | 55 | 现在,在游戏引擎中的输入事件将通过自定义 InputAdapter 处理,并自动传递给 InputManager。InputManager 将存储输入历史记录以供客户端进行预测和回滚。 56 | 57 | > 更多关于输入适配器的详细信息,请查看 [输入适配器](custom-input-adapter.md) 58 | 59 | ## 小贴士 60 | 61 | 在实际开发中,应该根据所使用的游戏引擎和输入设备事件类型去自定义 `InputEvent` 接口,使其符合实际需求。同时,可以根据实际项目需求对 `InputAdapter` 进行扩展和优化,提高可扩展性与维护性。 -------------------------------------------------------------------------------- /docs/interpolation.md: -------------------------------------------------------------------------------- 1 | # 客户端插值 2 | 3 | 为了在客户端提供平滑的视觉效果,您可以实现插值技术。插值可以用于平滑地显示实体在不同状态之间的过渡,从而降低网络延迟带来的影响。 4 | 5 | ## 实现Interpolatable接口 6 | 7 | 首先,在您的组件基类中定义一个Interpolatable接口,确保所有需要插值的组件都实现了所需的方法: 8 | 9 | ```ts 10 | interface Interpolatable { 11 | savePreviousState(): void; 12 | applyInterpolation(factor: number): void; 13 | } 14 | ``` 15 | 16 | ## 为组件添加插值支持 17 | 18 | 以TransformComponent为例,实现Interpolatable接口: 19 | 20 | ```ts 21 | class TransformComponent extends Component implements Interpolatable { 22 | position: Vector2; 23 | rotation: number; 24 | 25 | previousPosition: Vector2; 26 | previousRotation: number; 27 | 28 | // ... 29 | 30 | savePreviousState() { 31 | this.previousPosition = { ...this.position }; 32 | this.previousRotation = this.rotation; 33 | } 34 | 35 | applyInterpolation(factor: number) { 36 | const interpolatedPosition = { 37 | x: lerp(this.previousPosition.x, this.position.x, factor), 38 | y: lerp(this.previousPosition.y, this.position.y, factor), 39 | }; 40 | const interpolatedRotation = lerp(this.previousRotation, this.rotation, factor); 41 | 42 | // 应用插值后的状态 43 | this.setPosition(interpolatedPosition); 44 | this.setRotation(interpolatedRotation); 45 | } 46 | } 47 | ``` 48 | 49 | ## 在游戏循环中应用插值 50 | 51 | 在游戏循环中,根据当前时间和状态更新时间调用applyInterpolation方法。例如: 52 | 53 | ```ts 54 | function gameLoop(timestamp: number) { 55 | // ... 56 | 57 | // 计算插值因子 58 | const factor = (timestamp - lastUpdateTime) / timeBetweenUpdates; 59 | 60 | // 应用插值 61 | entityManager.applyInterpolation(factor); 62 | 63 | // 渲染 64 | render(); 65 | 66 | // ... 67 | } 68 | ``` 69 | 70 | 这样,您只需要关注实现具体的插值逻辑,而框架会负责调用和管理插值过程。 -------------------------------------------------------------------------------- /docs/network-adapter.md: -------------------------------------------------------------------------------- 1 | # 网络适配器(NetworkAdapter) 2 | 3 | 网络适配器(`NetworkAdapter`)是一个接口,用于将ECS框架与用户选择的通信协议相连接。通过实现这个接口,用户可以根据自己的需求定制通信协议,从而实现客户端与服务器之间的通信。 4 | 5 | ## 接口定义 6 | 7 | `NetworkAdapter`接口定义了两个方法: 8 | 9 | - `sendInput(frameNumber: number, inputData: any): void`:发送输入数据到服务器。 10 | - `onServerUpdate(callback: (serverState: any) => void): void`:处理从服务器接收到的状态更新。 11 | 12 | 需要实现这个接口,并在其中添加具体的通信逻辑。 13 | 14 | ## 如何使用 15 | 16 | 1. 实现`NetworkAdapter`接口 17 | 18 | 首先,用户需要根据自己的通信协议实现`NetworkAdapter`接口。例如,假设我们有一个基于WebSocket的通信协议,可以这样实现: 19 | 20 | ```ts 21 | class WebSocketNetworkAdapter implements gs.NetworkAdapter { 22 | private websocket: WebSocket; 23 | 24 | constructor(url: string) { 25 | this.websocket = new WebSocket(url); 26 | } 27 | 28 | sendInput(frameNumber: number, inputData: any): void { 29 | const message = { 30 | frameNumber, 31 | inputData, 32 | }; 33 | this.websocket.send(JSON.stringify(message)); 34 | } 35 | 36 | onServerUpdate(callback: (serverState: any) => void): void { 37 | this.websocket.addEventListener("message", (event) => { 38 | const serverState = JSON.parse(event.data); 39 | callback(serverState); 40 | }); 41 | } 42 | } 43 | ``` 44 | 45 | 2. 将实现的网络适配器设置到EntityManager 46 | 47 | 在实例化EntityManager后,使用setNetworkAdapter方法将实现的网络适配器设置到EntityManager中: 48 | 49 | ```ts 50 | const entityManager = new gs.EntityManager(componentManagers); 51 | const websocketAdapter = new WebSocketNetworkAdapter("wss://example.com/game"); 52 | entityManager.getNetworkManager().setNetworkAdapter(websocketAdapter); 53 | ``` 54 | 55 | 3. 使用网络适配器发送输入和处理服务器更新 56 | 57 | 在游戏逻辑中,使用sendInput方法发送客户端的输入数据到服务器。同时,使用onServerUpdate方法处理从服务器接收到的状态更新。 58 | 59 | ```ts 60 | // 注册输入事件处理逻辑 61 | const inputAdapter = new MyInputAdapter(entityManager.getInputManager()); 62 | inputAdapter.handleInputEvent(/* 某些特定于引擎的事件 */); 63 | 64 | // 将转换后的 InputEvent 添加到输入缓冲区中 65 | entityManager.getInputManager().sendInput(inputEvent); 66 | 67 | // 监听服务器更新 68 | entityManager.getNetworkManager().getNetworkAdapter().onServerUpdate((serverState) => { 69 | // 更新游戏状态 70 | }); 71 | 72 | ``` 73 | 74 | 通过以上步骤,可以将自定义的通信协议与ECS框架相结合,从而实现客户端与服务器之间的通信。 75 | 76 | ## 提示 77 | 78 | 上文提到的MyInputAdapter可以参考以下来实现 79 | 80 | - [输入适配器](custom-input-adapter.md) -------------------------------------------------------------------------------- /docs/object-pool.md: -------------------------------------------------------------------------------- 1 | # `ObjectPool`类的使用指南 2 | 3 | ## 简介 4 | 5 | `ObjectPool`是一个通用的对象池实现,它可以帮助你管理和重复利用对象,从而避免频繁的对象创建和销毁带来的性能开销。在游戏开发、资源管理等需要高效利用内存的场景中,`ObjectPool`可以发挥很大的作用。 6 | 7 | ## 使用方法 8 | 9 | 以下是如何使用`ObjectPool`类的详细步骤: 10 | 11 | ### 1. 创建对象池 12 | 13 | 首先,你需要提供两个函数:一个用于创建新对象,一个用于重置对象。然后你就可以创建一个`ObjectPool`实例了。 14 | 15 | ```ts 16 | const pool = new gs.ObjectPool( 17 | () => new SomeClass(), // createFn: 用于创建新的SomeClass对象 18 | (obj) => obj.reset() // resetFn: 用于重置SomeClass对象的状态 19 | ); 20 | ``` 21 | 22 | 在上面的例子中,SomeClass是你想要管理的对象的类。请确保该类有一个可以重置对象状态的方法(在这个例子中是reset方法)。 23 | 24 | ### 2. 获取对象 25 | 当你需要一个对象时,可以使用acquire方法来获取。如果对象池中有可用的对象,acquire方法会取出一个,重置它的状态,然后返回。如果对象池中没有可用的对象,acquire方法会创建一个新的对象然后返回。 26 | 27 | ```ts 28 | const obj = pool.acquire(); 29 | ``` 30 | 31 | ### 3. 归还对象 32 | 当你不再需要一个对象时,你可以使用release方法将其归还到对象池中,以便稍后重复使用。 33 | 34 | ```ts 35 | pool.release(obj); 36 | ``` 37 | 请注意,一旦你调用了release方法归还了一个对象,你就不应再使用该对象,除非你再次通过acquire方法获取到它。 38 | 39 | ## 示例 40 | 假设你有一个名为Bullet的类,它代表一颗子弹。子弹在游戏中经常被创建和销毁,所以你决定使用ObjectPool来管理它们。 41 | 42 | ```ts 43 | class Bullet { 44 | x: number; 45 | y: number; 46 | isAlive: boolean; 47 | 48 | constructor() { 49 | this.reset(); 50 | } 51 | 52 | reset() { 53 | this.x = 0; 54 | this.y = 0; 55 | this.isAlive = false; 56 | } 57 | 58 | fire(x: number, y: number) { 59 | this.x = x; 60 | this.y = y; 61 | this.isAlive = true; 62 | } 63 | } 64 | 65 | const bulletPool = new gs.ObjectPool( 66 | () => new Bullet(), // 创建Bullet对象 67 | (bullet) => bullet.reset() // 重置Bullet对象 68 | ); 69 | 70 | // 当你需要发射一颗子弹时 71 | const bullet = bulletPool.acquire(); 72 | bullet.fire(player.x, player.y); 73 | 74 | // 当子弹超出屏幕范围时 75 | if (bullet.x < 0 || bullet.x > screenWidth || bullet.y < 0 || bullet.y > screenHeight) { 76 | bulletPool.release(bullet); 77 | } 78 | ``` 79 | 80 | 在这个例子中,Bullet类有一个reset方法,用于重置子弹的状态。你创建了一个用于管理Bullet对象的ObjectPool,并提供了创建和重置Bullet对象的函数。当你需要发射一颗子弹时,你从对象池中获取一个Bullet对象,并调用它的fire方法。当子弹超出屏幕范围时,你将其归还到对象池中。 81 | 82 | ## 提示 83 | - 为了获取最佳性能,建议在可能的情况下尽量复用对象池中的对象,而不是频繁地创建和销毁对象。 84 | - 当你的resetFn函数被调用时,你应确保该对象的状态被完全重置,以便它可以被安全地重复使用。 85 | - 虽然ObjectPool类可以帮你管理对象的生命周期,但你仍需要自己负责管理和跟踪哪些对象正在使用,哪些对象可以归还到对象池中。 -------------------------------------------------------------------------------- /docs/state-machine.md: -------------------------------------------------------------------------------- 1 | # StateMachine 2 | 3 | `StateMachine` 是 G-Framework 中用于管理状态的通用模块。它允许您为游戏对象定义不同的状态,并在它们之间轻松地切换。 4 | 5 | ## 使用方法 6 | 7 | 首先,创建一个新的状态机实例: 8 | 9 | ```typescript 10 | const stateMachine = new gs.StateMachine(); 11 | ``` 12 | 13 | 接下来,创建并添加自定义状态: 14 | 15 | ```ts 16 | class IdleState extends gs.State { 17 | enter() { 18 | console.log("Entering idle state"); 19 | } 20 | 21 | exit() { 22 | console.log("Exiting idle state"); 23 | } 24 | } 25 | 26 | stateMachine.addState("idle", new IdleState()); 27 | ``` 28 | 29 | 在游戏循环中更新状态机: 30 | 31 | ```ts 32 | stateMachine.update(deltaTime); 33 | ``` 34 | 35 | 使用 setState 方法切换到不同的状态: 36 | 37 | ```ts 38 | stateMachine.setState("idle"); 39 | ``` 40 | 41 | ## State 42 | 43 | 所有状态都应继承自 gs.State 基类。自定义状态应实现以下方法: 44 | 45 | - `enter`: 当状态被激活时调用。 46 | - `exit`: 当状态被离开时调用。 47 | 48 | 您可以在这些方法中实现状态特定的逻辑,例如改变游戏对象的动画、行为等。 49 | 50 | ## 示例 51 | 52 | 以下是一个示例,展示如何使用 StateMachine 实现游戏角色的不同行为: 53 | 54 | ```ts 55 | class WalkingState extends gs.State { 56 | enter() { 57 | console.log("Entering walking state"); 58 | } 59 | 60 | exit() { 61 | console.log("Exiting walking state"); 62 | } 63 | } 64 | 65 | class JumpingState extends gs.State { 66 | enter() { 67 | console.log("Entering jumping state"); 68 | } 69 | 70 | exit() { 71 | console.log("Exiting jumping state"); 72 | } 73 | } 74 | 75 | stateMachine.addState("walking", new WalkingState()); 76 | stateMachine.addState("jumping", new JumpingState()); 77 | 78 | // 切换到行走状态 79 | stateMachine.setState("walking"); 80 | 81 | // 切换到跳跃状态 82 | stateMachine.setState("jumping"); 83 | ``` 84 | 85 | 通过使用 StateMachine,您可以轻松地在 G-Framework 中实现游戏对象的状态管理 -------------------------------------------------------------------------------- /docs/state-snapshop.md: -------------------------------------------------------------------------------- 1 | # 状态快照 2 | 3 | 状态快照(State Snapshot)是用于存储和同步游戏状态的一种技术。在多人游戏中,为了同步各个客户端的游戏状态,通常会使用状态快照。状态快照可以简化游戏的网络通信,通过传输游戏状态的快照,而不是逐个传输实体和组件的状态。 4 | 5 | 在我们的ECS框架中,已经实现了基本的状态快照功能。你可以使用`EntityManager`中的`createStateSnapshot`方法生成当前游戏状态的快照,使用`updateStateFromSnapshot`方法根据给定的状态快照来更新游戏状态。 6 | 7 | ## 使用方法 8 | 9 | ### 1. 生成状态快照 10 | 11 | 要生成状态快照,首先需要创建一个 EntityManager 实例。然后,调用 createStateSnapshot 方法生成当前游戏状态的快照。 12 | 13 | ```typescript 14 | const entityManager = new EntityManager(/* ... */); 15 | const stateSnapshot = entityManager.createStateSnapshot(); 16 | ``` 17 | 18 | `createStateSnapshot`方法会遍历所有实体,调用它们的`serialize`方法,将实体及其组件的状态序列化为一个对象。这个对象可以被用来传输给其他客户端,以便同步游戏状态。 19 | 20 | ### 2. 使用状态快照更新游戏状态 21 | 22 | 要使用状态快照更新游戏状态,需要调用 EntityManager 的 updateStateFromSnapshot 方法,并传入状态快照。 23 | 24 | ```typescript 25 | entityManager.updateStateFromSnapshot(stateSnapshot); 26 | ``` 27 | 28 | `updateStateFromSnapshot`方法会根据给定的状态快照更新游戏状态。首先,该方法会遍历状态快照中的实体数据,如果本地不存在对应实体,则创建一个新的实体。然后,使用实体数据中的组件数据来更新实体的组件状态。最后,用更新后的实体替换原有的实体。 29 | 30 | 31 | ### 3. 为组件重写 shouldSerialize 方法 32 | 33 | 在 Component 类中有一个 shouldSerialize 方法。默认情况下,它可以返回 true,表示该组件应该被序列化。子类可以根据需要覆盖此方法。 34 | 35 | 这样做的目的是为了在序列化时仅包含需要同步的组件,从而减少网络传输的数据量。 36 | 37 | ### 4. 实现增量更新 38 | 39 | 为了进一步减少网络传输的数据量,可以实现增量更新。增量更新是指在创建状态快照时仅包含自上次快照以来发生变化的实体和组件。 40 | 41 | 要实现增量更新,需要在 EntityManager 类中使用一个方法 `createIncrementalStateSnapshot`,用于创建增量状态快照。同时,在 Entity 类中有一个 `serializeIncremental` 方法,仅序列化具有较高版本号的组件。 42 | 43 | 首先,创建一个 EntityManager 实例。我们将在这个实例上调用增量更新方法: 44 | 45 | ```ts 46 | const entityManager = new EntityManager(/* ... */); 47 | ``` 48 | 49 | 为了跟踪上一次快照的版本号,首先需要将其存储在一个变量中。这里我们初始化为0: 50 | 51 | ```ts 52 | let lastSnapshotVersion = 0; 53 | ``` 54 | 55 | 每次您需要创建一个增量状态快照时,调用 createIncrementalStateSnapshot 方法,并将 lastSnapshotVersion 作为参数传入。 56 | 57 | ```ts 58 | const incrementalStateSnapshot = entityManager.createIncrementalStateSnapshot(lastSnapshotVersion); 59 | ``` 60 | 61 | 这将返回一个包含自上次快照版本以来的所有实体和组件更改的对象。在发送此增量快照并将其应用到其他客户端的 EntityManager 实例之后,您需要更新 lastSnapshotVersion 以便下一次使用。 62 | 63 | 例如,如果客户端收到了应用增量快照后的 currentSnapshotVersion,则可以更新 lastSnapshotVersion: 64 | 65 | ```ts 66 | lastSnapshotVersion = currentSnapshotVersion; 67 | ``` 68 | 69 | 当其他客户端收到增量状态快照时,它们可以使用 updateStateFromSnapshot 方法更新游戏状态: 70 | 71 | ```ts 72 | entityManager.updateStateFromSnapshot(incrementalStateSnapshot); 73 | ``` 74 | 75 | 通过这种方式,您可以在创建状态快照时仅将自上次快照以来发生变化的实体和组件包含在内,从而实现增量更新 76 | 77 | ## 注意事项 78 | 79 | 在使用状态快照时,需要注意同步问题。在客户端预测和服务器确认的过程中,可能需要将状态回滚到之前的某个快照,并重新应用输入数据。在这种情况下,可以使用`createStateSnapshot`和`updateStateFromSnapshot`方法进行回滚和更新。具体的同步策略和回滚机制取决于你的游戏逻辑和需求。 80 | 81 | 此外,为了提高性能,可以考虑使用增量更新。通过实现增量更新,您可以在创建状态快照时仅包含自上次快照以来发生变化的实体和组件,从而减少网络传输的数据量。 -------------------------------------------------------------------------------- /docs/sync-strategy.md: -------------------------------------------------------------------------------- 1 | # 游戏同步策略 2 | 3 | 游戏同步策略是一个轻量级扩展库,帮助游戏开发者实现多种同步策略,包括状态插值、状态压缩等。通过使用 SyncStrategyManager 类来管理选择的同步策略,可以更方便地切换同步策略实现。以下是一些简要说明和如何使用游戏同步策略框架的示例。 4 | 5 | ### SyncStrategyManager 6 | 7 | SyncStrategyManager 是一个同步策略管理器,它接收一个实现了 ISyncStrategy 接口的策略实例作为参数。它提供了发送状态、接收状态、处理状态更新和设置策略的方法。 8 | 9 | 1. 创建一个 SyncStrategyManager 实例,并提供一个同步策略实现,比如 SnapshotInterpolationStrategy: 10 | 11 | ```ts 12 | const syncStrategy = new gs.SnapshotInterpolationStrategy(); 13 | const strategyManager = new gs.SyncStrategyManager(syncStrategy); 14 | ``` 15 | 16 | 2. 使用同步策略管理器执行发送状态、接收状态、处理状态更新等方法: 17 | 18 | ```ts 19 | // 发送状态 20 | strategyManager.sendState(state); 21 | 22 | // 接收状态 23 | strategyManager.receiveState(state); 24 | 25 | // 处理状态更新 26 | strategyManager.handleStateUpdate(deltaTime); 27 | ``` 28 | 29 | 3. 如果需要更改同步策略实现,可以使用 setStrategy 方法: 30 | 31 | ```ts 32 | // 创建一个新的策略实现 33 | const newStrategy = new gs.StateCompressionStrategy(); 34 | // 设置新策略实现 35 | strategyManager.setStrategy(newStrategy); 36 | ``` 37 | 38 | 在实际应用中,您可以根据游戏的需求选择合适的同步策略,同时也可以在运行时更改同步策略。通过使用 SyncStrategyManager 管理同步策略,可以让您的代码更加灵活和易于扩展。 39 | 40 | ## 策略介绍 41 | 42 | 1. `SnapshotInterpolationStrategy`:基于快照的插值同步策略,适用于使用实体位置、旋转、缩放等属性进行插值的游戏。框架内部提供必要的钩子和事件,让用户可以根据实际项目需求实现自定义的实体状态插值逻辑。 43 | 44 | 2. `StateCompressionStrategy`:状态压缩同步策略,适用于需要减少网络传输数据量的场景。框架允许用户提供自定义的游戏状态压缩和解压缩方法,以降低网络传输负载。 45 | 46 | ## 使用方法 47 | 48 | ### SnapshotInterpolationStrategy 49 | 50 | 1. 创建一个 SnapshotInterpolationStrategy 实例: 51 | 52 | ```ts 53 | const syncStrategy = new gs.SnapshotInterpolationStrategy(); 54 | ``` 55 | 56 | 2. 为实例提供自定义插值逻辑: 57 | 58 | ```ts 59 | syncStrategy.onInterpolation = (prevSnapshot, nextSnapshot, progress) => { 60 | // 用户实现自定义实体插值逻辑 61 | // 例如:根据实体的类型更新位置、旋转、缩放等属性 62 | }; 63 | ``` 64 | 65 | ### StateCompressionStrategy 66 | 67 | 1. 创建一个 StateCompressionStrategy 实例: 68 | 69 | ```ts 70 | const syncStrategy = new gs.StateCompressionStrategy(); 71 | ``` 72 | 73 | 2. 为实例提供自定义压缩逻辑: 74 | 75 | ```ts 76 | syncStrategy.onCompressState = (state) => { 77 | // 使用合适的压缩方法将游戏状态进行压缩,返回压缩后的状态 78 | 79 | // 例如:使用LZ-string库实现游戏状态压缩 80 | return LZString.compressToUTF16(JSON.stringify(state)); 81 | }; 82 | ``` 83 | 84 | 3. 为实例提供自定义解压缩逻辑: 85 | 86 | ```ts 87 | syncStrategy.onDecompressState = (compressedState) => { 88 | // 使用合适的解压缩方法将压缩状态恢复为原始游戏状态,返回解压缩后的状态 89 | 90 | // 例如:使用LZ-string 实现游戏状态解压缩 91 | return JSON.parse(LZString.decompressFromUTF16(compressedState)); 92 | }; 93 | ``` 94 | 95 | 4. 为实例提供发送和接收状态更新的逻辑: 96 | 97 | ```ts 98 | syncStrategy.onSendState = (compressedState) => { 99 | // 在这里执行发送压缩状态的逻辑,例如使用网络库将压缩后的游戏状态发送给服务器或其他客户端 100 | }; 101 | 102 | syncStrategy.onReceiveState = (decompressedState) => { 103 | // 在这里执行接收状态后的逻辑,例如使用解压缩后的状态更新游戏状态 104 | }; 105 | ``` 106 | 107 | 5. 如果需要,为实例提供处理状态更新时执行的自定义操作: 108 | 109 | ```ts 110 | syncStrategy.handleStateUpdate = (state) => { 111 | // 在这里执行自定义操作,例如日志记录、错误检测等 112 | }; 113 | ``` 114 | 115 | ## 注意事项 116 | 117 | 框架的目标是提供通用的接口和事件,以便根据实际游戏需求实现不同的同步策略。在使用框架时,请确保适当地适应您的游戏逻辑和数据结构。 -------------------------------------------------------------------------------- /docs/time-manager.md: -------------------------------------------------------------------------------- 1 | # TimeManager 2 | 3 | `TimeManager` 是 G-Framework 中用于管理时间的模块。它提供了一些常用的时间属性和方法,如 `deltaTime`,`timeScale` 和 `totalTime`,以便于在游戏中进行帧事件的处理。 4 | 5 | ## 使用方法 6 | 7 | 首先,获取 `TimeManager` 的实例: 8 | 9 | ```typescript 10 | const timeManager = gs.TimeManager.getInstance(); 11 | ``` 12 | 13 | 在游戏循环中更新 TimeManager: 14 | 15 | ```typescript 16 | timeManager.update(deltaTime); 17 | ``` 18 | 19 | 在 SystemManager 的 update 方法中,框架内部会自动使用 TimeManager 提供的 deltaTime。 20 | 21 | ## 属性 22 | 23 | 以下是 TimeManager 的主要属性: 24 | 25 | - `deltaTime`: 当前帧与上一帧之间的时间间隔,考虑了 timeScale。 26 | - `timeScale`: 时间缩放系数,用于控制游戏速度。设置为 1 表示正常速度,设置为 0 表示暂停游戏。 27 | - `totalTime`: 自游戏开始以来经过的总时间。 28 | 29 | ## 示例 30 | 31 | 以下是一个示例,展示如何使用 TimeManager 控制游戏速度: 32 | 33 | ```ts 34 | // 设置 timeScale 为 0.5,游戏速度变为原来的一半 35 | timeManager.timeScale = 0.5; 36 | 37 | // 设置 timeScale 为 2,游戏速度加倍 38 | timeManager.timeScale = 2; 39 | 40 | // 设置 timeScale 为 0,暂停游戏 41 | timeManager.timeScale = 0; 42 | ``` 43 | 44 | 通过使用 TimeManager,您可以轻松地在 G-Framework 中实现对时间的管理。 45 | -------------------------------------------------------------------------------- /source/.npmignore: -------------------------------------------------------------------------------- 1 | # 忽略所有文件 2 | /* 3 | 4 | # 但是不忽略 bin 目录 5 | !/bin 6 | 7 | # 也不忽略 package.json 和 README.md 8 | !/package.json 9 | !/README.md 10 | -------------------------------------------------------------------------------- /source/gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const gulp = require('gulp'); 3 | const { series, parallel } = require('gulp'); 4 | const minify = require('gulp-minify'); 5 | const inject = require('gulp-inject-string'); 6 | const ts = require('gulp-typescript'); 7 | const merge = require('merge2'); 8 | const tsProject = ts.createProject('tsconfig.json'); 9 | 10 | function buildJs() { 11 | return tsProject.src() 12 | .pipe(tsProject()) 13 | .js.pipe(inject.replace('var gs;', '')) 14 | .pipe(inject.prepend('window.gs = {};\n')) 15 | .pipe(inject.replace('var __extends =', 'window.__extends =')) 16 | .pipe(minify({ ext: { min: ".min.js" } })) 17 | .pipe(gulp.dest('./bin')); 18 | } 19 | 20 | function buildDts() { 21 | return tsProject.src() 22 | .pipe(tsProject()) 23 | // .dts.pipe(inject.append('import e = framework;')) 24 | .pipe(gulp.dest('./bin')); 25 | } 26 | 27 | exports.buildJs = buildJs; 28 | exports.buildDts = buildDts; 29 | exports.build = series(buildJs, buildDts); -------------------------------------------------------------------------------- /source/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@esengine/g-framework", 3 | "version": "1.0.5", 4 | "description": "", 5 | "main": "bin/gs.js", 6 | "types": "bin/gs.d.ts", 7 | "directories": { 8 | "lib": "lib" 9 | }, 10 | "scripts": { 11 | "deploy": "npm publish" 12 | }, 13 | "author": "yhh", 14 | "license": "MIT", 15 | "devDependencies": { 16 | "@babel/core": "^7.21.0", 17 | "babel-preset-es2015": "^6.24.1", 18 | "browserify": "^14.3.0", 19 | "gulp": "^4.0.2", 20 | "gulp-babel": "^8.0.0", 21 | "gulp-concat": "^2.6.1", 22 | "gulp-inject-string": "^1.1.2", 23 | "gulp-minify": "^3.1.0", 24 | "gulp-string-replace": "^1.1.2", 25 | "gulp-typescript": "^5.0.1", 26 | "gulp-uglify": "^3.0.2", 27 | "merge2": "^1.4.1", 28 | "tsify": "^3.0.1", 29 | "typedoc": "^0.23.26", 30 | "typescript": "^2.2.2", 31 | "vinyl-source-stream": "^1.1.0", 32 | "watchify": "^4.0.0" 33 | }, 34 | "publishConfig": { 35 | "access": "public" 36 | }, 37 | "dependencies": {} 38 | } 39 | -------------------------------------------------------------------------------- /source/src/Core.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export class Core { 3 | private _entityManager: EntityManager; 4 | private _systemManager: SystemManager; 5 | private _timeManager: TimeManager; 6 | private _plugins: IPlugin[] = []; 7 | private _performanceProfiler: PerformanceProfiler; 8 | 9 | public get entityManager() { 10 | return this._entityManager; 11 | } 12 | 13 | public get systemManager() { 14 | return this._systemManager; 15 | } 16 | 17 | private static _instance: Core; 18 | public static get instance() { 19 | if (this._instance == null) { 20 | this._instance = new Core(); 21 | this._instance.onInit(); 22 | } 23 | 24 | return this._instance; 25 | } 26 | 27 | private constructor() {} 28 | 29 | private onInit() { 30 | this._entityManager = new EntityManager(); 31 | this._systemManager = new SystemManager(this._entityManager); 32 | this._timeManager = TimeManager.getInstance(); 33 | 34 | this._performanceProfiler = PerformanceProfiler.getInstance(); 35 | return this; 36 | } 37 | 38 | public registerPlugin(plugin: IPlugin): void { 39 | this._plugins.push(plugin); 40 | plugin.onInit(this); 41 | } 42 | 43 | update(deltaTime: number) { 44 | this._performanceProfiler.startFrame(); 45 | this._timeManager.update(deltaTime); 46 | this._systemManager.update(); 47 | 48 | for (const plugin of this._plugins) { 49 | plugin.onUpdate(deltaTime); 50 | } 51 | this._performanceProfiler.endFrame(); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /source/src/Core/Component.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | /** 3 | * 组件 4 | */ 5 | export abstract class Component { 6 | private _entity: Entity; 7 | private _version: number = 0; 8 | private _entityManager: EntityManager; 9 | 10 | public dependencies: ComponentConstructor[] = []; 11 | 12 | setEntity(entity: Entity, entityManager: EntityManager) { 13 | this._entity = entity; 14 | this._entityManager = entityManager; 15 | } 16 | 17 | get entityId(): number { 18 | return this.entity.getId(); 19 | } 20 | 21 | get entity(): Entity { 22 | return this._entity; 23 | } 24 | 25 | get version(): number { 26 | return this._version; 27 | } 28 | 29 | /** 30 | * 标记组件已更新的方法 31 | * 通过增加 _version 的值来表示组件已更新 32 | */ 33 | markUpdated(version: number): void { 34 | this._version = version; 35 | } 36 | 37 | /** 38 | * 重置组件的状态并进行必要的初始化 39 | * @param entity 40 | * @param entityManager 41 | */ 42 | reinitialize(entity: Entity, entityManager: EntityManager): void { } 43 | 44 | /** 45 | * 当组件初始化的时候调用 46 | * @param args 47 | */ 48 | onInitialize(...args: any[]) { } 49 | 50 | serialize(): any { 51 | const data: any = {}; 52 | for (const key of Object.keys(this)) { 53 | const value = (this as any)[key]; 54 | if (typeof value !== 'function') { 55 | data[key] = value; 56 | } 57 | } 58 | 59 | return data; 60 | } 61 | 62 | deserialize(data: any): void { 63 | for (const key in data) { 64 | if ((this as any)[key] !== undefined) { 65 | (this as any)[key] = data[key]; 66 | } 67 | } 68 | } 69 | 70 | /** 71 | * 判断是否需要序列化的方法 72 | * @returns 默认返回 true,表示需要序列化 73 | */ 74 | shouldSerialize(): boolean { 75 | return true; 76 | } 77 | 78 | 79 | /** 80 | * 清除数据方法,用于组件池在重用时 81 | */ 82 | public reset(): void { } 83 | 84 | /** 85 | * 默认的浅复制方法 86 | * @returns 克隆的组件实例 87 | */ 88 | public cloneShallow(): Component { 89 | const clonedComponent = new (this.constructor as ComponentConstructor); 90 | for (const key in this) { 91 | if (this.hasOwnProperty(key)) { 92 | (clonedComponent as any)[key] = (this as any)[key]; 93 | } 94 | } 95 | return clonedComponent; 96 | } 97 | 98 | /** 99 | * 默认的深复制方法 100 | * 不处理循环引用 101 | * 如果需要循环引用请重写该方法 102 | * @returns 克隆的组件实例 103 | */ 104 | public cloneDeep(): Component { 105 | const clonedComponent = new (this.constructor as ComponentConstructor); 106 | for (const key in this) { 107 | if (this.hasOwnProperty(key)) { 108 | const value = (this as any)[key]; 109 | if (typeof value === 'object' && value !== null) { 110 | (clonedComponent as any)[key] = this.deepCopy(value); 111 | } else { 112 | (clonedComponent as any)[key] = value; 113 | } 114 | } 115 | } 116 | return clonedComponent; 117 | } 118 | 119 | /** 120 | * 深复制辅助函数 121 | * @param obj 122 | * @returns 123 | */ 124 | private deepCopy(obj: any): any { 125 | if (Array.isArray(obj)) { 126 | return obj.map(element => this.deepCopy(element)); 127 | } else if (typeof obj === 'object') { 128 | const newObj: any = {}; 129 | for (const key in obj) { 130 | if (obj.hasOwnProperty(key)) { 131 | newObj[key] = this.deepCopy(obj[key]); 132 | } 133 | } 134 | return newObj; 135 | } else { 136 | return obj; 137 | } 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /source/src/Core/Entity.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | /** 3 | * 实体类,用于管理实体的组件和标签。 4 | */ 5 | export class Entity { 6 | private id: number; 7 | private componentManagers: Map>; 8 | private tags: Set; 9 | private eventEmitter: EventEmitter; 10 | private entityManager: EntityManager; 11 | public componentBits: Bits; 12 | 13 | private _parent?: Entity; // 父实体 14 | private _children = new LinkedList(); // 子实体列表 15 | private _childNode?: Node; // 当前实体在父实体的子实体列表中的节点 16 | 17 | // 缓存获取的组件 18 | private componentCache: Map = new Map(); 19 | 20 | constructor(id: number, entityManager: EntityManager, componentManagers: Map Component, ComponentManager>) { 21 | this.id = id; 22 | this.componentManagers = componentManagers; 23 | this.tags = new Set(); 24 | this.eventEmitter = new EventEmitter(); 25 | this.entityManager = entityManager; 26 | this.componentBits = new Bits(); 27 | } 28 | 29 | public getId(): number { 30 | return this.id; 31 | } 32 | 33 | get parent(): Entity | undefined { 34 | return this._parent; 35 | } 36 | 37 | get children(): Entity[] { 38 | return this._children.toArray(); 39 | } 40 | 41 | public setParent(parent: Entity): void { 42 | if (this._parent) { 43 | this._parent._children.remove(this._childNode!); 44 | } 45 | 46 | this._parent = parent; 47 | this._childNode = this._parent._children.append(this); 48 | } 49 | 50 | public removeParent(): void { 51 | if (this._parent) { 52 | this._parent._children.remove(this._childNode!); 53 | } 54 | this._parent = undefined; 55 | this._childNode = undefined; 56 | } 57 | 58 | public addChild(child: Entity): void { 59 | child.setParent(this); 60 | } 61 | 62 | public removeChild(child: Entity): void { 63 | child.removeParent(); 64 | } 65 | 66 | /** 67 | * 添加组件 68 | * @param componentType 69 | * @param args 70 | * @returns 71 | */ 72 | public addComponent(componentType: ComponentConstructor, ...args: any[]): T { 73 | let manager = this.componentManagers.get(componentType); 74 | if (!manager) { 75 | manager = this.entityManager.addComponentManager(componentType); 76 | } 77 | 78 | const component = manager.create(this, this.entityManager) as T; 79 | component.onInitialize(...args); 80 | 81 | const componentInfo = ComponentTypeManager.getIndexFor(componentType); 82 | for (const index of componentInfo.allAncestors) { 83 | this.componentBits.set(index); 84 | } 85 | 86 | if (this.entityManager.systemManager) { 87 | this.entityManager.systemManager.notifyComponentAdded(this, component); 88 | } 89 | 90 | return component; 91 | } 92 | 93 | /** 94 | * 获取组件 95 | * @param componentType 96 | * @returns 97 | */ 98 | public getComponent(componentType: ComponentConstructor): T | null { 99 | // 获取组件类型信息 100 | const componentInfo = ComponentTypeManager.getIndexFor(componentType); 101 | 102 | // 首先尝试直接从缓存中获取 103 | for (const index of componentInfo.allAncestors) { 104 | const cachedComponentType = ComponentTypeManager.getComponentTypeFor(index); 105 | const cachedComponent = this.componentCache.get(cachedComponentType); 106 | if (cachedComponent) { 107 | return cachedComponent as T; 108 | } 109 | } 110 | 111 | // 如果缓存中没有找到,那么从组件管理器中获取 112 | for (const manager of this.componentManagers.values()) { 113 | const component = manager.get(this.id); 114 | if (component && (component instanceof componentType)) { 115 | this.componentCache.set(componentType, component); 116 | return component as T; 117 | } 118 | } 119 | 120 | return null; 121 | } 122 | 123 | /** 124 | * 获取所有组件 125 | * @returns 126 | */ 127 | public getAllComponents(): Component[] { 128 | const components: Component[] = []; 129 | for (const component of this) { 130 | components.push(component); 131 | } 132 | return components; 133 | } 134 | 135 | /** 136 | * 移除组件 137 | * @param componentType 138 | * @returns 139 | */ 140 | public removeComponent(componentType: ComponentConstructor): void { 141 | const manager = this.componentManagers.get(componentType); 142 | if (!manager) { 143 | return; 144 | } 145 | 146 | const component = this.getComponent(componentType); 147 | if (component) { 148 | if (this.entityManager.systemManager) { 149 | this.entityManager.systemManager.notifyComponentRemoved(this, component); 150 | } 151 | manager.remove(this.id); 152 | } 153 | 154 | const componentInfo = ComponentTypeManager.getIndexFor(componentType); 155 | for (const index of componentInfo.allAncestors) { 156 | this.componentBits.clear(index); 157 | } 158 | 159 | // 移除组件缓存 160 | this.componentCache.delete(componentType); 161 | } 162 | 163 | /** 164 | * 是否有组件 165 | * @param componentType 166 | * @returns 167 | */ 168 | public hasComponent(componentType: new (entityId: number) => T): boolean { 169 | const manager = this.componentManagers.get(componentType); 170 | return manager ? manager.has(this.id) : false; 171 | } 172 | 173 | /** 174 | * 清除组件缓存 175 | */ 176 | public clearComponentCache(): void { 177 | this.componentCache.clear(); 178 | } 179 | 180 | /** 181 | * 添加标签 182 | * @param tag 183 | */ 184 | addTag(tag: string): void { 185 | this.tags.add(tag); 186 | this.entityManager.addToTagCache(tag, this); 187 | } 188 | 189 | /** 190 | * 获取标签 191 | * @returns 192 | */ 193 | getTags(): Set { 194 | return new Set(Array.from(this.tags)); 195 | } 196 | 197 | /** 198 | * 移除标签 199 | * @param tag 200 | */ 201 | removeTag(tag: string): void { 202 | this.tags.delete(tag); 203 | this.entityManager.removeFromTagCache(tag, this); 204 | } 205 | 206 | /** 207 | * 检查是否具有指定标签 208 | * @param tag 209 | * @returns 210 | */ 211 | hasTag(tag: string): boolean { 212 | return this.tags.has(tag); 213 | } 214 | 215 | /** 216 | * 序列化 217 | * @returns 218 | */ 219 | serialize(): any { 220 | const serializedEntity: any = { 221 | id: this.id, 222 | components: {}, 223 | }; 224 | 225 | for (const [componentType, manager] of this.componentManagers) { 226 | const component = manager.get(this.id) as Component; 227 | if (component && component.shouldSerialize()) { 228 | serializedEntity.components[componentType.name] = component.serialize(); 229 | } 230 | } 231 | 232 | return serializedEntity; 233 | } 234 | 235 | /** 236 | * 增量序列化 237 | * @param lastSnapshotVersion 上一次快照版本 238 | * @returns 返回增量序列化后的实体对象,如果没有更新的组件,则返回null 239 | */ 240 | serializeIncremental(lastSnapshotVersion: number): any | null { 241 | let hasUpdatedComponents = false; 242 | const serializedEntity = { 243 | id: this.id, 244 | components: {}, 245 | }; 246 | 247 | for (const [componentType, manager] of this.componentManagers) { 248 | const component = manager.get(this.id) as Component; 249 | if (component && component.shouldSerialize() && component.version > lastSnapshotVersion) { 250 | serializedEntity.components[componentType.name] = component.serialize(); 251 | hasUpdatedComponents = true; 252 | } 253 | } 254 | 255 | return hasUpdatedComponents ? serializedEntity : null; 256 | } 257 | 258 | 259 | /** 260 | * 反序列化 261 | * @param data 262 | */ 263 | deserialize(data: any): void { 264 | for (const componentName in data.components) { 265 | for (const [componentType, manager] of this.componentManagers) { 266 | if (componentType.name === componentName) { 267 | const component = manager.get(this.id) as Component; 268 | if (component) { 269 | component.deserialize(data.components[componentName]); 270 | } 271 | break; 272 | } 273 | } 274 | } 275 | } 276 | 277 | /** 278 | * 实体创建时的逻辑 279 | */ 280 | onCreate() { 281 | } 282 | 283 | /** 284 | * 实体销毁时的逻辑 285 | */ 286 | onDestroy() { 287 | } 288 | 289 | [Symbol.iterator](): Iterator { 290 | const managers = Array.from(this.componentManagers.values()); 291 | let index = 0; 292 | 293 | return { 294 | next: (): IteratorResult => { 295 | if (index < managers.length) { 296 | const component = managers[index++].get(this.id); 297 | if (component) { 298 | return { done: false, value: component }; 299 | } 300 | } 301 | 302 | return { done: true, value: null }; 303 | } 304 | }; 305 | } 306 | 307 | public on(eventType: string, listener: EventListener): void { 308 | this.eventEmitter.on(eventType, listener); 309 | } 310 | 311 | public once(eventType: string, callback: (event: GEvent) => void): void { 312 | this.eventEmitter.once(eventType, callback); 313 | } 314 | 315 | public off(eventType: string, listener: EventListener): void { 316 | this.eventEmitter.off(eventType, listener); 317 | } 318 | 319 | public emit(event: GEvent): void { 320 | this.eventEmitter.emitEvent(event); 321 | } 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /source/src/Core/Interpolatable.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export interface Interpolatable { 3 | savePreviousState(): void; 4 | applyInterpolation(factor: number): void; 5 | } 6 | } -------------------------------------------------------------------------------- /source/src/Core/System.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | /** 3 | * ECS 框架中的系统接口,定义了系统需要实现的方法。 4 | */ 5 | export interface ISystem { 6 | update(entities: Entity[]): void; 7 | filterEntities(entities: Entity[]): Entity[]; 8 | onRegister(): void; 9 | onUnregister(): void; 10 | } 11 | 12 | /** 13 | * 系统基类 14 | */ 15 | export abstract class System implements ISystem { 16 | protected entityManager: EntityManager; 17 | protected matcher: Matcher; 18 | 19 | protected lastUpdateTime: number; 20 | protected updateInterval: number; 21 | 22 | /** 23 | * 系统优先级,优先级越高,越先执行 24 | */ 25 | public readonly priority: number; 26 | /** 27 | * 系统所在的worker脚本 28 | */ 29 | public readonly workerScript?: string; 30 | 31 | constructor(entityManager: EntityManager, priority: number, matcher?: Matcher, workerScript?: string, updateInterval?: number) { 32 | this.entityManager = entityManager; 33 | this.priority = priority; 34 | this.workerScript = workerScript; 35 | this.matcher = matcher || Matcher.empty(); 36 | 37 | this.updateInterval = updateInterval || 0; // 默认为0,即每次都更新 38 | this.lastUpdateTime = 0; 39 | } 40 | 41 | protected _paused: boolean = false; 42 | protected _enabled: boolean = true; 43 | 44 | get paused(): boolean { 45 | return this._paused; 46 | } 47 | 48 | set paused(value: boolean) { 49 | this._paused = value; 50 | } 51 | 52 | get enabled(): boolean { 53 | return this._enabled; 54 | } 55 | 56 | set enabled(value: boolean) { 57 | this._enabled = value; 58 | } 59 | 60 | public isPaused(): boolean { 61 | return this.paused; 62 | } 63 | 64 | public isEnabled(): boolean { 65 | return this.enabled; 66 | } 67 | 68 | /** 69 | * 更新系统 70 | * @param entities 71 | */ 72 | performUpdate(entities: Entity[]): void { 73 | const now = Date.now(); 74 | if (now - this.lastUpdateTime >= this.updateInterval) { 75 | this.update(entities); 76 | this.lastUpdateTime = now; 77 | } 78 | } 79 | 80 | /** 81 | * 更新系统 82 | * @param entities 83 | */ 84 | abstract update(entities: Entity[]): void; 85 | 86 | /** 87 | * 筛选实体 88 | * @param entity 89 | */ 90 | entityFilter(entity: Entity): boolean { 91 | return true; 92 | } 93 | 94 | public filterEntities(entities: Entity[]): Entity[] { 95 | return entities.filter(entity => this.matcher.isInterestedEntity(entity) && this.entityFilter(entity)); 96 | } 97 | 98 | public handleComponentChange(entity: Entity, component: Component, added: boolean): void { 99 | if (this.entityFilter(entity)) { 100 | if (added) { 101 | this.onComponentAdded(entity, component); 102 | } else { 103 | this.onComponentRemoved(entity, component); 104 | } 105 | } 106 | } 107 | 108 | protected onComponentAdded(entity: Entity, component: Component): void { } 109 | 110 | protected onComponentRemoved(entity: Entity, component: Component): void { } 111 | 112 | /** 113 | * 系统注册时的逻辑 114 | */ 115 | onRegister() { } 116 | 117 | /** 118 | * 系统注销时的逻辑 119 | */ 120 | onUnregister() { } 121 | } 122 | } -------------------------------------------------------------------------------- /source/src/Debug/Debug.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export class Debug { 3 | static isEnabled: boolean = false; 4 | 5 | static enable() { 6 | this.isEnabled = true; 7 | } 8 | 9 | static disable() { 10 | this.isEnabled = false; 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /source/src/Debug/PerformanceProfiler.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export class PerformanceProfiler { 3 | private static instance: PerformanceProfiler; 4 | private performanceData: Record; 5 | private frameCount: number; 6 | private totalTime: number; 7 | private maxFrameTime: number; 8 | private minFrameTime: number; 9 | 10 | private constructor() { 11 | this.performanceData = {}; 12 | this.frameCount = 0; 13 | this.totalTime = 0; 14 | this.maxFrameTime = 0; 15 | this.minFrameTime = Infinity; 16 | } 17 | 18 | static getInstance(): PerformanceProfiler { 19 | if (!PerformanceProfiler.instance) { 20 | PerformanceProfiler.instance = new PerformanceProfiler(); 21 | } 22 | return PerformanceProfiler.instance; 23 | } 24 | 25 | startFrame() { 26 | if (Debug.isEnabled) { 27 | this.performanceData['frameStart'] = performance.now(); 28 | } 29 | } 30 | 31 | endFrame() { 32 | if (Debug.isEnabled) { 33 | const frameStart = this.performanceData['frameStart']; 34 | if (frameStart) { 35 | const frameTime = performance.now() - frameStart; 36 | this.totalTime += frameTime; 37 | this.frameCount++; 38 | this.maxFrameTime = Math.max(this.maxFrameTime, frameTime); 39 | this.minFrameTime = Math.min(this.minFrameTime, frameTime); 40 | console.log(`帧时间: ${frameTime}ms`); 41 | } 42 | } 43 | } 44 | 45 | reportPerformance() { 46 | if (Debug.isEnabled) { 47 | const averageFrameTime = this.totalTime / this.frameCount; 48 | const averageFrameRate = 1000 / averageFrameTime; 49 | console.log(`平均帧时间: ${averageFrameTime}ms 在 ${this.frameCount} 帧`); 50 | console.log(`平均帧率: ${averageFrameRate} FPS`); 51 | console.log(`最大帧时间: ${this.maxFrameTime}ms`); 52 | console.log(`最小帧时间: ${this.minFrameTime}ms`); 53 | } 54 | } 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /source/src/Event/EventEmitter.ts: -------------------------------------------------------------------------------- 1 | /// 2 | module gs { 3 | export class EventEmitter { 4 | private listeners: Map; 5 | private eventPool: EventPool; 6 | 7 | constructor() { 8 | this.listeners = new Map(); 9 | this.eventPool = new EventPool(); 10 | } 11 | 12 | /** 13 | * 用于订阅特定事件类型的侦听器。当事件类型不存在时,将创建一个新的侦听器数组 14 | * @param eventType 15 | * @param listener 16 | */ 17 | on(eventType: string, listener: EventListener): void { 18 | if (!this.listeners.has(eventType)) { 19 | this.listeners.set(eventType, []); 20 | } 21 | 22 | const eventListeners = this.listeners.get(eventType) as EventListener[]; 23 | if (eventListeners) 24 | eventListeners.push(listener); 25 | } 26 | 27 | /** 28 | * 用于订阅特定事件类型的侦听器。当事件类型不存在时,将创建一个新的侦听器数组。该方法只会在回调函数被执行后,移除监听器 29 | * @param eventType 30 | * @param callback 31 | */ 32 | once(eventType: string, callback: (event: GEvent) => void): void { 33 | const wrappedCallback = (event: GEvent) => { 34 | // 在回调函数被执行后,移除监听器 35 | this.off(eventType, wrappedCallback); 36 | callback(event); 37 | }; 38 | this.on(eventType, wrappedCallback); 39 | } 40 | 41 | /** 42 | * 用于取消订阅特定事件类型的侦听器。如果找到侦听器,则将其从数组中移除 43 | * @param eventType 44 | * @param listener 45 | */ 46 | off(eventType: string, listener: EventListener): void { 47 | const eventListeners = this.listeners.get(eventType); 48 | if (eventListeners) { 49 | const index = eventListeners.indexOf(listener); 50 | if (index > -1) { 51 | eventListeners.splice(index, 1); 52 | } 53 | } 54 | } 55 | 56 | /** 57 | * 用于触发事件。该方法将遍历所有订阅给定事件类型的侦听器,并调用它们 58 | * @param event 59 | */ 60 | emitEvent(event: GEvent): void { 61 | const eventType = event.getType(); 62 | const listeners = this.listeners.get(eventType); 63 | 64 | if (listeners) { 65 | for (const listener of listeners) { 66 | listener(event); 67 | } 68 | } 69 | 70 | this.eventPool.release(event); 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /source/src/Event/EventListener.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export interface EventListener { 3 | (event: GEvent): void; 4 | } 5 | } -------------------------------------------------------------------------------- /source/src/Event/GEvent.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export class GEvent { 3 | type: string; 4 | data: any; 5 | 6 | constructor(type: string, data?: any) { 7 | this.type = type; 8 | this.data = data; 9 | } 10 | 11 | reset(): void { 12 | this.type = ""; 13 | this.data = null; 14 | } 15 | 16 | getType(): string { 17 | return this.type; 18 | } 19 | 20 | getData(): any { 21 | return this.data; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /source/src/GlobalEventEmitter.ts: -------------------------------------------------------------------------------- 1 | /// 2 | module gs { 3 | export const GlobalEventEmitter = new EventEmitter(); 4 | } -------------------------------------------------------------------------------- /source/src/Input/InputAdapter.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export abstract class InputAdapter { 3 | protected inputManager: InputManager; 4 | 5 | constructor(inputManager: InputManager) { 6 | this.inputManager = inputManager; 7 | } 8 | 9 | /** 10 | * 需要实现此方法以适应使用的游戏引擎 11 | * @param event 12 | */ 13 | abstract handleInputEvent(event: any): void; 14 | 15 | protected sendInputToManager(inputEvent: InputEvent): void { 16 | this.inputManager.sendInput(inputEvent); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /source/src/Input/InputBuffer.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | /** 3 | * 输入缓冲区 4 | */ 5 | export class InputBuffer { 6 | private buffer: InputEvent[]; 7 | 8 | constructor() { 9 | this.buffer = []; 10 | } 11 | 12 | addEvent(event: InputEvent): void { 13 | this.buffer.push(event); 14 | } 15 | 16 | hasEvents(): boolean { 17 | return this.buffer.length > 0; 18 | } 19 | 20 | getEvents(): InputEvent[] { 21 | return this.buffer; 22 | } 23 | 24 | consumeEvent(): InputEvent | null { 25 | if (this.buffer.length === 0) { 26 | return null; 27 | } 28 | 29 | return this.buffer.shift(); 30 | } 31 | 32 | clear(): void { 33 | this.buffer = []; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /source/src/Input/InputEvent.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export interface InputEvent { 3 | type: string; 4 | data: any; 5 | } 6 | } -------------------------------------------------------------------------------- /source/src/Input/InputManager.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export class InputManager { 3 | private entityManager: EntityManager; 4 | private adapter?: InputAdapter; 5 | private inputBuffer: InputBuffer; 6 | /** 输入历史记录队列 */ 7 | private inputHistory: Array<{ frameNumber: number, input: InputEvent }> = []; 8 | private historySizeThreshold = 1000; 9 | 10 | private eventListeners: Array<(event: InputEvent) => void> = []; 11 | 12 | constructor(entityManager: EntityManager) { 13 | this.entityManager = entityManager; 14 | this.inputBuffer = new InputBuffer(); 15 | } 16 | 17 | public setHistorySizeThreshold(threshold: number): void { 18 | this.historySizeThreshold = threshold; 19 | } 20 | 21 | public addEventListener(callback: (event: InputEvent) => void): void { 22 | this.eventListeners.push(callback); 23 | } 24 | 25 | public setAdapter(adapter: InputAdapter): void { 26 | this.adapter = adapter; 27 | } 28 | 29 | public sendInput(event: InputEvent): void { 30 | this.handleInput(event); 31 | } 32 | 33 | private handleInput(event: InputEvent) { 34 | this.inputBuffer.addEvent(event); 35 | // 将输入和当前帧编号存储在输入历史记录中 36 | this.inputHistory.push({ frameNumber: this.getCurrentFrameNumber(), input: event }); 37 | 38 | // 触发输入事件监听器 39 | this.eventListeners.forEach(listener => listener(event)); 40 | 41 | // 当输入历史记录数量超过阈值时,删除最旧的事件 42 | if (this.inputHistory.length > this.historySizeThreshold) { 43 | this.inputHistory.splice(0, this.inputHistory.length - this.historySizeThreshold); 44 | } 45 | } 46 | 47 | /** 48 | * 获取当前帧编号的方法 49 | * @returns 50 | */ 51 | private getCurrentFrameNumber(): number { 52 | return this.entityManager.getCurrentFrameNumber(); 53 | } 54 | 55 | public getInputBuffer(): InputBuffer { 56 | return this.inputBuffer; 57 | } 58 | 59 | public getInputHistory(): Array<{ frameNumber: number, input: InputEvent }> { 60 | return this.inputHistory; 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /source/src/Manager/ComponentManager.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export type ComponentConstructor = new (...args: any[]) => T; 3 | 4 | /** 5 | * 组件管理器 6 | */ 7 | export class ComponentManager { 8 | private components: SparseSet; 9 | private componentPool: T[] = []; 10 | public componentType: ComponentConstructor; 11 | 12 | /** 13 | * ComponentManager 构造函数 14 | * @param componentType - 用于创建和管理的组件类型。 15 | * 16 | * 用法示例: 17 | * const positionManager = new ComponentManager(PositionComponent); 18 | */ 19 | constructor(componentType: ComponentConstructor) { 20 | this.componentType = componentType; 21 | this.components = new SparseSet(); 22 | this.preallocate(10); // 预先创建10个组件实例 23 | } 24 | 25 | public create(entity: Entity, entityManager: EntityManager): T { 26 | let component: T; 27 | 28 | if (this.componentPool.length > 0) { 29 | component = this.componentPool.pop(); 30 | component.reinitialize(entity, entityManager); // 重置组件状态并进行初始化 31 | } else { 32 | component = new this.componentType(); 33 | } 34 | component.setEntity(entity, entityManager); 35 | 36 | const entityId = entity.getId(); 37 | // 检查组件依赖 38 | for (const dependency of component.dependencies) { 39 | if (!entityManager.hasComponent(entityId, dependency)) { 40 | throw new Error(`组件 ${component.constructor.name} 依赖于 ${dependency.name},但实体 ${entityId} 缺少该组件。`); 41 | } 42 | } 43 | 44 | this.components.add(entityId, component); 45 | return component; 46 | } 47 | 48 | /** 49 | * 获取组件数据 50 | * @param entityId 实体ID 51 | * @returns 组件数据 52 | */ 53 | public get(entityId: number): T | null { 54 | return this.components.get(entityId); 55 | } 56 | 57 | /** 58 | * 59 | * @param entityId 60 | * @returns 61 | */ 62 | public has(entityId: number): boolean { 63 | return this.components.has(entityId); 64 | } 65 | 66 | /** 67 | * 68 | * @param entityId 69 | * @returns 70 | */ 71 | public remove(entityId: number): void { 72 | const component = this.components.get(entityId); 73 | if (component) { 74 | component.reset(); 75 | this.componentPool.push(component); // 将组件回收到组件池中 76 | } 77 | this.components.remove(entityId); 78 | } 79 | 80 | /** 81 | * 预先创建指定数量的组件实例,并将它们放入对象池 82 | * @param count 要预先创建的组件数量 83 | */ 84 | public preallocate(count: number, resetComponents: boolean = true): void { 85 | for (let i = 0; i < count; i++) { 86 | const component = new this.componentType(); 87 | if (resetComponents) { 88 | component.reset(); 89 | } 90 | this.componentPool.push(component); 91 | } 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /source/src/Manager/ComponentTypeInfo.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export class ComponentTypeInfo { 3 | public readonly index: number; 4 | public readonly type: new (...args: any[]) => Component; 5 | public readonly parents: ComponentTypeInfo[]; 6 | public readonly allAncestors: number[]; 7 | 8 | constructor(index: number, type: new (...args: any[]) => Component) { 9 | this.index = index; 10 | this.type = type; 11 | this.parents = []; 12 | this.allAncestors = [index]; 13 | 14 | let parent = Object.getPrototypeOf(type.prototype); 15 | while (parent && parent !== Object.prototype) { 16 | const parentInfo = ComponentTypeManager.getIndexFor(parent.constructor as new (...args: any[]) => Component); 17 | this.parents.push(parentInfo); 18 | this.allAncestors.push(...parentInfo.allAncestors); 19 | parent = Object.getPrototypeOf(parent); 20 | } 21 | 22 | this.allAncestors = [...new Set(this.allAncestors)]; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /source/src/Manager/ComponentTypeManager.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export class ComponentTypeManager { 3 | private static componentTypes: Map = new Map(); 4 | private static indexToComponentTypes: Map = new Map(); 5 | private static nextIndex: number = 0; 6 | 7 | public static getIndexFor(componentType: new (...args: any[]) => Component): ComponentTypeInfo { 8 | let info = this.componentTypes.get(componentType); 9 | if (info === undefined) { 10 | info = new ComponentTypeInfo(this.nextIndex++, componentType); 11 | this.componentTypes.set(componentType, info); 12 | this.indexToComponentTypes.set(info.index, componentType); 13 | } 14 | return info; 15 | } 16 | 17 | public static getComponentTypeFor(index: number): Function | undefined { 18 | return this.indexToComponentTypes.get(index); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /source/src/Manager/EntityManager.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export interface StateSnapshot { 3 | entities: any[]; 4 | } 5 | 6 | export class EntityManager { 7 | private entities: Map; 8 | private entityIdAllocator: EntityIdAllocator; 9 | public componentManagers: Map, ComponentManager>; 10 | /** 当前帧编号属性 */ 11 | private currentFrameNumber: number; 12 | private inputManager: InputManager; 13 | private networkManager: NetworkManager; 14 | // 查询缓存,用于缓存组件查询结果 15 | private queryCache: Map = new Map(); 16 | private tagToEntities: Map = new Map(); 17 | private prefabs: Map = new Map(); 18 | 19 | public systemManager?: SystemManager; 20 | 21 | constructor(systemManager?: SystemManager) { 22 | this.entities = new Map(); 23 | this.entityIdAllocator = new EntityIdAllocator(); 24 | this.inputManager = new InputManager(this); 25 | this.networkManager = new NetworkManager(); 26 | this.currentFrameNumber = 0; 27 | this.systemManager = systemManager; 28 | 29 | this.componentManagers = new Map(); 30 | } 31 | 32 | public setSystemManager(systemManager: SystemManager): void { 33 | this.systemManager = systemManager; 34 | } 35 | 36 | /** 37 | * 添加组件管理器 38 | * @param componentClass 要添加的组件类 39 | */ 40 | public addComponentManager(componentClass: ComponentConstructor): ComponentManager { 41 | if (!this.componentManagers.has(componentClass)) { 42 | const componentManager = new ComponentManager(componentClass); 43 | this.componentManagers.set(componentClass, componentManager); 44 | 45 | return componentManager; 46 | } 47 | 48 | return this.componentManagers.get(componentClass); 49 | } 50 | 51 | public updateFrameNumber(): void { 52 | this.currentFrameNumber++; 53 | } 54 | 55 | public getCurrentFrameNumber(): number { 56 | return this.currentFrameNumber; 57 | } 58 | 59 | public getInputManager(): InputManager { 60 | return this.inputManager; 61 | } 62 | 63 | public getNetworkManager(): NetworkManager { 64 | return this.networkManager; 65 | } 66 | 67 | /** 68 | * 创建实体 69 | * @returns customEntityClass 可选的自定义实体类 70 | */ 71 | public createEntity(customEntityClass: new (entityId: number, 72 | entityManager: EntityManager, 73 | componentManagers: Map, ComponentManager>) => Entity = Entity): Entity { 74 | const entityId = this.entityIdAllocator.allocate(); 75 | const entity = new customEntityClass(entityId, this, this.componentManagers); 76 | entity.onCreate(); 77 | this.entities.set(entityId, entity); 78 | 79 | for (const tag of entity.getTags()) { 80 | this.addToTagCache(tag, entity); 81 | } 82 | 83 | return entity; 84 | } 85 | 86 | /** 87 | * 删除实体 88 | * @param entityId 89 | */ 90 | public deleteEntity(entityId: number): void { 91 | const entity = this.getEntity(entityId); 92 | if (entity) { 93 | entity.onDestroy(); 94 | this.entities.delete(entityId); 95 | 96 | for (const tag of entity.getTags()) { 97 | this.removeFromTagCache(tag, entity); 98 | } 99 | } 100 | } 101 | 102 | /** 103 | * 从预制件创建实体 104 | * @param name 105 | * @param deepCopy 106 | * @returns 107 | */ 108 | public createEntityFromPrefab(name: string, deepCopy: boolean = false): Entity | null { 109 | const prefab = this.prefabs.get(name); 110 | if (!prefab) { 111 | console.warn(`找不到名称为 "${name}" 的预制件`); 112 | return null; 113 | } 114 | return this.cloneEntity(prefab, deepCopy); 115 | } 116 | 117 | /** 118 | * 注册预制件 119 | * @param name 120 | * @param entity 121 | */ 122 | public registerPrefab(name: string, entity: Entity): void { 123 | if (this.prefabs.has(name)) { 124 | console.warn(`名称为 "${name}" 的预制件已存在。正在覆盖...`); 125 | } 126 | this.prefabs.set(name, entity); 127 | } 128 | 129 | /** 130 | * 注销预制件 131 | * @param name 132 | */ 133 | public unregisterPrefab(name: string): void { 134 | if (this.prefabs.has(name)) { 135 | this.prefabs.delete(name); 136 | } else { 137 | console.warn(`名称为 "${name}" 的预制件不存在`); 138 | } 139 | } 140 | 141 | /** 142 | * 获取实体 143 | * @param entityId 实体id 144 | * @returns 实体 145 | */ 146 | public getEntity(entityId: number): Entity | null { 147 | return this.entities.has(entityId) ? this.entities.get(entityId) as Entity : null; 148 | } 149 | 150 | /** 151 | * 获取具有特定组件的所有实体 152 | * @param componentClass 要检查的组件类 153 | * @returns 具有指定组件的实体数组 154 | */ 155 | public getEntitiesWithComponent(componentClass: ComponentConstructor): Entity[] { 156 | return this.queryComponents([componentClass]); 157 | } 158 | 159 | /** 160 | * 查找具有指定组件的实体 161 | * @param componentClasses 162 | * @returns 163 | */ 164 | public getEntitiesWithComponents(componentClasses: ComponentConstructor[]): Entity[] { 165 | return this.queryComponents(componentClasses); 166 | } 167 | 168 | /** 169 | * 获取所有实体 170 | * @returns 171 | */ 172 | public getEntities(): Entity[] { 173 | return Array.from(this.entities.values()); 174 | } 175 | 176 | /** 177 | * 获取具有特定标签的所有实体 178 | * @param tag 要检查的标签 179 | * @returns 具有指定标签的实体数组 180 | */ 181 | public getEntitiesWithTag(tag: string): Entity[] { 182 | return this.tagToEntities.get(tag) || []; 183 | } 184 | 185 | /** 186 | * 检查实体是否具有指定类型的组件 187 | * @param entityId 要检查的实体的ID 188 | * @param componentClass 要检查的组件类型 189 | * @returns 如果实体具有指定类型的组件,则返回 true,否则返回 false 190 | */ 191 | public hasComponent(entityId: number, componentClass: ComponentConstructor): boolean { 192 | const componentManager = this.componentManagers.get(componentClass); 193 | if (componentManager) { 194 | return componentManager.has(entityId); 195 | } 196 | return false; 197 | } 198 | 199 | /** 200 | * 根据提供的组件数组查询实体 201 | * @param components 要查询的组件数组 202 | * @returns 符合查询条件的实体数组 203 | */ 204 | public queryComponents(components: ComponentConstructor[]): Entity[] { 205 | const key = `${components.map(c => c.name).sort().join('|')}`; 206 | if (!this.queryCache.has(key)) { 207 | const result = this.performQuery(components); 208 | this.queryCache.set(key, result); 209 | } 210 | return this.queryCache.get(key); 211 | } 212 | 213 | private performQuery(components: ComponentConstructor[]): Entity[] { 214 | const result: Entity[] = []; 215 | 216 | // 遍历所有实体 217 | for (const entity of this.getEntities()) { 218 | // 检查每个查询的组件是否存在于实体中 219 | const hasAllComponents = components.every(componentType => { 220 | return entity.hasComponent(componentType); 221 | }); 222 | 223 | // 如果所有组件存在,则将实体添加到结果中 224 | if (hasAllComponents) { 225 | result.push(entity); 226 | } 227 | } 228 | 229 | return result; 230 | } 231 | 232 | /** 233 | * 创建当前游戏状态的快照 234 | * @returns 235 | */ 236 | public createStateSnapshot(): StateSnapshot { 237 | const snapshot: StateSnapshot = { 238 | entities: [], 239 | }; 240 | 241 | for (const entity of this.getEntities()) { 242 | snapshot.entities.push(entity.serialize()); 243 | } 244 | 245 | return snapshot; 246 | } 247 | 248 | /** 249 | * 创建增量状态快照 250 | * @param lastSnapshotVersion 上一个快照的版本号 251 | * @returns 返回一个包含实体增量数据的快照对象 252 | */ 253 | public createIncrementalStateSnapshot(lastSnapshotVersion: number): any { 254 | const snapshot: StateSnapshot = { 255 | entities: [], 256 | }; 257 | 258 | for (const entity of this.getEntities()) { 259 | const serializedEntity = entity.serializeIncremental(lastSnapshotVersion); 260 | if (serializedEntity) { 261 | snapshot.entities.push(serializedEntity); 262 | } 263 | } 264 | 265 | return snapshot; 266 | } 267 | 268 | /** 269 | * 使用给定的增量状态快照更新游戏状态 270 | * @param incrementalSnapshot 增量状态快照 271 | */ 272 | public applyIncrementalSnapshot(incrementalSnapshot: any): void { 273 | for (const entityData of incrementalSnapshot.entities) { 274 | const entityId = entityData.id; 275 | let entity = this.getEntity(entityId); 276 | 277 | if (!entity) { 278 | // 如果实体不存在,可能需要创建一个新实体 279 | entity = new Entity(entityId, this, this.componentManagers); 280 | entity.onCreate(); 281 | this.entities.set(entityId, entity); 282 | 283 | for (const tag of entity.getTags()) { 284 | this.addToTagCache(tag, entity); 285 | } 286 | } 287 | 288 | // 更新实体状态使用增量数据 289 | entity.deserialize(entityData); 290 | } 291 | } 292 | 293 | 294 | /** 295 | * 使用给定的状态快照更新游戏状态 296 | * @param stateSnapshot 297 | */ 298 | public updateStateFromSnapshot(stateSnapshot: any): void { 299 | const newEntityMap: Map = new Map(); 300 | 301 | for (const entityData of stateSnapshot.entities) { 302 | const entityId = entityData.id; 303 | let entity = this.getEntity(entityId); 304 | 305 | if (!entity) { 306 | entity = new Entity(entityId, this, this.componentManagers); 307 | entity.onCreate(); 308 | this.entities.set(entityId, entity); 309 | 310 | for (const tag of entity.getTags()) { 311 | this.addToTagCache(tag, entity); 312 | } 313 | } 314 | 315 | entity.deserialize(entityData); 316 | newEntityMap.set(entityId, entity); 317 | } 318 | 319 | this.entities = newEntityMap; 320 | } 321 | 322 | /** 323 | * 应用插值 324 | * @param factor 325 | */ 326 | public applyInterpolation(factor: number) { 327 | for (const entity of this.getEntities()) { 328 | for (const [componentType, manager] of this.componentManagers) { 329 | const component = entity.getComponent(componentType); 330 | if (component instanceof Component && 'savePreviousState' in component && 'applyInterpolation' in component) { 331 | (component as Interpolatable).applyInterpolation(factor); 332 | } 333 | } 334 | } 335 | } 336 | 337 | /** 338 | * 克隆实体并返回新创建的实体 339 | * @param entity 要克隆的实体 340 | * @param deepCopy 是否使用深拷贝 341 | * @returns 新创建的实体 342 | */ 343 | public cloneEntity(entity: Entity, deepCopy: boolean = false): Entity { 344 | const newEntity = this.createEntity(entity.constructor as typeof Entity); 345 | 346 | // 遍历组件管理器并为新实体添加已克隆的组件 347 | for (const [componentType, manager] of this.componentManagers) { 348 | const component = entity.getComponent(componentType); 349 | if (component) { 350 | const clonedComponent = deepCopy ? component.cloneDeep() : component.cloneShallow(); 351 | newEntity.addComponent(componentType, clonedComponent); 352 | } 353 | } 354 | 355 | // 添加实体标签 356 | for (const tag of entity.getTags()) { 357 | newEntity.addTag(tag); 358 | } 359 | 360 | return newEntity; 361 | } 362 | 363 | /** 364 | * 将实体添加到指定标签的缓存 365 | * @param tag 366 | * @param entity 367 | */ 368 | public addToTagCache(tag: string, entity: Entity): void { 369 | if (!this.tagToEntities.has(tag)) { 370 | this.tagToEntities.set(tag, []); 371 | } 372 | this.tagToEntities.get(tag).push(entity); 373 | } 374 | 375 | /** 376 | * 将实体从指定标签的缓存中删除 377 | * @param tag 378 | * @param entity 379 | */ 380 | public removeFromTagCache(tag: string, entity: Entity): void { 381 | const entitiesWithTag = this.tagToEntities.get(tag); 382 | if (entitiesWithTag) { 383 | const index = entitiesWithTag.indexOf(entity); 384 | if (index > -1) { 385 | entitiesWithTag.splice(index, 1); 386 | } 387 | } 388 | } 389 | 390 | toJSON() { 391 | return { }; 392 | } 393 | } 394 | } -------------------------------------------------------------------------------- /source/src/Manager/SystemManager.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | /** 3 | * ECS 框架中的系统管理器类,负责管理系统的注册、注销以及更新。 4 | */ 5 | export class SystemManager { 6 | private systems: System[]; 7 | private entityManager: EntityManager; 8 | private systemWorkers: Map = new Map(); 9 | private entityCache: Map = new Map(); 10 | private dependencies: Map = new Map(); 11 | 12 | private workerWarningDisplayed: boolean = false; 13 | 14 | constructor(entityManager: EntityManager) { 15 | this.systems = []; 16 | this.entityManager = entityManager; 17 | entityManager.setSystemManager(this); 18 | } 19 | 20 | /** 21 | * 注册系统 22 | * @param system 系统 23 | * @param dependsOn 可选的依赖系统数组 24 | */ 25 | registerSystem(system: System, dependsOn?: System[]): void { 26 | system.onRegister(); 27 | if (dependsOn) { 28 | this.dependencies.set(system, dependsOn); 29 | } 30 | 31 | this.systems.push(system); 32 | this.sortSystemsByPriorityAndDependencies(); 33 | 34 | if (system.workerScript) { 35 | if (typeof Worker === 'undefined') { 36 | if (!this.workerWarningDisplayed) { 37 | console.warn( 38 | 'Web Workers 在当前环境中不受支持。系统将在主线程中运行' 39 | ); 40 | this.workerWarningDisplayed = true; 41 | } 42 | } else { 43 | const worker = new Worker(system.workerScript); 44 | worker.onmessage = (event) => { 45 | const updatedEntities = event.data.entities; 46 | for (const updatedEntityData of updatedEntities) { 47 | const entity = this.entityManager.getEntity(updatedEntityData.id); 48 | if (entity) { 49 | entity.deserialize(updatedEntityData); 50 | } 51 | } 52 | }; 53 | this.systemWorkers.set(system, worker); 54 | } 55 | } 56 | } 57 | 58 | /** 59 | * 注销系统 60 | * @param system 61 | */ 62 | unregisterSystem(system: System): void { 63 | system.onUnregister(); 64 | const index = this.systems.indexOf(system); 65 | if (index > -1) { 66 | this.systems.splice(index, 1); 67 | } 68 | this.systemWorkers.delete(system); 69 | this.entityCache.delete(system); 70 | } 71 | 72 | /** 73 | * 通知所有系统组件已添加 74 | * @param entity 75 | * @param component 76 | */ 77 | notifyComponentAdded(entity: Entity, component: Component): void { 78 | for (const system of this.systems) { 79 | system.handleComponentChange(entity, component, true); 80 | this.entityCache.delete(system); 81 | } 82 | } 83 | 84 | /** 85 | * 通知所有系统组件已删除 86 | * @param entity 87 | * @param component 88 | */ 89 | notifyComponentRemoved(entity: Entity, component: Component): void { 90 | for (const system of this.systems) { 91 | system.handleComponentChange(entity, component, false); 92 | this.entityCache.delete(system); 93 | } 94 | } 95 | 96 | /** 97 | * 使特定系统的实体缓存无效。 98 | * @param system 要使其实体缓存无效的系统 99 | */ 100 | invalidateEntityCacheForSystem(system: System): void { 101 | this.entityCache.delete(system); 102 | } 103 | 104 | /** 105 | * 更新系统 106 | */ 107 | update(): void { 108 | const entities = this.entityManager.getEntities(); 109 | for (const system of this.systems) { 110 | if (!system.isEnabled() || system.isPaused()) { 111 | continue; 112 | } 113 | 114 | let filteredEntities = this.entityCache.get(system); 115 | if (!filteredEntities) { 116 | filteredEntities = system.filterEntities(entities); 117 | this.entityCache.set(system, filteredEntities); 118 | } 119 | 120 | const worker = this.systemWorkers.get(system); 121 | if (worker) { 122 | const message = { 123 | entities: filteredEntities.map(entity => entity.serialize()), 124 | }; 125 | worker.postMessage(message); 126 | } else { 127 | system.performUpdate(filteredEntities); 128 | } 129 | } 130 | } 131 | 132 | /** 133 | * 按优先级和依赖关系对系统进行排序 134 | */ 135 | private sortSystemsByPriorityAndDependencies(): void { 136 | this.systems.sort((a, b) => { 137 | const priorityDiff = a.priority - b.priority; 138 | if (priorityDiff !== 0) { 139 | return priorityDiff; 140 | } 141 | 142 | if (this.dependsOn(a, b)) { 143 | return 1; 144 | } 145 | if (this.dependsOn(b, a)) { 146 | return -1; 147 | } 148 | return 0; 149 | }); 150 | } 151 | 152 | /** 153 | * 确定系统 a 是否依赖于系统 b 154 | * @param a 系统 a 155 | * @param b 系统 b 156 | * @returns 如果系统 a 依赖于系统 b,则为 true,否则为 false 157 | */ 158 | private dependsOn(a: System, b: System): boolean { 159 | const dependenciesOfA = this.dependencies.get(a); 160 | if (!dependenciesOfA) { 161 | return false; 162 | } 163 | if (dependenciesOfA.indexOf(b) !== -1) { 164 | return true; 165 | } 166 | return dependenciesOfA.some(dep => this.dependsOn(dep, b)); 167 | } 168 | 169 | public dispose(): void { 170 | for (const worker of this.systemWorkers.values()) { 171 | worker.terminate(); 172 | } 173 | 174 | this.systemWorkers.clear(); 175 | this.entityCache.clear(); 176 | } 177 | } 178 | } -------------------------------------------------------------------------------- /source/src/Manager/TimeManager.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | /** 3 | * 时间管理器 4 | */ 5 | export class TimeManager { 6 | private static instance: TimeManager; 7 | /** 8 | * 上一帧到这一帧的时间间隔 9 | */ 10 | deltaTime: number; 11 | /** 12 | * 时间缩放 13 | */ 14 | timeScale: number; 15 | /** 16 | * 游戏运行的总时间 17 | */ 18 | totalTime: number; 19 | 20 | /** 21 | * 固定时间步长 22 | */ 23 | fixedDeltaTime: number; 24 | accumulatedTime: number = 0; 25 | isPaused: boolean = false; 26 | 27 | private fixedUpdateCallbacks: ((deltaTime: number) => void)[] = []; 28 | 29 | private constructor() { 30 | this.deltaTime = 0; 31 | this.timeScale = 1; 32 | this.totalTime = 0; 33 | 34 | this.fixedDeltaTime = 1 / 60; // 设定固定更新频率为60次每秒 35 | } 36 | 37 | public static getInstance(): TimeManager { 38 | if (!TimeManager.instance) { 39 | TimeManager.instance = new TimeManager(); 40 | } 41 | return TimeManager.instance; 42 | } 43 | 44 | update(deltaTime: number): void { 45 | this.deltaTime = deltaTime * this.timeScale; 46 | this.totalTime += this.deltaTime; 47 | 48 | if (!this.isPaused) { 49 | this.accumulatedTime += deltaTime; 50 | 51 | while (this.accumulatedTime >= this.fixedDeltaTime) { 52 | this.fixedUpdate(this.fixedDeltaTime); 53 | this.accumulatedTime -= this.fixedDeltaTime; 54 | } 55 | } 56 | } 57 | 58 | fixedUpdate(deltaTime: number): void { 59 | for (const callback of this.fixedUpdateCallbacks) { 60 | callback(deltaTime); 61 | } 62 | } 63 | 64 | registerFixedUpdate(callback: (deltaTime: number) => void): void { 65 | this.fixedUpdateCallbacks.push(callback); 66 | } 67 | 68 | pause(): void { 69 | this.isPaused = true; 70 | } 71 | 72 | resume(): void { 73 | this.isPaused = false; 74 | } 75 | 76 | isGamePaused(): boolean { 77 | return this.isPaused; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /source/src/Network/Authentication.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export class Authentication { 3 | private connection: Connection; 4 | 5 | constructor(connection: Connection) { 6 | this.connection = connection; 7 | } 8 | 9 | /** 10 | * 启动身份验证过程。 11 | * @param username - 用户名。 12 | * @param password - 密码。 13 | */ 14 | public startAuthentication(username: string, password: string): void { 15 | const payload = { 16 | username, 17 | passwordHash: WebSocketUtils.hashPassword(password), 18 | }; 19 | WebSocketUtils.sendToConnection(this.connection, { 20 | type: 'authentication', 21 | payload, 22 | }); 23 | } 24 | 25 | /** 26 | * 处理服务器端发来的身份验证消息。 27 | * @param message - 身份验证消息对象。 28 | */ 29 | public handleAuthenticationMessage(message: Message): void { 30 | if (message.payload.code == ErrorCodes.SUCCESS) { 31 | this.afterAuthenticated(); 32 | } 33 | } 34 | 35 | 36 | /** 37 | * 在身份验证完成后执行一些操作。 38 | */ 39 | private afterAuthenticated(): void { 40 | // 身份验证完成后,可以进行一些操作,例如向服务器发送消息表示已经准备好开始游戏 41 | // 或者在 UI 中显示一个消息表示身份验证成功 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /source/src/Network/Connection.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export class Connection { 3 | private socket: WebSocket; 4 | public isAuthenticated: boolean = false; 5 | public token: string | null = null; 6 | public verificationCode: string | null = null; 7 | 8 | public get Socket() { 9 | return this.socket; 10 | } 11 | 12 | constructor(url: string) { 13 | this.socket = new WebSocket(url); 14 | } 15 | 16 | public send(message: any): void { 17 | this.socket.send(JSON.stringify(message)); 18 | } 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /source/src/Network/ErrorCode.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export const ErrorCodes = { 3 | SUCCESS: 'SUCCESS', 4 | AUTH_FAIL: 'AUTH_FAIL', 5 | WRONG_PASSWORD: 'WRONG_PASSWORD', 6 | REGISTRATION_FAILED: 'REGISTRATION_FAILED', 7 | RECONNECT_FAIL: 'RECONNECT_FAIL', 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /source/src/Network/GApi/MessageHandler.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export class MessageHandler { 3 | private messageHandlers: Record void>; 4 | 5 | public emit(message: Message) { 6 | this.handleMessage(message); 7 | } 8 | 9 | public on(type: string, handler: (msg: Message) => void) { 10 | this.messageHandlers[type] = handler; 11 | } 12 | 13 | private handleMessage(message: Message) { 14 | if (!this.messageHandlers) return; 15 | const handler = this.messageHandlers[message.type]; 16 | if (handler) { 17 | handler(message); 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /source/src/Network/GApi/Player.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export interface Player { 3 | id: string; 4 | } 5 | } -------------------------------------------------------------------------------- /source/src/Network/GApi/Room.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export interface Room { 3 | id: string; 4 | owner: string; 5 | maxPlayers: number; 6 | players: Player[]; 7 | } 8 | } -------------------------------------------------------------------------------- /source/src/Network/GApi/RoomApi.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export class RoomApi { 3 | private createRoomCallback: ((room: Room) => void) | null = null; 4 | private playerLeftCallback: ((playerId: string) => void) | null = null; 5 | private playerJoinedCallback: ((playerId: string, room: Room) => void) | null = null; 6 | private frameSyncCallback: ((payload: any) => void) | null = null; 7 | private snapshotCallback: ((snapshot: any) => void) | null = null; 8 | 9 | constructor(public adapter: NetworkAdapter) { 10 | } 11 | 12 | public createRoom(maxPlayers: number, callback: (room: Room) => void) { 13 | this.createRoomCallback = callback; 14 | 15 | const message: Message = { 16 | type: 'createRoom', 17 | payload: {'maxPlayers': maxPlayers} 18 | } 19 | 20 | this.adapter.send(message); 21 | } 22 | 23 | public joinRoom(roomId: string) { 24 | const message: Message = { 25 | type: 'joinRoom', 26 | payload: {'roomId': roomId} 27 | } 28 | 29 | this.adapter.send(message); 30 | } 31 | 32 | /** 33 | * 离开房间 34 | * @param roomId - 房间ID 35 | */ 36 | public leaveRoom(roomId: string) { 37 | const message: Message = { 38 | type: 'leaveRoom', 39 | payload: {'roomId': roomId} 40 | } 41 | 42 | this.adapter.send(message); 43 | } 44 | 45 | /** 46 | * 开始房间帧同步 47 | * @param roomId - 房间ID 48 | */ 49 | public startGame(roomId: string) { 50 | const message: Message = { 51 | type: 'startGame', 52 | payload: {'roomId': roomId} 53 | } 54 | 55 | this.adapter.send(message); 56 | } 57 | 58 | /** 59 | * 结束房间帧同步 60 | * @param roomId 61 | */ 62 | public endGame(roomId: string) { 63 | const message: Message = { 64 | type: 'endGame', 65 | payload: {'roomId': roomId} 66 | } 67 | 68 | this.adapter.send(message); 69 | } 70 | 71 | public action(act: any) { 72 | const message: Message = { 73 | type: 'action', 74 | payload: act 75 | } 76 | 77 | this.adapter.send(message); 78 | } 79 | 80 | public snapShot(roomId: string, snapshot: any, lastSnapVersion: number) { 81 | const message: Message = { 82 | type: 'snapshot', 83 | payload: { 'roomId': roomId, 'snapshot': snapshot, 'lastSnapVersion': lastSnapVersion } 84 | } 85 | 86 | this.adapter.send(message); 87 | } 88 | 89 | /** 90 | * 设置玩家离开回调 91 | * @param callback 92 | */ 93 | public setPlayerLeftCallback(callback: (playerId: string) => void) { 94 | this.playerLeftCallback = callback; 95 | } 96 | 97 | /** 98 | * 99 | * @param callback 100 | */ 101 | public setFrameSync(callback: (payload: any)=>void) { 102 | this.frameSyncCallback = callback; 103 | } 104 | 105 | /** 106 | * 设置玩家加入回调 107 | * @param callback 108 | */ 109 | public setPlayerJoinedCallback(callback: (playerId: string, room: Room) => void) { 110 | this.playerJoinedCallback = callback; 111 | } 112 | 113 | public setSnapshotCallback(callback: (snapshot: any) => void) { 114 | this.snapshotCallback = callback; 115 | } 116 | 117 | /** 118 | * 当房间创建成功时被调用 119 | * @param room - 房间信息 120 | */ 121 | public onRoomCreated(room: Room) { 122 | if (this.createRoomCallback) { 123 | this.createRoomCallback(room); 124 | } 125 | } 126 | 127 | /** 128 | * 当有玩家离开房间时调用 129 | * @param playerId 130 | */ 131 | public onPlayerLeft(playerId: string) { 132 | if (this.playerLeftCallback) { 133 | this.playerLeftCallback(playerId); 134 | } 135 | } 136 | 137 | /** 138 | * 139 | * @param playerId 140 | * @param room 141 | */ 142 | public onPlayerJoined(playerId: string, room: Room) { 143 | if (this.playerJoinedCallback) 144 | this.playerJoinedCallback(playerId, room); 145 | } 146 | 147 | /** 148 | * 149 | * @param payload 150 | */ 151 | public onFrameSync(payload: any) { 152 | if (this.frameSyncCallback) { 153 | this.frameSyncCallback(payload); 154 | } 155 | } 156 | 157 | public onSnapShot(snapshot: any) { 158 | if (this.snapshotCallback) 159 | this.snapshotCallback(snapshot); 160 | } 161 | } 162 | } -------------------------------------------------------------------------------- /source/src/Network/GNetworkAdapter.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export class GNetworkAdapter implements NetworkAdapter { 3 | private socket: WebSocket; 4 | private reconnectionAttempts: number = 0; 5 | private maxReconnectionAttempts: number = 10; 6 | private connection: Connection; 7 | private authentication: Authentication; 8 | private sessionId: string | null = null; 9 | private lastKnownState: any = null; 10 | private messageHandler: MessageHandler; 11 | private roomAPI: RoomApi; 12 | 13 | public get RoomAPI(): RoomApi { 14 | return this.roomAPI; 15 | } 16 | 17 | constructor(private serverUrl: string, username: string, password: string) { 18 | this.connection = new Connection(serverUrl); 19 | this.messageHandler = new MessageHandler(); 20 | this.authentication = new Authentication(this.connection); 21 | this.roomAPI = new RoomApi(this); 22 | this.connect(username, password); 23 | } 24 | 25 | private connect(username: string, password: string) { 26 | this.socket = this.connection.Socket; 27 | 28 | this.socket.addEventListener('open', () => { 29 | console.info('[g-client]: 连接到服务器'); 30 | this.reconnectionAttempts = 0; 31 | 32 | if (this.sessionId) { 33 | // 发送断线重连请求 34 | const reconnectMsg = { type: 'reconnect', sessionId: this.sessionId, lastKnownState: this.lastKnownState }; 35 | this.socket.send(JSON.stringify(reconnectMsg)); 36 | } else { 37 | // 开始身份验证 38 | this.authentication.startAuthentication(username, password); 39 | } 40 | }); 41 | 42 | this.socket.addEventListener('error', (error: Event) => { 43 | console.error('[g-client]: 发生错误:', error); 44 | }); 45 | 46 | this.socket.addEventListener('close', () => { 47 | if (this.reconnectionAttempts < this.maxReconnectionAttempts) { 48 | console.warn('[g-client]: 连接关闭, 尝试重新连接...'); 49 | setTimeout(() => this.connect(username, password), this.getReconnectDelay()); 50 | this.reconnectionAttempts++; 51 | } else { 52 | console.error('[g-client] 连接关闭, 超出最大重连次数.'); 53 | } 54 | }); 55 | 56 | this.socket.addEventListener('message', (event) => { 57 | const message: Message = JSON.parse(event.data); 58 | this.messageHandler.emit(message); 59 | if (message.type === 'authentication') { 60 | if (message.payload.code == ErrorCodes.SUCCESS) { 61 | this.sessionId = message.payload.sessionId; // 存储sessionId 62 | } 63 | 64 | this.authentication.handleAuthenticationMessage(message); 65 | } else if (message.type === 'sessionId') { 66 | this.sessionId = message.payload; 67 | } else if (message.type === 'stateUpdate') { 68 | this.lastKnownState = message.payload; // 更新lastKnownState 69 | } else if(message.type == 'heartbeat') { 70 | // 心跳包 71 | } else if(message.type == 'roomCreated') { 72 | // 房间创建 73 | this.roomAPI.onRoomCreated(message.payload.room); 74 | } else if(message.type == 'playerLeft') { 75 | // 房间玩家离开 76 | this.roomAPI.onPlayerLeft(message.payload.playerId); 77 | } else if(message.type == 'playerJoined') { 78 | // 房间玩家加入 79 | this.roomAPI.onPlayerJoined(message.payload.playerId, message.payload.room); 80 | } else if(message.type == 'frameSync') { 81 | // 开始帧同步消息 82 | this.roomAPI.onFrameSync(message.payload); 83 | } else if(message.type == 'snapshot') { 84 | this.roomAPI.onSnapShot(message.payload); 85 | } else { 86 | console.warn(`[g-client]: 未知的消息类型: ${message.type}`); 87 | } 88 | }); 89 | } 90 | 91 | sendInput(frameNumber: number, inputData: any): void { 92 | const message = { 93 | type: 'input', 94 | payload: {frameNumber: frameNumber, inputData: inputData}, 95 | }; 96 | this.socket.send(JSON.stringify(message)); 97 | } 98 | 99 | send(message: Message) { 100 | this.socket.send(JSON.stringify(message)); 101 | } 102 | 103 | onServerUpdate(callback: (serverState: any) => void): void { 104 | this.socket.addEventListener('message', (event) => { 105 | const serverState = JSON.parse(event.data.toString()); 106 | callback(serverState); 107 | }); 108 | } 109 | 110 | private getReconnectDelay(): number { 111 | return Math.min(1000 * Math.pow(2, this.reconnectionAttempts), 10000); 112 | } 113 | } 114 | } 115 | 116 | 117 | -------------------------------------------------------------------------------- /source/src/Network/Message.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export interface Message { 3 | type: string; 4 | subtype?: string; 5 | payload: any; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /source/src/Network/NetworkAdapter.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export interface NetworkAdapter { 3 | send(message: Message): void; 4 | 5 | /** 6 | * 将输入数据发送到服务器 7 | * @param frameNumber 客户端帧编号 8 | * @param inputData 输入数据 9 | */ 10 | sendInput(frameNumber: number, inputData: any): void; 11 | 12 | /** 13 | * 从服务器接收状态更新 14 | * @param callback 处理服务器状态更新的回调函数 15 | */ 16 | onServerUpdate(callback: (serverState: any) => void): void; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /source/src/Network/NetworkManager.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export class NetworkManager { 3 | private networkAdapter: NetworkAdapter | null = null; 4 | 5 | /** 6 | * 设置网络适配器 7 | * @param adapter 用户实现的NetworkAdapter接口 8 | */ 9 | setNetworkAdapter(adapter: NetworkAdapter): void { 10 | this.networkAdapter = adapter; 11 | } 12 | 13 | /** 14 | * 获取网络适配器 15 | * @returns 16 | */ 17 | getNetworkAdapter(): NetworkAdapter | null { 18 | return this.networkAdapter; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /source/src/Network/Strategy/ISyncStrategy.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export interface ISyncStrategy { 3 | sendState(state: any): void; 4 | receiveState(state: any): any; 5 | handleStateUpdate(deltaTime: number): void; 6 | } 7 | } -------------------------------------------------------------------------------- /source/src/Network/Strategy/InterpolationStrategy.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export interface InterpolationStrategy { 3 | interpolate(prevSnapshot: any, nextSnapshot: any, progress: number): any; 4 | } 5 | } -------------------------------------------------------------------------------- /source/src/Network/Strategy/LinearInterpolationStrategy.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export class LinearInterpolationStrategy implements InterpolationStrategy { 3 | interpolate(prevSnapshot: any, nextSnapshot: any, progress: number): any { 4 | if (prevSnapshot && nextSnapshot) { 5 | const interpolatedState = {}; 6 | 7 | // 遍历快照中的所有属性 8 | for (const key in prevSnapshot) { 9 | if (typeof prevSnapshot[key] === 'object' && typeof nextSnapshot[key] === 'object') { 10 | // 递归处理嵌套属性 11 | interpolatedState[key] = this.interpolate(prevSnapshot[key], nextSnapshot[key], progress); 12 | } else if (typeof prevSnapshot[key] === 'number' && typeof nextSnapshot[key] === 'number') { 13 | interpolatedState[key] = prevSnapshot[key] + (nextSnapshot[key] - prevSnapshot[key]) * progress; 14 | } else { 15 | // 非数值属性,直接复制 16 | interpolatedState[key] = nextSnapshot[key]; 17 | } 18 | } 19 | 20 | return interpolatedState; 21 | } else { 22 | // 如果无法插值,返回上一个快照 23 | return prevSnapshot; 24 | } 25 | } 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /source/src/Network/Strategy/SnapshotInterpolationStrategy.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | /** 3 | * 快照插值策略 4 | */ 5 | export class SnapshotInterpolationStrategy implements ISyncStrategy { 6 | private snapshotQueue: Array = []; 7 | 8 | private onInterpolation: (prevSnapshot: any, nextSnapshot: any) => void; 9 | 10 | setInterpolationCallback(callback: (prevSnapshot: any, nextSnapshot: any) => void): void { 11 | this.onInterpolation = callback; 12 | } 13 | /** 14 | * 发送游戏状态 15 | * @param state 16 | */ 17 | sendState(state: any): void { 18 | } 19 | 20 | /** 21 | * 在收到新的快照时将其添加到快照队列中 22 | * @param state 23 | */ 24 | receiveState(state: any): void { 25 | this.snapshotQueue.push(state); 26 | } 27 | 28 | handleStateUpdate(state: any): void { 29 | if (this.snapshotQueue.length < 2) { 30 | // 至少需要2个快照才能执行插值 31 | return; 32 | } 33 | 34 | const prevSnapshot = this.snapshotQueue[0]; 35 | const nextSnapshot = this.snapshotQueue[1]; 36 | if (prevSnapshot.timestamp == nextSnapshot.timestamp) { 37 | this.snapshotQueue.shift(); 38 | return; 39 | } 40 | 41 | // 调用用户自定义的插值回调方法 42 | if (this.onInterpolation) { 43 | this.onInterpolation(prevSnapshot, nextSnapshot); 44 | } 45 | 46 | this.snapshotQueue.shift(); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /source/src/Network/Strategy/SplineInterpolationStrategy.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export class SplineInterpolationStrategy implements InterpolationStrategy { 3 | interpolate(prevSnapshot: any, nextSnapshot: any, progress: number): any { 4 | if (prevSnapshot && nextSnapshot) { 5 | const interpolatedState = {}; 6 | 7 | // 遍历快照中的所有属性 8 | for (const key in prevSnapshot) { 9 | if (typeof prevSnapshot[key] === 'object' && typeof nextSnapshot[key] === 'object') { 10 | // 递归处理嵌套属性 11 | interpolatedState[key] = this.interpolate(prevSnapshot[key], nextSnapshot[key], progress); 12 | } else if (typeof prevSnapshot[key] === 'number' && typeof nextSnapshot[key] === 'number') { 13 | // 使用三次样条插值计算插值后的值 14 | const p0 = prevSnapshot[key]; 15 | const p1 = nextSnapshot[key]; 16 | const t = progress; 17 | 18 | const a = p1 - p0; 19 | const b = p0; 20 | const c = (-3 * p0 + 3 * p1 - 2 * a) / t; 21 | const d = (2 * p0 - 2 * p1 + a) / (t * t); 22 | 23 | interpolatedState[key] = a * t * t * t + b * t * t + c * t + d; 24 | } else { 25 | // 非数值属性,直接复制 26 | interpolatedState[key] = nextSnapshot[key]; 27 | } 28 | } 29 | 30 | return interpolatedState; 31 | } else { 32 | // 如果无法插值,返回上一个快照 33 | return prevSnapshot; 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /source/src/Network/Strategy/StateCompressionStrategy.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | /** 3 | * 状态压缩策略 4 | */ 5 | export class StateCompressionStrategy implements ISyncStrategy { 6 | public onCompressState: (state: any) => any; 7 | public onDecompressState: (compressedState: any) => any; 8 | public onSendState: (compressedState: any) => void; 9 | public onReceiveState: (decompressedState: any) => void; 10 | public handleStateUpdate: (state: any) => void = () => { }; 11 | 12 | /** 13 | * 发送游戏状态时,将游戏状态压缩 14 | * @param state 15 | */ 16 | sendState(state: any): void { 17 | let compressedState = state; 18 | 19 | if (this.onCompressState) { 20 | compressedState = this.onCompressState(state); 21 | } 22 | 23 | if (this.onSendState) { 24 | this.onSendState(compressedState); 25 | } 26 | } 27 | 28 | /** 29 | * 接收服务器或客户端发送的压缩后的游戏状态,并解压缩更新 30 | */ 31 | receiveState(compressedState: any) { 32 | let decompressedState = compressedState; 33 | 34 | if (this.onDecompressState) { 35 | decompressedState = this.onDecompressState(compressedState); 36 | } 37 | 38 | if (this.onReceiveState) { 39 | this.onReceiveState(decompressedState); 40 | } 41 | 42 | this.handleStateUpdate(decompressedState); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /source/src/Network/Strategy/SyncStrategyManager.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | /** 3 | * 同步策略管理器类 4 | */ 5 | export class SyncStrategyManager { 6 | private strategy: ISyncStrategy; 7 | 8 | /** 9 | * 构造函数 10 | * @param strategy - 同步策略实现 11 | */ 12 | constructor(strategy: ISyncStrategy) { 13 | // 将传入的策略实现赋值给私有变量 14 | this.strategy = strategy; 15 | } 16 | 17 | /** 18 | * 发送状态方法 19 | * @param state - 需要发送的状态对象 20 | */ 21 | sendState(state: any): void { 22 | this.strategy.sendState(state); 23 | } 24 | 25 | /** 26 | * 接收状态方法 27 | * @param state - 接收到的状态对象 28 | */ 29 | receiveState(state: any): void { 30 | this.strategy.receiveState(state); 31 | } 32 | 33 | /** 34 | * 处理状态更新方法 35 | * @param deltaTime - 时间增量 36 | */ 37 | handleStateUpdate(deltaTime: number): void { 38 | this.strategy.handleStateUpdate(deltaTime); 39 | } 40 | 41 | /** 42 | * 设置策略方法 43 | * @param strategy - 新的同步策略实现 44 | */ 45 | setStrategy(strategy: ISyncStrategy): void { 46 | this.strategy = strategy; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /source/src/Network/WebSocketUtils.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export class WebSocketUtils { 3 | public static hashPassword(password: string): string { 4 | let hash = 0, i, chr; 5 | for (i = 0; i < password.length; i++) { 6 | chr = password.charCodeAt(i); 7 | hash = ((hash << 5) - hash) + chr; 8 | hash |= 0; 9 | } 10 | return hash.toString(); 11 | } 12 | 13 | public static sendToConnection(connection: Connection, message: Message): void { 14 | connection.send(message); 15 | } 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /source/src/Plugins/IPlugin.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export interface IPlugin { 3 | name: string; 4 | onInit(core: Core): void; 5 | onUpdate(deltaTime: number): void; 6 | } 7 | } -------------------------------------------------------------------------------- /source/src/Pool/EventPool.ts: -------------------------------------------------------------------------------- 1 | /// 2 | module gs { 3 | export class EventPool extends ObjectPool { 4 | constructor() { 5 | super( 6 | () => new GEvent("", null), 7 | (event: GEvent) => event.reset() 8 | ); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /source/src/Pool/ObjectPool.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export class ObjectPool { 3 | private pool: T[]; 4 | 5 | constructor(private createFn: () => T, private resetFn: (obj: T) => void) { 6 | this.pool = []; 7 | } 8 | 9 | acquire(): T { 10 | if (this.pool.length > 0) { 11 | const obj = this.pool.pop() as T; 12 | this.resetFn(obj); 13 | return obj; 14 | } else { 15 | return this.createFn(); 16 | } 17 | } 18 | 19 | release(obj: T): void { 20 | this.pool.push(obj); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /source/src/State/StateMachine.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export interface State { 3 | enter?(): void; 4 | exit?(): void; 5 | update?(): void; 6 | } 7 | 8 | export class StateMachine { 9 | private currentState: State | null; 10 | private states: Map; 11 | 12 | constructor() { 13 | this.currentState = null; 14 | this.states = new Map(); 15 | } 16 | 17 | addState(name: string, state: State): void { 18 | this.states.set(name, state); 19 | } 20 | 21 | changeState(name: string): void { 22 | if (!this.states.has(name)) { 23 | console.warn(`状态 "${name}" 不存在.`); 24 | return; 25 | } 26 | 27 | const newState = this.states.get(name) as State; 28 | if (this.currentState && this.currentState.exit) { 29 | this.currentState.exit(); 30 | } 31 | 32 | this.currentState = newState; 33 | if (this.currentState.enter) { 34 | this.currentState.enter(); 35 | } 36 | } 37 | 38 | update(): void { 39 | if (this.currentState && this.currentState.update) { 40 | this.currentState.update(); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /source/src/State/StateMachineComponent.ts: -------------------------------------------------------------------------------- 1 | /// 2 | module gs { 3 | export class StateMachineComponent extends Component { 4 | stateMachine: StateMachine; 5 | 6 | constructor() { 7 | super(); 8 | this.stateMachine = new StateMachine(); 9 | }gu 10 | 11 | public reset(): void { 12 | this.stateMachine = new StateMachine(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /source/src/State/StateMachineSystem.ts: -------------------------------------------------------------------------------- 1 | /// 2 | module gs { 3 | export class StateMachineSystem extends System { 4 | constructor(entityManager: EntityManager) { 5 | super(entityManager, 1); 6 | } 7 | 8 | entityFilter(entity: Entity): boolean { 9 | return entity.hasComponent(StateMachineComponent); 10 | } 11 | 12 | update(entities: Entity[]): void { 13 | for (const entity of entities) { 14 | const stateMachineComponent = entity.getComponent(StateMachineComponent) as StateMachineComponent; 15 | stateMachineComponent.stateMachine.update(); 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /source/src/Utils/Bits.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export class Bits { 3 | private data: Uint32Array; 4 | 5 | constructor(size: number = 32) { 6 | this.data = new Uint32Array(Math.ceil(size / 32)); 7 | } 8 | 9 | public set(index: number): void { 10 | const dataIndex = Math.floor(index / 32); 11 | const bitIndex = index % 32; 12 | this.data[dataIndex] |= 1 << bitIndex; 13 | } 14 | 15 | public clear(index: number): void { 16 | const dataIndex = Math.floor(index / 32); 17 | const bitIndex = index % 32; 18 | this.data[dataIndex] &= ~(1 << bitIndex); 19 | } 20 | 21 | public get(index: number): boolean { 22 | const dataIndex = Math.floor(index / 32); 23 | const bitIndex = index % 32; 24 | return (this.data[dataIndex] & (1 << bitIndex)) !== 0; 25 | } 26 | 27 | public resize(newSize: number): void { 28 | const newData = new Uint32Array(Math.ceil(newSize / 32)); 29 | newData.set(this.data); 30 | this.data = newData; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /source/src/Utils/EntityIdAllocator.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export class EntityIdAllocator { 3 | private nextId: number; 4 | 5 | constructor() { 6 | this.nextId = 0; 7 | } 8 | 9 | allocate(): number { 10 | const newId = this.nextId; 11 | this.nextId += 1; 12 | return newId; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /source/src/Utils/LinkedList.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export class Node { 3 | value: T; 4 | next: Node | null = null; 5 | prev: Node | null = null; 6 | 7 | constructor(value: T) { 8 | this.value = value; 9 | } 10 | } 11 | 12 | /** 13 | * 双向链表 14 | */ 15 | export class LinkedList { 16 | head: Node | null = null; 17 | tail: Node | null = null; 18 | 19 | append(value: T): Node { 20 | const newNode = new Node(value); 21 | if (!this.head || !this.tail) { 22 | this.head = newNode; 23 | this.tail = newNode; 24 | } else { 25 | newNode.prev = this.tail; 26 | this.tail.next = newNode; 27 | this.tail = newNode; 28 | } 29 | return newNode; 30 | } 31 | 32 | remove(node: Node): void { 33 | if (node.prev) { 34 | node.prev.next = node.next; 35 | } else { 36 | this.head = node.next; 37 | } 38 | 39 | if (node.next) { 40 | node.next.prev = node.prev; 41 | } else { 42 | this.tail = node.prev; 43 | } 44 | 45 | node.prev = null; 46 | node.next = null; 47 | } 48 | 49 | toArray(): T[] { 50 | const result = []; 51 | let current = this.head; 52 | while (current) { 53 | result.push(current.value); 54 | current = current.next; 55 | } 56 | return result; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /source/src/Utils/Matcher.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | /** 3 | * 定义一个实体匹配器类。 4 | */ 5 | export class Matcher { 6 | protected allSet: (new (...args: any[]) => Component)[] = []; 7 | protected exclusionSet: (new (...args: any[]) => Component)[] = []; 8 | protected oneSet: (new (...args: any[]) => Component)[] = []; 9 | 10 | public static empty() { 11 | return new Matcher(); 12 | } 13 | 14 | public getAllSet() { 15 | return this.allSet; 16 | } 17 | 18 | public getExclusionSet() { 19 | return this.exclusionSet; 20 | } 21 | 22 | public getOneSet() { 23 | return this.oneSet; 24 | } 25 | 26 | public isInterestedEntity(e: Entity) { 27 | return this.isInterested(e.componentBits); 28 | } 29 | 30 | public isInterested(components: Bits) { 31 | return this.checkAllSet(components) && this.checkExclusionSet(components) && this.checkOneSet(components); 32 | } 33 | 34 | private checkAllSet(components: Bits): boolean { 35 | for (const type of this.allSet) { 36 | const info = ComponentTypeManager.getIndexFor(type); 37 | if (!info.allAncestors.some(index => components.get(index))) { 38 | return false; 39 | } 40 | } 41 | return true; 42 | } 43 | 44 | private checkExclusionSet(components: Bits): boolean { 45 | for (const type of this.exclusionSet) { 46 | const info = ComponentTypeManager.getIndexFor(type); 47 | // 如果有任何一个祖先类型的组件被拥有,就返回false 48 | if (info.allAncestors.some(index => components.get(index))) { 49 | return false; 50 | } 51 | } 52 | return true; 53 | } 54 | 55 | private checkOneSet(components: Bits): boolean { 56 | if (this.oneSet.length === 0) { 57 | return true; 58 | } 59 | 60 | for (const type of this.oneSet) { 61 | const info = ComponentTypeManager.getIndexFor(type); 62 | // 如果有任何一个祖先类型的组件被拥有,就返回true 63 | if (info.allAncestors.some(index => components.get(index))) { 64 | return true; 65 | } 66 | } 67 | return false; 68 | } 69 | 70 | /** 71 | * 添加所有包含的组件类型。 72 | * @param types 所有包含的组件类型列表 73 | */ 74 | public all(...types: (new (...args: any[]) => Component)[]): Matcher { 75 | this.allSet.push(...types); 76 | return this; 77 | } 78 | 79 | /** 80 | * 添加排除包含的组件类型。 81 | * @param types 排除包含的组件类型列表 82 | */ 83 | public exclude(...types: (new (...args: any[]) => Component)[]): Matcher { 84 | this.exclusionSet.push(...types); 85 | return this; 86 | } 87 | 88 | /** 89 | * 添加至少包含其中之一的组件类型。 90 | * @param types 至少包含其中之一的组件类型列表 91 | */ 92 | public one(...types: (new (...args: any[]) => Component)[]): Matcher { 93 | this.oneSet.push(...types); 94 | return this; 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /source/src/Utils/Random.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | export class Random { 3 | private seed: number; 4 | 5 | constructor(seed: number) { 6 | this.seed = seed; 7 | } 8 | 9 | /** 10 | * 生成 [0, 1) 范围内的随机浮点数 11 | * @returns 12 | */ 13 | next(): number { 14 | this.seed = (this.seed * 9301 + 49297) % 233280; 15 | return this.seed / 233280; 16 | } 17 | 18 | /** 19 | * 生成 [min, max) 范围内的随机整数 20 | * @param min 21 | * @param max 22 | * @returns 23 | */ 24 | nextInt(min: number, max: number): number { 25 | return min + Math.floor(this.next() * (max - min)); 26 | } 27 | 28 | /** 29 | * 生成 [min, max) 范围内的随机浮点数 30 | * @param min 31 | * @param max 32 | * @returns 33 | */ 34 | nextFloat(min: number, max: number): number { 35 | return min + this.next() * (max - min); 36 | } 37 | 38 | /** 39 | * 从数组中随机选择一个元素 40 | * @param array 41 | * @returns 42 | */ 43 | choose(array: T[]): T { 44 | const index = this.nextInt(0, array.length); 45 | return array[index]; 46 | } 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /source/src/Utils/SparseSet.ts: -------------------------------------------------------------------------------- 1 | module gs { 2 | /** 3 | * SparseSet数据结构 4 | */ 5 | export class SparseSet { 6 | private sparse: number[]; 7 | private dense: number[]; 8 | private items: T[]; 9 | private count: number; 10 | 11 | constructor() { 12 | this.sparse = []; 13 | this.dense = []; 14 | this.items = []; 15 | this.count = 0; 16 | } 17 | 18 | public add(index: number, item: T): void { 19 | if (index >= this.sparse.length) { 20 | this.sparse.length = index + 1; 21 | } 22 | 23 | this.sparse[index] = this.count; 24 | this.dense[this.count] = index; 25 | this.items[this.count] = item; 26 | this.count++; 27 | } 28 | 29 | public remove(index: number): void { 30 | const denseIndex = this.sparse[index]; 31 | const lastIndex = this.count - 1; 32 | const lastDense = this.dense[lastIndex]; 33 | const lastItem = this.items[lastIndex]; 34 | 35 | this.dense[denseIndex] = lastDense; 36 | this.items[denseIndex] = lastItem; 37 | this.sparse[lastDense] = denseIndex; 38 | 39 | this.count--; 40 | } 41 | 42 | public has(index: number): boolean { 43 | return this.sparse[index] < this.count && this.dense[this.sparse[index]] === index; 44 | } 45 | 46 | public get(index: number): T { 47 | if (!this.has(index)) { 48 | return null; 49 | } 50 | return this.items[this.sparse[index]]; 51 | } 52 | 53 | public getCount(): number { 54 | return this.count; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /source/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "downlevelIteration": true, 4 | "importHelpers": true, 5 | "pretty": true, 6 | "preserveConstEnums": true, 7 | "alwaysStrict": true, 8 | "module": "system", 9 | "target": "es5", 10 | "declaration": true, 11 | "sourceMap": false, 12 | "removeComments": false, 13 | "experimentalDecorators": true, 14 | "outFile": "./gs.js", 15 | "lib": [ 16 | "es5", 17 | "dom", 18 | "es2015.promise", 19 | "es6" 20 | ] 21 | }, 22 | "include": [ 23 | "src", 24 | "lib" 25 | ], 26 | "exclude": [ 27 | "node_modules" 28 | ] 29 | } --------------------------------------------------------------------------------