Update memory-manager-concurrent
1581
games/mario/3rd/backbone.js
Normal file
481
games/mario/3rd/backbone.native.js
Normal file
@@ -0,0 +1,481 @@
|
||||
/**
|
||||
* Backbone.Native
|
||||
*
|
||||
* For all details and documentation:
|
||||
* http://github.com/inkling/backbone.native
|
||||
*
|
||||
* Copyright 2013 Inkling Systems, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The purpose of this library is to allow Backbone to work without needing to load jQuery or Zepto.
|
||||
* This file provides a basic jQuery-like implementation for Backbone, implementing the
|
||||
* minimum functionality for Backbone to function. We assume that Backbone applications using
|
||||
* this will not expect the standard jQuery API to work, and will instead use native JS functions.
|
||||
*
|
||||
* Keep in mind that due to the APIs in this, it will likely only work on recent browsers.
|
||||
*
|
||||
* Note:
|
||||
* - Core Backbone only needs collections with single members, so that is all that has been
|
||||
* supported in this library. It is expected that you will just use querySelectorAll instead.
|
||||
* This will be most obvious if you make heavy use of 'view.$'.
|
||||
* - Events delegated with selectors starting with '>' are not supported.
|
||||
* - Due to 'currentTarget' being read-only on standard DOM events, we cannot make standard
|
||||
* events behave identically to jQuery's events when delegation is used. The element matching
|
||||
* the delegate selector is instead passed as the second argument to event handlers.
|
||||
* - The '$.ajax' implementation is very simple and likely needs to be expanded to better support
|
||||
* standard use-cases.
|
||||
*
|
||||
* Tested with Backbone v0.9.2 and 1.0.0.
|
||||
*/
|
||||
(function(){
|
||||
"use strict";
|
||||
|
||||
// Regular expression to match an event name and/or a namespace.
|
||||
var namespaceRE = /^([^.]+)?(?:\.([^.]+))?$/;
|
||||
|
||||
var matchesSelector = Element.prototype.matchesSelector || null;
|
||||
if (!matchesSelector){
|
||||
['webkit', 'moz', 'o', 'ms'].forEach(function(prefix){
|
||||
var func = Element.prototype[prefix + 'MatchesSelector'];
|
||||
if (func) matchesSelector = func;
|
||||
});
|
||||
}
|
||||
|
||||
// The element property to save the cache key on.
|
||||
var cacheKeyProp = 'backboneNativeKey' + Math.random();
|
||||
var id = 1;
|
||||
var handlers = {};
|
||||
var unusedKeys = [];
|
||||
|
||||
/**
|
||||
* Get the event handlers for a given element, creating an empty set if one doesn't exist.
|
||||
*
|
||||
* To avoid constantly filling the handlers object with null values, we reuse old IDs that
|
||||
* have been created and then cleared.
|
||||
*
|
||||
* @param {Element} el The element to get handlers for.
|
||||
*
|
||||
* @return {Array} An array of handlers.
|
||||
*/
|
||||
function handlersFor(el){
|
||||
if (!el[cacheKeyProp]){
|
||||
// Pick a new key, from the unused pool, or make a new one.
|
||||
el[cacheKeyProp] = unusedKeys.length === 0 ? ++id : unusedKeys.pop();
|
||||
}
|
||||
|
||||
var cacheKey = el[cacheKeyProp];
|
||||
return handlers[cacheKey] || (handlers[cacheKey] = []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the event handlers for a given element.
|
||||
*
|
||||
* @param {Element} el The element to clear.
|
||||
*/
|
||||
function clearHandlers(el){
|
||||
var cacheKey = el[cacheKeyProp];
|
||||
if (handlers[cacheKey]){
|
||||
handlers[cacheKey] = null;
|
||||
el[cacheKeyProp] = null;
|
||||
unusedKeys.push(cacheKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add event handlers to an element.
|
||||
*
|
||||
* @param {Element} parentElement The element to bind event handlers to.
|
||||
* @param {string} eventName The event to bind, e.g. 'click'.
|
||||
* @param {string} selector (Optional) The selector to match when an event propagates up.
|
||||
* @param {function(Event, Element)} callback The function to call when the event is fired.
|
||||
*/
|
||||
function on(parentElement, eventName, selector, callback){
|
||||
// Adjust arguments if selector was not provided.
|
||||
if (typeof selector === 'function'){
|
||||
callback = selector;
|
||||
selector = null;
|
||||
}
|
||||
|
||||
var parts = namespaceRE.exec(eventName);
|
||||
eventName = parts[1] || null;
|
||||
var namespace = parts[2] || null;
|
||||
|
||||
if (!eventName) return;
|
||||
|
||||
var handler = callback;
|
||||
var originalCallback = callback;
|
||||
if (selector){
|
||||
// Event delegation handler to match a selector for child element events.
|
||||
handler = function(event){
|
||||
for (var el = event.target; el && el !== parentElement; el = el.parentElement){
|
||||
if (matchesSelector.call(el, selector)){
|
||||
// jQuery does not include the second argument, but we have included it
|
||||
// for simplicity because 'this' will likely be bound to the view inside
|
||||
// the callback, and as noted above, we cannot override 'currentTarget'.
|
||||
var result = originalCallback.call(el, event, el);
|
||||
if (result === false){
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// Standard event handler bound directly to the element.
|
||||
handler = function(event){
|
||||
var result = originalCallback.call(parentElement, event, parentElement);
|
||||
if (result === false){
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
parentElement.addEventListener(eventName, handler, false);
|
||||
|
||||
// Save event handler metadata so that the handler can be unbound later.
|
||||
handlersFor(parentElement).push({
|
||||
eventName: eventName,
|
||||
callback: callback,
|
||||
handler: handler,
|
||||
namespace: namespace,
|
||||
selector: selector
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an event handler from an element.
|
||||
*
|
||||
* @param {Element} parentElement The element to unbind event handlers from.
|
||||
* @param {string} eventName (Optional) The event to unbind, e.g. 'click'.
|
||||
* @param {string} selector (Optional) The selector to unbind.
|
||||
* @param {function(Event, Element)} callback (Optional) The function to unbind.
|
||||
*/
|
||||
function off(parentElement, eventName, selector, callback){
|
||||
if (typeof selector === 'function'){
|
||||
callback = selector;
|
||||
selector = null;
|
||||
}
|
||||
|
||||
var parts = namespaceRE.exec(eventName || '');
|
||||
eventName = parts[1];
|
||||
var namespace = parts[2];
|
||||
var handlers = handlersFor(parentElement) || [];
|
||||
|
||||
if (!eventName && !namespace && !selector && !callback){
|
||||
// Fastpath to remove all handlers.
|
||||
handlers.forEach(function(item){
|
||||
parentElement.removeEventListener(item.eventName, item.handler, false);
|
||||
});
|
||||
clearHandlers(parentElement);
|
||||
} else {
|
||||
var matchedHandlers = handlers.filter(function(item){
|
||||
return ((!namespace || item.namespace === namespace) &&
|
||||
(!eventName || item.eventName === eventName) &&
|
||||
(!callback || item.callback === callback) &&
|
||||
(!selector || item.selector === selector));
|
||||
});
|
||||
|
||||
matchedHandlers.forEach(function(item){
|
||||
parentElement.removeEventListener(item.eventName, item.handler, false);
|
||||
|
||||
handlers.splice(handlers.indexOf(item), 1);
|
||||
});
|
||||
|
||||
if (handlers.length === 0) clearHandlers(parentElement);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new jQuery-style element representation.
|
||||
*
|
||||
* @param {string|Element|Window} element There are several different possible values for this
|
||||
* argument:
|
||||
* - {string} A snippet of HTML, if it starts with a '<', or a selector to find.
|
||||
* - {Element} An existing element to wrap.
|
||||
* - {Window} The window object to wrap.
|
||||
* @param {Element} context The context to search within, if a selector was given.
|
||||
* Defaults to document.
|
||||
*/
|
||||
function $(element, context){
|
||||
context = context || document;
|
||||
|
||||
// Call as a constructor if it was used as a function.
|
||||
if (!(this instanceof $)) return new $(element, context);
|
||||
|
||||
if (!element){
|
||||
this.length = 0;
|
||||
} else if (typeof element === 'string'){
|
||||
if (/^\s*</.test(element)){
|
||||
// Parse arbitrary HTML into an element.
|
||||
var div = document.createElement('div');
|
||||
div.innerHTML = element;
|
||||
this[0] = div.firstChild;
|
||||
div.removeChild(div.firstChild);
|
||||
this.length = 1;
|
||||
} else {
|
||||
element = context.querySelector(element);
|
||||
|
||||
// Length must be 0 if no elements found.
|
||||
if (element !== null){
|
||||
this[0] = element;
|
||||
this.length = 1;
|
||||
} else {
|
||||
this.length = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This handles both the 'Element' and 'Window' case, as both support
|
||||
// event binding via 'addEventListener'.
|
||||
this[0] = element;
|
||||
this.length = 1;
|
||||
}
|
||||
}
|
||||
|
||||
$.prototype = {
|
||||
/**
|
||||
* The following methods are used by Backbone, but only in code-paths for IE 6/7 support.
|
||||
* Since none of this will work for old IE anyway, they are not implemented, and
|
||||
* instead left for documentation purposes.
|
||||
*
|
||||
* Used in Backbone.History.prototype.start.
|
||||
*/
|
||||
hide: null,
|
||||
appendTo: null,
|
||||
|
||||
/**
|
||||
* Find is not supported to encourage the use of querySelector(All) as an alternative.
|
||||
*
|
||||
* e.g.
|
||||
* Instead of 'this.$(sel)', use 'this.el.querySelectorAll(sel)'.
|
||||
*
|
||||
* Used in Backbone.View.prototype.$, but not actually called internally.
|
||||
*/
|
||||
find: null,
|
||||
|
||||
/**
|
||||
* Add attributes to the element.
|
||||
*
|
||||
* Used in Backbone.View.prototype.make.
|
||||
*
|
||||
* @param {Object} attributes A set of attributes to apply to the element.
|
||||
*
|
||||
* @return {$} This instance.
|
||||
*/
|
||||
attr: function(attrs){
|
||||
Object.keys(attrs).forEach(function(attr){
|
||||
switch (attr){
|
||||
case 'html':
|
||||
this[0].innerHTML = attrs[attr];
|
||||
break;
|
||||
case 'text':
|
||||
this[0].textContent = attrs[attr];
|
||||
break;
|
||||
case 'class':
|
||||
this[0].className = attrs[attr];
|
||||
break;
|
||||
default:
|
||||
this[0].setAttribute(attr, attrs[attr]);
|
||||
break;
|
||||
}
|
||||
}, this);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the HTML content of the element. Backbone does not use the no-argument version
|
||||
* to read innerHTML, so that has not been implemented.
|
||||
*
|
||||
* Used in Backbone.View.prototype.make.
|
||||
*
|
||||
* @param {string} html The HTML to set as the element content.
|
||||
*
|
||||
* @return {$} This instance.
|
||||
*/
|
||||
html: function(html){
|
||||
this[0].innerHTML = html;
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove an element from the DOM and remove all event handlers bound to it and
|
||||
* its child elements.
|
||||
*
|
||||
* Used in Backbone.View.prototype.remove.
|
||||
*
|
||||
* @return {$} This instance.
|
||||
*/
|
||||
remove: function(){
|
||||
var el = this[0];
|
||||
if (el.parentElement) el.parentElement.removeChild(el);
|
||||
|
||||
// Unbind all event handlers on the element and children.
|
||||
(function removeChildEvents(element){
|
||||
off(element);
|
||||
|
||||
for (var i = 0, len = element.childNodes.length; i < len; i++){
|
||||
if (element.childNodes[i].nodeType !== Node.TEXT_NODE){
|
||||
removeChildEvents(element.childNodes[i]);
|
||||
}
|
||||
}
|
||||
})(el);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Bind an event handler to this element.
|
||||
*
|
||||
* @param {string} eventName The event to bind, e.g. 'click'.
|
||||
* @param {string} selector (Optional) The selector to match when an event propagates up.
|
||||
* @param {function(Event, Element)} callback The function to call when the event is fired.
|
||||
*/
|
||||
on: function(eventName, selector, callback){
|
||||
on(this[0], eventName, selector, callback);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Unbind an event handler to this element.
|
||||
*
|
||||
* @param {string} eventName (Optional) The event to unbind, e.g. 'click'.
|
||||
* @param {string} selector (Optional) The selector to unbind.
|
||||
* @param {function(Event, Element)} callback (Optional) The function to unbind.
|
||||
*/
|
||||
off: function(eventName, selector, callback){
|
||||
off(this[0], eventName, selector, callback);
|
||||
return this;
|
||||
},
|
||||
|
||||
// Backbone v0.9.2 support.
|
||||
bind: function(eventName, callback){
|
||||
return this.on(eventName, callback);
|
||||
},
|
||||
unbind: function(eventName, callback){
|
||||
return this.off(eventName, callback);
|
||||
},
|
||||
delegate: function(selector, eventName, callback){
|
||||
return this.on(eventName, selector, callback);
|
||||
},
|
||||
undelegate: function(selector, eventName, callback){
|
||||
return this.off(eventName, selector, callback);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Send an AJAX request.
|
||||
*
|
||||
* @param {Object} options The options to use for the connection:
|
||||
* - {string} url The URL to connect to.
|
||||
* - {string} type The type of request, e.g. 'GET', or 'POST'.
|
||||
* - {string} dataType The type of data expected, 'json'.
|
||||
* - {string} contentType The content-type of the data.
|
||||
* - {string|object} data The content to send.
|
||||
* - {function(XMLHttpRequest)} beforeSend A callback to call before sending.
|
||||
* - {boolean} processData True if 'data' should be converted
|
||||
* to a query string from an object.
|
||||
* - {function({string|object}, {string}, {XMLHttpRequest})} success The success callback.
|
||||
* - {function({XMLHttpRequest})} error The error callback.
|
||||
*/
|
||||
$.ajax = function(options){
|
||||
options = options || {};
|
||||
var type = options.type || 'GET';
|
||||
var url = options.url;
|
||||
var processData = options.processData === undefined ? true : !!options.processData;
|
||||
var contentType = options.contentType || 'application/x-www-form-urlencoded; charset=UTF-8';
|
||||
|
||||
// Process the data for sending.
|
||||
var data = options.data;
|
||||
if (processData && typeof data === 'object'){
|
||||
var params = Object.keys(data).map(function(prop){
|
||||
return encodeURIComponent(prop) + '=' + encodeURIComponent(data[prop]);
|
||||
});
|
||||
data = params.join('&');
|
||||
}
|
||||
|
||||
// Data for GET and HEAD goes in the URL.
|
||||
if (data && (type === 'GET' || type === 'HEAD')){
|
||||
url += (url.indexOf('?') === -1 ? '?' : '&') + data;
|
||||
data = undefined;
|
||||
}
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open(type, url, true);
|
||||
|
||||
xhr.setRequestHeader('Content-Type', contentType);
|
||||
if (options.beforeSend) options.beforeSend(xhr);
|
||||
|
||||
xhr.onload = function(){
|
||||
var error = false;
|
||||
var content = xhr.responseText;
|
||||
|
||||
// Parse the JSON before calling success.
|
||||
if (options.dataType === 'json'){
|
||||
try {
|
||||
content = JSON.parse(content);
|
||||
} catch (e){
|
||||
error = true
|
||||
}
|
||||
}
|
||||
|
||||
if (!error && (xhr.status >= 200 && xhr.status < 300)){
|
||||
// The last two arguments only apply to v0.9.2.
|
||||
if (options.success) options.success(content, xhr.statusText, xhr);
|
||||
} else {
|
||||
// This signature is inconsistent with v0.9.2, but is correct for 1.0.0.
|
||||
if (options.error) options.error(xhr);
|
||||
}
|
||||
}.bind(this);
|
||||
|
||||
xhr.onerror = xhr.onabort = function(){
|
||||
if (options.error) options.error(xhr);
|
||||
};
|
||||
|
||||
xhr.send(data);
|
||||
|
||||
return xhr;
|
||||
};
|
||||
|
||||
// Expose on/off for external use with having to instantiate a wrapper.
|
||||
$.on = on;
|
||||
$.off = off;
|
||||
|
||||
if (typeof exports !== 'undefined') {
|
||||
module.exports = $;
|
||||
} else {
|
||||
var root = this;
|
||||
var originalBackboneNative = root.Backbone ? root.Backbone.Native : null;
|
||||
var original$ = root.$;
|
||||
if (root.Backbone) root.Backbone.Native = $;
|
||||
root.$ = $;
|
||||
|
||||
$.noConflict = function(deep){
|
||||
root.$ = original$;
|
||||
if (deep) root.Backbone.Native = originalBackboneNative;
|
||||
return $;
|
||||
};
|
||||
|
||||
if (root.Backbone){
|
||||
if (root.Backbone.setDomLibrary){ // v0.9.2
|
||||
root.Backbone.setDomLibrary($);
|
||||
} else { // v1.0.0
|
||||
root.Backbone.$ = $;
|
||||
}
|
||||
}
|
||||
}
|
||||
}).call(this);
|
||||
358
games/mario/3rd/qtree.js
Normal file
@@ -0,0 +1,358 @@
|
||||
/**
|
||||
*
|
||||
* simple-quadtree is a minimal quadtree implementation that supports simple put, get,
|
||||
* remove and clear operations on objects having a x, y position and w, h dimension.
|
||||
*
|
||||
* Copyright (c) 2013 Antti Saarinen <antti.p.saarinen@gmail.com>
|
||||
* https://github.com/asaarinen/qtree
|
||||
*
|
||||
*/
|
||||
function QuadTree(x, y, w, h, options) {
|
||||
|
||||
if( typeof x != 'number' || isNaN(x) )
|
||||
x = 0;
|
||||
if( typeof y != 'number' || isNaN(y) )
|
||||
y = 0;
|
||||
if( typeof w != 'number' || isNaN(w) )
|
||||
w = 10;
|
||||
if( typeof h != 'number' || isNaN(h) )
|
||||
h = 10;
|
||||
|
||||
var maxc = 25;
|
||||
var leafratio = 0.5;
|
||||
if( options ) {
|
||||
if( typeof options.maxchildren == 'number' )
|
||||
if( options.maxchildren > 0 )
|
||||
maxc = options.maxchildren;
|
||||
if( typeof options.leafratio == 'number' )
|
||||
if( options.leafratio >= 0 )
|
||||
leafratio = options.leafratio;
|
||||
}
|
||||
|
||||
// validate an input object
|
||||
function validate(obj) {
|
||||
if( !obj )
|
||||
return false;
|
||||
if( typeof obj.x != 'number' ||
|
||||
typeof obj.y != 'number' ||
|
||||
typeof obj.w != 'number' ||
|
||||
typeof obj.h != 'number' )
|
||||
return false;
|
||||
if( isNaN(obj.x) || isNaN(obj.y) ||
|
||||
isNaN(obj.w) || isNaN(obj.h) )
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// test for deep equality for x,y,w,h
|
||||
function isequal(o1, o2) {
|
||||
if( o1.x == o2.x &&
|
||||
o1.y == o2.y &&
|
||||
o1.w == o2.w &&
|
||||
o1.h == o2.h )
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// create a new quadtree node
|
||||
function createnode(x, y, w, h) {
|
||||
return {
|
||||
x: x,
|
||||
y: y,
|
||||
w: w,
|
||||
h: h,
|
||||
c: [],
|
||||
l: [],
|
||||
n: []
|
||||
}
|
||||
}
|
||||
|
||||
// root node used by this quadtree
|
||||
var root = createnode(x, y, w, h);
|
||||
|
||||
// calculate distance between two points
|
||||
function distance(x1, y1, x2, y2) {
|
||||
return Math.sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
|
||||
}
|
||||
|
||||
// calculate distance between a point and a line (segment)
|
||||
function distancePL(x, y, x1, y1, dx1, dy1, len1 ) {
|
||||
if( !len1 ) // in case length is not provided, assume a line
|
||||
len1 = -1;
|
||||
|
||||
// x = x1 + s * dx1 + t * dy1
|
||||
// y = y1 + s * dy1 - t * dx1
|
||||
// x * dy1 - y * dx1 = x1 * dy1 - y1 * dx1 +
|
||||
// t * ( dy1 * dy1 + dx1 * dx1 )
|
||||
var t = dx1 * dx1 + dy1 * dy1;
|
||||
if( t == 0 )
|
||||
return null;
|
||||
else {
|
||||
t = ( x * dy1 - y * dx1 - x1 * dy1 + y1 * dx1 ) / t;
|
||||
if( Math.abs(dx1) > Math.abs(dy1) )
|
||||
var s = ( x - x1 - t * dy1 ) / dx1;
|
||||
else
|
||||
var s = ( y - y1 + t * dx1 ) / dy1;
|
||||
if( ( s >= 0 && s <= len1 ) || len1 < 0 )
|
||||
return {
|
||||
s: s,
|
||||
t: t,
|
||||
x: x1 + s * dx1,
|
||||
y: y1 + s * dy1,
|
||||
dist: Math.abs(t)
|
||||
};
|
||||
else if( s < 0 ) {
|
||||
var dist = distance(x, y, x1, y1);
|
||||
return {
|
||||
s: s,
|
||||
dist: dist
|
||||
};
|
||||
} else {
|
||||
var dist = distance(x, y,
|
||||
x1 + len1*dx1,
|
||||
y1 + len1*dy1);
|
||||
return {
|
||||
s: s,
|
||||
dist: dist
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// does a line and a rectangle overlap ?
|
||||
function overlap_line(o1, o2, buf) {
|
||||
if( !o1 || !o2 )
|
||||
return true;
|
||||
var dist = distancePL(o2.x + 0.5 * o2.w,
|
||||
o2.y + 0.5 * o2.h,
|
||||
o1.x, o1.y, o1.dx, o1.dy, o1.dist);
|
||||
if( dist ) {
|
||||
dist.dist -= buf;
|
||||
if( dist.dist < 0 )
|
||||
return true;
|
||||
if( dist.dist * dist.dist <= o2.w * o2.w + o2.h * o2.h )
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// do two rectangles overlap ?
|
||||
function overlap_rect(o1, o2, buf) {
|
||||
if( !o1 || !o2 )
|
||||
return true;
|
||||
if( o1.x + o1.w < o2.x - buf ||
|
||||
o1.y + o1.h < o2.y - buf ||
|
||||
o1.x - buf > o2.x + o2.w ||
|
||||
o1.y - buf > o2.y + o2.h )
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function isleaf(node, obj) {
|
||||
|
||||
var leaf = false;
|
||||
if( obj.w * obj.h > node.w * node.h * leafratio )
|
||||
leaf = true;
|
||||
|
||||
if( obj.x < node.x ||
|
||||
obj.y < node.y ||
|
||||
obj.x + obj.w > node.x + node.w ||
|
||||
obj.y + obj.h > node.y + node.h )
|
||||
leaf = true;
|
||||
|
||||
var childnode = null;
|
||||
for( var ni = 0; ni < node.n.length; ni++ )
|
||||
if( overlap_rect(obj, node.n[ni], 0) ) {
|
||||
if( childnode ) { // multiple hits
|
||||
leaf = true;
|
||||
break;
|
||||
} else
|
||||
childnode = node.n[ni];
|
||||
}
|
||||
|
||||
return { leaf: leaf,
|
||||
childnode: childnode };
|
||||
}
|
||||
|
||||
// put an object to one of the child nodes of this node
|
||||
function put_to_nodes(node, obj) {
|
||||
var leaf = isleaf(node, obj);
|
||||
if( leaf )
|
||||
node.l.push(obj);
|
||||
else if( leaf.childnode )
|
||||
put(leaf.childnode, obj);
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
// remove an object from this node
|
||||
function remove(node, obj, attr) {
|
||||
if( !validate(obj) )
|
||||
return 0;
|
||||
|
||||
if( !attr )
|
||||
attr = false;
|
||||
else if( typeof attr != 'string' )
|
||||
attr = 'id';
|
||||
|
||||
var count = 0;
|
||||
for( var ci = 0; ci < node.c.length; ci++ )
|
||||
if( ( attr && node.c[ci][attr] == obj[attr] ) ||
|
||||
( !attr && isequal(node.c[ci], obj) ) ) {
|
||||
count++;
|
||||
node.c.splice(ci, 1);
|
||||
ci--;
|
||||
}
|
||||
|
||||
for( var ci = 0; ci < node.l.length; ci++ )
|
||||
if( ( attr && node.l[ci][attr] == obj[attr] ) ||
|
||||
( !attr && isequal(node.l[ci], obj) ) ) {
|
||||
count++;
|
||||
node.l.splice(ci, 1);
|
||||
ci--;
|
||||
}
|
||||
|
||||
var leaf = isleaf(node, obj);
|
||||
if( !leaf.leaf && leaf.childnode )
|
||||
return count + remove(leaf.childnode, obj, attr);
|
||||
return count;
|
||||
}
|
||||
|
||||
// put an object to this node
|
||||
function put(node, obj, removeflag) {
|
||||
|
||||
if( !validate(obj) )
|
||||
return;
|
||||
|
||||
if( node.n.length == 0 ) {
|
||||
node.c.push(obj);
|
||||
|
||||
// subdivide
|
||||
if( node.c.length > maxc ) {
|
||||
var w2 = node.w / 2;
|
||||
var h2 = node.h / 2;
|
||||
node.n.push(createnode(node.x, node.y, w2, h2),
|
||||
createnode(node.x + w2, node.y, w2, h2),
|
||||
createnode(node.x, node.y + h2, w2, h2),
|
||||
createnode(node.x + w2, node.y + h2, w2, h2));
|
||||
for( var ci = 0; ci < node.c.length; ci++ )
|
||||
put_to_nodes(node, node.c[ci]);
|
||||
node.c = [];
|
||||
}
|
||||
} else
|
||||
put_to_nodes(node, obj);
|
||||
}
|
||||
|
||||
// iterate through all objects in this node matching given overlap
|
||||
// function
|
||||
function getter(overlapfun, node, obj, buf, strict, callbackOrArray) {
|
||||
for( var li = 0; li < node.l.length; li++ )
|
||||
if( !strict || overlapfun(obj, node.l[li], buf) )
|
||||
if( typeof callbackOrArray == 'object' )
|
||||
callbackOrArray.push(node.l[li]);
|
||||
else if( !callbackOrArray(node.l[li]) )
|
||||
return false;
|
||||
for( var li = 0; li < node.c.length; li++ )
|
||||
if( !strict || overlapfun(obj, node.c[li], buf) )
|
||||
if( typeof callbackOrArray == 'object' )
|
||||
callbackOrArray.push(node.c[li]);
|
||||
else if( !callbackOrArray(node.c[li]) )
|
||||
return false;
|
||||
for( var ni = 0; ni < node.n.length; ni++ ) {
|
||||
if( overlapfun(obj, node.n[ni], buf) ) {
|
||||
if( typeof callbackOrArray == 'object' )
|
||||
callbackOrArray.concat(getter(overlapfun, node.n[ni], obj, buf, strict, callbackOrArray));
|
||||
else if( !getter(overlapfun, node.n[ni], obj, buf, strict, callbackOrArray) )
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// iterate through all objects in this node matching the given rectangle
|
||||
function get_rect(node, obj, buf, callbackOrArray) {
|
||||
return getter(overlap_rect, node, obj, buf, true, callbackOrArray);
|
||||
}
|
||||
|
||||
// iterate through all objects in this node matching the given
|
||||
// line (segment)
|
||||
function get_line(node, obj, buf, callbackOrArray) {
|
||||
return getter(overlap_line, node, obj, buf, false, callbackOrArray);
|
||||
}
|
||||
|
||||
// iterate through all objects in this node matching given
|
||||
// geometry, either a rectangle or a line segment
|
||||
function get(node, obj, buf, callbackOrArray) {
|
||||
|
||||
if( typeof buf == 'function' && typeof callbackOrArray == 'undefined' ) {
|
||||
callbackOrArray = buf;
|
||||
buf = 0;
|
||||
}
|
||||
if( typeof callbackOrArray == 'undefined' ) {
|
||||
callbackOrArray = [];
|
||||
buf = 0;
|
||||
}
|
||||
if( obj == null )
|
||||
get_rect(node, obj, buf, callbackOrArray);
|
||||
else if( typeof obj.x == 'number' &&
|
||||
typeof obj.y == 'number' &&
|
||||
!isNaN(obj.x) && !isNaN(obj.y) ) {
|
||||
if( typeof obj.dx == 'number' &&
|
||||
typeof obj.dy == 'number' &&
|
||||
!isNaN(obj.dx) && !isNaN(obj.dy) )
|
||||
get_line(node, obj, buf, callbackOrArray);
|
||||
else if( typeof obj.w == 'number' &&
|
||||
typeof obj.h == 'number' &&
|
||||
!isNaN(obj.w) && !isNaN(obj.h) )
|
||||
get_rect(node, obj, buf, callbackOrArray);
|
||||
}
|
||||
if( typeof callbackOrArray == "object" ) return callbackOrArray;
|
||||
}
|
||||
|
||||
// return the object interface
|
||||
return {
|
||||
get: function(obj, buf, callbackOrArray) {
|
||||
return get(root, obj, buf, callbackOrArray);
|
||||
},
|
||||
put: function(obj) {
|
||||
put(root, obj);
|
||||
},
|
||||
remove: function(obj, attr) {
|
||||
return remove(root, obj, attr);
|
||||
},
|
||||
clear: function() {
|
||||
root = createnode(x, y, w, h);
|
||||
},
|
||||
stringify: function() {
|
||||
var strobj = {
|
||||
x: x, y: y, w: w, h: h,
|
||||
maxc: maxc,
|
||||
leafratio: leafratio,
|
||||
root: root
|
||||
};
|
||||
try {
|
||||
return JSON.stringify(strobj);
|
||||
} catch(err) {
|
||||
// could not stringify
|
||||
// probably due to objects included in qtree being non-stringifiable
|
||||
return null;
|
||||
}
|
||||
},
|
||||
parse: function(str) {
|
||||
if( typeof str == 'string' )
|
||||
str = JSON.parse(str);
|
||||
|
||||
x = str.x;
|
||||
y = str.y;
|
||||
w = str.w;
|
||||
h = str.h;
|
||||
maxc = str.maxc;
|
||||
leafratio = str.leafratio;
|
||||
root = str.root;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// for use within node.js
|
||||
if( typeof module != 'undefined' )
|
||||
module.exports = QuadTree;
|
||||
1343
games/mario/3rd/underscore.js
Normal file
20
games/mario/LICENSE
Normal file
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 AmiliaApp
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
12
games/mario/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
Backbone Game Engine
|
||||
====================
|
||||
|
||||
An elementary HTML5 Game Engine built on Backbone.
|
||||
|
||||
Documentation and examples can be found here:
|
||||
http://martindrapeau.github.io/backbone-game-engine
|
||||
|
||||
* * *
|
||||
|
||||
Copyright (c) Martin Drapeau<br/>
|
||||
Licensed under the MIT @license
|
||||
BIN
games/mario/apple_touch_icon.png
Normal file
|
After Width: | Height: | Size: 714 B |
45
games/mario/ball/index.html
Normal file
@@ -0,0 +1,45 @@
|
||||
<!doctype html>
|
||||
<html style="touch-action: none;">
|
||||
<head>
|
||||
<title>Mario - Backbone Game Engine</title>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<link href="../favicon.ico" rel="shortcut icon" type="image/x-icon" />
|
||||
<link href="../apple_touch_icon.png" rel="apple-touch-icon" />
|
||||
|
||||
<meta name="viewport" content="width=960, user-scalable=no"/>
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/>
|
||||
|
||||
<script src="../3rd/underscore.js" type="text/javascript"></script>
|
||||
<script src="../3rd/backbone.native.js" type="text/javascript"></script>
|
||||
<script src="../3rd/backbone.js" type="text/javascript"></script>
|
||||
|
||||
<script src="../src/adjust-viewport.js" type="text/javascript"></script>
|
||||
<script src="../src/shapes.js" type="text/javascript"></script>
|
||||
<script src="../src/core.js" type="text/javascript"></script>
|
||||
|
||||
<script src="main.js" type="text/javascript"></script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
background-color: #000;
|
||||
}
|
||||
canvas {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<canvas id="foreground" width="960" height="700">
|
||||
Your browser does not support canvas element.
|
||||
</canvas>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
107
games/mario/ball/main.js
Normal file
@@ -0,0 +1,107 @@
|
||||
$(window).on("load", function() {
|
||||
|
||||
/**
|
||||
*
|
||||
* Backbone Game Engine - An elementary HTML5 canvas game engine using Backbone.
|
||||
*
|
||||
* Copyright (c) 2014 Martin Drapeau
|
||||
* https://github.com/martindrapeau/backbone-game-engine
|
||||
*
|
||||
*/
|
||||
|
||||
// Bouncing ball.
|
||||
|
||||
Backbone.Ball = Backbone.Sprite.extend({
|
||||
defaults: _.extend(Backbone.Sprite.prototype.defaults, {
|
||||
x: 400,
|
||||
y: 400,
|
||||
radius: 20,
|
||||
color: "red",
|
||||
velocity: 400,
|
||||
yVelocity: 400
|
||||
}),
|
||||
update: function(dt) {
|
||||
if (!this.engine) return false;
|
||||
|
||||
var x = this.get("x"),
|
||||
y = this.get("y"),
|
||||
radius = this.get("radius"),
|
||||
velocity = this.get("velocity") - radius,
|
||||
yVelocity = this.get("yVelocity") - radius,
|
||||
maxX = this.engine.canvas.width - radius,
|
||||
maxY = this.engine.canvas.height - radius,
|
||||
attrs = {oldX: x, oldY: y};
|
||||
|
||||
x += velocity * (dt/1000);
|
||||
y += yVelocity * (dt/1000);
|
||||
|
||||
if (x <= 0) {
|
||||
x = 0;
|
||||
attrs.velocity = velocity * -1;
|
||||
}
|
||||
if (x >= maxX) {
|
||||
x = maxX;
|
||||
attrs.velocity = velocity * -1;
|
||||
}
|
||||
|
||||
if (y <= 0) {
|
||||
y = 0;
|
||||
attrs.yVelocity = yVelocity * -1;
|
||||
}
|
||||
if (y >= maxY) {
|
||||
y = maxY;
|
||||
attrs.yVelocity = yVelocity * -1;
|
||||
}
|
||||
attrs.x = x;
|
||||
attrs.y = y;
|
||||
|
||||
this.set(attrs);
|
||||
return true;
|
||||
},
|
||||
draw: function(context) {
|
||||
var x = this.get("x"),
|
||||
y = this.get("y"),
|
||||
oldX = this.get("oldX"),
|
||||
oldY = this.get("oldY"),
|
||||
radius = this.get("radius"),
|
||||
color = this.get("color");
|
||||
|
||||
if (oldX || oldY)
|
||||
context.clearRect(oldX, oldY, radius, radius);
|
||||
|
||||
drawCircle(context, x+radius/2, y+radius/2, radius, color);
|
||||
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
var canvas = document.getElementById("foreground");
|
||||
|
||||
var debugPanel = new Backbone.DebugPanel();
|
||||
|
||||
var ball = new Backbone.Ball({
|
||||
x: 100,
|
||||
y: 100,
|
||||
color: "blue"
|
||||
});
|
||||
|
||||
var engine = new Backbone.Engine({
|
||||
clearOnDraw: true
|
||||
}, {
|
||||
canvas: canvas,
|
||||
debugPanel: debugPanel
|
||||
});
|
||||
engine.add([
|
||||
ball,
|
||||
debugPanel
|
||||
]);
|
||||
|
||||
// Expose things as globals - easier to debug
|
||||
_.extend(window, {
|
||||
canvas: canvas,
|
||||
engine: engine
|
||||
});
|
||||
|
||||
adjustViewport(canvas);
|
||||
|
||||
});
|
||||
BIN
games/mario/docs/ball.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
7
games/mario/docs/bootstrap.min.css
vendored
Normal file
7
games/mario/docs/bootstrap.min.js
vendored
Normal file
66
games/mario/docs/docs.css
Normal file
@@ -0,0 +1,66 @@
|
||||
|
||||
a {
|
||||
color: #4242FF;
|
||||
}
|
||||
.navbar-inverse {
|
||||
border-radius: 0;
|
||||
background-color: #4242FF;
|
||||
border-color: #4242FF;
|
||||
}
|
||||
.navbar-inverse .navbar-collapse,
|
||||
.navbar-inverse .navbar-form {
|
||||
border-color: #4242FF;
|
||||
}
|
||||
.navbar-inverse .navbar-brand,
|
||||
.navbar-inverse .navbar-nav>li>a {
|
||||
color: #F0F0F0;
|
||||
}
|
||||
a.navbar-brand {
|
||||
padding-left: 50px;
|
||||
}
|
||||
a.navbar-brand img {
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 32px;
|
||||
margin: -8px 0 0 -42px;
|
||||
}
|
||||
footer.navbar-default {border-radius: 0; margin-bottom: 0;}
|
||||
.bs-sidebar .nav > .active > ul {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.bs-sidebar .nav > li > a {
|
||||
padding: 2px 4px;
|
||||
}
|
||||
.bs-sidebar .nav > li.active {
|
||||
background-color: #428bca;
|
||||
border-radius: 0;
|
||||
}
|
||||
.bs-sidebar .nav > li.active > a {
|
||||
color: white;
|
||||
}
|
||||
.bs-sidebar .nav > li.active > a:hover {
|
||||
background-color: #428bca;
|
||||
}
|
||||
li.github-icon {
|
||||
padding-left: 10px;
|
||||
}
|
||||
li.github-icon a {
|
||||
display: inline-block;
|
||||
padding-left: 32px;
|
||||
color: white;
|
||||
}
|
||||
li.github-icon a img {
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 32px;
|
||||
margin: -7px 0 0 -32px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
li.github-icon a:hover img {
|
||||
opacity: 1.0;
|
||||
}
|
||||
.bs-sidebar .nav > li.active,
|
||||
.bs-sidebar .nav > li.active > a:hover {
|
||||
background-color: #4242FF;
|
||||
}
|
||||
BIN
games/mario/docs/frog.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
games/mario/docs/github.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
games/mario/docs/hero-collisions.png
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
games/mario/docs/input.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
4
games/mario/docs/jquery.min.js
vendored
Normal file
BIN
games/mario/docs/ludosquest.png
Normal file
|
After Width: | Height: | Size: 215 KiB |
BIN
games/mario/docs/mario.png
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
games/mario/docs/mariocraft.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
games/mario/docs/super-mario-bros-level-1-1.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
games/mario/docs/super-mario-clone.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
games/mario/docs/super-mario-download.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
games/mario/docs/super-mario-sprite.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
games/mario/docs/world-editor.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
113
games/mario/examples.html
Normal file
@@ -0,0 +1,113 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Examples - Backbone Game Engine</title>
|
||||
<meta name="description" content="Games and samples created with Backbone Game Engine.">
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<link href="favicon.ico" rel="shortcut icon" type="image/x-icon" />
|
||||
<link href="apple_touch_icon.png" rel="apple-touch-icon" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
|
||||
<script src="docs/jquery.min.js" type="text/javascript"></script>
|
||||
<script src="docs/bootstrap.min.js" type="text/javascript"></script>
|
||||
<link href="docs/bootstrap.min.css" rel="stylesheet" type="text/css" charset="utf-8">
|
||||
|
||||
<link href="docs/docs.css" rel="stylesheet" type="text/css" charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body data-spy="scroll" data-target="#sidebar" data-offset="100">
|
||||
<header class="navbar navbar-inverse" role="banner">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle pull-left" data-toggle="collapse" data-target="#navbar-collapse">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="index.html" title="Elementary HTML5 Canvas Game Engine based on Backbone."><img src="apple_touch_icon.png" /> Backbone Game Engine</a>
|
||||
</div>
|
||||
<div id="navbar-collapse" class="collapse navbar-collapse" role="navigation">
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="index.html">Documentation</a></li>
|
||||
<li><a href="examples.html">Examples</a></li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li class="github-icon"><a href="https://github.com/martindrapeau/backbone-game-engine" title="Fork me on Github"><img src="docs/github.png" />Github</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1>Examples</h1>
|
||||
<p>
|
||||
Some examples built with Backbone Game Engine. Play and modify at will.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-4 col-xs-6">
|
||||
<h4>Super Mario Bros, level 1-1</h4>
|
||||
<a href="super-mario-bros/index.html" target="_blank">
|
||||
<img src="docs/super-mario-bros-level-1-1.png" class="img-responsive" alt="Super Mario Bros, level 1-1" title="Super Mario Bros, level 1-1" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 col-xs-6">
|
||||
<h4>Mariocraft</h4>
|
||||
<a href="http://www.mariocraft.club" target="_blank">
|
||||
<img src="docs/mariocraft.png" class="img-responsive" alt="Mariocraft" title="Kids can build and show-off their own level." />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 col-xs-6">
|
||||
<h4>Ludo's Quest <small><a href="https://itunes.apple.com/ca/app/ludos-quest/id1047863228" target="_blank">(available on the App Store)</a></small></h4>
|
||||
<a href="http://www.ludosquest.com" target="_blank">
|
||||
<img src="docs/ludosquest.png" class="img-responsive" alt="Ludo's Quest" title="HTML5/iOS/Android side-scrolling platformer set in medieval times. Rated for children." />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<br/>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-4 col-xs-6">
|
||||
<h4>Bouncing ball</h4>
|
||||
<a href="ball/index.html" target="_blank">
|
||||
<img src="docs/ball.png" class="img-responsive" alt="Bouncing Ball" title="Bouncing Ball" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 col-xs-6">
|
||||
<h4>Mario in an empty world</h4>
|
||||
<a href="mario/index.html" target="_blank">
|
||||
<img src="docs/mario.png" class="img-responsive" alt="Mario in an empty world" title="Mario in an empty world" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 col-xs-6">
|
||||
<h4>Hoppy frog</h4>
|
||||
<a href="frog/index.html" target="_blank">
|
||||
<img src="docs/frog.png" class="img-responsive" alt="Hoppy frog" title="A frog that can only hop" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<footer class="navbar navbar-default">
|
||||
<p class="navbar-text navbar-left">
|
||||
© 2014-2015 <a href="http://martindrapeau.tumblr.com/">Martin Drapeau.</a>
|
||||
<a href="https://github.com/martindrapeau/backbone-game-engine/blob/gh-pages/LICENSE">Licensed under MIT.</a>
|
||||
</p>
|
||||
<p class="navbar-text navbar-right">Written in Montréal, Canada.</p>
|
||||
<p class="navbar-text navbar-right"> </p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
BIN
games/mario/favicon.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
145
games/mario/frog/frog.js
Normal file
@@ -0,0 +1,145 @@
|
||||
(function() {
|
||||
|
||||
/**
|
||||
*
|
||||
* Backbone Game Engine - An elementary HTML5 canvas game engine using Backbone.
|
||||
*
|
||||
* Copyright (c) 2014 Martin Drapeau
|
||||
* https://github.com/martindrapeau/backbone-game-engine
|
||||
*
|
||||
*/
|
||||
|
||||
var velocity = 200,
|
||||
acceleration = 400,
|
||||
jumpVelocity = 550,
|
||||
jumpDeceleration = 1400,
|
||||
jumpHoldDeceleration = 900,
|
||||
fallVelocity = 600,
|
||||
fallAcceleration = 1200;
|
||||
|
||||
Backbone.Frog = Backbone.Hero.extend({
|
||||
defaults: _.extend({}, Backbone.Hero.prototype.defaults, {
|
||||
name: "frog",
|
||||
type: "character",
|
||||
width: 50,
|
||||
height: 60,
|
||||
spriteSheet: "frog",
|
||||
state: "idle-right",
|
||||
velocity: 0,
|
||||
acceleration: 0,
|
||||
yVelocity: 0,
|
||||
yAcceleration: 0
|
||||
}),
|
||||
animations: {
|
||||
"idle-right": {
|
||||
sequences: [1],
|
||||
velocity: 0,
|
||||
acceleration: 0,
|
||||
yVelocity: 0,
|
||||
yAcceleration: 0,
|
||||
scaleX: 1,
|
||||
scaleY: 1
|
||||
},
|
||||
"idle-left": {
|
||||
sequences: [1],
|
||||
velocity: 0,
|
||||
acceleration: 0,
|
||||
yVelocity: 0,
|
||||
yAcceleration: 0,
|
||||
scaleX: -1,
|
||||
scaleY: 1
|
||||
},
|
||||
"jump-right": {
|
||||
sequences: [2],
|
||||
velocity: velocity,
|
||||
acceleration: acceleration,
|
||||
yStartVelocity: -jumpVelocity,
|
||||
yEndVelocity: fallVelocity,
|
||||
yAscentAcceleration: jumpDeceleration,
|
||||
yHoldAscentAcceleration: jumpHoldDeceleration,
|
||||
yDescentAcceleration: fallAcceleration,
|
||||
scaleX: 1,
|
||||
scaleY: 1
|
||||
},
|
||||
"jump-left": {
|
||||
sequences: [2],
|
||||
velocity: -velocity,
|
||||
acceleration: acceleration,
|
||||
yStartVelocity: -jumpVelocity,
|
||||
yEndVelocity: fallVelocity,
|
||||
yAscentAcceleration: jumpDeceleration,
|
||||
yHoldAscentAcceleration: jumpHoldDeceleration,
|
||||
yDescentAcceleration: fallAcceleration,
|
||||
scaleX: -1,
|
||||
scaleY: 1
|
||||
},
|
||||
"dead-left": _.extend({}, Backbone.Hero.prototype.animations["dead-left"], {sequences: [0]}),
|
||||
"dead-right": _.extend({}, Backbone.Hero.prototype.animations["dead-right"], {sequences: [0]})
|
||||
},
|
||||
dirToggled: function(dirIntent) {
|
||||
if (this.ignoreInput()) return this;
|
||||
|
||||
if (dirIntent != "left" && dirIntent != "right")
|
||||
throw "Invalid or missing dirIntent. Must be left or right."
|
||||
|
||||
var cur = this.getStateInfo(),
|
||||
opoIntent = dirIntent == "right" ? "left" : "right",
|
||||
dirPressed = this.input ? this.input[dirIntent+"Pressed"]() : false,
|
||||
opoPressed = this.input ? this.input[opoIntent+"Pressed"]() : false,
|
||||
attrs = {};
|
||||
|
||||
if (dirPressed && cur.mov != "jump") {
|
||||
attrs.state = this.buildState(cur.mov, dirIntent);
|
||||
} else {
|
||||
if (opoPressed) this.dirToggled(opoIntent);
|
||||
}
|
||||
|
||||
if (!_.isEmpty(attrs)) this.set(attrs);
|
||||
|
||||
return this;
|
||||
},
|
||||
// Jump
|
||||
buttonAToggled: function() {
|
||||
if (this.ignoreInput()) return this;
|
||||
|
||||
var state = this.get("state"),
|
||||
cur = this.getStateInfo(),
|
||||
attrs = {};
|
||||
|
||||
if (this.input && this.input.buttonAPressed() && cur.mov != "jump") {
|
||||
attrs.state = this.buildState("jump", cur.dir);
|
||||
attrs.nextState = this.buildState("idle", cur.dir);
|
||||
var jumpAnimation = this.getAnimation(attrs.state);
|
||||
attrs.velocity = jumpAnimation.velocity;
|
||||
attrs.yVelocity = jumpAnimation.yStartVelocity;
|
||||
jumpAnimation.minY = this.get("y") - 200;
|
||||
}
|
||||
if (!_.isEmpty(attrs)) this.set(attrs);
|
||||
|
||||
return this;
|
||||
},
|
||||
// No action
|
||||
buttonBToggled: function() {
|
||||
return this;
|
||||
},
|
||||
onUpdate: function(dt) {
|
||||
var cur = this.getStateInfo(),
|
||||
velocity = this.get("velocity"),
|
||||
attrs = {};
|
||||
|
||||
// Upon landing...
|
||||
if (cur.mov == "idle" && velocity != 0) {
|
||||
// No momentum
|
||||
attrs.velocity = 0;
|
||||
// Turn around
|
||||
if (cur.dir == "left" && this.input && this.input.rightPressed() ||
|
||||
cur.dir == "right" && this.input && this.input.leftPressed())
|
||||
attrs.state = this.buildState(cur.mov, cur.opo);
|
||||
}
|
||||
if (!_.isEmpty(attrs)) this.set(attrs);
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
}).call(this);
|
||||
BIN
games/mario/frog/frog.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
games/mario/frog/icons.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
61
games/mario/frog/index.html
Normal file
@@ -0,0 +1,61 @@
|
||||
<!doctype html>
|
||||
<html style="touch-action: none;" nomanifest="offline.appcache">
|
||||
<head>
|
||||
<title>Frog - Backbone Game Engine</title>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<link href="../favicon.ico" rel="shortcut icon" type="image/x-icon" />
|
||||
<link href="../apple_touch_icon.png" rel="apple-touch-icon" />
|
||||
|
||||
<meta name="viewport" content="width=960, user-scalable=no"/>
|
||||
<meta name="mobileoptimized" content="0" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/>
|
||||
|
||||
<script src="../3rd/qtree.js" type="text/javascript"></script>
|
||||
<script src="../3rd/underscore.js" type="text/javascript"></script>
|
||||
<script src="../3rd/backbone.native.js" type="text/javascript"></script>
|
||||
<script src="../3rd/backbone.js" type="text/javascript"></script>
|
||||
|
||||
<script src="../src/adjust-viewport.js" type="text/javascript"></script>
|
||||
<script src="../src/shapes.js" type="text/javascript"></script>
|
||||
<script src="../src/core.js" type="text/javascript"></script>
|
||||
<script src="../src/input.js" type="text/javascript"></script>
|
||||
<script src="../src/character.js" type="text/javascript"></script>
|
||||
<script src="../src/hero.js" type="text/javascript"></script>
|
||||
<script src="../src/world.js" type="text/javascript"></script>
|
||||
<script src="../src/local-storage.js" type="text/javascript"></script>
|
||||
<script src="../src/camera.js" type="text/javascript"></script>
|
||||
<script src="../src/editor.js" type="text/javascript"></script>
|
||||
|
||||
<script src="frog.js" type="text/javascript"></script>
|
||||
<script src="tiles.js" type="text/javascript"></script>
|
||||
<script src="level.js" type="text/javascript"></script>
|
||||
<script src="main.js" type="text/javascript"></script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
background-color: #000;
|
||||
}
|
||||
canvas {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<img id="frog" src="frog.png" style="display:none;" />
|
||||
<img id="tiles" src="super-mario-tiles-2x.png" style="display:none;" />
|
||||
<img id="icons" src="icons.png" style="display:none;" />
|
||||
|
||||
<canvas id="foreground" width="960" height="700">
|
||||
Your browser does not support canvas element.
|
||||
</canvas>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
9
games/mario/frog/level.js
Normal file
237
games/mario/frog/main.js
Normal file
@@ -0,0 +1,237 @@
|
||||
$(window).on("load", function() {
|
||||
|
||||
/**
|
||||
*
|
||||
* Backbone Game Engine - An elementary HTML5 canvas game engine using Backbone.
|
||||
*
|
||||
* Copyright (c) 2014 Martin Drapeau
|
||||
* https://github.com/martindrapeau/backbone-game-engine
|
||||
*
|
||||
*/
|
||||
|
||||
var canvas = document.getElementById("foreground"),
|
||||
context = canvas.getContext("2d");
|
||||
adjustViewport(canvas);
|
||||
|
||||
var spriteNames = [
|
||||
"land1", "land2", "land3", "land4", "land5", "land6",
|
||||
"mush1", "mush2", "mush3", "mush4", "mush5", "mush6",
|
||||
"water1", "cloud1", "cloud2", "cloud3", "cloud-happy1", "cloud-happy2", "cloud-happy3",
|
||||
"cloud-small", "ground", "land8", "ground2", "block", "land7", "block2",
|
||||
"cloud-platform1", "cloud-platform2", "cloud-platform3", "cloud-platform4", "cloud-platform5", "cloud-platform6",
|
||||
"water2", "cloud4", "cloud5", "cloud6", "cloud-happy4", "cloud-happy5", "cloud-happy6",
|
||||
"frog", "platform"
|
||||
];
|
||||
|
||||
Backbone.Controller = Backbone.Model.extend({
|
||||
initialize: function(attributes, options) {
|
||||
options || (options = {});
|
||||
var controller = this;
|
||||
|
||||
_.bindAll(this, "onChangeState", "toggleState", "saveWorld", "loadWorld");
|
||||
|
||||
// Create our sprite sheets and attach them to existing sprite classes
|
||||
this.spriteSheets = new Backbone.SpriteSheetCollection([{
|
||||
id: "frog",
|
||||
img: "#frog",
|
||||
tileWidth: 50,
|
||||
tileHeight: 65,
|
||||
tileColumns: 3,
|
||||
tileRows: 1
|
||||
}, {
|
||||
id: "tiles",
|
||||
img: "#tiles",
|
||||
tileWidth: 32,
|
||||
tileHeight: 32,
|
||||
tileColumns: 29,
|
||||
tileRows: 28
|
||||
}]).attachToSpriteClasses();
|
||||
|
||||
// Create the debug panel
|
||||
this.debugPanel = new Backbone.DebugPanel();
|
||||
|
||||
// User input (turn off touchpad to start)
|
||||
this.input = new Backbone.Input({
|
||||
drawTouchpad: true
|
||||
});
|
||||
|
||||
// Camera
|
||||
this.camera = new Backbone.Camera();
|
||||
|
||||
// Our world
|
||||
this.world = new Backbone.World(
|
||||
_.extend({viewportBottom: 156}, window._world), {
|
||||
input: this.input,
|
||||
camera: this.camera
|
||||
});
|
||||
|
||||
// Message
|
||||
this.message = new Backbone.Message({
|
||||
x: 480, y: 20
|
||||
});
|
||||
|
||||
// Buttons
|
||||
this.toggleButton = new Backbone.Button({
|
||||
x: 4, y: 4, width: 52, height: 52, borderRadius: 5,
|
||||
img: "#icons", imgX: 0, imgY: 0, imgWidth: 32, imgHeight: 32, imgMargin: 10
|
||||
});
|
||||
this.toggleButton.on("tap", this.toggleState, this);
|
||||
|
||||
this.saveButton = new Backbone.Button({
|
||||
x: 904, y: 548, width: 52, height: 52, borderRadius: 5,
|
||||
img: "#icons", imgX: 96, imgY: 0, imgWidth: 32, imgHeight: 32, imgMargin: 10
|
||||
});
|
||||
this.saveButton.on("tap", this.saveWorld, this);
|
||||
|
||||
this.restartButton = new Backbone.Button({
|
||||
x: 904, y: 608, width: 52, height: 52, borderRadius: 5,
|
||||
img: "#icons", imgX: 128, imgY: 0, imgWidth: 32, imgHeight: 32, imgMargin: 10
|
||||
});
|
||||
this.restartButton.on("tap", this.restartWorld, this);
|
||||
|
||||
this.downloadButton = new Backbone.Button({
|
||||
x: 888, y: 10, width: 52, height: 52, borderRadius: 5,
|
||||
img: "#icons", imgX: 64, imgY: 0, imgWidth: 32, imgHeight: 32, imgMargin: 10
|
||||
});
|
||||
this.downloadButton.on("tap", this.downloadNewVersion, this);
|
||||
|
||||
// The game engine
|
||||
this.engine = new Backbone.Engine({}, {
|
||||
canvas: canvas,
|
||||
debugPanel: this.debugPanel
|
||||
});
|
||||
this.engine.add(_.compact([
|
||||
this.world,
|
||||
this.camera,
|
||||
this.toggleButton,
|
||||
this.message,
|
||||
this.debugPanel
|
||||
]));
|
||||
|
||||
// The sprite picker and editor
|
||||
this.editor = new Backbone.WorldEditor({
|
||||
spriteNames: spriteNames,
|
||||
width: 34*20+4
|
||||
}, {
|
||||
world: this.world
|
||||
});
|
||||
|
||||
// Controls
|
||||
$(document).on("keypress.Controller", function(e) {
|
||||
if (e.keyCode == 66 || e.keyCode == 98)
|
||||
controller.engine.toggle(); // b to break the animation
|
||||
else if (e.keyCode == 80 || e.keyCode == 112)
|
||||
controller.toggleState(); // p to pause and pause
|
||||
});
|
||||
|
||||
this.listenTo(this.world, "change:state", this.onChangeState);
|
||||
this.onChangeState();
|
||||
|
||||
// If we have Application Cache active, load the latest world
|
||||
this.loadWorld();
|
||||
},
|
||||
toggleState: function(e) {
|
||||
var state = this.world.get("state");
|
||||
this.world.set("state", state == "edit" ? "play" : "edit");
|
||||
if (!this.engine.isRunning()) this.engine.start();
|
||||
},
|
||||
onChangeState: function() {
|
||||
var state = this.world.get("state");
|
||||
if (state == "edit") {
|
||||
// Edit
|
||||
context.clearRect(0, 0, canvas.width, canvas.height);
|
||||
this.engine.remove(this.input);
|
||||
this.engine.add(this.editor);
|
||||
this.engine.add([this.saveButton, this.restartButton]);
|
||||
this.toggleButton.set({imgX: 32});
|
||||
} else {
|
||||
// Play
|
||||
context.clearRect(0, 0, canvas.width, canvas.height);
|
||||
this.engine.remove(this.editor);
|
||||
this.engine.remove([this.saveButton, this.restartButton]);
|
||||
this.engine.add(this.input);
|
||||
this.toggleButton.set({imgX: 0});
|
||||
}
|
||||
},
|
||||
// Save our world to the server when changed. Debounce by 5 seconds
|
||||
// and push back saving upon activity
|
||||
saveWorld: function() {
|
||||
var controller = this,
|
||||
world = this.world
|
||||
message = this.message;
|
||||
|
||||
message.show("Saving...");
|
||||
world.save(null, {
|
||||
success: function() {
|
||||
setTimeout(function() {
|
||||
message.hide();
|
||||
}, 1000);
|
||||
},
|
||||
error: function(xhr, textStatus, errorThrown ) {
|
||||
message.show("Error: " + errorThrown);
|
||||
}
|
||||
});
|
||||
return this;
|
||||
},
|
||||
// Load our world from the server.
|
||||
loadWorld: function() {
|
||||
var controller = this,
|
||||
world = this.world,
|
||||
message = this.message;
|
||||
|
||||
message.show("Loading...");
|
||||
world.fetch({
|
||||
success: function() {
|
||||
world.spawnSprites();
|
||||
message.hide();
|
||||
},
|
||||
error: function(xhr, textStatus, errorThrown ) {
|
||||
message.show('Error: ' + errorThrown);
|
||||
setTimeout(function() {
|
||||
message.hide();
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
return this;
|
||||
},
|
||||
restartWorld: function() {
|
||||
var controller = this,
|
||||
world = this.world,
|
||||
message = this.message;
|
||||
|
||||
message.show("Restarting...");
|
||||
|
||||
localStorage.removeItem(world.id);
|
||||
|
||||
world.set(window._world).spawnSprites();
|
||||
|
||||
setTimeout(function() {
|
||||
message.hide();
|
||||
}, 2000);
|
||||
|
||||
return this;
|
||||
},
|
||||
downloadNewVersion: function() {
|
||||
window.applicationCache.swapCache();
|
||||
this.message.show("Please wait...");
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
|
||||
var controller = new Backbone.Controller();
|
||||
|
||||
// When a newer version is available, load it and inform
|
||||
// the user it can be downloaded.
|
||||
if (window.applicationCache !== undefined)
|
||||
window.applicationCache.addEventListener('updateready', function() {
|
||||
controller.engine.add(controller.downloadButton);
|
||||
});
|
||||
|
||||
// Expose things as globals - easier to debug
|
||||
_.extend(window, {
|
||||
canvas: canvas,
|
||||
context: context,
|
||||
controller: controller
|
||||
});
|
||||
|
||||
});
|
||||
BIN
games/mario/frog/super-mario-tiles-2x.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
164
games/mario/frog/tiles.js
Normal file
@@ -0,0 +1,164 @@
|
||||
(function() {
|
||||
|
||||
/**
|
||||
*
|
||||
* Backbone Game Engine - An elementary HTML5 canvas game engine using Backbone.
|
||||
*
|
||||
* Copyright (c) 2014 Martin Drapeau
|
||||
* https://github.com/martindrapeau/backbone-game-engine
|
||||
*
|
||||
*/
|
||||
|
||||
Backbone.Tile = Backbone.Sprite.extend({
|
||||
defaults: {
|
||||
type: "tile",
|
||||
width: 32,
|
||||
height: 32,
|
||||
spriteSheet: "tiles",
|
||||
state: "idle",
|
||||
static: true,
|
||||
persist: true
|
||||
},
|
||||
initialize: function(attributes, options) {
|
||||
options || (options = {});
|
||||
this.world = options.world;
|
||||
this.lastSequenceChangeTime = 0;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function extendSprite(cls, name, attributes, animations) {
|
||||
var newCls = _.classify(name);
|
||||
Backbone[newCls] = Backbone[cls].extend({
|
||||
defaults: _.extend(
|
||||
_.deepClone(Backbone[cls].prototype.defaults),
|
||||
{name: name},
|
||||
attributes || {}
|
||||
),
|
||||
animations: _.extend(
|
||||
_.deepClone(Backbone[cls].prototype.animations),
|
||||
animations || {}
|
||||
)
|
||||
});
|
||||
return Backbone[newCls];
|
||||
}
|
||||
|
||||
extendSprite("Tile", "land1", {collision: true}, {idle: {sequences: [353]}});
|
||||
|
||||
extendSprite("Tile", "land2", {collision: true}, {idle: {sequences: [354]}});
|
||||
|
||||
extendSprite("Tile", "land3", {collision: true}, {idle: {sequences: [355]}});
|
||||
|
||||
extendSprite("Tile", "land4", {collision: true}, {idle: {sequences: [237]}});
|
||||
|
||||
extendSprite("Tile", "land5", {collision: true}, {idle: {sequences: [238]}});
|
||||
|
||||
extendSprite("Tile", "land6", {collision: true}, {idle: {sequences: [239]}});
|
||||
|
||||
extendSprite("Tile", "land7", {collision: true}, {idle: {sequences: [208]}});
|
||||
|
||||
extendSprite("Tile", "land8", {collision: true}, {idle: {sequences: [34]}});
|
||||
|
||||
extendSprite("Tile", "mush1", {collision: true}, {idle: {sequences: [382]}});
|
||||
|
||||
extendSprite("Tile", "mush2", {collision: true}, {idle: {sequences: [383]}});
|
||||
|
||||
extendSprite("Tile", "mush3", {collision: true}, {idle: {sequences: [384]}});
|
||||
|
||||
extendSprite("Tile", "mush4", {collision: true}, {idle: {sequences: [266]}});
|
||||
|
||||
extendSprite("Tile", "mush5", {collision: true}, {idle: {sequences: [267]}});
|
||||
|
||||
extendSprite("Tile", "mush6", {collision: true}, {idle: {sequences: [268]}});
|
||||
|
||||
extendSprite("Tile", "ground", {collision: true}, {idle: {sequences: [0]}});
|
||||
|
||||
extendSprite("Tile", "ground2", {collision: true}, {idle: {sequences: [31]}});
|
||||
|
||||
extendSprite("Tile", "block", {collision: true}, {idle: {sequences: [3]}});
|
||||
|
||||
extendSprite("Tile", "block2", {collision: true}, {idle: {sequences: [29]}});
|
||||
|
||||
extendSprite("Tile", "cloud-small", {collision: true}, {idle: {sequences: [613]}});
|
||||
|
||||
extendSprite("Tile", "water1", {collision: false}, {idle: {sequences: [583]}});
|
||||
|
||||
extendSprite("Tile", "water2", {collision: false}, {idle: {sequences: [612]}});
|
||||
|
||||
extendSprite("Tile", "cloud1", {collision: false}, {idle: {sequences: [580]}});
|
||||
|
||||
extendSprite("Tile", "cloud2", {collision: false}, {idle: {sequences: [581]}});
|
||||
|
||||
extendSprite("Tile", "cloud3", {collision: false}, {idle: {sequences: [582]}});
|
||||
|
||||
extendSprite("Tile", "cloud-happy1", {collision: false}, {idle: {sequences: [585]}});
|
||||
|
||||
extendSprite("Tile", "cloud-happy2", {collision: false}, {idle: {sequences: [586]}});
|
||||
|
||||
extendSprite("Tile", "cloud-happy3", {collision: false}, {idle: {sequences: [587]}});
|
||||
|
||||
extendSprite("Tile", "cloud4", {collision: false}, {idle: {sequences: [609]}});
|
||||
|
||||
extendSprite("Tile", "cloud5", {collision: false}, {idle: {sequences: [610]}});
|
||||
|
||||
extendSprite("Tile", "cloud6", {collision: false}, {idle: {sequences: [611]}});
|
||||
|
||||
extendSprite("Tile", "cloud-happy4", {collision: false}, {idle: {sequences: [614]}});
|
||||
|
||||
extendSprite("Tile", "cloud-happy5", {collision: false}, {idle: {sequences: [615]}});
|
||||
|
||||
extendSprite("Tile", "cloud-happy6", {collision: false}, {idle: {sequences: [616]}});
|
||||
|
||||
extendSprite("Tile", "cloud-platform1", {collision: true}, {idle: {sequences: [588]}});
|
||||
|
||||
extendSprite("Tile", "cloud-platform2", {collision: true}, {idle: {sequences: [589]}});
|
||||
|
||||
extendSprite("Tile", "cloud-platform3", {collision: true}, {idle: {sequences: [590]}});
|
||||
|
||||
extendSprite("Tile", "cloud-platform4", {collision: true}, {idle: {sequences: [704]}});
|
||||
|
||||
extendSprite("Tile", "cloud-platform5", {collision: true}, {idle: {sequences: [705]}});
|
||||
|
||||
extendSprite("Tile", "cloud-platform6", {collision: true}, {idle: {sequences: [706]}});
|
||||
|
||||
Backbone.Platform = Backbone.Character.extend({
|
||||
defaults: {
|
||||
type: "character",
|
||||
name: "platform",
|
||||
width: 96,
|
||||
height: 32,
|
||||
spriteSheet: "tiles",
|
||||
tileX: 256,
|
||||
tileY: 640,
|
||||
state: "idle",
|
||||
static: false,
|
||||
collision: true
|
||||
},
|
||||
initialize: function(attributes, options) {
|
||||
options || (options = {});
|
||||
this.world = options.world;
|
||||
},
|
||||
update: function(dt) {
|
||||
return true;
|
||||
},
|
||||
draw: function(context, options) {
|
||||
options || (options = {});
|
||||
|
||||
var x = this.get("x") + (options.offsetX || 0),
|
||||
y = this.get("y") + (options.offsetY || 0),
|
||||
tileX = this.get("tileX"),
|
||||
tileY = this.get("tileY"),
|
||||
tileWidth = this.get("width"),
|
||||
tileHeight = this.get("height");
|
||||
|
||||
context.drawImage(
|
||||
this.spriteSheet.img,
|
||||
tileX, tileY, tileWidth, tileHeight,
|
||||
Math.round(x), Math.round(y), tileWidth, tileHeight
|
||||
);
|
||||
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
}).call(this);
|
||||
BIN
games/mario/gui/arcade.ttf
Normal file
BIN
games/mario/gui/arcade.woff
Normal file
89
games/mario/gui/elements.js
Normal file
@@ -0,0 +1,89 @@
|
||||
(function() {
|
||||
|
||||
Backbone.LabelButton = Backbone.Button.extend({
|
||||
defaults: _.extend({}, Backbone.Button.prototype.defaults, {
|
||||
x: 400,
|
||||
y: 400,
|
||||
width: 160,
|
||||
height: 100,
|
||||
backgroundColor: "transparent",
|
||||
text: "",
|
||||
textPadding: 12,
|
||||
textContextAttributes: {
|
||||
fillStyle: "#FFC221",
|
||||
font: "40px arcade",
|
||||
textBaseline: "middle",
|
||||
fontWeight: "normal",
|
||||
textAlign: "center"
|
||||
},
|
||||
easing: "easeInCubic",
|
||||
easingTime: 400
|
||||
})
|
||||
});
|
||||
|
||||
Backbone.Scene = Backbone.Element.extend({
|
||||
defaults: _.extend({}, Backbone.Element.prototype.defaults, {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 960,
|
||||
height: 700,
|
||||
backgroundColor: "#000",
|
||||
opacity: 0,
|
||||
text: "",
|
||||
easing: "easeInCubic",
|
||||
easingTime: 400
|
||||
}),
|
||||
initialize: function(attributes, options) {
|
||||
Backbone.Element.prototype.initialize.apply(this, arguments);
|
||||
options || (options = {});
|
||||
this.saved = options.saved;
|
||||
this.world = options.world;
|
||||
this.levels = options.levels;
|
||||
this.pauseButton = options.pauseButton;
|
||||
this.input = options.input;
|
||||
_.bindAll(this, "enter", "exit");
|
||||
},
|
||||
enter: function() {
|
||||
this.set("opacity", 0);
|
||||
this.fadeIn();
|
||||
return this;
|
||||
},
|
||||
exit: function() {
|
||||
this.set("opacity", 1);
|
||||
this.fadeOut();
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
Backbone.Panel = Backbone.Element.extend({
|
||||
defaults: _.extend({}, Backbone.Element.prototype.defaults, {
|
||||
x: 160,
|
||||
y: 720,
|
||||
width: 640,
|
||||
height: 140,
|
||||
backgroundColor: "transparent",
|
||||
img: "#gui", imgX: 0, imgY: 473, imgWidth: 640, imgHeight: 480, imgMargin: 0,
|
||||
text: "",
|
||||
textPadding: 24,
|
||||
textContextAttributes: {
|
||||
fillStyle: "#FFC221",
|
||||
font: "40px arcade",
|
||||
textBaseline: "middle",
|
||||
fontWeight: "normal",
|
||||
textAlign: "center"
|
||||
},
|
||||
easing: "easeOutCubic",
|
||||
easingTime: 600
|
||||
}),
|
||||
initialize: function(attributes, options) {
|
||||
Backbone.Element.prototype.initialize.apply(this, arguments);
|
||||
_.bindAll(this, "show");
|
||||
this.set("y", Backbone.HEIGHT);
|
||||
},
|
||||
show: function() {
|
||||
this.moveTo(this.get("x"), 100);
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
}).call(this);
|
||||
BIN
games/mario/gui/gui.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
61
games/mario/gui/index.html
Normal file
@@ -0,0 +1,61 @@
|
||||
<!doctype html>
|
||||
<html style="touch-action: none;">
|
||||
<head>
|
||||
<title>GUI - Backbone Game Engine</title>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<link href="../favicon.ico" rel="shortcut icon" type="image/x-icon" />
|
||||
<link href="../apple_touch_icon.png" rel="apple-touch-icon" />
|
||||
|
||||
<meta name="viewport" content="width=960, user-scalable=no"/>
|
||||
<meta name="mobileoptimized" content="0" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/>
|
||||
|
||||
<script src="../3rd/underscore.js" type="text/javascript"></script>
|
||||
<script src="../3rd/backbone.native.js" type="text/javascript"></script>
|
||||
<script src="../3rd/backbone.js" type="text/javascript"></script>
|
||||
<script src="../3rd/qtree.js" type="text/javascript"></script>
|
||||
|
||||
<script src="../src/adjust-viewport.js" type="text/javascript"></script>
|
||||
<script src="../src/shapes.js" type="text/javascript"></script>
|
||||
<script src="../src/core.js" type="text/javascript"></script>
|
||||
|
||||
<script src="elements.js" type="text/javascript"></script>
|
||||
<script src="title-screen.js" type="text/javascript"></script>
|
||||
<script src="main.js" type="text/javascript"></script>
|
||||
|
||||
<style>
|
||||
@-ms-viewport {
|
||||
width: 960px;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'arcade';
|
||||
src: url('arcade.ttf'), url('arcade.woff');
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
background-color: #000;
|
||||
font-family: "arcade";
|
||||
line-height: 1.2em;
|
||||
}
|
||||
canvas {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<img id="title-screen" src="title-screen.jpg" style="display:none;" />
|
||||
<img id="gui" src="gui.png" style="display:none;" />
|
||||
|
||||
<canvas id="foreground" width="960" height="720" screencanvas="true">
|
||||
Your browser does not support canvas element.
|
||||
</canvas>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
99
games/mario/gui/main.js
Normal file
@@ -0,0 +1,99 @@
|
||||
$(window).on("load", function() {
|
||||
|
||||
var NATIVE = navigator.isCocoonJS,
|
||||
MOBILE = "onorientationchange" in window ||
|
||||
window.navigator.msMaxTouchPoints ||
|
||||
window.navigator.isCocoonJS;
|
||||
|
||||
var canvas = document.getElementById("foreground"),
|
||||
context = canvas.getContext("2d");
|
||||
|
||||
if (MOBILE) {
|
||||
canvas.height = Math.round(Math.min(canvas.height, canvas.width * Math.min(window.innerHeight, window.innerWidth) / Math.max(window.innerHeight, window.innerWidth) ));
|
||||
} else {
|
||||
canvas.height = Math.round(Math.min(canvas.height, window.innerHeight));
|
||||
adjustViewport(canvas, canvas.width, canvas.height);
|
||||
}
|
||||
console.log("canvas.width=" + canvas.width + " canvas.height=" + canvas.height);
|
||||
|
||||
_.extend(Backbone, {
|
||||
NATIVE: NATIVE,
|
||||
MOBILE: MOBILE,
|
||||
HEIGHT: canvas.height,
|
||||
WIDTH: canvas.width
|
||||
});
|
||||
|
||||
Backbone.Controller = Backbone.Model.extend({
|
||||
initialize: function(attributes, options) {
|
||||
options || (options = {});
|
||||
var controller = this;
|
||||
|
||||
this.debugPanel = new Backbone.DebugPanel({}, {color: "#fff"});
|
||||
|
||||
this.titleScreenGui = new Backbone.TitleScreenGui({
|
||||
id: "titleScreenGui",
|
||||
}, {
|
||||
saved: {
|
||||
level: 1,
|
||||
coins: 10,
|
||||
time: 1000000
|
||||
}
|
||||
});
|
||||
|
||||
// The game engine
|
||||
this.engine = new Backbone.Engine({}, {
|
||||
canvas: canvas,
|
||||
debugPanel: this.debugPanel
|
||||
});
|
||||
|
||||
// Controls
|
||||
$(document).on("keypress.Controller", function(e) {
|
||||
if (e.keyCode == 66 || e.keyCode == 98)
|
||||
controller.engine.toggle(); // b to break the animation
|
||||
});
|
||||
|
||||
this.listenTo(this.engine, "play", this.play);
|
||||
this.listenTo(this.engine, "nextLevel", this.nextLevel);
|
||||
|
||||
this.showTitleScreen();
|
||||
},
|
||||
showTitleScreen: function() {
|
||||
this.engine.stop();
|
||||
this.engine.reset();
|
||||
if (this.debugPanel) this.debugPanel.clear();
|
||||
|
||||
// Start everything
|
||||
this.engine.set("clearOnDraw", true);
|
||||
|
||||
this.engine.add([
|
||||
this.titleScreenGui,
|
||||
this.debugPanel
|
||||
]);
|
||||
this.engine.start();
|
||||
},
|
||||
play: function() {
|
||||
this.engine.stop();
|
||||
this.engine.reset();
|
||||
if (this.debugPanel) this.debugPanel.clear();
|
||||
|
||||
// TO DO: start a new game. Set the state and add world, input, etc to engine
|
||||
},
|
||||
nextLevel: function() {
|
||||
this.engine.stop();
|
||||
this.engine.reset();
|
||||
if (this.debugPanel) this.debugPanel.clear();
|
||||
|
||||
// TO DO: continue an existing game. Reset the state and add world, input, etc to engine
|
||||
}
|
||||
});
|
||||
|
||||
var controller = new Backbone.Controller();
|
||||
|
||||
// Expose things as globals - easier to debug
|
||||
_.extend(window, {
|
||||
canvas: canvas,
|
||||
context: context,
|
||||
controller: controller,
|
||||
});
|
||||
|
||||
});
|
||||
BIN
games/mario/gui/title-screen.jpg
Normal file
|
After Width: | Height: | Size: 253 KiB |
258
games/mario/gui/title-screen.js
Normal file
@@ -0,0 +1,258 @@
|
||||
(function() {
|
||||
|
||||
Backbone.PullOutButton = Backbone.Button.extend({
|
||||
defaults: _.extend({}, Backbone.Button.prototype.defaults, {
|
||||
x: -372,
|
||||
width: 372,
|
||||
height: 76,
|
||||
backgroundColor: "transparent",
|
||||
img: "#gui", imgX: 0, imgY: 0, imgWidth: 372, imgHeight: 80, imgMargin: 0,
|
||||
textPadding: 12,
|
||||
textContextAttributes: {
|
||||
fillStyle: "#FFC221",
|
||||
font: "34px arcade",
|
||||
textBaseline: "middle",
|
||||
fontWeight: "normal",
|
||||
textAlign: "right"
|
||||
},
|
||||
easing: "easeOutCubic",
|
||||
easingTime: 600
|
||||
})
|
||||
});
|
||||
|
||||
Backbone.SavedGame = Backbone.Element.extend({
|
||||
defaults: _.extend({}, Backbone.Element.prototype.defaults, {
|
||||
x: 960,
|
||||
y: 280,
|
||||
width: 333,
|
||||
height: 80,
|
||||
backgroundColor: "transparent",
|
||||
img: "#gui", imgX: 0, imgY: 225, imgWidth: 333, imgHeight: 247, imgMargin: 0,
|
||||
text: "High Score",
|
||||
textPadding: 24,
|
||||
textContextAttributes: {
|
||||
fillStyle: "#FFC221",
|
||||
font: "34px arcade",
|
||||
textBaseline: "middle",
|
||||
fontWeight: "normal",
|
||||
textAlign: "left"
|
||||
},
|
||||
easing: "easeOutCubic",
|
||||
easingTime: 600
|
||||
}),
|
||||
initialize: function(attributes, options) {
|
||||
Backbone.Element.prototype.initialize.apply(this, arguments);
|
||||
this.saved = options.saved;
|
||||
},
|
||||
onAttach: function() {
|
||||
Backbone.Element.prototype.onAttach.apply(this, arguments);
|
||||
this.set("text", "Level " + (this.saved ? this.saved.level : "?"));
|
||||
},
|
||||
onDraw: function(context) {
|
||||
var x = this.get("x"),
|
||||
y = this.get("y"),
|
||||
coins = this.saved ? this.saved.coins : "?",
|
||||
time = this.saved ? _.ms2time(this.saved.time) : "?";
|
||||
context.font = "30px arcade";
|
||||
context.fillStyle = "#FFF";
|
||||
context.textBaseline = this.attributes.textContextAttributes.textBaseline;
|
||||
context.fontWeight = this.attributes.textContextAttributes.fontWeight;
|
||||
context.textAlign = this.attributes.textContextAttributes.textAlign;
|
||||
context.fillText(coins, x+80, y+105);
|
||||
context.fillText(time, x+80, y+170);
|
||||
}
|
||||
});
|
||||
|
||||
Backbone.Credits = Backbone.Panel.extend({
|
||||
defaults: _.extend({}, Backbone.Panel.prototype.defaults, {
|
||||
text: "",
|
||||
textContextAttributes: {
|
||||
fillStyle: "#FFC221",
|
||||
font: "40px arcade",
|
||||
textBaseline: "middle",
|
||||
fontWeight: "normal",
|
||||
textAlign: "center"
|
||||
}
|
||||
}),
|
||||
onDraw: function(context, options) {
|
||||
var b = this.toJSON(),
|
||||
y = b.y;
|
||||
|
||||
// Titles
|
||||
b.textContextAttributes.font = "30px arcade";
|
||||
b.textContextAttributes.fillStyle = "#FFC221";
|
||||
|
||||
b.text = "Graphics";
|
||||
b.y = y;
|
||||
this.drawText(b, context, options);
|
||||
|
||||
b.text = "Testing";
|
||||
b.y = y + 130;
|
||||
this.drawText(b, context, options);
|
||||
|
||||
b.text = "Story & Coding";
|
||||
b.y = y + 230;
|
||||
this.drawText(b, context, options);
|
||||
|
||||
// Content
|
||||
b.textContextAttributes.font = "24px arcade";
|
||||
b.textContextAttributes.fillStyle = "#FFF";
|
||||
|
||||
b.text = "???";
|
||||
b.y = y + 40;
|
||||
this.drawText(b, context, options);
|
||||
|
||||
b.text = "???";
|
||||
b.y = y + 170;
|
||||
this.drawText(b, context, options);
|
||||
|
||||
b.text = "???";
|
||||
b.y = y + 270;
|
||||
this.drawText(b, context, options);
|
||||
|
||||
b.textContextAttributes.fillStyle = "#DDD";
|
||||
b.text = "Built with Backbone Game Engine";
|
||||
b.y = y + 340;
|
||||
this.drawText(b, context, options);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
Backbone.TitleScreenGui = Backbone.Scene.extend({
|
||||
defaults: _.extend({}, Backbone.Scene.prototype.defaults, {
|
||||
img: "#title-screen",
|
||||
imgWidth: 960,
|
||||
imgHeight: 700
|
||||
}),
|
||||
initialize: function(attributes, options) {
|
||||
Backbone.Scene.prototype.initialize.apply(this, arguments);
|
||||
|
||||
this.banner = new Backbone.Button({
|
||||
x: 0, y: 240,
|
||||
width: 960, height: 145,
|
||||
backgroundColor: "transparent",
|
||||
img: "#gui", imgX: 0, imgY: 80, imgWidth: 960, imgHeight: 144, imgMargin: 5,
|
||||
easing: "easeInOutQuad",
|
||||
easingTime: 400
|
||||
});
|
||||
|
||||
this.touchStart = new Backbone.LabelButton({
|
||||
text: "Touch to start",
|
||||
opacity: 0
|
||||
});
|
||||
|
||||
this.loading = new Backbone.LabelButton({
|
||||
y: Backbone.HEIGHT,
|
||||
text: "Loading...",
|
||||
easingTime: 300
|
||||
});
|
||||
|
||||
this.play = new Backbone.PullOutButton({
|
||||
y: Backbone.HEIGHT - 300,
|
||||
text: "New Game "
|
||||
});
|
||||
this.play.on("tap", _.partial(this.action, "play"), this);
|
||||
|
||||
this.showCredits = new Backbone.PullOutButton({
|
||||
y: Backbone.HEIGHT - 100,
|
||||
text: "Credits "
|
||||
});
|
||||
|
||||
this.savedGame = new Backbone.SavedGame({
|
||||
y: Backbone.HEIGHT - 300
|
||||
}, {
|
||||
saved: this.saved
|
||||
});
|
||||
|
||||
|
||||
this.credits = new Backbone.Credits();
|
||||
this.showCredits.on("tap", _.partial(this.showPanel, this.credits), this);
|
||||
},
|
||||
postInitialize: function() {
|
||||
this.listenTo(this.engine, "tap", this.onTouchStart);
|
||||
|
||||
// Hack to avoid FOUT
|
||||
this.touchStart.set("opacity", 1);
|
||||
this.play.textMetrics = undefined;
|
||||
this.showCredits.textMetrics = undefined;
|
||||
},
|
||||
onAttach: function() {
|
||||
Backbone.Scene.prototype.onAttach.apply(this, arguments);
|
||||
this.stopListening(this.engine);
|
||||
this.set("opacity", 1);
|
||||
this.loading.set("x", Backbone.HEIGHT);
|
||||
|
||||
this.play.set("text", this.saved ? "Continue " : "New Game ");
|
||||
|
||||
this.engine.add([this.banner, this.touchStart, this.loading, this.play, this.showCredits, this.credits, this.savedGame]);
|
||||
|
||||
if (!this.ready)
|
||||
setTimeout(this.postInitialize.bind(this), 200);
|
||||
else
|
||||
setTimeout(this.showButtons.bind(this), 100);
|
||||
},
|
||||
onDetach: function() {
|
||||
Backbone.Scene.prototype.onDetach.apply(this, arguments);
|
||||
this.engine.remove([this.banner, this.touchStart, this.loading, this.play, this.showCredits, this.credits, this.savedGame]);
|
||||
},
|
||||
onTouchStart: function(e) {
|
||||
// Animate some stuff
|
||||
this.banner.moveTo(this.banner.get("x"), 50);
|
||||
this.touchStart.moveTo(this.touchStart.get("x"), Backbone.HEIGHT);
|
||||
this.stopListening(this.engine);
|
||||
this.ready = true;
|
||||
this.showButtons();
|
||||
},
|
||||
showButtons: function() {
|
||||
this.play.moveTo(-this.play.get("width") + this.play.textMetrics.width + this.play.get("textPadding")*2, this.play.get("y"));
|
||||
this.showCredits.moveTo(-this.showCredits.get("width") + this.showCredits.textMetrics.width + this.showCredits.get("textPadding")*2, this.showCredits.get("y"));
|
||||
if (this.saved)
|
||||
this.savedGame.moveTo(720, this.savedGame.get("y"));
|
||||
},
|
||||
hideButtons: function() {
|
||||
this.play.moveTo(-this.play.get("width"), this.play.get("y"));
|
||||
this.showCredits.moveTo(-this.showCredits.get("width"), this.showCredits.get("y"));
|
||||
this.savedGame.moveTo(960, this.savedGame.get("y"));
|
||||
},
|
||||
showPanel: function(panel) {
|
||||
this.panel = panel;
|
||||
this.panel.moveTo(this.panel.get("x"), 50);
|
||||
this.hideButtons();
|
||||
this.listenTo(this.engine, "tap", this.hidePanel);
|
||||
},
|
||||
hidePanel: function() {
|
||||
this.stopListening(this.engine);
|
||||
this.panel.moveTo(this.panel.get("x"), Backbone.HEIGHT);
|
||||
this.panel = undefined;
|
||||
this.showButtons();
|
||||
},
|
||||
update: function(dt) {
|
||||
if (!Backbone.Scene.prototype.update.apply(this, arguments)) return false;
|
||||
|
||||
var attrs = {opacity: this.get("opacity")},
|
||||
options = {silent: true};
|
||||
|
||||
this.banner.set(attrs, options);
|
||||
this.touchStart.set(attrs, options);
|
||||
this.loading.set(attrs, options);
|
||||
this.play.set(attrs, options);
|
||||
this.showCredits.set(attrs, options);
|
||||
this.credits.set(attrs, options);
|
||||
this.savedGame.set(attrs, options);
|
||||
|
||||
return true;
|
||||
},
|
||||
action: function(event) {
|
||||
if (event == "play") this.loading.set("x", 400);
|
||||
|
||||
var gui = this;
|
||||
this.hideButtons();
|
||||
setTimeout(function() {
|
||||
gui.fadeOut(function() {
|
||||
gui.engine.trigger(event);
|
||||
});
|
||||
}, 400);
|
||||
}
|
||||
});
|
||||
|
||||
}).call(this);
|
||||
1870
games/mario/index.html
Normal file
52
games/mario/mario/index.html
Normal file
@@ -0,0 +1,52 @@
|
||||
<!doctype html>
|
||||
<html style="touch-action: none;">
|
||||
<head>
|
||||
<title>Mario - Backbone Game Engine</title>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<link href="../favicon.ico" rel="shortcut icon" type="image/x-icon" />
|
||||
<link href="../apple_touch_icon.png" rel="apple-touch-icon" />
|
||||
|
||||
<meta name="viewport" content="width=960, user-scalable=no"/>
|
||||
<meta name="mobileoptimized" content="0" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/>
|
||||
|
||||
<script src="../3rd/qtree.js" type="text/javascript"></script>
|
||||
<script src="../3rd/underscore.js" type="text/javascript"></script>
|
||||
<script src="../3rd/backbone.native.js" type="text/javascript"></script>
|
||||
<script src="../3rd/backbone.js" type="text/javascript"></script>
|
||||
|
||||
<script src="../src/adjust-viewport.js" type="text/javascript"></script>
|
||||
<script src="../src/shapes.js" type="text/javascript"></script>
|
||||
<script src="../src/core.js" type="text/javascript"></script>
|
||||
<script src="../src/input.js" type="text/javascript"></script>
|
||||
<script src="../src/character.js" type="text/javascript"></script>
|
||||
<script src="../src/hero.js" type="text/javascript"></script>
|
||||
<script src="../src/world.js" type="text/javascript"></script>
|
||||
|
||||
<script src="main.js" type="text/javascript"></script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
background-color: #000;
|
||||
}
|
||||
canvas {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<img id="mario" src="super-mario-2x.png" style="display:none;" />
|
||||
<canvas id="foreground" width="960" height="700">
|
||||
Your browser does not support canvas element.
|
||||
</canvas>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
75
games/mario/mario/main.js
Normal file
@@ -0,0 +1,75 @@
|
||||
$(window).on("load", function() {
|
||||
|
||||
/**
|
||||
*
|
||||
* Backbone Game Engine - An elementary HTML5 canvas game engine using Backbone.
|
||||
*
|
||||
* Copyright (c) 2014 Martin Drapeau
|
||||
* https://github.com/martindrapeau/backbone-game-engine
|
||||
*
|
||||
*/
|
||||
|
||||
// Mario alone in an empty world. Control him with the touchpad.
|
||||
|
||||
Backbone.Mario = Backbone.Hero.extend({
|
||||
defaults: _.extend({}, Backbone.Hero.prototype.defaults, {
|
||||
name: "mario",
|
||||
spriteSheet: "mario"
|
||||
})
|
||||
});
|
||||
|
||||
var canvas = document.getElementById("foreground");
|
||||
adjustViewport(canvas);
|
||||
|
||||
var spriteSheets = new Backbone.SpriteSheetCollection([{
|
||||
id: "mario",
|
||||
img: "#mario",
|
||||
tileWidth: 32,
|
||||
tileHeight: 64,
|
||||
tileColumns: 21,
|
||||
tileRows: 6
|
||||
}]).attachToSpriteClasses();
|
||||
|
||||
var debugPanel = new Backbone.DebugPanel();
|
||||
|
||||
var input = new Backbone.Input({
|
||||
drawTouchpad: true,
|
||||
drawPause: true
|
||||
});
|
||||
|
||||
var mario = new Backbone.Mario({
|
||||
x: 400, y: 200, floor: 500
|
||||
}, {
|
||||
input: input
|
||||
});
|
||||
|
||||
var world = new Backbone.World({
|
||||
width: 30, height: 17,
|
||||
tileWidth: 32, tileHeight: 32,
|
||||
viewportBottom: 156,
|
||||
backgroundColor: "rgba(66, 66, 255, 1)"
|
||||
}, {
|
||||
input: input
|
||||
});
|
||||
world.add(mario);
|
||||
|
||||
var engine = new Backbone.Engine({}, {
|
||||
canvas: canvas,
|
||||
debugPanel: debugPanel,
|
||||
input: input
|
||||
});
|
||||
engine.add([
|
||||
world,
|
||||
input,
|
||||
debugPanel
|
||||
]);
|
||||
|
||||
// Expose things as globals - easier to debug
|
||||
_.extend(window, {
|
||||
canvas: canvas,
|
||||
engine: engine,
|
||||
world: world,
|
||||
mario: mario
|
||||
});
|
||||
|
||||
});
|
||||
BIN
games/mario/mario/super-mario-2x.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
64
games/mario/src/adjust-viewport.js
Normal file
@@ -0,0 +1,64 @@
|
||||
(function() {
|
||||
|
||||
/**
|
||||
*
|
||||
* Backbone Game Engine - An elementary HTML5 canvas game engine using Backbone.
|
||||
*
|
||||
* Copyright (c) 2014 Martin Drapeau
|
||||
* https://github.com/martindrapeau/backbone-game-engine
|
||||
*
|
||||
*/
|
||||
|
||||
// Ensures the canvas is always full size and/or centered.
|
||||
//
|
||||
// On mobile:
|
||||
// Works in conjunction with meta tag viewport where width is set to the canvas width.
|
||||
// <meta name="viewport" content="width=960, user-scalable=no"/>
|
||||
// Will modify the canvas height to fit the device aspect ratio, never exceeding the
|
||||
// origin canvas height.
|
||||
// Set keepRatio to true to maintain your aspect ratio. In such a case, the viewport
|
||||
// is adjusted to the height of the canvas. The canvas is kept centered on screen (with
|
||||
// black bars left and right to fill empty space).
|
||||
//
|
||||
// On Desktop:
|
||||
// The viewport meta tag is ignored. Instead, the canvas is kept centered. The height
|
||||
// may be reduced if the window height is less than the canvas (to avoid scrolling).
|
||||
// Set keepRatio to maintain the canvas height.
|
||||
|
||||
function adjustViewport(canvas, keepRatio) {
|
||||
|
||||
var viewport = typeof document.querySelector == "function" ? document.querySelector("meta[name=viewport]") : null,
|
||||
mobile = "onorientationchange" in window ||
|
||||
window.navigator.msMaxTouchPoints ||
|
||||
window.navigator.isCocoonJS;
|
||||
|
||||
function onResize() {
|
||||
if (window.innerWidth > window.innerHeight) {
|
||||
// Landscape
|
||||
canvas.style.left = _.max([0, (window.innerWidth - canvas.width) / 2]) + "px";
|
||||
if (mobile && viewport)
|
||||
viewport.setAttribute("content", "width=" + Math.ceil(canvas.height * window.innerWidth / window.innerHeight) + ",user-scalable=no");
|
||||
} else {
|
||||
// Portrait
|
||||
canvas.style.left = "0px";
|
||||
if (mobile && viewport)
|
||||
viewport.setAttribute("content", "width=" + canvas.width + ",user-scalable=no");
|
||||
}
|
||||
}
|
||||
|
||||
if (mobile && !keepRatio) {
|
||||
canvas.height = Math.round(Math.min(canvas.height, canvas.width * Math.min(window.innerHeight, window.innerWidth) / Math.max(window.innerHeight, window.innerWidth) ));
|
||||
} else {
|
||||
if (!keepRatio)
|
||||
canvas.height = Math.round(Math.min(canvas.height, window.innerHeight));
|
||||
window.addEventListener("resize", _.debounce(onResize, 300));
|
||||
setTimeout(onResize, 10);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_.extend(window, {
|
||||
adjustViewport: adjustViewport
|
||||
});
|
||||
|
||||
}).call(this);
|
||||
84
games/mario/src/camera.js
Normal file
@@ -0,0 +1,84 @@
|
||||
(function() {
|
||||
|
||||
/**
|
||||
*
|
||||
* Backbone Game Engine - An elementary HTML5 canvas game engine using Backbone.
|
||||
*
|
||||
* Copyright (c) 2014 Martin Drapeau
|
||||
* https://github.com/martindrapeau/backbone-game-engine
|
||||
*
|
||||
*/
|
||||
|
||||
// Camera class
|
||||
// Ensures the hero is always in the viewport.
|
||||
// Properly pans the world.
|
||||
Backbone.Camera = Backbone.Model.extend({
|
||||
defaults: {
|
||||
left: 200,
|
||||
right: 400,
|
||||
top: 100,
|
||||
bottom: 100
|
||||
},
|
||||
initialize: function(attributes, options) {
|
||||
this.setOptions(options || {});
|
||||
},
|
||||
setOptions: function(options) {
|
||||
options || (options = {});
|
||||
_.extend(this, options || {});
|
||||
|
||||
this.stopListening();
|
||||
if (this.subject && this.world)
|
||||
this.listenTo(this.subject, "change:x change:y", this.maybePan);
|
||||
},
|
||||
maybePan: function() {
|
||||
if (!this.world) return this;
|
||||
var w = this.world.toShallowJSON(),
|
||||
worldX = w.x,
|
||||
worldY = w.y,
|
||||
worldWidth = w.width * w.tileWidth,
|
||||
worldHeight = w.height * w.tileHeight,
|
||||
viewportWidth = this.world.viewport.width,
|
||||
viewportHeight = this.world.viewport.height,
|
||||
subjectX = this.subject.get("x") + w.x,
|
||||
subjectY = this.subject.get("y") + w.y,
|
||||
subjectWidth = this.subject.get("tileWidth"),
|
||||
subjectHeight = this.subject.get("tileHeight"),
|
||||
left = this.get("left") + w.viewportLeft,
|
||||
right = w.viewportLeft + viewportWidth - this.get("right"),
|
||||
top = this.get("top") + w.viewportTop,
|
||||
bottom = w.viewportTop + viewportHeight - this.get("bottom");
|
||||
|
||||
if (subjectX < left && w.x < 0) {
|
||||
// Pan right (to see more left)
|
||||
worldX = w.x + (left - subjectX);
|
||||
if (worldX > 0) worldX = 0;
|
||||
} else if (subjectX > right && w.x + worldWidth > viewportWidth) {
|
||||
// Pan left (to see more right)
|
||||
worldX = w.x - (subjectX - right);
|
||||
if (worldX + worldWidth < viewportWidth)
|
||||
worldX = -worldWidth + viewportWidth;
|
||||
}
|
||||
|
||||
if (subjectY < top && w.y < 0) {
|
||||
// Pan down (to see more up)
|
||||
worldY = w.y + (top - subjectY);
|
||||
if (worldY > 0) worldY = 0;
|
||||
} else if (subjectY > bottom && w.y + worldHeight > viewportHeight) {
|
||||
// Pan up (to see more down)
|
||||
worldY = w.y - (subjectY - bottom);
|
||||
if (worldY + worldHeight < viewportHeight)
|
||||
worldY = -worldHeight + viewportHeight;
|
||||
}
|
||||
|
||||
if (worldX != w.x || worldY != w.y)
|
||||
this.world.set({x: worldX, y: worldY});
|
||||
},
|
||||
update: function(dt) {
|
||||
return false;
|
||||
},
|
||||
draw: function(context) {
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
}).call(this);
|
||||
512
games/mario/src/character.js
Normal file
@@ -0,0 +1,512 @@
|
||||
(function() {
|
||||
|
||||
/**
|
||||
*
|
||||
* Backbone Game Engine - An elementary HTML5 canvas game engine using Backbone.
|
||||
*
|
||||
* Copyright (c) 2014 Martin Drapeau
|
||||
* https://github.com/martindrapeau/backbone-game-engine
|
||||
*
|
||||
*/
|
||||
|
||||
var sequenceDelay = 300,
|
||||
walkVelocity = 50,
|
||||
fallAcceleration = 1200,
|
||||
fallVelocity = 600;
|
||||
|
||||
var animations = {
|
||||
"idle-left": {
|
||||
sequences: [0],
|
||||
delay: sequenceDelay,
|
||||
velocity: 0,
|
||||
scaleX: 1,
|
||||
scaleY: 1
|
||||
},
|
||||
"idle-right": {
|
||||
sequences: [0],
|
||||
delay: sequenceDelay,
|
||||
velocity: 0,
|
||||
scaleX: -1,
|
||||
scaleY: 1
|
||||
},
|
||||
"walk-left": {
|
||||
sequences: [1, 0],
|
||||
delay: sequenceDelay,
|
||||
velocity: -walkVelocity,
|
||||
scaleX: 1,
|
||||
scaleY: 1
|
||||
},
|
||||
"walk-right": {
|
||||
sequences: [1, 0],
|
||||
delay: sequenceDelay,
|
||||
velocity: walkVelocity,
|
||||
scaleX: -1,
|
||||
scaleY: 1
|
||||
},
|
||||
"fall-left": {
|
||||
sequences: [0],
|
||||
delay: sequenceDelay,
|
||||
velocity: -walkVelocity,
|
||||
yVelocity: fallVelocity,
|
||||
yAcceleration: fallAcceleration,
|
||||
scaleX: 1,
|
||||
scaleY: 1
|
||||
},
|
||||
"fall-right": {
|
||||
sequences: [0],
|
||||
delay: sequenceDelay,
|
||||
velocity: walkVelocity,
|
||||
yVelocity: fallVelocity,
|
||||
yAcceleration: fallAcceleration,
|
||||
scaleX: -1,
|
||||
scaleY: 1
|
||||
},
|
||||
"ko-left": {
|
||||
sequences: [0],
|
||||
delay: sequenceDelay,
|
||||
velocity: -walkVelocity,
|
||||
yVelocity: fallVelocity,
|
||||
yAcceleration: fallAcceleration,
|
||||
scaleX: 1,
|
||||
scaleY: -1
|
||||
},
|
||||
"ko-right": {
|
||||
sequences: [0],
|
||||
delay: sequenceDelay,
|
||||
velocity: walkVelocity,
|
||||
yVelocity: fallVelocity,
|
||||
yAcceleration: fallAcceleration,
|
||||
scaleX: -1,
|
||||
scaleY: -1
|
||||
}
|
||||
};
|
||||
|
||||
var hurtAnimation = {
|
||||
sequences: [0],
|
||||
delay: 300,
|
||||
yVelocity: fallVelocity,
|
||||
yAcceleration: fallAcceleration
|
||||
};
|
||||
animations["idle-hurt-left"] = _.extend({}, animations["idle-left"], hurtAnimation);
|
||||
animations["idle-hurt-right"] = _.extend({}, animations["idle-right"], hurtAnimation);
|
||||
animations["walk-hurt-left"] = _.extend({}, animations["walk-left"], hurtAnimation);
|
||||
animations["walk-hurt-right"] = _.extend({}, animations["walk-right"], hurtAnimation);
|
||||
animations["fall-hurt-left"] = _.extend({}, animations["fall-left"], hurtAnimation);
|
||||
animations["fall-hurt-right"] = _.extend({}, animations["fall-right"], hurtAnimation);
|
||||
|
||||
|
||||
Backbone.Character = Backbone.Sprite.extend({
|
||||
defaults: _.extend({}, Backbone.Sprite.prototype.defaults, {
|
||||
name: "character",
|
||||
type: "character",
|
||||
width: 32,
|
||||
height: 32,
|
||||
zIndex: 1,
|
||||
spriteSheet: undefined,
|
||||
state: "walk-left",
|
||||
velocity: 0,
|
||||
yVelocity: 0,
|
||||
collision: true,
|
||||
static: false,
|
||||
visible: true,
|
||||
health: 1,
|
||||
attackDamage: 1,
|
||||
floor: null,
|
||||
ceiling: null,
|
||||
aiDelay: 1000
|
||||
}),
|
||||
animations: animations,
|
||||
initialize: function(attributes, options) {
|
||||
Backbone.Sprite.prototype.initialize.apply(this, arguments);
|
||||
options || (options = {});
|
||||
|
||||
this.on("attach", this.onAttach, this);
|
||||
this.on("detach", this.onDetach, this);
|
||||
|
||||
this.on("hit", this.hit, this);
|
||||
this.on("change:health", this.onHealthChange, this);
|
||||
this.on("beforeFall", this.onBeforeFall, this);
|
||||
},
|
||||
onAttach: function() {
|
||||
if (!this.engine) return;
|
||||
this.onDetach();
|
||||
},
|
||||
onDetach: function() {
|
||||
},
|
||||
onBeforeFall: function() {
|
||||
return this;
|
||||
},
|
||||
onHealthChange: function(model, health, options) {
|
||||
options || (options = {});
|
||||
var cur = this.getStateInfo(),
|
||||
dir = options.dir || cur.dir,
|
||||
opo = dir == "left" ? "right" : "left";
|
||||
|
||||
if (health == 0)
|
||||
return this.knockout(options.sprite || null, dir, options.dir2 || null);
|
||||
else if (health < this.previous("health"))
|
||||
return this.hurt(options.sprite || null, dir, options.dir2 || null);
|
||||
|
||||
this.lastAIEvent = _.now();
|
||||
|
||||
return this;
|
||||
},
|
||||
knockout: function(sprite, dir) {
|
||||
var opo = dir == "left" ? "right" : "left",
|
||||
state = this.buildState("ko", opo);
|
||||
this.whenAnimationEnds = null;
|
||||
this.set({
|
||||
state: state,
|
||||
velocity: this.animations[state].velocity,
|
||||
yVelocity: -this.animations[state].yVelocity,
|
||||
sequenceIndex: 0,
|
||||
collision: false
|
||||
});
|
||||
this.cancelUpdate = true;
|
||||
return this;
|
||||
},
|
||||
hurt: function(sprite, dir) {
|
||||
this.whenAnimationEnds = null;
|
||||
this.set({
|
||||
state: this.buildState("fall", "hurt", dir),
|
||||
velocity: this.animations["ko-"+dir].velocity,
|
||||
yVelocity: -this.animations["ko-"+dir].yVelocity,
|
||||
sequenceIndex: 0
|
||||
});
|
||||
return this;
|
||||
},
|
||||
hit: function(sprite, dir, dir2) {
|
||||
if (this._handlingSpriteHit) return this;
|
||||
this._handlingSpriteHit = sprite;
|
||||
|
||||
var cur = this.getStateInfo();
|
||||
|
||||
if (cur.mov2 == "hurt") return this;
|
||||
|
||||
if (dir2 == "attack") {
|
||||
this.cancelUpdate = true;
|
||||
var attackDamage = sprite.get("attackDamage") || 1;
|
||||
this.set({health: Math.max(this.get("health") - attackDamage, 0)}, {sprite: sprite, dir: dir, dir2: dir2});
|
||||
} else if (cur.dir == dir && cur.mov2 == null) {
|
||||
this.cancelUpdate = true;
|
||||
this.set("state", this.buildState(cur.mov, cur.opo));
|
||||
}
|
||||
|
||||
tis._handlingSpriteHit = undefined;
|
||||
return this;
|
||||
},
|
||||
startNewAnimation: function(state, attrs, done) {
|
||||
this.lastSequenceChangeTime = _.now();
|
||||
this.set(_.extend({
|
||||
state: state,
|
||||
sequenceIndex: 0
|
||||
}, attrs));
|
||||
this.whenAnimationEnds = done;
|
||||
return this;
|
||||
},
|
||||
updateSequenceIndex: function(dt) {
|
||||
var sequenceIndex = this.get("sequenceIndex"),
|
||||
animation = this.getAnimation(),
|
||||
delay = animation.delay || 0,
|
||||
now = _.now(),
|
||||
triggerAnimationEnd = false;
|
||||
|
||||
if (!animation.sequences) {
|
||||
sequenceIndex = 0;
|
||||
this.lastSequenceChangeTime = now;
|
||||
} else if (sequenceIndex >= animation.sequences.length) {
|
||||
sequenceIndex = 0;
|
||||
this.lastSequenceChangeTime = now;
|
||||
triggerAnimationEnd = true;
|
||||
} else if (delay && now > this.lastSequenceChangeTime + delay) {
|
||||
sequenceIndex = sequenceIndex < animation.sequences.length-1 ? sequenceIndex + 1 : 0;
|
||||
this.lastSequenceChangeTime = now;
|
||||
if (sequenceIndex == 0) triggerAnimationEnd = true;
|
||||
}
|
||||
|
||||
if (triggerAnimationEnd && typeof this.whenAnimationEnds == "function") {
|
||||
this.whenAnimationEnds.call(this);
|
||||
this.whenAnimationEnds = null;
|
||||
}
|
||||
|
||||
return sequenceIndex;
|
||||
},
|
||||
ai: function(dt) {
|
||||
return this;
|
||||
},
|
||||
update: function(dt) {
|
||||
// Movements are only possible inside a world
|
||||
if (!this.world) return true;
|
||||
this.cancelUpdate = false;
|
||||
|
||||
// Velocity and state
|
||||
var self = this,
|
||||
velocity = this.get("velocity") || 0,
|
||||
yVelocity = this.get("yVelocity") || 0,
|
||||
x = this.get("x"),
|
||||
y = this.get("y"),
|
||||
state = this.get("state"),
|
||||
cur = this.getStateInfo(),
|
||||
animation = this.getAnimation(),
|
||||
now = _.now(),
|
||||
aiDelay = this.get("aiDelay"),
|
||||
attrs = {};
|
||||
|
||||
// Handle AI
|
||||
if (!this.lastAIEvent)
|
||||
this.lastAIEvent = now;
|
||||
else if (now > this.lastAIEvent + aiDelay) {
|
||||
this.ai(now - this.lastAIEvent);
|
||||
this.lastAIEvent = now;
|
||||
if (this.cancelUpdate) return true;
|
||||
}
|
||||
|
||||
if ((cur.mov == "ko" || cur.mov2 == "hurt") &&
|
||||
this.get("sequenceIndex") == animation.sequences.length-1) {
|
||||
// No sequence change - stay on last one
|
||||
} else {
|
||||
attrs.sequenceIndex = this.updateSequenceIndex();
|
||||
}
|
||||
|
||||
if (velocity != animation.velocity) velocity = animation.velocity || 0;
|
||||
|
||||
if (cur.mov == "fall" || cur.mov == "ko" || cur.mov2 == "hurt") {
|
||||
if (yVelocity < animation.yVelocity)
|
||||
yVelocity += animation.yAcceleration * (dt/1000);
|
||||
|
||||
if (yVelocity >= animation.yVelocity)
|
||||
yVelocity = animation.yVelocity;
|
||||
attrs.yVelocity = yVelocity;
|
||||
}
|
||||
|
||||
|
||||
// Collision detection
|
||||
var collision = this.get("collision"),
|
||||
tileWidth = this.get("width"),
|
||||
tileHeight = this.get("height"),
|
||||
paddingLeft = this.get("paddingLeft"),
|
||||
paddingRight = this.get("paddingRight"),
|
||||
paddingBottom = this.get("paddingBottom"),
|
||||
paddingTop = this.get("paddingTop"),
|
||||
charWidth = tileWidth - paddingLeft - paddingRight,
|
||||
charHeight = tileHeight - paddingTop - paddingBottom,
|
||||
charLeftX = Math.round(x + velocity * (dt/1000)) + paddingLeft,
|
||||
charRightX = charLeftX + charWidth,
|
||||
bottomWorld = this.world.height() + tileHeight,
|
||||
relativeVelocity = 0,
|
||||
bottomY = _.minNotNull([
|
||||
this.get("floor"),
|
||||
bottomWorld
|
||||
]);
|
||||
|
||||
var charBottomY, charTopY,
|
||||
bottomPlatform, sprite, i, type;
|
||||
function updateTopBottom() {
|
||||
charBottomY = Math.round(y + yVelocity * (dt/1000)) + tileHeight - paddingBottom,
|
||||
charTopY = charBottomY - charHeight,
|
||||
self.buildCollisionMap(charTopY, charRightX, charBottomY, charLeftX);
|
||||
if (collision)
|
||||
self.world.findCollisions(self.collisionMap, null, self, true);
|
||||
}
|
||||
updateTopBottom();
|
||||
|
||||
for (i = 0; i < this.collisionMap.bottom.sprites.length; i++) {
|
||||
sprite = this.collisionMap.bottom.sprites[i];
|
||||
type = sprite.get("type")
|
||||
if (type == "tile" || type == "platform")
|
||||
bottomY = Math.min(bottomY, sprite.getTop(true));
|
||||
if (type == "platform") bottomPlatform = sprite;
|
||||
}
|
||||
|
||||
if (yVelocity >= 0) {
|
||||
// Walking or Falling...
|
||||
if (charBottomY >= bottomY) {
|
||||
if (charBottomY >= bottomWorld) {
|
||||
this.world.remove(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (i = 0; i < this.collisionMap.bottom.sprites.length; i++)
|
||||
if (cur.mov != "ko")
|
||||
this.collisionMap.bottom.sprites[i].trigger("hit", this, "top");
|
||||
if (this.cancelUpdate) return this;
|
||||
|
||||
// Stop falling because obstacle below
|
||||
attrs.yVelocity = yVelocity = 0;
|
||||
attrs.y = y = bottomY - tileHeight + paddingBottom;
|
||||
if (cur.mov == "fall")
|
||||
attrs.state = this.buildState("walk", cur.mov2, cur.dir);
|
||||
else if (cur.mov == "ko") {
|
||||
attrs.velocity = velocity = 0;
|
||||
}
|
||||
updateTopBottom();
|
||||
|
||||
if (charBottomY == bottomY && bottomPlatform)
|
||||
relativeVelocity = bottomPlatform.get("velocity");
|
||||
|
||||
} else if (cur.mov != "fall" && cur.mov != "ko" && charBottomY < bottomY) {
|
||||
// Start falling if no obstacle below
|
||||
attrs.state = this.buildState("fall", cur.mov2, cur.dir);
|
||||
|
||||
if (cur.mov == "walk" && velocity != 0) {
|
||||
this.trigger("beforeFall");
|
||||
if (this.cancelUpdate) return true;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (yVelocity < 0) {
|
||||
// Jumping
|
||||
var topY = -400;
|
||||
for (i = 0; i < this.collisionMap.top.sprites.length; i++) {
|
||||
sprite = this.collisionMap.top.sprites[i];
|
||||
if (sprite.get("type") == "tile")
|
||||
topY = Math.max(topY, sprite.getBottom(true));
|
||||
}
|
||||
if (charTopY < topY) {
|
||||
attrs.yVelocity = yVelocity = 0;
|
||||
charTopY = topY;
|
||||
charBottomY = topY + charHeight;
|
||||
attrs.y = y = charBottomY - tileHeight;
|
||||
updateTopBottom();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// When not in play mode, do not allow horizontal displacements or animations
|
||||
if (this.world.get("state") != "play") {
|
||||
attrs.velocity = velocity = 0;
|
||||
attrs.sequenceIndex = this.get("sequenceIndex");
|
||||
|
||||
} else {
|
||||
|
||||
// Walls and other obstacles
|
||||
if (velocity <= 0 && collision) {
|
||||
// Turn around if obstacle left
|
||||
var worldLeft = -tileWidth,
|
||||
leftX = worldLeft,
|
||||
leftCharacter;
|
||||
if (cur.mov != "ko" && cur.mov != "idle")
|
||||
for (i = 0; i < this.collisionMap.left.sprites.length; i++) {
|
||||
sprite = this.collisionMap.left.sprites[i];
|
||||
leftX = Math.max(leftX, sprite.getRight(true));
|
||||
if (sprite.get("type") == "character" &&
|
||||
(!leftCharacter || sprite.getRight(true) > leftCharacter.getRight(true)))
|
||||
leftCharacter = sprite;
|
||||
}
|
||||
|
||||
if (charLeftX <= leftX) {
|
||||
if (charLeftX <= worldLeft) {
|
||||
this.world.remove(this);
|
||||
return false;
|
||||
}
|
||||
if (leftCharacter && cur.mov2 != "hurt") {
|
||||
leftCharacter.trigger("hit", this, "right", cur.mov2);
|
||||
if (this.cancelUpdate) return true;
|
||||
}
|
||||
attrs.velocity = velocity = velocity * -1;
|
||||
attrs.state = this.buildState(cur.mov, cur.mov2, cur.opo);
|
||||
attrs.x = x = leftX - paddingLeft;
|
||||
}
|
||||
}
|
||||
|
||||
if (velocity >= 0 && collision) {
|
||||
// Turn around if obstacle to the right
|
||||
var worldRight = this.world.width(),
|
||||
rightX = worldRight,
|
||||
rightCharacter;
|
||||
if (cur.mov != "ko" && cur.mov != "idle")
|
||||
for (i = 0; i < this.collisionMap.right.sprites.length; i++) {
|
||||
sprite = this.collisionMap.right.sprites[i];
|
||||
rightX = Math.min(rightX, sprite.getLeft(true));
|
||||
if (sprite.get("type") == "character" &&
|
||||
(!rightCharacter || sprite.getLeft(true) < rightCharacter.getLeft(true)))
|
||||
rightCharacter = sprite;
|
||||
}
|
||||
|
||||
if (charRightX >= rightX) {
|
||||
if (charRightX >= worldRight) {
|
||||
this.world.remove(this);
|
||||
return false;
|
||||
}
|
||||
if (rightCharacter && cur.mov2 != "hurt") {
|
||||
rightCharacter.trigger("hit", this, "left", cur.mov2);
|
||||
if (this.cancelUpdate) return true;
|
||||
}
|
||||
attrs.velocity = velocity = velocity * -1;
|
||||
attrs.state = this.buildState(cur.mov, cur.mov2, cur.opo);
|
||||
attrs.x = x = rightX - charWidth - paddingLeft;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (velocity || relativeVelocity) attrs.x = x = x + Math.round((velocity + relativeVelocity) * (dt/1000));
|
||||
if (yVelocity) attrs.y = y = y + Math.round(yVelocity * (dt/1000));
|
||||
|
||||
// Set modified attributes
|
||||
if (!_.isEmpty(attrs)) this.set(attrs);
|
||||
|
||||
if (typeof this.onUpdate == "function") return this.onUpdate(dt);
|
||||
return true;
|
||||
},
|
||||
toggleDirection: function(dirIntent) {
|
||||
var cur = this.getStateInfo();
|
||||
this.set({state: this.buildState(cur.mov, cur.mov2, dirIntent)});
|
||||
return this;
|
||||
},
|
||||
getStateInfo: function(state) {
|
||||
var state = state || this.get("state"),
|
||||
pieces = state.split("-");
|
||||
if (pieces.length < 2) return {
|
||||
state: state,
|
||||
mov: state
|
||||
};
|
||||
|
||||
var stateInfo = {};
|
||||
stateInfo.mov = pieces[0];
|
||||
stateInfo.mov2 = pieces.length == 3 ? pieces[1] : null;
|
||||
stateInfo.dir = pieces.length == 3 ? pieces[2] : pieces[1];
|
||||
stateInfo.opo = stateInfo.dir == "right" ? "left" : "right";
|
||||
return stateInfo;
|
||||
},
|
||||
isAttacking: function() {
|
||||
if (this.cancelUpdate) return false;
|
||||
var cur = this.getStateInfo();
|
||||
return cur.mov2 == "attack";
|
||||
},
|
||||
buildState: function(piece1, piece2, piece3) {
|
||||
var state = "";
|
||||
if (piece1) state += piece1;
|
||||
if (piece2) state += (state.length ? "-" : "") + piece2;
|
||||
if (piece3) state += (state.length ? "-" : "") + piece3;
|
||||
return state;
|
||||
},
|
||||
buildCollisionMap: function(top, right, bottom, left) {
|
||||
this.collisionMap || (this.collisionMap = {
|
||||
right: {x: 0, y: 0, dir: "right", sprites: [], sprite: null},
|
||||
left: {x: 0, y: 0, dir: "left", sprites: [], sprite: null},
|
||||
bottom: {x: 0, y: 0, dir: "bottom", sprites: [], sprite: null},
|
||||
top: {x: 0, y: 0, dir: "top", sprites: [], sprite: null}
|
||||
});
|
||||
|
||||
var width = right - left,
|
||||
height = bottom - top;
|
||||
this.collisionMap.left.x = left;
|
||||
this.collisionMap.right.x = right;
|
||||
this.collisionMap.left.y = this.collisionMap.right.y = top + height*0.20;
|
||||
this.collisionMap.top.x = this.collisionMap.bottom.x = left + width*0.20;
|
||||
this.collisionMap.top.y = top;
|
||||
this.collisionMap.bottom.y = bottom;
|
||||
this.collisionMap.left.height = this.collisionMap.right.height = height*0.60;
|
||||
this.collisionMap.left.width = this.collisionMap.right.width = 0;
|
||||
this.collisionMap.top.width = this.collisionMap.bottom.width = width*0.60;
|
||||
this.collisionMap.top.height = this.collisionMap.bottom.height = 0;
|
||||
|
||||
for (var m in this.collisionMap)
|
||||
if (this.collisionMap.hasOwnProperty(m)) {
|
||||
this.collisionMap[m].sprites.length = 0;
|
||||
this.collisionMap[m].sprite = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}).call(this);
|
||||
1303
games/mario/src/core.js
Normal file
324
games/mario/src/editor.js
Normal file
@@ -0,0 +1,324 @@
|
||||
(function() {
|
||||
|
||||
/**
|
||||
*
|
||||
* Backbone Game Engine - An elementary HTML5 canvas game engine using Backbone.
|
||||
*
|
||||
* Copyright (c) 2014 Martin Drapeau
|
||||
* https://github.com/martindrapeau/backbone-game-engine
|
||||
*
|
||||
*/
|
||||
|
||||
var pageImg = new Image();
|
||||
pageImg.src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAgCAYAAAAbifjMAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABYSURBVEhLYxheoB6I1SFM0gFIsycQ/wdikg1B1kyWISDFyJpHDRk1BBugqiGpUJosQ2CaQIbsh/JBNFGAIm+Mah7VTBSguFQGAYo0wwBFmkGAIs1kAgYGANR+XZBqOruRAAAAAElFTkSuQmCC";
|
||||
|
||||
var drawSpriteFn = function(context, options) {
|
||||
options || (options = {});
|
||||
var animation = this.getAnimation(),
|
||||
sequenceIndex = this.get("sequenceIndex") || 0;
|
||||
if (!animation || animation.sequences.length == 0) return;
|
||||
if (sequenceIndex >= animation.sequences.length) sequenceIndex = 0;
|
||||
|
||||
var sequence = animation.sequences[sequenceIndex]
|
||||
frameIndex = _.isNumber(sequence) ? sequence : sequence.frame,
|
||||
frame = this.spriteSheet.frames[frameIndex];
|
||||
|
||||
var width = options.tileWidth,
|
||||
height = options.tileHeight;
|
||||
if (this.attributes.width > this.attributes.height && this.attributes.width > options.tileWidth) {
|
||||
height = this.attributes.height * options.tileWidth / this.attributes.width;
|
||||
} else if (this.attributes.height > this.attributes.width && this.attributes.height > options.tileHeight) {
|
||||
width = this.attributes.width * options.tileHeight / this.attributes.height;
|
||||
}
|
||||
|
||||
context.drawImage(
|
||||
this.spriteSheet.img,
|
||||
frame.x, frame.y, frame.width, frame.height,
|
||||
this.get("x"), this.get("y"), width, height
|
||||
);
|
||||
|
||||
if (typeof this.onDraw == "function") this.onDraw(context, options);
|
||||
return this;
|
||||
};
|
||||
|
||||
// World Editor
|
||||
// Allows the user to place tiles and characters in the World.
|
||||
Backbone.WorldEditor = Backbone.Model.extend({
|
||||
defaults: {
|
||||
x: 136,
|
||||
y: 550,
|
||||
width: 820,
|
||||
height: 140,
|
||||
tileWidth: 32,
|
||||
tileHeight: 32,
|
||||
padding: 1,
|
||||
backgroundColor: "#333",
|
||||
selectColor: "#f00",
|
||||
selected: undefined,
|
||||
spriteNames: [],
|
||||
page: 0,
|
||||
pages: 1
|
||||
},
|
||||
initialize: function(attributes, options) {
|
||||
options || (options = {});
|
||||
|
||||
if (!attributes || !attributes.spriteNames) throw "Missing attribute spriteNames";
|
||||
|
||||
var defs = [];
|
||||
if (attributes.spriteNames.length && typeof attributes.spriteNames[0] == "object") {
|
||||
for (var page = 0; page < attributes.spriteNames.length; page++)
|
||||
for (var s = 0; s < attributes.spriteNames[page].length; s++)
|
||||
defs.push({name: attributes.spriteNames[page][s], page: page});
|
||||
this.set("pages", attributes.spriteNames.length);
|
||||
} else {
|
||||
defs = _.map(attributes.spriteNames, function(name) {
|
||||
return {name: name, page: 0};
|
||||
});
|
||||
}
|
||||
this.sprites = new Backbone.SpriteCollection(defs);
|
||||
this.sprites.each(function(sprite) {
|
||||
sprite.draw = drawSpriteFn;
|
||||
});
|
||||
|
||||
this.world = options.world;
|
||||
if (!this.world && !_.isFunction(this.world.add))
|
||||
throw "Missing or invalid world option.";
|
||||
|
||||
this.changePageButton = new Backbone.Button({
|
||||
width: 32, height: 50, borderRadius: 2,
|
||||
img: pageImg, imgX: 0, imgY: 0, imgWidth: 16, imgHeight: 32, imgMargin: 10
|
||||
});
|
||||
this.changePageButton.on("tap", this.changePage, this);
|
||||
|
||||
this.debugPanel = options.debugPanel;
|
||||
|
||||
_.bindAll(this,
|
||||
"onTap", "onDragStart", "onDrag", "onDragEnd",
|
||||
"getSelectedSprite", "onMouseMove"
|
||||
);
|
||||
|
||||
this.on("attach", this.onAttach);
|
||||
this.on("detach", this.onDetach);
|
||||
|
||||
var editor = this;
|
||||
this.on("change:page", function() {
|
||||
editor.set("selected", undefined);
|
||||
});
|
||||
},
|
||||
onAttach: function() {
|
||||
var world = this.world,
|
||||
engine = this.engine;
|
||||
this.onDetach();
|
||||
|
||||
this.listenTo(this.engine, "tap", this.onTap);
|
||||
this.listenTo(this.engine, "dragstart", this.onDragStart);
|
||||
this.listenTo(this.engine, "dragmove", this.onDrag);
|
||||
this.listenTo(this.engine, "dragend", this.onDragEnd);
|
||||
|
||||
$(document).on("mousemove.Edit", this.onMouseMove);
|
||||
|
||||
this.sprites.each(function(sprite) {
|
||||
if (sprite.attributes.type == "tile" || sprite.attributes.type == "character") {
|
||||
sprite.engine = engine;
|
||||
sprite.trigger("attach", engine);
|
||||
}
|
||||
});
|
||||
|
||||
if (this.get("pages") > 1) {
|
||||
this.changePageButton.engine = engine;
|
||||
this.changePageButton.trigger("attach");
|
||||
}
|
||||
|
||||
this.positionSprites();
|
||||
},
|
||||
onDetach: function() {
|
||||
this.stopListening(this.engine);
|
||||
$(document).off(".Edit");
|
||||
|
||||
this.sprites.each(function(sprite) {
|
||||
if (sprite.attributes.type == "tile" || sprite.attributes.type == "character") {
|
||||
sprite.engine = undefined;
|
||||
sprite.trigger("detach");
|
||||
}
|
||||
});
|
||||
|
||||
this.changePageButton.trigger("detach");
|
||||
},
|
||||
changePage: function() {
|
||||
var page = this.get("page") + 1;
|
||||
if (page >= this.get("pages")) page = 0;
|
||||
this.set("page", page);
|
||||
},
|
||||
positionSprites: function() {
|
||||
var sp = this.toJSON(),
|
||||
x = sp.x + sp.tileWidth + 4*sp.padding,
|
||||
y = sp.y + 2*sp.padding,
|
||||
page = 0;
|
||||
|
||||
this.sprites.each(function(sprite) {
|
||||
if (sprite.attributes.page > page) {
|
||||
page = sprite.attributes.page;
|
||||
x = sp.x + sp.tileWidth + 4*sp.padding;
|
||||
y = sp.y + 2*sp.padding;
|
||||
}
|
||||
|
||||
sprite.set({x: x, y: y});
|
||||
x += sp.tileWidth + 2*sp.padding;
|
||||
|
||||
if (x >= sp.x + sp.width - 2) {
|
||||
x = sp.x + 2*sp.padding;
|
||||
y += sp.tileHeight + 2*sp.padding;
|
||||
}
|
||||
});
|
||||
|
||||
if (this.get("pages") > 1)
|
||||
this.changePageButton.set({
|
||||
x: sp.x + sp.width - this.changePageButton.get("width"),
|
||||
y: sp.y + sp.height - this.changePageButton.get("height")
|
||||
});
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
update: function(dt) {
|
||||
for (var i = 0; i < this.sprites.models.length; i++)
|
||||
this.sprites.models[i].update(dt);
|
||||
this.changePageButton.update(dt);
|
||||
return true;
|
||||
},
|
||||
draw: function(context) {
|
||||
var sp = this.toJSON();
|
||||
|
||||
// Fill background
|
||||
drawRect(context, sp.x, sp.y, sp.width, sp.height, sp.backgroundColor);
|
||||
|
||||
// Highlight selected sprite
|
||||
var st = this.sprites.findWhere({name: sp.selected}),
|
||||
sx = st ? st.get("x") - 2 : sp.x,
|
||||
sy = st ? st.get("y") - 2 : sp.y,
|
||||
sw = sp.tileWidth + 4,
|
||||
sh = sp.tileHeight + 4;
|
||||
drawRect(context, sx, sy, sw, sh, sp.selectColor);
|
||||
|
||||
// Draw sprites
|
||||
this.sprites.each(function(sprite) {
|
||||
if (sprite.attributes.page == sp.page) sprite.draw(context, sp);
|
||||
});
|
||||
|
||||
// Highlight tile position (on desktop)
|
||||
if (this.mx != undefined && this.my != undefined) {
|
||||
var tileWidth = this.world.get("tileWidth"),
|
||||
tileHeight = this.world.get("tileHeight"),
|
||||
x = this.mx - this.mx % tileWidth + this.world.get("x"),
|
||||
y = this.my - this.my % tileHeight + this.world.get("y");
|
||||
|
||||
context.save();
|
||||
context.rect(
|
||||
this.world.get("viewportLeft"),
|
||||
this.world.get("viewportTop"),
|
||||
context.canvas.width - this.world.get("viewportRight"),
|
||||
context.canvas.height - this.world.get("viewportBottom")
|
||||
);
|
||||
context.clip();
|
||||
|
||||
context.beginPath();
|
||||
context.strokeStyle = "#FF0000";
|
||||
context.setLineDash([5,2]);
|
||||
context.rect(x, y, tileWidth, tileHeight);
|
||||
context.stroke();
|
||||
|
||||
context.restore();
|
||||
}
|
||||
|
||||
if (this.get("pages") > 1)
|
||||
this.changePageButton.draw(context);
|
||||
},
|
||||
|
||||
getSelectedSprite: function() {
|
||||
var selected = this.get("selected");
|
||||
if (!selected) return null
|
||||
return this.sprites.findWhere({name: selected});
|
||||
},
|
||||
onTap: function(e) {
|
||||
if (e.target != this.engine.canvas || e.canvasHandled) return;
|
||||
|
||||
var editor = this,
|
||||
sp = this.toJSON(),
|
||||
x = e.canvasX,
|
||||
y = e.canvasY;
|
||||
|
||||
// Sprite selection?
|
||||
if (x >= sp.x && y >= sp.y && x <= sp.x + sp.width && y <= sp.y + sp.height) {
|
||||
editor.set({selected: null});
|
||||
this.sprites.each(function(sprite) {
|
||||
var s = sprite.toJSON();
|
||||
if (s.page == sp.page && x >= s.x && y >= s.y && x <= s.x + sp.tileWidth && y <= s.y + sp.tileHeight) {
|
||||
editor.set({selected: s.name});
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Sprite placement
|
||||
if (y < sp.y) {
|
||||
var sprite = this.getSelectedSprite();
|
||||
x -= this.world.get("x");
|
||||
y -= this.world.get("y");
|
||||
this.world.cloneAtPosition(sprite, x, y);
|
||||
}
|
||||
},
|
||||
|
||||
// Pan the world
|
||||
onDragStart: function(e) {
|
||||
var world = this.world,
|
||||
sp = this.toJSON(),
|
||||
x = e.canvasX,
|
||||
y = e.canvasY;
|
||||
if (x >= sp.x && y >= sp.y && x <= sp.x + sp.width && y <= sp.y + sp.height) return;
|
||||
world.startDragWorldX = world.get("x");
|
||||
world.startDragWorldY = world.get("y");
|
||||
},
|
||||
onDrag: function (e) {
|
||||
var world = this.world;
|
||||
if (!_.isNumber(world.startDragWorldX) || !_.isNumber(world.startDragWorldX)) return false;
|
||||
var x = world.startDragWorldX + e.canvasDeltaX,
|
||||
y = world.startDragWorldY + e.canvasDeltaY;
|
||||
if (x > 0) {
|
||||
x = 0;
|
||||
} else {
|
||||
var min = -(world.get("width") * world.get("tileWidth") - world.engine.canvas.width);
|
||||
if (x < min) x = min;
|
||||
}
|
||||
if (y > 0) {
|
||||
y = 0;
|
||||
} else {
|
||||
var min = -(world.get("height") * world.get("tileHeight") - world.engine.canvas.height + world.get("viewportBottom"));
|
||||
if (min > 0) min = 0;
|
||||
if (y < min) y = min;
|
||||
}
|
||||
world.set({x: x, y: y});
|
||||
},
|
||||
onDragEnd: function(e) {
|
||||
var world = this.world;
|
||||
world.startDragWorldX = undefined;
|
||||
world.startDragWorldY = undefined;
|
||||
},
|
||||
|
||||
onMouseMove: function(e) {
|
||||
var mx = this.mx = e.pageX - this.world.get("x") - this.engine.canvas.offsetLeft + this.engine.canvas.scrollLeft,
|
||||
my = this.my = e.pageY - this.world.get("y") - this.engine.canvas.offsetTop + this.engine.canvas.scrollTop,
|
||||
id = this.world.getWorldIndex({x: mx, y: my}),
|
||||
sprites = this.world.filterAt(mx, my),
|
||||
nameOrIds = _.map(sprites, function(sprite) {
|
||||
if (sprite.get("type") == "tile") return sprite.get("name")
|
||||
return sprite.get("id");
|
||||
});
|
||||
|
||||
if (this.debugPanel)
|
||||
this.debugPanel.set({sprites: nameOrIds, mx: mx, my: my});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}).call(this);
|
||||
713
games/mario/src/hero.js
Normal file
@@ -0,0 +1,713 @@
|
||||
(function() {
|
||||
|
||||
// Velocity and acceleration values in absolute values
|
||||
var walkVelocity = 160,
|
||||
walkMinVelocity = 60,
|
||||
walkAcceleration = 150,
|
||||
runVelocity = 220,
|
||||
runMinVelocity = 100,
|
||||
runAcceleration = 400,
|
||||
releaseDeceleration = 200,
|
||||
skidDeceleration = 400,
|
||||
jumpVelocity = 650,
|
||||
jumpDeceleration = 1400,
|
||||
jumpHoldDeceleration = 900,
|
||||
fallAcceleration = 1200,
|
||||
airTurnaroundDeceleration = 400,
|
||||
fallVelocity = 600,
|
||||
idleDelay = 2500,
|
||||
walkDelay = 100,
|
||||
koDelay = 100,
|
||||
runDelay = 50,
|
||||
attackDelay = 50,
|
||||
attackSequences = [37, 38, 39, 38],
|
||||
jumpAttackSequences = [41],
|
||||
skidAttackSequences = [25],
|
||||
walkSequences = [22, 23, 24, 23],
|
||||
runSequences = [22, 23, 24, 23],
|
||||
hurtDelay = 300,
|
||||
hurtBounceVelocity = -300,
|
||||
hurtSequences = [27];
|
||||
|
||||
var animations = {
|
||||
"idle-left": {
|
||||
sequences: [21],
|
||||
delay: idleDelay,
|
||||
velocity: 0,
|
||||
acceleration: 0,
|
||||
scaleX: -1,
|
||||
scaleY: 1
|
||||
},
|
||||
"idle-right": {
|
||||
sequences: [21],
|
||||
delay: idleDelay,
|
||||
velocity: 0,
|
||||
acceleration: 0,
|
||||
scaleX: 1,
|
||||
scaleY: 1
|
||||
},
|
||||
"walk-left": {
|
||||
sequences: walkSequences,
|
||||
delay: walkDelay,
|
||||
velocity: -walkVelocity,
|
||||
minVelocity: -walkMinVelocity,
|
||||
acceleration: walkAcceleration,
|
||||
scaleX: -1,
|
||||
scaleY: 1
|
||||
},
|
||||
"walk-right": {
|
||||
sequences: walkSequences,
|
||||
delay: walkDelay,
|
||||
velocity: walkVelocity,
|
||||
minVelocity: walkMinVelocity,
|
||||
acceleration: walkAcceleration,
|
||||
scaleX: 1,
|
||||
scaleY: 1
|
||||
},
|
||||
"run-left": {
|
||||
sequences: runSequences,
|
||||
delay: runDelay,
|
||||
velocity: -runVelocity,
|
||||
minVelocity: -runMinVelocity,
|
||||
acceleration: runAcceleration,
|
||||
scaleX: -1,
|
||||
scaleY: 1
|
||||
},
|
||||
"run-right": {
|
||||
sequences: runSequences,
|
||||
delay: runDelay,
|
||||
velocity: runVelocity,
|
||||
minVelocity: runMinVelocity,
|
||||
acceleration: runAcceleration,
|
||||
scaleX: 1,
|
||||
scaleY: 1
|
||||
},
|
||||
"release-left": {
|
||||
sequences: walkSequences,
|
||||
delay: walkDelay,
|
||||
velocity: 0,
|
||||
acceleration: releaseDeceleration,
|
||||
scaleX: -1,
|
||||
scaleY: 1
|
||||
},
|
||||
"release-right": {
|
||||
sequences: walkSequences,
|
||||
delay: walkDelay,
|
||||
velocity: 0,
|
||||
acceleration: releaseDeceleration,
|
||||
scaleX: 1,
|
||||
scaleY: 1
|
||||
},
|
||||
"skid-left": {
|
||||
sequences: [25],
|
||||
velocity: 0,
|
||||
acceleration: skidDeceleration,
|
||||
scaleX: 1,
|
||||
scaleY: 1
|
||||
},
|
||||
"skid-right": {
|
||||
sequences: [25],
|
||||
velocity: 0,
|
||||
acceleration: skidDeceleration,
|
||||
scaleX: -1,
|
||||
scaleY: 1
|
||||
},
|
||||
"jump-left": {
|
||||
sequences: [26],
|
||||
delay: 1000,
|
||||
velocity: -walkVelocity,
|
||||
acceleration: airTurnaroundDeceleration,
|
||||
yStartVelocity: -jumpVelocity,
|
||||
yEndVelocity: fallVelocity,
|
||||
yAscentAcceleration: jumpDeceleration,
|
||||
yHoldAscentAcceleration: jumpHoldDeceleration,
|
||||
yDescentAcceleration: fallAcceleration,
|
||||
scaleX: -1,
|
||||
scaleY: 1
|
||||
},
|
||||
"jump-right": {
|
||||
sequences: [26],
|
||||
delay: 1000,
|
||||
velocity: walkVelocity,
|
||||
acceleration: airTurnaroundDeceleration,
|
||||
yStartVelocity: -jumpVelocity,
|
||||
yEndVelocity: fallVelocity,
|
||||
yAscentAcceleration: jumpDeceleration,
|
||||
yHoldAscentAcceleration: jumpHoldDeceleration,
|
||||
yDescentAcceleration: fallAcceleration,
|
||||
scaleX: 1,
|
||||
scaleY: 1
|
||||
},
|
||||
"ko-left": {
|
||||
sequences: [27],
|
||||
delay: koDelay,
|
||||
velocity: 0,
|
||||
yVelocity: fallVelocity,
|
||||
yAcceleration: fallAcceleration,
|
||||
scaleX: -1,
|
||||
scaleY: 1
|
||||
},
|
||||
"ko-right": {
|
||||
sequences: [27],
|
||||
delay: koDelay,
|
||||
velocity: 0,
|
||||
yVelocity: fallVelocity,
|
||||
yAcceleration: fallAcceleration,
|
||||
scaleX: 1,
|
||||
scaleY: 1
|
||||
},
|
||||
"dead-left": {
|
||||
sequences: [27],
|
||||
velocity: 0,
|
||||
scaleX: -1,
|
||||
scaleY: 1
|
||||
},
|
||||
"dead-right": {
|
||||
sequences: [27],
|
||||
velocity: 0,
|
||||
scaleX: 1,
|
||||
scaleY: 1
|
||||
}
|
||||
};
|
||||
animations["idle-attack-left"] = _.extend({}, animations["idle-left"], {sequences: attackSequences, delay: attackDelay});
|
||||
animations["idle-attack-right"] = _.extend({}, animations["idle-right"], {sequences: attackSequences, delay: attackDelay});
|
||||
animations["walk-attack-left"] = _.extend({}, animations["walk-left"], {sequences: attackSequences, delay: attackDelay});
|
||||
animations["walk-attack-right"] = _.extend({}, animations["walk-right"], {sequences: attackSequences, delay: attackDelay});
|
||||
animations["run-attack-left"] = _.extend({}, animations["run-left"], {sequences: attackSequences, delay: attackDelay});
|
||||
animations["run-attack-right"] = _.extend({}, animations["run-right"], {sequences: attackSequences, delay: attackDelay});
|
||||
animations["release-attack-left"] = _.extend({}, animations["release-left"], {sequences: attackSequences, delay: attackDelay});
|
||||
animations["release-attack-right"] = _.extend({}, animations["release-right"], {sequences: attackSequences, delay: attackDelay});
|
||||
animations["jump-attack-left"] = _.extend({}, animations["jump-left"], {sequences: jumpAttackSequences, delay: attackDelay});
|
||||
animations["jump-attack-right"] = _.extend({}, animations["jump-right"], {sequences: jumpAttackSequences, delay: attackDelay});
|
||||
animations["skid-attack-left"] = _.extend({}, animations["skid-left"], {sequences: skidAttackSequences, delay: attackDelay});
|
||||
animations["skid-attack-right"] = _.extend({}, animations["skid-right"], {sequences: skidAttackSequences, delay: attackDelay});
|
||||
|
||||
var hurtAnimation = {sequences: hurtSequences, delay: hurtDelay};
|
||||
animations["idle-hurt-left"] = _.extend({}, animations["idle-left"], hurtAnimation);
|
||||
animations["idle-hurt-right"] = _.extend({}, animations["idle-right"], hurtAnimation);
|
||||
animations["walk-hurt-left"] = _.extend({}, animations["walk-left"], hurtAnimation);
|
||||
animations["walk-hurt-right"] = _.extend({}, animations["walk-right"], hurtAnimation);
|
||||
animations["run-hurt-left"] = _.extend({}, animations["run-left"], hurtAnimation);
|
||||
animations["run-hurt-right"] = _.extend({}, animations["run-right"], hurtAnimation);
|
||||
animations["release-hurt-left"] = _.extend({}, animations["release-left"], hurtAnimation);
|
||||
animations["release-hurt-right"] = _.extend({}, animations["release-right"], hurtAnimation);
|
||||
animations["jump-hurt-left"] = _.extend({}, animations["jump-left"], hurtAnimation);
|
||||
animations["jump-hurt-right"] = _.extend({}, animations["jump-right"], hurtAnimation);
|
||||
animations["skid-hurt-left"] = _.extend({}, animations["skid-left"], hurtAnimation);
|
||||
animations["skid-hurt-right"] = _.extend({}, animations["skid-right"], hurtAnimation);
|
||||
|
||||
Backbone.Hero = Backbone.Character.extend({
|
||||
defaults: _.extend({}, Backbone.Character.prototype.defaults, {
|
||||
name: "hero",
|
||||
type: "character",
|
||||
hero: true,
|
||||
spriteSheet: undefined,
|
||||
width: 32,
|
||||
height: 64,
|
||||
paddingLeft: 0,
|
||||
paddingRight: 0,
|
||||
paddingTop: 32,
|
||||
paddingBottom: 0,
|
||||
state: "idle-right",
|
||||
velocity: 0,
|
||||
acceleration: 0,
|
||||
yVelocity: 0,
|
||||
yAcceleration: 0,
|
||||
collision: true,
|
||||
dead: false,
|
||||
health: 1,
|
||||
healthMax: 2,
|
||||
attackDamage: 1,
|
||||
coins: 0,
|
||||
ignoreInput: false,
|
||||
canAttack: false,
|
||||
canTurnInJump: false
|
||||
}),
|
||||
animations: animations,
|
||||
saveAttributes: _.union(
|
||||
Backbone.Character.prototype.saveAttributes,
|
||||
["nextState", "velocity", "acceleration", "yVelocity", "yAcceleration"]
|
||||
),
|
||||
initialize: function(attributes, options) {
|
||||
options || (options = {});
|
||||
Backbone.Character.prototype.initialize.apply(this, arguments);
|
||||
|
||||
this.input = options.input;
|
||||
},
|
||||
onAttach: function() {
|
||||
if (this.input) {
|
||||
this.stopListening(this.input);
|
||||
this.listenTo(this.input, "change:right", _.partial(this.dirToggled, "right"));
|
||||
this.listenTo(this.input, "change:left", _.partial(this.dirToggled, "left"));
|
||||
this.listenTo(this.input, "change:buttonB", this.buttonBToggled);
|
||||
this.listenTo(this.input, "change:buttonA", this.buttonAToggled);
|
||||
}
|
||||
},
|
||||
onDetach: function() {
|
||||
if (this.input) this.stopListening(this.input);
|
||||
},
|
||||
toggleDirection: function(dirIntent) {
|
||||
return this.dirToggled(dirIntent);
|
||||
},
|
||||
ignoreInput: function() {
|
||||
if (this.get("ignoreInput") || this.get("dead")) return true;
|
||||
var cur = this.getStateInfo();
|
||||
if (cur.mov == "ko" || cur.mov == "dead" || cur.mov2 == "hurt") return true;
|
||||
return false;
|
||||
},
|
||||
// User input toggled in right or left direction.
|
||||
// Can be pressed or depressed
|
||||
dirToggled: function(dirIntent) {
|
||||
if (this.ignoreInput()) return this;
|
||||
|
||||
if (dirIntent != "left" && dirIntent != "right")
|
||||
throw "Invalid or missing dirIntent. Must be left or right."
|
||||
|
||||
var cur = this.getStateInfo(),
|
||||
now = _.now(),
|
||||
opoIntent = dirIntent == "right" ? "left" : "right",
|
||||
dirPressed = this.input ? this.input[dirIntent+"Pressed"]() : false,
|
||||
opoPressed = this.input ? this.input[opoIntent+"Pressed"]() : false,
|
||||
run = this.input ? this.input.buttonBPressed() : false,
|
||||
velocity = this.get("velocity"),
|
||||
attrs = {};
|
||||
|
||||
if (dirPressed) {
|
||||
// Pressed. Intent to move in that direction
|
||||
if (cur.mov == "jump") {
|
||||
if (this.get("canTurnInJump"))
|
||||
attrs.state = this.buildState("jump", cur.mov2, dirIntent);
|
||||
if (dirIntent != cur.dir && velocity)
|
||||
attrs.nextState = this.buildState("skid", cur.mov2, opoIntent);
|
||||
else
|
||||
attrs.nextState = this.buildState(run ? "run" : "walk", cur.mov2, dirIntent);
|
||||
} else if (cur.dir == dirIntent || cur.mov == "idle") {
|
||||
// Start walking or running
|
||||
attrs.state = this.buildState(run ? "run" : "walk", cur.mov2, dirIntent);
|
||||
var animation = this.getAnimation(attrs.state);
|
||||
if (animation.minVelocity && Math.abs(velocity) < Math.abs(animation.minVelocity))
|
||||
attrs.velocity = animation.minVelocity;
|
||||
this.startWalk = now;
|
||||
} else if (cur.dir == opoIntent) {
|
||||
// Skid trying to stop before turning
|
||||
attrs.state = this.buildState("skid", cur.mov2, opoIntent);
|
||||
attrs.nextState = this.buildState(run ? "run" : "walk", cur.mov2, dirIntent);
|
||||
}
|
||||
} else if (opoPressed) {
|
||||
// Depressed but opposite direction still pressed. Intent = turnaround.
|
||||
// Handle by calling the opposite direction press event.
|
||||
this.dirToggled(opoIntent);
|
||||
} else {
|
||||
// Depressed. Intent = stop to idle
|
||||
if (cur.mov == "jump") {
|
||||
attrs.nextState = this.buildState("release", cur.mov2, dirIntent);
|
||||
} else {
|
||||
attrs.state = this.buildState("release", cur.mov2, dirIntent);
|
||||
attrs.nextState = this.buildState("idle", cur.mov2, dirIntent);
|
||||
if (now < this.startWalk + 250)
|
||||
attrs.velocity = velocity = velocity * 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_.isEmpty(attrs)) this.set(attrs);
|
||||
|
||||
return this;
|
||||
},
|
||||
// Attack and run
|
||||
buttonBToggled: function() {
|
||||
if (this.ignoreInput()) return this;
|
||||
|
||||
var cur = this.getStateInfo(),
|
||||
pressed = this.input ? this.input.buttonBPressed() : false;
|
||||
|
||||
if (pressed && cur.mov == "walk") {
|
||||
cur.mov = "run";
|
||||
this.set("state", this.buildState(cur.mov, cur.mov2, cur.dir));
|
||||
this.cancelUpdate = true;
|
||||
} else if (!pressed && cur.mov == "run") {
|
||||
cur.mov = "walk";
|
||||
this.set("state", this.buildState(cur.mov, cur.mov2, cur.dir));
|
||||
this.cancelUpdate = true;
|
||||
}
|
||||
|
||||
if (!this.get("canAttack")) return this;
|
||||
|
||||
if (pressed && cur.mov2 != "attack") {
|
||||
var nextState = this.get("nextState");
|
||||
if (nextState) {
|
||||
var nex = this.getStateInfo(nextState);
|
||||
nextState = this.buildState(nex.mov, "attack", nex.dir);
|
||||
}
|
||||
this.startNewAnimation(this.buildState(cur.mov, "attack", cur.dir), {nextState: nextState}, this.endAttack);
|
||||
this.cancelUpdate = true;
|
||||
} else if (!pressed && cur.mov2 == "attack") {
|
||||
this.endAttack();
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
endAttack: function() {
|
||||
var cur = this.getStateInfo(),
|
||||
attrs = {state: this.buildState(cur.mov, cur.dir)},
|
||||
nextState = this.get("nextState"),
|
||||
nex = nextState ? this.getStateInfo(nextState) : null;
|
||||
if (nextState) attrs.nextState = this.buildState(nex.mov, nex.dir);
|
||||
this.whenAnimationEnds = null;
|
||||
this.set(attrs);
|
||||
return this;
|
||||
},
|
||||
knockout: function(sprite, dir) {
|
||||
dir || (dir = cur.dir);
|
||||
var cur = this.getStateInfo(),
|
||||
opo = dir == "left" ? "right" : "left",
|
||||
state = this.buildState("ko", opo);
|
||||
|
||||
this.set({
|
||||
state: state,
|
||||
velocity: this.animations[state].velocity,
|
||||
yVelocity: -this.animations[state].yVelocity,
|
||||
nextState: this.buildState("dead", null, opo),
|
||||
dead: true,
|
||||
collision: false
|
||||
});
|
||||
this.cancelUpdate = true;
|
||||
return this;
|
||||
},
|
||||
hurt: function(sprite, dir) {
|
||||
this.set({
|
||||
state: this.buildState("jump", "hurt", dir == "left" ? "right" : "left"),
|
||||
nextState: this.buildState("idle", null, dir),
|
||||
yVelocity: hurtBounceVelocity,
|
||||
velocity: hurtBounceVelocity * (dir == "left" ? -1 : 1) / 2,
|
||||
sequenceIndex: 0
|
||||
});
|
||||
return this;
|
||||
},
|
||||
isAttacking: function() {
|
||||
return this.attributes.state.indexOf("-attack") > 0;
|
||||
},
|
||||
hit: function(sprite, dir, dir2) {
|
||||
if (this._handlingSpriteHit) return this;
|
||||
this._handlingSpriteHit = sprite;
|
||||
|
||||
var cur = this.getStateInfo(),
|
||||
type = sprite.get("type");
|
||||
|
||||
if (type == "artifact") {
|
||||
switch (sprite.get("name")) {
|
||||
case "a-coin":
|
||||
this.cancelUpdate = true;
|
||||
this.set("coins", this.get("coins") + 1);
|
||||
break;
|
||||
}
|
||||
} else if (type == "character" && cur.mov2 != "hurt") {
|
||||
if (this.isAttacking() && cur.dir == dir) {
|
||||
sprite.trigger("hit", this, cur.opo);
|
||||
} else {
|
||||
if (sprite.isAttacking()) {
|
||||
this.cancelUpdate = true;
|
||||
var attackDamage = sprite.get("attackDamage") || 1;
|
||||
this.set({health: Math.max(this.get("health") - attackDamage, 0)}, {sprite: sprite, dir: dir, dir2: dir2});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._handlingSpriteHit = undefined;
|
||||
return this;
|
||||
},
|
||||
// Jump
|
||||
buttonAToggled: function() {
|
||||
if (this.ignoreInput()) return this;
|
||||
|
||||
var state = this.get("state"),
|
||||
cur = this.getStateInfo(),
|
||||
attrs = {};
|
||||
|
||||
if (this.input && this.input.buttonAPressed() && cur.mov != "jump") {
|
||||
// Set new state (keep old as next)
|
||||
attrs.state = this.buildState("jump", cur.mov2, cur.dir);
|
||||
attrs.nextState = state;
|
||||
|
||||
// Determine vertical velocity as a factor of horizontal velocity
|
||||
var jumpAnimation = this.getAnimation(attrs.state),
|
||||
velocity = this.get("velocity"),
|
||||
walkVelocity = this.getAnimation("walk-right").velocity,
|
||||
runVelocity = this.getAnimation("run-right").velocity,
|
||||
ratio = Math.abs((Math.abs(velocity) > walkVelocity ? velocity : walkVelocity) / runVelocity);
|
||||
attrs.yVelocity = Math.round(jumpAnimation.yStartVelocity * (ratio + (1-ratio)/2));
|
||||
|
||||
var heroWidth = this.get("width"),
|
||||
tileHeight = this.get("height"),
|
||||
heroHeight = tileHeight - this.get("paddingTop") - this.get("paddingBottom"),
|
||||
heroBottomY = Math.round(this.get("y") - 4) + tileHeight - this.get("paddingBottom"),
|
||||
heroTopY = heroBottomY - heroHeight,
|
||||
heroLeftX = this.get("x"),
|
||||
topLeftTile = heroTopY > 0 ? this.world.findAt(heroLeftX + heroWidth*0.4, heroTopY, "tile", this, true) : null,
|
||||
topRightTile = heroTopY > 0 ? this.world.findAt(heroLeftX + heroWidth*0.6, heroTopY, "tile", this, true) : null;
|
||||
if (topLeftTile || topRightTile) attrs.yVelocity = -2*60;
|
||||
|
||||
// Keep the horizontal velocity
|
||||
jumpAnimation.minY = (this.get("y") - this.world.height()) * ratio;
|
||||
}
|
||||
if (!_.isEmpty(attrs)) this.set(attrs);
|
||||
|
||||
return this;
|
||||
},
|
||||
sequenceDelay: function(animation) {
|
||||
var velocity = this.get("velocity");
|
||||
return animation.velocity && velocity ?
|
||||
animation.delay * animation.velocity / velocity :
|
||||
animation.delay;
|
||||
},
|
||||
update: function(dt) {
|
||||
// Movements are only possible inside a world
|
||||
if (!this.world) return true;
|
||||
this.cancelUpdate = false;
|
||||
|
||||
// Velocity and state
|
||||
var hero = this,
|
||||
dead = this.get("dead"),
|
||||
input = !this.get("ignoreInput") ? this.input : null,
|
||||
velocity = this.get("velocity") || 0,
|
||||
yVelocity = this.get("yVelocity") || 0,
|
||||
yAcceleration = null,
|
||||
x = this.get("x"),
|
||||
y = this.get("y"),
|
||||
state = this.get("state"),
|
||||
cur = this.getStateInfo(),
|
||||
animation = this.getAnimation(),
|
||||
nextState = this.get("nextState"),
|
||||
nex = this.getStateInfo(nextState),
|
||||
nextAnimation = nextState ? (this.getAnimation(nextState) || {}) : null,
|
||||
attrs = {};
|
||||
|
||||
attrs.sequenceIndex = this.updateSequenceIndex();
|
||||
|
||||
switch (cur.mov + "-" + cur.dir) {
|
||||
case "walk-right":
|
||||
case "run-right":
|
||||
case "release-left":
|
||||
case "skid-left":
|
||||
if (velocity < animation.velocity)
|
||||
velocity += Math.round(animation.acceleration * (dt/1000));
|
||||
if (velocity >= animation.velocity) {
|
||||
velocity = animation.velocity;
|
||||
if (nextState) {
|
||||
attrs.state = nextState;
|
||||
animation = nextAnimation;
|
||||
if (animation.minVelocity && Math.abs(velocity) < Math.abs(animation.minVelocity))
|
||||
velocity = animation.minVelocity;
|
||||
attrs.nextState = null;
|
||||
}
|
||||
}
|
||||
attrs.velocity = velocity;
|
||||
break;
|
||||
|
||||
case "walk-left":
|
||||
case "run-left":
|
||||
case "release-right":
|
||||
case "skid-right":
|
||||
if (velocity > animation.velocity)
|
||||
velocity -= Math.round(animation.acceleration * (dt/1000));
|
||||
if (velocity <= animation.velocity) {
|
||||
velocity = animation.velocity;
|
||||
if (nextState) {
|
||||
attrs.state = nextState;
|
||||
animation = nextAnimation;
|
||||
if (animation.minVelocity && Math.abs(velocity) < Math.abs(animation.minVelocity))
|
||||
velocity = animation.minVelocity;
|
||||
attrs.nextState = null;
|
||||
}
|
||||
}
|
||||
attrs.velocity = velocity;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (cur.mov) {
|
||||
case "idle":
|
||||
// TO DO: This should never happen - but seems to. Figure out why...
|
||||
if (velocity != 0) {
|
||||
if (input && input.rightPressed())
|
||||
this.toggleDirection("right");
|
||||
else if (input && input.leftPressed())
|
||||
this.toggleDirection("left");
|
||||
else
|
||||
attrs.velocity = velocity = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case "jump":
|
||||
// Update vertical velocity. Determine proper vertical acceleration.
|
||||
if (yVelocity < animation.yEndVelocity) {
|
||||
yAcceleration = yVelocity < 0 ? animation.yAscentAcceleration : animation.yDescentAcceleration;
|
||||
if (yVelocity < 0 && input && input.buttonAPressed() && y > animation.minY)
|
||||
yAcceleration = animation.yHoldAscentAcceleration;
|
||||
yVelocity += yAcceleration * (dt/1000);
|
||||
}
|
||||
if (yVelocity >= animation.yEndVelocity)
|
||||
yVelocity = animation.yEndVelocity;
|
||||
attrs.yVelocity = yVelocity;
|
||||
|
||||
// Update horizontal velocity if trying to turnaround
|
||||
if (input && input.leftPressed() && velocity > -Math.abs(animation.velocity)) {
|
||||
velocity -= Math.abs(animation.acceleration) * (dt/1000);
|
||||
attrs.velocity = velocity;
|
||||
} else if (input && input.rightPressed() && velocity < Math.abs(animation.velocity)) {
|
||||
velocity += Math.abs(animation.acceleration) * (dt/1000);
|
||||
attrs.velocity = velocity;
|
||||
}
|
||||
break;
|
||||
|
||||
case "ko":
|
||||
if (yVelocity < animation.yVelocity)
|
||||
yVelocity += animation.yAcceleration * (dt/1000);
|
||||
|
||||
if (yVelocity >= animation.yVelocity)
|
||||
yVelocity = animation.yVelocity;
|
||||
attrs.yVelocity = yVelocity;
|
||||
break;
|
||||
}
|
||||
|
||||
// Collision detection
|
||||
var collision = this.get("collision"),
|
||||
tileWidth = this.get("width"),
|
||||
tileHeight = this.get("height"),
|
||||
paddingLeft = this.get("paddingLeft"),
|
||||
paddingRight = this.get("paddingRight"),
|
||||
paddingBottom = this.get("paddingBottom"),
|
||||
paddingTop = this.get("paddingTop"),
|
||||
heroWidth = tileWidth - paddingLeft - paddingRight,
|
||||
heroHeight = tileHeight - paddingTop - paddingBottom,
|
||||
heroLeftX = Math.round(x + velocity * (dt/1000)) + paddingLeft,
|
||||
heroRightX = heroLeftX + heroWidth,
|
||||
relativeVelocity = 0;
|
||||
|
||||
var heroBottomY, heroTopY,
|
||||
bottomPlatform, sprite, i;
|
||||
function updateTopBottom() {
|
||||
heroBottomY = Math.round(y + yVelocity * (dt/1000)) + tileHeight - paddingBottom;
|
||||
heroTopY = heroBottomY - heroHeight;
|
||||
hero.buildCollisionMap(heroTopY, heroRightX, heroBottomY, heroLeftX);
|
||||
if (collision)
|
||||
hero.world.findCollisions(hero.collisionMap, null, hero, true);
|
||||
}
|
||||
updateTopBottom();
|
||||
|
||||
if (yVelocity >= 0) {
|
||||
// Standing or falling, implement gravity
|
||||
var bottomWorld = this.world.height() + tileHeight,
|
||||
floor = this.get("floor") || bottomWorld,
|
||||
bottomY = Math.min(floor, bottomWorld);
|
||||
|
||||
for (i = 0; i < this.collisionMap.bottom.sprites.length; i++) {
|
||||
sprite = this.collisionMap.bottom.sprites[i];
|
||||
bottomY = Math.min(bottomY, sprite.getTop(true));
|
||||
if (sprite.get("type") == "platform") bottomPlatform = sprite;
|
||||
}
|
||||
|
||||
if (cur.mov == "jump" && cur.mov2 == null) attrs.sequenceIndex = 1;
|
||||
|
||||
if (heroBottomY >= bottomWorld) {
|
||||
attrs.y = y = bottomY - tileHeight + paddingBottom;
|
||||
attrs.velocity = velocity = 0;
|
||||
attrs.yVelocity = yVelocity = 0;
|
||||
attrs.state = this.buildState("dead", cur.dir);
|
||||
attrs.dead = true;
|
||||
}
|
||||
|
||||
function land(bottomY) {
|
||||
attrs.yVelocity = yVelocity = 0;
|
||||
attrs.y = y = bottomY - tileHeight + paddingBottom;
|
||||
updateTopBottom();
|
||||
attrs.state = nextState;
|
||||
if (nex.move == "walk" || nex.move == "run")
|
||||
attrs.nextState = hero.buildState(input && input.buttonBPressed() ? "run" : "walk", cur.mov2, nex.dir);
|
||||
else if (nex.mov == "skid")
|
||||
attrs.nextState = hero.buildState(input && input.buttonBPressed() ? "run" : "walk", cur.mov2, nex.opo);
|
||||
else if(nex.mov == "release")
|
||||
attrs.nextState = hero.buildState("idle", cur.mov2, nex.dir);
|
||||
else if (nex.mov == "dead") {
|
||||
attrs.velocity = velocity = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (yVelocity > 0 && heroBottomY >= bottomY) {
|
||||
// Stop falling
|
||||
land(bottomY);
|
||||
for (i = 0; i < this.collisionMap.bottom.sprites.length; i++)
|
||||
this.collisionMap.bottom.sprites[i].trigger("hit", this, "top", cur.dir);
|
||||
if (this.cancelUpdate) return true;
|
||||
} else if (cur.mov != "jump" && yVelocity == 0 && heroBottomY < bottomY) {
|
||||
// Start falling if no obstacle below
|
||||
attrs.nextState = state;
|
||||
attrs.state = this.buildState("jump", cur.mov2, cur.dir);
|
||||
} else if (yVelocity == 0 && heroBottomY == bottomY) {
|
||||
if (bottomPlatform)
|
||||
relativeVelocity = bottomPlatform.get("velocity");
|
||||
}
|
||||
|
||||
} else {
|
||||
// Velocity is negative (going up). Stop if obstacle above.
|
||||
var topY = Math.max(-400, this.get("ceiling") || -400);
|
||||
for (i = 0; i < this.collisionMap.top.sprites.length; i++)
|
||||
if (!dead && heroTopY > 0 )
|
||||
topY = Math.max(topY, this.collisionMap.top.sprites[i].getBottom(true));
|
||||
|
||||
if (cur.mov == "jump" && cur.mov2 == null) attrs.sequenceIndex = 0;
|
||||
|
||||
if (heroTopY < topY) {
|
||||
attrs.yVelocity = yVelocity = 0;
|
||||
attrs.y = y = topY - paddingTop;
|
||||
updateTopBottom();
|
||||
for (i = 0; i < this.collisionMap.top.sprites.length; i++)
|
||||
this.collisionMap.top.sprites[i].trigger("hit", this, "bottom", cur.dir);
|
||||
if (this.cancelUpdate) return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (velocity <= 0) {
|
||||
// Stop if obstacle left
|
||||
var leftX = 0;
|
||||
for (i = 0; i < this.collisionMap.left.sprites.length; i++)
|
||||
if (heroTopY > 0 )
|
||||
leftX = Math.max(leftX, this.collisionMap.left.sprites[i].getRight(true));
|
||||
|
||||
if (heroLeftX <= leftX) {
|
||||
attrs.velocity = velocity = 0;
|
||||
attrs.x = x = leftX - paddingLeft;
|
||||
for (i = 0; i < this.collisionMap.left.sprites.length; i++)
|
||||
this.collisionMap.left.sprites[i].trigger("hit", this, "right", cur.mov2);
|
||||
if (this.cancelUpdate) return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (velocity >= 0) {
|
||||
// Stop if obstacle to the right
|
||||
var rightX = this.world.width();
|
||||
for (i = 0; i < this.collisionMap.right.sprites.length; i++)
|
||||
if (heroTopY > 0 )
|
||||
rightX = Math.min(rightX, this.collisionMap.right.sprites[i].getLeft(true));
|
||||
|
||||
if (heroRightX >= rightX) {
|
||||
attrs.velocity = velocity = 0;
|
||||
attrs.x = x = rightX - heroWidth - paddingLeft;
|
||||
for (i = 0; i < this.collisionMap.right.sprites.length; i++)
|
||||
this.collisionMap.right.sprites[i].trigger("hit", this, "left", cur.mov2);
|
||||
if (this.cancelUpdate) return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (velocity || relativeVelocity) attrs.x = x = x + Math.round((velocity + relativeVelocity) * (dt/1000));
|
||||
if (yVelocity) attrs.y = y = y + Math.round(yVelocity * (dt/1000));
|
||||
|
||||
// Set modified attributes
|
||||
if (!_.isEmpty(attrs)) this.set(attrs);
|
||||
|
||||
if (this.debugPanel)
|
||||
this.debugPanel.set({velocity: velocity});
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
}).call(this);
|
||||
476
games/mario/src/input.js
Normal file
@@ -0,0 +1,476 @@
|
||||
(function() {
|
||||
|
||||
/**
|
||||
*
|
||||
* Backbone Game Engine - An elementary HTML5 canvas game engine using Backbone.
|
||||
*
|
||||
* Copyright (c) 2014 Martin Drapeau
|
||||
* https://github.com/martindrapeau/backbone-game-engine
|
||||
*
|
||||
*/
|
||||
|
||||
var keyboardImg = new Image();
|
||||
keyboardImg.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAAAwCAYAAABHTnUeAAACYklEQVR42u3dS5KCQBAEUNx5LJbcYs42t2DJsViOizFCnQH6U9Wd2ZW5JuQVmtAoEd4mRQmcW2+AovSMCqCEjgqghM5hAbZt++qNe808z9852zP7me1s/n8LgDZAyiCj+JntjP4/BUAd4GqQEfzMdla/CgDkZ7az+lUAID+zndWvAgD5me2sfhUAyM9sZ/WrAEB+ZjurXwUA8jPbWf0qAJCf2c7qVwGA/Mx2Vr8KAORntrP6VQAgf6798VpFhsd+zO0W/itX7vYpfvMCPJGlB7lkCEu/d1SAc/+RLWfbHL9pAV6RoxXAqtgqwPUMnz6LD/+R36QAVsDSIWr9uTPWzNb6HsDyvfE69mdGb39VAc7OQCMXoGbGlgWwPjF5Hvuj9X3tuv/KX1SAlEtvhAKUzNqqAB5X5ZYFONmHqT+rAKVrztSkDIdYACu/ld1rSep97K+OsUeBQxag5xzeBfC8H2tx8jl6b7wK7LIEut/v07qu1eDUIaz9tUEqgOVSVAVIxFqBU4co9edkWZZp3/fTbXovgby/jQu/BMqBj1YA9JvgFu9B+JvgnCEiFADla9BWJyB9DZqQ51Jh5AIg/RA2wtWX9oewHulZAIube4RHIX73ZWov8R/NQPcoRMvoYbi31yp2IBRguIfhWkQFeHutYgdiAYZ4HNo7KgCmndWvAgD5me2sfhUAyM9sZ/WrAEB+ZjurXwUA8jPbWf0qAJCf2c7qVwGA/Mx2Vr8KAORntrP6VQAgP7Od1a8CAPmZ7ax+/UkemJ/ZzujX36QC+pntbH79UbYSOiqAEjoqgBI6KoASOj+RrUxeKnOx4gAAAABJRU5ErkJggg==";
|
||||
|
||||
Backbone.InputButton = Backbone.Element.extend({
|
||||
defaults: _.extend({}, Backbone.Element.prototype.defaults, {
|
||||
// Relative position references
|
||||
left: undefined,
|
||||
right: undefined,
|
||||
top: undefined,
|
||||
bottom: undefined,
|
||||
backgroundColor: "transparent",
|
||||
pressed: false
|
||||
}),
|
||||
onAttach: function() {
|
||||
Backbone.Element.prototype.onAttach.apply(this, arguments);
|
||||
this.stopListening();
|
||||
this.calculatePosition();
|
||||
},
|
||||
onDetach: function() {
|
||||
Backbone.Element.prototype.onDetach.apply(this, arguments);
|
||||
this.set("pressed", false);
|
||||
},
|
||||
calculatePosition: function() {
|
||||
// Set x and y based on relative position references
|
||||
if (!this.engine) return this;
|
||||
var canvas = this.engine.canvas,
|
||||
attrs = {};
|
||||
|
||||
if (this.attributes.left !== undefined)
|
||||
attrs.x = this.attributes.left;
|
||||
else if (this.attributes.right !== undefined)
|
||||
attrs.x = canvas.width - this.attributes.right - this.attributes.width;
|
||||
else
|
||||
throw "InputButton " + this.id + " missing left or right attributes.";
|
||||
|
||||
if (this.attributes.top !== undefined)
|
||||
attrs.y = this.attributes.top;
|
||||
else if (this.attributes.bottom !== undefined)
|
||||
attrs.y = canvas.height - this.attributes.bottom - this.attributes.height;
|
||||
else
|
||||
throw "InputButton " + this.id + " missing top or bottom attributes.";
|
||||
|
||||
this.set(attrs);
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
Backbone.LeftInputButton = Backbone.InputButton.extend({
|
||||
onDraw: function(context) {
|
||||
var x = this.get("x"),
|
||||
y = this.get("y"),
|
||||
width = this.get("width"),
|
||||
height = this.get("height"),
|
||||
pressed = this.get("pressed");
|
||||
//drawRect(context, x, y, width, height, "rgba(64, 64, 64, 0.5)");
|
||||
context.save();
|
||||
context.beginPath();
|
||||
context.moveTo(x+130, y+20);
|
||||
context.lineTo(x+40, y+80);
|
||||
context.lineTo(x+130, y+140);
|
||||
context.lineTo(x+100, y+80);
|
||||
context.lineTo(x+130, y+20);
|
||||
context.lineJoin = 'bevel';
|
||||
context.fillStyle = pressed ? "#00FF00" : "#009900";
|
||||
context.fill();
|
||||
context.lineWidth = 5;
|
||||
context.strokeStyle = '#111';
|
||||
context.stroke();
|
||||
context.restore();
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
Backbone.RightInputButton = Backbone.InputButton.extend({
|
||||
onDraw: function(context) {
|
||||
var x = this.get("x"),
|
||||
y = this.get("y"),
|
||||
width = this.get("width"),
|
||||
height = this.get("height"),
|
||||
pressed = this.get("pressed");
|
||||
//drawRect(context, x, y, width, height, "rgba(128, 128, 128, 0.5)");
|
||||
context.save();
|
||||
context.beginPath();
|
||||
context.moveTo(x+30, y+20);
|
||||
context.lineTo(x+120, y+80);
|
||||
context.lineTo(x+30, y+140);
|
||||
context.lineTo(x+60, y+80);
|
||||
context.lineTo(x+30, y+20);
|
||||
context.lineJoin = 'bevel';
|
||||
context.fillStyle = pressed ? "#00FF00" : "#009900";
|
||||
context.fill();
|
||||
context.lineWidth = 5;
|
||||
context.strokeStyle = '#111';
|
||||
context.stroke();
|
||||
context.restore();
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
Backbone.AInputButton = Backbone.InputButton.extend({
|
||||
onDraw: function(context) {
|
||||
var x = this.get("x"),
|
||||
y = this.get("y"),
|
||||
width = this.get("width"),
|
||||
height = this.get("height"),
|
||||
pressed = this.get("pressed");
|
||||
//drawRect(context, x, y, width, height, "rgba(255, 255, 255, 0.5)");
|
||||
context.save();
|
||||
context.beginPath();
|
||||
context.arc(x+80, y+80, 60, 0, 2*Math.PI, false);
|
||||
context.fillStyle = "#111";
|
||||
context.fill();
|
||||
context.beginPath();
|
||||
context.arc(x+80, y+80, 55, 0, 2*Math.PI, false);
|
||||
context.fillStyle = pressed ? "#0000FF" : "#000099";
|
||||
context.fill();
|
||||
context.restore();
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
Backbone.BInputButton = Backbone.InputButton.extend({
|
||||
onDraw: function(context) {
|
||||
var x = this.get("x"),
|
||||
y = this.get("y"),
|
||||
width = this.get("width"),
|
||||
height = this.get("height"),
|
||||
pressed = this.get("pressed");
|
||||
//drawRect(context, x, y, width, height, "rgba(192, 192, 192, 0.5)");
|
||||
context.save();
|
||||
context.beginPath();
|
||||
context.arc(x+80, y+80, 60, 0, 2*Math.PI, false);
|
||||
context.fillStyle = "#111";
|
||||
context.fill();
|
||||
context.beginPath();
|
||||
context.arc(x+80, y+80, 55, 0, 2*Math.PI, false);
|
||||
context.fillStyle = pressed ? "#FF0000" : "#990000";
|
||||
context.fill();
|
||||
context.restore();
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Input class; a Backbone Model which captures input events
|
||||
// and stores them as model attributes with true if pressed.
|
||||
// Supports keyboard, and a drawn touchpad activated by touch
|
||||
// or mouse events.
|
||||
Backbone.Input = Backbone.Model.extend({
|
||||
defaults: {
|
||||
// Supported buttons
|
||||
left: false, // Left button pressed?
|
||||
right: false, // Right button pressed?
|
||||
buttonA: false, // A button pressed? (X on keyboard)
|
||||
buttonB: false, // B button pressed? (Z on keyboard)
|
||||
|
||||
// Touch pad
|
||||
drawTouchpad: "auto", // Boolean to draw. Set to auto to draw only for touch devices.
|
||||
touchEnabled: false // Touch device? Automatically determined. Do not set.
|
||||
},
|
||||
buttons: [{
|
||||
id: "left", name: "left-input-button",
|
||||
left: 0, bottom: 0, width: 160, height: 160
|
||||
}, {
|
||||
id: "right", name: "right-input-button",
|
||||
left: 160, bottom: 0, width: 160, height: 160
|
||||
}, {
|
||||
id: "buttonA", name: "a-input-button",
|
||||
right: 0, bottom: 0, width: 160, height: 160
|
||||
}, {
|
||||
id: "buttonB", name: "b-input-button",
|
||||
right: 160, bottom: 0, width: 160, height: 160
|
||||
}],
|
||||
initialize: function(attributes, options) {
|
||||
options || (options = {});
|
||||
this._ongoingTouches = [];
|
||||
|
||||
_.bindAll(this, "rightPressed", "leftPressed", "buttonBPressed", "buttonAPressed");
|
||||
|
||||
for (var i = 0; i < this.buttons.length; i++) {
|
||||
var config = this.buttons[i],
|
||||
cls = _.classify(config.name);
|
||||
config.instance = new Backbone[cls](config);
|
||||
}
|
||||
|
||||
// Handle touch events
|
||||
var touchEnabled =
|
||||
window.forceTouchpad ||
|
||||
"ontouchstart" in window ||
|
||||
"onorientationchange" in window ||
|
||||
window.navigator.msMaxTouchPoints ||
|
||||
window.navigator.maxTouchPoints > 0 ||
|
||||
window.navigator.isCocoonJS;
|
||||
this.set({touchEnabled: touchEnabled});
|
||||
|
||||
// Debug panel
|
||||
var debugPanel = this.debugPanel = options.debugPanel;
|
||||
if (debugPanel) {
|
||||
this.on("change:pressed", function() {
|
||||
debugPanel.set({pressed: this.get("pressed")});
|
||||
});
|
||||
this.on("change:touched", function() {
|
||||
debugPanel.set({touched: this.get("touched")});
|
||||
});
|
||||
this.on("change:clicked", function() {
|
||||
debugPanel.set({clicked: this.get("clicked")});
|
||||
});
|
||||
}
|
||||
|
||||
if (touchEnabled) {
|
||||
// Prevent touch scroll
|
||||
$(document).bind("touchmove.InputTouchScroll", function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
});
|
||||
|
||||
// Prevent links from opening popup after a while
|
||||
document.documentElement.style.webkitTouchCallout = "none";
|
||||
}
|
||||
|
||||
this.on("attach", this.onAttach, this);
|
||||
this.on("detach", this.onDetach, this);
|
||||
},
|
||||
onAttach: function() {
|
||||
this.onDetach();
|
||||
// Handle keyboard input
|
||||
$(document).on("keydown.Input", this.onKeydown.bind(this));
|
||||
$(document).on("keyup.Input", this.onKeyup.bind(this));
|
||||
|
||||
if (this.hasTouchpad()) {
|
||||
if (this.get("touchEnabled")) {
|
||||
if (window.navigator.msMaxTouchPoints) {
|
||||
$(document).on("pointerdown.InputTouchpad", this.onTouchStart.bind(this));
|
||||
$(document).on("pointermove.InputTouchpad", this.onTouchMove.bind(this));
|
||||
$(document).on("pointerup.InputTouchpad", this.onTouchEnd.bind(this));
|
||||
$(document).on("pointercancel.InputTouchpad", this.onTouchEnd.bind(this));
|
||||
} else {
|
||||
$(document).on("touchstart.InputTouchpad", this.onTouchStart.bind(this));
|
||||
$(document).on("touchmove.InputTouchpad", this.onTouchMove.bind(this));
|
||||
$(document).on("touchend.InputTouchpad", this.onTouchEnd.bind(this));
|
||||
$(document).on("touchleave.InputTouchpad", this.onTouchEnd.bind(this));
|
||||
$(document).on("touchcancel.InputTouchpad", this.onTouchEnd.bind(this));
|
||||
}
|
||||
} else {
|
||||
// Fallback to handling mouse events
|
||||
$(document).on("mousedown.InputTouchpad", this.onMouseDown.bind(this));
|
||||
$(document).on("mousemove.InputTouchpad", this.onMouseDown.bind(this));
|
||||
$(document).on("mouseup.InputTouchpad", this.onMouseUp.bind(this));
|
||||
}
|
||||
for (var i = 0; i < this.buttons.length; i++) {
|
||||
this.buttons[i].instance.engine = this.engine;
|
||||
this.buttons[i].instance.trigger("attach");
|
||||
}
|
||||
}
|
||||
},
|
||||
onDetach: function() {
|
||||
$(document).off(".Input");
|
||||
this._ongoingTouches = [];
|
||||
this.set({
|
||||
left: false,
|
||||
right: false,
|
||||
buttonA: false,
|
||||
buttonB: false
|
||||
});
|
||||
if (this.hasTouchpad()) {
|
||||
$(document).off(".InputTouchpad");
|
||||
for (var i = 0; i < this.buttons.length; i++) {
|
||||
this.buttons[i].instance.trigger("detach");
|
||||
this.buttons[i].instance.engine = undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
hasTouchpad: function() {
|
||||
var drawTouchpad = this.get("drawTouchpad");
|
||||
if (_.isBoolean(drawTouchpad)) return drawTouchpad;
|
||||
if (drawTouchpad == "auto" && this.get("touchEnabled")) return true;
|
||||
return false;
|
||||
},
|
||||
|
||||
// Engine core functions
|
||||
update: function(dt) {
|
||||
return true;
|
||||
},
|
||||
draw: function(context, options) {
|
||||
if (this.hasTouchpad()) {
|
||||
for (var i = 0; i < this.buttons.length; i++)
|
||||
this.buttons[i].instance.draw(context);
|
||||
} else{
|
||||
context.drawImage(keyboardImg, 8, context.canvas.height - 56);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
// Keyboard events
|
||||
onKeydown: function(e) {
|
||||
var buttonId = this.keyCodeToButtonId(e.keyCode),
|
||||
attrs = {};
|
||||
attrs[e.keyCode] = true;
|
||||
if (buttonId) {
|
||||
attrs[buttonId] = true;
|
||||
if (this.hasTouchpad())
|
||||
for (var i = 0; i < this.buttons.length; i++)
|
||||
if (this.buttons[i].instance.id == buttonId)
|
||||
this.buttons[i].instance.set("pressed", true);
|
||||
}
|
||||
this.set(attrs);
|
||||
},
|
||||
onKeyup: function(e) {
|
||||
var buttonId = this.keyCodeToButtonId(e.keyCode),
|
||||
attrs = {};
|
||||
attrs[e.keyCode] = false;
|
||||
if (buttonId) {
|
||||
attrs[buttonId] = false;
|
||||
if (this.hasTouchpad())
|
||||
for (var i = 0; i < this.buttons.length; i++)
|
||||
if (this.buttons[i].instance.id == buttonId)
|
||||
this.buttons[i].instance.set("pressed", false);
|
||||
}
|
||||
this.set(attrs);
|
||||
},
|
||||
|
||||
// Touch events
|
||||
detectTouched: function() {
|
||||
var attrs = {};
|
||||
var canvas = this.engine.canvas;
|
||||
// 计算 CSS 缩放比例
|
||||
var scaleX = canvas.width / canvas.offsetWidth;
|
||||
var scaleY = canvas.height / canvas.offsetHeight;
|
||||
|
||||
for (var i = 0; i < this.buttons.length; i++) {
|
||||
var button = this.buttons[i].instance,
|
||||
touched = false;
|
||||
for (var t = 0; t < this._ongoingTouches.length; t++) {
|
||||
// 转换触摸坐标到 canvas 坐标系
|
||||
var touchX = (this._ongoingTouches[t].pageX - canvas.offsetLeft) * scaleX;
|
||||
var touchY = (this._ongoingTouches[t].pageY - canvas.offsetTop) * scaleY;
|
||||
touched = button.overlaps(touchX, touchY);
|
||||
if (touched) break;
|
||||
}
|
||||
attrs[button.id] = touched;
|
||||
button.set("pressed", touched);
|
||||
}
|
||||
|
||||
this.set(attrs);
|
||||
|
||||
return this;
|
||||
},
|
||||
onTouchStart: function(e) {
|
||||
var touches = e.changedTouches || [{
|
||||
identifier: e.pointerId,
|
||||
pageX: e.pageX,
|
||||
pageY: e.pageY
|
||||
}];
|
||||
|
||||
for (var i = 0; i < touches.length; i++)
|
||||
this._ongoingTouches.push(this._copyTouch(touches[i]));
|
||||
this.detectTouched();
|
||||
},
|
||||
onTouchMove: function(e) {
|
||||
var touches = e.changedTouches || [{
|
||||
identifier: e.pointerId,
|
||||
pageX: e.pageX,
|
||||
pageY: e.pageY
|
||||
}];
|
||||
|
||||
for (var i = 0; i < touches.length; i++) {
|
||||
var idx = this._ongoingTouchIndexById(touches[i].identifier);
|
||||
if (idx >= 0) this._ongoingTouches.splice(idx, 1, this._copyTouch(touches[i]));
|
||||
}
|
||||
|
||||
this.detectTouched();
|
||||
},
|
||||
onTouchEnd: function(e) {
|
||||
var touches = e.changedTouches || [{
|
||||
identifier: e.pointerId,
|
||||
pageX: e.pageX,
|
||||
pageY: e.pageY
|
||||
}];
|
||||
|
||||
for (var i=0; i < touches.length; i++) {
|
||||
var idx = this._ongoingTouchIndexById(touches[i].identifier);
|
||||
if (idx >= 0) this._ongoingTouches.splice(idx, 1);
|
||||
}
|
||||
|
||||
this.detectTouched();
|
||||
},
|
||||
|
||||
// Mouse events
|
||||
onMouseDown: function(e) {
|
||||
if (!e.which) return;
|
||||
|
||||
var canvas = this.engine.canvas;
|
||||
// 计算 CSS 缩放比例
|
||||
var scaleX = canvas.width / canvas.offsetWidth;
|
||||
var scaleY = canvas.height / canvas.offsetHeight;
|
||||
var x = (e.pageX - canvas.offsetLeft) * scaleX,
|
||||
y = (e.pageY - canvas.offsetTop) * scaleY,
|
||||
attrs = {};
|
||||
|
||||
for (var i = 0; i < this.buttons.length; i++) {
|
||||
var button = this.buttons[i].instance,
|
||||
clicked = button.overlaps(x, y);
|
||||
attrs[button.id] = clicked;
|
||||
button.set("pressed", clicked);
|
||||
}
|
||||
|
||||
this.set(attrs);
|
||||
},
|
||||
onMouseUp: function(e) {
|
||||
for (var i = 0; i < this.buttons.length; i++) {
|
||||
var button = this.buttons[i].instance;
|
||||
if (this.get(button.id)) {
|
||||
this.set(button.id, false);
|
||||
button.set("pressed", false);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Button helpers
|
||||
rightPressed: function() {
|
||||
return !!this.get("right");
|
||||
},
|
||||
leftPressed: function() {
|
||||
return !!this.get("left");
|
||||
},
|
||||
buttonBPressed: function() {
|
||||
return !!this.get("buttonB");
|
||||
},
|
||||
buttonAPressed: function() {
|
||||
return !!this.get("buttonA");
|
||||
},
|
||||
keyCodeToButtonId: function(keyCode) {
|
||||
switch (keyCode) {
|
||||
case 39:
|
||||
return "right";
|
||||
case 37:
|
||||
return "left";
|
||||
case 90:
|
||||
return "buttonB";
|
||||
case 88:
|
||||
return "buttonA";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
// Touch event helpers.
|
||||
// Source: https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Touch_events
|
||||
_copyTouch: function(touch) {
|
||||
return {
|
||||
identifier: touch.identifier,
|
||||
pageX: touch.pageX,
|
||||
pageY: touch.pageY
|
||||
};
|
||||
},
|
||||
_ongoingTouchIndexById: function(idToFind) {
|
||||
for (var i = 0; i < this._ongoingTouches.length; i++) {
|
||||
var id = this._ongoingTouches[i].identifier;
|
||||
if (id == idToFind) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
});
|
||||
|
||||
}).call(this);
|
||||
50
games/mario/src/local-storage.js
Normal file
@@ -0,0 +1,50 @@
|
||||
(function() {
|
||||
|
||||
/**
|
||||
*
|
||||
* Backbone Game Engine - An elementary HTML5 canvas game engine using Backbone.
|
||||
*
|
||||
* Copyright (c) 2014 Martin Drapeau
|
||||
* https://github.com/martindrapeau/backbone-game-engine
|
||||
*
|
||||
*/
|
||||
|
||||
// Load this file to persist in local storage.
|
||||
// It will replace Backbone.World's save and fetch methods with
|
||||
// naive implementations.
|
||||
|
||||
Backbone.World.prototype.fetch = function(options) {
|
||||
options || (options = {});
|
||||
|
||||
var data = localStorage.getItem(this.id);
|
||||
if (data) {
|
||||
console.log("===== LOADING LOCAL STORAGE ====");
|
||||
this.set(JSON.parse(data));
|
||||
}
|
||||
|
||||
if (_.isFunction(options.success)) options.success();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Backbone.World.prototype.save = function(attributes, options) {
|
||||
options || (options = {});
|
||||
|
||||
|
||||
this.set({
|
||||
state: "play",
|
||||
sprites: this.sprites.map(function(sprite) {
|
||||
return sprite.toSave.apply(sprite);
|
||||
}),
|
||||
savedOn: new Date().toJSON()
|
||||
}, {silent: true});
|
||||
|
||||
console.log("===== SAVING LOCAL STORAGE ====");
|
||||
localStorage.setItem(this.id, JSON.stringify(this.toJSON()));
|
||||
|
||||
if (_.isFunction(options.success)) options.success();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
}).call(this);
|
||||
72
games/mario/src/shapes.js
Normal file
@@ -0,0 +1,72 @@
|
||||
(function() {
|
||||
|
||||
/**
|
||||
*
|
||||
* Backbone Game Engine - An elementary HTML5 canvas game engine using Backbone.
|
||||
*
|
||||
* Copyright (c) 2014 Martin Drapeau
|
||||
* https://github.com/martindrapeau/backbone-game-engine
|
||||
*
|
||||
*/
|
||||
|
||||
function drawRoundRect(ctx, x, y, width, height, borderRadius, fill, stroke) {
|
||||
if (typeof stroke == "undefined" ) stroke = true;
|
||||
if (typeof borderRadius === "undefined") borderRadius = 5;
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x + borderRadius, y);
|
||||
ctx.lineTo(x + width - borderRadius, y);
|
||||
ctx.quadraticCurveTo(x + width, y, x + width, y + borderRadius);
|
||||
ctx.lineTo(x + width, y + height - borderRadius);
|
||||
ctx.quadraticCurveTo(x + width, y + height, x + width - borderRadius, y + height);
|
||||
ctx.lineTo(x + borderRadius, y + height);
|
||||
ctx.quadraticCurveTo(x, y + height, x, y + height - borderRadius);
|
||||
ctx.lineTo(x, y + borderRadius);
|
||||
ctx.quadraticCurveTo(x, y, x + borderRadius, y);
|
||||
ctx.closePath();
|
||||
if (stroke) {
|
||||
ctx.strokeStyle = stroke;
|
||||
ctx.stroke();
|
||||
}
|
||||
if (fill) {
|
||||
ctx.fillStyle = fill;
|
||||
ctx.fill();
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function drawRect(ctx, x, y, width, height, fill, stroke) {
|
||||
ctx.save();
|
||||
if (fill) {
|
||||
ctx.fillStyle = fill;
|
||||
ctx.fillRect(x, y, width, height);
|
||||
}
|
||||
if (stroke) {
|
||||
ctx.strokeStyle = stroke;
|
||||
ctx.strokeRect(x, y, width, height);
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function drawCircle(ctx, x, y, borderRadius, fill, stroke) {
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, borderRadius, 0, 2*Math.PI, false);
|
||||
if (stroke) {
|
||||
ctx.strokeStyle = stroke;
|
||||
ctx.stroke();
|
||||
}
|
||||
if (fill) {
|
||||
ctx.fillStyle = fill;
|
||||
ctx.fill();
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
_.extend(window, {
|
||||
drawRect: drawRect,
|
||||
drawRoundRect: drawRoundRect,
|
||||
drawCircle: drawCircle
|
||||
});
|
||||
|
||||
}).call(this);
|
||||
914
games/mario/src/world.js
Normal file
@@ -0,0 +1,914 @@
|
||||
(function() {
|
||||
|
||||
/**
|
||||
*
|
||||
* Backbone Game Engine - An elementary HTML5 canvas game engine using Backbone.
|
||||
*
|
||||
* Copyright (c) 2014 Martin Drapeau
|
||||
* https://github.com/martindrapeau/backbone-game-engine
|
||||
*
|
||||
*/
|
||||
|
||||
// Backbone.World is Backbone model which contains a collection of sprites.
|
||||
Backbone.World = Backbone.Model.extend({
|
||||
defaults: {
|
||||
name: "",
|
||||
x: 0,
|
||||
y: 0,
|
||||
tileWidth: 32,
|
||||
tileHeight: 32,
|
||||
width: 30,
|
||||
height: 17,
|
||||
viewportTop: 0, viewportRight: 0, viewportBottom: 0, viewportLeft: 0,
|
||||
backgroundColor: "rgba(66, 66, 255, 1)",
|
||||
sprites: [], // Copy for persistence only. Use the direct member sprites which is a collection.
|
||||
state: "play", // play or pause
|
||||
time: 0 // Time played in ms
|
||||
},
|
||||
shallowAttributes: [
|
||||
"x", "y", "width", "height", "tileWidth", "tileHeight", "backgroundColor",
|
||||
"viewportLeft", "viewportRight", "viewportTop", "viewportBottom"
|
||||
],
|
||||
viewport: {x: 0, y: 0, width: 0, height: 0},
|
||||
spriteOptions: {offsetX: 0, offsetY: 0},
|
||||
initialize: function(attributes, options) {
|
||||
options || (options = {});
|
||||
this.input = options.input;
|
||||
this.camera = options.camera;
|
||||
this.debugPanel = options.debugPanel;
|
||||
|
||||
_.bindAll(this,
|
||||
"wrapTime",
|
||||
"save", "getWorldIndex", "getWorldCol", "getWorldRow", "cloneAtPosition",
|
||||
"spawnSprites", "height", "width", "add", "remove", "setTimeout", "clearTimeout", "getHumanTime"
|
||||
);
|
||||
|
||||
this._times = {};
|
||||
this.wrapTime("filterAt");
|
||||
this.wrapTime("findAt");
|
||||
this.wrapTime("findCollisions");
|
||||
this.wrapTime("draw");
|
||||
this.wrapTime("update");
|
||||
this.wrapTime("drawDynamicSprites");
|
||||
this.wrapTime("drawStaticSprites");
|
||||
|
||||
this.sprites = new Backbone.Collection();
|
||||
this.quadTree = QuadTree(0, 0, this.width(), this.height());
|
||||
this.setupSpriteLayers();
|
||||
this.spawnSprites();
|
||||
|
||||
this.on("change:backgroundImage", this.spawnBackgroundImage);
|
||||
this.spawnBackgroundImage();
|
||||
|
||||
this.on("attach", this.onAttach, this);
|
||||
this.on("detach", this.onDetach, this);
|
||||
|
||||
this.on("change:state", this.onStateChange, this);
|
||||
this.onStateChange();
|
||||
},
|
||||
reportTimes: function(fn) {
|
||||
var total = this.attributes.time;
|
||||
function reportTime(time, fn) {
|
||||
console.log(fn + ": " + Math.round(time*10000/total)/100 + "% " + time + "ms");
|
||||
}
|
||||
_.each(this._times, reportTime);
|
||||
reportTime(total, "total");
|
||||
|
||||
// Report collision detection as % of update
|
||||
var time = this._times["findAt"] + this._times["filterAt"] + this._times["findCollisions"];
|
||||
console.log("collision/update: " + Math.round(time*10000/this._times["update"])/100 + "% " + time + "ms");
|
||||
},
|
||||
wrapTime: function(fn) {
|
||||
var world = this,
|
||||
origFn = this[fn];
|
||||
this._times[fn] = 0;
|
||||
this[fn] = function() {
|
||||
var now = _.now(),
|
||||
result = origFn.apply(world, arguments);
|
||||
if (world.attributes.state == "play") world._times[fn] += _.now() - now;
|
||||
return result;
|
||||
}
|
||||
},
|
||||
height: function() {
|
||||
return this.get("height") * this.get("tileHeight");
|
||||
},
|
||||
width: function() {
|
||||
return this.get("width") * this.get("tileWidth");
|
||||
},
|
||||
toShallowJSON: function() {
|
||||
return _.pick(this.attributes, this.shallowAttributes);
|
||||
},
|
||||
onAttach: function() {
|
||||
var engine = this.engine;
|
||||
this.on("change:viewportLeft change:viewportRight change:viewportTop change:viewportBottom", this.updateViewport);
|
||||
this.updateViewport();
|
||||
this.sprites.each(function(sprite) {
|
||||
sprite.engine = engine;
|
||||
sprite.trigger("attach", engine);
|
||||
});
|
||||
this.listenTo(this.engine, "tap", this.onTap);
|
||||
this.listenTo(this.engine, "key", this.onKey);
|
||||
if (this.camera) this.camera.maybePan();
|
||||
},
|
||||
onDetach: function() {
|
||||
this.stopListening(this.engine);
|
||||
this.sprites.each(function(sprite) {
|
||||
sprite.engine = undefined;
|
||||
sprite.trigger("detach");
|
||||
});
|
||||
this.off("change:viewportLeft change:viewportRight change:viewportTop change:viewportBottom", this.updateViewport);
|
||||
},
|
||||
onStateChange: function() {
|
||||
var state = this.get("state"),
|
||||
now = _.now();
|
||||
|
||||
if (state == "pause") {
|
||||
this.accumTime += (now - (this.startTime || now));
|
||||
this.set("time", this.accumTime);
|
||||
if (this.attributes.time) this.reportTimes();
|
||||
} else if (state == "play") {
|
||||
this.startTime = now;
|
||||
}
|
||||
},
|
||||
getHumanTime: function() {
|
||||
return _.ms2time(this.attributes.time);
|
||||
},
|
||||
updateViewport: function() {
|
||||
this.viewport.width = this.engine.canvas.width - this.attributes.viewportLeft - this.attributes.viewportRight;
|
||||
this.viewport.height = this.engine.canvas.height - this.attributes.viewportTop - this.attributes.viewportBottom;
|
||||
this.spriteOptions.viewportLeft = this.attributes.viewportLeft;
|
||||
this.spriteOptions.viewportRight = this.attributes.viewportRight;
|
||||
this.spriteOptions.viewportTop = this.attributes.viewportTop;
|
||||
this.spriteOptions.viewportBottom = this.attributes.viewportBottom;
|
||||
this.backgroundCanvas.width = this.engine.canvas.width;
|
||||
this.backgroundCanvas.height = this.engine.canvas.height;
|
||||
},
|
||||
onTap: function(e) {
|
||||
if (this.attributes.state != "play") return;
|
||||
e.world = this;
|
||||
e.worldX = e.canvasX - this.attributes.x;
|
||||
e.worldY = e.canvasY - this.attributes.y;
|
||||
this.trigger("tap", e);
|
||||
},
|
||||
onKey: function(e) {
|
||||
if (this.attributes.state != "play") return;
|
||||
e.world = this;
|
||||
this.trigger("key", e);
|
||||
},
|
||||
|
||||
// Split static sprites (background tiles) from dynamic ones (animated or moving).
|
||||
// Draw static on a background and seldomly redraw.
|
||||
// Dynamic ones are redrawn every animation frame.
|
||||
// Maintain shadow collections to quickly access the two types.
|
||||
setupSpriteLayers: function() {
|
||||
var world = this,
|
||||
staticSprites = this.staticSprites = new Backbone.Collection(),
|
||||
dynamicSprites = this.dynamicSprites = new Backbone.Collection();
|
||||
staticSprites.lookup = {};
|
||||
staticSprites.maxSpriteWidth = staticSprites.maxSpriteHeight = 0;
|
||||
dynamicSprites.lookup = {};
|
||||
dynamicSprites.maxSpriteWidth = dynamicSprites.maxSpriteHeight = 0;
|
||||
|
||||
this.quadTree.clear();
|
||||
|
||||
function addQto(sprite) {
|
||||
sprite._qto || (sprite._qto = {});
|
||||
sprite._qto.id = sprite.id;
|
||||
sprite._qto.x = sprite.getLeft();
|
||||
sprite._qto.y = sprite.getTop();
|
||||
sprite._qto.w = sprite.getRight() - sprite._qto.x;
|
||||
sprite._qto.h = sprite.getBottom() - sprite._qto.y;
|
||||
world.quadTree.put(sprite._qto);
|
||||
}
|
||||
function removeQto(sprite) {
|
||||
world.quadTree.remove(sprite._qto, "id");
|
||||
sprite._qto.id = undefined;
|
||||
sprite._qto.x = undefined;
|
||||
sprite._qto.y = undefined;
|
||||
sprite._qto.w = undefined;
|
||||
sprite._qto.h = undefined;
|
||||
}
|
||||
|
||||
function add(sprite, collection) {
|
||||
collection.add(sprite);
|
||||
index = world.getWorldIndex(sprite);
|
||||
collection.lookup[index] || (collection.lookup[index] = []);
|
||||
collection.lookup[index].push(sprite);
|
||||
sprite.set("lookupIndex", index);
|
||||
collection.maxSpriteWidth = Math.max(collection.maxSpriteWidth, sprite.attributes.width);
|
||||
collection.maxSpriteHeight = Math.max(collection.maxSpriteHeight, sprite.attributes.width);
|
||||
addQto(sprite);
|
||||
}
|
||||
|
||||
function update(sprite, collection) {
|
||||
var oldIndex = sprite.attributes.lookupIndex,
|
||||
newIndex = world.getWorldIndex(sprite);
|
||||
removeQto(sprite);
|
||||
addQto(sprite);
|
||||
if (oldIndex == newIndex) return;
|
||||
if (oldIndex !== undefined && collection.lookup[oldIndex]) {
|
||||
var pos = _.indexOf(collection.lookup[oldIndex], sprite);
|
||||
if (pos >= 0) collection.lookup[oldIndex].splice(pos, 1);
|
||||
}
|
||||
collection.lookup[newIndex] || (collection.lookup[newIndex] = []);
|
||||
collection.lookup[newIndex].push(sprite);
|
||||
sprite.set("lookupIndex", newIndex);
|
||||
collection.maxSpriteWidth = Math.max(collection.maxSpriteWidth, sprite.attributes.width);
|
||||
collection.maxSpriteHeight = Math.max(collection.maxSpriteHeight, sprite.attributes.width);
|
||||
}
|
||||
|
||||
function remove(sprite, collection) {
|
||||
var oldIndex = sprite.attributes.lookupIndex;
|
||||
if (oldIndex !== undefined && collection.lookup[oldIndex]) {
|
||||
var pos = _.indexOf(collection.lookup[oldIndex], sprite);
|
||||
if (pos >= 0) collection.lookup[oldIndex].splice(pos, 1);
|
||||
}
|
||||
sprite.unset("lookupIndex");
|
||||
collection.remove(sprite);
|
||||
removeQto(sprite);
|
||||
}
|
||||
|
||||
this.listenTo(this.dynamicSprites, "change:x change:y", function(sprite) {
|
||||
update(sprite, dynamicSprites);
|
||||
});
|
||||
|
||||
this.listenTo(this.sprites, "add", function(sprite) {
|
||||
if (sprite.get("static"))
|
||||
add(sprite, staticSprites);
|
||||
else
|
||||
add(sprite, dynamicSprites);
|
||||
world.requestBackgroundRedraw = true;
|
||||
});
|
||||
|
||||
this.listenTo(this.sprites, "remove", function(sprite) {
|
||||
if (sprite.get("static"))
|
||||
remove(sprite, staticSprites);
|
||||
else
|
||||
remove(sprite, dynamicSprites);
|
||||
world.requestBackgroundRedraw = true;
|
||||
});
|
||||
|
||||
this.backgroundCanvas = document.createElement("canvas");
|
||||
this.backgroundCanvas.screencanvas = false;
|
||||
this.backgroundCanvas.style.display = "none";
|
||||
document.body.appendChild(this.backgroundCanvas);
|
||||
this.backgroundContext = this.backgroundCanvas.getContext("2d");
|
||||
|
||||
this.previewCanvas = document.createElement("canvas");
|
||||
this.previewCanvas.screencanvas = false;
|
||||
this.previewCanvas.style.display = "none";
|
||||
this.previewCanvas.width = 300;
|
||||
this.previewCanvas.height = 180;
|
||||
document.body.appendChild(this.previewCanvas);
|
||||
this.previewContext = this.previewCanvas.getContext("2d");
|
||||
drawRect(this.previewContext,
|
||||
0, 0, this.previewCanvas.width, this.previewCanvas.height,
|
||||
this.attributes.backgroundColor
|
||||
);
|
||||
|
||||
this.on("change", function() {
|
||||
this.requestBackgroundRedraw = true;
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
spawnBackgroundImage: function() {
|
||||
this.backgroundImage = undefined;
|
||||
var id = this.get("backgroundImage");
|
||||
if (!id) return;
|
||||
|
||||
id = id.replace("#", "");
|
||||
var img = document.getElementById(id);
|
||||
|
||||
if (!img)
|
||||
throw "Invalid img #" + id + " for world backgroundImage. Cannot find element by id.";
|
||||
|
||||
this.backgroundImage = img;
|
||||
return this;
|
||||
},
|
||||
spawnSprites: function() {
|
||||
var world = this,
|
||||
w = this.toShallowJSON(),
|
||||
_sprites = this.get("sprites"),
|
||||
options = {
|
||||
input: this.input
|
||||
};
|
||||
|
||||
var sprites = _.clone(this.sprites.models);
|
||||
_.each(sprites, function(sprite) {
|
||||
world.remove(sprite);
|
||||
sprite.input = undefined;
|
||||
sprite.trigger("detach");
|
||||
});
|
||||
|
||||
_.each(_sprites, function(sprite) {
|
||||
var s = sprite.attributes ? sprite.attributes : sprite,
|
||||
cls = _.classify(s.name),
|
||||
col = world.getWorldCol(s.x),
|
||||
row = world.getWorldRow(s.y);
|
||||
|
||||
var newSprite = new Backbone[cls](_.extend(s,
|
||||
{
|
||||
col: col,
|
||||
row: row
|
||||
}
|
||||
), options);
|
||||
|
||||
newSprite = world.add(newSprite);
|
||||
if (world.engine) {
|
||||
newSprite.engine = world.engine;
|
||||
newSprite.trigger("attach");
|
||||
}
|
||||
|
||||
if (newSprite.get("hero"))
|
||||
world.camera.setOptions({world: world, subject: newSprite});
|
||||
});
|
||||
|
||||
this.requestBackgroundRedraw = true;
|
||||
|
||||
this.accumTime = this.get("time");
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
// When saving, persist the sprite collection in the model attribute sprites.
|
||||
save: function() {
|
||||
var sprites = this.sprites.reduce(function(sprites, sprite) {
|
||||
var s = sprite.toSave.apply(sprite);
|
||||
if (s) sprites.push(s);
|
||||
return s;
|
||||
}, []);
|
||||
|
||||
// Save a screenshot of 30x15 tiles, skipping the two top first rows
|
||||
if (this.engine && this.viewport && this.viewport.width) {
|
||||
var x = this.attributes.viewportLeft + this.attributes.tileWidth*2,
|
||||
y = this.attributes.viewportTop + this.attributes.tileHeight*2,
|
||||
width = this.viewport.width - this.attributes.tileWidth*5,
|
||||
height = this.viewport.height - this.attributes.tileHeight*2;
|
||||
|
||||
this.previewContext.drawImage(
|
||||
this.engine.canvas,
|
||||
x, y, width, height,
|
||||
0, 0, this.previewCanvas.width, this.previewCanvas.height
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
this.set({sprites: sprites, preview: this.previewCanvas.toDataURL()}, {silent: true});
|
||||
|
||||
return Backbone.Model.prototype.save.apply(this, arguments);
|
||||
},
|
||||
|
||||
// Resize the world
|
||||
resize: function(width, height) {
|
||||
var deltaY = (height - this.attributes.height) * this.attributes.tileHeight;
|
||||
|
||||
if (deltaY == 0) {
|
||||
this.set({width: width});
|
||||
return;
|
||||
}
|
||||
|
||||
// When resizing height, add/remove tiles above requiring translation.
|
||||
var sprites = this.sprites.map(function(sprite) {
|
||||
var s = sprite.toSave.apply(sprite);
|
||||
s.y += deltaY;
|
||||
return s;
|
||||
});
|
||||
|
||||
this.set({
|
||||
y: this.attributes.y - deltaY,
|
||||
width: width,
|
||||
height: height,
|
||||
sprites: sprites
|
||||
}, {silent: true});
|
||||
|
||||
this.spawnSprites();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
timeouts: {},
|
||||
setTimeout: function(callback, delay) {
|
||||
var timerId = _.uniqueId();
|
||||
this.timeouts[timerId] = {
|
||||
expires: Date.now() + delay,
|
||||
callback: callback
|
||||
};
|
||||
return timerId;
|
||||
},
|
||||
clearTimeout: function(timerId) {
|
||||
if (this.timeouts[timerId] != undefined)
|
||||
delete this.timeouts[timerId];
|
||||
},
|
||||
handleTimeouts: function() {
|
||||
var now = Date.now(),
|
||||
timerIds = _.keys(this.timeouts),
|
||||
timerId, timeout;
|
||||
for (var i=0; i<timerIds.length; i++) {
|
||||
timerId = timerIds[i];
|
||||
timeout = this.timeouts[timerId];
|
||||
if (now > timeout.expires) {
|
||||
timeout.callback();
|
||||
delete this.timeouts[timerId];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
update: function(dt) {
|
||||
if (!this.engine) return false;
|
||||
|
||||
var start =_.now(),
|
||||
sprite, count = 0,
|
||||
minX = -this.attributes.x + this.attributes.viewportLeft - this.attributes.tileWidth*3,
|
||||
maxX = -this.attributes.x + this.engine.canvas.width - this.attributes.viewportRight + this.attributes.tileWidth*3,
|
||||
minY = -this.attributes.y + this.attributes.viewportTop - this.attributes.tileHeight*3,
|
||||
maxY = -this.attributes.y + this.engine.canvas.height - this.attributes.viewportBottom + this.attributes.tileHeight*3;
|
||||
|
||||
// Background
|
||||
if (this.requestBackgroundRedraw) {
|
||||
this.requestBackgroundRedraw = false;
|
||||
this.drawBackground = true;
|
||||
}
|
||||
|
||||
// Foreground
|
||||
for (var i = 0; i < this.dynamicSprites.models.length; i++) {
|
||||
var sprite = this.dynamicSprites.models[i];
|
||||
if (sprite.attributes.x + sprite.attributes.width >= minX && sprite.attributes.x <= maxX &&
|
||||
sprite.attributes.y + sprite.attributes.height >= minY && sprite.attributes.y <= maxY) {
|
||||
sprite._draw = sprite.update(dt);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.debugPanel)
|
||||
this.debugPanel.set({
|
||||
drawBackground: this.drawBackground,
|
||||
updateCount: count,
|
||||
updateTime: _.now()-start
|
||||
});
|
||||
|
||||
this.drawBackground = this.drawBackground || this.lastX != this.attributes.x || this.lastY || this.attributes.y;
|
||||
this.lastX = this.attributes.x; this.lastY = this.attributes.y;
|
||||
|
||||
if (this.attributes.state == "play") {
|
||||
this.handleTimeouts();
|
||||
this.set({time: this.accumTime + (start - this.startTime)}, {silent: true});
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
draw: function(context) {
|
||||
if (this._pan && this._pan.startTime)
|
||||
this._animatePan(context);
|
||||
|
||||
if (this.drawBackground) {
|
||||
this.drawStaticSprites(this.backgroundContext);
|
||||
this.drawBackground = false;
|
||||
}
|
||||
this.drawDynamicSprites(context);
|
||||
return this;
|
||||
},
|
||||
drawStaticSprites: function(context) {
|
||||
var start =_.now(),
|
||||
sprite, index, count = 0,
|
||||
tileX1 = this.getWorldCol(-this.attributes.x + this.attributes.viewportLeft - this.staticSprites.maxSpriteWidth/2),
|
||||
tileX2 = this.getWorldCol(-this.attributes.x + context.canvas.width - this.attributes.viewportRight + this.staticSprites.maxSpriteWidth/2),
|
||||
tileY1 = this.getWorldRow(-this.attributes.y + this.attributes.viewportTop - this.staticSprites.maxSpriteHeight/2),
|
||||
tileY2 = this.getWorldRow(-this.attributes.y + context.canvas.height - this.attributes.viewportBottom + this.staticSprites.maxSpriteHeight/2);
|
||||
this.spriteOptions.offsetX = this.attributes.x;
|
||||
this.spriteOptions.offsetY = this.attributes.y;
|
||||
|
||||
drawRect(
|
||||
context,
|
||||
0, 0, context.canvas.width, context.canvas.height,
|
||||
this.attributes.backgroundColor
|
||||
);
|
||||
|
||||
if (this.backgroundImage) {
|
||||
var img = this.backgroundImage,
|
||||
width = context.canvas.width < img.width ? context.canvas.width : img.width,
|
||||
height = context.canvas.height < img.height ? context.canvas.height : img.height;
|
||||
context.drawImage(
|
||||
img,
|
||||
0, 0, width, height,
|
||||
0, 0, width, height
|
||||
);
|
||||
}
|
||||
|
||||
var secondPass = [];
|
||||
for (var col = tileX1; col <= tileX2; col++)
|
||||
for (var row = tileY1; row <= tileY2; row++) {
|
||||
index = col * this.attributes.height + row;
|
||||
if (this.staticSprites.lookup[index])
|
||||
for (var s = 0; s < this.staticSprites.lookup[index].length; s++) {
|
||||
sprite = this.staticSprites.lookup[index][s];
|
||||
if (!sprite.attributes.zIndex) {
|
||||
sprite.draw.call(sprite, context, this.spriteOptions);
|
||||
count++;
|
||||
} else {
|
||||
secondPass.push(sprite);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var s = 0; s < secondPass.length; s++) {
|
||||
sprite = secondPass[s];
|
||||
sprite.draw.call(sprite, context, this.spriteOptions);
|
||||
}
|
||||
|
||||
if (this.debugPanel) this.debugPanel.set({
|
||||
staticDrwan: count,
|
||||
staticDrawTime: _.now()-start
|
||||
});
|
||||
|
||||
return this;
|
||||
},
|
||||
drawDynamicSprites: function(context) {
|
||||
var start =_.now(),
|
||||
sprite, index, count = 0,
|
||||
clip = this.attributes.viewportLeft || this.attributes.viewportRight || this.attributes.viewportTop || this.attributes.viewportBottom,
|
||||
tileX1 = this.getWorldCol(-this.attributes.x + this.attributes.viewportLeft - this.attributes.tileWidth*3),
|
||||
tileX2 = this.getWorldCol(-this.attributes.x + context.canvas.width - this.attributes.viewportRight + this.attributes.tileWidth*3),
|
||||
tileY1 = this.getWorldRow(-this.attributes.y + this.attributes.viewportTop - this.attributes.tileHeight*3),
|
||||
tileY2 = this.getWorldRow(-this.attributes.y + context.canvas.height - this.attributes.viewportBottom + this.attributes.tileHeight*3);
|
||||
this.spriteOptions.offsetX = this.attributes.x;
|
||||
this.spriteOptions.offsetY = this.attributes.y;
|
||||
|
||||
|
||||
if (clip) {
|
||||
context.save();
|
||||
context.rect(
|
||||
this.attributes.viewportLeft,
|
||||
this.attributes.viewportTop,
|
||||
context.canvas.width - this.attributes.viewportRight,
|
||||
context.canvas.height - this.attributes.viewportBottom);
|
||||
context.clip();
|
||||
}
|
||||
|
||||
context.drawImage(this.backgroundCanvas,
|
||||
this.attributes.viewportLeft, this.attributes.viewportTop, this.viewport.width, this.viewport.height,
|
||||
this.attributes.viewportLeft, this.attributes.viewportTop, this.viewport.width, this.viewport.height);
|
||||
|
||||
var secondPass = [];
|
||||
for (var col = tileX1; col <= tileX2; col++)
|
||||
for (var row = tileY1; row <= tileY2; row++) {
|
||||
index = col * this.attributes.height + row;
|
||||
if (this.dynamicSprites.lookup[index])
|
||||
for (var s = 0; s < this.dynamicSprites.lookup[index].length; s++) {
|
||||
sprite = this.dynamicSprites.lookup[index][s];
|
||||
if (sprite._draw) {
|
||||
if (!sprite.attributes.zIndex) {
|
||||
sprite.draw.call(sprite, context, this.spriteOptions);
|
||||
count++;
|
||||
} else {
|
||||
secondPass.push(sprite);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var s = 0; s < secondPass.length; s++) {
|
||||
sprite = secondPass[s];
|
||||
if (sprite._draw)
|
||||
sprite.draw.call(sprite, context, this.spriteOptions);
|
||||
}
|
||||
|
||||
if (clip) context.restore();
|
||||
|
||||
if (this.debugPanel) this.debugPanel.set({
|
||||
dynamicDrawn: count,
|
||||
dynamicDrawTime: _.now()-start
|
||||
});
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
// Sprites are ided (and ordered) by columns. This allows for
|
||||
// fast column drawing without lookup.
|
||||
getWorldIndex: function(object) {
|
||||
if (!_.isObject(object)) return null;
|
||||
var x = object.attributes ? (object.get("x") + object.get("width")/2) : (object.x || 0),
|
||||
y = object.attributes ? (object.get("y") + object.get("height")/2) : (object.y || 0),
|
||||
col = Math.floor(x / this.get("tileWidth")),
|
||||
row = Math.floor(y / this.get("tileHeight"));
|
||||
return col * this.get("height") + row;
|
||||
},
|
||||
isWorldIndexInBoundingBox: function(index, topLeft, bottomRight) {
|
||||
if (index < topLeft || index > bottomRight) return false;
|
||||
if (index % this.attributes.height < topLeft % this.attributes.height) return false;
|
||||
if (index % this.attributes.height > bottomRight % this.attributes.height) return false;
|
||||
return true;
|
||||
},
|
||||
getWorldCol: function(x) {
|
||||
return Math.floor(x / this.get("tileWidth"));
|
||||
},
|
||||
getWorldRow: function(y) {
|
||||
return Math.floor(y / this.get("tileHeight"));
|
||||
},
|
||||
findAt: function(x, y, type, exclude, collision) {
|
||||
return this._findOrFilter("find", x, y, type, exclude, collision);
|
||||
},
|
||||
filterAt: function(x, y, type, exclude, collision) {
|
||||
return this._findOrFilter("filter", x, y, type, exclude, collision);
|
||||
},
|
||||
_findOrFilter: function(fn, x, y, type, exclude, collision) {
|
||||
var id = exclude && exclude.id ? exclude.id : null,
|
||||
col = this.getWorldCol(x),
|
||||
row = this.getWorldRow(y),
|
||||
index, c, r, s,
|
||||
result = [];
|
||||
|
||||
function test(sprite) {
|
||||
return (sprite && sprite.id && sprite.id != id) &&
|
||||
(!type || sprite.get("type") == type) &&
|
||||
(collision === undefined || sprite.attributes.collision === collision) &&
|
||||
sprite.overlaps.call(sprite, x, y);
|
||||
}
|
||||
|
||||
if (type == "tile") {
|
||||
index = this.getWorldIndex({x: x, y: y});
|
||||
var sprite = index ? this.sprites.get(index) : null;
|
||||
if (sprite && test(sprite))
|
||||
return fn == "find" ? sprite : [sprite];
|
||||
return fn == "find" ? null : result;
|
||||
}
|
||||
|
||||
var matches = this.quadTree.get({x: x, y: y, w: 0, h: 0}),
|
||||
sprite, i;
|
||||
for (i = 0; i < matches.length; i++) {
|
||||
sprite = this.sprites.get(matches[i].id);
|
||||
if (test(sprite)) result.push(sprite);
|
||||
}
|
||||
|
||||
return fn == "find" ? result[0] || null : result;
|
||||
},
|
||||
// Detects collisions on sprites for a set of named coordinates. Works on moving
|
||||
// and static sprites.
|
||||
// Map is a map of objects describing the locations to look at, and the result.
|
||||
// Each map item is an object with:
|
||||
// - x, y: The lookup coordinate.
|
||||
// - width, height: Optional. If specified and non-zero, defines an area to lookup (not just a point).
|
||||
// - dir: The lookout direction; top, right, bottom or left.
|
||||
// - sprites: array of detected colliding sprites. Reset/initialized to [] every call.
|
||||
// - sprite: The closest sprite based on the lookout direction.
|
||||
// Returns the number of found collisions.
|
||||
findCollisions: function(map, type, exclude, collision) {
|
||||
if (_.size(map) == 0) return 0;
|
||||
|
||||
var id = exclude && exclude.id ? exclude.id : null,
|
||||
minX, minY, maxX, maxY,
|
||||
m, c, r, index, s,
|
||||
count = 0;
|
||||
|
||||
for (m in map)
|
||||
if (map.hasOwnProperty(m)) {
|
||||
map[m].width || (map[m].width = 0);
|
||||
map[m].height || (map[m].height = 0);
|
||||
if (minX == undefined || map[m].x < minX) minX = map[m].x;
|
||||
if (maxX == undefined || map[m].x + map[m].width > maxX) maxX = map[m].x + map[m].width;
|
||||
if (minY == undefined || map[m].y < minY) minY = map[m].y;
|
||||
if (maxY == undefined || map[m].y + map[m].height > maxY) maxY = map[m].y + map[m].height;
|
||||
map[m].sprites = [];
|
||||
map[m].sprite = null;
|
||||
}
|
||||
|
||||
function doIt(sprite) {
|
||||
if (sprite && sprite.id && sprite.id != id &&
|
||||
(!type || sprite.attributes.type == type) &&
|
||||
(collision === undefined || sprite.attributes.collision === collision))
|
||||
for (m in map)
|
||||
if (map.hasOwnProperty(m) &&
|
||||
sprite.overlaps.call(sprite, map[m])) {
|
||||
map[m].sprites.push(sprite);
|
||||
if (!map[m].sprite) map[m].sprite = sprite;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
function findClosestSprites() {
|
||||
for (m in map)
|
||||
if (map.hasOwnProperty(m) && map[m].sprites.length > 0)
|
||||
if (map[m].sprites.length == 1)
|
||||
map[m].sprite = map[m].sprites[0];
|
||||
else
|
||||
for (s = 0; s < map[m].sprites.length; s++)
|
||||
switch (map[m].dir) {
|
||||
case "left":
|
||||
c = map[m].sprites[s].getRight(true);
|
||||
if (c > map[m].x) map[m].sprite = map[m].sprites[s];
|
||||
break;
|
||||
case "right":
|
||||
c = map[m].sprites[s].getLeft(true);
|
||||
if (c < map[m].x) map[m].sprite = map[m].sprites[s];
|
||||
break;
|
||||
case "top":
|
||||
c = map[m].sprites[s].getBottom(true);
|
||||
if (c > map[m].y) map[m].sprite = map[m].sprites[s];
|
||||
break;
|
||||
case "bottom":
|
||||
c = map[m].sprites[s].getTop(true);
|
||||
if (c < map[m].y) map[m].sprite = map[m].sprites[s];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var matches = this.quadTree.get({x: minX, y: minY, w: maxX-minX, h: maxY-minY}),
|
||||
sprite, i;
|
||||
for (i = 0; i < matches.length; i++)
|
||||
doIt(this.sprites.get(matches[i].id));
|
||||
|
||||
findClosestSprites();
|
||||
return count;
|
||||
},
|
||||
// Static tiles lookup.
|
||||
// Note: Will not detect moving sprites!
|
||||
// DEPRECATED: Use findAt instead
|
||||
findCollidingAt:function(x, y) {
|
||||
return this.findAt(x, y, "tile", null, true);
|
||||
},
|
||||
add: function(models, options) {
|
||||
if (_.isArray(models))
|
||||
for (var i = 0; i < models.length; i++) {
|
||||
if (models[i].attributes)
|
||||
models[i].set("id", this.buildId(models[i]));
|
||||
else
|
||||
models[i].id = this.buildId(models[i]);
|
||||
}
|
||||
else {
|
||||
if (models.attributes)
|
||||
models.set("id", this.buildId(models));
|
||||
else
|
||||
models.id = this.buildId(models);
|
||||
}
|
||||
|
||||
models = this.sprites.add.call(this.sprites, models, options);
|
||||
|
||||
if (_.isArray(models)) {
|
||||
for (var i = 0; i < models.length; i++) {
|
||||
models[i].world = this;
|
||||
if (!options || !options.silent)
|
||||
models[i].trigger("addWorld", models[i], this, options);
|
||||
}
|
||||
} else {
|
||||
models.world = this;
|
||||
if (!options || !options.silent)
|
||||
models.trigger("addWorld", models, this, options);
|
||||
}
|
||||
|
||||
return models;
|
||||
},
|
||||
remove: function(models, options) {
|
||||
models = this.sprites.remove.apply(this.sprites, arguments);
|
||||
if (_.isArray(models)) {
|
||||
for (var i = 0; i < models.length; i++)
|
||||
if (models[i].world === this) {
|
||||
if (!options || !options.silent)
|
||||
models[i].trigger("removeWorld", models[i], this, options);
|
||||
models[i].world = undefined;
|
||||
}
|
||||
} else if (models && models.world === this) {
|
||||
if (!options || !options.silent)
|
||||
models.trigger("removeWorld", models, this, options);
|
||||
models.world = undefined;
|
||||
}
|
||||
return models;
|
||||
},
|
||||
cloneAtPosition: function(sprite, x, y, options) {
|
||||
options || (options = {});
|
||||
options.world = this;
|
||||
options.input = this.input;
|
||||
|
||||
var w = this.toShallowJSON(),
|
||||
existing = this.findAt(x, y),
|
||||
existingName = existing ? existing.get("name") : null,
|
||||
spriteName = sprite ? sprite.get("name") : "";
|
||||
if (!sprite && !existing) return null;
|
||||
|
||||
if (!sprite && existing) {
|
||||
this.sprites.remove(existing);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (existing) {
|
||||
if (spriteName == existingName) {
|
||||
if (!existing.getStateInfo) {
|
||||
this.sprites.remove(existing);
|
||||
return null;
|
||||
}
|
||||
// Toggle if same sprite - either turn around or remove
|
||||
var cur = existing.getStateInfo(),
|
||||
removeOnDir = sprite.get("hero") ? "left" : "right";
|
||||
if (!cur.dir || cur.dir == removeOnDir) {
|
||||
this.remove(existing);
|
||||
return null;
|
||||
}
|
||||
if (cur.opo && _.isFunction(existing.toggleDirection))
|
||||
existing.toggleDirection(cur.opo);
|
||||
return existing;
|
||||
} else {
|
||||
// Replace existing
|
||||
this.remove(existing);
|
||||
}
|
||||
}
|
||||
|
||||
var col = this.getWorldCol(x),
|
||||
row = this.getWorldRow(y - sprite.get("height") + w.tileHeight),
|
||||
cls = _.classify(spriteName);
|
||||
|
||||
var newSprite = new Backbone[cls](_.extend({}, sprite.toJSON(), {
|
||||
x: col * w.tileWidth,
|
||||
y: row * w.tileHeight
|
||||
}), options);
|
||||
|
||||
// A hero is a singleton
|
||||
var isHero = newSprite.get("hero");
|
||||
if (isHero) {
|
||||
var oldHeros = this.sprites.where({hero: true});
|
||||
if (oldHeros.length) this.sprites.remove(oldHeros);
|
||||
}
|
||||
|
||||
this.add(newSprite, options);
|
||||
|
||||
if (isHero && this.camera)
|
||||
this.camera.setOptions({world: this, subject: newSprite});
|
||||
|
||||
newSprite.engine = this.engine;
|
||||
newSprite.trigger("attach", this.engine);
|
||||
|
||||
return newSprite;
|
||||
},
|
||||
buildIdFromName: function(name) {
|
||||
var re = new RegExp("^" + name + "\\." + "\\d+$"),
|
||||
numbers = this.dynamicSprites.reduce(function(numbers, sprite) {
|
||||
if (sprite.id && sprite.id.length && sprite.id.match(re))
|
||||
numbers.push(parseInt(sprite.id.replace(name + ".", "")));
|
||||
return numbers;
|
||||
}, [0]);
|
||||
return name + "." + (_.max(numbers) + 1);
|
||||
},
|
||||
buildId: function(sprite) {
|
||||
var attributes = sprite.attributes || sprite;
|
||||
if (attributes.type != "tile")
|
||||
return this.buildIdFromName(attributes.name);
|
||||
|
||||
return this.getWorldCol(attributes.x) * this.attributes.height +
|
||||
this.getWorldRow(attributes.y);
|
||||
},
|
||||
clearBeyondWorldBoundaries: function() {
|
||||
var minX = 0,
|
||||
minY = 0,
|
||||
maxX = this.attributes.width * this.attributes.tileWidth,
|
||||
maxY = this.attributes.height * this.attributes.tileHeight,
|
||||
toRemove = this.sprites.reduce(function(result, sprite) {
|
||||
if (sprite.attributes.x + sprite.attributes.width < minX ||
|
||||
sprite.attributes.y + sprite.attributes.height < minY ||
|
||||
sprite.attributes.x > maxX ||
|
||||
sprite.attributes.y > maxY)
|
||||
result.push(sprite);
|
||||
return result;
|
||||
});
|
||||
|
||||
this.sprites.remove(toRemove);
|
||||
|
||||
return this;
|
||||
},
|
||||
pan: function(targetX, targetY, callback, easing, easingTime) {
|
||||
if (this.get("state") == "play") throw "Cannot pan world in play.";
|
||||
this._pan || (this._pan = {});
|
||||
this._pan.startTime = _.now();
|
||||
this._pan.startX = this.get("x");
|
||||
this._pan.startY = this.get("y");
|
||||
this._pan.targetX = targetX;
|
||||
this._pan.targetY = targetY;
|
||||
this._pan.easing = easing || "easeOutQuint";
|
||||
this._pan.easingTime = easingTime || 1000;
|
||||
this._pan.callback = callback;
|
||||
return this;
|
||||
},
|
||||
_animatePan: function() {
|
||||
var now = _.now();
|
||||
|
||||
if (now < this._pan.startTime + this._pan.easingTime) {
|
||||
var factor = Backbone.EasingFunctions[this._pan.easing]((now - this._pan.startTime) / this._pan.easingTime);
|
||||
this.set({
|
||||
x: this._pan.startX + factor * (this._pan.targetX - this._pan.startX),
|
||||
y: this._pan.startY + factor * (this._pan.targetY - this._pan.startY)
|
||||
});
|
||||
} else {
|
||||
this._endPan(true);
|
||||
}
|
||||
},
|
||||
_endPan: function(trigger) {
|
||||
if (trigger && typeof this._pan.callback == "function")
|
||||
_.defer(this._pan.callback.bind(this));
|
||||
if (this._pan.targetX || this._pan.targetY)
|
||||
this.set({x: this._pan.targetX, y: this._pan.targetY});
|
||||
this._pan.startTime = undefined;
|
||||
this._pan.startX = undefined;
|
||||
this._pan.startY = undefined;
|
||||
this._pan.targetX = undefined;
|
||||
this._pan.targetY = undefined;
|
||||
this._pan.callback = undefined;
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
}).call(this);
|
||||
103
games/mario/super-mario-bros/artifacts.js
Normal file
@@ -0,0 +1,103 @@
|
||||
(function() {
|
||||
|
||||
/**
|
||||
*
|
||||
* Backbone Game Engine - An elementary HTML5 canvas game engine using Backbone.
|
||||
*
|
||||
* Copyright (c) 2014 Martin Drapeau
|
||||
* https://github.com/martindrapeau/backbone-game-engine
|
||||
*
|
||||
*/
|
||||
|
||||
Backbone.Pennie = Backbone.AnimatedTile.extend({
|
||||
defaults: {
|
||||
name: "pennie",
|
||||
type: "artifact",
|
||||
width: 32,
|
||||
height: 32,
|
||||
spriteSheet: "tiles",
|
||||
state: "idle",
|
||||
collision: true
|
||||
},
|
||||
animations: {
|
||||
idle: {
|
||||
sequences: [52, 52, 53, 54, 53, 52],
|
||||
delay: 50
|
||||
}
|
||||
},
|
||||
initialize: function(attributes, options) {
|
||||
Backbone.AnimatedTile.prototype.initialize.apply(this, arguments);
|
||||
this.on("hit", this.hit, this);
|
||||
this.on("squish", this.hit, this);
|
||||
},
|
||||
hit: function(sprite, dir, dir2) {
|
||||
if (sprite.get("hero")) {
|
||||
sprite.trigger("hit", this);
|
||||
_.defer(this.world.remove, this);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Backbone.PennieUg = Backbone.Pennie.extend({
|
||||
defaults: _.extend(_.deepClone(Backbone.Pennie.prototype.defaults), {
|
||||
name: "pennie-ug"
|
||||
}),
|
||||
animations: _.deepClone(Backbone.Pennie.prototype.animations)
|
||||
});
|
||||
Backbone.PennieUg.prototype.animations.idle.sequences = [168, 168, 169, 170, 169, 168];
|
||||
|
||||
Backbone.Lever = Backbone.Pennie.extend({
|
||||
defaults: _.extend(_.deepClone(Backbone.Pennie.prototype.defaults), {
|
||||
name: "lever"
|
||||
}),
|
||||
animations: _.deepClone(Backbone.Pennie.prototype.animations),
|
||||
hit: function(sprite, dir, dir2) {}
|
||||
});
|
||||
Backbone.Lever.prototype.animations.idle.sequences = [55, 55, 56, 57, 56, 55];
|
||||
|
||||
Backbone.FlyingPennie = Backbone.Sprite.extend({
|
||||
defaults: {
|
||||
name: "flying-pennie",
|
||||
type: "decoration",
|
||||
width: 32,
|
||||
height: 32,
|
||||
spriteSheet: "tiles",
|
||||
state: "anim",
|
||||
collision: false
|
||||
},
|
||||
animations: {
|
||||
anim: {
|
||||
sequences: [
|
||||
{frame: 52, x: 0, y: -32, scaleX: 1.00, scaleY: 1},
|
||||
{frame: 52, x: 0, y: -64, scaleX: 0.50, scaleY: 1},
|
||||
{frame: 53, x: 0, y: -90, scaleX: 0.50, scaleY: 1},
|
||||
{frame: 53, x: 0, y: -128, scaleX: 1.00, scaleY: 1},
|
||||
{frame: 53, x: 0, y: -128, scaleX: 0.50, scaleY: 1},
|
||||
{frame: 52, x: 0, y: -112, scaleX: 0.50, scaleY: 1},
|
||||
{frame: 52, x: 0, y: -90, scaleX: 1.00, scaleY: 1},
|
||||
{frame: 52, x: 0, y: -80, scaleX: 0.50, scaleY: 1},
|
||||
{frame: 53, x: 0, y: -80, scaleX: 0.50, scaleY: 1}
|
||||
],
|
||||
delay: 50
|
||||
}
|
||||
},
|
||||
initialize: function(attributes, options) {
|
||||
options || (options = {});
|
||||
this.world = options.world;
|
||||
this.lastSequenceChangeTime = 0;
|
||||
},
|
||||
update: function(dt) {
|
||||
Backbone.Sprite.prototype.update.call(this, arguments);
|
||||
var animation = this.getAnimation(),
|
||||
sequenceIndex = this.get("sequenceIndex");
|
||||
|
||||
if (sequenceIndex >= animation.sequences.length-1) {
|
||||
_.defer(this.world.remove, this);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
}).call(this);
|
||||
88
games/mario/super-mario-bros/display.js
Normal file
@@ -0,0 +1,88 @@
|
||||
(function() {
|
||||
|
||||
/**
|
||||
*
|
||||
* Backbone Game Engine - An elementary HTML5 canvas game engine using Backbone.
|
||||
*
|
||||
* Copyright (c) 2014 Martin Drapeau
|
||||
* https://github.com/martindrapeau/backbone-game-engine
|
||||
*
|
||||
*/
|
||||
|
||||
Backbone.Display = Backbone.Model.extend({
|
||||
defaults: {
|
||||
pennies: 0
|
||||
},
|
||||
initialize: function(attributes, options) {
|
||||
options || (options = {});
|
||||
this.world = options.world;
|
||||
|
||||
this.pennieSprite = new Backbone.AnimatedTile({
|
||||
name: "pennie",
|
||||
type: "character",
|
||||
x: 150,
|
||||
y: 4,
|
||||
width: 32,
|
||||
height: 32,
|
||||
spriteSheet: "tiles",
|
||||
state: "idle"
|
||||
});
|
||||
this.pennieSprite.animations = {
|
||||
idle: {
|
||||
sequences: [52, 52, 53, 54, 53, 52],
|
||||
delay: 50,
|
||||
scaleX: 0.75,
|
||||
scaleY: 0.75
|
||||
}
|
||||
};
|
||||
|
||||
this.on("attach", this.onAttach, this);
|
||||
this.on("detach", this.onDetach, this);
|
||||
},
|
||||
onAttach: function() {
|
||||
this.pennieSprite.engine = this.engine;
|
||||
this.pennieSprite.trigger("attach");
|
||||
this.listenTo(this.world.dynamicSprites, "remove", this.onPennieRemoved);
|
||||
this.pennieSprite.set({x: this.engine.canvas.width/2 - 30});
|
||||
},
|
||||
onDetach: function() {
|
||||
this.pennieSprite.trigger("detach");
|
||||
this.pennieSprite.engine = undefined;
|
||||
this.stopListening();
|
||||
},
|
||||
update: function(dt) {
|
||||
return true;
|
||||
},
|
||||
draw: function(context) {
|
||||
|
||||
var text = "×" + (this.attributes.pennies < 10 ? "0" : "") + this.attributes.pennies;
|
||||
context.fillStyle = "#fff";
|
||||
context.font = "20px arcade, Verdana, Arial, Sans-Serif";
|
||||
context.textBaseline = "top";
|
||||
context.fontWeight = "normal";
|
||||
|
||||
context.textAlign = "left";
|
||||
context.fillText(text, context.canvas.width/2 - 100, 12);
|
||||
|
||||
this.pennieSprite.draw.call(this.pennieSprite, context);
|
||||
|
||||
context.textAlign = "right";
|
||||
context.fillText(this.world.attributes.name.replace(/_/g, " "), context.canvas.width - 100, 12);
|
||||
|
||||
return this;
|
||||
},
|
||||
onPennieRemoved: function(sprite) {
|
||||
if (this.world.get("state") != "play") return;
|
||||
|
||||
var name = sprite.get("name"),
|
||||
pennies = this.get("pennies");
|
||||
|
||||
if (name.indexOf("pennie") != -1) {
|
||||
pennies += 1;
|
||||
if (pennies > 99) pennies = 0;
|
||||
this.set("pennies", pennies);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}).call(this);
|
||||
313
games/mario/super-mario-bros/enemies.js
Normal file
@@ -0,0 +1,313 @@
|
||||
(function() {
|
||||
|
||||
/**
|
||||
*
|
||||
* Backbone Game Engine - An elementary HTML5 canvas game engine using Backbone.
|
||||
*
|
||||
* Copyright (c) 2014 Martin Drapeau
|
||||
* https://github.com/martindrapeau/backbone-game-engine
|
||||
*
|
||||
*/
|
||||
|
||||
var sequenceDelay = 300,
|
||||
animations;
|
||||
|
||||
// Mushroom is the base enemie class.
|
||||
Backbone.Mushroom = Backbone.Character.extend({
|
||||
defaults: _.extend(_.deepClone(Backbone.Character.prototype.defaults), {
|
||||
name: "mushroom",
|
||||
type: "character",
|
||||
width: 32,
|
||||
height: 64,
|
||||
paddingTop: 32,
|
||||
spriteSheet: "enemies",
|
||||
state: "idle-left",
|
||||
velocity: 0,
|
||||
yVelocity: 0,
|
||||
collision: true,
|
||||
aiDelay: 0
|
||||
}),
|
||||
animations: _.extend(_.deepClone(Backbone.Character.prototype.animations), {
|
||||
"squished-left": {
|
||||
sequences: [2],
|
||||
velocity: 0,
|
||||
scaleX: 1,
|
||||
scaleY: 1
|
||||
},
|
||||
"squished-right": {
|
||||
sequences: [2],
|
||||
velocity: 0,
|
||||
scaleX: -1,
|
||||
scaleY: 1
|
||||
}
|
||||
}),
|
||||
ai: function(dt) {
|
||||
var cur = this.getStateInfo();
|
||||
if (cur.mov == "squished" && !this.get("collision")) this.cancelUpdate = true;
|
||||
return this;
|
||||
},
|
||||
isAttacking: function(sprite, dir, dir2) {
|
||||
if (this.cancelUpdate) return false;
|
||||
var cur = this.getStateInfo();
|
||||
return (cur.mov == "walk" || cur.mov == "idle");
|
||||
},
|
||||
squish: function(sprite) {
|
||||
var self = this,
|
||||
cur = this.getStateInfo();
|
||||
this.set({
|
||||
state: this.buildState("squished", cur.dir),
|
||||
collision: false
|
||||
});
|
||||
this.world.setTimeout(function() {
|
||||
if (self && self.world) self.world.remove(self);
|
||||
}, 2000);
|
||||
this.cancelUpdate = true;
|
||||
return this;
|
||||
},
|
||||
hit: function(sprite, dir, dir2) {
|
||||
if (this._handlingSpriteHit) return this;
|
||||
this._handlingSpriteHit = sprite;
|
||||
|
||||
var cur = this.getStateInfo(),
|
||||
opo = dir == "left" ? "right" : (dir == "right" ? "left" : (dir == "top" ? "bottom" : "top"));
|
||||
|
||||
if (sprite.get("hero")) {
|
||||
if (dir == "top")
|
||||
this.squish.apply(this, arguments);
|
||||
} else if (sprite.get("state").indexOf("slide") != -1 ||
|
||||
sprite.get("type") == "tile" && dir == "bottom" && sprite.get("state") == "bounce") {
|
||||
this.knockout.apply(this, arguments);
|
||||
}
|
||||
sprite.trigger("hit", this, opo);
|
||||
|
||||
this._handlingSpriteHit = undefined;
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
Backbone.Turtle = Backbone.Mushroom.extend({
|
||||
defaults: _.extend(_.deepClone(Backbone.Mushroom.prototype.defaults), {
|
||||
name: "turtle"
|
||||
}),
|
||||
animations: _.deepClone(Backbone.Mushroom.prototype.animations),
|
||||
isAttacking: function() {
|
||||
var cur = this.getStateInfo();
|
||||
return (cur.mov == "walk" || cur.mov == "idle");
|
||||
},
|
||||
slide: function(sprite, dir, dir2) {
|
||||
if (this.wakeTimerId) {
|
||||
this.world.clearTimeout(this.wakeTimerId);
|
||||
this.wakeTimerId = null;
|
||||
}
|
||||
|
||||
var dir = sprite.getCenterX(true) > this.getCenterX(true) ? "left" : "right";
|
||||
this.set("state", this.buildState("walk", "slide", dir));
|
||||
this.cancelUpdate = true;
|
||||
return this;
|
||||
},
|
||||
squish: function(sprite, dir, dir2) {
|
||||
var cur = this.getStateInfo();
|
||||
|
||||
if (cur.mov == "squished" || cur.mov == "wake")
|
||||
return this.slide.apply(this, arguments);
|
||||
|
||||
if (this.wakeTimerId) {
|
||||
this.world.clearTimeout(this.wakeTimerId);
|
||||
this.wakeTimerId = null;
|
||||
}
|
||||
|
||||
this.set("state", this.buildState("squished", cur.dir));
|
||||
this.wakeTimerId = this.world.setTimeout(this.wake.bind(this), 5000);
|
||||
|
||||
this.cancelUpdate = true;
|
||||
return this;
|
||||
},
|
||||
hit: function(sprite, dir, dir2) {
|
||||
if (this._handlingSpriteHit) return this;
|
||||
this._handlingSpriteHit = sprite;
|
||||
|
||||
var cur = this.getStateInfo(),
|
||||
opo = dir == "left" ? "right" : (dir == "right" ? "left" : (dir == "top" ? "bottom" : "top"));
|
||||
if (cur.mov2 == "slide") this.cancelUpdate = true;
|
||||
|
||||
if (dir == "top") {
|
||||
this.squish.apply(this, arguments);
|
||||
} else if (sprite.get("hero") && (cur.mov == "squished" || cur.mov == "wake")) {
|
||||
this.slide.apply(this, arguments);
|
||||
opo = "bottom";
|
||||
} else if (sprite.get("state").indexOf("slide") != -1 ||
|
||||
sprite.get("type") == "tile" && dir == "bottom" && sprite.get("state") == "bounce") {
|
||||
this.knockout.apply(this, arguments);
|
||||
}
|
||||
|
||||
sprite.trigger("hit", this, opo);
|
||||
|
||||
this._handlingSpriteHit = undefined;
|
||||
return this;
|
||||
},
|
||||
wake: function() {
|
||||
var cur = this.getStateInfo();
|
||||
if (this.wakeTimerId) {
|
||||
this.world.clearTimeout(this.wakeTimerId);
|
||||
this.wakeTimerId = null;
|
||||
}
|
||||
|
||||
if (cur.mov == "squished") {
|
||||
this.set("state", this.buildState("wake", cur.dir));
|
||||
this.wakeTimerId = this.world.setTimeout(this.wake.bind(this), 5000);
|
||||
} else if (cur.mov == "wake") {
|
||||
this.set("state", this.buildState("walk", cur.dir));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
});
|
||||
animations = Backbone.Turtle.prototype.animations;
|
||||
animations["idle-left"].sequences = animations["idle-right"].sequences =
|
||||
animations["fall-left"].sequences = animations["fall-right"].sequences =
|
||||
animations["ko-left"].sequences = animations["ko-right"].sequences = [6];
|
||||
animations["walk-left"].sequences = animations["walk-right"].sequences = [6, 7];
|
||||
animations["squished-left"].sequences = animations["squished-right"].sequences = [10];
|
||||
_.extend(animations, {
|
||||
"wake-left": {
|
||||
sequences: [10, 11],
|
||||
velocity: 0,
|
||||
scaleX: 1,
|
||||
scaleY: 1,
|
||||
delay: sequenceDelay
|
||||
},
|
||||
"wake-right": {
|
||||
sequences: [10, 11],
|
||||
velocity: 0,
|
||||
scaleX: -1,
|
||||
scaleY: 1,
|
||||
delay: sequenceDelay
|
||||
},
|
||||
"walk-slide-left": {
|
||||
sequences: [10],
|
||||
velocity: -300,
|
||||
scaleX: 1,
|
||||
scaleY: 1
|
||||
},
|
||||
"walk-slide-right": {
|
||||
sequences: [10],
|
||||
velocity: 300,
|
||||
scaleX: -1,
|
||||
scaleY: 1
|
||||
},
|
||||
"fall-slide-left": {
|
||||
sequences: [10],
|
||||
velocity: -300,
|
||||
yVelocity: animations["fall-left"].yVelocity,
|
||||
yAcceleration: animations["fall-left"].yAcceleration,
|
||||
scaleX: 1,
|
||||
scaleY: 1
|
||||
},
|
||||
"fall-slide-right": {
|
||||
sequences: [10],
|
||||
velocity: 300,
|
||||
yVelocity: animations["fall-right"].yVelocity,
|
||||
yAcceleration: animations["fall-right"].yAcceleration,
|
||||
scaleX: -1,
|
||||
scaleY: 1
|
||||
}
|
||||
});
|
||||
|
||||
Backbone.FlyingTurtle = Backbone.Turtle.extend({
|
||||
defaults: _.extend(_.deepClone(Backbone.Turtle.prototype.defaults), {
|
||||
name: "flying-turtle"
|
||||
}),
|
||||
animations: _.deepClone(Backbone.Turtle.prototype.animations),
|
||||
fallbackSprite: Backbone.Turtle,
|
||||
onUpdate: function(dt) {
|
||||
var cur = this.getStateInfo(),
|
||||
animation = this.getAnimation(),
|
||||
attrs = {};
|
||||
if (cur.mov2 == null && cur.mov == "walk" && this.world.get("state") == "play") {
|
||||
attrs.state = this.buildState("fall", cur.dir);
|
||||
attrs.yVelocity = -this.animations["fall-right"].yVelocity;
|
||||
}
|
||||
if (!_.isEmpty(attrs)) this.set(attrs);
|
||||
return true;
|
||||
},
|
||||
squish: function(sprite) {
|
||||
var cur = this.getStateInfo();
|
||||
var newSprite = new this.fallbackSprite({
|
||||
x: this.get("x"),
|
||||
y: this.get("y"),
|
||||
state: "walk-" + cur.dir
|
||||
});
|
||||
newSprite.set("id", this.world.buildIdFromName(newSprite.get("name")));
|
||||
this.world.add(newSprite);
|
||||
this.world.remove(this);
|
||||
this.cancelUpdate = true;
|
||||
}
|
||||
});
|
||||
animations = Backbone.FlyingTurtle.prototype.animations;
|
||||
animations["idle-left"].sequences = animations["idle-right"].sequences =
|
||||
animations["fall-left"].sequences = animations["fall-right"].sequences =
|
||||
animations["ko-left"].sequences = animations["ko-right"].sequences = [8];
|
||||
animations["walk-left"].sequences = animations["walk-right"].sequences = [8, 9];
|
||||
|
||||
Backbone.RedTurtle = Backbone.Turtle.extend({
|
||||
defaults: _.extend(_.deepClone(Backbone.Turtle.prototype.defaults), {
|
||||
name: "red-turtle"
|
||||
}),
|
||||
animations: _.deepClone(Backbone.Turtle.prototype.animations)
|
||||
});
|
||||
animations = Backbone.RedTurtle.prototype.animations;
|
||||
animations["idle-left"].sequences = animations["idle-right"].sequences =
|
||||
animations["fall-left"].sequences = animations["fall-right"].sequences =
|
||||
animations["ko-left"].sequences = animations["ko-right"].sequences = [108];
|
||||
animations["walk-left"].sequences = animations["walk-right"].sequences = [108, 109];
|
||||
animations["squished-left"].sequences = animations["squished-right"].sequences =
|
||||
animations["walk-slide-left"].sequences = animations["walk-slide-right"].sequences =
|
||||
animations["fall-slide-left"].sequences = animations["fall-slide-right"].sequences = [112];
|
||||
animations["wake-left"].sequences = animations["wake-right"].sequences = [112, 113];
|
||||
|
||||
Backbone.RedFlyingTurtle = Backbone.FlyingTurtle.extend({
|
||||
defaults: _.extend(_.deepClone(Backbone.FlyingTurtle.prototype.defaults), {
|
||||
name: "red-flying-turtle"
|
||||
}),
|
||||
animations: _.deepClone(Backbone.FlyingTurtle.prototype.animations),
|
||||
fallbackSprite: Backbone.RedTurtle
|
||||
});
|
||||
animations = Backbone.RedFlyingTurtle.prototype.animations;
|
||||
animations["idle-left"].sequences = animations["idle-right"].sequences =
|
||||
animations["fall-left"].sequences = animations["fall-right"].sequences =
|
||||
animations["ko-left"].sequences = animations["ko-right"].sequences = [110];
|
||||
animations["walk-left"].sequences = animations["walk-right"].sequences = [110, 111];
|
||||
animations["squished-left"].sequences = animations["squished-right"].sequences =
|
||||
animations["walk-slide-left"].sequences = animations["walk-slide-right"].sequences =
|
||||
animations["fall-slide-left"].sequences = animations["fall-slide-right"].sequences = [112];
|
||||
animations["wake-left"].sequences = animations["wake-right"].sequences = [112, 113];
|
||||
|
||||
Backbone.Beetle = Backbone.Turtle.extend({
|
||||
defaults: _.extend(_.deepClone(Backbone.Turtle.prototype.defaults), {
|
||||
name: "beetle"
|
||||
}),
|
||||
animations: _.deepClone(Backbone.Turtle.prototype.animations)
|
||||
});
|
||||
animations = Backbone.Beetle.prototype.animations;
|
||||
animations["idle-left"].sequences = animations["idle-right"].sequences =
|
||||
animations["fall-left"].sequences = animations["fall-right"].sequences =
|
||||
animations["ko-left"].sequences = animations["ko-right"].sequences = [33];
|
||||
animations["walk-left"].sequences = animations["walk-right"].sequences = [33, 32];
|
||||
animations["squished-left"].sequences = animations["squished-right"].sequences =
|
||||
animations["walk-slide-left"].sequences = animations["walk-slide-right"].sequences =
|
||||
animations["fall-slide-left"].sequences = animations["fall-slide-right"].sequences =
|
||||
animations["wake-left"].sequences = animations["wake-right"].sequences = [34];
|
||||
|
||||
Backbone.Spike = Backbone.Mushroom.extend({
|
||||
defaults: _.extend(_.deepClone(Backbone.Mushroom.prototype.defaults), {
|
||||
name: "spike"
|
||||
}),
|
||||
animations: _.deepClone(Backbone.Mushroom.prototype.animations),
|
||||
squish: function() {}
|
||||
});
|
||||
animations = Backbone.Spike.prototype.animations;
|
||||
animations["idle-left"].sequences = animations["idle-right"].sequences =
|
||||
animations["fall-left"].sequences = animations["fall-right"].sequences =
|
||||
animations["ko-left"].sequences = animations["ko-right"].sequences = [133];
|
||||
animations["walk-left"].sequences = animations["walk-right"].sequences = [133, 132];
|
||||
|
||||
}).call(this);
|
||||
BIN
games/mario/super-mario-bros/icons.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
113
games/mario/super-mario-bros/index.html
Normal file
@@ -0,0 +1,113 @@
|
||||
<!doctype html>
|
||||
<html style="touch-action: none;" manifest="offline.appcache">
|
||||
<head>
|
||||
<title>Super Mario Bros Level 1-1 - Backbone Game Engine</title>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<link href="../favicon.ico" rel="shortcut icon" type="image/x-icon" />
|
||||
<link href="../apple_touch_icon.png" rel="apple-touch-icon" />
|
||||
|
||||
<meta name="viewport" content="width=960, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"/>
|
||||
<meta name="mobileoptimized" content="0" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/>
|
||||
|
||||
<script src="../3rd/qtree.js" type="text/javascript"></script>
|
||||
<script src="../3rd/underscore.js" type="text/javascript"></script>
|
||||
<script src="../3rd/backbone.native.js" type="text/javascript"></script>
|
||||
<script src="../3rd/backbone.js" type="text/javascript"></script>
|
||||
|
||||
<script src="../src/adjust-viewport.js" type="text/javascript"></script>
|
||||
<script src="../src/shapes.js" type="text/javascript"></script>
|
||||
<script src="../src/core.js" type="text/javascript"></script>
|
||||
<script src="../src/character.js" type="text/javascript"></script>
|
||||
<script src="../src/input.js" type="text/javascript"></script>
|
||||
<script src="../src/hero.js" type="text/javascript"></script>
|
||||
<script src="../src/world.js" type="text/javascript"></script>
|
||||
<script src="../src/local-storage.js" type="text/javascript"></script>
|
||||
<script src="../src/camera.js" type="text/javascript"></script>
|
||||
<script src="../src/editor.js" type="text/javascript"></script>
|
||||
|
||||
<script src="mario.js" type="text/javascript"></script>
|
||||
<script src="tiles.js" type="text/javascript"></script>
|
||||
<script src="artifacts.js" type="text/javascript"></script>
|
||||
<script src="enemies.js" type="text/javascript"></script>
|
||||
<script src="display.js" type="text/javascript"></script>
|
||||
<script src="level_1-1.js" type="text/javascript"></script>
|
||||
<script src="main.js" type="text/javascript"></script>
|
||||
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #000;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
canvas {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
/* canvas 自适应容器 */
|
||||
canvas {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// 强制启用触摸板
|
||||
window.forceTouchpad = true;
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<img id="mario" src="super-mario-2x.png" style="display:none;" />
|
||||
<img id="tiles" src="super-mario-tiles-2x.png" style="display:none;" />
|
||||
<img id="enemies" src="super-mario-enemies-2x.png" style="display:none;" />
|
||||
<img id="icons" src="icons.png" style="display:none;" />
|
||||
|
||||
<canvas id="foreground" width="960" height="700">
|
||||
Your browser does not support canvas element.
|
||||
</canvas>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const canvas = document.getElementById("foreground");
|
||||
if (!canvas) return;
|
||||
|
||||
function fitCanvas() {
|
||||
const viewportWidth = window.innerWidth || document.documentElement.clientWidth || 0;
|
||||
const viewportHeight = window.innerHeight || document.documentElement.clientHeight || 0;
|
||||
if (!viewportWidth || !viewportHeight) return;
|
||||
|
||||
const ratio = canvas.width / canvas.height;
|
||||
let w = viewportWidth;
|
||||
let h = viewportWidth / ratio;
|
||||
if (h > viewportHeight) {
|
||||
h = viewportHeight;
|
||||
w = viewportHeight * ratio;
|
||||
}
|
||||
|
||||
canvas.style.width = w + "px";
|
||||
canvas.style.height = h + "px";
|
||||
canvas.style.left = (viewportWidth - w) / 2 + "px";
|
||||
canvas.style.top = (viewportHeight - h) / 2 + "px";
|
||||
}
|
||||
|
||||
window.addEventListener("resize", fitCanvas, { passive: true });
|
||||
window.addEventListener("orientationchange", fitCanvas, { passive: true });
|
||||
fitCanvas();
|
||||
setTimeout(fitCanvas, 50);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
5635
games/mario/super-mario-bros/level_1-1.js
Normal file
254
games/mario/super-mario-bros/main.js
Normal file
@@ -0,0 +1,254 @@
|
||||
$(window).on("load", function() {
|
||||
|
||||
/**
|
||||
*
|
||||
* Backbone Game Engine - An elementary HTML5 canvas game engine using Backbone.
|
||||
*
|
||||
* Copyright (c) 2014 Martin Drapeau
|
||||
* https://github.com/martindrapeau/backbone-game-engine
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
var canvas = document.getElementById("foreground"),
|
||||
context = canvas.getContext("2d");
|
||||
adjustViewport(canvas);
|
||||
|
||||
var spriteNames = [
|
||||
"ground", "brick-top", "brick", "ground2", "block", "block2", "question-block", "pennie",
|
||||
"tube1", "tube2", "tube3-mirror", "tube4-mirror", "bush1", "bush2", "bush3", "cloud1", "cloud2", "cloud3",
|
||||
"cloud-happy1", "cloud-happy2", "cloud-happy3", "flag-pole1", "water-bridge",
|
||||
"cloud-small", "ground-ug", "brick-top-ug", "brick-ug", "ground2-ug", "block-ug", "block2-ug",
|
||||
"question-block-ug", "pennie-ug", "tube3", "tube4", "tube1-mirror", "tube2-mirror", "bush4", "bush5", "bush6",
|
||||
"cloud4", "cloud5", "cloud6", "cloud-happy4", "cloud-happy5", "cloud-happy6", "flag-pole2", "railing",
|
||||
"tube1-out", "tube2-out", "tube3-out", "tube1-out-mirror", "tube2-out-mirror", "tube3-out-mirror", "water1", "lava1",
|
||||
"platform1", "platform2", "platform3", "brick7-castle", "brick-castle", "brick2-castle", "brick3-castle",
|
||||
"mario", "luigi", "mushroom", "turtle", "flying-turtle", "red-turtle", "red-flying-turtle", "beetle", "spike",
|
||||
"tube4-out", "tube5-out", "tube6-out", "tube4-out-mirror", "tube5-out-mirror", "tube6-out-mirror", "water2", "lava2",
|
||||
"bush7", "bush8", "bush9", "platform-pole", "brick4-castle", "brick5-castle", "brick6-castle"
|
||||
];
|
||||
|
||||
Backbone.Controller = Backbone.Model.extend({
|
||||
initialize: function(attributes, options) {
|
||||
options || (options = {});
|
||||
var controller = this;
|
||||
|
||||
_.bindAll(this, "onChangeState", "toggleState", "saveWorld", "loadWorld");
|
||||
|
||||
// Create our sprite sheets and attach them to existing sprite classes
|
||||
this.spriteSheets = new Backbone.SpriteSheetCollection([{
|
||||
id: "mario",
|
||||
img: "#mario",
|
||||
tileWidth: 32,
|
||||
tileHeight: 64,
|
||||
tileColumns: 21,
|
||||
tileRows: 6
|
||||
}, {
|
||||
id: "tiles",
|
||||
img: "#tiles",
|
||||
tileWidth: 32,
|
||||
tileHeight: 32,
|
||||
tileColumns: 29,
|
||||
tileRows: 28
|
||||
}, {
|
||||
id: "enemies",
|
||||
img: "#enemies",
|
||||
tileWidth: 32,
|
||||
tileHeight: 64,
|
||||
tileColumns: 51,
|
||||
tileRows: 3
|
||||
}]).attachToSpriteClasses();
|
||||
|
||||
// Create the debug panel
|
||||
this.debugPanel = new Backbone.DebugPanel();
|
||||
|
||||
// User input (turn off touchpad to start)
|
||||
this.input = new Backbone.Input({
|
||||
drawTouchpad: true
|
||||
});
|
||||
|
||||
// Camera
|
||||
this.camera = new Backbone.Camera();
|
||||
|
||||
// Our world
|
||||
// Reserve bottom of canvas for input and editor
|
||||
this.world = new Backbone.World(
|
||||
_.extend({viewportBottom: 156}, window._world), {
|
||||
input: this.input,
|
||||
camera: this.camera
|
||||
});
|
||||
|
||||
this.display = new Backbone.Display({}, {
|
||||
world: this.world
|
||||
});
|
||||
|
||||
// Message
|
||||
this.message = new Backbone.Message({
|
||||
x: 480, y: 20
|
||||
});
|
||||
|
||||
// Buttons
|
||||
this.toggleButton = new Backbone.Button({
|
||||
x: 4, y: 4, width: 52, height: 52, borderRadius: 5,
|
||||
img: "#icons", imgX: 0, imgY: 0, imgWidth: 32, imgHeight: 32, imgMargin: 10
|
||||
});
|
||||
this.toggleButton.on("tap", this.toggleState, this);
|
||||
|
||||
this.saveButton = new Backbone.Button({
|
||||
x: 4, y: 548, width: 52, height: 52, borderRadius: 5,
|
||||
img: "#icons", imgX: 96, imgY: 0, imgWidth: 32, imgHeight: 32, imgMargin: 10
|
||||
});
|
||||
this.saveButton.on("tap", this.saveWorld, this);
|
||||
|
||||
this.restartButton = new Backbone.Button({
|
||||
x: 4, y: 608, width: 52, height: 52, borderRadius: 5,
|
||||
img: "#icons", imgX: 128, imgY: 0, imgWidth: 32, imgHeight: 32, imgMargin: 10
|
||||
});
|
||||
this.restartButton.on("tap", this.restartWorld, this);
|
||||
|
||||
this.downloadButton = new Backbone.Button({
|
||||
x: 888, y: 10, width: 52, height: 52, borderRadius: 5,
|
||||
img: "#icons", imgX: 64, imgY: 0, imgWidth: 32, imgHeight: 32, imgMargin: 10
|
||||
});
|
||||
this.downloadButton.on("tap", this.downloadNewVersion, this);
|
||||
|
||||
// The game engine
|
||||
this.engine = new Backbone.Engine({}, {
|
||||
canvas: canvas,
|
||||
debugPanel: this.debugPanel
|
||||
});
|
||||
this.engine.add(_.compact([
|
||||
this.world,
|
||||
this.display,
|
||||
this.camera,
|
||||
this.toggleButton,
|
||||
this.message,
|
||||
this.debugPanel
|
||||
]));
|
||||
|
||||
// The sprite picker and editor
|
||||
this.editor = new Backbone.WorldEditor({
|
||||
spriteNames: spriteNames
|
||||
}, {
|
||||
world: this.world
|
||||
});
|
||||
|
||||
// Controls
|
||||
$(document).on("keypress.Controller", function(e) {
|
||||
if (e.keyCode == 66 || e.keyCode == 98)
|
||||
controller.engine.toggle(); // b to break the animation
|
||||
else if (e.keyCode == 80 || e.keyCode == 112)
|
||||
controller.toggleState(); // p to pause and pause
|
||||
});
|
||||
|
||||
this.listenTo(this.world, "change:state", this.onChangeState);
|
||||
this.onChangeState();
|
||||
|
||||
// If we have Application Cache active, load the latest world
|
||||
this.loadWorld();
|
||||
},
|
||||
toggleState: function(e) {
|
||||
var state = this.world.get("state");
|
||||
this.world.set("state", state == "pause" ? "play" : "pause");
|
||||
if (!this.engine.isRunning()) this.engine.start();
|
||||
},
|
||||
onChangeState: function() {
|
||||
var state = this.world.get("state");
|
||||
if (state == "pause") {
|
||||
// Edit
|
||||
context.clearRect(0, 0, canvas.width, canvas.height);
|
||||
this.engine.remove(this.input);
|
||||
this.engine.add(this.editor);
|
||||
this.engine.add([this.saveButton, this.restartButton]);
|
||||
this.toggleButton.set({imgX: 32});
|
||||
} else {
|
||||
// Play
|
||||
context.clearRect(0, 0, canvas.width, canvas.height);
|
||||
this.engine.remove(this.editor);
|
||||
this.engine.remove([this.saveButton, this.restartButton]);
|
||||
this.engine.add(this.input);
|
||||
this.toggleButton.set({imgX: 0});
|
||||
}
|
||||
},
|
||||
// Save our world to the server when changed. Debounce by 5 seconds
|
||||
// and push back saving upon activity
|
||||
saveWorld: function() {
|
||||
var controller = this,
|
||||
world = this.world
|
||||
message = this.message;
|
||||
|
||||
message.show("Saving...");
|
||||
world.save(null, {
|
||||
success: function() {
|
||||
setTimeout(function() {
|
||||
message.hide();
|
||||
}, 1000);
|
||||
},
|
||||
error: function(xhr, textStatus, errorThrown ) {
|
||||
message.show("Error: " + errorThrown);
|
||||
}
|
||||
});
|
||||
return this;
|
||||
},
|
||||
// Load our world from the server.
|
||||
loadWorld: function() {
|
||||
var controller = this,
|
||||
world = this.world,
|
||||
message = this.message;
|
||||
|
||||
message.show("Loading...");
|
||||
world.fetch({
|
||||
success: function() {
|
||||
world.spawnSprites();
|
||||
message.hide();
|
||||
},
|
||||
error: function(xhr, textStatus, errorThrown ) {
|
||||
message.show('Error: ' + errorThrown);
|
||||
setTimeout(function() {
|
||||
message.hide();
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
return this;
|
||||
},
|
||||
restartWorld: function() {
|
||||
var controller = this,
|
||||
world = this.world,
|
||||
message = this.message;
|
||||
|
||||
message.show("Restarting...");
|
||||
|
||||
localStorage.removeItem(world.id);
|
||||
|
||||
world.set(window._world).spawnSprites();
|
||||
|
||||
setTimeout(function() {
|
||||
message.hide();
|
||||
}, 2000);
|
||||
|
||||
return this;
|
||||
},
|
||||
downloadNewVersion: function() {
|
||||
window.applicationCache.swapCache();
|
||||
this.message.show("Please wait...");
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
|
||||
var controller = new Backbone.Controller();
|
||||
|
||||
// When a newer version is available, load it and inform
|
||||
// the user it can be downloaded.
|
||||
if (window.applicationCache !== undefined)
|
||||
window.applicationCache.addEventListener('updateready', function() {
|
||||
controller.engine.add(controller.downloadButton);
|
||||
});
|
||||
|
||||
// Expose things as globals - easier to debug
|
||||
_.extend(window, {
|
||||
canvas: canvas,
|
||||
context: context,
|
||||
controller: controller,
|
||||
});
|
||||
|
||||
});
|
||||
68
games/mario/super-mario-bros/mario.js
Normal file
@@ -0,0 +1,68 @@
|
||||
(function() {
|
||||
|
||||
/**
|
||||
*
|
||||
* Backbone Game Engine - An elementary HTML5 canvas game engine using Backbone.
|
||||
*
|
||||
* Copyright (c) 2014 Martin Drapeau
|
||||
* https://github.com/martindrapeau/backbone-game-engine
|
||||
*
|
||||
*/
|
||||
|
||||
Backbone.Mario = Backbone.Hero.extend({
|
||||
defaults: _.extend({}, Backbone.Hero.prototype.defaults, {
|
||||
name: "mario",
|
||||
spriteSheet: "mario"
|
||||
}),
|
||||
bounce: function(sprite, dir, dir2) {
|
||||
var cur = this.getStateInfo(),
|
||||
state = this.buildState("jump", cur.dir);
|
||||
this.set({
|
||||
state: state,
|
||||
yVelocity: this.animations[state].yStartVelocity*0.5,
|
||||
nextState: this.buildState("idle", cur.dir)
|
||||
});
|
||||
this.cancelUpdate = true;
|
||||
return this;
|
||||
},
|
||||
hit: function(sprite, dir, dir2) {
|
||||
if (this._handlingSpriteHit) return this;
|
||||
this._handlingSpriteHit = sprite;
|
||||
|
||||
if (sprite.get("type") == "artifact") {
|
||||
this.cancelUpdate = true;
|
||||
} else if (sprite.get("type") == "character") {
|
||||
var name = sprite.get("name"),
|
||||
cur = this.getStateInfo(),
|
||||
opo = dir == "left" ? "right" : (dir == "right" ? "left" : (dir == "top" ? "bottom" : "top"));
|
||||
|
||||
if (dir == "bottom" && name != "spike") {
|
||||
this.bounce.apply(this, arguments);
|
||||
} else if (sprite.isAttacking()) {
|
||||
this.knockout(sprite, "left");
|
||||
}
|
||||
sprite.trigger("hit", this, opo);
|
||||
}
|
||||
|
||||
this._handlingSpriteHit = undefined;
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
Backbone.Luigi = Backbone.Mario.extend({
|
||||
defaults: _.extend({}, Backbone.Hero.prototype.defaults, {
|
||||
name: "luigi",
|
||||
spriteSheet: "mario"
|
||||
}),
|
||||
animations: _.reduce(Backbone.Hero.prototype.animations, function(animations, anim, name) {
|
||||
var clone = _.clone(anim);
|
||||
clone.sequences = _.map(anim.sequences, function(index) {
|
||||
return index + 42;
|
||||
});
|
||||
animations[name] = clone;
|
||||
return animations;
|
||||
}, {})
|
||||
});
|
||||
|
||||
|
||||
}).call(this);
|
||||
31
games/mario/super-mario-bros/offline.appcache
Normal file
@@ -0,0 +1,31 @@
|
||||
CACHE MANIFEST
|
||||
# Version 0.40 (c) 2014 Martin Drapeau
|
||||
|
||||
../3rd/qtree.js
|
||||
../3rd/underscore.js
|
||||
../3rd/backbone.native.js
|
||||
../3rd/backbone.js
|
||||
|
||||
../src/adjust-viewport.js
|
||||
../src/input.js
|
||||
../src/shapes.js
|
||||
../src/core.js
|
||||
../src/character.js
|
||||
../src/hero.js
|
||||
../src/world.js
|
||||
../src/local-storage.js
|
||||
../src/camera.js
|
||||
../src/editor.js
|
||||
|
||||
mario.js
|
||||
tiles.js
|
||||
artifacts.js
|
||||
enemies.js
|
||||
display.js
|
||||
level_1-1.js
|
||||
main.js
|
||||
|
||||
super-mario-2x.png
|
||||
super-mario-tiles-2x.png
|
||||
super-mario-enemies-2x.png
|
||||
icons.png
|
||||
BIN
games/mario/super-mario-bros/super-mario-2x.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
games/mario/super-mario-bros/super-mario-enemies-2x.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
games/mario/super-mario-bros/super-mario-tiles-2x.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
364
games/mario/super-mario-bros/tiles.js
Normal file
@@ -0,0 +1,364 @@
|
||||
(function() {
|
||||
|
||||
/**
|
||||
*
|
||||
* Backbone Game Engine - An elementary HTML5 canvas game engine using Backbone.
|
||||
*
|
||||
* Copyright (c) 2014 Martin Drapeau
|
||||
* https://github.com/martindrapeau/backbone-game-engine
|
||||
*
|
||||
*/
|
||||
|
||||
Backbone.Tile = Backbone.Sprite.extend({
|
||||
defaults: {
|
||||
type: "tile",
|
||||
width: 32,
|
||||
height: 32,
|
||||
spriteSheet: "tiles",
|
||||
state: "idle",
|
||||
static: true,
|
||||
persist: true
|
||||
},
|
||||
initialize: function(attributes, options) {
|
||||
options || (options = {});
|
||||
this.world = options.world;
|
||||
this.lastSequenceChangeTime = 0;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function extendSprite(cls, name, attributes, animations) {
|
||||
var newCls = _.classify(name);
|
||||
Backbone[newCls] = Backbone[cls].extend({
|
||||
defaults: _.extend(
|
||||
_.deepClone(Backbone[cls].prototype.defaults),
|
||||
{name: name},
|
||||
attributes || {}
|
||||
),
|
||||
animations: _.extend(
|
||||
_.deepClone(Backbone[cls].prototype.animations),
|
||||
animations || {}
|
||||
)
|
||||
});
|
||||
return Backbone[newCls];
|
||||
}
|
||||
|
||||
extendSprite("Tile", "ground", {collision: true}, {idle: {sequences: [0]}});
|
||||
|
||||
extendSprite("Tile", "ground2", {collision: true}, {idle: {sequences: [31]}});
|
||||
|
||||
extendSprite("Tile", "block", {collision: true}, {idle: {sequences: [3]}});
|
||||
|
||||
extendSprite("Tile", "block2", {collision: true}, {idle: {sequences: [29]}});
|
||||
|
||||
extendSprite("Tile", "tube1", {collision: true}, {idle: {sequences: [290]}});
|
||||
|
||||
extendSprite("Tile", "tube2", {collision: true}, {idle: {sequences: [291]}});
|
||||
|
||||
extendSprite("Tile", "tube1-mirror", {collision: true}, {idle: {sequences: [261]}});
|
||||
|
||||
extendSprite("Tile", "tube2-mirror", {collision: true}, {idle: {sequences: [262]}});
|
||||
|
||||
extendSprite("Tile", "tube1-ug", {collision: true}, {idle: {sequences: [406]}});
|
||||
|
||||
extendSprite("Tile", "tube2-ug", {collision: true}, {idle: {sequences: [407]}});
|
||||
|
||||
extendSprite("Tile", "bush1", {collision: false}, {idle: {sequences: [240]}});
|
||||
|
||||
extendSprite("Tile", "bush2", {collision: false}, {idle: {sequences: [241]}});
|
||||
|
||||
extendSprite("Tile", "bush3", {collision: false}, {idle: {sequences: [242]}});
|
||||
|
||||
extendSprite("Tile", "cloud1", {collision: false}, {idle: {sequences: [580]}});
|
||||
|
||||
extendSprite("Tile", "cloud2", {collision: false}, {idle: {sequences: [581]}});
|
||||
|
||||
extendSprite("Tile", "cloud3", {collision: false}, {idle: {sequences: [582]}});
|
||||
|
||||
extendSprite("Tile", "cloud-happy1", {collision: false}, {idle: {sequences: [585]}});
|
||||
|
||||
extendSprite("Tile", "cloud-happy2", {collision: false}, {idle: {sequences: [586]}});
|
||||
|
||||
extendSprite("Tile", "cloud-happy3", {collision: false}, {idle: {sequences: [587]}});
|
||||
|
||||
extendSprite("Tile", "flag-pole1", {collision: false}, {idle: {sequences: [306]}});
|
||||
|
||||
extendSprite("Tile", "cloud-small", {collision: true}, {idle: {sequences: [613]}});
|
||||
|
||||
extendSprite("Tile", "ground-ug", {collision: true}, {idle: {sequences: [116]}});
|
||||
|
||||
extendSprite("Tile", "ground2-ug", {collision: true}, {idle: {sequences: [147]}});
|
||||
|
||||
extendSprite("Tile", "block-ug", {collision: true}, {idle: {sequences: [119]}});
|
||||
|
||||
extendSprite("Tile", "block2-ug", {collision: true}, {idle: {sequences: [145]}});
|
||||
|
||||
extendSprite("Tile", "tube3", {collision: true}, {idle: {sequences: [319]}});
|
||||
|
||||
extendSprite("Tile", "tube4", {collision: true}, {idle: {sequences: [320]}});
|
||||
|
||||
extendSprite("Tile", "tube3-mirror", {collision: true}, {idle: {sequences: [232]}});
|
||||
|
||||
extendSprite("Tile", "tube4-mirror", {collision: true}, {idle: {sequences: [233]}});
|
||||
|
||||
extendSprite("Tile", "tube3-ug", {collision: true}, {idle: {sequences: [435]}});
|
||||
|
||||
extendSprite("Tile", "tube4-ug", {collision: true}, {idle: {sequences: [436]}});
|
||||
|
||||
extendSprite("Tile", "bush4", {collision: false}, {idle: {sequences: [269]}});
|
||||
|
||||
extendSprite("Tile", "bush5", {collision: false}, {idle: {sequences: [270]}});
|
||||
|
||||
extendSprite("Tile", "bush6", {collision: false}, {idle: {sequences: [271]}});
|
||||
|
||||
extendSprite("Tile", "cloud4", {collision: false}, {idle: {sequences: [609]}});
|
||||
|
||||
extendSprite("Tile", "cloud5", {collision: false}, {idle: {sequences: [610]}});
|
||||
|
||||
extendSprite("Tile", "cloud6", {collision: false}, {idle: {sequences: [611]}});
|
||||
|
||||
extendSprite("Tile", "cloud-happy4", {collision: false}, {idle: {sequences: [614]}});
|
||||
|
||||
extendSprite("Tile", "cloud-happy5", {collision: false}, {idle: {sequences: [615]}});
|
||||
|
||||
extendSprite("Tile", "cloud-happy6", {collision: false}, {idle: {sequences: [616]}});
|
||||
|
||||
extendSprite("Tile", "flag-pole2", {collision: false}, {idle: {sequences: [335]}});
|
||||
|
||||
extendSprite("Tile", "tube1-out", {collision: true}, {idle: {sequences: [292]}});
|
||||
|
||||
extendSprite("Tile", "tube2-out", {collision: true}, {idle: {sequences: [293]}});
|
||||
|
||||
extendSprite("Tile", "tube3-out", {collision: true}, {idle: {sequences: [294]}});
|
||||
|
||||
extendSprite("Tile", "tube1-out-mirror", {collision: true}, {idle: {sequences: [234]}});
|
||||
|
||||
extendSprite("Tile", "tube2-out-mirror", {collision: true}, {idle: {sequences: [235]}});
|
||||
|
||||
extendSprite("Tile", "tube3-out-mirror", {collision: true}, {idle: {sequences: [236]}});
|
||||
|
||||
extendSprite("Tile", "tube1-out-ug", {collision: true}, {idle: {sequences: [408]}});
|
||||
|
||||
extendSprite("Tile", "tube2-out-ug", {collision: true}, {idle: {sequences: [409]}});
|
||||
|
||||
extendSprite("Tile", "tube3-out-ug", {collision: true}, {idle: {sequences: [410]}});
|
||||
|
||||
extendSprite("Tile", "brick7-castle", {collision: true}, {idle: {sequences: [42]}});
|
||||
|
||||
extendSprite("Tile", "bush7", {collision: false}, {idle: {sequences: [272]}});
|
||||
|
||||
extendSprite("Tile", "bush8", {collision: false}, {idle: {sequences: [273]}});
|
||||
|
||||
extendSprite("Tile", "bush9", {collision: false}, {idle: {sequences: [274]}});
|
||||
|
||||
extendSprite("Tile", "water1", {collision: false}, {idle: {sequences: [583]}});
|
||||
|
||||
extendSprite("Tile", "water2", {collision: false}, {idle: {sequences: [612]}});
|
||||
|
||||
extendSprite("Tile", "water-bridge", {collision: true}, {idle: {sequences: [32]}});
|
||||
|
||||
extendSprite("Tile", "lava1", {collision: false}, {idle: {sequences: [699]}});
|
||||
|
||||
extendSprite("Tile", "lava2", {collision: false}, {idle: {sequences: [728]}});
|
||||
|
||||
extendSprite("Tile", "lava-bridge", {collision: true}, {idle: {sequences: [148]}});
|
||||
|
||||
extendSprite("Tile", "tube4-out", {collision: true}, {idle: {sequences: [321]}});
|
||||
|
||||
extendSprite("Tile", "tube5-out", {collision: true}, {idle: {sequences: [322]}});
|
||||
|
||||
extendSprite("Tile", "tube6-out", {collision: true}, {idle: {sequences: [323]}});
|
||||
|
||||
extendSprite("Tile", "tube4-out-mirror", {collision: true}, {idle: {sequences: [263]}});
|
||||
|
||||
extendSprite("Tile", "tube5-out-mirror", {collision: true}, {idle: {sequences: [264]}});
|
||||
|
||||
extendSprite("Tile", "tube6-out-mirror", {collision: true}, {idle: {sequences: [265]}});
|
||||
|
||||
extendSprite("Tile", "tube4-out-ug", {collision: true}, {idle: {sequences: [437]}});
|
||||
|
||||
extendSprite("Tile", "tube5-out-ug", {collision: true}, {idle: {sequences: [438]}});
|
||||
|
||||
extendSprite("Tile", "tube6-out-ug", {collision: true}, {idle: {sequences: [439]}});
|
||||
|
||||
extendSprite("Tile", "brick-castle", {collision: true}, {idle: {sequences: [11]}});
|
||||
|
||||
extendSprite("Tile", "brick2-castle", {collision: true}, {idle: {sequences: [12]}});
|
||||
|
||||
extendSprite("Tile", "brick3-castle", {collision: true}, {idle: {sequences: [13]}});
|
||||
|
||||
extendSprite("Tile", "brick4-castle", {collision: true}, {idle: {sequences: [14]}});
|
||||
|
||||
extendSprite("Tile", "brick5-castle", {collision: true}, {idle: {sequences: [40]}});
|
||||
|
||||
extendSprite("Tile", "brick6-castle", {collision: true}, {idle: {sequences: [41]}});
|
||||
|
||||
extendSprite("Tile", "railing", {collision: false}, {idle: {sequences: [363]}});
|
||||
|
||||
extendSprite("Tile", "platform1", {collision: true}, {idle: {sequences: [237]}});
|
||||
|
||||
extendSprite("Tile", "platform2", {collision: true}, {idle: {sequences: [238]}});
|
||||
|
||||
extendSprite("Tile", "platform3", {collision: true}, {idle: {sequences: [239]}});
|
||||
|
||||
extendSprite("Tile", "platform-pole", {collision: false}, {idle: {sequences: [208]}});
|
||||
|
||||
|
||||
// Subclass Sprite to use a global timer - so all animated
|
||||
// sprites animate in sync.
|
||||
Backbone.AnimatedTile = Backbone.Tile.extend({
|
||||
initialize: function(attributes, options) {
|
||||
Backbone.Tile.prototype.initialize.apply(this, arguments);
|
||||
this.on("attach", this.onAttach, this);
|
||||
this.on("detach", this.onDetach, this);
|
||||
},
|
||||
onAttach: function() {
|
||||
if (!this.engine) return;
|
||||
this.onDetach();
|
||||
|
||||
this.clock = this.engine.sprites.findWhere({name: "animatedTileClock"});
|
||||
|
||||
if (!this.clock)
|
||||
this.clock = this.engine.add(new Backbone.Clock({name: "animatedTileClock", delay: 200}));
|
||||
|
||||
this.listenTo(this.clock, "change:ticks", this.updateAnimationIndex);
|
||||
},
|
||||
onDetach: function() {
|
||||
if (this.clock) this.stopListening(this.clock);
|
||||
this.clock = undefined;
|
||||
},
|
||||
update: function(dt) {
|
||||
return true;
|
||||
},
|
||||
updateAnimationIndex: function() {
|
||||
var animation = this.getAnimation(),
|
||||
sequenceIndex = this.get("sequenceIndex") || 0;
|
||||
if (!animation) return;
|
||||
this.set("sequenceIndex", sequenceIndex < animation.sequences.length-1 ? sequenceIndex + 1 : 0);
|
||||
}
|
||||
});
|
||||
|
||||
Backbone.Brick = Backbone.AnimatedTile.extend({
|
||||
defaults: _.extend({}, Backbone.Tile.prototype.defaults, {
|
||||
name: "brick",
|
||||
state: "idle",
|
||||
collision: true,
|
||||
static: false
|
||||
}),
|
||||
animations: {
|
||||
idle: {
|
||||
sequences: [2]
|
||||
},
|
||||
bounce: {
|
||||
sequences: [
|
||||
{frame: 2, x: 0, y: -8},
|
||||
{frame: 2, x: 0, y: -8},
|
||||
{frame: 2, x: 0, y: -4},
|
||||
{frame: 2, x: 0, y: 0}
|
||||
],
|
||||
delay: 50
|
||||
}
|
||||
},
|
||||
initialize: function(attributes, options) {
|
||||
Backbone.AnimatedTile.prototype.initialize.apply(this, arguments);
|
||||
this.on("hit", this.hit, this);
|
||||
},
|
||||
hit: function(sprite, dir, dir2) {
|
||||
if (sprite.get("hero") && dir == "bottom") {
|
||||
var tile = this;
|
||||
this.set({state: "bounce", sequenceIndex: 0});
|
||||
this.world.setTimeout(function() {
|
||||
tile.set({state: "idle"});
|
||||
}, 200);
|
||||
} else if (dir == "top") {
|
||||
sprite.trigger("hit", this, "bottom");
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
var NewTile = extendSprite("Brick", "brick-top");
|
||||
animations = NewTile.prototype.animations;
|
||||
animations.idle.sequences = [1];
|
||||
_.each(animations.bounce.sequences, function(sequence) {sequence.frame = 1;});
|
||||
|
||||
NewTile = extendSprite("Brick", "brick-ug");
|
||||
animations = NewTile.prototype.animations;
|
||||
animations.idle.sequences = [118];
|
||||
_.each(animations.bounce.sequences, function(sequence) {sequence.frame = 118;});
|
||||
|
||||
NewTile = extendSprite("Brick", "brick-top-ug");
|
||||
animations = NewTile.prototype.animations;
|
||||
animations.idle.sequences = [117];
|
||||
_.each(animations.bounce.sequences, function(sequence) {sequence.frame = 117;});
|
||||
|
||||
|
||||
Backbone.QuestionBlock = Backbone.AnimatedTile.extend({
|
||||
defaults: _.extend({}, Backbone.Tile.prototype.defaults, {
|
||||
name: "question-block",
|
||||
state: "idle",
|
||||
collision: true,
|
||||
static: false
|
||||
}),
|
||||
animations: {
|
||||
idle: {
|
||||
sequences: [23, 23, 24, 25, 24, 23],
|
||||
delay: 150
|
||||
},
|
||||
bounce: {
|
||||
sequences: [
|
||||
{frame: 3, x: 0, y: -8},
|
||||
{frame: 3, x: 0, y: -8},
|
||||
{frame: 3, x: 0, y: -4},
|
||||
{frame: 3, x: 0, y: 0}
|
||||
],
|
||||
delay: 50
|
||||
},
|
||||
empty: {
|
||||
sequences: [3]
|
||||
}
|
||||
},
|
||||
initialize: function(attributes, options) {
|
||||
Backbone.AnimatedTile.prototype.initialize.apply(this, arguments);
|
||||
this.on("hit", this.hit, this);
|
||||
},
|
||||
hit: function(sprite, dir, dir2) {
|
||||
if (!sprite || !sprite.get("hero") || dir != "bottom") return;
|
||||
if (this.get("state") != "idle") return;
|
||||
|
||||
var tile = this;
|
||||
this.set({state: "bounce", sequenceIndex: 0});
|
||||
|
||||
if (this.world)
|
||||
this.world.add(new Backbone.FlyingPennie({
|
||||
x: this.get("x"),
|
||||
y: this.get("y")
|
||||
}));
|
||||
|
||||
setTimeout(function() {
|
||||
tile.set({state: "empty"});
|
||||
}, 200);
|
||||
}
|
||||
});
|
||||
|
||||
NewTile = extendSprite("QuestionBlock", "question-block-ug");
|
||||
animations = NewTile.prototype.animations;
|
||||
animations.idle.sequences = [139, 139, 140, 141, 140, 139];
|
||||
_.each(animations.bounce.sequences, function(sequence) {sequence.frame = 119;});
|
||||
animations.empty.sequences = [119];
|
||||
|
||||
NewTile = extendSprite("QuestionBlock", "invisible-question-block");
|
||||
animations = NewTile.prototype.animations;
|
||||
animations.idle.sequences = [];
|
||||
_.each(animations.bounce.sequences, function(sequence) {sequence.frame = 3;});
|
||||
animations.empty.sequences = [3];
|
||||
|
||||
NewTile = extendSprite("QuestionBlock", "invisible-question-block-ug");
|
||||
animations = NewTile.prototype.animations;
|
||||
animations.idle.sequences = [];
|
||||
_.each(animations.bounce.sequences, function(sequence) {sequence.frame = 119;});
|
||||
animations.empty.sequences = [119];
|
||||
|
||||
|
||||
}).call(this);
|
||||