├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .lintstagedrc.js ├── .npmrc ├── .prettierrc ├── README.md ├── assets ├── images │ └── face.png ├── logo.png ├── maps │ ├── new │ │ ├── map01.json │ │ ├── map01merged.json │ │ ├── objects.tsx │ │ ├── tiles.png │ │ ├── tiles.tsx │ │ └── tiles_reserve.tsx │ └── tiles.png ├── music │ └── phaser-quest-intro.ogg ├── phaser-logo.png ├── playersheets │ ├── mage.json │ └── mage.png ├── shaders │ └── fireball_shader.frag └── skillsheets │ ├── cast_001.png │ ├── fire_002.png │ └── s001.png ├── docs ├── assets │ ├── images │ │ └── face.png │ ├── logo.png │ ├── maps │ │ ├── new │ │ │ ├── map01.json │ │ │ ├── map01merged.json │ │ │ ├── objects.tsx │ │ │ ├── tiles.png │ │ │ ├── tiles.tsx │ │ │ └── tiles_reserve.tsx │ │ └── tiles.png │ ├── music │ │ └── phaser-quest-intro.ogg │ ├── phaser-logo.png │ ├── playersheets │ │ ├── mage.json │ │ └── mage.png │ ├── shaders │ │ └── fireball_shader.frag │ └── skillsheets │ │ ├── cast_001.png │ │ ├── fire_002.png │ │ └── s001.png ├── content │ └── main.c59fad21881d3d0cb5bf.css ├── index.html ├── main.f35f7020.js └── main.f35f7020.js.LICENSE.txt ├── npmw ├── npmw.cmd ├── package.json ├── postcss.config.js ├── readme ├── build.png ├── header.png ├── img.png ├── img_1.png └── img_2.png ├── src ├── actors │ └── phaserLogo.ts ├── app.scss ├── app.tsx ├── component │ ├── debug │ │ └── index.tsx │ ├── game │ │ └── index.tsx │ ├── loader │ │ └── index.tsx │ └── login │ │ ├── index.scss │ │ └── index.tsx ├── config │ ├── config.ts │ ├── constants.ts │ └── icon-loader.ts ├── game │ ├── actors │ │ ├── items │ │ │ └── face.ts │ │ ├── players │ │ │ ├── mage.ts │ │ │ └── player.ts │ │ └── skills │ │ │ ├── buff.ts │ │ │ ├── fireball.ts │ │ │ ├── skill-factory.ts │ │ │ └── skill.ts │ └── scenes │ │ ├── bootstrap.scene.ts │ │ └── game.scene.ts ├── hooks.ts ├── index.html ├── index.tsx ├── net │ └── game-web-socket.ts ├── phaser-game.ts ├── routes.tsx ├── shared │ └── Direction.ts ├── store │ ├── application.store.ts │ └── index.ts └── utils.ts ├── tsconfig.json └── webpack ├── environment.js ├── utils.js ├── webpack.common.js ├── webpack.dev.js └── webpack.prod.js /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | src/main/docker/ 3 | jest.conf.js 4 | webpack/ 5 | target/ 6 | build/ 7 | node/ 8 | assets/ 9 | postcss.config.js 10 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "plugins": [ 4 | "@typescript-eslint" 5 | ], 6 | "extends": [ 7 | "plugin:react/recommended", 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/eslint-recommended", 10 | "plugin:@typescript-eslint/recommended", 11 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 12 | "prettier", 13 | "eslint-config-prettier" 14 | ], 15 | "parserOptions": { 16 | "ecmaVersion": 2018, 17 | "sourceType": "module", 18 | "ecmaFeatures": { 19 | "jsx": true 20 | }, 21 | "project": "./tsconfig.json" 22 | }, 23 | "settings": { 24 | "react": { 25 | "version": "detect" 26 | } 27 | }, 28 | "rules": { 29 | "@typescript-eslint/member-ordering": [ 30 | "error", 31 | { 32 | "default": [ 33 | "static-field", 34 | "instance-field", 35 | "constructor", 36 | "static-method", 37 | "instance-method" 38 | ] 39 | } 40 | ], 41 | "@typescript-eslint/no-parameter-properties": [ 42 | "warn", 43 | { 44 | "allows": [ 45 | "public", 46 | "private", 47 | "protected" 48 | ] 49 | } 50 | ], 51 | "@typescript-eslint/no-this-alias": "off", 52 | "@typescript-eslint/no-unused-vars": "off", 53 | "@typescript-eslint/ban-ts-comment": "off", 54 | "@typescript-eslint/explicit-member-accessibility": "off", 55 | "@typescript-eslint/explicit-function-return-type": "off", 56 | "@typescript-eslint/no-explicit-any": "off", 57 | "@typescript-eslint/no-unsafe-argument": "off", 58 | "@typescript-eslint/no-unsafe-return": "off", 59 | "@typescript-eslint/no-unsafe-member-access": "off", 60 | "@typescript-eslint/no-unsafe-call": "off", 61 | "@typescript-eslint/no-unsafe-assignment": "off", 62 | "@typescript-eslint/explicit-module-boundary-types": "off", 63 | "@typescript-eslint/restrict-template-expressions": "off", 64 | "@typescript-eslint/restrict-plus-operands": "off", 65 | "@typescript-eslint/no-floating-promises": "off", 66 | "@typescript-eslint/ban-types": [ 67 | "error", 68 | { 69 | "types": { 70 | "Object": "Use {} instead." 71 | } 72 | } 73 | ], 74 | "@typescript-eslint/no-empty-interface": "off", 75 | "@typescript-eslint/interface-name-prefix": "off", 76 | "@typescript-eslint/no-empty-function": "off", 77 | "@typescript-eslint/unbound-method": "off", 78 | "@typescript-eslint/array-type": "off", 79 | "@typescript-eslint/no-shadow": "error", 80 | "spaced-comment": [ 81 | "warn", 82 | "always" 83 | ], 84 | "guard-for-in": "error", 85 | "no-labels": "error", 86 | "no-caller": "error", 87 | "no-bitwise": "error", 88 | "no-new-wrappers": "error", 89 | "no-eval": "error", 90 | "no-new": "off", 91 | "no-var": "off", 92 | "radix": "error", 93 | "eqeqeq": [ 94 | "error", 95 | "always", 96 | { 97 | "null": "ignore" 98 | } 99 | ], 100 | "prefer-const": "error", 101 | "object-shorthand": [ 102 | "error", 103 | "always", 104 | { 105 | "avoidExplicitReturnArrows": true 106 | } 107 | ], 108 | "default-case": "error", 109 | "complexity": [ 110 | "error", 111 | 200 112 | ], 113 | "no-invalid-this": "off", 114 | "react/prop-types": "off", 115 | "react/display-name": "off" 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | test/images/actual.png 2 | test/images/diff-*.png 3 | /build 4 | /target 5 | .github/ 6 | .idea/ 7 | *.iml 8 | # Generated files 9 | dist/ 10 | 11 | # Logs 12 | logs 13 | *.log 14 | npm-debug.log* 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | 21 | # Directory for instrumented libs generated by jscoverage/JSCover 22 | lib-cov 23 | 24 | # Coverage directory used by tools like istanbul 25 | coverage 26 | 27 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 28 | .grunt 29 | 30 | # node-waf configuration 31 | .lock-wscript 32 | 33 | # Compiled binary addons (http://nodejs.org/api/addons.html) 34 | build/Release 35 | 36 | # Dependency directories 37 | node_modules 38 | jspm_packages 39 | 40 | # Optional npm cache directory 41 | .npm 42 | 43 | # Optional REPL history 44 | .node_repl_history 45 | 46 | # ========================= 47 | # Operating System Files 48 | # ========================= 49 | 50 | # OSX 51 | # ========================= 52 | 53 | .DS_Store 54 | .AppleDouble 55 | .LSOverride 56 | 57 | # Thumbnails 58 | ._* 59 | 60 | # Files that might appear in the root of a volume 61 | .DocumentRevisions-V100 62 | .fseventsd 63 | .Spotlight-V100 64 | .TemporaryItems 65 | .Trashes 66 | .VolumeIcon.icns 67 | 68 | # Directories potentially created on remote AFP share 69 | .AppleDB 70 | .AppleDesktop 71 | Network Trash Folder 72 | Temporary Items 73 | .apdisk 74 | 75 | # Windows 76 | # ========================= 77 | 78 | # Windows image file caches 79 | Thumbs.db 80 | ehthumbs.db 81 | 82 | # Folder config file 83 | Desktop.ini 84 | 85 | # Recycle Bin used on file shares 86 | $RECYCLE.BIN/ 87 | 88 | # Windows Installer files 89 | *.cab 90 | *.msi 91 | *.msm 92 | *.msp 93 | 94 | # Windows shortcuts 95 | *.lnk 96 | package-lock.json 97 | -------------------------------------------------------------------------------- /.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '{,src/**/,webpack/}*.{md,json,yml,html,cjs,mjs,js,ts,tsx,css,scss,java}': ['prettier --write'], 3 | }; 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | # Prettier configuration 2 | 3 | printWidth: 140 4 | singleQuote: true 5 | tabWidth: 2 6 | useTabs: false 7 | 8 | # js and ts rules: 9 | arrowParens: avoid 10 | 11 | # jsx and tsx rules: 12 | jsxBracketSameLine: false -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 | header 4 |
5 | Phaser 3 React TypeScript Websocket demo 2D-RPG game 6 |
7 |

8 | 9 |

10 | This demo was created using Phaser 3 react template

11 | 12 |

13 | 14 | 15 | 16 |

17 | 18 |

19 | Key Features • 20 | Preview • 21 | How To Use • 22 | React controls • 23 | Websocket support • 24 | Credits 25 |

26 | 27 | --- 28 | 29 | ## Key Features 30 | 31 | - All newest ES 2020 features 32 | - Prettier 33 | - Webpack dev server 34 | - Includes Phaser 3 TypeScript typings 35 | - For development and production builds 36 | - React gui development 37 | - Websocket integration 38 | 39 | ## Preview 40 | 41 | This is what you get after installing this demo: 42 | 43 | 44 | 45 | 46 | ## How To Use 47 | 48 | To clone and run this template, you'll need [Git](https://git-scm.com) and [Node.js](https://nodejs.org/en/download/) (which comes with [npm](http://npmjs.com)) installed on your computer. From your command line: 49 | 50 | ```bash 51 | # Clone this repository (yes, npx not npm) 52 | $ git clone https://github.com/tfkfan/phaser3-game-demo.git 53 | 54 | # Go into the repository 55 | $ cd phaser3-game-demo 56 | 57 | # Install dependencies 58 | $ npm install 59 | 60 | # Start the local development server (on port 8080) 61 | $ npm start 62 | 63 | # Ready for production? 64 | # Build the production ready code to the /dist folder 65 | $ npm run build 66 | ``` 67 | -------------------------------------------------------------------------------- /assets/images/face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tfkfan/phaser3-game-demo/344117cf704cfda23614759630d35a599255ddd5/assets/images/face.png -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tfkfan/phaser3-game-demo/344117cf704cfda23614759630d35a599255ddd5/assets/logo.png -------------------------------------------------------------------------------- /assets/maps/new/objects.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 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 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /assets/maps/new/tiles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tfkfan/phaser3-game-demo/344117cf704cfda23614759630d35a599255ddd5/assets/maps/new/tiles.png -------------------------------------------------------------------------------- /assets/maps/new/tiles.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/maps/new/tiles_reserve.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 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 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /assets/maps/tiles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tfkfan/phaser3-game-demo/344117cf704cfda23614759630d35a599255ddd5/assets/maps/tiles.png -------------------------------------------------------------------------------- /assets/music/phaser-quest-intro.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tfkfan/phaser3-game-demo/344117cf704cfda23614759630d35a599255ddd5/assets/music/phaser-quest-intro.ogg -------------------------------------------------------------------------------- /assets/phaser-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tfkfan/phaser3-game-demo/344117cf704cfda23614759630d35a599255ddd5/assets/phaser-logo.png -------------------------------------------------------------------------------- /assets/playersheets/mage.json: -------------------------------------------------------------------------------- 1 | { 2 | "frames": { 3 | "up0": { 4 | "frame": { 5 | "x": 0, 6 | "y": 0, 7 | "w": 75, 8 | "h": 61 9 | } 10 | }, 11 | "up1": { 12 | "frame": { 13 | "x": 0, 14 | "y": 61, 15 | "w": 75, 16 | "h": 61 17 | } 18 | }, 19 | "up2": { 20 | "frame": { 21 | "x": 0, 22 | "y": 122, 23 | "w": 75, 24 | "h": 61 25 | } 26 | }, 27 | "up3": { 28 | "frame": { 29 | "x": 0, 30 | "y": 183, 31 | "w": 75, 32 | "h": 61 33 | } 34 | }, 35 | "up4": { 36 | "frame": { 37 | "x": 0, 38 | "y": 244, 39 | "w": 75, 40 | "h": 61 41 | } 42 | }, 43 | "upright0": { 44 | "frame": { 45 | "x": 75, 46 | "y": 0, 47 | "w": 75, 48 | "h": 61 49 | } 50 | }, 51 | "upright1": { 52 | "frame": { 53 | "x": 75, 54 | "y": 61, 55 | "w": 75, 56 | "h": 61 57 | } 58 | }, 59 | "upright2": { 60 | "frame": { 61 | "x": 75, 62 | "y": 122, 63 | "w": 75, 64 | "h": 61 65 | } 66 | }, 67 | "upright3": { 68 | "frame": { 69 | "x": 75, 70 | "y": 183, 71 | "w": 75, 72 | "h": 61 73 | } 74 | }, 75 | "upright4": { 76 | "frame": { 77 | "x": 75, 78 | "y": 244, 79 | "w": 75, 80 | "h": 61 81 | } 82 | }, 83 | "rightup0": { 84 | "frame": { 85 | "x": 75, 86 | "y": 0, 87 | "w": 75, 88 | "h": 61 89 | } 90 | }, 91 | "rightup1": { 92 | "frame": { 93 | "x": 75, 94 | "y": 61, 95 | "w": 75, 96 | "h": 61 97 | } 98 | }, 99 | "rightup2": { 100 | "frame": { 101 | "x": 75, 102 | "y": 122, 103 | "w": 75, 104 | "h": 61 105 | } 106 | }, 107 | "rightup3": { 108 | "frame": { 109 | "x": 75, 110 | "y": 183, 111 | "w": 75, 112 | "h": 61 113 | } 114 | }, 115 | "rightup4": { 116 | "frame": { 117 | "x": 75, 118 | "y": 244, 119 | "w": 75, 120 | "h": 61 121 | } 122 | }, 123 | "right0": { 124 | "frame": { 125 | "x": 150, 126 | "y": 0, 127 | "w": 75, 128 | "h": 61 129 | } 130 | }, 131 | "right1": { 132 | "frame": { 133 | "x": 150, 134 | "y": 61, 135 | "w": 75, 136 | "h": 61 137 | } 138 | }, 139 | "right2": { 140 | "frame": { 141 | "x": 150, 142 | "y": 122, 143 | "w": 75, 144 | "h": 61 145 | } 146 | }, 147 | "right3": { 148 | "frame": { 149 | "x": 150, 150 | "y": 183, 151 | "w": 75, 152 | "h": 61 153 | } 154 | }, 155 | "right4": { 156 | "frame": { 157 | "x": 150, 158 | "y": 244, 159 | "w": 75, 160 | "h": 61 161 | } 162 | }, 163 | "downright0": { 164 | "frame": { 165 | "x": 225, 166 | "y": 0, 167 | "w": 75, 168 | "h": 61 169 | } 170 | }, 171 | "downright1": { 172 | "frame": { 173 | "x": 225, 174 | "y": 61, 175 | "w": 75, 176 | "h": 61 177 | } 178 | }, 179 | "downright2": { 180 | "frame": { 181 | "x": 225, 182 | "y": 122, 183 | "w": 75, 184 | "h": 61 185 | } 186 | }, 187 | "downright3": { 188 | "frame": { 189 | "x": 225, 190 | "y": 183, 191 | "w": 75, 192 | "h": 61 193 | } 194 | }, 195 | "downright4": { 196 | "frame": { 197 | "x": 225, 198 | "y": 244, 199 | "w": 75, 200 | "h": 61 201 | } 202 | }, 203 | "rightdown0": { 204 | "frame": { 205 | "x": 225, 206 | "y": 0, 207 | "w": 75, 208 | "h": 61 209 | } 210 | }, 211 | "rightdown1": { 212 | "frame": { 213 | "x": 225, 214 | "y": 61, 215 | "w": 75, 216 | "h": 61 217 | } 218 | }, 219 | "rightdown2": { 220 | "frame": { 221 | "x": 225, 222 | "y": 122, 223 | "w": 75, 224 | "h": 61 225 | } 226 | }, 227 | "rightdown3": { 228 | "frame": { 229 | "x": 225, 230 | "y": 183, 231 | "w": 75, 232 | "h": 61 233 | } 234 | }, 235 | "rightdown4": { 236 | "frame": { 237 | "x": 225, 238 | "y": 244, 239 | "w": 75, 240 | "h": 61 241 | } 242 | }, 243 | "down0": { 244 | "frame": { 245 | "x": 300, 246 | "y": 0, 247 | "w": 75, 248 | "h": 61 249 | } 250 | }, 251 | "down1": { 252 | "frame": { 253 | "x": 300, 254 | "y": 61, 255 | "w": 75, 256 | "h": 61 257 | } 258 | }, 259 | "down2": { 260 | "frame": { 261 | "x": 300, 262 | "y": 122, 263 | "w": 75, 264 | "h": 61 265 | } 266 | }, 267 | "down3": { 268 | "frame": { 269 | "x": 300, 270 | "y": 183, 271 | "w": 75, 272 | "h": 61 273 | } 274 | }, 275 | "down4": { 276 | "frame": { 277 | "x": 300, 278 | "y": 244, 279 | "w": 75, 280 | "h": 61 281 | } 282 | }, 283 | "downleft0": { 284 | "frame": { 285 | "x": 375, 286 | "y": 0, 287 | "w": 75, 288 | "h": 61 289 | } 290 | }, 291 | "downleft1": { 292 | "frame": { 293 | "x": 375, 294 | "y": 61, 295 | "w": 75, 296 | "h": 61 297 | } 298 | }, 299 | "downleft2": { 300 | "frame": { 301 | "x": 375, 302 | "y": 122, 303 | "w": 75, 304 | "h": 61 305 | } 306 | }, 307 | "downleft3": { 308 | "frame": { 309 | "x": 375, 310 | "y": 183, 311 | "w": 75, 312 | "h": 61 313 | } 314 | }, 315 | "downleft4": { 316 | "frame": { 317 | "x": 375, 318 | "y": 244, 319 | "w": 75, 320 | "h": 61 321 | } 322 | }, 323 | "leftdown0": { 324 | "frame": { 325 | "x": 375, 326 | "y": 0, 327 | "w": 75, 328 | "h": 61 329 | } 330 | }, 331 | "leftdown1": { 332 | "frame": { 333 | "x": 375, 334 | "y": 61, 335 | "w": 75, 336 | "h": 61 337 | } 338 | }, 339 | "leftdown2": { 340 | "frame": { 341 | "x": 375, 342 | "y": 122, 343 | "w": 75, 344 | "h": 61 345 | } 346 | }, 347 | "leftdown3": { 348 | "frame": { 349 | "x": 375, 350 | "y": 183, 351 | "w": 75, 352 | "h": 61 353 | } 354 | }, 355 | "leftdown4": { 356 | "frame": { 357 | "x": 375, 358 | "y": 244, 359 | "w": 75, 360 | "h": 61 361 | } 362 | }, 363 | "left0": { 364 | "frame": { 365 | "x": 450, 366 | "y": 0, 367 | "w": 75, 368 | "h": 61 369 | } 370 | }, 371 | "left1": { 372 | "frame": { 373 | "x": 450, 374 | "y": 61, 375 | "w": 75, 376 | "h": 61 377 | } 378 | }, 379 | "left2": { 380 | "frame": { 381 | "x": 450, 382 | "y": 122, 383 | "w": 75, 384 | "h": 61 385 | } 386 | }, 387 | "left3": { 388 | "frame": { 389 | "x": 450, 390 | "y": 183, 391 | "w": 75, 392 | "h": 61 393 | } 394 | }, 395 | "left4": { 396 | "frame": { 397 | "x": 450, 398 | "y": 244, 399 | "w": 75, 400 | "h": 61 401 | } 402 | }, 403 | "upleft0": { 404 | "frame": { 405 | "x": 525, 406 | "y": 0, 407 | "w": 75, 408 | "h": 61 409 | } 410 | }, 411 | "upleft1": { 412 | "frame": { 413 | "x": 525, 414 | "y": 61, 415 | "w": 75, 416 | "h": 61 417 | } 418 | }, 419 | "upleft2": { 420 | "frame": { 421 | "x": 525, 422 | "y": 122, 423 | "w": 75, 424 | "h": 61 425 | } 426 | }, 427 | "upleft3": { 428 | "frame": { 429 | "x": 525, 430 | "y": 183, 431 | "w": 75, 432 | "h": 61 433 | } 434 | }, 435 | "upleft4": { 436 | "frame": { 437 | "x": 525, 438 | "y": 244, 439 | "w": 75, 440 | "h": 61 441 | } 442 | }, 443 | "leftup0": { 444 | "frame": { 445 | "x": 525, 446 | "y": 0, 447 | "w": 75, 448 | "h": 61 449 | } 450 | }, 451 | "leftup1": { 452 | "frame": { 453 | "x": 525, 454 | "y": 61, 455 | "w": 75, 456 | "h": 61 457 | } 458 | }, 459 | "leftup2": { 460 | "frame": { 461 | "x": 525, 462 | "y": 122, 463 | "w": 75, 464 | "h": 61 465 | } 466 | }, 467 | "leftup3": { 468 | "frame": { 469 | "x": 525, 470 | "y": 183, 471 | "w": 75, 472 | "h": 61 473 | } 474 | }, 475 | "leftup4": { 476 | "frame": { 477 | "x": 525, 478 | "y": 244, 479 | "w": 75, 480 | "h": 61 481 | } 482 | }, 483 | "upattack0": { 484 | "frame": { 485 | "x": 0, 486 | "y": 305, 487 | "w": 75, 488 | "h": 61 489 | } 490 | }, 491 | "upattack1": { 492 | "frame": { 493 | "x": 0, 494 | "y": 366, 495 | "w": 75, 496 | "h": 61 497 | } 498 | }, 499 | "upattack2": { 500 | "frame": { 501 | "x": 0, 502 | "y": 427, 503 | "w": 75, 504 | "h": 61 505 | } 506 | }, 507 | "upattack3": { 508 | "frame": { 509 | "x": 0, 510 | "y": 488, 511 | "w": 75, 512 | "h": 61 513 | } 514 | }, 515 | "uprightattack0": { 516 | "frame": { 517 | "x": 75, 518 | "y": 305, 519 | "w": 75, 520 | "h": 61 521 | } 522 | }, 523 | "uprightattack1": { 524 | "frame": { 525 | "x": 75, 526 | "y": 366, 527 | "w": 75, 528 | "h": 61 529 | } 530 | }, 531 | "uprightattack2": { 532 | "frame": { 533 | "x": 75, 534 | "y": 427, 535 | "w": 75, 536 | "h": 61 537 | } 538 | }, 539 | "uprightattack3": { 540 | "frame": { 541 | "x": 75, 542 | "y": 488, 543 | "w": 75, 544 | "h": 61 545 | } 546 | }, 547 | "rightupattack0": { 548 | "frame": { 549 | "x": 75, 550 | "y": 305, 551 | "w": 75, 552 | "h": 61 553 | } 554 | }, 555 | "rightupattack1": { 556 | "frame": { 557 | "x": 75, 558 | "y": 366, 559 | "w": 75, 560 | "h": 61 561 | } 562 | }, 563 | "rightupattack2": { 564 | "frame": { 565 | "x": 75, 566 | "y": 427, 567 | "w": 75, 568 | "h": 61 569 | } 570 | }, 571 | "rightupattack3": { 572 | "frame": { 573 | "x": 75, 574 | "y": 488, 575 | "w": 75, 576 | "h": 61 577 | } 578 | }, 579 | "rightattack0": { 580 | "frame": { 581 | "x": 150, 582 | "y": 305, 583 | "w": 75, 584 | "h": 61 585 | } 586 | }, 587 | "rightattack1": { 588 | "frame": { 589 | "x": 150, 590 | "y": 366, 591 | "w": 75, 592 | "h": 61 593 | } 594 | }, 595 | "rightattack2": { 596 | "frame": { 597 | "x": 150, 598 | "y": 427, 599 | "w": 75, 600 | "h": 61 601 | } 602 | }, 603 | "rightattack3": { 604 | "frame": { 605 | "x": 150, 606 | "y": 488, 607 | "w": 75, 608 | "h": 61 609 | } 610 | }, 611 | "downrightattack0": { 612 | "frame": { 613 | "x": 225, 614 | "y": 305, 615 | "w": 75, 616 | "h": 61 617 | } 618 | }, 619 | "downrightattack1": { 620 | "frame": { 621 | "x": 225, 622 | "y": 366, 623 | "w": 75, 624 | "h": 61 625 | } 626 | }, 627 | "downrightattack2": { 628 | "frame": { 629 | "x": 225, 630 | "y": 427, 631 | "w": 75, 632 | "h": 61 633 | } 634 | }, 635 | "downrightattack3": { 636 | "frame": { 637 | "x": 225, 638 | "y": 488, 639 | "w": 75, 640 | "h": 61 641 | } 642 | }, 643 | "rightdownattack0": { 644 | "frame": { 645 | "x": 225, 646 | "y": 305, 647 | "w": 75, 648 | "h": 61 649 | } 650 | }, 651 | "rightdownattack1": { 652 | "frame": { 653 | "x": 225, 654 | "y": 366, 655 | "w": 75, 656 | "h": 61 657 | } 658 | }, 659 | "rightdownattack2": { 660 | "frame": { 661 | "x": 225, 662 | "y": 427, 663 | "w": 75, 664 | "h": 61 665 | } 666 | }, 667 | "rightdownattack3": { 668 | "frame": { 669 | "x": 225, 670 | "y": 488, 671 | "w": 75, 672 | "h": 61 673 | } 674 | }, 675 | "downattack0": { 676 | "frame": { 677 | "x": 300, 678 | "y": 305, 679 | "w": 75, 680 | "h": 61 681 | } 682 | }, 683 | "downattack1": { 684 | "frame": { 685 | "x": 300, 686 | "y": 366, 687 | "w": 75, 688 | "h": 61 689 | } 690 | }, 691 | "downattack2": { 692 | "frame": { 693 | "x": 300, 694 | "y": 427, 695 | "w": 75, 696 | "h": 61 697 | } 698 | }, 699 | "downattack3": { 700 | "frame": { 701 | "x": 300, 702 | "y": 488, 703 | "w": 75, 704 | "h": 61 705 | } 706 | }, 707 | "downleftattack0": { 708 | "frame": { 709 | "x": 375, 710 | "y": 305, 711 | "w": 75, 712 | "h": 61 713 | } 714 | }, 715 | "downleftattack1": { 716 | "frame": { 717 | "x": 375, 718 | "y": 366, 719 | "w": 75, 720 | "h": 61 721 | } 722 | }, 723 | "downleftattack2": { 724 | "frame": { 725 | "x": 375, 726 | "y": 427, 727 | "w": 75, 728 | "h": 61 729 | } 730 | }, 731 | "downleftattack3": { 732 | "frame": { 733 | "x": 375, 734 | "y": 488, 735 | "w": 75, 736 | "h": 61 737 | } 738 | }, 739 | "leftdownattack0": { 740 | "frame": { 741 | "x": 375, 742 | "y": 305, 743 | "w": 75, 744 | "h": 61 745 | } 746 | }, 747 | "leftdownattack1": { 748 | "frame": { 749 | "x": 375, 750 | "y": 366, 751 | "w": 75, 752 | "h": 61 753 | } 754 | }, 755 | "leftdownattack2": { 756 | "frame": { 757 | "x": 375, 758 | "y": 427, 759 | "w": 75, 760 | "h": 61 761 | } 762 | }, 763 | "leftdownattack3": { 764 | "frame": { 765 | "x": 375, 766 | "y": 488, 767 | "w": 75, 768 | "h": 61 769 | } 770 | }, 771 | "leftattack0": { 772 | "frame": { 773 | "x": 450, 774 | "y": 305, 775 | "w": 75, 776 | "h": 61 777 | } 778 | }, 779 | "leftattack1": { 780 | "frame": { 781 | "x": 450, 782 | "y": 366, 783 | "w": 75, 784 | "h": 61 785 | } 786 | }, 787 | "leftattack2": { 788 | "frame": { 789 | "x": 450, 790 | "y": 427, 791 | "w": 75, 792 | "h": 61 793 | } 794 | }, 795 | "leftattack3": { 796 | "frame": { 797 | "x": 450, 798 | "y": 488, 799 | "w": 75, 800 | "h": 61 801 | } 802 | }, 803 | "upleftattack0": { 804 | "frame": { 805 | "x": 525, 806 | "y": 305, 807 | "w": 75, 808 | "h": 61 809 | } 810 | }, 811 | "upleftattack1": { 812 | "frame": { 813 | "x": 525, 814 | "y": 366, 815 | "w": 75, 816 | "h": 61 817 | } 818 | }, 819 | "upleftattack2": { 820 | "frame": { 821 | "x": 525, 822 | "y": 427, 823 | "w": 75, 824 | "h": 61 825 | } 826 | }, 827 | "upleftattack3": { 828 | "frame": { 829 | "x": 525, 830 | "y": 488, 831 | "w": 75, 832 | "h": 61 833 | } 834 | }, 835 | "leftupattack0": { 836 | "frame": { 837 | "x": 525, 838 | "y": 305, 839 | "w": 75, 840 | "h": 61 841 | } 842 | }, 843 | "leftupattack1": { 844 | "frame": { 845 | "x": 525, 846 | "y": 366, 847 | "w": 75, 848 | "h": 61 849 | } 850 | }, 851 | "leftupattack2": { 852 | "frame": { 853 | "x": 525, 854 | "y": 427, 855 | "w": 75, 856 | "h": 61 857 | } 858 | }, 859 | "leftupattack3": { 860 | "frame": { 861 | "x": 525, 862 | "y": 488, 863 | "w": 75, 864 | "h": 61 865 | } 866 | }, 867 | "death0": { 868 | "frame": { 869 | "x": 0, 870 | "y": 552, 871 | "w": 70, 872 | "h": 61 873 | } 874 | }, 875 | "death1": { 876 | "frame": { 877 | "x": 71, 878 | "y": 552, 879 | "w": 70, 880 | "h": 61 881 | } 882 | }, 883 | "death2": { 884 | "frame": { 885 | "x": 137, 886 | "y": 552, 887 | "w": 70, 888 | "h": 61 889 | } 890 | }, 891 | "death3": { 892 | "frame": { 893 | "x": 205, 894 | "y": 552, 895 | "w": 70, 896 | "h": 61 897 | } 898 | }, 899 | "death4": { 900 | "frame": { 901 | "x": 275, 902 | "y": 552, 903 | "w": 70, 904 | "h": 61 905 | } 906 | }, 907 | "death5": { 908 | "frame": { 909 | "x": 8, 910 | "y": 615, 911 | "w": 60, 912 | "h": 61 913 | } 914 | }, 915 | "death6": { 916 | "frame": { 917 | "x": 69, 918 | "y": 615, 919 | "w": 60, 920 | "h": 61 921 | } 922 | } 923 | } 924 | } -------------------------------------------------------------------------------- /assets/playersheets/mage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tfkfan/phaser3-game-demo/344117cf704cfda23614759630d35a599255ddd5/assets/playersheets/mage.png -------------------------------------------------------------------------------- /assets/shaders/fireball_shader.frag: -------------------------------------------------------------------------------- 1 | //Source: http://glslsandbox.com/e#39726.0 2 | 3 | #ifdef GL_ES 4 | precision mediump float; 5 | #endif 6 | 7 | // Yuldashev Mahmud Effect took from shaderToy mahmud9935@gmail.com 8 | 9 | uniform float time; 10 | uniform vec2 mouse; 11 | uniform vec2 resolution; 12 | 13 | float snoise(vec3 uv, float res) 14 | { 15 | const vec3 s = vec3(1e0, 1e2, 1e3); 16 | 17 | uv *= res; 18 | 19 | vec3 uv0 = floor(mod(uv, res))*s; 20 | vec3 uv1 = floor(mod(uv+vec3(1.), res))*s; 21 | 22 | vec3 f = fract(uv); f = f*f*(3.0-2.0*f); 23 | 24 | vec4 v = vec4(uv0.x+uv0.y+uv0.z, uv1.x+uv0.y+uv0.z, 25 | uv0.x+uv1.y+uv0.z, uv1.x+uv1.y+uv0.z); 26 | 27 | vec4 r = fract(sin(v*1e-1)*1e3); 28 | float r0 = mix(mix(r.x, r.y, f.x), mix(r.z, r.w, f.x), f.y); 29 | 30 | r = fract(sin((v + uv1.z - uv0.z)*1e-1)*1e3); 31 | float r1 = mix(mix(r.x, r.y, f.x), mix(r.z, r.w, f.x), f.y); 32 | 33 | return mix(r0, r1, f.z)*2.-1.; 34 | } 35 | 36 | void main( void ) { 37 | 38 | vec2 p = -.5 + gl_FragCoord.xy / resolution.xy; 39 | p.x *= resolution.x/resolution.y; 40 | 41 | float color = 3.0 - (3.*length(2.*p)); 42 | 43 | vec3 coord = vec3(atan(p.x,p.y)/6.2832+.5, length(p)*.4, .5); 44 | 45 | for(int i = 1; i <= 7; i++) 46 | { 47 | float power = pow(2.0, float(i)); 48 | color += (1.5 / power) * snoise(coord + vec3(0.,-time*.05, time*.01), power*16.); 49 | } 50 | gl_FragColor = vec4( color, pow(max(color,0.),1.)*0.4, pow(max(color,0.),2.)*0.15 , color); 51 | } -------------------------------------------------------------------------------- /assets/skillsheets/cast_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tfkfan/phaser3-game-demo/344117cf704cfda23614759630d35a599255ddd5/assets/skillsheets/cast_001.png -------------------------------------------------------------------------------- /assets/skillsheets/fire_002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tfkfan/phaser3-game-demo/344117cf704cfda23614759630d35a599255ddd5/assets/skillsheets/fire_002.png -------------------------------------------------------------------------------- /assets/skillsheets/s001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tfkfan/phaser3-game-demo/344117cf704cfda23614759630d35a599255ddd5/assets/skillsheets/s001.png -------------------------------------------------------------------------------- /docs/assets/images/face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tfkfan/phaser3-game-demo/344117cf704cfda23614759630d35a599255ddd5/docs/assets/images/face.png -------------------------------------------------------------------------------- /docs/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tfkfan/phaser3-game-demo/344117cf704cfda23614759630d35a599255ddd5/docs/assets/logo.png -------------------------------------------------------------------------------- /docs/assets/maps/new/objects.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 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 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /docs/assets/maps/new/tiles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tfkfan/phaser3-game-demo/344117cf704cfda23614759630d35a599255ddd5/docs/assets/maps/new/tiles.png -------------------------------------------------------------------------------- /docs/assets/maps/new/tiles.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/assets/maps/new/tiles_reserve.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 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 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /docs/assets/maps/tiles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tfkfan/phaser3-game-demo/344117cf704cfda23614759630d35a599255ddd5/docs/assets/maps/tiles.png -------------------------------------------------------------------------------- /docs/assets/music/phaser-quest-intro.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tfkfan/phaser3-game-demo/344117cf704cfda23614759630d35a599255ddd5/docs/assets/music/phaser-quest-intro.ogg -------------------------------------------------------------------------------- /docs/assets/phaser-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tfkfan/phaser3-game-demo/344117cf704cfda23614759630d35a599255ddd5/docs/assets/phaser-logo.png -------------------------------------------------------------------------------- /docs/assets/playersheets/mage.json: -------------------------------------------------------------------------------- 1 | { 2 | "frames": { 3 | "up0": { 4 | "frame": { 5 | "x": 0, 6 | "y": 0, 7 | "w": 75, 8 | "h": 61 9 | } 10 | }, 11 | "up1": { 12 | "frame": { 13 | "x": 0, 14 | "y": 61, 15 | "w": 75, 16 | "h": 61 17 | } 18 | }, 19 | "up2": { 20 | "frame": { 21 | "x": 0, 22 | "y": 122, 23 | "w": 75, 24 | "h": 61 25 | } 26 | }, 27 | "up3": { 28 | "frame": { 29 | "x": 0, 30 | "y": 183, 31 | "w": 75, 32 | "h": 61 33 | } 34 | }, 35 | "up4": { 36 | "frame": { 37 | "x": 0, 38 | "y": 244, 39 | "w": 75, 40 | "h": 61 41 | } 42 | }, 43 | "upright0": { 44 | "frame": { 45 | "x": 75, 46 | "y": 0, 47 | "w": 75, 48 | "h": 61 49 | } 50 | }, 51 | "upright1": { 52 | "frame": { 53 | "x": 75, 54 | "y": 61, 55 | "w": 75, 56 | "h": 61 57 | } 58 | }, 59 | "upright2": { 60 | "frame": { 61 | "x": 75, 62 | "y": 122, 63 | "w": 75, 64 | "h": 61 65 | } 66 | }, 67 | "upright3": { 68 | "frame": { 69 | "x": 75, 70 | "y": 183, 71 | "w": 75, 72 | "h": 61 73 | } 74 | }, 75 | "upright4": { 76 | "frame": { 77 | "x": 75, 78 | "y": 244, 79 | "w": 75, 80 | "h": 61 81 | } 82 | }, 83 | "rightup0": { 84 | "frame": { 85 | "x": 75, 86 | "y": 0, 87 | "w": 75, 88 | "h": 61 89 | } 90 | }, 91 | "rightup1": { 92 | "frame": { 93 | "x": 75, 94 | "y": 61, 95 | "w": 75, 96 | "h": 61 97 | } 98 | }, 99 | "rightup2": { 100 | "frame": { 101 | "x": 75, 102 | "y": 122, 103 | "w": 75, 104 | "h": 61 105 | } 106 | }, 107 | "rightup3": { 108 | "frame": { 109 | "x": 75, 110 | "y": 183, 111 | "w": 75, 112 | "h": 61 113 | } 114 | }, 115 | "rightup4": { 116 | "frame": { 117 | "x": 75, 118 | "y": 244, 119 | "w": 75, 120 | "h": 61 121 | } 122 | }, 123 | "right0": { 124 | "frame": { 125 | "x": 150, 126 | "y": 0, 127 | "w": 75, 128 | "h": 61 129 | } 130 | }, 131 | "right1": { 132 | "frame": { 133 | "x": 150, 134 | "y": 61, 135 | "w": 75, 136 | "h": 61 137 | } 138 | }, 139 | "right2": { 140 | "frame": { 141 | "x": 150, 142 | "y": 122, 143 | "w": 75, 144 | "h": 61 145 | } 146 | }, 147 | "right3": { 148 | "frame": { 149 | "x": 150, 150 | "y": 183, 151 | "w": 75, 152 | "h": 61 153 | } 154 | }, 155 | "right4": { 156 | "frame": { 157 | "x": 150, 158 | "y": 244, 159 | "w": 75, 160 | "h": 61 161 | } 162 | }, 163 | "downright0": { 164 | "frame": { 165 | "x": 225, 166 | "y": 0, 167 | "w": 75, 168 | "h": 61 169 | } 170 | }, 171 | "downright1": { 172 | "frame": { 173 | "x": 225, 174 | "y": 61, 175 | "w": 75, 176 | "h": 61 177 | } 178 | }, 179 | "downright2": { 180 | "frame": { 181 | "x": 225, 182 | "y": 122, 183 | "w": 75, 184 | "h": 61 185 | } 186 | }, 187 | "downright3": { 188 | "frame": { 189 | "x": 225, 190 | "y": 183, 191 | "w": 75, 192 | "h": 61 193 | } 194 | }, 195 | "downright4": { 196 | "frame": { 197 | "x": 225, 198 | "y": 244, 199 | "w": 75, 200 | "h": 61 201 | } 202 | }, 203 | "rightdown0": { 204 | "frame": { 205 | "x": 225, 206 | "y": 0, 207 | "w": 75, 208 | "h": 61 209 | } 210 | }, 211 | "rightdown1": { 212 | "frame": { 213 | "x": 225, 214 | "y": 61, 215 | "w": 75, 216 | "h": 61 217 | } 218 | }, 219 | "rightdown2": { 220 | "frame": { 221 | "x": 225, 222 | "y": 122, 223 | "w": 75, 224 | "h": 61 225 | } 226 | }, 227 | "rightdown3": { 228 | "frame": { 229 | "x": 225, 230 | "y": 183, 231 | "w": 75, 232 | "h": 61 233 | } 234 | }, 235 | "rightdown4": { 236 | "frame": { 237 | "x": 225, 238 | "y": 244, 239 | "w": 75, 240 | "h": 61 241 | } 242 | }, 243 | "down0": { 244 | "frame": { 245 | "x": 300, 246 | "y": 0, 247 | "w": 75, 248 | "h": 61 249 | } 250 | }, 251 | "down1": { 252 | "frame": { 253 | "x": 300, 254 | "y": 61, 255 | "w": 75, 256 | "h": 61 257 | } 258 | }, 259 | "down2": { 260 | "frame": { 261 | "x": 300, 262 | "y": 122, 263 | "w": 75, 264 | "h": 61 265 | } 266 | }, 267 | "down3": { 268 | "frame": { 269 | "x": 300, 270 | "y": 183, 271 | "w": 75, 272 | "h": 61 273 | } 274 | }, 275 | "down4": { 276 | "frame": { 277 | "x": 300, 278 | "y": 244, 279 | "w": 75, 280 | "h": 61 281 | } 282 | }, 283 | "downleft0": { 284 | "frame": { 285 | "x": 375, 286 | "y": 0, 287 | "w": 75, 288 | "h": 61 289 | } 290 | }, 291 | "downleft1": { 292 | "frame": { 293 | "x": 375, 294 | "y": 61, 295 | "w": 75, 296 | "h": 61 297 | } 298 | }, 299 | "downleft2": { 300 | "frame": { 301 | "x": 375, 302 | "y": 122, 303 | "w": 75, 304 | "h": 61 305 | } 306 | }, 307 | "downleft3": { 308 | "frame": { 309 | "x": 375, 310 | "y": 183, 311 | "w": 75, 312 | "h": 61 313 | } 314 | }, 315 | "downleft4": { 316 | "frame": { 317 | "x": 375, 318 | "y": 244, 319 | "w": 75, 320 | "h": 61 321 | } 322 | }, 323 | "leftdown0": { 324 | "frame": { 325 | "x": 375, 326 | "y": 0, 327 | "w": 75, 328 | "h": 61 329 | } 330 | }, 331 | "leftdown1": { 332 | "frame": { 333 | "x": 375, 334 | "y": 61, 335 | "w": 75, 336 | "h": 61 337 | } 338 | }, 339 | "leftdown2": { 340 | "frame": { 341 | "x": 375, 342 | "y": 122, 343 | "w": 75, 344 | "h": 61 345 | } 346 | }, 347 | "leftdown3": { 348 | "frame": { 349 | "x": 375, 350 | "y": 183, 351 | "w": 75, 352 | "h": 61 353 | } 354 | }, 355 | "leftdown4": { 356 | "frame": { 357 | "x": 375, 358 | "y": 244, 359 | "w": 75, 360 | "h": 61 361 | } 362 | }, 363 | "left0": { 364 | "frame": { 365 | "x": 450, 366 | "y": 0, 367 | "w": 75, 368 | "h": 61 369 | } 370 | }, 371 | "left1": { 372 | "frame": { 373 | "x": 450, 374 | "y": 61, 375 | "w": 75, 376 | "h": 61 377 | } 378 | }, 379 | "left2": { 380 | "frame": { 381 | "x": 450, 382 | "y": 122, 383 | "w": 75, 384 | "h": 61 385 | } 386 | }, 387 | "left3": { 388 | "frame": { 389 | "x": 450, 390 | "y": 183, 391 | "w": 75, 392 | "h": 61 393 | } 394 | }, 395 | "left4": { 396 | "frame": { 397 | "x": 450, 398 | "y": 244, 399 | "w": 75, 400 | "h": 61 401 | } 402 | }, 403 | "upleft0": { 404 | "frame": { 405 | "x": 525, 406 | "y": 0, 407 | "w": 75, 408 | "h": 61 409 | } 410 | }, 411 | "upleft1": { 412 | "frame": { 413 | "x": 525, 414 | "y": 61, 415 | "w": 75, 416 | "h": 61 417 | } 418 | }, 419 | "upleft2": { 420 | "frame": { 421 | "x": 525, 422 | "y": 122, 423 | "w": 75, 424 | "h": 61 425 | } 426 | }, 427 | "upleft3": { 428 | "frame": { 429 | "x": 525, 430 | "y": 183, 431 | "w": 75, 432 | "h": 61 433 | } 434 | }, 435 | "upleft4": { 436 | "frame": { 437 | "x": 525, 438 | "y": 244, 439 | "w": 75, 440 | "h": 61 441 | } 442 | }, 443 | "leftup0": { 444 | "frame": { 445 | "x": 525, 446 | "y": 0, 447 | "w": 75, 448 | "h": 61 449 | } 450 | }, 451 | "leftup1": { 452 | "frame": { 453 | "x": 525, 454 | "y": 61, 455 | "w": 75, 456 | "h": 61 457 | } 458 | }, 459 | "leftup2": { 460 | "frame": { 461 | "x": 525, 462 | "y": 122, 463 | "w": 75, 464 | "h": 61 465 | } 466 | }, 467 | "leftup3": { 468 | "frame": { 469 | "x": 525, 470 | "y": 183, 471 | "w": 75, 472 | "h": 61 473 | } 474 | }, 475 | "leftup4": { 476 | "frame": { 477 | "x": 525, 478 | "y": 244, 479 | "w": 75, 480 | "h": 61 481 | } 482 | }, 483 | "upattack0": { 484 | "frame": { 485 | "x": 0, 486 | "y": 305, 487 | "w": 75, 488 | "h": 61 489 | } 490 | }, 491 | "upattack1": { 492 | "frame": { 493 | "x": 0, 494 | "y": 366, 495 | "w": 75, 496 | "h": 61 497 | } 498 | }, 499 | "upattack2": { 500 | "frame": { 501 | "x": 0, 502 | "y": 427, 503 | "w": 75, 504 | "h": 61 505 | } 506 | }, 507 | "upattack3": { 508 | "frame": { 509 | "x": 0, 510 | "y": 488, 511 | "w": 75, 512 | "h": 61 513 | } 514 | }, 515 | "uprightattack0": { 516 | "frame": { 517 | "x": 75, 518 | "y": 305, 519 | "w": 75, 520 | "h": 61 521 | } 522 | }, 523 | "uprightattack1": { 524 | "frame": { 525 | "x": 75, 526 | "y": 366, 527 | "w": 75, 528 | "h": 61 529 | } 530 | }, 531 | "uprightattack2": { 532 | "frame": { 533 | "x": 75, 534 | "y": 427, 535 | "w": 75, 536 | "h": 61 537 | } 538 | }, 539 | "uprightattack3": { 540 | "frame": { 541 | "x": 75, 542 | "y": 488, 543 | "w": 75, 544 | "h": 61 545 | } 546 | }, 547 | "rightupattack0": { 548 | "frame": { 549 | "x": 75, 550 | "y": 305, 551 | "w": 75, 552 | "h": 61 553 | } 554 | }, 555 | "rightupattack1": { 556 | "frame": { 557 | "x": 75, 558 | "y": 366, 559 | "w": 75, 560 | "h": 61 561 | } 562 | }, 563 | "rightupattack2": { 564 | "frame": { 565 | "x": 75, 566 | "y": 427, 567 | "w": 75, 568 | "h": 61 569 | } 570 | }, 571 | "rightupattack3": { 572 | "frame": { 573 | "x": 75, 574 | "y": 488, 575 | "w": 75, 576 | "h": 61 577 | } 578 | }, 579 | "rightattack0": { 580 | "frame": { 581 | "x": 150, 582 | "y": 305, 583 | "w": 75, 584 | "h": 61 585 | } 586 | }, 587 | "rightattack1": { 588 | "frame": { 589 | "x": 150, 590 | "y": 366, 591 | "w": 75, 592 | "h": 61 593 | } 594 | }, 595 | "rightattack2": { 596 | "frame": { 597 | "x": 150, 598 | "y": 427, 599 | "w": 75, 600 | "h": 61 601 | } 602 | }, 603 | "rightattack3": { 604 | "frame": { 605 | "x": 150, 606 | "y": 488, 607 | "w": 75, 608 | "h": 61 609 | } 610 | }, 611 | "downrightattack0": { 612 | "frame": { 613 | "x": 225, 614 | "y": 305, 615 | "w": 75, 616 | "h": 61 617 | } 618 | }, 619 | "downrightattack1": { 620 | "frame": { 621 | "x": 225, 622 | "y": 366, 623 | "w": 75, 624 | "h": 61 625 | } 626 | }, 627 | "downrightattack2": { 628 | "frame": { 629 | "x": 225, 630 | "y": 427, 631 | "w": 75, 632 | "h": 61 633 | } 634 | }, 635 | "downrightattack3": { 636 | "frame": { 637 | "x": 225, 638 | "y": 488, 639 | "w": 75, 640 | "h": 61 641 | } 642 | }, 643 | "rightdownattack0": { 644 | "frame": { 645 | "x": 225, 646 | "y": 305, 647 | "w": 75, 648 | "h": 61 649 | } 650 | }, 651 | "rightdownattack1": { 652 | "frame": { 653 | "x": 225, 654 | "y": 366, 655 | "w": 75, 656 | "h": 61 657 | } 658 | }, 659 | "rightdownattack2": { 660 | "frame": { 661 | "x": 225, 662 | "y": 427, 663 | "w": 75, 664 | "h": 61 665 | } 666 | }, 667 | "rightdownattack3": { 668 | "frame": { 669 | "x": 225, 670 | "y": 488, 671 | "w": 75, 672 | "h": 61 673 | } 674 | }, 675 | "downattack0": { 676 | "frame": { 677 | "x": 300, 678 | "y": 305, 679 | "w": 75, 680 | "h": 61 681 | } 682 | }, 683 | "downattack1": { 684 | "frame": { 685 | "x": 300, 686 | "y": 366, 687 | "w": 75, 688 | "h": 61 689 | } 690 | }, 691 | "downattack2": { 692 | "frame": { 693 | "x": 300, 694 | "y": 427, 695 | "w": 75, 696 | "h": 61 697 | } 698 | }, 699 | "downattack3": { 700 | "frame": { 701 | "x": 300, 702 | "y": 488, 703 | "w": 75, 704 | "h": 61 705 | } 706 | }, 707 | "downleftattack0": { 708 | "frame": { 709 | "x": 375, 710 | "y": 305, 711 | "w": 75, 712 | "h": 61 713 | } 714 | }, 715 | "downleftattack1": { 716 | "frame": { 717 | "x": 375, 718 | "y": 366, 719 | "w": 75, 720 | "h": 61 721 | } 722 | }, 723 | "downleftattack2": { 724 | "frame": { 725 | "x": 375, 726 | "y": 427, 727 | "w": 75, 728 | "h": 61 729 | } 730 | }, 731 | "downleftattack3": { 732 | "frame": { 733 | "x": 375, 734 | "y": 488, 735 | "w": 75, 736 | "h": 61 737 | } 738 | }, 739 | "leftdownattack0": { 740 | "frame": { 741 | "x": 375, 742 | "y": 305, 743 | "w": 75, 744 | "h": 61 745 | } 746 | }, 747 | "leftdownattack1": { 748 | "frame": { 749 | "x": 375, 750 | "y": 366, 751 | "w": 75, 752 | "h": 61 753 | } 754 | }, 755 | "leftdownattack2": { 756 | "frame": { 757 | "x": 375, 758 | "y": 427, 759 | "w": 75, 760 | "h": 61 761 | } 762 | }, 763 | "leftdownattack3": { 764 | "frame": { 765 | "x": 375, 766 | "y": 488, 767 | "w": 75, 768 | "h": 61 769 | } 770 | }, 771 | "leftattack0": { 772 | "frame": { 773 | "x": 450, 774 | "y": 305, 775 | "w": 75, 776 | "h": 61 777 | } 778 | }, 779 | "leftattack1": { 780 | "frame": { 781 | "x": 450, 782 | "y": 366, 783 | "w": 75, 784 | "h": 61 785 | } 786 | }, 787 | "leftattack2": { 788 | "frame": { 789 | "x": 450, 790 | "y": 427, 791 | "w": 75, 792 | "h": 61 793 | } 794 | }, 795 | "leftattack3": { 796 | "frame": { 797 | "x": 450, 798 | "y": 488, 799 | "w": 75, 800 | "h": 61 801 | } 802 | }, 803 | "upleftattack0": { 804 | "frame": { 805 | "x": 525, 806 | "y": 305, 807 | "w": 75, 808 | "h": 61 809 | } 810 | }, 811 | "upleftattack1": { 812 | "frame": { 813 | "x": 525, 814 | "y": 366, 815 | "w": 75, 816 | "h": 61 817 | } 818 | }, 819 | "upleftattack2": { 820 | "frame": { 821 | "x": 525, 822 | "y": 427, 823 | "w": 75, 824 | "h": 61 825 | } 826 | }, 827 | "upleftattack3": { 828 | "frame": { 829 | "x": 525, 830 | "y": 488, 831 | "w": 75, 832 | "h": 61 833 | } 834 | }, 835 | "leftupattack0": { 836 | "frame": { 837 | "x": 525, 838 | "y": 305, 839 | "w": 75, 840 | "h": 61 841 | } 842 | }, 843 | "leftupattack1": { 844 | "frame": { 845 | "x": 525, 846 | "y": 366, 847 | "w": 75, 848 | "h": 61 849 | } 850 | }, 851 | "leftupattack2": { 852 | "frame": { 853 | "x": 525, 854 | "y": 427, 855 | "w": 75, 856 | "h": 61 857 | } 858 | }, 859 | "leftupattack3": { 860 | "frame": { 861 | "x": 525, 862 | "y": 488, 863 | "w": 75, 864 | "h": 61 865 | } 866 | }, 867 | "death0": { 868 | "frame": { 869 | "x": 0, 870 | "y": 552, 871 | "w": 70, 872 | "h": 61 873 | } 874 | }, 875 | "death1": { 876 | "frame": { 877 | "x": 71, 878 | "y": 552, 879 | "w": 70, 880 | "h": 61 881 | } 882 | }, 883 | "death2": { 884 | "frame": { 885 | "x": 137, 886 | "y": 552, 887 | "w": 70, 888 | "h": 61 889 | } 890 | }, 891 | "death3": { 892 | "frame": { 893 | "x": 205, 894 | "y": 552, 895 | "w": 70, 896 | "h": 61 897 | } 898 | }, 899 | "death4": { 900 | "frame": { 901 | "x": 275, 902 | "y": 552, 903 | "w": 70, 904 | "h": 61 905 | } 906 | }, 907 | "death5": { 908 | "frame": { 909 | "x": 8, 910 | "y": 615, 911 | "w": 60, 912 | "h": 61 913 | } 914 | }, 915 | "death6": { 916 | "frame": { 917 | "x": 69, 918 | "y": 615, 919 | "w": 60, 920 | "h": 61 921 | } 922 | } 923 | } 924 | } -------------------------------------------------------------------------------- /docs/assets/playersheets/mage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tfkfan/phaser3-game-demo/344117cf704cfda23614759630d35a599255ddd5/docs/assets/playersheets/mage.png -------------------------------------------------------------------------------- /docs/assets/shaders/fireball_shader.frag: -------------------------------------------------------------------------------- 1 | //Source: http://glslsandbox.com/e#39726.0 2 | 3 | #ifdef GL_ES 4 | precision mediump float; 5 | #endif 6 | 7 | // Yuldashev Mahmud Effect took from shaderToy mahmud9935@gmail.com 8 | 9 | uniform float time; 10 | uniform vec2 mouse; 11 | uniform vec2 resolution; 12 | 13 | float snoise(vec3 uv, float res) 14 | { 15 | const vec3 s = vec3(1e0, 1e2, 1e3); 16 | 17 | uv *= res; 18 | 19 | vec3 uv0 = floor(mod(uv, res))*s; 20 | vec3 uv1 = floor(mod(uv+vec3(1.), res))*s; 21 | 22 | vec3 f = fract(uv); f = f*f*(3.0-2.0*f); 23 | 24 | vec4 v = vec4(uv0.x+uv0.y+uv0.z, uv1.x+uv0.y+uv0.z, 25 | uv0.x+uv1.y+uv0.z, uv1.x+uv1.y+uv0.z); 26 | 27 | vec4 r = fract(sin(v*1e-1)*1e3); 28 | float r0 = mix(mix(r.x, r.y, f.x), mix(r.z, r.w, f.x), f.y); 29 | 30 | r = fract(sin((v + uv1.z - uv0.z)*1e-1)*1e3); 31 | float r1 = mix(mix(r.x, r.y, f.x), mix(r.z, r.w, f.x), f.y); 32 | 33 | return mix(r0, r1, f.z)*2.-1.; 34 | } 35 | 36 | void main( void ) { 37 | 38 | vec2 p = -.5 + gl_FragCoord.xy / resolution.xy; 39 | p.x *= resolution.x/resolution.y; 40 | 41 | float color = 3.0 - (3.*length(2.*p)); 42 | 43 | vec3 coord = vec3(atan(p.x,p.y)/6.2832+.5, length(p)*.4, .5); 44 | 45 | for(int i = 1; i <= 7; i++) 46 | { 47 | float power = pow(2.0, float(i)); 48 | color += (1.5 / power) * snoise(coord + vec3(0.,-time*.05, time*.01), power*16.); 49 | } 50 | gl_FragColor = vec4( color, pow(max(color,0.),1.)*0.4, pow(max(color,0.),2.)*0.15 , color); 51 | } -------------------------------------------------------------------------------- /docs/assets/skillsheets/cast_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tfkfan/phaser3-game-demo/344117cf704cfda23614759630d35a599255ddd5/docs/assets/skillsheets/cast_001.png -------------------------------------------------------------------------------- /docs/assets/skillsheets/fire_002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tfkfan/phaser3-game-demo/344117cf704cfda23614759630d35a599255ddd5/docs/assets/skillsheets/fire_002.png -------------------------------------------------------------------------------- /docs/assets/skillsheets/s001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tfkfan/phaser3-game-demo/344117cf704cfda23614759630d35a599255ddd5/docs/assets/skillsheets/s001.png -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | Game
-------------------------------------------------------------------------------- /docs/main.f35f7020.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /*! 8 | Copyright (c) 2018 Jed Watson. 9 | Licensed under the MIT License (MIT), see 10 | http://jedwatson.github.io/classnames 11 | */ 12 | 13 | /** 14 | * @author Ben Richards 15 | * @copyright 2024 Photon Storm Ltd. 16 | * @license {@link https://opensource.org/licenses/MIT|MIT License} 17 | */ 18 | 19 | /** 20 | * @author Benjamin D. Richards 21 | * @copyright 2013-2025 Phaser Studio Inc. 22 | * @license {@link https://opensource.org/licenses/MIT|MIT License} 23 | */ 24 | 25 | /** 26 | * @author Florian Vazelle 27 | * @author Geoffrey Glaive 28 | * @copyright 2013-2025 Phaser Studio Inc. 29 | * @license {@link https://opensource.org/licenses/MIT|MIT License} 30 | */ 31 | 32 | /** 33 | * @author Jason Nicholls 34 | * @copyright 2018 Photon Storm Ltd. 35 | * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} 36 | */ 37 | 38 | /** 39 | * @author Joachim Grill 40 | * @author Richard Davey 41 | * @copyright 2018 CodeAndWeb GmbH 42 | * @copyright 2013-2025 Phaser Studio Inc. 43 | * @license {@link https://opensource.org/licenses/MIT|MIT License} 44 | */ 45 | 46 | /** 47 | * @author Niklas von Hertzen (https://github.com/niklasvh/base64-arraybuffer) 48 | * @author Richard Davey 49 | * @copyright 2013-2025 Phaser Studio Inc. 50 | * @license {@link https://opensource.org/licenses/MIT|MIT License} 51 | */ 52 | 53 | /** 54 | * @author Richard Davey 55 | * @copyright 2013-2025 Phaser Studio Inc. 56 | * @license {@link https://opensource.org/licenses/MIT|MIT License} 57 | */ 58 | 59 | /** 60 | * @author Richard Davey 61 | * @author @samme 62 | * @copyright 2013-2025 Phaser Studio Inc. 63 | * @license {@link https://opensource.org/licenses/MIT|MIT License} 64 | */ 65 | 66 | /** 67 | * @author Richard Davey 68 | * @author Angry Bytes (and contributors) 69 | * @copyright 2013-2025 Phaser Studio Inc. 70 | * @license {@link https://opensource.org/licenses/MIT|MIT License} 71 | */ 72 | 73 | /** 74 | * @author Richard Davey 75 | * @author Felipe Alfonso <@bitnenfer> 76 | * @author Matthew Groves <@doormat> 77 | * @copyright 2013-2025 Phaser Studio Inc. 78 | * @license {@link https://opensource.org/licenses/MIT|MIT License} 79 | */ 80 | 81 | /** 82 | * @author Richard Davey 83 | * @author Felipe Alfonso <@bitnenfer> 84 | * @copyright 2013-2025 Phaser Studio Inc. 85 | * @license {@link https://opensource.org/licenses/MIT|MIT License} 86 | */ 87 | 88 | /** 89 | * @author Richard Davey 90 | * @author Florian Mertens 91 | * @copyright 2013-2025 Phaser Studio Inc. 92 | * @license {@link https://opensource.org/licenses/MIT|MIT License} 93 | */ 94 | 95 | /** 96 | * @author Richard Davey 97 | * @author Igor Ognichenko 98 | * @copyright 2013-2025 Phaser Studio Inc. 99 | * @license {@link https://opensource.org/licenses/MIT|MIT License} 100 | */ 101 | 102 | /** 103 | * @author Richard Davey 104 | * @author Pavle Goloskokovic (http://prunegames.com) 105 | * @copyright 2013-2025 Phaser Studio Inc. 106 | * @license {@link https://opensource.org/licenses/MIT|MIT License} 107 | */ 108 | 109 | /** 110 | * @author Richard Davey 111 | * @author samme 112 | * @copyright 2013-2025 Phaser Studio Inc. 113 | * @license {@link https://opensource.org/licenses/MIT|MIT License} 114 | */ 115 | 116 | /** 117 | * @author Richard Davey 118 | * @copyright 2013-2025 Phaser Studio Inc. 119 | * @license {@link https://opensource.org/licenses/MIT|MIT License} 120 | */ 121 | 122 | /** 123 | * @author Richard Davey 124 | * @copyright 2021 Photon Storm Ltd. 125 | * @license {@link https://opensource.org/licenses/MIT|MIT License} 126 | */ 127 | 128 | /** 129 | * @author Richard Davey 130 | * @copyright 2013-2023 Photon Storm Ltd. 131 | * @license {@link https://opensource.org/licenses/MIT|MIT License} 132 | */ 133 | 134 | /** 135 | * @author Seth Berrier 136 | * @copyright 2013-2025 Phaser Studio Inc. 137 | * @license {@link https://opensource.org/licenses/MIT|MIT License} 138 | */ 139 | 140 | /** 141 | * @author Stefan Hedman (http://steffe.se) 142 | * @license {@link https://opensource.org/licenses/MIT|MIT License} 143 | */ 144 | 145 | /** 146 | * @author Vladimir Agafonkin 147 | * @author Richard Davey 148 | * @copyright 2013-2025 Phaser Studio Inc. 149 | * @license {@link https://opensource.org/licenses/MIT|MIT License} 150 | */ 151 | 152 | /** 153 | * @author pi-kei 154 | * @copyright 2013-2025 Phaser Studio Inc. 155 | * @license {@link https://opensource.org/licenses/MIT|MIT License} 156 | */ 157 | 158 | /** 159 | * @author samme 160 | * @copyright 2013-2025 Phaser Studio Inc. 161 | * @license {@link https://opensource.org/licenses/MIT|MIT License} 162 | */ 163 | 164 | /** 165 | * @author samme 166 | * @copyright 2021 Photon Storm Ltd. 167 | * @license {@link https://opensource.org/licenses/MIT|MIT License} 168 | */ 169 | 170 | /** 171 | * @author Richard Davey 172 | * @copyright 2013-2025 Phaser Studio Inc. 173 | * @license {@link https://github.com/photonstorm/phaser3-plugin-template/blob/master/LICENSE|MIT License} 174 | */ 175 | 176 | /** @license React v0.20.2 177 | * scheduler.production.min.js 178 | * 179 | * Copyright (c) Facebook, Inc. and its affiliates. 180 | * 181 | * This source code is licensed under the MIT license found in the 182 | * LICENSE file in the root directory of this source tree. 183 | */ 184 | 185 | /** @license React v16.13.1 186 | * react-is.production.min.js 187 | * 188 | * Copyright (c) Facebook, Inc. and its affiliates. 189 | * 190 | * This source code is licensed under the MIT license found in the 191 | * LICENSE file in the root directory of this source tree. 192 | */ 193 | 194 | /** @license React v17.0.2 195 | * react-dom.production.min.js 196 | * 197 | * Copyright (c) Facebook, Inc. and its affiliates. 198 | * 199 | * This source code is licensed under the MIT license found in the 200 | * LICENSE file in the root directory of this source tree. 201 | */ 202 | 203 | /** @license React v17.0.2 204 | * react-is.production.min.js 205 | * 206 | * Copyright (c) Facebook, Inc. and its affiliates. 207 | * 208 | * This source code is licensed under the MIT license found in the 209 | * LICENSE file in the root directory of this source tree. 210 | */ 211 | 212 | /** @license React v17.0.2 213 | * react.production.min.js 214 | * 215 | * Copyright (c) Facebook, Inc. and its affiliates. 216 | * 217 | * This source code is licensed under the MIT license found in the 218 | * LICENSE file in the root directory of this source tree. 219 | */ 220 | -------------------------------------------------------------------------------- /npmw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | basedir=`dirname "$0"` 4 | 5 | if [ -f "$basedir/mvnw" ]; then 6 | bindir="$basedir/target/node" 7 | repodir="$basedir/target/node/node_modules" 8 | installCommand="$basedir/mvnw -Pwebapp frontend:install-node-and-npm@install-node-and-npm" 9 | 10 | PATH="$basedir/$builddir/:$PATH" 11 | NPM_EXE="$basedir/$builddir/node_modules/npm/bin/npm-cli.js" 12 | NODE_EXE="$basedir/$builddir/node" 13 | elif [ -f "$basedir/gradlew" ]; then 14 | bindir="$basedir/build/node/bin" 15 | repodir="$basedir/build/node/lib/node_modules" 16 | installCommand="$basedir/gradlew npmSetup" 17 | else 18 | echo "Using npm installed globally" 19 | exec npm "$@" 20 | fi 21 | 22 | NPM_EXE="$repodir/npm/bin/npm-cli.js" 23 | NODE_EXE="$bindir/node" 24 | 25 | if [ ! -x "$NPM_EXE" ] || [ ! -x "$NODE_EXE" ]; then 26 | $installCommand || true 27 | fi 28 | 29 | if [ -x "$NODE_EXE" ]; then 30 | echo "Using node installed locally $($NODE_EXE --version)" 31 | PATH="$bindir:$PATH" 32 | else 33 | NODE_EXE='node' 34 | fi 35 | 36 | if [ ! -x "$NPM_EXE" ]; then 37 | echo "Local npm not found, using npm installed globally" 38 | npm "$@" 39 | else 40 | echo "Using npm installed locally $($NODE_EXE $NPM_EXE --version)" 41 | $NODE_EXE $NPM_EXE "$@" 42 | fi 43 | -------------------------------------------------------------------------------- /npmw.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | @setlocal 4 | 5 | set NPMW_DIR=%~dp0 6 | 7 | if exist "%NPMW_DIR%\mvnw.cmd" ( 8 | set NODE_EXE="" 9 | set NPM_EXE=%NPMW_DIR%\target\node\npm.cmd 10 | set INSTALL_NPM_COMMAND=%NPMW_DIR%\mvnw.cmd -Pwebapp frontend:install-node-and-npm@install-node-and-npm 11 | ) else ( 12 | set NODE_EXE=%NPMW_DIR%\build\node\bin\node.exe 13 | set NPM_EXE=%NPMW_DIR%\build\node\lib\node_modules\npm\bin\npm-cli.js 14 | set INSTALL_NPM_COMMAND=%NPMW_DIR%\gradlew.bat npmSetup 15 | ) 16 | 17 | if not exist %NPM_EXE% ( 18 | call %INSTALL_NPM_COMMAND% 19 | ) 20 | 21 | if exist %NODE_EXE% ( 22 | Rem Executing local npm with local node 23 | call %NODE_EXE% %NPM_EXE% %* 24 | ) else if exist %NPM_EXE% ( 25 | Rem Executing local npm 26 | call %NPM_EXE% %* 27 | ) else ( 28 | call npm %* 29 | ) 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phaser3-react-template", 3 | "version": "1.0.2", 4 | "description": "Phaser 3 starter template with TypeScript , webpack, react, reduxjs and websocket.", 5 | "homepage": "https://github.com/tfkfan/phaser3-react-template#readme", 6 | "author": { 7 | "name": "tfkfan", 8 | "url": "" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/tfkfan/phaser3-react-template.git" 13 | }, 14 | "template": { 15 | "name": "phaser3-react-template", 16 | "url": "https://github.com/tfkfan/phaser3-react-template", 17 | "author": "TFKFAN (https://github.com/tfkfan)" 18 | }, 19 | "engines": { 20 | "node": ">=12" 21 | }, 22 | "license": "MIT", 23 | "main": "index.js", 24 | "scripts": { 25 | "build": "npm run webapp:prod --", 26 | "clean-www": "rimraf build/bundle/app/{src,build/}", 27 | "cleanup": "rimraf build/bundle/", 28 | "lint:fix": "npm run lint -- --fix", 29 | "prettier:check": "prettier --check \"{,src/**/,webpack/}*.{md,json,yml,html,cjs,mjs,js,ts,tsx,css,scss,java}\"", 30 | "prettier:format": "prettier --write \"{,src/**/,webpack/}*.{md,json,yml,html,cjs,mjs,js,ts,tsx,css,scss,java}\"", 31 | "start": "npm run webapp:dev --", 32 | "start-tls": "npm run webapp:dev -- --env.tls", 33 | "pretest": "npm run lint", 34 | "webapp:build": "npm run clean-www && npm run webapp:build:dev --", 35 | "webapp:build:dev": "webpack --config webpack/webpack.dev.js --env stats=minimal", 36 | "webapp:build:prod": "webpack --config webpack/webpack.prod.js --progress=profile", 37 | "webapp:dev": "npm run webpack-dev-server -- --config webpack/webpack.dev.js --env stats=minimal", 38 | "webapp:dev-verbose": "npm run webpack-dev-server -- --config webpack/webpack.dev.js --progress=profile --env stats=normal", 39 | "webapp:prod": "npm run cleanup && npm run webapp:build:prod --", 40 | "webpack-dev-server": "webpack serve" 41 | }, 42 | "config": { 43 | "backend_port": "8080", 44 | "default_environment": "prod", 45 | "packaging": "jar" 46 | }, 47 | "dependencies": { 48 | "@fortawesome/fontawesome-svg-core": "6.2.0", 49 | "@fortawesome/free-solid-svg-icons": "6.2.0", 50 | "@fortawesome/react-fontawesome": "0.1.18", 51 | "@reduxjs/toolkit": "1.8.1", 52 | "axios": "1.8.2", 53 | "bootstrap": "5.1.3", 54 | "bootswatch": "5.1.3", 55 | "browser-sync-client": "^2.27.9", 56 | "path-browserify": "1.0.1", 57 | "phaser": "^3.88.2", 58 | "react": "17.0.2", 59 | "react-dom": "17.0.2", 60 | "react-redux": "7.2.8", 61 | "react-router-dom": "5.3.0", 62 | "react-toastify": "8.2.0", 63 | "react-transition-group": "4.4.2", 64 | "reactstrap": "^8.10.1", 65 | "redux": "4.1.2", 66 | "redux-thunk": "2.4.1" 67 | }, 68 | "devDependencies": { 69 | "@types/lodash": "4.14.181", 70 | "@types/node": "16.11.26", 71 | "@types/react": "^18.2.6", 72 | "@types/react-dom": "17.0.14", 73 | "@types/react-redux": "7.1.23", 74 | "@types/react-router-dom": "5.3.3", 75 | "@types/redux": "3.6.31", 76 | "@types/webpack-env": "1.16.3", 77 | "@typescript-eslint/eslint-plugin": "5.18.0", 78 | "@typescript-eslint/parser": "5.18.0", 79 | "autoprefixer": "10.4.4", 80 | "browser-sync": "^2.27.9", 81 | "browser-sync-webpack-plugin": "2.3.0", 82 | "copy-webpack-plugin": "10.2.4", 83 | "core-js": "3.21.1", 84 | "cross-env": "7.0.3", 85 | "css-loader": "6.7.1", 86 | "css-minimizer-webpack-plugin": "3.4.1", 87 | "eslint": "8.12.0", 88 | "eslint-config-prettier": "8.5.0", 89 | "eslint-plugin-react": "7.29.4", 90 | "eslint-webpack-plugin": "3.1.1", 91 | "filemanager-webpack-plugin": "^7.0.0", 92 | "fork-ts-checker-webpack-plugin": "7.2.3", 93 | "html-webpack-plugin": "5.5.0", 94 | "identity-obj-proxy": "3.0.0", 95 | "json-loader": "0.5.7", 96 | "lint-staged": "12.3.7", 97 | "mini-css-extract-plugin": "2.6.0", 98 | "postcss-loader": "6.2.1", 99 | "prettier": "2.6.2", 100 | "prettier-plugin-java": "1.6.1", 101 | "prettier-plugin-packagejson": "2.2.17", 102 | "react-infinite-scroll-component": "6.1.0", 103 | "redux-mock-store": "1.5.4", 104 | "rimraf": "3.0.2", 105 | "sass": "1.49.11", 106 | "sass-loader": "12.6.0", 107 | "simple-progress-webpack-plugin": "2.0.0", 108 | "sinon": "13.0.1", 109 | "source-map-loader": "3.0.1", 110 | "sourcemap-istanbul-instrumenter-loader": "0.2.0", 111 | "style-loader": "3.3.1", 112 | "terser-webpack-plugin": "5.3.1", 113 | "thread-loader": "3.0.4", 114 | "ts-loader": "9.2.8", 115 | "typescript": "4.6.3", 116 | "webpack": "5.76.0", 117 | "webpack-cli": "4.10.0", 118 | "webpack-dev-server": "4.8.0", 119 | "webpack-merge": "5.8.0", 120 | "webpack-notifier": "1.15.0", 121 | "workbox-webpack-plugin": "6.5.2" 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [require('autoprefixer')], 3 | }; 4 | -------------------------------------------------------------------------------- /readme/build.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tfkfan/phaser3-game-demo/344117cf704cfda23614759630d35a599255ddd5/readme/build.png -------------------------------------------------------------------------------- /readme/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tfkfan/phaser3-game-demo/344117cf704cfda23614759630d35a599255ddd5/readme/header.png -------------------------------------------------------------------------------- /readme/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tfkfan/phaser3-game-demo/344117cf704cfda23614759630d35a599255ddd5/readme/img.png -------------------------------------------------------------------------------- /readme/img_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tfkfan/phaser3-game-demo/344117cf704cfda23614759630d35a599255ddd5/readme/img_1.png -------------------------------------------------------------------------------- /readme/img_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tfkfan/phaser3-game-demo/344117cf704cfda23614759630d35a599255ddd5/readme/img_2.png -------------------------------------------------------------------------------- /src/actors/phaserLogo.ts: -------------------------------------------------------------------------------- 1 | export default class PhaserLogo extends Phaser.Physics.Arcade.Sprite { 2 | constructor(scene, x, y) { 3 | super(scene, x, y, 'phaser-logo') 4 | scene.add.existing(this) 5 | scene.physics.add.existing(this) 6 | 7 | this.setCollideWorldBounds(true) 8 | .setBounce(0.6) 9 | .setInteractive() 10 | .on('pointerdown', () => { 11 | this.setVelocityY(-400) 12 | }) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app.scss: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | --bs-body-color: #ffffff; 4 | margin: 0; 5 | } 6 | 7 | a { 8 | // color: #533f03; 9 | font-weight: bold; 10 | } 11 | 12 | * { 13 | -moz-box-sizing: border-box; 14 | box-sizing: border-box; 15 | 16 | &:after, 17 | &::before { 18 | -moz-box-sizing: border-box; 19 | box-sizing: border-box; 20 | } 21 | } 22 | 23 | .app-container { 24 | box-sizing: border-box; 25 | 26 | .view-container { 27 | width: 100%; 28 | height: calc(100% - 40px); 29 | overflow-y: auto; 30 | overflow-x: hidden; 31 | padding: 1rem; 32 | 33 | .card { 34 | padding: 1rem; 35 | } 36 | 37 | .view-routes { 38 | height: 100%; 39 | 40 | > div { 41 | height: 100%; 42 | } 43 | } 44 | } 45 | } 46 | 47 | .fullscreen { 48 | position: fixed; 49 | top: 100px; 50 | left: 0px; 51 | width: 99% !important; 52 | height: calc(100vh - 110px) !important; 53 | margin: 5px; 54 | z-index: 1000; 55 | padding: 5px 25px 50px 25px !important; 56 | } 57 | 58 | /* ========================================================================== 59 | Browser Upgrade Prompt 60 | ========================================================================== */ 61 | 62 | .browserupgrade { 63 | margin: 0.2em 0; 64 | background: #ccc; 65 | color: #000; 66 | padding: 0.2em 0; 67 | } 68 | 69 | /* ========================================================================== 70 | Custom button styles 71 | ========================================================================== */ 72 | 73 | .icon-button > .btn { 74 | background-color: transparent; 75 | border-color: transparent; 76 | padding: 0.5rem; 77 | line-height: 1rem; 78 | 79 | &:hover { 80 | background-color: transparent; 81 | border-color: transparent; 82 | } 83 | 84 | &:focus { 85 | -webkit-box-shadow: none; 86 | box-shadow: none; 87 | } 88 | } 89 | 90 | /* ========================================================================== 91 | Generic styles 92 | ========================================================================== */ 93 | 94 | /* Temporary workaround for availity-reactstrap-validation */ 95 | .invalid-feedback { 96 | display: inline; 97 | } 98 | 99 | /* other generic styles */ 100 | .white-text{ 101 | color:white; 102 | } 103 | .title { 104 | font-size: 1.25em; 105 | margin: 1px 10px 1px 10px; 106 | } 107 | 108 | .description { 109 | font-size: 0.9em; 110 | margin: 1px 10px 1px 10px; 111 | } 112 | 113 | .shadow { 114 | box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 6px, rgba(0, 0, 0, 0.12) 0px 1px 4px; 115 | border-radius: 2px; 116 | } 117 | 118 | .error { 119 | color: white; 120 | background-color: red; 121 | } 122 | 123 | .break { 124 | white-space: normal; 125 | word-break: break-all; 126 | } 127 | 128 | .break-word { 129 | white-space: normal; 130 | word-break: keep-all; 131 | } 132 | 133 | .preserve-space { 134 | white-space: pre-wrap; 135 | } 136 | 137 | /* padding helpers */ 138 | 139 | @mixin pad($size, $side) { 140 | @if $size== '' { 141 | @if $side== '' { 142 | .pad { 143 | padding: 10px !important; 144 | } 145 | } @else { 146 | .pad { 147 | padding-#{$side}: 10px !important; 148 | } 149 | } 150 | } @else { 151 | @if $side== '' { 152 | .pad-#{$size} { 153 | padding: #{$size}px !important; 154 | } 155 | } @else { 156 | .pad-#{$side}-#{$size} { 157 | padding-#{$side}: #{$size}px !important; 158 | } 159 | } 160 | } 161 | } 162 | 163 | @include pad('', ''); 164 | @include pad('2', ''); 165 | @include pad('3', ''); 166 | @include pad('5', ''); 167 | @include pad('10', ''); 168 | @include pad('20', ''); 169 | @include pad('25', ''); 170 | @include pad('30', ''); 171 | @include pad('50', ''); 172 | @include pad('75', ''); 173 | @include pad('100', ''); 174 | @include pad('4', 'top'); 175 | @include pad('5', 'top'); 176 | @include pad('10', 'top'); 177 | @include pad('20', 'top'); 178 | @include pad('25', 'top'); 179 | @include pad('30', 'top'); 180 | @include pad('50', 'top'); 181 | @include pad('75', 'top'); 182 | @include pad('100', 'top'); 183 | @include pad('4', 'bottom'); 184 | @include pad('5', 'bottom'); 185 | @include pad('10', 'bottom'); 186 | @include pad('20', 'bottom'); 187 | @include pad('25', 'bottom'); 188 | @include pad('30', 'bottom'); 189 | @include pad('50', 'bottom'); 190 | @include pad('75', 'bottom'); 191 | @include pad('100', 'bottom'); 192 | @include pad('5', 'right'); 193 | @include pad('10', 'right'); 194 | @include pad('20', 'right'); 195 | @include pad('25', 'right'); 196 | @include pad('30', 'right'); 197 | @include pad('50', 'right'); 198 | @include pad('75', 'right'); 199 | @include pad('100', 'right'); 200 | @include pad('5', 'left'); 201 | @include pad('10', 'left'); 202 | @include pad('20', 'left'); 203 | @include pad('25', 'left'); 204 | @include pad('30', 'left'); 205 | @include pad('50', 'left'); 206 | @include pad('75', 'left'); 207 | @include pad('100', 'left'); 208 | 209 | @mixin no-padding($side) { 210 | @if $side== 'all' { 211 | .no-padding { 212 | padding: 0 !important; 213 | } 214 | } @else { 215 | .no-padding-#{$side} { 216 | padding-#{$side}: 0 !important; 217 | } 218 | } 219 | } 220 | 221 | @include no-padding('left'); 222 | @include no-padding('right'); 223 | @include no-padding('top'); 224 | @include no-padding('bottom'); 225 | @include no-padding('all'); 226 | 227 | /* end of padding helpers */ 228 | 229 | .no-margin { 230 | margin: 0px; 231 | } 232 | 233 | @mixin voffset($size) { 234 | @if $size== '' { 235 | .voffset { 236 | margin-top: 2px !important; 237 | } 238 | } @else { 239 | .voffset-#{$size} { 240 | margin-top: #{$size}px !important; 241 | } 242 | } 243 | } 244 | 245 | @include voffset(''); 246 | @include voffset('5'); 247 | @include voffset('10'); 248 | @include voffset('15'); 249 | @include voffset('30'); 250 | @include voffset('40'); 251 | @include voffset('60'); 252 | @include voffset('80'); 253 | @include voffset('100'); 254 | @include voffset('150'); 255 | 256 | .readonly { 257 | background-color: #eee; 258 | opacity: 1; 259 | } 260 | 261 | /* ========================================================================== 262 | make sure browsers use the pointer cursor for anchors, even with no href 263 | ========================================================================== */ 264 | 265 | a:hover { 266 | cursor: pointer; 267 | } 268 | 269 | .hand { 270 | cursor: pointer; 271 | } 272 | 273 | button.anchor-btn { 274 | background: none; 275 | border: none; 276 | padding: 0; 277 | align-items: initial; 278 | text-align: initial; 279 | width: 100%; 280 | } 281 | 282 | a.anchor-btn:hover { 283 | text-decoration: none; 284 | } 285 | 286 | /* ========================================================================== 287 | Metrics and Health styles 288 | ========================================================================== */ 289 | 290 | #threadDump .popover, 291 | #healthCheck .popover { 292 | top: inherit; 293 | display: block; 294 | font-size: 10px; 295 | max-width: 1024px; 296 | } 297 | 298 | .thread-dump-modal-lock { 299 | max-width: 450px; 300 | overflow: hidden; 301 | text-overflow: ellipsis; 302 | white-space: nowrap; 303 | } 304 | 305 | #healthCheck .popover { 306 | margin-left: -50px; 307 | } 308 | 309 | .health-details { 310 | min-width: 400px; 311 | } 312 | 313 | .width-min { 314 | width: 1% !important; 315 | } 316 | 317 | .svg-inline--fa { 318 | margin-right: 5px; 319 | } 320 | 321 | .nav-tabs .nav-link{ 322 | transition: none; 323 | } 324 | .nav-tabs .nav-link:focus, .nav-tabs .nav-link:hover { 325 | border-color: transparent transparent transparent !important; 326 | } 327 | .nav-tabs .nav-link:focus-visible, 328 | .nav-tabs .nav-link:focus, .nav-tabs .nav-link:hover, .nav-tabs .nav-item.open .nav-link:focus, .nav-tabs .nav-item.open .nav-link:hover, .nav-pills .nav-link:focus, .nav-pills .nav-link:hover, .nav-pills .nav-item.open .nav-link:focus, .nav-pills .nav-item.open .nav-link:hover , 329 | .nav-tabs .nav-link.active:focus, .nav-tabs .nav-link.active:hover, .nav-tabs .nav-item.open .nav-link:focus, .nav-tabs .nav-item.open .nav-link:hover, .nav-pills .nav-link.active:focus, .nav-pills .nav-link.active:hover, .nav-pills .nav-item.open .nav-link:focus, .nav-pills .nav-item.open .nav-link:hover { 330 | color: lightgray; 331 | } 332 | .inherit-size{ 333 | min-width: inherit; 334 | min-height: inherit; 335 | } 336 | .header-account-roles { 337 | position: fixed; 338 | top: 65px; 339 | right: 0; 340 | padding: 5px; 341 | } 342 | #game-root { 343 | position: absolute; 344 | top: 0; 345 | left: 0; 346 | min-width: 100vw; 347 | min-height: 100vh; 348 | } 349 | 350 | #root { 351 | min-height: 100vh; 352 | min-width: 100vw; 353 | position: relative; 354 | z-index: 2; 355 | } 356 | 357 | .pointers-none { 358 | pointer-events: none; 359 | } 360 | 361 | .pointers-active { 362 | pointer-events: initial; 363 | } 364 | 365 | #game-canvas { 366 | margin: 0 !important; 367 | } 368 | 369 | .app-container { 370 | 371 | } 372 | 373 | .center-extended { 374 | display: flex; 375 | justify-content: center; 376 | align-items: center; 377 | text-align: center; 378 | // background:black; 379 | min-height: 100vh; 380 | } 381 | 382 | .bottom { 383 | left: 0; 384 | right: 0; 385 | margin-left: auto; 386 | margin-right: auto; 387 | width: 20%; 388 | display: flex; 389 | justify-content: center; 390 | align-items: center; 391 | text-align: center; 392 | padding: 10px; 393 | position: absolute; 394 | bottom: 0; 395 | } 396 | 397 | 398 | .bottom-center { 399 | left: 0; 400 | right: 0; 401 | margin-left: auto; 402 | margin-right: auto; 403 | width: 20%; 404 | display: flex; 405 | justify-content: center; 406 | align-items: center; 407 | text-align: center; 408 | padding: 10px; 409 | position: absolute; 410 | bottom: 0; 411 | margin-bottom: 10%; 412 | } 413 | 414 | .top { 415 | left: 0; 416 | right: 0; 417 | margin-left: auto; 418 | margin-right: auto; 419 | width: 20%; 420 | display: flex; 421 | justify-content: center; 422 | align-items: center; 423 | text-align: center; 424 | padding: 10px; 425 | position: absolute; 426 | top: 0; 427 | } 428 | 429 | .game-form { 430 | padding: 20px; 431 | } 432 | 433 | 434 | .fade-out { 435 | -webkit-animation: fadeOutAnimation 5s linear 5s forwards; 436 | animation: fadeOutAnimation 5s linear 5s forwards; 437 | } 438 | 439 | @-webkit-keyframes fadeOutAnimation { 440 | 0% { 441 | opacity: 1; 442 | } 443 | 100% { 444 | opacity: 0; 445 | } 446 | } 447 | 448 | @keyframes fadeOutAnimation { 449 | 0% { 450 | opacity: 1; 451 | } 452 | 100% { 453 | opacity: 0; 454 | } 455 | } 456 | 457 | .fade-in { 458 | -webkit-animation: fadeInAnimation 2s linear forwards; 459 | animation: fadeInAnimation 2s linear forwards; 460 | } 461 | 462 | @-webkit-keyframes fadeInAnimation { 463 | 0% { 464 | opacity: 0; 465 | } 466 | 100% { 467 | opacity: 1; 468 | } 469 | } 470 | 471 | @keyframes fadeOutAnimation { 472 | 0% { 473 | opacity: 1; 474 | } 475 | 100% { 476 | opacity: 0; 477 | } 478 | } 479 | 480 | 481 | 482 | -------------------------------------------------------------------------------- /src/app.tsx: -------------------------------------------------------------------------------- 1 | import './app.scss'; 2 | import React from 'react'; 3 | import {useAppSelector} from "./hooks"; 4 | import Routes from "./routes"; 5 | import game from "./phaser-game" 6 | import Loader from "./component/loader"; 7 | 8 | window.addEventListener('load', () => { 9 | game.scene.start("bootstrap") 10 | 11 | }) 12 | 13 | export const App = () => { 14 | const page = useAppSelector((state) => state.application.currentPage) 15 | const loading = useAppSelector((state) => state.application.isLoading) 16 | return ( 17 |
18 | {!loading && Routes.get(page)} 19 |
20 | 21 |
22 |
23 | ); 24 | }; 25 | export default App; 26 | -------------------------------------------------------------------------------- /src/component/debug/index.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import 'phaser' 3 | import 'bootstrap/dist/css/bootstrap.min.css'; 4 | import { useGlobalReg } from '../../hooks'; 5 | 6 | const DebugPanel = () => { 7 | const [fps, setFps] = useState(0); 8 | const [version, setVersion] = useState(''); 9 | const [skill, setSkill] = useState(0); 10 | 11 | useGlobalReg({ 12 | setVersion, 13 | setFps, 14 | setSkill 15 | }); 16 | 17 | return ( 18 | <> 19 |
20 | 21 | Fps: {fps} 22 | 23 |

24 | 25 | Version: {version} 26 | 27 |

28 | 29 | Current skill: {skill+1} 30 | 31 |
32 | 33 | ); 34 | }; 35 | 36 | export default DebugPanel; 37 | -------------------------------------------------------------------------------- /src/component/game/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import DebugPanel from "../debug"; 3 | 4 | export const GameContainer = () => { 5 | return ( 6 |
7 | 8 |
9 | ); 10 | }; 11 | 12 | export default GameContainer; 13 | -------------------------------------------------------------------------------- /src/component/loader/index.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import {Progress} from 'reactstrap'; 3 | import { useGlobalReg } from '../../hooks'; 4 | 5 | export const Loader = ({isLoading = false}) => { 6 | const [progress, setProgress] = useState(0) 7 | 8 | useGlobalReg({ 9 | setProgress 10 | }) 11 | 12 | return ( 13 | isLoading ? : <> 14 | ); 15 | }; 16 | 17 | export default Loader; 18 | -------------------------------------------------------------------------------- /src/component/login/index.scss: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | Main page styles 3 | ========================================================================== */ 4 | .hipster { 5 | display: inline-block; 6 | width: 100%; 7 | height: 497px; 8 | background-size: contain; 9 | } 10 | -------------------------------------------------------------------------------- /src/component/login/index.tsx: -------------------------------------------------------------------------------- 1 | import './index.scss'; 2 | import React from 'react'; 3 | import {Button, Card, Form, Input} from 'reactstrap'; 4 | import { useAppDispatch, useGlobalState } from '../../hooks'; 5 | import {setCurrentPage, setLoading, setNickname} from "../../store/application.store"; 6 | import {Page} from "../../config/constants"; 7 | import {launchGame} from "../../phaser-game"; 8 | 9 | export const Login = () => { 10 | const dispatch = useAppDispatch() 11 | 12 | const onStart = (evt) => { 13 | evt.preventDefault() 14 | const data = new FormData(evt.target) 15 | if(!data.get("name")) { 16 | alert("Name is required") 17 | return; 18 | } 19 | useGlobalState(state=>state.setProgress(50)); 20 | dispatch(setLoading(true)) 21 | dispatch(setNickname(data.get("name").toString())) 22 | setTimeout(() => { 23 | dispatch(setLoading(false)) 24 | dispatch(setCurrentPage(Page.GAME)) 25 | launchGame() 26 | }, 3000) 27 | }; 28 | return ( 29 |
30 |
31 | 32 |
33 | 34 | 35 | 36 |
37 |
38 |
39 |
40 | ); 41 | }; 42 | 43 | export default Login; 44 | -------------------------------------------------------------------------------- /src/config/config.ts: -------------------------------------------------------------------------------- 1 | export var GameConfig = { 2 | nbGroundLayers: 4, 3 | cameraFollowing: true, 4 | playerAbsVelocity: 100, 5 | skillAbsVelocity: 300, 6 | skillCollisionDistance: 5, 7 | speechBubbleCornerSize: 5, 8 | charactersPool: {}, 9 | skillsPool: {}, 10 | playerAnims: ['death', 'up', 'upright','rightup', 'right', 'downright','rightdown', 'down', 'downleft', 'leftdown','left', 'upleft', 11 | 'leftup','upattack', 'uprightattack', 'rightupattack','rightattack', 'downrightattack', 'rightdownattack', 'downattack', 'downleftattack', 12 | 'leftdownattack','leftattack', 'upleftattack', 'leftupattack'] 13 | } -------------------------------------------------------------------------------- /src/config/constants.ts: -------------------------------------------------------------------------------- 1 | export const enum Page { 2 | GAME, 3 | LOGIN 4 | } 5 | -------------------------------------------------------------------------------- /src/config/icon-loader.ts: -------------------------------------------------------------------------------- 1 | import { faCogs } from '@fortawesome/free-solid-svg-icons/faCogs'; 2 | import { faBan } from '@fortawesome/free-solid-svg-icons/faBan'; 3 | import { faAsterisk } from '@fortawesome/free-solid-svg-icons/faAsterisk'; 4 | import { faArrowLeft } from '@fortawesome/free-solid-svg-icons/faArrowLeft'; 5 | import { faBell } from '@fortawesome/free-solid-svg-icons/faBell'; 6 | import { faBook } from '@fortawesome/free-solid-svg-icons/faBook'; 7 | import { faCloud } from '@fortawesome/free-solid-svg-icons/faCloud'; 8 | import { faDatabase } from '@fortawesome/free-solid-svg-icons/faDatabase'; 9 | import { faEye } from '@fortawesome/free-solid-svg-icons/faEye'; 10 | import { faFlag } from '@fortawesome/free-solid-svg-icons/faFlag'; 11 | import { faHeart } from '@fortawesome/free-solid-svg-icons/faHeart'; 12 | import { faHome } from '@fortawesome/free-solid-svg-icons/faHome'; 13 | import { faList } from '@fortawesome/free-solid-svg-icons/faList'; 14 | import { faLock } from '@fortawesome/free-solid-svg-icons/faLock'; 15 | import { faPencilAlt } from '@fortawesome/free-solid-svg-icons/faPencilAlt'; 16 | import { faPlus } from '@fortawesome/free-solid-svg-icons/faPlus'; 17 | import { faSave } from '@fortawesome/free-solid-svg-icons/faSave'; 18 | import { faSearch } from '@fortawesome/free-solid-svg-icons/faSearch'; 19 | import { faSort } from '@fortawesome/free-solid-svg-icons/faSort'; 20 | import { faSync } from '@fortawesome/free-solid-svg-icons/faSync'; 21 | import { faRoad } from '@fortawesome/free-solid-svg-icons/faRoad'; 22 | import { faSignInAlt } from '@fortawesome/free-solid-svg-icons/faSignInAlt'; 23 | import { faSignOutAlt } from '@fortawesome/free-solid-svg-icons/faSignOutAlt'; 24 | import { faTachometerAlt } from '@fortawesome/free-solid-svg-icons/faTachometerAlt'; 25 | import { faTasks } from '@fortawesome/free-solid-svg-icons/faTasks'; 26 | import { faThList } from '@fortawesome/free-solid-svg-icons/faThList'; 27 | import { faTimesCircle } from '@fortawesome/free-solid-svg-icons/faTimesCircle'; 28 | import { faTrash } from '@fortawesome/free-solid-svg-icons/faTrash'; 29 | import { faUser } from '@fortawesome/free-solid-svg-icons/faUser'; 30 | import { faUserPlus } from '@fortawesome/free-solid-svg-icons/faUserPlus'; 31 | import { faUsers } from '@fortawesome/free-solid-svg-icons/faUsers'; 32 | import { faUsersCog } from '@fortawesome/free-solid-svg-icons/faUsersCog'; 33 | import { faWrench } from '@fortawesome/free-solid-svg-icons/faWrench'; 34 | import { faFaceAngry } from '@fortawesome/free-solid-svg-icons/faFaceAngry'; 35 | import { faFaceFlushed } from '@fortawesome/free-solid-svg-icons/faFaceFlushed'; 36 | import { faFaceGrimace } from '@fortawesome/free-solid-svg-icons/faFaceGrimace'; 37 | import { faFaceDizzy } from '@fortawesome/free-solid-svg-icons/faFaceDizzy'; 38 | import { faFaceGrinWink } from '@fortawesome/free-solid-svg-icons/faFaceGrinWink'; 39 | import { faMoneyBill1Wave } from '@fortawesome/free-solid-svg-icons/faMoneyBill1Wave'; 40 | import { faMoneyBillWave } from '@fortawesome/free-solid-svg-icons/faMoneyBillWave'; 41 | import { faSackXmark } from '@fortawesome/free-solid-svg-icons/faSackXmark'; 42 | import { faHandHoldingHand } from '@fortawesome/free-solid-svg-icons/faHandHoldingHand'; 43 | import { faDollarSign } from '@fortawesome/free-solid-svg-icons/faDollarSign'; 44 | import { faBriefcase } from '@fortawesome/free-solid-svg-icons/faBriefcase'; 45 | import { faFileExcel } from '@fortawesome/free-solid-svg-icons/faFileExcel'; 46 | import { faFileArrowDown } from '@fortawesome/free-solid-svg-icons/faFileArrowDown'; 47 | import { faFile } from '@fortawesome/free-solid-svg-icons/faFile'; 48 | import { faFilePen } from '@fortawesome/free-solid-svg-icons/faFilePen'; 49 | import { faCoins } from '@fortawesome/free-solid-svg-icons/faCoins'; 50 | import { library } from '@fortawesome/fontawesome-svg-core'; 51 | 52 | export const loadIcons = () => { 53 | library.add( 54 | faFile, 55 | faFilePen, 56 | faCoins, 57 | faFaceGrinWink, 58 | faFileArrowDown, 59 | faFileExcel, 60 | faBriefcase, 61 | faDollarSign, 62 | faHandHoldingHand, 63 | faSackXmark, 64 | faMoneyBillWave, 65 | faMoneyBill1Wave, 66 | faFaceDizzy, 67 | faFaceGrimace, 68 | faFaceFlushed, 69 | faFaceAngry, 70 | faArrowLeft, 71 | faAsterisk, 72 | faBan, 73 | faBell, 74 | faBook, 75 | faCloud, 76 | faCogs, 77 | faDatabase, 78 | faEye, 79 | faFlag, 80 | faHeart, 81 | faHome, 82 | faList, 83 | faLock, 84 | faPencilAlt, 85 | faPlus, 86 | faRoad, 87 | faSave, 88 | faSignInAlt, 89 | faSignOutAlt, 90 | faSearch, 91 | faSort, 92 | faSync, 93 | faTachometerAlt, 94 | faTasks, 95 | faThList, 96 | faTimesCircle, 97 | faTrash, 98 | faUser, 99 | faUserPlus, 100 | faUsers, 101 | faUsersCog, 102 | faWrench 103 | ); 104 | }; 105 | -------------------------------------------------------------------------------- /src/game/actors/items/face.ts: -------------------------------------------------------------------------------- 1 | import Phaser, {Scene} from "phaser"; 2 | 3 | export default class Face extends Phaser.Physics.Arcade.Sprite { 4 | constructor(scene: Scene, x: number, y: number) { 5 | super(scene, x, y, 'face'); 6 | this.scene.physics.add.existing(this) 7 | this.scene.add.existing(this) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/game/actors/players/mage.ts: -------------------------------------------------------------------------------- 1 | import * as Phaser from "phaser" 2 | import Player from "./player"; 3 | import {SkillFactory} from "../skills/skill-factory"; 4 | import Scene = Phaser.Scene; 5 | import Vector2 = Phaser.Math.Vector2; 6 | import { useGlobalState } from '../../../hooks'; 7 | 8 | export default class Mage extends Player { 9 | private skillFactory: SkillFactory = new SkillFactory(); 10 | private skills = ["Fireball", "Buff"] 11 | private currentSkillIndex = 0 12 | 13 | constructor(scene: Scene, x: number, y: number, name:string) { 14 | super(scene, x, y, "mage", name); 15 | } 16 | 17 | public setSkillIndex(index: number) { 18 | if (index === undefined || index < 0 || index > 1) 19 | return 20 | useGlobalState(state=>state.setSkill(index)) 21 | this.currentSkillIndex = index 22 | } 23 | 24 | override attack(target: Vector2) { 25 | this.skillFactory.create(this.scene, this.x, this.y, target, this.skills[this.currentSkillIndex]) 26 | super.attack(target) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/game/actors/players/player.ts: -------------------------------------------------------------------------------- 1 | import * as Phaser from "phaser" 2 | import {Direction} from "../../../shared/Direction"; 3 | import {GameConfig} from "../../../config/config"; 4 | import Scene = Phaser.Scene; 5 | import Vector2 = Phaser.Math.Vector2; 6 | 7 | export default abstract class Player extends Phaser.Physics.Arcade.Sprite { 8 | private animationKey: string; 9 | private attackAnimationKey: string; 10 | public isMoving: boolean; 11 | public isAttack: boolean; 12 | public name: string; 13 | public target: Vector2; 14 | private nameHolder: Phaser.GameObjects.Text; 15 | private directionState: Map = new Map([ 16 | [Direction.RIGHT, false], 17 | [Direction.UP, false], 18 | [Direction.DOWN, false], 19 | [Direction.LEFT, false] 20 | ]); 21 | private directionVerticalVelocity: Map = new Map([ 22 | [Direction.UP, -GameConfig.playerAbsVelocity], 23 | [Direction.DOWN, GameConfig.playerAbsVelocity] 24 | ]) 25 | private directionHorizontalVelocity: Map = new Map([ 26 | [Direction.RIGHT, GameConfig.playerAbsVelocity], 27 | [Direction.LEFT, -GameConfig.playerAbsVelocity] 28 | ]) 29 | 30 | protected constructor(scene: Scene, x: number, y: number, textureKey: string, name: string) { 31 | super(scene, x, y, textureKey); 32 | this.name = name; 33 | this.init(); 34 | } 35 | 36 | private init() { 37 | this.isMoving = false; 38 | this.isAttack = false; 39 | this.animationKey = Direction.UP; 40 | this.scene.physics.add.existing(this) 41 | this.scene.add.existing(this); 42 | 43 | this.nameHolder = this.scene.add.text(0, 0, this.name, { 44 | font: '14px pixel', 45 | stroke: "#ffffff", 46 | strokeThickness: 2 47 | }).setOrigin(0.5); 48 | } 49 | 50 | attack(target: Vector2) { 51 | this.isAttack = true 52 | this.target = target 53 | this.attackAnimationKey = `${this.animationKey}attack` 54 | 55 | this.play(this.attackAnimationKey); 56 | this.on(Phaser.Animations.Events.ANIMATION_COMPLETE, () => { 57 | this.isAttack = false; 58 | this.handleMovingAnimation() 59 | }, this); 60 | } 61 | 62 | walk(direction: Direction, state: boolean) { 63 | if (this.directionState.get(direction) === state) 64 | return; 65 | 66 | this.directionState.set(direction, state) 67 | const vec = [0, 0] 68 | const activeState = Array.from(this.directionState.entries()) 69 | .filter(value => value[1]) 70 | .map(value => { 71 | if (this.directionVerticalVelocity.has(value[0])) { 72 | vec[1] = this.directionVerticalVelocity.get(value[0]) 73 | } else if (this.directionHorizontalVelocity.has(value[0])) 74 | vec[0] = this.directionHorizontalVelocity.get(value[0]) 75 | return value[0] 76 | }) 77 | this.isMoving = activeState.length > 0 78 | 79 | if (activeState.length === 1) 80 | this.animationKey = activeState[0] 81 | else if (activeState.length === 2) 82 | this.animationKey = activeState[1] + activeState[0] 83 | 84 | this.setVelocity(vec[0], vec[1]) 85 | 86 | this.handleMovingAnimation() 87 | } 88 | 89 | private handleMovingAnimation() { 90 | if (this.isAttack) 91 | return; 92 | if (this.isMoving) 93 | this.play(this.animationKey); 94 | else { 95 | this.play(this.animationKey); 96 | this.stop() 97 | } 98 | } 99 | 100 | override preUpdate(time, delta): void { 101 | super.preUpdate(time, delta); 102 | this.nameHolder.setPosition(this.x, this.y - 30); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/game/actors/skills/buff.ts: -------------------------------------------------------------------------------- 1 | import {Skill} from "./skill"; 2 | import Phaser from "phaser"; 3 | import Vector2 = Phaser.Math.Vector2; 4 | 5 | export class Buff extends Skill { 6 | constructor(scene: Phaser.Scene, x: number, y: number, target: Vector2) { 7 | super(scene, x, y, "buff", target); 8 | } 9 | 10 | override playFinalAnimation() { 11 | this.play("buff"); 12 | } 13 | 14 | override init(): void { 15 | this.setPosition(this.initialPosition.x, this.initialPosition.y) 16 | } 17 | } -------------------------------------------------------------------------------- /src/game/actors/skills/fireball.ts: -------------------------------------------------------------------------------- 1 | import {Skill} from "./skill"; 2 | import Vector2 = Phaser.Math.Vector2; 3 | 4 | export class Fireball extends Skill { 5 | constructor(scene: Phaser.Scene, x: number, y: number, target: Vector2) { 6 | super(scene, x, y, "fireball", target); 7 | } 8 | 9 | override init() { 10 | super.init(); 11 | this.setScale(0.02, 0.02); 12 | } 13 | 14 | override playFinalAnimation() { 15 | this.play("fireballBlast"); 16 | this.setScale(1, 1) 17 | } 18 | } -------------------------------------------------------------------------------- /src/game/actors/skills/skill-factory.ts: -------------------------------------------------------------------------------- 1 | import {Fireball} from "./fireball"; 2 | import {Skill} from "./skill"; 3 | import {Buff} from "./buff"; 4 | import Vector2 = Phaser.Math.Vector2; 5 | 6 | export class SkillFactory { 7 | 8 | create(scene: Phaser.Scene, x: number, y: number, target: Vector2, key: string): Skill { 9 | switch (key) { 10 | case "Fireball": 11 | return new Fireball(scene, x, y, target); 12 | case "Buff": 13 | return new Buff(scene, x, y, new Vector2(x, y)) 14 | default: 15 | return null; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/game/actors/skills/skill.ts: -------------------------------------------------------------------------------- 1 | import * as Phaser from "phaser" 2 | import Player from "../players/player"; 3 | import Vector2 = Phaser.Math.Vector2; 4 | import {GameConfig} from "../../../config/config"; 5 | import destroy = Phaser.Loader.FileTypesManager.destroy; 6 | 7 | export abstract class Skill extends Phaser.Physics.Arcade.Sprite { 8 | protected target: Vector2; 9 | protected initialPosition: Vector2; 10 | 11 | private finallyAnimated = false; 12 | 13 | protected constructor(scene: Phaser.Scene, x: number, y: number, image: string, target: Vector2) { 14 | super(scene, x, y, image, 0); 15 | this.scene.add.existing(this); 16 | this.scene.physics.add.existing(this) 17 | this.target = target; 18 | this.initialPosition = new Vector2(x, y) 19 | this.init() 20 | } 21 | 22 | protected preUpdate(time: number, delta: number) { 23 | super.preUpdate(time, delta); 24 | if (!this.finallyAnimated && new Vector2(this.x, this.y).distance(this.target) < GameConfig.skillCollisionDistance) { 25 | this.finallyAnimated = true 26 | this.setVelocity(0, 0) 27 | this.animateFinally().then(sprite => this.destroy(true)) 28 | .catch(e => this.destroy(true)) 29 | } 30 | } 31 | 32 | protected abstract playFinalAnimation(): void 33 | 34 | animateFinally(): Promise { 35 | return new Promise((resolve, reject) => { 36 | try { 37 | this.on(Phaser.Animations.Events.ANIMATION_COMPLETE, (animation: Phaser.Animations.Animation) => { 38 | try { 39 | resolve(this) 40 | } catch (e) { 41 | reject(e) 42 | } 43 | }, this); 44 | this.playFinalAnimation() 45 | } catch (e) { 46 | reject(e) 47 | } 48 | }) 49 | } 50 | 51 | init(): void { 52 | const vel = new Vector2(this.target.x - this.initialPosition.x, this.target.y - this.initialPosition.y).normalize() 53 | this.setPosition(this.initialPosition.x, this.initialPosition.y) 54 | this.setVelocity(vel.x * GameConfig.skillAbsVelocity, vel.y * GameConfig.skillAbsVelocity) 55 | } 56 | } -------------------------------------------------------------------------------- /src/game/scenes/bootstrap.scene.ts: -------------------------------------------------------------------------------- 1 | import store from '../../store'; 2 | import { setLoading } from '../../store/application.store'; 3 | import { useGlobalState } from '../../hooks'; 4 | 5 | export default class BootstrapScene extends Phaser.Scene { 6 | constructor() { 7 | super('bootstrap'); 8 | } 9 | 10 | init() { 11 | store.dispatch(setLoading(true)); 12 | } 13 | 14 | preload() { 15 | this.load.on(Phaser.Loader.Events.PROGRESS, (value: number) => { 16 | useGlobalState(state => state.setProgress(100 * value)); 17 | }); 18 | this.load.tilemapTiledJSON('worldmap', './assets/maps/new/map01merged.json'); 19 | this.load.image('tiles', './assets/maps/new/tiles.png'); 20 | this.load.atlas('mage', './assets/playersheets/mage.png', './assets/playersheets/mage.json'); 21 | this.load.image('fireball', './assets/skillsheets/fire_002.png'); 22 | this.load.spritesheet('buff', './assets/skillsheets/cast_001.png', { frameWidth: 192, frameHeight: 192 }); 23 | this.load.image('face', './assets/images/face.png'); 24 | this.load.spritesheet('fireballBlast', './assets/skillsheets/s001.png', { frameWidth: 192, frameHeight: 192 }); 25 | this.load.audio('intro', ['./assets/music/phaser-quest-intro.ogg']); 26 | this.load.glsl('fireball_shader', './assets/shaders/fireball_shader.frag'); 27 | } 28 | 29 | create() { 30 | useGlobalState(state => state.setProgress(100)); 31 | store.dispatch(setLoading(false)); 32 | 33 | this.sound.add('intro').play({ 34 | seek: 2.55, 35 | }); 36 | 37 | this.add.shader('fireball_shader', window.innerWidth / 2, window.innerHeight / 2, window.innerWidth, window.innerHeight); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/game/scenes/game.scene.ts: -------------------------------------------------------------------------------- 1 | import Mage from '../actors/players/mage'; 2 | import { GameConfig } from '../../config/config'; 3 | import { Direction } from '../../shared/Direction'; 4 | import store from '../../store'; 5 | import Face from '../actors/items/face'; 6 | import Vector2 = Phaser.Math.Vector2; 7 | import { useGlobalState } from '../../hooks'; 8 | 9 | export default class GameScene extends Phaser.Scene { 10 | private map: Phaser.Tilemaps.Tilemap; 11 | private player: Mage; 12 | private skillIndexMap = { '1': 0, '2': 1 }; 13 | private keymap: any = { 14 | 'd': Direction.RIGHT, 15 | 's': Direction.DOWN, 16 | 'a': Direction.LEFT, 17 | 'w': Direction.UP, 18 | 'в': Direction.RIGHT, 19 | 'ы': Direction.DOWN, 20 | 'ф': Direction.LEFT, 21 | 'ц': Direction.UP 22 | }; 23 | 24 | constructor() { 25 | super('game'); 26 | } 27 | 28 | create() { 29 | useGlobalState(state => state.setVersion(`Phaser v${Phaser.VERSION}`)) 30 | 31 | this.input.keyboard.on(Phaser.Input.Keyboard.Events.ANY_KEY_DOWN, (evt: { key: string; }) => { 32 | this.player.setSkillIndex(this.skillIndexMap[evt.key]); 33 | const direction = this.keymap[evt.key]; 34 | if (direction) 35 | this.player.walk(direction, true); 36 | }); 37 | this.input.keyboard.on(Phaser.Input.Keyboard.Events.ANY_KEY_UP, (evt: { key: string; }) => { 38 | const direction = this.keymap[evt.key]; 39 | if (direction) 40 | this.player.walk(direction, false); 41 | }); 42 | 43 | this.input.on(Phaser.Input.Events.POINTER_DOWN, (evt: { 44 | worldX: number; 45 | worldY: number; 46 | }) => this.player.attack(new Vector2(evt.worldX, evt.worldY))); 47 | 48 | this.createAnimations(); 49 | this.displayMap(); 50 | this.createPlayer(); 51 | this.cameras.main.startFollow(this.player); 52 | 53 | // examples 54 | 55 | // Animation/Sprite 56 | this.anims.create({ 57 | key: 'explosion', 58 | frames: this.anims.generateFrameNumbers('fireballBlast', { start: 0, end: 19, first: 0 }), 59 | frameRate: 20, 60 | repeat: -1 61 | }); 62 | 63 | this.add.sprite(2500, 1100, '').play('explosion'); 64 | 65 | // Arcade Physics / collision 66 | 67 | const items = this.add.group([this.createItem()]); 68 | this.physics.add.collider(this.player, items, (object1, object2) => { 69 | object2.destroy(true); 70 | setTimeout(() => { 71 | items.add(this.createItem(), true); 72 | }, 3000); 73 | }); 74 | 75 | } 76 | 77 | createItem(): Face { 78 | return new Face(this, 2500, 1100); 79 | } 80 | 81 | createPlayer(): Mage { 82 | return this.player = new Mage(this, 2100, 1000, store.getState().application.nickname); 83 | } 84 | 85 | createAnimations() { 86 | GameConfig.playerAnims.map((key) => ({ 87 | key, 88 | frames: this.anims.generateFrameNames('mage', { 89 | prefix: key, 90 | start: 0, 91 | end: 4 92 | }), 93 | frameRate: 8, 94 | repeat: !key.includes('attack') && !key.includes('death') ? -1 : 0 95 | })).concat([ 96 | { 97 | key: 'fireballBlast', 98 | frames: this.anims.generateFrameNumbers('fireballBlast', { start: 0, end: 19, first: 0 }), 99 | frameRate: 20, 100 | repeat: 0 101 | }, 102 | { 103 | key: 'buff', 104 | frames: this.anims.generateFrameNumbers('buff', { start: 0, end: 19, first: 0 }), 105 | frameRate: 20, 106 | repeat: 0 107 | } 108 | ]).forEach((config) => this.anims.create(config)); 109 | } 110 | 111 | displayMap() { 112 | this.map = this.add.tilemap('worldmap'); 113 | const tileset = this.map.addTilesetImage('tiles', 'tiles'); 114 | for (let i = 0; i < this.map.layers.length; i++) 115 | this.map.createLayer(0, tileset, 0, 0).setVisible(true); 116 | } 117 | 118 | update(time, delta) { 119 | useGlobalState(state => state.setFps(Math.trunc(this.sys.game.loop.actualFps))); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/hooks.ts: -------------------------------------------------------------------------------- 1 | import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; 2 | import type { RootState, AppDispatch } from './store'; 3 | import { Dispatch, SetStateAction } from 'react'; 4 | 5 | function GLOBAL_STATE_TYPE() {} 6 | 7 | const GLOBAL_STATE = new GLOBAL_STATE_TYPE(); 8 | 9 | export const useGlobalState = function (operation: (state: any) => void) { 10 | try { 11 | operation(GLOBAL_STATE); 12 | } catch (e) { 13 | console.log('Global state function run failed'); 14 | } 15 | }; 16 | 17 | export function useGlobalReg>>>(state: S): void { 18 | for (const [key, setStateAction] of Object.entries(state)) 19 | GLOBAL_STATE_TYPE.prototype[key] = setStateAction; 20 | } 21 | 22 | export const useAppDispatch = () => useDispatch(); 23 | export const useAppSelector: TypedUseSelectorHook = useSelector; 24 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Game 7 | 8 | 9 | 10 | 11 | 12 | 13 | 19 |
20 |
21 | 22 |
23 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import {Provider} from 'react-redux'; 4 | import store from './store'; 5 | import {loadIcons} from './config/icon-loader'; 6 | import App from "./app"; 7 | import "./hooks"; 8 | 9 | loadIcons(); 10 | 11 | const rootEl = document.getElementById('root'); 12 | 13 | const render = Component => 14 | // eslint-disable-next-line react/no-render-return-value 15 | ReactDOM.render( 16 | 17 | 18 | {/* */} 19 | 20 | {/* */} 21 | 22 | , 23 | rootEl 24 | ); 25 | 26 | render(App); 27 | -------------------------------------------------------------------------------- /src/net/game-web-socket.ts: -------------------------------------------------------------------------------- 1 | export type OnMessageHandler = (eventData: any) => void; 2 | 3 | export const MessageType = { 4 | UPDATE: 1, 5 | PLAYER_MOUSE_MOVE: 2, 6 | }; 7 | 8 | export default class GameWebSocket { 9 | private socket: any; 10 | private events: Map = new Map(); 11 | 12 | constructor() { 13 | if (!window.WebSocket) { 14 | // @ts-ignore 15 | window.WebSocket = window.MozWebSocket; 16 | } 17 | } 18 | 19 | public connect(host: string): Promise { 20 | return new Promise((resolve, reject) => { 21 | if (window.WebSocket) { 22 | this.socket = new WebSocket(`ws://${host}/websocket`); 23 | } else { 24 | reject('Your browser does not support Web Socket.'); 25 | } 26 | 27 | this.socket.addEventListener('open', event => { 28 | resolve('Connection established'); 29 | }); 30 | 31 | this.socket.addEventListener('error', event => { 32 | reject(event.message); 33 | }); 34 | 35 | this.socket.addEventListener('close', event => { 36 | reject('Web Socket closed'); 37 | }); 38 | 39 | this.socket.addEventListener('message', evt => { 40 | const eventData = JSON.parse(evt.data); 41 | if (this.events.has(eventData.type)) { 42 | const arr = this.events.get(eventData.type); 43 | arr[1].call(!arr[0] ? arr[0] : this, eventData.data); 44 | } 45 | }); 46 | }); 47 | } 48 | 49 | public on(type: number, handler: OnMessageHandler, thisArg: any = null) { 50 | this.events.set(type, [thisArg, handler]); 51 | } 52 | 53 | public send(type: number, data: any = null) { 54 | if (this.socket.readyState !== WebSocket.OPEN) { 55 | console.log('Socket is not ready'); 56 | return; 57 | } 58 | 59 | this.socket.send(this.createEvent(type, data)); 60 | } 61 | 62 | private createEvent = (eventType: number, payload: any = null) => { 63 | const obj: any = { 64 | type: eventType, 65 | data: null, 66 | }; 67 | if (payload) obj.data = payload; 68 | return JSON.stringify(obj); 69 | }; 70 | } 71 | 72 | export const webSocket = new GameWebSocket(); 73 | -------------------------------------------------------------------------------- /src/phaser-game.ts: -------------------------------------------------------------------------------- 1 | import Phaser from 'phaser' 2 | import BootstrapScene from "./game/scenes/bootstrap.scene"; 3 | import GameScene from "./game/scenes/game.scene"; 4 | 5 | const config = { 6 | type: Phaser.WEBGL, 7 | parent: 'game-root', 8 | canvas: document.getElementById('game-canvas') as HTMLCanvasElement, 9 | width: window.innerWidth , 10 | height: window.innerHeight, 11 | pixelArt: true, 12 | scene: [BootstrapScene, GameScene], 13 | physics: { 14 | default: 'arcade', 15 | arcade: { 16 | debug: false 17 | } 18 | } 19 | } 20 | 21 | 22 | const phaserGame = new Phaser.Game(config) 23 | 24 | ;(window as any).game = phaserGame 25 | 26 | export const launchGame = () => { 27 | document.getElementById("root").style.pointerEvents="none" 28 | phaserGame.scene.start('game') 29 | } 30 | 31 | 32 | export default phaserGame 33 | -------------------------------------------------------------------------------- /src/routes.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Game from "./component/game"; 3 | import {Page} from "./config/constants"; 4 | import Login from "./component/login"; 5 | 6 | const Routes: Map = new Map(); 7 | Routes.set(Page.GAME, ) 8 | Routes.set(Page.LOGIN, ) 9 | export default Routes 10 | -------------------------------------------------------------------------------- /src/shared/Direction.ts: -------------------------------------------------------------------------------- 1 | export enum Direction { 2 | UP = "up", 3 | RIGHT = "right", 4 | DOWN = "down", 5 | LEFT = "left" 6 | } 7 | 8 | export function valueOf(str: string) { 9 | return Direction[str.toUpperCase() as keyof typeof Direction] 10 | } -------------------------------------------------------------------------------- /src/store/application.store.ts: -------------------------------------------------------------------------------- 1 | import {createSlice, PayloadAction} from '@reduxjs/toolkit' 2 | import {Page} from "../config/constants"; 3 | 4 | const applicationSlice = createSlice({ 5 | name: 'application', 6 | initialState: { 7 | currentPage: Page.LOGIN, 8 | isLoading: false, 9 | nickname: '', 10 | }, 11 | reducers: { 12 | setCurrentPage(state, action: PayloadAction) { 13 | state.currentPage = action.payload 14 | }, 15 | setLoading(state, action: PayloadAction) { 16 | state.isLoading = action.payload 17 | }, 18 | setNickname(state, action: PayloadAction) { 19 | state.nickname = action.payload 20 | }, 21 | }, 22 | }) 23 | 24 | export const { 25 | setCurrentPage, 26 | setLoading, 27 | setNickname 28 | } = applicationSlice.actions 29 | 30 | export default applicationSlice.reducer 31 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import {configureStore} from '@reduxjs/toolkit' 2 | import applicationReducer from './application.store' 3 | 4 | const store = configureStore({ 5 | reducer: { 6 | application: applicationReducer, 7 | } 8 | }) 9 | 10 | export type RootState = ReturnType 11 | export type AppDispatch = typeof store.dispatch 12 | export default store 13 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | export const checkExists = data => data !== null && data !== undefined; 2 | export const getRandomNum = (min, max) => { 3 | return Math.random() * (max - min) + min; 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react", 4 | "target": "es6", 5 | "module": "es2020", 6 | "moduleResolution": "node", 7 | "sourceMap": true, 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "removeComments": false, 11 | "noImplicitAny": false, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "lib": ["dom", "es2015", "es2016", "es2017", "es2019", "es2020"], 14 | "types": ["webpack-env"], 15 | "allowJs": true, 16 | "checkJs": false, 17 | "baseUrl": "./", 18 | "importHelpers": true, 19 | "esModuleInterop": true, 20 | "allowSyntheticDefaultImports": true 21 | }, 22 | "include": ["src/**/*"], 23 | "exclude": ["node_modules", "**/*.spec.ts", "/assets"] 24 | } 25 | -------------------------------------------------------------------------------- /webpack/environment.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // The root URL for API calls, ending with a '/' - for example: `"https://www.aaaa:8081/myservice/"`. 3 | // If this URL is left empty (""), then it will be relative to the current context. 4 | // If you use an API server, in `prod` mode, you will need to enable CORS 5 | SERVER_API_URL: '' 6 | }; 7 | -------------------------------------------------------------------------------- /webpack/utils.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const tsconfig = require('../tsconfig.json'); 4 | 5 | module.exports = { 6 | root, 7 | mapTypescriptAliasToWebpackAlias, 8 | }; 9 | 10 | const _root = path.resolve(__dirname, '..'); 11 | 12 | function root(args) { 13 | args = Array.prototype.slice.call(arguments, 0); 14 | return path.join.apply(path, [_root].concat(args)); 15 | } 16 | 17 | function mapTypescriptAliasToWebpackAlias(alias = {}) { 18 | const webpackAliases = { ...alias }; 19 | if (!tsconfig.compilerOptions.paths) { 20 | return webpackAliases; 21 | } 22 | Object.entries(tsconfig.compilerOptions.paths) 23 | .filter(([key, value]) => { 24 | // use Typescript alias in Webpack only if this has value 25 | return !!value.length; 26 | }) 27 | .map(([key, value]) => { 28 | // if Typescript alias ends with /* then remove this for Webpack 29 | const regexToReplace = /\/\*$/; 30 | const aliasKey = key.replace(regexToReplace, ''); 31 | const aliasValue = value[0].replace(regexToReplace, ''); 32 | return [aliasKey, root(aliasValue)]; 33 | }) 34 | .reduce((aliases, [key, value]) => { 35 | aliases[key] = value; 36 | return aliases; 37 | }, webpackAliases); 38 | return webpackAliases; 39 | } 40 | -------------------------------------------------------------------------------- /webpack/webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const {merge} = require('webpack-merge'); 4 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 5 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 6 | const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); 7 | const ESLintPlugin = require('eslint-webpack-plugin'); 8 | const utils = require('./utils.js'); 9 | const environment = require('./environment'); 10 | 11 | const getTsLoaderRule = env => { 12 | const rules = [ 13 | { 14 | loader: 'thread-loader', 15 | options: { 16 | // There should be 1 cpu for the fork-ts-checker-webpack-plugin. 17 | // The value may need to be adjusted (e.g. to 1) in some CI environments, 18 | // as cpus() may report more cores than what are available to the build. 19 | workers: require('os').cpus().length - 1, 20 | }, 21 | }, 22 | { 23 | loader: 'ts-loader', 24 | options: { 25 | transpileOnly: true, 26 | happyPackMode: true, 27 | }, 28 | }, 29 | ]; 30 | return rules; 31 | }; 32 | 33 | module.exports = async options => { 34 | const development = options.env === 'development'; 35 | return merge( 36 | { 37 | cache: { 38 | // 1. Set cache type to filesystem 39 | type: 'filesystem', 40 | cacheDirectory: path.resolve(__dirname, '../build/webpack'), 41 | buildDependencies: { 42 | // 2. Add your config as buildDependency to get cache invalidation on config change 43 | config: [ 44 | __filename, 45 | path.resolve(__dirname, `webpack.${development ? 'dev' : 'prod'}.js`), 46 | path.resolve(__dirname, 'environment.js'), 47 | path.resolve(__dirname, 'utils.js'), 48 | path.resolve(__dirname, '../postcss.config.js'), 49 | path.resolve(__dirname, '../tsconfig.json'), 50 | ], 51 | }, 52 | }, 53 | resolve: { 54 | extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'], 55 | modules: ['node_modules'], 56 | alias: utils.mapTypescriptAliasToWebpackAlias(), 57 | fallback: { 58 | path: require.resolve('path'), 59 | }, 60 | }, 61 | module: { 62 | rules: [ 63 | { 64 | test: /\.tsx?$/, 65 | use: getTsLoaderRule(options.env), 66 | include: [utils.root('./src')], 67 | exclude: [utils.root('node_modules')], 68 | }, 69 | { 70 | enforce: 'pre', 71 | test: /\.jsx?$/, 72 | loader: 'source-map-loader' 73 | } 74 | ], 75 | }, 76 | stats: { 77 | children: false, 78 | }, 79 | plugins: [ 80 | new webpack.EnvironmentPlugin({ 81 | LOG_LEVEL: development ? 'info' : 'error', 82 | }), 83 | new webpack.DefinePlugin({ 84 | DEVELOPMENT: JSON.stringify(development), 85 | SERVER_API_URL: JSON.stringify(environment.SERVER_API_URL) 86 | }), 87 | new ESLintPlugin({ 88 | extensions: ['js', 'ts', 'jsx', 'tsx'], 89 | }), 90 | new ForkTsCheckerWebpackPlugin(), 91 | new CopyWebpackPlugin({ 92 | patterns: [ 93 | {from: './assets/', to: 'assets/' } 94 | ], 95 | }), 96 | new HtmlWebpackPlugin({ 97 | template: './src/index.html', 98 | chunksSortMode: 'auto', 99 | inject: 'body', 100 | }), 101 | ], 102 | } 103 | ); 104 | }; 105 | -------------------------------------------------------------------------------- /webpack/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const webpackMerge = require('webpack-merge').merge; 3 | const BrowserSyncPlugin = require('browser-sync-webpack-plugin'); 4 | const SimpleProgressWebpackPlugin = require('simple-progress-webpack-plugin'); 5 | const WebpackNotifierPlugin = require('webpack-notifier'); 6 | const sass = require('sass'); 7 | 8 | const utils = require('./utils.js'); 9 | const commonConfig = require('./webpack.common.js'); 10 | 11 | const ENV = 'development'; 12 | 13 | module.exports = async options => 14 | webpackMerge(await commonConfig({env: ENV}), { 15 | devtool: 'cheap-module-source-map', // https://reactjs.org/docs/cross-origin-errors.html 16 | mode: ENV, 17 | entry: ['./src/index'], 18 | output: { 19 | path: utils.root('build/bundle/'), 20 | filename: '[name].[contenthash:8].js', 21 | chunkFilename: '[name].[chunkhash:8].chunk.js', 22 | }, 23 | optimization: { 24 | moduleIds: 'named', 25 | }, 26 | module: { 27 | rules: [ 28 | { 29 | test: /\.(sa|sc|c)ss$/, 30 | use: [ 31 | 'style-loader', 32 | 'css-loader', 33 | { 34 | loader: 'postcss-loader', 35 | }, 36 | { 37 | loader: 'sass-loader', 38 | options: {implementation: sass}, 39 | }, 40 | ], 41 | }, 42 | ], 43 | }, 44 | devServer: { 45 | hot: true, 46 | static: { 47 | directory: './build/bundle/', 48 | }, 49 | port: 9060, 50 | proxy: [ 51 | { 52 | context: ['/api', '/services', '/management', '/v3/api-docs', '/h2-console', '/oauth2', '/login', '/auth'], 53 | target: `http${options.tls ? 's' : ''}://localhost:8080`, 54 | secure: false, 55 | changeOrigin: options.tls, 56 | }, 57 | ], 58 | https: options.tls, 59 | historyApiFallback: true, 60 | }, 61 | stats: options.stats, 62 | plugins: [ 63 | new SimpleProgressWebpackPlugin({ 64 | format: options.stats === 'minimal' ? 'compact' : 'expanded', 65 | }), 66 | new BrowserSyncPlugin( 67 | { 68 | https: options.tls, 69 | host: 'localhost', 70 | port: 9000, 71 | proxy: { 72 | target: `http${options.tls ? 's' : ''}://localhost:${options.watch ? '8080' : '9060'}`, 73 | ws: true, 74 | proxyOptions: { 75 | changeOrigin: false, //pass the Host header to the backend unchanged https://github.com/Browsersync/browser-sync/issues/430 76 | }, 77 | }, 78 | socket: { 79 | clients: { 80 | heartbeatTimeout: 60000, 81 | }, 82 | }, 83 | /* 84 | ,ghostMode: { // uncomment this part to disable BrowserSync ghostMode; https://github.com/jhipster/generator-jhipster/issues/11116 85 | clicks: false, 86 | location: false, 87 | forms: false, 88 | scroll: false 89 | } */ 90 | }, 91 | { 92 | reload: false, 93 | } 94 | ), 95 | new WebpackNotifierPlugin({ 96 | title: 'Phaser 3 game', 97 | }), 98 | ].filter(Boolean), 99 | }); 100 | -------------------------------------------------------------------------------- /webpack/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const webpackMerge = require('webpack-merge').merge; 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | const WorkboxPlugin = require('workbox-webpack-plugin'); 5 | const TerserPlugin = require('terser-webpack-plugin'); 6 | const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); 7 | const FileManagerPlugin = require('filemanager-webpack-plugin'); 8 | const sass = require('sass'); 9 | 10 | const utils = require('./utils.js'); 11 | const commonConfig = require('./webpack.common.js'); 12 | 13 | const ENV = 'production'; 14 | 15 | module.exports = async () => 16 | webpackMerge(await commonConfig({ env: ENV }), { 17 | // devtool: 'source-map', // Enable source maps. Please note that this will slow down the build 18 | mode: ENV, 19 | entry: { 20 | main: './src/index', 21 | }, 22 | output: { 23 | path: utils.root('build/bundle/'), 24 | filename: '[name].[contenthash:8].js', 25 | chunkFilename: '[name].[chunkhash:8].chunk.js', 26 | }, 27 | module: { 28 | rules: [ 29 | { 30 | test: /\.(sa|sc|c)ss$/, 31 | use: [ 32 | { 33 | loader: MiniCssExtractPlugin.loader, 34 | options: { 35 | publicPath: '../', 36 | }, 37 | }, 38 | 'css-loader', 39 | { 40 | loader: 'postcss-loader', 41 | }, 42 | { 43 | loader: 'sass-loader', 44 | options: { implementation: sass }, 45 | }, 46 | ], 47 | }, 48 | ], 49 | }, 50 | optimization: { 51 | runtimeChunk: false, 52 | minimizer: [ 53 | new TerserPlugin({ 54 | terserOptions: { 55 | parse: { 56 | // We want terser to parse ecma 8 code. However, we don't want it 57 | // to apply any minification steps that turns valid ecma 5 code 58 | // into invalid ecma 5 code. This is why the 'compress' and 'output' 59 | // sections only apply transformations that are ecma 5 safe 60 | // https://github.com/facebook/create-react-app/pull/4234 61 | ecma: 8, 62 | }, 63 | compress: { 64 | ecma: 5, 65 | warnings: false, 66 | // Disabled because of an issue with Uglify breaking seemingly valid code: 67 | // https://github.com/facebook/create-react-app/issues/2376 68 | // Pending further investigation: 69 | // https://github.com/mishoo/UglifyJS2/issues/2011 70 | comparisons: false, 71 | // Disabled because of an issue with Terser breaking valid code: 72 | // https://github.com/facebook/create-react-app/issues/5250 73 | // Pending further investigation: 74 | // https://github.com/terser-js/terser/issues/120 75 | inline: 2, 76 | }, 77 | mangle: { 78 | safari10: true, 79 | }, 80 | output: { 81 | ecma: 5, 82 | comments: false, 83 | // Turned on because emoji and regex is not minified properly using default 84 | // https://github.com/facebook/create-react-app/issues/2488 85 | ascii_only: true, 86 | }, 87 | }, 88 | }), 89 | new CssMinimizerPlugin({ 90 | parallel: true, 91 | }), 92 | ], 93 | }, 94 | plugins: [ 95 | new MiniCssExtractPlugin({ 96 | // Options similar to the same options in webpackOptions.output 97 | filename: 'content/[name].[contenthash].css', 98 | chunkFilename: 'content/[name].[chunkhash].css', 99 | }), 100 | new webpack.LoaderOptionsPlugin({ 101 | minimize: true, 102 | debug: false, 103 | }), 104 | ], 105 | }); 106 | --------------------------------------------------------------------------------