├── .github └── workflows │ └── .ci.yml ├── .gitignore ├── .prettierrc.json ├── README.md ├── deploy.sh ├── package.json ├── src ├── babylon.html ├── canvas.html ├── dom.html ├── excalibur.html ├── hilo.html ├── index.html ├── kaboom.html ├── kaplay.html ├── kontra.html ├── melon.html ├── partials │ ├── container.html │ ├── footer.html │ ├── head.html │ └── header.html ├── phaser.html ├── pixi.html ├── public │ ├── favicon.svg │ ├── sprite.png │ ├── spritesheet.png │ └── style.css ├── scripts │ ├── babylon.js │ ├── canvas.js │ ├── dom.js │ ├── engine.js │ ├── excalibur.js │ ├── hilo.js │ ├── kaboom.js │ ├── kaplay.js │ ├── kontra.js │ ├── melon.js │ ├── phaser.js │ ├── pixi.js │ ├── three.js │ └── two.js ├── three.html └── two.html ├── vite.config.js └── yarn.lock /.github/workflows/.ci.yml: -------------------------------------------------------------------------------- 1 | name: 'CI' 2 | on: 3 | - push 4 | - pull_request 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | if: github.ref == 'refs/heads/master' 10 | permissions: 11 | contents: write 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: 16 18 | cache: 'yarn' 19 | - run: yarn install --frozen-lockfile --ignore-engines 20 | 21 | - name: Build 22 | run: | 23 | yarn build 24 | echo > ./dist/.nojekyll 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | - name: Deploy to GitHub Pages 28 | uses: peaceiris/actions-gh-pages@v3 29 | with: 30 | github_token: ${{ secrets.GITHUB_TOKEN }} 31 | publish_dir: ./dist 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "semi": true, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Javascript rendering/game engines comparison 2 | 3 | https://shirajuki.js.org/js-game-rendering-benchmark/ 4 | 5 | This is a sprite-based performance test that compares a set of Javascript-based rendering/game engines that are currently maintained. The test includes rendering a set of graphics/shapes and sprites. 6 | 7 | ## Description 8 | 9 | - A preset of up to 10.000 different sprites moving on a canvas with various speed 10 | - A custom option through the count query is also available for comparing sprite counts between libraries more freely. Be aware that drawing a large number of sprites on specific libraries may cause the test to freeze. 11 | - Compare drawing of graphics/shapes and sprites through the type query 12 | - Different libraries used to render the scene are chosen only if they have been maintained in the previous month of this benchmark. The following libraries compared and sorted by popularity (stars) are: 13 | 14 | | Name | Stars | Last Commit | Description | Game engine | 15 | | ------------------------------------------------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | 16 | | [three.js](https://github.com/mrdoob/three.js) | ![GitHub Repo stars](https://img.shields.io/github/stars/mrdoob/three.js) | ![GitHub last commit](https://img.shields.io/github/last-commit/mrdoob/three.js) | JavaScript 3D library. | no | 17 | | [PixiJS](https://github.com/pixijs/pixi.js) | ![GitHub Repo stars](https://img.shields.io/github/stars/pixijs/pixi.js) | ![GitHub last commit](https://img.shields.io/github/last-commit/pixijs/pixi.js) | The HTML5 Creation Engine: Create beautiful digital content with the fastest, most flexible 2D WebGL renderer. | no | 18 | | [Phaser](https://github.com/photonstorm/phaser) | ![GitHub Repo stars](https://img.shields.io/github/stars/photonstorm/phaser) | ![GitHub last commit](https://img.shields.io/github/last-commit/photonstorm/phaser) | Phaser is a fun, free and fast 2D game framework for making HTML5 games for desktop and mobile web browsers, supporting Canvas and WebGL rendering. | yes | 19 | | [Babylon.js](https://github.com/BabylonJS/Babylon.js) | ![GitHub Repo stars](https://img.shields.io/github/stars/BabylonJS/Babylon.js) | ![GitHub last commit](https://img.shields.io/github/last-commit/BabylonJS/Babylon.js) | Babylon.js is a powerful, beautiful, simple, and open game and rendering engine packed into a friendly JavaScript framework. | yes | 20 | | [Two.js](https://github.com/jonobr1/two.js) | ![GitHub Repo stars](https://img.shields.io/github/stars/jonobr1/two.js) | ![GitHub last commit](https://img.shields.io/github/last-commit/jonobr1/two.js) | A renderer agnostic two-dimensional drawing api for the web. | no | 21 | | [Hilo](https://github.com/hiloteam/Hilo) | ![GitHub Repo stars](https://img.shields.io/github/stars/hiloteam/Hilo) | ![GitHub last commit](https://img.shields.io/github/last-commit/hiloteam/Hilo) | A Cross-end HTML5 Game development solution developed by Alibaba Group | yes | 22 | | [MelonJS](https://github.com/melonjs/melonjs) | ![GitHub Repo stars](https://img.shields.io/github/stars/melonjs/melonjs) | ![GitHub last commit](https://img.shields.io/github/last-commit/melonjs/melonjs) | A fresh & lightweight javascript game engine. | yes | 23 | | [Kaboom](https://github.com/replit/kaboom) (deprecated) | ![GitHub Repo stars](https://img.shields.io/github/stars/replit/kaboom) | ![GitHub last commit](https://img.shields.io/github/last-commit/replit/kaboom) | 💥 JavaScript game library. | yes | 24 | | [Excalibur](https://github.com/excaliburjs/Excalibur) | ![GitHub Repo stars](https://img.shields.io/github/stars/excaliburjs/Excalibur) | ![GitHub last commit](https://img.shields.io/github/last-commit/excaliburjs/Excalibur) | 🎮 Your friendly TypeScript 2D game engine for the web 🗡️ | yes | 25 | | [Kaplay](https://github.com/kaplayjs/kaplay) | ![GitHub Repo stars](https://img.shields.io/github/stars/kaplayjs/kaplay) | ![GitHub last commit](https://img.shields.io/github/last-commit/kaplayjs/kaplay) | 🦖 A JavaScript/TypeScript Game Library that feels like a game. | yes | 26 | | [Kontra](https://github.com/straker/kontra) | ![GitHub Repo stars](https://img.shields.io/github/stars/straker/kontra) | ![GitHub last commit](https://img.shields.io/github/last-commit/straker/kontra) | A lightweight JavaScript gaming micro-library, optimized for js13kGames. | yes | 27 | 28 | ## Canvas and WebGL 29 | 30 | As a baseline for benchmarking, a DOM engine and Canvas engine for rendering shapes and sprites are included. Canvas is a 2D rendering context that lets us draw and manipulate sprites on an HTML canvas element. It's simple to use and supported by the majority of modern browsers, making it a popular choice for creating simple graphics, animations, and games. 31 | 32 | In contrast to Canvas, WebGL is a more powerful and versatile alternative. WebGL allows for more detailed and interactive 3D graphics, as well as access to the GPU, which improves performance even more than Canvas. As a result, WebGL is the most powerful choice for any web application that needs graphics-intensive rendering or interactive 3D scenes. However, when comparing Canvas and WebGL performance benchmarks with 2D graphics and sprites, it is important to remember that there may be some bias depending on the specific implementation as well as the environment in which the tests are run. Because WebGL is designed primarily for 3D graphics, it may not provide a significant performance boost over Canvas in a 2D context due to its higher resource requirements. 33 | 34 | To summarize, both technologies have advantages and are used in different scenarios depending on the project's specific needs. As a result, comparing the differences between canvas and WebGL with 2D sprites may be biased. 35 | 36 | ## Game engines and rendering engines 37 | 38 | A game engine is a software framework that gives one the tools and functionality one need to create and develop games. Input handling, physics, collision detection, and sprite animation are examples of the common features. A rendering engine, on the other hand, is a software component that is in charge of displaying and rendering graphics on the screen. It usually does not include the full set of tools and features found in a game engine. Three.js, Pixi.js, and Two.js are examples of such rendering engines. 39 | 40 | The rendering engine libraries Three.js, Pixi.js, and Two.js were tested on rendering 2D graphics and sprites for the performance benchmark. The most performant was discovered to be Pixi.js, followed by Three.js and Two.js. As a fallback, all of these support rendering in both the WebGL and Canvas contexts. 41 | 42 | In terms of performance, Babylon.js and Phaser outperformed the other game engines tested. The former is a game engine designed primarily for 3D games capable of rendering 2D sprites, whereas the latter has a longer history and a larger user base. Babylon.js was discovered to be slightly faster than Phaser, but both game engines performed reasonably well when tested. 43 | 44 | ## Conclusion 45 | 46 | It should be noted that being faster is not always preferable. Aside from performance, one must consider the features a library provides such as asset loading, sprite animation, input, etc. to easily create various games. In addition to this, some rendering/game engines may have a steeper learning curve than others, making them less desirable to use. It is important to consider user experience as well as speed in game development. 47 | 48 | Results of the benchmark on 10.000 sprites: 49 | 50 | 1. **Babylon.js**: [56 FPS] Babylon.js outperforms Three.js and has the best overall performance when it comes to rendering 2D sprites, despite being one of only two 3D engines in this benchmark - game engine. [(link)](https://shirajuki.js.org/js-game-rendering-benchmark/babylon.html?count=10000&type=sprite) 51 | 2. **Pixi.js**: [47 FPS] Good performance - rendering engine. [(link)](https://shirajuki.js.org/js-game-rendering-benchmark/pixi.html?count=10000&type=sprite) 52 | 3. **Phaser**: [43 FPS] The most popular library for HTML5 game development, and the performance demonstrates why. Overall, a good performance - game engine. [(link)](https://shirajuki.js.org/js-game-rendering-benchmark/phaser.html?count=10000&type=sprite) 53 | 54 | Honourable mentions: 55 | 56 | - **Kontra**: [60 FPS] To avoid frame rate issues, the game loop implementation of Kontra uses a time-based animation with a fixed dt. As a result, each update call is always guaranteed set to be 1/60 of a second. The update at 60 FPS in this case is not as smooth as in the other tests above, ranking it lower. - lightweight game engine. [(link)](https://shirajuki.js.org/js-game-rendering-benchmark/kontra.html?count=10000&type=sprite) 57 | - **Kaboom**: [3 FPS] Despite being one of the slowest libraries in terms of performance, developing with Kaboom was the most enjoyable. The library is jam-packed with features that make development simple and enjoyable. It has a straightforward syntax, and the code is simple to read and understand. The documentation and examples are also among the best of any library - game engine. [(link)](https://shirajuki.js.org/js-game-rendering-benchmark/kaboom.html?count=10000&type=sprite) 58 | 59 | The performance of the different libraries was measured by running a benchmark test on a computer with the following specifications: 60 | 61 | - OS: `Microsoft Windows 11 Home - 10.0.22621 Build 22621` 62 | - CPU: `AMD Ryzen 5 4500U` 63 | - RAM: `8GB 2666MHz` 64 | - Browser: `Microsoft Edge - Version 109.0.1518.55 (Official build) (64-bit)` 65 | 66 | ## Readings 67 | 68 | - [A collection of WebGL and WebGPU frameworks and libraries](https://gist.github.com/dmnsgn/76878ba6903cf15789b712464875cfdc) 69 | - [A collection of primarily HTML5 based game engines and frameworks](https://github.com/bebraw/jswiki/wiki/Game-Engines) 70 | - [quidmonkey's Benchmark comparison of pixijs and impactjs](https://github.com/quidmonkey/particle_test) 71 | - [slaylines' benchmark comparison of some popular canvas engines](https://github.com/slaylines/canvas-engines-comparison) 72 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # abort on errors 4 | set -e 5 | 6 | # build 7 | npm run build 8 | 9 | # navigate into the build output directory 10 | cd dist 11 | 12 | # place .nojekyll to bypass Jekyll processing 13 | echo > .nojekyll 14 | 15 | git init 16 | git checkout -B master 17 | git add -A 18 | git commit -m 'ci: deploy web build' 19 | 20 | git push -f git@github.com:shirajuki/js-game-rendering-benchmark.git master:gh-pages 21 | 22 | cd - 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-game-rendering-benchmark", 3 | "private": true, 4 | "version": "1.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "NODE_ENV=development vite", 8 | "build": "vite build", 9 | "deploy": "bash deploy.sh", 10 | "preview": "vite preview" 11 | }, 12 | "devDependencies": { 13 | "vite": "^4.0.0", 14 | "vite-plugin-handlebars": "^1.6.0" 15 | }, 16 | "dependencies": { 17 | "babylonjs": "^5.42.0", 18 | "excalibur": "^0.30.3", 19 | "fpsmeter": "^0.3.1", 20 | "hilojs": "^2.0.2", 21 | "kaboom": "^2000.2.10", 22 | "kaplay": "^3001.0.12", 23 | "kontra": "^8.0.0", 24 | "melonjs": "^14.5.0", 25 | "phaser": "^3.55.2", 26 | "pixi.js": "^7.1.0", 27 | "three": "^0.148.0", 28 | "two.js": "^0.8.10" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/babylon.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{#> head }} Babylon.js — JS Game Rendering Benchmark {{/head}} 4 | 5 | {{> header }} 6 |
7 | {{> container }} 8 | 9 |
10 | {{> footer }} 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/canvas.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{#> head }} Canvas — JS Game Rendering Benchmark {{/head}} 4 | 5 | {{> header }} 6 |
7 | {{> container }} 8 | 9 |
10 | {{> footer }} 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/dom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{#> head }} DOM — JS Game Rendering Benchmark {{/head}} 4 | 5 | {{> header }} 6 |
7 | {{> container }} 8 |
9 |
10 | {{> footer }} 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/excalibur.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{#> head }} Excalibur — JS Game Rendering Benchmark {{/head}} 4 | 5 | {{> header }} 6 |
7 | {{> container }} 8 | 9 |
10 | {{> footer }} 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/hilo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{#> head }} Hilo — JS Game Rendering Benchmark {{/head}} 4 | 5 | {{> header }} 6 |
7 | {{> container }} 8 | 9 |
10 | {{> footer }} 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{#> head index=1 }} Index — JS Game Rendering Benchmark {{/head}} 4 | 5 | {{> header }} 6 |
7 | {{> container }} 8 | 9 |
10 | {{> footer }} 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/kaboom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{#> head }} Kaboom v2000 — JS Game Rendering Benchmark {{/head}} 4 | 5 | {{> header }} 6 |
7 | {{> container }} 8 | 9 |
10 | {{> footer }} 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/kaplay.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{#> head }} Kaboom v3000 — JS Game Rendering Benchmark {{/head}} 4 | 5 | {{> header }} 6 |
7 | {{> container }} 8 | 9 |
10 | {{> footer }} 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/kontra.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{#> head }} Kontra — JS Game Rendering Benchmark {{/head}} 4 | 5 | {{> header }} 6 |
7 | {{> container }} 8 | 9 |
10 | {{> footer }} 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/melon.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{#> head }} melonJS — JS Game Rendering Benchmark {{/head}} 4 | 5 | {{> header }} 6 |
7 | {{> container }} 8 | 9 |
10 | {{> footer }} 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/partials/container.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | COUNT 4 |
5 | 500 6 | 1000 7 | 2500 8 | 5000 9 | 10000 10 | CUSTOM 11 |
12 |
13 |
14 |
15 | Stroke 16 | Fill 17 | Sprite 18 |
19 |
20 | -------------------------------------------------------------------------------- /src/partials/footer.html: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /src/partials/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | {{> @partial-block }} 13 | {{#if index }} 14 | 15 | 18 | {{/if}} 19 | 20 | 77 | -------------------------------------------------------------------------------- /src/partials/header.html: -------------------------------------------------------------------------------- 1 |
2 | 17 |
18 | -------------------------------------------------------------------------------- /src/phaser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{#> head }} Phaser — JS Game Rendering Benchmark {{/head}} 4 | 5 | {{> header }} 6 |
7 | {{> container }} 8 | 9 |
10 | {{> footer }} 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/pixi.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{#> head }} Pixi.js — JS Game Rendering Benchmark {{/head}} 4 | 5 | {{> header }} 6 |
7 | {{> container }} 8 | 9 |
10 | {{> footer }} 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/public/sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shirajuki/js-game-rendering-benchmark/09f38e74716aeb3749a0c908d900e345c4fd0912/src/public/sprite.png -------------------------------------------------------------------------------- /src/public/spritesheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shirajuki/js-game-rendering-benchmark/09f38e74716aeb3749a0c908d900e345c4fd0912/src/public/spritesheet.png -------------------------------------------------------------------------------- /src/public/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | :root { 6 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif; 7 | font-size: 16px; 8 | line-height: 24px; 9 | font-weight: 400; 10 | 11 | color: rgba(255, 255, 255, 0.87); 12 | background-color: #242424; 13 | 14 | font-synthesis: none; 15 | text-rendering: optimizeLegibility; 16 | -webkit-font-smoothing: antialiased; 17 | -moz-osx-font-smoothing: grayscale; 18 | -webkit-text-size-adjust: 100%; 19 | } 20 | 21 | a { 22 | font-weight: 500; 23 | color: #9c64ff; 24 | text-decoration: none; 25 | border-bottom: 1px solid #9c64ff; 26 | transition: color 0.2s, filter 0.2s, border-color 0.2s; 27 | } 28 | 29 | a:hover { 30 | color: #934ddd; 31 | } 32 | 33 | html { 34 | zoom: 0.9; 35 | } 36 | 37 | body { 38 | margin: 0; 39 | min-width: 320px; 40 | min-height: 100vh; 41 | font-size: 18px; 42 | } 43 | 44 | header { 45 | padding: 2rem; 46 | min-height: 90px; 47 | font-size: 26px; 48 | } 49 | 50 | nav { 51 | display: flex; 52 | flex-wrap: wrap; 53 | gap: 1rem; 54 | } 55 | 56 | main { 57 | padding: 0 2rem; 58 | max-width: 100vw; 59 | overflow-x: hidden; 60 | } 61 | 62 | footer { 63 | position: fixed; 64 | display: flex; 65 | flex-direction: column; 66 | justify-content: flex-end; 67 | gap: 0.25rem; 68 | bottom: 0; 69 | width: 100%; 70 | height: 90px; 71 | padding: 2rem; 72 | } 73 | 74 | footer .name { 75 | font-size: 26px; 76 | } 77 | 78 | footer span { 79 | font-size: 14px; 80 | margin-left: 0.75rem; 81 | opacity: 0.7; 82 | } 83 | 84 | main>div { 85 | display: grid; 86 | grid-template-columns: 1fr 140px; 87 | min-height: 60px; 88 | align-items: center; 89 | width: 1024px; 90 | } 91 | 92 | .container { 93 | position: relative; 94 | margin-top: 0.2rem; 95 | margin-bottom: 0.8rem; 96 | } 97 | 98 | .fps-container { 99 | position: relative; 100 | transform: translate(4px, 13px); 101 | } 102 | 103 | .fps-container>div { 104 | height: 40px !important; 105 | border-radius: 6px; 106 | } 107 | 108 | .container span { 109 | font-size: 0.9rem; 110 | } 111 | 112 | .count-container>div { 113 | margin-top: 0.1rem; 114 | } 115 | 116 | .count-container a:last-of-type:not(.active) { 117 | pointer-events: none; 118 | opacity: 0.7; 119 | border-bottom: 1px solid transparent; 120 | filter: grayscale(1); 121 | } 122 | 123 | .container a { 124 | position: relative; 125 | display: inline-block; 126 | border-radius: 6px; 127 | border: 1px solid transparent; 128 | padding: 0.3em 0.6em; 129 | font-size: 1em; 130 | font-weight: 500; 131 | font-family: inherit; 132 | cursor: pointer; 133 | background-color: #1a1a1a; 134 | border-color: #9c64ff; 135 | transition: border-color 0.2s, color 0.2s; 136 | } 137 | 138 | .container a.active { 139 | border-color: #fff !important; 140 | border-bottom-color: #fff !important; 141 | } 142 | 143 | .count-container a:last-of-type { 144 | border-color: transparent; 145 | } 146 | 147 | .container a::before { 148 | content: ''; 149 | position: absolute; 150 | } 151 | 152 | .options-container { 153 | position: absolute; 154 | bottom: 0; 155 | right: 142px; 156 | } 157 | 158 | .active { 159 | color: white; 160 | pointer-events: none; 161 | } 162 | 163 | a.active { 164 | border-bottom: 1px solid transparent; 165 | } 166 | 167 | .particle { 168 | position: absolute; 169 | border: 1px solid white; 170 | border-radius: 50%; 171 | } 172 | 173 | .particle.fill { 174 | background-color: white; 175 | border: 1px solid black; 176 | } 177 | 178 | .particle.sprite { 179 | border: initial; 180 | border-radius: initial; 181 | } 182 | 183 | canvas, 184 | .canvas { 185 | display: inline-block; 186 | position: relative; 187 | overflow: hidden; 188 | width: 1024px; 189 | height: 480px; 190 | background-color: #1a1a1a; 191 | border-radius: 0.5rem; 192 | transform: translateZ(0); 193 | pointer-events: none; 194 | } 195 | 196 | button { 197 | border-radius: 8px; 198 | border: 1px solid transparent; 199 | padding: 0.6em 1.2em; 200 | font-size: 1em; 201 | font-weight: 500; 202 | font-family: inherit; 203 | background-color: #1a1a1a; 204 | cursor: pointer; 205 | transition: border-color 0.25s; 206 | } 207 | 208 | button:hover { 209 | border-color: #9c64ff; 210 | } 211 | 212 | button:focus, 213 | button:focus-visible { 214 | outline: 4px auto -webkit-focus-ring-color; 215 | } -------------------------------------------------------------------------------- /src/scripts/babylon.js: -------------------------------------------------------------------------------- 1 | import * as BABYLON from 'babylonjs'; 2 | import Engine from './engine.js'; 3 | 4 | class BabylonEngine extends Engine { 5 | init() { 6 | super.init(); 7 | 8 | // Clear the canvas 9 | this.canvas.innerHTML = ''; 10 | window.cancelAnimationFrame(this.request); 11 | 12 | // Load the 3D engine 13 | if (this.engine) this.engine.stopRenderLoop(); 14 | this.engine = new BABYLON.Engine(this.canvas, true, { 15 | preserveDrawingBuffer: true, 16 | stencil: true, 17 | }); 18 | // Create a basic BJS Scene object 19 | this.scene = new BABYLON.Scene(this.engine); 20 | this.scene.clearColor = new BABYLON.Color3(0.098, 0.098, 0.098); 21 | // Create a FreeCamera, and set its position to {x: 0, y: 5, z: -10} 22 | const camera = new BABYLON.FreeCamera( 23 | 'camera1', 24 | new BABYLON.Vector3(0, 1, 0), 25 | this.scene 26 | ); 27 | // We chose an orthographic view to simplify at most our mesh creation 28 | camera.mode = BABYLON.Camera.ORTHOGRAPHIC_CAMERA; 29 | // Setup the camera to fit with our canvas coordinates 30 | camera.orthoLeft = 0; 31 | camera.orthoTop = 0; 32 | camera.orthoRight = this.width; 33 | camera.orthoBottom = this.height; 34 | camera.fov = 0.9; 35 | // Target the camera to scene origin 36 | camera.setTarget(BABYLON.Vector3.Zero()); 37 | 38 | const light0 = new BABYLON.HemisphericLight( 39 | 'Hemi0', 40 | new BABYLON.Vector3(0, 0, 0), 41 | this.scene 42 | ); 43 | light0.diffuse = new BABYLON.Color3(1, 1, 1); 44 | light0.specular = new BABYLON.Color3(1, 1, 1); 45 | light0.groundColor = new BABYLON.Color3(1, 1, 1); 46 | 47 | const optimizerOptions = new BABYLON.SceneOptimizerOptions(60, 500); 48 | optimizerOptions.addOptimization( 49 | new BABYLON.HardwareScalingOptimization(0, 1) 50 | ); 51 | // Optimizer 52 | const optimizer = new BABYLON.SceneOptimizer(this.scene, optimizerOptions); 53 | 54 | // Spritemanager 55 | const spriteManager = new BABYLON.SpriteManager( 56 | 'textures', 57 | 'sprite.png', 58 | 1000000, 59 | { width: 64, height: 64 }, 60 | this.scene 61 | ); 62 | 63 | // Particle creation 64 | const particles = new Array(this.count); 65 | const rnd = [1, -1]; 66 | const circles = {}; 67 | for (let i = 0; i < this.count; i++) { 68 | const size = 10 + Math.random() * 80; 69 | const x = Math.random() * this.width; 70 | const y = Math.random() * (this.height - size); 71 | const [dx, dy] = [ 72 | 3 * Math.random() * rnd[Math.floor(Math.random() * 2)], 73 | 3 * Math.random() * rnd[Math.floor(Math.random() * 2)], 74 | ]; 75 | 76 | let particle; 77 | let filled; 78 | if (this.type === 'sprite') { 79 | particle = new BABYLON.Sprite('sprite', spriteManager); 80 | particle.width = 64; 81 | particle.height = 64; 82 | particle.angle = Math.PI; 83 | particle.invertU = true; 84 | particle.position.x = -x; 85 | particle.position.z = -y; 86 | particle.position.y = -i; 87 | } else { 88 | // Create circles by drawing lines in a 360 degree radius 89 | let points = []; 90 | if (circles[size]) { 91 | points = circles[size]; 92 | } else { 93 | const radius = size; 94 | for (let i = -Math.PI; i <= Math.PI; i += Math.PI / 360) { 95 | points.push( 96 | new BABYLON.Vector3(radius * Math.cos(i), 0, radius * Math.sin(i)) 97 | ); 98 | } 99 | circles[size] = points; 100 | } 101 | particle = BABYLON.MeshBuilder.CreateLines( 102 | 'circle', 103 | { points: points, updatable: false }, 104 | this.scene 105 | ); 106 | particle.color = new BABYLON.Color3.White(); 107 | particle.position.x = -x; 108 | particle.position.z = -y; 109 | particle.position.y = -i - 1; 110 | if (this.type === 'fill') { 111 | const mat = new BABYLON.StandardMaterial('mat1', this.scene); 112 | mat.alpha = 1; 113 | mat.diffuseColor = new BABYLON.Color3(1, 1, 1); 114 | mat.emissiveColor = new BABYLON.Color3.White(); 115 | mat.backFaceCulling = false; 116 | filled = BABYLON.MeshBuilder.CreateRibbon( 117 | 'filled_circle', 118 | { 119 | pathArray: [points], 120 | closePath: true, 121 | }, 122 | this.scene 123 | ); 124 | filled.color = BABYLON.Color3.White(); 125 | filled.material = mat; 126 | filled.position.x = -x; 127 | filled.position.z = -y; 128 | filled.position.y = -i; 129 | particle.color = new BABYLON.Color3.Black(); 130 | } 131 | } 132 | 133 | particles[i] = { x, y, size: size, dx, dy, el: [particle, filled] }; 134 | } 135 | this.particles = particles; 136 | } 137 | render() { 138 | this.engine.runRenderLoop(() => { 139 | // Particle animation 140 | const particles = this.particles; 141 | for (let i = 0; i < this.count; i++) { 142 | const r = particles[i]; 143 | r.x -= r.dx; 144 | r.y -= r.dy; 145 | if (r.x + r.size < 0) r.dx *= -1; 146 | else if (r.y + r.size < 0) r.dy *= -1; 147 | if (r.x > this.width) r.dx *= -1; 148 | else if (r.y > this.height) r.dy *= -1; 149 | r.el[0].position.x = -r.x; 150 | r.el[0].position.z = -r.y; 151 | if (r.el[1]) { 152 | r.el[1].position.x = -r.x; 153 | r.el[1].position.z = -r.y; 154 | } 155 | } 156 | this.scene.render(); 157 | this.fpsmeter.tick(); 158 | }); 159 | } 160 | } 161 | 162 | document.addEventListener('DOMContentLoaded', () => { 163 | const engine = new BabylonEngine(); 164 | engine.render(); 165 | }); 166 | -------------------------------------------------------------------------------- /src/scripts/canvas.js: -------------------------------------------------------------------------------- 1 | import Engine from './engine.js'; 2 | 3 | class CanvasEngine extends Engine { 4 | init() { 5 | super.init(); 6 | 7 | // Clear the canvas 8 | this.canvas.innerHTML = ''; 9 | window.cancelAnimationFrame(this.request); 10 | 11 | if (this.type === 'sprite') { 12 | this.sprite = new Image(); 13 | this.sprite.src = 'sprite.png'; 14 | } 15 | 16 | // Particle creation 17 | const particles = new Array(this.count); 18 | const rnd = [1, -1]; 19 | for (let i = 0; i < this.count; i++) { 20 | const size = 10 + Math.random() * 80; 21 | const x = Math.random() * this.width; 22 | const y = Math.random() * (this.height - size); 23 | const [dx, dy] = [ 24 | 3 * Math.random() * rnd[Math.floor(Math.random() * 2)], 25 | 3 * Math.random() * rnd[Math.floor(Math.random() * 2)], 26 | ]; 27 | particles[i] = { x, y, size: size, dx, dy }; 28 | } 29 | this.particles = particles; 30 | 31 | this.ctx = this.canvas.getContext('2d'); 32 | this.ctx.strokeStyle = this.type === 'stroke' ? 'white' : 'black'; 33 | if (this.type === 'fill') this.ctx.fillStyle = 'white'; 34 | this.ctx.lineWidth = 1; 35 | } 36 | render() { 37 | // Clear the canvas 38 | this.ctx.clearRect(0, 0, this.width, this.height); 39 | 40 | // Particle animation 41 | const particles = this.particles; 42 | for (let i = 0; i < this.count; i++) { 43 | const r = particles[i]; 44 | r.x -= r.dx; 45 | r.y -= r.dy; 46 | if (r.x + r.size < 0) r.dx *= -1; 47 | else if (r.y + r.size < 0) r.dy *= -1; 48 | if (r.x > this.width) r.dx *= -1; 49 | else if (r.y > this.height) r.dy *= -1; 50 | if (this.type === 'sprite') { 51 | if (this.sprite.complete) { 52 | this.ctx.beginPath(); 53 | this.ctx.drawImage(this.sprite, r.x, r.y); 54 | } 55 | } else { 56 | this.ctx.beginPath(); 57 | this.ctx.arc(r.x, r.y, r.size, 0, 2 * Math.PI); 58 | if (this.type === 'fill') this.ctx.fill(); 59 | if (this.type !== 'sprite') this.ctx.stroke(); 60 | } 61 | } 62 | 63 | this.fpsmeter.tick(); 64 | this.request = window.requestAnimationFrame(() => this.render()); 65 | } 66 | } 67 | 68 | document.addEventListener('DOMContentLoaded', () => { 69 | const engine = new CanvasEngine(); 70 | engine.render(); 71 | }); 72 | -------------------------------------------------------------------------------- /src/scripts/dom.js: -------------------------------------------------------------------------------- 1 | import Engine from './engine.js'; 2 | 3 | class DOMEngine extends Engine { 4 | init() { 5 | super.init(); 6 | 7 | // Clear the canvas 8 | this.canvas.innerHTML = ''; 9 | window.cancelAnimationFrame(this.request); 10 | 11 | // Particle creation 12 | const particles = new Array(this.count); 13 | const rnd = [1, -1]; 14 | for (let i = 0; i < this.count; i++) { 15 | const size = 10 + Math.random() * 80 * 2; 16 | const x = Math.random() * this.width; 17 | const y = Math.random() * (this.height - size); 18 | const [dx, dy] = [ 19 | 3 * Math.random() * rnd[Math.floor(Math.random() * 2)], 20 | 3 * Math.random() * rnd[Math.floor(Math.random() * 2)], 21 | ]; 22 | 23 | let particle; 24 | if (this.type === 'sprite') { 25 | particle = document.createElement('img'); 26 | particle.src = 'sprite.png'; 27 | particle.className = 'particle sprite'; 28 | } else { 29 | particle = document.createElement('div'); 30 | if (this.type === 'stroke') particle.className = 'particle'; 31 | else if (this.type === 'fill') particle.className = 'particle fill'; 32 | particle.style.width = `${size}px`; 33 | particle.style.height = `${size}px`; 34 | } 35 | particle.style.left = `${x}px`; 36 | particle.style.top = `${y}px`; 37 | this.canvas.appendChild(particle); 38 | particles[i] = { x, y, size: size, dx, dy, el: particle }; 39 | } 40 | this.particles = particles; 41 | } 42 | render() { 43 | // Particle animation 44 | const particles = this.particles; 45 | for (let i = 0; i < this.count; i++) { 46 | const r = particles[i]; 47 | r.x -= r.dx; 48 | r.y -= r.dy; 49 | if (r.x + r.size < 0) r.dx *= -1; 50 | else if (r.y + r.size < 0) r.dy *= -1; 51 | if (r.x > this.width) r.dx *= -1; 52 | else if (r.y > this.height) r.dy *= -1; 53 | r.el.style.left = `${r.x}px`; 54 | r.el.style.top = `${r.y}px`; 55 | } 56 | 57 | this.fpsmeter.tick(); 58 | this.request = window.requestAnimationFrame(() => this.render()); 59 | } 60 | } 61 | 62 | document.addEventListener('DOMContentLoaded', () => { 63 | const engine = new DOMEngine(); 64 | engine.render(); 65 | }); 66 | -------------------------------------------------------------------------------- /src/scripts/engine.js: -------------------------------------------------------------------------------- 1 | import 'fpsmeter'; 2 | 3 | const getCount = (search) => 4 | search 5 | .substring(1) 6 | .split('&') 7 | .filter((s) => s.startsWith('count=')) 8 | .map((s) => Number.parseInt(s.split('=')[1]))[0]; 9 | 10 | const getType = (search) => 11 | search 12 | .substring(1) 13 | .split('&') 14 | .filter((s) => s.startsWith('type=')) 15 | .map((s) => s.split('=')[1])[0]; 16 | 17 | class Engine { 18 | constructor() { 19 | this.canvas = document.querySelector('#canvas'); 20 | this.fpsContainer = document.querySelector('.fps-container'); 21 | this.countLinks = document.querySelectorAll('.count-container a'); 22 | this.typeLinks = document.querySelectorAll('.options-container a'); 23 | canvas.width = 1024; 24 | canvas.height = 480; 25 | this.width = this.canvas.width; 26 | this.height = this.canvas.height; 27 | this.count = 0; 28 | 29 | this.initCountLink(); 30 | this.initTypeLink(); 31 | this.init(); 32 | } 33 | 34 | initFpsmeter() { 35 | this.fpsContainer.innerHTML = ''; 36 | this.fpsmeter = new window.FPSMeter(this.fpsContainer, { 37 | top: 0, 38 | left: 0, 39 | heat: 1, 40 | theme: 'dark', 41 | graph: 1, 42 | history: 25, 43 | transform: 'translateY(-50%)', 44 | }); 45 | } 46 | 47 | initCountLink() { 48 | const toggleCountLinks = (count) => { 49 | for (const link of this.countLinks) { 50 | link.classList.toggle('active', false); 51 | } 52 | const link = [...this.countLinks].filter((l) => Number.parseInt(l.innerText) === count)[0]; 53 | if (link) { 54 | link.classList.toggle('active', true); 55 | this.count = count; 56 | } else 57 | this.countLinks[this.countLinks.length - 1].classList.toggle( 58 | 'active', 59 | true 60 | ); 61 | }; 62 | const { search, pathname } = window.location; 63 | const count = getCount(search); 64 | this.count = count || 1000; 65 | toggleCountLinks(this.count); 66 | this.countLinks.forEach((link, _index) => { 67 | link.addEventListener('click', (event) => { 68 | event.preventDefault(); 69 | event.stopPropagation(); 70 | const count = Number.parseInt(link.innerText); 71 | const type = getType(search) || this.type; 72 | if (count) { 73 | if (type) 74 | window.history.replaceState( 75 | {}, 76 | pathname, 77 | `?count=${count}&type=${type}` 78 | ); 79 | else window.history.replaceState({}, pathname, `?count=${count}`); 80 | toggleCountLinks(count); 81 | this.init(); 82 | this.render(); 83 | } 84 | }); 85 | }); 86 | } 87 | 88 | initTypeLink() { 89 | const toggleTypeLinks = (type) => { 90 | for (const link of this.typeLinks) { 91 | link.classList.toggle('active', false); 92 | } 93 | const link = [...this.typeLinks].filter((l) => l.innerText.toLowerCase() === type)[0]; 94 | if (link) { 95 | link.classList.toggle('active', true); 96 | this.type = type; 97 | } else 98 | this.typeLinks[this.typeLinks.length - 1].classList.toggle( 99 | 'active', 100 | true 101 | ); 102 | }; 103 | const { search, pathname } = window.location; 104 | const type = getType(search); 105 | this.type = ['stroke', 'fill', 'sprite'].includes(type) 106 | ? type 107 | : null || 'stroke'; 108 | toggleTypeLinks(this.type); 109 | this.typeLinks.forEach((link, _index) => { 110 | link.addEventListener('click', (event) => { 111 | event.preventDefault(); 112 | event.stopPropagation(); 113 | const type = link.innerText.toLowerCase(); 114 | const count = getCount(search) || this.count; 115 | if (type) { 116 | if (count) 117 | window.history.replaceState( 118 | {}, 119 | pathname, 120 | `?count=${count}&type=${type}` 121 | ); 122 | else window.history.replaceState({}, pathname, `?type=${type}`); 123 | toggleTypeLinks(type); 124 | this.init(); 125 | this.render(); 126 | } 127 | }); 128 | }); 129 | } 130 | 131 | initNavLink() { 132 | const navLinks = document.querySelectorAll('header > nav > a'); 133 | const { search, pathname } = window.location; 134 | 135 | for (const ml of [...navLinks]) { 136 | if (ml.pathname === pathname) ml.classList.add('active'); 137 | ml.addEventListener('click', (event) => { 138 | event.preventDefault(); 139 | event.stopPropagation(); 140 | const count = getCount(search); 141 | const type = getType(search); 142 | let href = count ? `${ml.pathname}?count=${count}` : ml.pathname; 143 | if (type) href += `${count ? '&' : '?'}type=${type}`; 144 | window.location.href = href; 145 | }); 146 | } 147 | } 148 | 149 | init() { 150 | this.initFpsmeter(); 151 | this.initNavLink(); 152 | } 153 | 154 | render() { } 155 | } 156 | 157 | export default Engine; 158 | -------------------------------------------------------------------------------- /src/scripts/excalibur.js: -------------------------------------------------------------------------------- 1 | import * as ex from 'excalibur'; 2 | import Engine from './engine.js'; 3 | 4 | export class Scene extends ex.Scene { 5 | onActivate(ctx) { 6 | const engine = ctx.engine; 7 | this.engine = ctx.data.engine; 8 | 9 | this.clear(); 10 | 11 | // Particle creation 12 | const particles = new Array(this.engine.count); 13 | const rnd = [1, -1]; 14 | const spriteImage = new ex.ImageSource('sprite.png'); 15 | spriteImage.load(); 16 | for (let i = 0; i < this.engine.count; i++) { 17 | const size = 10 + Math.random() * 80; 18 | const x = Math.random() * this.engine.width; 19 | const y = Math.random() * (this.engine.height - size); 20 | const [dx, dy] = [ 21 | 3 * Math.random() * rnd[Math.floor(Math.random() * 2)], 22 | 3 * Math.random() * rnd[Math.floor(Math.random() * 2)], 23 | ]; 24 | 25 | const particle = new ex.Actor({ 26 | x: x, 27 | y: y, 28 | radius: size, 29 | }); 30 | 31 | if (this.engine.type === 'sprite') { 32 | const sprite = ex.Sprite.from(spriteImage); 33 | particle.graphics.use(sprite); 34 | } else { 35 | const circle = new ex.Circle({ 36 | x: x, 37 | y: y, 38 | radius: size, 39 | color: 40 | this.engine.type === 'stroke' 41 | ? ex.Color.Transparent 42 | : ex.Color.fromRGB(255, 255, 255), 43 | strokeColor: 44 | this.engine.type === 'stroke' 45 | ? ex.Color.fromRGB(255, 255, 255) 46 | : ex.Color.fromRGB(0, 0, 0), 47 | strokeWidth: this.engine.type === 'stroke' ? 1 : undefined, 48 | }); 49 | particle.graphics.use(circle); 50 | } 51 | this.add(particle); 52 | 53 | particles[i] = { x, y, size: size, dx, dy, el: particle }; 54 | 55 | particle.on("postupdate", () => { 56 | const r = particles[i]; 57 | r.x -= r.dx; 58 | r.y -= r.dy; 59 | if (r.x + r.size < 0) r.dx *= -1; 60 | else if (r.y + r.size < 0) r.dy *= -1; 61 | if (r.x > engine.screen.drawWidth) r.dx *= -1; 62 | else if (r.y > engine.screen.drawHeight) r.dy *= -1; 63 | r.el.pos.x = r.x; 64 | r.el.pos.y = r.y; 65 | }); 66 | } 67 | } 68 | onPostUpdate() { 69 | this.engine.fpsmeter.tick(); 70 | } 71 | } 72 | 73 | class ExcaliburEngine extends Engine { 74 | init() { 75 | super.init(); 76 | 77 | window.cancelAnimationFrame(this.request); 78 | if (this.game) { 79 | this.game.dispose(); 80 | this.canvas = document.createElement("canvas"); 81 | this.canvas.id = "canvas"; 82 | this.canvas.className = "canvas"; 83 | this.canvas.width = this.width; 84 | this.canvas.height = this.height; 85 | document.querySelector("main").appendChild(this.canvas); 86 | } 87 | 88 | const game = new ex.Engine({ 89 | width: this.width, 90 | height: this.height, 91 | canvasElement: this.canvas, 92 | backgroundColor: ex.Color.fromRGB(26, 26, 26), 93 | scenes: { scene: Scene } 94 | }); 95 | this.game = game; 96 | } 97 | render() { 98 | this.game.start().then(() => { 99 | this.game.goToScene('scene', { sceneActivationData: { engine: this } }); 100 | }); 101 | } 102 | } 103 | 104 | document.addEventListener('DOMContentLoaded', () => { 105 | const engine = new ExcaliburEngine(); 106 | engine.render(); 107 | }); 108 | -------------------------------------------------------------------------------- /src/scripts/hilo.js: -------------------------------------------------------------------------------- 1 | import Hilo from 'hilojs'; 2 | import Engine from './engine.js'; 3 | 4 | class HiloEngine extends Engine { 5 | init() { 6 | const GraphicScene = Hilo.Class.create({ 7 | Extends: Hilo.Container, 8 | constructor: function (properties) { 9 | GraphicScene.superclass.constructor.call(this, properties); 10 | this.init(properties); 11 | }, 12 | init: (_properties) => { 13 | return; 14 | }, 15 | onUpdate: function (_properties) { 16 | if (!this.engine) return; 17 | // Particle animation 18 | const particles = this.engine.particles; 19 | for (let i = 0; i < this.engine.count; i++) { 20 | const r = particles[i]; 21 | r.x -= r.dx; 22 | r.y -= r.dy; 23 | if (r.x + r.size < 0) r.dx *= -1; 24 | else if (r.y + r.size < 0) r.dy *= -1; 25 | if (r.x > this.engine.width) r.dx *= -1; 26 | else if (r.y > this.engine.height) r.dy *= -1; 27 | r.el.x = r.x; 28 | r.el.y = r.y; 29 | } 30 | 31 | this.engine.fpsmeter.tick(); 32 | }, 33 | loadEngine: function (engine) { 34 | this.engine = engine; 35 | }, 36 | }); 37 | 38 | super.init(); 39 | 40 | if (this.ticker) this.ticker.stop(); 41 | 42 | // Clear the canvas 43 | this.canvas.innerHTML = ''; 44 | window.cancelAnimationFrame(this.request); 45 | const main = document.querySelector('main'); 46 | main.removeChild(main.lastElementChild); 47 | 48 | // Init stage and scene 49 | this.stage = new Hilo.Stage({ 50 | renderType: 'canvas', 51 | container: main, 52 | width: this.width, 53 | height: this.height, 54 | }); 55 | this.graphicScene = new GraphicScene({ 56 | width: this.width, 57 | height: this.height, 58 | engine: this, 59 | }).addTo(this.stage); 60 | this.graphicScene.loadEngine(this); 61 | 62 | // Particle creation 63 | const particles = new Array(this.count); 64 | const rnd = [1, -1]; 65 | for (let i = 0; i < this.count; i++) { 66 | const size = 10 + Math.random() * 80; 67 | const x = Math.random() * this.width; 68 | const y = Math.random() * (this.height - size); 69 | const [dx, dy] = [ 70 | 3 * Math.random() * rnd[Math.floor(Math.random() * 2)], 71 | 3 * Math.random() * rnd[Math.floor(Math.random() * 2)], 72 | ]; 73 | let particle; 74 | if (this.type === 'sprite') { 75 | particle = new Hilo.Bitmap({ 76 | image: 'sprite.png', 77 | }).addTo(this.graphicScene); 78 | } else { 79 | particle = new Hilo.Graphics({ 80 | width: size * 2, 81 | height: size * 2, 82 | x: x, 83 | y: y, 84 | }); 85 | if (this.type === 'stroke') 86 | particle 87 | .lineStyle(1, '#ffffff') 88 | .drawCircle(1, 1, size - 1) 89 | .closePath() 90 | .endFill() 91 | .addTo(this.graphicScene); 92 | else if (this.type === 'fill') 93 | particle 94 | .beginFill('#fff') 95 | .lineStyle(1, '#000000') 96 | .drawCircle(1, 1, size - 1) 97 | .closePath() 98 | .endFill() 99 | .addTo(this.graphicScene); 100 | } 101 | particles[i] = { x, y, size: size, dx, dy, el: particle }; 102 | } 103 | this.particles = particles; 104 | } 105 | render() { 106 | // Start game loop 107 | this.ticker = new Hilo.Ticker(60); 108 | this.ticker.addTick(this.stage); 109 | this.ticker.start(); 110 | } 111 | } 112 | 113 | document.addEventListener('DOMContentLoaded', () => { 114 | const engine = new HiloEngine(); 115 | engine.render(); 116 | }); 117 | -------------------------------------------------------------------------------- /src/scripts/kaboom.js: -------------------------------------------------------------------------------- 1 | import kaboom from 'kaboom'; 2 | import Engine from './engine.js'; 3 | 4 | class KaboomEngine extends Engine { 5 | init() { 6 | super.init(); 7 | 8 | const k = kaboom({ 9 | background: [26, 26, 26], 10 | global: false, 11 | canvas: this.canvas, 12 | width: this.width, 13 | height: this.height, 14 | }); 15 | k.loadSprite('sprite', 'sprite.png'); 16 | this.k = k; 17 | 18 | // Clear the canvas 19 | this.canvas.innerHTML = ''; 20 | window.cancelAnimationFrame(this.request); 21 | 22 | // Particle creation 23 | const particles = new Array(this.count); 24 | const rnd = [1, -1]; 25 | for (let i = 0; i < this.count; i++) { 26 | const size = 10 + Math.random() * 80; 27 | const x = Math.random() * this.width; 28 | const y = Math.random() * (this.height - size); 29 | const [dx, dy] = [ 30 | 3 * Math.random() * rnd[Math.floor(Math.random() * 2)], 31 | 3 * Math.random() * rnd[Math.floor(Math.random() * 2)], 32 | ]; 33 | let particle; 34 | if (this.type === 'sprite') { 35 | particle = k.add([k.sprite('sprite'), k.pos(x, y)]); 36 | } else { 37 | particle = k.add([ 38 | k.pos(x, y), 39 | k.circle(size), 40 | k.opacity(this.type === 'stroke' ? 0 : 1), 41 | k.outline( 42 | 1, 43 | this.type === 'stroke' ? k.rgb(255, 255, 255) : k.rgb(0, 0, 0) 44 | ), 45 | ]); 46 | } 47 | 48 | particles[i] = { x, y, size: size, dx, dy, el: particle }; 49 | } 50 | this.particles = particles; 51 | } 52 | render() { 53 | const k = this.k; 54 | k.onUpdate(() => { 55 | // Particle animation 56 | const particles = this.particles; 57 | for (let i = 0; i < this.count; i++) { 58 | const r = particles[i]; 59 | r.x -= r.dx; 60 | r.y -= r.dy; 61 | if (r.x + r.size < 0) r.dx *= -1; 62 | else if (r.y + r.size < 0) r.dy *= -1; 63 | if (r.x > this.width) r.dx *= -1; 64 | else if (r.y > this.height) r.dy *= -1; 65 | r.el.pos.x = r.x; 66 | r.el.pos.y = r.y; 67 | } 68 | this.fpsmeter.tick(); 69 | }); 70 | } 71 | } 72 | 73 | document.addEventListener('DOMContentLoaded', () => { 74 | const engine = new KaboomEngine(); 75 | engine.render(); 76 | }); 77 | -------------------------------------------------------------------------------- /src/scripts/kaplay.js: -------------------------------------------------------------------------------- 1 | import kaplay from 'kaplay'; 2 | import Engine from './engine.js'; 3 | 4 | class KaplayEngine extends Engine { 5 | init() { 6 | super.init(); 7 | 8 | const k = kaplay({ 9 | background: [26, 26, 26], 10 | global: false, 11 | canvas: this.canvas, 12 | width: this.width, 13 | height: this.height, 14 | }); 15 | k.loadSprite('sprite', 'sprite.png'); 16 | this.k = k; 17 | 18 | // Clear the canvas 19 | this.canvas.innerHTML = ''; 20 | window.cancelAnimationFrame(this.request); 21 | 22 | // Particle creation 23 | const particles = new Array(this.count); 24 | const rnd = [1, -1]; 25 | for (let i = 0; i < this.count; i++) { 26 | const size = 10 + Math.random() * 80; 27 | const x = Math.random() * this.width; 28 | const y = Math.random() * (this.height - size); 29 | const [dx, dy] = [ 30 | 3 * Math.random() * rnd[Math.floor(Math.random() * 2)], 31 | 3 * Math.random() * rnd[Math.floor(Math.random() * 2)], 32 | ]; 33 | let particle; 34 | if (this.type === 'sprite') { 35 | particle = k.add([k.sprite('sprite'), k.pos(x, y)]); 36 | } else { 37 | particle = k.add([ 38 | k.pos(x, y), 39 | k.circle(size), 40 | k.opacity(this.type === 'stroke' ? 0 : 1), 41 | k.outline( 42 | 1, 43 | this.type === 'stroke' ? k.rgb(255, 255, 255) : k.rgb(0, 0, 0) 44 | ), 45 | ]); 46 | } 47 | 48 | particles[i] = { x, y, size: size, dx, dy, el: particle }; 49 | } 50 | this.particles = particles; 51 | } 52 | render() { 53 | const k = this.k; 54 | k.onUpdate(() => { 55 | // Particle animation 56 | const particles = this.particles; 57 | for (let i = 0; i < this.count; i++) { 58 | const r = particles[i]; 59 | r.x -= r.dx; 60 | r.y -= r.dy; 61 | if (r.x + r.size < 0) r.dx *= -1; 62 | else if (r.y + r.size < 0) r.dy *= -1; 63 | if (r.x > this.width) r.dx *= -1; 64 | else if (r.y > this.height) r.dy *= -1; 65 | r.el.pos.x = r.x; 66 | r.el.pos.y = r.y; 67 | } 68 | this.fpsmeter.tick(); 69 | }); 70 | } 71 | } 72 | 73 | document.addEventListener('DOMContentLoaded', () => { 74 | const engine = new KaplayEngine(); 75 | engine.render(); 76 | }); 77 | -------------------------------------------------------------------------------- /src/scripts/kontra.js: -------------------------------------------------------------------------------- 1 | import { init, GameLoop, Sprite } from 'kontra'; 2 | import Engine from './engine.js'; 3 | 4 | class KontraEngine extends Engine { 5 | init() { 6 | super.init(); 7 | 8 | // Clear the canvas 9 | this.canvas.innerHTML = ''; 10 | window.cancelAnimationFrame(this.request); 11 | 12 | if (!this.loop) { 13 | const { context } = init(); 14 | this.ctx = context; 15 | this.ctx.strokeStyle = 'white'; 16 | this.ctx.lineWidth = 1; 17 | } 18 | if (this.type === 'fill') { 19 | this.ctx.strokeStyle = 'black'; 20 | this.ctx.fillStyle = 'white'; 21 | } 22 | const image = new Image(); 23 | image.src = 'sprite.png'; 24 | 25 | // Particle creation 26 | const particles = new Array(this.count); 27 | const rnd = [1, -1]; 28 | for (let i = 0; i < this.count; i++) { 29 | const size = 10 + Math.random() * 80; 30 | const x = Math.random() * this.width; 31 | const y = Math.random() * (this.height - size); 32 | const [dx, dy] = [ 33 | 3 * Math.random() * rnd[Math.floor(Math.random() * 2)], 34 | 3 * Math.random() * rnd[Math.floor(Math.random() * 2)], 35 | ]; 36 | let sprite; 37 | if (this.type === 'sprite') { 38 | sprite = Sprite({ 39 | x: x, 40 | y: y, 41 | anchor: { x: 0.5, y: 0.5 }, 42 | image: image, 43 | }); 44 | } 45 | particles[i] = { x, y, size: size, dx, dy, el: sprite }; 46 | } 47 | this.particles = particles; 48 | } 49 | render() { 50 | if (this.loop) return; 51 | this.loop = GameLoop({ 52 | update: () => { 53 | // Particle animation 54 | for (let i = 0; i < this.count; i++) { 55 | const r = this.particles[i]; 56 | r.x -= r.dx; 57 | r.y -= r.dy; 58 | if (r.x + r.size < 0) r.dx *= -1; 59 | else if (r.y + r.size < 0) r.dy *= -1; 60 | if (r.x > this.width) r.dx *= -1; 61 | else if (r.y > this.height) r.dy *= -1; 62 | if (r.el) { 63 | r.el.x = r.x; 64 | r.el.y = r.y; 65 | } 66 | } 67 | }, 68 | render: () => { 69 | for (let i = 0; i < this.count; i++) { 70 | const r = this.particles[i]; 71 | if (this.type === 'sprite') { 72 | r.el.render(); 73 | } else { 74 | this.ctx.beginPath(); 75 | this.ctx.arc(r.x, r.y, r.size, 0, 2 * Math.PI); 76 | if (this.type === 'fill') this.ctx.fill(); 77 | this.ctx.stroke(); 78 | } 79 | } 80 | this.fpsmeter.tick(); 81 | }, 82 | }); 83 | this.loop.start(); 84 | } 85 | } 86 | 87 | document.addEventListener('DOMContentLoaded', () => { 88 | const engine = new KontraEngine(); 89 | engine.render(); 90 | }); 91 | -------------------------------------------------------------------------------- /src/scripts/melon.js: -------------------------------------------------------------------------------- 1 | import * as me from 'melonjs'; 2 | import Engine from './engine.js'; 3 | 4 | class Graphics extends me.Renderable { 5 | constructor(engine) { 6 | super(0, 0, engine.width, engine.heigth); 7 | this.engine = engine; 8 | this.anchorPoint.set(0, 0); 9 | me.state.change(me.state.DEFAULT, true); 10 | 11 | // Particle creation 12 | const particles = new Array(engine.count); 13 | const rnd = [1, -1]; 14 | for (let i = 0; i < engine.count; i++) { 15 | const size = 10 + Math.random() * 80; 16 | const x = Math.random() * engine.width; 17 | const y = Math.random() * (engine.height - size); 18 | const [dx, dy] = [ 19 | 3 * Math.random() * rnd[Math.floor(Math.random() * 2)], 20 | 3 * Math.random() * rnd[Math.floor(Math.random() * 2)], 21 | ]; 22 | let sprite; 23 | if (engine.type === 'sprite') { 24 | sprite = new me.Sprite(x, y, { 25 | image: '/sprite.png', 26 | framewidth: 64, 27 | frameheight: 64, 28 | anchorPoint: new me.Vector2d(0.5, 0.5), 29 | }); 30 | me.game.world.addChild(sprite); 31 | } 32 | particles[i] = { x, y, size: size, dx, dy, el: sprite }; 33 | } 34 | engine.particles = particles; 35 | } 36 | update() { 37 | return true; 38 | } 39 | draw(renderer) { 40 | // Particle animation 41 | const particles = this.engine.particles; 42 | for (let i = 0; i < this.engine.count; i++) { 43 | const r = particles[i]; 44 | r.x -= r.dx; 45 | r.y -= r.dy; 46 | if (r.x + r.size < 0) r.dx *= -1; 47 | else if (r.y + r.size < 0) r.dy *= -1; 48 | if (r.x > this.engine.width) r.dx *= -1; 49 | else if (r.y > this.engine.height) r.dy *= -1; 50 | if (this.engine.type === 'stroke') { 51 | renderer.setColor('#ffffff'); 52 | renderer.strokeArc(r.x, r.y, r.size, 0, Math.PI * 2); 53 | } else if (this.engine.type === 'fill') { 54 | renderer.setColor('#ffffff'); 55 | renderer.fillArc(r.x, r.y, r.size, 0, Math.PI * 2, false); 56 | renderer.setColor('#000000'); 57 | renderer.strokeArc(r.x, r.y, r.size, 0, Math.PI * 2, false); 58 | } else if (this.engine.type === 'sprite' && r.el) { 59 | r.el.pos.set(r.x, r.y); 60 | } 61 | } 62 | this.engine.fpsmeter.tick(); 63 | } 64 | } 65 | 66 | class MelonEngine extends Engine { 67 | init() { 68 | super.init(); 69 | 70 | // Clear the canvas 71 | this.canvas.innerHTML = ''; 72 | window.cancelAnimationFrame(this.request); 73 | 74 | // Create if new, else reset/empty the game world 75 | this.textureLoaded = false; 76 | if (me.game.world) { 77 | me.game.world.reset(); 78 | } else { 79 | const main = document.querySelector('main'); 80 | main.removeChild(main.lastElementChild); 81 | me.video.init(this.width, this.height, { 82 | parent: 'main', 83 | renderer: me.video.AUTO, 84 | preferWebGL1: false, 85 | subPixel: false, 86 | }); 87 | me.game.world.backgroundColor.setColor('#1a1a1a'); 88 | } 89 | me.loader.preload( 90 | [ 91 | { 92 | name: 'sprite', 93 | type: 'image', 94 | src: 'sprite.png', 95 | }, 96 | ], 97 | () => { 98 | this.textureLoaded = true; 99 | this.render(); 100 | } 101 | ); 102 | } 103 | render() { 104 | if (this.textureLoaded) me.game.world.addChild(new Graphics(this)); 105 | } 106 | } 107 | 108 | document.addEventListener('DOMContentLoaded', () => { 109 | const engine = new MelonEngine(); 110 | engine.render(); 111 | }); 112 | -------------------------------------------------------------------------------- /src/scripts/phaser.js: -------------------------------------------------------------------------------- 1 | import * as Phaser from 'phaser'; 2 | import Engine from './engine.js'; 3 | 4 | const scene = (engine) => { 5 | return { 6 | preload() { 7 | this.load.image('sprite', 'sprite.png'); 8 | }, 9 | create() { 10 | for (let i = 0; i < engine.count; i++) { 11 | const r = engine.particles[i]; 12 | let particle; 13 | if (engine.type === 'sprite') { 14 | particle = this.add.sprite(r.x, r.y, 'sprite'); 15 | } else { 16 | particle = this.add.circle(r.x, r.y, r.size, 0xffffff); 17 | if (engine.type === 'stroke') { 18 | particle.setStrokeStyle(1, 0xffffff); 19 | particle.isFilled = false; 20 | } else if (engine.type === 'fill') 21 | particle.setStrokeStyle(1, 0x000000); 22 | } 23 | 24 | engine.particles[i].el = particle; 25 | } 26 | }, 27 | update(_time, _delta) { 28 | // Particle animation 29 | const particles = engine.particles; 30 | for (let i = 0; i < engine.count; i++) { 31 | const r = particles[i]; 32 | r.x -= r.dx; 33 | r.y -= r.dy; 34 | if (r.x + r.size < 0) r.dx *= -1; 35 | else if (r.y + r.size < 0) r.dy *= -1; 36 | if (r.x > engine.width) r.dx *= -1; 37 | else if (r.y > engine.height) r.dy *= -1; 38 | if (r.el) { 39 | r.el.x = r.x; 40 | r.el.y = r.y; 41 | } 42 | } 43 | engine.fpsmeter.tick(); 44 | }, 45 | }; 46 | }; 47 | 48 | class CanvasEngine extends Engine { 49 | init() { 50 | super.init(); 51 | 52 | // Clear the canvas 53 | this.canvas.innerHTML = ''; 54 | window.cancelAnimationFrame(this.request); 55 | 56 | // Configure phaser with custom scene 57 | this.config = { 58 | type: Phaser.WEBGL, 59 | width: this.width, 60 | height: this.height, 61 | canvas, 62 | physics: { 63 | default: 'arcade', 64 | arcade: { 65 | gravity: { y: 0 }, 66 | debug: false, 67 | fps: 60, 68 | }, 69 | }, 70 | backgroundColor: '#1a1a1a', 71 | scene: [scene(this)], 72 | render: { pixelArt: false, antialias: true }, 73 | }; 74 | 75 | // Particle creation 76 | const particles = new Array(this.count); 77 | const rnd = [1, -1]; 78 | for (let i = 0; i < this.count; i++) { 79 | const size = 10 + Math.random() * 80; 80 | const x = Math.random() * this.width; 81 | const y = Math.random() * (this.height - size); 82 | const [dx, dy] = [ 83 | 3 * Math.random() * rnd[Math.floor(Math.random() * 2)], 84 | 3 * Math.random() * rnd[Math.floor(Math.random() * 2)], 85 | ]; 86 | particles[i] = { x, y, size: size, dx, dy }; 87 | } 88 | this.particles = particles; 89 | } 90 | render() { 91 | if (this.game) this.game.destroy(false, false); 92 | this.game = new Phaser.Game(this.config); 93 | } 94 | } 95 | 96 | document.addEventListener('DOMContentLoaded', () => { 97 | const engine = new CanvasEngine(); 98 | engine.render(); 99 | }); 100 | -------------------------------------------------------------------------------- /src/scripts/pixi.js: -------------------------------------------------------------------------------- 1 | import * as PIXI from 'pixi.js'; 2 | import Engine from './engine.js'; 3 | 4 | class PixiEngine extends Engine { 5 | async init() { 6 | super.init(); 7 | 8 | // Clear the canvas 9 | this.canvas.innerHTML = ''; 10 | window.cancelAnimationFrame(this.request); 11 | 12 | // Setup application and stage 13 | if (this.app) this.app.ticker.destroy(); 14 | this.app = new PIXI.Application({ 15 | width: this.width, 16 | height: this.height, 17 | backgroundColor: 0x1a1a1a, 18 | antialias: true, 19 | }); 20 | this.app.view.classList.add('canvas'); 21 | 22 | // Update canvas with application view 23 | const main = document.querySelector('main'); 24 | main.removeChild(main.lastElementChild); 25 | main.appendChild(this.app.view); 26 | 27 | // Particle creation 28 | if (this.type === 'sprite') { 29 | this.texture = PIXI.Texture.from('sprite.png'); 30 | } 31 | const particles = new Array(this.count); 32 | const rnd = [1, -1]; 33 | for (let i = 0; i < this.count; i++) { 34 | const size = 10 + Math.random() * 80; 35 | const x = Math.random() * this.width; 36 | const y = Math.random() * (this.height - size); 37 | const [dx, dy] = [ 38 | 3 * Math.random() * rnd[Math.floor(Math.random() * 2)], 39 | 3 * Math.random() * rnd[Math.floor(Math.random() * 2)], 40 | ]; 41 | let particle; 42 | if (this.type === 'sprite') { 43 | particle = new PIXI.Sprite(this.texture); 44 | } else { 45 | particle = new PIXI.Graphics(); 46 | if (this.type === 'stroke') { 47 | particle.lineStyle(1, 0xffffff, 1); 48 | particle.drawCircle(-size / 2, -size / 2, size, 0, Math.PI); 49 | } else if (this.type === 'fill') { 50 | particle.beginFill(0xffffff); 51 | particle.lineStyle(1, 0x000000, 1); 52 | particle.drawCircle(-size / 2, -size / 2, size, 0, Math.PI); 53 | particle.endFill(); 54 | } 55 | } 56 | particle.position.set(x, y); 57 | this.app.stage.addChild(particle); 58 | particles[i] = { x, y, size: size, dx, dy, el: particle }; 59 | } 60 | this.particles = particles; 61 | } 62 | render() { 63 | this.app.ticker.add(() => { 64 | // Particle animation 65 | const particles = this.particles; 66 | for (let i = 0; i < this.count; i++) { 67 | const r = particles[i]; 68 | r.x -= r.dx; 69 | r.y -= r.dy; 70 | if (r.x + r.size < 0) r.dx *= -1; 71 | else if (r.y + r.size < 0) r.dy *= -1; 72 | if (r.x > this.width) r.dx *= -1; 73 | else if (r.y > this.height) r.dy *= -1; 74 | r.el.position.x = r.x; 75 | r.el.position.y = r.y; 76 | } 77 | 78 | this.fpsmeter.tick(); 79 | }); 80 | } 81 | } 82 | 83 | document.addEventListener('DOMContentLoaded', () => { 84 | const engine = new PixiEngine(); 85 | engine.render(); 86 | }); 87 | -------------------------------------------------------------------------------- /src/scripts/three.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import Engine from './engine.js'; 3 | 4 | class ThreeEngine extends Engine { 5 | init() { 6 | super.init(); 7 | 8 | // Clear the canvas 9 | this.canvas.innerHTML = ''; 10 | window.cancelAnimationFrame(this.request); 11 | 12 | // Setup application and stage 13 | this.camera = new THREE.PerspectiveCamera( 14 | 50, 15 | this.width / this.height, 16 | 0.1, 17 | 2000 18 | ); 19 | this.camera.position.set(this.width / 2, this.height / 2, 500); 20 | this.renderer = new THREE.WebGLRenderer({ 21 | antialias: true, 22 | depth: false, 23 | precision: 'lowp', 24 | }); 25 | this.renderer.setSize(this.width, this.height); 26 | this.renderer.sortObjects = false; 27 | this.renderer.domElement.classList.add('canvas'); 28 | 29 | if (this.scene) this.scene.clear(); 30 | this.scene = new THREE.Scene(); 31 | this.scene.background = new THREE.Color('#1a1a1a'); 32 | 33 | // Update canvas with renderer view 34 | const main = document.querySelector('main'); 35 | main.removeChild(main.lastElementChild); 36 | main.appendChild(this.renderer.domElement); 37 | 38 | // Particle creation 39 | const particles = new Array(this.count); 40 | const rnd = [1, -1]; 41 | const meshMaterial = new THREE.MeshBasicMaterial({ 42 | color: 0xffffff, 43 | side: THREE.FrontSide, 44 | depthTest: false, 45 | }); 46 | let lineMaterial = new THREE.LineBasicMaterial({ color: 0xffffff }); 47 | if (this.type === 'fill') 48 | lineMaterial = new THREE.LineBasicMaterial({ color: 0x000000 }); 49 | 50 | // Sprite texture 51 | const map = new THREE.TextureLoader().load('sprite.png'); 52 | const material = new THREE.SpriteMaterial({ map: map }); 53 | 54 | for (let i = 0; i < this.count; i++) { 55 | const size = 10 + Math.random() * 80; 56 | const x = Math.random() * this.width; 57 | const y = Math.random() * (this.height - size); 58 | const [dx, dy] = [ 59 | 3 * Math.random() * rnd[Math.floor(Math.random() * 2)], 60 | 3 * Math.random() * rnd[Math.floor(Math.random() * 2)], 61 | ]; 62 | 63 | const geometry = new THREE.CircleGeometry(size); 64 | let line; 65 | let plane; 66 | if (this.type === 'sprite') { 67 | line = new THREE.Sprite(material); 68 | line.scale.x = 64; 69 | line.scale.y = 64; 70 | } else { 71 | if (this.type === 'fill') { 72 | plane = new THREE.Mesh(geometry, meshMaterial); 73 | plane.position.set(x, y, 0); 74 | this.scene.add(plane); 75 | } 76 | const edges = new THREE.EdgesGeometry(geometry); 77 | line = new THREE.LineSegments(edges, lineMaterial); 78 | } 79 | line.position.set(x, y, 0); 80 | this.scene.add(line); 81 | particles[i] = { x, y, size: size, dx, dy, el: [line, plane] }; 82 | } 83 | this.particles = particles; 84 | } 85 | render() { 86 | // Particle animation 87 | const particles = this.particles; 88 | for (let i = 0; i < this.count; i++) { 89 | const r = particles[i]; 90 | r.x -= r.dx; 91 | r.y -= r.dy; 92 | if (r.x + r.size < 0) r.dx *= -1; 93 | else if (r.y + r.size < 0) r.dy *= -1; 94 | if (r.x > this.width) r.dx *= -1; 95 | else if (r.y > this.height) r.dy *= -1; 96 | 97 | r.el[0].position.x = r.x; 98 | r.el[0].position.y = r.y; 99 | if (r.el[1]) { 100 | r.el[1].position.x = r.x; 101 | r.el[1].position.y = r.y; 102 | } 103 | } 104 | this.renderer.render(this.scene, this.camera); 105 | 106 | this.fpsmeter.tick(); 107 | this.request = window.requestAnimationFrame(() => this.render()); 108 | } 109 | } 110 | 111 | document.addEventListener('DOMContentLoaded', () => { 112 | const engine = new ThreeEngine(); 113 | engine.render(); 114 | }); 115 | -------------------------------------------------------------------------------- /src/scripts/two.js: -------------------------------------------------------------------------------- 1 | import Two from 'two.js'; 2 | import Engine from './engine.js'; 3 | 4 | class TwoEngine extends Engine { 5 | init() { 6 | super.init(); 7 | 8 | // Clear the canvas 9 | this.canvas.innerHTML = ''; 10 | window.cancelAnimationFrame(this.request); 11 | 12 | const main = document.querySelector('main'); 13 | main.removeChild(main.lastElementChild); 14 | if (this.two) { 15 | this.two.unbind('update'); 16 | this.two.clear(); 17 | } 18 | this.two = new Two({ 19 | type: Two.Types.webgl, 20 | width: this.width, 21 | height: this.height, 22 | autostart: true, 23 | }).appendTo(main); 24 | 25 | // Particle creation 26 | const particles = new Array(this.count); 27 | const rnd = [1, -1]; 28 | for (let i = 0; i < this.count; i++) { 29 | const size = 10 + Math.random() * 80; 30 | const x = Math.random() * this.width; 31 | const y = Math.random() * (this.height - size); 32 | const [dx, dy] = [ 33 | 3 * Math.random() * rnd[Math.floor(Math.random() * 2)], 34 | 3 * Math.random() * rnd[Math.floor(Math.random() * 2)], 35 | ]; 36 | let particle; 37 | if (this.type === 'sprite') { 38 | particle = new Two.Sprite('sprite.png', x, y, 1, 1); 39 | this.two.add(particle); 40 | } else { 41 | particle = this.two.makeCircle(0, 0, size); 42 | if (this.type === 'stroke') particle.noFill().stroke = '#ffffff'; 43 | else if (this.type === 'fill') particle.stroke = '#000000'; 44 | } 45 | particle.position.set(0, 0); 46 | particles[i] = { x, y, size: size, dx, dy, el: particle }; 47 | } 48 | this.particles = particles; 49 | } 50 | render() { 51 | this.two.bind('update', () => { 52 | // Particle animation 53 | const particles = this.particles; 54 | for (let i = 0; i < this.count; i++) { 55 | const r = particles[i]; 56 | r.x -= r.dx; 57 | r.y -= r.dy; 58 | if (r.x + r.size < 0) r.dx *= -1; 59 | else if (r.y + r.size < 0) r.dy *= -1; 60 | if (r.x > this.width) r.dx *= -1; 61 | else if (r.y > this.height) r.dy *= -1; 62 | r.el.translation.set(r.x, r.y); 63 | } 64 | this.fpsmeter.tick(); 65 | }); 66 | } 67 | } 68 | 69 | document.addEventListener('DOMContentLoaded', () => { 70 | const engine = new TwoEngine(); 71 | engine.render(); 72 | }); 73 | -------------------------------------------------------------------------------- /src/three.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{#> head }} Three.js — JS Game Rendering Benchmark {{/head}} 4 | 5 | {{> header }} 6 |
7 | {{> container }} 8 | 9 |
10 | {{> footer }} 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/two.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{#> head }} Two.js — JS Game Rendering Benchmark {{/head}} 4 | 5 | {{> header }} 6 |
7 | {{> container }} 8 | 9 |
10 | {{> footer }} 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { resolve } from 'path'; 3 | import handlebars from 'vite-plugin-handlebars'; 4 | 5 | const input = {}; 6 | fs.readdirSync('src').forEach((file) => { 7 | if (file.endsWith('.html')) { 8 | if (file === 'index.html') return; 9 | input[file.split('.')[0]] = resolve('src', file); 10 | } 11 | }); 12 | 13 | export default { 14 | root: 'src', 15 | base: 16 | process.env.NODE_ENV === 'development' 17 | ? './' 18 | : '/js-game-rendering-benchmark/', 19 | build: { 20 | outDir: '../dist', 21 | emptyOutDir: true, 22 | rollupOptions: { 23 | input: { 24 | main: resolve('src', 'index.html'), 25 | ...input, 26 | }, 27 | }, 28 | }, 29 | plugins: [ 30 | handlebars({ 31 | partialDirectory: resolve('src', 'partials'), 32 | }), 33 | ], 34 | }; 35 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@esbuild/android-arm64@0.16.17": 6 | version "0.16.17" 7 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz#cf91e86df127aa3d141744edafcba0abdc577d23" 8 | integrity sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg== 9 | 10 | "@esbuild/android-arm@0.16.17": 11 | version "0.16.17" 12 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.16.17.tgz#025b6246d3f68b7bbaa97069144fb5fb70f2fff2" 13 | integrity sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw== 14 | 15 | "@esbuild/android-x64@0.16.17": 16 | version "0.16.17" 17 | resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.16.17.tgz#c820e0fef982f99a85c4b8bfdd582835f04cd96e" 18 | integrity sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ== 19 | 20 | "@esbuild/darwin-arm64@0.16.17": 21 | version "0.16.17" 22 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz#edef4487af6b21afabba7be5132c26d22379b220" 23 | integrity sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w== 24 | 25 | "@esbuild/darwin-x64@0.16.17": 26 | version "0.16.17" 27 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz#42829168730071c41ef0d028d8319eea0e2904b4" 28 | integrity sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg== 29 | 30 | "@esbuild/freebsd-arm64@0.16.17": 31 | version "0.16.17" 32 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz#1f4af488bfc7e9ced04207034d398e793b570a27" 33 | integrity sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw== 34 | 35 | "@esbuild/freebsd-x64@0.16.17": 36 | version "0.16.17" 37 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz#636306f19e9bc981e06aa1d777302dad8fddaf72" 38 | integrity sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug== 39 | 40 | "@esbuild/linux-arm64@0.16.17": 41 | version "0.16.17" 42 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz#a003f7ff237c501e095d4f3a09e58fc7b25a4aca" 43 | integrity sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g== 44 | 45 | "@esbuild/linux-arm@0.16.17": 46 | version "0.16.17" 47 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz#b591e6a59d9c4fe0eeadd4874b157ab78cf5f196" 48 | integrity sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ== 49 | 50 | "@esbuild/linux-ia32@0.16.17": 51 | version "0.16.17" 52 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz#24333a11027ef46a18f57019450a5188918e2a54" 53 | integrity sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg== 54 | 55 | "@esbuild/linux-loong64@0.14.54": 56 | version "0.14.54" 57 | resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz#de2a4be678bd4d0d1ffbb86e6de779cde5999028" 58 | integrity sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw== 59 | 60 | "@esbuild/linux-loong64@0.16.17": 61 | version "0.16.17" 62 | resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz#d5ad459d41ed42bbd4d005256b31882ec52227d8" 63 | integrity sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ== 64 | 65 | "@esbuild/linux-mips64el@0.16.17": 66 | version "0.16.17" 67 | resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz#4e5967a665c38360b0a8205594377d4dcf9c3726" 68 | integrity sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw== 69 | 70 | "@esbuild/linux-ppc64@0.16.17": 71 | version "0.16.17" 72 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz#206443a02eb568f9fdf0b438fbd47d26e735afc8" 73 | integrity sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g== 74 | 75 | "@esbuild/linux-riscv64@0.16.17": 76 | version "0.16.17" 77 | resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz#c351e433d009bf256e798ad048152c8d76da2fc9" 78 | integrity sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw== 79 | 80 | "@esbuild/linux-s390x@0.16.17": 81 | version "0.16.17" 82 | resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz#661f271e5d59615b84b6801d1c2123ad13d9bd87" 83 | integrity sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w== 84 | 85 | "@esbuild/linux-x64@0.16.17": 86 | version "0.16.17" 87 | resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz" 88 | integrity sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw== 89 | 90 | "@esbuild/netbsd-x64@0.16.17": 91 | version "0.16.17" 92 | resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz#7d4f4041e30c5c07dd24ffa295c73f06038ec775" 93 | integrity sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA== 94 | 95 | "@esbuild/openbsd-x64@0.16.17": 96 | version "0.16.17" 97 | resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz#970fa7f8470681f3e6b1db0cc421a4af8060ec35" 98 | integrity sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg== 99 | 100 | "@esbuild/sunos-x64@0.16.17": 101 | version "0.16.17" 102 | resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz#abc60e7c4abf8b89fb7a4fe69a1484132238022c" 103 | integrity sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw== 104 | 105 | "@esbuild/win32-arm64@0.16.17": 106 | version "0.16.17" 107 | resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz#7b0ff9e8c3265537a7a7b1fd9a24e7bd39fcd87a" 108 | integrity sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw== 109 | 110 | "@esbuild/win32-ia32@0.16.17": 111 | version "0.16.17" 112 | resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz#e90fe5267d71a7b7567afdc403dfd198c292eb09" 113 | integrity sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig== 114 | 115 | "@esbuild/win32-x64@0.16.17": 116 | version "0.16.17" 117 | resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz#c5a1a4bfe1b57f0c3e61b29883525c6da3e5c091" 118 | integrity sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q== 119 | 120 | "@pixi/accessibility@7.1.0": 121 | version "7.1.0" 122 | resolved "https://registry.npmjs.org/@pixi/accessibility/-/accessibility-7.1.0.tgz" 123 | integrity sha512-bbDW4LgMPtZPBTxa5uX/RASwKobfrr11/2c7haGFjTeESzwCkTTsEWdbNE3RRZYJVEKgkXCOyfZZVIunU1502w== 124 | 125 | "@pixi/app@7.1.0": 126 | version "7.1.0" 127 | resolved "https://registry.npmjs.org/@pixi/app/-/app-7.1.0.tgz" 128 | integrity sha512-9zuyNagKwOm7zYQHV35McgMIAtEnnMMhyhyvOAJ20xfiwUdAVRCYfQ3kOkAyvIpD1iA+9SGxOzzksY21Y9CF9A== 129 | 130 | "@pixi/assets@7.1.0": 131 | version "7.1.0" 132 | resolved "https://registry.npmjs.org/@pixi/assets/-/assets-7.1.0.tgz" 133 | integrity sha512-rwv8IsGSZjp6hqC7RZ7gxFsJssn+nSIJQcpXiCx9wFspS9b05IshNJ5VIzeHMEBzvEw53fCoYzejO2bf0VsBoA== 134 | dependencies: 135 | "@types/css-font-loading-module" "^0.0.7" 136 | 137 | "@pixi/compressed-textures@7.1.0": 138 | version "7.1.0" 139 | resolved "https://registry.npmjs.org/@pixi/compressed-textures/-/compressed-textures-7.1.0.tgz" 140 | integrity sha512-QfYetHnhrfyzNcHyXVeNvhjVNlJk0EbFNRg7Q9qJF6G9P78sFf7zB9rGq9T9bPuYQ8dEJndcwJVgGNfM9cF2Zw== 141 | 142 | "@pixi/constants@7.1.0": 143 | version "7.1.0" 144 | resolved "https://registry.npmjs.org/@pixi/constants/-/constants-7.1.0.tgz" 145 | integrity sha512-6HGMxhzNRyAYgjtTt9LFilqucBgl/Me6g1xEAYCt+8yMYYo2PVbI6Zxn160ggiwVYdOVROfL8HVdrsGxzy7ufA== 146 | 147 | "@pixi/core@7.1.0": 148 | version "7.1.0" 149 | resolved "https://registry.npmjs.org/@pixi/core/-/core-7.1.0.tgz" 150 | integrity sha512-r+tZdMYodBE0m9LCoCLz3UwxGAO2CW3+YEDt4vlsv7WmdKZVJhuYFUPTGAO49dzEL7bjS0qYVcUxvuUVXI/yww== 151 | dependencies: 152 | "@pixi/constants" "7.1.0" 153 | "@pixi/extensions" "7.1.0" 154 | "@pixi/math" "7.1.0" 155 | "@pixi/runner" "7.1.0" 156 | "@pixi/settings" "7.1.0" 157 | "@pixi/ticker" "7.1.0" 158 | "@pixi/utils" "7.1.0" 159 | "@types/offscreencanvas" "^2019.6.4" 160 | 161 | "@pixi/display@7.1.0": 162 | version "7.1.0" 163 | resolved "https://registry.npmjs.org/@pixi/display/-/display-7.1.0.tgz" 164 | integrity sha512-F9PCrjc7Z1uzkeeRahV/KihXEjvVA68veVPkwCgFTweWuu7quiIuaSAd1m5yCkcj4VjwyFlDoumqDejibYTrBg== 165 | 166 | "@pixi/events@7.1.0": 167 | version "7.1.0" 168 | resolved "https://registry.npmjs.org/@pixi/events/-/events-7.1.0.tgz" 169 | integrity sha512-AYevA+08Q8cw+ble0/Km7M1+EZ8JP+K0a+q4Hx6tRBTDTsn18OuwSP9vXW6/dAMEE3d8mcdquEjkoqOv5494QA== 170 | 171 | "@pixi/extensions@7.1.0": 172 | version "7.1.0" 173 | resolved "https://registry.npmjs.org/@pixi/extensions/-/extensions-7.1.0.tgz" 174 | integrity sha512-J2//MlKRAGZmaRgmSOuYGK1sTQjzv1rag8kIB96DjQb4SbkaLACZLN/QHbj+7Cf7ZcZ9XUMHzJU38V1epVPVGQ== 175 | 176 | "@pixi/extract@7.1.0": 177 | version "7.1.0" 178 | resolved "https://registry.npmjs.org/@pixi/extract/-/extract-7.1.0.tgz" 179 | integrity sha512-HTVQ9wKBZUb7AShUfnUkjPGrqVvFUuU4x1BIXKLtpkbMRjTkfgW/0d1PkhlQxanzMlCR/eOU43igzjB5zeVkZg== 180 | 181 | "@pixi/filter-alpha@7.1.0": 182 | version "7.1.0" 183 | resolved "https://registry.npmjs.org/@pixi/filter-alpha/-/filter-alpha-7.1.0.tgz" 184 | integrity sha512-tuCVvJXQRUXg8+DCKViOSobjRANYRlMEdfVOfrxmfve4nId1qJyw7VkEIpD3pOSOxHd8s0BZIkcLIMO+oOJu6w== 185 | 186 | "@pixi/filter-blur@7.1.0": 187 | version "7.1.0" 188 | resolved "https://registry.npmjs.org/@pixi/filter-blur/-/filter-blur-7.1.0.tgz" 189 | integrity sha512-JefcMjcQVngVNixvlYUnaFOiGdJqkfzWO90e0Cs7yqPHJpyzbdXtJbzWGTsnbYO8Q+q1j5pu7zYUbT5a3jUfaw== 190 | 191 | "@pixi/filter-color-matrix@7.1.0": 192 | version "7.1.0" 193 | resolved "https://registry.npmjs.org/@pixi/filter-color-matrix/-/filter-color-matrix-7.1.0.tgz" 194 | integrity sha512-IbdfIaV/sYWhHwGIRBNCo65n5Fi4GBzMpB5+dZBfarWjMOhO/MA43d49vLnTNNLrh2ZpjMuzc7IramRrUPLsyQ== 195 | 196 | "@pixi/filter-displacement@7.1.0": 197 | version "7.1.0" 198 | resolved "https://registry.npmjs.org/@pixi/filter-displacement/-/filter-displacement-7.1.0.tgz" 199 | integrity sha512-FNgDzkSqT3GzazBkITu+BXa+AVk5oxlgGKo/a0GsS7k7qNioS74o/p3U4ymARvDdQHdOxdP/SqIGxvJ45/7iGQ== 200 | 201 | "@pixi/filter-fxaa@7.1.0": 202 | version "7.1.0" 203 | resolved "https://registry.npmjs.org/@pixi/filter-fxaa/-/filter-fxaa-7.1.0.tgz" 204 | integrity sha512-tkqXJFKuRAi7gw32jIK2L0HTKDIOpPI8VWH4VnnC5bZf/7d1/y5b7WUcayxWUnyt10U8dicQrzsH9oZ9ESSpVA== 205 | 206 | "@pixi/filter-noise@7.1.0": 207 | version "7.1.0" 208 | resolved "https://registry.npmjs.org/@pixi/filter-noise/-/filter-noise-7.1.0.tgz" 209 | integrity sha512-i68BOZAxxSJ8/v2gWoKldKIQ6nwiCUlcK38Kh87b+3X4FXyBp98gW6ShzzpCpJ60BDVHDyEGGKYEReTVJbfMoQ== 210 | 211 | "@pixi/graphics@7.1.0": 212 | version "7.1.0" 213 | resolved "https://registry.npmjs.org/@pixi/graphics/-/graphics-7.1.0.tgz" 214 | integrity sha512-snZdUhlG0MFolMeR7codSGdGO7sFsciBrtiCuIU49zILZ7/TbC82Tv2GGZuseYI3TTkBdYfoLXlvHrzAk/g8Dw== 215 | 216 | "@pixi/math@7.1.0": 217 | version "7.1.0" 218 | resolved "https://registry.npmjs.org/@pixi/math/-/math-7.1.0.tgz" 219 | integrity sha512-du0H9egUtducS6WExFDRzoZM88DAHYSRMr2/PcEOpd2IEI6LvVCAonxFp0P1e7c62yMWYA1kAJV0qLlsiOCzzA== 220 | 221 | "@pixi/mesh-extras@7.1.0": 222 | version "7.1.0" 223 | resolved "https://registry.npmjs.org/@pixi/mesh-extras/-/mesh-extras-7.1.0.tgz" 224 | integrity sha512-P9zEsjTJFZlPI0eMazxl6WWY4HEGm81R4lYy3dfqQbIpj+7l8Av1FEAC24A6WKsI6+mA91GtJc+wc+TyTqtdYw== 225 | 226 | "@pixi/mesh@7.1.0": 227 | version "7.1.0" 228 | resolved "https://registry.npmjs.org/@pixi/mesh/-/mesh-7.1.0.tgz" 229 | integrity sha512-Kh6jKeL5kwYosZ3YQUSI+u8TyOLqrhx8BVKxiCHuQtDBM1EzwmfRbppNLzsz6TYnqgs9KwzgAJ2YU2qApiA+aA== 230 | 231 | "@pixi/mixin-cache-as-bitmap@7.1.0": 232 | version "7.1.0" 233 | resolved "https://registry.npmjs.org/@pixi/mixin-cache-as-bitmap/-/mixin-cache-as-bitmap-7.1.0.tgz" 234 | integrity sha512-MjjmICn62dPFS4Ut68iAZ8nh/ycJpivp1sxh2gLZnExVDslcSMhitoYZE4ESq57S//g5k2j3B0hTiURGTwkvgg== 235 | 236 | "@pixi/mixin-get-child-by-name@7.1.0": 237 | version "7.1.0" 238 | resolved "https://registry.npmjs.org/@pixi/mixin-get-child-by-name/-/mixin-get-child-by-name-7.1.0.tgz" 239 | integrity sha512-RHbQvh+xqE9Um1c9vKGf7czSjTgba0RdsIpkUWvlTIDX197yucmBmSxqt/gKHbgi4//AypJ6Zju/dvBDhNlwAQ== 240 | 241 | "@pixi/mixin-get-global-position@7.1.0": 242 | version "7.1.0" 243 | resolved "https://registry.npmjs.org/@pixi/mixin-get-global-position/-/mixin-get-global-position-7.1.0.tgz" 244 | integrity sha512-3MWUuccUQKf9eP2tp3HA46U+e7nav9JWVv8V3+SavfKADnnka1L3qnOlnk3EdPWbIEGc5JNguf84oLg2+sTBKw== 245 | 246 | "@pixi/particle-container@7.1.0": 247 | version "7.1.0" 248 | resolved "https://registry.npmjs.org/@pixi/particle-container/-/particle-container-7.1.0.tgz" 249 | integrity sha512-Mmz6eVDCMl3Cxttj06ozX1GYK7H0v8OD9i0/gHjdU89loQNNMKsSopgzeXTgmP37YPlElyLGqSYw4UX+Q8AAig== 250 | 251 | "@pixi/prepare@7.1.0": 252 | version "7.1.0" 253 | resolved "https://registry.npmjs.org/@pixi/prepare/-/prepare-7.1.0.tgz" 254 | integrity sha512-zmQcDe4dX/8HMXOtJRiDh0YaVpjamk/wvuOv0Xia7vs2GeUoxF3jXlnLyFzn0upMKbWbyvTblaKm/BX7u0cCKw== 255 | 256 | "@pixi/runner@7.1.0": 257 | version "7.1.0" 258 | resolved "https://registry.npmjs.org/@pixi/runner/-/runner-7.1.0.tgz" 259 | integrity sha512-Heuvu3zb+VGLUdwad2AE8w9HD1i3r1wbwTG/zuL0KrCa6SaEgV0Ro/GOE9HpCWppCylruToxEPsVKW/YJ1zL3w== 260 | 261 | "@pixi/settings@7.1.0": 262 | version "7.1.0" 263 | resolved "https://registry.npmjs.org/@pixi/settings/-/settings-7.1.0.tgz" 264 | integrity sha512-VnRI0R3vgglKZxbyA0lJVyS/qJHDEZlLYkVXb79CRF+wimaVgETtTLBpNaDxTMjwVwt2yqM1HvykvABuJ8YxNQ== 265 | dependencies: 266 | "@pixi/constants" "7.1.0" 267 | "@types/css-font-loading-module" "^0.0.7" 268 | 269 | "@pixi/sprite-animated@7.1.0": 270 | version "7.1.0" 271 | resolved "https://registry.npmjs.org/@pixi/sprite-animated/-/sprite-animated-7.1.0.tgz" 272 | integrity sha512-b6uQuW4H8MxLUQ95OOoAKshqqTiSyBDcSKJEzsn8n80P7/7zN9CokxFd9dCdEVGBdaUO3z69LZArBNaAROgn3g== 273 | 274 | "@pixi/sprite-tiling@7.1.0": 275 | version "7.1.0" 276 | resolved "https://registry.npmjs.org/@pixi/sprite-tiling/-/sprite-tiling-7.1.0.tgz" 277 | integrity sha512-MHM7hLnrYHC6m+4AFZ046uRMJ29nzNOdAGkQm3ZnsiwfHSJ5IVO2uF+5zScGiJMB4NJqK8enkE6Un7bfAChxpw== 278 | 279 | "@pixi/sprite@7.1.0": 280 | version "7.1.0" 281 | resolved "https://registry.npmjs.org/@pixi/sprite/-/sprite-7.1.0.tgz" 282 | integrity sha512-Nm9Aa/54OK+3T813OEHJdEjt/LTnktuCb+YUNpPf8WjNCGNiHGrfGS9YAj/F1PYtFpK006UPECE+bJCqjPw2ug== 283 | 284 | "@pixi/spritesheet@7.1.0": 285 | version "7.1.0" 286 | resolved "https://registry.npmjs.org/@pixi/spritesheet/-/spritesheet-7.1.0.tgz" 287 | integrity sha512-esbeVQ8G6QML0sDtdY9x9a0CFcRv4VAfuQkENZqbFrP3ZHbDODXy8WqbkMrukK4dxvfT0aCt1dMRQTMgLkxxwg== 288 | 289 | "@pixi/text-bitmap@7.1.0": 290 | version "7.1.0" 291 | resolved "https://registry.npmjs.org/@pixi/text-bitmap/-/text-bitmap-7.1.0.tgz" 292 | integrity sha512-2lYv6KbfE/3bXI9S8Pk73MVAJbu8sVx7cF5ldneXwNQYEXsy3+Abiq+1caOx9AXYoPtye03OFzFd5BR5Og7iHw== 293 | 294 | "@pixi/text@7.1.0": 295 | version "7.1.0" 296 | resolved "https://registry.npmjs.org/@pixi/text/-/text-7.1.0.tgz" 297 | integrity sha512-owcPLrvLMNRj4I8euXNwxE29f3FfIGg67ibZ/GO02lIR1HvVfNFYatNPt2xdpxdJM61+Qy5dH2m+dAh2w28YXA== 298 | 299 | "@pixi/ticker@7.1.0": 300 | version "7.1.0" 301 | resolved "https://registry.npmjs.org/@pixi/ticker/-/ticker-7.1.0.tgz" 302 | integrity sha512-P95JcTMsoqSJDFIp3v8ClhyFs/Eqv7/XbYxA6FuaHISFfX1I3xIywejqSvrtquQJZDyNED8QgGWR1xCZy0tBZw== 303 | dependencies: 304 | "@pixi/extensions" "7.1.0" 305 | "@pixi/settings" "7.1.0" 306 | "@pixi/utils" "7.1.0" 307 | 308 | "@pixi/utils@7.1.0": 309 | version "7.1.0" 310 | resolved "https://registry.npmjs.org/@pixi/utils/-/utils-7.1.0.tgz" 311 | integrity sha512-QPrugQvwR/Rw23TXAnLXIZwvto2f8/pgxIFvFOAVPBozBWBkibHuGevq7Jlcaybn95KafLfY9nFMbM0qRInWCg== 312 | dependencies: 313 | "@pixi/constants" "7.1.0" 314 | "@pixi/settings" "7.1.0" 315 | "@types/earcut" "^2.1.0" 316 | earcut "^2.2.4" 317 | eventemitter3 "^4.0.0" 318 | url "^0.11.0" 319 | 320 | "@teppeis/multimaps@^2.0.0": 321 | version "2.0.0" 322 | resolved "https://registry.npmjs.org/@teppeis/multimaps/-/multimaps-2.0.0.tgz" 323 | integrity sha512-TL1adzq1HdxUf9WYduLcQ/DNGYiz71U31QRgbnr0Ef1cPyOUOsBojxHVWpFeOSUucB6Lrs0LxFRA14ntgtkc9w== 324 | 325 | "@types/css-font-loading-module@^0.0.7": 326 | version "0.0.7" 327 | resolved "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz" 328 | integrity sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q== 329 | 330 | "@types/earcut@^2.1.0": 331 | version "2.1.1" 332 | resolved "https://registry.npmjs.org/@types/earcut/-/earcut-2.1.1.tgz" 333 | integrity sha512-w8oigUCDjElRHRRrMvn/spybSMyX8MTkKA5Dv+tS1IE/TgmNZPqUYtvYBXGY8cieSE66gm+szeK+bnbxC2xHTQ== 334 | 335 | "@types/offscreencanvas@^2019.6.4": 336 | version "2019.7.0" 337 | resolved "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.0.tgz" 338 | integrity sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg== 339 | 340 | babylonjs@^5.42.0: 341 | version "5.42.0" 342 | resolved "https://registry.npmjs.org/babylonjs/-/babylonjs-5.42.0.tgz" 343 | integrity sha512-Ri/nJbS99NVmg2qTa7+EcL3QV2wzUxiavNmX6iqc3VJSmT5dPH4db9nqPQfN49UEV+ER1zuaJwrJ2bWlP1K+UA== 344 | 345 | core-js@^3.27.2: 346 | version "3.28.0" 347 | resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.28.0.tgz#ed8b9e99c273879fdfff0edfc77ee709a5800e4a" 348 | integrity sha512-GiZn9D4Z/rSYvTeg1ljAIsEqFm0LaN9gVtwDCrKL80zHtS31p9BAjmTxVqTQDMpwlMolJZOFntUG2uwyj7DAqw== 349 | 350 | earcut@2.2.4, earcut@^2.2.4: 351 | version "2.2.4" 352 | resolved "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz" 353 | integrity sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ== 354 | 355 | esbuild-android-64@0.14.54: 356 | version "0.14.54" 357 | resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz#505f41832884313bbaffb27704b8bcaa2d8616be" 358 | integrity sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ== 359 | 360 | esbuild-android-arm64@0.14.54: 361 | version "0.14.54" 362 | resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz#8ce69d7caba49646e009968fe5754a21a9871771" 363 | integrity sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg== 364 | 365 | esbuild-darwin-64@0.14.54: 366 | version "0.14.54" 367 | resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz#24ba67b9a8cb890a3c08d9018f887cc221cdda25" 368 | integrity sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug== 369 | 370 | esbuild-darwin-arm64@0.14.54: 371 | version "0.14.54" 372 | resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz#3f7cdb78888ee05e488d250a2bdaab1fa671bf73" 373 | integrity sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw== 374 | 375 | esbuild-freebsd-64@0.14.54: 376 | version "0.14.54" 377 | resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz#09250f997a56ed4650f3e1979c905ffc40bbe94d" 378 | integrity sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg== 379 | 380 | esbuild-freebsd-arm64@0.14.54: 381 | version "0.14.54" 382 | resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz#bafb46ed04fc5f97cbdb016d86947a79579f8e48" 383 | integrity sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q== 384 | 385 | esbuild-linux-32@0.14.54: 386 | version "0.14.54" 387 | resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz#e2a8c4a8efdc355405325033fcebeb941f781fe5" 388 | integrity sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw== 389 | 390 | esbuild-linux-64@0.14.54: 391 | version "0.14.54" 392 | resolved "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz" 393 | integrity sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg== 394 | 395 | esbuild-linux-arm64@0.14.54: 396 | version "0.14.54" 397 | resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz#dae4cd42ae9787468b6a5c158da4c84e83b0ce8b" 398 | integrity sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig== 399 | 400 | esbuild-linux-arm@0.14.54: 401 | version "0.14.54" 402 | resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz#a2c1dff6d0f21dbe8fc6998a122675533ddfcd59" 403 | integrity sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw== 404 | 405 | esbuild-linux-mips64le@0.14.54: 406 | version "0.14.54" 407 | resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz#d9918e9e4cb972f8d6dae8e8655bf9ee131eda34" 408 | integrity sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw== 409 | 410 | esbuild-linux-ppc64le@0.14.54: 411 | version "0.14.54" 412 | resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz#3f9a0f6d41073fb1a640680845c7de52995f137e" 413 | integrity sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ== 414 | 415 | esbuild-linux-riscv64@0.14.54: 416 | version "0.14.54" 417 | resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz#618853c028178a61837bc799d2013d4695e451c8" 418 | integrity sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg== 419 | 420 | esbuild-linux-s390x@0.14.54: 421 | version "0.14.54" 422 | resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz#d1885c4c5a76bbb5a0fe182e2c8c60eb9e29f2a6" 423 | integrity sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA== 424 | 425 | esbuild-netbsd-64@0.14.54: 426 | version "0.14.54" 427 | resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz#69ae917a2ff241b7df1dbf22baf04bd330349e81" 428 | integrity sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w== 429 | 430 | esbuild-openbsd-64@0.14.54: 431 | version "0.14.54" 432 | resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz#db4c8495287a350a6790de22edea247a57c5d47b" 433 | integrity sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw== 434 | 435 | esbuild-sunos-64@0.14.54: 436 | version "0.14.54" 437 | resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz#54287ee3da73d3844b721c21bc80c1dc7e1bf7da" 438 | integrity sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw== 439 | 440 | esbuild-windows-32@0.14.54: 441 | version "0.14.54" 442 | resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz#f8aaf9a5667630b40f0fb3aa37bf01bbd340ce31" 443 | integrity sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w== 444 | 445 | esbuild-windows-64@0.14.54: 446 | version "0.14.54" 447 | resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz#bf54b51bd3e9b0f1886ffdb224a4176031ea0af4" 448 | integrity sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ== 449 | 450 | esbuild-windows-arm64@0.14.54: 451 | version "0.14.54" 452 | resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz#937d15675a15e4b0e4fafdbaa3a01a776a2be982" 453 | integrity sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg== 454 | 455 | esbuild@^0.14.27: 456 | version "0.14.54" 457 | resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.14.54.tgz" 458 | integrity sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA== 459 | optionalDependencies: 460 | "@esbuild/linux-loong64" "0.14.54" 461 | esbuild-android-64 "0.14.54" 462 | esbuild-android-arm64 "0.14.54" 463 | esbuild-darwin-64 "0.14.54" 464 | esbuild-darwin-arm64 "0.14.54" 465 | esbuild-freebsd-64 "0.14.54" 466 | esbuild-freebsd-arm64 "0.14.54" 467 | esbuild-linux-32 "0.14.54" 468 | esbuild-linux-64 "0.14.54" 469 | esbuild-linux-arm "0.14.54" 470 | esbuild-linux-arm64 "0.14.54" 471 | esbuild-linux-mips64le "0.14.54" 472 | esbuild-linux-ppc64le "0.14.54" 473 | esbuild-linux-riscv64 "0.14.54" 474 | esbuild-linux-s390x "0.14.54" 475 | esbuild-netbsd-64 "0.14.54" 476 | esbuild-openbsd-64 "0.14.54" 477 | esbuild-sunos-64 "0.14.54" 478 | esbuild-windows-32 "0.14.54" 479 | esbuild-windows-64 "0.14.54" 480 | esbuild-windows-arm64 "0.14.54" 481 | 482 | esbuild@^0.16.3: 483 | version "0.16.17" 484 | resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.16.17.tgz" 485 | integrity sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg== 486 | optionalDependencies: 487 | "@esbuild/android-arm" "0.16.17" 488 | "@esbuild/android-arm64" "0.16.17" 489 | "@esbuild/android-x64" "0.16.17" 490 | "@esbuild/darwin-arm64" "0.16.17" 491 | "@esbuild/darwin-x64" "0.16.17" 492 | "@esbuild/freebsd-arm64" "0.16.17" 493 | "@esbuild/freebsd-x64" "0.16.17" 494 | "@esbuild/linux-arm" "0.16.17" 495 | "@esbuild/linux-arm64" "0.16.17" 496 | "@esbuild/linux-ia32" "0.16.17" 497 | "@esbuild/linux-loong64" "0.16.17" 498 | "@esbuild/linux-mips64el" "0.16.17" 499 | "@esbuild/linux-ppc64" "0.16.17" 500 | "@esbuild/linux-riscv64" "0.16.17" 501 | "@esbuild/linux-s390x" "0.16.17" 502 | "@esbuild/linux-x64" "0.16.17" 503 | "@esbuild/netbsd-x64" "0.16.17" 504 | "@esbuild/openbsd-x64" "0.16.17" 505 | "@esbuild/sunos-x64" "0.16.17" 506 | "@esbuild/win32-arm64" "0.16.17" 507 | "@esbuild/win32-ia32" "0.16.17" 508 | "@esbuild/win32-x64" "0.16.17" 509 | 510 | eventemitter3@^4.0.0, eventemitter3@^4.0.7: 511 | version "4.0.7" 512 | resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz" 513 | integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== 514 | 515 | eventemitter3@^5.0.0: 516 | version "5.0.0" 517 | resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.0.tgz" 518 | integrity sha512-riuVbElZZNXLeLEoprfNYoDSwTBRR44X3mnhdI1YcnENpWTCsTTVZ2zFuqQcpoyqPQIUXdiPEU0ECAq0KQRaHg== 519 | 520 | excalibur@^0.30.3: 521 | version "0.30.3" 522 | resolved "https://registry.yarnpkg.com/excalibur/-/excalibur-0.30.3.tgz#e005ae22f85b64bddf6bb788b406e42d3a8a0946" 523 | integrity sha512-RUP3qQ6tFe9BJxvqh6p/I0B9rKMu+KGlHZruJyVOohiYUtCP0LsRgkVHxezvWtWsvCElVBL4PeCooE4KPlIldA== 524 | 525 | fpsmeter@^0.3.1: 526 | version "0.3.1" 527 | resolved "https://registry.npmjs.org/fpsmeter/-/fpsmeter-0.3.1.tgz" 528 | integrity sha512-i3zzNJwGkA+9WWIXpAtP0TCN64eO5VkKQgirYE7ZCVqyC3NfUPszU35R044fmSCjiMqefiBs5NiGKvD7lFJ87Q== 529 | 530 | fsevents@~2.3.2: 531 | version "2.3.2" 532 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 533 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 534 | 535 | function-bind@^1.1.1: 536 | version "1.1.1" 537 | resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" 538 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 539 | 540 | handlebars@^4.7.6: 541 | version "4.7.7" 542 | resolved "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz" 543 | integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== 544 | dependencies: 545 | minimist "^1.2.5" 546 | neo-async "^2.6.0" 547 | source-map "^0.6.1" 548 | wordwrap "^1.0.0" 549 | optionalDependencies: 550 | uglify-js "^3.1.4" 551 | 552 | has@^1.0.3: 553 | version "1.0.3" 554 | resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" 555 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 556 | dependencies: 557 | function-bind "^1.1.1" 558 | 559 | hilojs@^2.0.2: 560 | version "2.0.2" 561 | resolved "https://registry.npmjs.org/hilojs/-/hilojs-2.0.2.tgz" 562 | integrity sha512-PgRuYHTnXMeT4cP/rnhWqFX3xVY0BJ6+mwqIQI19MzSJ2eqLNlt0JpF2EjRJNGhtaLFH3QsXLkYnRjb7W+HGuA== 563 | 564 | howler@2.2.3: 565 | version "2.2.3" 566 | resolved "https://registry.npmjs.org/howler/-/howler-2.2.3.tgz" 567 | integrity sha512-QM0FFkw0LRX1PR8pNzJVAY25JhIWvbKMBFM4gqk+QdV+kPXOhleWGCB6AiAF/goGjIHK2e/nIElplvjQwhr0jg== 568 | 569 | inherits@2.0.3: 570 | version "2.0.3" 571 | resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" 572 | integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== 573 | 574 | is-core-module@^2.9.0: 575 | version "2.11.0" 576 | resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz" 577 | integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== 578 | dependencies: 579 | has "^1.0.3" 580 | 581 | kaboom@^2000.2.10: 582 | version "2000.2.10" 583 | resolved "https://registry.yarnpkg.com/kaboom/-/kaboom-2000.2.10.tgz#b8b6efa3f0e2d104e81dae5f3900f8f25d34a504" 584 | integrity sha512-OHWdRuRMNkTkpO0/1R4ljS4+vpPtf6PvN+S0WTe+PkPB6Z61rmunw4SKTQDkrCt7dDzEIZTnK1G6s7rttd61Bw== 585 | 586 | kaplay@^3001.0.12: 587 | version "3001.0.12" 588 | resolved "https://registry.yarnpkg.com/kaplay/-/kaplay-3001.0.12.tgz#ba1176b03ffbbc85301100af0815f3b7a81c94a6" 589 | integrity sha512-J4QOmQQRgSGg2qkiTwBTYdxGASlXGygIAZQhYIE2I5il5njOoFC7e1OTA5hEQZLjHajc/+lNhKYS8MLY4lgmyg== 590 | 591 | kontra@^8.0.0: 592 | version "8.0.0" 593 | resolved "https://registry.npmjs.org/kontra/-/kontra-8.0.0.tgz" 594 | integrity sha512-7Go/K4S+6gtyillSKSkSlNq46neuIWGvof9PYyzBwiBKGrs+1GHzsDknaeIID3GulV+sK1oKcV0hB2CyyO7ePQ== 595 | 596 | melonjs@^14.5.0: 597 | version "14.5.0" 598 | resolved "https://registry.yarnpkg.com/melonjs/-/melonjs-14.5.0.tgz#4a1393ef85ff12d18de6ac5d7fcaa60ecde2b5e0" 599 | integrity sha512-ePZp3Jm4GlsO242Kk9zSp4Y40yPLvAh8ZlouvitkqVmB7DyeXdvQ+0Q+7VoOEbuuCAnrHfmpS3V4QbSZs6NugQ== 600 | dependencies: 601 | "@teppeis/multimaps" "^2.0.0" 602 | core-js "^3.27.2" 603 | earcut "2.2.4" 604 | eventemitter3 "^5.0.0" 605 | howler "2.2.3" 606 | 607 | minimist@^1.2.5: 608 | version "1.2.7" 609 | resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz" 610 | integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== 611 | 612 | nanoid@^3.3.4: 613 | version "3.3.4" 614 | resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz" 615 | integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== 616 | 617 | neo-async@^2.6.0: 618 | version "2.6.2" 619 | resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz" 620 | integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== 621 | 622 | path-parse@^1.0.7: 623 | version "1.0.7" 624 | resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" 625 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== 626 | 627 | path@^0.12.7: 628 | version "0.12.7" 629 | resolved "https://registry.npmjs.org/path/-/path-0.12.7.tgz" 630 | integrity sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q== 631 | dependencies: 632 | process "^0.11.1" 633 | util "^0.10.3" 634 | 635 | phaser@^3.55.2: 636 | version "3.55.2" 637 | resolved "https://registry.npmjs.org/phaser/-/phaser-3.55.2.tgz" 638 | integrity sha512-amKXsbb2Ht29dGPKvt1edq3yGGYKtq8373GpJYGKPNPnneYY6MtVTOgjHDuZwtmUyK4v86FugkT3hzW/N4tjxQ== 639 | dependencies: 640 | eventemitter3 "^4.0.7" 641 | path "^0.12.7" 642 | 643 | picocolors@^1.0.0: 644 | version "1.0.0" 645 | resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" 646 | integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== 647 | 648 | pixi.js@^7.1.0: 649 | version "7.1.0" 650 | resolved "https://registry.npmjs.org/pixi.js/-/pixi.js-7.1.0.tgz" 651 | integrity sha512-CNWikodkMhOThr8l5fWOLjvkmp2po8Gu3HpQFZnjJK3yukY7z8Nao6XnGlBZy7xq3GFrRTulbvrToAZqql7GRQ== 652 | dependencies: 653 | "@pixi/accessibility" "7.1.0" 654 | "@pixi/app" "7.1.0" 655 | "@pixi/assets" "7.1.0" 656 | "@pixi/compressed-textures" "7.1.0" 657 | "@pixi/core" "7.1.0" 658 | "@pixi/display" "7.1.0" 659 | "@pixi/events" "7.1.0" 660 | "@pixi/extensions" "7.1.0" 661 | "@pixi/extract" "7.1.0" 662 | "@pixi/filter-alpha" "7.1.0" 663 | "@pixi/filter-blur" "7.1.0" 664 | "@pixi/filter-color-matrix" "7.1.0" 665 | "@pixi/filter-displacement" "7.1.0" 666 | "@pixi/filter-fxaa" "7.1.0" 667 | "@pixi/filter-noise" "7.1.0" 668 | "@pixi/graphics" "7.1.0" 669 | "@pixi/mesh" "7.1.0" 670 | "@pixi/mesh-extras" "7.1.0" 671 | "@pixi/mixin-cache-as-bitmap" "7.1.0" 672 | "@pixi/mixin-get-child-by-name" "7.1.0" 673 | "@pixi/mixin-get-global-position" "7.1.0" 674 | "@pixi/particle-container" "7.1.0" 675 | "@pixi/prepare" "7.1.0" 676 | "@pixi/sprite" "7.1.0" 677 | "@pixi/sprite-animated" "7.1.0" 678 | "@pixi/sprite-tiling" "7.1.0" 679 | "@pixi/spritesheet" "7.1.0" 680 | "@pixi/text" "7.1.0" 681 | "@pixi/text-bitmap" "7.1.0" 682 | 683 | postcss@^8.4.13, postcss@^8.4.20: 684 | version "8.4.21" 685 | resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz" 686 | integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg== 687 | dependencies: 688 | nanoid "^3.3.4" 689 | picocolors "^1.0.0" 690 | source-map-js "^1.0.2" 691 | 692 | process@^0.11.1: 693 | version "0.11.10" 694 | resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz" 695 | integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== 696 | 697 | punycode@1.3.2: 698 | version "1.3.2" 699 | resolved "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz" 700 | integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw== 701 | 702 | querystring@0.2.0: 703 | version "0.2.0" 704 | resolved "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz" 705 | integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g== 706 | 707 | resolve@^1.22.0, resolve@^1.22.1: 708 | version "1.22.1" 709 | resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz" 710 | integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== 711 | dependencies: 712 | is-core-module "^2.9.0" 713 | path-parse "^1.0.7" 714 | supports-preserve-symlinks-flag "^1.0.0" 715 | 716 | "rollup@>=2.59.0 <2.78.0": 717 | version "2.77.3" 718 | resolved "https://registry.npmjs.org/rollup/-/rollup-2.77.3.tgz" 719 | integrity sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g== 720 | optionalDependencies: 721 | fsevents "~2.3.2" 722 | 723 | rollup@^3.7.0: 724 | version "3.10.0" 725 | resolved "https://registry.npmjs.org/rollup/-/rollup-3.10.0.tgz" 726 | integrity sha512-JmRYz44NjC1MjVF2VKxc0M1a97vn+cDxeqWmnwyAF4FvpjK8YFdHpaqvQB+3IxCvX05vJxKZkoMDU8TShhmJVA== 727 | optionalDependencies: 728 | fsevents "~2.3.2" 729 | 730 | source-map-js@^1.0.2: 731 | version "1.0.2" 732 | resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz" 733 | integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== 734 | 735 | source-map@^0.6.1: 736 | version "0.6.1" 737 | resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" 738 | integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== 739 | 740 | supports-preserve-symlinks-flag@^1.0.0: 741 | version "1.0.0" 742 | resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" 743 | integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== 744 | 745 | three@^0.148.0: 746 | version "0.148.0" 747 | resolved "https://registry.npmjs.org/three/-/three-0.148.0.tgz" 748 | integrity sha512-8uzVV+qhTPi0bOFs/3te3RW6hb3urL8jYEl6irjCWo/l6sr8MPNMcClFev/MMYeIxr0gmDcoXTy/8LXh/LXkfw== 749 | 750 | two.js@^0.8.10: 751 | version "0.8.10" 752 | resolved "https://registry.yarnpkg.com/two.js/-/two.js-0.8.10.tgz#d01f8bb1c9401f850730a79abb9784b900ccde0b" 753 | integrity sha512-Rg0jn1n/0MWdDBrW47L245GWlWpbWlJ3Cv8xFlP05pSFqUT0Wn6w4yaOcxUCzAJQGRGdtmXOW3UI2W0LEEUD9Q== 754 | 755 | uglify-js@^3.1.4: 756 | version "3.17.4" 757 | resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz" 758 | integrity sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g== 759 | 760 | url@^0.11.0: 761 | version "0.11.0" 762 | resolved "https://registry.npmjs.org/url/-/url-0.11.0.tgz" 763 | integrity sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ== 764 | dependencies: 765 | punycode "1.3.2" 766 | querystring "0.2.0" 767 | 768 | util@^0.10.3: 769 | version "0.10.4" 770 | resolved "https://registry.npmjs.org/util/-/util-0.10.4.tgz" 771 | integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== 772 | dependencies: 773 | inherits "2.0.3" 774 | 775 | vite-plugin-handlebars@^1.6.0: 776 | version "1.6.0" 777 | resolved "https://registry.npmjs.org/vite-plugin-handlebars/-/vite-plugin-handlebars-1.6.0.tgz" 778 | integrity sha512-/TZ2FadScvJW6fmQ+3m3stm6ns+tDZ3VAgzEkSQYQurAnaQ/3MJfidhmTXzD1Hu1iwgkI3lNuEqybzjjKemCTg== 779 | dependencies: 780 | handlebars "^4.7.6" 781 | vite "^2.0.0" 782 | 783 | vite@^2.0.0: 784 | version "2.9.15" 785 | resolved "https://registry.npmjs.org/vite/-/vite-2.9.15.tgz" 786 | integrity sha512-fzMt2jK4vQ3yK56te3Kqpkaeq9DkcZfBbzHwYpobasvgYmP2SoAr6Aic05CsB4CzCZbsDv4sujX3pkEGhLabVQ== 787 | dependencies: 788 | esbuild "^0.14.27" 789 | postcss "^8.4.13" 790 | resolve "^1.22.0" 791 | rollup ">=2.59.0 <2.78.0" 792 | optionalDependencies: 793 | fsevents "~2.3.2" 794 | 795 | vite@^4.0.0: 796 | version "4.0.4" 797 | resolved "https://registry.npmjs.org/vite/-/vite-4.0.4.tgz" 798 | integrity sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw== 799 | dependencies: 800 | esbuild "^0.16.3" 801 | postcss "^8.4.20" 802 | resolve "^1.22.1" 803 | rollup "^3.7.0" 804 | optionalDependencies: 805 | fsevents "~2.3.2" 806 | 807 | wordwrap@^1.0.0: 808 | version "1.0.0" 809 | resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz" 810 | integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== 811 | --------------------------------------------------------------------------------