src/application.js
import { Model } from './model';
import { Drawer } from './drawer';
import ticker from './ticker';
import io from './io';
import loader from './loader';
import mouse from './mouse';
/**
* Create a new application
* @example
* let app = new Application({ container: document.getElementById('my-canvas'), width: 500, height: 300 });
*/
export class Application extends Drawer {
/**
* @param {Object} [options]
* @param {HTMLElement} [options.container] - HTML container (default: body element)
* @param {number} [options.width] - canvas width (default: window width)
* @param {number} [options.height] - canvas height (default: window height)
* @param {string} [options.background=#ffffff] - canvas background (default: 0xffffff)
* @param {function} [options.create] - called to load assets
* @param {function} [options.render] - called at every frame
* @param {function} [options.ready] - called when application is ready (need to use loader)
* @example
* const app = new Application({
* width: 500,
* height: 500,
* container: document.getElementById('my-canvas'),
*
* create: function() {
* loader.add('images/player-sprite.png', 'player');
* },
*
* ready: function() {
* this.addLayer(layer, 'home');
* }
* });
*/
constructor(options = {}) {
super();
/**
* Object of options
* @type {Object}
*/
this.options = options;
/**
* List of layers
* @type {Array<Object>}
*/
this.layers = [];
/**
* Current layer rendered
* @type {Object}
*/
this.currentLayer = null;
/** @type {io} */
this.io = io;
/** @type {loader} */
this.loader = loader;
/** @type {mouse} */
this.mouse = mouse;
/**
* Canvas width (default window width)
* @type {number}
*/
this.width = options.width || window.innerWidth;
/**
* Canvas height (default window height)
* @type {number}
*/
this.height = options.height || window.innerHeight;
/**
* Canvas background color
* @type {hex}
*/
this.background = options.background || '#ffffff';
if (typeof options.container != 'object') {
options.container = document.querySelector('body');
}
/** @type {HTMLCanvasElement} */
this.canvas = document.createElement('canvas');
this.canvas.width = this.width;
this.canvas.height = this.height;
this.canvas.style.backgroundColor = this.background;
/** @type {CanvasRenderingContext2D} */
this.context = this.canvas.getContext('2d');
/** @type {CanvasRenderingContext2D} */
this.ctx = this.context;
// Update mouse coordinates
this.canvas.addEventListener('mousemove', event => {
const rect = this.canvas.getBoundingClientRect();
this.mouse.x = event.clientX - rect.left;
this.mouse.y = event.clientY - rect.top;
});
options.container.appendChild(this.canvas);
if (!!options.create) {
options.create.call(this);
this.loader.on('ready', () => this.ready());
}
/** @type {Ticker} */
this.ticker = ticker;
this.ticker.on('step', this.step, this);
this.ticker.on('render', this.render, this);
if (this.loader.ready) {
this.ready();
}
}
/**
* Callback called at every frame to calculate models x,y positions
* @protected
* @param {number} dt - Delta between two frames
*/
step(dt) {
try {
if (!!this.options.step) {
this.options.step.call(this, dt);
}
if (!!this.currentLayer && !!this.currentLayer.step) {
this.currentLayer.step.call(this, dt);
}
} catch(e) {
this.handleError(e);
}
}
/**
* Callback called at every frame to render models
* @protected
* @param {number} dt - Delta between two frames
*/
render(dt) {
try {
if (!!this.options.render) {
this.options.render.call(this, dt);
}
if (!!this.currentLayer && !!this.currentLayer.render) {
this.currentLayer.render.call(this, dt);
}
} catch(e) {
this.handleError(e);
}
}
/**
* Add layer to application
* @param {Object} layer
* @param {function} layer.create - called to create models
* @param {function} layer.step - called at every frame
* @param {function} layer.render - called at every frame
* @param {String} name
* @example
* const layer = {
* create: function() {
* this.player = new Model(this.width / 2, this.height / 2, 100, 100);
* },
*
* step: function() {
* this.player.x += 1;
* if (this.player.x > this.width) {
* this.player.x = -100;
* }
* },
*
* render: function() {
* this.clearLayer();
* this.drawRect(this.player.x, this.player.y, this.player.width, this.player.height);
* }
* };
*
* app.addLayer('scene1', layer);
*/
addLayer(layer, name) {
this.layers.push({ layer, name });
if (this.layers.length === 1) {
this.changeLayer(name);
}
//maybe it's too hight concept for ligth canvas lib
/*for (let prop in this) {
if (this[prop] instanceof Model) {
this[prop].parent = this;
}
}*/
}
/**
* Switch the current layer
* @param {String} name
*/
changeLayer(name) {
let layer = this.layers.find(layer => layer.name === name);
if (!!layer) {
this.currentLayer = layer.layer;
if (!!this.currentLayer.create) {
this.currentLayer.create.call(this);
}
}
}
/**
* Start timer and called application ready function
* @protected
*/
ready() {
this.ticker.start();
if (!!this.options.ready) {
this.options.ready.call(this);
}
}
/**
* @private
* @param {Error} err
*/
handleError(err) {
this.ticker.stop();
console.log(err);
this.drawText(err, 10, 50, '10px', 'sans-serif', 'red');
}
}