├── .eslintignore ├── .eslintrc.js ├── .github ├── FUNDING.yml └── workflows │ └── tests.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .prettierignore ├── .prettierrc.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── cert.pem ├── docs ├── api_docs │ ├── button.js │ ├── gameObject.js │ ├── grid.js │ ├── plugin.js │ ├── pool.js │ ├── quadtree.js │ ├── sprite.js │ └── tileEngine.js ├── assets │ ├── data │ │ ├── tile_engine_add.json │ │ ├── tile_engine_add.tmx │ │ ├── tile_engine_basic.json │ │ ├── tile_engine_basic.tmx │ │ ├── tile_engine_camera.json │ │ └── tile_engine_camera.tmx │ ├── imgs │ │ ├── Attribution.txt │ │ ├── blue_button02.png │ │ ├── blue_button03.png │ │ ├── character.png │ │ ├── character_walk_sheet.png │ │ ├── games │ │ │ ├── 20461-dioretsa.jpg │ │ │ ├── a-day-in-the-life.jpg │ │ │ ├── an-offline-life.jpg │ │ │ ├── audio-dash.jpg │ │ │ ├── back-down-the-tower.jpg │ │ │ ├── back-forth.jpg │ │ │ ├── back-in-dino.jpg │ │ │ ├── back-on-track.jpg │ │ │ ├── back-to-bathroom.jpg │ │ │ ├── back-to-life.jpg │ │ │ ├── back-to-that-platform-i-was-on-before-jumping.jpg │ │ │ ├── back-to-the-island.jpg │ │ │ ├── back-to-the-nest.jpg │ │ │ ├── backside.jpg │ │ │ ├── backstabber-hero.jpg │ │ │ ├── backstone.jpg │ │ │ ├── barry-the-bird.jpg │ │ │ ├── blackout.jpg │ │ │ ├── bubble-burst.png │ │ │ ├── captain-katamov.jpg │ │ │ ├── cat-goric-escape-from-the-warp-chamber.png │ │ │ ├── close-to-me.jpg │ │ │ ├── detro.jpg │ │ │ ├── dodge-that-shit.jpg │ │ │ ├── earth-that-was.jpg │ │ │ ├── eat-my-dust.jpg │ │ │ ├── flight-back-home.jpg │ │ │ ├── friendly-alien.jpg │ │ │ ├── frog-jumper.png │ │ │ ├── godai-is-back.jpg │ │ │ ├── grow-back.jpg │ │ │ ├── hang-by-a-thread.jpeg │ │ │ ├── i-forgot-my-sword.jpeg │ │ │ ├── kontra-game-samples.jpg │ │ │ ├── kurve-space.jpg │ │ │ ├── lost-pages.jpg │ │ │ ├── lost-robot.jpg │ │ │ ├── meadow.jpg │ │ │ ├── mongol-march.png │ │ │ ├── ninja-take-back.jpg │ │ │ ├── noegnud.jpg │ │ │ ├── population-404.png │ │ │ ├── send-the-asteroids-back.jpg │ │ │ ├── shoot2live.jpg │ │ │ ├── snek-farm.png │ │ │ ├── start-over.jpg │ │ │ ├── tanky-mctankface.jpg │ │ │ ├── the-waffle.jpg │ │ │ ├── they-follow.jpg │ │ │ ├── toe-tac-tic.jpg │ │ │ ├── trench-fisher.jpeg │ │ │ ├── troposphere.jpg │ │ │ └── we-must-go-back.jpg │ │ └── mapPack_tilesheet.png │ ├── js │ │ ├── exampleTabList.js │ │ ├── navbar.js │ │ ├── pool.js │ │ ├── quadtree.js │ │ └── snake.js │ └── styles.css ├── pages │ ├── download.js │ ├── getting-started.js │ ├── index.js │ ├── made-with-kontra.js │ ├── reducing-file-size.js │ └── tutorials.js ├── service-worker.js └── template │ ├── partials │ ├── example-output.hbs │ └── parameter-table.hbs │ └── template.hbs ├── examples ├── audio │ ├── Attribution.txt │ ├── Digital_Forest.mp3 │ └── Digital_Forest.ogg ├── button │ ├── button.html │ ├── buttonImage.html │ └── buttonStates.html ├── event │ └── assetLoaded.html ├── galaxian │ ├── imgs │ │ ├── bg.png │ │ ├── bullet.png │ │ ├── bullet_enemy.png │ │ ├── enemy.png │ │ └── ship.png │ ├── index.html │ ├── js │ │ ├── boot.js │ │ ├── galaxian.js │ │ ├── gameOver.js │ │ └── index.js │ └── sounds │ │ ├── explosion.mp3 │ │ ├── game_over.mp3 │ │ ├── kick_shock.mp3 │ │ └── laser.mp3 ├── gamepad │ ├── gamepad.html │ └── multiplayer.html ├── gesture │ └── gesture.html ├── grid │ └── optionMenu.html ├── imgs │ ├── Attribution.txt │ ├── blue_button02.png │ ├── bulletBlue1.png │ ├── bulletGreen1.png │ ├── bulletRed1.png │ ├── bulletSand1.png │ ├── character.png │ ├── character_walk_sheet.png │ ├── oldHero.png │ ├── tank_blue.png │ ├── tank_green.png │ ├── tank_red.png │ └── tank_sand.png ├── pointer │ ├── pointer.html │ ├── touchEvents.html │ └── tracking.html ├── pool │ └── particleEngine.html ├── prism │ └── codeOutput.js ├── scene │ ├── depthSortObjects.html │ ├── gameScene.js │ ├── globals.js │ ├── menuScene.js │ ├── simpleScenes.html │ └── snake.js ├── sprite │ ├── animationSprite.html │ ├── clampSpriteMovement.html │ ├── controllingASprite.html │ ├── customUpdateAndDraw.html │ ├── extendingASprite.html │ ├── imageSprite.html │ ├── movingASprite.html │ ├── rectSprite.html │ └── spriteCollision.html ├── spriteSheet │ └── margin.html ├── text │ ├── text.html │ ├── textAlign.html │ ├── textAutoNewline.html │ ├── textLineHeight.html │ └── textNewline.html └── tileEngine │ ├── camera │ ├── Attribution.txt │ ├── chicken_eat.png │ ├── index.html │ ├── man.png │ ├── terrain.json │ ├── terrain.png │ └── terrain.tmx │ └── margin │ ├── index.html │ ├── marginTiles.json │ ├── marginTiles.tmx │ ├── roguelikeDungeon_transparent.json │ ├── roguelikeDungeon_transparent.png │ └── roguelikeDungeon_transparent.tsx ├── gulpfile.js ├── karma.conf.js ├── key.pem ├── package-lock.json ├── package.json ├── src ├── animation.js ├── assets.js ├── button.js ├── core.js ├── events.js ├── gameLoop.js ├── gameObject.js ├── gamepad.js ├── gesture.js ├── grid.js ├── helpers.js ├── input.js ├── keyboard.js ├── kontra.defaults.js ├── kontra.js ├── plugin.js ├── pointer.js ├── pool.js ├── quadtree.js ├── random.js ├── scene.js ├── sprite.js ├── spriteSheet.js ├── text.js ├── tileEngine.js ├── updatable.js ├── utils.js └── vector.js ├── tasks ├── docs.js ├── release.sh ├── ts-template.hbs └── typescript.js └── test ├── audio ├── shoot.mp3 └── shoot.ogg ├── data ├── source.json ├── test.json ├── test.txt └── tileset │ ├── bullet.png │ ├── readme.txt │ └── tileset.json ├── imgs └── bullet.png ├── integration ├── core.spec.js ├── scene.spec.js ├── sprite.spec.js ├── tileEngine.spec.js └── vector.spec.js ├── permutations ├── index.js └── karma.conf.template.js ├── setup.js ├── typings ├── animation.ts ├── assets.ts ├── button.ts ├── core.ts ├── events.ts ├── gameLoop.ts ├── gameObject.ts ├── gamepad.ts ├── gesture.ts ├── grid.ts ├── helpers.ts ├── input.ts ├── keyboard.ts ├── pointer.ts ├── pool.ts ├── quadtree.ts ├── random.ts ├── scene.ts ├── sprite.ts ├── spriteSheet.ts ├── text.ts ├── tileEngine.ts └── vector.ts ├── unit ├── animation.spec.js ├── assets.spec.js ├── button.spec.js ├── core.spec.js ├── events.spec.js ├── gameLoop.spec.js ├── gameObject.spec.js ├── gamepad.spec.js ├── gesture.spec.js ├── grid.spec.js ├── helpers.spec.js ├── input.spec.js ├── keyboard.spec.js ├── kontra.defaults.spec.js ├── kontra.spec.js ├── plugin.spec.js ├── pointer.spec.js ├── pool.spec.js ├── quadtree.spec.js ├── random.spec.js ├── scene.spec.js ├── sprite.spec.js ├── spriteSheet.spec.js ├── text.spec.js ├── tileEngine.spec.js ├── updatable.spec.js ├── utils.spec.js └── vector.spec.js └── utils.js /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | docs/* 3 | examples/* 4 | test/typings/*.js 5 | test/permutations/* 6 | tasks/* 7 | 8 | gulpfile.js 9 | karma.conf.js 10 | kontra.* 11 | .eslintrc.js -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true 5 | }, 6 | extends: 'eslint:recommended', 7 | parserOptions: { 8 | ecmaVersion: 13, 9 | sourceType: 'module' 10 | }, 11 | rules: { 12 | // byte saving rules 13 | 'no-duplicate-imports': 2, 14 | 'object-shorthand': 2, 15 | 'prefer-arrow-callback': 2, 16 | 'no-else-return': 2, 17 | 'one-var': [ 18 | 'error', 19 | { 20 | uninitialized: 'always' 21 | } 22 | ], 23 | 'prefer-exponentiation-operator': 2, 24 | 'no-restricted-syntax': [ 25 | 'error', 26 | { 27 | selector: 'BinaryExpression[operator="==="]', 28 | message: "Prefer '==' over '===' to save bytes" 29 | }, 30 | { 31 | selector: 'BinaryExpression[operator="!=="]', 32 | message: "Prefer '!=' over '!==' to save bytes" 33 | }, 34 | { 35 | selector: 36 | 'MemberExpression[object.name=Math][property.name=floor]', 37 | message: "Prefer '| 0' over 'Math.floor' to save bytes" 38 | }, 39 | { 40 | selector: 'VariableDeclaration[kind=const]', 41 | message: "Prefer 'let' over 'const' to save bytes" 42 | }, 43 | { 44 | selector: 'MemberExpression[property.name=forEach]', 45 | message: "Prefer '.map()' over '.forEach()' to save bytes" 46 | } 47 | ], 48 | 49 | // code style rules 50 | 'spaced-comment': [ 51 | 'error', 52 | 'always', 53 | { 54 | exceptions: ['@__PURE__'] 55 | } 56 | ], 57 | 'array-bracket-spacing': 2, 58 | 'object-curly-spacing': ['error', 'always'], 59 | 'no-trailing-spaces': 2, 60 | 'multiline-comment-style': ['error', 'separate-lines'], 61 | 'max-len': [ 62 | 'error', 63 | { 64 | comments: 70, 65 | // ignore JSDoc block comments, @ifdef comments, template 66 | // literals with placeholders (ignoreStrings option doesn't 67 | // seem to work with it), and mocha describe and it functions 68 | // with template literals with placeholders 69 | ignorePattern: 70 | '^\\s*(\\*|\\/\\/ @ifdef|\\/\\* @ifdef|`|describe\\(`)|it\\(`', 71 | ignoreTrailingComments: true, 72 | ignoreUrls: true, 73 | ignoreStrings: true, 74 | ignoreRegExpLiterals: true 75 | } 76 | ] 77 | }, 78 | overrides: [ 79 | { 80 | files: ['test/**/*.js'], 81 | env: { 82 | browser: true, 83 | es2021: true, 84 | mocha: true 85 | }, 86 | globals: { 87 | expect: true, 88 | sinon: true 89 | }, 90 | plugins: ['mocha-no-only'], 91 | rules: { 92 | 'no-restricted-syntax': 0, 93 | 'mocha-no-only/mocha-no-only': 2 94 | } 95 | } 96 | ] 97 | }; 98 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: straker 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | coverage 4 | docs/api 5 | docs/**/*.html 6 | docs/assets/js/kontra.js 7 | kontra.* 8 | test/permutations/*.js 9 | test/typings/*.js 10 | test/playground.html 11 | tmp 12 | 13 | !test/permutations/index.js 14 | !test/permutations/karma.conf.template.js -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "bracketSpacing": true, 4 | "endOfLine": "lf", 5 | "htmlWhitespaceSensitivity": "css", 6 | "insertPragma": false, 7 | "jsxBracketSameLine": false, 8 | "jsxSingleQuote": false, 9 | "printWidth": 70, 10 | "proseWrap": "preserve", 11 | "quoteProps": "as-needed", 12 | "requirePragma": false, 13 | "semi": true, 14 | "singleQuote": true, 15 | "tabWidth": 2, 16 | "trailingComma": "none", 17 | "useTabs": false, 18 | "vueIndentScriptAndStyle": false, 19 | "parser": "babel" 20 | } 21 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to make participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies within all project spaces, and it also applies when 49 | an individual is representing the project or its community in public spaces. 50 | Examples of representing a project or community include using an official 51 | project e-mail address, posting via an official social media account, or acting 52 | as an appointed representative at an online or offline event. Representation of 53 | a project may be further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at steven@sklambert.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Steven Lambert 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Version](https://badge.fury.io/js/kontra.svg)](https://badge.fury.io/js/kontra) 2 | [![Build Status](https://github.com/straker/kontra/actions/workflows/tests.yml/badge.svg)](https://github.com/straker/kontra/actions) 3 | 4 | # Kontra.js 5 | 6 | A lightweight JavaScript gaming micro-library, optimized for js13kGames. 7 | 8 | ## Documentation 9 | 10 | All the documentation can be found on the [github page](https://straker.github.io/kontra/). 11 | 12 | ## Community 13 | 14 | If you'd like to chat about Kontra, check out the `#kontra` channel of either [the js13kGames Slack](https://slack.js13kgames.com/) or [the Gamedev.js Discord](https://discord.gg/URWvCwv)! 15 | 16 | ## Contributing 17 | 18 | See the [Contributing file](CONTRIBUTING.md). 19 | 20 | ## Supporting 21 | 22 | Kontra.js is made possible by users like you. Through helping find issues, opening pull requests, and [funding continuous development](https://www.patreon.com/straker), Kontra.js can continue to provide you with quality improvements and updates. 23 | 24 | When you become a Patron, you get access to behind the scenes development logs, the ability to vote on which features to work on next, and early access to development builds. 25 | 26 | ### Top Patrons 27 | 28 | - Karar Al-Remahy 29 | - UnbrandedTech 30 | -------------------------------------------------------------------------------- /cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDqDCCApCgAwIBAgIJALpeHCqCkSB/MA0GCSqGSIb3DQEBCwUAMGkxCzAJBgNV 3 | BAYTAlVTMQ0wCwYDVQQIDARVdGFoMRkwFwYDVQQHDBBTYXJhdG9nYSBTcHJpbmdz 4 | MRcwFQYDVQQKDA5TdGV2ZW4gTGFtYmVydDEXMBUGA1UEAwwOU3RldmVuIExhbWJl 5 | cnQwHhcNMjEwMTMwMjIyMjU0WhcNMzEwMTI4MjIyMjU0WjBpMQswCQYDVQQGEwJV 6 | UzENMAsGA1UECAwEVXRhaDEZMBcGA1UEBwwQU2FyYXRvZ2EgU3ByaW5nczEXMBUG 7 | A1UECgwOU3RldmVuIExhbWJlcnQxFzAVBgNVBAMMDlN0ZXZlbiBMYW1iZXJ0MIIB 8 | IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4Nvp03b7D6wIlPKVpEsXYoRm 9 | cwbBRo6GkMgtsdedq9RXtneyYym/rzIywa292f63bFBgWuBWnmJMMluh5pxHJBCA 10 | EpiEiVNFBxMMrcsKFPS3FrPcHwht1USVeCFYa8egmdNScxrpJC4zt1x6tB1vpnS+ 11 | ge0GFDDPCKnhmEtbtdioX97GVbJcV/RQNE8MkuPCvf608OxnXkGNL/vfaznyXYBC 12 | bTn7+fVCIOAhhvd+FXNKOMQ78hU9Lc/FMf4YjmDoQUjm9xUiQfujYMV/thSfzmNg 13 | xPbKLe5om2gmC9LDhBfRYu+pe5RRW3MyH3H/lnL1SdVMe4vsKmcu/t9gZMBxcQID 14 | AQABo1MwUTAdBgNVHQ4EFgQUkS3L8tQNyqPOr3++5NTkp2s464UwHwYDVR0jBBgw 15 | FoAUkS3L8tQNyqPOr3++5NTkp2s464UwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG 16 | 9w0BAQsFAAOCAQEAq6QgEimVS61WLrkiRdS5mI8JgyEsUGZmmaF7QBdunckiQSfo 17 | 4qdcF7G4iQXwJk+c9oRW1KBQ180VCj2EsRHsX/nGxuWltis8EZTxVBYDmtpZu/Zt 18 | QzMspRbpUZoqqeSPYywssXVmqyV2FpGR84lyFZuyacM2Xfhwhyn6M8X6e6JUL1wB 19 | 9JM57NnsMVUSdgTuy/OpUl4TZXzJiVRtRnhtlZdbeJdQ+9AQ999u5bsZSShDlUEk 20 | GqnFHmKFOyjJM4ytFATITng1sdAd/LrzyyMENBH1sy5Qz+u3qUMOiZkxtsRH7gTt 21 | 7hFJsHn0snkJT6fKdP8iVyxswmeGbkcrExZmNg== 22 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /docs/api_docs/gameObject.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A GameObject is just a base class and typically isn't used directly. Instead, it's main purpose is to be extended by other classes, such as [Sprite](api/sprite). 3 | * 4 | * To extend the GameObject class, import the underlying class as `import { GameObjectClass }`. 5 | * 6 | * You should also override the `draw()` function instead of the `render()` function in your class. The `draw()` function determines how to draw the GameObject. It is called by the `render` function after transforms and rotations have been applied. 7 | * 8 | * Do note that the canvas has been rotated and translated to the objects position (taking into account anchor), so {0,0} will be the top-left corner of the game object when drawing. 9 | * 10 | * @sectionName Extending A GameObject 11 | * @example 12 | * // exclude-code:start 13 | * let { GameObjectClass } = kontra; 14 | * // exclude-code:end 15 | * // exclude-script:start 16 | * import { GameObjectClass } from 'kontra'; 17 | * // exclude-script:end 18 | * 19 | * class Triangle extends GameObjectClass { 20 | * constructor(properties) { 21 | * super(properties); 22 | * } 23 | * 24 | * draw() { 25 | * this.context.fillStyle = this.color; 26 | * this.context.beginPath(); 27 | * this.context.moveTo(0, 0); 28 | * this.context.lineTo(this.width, 0); 29 | * this.context.lineTo(this.width / 2, this.height); 30 | * this.context.fill(); 31 | * } 32 | * } 33 | * 34 | * let triangle = new Triangle({ 35 | * x: 300, 36 | * y: 100, 37 | * width: 30, 38 | * height: 40, 39 | * anchor: {x: 0.5, y: 0.5}, 40 | * color: 'red' 41 | * }); 42 | * // exclude-code:start 43 | * triangle.context = context; 44 | * // exclude-code:end 45 | * triangle.render(); 46 | */ 47 | -------------------------------------------------------------------------------- /docs/api_docs/grid.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The grid automatically positions each object based on the grids [flow](api/grid#flow). This means you do not need to set the x/y position of any of the objects and can let the grid handle that for you. This makes it extremely easy to set up UI elements such as menus without having to hand place each one. 3 | * 4 | * @sectionName Basic Use 5 | * @example 6 | * // exclude-code:start 7 | * let { Text, Grid } = kontra; 8 | * // exclude-code:end 9 | * // exclude-script:start 10 | * import { Text, Grid } from 'kontra'; 11 | * // exclude-script:end 12 | * 13 | * let textOptions = { 14 | * color: 'white', 15 | * font: '20px Arial, sans-serif' 16 | * }; 17 | * 18 | * let start = Text({ 19 | * text: 'Start', 20 | * ...textOptions 21 | * }); 22 | * 23 | * let options = Text({ 24 | * text: 'Options', 25 | * ...textOptions 26 | * }); 27 | * 28 | * let quit = Text({ 29 | * text: 'Quit', 30 | * ...textOptions 31 | * }); 32 | * 33 | * let menu = Grid({ 34 | * x: 300, 35 | * y: 100, 36 | * anchor: {x: 0.5, y: 0.5}, 37 | * // exclude-code:start 38 | * context: context, 39 | * // exclude-code:end 40 | * 41 | * // add 15 pixels of space between each row 42 | * rowGap: 15, 43 | * 44 | * // center the children 45 | * justify: 'center', 46 | * 47 | * children: [start, options, quit] 48 | * }); 49 | * 50 | * menu.render(); 51 | */ 52 | 53 | /** 54 | * The grid supports properties on the child objects that change how the child is positioned within the grid. 55 | * 56 | * - `alignSelf` - *String*. Align this item individually in the grid, overriding the grids [align](api/grid#align) setting. 57 | * - `justifySelf` - *String*. Justify this item individually in the grid, overriding the grids [justify](api/grid#justify) setting. 58 | * - `colSpan` - *Number*. Have the item take up more than 1 column. Great for menu titles. 59 | * 60 | * @sectionName Child Properties 61 | */ 62 | -------------------------------------------------------------------------------- /docs/api_docs/pool.js: -------------------------------------------------------------------------------- 1 | /** 2 | * To use the pool, you must pass the `create()` function argument. The create function should return a new instance of an object or [Sprite](api/sprite). This object will be added to the pool every time there are no more alive objects. 3 | * 4 | * The object must implement the functions `update()`, `render()`, `init()`, and `isAlive()`. If one of these functions is missing the pool will throw an error. [Sprite](api/sprite) defines these functions for you. 5 | * 6 | * An object is available for reuse when its `isAlive()` function returns `false`. For a sprite, this is typically when its ttl is `0`. 7 | * 8 | * When you want an object from the pool, use the pools [get()](api/pool/#get) function and pass it any properties you want the newly initialized object to have. 9 | * 10 | * ```js 11 | * import { Pool, Sprite } from 'kontra'; 12 | * 13 | * let pool = Pool({ 14 | * // create a new sprite every time the pool needs a new object. 15 | * // equivalent to `create(props) { return Sprite(props) }` 16 | * create: Sprite 17 | * }); 18 | * 19 | * // properties will be passed to the sprites init() function 20 | * pool.get({ 21 | * x: 100, 22 | * y: 200, 23 | * width: 20, 24 | * height: 40, 25 | * color: 'red', 26 | * ttl: 60 27 | * }); 28 | * ``` 29 | * 30 | * When you want to update or render all alive objects in the pool, use the pools [update()](api/pool/#update) and [render()](api/pool/#render) functions. 31 | * 32 | * ```js 33 | * import { GameLoop } from 'kontra'; 34 | * 35 | * let loop = GameLoop({ 36 | * update: function() { 37 | * pool.update(); 38 | * }, 39 | * render: function() { 40 | * pool.render(); 41 | * } 42 | * }); 43 | * ``` 44 | * @sectionName Basic Use 45 | */ 46 | -------------------------------------------------------------------------------- /docs/api_docs/quadtree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Every frame you should remove all objects from the quadtree using its [clear()](api/quadtree/#clear) function and then add all objects back using its [add()](api/quadtree/#add) function. You can add a single object, a list of objects, or an array of objects. 3 | * 4 | * ```js 5 | * import { Quadtree, Sprite, GameLoop } from 'kontra'; 6 | * 7 | * let quadtree = Quadtree(); 8 | * let player = Sprite({ 9 | * // ... 10 | * }); 11 | * let enemy = Sprite({ 12 | * // ... 13 | * }); 14 | * 15 | * let loop = GameLoop({ 16 | * update: function() { 17 | * quadtree.clear(); 18 | * quadtree.add(player, enemy); 19 | * } 20 | * }); 21 | * ``` 22 | * 23 | * You should clear the quadtree each frame as the quadtree is only a snapshot of the position of the objects when they were added. Since the quadtree doesn't know anything about those objects, it doesn't know when an object moved or when it should be removed from the tree. 24 | * 25 | * Objects added to the tree must have the properties `x`, `y`, `width`, and `height` so that their position in the quadtree can be calculated. [Sprite](api/sprite) defines these properties for you. 26 | * 27 | * When you need to get all objects in the same node as another object, use the quadtrees [get()](api/quadtree#get) function. 28 | * 29 | * ```js 30 | * // exclude-tablist 31 | * let objects = quadtree.get(player); //=> [enemy] 32 | * ``` 33 | * @sectionName Basic Use 34 | */ 35 | -------------------------------------------------------------------------------- /docs/assets/data/tile_engine_add.json: -------------------------------------------------------------------------------- 1 | { "height":9, 2 | "infinite":false, 3 | "layers":[ 4 | { 5 | "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 204, 204, 204, 204, 0, 0, 0, 0, 0, 0, 0, 204, 204, 204, 204, 204, 204, 0, 0, 0, 0, 0, 0, 0, 204, 204, 204, 204, 204, 204, 0, 0, 0, 0, 0, 0, 0, 204, 204, 204, 204, 204, 204, 0, 0, 0, 0, 0, 0, 0, 204, 204, 204, 204, 204, 204, 0, 0, 0, 0, 0, 0, 0, 204, 204, 204, 204, 204, 204], 6 | "height":9, 7 | "id":9, 8 | "name":"water", 9 | "opacity":1, 10 | "type":"tilelayer", 11 | "visible":true, 12 | "width":13, 13 | "x":0, 14 | "y":0 15 | }, 16 | { 17 | "data":[29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 14, 46, 46, 15, 29, 29, 29, 29, 29, 29, 29, 29, 14, 47, 11, 13, 28, 29, 29, 29, 29, 29, 29, 29, 29, 30, 11, 32, 30, 28, 29, 29, 29, 29, 29, 29, 29, 29, 30, 45, 46, 47, 28, 29, 29, 29, 29, 29, 29, 29, 29, 31, 12, 12, 12, 32], 18 | "height":9, 19 | "id":10, 20 | "name":"ground", 21 | "opacity":1, 22 | "type":"tilelayer", 23 | "visible":true, 24 | "width":13, 25 | "x":0, 26 | "y":0 27 | }, 28 | { 29 | "data":[49, 49, 49, 139, 143, 177, 143, 141, 143, 143, 143, 174, 49, 49, 49, 49, 142, 49, 49, 49, 142, 49, 49, 49, 142, 49, 143, 177, 143, 194, 49, 49, 49, 193, 143, 143, 143, 191, 49, 49, 49, 49, 142, 49, 49, 49, 142, 49, 49, 49, 49, 49, 49, 49, 49, 193, 143, 177, 143, 157, 49, 49, 0, 0, 49, 49, 0, 0, 142, 0, 49, 49, 49, 0, 0, 0, 0, 49, 0, 0, 48, 176, 48, 197, 143, 175, 143, 177, 143, 180, 49, 0, 48, 48, 142, 48, 48, 0, 142, 0, 0, 0, 0, 49, 0, 48, 48, 156, 143, 177, 143, 157, 0, 0, 0, 0, 49], 30 | "height":9, 31 | "id":11, 32 | "name":"path", 33 | "opacity":1, 34 | "type":"tilelayer", 35 | "visible":true, 36 | "width":13, 37 | "x":0, 38 | "y":0 39 | }, 40 | { 41 | "data":[0, 0, 0, 147, 0, 0, 0, 148, 0, 149, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 150, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 161, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 151, 0, 0, 0, 164, 0, 0, 0, 0, 0], 42 | "height":9, 43 | "id":12, 44 | "name":"places", 45 | "opacity":1, 46 | "type":"tilelayer", 47 | "visible":true, 48 | "width":13, 49 | "x":0, 50 | "y":0 51 | }], 52 | "nextlayerid":13, 53 | "nextobjectid":1, 54 | "orientation":"orthogonal", 55 | "renderorder":"right-down", 56 | "tiledversion":"1.2.1", 57 | "tileheight":64, 58 | "tilesets":[ 59 | { 60 | "columns":17, 61 | "firstgid":1, 62 | "image":"..\/imgs\/mapPack_tilesheet.png", 63 | "imageheight":768, 64 | "imagewidth":1088, 65 | "margin":0, 66 | "name":"mapPack_tilesheet", 67 | "spacing":0, 68 | "tilecount":204, 69 | "tileheight":64, 70 | "tilewidth":64 71 | }], 72 | "tilewidth":64, 73 | "type":"map", 74 | "version":1.2, 75 | "width":13 76 | } -------------------------------------------------------------------------------- /docs/assets/data/tile_engine_add.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 0,0,0,0,0,0,0,0,0,0,0,0,0, 9 | 0,0,0,0,0,0,0,0,0,0,0,0,0, 10 | 0,0,0,0,0,0,0,0,0,0,0,0,0, 11 | 0,0,0,0,0,0,0,0,0,204,204,204,204, 12 | 0,0,0,0,0,0,0,204,204,204,204,204,204, 13 | 0,0,0,0,0,0,0,204,204,204,204,204,204, 14 | 0,0,0,0,0,0,0,204,204,204,204,204,204, 15 | 0,0,0,0,0,0,0,204,204,204,204,204,204, 16 | 0,0,0,0,0,0,0,204,204,204,204,204,204 17 | 18 | 19 | 20 | 21 | 29,29,29,29,29,29,29,29,29,29,29,29,29, 22 | 29,29,29,29,29,29,29,29,29,29,29,29,29, 23 | 29,29,29,29,29,29,29,29,29,29,29,29,29, 24 | 29,29,29,29,29,29,29,29,29,29,29,29,29, 25 | 29,29,29,29,29,29,29,29,29,14,46,46,15, 26 | 29,29,29,29,29,29,29,29,14,47,11,13,28, 27 | 29,29,29,29,29,29,29,29,30,11,32,30,28, 28 | 29,29,29,29,29,29,29,29,30,45,46,47,28, 29 | 29,29,29,29,29,29,29,29,31,12,12,12,32 30 | 31 | 32 | 33 | 34 | 49,49,49,139,143,177,143,141,143,143,143,174,49, 35 | 49,49,49,142,49,49,49,142,49,49,49,142,49, 36 | 143,177,143,194,49,49,49,193,143,143,143,191,49, 37 | 49,49,49,142,49,49,49,142,49,49,49,49,49, 38 | 49,49,49,193,143,177,143,157,49,49,0,0,49, 39 | 49,0,0,142,0,49,49,49,0,0,0,0,49, 40 | 0,0,48,176,48,197,143,175,143,177,143,180,49, 41 | 0,48,48,142,48,48,0,142,0,0,0,0,49, 42 | 0,48,48,156,143,177,143,157,0,0,0,0,49 43 | 44 | 45 | 46 | 47 | 0,0,0,147,0,0,0,148,0,149,0,0,0, 48 | 0,0,0,0,0,0,0,0,0,0,0,0,0, 49 | 0,0,0,0,0,0,0,0,0,150,0,0,0, 50 | 0,0,0,0,0,0,0,0,0,0,0,0,0, 51 | 0,0,0,0,0,0,0,161,0,0,0,0,0, 52 | 0,0,0,0,0,0,0,0,0,0,0,0,0, 53 | 0,0,0,0,0,0,0,0,0,0,0,0,0, 54 | 0,0,0,0,0,0,0,0,0,0,0,0,0, 55 | 0,0,0,151,0,0,0,164,0,0,0,0,0 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /docs/assets/data/tile_engine_basic.json: -------------------------------------------------------------------------------- 1 | { "height":9, 2 | "layers":[ 3 | { 4 | "data":[203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203], 5 | "height":9, 6 | "name":"base", 7 | "opacity":1, 8 | "type":"tilelayer", 9 | "visible":true, 10 | "width":9, 11 | "x":0, 12 | "y":0 13 | }, 14 | { 15 | "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 7, 7, 8, 0, 0, 0, 0, 6, 27, 24, 24, 25, 0, 0, 0, 0, 23, 24, 24, 24, 26, 8, 0, 0, 0, 23, 24, 24, 24, 24, 26, 8, 0, 0, 23, 24, 24, 24, 24, 24, 25, 0, 0, 40, 41, 41, 10, 24, 24, 25, 0, 0, 0, 0, 0, 40, 41, 41, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 16 | "height":9, 17 | "name":"grass", 18 | "opacity":1, 19 | "type":"tilelayer", 20 | "visible":true, 21 | "width":9, 22 | "x":0, 23 | "y":0 24 | }, 25 | { 26 | "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 0, 0, 0, 0, 0, 0, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 61, 0, 0, 0, 0, 0, 44, 0, 0, 0, 0, 61, 0, 0, 0, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 27 | "height":9, 28 | "name":"decoration", 29 | "opacity":1, 30 | "type":"tilelayer", 31 | "visible":true, 32 | "width":9, 33 | "x":0, 34 | "y":0 35 | }], 36 | "orientation":"orthogonal", 37 | "properties": 38 | { 39 | 40 | }, 41 | "tileheight":64, 42 | "tilesets":[ 43 | { 44 | "firstgid":1, 45 | "image":"..\/imgs\/mapPack_tilesheet.png", 46 | "imageheight":768, 47 | "imagewidth":1088, 48 | "margin":0, 49 | "name":"mapPack_tilesheet", 50 | "properties": 51 | { 52 | 53 | }, 54 | "spacing":0, 55 | "tileheight":64, 56 | "tilewidth":64 57 | }], 58 | "tilewidth":64, 59 | "version":1, 60 | "width":9 61 | } -------------------------------------------------------------------------------- /docs/assets/data/tile_engine_basic.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAA 9 | 10 | 11 | 12 | 13 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAABwAAAAcAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAbAAAAGAAAABgAAAAZAAAAAAAAAAAAAAAAAAAAAAAAABcAAAAYAAAAGAAAABgAAAAaAAAACAAAAAAAAAAAAAAAAAAAABcAAAAYAAAAGAAAABgAAAAYAAAAGgAAAAgAAAAAAAAAAAAAABcAAAAYAAAAGAAAABgAAAAYAAAAGAAAABkAAAAAAAAAAAAAACgAAAApAAAAKQAAAAoAAAAYAAAAGAAAABkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAAAApAAAAKQAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 14 | 15 | 16 | 17 | 18 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAArAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAAAAAAAAAAAAAAAAAAAAAAAD0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /docs/assets/data/tile_engine_camera.json: -------------------------------------------------------------------------------- 1 | { "height":10, 2 | "layers":[ 3 | { 4 | "data":[203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203], 5 | "height":10, 6 | "name":"base", 7 | "opacity":1, 8 | "type":"tilelayer", 9 | "visible":true, 10 | "width":14, 11 | "x":0, 12 | "y":0 13 | }, 14 | { 15 | "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 7, 7, 7, 7, 8, 0, 0, 0, 0, 0, 0, 0, 6, 27, 24, 24, 24, 24, 26, 8, 0, 0, 0, 0, 0, 6, 27, 24, 24, 24, 24, 24, 24, 26, 7, 8, 0, 0, 0, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 25, 0, 0, 0, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 26, 8, 0, 0, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 25, 0, 0, 40, 41, 41, 41, 41, 10, 24, 24, 24, 24, 24, 25, 0, 0, 0, 0, 0, 0, 0, 40, 41, 41, 41, 41, 41, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 16 | "height":10, 17 | "name":"grass", 18 | "opacity":1, 19 | "type":"tilelayer", 20 | "visible":true, 21 | "width":14, 22 | "x":0, 23 | "y":0 24 | }, 25 | { 26 | "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 69, 70, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 86, 87, 89, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 103, 104, 73, 89, 70, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 86, 87, 87, 88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 103, 104, 104, 105, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 27 | "height":10, 28 | "name":"ice", 29 | "opacity":1, 30 | "type":"tilelayer", 31 | "visible":true, 32 | "width":14, 33 | "x":0, 34 | "y":0 35 | }, 36 | { 37 | "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 61, 0, 124, 107, 0, 106, 0, 0, 0, 0, 0, 0, 0, 0, 112, 0, 0, 0, 0, 0, 0, 124, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 0, 124, 0, 0, 0, 0, 0, 0, 0, 61, 0, 0, 0, 0, 0, 0, 0, 0, 0, 61, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 38 | "height":10, 39 | "name":"decoration", 40 | "opacity":1, 41 | "type":"tilelayer", 42 | "visible":true, 43 | "width":14, 44 | "x":0, 45 | "y":0 46 | }], 47 | "orientation":"orthogonal", 48 | "properties": 49 | { 50 | "sx":"0", 51 | "sy":"32" 52 | }, 53 | "tileheight":64, 54 | "tilesets":[ 55 | { 56 | "firstgid":1, 57 | "image":"..\/imgs\/mapPack_tilesheet.png", 58 | "imageheight":768, 59 | "imagewidth":1088, 60 | "margin":0, 61 | "name":"mapPack_tilesheet", 62 | "properties": 63 | { 64 | 65 | }, 66 | "spacing":0, 67 | "tileheight":64, 68 | "tilewidth":64 69 | }], 70 | "tilewidth":64, 71 | "version":1, 72 | "width":14 73 | } -------------------------------------------------------------------------------- /docs/assets/data/tile_engine_camera.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | ywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAADLAAAAywAAAMsAAAA= 13 | 14 | 15 | 16 | 17 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAABwAAAAcAAAAHAAAABwAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAABsAAAAYAAAAGAAAABgAAAAYAAAAGgAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAbAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGgAAAAcAAAAIAAAAAAAAAAAAAAAAAAAAFwAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABkAAAAAAAAAAAAAAAAAAAAXAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGgAAAAgAAAAAAAAAAAAAABcAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGQAAAAAAAAAAAAAAKAAAACkAAAApAAAAKQAAACkAAAAKAAAAGAAAABgAAAAYAAAAGAAAABgAAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAAAApAAAAKQAAACkAAAApAAAAKQAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= 18 | 19 | 20 | 21 | 22 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQAAAEYAAABHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABWAAAAVwAAAFkAAABHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGcAAABoAAAASQAAAFkAAABGAAAARwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABWAAAAVwAAAFcAAABYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGcAAABoAAAAaAAAAGkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= 23 | 24 | 25 | 26 | 27 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPQAAAAAAAAB8AAAAawAAAAAAAABqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /docs/assets/imgs/Attribution.txt: -------------------------------------------------------------------------------- 1 | License 2 | ------- 3 | 4 | CC0 1.0 Universal 5 | - http://creativecommons.org/publicdomain/zero/1.0/ 6 | 7 | Assets from: 8 | http://opengameart.org/content/platformer-tiles 9 | https://www.kenney.nl/assets/ui-pack 10 | 11 | Kenney.nl 12 | OpenGameArt Kenney 13 | Homepage http://www.kenney.nl/ -------------------------------------------------------------------------------- /docs/assets/imgs/blue_button02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/blue_button02.png -------------------------------------------------------------------------------- /docs/assets/imgs/blue_button03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/blue_button03.png -------------------------------------------------------------------------------- /docs/assets/imgs/character.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/character.png -------------------------------------------------------------------------------- /docs/assets/imgs/character_walk_sheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/character_walk_sheet.png -------------------------------------------------------------------------------- /docs/assets/imgs/games/20461-dioretsa.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/20461-dioretsa.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/a-day-in-the-life.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/a-day-in-the-life.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/an-offline-life.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/an-offline-life.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/audio-dash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/audio-dash.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/back-down-the-tower.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/back-down-the-tower.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/back-forth.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/back-forth.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/back-in-dino.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/back-in-dino.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/back-on-track.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/back-on-track.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/back-to-bathroom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/back-to-bathroom.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/back-to-life.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/back-to-life.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/back-to-that-platform-i-was-on-before-jumping.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/back-to-that-platform-i-was-on-before-jumping.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/back-to-the-island.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/back-to-the-island.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/back-to-the-nest.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/back-to-the-nest.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/backside.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/backside.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/backstabber-hero.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/backstabber-hero.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/backstone.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/backstone.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/barry-the-bird.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/barry-the-bird.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/blackout.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/blackout.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/bubble-burst.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/bubble-burst.png -------------------------------------------------------------------------------- /docs/assets/imgs/games/captain-katamov.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/captain-katamov.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/cat-goric-escape-from-the-warp-chamber.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/cat-goric-escape-from-the-warp-chamber.png -------------------------------------------------------------------------------- /docs/assets/imgs/games/close-to-me.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/close-to-me.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/detro.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/detro.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/dodge-that-shit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/dodge-that-shit.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/earth-that-was.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/earth-that-was.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/eat-my-dust.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/eat-my-dust.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/flight-back-home.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/flight-back-home.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/friendly-alien.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/friendly-alien.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/frog-jumper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/frog-jumper.png -------------------------------------------------------------------------------- /docs/assets/imgs/games/godai-is-back.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/godai-is-back.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/grow-back.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/grow-back.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/hang-by-a-thread.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/hang-by-a-thread.jpeg -------------------------------------------------------------------------------- /docs/assets/imgs/games/i-forgot-my-sword.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/i-forgot-my-sword.jpeg -------------------------------------------------------------------------------- /docs/assets/imgs/games/kontra-game-samples.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/kontra-game-samples.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/kurve-space.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/kurve-space.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/lost-pages.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/lost-pages.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/lost-robot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/lost-robot.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/meadow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/meadow.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/mongol-march.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/mongol-march.png -------------------------------------------------------------------------------- /docs/assets/imgs/games/ninja-take-back.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/ninja-take-back.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/noegnud.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/noegnud.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/population-404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/population-404.png -------------------------------------------------------------------------------- /docs/assets/imgs/games/send-the-asteroids-back.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/send-the-asteroids-back.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/shoot2live.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/shoot2live.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/snek-farm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/snek-farm.png -------------------------------------------------------------------------------- /docs/assets/imgs/games/start-over.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/start-over.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/tanky-mctankface.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/tanky-mctankface.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/the-waffle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/the-waffle.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/they-follow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/they-follow.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/toe-tac-tic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/toe-tac-tic.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/trench-fisher.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/trench-fisher.jpeg -------------------------------------------------------------------------------- /docs/assets/imgs/games/troposphere.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/troposphere.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/games/we-must-go-back.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/games/we-must-go-back.jpg -------------------------------------------------------------------------------- /docs/assets/imgs/mapPack_tilesheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/docs/assets/imgs/mapPack_tilesheet.png -------------------------------------------------------------------------------- /docs/assets/js/exampleTabList.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | let type = localStorage.getItem('kontra-example-type') || 'global'; 3 | document.body.setAttribute('data-examples', type); 4 | 5 | Array.from(document.querySelectorAll(`[role=tab]`)).forEach(tab => { 6 | tab.setAttribute('tabindex', -1); 7 | }); 8 | 9 | Array.from(document.querySelectorAll(`[data-tab=${type}] [role=tab]`)).forEach(tab => { 10 | tab.setAttribute('aria-selected', true); 11 | tab.setAttribute('tabindex', 0); 12 | }); 13 | 14 | Array.from(document.querySelectorAll('[role=tablist]')).forEach(tablist => { 15 | let tabs = []; 16 | 17 | function switchTab(index) { 18 | let tab = tabs[index]; 19 | let type = tab.parentElement.dataset.tab; 20 | localStorage.setItem('kontra-example-type', type); 21 | document.body.setAttribute('data-examples', type); 22 | 23 | let priorTab = tablist.querySelector('[aria-selected]'); 24 | priorTab.removeAttribute('aria-selected'); 25 | priorTab.setAttribute('tabindex', -1); 26 | 27 | tab.setAttribute('aria-selected', true); 28 | tab.focus(); 29 | tab.setAttribute('tabindex', 0); 30 | } 31 | 32 | Array.from(tablist.querySelectorAll('[role=tab]')).forEach((tab, index) => { 33 | tabs.push(tab); 34 | 35 | tab.addEventListener('click', e => { 36 | switchTab(index); 37 | }); 38 | 39 | tab.addEventListener('keydown', e => { 40 | let tabIndex; 41 | if (e.which === 37) { 42 | tabIndex = index - 1 <= 0 ? 0 : index - 1; 43 | switchTab(tabIndex); 44 | } else if (e.which === 39) { 45 | tabIndex = index + 1 >= tabs.length - 1 ? tabs.length - 1 : index + 1; 46 | switchTab(tabIndex); 47 | } 48 | }); 49 | }); 50 | }); 51 | })(); 52 | -------------------------------------------------------------------------------- /docs/assets/js/navbar.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | let nav = document.querySelector('.main-nav'); 3 | nav.addEventListener( 4 | 'scroll', 5 | function (e) { 6 | if (this.offsetHeight + this.scrollTop >= this.scrollHeight) { 7 | this.classList.remove('showScroll'); 8 | } else { 9 | this.classList.add('showScroll'); 10 | } 11 | }.bind(nav) 12 | ); 13 | 14 | let menuBtn = document.querySelector('.menu-button'); 15 | let menu = document.querySelector('#menu'); 16 | menuBtn.addEventListener('click', function () { 17 | let expanded = !(this.getAttribute('aria-expanded') === 'true'); 18 | this.setAttribute('aria-expanded', expanded); 19 | document.body.classList.toggle('menu-expanded'); 20 | if (expanded) { 21 | menu.style.height = menu.scrollHeight + 'px'; 22 | } else { 23 | menu.style.height = null; 24 | } 25 | }); 26 | 27 | // save scroll position between page loads 28 | window.addEventListener('unload', function (event) { 29 | sessionStorage.setItem('kontra-nav-scroll', nav.scrollTop); 30 | }); 31 | })(); 32 | -------------------------------------------------------------------------------- /docs/assets/js/pool.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | let { canvas, context } = kontra.init('pool-example'); 3 | 4 | let midX = canvas.width / 2; 5 | let midY = canvas.height / 2; 6 | 7 | let fields = [ 8 | { 9 | x: midX - 75, 10 | y: midY + 20, 11 | mass: 900 12 | }, 13 | { 14 | x: midX + 25, 15 | y: midY + 10, 16 | mass: -50 17 | } 18 | ]; 19 | 20 | let pool = kontra.Pool({ 21 | create: kontra.Sprite, 22 | maxSize: 1500, 23 | fill: true 24 | }); 25 | 26 | context.font = '18px Arial'; 27 | 28 | // redefine sprite update to account for fields 29 | function update() { 30 | // apply field 31 | let totalAccelerationX = 0; 32 | let totalAccelerationY = 0; 33 | 34 | for (let i = 0, field; (field = fields[i]); i++) { 35 | let vectorX = field.x - this.x; 36 | let vectorY = field.y - this.y; 37 | 38 | let force = field.mass / Math.pow(vectorX * vectorX + vectorY * vectorY, 1.5); 39 | 40 | totalAccelerationX += vectorX * force; 41 | totalAccelerationY += vectorY * force; 42 | } 43 | 44 | this.ddx = totalAccelerationX; 45 | this.ddy = totalAccelerationY; 46 | 47 | this.advance(); 48 | } 49 | 50 | let loop = kontra.GameLoop({ 51 | update: function () { 52 | for (let i = 0; i < 4; i++) { 53 | pool.get({ 54 | x: midX + 75, 55 | y: midY, 56 | dx: 2 - Math.random() * 4, 57 | dy: 2 - Math.random() * 4, 58 | color: 'red', 59 | width: 2, 60 | height: 2, 61 | ttl: 300, 62 | update: update 63 | }); 64 | } 65 | 66 | pool.update(); 67 | }, 68 | render: function () { 69 | pool.render(); 70 | 71 | context.fillStyle = 'white'; 72 | context.fillText(`Object count: ${pool.size}`, 15, 25); 73 | } 74 | }); 75 | 76 | loop.start(); 77 | })(); 78 | -------------------------------------------------------------------------------- /docs/assets/js/quadtree.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | let { canvas, context } = kontra.init('quadtree-example'); 3 | 4 | let sprites = []; 5 | for (let i = 0; i < 20; i++) { 6 | sprites.push( 7 | kontra.Sprite({ 8 | width: 10, 9 | height: 10, 10 | x: Math.random() * (canvas.width - 10), 11 | y: Math.random() * (canvas.height - 10), 12 | dx: Math.random() * 4 - 2, 13 | dy: Math.random() * 4 - 2, 14 | color: 'red' 15 | }) 16 | ); 17 | } 18 | 19 | function renderQuadtree(node) { 20 | context.strokeStyle = '#eee'; 21 | context.strokeRect(node.bounds.x, node.bounds.y, node.bounds.width, node.bounds.height); 22 | 23 | // render the subnodes so long as this node is a branch and has subnodes 24 | if (node._b && node._s.length) { 25 | for (let i = 0; i < 4; i++) { 26 | renderQuadtree(node._s[i]); 27 | } 28 | } 29 | } 30 | 31 | let quadtree = kontra.Quadtree({ 32 | maxObjects: 5 33 | }); 34 | 35 | let loop = kontra.GameLoop({ 36 | update() { 37 | sprites.forEach(sprite => { 38 | sprite.update(); 39 | 40 | if (sprite.x < 0) { 41 | sprite.dx *= -1; 42 | sprite.x = 0; 43 | } else if (sprite.x + sprite.width >= canvas.width) { 44 | sprite.dx *= -1; 45 | sprite.x = canvas.width - sprite.width; 46 | } 47 | 48 | if (sprite.y < 0) { 49 | sprite.dy *= -1; 50 | sprite.y = 0; 51 | } else if (sprite.y + sprite.height >= canvas.height) { 52 | sprite.dy *= -1; 53 | sprite.y = canvas.height - sprite.height; 54 | } 55 | }); 56 | 57 | quadtree.clear(); 58 | quadtree.add(sprites); 59 | }, 60 | render() { 61 | renderQuadtree(quadtree); 62 | sprites.forEach(sprite => sprite.render()); 63 | } 64 | }); 65 | 66 | loop.start(); 67 | })(); 68 | -------------------------------------------------------------------------------- /docs/pages/download.js: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | Download the latest version of Kontra. 4 | 5 | ## Source Code 6 | 7 | - [Global object production version](https://unpkg.com/kontra@latest/kontra.min.js) 8 | - [Global object development version](https://unpkg.com/kontra@latest/kontra.js) 9 | - [ES Module production version](https://unpkg.com/kontra@latest/kontra.min.mjs) 10 | - [ES Module development version](https://unpkg.com/kontra@latest/kontra.mjs) 11 | - [TGZ file](https://registry.npmjs.org/kontra/-/kontra-__packageVersion__.tgz) 12 | - [Github repo](https://github.com/straker/kontra) 13 | 14 | ## Package managers 15 | 16 | - `npm install kontra` 17 | - `yarn add kontra` 18 | 19 | ## CDN 20 | 21 | ### Global Object 22 | 23 | ```html 24 | 25 | ``` 26 | ```html 27 | 28 | ``` 29 | 30 | ### ES Module Import 31 | 32 | ```js 33 | // exclude-tablist 34 | import kontra from 'https://cdn.jsdelivr.net/npm/kontra@__packageVersion__/kontra.min.mjs'; 35 | ``` 36 | ```js 37 | // exclude-tablist 38 | import kontra from 'https://cdn.jsdelivr.net/npm/kontra@__packageVersion__/kontra.mjs'; 39 | ``` 40 | 41 | @section Download 42 | @page download 43 | @packageVersion 44 | 45 | */ 46 | -------------------------------------------------------------------------------- /docs/pages/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | Kontra is a lightweight JavaScript gaming micro-library created specifically for the [Js13kGames](http://js13kgames.com/) game jam. 4 | 5 | 6 | 7 | 8 | The goal of Kontra is not to implement everything you could possibly need to make a game. There are already libraries out there that do that (like [Phaser](http://phaser.io/)). 9 | 10 | Instead, Kontra aims to implement basic game requirements like asset loading, input, the game loop, and sprites to keep the library very small and focused. This allows it to be used when your game size is limited (a la Js13k). 11 | 12 | Kontra does provide some more advanced data structures like object pools and quadtrees that have been fine tuned to be small, fast, and memory efficient. 13 | 14 | Kontra prides itself in being: 15 | 16 | - **Lightweight**: tries to take up as little room as possible and strives to reduce the file size every major release. 17 | - **Modular**: pick and choose what you want through ES modules. 18 | - **Extensible**: everything is customizable and can be extended. 19 | - **Fast**: all logic has been removed from the update and render cycles. 20 | - **Memory conscious**: takes up as little memory as needed and tries not to be wasteful about the memory it does take up. 21 | 22 | ## Use it When 23 | 24 | - You want to get something up and running fairly quickly. 25 | - You want a basic structure that is easy to scale and extend. 26 | - In conjunction with other libraries (like [Playground.js](https://github.com/rezoner/playground)). 27 | - Prototyping. 28 | - Game jams. 29 | 30 | ## Ready to Get Going? 31 | 32 | - Read the [getting started guide](getting-started) and some [tutorials](tutorials). 33 | - Look at [games made with Kontra](made-with-kontra). 34 | - Gain in-depth knowledge from the API docs. 35 | 36 | ## Support Future Development 37 | 38 | Kontra.js is made possible by users like you. Through helping find issues, opening pull requests, and [funding continuous development](https://www.patreon.com/straker), Kontra.js can continue to provide you with quality improvements and updates. 39 | 40 | When you become a Patron, you get access to behind the scenes development logs, the ability to vote on which features to work on next, and early access to development builds. 41 | 42 | ### Top Patrons 43 | 44 | - Karar Al-Remahy 45 | - UnbrandedTech 46 | - Innkeeper Games 47 | 48 | @section What is Kontra.js? 49 | @page index 50 | 51 | */ 52 | -------------------------------------------------------------------------------- /docs/pages/tutorials.js: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | A list of articles and tutorials for making games with Kontra.js. I've collected as many as I could all in one place for your reading pleasure. 4 | 5 | Have a tutorial you want to add? [Let me know](https://github.com/straker/kontra/issues)! 6 | 7 | - [Js13kGames Video Tutorial Series](https://gamedevacademy.org/js13kgames-tutorial-video-series/) – Zenva 8 | - [Making Asteroids with Kontra.js and Web Maker](https://medium.com/web-maker/making-asteroids-with-kontra-js-and-web-maker-95559d39b45f) – Steven Lambert 9 | - [How to Write a Game in Under 13 Kb While Taking Care of a Baby](https://www.barbarianmeetscoding.com/blog/2018/09/19/how-to-write-a-game-under-13k-while-taking-care-of-a-baby/) – Jaime González García 10 | 11 | @section Tutorials 12 | @page tutorials 13 | 14 | */ 15 | -------------------------------------------------------------------------------- /docs/service-worker.js: -------------------------------------------------------------------------------- 1 | let prefix = '/kontra'; 2 | 3 | // adjust path based on location (github pages required kontra url) 4 | if (['localhost', '127.0.0.1'].includes(self.location.hostname)) { 5 | prefix = ''; 6 | } 7 | 8 | const filesToCache = [ 9 | // pages 10 | `${prefix}/`, 11 | 12 | // js 13 | `${prefix}/assets/js/kontra.js`, 14 | `${prefix}/assets/js/navbar.js`, 15 | `${prefix}/assets/js/exampleTabList.js`, 16 | 17 | // styles 18 | `${prefix}/assets/styles.css` 19 | ]; 20 | 21 | const staticCacheName = 'kontra-docs-v10.0.0'; 22 | 23 | // cache the application shell 24 | self.addEventListener('install', event => { 25 | event.waitUntil( 26 | caches.open(staticCacheName).then(cache => { 27 | return cache.addAll(filesToCache); 28 | }) 29 | ); 30 | }); 31 | 32 | // serve files from the cache 33 | self.addEventListener('fetch', event => { 34 | // respond with the cache first 35 | event.respondWith( 36 | caches 37 | .match(event.request) 38 | .then(response => { 39 | if (response) { 40 | return response; 41 | } 42 | 43 | // progressively add new files to the cache 44 | return updateCache(event.request); 45 | }) 46 | .catch(error => { 47 | // TODO 6 - Respond with custom offline page 48 | }) 49 | ); 50 | 51 | // update the cache with newest version 52 | event.waitUntil(updateCache(event.request)); 53 | }); 54 | 55 | function updateCache(request) { 56 | return fetch(request).then(response => { 57 | return caches.open(staticCacheName).then(cache => { 58 | cache.put(request.url, response.clone()); 59 | return response; 60 | }); 61 | }); 62 | } 63 | 64 | // delete outdated caches 65 | self.addEventListener('activate', event => { 66 | const cacheAllowlist = [staticCacheName]; 67 | 68 | event.waitUntil( 69 | caches.keys().then(cacheNames => { 70 | return Promise.all( 71 | cacheNames.map(cacheName => { 72 | if (cacheAllowlist.indexOf(cacheName) === -1) { 73 | return caches.delete(cacheName); 74 | } 75 | }) 76 | ); 77 | }) 78 | ); 79 | }); 80 | -------------------------------------------------------------------------------- /docs/template/partials/example-output.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 16 |
17 |
{{ example.globalOutput }}
18 |
19 |
20 |
{{ example.esOutput }}
21 |
22 |
23 |
{{ example.bundleOutput }}
24 |
25 |
26 | 27 | -------------------------------------------------------------------------------- /docs/template/partials/parameter-table.hbs: -------------------------------------------------------------------------------- 1 | {{#if param}} 2 |

{{ name }} Parameters

3 |
4 | {{#each param}} 5 |
6 | {{ name }} 7 | {{#if optional}}Optional{{/if}} 8 |
9 |
{{{ description }}}
10 | {{/each}} 11 |
12 | {{/if}} 13 | 14 | {{#if returns}} 15 |

{{ name }} Return value

16 |

{{{ returns.description }}}

17 | {{/if}} -------------------------------------------------------------------------------- /examples/audio/Attribution.txt: -------------------------------------------------------------------------------- 1 | License 2 | ------- 3 | 4 | CC BY 3.0 5 | - https://creativecommons.org/licenses/by/3.0/ 6 | 7 | Assets from: 8 | https://opengameart.org/content/digital-forest 9 | 10 | Pyerye 11 | OpenGameArt Pyerye 12 | Homepage https://opengameart.org/users/alundra -------------------------------------------------------------------------------- /examples/audio/Digital_Forest.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/examples/audio/Digital_Forest.mp3 -------------------------------------------------------------------------------- /examples/audio/Digital_Forest.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/examples/audio/Digital_Forest.ogg -------------------------------------------------------------------------------- /examples/button/button.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Button 5 | 6 | 7 | 8 | 9 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /examples/button/buttonImage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Button Image 5 | 6 | 7 | 8 | 9 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /examples/button/buttonStates.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Button States 5 | 6 | 7 | 8 | 9 | 10 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /examples/galaxian/imgs/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/examples/galaxian/imgs/bg.png -------------------------------------------------------------------------------- /examples/galaxian/imgs/bullet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/examples/galaxian/imgs/bullet.png -------------------------------------------------------------------------------- /examples/galaxian/imgs/bullet_enemy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/examples/galaxian/imgs/bullet_enemy.png -------------------------------------------------------------------------------- /examples/galaxian/imgs/enemy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/examples/galaxian/imgs/enemy.png -------------------------------------------------------------------------------- /examples/galaxian/imgs/ship.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/examples/galaxian/imgs/ship.png -------------------------------------------------------------------------------- /examples/galaxian/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Kontra Test 5 | 78 | 79 | 80 | 81 | 82 | 83 |

M - Mute

84 |

P - Pause

85 | 86 | 90 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /examples/galaxian/js/boot.js: -------------------------------------------------------------------------------- 1 | let assets = [ 2 | // images 3 | 'ship.png', 4 | 'bg.png', 5 | 'bullet_enemy.png', 6 | 'bullet.png', 7 | 'enemy.png', 8 | 9 | // audios 10 | 'explosion.mp3', 11 | 'game_over.mp3', 12 | 'kick_shock.mp3', 13 | 'laser.mp3' 14 | ]; 15 | 16 | let canvas = kontra.getCanvas(); 17 | let context = kontra.getContext(); 18 | 19 | let loadingText = kontra.Text({ 20 | color: '#FF7F00', 21 | text: 'Loading...', 22 | font: '18px Helvetica, sans-serif', 23 | anchor: { x: 0.5, y: 0.5 } 24 | }); 25 | 26 | // create a progress bar 27 | let loadingBar = kontra.Sprite({ 28 | width: canvas.width / 2, 29 | height: 30, 30 | progress: 0, 31 | anchor: { x: 0.5, y: 0.5 }, 32 | render() { 33 | context.strokeStyle = 'white'; 34 | context.strokeRect(0, 0, this.width, this.height); 35 | 36 | context.fillStyle = 'green'; 37 | context.fillRect(1, 1, this.width * (this.progress / assets.length) - 2, this.height - 2); 38 | } 39 | }); 40 | 41 | let playButton = kontra.Button({ 42 | padX: 15, 43 | padY: 15, 44 | anchor: { x: 0.5, y: 0.5 }, 45 | 46 | // align center to the grid 47 | justifySelf: 'center', 48 | 49 | text: { 50 | text: 'Play', 51 | color: '#FF7F00', 52 | font: '28px Helvetica, sans-serif', 53 | anchor: { x: 0.5, y: 0.5 } 54 | }, 55 | 56 | render() { 57 | if (this.disabled) { 58 | this.textNode.color = '#999'; 59 | return; 60 | } 61 | 62 | context.strokeStyle = 'white'; 63 | context.strokeRect(1, 1, this.width - 2, this.height - 2); 64 | 65 | if (this.pressed) { 66 | this.textNode.color = 'white'; 67 | } else if (this.focused || this.hovered) { 68 | this.textNode.color = '#62a2f9'; 69 | canvas.style.cursor = 'pointer'; 70 | } else { 71 | this.textNode.color = '#FF7F00'; 72 | canvas.style.cursor = 'initial'; 73 | } 74 | }, 75 | 76 | onUp() { 77 | kontra.emit('startGame'); 78 | } 79 | }); 80 | playButton.disable(); 81 | 82 | // let the grid manager handle auto placing the controls 83 | let grid = kontra.Grid({ 84 | x: canvas.width / 2, 85 | y: canvas.height / 2, 86 | 87 | // put extra space between the button and progress bar 88 | gapY: [0, 40], 89 | anchor: { x: 0.5, y: 0.5 }, 90 | children: [loadingText, loadingBar, playButton] 91 | }); 92 | 93 | let bootScene = kontra.Scene({ 94 | id: 'boot', 95 | objects: [grid] 96 | }); 97 | 98 | // set default asset paths 99 | kontra.setImagePath('imgs/'); 100 | kontra.setAudioPath('sounds/'); 101 | 102 | // asset progress 103 | kontra.on('assetLoaded', (asset, url) => { 104 | loadingBar.progress++; 105 | }); 106 | 107 | // load assets 108 | kontra.load(...assets).then(() => { 109 | kontra.emit('doneLoading'); 110 | playButton.enable(); 111 | playButton.focus(); 112 | }); 113 | 114 | export default bootScene; 115 | -------------------------------------------------------------------------------- /examples/galaxian/js/gameOver.js: -------------------------------------------------------------------------------- 1 | let canvas = kontra.getCanvas(); 2 | let context = kontra.getContext(); 3 | 4 | let gameOverText = kontra.Text({ 5 | color: '#FF7F00', 6 | text: 'Game Over', 7 | font: '28px Helvetica, sans-serif', 8 | anchor: { x: 0.5, y: 0.5 } 9 | }); 10 | 11 | let restartButton = kontra.Button({ 12 | padX: 15, 13 | padY: 15, 14 | anchor: { x: 0.5, y: 0.5 }, 15 | 16 | // align center to the grid 17 | justifySelf: 'center', 18 | 19 | text: { 20 | text: 'Restart', 21 | color: '#FF7F00', 22 | font: '18px Helvetica, sans-serif', 23 | anchor: { x: 0.5, y: 0.5 } 24 | }, 25 | 26 | render() { 27 | if (this.disabled) { 28 | this.textNode.color = '#999'; 29 | return; 30 | } 31 | 32 | context.strokeStyle = 'white'; 33 | context.strokeRect(1, 1, this.width - 2, this.height - 2); 34 | 35 | if (this.pressed) { 36 | this.textNode.color = 'white'; 37 | } else if (this.focused || this.hovered) { 38 | this.textNode.color = '#62a2f9'; 39 | canvas.style.cursor = 'pointer'; 40 | } else { 41 | this.textNode.color = '#FF7F00'; 42 | canvas.style.cursor = 'initial'; 43 | } 44 | }, 45 | 46 | onUp() { 47 | kontra.emit('startGame'); 48 | } 49 | }); 50 | 51 | // let the grid manager handle auto placing the controls 52 | let grid = kontra.Grid({ 53 | x: canvas.width / 2, 54 | y: canvas.height / 2, 55 | 56 | // put extra space between the button and text 57 | gapY: 40, 58 | anchor: { x: 0.5, y: 0.5 }, 59 | children: [gameOverText, restartButton] 60 | }); 61 | 62 | let gameOverScene = kontra.Scene({ 63 | id: 'gameOver', 64 | objects: [grid] 65 | }); 66 | export default gameOverScene; 67 | -------------------------------------------------------------------------------- /examples/galaxian/js/index.js: -------------------------------------------------------------------------------- 1 | import bootScene from './boot.js'; 2 | import getGameScene from './galaxian.js'; 3 | import gameOverScene from './gameOver.js'; 4 | 5 | let gameScene; 6 | let activeScene = bootScene; 7 | let isGameOver = false; 8 | 9 | let loop = kontra.GameLoop({ 10 | update() { 11 | if (isGameOver) { 12 | gameOverScene.update(); 13 | } else { 14 | activeScene.update(); 15 | } 16 | }, 17 | render() { 18 | activeScene.render(); 19 | 20 | if (isGameOver) { 21 | gameOverScene.render(); 22 | } 23 | } 24 | }); 25 | 26 | // pause/unpause game 27 | kontra.onKey('p', function () { 28 | if (loop.isStopped) { 29 | loop.start(); 30 | kontra.audioAssets.kick_shock.play(); 31 | } else { 32 | loop.stop(); 33 | kontra.audioAssets.kick_shock.pause(); 34 | } 35 | }); 36 | 37 | kontra.on('startGame', () => { 38 | isGameOver = false; 39 | 40 | if (!gameScene) { 41 | gameScene = getGameScene(); 42 | } 43 | 44 | bootScene.destroy(); 45 | activeScene = gameScene; 46 | gameScene.start(); 47 | }); 48 | 49 | kontra.on('gameOver', () => { 50 | isGameOver = true; 51 | 52 | kontra.audioAssets.kick_shock.pause(); 53 | kontra.audioAssets.game_over.currentTime = 0; 54 | kontra.audioAssets.game_over.play(); 55 | }); 56 | 57 | loop.start(); 58 | -------------------------------------------------------------------------------- /examples/galaxian/sounds/explosion.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/examples/galaxian/sounds/explosion.mp3 -------------------------------------------------------------------------------- /examples/galaxian/sounds/game_over.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/examples/galaxian/sounds/game_over.mp3 -------------------------------------------------------------------------------- /examples/galaxian/sounds/kick_shock.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/examples/galaxian/sounds/kick_shock.mp3 -------------------------------------------------------------------------------- /examples/galaxian/sounds/laser.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/examples/galaxian/sounds/laser.mp3 -------------------------------------------------------------------------------- /examples/gamepad/gamepad.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Gamepad 5 | 6 | 7 | 8 |
NOTE:Gamepad support requires using a secure context (HTTPS).
9 | 10 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /examples/gesture/gesture.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Gesture 5 | 6 | 7 | 8 | 9 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /examples/imgs/Attribution.txt: -------------------------------------------------------------------------------- 1 | License 2 | ------- 3 | 4 | CC0 1.0 Universal 5 | - http://creativecommons.org/publicdomain/zero/1.0/ 6 | 7 | Assets from: 8 | http://opengameart.org/content/platformer-tiles 9 | https://opengameart.org/content/classic-hero 10 | https://www.kenney.nl/assets/ui-pack 11 | https://www.kenney.nl/assets/topdown-tanks-redux 12 | 13 | Kenny.nl 14 | OpenGameArt Kenny 15 | Homepage http://www.kenney.nl/ -------------------------------------------------------------------------------- /examples/imgs/blue_button02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/examples/imgs/blue_button02.png -------------------------------------------------------------------------------- /examples/imgs/bulletBlue1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/examples/imgs/bulletBlue1.png -------------------------------------------------------------------------------- /examples/imgs/bulletGreen1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/examples/imgs/bulletGreen1.png -------------------------------------------------------------------------------- /examples/imgs/bulletRed1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/examples/imgs/bulletRed1.png -------------------------------------------------------------------------------- /examples/imgs/bulletSand1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/examples/imgs/bulletSand1.png -------------------------------------------------------------------------------- /examples/imgs/character.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/examples/imgs/character.png -------------------------------------------------------------------------------- /examples/imgs/character_walk_sheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/examples/imgs/character_walk_sheet.png -------------------------------------------------------------------------------- /examples/imgs/oldHero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/examples/imgs/oldHero.png -------------------------------------------------------------------------------- /examples/imgs/tank_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/examples/imgs/tank_blue.png -------------------------------------------------------------------------------- /examples/imgs/tank_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/examples/imgs/tank_green.png -------------------------------------------------------------------------------- /examples/imgs/tank_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/examples/imgs/tank_red.png -------------------------------------------------------------------------------- /examples/imgs/tank_sand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/examples/imgs/tank_sand.png -------------------------------------------------------------------------------- /examples/pointer/pointer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Pointer 5 | 6 | 7 | 8 | 9 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /examples/pointer/touchEvents.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Pointer 7 | 8 | 9 | 10 | 11 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /examples/pool/particleEngine.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Particle Engine 5 | 6 | 7 | 8 | 9 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /examples/prism/codeOutput.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var pre = document.createElement('pre'); 3 | pre.innerHTML = 4 | '' + 5 | document.getElementById('code').innerHTML + 6 | ''; 7 | document.body.appendChild(pre); 8 | 9 | var prisimcss = document.createElement('link'); 10 | prisimcss.setAttribute('rel', 'stylesheet'); 11 | prisimcss.href = 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.15.0/themes/prism.min.css'; 12 | document.head.appendChild(prisimcss); 13 | 14 | var prisimjs = document.createElement('script'); 15 | prisimjs.src = 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.15.0/prism.min.js'; 16 | document.body.appendChild(prisimjs); 17 | })(); 18 | -------------------------------------------------------------------------------- /examples/scene/depthSortObjects.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Rect Sprite 5 | 6 | 7 | 8 | 9 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /examples/scene/gameScene.js: -------------------------------------------------------------------------------- 1 | import { gridSize, width, height } from './globals.js'; 2 | import snake from './snake.js'; 3 | 4 | let apple = kontra.Sprite({ 5 | x: 320, 6 | y: 320, 7 | width: gridSize - 1, 8 | height: gridSize - 1, 9 | color: 'red' 10 | }); 11 | 12 | // keep track of where snake is so we know where we can place a new apple 13 | let availableCells = []; 14 | 15 | let gameScene = kontra.Scene({ 16 | id: 'game', 17 | update() { 18 | snake.update(); 19 | 20 | // snake ate apple 21 | if (snake.x === apple.x && snake.y === apple.y) { 22 | snake.maxCells++; 23 | 24 | availableCells = []; 25 | for (let row = 0; row < height - 1; row++) { 26 | for (let col = 0; col < width - 1; col++) { 27 | let snakeCell = snake.cells.find(cell => { 28 | return cell.x === col * gridSize && cell.y === row * gridSize; 29 | }); 30 | if (!snakeCell) { 31 | availableCells.push({ row, col }); 32 | } 33 | } 34 | } 35 | 36 | let randomCell = kontra.randInt(0, availableCells.length - 1); 37 | apple.x = availableCells[randomCell].col * gridSize; 38 | apple.y = availableCells[randomCell].row * gridSize; 39 | } 40 | 41 | // check collision with all cells after this one 42 | snake.cells.forEach((cell, index) => { 43 | for (var i = index + 1; i < snake.cells.length; i++) { 44 | // snake occupies same space as a body part. reset game 45 | if (cell.x === snake.cells[i].x && cell.y === snake.cells[i].y) { 46 | snake.x = 160; 47 | snake.y = 160; 48 | snake.cells = []; 49 | snake.maxCells = 4; 50 | snake.dx = gridSize; 51 | snake.dy = 0; 52 | 53 | apple.x = kontra.randInt(0, width) * gridSize; 54 | apple.y = kontra.randInt(0, height) * gridSize; 55 | } 56 | } 57 | }); 58 | } 59 | }); 60 | 61 | gameScene.add(snake, apple); 62 | 63 | kontra.onKey('esc', () => { 64 | kontra.emit('navigate', 'Menu'); 65 | }); 66 | 67 | export default gameScene; 68 | -------------------------------------------------------------------------------- /examples/scene/globals.js: -------------------------------------------------------------------------------- 1 | export let gridSize = 32; 2 | export let width = kontra.getCanvas().width / gridSize; 3 | export let height = kontra.getCanvas().height / gridSize; -------------------------------------------------------------------------------- /examples/scene/menuScene.js: -------------------------------------------------------------------------------- 1 | let canvas = kontra.getCanvas(); 2 | 3 | let startButton = kontra.Button({ 4 | text: { 5 | color: 'white', 6 | font: '30px Monospace', 7 | text: 'Start', 8 | anchor: { x: 0.5, y: 0.5 } 9 | }, 10 | anchor: { x: 0.5, y: 0.5 }, 11 | x: canvas.width / 2, 12 | y: canvas.height / 2, 13 | onUp() { 14 | kontra.emit('navigate', this.text); 15 | }, 16 | render() { 17 | this.draw(); 18 | 19 | if (this.focused || this.hovered) { 20 | this.textNode.color = 'red'; 21 | } else { 22 | this.textNode.color = 'white'; 23 | } 24 | } 25 | }); 26 | 27 | kontra.track(startButton); 28 | 29 | let menuScene = kontra.Scene({ 30 | id: 'menu', 31 | onShow() { 32 | startButton.text = 'Resume'; 33 | startButton.focus(); 34 | }, 35 | focus() { 36 | startButton.focus(); 37 | } 38 | }); 39 | 40 | menuScene.add(startButton); 41 | 42 | export default menuScene; 43 | -------------------------------------------------------------------------------- /examples/scene/simpleScenes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Scene 5 | 6 | 24 | 25 | 26 |
27 | 28 |

(Esc) Menu

29 |
30 | 35 | 71 | 72 | -------------------------------------------------------------------------------- /examples/scene/snake.js: -------------------------------------------------------------------------------- 1 | import { gridSize } from './globals.js'; 2 | 3 | let snake = kontra.Sprite({ 4 | x: 160, 5 | y: 160, 6 | width: gridSize, 7 | height: gridSize, 8 | 9 | // snake velocity. moves one grid length every frame in either the x or y direction 10 | dx: gridSize, 11 | dy: 0, 12 | 13 | // keep track of all grids the snake body occupies 14 | cells: [], 15 | 16 | // keep track of moves 17 | queue: [], 18 | 19 | // length of the snake. grows when eating an apple 20 | maxCells: 4, 21 | 22 | update() { 23 | let move = this.queue.shift(); 24 | if (move) { 25 | this.dx = move.dx; 26 | this.dy = move.dy; 27 | } 28 | 29 | this.advance(); 30 | let canvas = kontra.getCanvas(); 31 | 32 | // wrap snake position horizontally on edge of screen 33 | if (this.x < 0) { 34 | this.x = canvas.width - gridSize; 35 | } else if (this.x >= canvas.width) { 36 | this.x = 0; 37 | } 38 | 39 | // wrap snake position vertically on edge of screen 40 | if (this.y < 0) { 41 | this.y = canvas.height - gridSize; 42 | } else if (this.y >= canvas.height) { 43 | this.y = 0; 44 | } 45 | 46 | // keep track of where snake has been. front of the array is always the head 47 | this.cells.unshift({ x: this.x, y: this.y }); 48 | 49 | // remove cells as we move away from them 50 | if (this.cells.length > this.maxCells) { 51 | this.cells.pop(); 52 | } 53 | }, 54 | 55 | render() { 56 | this.context.fillStyle = 'green'; 57 | snake.cells.forEach((cell, index) => { 58 | // drawing 1 px smaller than the grid creates a grid effect in the snake body so you can see how long it is 59 | this.context.fillRect(cell.x - this.x, cell.y - this.y, gridSize - 1, gridSize - 1); 60 | }); 61 | } 62 | }); 63 | 64 | // prevent snake from backtracking on itself by checking that it's 65 | // not already moving on the same axis (pressing left while moving 66 | // left won't do anything, and pressing right while moving left 67 | // shouldn't let you collide with your own body) 68 | kontra.onKey('arrowleft', () => { 69 | if (snake.dx === 0) { 70 | // queue the move so we also don't change directions before an 71 | // update and still can run into ourselves 72 | snake.queue.push({ 73 | dx: -gridSize, 74 | dy: 0 75 | }); 76 | } 77 | }); 78 | kontra.onKey('arrowup', () => { 79 | if (snake.dy === 0) { 80 | snake.queue.push({ 81 | dy: -gridSize, 82 | dx: 0 83 | }); 84 | } 85 | }); 86 | kontra.onKey('arrowright', () => { 87 | snake.queue.push({ 88 | dx: gridSize, 89 | dy: 0 90 | }); 91 | }); 92 | kontra.onKey('arrowdown', () => { 93 | if (snake.dy === 0) { 94 | snake.queue.push({ 95 | dy: gridSize, 96 | dx: 0 97 | }); 98 | } 99 | }); 100 | 101 | export default snake; 102 | -------------------------------------------------------------------------------- /examples/sprite/animationSprite.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Animation Sprite 5 | 6 | 7 | 8 | 9 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /examples/sprite/clampSpriteMovement.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Controlling A Sprite 5 | 6 | 7 | 8 | 9 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /examples/sprite/controllingASprite.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Controlling A Sprite 5 | 6 | 7 | 8 | 9 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /examples/sprite/customUpdateAndDraw.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Controlling A Sprite 5 | 6 | 7 | 8 | 9 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /examples/sprite/extendingASprite.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sprite Collision 5 | 6 | 7 | 8 | 9 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /examples/sprite/imageSprite.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Image Sprite 5 | 6 | 7 | 8 | 9 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /examples/sprite/movingASprite.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Moving A Sprite 5 | 6 | 7 | 8 | 9 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /examples/sprite/rectSprite.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Rect Sprite 5 | 6 | 7 | 8 | 9 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/sprite/spriteCollision.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sprite Collision 5 | 6 | 7 | 8 | 9 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /examples/spriteSheet/margin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Animation Sprite 5 | 6 | 7 | 8 | 9 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /examples/text/text.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Text 5 | 6 | 7 | 8 | 9 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/text/textAlign.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Text Align 5 | 6 | 7 | 8 | 9 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/text/textAutoNewline.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Text Auto Newline 5 | 6 | 7 | 8 | 9 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/text/textLineHeight.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Text LineHeight 5 | 6 | 7 | 8 | 9 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/text/textNewline.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Text Newline 5 | 6 | 7 | 8 | 9 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/tileEngine/camera/Attribution.txt: -------------------------------------------------------------------------------- 1 | License 2 | ------- 3 | 4 | CC-BY-SA 3.0: 5 | - http://creativecommons.org/licenses/by-sa/3.0/ 6 | - See the file: cc-by-sa-3.0.txt 7 | GNU GPL 3.0: 8 | - http://www.gnu.org/licenses/gpl-3.0.html 9 | - See the file: gpl-3.0.txt 10 | 11 | Note the file is based on the LCP contest readme so don't expect the exact little pieces used like the base one. 12 | *Additional license information. 13 | 14 | Assets from: 15 | 16 | LPC participants: 17 | ---------------- 18 | 19 | Casper Nilsson 20 | *GNU GPL 3.0 or later 21 | email: casper.nilsson@gmail.com 22 | Freenode: CasperN 23 | OpenGameArt.org: C.Nilsson 24 | 25 | - LPC C.Nilsson (2D art) 26 | 27 | Daniel Eddeland 28 | *GNU GPL 3.0 or later 29 | - Tilesets of plants, props, food and environments, suitable for farming / fishing sims and other games. 30 | - Includes wheat, grass, sand tilesets, fence tilesets and plants such as corn and tomato. 31 | 32 | 33 | Johann CHARLOT 34 | *GNU LGPL Version 3. 35 | *Later versions are permitted. 36 | Homepage http://poufpoufproduction.fr 37 | Email johannc@poufpoufproduction.fr 38 | 39 | - Shoot'em up graphic kit 40 | 41 | Skyler Robert Colladay 42 | 43 | - FeralFantom's Entry (2D art) 44 | 45 | BASE assets: 46 | ------------ 47 | 48 | Lanea Zimmerman (AKA Sharm) 49 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 50 | 51 | - barrel.png 52 | - brackish.png 53 | - buckets.png 54 | - bridges.png 55 | - cabinets.png 56 | - cement.png 57 | - cementstair.png 58 | - chests.png 59 | - country.png 60 | - cup.png 61 | - dirt2.png 62 | - dirt.png 63 | - dungeon.png 64 | - grassalt.png 65 | - grass.png 66 | - holek.png 67 | - holemid.png 68 | - hole.png 69 | - house.png 70 | - inside.png 71 | - kitchen.png 72 | - lava.png 73 | - lavarock.png 74 | - mountains.png 75 | - rock.png 76 | - shadow.png 77 | - signs.png 78 | - stairs.png 79 | - treetop.png 80 | - trunk.png 81 | - waterfall.png 82 | - watergrass.png 83 | - water.png 84 | - princess.png and princess.xcf 85 | 86 | 87 | Stephen Challener (AKA Redshrike) 88 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 89 | 90 | - female_walkcycle.png 91 | - female_hurt.png 92 | - female_slash.png 93 | - female_spellcast.png 94 | - male_walkcycle.png 95 | - male_hurt.png 96 | - male_slash.png 97 | - male_spellcast.png 98 | - male_pants.png 99 | - male_hurt_pants.png 100 | - male_fall_down_pants.png 101 | - male_slash_pants.png 102 | 103 | 104 | Charles Sanchez (AKA CharlesGabriel) 105 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 106 | 107 | - bat.png 108 | - bee.png 109 | - big_worm.png 110 | - eyeball.png 111 | - ghost.png 112 | - man_eater_flower.png 113 | - pumpking.png 114 | - slime.png 115 | - small_worm.png 116 | - snake.png 117 | 118 | 119 | Manuel Riecke (AKA MrBeast) 120 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 121 | 122 | - hairfemale.png and hairfemale.xcf 123 | - hairmale.png and hairmale.xcf 124 | - soldier.png 125 | - soldier_altcolor.png 126 | 127 | 128 | Daniel Armstrong (AKA HughSpectrum) 129 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 130 | 131 | Castle work: 132 | 133 | - castlewalls.png 134 | - castlefloors.png 135 | - castle_outside.png 136 | - castlefloors_outside.png 137 | - castle_lightsources.png 138 | 139 | 140 | -------------------------------------------------------------------------------- /examples/tileEngine/camera/chicken_eat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/examples/tileEngine/camera/chicken_eat.png -------------------------------------------------------------------------------- /examples/tileEngine/camera/man.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/examples/tileEngine/camera/man.png -------------------------------------------------------------------------------- /examples/tileEngine/camera/terrain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/examples/tileEngine/camera/terrain.png -------------------------------------------------------------------------------- /examples/tileEngine/margin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Tile Engine 5 | 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/tileEngine/margin/roguelikeDungeon_transparent.json: -------------------------------------------------------------------------------- 1 | { "columns":28, 2 | "image":"./roguelikeDungeon_transparent.png", 3 | "imageheight":305, 4 | "imagewidth":492, 5 | "margin":1, 6 | "name":"roguelikeDungeon_transparent", 7 | "spacing":1, 8 | "tilecount":476, 9 | "tileheight":16, 10 | "tilewidth":16, 11 | "type":"tileset" 12 | } -------------------------------------------------------------------------------- /examples/tileEngine/margin/roguelikeDungeon_transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/examples/tileEngine/margin/roguelikeDungeon_transparent.png -------------------------------------------------------------------------------- /examples/tileEngine/margin/roguelikeDungeon_transparent.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const rename = require('gulp-rename'); 3 | const size = require('gulp-size'); 4 | const terser = require('gulp-terser'); 5 | const plumber = require('gulp-plumber'); 6 | const preprocess = require('gulp-preprocess'); 7 | const rollup = require('@rollup/stream'); 8 | const source = require('vinyl-source-stream'); 9 | const pkg = require('./package.json'); 10 | require('./tasks/docs.js'); 11 | require('./tasks/typescript.js'); 12 | 13 | const context = { 14 | GAMEOBJECT_GROUP: true, 15 | GAMEOBJECT_ROTATION: true, 16 | GAMEOBJECT_VELOCITY: true, 17 | GAMEOBJECT_ACCELERATION: true, 18 | GAMEOBJECT_TTL: true, 19 | GAMEOBJECT_ANCHOR: true, 20 | GAMEOBJECT_SCALE: true, 21 | GAMEOBJECT_OPACITY: true, 22 | SPRITE_IMAGE: true, 23 | SPRITE_ANIMATION: true, 24 | TEXT_AUTONEWLINE: true, 25 | TEXT_NEWLINE: true, 26 | TEXT_RTL: true, 27 | TEXT_ALIGN: true, 28 | TEXT_STROKE: true, 29 | TILEENGINE_CAMERA: true, 30 | TILEENGINE_DYNAMIC: true, 31 | TILEENGINE_QUERY: true, 32 | TILEENGINE_TILED: true, 33 | VECTOR_SUBTRACT: true, 34 | VECTOR_SCALE: true, 35 | VECTOR_NORMALIZE: true, 36 | VECTOR_DOT: true, 37 | VECTOR_LENGTH: true, 38 | VECTOR_DISTANCE: true, 39 | VECTOR_ANGLE: true, 40 | VECTOR_DIRECTION: true, 41 | VECTOR_CLAMP: true 42 | // DEBUG and VISUAL_DEBUG are turned off 43 | }; 44 | 45 | const headerComment = `/** 46 | * @preserve 47 | * Kontra.js v${pkg.version} 48 | */`; 49 | 50 | function buildIife() { 51 | return rollup({ 52 | input: './src/kontra.defaults.js', 53 | output: { 54 | format: 'iife', 55 | name: 'kontra', 56 | strict: false, 57 | banner: headerComment 58 | } 59 | }) 60 | .pipe(source('kontra.js')) 61 | .pipe(gulp.dest('.')) 62 | .pipe(gulp.dest('./docs/assets/js')); 63 | } 64 | 65 | function buildModule() { 66 | return rollup({ 67 | input: './src/kontra.js', 68 | output: { 69 | format: 'es', 70 | strict: false, 71 | banner: headerComment 72 | } 73 | }) 74 | .pipe(source('kontra.mjs')) 75 | .pipe(gulp.dest('.')); 76 | } 77 | 78 | function distIife() { 79 | return gulp 80 | .src('kontra.js') 81 | .pipe(preprocess({ context })) 82 | .pipe(plumber()) 83 | .pipe(terser()) 84 | .pipe(plumber.stop()) 85 | .pipe(gulp.dest('./docs/assets/js')) 86 | .pipe(rename('kontra.min.js')) 87 | .pipe( 88 | size({ 89 | showFiles: true 90 | }) 91 | ) 92 | .pipe( 93 | size({ 94 | showFiles: true, 95 | gzip: true 96 | }) 97 | ) 98 | .pipe(gulp.dest('.')); 99 | } 100 | 101 | function distModule() { 102 | return gulp 103 | .src('kontra.mjs') 104 | .pipe(preprocess({ context })) 105 | .pipe(plumber()) 106 | .pipe(terser()) 107 | .pipe(plumber.stop()) 108 | .pipe(rename('kontra.min.mjs')) 109 | .pipe( 110 | size({ 111 | showFiles: true 112 | }) 113 | ) 114 | .pipe( 115 | size({ 116 | showFiles: true, 117 | gzip: true 118 | }) 119 | ) 120 | .pipe(gulp.dest('.')); 121 | } 122 | 123 | gulp.task('build', gulp.series(buildIife, buildModule, 'build:docs', 'build:ts')); 124 | 125 | gulp.task('dist', gulp.series('build', distIife, distModule)); 126 | 127 | gulp.task('watch', function () { 128 | gulp.watch('src/*.js', gulp.series('build', 'dist')); 129 | }); 130 | 131 | gulp.task('default', gulp.series('build', 'watch')); 132 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Tue Apr 07 2015 23:14:35 GMT-0600 (MDT) 3 | const DEBUG = process.argv.find(argv => argv === '--debug'); 4 | 5 | module.exports = function (config) { 6 | config.set({ 7 | basePath: '', 8 | singleRun: false, 9 | autoWatch: true, 10 | frameworks: ['mocha', 'chai', 'sinon'], 11 | files: [ 12 | // setup 13 | { pattern: 'test/setup.js', type: 'module' }, 14 | 15 | // assets 16 | { pattern: 'test/imgs/**/*.*', included: false, served: true }, 17 | { pattern: 'test/audio/**/*.*', included: false, served: true }, 18 | { pattern: 'test/data/**/*.*', included: false, served: true }, 19 | 20 | { pattern: 'src/*.js', type: 'module', included: false }, 21 | { pattern: 'test/utils.js', type: 'module', included: false }, 22 | { pattern: 'test/unit/*.spec.js', type: 'module' }, 23 | { pattern: 'test/integration/*.spec.js', type: 'module' } 24 | ], 25 | browsers: [DEBUG ? 'Chrome' : 'ChromeHeadless'], 26 | proxies: { 27 | '/imgs': '/base/test/imgs', 28 | '/audio': '/base/test/audio', 29 | '/data': '/base/test/data' 30 | }, 31 | reporters: ['mocha', 'coverage'], 32 | preprocessors: { 33 | 'src/**/*.js': ['coverage'] 34 | }, 35 | coverageReporter: { 36 | check: { 37 | emitWarning: false, 38 | global: { 39 | statements: 95, 40 | branches: 95, 41 | functions: 95, 42 | lines: 95 43 | } 44 | }, 45 | dir: 'coverage/', 46 | reporters: [{ type: 'html' }, { type: 'text-summary' }] 47 | }, 48 | client: { 49 | mocha: { 50 | timeout: 4000, 51 | reporter: 'html' 52 | } 53 | } 54 | }); 55 | }; 56 | -------------------------------------------------------------------------------- /key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDg2+nTdvsPrAiU 3 | 8pWkSxdihGZzBsFGjoaQyC2x152r1Fe2d7JjKb+vMjLBrb3Z/rdsUGBa4FaeYkwy 4 | W6HmnEckEIASmISJU0UHEwytywoU9LcWs9wfCG3VRJV4IVhrx6CZ01JzGukkLjO3 5 | XHq0HW+mdL6B7QYUMM8IqeGYS1u12Khf3sZVslxX9FA0TwyS48K9/rTw7GdeQY0v 6 | +99rOfJdgEJtOfv59UIg4CGG934Vc0o4xDvyFT0tz8Ux/hiOYOhBSOb3FSJB+6Ng 7 | xX+2FJ/OY2DE9sot7mibaCYL0sOEF9Fi76l7lFFbczIfcf+WcvVJ1Ux7i+wqZy7+ 8 | 32BkwHFxAgMBAAECggEAbKkZB715eYtS4leQBMLc3BjLQU7EW4pIcPKrUkO1x/Fn 9 | KaASLmVgYhNJ/9or4op6rPbyeTfr48HwvG0Xgc+HeWAX4+ScN5hrxQ1plRqHFrVj 10 | PK9R8hUqrmLkMBc9GWhwraU3NLSOcZN6HmOsUBnheHj2DucxhtRHWBJwGB5ihS7z 11 | 2L3kLb/a+Uc/14fFDXde4QyEi7rLnNdkRIkeJaVNPnVnLyGto4TuVIj1GIJLYVNG 12 | 4zIEptSIALgFIzS5L09Cm13zJiH0TMSIuBf3SFJTJJhTQcMCzB37Lag/Hee1Ja0i 13 | bhUulCMMHquBHMW5d0Jb+qsUWiTPnNjlOCFgegPHoQKBgQDyhDejPP8/N+oDJmSi 14 | e0DRQpwqzOxwNfTmr7+kaW8cUwWJHnUr9J06BFOFNN0fWVL3CG6sSsZki9fAaOWI 15 | 0aTVJ5ufOgdsbcAoIukGFlxKTbYvLppqwOGImKudLtEMMprwzypNQotJW0c+VOiN 16 | K1sCJEVDhihvaNYIEXZWnBSD1QKBgQDtXF/ZwEBS/+pIJkg1ghdGtaNzx7F3Fm1U 17 | nfiI8MoV0hGHp8aZ0gKLFRonOr89OW3f/0LK9l6u0MFPygjkjC6yg+dlOdXmNc7U 18 | O/8b98BCkGDz4Na3zOFcJEVY6V4ZsqGfk8IGSTifvmiUSsfdXpDVolhQ9InefJh9 19 | wCOAEIOxLQKBgQDP0i/fiijtotu9gUwh1N9Rs/Qh1WQUMJjCiv7+RH+71QVcYKZm 20 | WYPWsNhlwUxwTdqD6Uz2BkoG5bOopft1CLppEz0P8OllqJNPkcMAvW6vGfMycYxQ 21 | SSO8K6B83R61hjQygkUs2gaEgV0G9Doop2ug5TYZzECgYEVxuo0fYTdPVQKBgQC3 22 | WWOxEJCfjI+sq+Wbb6ILPMPF67tqAijx9BTHszhnIp3n6/G9YDwWs3ZAV2DiKjp1 23 | jPhLT4RUBW1N9QJpiN+Jhdp4lvRjn6zkxHOLZxVcVaqOuF8kG175jgsDY0ENGK9A 24 | VSLLOERFIRAnfJxmo2W9oGoYHs1gz137xS+m/Rq9AQKBgH1IWfc4ljfv82fUOXZi 25 | Adk1uCQ0LP4S4Dyao3PCgKfYKkt/A0SrKHRhVTTRmVa13rnwj5+dVXzZXJ7K7isn 26 | yieVoBhj/LS2xUfn3opsAYfs+KXzYDm4ke3JloaUJquFYSmZCS/JC4hQ3ZyWgNAB 27 | O2Xt6sbEcWMHMJkZwn3411jp 28 | -----END PRIVATE KEY----- -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kontra", 3 | "version": "10.0.2", 4 | "description": "Kontra HTML5 game development library", 5 | "main": "kontra.js", 6 | "module": "kontra.mjs", 7 | "sideEffects": false, 8 | "files": [ 9 | "kontra.js", 10 | "kontra.mjs", 11 | "kontra.min.js", 12 | "kontra.min.mjs", 13 | "kontra.d.ts" 14 | ], 15 | "scripts": { 16 | "start": "http-server -S -C cert.pem", 17 | "test": "karma start --single-run", 18 | "test:watch": "karma start", 19 | "test:permutations": "node test/permutations", 20 | "test:ts": "tsc test/typings/*.ts --noEmit", 21 | "test:debug": "karma start --debug", 22 | "eslint": "eslint ./{src,test}/", 23 | "build": "gulp build", 24 | "build:docs": "gulp build:docs", 25 | "watch": "gulp watch", 26 | "dist": "gulp dist", 27 | "release": "sh tasks/release.sh", 28 | "prepare": "husky install" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "https://github.com/straker/kontra.git" 33 | }, 34 | "keywords": [ 35 | "HTML5", 36 | "JavaScript", 37 | "game", 38 | "library", 39 | "js13k" 40 | ], 41 | "engines": { 42 | "node": ">=14.0.0" 43 | }, 44 | "author": "Steven Lambert", 45 | "license": "MIT", 46 | "bugs": { 47 | "url": "https://github.com/straker/kontra/issues" 48 | }, 49 | "homepage": "https://github.com/straker/kontra", 50 | "devDependencies": { 51 | "@rollup/stream": "^2.0.0", 52 | "chai": "^4.3.4", 53 | "coveralls": "^3.1.1", 54 | "eslint": "^8.4.1", 55 | "eslint-plugin-mocha-no-only": "^1.1.1", 56 | "glob": "^7.2.0", 57 | "gulp": "^4.0.2", 58 | "gulp-livingcss": "^5.0.0", 59 | "gulp-plumber": "^1.2.1", 60 | "gulp-preprocess": "git+https://github.com/straker/gulp-preprocess.git", 61 | "gulp-rename": "^2.0.0", 62 | "gulp-replace": "^1.1.3", 63 | "gulp-size": "^4.0.1", 64 | "gulp-terser": "^2.1.0", 65 | "http-server": "^14.0.0", 66 | "husky": "^7.0.2", 67 | "karma": "^6.3.16", 68 | "karma-chai": "^0.1.0", 69 | "karma-chrome-launcher": "^3.1.0", 70 | "karma-coverage": "^2.2.1", 71 | "karma-mocha": "^2.0.1", 72 | "karma-mocha-reporter": "^2.2.5", 73 | "karma-sinon": "^1.0.5", 74 | "lint-staged": "^13.2.1", 75 | "livingcss": "^7.0.1", 76 | "marked": "^3.0.7", 77 | "mocha": "^9.2.0", 78 | "preprocess": "git+https://github.com/straker/preprocess.git", 79 | "prettier": "^2.4.1", 80 | "rollup": "^2.58.0", 81 | "sinon": "^11.1.2", 82 | "typescript": "^4.4.4", 83 | "vinyl-source-stream": "^2.0.0" 84 | }, 85 | "lint-staged": { 86 | "*.js": [ 87 | "prettier --write", 88 | "eslint --fix" 89 | ] 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/core.js: -------------------------------------------------------------------------------- 1 | import { noop } from './utils.js'; 2 | import { emit } from './events.js'; 3 | 4 | /** 5 | * Functions for initializing the Kontra library and getting the canvas and context 6 | * objects. 7 | * 8 | * ```js 9 | * import { getCanvas, getContext, init } from 'kontra'; 10 | * 11 | * let { canvas, context } = init(); 12 | * 13 | * // or can get canvas and context through functions 14 | * canvas = getCanvas(); 15 | * context = getContext(); 16 | * ``` 17 | * @sectionName Core 18 | */ 19 | 20 | let canvasEl, context; 21 | 22 | // allow contextless environments, such as using ThreeJS as the main 23 | // canvas, by proxying all canvas context calls 24 | let handler = { 25 | // by using noop we can proxy both property and function calls 26 | // so neither will throw errors 27 | get(target, key) { 28 | // export for testing 29 | if (key == '_proxy') return true; 30 | return noop; 31 | } 32 | }; 33 | 34 | /** 35 | * Return the canvas element. 36 | * @function getCanvas 37 | * 38 | * @returns {HTMLCanvasElement} The canvas element for the game. 39 | */ 40 | export function getCanvas() { 41 | return canvasEl; 42 | } 43 | 44 | /** 45 | * Return the context object. 46 | * @function getContext 47 | * 48 | * @returns {CanvasRenderingContext2D} The context object the game draws to. 49 | */ 50 | export function getContext() { 51 | return context; 52 | } 53 | 54 | /** 55 | * Initialize the library and set up the canvas. Typically you will call `init()` as the first thing and give it the canvas to use. This will allow all Kontra objects to reference the canvas when created. 56 | * 57 | * ```js 58 | * import { init } from 'kontra'; 59 | * 60 | * let { canvas, context } = init('game'); 61 | * ``` 62 | * @function init 63 | * 64 | * @param {String|HTMLCanvasElement} [canvas] - The canvas for Kontra to use. Can either be the ID of the canvas element or the canvas element itself. Defaults to using the first canvas element on the page. 65 | * @param {Object} [options] - Game options. 66 | * @param {Boolean} [options.contextless=false] - If the game will run in an contextless environment. A contextless environment uses a [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) for the `canvas` and `context` so all property and function calls will noop. 67 | * 68 | * @returns {{canvas: HTMLCanvasElement, context: CanvasRenderingContext2D}} An object with properties `canvas` and `context`. `canvas` it the canvas element for the game and `context` is the context object the game draws to. 69 | */ 70 | export function init(canvas, { contextless = false } = {}) { 71 | // check if canvas is a string first, an element next, or default to 72 | // getting first canvas on page 73 | canvasEl = 74 | document.getElementById(canvas) || 75 | canvas || 76 | document.querySelector('canvas'); 77 | 78 | if (contextless) { 79 | canvasEl = canvasEl || new Proxy({}, handler); 80 | } 81 | 82 | // @ifdef DEBUG 83 | if (!canvasEl) { 84 | throw Error('You must provide a canvas element for the game'); 85 | } 86 | // @endif 87 | 88 | context = canvasEl.getContext('2d') || new Proxy({}, handler); 89 | context.imageSmoothingEnabled = false; 90 | 91 | emit('init'); 92 | 93 | return { canvas: canvasEl, context }; 94 | } 95 | 96 | // expose for testing 97 | export function _reset() { 98 | canvasEl = context = undefined; 99 | } -------------------------------------------------------------------------------- /src/events.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A simple event system. Allows you to hook into Kontra lifecycle events or create your own, such as for [Plugins](api/plugin). 3 | * 4 | * ```js 5 | * import { on, off, emit } from 'kontra'; 6 | * 7 | * function callback(a, b, c) { 8 | * console.log({a, b, c}); 9 | * }); 10 | * 11 | * on('myEvent', callback); 12 | * emit('myEvent', 1, 2, 3); //=> {a: 1, b: 2, c: 3} 13 | * off('myEvent', callback); 14 | * ``` 15 | * @sectionName Events 16 | */ 17 | 18 | // expose for testing 19 | export let callbacks = {}; 20 | 21 | /** 22 | * There are currently only three lifecycle events: 23 | * - `init` - Emitted after `kontra.init()` is called. 24 | * - `tick` - Emitted every frame of [GameLoop](api/gameLoop) before the loops `update()` and `render()` functions are called. 25 | * - `assetLoaded` - Emitted after an asset has fully loaded using the asset loader. The callback function is passed the asset and the url of the asset as parameters. 26 | * @sectionName Lifecycle Events 27 | */ 28 | 29 | /** 30 | * Register a callback for an event to be called whenever the event is emitted. The callback will be passed all arguments used in the `emit` call. 31 | * @function on 32 | * 33 | * @param {String} event - Name of the event. 34 | * @param {Function} callback - Function that will be called when the event is emitted. 35 | */ 36 | export function on(event, callback) { 37 | callbacks[event] = callbacks[event] || []; 38 | callbacks[event].push(callback); 39 | } 40 | 41 | /** 42 | * Remove a callback for an event. 43 | * @function off 44 | * 45 | * @param {String} event - Name of the event. 46 | * @param {Function} callback - The function that was passed during registration. 47 | */ 48 | export function off(event, callback) { 49 | callbacks[event] = (callbacks[event] || []).filter( 50 | fn => fn != callback 51 | ); 52 | } 53 | 54 | /** 55 | * Call all callback functions for the event. All arguments will be passed to the callback functions. 56 | * @function emit 57 | * 58 | * @param {String} event - Name of the event. 59 | * @param {...*} args - Comma separated list of arguments passed to all callbacks. 60 | */ 61 | export function emit(event, ...args) { 62 | (callbacks[event] || []).map(fn => fn(...args)); 63 | } 64 | 65 | // expose for testing 66 | export function _reset() { 67 | Object.keys(callbacks).map(key => { 68 | delete callbacks[key]; 69 | }); 70 | } 71 | -------------------------------------------------------------------------------- /src/kontra.js: -------------------------------------------------------------------------------- 1 | export { default as Animation, AnimationClass } from './animation.js'; 2 | export { 3 | imageAssets, 4 | audioAssets, 5 | dataAssets, 6 | setImagePath, 7 | setAudioPath, 8 | setDataPath, 9 | loadImage, 10 | loadAudio, 11 | loadData, 12 | load 13 | } from './assets.js'; 14 | export { default as Button, ButtonClass } from './button.js'; 15 | export { init, getCanvas, getContext } from './core.js'; 16 | export { on, off, emit } from './events.js'; 17 | export { default as GameLoop } from './gameLoop.js'; 18 | export { 19 | default as GameObject, 20 | GameObjectClass 21 | } from './gameObject.js'; 22 | export { 23 | gamepadMap, 24 | updateGamepad, 25 | initGamepad, 26 | onGamepad, 27 | offGamepad, 28 | gamepadPressed, 29 | gamepadAxis 30 | } from './gamepad.js'; 31 | export { 32 | gestureMap, 33 | initGesture, 34 | onGesture, 35 | offGesture 36 | } from './gesture.js'; 37 | export { default as Grid, GridClass } from './grid.js'; 38 | export { 39 | degToRad, 40 | radToDeg, 41 | angleToTarget, 42 | rotatePoint, 43 | movePoint, 44 | lerp, 45 | inverseLerp, 46 | clamp, 47 | setStoreItem, 48 | getStoreItem, 49 | collides, 50 | getWorldRect, 51 | depthSort 52 | } from './helpers.js'; 53 | export { initInput, onInput, offInput } from './input.js'; 54 | export { 55 | keyMap, 56 | initKeys, 57 | onKey, 58 | offKey, 59 | keyPressed 60 | } from './keyboard.js'; 61 | export { 62 | registerPlugin, 63 | unregisterPlugin, 64 | extendObject 65 | } from './plugin.js'; 66 | export { 67 | initPointer, 68 | getPointer, 69 | track, 70 | untrack, 71 | pointerOver, 72 | onPointer, 73 | offPointer, 74 | pointerPressed 75 | } from './pointer.js'; 76 | export { default as Pool, PoolClass } from './pool.js'; 77 | export { default as Quadtree, QuadtreeClass } from './quadtree.js'; 78 | export { rand, randInt, getSeed, seedRand } from './random.js'; 79 | export { default as Scene, SceneClass } from './scene.js'; 80 | export { default as Sprite, SpriteClass } from './sprite.js'; 81 | export { 82 | default as SpriteSheet, 83 | SpriteSheetClass 84 | } from './spriteSheet.js'; 85 | export { default as Text, TextClass } from './text.js'; 86 | export { 87 | default as TileEngine, 88 | TileEngineClass 89 | } from './tileEngine.js'; 90 | export { default as Vector, VectorClass } from './vector.js'; 91 | export { default } from './kontra.defaults.js'; 92 | -------------------------------------------------------------------------------- /src/random.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A pseudo-random number generator (PRNG). 3 | * 4 | * @sectionName Random 5 | */ 6 | let seed; 7 | 8 | /** 9 | * Return a random number between 0 (inclusive) and 1 (exclusive). 10 | * @see https://github.com/bryc/code/blob/master/jshash/PRNGs.md#splitmix32 11 | * @function rand 12 | * 13 | * @returns {Number} Random number between 0 and <1. 14 | */ 15 | export function rand() { 16 | seed ??= Date.now(); 17 | seed |= 0; 18 | seed = (seed + 0x9e3779b9) | 0; 19 | let t = seed ^ (seed >>> 16); 20 | t = Math.imul(t, 0x21f0aaad); 21 | t = t ^ (t >>> 15); 22 | t = Math.imul(t, 0x735a2d97); 23 | return ((t = t ^ (t >>> 15)) >>> 0) / 4294967296; 24 | } 25 | 26 | /** 27 | * Return a random integer between a minimum (inclusive) and maximum (inclusive) integer. 28 | * 29 | * ```js 30 | * import { randInt, rand } from 'kontra'; 31 | * 32 | * // random number between 10 and 20 33 | * console.log( randInt(10, 20) ); 34 | * 35 | * // bias the result of the random integer to be closer 36 | * // to the max 37 | * console.log( randInt(10, 20, () => rand() ** 2) ); 38 | * ``` 39 | * @see https://stackoverflow.com/a/1527820/2124254 40 | * @function randInt 41 | * 42 | * @param {Number} min - Min integer. 43 | * @param {Number} max - Max integer. 44 | * @param {() => Number} [randFn] - Function that generates a random number. Useful for [biasing the random number](https://gamedev.stackexchange.com/a/116875). 45 | * 46 | * @returns {Number} Random integer between min and max values. 47 | */ 48 | export function randInt(min, max, randFn = rand) { 49 | return ((randFn() * (max - min + 1)) | 0) + min; 50 | } 51 | 52 | /** 53 | * Get the current seed value of the random number generator. 54 | * @function getSeed 55 | * 56 | * @returns {Number} The seed value. 57 | */ 58 | export function getSeed() { 59 | return seed; 60 | } 61 | 62 | /** 63 | * Initialize the random number generator with a given seed. 64 | * 65 | * ```js 66 | * import { seedRand, rand } from 'kontra'; 67 | * 68 | * seedRand('kontra'); 69 | * console.log(rand()); // => always 0.26133555523119867 70 | * ``` 71 | * @see https://stackoverflow.com/a/47593316/2124254 72 | * @see https://github.com/bryc/code/blob/master/jshash/PRNGs.md 73 | * 74 | * @function seedRand 75 | * 76 | * @param {Number|String} [value=Date.now()] - Number or string to seed the random number generator. 77 | */ 78 | export function seedRand(value = Date.now()) { 79 | seed = value; 80 | 81 | if (typeof value == 'string') { 82 | // create a suitable hash of the seed string using MurmurHash3 83 | // @see https://github.com/bryc/code/blob/master/jshash/PRNGs.md#addendum-a-seed-generating-functions 84 | for ( 85 | var i = 0, h = 1779033703 ^ value.length; 86 | i < value.length; 87 | i++ 88 | ) { 89 | (h = Math.imul(h ^ value.charCodeAt(i), 3432918353)), 90 | (h = (h << 13) | (h >>> 19)); 91 | } 92 | h = Math.imul(h ^ (h >>> 16), 2246822507); 93 | h = Math.imul(h ^ (h >>> 13), 3266489909); 94 | seed = (h ^= h >>> 16) >>> 0; 95 | } 96 | } 97 | 98 | // export just for testing 99 | export function _reset() { 100 | seed = null; 101 | } 102 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | import { getWorldRect } from './helpers.js'; 2 | 3 | export let noop = () => {}; 4 | 5 | // style used for DOM nodes needed for screen readers 6 | export let srOnlyStyle = 7 | 'position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);'; 8 | // prevent focus from scrolling the page 9 | export let focusParams = { preventScroll: true }; 10 | 11 | /** 12 | * Append a node directly after the canvas and as the last element of other kontra nodes. 13 | * 14 | * @param {HTMLElement} node - Node to append. 15 | * @param {HTMLCanvasElement} canvas - Canvas to append after. 16 | */ 17 | export function addToDom(node, canvas) { 18 | let container = canvas.parentNode; 19 | 20 | node.setAttribute('data-kontra', ''); 21 | if (container) { 22 | let target = 23 | [ 24 | ...container.querySelectorAll(':scope > [data-kontra]') 25 | ].pop() || canvas; 26 | target.after(node); 27 | } else if (canvas.nodeName == 'CANVAS') { 28 | document.body.append(node); 29 | } else { 30 | canvas.append(node); 31 | } 32 | } 33 | 34 | /** 35 | * Remove an item from an array. 36 | * 37 | * @param {*[]} array - Array to remove from. 38 | * @param {*} item - Item to remove. 39 | * 40 | * @returns {Boolean|undefined} True if the item was removed. 41 | */ 42 | export function removeFromArray(array, item) { 43 | let index = array.indexOf(item); 44 | if (index != -1) { 45 | array.splice(index, 1); 46 | return true; 47 | } 48 | } 49 | 50 | /** 51 | * Detection collision between a rectangle and a circle. 52 | * @see https://yal.cc/rectangle-circle-intersection-test/ 53 | * 54 | * @param {Object} rect - Rectangular object to check collision against. 55 | * @param {Object} circle - Circular object to check collision against. 56 | * 57 | * @returns {Boolean} True if objects collide. 58 | */ 59 | export function circleRectCollision(circle, rect) { 60 | let { x, y, width, height } = getWorldRect(rect); 61 | 62 | // account for camera 63 | do { 64 | x -= rect.sx || 0; 65 | y -= rect.sy || 0; 66 | } while ((rect = rect.parent)); 67 | 68 | let dx = circle.x - Math.max(x, Math.min(circle.x, x + width)); 69 | let dy = circle.y - Math.max(y, Math.min(circle.y, y + height)); 70 | return dx * dx + dy * dy < circle.radius * circle.radius; 71 | } 72 | -------------------------------------------------------------------------------- /tasks/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # colors 5 | # @see https://stackoverflow.com/a/5947802/2124254 6 | Cyan='\033[0;36m' 7 | NC='\033[0m' # No Color 8 | 9 | versions=("major" "minor" "patch") 10 | 11 | # https://stackoverflow.com/a/8574392/2124254 12 | containsElement () { 13 | local e match="$1" 14 | shift 15 | for e; do [[ "$e" == "$match" ]] && return 0; done 16 | return 1 17 | } 18 | 19 | # enforce passing an argument 20 | # https://stackoverflow.com/a/22725973/2124254 21 | if (( $# == 0 )) || ( ! containsElement $1 "${versions[@]}" ); then 22 | echo "usage: sh tasks/release.sh [major | minor | patch]" 23 | exit 1; 24 | fi 25 | 26 | printf "\n${Cyan}Releasing lib${NC}\n" 27 | npm version $1 28 | version=$(node -p "require('./package.json').version") 29 | 30 | npm run dist 31 | npm publish 32 | 33 | # push version changes to service-worker before updating docs 34 | # https://github.com/straker/kontra/issues/410 35 | git add . 36 | git commit -m "docs: update service-worker version" 37 | git push origin main 38 | 39 | printf "\n${Cyan}Releasing docs${NC}\n" 40 | # rebuild docs to generate download page with newest version 41 | npm run build:docs 42 | cd docs 43 | find . -name "*.html" | cpio -pvd ../tmp &>/dev/null 44 | cp -r assets ../tmp/assets 45 | cd .. 46 | git checkout gh-pages 47 | ls | grep -v tmp | grep -v node_modules | xargs rm -rf 48 | mv tmp/* . 49 | rm -rf tmp 50 | git add . 51 | git commit -m "docs: release $version" 52 | git push origin gh-pages 53 | git checkout main -------------------------------------------------------------------------------- /tasks/ts-template.hbs: -------------------------------------------------------------------------------- 1 | declare namespace kontra { 2 | {{#each allSections}} 3 | {{#if class}} 4 | {{! copied from Typescript es5 definitions of standard API functions }} 5 | {{! @see https://github.com/microsoft/TypeScript/blob/master/lib/lib.es5.d.ts#L272-L316 }} 6 | interface {{class}}{{#if extends}} extends {{extends}}{{/if}} { 7 | {{#each children}} 8 | {{#if function}} 9 | {{name}}({{{params}}}): {{{returns}}}; 10 | {{/if}} 11 | {{#if property}} 12 | {{#if readonly}}readonly {{/if}}{{property.name}}: {{{property.type}}}; 13 | {{/if}} 14 | {{/each}} 15 | } 16 | interface {{class}}Constructor { 17 | new({{{params}}}): {{class}}; 18 | } 19 | var {{class}}Class: {{class}}Constructor 20 | function {{class}}({{{params}}}): {{class}}; 21 | {{/if}} 22 | {{#unless memberof}} 23 | {{#if function}} 24 | function {{name}}({{{params}}}): {{{returns}}}; 25 | {{/if}} 26 | {{#if property}} 27 | var {{property.name}}: {{{property.type}}}; 28 | {{/if}} 29 | {{/unless}} 30 | {{/each}} 31 | } 32 | 33 | export = kontra -------------------------------------------------------------------------------- /test/audio/shoot.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/test/audio/shoot.mp3 -------------------------------------------------------------------------------- /test/audio/shoot.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/test/audio/shoot.ogg -------------------------------------------------------------------------------- /test/data/source.json: -------------------------------------------------------------------------------- 1 | { 2 | "image": "/imgs/bullet.png" 3 | } -------------------------------------------------------------------------------- /test/data/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "foo": "bar", 3 | "lorium": "ipsum" 4 | } -------------------------------------------------------------------------------- /test/data/test.txt: -------------------------------------------------------------------------------- 1 | Hello world! -------------------------------------------------------------------------------- /test/data/tileset/bullet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/test/data/tileset/bullet.png -------------------------------------------------------------------------------- /test/data/tileset/readme.txt: -------------------------------------------------------------------------------- 1 | In order to property fail the tileEngine integration test of loading a tileset image from a json file, we need a path that will not resolve when the tileset does not resolve properly. E.g. if the tileset image is `../imgs/bullet.png` and tileset data url incorrectly resoles to `https://localhost:9090/context.html`, that will resolve to `https://localhost:9090/imgs/bullet.png` which is a correct url, but an incorrect resolve. -------------------------------------------------------------------------------- /test/data/tileset/tileset.json: -------------------------------------------------------------------------------- 1 | { "height":9, 2 | "layers":[ 3 | { 4 | "data":[203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203, 203], 5 | "height":9, 6 | "name":"base", 7 | "opacity":1, 8 | "type":"tilelayer", 9 | "visible":true, 10 | "width":9, 11 | "x":0, 12 | "y":0 13 | }, 14 | { 15 | "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 7, 7, 8, 0, 0, 0, 0, 6, 27, 24, 24, 25, 0, 0, 0, 0, 23, 24, 24, 24, 26, 8, 0, 0, 0, 23, 24, 24, 24, 24, 26, 8, 0, 0, 23, 24, 24, 24, 24, 24, 25, 0, 0, 40, 41, 41, 10, 24, 24, 25, 0, 0, 0, 0, 0, 40, 41, 41, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 16 | "height":9, 17 | "name":"grass", 18 | "opacity":1, 19 | "type":"tilelayer", 20 | "visible":true, 21 | "width":9, 22 | "x":0, 23 | "y":0 24 | }, 25 | { 26 | "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 0, 0, 0, 0, 0, 0, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 61, 0, 0, 0, 0, 0, 44, 0, 0, 0, 0, 61, 0, 0, 0, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 27 | "height":9, 28 | "name":"decoration", 29 | "opacity":1, 30 | "type":"tilelayer", 31 | "visible":true, 32 | "width":9, 33 | "x":0, 34 | "y":0 35 | }], 36 | "orientation":"orthogonal", 37 | "properties": 38 | { 39 | 40 | }, 41 | "tileheight":64, 42 | "tilesets":[ 43 | { 44 | "firstgid":1, 45 | "image":"./bullet.png", 46 | "imageheight":768, 47 | "imagewidth":1088, 48 | "margin":0, 49 | "name":"bullet", 50 | "properties": 51 | { 52 | 53 | }, 54 | "spacing":0, 55 | "tileheight":64, 56 | "tilewidth":64 57 | }], 58 | "tilewidth":64, 59 | "version":1, 60 | "width":9 61 | } -------------------------------------------------------------------------------- /test/imgs/bullet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/straker/kontra/3066be56c47ca4c885e72ad3aefd513f42bda244/test/imgs/bullet.png -------------------------------------------------------------------------------- /test/integration/core.spec.js: -------------------------------------------------------------------------------- 1 | import Sprite from '../../src/sprite.js'; 2 | import TileEngine from '../../src/tileEngine.js'; 3 | import GameLoop from '../../src/gameLoop.js'; 4 | import Text from '../../src/text.js'; 5 | import Scene from '../../src/scene.js'; 6 | import { _reset, init } from '../../src/core.js'; 7 | import { noop } from '../../src/utils.js'; 8 | 9 | describe('core integration', () => { 10 | beforeEach(() => { 11 | _reset(); 12 | }); 13 | 14 | it('should allow calling init after creating objects', () => { 15 | let sprite = Sprite({ 16 | x: 10, 17 | y: 20, 18 | width: 10, 19 | height: 20, 20 | color: 'red' 21 | }); 22 | let loop = GameLoop({ 23 | update: noop, 24 | render: noop 25 | }); 26 | let text = Text({ text: 'Hello World' }); 27 | let tileEngine = TileEngine({ 28 | tilewidth: 10, 29 | tileheight: 10, 30 | width: 50, 31 | height: 50, 32 | tilesets: [ 33 | { 34 | image: new Image() 35 | } 36 | ], 37 | layers: [ 38 | { 39 | name: 'test', 40 | data: [0, 0, 1, 0, 0] 41 | } 42 | ] 43 | }); 44 | 45 | let scene = Scene({ 46 | id: 'myId', 47 | objects: [sprite, text] 48 | }); 49 | 50 | expect(() => scene.lookAt(sprite)).to.not.throw(); 51 | 52 | let canvas = document.createElement('canvas'); 53 | canvas.width = canvas.height = 600; 54 | init(canvas); 55 | 56 | expect(() => sprite.render()).to.not.throw(); 57 | expect(() => text.render()).to.not.throw(); 58 | expect(() => tileEngine.render()).to.not.throw(); 59 | expect(() => scene.render()).to.not.throw(); 60 | 61 | loop._last = performance.now() - (1e3 / 60) * 2.5; 62 | expect(() => loop._frame()).to.not.throw(); 63 | }); 64 | }); -------------------------------------------------------------------------------- /test/integration/scene.spec.js: -------------------------------------------------------------------------------- 1 | import Scene from '../../src/scene.js'; 2 | import Sprite from '../../src/sprite.js'; 3 | import TileEngine from '../../src/tileEngine.js'; 4 | import { init, getCanvas } from '../../src/core.js'; 5 | import { depthSort } from '../../src/helpers.js'; 6 | import * as pointer from '../../src/pointer.js'; 7 | import { noop } from '../../src/utils.js'; 8 | import { emit } from '../../src/events.js'; 9 | 10 | describe('scene integration', () => { 11 | before(() => { 12 | if (!getCanvas()) { 13 | let canvas = document.createElement('canvas'); 14 | canvas.width = canvas.height = 600; 15 | init(canvas); 16 | } 17 | }); 18 | 19 | it('should render a tileEngine', () => { 20 | let spy = sinon.spy(); 21 | let tileEngine = TileEngine({ 22 | width: 10, 23 | height: 12, 24 | tilewidth: 32, 25 | tileheight: 32, 26 | tilesets: [], 27 | render: spy 28 | }); 29 | 30 | let scene = Scene({ 31 | id: 'myId', 32 | objects: [tileEngine] 33 | }); 34 | 35 | scene.render(); 36 | 37 | expect(spy.called).to.be.true; 38 | }); 39 | 40 | it('should work with helpers.depthSort', () => { 41 | let objects = []; 42 | let spies = []; 43 | for (let i = 5; i > 0; i--) { 44 | let spy = sinon.spy(); 45 | spies.push(spy); 46 | objects.push( 47 | Sprite({ 48 | id: i, 49 | x: 5, 50 | y: i * 10, 51 | width: 15, 52 | height: 25, 53 | render: spy 54 | }) 55 | ); 56 | } 57 | 58 | let scene = Scene({ 59 | id: 'myId', 60 | objects, 61 | sortFunction: depthSort 62 | }); 63 | scene.render(); 64 | 65 | expect(spies[4].calledBefore(spies[3])).to.be.true; 66 | expect(spies[3].calledBefore(spies[2])).to.be.true; 67 | expect(spies[2].calledBefore(spies[1])).to.be.true; 68 | expect(spies[1].calledBefore(spies[0])).to.be.true; 69 | }); 70 | 71 | it('should correctly track objects with pointer when camera is moved', () => { 72 | let pntr = pointer.initPointer({ radius: 1 }); 73 | let object = { 74 | x: 100, 75 | y: 50, 76 | width: 10, 77 | height: 20, 78 | render: noop 79 | }; 80 | let scene = Scene({ 81 | id: 'myId', 82 | objects: [object] 83 | }); 84 | pointer.track(object); 85 | object.render(); 86 | emit('tick'); 87 | 88 | pntr.x = 105; 89 | pntr.y = 55; 90 | expect(pointer.pointerOver(object)).to.equal(true); 91 | 92 | scene.camera.x += 100; 93 | expect(pointer.pointerOver(object)).to.equal(false); 94 | 95 | pntr.x = 5; 96 | expect(pointer.pointerOver(object)).to.equal(true); 97 | }); 98 | }); 99 | -------------------------------------------------------------------------------- /test/integration/sprite.spec.js: -------------------------------------------------------------------------------- 1 | import Sprite from '../../src/sprite.js'; 2 | import SpriteSheet from '../../src/spriteSheet.js'; 3 | import { track, initPointer } from '../../src/pointer.js'; 4 | 5 | describe('sprite integration', () => { 6 | it('should clone spriteSheet animations to prevent frame corruption ', () => { 7 | let spriteSheet = SpriteSheet({ 8 | image: new Image(100, 200), 9 | frameWidth: 10, 10 | frameHeight: 10, 11 | animations: { 12 | walk: { 13 | frames: '1..10', 14 | frameRate: 30 15 | } 16 | } 17 | }); 18 | 19 | let sprite1 = Sprite({ 20 | animations: spriteSheet.animations 21 | }); 22 | let sprite2 = Sprite({ 23 | animations: spriteSheet.animations 24 | }); 25 | let sprite3 = Sprite(); 26 | sprite3.animations = spriteSheet.animations; 27 | 28 | expect(sprite1.animations.walk).to.not.equal( 29 | sprite2.animations.walk 30 | ); 31 | expect(sprite1.animations.walk).to.not.equal( 32 | sprite3.animations.walk 33 | ); 34 | expect(sprite2.animations.walk).to.not.equal( 35 | sprite3.animations.walk 36 | ); 37 | 38 | for (let i = 0; i < 7; i++) { 39 | sprite1.update(); 40 | } 41 | 42 | expect(sprite1.animations.walk._f).to.equal(3); 43 | expect(sprite2.animations.walk._f).to.equal(0); 44 | expect(sprite3.animations.walk._f).to.equal(0); 45 | }); 46 | 47 | it('should not corrupt frames when playing animation', () => { 48 | let spriteSheet = SpriteSheet({ 49 | image: new Image(100, 200), 50 | frameWidth: 10, 51 | frameHeight: 10, 52 | animations: { 53 | walk: { 54 | frames: '1..10', 55 | frameRate: 30 56 | } 57 | } 58 | }); 59 | 60 | let sprite1 = Sprite({ 61 | animations: spriteSheet.animations 62 | }); 63 | let sprite2 = Sprite({ 64 | animations: spriteSheet.animations 65 | }); 66 | let sprite3 = Sprite(); 67 | sprite3.animations = spriteSheet.animations; 68 | 69 | sprite1.playAnimation('walk'); 70 | sprite2.playAnimation('walk'); 71 | sprite3.playAnimation('walk'); 72 | 73 | for (let i = 0; i < 7; i++) { 74 | sprite1.update(); 75 | } 76 | 77 | expect(sprite1.animations.walk._f).to.equal(3); 78 | expect(sprite2.animations.walk._f).to.equal(0); 79 | expect(sprite3.animations.walk._f).to.equal(0); 80 | }); 81 | 82 | it('should render when tracked by pointer', () => { 83 | initPointer(); 84 | let spy = sinon.spy(); 85 | 86 | let sprite = Sprite({ 87 | x: 100, 88 | y: 200, 89 | color: 'red', 90 | render: spy 91 | }); 92 | 93 | track(sprite); 94 | sprite.render(); 95 | 96 | // retain _r as radius 97 | expect(typeof sprite._r).to.not.equal('function'); 98 | 99 | // retain _rf as render function 100 | expect(sprite._rf).to.equal(spy); 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /test/integration/vector.spec.js: -------------------------------------------------------------------------------- 1 | import Vector from '../../src/vector.js'; 2 | import { init, getCanvas } from '../../src/core.js'; 3 | import { rotatePoint, movePoint } from '../../src/helpers.js'; 4 | 5 | describe('vector integration', () => { 6 | before(() => { 7 | if (!getCanvas()) { 8 | let canvas = document.createElement('canvas'); 9 | canvas.width = canvas.height = 600; 10 | init(canvas); 11 | } 12 | }); 13 | 14 | it('should set from rotatePoint', () => { 15 | let vector = Vector(rotatePoint({ x: 10, y: 10 }, Math.PI)); 16 | 17 | expect(vector.x).to.be.closeTo(-10, 0.1); 18 | expect(vector.y).to.be.closeTo(-10, 0.1); 19 | }); 20 | 21 | it('should set from movePoint', () => { 22 | let vector = Vector(movePoint({ x: 10, y: 10 }, 0, 10)); 23 | 24 | expect(vector.x).to.equal(20); 25 | expect(vector.y).to.equal(10); 26 | }); 27 | }); -------------------------------------------------------------------------------- /test/permutations/karma.conf.template.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = function (config) { 4 | config.set({ 5 | basePath: '', 6 | singleRun: true, 7 | autoWatch: true, 8 | frameworks: ['mocha', 'chai', 'sinon'], 9 | files: [ 10 | { 11 | pattern: path.join(__dirname, '__option__.spec.js'), 12 | type: 'module' 13 | } 14 | ], 15 | proxies: { 16 | '/src': path.join('/absolute', __dirname, '../../src') 17 | }, 18 | browsers: ['ChromeHeadless'], 19 | reporters: ['mocha'] 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | // permutations script prepends this file with the test file 2 | // so we need to rename this import so other files that import 3 | // it don't import by the same name 4 | import { 5 | init as initCore, 6 | _reset as resetCore 7 | } from '../src/core.js'; 8 | import { _reset as resetAssets } from '../src/assets.js'; 9 | import { _reset as resetEvents } from '../src/events.js'; 10 | import { _reset as resetGesture } from '../src/gesture.js'; 11 | import { _reset as seedReset } from '../src/random.js'; 12 | 13 | // ensure canvas exists before each test 14 | function setup() { 15 | let canvas = document.createElement('canvas'); 16 | canvas.id = 'mainCanvas'; 17 | canvas.width = canvas.height = 600; 18 | document.body.appendChild(canvas); 19 | initCore(canvas); 20 | } 21 | 22 | beforeEach(() => { 23 | setup(); 24 | }); 25 | 26 | afterEach(() => { 27 | document 28 | .querySelectorAll('canvas') 29 | .forEach(canvas => canvas.remove()); 30 | document 31 | .querySelectorAll('[data-kontra]') 32 | .forEach(node => node.remove()); 33 | 34 | sinon.restore(); 35 | 36 | resetAssets(); 37 | resetCore(); 38 | resetEvents(); 39 | resetGesture(); 40 | seedReset(); 41 | }); 42 | -------------------------------------------------------------------------------- /test/typings/animation.ts: -------------------------------------------------------------------------------- 1 | import * as kontra from '../../kontra.js'; 2 | 3 | // animation 4 | let spriteSheet: kontra.SpriteSheet = kontra.SpriteSheet({ 5 | image: kontra.imageAssets.character_walk_sheet, 6 | frameWidth: 72, 7 | frameHeight: 97 8 | }); 9 | 10 | let animation: kontra.Animation = kontra.Animation({ 11 | name: 'anim', 12 | spriteSheet: spriteSheet, 13 | frames: [1,2,3,4], 14 | frameRate: 35 15 | }); 16 | 17 | animation.update(); 18 | animation.update(1/60); 19 | animation.render({x: 10, y: 20}); 20 | animation.reset(); 21 | animation.stop(); 22 | animation.start(); 23 | 24 | let spriteSheetAnim: kontra.SpriteSheet = animation.spriteSheet; 25 | let frames: number[] = animation.frames; 26 | let frameRate: number = animation.frameRate; 27 | let loop: boolean = animation.loop; 28 | let width: number = animation.width; 29 | let height: number = animation.height; 30 | let margin: number = animation.margin; 31 | let stopped: boolean = animation.isStopped; 32 | let name: string = animation.name; 33 | 34 | // clone 35 | let clone: kontra.Animation = animation.clone(); 36 | 37 | // loop 38 | let loopAnim = kontra.Animation({ 39 | spriteSheet: spriteSheet, 40 | frames: [1,2,3,4], 41 | frameRate: 35, 42 | loop: false 43 | }); 44 | 45 | // render props 46 | let context = document.createElement('canvas').getContext('2d'); 47 | loopAnim.render({ 48 | x: 10, 49 | y: 20, 50 | width: 100, 51 | height: 100, 52 | context: context 53 | }); 54 | 55 | // extends 56 | class CustomAnimation extends kontra.AnimationClass {} 57 | let myAnim = new CustomAnimation({ 58 | spriteSheet: spriteSheet, 59 | frames: [1,2,3,4], 60 | frameRate: 35 61 | }); 62 | myAnim.update(); -------------------------------------------------------------------------------- /test/typings/assets.ts: -------------------------------------------------------------------------------- 1 | import * as kontra from '../../kontra.js'; 2 | 3 | let img: HTMLImageElement = kontra.imageAssets.image; 4 | let audio: HTMLAudioElement = kontra.audioAssets.audio; 5 | let data: string = kontra.dataAssets.myStr; 6 | let json: object = kontra.dataAssets.json; 7 | 8 | kontra.setImagePath('/imgs'); 9 | kontra.setAudioPath('/audio'); 10 | kontra.setDataPath('/data'); 11 | 12 | kontra.loadImage('./path/to/img.png') 13 | .then(img => { 14 | let image: HTMLImageElement = img; 15 | }); 16 | kontra.loadAudio('./path/to/audio.mp3') 17 | .then(audio => { 18 | let song: HTMLAudioElement = audio; 19 | }); 20 | kontra.loadAudio(['./path/to/audio.mp3', './path/to/audio.wav']) 21 | .then(audio => { 22 | let song: HTMLAudioElement = audio; 23 | }); 24 | kontra.loadData('./path/to/data.txt') 25 | .then(data => { 26 | let txt: string = data; 27 | }); 28 | kontra.loadData('./path/to/data.json') 29 | .then(data => { 30 | let json: object = data; 31 | }); 32 | 33 | kontra.load('./path/to/img.png') 34 | .then(assets => { 35 | let image: HTMLImageElement = assets[0]; 36 | }); 37 | kontra.load( 38 | './path/to/img.png', 39 | ['./path/to/audio.mp3', './path/to/audio.wav'], 40 | './path/to/data.txt', 41 | './path/to/data.json' 42 | ) 43 | .then(assets => { 44 | let image: HTMLImageElement = assets[0]; 45 | let audio: HTMLAudioElement = assets[1]; 46 | let data: string = assets[2]; 47 | let json: object = assets[3]; 48 | }); -------------------------------------------------------------------------------- /test/typings/button.ts: -------------------------------------------------------------------------------- 1 | import * as kontra from '../../kontra.js'; 2 | 3 | let button: kontra.Button = kontra.Button({ 4 | padX: 0, 5 | padY: 10, 6 | text: { 7 | text: 'Hello World!', 8 | color: 'black', 9 | font: '32px Arial', 10 | align: {x: 0.5, y: 0.5} 11 | }, 12 | }); 13 | 14 | let str: string = button.text; 15 | let padX: number = button.padX; 16 | let padY: number = button.padY; 17 | let text: kontra.Text = button.textNode; 18 | let disabled: boolean = button.disabled; 19 | let focused: boolean = button.focused; 20 | let hovered: boolean = button.hovered; 21 | let pressed: boolean = button.pressed; 22 | let node: HTMLButtonElement = button.node; 23 | 24 | button.enable(); 25 | button.disable(); 26 | button.focus(); 27 | button.blur(); 28 | button.destroy(); 29 | button.onEnable(); 30 | button.onDisable(); 31 | button.onFocus(); 32 | button.onBlur(); 33 | 34 | // inheritance 35 | button.x += 20; 36 | button.rotation = Math.PI; 37 | button.advance(); 38 | button.render(); 39 | 40 | // options 41 | kontra.Button({ 42 | x: 10, 43 | y: 20, 44 | text: { 45 | text: 'Hello World!', 46 | color: 'black', 47 | font: '32px Arial', 48 | textAlign: 'right', 49 | width: 200 50 | }, 51 | onEnable() {}, 52 | onDisable() {}, 53 | onFocus() {}, 54 | onBlur() {}, 55 | onUp() {} 56 | }); 57 | 58 | // extends 59 | class CustomButton extends kontra.ButtonClass {} 60 | let myButton = new CustomButton(); 61 | myButton.enable(); -------------------------------------------------------------------------------- /test/typings/core.ts: -------------------------------------------------------------------------------- 1 | import * as kontra from '../../kontra.js'; 2 | 3 | let game: HTMLCanvasElement = kontra.getCanvas(); 4 | let ctx: CanvasRenderingContext2D = kontra.getContext(); 5 | 6 | // init 7 | kontra.init('#game'); 8 | kontra.init(game); 9 | kontra.init(); 10 | 11 | // return 12 | let { 13 | canvas: HTMLCanvasElement, 14 | context: CanvasRenderingContext2D 15 | } = kontra.init(); -------------------------------------------------------------------------------- /test/typings/events.ts: -------------------------------------------------------------------------------- 1 | import * as kontra from '../../kontra.js'; 2 | 3 | kontra.on('myEvent', () => { 4 | console.log('fired!'); 5 | }); 6 | kontra.off('myEvent', () => {}); 7 | kontra.emit('myEvent'); 8 | 9 | // args 10 | kontra.on('myEvent', (num, str, bool, obj) => { 11 | console.log({num, str, bool, obj}); 12 | }); 13 | kontra.emit('myEvent', 1, 'string', true, {}); -------------------------------------------------------------------------------- /test/typings/gameLoop.ts: -------------------------------------------------------------------------------- 1 | import * as kontra from '../../kontra.js'; 2 | 3 | let gameLoop: kontra.GameLoop = kontra.GameLoop({ 4 | update: () => {}, 5 | render: () => {} 6 | }); 7 | 8 | let stopped: boolean = gameLoop.isStopped; 9 | gameLoop.stop(); 10 | gameLoop.start(); 11 | gameLoop.update(); 12 | gameLoop.update(1/60); 13 | gameLoop.render(); 14 | 15 | // options 16 | let otherLoop = kontra.GameLoop({ 17 | update: () => {}, 18 | render: () => {}, 19 | clearCanvas: false, 20 | fps: 20, 21 | context: document.createElement('canvas').getContext('2d'), 22 | blur: true 23 | }); -------------------------------------------------------------------------------- /test/typings/gameObject.ts: -------------------------------------------------------------------------------- 1 | import * as kontra from '../../kontra.js'; 2 | 3 | let gameObject: kontra.GameObject = kontra.GameObject(); 4 | 5 | let position: kontra.Vector = gameObject.position; 6 | let velocity: kontra.Vector = gameObject.velocity; 7 | let acceleration: kontra.Vector = gameObject.acceleration; 8 | let width: number = gameObject.width; 9 | let height: number = gameObject.height; 10 | let context: CanvasRenderingContext2D = gameObject.context; 11 | let children: kontra.GameObject[] = gameObject.children; 12 | let rotation: number = gameObject.rotation; 13 | let drotation: number = gameObject.drotation; 14 | let ddrotation: number = gameObject.ddrotation; 15 | let ttl: number = gameObject.ttl; 16 | let anchor: {x: number, y: number} = gameObject.anchor; 17 | let sx: number = gameObject.sx; 18 | let sy: number = gameObject.sy; 19 | let x: number = gameObject.x; 20 | let y: number = gameObject.y; 21 | let dx: number = gameObject.dx; 22 | let dy: number = gameObject.dy; 23 | let ddx: number = gameObject.ddx; 24 | let ddy: number = gameObject.ddy; 25 | let scaleX: number = gameObject.scaleX; 26 | let scaleY: number = gameObject.scaleY; 27 | let opacity: number = gameObject.opacity; 28 | let world: { 29 | x: number, 30 | y: number, 31 | width: number, 32 | height: number, 33 | rotation: number, 34 | opacity: number, 35 | scaleX: number, 36 | scaleY: number 37 | } = gameObject.world; 38 | 39 | let alive: boolean = gameObject.isAlive(); 40 | gameObject.addChild(kontra.GameObject()); 41 | gameObject.addChild(kontra.GameObject(), kontra.GameObject()); 42 | gameObject.addChild([kontra.GameObject(), kontra.GameObject()]); 43 | gameObject.removeChild(kontra.GameObject()); 44 | gameObject.removeChild(kontra.GameObject(), kontra.GameObject()); 45 | gameObject.removeChild([kontra.GameObject(), kontra.GameObject()]); 46 | gameObject.update(); 47 | gameObject.update(1/60); 48 | gameObject.advance(); 49 | gameObject.advance(1/60); 50 | gameObject.render(); 51 | gameObject.draw(); 52 | gameObject.setScale(1); 53 | gameObject.setScale(1, 2); 54 | 55 | // options 56 | kontra.GameObject({ 57 | x: 10, 58 | y: 20, 59 | width: 100, 60 | height: 100, 61 | dx: 2, 62 | dy: 2, 63 | ddx: 2, 64 | ddy: 2, 65 | ttl: 10, 66 | rotation: 10, 67 | drotation: 10, 68 | ddrotation: 12, 69 | anchor: {x: 2, y: 2}, 70 | scaleX: 2, 71 | scaleY: 2, 72 | opacity: 0.5, 73 | context: document.createElement('canvas').getContext('2d'), 74 | update() {}, 75 | render() {} 76 | }); 77 | 78 | // custom props 79 | kontra.GameObject({ 80 | name: 'myObject' 81 | }); 82 | 83 | // extends 84 | class CustomGameObject extends kontra.GameObjectClass {} 85 | let myGameObj = new CustomGameObject(); 86 | myGameObj.render(); -------------------------------------------------------------------------------- /test/typings/gamepad.ts: -------------------------------------------------------------------------------- 1 | import * as kontra from '../../kontra.js'; 2 | 3 | let map: object = kontra.gamepadMap; 4 | kontra.gamepadMap[0] = 'A'; 5 | 6 | kontra.initGamepad(); 7 | kontra.updateGamepad(); 8 | 9 | kontra.onGamepad('south', (gamepad: Gamepad, button: GamepadButton) => { 10 | console.log('south pressed'); 11 | }); 12 | kontra.onGamepad(['south', 'north', 'east'], () => { 13 | console.log('south, north, or east pressed'); 14 | }); 15 | kontra.onGamepad(['south', 'north', 'east'], () => { 16 | console.log('south, north, or east pressed'); 17 | }, { 18 | gamepad: 1, 19 | handler: 'gamepaddown' 20 | }); 21 | kontra.onGamepad(['south', 'north', 'east'], () => { 22 | console.log('south, north, or east pressed'); 23 | }, { 24 | handler: 'gamepadup' 25 | }); 26 | 27 | kontra.offGamepad('south'); 28 | kontra.offGamepad(['south', 'north', 'east']); 29 | kontra.offGamepad(['south', 'north', 'east'], { 30 | gamepad: 1, 31 | handler: 'gamepaddown' 32 | }); 33 | kontra.offGamepad(['south', 'north', 'east'], { 34 | handler: 'gamepadup' 35 | }); 36 | 37 | kontra.gamepadPressed('south'); 38 | kontra.gamepadPressed('south', { gamepad: 1 }); 39 | 40 | kontra.gamepadAxis('leftstickx', 0); -------------------------------------------------------------------------------- /test/typings/gesture.ts: -------------------------------------------------------------------------------- 1 | import * as kontra from '../../kontra.js'; 2 | 3 | kontra.initGesture(); 4 | 5 | let map: object = kontra.gestureMap; 6 | kontra.gestureMap.pan = { 7 | touches: 1, 8 | threshold: 10, 9 | touchstart(touches: object) { 10 | console.log('touchstart'); 11 | }, 12 | touchmove() { 13 | console.log('touchmove'); 14 | }, 15 | touchend() { 16 | console.log('touchend'); 17 | } 18 | }; 19 | kontra.gestureMap.foo = { 20 | touches: 2, 21 | touchstart(touches: object) { 22 | console.log('touchstart'); 23 | } 24 | } 25 | kontra.gestureMap.bar = { 26 | touches: 2, 27 | touchmove(touches: object) { 28 | console.log('touchmove'); 29 | } 30 | } 31 | kontra.gestureMap.baz = { 32 | touches: 2, 33 | touchend(touches: object) { 34 | console.log('touchend'); 35 | } 36 | } 37 | 38 | kontra.onGesture('panleft', (evt: TouchEvent, touches: object) => { 39 | console.log('panleft'); 40 | }); 41 | kontra.onGesture(['panleft', 'swiperight'], () => {}) 42 | 43 | kontra.offGamepad('panleft'); 44 | kontra.offGamepad(['panleft', 'swiperight']); -------------------------------------------------------------------------------- /test/typings/grid.ts: -------------------------------------------------------------------------------- 1 | import * as kontra from '../../kontra.js'; 2 | 3 | let grid = kontra.Grid(); 4 | 5 | let flow: string = grid.flow; 6 | let align: string | string[] = grid.align; 7 | let justify: string | string[] = grid.justify; 8 | let colGap: number | number[] = grid.colGap; 9 | let rowGap: number | number[] = grid.rowGap; 10 | let numRows: number = grid.numRows; 11 | let dir: string = grid.dir; 12 | let breakpoints: {metric: Function, callback: Function}[] = grid.breakpoints; 13 | 14 | grid.destroy(); 15 | 16 | // inheritance 17 | grid.x += 20; 18 | grid.rotation = Math.PI; 19 | grid.advance(); 20 | grid.render(); 21 | 22 | // options 23 | kontra.Grid({ 24 | flow: 'grid', 25 | align: 'center', 26 | justify: 'center', 27 | colGap: 10, 28 | rowGap: 10, 29 | numCols: 2, 30 | dir: 'rtl', 31 | breakpoints: [{ 32 | metric() { return true }, 33 | callback() { this.numCols = 1 } 34 | }] 35 | }); 36 | 37 | // gap arrays 38 | kontra.Grid({ 39 | colGap: [10], 40 | rowGap: [10], 41 | }); 42 | 43 | // alignment arrays 44 | kontra.Grid({ 45 | align: ['end', 'center'], 46 | justify: ['end', 'center'], 47 | }); 48 | 49 | // extends 50 | class CustomGrid extends kontra.GridClass {} 51 | let myGrid = new CustomGrid(); 52 | myGrid.destroy(); -------------------------------------------------------------------------------- /test/typings/helpers.ts: -------------------------------------------------------------------------------- 1 | import * as kontra from '../../kontra.js'; 2 | 3 | let degToRad: number = kontra.degToRad(22.35); 4 | let radToDeg: number = kontra.radToDeg(0.39); 5 | 6 | let sourceSprite = kontra.Sprite({x: 0, y: 0}); 7 | let targetSprite = kontra.Sprite({x: 10, y: 10}); 8 | let angleToTargetXY: number = kontra.angleToTarget({x: 0, y: 0}, {x: 10, y: 10}); 9 | let angleToTargetSprite = kontra.angleToTarget(sourceSprite, targetSprite); 10 | 11 | let point: {x: number, y: number} = kontra.rotatePoint({x: 0, y: 0}, Math.PI); 12 | 13 | let lerp: number = kontra.lerp(10, 20, 0.5); 14 | let inverseLerp: number = kontra.inverseLerp(10, 20, 15); 15 | let clamp: number = kontra.clamp(10, 20, 30); 16 | 17 | kontra.setStoreItem('key', true); 18 | let item: boolean = kontra.getStoreItem('key'); 19 | 20 | let collision: boolean = kontra.collides({ 21 | x: 10, 22 | y: 20, 23 | width: 100, 24 | height: 100 25 | }, { 26 | x: 20, 27 | y: 40, 28 | width: 25, 29 | height: 25 30 | }); 31 | 32 | // sprites 33 | let sprite1 = kontra.Sprite(); 34 | let sprite2 = kontra.Sprite(); 35 | 36 | let theyCollides = kontra.collides(sprite1, sprite2); 37 | 38 | // rotation 39 | sprite1.rotation = Math.PI; 40 | let nope = kontra.collides(sprite1, sprite2); 41 | 42 | // anchor 43 | sprite1.anchor = {x: 0.5, y: 0.5}; 44 | let withAnchor = kontra.collides(sprite1, sprite2); 45 | 46 | // scale 47 | sprite1.scale = {x: 2, y: 2}; 48 | let withScale = kontra.collides(sprite1, sprite2); 49 | 50 | let rect = kontra.getWorldRect({ 51 | x: 10, 52 | y: 20, 53 | width: 100, 54 | height: 100 55 | }); 56 | 57 | // sprites 58 | rect = kontra.getWorldRect(sprite1); 59 | 60 | // tileEngines 61 | let tileEngine = kontra.TileEngine({ 62 | width: 10, 63 | height: 12, 64 | tilewidth: 32, 65 | tileheight: 32, 66 | tilesets: [], 67 | layers: [] 68 | }); 69 | rect = kontra.getWorldRect(tileEngine); 70 | 71 | let sortValue = kontra.depthSort({ 72 | x: 10, 73 | y: 20, 74 | width: 100, 75 | height: 100 76 | }, { 77 | x: 10, 78 | y: 20, 79 | width: 100, 80 | height: 100 81 | }); 82 | 83 | // sprites 84 | sortValue = kontra.depthSort(sprite1, sprite2); 85 | 86 | // options 87 | sortValue = kontra.depthSort(sprite1, sprite2, 'x'); -------------------------------------------------------------------------------- /test/typings/input.ts: -------------------------------------------------------------------------------- 1 | import * as kontra from '../../kontra.js'; 2 | 3 | kontra.initInput(); 4 | 5 | kontra.onInput('south', () => { 6 | console.log('south gamepad pressed'); 7 | }); 8 | kontra.onInput(['arrowleft', 'south'], () => {}); 9 | kontra.onInput('south', () => {}, { gamepad: { gamepad: 1 }}); 10 | kontra.onInput('arrowleft', () => {}, { key: { handler: 'keydown' }}); 11 | 12 | kontra.offInput('south'); 13 | kontra.offInput(['south', 'arrowleft']); 14 | kontra.offInput('south', { gamepad: { gamepad: 1 }}); 15 | kontra.offInput('arrowleft', { key: { handler: 'keydown' }}); 16 | -------------------------------------------------------------------------------- /test/typings/keyboard.ts: -------------------------------------------------------------------------------- 1 | import * as kontra from '../../kontra.js'; 2 | 3 | let map: object = kontra.keyMap; 4 | kontra.keyMap.namedKey = 'namedKey'; 5 | 6 | kontra.initKeys(); 7 | 8 | kontra.onKey('a', (evt: KeyboardEvent) => { 9 | console.log('a pressed'); 10 | }); 11 | kontra.onKey(['a', 'b', 'c'], () => { 12 | console.log('a, b, or c pressed'); 13 | }); 14 | kontra.onKey(['a', 'b', 'c'], () => { 15 | console.log('a, b, or c pressed'); 16 | }, { 17 | preventDefault: true, 18 | handler: 'keydown' 19 | }); 20 | kontra.onKey(['a', 'b', 'c'], () => { 21 | console.log('a, b, or c pressed'); 22 | }, { 23 | handler: 'keyup' 24 | }); 25 | 26 | 27 | kontra.offKey('a'); 28 | kontra.offKey(['a', 'b', 'c']); 29 | 30 | let pressed: boolean = kontra.keyPressed('a'); 31 | kontra.keyPressed(['a', 'b', 'c']); -------------------------------------------------------------------------------- /test/typings/pointer.ts: -------------------------------------------------------------------------------- 1 | import * as kontra from '../../kontra.js'; 2 | 3 | let pointer = kontra.initPointer(); 4 | 5 | let x: number = pointer.x; 6 | let y: number = pointer.y; 7 | let radius: number = pointer.radius; 8 | 9 | kontra.track({one: 1}); 10 | kontra.track({one: 1}, {two: 2}); 11 | kontra.track([{one: 1}, {two: 2}]); 12 | 13 | kontra.untrack({one: 1}); 14 | kontra.untrack({one: 1}, {two: 2}); 15 | kontra.untrack([{one: 1}, {two: 2}]); 16 | 17 | let over: boolean = kontra.pointerOver({one: 1}); 18 | 19 | kontra.onPointer('down', (evt: MouseEvent, object: object) => { 20 | let target = evt.target; 21 | if (object) { 22 | console.log('over!'); 23 | } 24 | }); 25 | kontra.onPointer('down', (evt: MouseEvent) => { 26 | let target = evt.target; 27 | console.log('no object'); 28 | }); 29 | kontra.onPointer('up', () => {}); 30 | 31 | kontra.offPointer('down'); 32 | kontra.offPointer('up'); -------------------------------------------------------------------------------- /test/typings/pool.ts: -------------------------------------------------------------------------------- 1 | import * as kontra from '../../kontra.js'; 2 | 3 | let pool: kontra.Pool = kontra.Pool({ 4 | create() { 5 | return kontra.Sprite(); 6 | } 7 | }); 8 | 9 | let objs: object[] = pool.objects; 10 | let size: number = pool.size; 11 | let maxSize: number = pool.maxSize; 12 | 13 | let object: object = pool.get(); 14 | let obj = pool.get({ 15 | x: 1, 16 | y: 2 17 | }); 18 | let aliveObjs: object[] = pool.getAliveObjects(); 19 | pool.clear(); 20 | pool.update(); 21 | pool.update(1/60); 22 | pool.render(); 23 | 24 | // options 25 | kontra.Pool({ 26 | create() { 27 | return kontra.Sprite(); 28 | }, 29 | maxSize: 2 30 | }); 31 | 32 | // custom create 33 | let customPool = kontra.Pool({ 34 | create() { 35 | return { 36 | init() {}, 37 | isAlive() { 38 | return true; 39 | }, 40 | update() {}, 41 | render() {} 42 | } 43 | } 44 | }); 45 | 46 | // custom create with options 47 | let customPoolOpts = kontra.Pool({ 48 | create() { 49 | return { 50 | init(properties) {}, 51 | isAlive() { 52 | return true; 53 | }, 54 | update(dt) {}, 55 | render() {} 56 | } 57 | } 58 | }); 59 | 60 | // kontra object create 61 | let spriteCreate = kontra.Pool({ 62 | create: kontra.Sprite 63 | }); 64 | 65 | // extends 66 | class CustomPool extends kontra.PoolClass {} 67 | let myPool = new CustomPool({ 68 | create() { 69 | return kontra.Sprite(); 70 | } 71 | }); 72 | myPool.clear(); -------------------------------------------------------------------------------- /test/typings/quadtree.ts: -------------------------------------------------------------------------------- 1 | import * as kontra from '../../kontra.js'; 2 | 3 | let quadtree: kontra.Quadtree = kontra.Quadtree(); 4 | 5 | let maxDepth: number = quadtree.maxDepth; 6 | let maxObjects: number = quadtree.maxObjects; 7 | let bounds: object = quadtree.bounds; 8 | 9 | quadtree.clear(); 10 | let objs: object[] = quadtree.get({x: 10, y: 20, width: 100, height: 100}); 11 | quadtree.get(kontra.Sprite()); 12 | 13 | let obj = { x: 1, y: 2, width: 3, height: 4 }; 14 | 15 | quadtree.add(obj); 16 | quadtree.add(obj, kontra.Sprite()); 17 | quadtree.add([obj, kontra.Sprite()]); 18 | 19 | // options 20 | kontra.Quadtree({ 21 | maxDepth: 1, 22 | maxObjects: 10, 23 | bounds: { 24 | x: 10, 25 | y: 20, 26 | width: 100, 27 | height: 100 28 | } 29 | }); 30 | 31 | // extends 32 | class CustomQuadtree extends kontra.QuadtreeClass {} 33 | let myQuadtree = new CustomQuadtree(); 34 | myQuadtree.get(kontra.Sprite()); -------------------------------------------------------------------------------- /test/typings/random.ts: -------------------------------------------------------------------------------- 1 | import * as kontra from '../../kontra.js'; 2 | 3 | let rand: number = kontra.rand(); 4 | let randInt: number = kontra.randInt(10, 20); 5 | 6 | function randFn(): number { 7 | return 12; 8 | } 9 | let randIntWithFunc: number = kontra.randInt(10, 20, randFn); 10 | 11 | let seed: number = kontra.getSeed(); 12 | 13 | kontra.seedRand('kontra'); 14 | kontra.seedRand(Date.now()); 15 | kontra.seedRand(123489719875); 16 | kontra.seedRand(); -------------------------------------------------------------------------------- /test/typings/scene.ts: -------------------------------------------------------------------------------- 1 | import * as kontra from '../../kontra.js'; 2 | 3 | let scene: kontra.Scene = kontra.Scene({ 4 | id: 'game' 5 | }); 6 | 7 | let id: string = scene.id; 8 | let name: string = scene.name; 9 | let hidden: boolean = scene.hidden; 10 | let context: CanvasRenderingContext2D = scene.context; 11 | let cullObjects: boolean = scene.cullObjects; 12 | let camera: kontra.GameObject = scene.camera; 13 | let node: HTMLElement = scene.node; 14 | 15 | scene.cullFunction(); 16 | scene.show(); 17 | scene.hide(); 18 | scene.destroy(); 19 | scene.update(); 20 | scene.update(1/60); 21 | scene.render(); 22 | scene.lookAt({x: 10, y: 20}); 23 | scene.onShow(); 24 | scene.onHide(); 25 | 26 | scene.add({x: 10, y: 20}); 27 | 28 | let sprite = kontra.Sprite(); 29 | scene.add(sprite); 30 | scene.add({x: 10, y: 20}, sprite); 31 | scene.add([{x: 10, y: 20}, sprite]); 32 | 33 | scene.remove({x: 10, y: 20}); 34 | scene.remove(sprite); 35 | scene.remove({x: 10, y: 20}, sprite); 36 | scene.remove([{x: 10, y: 20}, sprite]); 37 | 38 | // options 39 | kontra.Scene({ 40 | id: 'options', 41 | name: 'Options Menu', 42 | objects: [kontra.GameObject()], 43 | onShow() {}, 44 | onHide() {} 45 | }); 46 | 47 | // custom props 48 | kontra.Scene({ 49 | id: 'game' 50 | }); 51 | 52 | // extends 53 | class CustomScene extends kontra.SceneClass {} 54 | let myScene = new CustomScene({ 55 | id: 'game' 56 | }); 57 | myScene.lookAt({x: 10, y: 20}); -------------------------------------------------------------------------------- /test/typings/sprite.ts: -------------------------------------------------------------------------------- 1 | import * as kontra from '../../kontra.js'; 2 | 3 | // null 4 | let nullSprite: kontra.Sprite = kontra.Sprite(); 5 | 6 | // inheritance 7 | nullSprite.x += 20; 8 | nullSprite.rotation = Math.PI; 9 | nullSprite.advance(); 10 | 11 | // color 12 | let colorSprite = kontra.Sprite({ 13 | color: 'red', 14 | width: 20, 15 | height: 40, 16 | x: 20, 17 | y: 20 18 | }); 19 | 20 | // image 21 | let image = new Image(); 22 | let imageSprite = kontra.Sprite({ 23 | image 24 | }); 25 | 26 | imageSprite.update(); 27 | imageSprite.render(); 28 | 29 | // animation 30 | let spriteSheet = kontra.SpriteSheet({ 31 | image: kontra.imageAssets.character_walk_sheet, 32 | frameWidth: 72, 33 | frameHeight: 97, 34 | animations: { 35 | walk: { 36 | frames: '0..10', 37 | frameRate: 30 38 | } 39 | } 40 | }); 41 | 42 | let animSprite = kontra.Sprite({ 43 | width: 72 * 2, 44 | height: 97 * 2, 45 | anchor: { 46 | x: 0.5, 47 | y: 0.5, 48 | }, 49 | x: 300, 50 | y: 200, 51 | animations: spriteSheet.animations 52 | }); 53 | 54 | animSprite.playAnimation('walk'); 55 | let anims = animSprite.animations; 56 | let currAnim = animSprite.currentAnimation; 57 | 58 | // extends 59 | class CustomSprite extends kontra.SpriteClass { 60 | constructor(properties?: object) { 61 | super(properties); 62 | } 63 | } 64 | 65 | let customSprite = new CustomSprite({ 66 | x: 12, 67 | y: 10 68 | }); 69 | customSprite.advance(); 70 | 71 | // custom props 72 | let propSrpite = kontra.Sprite({ 73 | custom: 'foo' 74 | }); 75 | let prop = propSrpite.custom; -------------------------------------------------------------------------------- /test/typings/spriteSheet.ts: -------------------------------------------------------------------------------- 1 | import * as kontra from '../../kontra.js'; 2 | 3 | let spriteSheet: kontra.SpriteSheet = kontra.SpriteSheet({ 4 | image: new Image(), 5 | frameWidth: 32, 6 | frameHeight: 32 7 | }); 8 | 9 | let width: number = spriteSheet.frame.width; 10 | let height: number = spriteSheet.frame.height; 11 | let margin: number = spriteSheet.frame.margin; 12 | 13 | spriteSheet.createAnimations({ 14 | walk: { 15 | frames: 1, 16 | frameRate: 20 17 | } 18 | }); 19 | 20 | // options 21 | kontra.SpriteSheet({ 22 | image: new Image(), 23 | frameWidth: 32, 24 | frameHeight: 32, 25 | margin: 32, 26 | spacing: 2, 27 | animations: { 28 | walk: { 29 | frames: 1, 30 | frameRate: 20, 31 | loop: false 32 | } 33 | } 34 | }); 35 | 36 | // extends 37 | class CustomSpriteSheet extends kontra.SpriteSheetClass {} 38 | let mySpriteSheet = new CustomSpriteSheet({ 39 | image: new Image(), 40 | frameWidth: 32, 41 | frameHeight: 32 42 | }); 43 | mySpriteSheet.createAnimations({ 44 | walk: { 45 | frames: 1, 46 | frameRate: 20 47 | } 48 | }); -------------------------------------------------------------------------------- /test/typings/text.ts: -------------------------------------------------------------------------------- 1 | import * as kontra from '../../kontra.js'; 2 | 3 | let text: kontra.Text = kontra.Text({ 4 | text: 'Hello World!', 5 | color: 'black', 6 | font: '32px Arial' 7 | }); 8 | 9 | let str: string = text.text; 10 | let color: string = text.color; 11 | let font: string = text.font; 12 | let textAlign: string = text.textAlign; 13 | let width: number = text.width; 14 | let height: number = text.height; 15 | let lineHeight: number = text.lineHeight; 16 | let dir: string = text.dir; 17 | 18 | // inheritance 19 | text.x += 20; 20 | text.rotation = Math.PI; 21 | text.advance(); 22 | text.render(); 23 | 24 | // options 25 | kontra.Text({ 26 | x: 10, 27 | y: 20, 28 | text: 'Hello World!', 29 | color: 'black', 30 | font: '32px Arial', 31 | textAlign: 'right', 32 | width: 200, 33 | lineHeight: 3, 34 | lineWidth: 3, 35 | strokeColor: 'white' 36 | }); 37 | 38 | // extends 39 | class CustomText extends kontra.TextClass {} 40 | let myText = new CustomText({ 41 | text: 'Hello World!', 42 | color: 'black', 43 | font: '32px Arial' 44 | }); 45 | myText.advance(); -------------------------------------------------------------------------------- /test/typings/tileEngine.ts: -------------------------------------------------------------------------------- 1 | import * as kontra from '../../kontra.js'; 2 | 3 | let tileEngine: kontra.TileEngine = kontra.TileEngine({ 4 | tilewidth: 64, 5 | tileheight: 64, 6 | width: 9, 7 | height: 9, 8 | tilesets: [{ 9 | firstgid: 1, 10 | image: new Image() 11 | }], 12 | layers: [{ 13 | name: 'ground', 14 | data: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 15 | 0, 0, 6, 7, 7, 8, 0, 0, 0, 16 | 0, 6, 27, 24, 24, 25, 0, 0, 0, 17 | 0, 23, 24, 24, 24, 26, 8, 0, 0, 18 | 0, 23, 24, 24, 24, 24, 26, 8, 0, 19 | 0, 23, 24, 24, 24, 24, 24, 25, 0, 20 | 0, 40, 41, 41, 10, 24, 24, 25, 0, 21 | 0, 0, 0, 0, 40, 41, 41, 42, 0, 22 | 0, 0, 0, 0, 0, 0, 0, 0, 0 ] 23 | }] 24 | }); 25 | 26 | let width: number = tileEngine.width; 27 | let height: number = tileEngine.height; 28 | let tilewidth: number = tileEngine.tilewidth; 29 | let tileheight: number = tileEngine.tileheight; 30 | let layers: object[] = tileEngine.layers; 31 | let tilesets: object[] = tileEngine.tilesets; 32 | let context: CanvasRenderingContext2D = tileEngine.context; 33 | let mapwidth: number = tileEngine.mapwidth; 34 | let mapheight: number = tileEngine.mapheight; 35 | let sx: number = tileEngine.sx; 36 | let sy: number = tileEngine.sy; 37 | 38 | tileEngine.render(); 39 | tileEngine.renderLayer('ground'); 40 | let collides: boolean = tileEngine.layerCollidesWith('ground', {x: 10, y: 20, height: 100, width: 100}); 41 | let collidesGameObject: boolean = tileEngine.layerCollidesWith('ground', kontra.GameObject()); 42 | let tileX: number = tileEngine.tileAtLayer('ground', {x: 50, y: 50}); 43 | let tileRow: number = tileEngine.tileAtLayer('ground', {row: 5, col: 5}); 44 | tileEngine.setTileAtLayer('ground', {x: 50, y: 50}, 2); 45 | tileEngine.setTileAtLayer('ground', {row: 5, col: 5}, 2); 46 | tileEngine.setLayer('ground', [1,2,0,0,0,5,6]); 47 | tileEngine.add({}); 48 | tileEngine.add({}, {}); 49 | tileEngine.add([{}, {}]); 50 | tileEngine.add(kontra.GameObject()); 51 | tileEngine.remove({}); 52 | tileEngine.remove({}, {}); 53 | tileEngine.remove([{}, {}]); 54 | tileEngine.remove(kontra.GameObject()); 55 | const { x, y, row, col } = tileEngine.getPosition({x: 100, y: 200}) 56 | tileEngine.getPosition({x: 100, y: 200} as PointerEvent) 57 | tileEngine.setTileAtLayer('ground', { x, y }, 5); 58 | tileEngine.setTileAtLayer('ground', { row, col }, 5); 59 | 60 | // options 61 | kontra.TileEngine({ 62 | context: document.createElement('canvas').getContext('2d'), 63 | tilewidth: 64, 64 | tileheight: 64, 65 | width: 9, 66 | height: 9, 67 | tilesets: [{ 68 | firstgid: 1, 69 | image: new Image() 70 | }], 71 | layers: [{ 72 | name: 'ground', 73 | data: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 74 | 0, 0, 6, 7, 7, 8, 0, 0, 0, 75 | 0, 6, 27, 24, 24, 25, 0, 0, 0, 76 | 0, 23, 24, 24, 24, 26, 8, 0, 0, 77 | 0, 23, 24, 24, 24, 24, 26, 8, 0, 78 | 0, 23, 24, 24, 24, 24, 24, 25, 0, 79 | 0, 40, 41, 41, 10, 24, 24, 25, 0, 80 | 0, 0, 0, 0, 40, 41, 41, 42, 0, 81 | 0, 0, 0, 0, 0, 0, 0, 0, 0 ] 82 | }] 83 | }); -------------------------------------------------------------------------------- /test/typings/vector.ts: -------------------------------------------------------------------------------- 1 | import * as kontra from '../../kontra.js'; 2 | 3 | let vector: kontra.Vector = kontra.Vector(); 4 | 5 | let x: number = vector.x; 6 | let y: number = vector.y; 7 | 8 | vector.set({x: 10, y: 10}); 9 | let newVec: kontra.Vector = vector.add({x: 1, y: 2}); 10 | vector.add(newVec); 11 | vector.add({x: 10, y: 10}); 12 | let subtract: kontra.Vector = vector.subtract(newVec); 13 | vector.subtract({x: 10, y: 10}); 14 | let scale: kontra.Vector = vector.scale(5); 15 | let normalize: kontra.Vector = vector.normalize(); 16 | let dot: number = vector.dot(newVec); 17 | vector.dot({x: 10, y: 10}); 18 | let length: number = vector.length(); 19 | let distance: number = vector.distance(newVec); 20 | vector.distance({x: 10, y: 10}); 21 | let angle: number = vector.angle(newVec); 22 | vector.clamp(10, 20, 100, 100); 23 | 24 | // options 25 | kontra.Vector(10); 26 | kontra.Vector(10, 20); 27 | kontra.Vector({x: 10, y: 20}); 28 | 29 | // extends 30 | class CustomVector extends kontra.VectorClass {} 31 | let myVec = new CustomVector(); 32 | myVec.add(newVec); -------------------------------------------------------------------------------- /test/unit/random.spec.js: -------------------------------------------------------------------------------- 1 | import * as random from '../../src/random.js'; 2 | 3 | // -------------------------------------------------- 4 | // random 5 | // -------------------------------------------------- 6 | describe('random', () => { 7 | it('should export api', () => { 8 | expect(random.rand).to.be.an('function'); 9 | expect(random.randInt).to.be.an('function'); 10 | expect(random.getSeed).to.be.an('function'); 11 | expect(random.seedRand).to.be.an('function'); 12 | }); 13 | 14 | // -------------------------------------------------- 15 | // rand 16 | // -------------------------------------------------- 17 | describe('rand', () => { 18 | it('should get random value between 0 and <1', () => { 19 | expect(random.rand()).to.be.within(0, 1); 20 | }); 21 | 22 | it('should work if seed has not been seeded', () => { 23 | random._reset(); 24 | expect(random.rand).to.not.throw(); 25 | }); 26 | }); 27 | 28 | // -------------------------------------------------- 29 | // randInt 30 | // -------------------------------------------------- 31 | describe('randInt', () => { 32 | it('should get random integer between range', () => { 33 | expect(random.randInt(2, 10)).to.be.within(2, 10); 34 | }); 35 | }); 36 | 37 | // -------------------------------------------------- 38 | // seedRand 39 | // -------------------------------------------------- 40 | describe('seedRand', () => { 41 | function testSeededRandom() { 42 | expect(random.rand()).to.equal(0.26133555523119867); 43 | 44 | for (let i = 0; i < 20; i++) { 45 | random.rand(); 46 | } 47 | 48 | expect(random.rand()).to.equal(0.08834491996094584); 49 | } 50 | 51 | // all seed values = 2859059487 52 | it('should seed with value', () => { 53 | random.seedRand(2859059487); 54 | 55 | testSeededRandom(); 56 | }); 57 | 58 | it('should seed with string', () => { 59 | random.seedRand('kontra'); 60 | 61 | testSeededRandom(); 62 | }); 63 | 64 | it('should seed with time by default', () => { 65 | sinon.stub(Date, 'now').returns(2859059487); 66 | random.seedRand(); 67 | 68 | testSeededRandom(); 69 | }); 70 | }); 71 | 72 | // -------------------------------------------------- 73 | // getSeed 74 | // -------------------------------------------------- 75 | describe('getSeed', () => { 76 | it('should return the seed', () => { 77 | random.seedRand('kontra'); 78 | expect(random.getSeed()).to.equal(2859059487); 79 | }); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /test/unit/utils.spec.js: -------------------------------------------------------------------------------- 1 | import * as utils from '../../src/utils.js'; 2 | import { getCanvas } from '../../src/core.js'; 3 | 4 | // -------------------------------------------------- 5 | // utils 6 | // -------------------------------------------------- 7 | describe('utils', () => { 8 | let canvas; 9 | beforeEach(() => { 10 | canvas = getCanvas(); 11 | }); 12 | 13 | // -------------------------------------------------- 14 | // addToDom 15 | // -------------------------------------------------- 16 | describe('addToDom', () => { 17 | it('adds the node as a sibling to the canvas', () => { 18 | const node = document.createElement('div'); 19 | utils.addToDom(node, canvas); 20 | 21 | expect(canvas.nextSibling).to.equal(node); 22 | }); 23 | 24 | it('adds the node to the body if canvas is disconnected', () => { 25 | const node = document.createElement('div'); 26 | canvas.remove(); 27 | utils.addToDom(node, canvas); 28 | 29 | expect(node.parentNode).to.equal(document.body); 30 | }); 31 | 32 | it('adds the node to a container', () => { 33 | const div = document.createElement('div'); 34 | div.append(canvas); 35 | 36 | const node = document.createElement('div'); 37 | utils.addToDom(node, canvas); 38 | 39 | expect(div.contains(node)).to.be.true; 40 | }); 41 | 42 | it('adds `data-kontra` attribute', () => { 43 | const node = document.createElement('div'); 44 | utils.addToDom(node, canvas); 45 | 46 | expect(node.hasAttribute('data-kontra')).to.be.true; 47 | }); 48 | 49 | it('adds the node as the next sibling to the last `data-kontra` node', () => { 50 | const container = document.createElement('div'); 51 | container.append(canvas); 52 | 53 | const node1 = document.createElement('div'); 54 | utils.addToDom(node1, canvas); 55 | 56 | const div = document.createElement('div'); 57 | canvas.parentNode.append(div); 58 | 59 | const node2 = document.createElement('div'); 60 | utils.addToDom(node2, canvas); 61 | 62 | expect(node1.nextSibling).to.equal(node2); 63 | expect(canvas.parentNode.lastChild).to.equal(div); 64 | }); 65 | 66 | it('does not add the node as a child of an inner container with another `data-kontra` node', () => { 67 | const container = document.createElement('div'); 68 | container.append(canvas); 69 | 70 | const innerContainer = document.createElement('div'); 71 | container.append(innerContainer); 72 | 73 | const node1 = document.createElement('div'); 74 | node1.setAttribute('data-kontra', ''); 75 | innerContainer.appendChild(node1); 76 | 77 | const node2 = document.createElement('div'); 78 | utils.addToDom(node2, canvas); 79 | 80 | expect(canvas.nextSibling).to.equal(node2); 81 | }); 82 | }); 83 | 84 | // -------------------------------------------------- 85 | // removeFromArray 86 | // -------------------------------------------------- 87 | describe('removeFromArray', () => { 88 | it('removes item from array', () => { 89 | const array = [1, 2, 3, 4]; 90 | utils.removeFromArray(array, 3); 91 | 92 | expect(array).to.deep.equal([1, 2, 4]); 93 | }); 94 | 95 | it('returns true when item is removed', () => { 96 | const array = [1, 2, 3, 4]; 97 | const result = utils.removeFromArray(array, 3); 98 | 99 | expect(result).to.be.true; 100 | }); 101 | 102 | it('does not remove item if not found', () => { 103 | const array = [1, 2, 3, 4]; 104 | const result = utils.removeFromArray(array, 5); 105 | 106 | expect(array).to.deep.equal([1, 2, 3, 4]); 107 | expect(result).to.be.undefined; 108 | }); 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Simulate a keyboard event. 3 | * @param {string} type - Type of keyboard event. 4 | * @param {object} [config] - Additional settings for the event. 5 | * @param {HTMLElement} [node=window] - Node to dispatch the event on. 6 | */ 7 | export function simulateEvent(type, config = {}, node = window) { 8 | let evt = new Event(type); 9 | 10 | for (let prop in config) { 11 | evt[prop] = config[prop]; 12 | } 13 | 14 | if (config.async) { 15 | window.setTimeout(() => node.dispatchEvent(evt), 100); 16 | } else { 17 | node.dispatchEvent(evt); 18 | } 19 | 20 | return evt; 21 | } 22 | 23 | /** 24 | * Simulate a gamepad event. 25 | * @param {string} type - Type of gamepad event. 26 | * @param {object} gamepad - Gamepad object. 27 | * @param {number} gamepad.index - Index of the gamepad 28 | * @param {Object[]} [gamepad.buttons] - GamepadButtons and their state 29 | * @param {Number[]} [gamepad.axes] - Gamepad axes values 30 | * 31 | */ 32 | export function simulateGamepadEvent(type, gamepad) { 33 | let evt = new GamepadEvent(type); 34 | 35 | // evt.gamepad is read-only so we need to override it 36 | Object.defineProperty(evt, 'gamepad', { 37 | value: { 38 | buttons: [], 39 | axes: [], 40 | ...gamepad 41 | } 42 | }); 43 | 44 | window.dispatchEvent(evt); 45 | 46 | return evt; 47 | } 48 | 49 | export let getGamepadsStub = []; 50 | export function createGamepad(index = getGamepadsStub.length) { 51 | let gamepadObj = { 52 | index, 53 | connected: true, 54 | buttons: [], 55 | axes: [0, 0, 0, 0] 56 | }; 57 | for (let i = 0; i < 16; i++) { 58 | gamepadObj.buttons[i] = { pressed: false }; 59 | } 60 | 61 | simulateGamepadEvent('gamepadconnected', gamepadObj); 62 | getGamepadsStub[index] = gamepadObj; 63 | } 64 | --------------------------------------------------------------------------------