[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.

add music, start of sound effects

Florian (Jul 24, 2024, 12:22 AM +0200) 6b9345bc 71fbd039

+196 -27
+15 -2
Readme.md
··· 37 37 - [x] die 38 38 - weapons 39 39 - recoil on player 40 - - impulse on bullet impact 40 + - [x] impulse on bullet impact (done for enemies) 41 41 - different weapons (different shooting mechanics, colors) 42 42 - show weapon 43 43 - [x] change player rigidbody to dynamic ··· 49 49 - [ ] enemies drop stuff, player can pick it up 50 50 - [ ] music 51 51 - [ ] sound effects 52 - - [ ] menu: solo, co-op, credits 52 + - [ ] player shooting 53 + - [ ] player getting hit 54 + - [ ] enemy getting hit 55 + - [ ] enemy shooting 56 + - [ ] enemy dying 57 + - [ ] special effects 58 + - [ ] player getting hit (flash screen) 59 + - [ ] menu: solo, co-op, credits 60 + 61 + 62 + ## credits: 63 + 64 + - music: [Nicolas Palmer](https://soundcloud.com/nicholas-palmer-4) 65 + - sound effects: [Kenney](https://kenney.nl/)
+131 -7
index.html
··· 1 1 <!doctype html> 2 - <html lang="en"> 2 + <html lang="en" class="m-0"> 3 3 <head> 4 4 <meta charset="UTF-8" /> 5 5 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 6 <title>shadow shooter</title> 7 7 8 8 <script src="https://cdn.tailwindcss.com"></script> 9 - 10 9 </head> 11 - <body> 10 + <body class="m-0 bg-black"> 12 11 <script src="src/app.ts" type="module"></script> 13 12 14 13 <!-- ui: title, play button --> 15 - <div id="ui" class="absolute inset-0 h-screen w-screen z-10 text-white flex items-center justify-center flex-col"> 16 - <h1 id="title" class="font-bold text-5xl mb-8">Shadow Shooter</h1> 17 - <button id="play" class="text-2xl font-semibold border border-white py-2 px-4 bg-white/10 hover:bg-white/20">Play</button> 18 - </div> 14 + <div 15 + id="ui" 16 + class="absolute inset-0 h-screen w-screen z-10 text-white flex items-center justify-between flex-col" 17 + > 18 + <div></div> 19 + <div class="flex flex-col items-center"> 20 + <h1 id="title" class="font-bold text-5xl mb-8">shadow shooter</h1> 21 + <button 22 + id="play" 23 + class="text-2xl font-semibold border border-white py-2 px-4 bg-white/10 hover:bg-white/20" 24 + > 25 + play 26 + </button> 27 + </div> 28 + <div class="text-white mb-4 flex flex-col items-center gap-4 text-xs"> 29 + <div class="flex items-center gap-3"> 30 + code by <span class="font-semibold">flo-bit</span> 31 + <a href="https://github.com/flo-bit" target="_blank"> 32 + <svg fill="currentColor" viewBox="0 0 24 24" aria-hidden="true" class="w-5 fill-white hover:fill-cyan-400"> 33 + <path 34 + fill-rule="evenodd" 35 + d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" 36 + clip-rule="evenodd" 37 + /> 38 + </svg> 39 + <span class="sr-only">github link</span> 40 + </a> 41 + 42 + <a href="https://x.com/flobit_dev" target="_blank"> 43 + <svg 44 + viewBox="0 0 24 24" 45 + xmlns="http://www.w3.org/2000/svg" 46 + class="fill-white h-4 hover:fill-cyan-400" 47 + > 48 + <title>X</title> 49 + <path 50 + d="M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z" 51 + /> 52 + </svg> 53 + </a> 54 + </div> 55 + <div class="flex items-center gap-3"> 56 + music by <span class="font-semibold">nicolas palmer</span> 57 + <a href="https://discordapp.com/users/702179454818189313/" target="_blank"> 58 + <svg 59 + class="h-4 fill-white hover:fill-cyan-400" 60 + viewBox="0 -28.5 256 256" 61 + version="1.1" 62 + xmlns="http://www.w3.org/2000/svg" 63 + xmlns:xlink="http://www.w3.org/1999/xlink" 64 + preserveAspectRatio="xMidYMid" 65 + > 66 + <g> 67 + <path 68 + d="M216.856339,16.5966031 C200.285002,8.84328665 182.566144,3.2084988 164.041564,0 C161.766523,4.11318106 159.108624,9.64549908 157.276099,14.0464379 C137.583995,11.0849896 118.072967,11.0849896 98.7430163,14.0464379 C96.9108417,9.64549908 94.1925838,4.11318106 91.8971895,0 C73.3526068,3.2084988 55.6133949,8.86399117 39.0420583,16.6376612 C5.61752293,67.146514 -3.4433191,116.400813 1.08711069,164.955721 C23.2560196,181.510915 44.7403634,191.567697 65.8621325,198.148576 C71.0772151,190.971126 75.7283628,183.341335 79.7352139,175.300261 C72.104019,172.400575 64.7949724,168.822202 57.8887866,164.667963 C59.7209612,163.310589 61.5131304,161.891452 63.2445898,160.431257 C105.36741,180.133187 151.134928,180.133187 192.754523,160.431257 C194.506336,161.891452 196.298154,163.310589 198.110326,164.667963 C191.183787,168.842556 183.854737,172.420929 176.223542,175.320965 C180.230393,183.341335 184.861538,190.991831 190.096624,198.16893 C211.238746,191.588051 232.743023,181.531619 254.911949,164.955721 C260.227747,108.668201 245.831087,59.8662432 216.856339,16.5966031 Z M85.4738752,135.09489 C72.8290281,135.09489 62.4592217,123.290155 62.4592217,108.914901 C62.4592217,94.5396472 72.607595,82.7145587 85.4738752,82.7145587 C98.3405064,82.7145587 108.709962,94.5189427 108.488529,108.914901 C108.508531,123.290155 98.3405064,135.09489 85.4738752,135.09489 Z M170.525237,135.09489 C157.88039,135.09489 147.510584,123.290155 147.510584,108.914901 C147.510584,94.5396472 157.658606,82.7145587 170.525237,82.7145587 C183.391518,82.7145587 193.761324,94.5189427 193.539891,108.914901 C193.539891,123.290155 183.391518,135.09489 170.525237,135.09489 Z" 69 + fill-rule="nonzero" 70 + ></path> 71 + </g> 72 + </svg> 73 + </a> 74 + 75 + <a href="https://soundcloud.com/nicholas-palmer-4" target="_blank"> 76 + <svg 77 + xmlns="http://www.w3.org/2000/svg" 78 + viewBox="0 0 75 33.51" 79 + class="h-2.5 fill-white hover:fill-cyan-400" 80 + > 81 + <g id="Layer_2" data-name="Layer 2"> 82 + <g id="Orange"> 83 + <path 84 + d="M75,23.6a10.5,10.5,0,0,1-10.63,9.91H38.82a2.14,2.14,0,0,1-2.12-2.13V3.87a2.34,2.34,0,0,1,1.41-2.24S40.46,0,45.41,0A16.74,16.74,0,0,1,54,2.36a17,17,0,0,1,8,11.08,9.8,9.8,0,0,1,2.71-.37A10.23,10.23,0,0,1,75,23.6Z" 85 + /> 86 + <path 87 + d="M33.51,5.61a.83.83,0,1,0-1.65,0c-.7,9.25-1.24,17.92,0,27.14a.83.83,0,0,0,1.65,0C34.84,23.45,34.28,14.94,33.51,5.61Z" 88 + /> 89 + <path 90 + d="M28.35,8.81a.87.87,0,0,0-1.73,0,103.7,103.7,0,0,0,0,23.95.87.87,0,0,0,1.72,0A93.2,93.2,0,0,0,28.35,8.81Z" 91 + /> 92 + <path 93 + d="M23.16,8a.84.84,0,0,0-1.67,0c-.79,8.44-1.19,16.32,0,24.74a.83.83,0,0,0,1.66,0C24.38,24.21,24,16.55,23.16,8Z" 94 + /> 95 + <path 96 + d="M18,10.41a.86.86,0,0,0-1.72,0,87.61,87.61,0,0,0,0,22.36.85.85,0,0,0,1.69,0A81.68,81.68,0,0,0,18,10.41Z" 97 + /> 98 + <path 99 + d="M12.79,16a.85.85,0,0,0-1.7,0c-1.23,5.76-.65,11,.05,16.83a.81.81,0,0,0,1.6,0C13.51,26.92,14.1,21.8,12.79,16Z" 100 + /> 101 + <path 102 + d="M7.62,15.12a.88.88,0,0,0-1.75,0C4.78,21,5.14,26.18,5.9,32.05c.08.89,1.59.88,1.69,0C8.43,26.09,8.82,21.06,7.62,15.12Z" 103 + /> 104 + <path 105 + d="M2.4,18A.88.88,0,0,0,.65,18c-1,3.95-.69,7.22.07,11.18a.82.82,0,0,0,1.63,0C3.23,25.14,3.66,21.94,2.4,18Z" 106 + /> 107 + </g> 108 + </g> 109 + </svg> 110 + </a> 111 + </div> 112 + <div class="flex items-center gap-3"> 113 + sound effects by <span class="font-semibold">kenney</span> 114 + 115 + <a href="https://kenney.nl/" target="_blank"> 116 + <svg 117 + xmlns="http://www.w3.org/2000/svg" 118 + viewBox="0 0 24 24" 119 + fill="currentColor" 120 + class="h-4 fill-white hover:fill-cyan-400" 121 + > 122 + <path 123 + d="M21.721 12.752a9.711 9.711 0 0 0-.945-5.003 12.754 12.754 0 0 1-4.339 2.708 18.991 18.991 0 0 1-.214 4.772 17.165 17.165 0 0 0 5.498-2.477ZM14.634 15.55a17.324 17.324 0 0 0 .332-4.647c-.952.227-1.945.347-2.966.347-1.021 0-2.014-.12-2.966-.347a17.515 17.515 0 0 0 .332 4.647 17.385 17.385 0 0 0 5.268 0ZM9.772 17.119a18.963 18.963 0 0 0 4.456 0A17.182 17.182 0 0 1 12 21.724a17.18 17.18 0 0 1-2.228-4.605ZM7.777 15.23a18.87 18.87 0 0 1-.214-4.774 12.753 12.753 0 0 1-4.34-2.708 9.711 9.711 0 0 0-.944 5.004 17.165 17.165 0 0 0 5.498 2.477ZM21.356 14.752a9.765 9.765 0 0 1-7.478 6.817 18.64 18.64 0 0 0 1.988-4.718 18.627 18.627 0 0 0 5.49-2.098ZM2.644 14.752c1.682.971 3.53 1.688 5.49 2.099a18.64 18.64 0 0 0 1.988 4.718 9.765 9.765 0 0 1-7.478-6.816ZM13.878 2.43a9.755 9.755 0 0 1 6.116 3.986 11.267 11.267 0 0 1-3.746 2.504 18.63 18.63 0 0 0-2.37-6.49ZM12 2.276a17.152 17.152 0 0 1 2.805 7.121c-.897.23-1.837.353-2.805.353-.968 0-1.908-.122-2.805-.353A17.151 17.151 0 0 1 12 2.276ZM10.122 2.43a18.629 18.629 0 0 0-2.37 6.49 11.266 11.266 0 0 1-3.746-2.504 9.754 9.754 0 0 1 6.116-3.985Z" 124 + /> 125 + </svg> 126 + </a> 127 + 128 + <a href="https://x.com/KenneyNL" target="_blank"> 129 + <svg 130 + viewBox="0 0 24 24" 131 + xmlns="http://www.w3.org/2000/svg" 132 + class="fill-white h-4 hover:fill-cyan-400" 133 + > 134 + <title>X</title> 135 + <path 136 + d="M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z" 137 + /> 138 + </svg> 139 + </a> 140 + </div> 141 + </div> 142 + </div> 19 143 </body> 20 144 </html>
+9
package-lock.json
··· 9 9 "version": "0.0.0", 10 10 "dependencies": { 11 11 "@dimforge/rapier2d": "^0.11.2", 12 + "@pixi/sound": "^6.0.0", 12 13 "pixi-filters": "^6.0.4", 13 14 "pixi.js": "^8.2.5", 14 15 "prettier": "^3.3.3", ··· 401 402 "version": "2.9.6", 402 403 "resolved": "https://registry.npmjs.org/@pixi/colord/-/colord-2.9.6.tgz", 403 404 "integrity": "sha512-nezytU2pw587fQstUu1AsJZDVEynjskwOL+kibwcdxsMBFqPsFFNA7xl0ii/gXuDi6M0xj3mfRJj8pBSc2jCfA==" 405 + }, 406 + "node_modules/@pixi/sound": { 407 + "version": "6.0.0", 408 + "resolved": "https://registry.npmjs.org/@pixi/sound/-/sound-6.0.0.tgz", 409 + "integrity": "sha512-fEaCs2JmyYT1qqouFS3DydSccI35dyYD0pKK2hEbIGVDKUTvl224x0p4qme2YU9l465WRtM7gspLzP5fFf1mxQ==", 410 + "peerDependencies": { 411 + "pixi.js": "^8.0.0" 412 + } 404 413 }, 405 414 "node_modules/@rollup/plugin-typescript": { 406 415 "version": "11.1.6",
+1 -1
package.json
··· 10 10 }, 11 11 "dependencies": { 12 12 "@dimforge/rapier2d": "^0.11.2", 13 + "@pixi/sound": "^6.0.0", 13 14 "pixi-filters": "^6.0.4", 14 15 "pixi.js": "^8.2.5", 15 16 "prettier": "^3.3.3", ··· 21 22 "description": "Vite with rapier and pixi for quick testing.", 22 23 "devDependencies": { 23 24 "@types/node": "^20.14.11", 24 - "@types/pixi.js": "^5.0.0", 25 25 "@types/stats.js": "^0.17.3", 26 26 "ts-node": "^10.9.2", 27 27 "typescript": "^5.5.3",
public/laser.mp3

This is a binary file and will not be displayed.

public/music-intro.mp3

This is a binary file and will not be displayed.

public/music.mp3

This is a binary file and will not be displayed.

+18 -4
src/app.ts
··· 14 14 import PlayerManager from './player-manager.js'; 15 15 import ProjectileManager, { ProjectileData } from './projectile-manager.js'; 16 16 17 + import { sound } from '@pixi/sound'; 18 + 17 19 export default class Game { 18 20 container: PIXI.Container; 19 21 world!: World; ··· 42 44 43 45 window.container = this.container; 44 46 window.game = this; 47 + 48 + sound.add('music-intro', { 49 + url: '/shadow-shmup/music-intro.mp3' 50 + }); 51 + sound.play('music-intro', () => { 52 + console.log('music-intro complete'); 53 + sound.play('music'); 54 + }); 55 + sound.add('music', { url: '/shadow-shmup/music.mp3', loop: true }); 56 + 57 + sound.add('laser', { url: '/shadow-shmup/laser.mp3', volume: 0.3 }); 45 58 } 46 59 47 60 async setupPhysicsWorld() { ··· 108 121 } 109 122 this.particleSystem?.update(deltaTime); 110 123 this.update(deltaTime); 124 + 125 + if (this.debug) this.renderPhysicsDebug(); 126 + 111 127 if (this.stats) this.stats.end(); 112 128 }); 113 129 ··· 217 233 * @param {number} deltaTime 218 234 */ 219 235 update(deltaTime: number) { 220 - if (!this.playing) return; 221 - 222 236 // update game state here 223 237 this.playerManager?.update(deltaTime, this.keys); 238 + if (!this.playing) return; 224 239 225 240 this.enemyManager?.update(deltaTime); 226 241 ··· 228 243 this.enemyManager?.addEnemy(); 229 244 } 230 245 231 - if (this.debug) this.renderPhysicsDebug(); 232 - 233 246 // let player = this.playerManager?.players[0]; 234 247 // if (player) { 235 248 // player.health += deltaTime * 0.1; ··· 240 253 241 254 if (this.playerManager?.players[0].health <= 0) { 242 255 this.enemyManager?.killAll(); 256 + this.projectileManager?.clearAllProjectiles(); 243 257 244 258 this.playing = false; 245 259
+1
src/enemy.ts
··· 230 230 } 231 231 232 232 destroy() { 233 + if (this.destroyed) return; 233 234 this.game.spawnParticles(this.x, this.y, 50, this.color); 234 235 235 236 this.destroyed = true;
+17 -13
src/player.ts
··· 78 78 79 79 this.leftEye = new Eye(this.playerContainer, -this.size / 4, 0); 80 80 this.rightEye = new Eye(this.playerContainer, this.size / 4, 0); 81 + 82 + this.x = 130; 83 + this.y = 80; 81 84 } 82 85 83 86 async createLight() { ··· 159 162 160 163 update(deltaTime: number, keys: Record<string, boolean>) { 161 164 // move the player, wasd 162 - let dx = 0, 163 - dy = 0; 164 - if (keys['w']) dy -= 1; 165 - if (keys['s']) dy += 1; 166 - if (keys['a']) dx -= 1; 167 - if (keys['d']) dx += 1; 165 + if (this.game.playing) { 166 + let dx = 0, 167 + dy = 0; 168 + if (keys['w']) dy -= 1; 169 + if (keys['s']) dy += 1; 170 + if (keys['a']) dx -= 1; 171 + if (keys['d']) dx += 1; 168 172 169 - // Normalize diagonal movement 170 - if (dx !== 0 && dy !== 0) { 171 - dx *= Math.SQRT1_2; 172 - dy *= Math.SQRT1_2; 173 + // Normalize diagonal movement 174 + if (dx !== 0 && dy !== 0) { 175 + dx *= Math.SQRT1_2; 176 + dy *= Math.SQRT1_2; 177 + } 178 + if (dx || dy) this.rigidBody?.applyImpulse({ x: dx * this.speed, y: -dy * this.speed }, true); 173 179 } 174 180 175 181 if (this.light) { ··· 179 185 180 186 this.leftEye.update(deltaTime, 1); 181 187 this.rightEye.update(deltaTime, 1); 182 - 183 - if (dx || dy) this.rigidBody?.applyImpulse({ x: dx * this.speed, y: -dy * this.speed }, true); 184 188 185 189 // get closest enemy 186 190 const closestEnemy = this.game.enemyManager?.getClosestEnemy(this.position, 200); ··· 209 213 // ray cast around the player to create a shadow 210 214 this.shadow.clear(); 211 215 212 - const rays = 3600; 216 + const rays = 1080; 213 217 214 218 const angleStep = (Math.PI * 2) / rays; 215 219 const rayLength = 1000;
+4
src/weapon.ts
··· 1 1 import Game from './app'; 2 2 import { Projectile } from './projectile'; 3 3 4 + import { sound } from '@pixi/sound'; 5 + 4 6 export type WeaponOptions = { 5 7 damage?: number; 6 8 fireRate?: number; ··· 53 55 collisionGroups: this.collisionGroups 54 56 }); 55 57 this.cooldown = this.fireRate; 58 + 59 + sound.play('laser'); 56 60 } 57 61 } 58 62