├── .editorconfig ├── .gitattributes ├── .gitignore ├── .jshintignore ├── .jshintrc ├── .travis.yml ├── .vscode └── tasks.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── appveyor.yml ├── bower.json ├── build ├── api-gen │ ├── clean-name.js │ ├── doc-components.jsx │ ├── dynamic-server.js │ ├── index-docs.js │ └── server-side.jsx ├── parseNodes.coffee ├── parseSourceDocs.coffee ├── template.html └── webdriver │ ├── README.md │ └── failed │ └── README.md ├── dist ├── crafty-min.js └── crafty.js ├── package.json ├── playgrounds ├── .jshintrc ├── 2D │ ├── collision │ │ ├── collision.html │ │ └── collision.js │ └── raycast │ │ ├── raycast.html │ │ └── raycast.js ├── animation │ ├── sprite-animation.html │ └── sprite-animation.js ├── camera.html ├── parallax.html └── touchevents.html ├── src ├── aliases.js ├── controls │ ├── controls-system.js │ ├── controls.js │ ├── keyboard.js │ ├── keycodes.js │ ├── mouse.js │ └── touch.js ├── core │ ├── animation.js │ ├── core.js │ ├── extensions.js │ ├── loader.js │ ├── model.js │ ├── scenes.js │ ├── storage.js │ ├── systems.js │ ├── time.js │ ├── tween.js │ ├── utility.js │ └── version.js ├── crafty-common.js ├── crafty-headless.js ├── crafty.js ├── debug │ ├── debug-layer.js │ └── logging.js ├── graphics │ ├── canvas-layer.js │ ├── canvas.js │ ├── color.js │ ├── dom-helper.js │ ├── dom-layer.js │ ├── dom.js │ ├── drawing.js │ ├── gl-textures.js │ ├── html.js │ ├── image.js │ ├── layers.js │ ├── particles.js │ ├── renderable.js │ ├── shaders │ │ ├── color.frag │ │ ├── color.vert │ │ ├── sprite.frag │ │ └── sprite.vert │ ├── sprite-animation.js │ ├── sprite.js │ ├── text.js │ ├── viewport.js │ ├── webgl-layer.js │ └── webgl.js ├── inputs │ ├── device.js │ ├── dom-events.js │ ├── keyboard.js │ ├── lifecycle.js │ ├── mouse.js │ ├── pointer.js │ ├── touch.js │ └── util.js ├── isometric │ ├── diamond-iso.js │ └── isometric.js ├── sound │ └── sound.js └── spatial │ ├── 2d.js │ ├── collision.js │ ├── math.js │ ├── motion.js │ ├── platform.js │ ├── rect-manager.js │ └── spatial-grid.js ├── tests ├── test-browsers.json ├── unit │ ├── .jshintrc │ ├── assets │ │ ├── 100x100.jpeg │ │ ├── 100x100.png │ │ ├── craftyLogo.png │ │ └── numbers.png │ ├── common.js │ ├── controls │ │ └── controls.js │ ├── core │ │ ├── animation.js │ │ ├── core.js │ │ ├── events.js │ │ ├── instances.js │ │ ├── loader.js │ │ ├── model.js │ │ ├── scenes.js │ │ ├── storage.js │ │ ├── systems.js │ │ ├── time.js │ │ └── tween.js │ ├── debug │ │ ├── debug.js │ │ └── logging.js │ ├── graphics │ │ ├── color.js │ │ ├── dom-helper.js │ │ ├── dom.js │ │ ├── sprite-animation.js │ │ ├── text.js │ │ └── viewport.js │ ├── index-common.js │ ├── index-headless.js │ ├── index.html │ ├── index.js │ ├── inputs │ │ └── inputs.js │ ├── isometric │ │ └── isometric.js │ ├── lib │ │ ├── mockTouchEvents.js │ │ ├── qunit.css │ │ └── qunit.js │ ├── sound │ │ └── audio.js │ └── spatial │ │ ├── 2d.js │ │ ├── collision.js │ │ ├── math.js │ │ ├── motion.js │ │ ├── platform.js │ │ ├── raycast.js │ │ ├── sat.js │ │ └── spatial-grid.js └── webdriver │ ├── .jshintrc │ ├── assets │ ├── color-initial-expected.png │ ├── color-opaque-expected.png │ ├── color-transparent-expected.png │ ├── template-crafty-expected.png │ ├── template-local-expected.png │ ├── template-multi-firstLand-expected.png │ ├── template-multi-secondLand-expected.png │ └── template-multi-thirdLand-expected.png │ ├── color │ ├── color-canvas.js │ ├── color-common.js │ ├── color-dom.js │ └── color-webgl.js │ ├── commands │ ├── browser.js │ ├── generic.js │ └── test.js │ ├── common.css │ ├── common.js │ ├── index-webdriver-cloud.js │ ├── index-webdriver-local.js │ ├── index-webdriver.js │ └── template │ ├── template-crafty.html │ ├── template-crafty.js │ ├── template-generic.js │ ├── template-local.html │ ├── template-local.js │ ├── template-multi.html │ └── template-multi.js ├── travis-nightly.sh └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | ### http://EditorConfig.org 2 | 3 | root = true 4 | 5 | [**] 6 | charset = utf-8 7 | indent_size = 2 8 | indent_style = space 9 | trim_trailing_whitespace = true 10 | ### Don't force LF on Win platforms, change to native once available 11 | # end_of_line = lf 12 | insert_final_newline = true 13 | 14 | 15 | [Gruntfile.js] 16 | indent_size = 4 17 | 18 | [src/**] 19 | indent_size = 4 20 | 21 | [tests/unit/lib/mockTouchEvents.js] 22 | indent_size = 4 23 | 24 | [tests/webdriver/index*.js] 25 | indent_size = 4 26 | [tests/webdriver/commands/**] 27 | indent_size = 4 28 | 29 | [build/**.coffee] 30 | indent_size = 4 31 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Let git detect text files that are not listed below and let it normalize them 2 | # - convert line endings from LF to CRLF on checkout in Windows 3 | # - convert line endings from CRLF to LF on commit in Windows 4 | # - don't touch line endings in Linux / Mac 5 | 6 | * text=auto 7 | 8 | 9 | # Explicitly tell git which text files will be normalized 10 | 11 | # web languages 12 | *.js text 13 | *.jsx text 14 | *.json text 15 | *.coffee text 16 | *.html text 17 | *.css text 18 | 19 | # config files 20 | *.gitignore text 21 | *.gitattributes text 22 | *.yml text 23 | *.editorconfig text 24 | *.npmignore text 25 | *.jshintrc text 26 | *.jshintignore text 27 | 28 | # misc 29 | *.md text 30 | *.sh text 31 | *.frag text 32 | *.vert text 33 | 34 | 35 | # Explicitly tell git which binary files won't be normalized 36 | 37 | *.png binary 38 | *.jpg binary 39 | *.jpeg binary 40 | *.wav binary 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /crafty.js 2 | build/api/* 3 | build/api.json 4 | build/parseNodes.js 5 | build/parseSourceDocs.js 6 | build/api-gen.js 7 | build/webdriver/* 8 | !build/webdriver/README.md 9 | !build/webdriver/failed 10 | build/webdriver/failed/* 11 | !build/webdriver/failed/README.md 12 | .*.sw? 13 | .DS_store 14 | .project 15 | .idea 16 | node_modules/ 17 | localStorage/ 18 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | tests/unit/lib/** 2 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "eqeqeq": true, 3 | "esversion": 6, 4 | "freeze": true, 5 | "futurehostile": true, 6 | "latedef": "nofunc", 7 | "noarg": true, 8 | "nonbsp": true, 9 | "nonew": true, 10 | "strict": "implied", 11 | "undef": true, 12 | "unused": "vars", 13 | "validthis": true, 14 | "browser": true, 15 | "node": true, 16 | "laxbreak": true 17 | } 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | node_js: 5 | - '6' 6 | cache: 7 | yarn: true 8 | directories: 9 | - node_modules 10 | 11 | before_script: npm install -g grunt-cli 12 | after_success: "./travis-nightly.sh" 13 | after_failure: if [ "$GH_TOKEN" ]; then grunt gh-pages:crafty-distro-regression-tests; fi 14 | 15 | env: 16 | global: 17 | - SAUCE_BRANCH=testing 18 | # GitHub token 19 | - secure: nMIM+zttVf8rmBrI4RSvWcZiqN1rwFPt9xIY89QkRuHJeAvju9HM5N5RoossvDBQ+b/Z5Sm3L3aq+U2KfWnW5mSnAvmE2j2T4HfaQABnTiVzGhpailaX9O7ACZuLnsQ9hgbIxPOSlk68cDcAp3HWnofBYigauhWbEwbuBGWl/iU= 20 | # OpenSauce credentials: for pre-relase testing 21 | - secure: NO+7PK5P0XLGbnvdSDBlrlnzqM/hMTy3wxxAp8ZtzEuS2ZOXn8q0KjOO/cAs1NorG1SOl8+XfFZBTR5W504GrHYLP8fNkLJOAyKMfFeTojwQ+385ShVr/NejZFejsc+xJZymIeDsEzBZqosxPOi+8k7jljDK4mzLdKMpt8flWUw= 22 | - secure: iGO+VCkCq/26Dqrc6+G1nX8Dk+tTqhcINuiIVhxGtMARcY+WIzcyEK/cfXRaLnvIzIxQ0xUoGGnM9xe4DQT8L+BGzo8AGnVbzxITzceWTlhsFepcbOTgqOg0ZGMxQVQ6VBh888Cdks8KsOgtTNhzd+oVgcStsyxZPCVD/7VgNTE= 23 | 24 | addons: 25 | sauce_connect: 26 | no_ssl_bump_domains: all 27 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "0.1.0", 5 | "command": "grunt", 6 | "isShellCommand": true, 7 | "tasks": [ 8 | { 9 | "taskName": "build:release", 10 | "args": [], 11 | "isBuildCommand": true, 12 | "isWatching": false, 13 | "problemMatcher": [ 14 | "$lessCompile", 15 | "$tsc", 16 | "$jshint" 17 | ] 18 | }, 19 | { 20 | "taskName": "test", 21 | "args": [], 22 | "isTestCommand": true 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | Hi, thanks for contributing to Crafty! We've got guidlines on 4 | 5 | - [How to build Crafty](https://github.com/craftyjs/Crafty/wiki/Building) 6 | - [What workflow to use](https://github.com/craftyjs/Crafty/wiki/Workflow) 7 | 8 | ## Quick summary 9 | 10 | - You'll need node, npm, and grunt-cli (installed globally) to work with Crafty's dev tools 11 | - With those already installed, `npm install` from Crafty's directory will setup everything else you need 12 | - Once you've made any changes, then `grunt check` will build and test your new version of Crafty. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Louis Stowasser 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Crafty JS [](https://travis-ci.org/craftyjs/Crafty) [](https://ci.appveyor.com/project/starwed/crafty) [](https://saucelabs.com/u/mucaho) 2 | 3 | Crafty is a JavaScript game library that can help you create games in a structured way… 4 | 5 | Key Features: 6 | 7 | * Entities & Components - A clean and decoupled way to organize game elements. No inheritance needed! 8 | * Eventbinding - Event system for custom events that can be triggered whenever, whatever and bound just as easily. 9 | * No dom manipulation or custom drawing routines required. 10 | 11 | Other Goodies: 12 | 13 | * Thriving community - Help is readily available in the forum. 14 | * Community modules - A growing collection of user-generated code you can use. 15 | * Pure JavaScript - No magic. Works in all major browsers and can be combined with your favorite js library. 16 | 17 | 18 | ## Using Crafty 19 | 20 | A simple game of pong: 21 | ```javascript 22 | Crafty.init(600, 300); 23 | Crafty.background('rgb(127,127,127)'); 24 | 25 | //Paddles 26 | Crafty.e("Paddle, 2D, DOM, Color, Multiway") 27 | .color('rgb(255,0,0)') 28 | .attr({ x: 20, y: 100, w: 10, h: 100 }) 29 | .multiway(200, { W: -90, S: 90 }); 30 | Crafty.e("Paddle, 2D, DOM, Color, Multiway") 31 | .color('rgb(0,255,0)') 32 | .attr({ x: 580, y: 100, w: 10, h: 100 }) 33 | .multiway(200, { UP_ARROW: -90, DOWN_ARROW: 90 }); 34 | 35 | //Ball 36 | Crafty.e("2D, DOM, Color, Collision") 37 | .color('rgb(0,0,255)') 38 | .attr({ x: 300, y: 150, w: 10, h: 10, 39 | dX: Crafty.math.randomInt(2, 5), 40 | dY: Crafty.math.randomInt(2, 5) }) 41 | .bind('UpdateFrame', function () { 42 | //hit floor or roof 43 | if (this.y <= 0 || this.y >= 290) 44 | this.dY *= -1; 45 | 46 | // hit left or right boundary 47 | if (this.x > 600) { 48 | this.x = 300; 49 | Crafty("LeftPoints").each(function () { 50 | this.text(++this.points + " Points") }); 51 | } 52 | if (this.x < 10) { 53 | this.x = 300; 54 | Crafty("RightPoints").each(function () { 55 | this.text(++this.points + " Points") }); 56 | } 57 | 58 | this.x += this.dX; 59 | this.y += this.dY; 60 | }) 61 | .onHit('Paddle', function () { 62 | this.dX *= -1; 63 | }); 64 | 65 | //Score boards 66 | Crafty.e("LeftPoints, DOM, 2D, Text") 67 | .attr({ x: 20, y: 20, w: 100, h: 20, points: 0 }) 68 | .text("0 Points"); 69 | Crafty.e("RightPoints, DOM, 2D, Text") 70 | .attr({ x: 515, y: 20, w: 100, h: 20, points: 0 }) 71 | .text("0 Points"); 72 | ``` 73 | _Left paddle is controlled by `W` & `S`, right paddle by `UpArrow` & `DownArrow`._ 74 | [Check it out online and try to modify it yourself here](https://jsfiddle.net/mucaho/yL3v48r6/). 75 | 76 | ## Developing 77 | 78 | If you want to fix a bug, please submit a pull request against the development branch. Some guides to help you can be found [on the wiki](https://github.com/craftyjs/Crafty/wiki) 79 | 80 | If you would like to make larger contributions please catch us in the [forum](https://groups.google.com/forum/?fromgroups#!forum/craftyjs) and we will help you get started. Much appreciated :-) 81 | 82 | 83 | ### Quick build instructions 84 | 85 | The easiest way to build crafty is to use [gruntjs](http://gruntjs.com/), which requires [node](nodejs.org/) and [npm](https://npmjs.org/). If you have grunt, node, and npm already installed, then run `npm install` from Crafty's root directory. (This will pull down about 30MB of node packages.) From then on, just run `grunt` to build. 86 | 87 | You can also use [yarn](https://yarnpkg.com/) instead of npm. 88 | 89 | ([Full instructions here](https://github.com/craftyjs/Crafty/wiki/Building).) 90 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Test against this version of Node.js. 2 | environment: 3 | nodejs_version: "6" 4 | 5 | # preserve local npm and yarn modules but will reset them if package.json or appveyor.yml is modified 6 | cache: 7 | - node_modules -> appveyor.yml, package.json, yarn.lock 8 | - '%LOCALAPPDATA%\Yarn -> appveyor.yml, package.json, yarn.lock' 9 | 10 | version: '{build}' 11 | 12 | # Install scripts. (runs after repo cloning) 13 | install: 14 | # Get the specified version of Node.js 15 | - ps: Install-Product node $env:nodejs_version 16 | # install modules 17 | - yarn install 18 | - npm install -g grunt-cli 19 | 20 | # Post-install test scripts. 21 | test_script: 22 | # Output useful info for debugging. 23 | - node --version 24 | - npm --version 25 | - yarn --version 26 | # run tests 27 | - npm test || set FAILEDTESTS=true 28 | after_test: 29 | - IF "%FAILEDTESTS%"=="true" ( echo Tests failed! & echo upload screenshots for debugging & exit 1 ) 30 | 31 | # Don't actually build. 32 | build: off 33 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "crafty", 3 | "description": "Crafty is a modern component and event based framework for developing games in JavaScript that targets DOM, Canvas and WebGL.", 4 | "keywords": [ 5 | "framework", 6 | "javascript", 7 | "game", 8 | "engine" 9 | ], 10 | "license": "MIT", 11 | "authors": [{ 12 | "name": "Louis Stowasser", 13 | "homepage": "http://craftyjs.com/" 14 | }], 15 | "homepage": "https://github.com/craftyjs/Crafty", 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/craftyjs/Crafty.git" 19 | }, 20 | "ignore": [ 21 | "**", 22 | "!bower.json", 23 | "!README.md", 24 | "!CHANGELOG.md", 25 | "!LICENSE", 26 | "!dist", 27 | "!dist/crafty.js", 28 | "!dist/crafty-min.js" 29 | ], 30 | "main": "dist/crafty.js", 31 | "moduleType": "globals" 32 | } 33 | -------------------------------------------------------------------------------- /build/api-gen/clean-name.js: -------------------------------------------------------------------------------- 1 | var re = /[\.-]/g 2 | var omitRe = /[\(\)]/g 3 | 4 | module.exports = function cleanName(raw) { 5 | return raw.replace(re, "-").replace(omitRe, ""); 6 | } 7 | -------------------------------------------------------------------------------- /build/api-gen/dynamic-server.js: -------------------------------------------------------------------------------- 1 | var http = require('http'), 2 | director = require('director'); 3 | 4 | 5 | var React = require('react'); 6 | var ReactDOMServer = require('react-dom/server'); 7 | require('node-jsx').install({extension: '.jsx'}); 8 | var StaticPageCreator = require('./server-side'); // React component 9 | 10 | var createIndex = require("./index-docs"); 11 | var cleanName = require("./clean-name"); 12 | 13 | function startServer(grunt, input){ 14 | var api = grunt.file.readJSON(input), 15 | index = createIndex(api), 16 | pages = index.pages, 17 | props = {data: api, index: index}; 18 | 19 | function createPage(selector, filename) { 20 | filename = filename || cleanName(selector) + ".html"; 21 | props.selector = selector; 22 | var page = React.createElement(StaticPageCreator.create(), props); 23 | var raw = ReactDOMServer.renderToStaticMarkup(page); 24 | if (pages[selector]) { 25 | var title = pages[selector].main.name; 26 | } else { 27 | var title = selector || filename; 28 | } 29 | raw = "
16 | | Trapezoid | 17 |Parallelogram | 18 |Square (Yellow) | 19 |Square (Purple) | 20 |Square (Green) | 21 ||
---|---|---|---|---|---|---|
Trapezoid | 24 |25 | | 26 | | 27 | | 28 | | 29 | | 30 | |
Parallelogram | 33 |34 | | 35 | | 36 | | 37 | | 38 | | 39 | |
Square (Yellow) | 42 |43 | | 44 | | 45 | | 46 | | 47 | | 48 | |
Square (Purple) | 51 |52 | | 53 | | 54 | | 55 | | 56 | | 57 | |
Square (Green) | 60 |61 | | 62 | | 63 | | 64 | | 65 | | 66 | |
10 |
19 |
13 | Click on a specific bullet item to load that respective scene.
14 | Additionally, the viewport can be dragged around.
15 | Enable developer tools to view logs.
16 |
and in every working zoom example i can move out of bounds(?)
35 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /playgrounds/parallax.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 102 | 103 | -------------------------------------------------------------------------------- /playgrounds/touchevents.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 28 | 29 | -------------------------------------------------------------------------------- /src/aliases.js: -------------------------------------------------------------------------------- 1 | 2 | function createDeprecatedAlias(oldObject, oldName, newObject, newName) { 3 | Object.defineProperty(oldObject, oldName, { 4 | enumerable: false, 5 | configurable: false, 6 | get: function() { return newObject[newName]; }, 7 | set: function(value) { newObject[newName] = value; } 8 | }); 9 | } 10 | 11 | module.exports = { 12 | defineAliases: function defineAliases(Crafty) { 13 | createDeprecatedAlias(Crafty, "image_whitelist", Crafty, "imageWhitelist"); 14 | 15 | createDeprecatedAlias(Crafty, "mouseDispatch", Crafty.s('Mouse'), "processEvent"); 16 | createDeprecatedAlias(Crafty, "mouseButtonsDown", Crafty.s('Mouse'), "_buttonDown"); 17 | createDeprecatedAlias(Crafty, "lastEvent", Crafty.s('Mouse'), "lastMouseEvent"); 18 | createDeprecatedAlias(Crafty, "mouseObjs", Crafty.s('Mouse'), "mouseObjs"); 19 | 20 | createDeprecatedAlias(Crafty, "keyboardDispatch", Crafty.s('Keyboard'), "processEvent"); 21 | createDeprecatedAlias(Crafty, "keydown", Crafty.s('Keyboard'), "_keyDown"); 22 | createDeprecatedAlias(Crafty, "resetKeyDown", Crafty.s('Keyboard'), "resetKeyDown"); 23 | 24 | createDeprecatedAlias(Crafty, "touchDispatch", Crafty, "_touchDispatch"); 25 | createDeprecatedAlias(Crafty, "touchObjs", Crafty.s('Touch'), "touchObjs"); 26 | Crafty.touchHandler = {}; 27 | createDeprecatedAlias(Crafty.touchHandler, "fingers", Crafty.s('Touch'), "touchPoints"); 28 | } 29 | }; 30 | 31 | -------------------------------------------------------------------------------- /src/controls/keyboard.js: -------------------------------------------------------------------------------- 1 | var Crafty = require("../core/core.js"); 2 | 3 | /**@ 4 | * #KeyboardState 5 | * @category Input 6 | * @kind Component 7 | * 8 | * Handles valid key related events and key states for the entity. 9 | * @note This is an internally used component, automatically included in the `KeyboardSystem`. 10 | * 11 | * @trigger KeyDown - when a key is pressed - KeyboardEvent 12 | * @trigger KeyUp - when a key is released - KeyboardEvent 13 | * 14 | * The standard Crafty `KeyboardEvent` object: 15 | * ~~~ 16 | * // event name of key event 17 | * e.eventName 18 | * 19 | * // Normalized keyCode number according to `Crafty.keys` 20 | * e.key 21 | * 22 | * // Original keyboard event, containing additional native properties 23 | * e.originalEvent 24 | * ~~~ 25 | * 26 | * In addition to binding to these events, the current state (pressed/released) of a key can also be queried using the `.isKeyDown` method. 27 | * 28 | * @see Keyboard, KeyboardSystem 29 | * @see Crafty.keys 30 | */ 31 | Crafty.__keyboardStateTemplate = { 32 | _keyDown: null, 33 | 34 | init: function() { 35 | this._keyDown = {}; 36 | // use custom trigger method if specified 37 | this.triggerKeyEvent = this.triggerKeyEvent || this.trigger; 38 | }, 39 | 40 | /**@ 41 | * #.isKeyDown 42 | * @comp KeyboardState 43 | * @kind Method 44 | * 45 | * @sign public Boolean isKeyDown(String keyName) 46 | * @param keyName - Name of the key to check. See `Crafty.keys`. 47 | * @returns The pressed state of the key 48 | * 49 | * @sign public Boolean isKeyDown(Number keyCode) 50 | * @param keyCode - Key code in `Crafty.keys`. 51 | * @returns The pressed state of the key 52 | * 53 | * Determine if a certain key is currently down. 54 | * 55 | * @example 56 | * ~~~ 57 | * ent.bind('UpdateFrame', function() { 58 | * if (Crafty.s('Keyboard').isKeyDown('SPACE')) 59 | * this.y--; 60 | * }); 61 | * ~~~ 62 | * 63 | * @see .resetKeyDown 64 | * @see Crafty.keys 65 | */ 66 | isKeyDown: function(key) { 67 | if (typeof key === "string") { 68 | key = Crafty.keys[key]; 69 | } 70 | return !!this._keyDown[key]; 71 | }, 72 | 73 | /**@ 74 | * #.resetKeyDown 75 | * @comp KeyboardState 76 | * @kind Method 77 | * 78 | * @sign public this .resetKeyDown() 79 | * 80 | * Reset all currently pressed keys. Triggers appropriate "KeyUp" events. 81 | * 82 | * This method is called internally, but may be useful when running Crafty in headless mode. 83 | * 84 | * @see .isKeyDown 85 | * @see Crafty.keys 86 | */ 87 | resetKeyDown: function() { 88 | var evt = { key: -1, eventName: "KeyUp" }; 89 | 90 | // Tell all the keys they're no longer held down 91 | var keyDown = this._keyDown; 92 | for (var k in keyDown) { 93 | if (keyDown[k] === true) { 94 | evt.key = +k; // convert k propertyString to number! 95 | this.triggerKey("KeyUp", evt); 96 | } 97 | } 98 | 99 | return this; 100 | }, 101 | 102 | /**@ 103 | * #.triggerKey 104 | * @comp KeyboardState 105 | * @kind Method 106 | * 107 | * @sign public this triggerKey(String eventName, Object eventData) 108 | * @param eventName - Name of the key event to trigger ("KeyDown" or "KeyUp") 109 | * @param eventData - The key event to trigger 110 | * 111 | * Try to trigger a key event on this entity and persist the key state. 112 | * This method prevents inconsistent key state. 113 | * e.g. If this entity didn't receive a "KeyDown" previously, it won't fire a "KeyUp" event. 114 | * 115 | * This method is called internally, but may be useful when running Crafty in headless mode. 116 | * 117 | * @example 118 | * ~~~ 119 | * var wasTriggered = false; 120 | * 121 | * ent.requires('KeyboardState') 122 | * .bind('KeyUp', function(evt) { 123 | * wasTriggered = true; 124 | * }) 125 | * .triggerKey('KeyUp', { key: Crafty.keys.RIGHT_ARROW }); 126 | * 127 | * Crafty.log(wasTriggered); // prints false 128 | * ~~~ 129 | * 130 | * @see Crafty.keys 131 | */ 132 | triggerKey: function(eventName, eventData) { 133 | // trigger event only if valid state 134 | var key = eventData.key; 135 | if (eventName === "KeyDown") { 136 | // ignore KeyDown due to inconsistent state caused by loosing focus 137 | if (this._keyDown[key] !== true) { 138 | this._keyDown[key] = true; 139 | this.triggerKeyEvent(eventName, eventData); 140 | } 141 | } else if (eventName === "KeyUp") { 142 | // ignore KeyUp due to inconsistent state caused by loosing focus 143 | if (this._keyDown[key] === true) { 144 | this._keyDown[key] = false; 145 | this.triggerKeyEvent(eventName, eventData); 146 | } 147 | } else { 148 | // trigger the event otherwise 149 | this.triggerKeyEvent(eventName, eventData); 150 | } 151 | 152 | return this; 153 | } 154 | }; 155 | Crafty.c("KeyboardState", Crafty.__keyboardStateTemplate); 156 | 157 | // define a basic Keyboard system for headless mode 158 | // will be substituted with proper one in browser mode 159 | Crafty.s( 160 | "Keyboard", 161 | Crafty.extend.call( 162 | { 163 | // this method will be called by KeyboardState iff triggerKey event was valid 164 | triggerKeyEvent: function(eventName, e) { 165 | Crafty.trigger(eventName, e); 166 | } 167 | }, 168 | Crafty.__keyboardStateTemplate 169 | ), 170 | {}, 171 | false 172 | ); 173 | -------------------------------------------------------------------------------- /src/controls/keycodes.js: -------------------------------------------------------------------------------- 1 | var Crafty = require("../core/core.js"); 2 | 3 | Crafty.extend({ 4 | /**@ 5 | * #Crafty.keys 6 | * @category Input 7 | * @kind Property 8 | * 9 | * Object of key names and the corresponding Unicode key code. 10 | * 11 | * ~~~ 12 | * BACKSPACE: 8, 13 | * TAB: 9, 14 | * ENTER: 13, 15 | * PAUSE: 19, 16 | * CAPS: 20, 17 | * ESC: 27, 18 | * SPACE: 32, 19 | * PAGE_UP: 33, 20 | * PAGE_DOWN: 34, 21 | * END: 35, 22 | * HOME: 36, 23 | * LEFT_ARROW: 37, 24 | * UP_ARROW: 38, 25 | * RIGHT_ARROW: 39, 26 | * DOWN_ARROW: 40, 27 | * INSERT: 45, 28 | * DELETE: 46, 29 | * 0: 48, 30 | * 1: 49, 31 | * 2: 50, 32 | * 3: 51, 33 | * 4: 52, 34 | * 5: 53, 35 | * 6: 54, 36 | * 7: 55, 37 | * 8: 56, 38 | * 9: 57, 39 | * A: 65, 40 | * B: 66, 41 | * C: 67, 42 | * D: 68, 43 | * E: 69, 44 | * F: 70, 45 | * G: 71, 46 | * H: 72, 47 | * I: 73, 48 | * J: 74, 49 | * K: 75, 50 | * L: 76, 51 | * M: 77, 52 | * N: 78, 53 | * O: 79, 54 | * P: 80, 55 | * Q: 81, 56 | * R: 82, 57 | * S: 83, 58 | * T: 84, 59 | * U: 85, 60 | * V: 86, 61 | * W: 87, 62 | * X: 88, 63 | * Y: 89, 64 | * Z: 90, 65 | * NUMPAD_0: 96, 66 | * NUMPAD_1: 97, 67 | * NUMPAD_2: 98, 68 | * NUMPAD_3: 99, 69 | * NUMPAD_4: 100, 70 | * NUMPAD_5: 101, 71 | * NUMPAD_6: 102, 72 | * NUMPAD_7: 103, 73 | * NUMPAD_8: 104, 74 | * NUMPAD_9: 105, 75 | * MULTIPLY: 106, 76 | * ADD: 107, 77 | * SUBSTRACT: 109, 78 | * DECIMAL: 110, 79 | * DIVIDE: 111, 80 | * F1: 112, 81 | * F2: 113, 82 | * F3: 114, 83 | * F4: 115, 84 | * F5: 116, 85 | * F6: 117, 86 | * F7: 118, 87 | * F8: 119, 88 | * F9: 120, 89 | * F10: 121, 90 | * F11: 122, 91 | * F12: 123, 92 | * SHIFT: 16, 93 | * CTRL: 17, 94 | * ALT: 18, 95 | * PLUS: 187, 96 | * COMMA: 188, 97 | * MINUS: 189, 98 | * PERIOD: 190, 99 | * PULT_UP: 29460, 100 | * PULT_DOWN: 29461, 101 | * PULT_LEFT: 4, 102 | * PULT_RIGHT': 5 103 | * ~~~ 104 | */ 105 | keys: { 106 | BACKSPACE: 8, 107 | TAB: 9, 108 | ENTER: 13, 109 | PAUSE: 19, 110 | CAPS: 20, 111 | ESC: 27, 112 | SPACE: 32, 113 | PAGE_UP: 33, 114 | PAGE_DOWN: 34, 115 | END: 35, 116 | HOME: 36, 117 | LEFT_ARROW: 37, 118 | UP_ARROW: 38, 119 | RIGHT_ARROW: 39, 120 | DOWN_ARROW: 40, 121 | INSERT: 45, 122 | DELETE: 46, 123 | "0": 48, 124 | "1": 49, 125 | "2": 50, 126 | "3": 51, 127 | "4": 52, 128 | "5": 53, 129 | "6": 54, 130 | "7": 55, 131 | "8": 56, 132 | "9": 57, 133 | A: 65, 134 | B: 66, 135 | C: 67, 136 | D: 68, 137 | E: 69, 138 | F: 70, 139 | G: 71, 140 | H: 72, 141 | I: 73, 142 | J: 74, 143 | K: 75, 144 | L: 76, 145 | M: 77, 146 | N: 78, 147 | O: 79, 148 | P: 80, 149 | Q: 81, 150 | R: 82, 151 | S: 83, 152 | T: 84, 153 | U: 85, 154 | V: 86, 155 | W: 87, 156 | X: 88, 157 | Y: 89, 158 | Z: 90, 159 | NUMPAD_0: 96, 160 | NUMPAD_1: 97, 161 | NUMPAD_2: 98, 162 | NUMPAD_3: 99, 163 | NUMPAD_4: 100, 164 | NUMPAD_5: 101, 165 | NUMPAD_6: 102, 166 | NUMPAD_7: 103, 167 | NUMPAD_8: 104, 168 | NUMPAD_9: 105, 169 | MULTIPLY: 106, 170 | ADD: 107, 171 | SUBSTRACT: 109, 172 | DECIMAL: 110, 173 | DIVIDE: 111, 174 | F1: 112, 175 | F2: 113, 176 | F3: 114, 177 | F4: 115, 178 | F5: 116, 179 | F6: 117, 180 | F7: 118, 181 | F8: 119, 182 | F9: 120, 183 | F10: 121, 184 | F11: 122, 185 | F12: 123, 186 | SHIFT: 16, 187 | CTRL: 17, 188 | ALT: 18, 189 | PLUS: 187, 190 | COMMA: 188, 191 | MINUS: 189, 192 | PERIOD: 190, 193 | PULT_UP: 29460, 194 | PULT_DOWN: 29461, 195 | PULT_LEFT: 4, 196 | PULT_RIGHT: 5 197 | }, 198 | 199 | /**@ 200 | * #Crafty.mouseButtons 201 | * @category Input 202 | * @kind Property 203 | * 204 | * An object mapping mouseButton names to the corresponding button ID. 205 | * In all mouseEvents, we add the `e.mouseButton` property with a value normalized to match e.button of modern webkit browsers: 206 | * 207 | * ~~~ 208 | * LEFT: 0, 209 | * MIDDLE: 1, 210 | * RIGHT: 2 211 | * ~~~ 212 | */ 213 | mouseButtons: { 214 | LEFT: 0, 215 | MIDDLE: 1, 216 | RIGHT: 2 217 | } 218 | }); 219 | -------------------------------------------------------------------------------- /src/core/animation.js: -------------------------------------------------------------------------------- 1 | var Crafty = require("../core/core.js"); 2 | 3 | /**@ 4 | * #Crafty.easing 5 | * @category Animation 6 | * @kind Class 7 | * 8 | * 9 | * An object for tracking transitions. Typically used indirectly through "SpriteAnimation", "Tween", or viewport animations. 10 | * 11 | * If a method allows you to specify the type of easing, you can do so by providing a custom function or a string corresponding to the name of a built-in method. 12 | * 13 | * Built-in easing functions are "linear", "smoothStep", "smootherStep", "easeInQuad", "easeOutQuad", and "easeInOutQuad". 14 | * 15 | * A custom function will be passed a parameter `t` which will vary between 0 and 1, and should return the progress of the animation between 0 and 1. 16 | * @example 17 | * Here is how you might use easing functions with the "Tween" component. 18 | * ~~~~ 19 | * var e = Crafty.e("2D, Tween"); 20 | * // Use built-in easing functions 21 | * e.tween({x:100}, 1000, "smoothStep"); 22 | * e.tween({y:100}, 1000, "easeInQuad"); 23 | * // Define a custom easing function: 2t^2 - t 24 | * e.tween({w:0}, 1000, function(t){return 2*t*t - t;}); 25 | * ~~~ 26 | * @see Tween, SpriteAnimation 27 | */ 28 | var easing = function(duration, easingFn) { 29 | this.timePerFrame = 1000 / Crafty.timer.FPS(); 30 | this.duration = duration; //default duration given in ms 31 | if (typeof easingFn === "function") { 32 | this.easing_function = easingFn; 33 | } else if ( 34 | typeof easingFn === "string" && 35 | this.standardEasingFunctions[easingFn] 36 | ) { 37 | this.easing_function = this.standardEasingFunctions[easingFn]; 38 | } else { 39 | this.easing_function = this.standardEasingFunctions.linear; 40 | } 41 | this.reset(); 42 | }; 43 | 44 | easing.prototype = { 45 | duration: 0, 46 | clock: 0, 47 | steps: null, 48 | complete: false, 49 | paused: false, 50 | 51 | // init values 52 | reset: function() { 53 | this.loops = 1; 54 | this.clock = 0; 55 | this.complete = false; 56 | this.paused = false; 57 | }, 58 | 59 | repeat: function(loopCount) { 60 | this.loops = loopCount; 61 | }, 62 | 63 | setProgress: function(progress, loopCount) { 64 | this.clock = this.duration * progress; 65 | if (typeof loopCount !== "undefined") this.loops = loopCount; 66 | }, 67 | 68 | pause: function() { 69 | this.paused = true; 70 | }, 71 | 72 | resume: function() { 73 | this.paused = false; 74 | this.complete = false; 75 | }, 76 | 77 | // Increment the clock by some amount dt 78 | // Handles looping and sets a flag on completion 79 | tick: function(dt) { 80 | if (this.paused || this.complete) return; 81 | this.clock += dt; 82 | this.frames = Math.floor(this.clock / this.timePerFrame); 83 | while (this.clock >= this.duration && this.complete === false) { 84 | this.loops--; 85 | if (this.loops > 0) this.clock -= this.duration; 86 | else this.complete = true; 87 | } 88 | }, 89 | 90 | // same as value for now; with other time value functions would be more useful 91 | time: function() { 92 | return Math.min(this.clock / this.duration, 1); 93 | }, 94 | 95 | // Value is where along the tweening curve we are 96 | value: function() { 97 | return this.easing_function(this.time()); 98 | }, 99 | 100 | // Easing functions, formulas taken from https://gist.github.com/gre/1650294 101 | // and https://en.wikipedia.org/wiki/Smoothstep 102 | standardEasingFunctions: { 103 | // no easing, no acceleration 104 | linear: function(t) { 105 | return t; 106 | }, 107 | // smooth step; starts and ends with v=0 108 | smoothStep: function(t) { 109 | return (3 - 2 * t) * t * t; 110 | }, 111 | // smootherstep; starts and ends with v, a=0 112 | smootherStep: function(t) { 113 | return (6 * t * t - 15 * t + 10) * t * t * t; 114 | }, 115 | // quadratic curve; starts with v=0 116 | easeInQuad: function(t) { 117 | return t * t; 118 | }, 119 | // quadratic curve; ends with v=0 120 | easeOutQuad: function(t) { 121 | return t * (2 - t); 122 | }, 123 | // quadratic curve; starts and ends with v=0 124 | easeInOutQuad: function(t) { 125 | return t < 0.5 ? 2 * t * t : (4 - 2 * t) * t - 1; 126 | } 127 | } 128 | }; 129 | 130 | module.exports = easing; 131 | -------------------------------------------------------------------------------- /src/core/extensions.js: -------------------------------------------------------------------------------- 1 | var Crafty = require("../core/core.js"); 2 | var document = typeof window !== "undefined" && window.document; 3 | 4 | /**@ 5 | * #Crafty.support 6 | * @category Misc, Core 7 | * @kind CoreObject 8 | * 9 | * Determines feature support for what Crafty can do. 10 | */ 11 | (function testSupport() { 12 | var support = (Crafty.support = {}), 13 | ua = 14 | (typeof navigator !== "undefined" && 15 | navigator.userAgent.toLowerCase()) || 16 | (typeof process !== "undefined" && process.version), 17 | match = 18 | /(webkit)[ \/]([\w.]+)/.exec(ua) || 19 | /(o)pera(?:.*version)?[ \/]([\w.]+)/.exec(ua) || 20 | /(ms)ie ([\w.]+)/.exec(ua) || 21 | /(moz)illa(?:.*? rv:([\w.]+))?/.exec(ua) || 22 | /(v)\d+\.(\d+)/.exec(ua) || 23 | [], 24 | mobile = /iPad|iPod|iPhone|Android|webOS|IEMobile/i.exec(ua); 25 | 26 | /**@ 27 | * #Crafty.mobile 28 | * @comp Crafty.device 29 | * @kind Property 30 | * 31 | * Determines if Crafty is running on mobile device. 32 | * 33 | * If Crafty.mobile is equal true Crafty does some things under hood: 34 | * ~~~ 35 | * - set viewport on max device width and height 36 | * - set Crafty.stage.fullscreen on true 37 | * - hide window scrollbars 38 | * ~~~ 39 | * 40 | * @see Crafty.viewport 41 | */ 42 | if (mobile) Crafty.mobile = mobile[0]; 43 | 44 | /**@ 45 | * #Crafty.support.defineProperty 46 | * @comp Crafty.support 47 | * @kind Property 48 | * 49 | * Is `Object.defineProperty` supported? 50 | */ 51 | support.defineProperty = (function() { 52 | if (!("defineProperty" in Object)) return false; 53 | try { 54 | Object.defineProperty({}, "x", {}); 55 | } catch (e) { 56 | return false; 57 | } 58 | return true; 59 | })(); 60 | 61 | /**@ 62 | * #Crafty.support.audio 63 | * @comp Crafty.support 64 | * @kind Property 65 | * 66 | * Is HTML5 `Audio` supported? 67 | */ 68 | support.audio = 69 | typeof window !== "undefined" && 70 | "canPlayType" in document.createElement("audio"); 71 | 72 | /**@ 73 | * #Crafty.support.prefix 74 | * @comp Crafty.support 75 | * @kind Property 76 | * 77 | * Returns the browser specific prefix (`Moz`, `O`, `ms`, `webkit`, `node`). 78 | */ 79 | support.prefix = match[1] || match[0]; 80 | 81 | //browser specific quirks 82 | if (support.prefix === "moz") support.prefix = "Moz"; 83 | if (support.prefix === "o") support.prefix = "O"; 84 | if (support.prefix === "v") support.prefix = "node"; 85 | 86 | if (match[2]) { 87 | /**@ 88 | * #Crafty.support.versionName 89 | * @comp Crafty.support 90 | * @kind Property 91 | * 92 | * Version of the browser 93 | */ 94 | support.versionName = match[2]; 95 | 96 | /**@ 97 | * #Crafty.support.version 98 | * @comp Crafty.support 99 | * @kind Property 100 | * 101 | * Version number of the browser as an Integer (first number) 102 | */ 103 | support.version = +match[2].split(".")[0]; 104 | } 105 | 106 | /**@ 107 | * #Crafty.support.canvas 108 | * @comp Crafty.support 109 | * @kind Property 110 | * 111 | * Is the `canvas` element supported? 112 | */ 113 | support.canvas = 114 | typeof window !== "undefined" && 115 | "getContext" in document.createElement("canvas"); 116 | 117 | /**@ 118 | * #Crafty.support.webgl 119 | * @comp Crafty.support 120 | * @kind Property 121 | * 122 | * Is WebGL supported on the canvas element? 123 | */ 124 | if (support.canvas) { 125 | var gl; 126 | try { 127 | var c = document.createElement("canvas"); 128 | gl = c.getContext("webgl") || c.getContext("experimental-webgl"); 129 | gl.viewportWidth = support.canvas.width; 130 | gl.viewportHeight = support.canvas.height; 131 | } catch (e) {} 132 | support.webgl = !!gl; 133 | } else { 134 | support.webgl = false; 135 | } 136 | 137 | /**@ 138 | * #Crafty.support.css3dtransform 139 | * @comp Crafty.support 140 | * @kind Property 141 | * 142 | * Is css3Dtransform supported by browser. 143 | */ 144 | support.css3dtransform = 145 | typeof window !== "undefined" && 146 | (typeof document.createElement("div").style.Perspective !== 147 | "undefined" || 148 | typeof document.createElement("div").style[ 149 | support.prefix + "Perspective" 150 | ] !== "undefined"); 151 | 152 | /**@ 153 | * #Crafty.support.deviceorientation 154 | * @comp Crafty.support 155 | * @kind Property 156 | * Is deviceorientation event supported by browser. 157 | */ 158 | support.deviceorientation = 159 | typeof window !== "undefined" && 160 | (typeof window.DeviceOrientationEvent !== "undefined" || 161 | typeof window.OrientationEvent !== "undefined"); 162 | 163 | /**@ 164 | * #Crafty.support.devicemotion 165 | * @comp Crafty.support 166 | * @kind Property 167 | * 168 | * Is devicemotion event supported by browser. 169 | */ 170 | support.devicemotion = 171 | typeof window !== "undefined" && 172 | typeof window.DeviceMotionEvent !== "undefined"; 173 | })(); 174 | -------------------------------------------------------------------------------- /src/core/model.js: -------------------------------------------------------------------------------- 1 | var Crafty = require("../core/core.js"); 2 | 3 | /**@ 4 | * #Model 5 | * @category Model 6 | * @kind Component 7 | * 8 | * Model is a component that offers new features for isolating business 9 | * logic in your application. It offers default values, dirty values, 10 | * and deep events on your data. 11 | * 12 | * All data should be accessed via the appropriate methods `.get`, `.set`, 13 | * and `.data` for the proper events to be triggered. It is not encouraged 14 | * to access them directly. 15 | * 16 | * Dirty values make it simple to inspect a model and see what values have changed. 17 | * 18 | * Deep events allow you to bind to specific fields, like `name` or even deep fields 19 | * like `contact.email` and get notified when those specific fields are updated. 20 | * 21 | * @trigger Change - When any data on the model has changed. 22 | * @trigger Change[key] - When the specific key on the model has changed. 23 | * @trigger Change[key.key] - The nested key value has changed. 24 | * @example 25 | * ~~~ 26 | * Crafty.c('Person', { 27 | * name: 'Fox', 28 | * init: function() { this.requires('Model'); } 29 | * }); 30 | * person = Crafty.e('Person').attr({name: 'blaine'}); 31 | * person.bind('Change[name]', function() { 32 | * Crafty.log('name changed!'); 33 | * }); 34 | * person.attr('name', 'blainesch'); // Triggers event 35 | * person.is_dirty('name'); // true 36 | * person.changed // name 37 | * ~~~ 38 | */ 39 | module.exports = { 40 | init: function() { 41 | this.changed = []; 42 | this.bind("Change", this._changed_attributes); 43 | this.bind("Change", this._changed_triggers); 44 | }, 45 | 46 | /** 47 | * Fires more specific `Change` events. 48 | * 49 | * For instance a `Change[name]` may get fired when you 50 | * update the name data attribute on the model. 51 | */ 52 | _changed_triggers: function(data, options) { 53 | var key; 54 | options = Crafty.extend.call({ pre: "" }, options); 55 | for (key in data) { 56 | this.trigger("Change[" + options.pre + key + "]", data[key]); 57 | if (data[key].constructor === Object) { 58 | this._changed_triggers(data[key], { 59 | pre: options.pre + key + "." 60 | }); 61 | } 62 | } 63 | }, 64 | 65 | /** 66 | * Pushes all top-levle changed attribute names to the 67 | * changed array. 68 | */ 69 | _changed_attributes: function(data) { 70 | var key; 71 | for (key in data) { 72 | this.changed.push(key); 73 | } 74 | return this; 75 | }, 76 | 77 | /**@ 78 | * #.is_dirty 79 | * @comp Model 80 | * @kind Method 81 | * 82 | * Helps determine when data or the entire component is "dirty" or has changed attributes. 83 | * 84 | * @example 85 | * ~~~ 86 | * person = Crafty.e('Person').attr({name: 'Fox', age: 24}) 87 | * person.is_dirty() // false 88 | * person.is_dirty('name') // false 89 | * 90 | * person.attr('name', 'Lucky'); 91 | * person.is_dirty(); // true 92 | * person.is_dirty('name'); // true 93 | * person.is_dirty('age'); // false 94 | * person.changed; // ['name'] 95 | * ~~~ 96 | */ 97 | is_dirty: function(key) { 98 | if (arguments.length === 0) { 99 | return !!this.changed.length; 100 | } else { 101 | return this.changed.indexOf(key) > -1; 102 | } 103 | } 104 | }; 105 | -------------------------------------------------------------------------------- /src/core/storage.js: -------------------------------------------------------------------------------- 1 | var Crafty = require("../core/core.js"); 2 | 3 | try { 4 | var storage = 5 | (typeof window !== "undefined" && window.localStorage) || 6 | new require("node-localstorage").LocalStorage("./localStorage"); 7 | } catch (e) { 8 | var storage = null; 9 | } 10 | 11 | /**@ 12 | * #Storage 13 | * @category Utilities 14 | * @kind Property 15 | * 16 | * Very simple way to get and set values, which will persist when the browser is closed also. 17 | * Storage wraps around HTML5 Web Storage, which is well-supported across browsers and platforms, but limited to 5MB total storage per domain. 18 | * Storage is also available for node, which is permanently persisted to the `./localStorage` folder - take care of removing entries. Note that multiple Crafty instances use the same storage, so care has to be taken not to overwrite existing entries. 19 | */ 20 | /**@ 21 | * #Crafty.storage 22 | * @comp Storage 23 | * @kind Method 24 | * 25 | * @sign Crafty.storage(String key) 26 | * @param key - a key you would like to get from the storage. 27 | * @returns The stored value, or `null` if none saved under that key exists 28 | * 29 | * @sign Crafty.storage(String key, String value) 30 | * @param key - the key you would like to save the data under. 31 | * @param value - the value you would like to save. 32 | * 33 | * @sign Crafty.storage(String key, [Object value, Array value, Boolean value]) 34 | * @param key - the key you would like to save the data under. 35 | * @param value - the value you would like to save, can be an Object or an Array. 36 | * 37 | * `Crafty.storage` is used synchronously to either get or set values. 38 | * 39 | * You can store booleans, strings, objects and arrays. 40 | * 41 | * @note Because the underlying method is synchronous, it can cause slowdowns if used frequently during gameplay. 42 | * You should aim to load or save data at reasonable times such as on level load, 43 | * or in response to specific user actions. 44 | * 45 | * @note If used in a cross-domain context, the localStorage might not be accessible. 46 | * 47 | * @example 48 | * Get an already stored value 49 | * ~~~ 50 | * var playername = Crafty.storage('playername'); 51 | * ~~~ 52 | * 53 | * @example 54 | * Save a value 55 | * ~~~ 56 | * Crafty.storage('playername', 'Hero'); 57 | * ~~~ 58 | * 59 | * @example 60 | * Test to see if a value is already there. 61 | * ~~~ 62 | * var heroname = Crafty.storage('name'); 63 | * if(!heroname){ 64 | * // Maybe ask the player what their name is here 65 | * heroname = 'Guest'; 66 | * } 67 | * // Do something with heroname 68 | * ~~~ 69 | */ 70 | 71 | var store = function(key, value) { 72 | var _value = value; 73 | 74 | if (!storage) { 75 | Crafty.error( 76 | "Local storage is not accessible. (Perhaps you are including crafty.js cross-domain?)" 77 | ); 78 | return false; 79 | } 80 | 81 | if (arguments.length === 1) { 82 | try { 83 | return JSON.parse(storage.getItem(key)); 84 | } catch (e) { 85 | return storage.getItem(key); 86 | } 87 | } else { 88 | if (typeof value === "object") { 89 | _value = JSON.stringify(value); 90 | } 91 | 92 | storage.setItem(key, _value); 93 | } 94 | }; 95 | /**@ 96 | * #Crafty.storage.remove 97 | * @comp Storage 98 | * @kind Method 99 | * 100 | * @sign Crafty.storage.remove(String key) 101 | * @param key - a key where you will like to delete the value of. 102 | * 103 | * Generally you do not need to remove values from localStorage, but if you do 104 | * store large amount of text, or want to unset something you can do that with 105 | * this function. 106 | * 107 | * @example 108 | * Get an already stored value 109 | * ~~~ 110 | * Crafty.storage.remove('playername'); 111 | * ~~~ 112 | * 113 | */ 114 | store.remove = function(key) { 115 | if (!storage) { 116 | Crafty.error( 117 | "Local storage is not accessible. (Perhaps you are including crafty.js cross-domain?)" 118 | ); 119 | return; 120 | } 121 | storage.removeItem(key); 122 | }; 123 | 124 | module.exports = store; 125 | -------------------------------------------------------------------------------- /src/core/tween.js: -------------------------------------------------------------------------------- 1 | var Crafty = require("../core/core.js"); 2 | 3 | /**@ 4 | * #Tween 5 | * @category Animation 6 | * @kind Component 7 | * 8 | * @trigger TweenEnd - when a tween finishes - Object - an object containing the properties that finished tweening 9 | * 10 | * Component to animate the change in 2D properties over time. 11 | */ 12 | module.exports = { 13 | /**@ 14 | * #.tweenSpeed 15 | * @comp Tween 16 | * 17 | * The rate of the tween. This property defaults to 1. 18 | * When setting tweenSpeed to 0.5, tweens will take twice as long, 19 | * setting it to 2.0 will make them twice as short 20 | */ 21 | tweenSpeed: 1, 22 | 23 | init: function() { 24 | this.tweenGroup = {}; 25 | this.tweenStart = {}; 26 | this.tweens = []; 27 | this.uniqueBind("UpdateFrame", this._tweenTick); 28 | }, 29 | 30 | _tweenTick: function(frameData) { 31 | var tween, v, i; 32 | for (i = this.tweens.length - 1; i >= 0; i--) { 33 | tween = this.tweens[i]; 34 | tween.easing.tick(frameData.dt * this.tweenSpeed); 35 | v = tween.easing.value(); 36 | this._doTween(tween.props, v); 37 | if (tween.easing.complete) { 38 | this.tweens.splice(i, 1); 39 | this._endTween(tween.props); 40 | } 41 | } 42 | }, 43 | 44 | _doTween: function(props, v) { 45 | for (var name in props) 46 | this[name] = (1 - v) * this.tweenStart[name] + v * props[name]; 47 | }, 48 | 49 | /**@ 50 | * #.tween 51 | * @comp Tween 52 | * @kind Method 53 | * 54 | * @sign public this .tween(Object properties, Number duration[, String|function easingFn]) 55 | * @param properties - Object of numeric properties and what they should animate to 56 | * @param duration - Duration to animate the properties over, in milliseconds. 57 | * @param easingFn - A string or custom function specifying an easing. (Defaults to linear behavior.) See Crafty.easing for more information. 58 | * 59 | * This method will animate numeric properties over the specified duration. 60 | * These include `x`, `y`, `w`, `h`, `alpha` and `rotation`. 61 | * 62 | * The object passed should have the properties as keys and the value should be the resulting 63 | * values of the properties. The passed object might be modified if later calls to tween animate the same properties. 64 | * 65 | * @example 66 | * Move an object to 100,100 and fade out over 200 ms. 67 | * ~~~ 68 | * Crafty.e("2D, Tween") 69 | * .attr({alpha: 1.0, x: 0, y: 0}) 70 | * .tween({alpha: 0.0, x: 100, y: 100}, 200) 71 | * ~~~ 72 | * @example 73 | * Rotate an object over 2 seconds, using the "smootherStep" easing function. 74 | * ~~~ 75 | * Crafty.e("2D, Tween") 76 | * .attr({rotation:0}) 77 | * .tween({rotation:180}, 2000, "smootherStep") 78 | * ~~~ 79 | * 80 | * @see Crafty.easing 81 | * 82 | */ 83 | tween: function(props, duration, easingFn) { 84 | var tween = { 85 | props: props, 86 | easing: new Crafty.easing(duration, easingFn) 87 | }; 88 | 89 | // Tweens are grouped together by the original function call. 90 | // Individual properties must belong to only a single group 91 | // When a new tween starts, if it already belongs to a group, move it to the new one 92 | // Record the group it currently belongs to, as well as its starting coordinate. 93 | for (var propname in props) { 94 | if (typeof this.tweenGroup[propname] !== "undefined") 95 | this.cancelTween(propname); 96 | this.tweenStart[propname] = this[propname]; 97 | this.tweenGroup[propname] = props; 98 | } 99 | this.tweens.push(tween); 100 | 101 | return this; 102 | }, 103 | 104 | /**@ 105 | * #.cancelTween 106 | * @comp Tween 107 | * @kind Method 108 | * 109 | * @sign public this .cancelTween(String target) 110 | * @param target - The property to cancel 111 | * 112 | * @sign public this .cancelTween(Object target) 113 | * @param target - An object containing the properties to cancel. 114 | * 115 | * Stops tweening the specified property or properties. 116 | * Passing the object used to start the tween might be a typical use of the second signature. 117 | */ 118 | cancelTween: function(target) { 119 | if (typeof target === "string") { 120 | if (typeof this.tweenGroup[target] === "object") 121 | delete this.tweenGroup[target][target]; 122 | } else if (typeof target === "object") { 123 | for (var propname in target) this.cancelTween(propname); 124 | } 125 | 126 | return this; 127 | }, 128 | 129 | /**@ 130 | * #.pauseTweens 131 | * @comp Tween 132 | * @kind Method 133 | * 134 | * @sign public this .pauseTweens() 135 | * 136 | * Pauses all tweens associated with the entity 137 | */ 138 | pauseTweens: function() { 139 | this.tweens.map(function(e) { 140 | e.easing.pause(); 141 | }); 142 | }, 143 | 144 | /**@ 145 | * #.resumeTweens 146 | * @comp Tween 147 | * @kind Method 148 | * 149 | * @sign public this .resumeTweens() 150 | * 151 | * Resumes all paused tweens associated with the entity 152 | */ 153 | resumeTweens: function() { 154 | this.tweens.map(function(e) { 155 | e.easing.resume(); 156 | }); 157 | }, 158 | 159 | /* 160 | * Stops tweening the specified group of properties, and fires the "TweenEnd" event. 161 | */ 162 | _endTween: function(properties) { 163 | var notEmpty = false; 164 | for (var propname in properties) { 165 | notEmpty = true; 166 | delete this.tweenGroup[propname]; 167 | } 168 | if (notEmpty) this.trigger("TweenEnd", properties); 169 | } 170 | }; 171 | -------------------------------------------------------------------------------- /src/core/utility.js: -------------------------------------------------------------------------------- 1 | exports.blobOf = function blobOf(URI) { 2 | var XHR = new XMLHttpRequest(); 3 | XHR.responseType = "blob"; 4 | XHR.open("GET", URI); 5 | 6 | return new Promise(function(resolve, reject) { 7 | XHR.onload = function() { 8 | resolve(this.response); 9 | }; 10 | XHR.onerror = reject; 11 | XHR.send(); 12 | }); 13 | }; 14 | 15 | var DataURI = /^data:(.+?\/(.+?))?(;base64)?,(\S+)/; 16 | 17 | exports.fileTypeOf = function fileTypeOf(URI) { 18 | var schema = /^(?:(\w+):)?.+?(?:\.(\w+))?$/.exec(URI); 19 | 20 | switch (schema[1]) { 21 | case "data": 22 | return { 23 | schema: "data", 24 | type: DataURI.exec(URI)[2] 25 | }; 26 | case "blob": 27 | return exports.blobOf(URI).then(function(blob) { 28 | return { 29 | schema: "blob", 30 | type: blob.type 31 | }; 32 | }); 33 | default: 34 | return { 35 | schema: schema[1], 36 | type: schema[2] 37 | }; 38 | } 39 | }; 40 | 41 | var BlobBuilder = 42 | window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder; 43 | 44 | exports.toBlob = function toBlob(dataURI) { 45 | dataURI = DataURI.exec(dataURI); 46 | 47 | var type = dataURI[1], 48 | base64 = dataURI[3], 49 | data = dataURI[4]; 50 | data = base64 ? window.atob(data) : data; 51 | 52 | var aBuffer = new ArrayBuffer(data.length); 53 | var uBuffer = new Uint8Array(aBuffer); 54 | 55 | for (var i = 0; data[i]; i++) uBuffer[i] = data.charCodeAt(i); 56 | 57 | if (!BlobBuilder) return new window.Blob([aBuffer], { type: type }); 58 | 59 | var builder = new BlobBuilder(); 60 | builder.append(aBuffer); 61 | return builder.getBlob(type); 62 | }; 63 | -------------------------------------------------------------------------------- /src/core/version.js: -------------------------------------------------------------------------------- 1 | module.exports = "0.8.0"; 2 | -------------------------------------------------------------------------------- /src/crafty-common.js: -------------------------------------------------------------------------------- 1 | // Define common features available in both browser and node 2 | module.exports = function(requireNew) { 3 | if (requireNew) { 4 | require = requireNew; // jshint ignore:line 5 | } 6 | 7 | var Crafty = require('./core/core'); 8 | require('./core/extensions'); 9 | 10 | Crafty.easing = require('./core/animation'); 11 | Crafty.c('Model', require('./core/model')); 12 | Crafty.extend(require('./core/scenes')); 13 | Crafty.storage = require('./core/storage'); 14 | Crafty.c('Delay', require('./core/time')); 15 | Crafty.c('Tween', require('./core/tween')); 16 | 17 | var HashMap = require('./spatial/spatial-grid'); 18 | Crafty.HashMap = HashMap; 19 | Crafty.map = new HashMap(); 20 | 21 | require('./core/systems'); 22 | 23 | require('./spatial/2d'); 24 | require('./spatial/motion'); 25 | require('./spatial/platform'); 26 | require('./spatial/collision'); 27 | require('./spatial/rect-manager'); 28 | require('./spatial/math'); 29 | 30 | require('./controls/controls-system'); 31 | require('./controls/controls'); 32 | require('./controls/keyboard'); 33 | require('./controls/keycodes'); 34 | require('./controls/mouse'); 35 | require('./controls/touch'); 36 | 37 | require('./debug/logging'); 38 | 39 | return Crafty; 40 | }; -------------------------------------------------------------------------------- /src/crafty-headless.js: -------------------------------------------------------------------------------- 1 | function requireNew (id) { 2 | delete require.cache[require.resolve(id)]; 3 | return require(id); 4 | } 5 | 6 | module.exports = function() { 7 | // Define common features 8 | var Crafty = require('./crafty-common.js')(requireNew); 9 | 10 | // Define some aliases for renamed properties 11 | requireNew('./aliases').defineAliases(Crafty); 12 | 13 | // add dummys - TODO remove this in future 14 | Crafty.viewport = { 15 | _x: 0, 16 | _y: 0, 17 | width: 0, 18 | height: 0, 19 | init: function() {}, 20 | reset: function() {} 21 | }; 22 | 23 | return Crafty; 24 | }; 25 | -------------------------------------------------------------------------------- /src/crafty.js: -------------------------------------------------------------------------------- 1 | // Define common features 2 | var Crafty = require('./crafty-common.js')(); 3 | 4 | // Define features only available in browser environment 5 | 6 | Crafty.extend(require('./core/loader')); 7 | Crafty.extend(require('./inputs/dom-events')); 8 | 9 | // Needs to be required before any specific layers are 10 | require('./graphics/layers'); 11 | require('./graphics/canvas'); 12 | require('./graphics/canvas-layer'); 13 | require('./graphics/webgl'); 14 | require('./graphics/webgl-layer'); 15 | 16 | require('./graphics/color'); 17 | require('./graphics/dom'); 18 | require('./graphics/dom-helper'); 19 | require('./graphics/dom-layer'); 20 | require('./graphics/drawing'); 21 | require('./graphics/gl-textures'); 22 | require('./graphics/renderable'); 23 | require('./graphics/html'); 24 | require('./graphics/image'); 25 | require('./graphics/particles'); 26 | require('./graphics/sprite-animation'); 27 | require('./graphics/sprite'); 28 | require('./graphics/text'); 29 | require('./graphics/viewport'); 30 | 31 | require('./isometric/diamond-iso'); 32 | require('./isometric/isometric'); 33 | 34 | // Needs to be required before any specific inputs are 35 | require('./inputs/util'); 36 | require('./inputs/device'); 37 | require('./inputs/keyboard'); 38 | require('./inputs/lifecycle'); 39 | require('./inputs/mouse'); 40 | require('./inputs/pointer'); 41 | require('./inputs/touch'); 42 | 43 | require('./sound/sound'); 44 | 45 | require('./debug/debug-layer'); 46 | 47 | // Define some aliases for renamed properties 48 | require('./aliases').defineAliases(Crafty); 49 | 50 | if (window) window.Crafty = Crafty; 51 | 52 | module.exports = Crafty; 53 | -------------------------------------------------------------------------------- /src/debug/logging.js: -------------------------------------------------------------------------------- 1 | var Crafty = require("../core/core.js"); 2 | 3 | /**@ 4 | * #Crafty.log 5 | * @category Debug 6 | * @kind Method 7 | * 8 | * @sign Crafty.log( arguments ) 9 | * @param arguments - arguments which are passed to `console.log` 10 | * 11 | * This is a simple wrapper for `console.log`. You can disable logging messages by setting `Crafty.loggingEnabled` to false. 12 | * It is recommended to use `Crafty.log`, as `console.log` can crash on IE9. 13 | */ 14 | /**@ 15 | * #Crafty.error 16 | * @category Debug 17 | * @kind Method 18 | * 19 | * @sign Crafty.error( arguments ) 20 | * @param arguments - arguments which are passed to `console.error` 21 | * 22 | * This is a simple wrapper for `console.error`. You can disable logging messages by setting `Crafty.loggingEnabled` to false. 23 | * It is recommended to use `Crafty.error`, as `console.error` can crash on IE9. 24 | */ 25 | Crafty.extend({ 26 | // Allow logging to be disabled 27 | loggingEnabled: true, 28 | // In some cases console.log doesn't exist, so provide a wrapper for it 29 | log: function() { 30 | if ( 31 | Crafty.loggingEnabled && 32 | (typeof window !== "undefined" ? window.console : console) && 33 | console.log 34 | ) { 35 | console.log.apply(console, arguments); 36 | } 37 | }, 38 | error: function() { 39 | if ( 40 | Crafty.loggingEnabled && 41 | (typeof window !== "undefined" ? window.console : console) && 42 | console.error 43 | ) { 44 | console.error.apply(console, arguments); 45 | } 46 | } 47 | }); 48 | -------------------------------------------------------------------------------- /src/graphics/canvas.js: -------------------------------------------------------------------------------- 1 | var Crafty = require("../core/core.js"); 2 | 3 | /**@ 4 | * #Canvas 5 | * @category Graphics 6 | * @kind Component 7 | * 8 | * @trigger Draw - when the entity is ready to be drawn to the stage - {type: "canvas", pos, co, ctx} 9 | * @trigger NoCanvas - if the browser does not support canvas 10 | * 11 | * When this component is added to an entity it will be drawn to the global canvas element. The canvas element (and hence all Canvas entities) is always rendered below any DOM entities. 12 | * 13 | * The canvas layer will be automatically initialized if it has not been created yet. 14 | * 15 | * Create a canvas entity like this 16 | * ~~~ 17 | * var myEntity = Crafty.e("2D, Canvas, Color") 18 | * .color("green") 19 | * .attr({x: 13, y: 37, w: 42, h: 42}); 20 | *~~~ 21 | */ 22 | Crafty.c("Canvas", { 23 | init: function() { 24 | this.requires("Renderable"); 25 | 26 | //Allocate an object to hold this components current region 27 | this.currentRect = {}; 28 | 29 | // Add the default canvas layer if we aren't attached to a custom one 30 | if (!this._customLayer) { 31 | this._attachToLayer(Crafty.s("DefaultCanvasLayer")); 32 | } 33 | }, 34 | 35 | remove: function() { 36 | this._detachFromLayer(); 37 | }, 38 | 39 | /**@ 40 | * #.draw 41 | * @comp Canvas 42 | * @kind Method 43 | * 44 | * @sign public this .draw([Context ctx, Number x, Number y, Number w, Number h]) 45 | * @param ctx - Canvas 2D context if drawing on another canvas is required 46 | * @param x - X offset for drawing a segment 47 | * @param y - Y offset for drawing a segment 48 | * @param w - Width of the segment to draw 49 | * @param h - Height of the segment to draw 50 | * 51 | * Method to draw the entity on the canvas element. Can pass rect values for redrawing a segment of the entity. 52 | */ 53 | 54 | // Cache the various objects and arrays used in draw: 55 | drawVars: { 56 | type: "canvas", 57 | pos: {}, 58 | ctx: null, 59 | coord: [0, 0, 0, 0], 60 | co: { 61 | x: 0, 62 | y: 0, 63 | w: 0, 64 | h: 0 65 | } 66 | }, 67 | 68 | draw: function(ctx, x, y, w, h) { 69 | if (!this.ready) return; 70 | 71 | var pos = this.drawVars.pos; 72 | pos._x = this._x + (x || 0); 73 | pos._y = this._y + (y || 0); 74 | pos._w = w || this._w; 75 | pos._h = h || this._h; 76 | 77 | var context = ctx || this._drawContext; 78 | var coord = this.__coord || [0, 0, 0, 0]; 79 | var co = this.drawVars.co; 80 | co.x = coord[0] + (x || 0); 81 | co.y = coord[1] + (y || 0); 82 | co.w = w || coord[2]; 83 | co.h = h || coord[3]; 84 | 85 | // If we are going to perform any entity-specific changes to the current context, save the current state 86 | if (this._flipX || (this._flipY || this._rotation)) { 87 | context.save(); 88 | } 89 | 90 | // rotate the context about this entity's origin 91 | if (this._rotation !== 0) { 92 | context.translate( 93 | this._origin.x + this._x, 94 | this._origin.y + this._y 95 | ); 96 | pos._x = -this._origin.x; 97 | pos._y = -this._origin.y; 98 | context.rotate((this._rotation % 360) * (Math.PI / 180)); 99 | } 100 | 101 | // We realize a flipped entity by scaling the context in the opposite direction, then adjusting the position coordinates to match 102 | if (this._flipX || this._flipY) { 103 | context.scale(this._flipX ? -1 : 1, this._flipY ? -1 : 1); 104 | if (this._flipX) { 105 | pos._x = -(pos._x + pos._w); 106 | } 107 | if (this._flipY) { 108 | pos._y = -(pos._y + pos._h); 109 | } 110 | } 111 | 112 | var globalpha; 113 | 114 | //draw with alpha 115 | if (this._alpha < 1.0) { 116 | globalpha = context.globalAlpha; 117 | context.globalAlpha = this._alpha; 118 | } 119 | 120 | this.drawVars.ctx = context; 121 | this.trigger("Draw", this.drawVars); 122 | 123 | // If necessary, restore context 124 | if (this._rotation !== 0 || (this._flipX || this._flipY)) { 125 | context.restore(); 126 | } 127 | if (globalpha) { 128 | context.globalAlpha = globalpha; 129 | } 130 | return this; 131 | } 132 | }); 133 | -------------------------------------------------------------------------------- /src/graphics/dom-layer.js: -------------------------------------------------------------------------------- 1 | var Crafty = require("../core/core.js"), 2 | document = window.document; 3 | 4 | /**@ 5 | * #DomLayer 6 | * @category Graphics 7 | * @kind System 8 | * 9 | * Collection of mostly private methods to represent entities using the DOM. 10 | */ 11 | Crafty._registerLayerTemplate("DOM", { 12 | type: "DOM", 13 | 14 | _changedObjs: [], 15 | 16 | /**@ 17 | * #._div 18 | * @comp DomLayer 19 | * @kind Property 20 | * @private 21 | * 22 | * A div inside the `#cr-stage` div that holds all DOM entities. 23 | */ 24 | _div: null, 25 | 26 | events: { 27 | // Respond to init & remove events 28 | LayerInit: "layerInit", 29 | LayerRemove: "layerRemove", 30 | // Bind scene rendering (see drawing.js) 31 | RenderScene: "_render", 32 | // Listen for pixelart changes 33 | PixelartSet: "_setPixelart" 34 | // Layers should generally listen for resize events, 35 | // but the DOM layers automatically inherit the stage's dimensions 36 | //"ViewportResize": "_resize" 37 | }, 38 | 39 | layerInit: function() { 40 | // Avoid shared state between systems 41 | this._changedObjs = []; 42 | 43 | // Create the div that will contain DOM elements 44 | var div = (this._div = document.createElement("div")); 45 | 46 | Crafty.stage.elem.appendChild(div); 47 | div.style.position = "absolute"; 48 | div.style.zIndex = this.options.z; 49 | div.style.transformStyle = "preserve-3d"; // Seems necessary for Firefox to preserve zIndexes? 50 | }, 51 | 52 | // Cleanup the DOM when the layer is destroyed 53 | layerRemove: function() { 54 | this._div.parentNode.removeChild(this._div); 55 | }, 56 | 57 | // Handle whether images should be smoothed or not 58 | _setPixelArt: function(enabled) { 59 | var style = this._div.style; 60 | var camelize = Crafty.domHelper.camelize; 61 | if (enabled) { 62 | style[camelize("image-rendering")] = "optimizeSpeed"; /* legacy */ 63 | style[camelize("image-rendering")] = 64 | "-moz-crisp-edges"; /* Firefox */ 65 | style[camelize("image-rendering")] = "-o-crisp-edges"; /* Opera */ 66 | style[camelize("image-rendering")] = 67 | "-webkit-optimize-contrast"; /* Webkit (Chrome & Safari) */ 68 | style[camelize("-ms-interpolation-mode")] = 69 | "nearest-neighbor"; /* IE */ 70 | style[camelize("image-rendering")] = 71 | "optimize-contrast"; /* CSS3 proposed */ 72 | style[camelize("image-rendering")] = 73 | "pixelated"; /* CSS4 proposed */ 74 | style[camelize("image-rendering")] = 75 | "crisp-edges"; /* CSS4 proposed */ 76 | } else { 77 | style[camelize("image-rendering")] = "optimizeQuality"; /* legacy */ 78 | style[camelize("-ms-interpolation-mode")] = "bicubic"; /* IE */ 79 | style[camelize("image-rendering")] = "auto"; /* CSS3 */ 80 | } 81 | }, 82 | 83 | /**@ 84 | * #.debug 85 | * @comp DomLayer 86 | * @kind Method 87 | * 88 | * @sign public .debug() 89 | * 90 | * Logs the current list of entities that have been invalidated in this layer. 91 | */ 92 | debug: function() { 93 | Crafty.log(this._changedObjs); 94 | }, 95 | 96 | /**@ 97 | * #._render 98 | * @comp DomLayer 99 | * @kind Method 100 | * @private 101 | * 102 | * @sign public .render() 103 | * 104 | * When "RenderScene" is triggered, draws all DOM entities that have been flagged 105 | * 106 | * @see DOM#.draw 107 | */ 108 | _render: function() { 109 | var changed = this._changedObjs; 110 | // Adjust the viewport 111 | if (this._dirtyViewport) { 112 | this._setViewport(); 113 | this._dirtyViewport = false; 114 | } 115 | 116 | //if no objects have been changed, stop 117 | if (!changed.length) return; 118 | 119 | var i = 0, 120 | k = changed.length; 121 | //loop over all DOM elements needing updating 122 | for (; i < k; ++i) { 123 | changed[i].draw()._changed = false; 124 | } 125 | 126 | //reset DOM array 127 | changed.length = 0; 128 | }, 129 | 130 | /**@ 131 | * #.dirty 132 | * @comp DomLayer 133 | * @kind Method 134 | * @private 135 | * 136 | * @sign public .dirty(ent) 137 | * @param ent - The entity to mark as dirty 138 | * 139 | * Add an entity to the list of DOM object to draw 140 | */ 141 | dirty: function add(ent) { 142 | this._changedObjs.push(ent); 143 | }, 144 | 145 | /**@ 146 | * #.attach 147 | * @comp DomLayer 148 | * @kind Method 149 | * @private 150 | * 151 | * @sign public .attach(ent) 152 | * @param ent - The entity to add 153 | * 154 | * Add an entity to the layer 155 | */ 156 | attach: function attach(ent) { 157 | ent._drawContext = this.context; 158 | // attach the entity's div element to the dom layer 159 | this._div.appendChild(ent._element); 160 | // set position style and entity id 161 | ent._element.style.position = "absolute"; 162 | ent._element.id = "ent" + ent[0]; 163 | }, 164 | 165 | /**@ 166 | * #.detach 167 | * @comp DomLayer 168 | * @kind Method 169 | * @private 170 | * 171 | * @sign public .detach(ent) 172 | * @param ent - The entity to remove 173 | * 174 | * Removes an entity from the layer 175 | */ 176 | detach: function detach(ent) { 177 | this._div.removeChild(ent._element); 178 | }, 179 | 180 | // Sets the viewport position and scale 181 | // Called by render when the dirtyViewport flag is set 182 | _setViewport: function() { 183 | var style = this._div.style, 184 | view = this._viewportRect(); 185 | 186 | var scale = view._scale; 187 | var dx = -view._x * scale; 188 | var dy = -view._y * scale; 189 | 190 | style.transform = style[Crafty.support.prefix + "Transform"] = 191 | "scale(" + scale + ", " + scale + ")"; 192 | style.left = Math.round(dx) + "px"; 193 | style.top = Math.round(dy) + "px"; 194 | style.zIndex = this.options.z; 195 | } 196 | }); 197 | -------------------------------------------------------------------------------- /src/graphics/drawing.js: -------------------------------------------------------------------------------- 1 | var Crafty = require("../core/core.js"); 2 | 3 | Crafty.extend({ 4 | /**@ 5 | * #Crafty.background 6 | * @category Graphics, Stage 7 | * @kind Method 8 | * 9 | * @sign public void Crafty.background(String style) 10 | * @param style - Modify the background with a color or image 11 | * 12 | * This method is a shortcut for adding a background 13 | * style to the stage element, i.e. 14 | * `Crafty.stage.elem.style.background = ...` 15 | * 16 | * For example, if you want the background to be white, 17 | * with an image in the center, you might use: 18 | * ~~~ 19 | * Crafty.background('#FFFFFF url(landscape.png) no-repeat center center'); 20 | * ~~~ 21 | */ 22 | background: function(style) { 23 | Crafty.stage.elem.style.background = style; 24 | }, 25 | 26 | /**@ 27 | * #Crafty.pixelart 28 | * @category Graphics 29 | * @kind Method 30 | * 31 | * @sign public void Crafty.pixelart(Boolean enabled) 32 | * @param enabled - whether to preserve sharp edges when rendering images 33 | * 34 | * Sets the image smoothing for drawing images (for all layer types). 35 | * 36 | * Setting this to true disables smoothing for images, which is the preferred 37 | * way for drawing pixel art. Defaults to false. 38 | * 39 | * This feature is experimental and you should be careful with cross-browser compatibility. 40 | * The best way to disable image smoothing is to use the Canvas render method and the Sprite component for drawing your entities. 41 | * 42 | * If you want to switch modes in the middle of a scene, 43 | * be aware that canvas entities won't be drawn in the new style until something else invalidates them. 44 | * (You can manually invalidate all canvas entities with `Crafty("Canvas").trigger("Invalidate");`) 45 | * 46 | * @note Firefox_26 currently has a [bug](https://bugzilla.mozilla.org/show_bug.cgi?id=696630) 47 | * which prevents disabling image smoothing for Canvas entities that use the Image component. Use the Sprite 48 | * component instead. 49 | * 50 | * @note Webkit (Chrome & Safari) currently has a bug [link1](http://code.google.com/p/chromium/issues/detail?id=134040) 51 | * [link2](http://code.google.com/p/chromium/issues/detail?id=106662) that prevents disabling image smoothing 52 | * for DOM entities. 53 | * 54 | * @example 55 | * This is the preferred way to draw pixel art with the best cross-browser compatibility. 56 | * ~~~ 57 | * Crafty.pixelart(true); 58 | * 59 | * Crafty.sprite(imgWidth, imgHeight, "spriteMap.png", {sprite1:[0,0]}); 60 | * Crafty.e("2D, Canvas, sprite1"); 61 | * ~~~ 62 | */ 63 | _pixelartEnabled: false, 64 | pixelart: function(enabled) { 65 | Crafty._pixelartEnabled = enabled; 66 | Crafty.trigger("PixelartSet", enabled); 67 | } 68 | }); 69 | -------------------------------------------------------------------------------- /src/graphics/html.js: -------------------------------------------------------------------------------- 1 | var Crafty = require("../core/core.js"); 2 | 3 | /**@ 4 | * #HTML 5 | * @category Graphics 6 | * @kind Component 7 | * 8 | * A component which allows for the insertion of arbitrary HTML into a DOM entity. 9 | * 10 | * Adding this to an entity will automatically add the `DOM` component. 11 | */ 12 | Crafty.c("HTML", { 13 | inner: "", 14 | 15 | init: function() { 16 | this.requires("2D, DOM"); 17 | }, 18 | 19 | /**@ 20 | * #.replace 21 | * @comp HTML 22 | * @kind Method 23 | * 24 | * @sign public this .replace(String html) 25 | * @param html - arbitrary html 26 | * 27 | * This method will replace the content of this entity with the supplied html 28 | * 29 | * @example 30 | * Create a link 31 | * ~~~ 32 | * Crafty.e("HTML") 33 | * .attr({x:20, y:20, w:100, h:100}) 34 | * .replace("Index"); 35 | * ~~~ 36 | */ 37 | replace: function(new_html) { 38 | this.inner = new_html; 39 | this._element.innerHTML = new_html; 40 | return this; 41 | }, 42 | 43 | /**@ 44 | * #.append 45 | * @comp HTML 46 | * @kind Method 47 | * 48 | * @sign public this .append(String html) 49 | * @param html - arbitrary html 50 | * 51 | * This method will add the supplied html in the end of the entity 52 | * 53 | * @example 54 | * Create a link 55 | * ~~~ 56 | * Crafty.e("HTML") 57 | * .attr({x:20, y:20, w:100, h:100}) 58 | * .append("Index"); 59 | * ~~~ 60 | */ 61 | append: function(new_html) { 62 | this.inner += new_html; 63 | this._element.innerHTML += new_html; 64 | return this; 65 | }, 66 | 67 | /**@ 68 | * #.prepend 69 | * @comp HTML 70 | * @kind Method 71 | * 72 | * @sign public this .prepend(String html) 73 | * @param html - arbitrary html 74 | * 75 | * This method will add the supplied html in the beginning of the entity 76 | * 77 | * @example 78 | * Create a link 79 | * ~~~ 80 | * Crafty.e("HTML") 81 | * .attr({x:20, y:20, w:100, h:100}) 82 | * .prepend("Index"); 83 | * ~~~ 84 | */ 85 | prepend: function(new_html) { 86 | this.inner = new_html + this.inner; 87 | this._element.innerHTML = new_html + this.inner; 88 | return this; 89 | } 90 | }); 91 | -------------------------------------------------------------------------------- /src/graphics/image.js: -------------------------------------------------------------------------------- 1 | var Crafty = require("../core/core.js"); 2 | 3 | // 4 | // Define some variables required for webgl 5 | var fs = require("fs"); 6 | 7 | Crafty.defaultShader( 8 | "Image", 9 | new Crafty.WebGLShader( 10 | fs.readFileSync(__dirname + "/shaders/sprite.vert", "utf8"), 11 | fs.readFileSync(__dirname + "/shaders/sprite.frag", "utf8"), 12 | [ 13 | { name: "aPosition", width: 2 }, 14 | { name: "aOrientation", width: 3 }, 15 | { name: "aLayer", width: 2 }, 16 | { name: "aTextureCoord", width: 2 } 17 | ], 18 | function(e, _entity) { 19 | var pos = e.pos; 20 | // Write texture coordinates 21 | e.program.writeVector( 22 | "aTextureCoord", 23 | 0, 24 | 0, 25 | 0, 26 | pos._h, 27 | pos._w, 28 | 0, 29 | pos._w, 30 | pos._h 31 | ); 32 | } 33 | ) 34 | ); 35 | 36 | /**@ 37 | * #Image 38 | * @category Graphics 39 | * @kind Component 40 | * 41 | * Draw an image with or without repeating (tiling). 42 | * 43 | * If the entity's width and height are smaller than the width and height of the image source, the image will appear cropped. 44 | * If the entity's dimensions are larger than the dimensions of the image source, the exact appearance of the remaining space will depend on what renderer (WebGL, DOM, or Canvas) is used. 45 | * However, if tiling is enabled, the remaining space will be filled by a repeating pattern of the image. 46 | * 47 | * @note Image scaling is not supported by this component. Use a spritesheet, defined by `Crafty.sprite`, consisting of a single `Sprite` instead. 48 | * 49 | * @see Sprite, Crafty.sprite 50 | */ 51 | Crafty.c("Image", { 52 | _repeat: "repeat", 53 | ready: false, 54 | 55 | init: function() { 56 | this.bind("Draw", this._drawImage); 57 | this.bind("LayerAttached", this._setupImage); 58 | }, 59 | 60 | remove: function() { 61 | this.unbind("LayerAttached", this._setupImage); 62 | this.unbind("Draw", this._drawImage); 63 | }, 64 | 65 | /**@ 66 | * #.image 67 | * @comp Image 68 | * @kind Method 69 | * 70 | * @trigger Invalidate - when the image is loaded 71 | * @sign public this .image(String url[, String repeat]) 72 | * @param url - URL of the image 73 | * @param repeat - If the image should be repeated to fill the entity. This follows CSS syntax: (`"no-repeat", "repeat", "repeat-x", "repeat-y"`), but defaults to `no-repeat`. 74 | * 75 | * Draw the specified image. 76 | * 77 | * @note The default value of repeat is `no-repeat`, which is different than the standard CSS default 78 | * 79 | * If the width and height are `0` and repeat is set to `no-repeat` the width and 80 | * height will automatically assume that of the image. This is an 81 | * easy way to create an image without needing sprites. 82 | * 83 | * If set to `no-repeat` and given dimensions larger than that of the image, 84 | * the exact appearance will depend on what renderer (WebGL, DOM, or Canvas) is used. 85 | * 86 | * @example 87 | * Will default to no-repeat. Entity width and height will be set to the images width and height 88 | * ~~~ 89 | * var ent = Crafty.e("2D, DOM, Image").image("myimage.png"); 90 | * ~~~ 91 | * Create a repeating background. 92 | * ~~~ 93 | * var bg = Crafty.e("2D, DOM, Image") 94 | * .attr({w: Crafty.viewport.width, h: Crafty.viewport.height}) 95 | * .image("bg.png", "repeat"); 96 | * ~~~ 97 | * 98 | * @see Crafty.sprite 99 | */ 100 | image: function(url, repeat) { 101 | this.__image = url; 102 | this._repeat = repeat || "no-repeat"; 103 | 104 | this.img = Crafty.asset(url); 105 | if (!this.img) { 106 | this.img = new Image(); 107 | Crafty.asset(url, this.img); 108 | this.img.src = url; 109 | var self = this; 110 | 111 | this.img.onload = function() { 112 | self._setupImage(self._drawLayer); 113 | }; 114 | } else { 115 | this._setupImage(this._drawLayer); 116 | } 117 | 118 | this.trigger("Invalidate"); 119 | 120 | return this; 121 | }, 122 | 123 | // called on image change or layer attachment 124 | _setupImage: function(layer) { 125 | if (!this.img || !layer) return; 126 | 127 | if (layer.type === "Canvas") { 128 | this._pattern = this._drawContext.createPattern( 129 | this.img, 130 | this._repeat 131 | ); 132 | } else if (layer.type === "WebGL") { 133 | this._establishShader( 134 | "image:" + this.__image, 135 | Crafty.defaultShader("Image") 136 | ); 137 | this.program.setTexture( 138 | this._drawLayer.makeTexture( 139 | this.__image, 140 | this.img, 141 | this._repeat !== "no-repeat" 142 | ) 143 | ); 144 | } 145 | 146 | if (this._repeat === "no-repeat") { 147 | this.w = this.w || this.img.width; 148 | this.h = this.h || this.img.height; 149 | } 150 | 151 | this.ready = true; 152 | this.trigger("Invalidate"); 153 | }, 154 | 155 | _drawImage: function(e) { 156 | if (e.type === "canvas") { 157 | //skip if no image 158 | if (!this.ready || !this._pattern) return; 159 | 160 | var context = e.ctx; 161 | 162 | context.fillStyle = this._pattern; 163 | 164 | context.save(); 165 | context.translate(e.pos._x, e.pos._y); 166 | context.fillRect(0, 0, e.pos._w, e.pos._h); 167 | context.restore(); 168 | } else if (e.type === "DOM") { 169 | if (this.__image) { 170 | e.style.backgroundImage = "url(" + this.__image + ")"; 171 | e.style.backgroundRepeat = this._repeat; 172 | } 173 | } else if (e.type === "webgl") { 174 | e.program.draw(e, this); 175 | } 176 | } 177 | }); 178 | -------------------------------------------------------------------------------- /src/graphics/renderable.js: -------------------------------------------------------------------------------- 1 | var Crafty = require("../core/core.js"); 2 | 3 | /**@ 4 | * #Renderable 5 | * @category Graphics 6 | * @kind Component 7 | * 8 | * Component for any entity that has a position on the stage. 9 | * @trigger Invalidate - when the entity needs to be redrawn 10 | */ 11 | Crafty.c("Renderable", { 12 | // Flag for tracking whether the entity is dirty or not 13 | _changed: false, 14 | 15 | /**@ 16 | * #.alpha 17 | * @comp Renderable 18 | * @kind Property 19 | * 20 | * Transparency of an entity. Must be a decimal value between 0.0 being fully transparent to 1.0 being fully opaque. 21 | */ 22 | _alpha: 1.0, 23 | 24 | /**@ 25 | * #.visible 26 | * @comp Renderable 27 | * @kind Property 28 | * 29 | * If the entity is visible or not. Accepts a true or false value. 30 | * Can be used for optimization by setting an entities visibility to false when not needed to be drawn. 31 | * 32 | * The entity will still exist and can be collided with but just won't be drawn. 33 | */ 34 | _visible: true, 35 | 36 | _setterRenderable: function(name, value) { 37 | if (this[name] === value) { 38 | return; 39 | } 40 | 41 | //everything will assume the value 42 | this[name] = value; 43 | 44 | // flag for redraw 45 | this.trigger("Invalidate"); 46 | }, 47 | 48 | // Setup all the properties that we need to define 49 | properties: { 50 | alpha: { 51 | set: function(v) { 52 | this._setterRenderable("_alpha", v); 53 | }, 54 | get: function() { 55 | return this._alpha; 56 | }, 57 | configurable: true, 58 | enumerable: true 59 | }, 60 | _alpha: { enumerable: false }, 61 | 62 | visible: { 63 | set: function(v) { 64 | this._setterRenderable("_visible", v); 65 | }, 66 | get: function() { 67 | return this._visible; 68 | }, 69 | configurable: true, 70 | enumerable: true 71 | }, 72 | _visible: { enumerable: false } 73 | }, 74 | 75 | init: function() {}, 76 | 77 | // Need to store visibility before being frozen 78 | _hideOnUnfreeze: false, 79 | events: { 80 | Freeze: function() { 81 | this._hideOnUnfreeze = !this._visible; 82 | this._visible = false; 83 | this.trigger("Invalidate"); 84 | }, 85 | Unfreeze: function() { 86 | this._visible = !this._hideOnUnfreeze; 87 | this.trigger("Invalidate"); 88 | } 89 | }, 90 | 91 | // Renderable assumes that a draw layer has 3 important methods: attach, detach, and dirty 92 | 93 | // Dirty the entity when it's invalidated 94 | _invalidateRenderable: function() { 95 | //flag if changed 96 | if (this._changed === false) { 97 | this._changed = true; 98 | this._drawLayer.dirty(this); 99 | } 100 | }, 101 | 102 | // Attach the entity to a layer to be rendered 103 | _attachToLayer: function(layer) { 104 | if (this._drawLayer) { 105 | this._detachFromLayer(); 106 | } 107 | this._drawLayer = layer; 108 | layer.attach(this); 109 | this.bind("Invalidate", this._invalidateRenderable); 110 | this.trigger("LayerAttached", layer); 111 | this.trigger("Invalidate"); 112 | }, 113 | 114 | // Detach the entity from a layer 115 | _detachFromLayer: function() { 116 | if (!this._drawLayer) { 117 | return; 118 | } 119 | this._drawLayer.detach(this); 120 | this.unbind("Invalidate", this._invalidateRenderable); 121 | this.trigger("LayerDetached", this._drawLayer); 122 | delete this._drawLayer; 123 | }, 124 | 125 | /**@ 126 | * #.flip 127 | * @comp Renderable 128 | * @kind Method 129 | * 130 | * @trigger Invalidate - when the entity has flipped 131 | * @sign public this .flip(String dir) 132 | * @param dir - Flip direction 133 | * 134 | * Flip entity on passed direction 135 | * 136 | * @example 137 | * ~~~ 138 | * this.flip("X") 139 | * ~~~ 140 | */ 141 | flip: function(dir) { 142 | dir = dir || "X"; 143 | if (!this["_flip" + dir]) { 144 | this["_flip" + dir] = true; 145 | this.trigger("Invalidate"); 146 | } 147 | return this; 148 | }, 149 | 150 | /**@ 151 | * #.unflip 152 | * @comp Renderable 153 | * @kind Method 154 | * 155 | * @trigger Invalidate - when the entity has unflipped 156 | * @sign public this .unflip(String dir) 157 | * @param dir - Unflip direction 158 | * 159 | * Unflip entity on passed direction (if it's flipped) 160 | * 161 | * @example 162 | * ~~~ 163 | * this.unflip("X") 164 | * ~~~ 165 | */ 166 | unflip: function(dir) { 167 | dir = dir || "X"; 168 | if (this["_flip" + dir]) { 169 | this["_flip" + dir] = false; 170 | this.trigger("Invalidate"); 171 | } 172 | return this; 173 | } 174 | }); 175 | -------------------------------------------------------------------------------- /src/graphics/shaders/color.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | varying lowp vec4 vColor; 3 | void main(void) { 4 | gl_FragColor = vColor; 5 | } -------------------------------------------------------------------------------- /src/graphics/shaders/color.vert: -------------------------------------------------------------------------------- 1 | attribute vec2 aPosition; 2 | attribute vec3 aOrientation; 3 | attribute vec2 aLayer; 4 | attribute vec4 aColor; 5 | 6 | varying lowp vec4 vColor; 7 | 8 | uniform vec4 uViewport; 9 | 10 | mat4 viewportScale = mat4(2.0 / uViewport.z, 0, 0, 0, 0, -2.0 / uViewport.w, 0,0, 0, 0,1,0, -1,+1,0,1); 11 | vec4 viewportTranslation = vec4(uViewport.xy, 0, 0); 12 | 13 | void main() { 14 | vec2 pos = aPosition; 15 | vec2 entityOrigin = aOrientation.xy; 16 | mat2 entityRotationMatrix = mat2(cos(aOrientation.z), sin(aOrientation.z), -sin(aOrientation.z), cos(aOrientation.z)); 17 | 18 | pos = entityRotationMatrix * (pos - entityOrigin) + entityOrigin; 19 | gl_Position = viewportScale * (viewportTranslation + vec4(pos, 1.0/(1.0+exp(aLayer.x) ), 1) ); 20 | vColor = vec4(aColor.rgb*aColor.a*aLayer.y, aColor.a*aLayer.y); 21 | } -------------------------------------------------------------------------------- /src/graphics/shaders/sprite.frag: -------------------------------------------------------------------------------- 1 | varying mediump vec3 vTextureCoord; 2 | 3 | uniform sampler2D uSampler; 4 | uniform mediump vec2 uTextureDimensions; 5 | 6 | void main(void) { 7 | highp vec2 coord = vTextureCoord.xy / uTextureDimensions; 8 | mediump vec4 base_color = texture2D(uSampler, coord); 9 | gl_FragColor = vec4(base_color.rgb*base_color.a*vTextureCoord.z, base_color.a*vTextureCoord.z); 10 | } -------------------------------------------------------------------------------- /src/graphics/shaders/sprite.vert: -------------------------------------------------------------------------------- 1 | attribute vec2 aPosition; 2 | attribute vec3 aOrientation; 3 | attribute vec2 aLayer; 4 | attribute vec2 aTextureCoord; 5 | 6 | varying mediump vec3 vTextureCoord; 7 | 8 | uniform vec4 uViewport; 9 | uniform mediump vec2 uTextureDimensions; 10 | 11 | mat4 viewportScale = mat4(2.0 / uViewport.z, 0, 0, 0, 0, -2.0 / uViewport.w, 0,0, 0, 0,1,0, -1,+1,0,1); 12 | vec4 viewportTranslation = vec4(uViewport.xy, 0, 0); 13 | 14 | void main() { 15 | vec2 pos = aPosition; 16 | vec2 entityOrigin = aOrientation.xy; 17 | mat2 entityRotationMatrix = mat2(cos(aOrientation.z), sin(aOrientation.z), -sin(aOrientation.z), cos(aOrientation.z)); 18 | 19 | pos = entityRotationMatrix * (pos - entityOrigin) + entityOrigin ; 20 | gl_Position = viewportScale * (viewportTranslation + vec4(pos, 1.0/(1.0+exp(aLayer.x) ), 1) ); 21 | vTextureCoord = vec3(aTextureCoord, aLayer.y); 22 | } -------------------------------------------------------------------------------- /src/inputs/dom-events.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | _events: {}, 3 | 4 | /**@ 5 | * #Crafty.addEvent 6 | * @category Events, Misc 7 | * @kind Method 8 | * 9 | * @sign public this Crafty.addEvent(Object ctx, HTMLElement obj, String event, Function callback) 10 | * @param ctx - Context of the callback or the value of `this` 11 | * @param obj - Element to add the DOM event to 12 | * @param event - Event name to bind to 13 | * @param callback - Method to execute when triggered 14 | * 15 | * Adds DOM level 3 events to elements. The arguments it accepts are the call 16 | * context (the value of `this`), the DOM element to attach the event to, 17 | * the event name (without `on` (`click` rather than `onclick`)) and 18 | * finally the callback method. 19 | * 20 | * If no element is passed, the default element will be `window.document`. 21 | * 22 | * Callbacks are passed with event data. 23 | * 24 | * @note This is related to DOM events only, not Crafty's own event system. 25 | * Of course, you can trigger Crafty events in the callback function! 26 | * 27 | * @example 28 | * Normally you'd use Crafty's built-in mouse component, but for the sake of an example let's pretend that doesn't exist. 29 | * The following code will add a stage-wide MouseDown event listener to the player, and log both which button was pressed 30 | * and the (x,y) coordinates in viewport/world/game space. 31 | * ~~~ 32 | * var player = Crafty.e("2D"); 33 | * player.onMouseDown = function(e) { 34 | * Crafty.log(e.mouseButton, e.realX, e.realY); 35 | * }; 36 | * Crafty.addEvent(player, Crafty.stage.elem, "mousedown", player.onMouseDown); 37 | * ~~~ 38 | * @see Crafty.removeEvent 39 | */ 40 | addEvent: function(ctx, obj, type, callback) { 41 | if (arguments.length === 3) { 42 | callback = type; 43 | type = obj; 44 | obj = window.document; 45 | } 46 | 47 | //save anonymous function to be able to remove 48 | var id = ctx[0] || "", 49 | afn = function(e) { 50 | callback.call(ctx, e); 51 | }; 52 | 53 | if (!this._events[id + obj + type + callback]) 54 | this._events[id + obj + type + callback] = afn; 55 | else { 56 | return; 57 | } 58 | 59 | obj.addEventListener(type, afn, false); 60 | }, 61 | 62 | /**@ 63 | * #Crafty.removeEvent 64 | * @category Events, Misc 65 | * @kind Method 66 | * 67 | * @sign public this Crafty.removeEvent(Object ctx, HTMLElement obj, String event, Function callback) 68 | * @param ctx - Context of the callback or the value of `this` 69 | * @param obj - Element the event is on 70 | * @param event - Name of the event 71 | * @param callback - Method executed when triggered 72 | * 73 | * Removes events attached by `Crafty.addEvent()`. All parameters must 74 | * be the same that were used to attach the event including a reference 75 | * to the callback method. 76 | * 77 | * @see Crafty.addEvent 78 | */ 79 | removeEvent: function(ctx, obj, type, callback) { 80 | if (arguments.length === 3) { 81 | callback = type; 82 | type = obj; 83 | obj = window.document; 84 | } 85 | 86 | //retrieve anonymous function 87 | var id = ctx[0] || "", 88 | afn = this._events[id + obj + type + callback]; 89 | 90 | if (afn) { 91 | obj.removeEventListener(type, afn, false); 92 | delete this._events[id + obj + type + callback]; 93 | } 94 | } 95 | }; 96 | -------------------------------------------------------------------------------- /src/inputs/keyboard.js: -------------------------------------------------------------------------------- 1 | var Crafty = require("../core/core.js"); 2 | 3 | /**@ 4 | * #KeyboardSystem 5 | * @category Input 6 | * @kind System 7 | * 8 | * Provides access to key events. 9 | * @note Events and methods are inherited from the `KeyboardState` component. 10 | * 11 | * The event callbacks are triggered with a native [`KeyboardEvent`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent) 12 | * received by `window.document`, which is wrapped in a standard Crafty event object (as described in `KeyboardState`). 13 | * 14 | * These key events are triggered globally, thus on the global Crafty instance, every entity and system. 15 | * 16 | * @example 17 | * Move viewport by arrow keys. 18 | * ~~~ 19 | * Crafty.bind('KeyDown', function(e) { 20 | * if (e.key === Crafty.keys.LEFT_ARROW) { 21 | * Crafty.viewport.x++; 22 | * } else if (e.key === Crafty.keys.RIGHT_ARROW) { 23 | * Crafty.viewport.x--; 24 | * } else if (e.key === Crafty.keys.UP_ARROW) { 25 | * Crafty.viewport.y++; 26 | * } else if (e.key === Crafty.keys.DOWN_ARROW) { 27 | * Crafty.viewport.y--; 28 | * } 29 | * }); 30 | * ~~~ 31 | * @see KeyboardState, Keyboard 32 | */ 33 | Crafty.s( 34 | "Keyboard", 35 | Crafty.extend.call( 36 | Crafty.extend.call(new Crafty.__eventDispatcher(), { 37 | _evt: { 38 | // evt object to reuse 39 | eventName: "", 40 | key: 0, 41 | which: 0, 42 | originalEvent: null 43 | }, 44 | 45 | prepareEvent: function(e) { 46 | var evt = this._evt; 47 | 48 | // Normalize event name 49 | var type = e.type; 50 | evt.eventName = 51 | type === "keydown" 52 | ? "KeyDown" 53 | : type === "keyup" 54 | ? "KeyUp" 55 | : type; 56 | 57 | // Normalize key to avoid cross-browser issues 58 | evt.which = e.charCode !== null ? e.charCode : e.keyCode; 59 | evt.key = e.keyCode || e.which; 60 | 61 | // wrap original event into standard Crafty event object 62 | // as original key event's properties are read-only 63 | evt.originalEvent = e; 64 | 65 | return evt; 66 | }, 67 | 68 | // this method will be called by KeyboardState iff triggerKey event was valid 69 | triggerKeyEvent: function(eventName, e) { 70 | Crafty.trigger(eventName, e); 71 | }, 72 | 73 | dispatchEvent: function(e) { 74 | var evt = this.prepareEvent(e); 75 | this.triggerKey(evt.eventName, evt); 76 | } 77 | }), 78 | Crafty.__keyboardStateTemplate 79 | ), 80 | {}, 81 | false 82 | ); 83 | 84 | /**@ 85 | * #Keyboard 86 | * @category Input 87 | * @kind Component 88 | * 89 | * Provides the entity with keyboard events. 90 | * Keyboard events get dispatched to all entities that have the Keyboard component. 91 | * @note If you do not add this component, key events will not be triggered on the entity. 92 | * 93 | * Triggers all events described in the `KeyboardState` component, these are: 94 | * @trigger KeyDown - when a key is pressed - KeyboardEvent 95 | * @trigger KeyUp - when a key is released - KeyboardEvent 96 | * 97 | * @example 98 | * ~~~ 99 | * Crafty.e("2D, DOM, Color, Keyboard") 100 | * .attr({x: 100, y: 100, w: 50, h: 50}) 101 | * .color("red") 102 | * .bind('KeyDown', function(e) { 103 | * if (e.key == Crafty.keys.LEFT_ARROW) { 104 | * this.x -= 1; 105 | * } else if (e.key == Crafty.keys.RIGHT_ARROW) { 106 | * this.x += 1; 107 | * } else if (e.key == Crafty.keys.UP_ARROW) { 108 | * this.y -= 1; 109 | * } else if (e.key == Crafty.keys.DOWN_ARROW) { 110 | * this.y += 1; 111 | * } 112 | * }); 113 | * ~~~ 114 | * 115 | * @see KeyboardState, KeyboardSystem 116 | */ 117 | Crafty.c("Keyboard", { 118 | // DEPRECATED: remove in an upcoming release 119 | isDown: function(key) { 120 | return Crafty.s("Keyboard").isKeyDown(key); 121 | } 122 | }); 123 | -------------------------------------------------------------------------------- /src/inputs/lifecycle.js: -------------------------------------------------------------------------------- 1 | var Crafty = require("../core/core.js"), 2 | document = window.document; 3 | 4 | // figure out which eventName to listen to for mousewheel events 5 | var mouseWheelEvent = 6 | typeof document.onwheel !== "undefined" 7 | ? "wheel" // modern browsers 8 | : typeof document.onmousewheel !== "undefined" 9 | ? "mousewheel" // old Webkit and IE 10 | : "DOMMouseScroll"; // old Firefox 11 | 12 | //initialize the input events onload 13 | Crafty._preBind("Load", function() { 14 | Crafty.addEvent(this, document.body, "mouseup", Crafty.detectBlur); 15 | Crafty.addEvent( 16 | Crafty.s("Keyboard"), 17 | window, 18 | "blur", 19 | Crafty.s("Keyboard").resetKeyDown 20 | ); 21 | Crafty.addEvent( 22 | Crafty.s("Mouse"), 23 | window, 24 | "mouseup", 25 | Crafty.s("Mouse").resetButtonDown 26 | ); 27 | Crafty.addEvent( 28 | Crafty.s("Touch"), 29 | window, 30 | "touchend", 31 | Crafty.s("Touch").resetTouchPoints 32 | ); 33 | Crafty.addEvent( 34 | Crafty.s("Touch"), 35 | window, 36 | "touchcancel", 37 | Crafty.s("Touch").resetTouchPoints 38 | ); 39 | 40 | Crafty.addEvent( 41 | Crafty.s("Keyboard"), 42 | "keydown", 43 | Crafty.s("Keyboard").processEvent 44 | ); 45 | Crafty.addEvent( 46 | Crafty.s("Keyboard"), 47 | "keyup", 48 | Crafty.s("Keyboard").processEvent 49 | ); 50 | 51 | Crafty.addEvent( 52 | Crafty.s("Mouse"), 53 | Crafty.stage.elem, 54 | "mousedown", 55 | Crafty.s("Mouse").processEvent 56 | ); 57 | Crafty.addEvent( 58 | Crafty.s("Mouse"), 59 | Crafty.stage.elem, 60 | "mouseup", 61 | Crafty.s("Mouse").processEvent 62 | ); 63 | Crafty.addEvent( 64 | Crafty.s("Mouse"), 65 | Crafty.stage.elem, 66 | "mousemove", 67 | Crafty.s("Mouse").processEvent 68 | ); 69 | Crafty.addEvent( 70 | Crafty.s("Mouse"), 71 | Crafty.stage.elem, 72 | "click", 73 | Crafty.s("Mouse").processEvent 74 | ); 75 | Crafty.addEvent( 76 | Crafty.s("Mouse"), 77 | Crafty.stage.elem, 78 | "dblclick", 79 | Crafty.s("Mouse").processEvent 80 | ); 81 | 82 | Crafty.addEvent(this, Crafty.stage.elem, "touchstart", this._touchDispatch); 83 | Crafty.addEvent(this, Crafty.stage.elem, "touchmove", this._touchDispatch); 84 | Crafty.addEvent(this, Crafty.stage.elem, "touchend", this._touchDispatch); 85 | Crafty.addEvent( 86 | this, 87 | Crafty.stage.elem, 88 | "touchcancel", 89 | this._touchDispatch 90 | ); 91 | Crafty.addEvent(this, Crafty.stage.elem, "touchleave", this._touchDispatch); 92 | 93 | Crafty.addEvent( 94 | Crafty.s("MouseWheel"), 95 | Crafty.stage.elem, 96 | mouseWheelEvent, 97 | Crafty.s("MouseWheel").processEvent 98 | ); 99 | }); 100 | 101 | Crafty.bind("Pause", function() { 102 | // Reset pressed keys and buttons 103 | Crafty.s("Keyboard").resetKeyDown(); 104 | Crafty.s("Mouse").resetButtonDown(); 105 | }); 106 | 107 | Crafty._preBind("CraftyStop", function() { 108 | // Reset pressed keys and buttons 109 | Crafty.s("Keyboard").resetKeyDown(); 110 | Crafty.s("Mouse").resetButtonDown(); 111 | }); 112 | 113 | Crafty._preBind("CraftyStop", function() { 114 | Crafty.removeEvent(this, document.body, "mouseup", Crafty.detectBlur); 115 | Crafty.removeEvent( 116 | Crafty.s("Keyboard"), 117 | window, 118 | "blur", 119 | Crafty.s("Keyboard").resetKeyDown 120 | ); 121 | Crafty.removeEvent( 122 | Crafty.s("Mouse"), 123 | window, 124 | "mouseup", 125 | Crafty.s("Mouse").resetButtonDown 126 | ); 127 | Crafty.removeEvent( 128 | Crafty.s("Touch"), 129 | window, 130 | "touchend", 131 | Crafty.s("Touch").resetTouchPoints 132 | ); 133 | Crafty.removeEvent( 134 | Crafty.s("Touch"), 135 | window, 136 | "touchcancel", 137 | Crafty.s("Touch").resetTouchPoints 138 | ); 139 | 140 | Crafty.removeEvent( 141 | Crafty.s("Keyboard"), 142 | "keydown", 143 | Crafty.s("Keyboard").processEvent 144 | ); 145 | Crafty.removeEvent( 146 | Crafty.s("Keyboard"), 147 | "keyup", 148 | Crafty.s("Keyboard").processEvent 149 | ); 150 | 151 | if (Crafty.stage) { 152 | Crafty.removeEvent( 153 | Crafty.s("Mouse"), 154 | Crafty.stage.elem, 155 | "mousedown", 156 | Crafty.s("Mouse").processEvent 157 | ); 158 | Crafty.removeEvent( 159 | Crafty.s("Mouse"), 160 | Crafty.stage.elem, 161 | "mouseup", 162 | Crafty.s("Mouse").processEvent 163 | ); 164 | Crafty.removeEvent( 165 | Crafty.s("Mouse"), 166 | Crafty.stage.elem, 167 | "mousemove", 168 | Crafty.s("Mouse").processEvent 169 | ); 170 | Crafty.removeEvent( 171 | Crafty.s("Mouse"), 172 | Crafty.stage.elem, 173 | "click", 174 | Crafty.s("Mouse").processEvent 175 | ); 176 | Crafty.removeEvent( 177 | Crafty.s("Mouse"), 178 | Crafty.stage.elem, 179 | "dblclick", 180 | Crafty.s("Mouse").processEvent 181 | ); 182 | 183 | Crafty.removeEvent( 184 | this, 185 | Crafty.stage.elem, 186 | "touchstart", 187 | this._touchDispatch 188 | ); 189 | Crafty.removeEvent( 190 | this, 191 | Crafty.stage.elem, 192 | "touchmove", 193 | this._touchDispatch 194 | ); 195 | Crafty.removeEvent( 196 | this, 197 | Crafty.stage.elem, 198 | "touchend", 199 | this._touchDispatch 200 | ); 201 | Crafty.removeEvent( 202 | this, 203 | Crafty.stage.elem, 204 | "touchcancel", 205 | this._touchDispatch 206 | ); 207 | Crafty.removeEvent( 208 | this, 209 | Crafty.stage.elem, 210 | "touchleave", 211 | this._touchDispatch 212 | ); 213 | 214 | Crafty.removeEvent( 215 | Crafty.s("MouseWheel"), 216 | Crafty.stage.elem, 217 | mouseWheelEvent, 218 | Crafty.s("MouseWheel").processEvent 219 | ); 220 | } 221 | }); 222 | -------------------------------------------------------------------------------- /src/inputs/util.js: -------------------------------------------------------------------------------- 1 | var Crafty = require("../core/core.js"); 2 | 3 | // common base functionality for all EventDispatchers 4 | Crafty.__eventDispatcher = function EventDispatcher() {}; 5 | Crafty.__eventDispatcher.prototype = { 6 | // this method should be setup as the entry callback for DOM events 7 | processEvent: function(e) { 8 | this.dispatchEvent(e); 9 | return this.preventBubbling(e); 10 | }, 11 | 12 | // main method that handles logic of incoming DOM events 13 | // to be implemented by instances 14 | dispatchEvent: function(e) { 15 | // normalize the event and prepare it for dispatching to Crafty, a system or entities 16 | // set e.eventName to proper event to be triggered 17 | // dispatch the element to Crafty, the proper system or entities 18 | // find the entity to dispatch to (e.g. mouse events) or dispatch it globally (e.g. key events) 19 | }, 20 | 21 | // prevents interaction with page (e.g. scrolling of page), if DOM events target Crafty's stage 22 | // automatically called for all incoming DOM events 23 | preventBubbling: function(e) { 24 | // only prevent something if DOM event targets Crafty's stage 25 | // prevent bubbling up for all events except key events backspace and F1-F12. 26 | // prevent default actions for all events except key events backspace and F1-F12 and except actions on INPUT and TEXTAREA. 27 | // Among others this prevent the arrow keys from scrolling the parent page of an iframe hosting the game 28 | if ( 29 | Crafty.selected && 30 | !(e.key === 8 || (e.key >= 112 && e.key <= 135)) 31 | ) { 32 | if (e.stopPropagation) e.stopPropagation(); 33 | else e.cancelBubble = true; 34 | 35 | // Don't prevent default actions if target node is input or textarea. 36 | if ( 37 | !e.target || 38 | (e.target.nodeName !== "INPUT" && 39 | e.target.nodeName !== "TEXTAREA") 40 | ) { 41 | if (e.preventDefault) { 42 | e.preventDefault(); 43 | } else { 44 | e.returnValue = false; 45 | } 46 | return false; 47 | } 48 | return true; 49 | } 50 | } 51 | }; 52 | 53 | Crafty.extend({ 54 | /**@ 55 | * #Crafty.selected 56 | * @category Input 57 | * @kind Property 58 | * @trigger CraftyFocus - is triggered when Crafty's stage gets selected 59 | * @trigger CraftyBlur - is triggered when Crafty's stage is no longer selected 60 | * 61 | * Check whether Crafty's stage (`Crafty.stage.elem`) is currently selected. 62 | * 63 | * After a click occurs inside Crafty's stage, this property is set to `true`. 64 | * After a click occurs outside Crafty's stage, this property is set to `false`. 65 | * 66 | * Defaults to true. 67 | * 68 | * @see Crafty.stage#Crafty.stage.elem 69 | */ 70 | selected: true, 71 | 72 | detectBlur: function(e) { 73 | var selected = 74 | e.clientX > Crafty.stage.x && 75 | e.clientX < Crafty.stage.x + Crafty.viewport.width && 76 | (e.clientY > Crafty.stage.y && 77 | e.clientY < Crafty.stage.y + Crafty.viewport.height); 78 | 79 | if (!Crafty.selected && selected) { 80 | Crafty.trigger("CraftyFocus"); 81 | } 82 | 83 | if (Crafty.selected && !selected) { 84 | Crafty.trigger("CraftyBlur"); 85 | } 86 | 87 | Crafty.selected = selected; 88 | } 89 | }); 90 | -------------------------------------------------------------------------------- /tests/test-browsers.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "browserName": "internet explorer", 4 | "version": "9", 5 | "platform": "Windows 7" 6 | }, 7 | { 8 | "browserName": "MicrosoftEdge", 9 | "version": "latest", 10 | "platform": "Windows 10", 11 | "hasWebGL": true 12 | }, 13 | { 14 | "browserName": "chrome", 15 | "version": "26", 16 | "platform": "Windows XP" 17 | }, 18 | { 19 | "browserName": "chrome", 20 | "version": "latest", 21 | "platform": "Windows 10", 22 | "syntheticMouseEvents": true 23 | }, 24 | { 25 | "browserName": "safari", 26 | "version": "6", 27 | "platform": "OS X 10.8", 28 | "syntheticKeyEvents": true, 29 | "syntheticMouseEvents": true 30 | }, 31 | { 32 | "browserName": "safari", 33 | "version": "10", 34 | "platform": "macOS 10.12", 35 | "syntheticKeyEvents": true, 36 | "syntheticMouseEvents": true 37 | }, 38 | { 39 | "browserName": "firefox", 40 | "version": "6", 41 | "platform": "Linux", 42 | "noWebdriver": true 43 | }, 44 | { 45 | "browserName": "firefox", 46 | "version": "latest", 47 | "platform": "Linux", 48 | "syntheticKeyEvents": true 49 | }, 50 | { 51 | "browserName": "android", 52 | "version": "4.4", 53 | "deviceName": "Android Emulator", 54 | "platform": "linux", 55 | "deviceOrientation": "landscape", 56 | "noWebdriver": true 57 | }, 58 | { 59 | "browserName": "android", 60 | "version": "5.1", 61 | "deviceName": "Android Emulator", 62 | "platform": "Linux", 63 | "deviceOrientation": "landscape", 64 | "noWebdriver": true 65 | }, 66 | { 67 | "browserName": "iphone", 68 | "version": "10.2", 69 | "deviceName": "iPhone Simulator", 70 | "platform": "OS X 10.12", 71 | "deviceOrientation": "landscape", 72 | "noWebdriver": true 73 | } 74 | ] 75 | -------------------------------------------------------------------------------- /tests/unit/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.jshintrc", 3 | "qunit": true, 4 | "globals": { 5 | "Crafty": false, 6 | "createTouchEvent": false, 7 | "Round": false, 8 | "resetStage": false, 9 | "keysUp": false, 10 | "keysDown": false 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/unit/assets/100x100.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftyjs/Crafty/f3982baa51af24d4e8a14a3194fd3f0e92a2dcba/tests/unit/assets/100x100.jpeg -------------------------------------------------------------------------------- /tests/unit/assets/100x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftyjs/Crafty/f3982baa51af24d4e8a14a3194fd3f0e92a2dcba/tests/unit/assets/100x100.png -------------------------------------------------------------------------------- /tests/unit/assets/craftyLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftyjs/Crafty/f3982baa51af24d4e8a14a3194fd3f0e92a2dcba/tests/unit/assets/craftyLogo.png -------------------------------------------------------------------------------- /tests/unit/assets/numbers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftyjs/Crafty/f3982baa51af24d4e8a14a3194fd3f0e92a2dcba/tests/unit/assets/numbers.png -------------------------------------------------------------------------------- /tests/unit/common.js: -------------------------------------------------------------------------------- 1 | ///////////////////////// 2 | // INIT & PAUSE CRAFTY // 3 | ///////////////////////// 4 | 5 | // Init Crafty. 6 | Crafty.init(); 7 | 8 | // By default Crafty is paused for all tests and time is simulated manually 9 | // by calling Crafty.timer.simulateFrames(amountOfFrames). 10 | // If you need to advance the real timer automatically, 11 | // enable the timer before your test and disable it again after your test. 12 | /* 13 | module("MyModule", { 14 | beforeEach: function() { 15 | // enable timer before each test in this module 16 | Crafty.pause(false); 17 | }, 18 | afterEach: function() { 19 | // disable timer after each test in this module 20 | Crafty.pause(true); 21 | } 22 | }); 23 | */ 24 | Crafty.pause(); 25 | 26 | ////////////////////// 27 | // Helper functions // 28 | ////////////////////// 29 | 30 | // Disable jshint rules for setting global vars 31 | // jshint -W020 32 | resetStage = function() { 33 | Crafty.viewport.reset(); 34 | Crafty.viewport.scroll("_x", 0); 35 | Crafty.viewport.scroll("_y", 0); 36 | Crafty.viewport.clampToEntities = true; 37 | }; 38 | 39 | Round = function(x) { 40 | return Math.round(x * 100) / 100; 41 | }; 42 | 43 | keysUp = function() { 44 | var keysToRelease = Array.prototype.slice.call(arguments); 45 | for (var k in keysToRelease) { 46 | var key = Crafty.keys[keysToRelease[k]] || keysToRelease[k]; 47 | Crafty.s("Keyboard").triggerKey("KeyUp", { eventName: "KeyUp", key: key }); 48 | } 49 | }; 50 | keysDown = function() { 51 | var keysToPress = Array.prototype.slice.call(arguments); 52 | for (var k in keysToPress) { 53 | var key = Crafty.keys[keysToPress[k]] || keysToPress[k]; 54 | Crafty.s("Keyboard").triggerKey("KeyDown", { 55 | eventName: "KeyDown", 56 | key: key 57 | }); 58 | } 59 | }; 60 | // jshint +W020 61 | 62 | ////////////////// 63 | // QUnit config // 64 | ////////////////// 65 | 66 | QUnit.testDone(function() { 67 | // Clean all entities at the end of each test 68 | Crafty("*").destroy(); 69 | }); 70 | 71 | QUnit.config.hidepassed = true; 72 | QUnit.config.reorder = false; 73 | 74 | /////////////////////// 75 | // OpenSauce logging // 76 | /////////////////////// 77 | 78 | var log = []; 79 | QUnit.testStart(function(testDetails) { 80 | QUnit.log(function(details) { 81 | if (!details.result) { 82 | details.name = testDetails.name; 83 | log.push(details); 84 | } 85 | }); 86 | }); 87 | QUnit.done(function(test_results) { 88 | var tests = []; 89 | for (var i = 0, len = log.length; i < len; i++) { 90 | var details = log[i]; 91 | tests.push({ 92 | name: details.name, 93 | result: details.result, 94 | expected: details.expected, 95 | actual: details.actual, 96 | source: details.source 97 | }); 98 | } 99 | test_results.tests = tests; 100 | if (typeof window !== "undefined") window.global_test_results = test_results; 101 | else if (typeof global !== "undefined") 102 | global.global_test_results = test_results; 103 | }); 104 | -------------------------------------------------------------------------------- /tests/unit/core/animation.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var module = QUnit.module; 3 | var test = QUnit.test; 4 | 5 | module("Easing"); 6 | 7 | test("Crafty.easing duration", function(_) { 8 | var e = new Crafty.easing(80); // 4 frames == 80ms by default 9 | _.strictEqual(e.duration, 80, "Default duration in ms"); 10 | }); 11 | 12 | test("Crafty.easing", function(_) { 13 | var e = new Crafty.easing(80); // 4 frames == 80ms by default 14 | e.tick(20); 15 | e.tick(20); 16 | _.strictEqual(e.value(), 0.5, ".5 after two steps"); 17 | e.tick(20); 18 | e.tick(20); 19 | _.strictEqual(e.value(), 1, "1 after completed"); 20 | e.tick(20); 21 | _.strictEqual(e.value(), 1, "Remains 1 after completion"); 22 | }); 23 | 24 | test("Crafty.easing with custom function", function(_) { 25 | var e = new Crafty.easing(80, function(t) { 26 | return t * t; 27 | }); // 4 frames == 80ms by default 28 | e.tick(20); 29 | e.tick(20); 30 | _.strictEqual(e.value(), 0.25, ".25 after two steps"); 31 | e.tick(20); 32 | e.tick(20); 33 | _.strictEqual(e.value(), 1, "1 after completed"); 34 | }); 35 | 36 | test("Crafty.easing with built-in smoothStep function", function(_) { 37 | var e = new Crafty.easing(80, "smoothStep"); // 4 frames == 80ms by default 38 | e.tick(20); 39 | _.strictEqual(e.value(), 0.15625, "0.15625 after one step"); 40 | e.tick(20); 41 | _.strictEqual(e.value(), 0.5, ".5 after two steps"); 42 | e.tick(20); 43 | e.tick(20); 44 | _.strictEqual(e.value(), 1, "1 after completed"); 45 | }); 46 | })(); 47 | -------------------------------------------------------------------------------- /tests/unit/core/instances.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var module = QUnit.module; 3 | var test = QUnit.test; 4 | 5 | var Crafty2 = global.craftyFactory(); 6 | 7 | module("Instances"); 8 | 9 | test("Separate crafty instances do not share property", function(_) { 10 | Crafty2.__SOME_PROPERTY__ = "__SOME_PROPERTY__"; 11 | 12 | _.strictEqual( 13 | Crafty2.__SOME_PROPERTY__, 14 | "__SOME_PROPERTY__", 15 | "Property set on one instance" 16 | ); 17 | _.notEqual( 18 | Crafty.__SOME_PROPERTY__, 19 | "__SOME_PROPERTY__", 20 | "Property not set on other instance" 21 | ); 22 | }); 23 | 24 | test("Separate crafty instances consist of different subobjects", function(_) { 25 | _.notDeepEqual(Crafty2, Crafty, "Properties are different"); 26 | }); 27 | })(); 28 | -------------------------------------------------------------------------------- /tests/unit/core/loader.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var module = QUnit.module; 3 | var test = QUnit.test; 4 | 5 | module("Loader"); 6 | 7 | test("Warning on old syntax", function(_) { 8 | var original_log = Crafty.log; 9 | var logged_message = ""; 10 | Crafty.log = function(msg) { 11 | logged_message = msg; 12 | }; 13 | Crafty.load(["falsey.png"], function() { 14 | logged_message = "nope"; 15 | }); 16 | _.ok( 17 | logged_message.indexOf("no longer works") >= 0, 18 | "Correctly logged warning." 19 | ); 20 | Crafty.log = original_log; 21 | }); 22 | 23 | test("assets loading", function(_) { 24 | _.expect(2); 25 | var done = _.async(); 26 | 27 | var items = [], 28 | checkItems = function() { 29 | var checks = 0; 30 | for (var i = 0, l = items.length, src; i < l; i++) { 31 | src = items[i].src; 32 | if (src) checks++; 33 | } 34 | return checks; 35 | }, 36 | wereItemsRemoved = function() { 37 | return ( 38 | Crafty.assets[Crafty.paths().images + "100x100.png"] === undefined && 39 | Crafty.assets[Crafty.paths().images + "100x100.jpeg"] === undefined && 40 | Crafty.assets[Crafty.paths().images + "craftyLogo.png"] === 41 | undefined && 42 | Crafty.components().no_free === undefined 43 | ); 44 | }; 45 | 46 | Crafty.paths({ images: "assets/" }); 47 | 48 | var assets_to_load = { 49 | images: ["100x100.png", "100x100.png", "100x100.jpeg"], 50 | sprites: { 51 | "craftyLogo.png": { 52 | tile: 147, 53 | tileh: 90, 54 | map: { 55 | no_free: [0, 0] 56 | } 57 | } 58 | } 59 | }; 60 | 61 | Crafty.load( 62 | assets_to_load, 63 | function() { 64 | Crafty.removeAssets(assets_to_load); 65 | _.ok( 66 | checkItems() === 3 && wereItemsRemoved(), 67 | "all assets have been successfully loaded, and then successfully removed" 68 | ); 69 | done(); 70 | }, 71 | function(data) { 72 | items.push(data); 73 | }, 74 | function(error) { 75 | _.strictEqual( 76 | error.src, 77 | "assets/100x100.png", 78 | "duplicate asset reported as error of load operation" 79 | ); 80 | } 81 | ); 82 | }); 83 | })(); 84 | -------------------------------------------------------------------------------- /tests/unit/core/model.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var module = QUnit.module; 3 | var test = QUnit.test; 4 | 5 | module("Model"); 6 | 7 | test("Get", function(_) { 8 | var fox; 9 | Crafty.c("Animal", { 10 | contact: { 11 | email: "test@example.com", 12 | address: { 13 | city: "Portland", 14 | state: "Oregon" 15 | } 16 | }, 17 | name: "Fox" 18 | }); 19 | fox = Crafty.e("Animal, Model"); 20 | 21 | _.strictEqual(fox.attr("contact.address.city"), "Portland"); 22 | _.strictEqual(fox.attr("contact.email"), "test@example.com"); 23 | _.strictEqual(fox.attr("name"), "Fox"); 24 | }); 25 | 26 | test("Set", function(_) { 27 | var fox; 28 | Crafty.c("Animal", { 29 | name: "Fox" 30 | }); 31 | 32 | fox = Crafty.e("Animal, Model"); 33 | 34 | fox.attr("name", "Foxxy"); 35 | _.strictEqual(fox.attr("name"), "Foxxy"); 36 | 37 | fox.attr("name", "Slick", {}); 38 | _.strictEqual(fox.attr("name"), "Slick"); 39 | 40 | fox.attr({ name: "Lucky" }); 41 | _.strictEqual(fox.attr("name"), "Lucky"); 42 | 43 | fox.attr({ name: "Spot" }, {}); 44 | _.strictEqual(fox.attr("name"), "Spot"); 45 | }); 46 | 47 | test("Set with dot notation", function(_) { 48 | var fox; 49 | Crafty.c("Animal", { 50 | contact: { 51 | email: "test@example.com", 52 | address: { 53 | city: "Portland", 54 | state: "Oregon" 55 | } 56 | }, 57 | name: "Fox" 58 | }); 59 | fox = Crafty.e("Animal, Model"); 60 | 61 | fox.attr("contact.address.city", "Salem"); 62 | 63 | _.deepEqual(fox.attr("contact.address"), { 64 | city: "Salem", 65 | state: "Oregon" 66 | }); 67 | }); 68 | 69 | test("Set Silent", function(_) { 70 | var fox, called; 71 | Crafty.c("Animal", { 72 | name: "Fox" 73 | }); 74 | 75 | fox = Crafty.e("Animal, Model"); 76 | 77 | called = false; 78 | fox.bind("Change", function() { 79 | called = true; 80 | }); 81 | 82 | fox.attr({ name: "Lucky" }, true); 83 | _.strictEqual(called, false); 84 | 85 | fox.attr({ name: "Spot" }, false); 86 | _.strictEqual(called, true); 87 | }); 88 | 89 | test("Set Recursive", function(_) { 90 | var fox; 91 | Crafty.c("Animal", { 92 | name: "Fox", 93 | contact: { 94 | email: "fox@example.com", 95 | phone: "555-555-4545" 96 | } 97 | }); 98 | 99 | fox = Crafty.e("Animal, Model"); 100 | 101 | fox.attr({ contact: { email: "foxxy@example.com" } }, false, true); 102 | 103 | _.deepEqual(fox.attr("contact"), { 104 | email: "foxxy@example.com", 105 | phone: "555-555-4545" 106 | }); 107 | }); 108 | 109 | test("Set triggers change events", function(_) { 110 | var fox, 111 | results = []; 112 | Crafty.c("Animal", { 113 | name: "Fox", 114 | contact: { 115 | email: "fox@example.com", 116 | phone: "555-555-4545" 117 | } 118 | }); 119 | 120 | fox = Crafty.e("Animal, Model"); 121 | 122 | fox.bind("Change", function() { 123 | results.push("Change"); 124 | }); 125 | fox.bind("Change[name]", function() { 126 | results.push("Change[name]"); 127 | }); 128 | fox.bind("Change[contact.email]", function() { 129 | results.push("Change[contact.email]"); 130 | }); 131 | 132 | fox.attr({ name: "Lucky" }); 133 | fox.attr({ contact: { email: "foxxy@example.com" } }, false, true); 134 | 135 | _.deepEqual(results, [ 136 | "Change[name]", 137 | "Change", 138 | "Change[contact.email]", 139 | "Change" 140 | ]); 141 | }); 142 | 143 | test("Dirty", function(_) { 144 | var fox; 145 | Crafty.c("Animal", { 146 | name: "Fox", 147 | dob: "March 21", 148 | age: 24 149 | }); 150 | 151 | fox = Crafty.e("Animal, Model"); 152 | 153 | _.strictEqual(fox.is_dirty(), false); 154 | _.strictEqual(fox.is_dirty("name"), false); 155 | _.strictEqual(fox.is_dirty("age"), false); 156 | 157 | fox.attr("name", "Lucky"); 158 | fox.attr("dob", "March 22"); 159 | 160 | _.strictEqual(fox.is_dirty(), true); 161 | _.strictEqual(fox.is_dirty("name"), true); 162 | _.strictEqual(fox.is_dirty("age"), false); 163 | 164 | _.deepEqual(fox.changed, ["name", "dob"]); 165 | }); 166 | })(); 167 | -------------------------------------------------------------------------------- /tests/unit/core/scenes.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var module = QUnit.module; 3 | var test = QUnit.test; 4 | 5 | module("Scenes"); 6 | 7 | test("Scene calling", function(_) { 8 | var x = 0; 9 | var sceneInit = function() { 10 | x = 13; 11 | }; 12 | Crafty.scene("test-call", sceneInit); 13 | Crafty.scene("test-call"); 14 | _.strictEqual(x, 13, "Scene called succesfully."); 15 | }); 16 | 17 | test("Scene parameters", function(_) { 18 | var x = 0; 19 | var paramTaker = function(y) { 20 | x = y; 21 | }; 22 | Crafty.scene("test-param", paramTaker); 23 | Crafty.scene("test-param", 11); 24 | _.strictEqual(x, 11, "Scene called succesfully with parameter."); 25 | }); 26 | 27 | test("Calling a scene destroys 2D entities", function(_) { 28 | Crafty.e("2D"); 29 | var sceneInit = function() {}; 30 | Crafty.scene("test-destroy", sceneInit); 31 | Crafty.scene("test-destroy"); 32 | var l = Crafty("2D").length; 33 | _.strictEqual(l, 0, "2D entity destroyed on scene change."); 34 | }); 35 | 36 | test("Calling a scene doesn't destroy 2D entities with Persist", function(_) { 37 | Crafty.e("2D, Persist"); 38 | var sceneInit = function() {}; 39 | Crafty.scene("test-persist", sceneInit); 40 | Crafty.scene("test-persist"); 41 | var l = Crafty("2D").length; 42 | _.strictEqual(l, 1, "Persist entity remains on scene change."); 43 | }); 44 | 45 | test("Scene uninit function called", function(_) { 46 | var x = 0; 47 | var y = 0; 48 | var sceneInit = function() { 49 | x = 13; 50 | }; 51 | var sceneUninit = function() { 52 | x = 20; 53 | }; 54 | var sceneGame = function() { 55 | y = 5; 56 | }; 57 | Crafty.defineScene("test-uninit", sceneInit, sceneUninit); 58 | Crafty.defineScene("game", sceneGame); 59 | Crafty.enterScene("test-uninit"); 60 | Crafty.enterScene("game"); 61 | _.strictEqual( 62 | x, 63 | 20, 64 | "Uninit scene called successfully when chanced to another scene" 65 | ); 66 | }); 67 | })(); 68 | -------------------------------------------------------------------------------- /tests/unit/core/storage.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var module = QUnit.module; 3 | var test = QUnit.test; 4 | 5 | module("Storage"); 6 | 7 | test("get a value", function(_) { 8 | Crafty.storage("name", "test"); 9 | var name = Crafty.storage("name"); 10 | 11 | _.strictEqual(name, "test", "the values should be equal"); 12 | 13 | Crafty.storage.remove("name"); 14 | }); 15 | 16 | test("get null when a value does not exist", function(_) { 17 | var name = Crafty.storage("notexisting"); 18 | _.strictEqual(name, null, "should be null"); 19 | }); 20 | 21 | test("remove an value", function(_) { 22 | Crafty.storage("person", "test"); 23 | _.strictEqual(Crafty.storage("person"), "test", "person should be defined"); 24 | 25 | Crafty.storage.remove("person"); 26 | 27 | var savedperson = Crafty.storage("person"); 28 | _.strictEqual( 29 | savedperson, 30 | null, 31 | "should be null because we just removed the value" 32 | ); 33 | }); 34 | })(); 35 | -------------------------------------------------------------------------------- /tests/unit/debug/debug.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var module = QUnit.module; 3 | var test = QUnit.test; 4 | 5 | module("DebugLayer"); 6 | 7 | test("DebugCanvas", function(_) { 8 | if (!Crafty.support.canvas) { 9 | expect(0); 10 | return; 11 | } 12 | var e = Crafty.e("2D, DebugCanvas"); 13 | var ctx = Crafty.DebugCanvas.context; 14 | 15 | e.debugFill("purple"); 16 | _.strictEqual( 17 | e._debug.fillStyle, 18 | "purple", 19 | "fill style set correctly on entity" 20 | ); 21 | 22 | e.debugStroke("green"); 23 | _.strictEqual( 24 | e._debug.strokeStyle, 25 | "green", 26 | "stroke style set correctly on entity" 27 | ); 28 | 29 | e.debugDraw(ctx); 30 | _.strictEqual( 31 | ctx.fillStyle, 32 | "#800080", 33 | "context.fillStyle set correctly on draw" 34 | ); // fillStyle will report the hex code 35 | _.strictEqual( 36 | ctx.strokeStyle, 37 | "#008000", 38 | "context.strokeStyle set correctly on draw" 39 | ); 40 | 41 | e.debugFill(); 42 | _.strictEqual( 43 | e._debug.fillStyle, 44 | "red", 45 | "default fill style set correctly" 46 | ); 47 | 48 | e.debugStroke(); 49 | _.strictEqual( 50 | e._debug.strokeStyle, 51 | "red", 52 | "default stroke style set correctly" 53 | ); 54 | 55 | e.destroy(); 56 | }); 57 | 58 | test("DebugRectangle", function(_) { 59 | var e = Crafty.e("2D, DebugRectangle").attr({ 60 | x: 10, 61 | y: 10, 62 | w: 10, 63 | h: 20 64 | }); 65 | e.debugRectangle(e._mbr || e); 66 | _.strictEqual(e.debugRect._x, 10, "debugRect has correct x coord"); 67 | _.strictEqual(e.debugRect._h, 20, "debugRect has correct height"); 68 | 69 | e.rotation = 90; 70 | e.debugRectangle(e._mbr || e); 71 | _.strictEqual( 72 | e.debugRect._h, 73 | 10, 74 | "debugRect has correct height of MBR after rotation" 75 | ); 76 | 77 | e.destroy(); 78 | }); 79 | 80 | test("Hitbox debugging", function(_) { 81 | var e = Crafty.e("2D, Collision, WiredHitBox") 82 | .attr({ 83 | x: 10, 84 | y: 10, 85 | w: 10, 86 | h: 20 87 | }) 88 | .collision(); 89 | _.strictEqual( 90 | e.polygon.points[0], 91 | 10, 92 | "WiredHitBox -- correct x coord for upper right corner" 93 | ); 94 | _.strictEqual( 95 | e.polygon.points[5], 96 | 30, 97 | "correct y coord for lower right corner" 98 | ); 99 | _.notEqual( 100 | typeof e._debug.strokeStyle, 101 | "undefined", 102 | "stroke style is assigned" 103 | ); 104 | _.strictEqual( 105 | typeof e._debug.fillStyle, 106 | "undefined", 107 | "fill style is undefined" 108 | ); 109 | 110 | e.destroy(); 111 | 112 | var e2 = Crafty.e("2D, Collision, SolidHitBox") 113 | .attr({ 114 | x: 10, 115 | y: 10, 116 | w: 10, 117 | h: 20 118 | }) 119 | .collision(); 120 | _.strictEqual( 121 | e2.polygon.points[0], 122 | 10, 123 | "SolidHitBox -- correct x coord for upper right corner" 124 | ); 125 | _.strictEqual( 126 | e2.polygon.points[5], 127 | 30, 128 | "correct y coord for lower right corner" 129 | ); 130 | _.strictEqual( 131 | typeof e2._debug.strokeStyle, 132 | "undefined", 133 | "stroke style is undefined" 134 | ); 135 | _.notEqual( 136 | typeof e2._debug.fillStyle, 137 | "undefined", 138 | "fill style is assigned" 139 | ); 140 | 141 | e2.collision(new Crafty.polygon([0, 0, 15, 0, 0, 15])); 142 | _.strictEqual( 143 | e2.polygon.points[5], 144 | 25, 145 | "After change -- correct y coord for third point" 146 | ); 147 | 148 | e2.destroy(); 149 | }); 150 | 151 | test("AreaMap debugging", function(_) { 152 | var e = Crafty.e("2D, AreaMap, WiredAreaMap") 153 | .attr({ 154 | x: 10, 155 | y: 10, 156 | w: 10, 157 | h: 20 158 | }) 159 | .areaMap(50, 0, 100, 100, 0, 100); 160 | _.strictEqual( 161 | e.polygon.points[0], 162 | 60, 163 | "WiredAreaMap -- correct x coord for upper right corner" 164 | ); 165 | _.strictEqual( 166 | e.polygon.points[5], 167 | 110, 168 | "correct y coord for lower right corner" 169 | ); 170 | _.notEqual( 171 | typeof e._debug.strokeStyle, 172 | "undefined", 173 | "stroke style is assigned" 174 | ); 175 | _.strictEqual( 176 | typeof e._debug.fillStyle, 177 | "undefined", 178 | "fill style is undefined" 179 | ); 180 | 181 | e.destroy(); 182 | 183 | var e2 = Crafty.e("2D, AreaMap, SolidAreaMap") 184 | .attr({ 185 | x: 10, 186 | y: 10, 187 | w: 10, 188 | h: 20 189 | }) 190 | .areaMap(50, 0, 100, 100, 0, 100); 191 | _.strictEqual( 192 | e2.polygon.points[0], 193 | 60, 194 | "SolidAreaMap -- correct x coord for upper right corner" 195 | ); 196 | _.strictEqual( 197 | e2.polygon.points[5], 198 | 110, 199 | "correct y coord for lower right corner" 200 | ); 201 | _.strictEqual( 202 | typeof e2._debug.strokeStyle, 203 | "undefined", 204 | "stroke style is undefined" 205 | ); 206 | _.notEqual( 207 | typeof e2._debug.fillStyle, 208 | "undefined", 209 | "fill style is assigned" 210 | ); 211 | 212 | e2.areaMap(new Crafty.polygon([0, 0, 15, 0, 0, 15])); 213 | _.strictEqual( 214 | e2.polygon.points[5], 215 | 25, 216 | "After change -- correct y coord for third point" 217 | ); 218 | 219 | e2.destroy(); 220 | }); 221 | })(); 222 | -------------------------------------------------------------------------------- /tests/unit/debug/logging.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var module = QUnit.module; 3 | var test = QUnit.test; 4 | 5 | module("Crafty.log"); 6 | 7 | test("Logging works when console.log is enabled", function(_) { 8 | // this makes sure we don't crash on IE9; handle with care as console not always available on IE9 9 | var logger = 10 | typeof window !== "undefined" 11 | ? (window.console = window.console || {}) 12 | : console; 13 | 14 | var original_log = logger.log; 15 | var logged_message = ""; 16 | logger.log = function(msg) { 17 | logged_message = msg; 18 | }; 19 | var test_message = "test message"; 20 | 21 | Crafty.log(test_message); 22 | _.strictEqual( 23 | logged_message, 24 | test_message, 25 | "Crafty.log correctly passes through to console.log" 26 | ); 27 | 28 | Crafty.loggingEnabled = false; 29 | logged_message = ""; 30 | Crafty.log(test_message); 31 | _.strictEqual( 32 | logged_message, 33 | "", 34 | "Crafty.log does nothing when logging is disabled." 35 | ); 36 | Crafty.loggingEnabled = true; 37 | 38 | logger.log = undefined; 39 | Crafty.log(test_message); 40 | _.strictEqual( 41 | logged_message, 42 | "", 43 | "Crafty.log does not crash when console.log is undefined." 44 | ); 45 | 46 | logger.log = original_log; 47 | }); 48 | })(); 49 | -------------------------------------------------------------------------------- /tests/unit/graphics/color.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var module = QUnit.module; 3 | var test = QUnit.test; 4 | 5 | module("Crafty.assignColor"); 6 | 7 | test("hex codes", function(_) { 8 | var c = {}; 9 | Crafty.assignColor("#FF0000", c); 10 | _.strictEqual(c._red, 255, "red is 255"); 11 | _.strictEqual(c._green, 0, "green is 0"); 12 | _.strictEqual(c._blue, 0, "blue is 0"); 13 | _.strictEqual(c._strength, 1, "strength is 1.0"); 14 | }); 15 | 16 | test("short hex codes", function(_) { 17 | var c = {}; 18 | Crafty.assignColor("#123", c); 19 | _.strictEqual(c._red, 17, "red is #11"); 20 | _.strictEqual(c._green, 34, "green is #22"); 21 | _.strictEqual(c._blue, 51, "blue is #33"); 22 | _.strictEqual(c._strength, 1, "strength is 1.0"); 23 | }); 24 | 25 | test("common color names", function(_) { 26 | var c = {}; 27 | Crafty.assignColor("red", c); 28 | _.strictEqual(c._red, 255, "red is 255"); 29 | _.strictEqual(c._green, 0, "green is 0"); 30 | _.strictEqual(c._blue, 0, "blue is 0"); 31 | _.strictEqual(c._strength, 1, "strength is 1.0"); 32 | }); 33 | 34 | test("less common color names", function(_) { 35 | var c = {}; 36 | Crafty.assignColor("lightsalmon", c); 37 | _.strictEqual(c._red, 255, "red is 255"); 38 | _.strictEqual(c._green, 160, "green is 160"); 39 | _.strictEqual(c._blue, 122, "blue is 122"); 40 | _.strictEqual(c._strength, 1, "strength is 1.0"); 41 | 42 | Crafty.assignColor("cadetblue", c); 43 | _.strictEqual(c._red, 95, "red is 255"); 44 | _.strictEqual(c._green, 158, "green is 160"); 45 | _.strictEqual(c._blue, 160, "blue is 122"); 46 | _.strictEqual(c._strength, 1, "strength is 1.0"); 47 | }); 48 | 49 | test("rgb strings", function(_) { 50 | var c = {}; 51 | Crafty.assignColor("rgb(1, 2, 3)", c); 52 | _.strictEqual(c._red, 1, "red is 1"); 53 | _.strictEqual(c._green, 2, "green is 2"); 54 | _.strictEqual(c._blue, 3, "blue is 3"); 55 | _.strictEqual(c._strength, 1, "strength is 1.0"); 56 | }); 57 | 58 | test("rgba strings", function(_) { 59 | var c = {}; 60 | Crafty.assignColor("rgba(255, 0, 0, 0.5)", c); 61 | _.strictEqual(c._red, 255, "red is 255"); 62 | _.strictEqual(c._green, 0, "green is 0"); 63 | _.strictEqual(c._blue, 0, "blue is 0"); 64 | _.strictEqual(c._strength, 0.5, "strength is 0.5"); 65 | }); 66 | 67 | module("Color"); 68 | 69 | test("Color by single string", function(_) { 70 | var e = Crafty.e("2D, DOM, Color"); 71 | e.color("red"); 72 | _.strictEqual(e._red, 255, "red is 255"); 73 | _.strictEqual(e._green, 0, "green is 0"); 74 | _.strictEqual(e._blue, 0, "blue is 0"); 75 | _.strictEqual(e._strength, 1, "strength is 1.0"); 76 | }); 77 | 78 | test("Color by rgb", function(_) { 79 | var e = Crafty.e("2D, DOM, Color"); 80 | e.color(255, 0, 0); 81 | _.strictEqual(e._red, 255, "red is 255"); 82 | _.strictEqual(e._green, 0, "green is 0"); 83 | _.strictEqual(e._blue, 0, "blue is 0"); 84 | _.strictEqual(e._strength, 1, "strength is 1.0"); 85 | }); 86 | 87 | test("Color by rgba", function(_) { 88 | var e = Crafty.e("2D, DOM, Color"); 89 | e.color(255, 0, 0, 0.5); 90 | _.strictEqual(e._red, 255, "red is 255"); 91 | _.strictEqual(e._green, 0, "green is 0"); 92 | _.strictEqual(e._blue, 0, "blue is 0"); 93 | _.strictEqual(e._strength, 0.5, "strength is 0.5"); 94 | }); 95 | 96 | test("Color by string + alpha", function(_) { 97 | var e = Crafty.e("2D, DOM, Color"); 98 | e.color("red", 0.5); 99 | _.strictEqual(e._red, 255, "red is 255"); 100 | _.strictEqual(e._green, 0, "green is 0"); 101 | _.strictEqual(e._blue, 0, "blue is 0"); 102 | _.strictEqual(e._strength, 0.5, "strength is 0.5"); 103 | }); 104 | })(); 105 | -------------------------------------------------------------------------------- /tests/unit/graphics/dom-helper.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var module = QUnit.module; 3 | var test = QUnit.test; 4 | 5 | module("dom-helper", { 6 | beforeEach: function() { 7 | var div = document.createElement("div"); 8 | div.style.position = "absolute"; 9 | div.style.top = "10000px"; 10 | div.textContent = "test"; 11 | div.id = "test"; 12 | document.body.appendChild(div); 13 | 14 | Crafty.stage.x = 10; 15 | Crafty.stage.y = 10; 16 | Crafty.viewport.scale(); 17 | Crafty.viewport.x = 0; 18 | Crafty.viewport.y = 0; 19 | 20 | if (document.documentElement) { 21 | document.documentElement.scrollTop = 100; 22 | } 23 | // 2nd condition is workaround for limitation while testing on headless browser (phantomjs) 24 | if ( 25 | !document.documentElement || 26 | document.documentElement.scrollTop !== 100 27 | ) { 28 | document.body.scrollTop = 100; 29 | } 30 | }, 31 | 32 | afterEach: function() { 33 | resetStage(); 34 | } 35 | }); 36 | 37 | test("translate coordinates", function(_) { 38 | _.strictEqual( 39 | Crafty.domHelper.translate(10, 10).x, 40 | 0, 41 | "translates x from 10 to 0" 42 | ); 43 | _.strictEqual( 44 | Crafty.domHelper.translate(10, 10).y, 45 | 100, 46 | "translates y from 10 to 100" 47 | ); 48 | }); 49 | 50 | test("reuses obj", function(_) { 51 | var obj1 = Crafty.domHelper.translate(20, 20); 52 | var obj2 = Crafty.domHelper.translate(20, 20); 53 | _.notEqual(obj1, obj2, "returns new object"); 54 | _.deepEqual(obj1, obj2, "contents are same"); 55 | 56 | var obj = {}; 57 | var out = Crafty.domHelper.translate(30, 30, undefined, obj); 58 | _.strictEqual(out, obj, "reuses objet"); 59 | _.deepEqual(out, obj, "contents are same"); 60 | }); 61 | })(); 62 | -------------------------------------------------------------------------------- /tests/unit/graphics/dom.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var module = QUnit.module; 3 | var test = QUnit.test; 4 | 5 | module("DOM"); 6 | 7 | test("avoidCss3dTransforms", function(_) { 8 | var useCss3dTransforms = Crafty.e("2D, DOM") 9 | .attr({ 10 | x: 10, 11 | y: 10 12 | }) 13 | .draw(); 14 | 15 | _.strictEqual(useCss3dTransforms.avoidCss3dTransforms, false); 16 | if (Crafty.support.css3dtransform) { 17 | _.strictEqual( 18 | useCss3dTransforms._cssStyles.transform, 19 | "translate3d(10px,10px,0)" 20 | ); 21 | _.strictEqual(useCss3dTransforms._cssStyles.top, ""); 22 | _.strictEqual(useCss3dTransforms._cssStyles.left, ""); 23 | } else { 24 | _.strictEqual(useCss3dTransforms._cssStyles.transform, ""); 25 | _.strictEqual(useCss3dTransforms._cssStyles.top, 10); 26 | _.strictEqual(useCss3dTransforms._cssStyles.left, 10); 27 | } 28 | 29 | var avoidCss3dTransforms = Crafty.e("2D, DOM") 30 | .attr({ 31 | x: 10, 32 | y: 10, 33 | avoidCss3dTransforms: true 34 | }) 35 | .draw(); 36 | 37 | _.strictEqual(avoidCss3dTransforms.avoidCss3dTransforms, true); 38 | _.strictEqual(avoidCss3dTransforms._cssStyles.transform, ""); 39 | _.strictEqual(avoidCss3dTransforms._cssStyles.top, 10); 40 | _.strictEqual(avoidCss3dTransforms._cssStyles.left, 10); 41 | // Clean up 42 | Crafty("*").destroy(); 43 | }); 44 | 45 | test("removeComponent removes element class", function(_) { 46 | var element = Crafty.e("DOM"); 47 | var hasClassName = function(el, name) { 48 | return el._element.className.indexOf(name) >= 0; 49 | }; 50 | element.addComponent("removeMe"); 51 | _.strictEqual(element.has("removeMe"), true, "component added"); 52 | _.strictEqual(hasClassName(element, "removeMe"), true, "classname added"); 53 | 54 | element.removeComponent("removeMe"); 55 | _.strictEqual(element.has("removeMe"), false, "component removed"); 56 | _.strictEqual( 57 | hasClassName(element, "removeMe"), 58 | false, 59 | "classname removed" 60 | ); 61 | }); 62 | 63 | test("DOM component correctly invalidates", function(_) { 64 | var element = Crafty.e("DOM"); 65 | _.strictEqual(element._changed, true, "element starts dirty"); 66 | element._changed = false; 67 | element.trigger("Invalidate"); 68 | _.strictEqual(element._changed, true, "element dirty after invalidate"); 69 | }); 70 | 71 | test("removing DOM component cleans up", function(_) { 72 | var element = Crafty.e("DOM"); 73 | var node = element._element; 74 | _.strictEqual( 75 | node.parentNode, 76 | Crafty.s("DefaultDOMLayer")._div, 77 | "child of the stage" 78 | ); 79 | element._changed = false; 80 | element.removeComponent("DOM"); 81 | element.trigger("Invalidate"); 82 | _.strictEqual( 83 | element._changed, 84 | false, 85 | "no longer gets dirty after removal of 'DOM'" 86 | ); 87 | _.strictEqual( 88 | node.parentNode, 89 | null, 90 | "no parent node after removal of 'DOM'" 91 | ); 92 | }); 93 | 94 | test("Removing Color component resets DOM correctly", function(_) { 95 | var element = Crafty.e("DOM, Color"); 96 | var node = element._element; 97 | element.color("red"); 98 | 99 | // Style won't be updated until rendering occurs 100 | Crafty.timer.simulateFrames(1); 101 | _.notEqual( 102 | node.style.backgroundColor, 103 | "transparent", 104 | "Element is not initially transparent" 105 | ); 106 | 107 | element.removeComponent("Color"); 108 | _.strictEqual( 109 | node.style.backgroundColor, 110 | "transparent", 111 | "Transparent after removal of Color" 112 | ); 113 | }); 114 | })(); 115 | -------------------------------------------------------------------------------- /tests/unit/index-common.js: -------------------------------------------------------------------------------- 1 | var files = [ 2 | /** HELPER FUNCTIONS **/ 3 | "./common.js", 4 | /** COMMON TEST CODE THAT RUNS IN BROWSER AND NODE **/ 5 | "./controls/controls.js", 6 | "./core/animation.js", 7 | "./core/core.js", 8 | "./core/events.js", 9 | "./core/model.js", 10 | "./core/scenes.js", 11 | "./core/storage.js", 12 | "./core/systems.js", 13 | "./core/time.js", 14 | "./core/tween.js", 15 | "./debug/logging.js", 16 | "./spatial/2d.js", 17 | "./spatial/collision.js", 18 | "./spatial/math.js", 19 | "./spatial/motion.js", 20 | "./spatial/platform.js", 21 | "./spatial/sat.js", 22 | "./spatial/spatial-grid.js", 23 | "./spatial/raycast.js" 24 | ]; 25 | 26 | if (typeof require === "function") { 27 | files.forEach(function(file) { 28 | require(file); 29 | }); 30 | } 31 | 32 | if (typeof window !== "undefined") { 33 | window.COMMON_TEST_FILES = files; 34 | } 35 | -------------------------------------------------------------------------------- /tests/unit/index-headless.js: -------------------------------------------------------------------------------- 1 | /** CRAFTY-JS **/ 2 | global.craftyFactory = require("../../src/crafty-headless.js"); 3 | global.Crafty = global.craftyFactory(); 4 | 5 | /** TEST CODE THAT RUNS IN NODE **/ 6 | require("./core/instances.js"); 7 | 8 | /** COMMON TEST CODE THAT RUNS IN BROWSER AND NODE **/ 9 | require("./index-common.js"); 10 | -------------------------------------------------------------------------------- /tests/unit/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |