[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 30, 2024, 11:41 AM +0200) 74454628 bf8d58d5

+241 -324
+4
index.html
··· 173 173 <div id="wave-text" class="text-8xl font-bold text-neutral-200">Wave</div> 174 174 </div> 175 175 176 + <div id="items" class="absolute top-0 left-0 right-0 h-1 w-full flex"> 177 + 178 + </div> 179 + 176 180 <div id="upgrades" class="hidden z-50 absolute inset-0 overflow-y-scroll py-2 sm:py-16 px-2 bg-black/70"> 177 181 <div 178 182 id="upgrades-container"
+7 -6
src/app.ts
··· 53 53 54 54 debug: boolean = false; 55 55 56 - showStats: boolean = true; 56 + showStats: boolean = false; 57 57 58 58 stats?: Stats; 59 59 ··· 400 400 } 401 401 } 402 402 403 - let playerManager = this.playerManager; 403 + if (this.upgradeManager.items >= this.upgradeManager.neededItems) { 404 + this.waveManager?.nextWave(); 405 + this.upgradeManager.reset(); 406 + } 404 407 405 408 /* 409 + let playerManager = this.playerManager; 406 410 if (playerManager) { 407 411 let timeSinceLastDamage = playerManager.smallestTimeSinceLastDamage(); 408 412 if (timeSinceLastDamage < 150) { ··· 424 428 if (wave && !wave.isActive && waveText && this.playing) { 425 429 waveUI?.classList.remove('hidden'); 426 430 waveText.innerText = `Wave ${wave.index + 1}`; 427 - 428 - let counter = document.getElementById('counter'); 429 - if (counter) counter.innerText = (wave.index + 1).toString(); 430 431 } else { 431 432 waveUI?.classList.add('hidden'); 432 433 } ··· 460 461 // set inner text to "Game Over" 461 462 if (title) title.innerText = 'Game Over'; 462 463 463 - this.waveManager = new WaveManager(this, this.startWave); 464 + this.waveManager = new WaveManager(this, -1); 464 465 465 466 let counter = document.getElementById('counter'); 466 467 if (counter) counter.innerText = '';
+2
src/enemy-manager.ts
··· 23 23 addEnemy(enemyType: typeof Enemy = this.randomEnemyType()) { 24 24 const enemy = new enemyType(this.game); 25 25 this.enemies.push(enemy); 26 + 27 + return enemy; 26 28 } 27 29 28 30 update(deltaTime: number) {
+12 -2
src/enemy.ts
··· 48 48 49 49 type: number = -1; 50 50 51 + value: number = 1; 52 + 51 53 hitPlayer?(player: Player): void; 52 54 53 55 constructor(game: Game) { ··· 254 256 if (this.destroyed) return; 255 257 256 258 if (dropItem) 257 - this.game.dropItem({ x: this.x, y: this.y, color: this.color, size: 10, type: this.type }); 259 + this.game.dropItem({ 260 + x: this.x, 261 + y: this.y, 262 + color: this.color, 263 + size: 10, 264 + type: this.type, 265 + value: this.value 266 + }); 258 267 259 268 this.game.spawnParticles(this.x, this.y, 50, this.color); 260 269 ··· 481 490 fireRate: 2000, 482 491 projectileSize: 12, 483 492 damage: 10, 484 - lifetime: 8000 493 + lifetime: 8000, 494 + outline: true 485 495 }); 486 496 487 497 this.speed = 900;
+6 -1
src/item.ts
··· 12 12 type: number; 13 13 collisionGroups?: number; 14 14 lifetime?: number; 15 + value?: number; 15 16 }; 16 17 17 18 export class Item { ··· 35 36 36 37 light?: PIXI.Sprite; 37 38 39 + value: number = 1; 40 + 38 41 constructor(game: Game, options: ItemOptions) { 39 42 this.game = game; 40 43 ··· 49 52 50 53 this.shape.x = options.x; 51 54 this.shape.y = options.y; 55 + 56 + if (options.value) this.value = options.value; 52 57 53 58 game.container.addChild(this.shape); 54 59 ··· 121 126 122 127 player.items[this.type] += 1; 123 128 124 - this.game.upgradeManager.addItem(this.type); 129 + this.game.upgradeManager.addItem(this); 125 130 this.destroy(); 126 131 } 127 132
+1
src/obstacles.ts
··· 30 30 this.graphics = new PIXI.Graphics().rect(0, 0, width, height).fill(0); 31 31 this.graphics.pivot.set(width / 2, height / 2); 32 32 this.graphics.position.set(x, y); 33 + this.graphics.zIndex = -10; 33 34 34 35 this.container = container; 35 36 container.addChild(this.graphics);
+12 -12
src/player.ts
··· 106 106 this.x = 0; 107 107 this.y = 0; 108 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); 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 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)); 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 122 123 123 //this.weapons = []; 124 124 //this.weapons = [new BallWeapon(this.game, this.color), new Knife(this.game, this.color)];
+3
src/projectile-manager.ts
··· 12 12 lifetime?: number; 13 13 collisionGroups?: number; 14 14 showParticles?: boolean; 15 + 16 + outline?: boolean; 17 + 15 18 hit?: () => void; 16 19 17 20 piercing?: number;
+5 -1
src/projectile.ts
··· 49 49 50 50 this.color = data.color ?? 0xffffff; 51 51 52 - this.shape = new PIXI.Graphics().rect(0, 0, this.size, this.size).fill(this.color); 52 + this.shape = new PIXI.Graphics().rect(0, 0, this.size, this.size); 53 + if (data.outline) this.shape.stroke({ color: this.color, width: 4 }); 54 + else this.shape.fill(this.color); 55 + 56 + this.shape.zIndex = 10; 53 57 this.shape.pivot.set(this.size / 2, this.size / 2); 54 58 55 59 this.shape.x = data.position.x;
+33 -42
src/upgrade-manager.ts
··· 1 1 import Game from './app'; 2 + import { Item } from './item'; 2 3 3 4 export class UpgradeManager { 4 - colors_classes = ['bg-sky-500', 'bg-orange-500', 'bg-emerald-500']; 5 + colors_classes = ['bg-sky-500', 'bg-pink-500', 'bg-emerald-500']; 5 6 colors: number[] = []; 6 7 7 - items: number[] = [0, 0, 0]; 8 + items: number = 0; 9 + neededItems: number = 4; 8 10 9 11 game: Game; 10 12 11 - itemUI?: HTMLElement; 12 - 13 - itemCounters: HTMLElement[] = []; 13 + itemsUI?: HTMLElement; 14 14 15 15 constructor(game: Game) { 16 16 this.game = game; 17 17 18 - this.itemUI = document.getElementById('items') ?? undefined; 19 - 20 - this.createUI(); 18 + this.itemsUI = document.getElementById('items') ?? undefined; 21 19 } 22 20 23 - createUI() { 24 - if (!this.itemUI) return; 21 + createUI() {} 25 22 26 - this.itemUI.innerHTML = ''; 23 + reset() { 24 + this.items = 0; 27 25 28 - let count = this.colors_classes.length; 29 - for (let i = 0; i < count; i++) { 30 - const stat = document.createElement('div'); 31 - stat.className = 'flex items-center gap-2 justify-end'; 26 + // remove all items from UI 27 + if (this.itemsUI) { 28 + this.itemsUI.innerHTML = ''; 29 + } 30 + } 32 31 33 - const stat1 = document.createElement('div'); 34 - stat1.className = 'block h-4 w-4 rounded-full relative ' + this.colors_classes[i]; 32 + addItem(item: Item) { 33 + this.items += item.value; 35 34 36 - const statText = document.createElement('div'); 37 - statText.className = 'block'; 38 - statText.innerText = this.items[i].toString(); 39 - stat.appendChild(statText); 35 + // get percentage of needed: 36 + const percentage = (item.value / this.neededItems) * 100; 37 + // add to UI 38 + if (this.itemsUI) { 39 + let itemUI = document.createElement('div'); 40 40 41 - this.itemCounters.push(statText); 41 + itemUI.classList.add( 42 + 'h-full', 43 + 'transition-all', 44 + 'duration-200', 45 + this.colors_classes[item.type] 46 + ); 47 + // set width to percentage dwv 48 + itemUI.style.width = '0px'; 49 + setTimeout(() => { 50 + itemUI.style.width = `${percentage}%`; 51 + }, 100); 42 52 43 - const statGlow = document.createElement('div'); 44 - statGlow.className = 45 - 'absolute -inset-0.5 h-5 w-5 rounded-full blur-sm ' + this.colors_classes[i]; 46 - stat1.appendChild(statGlow); 47 - 48 - stat.appendChild(stat1); 49 - this.itemUI.appendChild(stat); 53 + this.itemsUI.appendChild(itemUI); 50 54 } 51 - } 52 - 53 - reset() { 54 - for (let i = 0; i < this.items.length; i++) { 55 - this.items[i] = 0; 56 - this.itemCounters[i].innerText = '0'; 57 - } 58 - } 59 - 60 - addItem(index: number) { 61 - this.items[index]++; 62 - 63 - this.itemCounters[index].innerText = this.items[index].toString(); 64 55 } 65 56 }
+149 -259
src/wave.ts
··· 1 - /* 2 - 3 - each wave, we have a list of enemies that will spawn, 4 - each enemy has a type, and a number of enemies of that type that will spawn 5 - 6 - */ 7 - 8 1 import Game from './app'; 9 - import Enemy, { CrossEnemy, PentagonEnemy, SphereEnemy, TriangleEnemy } from './enemy'; 2 + import Enemy, { PentagonEnemy, SphereEnemy, TriangleEnemy } from './enemy'; 3 + 4 + type EnemySpawnConfig = { 5 + type: typeof Enemy; 6 + spawnRate: number; 7 + maxCount?: number; 8 + upgradeFunction?: (enemy: Enemy) => void; 9 + }; 10 10 11 11 export class Wave { 12 12 game: Game; 13 - 14 - enemies: { type: typeof Enemy; num: number; spawned: number }[] = []; 15 - 16 - spawnPerSecond: number = 1; 17 - spawnAccumulator: number = 0; 18 - startDelay: number; 13 + enemies: EnemySpawnConfig[]; 14 + spawnAccumulators: number[]; 15 + enemyCounts: number[]; 19 16 waveTime: number = 0; 20 17 isActive: boolean = false; 21 - 18 + startDelay: number; 22 19 index: number; 23 20 24 - constructor( 25 - game: Game, 26 - enemies: { type: typeof Enemy; num: number }[], 27 - index: number, 28 - spawnPerSecond: number = 1, 29 - startDelay: number = 3 30 - ) { 21 + constructor(game: Game, enemies: EnemySpawnConfig[], index: number, startDelay: number = 3) { 31 22 this.game = game; 32 - 33 - this.enemies = enemies.map((enemy) => ({ 34 - type: enemy.type, 35 - num: enemy.num, 36 - spawned: 0 37 - })); 38 - this.spawnPerSecond = spawnPerSecond; 23 + this.enemies = enemies; 24 + this.spawnAccumulators = new Array(enemies.length).fill(0); 25 + this.enemyCounts = new Array(enemies.length).fill(0); 39 26 this.startDelay = startDelay; 40 27 this.index = index; 41 28 } 42 29 43 - randomEnemyIndex(): number { 44 - if (this.isComplete()) return -1; 45 - 46 - let i = Math.floor(Math.random() * this.enemies.length); 47 - let enemy = this.enemies[i]; 48 - if (enemy.spawned >= enemy.num * (this.game.playerManager?.players.length ?? 1)) { 49 - return this.randomEnemyIndex(); 50 - } 51 - 52 - return i; 53 - } 54 - 55 30 update(deltaTime: number) { 56 - // Convert deltaTime to seconds if it's in milliseconds 57 31 const deltaTimeSeconds = deltaTime / 1000; 58 - 59 - // Update wave time 60 32 this.waveTime += deltaTimeSeconds; 61 33 62 - // Check if we should start spawning 63 34 if (this.waveTime >= this.startDelay) { 64 35 this.isActive = true; 65 36 } 66 37 67 - // Only spawn if the wave is active 68 38 if (this.isActive) { 69 - this.spawnAccumulator += deltaTimeSeconds; 70 - const spawnInterval = 71 - 1 / (this.spawnPerSecond * (this.game.playerManager?.players.length ?? 1)); 39 + this.enemies.forEach((enemy, index) => { 40 + this.spawnAccumulators[index] += deltaTimeSeconds; 41 + const spawnInterval = 1 / enemy.spawnRate; 72 42 73 - while (this.spawnAccumulator >= spawnInterval) { 74 - const enemyIndex = this.randomEnemyIndex(); 75 - if (enemyIndex !== -1) { 76 - const enemy = this.enemies[enemyIndex]; 77 - this.game.enemyManager?.addEnemy(enemy.type); // Assuming there's an addEnemy method in the Game class 78 - enemy.spawned++; 43 + while (this.spawnAccumulators[index] >= spawnInterval) { 44 + if (!enemy.maxCount || this.enemyCounts[index] < enemy.maxCount) { 45 + this.spawnEnemy(enemy.type); 46 + this.enemyCounts[index]++; 47 + } 48 + this.spawnAccumulators[index] -= spawnInterval; 79 49 } 80 - this.spawnAccumulator -= spawnInterval; 81 - } 50 + }); 82 51 } 83 52 } 84 53 85 - isComplete(): boolean { 86 - const playerCount = this.game.playerManager?.players.length ?? 1; 87 - return this.isActive && this.enemies.every((e) => e.spawned >= e.num * playerCount); 54 + spawnEnemy(enemyType: typeof Enemy) { 55 + const enemy = this.game.enemyManager?.addEnemy(enemyType); 56 + if (enemy) { 57 + // Apply all accumulated upgrades to the new enemy 58 + const upgrades = this.game.waveManager?.getEnemyUpgrades(enemyType) ?? []; 59 + upgrades.forEach((upgrade) => upgrade(enemy)); 60 + } 88 61 } 89 62 } 90 63 91 64 type WaveData = { 92 - enemies: { type: typeof Enemy; num: number }[]; 93 - spawnSpeed: number; 65 + enemies: EnemySpawnConfig[]; 94 66 startDelay?: number; 67 + neededItems: number; 95 68 }; 96 69 97 - export const defaultWaves: WaveData[] = [ 98 - //{ enemies: [{ type: CrossEnemy, num: 1 }], spawnSpeed: 1 }, 99 - // test wave for measuring performance 100 - // { 101 - // enemies: [ 102 - // { type: TriangleEnemy, num: 100 }, 103 - // { type: SphereEnemy, num: 100 }, 104 - // { 105 - // type: PentagonEnemy, 106 - // num: 100 107 - // } 108 - // ], 109 - // spawnSpeed: 100 110 - // }, 111 - { 112 - enemies: [ 113 - { type: TriangleEnemy, num: 500 }, 114 - { type: SphereEnemy, num: 500 } 115 - ], 116 - spawnSpeed: 10000 117 - }, 118 - { enemies: [{ type: SphereEnemy, num: 50 }], spawnSpeed: 3 }, 119 - 120 - { 121 - enemies: [ 122 - { type: TriangleEnemy, num: 15 }, 123 - { type: SphereEnemy, num: 10 } 124 - ], 125 - spawnSpeed: 2 126 - }, 127 - { 128 - enemies: [ 129 - { type: TriangleEnemy, num: 20 }, 130 - { type: SphereEnemy, num: 15 }, 131 - { type: PentagonEnemy, num: 5 } 132 - ], 133 - spawnSpeed: 3 134 - }, 135 - { enemies: [{ type: SphereEnemy, num: 50 }], spawnSpeed: 3 }, 136 - { enemies: [{ type: TriangleEnemy, num: 50 }], spawnSpeed: 2 }, 137 - { enemies: [{ type: PentagonEnemy, num: 50 }], spawnSpeed: 3 }, 138 - { 139 - enemies: [ 140 - { type: PentagonEnemy, num: 25 }, 141 - { type: TriangleEnemy, num: 25 }, 142 - { type: SphereEnemy, num: 25 } 143 - ], 144 - spawnSpeed: 3 145 - }, 146 - { 147 - enemies: [ 148 - { type: TriangleEnemy, num: 40 }, 149 - { type: SphereEnemy, num: 30 }, 150 - { type: PentagonEnemy, num: 15 } 151 - ], 152 - spawnSpeed: 4 153 - }, 154 - { 155 - enemies: [{ type: PentagonEnemy, num: 75 }], 156 - spawnSpeed: 3 157 - }, 158 - { 159 - enemies: [ 160 - { type: TriangleEnemy, num: 80 }, 161 - { type: SphereEnemy, num: 60 } 162 - ], 163 - spawnSpeed: 5 164 - }, 165 - { 166 - enemies: [ 167 - { type: TriangleEnemy, num: 50 }, 168 - { type: PentagonEnemy, num: 40 } 169 - ], 170 - spawnSpeed: 4 171 - }, 172 - { 173 - enemies: [ 174 - { type: SphereEnemy, num: 70 }, 175 - { type: PentagonEnemy, num: 50 } 176 - ], 177 - spawnSpeed: 5 178 - }, 179 - { 180 - enemies: [{ type: TriangleEnemy, num: 150 }], 181 - spawnSpeed: 6 182 - }, 183 - { 184 - enemies: [ 185 - { type: TriangleEnemy, num: 70 }, 186 - { type: SphereEnemy, num: 50 }, 187 - { type: PentagonEnemy, num: 30 } 188 - ], 189 - spawnSpeed: 5 190 - }, 191 - { 192 - enemies: [{ type: SphereEnemy, num: 120 }], 193 - spawnSpeed: 5 194 - }, 195 - { 196 - enemies: [ 197 - { type: PentagonEnemy, num: 60 }, 198 - { type: TriangleEnemy, num: 80 } 199 - ], 200 - spawnSpeed: 4 201 - }, 202 - { 203 - enemies: [ 204 - { type: TriangleEnemy, num: 100 }, 205 - { type: SphereEnemy, num: 75 }, 206 - { type: PentagonEnemy, num: 50 } 207 - ], 208 - spawnSpeed: 6 209 - }, 210 - // 10 new waves start here 211 - { 212 - enemies: [ 213 - { type: TriangleEnemy, num: 120 }, 214 - { type: SphereEnemy, num: 90 }, 215 - { type: PentagonEnemy, num: 60 } 216 - ], 217 - spawnSpeed: 7 218 - }, 219 - { 220 - enemies: [{ type: PentagonEnemy, num: 150 }], 221 - spawnSpeed: 5 222 - }, 223 - { 224 - enemies: [ 225 - { type: TriangleEnemy, num: 200 }, 226 - { type: SphereEnemy, num: 100 } 227 - ], 228 - spawnSpeed: 8 229 - }, 230 - { 231 - enemies: [ 232 - { type: SphereEnemy, num: 150 }, 233 - { type: PentagonEnemy, num: 100 } 234 - ], 235 - spawnSpeed: 6 236 - }, 237 - { 238 - enemies: [ 239 - { type: TriangleEnemy, num: 180 }, 240 - { type: PentagonEnemy, num: 90 } 241 - ], 242 - spawnSpeed: 7 243 - }, 244 - { 245 - enemies: [{ type: SphereEnemy, num: 250 }], 246 - spawnSpeed: 8 247 - }, 248 - { 249 - enemies: [ 250 - { type: TriangleEnemy, num: 150 }, 251 - { type: SphereEnemy, num: 100 }, 252 - { type: PentagonEnemy, num: 75 } 253 - ], 254 - spawnSpeed: 7 255 - }, 256 - { 257 - enemies: [ 258 - { type: PentagonEnemy, num: 120 }, 259 - { type: TriangleEnemy, num: 180 } 260 - ], 261 - spawnSpeed: 6 262 - }, 263 - { 264 - enemies: [ 265 - { type: TriangleEnemy, num: 200 }, 266 - { type: SphereEnemy, num: 150 }, 267 - { type: PentagonEnemy, num: 100 } 268 - ], 269 - spawnSpeed: 8 270 - }, 271 - { 272 - enemies: [ 273 - { type: TriangleEnemy, num: 250 }, 274 - { type: SphereEnemy, num: 200 }, 275 - { type: PentagonEnemy, num: 150 } 276 - ], 277 - spawnSpeed: 9 278 - } 279 - ]; 280 - 281 70 export class WaveManager { 282 71 game: Game; 283 - 284 72 waves: Wave[]; 73 + currentWave: number = -1; 74 + private enemyUpgrades: Map<typeof Enemy, ((enemy: Enemy) => void)[]> = new Map(); 285 75 286 - currentWave: number = -1; 76 + data: WaveData[]; 287 77 288 - constructor(game: Game, startWave: number = 0, waves: WaveData[] = defaultWaves) { 78 + constructor(game: Game, startWave: number = -1, waves: WaveData[] = exampleWaves) { 289 79 this.game = game; 290 - 291 80 this.currentWave = startWave; 292 - 293 81 this.waves = waves.map( 294 - (wave, index) => new Wave(this.game, wave.enemies, index, wave.spawnSpeed, wave.startDelay) 82 + (wave, index) => new Wave(this.game, wave.enemies, index, wave.startDelay) 295 83 ); 84 + 85 + this.data = waves; 296 86 } 297 87 298 88 update(deltaTime: number) { 299 - if (this.waves.length === 0) return; 300 - if (this.currentWave >= this.waves.length) return; 89 + if (this.waves.length === 0 || this.currentWave >= this.waves.length) return; 301 90 302 - if ( 303 - this.currentWave === -1 || 304 - (this.game.enemyManager?.enemies.length === 0 && this.waves[this.currentWave].isComplete()) 305 - ) { 306 - this.currentWave += 1; 91 + if (this.currentWave === -1) { 92 + this.nextWave(); 307 93 } 308 94 309 95 if (this.currentWave >= 0 && this.currentWave < this.waves.length) { ··· 311 97 } 312 98 } 313 99 100 + nextWave() { 101 + if (this.currentWave < this.waves.length - 1) { 102 + this.collectEnemyUpgrades(); 103 + 104 + this.currentWave++; 105 + this.game.upgradeManager.neededItems = this.data[this.currentWave].neededItems; 106 + console.log('Starting wave', this.currentWave, this.game.upgradeManager.neededItems); 107 + } 108 + } 109 + 110 + collectEnemyUpgrades() { 111 + if (this.currentWave < 0 || this.currentWave >= this.waves.length) return; 112 + 113 + const currentWaveData = this.waves[this.currentWave]; 114 + currentWaveData.enemies.forEach((enemy) => { 115 + if (enemy.upgradeFunction) { 116 + if (!this.enemyUpgrades.has(enemy.type)) { 117 + this.enemyUpgrades.set(enemy.type, []); 118 + } 119 + const upgrades = this.enemyUpgrades.get(enemy.type); 120 + if (!upgrades) { 121 + console.error('No upgrades found for', enemy.type); 122 + return; 123 + } 124 + upgrades.push(enemy.upgradeFunction); 125 + this.enemyUpgrades.set(enemy.type, upgrades); 126 + } 127 + }); 128 + } 129 + 130 + getEnemyUpgrades(enemyType: typeof Enemy): ((enemy: Enemy) => void)[] { 131 + return this.enemyUpgrades.get(enemyType) || []; 132 + } 133 + 314 134 getCurrentWave(): Wave | undefined { 315 135 if (this.currentWave >= 0 && this.currentWave < this.waves.length) { 316 136 return this.waves[this.currentWave]; 317 137 } 318 138 } 319 139 } 140 + 141 + // Example usage: 142 + const exampleWaves: WaveData[] = [ 143 + { 144 + enemies: [ 145 + { 146 + type: PentagonEnemy, 147 + spawnRate: 1, 148 + upgradeFunction: (enemy) => { 149 + enemy.health = 20; 150 + enemy.value = 3; 151 + } 152 + }, 153 + { 154 + type: TriangleEnemy, 155 + spawnRate: 1, 156 + upgradeFunction: (enemy) => { 157 + enemy.speed *= 1.5; 158 + } 159 + } 160 + ], 161 + startDelay: 3, 162 + neededItems: 4 163 + }, 164 + { 165 + enemies: [ 166 + { 167 + type: SphereEnemy, 168 + spawnRate: 1, 169 + upgradeFunction: (enemy) => { 170 + enemy.health = 30; 171 + } 172 + }, 173 + { 174 + type: PentagonEnemy, 175 + spawnRate: 0.1, 176 + upgradeFunction: (enemy) => { 177 + if (enemy instanceof PentagonEnemy) { 178 + enemy.weapon.fireRate = 1000; 179 + } 180 + } 181 + } 182 + ], 183 + startDelay: 3, 184 + neededItems: 20 185 + }, 186 + { 187 + enemies: [ 188 + { 189 + type: PentagonEnemy, 190 + spawnRate: 1, 191 + upgradeFunction: (enemy) => { 192 + if (enemy instanceof PentagonEnemy) { 193 + enemy.weapon.fireRate = 100; 194 + } 195 + } 196 + }, 197 + { 198 + type: TriangleEnemy, 199 + spawnRate: 2, 200 + upgradeFunction: (enemy) => { 201 + enemy.speed *= 1.05; 202 + } 203 + } 204 + ], 205 + startDelay: 3, 206 + neededItems: 100 207 + } 208 + // Add more waves as needed 209 + ];
+7 -1
src/weapons/gun.ts
··· 18 18 showParticles?: boolean; 19 19 20 20 piercing?: number; 21 + 22 + outline?: boolean; 21 23 }; 22 24 23 25 export class GunWeapon { ··· 41 43 showParticles: boolean = false; 42 44 43 45 piercing: number = 0; 46 + 47 + outline: boolean = false; 44 48 45 49 constructor(game: Game, options: WeaponOptions | undefined = undefined) { 46 50 this.game = game; ··· 63 67 if (options.showParticles !== undefined) this.showParticles = options.showParticles; 64 68 65 69 if (options.piercing) this.piercing = options.piercing; 70 + if (options.outline) this.outline = options.outline; 66 71 } 67 72 68 73 fire(position: { x: number; y: number }, enemyPosition: { x: number; y: number }) { ··· 78 83 collisionGroups: this.collisionGroups, 79 84 lifetime: this.lifetime, 80 85 showParticles: this.showParticles, 81 - piercing: this.piercing 86 + piercing: this.piercing, 87 + outline: this.outline 82 88 }); 83 89 this.cooldown = this.fireRate; 84 90