/**
* @author Bonsaiheldin <dm@bonsaiheld.org> (http://bonsaiheld.org/)
* @copyright 2018 Bonsaiheldin
* @license {@link https://github.com/bonsaiheldin/carrotJS/blob/master/LICENSE.md|MIT License}
*/
/** Initialize the main object. It will be populated afterwards.
*
* @namespace Carrot
*/
var Carrot =
{
"Version": "0.1.1"
};
/**
* The core game object. Starts the game.
*
* @class Carrot.Game
* @constructor
* @param {integer} [width=800] - The width of the container.
* @param {integer} [height=600] - The height of the container.
* @param {string} [container=null] - The parent div of the container.
* @param {object} [scene=null] - Custom scene the game shall use.
* @param {boolean} [transparent=false] - Defines if the container shall be transparent.
*/
Carrot.Game = function(width, height, container, scene, transparent)
{
let that = this;
let start = function()
{
that.width = width || 800;
that.height = height || 600;
that.parent = document.getElementById(container) || null;
that.scene = scene || null;
that.transparent = transparent || false;
// Config object
that.config =
{
width: that.width,
height: that.height,
parent: that.parent,
scene: that.scene,
transparent: that.transparent
};
// If no container was passed, create one
if (that.parent === null)
{
// Create background div
let backgroundDiv = document.createElement('div');
document.body.appendChild(backgroundDiv);
backgroundDiv.className = "carrotJS background";
that.background = backgroundDiv;
// Create main div for game content
mainDiv = document.createElement('div');
mainDiv.className = "main";
backgroundDiv.appendChild(mainDiv);
that.parent = mainDiv;
}
else
{
// Create a main div and put it inside the background div.
let mainDiv = document.createElement('div');
mainDiv.className = "main";
that.parent.appendChild(mainDiv);
// Turn the the passed div into the background div ...
that.background = that.parent;
// ... and the newly made div into the main one.
that.parent = mainDiv;
}
// If the container shall not be transparent, color it black
if (that.transparent === false)
{
that.background.style.backgroundColor = '#000000';
}
// Apply inline CSS
that.background.style.position = "relative";
that.background.style.width = that.width + 'px';
that.background.style.height = that.height + 'px';
that.background.style.overflow = "hidden";
that.background.style.fontFamily = "sans-serif";
that.background.style.boxSizing = "border-box";
that.parent.style.position = "relative";
that.parent.style.width = that.width + 'px';
that.parent.style.height = that.height + 'px';
// Init modules
that.time = new Carrot.Time(that);
that.debug = new Carrot.Debug(that);
that.utils = new Carrot.Utilities(that);
that.physics = new Carrot.Physics(that);
that.math = new Carrot.Math(that);
that.world = new Carrot.World(that);
that.camera = new Carrot.Camera(that);
that.cache = new Carrot.Cache(that);
that.load = new Carrot.AssetLoader(that);
that.add = new Carrot.ObjectFactory(that);
that.sound = new Carrot.SoundManager(that);
that.keyboard = new Carrot.Keyboard(that);
that.mouse = new Carrot.Mouse(that);
// Default config
that.roundPixels = false;
// Run the given preload state, if available.
if (that.scene !== null)
{
if (that.scene.preload)
{
that.background.style.background = "#0e1011 ";
// Create div for the progress bar.
let progress = document.createElement('div');
progress.style.userSelect = "none";
progress.style.pointerEvents = "none";
progress.style.position = "absolute";
progress.style.left = "50%";
progress.style.top = "50%";
progress.style.transform = "translate(-50%, -50%)";
progress.style.textAlign = "center";
progress.style.color = "#eeeeee";
progress.style.fontSize = "24px";
progress.style.lineHeight = "24px";
progress.innerHTML = "Made with "
+ "<img src='data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"64\" height=\"64\"><defs><linearGradient id=\"a\"><stop offset=\"0\" stop-color=\"%23ffdbc2\"/><stop offset=\"1\" stop-color=\"%23fff\"/></linearGradient><linearGradient id=\"b\"><stop offset=\"0\" stop-color=\"%23fff\"/><stop offset=\"1\" stop-color=\"%23ffaa6e\"/></linearGradient><linearGradient id=\"c\"><stop offset=\"0\" stop-color=\"%23ff2929\"/><stop offset=\".5\" stop-color=\"%23f00000\"/><stop offset=\"1\" stop-color=\"%23820000\"/></linearGradient><linearGradient id=\"d\"><stop offset=\"0\" stop-color=\"%23fff\" stop-opacity=\".771\"/><stop offset=\"1\" stop-color=\"%23fff\" stop-opacity=\".157\"/></linearGradient></defs><path d=\"M44.268 12.869S46.504 2.734 47.236 1.27c.732-1.463 2.066-1.664 2.212-.859.146.806-1.943 9-2.437 9.714-.494.714.638 1.922 1.224 1.483.584-.439 3.55-8.377 4.574-9.145 1.024-.768 2.486-.035 2.486-.035s.822 1.555.054 2.36c-.769.805-3.477 6.95-5.123 7.498-1.645.548.657 1.794.657 1.794s4.72-5.596 6.657-6.363c1.938-.767 4.094-.327 3.308.606-.787.933.473 2.16-.623 2.525-1.097.365-6.29 4.716-6.967 4.88-.676.164-.11 1.683.657 1.794.768.11 6.363-5.23 7.515-5.064 1.151.166 2.576.24 2.448 1.137-.13.896-.696 2.122-2.232 2.816-1.535.694-7.385 2.337-8.518 2.958-1.134.622-9.153-5.366-8.86-6.5z\" fill=\"%234cd600\"/><path d=\"M367.137 604.92s-1.55-.1-2.102.864c-.55.963-3.403 5.89-3.224 7.009.18 1.119 1.543-.002 1.543-.002s4.886-5.271 5.13-6.213c.244-.943-.712-1.392-1.347-1.657z\" fill=\"%23ffa000\" stroke=\"%23e9621f\" stroke-width=\".406\" transform=\"matrix(6.50417 1.39252 -1.39097 6.51142 -1499.653 -4435.396)\"/><path d=\"M365.16 606.471s1.639-.893 2.41.772M364.578 607.708s1.476-.582 2.14.718M363.942 608.886s1.164-.556 1.841.555M363.373 610.104s1.124-.541 1.476.501M362.676 611.247s.866-.393 1.259.406\" fill=\"none\" stroke=\"%23f66000\" stroke-width=\".283\" transform=\"matrix(6.50417 1.39252 -1.39097 6.51142 -1499.653 -4435.396)\"/></svg>' style='width: 32px; height: 32px; vertical-align: bottom;'>"
+ "carrotJS";
// Add actual progress bar
progress.innerHTML += ""
+ "<div><div></div></div>"
// Add "loading ..." below.
progress.innerHTML += "<font style='font-size: 16px;'>Loading ...</font>"
// Create the progress bar itself.
let progressBarBackground = progress.getElementsByTagName('div')[0];
progressBarBackground.style.width = "320px";
progressBarBackground.style.height = "24px";
progressBarBackground.style.backgroundColor = "#1d2124";
progressBarBackground.style.borderRadius = "12px";
progressBarBackground.style.marginTop = "8px";
progressBarBackground.style.overflow = "hidden";
let progressBar = progress.getElementsByTagName('div')[1];
progressBar.style.width = "0%";
progressBar.style.height = "100%";
progressBar.style.backgroundColor = "#2c3135";
progressBar.style.transition = "width 0.5s linear";
// Add everything to the game!
that.background.appendChild(progress);
// Store progress bar for manipulation through loader.
that.load.progressBar = progressBar;
// Start preload state
that.scene.preload();
}
}
console.log("%c🥕 carrotJS v" + Carrot.Version + " | HTML5 game engine utilizing the DOM | https://github.com/bonsaiheldin/carrotJS", "font-weight: bold;");
return that;
};
document.addEventListener('DOMContentLoaded', start, false);
}
Carrot.Game.prototype =
{
/**
* Starts the update and the render loops of the core.
* @method Carrot.Game#_render
* @param {Carrot.Game} game - The core game object.
* @private
*/
start(game)
{
// Start the two core loops
MainLoop.setUpdate(function(delta)
{
game._update(delta);
}).setDraw(function()
{
game._render();
}).start();
// Run the given create state, if available.
if (this.scene !== null)
{
if (this.scene.create)
{
this.scene.create();
}
}
},
/**
* Sets the size of the game container.
*
* @method Carrot.Game#setBounds
* @param {integer} [width=800] - The right bound of the game container.
* @param {integer} [height=600] - The bottom bound of the game container.
*/
setBounds(width, height)
{
this.width = width;
this.height = height;
if (width === undefined) this.width = 800;
if (height === undefined) this.height = 600;
this.background.style.width = this.width + "px";
this.background.style.height = this.height + "px";
},
/**
* Sets the size of the game container to the size of the camera.
*
* @method Carrot.Game#setBoundsToCamera
*/
setBoundsToCamera()
{
this.width = this.camera.width;
this.height = this.camera.height;
this.background.style.width = this.width + "px";
this.background.style.height = this.height + "px";
},
/**
* Sets the size of the game container to the size of the game world.
*
* @method Carrot.Game#setBoundsToWorld
*/
setBoundsToWorld()
{
this.width = this.world.width;
this.height = this.world.height;
this.background.style.width = this.width + "px";
this.background.style.height = this.height + "px";
},
/**
* Sets the cursor image shown when the mouse is hovering the game container.
* @method Carrot.Game#setCursor
* @param {key} key - The key (name) of the image to be used. If the image is not a previously loaded game asset, it will use the browser's set of default cursors.
* @param {integer} [x=0] - The x coordinate of the cursor's hotspot.
* @param {integer} [y=0] - The y coordinate of the cursor's hotspot.
*/
setCursor(key, x, y)
{
if (x === undefined) x = 0;
if (y === undefined) y = 0;
if (key === undefined)
{
this.background.style.cursor = "";
}
else
{
let image = this.cache.images[key];
if (image !== undefined)
{
this.background.style.cursor = "url(" + image.src + ") " + x + " " + y + ", default";
}
else
{
this.background.style.cursor = key;
}
}
},
/**
* The internal update loop of the core. Happens automatically.
* @method Carrot.Game#_update
* @param {integer} delta - The time the last frame took in miliseconds. It is managed by the `_update` loop of {Carrot.Time}.
* @private
*/
_update(delta, b, c)
{
this.time._update(delta);
this.world._update();
this.camera._update();
// Run the given update state, if available.
if (this.scene !== null)
{
if (this.scene.update)
{
this.scene.update();
}
}
},
/**
* The internal render loop of the core. Happens automatically.
* @method Carrot.Game#_render
* @private
*/
_render(a, b, c)
{
this.world._render();
this.camera._render();
// Run the given render state, if available.
if (this.scene !== null)
{
if (this.scene.render)
{
this.scene.render();
}
}
}
};
Carrot.Game.prototype.constructor = Carrot.Game;
/**
* The world container stores every sprite or group and updates them automatically. It is managed automatically by {@link Carrot.Game}.
*
* @class Carrot.World
* @constructor
* @param {Carrot.Game} game - The core game object.
*/
Carrot.World = function(game)
{
this.game = game;
this.camera = new Carrot.Camera(this.game);
this.x = 0;
this.y = 0;
this.width = this.game.width;
this.height = this.game.height;
this.bounds = new Carrot.Rectangle(this.x, this.y, this.width, this.height);
this.children = [];
return this;
};
Carrot.World.prototype =
{
/**
* Adds a child to the world container. The child can be a sprite or a group.
*
* @method Carrot.World#addChild
* @param {Carrot.Group | Carrot.Sprite} entity - The entity to add.
*/
addChild(entity)
{
// Remove from old parent, if existing
if (entity.parent !== this)
{
entity.parent.removeChild(entity);
}
// Add to world
this.children.push(entity);
entity.parent = this; // Update parent
},
/**
* Removes the given child from the world container.
*
* @method Carrot.World#removeChild
* @param {Carrot.Group | Carrot.Sprite} entity - The child to remove.
*/
removeChild(entity)
{
this.children.splice(this.children.indexOf(entity), 1);
},
/**
* Creates a sprite and immediately adds it to the world container.
*
* @method Carrot.World#create
* @param {integer} [x=0] - The x coordinate in the world of the sprite.
* @param {integer} [y=0] - The y coordinate in the world of the sprite.
* @param {string} [key=null] - This is the image for the sprite. If left empty, the sprite will be just a green rectangle.
* @param {integer} [frame=0] - Only for spritesheets: The starting frame of the image. If not passed, it will be 0, the first frame.
* @param {boolean} [active=false] - The default active property of the sprite.
* @return {Carrot.Sprite}
*/
create(x, y, key, frame, active)
{
x = x || 0;
y = y || 0;
key = key || null;
frame = frame || 0;
active = active || false;
let sprite = this.game.add.sprite(x, y, key, frame, active);
// Add the sprite to the group.
this.addChild(sprite);
return sprite;
},
/**
* Creates multiple sprites and adds them to the world container.
*
* @method Carrot.World#createMultiple
* @param {integer} [quantity=1] - The x coordinate in the world of the sprite.
* @param {integer} [x=0] - The x coordinate in the world of the sprite.
* @param {integer} [y=0] - The y coordinate in the world of the sprite.
* @param {string} [key=null] - This is the image for the sprite. If left empty, the sprite will be just a green rectangle.
* @param {integer} [frame=0] - Only for spritesheets: The starting frame of the image. If not passed, it will be 0, the first frame.
* @param {boolean} [active=false] - The default active property of the sprites.
* @return {array}
*/
createMultiple(quantity, x, y, key, frame, active)
{
quantity = quantity || 1;
x = x || 0;
y = y || 0;
key = key || null;
frame = frame || 0;
active = active || false;
let sprites = [];
for (let i = 0; i < quantity; i++)
{
let sprite = this.game.add.sprite(x, y, key, frame, active);
// Add the sprites to the group.
this.addChild(sprite);
sprites.push(sprite);
}
return sprites;
},
/**
* Sets the size (bounds) of the game world.
*
* @method Carrot.World#setBounds
* @param {integer} [x=0] - The left bound of the world.
* @param {integer} [y=0] - The top bound of the world.
* @param {integer} [width=800] - The right bound of the world.
* @param {integer} [height=600] - The bottom bound of the world.
*/
setBounds(x, y, width, height)
{
this.x = 0;
this.y = 0;
this.width = width;
this.height = height;
if (x === undefined) this.x = 0;
if (y === undefined) this.y = 0;
if (width === undefined) this.width = 800;
if (height === undefined) this.height = 600;
},
/**
* Sets the size (bounds) of the game world to size of the game container.
*
* @method Carrot.World#setBoundsToGame
*/
setBoundsToGame()
{
this.width = this.game.width;
this.height = this.game.height;
},
/**
* Sets the size (bounds) of the game world to the size of the camera.
*
* @method Carrot.World#setBoundsToCamera
*/
setBoundsToCamera()
{
this.width = this.camera.width;
this.height = this.camera.height;
},
/**
* The internal update loop of the world container. Happens automatically.
*
* @method Carrot.World#_update
* @private
*/
_update()
{
for (let i = 0; i < this.children.length; i++)
{
let child = this.children[i];
if (child.active)
{
child._update();
// If the child has a custom update loop, call it.
if (child.update)
{
child.update();
}
}
}
},
/**
* The internal render loop of the world container. Happens automatically.
*
* @method Carrot.World#_render
* @private
*/
_render()
{
for (let i = 0; i < this.children.length; i++)
{
let child = this.children[i];
child._render();
}
}
};
Carrot.World.prototype.constructor = Carrot.World;
/**
* The camera presents the game to the player. It is managed automatically by {@link Carrot.Game}.
*
* @class Carrot.Camera
* @constructor
* @param {Carrot.Game} game - The core game object.
*/
Carrot.Camera = function(game)
{
this.game = game;
this.world = this.game.world;
this.x = 0;
this.y = 0;
this.width = this.game.width;
this.height = this.game.height;
this.bounds = new Carrot.Rectangle(this.x, this.y, this.width, this.height);
this.left = this.x;
this.right = this.x + this.width;
this.top = this.y;
this.bottom = this.y + this.height;
this.roundPixels = false;
// Internal values
this._x = this.x;
this._y = this.y;
this.target = null;
return this;
};
Carrot.Camera.prototype =
{
/**
* Sets the size (bounds) of the camera.
*
* @method Carrot.World#setBounds
* @param {integer} [width=800] - The width of the camera.
* @param {integer} [height=600] - The height of the camera.
*/
setBounds(width, height)
{
this.width = width;
this.height = height;
if (width === undefined) this.width = 800;
if (height === undefined) this.height = 600;
},
/**
* Sets the size (bounds) of the camera to the size of the game container.
*
* @method Carrot.Camera#setBoundsToGame
*/
setBoundsToGame()
{
this.width = this.game.width;
this.height = this.game.height;
},
/**
* Sets the size (bounds) of the camera to size of the game world.
*
* @method Carrot.Camera#setBoundsToWorld
*/
setBoundsToWorld()
{
this.width = this.world.width;
this.height = this.world.height;
},
/**
* Let the camera follow a sprite.
*
* @method Carrot.Camera#follow
* @param {Carrot.Sprite} target - The sprite the camera shall follow.
*/
follow(target)
{
if (target)
{
this.target = target;
}
},
/**
* Let the camera stop following any entity.
*
* @method Carrot.Camera#unfollow
*/
unfollow()
{
this.target = null;
},
/**
* The internal update loop of the camera. It is managed automatically by {@link Carrot.World}.
*
* @method Carrot.Camera#_update
* @private
*/
_update()
{
if (this.target !== null)
{
this._x = this.x;
this._y = this.y;
let targetX = this.target.x;
let targetY = this.target.y;
let worldWidthHalf = this.width * 0.5;
let worldHeightHalf = this.height * 0.5;
// Left / right
if (targetX > worldWidthHalf
&& targetX <= this.world.width - worldWidthHalf)
{
this.x = targetX - (this.width * 0.5);
}
// Top / bottom
if (targetY > worldHeightHalf
&& targetY <= this.world.height - worldHeightHalf)
{
this.y = targetY - worldHeightHalf;
}
}
// Rounding pixels if desired.
if (this.game.roundPixels
|| this.roundPixels)
{
this.x = Math.round(this.x);
this.y = Math.round(this.y);
}
// Update internal values
this.left = this.x;
this.right = this.left + this.width;
this.top = this.y;
this.bottom = this.top + this.height;
},
/**
* The internal render loop of the camera. It is managed automatically by {@link Carrot.World}.
*
* @method Carrot.Camera#_render
* @private
*/
_render()
{
// Transform the game div according to the camera.
// But only if the position has changed.
if (this.x !== this._x || this.y !== this._y)
{
this.game.parent.style.left = -this.x + "px";
this.game.parent.style.top = -this.y + "px";
}
}
};
Carrot.Camera.prototype.constructor = Carrot.Camera;
/**
* Groups are containers storing game objects (sprites). They are added automatically to the world container.
*
* @class Carrot.Group
* @constructor
* @param {Carrot.Game} game - The core game object.
*/
Carrot.Group = function(game)
{
this.game = game;
this.world = this.game.world;
this.physicsEnabled = false;
// Internal values
this.type = Carrot.GROUP;
this.active = true;
this.parent = this.world;
this.children = [];
this.inactives = [];
// Add it to the world
this.parent.addChild(this);
return this;
};
Carrot.Group.prototype =
{
/**
* Adds an entity to a group. The entity can be a sprite or another group.
*
* @method Carrot.Group#addChild
* @param {Carrot.Group | Carrot.Sprite} entity - The entity to add.
*/
addChild(entity)
{
// Remove from old parent
if (entity.parent !== this)
{
entity.parent.removeChild(entity);
}
// Add to group
if (entity.active)
{
this.children.push(entity);
}
else
{
this.inactives.push(entity);
}
entity.parent = this; // Update parent.
},
/**
* Removes the given entity from a group.
*
* @method Carrot.Group#removeChild
* @param {Carrot.Group | Carrot.Sprite} entity - The entity to remove.
*/
removeChild(entity)
{
if (entity.active)
{
this.children.splice(this.children.indexOf(entity), 1);
}
else
{
this.inactives.splice(this.inactives.indexOf(entity), 1);
}
},
/**
* Iterates all children of the group and sets their `property` to the given `value`.
*
* @method Carrot.Group#setAll
* @param {string} property - The property to change.
* @param {any} value - The new value for the property.
* @param {boolean} [active=true] - Defines if the active or the inactive sprites shall be affected.
*/
setAll(property, value, active)
{
property = property.split('.');
active = active;
if (active === undefined) active = true;
let key1 = property[0] || "";
let key2 = property[1] || "";
let key3 = property[2] || "";
let array = this.children;
if (active === false) array = this.inactives;
for (let i = 0; i < array.length; i++)
{
let child = array[i];
if (child[key1] !== undefined)
{
if (child[key1][key2] !== undefined)
{
if (child[key1][key2][key3] !== undefined)
{
child[key1][key2][key3] = value;
}
else
{
child[key1][key2] = value;
}
}
else
{
child[key1] = value;
}
}
}
},
/**
* Creates a sprite and adds it to the group.
*
* @method Carrot.Group#create
* @param {integer} [x=0] - The x coordinate in the world of the sprite.
* @param {integer} [y=0] - The y coordinate in the world of the sprite.
* @param {string} [key=null] - This is the image for the sprite. If left empty, the sprite will be just a green rectangle.
* @param {integer} [frame=0] - Only for spritesheets: The starting frame of the image. If not passed, it will be 0, the first frame.
* @param {boolean} [active=false] - The default active property of the sprites.
* @return {Carrot.Sprite}
*/
create(x, y, key, frame, active)
{
if (x === undefined) x = 0;
if (y === undefined) y = 0;
if (key === undefined) key = null;
if (frame === undefined) frame = 0;
if (active === undefined) active = false;
let sprite = this.game.add.sprite(x, y, key, frame, active);
if (this.physicsEnabled)
{
app.game.physics.enable(sprite);
}
// Add the sprite to the group.
this.addChild(sprite);
return sprite;
},
/**
* Creates multiple sprites and adds them to the group.
*
* @method Carrot.Group#createMultiple
* @param {integer} [quantity=0] - The quantity of sprites to create.
* @param {integer} [x=0] - The x coordinate in the world of the sprite.
* @param {integer} [y=0] - The y coordinate in the world of the sprite.
* @param {string} [key=null] - This is the image for the sprite. If left empty, the sprite will be just a green rectangle.
* @param {integer} [frame=0] - Only for spritesheets: The starting frame of the image. If not passed, it will be 0, the first frame.
* @param {boolean} [active=false] - The default active property of the sprites.
* @return {Carrot.Sprite[]}
*/
createMultiple(quantity, x, y, key, frame, active)
{
if (quantity === undefined) quantity = 0;
if (x === undefined) x = 0;
if (y === undefined) y = 0;
if (key === undefined) key = null;
if (frame === undefined) frame = 0;
if (active === undefined) active = false;
let sprites = [];
for (let i = 0; i < quantity; i++)
{
let sprite = this.game.add.sprite(x, y, key, frame, active);
if (this.physicsEnabled)
{
app.game.physics.enable(sprite);
}
// Add the sprites to the group.
this.addChild(sprite);
sprites.push(sprite);
}
return sprites;
},
/**
* Returns an inactive sprite from the group.
*
* @method Carrot.Group#getInactive
* @param {boolean} [createIfNull=false] - If no inactive sprite could be found, create one, add it to this group and return it.
* @param {integer} [x=0] - The x coordinate in the world of the sprite.
* @param {integer} [y=0] - The y coordinate in the world of the sprite.
* @param {string} [key=null] - This is the image for the sprite. If left empty, the sprite will be just a green rectangle.
* @param {integer} [frame=0] - Only for spritesheets: The starting frame of the image. If not passed, it will be 0, the first frame.
* @return {Carrot.Sprite}
*/
getInactive(createIfNull, x, y, key, frame)
{
if (createIfNull === undefined) createIfNull = false;;
if (x === undefined) x = 0;
if (y === undefined) y = 0;
if (key === undefined) key = null;
if (frame === undefined) frame = 0;
for (let i = 0; i < this.inactives.length; i++)
{
let child = this.inactives[i];
if (child.active === false)
{
child.x = x;
child.y = y;
child.key = key;
child.frame = 0;
return child;
}
}
// If no inactive sprite could be found, consider creating a new one.
if (createIfNull)
{
return this.create(x, y, key, frame, false);
}
else
{
return null;
}
},
/**
* Counts the inactive sprites in the group.
*
* @method Carrot.Group#countInactive
* @return {integer}
*/
countInactive()
{
let result = 0;
for (let i = 0; i < this.children.length; i++)
{
let child = this.children[i];
if (child.active === false)
{
result += 1;
}
}
return result;
},
/**
* Counts the active sprites in the group.
*
* @method Carrot.Group#countActive
* @return {integer}
*/
countActive()
{
let result = 0;
for (let i = 0; i < this.children.length; i++)
{
let child = this.children[i];
if (child.active === true)
{
result += 1;
}
}
return result;
},
/**
* Destroys the group and removes it entirely from the game world.
*
* @method Carrot.Group#destroy
* @param {boolean} [destroyChildren=false] - If true, all the children of the sprite and their children are destroyed, too.
*/
destroy(destroyChildren)
{
for (let i = 0; i < this.children.length; i++)
{
let child = this.children[i];
// If the children of the sprite shall be destroyed, too.
if (destroyChildren)
{
child.destroy(true);
}
// If not, add them to the world container instead, so they're still updated.
else
{
this.removeChild(child);
}
}
this.active = false;
// Remove the group from the world container
this.parent.removeChild(this);
},
/**
* The internal update loop of the group. It is managed automatically by {@link Carrot.World}.
*
* @method Carrot.Group#_update
* @private
*/
_update()
{
for (let i = 0; i < this.children.length; i++)
{
let child = this.children[i];
if (child.active)
{
child._update();
// If the child has a custom update loop, call it.
if (child.update)
{
child.update();
}
}
}
},
/**
* The internal render loop of the group. It is managed automatically by {@link Carrot.World}.
*
* @method Carrot.Group#_render
* @private
*/
_render()
{
for (let i = 0; i < this.children.length; i++)
{
let child = this.children[i];
child._render();
}
}
};
Carrot.Group.prototype.constructor = Carrot.Group;
/**
* Sprites are game objects which contain the actual HTML elements for rendering.
*
* @class Carrot.Sprite
* @constructor
* @param {Carrot.Game} game - The core game object.
* @param {integer} [x=0] - The x coordinate in the world of the sprite.
* @param {integer} [y=0] - The y coordinate in the world of the sprite.
* @param {string} [key=null] - This is the image for the sprite. If left empty, the sprite will be just a green rectangle.
* @param {integer} [frame=0] - Only for spritesheets: The starting frame of the image. If not passed, it will be 0, the first frame.
*/
Carrot.Sprite = function(game, x, y, key, frame, active)
{
this.x = x;
this.y = y;
this.key = key;
this.frame = frame;
this.active = active;
this.health = 0;
this.damage = 0;
if (x === undefined) this.x = 0;
if (y === undefined) this.y = 0;
if (key === undefined) this.key = null;
if (frame === undefined) this.frame = 0;
if (active === undefined) this.active = true;
// Internal values
this.game = game;
this.world = this.game.world;
this.camera = this.game.camera;
this.time = this.game.time;
this.type = Carrot.SPRITE;
this.parent = this.world;
this.children = [];
this.alpha = 1;
this.width = 32;
this.height = 32;
this.anchor = new Carrot.Point(0, 0);
this.left = this.x - (this.width * this.anchor.x);
this.right = this.left + this.width;
this.top = this.y - (this.height * this.anchor.y);
this.bottom = this.top + this.height;
this.outOfBoundsKill = false;
this.inCamera = false;
this.transform = ''; // String to collect CSS transforms for this sprite.
this.angle = 0; // Default image angle
this.roundPixels = false;
// Values from the previous frame
this._x = this.x;
this._y = this.y;
this._angle = this.angle;
this._alpha = this.alpha;
// Physics are disabled by default.
this.body = null;
// Create HTML element if desired.
this.image = null;
this.style = null;
this.visible = true;
// Add event listeners
this.events = new Carrot.Events(this);
if (this.active)
{
this.revive();
}
// Add it to the world
this.parent.addChild(this);
// Test
this.name = "Unknown sprite";
return this;
};
Carrot.Sprite.prototype =
{
/**
* Creates the HTML element for this sprite. Called by {Carrot.Sprite} or revive().
*
* @method Carrot.Sprite#createNode
* @return {object}
*/
createNode()
{
if (this.image === null)
{
// HTML magic
let node = document.createElement('div');
this.image = node;
this.style = this.image.style;
// Apply standard values
this.image.style.position = "absolute";
this.image.style.width = "32px";
this.image.style.height = "32px";
this.image.style.backgroundSize = "cover";
// If an image was given, apply it as a background image
if (this.key !== null)
{
let image = this.game.cache.images[this.key];
this.style.backgroundImage = "url(" + image.src + ")";
this.width = image.width;
this.height = image.height;
this.image.style.width = this.width + "px";
this.image.style.height = this.height + "px";
// Apply frame on spritesheet
if (this.frame !== 0)
{
let frame = image.frames[this.frame];
this.image.style.backgroundPosition = frame.x + "px " + frame.y + "px";
}
}
// Render it once
this.image.style.left = this.x - (this.width * this.anchor.x) + "px";
this.image.style.top = this.y - (this.height * this.anchor.y) + "px";
// Add the HTML div to the page.
this.game.parent.appendChild(this.image);
return node;
}
},
/**
* Removes the HTML element of this sprite. Called by {Carrot.Sprite}, kill or destroy.
*
* @method Carrot.Sprite#destroyNode
*/
destroyNode()
{
if (this.image !== null)
{
this.image.parentNode.removeChild(this.image);
this.image = null;
this.style = null;
}
},
/**
* Adds a child to the sprite. The entity must be another sprite.
*
* @method Carrot.Sprite#addChild
* @param {Carrot.Sprite} entity - The entity to add.
*/
addChild(entity)
{
// HTML magic
if (entity.image !== null)
{
// Remove the sprite's image from the main HTML and add it to the sprite's div.
this.game.parent.removeChild(entity.image);
this.image.appendChild(entity.image);
entity.image.style.left = entity.x - (entity.width * entity.anchor.x) + "px";
entity.image.style.top = entity.y - (entity.height * entity.anchor.y) + "px";
}
// Remove from old parent
if (entity.parent !== this)
{
entity.parent.removeChild(entity);
}
// Add to sprite
this.children.push(entity);
entity.parent = this; // Update parent
},
/**
* Removes the passed child from the sprite.
*
* @method Carrot.Sprite#removeChild
* @param {Carrot.Sprite} entity - The entity to remove.
*/
removeChild(entity)
{
// Remove the sprite's image from the sprite and add it to the main HTML.
this.image.removeChild(entity.image);
this.game.parent.appendChild(entity.image);
this.children.splice(this.children.indexOf(entity), 1);
},
/**
* Kills the sprite. Used for object pools.
*
* @method Carrot.Sprite#kill
* @param {boolean} [destroyNode] - If `true`, will destroy the sprite's HTML element.
*/
kill(destroyNode)
{
// Fire onKilled event
this.events.onKilled();
// Remove from actives array.
this.parent.removeChild(this);
this.active = false;
this.visible = false;
// Add to inactives array.
this.parent.addChild(this);
// Remove the sprite's HTML element if there is one
if (destroyNode)
{
this.destroyNode();
}
},
/**
* Revives the sprite. Used for object pools.
*
* @method Carrot.Sprite#revive
*/
revive()
{
// Fire onRevived event
this.events.onRevived();
// Remove from inactives array.
this.parent.removeChild(this);
this.active = true;
this.visible = true;
// Add to actives array.
this.parent.addChild(this);
// Create HTML element if none exists
this.createNode();
},
/**
* Destroys the sprite and removes it from its group and the game world.
*
* @method Carrot.Sprite#destroy
* @param {boolean} [destroyChildren=false] - If true, all the children of the sprite and their children are destroyed, too.
*/
destroy(destroyChildren)
{
// Fire onRevived event
this.events.onDestroyed();
destroyChildren = destroyChildren || false;
for (let i = 0; i < this.children.length; i++)
{
let child = this.children[i];
// If the children of the sprite shall be destroyed, too.
if (destroyChildren)
{
child.destroy(true);
}
// If not, add them to the world container instead, so they're still updated.
else
{
this.removeChild(child);
}
}
// Remove the sprite from its group or the world container.
this.parent.removeChild(this);
// Remove the HTML element of the sprite
this.destroyNode();
},
/**
* Changes the frame shown. Only for spritesheets.
*
* @method Carrot.Sprite#setFrame
* @param {integer} [frame=0]
*/
setFrame(frame)
{
frame = frame || 0;
frame = this.game.cache.images[this.key].frames[frame];
this.image.style.backgroundPosition = frame.x + "px " + frame.y + "px";
},
/**
* Applies a glow effect on the sprite. Its shape is determined by the sprite's body which can be a rectangle or a circle.
*
* @method Carrot.Sprite#setGlow
* @param {integer} [blur=0] - Blur in pixels.
* @param {integer} [spread=0] - Spread in pixels.
* @param {Carrot.Color | string} [color=Carrot.Color.Lime] - The color of the glow. Must be given in one of the following formats: Hexadecimal, RGB, RGBA, HSL, HSLA or one of the 140 predefined browser colors.
* @param {boolean} [inset=false] - Defines if the glow should be go out or inside the sprite.
*/
setGlow(blur, spread, color, inset)
{
if (blur !== false)
{
blur = blur || 0;
blur = blur + "px ";
spread = spread || 0;
spread = spread + "px ";
color = color || Carrot.Color.Lime;
if (inset) { inset = " inset"; }
else { inset = ""; }
this.image.style.boxShadow = "0px 0px " + blur + spread + color + inset;
}
// If the first parameter is false or not given, disable the glow.
else if (blur === undefined)
{
this.image.style.boxShadow = "";
}
},
/**
* The custom update loop of the sprite. It is managed automatically by {@link Carrot.World} or {@link Carrot.Group}, depending on whhich it was added to.
*
* @method Carrot.Sprite#update
* @private
*/
update: function(){},
/**
* The internal update loop of the sprite. It is managed automatically by {@link Carrot.World} or {@link Carrot.Group}, depending on whhich it was added to.
*
* @method Carrot.Sprite#_update
* @private
*/
_update()
{
// Save values of this frame
this._x = this.x;
this._y = this.y;
this._angle = this.angle;
this._alpha = this.alpha;
this._transform = this.transform;
// Store some variables for faster accessing
let worldWidth = this.world.width;
let worldHeight = this.world.height;
// Check if inside camera bounds
this.inCamera = false;
if (this.right > this.camera.left
&& this.bottom > this.camera.top
&& this.left < this.camera.right
&& this.top < this.camera.bottom)
{
this.inCamera = true;
}
// Physics
if (this.body !== null)
{
// Physics enabled on this body?
if (this.body.enabled)
{
// Reset body.touching
this.body.touching.none = true;
this.body.touching.left = false;
this.body.touching.right = false;
this.body.touching.top = false;
this.body.touching.bottom = false;
// Rotating
if (this.body.angularVelocity !== 0)
{
this.angle += this.body.angularVelocity * this.time.delta;
}
// Rotation decelaration
if (this.body.allowAngularDrag)
{
if (this.body.angularDrag > 0)
{
this.angle += (1 - this.body.angularDrag);
}
}
// Acceleration
if (this.body.allowAcceleration)
{
this.body.velocity.x += this.body.acceleration.x * this.time.delta;
this.body.velocity.y += this.body.acceleration.y * this.time.delta;
}
// Gravity
if (this.body.allowGravity)
{
this.body.velocity.x += this.body.gravity.x * this.time.delta;
this.body.velocity.y += this.body.gravity.y * this.time.delta;
}
// Drag: Deceleration
if (this.body.allowDrag)
{
this.body.velocity.x *= (1 - this.body.drag.x);
this.body.velocity.y *= (1 - this.body.drag.y);
}
// Limit velocity
let maxVelX = this.body.maxVelocity.x;
let maxVelY = this.body.maxVelocity.y;
if (this.body.velocity.x > maxVelX) { this.body.velocity.x = maxVelX; }
else if (this.body.velocity.x < -maxVelX) { this.body.velocity.x = -maxVelX; }
if (this.body.velocity.y > maxVelY) { this.body.velocity.y = maxVelY; }
else if (this.body.velocity.y < -maxVelY) { this.body.velocity.y = -maxVelY; }
// Moving
this.x += this.body.velocity.x * this.time.delta;
this.y += this.body.velocity.y * this.time.delta;
// Rounding pixels if desired.
if (this.game.roundPixels
|| this.roundPixels)
{
this.x = Math.round(this.x);
this.y = Math.round(this.y);
}
// Let the sprite collide with the world bounds
if (this.body.collideWorldBounds)
{
if (this.body.checkCollision.none === false)
{
if (this.body.checkCollision.left)
{
if (this.x <= this.width)
{
this.x = this.width;
this.body.touching.none = false;
this.body.touching.left = true;
// Bouncing
if (this.body.allowBounce)
{
this.body.velocity.x = -(this.body.velocity.x * this.body.bounce.x);
}
}
}
if (this.body.checkCollision.right)
{
if (this.x + this.width >= worldWidth)
{
this.x = worldWidth - this.width;
this.body.touching.none = false;
this.body.touching.right = true;
// Bouncing
if (this.body.allowBounce)
{
this.body.velocity.x = -(this.body.velocity.x * this.body.bounce.x);
}
}
}
if (this.body.checkCollision.top)
{
if (this.y <= this.height)
{
this.y = this.height;
this.body.touching.none = false;
this.body.touching.top = true;
// Bouncing
if (this.body.allowBounce)
{
this.body.velocity.y = -(this.body.velocity.y * this.body.bounce.y);
}
}
}
if (this.body.checkCollision.bottom)
{
if (this.y + this.height >= worldHeight)
{
this.y = worldHeight - this.height;
this.body.touching.none = false;
this.body.touching.bottom = true;
// Bouncing
if (this.body.allowBounce)
{
this.body.velocity.y = -(this.body.velocity.y * this.body.bounce.y);
}
}
}
}
}
}
}
// Kill the sprite if it leaves the world bounds
if (this.outOfBoundsKill)
{
// Left, right, top, bottom
if (this.left < 0
|| this.right > worldWidth
|| this.top < 0
|| this.bottom > worldHeight)
{
this.kill(true); // Remove its HTML element, too.
}
}
// Update some internal stuff
this.left = this.x;
this.right = this.x + this.width;
this.top = this.y;
this.bottom = this.y + this.height;
// Collect all transforms and apply them in the render function
this.transform = "";
if (this.angle !== 0)
{
this.transform += " rotate(" + this.angle + "deg) ";
}
// Update the sprite's children
for (let i = 0; i < this.children.length; i++)
{
let child = this.children[i];
if (child.active)
{
child._update();
// If the child has a custom update loop, call it.
if (child.update)
{
child.update();
}
}
}
},
/**
* The internal render loop of the sprite. It is managed automatically by {@link Carrot.World} or {@link Carrot.Group}, depending on whhich it was added to.
*
* @method Carrot.Sprite#_render
* @private
*/
_render()
{
if (this.active)
{
// Don't display the sprite if not in camera.
if (this.inCamera === false)
{
if (this.visible === true)
{
this.visible = false;
this.style.display = "none";
}
}
// Apply cosmetic CSS rules only when inside camera bounds.
else
{
// Update only if the current values don't match the ones from the last frame
if (this.visible === false)
{
this.visible = true;
this.style.display = "";
}
if (this.alpha !== this._alpha)
{
if (this.alpha < 1)
{
this.style.opacity = this.alpha;
}
}
if (this.x !== this._x)
{
this.image.style.left = this.x - (this.width * this.anchor.x) + "px";
}
if (this.y !== this._y)
{
this.image.style.top = this.y - (this.height * this.anchor.y) + "px";
}
// Apply CSS transform.
if (this.transform !== this._transform)
{
this.style.transform = this.transform;
}
}
// Render the sprite's children
for (let i = 0; i < this.children.length; i++)
{
let child = this.children[i];
child._render();
}
}
}
};
Carrot.Sprite.prototype.constructor = Carrot.Sprite;
/**
* The Time container stores the current time, the time the game has started at and the delta time for animating.
*
* @class Carrot.Time
* @constructor
* @param {Carrot.Game} game - The core game object.
*/
Carrot.Time = function(game)
{
this.game = game;
this.now = performance.now();
this.lastFrame = this.now;
this.delta = 1000 / 60;
this.timers = [];
return this;
};
Carrot.Time.prototype =
{
/**
* The internal update loop of the time object. It is managed automatically by {@link Carrot.Game}.
* @method Carrot.Time#_update
* @private
*/
_update(delta)
{
this.lastFrame = this.now;
this.now = performance.now();
this.lastFrame = this.now - this.lastFrame;
this.delta = delta / 1000;
// Update timers
for (let i = 0; i < this.timers.length; i++)
{
let timer = this.timers[i];
timer._update(this.lastFrame);
}
}
};
Carrot.Time.prototype.constructor = Carrot.Time;
/**
* Handler for timed events.
*
* @class Carrot.Timer
* @constructor
* @param {Carrot.Game} game - The core game object.
* @param {delay} delay - The amount of time when the timer shall fire.
* @param {function} callback - The method to be called when the timer fires.
* @param {integer} timesToRepeat - Times the timer shall be repeat. 0 is a one time event.
*/
Carrot.Timer = function(game, delay, callback, timesToRepeat, startNow)
{
this.game = game;
this.time = this.game.time;
this.delay = delay;
this.callback = callback;
this.timesToRepeat = timesToRepeat;
this.startNow = startNow;
if (this.delay === undefined) { this.delay = 0; }
if (this.callback === undefined) { this.callback = function(){}; }
if (this.timesToRepeat === undefined) { this.timesToRepeat = 0; }
if (this.startNow === undefined) { this.startNow = true; }
this.startTime = this.time.now;
this.endTime = this.startTime + this.delay;
this.timesExecuted = 0;
this.isRunning = true;
if (this.startNow === false)
{
this.isRunning = false
}
// Add it to {@link Carrot.Time} for managing.
this.time.timers.push(this);
return this;
};
Carrot.Timer.prototype =
{
/**
* Starts the timer
* @method Carrot.Timer#start
*/
start()
{
this.isRunning = true;
},
/**
* Pauses the timer.
* @method Carrot.Timer#pause
*/
stop()
{
this.isRunning = false;
},
/**
* Resumes the timer.
* @method Carrot.Timer#pause
*/
resume()
{
this.isRunning = true;
},
/**
* Destroys the timer and removes it from {@link @Carrot.Time}.
* @method Carrot.Timer#destroy
*/
destroy()
{
this.time.timers.splice(this.time.timers.indexOf(this), 1);
},
/**
* The internal update loop of the timed event. It is managed automatically by {@link Carrot.Time}.
* @method Carrot.Timer#_update
* @private
*/
_update()
{
if (this.isRunning)
{
if (this.time.now >= this.endTime)
{
this.callback();
//console.log("time passed since start: ", this.time.now - this.startTime)
this.timesExecuted += 1;
// If desired repeats are reached, stop the timer.
if (this.timesToRepeat !== -1
&& this.timesExecuted > this.timesToRepeat)
{
this.destroy();
}
// If not, repeat.
else
{
this.endTime = this.time.now + this.delay;
}
}
}
}
};
Carrot.Timer.prototype.constructor = Carrot.Timer;
/**
* The Math object offers various standard math functions like measuring a distance or angles.
*
* @class Carrot.Math
* @param {Carrot.Game} game - The core game object.
* @static
*/
Carrot.Math = function(game)
{
this.game = game;
return this;
};
Carrot.Math.prototype =
{
/**
* PI.
* @property {integer} Carrot.Math#PI
* @type {integer}
*/
PI: Math.PI,
/**
* Twice PI.
* @property {integer} Carrot.Math#PI2
* @type {integer}
*/
PI2: Math.PI * 2,
/**
* Degrees to Radians factor.
* @property {integer} Carrot.Math#DEG_TO_RAD
*/
DEG_TO_RAD: Math.PI / 180,
/**
* Degrees to Radians factor.
* @property {integer} Carrot.Math#RAD_TO_DEG
*/
RAD_TO_DEG: 180 / Math.PI,
/**
* Converts degrees to radians.
*
* @method Carrot.Math#degToRad
* @param {integer} degrees - Angle in degrees.
* @return {float} Angle in radians.
*/
degToRad(degrees)
{
return degrees * this.DEG_TO_RAD;
},
/**
* Converts radians to degrees.
*
* @method Carrot.Math#radToDeg
* @param {integer} radians - Angle in radians.
* @return {integer} Angle in degrees.
*/
radToDeg(radians)
{
return radians * this.RAD_TO_DEG;
},
/**
* Returns an integer between (including) min and (including) max
*
* @method Carrot.Math#integerInRange
* @param {integer} min - Min.
* @param {integer} max - Max.
* @return {integer}
*/
integerInRange(min, max)
{
return Math.floor(Math.random() * (max - min + 1)) + min;
},
/**
* Calculates the angle between two vectors in degrees.
*
* @method Carrot.Math#angleBetweenPoints
* @param {integer} x1 - x1
* @param {integer} y1 - x1
* @param {integer} x2 - x2
* @param {integer} y2 - y2
* @return {integer}
*/
angleBetweenPoints(x1, y1, x2, y2)
{
return Math.atan2(y2 - y1, x2 - x1);
},
/**
* Calculates the angle between two entities in degrees. Both must have x / y coordinates.
*
* @method Carrot.Math#angleBetween
* @param {Carrot.Sprite | object} a - The first entity.
* @param {Carrot.Sprite | object} b - The second entity.
* @return {floar}
*/
angleBetween(a, b)
{
return Math.atan2(b.y - a.y, b.x - a.x);
},
/**
* Calculates the distance between two vectors in pixels.
*
* @method Carrot.Math#distanceBetweenPoints
* @param {integer} x1 - x1
* @param {integer} y1 - x1
* @param {integer} x2 - x2
* @param {integer} y2 - y2
* @return {floar}
*/
distanceBetweenPoints(x1, y1, x2, y2)
{
return Math.hypot(x2 - x1, y2 - y1);
},
/**
* Calculates the distance between two entities. Both must have x / y coordinates.
*
* @method Carrot.Math#distanceBetween
* @param {Carrot.Sprite | object} a - The first entity.
* @param {Carrot.Sprite | object} b - The second entity.
* @return {float}
*/
distanceBetween(a, b)
{
return Math.hypot(b.x - a.x, b.y - a.y);
}
};
Carrot.Math.prototype.constructor = Carrot.Math;
/**
* The Sound Manager offers audio functions.
*
* @class Carrot.SoundManager
* @constructor
* @param {Carrot.Game} game - The core game object.
*/
Carrot.SoundManager = function(game)
{
this.game = game;
return this;
};
Carrot.SoundManager.prototype =
{
/**
* Plays a sound. If the sound is already playing, restart it.
*
* @method Carrot.SoundManager#play
*/
play(key, volume, loop)
{
let file = this.game.cache.sounds[key];
if (file)
{
// Music?
if (loop !== undefined)
{
file.loop = loop;
}
file.volume = volume;
if (! file.paused)
{
file.pause();
file.currentTime = 0;
file.play();
}
else
{
file.play();
}
}
},
/**
* Stops a sound.
*
* @method Carrot.SoundManager#stop
*/
stop(key)
{
let file = this.game.cache.sounds[key];
if (file)
{
if (! file.paused)
{
file.loop = false;
file.currentTime = 0;
file.stop();
}
}
},
/**
* Resumes a sound.
*
* @method Carrot.SoundManager#resume
*/
resume(key)
{
let file = this.game.cache.sounds[key];
if (file)
{
if (file.paused)
{
file.loop = false;
file.currentTime = 0;
file.stop();
}
}
},
/**
* Pauses a sound.
*
* @method Carrot.SoundManager#resume
*/
pause(key)
{
let file = this.game.cache.sounds[key];
if (file)
{
if (! file.paused)
{
file.pause();
}
}
},
/**
* Plays a sound and repeats it forever.
*
* @method Carrot.SoundManager#loop
*/
loop(key, volume)
{
this.play(key, volume, true);
},
/**
* Changes the volume of a sound.
*
* @method Carrot.SoundManager#volume
*/
volume(key, volume)
{
let file = this.game.cache.sounds[key];
if (file)
{
file.volume = volume;
}
},
};
Carrot.SoundManager.prototype.constructor = Carrot.SoundManager;
/**
* The Physics object offers physics related functions like collision detection.
*
* @class Carrot.Physics
* @constructor
* @param {Carrot.Game} game - The core game object.
*/
Carrot.Physics = function(game)
{
this.game = game;
this.time = this.game.time;
return this;
}
Carrot.Physics.prototype =
{
/**
* Enables physics on a sprite by adding a physics body to it.
*
* @method Carrot.Physics#enable
* @param {Carrot.Sprite} entity - The sprite to get its physics enabled.
*/
enable(entity)
{
if (entity.body === null)
{
entity.body = new Carrot.Physics.Body(entity);
}
},
/**
* Disables physics on a sprite by setting its body to null.
*
* @method Carrot.Physics#disable
* @param {Carrot.Sprite} entity - The sprite to get its physics disabled.
*/
disable(entity)
{
if (entity.body !== null)
{
entity.body = null;
}
},
/**
* Checks for collision between entities which can be sprites or groups.
*
* @method Carrot.Physics#collide
* @param {Carrot.Group | Carrot.Sprite} entity1
* @param {Carrot.Group | Carrot.Sprite} entity2
* @param {function} [callback=null] - The function that shall be executed when the collision happens.
* @return {boolean} If a collision was detected.
*/
collide(entity1, entity2, callback)
{
callback = callback || null;
if (entity1.type === Carrot.SPRITE)
{
if (entity2.type === Carrot.SPRITE)
{
this.collideSpriteVsSprite(entity1, entity2, callback);
}
else if (entity2.type === Carrot.GROUP)
{
this.collideSpriteVsGroup(entity1, entity2, callback);
}
}
else if (entity1.type === Carrot.GROUP)
{
if (entity2.type === Carrot.SPRITE)
{
this.collideSpriteVsGroup(entity2, entity1, callback);
}
else if (entity2.type === Carrot.GROUP)
{
this.collideGroupVsGroup(entity1, entity2, callback);
}
}
},
/**
* Checks for overlaps between entities which can be sprites or groups.
*
* @method Carrot.Physics#overlap
* @param {Carrot.Group | Carrot.Sprite} entity1
* @param {Carrot.Group | Carrot.Sprite} entity2
* @param {function} [callback=null] - The function that shall be executed when the overlap happens.
* @return {boolean} If an overlap was detected.
*/
overlap(entity1, entity2, callback)
{
callback = callback || null;
if (entity1.type === Carrot.SPRITE)
{
if (entity2.type === Carrot.SPRITE)
{
this.collideSpriteVsSprite(entity1, entity2, callback, true);
}
else if (entity2.type === Carrot.GROUP)
{
this.collideSpriteVsGroup(entity1, entity2, callback, true);
}
}
else if (entity1.type === Carrot.GROUP)
{
if (entity2.type === Carrot.SPRITE)
{
this.collideSpriteVsGroup(entity2, entity1, callback, true);
}
else if (entity2.type === Carrot.GROUP)
{
this.collideGroupVsGroup(entity1, entity2, callback, true);
}
}
},
/**
* Checks for collision between two groups. Use {Carrot.Physics#collide} or {Carrot.Physics#overlap} instead.
*
* @method Carrot.Physics#collideGroupVsGroup
* @param {Carrot.Group} group1
* @param {Carrot.Group} group2
* @param {function} [callback=null] - The function that shall be executed when the collision or overlap happens.
* @param {boolean} [overlapOnly=false] - Defines if the function shall only check for overlap and disable physics.
* @return {boolean} If a collision was detected.
* @private
*/
collideGroupVsGroup(group1, group2, callback, overlapOnly)
{
callback = callback || null;
overlapOnly = overlapOnly || false;
for (let i = 0; i < group1.children.length; i++)
{
let a = group1.children[i];
for (let j = 0; j < group2.children.length; j++)
{
let b = group2.children[j];
if (b !== a)
{
if (a.body.isRectangle === true)
{
if (b.body.isRectangle === true)
{
if (this.intersectRectangleVsRectangle(a, b))
{
// If a callback was passed, call it.
if (callback !== null)
{
callback(a, b);
}
// Apply collision, if desired.
if (overlapOnly === false)
{
}
}
}
else if (b.body.isCircle === true)
{
if (this.intersectRectangleVsCircle(a, b))
{
// If a callback was passed, call it.
if (callback !== null)
{
callback(a, b);
}
// Apply collision, if desired.
if (overlapOnly === false)
{
}
}
}
}
else if (a.body.isCircle === true)
{
if (b.body.isCircle === true)
{
if (this.intersectCircleVsCircle(a, b))
{
// If a callback was passed, call it.
if (callback !== null)
{
callback(a, b);
}
}
}
else if (a.body.isRectangle === true)
{
if (this.intersectRectangleVsCircle(b, a))
{
// If a callback was passed, call it.
if (callback !== null)
{
callback(a, b);
}
// Apply collision, if desired.
if (overlapOnly === false)
{
}
}
}
}
}
}
}
},
/**
* Checks for collision between a sprite and a group. Use {Carrot.Physics#collide} or {Carrot.Physics#overlap} instead.
*
* @method Carrot.Physics#collideSpriteVsGroup
* @param {Carrot.Sprite} sprite
* @param {Carrot.Group} group
* @param {function} [callback=null] - The function that shall be executed when the collision or overlap happens.
* @param {boolean} [overlapOnly=false] - Defines if the function shall only check for overlap and disable physics.
* @return {boolean} If a collision was detected.
* @private
*/
collideSpriteVsGroup(sprite, group, callback, overlapOnly)
{
callback = callback || null;
overlapOnly = overlapOnly || false;
let a = sprite;
for (let i = 0; i < group.children.length; i++)
{
let b = group.children[i];
if (b !== a)
{
if (a.body.isRectangle === true)
{
if (b.body.isRectangle === true)
{
if (this.intersectRectangleVsRectangle(a, b))
{
// If a callback was passed, call it.
if (callback !== null)
{
callback(a, b);
}
// Apply collision, if desired.
if (overlapOnly === false)
{
}
}
}
else if (b.body.isCircle === true)
{
if (this.intersectRectangleVsCircle(a, b))
{
// If a callback was passed, call it.
if (callback !== null)
{
callback(a, b);
}
// Apply collision, if desired.
if (overlapOnly === false)
{
}
}
}
}
else if (a.body.isCircle === true)
{
if (b.body.isCircle === true)
{
if (this.intersectCircleVsCircle(a, b))
{
// If a callback was passed, call it.
if (callback !== null)
{
callback(a, b);
}
}
}
else if (a.body.isRectangle === true)
{
if (this.intersectRectangleVsCircle(b, a))
{
// If a callback was passed, call it.
if (callback !== null)
{
callback(a, b);
}
// Apply collision, if desired.
if (overlapOnly === false)
{
}
}
}
}
}
}
},
/**
* Checks for collision between two sprites. Use {Carrot.Physics#collide} or {Carrot.Physics#overlap} instead.
*
* @method Carrot.Physics#collideSpriteVsSprite
* @param {Carrot.Sprite} sprite1
* @param {Carrot.Sprite} sprite2
* @param {function} [callback=null] - The function that shall be executed when the collision or overlap happens.
* @param {boolean} [overlapOnly=false] - Defines if the function shall only check for overlap and disable physics.
* @return {boolean} If a collision was detected.
* @private
*/
collideSpriteVsSprite(sprite1, sprite2, callback, overlapOnly)
{
callback = callback || null;
overlapOnly = overlapOnly || false;
let a = sprite1;
let b = sprite2;
if (b !== a)
{
if (a.body.isRectangle === true)
{
if (b.body.isRectangle === true)
{
if (this.intersectRectangleVsRectangle(a, b))
{
// If a callback was passed, call it.
if (callback !== null)
{
callback(a, b);
}
// Apply collision, if desired.
if (overlapOnly === false)
{
}
}
}
else if (b.body.isCircle === true)
{
if (this.intersectRectangleVsCircle(a, b))
{
// If a callback was passed, call it.
if (callback !== null)
{
callback(a, b);
}
// Apply collision, if desired.
if (overlapOnly === false)
{
}
}
}
}
else if (a.body.isCircle === true)
{
if (b.body.isCircle === true)
{
if (this.intersectCircleVsCircle(a, b))
{
// If a callback was passed, call it.
if (callback !== null)
{
callback(a, b);
}
}
}
else if (a.body.isRectangle === true)
{
if (this.intersectRectangleVsCircle(b, a))
{
// If a callback was passed, call it.
if (callback !== null)
{
callback(a, b);
}
// Apply collision, if desired.
if (overlapOnly === false)
{
}
}
}
}
}
},
/**
* Checks for intersection between two rectangles.
*
* @method Carrot.Physics#intersectRectangleVsRectangle
* @param {Carrot.Rectangle} a
* @param {Carrot.Rectangle} b
* @return {boolean} If an intersection was detected.
* @private
*/
intersectRectangleVsRectangle(a, b)
{
if (a.left > b.right) { return false; }
if (a.right < b.left) { return false; }
if (a.top > b.bottom) { return false; }
if (a.bottom < b.top) { return false; }
return true;
/*
return (a.left < b.right
&& a.right > b.left
&& a.top < b.bottom
&& a.bottom > b.top);
*/
},
/**
* Checks for intersection between two circles.
*
* @method Carrot.Physics#intersectCircleVsCircle
* @param {Carrot.Circle} a
* @param {Carrot.Circle} b
* @return {boolean} If an intersection was detected.
* @private
*/
intersectCircleVsCircle(a, b)
{
let x = a.x - b.x;
let y = a.y - b.y;
let r = (a.width * 0.5) + (b.width * 0.5);
return (x * x) + (y * y) < (r * r);
},
/**
* Checks for intersection between a rectangle and a circle.
*
* @method Carrot.Physics#intersectRectangleVsCircle
* @param {Carrot.Rectangle} a
* @param {Carrot.Circle} b
* @return {boolean} If an intersection was detected.
* @private
*/
intersectRectangleVsCircle(a, b)
{
let cr = b.width * 0.5;
let cx = b.width - cr;
let cy = b.height - cr;
let rw = a.width;
let rh = a.height;
let rx = rw - (rw * 0.5);
let ry = rh - (rh * 0.5);
let dx = cx - Math.max(rx, Math.min(cx, ry + rw));
let dy = cy - Math.max(ry, Math.min(cy, ry + rh));
return (dx * dx + dy * dy) < (cr * cr);
}
};
Carrot.Physics.prototype.constructor = Carrot.Physics;
/**
* Creates a physics body.
*
* @class Carrot.Physics.Body
* @constructor
* @param {Carrot.Game} game - The core game object.
* @param {integer} x - X position relative to the sprite.
* @param {integer} y - Y position relative the sprite.
*/
Carrot.Physics.Body = function(parent, x, y)
{
this.parent = parent;
this.x = x || 0;
this.y = y || 0;
this.velocity = new Carrot.Point(0, 0);
this.bounce = new Carrot.Point(0, 0);
this.drag = new Carrot.Point(0, 0);
this.gravity = new Carrot.Point(0, 0);
this.acceleration = new Carrot.Point(0, 0);
this.maxVelocity = new Carrot.Point(10000, 10000);
this.angularVelocity = 0;
this.angularDrag = 0;
this.allowBounce = true;
this.allowDrag = true;
this.allowGravity = true;
this.allowAcceleration = true;
this.allowAngularDrag = true;
this.touching =
{
none: true,
left: false,
right: false,
top: false,
bottom: false
};
this.checkCollision =
{
none: false,
left: true,
right: true,
top: true,
bottom: true
};
this.collideWorldBounds = false;
this.isRectangle = true;
this.isCircle = false;
this.enabled = true;
return this;
};
Carrot.Physics.Body.prototype =
{
/**
* Changes the shape of the body and its parent sprite into a circle.
*
* @method Carrot.Physics#setCircle
*/
setCircle()
{
this.isRectangle = false;
this.isCircle = true;
this.sprite.style.borderRadius = (this.sprite.width * 0.5) + "px";
},
/**
* Changes the shape of the body and its parent sprite into a rectangle.
*
* @method Carrot.Physics#setRectangle
*/
setRectangle()
{
this.isRectangle = true;
this.isCircle = false;
this.sprite.image.style.borderRadius = "";
}
};
Carrot.Physics.Body.prototype.constructor = Carrot.Physics.Body;
/**
* This class stores images, sounds and custom CSS classes.
*
* @class Carrot.Cache
* @constructor
* @param {Carrot.Game} game - The core game object.
*/
Carrot.Cache = function(game)
{
this.game = game;
this.images = {};
this.sounds = {};
this.json = {};
this.csv = {};
this.classes = {};
};
Carrot.Cache.prototype =
{
};
Carrot.Cache.prototype.constructor = Carrot.Cache;
/**
* This class allows for creating custom stylesheets for game objects.
*
* @class Carrot.Entity
* @constructor
* @param {Carrot.Game} game - The core game object.
*/
Carrot.Entity = function(game, name, css)
{
this.game = game;
this.name = name;
this.css = css || {};
return this;
};
Carrot.Entity.prototype =
{
/**
* Adds a new CSS rule to the class.
*
* @method Carrot.Entity#add
* @param {string} name - The unique name of the class.
* @param {object} [style=null] - The style of the class.
*/
add(name, style)
{
if (name)
{
style = style || null;
if (style !== null)
{
this.game.cache.classes[name] = style;
}
else
{
this.game.cache.classes[name] = {};
}
}
},
/**
* Removes an existing CSS rule from the class.
*
* @method Carrot.Entity#remove
* @param {string} name - The unique name of the class.
*/
remove(name)
{
delete this.game.cache.classes[name];
},
/**
* Edits an existing CSS rule of the class.
*
* @method Carrot.Entity#add
* @param {string} name - The unique name of the class.
* @param {string} key
* @param {string} value
*/
edit(name, key, value)
{
let customClass = this.game.cache.classes[name];
if (customClass)
{
customClass[key] = value;
}
},
};
Carrot.Entity.prototype.constructor = Carrot.Entity;
/**
* Creates a point.
*
* @class Carrot.Point
* @constructor
* @param {integer} [x=0]
* @param {integer} [y=x]
*/
Carrot.Point = function(x, y)
{
x = x || 0;
y = y || x;
this.setTo(x, y);
return this;
};
Carrot.Point.prototype =
{
/**
* Sets the point up.
*
* @method Carrot.Point#setTo
* @param {integer} [x=0]
* @param {integer} [y=x]
*/
setTo(x, y)
{
this.x = x || 0;
this.y = y || 0;
}
};
Carrot.Point.prototype.constructor = Carrot.Point;
/**
* Creates a line with a start and an end point.
*
* @class Carrot.Line
* @constructor
* @param {integer} [x1=0]
* @param {integer} [y1=0]
* @param {integer} [x2=0]
* @param {integer} [y2=0]
*/
Carrot.Line = function(x1, y1, x2, y2)
{
x1 = x1 || 0;
y1 = y1 || 0;
x2 = x2 || 0;
y2 = y2 || 0;
this.start = null;
this.end = null;
this.setTo(x1, y1, x2, y2);
return this;
};
Carrot.Line.prototype =
{
/**
* Sets the line up.
*
* @method Carrot.Line#setTo
* @param {integer} [x=0]
* @param {integer} [y=x]
*/
setTo(x1, y1, x2, y2)
{
this.start = new Carrot.Point(x1, y1);
this.end = new Carrot.Point(x2, y2);
}
};
Carrot.Line.prototype.constructor = Carrot.Line;
/**
* Creates a rectangle.
*
* @class Carrot.Rectangle
* @constructor
* @param {integer} [x=0]
* @param {integer} [y=0]
* @param {integer} [width=0]
* @param {integer} [height=0]
*/
Carrot.Rectangle = function(x, y, width, height)
{
x = x || 0;
y = y || 0;
width = width || 0;
height = height || 0;
this.setTo(x, y, width, height);
return this;
};
Carrot.Rectangle.prototype =
{
/**
* Sets the rectangle up.
*
* @method Carrot.Rectangle#setTo
* @param {integer} [x=0]
* @param {integer} [y=x]
* @param {integer} [width=0]
* @param {integer} [height=0]
*/
setTo(x, y, width, height)
{
this.x = x || 0;
this.y = y || 0;
this.width = width || 0;
this.height = height || 0;
}
};
Carrot.Rectangle.prototype.constructor = Carrot.Rectangle;
/**
* Creates a circle.
*
* @class Carrot.Circle
* @constructor
* @param {integer} [x=0]
* @param {integer} [y=x]
* @param {diameter} [diameter=0]
*/
Carrot.Circle = function(x, y, diameter)
{
x = x || 0;
y = y || 0;
diameter = diameter || 0;
this.setTo(x, y, diameter);
return this;
};
Carrot.Circle.prototype =
{
/**
* Sets the circle up.
*
* @method Carrot.Circle#setTo
* @param {integer} [x=0]
* @param {integer} [y=x]
* @param {diameter} [diameter=0]
*/
setTo(x, y, diameter)
{
this.x = x || 0;
this.y = y || x;
this.diameter = diameter || 0;
this.radius = diameter * 0.5;
}
};
Carrot.Circle.prototype.constructor = Carrot.Circle;
/**
* This claass offers the possibility of creating sprites and groups.
*
* @class Carrot.ObjectFactory
* @constructor
* @param {Carrot.Game} game - The core game object.
*/
Carrot.ObjectFactory = function(game)
{
this.game = game;
return this;
};
Carrot.ObjectFactory.prototype =
{
/**
* Creates a sprite.
*
* @method Carrot.ObjectFactory#sprite
* @param {integer} [x=0] - X position
* @param {integer} [y=0] - Y position
* @param {string} [key=null] - The key (name) of the image. If null, the sprite will be a green rectangle.
* @param {integer} [frame=0] - The initial frame to show. Only for spritesheets.
* @param {boolean} [active=true] - The initial frame to show. Only for spritesheets.
* @return {Carrot.Sprite}
*/
sprite(x, y, key, frame, active)
{
if (x === undefined) x = 0;
if (y === undefined) y = 0;
if (key === undefined) key = null;
if (frame === undefined) frame = 0;
if (active === undefined) active = true;
return new Carrot.Sprite(this.game, x, y, key, frame, active);
},
/**
* Creates a group.
*
* @method Carrot.ObjectFactory#group
*/
group()
{
return new Carrot.Group(this.game);
},
/**
* Creates a timed event.
*
* @method Carrot.ObjectFactory#timer
*/
timer(delay, callback, timesToRepeat, startNow)
{
return new Carrot.Timer(this.game, delay, callback, timesToRepeat, startNow);
}
};
Carrot.ObjectFactory.prototype.constructor = Carrot.ObjectFactory;
/**
* A very basic asset loader without progess functions. Yet.
*
* @class Carrot.AssetLoader
* @constructor
* @param {Carrot.Game} game - The core game object.
*/
Carrot.AssetLoader = function(game)
{
this.game = game;
this.utils = this.game.utils;
this.filesLoaded = 0;
this.filesToLoad = 0;
return this;
};
Carrot.AssetLoader.prototype =
{
/**
* Loads a simple image.
*
* @method Carrot.AssetLoader#image
* @param {string} key - The path (url) to the image.
* @param {string} path - The key (name) for the image.
*/
image(key, path)
{
let that = this;
let img = document.createElement('img');
img.src = path;
img.onload = function(event)
{
img.onload = null;
img.onerror = null;
that.fileLoaded(key);
};
img.onerror = function(event)
{
delete that.game.cache.images[key];
};
this.game.cache.images[key] = img;
this.filesToLoad += 1;
},
/**
* Loads a spritesheet.
*
* @method Carrot.AssetLoader#spritesheet
* @param {string} key - The key (name) to the image.
* @param {string} path - The path (url) to the image.
* @param {integer} [frameWidth=32] - The width of the spritesheet's frames.
* @param {integer} [frameHeight=32] - The height of the spritesheet's frames.
* @param {integer} [frameIndexes=Infinity] - The frames indexes.
*/
spritesheet(key, path, frameWidth, frameHeight, frameIndexes)
{
frameWidth = frameWidth || 32;
frameHeight = frameHeight || 32
frameIndexes = frameIndexes || Infinity;
let that = this;
let img = document.createElement('img');
img.src = path;
img.onload = function(event)
{
// Save frames for spritesheet animation
let frames = [];
let frameFound = 0;
for (let x = 0; x < that.width; x += frameWidth)
{
for (let y = 0; y < that.height; y += frameHeight)
{
frameFound += 1;
if (frameFound === frameIndexes) break;
{
frames.push(
{
x: -x,
y: -y
});
}
}
}
that.game.cache.images[key].frames = frames;
img.onload = null;
img.onerror = null;
that.fileLoaded(key);
};
img.onerror = function(event)
{
delete that.game.cache.images[key];
};
this.game.cache.images[key] = img;
this.game.cache.images[key].frames = [];
this.game.cache.images[key].frameWidth = frameWidth;
this.game.cache.images[key].frameHeight = frameHeight;
this.filesToLoad += 1;
},
/**
* Loads a sound.
*
* @method Carrot.AssetLoader#sound
* @param {string} key - The key (name) for the sound.
* @param {string} path - The path (url) to the sound.
*/
sound(key, path)
{
let that = this;
let sound = document.createElement('audio');
sound.src = path;
sound.oncanplaythrough = function(event)
{
sound.oncanplaythrough = null;
sound.onerror = null;
that.fileLoaded(key);
};
sound.onerror = function(event)
{
delete that.game.cache.sounds[key];
};
this.game.cache.sounds[key] = sound;
this.filesToLoad += 1;
},
/**
* Loads a JSON file.
*
* @method Carrot.AssetLoader#json
* @param {string} key - The key (name) for the JSON file.
* @param {string} path - The path (url) to the JSON file.
*/
json(key, path)
{
let that = this;
let xhr = new XMLHttpRequest();
xhr.onload = function(event)
{
that.game.cache.json[key] = JSON.parse(this.responseText);
that.fileLoaded(key);
}
xhr.onerror = function(event)
{
delete this.game.cache.json[key];
}
xhr.open('GET', window.location.href + "/" + path, true);
xhr.send();
this.game.cache.json[key] = {};
this.filesToLoad += 1;
},
/**
* Loads a CSV file.
*
* @method Carrot.AssetLoader#csv
* @param {string} key - The key (name) for the CSV file.
* @param {string} path - The path (url) to the CSV file.
*/
csv(key, path)
{
let that = this;
let xhr = new XMLHttpRequest();
xhr.onload = function(event)
{
// Convert CSV to JSON.
that.game.cache.csv[key] = that.utils.csvToJson(this.responseText);
that.fileLoaded(key);
}
xhr.onerror = function(event)
{
delete this.game.cache.csv[key];
}
xhr.open('GET', window.location.href + "/" + path, true);
xhr.send();
this.game.cache.csv[key] = {};
this.filesToLoad += 1;
},
/**
* Is called when a file was successfully loaded.
*
* @method Carrot.AssetLoader#fileLoaded
* @param {string} key - The key of the image that was loaded.
* @private
*/
fileLoaded(key)
{
this.filesLoaded += 1;
// Update progress bar, if existing.
if (this.progressBar)
{
this.progressBar.style.width = ((this.filesLoaded / this.filesToLoad) * 100) + "%";
}
this.checkFilesLoaded();
},
/**
* Checks if all files are loaded. If yes, it starts the game. Is automatically managed by {Carrot.AssetLoader};
*
* @method Carrot.AssetLoader#checkFilesLoaded
* @private
*/
checkFilesLoaded()
{
if (this.filesToLoad === this.filesLoaded)
{
if (this.progressBar)
{
// Remove progress bar
this.progressBar.parentNode.parentNode.remove();
delete this.progressBar;
// Reset background color set by progress bar
if (this.game.transparent === false)
{
this.game.background.style.backgroundColor = '#000000';
}
else
{
this.game.background.style.background = "";
}
this.game.start(this.game);
}
}
}
};
Carrot.AssetLoader.prototype.constructor = Carrot.AssetLoader;
/**
* This class handles all keyboard interactions.
*
* @class Carrot.Keyboard
* @constructor
* @param {Carrot.Game} game - The core game object.
*/
Carrot.Keyboard = function(game)
{
this.isDown = {};
this.isPressed = {};
this.isUp = {};
// Internal values
this.game = game;
// Set all buttons to false
for (let i = 0; i < 200; i++)
{
this.isDown[i] = false;
this.isPressed[i] = false;
this.isUp[i] = false;
}
// Add the event listeners for the mouse to the game container
let gameDiv = this.game.parent;
let that = this;
window.addEventListener('keydown', function(event)
{
that.isDown[event.keyCode] = true;
}, false);
window.addEventListener('keypress', function(event)
{
that.isPressed[event.keyCode] = true;
}, false);
window.addEventListener('keyup', function(event)
{
that.isDown[event.keyCode] = false;
that.isPressed[event.keyCode] = false;
that.isUp[event.keyCode] = true;
}, false);
return this;
};
Carrot.Keyboard.prototype =
{
isDown(key)
{
},
isUp(key)
{
}
};
Carrot.Keyboard.prototype.constructor = Carrot.Keyboard;
/**
* This class stores common keyCodes of keyboards, so there is no need to memorize them.
*
* @class Carrot.KeyCode
* @static
*/
Carrot.KeyCode =
{
/** @static */
A: "A".toUpperCase().charCodeAt(0),
/** @static */
B: "B".toUpperCase().charCodeAt(0),
/** @static */
C: "C".toUpperCase().charCodeAt(0),
/** @static */
D: "D".toUpperCase().charCodeAt(0),
/** @static */
E: "E".toUpperCase().charCodeAt(0),
/** @static */
F: "F".toUpperCase().charCodeAt(0),
/** @static */
G: "G".toUpperCase().charCodeAt(0),
/** @static */
H: "H".toUpperCase().charCodeAt(0),
/** @static */
I: "I".toUpperCase().charCodeAt(0),
/** @static */
J: "J".toUpperCase().charCodeAt(0),
/** @static */
K: "K".toUpperCase().charCodeAt(0),
/** @static */
L: "L".toUpperCase().charCodeAt(0),
/** @static */
M: "M".toUpperCase().charCodeAt(0),
/** @static */
N: "N".toUpperCase().charCodeAt(0),
/** @static */
O: "O".toUpperCase().charCodeAt(0),
/** @static */
P: "P".toUpperCase().charCodeAt(0),
/** @static */
Q: "Q".toUpperCase().charCodeAt(0),
/** @static */
R: "R".toUpperCase().charCodeAt(0),
/** @static */
S: "S".toUpperCase().charCodeAt(0),
/** @static */
T: "T".toUpperCase().charCodeAt(0),
/** @static */
U: "U".toUpperCase().charCodeAt(0),
/** @static */
V: "V".toUpperCase().charCodeAt(0),
/** @static */
W: "W".toUpperCase().charCodeAt(0),
/** @static */
X: "X".toUpperCase().charCodeAt(0),
/** @static */
Y: "Y".toUpperCase().charCodeAt(0),
/** @static */
Z: "Z".toUpperCase().charCodeAt(0),
/** @static */
ZERO: "0".charCodeAt(0),
/** @static */
ONE: "1".charCodeAt(0),
/** @static */
TWO: "2".charCodeAt(0),
/** @static */
THREE: "3".charCodeAt(0),
/** @static */
FOUR: "4".charCodeAt(0),
/** @static */
FIVE: "5".charCodeAt(0),
/** @static */
SIX: "6".charCodeAt(0),
/** @static */
SEVEN: "7".charCodeAt(0),
/** @static */
EIGHT: "8".charCodeAt(0),
/** @static */
NINE: "9".charCodeAt(0),
/** @static */
NUMPAD_0: 96,
/** @static */
NUMPAD_1: 97,
/** @static */
NUMPAD_2: 98,
/** @static */
NUMPAD_3: 99,
/** @static */
NUMPAD_4: 100,
/** @static */
NUMPAD_5: 101,
/** @static */
NUMPAD_6: 102,
/** @static */
NUMPAD_7: 103,
/** @static */
NUMPAD_8: 104,
/** @static */
NUMPAD_9: 105,
/** @static */
NUMPAD_MULTIPLY: 106,
/** @static */
NUMPAD_ADD: 107,
/** @static */
NUMPAD_ENTER: 108,
/** @static */
NUMPAD_SUBTRACT: 109,
/** @static */
NUMPAD_DECIMAL: 110,
/** @static */
NUMPAD_DIVIDE: 111,
/** @static */
F1: 112,
/** @static */
F2: 113,
/** @static */
F3: 114,
/** @static */
F4: 115,
/** @static */
F5: 116,
/** @static */
F6: 117,
/** @static */
F7: 118,
/** @static */
F8: 119,
/** @static */
F9: 120,
/** @static */
F10: 121,
/** @static */
F11: 122,
/** @static */
F12: 123,
/** @static */
F13: 124,
/** @static */
F14: 125,
/** @static */
F15: 126,
/** @static */
COLON: 186,
/** @static */
EQUALS: 187,
/** @static */
COMMA: 188,
/** @static */
UNDERSCORE: 189,
/** @static */
PERIOD: 190,
/** @static */
QUESTION_MARK: 191,
/** @static */
TILDE: 192,
/** @static */
OPEN_BRACKET: 219,
/** @static */
BACKWARD_SLASH: 220,
/** @static */
CLOSED_BRACKET: 221,
/** @static */
QUOTES: 222,
/** @static */
BACKSPACE: 8,
/** @static */
TAB: 9,
/** @static */
CLEAR: 12,
/** @static */
ENTER: 13,
/** @static */
SHIFT: 16,
/** @static */
CONTROL: 17,
/** @static */
ALT: 18,
/** @static */
CAPS_LOCK: 20,
/** @static */
ESC: 27,
/** @static */
SPACEBAR: 32,
/** @static */
PAGE_UP: 33,
/** @static */
PAGE_DOWN: 34,
/** @static */
END: 35,
/** @static */
HOME: 36,
/** @static */
LEFT: 37,
/** @static */
UP: 38,
/** @static */
RIGHT: 39,
/** @static */
DOWN: 40,
/** @static */
PLUS: 43,
/** @static */
MINUS: 44,
/** @static */
INSERT: 45,
/** @static */
DELETE: 46,
/** @static */
HELP: 47,
/** @static */
NUM_LOCK: 144
};
// Duplicate Carrot.KeyCode values in Carrot.Keyboard for compatibility
for (let key in Carrot.KeyCode)
{
if (Carrot.KeyCode.hasOwnProperty(key) && !key.match(/[a-z]/))
{
Carrot.Keyboard[key] = Carrot.KeyCode[key];
}
}
/**
* This class stores the hex values of the 140 standard HTML & CSS colors for easy access.
*
* @class Carrot.Color
* @static
*/
Carrot.Color =
{
/** @static */
AliceBlue: "#F0F8FF",
/** @static */
AntiqueWhite: "#FAEBD7",
/** @static */
Aqua: "#00FFFF",
/** @static */
Aquamarine: "#7FFFD4",
/** @static */
Azure: "#F0FFFF",
/** @static */
Beige: "#F5F5DC",
/** @static */
Bisque: "#FFE4C4",
/** @static */
Black: "#000000",
/** @static */
BlanchedAlmond: "#FFEBCD",
/** @static */
Blue: "#0000FF",
/** @static */
BlueViolet: "#8A2BE2",
/** @static */
Brown: "#A52A2A",
/** @static */
BurlyWood: "#DEB887",
/** @static */
CadetBlue: "#5F9EA0",
/** @static */
Chartreuse: "#7FFF00",
/** @static */
Chocolate: "#D2691E",
/** @static */
Coral: "#FF7F50",
/** @static */
CornflowerBlue: "#6495ED",
/** @static */
Cornsilk: "#FFF8DC",
/** @static */
Crimson: "#DC143C",
/** @static */
Cyan: "#00FFFF",
/** @static */
DarkBlue: "#00008B",
/** @static */
DarkCyan: "#008B8B",
/** @static */
DarkGoldenRod: "#B8860B",
/** @static */
DarkGray: "#A9A9A9",
/** @static */
DarkGrey: "#A9A9A9",
/** @static */
DarkGreen: "#006400",
/** @static */
DarkKhaki: "#BDB76B",
/** @static */
DarkMagenta: "#8B008B",
/** @static */
DarkOliveGreen: "#556B2F",
/** @static */
DarkOrange: "#FF8C00",
/** @static */
DarkOrchid: "#9932CC",
/** @static */
DarkRed: "#8B0000",
/** @static */
DarkSalmon: "#E9967A",
/** @static */
DarkSeaGreen: "#8FBC8F",
/** @static */
DarkSlateBlue: "#483D8B",
/** @static */
DarkSlateGray: "#2F4F4F",
/** @static */
DarkSlateGrey: "#2F4F4F",
/** @static */
DarkTurquoise: "#00CED1",
/** @static */
DarkViolet: "#9400D3",
/** @static */
DeepPink: "#FF1493",
/** @static */
DeepSkyBlue: "#00BFFF",
/** @static */
DimGray: "#696969",
/** @static */
DimGrey: "#696969",
/** @static */
DodgerBlue: "#1E90FF",
/** @static */
FireBrick: "#B22222",
/** @static */
FloralWhite: "#FFFAF0",
/** @static */
ForestGreen: "#228B22",
/** @static */
Fuchsia: "#FF00FF",
/** @static */
Gainsboro: "#DCDCDC",
/** @static */
GhostWhite: "#F8F8FF",
/** @static */
Gold: "#FFD700",
/** @static */
GoldenRod: "#DAA520",
/** @static */
Gray: "#808080",
/** @static */
Grey: "#808080",
/** @static */
Green: "#008000",
/** @static */
GreenYellow: "#ADFF2F",
/** @static */
HoneyDew: "#F0FFF0",
/** @static */
HotPink: "#FF69B4",
/** @static */
IndianRed : "#CD5C5C",
/** @static */
Indigo : "#4B0082",
/** @static */
Ivory: "#FFFFF0",
/** @static */
Khaki: "#F0E68C",
/** @static */
Lavender: "#E6E6FA",
/** @static */
LavenderBlush: "#FFF0F5",
/** @static */
LawnGreen: "#7CFC00",
/** @static */
LemonChiffon: "#FFFACD",
/** @static */
LightBlue: "#ADD8E6",
/** @static */
LightCoral: "#F08080",
/** @static */
LightCyan: "#E0FFFF",
/** @static */
LightGoldenRodYellow: "#FAFAD2",
/** @static */
LightGray: "#D3D3D3",
/** @static */
LightGrey: "#D3D3D3",
/** @static */
LightGreen: "#90EE90",
/** @static */
LightPink: "#FFB6C1",
/** @static */
LightSalmon: "#FFA07A",
/** @static */
LightSeaGreen: "#20B2AA",
/** @static */
LightSkyBlue: "#87CEFA",
/** @static */
LightSlateGray: "#778899",
/** @static */
LightSlateGrey: "#778899",
/** @static */
LightSteelBlue: "#B0C4DE",
/** @static */
LightYellow: "#FFFFE0",
/** @static */
Lime: "#00FF00",
/** @static */
LimeGreen: "#32CD32",
/** @static */
Linen: "#FAF0E6",
/** @static */
Magenta: "#FF00FF",
/** @static */
Maroon: "#800000",
/** @static */
MediumAquaMarine: "#66CDAA",
/** @static */
MediumBlue: "#0000CD",
/** @static */
MediumOrchid: "#BA55D3",
/** @static */
MediumPurple: "#9370DB",
/** @static */
MediumSeaGreen: "#3CB371",
/** @static */
MediumSlateBlue: "#7B68EE",
/** @static */
MediumSpringGreen: "#00FA9A",
/** @static */
MediumTurquoise: "#48D1CC",
/** @static */
MediumVioletRed: "#C71585",
/** @static */
MidnightBlue: "#191970",
/** @static */
MintCream: "#F5FFFA",
/** @static */
MistyRose: "#FFE4E1",
/** @static */
Moccasin: "#FFE4B5",
/** @static */
NavajoWhite: "#FFDEAD",
/** @static */
Navy: "#000080",
/** @static */
OldLace: "#FDF5E6",
/** @static */
Olive: "#808000",
/** @static */
OliveDrab: "#6B8E23",
/** @static */
Orange: "#FFA500",
/** @static */
OrangeRed: "#FF4500",
/** @static */
Orchid: "#DA70D6",
/** @static */
PaleGoldenRod: "#EEE8AA",
/** @static */
PaleGreen: "#98FB98",
/** @static */
PaleTurquoise: "#AFEEEE",
/** @static */
PaleVioletRed: "#DB7093",
/** @static */
PapayaWhip: "#FFEFD5",
/** @static */
PeachPuff: "#FFDAB9",
/** @static */
Peru: "#CD853F",
/** @static */
Pink: "#FFC0CB",
/** @static */
Plum: "#DDA0DD",
/** @static */
PowderBlue: "#B0E0E6",
/** @static */
Purple: "#800080",
/** @static */
RebeccaPurple: "#663399",
/** @static */
Red: "#FF0000",
/** @static */
RosyBrown: "#BC8F8F",
/** @static */
RoyalBlue: "#4169E1",
/** @static */
SaddleBrown: "#8B4513",
/** @static */
Salmon: "#FA8072",
/** @static */
SandyBrown: "#F4A460",
/** @static */
SeaGreen: "#2E8B57",
/** @static */
SeaShell: "#FFF5EE",
/** @static */
Sienna: "#A0522D",
/** @static */
Silver: "#C0C0C0",
/** @static */
SkyBlue: "#87CEEB",
/** @static */
SlateBlue: "#6A5ACD",
/** @static */
SlateGray: "#708090",
/** @static */
SlateGrey: "#708090",
/** @static */
Snow: "#FFFAFA",
/** @static */
SpringGreen: "#00FF7F",
/** @static */
SteelBlue: "#4682B4",
/** @static */
Tan: "#D2B48C",
/** @static */
Teal: "#008080",
/** @static */
Thistle: "#D8BFD8",
/** @static */
Tomato: "#FF6347",
/** @static */
Turquoise: "#40E0D0",
/** @static */
Violet: "#EE82EE",
/** @static */
Wheat: "#F5DEB3",
/** @static */
White: "#FFFFFF",
/** @static */
WhiteSmoke: "#F5F5F5",
/** @static */
Yellow: "#FFFF00",
/** @static */
YellowGreen: "#9ACD32",
};
/**
* This class handles all mouse interactions (but the mouse wheel, yet).
*
* @class Carrot.Mouse
* @constructor
* @param {Carrot.Game} game - The core game object.
*/
Carrot.Mouse = function(game)
{
this.x = 0;
this.y = 0;
this.worldX = 0;
this.worldY = 0;
this.wheel = 0;
this.isDown = [];
this.isUp = [];
// Internal values
this.game = game;
// Add the event listeners for the mouse to the game container
let gameDiv = this.game.background;
let that = this;
gameDiv.addEventListener("mousemove", function(event)
{
that.x = event.offsetX;
that.y = event.offsetY;
that.worldX = that.x + that.game.camera.x;
that.worldY = that.y + that.game.camera.y;
}, true);
gameDiv.addEventListener("mousedown", function(event)
{
that.isDown[event.button] = true;
that.isUp[event.button] = false;
}, true);
gameDiv.addEventListener("mouseup", function(event)
{
that.isDown[event.button] = false;
that.isUp[event.button] = true;
}, true);
return this;
}
/** @static */
Carrot.Mouse.LEFT_BUTTON = 0;
/** @static */
Carrot.Mouse.MIDDLE_BUTTON = 1;
/** @static */
Carrot.Mouse.RIGHT_BUTTON = 2;
// The stuff below is not working yet
Carrot.Mouse.prototype =
{
onMouseMove(event)
{
this.x = event.offsetX;
this.y = event.offsetY;
},
onMouseDown(event)
{
this.isDown[event.button] = true;
this.isUp[event.button] = false;
},
onMouseUp(event)
{
this.isDown[event.button] = false;
this.isUp[event.button] = true;
}
};
Carrot.Mouse.prototype.constructor = Carrot.Mouse;
/**
* This class handles various event listeners for game objects.
*
* @class Carrot.Events
* @constructor
* @param {Carrot.Sprite | object} entity - The entity the event listeners shall be attached to.
*/
Carrot.Events = function(entity)
{
this.parent = entity;
// Internal values
this.game = entity.game;
return this;
};
// The stuff below is not working yet
Carrot.Events.prototype =
{
/**
* Is fired if the parent object is killed.
*
* @method Carrot.Events#onKilled
* @private
*/
onKilled()
{
},
/**
* Is fired if the parent object is destroyed.
*
* @method Carrot.Events#onKilled
* @private
*/
onDestroyed()
{
},
/**
* Is fired if the parent object is revived.
*
* @method Carrot.Events#onKilled
* @private
*/
onRevived()
{
}
};
Carrot.Events.prototype.constructor = Carrot.Events;
/**
* This class offers various utilities, such as a CSV to JSON converter.
*
* @class Carrot.Utilities
* @constructor
* @param {Carrot.Game} game - The core game object.
*/
Carrot.Utilities = function(game)
{
this.game = game;
return this;
};
// The stuff below is not working yet
Carrot.Utilities.prototype =
{
/**
* Converts CSV into JSON. The CSV file / string is being processed vertically, having the first line defining the properties and every following line defining a new entry.
*
* @method Carrot.Debug#sprite
* @param {string} file - The CSV file to convert. Must be a string.
* @return {object}
*/
csvToJson(file)
{
let csv = {};
// Split up at line breaks
let rows = file.split(/\n|\r/);
// Save properties defined in the first line of the CSV file.
let properties = rows[0].replace(/['"]+/g, ''); // Remove "s if existing.
properties = properties.replace(/\s/g,''); // Remove all white spaces
properties = properties.split(",");
// Get the values of the lines after the first one.
for (let i = 1; i < rows.length; i++)
{
let columns = rows[i].split(",");
let object = columns[0];
object = object.trim(); // Remove spaces at begin and end.
// Check for empty line (the last one mostly)
if (object !== "")
{
// Create property in object.
csv[object] = {};
for (let j = 1; j < properties.length; j++)
{
let property = properties[j];
let value = parseInt(columns[j]);
// If value is a string
if (isNaN(value))
{
// Remove its "s
value = columns[j].replace(/['"]+/g, '');
}
// Store value
csv[object][property] = value;
}
}
}
return csv;
}
};
Carrot.Utilities.prototype.constructor = Carrot.Utilities;
/**
* This class offers methods to debug the game.
*
* @class Carrot.Debug
* @constructor
* @param {Carrot.Game} game - The core game object.
*/
Carrot.Debug = function(game)
{
this.game = game;
return this;
};
Carrot.Debug.prototype =
{
/**
* Shows a debug rectangle of the same size as the sprite.
*
* @method Carrot.Debug#sprite
* @param {Carrot.Sprite} sprite - The sprite to debug.
* @param {string} [color=rgba(0, 255, 0, 0.33)] - The color to tint the sprite width.
* @param {boolean} [border=false] - Defined if the debug div should only consist of a border.
*/
sprite(sprite, color, border)
{
if (sprite === undefined) return;
if (color === undefined) color = "rgba(0, 255, 0, 0.33)";
if (border === undefined) border = false;
if (sprite.image)
{
let debugDiv = sprite.image.getElementsByClassName('debugSprite')[0];
// If the sprite has no debug div, create one.
if (debugDiv === undefined)
{
debugDiv = document.createElement('div');
sprite.image.insertBefore(debugDiv, sprite.image.firstChild);
debugDiv.className = "debugSprite";
debugDiv.style.height = "100%";
if (border)
{
debugDiv.style.border = "1px solid " + color;
}
else
{
debugDiv.style.backgroundColor = color;
}
}
}
},
/**
* Shows detailed debug info about a sprite.
*
* @method Carrot.Debug#sprite
* @param {Carrot.Sprite} sprite - The sprite to debug.
* @param {integer} [x=0] - The x position on screen.
* @param {integer} [y=0] - The y position on screen.
*/
spriteInfo(sprite, x, y)
{
if (sprite === undefined) return;
if (x === undefined) x = 0;
if (y === undefined) y = 0;
let debugDiv = this.game.background.getElementsByClassName('debugSpriteInfo')[0];
if (debugDiv !== undefined)
{
let spriteX = Math.round(sprite.x * 10) / 10;
let spriteY = Math.round(sprite.x * 10) / 10;
let spriteAngle = Math.round(sprite.angle * 10) / 10;
let spriteAnchorX = Math.round(sprite.anchor.x * 10) / 10;
let spriteAnchorY = Math.round(sprite.anchor.y * 10) / 10;
debugDiv.innerHTML = ""
+ "<strong>Sprite:</strong>"
+ "<br>x: " + spriteX + " y: " + spriteY + " angle: " + spriteAngle + " anchor: " + spriteAnchorX + " x " + spriteAnchorY
+ "<br>bounds: { left: " + sprite.left + ", top: " + sprite.top + ", right: " + sprite.right + ", bottom: " + sprite.bottom + " }"
+ "<br>key: \"" + sprite.key + "\" width: " + sprite.width + " height: " + sprite.height + " frame: " + sprite.frame
+ "<br>active: " + sprite.active + " inCamera: " + sprite.inCamera + " visible: " + sprite.visible
}
// If there's no debug div, create one.
else
{
debugDiv = document.createElement('div');
this.game.background.appendChild(debugDiv);
debugDiv.className = "debugSpriteInfo";
debugDiv.style.position = "absolute";
debugDiv.style.left = x + "px";
debugDiv.style.top = y + "px";
debugDiv.style.color = "#ffffff";
debugDiv.style.fontSize = "16px";
debugDiv.style.lineHeight = "20px";
debugDiv.style.textShadow = "1px 1px 0px #111111";
debugDiv.style.userSelect = "none";
debugDiv.style.pointerEvents = "none";
}
},
/**
* Shows detailed debug info about the camera.
*
* @method Carrot.Debug#cameraInfo
* @param {integer} [x=0] - The x position on screen.
* @param {integer} [y=0] - The y position on screen.
*/
cameraInfo(x, y)
{
if (x === undefined) x = 0;
if (y === undefined) y = 0;
let debugDiv = this.game.background.getElementsByClassName('debugCameraInfo')[0];
if (debugDiv !== undefined)
{
let camera = app.game.camera;
let cameraX = Math.round(camera.x * 10) / 10;
let cameraY = Math.round(camera.x * 10) / 10;
debugDiv.innerHTML = ""
+ "<strong>Camera:</strong>"
+ "<br>x: " + cameraX + " y: " + cameraY + " width: " + camera.width + " height: " + camera.height
}
// If there's no debug div, create one.
else
{
debugDiv = document.createElement('div');
this.game.background.appendChild(debugDiv);
debugDiv.className = "debugCameraInfo";
debugDiv.style.position = "absolute";
debugDiv.style.left = x + "px";
debugDiv.style.top = y + "px";
debugDiv.style.color = "#ffffff";
debugDiv.style.fontSize = "16px";
debugDiv.style.lineHeight = "20px";
debugDiv.style.textShadow = "1px 1px 0px #111111";
debugDiv.style.userSelect = "none";
debugDiv.style.pointerEvents = "none";
}
},
/**
* Displays some text.
*
* @method Carrot.Debug#text
* @param {integer} [x=0] - The x position on screen.
* @param {integer} [y=0] - The y position on screen.
*/
text(text, x, y)
{
if (x === undefined) x = 0;
if (y === undefined) y = 0;
let debugDiv = this.game.background.getElementsByClassName('debugText')[0];
if (debugDiv !== undefined)
{
debugDiv.innerHTML = text;
}
// If there's no debug div, create one.
else
{
debugDiv = document.createElement('div');
this.game.background.appendChild(debugDiv);
debugDiv.className = "debugText";
debugDiv.style.position = "absolute";
debugDiv.style.left = x + "px";
debugDiv.style.top = y + "px";
debugDiv.style.color = "#ffffff";
debugDiv.style.fontSize = "16px";
debugDiv.style.lineHeight = "20px";
debugDiv.style.textShadow = "1px 1px 0px #111111";
debugDiv.style.userSelect = "none";
debugDiv.style.pointerEvents = "none";
}
},
test()
{
let div = document.createElement('div');
this.game.background.appendChild(div);
div.style.position = "absolute";
div.style.left = "100px";
div.style.top = "100px";
div.style.width = "100px";
div.style.height = "100px";
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
ctx.strokeStyle = "#ff0000";
ctx.moveTo(0,0);
ctx.lineTo(100, 100);
ctx.lineWidth = 5;
ctx.stroke();
div.style.backgroundImage = "url(" + canvas.toDataURL() + ")";
}
};
Carrot.Debug.prototype.constructor = Carrot.Debug;
// Object tpe IDs
/** @static */
Carrot.SPRITE = 0;
/** @static */
Carrot.TILESPRITE = 1;
/** @static */
Carrot.GROUP = 2;
/******************************************************************************/
/****************************** carrotJS END **********************************/
/******************************************************************************/
/**
* mainloop.js 1.0.3-20170529
*
* @author Isaac Sukin (http://www.isaacsukin.com/)
* @license MIT
*/
!function(a){function b(a){if(x=q(b),!(a<e+l)){for(d+=a-e,e=a,t(a,d),a>i+h&&(f=g*j*1e3/(a-i)+(1-g)*f,i=a,j=0),j++,k=0;d>=c;)if(u(c),d-=c,++k>=240){o=!0;break}v(d/c),w(f,o),o=!1}}var c=1e3/60,d=0,e=0,f=60,g=.9,h=1e3,i=0,j=0,k=0,l=0,m=!1,n=!1,o=!1,p="object"==typeof window?window:a,q=p.requestAnimationFrame||function(){var a=Date.now(),b,d;return function(e){return b=Date.now(),d=Math.max(0,c-(b-a)),a=b+d,setTimeout(function(){e(b+d)},d)}}(),r=p.cancelAnimationFrame||clearTimeout,s=function(){},t=s,u=s,v=s,w=s,x;a.MainLoop={getSimulationTimestep:function(){return c},setSimulationTimestep:function(a){return c=a,this},getFPS:function(){return f},getMaxAllowedFPS:function(){return 1e3/l},setMaxAllowedFPS:function(a){return"undefined"==typeof a&&(a=1/0),0===a?this.stop():l=1e3/a,this},resetFrameDelta:function(){var a=d;return d=0,a},setBegin:function(a){return t=a||t,this},setUpdate:function(a){return u=a||u,this},setDraw:function(a){return v=a||v,this},setEnd:function(a){return w=a||w,this},start:function(){return n||(n=!0,x=q(function(a){v(1),m=!0,e=a,i=a,j=0,x=q(b)})),this},stop:function(){return m=!1,n=!1,r(x),this},isRunning:function(){return m}},"function"==typeof define&&define.amd?define(a.MainLoop):"object"==typeof module&&null!==module&&"object"==typeof module.exports&&(module.exports=a.MainLoop)}(this);