Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign up| /* | |
| Copyright (c) 2011 Batiste Bieler and contributors, | |
| https://github.com/batiste/sprite.js | |
| Permission is hereby granted, free of charge, to any person obtaining | |
| a copy of this software and associated documentation files (the | |
| "Software"), to deal in the Software without restriction, including | |
| without limitation the rights to use, copy, modify, merge, publish, | |
| distribute, sublicense, and/or sell copies of the Software, and to | |
| permit persons to whom the Software is furnished to do so, subject to | |
| the following conditions: | |
| The above copyright notice and this permission notice shall be | |
| included in all copies or substantial portions of the Software. | |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |
| LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |
| OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
| WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| */ | |
| /*jslint bitwise: true, undef: true, white: true, maxerr: 50, indent: 4 */ | |
| /* Sprite.js v1.2.1 | |
| * | |
| * coding guideline | |
| * | |
| * CamelCase everywhere (I don't like it but it seems to the standard these days). | |
| * Tabs have to be 4 spaces (python style). | |
| * If you contribute don't forget to add your name in the AUTHORS file. | |
| */ | |
| (function (global) { | |
| "use strict"; | |
| var sjs, Sprite, Scene, Layer, Ticker, Ticker_, Cycle, Input, _Input, List, | |
| doc = global.document, | |
| // number of sprites | |
| nb_sprite = 0, | |
| // number of scenes | |
| nb_scene = 0, | |
| // number of cycle | |
| nb_cycle = 0, | |
| browser_specific_runned = false, | |
| // global z-index | |
| zindex = 1; | |
| //IE 8 fix help functions | |
| function _addEventListener(element, type,listener,useCapture){ | |
| if(element.addEventListener){ | |
| element.addEventListener(type, listener, useCapture); | |
| }else if(element.attachEvent){ | |
| element.attachEvent("on" + type, listener); | |
| } | |
| } | |
| function _removeEventListener(element, type,listener,useCapture){ | |
| if(element.removeEventListener){ | |
| element.removeEventListener(type, listener, useCapture); | |
| }else if (element.detachEvent){ | |
| element.detachEvent(type, listener); | |
| } | |
| } | |
| function _preventEvent(e){ | |
| if (e.preventDefault) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| }else{ | |
| e.returnValue = false; | |
| } | |
| } | |
| // math functions | |
| function mod(n, base) { | |
| // strictly positive modulo | |
| return ((n % base) + base) % base; | |
| } | |
| function hypo(x, y) { | |
| return Math.sqrt(x * x + y * y); | |
| } | |
| function normalVector(vx, vy, intensity) { | |
| var n = hypo(vx, vy); | |
| if (n === 0) { | |
| return {x: vx, y: vy}; | |
| } | |
| if (intensity) { | |
| return {x: ((vx / n) * intensity), y: ((vy / n) * intensity)}; | |
| } | |
| return {x: vx / n, y: vy / n}; | |
| } | |
| function lineSide(ax, ay, bx, by, cx, cy) { | |
| // return true if the point C is on the right of the line (A, B) | |
| var v = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax); | |
| if (v === 0) { | |
| return null; | |
| } | |
| return v > 0; | |
| } | |
| // browser specific feature detection | |
| function has(el, propList) { | |
| var prop = propList.shift(); | |
| while (prop) { | |
| if (typeof el[prop] !== 'undefined') { | |
| return prop; | |
| } | |
| prop = propList.shift(); | |
| } | |
| } | |
| function initBrowserSpecific() { | |
| sjs.tproperty = has(doc.body.style, [ | |
| 'transform', | |
| 'webkitTransform', | |
| 'MozTransform', | |
| 'OTransform', | |
| 'msTransform']); | |
| sjs.requestAnimationFrame = has(global, [ | |
| 'requestAnimationFrame', | |
| 'mozRequestAnimationFrame', | |
| 'webkitRequestAnimationFrame', | |
| 'oRequestAnimationFrame', | |
| 'msRequestAnimationFrame']); | |
| sjs.cancelAnimationFrame = has(global, [ | |
| 'cancelAnimationFrame', | |
| 'cancelRequestAnimationFrame', | |
| 'mozCancelAnimationFrame', | |
| 'mozCancelRequestAnimationFrame', | |
| 'webkitCancelAnimationFrame', | |
| 'webkitCancelRequestAnimationFrame', | |
| 'oCancelAnimationFrame', | |
| 'oCancelRequestAnimationFrame', | |
| 'msCancelAnimationFrame', | |
| 'msCancelRequestAnimationFrame']); | |
| sjs.createEventProperty = has(doc, ['createEvent', 'createEventObject']); | |
| browser_specific_runned = true; | |
| } | |
| function optionValue(options, name, default_value, type) { | |
| if (options && options[name] !== undefined) { | |
| if (type === 'int') { | |
| return options[name] | 0; | |
| } | |
| return options[name]; | |
| } | |
| return default_value; | |
| } | |
| function overlay(x, y, w, h) { | |
| var div = doc.createElement('div'), | |
| s = div.style; | |
| s.top = y + 'px'; | |
| s.left = x + 'px'; | |
| s.width = w + 'px'; | |
| s.height = h + 'px'; | |
| s.color = '#fff'; | |
| s.zIndex = 100; | |
| s.position = 'absolute'; | |
| s.backgroundColor = '#000'; | |
| s.opacity = 0.7; | |
| return div; | |
| } | |
| Scene = function Scene(options) { | |
| if (this.constructor !== Scene) { | |
| return new Scene(options); | |
| } | |
| if (!browser_specific_runned) { | |
| initBrowserSpecific(); | |
| } | |
| this.autoPause = optionValue(options, 'autoPause', true); | |
| // main function | |
| this.main = optionValue(options, 'main', function () {}); | |
| var div = doc.createElement('div'), parent; | |
| div.style.overflow = 'hidden'; | |
| // TODO: detect those features | |
| // image-rendering: -moz-crisp-edges; | |
| // ms-interpolation-mode: nearest-neighbor; | |
| div.style.imageRendering = '-webkit-optimize-contrast'; | |
| div.style.position = 'relative'; | |
| div.className = 'sjs'; | |
| div.id = 'sjs' + nb_scene; | |
| this.id = nb_scene; | |
| nb_scene = nb_scene + 1; | |
| parent = optionValue(options, 'parent', doc.body); | |
| parent.appendChild(div); | |
| this.w = optionValue(options, 'w', 480, 'int'); | |
| this.h = optionValue(options, 'h', 320, 'int'); | |
| this.dom = div; | |
| this.dom.style.width = this.w + 'px'; | |
| this.dom.style.height = this.h + 'px'; | |
| this.layers = {}; | |
| this.ticker = null; | |
| this.useCanvas = optionValue(options, "useCanvas", | |
| global.location.href.indexOf('canvas') !== -1); | |
| this.xscale = 1; | |
| this.yscale = 1; | |
| // needs to be done after this.useCanvas | |
| this.Layer("default"); | |
| sjs.scenes.push(this); | |
| return this; | |
| }; | |
| Scene.prototype.constructor = Scene; | |
| Scene.prototype.Sprite = function SceneSprite(src, layer) { | |
| // A shortcut for sjs.Sprite | |
| if(layer===undefined) | |
| sjs.error("When you create Sprite from the scene the layer should be specified or false."); | |
| return new Sprite(this, src, layer); | |
| }; | |
| Scene.prototype.Layer = function SceneLayer(name, options) { | |
| return new Layer(this, name, options); | |
| }; | |
| // just for convenience | |
| Scene.prototype.Cycle = function SceneCycle(triplets) { | |
| return new Cycle(triplets); | |
| }; | |
| Scene.prototype.Input = function SceneInput() { | |
| this.input = new Input(this); | |
| return this.input; | |
| }; | |
| Scene.prototype.scale = function SceneScale(x, y) { | |
| this.xscale = x; | |
| this.yscale = y; | |
| this.dom.style[sjs.tproperty+"Origin"] = "0 0"; | |
| this.dom.style[sjs.tproperty] = "scale(" + x + "," + y + ")"; | |
| }; | |
| Scene.prototype.toString = function () { | |
| return "Scene(" + String(this.id) + ")"; | |
| }; | |
| Scene.prototype.reset = function reset() { | |
| var l; | |
| if (this.ticker) { | |
| this.ticker.pause(); | |
| } | |
| for (l in this.layers) { | |
| if (this.layers.hasOwnProperty(l)) { | |
| this.layers[l].dom.parentNode.removeChild(this.layers[l].dom); | |
| delete this.layers[l]; | |
| } | |
| } | |
| // remove remaining children | |
| while (this.dom.childNodes.length >= 1) { | |
| this.dom.removeChild(this.dom.firstChild); | |
| } | |
| this.layers = {}; | |
| this.Layer("default"); | |
| }; | |
| Scene.prototype.Ticker = function Ticker(paint, options) { | |
| if (this.ticker) { | |
| this.ticker.pause(); | |
| this.ticker.paint = function () {}; | |
| } | |
| this.ticker = new Ticker_(this, paint, options); | |
| return this.ticker; | |
| }; | |
| Scene.prototype.loadImages = function loadImages(images, callback) { | |
| // function used to preload the sprite images | |
| if (!callback) { | |
| callback = this.main; | |
| } | |
| var toLoad = 0, total, div, img, src, error, scene, i; | |
| for (i = 0; i < images.length; i++) { | |
| if (!sjs.spriteCache[images[i]]) { | |
| toLoad += 1; | |
| sjs.spriteCache[images[i]] = {src: images[i], loaded: false, loading: false}; | |
| } | |
| } | |
| if (toLoad === 0) { | |
| return callback(); | |
| } | |
| total = toLoad; | |
| div = overlay(0, 0, this.w, this.h); | |
| div.style.textAlign = 'center'; | |
| div.style.paddingTop = (this.h / 2 - 16) + 'px'; | |
| div.innerHTML = 'Loading'; | |
| this.dom.appendChild(div); | |
| scene = this; | |
| error = false; | |
| var _loadImg = function(src) { | |
| sjs.spriteCache[src].loading = true; | |
| img = doc.createElement('img'); | |
| sjs.spriteCache[src].img = img; | |
| _addEventListener(img, 'load', function () { | |
| sjs.spriteCache[src].loaded = true; | |
| toLoad -= 1; | |
| if (error === false) { | |
| if (toLoad === 0) { | |
| scene.dom.removeChild(div); | |
| callback(); | |
| } else { | |
| div.innerHTML = 'Loading ' + ((total - toLoad) / total * 100 | 0) + '%'; | |
| } | |
| } | |
| }, false); | |
| _addEventListener(img, 'error', function () { | |
| error = true; | |
| div.innerHTML = 'Error loading image ' + src; | |
| }, false); | |
| img.src = src; | |
| } | |
| for (src in sjs.spriteCache) { | |
| if (sjs.spriteCache.hasOwnProperty(src)) { | |
| if (!sjs.spriteCache[src].loading) { | |
| _loadImg(src); | |
| } | |
| } | |
| } | |
| }; | |
| Sprite = function Sprite(scene, src, layer) { | |
| this.scene = scene; | |
| this._dirty = {}; | |
| this.changed = false; | |
| // positions | |
| this.y = 0; | |
| this.x = 0; | |
| this._x_before = 0; | |
| this._x_rounded = 0; | |
| this._y_before = 0; | |
| this._y_rounded = 0; | |
| //velocity | |
| this.xv = 0; | |
| this.yv = 0; | |
| this.rv = 0; | |
| // shape: rectangle, circle | |
| this.type = "rectangle"; | |
| // newton | |
| this.mass = 1; | |
| this.friction = 0.05; | |
| // forces | |
| this.xf = 0; | |
| this.yf = 0; | |
| // image | |
| this.src = null; | |
| this.img = null; | |
| this.imgNaturalWidth = null; | |
| this.imgNaturalHeight = null; | |
| // width and height of the sprite view port | |
| this.w = null; | |
| this.h = null; | |
| // offsets of the image within the viewport | |
| this.xoffset = 0; | |
| this.yoffset = 0; | |
| this.dom = null; | |
| this.cycle = null; | |
| this.xscale = 1; | |
| this.yscale = 1; | |
| this.angle = 0; | |
| this.xTransformOrigin = null; | |
| this.yTransformOrigin = null; | |
| this.backgroundRepeat = null; | |
| this.opacity = 1; | |
| this.color = false; | |
| this.id = ++nb_sprite; | |
| // necessary to get set | |
| this.layer = null; | |
| var value, target, setF, first_char, d, p, properties; | |
| // if it doesn't seems to kouak like a Layer object | |
| if (layer) { | |
| // this is a layer object | |
| if (layer.sprites) { | |
| this.layer = layer; | |
| } else { | |
| // we can receive things like this | |
| // {x: 10, y: 10, w: 10, h: 50, size: [20, 30], layer: var} | |
| properties = layer; | |
| // this is the messy magic options initializer code | |
| for (p in properties) { | |
| if (properties.hasOwnProperty(p)) { | |
| value = properties[p]; | |
| target = this[p]; | |
| if (typeof target === "function") { | |
| this[p].apply(this, value); | |
| } | |
| else if (target !== undefined) { | |
| // this is necessary to set cache value properly | |
| first_char = p.charAt(0); | |
| if ((first_char === 'x' || first_char === 'y') && p.length > 1) { | |
| setF = 'set' + first_char.toUpperCase() + p.charAt(1).toUpperCase() + p.slice(2); | |
| } else { | |
| setF = 'set' + first_char.toUpperCase() + p.slice(1); | |
| } | |
| if (this[setF]) { | |
| this[setF].apply(this, [value]); | |
| } else { | |
| // necessary for layer option | |
| this[p] = value; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // can be set by the properties | |
| if (this.layer === undefined || layer === undefined) { | |
| this.layer = scene.layers['default']; | |
| } | |
| if (this.layer && !this.layer.useCanvas) { | |
| d = doc.createElement('div'); | |
| d.style.position = 'absolute'; | |
| this.dom = d; | |
| this.layer.dom.appendChild(d); | |
| } | |
| if (src) { | |
| this.loadImg(src); | |
| } | |
| return this; | |
| }; | |
| Sprite.prototype.constructor = Sprite; | |
| /* boilerplate setter functions */ | |
| Sprite.prototype.setX = function setX(value) { | |
| this.x = value; | |
| // this secessary for the physic | |
| this._x_rounded = value | 0; | |
| this.changed = true; | |
| return this; | |
| }; | |
| Sprite.prototype.setY = function setY(value) { | |
| this.y = value; | |
| this._y_rounded = value | 0; | |
| this.changed = true; | |
| return this; | |
| }; | |
| Sprite.prototype.setW = function setW(value) { | |
| this.w = value; | |
| this._dirty.w = true; | |
| this.changed = true; | |
| return this; | |
| }; | |
| Sprite.prototype.setH = function setH(value) { | |
| this.h = value; | |
| this._dirty.h = true; | |
| this.changed = true; | |
| return this; | |
| }; | |
| Sprite.prototype.setXOffset = function setXoffset(value) { | |
| this.xoffset = value; | |
| this._dirty.xoffset = true; | |
| this.changed = true; | |
| return this; | |
| }; | |
| Sprite.prototype.setYOffset = function setYoffset(value) { | |
| this.yoffset = value; | |
| this._dirty.yoffset = true; | |
| this.changed = true; | |
| return this; | |
| }; | |
| Sprite.prototype.setAngle = function setAngle(value) { | |
| this.angle = value; | |
| this._dirty.angle = true; | |
| this.changed = true; | |
| return this; | |
| }; | |
| Sprite.prototype.setColor = function setColor(value) { | |
| this.color = value; | |
| this._dirty.color = true; | |
| this.changed = true; | |
| return this; | |
| }; | |
| Sprite.prototype.setOpacity = function setOpacity(value) { | |
| this.opacity = value; | |
| this._dirty.opacity = true; | |
| this.changed = true; | |
| return this; | |
| }; | |
| Sprite.prototype.setXScale = function setXscale(value) { | |
| this.xscale = value; | |
| this._dirty.xscale = true; | |
| this.changed = true; | |
| return this; | |
| }; | |
| Sprite.prototype.setYScale = function setYscale(value) { | |
| this.yscale = value; | |
| this._dirty.yscale = true; | |
| this.changed = true; | |
| return this; | |
| }; | |
| Sprite.prototype.transformOrigin = function transformOrigin(x, y) { | |
| this.xTransformOrigin = x; | |
| this.yTransformOrigin = y; | |
| this._dirty.transform = true; | |
| this.changed = true; | |
| return this; | |
| }; | |
| Sprite.prototype.setBackgroundRepeat = function setBackgroundRepeat(value) { | |
| this._dirty.backgroundRepeat = true; | |
| this.backgroundRepeat = value; | |
| return this; | |
| }; | |
| // End of boilerplate setters, start of helpers | |
| Sprite.prototype.rotate = function (v) { | |
| this.setAngle(this.angle + v); | |
| return this; | |
| }; | |
| Sprite.prototype.orient = function orient(x, y) { | |
| var a = Math.atan2(y, x); | |
| this.setAngle(a); | |
| }; | |
| Sprite.prototype.scale = function (x, y) { | |
| if (this.xscale !== x) { | |
| this.setXScale(x); | |
| } | |
| if (y === undefined) { | |
| y = x; | |
| } | |
| if (this.yscale !== y) { | |
| this.setYScale(y); | |
| } | |
| return this; | |
| }; | |
| Sprite.prototype.move = function (x, y) { | |
| this.setX(this.x + x); | |
| this.setY(this.y + y); | |
| return this; | |
| }; | |
| Sprite.prototype.position = function (x, y) { | |
| this.setX(x); | |
| this.setY(y); | |
| return this; | |
| }; | |
| Sprite.prototype.offset = function (x, y) { | |
| this.setXOffset(x); | |
| this.setYOffset(y); | |
| return this; | |
| }; | |
| Sprite.prototype.size = function (w, h) { | |
| this.setW(w); | |
| this.setH(h); | |
| return this; | |
| }; | |
| Sprite.prototype.toFront = function(){ | |
| this.layer.lastZIndex++; | |
| return this.setZIndex(this.layer.lastZIndex); | |
| }; | |
| Sprite.prototype.toBack = function(){ | |
| this.layer.lastZIndex++; | |
| return this.setZIndex(-this.layer.lastZIndex); | |
| }; | |
| Sprite.prototype.setZIndex = function(z){ | |
| if(this.dom && this.layer) { | |
| this._dirty.zindex = true; | |
| this.changed = true; | |
| this.zindex = z; | |
| } | |
| return this; | |
| }; | |
| // Physic | |
| Sprite.prototype.setForce = function setForce(xf, yf) { | |
| this.xf = xf; | |
| this.yf = yf; | |
| }; | |
| Sprite.prototype.addForce = function addForce(xf, yf) { | |
| this.xf += xf; | |
| this.yf += yf; | |
| }; | |
| Sprite.prototype.applyForce = function applyForce(ticks) { | |
| if (ticks === undefined) { | |
| ticks = 1; | |
| } | |
| // Integrate newton's laws of motion F = ma => a = F / m | |
| this.xv -= this.friction * this.xv * this.mass * ticks; | |
| this.xv += (this.xf / this.mass) * ticks; | |
| this.yv -= this.friction * this.yv * this.mass * ticks; | |
| this.yv += (this.yf / this.mass) * ticks; | |
| }; | |
| Sprite.prototype.velocity = function () { | |
| return hypo(this.xv, this.yv); | |
| }; | |
| Sprite.prototype.setVelocity = function (xv, yv) { | |
| this.xv = xv; | |
| this.yv = yv; | |
| }; | |
| Sprite.prototype.addVelocity = function (xv, yv) { | |
| this.xv += xv; | |
| this.yv += yv; | |
| }; | |
| Sprite.prototype.applyVelocity = function (ticks) { | |
| if (ticks === undefined) | |
| ticks = 1; | |
| if (this.xv !== 0) | |
| this.setX(this.x + this.xv * ticks); | |
| if (this.yv !== 0) | |
| this.setY(this.y + this.yv * ticks); | |
| if (this.rv !== 0) | |
| this.setAngle(this.angle + this.rv * ticks); | |
| return this; | |
| }; | |
| Sprite.prototype.reverseVelocity = function (ticks) { | |
| if (ticks === undefined) | |
| ticks = 1; | |
| if (this.xv !== 0) | |
| this.setX(this.x - this.xv * ticks); | |
| if (this.yv !== 0) | |
| this.setY(this.y - this.yv * ticks); | |
| if (this.rv !== 0) | |
| this.setAngle(this.angle - this.rv * ticks); | |
| return this; | |
| }; | |
| Sprite.prototype.applyXVelocity = function (ticks) { | |
| if (ticks === undefined) | |
| ticks = 1; | |
| if (this.xv !== 0) | |
| this.setX(this.x + this.xv * ticks); | |
| }; | |
| Sprite.prototype.reverseXVelocity = function (ticks) { | |
| if (ticks === undefined) | |
| ticks = 1; | |
| if (this.xv !== 0) | |
| this.setX(this.x-this.xv * ticks); | |
| }; | |
| Sprite.prototype.applyYVelocity = function (ticks) { | |
| if (ticks === undefined) | |
| ticks = 1; | |
| if (this.yv !== 0) | |
| this.setY(this.y+this.yv * ticks); | |
| }; | |
| Sprite.prototype.reverseYVelocity = function (ticks) { | |
| if (ticks === undefined) | |
| ticks = 1; | |
| if (this.yv !== 0) | |
| this.setY(this.y-this.yv * ticks); | |
| }; | |
| Sprite.prototype.rotateVelocity = function (a) { | |
| var x = this.xv * Math.cos(a) - this.yv * Math.sin(a); | |
| this.yv = this.xv * Math.sin(a) + this.yv * Math.cos(a); | |
| this.xv = x; | |
| }; | |
| Sprite.prototype.orientVelocity = function (x, y) { | |
| var intensity = hypo(this.xv, this.yv), v; | |
| v = normalVector(x, y, intensity); | |
| this.xv = v.x; | |
| this.yv = v.y; | |
| }; | |
| Sprite.prototype.remove = function remove() { | |
| if (this.cycle) | |
| this.cycle.removeSprite(this); | |
| if (this.layer && !this.layer.useCanvas) { | |
| this.layer.dom.removeChild(this.dom); | |
| this.dom = null; | |
| } | |
| if (this.texture) | |
| this.texture.remove(); | |
| this.texture = null; | |
| //delete this.layer.sprites[this.layerIndex]; | |
| this.layer = null; | |
| this.img = null; | |
| }; | |
| // Update methods | |
| Sprite.prototype.webGLUpdate = function webGLUpdate () { | |
| if (!this.texture) { | |
| this.texture = new webgl.Texture(this); | |
| } | |
| this.texture.render(this.x, this.y); | |
| return this; | |
| }; | |
| Sprite.prototype.update = function updateDomProperties () { | |
| if(this.layer.scene.disableUpdate) | |
| return this; | |
| // This is the CPU heavy function. | |
| if (this.layer.useWebGL) { | |
| return this.webGLUpdate(); | |
| } | |
| if (this.layer.useCanvas) { | |
| return this.canvasUpdate(); | |
| } | |
| var style = this.dom.style, trans; | |
| // using Math.round to round integers before changing seems to improve a bit performances | |
| if (this._x_before !== this._x_rounded) | |
| style.left=(this.x | 0) + 'px'; | |
| if (this._y_before !== this._y_rounded) | |
| style.top=(this.y | 0) + 'px'; | |
| // cache rounded positions, it's used to avoid unecessary update | |
| this._x_before = this._x_rounded; | |
| this._y_before = this._y_rounded; | |
| if (!this.changed) | |
| return this; | |
| if (this._dirty.w) | |
| style.width=(this.w | 0) +'px'; | |
| if (this._dirty.h) | |
| style.height=(this.h | 0) + 'px'; | |
| // translate and translate3d doesn't seems to offer any speedup | |
| // in my tests. | |
| if (this._dirty.xoffset || this._dirty.yoffset) | |
| style.backgroundPosition=-(this.xoffset | 0) + 'px ' + -(this.yoffset | 0) + 'px'; | |
| if (this._dirty.opacity) | |
| if ('opacity' in document.body.style) { | |
| style.opacity = this.opacity; | |
| } else { | |
| style.filter = "alpha(opacity="+ this.opacity*100 + ")"; | |
| } | |
| if (this._dirty.color) | |
| style.backgroundColor = this.color; | |
| if (this._dirty.zindex) | |
| style.zIndex = this.zindex; | |
| if(this._dirty.transform) { | |
| style[sjs.tproperty + 'Origin'] = this.xTransformOrigin + " " + this.yTransformOrigin; | |
| } | |
| if(this._dirty.backgroundRepeat) { | |
| style.backgroundRepeat = this.backgroundRepeat; | |
| } | |
| // those transformation have pretty bad perfs implication on Opera, | |
| // don't update those values if nothing changed | |
| if (this._dirty.xscale || this._dirty.yscale || this._dirty.angle) { | |
| trans = ""; | |
| if (this.angle !== 0) | |
| trans += 'rotate(' + this.angle + 'rad) '; | |
| if (this.xscale !== 1 || this.yscale !== 1) { | |
| trans += ' scale(' + this.xscale + ', ' + this.yscale + ')'; | |
| } | |
| style[sjs.tproperty] = trans; | |
| } | |
| // reset | |
| this.changed = false; | |
| this._dirty = {}; | |
| return this; | |
| }; | |
| Sprite.prototype.canvasUpdate = function canvasUpdate(layer) { | |
| var ctx, transx, transy, repeat_w, repeat_y; | |
| if (layer) | |
| ctx = layer.ctx; | |
| else | |
| ctx = this.layer.ctx; | |
| var fast_track = ( | |
| this.angle == 0 | |
| && this.opacity == 1 | |
| && this.imgNaturalWidth == this.w | |
| && this.imgNaturalHeight == this.h | |
| && this.xTransformOrigin === null | |
| ) | |
| if(fast_track) { | |
| ctx.drawImage(this.img, this.xoffset, this.yoffset, this.w, this.h, | |
| this._x_rounded, this._y_rounded, this.w, this.h); | |
| return this; | |
| } | |
| ctx.save(); | |
| if (this.xTransformOrigin === null) { | |
| // 50% 505 in CSS | |
| transx = this.w >> 1; | |
| transy = this.h >> 1; | |
| } else { | |
| transx = this.xTransformOrigin; | |
| transy = this.yTransformOrigin; | |
| } | |
| // rounding the coordinates yield a big performance improvement | |
| ctx.translate(this._x_rounded + transx, this._y_rounded + transy); | |
| ctx.rotate(this.angle); | |
| if (this.xscale !== 1 || this.yscale !== 1) | |
| ctx.scale(this.xscale, this.yscale); | |
| ctx.globalAlpha = this.opacity; | |
| ctx.translate(-transx, -transy); | |
| // handle background colors. | |
| if (this.color) { | |
| ctx.fillStyle = this.color; | |
| ctx.fillRect(0, 0, this.w, this.h); | |
| } | |
| // handle repeating images, a way to implement repeating background in canvas | |
| if (this.imgLoaded && this.img) { | |
| if (this.imgNaturalWidth < this.w || this.imgNaturalHeight < this.h) { | |
| repeat_w = Math.floor(this.w / this.imgNaturalWidth); | |
| while(repeat_w > 0) { | |
| repeat_w = repeat_w-1; | |
| repeat_y = Math.floor(this.h / this.imgNaturalHeight); | |
| while(repeat_y > 0) { | |
| repeat_y = repeat_y-1; | |
| ctx.drawImage(this.img, this.xoffset, this.yoffset, | |
| this.imgNaturalWidth, | |
| this.imgNaturalHeight, | |
| repeat_w * this.imgNaturalWidth, | |
| repeat_y * this.imgNaturalHeight, | |
| this.imgNaturalWidth, | |
| this.imgNaturalHeight); | |
| } | |
| } | |
| } else { | |
| // image with normal size or with | |
| ctx.drawImage(this.img, this.xoffset, this.yoffset, this.w, this.h, 0, 0, this.w, this.h); | |
| } | |
| } | |
| ctx.restore(); | |
| return this; | |
| }; | |
| // Other methods | |
| Sprite.prototype.toString = function () { | |
| return "Sprite(" + String(this.id) + ")"; | |
| }; | |
| Sprite.prototype.onload = function (callback) { | |
| if (this.imgLoaded && this._callback) { | |
| this._callback = callback; | |
| } | |
| }; | |
| Sprite.prototype.loadImg = function (src, resetSize) { | |
| // the image exact source value will change according to the | |
| // hostname, this is useful to retain the original source value here. | |
| var _loaded, there = this, img; | |
| this.src = src; | |
| // check if the image is already in the cache | |
| if (!sjs.spriteCache[src]) { | |
| // if not we create the image in the cache | |
| this.img = doc.createElement('img'); | |
| sjs.spriteCache[src] = {src: src, img: this.img, loaded: false, loading: true}; | |
| _loaded = false; | |
| } else { | |
| // if it's already there, we set img object and check if it's loaded | |
| this.img = sjs.spriteCache[src].img; | |
| _loaded = sjs.spriteCache[src].loaded; | |
| } | |
| // actions to perform when the image is loaded | |
| function imageReady(e) { | |
| img = there.img; | |
| sjs.spriteCache[src].loaded = true; | |
| there.imgLoaded = true; | |
| if (there.layer && !there.layer.useCanvas) | |
| there.dom.style.backgroundImage = 'url(' + src + ')'; | |
| there.imgNaturalWidth = img.width; | |
| there.imgNaturalHeight = img.height; | |
| if (there.w === null || resetSize) | |
| there.setW(img.width); | |
| if (there.h === null || resetSize) | |
| there.setH(img.height); | |
| there.onload(); | |
| } | |
| if (_loaded) | |
| imageReady(); | |
| else { | |
| _addEventListener(this.img, 'load', imageReady, false); | |
| this.img.src = src; | |
| } | |
| return this; | |
| }; | |
| Sprite.prototype.distance = function distance(x, y) { | |
| // Return the distance between this sprite and the point (x, y) or a Sprite | |
| if (typeof x === "number") { | |
| return Math.sqrt(Math.pow(this.x + this.w / 2 - x, 2) + | |
| Math.pow(this.y + this.h / 2 - y, 2)); | |
| } else { | |
| return Math.sqrt(Math.pow(this.x + (this.w / 2) - (x.x + (x.w / 2)), 2) + | |
| Math.pow(this.y + (this.h / 2) - (x.y + (x.h / 2)), 2)); | |
| } | |
| }; | |
| Sprite.prototype.center = function center() { | |
| return {x: this.x + this.w / 2, y: this.y + this.h / 2}; | |
| }; | |
| // Fx | |
| Sprite.prototype.explode2 = function explode(v, horizontal, layer) { | |
| if (!layer) | |
| layer = this.layer; | |
| var props = {layer:layer, color:this.color}; | |
| if (v === undefined) { | |
| if (horizontal) | |
| v = this.h >> 1; | |
| else | |
| v = this.w >> 1; | |
| } | |
| var s1 = layer.scene.Sprite(this.src, props); | |
| var s2 = layer.scene.Sprite(this.src, props); | |
| if (horizontal) { | |
| s1.size(this.w, v); | |
| s1.position(this.x, this.y); | |
| s2.size(this.w, this.h - v); | |
| s2.position(this.x, this.y + v); | |
| s2.setYOffset(v); | |
| } else { | |
| s1.size(v, this.h); | |
| s1.position(this.x, this.y); | |
| s2.size(this.w - v, this.h); | |
| s2.position(this.x + v, this.y); | |
| s2.setXOffset(v); | |
| } | |
| return [s1, s2]; | |
| }; | |
| Sprite.prototype.explode4 = function explode(x, y, layer) { | |
| if (x === undefined) | |
| x = this.w >> 1; | |
| if (y === undefined) | |
| y = this.h >> 1; | |
| if (!layer) | |
| layer = this.layer; | |
| var props = {layer:layer, color:this.color}; | |
| // top left sprite, going counterclockwise | |
| var s1 = layer.scene.Sprite(this.src, props), | |
| s2 = layer.scene.Sprite(this.src, props), | |
| s3 = layer.scene.Sprite(this.src, props), | |
| s4 = layer.scene.Sprite(this.src, props); | |
| s1.size(x, y); | |
| s1.position(this.x, this.y); | |
| s2.size(this.w - x, y); | |
| s2.position(this.x + x, this.y); | |
| s2.offset(x, 0); | |
| s3.size(this.w - x, this.h - y); | |
| s3.position(this.x + x, this.y + y); | |
| s3.offset(x, y); | |
| s4.size(x, this.h - y); | |
| s4.position(this.x, this.y + y); | |
| s4.offset(0, y); | |
| return [s1, s2, s3, s4]; | |
| }; | |
| Cycle = function Cycle(triplets) { | |
| if (this.constructor !== Cycle) { | |
| return new Cycle(triplets); | |
| } | |
| var i, triplet; | |
| // Cycle for the Sprite image. | |
| // A cycle is a list of triplet (x offset, y offset, game tick duration) | |
| this.triplets = triplets; | |
| // total duration of the animation in ticks | |
| this.cycleDuration = 0; | |
| // this array knows on which ticks in the animation | |
| // an image change is needed | |
| this.changingTicks = triplets.map(function(triplet) { | |
| this.cycleDuration += triplet[2]; | |
| return this.cycleDuration; | |
| }, this); | |
| this.changingTicks.unshift(0); | |
| this.currentTripletIndex = undefined; | |
| // suppose to be private | |
| this.sprites = []; | |
| // if set to false, the animation will stop automaticaly after one run | |
| this.repeat = true; | |
| this.tick = 0; | |
| this.done = false; | |
| this.id = ++nb_cycle; | |
| }; | |
| Cycle.prototype.addSprite = function addSprite(sprite) { | |
| this.sprites.push(sprite); | |
| sprite.cycle = this; | |
| return this; | |
| }; | |
| Cycle.prototype.toString = function () { | |
| return "Cycle(" + String(this.id) + ")"; | |
| }; | |
| Cycle.prototype.update = function update() { | |
| var sprites = this.sprites, i, sp; | |
| for (i = 0; sp = sprites[i]; i++) { | |
| sp.update(); | |
| } | |
| return this; | |
| }; | |
| Cycle.prototype.addSprites = function addSprites(sprites) { | |
| this.sprites = this.sprites.concat(sprites); | |
| var j, sp; | |
| for (j = 0; sp = sprites[j]; j++) { | |
| sp.cycle = this; | |
| } | |
| return this; | |
| }; | |
| Cycle.prototype.removeSprite = function removeSprite(sprite) { | |
| var j, sp; | |
| for (j = 0; sp = this.sprites[j]; j++) { | |
| if (sprite == sp) { | |
| sp.cycle = null; | |
| this.sprites.splice(j, 1); | |
| } | |
| } | |
| return this; | |
| }; | |
| Cycle.prototype.next = function (ticks, update) { | |
| if (this.tick > this.cycleDuration) { | |
| if (this.repeat) | |
| this.tick = 0; | |
| else { | |
| this.done = true; | |
| return this; | |
| } | |
| } | |
| // search the current triplet index | |
| var currentTripletIndex, i, j, sprite, next; | |
| for (i = 0; i < this.changingTicks.length; i++) { | |
| next = this.changingTicks[i+1]; | |
| if (this.tick >= this.changingTicks[i]) { | |
| if(next === undefined) { | |
| currentTripletIndex = 0; | |
| this.tick = 0; | |
| break; | |
| } else if(this.tick < next) { | |
| currentTripletIndex = i; | |
| break; | |
| } | |
| } | |
| } | |
| if (currentTripletIndex !== undefined && currentTripletIndex !== this.currentTripletIndex) { | |
| this.sprites.map(function(sprite) { | |
| sprite.setXOffset(this.triplets[currentTripletIndex][0]); | |
| sprite.setYOffset(this.triplets[currentTripletIndex][1]); | |
| if (update) { | |
| sprite.update(); | |
| } | |
| }.bind(this)); | |
| this.currentTripletIndex = currentTripletIndex; | |
| } | |
| ticks = ticks || 1; // default tick: 1 | |
| this.tick = this.tick + ticks; | |
| return this; | |
| }; | |
| Cycle.prototype.reset = function resetCycle(update) { | |
| var j, sprite; | |
| this.tick = 0; | |
| this.done = false; | |
| for (j = 0; sprite = this.sprites[j]; j++) { | |
| sprite.setXOffset(this.triplets[0][0]); | |
| sprite.setYOffset(this.triplets[0][1]); | |
| if (update) | |
| sprite.update(); | |
| } | |
| return this; | |
| }; | |
| Cycle.prototype.go = function gotoCycle(n) { | |
| var j, sprite; | |
| for (j = 0; sprite = this.sprites[j]; j++) { | |
| sprite.setXOffset(this.triplets[n][0]); | |
| sprite.setYOffset(this.triplets[n][1]); | |
| } | |
| return this; | |
| }; | |
| Ticker_ = function Ticker_(scene, paint, options) { | |
| // backward compatiblity from the 1.1.1 API | |
| if (typeof paint == "number") { | |
| var buf = paint; | |
| paint = options; | |
| options = {tickDuration: buf} | |
| } | |
| this.scene = scene; | |
| if (this.constructor !== Ticker_){ | |
| return new Ticker_(tickDuration, paint); | |
| } | |
| this.tickDuration = optionValue(options, 'tickDuration', 16); | |
| this.expectedFps = 1000 / this.tickDuration; | |
| this.useAnimationFrame = optionValue(options, 'useAnimationFrame', false); | |
| if (!sjs.requestAnimationFrame || !sjs.cancelAnimationFrame) { | |
| this.useAnimationFrame = false; | |
| } | |
| this.paint = paint; | |
| var that = this; | |
| this.bindedRun = function bindedRun(t) {that.run(t);} | |
| this.start = new Date().getTime(); | |
| this.now = this.start; | |
| this.ticksElapsed = 0; | |
| // absolute number of ticks that have been played ever | |
| this.currentTick = 0; | |
| this.ticksSinceLastStart = 0; | |
| this.droppedFrames = 0; | |
| // will divide the framerate by 2 if true | |
| this.lowFrameRate = false; | |
| }; | |
| Ticker_.prototype.next = function (timestamp) { | |
| var now = new Date().getTime(); | |
| this.diff = now - this.now; | |
| this.now = now; | |
| // number of ticks that have elapsed since the last start | |
| this.lastTicksElapsed = Math.round(this.diff / this.tickDuration); | |
| this.droppedFrames += Math.max(0, this.lastTicksElapsed - 1); | |
| this.ticksSinceLastStart += this.lastTicksElapsed; | |
| // add the diff to the current ticks | |
| this.currentTick += this.lastTicksElapsed; | |
| return this.lastTicksElapsed; | |
| }; | |
| Ticker_.prototype.run = function(timestamp) { | |
| if (this.paused) { | |
| return; | |
| } | |
| /*if(this.lowFrameRate || this.load > 20 && this.fps < (this.expectedFps / 2)) { | |
| this.lowFrameRate = true; | |
| if(this.skippedFrames == 1) { | |
| this.skippedFrames = 0; | |
| this.skipPaint = true; | |
| this.scene.disableUpdate = true; | |
| } else { | |
| this.skippedFrames = 1; | |
| this.skipPaint = false; | |
| this.scene.disableUpdate = false; | |
| } | |
| } else { | |
| this.skipPaint = false; | |
| }*/ | |
| var t = this; | |
| var ticksElapsed = this.next(timestamp); | |
| // no update needed, this happen on the first run | |
| /*if (ticksElapsed == 0) { | |
| // this is not a cheap operation | |
| setTimeout(this.bindedRun, this.tickDuration); | |
| return; | |
| }*/ | |
| //if(!this.skipPaint) { | |
| for (var name in this.scene.layers) { | |
| var layer = this.scene.layers[name]; | |
| if (layer.useCanvas && layer.autoClear) { | |
| layer.clear(); | |
| } | |
| } | |
| //} | |
| this.paint(this); | |
| // reset the keyboard change | |
| if (this.scene.input) { | |
| this.scene.input.next(); | |
| } | |
| this.timeToPaint = (new Date().getTime()) - this.now; | |
| // spread the load value on 2 frames so the value is more stable | |
| this.load = ((this.timeToPaint / this.tickDuration * 100) + this.load) >> 1; | |
| this.fps = Math.round(1000 / (this.now - (this.lastPaintAt || 0))); | |
| this.lastPaintAt = this.now; | |
| if (this.useAnimationFrame) { | |
| this.tickDuration = 16; | |
| this.animationId = global[sjs.requestAnimationFrame](this.bindedRun); | |
| } else { | |
| var _nextPaint = Math.max(this.tickDuration - this.timeToPaint, 6); | |
| this.timeout = setTimeout(this.bindedRun, _nextPaint); | |
| } | |
| }; | |
| Ticker_.prototype.pause = function () { | |
| if (this.useAnimationFrame) { | |
| global[sjs.cancelAnimationFrame](this.animationId); | |
| } else { | |
| global.clearTimeout(this.timeout); | |
| } | |
| this.paused = true; | |
| }; | |
| Ticker_.prototype.resume = function () { | |
| this.start = new Date().getTime(); | |
| this.ticksElapsed = 0; | |
| this.ticksSinceLastStart = 0; | |
| this.paused = false; | |
| this.run(); | |
| }; | |
| var inputSingleton = false; | |
| function Input(scene) { | |
| if (!inputSingleton) | |
| inputSingleton = new _Input(scene); | |
| return inputSingleton; | |
| }; | |
| _Input = function _Input(scene) { | |
| if (scene) | |
| this.dom = scene.dom; | |
| else | |
| this.dom = doc.body; | |
| var that = this; | |
| // record the current keyboard state | |
| this.keyboard = {}; | |
| this.mouse = {position: {}, click: undefined}; | |
| // record the keyboard changes since the last call | |
| this.keyboardChange = {}; | |
| this.mousedown = false; | |
| that.mousepressed = false; | |
| this.mousereleased = false; | |
| this.keydown = false; | |
| this.touchMoveSensibility = 20; | |
| this.enableCustomEvents = false; | |
| this.touchable = 'ontouchstart' in global; | |
| this.next = function () { | |
| if(this.disableFor) | |
| this.disableFor = that.disableFor - 1; | |
| this.keyboardChange = {}; | |
| this.mousepressed = false; | |
| this.mouse.click = undefined; | |
| this.mousereleased = false; | |
| } | |
| this.disableFor = 0; | |
| this.disable = function (ticks) { | |
| that.disableFor = ticks; | |
| } | |
| this.keyPressed = function (name) { | |
| return that.keyboardChange[name] !== undefined && that.keyboardChange[name]; | |
| }; | |
| this.keyReleased = function (name) { | |
| return that.keyboardChange[name] !== undefined && !that.keyboardChange[name]; | |
| }; | |
| this.arrows = function arrows() { | |
| /* Return true if any arrow key is pressed */ | |
| return this.keyboard.right || this.keyboard.left || this.keyboard.up || this.keyboard.down; | |
| }; | |
| function fireEvent(name, value) { | |
| if(!that.enableCustomEvents) | |
| return; | |
| if(doc.createEvent) { | |
| var evObj = doc.createEvent('Events'); | |
| evObj.initEvent('sjs' + name, true, true); | |
| evObj.value = value; | |
| that.dom.dispatchEvent(evObj); | |
| } else if(doc.createEventObject) { | |
| var evObj = doc.createEventObject(); | |
| evObj.value = value; | |
| that.dom.fireEvent('onsjs' + name, evObj); | |
| } | |
| } | |
| function updateKeyChange(name, val) { | |
| fireEvent(name, val); | |
| if(name == "space" || name == "enter") { | |
| updateKeyChange("action", val) | |
| } | |
| if (that.keyboard[name] !== val) { | |
| that.keyboard[name] = val; | |
| that.keyboardChange[name] = val; | |
| } | |
| } | |
| // this is handling WASD, and arrows keys | |
| function updateKeyboard(e, val) { | |
| if (e.keyCode == 40 || e.keyCode == 83) { | |
| updateKeyChange('down', val); | |
| } | |
| if (e.keyCode == 38 || e.keyCode == 87) { | |
| updateKeyChange('up', val); | |
| } | |
| if (e.keyCode == 39 || e.keyCode == 68) { | |
| updateKeyChange('right', val); | |
| } | |
| if (e.keyCode == 37 || e.keyCode == 65) { | |
| updateKeyChange('left', val); | |
| } | |
| if (e.keyCode == 32) { | |
| updateKeyChange('space', val); | |
| } | |
| if (e.keyCode == 17) { | |
| updateKeyChange('ctrl', val); | |
| } | |
| if (e.keyCode == 13) { | |
| updateKeyChange('enter', val); | |
| } | |
| if (e.keyCode == 27) { | |
| updateKeyChange('esc', val); | |
| } | |
| // 0..9, a-z | |
| if (e.keyCode >= 48 && e.keyCode <= 90) { | |
| var keyStr = String.fromCharCode(e.keyCode); | |
| updateKeyChange(keyStr.toLowerCase(), val); | |
| } | |
| } | |
| var listen = function (name, fct) { | |
| _addEventListener(global, name, fct, false); | |
| } | |
| // Mouse like events | |
| function clickEvent(event) { | |
| that.mouse.click = { | |
| x: (event.clientX - that.dom.offsetLeft) / scene.xscale, | |
| y: (event.clientY - that.dom.offsetTop) / scene.yscale | |
| }; | |
| } | |
| function mouseDownEvent(event) { | |
| that.mousedown = true; | |
| that.mouse.down = true; | |
| that.mousepressed = true; | |
| // prevent unwanted browser drag and drop behavior | |
| _preventEvent(event); | |
| } | |
| function mouseUpEvent(event) { | |
| that.mousedown = false; | |
| that.mouse.down = false; | |
| that.mousereleased = true; | |
| that.mouse.click = { | |
| x: (event.clientX - that.dom.offsetLeft) / scene.xscale, | |
| y: (event.clientY - that.dom.offsetTop) / scene.yscale | |
| }; | |
| } | |
| function mouseMoveEvent(event) { | |
| that.mouse.position = { | |
| x: (event.clientX - that.dom.offsetLeft) / scene.xscale, | |
| y: (event.clientY - that.dom.offsetTop) / scene.yscale | |
| }; | |
| } | |
| function reduceTapEvent(e) { | |
| // To simplify I ignore multiple touch events and only return the first event | |
| if (e.touches && e.touches.length) { e = e.touches[0]; } | |
| else if (e.changedTouches && e.changedTouches.length) { e = e.changedTouches[0];} | |
| return e | |
| } | |
| if (this.touchable) { | |
| listen("touchstart", function (e) { | |
| e = reduceTapEvent(e); | |
| updateKeyChange('space', true); // tap imitates space | |
| // simulate the click | |
| clickEvent(e); | |
| //store initial coordinates to find out swipe directions later | |
| that.touchStart = {"x" : e.clientX, "y": e.clientY}; | |
| }); | |
| listen("touchend", function (e) { | |
| mouseUpEvent(e); | |
| that.keyboard = {} | |
| that.touchStart = null; | |
| }); | |
| listen("touchmove", function (e) { | |
| _preventEvent(e); // avoid scrolling the page | |
| e = reduceTapEvent(e); | |
| updateKeyChange('space', false); // if it moves: it is not a tap | |
| mouseMoveEvent(e); | |
| if (that.touchStart) { | |
| var deltaX = e.clientX - that.touchStart.x; | |
| var deltaY = e.clientY - that.touchStart.y; | |
| if (deltaY < -that.touchMoveSensibility) { | |
| updateKeyChange('up', true); | |
| updateKeyChange('down', false); | |
| } else if (deltaY > that.touchMoveSensibility) { | |
| updateKeyChange('down', true); | |
| updateKeyChange('up', false); | |
| } else { | |
| updateKeyChange('up', false); | |
| updateKeyChange('down', false); | |
| } | |
| if (deltaX < -that.touchMoveSensibility) { | |
| updateKeyChange('left', true); | |
| updateKeyChange('right', false); | |
| } else if(deltaX > that.touchMoveSensibility) { | |
| updateKeyChange('right', true); | |
| updateKeyChange('left', false); | |
| } else { | |
| updateKeyChange('left', false); | |
| updateKeyChange('right', false); | |
| } | |
| } | |
| }); | |
| listen("touchmove", function (e) { | |
| e = reduceTapEvent(e); | |
| mouseMoveEvent(e); | |
| }); | |
| }; | |
| listen("mousedown", mouseDownEvent); | |
| listen("mouseup", mouseUpEvent); | |
| listen("click", clickEvent); | |
| listen("mousemove", mouseMoveEvent); | |
| listen("keydown", function (e) { | |
| that.keydown = true; | |
| updateKeyboard(e, true); | |
| }); | |
| listen("keyup", function (e) { | |
| that.keydown = false; | |
| updateKeyboard(e, false); | |
| }); | |
| // can be used to avoid key jamming | |
| listen("keypress", function (e) {}); | |
| if (!sjs.debug) | |
| listen("contextmenu", function (e) {_preventEvent(e);}); | |
| }; | |
| // Add an automatic pause to all the scenes when the user | |
| // quit the current window. | |
| _addEventListener(global, "blur", function (e) { | |
| for (var i = 0; i < sjs.scenes.length; i++) { | |
| var scene = sjs.scenes[i]; | |
| if (!scene.autoPause) | |
| continue; | |
| var anon = function (scene) { | |
| Input(scene); | |
| inputSingleton.keyboard = {}; | |
| inputSingleton.keydown = false; | |
| inputSingleton.mousedown = false; | |
| // create a semi transparent layer on the game | |
| if (scene.ticker && !scene.ticker.paused) { | |
| scene.ticker.pause(); | |
| var div = overlay(0, 0, scene.w, scene.h); | |
| div.innerHTML = '<h1>Paused</h1><p>Click or press any key to resume.</p>'; | |
| div.style.textAlign = 'center'; | |
| div.style.paddingTop = ((scene.h / 2) - 32) + 'px'; | |
| var listener = function (e) { | |
| _preventEvent(e); | |
| scene.dom.removeChild(div); | |
| _removeEventListener(doc, 'click', listener, false); | |
| _removeEventListener(doc, 'keyup', listener, false); | |
| scene.ticker.resume(); | |
| } | |
| _addEventListener(doc, 'click', listener, false); | |
| _addEventListener(doc, 'keyup', listener, false); | |
| scene.dom.appendChild(div); | |
| } | |
| } | |
| anon(scene); | |
| } | |
| }, false); | |
| Layer = function Layer(scene, name, options) { | |
| var domElement, needToCreate, domH, domW; | |
| if (!this || this.constructor !== Layer) | |
| return new Layer(scene, name, options); | |
| this.sprites = {}; | |
| this.scene = scene; | |
| if (options === undefined) | |
| options = {useCanvas: scene.useCanvas, autoClear: true} | |
| if (options.useWebGL) | |
| options.useCanvas = true; | |
| if (options.autoClear === undefined) | |
| this.autoClear = true; | |
| else | |
| this.autoClear = options.autoClear; | |
| if (options.useCanvas === undefined) | |
| this.useCanvas = this.scene.useCanvas; | |
| else | |
| this.useCanvas = options.useCanvas; | |
| this.useWebGL = options.useWebGL; | |
| this.name = name; | |
| if (this.scene.layers[name] === undefined) { | |
| this.scene.layers[name] = this; | |
| } else { | |
| if (sjs.debug) { | |
| sjs.warning("A layer named " + name + " already exist."); | |
| } | |
| // if the user try to create a Layer that already exists, | |
| // we send back the same. | |
| return this.scene.layers[name]; | |
| } | |
| this.lastZIndex = 0; | |
| domElement = doc.getElementById(name); | |
| if (!domElement) | |
| needToCreate = true; | |
| else | |
| needToCreate = false; | |
| if (this.useCanvas) { | |
| if (domElement && domElement.nodeName.toLowerCase() !== "canvas") { | |
| sjs.error("Cannot use HTMLElement " + domElement.nodeName + " with canvas renderer."); | |
| } | |
| if (needToCreate) { | |
| domElement = doc.createElement('canvas'); | |
| } | |
| } else { | |
| if (needToCreate) { | |
| domElement = doc.createElement('div'); | |
| } | |
| } | |
| if (!needToCreate) { | |
| domH = domElement.height || domElement.style.height; | |
| domW = domElement.width || domElement.style.width; | |
| } else { | |
| domH = false; | |
| domW = false; | |
| } | |
| if (options.parent) | |
| this.parent = options.parent; | |
| else | |
| this.parent = this.scene.dom; | |
| this.parent.appendChild(domElement); | |
| domElement.id = domElement.id || 'sjs' + scene.id + '-' + name; | |
| if (!options.disableAutoZIndex) { | |
| zindex += 1; | |
| domElement.style.zIndex = String(zindex); | |
| } | |
| domElement.style.backgroundColor = options.color || domElement.style.backgroundColor; | |
| this.h = options.h || domH || scene.h; | |
| this.w = options.w || domW || scene.w; | |
| if (domElement.nodeName == "CANVAS") { | |
| domElement.height = this.h; | |
| domElement.width = this.w; | |
| } else { | |
| domElement.style.height = this.h + 'px'; | |
| domElement.style.width = this.w +'px'; | |
| }; | |
| domElement.style.position = 'absolute'; | |
| domElement.style.top = domElement.style.top || '0px'; | |
| domElement.style.left = domElement.style.left || '0px'; | |
| this.dom = domElement; | |
| // webgl needs to be set after the size | |
| if (this.useCanvas) { | |
| if (options.useWebGL) { | |
| this.ctx = webgl.init(domElement); | |
| } else { | |
| this.ctx = domElement.getContext('2d'); | |
| } | |
| } | |
| }; | |
| Layer.prototype.constructor = Layer; | |
| Layer.prototype.clear = function clear() { | |
| if (this.useWebGL) | |
| this.ctx.clear(this.ctx.COLOR_BUFFER_BIT | this.ctx.DEPTH_BUFFER_BIT); | |
| else | |
| this.ctx.clearRect(0, 0, this.dom.width, this.dom.height); | |
| }; | |
| Layer.prototype.Sprite = function (src, options) { | |
| if (options) | |
| options.layer = this; | |
| else | |
| options = this; | |
| return new Sprite(this.scene, src, options); | |
| }; | |
| Layer.prototype.remove = function remove() { | |
| this.parent.removeChild(this.dom); | |
| delete this.scene.layers[this.name]; | |
| }; | |
| Layer.prototype.addSprite = function addSprite(sprite) { | |
| var index = Math.random() * 11; | |
| this.sprites[index] = sprite; | |
| return index | |
| }; | |
| Layer.prototype.setColor = function setColor(color) { | |
| this.dom.style.backgroundColor = color; | |
| }; | |
| Layer.prototype.onTop = function onTop(color) { | |
| zindex += 1; | |
| this.dom.style.zIndex = String(zindex); | |
| }; | |
| List = function List(list) { | |
| if (this.constructor !== List) | |
| return new List(list); | |
| // ensure that a List can be initialized with a list. | |
| this.list = (list && (list.list || list)) || []; | |
| this.length = this.list.length; | |
| this.index = -1; | |
| }; | |
| List.prototype.add = function add(sprite) { | |
| if (sprite.length) | |
| this.list.push.apply(this.list, sprite); | |
| else | |
| this.list.push(sprite); | |
| this.length = this.list.length; | |
| }; | |
| // alias | |
| List.prototype.append = List.prototype.add; | |
| List.prototype.remove = function remove(toRemove) { | |
| var removed = false | |
| for (var i = 0, el; el = this.list[i]; i++) { | |
| if (el == toRemove) { | |
| this.list.splice(i, 1); | |
| // delete during the iteration is possible | |
| if (this.index > -1) | |
| this.index = this.index - 1; | |
| i--; | |
| removed = true; | |
| } | |
| } | |
| this.length = this.list.length; | |
| return removed; | |
| }; | |
| List.prototype.iterate = function iterate() { | |
| this.index += 1; | |
| if (this.index >= this.list.length) { | |
| this.index = -1; | |
| return false; | |
| } | |
| return this.list[this.index]; | |
| }; | |
| List.prototype.pop = function pop() { | |
| this.length -= 1; | |
| return this.list.pop(); | |
| }; | |
| List.prototype.shift = function shift() { | |
| this.index -= 1; | |
| this.length -= 1; | |
| return this.list.shift(); | |
| }; | |
| List.prototype.isIn = function isInList(el) { | |
| for(var i=0; i<this.list.length; i++) { | |
| if(this.list[i] == el) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| List.prototype.filter = function filterList(name, value) { | |
| var newList = new List(); | |
| for(var i=0; i<this.list.length; i++) { | |
| if(this.list[i][name] == value) { | |
| newList.add(this.list[i]); | |
| } | |
| } | |
| return newList; | |
| } | |
| List.prototype.empty = function () { | |
| this.list = []; | |
| this.length = 0; | |
| this.index = -1; | |
| } | |
| var log_output = null; | |
| function output(value) { | |
| if(!log_output) { | |
| log_output = doc.createElement('textarea'); | |
| log_output.style.height = "200px"; | |
| log_output.style.width = (window.innerWidth - 100) + 'px'; | |
| doc.body.appendChild(log_output); | |
| } | |
| log_output.value = log_output.value + value; | |
| log_output.scrollTop = log_output.scrollHeight; | |
| } | |
| function _log() { | |
| if(!sjs.debug) | |
| return; | |
| var result = ""; | |
| for (var i = 0; i < arguments.length; i++) { | |
| result += arguments[i] + ' '; | |
| } | |
| output(result + "\r\n"); | |
| } | |
| var sjs = { | |
| // a global cache to load each sprite only one time. | |
| spriteCache: {}, | |
| debug: false, | |
| Cycle: Cycle, | |
| Input: Input, | |
| Scene: Scene, | |
| SpriteList: List, // backward compatibility 1.1.1 | |
| List: List, | |
| Sprite: Sprite, | |
| overlay: overlay, | |
| scenes: [], | |
| error: function error(msg) {_log("Error: " + msg);}, | |
| warning:function warning(msg) {_log("Warning: " + msg);}, | |
| log:_log, | |
| createEvent: null, | |
| math: {hypo: hypo, mod: mod, normalVector: normalVector, lineSide: lineSide} | |
| }; | |
| global.sjs = sjs; | |
| })(this); |