forked from sent/waves
1520 lines
46 KiB
JavaScript
1520 lines
46 KiB
JavaScript
/*
|
|
*
|
|
* Jaws - a HTML5 canvas/javascript 2D game development framework
|
|
*
|
|
* Homepage: http://jawsjs.com/
|
|
* Works with: Chrome 6.0+, Firefox 3.6+, 4+, IE 9+
|
|
* License: LGPL - http://www.gnu.org/licenses/lgpl.html
|
|
*
|
|
* Formating guide:
|
|
*
|
|
* jaws.oneFunction()
|
|
* jaws.one_variable = 1
|
|
* new jaws.OneConstructor
|
|
*
|
|
* Jaws uses the "module pattern" and exposes itself through the global "jaws".
|
|
* It should play nice with all other JS libs.
|
|
*
|
|
* Have fun!
|
|
*
|
|
* ippa.
|
|
*
|
|
*/
|
|
|
|
var jaws = (function(jaws) {
|
|
|
|
var title
|
|
var log_tag
|
|
|
|
jaws.title = function(value) {
|
|
if(value) { return (title.innerHTML = value) }
|
|
return title.innerHTML
|
|
}
|
|
|
|
/*
|
|
* Unpacks Jaws core-constructors into the global namespace
|
|
* After calling unpack you can use:
|
|
* "Sprite()" instead of "jaws.Sprite()"
|
|
* "Animation()" instead of "jaws.Animation()"
|
|
* .. and so on.
|
|
*
|
|
*/
|
|
jaws.unpack = function() {
|
|
var make_global = ["Sprite", "SpriteList", "Animation", "Viewport", "SpriteSheet", "Parallax", "TileMap", "Rect", "pressed"]
|
|
|
|
make_global.forEach( function(item, array, total) {
|
|
if(window[item]) { jaws.log(item + "already exists in global namespace") }
|
|
else { window[item] = jaws[item] }
|
|
});
|
|
}
|
|
|
|
|
|
/*
|
|
* Logger, adds text to previously found or created <div id="jaws-log">
|
|
*/
|
|
jaws.log = function(msg, add) {
|
|
if(log_tag) {
|
|
msg += "<br />"
|
|
if(add) { log_tag.innerHTML = log_tag.innerHTML.toString() + msg }
|
|
else { log_tag.innerHTML = msg }
|
|
}
|
|
}
|
|
|
|
/*
|
|
* init()
|
|
*
|
|
* Initializes / creates:
|
|
* - jaws.canvas / jaws.context / jaws.dom (our drawable gamearea)
|
|
* - jaws.width / jaws.height (width/height of drawable gamearea)
|
|
* - jaws.url_parameters (hash of key/values of all parameters in current url)
|
|
* - title / log_tag (used internally by jaws)
|
|
*
|
|
* */
|
|
jaws.init = function(options) {
|
|
/* Find <title> tag */
|
|
title = document.getElementsByTagName('title')[0]
|
|
jaws.url_parameters = getUrlParameters()
|
|
|
|
/*
|
|
* If debug=1 parameter is present in the URL, let's either find <div id="jaws-log"> or create the tag.
|
|
* jaws.log(message) will use this div for debug/info output to the gamer or developer
|
|
*
|
|
*/
|
|
log_tag = document.getElementById('jaws-log')
|
|
if(jaws.url_parameters["debug"]) {
|
|
if(!log_tag) {
|
|
log_tag = document.createElement("div")
|
|
log_tag.style.cssText = "overflow: auto; color: #aaaaaa; width: 300px; height: 150px; margin: 40px auto 0px auto; padding: 5px; border: #444444 1px solid; clear: both; font: 10px verdana; text-align: left;"
|
|
document.body.appendChild(log_tag)
|
|
}
|
|
}
|
|
|
|
jaws.canvas = document.getElementsByTagName('canvas')[0]
|
|
if(jaws.canvas) {
|
|
jaws.context = jaws.canvas.getContext('2d');
|
|
}
|
|
else {
|
|
jaws.dom = document.getElementById("canvas")
|
|
jaws.dom.style.position = "relative" // This is needed to have sprites with position = "absolute" stay within the canvas
|
|
}
|
|
|
|
jaws.width = jaws.canvas ? jaws.canvas.width : jaws.dom.offsetWidth
|
|
jaws.height = jaws.canvas ? jaws.canvas.height : jaws.dom.offsetHeigh
|
|
}
|
|
|
|
/*
|
|
*
|
|
* Find the <canvas> so following draw-operations can use it.
|
|
* If the developer didn't provide a <canvas> in his HTML, let's create one.
|
|
*
|
|
*/
|
|
function findOrCreateCanvas() {
|
|
jaws.canvas = document.getElementsByTagName('canvas')[0]
|
|
if(!jaws.canvas) {
|
|
jaws.canvas = document.createElement("canvas")
|
|
jaws.canvas.width = 500
|
|
jaws.canvas.height = 300
|
|
document.body.appendChild(jaws.canvas)
|
|
jaws.log("creating canvas", true)
|
|
}
|
|
else {
|
|
jaws.log("found canvas", true)
|
|
}
|
|
jaws.context = jaws.canvas.getContext('2d');
|
|
}
|
|
|
|
/*
|
|
* Quick and easy startup of a jaws gameloop. Can be called in different ways:
|
|
*
|
|
* jaws.start(Game) // Start game state Game() with default options
|
|
* jaws.start(Game, {fps: 30}) // Start game state Geme() with options, in this case jaws will un Game with FPS 30
|
|
* jaws.start(window) //
|
|
*
|
|
*/
|
|
jaws.start = function(game_state, options) {
|
|
var wanted_fps = (options && options.fps) || 60
|
|
|
|
jaws.init()
|
|
jaws.log("setupInput()", true)
|
|
jaws.setupInput()
|
|
|
|
/* Callback for when one single assets has been loaded */
|
|
function assetLoaded(src, percent_done) {
|
|
jaws.log( percent_done + "%: " + src, true)
|
|
}
|
|
|
|
/* Callback for when an asset can't be loaded*/
|
|
function assetError(src) {
|
|
jaws.log( "Error loading: " + src)
|
|
}
|
|
|
|
/* Callback for when all assets are loaded */
|
|
function assetsLoaded() {
|
|
jaws.log("all assets loaded", true)
|
|
|
|
// This makes both jaws.start() and jaws.start(MenuState) possible
|
|
// Run game state constructor (new) after all assets are loaded
|
|
if( game_state && jaws.isFunction(game_state) ) { game_state = new game_state }
|
|
if(!game_state) { game_state = window }
|
|
|
|
jaws.gameloop = new jaws.GameLoop(game_state.setup, game_state.update, game_state.draw, wanted_fps)
|
|
jaws.game_state = game_state
|
|
jaws.gameloop.start()
|
|
}
|
|
|
|
jaws.log("assets.loadAll()", true)
|
|
if(jaws.assets.length() > 0) { jaws.assets.loadAll({onload:assetLoaded, onerror:assetError, onfinish:assetsLoaded}) }
|
|
else { assetsLoaded() }
|
|
}
|
|
|
|
/*
|
|
* Switch to a new active game state
|
|
* Save previous game state in jaws.previous_game_state
|
|
*/
|
|
jaws.switchGameState = function(game_state) {
|
|
jaws.gameloop.stop()
|
|
|
|
jaws.clearKeyCallbacks() // clear out all keyboard callbacks
|
|
|
|
if(jaws.isFunction(game_state)) { game_state = new game_state }
|
|
|
|
jaws.previous_game_state = jaws.game_state
|
|
jaws.game_state = game_state
|
|
jaws.gameloop = new jaws.GameLoop(game_state.setup, game_state.update, game_state.draw, jaws.gameloop.fps)
|
|
jaws.gameloop.start()
|
|
}
|
|
|
|
/* Always return obj as an array. forceArray(1) -> [1], forceArray([1,2]) -> [1,2] */
|
|
jaws.forceArray = function(obj) {
|
|
return Array.isArray(obj) ? obj : [obj]
|
|
}
|
|
|
|
/* Clears canvas through context.clearRect() */
|
|
jaws.clear = function() {
|
|
jaws.context.clearRect(0,0,jaws.width,jaws.height)
|
|
}
|
|
|
|
/* returns true if obj is an Image */
|
|
jaws.isImage = function(obj) {
|
|
return Object.prototype.toString.call(obj) === "[object HTMLImageElement]"
|
|
}
|
|
|
|
/* returns true of obj is a Canvas-element */
|
|
jaws.isCanvas = function(obj) {
|
|
return Object.prototype.toString.call(obj) === "[object HTMLCanvasElement]"
|
|
}
|
|
|
|
/* returns true of obj is either an Image or a Canvas-element */
|
|
jaws.isDrawable = function(obj) {
|
|
return jaws.isImage(obj) || jaws.isCanvas(obj)
|
|
}
|
|
|
|
/* returns true if obj is a String */
|
|
jaws.isString = function(obj) {
|
|
return (typeof obj == 'string')
|
|
}
|
|
|
|
/* returns true if obj is an Array */
|
|
jaws.isArray = function(obj) {
|
|
return !(obj.constructor.toString().indexOf("Array") == -1)
|
|
}
|
|
|
|
/* returns true of obj is a Function */
|
|
jaws.isFunction = function(obj) {
|
|
return (Object.prototype.toString.call(obj) === "[object Function]")
|
|
}
|
|
|
|
/*
|
|
* Return a hash of url-parameters and their values
|
|
*
|
|
* http://test.com/?debug=1&foo=bar -> [debug: 1, foo: bar]
|
|
*/
|
|
function getUrlParameters() {
|
|
var vars = [], hash;
|
|
var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
|
|
for(var i = 0; i < hashes.length; i++) {
|
|
hash = hashes[i].split('=');
|
|
vars.push(hash[0]);
|
|
vars[hash[0]] = hash[1];
|
|
}
|
|
return vars;
|
|
}
|
|
|
|
return jaws;
|
|
})(jaws || {});
|
|
|
|
var jaws = (function(jaws) {
|
|
|
|
var pressed_keys = {}
|
|
var keycode_to_string = []
|
|
var on_keydown_callbacks = []
|
|
var on_keyup_callbacks = []
|
|
|
|
/*
|
|
* Map all javascript keycodes to easy-to-remember letters/words
|
|
*/
|
|
jaws.setupInput = function() {
|
|
var k = []
|
|
|
|
k[8] = "backspace"
|
|
k[9] = "tab"
|
|
k[13] = "enter"
|
|
k[16] = "shift"
|
|
k[17] = "ctrl"
|
|
k[18] = "alt"
|
|
k[19] = "pause"
|
|
k[20] = "capslock"
|
|
k[27] = "esc"
|
|
k[32] = "space"
|
|
k[33] = "pageup"
|
|
k[34] = "pagedown"
|
|
k[35] = "end"
|
|
k[36] = "home"
|
|
k[37] = "left"
|
|
k[38] = "up"
|
|
k[39] = "right"
|
|
k[40] = "down"
|
|
k[45] = "insert"
|
|
k[46] = "delete"
|
|
|
|
k[91] = "leftwindowkey"
|
|
k[92] = "rightwindowkey"
|
|
k[93] = "selectkey"
|
|
k[106] = "multiply"
|
|
k[107] = "add"
|
|
k[109] = "subtract"
|
|
k[110] = "decimalpoint"
|
|
k[111] = "divide"
|
|
|
|
k[144] = "numlock"
|
|
k[145] = "scrollock"
|
|
k[186] = "semicolon"
|
|
k[187] = "equalsign"
|
|
k[188] = "comma"
|
|
k[189] = "dash"
|
|
k[190] = "period"
|
|
k[191] = "forwardslash"
|
|
k[192] = "graveaccent"
|
|
k[219] = "openbracket"
|
|
k[220] = "backslash"
|
|
k[221] = "closebracket"
|
|
k[222] = "singlequote"
|
|
|
|
var numpadkeys = ["numpad1","numpad2","numpad3","numpad4","numpad5","numpad6","numpad7","numpad8","numpad9"]
|
|
var fkeys = ["f1","f2","f3","f4","f5","f6","f7","f8","f9"]
|
|
var numbers = ["0","1","2","3","4","5","6","7","8","9"]
|
|
var letters = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"]
|
|
for(var i = 0; numbers[i]; i++) { k[48+i] = numbers[i] }
|
|
for(var i = 0; letters[i]; i++) { k[65+i] = letters[i] }
|
|
for(var i = 0; numpadkeys[i]; i++) { k[96+i] = numpadkeys[i] }
|
|
for(var i = 0; fkeys[i]; i++) { k[112+i] = fkeys[i] }
|
|
|
|
keycode_to_string = k
|
|
|
|
window.onkeydown = function(e) { handleKeyDown(e) }
|
|
window.onkeyup = function(e) { handleKeyUp(e) }
|
|
window.onkeypress = function(e) {};
|
|
}
|
|
|
|
// handle event "onkeydown" by remembering what key was pressed
|
|
function handleKeyUp(e) {
|
|
event = (e) ? e : window.event
|
|
var human_name = keycode_to_string[event.keyCode]
|
|
pressed_keys[human_name] = false
|
|
if(on_keyup_callbacks[human_name]) {
|
|
on_keyup_callbacks[human_name]()
|
|
e.preventDefault()
|
|
}
|
|
if(prevent_default_keys[human_name]) { e.preventDefault() }
|
|
}
|
|
|
|
// handle event "onkeydown" by remembering what key was un-pressed
|
|
function handleKeyDown(e) {
|
|
event = (e) ? e : window.event
|
|
var human_name = keycode_to_string[event.keyCode]
|
|
pressed_keys[human_name] = true
|
|
if(on_keydown_callbacks[human_name]) {
|
|
on_keydown_callbacks[human_name]()
|
|
e.preventDefault()
|
|
}
|
|
if(prevent_default_keys[human_name]) { e.preventDefault() }
|
|
|
|
// jaws.log(event.type + " - " + event.keyCode + " " + keycode_to_string[event.keyCode]);
|
|
// e.preventDefault();
|
|
}
|
|
|
|
|
|
var prevent_default_keys = []
|
|
jaws.preventDefaultKeys = function(array_of_strings) {
|
|
array_of_strings.forEach( function(item, index) {
|
|
prevent_default_keys[item] = true
|
|
});
|
|
}
|
|
|
|
/*
|
|
* helper to check if a given key currently is pressed. returns true or false.
|
|
*/
|
|
jaws.pressed = function(string) {
|
|
return pressed_keys[string]
|
|
}
|
|
|
|
jaws.on_keydown = function(key, callback) {
|
|
if(jaws.isArray(key)) {
|
|
for(var i=0; key[i]; i++) {
|
|
on_keydown_callbacks[key[i]] = callback
|
|
}
|
|
}
|
|
else {
|
|
on_keydown_callbacks[key] = callback
|
|
}
|
|
}
|
|
|
|
jaws.on_keyup = function(key, callback) {
|
|
if(jaws.isArray(key)) {
|
|
for(var i=0; key[i]; i++) {
|
|
on_keyup_callbacks[key[i]] = callback
|
|
}
|
|
}
|
|
else {
|
|
on_keyup_callbacks[key] = callback
|
|
}
|
|
}
|
|
|
|
/* Clean up all callbacks set by on_keydown / on_keyup */
|
|
jaws.clearKeyCallbacks = function() {
|
|
on_keyup_callbacks = []
|
|
on_keydown_callbacks = []
|
|
}
|
|
|
|
return jaws;
|
|
})(jaws || {});
|
|
|
|
var jaws = (function(jaws) {
|
|
|
|
/*
|
|
* jaws.Assets()
|
|
*
|
|
* Mass load / processing of assets (images, sound, video, json)
|
|
*
|
|
*/
|
|
jaws.Assets = function() {
|
|
this.loaded = [] // Hash of all URLs that's been loaded
|
|
this.loading = [] // Hash of all URLs currently loading
|
|
this.src_list = [] // Hash of all unloaded URLs that loadAll() will try to load
|
|
this.data = [] // Hash of loaded raw asset data, URLs are keys
|
|
|
|
this.image_to_canvas = true
|
|
this.fuchia_to_transparent = true
|
|
this.root = ""
|
|
|
|
this.file_type = {}
|
|
this.file_type["json"] = "json"
|
|
this.file_type["wav"] = "audio"
|
|
this.file_type["mp3"] = "audio"
|
|
this.file_type["ogg"] = "audio"
|
|
this.file_type["png"] = "image"
|
|
this.file_type["jpg"] = "image"
|
|
this.file_type["jpeg"] = "image"
|
|
this.file_type["gif"] = "image"
|
|
this.file_type["bmp"] = "image"
|
|
this.file_type["tiff"] = "image"
|
|
var that = this
|
|
|
|
this.length = function() {
|
|
return this.src_list.length
|
|
}
|
|
|
|
/*
|
|
* Get one or many resources
|
|
*
|
|
* @param String or Array of strings
|
|
* @returns The raw resource or an array of resources
|
|
*
|
|
*/
|
|
this.get = function(src) {
|
|
if(jaws.isArray(src)) {
|
|
return src.map( function(i) { return that.data[i] } )
|
|
}
|
|
else {
|
|
if(this.loaded[src]) { return this.data[src] }
|
|
else { jaws.log("No such asset: " + src) }
|
|
}
|
|
}
|
|
|
|
this.isLoading = function(src) {
|
|
return this.loading[src]
|
|
}
|
|
|
|
this.isLoaded = function(src) {
|
|
return this.loaded[src]
|
|
}
|
|
|
|
this.getPostfix = function(src) {
|
|
postfix_regexp = /\.([a-zA-Z]+)/;
|
|
return postfix_regexp.exec(src)[1]
|
|
}
|
|
|
|
this.getType = function(src) {
|
|
var postfix = this.getPostfix(src)
|
|
return (this.file_type[postfix] ? this.file_type[postfix] : postfix)
|
|
}
|
|
|
|
/* Add array of paths or single path to asset-list. Later load with loadAll() */
|
|
this.add = function(src) {
|
|
if(jaws.isArray(src)) { for(var i=0; src[i]; i++) { this.add(src[i]) } }
|
|
else { src = this.root + src; this.src_list.push(src) }
|
|
return this
|
|
}
|
|
|
|
/* Load all assets */
|
|
this.loadAll = function(options) {
|
|
this.load_count = 0
|
|
this.error_count = 0
|
|
|
|
/* With these 3 callbacks you can display progress and act when all assets are loaded */
|
|
this.onload = options.onload
|
|
this.onerror = options.onerror
|
|
this.onfinish = options.onfinish
|
|
|
|
for(i=0; this.src_list[i]; i++) {
|
|
this.load(this.src_list[i])
|
|
}
|
|
}
|
|
|
|
/* Calls onload right away if asset is available since before, otherwise try to load it */
|
|
this.getOrLoad = function(src, onload, onerror) {
|
|
if(this.data[src]) { onload() }
|
|
else { this.load(src, onload, onerror) }
|
|
}
|
|
|
|
/* Load one asset-object, i.e: {src: "foo.png"} */
|
|
this.load = function(src, onload, onerror) {
|
|
var asset = {}
|
|
asset.src = src
|
|
asset.onload = onload
|
|
asset.onerror = onerror
|
|
this.loading[src] = true
|
|
|
|
switch(this.getType(asset.src)) {
|
|
case "image":
|
|
var src = asset.src + "?" + parseInt(Math.random()*10000000)
|
|
asset.image = new Image()
|
|
asset.image.asset = asset // enables us to access asset in the callback
|
|
asset.image.onload = this.assetLoaded
|
|
asset.image.onerror = this.assetError
|
|
asset.image.src = src
|
|
break;
|
|
case "audio":
|
|
var src = asset.src + "?" + parseInt(Math.random()*10000000)
|
|
asset.audio = new Audio(src)
|
|
asset.audio.asset = asset // enables us access asset in the callback
|
|
this.data[asset.src] = asset.audio
|
|
asset.audio.addEventListener("canplay", this.assetLoaded, false);
|
|
asset.audio.addEventListener("error", this.assetError, false);
|
|
asset.audio.load()
|
|
break;
|
|
default:
|
|
var src = asset.src + "?" + parseInt(Math.random()*10000000)
|
|
var req = new XMLHttpRequest()
|
|
req.asset = asset // enables us access asset in the callback
|
|
req.onreadystatechange = this.assetLoaded
|
|
req.open('GET', src, true)
|
|
req.send(null)
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Callback for all asset-loading.
|
|
* 1) Parse data depending on filetype. Images are (optionally) converted to canvas-objects. json are parsed into native objects and so on.
|
|
* 2) Save processed data in internal list for easy fetching with assets.get(src) later on
|
|
* 3) Call callbacks if defined
|
|
*/
|
|
this.assetLoaded = function(e) {
|
|
var asset = this.asset
|
|
var src = asset.src
|
|
var filetype = that.getType(asset.src)
|
|
|
|
// Keep loading and loaded hash up to date
|
|
that.loaded[src] = true
|
|
that.loading[src] = false
|
|
|
|
// Process data depending differently on postfix
|
|
if(filetype == "json") {
|
|
if (this.readyState != 4) { return }
|
|
that.data[asset.src] = JSON.parse(this.responseText)
|
|
}
|
|
else if(filetype == "image") {
|
|
var new_image = that.image_to_canvas ? imageToCanvas(asset.image) : asset.image
|
|
if(that.fuchia_to_transparent && that.getPostfix(asset.src) == "bmp") { new_image = fuchiaToTransparent(new_image) }
|
|
that.data[asset.src] = new_image
|
|
}
|
|
else if(filetype == "audio") {
|
|
asset.audio.removeEventListener("canplay", that.assetLoaded, false);
|
|
that.data[asset.src] = asset.audio
|
|
}
|
|
|
|
that.load_count++
|
|
if(asset.onload) { asset.onload() } // single asset load()-callback
|
|
that.processCallbacks(asset)
|
|
}
|
|
|
|
this.assetError = function(e) {
|
|
var asset = this.asset
|
|
that.error_count++
|
|
if(asset.onerror) { asset.onerror(asset) }
|
|
that.processCallbacks(asset)
|
|
}
|
|
|
|
this.processCallbacks = function(asset) {
|
|
var percent = parseInt( (that.load_count+that.error_count) / that.src_list.length * 100)
|
|
if(that.onload) { that.onload(asset.src, percent) } // loadAll() - single asset has loaded callback
|
|
|
|
// When loadAll() is 100%, call onfinish() and kill callbacks (reset with next loadAll()-call)
|
|
if(percent==100) {
|
|
if(that.onfinish) { that.onfinish() }
|
|
that.onload = null
|
|
that.onerror = null
|
|
that.onfinish = null
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Takes an image, returns a canvas.
|
|
* Benchmarks has proven canvas to be faster to work with then images.
|
|
* Returns: a canvas
|
|
*/
|
|
function imageToCanvas(image) {
|
|
var canvas = document.createElement("canvas")
|
|
canvas.src = image.src // Make canvas look more like an image
|
|
canvas.width = image.width
|
|
canvas.height = image.height
|
|
|
|
var context = canvas.getContext("2d")
|
|
context.drawImage(image, 0, 0, image.width, image.height)
|
|
return canvas
|
|
}
|
|
|
|
/*
|
|
* Make Fuchia (0xFF00FF) transparent
|
|
* This is the de-facto standard way to do transparency in BMPs
|
|
* Returns: a canvas
|
|
*/
|
|
function fuchiaToTransparent(image) {
|
|
canvas = jaws.isImage(image) ? imageToCanvas(image) : image
|
|
var context = canvas.getContext("2d")
|
|
var img_data = context.getImageData(0,0,canvas.width,canvas.height)
|
|
var pixels = img_data.data
|
|
for(var i = 0; i < pixels.length; i += 4) {
|
|
if(pixels[i]==255 && pixels[i+1]==0 && pixels[i+2]==255) { // Color: Fuchia
|
|
pixels[i+3] = 0 // Set total see-through transparency
|
|
}
|
|
}
|
|
context.putImageData(img_data,0,0);
|
|
return canvas
|
|
}
|
|
|
|
/* Scale image by factor and keep jaggy retro-borders */
|
|
function retroScale(image, factor) {
|
|
canvas = jaws.isImage(image) ? imageToCanvas(image) : image
|
|
var context = canvas.getContext("2d")
|
|
var img_data = context.getImageData(0,0,canvas.width,canvas.height)
|
|
var pixels = img_data.data
|
|
|
|
var canvas2 = document.createElement("canvas")
|
|
canvas2.width = image.width * factor
|
|
canvas2.height = image.height * factor
|
|
var context2 = canvas.getContext("2d")
|
|
var img_data2 = context2.getImageData(0,0,canvas2.width,canvas2.height)
|
|
var pixels2 = img_data2.data
|
|
|
|
for (var x = 0; x < canvas.width * factor; x++) {
|
|
for (var y = 0; y < canvas.height * factor; y++) {
|
|
pixels2[x*y] = pixels[x*y / factor]
|
|
pixels2[x*y+1] = pixels[x*y+1 / factor]
|
|
pixels2[x*y+2] = pixels[x*y+2 / factor]
|
|
pixels2[x*y+3] = pixels[x*y+3 / factor]
|
|
}
|
|
}
|
|
|
|
context2.putImageData(img_data2,0,0);
|
|
return canvas2
|
|
}
|
|
|
|
jaws.assets = new jaws.Assets()
|
|
|
|
return jaws;
|
|
})(jaws || {});
|
|
|
|
var jaws = (function(jaws) {
|
|
|
|
/*
|
|
*
|
|
* GameLoop
|
|
*
|
|
* function draw() {
|
|
* ... your stuff executed every 30 FPS ...
|
|
* }
|
|
*
|
|
* gameloop = jaws.GameLoop(setup, update, draw, 30)
|
|
* gameloop.start()
|
|
*
|
|
* gameloop.start() starts a 2-step process, where first all assets are loaded.
|
|
* Then the real gameloop is started with the userspecified FPS.
|
|
*
|
|
* If using the shorter jaws.init() a GameLoop will automatically be created and started for you.
|
|
*
|
|
*/
|
|
jaws.GameLoop = function(setup, update, draw, wanted_fps) {
|
|
this.ticks = 0
|
|
this.tick_duration = 0
|
|
this.fps = 0
|
|
|
|
var update_id
|
|
var paused = false
|
|
var that = this
|
|
var mean_value = new MeanValue(20) // let's have a smooth, non-jittery FPS-value
|
|
|
|
this.start = function() {
|
|
jaws.log("gameloop start", true)
|
|
this.current_tick = (new Date()).getTime();
|
|
this.last_tick = (new Date()).getTime();
|
|
if(setup) { setup() }
|
|
update_id = setInterval(this.loop, 1000 / wanted_fps);
|
|
jaws.log("gameloop loop", true)
|
|
}
|
|
|
|
this.loop = function() {
|
|
that.current_tick = (new Date()).getTime();
|
|
that.tick_duration = that.current_tick - that.last_tick
|
|
//that.fps = parseInt(1000 / that.tick_duration)
|
|
that.fps = mean_value.add(1000/that.tick_duration).get()
|
|
|
|
if(!paused) {
|
|
if(update) { update() }
|
|
if(draw) { draw() }
|
|
that.ticks++
|
|
}
|
|
|
|
that.last_tick = that.current_tick;
|
|
}
|
|
|
|
this.pause = function() { paused = true }
|
|
this.unpause = function() { paused = false }
|
|
|
|
this.stop = function() {
|
|
if(update_id) { clearInterval(update_id); }
|
|
}
|
|
}
|
|
|
|
function MeanValue(size) {
|
|
this.size = size
|
|
this.values = new Array(this.size)
|
|
this.value
|
|
|
|
this.add = function(value) {
|
|
if(this.values.length > this.size) { // is values filled?
|
|
this.values.splice(0,1)
|
|
this.value = 0
|
|
for(var i=0; this.values[i]; i++) {
|
|
this.value += this.values[i]
|
|
}
|
|
this.value = this.value / this.size
|
|
}
|
|
this.values.push(value)
|
|
|
|
return this
|
|
}
|
|
|
|
this.get = function() {
|
|
return parseInt(this.value)
|
|
}
|
|
|
|
}
|
|
|
|
return jaws;
|
|
})(jaws || {});
|
|
|
|
var jaws = (function(jaws) {
|
|
|
|
/*
|
|
* A bread and butter Rect() - useful for basic collision detection
|
|
*/
|
|
jaws.Rect = function(x,y,width,height) {
|
|
this.x = x
|
|
this.y = y
|
|
this.width = width
|
|
this.height = height
|
|
this.right = x + width
|
|
this.bottom = y + height
|
|
}
|
|
|
|
jaws.Rect.prototype.getPosition = function() {
|
|
return [this.x, this.y]
|
|
}
|
|
|
|
jaws.Rect.prototype.move = function(x,y) {
|
|
this.x += x
|
|
this.y += y
|
|
this.right += x
|
|
this.bottom += y
|
|
}
|
|
|
|
jaws.Rect.prototype.moveTo = function(x,y) {
|
|
this.x = x
|
|
this.y = y
|
|
this.right = this.x + this.width
|
|
this.bottom = this.y + this.height
|
|
return this
|
|
}
|
|
|
|
jaws.Rect.prototype.resize = function(width,height) {
|
|
this.width += width
|
|
this.height += height
|
|
this.right = this.x + this.width
|
|
this.bottom = this.y + this.height
|
|
return this
|
|
}
|
|
|
|
jaws.Rect.prototype.resizeTo = function(width,height) {
|
|
this.width = width
|
|
this.height = height
|
|
this.right = this.x + this.width
|
|
this.bottom = this.y + this.height
|
|
return this
|
|
}
|
|
|
|
// Draw a red rectangle, useful for debug
|
|
jaws.Rect.prototype.draw = function() {
|
|
jaws.context.strokeStyle = "red"
|
|
jaws.context.strokeRect(this.x, this.y, this.width, this.height)
|
|
return this
|
|
}
|
|
|
|
// Returns true if point at x, y lies within calling rect
|
|
jaws.Rect.prototype.collidePoint = function(x, y) {
|
|
return (x >= this.x && x <= this.right && y >= this.y && y <= this.bottom)
|
|
}
|
|
|
|
// Returns true if calling rect overlaps with given rect in any way
|
|
jaws.Rect.prototype.collideRect = function(rect) {
|
|
return ((this.x >= rect.x && this.x <= rect.right) || (rect.x >= this.x && rect.x <= this.right)) &&
|
|
((this.y >= rect.y && this.y <= rect.bottom) || (rect.y >= this.y && rect.y <= this.bottom))
|
|
}
|
|
|
|
/*
|
|
// Possible future functions
|
|
jaws.Rect.prototype.collideRightSide = function(rect) { return(this.right >= rect.x && this.x < rect.x) }
|
|
jaws.Rect.prototype.collideLeftSide = function(rect) { return(this.x > rect.x && this.x <= rect.right) }
|
|
jaws.Rect.prototype.collideTopSide = function(rect) { return(this.y >= rect.y && this.y <= rect.bottom) }
|
|
jaws.Rect.prototype.collideBottomSide = function(rect) { return(this.bottom >= rect.y && this.y < rect.y) }
|
|
*/
|
|
|
|
jaws.Rect.prototype.toString = function() { return "[Rect " + this.x + ", " + this.y + ", " + this.width + ", " + this.height + "]" }
|
|
|
|
return jaws;
|
|
})(jaws || {});
|
|
|
|
// Support CommonJS require()
|
|
if(typeof module !== "undefined" && ('exports' in module)) { module.exports = jaws.Rect }
|
|
|
|
/*
|
|
*
|
|
* When we wan't to move something visible around on the screen :).
|
|
*
|
|
*
|
|
*/
|
|
var jaws = (function(jaws) {
|
|
|
|
jaws.Sprite = function(options) {
|
|
this.options = options
|
|
this.set(options)
|
|
this.context = options.context || jaws.context
|
|
if(!this.context) { this.createDiv() } // No canvas context? Switch to DOM-based spritemode
|
|
}
|
|
|
|
/* Call setters from JSON object. Used to parse options. */
|
|
jaws.Sprite.prototype.set = function(options) {
|
|
this.scale_factor_x = this.scale_factor_y = (options.scale || 1)
|
|
if(!options.anchor_x == undefined) {this.anchor_x = options.anchor_x}
|
|
if(!options.anchor_y == undefined) {this.anchor_y = options.anchor_y}
|
|
this.x = options.x || 0
|
|
this.y = options.y || 0
|
|
this.alpha = options.alpha || 1
|
|
this.angle = options.angle || 0
|
|
this.flipped = options.flipped || false
|
|
this.anchor(options.anchor || "top_left")
|
|
options.image && this.setImage(options.image)
|
|
this.cacheOffsets()
|
|
return this
|
|
}
|
|
|
|
/*
|
|
//
|
|
// Chainable setters under consideration:
|
|
//
|
|
jaws.Sprite.prototype.setFlipped = function(value) { this.flipped = value; return this }
|
|
jaws.Sprite.prototype.setAlpha = function(value) { this.alpha = value; return this }
|
|
jaws.Sprite.prototype.setAnchorX = function(value) { this.anchor_x = value; this.cacheOffsets(); return this }
|
|
jaws.Sprite.prototype.setAnchorY = function(value) { this.anchor_y = value; this.cacheOffsets(); return this }
|
|
jaws.Sprite.prototype.setAngle = function(value) { this.angle = value; return this }
|
|
jaws.Sprite.prototype.setScaleFactor = function(value) { this.scale_factor_x = this.scale_factor_y = value; this.cacheOffsets(); return this }
|
|
jaws.Sprite.prototype.setScaleFactorX = function(value) { this.scale_factor_x = value; this.cacheOffsets(); return this }
|
|
jaws.Sprite.prototype.setScaleFactorY = function(value) { this.scale_factor_y = value; this.cacheOffsets(); return this }
|
|
jaws.Sprite.prototype.moveX = function(x) { this.x += x; return this }
|
|
jaws.Sprite.prototype.moveXTo = function(x) { this.x = x; return this }
|
|
jaws.Sprite.prototype.moveY = function(y) { this.y += y; return this }
|
|
jaws.Sprite.prototype.moveYTo = function(y) { this.y = y; return this }
|
|
jaws.Sprite.prototype.scaleWidthTo = function(value) { this.scale_factor_x = value; return this.cacheOffsets() }
|
|
jaws.Sprite.prototype.scaleHeightTo = function(value) { this.scale_factor_y = value; return this.cachOfffsets() }
|
|
*/
|
|
|
|
/* Sprite modifiers. Modifies 1 or more properties and returns this for chainability. */
|
|
jaws.Sprite.prototype.setImage = function(value) {
|
|
var that = this
|
|
|
|
// An image, great, set this.image and return
|
|
if(jaws.isDrawable(value)) {
|
|
this.image = value
|
|
return this.cacheOffsets()
|
|
}
|
|
// Not an image, therefore an asset string, i.e. "ship.bmp"
|
|
else {
|
|
// Assets already loaded? Set this.image
|
|
if(jaws.assets.isLoaded(value)) { this.image = jaws.assets.get(value); this.cacheOffsets(); }
|
|
|
|
// Not loaded? Load it with callback to set image.
|
|
else { jaws.assets.load(value, function() { that.image = jaws.assets.get(value); that.cacheOffsets(); }) }
|
|
}
|
|
return this
|
|
}
|
|
jaws.Sprite.prototype.flip = function() { this.flipped = this.flipped ? false : true; return this }
|
|
jaws.Sprite.prototype.flipTo = function(value) { this.flipped = value; return this }
|
|
jaws.Sprite.prototype.rotate = function(value) { this.angle += value; return this }
|
|
jaws.Sprite.prototype.rotateTo = function(value) { this.angle = value; return this }
|
|
jaws.Sprite.prototype.moveTo = function(x,y) { this.x = x; this.y = y; return this }
|
|
jaws.Sprite.prototype.move = function(x,y) { if(x) this.x += x; if(y) this.y += y; return this }
|
|
jaws.Sprite.prototype.scale = function(value) { this.scale_factor_x *= value; this.scale_factor_y *= value; return this.cacheOffsets() }
|
|
jaws.Sprite.prototype.scaleTo = function(value) { this.scale_factor_x = this.scale_factor_y = value; return this.cacheOffsets() }
|
|
jaws.Sprite.prototype.scaleWidth = function(value) { this.scale_factor_x *= value; return this.cacheOffsets() }
|
|
jaws.Sprite.prototype.scaleHeight = function(value) { this.scale_factor_y *= value; return this.cacheOffsets() }
|
|
jaws.Sprite.prototype.setX = function(value) { this.x = value; return this }
|
|
jaws.Sprite.prototype.setY = function(value) { this.y = value; return this }
|
|
jaws.Sprite.prototype.setWidth = function(value) { this.scale_factor_x = value/this.image.width; return this.cacheOffsets() }
|
|
jaws.Sprite.prototype.setHeight = function(value) { this.scale_factor_y = value/this.image.height; return this.cacheOffsets() }
|
|
jaws.Sprite.prototype.resize = function(width, height) {
|
|
this.scale_factor_x = (this.width + width) / this.image.width
|
|
this.scale_factor_y = (this.height + height) / this.image.height
|
|
return this.cacheOffsets()
|
|
}
|
|
jaws.Sprite.prototype.resizeTo = function(width, height) {
|
|
this.scale_factor_x = width / this.image.width
|
|
this.scale_factor_y = height / this.image.height
|
|
return this.cacheOffsets()
|
|
}
|
|
|
|
/*
|
|
* The sprites anchor could be describe as "the part of the sprite will be placed at x/y"
|
|
* or "when rotating, what point of the of the sprite will it rotate round"
|
|
*
|
|
* For example, a topdown shooter could use anchor("center") --> Place middle of the ship on x/y
|
|
* .. and a sidescroller would probably use anchor("center_bottom") --> Place "feet" at x/y
|
|
*/
|
|
jaws.Sprite.prototype.anchor = function(value) {
|
|
var anchors = {
|
|
top_left: [0,0],
|
|
left_top: [0,0],
|
|
center_left: [0,0.5],
|
|
left_center: [0,0.5],
|
|
bottom_left: [0,1],
|
|
left_bottom: [0,1],
|
|
top_center: [0.5,0],
|
|
center_top: [0.5,0],
|
|
center_center: [0.5,0.5],
|
|
center: [0.5,0.5],
|
|
bottom_center: [0.5,1],
|
|
center_bottom: [0.5,1],
|
|
top_right: [1,0],
|
|
right_top: [1,0],
|
|
center_right: [1,0.5],
|
|
right_center: [1,0.5],
|
|
bottom_right: [1,1],
|
|
right_bottom: [1,1]
|
|
}
|
|
|
|
if(a = anchors[value]) {
|
|
this.anchor_x = a[0]
|
|
this.anchor_y = a[1]
|
|
if(this.image) this.cacheOffsets();
|
|
}
|
|
return this
|
|
}
|
|
|
|
jaws.Sprite.prototype.cacheOffsets = function() {
|
|
if(!this.image) { return }
|
|
|
|
this.width = this.image.width * this.scale_factor_x
|
|
this.height = this.image.height * this.scale_factor_y
|
|
this.left_offset = this.width * this.anchor_x
|
|
this.top_offset = this.height * this.anchor_y
|
|
this.right_offset = this.width * (1.0 - this.anchor_x)
|
|
this.bottom_offset = this.height * (1.0 - this.anchor_y)
|
|
|
|
if(this.cached_rect) this.cached_rect.resizeTo(this.width, this.height);
|
|
return this
|
|
}
|
|
|
|
/* Saves a Rect() perfectly surrouning our sprite in this.cached_rect and returns it */
|
|
jaws.Sprite.prototype.rect = function() {
|
|
if(!this.cached_rect) this.cached_rect = new jaws.Rect(this.x, this.top, this.width, this.height)
|
|
this.cached_rect.moveTo(this.x - this.left_offset, this.y - this.top_offset)
|
|
return this.cached_rect
|
|
}
|
|
|
|
/* Make this sprite a DOM-based <div> sprite */
|
|
jaws.Sprite.prototype.createDiv = function() {
|
|
this.div = document.createElement("div")
|
|
this.div.style.position = "absolute"
|
|
if(this.image) {
|
|
this.div.style.width = this.image.width + "px"
|
|
this.div.style.height = this.image.height + "px"
|
|
this.div.style.backgroundImage = "url(" + this.image.src + ")"
|
|
}
|
|
if(jaws.dom) { jaws.dom.appendChild(this.div) }
|
|
this.updateDiv()
|
|
}
|
|
|
|
/* Update properties for DOM-based sprite */
|
|
jaws.Sprite.prototype.updateDiv = function() {
|
|
this.div.style.left = this.x + "px"
|
|
this.div.style.top = this.y + "px"
|
|
|
|
var transform = ""
|
|
transform += "rotate(" + this.angle + "deg) "
|
|
if(this.flipped) { transform += "scale(-" + this.scale_factor_x + "," + this.scale_factor_y + ")"; }
|
|
else { transform += "scale(" + this.scale_factor_x + "," + this.scale_factor_y + ")"; }
|
|
|
|
this.div.style.MozTransform = transform
|
|
this.div.style.WebkitTransform = transform
|
|
this.div.style.transform = transform
|
|
return this
|
|
}
|
|
|
|
// Draw the sprite on screen via its previously given context
|
|
jaws.Sprite.prototype.draw = function() {
|
|
if(!this.image) { return this }
|
|
if(jaws.dom) { return this.updateDiv() }
|
|
|
|
this.context.save()
|
|
this.context.translate(this.x, this.y)
|
|
if(this.angle!=0) { jaws.context.rotate(this.angle * Math.PI / 180) }
|
|
this.flipped && this.context.scale(-1, 1)
|
|
this.context.globalAlpha = this.alpha
|
|
this.context.translate(-this.left_offset, -this.top_offset) // Needs to be separate from above translate call cause of flipped
|
|
this.context.drawImage(this.image, 0, 0, this.width, this.height)
|
|
this.context.restore()
|
|
return this
|
|
}
|
|
|
|
// Create a new canvas context, draw sprite on it and return. Use to get a raw canvas copy of the current sprite state.
|
|
jaws.Sprite.prototype.asCanvasContext = function() {
|
|
var canvas = document.createElement("canvas")
|
|
canvas.width = this.width
|
|
canvas.height = this.height
|
|
|
|
var context = canvas.getContext("2d")
|
|
context.mozImageSmoothingEnabled = jaws.context.mozImageSmoothingEnabled
|
|
|
|
context.drawImage(this.image, 0, 0, this.width, this.height)
|
|
return context
|
|
}
|
|
|
|
jaws.Sprite.prototype.toString = function() { return "[Sprite " + this.x + ", " + this.y + "," + this.width + "," + this.height + "]" }
|
|
|
|
return jaws;
|
|
})(jaws || {});
|
|
|
|
var jaws = (function(jaws) {
|
|
|
|
/*
|
|
*
|
|
* Constructor to manage your Sprites.
|
|
*
|
|
* Sprites (your bullets, aliens, enemies, players etc) will need to be
|
|
* updated, draw, deleted. Often in various orders and based on different conditions.
|
|
*
|
|
* This is where SpriteList() comes in.
|
|
*
|
|
* var enemies = new SpriteList()
|
|
*
|
|
* for(i=0; i < 100; i++) { // create 100 enemies
|
|
* enemies.push(new Sprite({image: "enemy.png", x: i, y: 200}))
|
|
* }
|
|
* enemies.draw() // calls draw() on all enemies
|
|
* enemies.deleteIf(isOutsideCanvas) // deletes each item in enemies that returns true when isOutsideCanvas(item) is called
|
|
* enemies.drawIf(isInsideViewport) // only call draw() on items that returns true when isInsideViewport is called with item as argument
|
|
*
|
|
*/
|
|
|
|
jaws.SpriteList = function() {}
|
|
jaws.SpriteList.prototype = new Array
|
|
|
|
jaws.SpriteList.prototype.remove = function(obj) {
|
|
var index = this.indexOf(obj)
|
|
if(index > -1) { this.splice(index, 1) }
|
|
}
|
|
|
|
jaws.SpriteList.prototype.draw = function() {
|
|
for(i=0; this[i]; i++) {
|
|
this[i].draw()
|
|
}
|
|
}
|
|
|
|
jaws.SpriteList.prototype.drawIf = function(condition) {
|
|
for(i=0; this[i]; i++) {
|
|
if( condition(this[i]) ) { this[i].draw() }
|
|
}
|
|
}
|
|
|
|
jaws.SpriteList.prototype.update = function() {
|
|
for(i=0; this[i]; i++) {
|
|
this[i].update()
|
|
}
|
|
}
|
|
|
|
jaws.SpriteList.prototype.updateIf = function(condition) {
|
|
for(i=0; this[i]; i++) {
|
|
if( condition(this[i]) ) { this[i].update() }
|
|
}
|
|
}
|
|
|
|
jaws.SpriteList.prototype.deleteIf = function(condition) {
|
|
for(var i=0; this[i]; i++) {
|
|
if( condition(this[i]) ) { this.splice(i,1) }
|
|
}
|
|
}
|
|
jaws.SpriteList.prototype.toString = function() { return "[SpriteList " + this.length + " sprites]" }
|
|
|
|
return jaws;
|
|
})(jaws || {});
|
|
|
|
var jaws = (function(jaws) {
|
|
|
|
/* Cut out a rectangular piece of a an image, returns as canvas-element */
|
|
function cutImage(image, x, y, width, height) {
|
|
var cut = document.createElement("canvas")
|
|
cut.width = width
|
|
cut.height = height
|
|
|
|
var ctx = cut.getContext("2d")
|
|
ctx.drawImage(image, x, y, width, height, 0, 0, cut.width, cut.height)
|
|
|
|
return cut
|
|
};
|
|
|
|
/* Cut up into frame_size pieces and put them in frames[] */
|
|
jaws.SpriteSheet = function(options) {
|
|
this.image = jaws.isDrawable(options.image) ? options.image : jaws.assets.data[options.image]
|
|
this.orientation = options.orientation || "right"
|
|
this.frame_size = options.frame_size || [32,32]
|
|
this.frames = []
|
|
|
|
var index = 0
|
|
for(var x=0; x < this.image.width; x += this.frame_size[0]) {
|
|
for(var y=0; y < this.image.height; y += this.frame_size[1]) {
|
|
this.frames.push( cutImage(this.image, x, y, this.frame_size[0], this.frame_size[1]) )
|
|
}
|
|
}
|
|
}
|
|
|
|
jaws.SpriteSheet.prototype.toString = function() { return "[SpriteSheet " + this.frames.length + " frames]" }
|
|
|
|
return jaws;
|
|
})(jaws || {});
|
|
|
|
var jaws = (function(jaws) {
|
|
|
|
jaws.Parallax = function(options) {
|
|
this.scale = options.scale || 1
|
|
this.repeat_x = options.repeat_x
|
|
this.repeat_y = options.repeat_y
|
|
this.camera_x = options.camera_x || 0
|
|
this.camera_y = options.camera_y || 0
|
|
this.layers = []
|
|
}
|
|
|
|
jaws.Parallax.prototype.draw = function(options) {
|
|
var layer, save_x, save_y;
|
|
|
|
for(var i=0; i < this.layers.length; i++) {
|
|
layer = this.layers[i]
|
|
|
|
save_x = layer.x
|
|
save_y = layer.y
|
|
|
|
layer.x = -(this.camera_x / layer.damping)
|
|
layer.y = -(this.camera_y / layer.damping)
|
|
|
|
while(this.repeat_x && layer.x > 0) { layer.x -= layer.width }
|
|
while(this.repeat_y && layer.y > 0) { layer.y -= layer.width }
|
|
|
|
while(this.repeat_x && layer.x < jaws.width) {
|
|
while(this.repeat_y && layer.y < jaws.height) {
|
|
layer.draw()
|
|
layer.y += layer.height
|
|
}
|
|
layer.y = save_y
|
|
layer.draw()
|
|
layer.x += (layer.width-1) // -1 to compensate for glitches in repeating tiles
|
|
}
|
|
while(layer.repeat_y && !layer.repeat_x && layer.y < jaws.height) {
|
|
layer.draw()
|
|
layer.y += layer.height
|
|
}
|
|
layer.x = save_x
|
|
}
|
|
}
|
|
jaws.Parallax.prototype.addLayer = function(options) {
|
|
var layer = new jaws.ParallaxLayer(options)
|
|
layer.scale(this.scale)
|
|
this.layers.push(layer)
|
|
}
|
|
jaws.Parallax.prototype.toString = function() { return "[Parallax " + this.x + ", " + this.y + ". " + this.layers.length + " layers]" }
|
|
|
|
jaws.ParallaxLayer = function(options) {
|
|
this.damping = options.damping || 0
|
|
jaws.Sprite.call(this, options)
|
|
}
|
|
jaws.ParallaxLayer.prototype = jaws.Sprite.prototype
|
|
jaws.Parallax.prototype.toString = function() { return "[ParallaxLayer " + this.x + ", " + this.y + "]" }
|
|
|
|
return jaws;
|
|
})(jaws || {});
|
|
|
|
var jaws = (function(jaws) {
|
|
|
|
/*
|
|
*
|
|
* Animation()
|
|
*
|
|
* Manages animation with a given list of frames and durations
|
|
* Takes a object as argument:
|
|
*
|
|
* loop: true|false - restart animation when end is reached
|
|
* bounce: true|false - rewind the animation frame by frame when end is reached
|
|
* index: int - start on this frame
|
|
* frames array - array of image/canvas items
|
|
* frame_duration int - how long should each frame be displayed
|
|
*
|
|
*/
|
|
jaws.Animation = function(options) {
|
|
this.options = options
|
|
this.frames = options.frames || []
|
|
this.frame_duration = options.frame_duration || 100 // default: 100ms between each frameswitch
|
|
this.index = options.index || 0 // default: start with the very first frame
|
|
this.loop = options.loop || 1
|
|
this.bounce = options.bounce || 0
|
|
this.frame_direction = 1
|
|
|
|
if(options.sprite_sheet) {
|
|
var image = (jaws.isDrawable(options.sprite_sheet) ? options.sprite_sheet : jaws.assets.get(options.sprite_sheet))
|
|
var sprite_sheet = new jaws.SpriteSheet({image: image, frame_size: options.frame_size})
|
|
this.frames = sprite_sheet.frames
|
|
}
|
|
|
|
/* Initializing timer-stuff */
|
|
this.current_tick = (new Date()).getTime();
|
|
this.last_tick = (new Date()).getTime();
|
|
this.sum_tick = 0
|
|
}
|
|
|
|
// Propells the animation forward by counting milliseconds and changing this.index accordingly
|
|
// Supports looping and bouncing animations.
|
|
jaws.Animation.prototype.update = function() {
|
|
this.current_tick = (new Date()).getTime();
|
|
this.sum_tick += (this.current_tick - this.last_tick);
|
|
this.last_tick = this.current_tick;
|
|
|
|
if(this.sum_tick > this.frame_duration) {
|
|
this.index += this.frame_direction
|
|
this.sum_tick = 0
|
|
}
|
|
if( (this.index >= this.frames.length) || (this.index <= 0) ) {
|
|
if(this.bounce) {
|
|
this.frame_direction = -this.frame_direction
|
|
this.index += this.frame_direction*2
|
|
}
|
|
else if(this.loop) {
|
|
this.index = 0
|
|
}
|
|
}
|
|
return this
|
|
}
|
|
|
|
// Like array.slice but returns a new Animation-object with a subset of the frames
|
|
jaws.Animation.prototype.slice = function(start, stop) {
|
|
var o = {}
|
|
o.frame_duration = this.frame_duration
|
|
o.loop = this.loop
|
|
o.bounce = this.bounce
|
|
o.frame_direction = this.frame_direction
|
|
o.frames = this.frames.slice().slice(start, stop)
|
|
return new jaws.Animation(o)
|
|
};
|
|
|
|
// Moves animation forward by calling update() and then return the current frame
|
|
jaws.Animation.prototype.next = function() {
|
|
this.update()
|
|
return this.frames[this.index]
|
|
};
|
|
|
|
// returns the current frame
|
|
jaws.Animation.prototype.currentFrame = function() {
|
|
return this.frames[this.index]
|
|
};
|
|
|
|
jaws.Animation.prototype.toString = function() { return "[Animation, " + this.frames.length + " frames]" }
|
|
|
|
return jaws;
|
|
})(jaws || {});
|
|
|
|
var jaws = (function(jaws) {
|
|
|
|
/*
|
|
*
|
|
* Viewport() is a window (a Rect) into a bigger canvas/image
|
|
*
|
|
* It won't every go "outside" that image.
|
|
* It comes with convenience methods as:
|
|
*
|
|
* viewport.centerAround(player) which will do just what you think. (player needs to have properties x and y)
|
|
*
|
|
*
|
|
*/
|
|
jaws.Viewport = function(options) {
|
|
this.options = options
|
|
this.context = options.context || jaws.context
|
|
this.width = options.width || jaws.width
|
|
this.height = options.height || jaws.height
|
|
this.max_x = options.max_x || jaws.width
|
|
this.max_y = options.max_y || jaws.height
|
|
|
|
this.verifyPosition = function() {
|
|
var max = this.max_x - this.width
|
|
if(this.x < 0) { this.x = 0 }
|
|
if(this.x > max) { this.x = max }
|
|
|
|
var max = this.max_y - this.height
|
|
if(this.y < 0) { this.y = 0 }
|
|
if(this.y > max) { this.y = max }
|
|
};
|
|
|
|
this.move = function(x, y) {
|
|
x && (this.x += x)
|
|
y && (this.y += y)
|
|
this.verifyPosition()
|
|
};
|
|
|
|
this.moveTo = function(x, y) {
|
|
if(!(x==undefined)) { this.x = x }
|
|
if(!(y==undefined)) { this.y = y }
|
|
this.verifyPosition()
|
|
};
|
|
|
|
this.isOutside = function(item) {
|
|
return(!this.isInside(item))
|
|
};
|
|
|
|
this.isInside = function(item) {
|
|
return( item.x >= this.x && item.x <= (this.x + this.width) && item.y >= this.y && item.y <= (this.y + this.height) )
|
|
};
|
|
|
|
this.centerAround = function(item) {
|
|
this.x = (item.x - this.width / 2)
|
|
this.y = (item.y - this.height / 2)
|
|
this.verifyPosition()
|
|
};
|
|
|
|
this.apply = function(func) {
|
|
this.context.save()
|
|
this.context.translate(-this.x, -this.y)
|
|
func()
|
|
this.context.restore()
|
|
};
|
|
|
|
this.moveTo(options.x||0, options.y||0)
|
|
}
|
|
|
|
jaws.Viewport.prototype.toString = function() { return "[Viewport " + this.x + ", " + this.y + "," + this.width + "," + this.height + "]" }
|
|
|
|
return jaws;
|
|
})(jaws || {});
|
|
|
|
var jaws = (function(jaws) {
|
|
|
|
/*
|
|
* TileMap - fast access to tiles
|
|
*
|
|
* var tile_map = new TileMap({size: [10, 10], cell_size: [16,16]})
|
|
* var sprite = new jaws.Sprite({x: 40, y: 40})
|
|
* var sprite2 = new jaws.Sprite({x: 41, y: 41})
|
|
* tile_map.push(sprite)
|
|
*
|
|
* tile_map.at(10,10) // []
|
|
* tile_map.at(40,40) // [sprite]
|
|
* tile_map.cell(0,0) // []
|
|
* tile_map.cell(1,1) // [sprite]
|
|
*
|
|
*/
|
|
jaws.TileMap = function(options) {
|
|
this.cell_size = options.cell_size || [32,32]
|
|
this.size = options.size
|
|
this.cells = new Array(this.size[0])
|
|
this.sortFunction = undefined
|
|
|
|
for(var col=0; col < this.size[0]; col++) {
|
|
this.cells[col] = new Array(this.size[1])
|
|
for(var row=0; row < this.size[1]; row++) {
|
|
this.cells[col][row] = [] // populate each cell with an empty array
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Clear all cells in tile map */
|
|
jaws.TileMap.prototype.clear = function() {
|
|
for(var col=0; col < this.size[0]; col++) {
|
|
for(var row=0; row < this.size[1]; row++) {
|
|
this.cells[col][row] = []
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Sort arrays in each cell in tile map according to sorter-function (see Array.sort) */
|
|
jaws.TileMap.prototype.sortCells = function(sortFunction) {
|
|
for(var col=0; col < this.size[0]; col++) {
|
|
for(var row=0; row < this.size[1]; row++) {
|
|
this.cells[col][row].sort( sortFunction )
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Push obj (or array of objs) into our cell-grid.
|
|
*
|
|
* Tries to read obj.x and obj.y to calculate what cell to occopy
|
|
*/
|
|
jaws.TileMap.prototype.push = function(obj) {
|
|
if(obj.length) {
|
|
for(var i=0; i < obj.length; i++) { this.push(obj[i]) }
|
|
return obj
|
|
}
|
|
if(obj.rect) {
|
|
return this.pushAsRect(obj, obj.rect())
|
|
}
|
|
else {
|
|
var col = parseInt(obj.x / this.cell_size[0])
|
|
var row = parseInt(obj.y / this.cell_size[1])
|
|
return this.pushToCell(col, row, obj)
|
|
}
|
|
|
|
}
|
|
jaws.TileMap.prototype.pushAsPoint = function(obj) {
|
|
if(Array.isArray(obj)) {
|
|
for(var i=0; i < obj.length; i++) { this.pushAsPoint(obj[i]) }
|
|
return obj
|
|
}
|
|
else {
|
|
var col = parseInt(obj.x / this.cell_size[0])
|
|
var row = parseInt(obj.y / this.cell_size[1])
|
|
return this.pushToCell(col, row, obj)
|
|
}
|
|
}
|
|
|
|
/* save 'obj' in cells touched by 'rect' */
|
|
jaws.TileMap.prototype.pushAsRect = function(obj, rect) {
|
|
var from_col = parseInt(rect.x / this.cell_size[0])
|
|
var to_col = parseInt((rect.right-1) / this.cell_size[0])
|
|
//jaws.log("rect.right: " + rect.right + " from/to col: " + from_col + " " + to_col, true)
|
|
|
|
for(var col = from_col; col <= to_col; col++) {
|
|
var from_row = parseInt(rect.y / this.cell_size[1])
|
|
var to_row = parseInt((rect.bottom-1) / this.cell_size[1])
|
|
|
|
//jaws.log("rect.bottom " + rect.bottom + " from/to row: " + from_row + " " + to_row, true)
|
|
for(var row = from_row; row <= to_row; row++) {
|
|
// console.log("pushAtRect() col/row: " + col + "/" + row + " - " + this.cells[col][row])
|
|
this.pushToCell(col, row, obj)
|
|
}
|
|
}
|
|
return obj
|
|
}
|
|
|
|
/*
|
|
* Push obj to a specific cell specified by col and row
|
|
* If cell is already occupied we create an array and push to that
|
|
*/
|
|
jaws.TileMap.prototype.pushToCell = function(col, row, obj) {
|
|
this.cells[col][row].push(obj)
|
|
if(this.sortFunction) this.cells[col][row].sort(this.sortFunction);
|
|
return this
|
|
}
|
|
|
|
//
|
|
// READERS
|
|
//
|
|
|
|
/* Get objects in cell that exists at coordinates x / y */
|
|
jaws.TileMap.prototype.at = function(x, y) {
|
|
var col = parseInt(x / this.cell_size[0])
|
|
var row = parseInt(y / this.cell_size[1])
|
|
// console.log("at() col/row: " + col + "/" + row)
|
|
return this.cells[col][row]
|
|
}
|
|
|
|
/* Returns occupants of all cells touched by 'rect' */
|
|
jaws.TileMap.prototype.atRect = function(rect) {
|
|
var objects = []
|
|
var items
|
|
var from_col = parseInt(rect.x / this.cell_size[0])
|
|
var to_col = parseInt(rect.right / this.cell_size[0])
|
|
for(var col = from_col; col <= to_col; col++) {
|
|
var from_row = parseInt(rect.y / this.cell_size[1])
|
|
var to_row = parseInt(rect.bottom / this.cell_size[1])
|
|
|
|
for(var row = from_row; row <= to_row; row++) {
|
|
this.cells[col][row].forEach( function(item, total) {
|
|
if(objects.indexOf(item) == -1) { objects.push(item) }
|
|
})
|
|
}
|
|
}
|
|
return objects
|
|
}
|
|
|
|
/* Returns all objects in tile map */
|
|
jaws.TileMap.prototype.all = function() {
|
|
var all = []
|
|
for(var col=0; col < this.size[0]; col++) {
|
|
for(var row=0; row < this.size[1]; row++) {
|
|
this.cells[col][row].forEach( function(element, total) {
|
|
all.push(element)
|
|
});
|
|
}
|
|
}
|
|
return all
|
|
}
|
|
|
|
/*
|
|
* Get objects in cell at col / row
|
|
*/
|
|
jaws.TileMap.prototype.cell = function(col, row) {
|
|
return this.cells[col][row]
|
|
}
|
|
|
|
jaws.TileMap.prototype.toString = function() { return "[TileMap " + this.size[0] + " cols, " + this.size[1] + " rows]" }
|
|
|
|
return jaws;
|
|
})(jaws || {});
|
|
|
|
// Support CommonJS require()
|
|
if(typeof module !== "undefined" && ('exports' in module)) { module.exports = jaws.TileMap } |