[READ-ONLY] Mirror of https://github.com/flo-bit/shadow-shmup. Fun and casual top-down, roguelike shoot 'em up browsergame with shadow elements and colorful neon effects flo-bit.dev/shadow-shmup/
browsergame pixijs rapier2d roguelike shoot-em-up typescript webgame
0

Configure Feed

Select the types of activity you want to include in your feed.

commit

Florian (Jul 29, 2024, 9:19 PM +0200) bf8d58d5 f24c28a4

+476 -134
+1
Readme.md
··· 49 49 - [ ] stats 50 50 - [ ] count kills, deaths, damage taken, damage dealt 51 51 - [ ] show stats at end of game 52 + - [ ] tutorial 52 53 - weapons 53 54 - recoil on player 54 55 - [x] impulse on bullet impact (done for enemies)
+16 -3
src/app.ts
··· 23 23 import { createNoiseSprite } from './helper.js'; 24 24 import { addUpgradeOption } from './upgrades.js'; 25 25 import { UpgradeManager } from './upgrade-manager.js'; 26 + import { BallWeapon } from './weapons/ball.js'; 27 + import { Weapon } from './weapons/weapon.js'; 26 28 27 29 export default class Game { 28 30 container: PIXI.Container; ··· 305 307 let projectile: Projectile | undefined; 306 308 let player: Player | undefined; 307 309 let item: Item | undefined; 310 + let weapon: Weapon | undefined; 308 311 309 312 if (userData1 instanceof Enemy) enemy = userData1; 310 313 if (userData2 instanceof Enemy) enemy = userData2; ··· 318 321 if (userData1 instanceof Item) item = userData1; 319 322 if (userData2 instanceof Item) item = userData2; 320 323 324 + if (userData1 instanceof Weapon) weapon = userData1; 325 + if (userData2 instanceof Weapon) weapon = userData2; 326 + 321 327 if (enemy && projectile) { 322 328 this.spawnParticles(projectile.shape.x, projectile.shape.y, 10, projectile.color); 323 329 ··· 328 334 if (projectile.piercing < 0) projectile.destroy(); 329 335 } 330 336 337 + if (enemy && weapon) { 338 + this.spawnParticles(weapon.x, weapon.y, 10, weapon.color); 339 + 340 + enemy.takeDamage(weapon.damage); 341 + } 342 + 331 343 if (enemy && player && enemy.hitPlayer) { 332 344 enemy.hitPlayer(player); 333 345 } 334 346 335 - if (player && projectile) { 347 + if (player && !player.dead && projectile && !this.invincible) { 336 348 this.spawnParticles(projectile.shape.x, projectile.shape.y, 10, projectile.color); 337 349 338 350 player.takeDamage(projectile.damage); ··· 390 402 391 403 let playerManager = this.playerManager; 392 404 405 + /* 393 406 if (playerManager) { 394 407 let timeSinceLastDamage = playerManager.smallestTimeSinceLastDamage(); 395 408 if (timeSinceLastDamage < 150) { ··· 397 410 } else { 398 411 this.container.alpha = 1; 399 412 } 400 - } 413 + }*/ 401 414 402 415 // if (Math.random() < deltaTime * 0.006) { 403 416 // this.enemyManager?.addEnemy(); 404 417 // } 405 - this.waveManager?.update(deltaTime); 418 + if (this.playing) this.waveManager?.update(deltaTime); 406 419 407 420 const wave = this.waveManager?.getCurrentWave(); 408 421 const waveUI = document.getElementById('wave');
+23 -4
src/controls.ts
··· 7 7 8 8 deltaTouch: { x: number; y: number } | undefined = undefined; 9 9 10 + deadzone = 0.1; 11 + 10 12 constructor(game: Game) { 11 13 this.game = game; 12 14 ··· 84 86 let secondGamepad = navigator.getGamepads()[1]; 85 87 86 88 if (num === 0 && this.deltaTouch) return this.deltaTouch.x; 87 - if (num === 0 && gamepad) return gamepad.axes[0]; 88 - if (num === 1 && secondGamepad) return secondGamepad.axes[0]; 89 + if (num === 0 && gamepad) 90 + return Math.abs(gamepad.axes[0]) < this.deadzone ? 0 : gamepad.axes[0]; 91 + if (num === 1 && secondGamepad) 92 + return Math.abs(secondGamepad.axes[0]) < this.deadzone ? 0 : secondGamepad.axes[0]; 89 93 90 94 return this.left(num) + this.right(num); 91 95 } ··· 94 98 let secondGamepad = navigator.getGamepads()[1]; 95 99 96 100 if (num === 0 && this.deltaTouch) return this.deltaTouch.y; 97 - if (num === 0 && gamepad) return gamepad.axes[1]; 98 - if (num === 1 && secondGamepad) return secondGamepad.axes[1]; 101 + if (num === 0 && gamepad) 102 + return Math.abs(gamepad.axes[1]) < this.deadzone ? 0 : gamepad.axes[1]; 103 + if (num === 1 && secondGamepad) 104 + return Math.abs(secondGamepad.axes[1]) < this.deadzone ? 0 : secondGamepad.axes[1]; 99 105 100 106 return this.up(num) + this.down(num); 101 107 } ··· 131 137 if (num === 1) return this.keys['arrowdown'] ? 1 : 0; 132 138 133 139 return 0; 140 + } 141 + 142 + rumble(num: number = 0, strength: number = 0.5) { 143 + let gamepad = navigator.getGamepads()[num]; 144 + 145 + if (!gamepad) return; 146 + 147 + strength = Math.min(1, Math.max(0, strength)); 148 + gamepad.vibrationActuator?.playEffect('dual-rumble', { 149 + duration: 100, 150 + strongMagnitude: strength, 151 + weakMagnitude: strength 152 + }); 134 153 } 135 154 }
+34 -4
src/enemy-manager.ts
··· 1 + import { Vector2 } from '@dimforge/rapier2d'; 1 2 import Game from './app.js'; 2 3 import Enemy, { PentagonEnemy, SphereEnemy, TriangleEnemy } from './enemy.js'; 3 4 ··· 7 8 enemies: Enemy[] = []; 8 9 9 10 enemyTypes = [SphereEnemy, TriangleEnemy, PentagonEnemy]; 11 + 12 + point: Vector2 = new Vector2(0, 0); 10 13 11 14 constructor(game: Game) { 12 15 this.game = game; ··· 31 34 }); 32 35 } 33 36 34 - getClosestEnemy(position: { x: number; y: number }, maxDist: number): Enemy | undefined { 35 - let point = { x: position.x, y: -position.y }; 37 + getClosestEnemy( 38 + position: { x: number; y: number }, 39 + maxDist: number, 40 + output: 41 + | { 42 + enemy: Enemy | undefined; 43 + distance: number; 44 + } 45 + | undefined = undefined 46 + ): Enemy | undefined { 47 + this.point.x = position.x; 48 + this.point.y = -position.y; 49 + 36 50 let solid = true; 37 51 38 - let proj = this.game.world.projectPoint(point, solid, undefined, 0x00020002); 52 + let proj = this.game.world.projectPoint(this.point, solid, undefined, 0x00020002); 39 53 40 54 if (proj) { 41 55 let enemy = proj.collider.parent()?.userData as Enemy; 42 56 43 - if (!enemy) return; 57 + if (!enemy) { 58 + if (output) { 59 + output.enemy = undefined; 60 + output.distance = 0; 61 + } 62 + return; 63 + } 44 64 45 65 // check if enemy is within max distance 46 66 const dist = Math.sqrt((enemy.x - position.x) ** 2 + (enemy.y - position.y) ** 2); 47 67 48 68 if (dist < maxDist) { 69 + if (output) { 70 + output.enemy = enemy; 71 + output.distance = dist; 72 + } 49 73 return enemy; 50 74 } 51 75 } 76 + 77 + if (output) { 78 + output.enemy = undefined; 79 + output.distance = 0; 80 + } 81 + return undefined; 52 82 } 53 83 54 84 killAll(dropItems: boolean = false) {
+32 -61
src/enemy.ts
··· 4 4 import { Vector2, type RigidBody } from '@dimforge/rapier2d'; 5 5 import Eye from './eye'; 6 6 import Player from './player'; 7 - import { Weapon } from './weapon'; 7 + import { GunWeapon } from './weapons/gun'; 8 8 import { Projectile } from './projectile'; 9 9 import { Light } from './light'; 10 10 ··· 44 44 45 45 eyes?: PIXI.Container; 46 46 47 - color: number = 0xfb923c; 47 + color: number = 0xd946ef; 48 48 49 49 type: number = -1; 50 50 ··· 64 64 65 65 this.createShape(); 66 66 67 - this.maxHealth = 100; 67 + this.maxHealth = 10; 68 68 this.health = this.maxHealth; 69 69 70 70 if (game.debug) this.createHealthBar(); ··· 201 201 } 202 202 } 203 203 204 + updateEyes(deltaTime: number, nearestPlayer: Player, distance: number) { 205 + if (distance < nearestPlayer.viewDistance * 1.5) { 206 + let alpha = Math.min(1, 1 - distance / (nearestPlayer.viewDistance * 1.5)); 207 + this.leftEye?.update(deltaTime, alpha); 208 + this.rightEye?.update(deltaTime, alpha); 209 + } else { 210 + this.leftEye?.update(deltaTime, 0); 211 + this.rightEye?.update(deltaTime, 0); 212 + } 213 + } 214 + 204 215 updateVisuals( 205 216 deltaTime: number, 206 217 nearestPlayer: Player, 207 - nearestLight: Light | undefined, 208 218 dx: number, 209 219 dy: number, 210 220 distance: number ··· 214 224 this.leftEye?.move(angle); 215 225 this.rightEye?.move(angle); 216 226 217 - if (nearestLight) { 218 - const light_distance = Math.hypot(nearestLight.x - this.x, nearestLight.y - this.y); 219 - 220 - let alpha = Math.min(1, 1 - light_distance / (nearestLight.scale * 350)); 221 - this.leftEye?.update(deltaTime, alpha); 222 - this.rightEye?.update(deltaTime, alpha); 223 - } else { 224 - this.leftEye?.update(deltaTime, 0); 225 - this.rightEye?.update(deltaTime, 0); 226 - } 227 + this.updateEyes(deltaTime, nearestPlayer, distance); 227 228 228 229 this.enemyContainer.position.set(this.x, this.y); 229 230 } ··· 234 235 235 236 update(deltaTime: number) { 236 237 let player = this.game.playerManager?.getClosestPlayer(this.position); 237 - let light = this.game.lightManager?.getClosestLight(this.position); 238 + //let light = this.game.lightManager?.getClosestLight(this.position); 238 239 239 240 if (this.destroyed || !player) return; 240 241 ··· 244 245 245 246 this.move(deltaTime, player, dx, dy, distance); 246 247 247 - this.updateVisuals(deltaTime, player, light, dx, dy, distance); 248 + this.updateVisuals(deltaTime, player, dx, dy, distance); 248 249 249 250 this.attack(deltaTime, player, dx, dy, distance); 250 251 } ··· 276 277 } 277 278 278 279 export class SphereEnemy extends Enemy { 279 - weapon: Weapon; 280 + weapon: GunWeapon; 280 281 281 282 indicator?: PIXI.Graphics; 282 283 ··· 292 293 293 294 this.type = 1; 294 295 295 - this.weapon = new Weapon(this.game, { 296 + this.weapon = new GunWeapon(this.game, { 296 297 color: this.color, 297 298 collisionGroups: 0x00100001, 298 299 projectileSpeed: 0.3, ··· 305 306 306 307 this.createIndicator(); 307 308 308 - this.speed = 500; 309 + this.speed = 300; 309 310 } 310 311 311 312 async createIndicator() { ··· 366 367 super(game); 367 368 368 369 this.type = 0; 369 - this.speed = 2000; 370 + this.speed = 600; 370 371 371 372 let position = { x: this.x, y: this.y }; 372 373 ··· 386 387 } 387 388 388 389 createEyes() { 389 - this.color = 0x38bdf8; 390 + this.color = 0x0ea5e9; 390 391 super.createEyes(); 391 392 } 392 393 ··· 424 425 updateVisuals( 425 426 deltaTime: number, 426 427 nearestPlayer: Player, 427 - nearestLight: Light | undefined, 428 428 dx: number, 429 429 dy: number, 430 430 distance: number ··· 436 436 this.leftEye?.move(Math.PI / 2); 437 437 this.rightEye?.move(Math.PI / 2); 438 438 439 - if (nearestLight) { 440 - const light_distance = Math.hypot(nearestLight.x - this.x, nearestLight.y - this.y); 441 - 442 - let alpha = Math.min(1, 1 - light_distance / (nearestLight.scale * 350)); 443 - this.leftEye?.update(deltaTime, alpha); 444 - this.rightEye?.update(deltaTime, alpha); 445 - } else { 446 - this.leftEye?.update(deltaTime, 0); 447 - this.rightEye?.update(deltaTime, 0); 448 - } 439 + this.updateEyes(deltaTime, nearestPlayer, distance); 449 440 450 441 this.rotation = angle - Math.PI / 2; 451 442 ··· 476 467 } 477 468 478 469 export class PentagonEnemy extends Enemy { 479 - weapon: Weapon; 470 + weapon: GunWeapon; 480 471 481 472 constructor(game: Game) { 482 473 super(game); 483 474 484 475 this.type = 2; 485 476 486 - this.weapon = new Weapon(this.game, { 477 + this.weapon = new GunWeapon(this.game, { 487 478 color: this.color, 488 479 collisionGroups: 0x00100001, 489 - projectileSpeed: 0.2, 480 + projectileSpeed: 0.15, 490 481 fireRate: 2000, 491 482 projectileSize: 12, 492 483 damage: 10, 493 484 lifetime: 8000 494 485 }); 495 486 496 - this.speed = 1000; 487 + this.speed = 900; 497 488 } 498 489 499 490 createEyes() { 500 - this.color = 0x4ade80; 491 + this.color = 0x6366f1; 501 492 super.createEyes(); 502 493 } 503 494 ··· 544 535 updateVisuals( 545 536 deltaTime: number, 546 537 nearestPlayer: Player, 547 - nearestLight: Light | undefined, 548 538 dx: number, 549 539 dy: number, 550 540 distance: number ··· 558 548 this.leftEye?.move(-this.rotation + angle); 559 549 this.rightEye?.move(-this.rotation + angle); 560 550 561 - if (nearestLight) { 562 - const light_distance = Math.hypot(nearestLight.x - this.x, nearestLight.y - this.y); 563 - 564 - let alpha = Math.min(1, 1 - light_distance / (nearestLight.scale * 350)); 565 - this.leftEye?.update(deltaTime, alpha); 566 - this.rightEye?.update(deltaTime, alpha); 567 - } else { 568 - this.leftEye?.update(deltaTime, 0); 569 - this.rightEye?.update(deltaTime, 0); 570 - } 551 + this.updateEyes(deltaTime, nearestPlayer, distance); 571 552 572 553 this.enemyContainer.position.set(this.x, this.y); 573 554 } ··· 582 563 } 583 564 584 565 export class CrossEnemy extends Enemy { 585 - weapon: Weapon; 566 + weapon: GunWeapon; 586 567 587 568 shootAngle: number = 0; 588 569 ··· 591 572 592 573 this.type = 4; 593 574 594 - this.weapon = new Weapon(this.game, { 575 + this.weapon = new GunWeapon(this.game, { 595 576 color: this.color, 596 577 collisionGroups: 0x00100001, 597 578 projectileSpeed: 0.2, ··· 699 680 updateVisuals( 700 681 deltaTime: number, 701 682 nearestPlayer: Player, 702 - nearestLight: Light | undefined, 703 683 dx: number, 704 684 dy: number, 705 685 distance: number ··· 713 693 this.leftEye?.move(-this.rotation + angle); 714 694 this.rightEye?.move(-this.rotation + angle); 715 695 716 - if (nearestLight) { 717 - const light_distance = Math.hypot(nearestLight.x - this.x, nearestLight.y - this.y); 718 - 719 - let alpha = Math.min(1, 1 - light_distance / (nearestLight.scale * 350)); 720 - this.leftEye?.update(deltaTime, alpha); 721 - this.rightEye?.update(deltaTime, alpha); 722 - } else { 723 - this.leftEye?.update(deltaTime, 0); 724 - this.rightEye?.update(deltaTime, 0); 725 - } 696 + this.updateEyes(deltaTime, nearestPlayer, distance); 726 697 727 698 this.enemyContainer.position.set(this.x, this.y); 728 699 }
+2 -12
src/item.ts
··· 43 43 44 44 this.type = options.type; 45 45 46 - this.lifetime = options.lifetime ?? 20000; 46 + this.lifetime = options.lifetime ?? 30000; 47 47 48 48 this.shape = new PIXI.Graphics().circle(0, 0, this.size / 2).fill(this.color); 49 49 ··· 146 146 147 147 // move towards player 148 148 if (dist < 80) { 149 - let speed = 0.3; 149 + let speed = 0.4; 150 150 this.x += (dx / dist) * speed * deltaTime; 151 151 this.y += (dy / dist) * speed * deltaTime; 152 152 } 153 153 } 154 - /* 155 - let closestPlayer = this.game.lightManager?.getClosestLight({ 156 - x: this.shape.x, 157 - y: this.shape.y 158 - }); 159 - 160 - if (closestPlayer) { 161 - let dist = Math.hypot(closestPlayer.x - this.shape.x, closestPlayer.y - this.shape.y); 162 - this.shape.alpha = 1 - dist / (closestPlayer.scale * 300); 163 - }*/ 164 154 165 155 this.shape.scale.set(1 - this.life / this.lifetime + 0.2); 166 156 }
+29 -19
src/light-manager.ts
··· 25 25 26 26 getClosestLight( 27 27 position: { x: number; y: number }, 28 - maxDist: number | undefined = undefined 28 + maxDist: number | undefined = undefined, 29 + output: 30 + | { 31 + light: Light | undefined; 32 + distance: number; 33 + } 34 + | undefined = undefined 29 35 ): Light | undefined { 30 36 position.y = -position.y; 31 37 let solid = true; ··· 35 41 if (proj) { 36 42 let light = proj.collider.parent()?.userData as Light; 37 43 38 - return light; 39 - } 40 - 41 - // let closestLight: Light | undefined; 42 - // let closestDistance = Infinity; 44 + if (!light) { 45 + if (output) { 46 + output.light = undefined; 47 + output.distance = 0; 48 + } 49 + return; 50 + } 43 51 44 - // for (let light of this.lights) { 45 - // if (light.destroyed) continue; 46 - // console.log(light); 47 - // const distance = Math.hypot(light.x - position.x, light.y - position.y); 48 - // if (distance < closestDistance) { 49 - // closestDistance = distance; 50 - // closestLight = light; 51 - // } 52 - // } 52 + // check if light is within max distance 53 + const dist = Math.hypot(light.x - position.x, light.y - position.y); 53 54 54 - // if (!maxDist || closestDistance < maxDist) { 55 - // return closestLight; 56 - // } 55 + if (!maxDist || dist < maxDist) { 56 + if (output) { 57 + output.light = light; 58 + output.distance = dist; 59 + } 60 + return light; 61 + } 62 + } 57 63 58 - // return undefined; 64 + if (output) { 65 + output.light = undefined; 66 + output.distance = 0; 67 + } 68 + return undefined; 59 69 } 60 70 }
+8 -3
src/light.ts
··· 156 156 157 157 let firstPoint; 158 158 159 + let hitPoint = { x: 0, y: 0 }; 160 + 159 161 for (let i = 0; i < rays; i++) { 160 162 const angle = i * angleStep; 161 163 ··· 166 168 167 169 const hit = this.game.world.castRay(ray, rayLength, true, undefined, this.collisionGroups); 168 170 169 - let hitPoint = hit 170 - ? ray.pointAt(hit.toi) 171 - : { x: ray.dir.x * rayLength, y: ray.dir.y * rayLength }; 171 + if (hit) { 172 + hitPoint = ray.pointAt(hit.toi); 173 + } else { 174 + hitPoint.x = ray.dir.x * rayLength; 175 + hitPoint.y = ray.dir.y * rayLength; 176 + } 172 177 173 178 let x = hitPoint.x - this.x; 174 179 let y = -hitPoint.y - this.y;
+22 -3
src/player-manager.ts
··· 76 76 77 77 getClosestPlayer( 78 78 position: { x: number; y: number }, 79 - maxDist: number | undefined = undefined 79 + maxDist: number | undefined = undefined, 80 + output: { player: Player | undefined; distance: number } | undefined = undefined 80 81 ): Player | undefined { 81 82 if (this.players.length === 1) { 82 83 const player = this.players[0]; 83 - const dist = (player.x - position.x) ** 2 + (player.y - position.y) ** 2; 84 - if ((!maxDist || dist < maxDist ** 2) && !player.dead) return player; 84 + const dist = Math.hypot(player.x - position.x, player.y - position.y); 85 + if ((!maxDist || dist < maxDist) && !player.dead) { 86 + if (output) { 87 + output.player = player; 88 + output.distance = dist; 89 + } 90 + 91 + return player; 92 + } 93 + 94 + if (output) { 95 + output.player = undefined; 96 + output.distance = 0; 97 + } 98 + 99 + return undefined; 85 100 } 86 101 87 102 // go through all players and find the closest one ··· 98 113 } 99 114 100 115 if (!maxDist || closestDist < maxDist) { 116 + if (output) { 117 + output.player = closestPlayer; 118 + output.distance = closestDist; 119 + } 101 120 return closestPlayer; 102 121 } 103 122
+45 -17
src/player.ts
··· 1 - import { Weapon } from './weapon.js'; 1 + import { GunWeapon } from './weapons/gun.js'; 2 2 import * as PIXI from 'pixi.js'; 3 3 4 4 import Game from './app'; ··· 7 7 import Eye from './eye.js'; 8 8 import { Light } from './light.js'; 9 9 import { blendColors } from './helper.js'; 10 + import { BallWeapon } from './weapons/ball.js'; 11 + import { Weapon } from './weapons/weapon.js'; 12 + import { Knife } from './weapons/knife.js'; 13 + import { BurstWeapon } from './weapons/burst.js'; 10 14 11 15 /** 12 16 * Player class ··· 30 34 31 35 shape: PIXI.Graphics; 32 36 33 - weapon: Weapon; 37 + weapon: GunWeapon; 34 38 35 39 isPlayer: true; 36 40 ··· 55 59 56 60 items: number[] = [0, 0, 0, 0]; 57 61 62 + weapons: Weapon[] = []; 63 + 58 64 constructor(game: Game, num: number) { 59 65 this.game = game; 60 66 this.num = num; ··· 82 88 83 89 this.createHealthBar(); 84 90 85 - this.speed = 30000; 91 + this.speed = 20000; 86 92 this.shape = shape; 87 93 88 - this.weapon = new Weapon(this.game, { color: this.color, lifetime: 2000, piercing: 1 }); 94 + this.weapon = new GunWeapon(this.game, { 95 + color: this.color, 96 + lifetime: 2000, 97 + piercing: 1, 98 + fireRate: 1000 99 + }); 89 100 90 101 this.isPlayer = true; 91 102 ··· 94 105 95 106 this.x = 0; 96 107 this.y = 0; 108 + 109 + for (let i = 0; i < 5; i++) { 110 + let ball = new BallWeapon(this.game, this.color); 111 + ball.distance = 100 + i * 50; 112 + ball.angle = (Math.PI * 2 * i) / 20; 113 + ball.speed *= i * 0.1 + 2; 114 + this.weapons.push(ball); 115 + 116 + let knife = new Knife(this.game, this.color); 117 + knife.distance = 200; 118 + knife.angle = (Math.PI * 2 * i) / 5; 119 + this.weapons.push(knife); 120 + } 121 + this.weapons.push(new BurstWeapon(this.game, this.color)); 122 + 123 + //this.weapons = []; 124 + //this.weapons = [new BallWeapon(this.game, this.color), new Knife(this.game, this.color)]; 97 125 } 98 126 99 127 createHealthBar() { ··· 166 194 let dx = this.game.controls.x(this.num), 167 195 dy = this.game.controls.y(this.num); 168 196 169 - console.log(dx, dy); 170 - 171 197 let dist = Math.hypot(dx, dy); 172 198 173 199 if (dist > 1) { ··· 175 201 dy /= dist; 176 202 } 177 203 178 - // if (this.game.controls.up(this.num)) dy -= 1; 179 - // if (this.game.controls.down(this.num)) dy += 1; 180 - // if (this.game.controls.left(this.num)) dx -= 1; 181 - // if (this.game.controls.right(this.num)) dx += 1; 182 - 183 - // Normalize diagonal movement 184 - // if (dx !== 0 && dy !== 0) { 185 - // dx *= Math.SQRT1_2; 186 - // dy *= Math.SQRT1_2; 187 - // } 188 - 189 204 let mult = deltaTime * 120 * 0.001; 190 205 if (dx || dy) 191 206 this.rigidBody?.applyImpulse( 192 207 { x: dx * this.speed * mult, y: -dy * this.speed * mult }, 193 208 true 194 209 ); 210 + } 211 + 212 + for (let weapon of this.weapons) { 213 + weapon.update(deltaTime, this.x, this.y); 195 214 } 196 215 197 216 if (this.dead && this.respawnTime > 0) { ··· 249 268 } 250 269 251 270 takeDamage(amount: number) { 271 + if (this.dead) return; 272 + 252 273 this.health -= amount; 274 + 275 + this.game.controls.rumble(this.num, (amount / this.maxHealth) * 50); 276 + 253 277 if (this.health < 0) { 254 278 this.health = 0; 255 279 this.dead = true; ··· 264 288 if (this.rigidBody) this.game.world.removeRigidBody(this.rigidBody); 265 289 this.playerContainer.destroy(); 266 290 this.light.destroy(); 291 + 292 + for (let weapon of this.weapons) { 293 + weapon.destroy(); 294 + } 267 295 } 268 296 }
+1 -1
src/projectile.ts
··· 58 58 game.container.addChild(this.shape); 59 59 60 60 const rigidBodyDesc = RAPIER() 61 - .RigidBodyDesc.dynamic() 61 + .RigidBodyDesc.kinematicPositionBased() 62 62 .setTranslation(this.shape.x, this.shape.y); 63 63 this.rigidBody = game.world.createRigidBody(rigidBodyDesc); 64 64
+5 -4
src/wave.ts
··· 108 108 // ], 109 109 // spawnSpeed: 100 110 110 // }, 111 - { enemies: [{ type: SphereEnemy, num: 50 }], spawnSpeed: 3 }, 112 111 { 113 112 enemies: [ 114 - { type: TriangleEnemy, num: 10 }, 115 - { type: SphereEnemy, num: 5 } 113 + { type: TriangleEnemy, num: 500 }, 114 + { type: SphereEnemy, num: 500 } 116 115 ], 117 - spawnSpeed: 3 116 + spawnSpeed: 10000 118 117 }, 118 + { enemies: [{ type: SphereEnemy, num: 50 }], spawnSpeed: 3 }, 119 + 119 120 { 120 121 enemies: [ 121 122 { type: TriangleEnemy, num: 15 },
+3 -3
src/weapon.ts src/weapons/gun.ts
··· 1 - import Game from './app'; 2 - import { Projectile } from './projectile'; 1 + import Game from '../app'; 2 + import { Projectile } from '../projectile'; 3 3 4 4 import { sound } from '@pixi/sound'; 5 5 ··· 20 20 piercing?: number; 21 21 }; 22 22 23 - export class Weapon { 23 + export class GunWeapon { 24 24 game: Game; 25 25 26 26 cooldown: number = 0;
+76
src/weapons/ball.ts
··· 1 + import Game from '../app'; 2 + import { Light } from '../light'; 3 + import * as PIXI from 'pixi.js'; 4 + import { RAPIER } from '../rapier'; 5 + import { Collider, RigidBody } from '@dimforge/rapier2d'; 6 + import { Weapon } from './weapon'; 7 + 8 + /** 9 + * ball that flies in a circle around player 10 + */ 11 + export class BallWeapon extends Weapon { 12 + distance: number = 200; 13 + speed: number = 0.002; 14 + angle: number = 0; 15 + 16 + constructor(game: Game, color: number) { 17 + super(game, color); 18 + 19 + this.shape = new PIXI.Graphics().circle(0, 0, this.size).fill(this.color); 20 + for (let i = 0; i < 10; i++) { 21 + let angle1 = (Math.PI * 2 * i) / 10; 22 + let angle2 = (Math.PI * 2 * (i + 1)) / 10; 23 + let angleBetween = (Math.PI * 2 * (i + 0.5)) / 10; 24 + 25 + let x1 = Math.cos(angle1) * this.size; 26 + let y1 = Math.sin(angle1) * this.size; 27 + let x2 = Math.cos(angle2) * this.size; 28 + let y2 = Math.sin(angle2) * this.size; 29 + let x3 = Math.cos(angleBetween) * this.size * 1.5; 30 + let y3 = Math.sin(angleBetween) * this.size * 1.5; 31 + this.shape.poly([x1, y1, x2, y2, x3, y3]).fill(this.color); 32 + } 33 + this.game.container.addChild(this.shape); 34 + 35 + const rigidBodyDesc = RAPIER() 36 + .RigidBodyDesc.kinematicPositionBased() 37 + .setTranslation(this.shape.x, this.shape.y); 38 + this.rigidBody = game.world.createRigidBody(rigidBodyDesc); 39 + 40 + const colliderDesc = RAPIER() 41 + .ColliderDesc.ball(this.size * 1.5) 42 + .setCollisionGroups(0x00040002) 43 + .setSensor(true) 44 + .setActiveEvents(RAPIER().ActiveEvents.COLLISION_EVENTS); 45 + 46 + this.collider = game.world.createCollider(colliderDesc, this.rigidBody); 47 + 48 + this.rigidBody.userData = this; 49 + } 50 + 51 + set x(value: number) { 52 + if (this.shape) this.shape.x = value; 53 + this.rigidBody?.setTranslation({ x: value, y: -(this.shape?.y ?? 0) }, true); 54 + } 55 + 56 + set y(value: number) { 57 + if (this.shape) this.shape.y = value; 58 + this.rigidBody?.setTranslation({ x: this.shape?.x ?? 0, y: -value }, true); 59 + } 60 + 61 + update(deltaTime: number, x: number, y: number) { 62 + this.angle += deltaTime * this.speed; 63 + 64 + this.x = x + Math.cos(this.angle) * this.distance; 65 + this.y = y + Math.sin(this.angle) * this.distance; 66 + 67 + if (this.shape) { 68 + this.shape.rotation = this.angle * 2; 69 + } 70 + } 71 + 72 + destroy() { 73 + if (this.shape) this.shape.destroy(); 74 + if (this.rigidBody) this.game.world.removeRigidBody(this.rigidBody); 75 + } 76 + }
+45
src/weapons/burst.ts
··· 1 + import Game from '../app'; 2 + import { Light } from '../light'; 3 + import * as PIXI from 'pixi.js'; 4 + import { RAPIER } from '../rapier'; 5 + import { Collider, RigidBody } from '@dimforge/rapier2d'; 6 + import { Weapon } from './weapon'; 7 + import { GunWeapon } from './gun'; 8 + 9 + /** 10 + * ball that flies in a circle around player 11 + */ 12 + export class BurstWeapon extends Weapon { 13 + cooldown: number = 0; 14 + fireRate: number = 1000; 15 + 16 + gun: GunWeapon; 17 + 18 + constructor(game: Game, color: number) { 19 + super(game, color); 20 + 21 + this.gun = new GunWeapon(game, { 22 + color: this.color, 23 + lifetime: 200, 24 + fireRate: 300, 25 + projectileSpeed: 1, 26 + projectileSize: 10, 27 + sound: false 28 + }); 29 + } 30 + 31 + update(deltaTime: number, x: number, y: number) { 32 + this.gun.update(deltaTime); 33 + 34 + if (this.gun.cooldown <= 0) { 35 + for (let i = 0; i < 20; i++) { 36 + this.gun.cooldown = -1; 37 + // get angle 38 + let angle = (Math.PI / 10) * i; 39 + let dx = Math.cos(angle) + x; 40 + let dy = Math.sin(angle) + y; 41 + this.gun.fire({ x, y }, { x: dx, y: dy }); 42 + } 43 + } 44 + } 45 + }
+83
src/weapons/knife.ts
··· 1 + import Game from '../app'; 2 + import { Light } from '../light'; 3 + import * as PIXI from 'pixi.js'; 4 + import { RAPIER } from '../rapier'; 5 + import { Collider, RigidBody, Vector2 } from '@dimforge/rapier2d'; 6 + import { Weapon } from './weapon'; 7 + 8 + /** 9 + * triangle that spins and goes to left and right of player 10 + */ 11 + export class Knife extends Weapon { 12 + speed: number = 0.006; 13 + distance: number = 300; 14 + current: number = 0; 15 + 16 + angle: number = 0; 17 + 18 + direction: 'left' | 'right' | 'both' = 'both'; 19 + 20 + constructor(game: Game, color: number) { 21 + super(game, color); 22 + 23 + this.shape = new PIXI.Graphics() 24 + .poly([-this.size, -this.size / 2, this.size, -this.size / 2, 0, this.size]) 25 + .fill(this.color); 26 + 27 + this.game.container.addChild(this.shape); 28 + 29 + this.createRidigBody(); 30 + } 31 + 32 + createRidigBody(): void { 33 + if (!this.shape) return; 34 + 35 + const rigidBodyDesc = RAPIER() 36 + .RigidBodyDesc.kinematicPositionBased() 37 + .setTranslation(this.shape.x, this.shape.y); 38 + this.rigidBody = this.game.world.createRigidBody(rigidBodyDesc); 39 + 40 + const colliderDesc = RAPIER() 41 + .ColliderDesc.ball(this.size) 42 + .setCollisionGroups(0x00040002) 43 + .setSensor(true) 44 + .setActiveEvents(RAPIER().ActiveEvents.COLLISION_EVENTS); 45 + 46 + this.game.world.createCollider(colliderDesc, this.rigidBody); 47 + 48 + this.rigidBody.userData = this; 49 + } 50 + 51 + set x(value: number) { 52 + if (this.shape) this.shape.x = value; 53 + this.rigidBody?.setTranslation({ x: value, y: -(this.shape?.y ?? 0) }, true); 54 + } 55 + 56 + set y(value: number) { 57 + if (this.shape) this.shape.y = value; 58 + this.rigidBody?.setTranslation({ x: this.shape?.x ?? 0, y: -value }, true); 59 + } 60 + 61 + update(deltaTime: number, x: number, y: number) { 62 + this.current += deltaTime * this.speed; 63 + 64 + let dx = Math.cos(this.current) * this.distance; 65 + if (this.direction === 'left') dx = -Math.abs(dx); 66 + else if (this.direction === 'right') dx = Math.abs(dx); 67 + 68 + this.x = x + Math.cos(this.angle) * dx; 69 + this.y = y + Math.sin(this.angle) * dx; 70 + 71 + // this.x = x + dx; 72 + // this.y = y + Math.cos(this.current * 0.25) * this.distance * 0.05; 73 + 74 + if (this.shape) { 75 + this.shape.rotation = this.current * 4; 76 + } 77 + } 78 + 79 + destroy() { 80 + if (this.shape) this.shape.destroy(); 81 + if (this.rigidBody) this.game.world.removeRigidBody(this.rigidBody); 82 + } 83 + }
+51
src/weapons/weapon.ts
··· 1 + import { Collider, RigidBody } from '@dimforge/rapier2d'; 2 + import Game from '../app'; 3 + import * as PIXI from 'pixi.js'; 4 + 5 + /** 6 + * ball that flies in a circle around player 7 + */ 8 + export class Weapon { 9 + game: Game; 10 + 11 + size: number = 10; 12 + 13 + damage: number = 20; 14 + 15 + color: number; 16 + 17 + shape?: PIXI.Graphics; 18 + 19 + rigidBody?: RigidBody; 20 + collider?: Collider; 21 + 22 + constructor(game: Game, color: number) { 23 + this.game = game; 24 + 25 + this.color = color; 26 + } 27 + 28 + get x() { 29 + if (this.shape) this.shape?.x ?? 0; 30 + 31 + return this.rigidBody?.translation().x ?? 0; 32 + } 33 + set x(value) { 34 + if (this.shape) this.shape.x = value; 35 + this.rigidBody?.setTranslation({ x: value, y: -this.y }, true); 36 + } 37 + 38 + get y() { 39 + if (this.shape) return this.shape.y; 40 + 41 + return -(this.rigidBody?.translation().y ?? 0); 42 + } 43 + set y(value) { 44 + if (this.shape) this.shape.y = value; 45 + this.rigidBody?.setTranslation({ x: this.x, y: -value }, true); 46 + } 47 + 48 + update(deltaTime: number, x: number, y: number) {} 49 + 50 + destroy() {} 51 + }