diff options
author | Juan J. Martinez <jjm@usebox.net> | 2025-02-09 17:25:59 +0000 |
---|---|---|
committer | Juan J. Martinez <jjm@usebox.net> | 2025-02-09 17:25:59 +0000 |
commit | 6f5fa0b5240d6edd3bce72ea1549e4cd9b0ce2bc (patch) | |
tree | 44ebe6f5cfb1eb9214c5877342c7914de07f215a /js/game.js | |
download | js-twin-shooter-6f5fa0b5240d6edd3bce72ea1549e4cd9b0ce2bc.tar.gz js-twin-shooter-6f5fa0b5240d6edd3bce72ea1549e4cd9b0ce2bc.zip |
Initial import
Diffstat (limited to 'js/game.js')
-rw-r--r-- | js/game.js | 293 |
1 files changed, 293 insertions, 0 deletions
diff --git a/js/game.js b/js/game.js new file mode 100644 index 0000000..6527584 --- /dev/null +++ b/js/game.js @@ -0,0 +1,293 @@ +const floor = Math.floor; +const max = Math.max; +const min = Math.min; + +class Game { + constructor(canvas, width, height) { + this.canvas = canvas; + this.canvas.width = width; + this.canvas.height = height; + this.ctx = this.canvas.getContext("2d"); + + // 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", + 32: "start", + 37: "left", + 38: "up", + 39: "right", + 40: "down", + 87: "f_up", + 83: "f_down", + 65: "f_left", + 68: "f_right" + }; + + // 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, + f_up: false, + f_down: false, + f_left: false, + f_right: false + }; + + this.gamepads = {}; + + this.minFps = 60; + this.then = -1 / this.minFps; + + this.loadingError = undefined; + } + + start() { + this.canvas.style.background = "rgb(21, 21, 21)"; + this.resize(); + + window.onresize = (ev) => { + this.resize(); + }; + + window.addEventListener("gamepadconnected", (e) => { + console.log( + "Gamepad connected at index %d: %s. %d buttons, %d axes.", + e.gamepad.index, + e.gamepad.id, + e.gamepad.buttons.length, + e.gamepad.axes.length, + ); + this.gamepads[e.gamepad.index] = e.gamepad; + }); + window.addEventListener("gamepaddisconnected", (e) => { + console.log( + "Gamepad disconnected from index %d: %s", + e.gamepad.index, + e.gamepad.id, + ); + delete this.gamepads[e.gamepad.index]; + }); + + this.loader(); + } + + // override this + init() {} + // override this + update(dt) {} + // override this + draw() {} + + _update(dt) {} + _draw() {} + + resize() { + const scale = floor(window.innerHeight / this.canvas.height); + this.canvas.style["imageRendering"] = "pixelated"; + this.canvas.style["transformOrigin"] = "top"; + this.canvas.style["transform"] = `scale(${scale})`; + } + + loop(now) { + + Object.keys(this.gamepads).forEach((g) => { + const gp = this.gamepads[g]; + + this.keys["f_up"] = gp.buttons[2].pressed; + this.keys["f_down"] = gp.buttons[0].pressed; + this.keys["f_left"] = gp.buttons[3].pressed; + this.keys["f_right"] = gp.buttons[1].pressed; + + if (gp.buttons[14].pressed) { + this.keys["left"] = true; + this.keys["right"] = false; + } else { + if (gp.buttons[15].pressed) { + this.keys["left"] = false; + this.keys["right"] = true; + } else { + this.keys["left"] = false; + this.keys["right"] = false; + } + } + + if (gp.buttons[12].pressed) { + this.keys["up"] = true; + this.keys["down"] = false; + } else { + if (gp.buttons[13].pressed) { + this.keys["up"] = false; + this.keys["down"] = true; + } else { + this.keys["up"] = false; + this.keys["down"] = false; + } + } + }); + + let dt = min(1000 / this.minFps, now - this.then); + this._update(dt/1000); + + this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + this.ctx.save(); + this._draw(); + this.ctx.restore(); + + 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 = 6; + + if (this.loadingError == true) { + this.ctx.fillStyle = "rgb(255, 128, 128)"; + this.ctx.font = "8px monospace"; + this.ctx.fillText( + "ERROR Loading Resources", + floor(this.ctx.canvas.width * 0.1), + floor(this.ctx.canvas.height / 2 - h - h / 2) + ); + } + + this.ctx.fillStyle = "rgb(128, 128, 128)"; + this.ctx.fillRect(floor(this.ctx.canvas.width * 0.1), floor(this.ctx.canvas.height / 2) - h, this.ctx.canvas.width - floor(this.ctx.canvas.width * 0.2), h); + this.ctx.fillStyle = "rgb(255, 255, 255)"; + this.ctx.fillRect(floor(this.ctx.canvas.width * 0.1), floor(this.ctx.canvas.height / 2) - h, ( + floor(this.ctx.canvas.resSize * (this.ctx.canvas.width - floor(this.ctx.canvas.width * 0.2)) / this.ctx.canvas.dataSize) + ), h); + + // 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.draw; + } + } + + playSnd(playable, loop, clone) { + if (this.playCount < this.playLimit) { + this.playCount++; + + if (clone || false) { + playable = playable.cloneNode(true) + } + playable.onended = (ev) => { + playable.onended = null; + this.playCount--; + }; + 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; + } + } +} |