├── .eslintrc.json ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE.TXT ├── README.md ├── images └── splat-logo.png ├── jsdoc ├── conf.json ├── layout.tmpl └── static │ └── docs.css ├── lib ├── accelerometer.js ├── ads.js ├── assets │ ├── asset-loader.js │ ├── load-asset.js │ ├── load-assets.js │ ├── load-image.js │ └── load-sound.js ├── astar.js ├── binary-heap.js ├── box-pool.js ├── buffer.js ├── callbacks.jsdoc ├── clone.js ├── components │ ├── acceleration.js │ ├── animation.js │ ├── box-collider.js │ ├── collisions.js │ ├── constrain-position.js │ ├── easing.js │ ├── follow.js │ ├── friction.js │ ├── grid.js │ ├── image.js │ ├── index.js │ ├── life-span.js │ ├── match-aspect-ratio.js │ ├── match-center.js │ ├── match.js │ ├── movement-2d.js │ ├── player-controller-2d.js │ ├── position.js │ ├── register.js │ ├── rotation.js │ ├── shake.js │ ├── size.js │ ├── timers.js │ └── velocity.js ├── external.jsdoc ├── font-loader.js ├── game.js ├── iap.js ├── import-from-tiled.js ├── input.js ├── leaderboards.js ├── main.js ├── math.js ├── math2d.js ├── mouse.js ├── ninepatch.js ├── once.js ├── openUrl.js ├── particles.js ├── platform.js ├── prefabs.js ├── random.js ├── save-data.js ├── scene.js ├── set-or-add-component.js ├── sound-manager.js ├── split-filmstrip-animations.js └── systems │ ├── index.js │ ├── renderer │ ├── apply-shake.js │ ├── background-color.js │ ├── clear-screen.js │ ├── draw-frame-rate.js │ ├── draw-image.js │ ├── draw-rectangles.js │ ├── revert-shake.js │ ├── viewport-move-to-camera.js │ └── viewport-reset.js │ └── simulation │ ├── advance-animations.js │ ├── advance-timers.js │ ├── apply-acceleration.js │ ├── apply-easing.js │ ├── apply-friction.js │ ├── apply-movement-2d.js │ ├── apply-velocity.js │ ├── box-collider.js │ ├── box-group-collider.js │ ├── constrain-position.js │ ├── control-player.js │ ├── decay-life-span.js │ ├── follow-mouse.js │ ├── follow-parent.js │ ├── match-aspect-ratio.js │ ├── match-canvas-size.js │ ├── match-center.js │ ├── match-parent.js │ └── set-virtual-buttons.js ├── package.json └── vendor └── FontLoader.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "brace-style": [ 2, "1tbs", { "allowSingleLine": true } ], 4 | "block-spacing": [ 2, "always" ], 5 | "comma-style": [ 2, "last" ], 6 | "eol-last": [ 2, "unix" ], 7 | "indent": [ 2, 2 ], 8 | "key-spacing": [ 2, { "beforeColon": false, "afterColon": true } ], 9 | "keyword-spacing": [ 2 ], 10 | "linebreak-style": [ 2, "unix" ], 11 | "new-cap": [ 2 ], 12 | "new-parens": [ 2 ], 13 | "no-console": [ 0 ], 14 | "no-trailing-spaces": [ 2 ], 15 | "object-curly-spacing": [ 2, "always" ], 16 | "quotes": [ 2, "double" ], 17 | "semi": [ 2, "always" ], 18 | "semi-spacing": [ 2, { "before": false, "after": true } ], 19 | "space-before-blocks": [ 2, "always" ], 20 | "space-before-function-paren": [ 2, "never" ], 21 | "space-in-parens": [ 2, "never" ], 22 | "space-infix-ops": [ 2 ], 23 | "space-unary-ops": [2, { "words": true, "nonwords": false }] 24 | }, 25 | "env": { 26 | "browser": true, 27 | "commonjs": true 28 | }, 29 | "extends": "eslint:recommended" 30 | } 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | docs 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | ## [Unreleased] 6 | 7 | ## [7.6.0] - 2017-01-31 8 | ### Fixed 9 | - Crash in Safari due to missing gamepad suppport 10 | 11 | ### Added 12 | - z-axis support for velocity 13 | 14 | ## [7.5.1] - 2017-01-21 15 | ### Fixed 16 | - Error handling stack traces in `getMultiple` and `setMultiple`. 17 | - Gamepads in Chrome 18 | 19 | ## [7.5.0] - 2016-10-28 20 | ### Fixed 21 | - Bug where sound said it was loaded, but wasn't 22 | - Crash on undefined sound 23 | - Object leak in touch code 24 | - Function leak in draw image system 25 | 26 | ### Added 27 | - Set virtual axes with `game.inputs.setAxis()`. Useful for on-screen joysticks 28 | (coming soon). 29 | 30 | ## [7.4.1] - 2016-10-14 31 | ### Fixed 32 | - Correct the y position of non-tile-size tiles 33 | 34 | ## [7.4.0] - 2016-09-29 35 | ### Fixed 36 | - Filter images before sorting them to improve performance. 37 | 38 | ### Added 39 | - Scene configuration data is now available as `game.sceneConfig`. This can be 40 | useful for configuring systems on a scene-by-scene basis. 41 | - The `box-group-collider` system now supports a list of groups to skip 42 | collision checks against in `game.sceneConfig`. 43 | 44 | ## [7.3.0] - 2016-09-02 45 | ### Added 46 | - New `box-group-collider` system that supports `onEnter`/`onExit`/`script` 47 | event scripts, and allows you to group entities to improve performance. 48 | 49 | ### Deprecated 50 | - `box-collider` system. Upgrade to `box-group-collider`. 51 | 52 | ## [7.2.2] - 2016-08-28 53 | ### Fixed 54 | - Small performance improvement when drawing lots of images. Don't draw anything outside of camera. 55 | 56 | ## [7.2.1] - 2016-08-27 57 | ### Fixed 58 | - Property names of size component 59 | 60 | ## [7.2.0] - 2016-08-27 61 | ### Added 62 | - Support Tiled layer visibility by not importing invisible layers 63 | 64 | ### Changed 65 | - Move some new systems to the correct folder 66 | 67 | ### Fixed 68 | - Use new ECS functions in background-color system 69 | - Use new ECS functions in follow-mouse system 70 | 71 | ## [7.1.0] - 2016-08-23 72 | ### Added 73 | - Support Tiled layer offsets 74 | - Support Tiled zlib & base64 layers 75 | 76 | ## [7.0.0] - 2016-08-23 77 | ### Added 78 | - Multiple scenes can run at the same time. This can be used to draw a UI scene 79 | on top of a game scene. 80 | - Scenes now have a `speed` that effects how fast time passes 81 | 82 | ### Changed 83 | - Systems have been separated into `simulation` and `renderer` folders 84 | - Upgraded to 85 | [`entity-component-system`](https://github.com/ericlathrop/entity-component-system/blob/master/README.md) 86 | v4.x, which is a breaking change that passes through to your game via 87 | `game.entities`. 88 | - Convert the `match-center-x` and `match-center-y` systems to just 89 | `match-center`. 90 | 91 | ## [6.1.0] - 2016-07-02 92 | ### Added 93 | - `importTilemap` can now import "collection of images" tilesets 94 | 95 | ## [6.0.1] - 2016-06-27 96 | ### Fixed 97 | - Handle nonexistant tileset properties on Tiled importer 98 | 99 | ## [6.0.0] - 2016-06-04 100 | ### Changed 101 | - Update renderer systems to only have 2 arguments 102 | ### Removed 103 | - Old touch button support. Not needed since entities can now be buttons 104 | 105 | ## [5.5.1] - 2016-06-04 106 | ### Changed 107 | - Updated `entity-component-system` module. 108 | 109 | ## [5.5.0] - 2016-05-20 110 | ### Added 111 | - Entities can act as virtual buttons now with the `setVirtualButtons` system. 112 | 113 | ## [5.4.0] - 2016-04-23 114 | ### Added 115 | - `applyEasing` system 116 | 117 | ## [5.3.0] - 2016-04-16 118 | ### Fixed 119 | - `matchParent` system now also matches the z property on the `position` component 120 | ### Added 121 | - `decayLifeSpan` system 122 | - `particles` module 123 | 124 | ## [5.2.0] - 2016-04-05 125 | ### Added 126 | - `importTilemap` function for importing [Tiled](http://www.mapeditor.org/) tilemaps. 127 | - `timer` component now has a `loop` flag to make it repeat 128 | - `applyAcceleration` system 129 | - `random.inRange()` & `random.from()` 130 | - `apply-shake` and `revert-shake` systems 131 | - Use `"all"` in `systems.json` for a system to apply to all scenes. This replaces the array. 132 | 133 | ### Changed 134 | - Improved the look of the FPS counter 135 | 136 | ### Fixed 137 | - `game.sounds.setVolume` now works 138 | 139 | ## [5.1.2] - 2016-03-23 140 | ### Fixed 141 | - Updated `html5-gamepad` to fix crash in Safari 142 | 143 | ## [5.1.1] - 2016-03-17 144 | ### Fixed 145 | - Mouse button should default to not pressed 146 | 147 | ## [5.1.0] - 2016-03-13 148 | ### Fixed 149 | - Mouse bug where input sometimes doesn't register 150 | - Make draw ordering stable when entities are on the same Y position 151 | 152 | ### Added 153 | - Add `game.registerPrefab` and `game.registerPrefabs` to create new prefabs at runtime. 154 | - Suppport `alpha` transparency in `image` component. 155 | 156 | ## [5.0.0] - 2016-03-05 157 | ### Removed 158 | - `match-center` system, you should use `match-center-x` and `match-center-y` to achieve the same thing. 159 | 160 | ### Added 161 | - Gamepad support! 162 | 163 | ## [4.1.1] - 2016-02-29 164 | ### Fixed 165 | - Fix `constrainPosition` system 166 | - Fix more places in game.js where `input` needed to be `inputs`. 167 | 168 | ## [4.1.0] - 2016-02-29 169 | ### Fixed 170 | - Fix bug in game.js where `input` needed to be `inputs`. 171 | 172 | ### Added 173 | - Add `matchCenterX` and `matchCenterY` systems 174 | 175 | ## [4.0.0] - 2016-02-28 176 | ### Changed 177 | - Change `contstrain-to-playable-area` to `constrain-position`, and make the system use an entity for the area. 178 | - Renamed `game.input` to `game.inputs`. 179 | - Moved `zindex` component into the `position` component's `z` property. 180 | 181 | ## [3.2.0] - 2015-01-30 182 | ### Added 183 | - Inputs support mouse buttons 184 | 185 | ## [3.1.1] - 2015-01-30 186 | ### Fixed 187 | - allow `game.switchScene()` during scene enter script 188 | 189 | ## [3.1.0] - 2015-01-30 190 | ### Added 191 | - match-center system 192 | 193 | ## [3.0.2] - 2015-12-30 194 | ### Fixed 195 | - Fix soundloader bug. 196 | - Default rotation.x and rotation.y to the center of the entity. 197 | 198 | ## [3.0.1] - 2015-12-30 199 | ### Fixed 200 | - Remove deleted entities from collision lists. 201 | 202 | ## [3.0.0] - 2015-12-30 203 | ### Added 204 | - Add `instantiatePrefab` function to instantiate new entities from prefabs 205 | 206 | ### Changed 207 | - `Game` constructor now loads all the json files by itself. Now it only needs 2 arguments. 208 | 209 | ### Fixed 210 | - animation frame splitting now copies all animation properties, and doesn't lose any 211 | 212 | ## [2.0.0] - 2015-12-28 213 | ### Removed 214 | - remove magical "splatjs:" way of loading systems. 215 | 216 | ## [1.0.0] - 2015-12-28 217 | ### Changed 218 | - automatically size the canvas based on a selectable algorithm. 219 | 220 | ### Added 221 | - matchCanvasSize system to make an entity the same size as the canvas 222 | - matchAspectRatio system to make an entity match the aspect ratio of another entity 223 | 224 | ## [0.7.0] - 2015-12-21 225 | ### Added 226 | - add `Input.buttonPressed()` and `Input.buttonReleased()` 227 | 228 | ## [0.6.2] - 2015-12-21 229 | ### Added 230 | - add warnings about bad image component values and provide defaults for unset values 231 | 232 | ### Fixed 233 | - fix bug where animations wouldn't work 234 | 235 | ## [0.6.1] - 2015-12-20 236 | ### Fixed 237 | - mouse coordinates scale correctly when no css is applied to canvas 238 | 239 | ## [0.6.0] - 2015-12-20 240 | ### Changed 241 | - use box-intersect module for faster collision detection 242 | 243 | ## [0.5.0] - 2015-12-19 244 | ### Added 245 | - window.timeSystems() to log timings of ECS systems 246 | 247 | ### Changed 248 | - Speed up advanceAnimations system 249 | 250 | ## [0.4.2] - 2015-12-19 251 | ### Fixed 252 | - applyMovement2d never found entities 253 | 254 | ## [0.4.1] - 2015-12-17 255 | ### Fixed 256 | - Readme typo 257 | - Format changelog 258 | 259 | ## [0.4.0] - 2015-12-17 260 | ### Changed 261 | - Upgrade to entity-component-system 2.0.0 262 | 263 | ## [0.3.2] 264 | - Add method to reset box collider cache 265 | 266 | ## [0.3.1] 267 | - Un-scale the viewport when it is reset 268 | 269 | ## [0.3.0] 270 | - Support scaling of viewport through camera 271 | - Draw custom buffer for an entity if it is specified 272 | 273 | ## [0.2.0] 274 | - Support rotation when drawing images. 275 | 276 | ## [0.1.1] 277 | - Log more info on no such image error 278 | 279 | ## [0.1.0] 280 | - Add matchParent system. 281 | - Allow sound loop start and end settings 282 | 283 | ## [0.0.1] 284 | - Add a way to remove a deleted entity from the collision detection cache. 285 | 286 | ## [0.0.0] 287 | - Fork from original splatjs project. 288 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of 4 | fostering an open and welcoming community, we pledge to respect all people who 5 | contribute through reporting issues, posting feature requests, updating 6 | documentation, submitting pull requests or patches, and other activities. 7 | 8 | We are committed to making participation in this project a harassment-free 9 | experience for everyone, regardless of level of experience, gender, gender 10 | identity and expression, sexual orientation, disability, personal appearance, 11 | body size, race, ethnicity, age, religion, or nationality. 12 | 13 | Examples of unacceptable behavior by participants include: 14 | 15 | * The use of sexualized language or imagery 16 | * Personal attacks 17 | * Trolling or insulting/derogatory comments 18 | * Public or private harassment 19 | * Publishing other's private information, such as physical or electronic 20 | addresses, without explicit permission 21 | * Other unethical or unprofessional conduct 22 | 23 | Project maintainers have the right and responsibility to remove, edit, or 24 | reject comments, commits, code, wiki edits, issues, and other contributions 25 | that are not aligned to this Code of Conduct, or to ban temporarily or 26 | permanently any contributor for other behaviors that they deem inappropriate, 27 | threatening, offensive, or harmful. 28 | 29 | By adopting this Code of Conduct, project maintainers commit themselves to 30 | fairly and consistently applying these principles to every aspect of managing 31 | this project. Project maintainers who do not follow or enforce the Code of 32 | Conduct may be permanently removed from the project team. 33 | 34 | This Code of Conduct applies both within project spaces and in public spaces 35 | when an individual is representing the project or its community. 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 38 | reported by contacting a project maintainer at eric@ericlathrop.com. All 39 | complaints will be reviewed and investigated and will result in a response that 40 | is deemed necessary and appropriate to the circumstances. Maintainers are 41 | obligated to maintain confidentiality with regard to the reporter of an 42 | incident. 43 | 44 | 45 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 46 | version 1.3.0, available at 47 | [http://contributor-covenant.org/version/1/3/0/][version] 48 | 49 | [homepage]: http://contributor-covenant.org 50 | [version]: http://contributor-covenant.org/version/1/3/0/ 51 | -------------------------------------------------------------------------------- /LICENSE.TXT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Eric Lathrop 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |  2 | 3 | A 2d HTML5 Canvas game engine 4 | 5 | Splat ECS is a 2d game engine made for creating multi-platform games entirely in JavaScript. Splat ECS is built around the [Entity Component System](https://github.com/ericlathrop/entity-component-system) pattern, which is flexible and promotes composition of behaviors. 6 | 7 | # Features 8 | 9 | * Rectangles! 10 | * Keyboard, mouse, touch, & gamepad input 11 | * Sounds and music (Web Audio API and HTML5 Audio) 12 | * Sprite animation 13 | * Asset loading, and built-in loading screen 14 | * Games work well on phones, tablets, and desktop browsers. 15 | * A\* Pathfinding 16 | * Particles 17 | * SCREENSHAKE 18 | * Tiled map editor support 19 | * Easing 20 | 21 | # Supported (tested) Platforms 22 | 23 | * Chrome (desktop & mobile) 24 | * Firefox 25 | * Internet Explorer (desktop & mobile) 26 | * Safari (desktop & mobile) 27 | * Mac using [Electron](https://github.com/atom/electron) 28 | * Linux x64 using [Electron](https://github.com/atom/electron) 29 | * Chrome Web Store (currently broken [see issue #69](https://github.com/SplatJS/splat-ecs/issues/69)) 30 | * Android using [Cordova](https://cordova.apache.org/) 31 | 32 | Splat now works in Cordova, and due to updates to recent phone browsers we have seen good framerates on Android in google Chome. We have not tested Cordova builds on iOS yet, please let us know what you find out. 33 | 34 | # Requirements 35 | * Browser (like Firefox or Chrome) 36 | * Text editor 37 | * Terminal 38 | * [Node.js](https://nodejs.org/en/) 39 | 40 | # New to Splat? 41 | If you are new to Splat, it is highly recommended that you try out the [tutorial project](http://splatjs.com/tutorials/splatformer). 42 | 43 | # Create a new Game 44 | 1. [Clone or download a zip of the starter project](https://github.com/SplatJS/splat-ecs-starter-project) 45 | 46 | 2. (skip this step if you are cloning the repo) The zip file should be called splat-ecs-starter-project-master.zip. Unzip this file and you will be left with a folder named splat-ecs-starter-project. 47 | 48 | 3. In your terminal navigate into the splat-ecs-starter-project folder. 49 | 50 | `cd /Path/To/splat-ecs-starter-project` 51 | 52 | 4. Next we will run npm install to install Splat ECS and all of it's modules: 53 | 54 | `npm install` 55 | 5. This will install all of the game and engine dependencies from NPM — it can take a couple of minutes. If you see any warning (denoted by npm WARN) this is okay, this just means that a package Splat-ECS uses is out of date it should not effect your game and newer versions of Splat-ECS Starter Project will take care of this issue. You will know npm install is finished when the terminal returns to your command prompt (you will see your username). 56 | 57 | 6. To run a Splat ECS game all you need to do is navigate to the project folder in your terminal and type `npm start` 58 | This will run webpack, which builds your game and also runs eslint which checks your JavaScript code for errors. 59 | 60 | 7. You should try running your game to make sure it is working before you continue. When the last line in your terminal reads 'webpack: bundle is now VALID.' this means webpack is done and now you can open a browser and go to `localhost:4000`. 61 | 62 | The Splat ECS sample game is just white screen with a black-outlined square you can control with WASD, or arrow keys. Test that this is working and note that if the keys are not working you may need to click inside the browser window to give the game your 'focus'. 63 | 64 | # Games using Splat (ECS) 65 | * [Cluster Junk](https://github.com/TwoScoopGames/Cluster-Junk) 66 | * [Cali Bunga](https://riseshinegames.itch.io/cali-bunga) 67 | * [Flip Flap Pong](https://riseshinegames.itch.io/flip-flap-pong) 68 | * [Polymorphic](http://riseandshinegames.github.io/Polymorphic/build/) 69 | * [Electropolis](https://two-scoop-games.itch.io/electropolis) 70 | * [Morning Ritual](http://twoscoopgames.com/morningritual/game/) 71 | * [Drunken Boss Fight](http://aquisenberry.itch.io/jam-build) 72 | * [Zen Madness](http://aquisenberry.github.io/ggj_meditate/build/) 73 | * [Treatment and Control](http://twoscoopgames.com/ggj15/) 74 | * [The Day the World Changed](https://github.com/TwoScoopGames/ggj15) 75 | * [Uprooted](http://twoscoopgames.com/ld32/) 76 | 77 | See more Splat games at [http://splatjs.com/](http://splatjs.com/) 78 | 79 | Send a pull request to add your game to the list! 80 | 81 | ## Contributing 82 | 83 | If you are interested in participating in this project, please read [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for details on our code of conduct. 84 | 85 | ## Versioning 86 | 87 | We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/SplatJS/splat-ecs/tags). 88 | 89 | ## Authors 90 | 91 | * **[Eric Lathrop](https://github.com/ericlathrop)** - *Initial work* 92 | 93 | See also the list of [contributors](https://github.com/SplatJS/splat-ecs/contributors) who participated in this project. 94 | 95 | ## License 96 | 97 | This project is licensed under the MIT License - see the [LICENSE.TXT](LICENSE.TXT) file for details 98 | -------------------------------------------------------------------------------- /images/splat-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SplatJS/splat-ecs/c8d4f5f5871c1f02781c6e6adc49a88635124415/images/splat-logo.png -------------------------------------------------------------------------------- /jsdoc/conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": { 3 | "default": { 4 | "layoutFile": "./layout.tmpl", 5 | "staticFiles": { 6 | "include": [ 7 | "./jsdoc/static" 8 | ] 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /jsdoc/layout.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |true
if the timer should repeat after it reaches max
.
11 | * @property {float} max - The maximum amount of time to accumulate. If time
reaches max
, then time
will be set to 0
and running
will be set to false
.
12 | * @property {bool} running - Determines if the timer is accumulating time.
13 | * @property {string} script - The require
path to a script to run when the timer is reset. The path is relative to your game's src
folder. For example ./scripts/next-scene
might execute the code in /src/scripts/next-scene.js
.
14 | * @property {float} time - The amount of time, in milliseconds, the timer has accumulated.
15 | */
16 |
17 | module.exports = {
18 | factory: function() {
19 | return {};
20 | },
21 | reset: function(timers) {
22 | var names = Object.keys(timers);
23 | for (var i = 0; i < names.length; i++) {
24 | delete timers[name[i]];
25 | }
26 | }
27 | };
28 |
--------------------------------------------------------------------------------
/lib/components/velocity.js:
--------------------------------------------------------------------------------
1 | /**
2 | * The speed of an entity in the game world in pixels-per-millisecond.
3 | * @typedef {Object} velocity
4 | * @memberof Components
5 | * @property {float} x - The velocity of this entity along the x-axis.
6 | * @property {float} y - The velocity of this entity along the y-axis.
7 | * @property {float} z - The velocity of this entity along the z-axis.
8 | */
9 |
10 | module.exports = {
11 | factory: function() {
12 | return {
13 | x: 0,
14 | y: 0,
15 | z: 0
16 | };
17 | },
18 | reset: function(velocity) {
19 | velocity.x = 0;
20 | velocity.y = 0;
21 | velocity.z = 0;
22 | }
23 | };
24 |
--------------------------------------------------------------------------------
/lib/external.jsdoc:
--------------------------------------------------------------------------------
1 | /**
2 | * The built-in image DOM element.
3 | * @typedef image
4 | * @external image
5 | * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement Image at the Mozilla Developer Network}
6 | */
7 |
8 | /**
9 | * The built-in canvas DOM element.
10 | * @typedef canvas
11 | * @external canvas
12 | * @see {@link https://developer.mozilla.org/en-US/docs/HTML/Canvas Canvas at the Mozilla Developer Network}
13 | */
14 |
15 | /**
16 | * The built-in 2D rendering context for the drawing surface of a {@link external:canvas}.
17 | * @typedef CanvasRenderingContext2D
18 | * @external CanvasRenderingContext2D
19 | * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D CanvasRenderingContext2D at the Mozilla Developer Network}
20 | */
21 |
22 | /**
23 | * The built-in Web Audio API audio context.
24 | * @typedef AudioContext
25 | * @external AudioContext
26 | * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/AudioContext AudioContext at the Mozilla Developer Network}
27 | */
28 |
29 | /**
30 | * A container for all entities in a scene.
31 | * @typedef EntityPool
32 | * @external EntityPool
33 | * @see {@link https://github.com/ericlathrop/entity-component-system#entitypool EntityPool documentation on Github}
34 | */
35 |
--------------------------------------------------------------------------------
/lib/font-loader.js:
--------------------------------------------------------------------------------
1 | require("../vendor/FontLoader.js");
2 | var platform = require("./platform");
3 |
4 | function buildFontFaceRule(family, urls) {
5 | var eot = urls["embedded-opentype"];
6 | var woff = urls.woff;
7 | var ttf = urls.truetype;
8 | var svg = urls.svg;
9 |
10 | var css = "\n";
11 | css += "@font-face {\n";
12 | css += " font-family: \"" + family + "\";\n";
13 | css += " src: url(\"" + eot + "\");\n";
14 | css += " src: url(\"" + eot + "?iefix\") format(\"embedded-opentype\"),\n";
15 | css += " url(\"" + woff + "\") format(\"woff\"),\n";
16 | css += " url(\"" + ttf + "\") format(\"ttf\"),\n";
17 | css += " url(\"" + svg + "\") format(\"svg\");\n";
18 | css += "}\n";
19 | return css;
20 | }
21 |
22 | function createCssFontFaces(fontFamilies) {
23 | var style = document.createElement("style");
24 | style.setAttribute("type", "text/css");
25 | var css = "";
26 | for (var family in fontFamilies) {
27 | if (fontFamilies.hasOwnProperty(family)) {
28 | css += buildFontFaceRule(family, fontFamilies[family]);
29 | }
30 | }
31 | style.appendChild(document.createTextNode(css));
32 | document.head.appendChild(style);
33 | }
34 |
35 | /**
36 | * Load fonts and lets you know when they're all available. An instance of FontLoader is available as {@link Splat.Game#fonts}.
37 | * @constructor
38 | */
39 | function FontLoader() {
40 | /**
41 | * The total number of fonts to be loaded.
42 | * @member {number}
43 | * @private
44 | */
45 | this.totalFonts = 0;
46 | /**
47 | * The number of fonts that have loaded completely.
48 | * @member {number}
49 | * @private
50 | */
51 | this.loadedFonts = 0;
52 | }
53 | /**
54 | * Load a font.
55 | * @param {object} fontFamilies A key-value object that maps css font-family names to another object that holds paths to the various font files in different formats.
56 | * @example
57 | game.fonts.load({
58 | "pixelade": {
59 | "embedded-opentype": "pixelade/pixelade-webfont.eot",
60 | "woff": "pixelade/pixelade-webfont.woff",
61 | "truetype": "pixelade/pixelade-webfont.ttf",
62 | "svg": "pixelade/pixelade-webfont.svg#pixeladeregular"
63 | }
64 | });
65 | */
66 | FontLoader.prototype.load = function(fontFamilies) {
67 | createCssFontFaces(fontFamilies);
68 |
69 | var families = [];
70 | for (var family in fontFamilies) {
71 | if (families.hasOwnProperty(family)) {
72 | families.push(family);
73 | }
74 | }
75 | this.totalFonts += families.length;
76 |
77 | var self = this;
78 | var loader = new window.FontLoader(families, {
79 | "fontLoaded": function() {
80 | self.loadedFonts++;
81 | }
82 | });
83 | loader.loadFonts();
84 | };
85 | /**
86 | * Test if all font fonts have loaded.
87 | * @returns {boolean}
88 | */
89 | FontLoader.prototype.allLoaded = function() {
90 | return this.totalFonts === this.loadedFonts;
91 | };
92 |
93 | /**
94 | * An alternate {@link FontLoader} when the game is running inside [Ejecta]{@link http://impactjs.com/ejecta}. You shouldn't need to worry about this.
95 | * @constructor
96 | * @private
97 | */
98 | function EjectaFontLoader() {
99 | this.totalFonts = 0;
100 | this.loadedFonts = 0;
101 | }
102 | /**
103 | * See {@link FontLoader#load}.
104 | */
105 | EjectaFontLoader.prototype.load = function(fontFamilies) {
106 | for (var family in fontFamilies) {
107 | if (fontFamilies.hasOwnProperty(family)) {
108 | var fontPath = fontFamilies[family].truetype;
109 | if (fontPath) {
110 | window.ejecta.loadFont(fontPath);
111 | }
112 | }
113 | }
114 | };
115 | /**
116 | * See {@link FontLoader#allLoaded}.
117 | */
118 | EjectaFontLoader.prototype.allLoaded = function() {
119 | return true;
120 | };
121 |
122 | if (platform.isEjecta()) {
123 | module.exports = EjectaFontLoader;
124 | } else {
125 | module.exports = FontLoader;
126 | }
127 |
--------------------------------------------------------------------------------
/lib/game.js:
--------------------------------------------------------------------------------
1 | var AssetLoader = require("./assets/asset-loader");
2 | var loadImage = require("./assets/load-image");
3 | var Input = require("./input");
4 | var Prefabs = require("./prefabs");
5 | var Scene = require("./scene");
6 | var SoundManager = require("./sound-manager");
7 | var splitFilmStripAnimations = require("./split-filmstrip-animations");
8 |
9 | function Game(canvas, customRequire) {
10 | this.animations = customRequire("./data/animations");
11 | splitFilmStripAnimations(this.animations);
12 | this.canvas = canvas;
13 | this.context = canvas.getContext("2d");
14 | this.images = new AssetLoader(customRequire("./data/images"), loadImage);
15 | this.inputs = new Input(customRequire("./data/inputs"), canvas);
16 | this.require = customRequire;
17 | this.sounds = new SoundManager(customRequire("./data/sounds"));
18 | this.prefabs = new Prefabs(customRequire("./data/prefabs"));
19 | this.lastTime = -1;
20 | this.remainingDebugTime = undefined;
21 |
22 | this.scaleCanvasToCssSize();
23 | window.addEventListener("resize", this.onCanvasResize.bind(this));
24 |
25 | this.scenes = this.makeScenes(customRequire("./data/scenes"));
26 | this.run = this.run.bind(this);
27 |
28 | this.timings = [];
29 | window.timingIdx = -1;
30 | for (var i = 0; i < 100; i++) {
31 | this.timings.push({
32 | elapsed: 0,
33 | totalTime: 0,
34 | simulationTime: 0,
35 | rendererTime: 0 });
36 | }
37 | }
38 | Game.prototype.makeScenes = function(sceneList) {
39 | var names = Object.keys(sceneList);
40 | var scenes = {};
41 | for (var i = 0; i < names.length; i++) {
42 | var name = names[i];
43 | scenes[name] = new Scene(name, {
44 | animations: this.animations,
45 | canvas: this.canvas,
46 | context: this.context,
47 | images: this.images,
48 | inputs: this.inputs,
49 | prefabs: this.prefabs,
50 | require: this.require,
51 | scaleCanvasToCssSize: this.scaleCanvasToCssSize.bind(this),
52 | scaleCanvasToFitRectangle: this.scaleCanvasToFitRectangle.bind(this),
53 | scenes: scenes,
54 | sounds: this.sounds
55 | });
56 | if (sceneList[name].first) {
57 | scenes[name].start();
58 | }
59 | }
60 | return scenes;
61 | };
62 | Game.prototype.start = function() {
63 | if (this.running) {
64 | return;
65 | }
66 | this.running = true;
67 | this.lastTime = -1;
68 | window.requestAnimationFrame(this.run);
69 | };
70 | Game.prototype.stop = function() {
71 | this.running = false;
72 | };
73 | Game.prototype.run = function(time) {
74 | var scenes = Object.keys(this.scenes);
75 |
76 | if (this.lastTime === -1) {
77 | this.lastTime = time;
78 | }
79 | var elapsed = time - this.lastTime;
80 | this.lastTime = time;
81 |
82 | var simulationStart = window.performance.now();
83 | for (var i = 0; i < scenes.length; i++) {
84 | var name = scenes[i];
85 | var scene = this.scenes[name];
86 | scene.simulate(elapsed);
87 | }
88 | var simulationEnd = window.performance.now();
89 |
90 | for (i = 0; i < scenes.length; i++) {
91 | name = scenes[i];
92 | scene = this.scenes[name];
93 | this.context.save();
94 | scene.render(elapsed);
95 | this.context.restore();
96 | }
97 | var renderEnd = window.performance.now();
98 |
99 | if (window.timingIdx >= 0) {
100 | this.timings[window.timingIdx].elapsed = elapsed;
101 | this.timings[window.timingIdx].simulationTime = simulationEnd - simulationStart;
102 | this.timings[window.timingIdx].rendererTime = renderEnd - simulationEnd;
103 | this.timings[window.timingIdx].totalTime = renderEnd - simulationStart;
104 | window.timingIdx++;
105 | }
106 | if (window.timingIdx >= this.timings.length) {
107 | window.timingIdx = -1;
108 | console.table(this.timings);
109 | }
110 |
111 | if (this.remainingDebugTime !== undefined) {
112 | this.remainingDebugTime -= elapsed;
113 | if (this.remainingDebugTime <= 0) {
114 | this.remainingDebugTime = undefined;
115 | this.logDebugTimes();
116 | }
117 | }
118 |
119 | if (this.running) {
120 | window.requestAnimationFrame(this.run);
121 | }
122 | };
123 | Game.prototype.timeSystems = function(total) {
124 | var scenes = Object.keys(this.scenes);
125 | for (var i = 0; i < scenes.length; i++) {
126 | var name = scenes[i];
127 | var scene = this.scenes[name];
128 | scene.simulation.resetTimings();
129 | scene.renderer.resetTimings();
130 | }
131 | this.remainingDebugTime = total;
132 | };
133 | Game.prototype.logDebugTimes = function() {
134 | var scenes = Object.keys(this.scenes);
135 | var timings = [];
136 | for (var i = 0; i < scenes.length; i++) {
137 | var name = scenes[i];
138 | var scene = this.scenes[name];
139 | timings = timings.concat(scene.simulation.timings());
140 | timings = timings.concat(scene.renderer.timings());
141 | }
142 | console.table(groupTimings(timings));
143 | };
144 | function groupTimings(timings) {
145 | var total = timings.map(function(timing) {
146 | return timing.time;
147 | }).reduce(function(a, b) {
148 | return a + b;
149 | });
150 | timings.sort(function(a, b) {
151 | return b.time - a.time;
152 | }).forEach(function(timing) {
153 | timing.percent = timing.time / total;
154 | });
155 | return timings;
156 | }
157 | Game.prototype.onCanvasResize = function() {
158 | this.resizer();
159 | };
160 | Game.prototype.scaleCanvasToCssSize = function() {
161 | this.resizer = function() {
162 | var canvasStyle = window.getComputedStyle(this.canvas);
163 | var width = parseInt(canvasStyle.width);
164 | var height = parseInt(canvasStyle.height);
165 | this.canvas.width = width;
166 | this.canvas.height = height;
167 | }.bind(this);
168 | this.resizer();
169 | };
170 | Game.prototype.scaleCanvasToFitRectangle = function(width, height) {
171 | this.resizer = function() {
172 | var canvasStyle = window.getComputedStyle(this.canvas);
173 | var cssWidth = parseInt(canvasStyle.width);
174 | var cssHeight = parseInt(canvasStyle.height);
175 | var cssAspectRatio = cssWidth / cssHeight;
176 |
177 | var desiredWidth = width;
178 | var desiredHeight = height;
179 | var desiredAspectRatio = width / height;
180 | if (desiredAspectRatio > cssAspectRatio) {
181 | desiredHeight = Math.floor(width / cssAspectRatio);
182 | } else if (desiredAspectRatio < cssAspectRatio) {
183 | desiredWidth = Math.floor(height * cssAspectRatio);
184 | }
185 |
186 | this.canvas.width = desiredWidth;
187 | this.canvas.height = desiredHeight;
188 | }.bind(this);
189 | this.resizer();
190 | };
191 |
192 | module.exports = Game;
193 |
--------------------------------------------------------------------------------
/lib/iap.js:
--------------------------------------------------------------------------------
1 | var platform = require("./platform");
2 |
3 | if (platform.isEjecta()) {
4 | var iap = new window.Ejecta.IAPManager();
5 |
6 | module.exports = {
7 | "get": function(sku, callback) {
8 | iap.getProducts([sku], function(err, products) {
9 | if (err) {
10 | callback(err);
11 | return;
12 | }
13 | callback(undefined, products[0]);
14 | });
15 | },
16 | "buy": function(product, quantity, callback) {
17 | product.purchase(quantity, callback);
18 | },
19 | "restore": function(callback) {
20 | iap.restoreTransactions(function(err, transactions) {
21 | if (err) {
22 | callback(err);
23 | return;
24 | }
25 | callback(undefined, transactions.map(function(transaction) {
26 | return transaction.productId;
27 | }));
28 | });
29 | }
30 | };
31 | } else if (platform.isChromeApp()) {
32 | // FIXME: needs google's buy.js included
33 | // https://developer.chrome.com/webstore/payments-iap
34 | module.exports = {
35 | "get": function(sku, callback) {
36 | window.google.payments.inapp.getSkuDetails({
37 | "parameters": {
38 | "env": "prod"
39 | },
40 | "sku": sku,
41 | "success": function(response) {
42 | callback(undefined, response.response.details.inAppProducts[0]);
43 | },
44 | "failure": function(response) {
45 | callback(response);
46 | }
47 | });
48 | },
49 | "buy": function(product, quantity, callback) {
50 | window.google.payments.inapp.buy({
51 | "parameters": {
52 | "env": "prod"
53 | },
54 | "sku": product.sku,
55 | "success": function(response) {
56 | callback(undefined, response);
57 | },
58 | "failure": function(response) {
59 | callback(response);
60 | }
61 | });
62 | },
63 | "restore": function(callback) {
64 | window.google.payments.inapp.getPurchases({
65 | "success": function(response) {
66 | callback(undefined, response.response.details.map(function(detail) {
67 | return detail.sku;
68 | }));
69 | },
70 | "failure": function(response) {
71 | callback(response);
72 | }
73 | });
74 | }
75 | };
76 | } else {
77 | module.exports = {
78 | "get": function(sku, callback) {
79 | callback(undefined, undefined);
80 | },
81 | "buy": function(product, quantity, callback) {
82 | callback(undefined);
83 | },
84 | "restore": function(callback) {
85 | callback(undefined, []);
86 | }
87 | };
88 | }
89 |
--------------------------------------------------------------------------------
/lib/import-from-tiled.js:
--------------------------------------------------------------------------------
1 | /** @module splat-ecs/lib/import-from-tiled */
2 |
3 | var clone = require("./clone");
4 | var pako = require("pako");
5 | var path = require("path");
6 | var setOrAddComponent = require("./set-or-add-component");
7 |
8 | /**
9 | * Import an orthogonal tilemap from the Tiled map editor.
10 | *This will create entities in the current scene representing all the tiles & objects in the tilemap. Each tile gets a tile
component tag so you can find them later. Each tile also gets a grid
component with the x/y/z grid coordinates in tile space.
All of the properties that are in the tilemap are set as components in each tile entity, and the property values are treated as JSON. More specific properties override less specific properties. The order of precedence for tile properties is:
12 | *The order of precendence for object properties is:
18 | *A special "container" entity is also created with a container
component tag that has the dimensions of the map in its size
component.
config
.
8 | * @param {object} game The game
object that you get in systems and scripts.
9 | * @param {module:splat-ecs/lib/particles.Config} config The settings to use to create the particles.
10 | */
11 | "create": function(game, config) {
12 | var particleCount = Math.floor(random.inRange(config.qtyMin, config.qtyMax));
13 | for (var i = 0; i < particleCount; i++) {
14 | var particle = game.prefabs.instantiate(game.entities, config.prefab);
15 | // check if origin is an entity
16 | var origin = config.origin;
17 | if (typeof config.origin === "number") {
18 | origin = choosePointInEntity(game, origin);
19 | }
20 |
21 | var randomSize = random.inRange(config.sizeMin, config.sizeMax);
22 | scaleEntityRect(game, particle, randomSize);
23 |
24 | centerEntityOnPoint(game, particle, origin);
25 |
26 | var velocity = random.inRange(config.velocityMin, config.velocityMax);
27 |
28 | var angle = pickAngle(config, i, particleCount);
29 | var velocityComponent = game.entities.addComponent(particle, "velocity");
30 | var direction = pointOnCircle(angle, velocity);
31 | velocityComponent.x = direction.x;
32 | velocityComponent.y = direction.y;
33 |
34 | if (config.accelerationX || config.accelerationY) {
35 | var accel = game.entities.addComponent(particle, "acceleration");
36 | accel.x = config.accelerationX;
37 | accel.y = config.accelerationY;
38 | }
39 | var lifeSpan = game.entities.addComponent(particle, "lifeSpan");
40 | lifeSpan.max = random.inRange(config.lifeSpanMin, config.lifeSpanMax);
41 | }
42 | },
43 |
44 | /**
45 | * The settings for a type of particle.
46 | * @constructor
47 | * @param {string} prefab The name of a prefab to instantiate for the particle, as defined in prefabs.json
.
48 | */
49 | "Config": function(prefab) {
50 | /**
51 | * The name of a prefab to instantiate for the particle, as defined in prefabs.json
.
52 | * @member {string}
53 | */
54 | this.prefab = prefab;
55 | /**
56 | * The origin point in which to create particles.
57 | *
58 | * If the origin is a number it represents an entity and a random point inside the entity will be used.
59 | * If origin is a point like {"x": 50, "y": 50}
particles will spawn at that position.
60 | * @member {object | number}
61 | */
62 | this.origin = { "x": 0, "y": 0 };
63 | /**
64 | * How to distribute particles along the {@link module:splat-ecs/lib/particles.Config#arcWidth arcWidth}.
65 | *
66 | * Possible values:
67 | * "even"
"random"
{"x": 50, "y": 50}
on which to center the entity.
178 | */
179 | function centerEntityOnPoint(game, entity, point) {
180 | var size = game.entities.getComponent(entity, "size");
181 | var position = game.entities.addComponent(entity, "position");
182 | position.x = point.x - (size.width / 2);
183 | position.y = point.y - (size.height / 2);
184 | }
185 |
186 | /**
187 | * Choose a random point inside the bounding rectangle of an entity.
188 | * @private
189 | * @param {object} game Required for game.entities.get().
190 | * @param {integer} entity The id of entity to pick a point within.
191 | * @returns {object} an point object {"x": 50, "y": 50}
.
192 | */
193 | function choosePointInEntity(game, entity) {
194 | var position = game.entities.getComponent(entity, "position");
195 | var size = game.entities.getComponent(entity, "size");
196 | if (size === undefined) {
197 | return {
198 | "x": position.x,
199 | "y": position.y
200 | };
201 | }
202 | return {
203 | "x": random.inRange(position.x, (position.x + size.width)),
204 | "y": random.inRange(position.y, (position.y + size.height))
205 | };
206 | }
207 |
--------------------------------------------------------------------------------
/lib/platform.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | isChromeApp: function() {
3 | return window.chrome && window.chrome.app && window.chrome.app.runtime;
4 | },
5 | isEjecta: function() {
6 | return window.ejecta;
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/lib/prefabs.js:
--------------------------------------------------------------------------------
1 | var clone = require("./clone");
2 | var setOrAddComponent = require("./set-or-add-component");
3 |
4 | function Prefabs(prefabs) {
5 | this.prefabs = prefabs;
6 | }
7 | Prefabs.prototype.instantiate = function(entities, name) {
8 | var id = entities.create();
9 | var prefab = this.prefabs[name];
10 | Object.keys(prefab).forEach(function(key) {
11 | if (key === "id") {
12 | return;
13 | }
14 | setOrAddComponent(entities, id, key, clone(prefab[key]));
15 | });
16 | return id;
17 | };
18 | Prefabs.prototype.register = function(name, components) {
19 | this.prefabs[name] = components;
20 | };
21 | Prefabs.prototype.registerMultiple = function(prefabs) {
22 | Object.keys(prefabs).forEach(function(key) {
23 | this.registerPrefab(key, prefabs[key]);
24 | }.bind(this));
25 | };
26 |
27 | module.exports = Prefabs;
28 |
--------------------------------------------------------------------------------
/lib/random.js:
--------------------------------------------------------------------------------
1 | /** @module splat-ecs/lib/random */
2 |
3 | module.exports = {
4 | /**
5 | * Get a pseudo-random number between the minimum (inclusive) and maximum (exclusive) parameters.
6 | * @function inRange
7 | * @param {number} min Inclusive minimum value for the random number
8 | * @param {number} max Exclusive maximum value for the random number
9 | * @returns {number} A number between min
and max
10 | * @see [Bracket Notation: Inclusion and Exclusion]{@link https://en.wikipedia.org/wiki/Bracket_%28mathematics%29#Intervals}
11 | * @example
12 | var random = require("splat-ecs/lib/random");
13 | random.inRange(0, 1) // Returns 0.345822917402371
14 | random.inRange(10, 100) // Returns 42.4823819274931274
15 | */
16 | "inRange": function(min, max) {
17 | return min + Math.random() * (max - min);
18 | },
19 |
20 | /**
21 | * Get a random element in an array
22 | * @function from
23 | * @param {array} array Array of elements to choose from
24 | * @returns {Object} A random element from the given array
25 | * @example
26 | var random = require("splat-ecs/lib/random");
27 | var fruit = ["Apple", "Banana", "Orange", "Peach"];
28 | random.from(fruit); // Could return "Orange"
29 | random.from(fruit); // Could return "Apple"
30 | random.from(fruit); // Could return "Peach"
31 | random.from(fruit); // Could return "Banana"
32 | */
33 | "from": function(array) {
34 | return array[Math.floor(Math.random() * array.length)];
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/lib/save-data.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @namespace Splat.saveData
3 | */
4 |
5 | var platform = require("./platform");
6 |
7 | function cookieGet(name) {
8 | var value = "; " + document.cookie;
9 | var parts = value.split("; " + name + "=");
10 | if (parts.length === 2) {
11 | return parts.pop().split(";").shift();
12 | } else {
13 | throw "cookie " + name + " was not found";
14 | }
15 | }
16 |
17 | function cookieSet(name, value) {
18 | var expire = new Date();
19 | expire.setTime(expire.getTime() + 1000 * 60 * 60 * 24 * 365);
20 | var cookie = name + "=" + value + "; expires=" + expire.toUTCString() + ";";
21 | document.cookie = cookie;
22 | }
23 |
24 | function getMultiple(getSingleFunc, keys, callback) {
25 | if (typeof keys === "string") {
26 | keys = [keys];
27 | }
28 |
29 | var data;
30 | try {
31 | data = keys.map(function(key) {
32 | return [key, getSingleFunc(key)];
33 | }).reduce(function(accum, pair) {
34 | accum[pair[0]] = pair[1];
35 | return accum;
36 | }, {});
37 | } catch (e) {
38 | return callback(e);
39 | }
40 | callback(undefined, data);
41 | }
42 |
43 | function setMultiple(setSingleFunc, data, callback) {
44 | try {
45 | for (var key in data) {
46 | if (data.hasOwnProperty(key)) {
47 | setSingleFunc(key, data[key]);
48 | }
49 | }
50 | } catch (e) {
51 | return callback(e);
52 | }
53 | callback();
54 | }
55 |
56 | var cookieSaveData = {
57 | "get": getMultiple.bind(undefined, cookieGet),
58 | "set": setMultiple.bind(undefined, cookieSet)
59 | };
60 |
61 | function localStorageGet(name) {
62 | return window.localStorage.getItem(name);
63 | }
64 |
65 | function localStorageSet(name, value) {
66 | window.localStorage.setItem(name, value.toString());
67 | }
68 |
69 | var localStorageSaveData = {
70 | "get": getMultiple.bind(undefined, localStorageGet),
71 | "set": setMultiple.bind(undefined, localStorageSet)
72 | };
73 |
74 | /**
75 | * A function that is called when save data has finished being retrieved.
76 | * @callback saveDataGetFinished
77 | * @param {error} err If defined, err is the error that occurred when retrieving the data.
78 | * @param {object} data The key-value pairs of data that were previously saved.
79 | */
80 | /**
81 | * Retrieve data previously stored with {@link Splat.saveData.set}.
82 | * @alias Splat.saveData.get
83 | * @param {string | Array} keys A single key or array of key names of data items to retrieve.
84 | * @param {saveDataGetFinished} callback A callback that is called with the data when it has been retrieved.
85 | */
86 | function chromeStorageGet(keys, callback) {
87 | window.chrome.storage.sync.get(keys, function(data) {
88 | if (window.chrome.runtime.lastError) {
89 | callback(window.chrome.runtime.lastError);
90 | } else {
91 | callback(undefined, data);
92 | }
93 | });
94 | }
95 |
96 | /**
97 | * A function that is called when save data has finished being stored.
98 | * @callback saveDataSetFinished
99 | * @param {error} err If defined, err is the error that occurred when saving the data.
100 | */
101 | /**
102 | * Store data for later.
103 | * @alias Splat.saveData.set
104 | * @param {object} data An object containing key-value pairs of data to save.
105 | * @param {saveDataSetFinished} callback A callback that is called when the data has finished saving.
106 | */
107 | function chromeStorageSet(data, callback) {
108 | window.chrome.storage.sync.set(data, function() {
109 | callback(window.chrome.runtime.lastError);
110 | });
111 | }
112 |
113 | var chromeStorageSaveData = {
114 | "get": chromeStorageGet,
115 | "set": chromeStorageSet
116 | };
117 |
118 | if (platform.isChromeApp()) {
119 | module.exports = chromeStorageSaveData;
120 | } else if (window.localStorage) {
121 | module.exports = localStorageSaveData;
122 | } else {
123 | module.exports = cookieSaveData;
124 | }
125 |
--------------------------------------------------------------------------------
/lib/scene.js:
--------------------------------------------------------------------------------
1 | var clone = require("./clone");
2 | var components = require("./components");
3 | var ECS = require("entity-component-system").EntityComponentSystem;
4 | var EntityPool = require("entity-component-system").EntityPool;
5 | var registerComponents = require("./components/register");
6 |
7 | function Scene(name, globals) {
8 | this.data = {};
9 | this.entities = new EntityPool();
10 | this.globals = globals;
11 | this.name = name;
12 | this.onEnter = function() {};
13 | this.onExit = function() {};
14 | this.renderer = new ECS();
15 | this.state = "stopped";
16 | this.speed = 1.0;
17 | this.simulation = new ECS();
18 | this.simulationStepTime = 5;
19 |
20 | this.firstTime = true;
21 | this.accumTime = 0;
22 |
23 | this.sceneConfig = globals.require("./data/scenes")[name];
24 | if (typeof this.sceneConfig.onEnter === "string") {
25 | this.onEnter = globals.require(this.sceneConfig.onEnter);
26 | }
27 | if (typeof this.sceneConfig.onExit === "string") {
28 | this.onExit = globals.require(this.sceneConfig.onExit);
29 | }
30 | }
31 | Scene.prototype.start = function(sceneArgs) {
32 | if (this.state !== "stopped") {
33 | return;
34 | }
35 | this.state = "starting";
36 | this.tempArguments = sceneArgs;
37 | };
38 | Scene.prototype._initialize = function() {
39 | this.entities = new EntityPool();
40 | this.firstTime = true;
41 | this.accumTime = 0;
42 |
43 | this.data = {
44 | animations: this.globals.animations,
45 | arguments: this.tempArguments || {},
46 | canvas: this.globals.canvas,
47 | context: this.globals.context,
48 | entities: this.entities,
49 | images: this.globals.images,
50 | inputs: this.globals.inputs,
51 | prefabs: this.globals.prefabs,
52 | require: this.globals.require,
53 | scaleCanvasToCssSize: this.globals.scaleCanvasToCssSize,
54 | scaleCanvasToFitRectangle: this.globals.scaleCanvasToFitRectangle,
55 | sceneConfig: this.sceneConfig,
56 | scenes: this.globals.scenes,
57 | sounds: this.globals.sounds,
58 | switchScene: this.switchScene.bind(this)
59 | };
60 |
61 | this.simulation = new ECS();
62 | this.renderer = new ECS();
63 | this.simulation.add(function processInputUpdates() {
64 | this.globals.inputs.processUpdates();
65 | }.bind(this));
66 |
67 | var systems = this.globals.require("./data/systems");
68 | this.installSystems(systems.simulation, this.simulation, this.data);
69 | this.installSystems(systems.renderer, this.renderer, this.data);
70 |
71 | registerComponents(this.entities, components);
72 | registerComponents(this.entities, this.globals.require("./data/components"));
73 | var entities = this.globals.require("./data/entities");
74 | this.entities.load(clone(entities[this.name]) || []);
75 |
76 | this.onEnter(this.data);
77 | };
78 | Scene.prototype.stop = function() {
79 | if (this.state === "stopped") {
80 | return;
81 | }
82 | this.state = "stopped";
83 | this.onExit(this.data);
84 | };
85 | Scene.prototype.switchScene = function(scene, sceneArgs) {
86 | this.stop();
87 | this.data.scenes[scene].start(sceneArgs);
88 | };
89 | Scene.prototype.installSystems = function(systems, ecs, data) {
90 | for (var i = 0; i < systems.length; i++) {
91 | var system = systems[i];
92 |
93 | if (system.scenes.indexOf(this.name) === -1 && system.scenes !== "all") {
94 | continue;
95 | }
96 | var script = this.globals.require(system.name);
97 | if (script === undefined) {
98 | console.error("failed to load script", system.name);
99 | }
100 | script(ecs, data);
101 | }
102 | };
103 | Scene.prototype.simulate = function(elapsed) {
104 | if (this.state === "stopped") {
105 | return;
106 | }
107 | if (this.state === "starting") {
108 | var start = window.performance.now();
109 | this._initialize();
110 | var end = window.performance.now();
111 | this.accumTime = start - end; // negative so a long initialize doesn't make the first few frames slow
112 | this.state = "started";
113 | }
114 |
115 | if (this.firstTime) {
116 | this.firstTime = false;
117 | // run simulation the first time, because not enough time will have elapsed
118 | this.simulation.run(this.entities, 0);
119 | elapsed = 0;
120 | }
121 |
122 | elapsed *= this.speed;
123 |
124 | this.accumTime += elapsed;
125 | while (this.accumTime >= this.simulationStepTime) {
126 | this.accumTime -= this.simulationStepTime;
127 | this.simulation.run(this.entities, this.simulationStepTime);
128 | }
129 | };
130 | Scene.prototype.render = function(elapsed) {
131 | if (this.state !== "started") {
132 | return;
133 | }
134 | this.renderer.run(this.entities, elapsed);
135 | };
136 |
137 | module.exports = Scene;
138 |
--------------------------------------------------------------------------------
/lib/set-or-add-component.js:
--------------------------------------------------------------------------------
1 | module.exports = function setOrAddComponent(entities, entity, component, value) {
2 | if (typeof value !== "object") {
3 | entities.setComponent(entity, component, value);
4 | } else {
5 | var data = entities.addComponent(entity, component);
6 | Object.keys(value).forEach(function(valKey) {
7 | data[valKey] = value[valKey];
8 | });
9 | }
10 | };
11 |
--------------------------------------------------------------------------------
/lib/sound-manager.js:
--------------------------------------------------------------------------------
1 | var AssetLoader = require("./assets/asset-loader");
2 | var loadSound = require("./assets/load-sound");
3 |
4 | window.AudioContext = window.AudioContext || window.webkitAudioContext;
5 |
6 | /**
7 | * Plays audio, tracks looping sounds, and manages volume.
8 | * This implementation uses the Web Audio API.
9 | * @constructor
10 | * @param {Object} manifest A hash where the key is the name of a sound, and the value is the URL of a sound file.
11 | */
12 | function SoundManager(manifest) {
13 | /**
14 | * A flag signifying if sounds have been muted through {@link SoundManager#mute}.
15 | * @member {boolean}
16 | * @private
17 | */
18 | this.muted = false;
19 | /**
20 | * A key-value object that stores named looping sounds.
21 | * @member {object}
22 | * @private
23 | */
24 | this.looping = {};
25 |
26 | /**
27 | * The Web Audio API AudioContext
28 | * @member {external:AudioContext}
29 | * @private
30 | */
31 | this.context = new window.AudioContext();
32 |
33 | this.gainNode = this.context.createGain();
34 | this.gainNode.connect(this.context.destination);
35 | this.volume = this.gainNode.gain.value;
36 | this.installSafariWorkaround();
37 | this.assets = new AssetLoader(manifest, loadSound.bind(undefined, this.context));
38 | }
39 | SoundManager.prototype.installSafariWorkaround = function() {
40 | // safari on iOS mutes sounds until they're played in response to user input
41 | // play a dummy sound on first touch
42 | var firstTouchHandler = function() {
43 | window.removeEventListener("click", firstTouchHandler);
44 | window.removeEventListener("keydown", firstTouchHandler);
45 | window.removeEventListener("touchstart", firstTouchHandler);
46 |
47 | var source = this.context.createOscillator();
48 | source.connect(this.gainNode);
49 | source.start(0);
50 | source.stop(0);
51 |
52 | if (this.firstPlay) {
53 | this.play(this.firstPlay, this.firstPlayLoop);
54 | } else {
55 | this.firstPlay = "workaround";
56 | }
57 | }.bind(this);
58 | window.addEventListener("click", firstTouchHandler);
59 | window.addEventListener("keydown", firstTouchHandler);
60 | window.addEventListener("touchstart", firstTouchHandler);
61 | };
62 | /**
63 | * Play a sound.
64 | * @param {string} name The name of the sound to play.
65 | * @param {Object} [loop=undefined] A hash containing loopStart and loopEnd options. To stop a looped sound use {@link SoundManager#stop}.
66 | */
67 | SoundManager.prototype.play = function(name, loop) {
68 | if (loop && this.looping[name]) {
69 | return;
70 | }
71 | if (!this.firstPlay) {
72 | // let the iOS user input workaround handle it
73 | this.firstPlay = name;
74 | this.firstPlayLoop = loop;
75 | return;
76 | }
77 | var snd = this.assets.get(name);
78 | if (snd === undefined) {
79 | console.error("Unknown sound: " + name);
80 | return;
81 | }
82 | var source = this.context.createBufferSource();
83 | source.buffer = snd;
84 | source.connect(this.gainNode);
85 | if (loop) {
86 | source.loop = true;
87 | source.loopStart = loop.loopStart || 0;
88 | source.loopEnd = loop.loopEnd || 0;
89 | this.looping[name] = source;
90 | }
91 | source.start(0);
92 | };
93 | /**
94 | * Stop playing a sound. This currently only stops playing a sound that was looped earlier, and doesn't stop a sound mid-play. Patches welcome.
95 | * @param {string} name The name of the sound to stop looping.
96 | */
97 | SoundManager.prototype.stop = function(name) {
98 | if (!this.looping[name]) {
99 | return;
100 | }
101 | this.looping[name].stop(0);
102 | delete this.looping[name];
103 | };
104 | /**
105 | * Silence all sounds. Sounds keep playing, but at zero volume. Call {@link SoundManager#unmute} to restore the previous volume level.
106 | */
107 | SoundManager.prototype.mute = function() {
108 | this.gainNode.gain.value = 0;
109 | this.muted = true;
110 | };
111 | /**
112 | * Restore volume to whatever value it was before {@link SoundManager#mute} was called.
113 | */
114 | SoundManager.prototype.unmute = function() {
115 | this.gainNode.gain.value = this.volume;
116 | this.muted = false;
117 | };
118 | /**
119 | * Set the volume of all sounds.
120 | * @param {number} gain The desired volume level. A number between 0.0 and 1.0, with 0.0 being silent, and 1.0 being maximum volume.
121 | */
122 | SoundManager.prototype.setVolume = function(gain) {
123 | this.volume = gain;
124 | this.gainNode.gain.value = gain;
125 | this.muted = false;
126 | };
127 | /**
128 | * Test if the volume is currently muted.
129 | * @return {boolean} True if the volume is currently muted.
130 | */
131 | SoundManager.prototype.isMuted = function() {
132 | return this.muted;
133 | };
134 |
135 |
136 | function FakeSoundManager() {}
137 | FakeSoundManager.prototype.play = function() {};
138 | FakeSoundManager.prototype.stop = function() {};
139 | FakeSoundManager.prototype.mute = function() {};
140 | FakeSoundManager.prototype.unmute = function() {};
141 | FakeSoundManager.prototype.setVolume = function() {};
142 | FakeSoundManager.prototype.isMuted = function() {
143 | return true;
144 | };
145 |
146 | if (window.AudioContext) {
147 | module.exports = SoundManager;
148 | } else {
149 | console.warn("This browser doesn't support the Web Audio API.");
150 | module.exports = FakeSoundManager;
151 | }
152 |
--------------------------------------------------------------------------------
/lib/split-filmstrip-animations.js:
--------------------------------------------------------------------------------
1 | var clone = require("./clone");
2 |
3 | module.exports = function splitFilmStripAnimations(animations) {
4 | Object.keys(animations).forEach(function(key) {
5 | var firstFrame = animations[key][0];
6 | if (firstFrame.filmstripFrames) {
7 | splitFilmStripAnimation(animations, key);
8 | }
9 | });
10 | };
11 |
12 | function splitFilmStripAnimation(animations, key) {
13 | var firstFrame = animations[key][0];
14 | if (firstFrame.properties.image.sourceWidth % firstFrame.filmstripFrames != 0) {
15 | console.warn("The \"" + key + "\" animation is " + firstFrame.properties.image.sourceWidth + " pixels wide and that is is not evenly divisible by " + firstFrame.filmstripFrames + " frames.");
16 | }
17 | for (var i = 0; i < firstFrame.filmstripFrames; i++) {
18 | var frameWidth = firstFrame.properties.image.sourceWidth / firstFrame.filmstripFrames;
19 | var newFrame = clone(firstFrame);
20 | newFrame.properties.image.sourceX = frameWidth * i;
21 | newFrame.properties.image.sourceWidth = frameWidth;
22 | animations[key].push(newFrame);
23 | }
24 | animations[key].splice(0,1);
25 | }
26 |
--------------------------------------------------------------------------------
/lib/systems/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * A "system" is a function that runs on all entities with specific {@link Components}.
3 | *The systems listed here are built-in to splat and should not be called directly in your game.
4 | *Instead these systems are included in [systems.json]{@link https://github.com/SplatJS/splat-ecs-starter-project/blob/master/src/data/systems.json} in your project.
5 | *When you write your own systems they will also be included in your project's systems.json
file.
6 | * @namespace Systems
7 | */
8 |
9 |
--------------------------------------------------------------------------------
/lib/systems/renderer/apply-shake.js:
--------------------------------------------------------------------------------
1 | var random = require("../../random");
2 |
3 | /**
4 | * System that looks for an entity with the {@link Components.shake} and {@link Components.position} components.
5 | * Every frame the apply shake system will move the entity's position by a pseudo-random number of pixels between half the magnitude (positive and negative).
6 | * @memberof Systems
7 | * @alias applyShake
8 | * @requires Systems.revertShake
9 | * @see [addEach]{@link https://github.com/ericlathrop/entity-component-system#addeachsystem-search}
10 | * @see [random]{@link splat-ecs/lib/random}
11 | * @see [registerSearch]{@link https://github.com/ericlathrop/entity-component-system#registersearchsearch-components}
12 | * @see [revertShake]{@link Systems.revertShake}
13 | */
14 | module.exports = function(ecs, game) {
15 | game.entities.registerSearch("applyShakeSearch", ["shake", "position"]);
16 | ecs.addEach(function applyShake(entity, elapsed) {
17 | var shake = game.entities.getComponent(entity, "shake");
18 | if (shake.duration !== undefined) {
19 | shake.duration -= elapsed;
20 | if (shake.duration <= 0) {
21 | game.entities.removeComponent(entity, "shake");
22 | return;
23 | }
24 | }
25 | var position = game.entities.getComponent(entity, "position");
26 | shake.lastPositionX = position.x;
27 | shake.lastPositionY = position.y;
28 |
29 | var mx = shake.magnitudeX;
30 | if (mx === undefined) {
31 | mx = shake.magnitude || 0;
32 | }
33 | mx /= 2;
34 | position.x += random.inRange(-mx, mx);
35 |
36 | var my = shake.magnitudeY;
37 | if (my === undefined) {
38 | my = shake.magnitude || 0;
39 | }
40 | my /= 2;
41 | position.y += random.inRange(-my, my);
42 | }, "applyShakeSearch");
43 | };
44 |
--------------------------------------------------------------------------------
/lib/systems/renderer/background-color.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | module.exports = function(ecs, game) { // eslint-disable-line no-unused-vars
4 | game.entities.registerSearch("backgroundColorSearch", ["backgroundColor", "size", "position"]);
5 | ecs.addEach(function(entity, elapsed) { // eslint-disable-line no-unused-vars
6 | var color = game.entities.getComponent(entity, "backgroundColor");
7 | var position = game.entities.getComponent(entity, "position");
8 | var size = game.entities.getComponent(entity, "size");
9 | game.context.fillStyle = color;
10 | game.context.fillRect(position.x, position.y, size.width, size.height);
11 | }, "backgroundColorSearch");
12 | };
13 |
--------------------------------------------------------------------------------
/lib/systems/renderer/clear-screen.js:
--------------------------------------------------------------------------------
1 | module.exports = function(ecs, game) {
2 | ecs.add(function clearScreen() {
3 | game.context.clearRect(0, 0, game.canvas.width, game.canvas.height);
4 | });
5 | };
6 |
--------------------------------------------------------------------------------
/lib/systems/renderer/draw-frame-rate.js:
--------------------------------------------------------------------------------
1 | function roundRect(context, x, y, width, height, radius, stroke) {
2 | if (typeof stroke == "undefined") {
3 | stroke = true;
4 | }
5 | if (typeof radius === "undefined") {
6 | radius = 5;
7 | }
8 | context.beginPath();
9 | context.moveTo(x + radius, y);
10 | context.lineTo(x + width - radius, y);
11 | context.quadraticCurveTo(x + width, y, x + width, y + radius);
12 | context.lineTo(x + width, y + height - radius);
13 | context.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
14 | context.lineTo(x + radius, y + height);
15 | context.quadraticCurveTo(x, y + height, x, y + height - radius);
16 | context.lineTo(x, y + radius);
17 | context.quadraticCurveTo(x, y, x + radius, y);
18 | context.closePath();
19 | if (stroke) {
20 | context.stroke();
21 | }
22 | context.fill();
23 | }
24 |
25 | module.exports = function(ecs, game) {
26 | ecs.add(function drawFrameRate(entities, elapsed) {
27 | var fps = Math.floor(1000 / elapsed);
28 |
29 | var msg = fps + " FPS";
30 | game.context.font = "24px monospace";
31 | var w = game.context.measureText(msg).width;
32 |
33 | game.context.fillStyle = "rgba(0,0,0,0.8)";
34 | game.context.strokeStyle = "rgba(0,0,0,0.9)";
35 | roundRect(game.context, game.canvas.width - 130, -5, 120, 45, 5);
36 |
37 | if (fps < 30) {
38 | game.context.fillStyle = "#FE4848"; //red
39 | } else if (fps < 50) {
40 | game.context.fillStyle = "#FDFA3C"; //yellow
41 | } else {
42 | game.context.fillStyle = "#38F82A"; //green
43 | }
44 |
45 | if (fps < 10) {
46 | fps = " " + fps;
47 | }
48 |
49 | game.context.fillText(msg, game.canvas.width - w - 26, 25);
50 | });
51 | };
52 |
--------------------------------------------------------------------------------
/lib/systems/renderer/draw-image.js:
--------------------------------------------------------------------------------
1 | var defaultSize = { "width": 0, "height": 0 };
2 |
3 | function drawEntity(game, entity, context) {
4 | var imageComponent = game.entities.getComponent(entity, "image");
5 |
6 | var image = imageComponent.buffer;
7 | if (!image) {
8 | image = game.images.get(imageComponent.name);
9 | }
10 | if (!image) {
11 | console.error("No such image", imageComponent.name, "for entity", entity, game.entities.getComponent(entity, "name"));
12 | return;
13 | }
14 |
15 | // FIXME: disable these checks/warnings in production version
16 |
17 | var sx = imageComponent.sourceX || 0;
18 | var sy = imageComponent.sourceY || 0;
19 |
20 | var dx = imageComponent.destinationX || 0;
21 | var dy = imageComponent.destinationY || 0;
22 |
23 | var size = game.entities.getComponent(entity, "size") || defaultSize;
24 |
25 | var sw = imageComponent.sourceWidth || image.width;
26 | if (sw === 0) {
27 | console.warn("sourceWidth is 0, image would be invisible for entity", entity, game.entities.getComponent(entity, "name"));
28 | }
29 | var sh = imageComponent.sourceHeight || image.height;
30 | if (sh === 0) {
31 | console.warn("sourceHeight is 0, image would be invisible for entity", entity, game.entities.getComponent(entity, "name"));
32 | }
33 |
34 | var dw = imageComponent.destinationWidth || size.width || image.width;
35 | var dh = imageComponent.destinationHeight || size.height || image.height;
36 |
37 | var position = game.entities.getComponent(entity, "position");
38 |
39 | var dx2 = dx + position.x;
40 | var dy2 = dy + position.y;
41 |
42 | var rotation = game.entities.getComponent(entity, "rotation");
43 | if (rotation !== undefined) {
44 | context.save();
45 | var rx = rotation.x || size.width / 2 || 0;
46 | var ry = rotation.y || size.height / 2 || 0;
47 | var x = position.x + rx;
48 | var y = position.y + ry;
49 | context.translate(x, y);
50 | context.rotate(rotation.angle);
51 |
52 | dx2 = dx - rx;
53 | dy2 = dy - ry;
54 | }
55 |
56 | var alpha = 1;
57 | if (imageComponent.alpha !== undefined) {
58 | alpha = imageComponent.alpha;
59 | }
60 | context.globalAlpha = alpha;
61 | context.drawImage(image, sx, sy, sw, sh, dx2, dy2, dw, dh);
62 |
63 | if (rotation !== undefined) {
64 | context.restore();
65 | }
66 | }
67 |
68 | var defaultCameraPosition = { x: 0, y: 0 };
69 | var defaultCameraSize = { width: 0, height: 0 };
70 |
71 | module.exports = function(ecs, game) {
72 |
73 | var toDraw = [];
74 |
75 | function comparePositions(a, b) {
76 | if (a === -1) {
77 | return 1;
78 | }
79 | if (b === -1) {
80 | return -1;
81 | }
82 | var pa = game.entities.getComponent(a, "position");
83 | var pb = game.entities.getComponent(b, "position");
84 | var za = pa.z || 0;
85 | var zb = pb.z || 0;
86 | var ya = pa.y || 0;
87 | var yb = pb.y || 0;
88 | return za - zb || ya - yb || a - b;
89 | }
90 |
91 | game.entities.registerSearch("drawImage", ["image", "position"]);
92 | ecs.add(function drawImage(entities) {
93 | var camera = game.entities.find("camera")[0];
94 | var cameraPosition;
95 | var cameraSize;
96 | if (camera) {
97 | cameraPosition = game.entities.getComponent(camera, "position");
98 | cameraSize = game.entities.getComponent(camera, "size");
99 | } else {
100 | cameraPosition = defaultCameraPosition;
101 | cameraSize = defaultCameraSize;
102 | cameraSize.width = game.canvas.width;
103 | cameraSize.height = game.canvas.height;
104 | }
105 |
106 | toDraw.length = 0;
107 |
108 | var ids = entities.find("drawImage");
109 | for (var i = 0; i < ids.length; i++) {
110 | if (isOnScreen(game, ids[i], cameraPosition, cameraSize)) {
111 | toDraw.push(ids[i]);
112 | }
113 | }
114 |
115 | toDraw.sort(comparePositions);
116 |
117 | for (i = 0; i < toDraw.length; i++) {
118 | drawEntity(game, toDraw[i], game.context);
119 | }
120 | });
121 | };
122 |
123 | function isOnScreen(game, entity, cameraPosition, cameraSize) {
124 | var imageComponent = game.entities.getComponent(entity, "image");
125 |
126 | var image = imageComponent.buffer;
127 | if (!image) {
128 | image = game.images.get(imageComponent.name);
129 | }
130 | if (!image) {
131 | console.error("No such image", imageComponent.name, "for entity", entity, game.entities.getComponent(entity, "name"));
132 | return false;
133 | }
134 |
135 | // FIXME: disable these checks/warnings in production version
136 |
137 | var dx = imageComponent.destinationX || 0;
138 | var dy = imageComponent.destinationY || 0;
139 |
140 | var size = game.entities.getComponent(entity, "size") || defaultSize;
141 |
142 | var dw = imageComponent.destinationWidth || size.width || image.width;
143 | if (dw === 0) {
144 | console.warn("destinationWidth is 0, image would be invisible for entity", entity, game.entities.getComponent(entity, "name"));
145 | }
146 | var dh = imageComponent.destinationHeight || size.height || image.height;
147 | if (dh === 0) {
148 | console.warn("destinationHeight is 0, image would be invisible for entity", entity, game.entities.getComponent(entity, "name"));
149 | }
150 |
151 | var position = game.entities.getComponent(entity, "position");
152 |
153 | var dx2 = dx + position.x;
154 | var dy2 = dy + position.y;
155 |
156 | return (dx2 + dw >= cameraPosition.x &&
157 | dy2 + dh >= cameraPosition.y &&
158 | dx2 < cameraPosition.x + cameraSize.width &&
159 | dy2 < cameraPosition.y + cameraSize.height
160 | );
161 | }
162 |
--------------------------------------------------------------------------------
/lib/systems/renderer/draw-rectangles.js:
--------------------------------------------------------------------------------
1 | module.exports = function(ecs, game) {
2 | game.entities.registerSearch("drawRectangles", ["position", "size"]);
3 | ecs.addEach(function drawRectangles(entity) {
4 | var strokeStyle = game.entities.getComponent(entity, "strokeStyle");
5 | if (strokeStyle) {
6 | game.context.strokeStyle = strokeStyle;
7 | }
8 | var position = game.entities.getComponent(entity, "position");
9 | var size = game.entities.getComponent(entity, "size");
10 | game.context.strokeRect(Math.floor(position.x), Math.floor(position.y), size.width, size.height);
11 | }, "drawRectangles");
12 | };
13 |
--------------------------------------------------------------------------------
/lib/systems/renderer/revert-shake.js:
--------------------------------------------------------------------------------
1 | /**
2 | * System that looks for an entity with the {@link Components.shake} and {@link Components.position} components.
3 | * After each iteration of the applyShake system, the revertShake system will move the entity back to where it started
4 | * @memberof Systems
5 | * @alias revertShake
6 | * @requires Systems.applyShake
7 | * @see [addEach]{@link https://github.com/ericlathrop/entity-component-system#addeachsystem-search}
8 | * @see [registerSearch]{@link https://github.com/ericlathrop/entity-component-system#registersearchsearch-components}
9 | * @see [applyShake]{@link Systems.applyShake}
10 | */
11 | module.exports = function(ecs, game) {
12 | game.entities.registerSearch("revertShakeSearch",["shake", "position"]);
13 | ecs.addEach(function revertShake(entity) {
14 | var shake = game.entities.getComponent(entity, "shake");
15 | var position = game.entities.getComponent(entity, "position");
16 | position.x = shake.lastPositionX;
17 | position.y = shake.lastPositionY;
18 | }, "revertShakeSearch");
19 | };
20 |
--------------------------------------------------------------------------------
/lib/systems/renderer/viewport-move-to-camera.js:
--------------------------------------------------------------------------------
1 | module.exports = function(ecs, game) {
2 | game.entities.registerSearch("viewport", ["camera", "position", "size"]);
3 | ecs.addEach(function viewportMoveToCamera(entity) {
4 | var position = game.entities.getComponent(entity, "position");
5 | var size = game.entities.getComponent(entity, "size");
6 |
7 | game.context.save();
8 | game.context.scale(game.canvas.width / size.width, game.canvas.height / size.height);
9 | game.context.translate(-Math.floor(position.x), -Math.floor(position.y));
10 | }, "viewport");
11 | };
12 |
--------------------------------------------------------------------------------
/lib/systems/renderer/viewport-reset.js:
--------------------------------------------------------------------------------
1 | module.exports = function(ecs, game) {
2 | ecs.add(function viewportReset() {
3 | game.context.restore();
4 | });
5 | };
6 |
--------------------------------------------------------------------------------
/lib/systems/simulation/advance-animations.js:
--------------------------------------------------------------------------------
1 | function setOwnPropertiesDeep(src, dest) {
2 | var props = Object.keys(src);
3 | for (var i = 0; i < props.length; i++) {
4 | var prop = props[i];
5 | var val = src[prop];
6 | if (typeof val === "object") {
7 | if (!dest[prop]) {
8 | dest[prop] = {};
9 | }
10 | setOwnPropertiesDeep(val, dest[prop]);
11 | } else {
12 | dest[prop] = val;
13 | }
14 | }
15 | }
16 |
17 | function applyAnimation(entity, a, animation, entities) {
18 | a.lastName = a.name; // track the old name so we can see if it changes
19 | Object.keys(animation[a.frame].properties).forEach(function(property) {
20 | var dest = entities.getComponent(entity, property);
21 | if (!dest) {
22 | dest = entities.addComponent(entity, property);
23 | }
24 | setOwnPropertiesDeep(animation[a.frame].properties[property], dest);
25 | });
26 | }
27 |
28 | module.exports = function(ecs, game) {
29 | game.entities.onAddComponent("animation", function(entity, component, a) {
30 | var animation = game.animations[a.name];
31 | if (!animation) {
32 | return;
33 | }
34 | applyAnimation(entity, a, animation, game.entities);
35 | });
36 | ecs.addEach(function advanceAnimations(entity, elapsed) {
37 | var a = game.entities.getComponent(entity, "animation");
38 | var animation = game.animations[a.name];
39 | if (!animation) {
40 | return;
41 | }
42 | if (a.name != a.lastName) {
43 | a.frame = 0;
44 | a.time = 0;
45 | }
46 | a.time += elapsed * a.speed;
47 | var lastFrame = a.frame;
48 | while (a.time > animation[a.frame].time) {
49 | a.time -= animation[a.frame].time;
50 | a.frame++;
51 | if (a.frame >= animation.length) {
52 | if (a.loop) {
53 | a.frame = 0;
54 | } else {
55 | a.frame--;
56 | }
57 | }
58 | }
59 | if (lastFrame != a.frame || a.name != a.lastName) {
60 | applyAnimation(entity, a, animation, game.entities);
61 | }
62 | }, "animation");
63 | };
64 |
--------------------------------------------------------------------------------
/lib/systems/simulation/advance-timers.js:
--------------------------------------------------------------------------------
1 | /**
2 | * System that looks for an entity with the {@link Components.timers} components.
3 | * Every frame the advanceTimers system will loop through an entity's timers component and increment the "time" property by the elapsed time since the last frame. If the timer is set to loop it will restart the time when it hits max.
4 | * @memberof Systems
5 | * @alias advanceTimers
6 | * @see [addEach]{@link https://github.com/ericlathrop/entity-component-system#addeachsystem-search}
7 | * @see [registerSearch]{@link https://github.com/ericlathrop/entity-component-system#registersearchsearch-components}
8 | */
9 | module.exports = function(ecs, game) {
10 | ecs.addEach(function advanceTimers(entity, elapsed) {
11 | var timers = game.entities.getComponent(entity, "timers");
12 | var names = Object.keys(timers);
13 |
14 | names.forEach(function(name) {
15 | var timer = timers[name];
16 | if (!timer.running) {
17 | return;
18 | }
19 |
20 | timer.time += elapsed;
21 |
22 | while (timer.time > timer.max) {
23 | if (timer.loop) {
24 | timer.time -= timer.max;
25 | } else {
26 | timer.running = false;
27 | timer.time = 0;
28 | }
29 | if (timer.script !== undefined) {
30 | var script = game.require(timer.script);
31 | script(entity, game);
32 | }
33 | }
34 | });
35 | }, "timers");
36 | };
37 |
--------------------------------------------------------------------------------
/lib/systems/simulation/apply-acceleration.js:
--------------------------------------------------------------------------------
1 | /**
2 | * System that looks for an entity with the {@link Components.acceleration} and {@link Components.velocity} components.
3 | * Every frame the apply acceleration system will modify the entity's velocity by the acceleration per elapsed millisecond.
4 | * @memberof Systems
5 | * @alias applyAcceleration
6 | * @see [addEach]{@link https://github.com/ericlathrop/entity-component-system#addeachsystem-search}
7 | * @see [registerSearch]{@link https://github.com/ericlathrop/entity-component-system#registersearchsearch-components}
8 | */
9 | module.exports = function(ecs, game) {
10 | game.entities.registerSearch("applyAcceleration", ["acceleration", "velocity"]);
11 | ecs.addEach(function applyAcceleration(entity, elapsed) {
12 | var velocity = game.entities.getComponent(entity, "velocity");
13 | var acceleration = game.entities.getComponent(entity, "acceleration");
14 | velocity.x += acceleration.x * elapsed;
15 | velocity.y += acceleration.y * elapsed;
16 | }, "applyAcceleration");
17 | };
18 |
--------------------------------------------------------------------------------
/lib/systems/simulation/apply-easing.js:
--------------------------------------------------------------------------------
1 | var easingJS = require("easing-js");
2 |
3 | module.exports = function(ecs, game) {
4 | ecs.addEach(function applyEasing(entity, elapsed) {
5 | var easing = game.entities.getComponent(entity, "easing");
6 |
7 | var properties = Object.keys(easing);
8 | for (var i = 0; i < properties.length; i++) {
9 | var current = easing[properties[i]];
10 | current.time += elapsed;
11 | easeProperty(game, entity, properties[i], current);
12 | if (current.time > current.max) {
13 | delete easing[properties[i]];
14 | }
15 | }
16 | }, "easing");
17 | };
18 |
19 | function easeProperty(game, entity, property, easing) {
20 | var parts = property.split(".");
21 | var componentName = parts[0];
22 | var component = game.entities.getComponent(entity, componentName);
23 | var partNames = parts.slice(1, parts.length, parts - 1);
24 | for (var i = 0; i < partNames.length - 1; i++) {
25 | component = component[partNames[i]];
26 | }
27 | var last = parts[parts.length - 1];
28 | component[last] = easingJS[easing.type](easing.time, easing.start, easing.end - easing.start, easing.max);
29 | }
30 |
--------------------------------------------------------------------------------
/lib/systems/simulation/apply-friction.js:
--------------------------------------------------------------------------------
1 | /**
2 | * System that looks for an entity with the {@link Components.friction} and {@link Components.velocity} components.
3 | * Every frame the apply friction system will modify the entity's velocity by the friction.
4 | * @memberof Systems
5 | * @alias applyFriction
6 | * @see [addEach]{@link https://github.com/ericlathrop/entity-component-system#addeachsystem-search}
7 | * @see [registerSearch]{@link https://github.com/ericlathrop/entity-component-system#registersearchsearch-components}
8 | */
9 | module.exports = function(ecs, game) {
10 | game.entities.registerSearch("applyFriction", ["velocity", "friction"]);
11 | ecs.addEach(function applyFriction(entity) {
12 | var velocity = game.entities.getComponent(entity, "velocity");
13 | var friction = game.entities.getComponent(entity, "friction");
14 | velocity.x *= friction.x;
15 | velocity.y *= friction.y;
16 | }, "applyFriction");
17 | };
18 |
--------------------------------------------------------------------------------
/lib/systems/simulation/apply-movement-2d.js:
--------------------------------------------------------------------------------
1 | module.exports = function(ecs, game) {
2 | game.entities.registerSearch("applyMovement2d", ["velocity", "movement2d"]);
3 | ecs.addEach(function applyMovement2d(entity) {
4 | var velocity = game.entities.getComponent(entity, "velocity");
5 | var movement2d = game.entities.getComponent(entity, "movement2d");
6 | if (movement2d.up && velocity.y > movement2d.upMax) {
7 | velocity.y += movement2d.upAccel;
8 | }
9 | if (movement2d.down && velocity.y < movement2d.downMax) {
10 | velocity.y += movement2d.downAccel;
11 | }
12 | if (movement2d.left && velocity.x > movement2d.leftMax) {
13 | velocity.x += movement2d.leftAccel;
14 | }
15 | if (movement2d.right && velocity.x < movement2d.rightMax) {
16 | velocity.x += movement2d.rightAccel;
17 | }
18 | }, "applyMovement2d");
19 | };
20 |
--------------------------------------------------------------------------------
/lib/systems/simulation/apply-velocity.js:
--------------------------------------------------------------------------------
1 | /**
2 | * System that looks for an entity with the {@link Components.position} and {@link Components.velocity} components.
3 | * Every frame the apply velocity system will move the entity's position by the velocity per elapsed millisecond.
4 | * @memberof Systems
5 | * @alias applyVelocity
6 | * @see [addEach]{@link https://github.com/ericlathrop/entity-component-system#addeachsystem-search}
7 | * @see [registerSearch]{@link https://github.com/ericlathrop/entity-component-system#registersearchsearch-components}
8 | */
9 | module.exports = function(ecs, game) {
10 | game.entities.registerSearch("applyVelocity", ["position", "velocity"]);
11 | ecs.addEach(function applyVelocity(entity, elapsed) {
12 | var position = game.entities.getComponent(entity, "position");
13 | var velocity = game.entities.getComponent(entity, "velocity");
14 | position.x += velocity.x * elapsed;
15 | position.y += velocity.y * elapsed;
16 | position.z += velocity.z * elapsed;
17 | }, "applyVelocity");
18 | };
19 |
--------------------------------------------------------------------------------
/lib/systems/simulation/box-collider.js:
--------------------------------------------------------------------------------
1 | var boxIntersect = require("box-intersect");
2 |
3 | // This system is deprecated and will be removed in the next major version.
4 | // Use box-group-collider instead.
5 | module.exports = function(ecs, game) {
6 |
7 | game.entities.registerSearch("boxCollider", ["position", "size", "collisions"]);
8 |
9 | game.entities.onRemoveComponent("collisions", function(entity, component, collisions) {
10 | for (var i = 0; i < collisions.length; i++) {
11 | var otherCollisions = game.entities.getComponent(collisions[i], "collisions");
12 | var idx = otherCollisions.indexOf(entity);
13 | if (idx !== -1) {
14 | otherCollisions.splice(idx, 1);
15 | }
16 | }
17 | });
18 |
19 | var idPool = [];
20 | var boxPool = [];
21 | var boxPoolLength = 0;
22 | function growBoxPool(size) {
23 | boxPoolLength = size;
24 | while (boxPool.length < size) {
25 | for (var i = 0; i < 50; i++) {
26 | idPool.push(0);
27 | boxPool.push([0, 0, 0, 0]);
28 | }
29 | }
30 | }
31 |
32 | function handleCollision(a, b) {
33 | if (a >= boxPoolLength || b >= boxPoolLength) {
34 | return;
35 | }
36 | var idA = idPool[a];
37 | var idB = idPool[b];
38 | game.entities.getComponent(idA, "collisions").push(idB);
39 | game.entities.getComponent(idB, "collisions").push(idA);
40 | }
41 |
42 | ecs.add(function boxCollider() {
43 | var ids = game.entities.find("boxCollider");
44 |
45 | growBoxPool(ids.length);
46 |
47 | for (var i = 0; i < ids.length; i++) {
48 | var entity = ids[i];
49 | game.entities.getComponent(entity, "collisions").length = 0;
50 | var position = game.entities.getComponent(entity, "position");
51 | var size = game.entities.getComponent(entity, "size");
52 | idPool[i] = entity;
53 | boxPool[i][0] = position.x;
54 | boxPool[i][1] = position.y;
55 | boxPool[i][2] = position.x + size.width;
56 | boxPool[i][3] = position.y + size.height;
57 | }
58 | boxIntersect(boxPool, handleCollision);
59 | });
60 | };
61 |
--------------------------------------------------------------------------------
/lib/systems/simulation/box-group-collider.js:
--------------------------------------------------------------------------------
1 | var BoxPool = require("../../box-pool");
2 |
3 | module.exports = function(ecs, game) {
4 |
5 | game.entities.registerSearch("boxColliderSearch", ["position", "size", "boxCollider"]);
6 |
7 | game.entities.onRemoveComponent("collisions", function(entity, component, collisions) {
8 | for (var i = 0; i < collisions.entities.length; i++) {
9 | var otherCollisions = game.entities.getComponent(collisions.entities[i], "boxCollider");
10 | var idx = otherCollisions.entities.indexOf(entity);
11 | if (idx !== -1) {
12 | otherCollisions.entities.splice(idx, 1);
13 | }
14 | }
15 | });
16 |
17 | var boxPoolNames = [ "unnamed" ];
18 | var boxPools = {
19 | unnamed: new BoxPool()
20 | };
21 |
22 | function handleCollision(a, b) {
23 | handleCollision2(game, a, b);
24 | handleCollision2(game, b, a);
25 | }
26 |
27 | ecs.add(function boxGroupCollider() {
28 | var ids = game.entities.find("boxColliderSearch");
29 |
30 | for (var i = 0; i < boxPoolNames.length; i++) {
31 | boxPools[boxPoolNames[i]].reset();
32 | }
33 |
34 | for (i = 0; i < ids.length; i++) {
35 | var entity = ids[i];
36 | var collisions = game.entities.getComponent(entity, "boxCollider");
37 | collisions.entities.length = 0;
38 | var position = game.entities.getComponent(entity, "position");
39 | var size = game.entities.getComponent(entity, "size");
40 |
41 | var name = collisions.group || "unnamed";
42 | if (!boxPools[name]) {
43 | boxPoolNames.push(name);
44 | boxPools[name] = new BoxPool();
45 | }
46 | boxPools[name].add(entity, position, size);
47 | }
48 | for (i = 0; i < boxPoolNames.length; i++) {
49 | for (var j = i + 1; j < boxPoolNames.length; j++) {
50 | var aName = boxPoolNames[i];
51 | var bName = boxPoolNames[j];
52 | if (areGroupsExcluded(game, aName, bName)) {
53 | continue;
54 | }
55 |
56 | var a = boxPools[aName];
57 | var b = boxPools[bName];
58 | if (a && b) {
59 | a.collideOther(b, handleCollision);
60 | }
61 | }
62 | }
63 | boxPools["unnamed"].collideSelf(handleCollision);
64 | for (i = 0; i < ids.length; i++) {
65 | updateLastCollisions(game, ids[i]);
66 | }
67 | });
68 | };
69 |
70 | function areGroupsExcluded(game, a, b) {
71 | var config = game.sceneConfig["box-group-collider"];
72 | if (!config) {
73 | return false;
74 | }
75 | var skip = config.skip;
76 | if (!skip) {
77 | return false;
78 | }
79 | for (var i = 0; i < skip.length; i++) {
80 | if (
81 | (skip[i][0] === a && skip[i][1] === b) ||
82 | (skip[i][0] === b && skip[i][1] === a)
83 | ) {
84 | return true;
85 | }
86 | }
87 | return false;
88 | }
89 |
90 | function updateLastCollisions(game, entity) {
91 | var collisions = game.entities.getComponent(entity, "boxCollider");
92 |
93 | if (collisions.onExit) {
94 | var currentCollisions = {};
95 | for (var j = 0; j < collisions.entities.length; j++) {
96 | currentCollisions[collisions.entities[j]] = true;
97 | }
98 | for (var k = 0; k < collisions.last.length; k++) {
99 | if (!currentCollisions[collisions.last[k]]) {
100 | var onExit = game.require(collisions.onExit);
101 | onExit(entity, collisions.last[k], game);
102 | }
103 | }
104 | }
105 |
106 | if (collisions.last) {
107 | collisions.last.length = 0;
108 | } else {
109 | collisions.last = [];
110 | }
111 | for (var i = 0; i < collisions.entities.length; i++) {
112 | collisions.last.push(collisions.entities[i]);
113 | }
114 | }
115 |
116 | function handleCollision2(game, entity, other) {
117 | var collisions = game.entities.getComponent(entity, "boxCollider");
118 | collisions.entities.push(other);
119 | if (collisions.onEnter && collisions.last.indexOf(other) === -1) {
120 | var onEnter = game.require(collisions.onEnter);
121 | onEnter(entity, other, game);
122 | }
123 | if (collisions.script) {
124 | var script = game.require(collisions.script);
125 | script(entity, other, game);
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/lib/systems/simulation/constrain-position.js:
--------------------------------------------------------------------------------
1 | module.exports = function(ecs, game) {
2 | game.entities.registerSearch("constrainPositionSearch", ["position", "size", "constrainPosition"]);
3 | ecs.addEach(function constrainTocontrainPosition(entity) {
4 | var position = game.entities.getComponent(entity, "position");
5 | var size = game.entities.getComponent(entity, "size");
6 |
7 | var constrainPosition = game.entities.getComponent(entity, "constrainPosition");
8 | var other = constrainPosition.id;
9 | var otherPosition = game.entities.getComponent(other, "position");
10 | var otherSize = game.entities.getComponent(other, "size");
11 |
12 | if (position.x < otherPosition.x) {
13 | position.x = otherPosition.x;
14 | }
15 | if (position.x + size.width > otherPosition.x + otherSize.width) {
16 | position.x = otherPosition.x + otherSize.width - size.width;
17 | }
18 | if (position.y < otherPosition.y) {
19 | position.y = otherPosition.y;
20 | }
21 | if (position.y + size.height > otherPosition.y + otherSize.height) {
22 | position.y = otherPosition.y + otherSize.height - size.height;
23 | }
24 | }, "constrainPositionSearch");
25 | };
26 |
--------------------------------------------------------------------------------
/lib/systems/simulation/control-player.js:
--------------------------------------------------------------------------------
1 | module.exports = function(ecs, game) {
2 | game.entities.registerSearch("controlPlayer", ["movement2d", "playerController2d"]);
3 | ecs.addEach(function controlPlayer(entity) {
4 | var movement2d = game.entities.getComponent(entity, "movement2d");
5 | var playerController2d = game.entities.getComponent(entity, "playerController2d");
6 | movement2d.up = game.inputs.button(playerController2d.up);
7 | movement2d.down = game.inputs.button(playerController2d.down);
8 | movement2d.left = game.inputs.button(playerController2d.left);
9 | movement2d.right = game.inputs.button(playerController2d.right);
10 | }, "controlPlayer");
11 | };
12 |
--------------------------------------------------------------------------------
/lib/systems/simulation/decay-life-span.js:
--------------------------------------------------------------------------------
1 | module.exports = function(ecs, game) {
2 | ecs.addEach(function decayLifeSpan(entity, elapsed) {
3 | var lifeSpan = game.entities.getComponent(entity, "lifeSpan");
4 | lifeSpan.current += elapsed;
5 | if (lifeSpan.current >= lifeSpan.max) {
6 | game.entities.destroy(entity);
7 | }
8 | }, "lifeSpan");
9 | };
10 |
--------------------------------------------------------------------------------
/lib/systems/simulation/follow-mouse.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | module.exports = function(ecs, game) { // eslint-disable-line no-unused-vars
4 | ecs.addEach(function(entity, elapsed) { // eslint-disable-line no-unused-vars
5 | var position = game.entities.getComponent(entity, "position");
6 | var camera = game.entities.find("camera")[0];
7 | var cameraPosition = game.entities.getComponent(camera, "position");
8 | position.x = cameraPosition.x + game.inputs.mouse.x;
9 | position.y = cameraPosition.y + game.inputs.mouse.y;
10 | }, "followMouse");
11 | };
12 |
--------------------------------------------------------------------------------
/lib/systems/simulation/follow-parent.js:
--------------------------------------------------------------------------------
1 | var distanceSquared = require("../../math2d").distanceSquared;
2 |
3 | module.exports = function(ecs, game) {
4 | game.entities.registerSearch("followParent", ["position", "size", "follow"]);
5 | ecs.addEach(function followParent(entity) {
6 | var position = game.entities.getComponent(entity, "position");
7 | var follow = game.entities.getComponent(entity, "follow");
8 | var size = game.entities.getComponent(entity, "size");
9 |
10 | var x1 = position.x + (size.width / 2);
11 | var y1 = position.y + (size.height / 2);
12 |
13 | var parent = follow.id;
14 | if (game.entities.getComponent(parent, "id") === undefined) {
15 | return;
16 | }
17 | var parentPosition = game.entities.getComponent(parent, "position");
18 | var parentSize = game.entities.getComponent(parent, "size");
19 |
20 | var x2 = parentPosition.x + (parentSize.width / 2);
21 | var y2 = parentPosition.y + (parentSize.height / 2);
22 |
23 | var angle = Math.atan2(y2 - y1, x2 - x1);
24 | var rotation = game.entities.getComponent(entity, "rotation");
25 | if (rotation !== undefined) {
26 | rotation.angle = angle - (Math.PI / 2);
27 | }
28 |
29 | var distSquared = distanceSquared(x1, y1, x2, y2);
30 | if (distSquared < follow.distance * follow.distance) {
31 | return;
32 | }
33 |
34 | var toMove = Math.sqrt(distSquared) - follow.distance;
35 |
36 | position.x += toMove * Math.cos(angle);
37 | position.y += toMove * Math.sin(angle);
38 | }, "followParent");
39 | };
40 |
--------------------------------------------------------------------------------
/lib/systems/simulation/match-aspect-ratio.js:
--------------------------------------------------------------------------------
1 | module.exports = function(ecs, game) {
2 | game.entities.registerSearch("matchAspectRatioSearch", ["matchAspectRatio", "size"]);
3 | ecs.addEach(function matchCanvasSize(entity) {
4 | var size = game.entities.getComponent(entity, "size");
5 |
6 | var match = game.entities.getComponent(entity, "matchAspectRatio").id;
7 | var matchSize = game.entities.getComponent(match, "size");
8 | if (matchSize === undefined) {
9 | return;
10 | }
11 |
12 | var matchAspectRatio = matchSize.width / matchSize.height;
13 |
14 | var currentAspectRatio = size.width / size.height;
15 | if (currentAspectRatio > matchAspectRatio) {
16 | size.height = Math.floor(size.width / matchAspectRatio);
17 | } else if (currentAspectRatio < matchAspectRatio) {
18 | size.width = Math.floor(size.height * matchAspectRatio);
19 | }
20 | }, "matchAspectRatioSearch");
21 | };
22 |
--------------------------------------------------------------------------------
/lib/systems/simulation/match-canvas-size.js:
--------------------------------------------------------------------------------
1 | module.exports = function(ecs, game) {
2 | ecs.addEach(function matchCanvasSize(entity) {
3 | var size = game.entities.addComponent(entity, "size");
4 | size.width = game.canvas.width;
5 | size.height = game.canvas.height;
6 | }, "matchCanvasSize");
7 | };
8 |
--------------------------------------------------------------------------------
/lib/systems/simulation/match-center.js:
--------------------------------------------------------------------------------
1 | module.exports = function(ecs, game) {
2 | game.entities.registerSearch("matchCenterXSearch", ["matchCenter", "size", "position"]);
3 | ecs.addEach(function matchCenterX(entity) {
4 | var position = game.entities.getComponent(entity, "position");
5 | var size = game.entities.getComponent(entity, "size");
6 |
7 | var matchCenter = game.entities.getComponent(entity, "matchCenter");
8 |
9 | var idX = matchCenter.x;
10 | if (idX === undefined) {
11 | idX = matchCenter.id;
12 | }
13 | if (idX !== undefined) {
14 | verifyTarget(game, idX, adjustX, position, size);
15 | }
16 |
17 | var idY = matchCenter.y;
18 | if (idY === undefined) {
19 | idY = matchCenter.id;
20 | }
21 | if (idY !== undefined) {
22 | verifyTarget(game, idY, adjustY, position, size);
23 | }
24 | }, "matchCenterXSearch");
25 | };
26 |
27 | function verifyTarget(game, target, fn, position, size) {
28 | var matchPosition = game.entities.getComponent(target, "position");
29 | if (matchPosition === undefined) {
30 | return;
31 | }
32 | var matchSize = game.entities.getComponent(target, "size");
33 | if (matchSize === undefined) {
34 | return;
35 | }
36 |
37 | fn(position, size, matchPosition, matchSize);
38 | }
39 |
40 | function adjustX(position, size, matchPosition, matchSize) {
41 | position.x = matchPosition.x + (matchSize.width / 2) - (size.width / 2);
42 | }
43 |
44 | function adjustY(position, size, matchPosition, matchSize) {
45 | position.y = matchPosition.y + (matchSize.height / 2) - (size.height / 2);
46 | }
47 |
--------------------------------------------------------------------------------
/lib/systems/simulation/match-parent.js:
--------------------------------------------------------------------------------
1 | module.exports = function(ecs, game) {
2 | game.entities.registerSearch("matchParent", ["position", "match"]);
3 | ecs.addEach(function matchParent(entity) {
4 | var match = game.entities.getComponent(entity, "match");
5 |
6 | var parentPosition = game.entities.getComponent(match.id, "position");
7 | if (parentPosition === undefined) {
8 | return;
9 | }
10 |
11 | var position = game.entities.addComponent(entity, "position");
12 | position.x = parentPosition.x + match.offsetX;
13 | position.y = parentPosition.y + match.offsetY;
14 | position.z = parentPosition.z + match.offsetZ;
15 | }, "matchParent");
16 | };
17 |
--------------------------------------------------------------------------------
/lib/systems/simulation/set-virtual-buttons.js:
--------------------------------------------------------------------------------
1 | module.exports = function(ecs, game) {
2 | game.entities.registerSearch("setVirtualButtons", ["virtualButton", "position", "size"]);
3 | ecs.addEach(function setVirtualButtons(entity) {
4 | var virtualButton = game.entities.getComponent(entity, "virtualButton");
5 | var position = game.entities.getComponent(entity, "position");
6 | var size = game.entities.getComponent(entity, "size");
7 |
8 | var camera = game.entities.find("camera")[0];
9 | var cameraPosition = { x: 0, y: 0 };
10 | if (camera !== undefined) {
11 | cameraPosition = game.entities.getComponent(camera, "position");
12 | }
13 |
14 | for (var i = 0; i < game.inputs.mouse.touches.length; i++) {
15 | var t = game.inputs.mouse.touches[i];
16 | var tx = t.x + cameraPosition.x;
17 | var ty = t.y + cameraPosition.y;
18 | if (tx >= position.x && tx < position.x + size.width && ty >= position.y && ty < position.y + size.height) {
19 | game.inputs.setButton(virtualButton, entity, true);
20 | return true;
21 | }
22 | }
23 | game.inputs.setButton(virtualButton, entity, false);
24 | }, "setVirtualButtons");
25 | };
26 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "splat-ecs",
3 | "version": "7.6.0",
4 | "description": "A 2D HTML5 Canvas game engine",
5 | "main": "lib/main.js",
6 | "scripts": {
7 | "lint-js": "eslint lib",
8 | "docs": "jsdoc lib -c jsdoc/conf.json -d docs -r README.md",
9 | "build": "npm run lint-js && npm run docs"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/SplatJS/splat-ecs.git"
14 | },
15 | "keywords": [
16 | "html5",
17 | "canvas",
18 | "game",
19 | "browser"
20 | ],
21 | "author": "Eric Lathrop