class Game { constructor(canvas, width, height) { this.buffer = document.createElement("canvas"); this.buffer.width = width; this.buffer.height = height; this.ctx = this.buffer.getContext("2d"); this.canvas = canvas; this.width = width; this.height = height; // override this with your data to be loaded: // // { id: "file.png" } // // Supports .png, .ogg and .json files. // // and also override dataSize with: // // this.dataSize = Object.keys(this.data).length // this.data = {}; this.dataSize = 0; // override to change keys this.controls = { 80: "pause", 83: "start", 37: "left", 38: "up", 39: "right", 40: "down", 90: "a", 88: "b" }; this.scale = 1; // will store the resouces indexed by id this.res = {}; this.resSize = 0; // override if you want to change the limit // defaults to 8 "channels" this.playLimit = 8; this.playCount = 0; // test to see if a key is down or not this.keys = { pause: false, start: false, left: false, up: false, right: false, down: false, a: false, b: false }; this.minFps = 60; this.then = -1 / 60; this.loadingError = undefined; } start() { this.canvas.style.background = "rgb(21, 21, 21)"; this.resize(); window.onresize = (ev) => { this.resize(); }; this.loader(); } // override this init() {} // override this update(dt) {} // override this draw() {} _update(dt) {} _draw() {} resize() { this.scale = Math.floor(window.innerHeight / this.height); this.canvas.width = this.width * this.scale; this.canvas.height = this.height * this.scale; } loop(now) { let dt = Math.min(1 / this.minFps, now - this.then); this._update(dt); this._draw(); this.then = now; requestAnimationFrame((now) => { this.loop(now) }); } loader() { this._draw = this.drawLoading; this.loop(0); const onError = (ev) => { console.log(ev); this.loadingError = true; }; for (const id in this.data) { if (this.data[id].indexOf(".png") != -1) { this.res[id] = new Image(); this.res[id].src = this.data[id]; this.res[id].onerror = onError this.res[id].onload = (ev) => { this.res[id].onload = null; ev.currentTarget.removeEventListener("error", onError); this.resSize += 1; }; continue; } if (this.data[id].indexOf(".ogg") != -1) { this.res[id] = new Audio(); this.res[id].src = this.data[id]; this.res[id].autoplay = false; this.res[id].addEventListener("error", onError); this.res[id].oncanplaythrough = (ev) => { this.res[id].oncanplaythrough = null; ev.currentTarget.removeEventListener("error", onError); this.resSize += 1; }; continue; } if (this.data[id].indexOf(".json") != -1) { let xhr = new window.XMLHttpRequest(); xhr.open("GET", this.data[id]); xhr.responseType = "json"; xhr.addEventListener("error", onError); xhr.onload = (ev) => { ev.currentTarget.removeEventListener("error", onError); xhr.onload = null; if (xhr.status == 200) { this.res[id] = xhr.response; this.resSize += 1; } else { console.log(ev); this.loadingError = true; } }; xhr.send(); continue; } } } // won't use drawStart/drawEnd because may render regular fonts drawLoading() { // height of the loading bar const h = 20; let ctx = this.canvas.getContext("2d"); ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); ctx.save(); if (this.loadingError == true) { ctx.fillStyle = "rgb(255, 128, 128)"; ctx.font = "caption"; ctx.fillText( "ERROR Loading Resources", Math.floor(ctx.canvas.width * 0.1), Math.floor(ctx.canvas.height / 2 - h - h / 2) ); } ctx.fillStyle = "rgb(128, 128, 128)"; ctx.fillRect(Math.floor(ctx.canvas.width * 0.1), Math.floor(ctx.canvas.height / 2) - h, ctx.canvas.width - Math.floor(ctx.canvas.width * 0.2), h); ctx.fillStyle = "rgb(255, 255, 255)"; ctx.fillRect(Math.floor(ctx.canvas.width * 0.1), Math.floor(ctx.canvas.height / 2) - h, ( Math.floor(ctx.canvas.resSize * (ctx.canvas.width - Math.floor(ctx.canvas.width * 0.2)) / ctx.canvas.dataSize) ), h); ctx.restore(); // we don't do this on update because we want // the progress bar to finish drawing if (this.resSize == this.dataSize) { console.log("Loader done"); document.addEventListener("keydown", (ev) => { this.keyDown(ev) }, false); document.addEventListener("keyup", (ev) => { this.keyUp(ev) }, false); this.init(); this._update = this.update; this._draw = () => { this.ctx.clearRect(0, 0, this.width, this.height); this.draw(); let ctx = this.canvas.getContext("2d"); ctx.save(); ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); ctx.imageSmoothingEnabled = false; ctx.scale(this.scale, this.scale); ctx.drawImage(this.buffer, 0, 0); ctx.restore(); }; } } playSnd(playable, loop, clone) { if (this.playCount < this.playLimit) { this.playCount += 1; if (clone || false) { playable = playable.cloneNode(true) } playable.onended = (ev) => { playable.onended = null; this.playCount -= 1; }; playable.loop = loop || false; playable.play(); } } keyDown(ev) { let key = this.controls[ev.keyCode]; if (key != undefined) { this.keys[key] = true; ev.preventDefault(); } } keyUp(ev) { let key = this.controls[ev.keyCode]; if (key != undefined) { this.keys[key] = false; } } }