├── .babelrc ├── .eslintrc.json ├── .github └── workflows │ └── linters.yml ├── .gitignore ├── .prettierrc ├── .stylelintrc.json ├── LICENSE.txt ├── README.md ├── docs ├── js-game-screenshot.gif ├── logos │ ├── es6.png │ ├── eslint.png │ ├── jest.png │ ├── npm.png │ ├── phaser.png │ └── webpack.png └── screenshot.jpg ├── jest.config.js ├── package-lock.json ├── package.json ├── setupTests.js ├── src ├── audio │ ├── background.mp3 │ ├── background.ogg │ ├── enemyShoot.ogg │ ├── enemyShoot.wav │ ├── explode.ogg │ ├── explode.wav │ ├── playerShoot.ogg │ └── playerShoot.wav ├── images │ ├── background.jpg │ ├── bullet.png │ ├── enemy.png │ ├── enemyBullet.png │ ├── exp.png │ ├── player copy.png │ ├── player.png │ ├── rocks.png │ ├── target.png │ ├── title.png │ └── ui │ │ ├── buttons │ │ ├── 1 │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ ├── 3.png │ │ │ ├── 4.png │ │ │ ├── 5.png │ │ │ └── 6.png │ │ └── 2 │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ ├── 3.png │ │ │ ├── 4.png │ │ │ ├── 5.png │ │ │ └── 6.png │ │ ├── icons │ │ ├── music_off.png │ │ ├── music_on.png │ │ ├── sfx_off.png │ │ └── sfx_on.png │ │ └── toggles │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ └── 6.png ├── js │ ├── classes │ │ ├── comps │ │ │ ├── bar.js │ │ │ └── scoreBox.js │ │ ├── modelAndController │ │ │ ├── controller.js │ │ │ └── model.js │ │ ├── ui │ │ │ ├── flatButton.js │ │ │ ├── soundButtons.js │ │ │ └── toggleButton.js │ │ └── util │ │ │ ├── align.js │ │ │ ├── alignGrid.js │ │ │ ├── eventEmitter.js │ │ │ ├── mediaManager.js │ │ │ └── serviceApi.js │ ├── constants.js │ ├── game.js │ ├── index.js │ └── scenes │ │ ├── sceneBoot.js │ │ ├── sceneLeaderboard.js │ │ ├── sceneMain.js │ │ ├── scenePreloader.js │ │ └── sceneTitle.js └── styles │ └── style.css ├── tests ├── eventEmitter.test.js ├── flatButton.test.js ├── mocks │ ├── fileMock.js │ └── styleMock.js ├── model.test.js ├── sceneBoot.test.js ├── sceneLeaderboard.test.js ├── sceneMain.test.js ├── scenePreloader.test.js ├── sceneTitle.test.js ├── scoreBox.test.js └── serviceApi.test.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ] 5 | } -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "jest": true 6 | }, 7 | "parser": "babel-eslint", 8 | "parserOptions": { 9 | "ecmaVersion": 2018, 10 | "sourceType": "module" 11 | }, 12 | "extends": ["airbnb-base"], 13 | "rules": { 14 | "no-shadow": "off", 15 | "no-param-reassign": "off", 16 | "eol-last": "off", 17 | "arrow-parens": "off" 18 | }, 19 | "ignorePatterns": [ 20 | "dist/", 21 | "build/" 22 | ] 23 | } -------------------------------------------------------------------------------- /.github/workflows/linters.yml: -------------------------------------------------------------------------------- 1 | name: Linters 2 | 3 | on: pull_request 4 | 5 | env: 6 | FORCE_COLOR: 1 7 | 8 | jobs: 9 | eslint: 10 | name: ESLint 11 | runs-on: ubuntu-18.04 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-node@v1 15 | with: 16 | node-version: "12.x" 17 | - name: Setup ESLint 18 | run: | 19 | npm install --save-dev eslint@6.8.x eslint-config-airbnb-base@14.1.x eslint-plugin-import@2.20.x babel-eslint@10.1.x 20 | [ -f .eslintrc.json ] || wget https://raw.githubusercontent.com/microverseinc/linters-config/master/javascript/.eslintrc.json 21 | - name: ESLint Report 22 | run: npx eslint . 23 | stylelint: 24 | name: Stylelint 25 | runs-on: ubuntu-18.04 26 | steps: 27 | - uses: actions/checkout@v2 28 | - uses: actions/setup-node@v1 29 | with: 30 | node-version: "12.x" 31 | - name: Setup Stylelint 32 | run: | 33 | npm install --save-dev stylelint@13.3.x stylelint-scss@3.17.x stylelint-config-standard@20.0.x stylelint-csstree-validator 34 | [ -f .stylelintrc.json ] || wget https://raw.githubusercontent.com/microverseinc/linters-config/master/javascript/.stylelintrc.json 35 | - name: Stylelint Report 36 | run: npx stylelint "**/*.{css,scss}" 37 | 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | .DS_Store 107 | src/.DS_Store 108 | 109 | dist 110 | dist/* -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false 4 | } 5 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["stylelint-config-standard"], 3 | "plugins": ["stylelint-scss", "stylelint-csstree-validator"], 4 | "rules": { 5 | "at-rule-no-unknown": null, 6 | "scss/at-rule-no-unknown": true, 7 | "csstree/validator": true 8 | }, 9 | "ignoreFiles": ["build/**", "dist/**", "**/reset*.css", "**/bootstrap*.css"] 10 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Bereket A. Beshane 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Contributors][contributors-shield]][contributors-url] 2 | [![Forks][forks-shield]][forks-url] 3 | [![Stargazers][stars-shield]][stars-url] 4 | [![Issues][issues-shield]][issues-url] 5 | [![MIT License][license-shield]][license-url] 6 | [![LinkedIn][linkedin-shield]][linkedin-url] 7 | 8 | 9 |
10 |

11 | 12 | Logo 13 | 14 |
15 | 16 | Logo 17 | 18 | 19 |

20 | 21 | phaser 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |

33 | 34 |

Cosmos Wars: Shooter Game

35 | 36 |

37 | A shooter game developed with phaser 3.5 game development framework 38 |
39 | Explore the docs » 40 |
41 |
42 | Play the game online 43 | · 44 | Report Bug 45 | · 46 | Request Feature 47 |

48 |

49 | 50 | 51 | 52 | # Table of Contents 53 | 54 |
55 |
    56 |
  1. 57 | About The Project 58 | 63 |
  2. 64 |
  3. 65 | Getting Started 66 | 72 |
  4. 73 |
  5. Author
  6. 74 |
  7. Contributing
  8. 75 |
  9. License
  10. 76 |
  11. Acknowledgments
  12. 77 |
