├── .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 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
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 | -
57 | About The Project
58 |
63 |
64 | -
65 | Getting Started
66 |
72 |
73 | - Author
74 | - Contributing
75 | - License
76 | - Acknowledgments
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 |
--------------------------------------------------------------------------------