Created
April 24, 2014 22:04
-
-
Save jcarbaugh/11271082 to your computer and use it in GitHub Desktop.
dreamcast2.js 2D game engine
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/************************* | |
* framework | |
************************/ | |
var dreamcast2; | |
if (window.log === undefined) { | |
window.log = function() { | |
log.history = log.history || []; // store logs to an array for reference | |
log.history.push(arguments); | |
if (this.console) { | |
console.log(Array.prototype.slice.call(arguments)); | |
} | |
}; | |
} | |
(function($) { | |
var noclick = function(ev) { | |
ev.preventDefault(); | |
}; | |
dreamcast2 = { | |
util: { | |
distance: function(pos1, pos2) { | |
return Math.sqrt(Math.pow(pos2.x - pos1.x, 2) + Math.pow(pos2.y - pos1.y, 2)); | |
}, | |
pad: function(s, n, c) { | |
var padLen = n - s.length; | |
if (padLen > 0) { | |
for (var i = 0; i < padLen; i++) { | |
s = c + s; | |
} | |
} | |
return s; | |
}, | |
safe_remove: function(element) { | |
if (element.jquery) element = element.get(0); | |
element.parentNode && element.parentNode.removeChild(element); | |
} | |
} | |
}; | |
var Game = function(sel, options, callback) { | |
$.extend(this, { | |
'runOnLoad': true, | |
'width': 640, | |
'height': 480, | |
'frameRate': 30, | |
/* set to zero for fast-as-possible display */ 'uuid': (new Date()).getTime(), | |
'masks': [], | |
'preload': null, | |
'soundMode': typeof(soundManager) != 'undefined' ? 'sm2' : 'html5', | |
'audioEnabled': true | |
}, options); | |
this.intervalRate = this.frameRate ? 1000 / this.frameRate : 0; | |
this.masks = []; | |
this.scenes = []; | |
this.elapsed = 0; | |
this.elem = $(sel); | |
this.preloadSounds = this['_' + this.soundMode + 'PreloadSounds']; | |
this.playSound = function(sound) { | |
var func = this.audioEnabled ? this['_' + this.soundMode + 'PlaySound'] : this._noopPlaySound; | |
return func.call(this, sound); | |
} | |
var game = this; | |
this.elem.css({ | |
'width': this.width, | |
'height': this.height | |
}).svg(function(svg) { | |
game.svg = svg; | |
// disable text on flash | |
if (svgweb.config && svgweb.config.use == 'flash') { | |
game.svg.text = function() { | |
return this; | |
}; | |
} | |
game.defs = game.svg.defs(); | |
game.background = svg.group(svg, 'game-background'); | |
game.svg.rect(game.background, 0, 0, game.width, game.height, { | |
fill: '#000000' | |
}); | |
if (game.preload) { | |
for (var i = 0; i < game.preload.length; i++) { | |
var img = new Image(); | |
img.src = game.preload[i]; | |
} | |
} | |
if (game.runOnLoad) { | |
game.run(); | |
} | |
callback(game); | |
}); | |
}; | |
Game.prototype.run = function() { | |
var then = new Date().getTime(); | |
var game = this; | |
var tick = function() { | |
var suspendID = game.svg.root().suspendRedraw(5000); | |
var now = new Date().getTime(); | |
var delta = now - then; | |
game.elapsed += delta; | |
then = now; | |
var scene = game.getCurrentScene(); | |
if (scene && !scene.isPaused()) { | |
scene.tick(delta); | |
} | |
game.svg.root().unsuspendRedraw(suspendID); | |
}; | |
this.interval = setInterval(tick, this.intervalRate); | |
}; | |
Game.prototype.getCurrentScene = function() { | |
var scene; | |
if (this.scenes.length > 0) { | |
scene = this.scenes[this.scenes.length - 1]; | |
} | |
return scene; | |
}; | |
Game.prototype.newScene = function(id, options) { | |
var scene = new Scene(id, options); | |
scene.game = this; | |
scene.layer = this.svg.group(this.svg, 'scene-' + id); | |
return scene; | |
}; | |
Game.prototype.popScene = function() { | |
var scene = this.scenes.pop(); | |
if (scene) { | |
scene.destroy(); | |
if (scene.layer) { | |
dreamcast2.util.safe_remove(scene.layer); | |
} | |
} | |
var previousScene = this.getCurrentScene(); | |
if (previousScene) { | |
if (previousScene.layer) { | |
previousScene.layer.hasAttribute('display') && previousScene.layer.removeAttribute('display'); | |
} | |
if (previousScene.init) { | |
previousScene.init(); | |
} | |
previousScene.resume(); | |
} | |
}; | |
Game.prototype.pushScene = function(scene) { | |
scene.game = this; | |
var previousScene = this.getCurrentScene(); | |
if (previousScene) { | |
previousScene.pause(); | |
if (previousScene.layer) { | |
previousScene.layer.setAttribute('display', 'none'); | |
} | |
} | |
if (scene.init) { | |
scene.init(); | |
} | |
this.scenes.push(scene); | |
}; | |
Game.prototype.setBackgroundImage = function(x, y, width, height, path) { | |
this.svg.image(this.background, x, y, width, height, path); | |
dreamcast2.util.safe_remove($(this.background).children().first()); | |
}; | |
Game.prototype.setBackgroundColor = function(color) { | |
this.svg.rect(this.background, 0, 0, this.width, this.height, { | |
fill: color | |
}); | |
dreamcast2.util.safe_remove($(this.background).children().first()); | |
}; | |
Game.prototype.getElapsedTime = function() { | |
return this.elapsed; | |
}; | |
Game.prototype.getUUID = function() { | |
return this.uuid++; | |
}; | |
Game.prototype.getMask = function(width, height) { | |
var id = width + '_' + height; | |
if (!this.masks[id]) { | |
this.masks[id] = this.svg._makeNode(this.defs, 'clipPath', { | |
id: id, | |
x: 0, | |
y: 0, | |
width: width, | |
height: height | |
}); | |
this.svg.rect(this.masks[id], 0, 0, width, height); | |
} | |
return id; | |
}; | |
Game.prototype._html5PreloadSounds = function(sounds) { | |
$.each(sounds, function(name, file) { | |
var id = 'sound-' + name; | |
$('body').append('<audio src="' + file + '.wav" autobuffer="autobuffer" preload="auto" id="' + id + '" />'); | |
}) | |
} | |
Game.prototype._html5PlaySound = function(sound) { | |
var snd = new Audio($('#sound-' + sound).attr('src')); | |
snd.play(); | |
return snd; | |
} | |
Game.prototype._sm2PreloadSounds = function(sounds) { | |
soundManager.onready(function() { | |
$.each(sounds, function(name, file) { | |
soundManager.createSound({ | |
id: name, | |
url: file + '.mp3', | |
autoLoad: true, | |
multiShotEvents: true | |
}); | |
}); | |
}) | |
} | |
Game.prototype._sm2PlaySound = function(sound) { | |
var out = { | |
'ended': false | |
} | |
var snd = soundManager.play(sound, { | |
onfinish: function() { | |
out.ended = true; | |
} | |
}); | |
out.pause = function() { | |
if (snd && snd.pause) { | |
snd.pause(); | |
} | |
}; | |
return out; | |
} | |
Game.prototype._noopPlaySound = function(sound) { | |
return { | |
'ended': true, | |
'pause': function() {} | |
} | |
} | |
dreamcast2.Game = Game; | |
/*** game scene ***/ | |
var Scene = function(id, options) { | |
$.extend(this, { | |
paused: false, | |
layers: {}, | |
layer: null, | |
ondestroy: null | |
}, options || {}); | |
this.id = id; | |
this.elapsed = 0; | |
this.actors = []; | |
this.scheduledTasks = []; | |
}; | |
Scene.prototype.tick = function(delta) { | |
this.elapsed += delta; | |
for (var i = 0; i < this.actors.length; i++) { | |
this.actors[i].update(delta); | |
} | |
if (this.update) { | |
this.update(delta); | |
} | |
}; | |
Scene.prototype.pause = function() { | |
this.paused = true; | |
}; | |
Scene.prototype.resume = function() { | |
this.paused = false; | |
}; | |
Scene.prototype.isPaused = function() { | |
return this.paused; | |
}; | |
Scene.prototype.addActor = function(actor, layer) { | |
if (!actor) return; | |
actor.scene = this; | |
actor.layer = layer && this.layers[layer] ? this.layers[layer] : this.game.svg.root(); | |
this.actors.push(actor); | |
return actor; | |
}; | |
Scene.prototype.addLayer = function(layer) { | |
var layerId = "scene-" + this.id + "-" + layer; | |
this.layers[layer] = this.game.svg.group( | |
this.layer || this.game.svg, | |
layerId | |
); | |
return this.layers[layer]; | |
}; | |
Scene.prototype.addScheduledTask = function(fn, delay) { | |
this.scheduledTasks.push(setInterval(function() { | |
if (!this.paused) fn(); | |
}, delay)); | |
}; | |
Scene.prototype.destroy = function() { | |
for (var i = 0; i < this.scheduledTasks.length; i++) { | |
clearInterval(this.scheduledTasks[i]); | |
} | |
if (this.ondestroy) { | |
this.ondestroy(); | |
} | |
}; | |
dreamcast2.Scene = Scene; | |
/*** sprite ***/ | |
var Sprite = function(options) { | |
$.extend(this, { | |
className: null, | |
enabled: true, | |
pos: { | |
x: 0, | |
y: 0 | |
}, | |
hasDirection: true, | |
direction: 90, | |
front: 90, | |
frameSize: { | |
width: 32, | |
height: 32 | |
}, | |
center: false, | |
background: '', | |
frameCount: 1, | |
animating: false, | |
animateWhileMoving: true, | |
animateCallback: false, | |
frame: 0, | |
image: '', | |
animInterval: 100, | |
onclick: noclick | |
}, options); | |
if (!this.center) this.center = { | |
x: Math.round(this.frameSize.width / 2), | |
y: Math.round(this.frameSize.height / 2) | |
}; | |
this.element = null; | |
this.moving = false; | |
this.moveTime = 0; | |
this.animateTime = 0; | |
this.mode = svgweb.config && svgweb.config.use == 'flash' ? 'embed' : 'clip'; | |
}; | |
Sprite.prototype.update = function(elapsed) { | |
if (this.enabled) { | |
if (!this.element && this.scene) { | |
var game = this.scene.game; | |
if (this.mode == 'clip') { | |
this.element = game.svg.image(this.layer, 0, 0, this.frameSize.width * this.frameCount, this.frameSize.height, this.image, { | |
'clip-path': 'url(#' + game.getMask(this.frameSize.width, this.frameSize.height) + ')' | |
}); | |
this.imageElement = this.element; | |
} else { | |
this.element = game.svg.group(this.layer); | |
this.embeddedSvg = game.svg._makeNode(this.element, 'svg', { | |
x: 0, | |
y: 0, | |
width: this.frameSize.width, | |
height: this.frameSize.height, | |
viewBox: '0 0 ' + this.frameSize.width + ' ' + this.frameSize.height | |
}); | |
this.imageElement = game.svg.image(this.embeddedSvg, 0, 0, this.frameSize.width * this.frameCount, this.frameSize.height, this.image); | |
} | |
var elem = $(this.element); | |
if (this.className) { | |
elem.addClass(this.className); | |
} | |
if (this.onclick) { | |
elem.click(this.onclick); | |
} | |
this.updateTransform(); | |
} | |
if (this.moving) { | |
this.moveTime += elapsed; | |
this.updatePosition(); | |
} | |
if (this.animating) { | |
this.animateTime += elapsed; | |
if (this.animateTime > this.animInterval) { | |
var numFrames = Math.floor(this.animateTime / this.animInterval); | |
this.advanceFrame(numFrames); | |
this.animateTime = this.animateTime % this.animInterval; | |
} | |
} | |
} | |
}; | |
Sprite.prototype.advanceFrame = function(numFrames) { | |
if (!numFrames) numFrames = 1; | |
var oldFrame = this.frame; | |
this.frame = (this.frame + numFrames) % this.frameCount; | |
if (this.animateCallback && this.frame < oldFrame) { | |
this.animateCallback(); | |
this.animating = false; | |
} else { | |
var offset = this.frame * this.frameSize.width * -1; | |
$(this.imageElement).attr('x', offset); | |
} | |
}; | |
Sprite.prototype.moveTo = function(x, y) { | |
this.pos.x = x; | |
this.pos.y = y; | |
this.updateTransform(); | |
}; | |
Sprite.prototype.updateTransform = function() { | |
var transform = 'translate(' + (this.pos.x - this.center.x) + ', ' + (this.pos.y - this.center.y) + ')'; | |
if (this.rotate) transform = transform + ' rotate(' + this.rotate + ',' + this.center.x + ',' + this.center.y + ')'; | |
$(this.element).attr({ | |
'transform': transform | |
}); | |
}; | |
// Sprite.prototype.directions = {true: {true: 0, false: 360}, false: {true: 180, false: 180}}; | |
Sprite.prototype.directions = {}; | |
Sprite.prototype.directions[true] = {} | |
Sprite.prototype.directions[true][true] = 0; | |
Sprite.prototype.directions[true][false] = 360; | |
Sprite.prototype.directions[false] = {} | |
Sprite.prototype.directions[false][true] = 180; | |
Sprite.prototype.directions[false][false] = 180; | |
Sprite.prototype.moveToward = function(x, y, duration, callback, easing) { | |
this.moving = true; | |
this.moveStartPos = { | |
x: this.pos.x, | |
y: this.pos.y | |
}; | |
this.moveTime = 0; | |
this.moveDestPos = { | |
x: x, | |
y: y | |
}; | |
this.moveDuration = duration; | |
this.moveCallback = callback; | |
if (this.animateWhileMoving) this.animating = true; | |
if (easing) { | |
this.easing = $.easing[easing]; | |
} else { | |
this.easing = $.easing.linear; | |
} | |
if (this.hasDirection) { | |
var xdiff = this.moveDestPos.x - this.pos.x; | |
var ydiff = this.moveDestPos.y - this.pos.y; | |
if (xdiff == 0) { | |
if (ydiff > 0) { | |
this.rotate = (this.front + 90) % 360; | |
} else if (ydiff < 0) { | |
this.rotate = (this.front + 270) % 360; | |
} | |
} else { | |
var theta = Math.atan(ydiff / xdiff) * (180 / Math.PI); | |
theta += this.directions[xdiff >= 0][ydiff >= 0]; | |
theta += this.front | |
this.rotate = theta % 360; | |
} | |
} | |
}; | |
Sprite.prototype.updatePosition = function() { | |
if (this.moveTime > this.moveDuration) { | |
this.finishMoving(); | |
if (this.moveCallback) this.moveCallback(); | |
} else { | |
var newX = this.easing(this.moveTime / this.moveDuration, this.moveTime, this.moveStartPos.x, this.moveDestPos.x - this.moveStartPos.x, this.moveDuration); | |
var newY = this.easing(this.moveTime / this.moveDuration, this.moveTime, this.moveStartPos.y, this.moveDestPos.y - this.moveStartPos.y, this.moveDuration); | |
this.moveTo(newX, newY); | |
} | |
}; | |
Sprite.prototype.finishMoving = function() { | |
this.moveTo(this.moveDestPos.x, this.moveDestPos.y); | |
this.moving = false; | |
if (this.animateWhileMoving) { | |
this.frame = 0; | |
$(this.element).attr('x', 0); | |
this.animating = false; | |
} | |
}; | |
Sprite.prototype.remove = function() { | |
this.enabled = false; | |
var arrayPos = $.inArray(this, this.scene.sprites); | |
if (arrayPos != -1) { | |
this.scene.sprites.splice(arrayPos, 1); | |
} | |
dreamcast2.util.safe_remove(this.element); | |
}; | |
dreamcast2.Sprite = Sprite; | |
})(jQuery); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment