├── .gitignore
├── FAQ.md
├── LICENSE
├── README.md
├── dist
├── littlejs.d.ts
├── littlejs.esm.js
├── littlejs.esm.min.js
├── littlejs.js
├── littlejs.min.js
└── littlejs.release.js
├── docs
├── Audio.html
├── Color.html
├── Debug.html
├── Draw.html
├── Engine.html
├── EngineObject.html
├── FontImage.html
├── Input.html
├── Medal.html
├── Medals.html
├── Music.html
├── Newgrounds.html
├── Particle.html
├── ParticleEmitter.html
├── Random.html
├── RandomGenerator.html
├── Settings.html
├── Sound.html
├── SoundWave.html
├── TextureInfo.html
├── TileCollision.html
├── TileInfo.html
├── TileLayer.html
├── TileLayerData.html
├── Timer.html
├── Utilities.html
├── Vector2.html
├── WebGL.html
├── data
│ └── search.json
├── engine.js.html
├── engineAudio.js.html
├── engineDebug.js.html
├── engineDraw.js.html
├── engineInput.js.html
├── engineMedals.js.html
├── engineObject.js.html
├── engineParticles.js.html
├── engineSettings.js.html
├── engineTileLayer.js.html
├── engineUtilities.js.html
├── engineWebGL.js.html
├── examples
│ ├── favicon.png
│ ├── logo.png
│ └── screenshot.jpg
├── fonts
│ ├── Inconsolata-Regular.ttf
│ ├── OpenSans-Regular.ttf
│ └── WorkSans-Bold.ttf
├── index.html
├── scripts
│ ├── core.js
│ ├── core.min.js
│ ├── resize.js
│ ├── search.js
│ ├── search.min.js
│ └── third-party
│ │ ├── Apache-License-2.0.txt
│ │ ├── fuse.js
│ │ ├── hljs-line-num-original.js
│ │ ├── hljs-line-num.js
│ │ ├── hljs-original.js
│ │ ├── hljs.js
│ │ ├── popper.js
│ │ ├── tippy.js
│ │ ├── tocbot.js
│ │ └── tocbot.min.js
├── static
│ └── favicon.png
└── styles
│ ├── clean-jsdoc-theme-base.css
│ ├── clean-jsdoc-theme-dark.css
│ ├── clean-jsdoc-theme-light.css
│ ├── clean-jsdoc-theme-scrollbar.css
│ ├── clean-jsdoc-theme-without-scrollbar.min.css
│ └── clean-jsdoc-theme.min.css
├── examples
├── box2d
│ ├── game.js
│ ├── gameObjects.js
│ ├── index.html
│ ├── scenes.js
│ └── tiles.png
├── breakout
│ ├── game.js
│ ├── gameObjects.js
│ ├── index.html
│ └── tiles.png
├── breakoutTutorial
│ ├── README.md
│ ├── game.js
│ ├── images
│ │ ├── 1.png
│ │ ├── 10.png
│ │ ├── 11.png
│ │ ├── 2.png
│ │ ├── 3.png
│ │ ├── 4.png
│ │ ├── 5.png
│ │ ├── 6.png
│ │ ├── 7.png
│ │ ├── 8.png
│ │ └── 9.png
│ └── index.html
├── empty
│ ├── game.js
│ ├── index.html
│ └── tiles.png
├── favicon.png
├── htmlMenu
│ ├── game.js
│ ├── index.html
│ └── tiles.png
├── index.html
├── logo.png
├── module
│ ├── game.js
│ ├── index.html
│ └── tiles.png
├── particles
│ ├── index.html
│ └── tiles.png
├── platformer
│ ├── data
│ │ ├── gameLevelData.tmx
│ │ └── gameLevelData.tsx
│ ├── game.js
│ ├── gameCharacter.js
│ ├── gameEffects.js
│ ├── gameLevel.js
│ ├── gameLevelData.js
│ ├── gameObjects.js
│ ├── gamePlayer.js
│ ├── index.html
│ ├── tiles.png
│ └── tilesLevel.png
├── puzzle
│ ├── game.js
│ ├── index.html
│ └── tiles.png
├── screenshot.jpg
├── shorts
│ ├── animation.js
│ ├── base.html
│ ├── blending.js
│ ├── clock.js
│ ├── colors.js
│ ├── helloWorld.js
│ ├── particles.js
│ ├── platformer.js
│ ├── playSound.js
│ ├── pong.js
│ ├── shapes.js
│ ├── spriteAtlas.js
│ ├── systemFont.js
│ ├── texture.js
│ ├── tileLayer.js
│ ├── tiles.png
│ ├── tiltedView.js
│ ├── timers.js
│ └── topDown.js
├── starter
│ ├── build.bat
│ ├── build.js
│ ├── game.js
│ ├── index.html
│ └── tiles.png
├── stress
│ └── index.html
└── uiSystem
│ ├── game.js
│ ├── index.html
│ └── tiles.png
├── jsconfig.json
├── package-lock.json
├── package.json
├── plugins
├── Box2D_License.txt
├── Box2D_v2.3.1_min.wasm.js
├── Box2D_v2.3.1_min.wasm.wasm
├── box2d.js
├── newgrounds.js
├── postProcess.js
└── uiSystem.js
├── reference.md
└── src
├── engine.js
├── engineAudio.js
├── engineBuild.bat
├── engineBuild.js
├── engineDebug.js
├── engineDraw.js
├── engineExport.js
├── engineFont.png
├── engineInput.js
├── engineMedals.js
├── engineObject.js
├── engineParticles.js
├── engineRelease.js
├── engineSettings.js
├── engineTileLayer.js
├── engineUtilities.js
├── engineWebGL.js
└── jsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # node files
2 | node_modules
3 |
4 | # example builds
5 | examples/starter/build
6 | examples/starter/*.zip
7 | examples/js13k/build
8 | examples/js13k/*.zip
9 | examples/typescript/build
10 | examples/electron/build
11 | examples/electron/LittleJSGame-win32-x64
12 | examples/electron/package-lock.json
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Frank Force
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 | -------------------------------------------------------------------------------
24 |
25 | This project uses the Box2D physics engine, which is licensed under the zlib license.
26 | Copyright (c) 2006 Erin Catto (Box2D)
27 | Copyright (c) 2011 The box2d.js contributors
28 |
29 | This software is provided 'as-is', without any express or implied
30 | warranty. In no event will the authors be held liable for any damages
31 | arising from the use of this software.
32 |
33 | Permission is granted to anyone to use this software for any purpose,
34 | including commercial applications, and to alter it and redistribute it
35 | freely, subject to the following restrictions:
36 |
37 | 1. The origin of this software must not be misrepresented; you must not
38 | claim that you wrote the original software. If you use this software
39 | in a product, an acknowledgment in the product documentation would be
40 | appreciated but is not required.
41 | 2. Altered source versions must be plainly marked as such, and must not be
42 | misrepresented as being the original software.
43 | 3. This notice may not be removed or altered from any source distribution.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LittleJS - The Tiny Fast JavaScript Game Engine
2 |
3 |
4 |
5 | 
6 |
7 | [![NPM Package][npm]][npm-url]
8 | [![Build Size][build-size]][build-size-url]
9 | [![NPM Downloads][npm-downloads]][npmtrends-url]
10 | [![DeepScan][deepscan]][deepscan-url]
11 | [![Discord][discord]][discord-url]
12 |
13 |
14 |
15 | ## 🚂 All aboard!
16 |
17 | LittleJS is a fast, lightweight, and fully open source HTML5 game engine designed for simplicity and performance.
18 | Its small footprint is packed with a comprehensive feature set including hybrid rendering, physics, particles, sound, and input handling.
19 | The code is clean and well documented with some fun examples to get you started right away. Choo-Choo!
20 |
21 | ### 🚀 Join the LittleJS Game Jam
22 |
23 | *The Second Annual LittleJS Game Jam will take place From Oct 3 to Nov 3! Unleash your creativity and develop amazing games using the LittleJS game engine. 🕹️🎮 [Sign up today and get more info about the jam on itch.io!](https://itch.io/jam/littlejs-game-jam-2025)*
24 |
25 |
26 |
27 | ## [Demo](https://killedbyapixel.github.io/LittleJS/examples/starter/) | [Docs](https://killedbyapixel.github.io/LittleJS/docs) | [Trailer](https://youtu.be/chuBzGjv7Ms) | [Discord](https://discord.gg/zb7hcGkyZe) | [Tutorial](https://github.com/KilledByAPixel/LittleJS/blob/main/examples/breakoutTutorial/README.md) | [FAQ](https://github.com/KilledByAPixel/LittleJS/blob/main/FAQ.md)
28 |
29 |
30 |
31 | 
32 |
33 | ## About LittleJS Engine
34 |
35 | LittleJS is a small but powerful game engine with many features and no dependencies.
36 |
37 | ### ✨ Graphics
38 |
39 | - Super fast sprite and tile map rendering engine with WebGL2
40 | - Update and render 100,000+ sprites at a solid 60fps
41 | - Apply [Shadertoy](https://www.shadertoy.com) compatible shaders for post-processing effects
42 | - Robust particle effect system and [effect design tool](https://killedbyapixel.github.io/LittleJS/examples/particles/)
43 |
44 | ### 🔊 Audio
45 |
46 | - Positional sound effects with wave files, mp3s, or ZzFX
47 | - Use [ZzFX](https://killedbyapixel.github.io/ZzFX/) sound effect generator to play sounds without asset files
48 | - Music with mp3, ogg, wave, or [ZzFXM](https://keithclark.github.io/ZzFXM/)
49 |
50 | ### 🎮 Input
51 |
52 | - Comprehensive input handling for keyboard, mouse, gamepad, and touch
53 | - On screen touch gamepad designed for mobile devices
54 |
55 | ### 💥 Physics
56 |
57 | - Robust arcade physics system with collision handling
58 | - Very fast collision and raycasting for tile maps
59 | - Full Box2d support using [super fast wasm build of box2d.js](https://github.com/kripken/box2d.js/)
60 |
61 | ### 🚀 Flexibility
62 |
63 | - Compatible with all modern web browsers and on mobile devices
64 | - Support for TypeScript and Modules with example projects for both
65 | - Ideal for size coding competitions like [js13kGames](https://js13kgames.com/), [starter project builds to a 7KB zip](https://github.com/KilledByAPixel/LittleJS/tree/js13k)
66 | - Open Source with the [MIT license](https://github.com/KilledByAPixel/LittleJS/blob/main/LICENSE) so it can be used for anything you want forever
67 |
68 | ### 🛠️ And more...
69 |
70 | - Node.js build system
71 | - 2D vector math library
72 | - Debug primitive rendering system
73 | - Bitmap font rendering and built in engine font
74 | - Medal tracking system with [Newgrounds](https://www.newgrounds.com/) support
75 |
76 | ## How To Use LittleJS
77 |
78 | To get started download the latest LittleJS package from GitHub or install via npm: ```npm install littlejsengine```
79 |
80 | *You need to run a local web server to run LittleJS games during development!* You may see a console error like "The image element contains cross-origin data." Don't panic, it's easy to fix! If you are using [Visual Studio Code](https://code.visualstudio.com/) there is a [Live Preview Extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.live-server) that will handle this for you automatically. Another option is to setup a simple local web server like [http-server](https://www.npmjs.com/package/http-server) via npm.
81 |
82 |
83 | - [Watch this GitNation talk](https://youtu.be/_dXKU0WgAj8?si=ZDXLYAFDWp54hrGT) to hear more about LittleJS works and get some tips on how to use it.
84 | - Learn how to make a simple game from scratch with [The Breakout Tutorial.](https://github.com/KilledByAPixel/LittleJS/tree/main/examples/breakoutTutorial)
85 | - [Make a ski game with LittleJS](https://eoinmcgrath.com/little-ski/tutorial.html) - Check out this tutorial by eoinmcg that shows how to make a pixel art style game.
86 | - [LittleJS Engine Quick Reference Sheet](https://github.com/KilledByAPixel/LittleJS/blob/main/reference.md) - This cheat sheet can help you get started.
87 | - [Check out The Little JS FAQ for more help getting started](https://github.com/KilledByAPixel/LittleJS/blob/main/FAQ.md).
88 | - Join our vibrant community on [Discord](https://discord.gg/zb7hcGkyZe) to get help, share your projects, and collaborate with others!
89 |
90 | ## Examples
91 |
92 | These demos are for both learning and using as starter projects to create your own games.
93 |
94 | - [Starter Project](https://killedbyapixel.github.io/LittleJS/examples/starter/) - Clean example with only a few things to get you started
95 | - [Breakout](https://killedbyapixel.github.io/LittleJS/examples/breakout/) - Block breaking game with post-processing effects
96 | - [Puzzle Game](https://killedbyapixel.github.io/LittleJS/examples/puzzle/) - Match 3 puzzle game with HD rendering and high score tracking
97 | - [Platformer](https://killedbyapixel.github.io/LittleJS/examples/platformer/) - Platformer/shooter with level data from [Tiled Editor](https://github.com/mapeditor/tiled)
98 | - [Box2D Demo](https://killedbyapixel.github.io/LittleJS/examples/box2d/) - Box2D plugin demonstration and testbed
99 | - [Stress Test](https://killedbyapixel.github.io/LittleJS/examples/stress/) - Max sprite/object test and music system demo
100 | - [Particle System Designer](https://killedbyapixel.github.io/LittleJS/examples/particles/) - Particle system editor and visualizer
101 | - [Example Browser](https://killedbyapixel.github.io/LittleJS/examples/) - Live example browser with all examples
102 |
103 | ## Builds
104 |
105 | To easily include LittleJS in your game, you can use one of the pre-built js files.
106 |
107 | - [littlejs.js](https://github.com/KilledByAPixel/LittleJS/blob/main/dist/littlejs.js) - The full game engine with debug mode available
108 | - [littlejs.release.js](https://github.com/KilledByAPixel/LittleJS/blob/main/dist/littlejs.release.js) - The engine optimized for release builds
109 | - [littlejs.min.js](https://github.com/KilledByAPixel/LittleJS/blob/main/dist/littlejs.min.js) - The engine in release mode and minified
110 | - [littlejs.esm.js](https://github.com/KilledByAPixel/LittleJS/blob/main/dist/littlejs.esm.js) - The engine exported as a module with debug mode available
111 | - [littlejs.esm.min.js](https://github.com/KilledByAPixel/LittleJS/blob/main/dist/littlejs.esm.min.js) - The engine exported as a minified module in release mode
112 |
113 | To rebuild the engine you must first run ```npm install``` to setup the necessary npm dependencies. Then call ```npm run build``` to build the engine.
114 |
115 | The starter example project includes a node js file [build.js](https://github.com/KilledByAPixel/LittleJS/blob/main/examples/starter/build.js) that compresses everything into a tiny zip file using Google Closure, UglifyJS, and ECT Zip.
116 |
117 | ## Games Made With LittleJS
118 |
119 | Here are a few of the many amazing games created with LittleJS...
120 |
121 | - [Space Huggers](https://www.newgrounds.com/portal/view/819609) - Rogulike platformer shoot-em-up game with procedural levels. by [KilledByAPixel](https://frankforce.com/)
122 | - [Undergrowth](https://undergrowth.squidband.uk/) - An interactive music videogame for the band Squid. by [KilledByAPixel](https://frankforce.com/)
123 | - [The Way of the Dodo](https://js13kgames.com/2024/games/the-way-of-the-dodo) - Single button platformer. JS13k 5th place winner! by [repsej](https://github.com/repsej)
124 | - [204Snake!](https://www.newgrounds.com/portal/view/960100) - A puzzle game that combines 2048 with snake. LittleJS Jam 1st place winner! by [Sodoj](https://sodoj.itch.io/) and [Shai-P](https://shai-p.itch.io/)
125 | - [GATOR](https://www.newgrounds.com/portal/view/960757) - Retro platformer shooter game where you rescue animals. LittleJS Jam 2nd place winner! by [eoinmcg](https://eoinmcg.itch.io/)
126 | - [A Hedgehog's search](https://willsm1111.itch.io/a-hedgehogs-search) - Adventure game staring a hedgehog. LittleJS Jam 3rd place winner! by [willsm1111](https://willsm1111.itch.io/)
127 | - [Wendol Village](https://js13kgames.com/2024/games/wendol-village) - Warcraft inspired RTS game. by [sanojian](https://github.com/sanojian)
128 | - [Dead Again](https://js13kgames.com/entries/dead-again) - Top down survial horror. by [sanojian & repsej](https://github.com/sanojian/js13k_2022)
129 | - [Isletopia](https://store.steampowered.com/app/1861260/Isletopia) - Relaxing strategy game of greenifying barren islands. by [Gamex Studio](https://x.com/gamesgamex)
130 | - [Tetrimals](https://nixn.itch.io/tetrimals) - A puzzle game mixing Tetris with animals. by [nixn](https://nixn.itch.io/)
131 | - [Watch the Pups](https://ma5a.itch.io/watch-the-pups) - The aim of the game is to take care of some puppies. by [masa](https://ma5a.itch.io/)
132 | - [LittleJS Game Jam Results](https://itch.io/jam/littlejs-game-jam/results) - Check out all the games from the first LittleJS Game Jam!
133 |
134 | 
135 |
136 | [npm]: https://img.shields.io/npm/v/littlejsengine
137 | [npm-url]: https://www.npmjs.com/package/littlejsengine
138 | [build-size]: https://img.shields.io/bundlephobia/minzip/littlejsengine
139 | [build-size-url]: https://bundlephobia.com/result?p=littlejsengine
140 | [npm-downloads]: https://img.shields.io/npm/dw/littlejsengine
141 | [npmtrends-url]: https://www.npmtrends.com/littlejsengine
142 | [deepscan]: https://deepscan.io/api/teams/22950/projects/26229/branches/831487/badge/grade.svg
143 | [deepscan-url]: https://deepscan.io/dashboard#view=project&tid=22950&pid=26229&bid=831487
144 | [discord]: https://img.shields.io/discord/939926111469568050
145 | [discord-url]: https://discord.gg/zb7hcGkyZe
146 |
--------------------------------------------------------------------------------
/docs/examples/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/docs/examples/favicon.png
--------------------------------------------------------------------------------
/docs/examples/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/docs/examples/logo.png
--------------------------------------------------------------------------------
/docs/examples/screenshot.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/docs/examples/screenshot.jpg
--------------------------------------------------------------------------------
/docs/fonts/Inconsolata-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/docs/fonts/Inconsolata-Regular.ttf
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/docs/fonts/OpenSans-Regular.ttf
--------------------------------------------------------------------------------
/docs/fonts/WorkSans-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/docs/fonts/WorkSans-Bold.ttf
--------------------------------------------------------------------------------
/docs/scripts/core.min.js:
--------------------------------------------------------------------------------
1 | var accordionLocalStorageKey="accordion-id",themeLocalStorageKey="theme",fontSizeLocalStorageKey="font-size",html=document.querySelector("html"),MAX_FONT_SIZE=30,MIN_FONT_SIZE=10,localStorage=window.localStorage;function getTheme(){var e=localStorage.getItem(themeLocalStorageKey);if(e)return e;switch(e=document.body.getAttribute("data-theme")){case"dark":case"light":return e;case"fallback-dark":return window.matchMedia("(prefers-color-scheme)").matches&&window.matchMedia("(prefers-color-scheme: light)").matches?"light":"dark";case"fallback-light":return window.matchMedia("(prefers-color-scheme)").matches&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light";default:return"dark"}}function localUpdateTheme(e){var t=document.body,o=document.querySelectorAll(".theme-svg-use"),n="dark"===e?"#light-theme-icon":"#dark-theme-icon";t.setAttribute("data-theme",e),t.classList.remove("dark","light"),t.classList.add(e),o.forEach(function(e){e.setAttribute("xlink:href",n)})}function updateTheme(e){localUpdateTheme(e),localStorage.setItem(themeLocalStorageKey,e)}function toggleTheme(){updateTheme("dark"===document.body.getAttribute("data-theme")?"light":"dark")}function setAccordionIdToLocalStorage(e){var t=JSON.parse(localStorage.getItem(accordionLocalStorageKey));t[e]=e,localStorage.setItem(accordionLocalStorageKey,JSON.stringify(t))}function removeAccordionIdFromLocalStorage(e){var t=JSON.parse(localStorage.getItem(accordionLocalStorageKey));delete t[e],localStorage.setItem(accordionLocalStorageKey,JSON.stringify(t))}function getAccordionIdsFromLocalStorage(){return JSON.parse(localStorage.getItem(accordionLocalStorageKey))||{}}function toggleAccordion(e){"false"===e.getAttribute("data-isopen")?(e.setAttribute("data-isopen","true"),setAccordionIdToLocalStorage(e.id)):(e.setAttribute("data-isopen","false"),removeAccordionIdFromLocalStorage(e.id))}function initAccordion(){void 0!==localStorage.getItem(accordionLocalStorageKey)&&null!==localStorage.getItem(accordionLocalStorageKey)||localStorage.setItem(accordionLocalStorageKey,"{}");var e=document.querySelectorAll(".sidebar-section-title"),t=getAccordionIdsFromLocalStorage();e.forEach(function(e){e.addEventListener("click",function(){toggleAccordion(e)}),e.id in t&&toggleAccordion(e)})}function isSourcePage(){return Boolean(document.querySelector("#source-page"))}function bringElementIntoView(e,t=!0){var o,n,i,c;e&&(tocbotInstance&&setTimeout(()=>tocbotInstance.updateTocListActiveElement(e),60),o=document.querySelector(".navbar-container"),n=document.querySelector(".main-content"),i=e.getBoundingClientRect().top,c=16,o&&(c+=o.scrollHeight),n&&n.scrollBy(0,i-c),t&&history.pushState(null,null,"#"+e.id))}function bringLinkToView(e){e.preventDefault(),e.stopPropagation();var e=e.currentTarget.getAttribute("href");!e||(e=document.getElementById(e.slice(1)))&&bringElementIntoView(e)}function bringIdToViewOnMount(){var e,t;isSourcePage()||""!==(e=window.location.hash)&&((t=document.getElementById(e.slice(1)))||(e=decodeURI(e),t=document.getElementById(e.slice(1))),t&&bringElementIntoView(t,!1))}function createAnchorElement(e){var t=document.createElement("a");return t.textContent="#",t.href="#"+e,t.classList.add("link-anchor"),t.onclick=bringLinkToView,t}function addAnchor(){var e=document.querySelector(".main-content").querySelector("section");[e.querySelectorAll("h1"),e.querySelectorAll("h2"),e.querySelectorAll("h3"),e.querySelectorAll("h4")].forEach(function(e){e.forEach(function(e){var t=createAnchorElement(e.id);e.classList.add("has-anchor"),e.append(t)})})}function copy(e){const t=document.createElement("textarea");t.value=e,document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t)}function showTooltip(e){var t=document.getElementById(e);t.classList.add("show-tooltip"),setTimeout(function(){t.classList.remove("show-tooltip")},3e3)}function copyFunction(e){var t=document.getElementById(e);copy((t.querySelector(".linenums")||t.querySelector("code")).innerText.trim().replace(/(^\t)/gm,"")),showTooltip("tooltip-"+e)}function hideTocOnSourcePage(){isSourcePage()&&(document.querySelector(".toc-container").style.display="none")}function getPreTopBar(e,t=""){e=' '+('Copied!
')+" ";return''+('
'+t.toLocaleUpperCase()+"
")+e+"
"}function getPreDiv(){var e=document.createElement("div");return e.classList.add("pre-div"),e}function processAllPre(){var e=document.querySelectorAll("pre"),t=document.querySelector("#PeOAagUepe"),o=document.querySelector("#VuAckcnZhf"),n=0,i=0,c=(t&&(i=t.getBoundingClientRect().height),o&&(n=o.getBoundingClientRect().height),window.innerHeight-n-i-250);e.forEach(function(e,t){var o,n=e.parentNode;n&&"true"===n.getAttribute("data-skip-pre-process")||(n=getPreDiv(),o=getPreTopBar(t="ScDloZOMdL"+t,e.getAttribute("data-lang")||"code"),n.innerHTML=o,e.style.maxHeight=c+"px",e.id=t,e.classList.add("prettyprint"),e.parentNode.insertBefore(n,e),n.appendChild(e))})}function highlightAndBringLineIntoView(){var e=window.location.hash.replace("#line","");try{var t='[data-line-number="'+e+'"',o=document.querySelector(t);o.scrollIntoView(),o.parentNode.classList.add("selected")}catch(e){console.error(e)}}function getFontSize(){var e=16;try{e=Number.parseInt(html.style.fontSize.split("px")[0],10)}catch(e){console.log(e)}return e}function localUpdateFontSize(e){html.style.fontSize=e+"px";var t=document.querySelector("#b77a68a492f343baabea06fad81f651e");t&&(t.innerHTML=e)}function updateFontSize(e){localUpdateFontSize(e),localStorage.setItem(fontSizeLocalStorageKey,e)}function incrementFont(e){var t=getFontSize();t
3 |
4 |
5 |
6 |
7 |
8 |
9 | ${e}
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | `}function initTooltip(){tippy(".theme-toggle",{content:"Toggle Theme",delay:500}),tippy(".search-button",{content:"Search",delay:500}),tippy(".font-size",{content:"Change font size",delay:500}),tippy(".codepen-button",{content:"Open code in CodePen",placement:"left"}),tippy(".copy-code",{content:"Copy this code",placement:"left"}),tippy(".font-size",{content:fontSizeTooltip(),trigger:"click",interactive:!0,allowHTML:!0,placement:"left"})}function fixTable(){for(const t of document.querySelectorAll("table")){if(t.classList.contains("hljs-ln"))return;var e=document.createElement("div");e.classList.add("table-div"),t.parentNode.insertBefore(e,t),e.appendChild(t)}}function hideMobileMenu(){var e=document.querySelector("#mobile-sidebar"),t=document.querySelector("#mobile-menu"),o=t.querySelector("use");e&&e.classList.remove("show"),t&&t.setAttribute("data-isopen","false"),o&&o.setAttribute("xlink:href","#menu-icon")}function showMobileMenu(){var e=document.querySelector("#mobile-sidebar"),t=document.querySelector("#mobile-menu"),o=t.querySelector("use");e&&e.classList.add("show"),t&&t.setAttribute("data-isopen","true"),o&&o.setAttribute("xlink:href","#close-icon")}function onMobileMenuClick(){("true"===document.querySelector("#mobile-menu").getAttribute("data-isopen")?hideMobileMenu:showMobileMenu)()}function initMobileMenu(){var e=document.querySelector("#mobile-menu");e&&e.addEventListener("click",onMobileMenuClick)}function addHrefToSidebarTitle(){document.querySelectorAll(".sidebar-title-anchor").forEach(function(e){e.setAttribute("href",baseURL)})}function highlightActiveLinkInSidebar(){var e=document.location.href.split("/");const t=e[e.length-1];let o=document.querySelector(`.sidebar a[href*='${t}']`);if(!o)try{o=document.querySelector(`.sidebar a[href*='${t.split("#")[0]}']`)}catch(e){return void console.error(e)}o&&(o.parentElement.classList.add("active"),o.scrollIntoView())}function onDomContentLoaded(){var e=document.querySelectorAll(".theme-toggle");initMobileMenu(),e&&e.forEach(function(e){e.addEventListener("click",toggleTheme)}),hljs.addPlugin({"after:highlightElement":function(e){e.el.parentNode.setAttribute("data-lang","code")}}),hljs.highlightAll(),hljs.initLineNumbersOnLoad({singleLine:!0}),initAccordion(),addAnchor(),processAllPre(),hideTocOnSourcePage(),setTimeout(function(){bringIdToViewOnMount(),isSourcePage()&&highlightAndBringLineIntoView()},1e3),initTooltip(),fixTable(),addHrefToSidebarTitle(),highlightActiveLinkInSidebar()}updateTheme(getTheme()),function(){var e=getFontSize(),t=localStorage.getItem(fontSizeLocalStorageKey);t?(t=Number.parseInt(t,10))!==e&&updateFontSize(t):updateFontSize(e)}(),window.addEventListener("DOMContentLoaded",onDomContentLoaded),window.addEventListener("hashchange",e=>{e=new URL(e.newURL);""!==e.hash&&bringIdToViewOnMount(e.hash)}),window.addEventListener("storage",e=>{"undefined"!==e.newValue&&(initTooltip(),e.key===themeLocalStorageKey&&localUpdateTheme(e.newValue),e.key===fontSizeLocalStorageKey&&localUpdateFontSize(e.newValue))});
--------------------------------------------------------------------------------
/docs/scripts/resize.js:
--------------------------------------------------------------------------------
1 | /* global document */
2 | // This file is @deprecated
3 |
4 | var NAVBAR_OPTIONS = {};
5 |
6 | (function() {
7 | var NAVBAR_RESIZE_LOCAL_STORAGE_KEY = 'NAVBAR_RESIZE_LOCAL_STORAGE_KEY';
8 |
9 | var navbar = document.querySelector('#navbar');
10 | var footer = document.querySelector('#footer');
11 | var mainSection = document.querySelector('#main');
12 | var localStorageResizeObject = JSON.parse(
13 | // eslint-disable-next-line no-undef
14 | localStorage.getItem(NAVBAR_RESIZE_LOCAL_STORAGE_KEY)
15 | );
16 |
17 | /**
18 | * Check whether we have any resize value in local storage or not.
19 | * If we have resize value then resize the navbar.
20 | **/
21 | if (localStorageResizeObject) {
22 | navbar.style.width = localStorageResizeObject.width;
23 | mainSection.style.marginLeft = localStorageResizeObject.width;
24 | footer.style.marginLeft = localStorageResizeObject.width;
25 | }
26 |
27 | var navbarSlider = document.querySelector('#navbar-resize');
28 |
29 | function resizeNavbar(event) {
30 | var pageX = event.pageX,
31 | pageXPlusPx = event.pageX + 'px',
32 | min = Number.parseInt(NAVBAR_OPTIONS.min, 10) || 300,
33 | max = Number.parseInt(NAVBAR_OPTIONS.max, 10) || 600;
34 |
35 | /**
36 | * Just to add some checks. If min is smaller than 10 then
37 | * user may accidentally end up reducing the size of navbar
38 | * less than 10. In that case user will not able to resize navbar
39 | * because navbar slider will be hidden.
40 | */
41 | if (min < 10) {
42 | min = 10;
43 | }
44 |
45 | /**
46 | * Only resize if pageX in range between min and max
47 | * allowed value.
48 | */
49 | if (min < pageX && pageX < max) {
50 | navbar.style.width = pageXPlusPx;
51 | mainSection.style.marginLeft = pageXPlusPx;
52 | footer.style.marginLeft = pageXPlusPx;
53 | }
54 | }
55 |
56 | function setupEventListeners() {
57 | // eslint-disable-next-line no-undef
58 | window.addEventListener('mousemove', resizeNavbar);
59 | // eslint-disable-next-line no-undef
60 | window.addEventListener('touchmove', resizeNavbar);
61 | }
62 |
63 | function afterRemovingEventListeners() {
64 | // eslint-disable-next-line no-undef
65 | localStorage.setItem(
66 | NAVBAR_RESIZE_LOCAL_STORAGE_KEY,
67 | JSON.stringify({
68 | width: navbar.style.width
69 | })
70 | );
71 | }
72 |
73 | function removeEventListeners() {
74 | // eslint-disable-next-line no-undef
75 | window.removeEventListener('mousemove', resizeNavbar);
76 | // eslint-disable-next-line no-undef
77 | window.removeEventListener('touchend', resizeNavbar);
78 | afterRemovingEventListeners();
79 | }
80 |
81 | navbarSlider.addEventListener('mousedown', setupEventListeners);
82 | navbarSlider.addEventListener('touchstart', setupEventListeners);
83 | // eslint-disable-next-line no-undef
84 | window.addEventListener('mouseup', removeEventListeners);
85 | })();
86 |
87 | // eslint-disable-next-line no-unused-vars
88 | function setupResizeOptions(options) {
89 | NAVBAR_OPTIONS = options;
90 | }
91 |
--------------------------------------------------------------------------------
/docs/scripts/search.js:
--------------------------------------------------------------------------------
1 | /* global document */
2 |
3 | const searchId = 'LiBfqbJVcV';
4 | const searchHash = '#' + searchId;
5 | const searchContainer = document.querySelector('#PkfLWpAbet');
6 | const searchWrapper = document.querySelector('#iCxFxjkHbP');
7 | const searchCloseButton = document.querySelector('#VjLlGakifb');
8 | const searchInput = document.querySelector('#vpcKVYIppa');
9 | const resultBox = document.querySelector('#fWwVHRuDuN');
10 |
11 | function showResultText(text) {
12 | resultBox.innerHTML = `${text} `;
13 | }
14 |
15 | function hideSearch() {
16 | // eslint-disable-next-line no-undef
17 | if (window.location.hash === searchHash) {
18 | // eslint-disable-next-line no-undef
19 | history.go(-1);
20 | }
21 |
22 | // eslint-disable-next-line no-undef
23 | window.onhashchange = null;
24 |
25 | if (searchContainer) {
26 | searchContainer.style.display = 'none';
27 | }
28 | }
29 |
30 | function listenCloseKey(event) {
31 | if (event.key === 'Escape') {
32 | hideSearch();
33 | // eslint-disable-next-line no-undef
34 | window.removeEventListener('keyup', listenCloseKey);
35 | }
36 | }
37 |
38 | function showSearch() {
39 | try {
40 | // Closing mobile menu before opening
41 | // search box.
42 | // It is defined in core.js
43 | // eslint-disable-next-line no-undef
44 | hideMobileMenu();
45 | } catch (error) {
46 | console.error(error);
47 | }
48 |
49 | // eslint-disable-next-line no-undef
50 | window.onhashchange = hideSearch;
51 |
52 | // eslint-disable-next-line no-undef
53 | if (window.location.hash !== searchHash) {
54 | // eslint-disable-next-line no-undef
55 | history.pushState(null, null, searchHash);
56 | }
57 |
58 | if (searchContainer) {
59 | searchContainer.style.display = 'flex';
60 | // eslint-disable-next-line no-undef
61 | window.addEventListener('keyup', listenCloseKey);
62 | }
63 |
64 | if (searchInput) {
65 | searchInput.focus();
66 | }
67 | }
68 |
69 | async function fetchAllData() {
70 | // eslint-disable-next-line no-undef
71 | const { hostname, protocol, port } = location;
72 |
73 | // eslint-disable-next-line no-undef
74 | const base = protocol + '//' + hostname + (port !== '' ? ':' + port : '') + baseURL;
75 | // eslint-disable-next-line no-undef
76 | const url = new URL('data/search.json', base);
77 | const result = await fetch(url);
78 | const { list } = await result.json();
79 |
80 | return list;
81 | }
82 |
83 | // eslint-disable-next-line no-unused-vars
84 | function onClickSearchItem(event) {
85 | const target = event.currentTarget;
86 |
87 | if (target) {
88 | const href = target.getAttribute('href') || '';
89 | let elementId = href.split('#')[1] || '';
90 | let element = document.getElementById(elementId);
91 |
92 | if (!element) {
93 | elementId = decodeURI(elementId);
94 | element = document.getElementById(elementId);
95 | }
96 |
97 | if (element) {
98 | setTimeout(function() {
99 | // eslint-disable-next-line no-undef
100 | bringElementIntoView(element); // defined in core.js
101 | }, 100);
102 | }
103 | }
104 | }
105 |
106 | function buildSearchResult(result) {
107 | let output = '';
108 | const removeHTMLTagsRegExp = /(<([^>]+)>)/ig;
109 |
110 | for (const res of result) {
111 | const { title = '', description = '' } = res.item;
112 |
113 | const _link = res.item.link.replace('.*/, '');
114 | const _title = title.replace(removeHTMLTagsRegExp, "");
115 | const _description = description.replace(removeHTMLTagsRegExp, "");
116 |
117 | output += `
118 |
119 | ${_title}
120 | ${_description || 'No description available.'}
121 |
122 | `;
123 | }
124 |
125 | return output;
126 | }
127 |
128 | function getSearchResult(list, keys, searchKey) {
129 | const defaultOptions = {
130 | shouldSort: true,
131 | threshold: 0.4,
132 | location: 0,
133 | distance: 100,
134 | maxPatternLength: 32,
135 | minMatchCharLength: 1,
136 | keys: keys
137 | };
138 |
139 | const options = { ...defaultOptions };
140 |
141 | // eslint-disable-next-line no-undef
142 | const searchIndex = Fuse.createIndex(options.keys, list);
143 |
144 | // eslint-disable-next-line no-undef
145 | const fuse = new Fuse(list, options, searchIndex);
146 |
147 | const result = fuse.search(searchKey);
148 |
149 | if (result.length > 20) {
150 | return result.slice(0, 20);
151 | }
152 |
153 | return result;
154 | }
155 |
156 | function debounce(func, wait, immediate) {
157 | let timeout;
158 |
159 | return function() {
160 | const args = arguments;
161 |
162 | clearTimeout(timeout);
163 | timeout = setTimeout(() => {
164 | timeout = null;
165 | if (!immediate) {
166 | // eslint-disable-next-line consistent-this, no-invalid-this
167 | func.apply(this, args);
168 | }
169 | }, wait);
170 |
171 | if (immediate && !timeout) {
172 | // eslint-disable-next-line consistent-this, no-invalid-this
173 | func.apply(this, args);
174 | }
175 | };
176 | }
177 |
178 | let searchData;
179 |
180 | async function search(event) {
181 | const value = event.target.value;
182 | const keys = ['title', 'description'];
183 |
184 | if (!resultBox) {
185 | console.error('Search result container not found');
186 |
187 | return;
188 | }
189 |
190 | if (!value) {
191 | showResultText('Type anything to view search result');
192 |
193 | return;
194 | }
195 |
196 | if (!searchData) {
197 | showResultText('Loading...');
198 |
199 | try {
200 | // eslint-disable-next-line require-atomic-updates
201 | searchData = await fetchAllData();
202 | } catch (e) {
203 | console.log(e);
204 | showResultText('Failed to load result.');
205 |
206 | return;
207 | }
208 | }
209 |
210 | const result = getSearchResult(searchData, keys, value);
211 |
212 | if (!result.length) {
213 | showResultText('No result found! Try some different combination.');
214 |
215 | return;
216 | }
217 |
218 | // eslint-disable-next-line require-atomic-updates
219 | resultBox.innerHTML = buildSearchResult(result);
220 | }
221 |
222 | function onDomContentLoaded() {
223 | const searchButton = document.querySelectorAll('.search-button');
224 | const debouncedSearch = debounce(search, 300);
225 |
226 | if (searchCloseButton) {
227 | searchCloseButton.addEventListener('click', hideSearch);
228 | }
229 |
230 | if (searchButton) {
231 | searchButton.forEach(function(item) {
232 | item.addEventListener('click', showSearch);
233 | });
234 | }
235 |
236 | if (searchContainer) {
237 | searchContainer.addEventListener('click', hideSearch);
238 | }
239 |
240 | if (searchWrapper) {
241 | searchWrapper.addEventListener('click', function(event) {
242 | event.stopPropagation();
243 | });
244 | }
245 |
246 | if (searchInput) {
247 | searchInput.addEventListener('keyup', debouncedSearch);
248 | }
249 |
250 | // eslint-disable-next-line no-undef
251 | if (window.location.hash === searchHash) {
252 | showSearch();
253 | }
254 | }
255 |
256 | // eslint-disable-next-line no-undef
257 | window.addEventListener('DOMContentLoaded', onDomContentLoaded);
258 |
259 | // eslint-disable-next-line no-undef
260 | window.addEventListener('hashchange', function() {
261 | // eslint-disable-next-line no-undef
262 | if (window.location.hash === searchHash) {
263 | showSearch();
264 | }
265 | });
266 |
--------------------------------------------------------------------------------
/docs/scripts/search.min.js:
--------------------------------------------------------------------------------
1 | const searchId="LiBfqbJVcV",searchHash="#"+searchId,searchContainer=document.querySelector("#PkfLWpAbet"),searchWrapper=document.querySelector("#iCxFxjkHbP"),searchCloseButton=document.querySelector("#VjLlGakifb"),searchInput=document.querySelector("#vpcKVYIppa"),resultBox=document.querySelector("#fWwVHRuDuN");function showResultText(e){resultBox.innerHTML=`${e} `}function hideSearch(){window.location.hash===searchHash&&history.go(-1),window.onhashchange=null,searchContainer&&(searchContainer.style.display="none")}function listenCloseKey(e){"Escape"===e.key&&(hideSearch(),window.removeEventListener("keyup",listenCloseKey))}function showSearch(){try{hideMobileMenu()}catch(e){console.error(e)}window.onhashchange=hideSearch,window.location.hash!==searchHash&&history.pushState(null,null,searchHash),searchContainer&&(searchContainer.style.display="flex",window.addEventListener("keyup",listenCloseKey)),searchInput&&searchInput.focus()}async function fetchAllData(){var{hostname:e,protocol:t,port:n}=location,t=t+"//"+e+(""!==n?":"+n:"")+baseURL,e=new URL("data/search.json",t);const a=await fetch(e);n=(await a.json()).list;return n}function onClickSearchItem(t){const n=t.currentTarget;if(n){const a=n.getAttribute("href")||"";t=a.split("#")[1]||"";let e=document.getElementById(t);e||(t=decodeURI(t),e=document.getElementById(t)),e&&setTimeout(function(){bringElementIntoView(e)},100)}}function buildSearchResult(e){let t="";var n=/(<([^>]+)>)/gi;for(const s of e){const{title:c="",description:i=""}=s.item;var a=s.item.link.replace('.*/,""),o=c.replace(n,""),r=i.replace(n,"");t+=`
2 |
3 | ${o}
4 | ${r||"No description available."}
5 |
6 | `}return t}function getSearchResult(e,t,n){var t={...{shouldSort:!0,threshold:.4,location:0,distance:100,maxPatternLength:32,minMatchCharLength:1,keys:t}},a=Fuse.createIndex(t.keys,e);const o=new Fuse(e,t,a),r=o.search(n);return 20{o=null,a||t.apply(this,e)},n),a&&!o&&t.apply(this,e)}}let searchData;async function search(e){e=e.target.value;if(resultBox)if(e){if(!searchData){showResultText("Loading...");try{searchData=await fetchAllData()}catch(e){return console.log(e),void showResultText("Failed to load result.")}}e=getSearchResult(searchData,["title","description"],e);e.length?resultBox.innerHTML=buildSearchResult(e):showResultText("No result found! Try some different combination.")}else showResultText("Type anything to view search result");else console.error("Search result container not found")}function onDomContentLoaded(){const e=document.querySelectorAll(".search-button");var t=debounce(search,300);searchCloseButton&&searchCloseButton.addEventListener("click",hideSearch),e&&e.forEach(function(e){e.addEventListener("click",showSearch)}),searchContainer&&searchContainer.addEventListener("click",hideSearch),searchWrapper&&searchWrapper.addEventListener("click",function(e){e.stopPropagation()}),searchInput&&searchInput.addEventListener("keyup",t),window.location.hash===searchHash&&showSearch()}window.addEventListener("DOMContentLoaded",onDomContentLoaded),window.addEventListener("hashchange",function(){window.location.hash===searchHash&&showSearch()});
--------------------------------------------------------------------------------
/docs/scripts/third-party/hljs-line-num.js:
--------------------------------------------------------------------------------
1 | !function(r,o){"use strict";var e,l="hljs-ln",s="hljs-ln-line",f="hljs-ln-code",c="hljs-ln-numbers",u="hljs-ln-n",h="data-line-number",n=/\r\n|\r|\n/g;function t(e){for(var n=e.toString(),t=e.anchorNode;"TD"!==t.nodeName;)t=t.parentNode;for(var r=e.focusNode;"TD"!==r.nodeName;)r=r.parentNode;var e=parseInt(t.dataset.lineNumber),o=parseInt(r.dataset.lineNumber);if(e==o)return n;var a,i=t.textContent,l=r.textContent;for(o{6} ',[s,c,u,h,f,a+t.startFrom,0{1}',[l,o])}return e}function m(e){var n=e.className;if(/hljs-/.test(n)){for(var t=g(e.innerHTML),r=0,o="";r{1}\n',[n,0=r.collapseDepth&&(t.isCollapsed=!0),o.push(t)}return e},{nest:[]})},selectHeadings:function(e,t){var l=t;r.ignoreSelector&&(l=t.split(",").map(function(e){return e.trim()+":not("+r.ignoreSelector+")"}));try{return e.querySelectorAll(l)}catch(e){return console.warn("Headers not found with selector: "+l),null}}}}function BuildHtml(i){var r,n=[].forEach,a=[].some,c=document.body,d=document.querySelector(i.contentSelector),u=!0,m=" ";function o(e,t){var l,t=t.appendChild(function(e){var t=document.createElement("li"),l=document.createElement("a");i.listItemClass&&t.setAttribute("class",i.listItemClass);i.onClick&&(l.onclick=i.onClick);i.includeTitleTags&&l.setAttribute("title",e.textContent);i.includeHtml&&e.childNodes.length?n.call(e.childNodes,function(e){l.appendChild(e.cloneNode(!0))}):l.textContent=e.textContent;return l.setAttribute("href",i.basePath+"#"+e.id),l.setAttribute("class",i.linkClass+m+"node-name--"+e.nodeName+m+i.extraLinkClasses),t.appendChild(l),t}(e));e.children.length&&(l=s(e.isCollapsed),e.children.forEach(function(e){o(e,l)}),t.appendChild(l))}function s(e){var t=i.orderedList?"ol":"ul",t=document.createElement(t),l=i.listClass+m+i.extraListClasses;return e&&(l=(l+=m+i.collapsibleClass)+(m+i.isCollapsedClass)),t.setAttribute("class",l),t}function f(e){var t=[].forEach,l=r.querySelectorAll("."+i.linkClass),l=(t.call(l,function(e){e.className=e.className.split(m+i.activeLinkClass).join("")}),r.querySelectorAll("."+i.listItemClass)),l=(t.call(l,function(e){e.className=e.className.split(m+i.activeListItemClass).join("")}),r.querySelector("."+i.linkClass+".node-name--"+e.nodeName+'[href="'+i.basePath+"#"+e.id.replace(/([ #;&,.+*~':"!^$[\]()=>|/@])/g,"\\$1")+'"]')),e=(l&&-1===l.className.indexOf(i.activeLinkClass)&&(l.className+=m+i.activeLinkClass),l&&l.parentNode),e=(e&&-1===e.className.indexOf(i.activeListItemClass)&&(e.className+=m+i.activeListItemClass),r.querySelectorAll("."+i.listClass+"."+i.collapsibleClass));t.call(e,function(e){-1===e.className.indexOf(i.isCollapsedClass)&&(e.className+=m+i.isCollapsedClass)}),l&&l.nextSibling&&-1!==l.nextSibling.className.indexOf(i.isCollapsedClass)&&(l.nextSibling.className=l.nextSibling.className.split(m+i.isCollapsedClass).join("")),function e(t){if(t&&-1!==t.className.indexOf(i.collapsibleClass)&&-1!==t.className.indexOf(i.isCollapsedClass))return t.className=t.className.split(m+i.isCollapsedClass).join(""),e(t.parentNode.parentNode);return t}(l&&l.parentNode.parentNode)}return{enableTocAnimation:function(){u=!0},disableTocAnimation:function(e){"string"==typeof(e=e.target||e.srcElement).className&&-1!==e.className.indexOf(i.linkClass)&&(u=!1)},render:function(e,t){var l=s(!1);if(t.forEach(function(e){o(e,l)}),null!==(r=e||r))return r.firstChild&&r.removeChild(r.firstChild),0===t.length?r:r.appendChild(l)},updateToc:function(e){n=i.scrollContainer&&document.querySelector(i.scrollContainer)?document.querySelector(i.scrollContainer).scrollTop:document.documentElement.scrollTop||c.scrollTop,i.positionFixedSelector&&(t=i.scrollContainer&&document.querySelector(i.scrollContainer)?document.querySelector(i.scrollContainer).scrollTop:document.documentElement.scrollTop||c.scrollTop,l=document.querySelector(i.positionFixedSelector),"auto"===i.fixedSidebarOffset&&(i.fixedSidebarOffset=r.offsetTop),t>i.fixedSidebarOffset?-1===l.className.indexOf(i.positionFixedClass)&&(l.className+=m+i.positionFixedClass):l.className=l.className.split(m+i.positionFixedClass).join(""));var n,t,l,o,s=e;u&&null!==r&&0l?(o=s[0===t?t:t-1],!0):t===s.length-1?(o=s[s.length-1],!0):void 0}),f(o))},updateListActiveElement:f}}function updateTocScroll(e){var t,l=e.tocElement||document.querySelector(e.tocSelector);l&&l.scrollHeight>l.clientHeight&&((e=l.querySelector("."+e.activeListItemClass))&&(t=l.getBoundingClientRect().top,l.scrollTop=e.offsetTop-t))}!function(e,t){"function"==typeof define&&define.amd?define([],t(e)):"object"==typeof exports?module.exports=t(e):e.tocbot=t(e)}("undefined"!=typeof global?global:this.window||this.global,function(e){"use strict";var l,a,c,d,u={},m={},f=!!(e&&e.document&&e.document.querySelector&&e.addEventListener);if("undefined"!=typeof window||f)return d=Object.prototype.hasOwnProperty,m.destroy=function(){var e=C(u);null!==e&&(u.skipRendering||e&&(e.innerHTML=""),u.scrollContainer&&document.querySelector(u.scrollContainer)?(document.querySelector(u.scrollContainer).removeEventListener("scroll",this._scrollListener,!1),document.querySelector(u.scrollContainer).removeEventListener("resize",this._scrollListener,!1)):(document.removeEventListener("scroll",this._scrollListener,!1),document.removeEventListener("resize",this._scrollListener,!1)))},m.init=function(e){if(f){u=function(){for(var e={},t=0;t tile(i, 16, 0, 1);
33 | spriteAtlas =
34 | {
35 | circle: gameTile(0),
36 | dot: gameTile(1),
37 | circleOutline: gameTile(2),
38 | squareOutline: gameTile(3),
39 | wheel: gameTile(4),
40 | gear: gameTile(5),
41 | squareOutline2: gameTile(6),
42 | };
43 |
44 | loadScene(scene);
45 | }
46 |
47 | ///////////////////////////////////////////////////////////////////////////////
48 | function gameUpdate()
49 | {
50 | // scale canvas to fit based on 1080p
51 | cameraScale = mainCanvasSize.y * 48 / 1080;
52 |
53 | // mouse controls
54 | if (mouseWasPressed(0))
55 | {
56 | // grab object
57 | sound_click.play(mousePos);
58 | const object = box2dPointCast(mousePos);
59 | if (object)
60 | mouseJoint = box2dCreateMouseJoint(object, groundObject, mousePos);
61 | }
62 | if (mouseWasReleased(0))
63 | {
64 | // release object
65 | sound_click.play(mousePos, 1, .5);
66 | if (mouseJoint)
67 | {
68 | box2dDestroyJoint(mouseJoint);
69 | mouseJoint = 0;
70 | }
71 | }
72 | if (mouseIsDown(1) || mouseIsDown('KeyZ'))
73 | {
74 | const isSet = repeatSpawnTimer.isSet();
75 | if (!isSet || repeatSpawnTimer.elapsed())
76 | {
77 | // spawn continuously after a delay
78 | isSet || repeatSpawnTimer.set(.5);
79 | spawnRandomObject(mousePos);
80 | }
81 | }
82 | else
83 | repeatSpawnTimer.unset();
84 | if (mouseWasPressed(2) || keyWasPressed('KeyX'))
85 | explosion(mousePos);
86 | if (mouseJoint)
87 | mouseJoint.SetTarget(mousePos.getBox2d());
88 |
89 | if (keyWasPressed('KeyR'))
90 | loadScene(scene); // reset scene
91 | if (keyWasPressed('ArrowUp') || keyWasPressed('ArrowDown'))
92 | {
93 | // change scene
94 | scene += keyWasPressed('ArrowUp') ? 1 : -1;
95 | scene = mod(scene, maxScenes);
96 | loadScene(scene);
97 | }
98 |
99 | if (car)
100 | {
101 | // update car control
102 | const input = keyDirection();
103 | car.applyMotorInput(-input.x);
104 | }
105 | }
106 |
107 | ///////////////////////////////////////////////////////////////////////////////
108 | function gameUpdatePost()
109 | {
110 |
111 | }
112 |
113 | ///////////////////////////////////////////////////////////////////////////////
114 | function gameRender()
115 | {
116 | // draw a grey square in the background without using webgl
117 | drawRect(vec2(20,8), vec2(100), hsl(0,0,.8), 0, 0);
118 |
119 | if (scene == 5)
120 | {
121 | // raycast test
122 | const count = 100;
123 | const distance = 10;
124 | for (let i=count;i--;)
125 | {
126 | const start = mousePos;
127 | const end = mousePos.add(vec2(distance,0).rotate(i/count*PI*2));
128 | const result = box2dRaycast(start, end);
129 | const color = result ? hsl(0,1,.5,.5) : hsl(.5,1,.5,.5);
130 | drawLine(start, result ? result.point : end, .1, color);
131 | }
132 | }
133 | }
134 |
135 | ///////////////////////////////////////////////////////////////////////////////
136 | function gameRenderPost()
137 | {
138 | if (mouseJoint)
139 | {
140 | // draw mouse joint
141 | const ab = vec2(mouseJoint.GetAnchorB());
142 | drawTile(ab, vec2(.3), spriteAtlas.circle, BLACK);
143 | drawLine(mousePos, ab, .1, BLACK);
144 | }
145 |
146 | // draw to overlay canvas for hud rendering
147 | const pos = vec2(mainCanvasSize.x/2, 50);
148 | drawText('LittleJS Box2D Demo', 80, 80);
149 | drawText(sceneName, 60, 100);
150 | if (scene == 0)
151 | {
152 | drawText('Mouse Left = Grab');
153 | drawText('Mouse Middle or Z = Spawn');
154 | drawText('Mouse Right or X = Explode');
155 | drawText('Arrows Up/Down = Change Scene');
156 | }
157 | if (scene == 3)
158 | {
159 | drawText('Right = Accelerate');
160 | drawText('Left = Reverse');
161 | }
162 |
163 | function drawText(text, size=40, gap=50)
164 | { drawTextScreen(text, pos, size, WHITE, size*.1); pos.y += gap; }
165 | }
166 |
167 | ///////////////////////////////////////////////////////////////////////////////
168 | // Startup LittleJS Engine with Box2D
169 |
170 | box2dEngineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, ['tiles.png']);
--------------------------------------------------------------------------------
/examples/box2d/index.html:
--------------------------------------------------------------------------------
1 |
2 | LittleJS Box2D Example
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/box2d/scenes.js:
--------------------------------------------------------------------------------
1 | /*
2 | LittleJS Box2D Example - Scenes
3 | - Each scene demonstrates a feature of Box2D
4 | - Feel free to use tis code in your own projects
5 | - 0 = Shapes: box, circle, poly, and edge
6 | - 1 = Pyramid stack
7 | - 2 = Dominoes chain reaction
8 | - 3 = Car with controls
9 | - 4 = Rope with box attached
10 | - 5 = Raycasts in all directions
11 | - 6 = Joints demo with every joint type
12 | - 7 = Contacts begin and end callbacks
13 | - 8 = Mobile object with multiple bodies
14 | - 9 = Cloth object grid using rope joints
15 | - 10 = Softbody object grid using weld joints
16 | */
17 |
18 | 'use strict';
19 |
20 | function loadScene(_scene)
21 | {
22 | scene = _scene;
23 |
24 | // setup
25 | cameraPos = vec2(20,10);
26 | setGravity(-20);
27 |
28 | // destroy old scene
29 | engineObjectsDestroy();
30 | mouseJoint = 0;
31 | car = 0;
32 |
33 | // create walls
34 | groundObject = spawnBox(vec2(0,-4), vec2(1e3,8), hsl(0,0,.2), box2dBodyTypeStatic);
35 | spawnBox(vec2(-4, 0), vec2(8,1e3), BLACK, box2dBodyTypeStatic);
36 | spawnBox(vec2(44, 0), vec2(8,1e3), BLACK, box2dBodyTypeStatic);
37 | spawnBox(vec2(0,100), vec2(1e3,8), BLACK, box2dBodyTypeStatic);
38 |
39 | if (scene == 0)
40 | {
41 | sceneName = 'Shapes';
42 | spawnRandomEdges();
43 | spawnBox(vec2(11,8), 4, randColor(), box2dBodyTypeStatic, false);
44 | spawnCircle(vec2(20,8), 4, randColor(), box2dBodyTypeStatic, false);
45 | spawnRandomPoly(vec2(29,8), 4, randColor(), box2dBodyTypeStatic, false);
46 | for (let i=500;i--;)
47 | spawnRandomObject(vec2(rand(1,39), rand(10,50)));
48 | }
49 | if (scene == 1)
50 | {
51 | sceneName = 'Pyramid';
52 | spawnPyramid(vec2(20,0), 15);
53 | spawnBox(vec2(10,2), 4, randColor());
54 | spawnCircle(vec2(30,2), 4, randColor());
55 | }
56 | if (scene == 2)
57 | {
58 | sceneName = 'Dominoes';
59 | spawnDominoes(vec2(11,11), 11);
60 | spawnDominoes(vec2(2,0), 13, vec2(1,3));
61 | spawnCircle(vec2(10,20), 2, randColor());
62 | spawnCircle(vec2(31.7,12), 2, randColor());
63 | spawnBox(vec2(16,11), vec2(32,1), randColor(), box2dBodyTypeStatic, false);
64 | spawnBox(vec2(24,6.5), vec2(32,1), randColor(), box2dBodyTypeStatic, false, -.15);
65 | }
66 | if (scene == 3)
67 | {
68 | sceneName = 'Car';
69 | car = new CarObject(vec2(10,2));
70 | spawnBox(vec2(20,0), vec2(10,2), randColor(), box2dBodyTypeStatic, false, -.2);
71 | spawnPyramid(vec2(32,0), 6);
72 | }
73 | if (scene == 4)
74 | {
75 | sceneName = 'Rope';
76 | const startPos = vec2(20, 14);
77 | const angle = PI/2;
78 | const color = randColor();
79 | const count = 10;
80 | const endObject = spawnRope(startPos, count, angle, color);
81 |
82 | // connect box to end
83 | const endPos = endObject.localToWorld(vec2(0,-endObject.size.y/2));
84 | const o = spawnBox(endPos, 3, randColor());
85 | o.setAngularDamping(.5);
86 | o.setFilterData(2, 2);
87 | box2dCreateRevoluteJoint(endObject, o);
88 | spawnPyramid(vec2(20,0), 7);
89 | }
90 | if (scene == 5)
91 | {
92 | sceneName = 'Raycasts';
93 | spawnRandomEdges();
94 | for (let i=100;i--;)
95 | spawnRandomObject(vec2(rand(1,39), rand(20)), 2, box2dBodyTypeStatic, rand(PI*2));
96 | }
97 | if (scene == 6)
98 | {
99 | sceneName = 'Joints';
100 | {
101 | // prismatic joint
102 | const o1 = spawnBox(vec2(20,8), vec2(3,2), randColor());
103 | box2dCreatePrismaticJoint(groundObject, o1, o1.pos, vec2(1,0), true);
104 | const o2 = spawnBox(vec2(20,15), vec2(2,3), randColor());
105 | box2dCreatePrismaticJoint(groundObject, o2, o2.pos, vec2(0,1), true);
106 |
107 | // lines to make it look line a track
108 | const l1 = new EngineObject(vec2(20, 8), vec2(39,.5), 0, 0, GRAY);
109 | const l2 = new EngineObject(vec2(20,50), vec2(.5,99), 0, 0, GRAY);
110 | l1.gravityScale = l2.gravityScale = 0;
111 | l1.renderOrder = l2.renderOrder = -2;
112 | }
113 | {
114 | // pulley joint
115 | const anchorA = vec2(14,15);
116 | const anchorB = vec2(26,15);
117 | const oA = new PulleyJointObjects(vec2(15,8), vec2(1,2), randColor(), anchorA);
118 | const oB = new PulleyJointObjects(vec2(25,8), vec2(1,2), randColor(), anchorB);
119 | const aA = spawnCircle(anchorA, 2, randColor(), box2dBodyTypeStatic);
120 | const aB = spawnCircle(anchorB, 2, randColor(), box2dBodyTypeStatic);
121 | const oaA = oA.localToWorld(vec2(0,oA.size.y/2));
122 | const oaB = oB.localToWorld(vec2(0,oB.size.y/2));
123 | box2dCreatePulleyJoint(oA, oB, aA.pos, aB.pos, oaA, oaB);
124 |
125 | // a line to make it look like connecting rope
126 | const line = new EngineObject(vec2(20,15), vec2(10,.2), 0, 0, BLACK);
127 | line.gravityScale = 0;
128 | line.renderOrder = -1;
129 | }
130 | {
131 | // gear joint
132 | const o1 = spawnCircle(vec2(23.5,3), 3, randColor());
133 | const j1 = box2dCreateRevoluteJoint(groundObject, o1, o1.pos);
134 | const o2 = spawnCircle(vec2(28,3), 6, randColor());
135 | o1.tileInfo = o2.tileInfo = spriteAtlas.gear;
136 | const j2 = box2dCreateRevoluteJoint(groundObject, o2, o2.pos);
137 | box2dCreateGearJoint(o1, o2, j1, j2, 2);
138 | }
139 | {
140 | // weld joint
141 | const o1 = spawnBox(vec2(15,2), 4, randColor());
142 | const o2 = spawnBox(vec2(17,2), 2, randColor());
143 | box2dCreateWeldJoint(o1, o2);
144 | }
145 | {
146 | // distance joint
147 | const o1 = new Box2dObject(vec2(30,11), vec2(4), spriteAtlas.circleOutline, 0, randColor(), box2dBodyTypeStatic);
148 | o1.renderOrder = -2;
149 | const o2 = spawnCircle(vec2(30,8), 2, randColor());
150 | box2dCreateDistanceJoint(o1, o2);
151 | }
152 | {
153 | // motor joint
154 | const o = new Box2dObject(vec2(10,8), vec2(4), spriteAtlas.circleOutline, 0, randColor(), box2dBodyTypeStatic);
155 | o.renderOrder = -2;
156 | new MotorJointObject(vec2(10,8), vec2(2), randColor(), o);
157 | }
158 | {
159 | // friction joint
160 | const o = spawnBox(vec2(10,15), 3, randColor());
161 | o.tileInfo = spriteAtlas.squareOutline2;
162 | const joint = box2dCreateFrictionJoint(groundObject, o);
163 | joint.SetMaxForce(200);
164 | joint.SetMaxTorque(200);
165 | }
166 | }
167 | if (scene == 7)
168 | {
169 | sceneName = 'Contacts';
170 | new ContactTester(vec2(15,8), vec2(5), RED, RED);
171 | new ContactTester(vec2(25,8), vec2(5), CYAN, CYAN, false, false);
172 | for (let i=200;i--;)
173 | spawnRandomObject(vec2(rand(1,39), rand(10,50)));
174 | }
175 | if (scene == 8)
176 | {
177 | sceneName = 'Mobile';
178 | const pos = vec2(20, 16);
179 | const mobile = new MobileObject(pos, 12, 2, 5);
180 | box2dCreateRevoluteJoint(groundObject, mobile, pos);
181 | }
182 | if (scene == 9)
183 | {
184 | sceneName = 'Cloth';
185 | new ClothObject(vec2(20, 9), vec2(15), vec2(24), randColor());
186 | }
187 | if (scene == 10)
188 | {
189 | sceneName = 'Softbodies';
190 | for(let i=3;i--;)
191 | new SoftBodyObject(vec2(20, 3+i*7), vec2(6-i), vec2(9-i), randColor());
192 | }
193 | }
--------------------------------------------------------------------------------
/examples/box2d/tiles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/examples/box2d/tiles.png
--------------------------------------------------------------------------------
/examples/breakout/game.js:
--------------------------------------------------------------------------------
1 | /*
2 | Little JS Breakout Game
3 | - A simple breakout game
4 | - Includes sound and particles
5 | - Uses a post processing effect
6 | - Control with mouse, touch, or gamepad
7 | */
8 |
9 | 'use strict';
10 |
11 | // show the LittleJS splash screen
12 | setShowSplashScreen(true);
13 |
14 | let levelSize, ball, paddle, score, brickCount;
15 |
16 | // sound effects
17 | const sound_start = new Sound([,0,500,,.04,.3,1,2,,,570,.02,.02,,,,.04]);
18 | const sound_break = new Sound([,,90,,.01,.03,4,,,,,,,9,50,.2,,.2,.01]);
19 | const sound_bounce = new Sound([,,1e3,,.03,.02,1,2,,,940,.03,,,,,.2,.6,,.06]);
20 |
21 | ///////////////////////////////////////////////////////////////////////////////
22 | function gameInit()
23 | {
24 | canvasFixedSize = vec2(1920, 1080); // 1080p
25 | levelSize = vec2(38, 20);
26 | cameraPos = levelSize.scale(.5);
27 | cameraScale = 48;
28 | paddle = new Paddle(vec2(levelSize.x/2-12, 1));
29 | score = brickCount = 0;
30 |
31 | // spawn bricks
32 | const pos = vec2();
33 | for (pos.x = 4; pos.x <= levelSize.x-4; pos.x += 2)
34 | for (pos.y = 12; pos.y <= levelSize.y-2; pos.y += 1)
35 | new Brick(pos);
36 |
37 | // create walls
38 | new Wall(vec2(-.5,levelSize.y/2), vec2(1,100)); // top
39 | new Wall(vec2(levelSize.x+.5,levelSize.y/2), vec2(1,100)); // left
40 | new Wall(vec2(levelSize.x/2,levelSize.y+.5), vec2(100,1)); // right
41 |
42 | setupPostProcess(); // set up a post processing shader
43 | }
44 |
45 | ///////////////////////////////////////////////////////////////////////////////
46 | function gameUpdate()
47 | {
48 | // spawn ball
49 | if (!ball && (mouseWasPressed(0) || gamepadWasPressed(0)))
50 | {
51 | ball = new Ball(vec2(levelSize.x/2, levelSize.y/2));
52 | sound_start.play();
53 | }
54 | }
55 |
56 | ///////////////////////////////////////////////////////////////////////////////
57 | function gameUpdatePost()
58 | {
59 |
60 | }
61 |
62 | ///////////////////////////////////////////////////////////////////////////////
63 | function gameRender()
64 | {
65 | // draw a the background
66 | drawRect(cameraPos, levelSize.scale(2), hsl(0,0,.5));
67 | drawRect(cameraPos, levelSize, hsl(0,0,.02));
68 | }
69 |
70 | ///////////////////////////////////////////////////////////////////////////////
71 | function gameRenderPost()
72 | {
73 | // use built in image font for text
74 | const font = new FontImage;
75 | font.drawText('Score: ' + score, cameraPos.add(vec2(0,9.7)), .15, true);
76 | if (!brickCount)
77 | font.drawText('You Win!', cameraPos.add(vec2(0,-5)), .2, true);
78 | else if (!ball)
79 | font.drawText('Click to Play', cameraPos.add(vec2(0,-5)), .2, true);
80 | }
81 |
82 | ///////////////////////////////////////////////////////////////////////////////
83 | // an example shader that can be used to apply a post processing effect
84 | function setupPostProcess()
85 | {
86 | const televisionShader = `
87 | // Simple TV Shader Code
88 | float hash(vec2 p)
89 | {
90 | p=fract(p*.3197);
91 | return fract(1.+sin(51.*p.x+73.*p.y)*13753.3);
92 | }
93 | float noise(vec2 p)
94 | {
95 | vec2 i=floor(p),f=fract(p),u=f*f*(3.-2.*f);
96 | return mix(mix(hash(i),hash(i+vec2(1,0)),u.x),mix(hash(i+vec2(0,1)),hash(i+1.),u.x),u.y);
97 | }
98 | void mainImage(out vec4 c, vec2 p)
99 | {
100 | // put uv in texture pixel space
101 | p /= iResolution.xy;
102 |
103 | // apply fuzz as horizontal offset
104 | const float fuzz = .0005;
105 | const float fuzzScale = 800.;
106 | const float fuzzSpeed = 9.;
107 | p.x += fuzz*(noise(vec2(p.y*fuzzScale, iTime*fuzzSpeed))*2.-1.);
108 |
109 | // init output color
110 | c = texture(iChannel0, p);
111 |
112 | // chromatic aberration
113 | const float chromatic = .002;
114 | c.r = texture(iChannel0, p - vec2(chromatic,0)).r;
115 | c.b = texture(iChannel0, p + vec2(chromatic,0)).b;
116 |
117 | // tv static noise
118 | const float staticNoise = .1;
119 | c += staticNoise * hash(p + mod(iTime, 1e3));
120 |
121 | // scan lines
122 | const float scanlineScale = 1e3;
123 | const float scanlineAlpha = .1;
124 | c *= 1. + scanlineAlpha*sin(p.y*scanlineScale);
125 |
126 | // black vignette around edges
127 | const float vignette = 2.;
128 | const float vignettePow = 6.;
129 | float dx = 2.*p.x-1., dy = 2.*p.y-1.;
130 | c *= 1.-pow((dx*dx + dy*dy)/vignette, vignettePow);
131 | }`;
132 |
133 | const includeOverlay = true;
134 | initPostProcess(televisionShader, includeOverlay);
135 | }
136 |
137 | ///////////////////////////////////////////////////////////////////////////////
138 | // Startup LittleJS Engine
139 | engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, ['tiles.png']);
--------------------------------------------------------------------------------
/examples/breakout/gameObjects.js:
--------------------------------------------------------------------------------
1 | /*
2 | LittleJS Breakout Objects
3 | */
4 |
5 | 'use strict';
6 |
7 | ///////////////////////////////////////////////////////////////////////////////
8 | class PhysicsObject extends EngineObject
9 | {
10 | constructor(pos, size, tileInfo, angle, color)
11 | {
12 | super(pos, size, tileInfo, angle, color);
13 | this.setCollision(); // make object collide
14 | this.mass = 0; // make object have static physics
15 | }
16 | }
17 |
18 | ///////////////////////////////////////////////////////////////////////////////
19 | class Wall extends PhysicsObject
20 | {
21 | constructor(pos, size)
22 | {
23 | super(pos, size, 0, 0, new Color(0,0,0,0));
24 | }
25 | }
26 |
27 | ///////////////////////////////////////////////////////////////////////////////
28 | class Paddle extends PhysicsObject
29 | {
30 | constructor(pos)
31 | {
32 | super(pos, vec2(5,.5));
33 | }
34 |
35 | update()
36 | {
37 | // control with gamepad or mouse
38 | this.pos.x = isUsingGamepad ? this.pos.x + gamepadStick(0).x : mousePos.x;
39 |
40 | // keep paddle in bounds of level
41 | this.pos.x = clamp(this.pos.x, this.size.x/2, levelSize.x - this.size.x/2);
42 | }
43 | }
44 |
45 | ///////////////////////////////////////////////////////////////////////////////
46 | class Brick extends PhysicsObject
47 | {
48 | constructor(pos)
49 | {
50 | super(pos, vec2(2,1), tile(1, vec2(32,16)), 0, randColor());
51 | ++brickCount;
52 | }
53 |
54 | collideWithObject(o)
55 | {
56 | // destroy brick when hit with ball
57 | this.destroy();
58 | ++score;
59 | --brickCount;
60 | sound_break.play(this.pos);
61 |
62 | // make explosion effect
63 | const color1 = this.color;
64 | const color2 = color1.lerp(hsl(), .5);
65 | new ParticleEmitter(
66 | this.pos, 0, // pos, angle
67 | this.size, .1, 200, PI, // emitSize, emitTime, emitRate, emitCone
68 | tile(0, 16), // tileIndex, tileSize
69 | color1, color2, // colorStartA, colorStartB
70 | color1.scale(1,0), color2.scale(1,0), // colorEndA, colorEndB
71 | .3, .8, .3, .05, .05,// time, sizeStart, sizeEnd, speed, angleSpeed
72 | .99, .95, .4, PI, // damp, angleDamp, gravity, cone
73 | .1, .8, 0, 1 // fade, randomness, collide, additive
74 | );
75 |
76 | // set ball trail color
77 | if (o.trailEffect)
78 | {
79 | o.trailEffect.colorStartA = this.color;
80 | o.trailEffect.colorStartB = this.color.lerp(hsl(), .5);
81 | }
82 |
83 | return 1;
84 | }
85 | }
86 |
87 | ///////////////////////////////////////////////////////////////////////////////
88 | class Ball extends PhysicsObject
89 | {
90 | constructor(pos)
91 | {
92 | super(pos, vec2(.5), tile(0));
93 |
94 | // make a bouncy ball
95 | this.velocity = vec2(0, -.1);
96 | this.elasticity = 1;
97 | this.mass = 1;
98 |
99 | // attach a trail effect
100 | const color = hsl(0,0,.2);
101 | this.trailEffect = new ParticleEmitter(
102 | this.pos, 0, // pos, angle
103 | this.size, 0, 80, PI, // emitSize, emitTime, emitRate, emitCone
104 | tile(0, 16), // tileIndex, tileSize
105 | color, color, // colorStartA, colorStartB
106 | color.scale(0), color.scale(0), // colorEndA, colorEndB
107 | 2, .4, 1, .001, .05,// time, sizeStart, sizeEnd, speed, angleSpeed
108 | .99, .95, 0, PI, // damp, angleDamp, gravity, cone
109 | .1, .5, 0, 1 // fade, randomness, collide, additive
110 | );
111 | this.addChild(this.trailEffect);
112 | }
113 |
114 | update()
115 | {
116 | if (this.pos.y < -1)
117 | {
118 | // destroy ball if it goes below the level
119 | ball = 0;
120 | this.destroy();
121 | }
122 |
123 | // update physics
124 | super.update();
125 | }
126 |
127 | collideWithObject(o)
128 | {
129 | // prevent colliding with paddle if moving upwards
130 | if (o == paddle && this.velocity.y > 0)
131 | return false;
132 |
133 | if (o == paddle)
134 | {
135 | // put english on the ball when it collides with paddle
136 | this.velocity = this.velocity.rotate(.2 * (o.pos.x - this.pos.x));
137 | this.velocity.y = max(-this.velocity.y, .2);
138 |
139 | // speed up
140 | const speed = min(1.04*this.velocity.length(), .5);
141 | this.velocity = this.velocity.normalize(speed);
142 | sound_bounce.play(this.pos, 1, speed*2);
143 |
144 | return false; // prevent default collision code
145 | }
146 |
147 | return true;
148 | }
149 | }
--------------------------------------------------------------------------------
/examples/breakout/index.html:
--------------------------------------------------------------------------------
1 |
2 | LittleJS Breakout Game
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/examples/breakout/tiles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/examples/breakout/tiles.png
--------------------------------------------------------------------------------
/examples/breakoutTutorial/game.js:
--------------------------------------------------------------------------------
1 | /*
2 | Little JS Breakout Tutorial
3 | - Shows how to make a simple breakout game
4 | - Includes sound and particles
5 | - Control with mouse or touch
6 | */
7 |
8 | 'use strict';
9 |
10 | ///////////////////////////////////////////////////////////////////////////////
11 |
12 | // globals
13 | const levelSize = vec2(38, 20); // size of play area
14 | let score = 0; // start score at 0
15 | let ball; // keep track of ball object
16 | let paddle; // keep track of player's paddle
17 |
18 | // sound effects
19 | const sound_bounce = new Sound([,,1e3,,.03,.02,1,2,,,940,.03,,,,,.2,.6,,.06], 0);
20 | const sound_break = new Sound([,,90,,.01,.03,4,,,,,,,9,50,.2,,.2,.01], 0);
21 | const sound_start = new Sound([,0,500,,.04,.3,1,2,,,570,.02,.02,,,,.04]);
22 |
23 | ///////////////////////////////////////////////////////////////////////////////
24 |
25 | class Paddle extends EngineObject
26 | {
27 | constructor()
28 | {
29 | super(vec2(0,1), vec2(6,.5)); // set object position and size
30 | this.setCollision(); // make object collide
31 | this.mass = 0; // make object have static physics
32 | }
33 |
34 | update()
35 | {
36 | this.pos.x = mousePos.x; // move paddle to mouse
37 |
38 | // clamp paddle to level size
39 | this.pos.x = clamp(this.pos.x, this.size.x/2, levelSize.x - this.size.x/2);
40 | }
41 | }
42 |
43 | class Ball extends EngineObject
44 | {
45 | constructor(pos)
46 | {
47 | super(pos, vec2(.5)); // set object position and size
48 |
49 | this.velocity = vec2(-.1, -.1); // give ball some movement
50 | this.setCollision(); // make object collide
51 | this.elasticity = 1; // make object bounce
52 | }
53 | collideWithObject(o)
54 | {
55 | // prevent colliding with paddle if moving upwards
56 | if (o == paddle && this.velocity.y > 0)
57 | return false;
58 |
59 | // speed up
60 | const speed = min(1.04*this.velocity.length(), .5);
61 | this.velocity = this.velocity.normalize(speed);
62 |
63 | // play bounce sound with pitch scaled by speed
64 | sound_bounce.play(this.pos, 1, speed);
65 |
66 | if (o == paddle)
67 | {
68 | // control bounce angle when ball collides with paddle
69 | const deltaX = o.pos.x - this.pos.x;
70 | this.velocity = this.velocity.rotate(.3 * deltaX);
71 |
72 | // make sure ball is moving upwards with a minimum speed
73 | this.velocity.y = max(-this.velocity.y, .2);
74 |
75 | // prevent default collision code
76 | return false;
77 | }
78 |
79 | return true; // allow object to collide
80 | }
81 | }
82 |
83 | class Wall extends EngineObject
84 | {
85 | constructor(pos, size)
86 | {
87 | super(pos, size); // set object position and size
88 |
89 | this.setCollision(); // make object collide
90 | this.mass = 0; // make object have static physics
91 | this.color = new Color(0,0,0,0); // make object invisible
92 | }
93 | }
94 |
95 | class Brick extends EngineObject
96 | {
97 | constructor(pos, size)
98 | {
99 | super(pos, size);
100 |
101 | this.setCollision(); // make object collide
102 | this.mass = 0; // make object have static physics
103 | this.color = randColor(); // give brick a random color
104 | }
105 |
106 | collideWithObject(o)
107 | {
108 | this.destroy(); // destroy block when hit
109 | sound_break.play(this.pos); // play brick break sound
110 | ++score; // award a point for each brick broke
111 |
112 | // create explosion effect
113 | const color = this.color;
114 | new ParticleEmitter(
115 | this.pos, 0, // pos, angle
116 | this.size, .1, 200, PI, // emitSize, emitTime, emitRate, emitCone
117 | undefined, // tileInfo
118 | color, color, // colorStartA, colorStartB
119 | color.scale(1,0), color.scale(1,0), // colorEndA, colorEndB
120 | .2, .5, 1, .1, .1, // time, sizeStart, sizeEnd, speed, angleSpeed
121 | .99, .95, .4, PI, // damp, angleDamp, gravity, cone
122 | .1, .5, false, true // fade, randomness, collide, additive
123 | );
124 |
125 | return true; // allow object to collide
126 | }
127 | }
128 |
129 | ///////////////////////////////////////////////////////////////////////////////
130 | function gameInit()
131 | {
132 | // create bricks
133 | for(let x=2; x<=levelSize.x-2; x+=2)
134 | for(let y=12; y<=levelSize.y-2; y+=1)
135 | new Brick(vec2(x,y), vec2(2,1)); // create a brick
136 |
137 | cameraPos = levelSize.scale(.5); // center camera in level
138 | canvasFixedSize = vec2(1280, 720); // use a 720p fixed size canvas
139 |
140 | paddle = new Paddle; // create player's paddle
141 |
142 | // create walls
143 | new Wall(vec2(-.5,levelSize.y/2), vec2(1,100)) // top
144 | new Wall(vec2(levelSize.x+.5,levelSize.y/2), vec2(1,100)) // left
145 | new Wall(vec2(levelSize.x/2,levelSize.y+.5), vec2(100,1)) // right
146 | }
147 |
148 | ///////////////////////////////////////////////////////////////////////////////
149 | function gameUpdate()
150 | {
151 | if (ball && ball.pos.y < -1) // if ball is below level
152 | {
153 | // destroy old ball
154 | ball.destroy();
155 | ball = 0;
156 | }
157 | if (!ball && mouseWasPressed(0)) // if there is no ball and left mouse is pressed
158 | {
159 | ball = new Ball(cameraPos); // create a ball
160 | sound_start.play(); // play start sound
161 | }
162 | }
163 |
164 | ///////////////////////////////////////////////////////////////////////////////
165 | function gameUpdatePost()
166 | {
167 | }
168 |
169 | ///////////////////////////////////////////////////////////////////////////////
170 | function gameRender()
171 | {
172 | drawRect(cameraPos, vec2(100), new Color(.5,.5,.5)); // draw background
173 | drawRect(cameraPos, levelSize, new Color(.1,.1,.1)); // draw level boundary
174 | }
175 |
176 | ///////////////////////////////////////////////////////////////////////////////
177 | function gameRenderPost()
178 | {
179 | drawTextScreen("Score " + score, vec2(mainCanvasSize.x/2, 70), 50); // show score
180 | }
181 |
182 | ///////////////////////////////////////////////////////////////////////////////
183 | // Startup LittleJS Engine
184 | engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost);
--------------------------------------------------------------------------------
/examples/breakoutTutorial/images/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/examples/breakoutTutorial/images/1.png
--------------------------------------------------------------------------------
/examples/breakoutTutorial/images/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/examples/breakoutTutorial/images/10.png
--------------------------------------------------------------------------------
/examples/breakoutTutorial/images/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/examples/breakoutTutorial/images/11.png
--------------------------------------------------------------------------------
/examples/breakoutTutorial/images/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/examples/breakoutTutorial/images/2.png
--------------------------------------------------------------------------------
/examples/breakoutTutorial/images/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/examples/breakoutTutorial/images/3.png
--------------------------------------------------------------------------------
/examples/breakoutTutorial/images/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/examples/breakoutTutorial/images/4.png
--------------------------------------------------------------------------------
/examples/breakoutTutorial/images/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/examples/breakoutTutorial/images/5.png
--------------------------------------------------------------------------------
/examples/breakoutTutorial/images/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/examples/breakoutTutorial/images/6.png
--------------------------------------------------------------------------------
/examples/breakoutTutorial/images/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/examples/breakoutTutorial/images/7.png
--------------------------------------------------------------------------------
/examples/breakoutTutorial/images/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/examples/breakoutTutorial/images/8.png
--------------------------------------------------------------------------------
/examples/breakoutTutorial/images/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/examples/breakoutTutorial/images/9.png
--------------------------------------------------------------------------------
/examples/breakoutTutorial/index.html:
--------------------------------------------------------------------------------
1 |
2 | LittleJS Breakout Tutorial
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/examples/empty/game.js:
--------------------------------------------------------------------------------
1 | /*
2 | Little JS Hello World Demo
3 | - Just prints "Hello World!"
4 | - A good starting point for new projects
5 | */
6 |
7 | 'use strict';
8 |
9 | ///////////////////////////////////////////////////////////////////////////////
10 | function gameInit()
11 | {
12 | // called once after the engine starts up
13 | // setup the game
14 | }
15 |
16 | ///////////////////////////////////////////////////////////////////////////////
17 | function gameUpdate()
18 | {
19 | // called every frame at 60 frames per second
20 | // handle input and update the game state
21 | }
22 |
23 | ///////////////////////////////////////////////////////////////////////////////
24 | function gameUpdatePost()
25 | {
26 | // called after physics and objects are updated
27 | // setup camera and prepare for render
28 | }
29 |
30 | ///////////////////////////////////////////////////////////////////////////////
31 | function gameRender()
32 | {
33 | // called before objects are rendered
34 | // draw any background effects that appear behind objects
35 | }
36 |
37 | ///////////////////////////////////////////////////////////////////////////////
38 | function gameRenderPost()
39 | {
40 | // called after objects are rendered
41 | // draw effects or hud that appear above all objects
42 | drawTextScreen('Hello World!', mainCanvasSize.scale(.5), 80);
43 | }
44 |
45 | ///////////////////////////////////////////////////////////////////////////////
46 | // Startup LittleJS Engine
47 | engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, ['tiles.png']);
--------------------------------------------------------------------------------
/examples/empty/index.html:
--------------------------------------------------------------------------------
1 |
2 | LittleJS Hello World Demo
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/examples/empty/tiles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/examples/empty/tiles.png
--------------------------------------------------------------------------------
/examples/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/examples/favicon.png
--------------------------------------------------------------------------------
/examples/htmlMenu/game.js:
--------------------------------------------------------------------------------
1 | /*
2 | Little JS HTML Menu Example Project
3 | - Setup a simple html menu system
4 | - Menu can be toggled
5 | - Pauses game when menu is visible
6 | - Shows several input types
7 | */
8 |
9 | 'use strict';
10 |
11 | // show the LittleJS splash screen
12 | setShowSplashScreen(true);
13 |
14 | // sound effects
15 | const sound_click = new Sound([1,.5]);
16 |
17 | ///////////////////////////////////////////////////////////////////////////////
18 | function gameInit()
19 | {
20 | // show menu for demo
21 | setMenuVisible(true);
22 | }
23 |
24 | ///////////////////////////////////////////////////////////////////////////////
25 | function gameUpdate()
26 | {
27 | // play sound when mouse is pressed
28 | if (mouseWasPressed(0))
29 | sound_click.play(mousePos);
30 | }
31 |
32 | ///////////////////////////////////////////////////////////////////////////////
33 | function gameUpdatePost()
34 | {
35 | // pause game when menu is visible
36 | const menuVisible = getMenuVisible();
37 | paused = menuVisible;
38 |
39 | // toggle menu visibility
40 | if (keyWasPressed('KeyM'))
41 | setMenuVisible(!menuVisible);
42 | }
43 |
44 | ///////////////////////////////////////////////////////////////////////////////
45 | function gameRender()
46 | {
47 | // test game rendering
48 | drawRect(vec2(), vec2(1e3), hsl(0,0,.2));
49 | for(let i=0; i<1e3; ++i)
50 | {
51 | const pos = vec2(30*Math.sin(i+time/9),20*Math.sin(i*i+time/9));
52 | drawTile(pos, vec2(2), tile(3,128), hsl(i/9,1,.4), time+i, !(i%2), hsl(i/9,1,.1,0));
53 | }
54 | }
55 |
56 | ///////////////////////////////////////////////////////////////////////////////
57 | function gameRenderPost()
58 | {
59 | // draw to overlay canvas for hud rendering
60 | drawTextScreen('LittleJS HTML Menu Example\nM = Toggle menu',
61 | vec2(mainCanvasSize.x/2, 70), 60, // position, size
62 | hsl(0,0,1), 6, hsl(0,0,0)); // color, outline size and color
63 | }
64 |
65 | ///////////////////////////////////////////////////////////////////////////////
66 | // Startup LittleJS Engine
67 | engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, ['tiles.png']);
--------------------------------------------------------------------------------
/examples/htmlMenu/index.html:
--------------------------------------------------------------------------------
1 |
2 | LittleJS HTML Menu Example
3 |
4 |
5 |
6 |
7 |
29 |
30 |
31 |
32 |
33 |
34 |
42 |
43 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/examples/htmlMenu/tiles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/examples/htmlMenu/tiles.png
--------------------------------------------------------------------------------
/examples/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/examples/logo.png
--------------------------------------------------------------------------------
/examples/module/game.js:
--------------------------------------------------------------------------------
1 | /*
2 | Little JS Module Demo
3 | - A simple starter project
4 | - Shows how to use LittleJS with modules
5 | */
6 |
7 | 'use strict';
8 |
9 | // import module
10 | import * as LittleJS from '../../dist/littlejs.esm.js';
11 | const {tile, vec2, hsl} = LittleJS;
12 |
13 | // show the LittleJS splash screen
14 | LittleJS.setShowSplashScreen(true);
15 |
16 | // fix texture bleeding by shrinking tile slightly
17 | LittleJS.setTileFixBleedScale(.5);
18 |
19 | // sound effects
20 | const sound_click = new LittleJS.Sound([1,.5]);
21 |
22 | // medals
23 | const medal_example = new LittleJS.Medal(0, 'Example Medal', 'Welcome to LittleJS!');
24 | LittleJS.medalsInit('Hello World');
25 |
26 | // game variables
27 | let particleEmitter;
28 |
29 | ///////////////////////////////////////////////////////////////////////////////
30 | function gameInit()
31 | {
32 | // create tile collision and visible tile layer
33 | const tileCollisionSize = vec2(32, 16);
34 | LittleJS.initTileCollision(tileCollisionSize);
35 | const pos = vec2();
36 | const tileLayer = new LittleJS.TileLayer(pos, tileCollisionSize);
37 |
38 | // get level data from the tiles image
39 | const mainContext = LittleJS.mainContext;
40 | const tileImage = LittleJS.textureInfos[0].image;
41 | mainContext.drawImage(tileImage, 0, 0);
42 | const imageData = mainContext.getImageData(0,0,tileImage.width,tileImage.height).data;
43 | for (pos.x = tileCollisionSize.x; pos.x--;)
44 | for (pos.y = tileCollisionSize.y; pos.y--;)
45 | {
46 | // check if this pixel is set
47 | const i = pos.x + tileImage.width*(15 + tileCollisionSize.y - pos.y);
48 | if (!imageData[4*i])
49 | continue;
50 |
51 | // set tile data
52 | const tileIndex = 1;
53 | const direction = LittleJS.randInt(4)
54 | const mirror = !LittleJS.randInt(2);
55 | const color = LittleJS.randColor();
56 | const data = new LittleJS.TileLayerData(tileIndex, direction, mirror, color);
57 | tileLayer.setData(pos, data);
58 | LittleJS.setTileCollisionData(pos, 1);
59 | }
60 |
61 | // draw tile layer with new data
62 | tileLayer.redraw();
63 |
64 | // move camera to center of collision
65 | LittleJS.setCameraPos(tileCollisionSize.scale(.5));
66 | LittleJS.setCameraScale(48);
67 |
68 | // enable gravity
69 | LittleJS.setGravity(-.01);
70 |
71 | // create particle emitter
72 | particleEmitter = new LittleJS.ParticleEmitter(
73 | vec2(16,9), 0, // emitPos, emitAngle
74 | 1, 0, 500, Math.PI, // emitSize, emitTime, emitRate, emitCone
75 | tile(0, 16), // tileIndex, tileSize
76 | hsl(1,1,1), hsl(0,0,0), // colorStartA, colorStartB
77 | hsl(0,0,0,0), hsl(0,0,0,0), // colorEndA, colorEndB
78 | 2, .2, .2, .1, .05, // time, sizeStart, sizeEnd, speed, angleSpeed
79 | .99, 1, 1, Math.PI, // damping, angleDamping, gravityScale, cone
80 | .05, .5, true, true // fadeRate, randomness, collide, additive
81 | );
82 | particleEmitter.elasticity = .3; // bounce when it collides
83 | particleEmitter.trailScale = 2; // stretch in direction of motion
84 | }
85 |
86 | ///////////////////////////////////////////////////////////////////////////////
87 | function gameUpdate()
88 | {
89 | if (LittleJS.mouseWasPressed(0))
90 | {
91 | // play sound when mouse is pressed
92 | sound_click.play(LittleJS.mousePos);
93 |
94 | // change particle color and set to fade out
95 | particleEmitter.colorStartA = hsl();
96 | particleEmitter.colorStartB = LittleJS.randColor();
97 | particleEmitter.colorEndA = particleEmitter.colorStartA.scale(1,0);
98 | particleEmitter.colorEndB = particleEmitter.colorStartB.scale(1,0);
99 |
100 | // unlock medals
101 | medal_example.unlock();
102 | }
103 |
104 | // move particles to mouse location if on screen
105 | if (LittleJS.mousePosScreen.x)
106 | particleEmitter.pos = LittleJS.mousePos;
107 | }
108 |
109 | ///////////////////////////////////////////////////////////////////////////////
110 | function gameUpdatePost()
111 | {
112 |
113 | }
114 |
115 | ///////////////////////////////////////////////////////////////////////////////
116 | function gameRender()
117 | {
118 | // draw a grey square in the background without using webgl
119 | LittleJS.drawRect(vec2(16,8), vec2(20,14), hsl(0,0,.6), 0, false);
120 |
121 | // draw the logo as a tile
122 | LittleJS.drawTile(vec2(21,5), vec2(4.5), tile(3,128));
123 | }
124 |
125 | ///////////////////////////////////////////////////////////////////////////////
126 | function gameRenderPost()
127 | {
128 | // draw to overlay canvas for hud rendering
129 | LittleJS.drawTextOverlay('LittleJS with Modules', vec2(LittleJS.mainCanvasSize.x/2, 80), 80);
130 | }
131 |
132 | ///////////////////////////////////////////////////////////////////////////////
133 | // Startup LittleJS Engine
134 | LittleJS.engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, ['tiles.png']);
--------------------------------------------------------------------------------
/examples/module/index.html:
--------------------------------------------------------------------------------
1 |
2 | LittleJS Module Demo
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/examples/module/tiles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/examples/module/tiles.png
--------------------------------------------------------------------------------
/examples/particles/tiles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/examples/particles/tiles.png
--------------------------------------------------------------------------------
/examples/platformer/data/gameLevelData.tsx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/examples/platformer/game.js:
--------------------------------------------------------------------------------
1 | /*
2 | Little JS Platforming Game
3 | - A basic platforming starter project
4 | - Platforming physics and controls
5 | - Includes destructible terrain
6 | - Control with keyboard, mouse, touch, or gamepad
7 | */
8 |
9 | 'use strict';
10 |
11 | // show the LittleJS splash screen
12 | setShowSplashScreen(true);
13 |
14 | let spriteAtlas, score, deaths;
15 |
16 | // enable touch gamepad on touch devices
17 | touchGamepadEnable = true;
18 |
19 | ///////////////////////////////////////////////////////////////////////////////
20 | function gameInit()
21 | {
22 | // engine settings
23 | gravity = -.01;
24 | objectDefaultDamping = .99;
25 | objectDefaultAngleDamping = .99;
26 | cameraScale = 4*16;
27 |
28 | // create a table of all sprites
29 | const gameTile = (i, size=16)=> tile(i, size, 0, 1);
30 | spriteAtlas =
31 | {
32 | // large tiles
33 | circle: gameTile(0),
34 | crate: gameTile(1),
35 | player: gameTile(2),
36 | enemy: gameTile(4),
37 | coin: gameTile(5),
38 |
39 | // small tiles
40 | gun: gameTile(vec2(0,2),8),
41 | grenade: gameTile(vec2(1,2),8),
42 | };
43 |
44 | // setup level
45 | buildLevel();
46 |
47 | // init game
48 | score = deaths = 0;
49 | }
50 |
51 | ///////////////////////////////////////////////////////////////////////////////
52 | function gameUpdate()
53 | {
54 | // respawn player
55 | if (player.deadTimer > 1)
56 | {
57 | player = new Player(playerStartPos);
58 | player.velocity = vec2(0,.1);
59 | sound_jump.play();
60 | }
61 |
62 | // mouse wheel = zoom
63 | cameraScale = clamp(cameraScale*(1-mouseWheel/10), 1, 1e3);
64 |
65 | // T = drop test crate
66 | if (keyWasPressed('KeyT'))
67 | new Crate(mousePos);
68 |
69 | // E = drop enemy
70 | if (keyWasPressed('KeyE'))
71 | new Enemy(mousePos);
72 |
73 | // X = make explosion
74 | if (keyWasPressed('KeyX'))
75 | explosion(mousePos);
76 |
77 | // M = move player to mouse
78 | if (keyWasPressed('KeyM'))
79 | player.pos = mousePos;
80 |
81 | // R = restart level
82 | if (keyWasPressed('KeyR'))
83 | buildLevel();
84 | }
85 |
86 | ///////////////////////////////////////////////////////////////////////////////
87 | function getCameraTarget()
88 | {
89 | // camera is above player
90 | const offset = 200/cameraScale*percent(mainCanvasSize.y, 300, 600);
91 | return player.pos.add(vec2(0, offset));
92 | }
93 |
94 | ///////////////////////////////////////////////////////////////////////////////
95 | function gameUpdatePost()
96 | {
97 | // update camera
98 | cameraPos = cameraPos.lerp(getCameraTarget(), clamp(player.getAliveTime()/2));
99 | }
100 |
101 | ///////////////////////////////////////////////////////////////////////////////
102 | function gameRender()
103 | {
104 | }
105 |
106 | ///////////////////////////////////////////////////////////////////////////////
107 | function gameRenderPost()
108 | {
109 | // draw to overlay canvas for hud rendering
110 | const drawText = (text, x, y, size=40) =>
111 | {
112 | overlayContext.textAlign = 'center';
113 | overlayContext.textBaseline = 'top';
114 | overlayContext.font = size + 'px arial';
115 | overlayContext.fillStyle = '#fff';
116 | overlayContext.lineWidth = 3;
117 | overlayContext.strokeText(text, x, y);
118 | overlayContext.fillText(text, x, y);
119 | }
120 | drawText('Score: ' + score, overlayCanvas.width*1/4, 20);
121 | drawText('Deaths: ' + deaths, overlayCanvas.width*3/4, 20);
122 | }
123 |
124 | ///////////////////////////////////////////////////////////////////////////////
125 | // Startup LittleJS Engine
126 | engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, ['tiles.png', 'tilesLevel.png']);
--------------------------------------------------------------------------------
/examples/platformer/gameLevel.js:
--------------------------------------------------------------------------------
1 | /*
2 | LittleJS Platformer Example - Level Generator
3 | - Procedurally generates level geometry
4 | - Picks colors for the level and background
5 | - Creates ladders and spawns enemies and crates
6 | */
7 |
8 | 'use strict';
9 |
10 | const tileType_ladder = -1;
11 | const tileType_empty = 0;
12 | const tileType_solid = 1;
13 | const tileType_breakable = 2;
14 |
15 | let player, playerStartPos, tileLayers, foregroundLayerIndex, sky;
16 | let levelSize, levelColor, levelBackgroundColor, levelOutlineColor;
17 |
18 | function buildLevel()
19 | {
20 | // destroy all objects
21 | engineObjectsDestroy();
22 |
23 | // create the level
24 | levelColor = randColor(hsl(0,0,.2), hsl(0,0,.8));
25 | levelBackgroundColor = levelColor.mutate().scale(.4,1);
26 | levelOutlineColor = levelColor.mutate().add(hsl(0,0,.4)).clamp();
27 | loadLevel();
28 |
29 | // create sky object with gradient and stars
30 | sky = new Sky;
31 |
32 | // create parallax layers
33 | for (let i=3; i--;)
34 | new ParallaxLayer(i);
35 |
36 | // spawn player
37 | player = new Player(playerStartPos);
38 | cameraPos = getCameraTarget();
39 | }
40 |
41 | function loadLevel(level=0)
42 | {
43 | // load level data from an exported Tiled js file
44 | const tileMapData = TileMaps['gameLevelData'];
45 | levelSize = vec2(tileMapData.width, tileMapData.height);
46 | initTileCollision(levelSize);
47 |
48 | // create table for tiles in the level tilemap
49 | const tileLookup =
50 | {
51 | circle: 1,
52 | ground: 2,
53 | ladder: 4,
54 | metal: 5,
55 | player: 17,
56 | crate: 18,
57 | enemy: 19,
58 | coin: 20,
59 | }
60 |
61 | // set all level data tiles
62 | tileLayers = [];
63 | playerStartPos = vec2(1, levelSize.y);
64 | const layerCount = tileMapData.layers.length;
65 | foregroundLayerIndex = layerCount-1;
66 | for (let layer=layerCount; layer--;)
67 | {
68 | const layerData = tileMapData.layers[layer].data;
69 | const tileLayer = new TileLayer(vec2(), levelSize, tile(0,16,1));
70 | tileLayer.renderOrder = -1e3+layer;
71 | tileLayers[layer] = tileLayer;
72 |
73 | for (let x=levelSize.x; x--;)
74 | for (let y=levelSize.y; y--;)
75 | {
76 | const pos = vec2(x,levelSize.y-1-y);
77 | const tile = layerData[y*levelSize.x+x];
78 |
79 | if (tile >= tileLookup.player)
80 | {
81 | // create object instead of tile
82 | const objectPos = pos.add(vec2(.5));
83 | if (tile == tileLookup.player)
84 | playerStartPos = objectPos;
85 | if (tile == tileLookup.crate)
86 | new Crate(objectPos);
87 | if (tile == tileLookup.enemy)
88 | new Enemy(objectPos);
89 | if (tile == tileLookup.coin)
90 | new Coin(objectPos);
91 | continue;
92 | }
93 |
94 | // get tile type for special tiles
95 | let tileType = tileType_empty;
96 | if (tile > 0)
97 | tileType = tileType_breakable;
98 | if (tile == tileLookup.ladder)
99 | tileType = tileType_ladder;
100 | if (tile == tileLookup.metal)
101 | tileType = tileType_solid;
102 | if (tileType)
103 | {
104 | // set collision for solid tiles
105 | if (layer == foregroundLayerIndex)
106 | setTileCollisionData(pos, tileType);
107 |
108 | // randomize tile appearance
109 | let direction, mirror, color;
110 | if (tileType == tileType_breakable)
111 | {
112 | direction = randInt(4);
113 | mirror = randInt(2);
114 | color = layer ? levelColor : levelBackgroundColor;
115 | color = color.mutate(.03);
116 | }
117 |
118 | // set tile layer data
119 | const data = new TileLayerData(tile-1, direction, mirror, color);
120 | tileLayer.setData(pos, data);
121 | }
122 | }
123 | tileLayer.redraw();
124 | }
125 |
126 | // apply decoration to all level tiles
127 | const pos = vec2();
128 | for (let layer=layerCount; layer--;)
129 | for (pos.x=levelSize.x; pos.x--;)
130 | for (pos.y=levelSize.y; pos.y--;)
131 | decorateTile(pos, layer);
132 | }
133 |
134 | function decorateTile(pos, layer=1)
135 | {
136 | ASSERT((pos.x|0) == pos.x && (pos.y|0)== pos.y);
137 | const tileLayer = tileLayers[layer];
138 |
139 | if (layer == foregroundLayerIndex)
140 | {
141 | const tileType = getTileCollisionData(pos);
142 | if (tileType <= 0)
143 | {
144 | // force it to clear if it is empty
145 | tileType || tileLayer.setData(pos, new TileLayerData, 1);
146 | return;
147 | }
148 | if (tileType == tileType_breakable)
149 | for (let i=4;i--;)
150 | {
151 | // outline towards neighbors of differing type
152 | const neighborTileType = getTileCollisionData(pos.add(vec2().setDirection(i)));
153 | if (neighborTileType == tileType)
154 | continue;
155 |
156 | // make pixel perfect outlines
157 | const size = i&1 ? vec2(2, 16) : vec2(16, 2);
158 | tileLayer.context.fillStyle = levelOutlineColor.mutate(.1);
159 | const drawPos = pos.scale(16)
160 | .add(vec2(i==1?14:0,(i==0?14:0)))
161 | .subtract((i&1? vec2(0,8-size.y/2) : vec2(8-size.x/2,0)));
162 | tileLayer.context.fillRect(
163 | drawPos.x, tileLayer.canvas.height - drawPos.y, size.x, -size.y);
164 | }
165 | }
166 | else
167 | {
168 | // make round corners
169 | for (let i=4; i--;)
170 | {
171 | // check corner neighbors
172 | const neighborTileDataA = tileLayer.getData(pos.add(vec2().setDirection(i))).tile;
173 | const neighborTileDataB = tileLayer.getData(pos.add(vec2().setDirection((i+1)%4))).tile;
174 | if (neighborTileDataA > 0 || neighborTileDataB > 0)
175 | continue;
176 |
177 | const directionVector = vec2().setAngle(i*PI/2+PI/4, 10).floor();
178 | const drawPos = pos.add(vec2(.5)) // center
179 | .scale(16).add(directionVector).floor(); // direction offset
180 |
181 | // clear rect without any scaling to prevent blur from filtering
182 | const s = 2;
183 | tileLayer.context.clearRect(
184 | drawPos.x - s/2, tileLayer.canvas.height - drawPos.y - s/2, s, s);
185 | }
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/examples/platformer/gamePlayer.js:
--------------------------------------------------------------------------------
1 | /*
2 | LittleJS Platformer Example - Player
3 | - Extends character class
4 | - Uses character physics system
5 | - Player can jump, shoot, roll, and throw grenades
6 | - Supports keyboard, mouse, and gamepad controls
7 | - Keeps track of player deaths
8 | */
9 |
10 | 'use strict';
11 |
12 | class Player extends Character
13 | {
14 | update()
15 | {
16 | // movement control
17 | this.moveInput = isUsingGamepad ? gamepadStick(0) : keyDirection();
18 | this.holdingJump = keyIsDown('ArrowUp') || gamepadIsDown(0);
19 | this.holdingShoot = !isUsingGamepad && mouseIsDown(0) || keyIsDown('KeyZ') || gamepadIsDown(2);
20 | this.pressingThrow = keyIsDown('KeyC') || mouseIsDown(1) || gamepadIsDown(1);
21 | this.pressedDodge = keyIsDown('KeyX') || mouseIsDown(2) || gamepadIsDown(3);
22 | super.update();
23 | }
24 |
25 | kill()
26 | {
27 | ++deaths;
28 | super.kill();
29 | }
30 | }
--------------------------------------------------------------------------------
/examples/platformer/index.html:
--------------------------------------------------------------------------------
1 |
2 | LittleJS Platforming Game
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/examples/platformer/tiles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/examples/platformer/tiles.png
--------------------------------------------------------------------------------
/examples/platformer/tilesLevel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/examples/platformer/tilesLevel.png
--------------------------------------------------------------------------------
/examples/puzzle/index.html:
--------------------------------------------------------------------------------
1 |
2 | LittleJS Puzzle Game
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/examples/puzzle/tiles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/examples/puzzle/tiles.png
--------------------------------------------------------------------------------
/examples/screenshot.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/examples/screenshot.jpg
--------------------------------------------------------------------------------
/examples/shorts/animation.js:
--------------------------------------------------------------------------------
1 | function gameRender()
2 | {
3 | drawRect(vec2(0,0), vec2(100), GRAY); // draw background
4 |
5 | {
6 | // animate using multiple frames
7 | const pos = vec2(-5, 2*abs(Math.sin(time*2*PI)));
8 | const frame = (time*4)%2|0;
9 | drawTile(pos, vec2(7), tile(3).frame(frame), RED);
10 | }
11 | {
12 | // animate with stretch and squash
13 | const s = Math.sin(time*9)*.5;
14 | const size = vec2(7-s,7+s);
15 | const pos = vec2(5,size.y-7);
16 | drawTile(pos, size, tile(5), GREEN);
17 | }
18 | }
--------------------------------------------------------------------------------
/examples/shorts/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/examples/shorts/blending.js:
--------------------------------------------------------------------------------
1 | function gameRender()
2 | {
3 | const circleTile = tile(2, 128); // get the circle tile
4 |
5 | // additive blend
6 | setBlendMode(1);
7 | drawTile(vec2(-4,-2), vec2(7), circleTile, rgb(1,0,0));
8 | drawTile(vec2(-6, 2), vec2(7), circleTile, rgb(0,1,0));
9 | drawTile(vec2(-8,-2), vec2(7), circleTile, rgb(0,0,1));
10 |
11 | // alpha blend
12 | setBlendMode(0);
13 | drawTile(vec2(4,-2), vec2(7), circleTile, rgb(1,0,0,.5));
14 | drawTile(vec2(6, 2), vec2(7), circleTile, rgb(0,1,0,.5));
15 | drawTile(vec2(8,-2), vec2(7), circleTile, rgb(0,0,1,.5));
16 | }
--------------------------------------------------------------------------------
/examples/shorts/clock.js:
--------------------------------------------------------------------------------
1 | function gameRender()
2 | {
3 | // draw background
4 | for(let i=12; i--;)
5 | {
6 | const a = i/6*PI;
7 | const pos = vec2(0,7).rotate(a);
8 | drawRect(pos, vec2(.5,1), hsl(i/12,1,.5), a);
9 | }
10 |
11 | // get current time
12 | const d = Date().slice(16,24);
13 | const s = d.slice(6,8)|0;
14 | const m = d.slice(3,5)|0;
15 | const h = (d.slice(0,2)|0) + m/60;
16 |
17 | // draw clock hands
18 | drawLine(vec2(0,0), vec2(0,4).rotate(h/12*2*PI), 1);
19 | drawLine(vec2(0,0), vec2(0,6).rotate(m/60*2*PI), .4);
20 | drawLine(vec2(0,0), vec2(0,8).rotate(s/60*2*PI), .1);
21 | }
--------------------------------------------------------------------------------
/examples/shorts/colors.js:
--------------------------------------------------------------------------------
1 | function gameRender()
2 | {
3 | // color constants
4 | drawRect(vec2(-8, 5), vec2(3), RED);
5 | drawRect(vec2(-4, 5), vec2(3), YELLOW);
6 | drawRect(vec2(-0, 5), vec2(3), GREEN);
7 | drawRect(vec2( 4, 5), vec2(3), BLUE);
8 | drawRect(vec2( 8, 5), vec2(3), WHITE);
9 |
10 | // rgb and hsl color
11 | drawRect(vec2(-8, 0), vec2(3), new Color(1,0,0));
12 | drawRect(vec2(-4, 0), vec2(3), rgb(0,1,1));
13 | drawRect(vec2(-0, 0), vec2(3), hsl(0,1,.5));
14 | drawRect(vec2( 4, 0), vec2(3), hsl(.6,.5,.5));
15 | drawRect(vec2( 8, 0), vec2(3), hsl(0,0,1));
16 |
17 | // color lerpping
18 | for(let i=5; i--;)
19 | {
20 | const color1 = RED;
21 | const color2 = YELLOW;
22 | const color = color1.lerp(color2, i/4);
23 | drawRect(vec2(-8+i*4, -5), vec2(3), color);
24 | }
25 | }
--------------------------------------------------------------------------------
/examples/shorts/helloWorld.js:
--------------------------------------------------------------------------------
1 | function gameRender()
2 | {
3 | // draw text
4 | drawText('Hello World', vec2(0,2), 3);
5 |
6 | // draw a tile
7 | drawTile(vec2(0,-2), vec2(7), tile(3,128));
8 | }
--------------------------------------------------------------------------------
/examples/shorts/particles.js:
--------------------------------------------------------------------------------
1 | function gameInit()
2 | {
3 | gravity = -.01; // set default gravity
4 |
5 | // fire
6 | new ParticleEmitter(
7 | vec2(-5,-2), 0, // pos, angle
8 | 2, 0, 200, PI, // emitSize, emitTime, emitRate, emitCone
9 | tile(0), // tileInfo
10 | rgb(1,.5,.1), rgb(1,.1,.1), // colorStartA, colorStartB
11 | rgb(1,.5,.1,0), rgb(1,.1,.1,0), // colorEndA, colorEndB
12 | .7, 2, 0, .2, .05, // time, sizeStart, sizeEnd, speed, angleSpeed
13 | .9, 1, -1, PI, .05, // damp, angleDamp, gravity, particleCone, fade
14 | .5, 0, 1, 0, 1e9 // randomness, collide, additive, colorLinear, renderOrder
15 | );
16 |
17 | // smoke
18 | new ParticleEmitter(
19 | vec2(5,-2), 0, // pos, angle
20 | 3, 0, 100, PI, // emitSize, emitTime, emitRate, emitCone
21 | tile(0), // tileInfo
22 | hsl(0,0,0,.5), hsl(0,0,1,.5), // colorStartA, colorStartB
23 | hsl(0,0,0,0), hsl(0,0,1,0), // colorEndA, colorEndB
24 | 1, 1, 5, .2, .01, // time, sizeStart, sizeEnd, speed, angleSpeed
25 | .85, 1, -1, PI, .3, // damp, angleDamp, gravity, particleCone, fade
26 | .5, 0, 0, 1, 1e8 // randomness, collide, additive, colorLinear, renderOrder
27 | );
28 | }
--------------------------------------------------------------------------------
/examples/shorts/platformer.js:
--------------------------------------------------------------------------------
1 | class PhysicsObject extends EngineObject
2 | {
3 | constructor(pos, size, color)
4 | {
5 | super(pos, size, 0, 0, color);
6 | this.setCollision(); // make object collide
7 | this.mass = 0; // make object have static physics
8 | }
9 | }
10 |
11 | class Player extends PhysicsObject
12 | {
13 | constructor(pos)
14 | {
15 | super(pos, vec2(2,4), RED);
16 | this.mass = 1; // make object have dynamic physics
17 | }
18 |
19 | update()
20 | {
21 | // apply movement controls
22 | const moveInput = keyDirection();
23 | this.velocity.x += moveInput.x * (this.groundObject ? .1: .01);
24 | if (this.groundObject && moveInput.y > 0)
25 | this.velocity.y = .9; // jump
26 |
27 | super.update(); // call parent update function
28 | cameraPos = vec2(this.pos.x, 9); // move camera with player
29 | }
30 | }
31 |
32 | function gameInit()
33 | {
34 | // setup level
35 | gravity = -.05;
36 | new Player(vec2(0,4));
37 |
38 | // create random objects
39 | new PhysicsObject(vec2(), vec2(1e3,4), GRAY); // ground
40 | for (let i = 1; i < 500; ++i)
41 | new PhysicsObject(vec2(i*10+randInt(4), 0), vec2(2+randInt(20),4+randInt(8)), GREEN);
42 | }
--------------------------------------------------------------------------------
/examples/shorts/playSound.js:
--------------------------------------------------------------------------------
1 | class SoundButton extends EngineObject
2 | {
3 | constructor(pos, icon, sound)
4 | {
5 | super(pos, vec2(5,4));
6 | this.icon = icon;
7 | this.sound = sound;
8 | }
9 |
10 | update()
11 | {
12 | // play sound when clicked
13 | if (mouseWasPressed(0) && isOverlapping(this.pos, this.size, mousePos))
14 | this.sound.play(this.pos);
15 | }
16 |
17 | render()
18 | {
19 | // draw the button and icon
20 | drawRect(this.pos, this.size, hsl(0,0,.8));
21 | drawTextOverlay(this.icon, this.pos, 3);
22 | }
23 | }
24 |
25 | function gameInit()
26 | {
27 | // create a grid of buttons with different sounds
28 | new SoundButton(vec2(-6, 5),'💰', new Sound([,,1675,,.06,.24,1,1.82,,,837,.06]));
29 | new SoundButton(vec2( 0, 5),'🥊', new Sound([,,925,.04,.3,.6,1,.3,,6.27,-184,.09,.17]));
30 | new SoundButton(vec2( 6, 5),'✨', new Sound([,,539,0,.04,.29,1,1.92,,,567,.02,.02,,,,.04]));
31 | new SoundButton(vec2(-6, 0),'🐁', new Sound([,.2,1e3,.02,,.01,2,,18,,475,.01,.01]));
32 | new SoundButton(vec2( 0, 0),'🎹', new Sound([1.5,.5,270,,.1,,1,1.5,,,,,,,,.1,.01]));
33 | new SoundButton(vec2( 6, 0),'🏌️', new Sound([,,150,.05,,.05,,1.3,,,,,,3]));
34 | new SoundButton(vec2(-6,-5),'🌊', new Sound([,.2,40,.5,,1.5,,11,,,,,,199]));
35 | new SoundButton(vec2( 0,-5),'🛰️', new Sound([,.5,847,.02,.3,.9,1,1.67,,,-294,.04,.13,,,,.1]));
36 | new SoundButton(vec2( 6,-5),'⚡', new Sound([,,471,,.09,.47,4,1.06,-6.7,,,,,.9,61,.1,,.82,.1]));
37 | }
--------------------------------------------------------------------------------
/examples/shorts/pong.js:
--------------------------------------------------------------------------------
1 | // globals objects
2 | let paddle, ball;
3 |
4 | class PhysicsObject extends EngineObject
5 | {
6 | constructor(pos, size)
7 | {
8 | super(pos, size); // set object position and size
9 | this.setCollision(); // make object collide
10 | this.mass = 0; // make object have static physics
11 | }
12 | }
13 |
14 | function gameInit()
15 | {
16 | // setup level
17 | const levelSize = vec2(38, 21); // size of play area
18 | cameraPos = levelSize.scale(.5); // center camera in level
19 | canvasFixedSize = vec2(1280, 720); // use a 720p fixed size canvas
20 |
21 | // create objects
22 | paddle = new PhysicsObject(vec2(0,1), vec2(6,1)); // create player's paddle
23 | new PhysicsObject(vec2(-.5,levelSize.y/2), vec2(1,100)); // left wall
24 | new PhysicsObject(vec2(levelSize.x+.5,levelSize.y/2), vec2(1,100)); // right wall
25 | new PhysicsObject(vec2(levelSize.x/2,levelSize.y+.5), vec2(100,1)); // top wall
26 | }
27 |
28 | function gameUpdate()
29 | {
30 | if (!ball || ball.pos.y < -1) // spawn ball
31 | {
32 | ball && ball.destroy(); // destroy old ball
33 | ball = new PhysicsObject(cameraPos); // create a ball
34 | ball.velocity = vec2(.2); // give ball some movement
35 | ball.elasticity = 1; // make ball bounce
36 | ball.mass = 1; // make ball have dynamic physics
37 | }
38 |
39 | paddle.pos.x = mousePos.x; // move paddle to mouse
40 | }
--------------------------------------------------------------------------------
/examples/shorts/shapes.js:
--------------------------------------------------------------------------------
1 | function gameRender()
2 | {
3 | // polygon shapes
4 | for(let j=3; j<7; ++j)
5 | {
6 | let points = [];
7 | for(let i=0; i tile(i, size);
7 | spriteAtlas =
8 | {
9 | circle: gameTile(0),
10 | crate: gameTile(1),
11 | icon: gameTile(2),
12 | circleBig: gameTile(2, 128),
13 | iconBig: gameTile(3, 128),
14 | };
15 | }
16 |
17 | function gameRender()
18 | {
19 | drawRect(vec2(0,0), vec2(100), GRAY); // draw background
20 |
21 | // draw a sprite from the atlas
22 | let pos = vec2(Math.sin(time)*5,-3);// world position to draw
23 | let angle = 0; // world space angle to draw the tile
24 | let size = vec2(9); // world size of the tile
25 | let mirror = 0; // should tile be mirrored?
26 | let color = hsl(0,0,1); // color to multiply the tile by
27 | let additiveColor = hsl(0,0,0,0); // color to add to
28 | drawTile(pos, size, spriteAtlas.iconBig, color, angle, mirror, additiveColor);
29 |
30 | // draw more sprites from the atlas
31 | drawTile(vec2(-7,4), vec2(5), spriteAtlas.crate);
32 | drawTile(vec2( 0,4), vec2(5), spriteAtlas.circle);
33 | drawTile(vec2( 7,4), vec2(5), spriteAtlas.circleBig);
34 | }
--------------------------------------------------------------------------------
/examples/shorts/systemFont.js:
--------------------------------------------------------------------------------
1 | function gameRender()
2 | {
3 | // draw text with built in font image system font
4 | const font = new FontImage;
5 | font.drawText('System Font Test', cameraPos.add(vec2(0,3)), .2, true);
6 |
7 | // show every character in the system font
8 | let s = '';
9 | for(let i=32; i<128; ++i)
10 | {
11 | if (i%32 == 0)
12 | s += '\n';
13 | s += String.fromCharCode(i);
14 | }
15 | font.drawText(s, cameraPos, .1, true);
16 | }
--------------------------------------------------------------------------------
/examples/shorts/texture.js:
--------------------------------------------------------------------------------
1 | function gameRender()
2 | {
3 | // show the full texture
4 | let pos = vec2(0,0); // world position to draw
5 | let size = vec2(15); // world size of the tile
6 | let color = hsl(0,0,1); // color to multiply the tile by
7 | let tilePos = vec2(0,0); // top left corner of tile in pixels
8 | let tileSize = vec2(256); // size of tile in pixels
9 | let tileInfo = new TileInfo(tilePos, tileSize); // tile info
10 | drawRect(pos, size, GRAY); // draw background
11 | drawTile(pos, size, tileInfo, color);
12 | }
--------------------------------------------------------------------------------
/examples/shorts/tileLayer.js:
--------------------------------------------------------------------------------
1 | function gameInit()
2 | {
3 | cameraPos = vec2(16); // setup camera
4 | gravity = -.01; // enable gravity
5 |
6 | // create tile collision and visible tile layer
7 | initTileCollision(vec2(32));
8 | const pos = vec2();
9 | const tileLayer = new TileLayer(pos, tileCollisionSize);
10 |
11 | // init the tile layer
12 | for (pos.x = tileCollisionSize.x; pos.x--;)
13 | for (pos.y = tileCollisionSize.y; pos.y--;)
14 | {
15 | // check if tile should be solid
16 | if (rand() < .7)
17 | continue;
18 |
19 | // set tile data
20 | const tileIndex = 1;
21 | const direction = randInt(4)
22 | const mirror = !randInt(2);
23 | const color = randColor(WHITE, hsl(0,0,.2));
24 | const data = new TileLayerData(tileIndex, direction, mirror, color);
25 | tileLayer.setData(pos, data);
26 | setTileCollisionData(pos, 1);
27 | }
28 | tileLayer.redraw(); // redraw tile layer with new data
29 | }
30 |
31 | function gameUpdate()
32 | {
33 | if (mouseWasPressed(0))
34 | {
35 | // create particle emitter
36 | const hue = rand();
37 | const particleEmitter = new ParticleEmitter(
38 | mousePos, 0, // emitPos, emitAngle
39 | 0, 0.1, 500, PI, // emitSize, emitTime, emitRate, emitCone
40 | tile(0, 16), // tileIndex, tileSize
41 | hsl(hue,1,.5), hsl(hue,1,1), // colorStartA, colorStartB
42 | hsl(hue,1,.5,0), hsl(hue,1,1,0), // colorEndA, colorEndB
43 | 2, .2, .2, .2, .05, // time, sizeStart, sizeEnd, speed, angleSpeed
44 | .99, 1, 1, PI, // damping, angleDamping, gravityScale, cone
45 | .05, .8, true, false // fadeRate, randomness, collide, additive
46 | );
47 | particleEmitter.elasticity = .5; // bounce when it collides
48 | particleEmitter.trailScale = 2; // stretch in direction of motion
49 | }
50 | }
--------------------------------------------------------------------------------
/examples/shorts/tiles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/examples/shorts/tiles.png
--------------------------------------------------------------------------------
/examples/shorts/tiltedView.js:
--------------------------------------------------------------------------------
1 | class GameObject extends EngineObject
2 | {
3 | update()
4 | {
5 | this.renderOrder = -this.pos.y; // sort by y position
6 | super.update();
7 | }
8 |
9 | render()
10 | {
11 | // adjust draw postion to be at the bottom of the object
12 | const pos = this.pos.add(vec2(0,this.size.y/2).rotate(this.angle));
13 | drawTile(pos, this.size, this.tileInfo, this.color, this.angle);
14 | }
15 | }
16 |
17 | class Player extends GameObject
18 | {
19 | update()
20 | {
21 | // apply movement controls
22 | const moveInput = keyDirection().clampLength(1).scale(.2); // clamp and scale input
23 | this.velocity = this.velocity.add(moveInput); // apply movement
24 |
25 | super.update(); // call parent update function
26 | cameraPos = this.pos.add(vec2(0,2)); // move camera with player
27 | }
28 | }
29 |
30 |
31 | function gameInit()
32 | {
33 | // setup level
34 | objectDefaultDamping = .7;
35 | new Player(vec2(), vec2(3), tile(5), 0, RED);
36 |
37 | // create background objects
38 | for (let i = 1; i < 300; ++i)
39 | new EngineObject(randInCircle(90), vec2(rand(59),rand(59)), 0, 0, hsl(.4,.3,rand(.1,.2),.8), -1e5);
40 |
41 | // create world objects
42 | for (let i = 1; i < 1e3; ++i)
43 | new GameObject(randInCircle(7+i,7), vec2(rand(1,2),rand(4,9)), 0, 0, hsl(.1,.5,rand(.2,.3)));
44 | }
--------------------------------------------------------------------------------
/examples/shorts/timers.js:
--------------------------------------------------------------------------------
1 | class TimerButton extends EngineObject
2 | {
3 | constructor(pos, time)
4 | {
5 | super(pos, vec2(5.5));
6 | this.time = time;
7 | this.timer = new Timer;
8 | }
9 |
10 | update()
11 | {
12 | // start or stop the timer when clicked
13 | if (mouseWasPressed(0) && isOverlapping(this.pos, this.size, mousePos))
14 | this.timer.isSet() ? this.timer.unset() : this.timer.set(this.time);
15 | }
16 |
17 | render()
18 | {
19 | // get color based on timer state
20 | this.color = this.timer.isSet() ? this.timer.active() ? BLUE : RED : GRAY;
21 |
22 | // draw the button and timer text
23 | drawRect(this.pos, this.size, this.color);
24 | if (this.timer.isSet())
25 | {
26 | drawTextOverlay(this.timer.get().toFixed(1), this.pos.add(vec2(0,1)), 2);
27 | const percent = this.timer.getPercent()*100|0;
28 | drawTextOverlay(percent+'%', this.pos.add(vec2(0,-1)), 2);
29 | }
30 | else
31 | drawTextOverlay('Click\nto set', this.pos, 2);
32 | }
33 | }
34 |
35 | function gameInit()
36 | {
37 | // create some timer buttons
38 | new TimerButton(vec2(-5, 0), 3);
39 | new TimerButton(vec2( 5, 0), 0);
40 | }
--------------------------------------------------------------------------------
/examples/shorts/topDown.js:
--------------------------------------------------------------------------------
1 | class PhysicsObject extends EngineObject
2 | {
3 | constructor(pos, size, color)
4 | {
5 | super(pos, size, 0, 0, color);
6 | this.setCollision(); // make object collide
7 | this.mass = 0; // make object have static physics
8 | }
9 | }
10 |
11 | class Player extends PhysicsObject
12 | {
13 | constructor(pos)
14 | {
15 | super(pos, vec2(2), RED);
16 | this.mass = 1; // make object have dynamic physics
17 | this.renderOrder = 1; // render player on top of other objects
18 | }
19 |
20 | update()
21 | {
22 | // apply movement controls
23 | const moveInput = keyDirection().clampLength(1).scale(.2); // clamp and scale input
24 | this.velocity = this.velocity.add(moveInput); // apply movement
25 |
26 | super.update(); // call parent update function
27 | cameraPos = this.pos; // move camera with player
28 | }
29 | }
30 |
31 | function gameInit()
32 | {
33 | // setup level
34 | objectDefaultDamping = .7;
35 | new Player();
36 |
37 | // create background objects
38 | for (let i = 1; i < 300; ++i)
39 | new EngineObject(randInCircle(90), vec2(rand(59),rand(59)), 0, rand(), hsl(.4,.5,rand(.2)));
40 |
41 | // create foreground objects
42 | for (let i = 1; i < 300; ++i)
43 | new PhysicsObject(randInCircle(7+i,7), vec2(rand(4,9),rand(4,9)), hsl(0,0,rand(.8,1)));
44 | }
--------------------------------------------------------------------------------
/examples/starter/build.bat:
--------------------------------------------------------------------------------
1 | rem LittleJS Build Script
2 | call node build.js
--------------------------------------------------------------------------------
/examples/starter/build.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * LittleJS Build System
5 | */
6 |
7 | 'use strict';
8 |
9 | const PROGRAM_TITLE = 'Little JS Starter Project';
10 | const PROGRAM_NAME = 'game';
11 | const BUILD_FOLDER = 'build';
12 | const sourceFiles =
13 | [
14 | '../../dist/littlejs.release.js',
15 | 'game.js',
16 | // add your game's files here
17 | ];
18 | const dataFiles =
19 | [
20 | 'tiles.png',
21 | // add your game's data files here
22 | ];
23 |
24 | console.log(`Building ${PROGRAM_NAME}...`);
25 | const startTime = Date.now();
26 | const fs = require('node:fs');
27 | const child_process = require('node:child_process');
28 |
29 | // rebuild engine
30 | //child_process.execSync(`npm run build`, { stdio: 'inherit' });
31 |
32 | // remove old files and setup build folder
33 | fs.rmSync(BUILD_FOLDER, { recursive: true, force: true });
34 | fs.rmSync(`${PROGRAM_NAME}.zip`, { force: true });
35 | fs.mkdirSync(BUILD_FOLDER);
36 |
37 | // copy data files
38 | for(const file of dataFiles)
39 | fs.copyFileSync(file, `${BUILD_FOLDER}/${file}`);
40 |
41 | Build
42 | (
43 | `${BUILD_FOLDER}/index.js`,
44 | sourceFiles,
45 | [closureCompilerStep, uglifyBuildStep, htmlBuildStep, zipBuildStep]
46 | );
47 |
48 | console.log('');
49 | console.log(`Build Completed in ${((Date.now() - startTime)/1e3).toFixed(2)} seconds!`);
50 | console.log(`Size of ${PROGRAM_NAME}.zip: ${fs.statSync(`${PROGRAM_NAME}.zip`).size} bytes`);
51 |
52 | ///////////////////////////////////////////////////////////////////////////////
53 |
54 | // A single build with its own source files, build steps, and output file
55 | // - each build step is a callback that accepts a single filename
56 | function Build(outputFile, files=[], buildSteps=[])
57 | {
58 | // copy files into a buffer
59 | let buffer = '';
60 | for (const file of files)
61 | buffer += fs.readFileSync(file) + '\n';
62 |
63 | // output file
64 | fs.writeFileSync(outputFile, buffer, {flag: 'w+'});
65 |
66 | // execute build steps in order
67 | for (const buildStep of buildSteps)
68 | buildStep(outputFile);
69 | }
70 |
71 | function closureCompilerStep(filename)
72 | {
73 | console.log(`Running closure compiler...`);
74 |
75 | const filenameTemp = filename + '.tmp';
76 | fs.copyFileSync(filename, filenameTemp);
77 | child_process.execSync(`npx google-closure-compiler --js=${filenameTemp} --js_output_file=${filename} --compilation_level=ADVANCED --warning_level=VERBOSE --jscomp_off=* --assume_function_wrapper`, {stdio: 'inherit'});
78 | fs.rmSync(filenameTemp);
79 | };
80 |
81 | function uglifyBuildStep(filename)
82 | {
83 | console.log(`Running uglify...`);
84 | child_process.execSync(`npx uglifyjs ${filename} -c -m -o ${filename}`, {stdio: 'inherit'});
85 | };
86 |
87 | function htmlBuildStep(filename)
88 | {
89 | console.log(`Building html...`);
90 |
91 | // create html file
92 | let buffer = '';
93 | buffer += '';
94 | buffer += `${PROGRAM_TITLE} `;
95 | buffer += '';
96 | buffer += '';
97 | buffer += '';
100 |
101 | // output html file
102 | fs.writeFileSync(`${BUILD_FOLDER}/index.html`, buffer, {flag: 'w+'});
103 | };
104 |
105 | function zipBuildStep(filename)
106 | {
107 | console.log(`Zipping...`);
108 |
109 | const ect = '../../../node_modules/ect-bin/vendor/win32/ect.exe';
110 | const args = ['-9', '-strip', '-zip', `../${PROGRAM_NAME}.zip`, 'index.html', ...dataFiles];
111 | child_process.spawnSync(ect, args, {stdio: 'inherit', cwd: BUILD_FOLDER});
112 | };
--------------------------------------------------------------------------------
/examples/starter/game.js:
--------------------------------------------------------------------------------
1 | /*
2 | Little JS Starter Project
3 | - A simple starter project for LittleJS
4 | - Demos all the main engine features
5 | - Builds to a zip file
6 | */
7 |
8 | 'use strict';
9 |
10 | // show the LittleJS splash screen
11 | setShowSplashScreen(true);
12 |
13 | // fix texture bleeding by shrinking tile slightly
14 | tileFixBleedScale = .5;
15 |
16 | // sound effects
17 | const sound_click = new Sound([1,.5]);
18 |
19 | // medals
20 | const medal_example = new Medal(0, 'Example Medal', 'Welcome to LittleJS!');
21 | medalsInit('Hello World');
22 |
23 | // game variables
24 | let particleEmitter;
25 |
26 | ///////////////////////////////////////////////////////////////////////////////
27 | function gameInit()
28 | {
29 | // create tile collision and visible tile layer
30 | initTileCollision(vec2(32,16));
31 | const pos = vec2();
32 | const tileLayer = new TileLayer(pos, tileCollisionSize);
33 |
34 | // get level data from the tiles image
35 | const tileImage = textureInfos[0].image;
36 | mainContext.drawImage(tileImage,0,0);
37 | const imageData = mainContext.getImageData(0,0,tileImage.width,tileImage.height).data;
38 | for (pos.x = tileCollisionSize.x; pos.x--;)
39 | for (pos.y = tileCollisionSize.y; pos.y--;)
40 | {
41 | // check if this pixel is set
42 | const i = pos.x + tileImage.width*(15 + tileCollisionSize.y - pos.y);
43 | if (!imageData[4*i])
44 | continue;
45 |
46 | // set tile data
47 | const tileIndex = 1;
48 | const direction = randInt(4)
49 | const mirror = !randInt(2);
50 | const color = randColor();
51 | const data = new TileLayerData(tileIndex, direction, mirror, color);
52 | tileLayer.setData(pos, data);
53 | setTileCollisionData(pos, 1);
54 | }
55 |
56 | // draw tile layer with new data
57 | tileLayer.redraw();
58 |
59 | // setup camera
60 | cameraPos = vec2(16,8);
61 | cameraScale = 48;
62 |
63 | // enable gravity
64 | gravity = -.01;
65 |
66 | // create particle emitter
67 | particleEmitter = new ParticleEmitter(
68 | vec2(16,9), 0, // emitPos, emitAngle
69 | 0, 0, 500, PI, // emitSize, emitTime, emitRate, emitCone
70 | tile(0, 16), // tileIndex, tileSize
71 | hsl(1,1,1), hsl(0,0,0), // colorStartA, colorStartB
72 | hsl(0,0,0,0), hsl(0,0,0,0), // colorEndA, colorEndB
73 | 2, .2, .2, .1, .05, // time, sizeStart, sizeEnd, speed, angleSpeed
74 | .99, 1, 1, PI, // damping, angleDamping, gravityScale, cone
75 | .05, .5, true, true // fadeRate, randomness, collide, additive
76 | );
77 | particleEmitter.elasticity = .3; // bounce when it collides
78 | particleEmitter.trailScale = 2; // stretch in direction of motion
79 | }
80 |
81 | ///////////////////////////////////////////////////////////////////////////////
82 | function gameUpdate()
83 | {
84 | if (mouseWasPressed(0))
85 | {
86 | // play sound when mouse is pressed
87 | sound_click.play(mousePos);
88 |
89 | // change particle color and set to fade out
90 | particleEmitter.colorStartA = hsl();
91 | particleEmitter.colorStartB = randColor();
92 | particleEmitter.colorEndA = particleEmitter.colorStartA.scale(1,0);
93 | particleEmitter.colorEndB = particleEmitter.colorStartB.scale(1,0);
94 |
95 | // unlock medals
96 | medal_example.unlock();
97 | }
98 |
99 | // move particles to mouse location if on screen
100 | if (mousePosScreen.x)
101 | particleEmitter.pos = mousePos;
102 | }
103 |
104 | ///////////////////////////////////////////////////////////////////////////////
105 | function gameUpdatePost()
106 | {
107 |
108 | }
109 |
110 | ///////////////////////////////////////////////////////////////////////////////
111 | function gameRender()
112 | {
113 | // draw a grey square in the background without using webgl
114 | drawRect(vec2(16,8), vec2(20,14), hsl(0,0,.6), 0, false);
115 |
116 | // draw the logo as a tile
117 | drawTile(vec2(21,5), vec2(4.5), tile(3,128));
118 | }
119 |
120 | ///////////////////////////////////////////////////////////////////////////////
121 | function gameRenderPost()
122 | {
123 | // draw to overlay canvas for hud rendering
124 | drawTextScreen('LittleJS Demo',
125 | vec2(mainCanvasSize.x/2, 70), 80, // position, size
126 | hsl(0,0,1), 6, hsl(0,0,0)); // color, outline size and color
127 | }
128 |
129 | ///////////////////////////////////////////////////////////////////////////////
130 | // Startup LittleJS Engine
131 | engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, ['tiles.png']);
--------------------------------------------------------------------------------
/examples/starter/index.html:
--------------------------------------------------------------------------------
1 |
2 | LittleJS Starter Project
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/examples/starter/tiles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/examples/starter/tiles.png
--------------------------------------------------------------------------------
/examples/stress/index.html:
--------------------------------------------------------------------------------
1 |
2 | LittleJS Stress Test
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/examples/uiSystem/game.js:
--------------------------------------------------------------------------------
1 | /*
2 | Little JS UI System Example
3 | - Shows how to use the LittleJS UI plugin
4 | - Modal windows, buttons, text, checkboxes, and more
5 | */
6 |
7 | 'use strict';
8 |
9 | // show the LittleJS splash screen
10 | setShowSplashScreen(true);
11 |
12 | // sound effects
13 | const sound_ui = new Sound([1,0]);
14 |
15 | // UI system
16 | let uiRoot, uiMenu;
17 | const getMenuVisible =()=> uiMenu.visible;
18 | const setMenuVisible =(visible)=> uiMenu.visible = visible;
19 |
20 | // use a fixed size canvas
21 | canvasFixedSize = vec2(1920, 1080); // 1080p
22 | canvasPixelated = false;
23 |
24 | function createUI()
25 | {
26 | // setup root to attach all ui elements to
27 | uiRoot = new UIObject();
28 | const uiInfo = new UIText(vec2(0,90), vec2(1e3, 70),
29 | 'LittleJS UI System Example\nM = Toggle menu');
30 | uiInfo.textColor = WHITE;
31 | uiInfo.lineWidth = 8;
32 | uiRoot.addChild(uiInfo);
33 |
34 | // setup example menu
35 | uiMenu = new UIObject(vec2(0,450));
36 | uiRoot.addChild(uiMenu);
37 | const uiBackground = new UIObject(vec2(0,0), vec2(450,580));
38 | uiBackground.lineWidth = 8;
39 | uiMenu.addChild(uiBackground);
40 |
41 | // example large text
42 | const textTitle = new UIText(vec2(0,-220), vec2(500, 90), 'Test Title');
43 | uiMenu.addChild(textTitle);
44 | textTitle.textColor = RED;
45 | textTitle.lineWidth = 4;
46 | textTitle.lineColor = BLUE;
47 |
48 | // example multiline text
49 | const textTest = new UIText(vec2(-60,-140), vec2(300, 50), 'Test Text\nSecond text line.')
50 | uiMenu.addChild(textTest);
51 |
52 | // example tile image
53 | const tileTest = new UITile(vec2(150,-130), vec2(110), tile(3,128))
54 | uiMenu.addChild(tileTest);
55 |
56 | // example checkbox
57 | const checkbox = new UICheckbox(vec2(-140,-20), vec2(40));
58 | uiMenu.addChild(checkbox);
59 | checkbox.onPress = ()=>
60 | {
61 | checkbox.checked = !checkbox.checked;
62 | console.log('Checkbox clicked');
63 | sound_ui.play(0,.5,checkbox.checked?4:1);
64 | }
65 |
66 | // text attached to checkbox
67 | const checkboxText = new UIText(vec2(170,0), vec2(300, 40), 'Test Checkbox');
68 | checkbox.addChild(checkboxText);
69 |
70 | // example scrollbar
71 | const scrollbar = new UIScrollbar(vec2(0,60), vec2(350, 50));
72 | uiMenu.addChild(scrollbar);
73 | scrollbar.onChange = ()=> console.log('New scrollbar value:', scrollbar.value);
74 | scrollbar.onPress = ()=> sound_ui.play(0,.3,2);
75 | scrollbar.onRelease = ()=> sound_ui.play(0,.3,4);
76 |
77 | // example button
78 | const button1 = new UIButton(vec2(0,140), vec2(350, 50), 'Test Button');
79 | uiMenu.addChild(button1);
80 | button1.onPress = ()=>
81 | {
82 | console.log('Button 1 clicked');
83 | sound_ui.play();
84 | }
85 |
86 | // exit button
87 | const button2 = new UIButton(vec2(0,220), vec2(350, 50), 'Exit Menu');
88 | uiMenu.addChild(button2);
89 | button2.onPress = ()=>
90 | {
91 | console.log('Button 2 clicked');
92 | sound_ui.play(0,.5,2);
93 | setMenuVisible(false);
94 | }
95 | }
96 |
97 | ///////////////////////////////////////////////////////////////////////////////
98 | function gameInit()
99 | {
100 | initUISystem();
101 | createUI();
102 | }
103 |
104 | ///////////////////////////////////////////////////////////////////////////////
105 | function gameUpdate()
106 | {
107 | }
108 |
109 | ///////////////////////////////////////////////////////////////////////////////
110 | function gameUpdatePost()
111 | {
112 | // center ui
113 | uiRoot.pos.x = mainCanvasSize.x/2;
114 |
115 | // menu system
116 | const menuVisible = getMenuVisible();
117 | paused = menuVisible;
118 |
119 | // toggle menu visibility
120 | if (keyWasPressed('KeyM'))
121 | setMenuVisible(!menuVisible);
122 | }
123 |
124 | ///////////////////////////////////////////////////////////////////////////////
125 | function gameRender()
126 | {
127 | // test game rendering
128 | drawRect(vec2(), vec2(1e3), hsl(0,0,.2));
129 |
130 | // test game rendering
131 | drawRect(vec2(), vec2(1e3), hsl(0,0,.2));
132 | for(let i=0; i<1e3; ++i)
133 | {
134 | const pos = vec2(30*Math.sin(i+time/9),20*Math.sin(i*i+time/9));
135 | drawTile(pos, vec2(2), tile(3,128), hsl(i/9,1,.4), time+i, !(i%2), hsl(i/9,1,.1,0));
136 | }
137 | }
138 |
139 | ///////////////////////////////////////////////////////////////////////////////
140 | function gameRenderPost()
141 | {
142 | }
143 |
144 | ///////////////////////////////////////////////////////////////////////////////
145 | // Startup LittleJS Engine
146 | engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, ['tiles.png']);
--------------------------------------------------------------------------------
/examples/uiSystem/index.html:
--------------------------------------------------------------------------------
1 |
2 | LittleJS Starter Project
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/uiSystem/tiles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/examples/uiSystem/tiles.png
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | },
4 | "include": [
5 | "./src/*.js",
6 | "./plugins/*.js",
7 | "./examples/**/*.js",
8 | "./shortExamples/**/*.js",
9 | ],
10 | "exclude": [
11 | ]
12 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "littlejsengine",
3 | "version": "1.11.8",
4 | "description": "LittleJS - Tiny and Fast HTML5 Game Engine",
5 | "main": "dist/littlejs.esm.js",
6 | "types": "dist/littlejs.d.ts",
7 | "exports": {
8 | "types": "./dist/littlejs.d.ts",
9 | "production": "./dist/littlejs.esm.min.js",
10 | "default": "./dist/littlejs.esm.js"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/KilledByAPixel/LittleJS.git"
15 | },
16 | "keywords": [
17 | "LittleJS",
18 | "HTML5",
19 | "JavaScript",
20 | "game",
21 | "engine",
22 | "library",
23 | "JS13K",
24 | "webgl"
25 | ],
26 | "author": "Frank Force",
27 | "license": "MIT",
28 | "bugs": {
29 | "url": "https://github.com/KilledByAPixel/LittleJS/issues"
30 | },
31 | "homepage": "https://github.com/KilledByAPixel/LittleJS",
32 | "scripts": {
33 | "build": "node src/engineBuild.js"
34 | },
35 | "devDependencies": {
36 | "google-closure-compiler": "~20230502.0.0",
37 | "roadroller": "~2.1.0",
38 | "uglify-js": "~3.17.4",
39 | "typescript": "~5.1.6",
40 | "ect-bin": "~1.4.1"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/plugins/Box2D_License.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2006-2010 Erin Catto http://www.gphysics.com
2 |
3 | This software is provided 'as-is', without any express or implied
4 | warranty. In no event will the authors be held liable for any damages
5 | arising from the use of this software.
6 |
7 | Permission is granted to anyone to use this software for any purpose,
8 | including commercial applications, and to alter it and redistribute it
9 | freely, subject to the following restrictions:
10 |
11 | 1. The origin of this software must not be misrepresented; you must not
12 | claim that you wrote the original software. If you use this software
13 | in a product, an acknowledgment in the product documentation would be
14 | appreciated but is not required.
15 | 2. Altered source versions must be plainly marked as such, and must not be
16 | misrepresented as being the original software.
17 | 3. This notice may not be removed or altered from any source distribution.
--------------------------------------------------------------------------------
/plugins/Box2D_v2.3.1_min.wasm.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/plugins/Box2D_v2.3.1_min.wasm.wasm
--------------------------------------------------------------------------------
/plugins/newgrounds.js:
--------------------------------------------------------------------------------
1 | /**
2 | * LittleJS Newgrounds API
3 | * - NewgroundsMedal extends Medal with Newgrounds API functionality
4 | * - Call newgroundsInit to enable Newgrounds functionality
5 | * - Uses CryptoJS for encryption if optional cipher is provided
6 | * - Keeps connection alive and logs views
7 | * - Functions to interact with scoreboards
8 | * - Functions to unlock medals
9 | */
10 |
11 | 'use strict';
12 |
13 | ///////////////////////////////////////////////////////////////////////////////
14 |
15 | /** Newgrounds medal auto unlocks in newgrounds API */
16 | class NewgroundsMedal extends Medal
17 | {
18 | /** Create a medal object and adds it to the list of medals */
19 | constructor(id, name, description, icon, src)
20 | { super(id, name, description, icon, src); }
21 |
22 | /** Unlocks a medal if not already unlocked */
23 | unlock()
24 | {
25 | super.unlock();
26 | newgrounds && newgrounds.unlockMedal(this.id);
27 | }
28 | }
29 |
30 | ///////////////////////////////////////////////////////////////////////////////
31 |
32 | /** Global Newgrounds object */
33 | let newgrounds;
34 |
35 | /** This can used to enable Newgrounds functionality
36 | * @param {Number} app_id - The newgrounds App ID
37 | * @param {String} [cipher] - The encryption Key (AES-128/Base64)
38 | * @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher
39 | * @memberof Medals */
40 | function newgroundsInit(app_id, cipher, cryptoJS)
41 | { newgrounds = new Newgrounds(app_id, cipher, cryptoJS); }
42 |
43 | /**
44 | * Newgrounds API wrapper object
45 | * @example
46 | * // create a newgrounds object, replace the app id with your own
47 | * const app_id = '52123:1ZuSTQ7l';
48 | * newgrounds = new Newgrounds(app_id);
49 | */
50 | class Newgrounds
51 | {
52 | /** Create a newgrounds object
53 | * @param {String} app_id - The newgrounds App ID
54 | * @param {String} [cipher] - The encryption Key (AES-128/Base64)
55 | * @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher */
56 | constructor(app_id, cipher, cryptoJS)
57 | {
58 | ASSERT(!newgrounds, 'there can only be one newgrounds object');
59 | ASSERT(!cipher || cryptoJS, 'must provide cryptojs if there is a cipher');
60 |
61 | this.app_id = app_id;
62 | this.cipher = cipher;
63 | this.cryptoJS = cryptoJS;
64 | this.host = location ? location.hostname : '';
65 |
66 | // get session id from url search params
67 | const url = new URL(location.href);
68 | this.session_id = url.searchParams.get('ngio_session_id');
69 |
70 | if (!this.session_id)
71 | return; // only use newgrounds when logged in
72 |
73 | // get medals
74 | const medalsResult = this.call('Medal.getList');
75 | this.medals = medalsResult ? medalsResult.result.data['medals'] : [];
76 | debugMedals && console.log(this.medals);
77 | for (const newgroundsMedal of this.medals)
78 | {
79 | const medal = medals[newgroundsMedal['id']];
80 | if (medal)
81 | {
82 | // copy newgrounds medal data
83 | medal.image = new Image;
84 | medal.image.src = newgroundsMedal['icon'];
85 | medal.name = newgroundsMedal['name'];
86 | medal.description = newgroundsMedal['description'];
87 | medal.unlocked = newgroundsMedal['unlocked'];
88 | medal.difficulty = newgroundsMedal['difficulty'];
89 | medal.value = newgroundsMedal['value'];
90 |
91 | if (medal.value) // add value to description
92 | medal.description = medal.description + ` (${ medal.value })`;
93 | }
94 | }
95 |
96 | // get scoreboards
97 | const scoreboardResult = this.call('ScoreBoard.getBoards');
98 | this.scoreboards = scoreboardResult ? scoreboardResult.result.data.scoreboards : [];
99 | debugMedals && console.log(this.scoreboards);
100 |
101 | // keep the session alive with a ping every 5 minutes
102 | const keepAliveMS = 5 * 60 * 1e3;
103 | setInterval(()=>this.call('Gateway.ping', 0, true), keepAliveMS);
104 | }
105 |
106 | /** Send message to unlock a medal by id
107 | * @param {Number} id - The medal id */
108 | unlockMedal(id) { return this.call('Medal.unlock', {'id':id}, true); }
109 |
110 | /** Send message to post score
111 | * @param {Number} id - The scoreboard id
112 | * @param {Number} value - The score value */
113 | postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, true); }
114 |
115 | /** Get scores from a scoreboard
116 | * @param {Number} id - The scoreboard id
117 | * @param {String} [user] - A user's id or name
118 | * @param {Number} [social] - If true, only social scores will be loaded
119 | * @param {Number} [skip] - Number of scores to skip before start
120 | * @param {Number} [limit] - Number of scores to include in the list
121 | * @return {Object} - The response JSON object
122 | */
123 | getScores(id, user, social=0, skip=0, limit=10)
124 | { return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
125 |
126 | /** Send message to log a view */
127 | logView() { return this.call('App.logView', {'host':this.host}, true); }
128 |
129 | /** Send a message to call a component of the Newgrounds API
130 | * @param {String} component - Name of the component
131 | * @param {Object} [parameters] - Parameters to use for call
132 | * @param {Boolean} [async] - If true, don't wait for response before continuing
133 | * @return {Object} - The response JSON object
134 | */
135 | call(component, parameters, async=false)
136 | {
137 | const call = {'component':component, 'parameters':parameters};
138 | if (this.cipher)
139 | {
140 | // encrypt using AES-128 Base64 with cryptoJS
141 | const cryptoJS = this.cryptoJS;
142 | const aesKey = cryptoJS['enc']['Base64']['parse'](this.cipher);
143 | const iv = cryptoJS['lib']['WordArray']['random'](16);
144 | const encrypted = cryptoJS['AES']['encrypt'](JSON.stringify(call), aesKey, {'iv':iv});
145 | call['secure'] = cryptoJS['enc']['Base64']['stringify'](iv.concat(encrypted['ciphertext']));
146 | call['parameters'] = 0;
147 | }
148 |
149 | // build the input object
150 | const input =
151 | {
152 | 'app_id': this.app_id,
153 | 'session_id': this.session_id,
154 | 'call': call
155 | };
156 |
157 | // build post data
158 | const formData = new FormData();
159 | formData.append('input', JSON.stringify(input));
160 |
161 | // send post data
162 | const xmlHttp = new XMLHttpRequest();
163 | const url = 'https://newgrounds.io/gateway_v3.php';
164 | xmlHttp.open('POST', url, !debugMedals && async);
165 | xmlHttp.send(formData);
166 | debugMedals && console.log(xmlHttp.responseText);
167 | return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
168 | }
169 | }
--------------------------------------------------------------------------------
/plugins/postProcess.js:
--------------------------------------------------------------------------------
1 | /**
2 | * LittleJS Post Processing Plugin
3 | * - Supports shadertoy style post processing shaders
4 | * - call initPostProcess to set it up
5 | */
6 |
7 | 'use strict';
8 |
9 | ///////////////////////////////////////////////////////////////////////////////
10 | // post processing - can be enabled to pass other canvases through a final shader
11 |
12 | let glPostShader, glPostTexture, glPostIncludeOverlay;
13 |
14 | /** Set up a post processing shader
15 | * @param {String} shaderCode
16 | * @param {Boolean} includeOverlay
17 | * @memberof WebGL */
18 | function initPostProcess(shaderCode, includeOverlay=false)
19 | {
20 | ASSERT(!glPostShader, 'can only have 1 post effects shader');
21 | if (headlessMode) return;
22 |
23 | if (!shaderCode) // default shader pass through
24 | shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
25 |
26 | // create the shader
27 | glPostShader = glCreateProgram(
28 | '#version 300 es\n' + // specify GLSL ES version
29 | 'precision highp float;'+ // use highp for better accuracy
30 | 'in vec2 p;'+ // position
31 | 'void main(){'+ // shader entry point
32 | 'gl_Position=vec4(p+p-1.,1,1);'+ // set position
33 | '}' // end of shader
34 | ,
35 | '#version 300 es\n' + // specify GLSL ES version
36 | 'precision highp float;'+ // use highp for better accuracy
37 | 'uniform sampler2D iChannel0;'+ // input texture
38 | 'uniform vec3 iResolution;'+ // size of output texture
39 | 'uniform float iTime;'+ // time
40 | 'out vec4 c;'+ // out color
41 | '\n' + shaderCode + '\n'+ // insert custom shader code
42 | 'void main(){'+ // shader entry point
43 | 'mainImage(c,gl_FragCoord.xy);'+ // call post process function
44 | 'c.a=1.;'+ // always use full alpha
45 | '}' // end of shader
46 | );
47 |
48 | // create buffer and texture
49 | glPostTexture = glCreateTexture(undefined);
50 | glPostIncludeOverlay = includeOverlay;
51 |
52 | // Render the post processing shader, called automatically by the engine
53 | engineAddPlugin(undefined, postProcessRender);
54 | function postProcessRender()
55 | {
56 | if (headlessMode) return;
57 |
58 | // prepare to render post process shader
59 | if (glEnable)
60 | {
61 | glFlush(); // clear out the buffer
62 | mainContext.drawImage(glCanvas, 0, 0); // copy to the main canvas
63 | }
64 | else
65 | {
66 | // set the viewport
67 | glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
68 | }
69 |
70 | if (glPostIncludeOverlay)
71 | {
72 | // copy overlay canvas so it will be included in post processing
73 | mainContext.drawImage(overlayCanvas, 0, 0);
74 | overlayCanvas.width |= 0;
75 | }
76 |
77 | // setup shader program to draw one triangle
78 | glContext.useProgram(glPostShader);
79 | glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
80 | glContext.pixelStorei(glContext.UNPACK_FLIP_Y_WEBGL, 1);
81 | glContext.disable(glContext.BLEND);
82 |
83 | // set textures, pass in the 2d canvas and gl canvas in separate texture channels
84 | glContext.activeTexture(glContext.TEXTURE0);
85 | glContext.bindTexture(glContext.TEXTURE_2D, glPostTexture);
86 | glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, mainCanvas);
87 |
88 | // set vertex position attribute
89 | const vertexByteStride = 8;
90 | const pLocation = glContext.getAttribLocation(glPostShader, 'p');
91 | glContext.enableVertexAttribArray(pLocation);
92 | glContext.vertexAttribPointer(pLocation, 2, glContext.FLOAT, false, vertexByteStride, 0);
93 |
94 | // set uniforms and draw
95 | const uniformLocation = (name)=>glContext.getUniformLocation(glPostShader, name);
96 | glContext.uniform1i(uniformLocation('iChannel0'), 0);
97 | glContext.uniform1f(uniformLocation('iTime'), time);
98 | glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
99 | glContext.drawArrays(glContext.TRIANGLE_STRIP, 0, 4);
100 | }
101 | }
--------------------------------------------------------------------------------
/plugins/uiSystem.js:
--------------------------------------------------------------------------------
1 | /**
2 | * LittleJS User Interface Plugin
3 | * - Nested Menus
4 | * - Text
5 | * - Buttons
6 | * - Checkboxes
7 | * - Images
8 | */
9 |
10 | 'use strict';
11 |
12 | ///////////////////////////////////////////////////////////////////////////////
13 |
14 | // ui defaults
15 | let uiDefaultColor = WHITE;
16 | let uiDefaultLineColor = BLACK;
17 | let uiDefaultTextColor = BLACK;
18 | let uiDefaultButtonColor = hsl(0,0,.5);
19 | let uiDefaultHoverColor = hsl(0,0,.7);
20 | let uiDefaultLineWidth = 4;
21 | let uiDefaultFont = 'arial';
22 |
23 | // ui system
24 | let uiObjects = [];
25 | let uiContext;
26 |
27 | function initUISystem(context=overlayContext)
28 | {
29 | uiContext = context;
30 | engineAddPlugin(uiUpdate, uiRender);
31 |
32 | // setup recursive update and render
33 | function uiUpdate()
34 | {
35 | function updateObject(o)
36 | {
37 | if (!o.visible)
38 | return;
39 | if (o.parent)
40 | o.pos = o.localPos.add(o.parent.pos);
41 | o.update();
42 | for(const c of o.children)
43 | updateObject(c);
44 | }
45 | uiObjects.forEach(o=> o.parent || updateObject(o));
46 | }
47 | function uiRender()
48 | {
49 | function renderObject(o)
50 | {
51 | if (!o.visible)
52 | return;
53 | if (o.parent)
54 | o.pos = o.localPos.add(o.parent.pos);
55 | o.render();
56 | for(const c of o.children)
57 | renderObject(c);
58 | }
59 | uiObjects.forEach(o=> o.parent || renderObject(o));
60 | }
61 | }
62 |
63 | function drawUIRect(pos, size, color=uiDefaultColor, lineWidth=uiDefaultLineWidth, lineColor=uiDefaultLineColor)
64 | {
65 | uiContext.fillStyle = color.toString();
66 | uiContext.beginPath();
67 | uiContext.rect(pos.x-size.x/2, pos.y-size.y/2, size.x, size.y);
68 | uiContext.fill();
69 | if (lineWidth)
70 | {
71 | uiContext.strokeStyle = lineColor.toString();
72 | uiContext.lineWidth = lineWidth;
73 | uiContext.stroke();
74 | }
75 | }
76 |
77 | function drawUILine(posA, posB, thickness=uiDefaultLineWidth, color=uiDefaultLineColor)
78 | {
79 | uiContext.strokeStyle = color.toString();
80 | uiContext.lineWidth = thickness;
81 | uiContext.beginPath();
82 | uiContext.lineTo(posA.x, posA.y);
83 | uiContext.lineTo(posB.x, posB.y);
84 | uiContext.stroke();
85 | }
86 |
87 | function drawUITile(pos, size, tileInfo, color=uiDefaultColor, angle=0, mirror=false)
88 | {
89 | drawTile(pos, size, tileInfo, color, angle, mirror, BLACK, false, true, uiContext);
90 | }
91 |
92 | function drawUIText(text, pos, size, color=uiDefaultColor, lineWidth=uiDefaultLineWidth, lineColor=uiDefaultLineColor, align='center', font=uiDefaultFont)
93 | {
94 | drawTextScreen(text, pos, size.y, color, lineWidth, lineColor, align, font, size.x, uiContext);
95 | }
96 |
97 | ///////////////////////////////////////////////////////////////////////////////
98 |
99 | class UIObject
100 | {
101 | constructor(localPos=vec2(), size=vec2())
102 | {
103 | this.localPos = localPos.copy();
104 | this.pos = localPos.copy();
105 | this.size = size.copy();
106 | this.color = uiDefaultColor;
107 | this.lineColor = uiDefaultLineColor;
108 | this.textColor = uiDefaultTextColor;
109 | this.hoverColor = uiDefaultHoverColor;
110 | this.lineWidth = uiDefaultLineWidth;
111 | this.font = uiDefaultFont;
112 | this.visible = true;
113 | this.children = [];
114 | this.parent = null;
115 | uiObjects.push(this);
116 | }
117 |
118 | addChild(child)
119 | {
120 | ASSERT(!child.parent && !this.children.includes(child));
121 | this.children.push(child);
122 | child.parent = this;
123 | }
124 |
125 | removeChild(child)
126 | {
127 | ASSERT(child.parent == this && this.children.includes(child));
128 | this.children.splice(this.children.indexOf(child), 1);
129 | child.parent = 0;
130 | }
131 |
132 | update()
133 | {
134 | // track mouse input
135 | const mouseWasOver = this.mouseIsOver;
136 | const mouseDown = mouseIsDown(0);
137 | if (!mouseDown || isTouchDevice)
138 | {
139 | this.mouseIsOver = isOverlapping(this.pos, this.size, mousePosScreen);
140 | if (!mouseDown && isTouchDevice)
141 | this.mouseIsOver = false;
142 | if (this.mouseIsOver && !mouseWasOver)
143 | this.onEnter();
144 | if (!this.mouseIsOver && mouseWasOver)
145 | this.onLeave();
146 | }
147 | if (mouseWasPressed(0) && this.mouseIsOver)
148 | {
149 | this.mouseIsHeld = true;
150 | this.onPress();
151 | if (isTouchDevice)
152 | this.mouseIsOver = false;
153 | }
154 | else if (this.mouseIsHeld && !mouseDown)
155 | {
156 | this.mouseIsHeld = false;
157 | this.onRelease();
158 | }
159 | }
160 | render()
161 | {
162 | if (this.size.x && this.size.y)
163 | drawUIRect(this.pos, this.size, this.color, this.lineWidth, this.lineColor);
164 | }
165 |
166 | // callback functions
167 | onEnter() {}
168 | onLeave() {}
169 | onPress() {}
170 | onRelease() {}
171 | onChange() {}
172 | }
173 |
174 | ///////////////////////////////////////////////////////////////////////////////
175 |
176 | class UIText extends UIObject
177 | {
178 | constructor(pos, size, text='', align='center', font=fontDefault)
179 | {
180 | super(pos, size);
181 |
182 | this.text = text;
183 | this.align = align;
184 | this.font = font;
185 | this.lineWidth = 0;
186 | }
187 | render()
188 | {
189 | drawUIText(this.text, this.pos, this.size, this.textColor, this.lineWidth, this.lineColor, this.align, this.font);
190 | }
191 | }
192 |
193 | ///////////////////////////////////////////////////////////////////////////////
194 |
195 | class UITile extends UIObject
196 | {
197 | constructor(pos, size, tileInfo, color=WHITE, angle=0, mirror=false)
198 | {
199 | super(pos, size);
200 |
201 | this.tileInfo = tileInfo;
202 | this.color = color;
203 | this.angle = angle;
204 | this.mirror = mirror;
205 | }
206 | render()
207 | {
208 | drawUITile(this.pos, this.size, this.tileInfo, this.color, this.angle, this.mirror);
209 | }
210 | }
211 |
212 | ///////////////////////////////////////////////////////////////////////////////
213 |
214 | class UIButton extends UIObject
215 | {
216 | constructor(pos, size, text)
217 | {
218 | super(pos, size);
219 | this.text = text;
220 | this.color = uiDefaultButtonColor;
221 | }
222 | render()
223 | {
224 | const lineColor = this.mouseIsHeld ? this.color : this.lineColor;
225 | const color = this.mouseIsOver? this.hoverColor : this.color;
226 | drawUIRect(this.pos, this.size, color, this.lineWidth, lineColor);
227 | const textSize = vec2(this.size.x, this.size.y*.8);
228 | drawUIText(this.text, this.pos, textSize,
229 | this.textColor, 0, undefined, this.align, this.font);
230 | }
231 | }
232 |
233 | ///////////////////////////////////////////////////////////////////////////////
234 |
235 | class UICheckbox extends UIObject
236 | {
237 | constructor(pos, size, checked=false)
238 | {
239 | super(pos, size);
240 | this.checked = checked;
241 | }
242 | onPress()
243 | {
244 | this.checked = !this.checked;
245 | this.onChange();
246 | }
247 | render()
248 | {
249 | const color = this.mouseIsOver? this.hoverColor : this.color;
250 | drawUIRect(this.pos, this.size, color, this.lineWidth, this.lineColor);
251 | if (this.checked)
252 | {
253 | // draw an X if checked
254 | drawUILine(this.pos.add(this.size.multiply(vec2(-.5,-.5))), this.pos.add(this.size.multiply(vec2(.5,.5))), this.lineWidth, this.lineColor);
255 | drawUILine(this.pos.add(this.size.multiply(vec2(-.5,.5))), this.pos.add(this.size.multiply(vec2(.5,-.5))), this.lineWidth, this.lineColor);
256 | }
257 | }
258 | }
259 |
260 | ///////////////////////////////////////////////////////////////////////////////
261 |
262 | class UIScrollbar extends UIObject
263 | {
264 | constructor(pos, size, value=.5, text='')
265 | {
266 | super(pos, size);
267 | this.value = value;
268 | this.text = text;
269 | this.color = uiDefaultButtonColor;
270 | this.handleColor = WHITE;
271 | }
272 | update()
273 | {
274 | super.update();
275 | if (this.mouseIsHeld)
276 | {
277 | const handleSize = vec2(this.size.y);
278 | const handleWidth = this.size.x - handleSize.x;
279 | const p1 = this.pos.x - handleWidth/2;
280 | const p2 = this.pos.x + handleWidth/2;
281 | const oldValue = this.value;
282 | this.value = percent(mousePosScreen.x, p1, p2);
283 | this.value == oldValue || this.onChange();
284 | }
285 | }
286 | render()
287 | {
288 | const lineColor = this.mouseIsHeld ? this.color : this.lineColor;
289 | const color = this.mouseIsOver? this.hoverColor : this.color;
290 | drawUIRect(this.pos, this.size, color, this.lineWidth, lineColor);
291 |
292 | const handleSize = vec2(this.size.y);
293 | const handleWidth = this.size.x - handleSize.x;
294 | const p1 = this.pos.x - handleWidth/2;
295 | const p2 = this.pos.x + handleWidth/2;
296 | const handlePos = vec2(lerp(this.value, p1, p2), this.pos.y);
297 | const barColor = this.mouseIsHeld ? this.color : this.handleColor;
298 | drawUIRect(handlePos, handleSize, barColor, this.lineWidth, this.lineColor);
299 |
300 | const textSize = vec2(this.size.x, this.size.y*.8);
301 | drawUIText(this.text, this.pos, textSize,
302 | this.textColor, 0, undefined, this.align, this.font);
303 | }
304 | }
--------------------------------------------------------------------------------
/src/engineBuild.bat:
--------------------------------------------------------------------------------
1 | rem LittleJS Build Script
2 | call npm run build
--------------------------------------------------------------------------------
/src/engineBuild.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * LittleJS Build System
5 | * - Combine input files
6 | * - Run custom build steps
7 | * - Check for errors
8 | * - Output to build folder
9 | */
10 |
11 | 'use strict';
12 |
13 | const ENGINE_NAME = 'littlejs';
14 | const BUILD_FOLDER = 'dist';
15 | const SOURCE_FOLDER = 'src';
16 | const engineSourceFiles =
17 | [
18 | `${SOURCE_FOLDER}/engineUtilities.js`,
19 | `${SOURCE_FOLDER}/engineSettings.js`,
20 | `${SOURCE_FOLDER}/engineObject.js`,
21 | `${SOURCE_FOLDER}/engineDraw.js`,
22 | `${SOURCE_FOLDER}/engineInput.js`,
23 | `${SOURCE_FOLDER}/engineAudio.js`,
24 | `${SOURCE_FOLDER}/engineTileLayer.js`,
25 | `${SOURCE_FOLDER}/engineParticles.js`,
26 | `${SOURCE_FOLDER}/engineMedals.js`,
27 | `${SOURCE_FOLDER}/engineWebGL.js`,
28 | `${SOURCE_FOLDER}/engine.js`,
29 | ];
30 | const asciiArt =`
31 | ~~~~°°°°ooo°oOo°ooOooOooOo.
32 | __________ ________ ____'°oO.
33 | |LittleJS| |Engine| |[]|_._Y
34 | .|________|_._|______|_._|__|_|_|}
35 | OOO OOO OO OO OO=OO-oo\\
36 | `;
37 | const license = '// LittleJS Engine - MIT License - Copyright 2021 Frank Force\n'+
38 | '// https://github.com/KilledByAPixel/LittleJS\n';
39 |
40 | console.log(asciiArt);
41 | console.log('Choo Choo... Building LittleJS Engine!\n');
42 | const startTime = Date.now();
43 | const fs = require('node:fs');
44 | const child_process = require('node:child_process');
45 |
46 | try
47 | {
48 | // Setup build folder
49 | fs.rmSync(BUILD_FOLDER, { recursive: true, force: true });
50 | fs.mkdirSync(BUILD_FOLDER);
51 | }
52 | catch (e) { handleError(e, 'Failed to create build folder!'); }
53 |
54 | Build
55 | (
56 | 'Build Engine -- all',
57 | `${BUILD_FOLDER}/${ENGINE_NAME}.js`,
58 | [`${SOURCE_FOLDER}/engineDebug.js`, ...engineSourceFiles],
59 | [], true
60 | );
61 |
62 | Build
63 | (
64 | 'Build Engine -- release',
65 | `${BUILD_FOLDER}/${ENGINE_NAME}.release.js`,
66 | [`${SOURCE_FOLDER}/engineRelease.js`, ...engineSourceFiles],
67 | [], true
68 | );
69 |
70 | Build
71 | (
72 | 'Build Engine -- minified',
73 | `${BUILD_FOLDER}/${ENGINE_NAME}.min.js`,
74 | [`${BUILD_FOLDER}/${ENGINE_NAME}.release.js`],
75 | [closureCompilerStep, uglifyBuildStep]
76 | );
77 |
78 | Build
79 | (
80 | 'Build Engine -- ESM',
81 | `${BUILD_FOLDER}/${ENGINE_NAME}.esm.js`,
82 | [`${BUILD_FOLDER}/${ENGINE_NAME}.js`, `${SOURCE_FOLDER}/engineExport.js`],
83 | [typeScriptBuildStep]
84 | );
85 |
86 | Build
87 | (
88 | 'Build Engine -- ESM minified release',
89 | `${BUILD_FOLDER}/${ENGINE_NAME}.esm.min.js`,
90 | [`${BUILD_FOLDER}/${ENGINE_NAME}.min.js`, `${SOURCE_FOLDER}/engineExport.js`],
91 | [uglifyBuildStep]
92 | );
93 |
94 | console.log(`Engine built in ${((Date.now() - startTime)/1e3).toFixed(2)} seconds!`);
95 |
96 | ///////////////////////////////////////////////////////////////////////////////
97 |
98 | // A single build with its own source files, build steps, and output file
99 | // - each build step is a callback that accepts a single filename
100 | function Build(message, outputFile, files=[], buildSteps=[], isPrimaryBuild)
101 | {
102 | console.log(message);
103 |
104 | // copy files into a buffer
105 | let buffer = '';
106 | if (isPrimaryBuild)
107 | {
108 | // add license and strict mode to top
109 | buffer += license + '\n';
110 | buffer += "'use strict';\n\n";
111 | }
112 |
113 | for (const file of files)
114 | {
115 | // get file content
116 | let fileContent = fs.readFileSync(file) + '\n';
117 |
118 | // remove first 'use strict' from each file
119 | if (isPrimaryBuild)
120 | fileContent = fileContent.replace("'use strict';", '');
121 |
122 | // add it to the buffer
123 | buffer += fileContent;
124 | }
125 |
126 | // output file
127 | fs.writeFileSync(outputFile, buffer, {flag: 'w+'});
128 |
129 | // execute build steps in order
130 | for (const buildStep of buildSteps)
131 | buildStep(outputFile);
132 | }
133 |
134 | // Process with Closure Compiler to minify and check for errors
135 | function closureCompilerStep(filename)
136 | {
137 | const filenameTemp = filename + '.tmp';
138 | fs.copyFileSync(filename, filenameTemp);
139 | try
140 | {
141 | child_process.execSync(`npx google-closure-compiler --js=${filenameTemp} --js_output_file=${filename} --warning_level=VERBOSE --jscomp_off=*`);
142 | fs.rmSync(filenameTemp);
143 | }
144 | catch (e) { handleError(e, 'Failed to run Closure Compiler step!'); }
145 | };
146 |
147 | // Process with Uglify to minify
148 | function uglifyBuildStep(filename)
149 | {
150 | try
151 | {
152 | child_process.execSync(`npx uglifyjs ${filename} -o ${filename}`);
153 | }
154 | catch (e) { handleError(e,'Failed to run Uglify minification step!'); }
155 | };
156 |
157 | // Build TypeScript definitions
158 | function typeScriptBuildStep(filename)
159 | {
160 | try
161 | {
162 | const tsFilename = `${BUILD_FOLDER}/${ENGINE_NAME}.d.ts`
163 | child_process.execSync(`npx tsc ${filename} --declaration --allowJs --emitDeclarationOnly --outFile ${tsFilename}`);
164 |
165 | // Make declare module part use the package name "littlejsengine"
166 | let fileContent = fs.readFileSync(tsFilename, 'utf8');
167 | fileContent = fileContent.replace(`${ENGINE_NAME}\.esm`, 'littlejsengine')
168 | fs.writeFileSync(tsFilename, fileContent);
169 |
170 | }
171 | catch (e) { handleError(e, 'Failed to run TypeScript build step!'); }
172 | };
173 |
174 | // display the error and exit
175 | function handleError(e,message)
176 | {
177 | console.error(e);
178 | console.error(message);
179 | process.exit(1);
180 | }
--------------------------------------------------------------------------------
/src/engineExport.js:
--------------------------------------------------------------------------------
1 | /**
2 | * LittleJS Module Export
3 | * - Export engine as a module
4 | */
5 |
6 | export {
7 |
8 | // Engine
9 | engineName,
10 | engineVersion,
11 | frameRate,
12 | timeDelta,
13 | engineObjects,
14 | frame,
15 | time,
16 | timeReal,
17 | paused,
18 | setPaused,
19 | engineInit,
20 | engineObjectsUpdate,
21 | engineObjectsDestroy,
22 | engineObjectsCollect,
23 | engineObjectsCallback,
24 | engineObjectsRaycast,
25 | engineAddPlugin,
26 |
27 | // Globals
28 | debug,
29 | debugOverlay,
30 | showWatermark,
31 |
32 | // Debug
33 | ASSERT,
34 | debugRect,
35 | debugPoly,
36 | debugCircle,
37 | debugPoint,
38 | debugLine,
39 | debugOverlap,
40 | debugText,
41 | debugClear,
42 | debugScreenshot,
43 | debugSaveCanvas,
44 | debugSaveText,
45 | debugSaveDataURL,
46 |
47 | // Settings
48 | cameraPos,
49 | cameraScale,
50 | canvasMaxSize,
51 | canvasFixedSize,
52 | canvasPixelated,
53 | tilesPixelated,
54 | fontDefault,
55 | showSplashScreen,
56 | headlessMode,
57 | tileSizeDefault,
58 | tileFixBleedScale,
59 | enablePhysicsSolver,
60 | objectDefaultMass,
61 | objectDefaultDamping,
62 | objectDefaultAngleDamping,
63 | objectDefaultElasticity,
64 | objectDefaultFriction,
65 | objectMaxSpeed,
66 | gravity,
67 | particleEmitRateScale,
68 | glEnable,
69 | glOverlay,
70 | gamepadsEnable,
71 | gamepadDirectionEmulateStick,
72 | inputWASDEmulateDirection,
73 | touchGamepadEnable,
74 | touchGamepadAnalog,
75 | touchGamepadSize,
76 | touchGamepadAlpha,
77 | vibrateEnable,
78 | soundEnable,
79 | soundVolume,
80 | soundDefaultRange,
81 | soundDefaultTaper,
82 | medalDisplayTime,
83 | medalDisplaySlideTime,
84 | medalDisplaySize,
85 | medalDisplayIconSize,
86 |
87 | // Setters for globals
88 | setCameraPos,
89 | setCameraScale,
90 | setCanvasMaxSize,
91 | setCanvasFixedSize,
92 | setCanvasPixelated,
93 | setTilesPixelated,
94 | setFontDefault,
95 | setShowSplashScreen,
96 | setHeadlessMode,
97 | setGlEnable,
98 | setGlOverlay,
99 | setTileSizeDefault,
100 | setTileFixBleedScale,
101 | setEnablePhysicsSolver,
102 | setObjectDefaultMass,
103 | setObjectDefaultDamping,
104 | setObjectDefaultAngleDamping,
105 | setObjectDefaultElasticity,
106 | setObjectDefaultFriction,
107 | setObjectMaxSpeed,
108 | setGravity,
109 | setParticleEmitRateScale,
110 | setTouchInputEnable,
111 | setGamepadsEnable,
112 | setGamepadDirectionEmulateStick,
113 | setInputWASDEmulateDirection,
114 | setTouchGamepadEnable,
115 | setTouchGamepadAnalog,
116 | setTouchGamepadSize,
117 | setTouchGamepadAlpha,
118 | setVibrateEnable,
119 | setSoundEnable,
120 | setSoundVolume,
121 | setSoundDefaultRange,
122 | setSoundDefaultTaper,
123 | setMedalDisplayTime,
124 | setMedalDisplaySlideTime,
125 | setMedalDisplaySize,
126 | setMedalDisplayIconSize,
127 | setMedalsPreventUnlock,
128 | setShowWatermark,
129 | setDebugKey,
130 |
131 | // Utilities
132 | PI,
133 | abs,
134 | min,
135 | max,
136 | sign,
137 | mod,
138 | clamp,
139 | percent,
140 | distanceWrap,
141 | lerpWrap,
142 | distanceAngle,
143 | lerpAngle,
144 | lerp,
145 | smoothStep,
146 | nearestPowerOfTwo,
147 | isOverlapping,
148 | isIntersecting,
149 | wave,
150 | formatTime,
151 |
152 | // Random
153 | rand,
154 | randInt,
155 | randSign,
156 | randInCircle,
157 | randVector,
158 | randColor,
159 |
160 | // Utility Classes
161 | RandomGenerator,
162 | Vector2,
163 | Color,
164 | Timer,
165 | vec2,
166 | rgb,
167 | hsl,
168 | isColor,
169 |
170 | // Default Colors
171 | WHITE,
172 | BLACK,
173 | GRAY,
174 | RED,
175 | ORANGE,
176 | YELLOW,
177 | GREEN,
178 | CYAN,
179 | BLUE,
180 | PURPLE,
181 | MAGENTA,
182 |
183 | // Draw
184 | textureInfos,
185 | tile,
186 | TileInfo,
187 | TextureInfo,
188 | mainCanvas,
189 | mainContext,
190 | overlayCanvas,
191 | overlayContext,
192 | mainCanvasSize,
193 | screenToWorld,
194 | worldToScreen,
195 | drawTile,
196 | drawRect,
197 | drawLine,
198 | drawPoly,
199 | drawEllipse,
200 | drawCircle,
201 | drawCanvas2D,
202 | drawText,
203 | drawTextOverlay,
204 | drawTextScreen,
205 | setBlendMode,
206 | combineCanvases,
207 | engineFontImage,
208 | FontImage,
209 | isFullscreen,
210 | toggleFullscreen,
211 | setCursor,
212 | getCameraSize,
213 |
214 | // WebGL
215 | glCanvas,
216 | glContext,
217 | glCompileShader,
218 | glCopyToContext,
219 | glCreateProgram,
220 | glCreateTexture,
221 | glDraw,
222 | glFlush,
223 | glSetTexture,
224 | glSetAntialias,
225 | glClearCanvas,
226 | glAntialias,
227 | glShader,
228 | glActiveTexture,
229 | glArrayBuffer,
230 | glGeometryBuffer,
231 | glPositionData,
232 | glColorData,
233 | glInstanceCount,
234 | glAdditive,
235 | glBatchAdditive,
236 |
237 | // Input
238 | keyIsDown,
239 | keyWasPressed,
240 | keyWasReleased,
241 | keyDirection,
242 | clearInput,
243 | mouseIsDown,
244 | mouseWasPressed,
245 | mouseWasReleased,
246 | mousePos,
247 | mousePosScreen,
248 | mouseWheel,
249 | isUsingGamepad,
250 | preventDefaultInput,
251 | gamepadIsDown,
252 | gamepadWasPressed,
253 | gamepadWasReleased,
254 | gamepadStick,
255 | gamepadsUpdate,
256 | vibrate,
257 | vibrateStop,
258 | isTouchDevice,
259 |
260 | // Audio
261 | Sound,
262 | SoundWave,
263 | Music,
264 | playAudioFile,
265 | speak,
266 | speakStop,
267 | getNoteFrequency,
268 | audioContext,
269 | playSamples,
270 | zzfx,
271 |
272 | // Base Object
273 | EngineObject,
274 |
275 | // Tiles
276 | tileCollision,
277 | tileCollisionSize,
278 | initTileCollision,
279 | setTileCollisionData,
280 | getTileCollisionData,
281 | tileCollisionTest,
282 | tileCollisionRaycast,
283 | TileLayerData,
284 | TileLayer,
285 |
286 | // Particles
287 | ParticleEmitter,
288 | Particle,
289 |
290 | // Medals
291 | medals,
292 | medalsPreventUnlock,
293 | medalsInit,
294 | Medal,
295 | };
296 |
--------------------------------------------------------------------------------
/src/engineFont.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KilledByAPixel/LittleJS/5ca398a73e9097e7c65d09494c8d4b1625a571da/src/engineFont.png
--------------------------------------------------------------------------------
/src/engineMedals.js:
--------------------------------------------------------------------------------
1 | /**
2 | * LittleJS Medal System
3 | * - Tracks and displays medals
4 | * - Saves medals to local storage
5 | * - Newgrounds integration
6 | * @namespace Medals
7 | */
8 |
9 | 'use strict';
10 |
11 | /** List of all medals
12 | * @type {Object}
13 | * @memberof Medals */
14 | const medals = {};
15 |
16 | // Engine internal variables not exposed to documentation
17 | let medalsDisplayQueue = [], medalsSaveName, medalsDisplayTimeLast;
18 |
19 | ///////////////////////////////////////////////////////////////////////////////
20 |
21 | /** Initialize medals with a save name used for storage
22 | * - Call this after creating all medals
23 | * - Checks if medals are unlocked
24 | * @param {String} saveName
25 | * @memberof Medals */
26 | function medalsInit(saveName)
27 | {
28 | // check if medals are unlocked
29 | medalsSaveName = saveName;
30 | if (!debugMedals)
31 | medalsForEach(medal=> medal.unlocked = !!localStorage[medal.storageKey()]);
32 |
33 | // engine automatically renders medals
34 | engineAddPlugin(undefined, medalsRender);
35 | function medalsRender()
36 | {
37 | if (!medalsDisplayQueue.length)
38 | return;
39 |
40 | // update first medal in queue
41 | const medal = medalsDisplayQueue[0];
42 | const time = timeReal - medalsDisplayTimeLast;
43 | if (!medalsDisplayTimeLast)
44 | medalsDisplayTimeLast = timeReal;
45 | else if (time > medalDisplayTime)
46 | {
47 | medalsDisplayTimeLast = 0;
48 | medalsDisplayQueue.shift();
49 | }
50 | else
51 | {
52 | // slide on/off medals
53 | const slideOffTime = medalDisplayTime - medalDisplaySlideTime;
54 | const hidePercent =
55 | time < medalDisplaySlideTime ? 1 - time / medalDisplaySlideTime :
56 | time > slideOffTime ? (time - slideOffTime) / medalDisplaySlideTime : 0;
57 | medal.render(hidePercent);
58 | }
59 | }
60 | }
61 |
62 | /** Calls a function for each medal
63 | * @param {Function} callback
64 | * @memberof Medals */
65 | function medalsForEach(callback)
66 | { Object.values(medals).forEach(medal=>callback(medal)); }
67 |
68 | ///////////////////////////////////////////////////////////////////////////////
69 |
70 | /**
71 | * Medal - Tracks an unlockable medal
72 | * @example
73 | * // create a medal
74 | * const medal_example = new Medal(0, 'Example Medal', 'More info about the medal goes here.', '🎖️');
75 | *
76 | * // initialize medals
77 | * medalsInit('Example Game');
78 | *
79 | * // unlock the medal
80 | * medal_example.unlock();
81 | */
82 | class Medal
83 | {
84 | /** Create a medal object and adds it to the list of medals
85 | * @param {Number} id - The unique identifier of the medal
86 | * @param {String} name - Name of the medal
87 | * @param {String} [description] - Description of the medal
88 | * @param {String} [icon] - Icon for the medal
89 | * @param {String} [src] - Image location for the medal
90 | */
91 | constructor(id, name, description='', icon='🏆', src)
92 | {
93 | ASSERT(id >= 0 && !medals[id]);
94 |
95 | /** @property {Number} - The unique identifier of the medal */
96 | this.id = id;
97 |
98 | /** @property {String} - Name of the medal */
99 | this.name = name;
100 |
101 | /** @property {String} - Description of the medal */
102 | this.description = description;
103 |
104 | /** @property {String} - Icon for the medal */
105 | this.icon = icon;
106 |
107 | /** @property {Boolean} - Is the medal unlocked? */
108 | this.unlocked = false;
109 |
110 | // load the source image if provided
111 | if (src)
112 | (this.image = new Image).src = src;
113 |
114 | // add this to list of medals
115 | medals[id] = this;
116 | }
117 |
118 | /** Unlocks a medal if not already unlocked */
119 | unlock()
120 | {
121 | if (medalsPreventUnlock || this.unlocked)
122 | return;
123 |
124 | // save the medal
125 | ASSERT(medalsSaveName, 'save name must be set');
126 | localStorage[this.storageKey()] = this.unlocked = true;
127 | medalsDisplayQueue.push(this);
128 | }
129 |
130 | /** Render a medal
131 | * @param {Number} [hidePercent] - How much to slide the medal off screen
132 | */
133 | render(hidePercent=0)
134 | {
135 | const context = overlayContext;
136 | const width = min(medalDisplaySize.x, mainCanvas.width);
137 | const x = overlayCanvas.width - width;
138 | const y = -medalDisplaySize.y*hidePercent;
139 |
140 | // draw containing rect and clip to that region
141 | context.save();
142 | context.beginPath();
143 | context.fillStyle = new Color(.9,.9,.9).toString();
144 | context.strokeStyle = new Color(0,0,0).toString();
145 | context.lineWidth = 3;
146 | context.rect(x, y, width, medalDisplaySize.y);
147 | context.fill();
148 | context.stroke();
149 | context.clip();
150 |
151 | // draw the icon and text
152 | this.renderIcon(vec2(x+15+medalDisplayIconSize/2, y+medalDisplaySize.y/2));
153 | const pos = vec2(x+medalDisplayIconSize+30, y+28);
154 | drawTextScreen(this.name, pos, 38, new Color(0,0,0), 0, undefined, 'left');
155 | pos.y += 32;
156 | drawTextScreen(this.description, pos, 24, new Color(0,0,0), 0, undefined, 'left');
157 | context.restore();
158 | }
159 |
160 | /** Render the icon for a medal
161 | * @param {Vector2} pos - Screen space position
162 | * @param {Number} [size=medalDisplayIconSize] - Screen space size
163 | */
164 | renderIcon(pos, size=medalDisplayIconSize)
165 | {
166 | // draw the image or icon
167 | if (this.image)
168 | overlayContext.drawImage(this.image, pos.x-size/2, pos.y-size/2, size, size);
169 | else
170 | drawTextScreen(this.icon, pos, size*.7, new Color(0,0,0));
171 | }
172 |
173 | // Get local storage key used by the medal
174 | storageKey() { return medalsSaveName + '_' + this.id; }
175 | }
--------------------------------------------------------------------------------
/src/engineRelease.js:
--------------------------------------------------------------------------------
1 | /**
2 | * LittleJS - Release Mode
3 | * - This file is used for release builds in place of engineDebug.js
4 | * - Debug functionality is disabled to reduce size and increase performance
5 | */
6 |
7 | 'use strict';
8 |
9 | let showWatermark = 0;
10 | let debugKey = '';
11 | const debug = 0;
12 | const debugOverlay = 0;
13 | const debugPhysics = 0;
14 | const debugParticles = 0;
15 | const debugRaycast = 0;
16 | const debugGamepads = 0;
17 | const debugMedals = 0;
18 |
19 | // debug commands are automatically removed from the final build
20 | function ASSERT (){}
21 | function debugInit (){}
22 | function debugUpdate (){}
23 | function debugRender (){}
24 | function debugRect (){}
25 | function debugPoly (){}
26 | function debugCircle (){}
27 | function debugPoint (){}
28 | function debugLine (){}
29 | function debugOverlap (){}
30 | function debugText (){}
31 | function debugClear (){}
32 | function debugScreenshot (){}
33 | function debugSaveCanvas (){}
34 | function debugSaveText (){}
35 | function debugSaveDataURL(){}
--------------------------------------------------------------------------------
/src/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "checkJs": true,
4 | "target": "ES6"
5 | },
6 | "exclude": [
7 | "engineRelease.js",
8 | "engineExport.js"
9 | ]
10 | }
--------------------------------------------------------------------------------