You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

game.js 7.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. let Vec2 = require("./vec2");
  2. function randint(min, max) {
  3. return Math.floor(Math.random() * (max - min + 1)) + min;
  4. }
  5. function random(min, max) {
  6. return Math.random() * (max - min + 1) + min;
  7. }
  8. function diff(n1, n2) {
  9. return Math.abs(n1 - n2);
  10. }
  11. function background(ctx, camera, offset) {
  12. if (!background.cache) {
  13. let cache = [];
  14. let n = 1000;
  15. for (let i = 0; i < n; ++i) {
  16. let parallax = random(5.6, 9);
  17. cache.push({
  18. x: randint(0, window.innerWidth * parallax),
  19. y: randint(0, window.innerHeight * parallax),
  20. p: parallax
  21. });
  22. }
  23. background.cache = cache;
  24. }
  25. let cx = camera.x;
  26. let cy = camera.y;
  27. let cache = background.cache;
  28. let fs = ctx.fillStyle;
  29. ctx.fillStyle = "#FFFFFF";
  30. for (let i = 0, len = cache.length; i < len; ++i) {
  31. let p = cache[i];
  32. let x = (((p.x - cx) / p.p) + offset.x) % window.innerWidth;
  33. let y = (((p.y - cy) / p.p) + offset.y) % window.innerHeight;
  34. if (x < 0)
  35. x += window.innerWidth;
  36. if (y < 0)
  37. y += window.innerHeight;
  38. ctx.beginPath();
  39. ctx.arc(x, y, 1, 0, 2*Math.PI);
  40. ctx.closePath();
  41. ctx.fill();
  42. }
  43. ctx.fillStyle = fs;
  44. }
  45. window.addEventListener("resize", () => background.cache = null);
  46. class Animation {
  47. constructor({img, x, y, width, height, nsteps, loop = false, fps = 10}) {
  48. this.img = img;
  49. this.x = x;
  50. this.y = y;
  51. this.width = width;
  52. this.height = height;
  53. this.nsteps = nsteps;
  54. this.step = 0;
  55. this.onend = function(){};
  56. let interval = setInterval(() => {
  57. this.step += 1;
  58. if (this.step > this.nsteps) {
  59. this.step = 0;
  60. if (!loop)
  61. this.onend();
  62. }
  63. }, 1000/fps);
  64. }
  65. animate(ctx) {
  66. ctx.drawImage(
  67. this.img,
  68. this.step * this.width,
  69. 0,
  70. this.width,
  71. this.height,
  72. this.x,
  73. this.y,
  74. this.width,
  75. this.height
  76. );
  77. }
  78. }
  79. class Entity {
  80. constructor(x, y, width, height, id, game) {
  81. this.pos = new Vec2(x, y);
  82. this.vel = new Vec2(0, 0);
  83. this.width = width;
  84. this.height = height;
  85. this.game = game;
  86. this.id = id;
  87. }
  88. draw(ctx) {}
  89. set(obj) {
  90. this.pos.set(obj.pos.x, obj.pos.y);
  91. this.vel.set(obj.vel.x, obj.vel.y);
  92. }
  93. update(dt) {
  94. this.pos.x += this.vel.x * dt;
  95. this.pos.y += this.vel.y * dt;
  96. }
  97. despawn() {}
  98. }
  99. let BulletImgs = {
  100. despawn: createImage("imgs/bullet_despawn.png")
  101. }
  102. class Bullet extends Entity {
  103. constructor(x: 0, y: 0, vel, id, ownerId, game) {
  104. super(x, y, 5, 5, id, game);
  105. this.imgs = BulletImgs;
  106. this.vel.set(vel.x, vel.y);
  107. this.ownerId = ownerId;
  108. if (ownerId == game.id)
  109. game.screenShake(10, 0);
  110. }
  111. draw(ctx, selfId) {
  112. if (selfId == this.ownerId) {
  113. ctx.fillStyle = "#FFFFFF";
  114. } else {
  115. ctx.fillStyle = "#FF0000";
  116. }
  117. ctx.beginPath();
  118. ctx.arc(-(this.width/2), -(this.height/2), this.width/2, 0, 2*Math.PI);
  119. ctx.closePath();
  120. ctx.fill();
  121. }
  122. set(obj) {
  123. super.set(obj);
  124. }
  125. despawn() {
  126. this.game.animate(new Animation({
  127. img: this.imgs.despawn,
  128. x: this.pos.x,
  129. y: this.pos.y
  130. }));
  131. }
  132. }
  133. let PlayerImgs = {
  134. thrust_back: createImage("imgs/player_thrust_back.png"),
  135. explosion: createImage("imgs/player_explosion.png")
  136. }
  137. class Player extends Entity {
  138. constructor(x, y, id, rot, game) {
  139. super(x, y, 25, 60, id, game);
  140. this.imgs = PlayerImgs;
  141. this.rot = rot;
  142. this.rotVel = 0;
  143. this.keys = {};
  144. this.health = 0;
  145. }
  146. draw(ctx, selfId) {
  147. let h = 255-((100-this.health) * 2)
  148. if (selfId == this.id) {
  149. ctx.fillStyle = "rgb("+h+", "+h+", "+h+")";
  150. } else {
  151. ctx.fillStyle = "rgb("+h+", 0, 0)";
  152. }
  153. ctx.rotate(this.rot);
  154. if (this.keys.up) {
  155. ctx.drawImage(
  156. this.imgs.thrust_back,
  157. -this.width,
  158. this.height/2,
  159. this.width*2,
  160. this.height*2
  161. );
  162. }
  163. ctx.beginPath();
  164. ctx.moveTo(0, -(this.height/2));
  165. ctx.lineTo(-this.width, this.height/2);
  166. ctx.lineTo(this.width, this.height/2);
  167. ctx.closePath();
  168. ctx.fill();
  169. ctx.rotate(-this.rot);
  170. //Draw pointers to far away players
  171. if (selfId == this.id) {
  172. this.game.entities.forEach((e) => {
  173. //Only draw players
  174. if (!(e instanceof Player))
  175. return;
  176. //Only draw far away players
  177. if (
  178. diff(e.pos.x, this.pos.x) < window.innerWidth / 2 &&
  179. diff(e.pos.y, this.pos.y) < window.innerHeight / 2
  180. ) {
  181. return;
  182. }
  183. let pos = e.pos.clone().sub(this.pos).normalize().scale(100);
  184. console.log(pos);
  185. ctx.fillStyle = "rgb(255, 0, 0)";
  186. ctx.beginPath();
  187. ctx.arc(pos.x, pos.y, 10, 0, 2*Math.PI);
  188. ctx.closePath();
  189. ctx.fill();
  190. });
  191. }
  192. }
  193. set(obj) {
  194. super.set(obj);
  195. let lastHealth = this.health;
  196. this.rot = obj.rot;
  197. this.rotVel = obj.rotVel;
  198. this.keys = obj.keys;
  199. this.health = obj.health;
  200. if (this.id == this.game.id && lastHealth > obj.health) {
  201. this.game.screenShake(200);
  202. }
  203. }
  204. update(dt) {
  205. super.update(dt);
  206. this.rot += this.rotVel * dt;
  207. }
  208. }
  209. function createEntity(obj, game) {
  210. if (obj.type == "player") {
  211. return new Player(obj.pos.x, obj.pos.y, obj.id, obj.rot, game);
  212. } else if (obj.type == "bullet") {
  213. return new Bullet(obj.pos.x, obj.pos.y, obj.vel, obj.id, obj.ownerId, game);
  214. } else {
  215. console.log("Unknown entity type: "+obj.type);
  216. return false;
  217. }
  218. }
  219. function createImage(url) {
  220. var img = document.createElement("img");
  221. img.src = url;
  222. return img;
  223. }
  224. export default class Game {
  225. constructor(sock, canvas) {
  226. this.sock = sock;
  227. this.canvas = canvas;
  228. this.ctx = canvas.getContext("2d");
  229. this.id = null;
  230. this.camera = new Vec2(0, 0);
  231. this.raf = null;
  232. this.prevTime = new Date().getTime();
  233. this.player = null;
  234. this.shake = 0;
  235. this.shakedec = 0.5;
  236. this.keymap = [];
  237. this.keymap[87] = "up";
  238. this.keymap[83] = "down";
  239. this.keymap[65] = "left";
  240. this.keymap[68] = "right";
  241. this.keymap[32] = "shoot";
  242. this.entities = [];
  243. this.animations = [];
  244. sock.on("ready", () => {
  245. sock.send("get_id", {}, (err, res) => {
  246. this.id = res.id;
  247. });
  248. });
  249. sock.on("set", (msg) => {
  250. if (!this.entities[msg.id]) {
  251. let ent = createEntity(msg, this);
  252. if (ent)
  253. this.entities[msg.id] = ent;
  254. } else {
  255. this.entities[msg.id].set(msg);
  256. }
  257. });
  258. sock.on("despawn", (msg) => {
  259. delete this.entities[msg.id];
  260. if (msg.id == this.id) {
  261. alert("You died.");
  262. this.stop();
  263. }
  264. });
  265. window.addEventListener("keydown", (evt) => {
  266. if (this.keymap[evt.keyCode]) {
  267. evt.preventDefault();
  268. this.sock.send("keydown", {
  269. key: this.keymap[evt.keyCode]
  270. });
  271. }
  272. });
  273. window.addEventListener("keyup", (evt) => {
  274. if (this.keymap[evt.keyCode]) {
  275. this.sock.send("keyup", {
  276. key: this.keymap[evt.keyCode]
  277. });
  278. }
  279. });
  280. this.update();
  281. }
  282. update() {
  283. let dt = new Date().getTime() - this.prevTime;
  284. this.prevTime = new Date().getTime();
  285. this.canvas.width = window.innerWidth;
  286. this.canvas.height = window.innerHeight;
  287. let player = this.entities[this.id];
  288. if (player) {
  289. this.camera.set(
  290. player.pos.x - (window.innerWidth / 2),
  291. player.pos.y - (window.innerHeight / 2)
  292. );
  293. }
  294. let shakeOffset = new Vec2(0, 0);
  295. if (this.shake > 0) {
  296. shakeOffset.set(
  297. (Math.random() - 0.5) * this.shake,
  298. (Math.random() - 0.5) * this.shake
  299. );
  300. this.shake -= dt * this.shakedec;
  301. } else {
  302. shakeOffset.set(0, 0);
  303. }
  304. let cam = this.camera.clone().add(shakeOffset);
  305. background(this.ctx, this.camera, shakeOffset);
  306. this.ctx.translate(-cam.x, -cam.y);
  307. this.entities.forEach((ent) => {
  308. this.ctx.save();
  309. this.ctx.translate(ent.pos.x, ent.pos.y);
  310. ent.draw(this.ctx, this.id);
  311. this.ctx.restore();
  312. ent.update(dt);
  313. });
  314. this.animations.forEach((a) => a.animate());
  315. this.ctx.translate(cam.x, cam.y);
  316. this.raf = window.requestAnimationFrame(this.update.bind(this));
  317. }
  318. stop() {
  319. window.cancelAnimationFrame(this.raf);
  320. }
  321. screenShake(n) {
  322. if (this.shake < n)
  323. this.shake = n;
  324. }
  325. animate(animation) {
  326. let i = this.animations.length;
  327. this.animations.add(animation);
  328. animation.onend = () => {
  329. delete this.animations[i];
  330. }
  331. }
  332. }