[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 (Aug 5, 2024, 1:35 PM +0200) 38042a24 185123aa

+982 -86
+407
LICENSE
··· 1 + Attribution-NonCommercial 4.0 International 2 + 3 + ======================================================================= 4 + 5 + Creative Commons Corporation ("Creative Commons") is not a law firm and 6 + does not provide legal services or legal advice. Distribution of 7 + Creative Commons public licenses does not create a lawyer-client or 8 + other relationship. Creative Commons makes its licenses and related 9 + information available on an "as-is" basis. Creative Commons gives no 10 + warranties regarding its licenses, any material licensed under their 11 + terms and conditions, or any related information. Creative Commons 12 + disclaims all liability for damages resulting from their use to the 13 + fullest extent possible. 14 + 15 + Using Creative Commons Public Licenses 16 + 17 + Creative Commons public licenses provide a standard set of terms and 18 + conditions that creators and other rights holders may use to share 19 + original works of authorship and other material subject to copyright 20 + and certain other rights specified in the public license below. The 21 + following considerations are for informational purposes only, are not 22 + exhaustive, and do not form part of our licenses. 23 + 24 + Considerations for licensors: Our public licenses are 25 + intended for use by those authorized to give the public 26 + permission to use material in ways otherwise restricted by 27 + copyright and certain other rights. Our licenses are 28 + irrevocable. Licensors should read and understand the terms 29 + and conditions of the license they choose before applying it. 30 + Licensors should also secure all rights necessary before 31 + applying our licenses so that the public can reuse the 32 + material as expected. Licensors should clearly mark any 33 + material not subject to the license. This includes other CC- 34 + licensed material, or material used under an exception or 35 + limitation to copyright. More considerations for licensors: 36 + wiki.creativecommons.org/Considerations_for_licensors 37 + 38 + Considerations for the public: By using one of our public 39 + licenses, a licensor grants the public permission to use the 40 + licensed material under specified terms and conditions. If 41 + the licensor's permission is not necessary for any reason--for 42 + example, because of any applicable exception or limitation to 43 + copyright--then that use is not regulated by the license. Our 44 + licenses grant only permissions under copyright and certain 45 + other rights that a licensor has authority to grant. Use of 46 + the licensed material may still be restricted for other 47 + reasons, including because others have copyright or other 48 + rights in the material. A licensor may make special requests, 49 + such as asking that all changes be marked or described. 50 + Although not required by our licenses, you are encouraged to 51 + respect those requests where reasonable. More considerations 52 + for the public: 53 + wiki.creativecommons.org/Considerations_for_licensees 54 + 55 + ======================================================================= 56 + 57 + Creative Commons Attribution-NonCommercial 4.0 International Public 58 + License 59 + 60 + By exercising the Licensed Rights (defined below), You accept and agree 61 + to be bound by the terms and conditions of this Creative Commons 62 + Attribution-NonCommercial 4.0 International Public License ("Public 63 + License"). To the extent this Public License may be interpreted as a 64 + contract, You are granted the Licensed Rights in consideration of Your 65 + acceptance of these terms and conditions, and the Licensor grants You 66 + such rights in consideration of benefits the Licensor receives from 67 + making the Licensed Material available under these terms and 68 + conditions. 69 + 70 + 71 + Section 1 -- Definitions. 72 + 73 + a. Adapted Material means material subject to Copyright and Similar 74 + Rights that is derived from or based upon the Licensed Material 75 + and in which the Licensed Material is translated, altered, 76 + arranged, transformed, or otherwise modified in a manner requiring 77 + permission under the Copyright and Similar Rights held by the 78 + Licensor. For purposes of this Public License, where the Licensed 79 + Material is a musical work, performance, or sound recording, 80 + Adapted Material is always produced where the Licensed Material is 81 + synched in timed relation with a moving image. 82 + 83 + b. Adapter's License means the license You apply to Your Copyright 84 + and Similar Rights in Your contributions to Adapted Material in 85 + accordance with the terms and conditions of this Public License. 86 + 87 + c. Copyright and Similar Rights means copyright and/or similar rights 88 + closely related to copyright including, without limitation, 89 + performance, broadcast, sound recording, and Sui Generis Database 90 + Rights, without regard to how the rights are labeled or 91 + categorized. For purposes of this Public License, the rights 92 + specified in Section 2(b)(1)-(2) are not Copyright and Similar 93 + Rights. 94 + d. Effective Technological Measures means those measures that, in the 95 + absence of proper authority, may not be circumvented under laws 96 + fulfilling obligations under Article 11 of the WIPO Copyright 97 + Treaty adopted on December 20, 1996, and/or similar international 98 + agreements. 99 + 100 + e. Exceptions and Limitations means fair use, fair dealing, and/or 101 + any other exception or limitation to Copyright and Similar Rights 102 + that applies to Your use of the Licensed Material. 103 + 104 + f. Licensed Material means the artistic or literary work, database, 105 + or other material to which the Licensor applied this Public 106 + License. 107 + 108 + g. Licensed Rights means the rights granted to You subject to the 109 + terms and conditions of this Public License, which are limited to 110 + all Copyright and Similar Rights that apply to Your use of the 111 + Licensed Material and that the Licensor has authority to license. 112 + 113 + h. Licensor means the individual(s) or entity(ies) granting rights 114 + under this Public License. 115 + 116 + i. NonCommercial means not primarily intended for or directed towards 117 + commercial advantage or monetary compensation. For purposes of 118 + this Public License, the exchange of the Licensed Material for 119 + other material subject to Copyright and Similar Rights by digital 120 + file-sharing or similar means is NonCommercial provided there is 121 + no payment of monetary compensation in connection with the 122 + exchange. 123 + 124 + j. Share means to provide material to the public by any means or 125 + process that requires permission under the Licensed Rights, such 126 + as reproduction, public display, public performance, distribution, 127 + dissemination, communication, or importation, and to make material 128 + available to the public including in ways that members of the 129 + public may access the material from a place and at a time 130 + individually chosen by them. 131 + 132 + k. Sui Generis Database Rights means rights other than copyright 133 + resulting from Directive 96/9/EC of the European Parliament and of 134 + the Council of 11 March 1996 on the legal protection of databases, 135 + as amended and/or succeeded, as well as other essentially 136 + equivalent rights anywhere in the world. 137 + 138 + l. You means the individual or entity exercising the Licensed Rights 139 + under this Public License. Your has a corresponding meaning. 140 + 141 + 142 + Section 2 -- Scope. 143 + 144 + a. License grant. 145 + 146 + 1. Subject to the terms and conditions of this Public License, 147 + the Licensor hereby grants You a worldwide, royalty-free, 148 + non-sublicensable, non-exclusive, irrevocable license to 149 + exercise the Licensed Rights in the Licensed Material to: 150 + 151 + a. reproduce and Share the Licensed Material, in whole or 152 + in part, for NonCommercial purposes only; and 153 + 154 + b. produce, reproduce, and Share Adapted Material for 155 + NonCommercial purposes only. 156 + 157 + 2. Exceptions and Limitations. For the avoidance of doubt, where 158 + Exceptions and Limitations apply to Your use, this Public 159 + License does not apply, and You do not need to comply with 160 + its terms and conditions. 161 + 162 + 3. Term. The term of this Public License is specified in Section 163 + 6(a). 164 + 165 + 4. Media and formats; technical modifications allowed. The 166 + Licensor authorizes You to exercise the Licensed Rights in 167 + all media and formats whether now known or hereafter created, 168 + and to make technical modifications necessary to do so. The 169 + Licensor waives and/or agrees not to assert any right or 170 + authority to forbid You from making technical modifications 171 + necessary to exercise the Licensed Rights, including 172 + technical modifications necessary to circumvent Effective 173 + Technological Measures. For purposes of this Public License, 174 + simply making modifications authorized by this Section 2(a) 175 + (4) never produces Adapted Material. 176 + 177 + 5. Downstream recipients. 178 + 179 + a. Offer from the Licensor -- Licensed Material. Every 180 + recipient of the Licensed Material automatically 181 + receives an offer from the Licensor to exercise the 182 + Licensed Rights under the terms and conditions of this 183 + Public License. 184 + 185 + b. No downstream restrictions. You may not offer or impose 186 + any additional or different terms or conditions on, or 187 + apply any Effective Technological Measures to, the 188 + Licensed Material if doing so restricts exercise of the 189 + Licensed Rights by any recipient of the Licensed 190 + Material. 191 + 192 + 6. No endorsement. Nothing in this Public License constitutes or 193 + may be construed as permission to assert or imply that You 194 + are, or that Your use of the Licensed Material is, connected 195 + with, or sponsored, endorsed, or granted official status by, 196 + the Licensor or others designated to receive attribution as 197 + provided in Section 3(a)(1)(A)(i). 198 + 199 + b. Other rights. 200 + 201 + 1. Moral rights, such as the right of integrity, are not 202 + licensed under this Public License, nor are publicity, 203 + privacy, and/or other similar personality rights; however, to 204 + the extent possible, the Licensor waives and/or agrees not to 205 + assert any such rights held by the Licensor to the limited 206 + extent necessary to allow You to exercise the Licensed 207 + Rights, but not otherwise. 208 + 209 + 2. Patent and trademark rights are not licensed under this 210 + Public License. 211 + 212 + 3. To the extent possible, the Licensor waives any right to 213 + collect royalties from You for the exercise of the Licensed 214 + Rights, whether directly or through a collecting society 215 + under any voluntary or waivable statutory or compulsory 216 + licensing scheme. In all other cases the Licensor expressly 217 + reserves any right to collect such royalties, including when 218 + the Licensed Material is used other than for NonCommercial 219 + purposes. 220 + 221 + 222 + Section 3 -- License Conditions. 223 + 224 + Your exercise of the Licensed Rights is expressly made subject to the 225 + following conditions. 226 + 227 + a. Attribution. 228 + 229 + 1. If You Share the Licensed Material (including in modified 230 + form), You must: 231 + 232 + a. retain the following if it is supplied by the Licensor 233 + with the Licensed Material: 234 + 235 + i. identification of the creator(s) of the Licensed 236 + Material and any others designated to receive 237 + attribution, in any reasonable manner requested by 238 + the Licensor (including by pseudonym if 239 + designated); 240 + 241 + ii. a copyright notice; 242 + 243 + iii. a notice that refers to this Public License; 244 + 245 + iv. a notice that refers to the disclaimer of 246 + warranties; 247 + 248 + v. a URI or hyperlink to the Licensed Material to the 249 + extent reasonably practicable; 250 + 251 + b. indicate if You modified the Licensed Material and 252 + retain an indication of any previous modifications; and 253 + 254 + c. indicate the Licensed Material is licensed under this 255 + Public License, and include the text of, or the URI or 256 + hyperlink to, this Public License. 257 + 258 + 2. You may satisfy the conditions in Section 3(a)(1) in any 259 + reasonable manner based on the medium, means, and context in 260 + which You Share the Licensed Material. For example, it may be 261 + reasonable to satisfy the conditions by providing a URI or 262 + hyperlink to a resource that includes the required 263 + information. 264 + 265 + 3. If requested by the Licensor, You must remove any of the 266 + information required by Section 3(a)(1)(A) to the extent 267 + reasonably practicable. 268 + 269 + 4. If You Share Adapted Material You produce, the Adapter's 270 + License You apply must not prevent recipients of the Adapted 271 + Material from complying with this Public License. 272 + 273 + 274 + Section 4 -- Sui Generis Database Rights. 275 + 276 + Where the Licensed Rights include Sui Generis Database Rights that 277 + apply to Your use of the Licensed Material: 278 + 279 + a. for the avoidance of doubt, Section 2(a)(1) grants You the right 280 + to extract, reuse, reproduce, and Share all or a substantial 281 + portion of the contents of the database for NonCommercial purposes 282 + only; 283 + 284 + b. if You include all or a substantial portion of the database 285 + contents in a database in which You have Sui Generis Database 286 + Rights, then the database in which You have Sui Generis Database 287 + Rights (but not its individual contents) is Adapted Material; and 288 + 289 + c. You must comply with the conditions in Section 3(a) if You Share 290 + all or a substantial portion of the contents of the database. 291 + 292 + For the avoidance of doubt, this Section 4 supplements and does not 293 + replace Your obligations under this Public License where the Licensed 294 + Rights include other Copyright and Similar Rights. 295 + 296 + 297 + Section 5 -- Disclaimer of Warranties and Limitation of Liability. 298 + 299 + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 300 + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 301 + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 302 + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 303 + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 304 + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 305 + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 306 + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 307 + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 308 + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 309 + 310 + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 311 + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 312 + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 313 + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 314 + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 315 + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 316 + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 317 + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 318 + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 319 + 320 + c. The disclaimer of warranties and limitation of liability provided 321 + above shall be interpreted in a manner that, to the extent 322 + possible, most closely approximates an absolute disclaimer and 323 + waiver of all liability. 324 + 325 + 326 + Section 6 -- Term and Termination. 327 + 328 + a. This Public License applies for the term of the Copyright and 329 + Similar Rights licensed here. However, if You fail to comply with 330 + this Public License, then Your rights under this Public License 331 + terminate automatically. 332 + 333 + b. Where Your right to use the Licensed Material has terminated under 334 + Section 6(a), it reinstates: 335 + 336 + 1. automatically as of the date the violation is cured, provided 337 + it is cured within 30 days of Your discovery of the 338 + violation; or 339 + 340 + 2. upon express reinstatement by the Licensor. 341 + 342 + For the avoidance of doubt, this Section 6(b) does not affect any 343 + right the Licensor may have to seek remedies for Your violations 344 + of this Public License. 345 + 346 + c. For the avoidance of doubt, the Licensor may also offer the 347 + Licensed Material under separate terms or conditions or stop 348 + distributing the Licensed Material at any time; however, doing so 349 + will not terminate this Public License. 350 + 351 + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 352 + License. 353 + 354 + 355 + Section 7 -- Other Terms and Conditions. 356 + 357 + a. The Licensor shall not be bound by any additional or different 358 + terms or conditions communicated by You unless expressly agreed. 359 + 360 + b. Any arrangements, understandings, or agreements regarding the 361 + Licensed Material not stated herein are separate from and 362 + independent of the terms and conditions of this Public License. 363 + 364 + 365 + Section 8 -- Interpretation. 366 + 367 + a. For the avoidance of doubt, this Public License does not, and 368 + shall not be interpreted to, reduce, limit, restrict, or impose 369 + conditions on any use of the Licensed Material that could lawfully 370 + be made without permission under this Public License. 371 + 372 + b. To the extent possible, if any provision of this Public License is 373 + deemed unenforceable, it shall be automatically reformed to the 374 + minimum extent necessary to make it enforceable. If the provision 375 + cannot be reformed, it shall be severed from this Public License 376 + without affecting the enforceability of the remaining terms and 377 + conditions. 378 + 379 + c. No term or condition of this Public License will be waived and no 380 + failure to comply consented to unless expressly agreed to by the 381 + Licensor. 382 + 383 + d. Nothing in this Public License constitutes or may be interpreted 384 + as a limitation upon, or waiver of, any privileges and immunities 385 + that apply to the Licensor or You, including from the legal 386 + processes of any jurisdiction or authority. 387 + 388 + ======================================================================= 389 + 390 + Creative Commons is not a party to its public 391 + licenses. Notwithstanding, Creative Commons may elect to apply one of 392 + its public licenses to material it publishes and in those instances 393 + will be considered the “Licensor.” The text of the Creative Commons 394 + public licenses is dedicated to the public domain under the CC0 Public 395 + Domain Dedication. Except for the limited purpose of indicating that 396 + material is shared under a Creative Commons public license or as 397 + otherwise permitted by the Creative Commons policies published at 398 + creativecommons.org/policies, Creative Commons does not authorize the 399 + use of the trademark "Creative Commons" or any other trademark or logo 400 + of Creative Commons without its prior written consent including, 401 + without limitation, in connection with any unauthorized modifications 402 + to any of its public licenses or any other arrangements, 403 + understandings, or agreements concerning use of licensed material. For 404 + the avoidance of doubt, this paragraph does not form part of the 405 + public licenses. 406 + 407 + Creative Commons may be contacted at creativecommons.org.
+51 -41
Readme.md
··· 1 1 # shadow shmup 2 2 3 - The shadows are coming for you! Can you survive the onslaught? Move with WASD, auto-fires nearest enemy. 3 + Browsergame, fun and casual top-down, roguelike shoot 'em up with shadow elements and colorful neon effects, developed for the [2024 piratejam](https://itch.io/jam/pirate), with the theme "shadows and alchemy". 4 4 5 - ## sounds 5 + See the submission on [itch.io](https://flo-bit.itch.io/shadow-shmup). 6 6 7 - - gun shooting 8 - - knife sound 9 - - chainsaw sound 7 + WIP. See the current development version on [GitHub Pages](https://flo-bit.github.io/shadow-shmup/). 10 8 11 - - player getting hit 12 - - enemy getting hit 13 - - player dying 9 + ## How to play 10 + 11 + - Move with WASD or arrow keys 12 + - Player auto-shoots nearest enemy 13 + 14 + For co-op mode: 15 + 16 + - Player 1: WASD to move 17 + - Player 2: arrow keys to move 14 18 15 - - enemy shooting 16 - - enemy exploding 17 - - enemy dying 19 + ## Techstack 18 20 19 - - collecting coins 20 - - collecting powerup 21 + PixiJS, Typescript, Vite, TailwindCSS, Rapier 21 22 22 - - click (selecting upgrade) 23 - - level up 24 23 24 + ## Development 25 + 26 + 1. Clone the repository 27 + 28 + ```bash 29 + git clone https://github.com/flo-bit/shadow-shmup.git 30 + ``` 31 + 32 + 2. Install dependencies 33 + 34 + ```bash 35 + npm install 36 + ``` 37 + 38 + 3. Run the development server 39 + 40 + ```bash 41 + npm run dev 42 + ``` 43 + 44 + 4. Open the game in your browser at [http://localhost:5173](http://localhost:5173) 45 + 46 + 47 + ## credits: 48 + 49 + - music: [Nicholas Palmer](https://soundcloud.com/nicholas-palmer-4) 50 + - code and concept: [flo-bit](https://github.com/flo-bit) and [unbedenklich](https://github.com/unbedenklich) 51 + 52 + ## License 53 + 54 + CC BY-NC 4.0 25 55 26 56 ## TODO 27 57 58 + - [ ] more enemies 59 + - [ ] laser dude 28 60 - [ ] stats 29 61 - [ ] count kills, deaths, damage taken, damage dealt 30 62 - [ ] show stats at end of game ··· 105 137 - [x] enemies should be lit by light distance instead of player distance 106 138 - [x] scale canvas so that it always shows a 1000x1000 area or something like that 107 139 108 - ## Development 109 - 110 - 1. Clone the repository 111 - 112 - ```bash 113 - git clone https://github.com/flo-bit/shadow-shmup.git 114 - ``` 115 - 116 - 2. Install dependencies 117 - 118 - ```bash 119 - npm install 120 - ``` 121 - 122 - 3. Run the development server 123 - 124 - ```bash 125 - npm run dev 126 - ``` 140 + ## Bugs 127 141 128 - 4. Open the game in your browser at [http://localhost:5173](http://localhost:5173) 129 - 130 - 131 - ## credits: 132 - 133 - - music: [Nicholas Palmer](https://soundcloud.com/nicholas-palmer-4) 134 - - sound effects: [Kenney](https://kenney.nl/) 142 + - [ ] sometimes stuck in wall (moving when low fps or spawn in wall) 143 + - [ ] weapons still active after player death (in coop) 144 + - [ ] bloom sometimes doesn't work (line enemy?)
-29
index.html
··· 157 157 </svg> 158 158 </a> 159 159 </div> 160 - <div class="flex items-center gap-3"> 161 - sound effects by <span class="font-semibold">kenney</span> 162 - 163 - <a href="https://kenney.nl/" target="_blank"> 164 - <svg 165 - xmlns="http://www.w3.org/2000/svg" 166 - viewBox="0 0 24 24" 167 - fill="currentColor" 168 - class="h-4 fill-white hover:fill-cyan-400" 169 - > 170 - <path 171 - 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" 172 - /> 173 - </svg> 174 - </a> 175 - 176 - <a href="https://x.com/KenneyNL" target="_blank"> 177 - <svg 178 - viewBox="0 0 24 24" 179 - xmlns="http://www.w3.org/2000/svg" 180 - class="fill-white h-4 hover:fill-cyan-400" 181 - > 182 - <title>X</title> 183 - <path 184 - 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" 185 - /> 186 - </svg> 187 - </a> 188 - </div> 189 160 </div> 190 161 </div> 191 162
+19 -3
src/app.ts
··· 26 26 import { UpgradeManager } from './upgrades/upgrade-manager.js'; 27 27 import { Weapon } from './weapons/weapon.js'; 28 28 import { TextManager } from './visuals/text-manager.js'; 29 + import { VignetteFilter } from './helper/vignette.js'; 29 30 30 31 export default class Game { 31 32 container: PIXI.Container; ··· 78 79 79 80 minWidth = 700; 80 81 minHeight = 900; 82 + 83 + vignetteFilter?: VignetteFilter; 81 84 82 85 constructor() { 83 86 this.setup(); ··· 166 169 const bloomFilter = new AdvancedBloomFilter({ 167 170 threshold: 0.2, 168 171 quality: 32, 169 - bloomScale: 1.4, 172 + bloomScale: 1.3, 170 173 blur: 4 171 174 }); 172 - app.stage.filters = [bloomFilter]; 175 + this.vignetteFilter = new VignetteFilter({ 176 + color: 0xff0000, 177 + strength: 0.5 178 + }); 179 + app.stage.filters = [this.vignetteFilter, bloomFilter]; 173 180 174 181 // add the canvas to the HTML document 175 182 document.body.appendChild(app.canvas); ··· 195 202 let noise = createNoiseSprite(2000, 2000); 196 203 noise.anchor.set(0.5); 197 204 noise.scale.set(1); 198 - noise.alpha = 0.3; 205 + noise.alpha = 0.2; 199 206 noise.zIndex = 100; 200 207 this.mainContainer.addChild(noise); 201 208 202 209 app.ticker.add((ticker) => { 203 210 if (this.paused) return; 204 211 if (this.stats) this.stats.begin(); 212 + 213 + let alpha = Math.sin(this.playingTime * 0.001) * 0.5 + 0.5; 205 214 206 215 // get ellapsed time 207 216 const deltaTime = ticker.deltaMS; ··· 420 429 for (let players of this.playerManager?.players ?? []) { 421 430 players.health = players._maxHealth; 422 431 } 432 + } 433 + 434 + let timeSinceLastDamage = this.playerManager?.smallestTimeSinceLastDamage(); 435 + if (timeSinceLastDamage !== undefined && this.vignetteFilter && timeSinceLastDamage < 250) { 436 + this.vignetteFilter.alpha = (0.5 - timeSinceLastDamage / 500) * 0.5; 437 + } else if (this.vignetteFilter) { 438 + this.vignetteFilter.alpha = 0; 423 439 } 424 440 425 441 if (this.upgradeManager.canAdvance()) {
+28 -8
src/enemies/enemy.ts
··· 27 27 speed: number = 500; 28 28 29 29 shape?: PIXI.Graphics; 30 + highlight?: PIXI.Graphics; 31 + 32 + timeSinceLastHit: number = 10000; 30 33 31 34 exploding: boolean; 32 35 destroyTime: number; ··· 42 45 43 46 eyes?: PIXI.Container; 44 47 45 - color: number = 0xd946ef; 48 + color: number = 0xffffff; 46 49 47 50 type: number = -1; 48 51 ··· 62 65 63 66 game.container.addChild(this.enemyContainer); 64 67 65 - this.createShape(); 66 - 67 - this.maxHealth = 10; 68 + this.maxHealth = 30; 68 69 this.health = this.maxHealth; 69 70 70 - if (game.debug) this.createHealthBar(); 71 - 72 - this.createRidigBody(); 73 - 74 71 this.exploding = false; 75 72 this.destroyTime = -1; 76 73 ··· 78 75 79 76 this.isEnemy = true; 80 77 78 + if (game.debug) this.createHealthBar(); 79 + } 80 + 81 + setup() { 82 + this.createShape(); 83 + this.createRidigBody(); 81 84 this.createEyes(); 82 85 } 83 86 ··· 107 110 createShape() { 108 111 this.shape = new PIXI.Graphics().circle(0, 0, this.size / 2).fill(0); 109 112 this.enemyContainer.addChild(this.shape); 113 + 114 + this.highlight = new PIXI.Graphics().circle(0, 0, this.size / 2).fill(this.color); 115 + this.highlight.alpha = 0; 116 + this.enemyContainer.addChild(this.highlight); 110 117 } 111 118 112 119 createHealthBar() { ··· 227 234 this.updateEyes(deltaTime, nearestPlayer, distance); 228 235 229 236 this.enemyContainer.position.set(this.x, this.y); 237 + 238 + this.updateHighlight(deltaTime); 239 + } 240 + 241 + updateHighlight(deltaTime: number) { 242 + this.timeSinceLastHit += deltaTime; 243 + if (this.highlight) { 244 + if (this.timeSinceLastHit < 400) 245 + this.highlight.alpha = (1 - this.timeSinceLastHit / 400) * 0.8; 246 + else this.highlight.alpha = 0; 247 + } 230 248 } 231 249 232 250 attack(deltaTime: number, nearestPlayer: Player, dx: number, dy: number, distance: number) { ··· 276 294 this.health -= amount; 277 295 278 296 if (this.healthBar) this.healthBar.width = (this.health / this.maxHealth) * this.size; 297 + 298 + this.timeSinceLastHit = 0; 279 299 280 300 if (this.health <= 0) { 281 301 this.destroy();
+157
src/enemies/line-enemy.ts
··· 1 + import * as PIXI from 'pixi.js'; 2 + import { RAPIER } from '../helper/rapier'; 3 + import { Vector2 } from '@dimforge/rapier2d'; 4 + 5 + import Game from '../app'; 6 + import Enemy from './enemy'; 7 + import Player from '../player/player'; 8 + 9 + export class LineEnemy extends Enemy { 10 + color = 0x22c55e; 11 + 12 + line: PIXI.Graphics; 13 + 14 + cooldown: number = 0; 15 + fireTime: number = 1000; 16 + fireRate: number = 1000; 17 + 18 + currentTime: number = 0; 19 + 20 + constructor(game: Game) { 21 + super(game); 22 + 23 + this.type = 0; 24 + this.speed = 600; 25 + 26 + this.line = new PIXI.Graphics(); 27 + this.enemyContainer.addChild(this.line); 28 + 29 + this.setup(); 30 + } 31 + 32 + createEyes() { 33 + super.createEyes(); 34 + } 35 + 36 + createShape(): void { 37 + this.size = 20; 38 + this.shape = new PIXI.Graphics() 39 + .rect(-this.size * 2, -this.size / 4, this.size * 4, this.size / 2) 40 + .fill(0); 41 + this.enemyContainer.addChild(this.shape); 42 + 43 + this.highlight = new PIXI.Graphics() 44 + .rect(-this.size * 2, -this.size / 4, this.size * 4, this.size / 2) 45 + .fill(this.color); 46 + 47 + this.enemyContainer.addChild(this.highlight); 48 + this.highlight.alpha = 0; 49 + } 50 + 51 + createRidigBody(): void { 52 + const rigidBodyDesc = RAPIER() 53 + .RigidBodyDesc.dynamic() 54 + .setTranslation(this.x, this.y) 55 + .lockRotations() 56 + .setLinearDamping(1); 57 + this.rigidBody = this.game.world.createRigidBody(rigidBodyDesc); 58 + 59 + const colliderDesc = RAPIER() 60 + .ColliderDesc.cuboid(this.size * 2, this.size / 4) 61 + .setActiveEvents(RAPIER().ActiveEvents.COLLISION_EVENTS) 62 + .setCollisionGroups(0x00020007) 63 + .setDensity(1); 64 + 65 + this.game.world.createCollider(colliderDesc, this.rigidBody); 66 + 67 + this.rigidBody.userData = this; 68 + } 69 + 70 + updateVisuals( 71 + deltaTime: number, 72 + nearestPlayer: Player, 73 + dx: number, 74 + dy: number, 75 + distance: number 76 + ) { 77 + // move eyes 78 + const angle = Math.atan2(dy, dx); 79 + 80 + const rotationSpeed = 0.0003; 81 + this.rotation += rotationSpeed * deltaTime; 82 + 83 + // move eyes 84 + this.leftEye?.move(-this.rotation + angle); 85 + this.rightEye?.move(-this.rotation + angle); 86 + 87 + this.updateEyes(deltaTime, nearestPlayer, distance); 88 + 89 + this.enemyContainer.position.set(this.x, this.y); 90 + 91 + this.updateHighlight(deltaTime); 92 + 93 + const ray = new (RAPIER().Ray)( 94 + new (RAPIER().Vector2)(this.x, -this.y), 95 + new (RAPIER().Vector2)(Math.cos(this.rotation), -Math.sin(this.rotation)) 96 + ); 97 + 98 + let hitPoint = { x: 0, y: 0 }; 99 + 100 + const rayLength = 10000000; 101 + 102 + const hit = this.game.world.castRay(ray, rayLength, true, undefined, 0x00090009); 103 + 104 + if (hit) { 105 + let hitRB = hit.collider.parent(); 106 + if (hitRB?.userData instanceof Player) { 107 + hitRB.userData.takeDamage(deltaTime * 0.05); 108 + } 109 + hitPoint = ray.pointAt(hit.toi); 110 + } else { 111 + hitPoint.x = ray.dir.x * rayLength; 112 + hitPoint.y = ray.dir.y * rayLength; 113 + } 114 + 115 + let x = hitPoint.x - this.x; 116 + let y = -hitPoint.y - this.y; 117 + 118 + let length = Math.hypot(x, y); 119 + 120 + this.line.clear(); 121 + this.line.moveTo(0, 0); 122 + this.line.lineTo(length, 0); 123 + 124 + this.line.stroke({ width: 4, color: this.color }); 125 + } 126 + 127 + attack(deltaTime: number, nearestPlayer: Player, dx: number, dy: number, distance: number): void { 128 + if (this.destroyed) return; 129 + 130 + const x = Math.cos(this.rotation + Math.PI / 2); 131 + const y = Math.sin(this.rotation + Math.PI / 2); 132 + 133 + let hitPoint = { x: 0, y: 0 }; 134 + } 135 + 136 + move(deltaTime: number, nearestPlayer: Player, dx: number, dy: number, distance: number) { 137 + if (distance > 300) { 138 + const mult = deltaTime * 120 * 0.001; 139 + const x = (dx / distance) * this.speed * mult; 140 + const y = (dy / distance) * this.speed * mult; 141 + 142 + this.rigidBody?.applyImpulse({ x: x, y: -y }, true); 143 + 144 + // if too far away (> 2000), teleport to player 145 + if (distance > 2000) { 146 + this.setPositionNearPlayer(); 147 + console.log('teleporting', this.x, this.y); 148 + } 149 + } 150 + } 151 + 152 + destroy(dropItem: boolean = true): void { 153 + if (this.destroyed) return; 154 + 155 + super.destroy(dropItem); 156 + } 157 + }
+10 -1
src/enemies/pentagon-enemies.ts
··· 10 10 export class PentagonEnemy extends Enemy { 11 11 weapon: GunWeapon; 12 12 13 + color = 0x6366f1; 14 + 13 15 constructor(game: Game) { 14 16 super(game); 15 17 ··· 27 29 }); 28 30 29 31 this.speed = 900; 32 + 33 + this.setup(); 30 34 } 31 35 32 36 createEyes() { 33 - this.color = 0x6366f1; 34 37 super.createEyes(); 35 38 } 36 39 ··· 43 46 } 44 47 this.shape = new PIXI.Graphics().poly(points).fill(0); 45 48 this.enemyContainer.addChild(this.shape); 49 + 50 + this.highlight = new PIXI.Graphics().poly(points).fill(this.color); 51 + this.enemyContainer.addChild(this.highlight); 52 + this.highlight.alpha = 0; 46 53 } 47 54 48 55 createRidigBody(): void { ··· 93 100 this.updateEyes(deltaTime, nearestPlayer, distance); 94 101 95 102 this.enemyContainer.position.set(this.x, this.y); 103 + 104 + this.updateHighlight(deltaTime); 96 105 } 97 106 98 107 attack(deltaTime: number, nearestPlayer: Player, dx: number, dy: number, distance: number): void {
+4
src/enemies/sphere-enemies.ts
··· 20 20 21 21 burstNumber: number = 10; 22 22 23 + color: number = 0xd946ef; 24 + 23 25 constructor(game: Game) { 24 26 super(game); 25 27 ··· 40 42 this.createIndicator(); 41 43 42 44 this.speed = 300; 45 + 46 + this.setup(); 43 47 } 44 48 45 49 async createIndicator() {
+13 -1
src/enemies/triangle-enemies.ts
··· 10 10 export class TriangleEnemy extends Enemy { 11 11 projectile: Projectile; 12 12 13 + color = 0x0ea5e9; 14 + 13 15 constructor(game: Game) { 14 16 super(game); 15 17 ··· 31 33 collisionGroups: 0x00100001, 32 34 color: this.color 33 35 }); 36 + 37 + this.setup(); 34 38 } 35 39 36 40 createEyes() { 37 - this.color = 0x0ea5e9; 38 41 super.createEyes(); 39 42 } 40 43 ··· 44 47 .poly([-this.size, -this.size / 2, this.size, -this.size / 2, 0, this.size]) 45 48 .fill(0); 46 49 this.enemyContainer.addChild(this.shape); 50 + 51 + this.highlight = new PIXI.Graphics() 52 + .poly([-this.size, -this.size / 2, this.size, -this.size / 2, 0, this.size]) 53 + .fill(this.color); 54 + 55 + this.enemyContainer.addChild(this.highlight); 56 + this.highlight.alpha = 0; 47 57 } 48 58 49 59 createRidigBody(): void { ··· 88 98 this.rotation = angle - Math.PI / 2; 89 99 90 100 this.enemyContainer.position.set(this.x, this.y); 101 + 102 + this.updateHighlight(deltaTime); 91 103 } 92 104 93 105 attack(deltaTime: number, nearestPlayer: Player, dx: number, dy: number, distance: number): void {
+9
src/helper/rapier.ts
··· 6 6 // @ts-ignore 7 7 return window.RAPIER; 8 8 } 9 + 10 + export const COLLISION_GROUPS = { 11 + PLAYER: 0x0001, 12 + ENEMY: 0x0002, 13 + PROJECTILE: 0x0004, 14 + ITEM: 0x0008, 15 + LIGHT: 0x0010, 16 + ALL: 0xffff 17 + };
+267
src/helper/vignette.ts
··· 1 + import { Filter, GlProgram, GpuProgram } from 'pixi.js'; 2 + import type { FilterSystem, RenderSurface, Texture } from 'pixi.js'; 3 + 4 + const vertex = `in vec2 aPosition; 5 + out vec2 vTextureCoord; 6 + 7 + uniform vec4 uInputSize; 8 + uniform vec4 uOutputFrame; 9 + uniform vec4 uOutputTexture; 10 + 11 + vec4 filterVertexPosition( void ) 12 + { 13 + vec2 position = aPosition * uOutputFrame.zw + uOutputFrame.xy; 14 + 15 + position.x = position.x * (2.0 / uOutputTexture.x) - 1.0; 16 + position.y = position.y * (2.0*uOutputTexture.z / uOutputTexture.y) - uOutputTexture.z; 17 + 18 + return vec4(position, 0.0, 1.0); 19 + } 20 + 21 + vec2 filterTextureCoord( void ) 22 + { 23 + return aPosition * (uOutputFrame.zw * uInputSize.zw); 24 + } 25 + 26 + void main(void) 27 + { 28 + gl_Position = filterVertexPosition(); 29 + vTextureCoord = filterTextureCoord(); 30 + } 31 + `; 32 + 33 + const wgslVertex = ` 34 + struct GlobalFilterUniforms { 35 + uInputSize:vec4<f32>, 36 + uInputPixel:vec4<f32>, 37 + uInputClamp:vec4<f32>, 38 + uOutputFrame:vec4<f32>, 39 + uGlobalFrame:vec4<f32>, 40 + uOutputTexture:vec4<f32>, 41 + }; 42 + 43 + @group(0) @binding(0) var<uniform> gfu: GlobalFilterUniforms; 44 + 45 + struct VSOutput { 46 + @builtin(position) position: vec4<f32>, 47 + @location(0) uv : vec2<f32> 48 + }; 49 + 50 + fn filterVertexPosition(aPosition:vec2<f32>) -> vec4<f32> 51 + { 52 + var position = aPosition * gfu.uOutputFrame.zw + gfu.uOutputFrame.xy; 53 + 54 + position.x = position.x * (2.0 / gfu.uOutputTexture.x) - 1.0; 55 + position.y = position.y * (2.0*gfu.uOutputTexture.z / gfu.uOutputTexture.y) - gfu.uOutputTexture.z; 56 + 57 + return vec4(position, 0.0, 1.0); 58 + } 59 + 60 + fn filterTextureCoord( aPosition:vec2<f32> ) -> vec2<f32> 61 + { 62 + return aPosition * (gfu.uOutputFrame.zw * gfu.uInputSize.zw); 63 + } 64 + 65 + fn globalTextureCoord( aPosition:vec2<f32> ) -> vec2<f32> 66 + { 67 + return (aPosition.xy / gfu.uGlobalFrame.zw) + (gfu.uGlobalFrame.xy / gfu.uGlobalFrame.zw); 68 + } 69 + 70 + fn getSize() -> vec2<f32> 71 + { 72 + return gfu.uGlobalFrame.zw; 73 + } 74 + 75 + @vertex 76 + fn mainVertex( 77 + @location(0) aPosition : vec2<f32>, 78 + ) -> VSOutput { 79 + return VSOutput( 80 + filterVertexPosition(aPosition), 81 + filterTextureCoord(aPosition) 82 + ); 83 + } 84 + `; 85 + 86 + const fragment = ` 87 + precision highp float; 88 + in vec2 vTextureCoord; 89 + out vec4 finalColor; 90 + 91 + uniform sampler2D uTexture; 92 + uniform vec2 uDimensions; 93 + uniform vec4 uVignetteColor; 94 + uniform float uVignetteStrength; 95 + uniform float uVignetteBlur; 96 + uniform float uAspectRatio; 97 + 98 + uniform vec4 uInputSize; 99 + 100 + const float SQRT_2 = 1.414213; 101 + 102 + void main() 103 + { 104 + 105 + finalColor = texture(uTexture, vTextureCoord); 106 + vec3 color = finalColor.rgb; 107 + 108 + vec2 coord = vTextureCoord * uInputSize.xy / uDimensions.xy; 109 + 110 + float vignetteAlpha = uVignetteColor.a; 111 + 112 + float outter = SQRT_2 - uVignetteStrength * SQRT_2; 113 + vec2 dir = vec2(vec2(0.5, 0.5) - coord); 114 + dir.y *= uDimensions.y / uDimensions.x; 115 + float darker = clamp((outter - length(dir) * SQRT_2) / ( 0.00001 + uVignetteBlur * SQRT_2), 0.0, 1.0); 116 + 117 + color.rgb = mix(uVignetteColor.rgb, color.rgb, darker + (1.0 - darker) * (1.0 - vignetteAlpha)); 118 + 119 + finalColor.rgb = color; 120 + } 121 + `; 122 + 123 + const wgslSource = ` 124 + struct VignetteUniforms { 125 + uDimensions: vec2<f32>, 126 + uVignetteColor: vec4<f32>, 127 + uVignetteRadius: f32, 128 + uVignetteStrength: f32, 129 + uAspectRatio: f32, 130 + }; 131 + 132 + struct GlobalFilterUniforms { 133 + uInputSize: vec4<f32>, 134 + uInputPixel: vec4<f32>, 135 + uInputClamp: vec4<f32>, 136 + uOutputFrame: vec4<f32>, 137 + uGlobalFrame: vec4<f32>, 138 + uOutputTexture: vec4<f32>, 139 + }; 140 + 141 + @group(0) @binding(0) var<uniform> gfu: GlobalFilterUniforms; 142 + @group(0) @binding(1) var uTexture: texture_2d<f32>; 143 + @group(0) @binding(2) var uSampler: sampler; 144 + @group(1) @binding(0) var<uniform> vignetteUniforms: VignetteUniforms; 145 + 146 + @fragment 147 + fn mainFragment( 148 + @builtin(position) position: vec4<f32>, 149 + @location(0) uv: vec2<f32> 150 + ) -> @location(0) vec4<f32> { 151 + var color = textureSample(uTexture, uSampler, uv); 152 + 153 + var coord = uv - 0.5; 154 + coord.x *= vignetteUniforms.uAspectRatio; 155 + 156 + let dist = length(coord); 157 + let maxDist = sqrt(0.5 * vignetteUniforms.uAspectRatio * vignetteUniforms.uAspectRatio + 0.5); 158 + let vignette = smoothstep(vignetteUniforms.uVignetteRadius, vignetteUniforms.uVignetteRadius - vignetteUniforms.uVignetteStrength, dist / maxDist); 159 + 160 + color = mix(vignetteUniforms.uVignetteColor, color, vignette); 161 + 162 + return color; 163 + } 164 + `; 165 + 166 + export interface VignetteFilterOptions { 167 + color?: number; 168 + alpha?: number; 169 + blur?: number; 170 + strength?: number; 171 + } 172 + 173 + export class VignetteFilter extends Filter { 174 + public static readonly DEFAULT_OPTIONS: VignetteFilterOptions = { 175 + color: 0x000000, 176 + alpha: 1, 177 + strength: 0.5, 178 + blur: 0.2 179 + }; 180 + 181 + constructor(options?: VignetteFilterOptions) { 182 + options = { ...VignetteFilter.DEFAULT_OPTIONS, ...options }; 183 + 184 + const gpuProgram = GpuProgram.from({ 185 + vertex: { 186 + source: wgslVertex, 187 + entryPoint: 'mainVertex' 188 + }, 189 + fragment: { 190 + source: wgslSource, 191 + entryPoint: 'mainFragment' 192 + } 193 + }); 194 + 195 + const glProgram = GlProgram.from({ 196 + vertex, 197 + fragment, 198 + name: 'vignette-filter' 199 + }); 200 + 201 + super({ 202 + gpuProgram, 203 + glProgram, 204 + resources: { 205 + vignetteUniforms: { 206 + uDimensions: { value: new Float32Array(2), type: 'vec2<f32>' }, 207 + uVignetteColor: { value: new Float32Array(4), type: 'vec4<f32>' }, 208 + uVignetteStrength: { value: options.strength, type: 'f32' }, 209 + uAspectRatio: { value: 1, type: 'f32' }, 210 + uVignetteBlur: { value: options.blur, type: 'f32' } 211 + } 212 + } 213 + }); 214 + 215 + this.color = options.color!; 216 + this.alpha = options.alpha!; 217 + } 218 + 219 + apply( 220 + filterManager: FilterSystem, 221 + input: Texture, 222 + output: RenderSurface, 223 + clearMode: boolean 224 + ): void { 225 + const { uniforms } = this.resources.vignetteUniforms; 226 + uniforms.uDimensions[0] = input.frame.width; 227 + uniforms.uDimensions[1] = input.frame.height; 228 + uniforms.uAspectRatio = input.frame.width / input.frame.height; 229 + 230 + filterManager.applyFilter(this, input, output, clearMode); 231 + } 232 + 233 + get color(): number { 234 + return ( 235 + ((this.resources.vignetteUniforms.uniforms.uVignetteColor[0] * 255) << 16) | 236 + ((this.resources.vignetteUniforms.uniforms.uVignetteColor[1] * 255) << 8) | 237 + (this.resources.vignetteUniforms.uniforms.uVignetteColor[2] * 255) 238 + ); 239 + } 240 + set color(value: number) { 241 + const { uniforms } = this.resources.vignetteUniforms; 242 + uniforms.uVignetteColor[0] = ((value >> 16) & 0xff) / 255; 243 + uniforms.uVignetteColor[1] = ((value >> 8) & 0xff) / 255; 244 + uniforms.uVignetteColor[2] = (value & 0xff) / 255; 245 + } 246 + 247 + get alpha(): number { 248 + return this.resources.vignetteUniforms.uniforms.uVignetteColor[3]; 249 + } 250 + set alpha(value: number) { 251 + this.resources.vignetteUniforms.uniforms.uVignetteColor[3] = value; 252 + } 253 + 254 + get strength(): number { 255 + return this.resources.vignetteUniforms.uniforms.uVignetteStrength; 256 + } 257 + set strength(value: number) { 258 + this.resources.vignetteUniforms.uniforms.uVignetteStrength = value; 259 + } 260 + 261 + get radius(): number { 262 + return this.resources.vignetteUniforms.uniforms.uVignetteRadius; 263 + } 264 + set radius(value: number) { 265 + this.resources.vignetteUniforms.uniforms.uVignetteRadius = value; 266 + } 267 + }
+1 -1
src/player/player.ts
··· 66 66 this._maxHealth = 100; 67 67 this._health = this._maxHealth; 68 68 69 - this.color = num === 0 ? 0xbe123c : 0x4f46e5; 69 + this.color = num === 0 ? 0xbe123c : 0xf97316; 70 70 71 71 this.playerContainer = new PIXI.Container(); 72 72 game.container.addChild(this.playerContainer);
+1 -1
src/visuals/light-manager.ts
··· 1 - import Game from './app'; 1 + import Game from '../app'; 2 2 import { Light, LightOptions } from './light'; 3 3 4 4 export class LightManager {
+1 -1
src/visuals/light.ts
··· 189 189 this.shadow.lineTo(firstPoint.x, firstPoint.y); 190 190 } 191 191 } 192 - this.shadow.fill(0, 0); 192 + this.shadow.fill({ color: 0, alpha: 0 }); 193 193 } 194 194 195 195 update(deltaTime: number) {
+14
src/wave.ts
··· 1 1 import Game from './app'; 2 2 import Enemy from './enemies/enemy'; 3 + import { LineEnemy } from './enemies/line-enemy'; 3 4 4 5 import { PentagonEnemy } from './enemies/pentagon-enemies'; 5 6 import { SphereEnemy } from './enemies/sphere-enemies'; ··· 143 144 144 145 // Example usage: 145 146 const exampleWaves: WaveData[] = [ 147 + // { 148 + // enemies: [ 149 + // { 150 + // type: LineEnemy, 151 + // spawnRate: 1, 152 + // upgradeFunction: (enemy) => { 153 + // enemy.health *= 1.5; 154 + // enemy.value *= 2; 155 + // } 156 + // } 157 + // ], 158 + // neededItems: 10 159 + // }, 146 160 // wave 1 147 161 { 148 162 enemies: [