78 |
79 | 80 | 81 | 82 | ## About The Project 83 | 84 | 85 | 86 | It is an HTML5 game developed with a [Phaser](https://phaser.io/) game development library. The game was inspired by this [Space Shooter project](https://learn.yorkcs.com/category/tutorials/gamedev/phaser-3/build-a-space-shooter-with-phaser-3/) and implemented by using game development best practices as much as possible. 87 | 88 | The specific [requirements](https://www.notion.so/Shooter-game-203e819041c7486bb36f9e65faecba27) are followed throughout the project. The game works on desktop computers and mobile devices. Several features are developed to work differently for mobile devices specifically because there is no keyboard in mobile devices and another way of player relocation must be done. 89 | 90 | ### Instructions 91 | 92 | You will start this game by having control of a player spaceship to move around and destroy an enemy ship. Read the control instruction carefully below. 93 | 94 | - The game starts by loading and asking you to insert your name. After that, click on the Play button to start the game. 95 | - You will be spawned on the top left corner of the screen. 96 | - From there, the user can navigate by clicking on the specific location to which you want to go. The user can rotate the ship body using the mouse on desktop computers. The rotation feature in a mobile device is calculated automatically when the aimed location to move is clicked. 97 | - The User can fire a bullet by clicking the `spacebar key` on the keyboard. The target location to which the bullet is sent is specified by the mouse `pointer`. 98 | - The enemy ship is designed to chase the player ship if the user gets close to it. The enemy ship generally goes slower than the user ship, although when it is attacked by the player ship, it increases its speed and towards the player ship. 99 | - Player and enemy powers (healths) are displayed on the top of the screen. 100 | - There are extra disturbers in the game, there are stones. The player's power gets lower when they touch the player. However, the player can destroy them by fire bullets at them and can get a score. 101 | 102 | - When the player dies, it can restart the game or can go to the leaderboard scene. Either way, your score, and name will be uploaded to the leaderboard API. 103 | - On the title screen and during the game, the user can mute or unmute the background music of the game by clicking the music icon on the bottom left corner of the screen. The user can also turn audios (such as shooting, explosion) on and off if needed by clicking the sound icon in the bottom right corner of the screen. you can navigate to the options screen to turn audio on/off. 104 | 105 | ### Live Demo 106 | 107 | [Play the game online on GitHub Page](https://raminmammadzada.github.io/js-shooter-game/index.html) 108 | 109 | ### Built With 110 | 111 | - [Phaser 3.5](https://phaser.io/) 112 | - Javascript ES6 113 | - Canvas 114 | - HTML 115 | - npm 116 | - Webpack 117 | - Jest 118 | - Eslint 119 | - Babel 120 | 121 | 122 | 123 | ## Getting Started 124 | 125 | To set this project upon a local environment clone, or download this repo and follow the instructions below. 126 | 127 | ### Prerequisites 128 | 129 | - Web browser 130 | - CLI tool 131 | - npm 132 | `npm install npm@latest -g` 133 | 134 | **Very Important Note: Some browsers may have their GPU hardware acceleration turned off. You have to turn this feature or for a smooth gaming experience. For example, use [this](https://www.lifewire.com/hardware-acceleration-in-chrome-4125122) instruction to turn chrome's GPU feature on if you experience a lag** 135 | 136 | ### Installation 137 | 138 | 1. Navigate to the root folder of your local copy and, using any CLI tool, type `npm install`, and press Enter. These will install the dependencies. 139 | 2. After it is completed, 140 | 141 | - If you want to repack the file 142 | - Run `npm run build` to pack the files in the src folder 143 | - Go to the build folder and open index.html 144 | - Else 145 | - Run `npm run start` to open it on a live server. In webpack config, the port is set to 8072. 146 | 147 | ### Testing 148 | 149 | - Navigate to the root folder of your local copy and type `npm run test` or `npm run test-watch` to run jest. 150 | - You can add more tests in the `./test` directory. 151 | 152 | ### other information 153 | 154 | - if you run into `npm ERR! Unexpected end of JSON input while parsing near...` run the following in their respective order. `npm config set fetch-retry-maxtimeout 60000` then 155 | `npm cache clean --force && npm i` 156 | 157 | 158 | 159 | ## Contributing 160 | 161 | Contributions are what make the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. 162 | 163 | 1. Fork the Project 164 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) 165 | 3. Commit your changes (`git commit -m 'Add some AmazingFeature'`) 166 | 4. Push to the Branch (`git push origin feature/AmazingFeature`) 167 | 5. Open a Pull Request 168 | 169 | ## Author 170 | 171 | 👤 **Ramin Mammadzada** 172 | 173 | - Github: [@RaminMammadzada](https://github.com/RaminMammadzada) 174 | - Twitter: [@RaminMammadzada](https://twitter.com/RaminMammadzada) 175 | - Linkedin: [@RaminMammadzada](https://www.linkedin.com/in/raminmammadzada) 176 | 177 | 178 | 179 | ## License 180 | 181 | This project is [MIT]('./LICENSE.txt') licensed. 182 | 183 | ## Show your support 184 | 185 | Give a ⭐️ if you like this project! 186 | 187 | 188 | 189 | ## Acknowledgments 190 | 191 | - [Microverse](Microverse.org) 192 | - [Phaser.io](https://phaser.io/) 193 | - [Free Sound](http://freesound.org/) 194 | - [Open Game Art](https://opengameart.org/) 195 | - [Text Craft](https://textcraft.net) 196 | 197 | [contributors-shield]: https://img.shields.io/github/contributors/RaminMammadzada/js-shooter-game.svg?style=for-the-badge 198 | [contributors-url]: https://github.com/RaminMammadzada/js-shooter-game/graphs/contributors 199 | [forks-shield]: https://img.shields.io/github/forks/RaminMammadzada/js-shooter-game.svg?style=for-the-badge 200 | [forks-url]: https://github.com/RaminMammadzada/js-shooter-game/network/members 201 | [stars-shield]: https://img.shields.io/github/stars/RaminMammadzada/js-shooter-game.svg?style=for-the-badge 202 | [stars-url]: https://github.com/RaminMammadzada/js-shooter-game/stargazers 203 | [issues-shield]: https://img.shields.io/github/issues/RaminMammadzada/js-shooter-game.svg?style=for-the-badge 204 | [issues-url]: https://github.com/RaminMammadzada/js-shooter-game/issues 205 | [license-shield]: https://img.shields.io/github/license/RaminMammadzada/js-shooter-game.svg?style=for-the-badge 206 | [license-url]: https://github.com/RaminMammadzada/js-shooter-game/blob/development/LICENSE.txt 207 | [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555 208 | [linkedin-url]: https://www.linkedin.com/in/raminmammadzada/ 209 | [product-screenshot]: public/logo1.pngd 210 | -------------------------------------------------------------------------------- /docs/js-game-screenshot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/docs/js-game-screenshot.gif -------------------------------------------------------------------------------- /docs/logos/es6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/docs/logos/es6.png -------------------------------------------------------------------------------- /docs/logos/eslint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/docs/logos/eslint.png -------------------------------------------------------------------------------- /docs/logos/jest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/docs/logos/jest.png -------------------------------------------------------------------------------- /docs/logos/npm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/docs/logos/npm.png -------------------------------------------------------------------------------- /docs/logos/phaser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/docs/logos/phaser.png -------------------------------------------------------------------------------- /docs/logos/webpack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/docs/logos/webpack.png -------------------------------------------------------------------------------- /docs/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/docs/screenshot.jpg -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | setupFiles: ['./setupTests.js', 'jest-canvas-mock'], 3 | setupFilesAfterEnv: [ 4 | 'jest-expect-subclass', 5 | ], 6 | moduleNameMapper: { 7 | '\\.(css|less|sass|scss)$': '/tests/mocks/styleMock.js', 8 | '\\.(gif|ttf|eot|svg|png|mp3|ogg)$': '/tests/mocks/fileMock.js', 9 | }, 10 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-game-1", 3 | "jest": { 4 | "preset": "ts-jest", 5 | "testEnvironment": "node", 6 | "transform": { 7 | "node_modules/variables/.+\\.(j|t)sx?$": "ts-jest" 8 | }, 9 | "transformIgnorePatterns": [ 10 | "node_modules/(?!variables/.*)" 11 | ] 12 | }, 13 | "version": "1.0.0", 14 | "description": "", 15 | "main": "index.js", 16 | "scripts": { 17 | "test": "jest", 18 | "test-watch": "jest --watch *.js", 19 | "build": "webpack --config webpack.config.js ", 20 | "dev": "node_modules/.bin/webpack serve --mode=development --watch --hot", 21 | "start": "node_modules/.bin/webpack serve" 22 | }, 23 | "author": "", 24 | "license": "ISC", 25 | "devDependencies": { 26 | "@babel/core": "^7.12.10", 27 | "@babel/preset-env": "^7.12.11", 28 | "@fortawesome/fontawesome-free": "^5.15.1", 29 | "@popperjs/core": "^2.6.0", 30 | "babel-eslint": "^10.1.0", 31 | "babel-loader": "^8.2.2", 32 | "bootstrap": "^4.5.3", 33 | "css-loader": "^5.0.1", 34 | "eslint": "^6.8.0", 35 | "eslint-config-airbnb-base": "^14.1.0", 36 | "eslint-plugin-import": "^2.20.2", 37 | "file-loader": "^6.2.0", 38 | "html-webpack-plugin": "^4.5.0", 39 | "jest": "^26.6.3", 40 | "jest-canvas-mock": "^2.3.0", 41 | "jest-expect-subclass": "^1.0.1", 42 | "jest-fetch-mock": "^3.0.3", 43 | "jquery": "^3.5.1", 44 | "mini-css-extract-plugin": "^1.3.3", 45 | "node-fetch": "^2.6.1", 46 | "popper.js": "^1.16.1", 47 | "postcss-loader": "^4.1.0", 48 | "sass": "^1.30.0", 49 | "sass-loader": "^10.1.0", 50 | "sparticles": "^1.2.0", 51 | "style-loader": "^2.0.0", 52 | "stylelint": "^13.3.3", 53 | "stylelint-config-standard": "^20.0.0", 54 | "stylelint-csstree-validator": "^1.9.0", 55 | "stylelint-scss": "^3.17.2", 56 | "unsplash-js": "^7.0.2", 57 | "webpack": "^5.11.0", 58 | "webpack-cli": "^4.2.0", 59 | "webpack-dev-server": "^3.11.0" 60 | }, 61 | "dependencies": { 62 | "phaser": "^3.52.0", 63 | "phaser3-rex-plugins": "^1.1.39", 64 | "regenerator-runtime": "^0.13.7" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /setupTests.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | /* eslint-disable import/no-unresolved */ 3 | 4 | global.fetch = require('jest-fetch-mock'); -------------------------------------------------------------------------------- /src/audio/background.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/audio/background.mp3 -------------------------------------------------------------------------------- /src/audio/background.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/audio/background.ogg -------------------------------------------------------------------------------- /src/audio/enemyShoot.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/audio/enemyShoot.ogg -------------------------------------------------------------------------------- /src/audio/enemyShoot.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/audio/enemyShoot.wav -------------------------------------------------------------------------------- /src/audio/explode.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/audio/explode.ogg -------------------------------------------------------------------------------- /src/audio/explode.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/audio/explode.wav -------------------------------------------------------------------------------- /src/audio/playerShoot.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/audio/playerShoot.ogg -------------------------------------------------------------------------------- /src/audio/playerShoot.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/audio/playerShoot.wav -------------------------------------------------------------------------------- /src/images/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/background.jpg -------------------------------------------------------------------------------- /src/images/bullet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/bullet.png -------------------------------------------------------------------------------- /src/images/enemy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/enemy.png -------------------------------------------------------------------------------- /src/images/enemyBullet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/enemyBullet.png -------------------------------------------------------------------------------- /src/images/exp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/exp.png -------------------------------------------------------------------------------- /src/images/player copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/player copy.png -------------------------------------------------------------------------------- /src/images/player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/player.png -------------------------------------------------------------------------------- /src/images/rocks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/rocks.png -------------------------------------------------------------------------------- /src/images/target.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/target.png -------------------------------------------------------------------------------- /src/images/title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/title.png -------------------------------------------------------------------------------- /src/images/ui/buttons/1/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/ui/buttons/1/1.png -------------------------------------------------------------------------------- /src/images/ui/buttons/1/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/ui/buttons/1/2.png -------------------------------------------------------------------------------- /src/images/ui/buttons/1/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/ui/buttons/1/3.png -------------------------------------------------------------------------------- /src/images/ui/buttons/1/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/ui/buttons/1/4.png -------------------------------------------------------------------------------- /src/images/ui/buttons/1/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/ui/buttons/1/5.png -------------------------------------------------------------------------------- /src/images/ui/buttons/1/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/ui/buttons/1/6.png -------------------------------------------------------------------------------- /src/images/ui/buttons/2/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/ui/buttons/2/1.png -------------------------------------------------------------------------------- /src/images/ui/buttons/2/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/ui/buttons/2/2.png -------------------------------------------------------------------------------- /src/images/ui/buttons/2/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/ui/buttons/2/3.png -------------------------------------------------------------------------------- /src/images/ui/buttons/2/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/ui/buttons/2/4.png -------------------------------------------------------------------------------- /src/images/ui/buttons/2/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/ui/buttons/2/5.png -------------------------------------------------------------------------------- /src/images/ui/buttons/2/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/ui/buttons/2/6.png -------------------------------------------------------------------------------- /src/images/ui/icons/music_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/ui/icons/music_off.png -------------------------------------------------------------------------------- /src/images/ui/icons/music_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/ui/icons/music_on.png -------------------------------------------------------------------------------- /src/images/ui/icons/sfx_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/ui/icons/sfx_off.png -------------------------------------------------------------------------------- /src/images/ui/icons/sfx_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/ui/icons/sfx_on.png -------------------------------------------------------------------------------- /src/images/ui/toggles/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/ui/toggles/1.png -------------------------------------------------------------------------------- /src/images/ui/toggles/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/ui/toggles/2.png -------------------------------------------------------------------------------- /src/images/ui/toggles/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/ui/toggles/3.png -------------------------------------------------------------------------------- /src/images/ui/toggles/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/ui/toggles/4.png -------------------------------------------------------------------------------- /src/images/ui/toggles/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/ui/toggles/5.png -------------------------------------------------------------------------------- /src/images/ui/toggles/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaminMammadzada/js-shooter-game/63f772632f429bd4dc037c1df793e381399ec102/src/images/ui/toggles/6.png -------------------------------------------------------------------------------- /src/js/classes/comps/bar.js: -------------------------------------------------------------------------------- 1 | import Phaser from 'phaser'; 2 | 3 | class Bar extends Phaser.GameObjects.Container { 4 | constructor(config) { 5 | super(config.scene); 6 | this.scene = config.scene; 7 | if (!config.color) { 8 | config.color = 0xff0000; 9 | } 10 | if (!config.width) { 11 | config.width = 200; 12 | } 13 | 14 | if (!config.height) { 15 | config.height = config.width / 4; 16 | } 17 | this.graphics = this.scene.add.graphics(); 18 | this.graphics.fillStyle(config.color, 1); 19 | this.graphics.fillRect(0, 0, config.width, config.height); 20 | this.add(this.graphics); 21 | this.graphics.x = -config.width / 2; 22 | this.graphics.y = -config.height / 2; 23 | if (config.x) { 24 | this.x = config.x; 25 | } 26 | if (config.y) { 27 | this.y = config.y; 28 | } 29 | this.scene.add.existing(this); 30 | } 31 | 32 | setPercent(per) { 33 | this.graphics.scaleX = per; 34 | } 35 | } 36 | 37 | export default Bar; -------------------------------------------------------------------------------- /src/js/classes/comps/scoreBox.js: -------------------------------------------------------------------------------- 1 | import Phaser from 'phaser'; 2 | import EventEmitter from '../util/eventEmitter'; 3 | import Model from '../modelAndController/model'; 4 | import Constants from '../../constants'; 5 | 6 | export default class ScoreBox extends Phaser.GameObjects.Container { 7 | constructor(config) { 8 | super(config.scene); 9 | this.scene = config.scene; 10 | 11 | this.textForScore = this.scene.add.text(0, 0, 'SCORE:0', { fontSize: config.scene.game.config.width / 20 }); 12 | this.textForScore.setOrigin(0.5, 0.5); 13 | this.add(this.textForScore); 14 | 15 | this.scene.add.existing(this); 16 | this.setScrollFactor(0); 17 | 18 | EventEmitter.on(Constants.SCORE_UPDATED, this.scoreUpdated, this); 19 | } 20 | 21 | scoreUpdated() { 22 | this.textForScore.setText(`SCORE: ${Model.score}`); 23 | } 24 | } -------------------------------------------------------------------------------- /src/js/classes/modelAndController/controller.js: -------------------------------------------------------------------------------- 1 | import EventEmitter from '../util/eventEmitter'; 2 | import Model from './model'; 3 | import Constants from '../../constants'; 4 | 5 | class Controller { 6 | constructor() { 7 | this.setEmitters(); 8 | } 9 | 10 | setEmitters() { 11 | EventEmitter.on(Constants.SET_SCORE, this.setScore); 12 | EventEmitter.on(Constants.UP_POINTS, this.upPoints); 13 | EventEmitter.on(Constants.TOGGLE_SOUND, this.toggleSound); 14 | EventEmitter.on(Constants.TOGGLE_MUSIC, this.toggleMusic); 15 | } 16 | 17 | toggleSound(val) { 18 | this.val = val; 19 | Model.soundOn = this.val; 20 | } 21 | 22 | toggleMusic(val) { 23 | this.val = val; 24 | Model.musicOn = this.val; 25 | } 26 | 27 | setScore(score) { 28 | this.score = score; 29 | Model.score = this.score; 30 | } 31 | 32 | upPoints(points) { 33 | this.points = points; 34 | let { score } = Model; 35 | score += this.points; 36 | Model.score = score; 37 | } 38 | } 39 | 40 | export default (new Controller()); -------------------------------------------------------------------------------- /src/js/classes/modelAndController/model.js: -------------------------------------------------------------------------------- 1 | import EventEmitter from '../util/eventEmitter'; 2 | import Constants from '../../constants'; 3 | 4 | class Model { 5 | constructor() { 6 | this.a_score = 0; 7 | this.soundOn = true; 8 | this.a_musicOn = true; 9 | } 10 | 11 | set musicOn(val) { 12 | this.a_musicOn = val; 13 | EventEmitter.emit(Constants.MUSIC_CHANGED); 14 | } 15 | 16 | get musicOn() { 17 | return this.a_musicOn; 18 | } 19 | 20 | set score(val) { 21 | this.a_score = val; 22 | EventEmitter.emit(Constants.SCORE_UPDATED); 23 | } 24 | 25 | get score() { 26 | return this.a_score; 27 | } 28 | } 29 | 30 | export default (new Model()); -------------------------------------------------------------------------------- /src/js/classes/ui/flatButton.js: -------------------------------------------------------------------------------- 1 | import Phaser from 'phaser'; 2 | import EventEmitter from '../util/eventEmitter'; 3 | import Model from '../modelAndController/model'; 4 | 5 | class FlatButton extends Phaser.GameObjects.Container { 6 | constructor(config) { 7 | super(config.scene); 8 | if (!config.scene) { 9 | return; 10 | } 11 | if (!config.key) { 12 | return; 13 | } 14 | 15 | this.config = config; 16 | this.scene = config.scene; 17 | this.back = this.scene.add.image(0, 0, config.key); 18 | 19 | this.add(this.back); 20 | if (config.text) { 21 | if (config.textConfig) { 22 | this.text1 = this.scene.add.text(0, 0, config.text, config.textConfig); 23 | } else { 24 | this.text1 = this.scene.add.text(0, 0, config.text); 25 | } 26 | this.text1.setOrigin(0.5, 0.5); 27 | this.add(this.text1); 28 | } 29 | if (config.x) { 30 | this.x = config.x; 31 | } 32 | if (config.y) { 33 | this.y = config.y; 34 | } 35 | 36 | this.scene.add.existing(this); 37 | if (config.event) { 38 | this.back.setInteractive(); 39 | this.back.on('pointerdown', this.pressed, this); 40 | } 41 | 42 | if (Model.isMobile === -1) { 43 | this.back.on('pointerover', this.over, this); 44 | this.back.on('pointerout', this.out, this); 45 | } 46 | } 47 | 48 | over() { 49 | this.y -= 5; 50 | } 51 | 52 | out() { 53 | this.y += 5; 54 | } 55 | 56 | pressed() { 57 | if (this.config.params) { 58 | EventEmitter.emit(this.config.event, this.config.params); 59 | } else { 60 | EventEmitter.emit(this.config.event); 61 | } 62 | } 63 | } 64 | 65 | export default FlatButton; -------------------------------------------------------------------------------- /src/js/classes/ui/soundButtons.js: -------------------------------------------------------------------------------- 1 | import Phaser from 'phaser'; 2 | import Model from '../modelAndController/model'; 3 | import ToggleButton from './toggleButton'; 4 | import Constants from '../../constants'; 5 | 6 | class SoundButtons extends Phaser.GameObjects.Container { 7 | constructor(config) { 8 | super(config.scene); 9 | // console.log(Constants.TOGGLE_MUSIC); 10 | this.scene = config.scene; 11 | this.musicButton = new ToggleButton({ 12 | scene: this.scene, 13 | backKey: 'toggleBack', 14 | onIcon: 'musicOn', 15 | offIcon: 'musicOff', 16 | event: Constants.TOGGLE_MUSIC, 17 | }); 18 | this.sfxButton = new ToggleButton({ 19 | scene: this.scene, 20 | backKey: 'toggleBack', 21 | onIcon: 'sfxOn', 22 | offIcon: 'sfxOff', 23 | event: Constants.TOGGLE_SOUND, 24 | x: 240, 25 | y: 450, 26 | }); 27 | 28 | this.add(this.musicButton); 29 | 30 | this.musicButton.y = config.scene.game.config.height * 0.95; 31 | this.musicButton.x = this.musicButton.width * 0.6; 32 | 33 | this.sfxButton.x = config.scene.game.config.width - this.sfxButton.width / 2; 34 | this.sfxButton.y = this.musicButton.y; 35 | 36 | this.musicButton.setNoScroll(); 37 | this.sfxButton.setNoScroll(); 38 | 39 | if (Model.musicOn === true) { 40 | this.musicButton.toggle(); 41 | } 42 | if (Model.soundOn === true) { 43 | this.sfxButton.toggle(); 44 | } 45 | 46 | this.scene.add.existing(this); 47 | } 48 | } 49 | 50 | export default SoundButtons; -------------------------------------------------------------------------------- /src/js/classes/ui/toggleButton.js: -------------------------------------------------------------------------------- 1 | import Phaser from 'phaser'; 2 | import Align from '../util/align'; 3 | import EventEmitter from '../util/eventEmitter'; 4 | 5 | class ToggleButton extends Phaser.GameObjects.Container { 6 | constructor(config) { 7 | super(config.scene); 8 | this.scene = config.scene; 9 | 10 | this.back = this.scene.add.image(0, 0, config.backKey); 11 | this.onIcon = this.scene.add.image(0, 0, config.onIcon); 12 | this.offIcon = this.scene.add.image(0, 0, config.offIcon); 13 | 14 | Align.scaleToGameW(this.back, 0.1, config.scene.game); 15 | Align.scaleToGameW(this.onIcon, 0.05, config.scene.game); 16 | Align.scaleToGameW(this.offIcon, 0.05, config.scene.game); 17 | 18 | this.add(this.back); 19 | this.add(this.onIcon); 20 | this.add(this.offIcon); 21 | 22 | if (!config) { 23 | config.value = false; 24 | } 25 | 26 | this.value = config.value; 27 | 28 | if (config.event) { 29 | this.event = config.event; 30 | } 31 | 32 | this.setIcons(); 33 | this.back.setInteractive(); 34 | this.back.on('pointerdown', this.toggle, this); 35 | 36 | if (config.x) { 37 | this.x = config.x; 38 | } 39 | if (config.y) { 40 | this.y = config.y; 41 | } 42 | this.setSize(this.back.displayWidth, this.back.displayHeight); 43 | this.scene.add.existing(this); 44 | } 45 | 46 | setNoScroll() { 47 | this.back.setScrollFactor(0); 48 | this.onIcon.setScrollFactor(0); 49 | this.offIcon.setScrollFactor(0); 50 | } 51 | 52 | toggle() { 53 | this.value = !this.value; 54 | this.setIcons(); 55 | if (this.event) { 56 | EventEmitter.emit(this.event, this.value); 57 | } 58 | } 59 | 60 | setIcons() { 61 | if (this.value === true) { 62 | this.onIcon.visible = true; 63 | this.offIcon.visible = false; 64 | } else { 65 | this.onIcon.visible = false; 66 | this.offIcon.visible = true; 67 | } 68 | } 69 | } 70 | 71 | export default ToggleButton; -------------------------------------------------------------------------------- /src/js/classes/util/align.js: -------------------------------------------------------------------------------- 1 | class Align { 2 | static scaleToGameW(obj, per, game) { 3 | obj.displayWidth = game.config.width * per; 4 | obj.scaleY = obj.scaleX; 5 | } 6 | 7 | static center(obj) { 8 | obj.x = this.game.config.width / 2; 9 | obj.y = this.game.config.height / 2; 10 | } 11 | 12 | static centerH(obj) { 13 | obj.x = this.game.config.width / 2; 14 | } 15 | 16 | static centerV(obj) { 17 | obj.y = this.game.config.height / 2; 18 | } 19 | } 20 | 21 | export default Align; -------------------------------------------------------------------------------- /src/js/classes/util/alignGrid.js: -------------------------------------------------------------------------------- 1 | class AlignGrid { 2 | constructor(config) { 3 | this.config = config; 4 | if (!config.scene) { 5 | return; 6 | } 7 | if (!config.rows) { 8 | config.rows = 5; 9 | } if (!config.cols) { 10 | config.cols = 5; 11 | } 12 | if (!config.height) { 13 | config.height = config.scene.game.config.height; 14 | } 15 | if (!config.width) { 16 | config.width = config.scene.game.config.width; 17 | } 18 | 19 | this.scene = config.scene; 20 | 21 | this.cw = config.width / config.cols; 22 | this.ch = config.height / config.rows; 23 | } 24 | 25 | show() { 26 | this.graphics = this.scene.add.graphics(); 27 | this.graphics.lineStyle(2, 0xff0000); 28 | 29 | for (let i = 0; i < this.config.width; i += this.cw) { 30 | this.graphics.moveTo(i, 0); 31 | this.graphics.lineTo(i, this.config.height); 32 | } 33 | 34 | for (let i = 0; i < this.config.height; i += this.ch) { 35 | this.graphics.moveTo(0, i); 36 | this.graphics.lineTo(this.config.width, i); 37 | } 38 | this.graphics.strokePath(); 39 | } 40 | 41 | placeAt(xx, yy, obj) { 42 | const x2 = this.cw * xx + this.cw / 2; 43 | const y2 = this.ch * yy + this.ch / 2; 44 | 45 | obj.x = x2; 46 | obj.y = y2; 47 | } 48 | 49 | placeAtIndex(index, obj) { 50 | const yy = Math.floor(index / this.config.cols); 51 | const xx = index - (yy * this.config.cols); 52 | 53 | this.placeAt(xx, yy, obj); 54 | } 55 | 56 | showNumbers() { 57 | this.show(); 58 | let count = 0; 59 | for (let i = 0; i < this.config.rows; i += 1) { 60 | for (let j = 0; j < this.config.cols; j += 1) { 61 | const numText = this.scene.add.text(0, 0, count, { color: '#ff0000' }); 62 | numText.setOrigin(0.5, 0.5); 63 | this.placeAtIndex(count, numText); 64 | count += 1; 65 | } 66 | } 67 | } 68 | } 69 | 70 | export default AlignGrid; -------------------------------------------------------------------------------- /src/js/classes/util/eventEmitter.js: -------------------------------------------------------------------------------- 1 | import Phaser from 'phaser'; 2 | 3 | class EventEmitter extends Phaser.Events.EventEmitter { 4 | } 5 | 6 | export default (new EventEmitter()); -------------------------------------------------------------------------------- /src/js/classes/util/mediaManager.js: -------------------------------------------------------------------------------- 1 | import Model from '../modelAndController/model'; 2 | import EventEmitter from './eventEmitter'; 3 | import Constants from '../../constants'; 4 | 5 | class MediaManager { 6 | constructor(config) { 7 | this.scene = config.scene; 8 | 9 | EventEmitter.on(Constants.PLAY_SOUND, this.playSound, this); 10 | EventEmitter.on(Constants.MUSIC_CHANGED, this.musicChanged, this); 11 | } 12 | 13 | musicChanged() { 14 | if (this.background) { 15 | if (Model.musicOn === false) { 16 | this.background.stop(); 17 | } else { 18 | this.background.play(); 19 | } 20 | } 21 | } 22 | 23 | playSound(key) { 24 | if (Model.soundOn) { 25 | const sound = this.scene.sound.add(key, { volume: 0.2 }); 26 | sound.play(); 27 | } 28 | } 29 | 30 | setBackgroundMusic(key) { 31 | if (Model.musicOn) { 32 | this.background = this.scene.sound.add( 33 | key, 34 | { 35 | volume: 0.5, 36 | loop: true, 37 | }, 38 | ); 39 | this.background.play(); 40 | } 41 | } 42 | } 43 | 44 | export default MediaManager; -------------------------------------------------------------------------------- /src/js/classes/util/serviceApi.js: -------------------------------------------------------------------------------- 1 | import 'regenerator-runtime/runtime'; 2 | 3 | const baseUrl = () => 'https://us-central1-js-capstone-backend.cloudfunctions.net/api/games/'; 4 | const getUrl = () => `${baseUrl()}plPX1KbSDTjkjLXIA8ui/scores/`; 5 | 6 | const postScore = async (name, score) => { 7 | const data = { 8 | user: name, 9 | score, 10 | }; 11 | try { 12 | const response = await fetch(getUrl(), { 13 | method: 'POST', 14 | body: JSON.stringify(data), 15 | headers: { 16 | Accept: 'application/json', 17 | 'Content-type': 'application/json; charset=UTF-8', 18 | }, 19 | }); 20 | return response; 21 | } catch (error) { 22 | return error.message; 23 | } 24 | }; 25 | 26 | const getScores = async () => { 27 | try { 28 | const response = await fetch(getUrl(), { 29 | method: 'GET', 30 | headers: { 'Content-type': 'application/json;charset=UTF-8' }, 31 | }); 32 | const filter = await response.json(); 33 | return filter; 34 | } catch (error) { 35 | return error.message; 36 | } 37 | }; 38 | 39 | export { postScore, getScores }; -------------------------------------------------------------------------------- /src/js/constants.js: -------------------------------------------------------------------------------- 1 | class Constants { 2 | static get SET_SCORE() { return 'setScore'; } 3 | 4 | static get UP_POINTS() { return 'upPoints'; } 5 | 6 | static get SCORE_UPDATED() { return 'scoreUpdated'; } 7 | 8 | static get PLAY_SOUND() { return 'playSound'; } 9 | 10 | static get MUSIC_CHANGED() { return 'musicChanged'; } 11 | 12 | static get TOGGLE_SOUND() { return 'toggleSound'; } 13 | 14 | static get TOGGLE_MUSIC() { return 'toggleMusic'; } 15 | } 16 | 17 | export default Constants; -------------------------------------------------------------------------------- /src/js/game.js: -------------------------------------------------------------------------------- 1 | import Phaser from 'phaser'; 2 | import RexUIPlugin from 'phaser3-rex-plugins/templates/ui/ui-plugin'; 3 | import SceneBoot from './scenes/sceneBoot'; 4 | import SceneTitle from './scenes/sceneTitle'; 5 | import SceneMain from './scenes/sceneMain'; 6 | import ScenePreloader from './scenes/scenePreloader'; 7 | import SceneLeaderboard from './scenes/sceneLeaderboard'; 8 | import Model from './classes/modelAndController/model'; 9 | 10 | class Game extends Phaser.Game { 11 | constructor() { 12 | const isMobile = navigator.userAgent.indexOf('Mobile'); 13 | const isTablet = navigator.userAgent.indexOf('Tablet'); 14 | Model.isMobile = (isMobile !== -1); 15 | Model.isTablet = (isTablet !== -1); 16 | 17 | let config; 18 | if (!Model.isMobile) { 19 | config = { 20 | type: Phaser.AUTO, 21 | width: 480, 22 | height: 640, 23 | parent: 'phaser-game', 24 | physics: { 25 | default: 'arcade', 26 | arcade: { 27 | debug: false, 28 | }, 29 | }, 30 | scene: [ScenePreloader, SceneTitle, SceneMain, SceneBoot, SceneLeaderboard], 31 | dom: { 32 | createContainer: true, 33 | }, 34 | plugins: { 35 | scene: [ 36 | { 37 | key: 'rexUI', 38 | plugin: RexUIPlugin, 39 | mapping: 'rexUI', 40 | }, 41 | ], 42 | }, 43 | }; 44 | } else { 45 | config = { 46 | type: Phaser.AUTO, 47 | width: window.innerWidth, 48 | height: window.innerHeight, 49 | parent: 'phaser-game', 50 | physics: { 51 | default: 'arcade', 52 | arcade: { 53 | debug: false, 54 | }, 55 | }, 56 | scene: [ScenePreloader, SceneTitle, SceneMain, SceneBoot, SceneLeaderboard], 57 | dom: { 58 | createContainer: true, 59 | }, 60 | plugins: { 61 | scene: [ 62 | { 63 | key: 'rexUI', 64 | plugin: RexUIPlugin, 65 | mapping: 'rexUI', 66 | }, 67 | ], 68 | }, 69 | }; 70 | } 71 | super(config); 72 | } 73 | } 74 | 75 | export default Game; -------------------------------------------------------------------------------- /src/js/index.js: -------------------------------------------------------------------------------- 1 | import Game from './game'; 2 | import '../styles/style.css'; 3 | 4 | window.game = new Game(); -------------------------------------------------------------------------------- /src/js/scenes/sceneBoot.js: -------------------------------------------------------------------------------- 1 | import Phaser from 'phaser'; 2 | import Align from '../classes/util/align'; 3 | import AlignGrid from '../classes/util/alignGrid'; 4 | import FlatButton from '../classes/ui/flatButton'; 5 | import EventEmitter from '../classes/util/eventEmitter'; 6 | import Model from '../classes/modelAndController/model'; 7 | 8 | class SceneBoot extends Phaser.Scene { 9 | constructor() { 10 | super('SceneBoot'); 11 | } 12 | 13 | create() { 14 | this.add.image(0, 0, 'background').setOrigin(0.5, 0.5); 15 | this.alignGrid = new AlignGrid({ rows: 15, cols: 15, scene: this }); 16 | 17 | const title = this.add.image(0, 0, 'title'); 18 | Align.scaleToGameW(title, 0.8, this.game); 19 | this.alignGrid.placeAtIndex(37, title); 20 | 21 | this.winnerText = this.add.text(0, 0, 'WINNER IS', { fontSize: this.game.config.width / 10, color: '#C73B2C' }); 22 | this.winnerText.setOrigin(0.5, 0.5); 23 | this.alignGrid.placeAtIndex(67, this.winnerText); 24 | 25 | const playerFinalScore = this.add.text(0, 0, 'Your score: ', { fontSize: this.game.config.width / 20, color: '#FFFFFF' }); 26 | playerFinalScore.setOrigin(0.5, 0.5); 27 | 28 | if (Model.playerWon === true) { 29 | this.winner = this.add.image(0, 0, 'playerShip'); 30 | this.winnerText.setText('YOU WON'); 31 | this.alignGrid.placeAtIndex(82, playerFinalScore); 32 | playerFinalScore.setText(`Your score: ${Model.score}`); 33 | } else { 34 | this.winner = this.add.image(0, 0, 'enemyShip'); 35 | this.winnerText.setText('THE ENEMY WON'); 36 | } 37 | 38 | Align.scaleToGameW(this.winner, 0.25, this.game); 39 | this.winner.angle = 0; 40 | this.alignGrid.placeAtIndex(112, this.winner); 41 | 42 | const buttonStart = new FlatButton({ 43 | scene: this, key: 'button1', text: 'Play Again!', event: 'start_game', 44 | }); 45 | this.alignGrid.placeAtIndex(157, buttonStart); 46 | 47 | const buttonLeaderboard = new FlatButton({ 48 | scene: this, key: 'button1', text: 'Leaderboard', event: 'go_leaderboard', 49 | }); 50 | this.alignGrid.placeAtIndex(202, buttonLeaderboard); 51 | 52 | EventEmitter.on('start_game', this.startGame, this); 53 | EventEmitter.on('go_leaderboard', this.goToLeaderboard, this); 54 | } 55 | 56 | // eslint-disable-next-line class-methods-use-this 57 | startGame() { 58 | // eslint-disable-next-line no-restricted-globals 59 | location.reload(); 60 | } 61 | 62 | goToLeaderboard() { 63 | this.scene.start('SceneLeaderboard'); 64 | } 65 | } 66 | 67 | export default SceneBoot; -------------------------------------------------------------------------------- /src/js/scenes/sceneLeaderboard.js: -------------------------------------------------------------------------------- 1 | import Phaser from 'phaser'; 2 | import button1 from '../../images/ui/buttons/2/1.png'; 3 | import title from '../../images/title.png'; 4 | import EventEmitter from '../classes/util/eventEmitter'; 5 | import AlignGrid from '../classes/util/alignGrid'; 6 | import FlatButton from '../classes/ui/flatButton'; 7 | import { getScores } from '../classes/util/serviceApi'; 8 | 9 | class SceneLeaderboard extends Phaser.Scene { 10 | constructor() { 11 | super('SceneLeaderboard'); 12 | } 13 | 14 | preload() { 15 | this.load.image('button1', button1); 16 | this.load.image('title', title); 17 | } 18 | 19 | create() { 20 | this.add.image(0, 0, 'background').setOrigin(0.5, 0.5); 21 | this.alignGrid = new AlignGrid({ rows: 13, cols: 13, scene: this }); 22 | 23 | this.addTitleOfLeaderboard(); 24 | getScores().then((response) => { this.addUserAndScore(response.result); }); 25 | 26 | this.buttonStart = new FlatButton({ 27 | scene: this, key: 'button1', text: 'Play Again!', event: 'start_game', 28 | }); 29 | this.alignGrid.placeAtIndex(149, this.buttonStart); 30 | 31 | EventEmitter.on('start_game', this.startGame, this); 32 | } 33 | 34 | addTitleOfLeaderboard() { 35 | this.headText = this.add.text(0, 0, 'LEADERBOARD', { fontSize: this.game.config.width / 10 }); 36 | this.headText.setOrigin(0.5, 0.5); 37 | this.alignGrid.placeAtIndex(19, this.headText); 38 | 39 | this.subText = this.add.text(0, 0, '... The kings in the cosmos war ...', { fontSize: this.game.config.width / 27 }); 40 | this.subText.setOrigin(0.5, 0.5); 41 | this.alignGrid.placeAtIndex(32, this.subText); 42 | } 43 | 44 | addUserAndScore(userAnScores) { 45 | userAnScores.sort((a, b) => ((a.score < b.score) ? 1 : -1)); 46 | const leaders = userAnScores.slice(0, 5); 47 | 48 | const startLocation = 54; 49 | for (let i = 0; i < leaders.length; i += 1) { 50 | this.userName = this.add.text( 51 | 0, 52 | 0, 53 | leaders[i].user, 54 | { fontSize: this.game.config.width / 25 }, 55 | ); 56 | this.userName.setOrigin(0, 0); 57 | this.alignGrid.placeAtIndex(startLocation + 13 * i, this.userName); 58 | 59 | this.userScore = this.add.text( 60 | 0, 61 | 0, 62 | leaders[i].score, 63 | { fontSize: this.game.config.width / 25 }, 64 | ); 65 | this.userScore.setOrigin(0, 0); 66 | this.alignGrid.placeAtIndex(startLocation + 13 * i + 7, this.userScore); 67 | } 68 | } 69 | 70 | // eslint-disable-next-line class-methods-use-this 71 | startGame() { 72 | // eslint-disable-next-line no-restricted-globals 73 | location.reload(); 74 | } 75 | } 76 | 77 | export default SceneLeaderboard; -------------------------------------------------------------------------------- /src/js/scenes/sceneMain.js: -------------------------------------------------------------------------------- 1 | import Phaser from 'phaser'; 2 | import EventEmitter from '../classes/util/eventEmitter'; 3 | import MediaManager from '../classes/util/mediaManager'; 4 | import SoundButtons from '../classes/ui/soundButtons'; 5 | import Controller from '../classes/modelAndController/controller'; 6 | import Align from '../classes/util/align'; 7 | import AlignGrid from '../classes/util/alignGrid'; 8 | import Constants from '../constants'; 9 | import Model from '../classes/modelAndController/model'; 10 | import ScoreBox from '../classes/comps/scoreBox'; 11 | import { postScore } from '../classes/util/serviceApi'; 12 | 13 | class SceneMain extends Phaser.Scene { 14 | constructor() { 15 | super('SceneMain'); 16 | } 17 | 18 | create() { 19 | Controller.setEmitters(); 20 | const mediaManager = new MediaManager({ scene: this }); 21 | mediaManager.setBackgroundMusic('backgroundMusic'); 22 | 23 | this.playerPower = 30; 24 | this.enemyPower = 30; 25 | Model.playerWon = true; 26 | this.centerX = this.game.config.width / 2; 27 | this.center = this.game.config.height / 2; 28 | 29 | this.background = this.add.image(0, 0, 'background'); 30 | this.background.setOrigin(0, 0); 31 | this.playerShip = this.physics.add.sprite(this.centerX, this.centerY, 'playerShip'); 32 | this.playerShip.setOrigin(0.5, 0.5); 33 | Align.scaleToGameW(this.playerShip, 0.125, this.game); 34 | this.playerShip.body.collideWorldBounds = true; 35 | this.playerShip.setInteractive(); 36 | this.playerShip.on('pointerdown', this.fireBulletForPlayerShip, this); 37 | 38 | this.keySpace = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE); 39 | 40 | this.background.setInteractive(); 41 | this.background.on('pointerup', this.backgroundClicked, this); 42 | this.physics.world.setBounds(0, 0, this.background.displayWidth, this.background.displayHeight); 43 | 44 | this.cameras.main.setBounds(0, 0, this.background.displayWidth, this.background.displayHeight); 45 | this.cameras.main.startFollow(this.playerShip, true); 46 | 47 | this.playerBulletGroup = this.physics.add.group(); 48 | this.enemyBulletGroup = this.physics.add.group(); 49 | 50 | const frameNames = this.anims.generateFrameNumbers('exp'); 51 | 52 | this.anims.create({ 53 | key: 'boom', 54 | frames: this.updateFrameNamesForExplosion(frameNames), 55 | frameRate: 40, 56 | repeat: false, 57 | }); 58 | 59 | this.enemyShip = this.physics.add.sprite(this.centerX, 0, 'enemyShip'); 60 | Align.scaleToGameW(this.enemyShip, 0.25, this.game); 61 | this.enemyShip.body.collideWorldBounds = true; 62 | 63 | this.rockGroup = this.physics.add.group(); 64 | this.addRocks(); 65 | 66 | this.showInfo(); 67 | this.setColliders(); 68 | 69 | const soundButtons = new SoundButtons({ scene: this }); 70 | soundButtons.depth = 1; 71 | 72 | if (!Model.isMobile) { 73 | this.target = this.add.image(0, 0, 'target'); 74 | Align.scaleToGameW(this.target, 0.08, this.game); 75 | } 76 | } 77 | 78 | setColliders() { 79 | this.physics.add.collider( 80 | this.playerBulletGroup, 81 | this.enemyShip, 82 | this.damageEnemyShip, 83 | this.increaseScore, 84 | this, 85 | ); 86 | this.physics.add.collider( 87 | this.enemyBulletGroup, 88 | this.playerShip, 89 | this.damagePlayerShip, 90 | null, 91 | this, 92 | ); 93 | this.physics.add.collider( 94 | this.enemyShip, 95 | this.playerShip, 96 | null, 97 | null, 98 | this, 99 | ); 100 | this.physics.add.collider( 101 | this.enemyBulletGroup, 102 | this.playerBulletGroup, 103 | this.destroyBullets, 104 | this.increaseScore, 105 | this, 106 | ); 107 | } 108 | 109 | setRockColliders() { 110 | this.physics.add.collider(this.rockGroup); 111 | this.physics.add.collider( 112 | this.rockGroup, 113 | this.playerShip, 114 | this.rockHitPlayerShip, 115 | null, 116 | this, 117 | ); 118 | this.physics.add.collider( 119 | this.rockGroup, 120 | this.enemyShip, 121 | this.rockHitEnemyShip, 122 | null, 123 | this, 124 | ); 125 | this.physics.add.collider( 126 | this.rockGroup, 127 | this.playerBulletGroup, 128 | this.destroyRock, 129 | this.increaseScore, 130 | this, 131 | ); 132 | this.physics.add.collider( 133 | this.rockGroup, 134 | this.enemyBulletGroup, 135 | this.destroyRock, 136 | null, 137 | this, 138 | ); 139 | } 140 | 141 | updateFrameNamesForExplosion(frameNames) { 142 | this.frameNamesSliced = frameNames.slice(); 143 | this.frameNamesSliced.reverse(); 144 | return this.frameNamesSliced.concat(frameNames); 145 | } 146 | 147 | increaseScore() { 148 | if (this.playerPower !== 0) { 149 | EventEmitter.emit(Constants.UP_POINTS, 1); 150 | } 151 | return true; 152 | } 153 | 154 | destroyBullets(playerBullet, enemyBullet) { 155 | const explosion = this.add.sprite(playerBullet.x, playerBullet.y, 'exp'); 156 | explosion.play('boom'); 157 | EventEmitter.emit(Constants.PLAY_SOUND, 'explode'); 158 | playerBullet.destroy(); 159 | enemyBullet.destroy(); 160 | } 161 | 162 | decreasePlayerPower() { 163 | this.playerPower -= 1; 164 | this.playerPowerText.setText(`Player Power\n ${this.playerPower}`); 165 | if (this.playerPower === 0) { 166 | Model.playerWon = false; 167 | postScore(Model.username, Model.score); 168 | this.scene.start('SceneBoot'); 169 | } 170 | } 171 | 172 | decreaseEnemyPower() { 173 | this.enemyPower -= 1; 174 | this.enemyPowerText.setText(`Enemy Power\n ${this.enemyPower}`); 175 | if (this.enemyPower === 0) { 176 | Model.playerWon = true; 177 | postScore(Model.username, Model.score); 178 | this.scene.start('SceneBoot'); 179 | } 180 | } 181 | 182 | rockHitPlayerShip(playerShip, rock) { 183 | this.destroyRock(null, rock); 184 | this.decreasePlayerPower(); 185 | } 186 | 187 | rockHitEnemyShip(enemyShip, rock) { 188 | this.destroyRock(null, rock); 189 | this.decreaseEnemyPower(); 190 | } 191 | 192 | destroyRock(bullet, rock) { 193 | const explosion = this.add.sprite(rock.x, rock.y, 'exp'); 194 | EventEmitter.emit(Constants.PLAY_SOUND, 'explode'); 195 | explosion.play('boom'); 196 | rock.destroy(); 197 | if (bullet !== null) { 198 | bullet.destroy(); 199 | } 200 | this.addRocks(); 201 | } 202 | 203 | damageEnemyShip(enemyShip, playerBullet) { 204 | const explosion = this.add.sprite(playerBullet.x, playerBullet.y, 'exp'); 205 | explosion.play('boom'); 206 | EventEmitter.emit(Constants.PLAY_SOUND, 'explode'); 207 | playerBullet.destroy(); 208 | 209 | let angleForEnemyShip = this.physics.moveTo( 210 | this.enemyShip, 211 | this.playerShip.x, 212 | this.playerShip.y, 213 | 120, 214 | ); 215 | angleForEnemyShip = this.toDegrees(angleForEnemyShip); 216 | this.enemyShip.angle = angleForEnemyShip; 217 | 218 | this.decreaseEnemyPower(); 219 | } 220 | 221 | damagePlayerShip(playerShip, enemyBullet) { 222 | const explosion = this.add.sprite(playerShip.x, playerShip.y, 'exp'); 223 | explosion.play('boom'); 224 | EventEmitter.emit(Constants.PLAY_SOUND, 'explode'); 225 | enemyBullet.destroy(); 226 | this.decreasePlayerPower(); 227 | } 228 | 229 | getTimer() { 230 | this.currentDate = new Date(); 231 | return this.currentDate.getTime(); 232 | } 233 | 234 | onDown() { 235 | this.downTime = this.getTimer(); 236 | } 237 | 238 | backgroundClicked() { 239 | const tx = this.background.input.localX; 240 | const ty = this.background.input.localY; 241 | this.tx = tx; 242 | this.ty = ty; 243 | let angle = this.physics.moveTo(this.playerShip, tx, ty, 150); 244 | 245 | if (Model.isMobile) { 246 | angle = this.toDegrees(angle); 247 | this.playerShip.angle = angle + 90; 248 | } 249 | 250 | const distanceX2 = Math.abs(this.playerShip.x - this.tx); 251 | const distanceY2 = Math.abs(this.playerShip.y - this.tx); 252 | if (distanceX2 < this.game.config.width / 3 && distanceY2 < this.game.config.height / 3) { 253 | let angleForEnemyShip = this.physics.moveTo( 254 | this.enemyShip, 255 | this.playerShip.x, 256 | this.playerShip.y, 257 | 60, 258 | ); 259 | angleForEnemyShip = this.toDegrees(angleForEnemyShip); 260 | this.enemyShip.angle = angleForEnemyShip; 261 | } 262 | } 263 | 264 | setTargetIconLocation() { 265 | if (this.target) { 266 | this.target.setPosition( 267 | this.game.input.mousePointer.worldX, 268 | this.game.input.mousePointer.worldY, 269 | ); 270 | } 271 | } 272 | 273 | rotatePlayer() { 274 | const rotate = Phaser.Math.Angle.Between( 275 | this.playerShip.body.x, 276 | this.playerShip.body.y, 277 | this.game.input.mousePointer.worldX, 278 | this.game.input.mousePointer.worldY, 279 | ); 280 | const angle = rotate + Math.PI / 2; 281 | this.playerShip.rotation = angle; 282 | } 283 | 284 | fireBulletForPlayerShip() { 285 | const elapsed = Math.abs(this.lastTimePlayerBulletFired - this.getTimer()); 286 | if (elapsed < 300) { 287 | return; 288 | } 289 | this.lastTimePlayerBulletFired = this.getTimer(); 290 | const directionObj = this.getDirectionFromAngle(this.playerShip.angle - 90); 291 | const bullet = this.physics.add.sprite(this.playerShip.x + directionObj.tx, this.playerShip.y + directionObj.ty, 'bullet'); 292 | bullet.setOrigin(0.5, 0.5); 293 | this.playerBulletGroup.add(bullet); 294 | bullet.angle = this.playerShip.angle - 90; 295 | bullet.body.setVelocity(directionObj.tx * 200, directionObj.ty * 200); 296 | EventEmitter.emit(Constants.PLAY_SOUND, 'playerShoot'); 297 | } 298 | 299 | fireBulletForEnemyShip() { 300 | const elapsed = Math.abs(this.lastTimeEnemyBulletFired - this.getTimer()); 301 | if (elapsed < 3000) { 302 | return; 303 | } 304 | this.lastTimeEnemyBulletFired = this.getTimer(); 305 | const enemyBullet = this.physics.add.sprite(this.enemyShip.x, this.enemyShip.y, 'enemyBullet'); 306 | enemyBullet.body.angularVelocity = 10; 307 | 308 | enemyBullet.angle = this.enemyShip.angle; 309 | this.enemyBulletGroup.add(enemyBullet); 310 | this.physics.moveTo(enemyBullet, this.playerShip.x, this.playerShip.y, 150); 311 | EventEmitter.emit(Constants.PLAY_SOUND, 'playerShoot'); 312 | } 313 | 314 | toDegrees(angle) { 315 | this.angleInDegrees = angle * (180 / Math.PI); 316 | return this.angleInDegrees; 317 | } 318 | 319 | getDirectionFromAngle(angle) { 320 | this.temporary = (Math.PI / 180); 321 | const rads = angle * this.temporary; 322 | const tx = Math.cos(rads); 323 | const ty = Math.sin(rads); 324 | return { tx, ty }; 325 | } 326 | 327 | showInfo() { 328 | this.playerPowerText = this.add.text(0, 0, 'Player power\n30', { fontSize: this.game.config.width / 30, align: 'center' }); 329 | this.enemyPowerText = this.add.text(0, 0, 'Enemy power\n30', { fontSize: this.game.config.width / 30, align: 'center' }); 330 | 331 | this.playerPowerText.setOrigin(0.5, 0.5); 332 | this.enemyPowerText.setOrigin(0.5, 0.5); 333 | this.uiGrid = new AlignGrid({ scene: this, rows: 11, cols: 11 }); 334 | // this.uiGrid.showNumbers(); 335 | 336 | this.uiGrid.placeAtIndex(3, this.playerPowerText); 337 | this.uiGrid.placeAtIndex(9, this.enemyPowerText); 338 | 339 | this.icon1 = this.add.image(0, 0, 'playerShip'); 340 | this.icon2 = this.add.image(0, 0, 'enemyShip'); 341 | Align.scaleToGameW(this.icon1, 0.1, this.game); 342 | Align.scaleToGameW(this.icon2, 0.1, this.game); 343 | this.uiGrid.placeAtIndex(1, this.icon1); 344 | this.uiGrid.placeAtIndex(7, this.icon2); 345 | this.icon1.angle = 0; 346 | this.icon2.angle = 270; 347 | 348 | this.playerPowerText.setScrollFactor(0); 349 | this.enemyPowerText.setScrollFactor(0); 350 | this.icon1.setScrollFactor(0); 351 | this.icon2.setScrollFactor(0); 352 | 353 | this.scoreBox = new ScoreBox({ scene: this }); 354 | this.uiGrid.placeAtIndex(16, this.scoreBox); 355 | } 356 | 357 | addRocks() { 358 | if (this.rockGroup.getChildren().length === 0) { 359 | this.rockGroup = this.physics.add.group({ 360 | key: 'rocks', 361 | frame: [0, 1, 2], 362 | frameQuantity: 7, 363 | bounceX: 1, 364 | bounceY: 1, 365 | angularVelocity: 1, 366 | collideWorldBounds: true, 367 | }); 368 | 369 | this.rockGroup.children.iterate((child) => { 370 | const xx = Math.floor(Math.random() * this.background.displayWidth); 371 | const yy = Math.floor(Math.random() * this.background.displayHeight); 372 | 373 | child.x = xx; 374 | child.y = yy; 375 | 376 | Align.scaleToGameW(child, 0.1, this.game); 377 | 378 | let vx = Math.floor(Math.random() * 2 - 1); 379 | let vy = Math.floor(Math.random() * 2 - 1); 380 | 381 | if (vx === 0 * vy === 0) { 382 | vx = 1; 383 | vy = 1; 384 | } 385 | 386 | const speed = Math.floor(Math.random() * 200) + 10; 387 | child.body.setVelocity(vx * speed, vy * speed); 388 | }); 389 | this.setRockColliders(); 390 | } 391 | } 392 | 393 | update() { 394 | if (this.playerShip && this.enemyShip) { 395 | const distanceX = Math.abs(this.playerShip.x - this.tx); 396 | const distanceY = Math.abs(this.playerShip.y - this.ty); 397 | if (distanceX < 10 && distanceY < 10) { 398 | if (this.playerShip.body) { 399 | this.playerShip.body.setVelocity(0, 0); 400 | } 401 | } 402 | 403 | const distanceX2 = Math.abs(this.playerShip.x - this.enemyShip.x); 404 | const distanceY2 = Math.abs(this.playerShip.y - this.enemyShip.y); 405 | if (distanceX2 < this.game.config.width / 3 && distanceY2 < this.game.config.height / 3) { 406 | this.fireBulletForEnemyShip(); 407 | } 408 | 409 | if (this.keySpace.isDown) { 410 | this.fireBulletForPlayerShip(); 411 | } 412 | } 413 | 414 | if (!Model.isMobile) { 415 | this.rotatePlayer(); 416 | this.setTargetIconLocation(); 417 | } 418 | } 419 | } 420 | 421 | export default SceneMain; -------------------------------------------------------------------------------- /src/js/scenes/scenePreloader.js: -------------------------------------------------------------------------------- 1 | import Phaser from 'phaser'; 2 | import toggle1 from '../../images/ui/toggles/1.png'; 3 | import sfxOff from '../../images/ui/icons/sfx_off.png'; 4 | import sfxOn from '../../images/ui/icons/sfx_on.png'; 5 | import musicOn from '../../images/ui/icons/music_on.png'; 6 | import musicOff from '../../images/ui/icons/music_off.png'; 7 | import playerShip from '../../images/player.png'; 8 | import backgroundImage from '../../images/background.jpg'; 9 | import rocks from '../../images/rocks.png'; 10 | import bullet from '../../images/bullet.png'; 11 | import enemyBullet from '../../images/enemyBullet.png'; 12 | import exp from '../../images/exp.png'; 13 | import enemyShip from '../../images/enemy.png'; 14 | import target from '../../images/target.png'; 15 | import Bar from '../classes/comps/bar'; 16 | import backgroundMusic1 from '../../audio/background.mp3'; 17 | import backgroundMusic2 from '../../audio/background.ogg'; 18 | import explode1 from '../../audio/explode.wav'; 19 | import explode2 from '../../audio/explode.ogg'; 20 | import playerShoot1 from '../../audio/playerShoot.wav'; 21 | import playerShoot2 from '../../audio/playerShoot.ogg'; 22 | import enemyShoot1 from '../../audio/enemyShoot.wav'; 23 | import enemyShoot2 from '../../audio/enemyShoot.ogg'; 24 | 25 | class ScenePreloader extends Phaser.Scene { 26 | constructor() { 27 | super('ScenePreloader'); 28 | } 29 | 30 | preload() { 31 | this.bar = new Bar({ scene: this, x: 240, y: 320 }); 32 | 33 | this.progText = this.add.text( 34 | this.game.config.width / 2, 35 | this.game.config.height / 2, 36 | '0%', 37 | { 38 | color: '#ffffff', 39 | fontSize: this.game.config.width / 20, 40 | }, 41 | ); 42 | this.progText.setOrigin(0.5, 0.5); 43 | this.load.on('progress', this.onProgress, this); 44 | 45 | this.load.audio('explode', [explode1, explode2]); 46 | this.load.audio('playerShoot', [playerShoot1, playerShoot2]); 47 | this.load.audio('enemyShoot', [enemyShoot1, enemyShoot2]); 48 | this.load.audio('backgroundMusic', [backgroundMusic1, backgroundMusic2]); 49 | 50 | this.load.image('toggleBack', toggle1); 51 | this.load.image('sfxOff', sfxOff); 52 | this.load.image('sfxOn', sfxOn); 53 | this.load.image('musicOn', musicOn); 54 | this.load.image('musicOff', musicOff); 55 | 56 | this.load.image('playerShip', playerShip); 57 | this.load.image('background', backgroundImage); 58 | 59 | this.load.spritesheet('rocks', rocks, { frameWidth: 125, frameHeight: 100 }); 60 | this.load.spritesheet('exp', exp, { frameWidth: 64, frameHeight: 64 }); 61 | this.load.image('bullet', bullet); 62 | this.load.image('enemyBullet', enemyBullet); 63 | this.load.image('enemyShip', enemyShip); 64 | this.load.image('target', target); 65 | } 66 | 67 | onProgress(value) { 68 | this.bar.setPercent(value); 69 | const per = Math.floor(value * 100); 70 | this.progText.setText(`${per}%`); 71 | } 72 | 73 | create() { 74 | this.scene.start('SceneTitle'); 75 | } 76 | } 77 | 78 | export default ScenePreloader; -------------------------------------------------------------------------------- /src/js/scenes/sceneTitle.js: -------------------------------------------------------------------------------- 1 | import Phaser from 'phaser'; 2 | import button1 from '../../images/ui/buttons/2/1.png'; 3 | import title from '../../images/title.png'; 4 | import EventEmitter from '../classes/util/eventEmitter'; 5 | import Align from '../classes/util/align'; 6 | import AlignGrid from '../classes/util/alignGrid'; 7 | import FlatButton from '../classes/ui/flatButton'; 8 | import SoundButtons from '../classes/ui/soundButtons'; 9 | import MediaManager from '../classes/util/mediaManager'; 10 | import Model from '../classes/modelAndController/model'; 11 | 12 | class SceneTitle extends Phaser.Scene { 13 | constructor() { 14 | super('SceneTitle'); 15 | } 16 | 17 | preload() { 18 | this.load.image('button1', button1); 19 | this.load.image('title', title); 20 | } 21 | 22 | create() { 23 | this.add.image(0, 0, 'background').setOrigin(0.5, 0.5); 24 | 25 | const mediaManager = new MediaManager({ scene: this }); 26 | mediaManager.setBackgroundMusic('backgroundMusic'); 27 | 28 | this.alignGrid = new AlignGrid({ rows: 11, cols: 11, scene: this }); 29 | 30 | const title = this.add.image(0, 0, 'title'); 31 | Align.scaleToGameW(title, 0.8, this.game); 32 | this.alignGrid.placeAtIndex(38, title); 33 | 34 | const playerIcon = this.add.image(0, 0, 'playerShip'); 35 | const enemyIcon = this.add.image(0, 0, 'enemyShip'); 36 | Align.scaleToGameW(playerIcon, 0.15, this.game); 37 | Align.scaleToGameW(enemyIcon, 0.35, this.game); 38 | this.alignGrid.placeAtIndex(69, playerIcon); 39 | this.alignGrid.placeAtIndex(73, enemyIcon); 40 | playerIcon.angle = 90; 41 | playerIcon.flipX = true; 42 | enemyIcon.angle = 180; 43 | 44 | this.textField = this.add.text(0, 0, 'write your name here', { fixedWidth: this.game.config.width / 2.2, fixedHeight: this.game.config.height / 20, backgroundColor: '#0A0A0A' }); 45 | this.textField.setOrigin(0.5, 0.5); 46 | this.alignGrid.placeAtIndex(104, this.textField); 47 | this.elem = ''; 48 | this.textField.setInteractive().on('pointerdown', () => { 49 | const editor = this.rexUI.edit(this.textField); 50 | this.elem = editor.inputText.node; 51 | this.elem.placeholder = 'write your name here'; 52 | this.elem.autofocus = true; 53 | this.elem.value = ''; 54 | }); 55 | 56 | this.buttonStart = new FlatButton({ 57 | scene: this, 58 | key: 'button1', 59 | text: 'start', 60 | event: 'start_game', 61 | }); 62 | this.alignGrid.placeAtIndex(115, this.buttonStart); 63 | 64 | this.buttonStart.visible = false; 65 | 66 | EventEmitter.on('start_game', this.startGame, this); 67 | 68 | const soundButtons = new SoundButtons({ scene: this }); 69 | soundButtons.depth = 1; 70 | } 71 | 72 | startGame() { 73 | this.scene.start('SceneMain'); 74 | } 75 | 76 | update() { 77 | if (this.elem !== '') { 78 | if (this.elem.value !== '') { 79 | Model.username = this.elem.value; 80 | this.buttonStart.visible = true; 81 | } else { 82 | Model.username = this.elem.value; 83 | this.buttonStart.visible = false; 84 | } 85 | } 86 | } 87 | } 88 | 89 | export default SceneTitle; -------------------------------------------------------------------------------- /src/styles/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 1%; 3 | padding: 1%; 4 | width: 100%; 5 | height: auto; 6 | background-color: black; 7 | display: flex; 8 | justify-content: center; 9 | } 10 | -------------------------------------------------------------------------------- /tests/eventEmitter.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | import EventEmitter from '../src/js/classes/util/eventEmitter'; 3 | 4 | test('EventEmitter is defined when it is initiated', () => { 5 | expect(EventEmitter).toBeDefined(); 6 | }); -------------------------------------------------------------------------------- /tests/flatButton.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | import FlatButton from '../src/js/classes/ui/flatButton'; 3 | 4 | test('FlatButton is defined when it is initiated', () => { 5 | expect(FlatButton).toBeDefined(); 6 | }); 7 | 8 | test('FlatButton is subclass of Phaser.GameObjects.Container', () => { 9 | expect(FlatButton).toBeSubclassOf(Phaser.GameObjects.Container); 10 | }); 11 | -------------------------------------------------------------------------------- /tests/mocks/fileMock.js: -------------------------------------------------------------------------------- 1 | module.exports = 'test-file-stub'; -------------------------------------------------------------------------------- /tests/mocks/styleMock.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; -------------------------------------------------------------------------------- /tests/model.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | 3 | import Model from '../src/js/classes/modelAndController/model'; 4 | 5 | test('Sound will be true as a default given in Model', () => { 6 | expect(Model.soundOn).toEqual(true); 7 | }); 8 | 9 | test('Music will be true as a default given in Model', () => { 10 | expect(Model.musicOn).toBeTruthy(); 11 | }); 12 | 13 | test('After setting musicOn to false with setter, it can be taken by getter in Model', () => { 14 | Model.musicOn = false; 15 | expect(Model.musicOn).toBeFalsy(); 16 | }); 17 | 18 | test('After setting score with setter in Model function, the score will be gettable by getter', () => { 19 | Model.score = 77; 20 | expect(Model.score).toEqual(77); 21 | }); -------------------------------------------------------------------------------- /tests/sceneBoot.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | import SceneBoot from '../src/js/scenes/sceneBoot'; 3 | 4 | test('SceneBoot is a subclass of Phaser.Scene', () => { 5 | expect(SceneBoot).toBeSubclassOf(Phaser.Scene); 6 | }); -------------------------------------------------------------------------------- /tests/sceneLeaderboard.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | import SceneLeaderboard from '../src/js/scenes/sceneBoot'; 3 | 4 | test('SceneLeaderboard is a subclass of Phaser.Scene', () => { 5 | expect(SceneLeaderboard).toBeSubclassOf(Phaser.Scene); 6 | }); -------------------------------------------------------------------------------- /tests/sceneMain.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | import SceneMain from '../src/js/scenes/sceneBoot'; 3 | 4 | test('SceneMain is a subclass of Phaser.Scene', () => { 5 | expect(SceneMain).toBeSubclassOf(Phaser.Scene); 6 | }); -------------------------------------------------------------------------------- /tests/scenePreloader.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | import ScenePreloader from '../src/js/scenes/sceneBoot'; 3 | 4 | test('ScenePreloader is a subclass of Phaser.Scene', () => { 5 | expect(ScenePreloader).toBeSubclassOf(Phaser.Scene); 6 | }); -------------------------------------------------------------------------------- /tests/sceneTitle.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | import SceneTitle from '../src/js/scenes/sceneBoot'; 3 | 4 | test('SceneTitle is a subclass of Phaser.Scene', () => { 5 | expect(SceneTitle).toBeSubclassOf(Phaser.Scene); 6 | }); -------------------------------------------------------------------------------- /tests/scoreBox.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | 3 | import ScoreBox from '../src/js/classes/comps/scoreBox'; 4 | 5 | test('ScoreBox is a subclass of Phaser.GameObjects.Container', () => { 6 | expect(ScoreBox).toBeSubclassOf(Phaser.GameObjects.Container); 7 | }); 8 | -------------------------------------------------------------------------------- /tests/serviceApi.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | 3 | import { postScore, getScores } from '../src/js/classes/util/serviceApi'; 4 | 5 | beforeEach(() => { 6 | fetch.resetMocks(); 7 | }); 8 | 9 | describe('GET', () => { 10 | it('should request with the right url (base_url/:id/scores)', () => { 11 | fetch.mockResponseOnce(JSON.stringify({ 12 | result: [ 13 | { 14 | user: 'Simon Rally', 15 | score: 110, 16 | }], 17 | })); 18 | getScores() 19 | .then(() => { 20 | expect(global.fetch).toHaveBeenCalledWith( 21 | 'https://us-central1-js-capstone-backend.cloudfunctions.net/api/games/plPX1KbSDTjkjLXIA8ui/scores/', { 22 | method: 'GET', 23 | headers: { 24 | 'Content-type': 'application/json;charset=UTF-8', 25 | }, 26 | }, 27 | ); 28 | }) 29 | .catch(() => {}); 30 | }); 31 | 32 | it('should get name and score using the base_url/:id/scores and return a 201 response code', () => { 33 | getScores() 34 | .then(response => { 35 | expect(response.name).toBe('test user'); 36 | }) 37 | .catch(() => {}); 38 | }); 39 | }); 40 | 41 | describe('POST', () => { 42 | fetch.mockResponseOnce(JSON.stringify([{ result: 'Leaderboard score created correctly.' }])); 43 | const onResponse = jest.fn(); 44 | const onError = jest.fn(); 45 | 46 | it('should post name and score using the base_url/:id/scores and return a 201 response code', () => { 47 | postScore() 48 | .then(() => { 49 | expect(global.fetch).toHaveBeenCalledWith( 50 | 'https://cors-anywhere.herokuapp.com/https://us-central1-js-capstone-backend.cloudfunctions.net/api/games/plPX1KbSDTjkjLXIA8ui/scores/', { 51 | mode: 'cors', 52 | method: 'POST', 53 | body: JSON.stringify(data), 54 | headers: { 55 | Accept: 'application/json', 56 | 'Content-type': 'application/json; charset=UTF-8', 57 | }, 58 | }, 59 | ); 60 | }) 61 | .catch(() => {}); 62 | }); 63 | it('should post name and score using the base_url/:id/scores', () => { 64 | postScore('usernameForTest', 319) 65 | .then(onResponse) 66 | .catch(onError) 67 | .finally(() => { 68 | expect(onResponse).toHaveBeenCalled(); 69 | expect(onError).not.toHaveBeenCalled(); 70 | }); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | // Webpack uses this to work with directories 2 | const path = require('path'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | 5 | // This is the main configuration object. 6 | // Here you write different options and tell Webpack what to do 7 | module.exports = { 8 | 9 | // Path to your entry point. From this file Webpack will begin his work 10 | entry: './src/js/index.js', 11 | 12 | // Path and filename of your result bundle. 13 | // Webpack will bundle all JavaScript into this file 14 | output: { 15 | path: path.resolve(__dirname, 'dist'), 16 | filename: './bundle.js', 17 | }, 18 | plugins: [ 19 | new HtmlWebpackPlugin({ 20 | hash: false, 21 | filename: './index.html', 22 | }), 23 | ], 24 | module: { 25 | rules: [ 26 | { 27 | test: /\.css$/i, 28 | use: [ 29 | { loader: 'style-loader' }, 30 | { loader: 'css-loader' }], 31 | }, 32 | { 33 | // Now we apply rule for images 34 | test: /\.(png|jpe?g|gif|svg)$/, 35 | use: [ 36 | { 37 | // Using file-loader for these files 38 | loader: 'file-loader', 39 | 40 | // In options we can set different things like format 41 | // and directory to save 42 | options: { 43 | outputPath: 'images', 44 | }, 45 | }, 46 | ], 47 | }, 48 | { 49 | // Apply rule for fonts files 50 | test: /\.(woff|woff2|ttf|otf|eot)$/, 51 | use: [ 52 | { 53 | // Using file-loader too 54 | loader: 'file-loader', 55 | options: { 56 | outputPath: 'fonts', 57 | }, 58 | }, 59 | ], 60 | }, 61 | { 62 | test: /\.(wav|mp3|ogg)$/, 63 | loader: 'file-loader', 64 | options: { 65 | name: '[path][name].[ext]', 66 | }, 67 | }, 68 | ], 69 | }, 70 | devServer: { 71 | contentBase: path.join(__dirname, 'dist'), 72 | compress: true, 73 | port: 8072, 74 | headers: { 75 | 'Access-Control-Allow-Origin': '*', 76 | 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS', 77 | 'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization', 78 | }, 79 | allowedHosts: [ 80 | 'localhost:8072', 81 | 'labs.phaser.io', 82 | ], 83 | }, 84 | 85 | // Default mode for Webpack is production. 86 | // Depending on mode Webpack will apply different things 87 | // on final bundle. For now we don't need production's JavaScript 88 | // minifying and other thing so let's set mode to development 89 | mode: 'development', 90 | }; 91 | --------------------------------------------------------------------------------