123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573 |
- let Vec2 = require("./vec2");
-
- function randint(min, max) {
- return Math.floor(Math.random() * (max - min + 1)) + min;
- }
-
- function random(min, max) {
- return Math.random() * (max - min + 1) + min;
- }
-
- function diff(n1, n2) {
- return Math.abs(n1 - n2);
- }
-
- function createImage(url) {
- var img = document.createElement("img");
- img.src = url;
- return img;
- }
-
- function background(ctx, camera, offset) {
- if (!background.cache) {
- let cache = [];
- let n = window.innerWidth / 2;
- for (let i = 0; i < n; ++i) {
- let parallax = random(5.6, 9);
- cache.push({
- x: randint(0, window.innerWidth * parallax),
- y: randint(0, window.innerHeight * parallax),
- p: parallax
- });
- }
- background.cache = cache;
- }
-
- let cx = camera.x;
- let cy = camera.y;
- let cache = background.cache;
-
- let fs = ctx.fillStyle;
- ctx.fillStyle = "#FFFFFF";
- for (let i = 0, len = cache.length; i < len; ++i) {
- let p = cache[i];
- let x = (((p.x - cx) / p.p) + offset.x) % window.innerWidth;
- let y = (((p.y - cy) / p.p) + offset.y) % window.innerHeight;
- if (x < 0)
- x += window.innerWidth;
- if (y < 0)
- y += window.innerHeight;
-
- ctx.beginPath();
- ctx.arc(x, y, 1, 0, 2*Math.PI);
- ctx.closePath();
- ctx.fill();
- }
- ctx.fillStyle = fs;
- }
-
- window.addEventListener("resize", () => background.cache = null);
-
- class Animation {
- constructor({img, x, y, width, height, dwidth, dheight, wsteps, hsteps, nsteps, loop, fps, rot, offsetX, offsetY}) {
- loop = loop || false;
- fps = fps || 30;
- nsteps = nsteps || wsteps * hsteps;
- dwidth = dwidth || width;
- dheight = dheight || height;
- rot = rot || 0;
- offsetX = offsetX || 0;
- offsetY = offsetY || 0;
-
- this.img = img;
- this.x = x;
- this.y = y;
- this.offsetX = offsetX;
- this.offsetY = offsetY;
- this.rot = rot;
- this.width = width;
- this.height = height;
- this.dwidth = dwidth;
- this.dheight = dheight;
- this.wsteps = wsteps;
- this.hsteps = hsteps;
- this.wstep = 0;
- this.hstep = 0;
- this.step = 0;
- this.nsteps = nsteps;
- this.loop = loop;
- this.visible = true;
- this.onend = function(){};
-
- let interval = setInterval(() => {
- this.step += 1;
- if (this.step >= this.nsteps) {
- this.step = 0;
- this.wstep = 0;
- this.hstep = 0;
-
- if (!this.loop) {
- clearInterval(interval);
- this.onend();
- }
-
- return;
- }
-
- this.wstep += 1;
- if (this.wstep >= this.wsteps) {
- this.wstep = 0;
- this.hstep += 1;
- }
-
- }, 1000/fps);
- }
-
- animate(ctx) {
- if (!this.visible)
- return;
-
- ctx.translate(this.x, this.y);
-
- if (this.rot)
- ctx.rotate(this.rot);
-
- ctx.drawImage(
- this.img,
- this.wstep * this.width,
- this.hstep * this.width,
- this.width,
- this.height,
- this.offsetX,
- this.offsetY,
- this.dwidth,
- this.dheight
- );
-
- if (this.rot)
- ctx.rotate(-this.rot);
- }
- }
-
- class Entity {
- constructor(x, y, width, height, id, game) {
- this.pos = new Vec2(x, y);
- this.vel = new Vec2(0, 0);
- this.width = width;
- this.height = height;
- this.game = game;
-
- this.id = id;
- }
-
- draw(ctx) {}
-
- set(obj) {
- this.pos.set(obj.pos.x, obj.pos.y);
- this.vel.set(obj.vel.x, obj.vel.y);
- }
-
- update(dt) {
- this.pos.x += this.vel.x * dt;
- this.pos.y += this.vel.y * dt;
- }
-
- despawn() {}
- }
-
- let BulletImgs = {
- despawn: createImage("imgs/bullet_despawn.png")
- };
- let BulletSounds = {
- spawn: "sounds/bullet_spawn.wav",
- despawn: "sounds/bullet_despawn.wav"
- };
-
- class Bullet extends Entity {
- constructor(x, y, vel, id, ownerId, game) {
- super(x, y, 5, 5, id, game);
- this.vel.set(vel.x, vel.y);
- this.ownerId = ownerId;
-
- game.playSound(BulletSounds.spawn, this.pos);
- }
-
- draw(ctx, selfId) {
- if (selfId == this.ownerId) {
- ctx.fillStyle = "#FFFFFF";
- } else {
- ctx.fillStyle = "#FF0000";
- }
-
- ctx.beginPath();
- ctx.arc(-(this.width/2), -(this.height/2), this.width/2, 0, 2*Math.PI);
- ctx.closePath();
- ctx.fill();
- }
-
- set(obj) {
- super.set(obj);
- }
-
- despawn() {
- this.game.animate(new Animation({
- img: BulletImgs.despawn,
- x: this.pos.x,
- y: this.pos.y,
- width: 64,
- height: 64,
- dwidth: 16,
- dheight: 16,
- wsteps: 5,
- hsteps: 5
- }));
- this.game.playSound(BulletSounds.despawn, this.pos);
- }
- }
-
- let PlayerImgs = {
- thrust_back: createImage("imgs/player_thrust_back.png"),
- despawn: createImage("imgs/player_despawn.png")
- };
- let PlayerSounds = {
- despawn: "sounds/player_despawn.wav",
- thrust: "sounds/player_thrust.wav"
- };
-
- class Player extends Entity {
- constructor(x, y, id, rot, name, game) {
- super(x, y, 25, 60, id, game);
- this.rot = rot;
- this.rotVel = 0;
- this.keys = {};
- this.health = 0;
- this.name = name;
-
- this.thrustAnim = new Animation({
- img: PlayerImgs.thrust_back,
- x: this.pos.x,
- y: this.pos.y,
- width: 128,
- height: 128,
- dwidth: 64,
- dheight: 64,
- wsteps: 4,
- hsteps: 4,
- fps: 60,
- loop: true
- });
- this.thrustAnim.visible = false;
- game.animate(this.thrustAnim);
-
- if (this.id === game.id) {
- this.thrustSound = document.createElement("audio");
- this.thrustSound.src = PlayerSounds.thrust;
- this.thrustSound.loop = true;
- this.thrustSound.play();
- this.thrustSound.volume = 0;
- }
- }
-
- draw(ctx, selfId) {
- let h = Math.round((this.health * 2.4) + 15);
-
- if (selfId == this.id) {
- ctx.fillStyle = "rgb("+h+", "+h+", "+h+")";
- } else {
- ctx.fillStyle = "rgb("+h+", 0, 0)";
- }
-
- ctx.rotate(this.rot);
-
- if (this.keys.up) {
- this.thrustAnim.rot = this.rot;
- this.thrustAnim.x = this.pos.x;
- this.thrustAnim.y = this.pos.y;
- this.thrustAnim.offsetX = -this.thrustAnim.dwidth/2;
- this.thrustAnim.offsetY = this.thrustAnim.dwidth/2;
-
- if (this.keys.sprint) {
- this.thrustAnim.dheight = 100;
- if (this.id == selfId)
- this.game.screenShake(10);
- } else {
- this.thrustAnim.dheight = 64;
- }
- }
-
- ctx.beginPath();
- ctx.moveTo(0, -(this.height/2));
- ctx.lineTo(-this.width, this.height/2);
- ctx.lineTo(this.width, this.height/2);
- ctx.closePath();
- ctx.fill();
-
- {
-
- let h = (this.height/2) + 10;
- if (this.thrustAnim.visible)
- h += this.thrustAnim.dheight;
-
- }
-
- ctx.rotate(-this.rot);
-
- ctx.textAlign = "center";
- ctx.textBaseline = "middle";
- ctx.font = "bold 30px Arial";
- ctx.strokeStyle = "#000000";
- ctx.fillStyle = "#ffffff";
- ctx.lineWidth = 2;
- ctx.fillText(this.name, 0, 0);
- ctx.strokeText(this.name, 0, 0);
-
- //Draw pointers to far away players
- if (selfId == this.id) {
- this.game.entities.forEach((e) => {
-
- //Only draw players
- if (!(e instanceof Player))
- return;
-
- //Only draw far away players
- if (
- diff(e.pos.x, this.pos.x) < window.innerWidth / 2 &&
- diff(e.pos.y, this.pos.y) < window.innerHeight / 2
- ) {
- return;
- }
-
- let pos = e.pos.clone().sub(this.pos).normalize().scale(130);
-
- ctx.fillStyle = "rgb(255, 0, 0)";
- ctx.beginPath();
- ctx.arc(pos.x, pos.y, 5, 0, 2*Math.PI);
- ctx.closePath();
- ctx.fill();
- });
- }
- }
-
- set(obj) {
- super.set(obj);
- let lastHealth = this.health;
-
- this.rot = obj.rot;
- this.rotVel = obj.rotVel;
- this.keys = obj.keys;
- this.health = obj.health;
-
- if (obj.name)
- this.name = obj.name;
-
- if (this.id == this.game.id && lastHealth > obj.health) {
- this.game.screenShake(50);
- }
- }
-
- update(dt) {
- super.update(dt);
- this.rot += this.rotVel * dt;
-
- if (this.keys.up) {
- this.thrustAnim.visible = true;
-
- if (this.keys.sprint && this.thrustSound)
- this.thrustSound.volume = 0.6;
- else if (this.id === this.game.id)
- this.thrustSound.volume = 0.3;
- } else {
- this.thrustAnim.visible = false;
-
- if (this.thrustSound)
- this.thrustSound.volume = 0;
- }
- }
-
- despawn() {
- this.game.animate(new Animation({
- img: PlayerImgs.despawn,
- x: this.pos.x,
- y: this.pos.y,
- width: 64,
- height: 64,
- dwidth: 256,
- dheight: 256,
- offsetX: -128,
- offsetY: -128,
- wsteps: 5,
- hsteps: 5
- }));
- this.thrustAnim.visible = false;
- this.thrustAnim.loop = false;
- this.game.playSound(PlayerSounds.despawn, this.pos);
- if (this.thrustSound)
- this.thrustSound.stop();
- }
- }
-
- function createEntity(obj, game) {
- if (obj.type == "player") {
- return new Player(obj.pos.x, obj.pos.y, obj.id, obj.rot, obj.name, game);
- } else if (obj.type == "bullet") {
- return new Bullet(obj.pos.x, obj.pos.y, obj.vel, obj.id, obj.ownerId, game);
- } else {
- throw new Error("Unknown entity type: "+obj.type);
- return false;
- }
- }
-
- export default class Game {
- constructor(sock, canvas, name) {
- this.sock = sock;
- this.canvas = canvas;
- this.ctx = canvas.getContext("2d");
- this.id = null;
- this.camera = new Vec2(0, 0);
- this.raf = null;
- this.prevTime = new Date().getTime();
- this.player = null;
- this.onloss = function(){};
-
- this.shake = 0;
- this.shakedec = 0.5;
-
- this.keymap = [];
- this.keymap[87] = "up";
- this.keymap[83] = "down";
- this.keymap[65] = "left";
- this.keymap[68] = "right";
- this.keymap[32] = "shoot";
- this.keymap[16] = "sprint";
-
- this.entities = [];
- this.animations = [];
-
- sock.on("ready", () => {
- sock.send("get_id", {
- name: name
- }, (err, res) => {
- this.id = res.id;
- });
- });
-
- sock.on("set", (msg) => {
- msg.forEach((m) => {
- if (this.entities[m.id]) {
- this.entities[m.id].set(m);
- } else {
- if (!m.type)
- return;
-
- let ent = createEntity(m, this);
- this.entities[m.id] = ent;
- }
- });
- });
-
- sock.on("despawn", (msg) => {
- console.log(msg);
- if (!this.entities[msg.id])
- return;
-
- this.entities[msg.id].despawn();
- delete this.entities[msg.id];
- if (msg.id == this.id) {
- this.screenShake(400);
- setTimeout(() => {
- this.stop();
- this.onloss();
- }, 1200);
- }
- });
-
- window.addEventListener("keydown", (evt) => {
- if (this.keymap[evt.keyCode]) {
- evt.preventDefault();
- evt.stopPropagation();
- this.sock.send("keydown", {
- key: this.keymap[evt.keyCode]
- });
- }
- });
-
- window.addEventListener("keyup", (evt) => {
- if (this.keymap[evt.keyCode]) {
- this.sock.send("keyup", {
- key: this.keymap[evt.keyCode]
- });
- }
- });
-
- this.update();
- }
-
- update() {
- let dt = new Date().getTime() - this.prevTime;
- this.prevTime = new Date().getTime();
-
- this.canvas.width = window.innerWidth;
- this.canvas.height = window.innerHeight;
-
- let player = this.entities[this.id];
- if (player) {
- this.camera.set(
- player.pos.x - (window.innerWidth / 2),
- player.pos.y - (window.innerHeight / 2)
- );
- }
-
- let shakeOffset = new Vec2(0, 0);
- if (this.shake > 0) {
- shakeOffset.set(
- (Math.random() - 0.5) * this.shake,
- (Math.random() - 0.5) * this.shake
- );
- this.shake -= dt * this.shakedec;
- } else {
- shakeOffset.set(0, 0);
- }
-
- let cam = this.camera.clone().add(shakeOffset);
-
- background(this.ctx, this.camera, shakeOffset);
-
- this.ctx.translate(-cam.x, -cam.y);
- this.entities.forEach((ent) => {
- this.ctx.save();
- this.ctx.translate(ent.pos.x, ent.pos.y);
- ent.draw(this.ctx, this.id);
- this.ctx.restore();
-
- ent.update(dt);
- });
- this.animations.forEach((a) => {
- this.ctx.save();
- a.animate(this.ctx);
- this.ctx.restore();
- });
- this.ctx.translate(cam.x, cam.y);
-
- this.raf = window.requestAnimationFrame(this.update.bind(this));
- }
-
- stop() {
- window.cancelAnimationFrame(this.raf);
- }
-
- screenShake(n) {
- if (this.shake < n)
- this.shake = n;
- }
-
- animate(animation) {
- let i = this.animations.length;
- this.animations.push(animation);
- animation.onend = () => {
- delete this.animations[i];
- };
- }
-
- playSound(url, pos) {
- let player = this.entities[this.id];
- if (!player)
- return;
-
- let dist = player.pos.clone().sub(pos);
-
- let sound = document.createElement("audio");
- sound.src = url;
- sound.volume = Math.max(1 - (dist.length() / 1000), 0);
- sound.play();
- }
- }
|