window.Luv = (function() {
window.Luv = (function() {
Luv.js has a very minimal (and optional) class system, based on functions and, in some cases, prototypes.
By "optional", I mean that you are not required to "inherit from" Luv objects when
creating a game with Luv.js. You can build your game entities however you want. You
can make them hold references to Luv.js objects when needed.
In other words, the relationship between your objects and Luv.js should
be composition (has_a
) not inheritance (is_a
).
This said, you can certainly use Luv.js class system as a base, if you like it.
That is, unless you are programming some sort of Luv.js plugin. Then you will probably be better off using Luv.js' class system.
extend
is similar to underscore's extend, it
copies into dest
all the methods of the objects passed in as extra arguments.
var extend = function(dest) {
var properties;
for(var i=1; i < arguments.length; i++) {
properties = arguments[i];
for(var property in properties) {
if(properties.hasOwnProperty(property)) {
dest[property] = properties[property];
}
}
}
return dest;
};
remove
deletes the elements from an object, given an array of names of methods to be deleted
var remove = function(dest, names) {
names = Array.isArray(names) ? names : [names];
for(var i=0; i < names.length; i++) { delete dest[names[i]]; }
return dest;
};
create
expects an object, and creates another one which "points to it" through its __proto__
For now, it's just an alias to Object.create
var create = Object.create;
baseMethods
contains the instance methods of a basic object (by default just two: toString
and getClass
)
var baseMethods = {
toString: function() { return 'instance of ' + this.getClass().getName(); }
};
var Base = extend(function() {
return create(baseMethods);
}, {
init : function() {},
getName : function() { return "Base"; },
toString: function() { return "Base"; },
getSuper: function() { return null; },
methods : baseMethods,
include
will extend a class with one or more objects. Acts very similarly to what other languages call
"mixins". Example usage:
var Flyer = { fly: function(){ console.log('flap flap'); } };
var Bee = Luv.Class('Bee', {...});
Bee.include(Flyer);
It returns the class, so a compressed version of the previous example is:
var Flyer = { fly: function(){ console.log('flap flap'); } };
var Bee = Luv.Class('Bee', {...}).include(Flyer);
include : function() {
return extend.apply(this, [this.methods].concat(Array.prototype.slice.call(arguments, 0)));
},
subclass
is used like this:
var Enemy = Luv.Class('Enemy', {
fight: function() { console.log('zap!'); }
shout: function() { console.log('hey!'); }
});
var Ninja = Enemy.subclass('Ninja', {
shout: function() { console.log('...'); }
});
// Luv.js' class system doesn't require "new"
var peter = Ninja();
peter.fight(); // zap!
peter.shout(); // ...
subclass: function(name, methods) {
methods = methods || {};
var superClass = this;
var getName = function(){ return name; };
var newMethods = remove(extend(create(superClass.methods), methods), 'init');
var newClass = extend(function() {
var instance = create(newMethods);
newClass.init.apply(instance, arguments);
return instance;
},
superClass,
methods, {
getName : getName,
toString: getName,
getSuper: function(){ return superClass; },
methods : newMethods
});
newMethods.getClass = function() { return newClass; };
return newClass;
}
});
baseMethods.getClass = function() { return Base; };
var Luv = Base.subclass('Luv', {
Luv expects a single options
parameter (see initializeOptions
for a list of accepted options).
and returns a game.
The recommended name for the variable to store the game is luv
, but you are free to choose any other.
var luv = Luv({...});
// options omitted, see below for details
The game will not start until you execute luv.run()
(assuming that your game variable name is luv
).
var luv = Luv({...});
... // more code ommited, see LuvProto below for details
luv.run();
If you have initialized your game completely with options, you could just run it straight away, without storing it into a variable:
Luv({...}).run();
The options
param is optional, so you can start with an empty call to Luv
, personalize the game variable
however you want, and then call run:
var luv = Luv();
... // do stuff with luv, i.e. define luv.update and luv.draw
luv.run(); // start the game
init: function(options) {
options = initializeOptions(options);
var luv = this;
luv.el
contains a reference to the specified DOM element representing the "Main game canvas".
If no element was specified via the options
parameter, then a new DOM element will be created
and inserted into the document's body.
luv.el = options.el;
make el focus-able
luv.el.tabIndex = 1;
"load update draw run onResize onBlur onFocus".split(" ").forEach(function(name) {
if(options[name]) { luv[name] = options[name]; }
});
Initialize all the game submodules (see their docs for more info about each one)
luv.media = Luv.Media();
luv.timer = Luv.Timer();
luv.keyboard = Luv.Keyboard(luv.el);
luv.mouse = Luv.Mouse(luv.el);
luv.touch = Luv.Touch(luv.el);
luv.audio = Luv.Audio(luv.media);
luv.graphics = Luv.Graphics(luv.el, luv.media);
luv.canvas = Luv.Graphics.Canvas(luv.el);
Attach listeners to the window, if the game is in fullWindow mode, to resize the canvas accordingly
if(options.fullWindow) {
var resize = function() {
luv.canvas.setDimensions(window.innerWidth, window.innerHeight);
luv.onResize(window.innerWidth, window.innerHeight);
};
window.addEventListener('resize', resize, false);
window.addEventListener('orientationChange', resize, false);
luv.el.focus();
}
Attach onBlur/onFocus
luv.el.addEventListener('blur', function() { luv.onBlur(); });
luv.el.addEventListener('focus', function() { luv.onFocus(); });
},
Use the load
function to start loading up resources:
var image;
var luv = Luv();
luv.load = function() {
image = luv.media.Image('cat.png');
};
As al alternative, you can override it directly on the options parameter. Note that in that case, you must use
this
instead of luv
inside the function:
var image;
var luv = Luv({
load: function() {
// notice the usage of this.media instead of luv.media
image = this.media.Image('cat.png');
};
});
load
will be called once at the beginning of the first game cycle, and then never again (see the run
function below for details). By default, it does nothing (it is an empty function).
load : function(){},
Use the draw function to draw everything on the canvas. For example:
var luv = Luv();
luv.draw = function() {
luv.graphics.print("hello", 100, 200);
};
Alternative syntax, passed as an option:
var luv = Luv({
draw: function() {
// this.graphics instead of luv.graphics
this.graphics.print("hello", 100, 200);
}
});
draw
is called once per frame, after the screen has been erased. See the run
function below for details.
draw : function(){},
Use the update
function to update your game objects/variables between frames.
Note that update
has a parameter dt
, which is the time that has passed since the last frame.
You should use dt to update your entity positions according to their velocity/movement – Don't assume that
the time between frames stays constant.
var player = { x: 0, y: 0 };
var luv = Luv();
luv.update = function(dt) {
// Player moves to the right 10 pixels per second
player.x += dt * 10;
};
Alternative syntax, passed as an option:
var player = { x: 0, y: 0 };
var luv = Luv({
update: function(dt) {
// Player moves to the right 10 pixels per second
player.x += dt * 10;
};
});
As with all other luv methods, if you choose this syntax you must use this
instead of luv
, since
luv
is still not defined.
update
will be invoked once per frame (see run
below) and is empty by default (it updates nothing).
update: function(dt){},
The run
function provides a default game loop. It is usually a good default, and you rarely will need to
change it. But you could, if you so desired, the same way you can change load
, update
and draw
(as a
field of the luv
variable or as an option).
run : function(){
var luv = this;
luv.load(); // luv.run execute luv.load just once, at the beginning
var loop = function() {
The first thing we do is updating the timer with the new frame
luv.timer.nativeUpdate(luv.el);
We obtain dt (the difference between previous and this frame's timestamp, in seconds) and pass it to luv.update
var dt = luv.timer.getDeltaTime();
luv.update(dt); // Execute luv.update(dt) once per frame
luv.canvas.clear(); // And clear everything
luv.draw();
This enqueues another call to the loop function in the next available frame
luv.timer.nextFrame(loop);
};
Once the loop function is defined, we call it once, so the cycle begins
luv.timer.nextFrame(loop);
},
onResize
gets called when fullWindow
is active and the window is resized. It can be used to
control game resizings, i.e. recalculate your camera's viewports. By default, it does nothing.
onResize : function(newWidth, newHeight) {},
overridable callback which is called when the main game element loses focus. useful for things like pausing a game when the user clicks outside of it Does nothing by default
onBlur : function() {},
overridable callback called when the main game element gains focus mainly useful for "undoing" the actions done in onBlur, for example unpausing the game
onFocus : function() {}
});
Creates classes; takes two parameters: the class name and an object containing instance methods
For inheritance, do
Luv.Class = function(name, methods) {
return Base.subclass(name, methods);
};
Luv.Base = Base;
Luv.extend = extend;
var initializeOptions = function(options) {
Accepted options:
el
: A canvas DOM element to be usedid
: A canvas DOM id to be used (Ignored if el
is provided)width
: Sets the width of the canvas, in pixelsheight
: Sets the height of the canvas, in pixelsfullWindow
: If set to true, the game canvas will ocuppy the whole window, and auto-adjust (off by default)load
: A load function (see above for details)update
: A update function (see above for details)draw
: A draw function (see above for details)run
: A run function (see above for details)onResize
: A callback that is called when the window is resized (only works when fullWindow
is active)onBlur
: Callback invoked when the user clicks outside the game (useful for pausing the game, for example)onFocus
: Callback invoked when the user set the focus back on the game (useful for unpausing after pausing with onBlur)Notes:
var luv = Luv();
)options
hash are ignoredel
or id
is specified, a new DOM canvas element will be generated and appended to the window.width
and height
will attempt to get their values from the DOM element. If they can't, and they are not
provided as options, they will default to 800x600px options = options || {};
var el = options.el,
id = options.id,
width = options.width,
height = options.height,
body = document.getElementsByTagName('body')[0],
html = document.getElementsByTagName('html')[0],
fullCss = "width: 100%; height: 100%; margin: 0; overflow: hidden;";
if(!el && id) { el = document.getElementById(id); }
if(el) {
if(!width && el.getAttribute('width')) { width = Number(el.getAttribute('width')); }
if(!height && el.getAttribute('height')) { height = Number(el.getAttribute('height')); }
} else {
el = document.createElement('canvas');
body.appendChild(el);
}
if(options.fullWindow) {
html.style.cssText = body.style.cssText = fullCss;
width = window.innerWidth;
height = window.innerHeight;
} else {
width = width || 800;
height = height || 600;
}
el.setAttribute('width', width);
el.setAttribute('height', height);
options.el = el;
options.width = width;
options.height = height;
return options;
};
export the local Luv variable so that the window.Luv = ... at the top of this file can take it
return Luv;
}());