├── .gitignore ├── Color └── index.js ├── EventEmitter └── index.js ├── LICENSE ├── NineSlice ├── index.js └── init.js ├── README.md ├── ShaderFilter ├── fullScreen.vert └── index.js ├── Texture ├── canvas2D.js ├── index.js ├── minitext.js ├── textCanvas2D.js ├── textWebGL.js └── webGL.js ├── TileMap ├── TileMap.md ├── index.js ├── transforms.js └── webGL.js ├── assetLoader └── index.js ├── bleeper ├── index.js └── readme.md ├── bootstrap.js ├── domUtils ├── createCanvas.js └── index.js ├── gamepad └── index.js ├── index.js ├── inherits └── index.js ├── jsdoc.json ├── package.json ├── pataTracker ├── index.js └── readme.md ├── pointerEvents └── index.js ├── spritesheet ├── Sprite.js └── index.js └── webGL ├── IndexBuffer.js ├── VertexBuffer.js ├── batcher.js ├── context.js ├── index.js ├── inspector.js ├── renderers ├── colorQuad.js ├── colorSprite.js ├── line.js └── sprite.js └── shaders ├── color.frag ├── color.vert ├── colorSprite.frag ├── colorSprite.vert ├── sprite.frag └── sprite.vert /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | # Ignore Mac OS files 30 | .DS_Store -------------------------------------------------------------------------------- /Color/index.js: -------------------------------------------------------------------------------- 1 | var RGB_REGEXP = /rgba?\(\s*([.0-9]+)\s*,\s*([.0-9]+)\s*,\s*([.0-9]+)\s*,?\s*([.0-9]+)?\s*\)/; 2 | 3 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 4 | function Color() { 5 | // RGB values as int8 (0..255) 6 | this.r = 0; 7 | this.g = 0; 8 | this.b = 0; 9 | this.a = undefined; 10 | 11 | // CSS string 12 | this.string = ''; 13 | } 14 | module.exports = Color; 15 | 16 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 17 | Color.prototype._setRGB = function (r, g, b) { 18 | this.r = r; 19 | this.g = g; 20 | this.b = b; 21 | }; 22 | 23 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 24 | Color.prototype.fromPixel = function (pixels, offset) { 25 | var r = pixels[offset + 0]; 26 | var g = pixels[offset + 1]; 27 | var b = pixels[offset + 2]; 28 | 29 | this.string = 'rgb(' + r + ',' + g + ',' + b + ')'; 30 | this._setRGB(r, g, b); 31 | return this; 32 | }; 33 | 34 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 35 | Color.prototype.fromString = function (str) { 36 | this.string = str; 37 | 38 | var r, g, b; 39 | 40 | // try to parse `rgb()` type string 41 | var match = str.match(RGB_REGEXP); 42 | 43 | if (match) { 44 | r = ~~match[1]; 45 | g = ~~match[2]; 46 | b = ~~match[3]; 47 | this._setRGB(r, g, b); 48 | } else { 49 | // parse HEX color 50 | var a = '0x' + str.slice(1).replace(str.length < 5 && /./g, '$&$&'); 51 | this._setRGB(a >> 16, a >> 8 & 255, a & 255); 52 | } 53 | 54 | return this; 55 | }; 56 | 57 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 58 | Color.prototype.fromRGB = function (r, g, b) { 59 | this._setRGB(r, g, b); 60 | this.string = 'rgb(' + r + ',' + g + ',' + b + ')'; 61 | return this; 62 | }; 63 | 64 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 65 | Color.prototype.fromColor = function (color) { 66 | this.r = color.r; 67 | this.g = color.g; 68 | this.b = color.b; 69 | this.a = color.a; 70 | 71 | // CSS string 72 | this.string = color.string; 73 | return this; 74 | }; 75 | -------------------------------------------------------------------------------- /EventEmitter/index.js: -------------------------------------------------------------------------------- 1 | function EventEmitter() { 2 | this._events = {}; 3 | }; 4 | 5 | module.exports = EventEmitter; 6 | 7 | EventEmitter.listenerCount = function (emitter, event) { 8 | var handlers = emitter._events[event]; 9 | return handlers ? handlers.length : 0; 10 | }; 11 | 12 | EventEmitter.prototype.on = function (event, fn) { 13 | if (typeof fn !== 'function') throw new TypeError('Tried to register non-function as event handler: ' + event); 14 | 15 | // we emit first, because if event is "newListener" it would go recursive 16 | // this.emit('newListener', event, fn); 17 | 18 | var allHandlers = this._events; 19 | var eventHandlers = allHandlers[event]; 20 | if (eventHandlers === undefined) { 21 | // first event handler for this event type 22 | allHandlers[event] = [fn]; 23 | } else { 24 | eventHandlers.push(fn); 25 | } 26 | 27 | return this; 28 | }; 29 | 30 | EventEmitter.prototype.addListener = EventEmitter.prototype.on; 31 | 32 | EventEmitter.prototype.once = function (event, fn) { 33 | if (!fn.once) { 34 | fn.once = 1; 35 | } else { 36 | fn.once += 1; 37 | } 38 | 39 | return this.on(event, fn); 40 | }; 41 | 42 | EventEmitter.prototype.removeListener = function (event, handler) { 43 | var handlers = this._events[event]; 44 | if (handlers !== undefined) { 45 | var index = handlers.indexOf(handler); 46 | if (index === -1) { 47 | console.warn('No event listener registered', event, handler) 48 | return this; 49 | } 50 | handlers.splice(index, 1); 51 | if (handlers.length === 0) delete this._events[event]; 52 | } 53 | return this; 54 | }; 55 | 56 | EventEmitter.prototype.off = EventEmitter.prototype.removeListener; 57 | 58 | EventEmitter.prototype.removeAllListeners = function (event) { 59 | if (event) { 60 | delete this._events[event]; 61 | } else { 62 | this._events = {}; 63 | } 64 | return this; 65 | }; 66 | 67 | EventEmitter.prototype.hasListeners = function (event) { 68 | return this._events[event] !== undefined; 69 | }; 70 | 71 | EventEmitter.prototype.listeners = function (event) { 72 | var handlers = this._events[event]; 73 | if (handlers !== undefined) return handlers.slice(); 74 | 75 | return []; 76 | }; 77 | 78 | EventEmitter.prototype.emit = function (event) { 79 | var handlers = this._events[event]; 80 | if (handlers === undefined) return false; 81 | 82 | // copy handlers into a new array, so that handler removal doesn't affect array length 83 | handlers = handlers.slice(); 84 | 85 | var hadListener = false; 86 | 87 | // copy all arguments, but skip the first (the event name) 88 | var args = []; 89 | for (var i = 1; i < arguments.length; i++) { 90 | args.push(arguments[i]); 91 | } 92 | 93 | for (var i = 0, len = handlers.length; i < len; i++) { 94 | var handler = handlers[i]; 95 | 96 | handler.apply(this, args); 97 | hadListener = true; 98 | 99 | if (handler.once) { 100 | if (handler.once > 1) { 101 | handler.once--; 102 | } else { 103 | delete handler.once; 104 | } 105 | 106 | this.removeListener(event, handler); 107 | } 108 | } 109 | 110 | return hadListener; 111 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Cedric Stoquer 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 | -------------------------------------------------------------------------------- /NineSlice/index.js: -------------------------------------------------------------------------------- 1 | var Sprite = require('../spritesheet/Sprite'); 2 | 3 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 4 | function NineSlice(asset, w, h) { 5 | 6 | // 9-slice sprites 7 | // 8 | // 0 w w+1 asset.width 9 | // 0 ┌───┬─────────┬───┐ 10 | // │ A │ B | C │ 11 | // h ├───┼─────────┼───┤ 12 | // │ │ | │ 13 | // │ D │ E | F │ 14 | // │ │ | │ 15 | // h+1 ├───┼─────────┼───┤ 16 | // │ G │ H | I │ 17 | // asset.height └───┴─────────┴───┘ 18 | // 19 | // 20 | // 3-slices optimizations: 21 | // ┌───┬───────┬───┐ ┌───┐ 22 | // │ J │ K | L │ │ M │ 23 | // └───┴───────┴───┘ ├───┤ 24 | // │ N │ 25 | // ├───┤ 26 | // │ O │ 27 | // └───┘ 28 | 29 | if (asset._isNineSlice) { 30 | asset = asset.asset; 31 | } 32 | 33 | this.asset = asset; 34 | var path = asset.path; 35 | var x = 0; 36 | var y = 0; 37 | 38 | if (asset._isTileMap) { 39 | throw new Error('TileMap cannot be used as NineSlice'); 40 | } 41 | 42 | var width = asset.width; 43 | var height = asset.height; 44 | 45 | if (asset._isSprite) { 46 | x = asset.x; 47 | y = asset.y; 48 | asset = asset.img; 49 | } else if (asset._isTexture) { 50 | asset = asset.canvas; 51 | path = ''; 52 | } 53 | 54 | this.w0 = w; 55 | this.w1 = width - 1; 56 | this.w2 = width - w - 1; 57 | this.h0 = h; 58 | this.h1 = height - 1; 59 | this.h2 = height - h - 1; 60 | 61 | // 9-slices 62 | this._a = new Sprite(asset, path, x, y, w, h); 63 | this._b = new Sprite(asset, path, x + w, y, 1, h); 64 | this._c = new Sprite(asset, path, x + w + 1, y, this.w2, h); 65 | this._d = new Sprite(asset, path, x, y + h, w, 1); 66 | this._e = new Sprite(asset, path, x + w, y + h, 1, 1); 67 | this._f = new Sprite(asset, path, x + w + 1, y + h, this.w2, 1); 68 | this._g = new Sprite(asset, path, x, y + h + 1, w, this.h2); 69 | this._h = new Sprite(asset, path, x + w, y + h + 1, 1, this.h2); 70 | this._i = new Sprite(asset, path, x + w + 1, y + h + 1, this.w2, this.h2); 71 | 72 | // horizontal 3-slices 73 | this._j = new Sprite(asset, path, x, y, w, height); 74 | this._k = new Sprite(asset, path, x + w, y, 1, height); 75 | this._l = new Sprite(asset, path, x + w + 1, y, this.w2, height); 76 | 77 | // vertical 3-slices 78 | this._m = new Sprite(asset, path, x, y, width, h); 79 | this._n = new Sprite(asset, path, x, y + h, width, 1); 80 | this._o = new Sprite(asset, path, x, y + h + 1, width, this.h2); 81 | } 82 | module.exports = NineSlice; 83 | NineSlice.prototype._isNineSlice = true; 84 | 85 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 86 | NineSlice.prototype._draw = function (texture, x, y, w, h) { 87 | w = Math.round(w); 88 | h = Math.round(h); 89 | 90 | if (w < 0) { x += w; w = -w; } 91 | if (h < 0) { y += h; h = -h; } 92 | 93 | var mw = this.w1 + 1; 94 | var mh = this.h1 + 1; 95 | 96 | if (w <= mw && h <= mh) { 97 | texture.draw(this.asset, x, y); 98 | return; 99 | } 100 | 101 | var x1 = x + this.w0; 102 | var x2 = x + w - this.w2 103 | var y1 = y + this.h0; 104 | var y2 = y + h - this.h2; 105 | 106 | if (h <= mh) { 107 | texture.draw (this._j, x, y); 108 | texture.stretchDraw(this._k, x1, y, w - this.w1, mh); 109 | texture.draw (this._l, x2, y); 110 | return; 111 | } 112 | 113 | if (w <= mw) { 114 | texture.draw (this._m, x, y); 115 | texture.stretchDraw(this._n, x, y1, mw, h - this.h1); 116 | texture.draw (this._o, x, y2); 117 | return; 118 | } 119 | 120 | texture.draw (this._a, x, y); 121 | texture.stretchDraw(this._b, x1, y, w - this.w1, this.h0); 122 | texture.draw (this._c, x2, y); 123 | texture.stretchDraw(this._d, x, y1, this.w0, h - this.h1); 124 | texture.stretchDraw(this._e, x1, y1, w - this.w1, h - this.h1); 125 | texture.stretchDraw(this._f, x2, y1, this.w2, h - this.h1); 126 | texture.draw (this._g, x, y2); 127 | texture.stretchDraw(this._h, x1, y2, w - this.w1, this.h2); 128 | texture.draw (this._i, x2, y2); 129 | }; 130 | 131 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 132 | NineSlice.prototype.draw = function (x, y, w, h) { 133 | this._draw($screen, x, y, w, h); 134 | }; 135 | -------------------------------------------------------------------------------- /NineSlice/init.js: -------------------------------------------------------------------------------- 1 | var NineSlice = require('.'); 2 | 3 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 4 | function printPathError(path) { 5 | console.error('Could not find asset "' + path + '"'); 6 | } 7 | 8 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 9 | function convertAsset(path, w, h) { 10 | var pathes = path.split('/'); 11 | var parent = window.assets; 12 | var assetName = pathes.pop(); 13 | 14 | while (pathes.length > 0) { 15 | parent = parent[pathes.shift()]; 16 | if (!parent) { 17 | printPathError(path); 18 | return; 19 | } 20 | } 21 | 22 | if (!parent[assetName]) { 23 | printPathError(path); 24 | return; 25 | } 26 | 27 | parent[assetName] = new NineSlice(parent[assetName], w, h); 28 | } 29 | 30 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 31 | module.exports = function (data) { 32 | if (data._type !== 'Nine slices config') return; 33 | var slicesData = data.sprites; 34 | for (var path in slicesData) { 35 | var sliceData = slicesData[path]; 36 | convertAsset(path, sliceData.w, sliceData.h); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![pixelbox](https://user-images.githubusercontent.com/2462139/77128117-527e4d80-6a92-11ea-9347-ae262e520620.png) 2 | 3 | 4 | ### A sandbox framework to fast-prototype tile-based games 5 | 6 | 7 | Pixelbox takes inspiration from fantasy consoles like [PICO8](http://www.lexaloffle.com/pico-8.php) and game creation frameworks like Unity3D. 8 | 9 | # Install 10 | 11 | From version 2, the Pixelbox package no longer includes the editor. Instead, the editor is now a standalone application that can be downloaded [on Itch.io](https://cstoquer.itch.io/pixelbox). 12 | 13 | 14 | 15 | # Use 16 | 17 | A Pixelbox project has the following structure: 18 | ``` 19 | assets/ 20 | ├── tilesheet.png 21 | ├── palette.png 22 | └── maps.json 23 | audio/ 24 | build/ 25 | src/ 26 | └── main.js 27 | tools/ 28 | node_modules/ 29 | project.pixelbox 30 | package.json 31 | index.html 32 | ``` 33 | 34 | - `assets/` is where game assets go (images, text files, JSON) 35 | - `audio/` is where sounds and music go 36 | - `src/` is the source code folder, and `main.js` is the entry file of the game 37 | 38 | # Programming with pixelbox 39 | 40 | ### Program structure 41 | 42 | The game entry point is the `src/main.js` file. If you provide an `exports.update` function, Pixelbox will call it every frame. 43 | 44 | Building is done using [browserify](http://browserify.org/) which gives you access to `require` (or `import`) and `exports` to easily modularize your project. 45 | 46 | ### Assets 47 | 48 | Pixelbox automatically loads all assets at startup, before executing the game code. All supported files added inside the `assets/` directory will be available in the `assets` global object. The structure follows the structure of the directory. For instance, the file located in `assets/sprites/player.png` will be accessible with `assets.sprites.player`. 49 | 50 | Supported files include: 51 | - images (`.png`, `.jpg`) 52 | - JSON formatted data (`.json`) 53 | - plain text files (`.txt`, `.css`, `.vert`, `.frag`) 54 | 55 | You have direct access to the content of JSON files. 56 | 57 | Because files are loaded inside the `assets` object and refered wthout their extension, you cannot have a file and a directory with the same name inside the same directory. 58 | 59 | # Pixelbox API 60 | 61 | Pixelbox exposes the following methods directly on the global scope: 62 | 63 | ### Drawing graphics 64 | 65 | - `cls()` clear screen with *paper* color 66 | - `sprite(n, x, y [,flipH [,flipV [, flipR]]])` draw sprite number `n` on screen at pixel position `(x, y)` 67 | `flipH` and `flipV` can be used to flip sprites horizontally or vertically, `flipR` adds a 90 degree clockwise rotation 68 | - `draw(image, x, y [,flipH [,flipV [, flipR]]])` draw an *Image*, *Texture* or *TileMap* on screen at pixel position `(x, y)` 69 | - `tilesheet(image)` change image used as default tilesheet 70 | - `rect(x, y, w, h)` stroke a rectangle with *pen* color 71 | - `rectf(x, y, w, h)` fill a rectangle with *paper* color 72 | - `camera(x, y)` scroll add further drawing by provided position 73 | 74 | ### Printing text 75 | 76 | Pixelbox has a predefined *"minitext"* bitmap font that you can use to print text on screen or in textures. *Minitext* is available by default, but can be disabled in the project settings. 77 | 78 | - `print(text, [x, y])` if x, y is provided, print `text` at pixel position (`x`, `y`); 79 | else print text at cursor current position 80 | - `println(text)` print `text` and feed new line; 81 | when the cursor reaches the bottom of the screen, vertical scroll is applied 82 | (just like it would happen in a terminal) 83 | - `locate(i, j)` set cursor position at column `i` line `j` 84 | - `pen(colorId)` set text color to `colorId` in color palette 85 | - `paper(colorId)` set paper color to `colorId` in color palette 86 | 87 | ### User controls 88 | 89 | - `btn` state of the buttons — by default, available buttons are: `up`, `down`, `left`, `right`, `A`, `B` (buttons names and binding can be configured in the project settings) 90 | - `btnp` whether button has been pressed during current frame 91 | - `btnr` whether button has been released during current frame 92 | 93 | ### Playing sound 94 | 95 | - `sfx('sound');` play the sound.mp3 file in `audio/` folder 96 | - `music('bgm');` play the bgm.mp3 file in loop; if another music is already playing, it will cross fade to the new music; if no `soundId` is provided, music stops 97 | 98 | [AudioManager](https://github.com/Wizcorp/AudioManager) is the module that handles audio 99 | loading and playback. You have access to its instance on `audioManager`. 100 | 101 | ### Other utility functions 102 | 103 | - `clamp(value, min, max)` clip a value between min and max 104 | - `chr$(n)` return a character from code `n` 105 | - `random(n)` return a random **integer** between 0 and n 106 | - `inherits(Child, Parent)` make class *Child* inherit from class *Parent* 107 | 108 | # Pixelbox components 109 | 110 | ## Texture 111 | 112 | Texture is a canvas that can be drawn, and inside which things can be drawn. In Canvas2D mode, it is implemented with a HTML canvas. In WebGL mode, it is implemented with a GLTexture2D. 113 | 114 | The main screen (accessible by the global variable `$screen`) is an instance of Texture and most of its methods are accessible from the global scope. 115 | 116 | To create new texture, you need to require the `Texture` module: 117 | ```javascript 118 | var Texture = require('pixelbox/Texture'); 119 | var texture = new Texture(128, 128); // create a new texture of 128 by 128 pixels 120 | ``` 121 | 122 | #### Texture settings 123 | 124 | ```javascript 125 | texture.resize(width, height); 126 | texture.setPalette(palette); 127 | texture.pen(colorIndex); // set PEN color index from palette (pen is used for text and stroke) 128 | texture.paper(colorIndex); // set PAPER color index from palette (paper is used for fill) 129 | texture.setTilesheet(tilesheet); // set tilesheet used for this texture 130 | ``` 131 | 132 | A tilesheet is an Image containing 256 sprites organized in a 16 x 16 grid (the size of the tilesheet depend of the sprite size you set for your game). 133 | 134 | 135 | #### Rendering 136 | 137 | ```javascript 138 | texture.clear(); // clear texture (it becomes transparent) 139 | texture.cls(); // clear screen (the whole texture is filled with the PAPER color) 140 | texture.sprite(sprite, x, y, flipH, flipV, flipR); // draw a sprite from current tilesheet in the texture 141 | texture.draw((img, x, y, flipH, flipV, flipR); // draw an image (or Texture or Map) in the texture 142 | texture.rect(x, y, width, height); // stroke a rectangle 143 | texture.rectf(x, y, width, height); // fill a rectangle 144 | ``` 145 | 146 | #### Printing text 147 | 148 | ```javascript 149 | texture.locate(i, j); // set text cursor to specified location 150 | texture.print(text, x, y); // print some text 151 | texture.println(text); // print some text and feed a new line 152 | ``` 153 | 154 | ## Tile Maps 155 | 156 | Pixelbox has a built-in `TileMap` component. 157 | A TileMap consist of: 158 | - A name 159 | - A tilesheet — when the tilesheet is changed, the whole map will be redrawn with the new tilesheet 160 | - A grid of sprites from the tilesheet plus few flags to flip or rotate sprites 161 | 162 | Once created, a tile map is rendered in one draw call only. 163 | 164 | TileMap can be used to render a level made of sprites, or just to store game data. 165 | 166 | You can create tile maps from your game code; But usually, you will be using Pixelbox's tools (see the Tools section below) to create and manage your maps as game assets. A map can then be retrieved by its name with Pixelbox's `getMap` function. The tile map can then be drawn on screen (or in another Texture), modified, copied, pasted, resized, etc. 167 | 168 | When stored in assets, the map is compressed to Pixelbox format to reduce file size. 169 | 170 | #### Get tile map 171 | 172 | ```javascript 173 | var map = getMap('mapName'); // get a tile map by its name 174 | ``` 175 | 176 | To create new maps, you need to require the `Map` module: 177 | ```javascript 178 | var TileMap = require('pixelbox/TileMap'); 179 | var map = new TileMap(16, 16); // create a new tile map of 16 by 16 tiles 180 | ``` 181 | 182 | #### Draw map 183 | 184 | ```javascript 185 | map.draw(x, y); // draw map on screen at [x, y] position 186 | draw(map, x, y); // idem, using the global draw function 187 | texture.draw(map, x, y); // draw a map in another texture 188 | map.setTilesheet(tilesheet); // set tilesheet to use for this map 189 | // The whole map is redrawn when calling this function 190 | ``` 191 | 192 | #### Access map content 193 | 194 | ```javascript 195 | map.get(x, y); // returns the Tile at position [x, y]. null if empty 196 | map.set(x, y, tile, flipH, flipV, flipR, flagA, flagB); // add a tile 197 | map.remove(x, y); // remove tile at position [x, y]. (set it to null) 198 | map.find(tile, flagA, flagB); // find all tiles with specified properties 199 | ``` 200 | 201 | #### Modifying maps programatically 202 | 203 | ```javascript 204 | map.resize(width, height); // resize the map (size unit is tiles) 205 | map.clear(); // Reset the whole map content by setting all its tiles to null 206 | var mapCopy = map.copy(x, y, width, height); // copy this map to a new one 207 | // x, y, width, height can be specified to copy only a rectangular part of the map 208 | map.paste(mapCopy, x, y, merge); // paste map data in the map at position offset [x, y] 209 | // if 'merge' flag is set, then null tiles will not overwrite current map tile 210 | ``` 211 | 212 | ## Gamepad 213 | 214 | The `gamepad` module allows for easy access to gamepads if the browser supports it. When the gamepad feature is enabled in the project settings, you get access to these objects on the global scope: 215 | ```javascript 216 | gamepads[id]; // get gamepad state. id is a number in the range [0..4] (4 is computer keyboard) 217 | gamepad; // Merge states of all gamepads and return a global gamepad state. 218 | ``` 219 | 220 | Gamepad state works like keyboard controls: You get the state of each button, button presses and button releases, plus the values of analog controls. 221 | 222 | ```javascript 223 | var state = gamepads[0]; // get state of gamepad id 0 224 | 225 | // buttons: 226 | state.btn.A; // state of A button 227 | state.btn.B; // state of B button 228 | state.btn.X; // state of X button 229 | state.btn.Y; // state of Y button 230 | state.btn.start; // state of 'start' button 231 | state.btn.back; // state of 'back' button 232 | state.btn.up; // directionnal pad's up button 233 | state.btn.down; // directionnal pad's down button 234 | state.btn.left; // directionnal pad's left button 235 | state.btn.right; // directionnal pad's right button 236 | state.btn.lb; // left bumper button 237 | state.btn.rb; // right bumper button 238 | state.btn.lt; // left trigger button 239 | state.btn.rt; // right trigger button 240 | 241 | // button press and release. 242 | // the structure is the same as state.btn but the values are true only 243 | // on button press or release. 244 | state.btnp; // button press 245 | state.btnr; // button release 246 | 247 | // analog values 248 | state.x // x axe value (first stick horizontal) 249 | state.y // y axe value (first stick vertical) 250 | state.z // z axe value (second stick horizontal) 251 | state.w // w axe value (second stick vertical) 252 | state.lt // left trigger analog value 253 | state.rt // right trigger analog value 254 | ``` 255 | 256 | ## PataTracker 257 | 258 | Pixelbox editor is bundled with a music tracker called *PataTracker*. 259 | The tracker player must be enabled in the project settings. Player allows to directly plays the songs in the `json` formatted tracker files. 260 | 261 | PataTracker player is exposed as a `patatracker` global variable. 262 | ```js 263 | patatracker.playSong(songNumber); 264 | patatracker.stop(); 265 | ``` 266 | 267 | PataTracker automatically loads the project's album data (`assets/patatracker.json`). If you need to load a different album, you can do it with the following API: 268 | ```js 269 | patatracker.loadData(data); 270 | ``` 271 | 272 | ## Bleeper 273 | 274 | Bleeper is the sound effect editor of Pixelbox. Like for PataTracker, it must be enabled in the project settings. 275 | Note that Bleeper depends on the *AudioManager* component. 276 | 277 | There are several ways to play Bleeper sounds: 278 | 279 | #### Named sounds 280 | If the sound is named, it is accessible on the `assets` global, and automatically added to AudioManager. 281 | ```js 282 | // from assets global 283 | assets.bleeper.mySound.play(volume, panoramic, pitch); // all parameters optional 284 | 285 | // using audioManager 286 | sfx('mySound', volume, panoramic, pitch); // using default channel 287 | audioManager.playSound('sfx', 'mySound', volume, panoramic, pitch); 288 | ``` 289 | 290 | #### Using bleeper module 291 | The Bleeper module exposes an array of all sounds defined in the program. 292 | ```js 293 | var bleeper = require('pixelbox/bleeper'); 294 | bleeper.sounds[3].play(volume, panoramic, pitch); 295 | ``` 296 | -------------------------------------------------------------------------------- /ShaderFilter/fullScreen.vert: -------------------------------------------------------------------------------- 1 | attribute vec2 a_coordinates; 2 | attribute vec2 a_uv; 3 | uniform vec2 u_uvScale; 4 | varying vec2 fragCoord; 5 | 6 | void main(void) { 7 | gl_Position = vec4( 8 | a_coordinates.x, 9 | a_coordinates.y, 10 | 0.0, 11 | 1.0 12 | ); 13 | fragCoord = a_uv * u_uvScale; 14 | } 15 | -------------------------------------------------------------------------------- /ShaderFilter/index.js: -------------------------------------------------------------------------------- 1 | var vertexShader = require('./fullScreen.vert'); 2 | var Texture = require('../Texture'); 3 | var webGL = require('../webGL'); 4 | var context = webGL.context; 5 | var batcher = webGL.batcher; 6 | var gl = context.gl; 7 | var pixelbox = require('..'); 8 | 9 | 10 | // var INT16_SIZE = 2; // byte 11 | // var INT8_SIZE = 1; // byte 12 | var VERTEX_SIZE = 4; // 2 positions + 2 uv 13 | var START_TIME = Date.now() / 1000; 14 | var W = pixelbox.$screen.width; 15 | var H = pixelbox.$screen.height; 16 | 17 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 18 | // create vertex buffer for full screen quad 19 | 20 | var arrayBuffer = new Int8Array([ 21 | -1, 1, 0, 1, // A ╖ A B 22 | 1, 1, 1, 1, // B ╟─ triangle 1 ┌──┐ 23 | -1, -1, 0, 0, // D ╜ │1/│ 24 | 1, 1, 1, 1, // B ╖ │/2│ 25 | 1, -1, 1, 0, // C ╟─ triangle 2 └──┘ 26 | -1, -1, 0, 0 // D ╜ D C 27 | ]); 28 | 29 | var vertexBuffer = gl.createBuffer(); 30 | gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); // work on the vertexBuffer 31 | gl.bufferData(gl.ARRAY_BUFFER, arrayBuffer, gl.STATIC_DRAW); // upload vertex data 32 | 33 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 34 | function ShaderFilter(fragmentShader, uniformDef, channels) { 35 | this._program = context.createProgram('', vertexShader, fragmentShader); 36 | this._target = pixelbox.$screen; 37 | this._uniformDef = uniformDef || {}; 38 | this._uniformIds = []; 39 | this.uniforms = {}; 40 | 41 | this._frame = 0; 42 | this._lastTime = START_TIME; 43 | 44 | // Automatically adds texture channels found in the shader 45 | channels = channels || {}; 46 | this.channel0 = this._checkChannel('iChannel0', channels.channel0); 47 | this.channel1 = this._checkChannel('iChannel1', channels.channel1); 48 | this.channel2 = this._checkChannel('iChannel2', channels.channel2); 49 | this.channel3 = this._checkChannel('iChannel3', channels.channel3); 50 | 51 | // Automatically adds common uniforms if found in the shader 52 | this._checkUniform('iResolution', 'vec2', [W, H]); // viewport resolution (in pixels) originaly vec3 in ShaderToy 53 | this._checkUniform('iTime', 'float', 0); // shader playback time (in seconds) 54 | this._checkUniform('iTimeDelta', 'float', 0); // render time (in seconds) 55 | this._checkUniform('iFrame', 'float', 0); // shader playback frame 56 | 57 | // TODO: also add these like ShaderToy ? 58 | // uniform float iChannelTime[4]; // channel playback time (in seconds) 59 | // uniform vec3 iChannelResolution[4]; // channel resolution (in pixels) 60 | // uniform vec4 iMouse; // mouse pixel coords. xy: current (if MLB down), zw: click 61 | // uniform vec4 iDate; // (year, month, day, time in seconds) 62 | 63 | 64 | // User defined uniforms 65 | for (var uniformId in this._uniformDef) { 66 | var value; 67 | var def = this._uniformDef[uniformId] 68 | switch (def) { 69 | case 'float': value = 0; break; 70 | case 'vec2': value = [0, 0]; break; 71 | case 'vec3': value = [0, 0, 0]; break; 72 | case 'vec4': value = [0, 0, 0, 0]; break; 73 | default: 74 | console.error('Unsupported type', def); 75 | continue; 76 | } 77 | this._uniformIds.push(uniformId); 78 | if (this.uniforms[uniformId] === undefined) { 79 | this.uniforms[uniformId] = value; 80 | } 81 | } 82 | } 83 | module.exports = ShaderFilter; 84 | 85 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 86 | ShaderFilter.prototype._checkUniform = function (uniformId, type, value) { 87 | try { 88 | gl.getUniformLocation(this._program, uniformId); 89 | this._uniformDef[uniformId] = type; 90 | this.uniforms[uniformId] = value; 91 | } catch (e) {} 92 | }; 93 | 94 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 95 | ShaderFilter.prototype._checkChannel = function (uniformId, channel) { 96 | try { 97 | gl.getUniformLocation(this._program, uniformId); 98 | if (channel) { 99 | if (channel._isTexture) { 100 | return channel; 101 | } 102 | if (channel._isSprite) { 103 | var texture = new Texture(channel.width, channel.height); 104 | texture.draw(channel, 0, 0); 105 | return texture; 106 | } 107 | if (channel instanceof Image) { 108 | return context.getTextureFromImage(channel); 109 | } 110 | console.error(uniformId + ' type not supported'); 111 | } 112 | return new Texture(W, H); 113 | } catch (e) { 114 | return null; 115 | } 116 | }; 117 | 118 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 119 | ShaderFilter.prototype.renderTo = function (target) { 120 | this._target = target; 121 | if (this._uniformDef.iResolution) { 122 | this.uniforms.iResolution = [target.width, target.height]; 123 | } 124 | return this; 125 | }; 126 | 127 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 128 | function enableAttribute(program, variableName) { 129 | var location = gl.getAttribLocation(program, variableName); 130 | gl.enableVertexAttribArray(location); 131 | return location; 132 | } 133 | 134 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 135 | function bindChannel(program, channel, uniformId, index) { 136 | if (channel._isGlTexture) channel = channel.ctx; 137 | gl.activeTexture(gl.TEXTURE0 + index); 138 | gl.bindTexture(gl.TEXTURE_2D, channel); 139 | gl.uniform1i(gl.getUniformLocation(program, uniformId), index); 140 | return this; 141 | }; 142 | 143 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 144 | ShaderFilter.prototype.render = function () { 145 | batcher.flush(); 146 | 147 | var program = context.useProgram(this._program); 148 | var w = this._target.width; 149 | var h = this._target.height; 150 | 151 | // if (this._uniformDef.iResolution) { this.uniforms.iResolution = [W, H]; } // Done only once at setup 152 | if (this._uniformDef.iTime) { this.uniforms.iTime = Date.now() / 1000 - START_TIME; } 153 | if (this._uniformDef.iFrame) { this.uniforms.iFrame = this._frame++; } 154 | if (this._uniformDef.iTimeDelta) { 155 | var now = Date.now() / 1000; 156 | this.uniforms.iFrame = now - this._lastTime; 157 | this._lastTime = now; 158 | } 159 | 160 | gl.bindFramebuffer(gl.FRAMEBUFFER, this._target.framebuffer); // FIXME: allow to render in another Texture 161 | gl.viewport(0, 0, w, h); 162 | 163 | gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); 164 | 165 | // uniforms for vertex shader 166 | gl.uniform2f(gl.getUniformLocation(program, 'u_uvScale'), w, h); 167 | 168 | // uniforms for fragment shader 169 | for (var i = 0; i < this._uniformIds.length; i++) { 170 | var uniformId = this._uniformIds[i]; 171 | var v = this.uniforms[uniformId]; 172 | var loc = gl.getUniformLocation(program, uniformId); 173 | switch (this._uniformDef[uniformId]) { 174 | case 'float': gl.uniform1f(loc, v); break; 175 | case 'vec2': gl.uniform2f(loc, v[0], v[1]); break; 176 | case 'vec3': gl.uniform3f(loc, v[0], v[1], v[2]); break; 177 | case 'vec4': gl.uniform4f(loc, v[0], v[1], v[2], v[3]); break; 178 | // TODO: uniformMatrix2fv, uniformMatrix3fv, uniformMatrix4fv 179 | } 180 | } 181 | 182 | gl.vertexAttribPointer(enableAttribute(program, 'a_coordinates'), 2, gl.BYTE, false, /* INT8_SIZE * */ VERTEX_SIZE, 0); 183 | gl.vertexAttribPointer(enableAttribute(program, 'a_uv'), 2, gl.BYTE, false, /* INT8_SIZE * */ VERTEX_SIZE, /* INT8_SIZE * */ 2); 184 | 185 | if (this.channel0) bindChannel(program, this.channel0, 'iChannel0', 0); 186 | if (this.channel1) bindChannel(program, this.channel1, 'iChannel1', 1); 187 | if (this.channel2) bindChannel(program, this.channel2, 'iChannel2', 2); 188 | if (this.channel3) bindChannel(program, this.channel3, 'iChannel3', 3); 189 | 190 | gl.drawArrays(gl.TRIANGLES, 0, 6); 191 | 192 | return this; 193 | }; 194 | -------------------------------------------------------------------------------- /Texture/canvas2D.js: -------------------------------------------------------------------------------- 1 | var Texture = require('./index'); 2 | var createCanvas = require('../domUtils/createCanvas'); 3 | 4 | var TILE_WIDTH = 8; 5 | var TILE_HEIGHT = 8; 6 | var TILES_PER_LINE = 16; // (in a tilesheet) 7 | var PI2 = Math.PI / 2; 8 | 9 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 10 | // NOTA: this is used by Tool 11 | Texture.setTileSize = function (width, height) { 12 | TILE_WIDTH = ~~width; 13 | TILE_HEIGHT = ~~height; 14 | }; 15 | 16 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 17 | Texture.prototype._init = function () { 18 | this.canvas = createCanvas(this.width, this.height); 19 | this.ctx = this.canvas.getContext('2d'); 20 | this.ctx.fillStyle = this.palette[0].string; 21 | this.ctx.strokeStyle = this.palette[1].string; 22 | 23 | this.resize(this.width, this.height); 24 | }; 25 | 26 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 27 | /** Resize texture. The texture is cleared. 28 | * @param {number} width - texture new width in pixels 29 | * @param {number} height - texture new height in pixels 30 | * @returns {Texture} the texture itself 31 | */ 32 | Texture.prototype.resize = function (width, height) { 33 | this.canvas.width = this.width = width; 34 | this.canvas.height = this.height = height; 35 | this.clear(); 36 | return this; 37 | }; 38 | 39 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 40 | /** Draw a tile in Texture using the current tilesheet. 41 | * @param {number} tile - tile index (number between 0 and 255) 42 | * @param {number} [x] - x position in pixels 43 | * @param {number} [y] - y position in pixels 44 | * @param {boolean} [flipH] - if set, the tile is horizontally flipped 45 | * @param {boolean} [flipV] - if set, the tile is vertically flipped 46 | * @param {boolean} [flipR] - if set, the tile rotated by 90 degree 47 | * 48 | * @returns {Texture} the texture itself 49 | */ 50 | Texture.prototype.sprite = function (tile, x, y, flipH, flipV, flipR) { 51 | var sx = TILE_WIDTH * (tile % TILES_PER_LINE); 52 | var sy = TILE_HEIGHT * ~~(tile / TILES_PER_LINE); 53 | var ctx = this.ctx; 54 | 55 | // add camera and round to the pixel 56 | x = x || 0; 57 | y = y || 0; 58 | x = ~~Math.round(x - this.camera.x); 59 | y = ~~Math.round(y - this.camera.y); 60 | 61 | if (!flipH && !flipV && !flipR) { 62 | ctx.drawImage(this.tilesheet, this.ox + sx, this.oy + sy, TILE_WIDTH, TILE_HEIGHT, x, y, TILE_WIDTH, TILE_HEIGHT); 63 | return this; 64 | } 65 | ctx.save(); 66 | 67 | if (flipH) { 68 | ctx.scale(-1, 1); 69 | x = -x - TILE_WIDTH; 70 | } 71 | 72 | if (flipV) { 73 | ctx.scale(1, -1); 74 | y = -y - TILE_HEIGHT; 75 | } 76 | 77 | if (flipR) { 78 | ctx.translate(x + TILE_HEIGHT, y); 79 | ctx.rotate(PI2); 80 | } else { 81 | ctx.translate(x, y); 82 | } 83 | 84 | ctx.drawImage(this.tilesheet, this.ox + sx, this.oy + sy, TILE_WIDTH, TILE_HEIGHT, 0, 0, TILE_WIDTH, TILE_HEIGHT); 85 | ctx.restore(); 86 | return this; 87 | }; 88 | 89 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 90 | /** Draw an Image (or anything drawable) in the texture 91 | * 92 | * @param {Image | Canvas | Texture | Map} element - thing to draw in the texture 93 | * @param {number} [x] - x coordinate of where to draw the image. The value is offseted by Texture's camera position 94 | * @param {number} [y] - y coordinate of where to draw the image. The value is offseted by Texture's camera position 95 | * @param {boolean} [flipH] - if set, the image is horizontally flipped 96 | * @param {boolean} [flipV] - if set, the image is vertically flipped 97 | * @param {boolean} [flipR] - if set, the image rotated by 90 degree 98 | * 99 | * @returns {Texture} the texture itself 100 | */ 101 | Texture.prototype.draw = function (img, x, y, flipH, flipV, flipR) { 102 | if (!img) { 103 | console.error('Invalid asset'); 104 | return this; 105 | } 106 | 107 | if (img._isNineSlice) { 108 | img._draw(this, x, y, flipH, flipV); 109 | return this; 110 | } 111 | 112 | var hasFlip = flipH || flipV || flipR; 113 | 114 | // spritesheet element 115 | if (img._isSprite) { 116 | var sprite = img; 117 | img = sprite.img; 118 | var sx = sprite.x; 119 | var sy = sprite.y; 120 | var sw = sprite.width; 121 | var sh = sprite.height; 122 | 123 | // TODO: pivot point 124 | 125 | var px = ~~Math.round((x || 0) - this.camera.x); 126 | var py = ~~Math.round((y || 0) - this.camera.y); 127 | 128 | if (!hasFlip) { 129 | // fast version 130 | this.ctx.drawImage(img, sx, sy, sw, sh, px, py, sw, sh); 131 | return this; 132 | } 133 | 134 | var ctx = this.ctx; 135 | ctx.save(); 136 | 137 | if (flipR) { 138 | if (flipH) { ctx.scale(-1, 1); px *= -1; px -= sh; } 139 | if (flipV) { ctx.scale(1, -1); py *= -1; py -= sw; } 140 | ctx.translate(px + sh, py); 141 | ctx.rotate(PI2); 142 | } else { 143 | if (flipH) { ctx.scale(-1, 1); px *= -1; px -= sw; } 144 | if (flipV) { ctx.scale(1, -1); py *= -1; py -= sh; } 145 | ctx.translate(px, py); 146 | } 147 | 148 | ctx.drawImage(img, sx, sy, sw, sh, 0, 0, sw, sh); 149 | ctx.restore(); 150 | return this; 151 | } 152 | 153 | // Image, Texture or TileMap 154 | if (img._isTileMap) { 155 | if (!img.texture) img._prepareTexture(); 156 | img = img.texture.canvas; 157 | } 158 | if (img._isTexture) img = img.canvas; 159 | var px = ~~Math.round((x || 0) - this.camera.x); 160 | var py = ~~Math.round((y || 0) - this.camera.y); 161 | 162 | if (!hasFlip) { 163 | // fast version 164 | this.ctx.drawImage(img, px, py); 165 | return this; 166 | } 167 | 168 | var ctx = this.ctx; 169 | ctx.save(); 170 | 171 | if (flipR) { 172 | if (flipH) { ctx.scale(-1, 1); px *= -1; px -= img.height; } 173 | if (flipV) { ctx.scale(1, -1); py *= -1; py -= img.width; } 174 | ctx.translate(px + img.height, py); 175 | ctx.rotate(PI2); 176 | } else { 177 | if (flipH) { ctx.scale(-1, 1); px *= -1; px -= img.width; } 178 | if (flipV) { ctx.scale(1, -1); py *= -1; py -= img.height; } 179 | ctx.translate(px, py); 180 | } 181 | 182 | ctx.drawImage(img, 0, 0); 183 | ctx.restore(); 184 | return this; 185 | }; 186 | 187 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 188 | Texture.prototype.stretchDraw = function (asset, x, y, w, h) { 189 | // spritesheet element 190 | if (asset._isSprite) { 191 | var sprite = asset; 192 | asset = sprite.img; 193 | var sx = sprite.x; 194 | var sy = sprite.y; 195 | var sw = sprite.width; 196 | var sh = sprite.height; 197 | 198 | 199 | var px = ~~Math.round((x || 0) - this.camera.x); 200 | var py = ~~Math.round((y || 0) - this.camera.y); 201 | 202 | this.ctx.drawImage(asset, sx, sy, sw, sh, px, py, w, h); 203 | return this; 204 | } 205 | 206 | // Image or Texture 207 | if (asset._isTexture) asset = asset.canvas; 208 | 209 | var px = ~~Math.round((x || 0) - this.camera.x); 210 | var py = ~~Math.round((y || 0) - this.camera.y); 211 | 212 | this.ctx.drawImage(asset, 0, 0, asset.width, asset.height, px, py, w, h); 213 | return this; 214 | }; 215 | 216 | 217 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 218 | /** Clear the whole texture (make it transparent) 219 | * @returns {Texture} the texture itself 220 | */ 221 | Texture.prototype.clear = function () { 222 | this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); 223 | return this; 224 | }; 225 | 226 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 227 | /** Clear texture with current paper color (CLear Screen) 228 | * @returns {Texture} the texture itself 229 | */ 230 | Texture.prototype.cls = function () { 231 | this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); 232 | return this; 233 | }; 234 | 235 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 236 | /** Set PEN color index. This color is used when printing text in the texture, 237 | * and for outline when drawing shapes. 238 | * @param {number} p - pen color index in the palette 239 | * 240 | * @returns {Texture} the texture itself 241 | */ 242 | Texture.prototype.pen = function (p) { 243 | this._pen = p % this.palette.length; 244 | this.ctx.strokeStyle = this.palette[this._pen].string; 245 | return this; 246 | }; 247 | 248 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 249 | /** Set PAPER color index. This color is used for fill when drawing shapes 250 | * or when clearing the texture (cls) 251 | * @param {number} p - pen color index in the palette 252 | * @returns {Texture} the texture itself 253 | */ 254 | Texture.prototype.paper = function (p) { 255 | this._paper = p % this.palette.length; 256 | this.ctx.fillStyle = this.palette[this._paper].string; 257 | return this; 258 | }; 259 | 260 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 261 | /** Draw an outlined rectangle, using current PEN color. 262 | * Drawing is offset by Texture's camera. 263 | * @param {number} x - x coordinate of rectangle upper left corner 264 | * @param {number} y - y coordinate of rectangle upper left corner 265 | * @param {number} w - rectangle width 266 | * @param {number} h - rectangle height 267 | * 268 | * @returns {Texture} the texture itself 269 | */ 270 | Texture.prototype.rect = function (x, y, w, h) { 271 | if (w <= 1 || h <= 1) { 272 | var fill = this.ctx.fillStyle; 273 | this.ctx.fillStyle = this.ctx.strokeStyle; 274 | this.ctx.fillRect(~~(x - this.camera.x), ~~(y - this.camera.y), ~~w, ~~h); 275 | this.ctx.fillStyle = fill; 276 | return this; 277 | } 278 | this.ctx.strokeRect(~~(x - this.camera.x) + 0.5, ~~(y - this.camera.y) + 0.5, ~~(w - 1), ~~(h - 1)); 279 | return this; 280 | }; 281 | 282 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 283 | /** Draw a filled rectangle, using current PAPER color. 284 | * Drawing is offset by Texture's camera. 285 | * 286 | * The minimum size of a filled rectangle is 2. 287 | * If `w` or `h` is smaller than 2, nothing is drawn. 288 | * 289 | * @param {number} x - x coordinate of rectangle upper left corner 290 | * @param {number} y - y coordinate of rectangle upper left corner 291 | * @param {number} w - rectangle width 292 | * @param {number} h - rectangle height 293 | * 294 | * @returns {Texture} the texture itself 295 | */ 296 | Texture.prototype.rectf = function (x, y, w, h) { 297 | this.ctx.fillRect(~~(x - this.camera.x), ~~(y - this.camera.y), ~~w, ~~h); 298 | return this; 299 | }; 300 | 301 | // legacy 302 | Texture.prototype.rectfill = Texture.prototype.rectf; 303 | 304 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 305 | 306 | // TODO: do we need the default tilesheet to have full dimensions? 307 | Texture.prototype.tilesheet = createCanvas(TILE_WIDTH * TILES_PER_LINE, TILE_HEIGHT * TILES_PER_LINE); 308 | -------------------------------------------------------------------------------- /Texture/index.js: -------------------------------------------------------------------------------- 1 | 2 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 3 | /** @class Texture 4 | * @author Cedric Stoquer 5 | * @classdesc 6 | * Wrap a canvas with functionalities for Pixelbox rendering. 7 | * 8 | * Main screen (accessible on `$screen`) is an instance of that class and most of its methods 9 | * are also accessible from the global scope. 10 | * 11 | * @param {number} width - Texture width in pixel 12 | * @param {number} height - Texture height in pixel 13 | * 14 | * @property {Canvas} canvas - HTML canvas element 15 | * @property {Canvas2DContext} ctx - canvas's context 2D 16 | * @property {string[]} palette - Texture's color palette 17 | * @property {Texture} tilesheet - Texture's tilesheet 18 | */ 19 | function Texture(width, height) { 20 | this.width = width; 21 | this.height = height; 22 | this.camera = { x: 0, y: 0 }; // camera offset 23 | this._cursor = { i: 0, j: 0 }; // text cursor 24 | this._paper = 0; // paper color index 25 | this._pen = 1; // pen color index 26 | 27 | this._init(); 28 | } 29 | module.exports = Texture; 30 | Texture.prototype._isTexture = true; 31 | 32 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 33 | // FIXME better have all these private 34 | 35 | // default palette 36 | Texture.prototype.palette = [ 37 | { string: '#000000', r: 0, g: 0, b: 0 }, // TODO: use ColorRGBB component 38 | { string: '#FFFFFF', r: 1, g: 1, b: 1 } 39 | ]; 40 | 41 | // if tilesheet is a sprite, these stores the sprite offset: 42 | Texture.prototype.ox = 0; 43 | Texture.prototype.oy = 0; 44 | 45 | Texture.setTileSize = function () { /* virtual */ } 46 | 47 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 48 | /** Set texture palette. The charset sheet is recreated when this method is called. 49 | * @param {string[]} palette - palette definition. This is an array of css formated colors. 50 | * At initialisation it is `['#000000', '#FFFFFF']` and is it is redifined by 51 | * the default palette in the settings file. 52 | */ 53 | Texture.prototype.setPalette = function (palette) { 54 | Texture.prototype.palette = palette; 55 | }; 56 | 57 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 58 | function setTilesheet(target, tilesheet) { 59 | target.ox = 0; 60 | target.oy = 0; 61 | 62 | if (tilesheet._isImageWrapper) { 63 | // Image Wrapper (for tool) 64 | tilesheet = tilesheet.asset; 65 | } 66 | 67 | if (tilesheet._isSprite) { 68 | // Sprite 69 | target.ox = tilesheet.x; 70 | target.oy = tilesheet.y; 71 | target.tilesheet = tilesheet.img; 72 | 73 | } else if (tilesheet._isMap) { 74 | // Tilemap 75 | // force redraw if map's texture is not set yet 76 | if (!tilesheet.texture) tilesheet._prepareTexture(); 77 | target.tilesheet = tilesheet.texture.canvas; 78 | 79 | } else if (tilesheet._isTexture) { 80 | // Texture 81 | target.tilesheet = tilesheet.canvas; 82 | 83 | } else if (tilesheet instanceof Image) { 84 | // Image 85 | target.tilesheet = tilesheet; 86 | 87 | } else if (tilesheet instanceof HTMLCanvasElement) { 88 | // Canvas 89 | target.tilesheet = tilesheet; 90 | 91 | } 92 | } 93 | 94 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 95 | /** Set default tilesheet for all Textures 96 | * @param {Image | Texture | Map} tilesheet - tilesheet to use 97 | */ 98 | Texture.prototype.setGlobalTilesheet = function (tilesheet) { 99 | setTilesheet(Texture.prototype, tilesheet); 100 | }; 101 | 102 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 103 | /** Set tilesheet to be used to draw tile in this Texture. 104 | * By default, it is set to null and fallback to default tilesheet which is `assets/tilesheet` 105 | * (the default tilesheet can also be changed with the global method `tilesheet`). 106 | * The tilesheet can be anything drawable in Pixelbox: an image, canvas, another Texture or a Map. 107 | * The texture use a direct reference to the tilesheet root element. 108 | * 109 | * @param {Image | Texture} tilesheet - tilesheet to use 110 | * 111 | * @returns {Texture} the texture itself 112 | */ 113 | Texture.prototype.setTilesheet = function (tilesheet) { 114 | // TODO: this solution is not well optimized as we add and remove attributes on an instance 115 | if (!tilesheet) { 116 | // remove local spritesheet so it naturally fallback to the prototype one 117 | delete this.tilesheet; 118 | return; 119 | } 120 | setTilesheet(this, tilesheet); 121 | return this; 122 | }; 123 | 124 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 125 | /** Set camera position 126 | * @param {number} [x] - camera x position in pixels, default is 0 127 | * @param {number} [y] - camera y position in pixels, default is 0 128 | * @returns {Texture} the texture itself 129 | */ 130 | Texture.prototype.setCamera = function (x, y) { 131 | this.camera.x = x || 0; 132 | this.camera.y = y || 0; 133 | return this; 134 | }; 135 | -------------------------------------------------------------------------------- /Texture/minitext.js: -------------------------------------------------------------------------------- 1 | var createCanvas = require('../domUtils/createCanvas'); 2 | 3 | var CHAR_WIDTH = 4; 4 | var CHAR_HEIGHT = 6; 5 | 6 | var MINITEXT = [ 7 | // 219, 438, 511, 14016, 14043, 14326, 14335, 28032, 28123, 28086, 28159, 32704, 32731, 32758, 32767, 128, 8 | // 146, 384, 402, 9344, 9362, 9600, 9618, 192, 210, 448, 466, 9408, 9426, 9664, 9682, 32767, 9 | 0, 8338, 45, 11962, 5588, 21157, 29354, 10, 17556, 5265, 21973, 1488, 5312, 448, 13824, 5268, 10 | 31599, 29843, 29671, 31143, 18925, 31183, 31689, 18735, 31727, 18927, 1040, 5136, 17492, 3640, 5393, 8359, 11 | 25450, 23530, 31467, 25166, 15211, 29391, 4815, 27470, 23533, 29847, 15142, 23277, 29257, 23421, 23403, 11114, 12 | 4843, 26474, 23279, 14798, 9367, 27501, 12141, 24429, 23213, 14829, 29351, 25750, 17553, 13459, 9402, 28672, 13 | 34, 23530, 31467, 25166, 15211, 29391, 4815, 27470, 23533, 29847, 15142, 23277, 29257, 23421, 23403, 11114, 14 | 4843, 26474, 23279, 14798, 9367, 27501, 12141, 24429, 23213, 14829, 29351, 25686, 9362, 13587, 42, 21845 15 | ]; 16 | 17 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 18 | function createDefaultCharsetImage() { 19 | var canvas = createCanvas(64, 36); 20 | var ctx = canvas.getContext('2d'); 21 | 22 | ctx.fillStyle = '#FFF'; 23 | for (var c = 0; c < MINITEXT.length; c++) { 24 | var code = MINITEXT[c]; 25 | 26 | var i = c % 16; 27 | var j = ~~(c / 16); 28 | 29 | for (var bit = 0; bit < 15; bit++) { 30 | var x = bit % 3; 31 | var y = ~~(bit / 3); 32 | var pixel = (code >> bit) & 1; 33 | if (pixel !== 1) continue; 34 | ctx.fillRect(i * CHAR_WIDTH + x, j * CHAR_HEIGHT + y, 1, 1); 35 | } 36 | } 37 | return canvas; 38 | } 39 | 40 | module.exports = createDefaultCharsetImage; 41 | -------------------------------------------------------------------------------- /Texture/textCanvas2D.js: -------------------------------------------------------------------------------- 1 | var Texture = require('./index.js'); 2 | var minitext = require('./minitext'); 3 | 4 | 5 | var CHAR_PER_LINE = 16; 6 | var CHAR_WIDTH = 4; 7 | var CHAR_HEIGHT = 6; 8 | 9 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 10 | function Charset(template) { 11 | if (!template._isTexture) { 12 | var texture = new Texture(template.width, template.height); 13 | texture.draw(template, 0, 0); 14 | template = texture; 15 | } 16 | 17 | this.template = template; 18 | this.colors = {}; 19 | } 20 | 21 | Charset.prototype.getColor = function (colorString) { 22 | if (this.colors[colorString]) return this.colors[colorString]; 23 | var w = this.template.width; 24 | var h = this.template.height; 25 | var texture = new Texture(w, h); 26 | 27 | // copy image and change color 28 | texture.clear(); 29 | texture.ctx.fillStyle = colorString; 30 | texture.ctx.fillRect(0, 0, w, h); 31 | texture.ctx.globalCompositeOperation = 'destination-in'; 32 | texture.ctx.drawImage(this.template.canvas, 0, 0); 33 | 34 | var canvas = texture.canvas; 35 | this.colors[colorString] = canvas; 36 | return canvas; 37 | }; 38 | 39 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 40 | var _resize = Texture.prototype.resize; 41 | 42 | Texture.prototype.resize = function (width, height) { 43 | this._cursor.i = 0; 44 | this._cursor.j = 0; 45 | _resize.call(this, width, height); 46 | return this; 47 | }; 48 | 49 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 50 | Texture.prototype.cls = function () { 51 | this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); 52 | this.locate(0, 0); 53 | return this; 54 | }; 55 | 56 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 57 | // default text charset generation 58 | var DEFAULT_CHARSET = new Charset(minitext()); 59 | var CHARSET = DEFAULT_CHARSET; 60 | 61 | 62 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 63 | /** Set cursor position for the text. 64 | * @param {number} i - cursor x position in character size (4 pixels) 65 | * @param {number} j - cursor y position in character size (6 pixels) 66 | * 67 | * @returns {Texture} the texture itself 68 | */ 69 | Texture.prototype.locate = function (i, j) { 70 | this._cursor.i = ~~i; 71 | this._cursor.j = ~~j; 72 | return this; 73 | }; 74 | 75 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 76 | /** Print text. A position in pixel can be specified. If you do so, the text 77 | * will be drawn at specified position plus camera offset. If not, the text is 78 | * printed at current cursor position and wil wrap and make screen scroll down 79 | * when cursor reach the texture bottom. 80 | * 81 | * @param (string) str - text to be printed 82 | * @param (number) [x] - text x position in pixel 83 | * @param (number) [y] - text y position in pixel 84 | * 85 | * @returns {Texture} the texture itself 86 | */ 87 | Texture.prototype.print = function (str, x, y) { 88 | // string transformation 89 | if (typeof str === 'object') { 90 | try { 91 | str = JSON.stringify(str); 92 | } catch (error) { 93 | str = '[Object]'; 94 | } 95 | } else if (typeof str !== 'string') { 96 | str = str.toString(); 97 | } 98 | 99 | var color = this.palette[this._pen].string; 100 | var charset = CHARSET.getColor(color); 101 | 102 | // print at cooordinate 103 | if (x !== undefined) { 104 | x = ~~Math.round(x - this.camera.x); 105 | y = ~~Math.round(y - this.camera.y); 106 | var originX = x; 107 | for (var i = 0; i < str.length; i++) { 108 | var chr = str.charCodeAt(i); 109 | if (chr === 10 || chr === 13) { 110 | y += CHAR_HEIGHT; 111 | x = originX; 112 | continue; 113 | } 114 | chr -= 32; 115 | if (chr < 0 || chr > 255) { 116 | x += CHAR_WIDTH; 117 | continue; 118 | } 119 | var sx = CHAR_WIDTH * (chr % CHAR_PER_LINE); 120 | var sy = CHAR_HEIGHT * ~~(chr / CHAR_PER_LINE); 121 | this.ctx.drawImage( 122 | charset, 123 | sx, 124 | sy, 125 | CHAR_WIDTH, CHAR_HEIGHT, 126 | x, y, 127 | CHAR_WIDTH, CHAR_HEIGHT 128 | ); 129 | x += CHAR_WIDTH; 130 | } 131 | return this; 132 | } 133 | 134 | // print at cursor 135 | var i = this._cursor.i; 136 | var j = this._cursor.j; 137 | 138 | for (var c = 0; c < str.length; c++) { 139 | if (this.width - i * CHAR_WIDTH < CHAR_WIDTH) { 140 | i = 0; 141 | j += 1; 142 | } 143 | if (this.height - j * CHAR_HEIGHT < CHAR_HEIGHT) { 144 | this.textScroll(); 145 | j -= 1; 146 | } 147 | var chr = str.charCodeAt(c); 148 | if (chr === 10 || chr === 13) { 149 | i = 0; 150 | j += 1; 151 | continue; 152 | } 153 | chr -= 32; 154 | if (chr < 0 || chr > 255) { 155 | i += 1; 156 | continue; 157 | } 158 | var sx = CHAR_WIDTH * (chr % CHAR_PER_LINE); 159 | var sy = CHAR_HEIGHT * ~~(chr / CHAR_PER_LINE); 160 | this.ctx.drawImage( 161 | charset, 162 | sx, 163 | sy, 164 | CHAR_WIDTH, CHAR_HEIGHT, 165 | i * CHAR_WIDTH, 166 | j * CHAR_HEIGHT, 167 | CHAR_WIDTH, CHAR_HEIGHT 168 | ); 169 | i += 1; 170 | } 171 | 172 | this._cursor.i = i; 173 | this._cursor.j = j; 174 | 175 | return this; 176 | }; 177 | 178 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 179 | /** Same as print and add a go to the next line. 180 | * @param (string) str - text to be printed 181 | * 182 | * @returns {Texture} the texture itself 183 | */ 184 | Texture.prototype.println = function (str) { 185 | this.print(str); 186 | this.print('\n'); 187 | return this; 188 | }; 189 | 190 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 191 | Texture.prototype.textScroll = function (n) { 192 | if (n === undefined) n = 1; 193 | this._cursor.j -= n; 194 | n *= CHAR_HEIGHT; 195 | this.ctx.drawImage(this.canvas, 0, -n); 196 | this.ctx.fillRect(0, this.canvas.height - n, this.canvas.width, n); 197 | return this; 198 | }; 199 | 200 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 201 | var CHARSETS_MAP = new WeakMap(); 202 | 203 | Texture.prototype.setCharset = function (img) { 204 | var i = this._cursor.i * CHAR_WIDTH; 205 | var j = this._cursor.j * CHAR_HEIGHT; 206 | 207 | if (!img) { 208 | CHARSET = DEFAULT_CHARSET; 209 | CHAR_WIDTH = 4; 210 | CHAR_HEIGHT = 6; 211 | } else { 212 | CHAR_WIDTH = ~~(img.width / 16); 213 | CHAR_HEIGHT = ~~(img.height / 6); 214 | 215 | if (CHARSETS_MAP.has(img)) { 216 | CHARSET = CHARSETS_MAP.get(img); 217 | } else { 218 | CHARSET = new Charset(img); 219 | CHARSETS_MAP.set(img, CHARSET); 220 | } 221 | } 222 | 223 | this._cursor.i = Math.ceil(i / CHAR_WIDTH); 224 | this._cursor.j = Math.ceil(j / CHAR_HEIGHT); 225 | }; 226 | -------------------------------------------------------------------------------- /Texture/textWebGL.js: -------------------------------------------------------------------------------- 1 | var Texture = require('./index.js'); 2 | var minitext = require('./minitext'); 3 | var batcher = require('../webGL/batcher'); 4 | var renderers = batcher.renderers; 5 | 6 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 7 | var CHAR_WIDTH = 4; 8 | var CHAR_HEIGHT = 6; 9 | var CHAR_PER_LINE = 16; 10 | var OX = 0; 11 | var OY = 0; 12 | 13 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 14 | var DEFAULT_CHARSET = minitext(); 15 | var CHARSET = DEFAULT_CHARSET; 16 | 17 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 18 | var _resize = Texture.prototype.resize; 19 | Texture.prototype.resize = function (width, height) { 20 | this._cursor.i = 0; 21 | this._cursor.j = 0; 22 | 23 | _resize.call(this, width, height); 24 | return this; 25 | }; 26 | 27 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 28 | var _cls = Texture.prototype.cls; 29 | Texture.prototype.cls = function () { 30 | _cls.call(this); 31 | this.locate(0, 0); 32 | return this; 33 | }; 34 | 35 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 36 | Texture.prototype.locate = function (i, j) { 37 | this._cursor.i = ~~i; 38 | this._cursor.j = ~~j; 39 | return this; 40 | }; 41 | 42 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 43 | Texture.prototype.print = function (str, x, y) { 44 | // string transformation 45 | if (typeof str === 'object') { 46 | try { 47 | str = JSON.stringify(str); 48 | } catch (error) { 49 | str = "[Object]"; 50 | } 51 | } else if (typeof str !== 'string') { 52 | str = str.toString(); 53 | } 54 | 55 | // prepare renderer 56 | var renderer = batcher.prepare(renderers.colorSprite, CHARSET, this); 57 | 58 | var color = this.palette[this._pen]; 59 | var r = color.r; 60 | var g = color.g; 61 | var b = color.b; 62 | 63 | // print at cooordinate 64 | if (x !== undefined) { 65 | x = ~~Math.round(x - this.camera.x); 66 | y = ~~Math.round(y - this.camera.y); 67 | var originX = x; 68 | for (var i = 0; i < str.length; i++) { 69 | var chr = str.charCodeAt(i); 70 | if (chr === 10 || chr === 13) { 71 | y += CHAR_HEIGHT; 72 | x = originX; 73 | continue; 74 | } 75 | chr -= 32; 76 | if (chr < 0 || chr > 255) { 77 | x += CHAR_WIDTH; 78 | continue; 79 | } 80 | var sx = CHAR_WIDTH * (chr % CHAR_PER_LINE) + OX; 81 | var sy = CHAR_HEIGHT * ~~(chr / CHAR_PER_LINE) + OY; 82 | renderer.pushSprite(x, y, CHAR_WIDTH, CHAR_HEIGHT, sx, sy, r, g, b); 83 | x += CHAR_WIDTH; 84 | } 85 | return this; 86 | } 87 | 88 | // print at cursor 89 | var i = this._cursor.i; 90 | var j = this._cursor.j; 91 | 92 | for (var c = 0; c < str.length; c++) { 93 | if (this.width - i * CHAR_WIDTH < CHAR_WIDTH) { 94 | i = 0; 95 | j += 1; 96 | } 97 | if (this.height - j * CHAR_HEIGHT < CHAR_HEIGHT) { 98 | this.textScroll(); 99 | j -= 1; 100 | // don't forget to switch back the renderer 101 | renderer = batcher.prepare(renderers.colorSprite, CHARSET, this); 102 | } 103 | var chr = str.charCodeAt(c); 104 | if (chr === 10 || chr === 13) { 105 | j += 1; 106 | i = 0; 107 | continue; 108 | } 109 | chr -= 32; 110 | if (chr < 0 || chr > 255) { 111 | i += 1; 112 | continue; 113 | } 114 | var sx = CHAR_WIDTH * (chr % CHAR_PER_LINE) + OX; 115 | var sy = CHAR_HEIGHT * ~~(chr / CHAR_PER_LINE) + OY; 116 | renderer.pushSprite(i * CHAR_WIDTH, j * CHAR_HEIGHT, CHAR_WIDTH, CHAR_HEIGHT, sx, sy, r, g, b); 117 | i += 1; 118 | } 119 | 120 | this._cursor.i = i; 121 | this._cursor.j = j; 122 | 123 | return this; 124 | }; 125 | 126 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 127 | Texture.prototype.println = function (str) { 128 | this.print(str); 129 | this.print('\n'); 130 | return this; 131 | }; 132 | 133 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 134 | Texture.prototype.textScroll = function (n) { 135 | if (n === undefined) n = 1; 136 | this._cursor.j -= n; 137 | n *= CHAR_HEIGHT; 138 | 139 | // NOTE: for optimization purpose, this code is duplicated from `draw` method 140 | if (!this._copyTexture) { 141 | this._copyTexture = new Texture(this.width, this.height); 142 | } 143 | 144 | this._copyTexture.draw(this, 0, 0); 145 | 146 | this.cls(); 147 | this.draw(this._copyTexture, 0, -n); 148 | 149 | return this; 150 | }; 151 | 152 | 153 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 154 | Texture.prototype.setCharset = function (img) { 155 | var i = this._cursor.i * CHAR_WIDTH; 156 | var j = this._cursor.j * CHAR_HEIGHT; 157 | 158 | CHARSET = img || DEFAULT_CHARSET; 159 | CHAR_WIDTH = ~~(CHARSET.width / 16); 160 | CHAR_HEIGHT = ~~(CHARSET.height / 6); 161 | 162 | this._cursor.i = Math.ceil(i / CHAR_WIDTH); 163 | this._cursor.j = Math.ceil(j / CHAR_HEIGHT); 164 | 165 | OX = 0; 166 | OY = 0; 167 | 168 | // if charset is a sprite in an atlas 169 | if (CHARSET._isSprite) { 170 | OX = CHARSET.x; 171 | OY = CHARSET.y; 172 | CHARSET = CHARSET.img; 173 | } 174 | }; 175 | -------------------------------------------------------------------------------- /Texture/webGL.js: -------------------------------------------------------------------------------- 1 | var Texture = require('./index.js'); 2 | var context = require('../webGL/context'); 3 | var batcher = require('../webGL/batcher'); 4 | var gl = context.gl; 5 | var renderers = batcher.renderers; 6 | var pixelbox = require('..'); 7 | var settings = pixelbox.settings; 8 | 9 | var TILE_WIDTH = ~~settings.tileSize.width; 10 | var TILE_HEIGHT = ~~settings.tileSize.height; 11 | var TILES_PER_LINE = 16; // (in a tilesheet) 12 | 13 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 14 | Texture.prototype._init = function () { 15 | if (this.width === 0 || this.height === 0) { 16 | this.ctx = null; // gl.Texture2D 17 | this.framebuffer = null; // gl.framebuffer 18 | return; 19 | } 20 | 21 | this.ctx = gl.createTexture(); 22 | gl.bindTexture(gl.TEXTURE_2D, this.ctx); 23 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.width, this.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); 24 | 25 | // set the filtering so we don't need mips 26 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 27 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 28 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 29 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 30 | 31 | // Create and bind the framebuffer 32 | this.framebuffer = gl.createFramebuffer(); 33 | gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); 34 | gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.ctx, 0); 35 | }; 36 | 37 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 38 | Texture.prototype.setCamera = function (x, y) { 39 | this.camera.x = x || 0; 40 | this.camera.y = y || 0; 41 | return this; 42 | }; 43 | 44 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 45 | Texture.prototype.sprite = function (tile, x, y, flipH, flipV, flipR) { 46 | 47 | // add camera and round to the pixel 48 | x = Math.round((x || 0) - this.camera.x); 49 | y = Math.round((y || 0) - this.camera.y); 50 | 51 | // optimize out if render outside viewport 52 | if ( x >= this.width 53 | || y >= this.height 54 | || x <= -TILE_WIDTH 55 | || y <= -TILE_HEIGHT 56 | ) return this; 57 | 58 | var sx = TILE_WIDTH * (tile % TILES_PER_LINE) + this.ox; 59 | var sy = TILE_HEIGHT * ~~(tile / TILES_PER_LINE) + this.oy; 60 | 61 | batcher 62 | .prepare(renderers.sprite, this.tilesheet, this) 63 | .pushSprite(x, y, TILE_WIDTH, TILE_HEIGHT, sx, sy, flipH, flipV, flipR); 64 | 65 | return this; 66 | }; 67 | 68 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 69 | Texture.prototype.draw = function (img, x, y, flipH, flipV, flipR) { 70 | if (!img) { 71 | console.error('Invalid asset'); 72 | return this; 73 | } 74 | 75 | if (img._isNineSlice) { 76 | img._draw(this, x, y, flipH, flipV); 77 | return this; 78 | } 79 | 80 | if (img === this) { 81 | // webGL cannot draw a texture in itslef. 82 | if (!this._copyTexture) { 83 | this._copyTexture = new Texture(this.width, this.height); 84 | } 85 | 86 | this._copyTexture.draw(this, 0, 0); 87 | img = this._copyTexture; 88 | } 89 | 90 | if (img._isTileMap) { 91 | img._draw(x, y, flipH, flipV, flipR, this); 92 | return this; 93 | } 94 | 95 | x = Math.round((x || 0) - this.camera.x); 96 | y = Math.round((y || 0) - this.camera.y); 97 | 98 | // optimize out off-screen sprite 99 | if (x >= this.width || y >= this.height) return this; 100 | 101 | if (img._isSprite) { 102 | var sprite = img; 103 | img = sprite.img; 104 | var sx = sprite.x; 105 | var sy = sprite.y; 106 | var sw = sprite.width; 107 | var sh = sprite.height; 108 | 109 | // optimize out off-screen sprite 110 | if (x <= -sw || y <= -sh) return this; 111 | 112 | batcher 113 | .prepare(renderers.sprite, img, this) 114 | .pushSprite(x, y, sw, sh, sx, sy, flipH, flipV, flipR); 115 | 116 | // } else if (img._isTexture) { 117 | // if (x <= -img.width || y <= -img.height) return this; 118 | // batcher 119 | // .prepare(renderers.sprite, img.canvas, this) 120 | // .pushSprite(x, y, img.width, img.height, 0, 0, flipH, flipV, flipR); 121 | 122 | } else { 123 | // assuming this is a GlTexture or a regular Image instance 124 | if (x <= -img.width || y <= -img.height) return this; 125 | batcher 126 | .prepare(renderers.sprite, img, this) 127 | .pushSprite(x, y, img.width, img.height, 0, 0, flipH, flipV, flipR); 128 | } 129 | 130 | return this; 131 | }; 132 | 133 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 134 | Texture.prototype.stretchDraw = function (asset, x, y, w, h) { 135 | // Sprite 136 | if (asset._isSprite) { 137 | var sprite = asset; 138 | asset = sprite.img; 139 | var sx = sprite.x; 140 | var sy = sprite.y; 141 | var sw = sprite.width; 142 | var sh = sprite.height; 143 | 144 | 145 | var px = ~~Math.round((x || 0) - this.camera.x); 146 | var py = ~~Math.round((y || 0) - this.camera.y); 147 | 148 | batcher 149 | .prepare(renderers.sprite, asset, this) 150 | .pushStretchSprite(px, py, w, h, sx, sy, sw, sh); 151 | 152 | return this; 153 | } 154 | 155 | // Image or Texture 156 | if (asset._isTexture) asset = asset.canvas; 157 | 158 | var px = ~~Math.round((x || 0) - this.camera.x); 159 | var py = ~~Math.round((y || 0) - this.camera.y); 160 | 161 | batcher 162 | .prepare(renderers.sprite, asset, this) 163 | .pushStretchSprite(px, py, w, h, 0, 0, img.width, img.height); 164 | 165 | return this; 166 | }; 167 | 168 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 169 | Texture.prototype.clear = function () { 170 | batcher.flush(); 171 | gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); 172 | gl.viewport(0, 0, this.width, this.height); 173 | gl.clearColor(0.0, 0.0, 0.0, 0.0); 174 | gl.clear(gl.COLOR_BUFFER_BIT); 175 | return this; 176 | }; 177 | 178 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 179 | Texture.prototype.cls = function () { 180 | var color = this.palette[this._paper]; 181 | batcher.flush(); 182 | gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); 183 | gl.viewport(0, 0, this.width, this.height); 184 | gl.clearColor(color.r / 255, color.g / 255, color.b / 255, 1.0); 185 | gl.clear(gl.COLOR_BUFFER_BIT); 186 | return this; 187 | }; 188 | 189 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 190 | Texture.prototype.paper = function (paper) { 191 | this._paper = paper % this.palette.length; 192 | return this; 193 | }; 194 | 195 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 196 | Texture.prototype.pen = function (pen) { 197 | this._pen = pen % this.palette.length; 198 | return this; 199 | }; 200 | 201 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 202 | function setTilesheet(target, tilesheet) { 203 | target.ox = 0; 204 | target.oy = 0; 205 | 206 | if (tilesheet._isImageWrapper) { 207 | // Image Wrapper (for tool) 208 | tilesheet = tilesheet.asset; 209 | } 210 | 211 | if (tilesheet._isSprite) { 212 | // Sprite 213 | target.ox = tilesheet.x; 214 | target.oy = tilesheet.y; 215 | target.tilesheet = tilesheet.img; 216 | 217 | } else if (tilesheet._isMap) { 218 | // Tilemap 219 | // force redraw if map's texture is not set yet 220 | // if (!tilesheet.texture) tilesheet._prepareTexture(); 221 | // target.tilesheet = tilesheet.texture.canvas; 222 | 223 | throw new Error('TileMap cannot be used as tilesheet if using webGL'); 224 | 225 | } else if (tilesheet._isTexture) { 226 | // Texture 227 | target.tilesheet = tilesheet.canvas; 228 | 229 | } else if (tilesheet._isGlTexture) { 230 | // Texture 231 | target.tilesheet = tilesheet; 232 | 233 | } else if (tilesheet instanceof Image) { 234 | // Image 235 | target.tilesheet = tilesheet; 236 | 237 | } else if (tilesheet instanceof HTMLCanvasElement) { 238 | // Canvas 239 | target.tilesheet = tilesheet; 240 | 241 | } 242 | } 243 | 244 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 245 | Texture.prototype.setTilesheet = function (tilesheet) { 246 | // TODO: this solution is not well optimized as we add and remove attributes on an instance 247 | if (!tilesheet) { 248 | // remove local spritesheet so it naturally fallback to the prototype one 249 | delete this.tilesheet; 250 | return; 251 | } 252 | setTilesheet(this, tilesheet); 253 | return this; 254 | }; 255 | 256 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 257 | Texture.prototype.setGlobalTilesheet = function (tilesheet) { 258 | setTilesheet(Texture.prototype, tilesheet); 259 | }; 260 | 261 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 262 | window.tilesheet = function(img) { 263 | return Texture.prototype.setGlobalTilesheet(img); 264 | }; 265 | 266 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 267 | window.texture = function (img) { 268 | var texture = new Texture(img.width, img.height); 269 | texture.clear().draw(img, 0, 0); 270 | return texture; 271 | }; 272 | 273 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 274 | Texture.prototype.rect = function (x, y, w, h) { 275 | var color = this.palette[this._pen]; 276 | 277 | // FIXME: there is a bug within webbGL that make one pixel missing 278 | // batcher 279 | // .prepare(renderers.line, null, this) 280 | // .pushRect(x, y, w, h, color.r, color.g, color.b); 281 | 282 | // HACK: Use rectf to render lines. It's less efficient, but working properly. 283 | var renderer = batcher.prepare(renderers.color, null, this); 284 | 285 | if (w <= 2 || h <= 2) { 286 | renderer.pushRect(x, y, w, h, color.r, color.g, color.b); 287 | return this; 288 | } 289 | 290 | renderer.pushRect(x, y, w, 1, color.r, color.g, color.b); 291 | renderer.pushRect(x, y + h - 1, w, 1, color.r, color.g, color.b); 292 | renderer.pushRect(x, y, 1, h, color.r, color.g, color.b); 293 | renderer.pushRect(x + w - 1, y, 1, h, color.r, color.g, color.b); 294 | 295 | return this; 296 | }; 297 | 298 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 299 | Texture.prototype.rectf = function (x, y, w, h) { 300 | var color = this.palette[this._paper]; 301 | 302 | batcher 303 | .prepare(renderers.color, null, this) 304 | .pushRect(x, y, w, h, color.r, color.g, color.b); 305 | 306 | return this; 307 | }; 308 | 309 | Texture.prototype.rectfill = Texture.prototype.rectf; 310 | 311 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 312 | Texture.prototype.resize = function (width, height) { 313 | if (this.width === width && this.height === height) return this; 314 | this.width = width; 315 | this.height = height; 316 | this._init(); 317 | 318 | if (this._copyTexture) { 319 | this._copyTexture = null; 320 | } 321 | 322 | return this; 323 | }; 324 | 325 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 326 | Texture.prototype._isGlTexture = true; 327 | 328 | // FIXME better have all these private 329 | // Texture.prototype.tilesheet = assets.tilesheet || new Texture(TILE_WIDTH * TILES_PER_LINE, TILE_HEIGHT * TILES_PER_LINE); 330 | 331 | -------------------------------------------------------------------------------- /TileMap/TileMap.md: -------------------------------------------------------------------------------- 1 | # TileMap 2 | 3 | Pixelbox has a built-in `TileMap` component. 4 | A TileMap consist of: 5 | - A tilesheet 6 | - A grid of sprites from the tilesheet, each sprite can be flipped or rotated 7 | - A name 8 | 9 | 10 | TileMap are easily created and edited easily using the MapEditor. Various scripts, called *tile brush*, are available to modify TileMap, and it is possible to program your own *tile brush*. 11 | 12 | The *assets* directory should contain a `maps.json` file in which TileMap are stored. Any number of maps can be created, and you can organise the map within *map folders*. To create a new TileMap from the editor: locate the `maps.json` file in the *Assets window* and click on it to open the contextual menu. Select *New map* to create an empty map. Open the map in the *MapEditor window* by double click on its file. From this window, you can change its name, size and draw tiles using current *tile brush*. 13 | 14 | 15 | A maps created this way is retrived in the game code by its name using the `getMap` function. The tile map can then be drawn on screen (or in another Texture), modified, copied, pasted, resized, etc. 16 | 17 | TileMap can be used to render a level made of sprites, or just to store game data. 18 | 19 | 20 | ## Get or create a TileMap 21 | 22 | Get a tile by its name by using `getMap` function. The name id the full path to the map. 23 | ```javascript 24 | var map = getMap('mapName'); 25 | ``` 26 | 27 | To create new maps, you need to require the `TileMap` module. The constructor takes the size of the map in tiles and create an empty map. The following example create a new map of 16 by 16 tiles: 28 | ```javascript 29 | var TileMap = require('TileMap'); 30 | var map = new TileMap(16, 16); 31 | ``` 32 | 33 | ## TileMap properties 34 | 35 | - `name`: map name 36 | - `width`: map width in tiles 37 | - `height`: map height in tiles 38 | - `items` 39 | - `texture` 40 | 41 | ## Draw map 42 | 43 | To draw the map on screen at position `(x, y)` you use one of the following methods: 44 | ```javascript 45 | map.draw(x, y); // draw map on screen using map's draw method 46 | draw(map, x, y); // draw map on screen, using global draw 47 | texture.draw(map, x, y); // draw a map on a texture 48 | ``` 49 | 50 | The map's tilesheet can be changed using the `setTilesheet` method and passing it an image, sprite or texture. 51 | ```javascript 52 | map.setTilesheet(tilesheet); 53 | ``` 54 | 55 | ## Access and modify map's tiles 56 | TileMap store its tiles as a 2D array of `Tile`. 57 | 58 | A `Tile` have the following attributes: 59 | - `x`: Abscissa coordinate of the tile in the map. 60 | - `y`: Ordinate coordinate of the tile in the map. Vertical axis is oriented from top to bottom. 61 | - `sprite`: Sprite index of the tile. This is a number between 0 and 255. 62 | - `flipH`: a boolean encoding if the tile is horizontally flipped. 63 | - `flipV`: a boolean encoding if the tile is vertically flipped. 64 | - `flipR`: a boolean encoding if the tile is rotated. 65 | - `flagA`: a boolean flag for user. 66 | - `flagB`: a boolean flag for user. 67 | 68 | 69 | This 2D array is accessible on the `items` property of the TileMap, but you should get or set tiles using the following methods: 70 | 71 | `TileMap.get` returns the tile at position [x, y] or `null` if this position is empty. 72 | ```javascript 73 | map.get(x, y); 74 | ``` 75 | 76 | `TileMap.set` add a tile at position [x, y] with the specified attributes. 77 | Only `x`, `y` and `sprite` are required, everything else is optional. 78 | ```javascript 79 | map.set(x, y, sprite, flipH, flipV, flipR, flagA, flagB); 80 | ``` 81 | 82 | `TileMap.remove` remove the tile at specified position. 83 | ```javascript 84 | map.remove(x, y); 85 | ``` 86 | 87 | `TileMap.find` let you search for all the tiles with specified properties. 88 | The `sprite` parameter can be `null`, in which case the function will return 89 | the positions where there are no tiles. 90 | ```javascript 91 | map.find(sprite, flagA, flagB); 92 | ``` 93 | 94 | ## Modifying maps 95 | 96 | A map can be resized or reset. 97 | 98 | ```javascript 99 | map.resize(width, height); // resize the map (size unit is tiles) 100 | map.clear(); // Reset the whole map content by setting all its tiles to null 101 | ``` 102 | 103 | 104 | ```javascript 105 | var mapCopy = map.copy(x, y, width, height); // copy this map to a new one. 106 | // x, y, width, height can be specified to copy only a rectangular part of the map. 107 | map.paste(mapCopy, x, y, merge); // paste map data in the map at position offset [x, y]. 108 | // if 'merge' flag is set, then null tiles will not overwrite current map tile. 109 | ``` 110 | 111 | 112 | ## Methods 113 | 114 | - `resize(width, height)` 115 | - `redraw()` 116 | - `release()` 117 | - `save()` 118 | - `load(mapDefinition)` 119 | 120 | ## Transform component 121 | 122 | - `flipH()` 123 | - `flipV()` 124 | - `flipR()` 125 | - `flipC()` 126 | - `trim()` 127 | -------------------------------------------------------------------------------- /TileMap/index.js: -------------------------------------------------------------------------------- 1 | var Texture = require('../Texture'); 2 | var pixelbox = require('..'); 3 | 4 | var TILE_WIDTH = 8; 5 | var TILE_HEIGHT = 8; 6 | 7 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 8 | /** @class Tile 9 | * 10 | * @property {number} x - x position in tilemap 11 | * @property {number} y - y position in tilemap 12 | * @property {number} sprite - sprite index (number between 0 and 255) 13 | * @property {boolean} flipH - flip horizontal 14 | * @property {boolean} flipV - flip vertical 15 | * @property {boolean} flipR - flip rotation 16 | * @property {boolean} flagA - user purpose flag A 17 | * @property {boolean} flagB - user purpose flag B 18 | */ 19 | function Tile(x, y, sprite, flipH, flipV, flipR, flagA, flagB) { 20 | this.x = ~~x; 21 | this.y = ~~y; 22 | this.sprite = ~~sprite; 23 | this.flipH = !!flipH; 24 | this.flipV = !!flipV; 25 | this.flipR = !!flipR; 26 | this.flagA = !!flagA; 27 | this.flagB = !!flagB; 28 | } 29 | 30 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 31 | /** @class TileMap 32 | * @author Cedric Stoquer 33 | * @classdesc 34 | * A tilemap is a 2 dimensional array of tiles (`Tile`). 35 | * This can be used to reder a level made of tiles, or just to store game data. 36 | * 37 | * A tilemap can be stored as an asset and is usually much smaller than a image because 38 | * the only data saved are tile ids (a number between 0 and 255) plus few flags 39 | * for how to render the tile (flipping horizontally, vertically, and 90 degree rotation). 40 | * 41 | * For its rendering, a tilemap use one tilesheet, which is an Image containing 256 42 | * tiles organized in a 16 x 16 grid (the size of the tilesheet depend of the tile 43 | * size you set for your game). The tilesheet can be changed, and the whole map will 44 | * be redrawn. 45 | * 46 | * You can create maps from your game code; But usually, you will be using Pixelbox's 47 | * tools to create and manage your maps as game assets. A map can then be retrived 48 | * by its name with Pixelbox's `getMap` function. The map can then be drawn on screen 49 | * (or in another Texture), modified, copied, pasted, resized, etc. 50 | * 51 | * When stored in assets, the map is compressed to Pixelbox format to reduce file size. 52 | * 53 | * @see Tile 54 | * 55 | * @property (string) name - map name 56 | * @property {number} width - map width (in tiles) 57 | * @property {number} height - map height (in tiles) 58 | * @property {Tile[][]} items - 2D array of map items 59 | * @property {Texture} texture - generated texture 60 | */ 61 | function TileMap(width, height) { 62 | this._name = ''; 63 | this.width = 0; 64 | this.height = 0; 65 | this.items = []; 66 | this.texture = null; 67 | this.tilesheet = null; 68 | this._tilesheetPath = ''; 69 | 70 | if (width && height) this._init(width, height); 71 | } 72 | module.exports = TileMap; 73 | 74 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 75 | // static properties and methods 76 | TileMap.prototype._isTileMap = true; 77 | 78 | var _mapById = {}; 79 | var _maps = []; 80 | TileMap.getMap = function (id) { 81 | if (typeof id === 'string') return _mapById[id]; 82 | if (typeof id === 'number') return _maps[id]; 83 | console.error('Map does not exist', id); 84 | return null; 85 | }; 86 | 87 | TileMap._checkBankFormat = function (bank) { 88 | if (!bank) { 89 | return { _type: 'maps', maps: [] }; 90 | } 91 | 92 | // DEPRECATED check for old maps format 93 | if (Array.isArray(bank)) { 94 | return { _type: 'maps', maps: bank }; 95 | } 96 | 97 | if (bank._type !== 'maps') { 98 | console.warn('Map bank format incorrect'); 99 | return { _type: 'maps', maps: [] }; 100 | } 101 | 102 | return bank; 103 | }; 104 | 105 | TileMap.loadBank = function (bank) { 106 | bank = TileMap._checkBankFormat(bank); 107 | 108 | // reset current data 109 | _mapById = {}; 110 | _maps = []; 111 | 112 | // load and construct all maps in bank 113 | var maps = bank.maps || []; 114 | for (var i = 0; i < maps.length; i++) { 115 | _maps.push(new TileMap().load(maps[i])); 116 | } 117 | }; 118 | 119 | TileMap.setTileSize = function (width, height) { 120 | TILE_WIDTH = ~~width; 121 | TILE_HEIGHT = ~~height; 122 | }; 123 | 124 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 125 | Object.defineProperty(TileMap.prototype, 'name', { 126 | get: function () { return this._name; }, 127 | set: function (name) { 128 | if (this._name && _mapById[this._name] && _mapById[this._name] === this) delete _mapById[this._name]; 129 | this._name = name; 130 | if (name && !_mapById[name]) _mapById[name] = this; 131 | } 132 | }); 133 | 134 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 135 | TileMap.prototype._init = function (width, height) { 136 | this.width = width; 137 | this.height = height; 138 | this.items = []; 139 | for (var x = 0; x < width; x++) { 140 | this.items.push([]); 141 | for (var y = 0; y < height; y++) { 142 | this.items[x][y] = null; 143 | } 144 | } 145 | if (this.texture) this.texture.resize(width * TILE_WIDTH, height * TILE_HEIGHT); 146 | }; 147 | 148 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 149 | /** Resize map 150 | * @param {number} width - map width 151 | * @param {number} heigth - map heigth 152 | */ 153 | TileMap.prototype.resize = function (width, height) { 154 | var items = this.items; 155 | var w = Math.min(this.width, width); 156 | var h = Math.min(this.height, height); 157 | this._init(width, height); 158 | for (var x = 0; x < w; x++) { 159 | for (var y = 0; y < h; y++) { 160 | this.items[x][y] = items[x][y]; 161 | }} 162 | this.redraw(); 163 | return this; 164 | }; 165 | 166 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 167 | /** Set a tile in the map, the texture is automatically updated with the tile 168 | * @param {number} x - x coordinate of the tile to add 169 | * @param {number} y - y coordinate of the tile to add 170 | * @param {number} sprite - sprite id of the tile (integer between 0 and 255) 171 | * @param {boolean} [flipH] - flip horizontal flag value 172 | * @param {boolean} [flipV] - flip vertical flag value 173 | * @param {boolean} [flipR] - flip rotation flag value 174 | * @param {boolean} [flagA] - user pupose flag A value 175 | * @param {boolean} [flagB] - user pupose flag Bvalue 176 | */ 177 | TileMap.prototype.set = function (x, y, sprite, flipH, flipV, flipR, flagA, flagB) { 178 | if (sprite === null || sprite === undefined) return this.remove(x, y); 179 | if (x < 0 || y < 0 || x >= this.width || y >= this.height) return; 180 | var item = this.items[x][y] = new Tile(x, y, sprite, flipH, flipV, flipR, flagA, flagB); 181 | if (this.texture) { 182 | this.texture.ctx.clearRect(x * TILE_WIDTH, y * TILE_HEIGHT, TILE_WIDTH, TILE_HEIGHT); 183 | this.texture.sprite(item.sprite, item.x * TILE_WIDTH, item.y * TILE_HEIGHT, item.flipH, item.flipV, item.flipR); 184 | } 185 | return this; 186 | }; 187 | 188 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 189 | /** Remove a tile from the map by setting the item at the specified coordinate to null. 190 | * The internal texture is automatically updated. 191 | * @param {number} x - x coordinate of the tile to remove 192 | * @param {number} y - y coordinate of the tile to remove 193 | * @returns {Map} the map itself 194 | */ 195 | TileMap.prototype.remove = function (x, y) { 196 | if (x < 0 || y < 0 || x >= this.width || y >= this.height) return; 197 | this.items[x][y] = null; 198 | if (this.texture) this.texture.ctx.clearRect(x * TILE_WIDTH, y * TILE_HEIGHT, TILE_WIDTH, TILE_HEIGHT); 199 | return this; 200 | }; 201 | 202 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 203 | /** Retrieve the map item at the specified coordinate. 204 | * @param {number} x - x coordinate of the tile to remove 205 | * @param {number} y - y coordinate of the tile to remove 206 | * @returns {Tile | null} tile value at specified coordinates 207 | */ 208 | TileMap.prototype.get = function (x, y) { 209 | if (x < 0 || y < 0 || x >= this.width || y >= this.height) return null; 210 | return this.items[x][y]; 211 | }; 212 | 213 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 214 | /** Redraw the texture. 215 | * @returns {Map} the map itself 216 | */ 217 | TileMap.prototype.redraw = function () { 218 | if (!this.texture) return this; 219 | this.texture.clear(); 220 | for (var x = 0; x < this.width; x++) { 221 | for (var y = 0; y < this.height; y++) { 222 | var i = this.items[x][y]; 223 | if (i) this.texture.sprite(i.sprite, i.x * TILE_WIDTH, i.y * TILE_HEIGHT, i.flipH, i.flipV, i.flipR); 224 | }} 225 | return this; 226 | }; 227 | 228 | 229 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 230 | TileMap.prototype._prepareTexture = function () { 231 | this.texture = new Texture(this.width * TILE_WIDTH, this.height * TILE_HEIGHT); 232 | this.texture.setTilesheet(this.tilesheet); 233 | this.redraw(); 234 | }; 235 | 236 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 237 | /** Release texture memory 238 | */ 239 | TileMap.prototype.release = function () { 240 | this.texture = null; 241 | }; 242 | 243 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 244 | /** Draw map on screen at specified position. 245 | * Alternatively, a map can be drawn from `Texture.draw(map)` 246 | * @param {number} x - x coordinate in pixels 247 | * @param {number} y - y coordinate in pixels 248 | */ 249 | TileMap.prototype.draw = function (x, y) { 250 | if (!this.texture) this._prepareTexture(); 251 | draw(this.texture, x, y); 252 | }; 253 | 254 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 255 | /** Set map tilesheet. The map is redrawn with the new tilesheet. 256 | * @param {Texture | Image | Sprite | null} tilesheet - the new tilesheet for the map. 257 | * If null, use the default tilesheet (assets/tilesheet). 258 | * @returns {Map} the map itself 259 | */ 260 | TileMap.prototype.setTilesheet = function (tilesheet) { 261 | this.tilesheet = tilesheet; 262 | this._tilesheetPath = tilesheet && tilesheet.path || ''; 263 | if (!this.texture) return this; 264 | this.texture.setTilesheet(tilesheet); 265 | this.redraw(); 266 | return this 267 | }; 268 | 269 | TileMap.prototype._setTilesheetPath = function (path) { 270 | this._tilesheetPath = path || ''; 271 | if (!path) return this.setTilesheet(); 272 | var pathes = path.split('/'); 273 | var node = pixelbox.assets || {}; 274 | for (var i = 0; i < pathes.length; i++) { 275 | node = node[pathes[i]]; 276 | if (!node) return console.warn('Could not find tilesheet', path); 277 | } 278 | this.setTilesheet(node); 279 | }; 280 | 281 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 282 | var encode, decode; 283 | 284 | (function () { 285 | var BASE = "#$%&'()*+,-~/0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}. !"; 286 | var INVERSE = {}; 287 | for (var i = 0; i < BASE.length; i++) INVERSE[BASE[i]] = i; 288 | 289 | var LENGTH = BASE.length; 290 | var NULL = LENGTH * LENGTH - 1; 291 | var DUPL = Math.pow(2, 13); 292 | var MAX_DUPL = NULL - DUPL - 1; 293 | 294 | function getCode(value) { 295 | var be = ~~(value / LENGTH); 296 | var le = value % LENGTH; 297 | return BASE[be] + BASE[le]; 298 | } 299 | 300 | encode = function (arr) { 301 | var str = ''; 302 | var count = 0; 303 | for (var i = 0; i < arr.length; i++) { 304 | var value = arr[i]; 305 | if (value === arr[i + 1] && ++count < MAX_DUPL) continue; 306 | if (value === null) value = NULL; 307 | str += getCode(value); 308 | if (count === MAX_DUPL) count--; 309 | if (count !== 0) str += getCode(DUPL + count); 310 | count = 0; 311 | } 312 | 313 | if (count === MAX_DUPL) count--; 314 | if (count !== 0) str += getCode(DUPL + count); 315 | return str; 316 | } 317 | 318 | decode = function (str) { 319 | var arr = []; 320 | for (var i = 0; i < str.length;) { 321 | var be = str[i++]; 322 | var le = str[i++]; 323 | var value = INVERSE[be] * LENGTH + INVERSE[le]; 324 | if (value === NULL) { 325 | arr.push(null); 326 | } else if (value > DUPL) { 327 | var count = value - DUPL; 328 | var duplicate = arr[arr.length - 1]; 329 | 330 | for (var j = 0; j < count; j++) { 331 | arr.push(duplicate); 332 | } 333 | } else { 334 | arr.push(value); 335 | } 336 | } 337 | return arr; 338 | } 339 | })(); 340 | 341 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 342 | /** Serialize the Map to a JSON object. 343 | * @private 344 | * @returns {object} Pixelbox formated map data 345 | */ 346 | TileMap.prototype.save = function () { 347 | var w = this.width; 348 | var h = this.height; 349 | var arr = new Array(w * h); 350 | for (var x = 0; x < w; x++) { 351 | for (var y = 0; y < h; y++) { 352 | var item = this.items[x][y]; 353 | arr[x + y * w] = item 354 | ? item.sprite 355 | + (item.flipH << 8) 356 | + (item.flipV << 9) 357 | + (item.flipR << 10) 358 | + (item.flagA << 11) 359 | + (item.flagB << 12) 360 | : null; 361 | }} 362 | 363 | var obj = { w: w, h: h, name: this.name, sheet: this._tilesheetPath || '', data: encode(arr) }; 364 | return obj; 365 | }; 366 | 367 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 368 | /** Deserialize a JSON object and set the data to this map. 369 | * @private 370 | * @param {object} obj - Pixelbox formated map data 371 | * @returns {Map} the map itself 372 | */ 373 | TileMap.prototype.load = function (obj) { 374 | this.texture = null; 375 | var w = obj.w; 376 | var h = obj.h; 377 | this._init(w, h); 378 | this.name = obj.name || ''; 379 | this._setTilesheetPath(obj.sheet); 380 | var arr = decode(obj.data); 381 | for (var x = 0; x < w; x++) { 382 | for (var y = 0; y < h; y++) { 383 | var d = arr[x + y * w]; 384 | if (d === null) continue; 385 | var tile = d & 255; 386 | var flipH = (d >> 8 ) & 1; 387 | var flipV = (d >> 9 ) & 1; 388 | var flipR = (d >> 10) & 1; 389 | var flagA = (d >> 11) & 1; 390 | var flagB = (d >> 12) & 1; 391 | this.set(x, y, tile, flipH, flipV, flipR, flagA, flagB); 392 | }} 393 | 394 | return this; 395 | }; 396 | 397 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 398 | /** Copy this map to a new map. Tile are duplicated. 399 | * @param {number} [x] - x offset for the copy 400 | * @param {number} [y] - y offset for the copy 401 | * @param {number} [w] - width cropping (width of the copy) 402 | * @param {number} [h] - height cropping (height of the copy) 403 | * @returns {Map} the map copy 404 | */ 405 | TileMap.prototype.copy = function (x, y, w, h) { 406 | x = x || 0; 407 | y = y || 0; 408 | if (w === undefined || w === null) w = this.width; 409 | if (h === undefined || h === null) h = this.height; 410 | var map = new TileMap(w, h); 411 | map.paste(this, -x, -y); 412 | return map; 413 | }; 414 | 415 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 416 | /** Paste another map data in this map. 417 | * @param {Map} map - map to be pasted 418 | * @param {number} [x] - x offset of where to paste map 419 | * @param {number} [y] - y offset of where to paste map 420 | * @param {boolean} [merge] - if set, then null tiles will not overwrite current map tile. 421 | * @returns {Map} the map itself 422 | */ 423 | TileMap.prototype.paste = function (map, x, y, merge) { 424 | x = x || 0; 425 | y = y || 0; 426 | var width = Math.min(map.width, this.width - x); 427 | var height = Math.min(map.height, this.height - y); 428 | var sx = Math.max(0, -x); 429 | var sy = Math.max(0, -y); 430 | 431 | for (var i = sx; i < width; i++) { 432 | for (var j = sy; j < height; j++) { 433 | var item = map.items[i][j]; 434 | if (!item) { 435 | if (merge) continue; 436 | this.remove(i + x, j + y); 437 | continue; 438 | } 439 | this.set(i + x, j + y, item.sprite, item.flipH, item.flipV, item.flipR, item.flagA, item.flagB); 440 | } 441 | } 442 | return this; 443 | }; 444 | 445 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 446 | /** Clear the whole map content by setting all its items to null 447 | * @returns {Map} the map itself 448 | */ 449 | TileMap.prototype.clear = function () { 450 | for (var x = 0; x < this.width; x++) { 451 | for (var y = 0; y < this.height; y++) { 452 | this.items[x][y] = null; 453 | }} 454 | if (this.texture) this.texture.clear(); 455 | return this; 456 | }; 457 | 458 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 459 | /** Find specific tiles in the map 460 | * @param {number} [sprite] - sprite id of the tile to find. 461 | * Set this parameter to null to find empty tiles. 462 | * @param {boolean} [flagA] - filter with flag A value 463 | * @param {boolean} [flagB] - filter with flag B value 464 | */ 465 | TileMap.prototype.find = function (sprite, flagA, flagB) { 466 | if (sprite === null) return this._findNull(); 467 | if (flagA === undefined) flagA = null; 468 | if (flagB === undefined) flagB = null; 469 | var result = []; 470 | for (var x = 0; x < this.width; x++) { 471 | for (var y = 0; y < this.height; y++) { 472 | var item = this.items[x][y]; 473 | if (!item) continue; 474 | var isSameFlagA = flagA === null || item.flagA === flagA; 475 | var isSameFlagB = flagB === null || item.flagB === flagB; 476 | if (item.sprite === sprite && isSameFlagA && isSameFlagB) result.push(item); 477 | }} 478 | return result; 479 | }; 480 | 481 | TileMap.prototype._findNull = function () { 482 | var result = []; 483 | for (var x = 0; x < this.width; x++) { 484 | for (var y = 0; y < this.height; y++) { 485 | if (this.items[x][y] === null) result.push({ x: x, y: y }); 486 | }} 487 | return result; 488 | }; 489 | -------------------------------------------------------------------------------- /TileMap/transforms.js: -------------------------------------------------------------------------------- 1 | var TileMap = require('./index.js'); 2 | 3 | var FLAG_MAP = [ 4 | /* 0 */ { H: false, V: false, R: false }, 5 | /* 1 */ { H: true, V: false, R: false }, 6 | /* 2 */ { H: false, V: true, R: false }, 7 | /* 3 */ { H: true, V: true, R: false }, 8 | /* 4 */ { H: false, V: false, R: true }, 9 | /* 5 */ { H: true, V: false, R: true }, 10 | /* 6 */ { H: false, V: true, R: true }, 11 | /* 7 */ { H: true, V: true, R: true } 12 | ]; 13 | 14 | var TILE_TRANSFORM_MAP = [ 15 | // H V R C 16 | /* 0 */ [1, 2, 4, 7], 17 | /* 1 */ [0, 3, 6, 5], 18 | /* 2 */ [3, 0, 5, 6], 19 | /* 3 */ [2, 1, 7, 4], 20 | /* 4 */ [5, 6, 3, 0], 21 | /* 5 */ [4, 7, 1, 2], 22 | /* 6 */ [7, 4, 2, 1], 23 | /* 7 */ [6, 5, 0, 3] 24 | ]; 25 | 26 | function tileFlipH(tile, w, h) { 27 | tile.x = w - 1 - tile.x; 28 | return tile; 29 | } 30 | 31 | function tileFlipV(tile, w, h) { 32 | tile.y = h - 1 - tile.y; 33 | return tile; 34 | } 35 | 36 | function tileFlipR(tile, w, h) { 37 | var x = tile.x; 38 | tile.x = h - 1 - tile.y; 39 | tile.y = x; 40 | return tile; 41 | } 42 | 43 | function tileFlipC(tile, w, h) { 44 | var x = tile.x; 45 | tile.x = tile.y; 46 | tile.y = w - 1 - x; 47 | return tile; 48 | } 49 | 50 | 51 | var TRANSFORM_POSITION = [ 52 | /* 0 */ tileFlipH, 53 | /* 1 */ tileFlipV, 54 | /* 2 */ tileFlipR, 55 | /* 3 */ tileFlipC 56 | ]; 57 | 58 | function getTransformFlag(tile) { 59 | return ~~tile.flipH 60 | + ~~tile.flipV * 2 61 | + ~~tile.flipR * 4; 62 | } 63 | 64 | function applyTransformFlag(tile, flag) { 65 | var transform = FLAG_MAP[flag]; 66 | tile.flipH = transform.H; 67 | tile.flipV = transform.V; 68 | tile.flipR = transform.R; 69 | } 70 | 71 | function transformTile(tile, w, h, transformCode) { 72 | var flag = getTransformFlag(tile); 73 | applyTransformFlag(tile, TILE_TRANSFORM_MAP[flag][transformCode]); 74 | return TRANSFORM_POSITION[transformCode](tile, w, h); 75 | } 76 | 77 | 78 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 79 | TileMap.prototype._transform = function (transformCode) { 80 | var tiles = this.items; 81 | var w = this.width; 82 | var h = this.height; 83 | 84 | if (transformCode > 1) { 85 | // it's a rotation 86 | this._init(h, w); 87 | } else { 88 | this._init(w, h); 89 | } 90 | 91 | for (var x = 0; x < w; x++) { 92 | for (var y = 0; y < h; y++) { 93 | var tile = tiles[x][y]; 94 | if (!tile) continue; 95 | transformTile(tile, w, h, transformCode); 96 | this.items[tile.x][tile.y] = tile; 97 | }} 98 | 99 | this.redraw(); 100 | }; 101 | 102 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 103 | TileMap.prototype.flipH = function () { this._transform(0); }; 104 | TileMap.prototype.flipV = function () { this._transform(1); }; 105 | TileMap.prototype.flipR = function () { this._transform(2); }; 106 | TileMap.prototype.flipC = function () { this._transform(3); }; 107 | 108 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 109 | TileMap.prototype.trim = function () { 110 | // init bounds as an "anti-box" 111 | var l = this.width; 112 | var r = 0; 113 | var t = this.height; 114 | var b = 0; 115 | 116 | // find bounds 117 | for (var x = 0; x < this.width; x++) { 118 | for (var y = 0; y < this.height; y++) { 119 | if (this.get(x, y) === null) continue; 120 | l = Math.min(x, l); 121 | r = Math.max(x, r); 122 | t = Math.min(y, t); 123 | b = Math.max(y, b); 124 | } 125 | } 126 | 127 | var w = r - l + 1; 128 | var h = b - t + 1; 129 | if (w <= 0 || h <= 0) return null; 130 | return this.copy(l, t, w, h); 131 | }; -------------------------------------------------------------------------------- /TileMap/webGL.js: -------------------------------------------------------------------------------- 1 | var TileMap = require('./index.js'); 2 | var Texture = require('../Texture'); // webGL Texture 3 | var batcher = require('../webGL/batcher'); 4 | var pixelbox = require('..'); 5 | var settings = pixelbox.settings; 6 | 7 | var TILE_WIDTH = settings.tileSize.width; 8 | var TILE_HEIGHT = settings.tileSize.height; 9 | var SCREEN_WIDTH = settings.screen.width; 10 | var SCREEN_HEIGHT = settings.screen.height; 11 | var SPRITE_RENDERER = batcher.renderers.sprite; 12 | 13 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 14 | var ROT_MAP = { 15 | false: { false: { false: { flipH: false, flipV: false, flipR: true }, 16 | true: { flipH: true, flipV: true, flipR: false } }, 17 | true: { false: { flipH: true, flipV: false, flipR: true }, 18 | true: { flipH: false, flipV: true, flipR: false } } }, 19 | true: { false: { false: { flipH: false, flipV: true, flipR: true }, 20 | true: { flipH: true, flipV: false, flipR: false } }, 21 | true: { false: { flipH: true, flipV: true, flipR: true }, 22 | true: { flipH: false, flipV: false, flipR: false } } } 23 | }; 24 | 25 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 26 | TileMap.prototype._prepareTexture = function () { /* NOP */ }; 27 | TileMap.prototype.release = function () { /* NOP */ }; 28 | TileMap.prototype.redraw = function () { /* NOP */ }; 29 | 30 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 31 | /** 32 | * NOTE: By overwriting draw, we prevent TileMap to create a Texture instance 33 | */ 34 | TileMap.prototype.draw = function (px, py, flipH, flipV, flipR) { 35 | this._draw(px, py, flipH, flipV, flipR, pixelbox.$screen); 36 | }; 37 | 38 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 39 | TileMap.prototype._draw = function (px, py, flipH, flipV, flipR, renderTarget) { 40 | var image = this.tilesheet || Texture.prototype.tilesheet; 41 | var renderer = batcher.prepare(SPRITE_RENDERER, image, renderTarget); 42 | 43 | var camera = pixelbox.$screen.camera; 44 | px = Math.round((px || 0) - camera.x); 45 | py = Math.round((py || 0) - camera.y); 46 | 47 | // TODO: could probably avoid recalculation if nothing changed (camera, x, y) 48 | 49 | var tilesheet = this.tilesheet || assets.tilesheet; // TODO: get default tilesheet 50 | var ox = 0; 51 | var oy = 0; 52 | 53 | if (tilesheet._isSprite) { 54 | // Sprite 55 | ox = tilesheet.x; 56 | oy = tilesheet.y; 57 | } 58 | 59 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 60 | // Render with a flip flags set (special case, normally rarely used) 61 | if (flipH || flipV || flipR) { 62 | var width, height; 63 | 64 | if (flipR) { 65 | width = this.height; 66 | height = this.width; 67 | } else { 68 | width = this.width; 69 | height = this.height; 70 | } 71 | 72 | var startX = Math.max(0, ~~(-px / TILE_WIDTH)); 73 | var startY = Math.max(0, ~~(-py / TILE_HEIGHT)); 74 | var endX = Math.min(width, ~~(1 + (SCREEN_WIDTH - px) / TILE_WIDTH)); 75 | var endY = Math.min(height, ~~(1 + (SCREEN_HEIGHT - py) / TILE_HEIGHT)); 76 | 77 | for (var y = startY; y < endY; y++) { 78 | for (var x = startX; x < endX; x++) { 79 | var sourceX, sourceY; 80 | 81 | // source tile 82 | if (flipR) { 83 | sourceX = y; 84 | sourceY = this.height - 1 - x; 85 | } else { 86 | sourceX = x; 87 | sourceY = y; 88 | } 89 | if (flipH) sourceX = this.width - 1 - sourceX; 90 | if (flipV) sourceY = this.height - 1 - sourceY; 91 | 92 | var tile = this.get(sourceX, sourceY); 93 | if (!tile) continue; 94 | 95 | var sprite = tile.sprite; 96 | 97 | // uv coordinates from sprite index 98 | var u1 = (sprite % 16) * TILE_WIDTH + ox; 99 | var v1 = ~~(sprite / 16) * TILE_HEIGHT + oy; 100 | 101 | // vertex positions 102 | var x1 = px + x * TILE_WIDTH; 103 | var y1 = py + y * TILE_HEIGHT; 104 | 105 | // transform tile flags 106 | var dh, dv, dr; 107 | if (flipR) { 108 | var f = ROT_MAP[tile.flipH][tile.flipV][tile.flipR]; 109 | dh = f.flipH; 110 | dv = f.flipV; 111 | dr = f.flipR; 112 | if (flipH) dv = !dv; 113 | if (flipV) dh = !dh; 114 | } else { 115 | dh = tile.flipH; 116 | dv = tile.flipV; 117 | dr = tile.flipR; 118 | if (flipH) dh = !dh; 119 | if (flipV) dv = !dv; 120 | } 121 | 122 | renderer.pushSprite(x1, y1, TILE_WIDTH, TILE_HEIGHT, u1, v1, dh, dv, dr); 123 | }} 124 | 125 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 126 | // Map is drawn without any flip flag (this should covers 95% of use cases) 127 | } else { 128 | var startX = Math.max(0, ~~(-px / TILE_WIDTH)); 129 | var startY = Math.max(0, ~~(-py / TILE_HEIGHT)); 130 | var endX = Math.min(this.width, ~~(1 + (SCREEN_WIDTH - px) / TILE_WIDTH)); 131 | var endY = Math.min(this.height, ~~(1 + (SCREEN_HEIGHT - py) / TILE_HEIGHT)); 132 | 133 | for (var y = startY; y < endY; y++) { 134 | for (var x = startX; x < endX; x++) { 135 | var tile = this.get(x, y); 136 | if (!tile) continue; 137 | 138 | var sprite = tile.sprite; 139 | 140 | // uv coordinates from sprite index 141 | var u1 = (sprite % 16) * TILE_WIDTH + ox; 142 | var v1 = ~~(sprite / 16) * TILE_HEIGHT + oy; 143 | 144 | // vertex positions 145 | var x1 = px + x * TILE_WIDTH; 146 | var y1 = py + y * TILE_HEIGHT; 147 | 148 | renderer.pushSprite(x1, y1, TILE_WIDTH, TILE_HEIGHT, u1, v1, tile.flipH, tile.flipV, tile.flipR); 149 | 150 | // TODO: cache uv in the tiles ? 151 | // TODO: use index buffer to reduce vertex buffer size 152 | // TODO: save buffer index infos in the tile 153 | }} 154 | } 155 | }; 156 | 157 | module.exports = TileMap; 158 | -------------------------------------------------------------------------------- /assetLoader/index.js: -------------------------------------------------------------------------------- 1 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 2 | /** 3 | * @module assetLoader 4 | * @desc Loading functions helpers 5 | * 6 | * @author Cedric Stoquer 7 | */ 8 | 9 | 10 | function loadData(path, responseType, cb) { 11 | var xobj = new XMLHttpRequest(); 12 | xobj.responseType = responseType || 'arraybuffer'; 13 | 14 | xobj.onreadystatechange = function onXhrStateChange() { 15 | if (~~xobj.readyState !== 4) return; 16 | if (~~xobj.status !== 200 && ~~xobj.status !== 0) { 17 | return cb && cb('xhrError:' + xobj.status); 18 | } 19 | return cb && cb(null, xobj.response); 20 | }; 21 | 22 | xobj.open('GET', path, true); 23 | xobj.send(); 24 | } 25 | exports.loadData = loadData; 26 | 27 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 28 | /** 29 | * @function module:loader.loadJson 30 | * @desc load a json file 31 | * 32 | * @param {String} path - file path 33 | * @param {Function} cb - asynchronous callback function 34 | */ 35 | function loadJson(path, cb) { 36 | var xobj = new XMLHttpRequest(); 37 | xobj.onreadystatechange = function () { 38 | if (~~xobj.readyState !== 4) return; 39 | if (~~xobj.status !== 200) return cb('xhrError:' + xobj.status); 40 | return cb && cb(null, JSON.parse(xobj.response)); 41 | }; 42 | xobj.open('GET', path, true); 43 | xobj.send(); 44 | } 45 | exports.loadJson = loadJson; 46 | 47 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 48 | /** 49 | * @function module:loader.loadImage 50 | * @desc load an image file 51 | * 52 | * @param {String} path - file path 53 | * @param {Function} cb - asynchronous callback function 54 | */ 55 | function loadImage(path, cb) { 56 | var img = new Image(); 57 | // TODO: remove listeners when load / error 58 | img.onload = function () { 59 | this.onload = null; 60 | this.onerror = null; 61 | cb && cb(null, this); 62 | }; 63 | img.onerror = function () { 64 | this.onload = null; 65 | this.onerror = null; 66 | cb && cb('img:' + path); 67 | }; 68 | img.src = path; 69 | } 70 | exports.loadImage = loadImage; 71 | 72 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 73 | /** 74 | * @function module:loader.loadSound 75 | * @desc load an image file 76 | * 77 | * @param {String} path - file path 78 | * @param {Function} cb - asynchronous callback function 79 | */ 80 | function loadSound(path, cb) { 81 | var snd = new Audio(); 82 | snd.preload = true; 83 | snd.loop = false; 84 | 85 | function onSoundLoad() { 86 | cb && cb(null, snd); 87 | snd.removeEventListener('canplaythrough', onSoundLoad); 88 | snd.removeEventListener('error', onSoundError); 89 | } 90 | 91 | function onSoundError() { 92 | cb && cb('snd:load'); 93 | snd.removeEventListener('canplaythrough', onSoundLoad); 94 | snd.removeEventListener('error', onSoundError); 95 | } 96 | 97 | snd.addEventListener('canplaythrough', onSoundLoad); 98 | snd.addEventListener('error', onSoundError); 99 | snd.src = path; 100 | snd.load(); 101 | } 102 | exports.loadSound = loadSound; 103 | 104 | 105 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 106 | /** 107 | * @function module:loader.preloadStaticAssets 108 | * 109 | * @desc Preload all static assets of the game. 110 | * The function first ask the server for the asset list. 111 | * Server respond with an object containing the list of images 112 | * to load and all data that are put in the www/asset folder. 113 | * At this step, if request fail, function send an error. 114 | * The function then proceed the loading of image assets. 115 | * If an image loading fail, the loading continue, and loading 116 | * status is set to 1 (an image load fail). 117 | * Images are load by 5 in parallel. 118 | * 119 | * Function end and wil return an object that mix all data and 120 | * all assets so that it will have the same structure as the 121 | * 'assets' folder. 122 | * 123 | * 124 | * Assets list and data are automaticaly generated by server 125 | * Just drop images and json files in the www/asset/ folder 126 | * and the server will take care of it ! 127 | * 128 | * 129 | * @param {Function} cb - asynchronous callback function to 130 | * call when all is preloaded 131 | * 132 | * @param {Function} onEachLoad - optional callback function called 133 | * every time one file is loaded 134 | * (for loading progress purpose) 135 | * 136 | */ 137 | 138 | function preloadStaticAssets(assetList, cb, onEachLoad) { 139 | var data = assetList.dat; 140 | var imgCount = assetList.img.length; 141 | var count = imgCount + assetList.snd.length; 142 | var root = assetList.root; 143 | var load = 0; 144 | var done = 0; 145 | function storeAsset(path, obj) { 146 | var splitted = path.split('/'); 147 | var filename = splitted.pop(); 148 | var id = filename.split('.'); 149 | id.pop(); 150 | id = id.join('.'); 151 | var container = data; 152 | for (var i = 0, len = splitted.length; i < len; i++) { 153 | container = container[splitted[i]]; 154 | } 155 | container[id] = obj; 156 | splitted.push(id); 157 | obj.name = id; 158 | obj.path = splitted.join('/'); 159 | } 160 | function loadAssets() { 161 | var current = load + done; 162 | var percent = current / count; 163 | onEachLoad && onEachLoad(load, current, count, percent); 164 | var path; 165 | var loadFunc; 166 | if (current < imgCount) { 167 | path = assetList.img[current]; 168 | loadFunc = loadImage; 169 | } else { 170 | path = assetList.snd[current - imgCount]; 171 | loadFunc = loadSound; 172 | } 173 | done += 1; 174 | loadFunc(root + path, function onAssetLoaded(error, img) { 175 | if (!error) storeAsset(path, img); 176 | load += 1; 177 | done -= 1; 178 | if (load + done < count) loadAssets() 179 | else if (done === 0) cb(null, data); 180 | }); 181 | } 182 | // loading assets in parallel, with a limit of 5 parallel downloads. 183 | if (count === 0) return cb(null, data); 184 | var parallel = Math.min(5, count - 1); 185 | for (var j = 0; j <= parallel; j++) loadAssets(); 186 | // }); 187 | } 188 | exports.preloadStaticAssets = preloadStaticAssets; 189 | -------------------------------------------------------------------------------- /bleeper/index.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.bleeper=e():t.bleeper=e()}(window,(function(){return function(t){var e={};function s(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return t[i].call(o.exports,o,o.exports,s),o.l=!0,o.exports}return s.m=t,s.c=e,s.d=function(t,e,i){s.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:i})},s.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},s.t=function(t,e){if(1&e&&(t=s(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var i=Object.create(null);if(s.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)s.d(i,o,function(e){return t[e]}.bind(null,o));return i},s.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return s.d(e,"a",e),e},s.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},s.p="",s(s.s=2)}([function(t,e,s){var i=s(4);function o(){this.name="",this.speed=20,this.pmin=0,this.pmax=127,this.snap=!1,this.steps=[],this.audio=null}t.exports=o,o.prototype.serialize=function(){for(var t="",e=0;e11&&(this.name=this.name.substr(0,11)),this.speed=Math.max(0,Math.min(53,~~this.speed)),this.pmin=Math.max(0,Math.min(101,~~this.pmin)),this.pmax=Math.max(26,Math.min(127,~~this.pmax));for(var t=0;t92&&(t+=8,e-=92),i.encodeUint6(t)+i.encodeUint6(e)+i.encodeUint6(this.volume)},o.prototype.deserialize=function(t,e){var s=i.decodeUint6(t,e),o=i.decodeUint6(t,e+1);return this.volume=i.decodeUint6(t,e+2),s>=8&&(s-=8,o+=92),this.wave=s,this.pitch=o,this},o.prototype.check=function(){this.wave=Math.max(0,Math.min(7,~~this.wave)),this.pitch=Math.max(0,Math.min(93,~~this.pitch)),this.volume=Math.max(0,Math.min(62,~~this.volume))}},function(t,e){for(var s="#$%&'()*+,-~/0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}. !",i=s.length,o={},n=0;n1&&(this._oscPos=this._oscPos%1),i=this._oscPos>this._duty?this._amp:-this._amp,t[o]=i;this._prevOut=i},h.prototype._generateTriangle=function(t,e,s){for(var i=this._prevOut,o=e;o1&&(this._oscPos=this._oscPos%1),i=r[~~(32*this._oscPos)]*this._amp,t[o]=i;this._prevOut=i},h.prototype._generateSine=function(t,e,s){for(var i=this._prevOut,o=e;o1&&(this._oscPos=this._oscPos%1),i=Math.sin(this._oscPos*n)*this._amp,t[o]=i;this._prevOut=i},h.prototype._generateSaw=function(t,e,s){for(var i=this._prevOut,o=e;o1&&(this._oscPos=this._oscPos%1),i=(this._oscPos-.5)*this._amp,t[o]=i;this._prevOut=i},h.prototype._generateNoise=function(t,e,s){for(var i=this._prevOut,o=e;o1){this._oscPos=this._oscPos%1;var n=!(16384&this._oscState)^!(8192&this._oscState);this._oscState=32767&(this._oscState<<1|n),this._oscOut=(16384&this._oscState)>>14}i=(this._oscOut-.5)*this._amp,t[o]=i}this._prevOut=i},h.prototype.generateEndDamping=function(t,e,s){for(var i=this._prevOut,o=e;o127&&(t=127);var e=~~t,s=t-e,o=i[e];return o+s*(i[e+1]-o)}}])})); -------------------------------------------------------------------------------- /bleeper/readme.md: -------------------------------------------------------------------------------- 1 | # Bleeper 2 | 3 | Bleeper component can be activated from the `Components` tab of the `Project settings` panel. 4 | Note that Bleeper depends on the *AudioManager* component. 5 | 6 | ## Usage 7 | 8 | There are several ways to play Bleeper sounds: 9 | 10 | ### Named sounds 11 | If the sound is named, it is accessible on the `assets` global, and automatically added to AudioManager. 12 | ```js 13 | // from assets global 14 | assets.bleeper.mySound.play(volume, panoramic, pitch); // all parameters optionnals 15 | 16 | // using audioManager 17 | sfx('mySound', volume, panoramic, pitch); // using default channel 18 | audioManager.playSound('sfx', 'mySound', volume, panoramic, pitch); 19 | ``` 20 | 21 | ### Using bleeper module 22 | Bleeper module exposes an array of all sounds defined in the program. 23 | ```js 24 | var bleeper = require('pixelbox/bleeper'); 25 | bleeper.sounds[3].play(volume, panoramic, pitch); 26 | ``` 27 | -------------------------------------------------------------------------------- /bootstrap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Pixelbox Bootstrap 3 | * @author Cedric Stoquer 4 | * 5 | * @license 6 | * This file is part of the Pixelbox SDK. 7 | * 8 | * Copyright (C) 2016-2020 Cedric Stoquer 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 11 | * this software and associated documentation files (the "Software"), to deal in 12 | * the Software without restriction, including without limitation the rights to 13 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 14 | * the Software, and to permit persons to whom the Software is furnished to do so, 15 | * subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all 18 | * copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 22 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 23 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 24 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 25 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | */ 27 | 28 | var pixelbox = require('.'); 29 | var projectData = __PROJECT_DATA__; 30 | 31 | // projectData.settings.buildTime = __BUILD_TIME__; 32 | // projectData.settings.version = __GAME_VERSION__; 33 | // projectData.settings.pixelboxVersion = __PIXELBOX_VERSION__; 34 | var settings = pixelbox.settings = projectData.settings; 35 | 36 | var assetLoader = require('./assetLoader'); 37 | 38 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 39 | // built-in modules 40 | 41 | pixelbox.inherits = require('./inherits'); 42 | 43 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 44 | // electron 45 | if (__BUILD_TYPE__ === 'electron') { 46 | var remote = require('electron').remote; 47 | pixelbox.setFullScreen = function(value) { 48 | remote.getCurrentWindow().setFullScreen(value); 49 | } 50 | } else { 51 | pixelbox.setFullScreen = function () {}; 52 | } 53 | 54 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 55 | // TINA 56 | if (__USE_TINA__) { 57 | var TINA = require('tina'); 58 | pixelbox.TINA = TINA; 59 | 60 | // setup TINA with a ticker 61 | var ticker = new TINA.Ticker().useAsDefault(); 62 | TINA.add(ticker); 63 | } 64 | 65 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 66 | // Polyfill to ensure only one instance of AudioContext 67 | (function(){ 68 | var audioContextInstance = null; 69 | var AudioContext = window.AudioContext || window.webkitAudioContext; 70 | 71 | function resumeContext() { 72 | audioContextInstance.resume(); 73 | document.body.removeEventListener('mousedown', resumeContext); 74 | document.body.removeEventListener('touchstart', resumeContext); 75 | } 76 | 77 | window.AudioContext = window.webkitAudioContext = function () { 78 | if (audioContextInstance) return audioContextInstance; 79 | audioContextInstance = new AudioContext(); 80 | 81 | document.body.addEventListener('mousedown', resumeContext); 82 | document.body.addEventListener('touchstart', resumeContext); 83 | 84 | return audioContextInstance; 85 | } 86 | })(); 87 | 88 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 89 | // Audio Manager 90 | if (__USE_AUDIO__ || __USE_BLEEPER__) { 91 | var AudioManager = require('audio-manager'); 92 | var audioManager = pixelbox.audioManager = new AudioManager(['sfx']); 93 | audioManager.settings.audioPath = 'audio/'; 94 | audioManager.settings.defaultFade = 0.3; 95 | 96 | audioManager.init(); 97 | audioManager.setVolume('sfx', 1.0); 98 | 99 | pixelbox.sfx = function (soundId, volume, panoramic, pitch) { 100 | audioManager.playSound('sfx', soundId, volume, panoramic, pitch); 101 | }; 102 | 103 | pixelbox.music = function (soundId, volume, loopStart, loopEnd) { 104 | if (!soundId) { 105 | audioManager.stopLoopSound('sfx'); 106 | return; 107 | } 108 | audioManager.playLoopSound('sfx', soundId, volume, 0, 0, loopStart, loopEnd); 109 | }; 110 | } 111 | 112 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 113 | // controls 114 | 115 | var button = pixelbox.btn = {}; 116 | var bpress = pixelbox.btnp = {}; 117 | var brelease = pixelbox.btnr = {}; 118 | var keyMap = {}; 119 | var buttonNames = []; 120 | var buttonLength = 0; 121 | 122 | (function setupKeyMap() { 123 | var controls = settings.controls || { up: 38, down: 40, left: 37, right: 39, A: 32, B: 88 }; 124 | for (var key in controls) { 125 | var keyCode = controls[key]; 126 | keyMap[keyCode] = key; 127 | button[key] = false; 128 | bpress[key] = false; 129 | brelease[key] = false; 130 | buttonNames.push(key); 131 | } 132 | buttonLength = buttonNames.length; 133 | })(); 134 | 135 | 136 | if (__KEYBOARD__) { 137 | 138 | function resetControlTriggers() { 139 | for (var i = 0; i < buttonLength; i++) { 140 | bpress[buttonNames[i]] = false; 141 | brelease[buttonNames[i]] = false; 142 | } 143 | } 144 | 145 | function keyChange(keyCode, isPressed) { 146 | var key = keyMap[keyCode]; 147 | if (!key) return; 148 | if ( isPressed && !button[key]) bpress[key] = true; 149 | if (!isPressed && button[key]) brelease[key] = true; 150 | button[key] = isPressed; 151 | } 152 | 153 | window.addEventListener('keydown', function onKeyPressed(e) { 154 | e.preventDefault(); 155 | if (e.repeat) return; 156 | keyChange(e.keyCode, true); 157 | }); 158 | 159 | window.addEventListener('keyup', function onKeyRelease(e) { 160 | e.preventDefault(); 161 | keyChange(e.keyCode, false); 162 | }); 163 | } 164 | 165 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 166 | // utility functions 167 | 168 | pixelbox.chr$ = function (chr) { 169 | return String.fromCharCode(chr); 170 | }; 171 | 172 | pixelbox.clamp = function (value, min, max) { 173 | return Math.max(min, Math.min(max, value)); 174 | }; 175 | 176 | pixelbox.random = function (n, max) { 177 | if (max === undefined) return ~~(n * Math.random()); 178 | return ~~(n + Math.random() * (max - n)); 179 | }; 180 | 181 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 182 | // pixelbox Core 183 | 184 | var webGL; 185 | 186 | if (__USE_CORE__) { 187 | var Texture = require('./Texture'); 188 | var TileMap = require('./TileMap'); 189 | 190 | if (__USE_WEBGL__) { 191 | webGL = require('./webGL'); 192 | require('./Texture/webGL'); 193 | require('./TileMap/webGL'); 194 | pixelbox.webGL = webGL; 195 | if (__MINI_TEXT__) require('./Texture/textWebGL'); 196 | } else { 197 | require('./Texture/canvas2D'); 198 | if (__MINI_TEXT__) require('./Texture/textCanvas2D'); 199 | } 200 | 201 | var TILE_WIDTH = settings.tileSize.width; 202 | var TILE_HEIGHT = settings.tileSize.height; 203 | Texture.setTileSize(TILE_WIDTH, TILE_HEIGHT); 204 | TileMap.setTileSize(TILE_WIDTH, TILE_HEIGHT); 205 | 206 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 207 | // Texture 208 | 209 | pixelbox.texture = function (img) { 210 | var texture = new Texture(img.width, img.height); 211 | texture.clear().draw(img, 0, 0); 212 | return texture; 213 | } 214 | /** change default tilesheet used. 215 | * @param {Image | Texture | Map} img - tilesheet to use as default. 216 | * It can be any renderable thing in Pixelbox 217 | */ 218 | pixelbox.tilesheet = function(img) { 219 | return Texture.prototype.setGlobalTilesheet(img); 220 | }; 221 | 222 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 223 | // screen 224 | 225 | function createScreen() { 226 | var params = settings.screen; 227 | var width = params.width; 228 | var height = params.height; 229 | var pixelSize = params.pixelSize; 230 | 231 | var texture = pixelbox.$screen = new Texture(width, height); 232 | if (__USE_WEBGL__) texture.canvas = webGL.context.canvas; 233 | var canvas = texture.canvas; 234 | var style = canvas.style; 235 | 236 | document.body.appendChild(canvas); 237 | 238 | if (params.fullscreen) { 239 | if (params.keepAspectRatio) { 240 | function resizeScreen() { 241 | var iw = window.innerWidth; 242 | var ih = window.innerHeight; 243 | var sw = width * pixelSize.width; 244 | var sh = height * pixelSize.height; 245 | var ratio = Math.min(iw / sw, ih / sh); 246 | var fw = sw * ratio; 247 | var fh = sh * ratio; 248 | // get current canvas (allow $screen redefinition, e.g. webGL) 249 | var style = pixelbox.$screen.canvas.style; 250 | style.width = fw + 'px'; 251 | style.height = fh + 'px'; 252 | style.left = (iw - fw) / 2 + 'px'; 253 | style.top = (ih - fh) / 2 + 'px'; 254 | } 255 | addEventListener('resize', resizeScreen); 256 | resizeScreen(); 257 | } else { 258 | style.width = style.height = '100%'; 259 | } 260 | } else { 261 | style.width = width * pixelSize.width + 'px'; 262 | style.height = height * pixelSize.height + 'px'; 263 | } 264 | 265 | return texture; 266 | } 267 | 268 | var screen = createScreen(); 269 | 270 | pixelbox.cls = function () { return screen.cls(); }; 271 | pixelbox.sprite = function (s, x, y, h, v, r) { return screen.sprite(s, x, y, h, v, r); }; 272 | pixelbox.draw = function (i, x, y, h, v, r) { return screen.draw(i, x, y, h, v, r); }; 273 | pixelbox.rect = function (x, y, w, h) { return screen.rect(x, y, w, h); }; 274 | pixelbox.rectf = function (x, y, w, h) { return screen.rectf(x, y, w, h); }; 275 | pixelbox.camera = function (x, y) { return screen.setCamera(x, y); }; 276 | pixelbox.pen = function (paletteIndex) { return screen.pen(paletteIndex); }; 277 | pixelbox.paper = function (paletteIndex) { return screen.paper(paletteIndex); }; 278 | 279 | if (__MINI_TEXT__) { 280 | pixelbox.locate = function (i, j) { return screen.locate(i, j); }; 281 | pixelbox.print = function (text, x, y) { return screen.print(text, x, y); }; 282 | pixelbox.println = function (text) { return screen.println(text); }; 283 | pixelbox.setCharset = function (img) { return screen.setCharset(img); }; 284 | } 285 | 286 | pixelbox.getMap = TileMap.getMap; 287 | pixelbox.rectfill = pixelbox.rectf; // legacy 288 | 289 | if (__NO_CONTEXT_MENU__) { 290 | // disable browser's context menu 291 | screen.canvas.addEventListener('contextmenu', function (e) { e.preventDefault(); return false; }); 292 | } 293 | } else { 294 | if (__NO_CONTEXT_MENU__) { 295 | document.body.addEventListener('contextmenu', function (e) { e.preventDefault(); return false; }); 296 | } 297 | } 298 | 299 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 300 | // main 301 | 302 | var FRAME_INTERVAL = 1 / 60; 303 | 304 | var requestAnimationFrame = 305 | window.requestAnimationFrame || 306 | window.webkitRequestAnimationFrame || 307 | window.mozRequestAnimationFrame || 308 | window.oRequestAnimationFrame || 309 | window.msRequestAnimationFrame || 310 | function nextFrame(callback) { window.setTimeout(callback, FRAME_INTERVAL); }; 311 | 312 | window.frameId = 0; 313 | 314 | function onAssetsLoaded(error, assets) { 315 | if (__USE_CORE__) screen.paper(0).pen(1).cls(); 316 | 317 | if (error) { 318 | console.error(error); 319 | print(error); 320 | return; 321 | } 322 | 323 | pixelbox.assets = assets; 324 | 325 | // expose pixelbox on global scope 326 | for (var key in pixelbox) { 327 | window[key] = pixelbox[key]; 328 | } 329 | 330 | if (__USE_BLEEPER__) { 331 | var bleeper = pixelbox.bleeper = require('./bleeper'); 332 | window.bleeper = bleeper; 333 | // NOTA: `assets.bleeper` program is overwritten with a sound map 334 | if (assets.bleeper) bleeper.loadProgram(assets.bleeper); 335 | } 336 | 337 | if (__USE_TRACKER__) { 338 | var patatracker = pixelbox.patatracker = require('./pataTracker'); 339 | window.patatracker = patatracker; 340 | if (assets.patatracker) patatracker.loadData(assets.patatracker); 341 | } 342 | 343 | if (__HAS_ATLAS__) { 344 | require('./spritesheet').unpackSpritesheets(); 345 | } 346 | 347 | if (__USE_CORE__) { 348 | // extract palette 349 | function getPalette() { 350 | var Color = require('./Color'); 351 | var palette = settings.palette; 352 | 353 | if (palette.file) { 354 | // TODO: allow file to be json data 355 | var paths = palette.file.split('/'); 356 | var img = assets; 357 | while (img && paths.length) img = img[paths.shift()]; 358 | if (!img) return; // TODO: default palette 359 | 360 | var w = img.width; 361 | var h = img.height; 362 | var ox = img.x || 0; 363 | var oy = img.y || 0; 364 | if (img._isSprite) img = img.img; 365 | 366 | var createCanvas = require('./domUtils/createCanvas'); 367 | var ctx = createCanvas(w, h).getContext('2d'); 368 | ctx.drawImage(img, ox, oy, w, h, 0, 0, w, h); 369 | var pixels = ctx.getImageData(0, 0, w, h).data; 370 | 371 | var colors = []; 372 | for (var i = 0; i < pixels.length; i += 4) { 373 | if (pixels[i + 3] <= 1) continue; 374 | colors.push(new Color().fromPixel(pixels, i)); 375 | } 376 | screen.setPalette(colors); 377 | } else { 378 | var colorConfig; 379 | if (palette.colors) { 380 | colorConfig = palette.colors 381 | } else { 382 | // TODO: to be deprecated. assuming legacy format, an array of css colors strings 383 | colorConfig = palette; 384 | } 385 | 386 | var colors = []; 387 | for (var i = 0; i < colorConfig.length; i++) { 388 | colors.push(new Color().fromString(colorConfig[i])); 389 | } 390 | 391 | screen.setPalette(colors); 392 | } 393 | } 394 | 395 | getPalette(); 396 | 397 | // set default tilesheet 398 | if (assets.tilesheet) pixelbox.tilesheet(assets.tilesheet); 399 | 400 | // setup all maps 401 | TileMap.loadBank(assets.maps); 402 | 403 | // setup nine slice 404 | if (assets.slicesConfig) { 405 | var initNineSlices = require('./NineSlice/init'); 406 | initNineSlices(assets.slicesConfig); 407 | } 408 | } 409 | 410 | if (__GAMEPAD__) { 411 | var gamepad = require('./gamepad'); 412 | var gamepadSettings = settings.gamepad || {}; 413 | if (gamepadSettings.analogToDpad) { 414 | gamepad.mapAnalogStickToDpad(gamepadSettings.deadZone); 415 | } 416 | } 417 | 418 | // `main.js` from `node_modules/pixelbox/boostrap` 419 | var main = require('../../src/main.js'); 420 | 421 | if (!main.update) { 422 | if (__USE_WEBGL__) { 423 | // still need an update loop to commit webGL 424 | function updateWebGL() { 425 | webGL.commit(); 426 | requestAnimationFrame(updateWebGL); 427 | } 428 | updateWebGL(); 429 | } 430 | return; 431 | } 432 | 433 | if (__USE_WEBGL__) webGL.batcher.flush(); 434 | 435 | function update() { 436 | window.frameId += 1; 437 | if (__USE_TINA__) TINA.update(); // update all tweeners 438 | if (__GAMEPAD__) gamepad.update(); 439 | main.update(); // call main update function 440 | if (__KEYBOARD__) resetControlTriggers(); // reset button pressed and release 441 | // TODO: pointer/cursor/mouse/touch reset 442 | if (__USE_WEBGL__) webGL.commit(); 443 | requestAnimationFrame(update); 444 | } 445 | 446 | if (__KEYBOARD__) resetControlTriggers(); 447 | update(); 448 | } 449 | 450 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 451 | // loader progress bar 452 | 453 | if (__CUSTOM_LOADER__) { 454 | var customLoader = require('../../src/' + __CUSTOM_LOADER__); 455 | function nopAsync(cb) { return cb(); } 456 | 457 | var customStart = customLoader.onStart || nopAsync; 458 | var customEnd = customLoader.onEnd || nopAsync; 459 | var customProgress = customLoader.onProgress || function (load, current, count, percent) {}; 460 | 461 | customStart(function () { 462 | assetLoader.preloadStaticAssets( 463 | projectData, 464 | function (error, assets) { 465 | customEnd(function () { 466 | onAssetsLoaded(error, assets); 467 | }, error); 468 | }, 469 | customProgress 470 | ); 471 | }); 472 | 473 | } else if (__USE_CORE__) { 474 | 475 | var CENTER = ~~(settings.screen.width / 2); 476 | var HALF_WIDTH = ~~(settings.screen.width / 4); 477 | var MIDDLE = ~~(settings.screen.height / 2); 478 | 479 | function showProgress(load, current, count, percent) { 480 | screen 481 | .rect(CENTER - HALF_WIDTH - 2, MIDDLE - 4, HALF_WIDTH * 2 + 4, 8) 482 | .rectf(CENTER - HALF_WIDTH, MIDDLE - 2, ~~(percent * HALF_WIDTH * 2), 4); 483 | if (__USE_WEBGL__) webGL.commit(); 484 | } 485 | 486 | var DEFAULT_LOADER_COLORS = ['#000', '#FFF']; 487 | var loaderColors = (settings.loader && settings.loader.colors) || settings.loaderColors || DEFAULT_LOADER_COLORS; 488 | if (!Array.isArray(loaderColors)) loaderColors = DEFAULT_LOADER_COLORS; 489 | var Color = require('./Color'); 490 | screen.setPalette([ 491 | new Color().fromString(loaderColors[0]), 492 | new Color().fromString(loaderColors[1]) 493 | ]); 494 | 495 | screen.paper(0).cls().paper(1).pen(1).rect(CENTER - HALF_WIDTH - 2, MIDDLE - 4, HALF_WIDTH * 2 + 4, 8); // loading bar border 496 | if (__USE_WEBGL__) webGL.commit(); 497 | assetLoader.preloadStaticAssets(projectData, onAssetsLoaded, showProgress); 498 | 499 | } else { 500 | assetLoader.preloadStaticAssets(projectData, onAssetsLoaded); 501 | } 502 | -------------------------------------------------------------------------------- /domUtils/createCanvas.js: -------------------------------------------------------------------------------- 1 | module.exports = function createCanvas(width, height) { 2 | var canvas = document.createElement('canvas'); 3 | canvas.width = width; 4 | canvas.height = height; 5 | return canvas; 6 | }; 7 | -------------------------------------------------------------------------------- /domUtils/index.js: -------------------------------------------------------------------------------- 1 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 2 | /** @module domUtils 3 | * @desc dom utilities 4 | * @author Cedric Stoquer 5 | */ 6 | 7 | var DOCUMENT_BODY = document.body; 8 | 9 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 10 | exports.createDom = function (type, className, parent) { 11 | parent = parent || DOCUMENT_BODY; 12 | var dom = document.createElement(type); 13 | parent.appendChild(dom); 14 | if (className) dom.className = className; 15 | return dom; 16 | }; 17 | 18 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 19 | exports.createDiv = function (className, parent) { 20 | return exports.createDom('div', className, parent); 21 | }; 22 | 23 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 24 | exports.removeDom = function (dom, parent) { 25 | parent = parent || DOCUMENT_BODY; 26 | parent.removeChild(dom); 27 | }; 28 | 29 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 30 | exports.makeClickable = function (dom, onClic) { 31 | dom.addEventListener('mousedown', function (e) { 32 | e.stopPropagation(); 33 | e.preventDefault(); 34 | onClic.call(dom, e); 35 | }); 36 | return dom; 37 | }; 38 | 39 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 40 | function startDrag(dom, e) { 41 | var d = document; 42 | 43 | rect = dom.getBoundingClientRect(); 44 | 45 | var startX = e.clientX - rect.left; 46 | var startY = e.clientY - rect.top; 47 | 48 | function dragMove(e) { 49 | e.preventDefault(); 50 | dom.style.left = (e.clientX - startX) + 'px'; 51 | dom.style.top = (e.clientY - startY) + 'px'; 52 | } 53 | 54 | function dragEnd(e) { 55 | e.preventDefault(); 56 | d.removeEventListener('mouseup', dragEnd); 57 | d.removeEventListener('mousemove', dragMove); 58 | } 59 | 60 | d.addEventListener('mousemove', dragMove, false); 61 | d.addEventListener('mouseup', dragEnd, false); 62 | } 63 | 64 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 65 | exports.makeDragable = function (handle, target) { 66 | target = target || handle; 67 | handle.addEventListener('mousedown', function (e) { 68 | e.stopPropagation(); 69 | e.preventDefault(); 70 | startDrag(target, e); 71 | }); 72 | return handle; 73 | }; 74 | 75 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 76 | exports.addCss = function (css) { 77 | var stylesheet = document.createElement('style'); 78 | stylesheet.type = 'text/css'; 79 | var textNode = document.createTextNode(css); 80 | stylesheet.appendChild(textNode); 81 | document.head.appendChild(stylesheet); 82 | return stylesheet; 83 | }; 84 | 85 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 86 | exports.addCssRef = function (href) { 87 | var stylesheet = document.createElement('link'); 88 | stylesheet.href = href; 89 | stylesheet.rel = 'stylesheet'; 90 | stylesheet.type = 'text/css'; 91 | document.head.appendChild(stylesheet); 92 | return stylesheet; 93 | }; -------------------------------------------------------------------------------- /gamepad/index.js: -------------------------------------------------------------------------------- 1 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 2 | /** @module gamepad 3 | * @author Cedric Stoquer 4 | */ 5 | var MAPPING_BUTTONS = [ 6 | 'A', 'B', 'X', 'Y', // buttons 7 | 'lb', 'rb', 'lt','rt', // bumpers and triggers 8 | 'back', 'start', // menu 9 | 'lp', 'rp', // axis push 10 | 'up', 'down', 'left', 'right' // dpad 11 | ]; 12 | 13 | var GAMEPAD_AVAILABLE = !!(navigator && navigator.getGamepads); 14 | var MAP_ANALOG_STICK_TO_DPAD = false; 15 | var AXIS_DEADZONE = 0.2; 16 | 17 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 18 | /** @class Gamepad 19 | * @classdesc gamepad state 20 | * 21 | * @attribute {boolean} available - is gamepaad available 22 | * @property {object} btn - button state 23 | * 24 | * @property {boolean} btn.A - A button 25 | * @property {boolean} btn.B - B button 26 | * @property {boolean} btn.X - X button 27 | * @property {boolean} btn.Y - Y button 28 | * @property {boolean} btn.lb - left bumper button 29 | * @property {boolean} btn.rb - right bumper button 30 | * @property {boolean} btn.lt - left trigger button 31 | * @property {boolean} btn.rt - right trigger button 32 | * @property {boolean} btn.back - back button 33 | * @property {boolean} btn.start - start button 34 | * @property {boolean} btn.lp - first axis push button 35 | * @property {boolean} btn.rp - second axis push button 36 | * @property {boolean} btn.up - directional pad up button 37 | * @property {boolean} btn.down - directional pad down button 38 | * @property {boolean} btn.left - directional pad left button 39 | * @property {boolean} btn.right - directional pad right button 40 | * 41 | * @property {object} btnp - button press. This object has the same structure as `btn` but the value are true only on button press 42 | * @property {object} btnr - button release. This object has the same structure as `btn` but the value are true only on button release 43 | * @property {number} x - x axe value (first stick horizontal) 44 | * @property {number} y - y axe value (first stick vertical) 45 | * @property {number} z - z axe value (second stick horizontal) 46 | * @property {number} w - w axe value (second stick vertical) 47 | * @property {number} lt - left trigger analog value 48 | * @property {number} rt - right trigger analog value 49 | */ 50 | function Gamepad() { 51 | this.available = false; 52 | 53 | // buttons 54 | this.btn = {}; 55 | this.btnp = {}; 56 | this.btnr = {}; 57 | 58 | // axes 59 | this.x = 0; 60 | this.y = 0; 61 | this.z = 0; 62 | this.w = 0; 63 | 64 | // trigger analog value 65 | this.lt = 0; 66 | this.rt = 0; 67 | 68 | this._setupButtons(); 69 | } 70 | 71 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 72 | Gamepad.prototype._setupButtons = function () { 73 | for (var i = 0; i < MAPPING_BUTTONS.length; i++) { 74 | var key = MAPPING_BUTTONS[i]; 75 | this.btn[key] = false; 76 | this.btnp[key] = false; 77 | this.btnr[key] = false; 78 | } 79 | }; 80 | 81 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 82 | // function gamepadHandler(event, connecting) { 83 | // console.log('Gamepad event', connecting) 84 | // var gamepad = event.gamepad; 85 | // if (connecting) { 86 | // gamepads[gamepad.index] = gamepad; 87 | // } else { 88 | // delete gamepads[gamepad.index]; 89 | // } 90 | // } 91 | 92 | // window.addEventListener("gamepadconnected", function (e) { gamepadHandler(e, true); }, false); 93 | // window.addEventListener("gamepaddisconnected", function (e) { gamepadHandler(e, false); }, false); 94 | 95 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 96 | var GAMEPADS = [ 97 | new Gamepad(), 98 | new Gamepad(), 99 | new Gamepad(), 100 | new Gamepad(), 101 | window // so we can define gamepad 5 as the keyboard 102 | ]; 103 | 104 | var ANY_GAMEPADS = new Gamepad(); 105 | // var LAST_FRAME_ID = -1; 106 | 107 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 108 | function getAllGamepads() { 109 | // check frame number and return cached value if same frame 110 | // if (window.frameId === LAST_FRAME_ID) return GAMEPADS; 111 | // LAST_FRAME_ID = window.frameId; 112 | 113 | var gamepads = navigator.getGamepads(); 114 | 115 | for (var gamepadIndex = 0; gamepadIndex < 4; gamepadIndex++) { 116 | var gamepad = gamepads[gamepadIndex]; 117 | var GAMEPAD = GAMEPADS[gamepadIndex]; 118 | if (!gamepad) { 119 | GAMEPAD.available = false; 120 | continue; 121 | } 122 | 123 | GAMEPAD.available = true; 124 | 125 | // previous values of dpad 126 | var pr, pl, pu, pd; 127 | if (MAP_ANALOG_STICK_TO_DPAD) { 128 | pr = GAMEPAD.btn.right; 129 | pl = GAMEPAD.btn.left; 130 | pd = GAMEPAD.btn.down; 131 | pu = GAMEPAD.btn.up; 132 | } 133 | 134 | // buttons 135 | for (var i = 0; i < MAPPING_BUTTONS.length; i++) { 136 | var key = MAPPING_BUTTONS[i]; 137 | var button = gamepad.buttons[i].pressed; 138 | GAMEPAD.btnp[key] = !GAMEPAD.btn[key] && button; 139 | GAMEPAD.btnr[key] = GAMEPAD.btn[key] && !button; 140 | GAMEPAD.btn[key] = button; 141 | } 142 | 143 | // axes 144 | GAMEPAD.x = gamepad.axes[0]; 145 | GAMEPAD.y = gamepad.axes[1]; 146 | GAMEPAD.z = gamepad.axes[2]; 147 | GAMEPAD.w = gamepad.axes[3]; 148 | 149 | // triggers 150 | GAMEPAD.lt = gamepad.buttons[6].value; 151 | GAMEPAD.rt = gamepad.buttons[7].value; 152 | 153 | // map left analog stick to dpad 154 | if (MAP_ANALOG_STICK_TO_DPAD) { 155 | // button 156 | GAMEPAD.btn.right = GAMEPAD.btn.right || GAMEPAD.x > AXIS_DEADZONE; 157 | GAMEPAD.btn.left = GAMEPAD.btn.left || GAMEPAD.x < -AXIS_DEADZONE; 158 | GAMEPAD.btn.down = GAMEPAD.btn.down || GAMEPAD.y > AXIS_DEADZONE; 159 | GAMEPAD.btn.up = GAMEPAD.btn.up || GAMEPAD.y < -AXIS_DEADZONE; 160 | 161 | // button press 162 | GAMEPAD.btnp.right = GAMEPAD.btn.right && !pr; 163 | GAMEPAD.btnp.left = GAMEPAD.btn.left && !pl; 164 | GAMEPAD.btnp.down = GAMEPAD.btn.down && !pd; 165 | GAMEPAD.btnp.up = GAMEPAD.btn.up && !pu; 166 | 167 | // button release 168 | GAMEPAD.btnr.right = !GAMEPAD.btn.right && pr; 169 | GAMEPAD.btnr.left = !GAMEPAD.btn.left && pl; 170 | GAMEPAD.btnr.down = !GAMEPAD.btn.down && pd; 171 | GAMEPAD.btnr.up = !GAMEPAD.btn.up && pu; 172 | } 173 | } 174 | 175 | return GAMEPADS; 176 | } 177 | 178 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 179 | function getGamepadsFallback() { 180 | return GAMEPADS; 181 | } 182 | 183 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 184 | /** Get all gamepads states 185 | * @return {Gamepad[]} gamepads states 186 | */ 187 | var getGamepads = GAMEPAD_AVAILABLE ? getAllGamepads : getGamepadsFallback; 188 | 189 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 190 | /** Get gamepad state 191 | * @param {number} inputId - id of the gamepad (number between 0 and 4) 192 | * fallback to keyboard access with -1 value (use with care) 193 | * @return {Gamepad} gamepad state 194 | */ 195 | // exports.getGamepad = function (inputId) { 196 | // if (inputId === -1) return window; // fallback to keyboard 197 | // // TODO optimize this (only get the relevant gamepad) 198 | // return getAllGamepads()[inputId]; 199 | // }; 200 | 201 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 202 | /** Merge states of keyboard and all gamepads and return a global gamepad state. 203 | * This is usefull for 1 player games when you don't want to bother with 204 | * gamepad configuration. 205 | * @todo Only works for gamepad buttons. Analog values (axes and triggers) are not handled 206 | * 207 | * @return {Gamepad} gamepads merged state 208 | */ 209 | function getAnyGamepad() { 210 | // buttons 211 | for (var i = 0; i < MAPPING_BUTTONS.length; i++) { 212 | var key = MAPPING_BUTTONS[i]; 213 | ANY_GAMEPADS.btnp[key] = btnp[key] || GAMEPADS[0].btnp[key] || GAMEPADS[1].btnp[key] || GAMEPADS[2].btnp[key] || GAMEPADS[3].btnp[key]; 214 | ANY_GAMEPADS.btnr[key] = btnr[key] || GAMEPADS[0].btnr[key] || GAMEPADS[1].btnr[key] || GAMEPADS[2].btnr[key] || GAMEPADS[3].btnr[key]; 215 | ANY_GAMEPADS.btn[key] = btn[key] || GAMEPADS[0].btn[key] || GAMEPADS[1].btn[key] || GAMEPADS[2].btn[key] || GAMEPADS[3].btn[key]; 216 | } 217 | 218 | // forbid up and left or up and down to be set at the same time 219 | if (ANY_GAMEPADS.btn.up && ANY_GAMEPADS.btn.down) ANY_GAMEPADS.btn.down = false; 220 | if (ANY_GAMEPADS.btn.right && ANY_GAMEPADS.btn.left) ANY_GAMEPADS.btn.left = false; 221 | 222 | // TODO: axes and triggers 223 | 224 | return ANY_GAMEPADS; 225 | }; 226 | 227 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 228 | /** Merge gamepad's left analog stick state (x, y axis) with Dpad. 229 | * 230 | * @param {number} deadzone - stick minimum value before considering it as pressed 231 | */ 232 | exports.mapAnalogStickToDpad = function (deadzone) { 233 | MAP_ANALOG_STICK_TO_DPAD = true; 234 | AXIS_DEADZONE = deadzone === undefined ? 0.3 : deadzone; 235 | }; 236 | 237 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 238 | exports.update = function () { 239 | window.gamepads = getGamepads(); 240 | window.gamepad = getAnyGamepad(); 241 | }; 242 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; -------------------------------------------------------------------------------- /inherits/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function inherits(Child, Parent) { 2 | Child.prototype = Object.create(Parent.prototype, { 3 | constructor: { 4 | value: Child, 5 | enumerable: false, 6 | writable: true, 7 | configurable: true 8 | } 9 | }); 10 | }; -------------------------------------------------------------------------------- /jsdoc.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "tags": { 4 | "allowUnknownTags": true, 5 | "dictionaries": ["jsdoc","closure"] 6 | }, 7 | "source": { 8 | "include": ["."], 9 | "includePattern": ".+\\.js(doc)?$", 10 | "excludePattern": "(^|\\/|\\\\)_" 11 | }, 12 | "opts": { 13 | "encoding": "utf8", 14 | "recurse": true 15 | }, 16 | "plugins": [ 17 | "plugins/markdown" 18 | ], 19 | "templates": { 20 | "default": { 21 | "outputSourceFiles": false, 22 | "layoutFile": "" 23 | }, 24 | "cleverLinks": false, 25 | "monospaceLinks": false 26 | } 27 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pixelbox", 3 | "author": "Cedric Stoquer", 4 | "description": "A framework to fast-prototype pixel-based games", 5 | "version": "2.1.1", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/cstoquer/pixelbox.git" 10 | }, 11 | "bugs": { 12 | "url": "http://github.com/cstoquer/pixelbox/issues" 13 | }, 14 | "keywords": [ 15 | "jam", "retro", "console", "arcade", 16 | "box", "sandbox", "fast", "prototyping", "prototype", 17 | "canvas", "pixel", "game" 18 | ], 19 | "scripts": { 20 | "doc": "jsdoc -c jsdoc.json -t ./doc/templates/ink-docstrap -d doc/documentation -R README.md" 21 | }, 22 | "dependencies": { 23 | }, 24 | "devDependencies": { 25 | "moment": "2.24.0", 26 | "sanitize-html": "1.13.0", 27 | "jsdoc": "3.4.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /pataTracker/readme.md: -------------------------------------------------------------------------------- 1 | # Pata-Tracker 2 | 3 | Tracker component can be activated from the `Components` tab of the `Project settings` panel. 4 | 5 | ## Usage 6 | 7 | Pata-tracker is exposed as a `patatracker` global variable. 8 | ```js 9 | patatracker.playSong(songNumber); 10 | patatracker.stop(); 11 | ``` 12 | 13 | Pata-Tracker automatically loads project album data (`assets/patatracker.json`). 14 | If you need to load a different album, you can do it with the following API: 15 | ```js 16 | patatracker.loadData(data); 17 | ``` 18 | -------------------------------------------------------------------------------- /pointerEvents/index.js: -------------------------------------------------------------------------------- 1 | var pixelbox = require('..'); 2 | var settings = pixelbox.settings; 3 | var PIXEL_WIDTH = ~~settings.screen.pixelSize.width; 4 | var PIXEL_HEIGHT = ~~settings.screen.pixelSize.height; 5 | var CANVAS = pixelbox.$screen.canvas; 6 | var SINGLETOUCH = true; 7 | var MOUSE_ID = 0; 8 | var TOUCH_OFFSET_X = 0; 9 | var TOUCH_OFFSET_Y = 0; 10 | 11 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 12 | // auto update pixel size when game is full screen 13 | if (settings.screen.fullscreen) { 14 | 15 | var SCREEN_WIDTH = settings.screen.width; 16 | var SCREEN_HEIGHT = settings.screen.height; 17 | var resizeTimeout = null; 18 | 19 | function resizePixels() { 20 | resizeTimeout = null; 21 | var rect = CANVAS.getBoundingClientRect(); 22 | PIXEL_WIDTH = rect.width / SCREEN_WIDTH; 23 | PIXEL_HEIGHT = rect.height / SCREEN_HEIGHT; 24 | } 25 | 26 | window.onresize = function () { 27 | if (resizeTimeout) window.clearTimeout(resizeTimeout); 28 | resizeTimeout = window.setTimeout(resizePixels, 300); 29 | }; 30 | 31 | // call resize once when the module is required 32 | resizePixels(); 33 | } 34 | 35 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 36 | var MOUSE = { x: 0, y: 0, pressed: false }; 37 | 38 | var onStart = null; 39 | var onMove = null; 40 | var onRelease = null; 41 | var onCancel = null; 42 | var onScroll = null; 43 | 44 | var touchIdentifier = 0; 45 | 46 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 47 | var canvasPosition = CANVAS.getBoundingClientRect(); 48 | TOUCH_OFFSET_X = canvasPosition.left; 49 | TOUCH_OFFSET_Y = canvasPosition.top; 50 | 51 | var touchEventSettings = settings.touchEvent || {}; 52 | 53 | if (touchEventSettings.disableContextMenu) { 54 | // disable right clic context menu 55 | CANVAS.oncontextmenu = function () { 56 | return false; 57 | }; 58 | } 59 | 60 | if (touchEventSettings.hideMousePointer) { 61 | CANVAS.style.cursor = 'none'; 62 | } 63 | 64 | if (touchEventSettings.multiTouch) { 65 | SINGLETOUCH = false; 66 | } 67 | 68 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 69 | CANVAS.addEventListener('mousedown', function (e) { 70 | e.preventDefault(); 71 | window.focus(); 72 | MOUSE.pressed = true; 73 | MOUSE.x = ~~(e.layerX / PIXEL_WIDTH); 74 | MOUSE.y = ~~(e.layerY / PIXEL_HEIGHT); 75 | onStart && onStart(MOUSE.x, MOUSE.y, MOUSE_ID, e); 76 | }); 77 | 78 | CANVAS.addEventListener('mousemove', function (e) { 79 | e.preventDefault(); 80 | MOUSE.x = ~~(e.layerX / PIXEL_WIDTH); 81 | MOUSE.y = ~~(e.layerY / PIXEL_HEIGHT); 82 | onMove && onMove(MOUSE.x, MOUSE.y, MOUSE_ID, e); 83 | }); 84 | 85 | window.addEventListener('mouseup', function (e) { 86 | e.preventDefault(); 87 | if (!MOUSE.pressed) return; 88 | MOUSE.pressed = false; 89 | MOUSE.x = ~~(e.layerX / PIXEL_WIDTH); 90 | MOUSE.y = ~~(e.layerY / PIXEL_HEIGHT); 91 | onRelease && onRelease(MOUSE.x, MOUSE.y, MOUSE_ID, e); 92 | }); 93 | 94 | document.addEventListener('mouseleave', function (e) { 95 | if (!MOUSE.pressed) return; 96 | MOUSE.pressed = false; 97 | if (onCancel) { 98 | onCancel(MOUSE_ID, e) 99 | } else { 100 | onRelease && onRelease(MOUSE.x, MOUSE.y, MOUSE_ID, e); 101 | } 102 | }); 103 | 104 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 105 | CANVAS.addEventListener('touchstart', function (e) { 106 | e.preventDefault(); 107 | if (!onStart) return; 108 | 109 | var touches = e.changedTouches; 110 | 111 | // single touch 112 | if (SINGLETOUCH) { 113 | var touch = touches[0]; 114 | touchIdentifier = touch.identifier; 115 | MOUSE.pressed = true; 116 | MOUSE.x = ~~((touch.clientX - TOUCH_OFFSET_X) / PIXEL_WIDTH); 117 | MOUSE.y = ~~((touch.clientY - TOUCH_OFFSET_Y) / PIXEL_HEIGHT); 118 | onStart(MOUSE.x, MOUSE.y, touch.identifier, touch); 119 | return; 120 | } 121 | 122 | // multi touch 123 | for (var t = 0; t < touches.length; t++) { 124 | var touch = touches[t]; 125 | var x = ~~((touch.clientX - TOUCH_OFFSET_X) / PIXEL_WIDTH); 126 | var y = ~~((touch.clientY - TOUCH_OFFSET_Y) / PIXEL_HEIGHT); 127 | onStart(x, y, touch.identifier, touch); 128 | } 129 | }); 130 | 131 | CANVAS.addEventListener('touchmove', function (e) { 132 | e.preventDefault(); 133 | if (!onMove) return; 134 | 135 | var touches = e.changedTouches; 136 | for (var t = 0; t < touches.length; t++) { 137 | var touch = touches[t]; 138 | var identifier = touch.identifier; 139 | 140 | // single touch 141 | if (SINGLETOUCH) { 142 | if (identifier !== touchIdentifier) continue; 143 | MOUSE.x = ~~((touch.clientX - TOUCH_OFFSET_X) / PIXEL_WIDTH); 144 | MOUSE.y = ~~((touch.clientY - TOUCH_OFFSET_Y) / PIXEL_HEIGHT); 145 | onMove(MOUSE.x, MOUSE.y, touch.identifier, touch); 146 | return; 147 | } 148 | 149 | // multitouch 150 | var x = ~~((touch.clientX - TOUCH_OFFSET_X) / PIXEL_WIDTH); 151 | var y = ~~((touch.clientY - TOUCH_OFFSET_Y) / PIXEL_HEIGHT); 152 | onMove(x, y, touch.identifier, touch); 153 | } 154 | }); 155 | 156 | CANVAS.addEventListener('touchend', function (e) { 157 | e.preventDefault(); 158 | if (!onRelease) return; 159 | 160 | var touches = e.changedTouches; 161 | for (var t = 0; t < touches.length; t++) { 162 | var touch = touches[t]; 163 | var identifier = touch.identifier; 164 | 165 | // single touch 166 | if (SINGLETOUCH) { 167 | if (identifier !== touchIdentifier) continue; 168 | MOUSE.pressed = false; 169 | MOUSE.x = ~~((touch.clientX - TOUCH_OFFSET_X) / PIXEL_WIDTH); 170 | MOUSE.y = ~~((touch.clientY - TOUCH_OFFSET_Y) / PIXEL_HEIGHT); 171 | onRelease(MOUSE.x, MOUSE.y, touch.identifier, touch); 172 | return; 173 | } 174 | 175 | // multitouch 176 | var x = ~~((touch.clientX - TOUCH_OFFSET_X) / PIXEL_WIDTH); 177 | var y = ~~((touch.clientY - TOUCH_OFFSET_Y) / PIXEL_HEIGHT); 178 | onRelease(x, y, touch.identifier, touch); 179 | } 180 | }); 181 | 182 | CANVAS.addEventListener('touchcancel', function (e) { 183 | e.preventDefault(); 184 | if (!onCancel) return; 185 | 186 | var touches = e.changedTouches; 187 | for (var t = 0; t < touches.length; t++) { 188 | var touch = touches[t]; 189 | var identifier = touch.identifier; 190 | 191 | // single touch 192 | if (SINGLETOUCH) { 193 | if (identifier !== touchIdentifier) continue; 194 | MOUSE.pressed = false; 195 | onCancel(touch.identifier, touch); 196 | return; 197 | } 198 | 199 | // multitouch 200 | onCancel(touch.identifier, touch); 201 | } 202 | }); 203 | 204 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 205 | CANVAS.addEventListener('wheel', function (e) { 206 | e.preventDefault(); 207 | var delta = e.wheelDeltaY; 208 | if (delta === 0) return; 209 | // NOTE: mouse scroll is usually 120 units. trackpad is much finer (3 units) 210 | onScroll && onScroll(delta, e); 211 | }); 212 | 213 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 214 | exports.onPress = function (cb) { onStart = cb } 215 | exports.onRelease = function (cb) { onRelease = cb } 216 | exports.onMove = function (cb) { onMove = cb } 217 | exports.onCancel = function (cb) { onCancel = cb } 218 | exports.onScroll = function (cb) { onScroll = cb } 219 | exports.pointer = MOUSE; 220 | -------------------------------------------------------------------------------- /spritesheet/Sprite.js: -------------------------------------------------------------------------------- 1 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 2 | function Sprite(img, path, x, y, width, height, pivotX, pivotY) { 3 | this.img = img; // direct reference to the image (or canvas) object 4 | this.path = path; 5 | this.x = x; 6 | this.y = y; 7 | this.width = width; 8 | this.height = height; 9 | this.pivotX = pivotX || 0; 10 | this.pivotY = pivotY || 0; 11 | } 12 | Sprite.prototype._isSprite = true; 13 | 14 | module.exports = Sprite; -------------------------------------------------------------------------------- /spritesheet/index.js: -------------------------------------------------------------------------------- 1 | var Sprite = require('./Sprite'); 2 | 3 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 4 | function getFilePath(path, id) { 5 | if (path) return path + '/' + id; 6 | return id; 7 | } 8 | 9 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 10 | function unpackSprite(root, path, data, spriteId, image, meta) { 11 | var sprite = new Sprite( 12 | image, 13 | path, 14 | data.x, 15 | data.y, 16 | data.w || meta.defaultWidth, 17 | data.h || meta.defaultHeight, 18 | data.p || meta.defaultPivotX, 19 | data.q || meta.defaultPivotY 20 | ); 21 | root[spriteId] = sprite; 22 | } 23 | 24 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 25 | function unpackFolder(root, path, data, folderId, image, meta) { 26 | if (!root[folderId]) root[folderId] = {}; 27 | var folder = root[folderId]; 28 | 29 | for (var id in data) { 30 | if (id === '_folder') continue; 31 | var subData = data[id]; 32 | var subPath = getFilePath(path, id); 33 | if (subData._folder) { 34 | unpackFolder(folder, subPath, subData, id, image, meta); 35 | } else { 36 | unpackSprite(folder, subPath, subData, id, image, meta); 37 | } 38 | } 39 | } 40 | 41 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 42 | function unpackGrid(root, path, data, image, meta) { 43 | var gridWidth = image.width; 44 | var gridHeight = image.height; 45 | 46 | var w = data.spriteWidth; 47 | var h = data.spriteHeight; 48 | 49 | var list = data.list; 50 | var index = 0; 51 | 52 | for (var y = 0; y < gridHeight; y += h) { 53 | for (var x = 0; x < gridWidth; x += w) { 54 | if (index >= list.length) return; 55 | var spriteId = list[index]; 56 | var sprite = new Sprite(image, getFilePath(path, spriteId), x, y, w, h); 57 | root[spriteId] = sprite; 58 | index++; 59 | } 60 | } 61 | } 62 | 63 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 64 | /** 65 | * @param {Object} root - subobject of the `assets` object in which the sprite should live (must exist) 66 | * @param {Object} data - spritesheet data 67 | * @param {Image} image - spritesheet's image 68 | */ 69 | function unpackSpritesheetV1(root, path, data, image, meta) { 70 | for (var id in data) { 71 | if (id === '_type' || id === '_meta') continue; 72 | var subData = data[id]; 73 | var subPath = getFilePath(path, id); 74 | if (subData._folder) { 75 | unpackFolder(root, subPath, subData, id, image, meta); 76 | } else { 77 | unpackSprite(root, subPath, subData, id, image, meta); 78 | } 79 | } 80 | } 81 | 82 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 83 | /** 84 | * @param {Object} root - an object where to unpack the spritesheet data 85 | * @param {Object} data - the raw JSON spritesheet data 86 | * @param {Image} image - spritesheet image 87 | */ 88 | function unpackSpritesheet(root, path, data, image) { 89 | var meta = data._meta; 90 | if (meta.grid) { 91 | unpackGrid(root, path, meta, image, meta); 92 | return; 93 | } 94 | unpackSpritesheetV1(root, path, data, image, meta); 95 | } 96 | 97 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 98 | function unpackSpritesheets() { 99 | // check there are spritesheets to unpack 100 | var dir = assets['.atlas']; 101 | if (!dir || !dir.data) return; 102 | 103 | var data = dir.data; 104 | 105 | var spritesheets = data.spritesheets; 106 | for (var id in spritesheets) { 107 | var spritesheet = spritesheets[id]; 108 | var imageId = spritesheet._meta.image; 109 | var image = dir[imageId]; 110 | 111 | var paths = id.split('/'); 112 | var root = window.assets; 113 | while (paths.length) { 114 | var p = paths.shift(); 115 | if (p === '') continue; // corner case if `assets` is set as spritesheet 116 | if (!root[p]) root[p] = {}; 117 | root = root[p]; 118 | } 119 | 120 | unpackSpritesheet(root, id, spritesheet, image); 121 | } 122 | } 123 | 124 | exports.unpackSpritesheets = unpackSpritesheets; 125 | -------------------------------------------------------------------------------- /webGL/IndexBuffer.js: -------------------------------------------------------------------------------- 1 | var context = require('./context'); 2 | var gl = context.gl; 3 | 4 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 5 | function IndexBuffer(size) { 6 | this.uShortView = new Uint16Array(size); 7 | this.binder = gl.createBuffer(); 8 | this.length = 0; 9 | this.vertexIndice = 0; 10 | } 11 | 12 | module.exports = IndexBuffer; 13 | 14 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 15 | IndexBuffer.prototype.uploadData = function () { 16 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.binder); 17 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.uShortView, gl.STATIC_DRAW); 18 | }; 19 | 20 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 21 | IndexBuffer.prototype.bindBuffer = function () { 22 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.binder); 23 | }; 24 | 25 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 26 | IndexBuffer.prototype.reset = function () { 27 | this.length = 0; 28 | this.vertexIndice = 0; 29 | }; 30 | -------------------------------------------------------------------------------- /webGL/VertexBuffer.js: -------------------------------------------------------------------------------- 1 | var context = require('./context'); 2 | var gl = context.gl; 3 | 4 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 5 | function VertexBuffer(size) { 6 | this.arrayBuffer = new ArrayBuffer(size); // size in bytes 7 | 8 | // Creation of views on the vertex buffer 9 | this.floatView = new Float32Array(this.arrayBuffer); // 4 bytes 10 | this.uLongView = new Uint32Array(this.arrayBuffer); // 4 bytes 11 | this.longView = new Int32Array(this.arrayBuffer); // 4 bytes 12 | this.uShortView = new Uint16Array(this.arrayBuffer); // 2 bytes 13 | this.shortView = new Int16Array(this.arrayBuffer); // 2 bytes 14 | this.byteView = new Uint8Array(this.arrayBuffer); // 1 bytes 15 | this.uByteView = new Int8Array(this.arrayBuffer); // 1 bytes 16 | 17 | this.binder = gl.createBuffer(); 18 | gl.bindBuffer(gl.ARRAY_BUFFER, this.binder); 19 | // gl.bufferData(gl.ARRAY_BUFFER, this.arrayBuffer, gl.DYNAMIC_DRAW); 20 | } 21 | 22 | module.exports = VertexBuffer; 23 | 24 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 25 | VertexBuffer.prototype.uploadData = function () { 26 | gl.bindBuffer(gl.ARRAY_BUFFER, this.binder); // work on the vertexBuffer 27 | gl.bufferData(gl.ARRAY_BUFFER, this.arrayBuffer, gl.DYNAMIC_DRAW); // upload vertex data 28 | }; 29 | -------------------------------------------------------------------------------- /webGL/batcher.js: -------------------------------------------------------------------------------- 1 | var context = require('./context'); 2 | var VertexBuffer = require('./VertexBuffer'); 3 | var IndexBuffer = require('./IndexBuffer'); 4 | var spriteRenderer = require('./renderers/sprite'); 5 | var lineRenderer = require('./renderers/line'); 6 | var colorRenderer = require('./renderers/colorQuad'); 7 | var colorSpriteRenderer = require('./renderers/colorSprite'); 8 | var gl = context.gl; 9 | 10 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 11 | var FLOAT32_SIZE = 4; // byte 12 | var INT16_SIZE = 2; // byte 13 | var VERTEX_SIZE = 4; // 2 positions + 2 uv 14 | 15 | var MAX_NB_BYTES_PER_BATCH = 65536; // WebGL specs 16 | var VERTEX_INDEX_LIMIT = ~~(MAX_NB_BYTES_PER_BATCH / INT16_SIZE); 17 | var FLUSH_TRIGGER = VERTEX_INDEX_LIMIT - 4 * VERTEX_SIZE; // size of a quad (4 vertex) 18 | 19 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 20 | function Batcher() { 21 | this._renderer = spriteRenderer; // current renderer in use 22 | this._program = null; // current program in use 23 | this._renderTarget = null; // current render target 24 | this._channel0 = null; // TODO: allow more channels (up to 4) 25 | this._batchIndex = 0; // in bytes 26 | 27 | // TODO: 28 | // this._blending = null; 29 | 30 | this.vertexBuffer = new VertexBuffer(MAX_NB_BYTES_PER_BATCH); 31 | this.indexBuffer = new IndexBuffer(MAX_NB_BYTES_PER_BATCH / 2); 32 | this.quadIndexBuffer = new IndexBuffer(VERTEX_INDEX_LIMIT * 6 / 4); 33 | 34 | this._initQuadIndexBuffer(); 35 | 36 | this.renderers = { 37 | sprite: spriteRenderer, 38 | line: lineRenderer, 39 | color: colorRenderer, 40 | colorSprite: colorSpriteRenderer, 41 | }; 42 | } 43 | 44 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 45 | Batcher.prototype._initQuadIndexBuffer = function () { 46 | var buff = this.quadIndexBuffer.uShortView; 47 | var i = 0; 48 | 49 | for (var j = 0; j < VERTEX_INDEX_LIMIT; j += 4) { 50 | buff[i] = j; 51 | buff[i + 1] = j + 1; 52 | buff[i + 2] = j + 2; 53 | buff[i + 3] = j; 54 | buff[i + 4] = j + 2; 55 | buff[i + 5] = j + 3; 56 | i += 6; 57 | } 58 | 59 | this.quadIndexBuffer.uploadData(); 60 | }; 61 | 62 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 63 | /** 64 | * Prepare the batch. If passed and current attributes are different current 65 | * batch is rendered and batch reset to empty. 66 | * 67 | * @param {string} renderer - renderer 68 | * @param {Image} channel - Image or Pixelbox's Texture (frameBuffer) 69 | * @param {texture} renderTarget - Pixelbox's Texture 70 | */ 71 | Batcher.prototype.prepare = function (renderer, channel, renderTarget) { 72 | // TODO: also add uniforms and flush if uniforms changes 73 | 74 | if (channel && channel._isSprite) channel = channel.img; 75 | // var program = renderer.program; // TODO: keep reference to the renderer 76 | 77 | var needFlush = false; 78 | 79 | // if (program !== this._program ) needFlush = true; 80 | if (channel !== this._channel0 ) needFlush = true; 81 | if (renderTarget !== this._renderTarget) needFlush = true; 82 | if (renderer !== this._renderer ) needFlush = true; 83 | 84 | if (needFlush) this.flush(); 85 | 86 | this._renderer.batcher = null; 87 | renderer.batcher = this; 88 | 89 | this._renderer = renderer; 90 | this._program = renderer.program; 91 | this._renderTarget = renderTarget; 92 | this._channel0 = channel; 93 | 94 | return this._renderer; 95 | }; 96 | 97 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 98 | Batcher.prototype.flush = function () { 99 | if (this._batchIndex === 0) return; 100 | 101 | // TODO: optimize: only do this if framebuffer changed 102 | var renderTarget = this._renderTarget; 103 | gl.bindFramebuffer(gl.FRAMEBUFFER, renderTarget.framebuffer); 104 | gl.viewport(0, 0, renderTarget.width, renderTarget.height); 105 | 106 | var program = context.useProgram(this._renderer.program); 107 | 108 | this._renderer.render.call(this, program); 109 | 110 | this._batchIndex = 0; 111 | }; 112 | 113 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 114 | module.exports = new Batcher(); 115 | -------------------------------------------------------------------------------- /webGL/context.js: -------------------------------------------------------------------------------- 1 | var createCanvas = require('../domUtils/createCanvas'); 2 | var pixelbox = require('..'); 3 | var settings = pixelbox.settings; 4 | 5 | var SCREEN_WIDTH = settings.screen.width; 6 | var SCREEN_HEIGHT = settings.screen.height; 7 | var PIXEL_WIDTH = settings.screen.pixelSize.width; 8 | var PIXEL_HEIGHT = settings.screen.pixelSize.height; 9 | 10 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 11 | // replace default Pixelbox 2d canvas for a WebGL canvas 12 | 13 | var canvas = createCanvas(SCREEN_WIDTH, SCREEN_HEIGHT); 14 | canvas.style.width = SCREEN_WIDTH * PIXEL_WIDTH + 'px'; 15 | canvas.style.height = SCREEN_HEIGHT * PIXEL_HEIGHT + 'px'; 16 | 17 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 18 | // init webGL 19 | 20 | var gl = canvas.getContext('webgl', { 21 | antialias: false, // that indicates whether or not to perform anti-aliasing. 22 | alpha: false, // that indicates if the canvas contains an alpha buffer. 23 | depth: false, // that indicates that the drawing buffer has a depth buffer of at least 16 bits. 24 | failIfMajorPerformanceCaveat: false, // that indicates if a context will be created if the system performance is low. 25 | powerPreference: 'default', // A hint to the user agent indicating what configuration of GPU is suitable for the WebGL context. Possible values are: 26 | premultipliedAlpha: false, // that indicates that the page compositor will assume the drawing buffer contains colors with pre-multiplied alpha. 27 | preserveDrawingBuffer: false, // If the value is true the buffers will not be cleared and will preserve their values until cleared or overwritten by the author. 28 | stencil: false, // that indicates that the drawing buffer has a stencil buffer of at least 8 bits. 29 | }); 30 | 31 | gl.viewport(0, 0, canvas.width, canvas.height); 32 | gl.enable(gl.BLEND); 33 | gl.disable(gl.DEPTH_TEST); // depth testing 34 | gl.depthMask(false); 35 | // gl.depthFunc(gl.LEQUAL); // Near things obscure far things 36 | 37 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 38 | var DUMMY_PROGRAM = null; // TODO 39 | 40 | var programs = {}; 41 | var _currentProgram = DUMMY_PROGRAM; 42 | 43 | 44 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 45 | function _createShader(str, type) { 46 | var shader = gl.createShader(type); 47 | gl.shaderSource(shader, str); 48 | gl.compileShader(shader); 49 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 50 | throw new Error(gl.getShaderInfoLog(shader)); 51 | } 52 | return shader; 53 | } 54 | 55 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 56 | function createProgram(id, vert, frag) { 57 | if (id && programs[id]) return programs[id]; 58 | 59 | var vertShader = _createShader(vert, gl.VERTEX_SHADER); 60 | var fragShader = _createShader(frag, gl.FRAGMENT_SHADER); 61 | 62 | var program = gl.createProgram(); 63 | gl.attachShader(program, vertShader); 64 | gl.attachShader(program, fragShader); 65 | 66 | gl.linkProgram(program); 67 | if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { 68 | gl.deleteProgram(program); 69 | throw new Error(gl.getProgramInfoLog(program)); 70 | } 71 | 72 | // register program in pool 73 | if (id) programs[id] = program; 74 | 75 | // TODO: Use the program and construct Uniforms and Attributes 76 | // (so we don't have to do gl.getAttribLocation each time) 77 | 78 | return program; 79 | } 80 | 81 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 82 | function useProgram(program) { 83 | // don't call if it same program as previous draw call 84 | if (_currentProgram === program) return program; 85 | 86 | if (_currentProgram !== null) { 87 | // switch attributes 88 | 89 | // Gets the number of attributes in the current and new programs 90 | var currentAttributes = gl.getProgramParameter(_currentProgram, gl.ACTIVE_ATTRIBUTES); 91 | var newAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); 92 | 93 | // Fortunately, in OpenGL, attribute index values are always assigned in the 94 | // range [0, ..., NUMBER_OF_VERTEX_ATTRIBUTES - 1], so we can use that to 95 | // enable or disable attributes 96 | if (newAttributes > currentAttributes) { 97 | // We need to enable the missing attributes 98 | for (var i = currentAttributes; i < newAttributes; i++) { 99 | gl.enableVertexAttribArray(i); 100 | } 101 | } else if (newAttributes < currentAttributes) { 102 | // We need to disable the extra attributes 103 | for (var i = newAttributes; i < currentAttributes; i++) { 104 | gl.disableVertexAttribArray(i); 105 | } 106 | } 107 | } 108 | 109 | // switch program 110 | _currentProgram = program; 111 | gl.useProgram(program); 112 | 113 | return program; 114 | } 115 | 116 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 117 | // function enableAttribute(program, variableName) { 118 | // var location = gl.getAttribLocation(program, variableName); 119 | // // gl.enableVertexAttribArray(location); // TODO: already done in `context.useProgram` 120 | // return location; 121 | // } 122 | 123 | 124 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 125 | var _textureMap = new WeakMap(); 126 | 127 | function getTextureFromImage(image) { 128 | // var texture = image._glTexture; 129 | var texture = _textureMap.get(image); 130 | if (texture) return texture; 131 | 132 | if (DEBUG) console.log('Create texture for image', image) 133 | 134 | texture = gl.createTexture(); 135 | 136 | // same as bindBuffer, once a texture is bind, all textures operations are done on this one. 137 | gl.bindTexture(gl.TEXTURE_2D, texture); 138 | 139 | gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); 140 | 141 | // upload the texture to the GPU 142 | gl.texImage2D( 143 | gl.TEXTURE_2D, // binding point (target) of the active texture 144 | 0, // level of detail. Level 0 is the base image level and level n is the nth mipmap reduction level 145 | gl.RGBA, // internalformat specifying the color components in the texture 146 | gl.RGBA, // format of the texel data. In WebGL 1, this must be the same as internalformat 147 | gl.UNSIGNED_BYTE, // data type of the texel data 148 | image // pixels 149 | ); 150 | 151 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); // NEAREST, LINEAR, LINEAR_MIPMAP_LINEAR, NEAREST_MIPMAP_NEAREST, NEAREST_MIPMAP_LINEAR, LINEAR_MIPMAP_NEAREST 152 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 153 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); // CLAMP_TO_EDGE, REPEAT, MIRRORED_REPEAT 154 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 155 | 156 | // image._glTexture = texture; 157 | _textureMap.set(image, texture); 158 | 159 | return texture; 160 | } 161 | 162 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 163 | function bindTexture(program, channel, uniformId, index) { 164 | var texture; 165 | 166 | if (channel._isGlTexture) { 167 | // texture is a frameBuffer 168 | texture = channel.ctx; 169 | } else { 170 | // We assume normal image 171 | texture = getTextureFromImage(channel); 172 | } 173 | 174 | gl.activeTexture(gl.TEXTURE0 + index); 175 | gl.bindTexture(gl.TEXTURE_2D, texture); 176 | gl.uniform1i(gl.getUniformLocation(program, uniformId), index); 177 | } 178 | 179 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 180 | exports.canvas = canvas; 181 | exports.gl = gl; 182 | exports.programs = programs; 183 | exports.createProgram = createProgram; 184 | exports.useProgram = useProgram; 185 | exports.bindTexture = bindTexture; 186 | exports.getTextureFromImage = getTextureFromImage; 187 | -------------------------------------------------------------------------------- /webGL/index.js: -------------------------------------------------------------------------------- 1 | var context = require('./context'); 2 | var batcher = require('./batcher'); 3 | var pixelbox = require('..'); 4 | var settings = pixelbox.settings; 5 | 6 | var gl = context.gl; 7 | var INT16_SIZE = 2; // byte 8 | var VERTEX_SIZE = 4; // 2 positions + 2 uv 9 | 10 | 11 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 12 | // create a vertex buffer with one quad that covers the full screen 13 | var W = ~~settings.screen.width; 14 | var H = ~~settings.screen.height; 15 | 16 | var arrayBuffer = new Int16Array([ 17 | 0, H, 0, H, // A ╖ A B 18 | W, H, W, H, // B ╟─ triangle 1 ┌──┐ 19 | 0, 0, 0, 0, // D ╜ │1/│ 20 | W, H, W, H, // B ╖ │/2│ 21 | W, 0, W, 0, // C ╟─ triangle 2 └──┘ 22 | 0, 0, 0, 0 // D ╜ D C 23 | ]); 24 | 25 | var vertexBuffer = gl.createBuffer(); 26 | gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); // work on the vertexBuffer 27 | gl.bufferData(gl.ARRAY_BUFFER, arrayBuffer, gl.STATIC_DRAW); // upload vertex data 28 | 29 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 30 | function enableAttribute(program, variableName) { 31 | var location = gl.getAttribLocation(program, variableName); 32 | gl.enableVertexAttribArray(location); 33 | return location; 34 | } 35 | 36 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 37 | function bindChannel(program, channel, uniformId, index) { 38 | gl.activeTexture(gl.TEXTURE0 + index); 39 | gl.bindTexture(gl.TEXTURE_2D, channel.ctx); 40 | gl.uniform1i(gl.getUniformLocation(program, uniformId), index); 41 | } 42 | 43 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 44 | /** 45 | * Flush all current rendering and draw $screen Texture on main canvas 46 | */ 47 | function commit() { 48 | batcher.flush(); 49 | 50 | var program = context.useProgram(context.programs.sprite); 51 | 52 | gl.bindFramebuffer(gl.FRAMEBUFFER, null); // render to the main canvas 53 | gl.viewport(0, 0, W, H); 54 | 55 | gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); 56 | gl.uniform2f(gl.getUniformLocation(program, 'iChannel0Resolution'), W, H); // size of the $screen Texture 57 | gl.uniform2f(gl.getUniformLocation(program, 'iResolution'), W, H); // TODO: should be size of the main canvas (if upscaling) 58 | gl.vertexAttribPointer(enableAttribute(program, 'a_coordinates'), 2, gl.SHORT, false, INT16_SIZE * VERTEX_SIZE, 0); 59 | gl.vertexAttribPointer(enableAttribute(program, 'a_uv'), 2, gl.SHORT, false, INT16_SIZE * VERTEX_SIZE, INT16_SIZE * 2); 60 | bindChannel(program, pixelbox.$screen, 'iChannel0', 0); 61 | 62 | gl.drawArrays(gl.TRIANGLES, 0, 6); 63 | } 64 | 65 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 66 | exports.shaders = context.programs; // collection of shaders 67 | exports.context = context; // webGL helpers 68 | exports.canvas = context.canvas; // main canvas dom element 69 | exports.gl = gl; // GL context 70 | exports.batcher = batcher; // sprite batcher (also polygons, lines, etc.) 71 | exports.commit = commit; // call at the end of each frame (done in CORE) 72 | -------------------------------------------------------------------------------- /webGL/inspector.js: -------------------------------------------------------------------------------- 1 | var gl = require('./context').gl; 2 | 3 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 4 | function addInspector() { 5 | var daCount = 0; 6 | var deCount = 0; 7 | 8 | var l = 0; 9 | 10 | gl._drawArrays = gl.drawArrays; 11 | gl._drawElements = gl.drawElements; 12 | 13 | gl.drawArray = function (mode, first, count) { 14 | daCount += 1; 15 | gl._drawArrays(mode, first, count); 16 | if (++l % 100 === 0) console.log('drawArray', daCount); 17 | } 18 | 19 | gl.drawElements = function (mode, count, type, offset) { 20 | deCount += 1; 21 | gl._drawElements(mode, count, type, offset); 22 | if (++l % 100 === 0) console.log('drawElements', deCount); 23 | } 24 | } 25 | 26 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 27 | exports.addInspector = addInspector; 28 | -------------------------------------------------------------------------------- /webGL/renderers/colorQuad.js: -------------------------------------------------------------------------------- 1 | var context = require('../context'); 2 | var gl = context.gl; 3 | 4 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 5 | var INT16_SIZE = 2; // byte 6 | var VERTEX_SIZE = 4; // 2 positions + 2 uv 7 | 8 | var MAX_NB_BYTES_PER_BATCH = 65536; // WebGL specs 9 | var VERTEX_INDEX_LIMIT = ~~(MAX_NB_BYTES_PER_BATCH / INT16_SIZE); 10 | var FLUSH_TRIGGER = VERTEX_INDEX_LIMIT - 4 * VERTEX_SIZE; // size of a quad (4 vertex) 11 | 12 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 13 | function ColoredQuadRenderer() { 14 | this.batcher = null; 15 | this.program = context.createProgram( 16 | 'color', 17 | require('../shaders/color.vert'), 18 | require('../shaders/color.frag') 19 | ); 20 | } 21 | 22 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 23 | /** 24 | * NOTA: all values must be integer. 25 | */ 26 | ColoredQuadRenderer.prototype.pushRect = function (x1, y1, w, h, r, g, b) { 27 | var x2 = x1 + w; 28 | var y2 = y1 + h; 29 | var rg = (g << 8) + r; // litte endian 30 | 31 | // A ┌──┐ B 32 | // │1/│ 33 | // │/2│ 34 | // D └──┘ C 35 | 36 | var V = this.batcher.vertexBuffer.shortView; 37 | var C = this.batcher.vertexBuffer.uShortView; 38 | var i = this.batcher._batchIndex; 39 | 40 | V[i++] = x1; V[i++] = y1; C[i++] = rg; C[i++] = b; // A 41 | V[i++] = x2; V[i++] = y1; C[i++] = rg; C[i++] = b; // B 42 | V[i++] = x2; V[i++] = y2; C[i++] = rg; C[i++] = b; // C 43 | V[i++] = x1; V[i++] = y2; C[i++] = rg; C[i++] = b; // D 44 | 45 | if (i < FLUSH_TRIGGER) { 46 | this.batcher._batchIndex = i; 47 | } else { 48 | this.batcher.flush(); 49 | } 50 | }; 51 | 52 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 53 | ColoredQuadRenderer.prototype.render = function (program) { 54 | // attributes: 55 | this.quadIndexBuffer.bindBuffer(); 56 | this.vertexBuffer.uploadData(); 57 | 58 | // ┌───────────────────────────────────┬───────────────────────────────────┐ 59 | // │ a_coordinates │ a_color │ 60 | // ╔════════╤════════╤════════╤════════╦════════╤════════╤════════╤════════╗ 61 | // ║ x │ y ║ r │ g │ b │ a ║ 62 | // ╚════════╧════════╧════════╧════════╩════════╧════════╧════════╧════════╝ 63 | // 0 1 2 3 4 5 6 7 8 64 | 65 | gl.vertexAttribPointer(gl.getAttribLocation(program, 'a_coordinates'), 2, gl.SHORT, false, 8, 0); 66 | gl.vertexAttribPointer(gl.getAttribLocation(program, 'a_color'), 4, gl.UNSIGNED_BYTE, true, 8, 4); 67 | 68 | // uniforms: 69 | gl.uniform2f(gl.getUniformLocation(program, 'iResolution'), this._renderTarget.width, this._renderTarget.height); 70 | 71 | // draw: 72 | var count = this._batchIndex / VERTEX_SIZE / 4 * 6; // per quad: 4 vertex -> 6 indexes 73 | gl.drawElements(gl.TRIANGLES, count, gl.UNSIGNED_SHORT, 0); 74 | }; 75 | 76 | module.exports = new ColoredQuadRenderer(); 77 | -------------------------------------------------------------------------------- /webGL/renderers/colorSprite.js: -------------------------------------------------------------------------------- 1 | var context = require('../context'); 2 | var gl = context.gl; 3 | 4 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 5 | var INT16_SIZE = 2; // byte 6 | var VERTEX_SIZE = 6; // 2 positions + 2 uv + 2 colors 7 | 8 | var MAX_NB_BYTES_PER_BATCH = 65536; // WebGL specs 9 | var VERTEX_INDEX_LIMIT = ~~(MAX_NB_BYTES_PER_BATCH / INT16_SIZE); 10 | var FLUSH_TRIGGER = VERTEX_INDEX_LIMIT - 4 * VERTEX_SIZE; // size of a quad (4 vertex) 11 | 12 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 13 | function ColorSpriteRenderer() { 14 | this.batcher = null; 15 | this.program = context.createProgram( 16 | 'colorSprite', 17 | require('../shaders/colorSprite.vert'), 18 | require('../shaders/colorSprite.frag') 19 | ); 20 | } 21 | 22 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 23 | /** 24 | * NOTA: all values must be integer. 25 | */ 26 | ColorSpriteRenderer.prototype.pushSprite = function (x1, y1, w, h, u1, v1, r, g, b) { 27 | var x2 = x1 + w; 28 | var y2 = y1 + h; 29 | var u2 = u1 + w; 30 | var v2 = v1 + h; 31 | var rg = (g << 8) + r; // litte endian 32 | 33 | // A ┌──┐ B 34 | // │1/│ 35 | // │/2│ 36 | // D └──┘ C 37 | 38 | var V = this.batcher.vertexBuffer.shortView; 39 | var C = this.batcher.vertexBuffer.uShortView; 40 | var i = this.batcher._batchIndex; 41 | 42 | V[i++] = x1; V[i++] = y1; V[i++] = u1; V[i++] = v1; C[i++] = rg; C[i++] = b; // A 43 | V[i++] = x2; V[i++] = y1; V[i++] = u2; V[i++] = v1; C[i++] = rg; C[i++] = b; // B 44 | V[i++] = x2; V[i++] = y2; V[i++] = u2; V[i++] = v2; C[i++] = rg; C[i++] = b; // C 45 | V[i++] = x1; V[i++] = y2; V[i++] = u1; V[i++] = v2; C[i++] = rg; C[i++] = b; // D 46 | 47 | if (i < FLUSH_TRIGGER) { 48 | this.batcher._batchIndex = i; 49 | } else { 50 | this.batcher.flush(); 51 | } 52 | }; 53 | 54 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 55 | ColorSpriteRenderer.prototype.render = function (program) { 56 | // attributes: 57 | this.quadIndexBuffer.bindBuffer(); 58 | this.vertexBuffer.uploadData(); 59 | 60 | // ┌───────────────────────────────────┬───────────────────────────────────┬───────────────────────────────────┐ 61 | // │ a_coordinates │ a_uv │ a_color │ 62 | // ╔════════╤════════╤════════╤════════╦════════╤════════╤════════╤════════╦════════╤════════╤════════╤════════╗ 63 | // ║ x │ y ║ u │ v ║ r │ g │ b │ 0 ║ 64 | // ╚════════╧════════╧════════╧════════╩════════╧════════╧════════╧════════╩════════╧════════╧════════╧════════╝ 65 | // 0 1 2 3 4 5 6 7 8 9 10 11 12 66 | 67 | gl.vertexAttribPointer(gl.getAttribLocation(program, 'a_coordinates'), 2, gl.SHORT, false, 12, 0); 68 | gl.vertexAttribPointer(gl.getAttribLocation(program, 'a_uv'), 2, gl.SHORT, false, 12, 4); 69 | gl.vertexAttribPointer(gl.getAttribLocation(program, 'a_color'), 4, gl.UNSIGNED_BYTE, true, 12, 8); 70 | 71 | // uniforms: 72 | gl.uniform2f(gl.getUniformLocation(program, 'iResolution'), this._renderTarget.width, this._renderTarget.height); 73 | gl.uniform2f(gl.getUniformLocation(program, 'iChannel0Resolution'), this._channel0.width, this._channel0.height); 74 | context.bindTexture(program, this._channel0, 'iChannel0', 0); // TODO: check if needed by the shader 75 | 76 | // draw: 77 | var count = this._batchIndex / VERTEX_SIZE / 4 * 6; // per quad: 4 vertex -> 6 indexes 78 | gl.drawElements(gl.TRIANGLES, count, gl.UNSIGNED_SHORT, 0); 79 | }; 80 | 81 | module.exports = new ColorSpriteRenderer(); 82 | -------------------------------------------------------------------------------- /webGL/renderers/line.js: -------------------------------------------------------------------------------- 1 | var context = require('../context'); 2 | var gl = context.gl; 3 | 4 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 5 | var INT16_SIZE = 2; // byte 6 | var VERTEX_SIZE = 4; // 2 positions + 2 uv 7 | 8 | var MAX_NB_BYTES_PER_BATCH = 65536; // WebGL specs 9 | var VERTEX_INDEX_LIMIT = ~~(MAX_NB_BYTES_PER_BATCH / INT16_SIZE); 10 | var FLUSH_TRIGGER = VERTEX_INDEX_LIMIT - 4 * VERTEX_SIZE; // size of a quad (4 vertex) 11 | 12 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 13 | function LineRenderer() { 14 | this.batcher = null; 15 | this.program = context.createProgram( 16 | 'line', 17 | require('../shaders/color.vert'), 18 | require('../shaders/color.frag') 19 | ); 20 | } 21 | 22 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 23 | LineRenderer.prototype.pushLines = function (lines, r, g, b) { 24 | // TODO 25 | }; 26 | 27 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 28 | LineRenderer.prototype.pushRect = function (x1, y1, w, h, r, g, b) { 29 | // HACK: coordinates needs to be tweaked a bit to get the rectangle correct 30 | var x2 = x1 + w; 31 | var y2 = y1 + h - 1; 32 | x1 += 1; 33 | 34 | var rg = (g << 8) + r; // litte endian 35 | 36 | // A ┌─────┐ B 37 | // │ │ 38 | // D └─────┘ C 39 | 40 | // NOTA: there is a bug in webGL where one pixel is missing at vertex D 41 | // https://www.gamedev.net/forums/topic/673088-missing-pixels-when-rendering-lines-with-webgl/ 42 | // http://factor-language.blogspot.com/2008/11/some-recent-ui-rendering-fixes.html 43 | 44 | // vertex 45 | var vertexBuffer = this.batcher.vertexBuffer; 46 | var V = vertexBuffer.shortView; // 2 bytes 47 | var C = vertexBuffer.uShortView; // 2 bytes 48 | var i = this.batcher._batchIndex; 49 | V[i++] = x1; V[i++] = y1; C[i++] = rg; C[i++] = b; // A 50 | V[i++] = x2; V[i++] = y1; C[i++] = rg; C[i++] = b; // B 51 | V[i++] = x2; V[i++] = y2; C[i++] = rg; C[i++] = b; // C 52 | V[i++] = x1; V[i++] = y2; C[i++] = rg; C[i++] = b; // D 53 | this.batcher._batchIndex = i; 54 | 55 | // indices 56 | var indexBuffer = this.batcher.indexBuffer; 57 | var I = indexBuffer.uShortView; 58 | var i = indexBuffer.length; 59 | var a = indexBuffer.vertexIndice; 60 | I[i++] = a + 0; I[i++] = a + 1; // segment 1 61 | I[i++] = a + 1; I[i++] = a + 2; // segment 2 62 | I[i++] = a + 2; I[i++] = a + 3; // segment 2 63 | I[i++] = a + 3; I[i++] = a + 0; // segment 2 64 | indexBuffer.length = i; 65 | indexBuffer.vertexIndice += 4; 66 | 67 | if (i >= FLUSH_TRIGGER) this.batcher.flush(); 68 | }; 69 | 70 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 71 | LineRenderer.prototype.render = function (program) { 72 | // attributes: 73 | this.indexBuffer.uploadData(); 74 | this.vertexBuffer.uploadData(); 75 | 76 | // ┌───────────────────────────────────┬───────────────────────────────────┐ 77 | // │ a_coordinates │ a_color │ 78 | // ╔════════╤════════╤════════╤════════╦════════╤════════╤════════╤════════╗ 79 | // ║ x │ y ║ r │ g │ b │ a ║ 80 | // ╚════════╧════════╧════════╧════════╩════════╧════════╧════════╧════════╝ 81 | // 0 1 2 3 4 5 6 7 8 82 | 83 | gl.vertexAttribPointer(gl.getAttribLocation(program, 'a_coordinates'), 2, gl.SHORT, false, 8, 0); 84 | gl.vertexAttribPointer(gl.getAttribLocation(program, 'a_color'), 4, gl.UNSIGNED_BYTE, true, 8, 4); 85 | 86 | // uniforms: 87 | gl.uniform2f(gl.getUniformLocation(program, 'iResolution'), this._renderTarget.width, this._renderTarget.height); 88 | 89 | // draw: 90 | gl.drawElements(gl.LINES, this.indexBuffer.length, gl.UNSIGNED_SHORT, 0); 91 | 92 | this.indexBuffer.reset(); 93 | }; 94 | 95 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 96 | module.exports = new LineRenderer(); 97 | -------------------------------------------------------------------------------- /webGL/renderers/sprite.js: -------------------------------------------------------------------------------- 1 | var context = require('../context'); 2 | var gl = context.gl; 3 | 4 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 5 | var INT16_SIZE = 2; // byte 6 | var VERTEX_SIZE = 4; // 2 positions + 2 uv 7 | 8 | var MAX_NB_BYTES_PER_BATCH = 65536; // WebGL specs 9 | var VERTEX_INDEX_LIMIT = ~~(MAX_NB_BYTES_PER_BATCH / INT16_SIZE); 10 | var FLUSH_TRIGGER = VERTEX_INDEX_LIMIT - 4 * VERTEX_SIZE; // size of a quad (4 vertex) 11 | 12 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 13 | function SpriteRenderer() { 14 | this.batcher = null; 15 | this.program = context.createProgram( 16 | 'sprite', 17 | require('../shaders/sprite.vert'), 18 | require('../shaders/sprite.frag') 19 | ); 20 | } 21 | 22 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 23 | SpriteRenderer.prototype.pushQuad = function ( 24 | xA, yA, uA, vA, 25 | xB, yB, uB, vB, 26 | xC, yC, uC, vC, 27 | xD, yD, uD, vD 28 | ) { 29 | var V = this.batcher.vertexBuffer.shortView; 30 | var i = this.batcher._batchIndex; 31 | 32 | // A ┌──┐ B 33 | // │1/│ 34 | // │/2│ 35 | // D └──┘ C 36 | 37 | V[i++] = xA; V[i++] = yA; V[i++] = uA; V[i++] = vA; // A 38 | V[i++] = xB; V[i++] = yB; V[i++] = uB; V[i++] = vB; // B 39 | V[i++] = xC; V[i++] = yC; V[i++] = uC; V[i++] = vC; // C 40 | V[i++] = xD; V[i++] = yD; V[i++] = uD; V[i++] = vD; // D 41 | 42 | if (i < FLUSH_TRIGGER) { 43 | this.batcher._batchIndex = i; 44 | } else { 45 | this.batcher.flush(); 46 | } 47 | }; 48 | 49 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 50 | /** 51 | * NOTA: all values must be integer. 52 | */ 53 | SpriteRenderer.prototype.pushSprite = function (x1, y1, w, h, u1, v1, flipH, flipV, flipR) { 54 | var x2, y2; 55 | var u2 = u1 + w; 56 | var v2 = v1 + h; 57 | 58 | // flip flags 59 | var t; 60 | 61 | if (flipR) { 62 | x2 = x1 + h; 63 | y2 = y1 + w; 64 | 65 | var uA = u1; var vA = v2; 66 | var uB = u1; var vB = v1; 67 | var uC = u2; var vC = v1; 68 | var uD = u2; var vD = v2; 69 | 70 | if (flipH) { 71 | t = vA; vA = vB; vB = t; 72 | t = vD; vD = vC; vC = t; 73 | } 74 | if (flipV) { 75 | t = uA; uA = uC; uC = t; 76 | t = uB; uB = uD; uD = t; 77 | } 78 | 79 | this.pushQuad( 80 | x1, y1, uA, vA, 81 | x2, y1, uB, vB, 82 | x2, y2, uC, vC, 83 | x1, y2, uD, vD 84 | ); 85 | 86 | } else { 87 | x2 = x1 + w; 88 | y2 = y1 + h; 89 | 90 | if (flipH) { t = u1; u1 = u2; u2 = t; } 91 | if (flipV) { t = v1; v1 = v2; v2 = t; } 92 | 93 | this.pushQuad( 94 | x1, y1, u1, v1, 95 | x2, y1, u2, v1, 96 | x2, y2, u2, v2, 97 | x1, y2, u1, v2 98 | ); 99 | } 100 | }; 101 | 102 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 103 | SpriteRenderer.prototype.pushStretchSprite = function (x1, y1, w, h, u1, v1, spriteW, spriteH) { 104 | var u2 = u1 + spriteW; 105 | var v2 = v1 + spriteH; 106 | var x2 = x1 + w; 107 | var y2 = y1 + h; 108 | 109 | this.pushQuad( 110 | x1, y1, u1, v1, 111 | x2, y1, u2, v1, 112 | x2, y2, u2, v2, 113 | x1, y2, u1, v2 114 | ); 115 | }; 116 | 117 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 118 | SpriteRenderer.prototype.render = function (program) { 119 | // attributes: 120 | this.quadIndexBuffer.bindBuffer(); 121 | this.vertexBuffer.uploadData(); 122 | 123 | // ┌───────────────────────────────────┬───────────────────────────────────┐ 124 | // │ a_coordinates │ a_uv │ 125 | // ╔════════╤════════╤════════╤════════╦════════╤════════╤════════╤════════╗ 126 | // ║ x │ y ║ u │ v ║ 127 | // ╚════════╧════════╧════════╧════════╩════════╧════════╧════════╧════════╝ 128 | // 0 1 2 3 4 5 6 7 8 129 | 130 | gl.vertexAttribPointer(gl.getAttribLocation(program, 'a_coordinates'), 2, gl.SHORT, false, 8, 0); 131 | gl.vertexAttribPointer(gl.getAttribLocation(program, 'a_uv'), 2, gl.SHORT, false, 8, 4); 132 | 133 | // uniforms: 134 | gl.uniform2f(gl.getUniformLocation(program, 'iResolution'), this._renderTarget.width, this._renderTarget.height); 135 | gl.uniform2f(gl.getUniformLocation(program, 'iChannel0Resolution'), this._channel0.width, this._channel0.height); 136 | context.bindTexture(program, this._channel0, 'iChannel0', 0); // TODO: check if needed by the shader 137 | 138 | // draw: 139 | var count = this._batchIndex / VERTEX_SIZE / 4 * 6; // per quad: 4 vertex -> 6 indexes 140 | gl.drawElements(gl.TRIANGLES, count, gl.UNSIGNED_SHORT, 0); 141 | }; 142 | 143 | module.exports = new SpriteRenderer(); 144 | -------------------------------------------------------------------------------- /webGL/shaders/color.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | varying vec4 iColor; 4 | 5 | void main() { 6 | vec4 color = iColor; 7 | color.a = 1.0; 8 | gl_FragColor = color; 9 | } 10 | -------------------------------------------------------------------------------- /webGL/shaders/color.vert: -------------------------------------------------------------------------------- 1 | uniform vec2 iResolution; 2 | attribute vec2 a_coordinates; 3 | attribute vec4 a_color; 4 | varying vec4 iColor; 5 | 6 | void main(void) { 7 | gl_Position = vec4( 8 | a_coordinates.x * 2.0 / iResolution.x - 1.0, 9 | a_coordinates.y * -2.0 / iResolution.y + 1.0, 10 | 0.0, 11 | 1.0 12 | ); 13 | iColor = a_color; 14 | } 15 | -------------------------------------------------------------------------------- /webGL/shaders/colorSprite.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | uniform sampler2D iChannel0; 4 | varying vec2 fragCoord; 5 | varying vec4 iColor; 6 | 7 | void main(void) { 8 | vec4 color = texture2D(iChannel0, fragCoord); 9 | if (color.a == 0.0) discard; 10 | gl_FragColor = iColor; 11 | } 12 | -------------------------------------------------------------------------------- /webGL/shaders/colorSprite.vert: -------------------------------------------------------------------------------- 1 | uniform vec2 iResolution; 2 | uniform vec2 iChannel0Resolution; 3 | attribute vec2 a_coordinates; 4 | attribute vec2 a_uv; 5 | attribute vec4 a_color; 6 | varying vec2 fragCoord; 7 | varying vec4 iColor; 8 | 9 | void main(void) { 10 | gl_Position = vec4( 11 | a_coordinates.x * 2.0 / iResolution.x - 1.0, 12 | a_coordinates.y * -2.0 / iResolution.y + 1.0, 13 | 0.0, 14 | 1.0 15 | ); 16 | fragCoord = vec2(a_uv.x / iChannel0Resolution.x, 1.0 - a_uv.y / iChannel0Resolution.y); 17 | iColor = vec4(a_color.rgb, 1.0); 18 | } 19 | -------------------------------------------------------------------------------- /webGL/shaders/sprite.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | uniform sampler2D iChannel0; 4 | varying vec2 fragCoord; 5 | 6 | void main(void) { 7 | vec4 color = texture2D(iChannel0, fragCoord); 8 | if (color.a == 0.0) discard; 9 | gl_FragColor = color; 10 | } 11 | -------------------------------------------------------------------------------- /webGL/shaders/sprite.vert: -------------------------------------------------------------------------------- 1 | uniform vec2 iResolution; 2 | uniform vec2 iChannel0Resolution; 3 | attribute vec2 a_coordinates; 4 | attribute vec2 a_uv; 5 | varying vec2 fragCoord; 6 | 7 | void main(void) { 8 | gl_Position = vec4( 9 | a_coordinates.x * 2.0 / iResolution.x - 1.0, 10 | a_coordinates.y * -2.0 / iResolution.y + 1.0, 11 | 0.0, 12 | 1.0 13 | ); 14 | fragCoord = vec2(a_uv.x / iChannel0Resolution.x, 1.0 - a_uv.y / iChannel0Resolution.y); 15 | } 16 | --------------------------------------------------------------------------------