├── .eslintrc.js
├── .gitattributes
├── .github
└── workflows
│ └── deploy-to-gh-pages.yml
├── .gitignore
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── scripts
├── copy-tileset-data.js
├── generate-atlas-files.js
├── generate-bitmap-font.js
└── tests.js
├── source_files
├── enemy.psd
└── game_sample.gif
└── src
├── Game.jsx
├── assets
├── atlases
│ ├── generated
│ │ ├── coin.json
│ │ ├── coin.png
│ │ ├── enemy.json
│ │ ├── enemy.png
│ │ ├── hero.json
│ │ ├── hero.png
│ │ ├── npc_01.json
│ │ ├── npc_01.png
│ │ ├── npc_02.json
│ │ ├── npc_02.png
│ │ ├── npc_03.json
│ │ ├── npc_03.png
│ │ ├── npc_04.json
│ │ ├── npc_04.png
│ │ ├── npc_05.json
│ │ └── npc_05.png
│ └── sources
│ │ ├── coin
│ │ ├── coin_idle_01.png
│ │ └── coin_idle_02.png
│ │ ├── enemy
│ │ ├── walk_down_01.png
│ │ ├── walk_down_02.png
│ │ ├── walk_down_03.png
│ │ ├── walk_left_01.png
│ │ ├── walk_left_02.png
│ │ ├── walk_left_03.png
│ │ ├── walk_right_01.png
│ │ ├── walk_right_02.png
│ │ ├── walk_right_03.png
│ │ ├── walk_up_01.png
│ │ ├── walk_up_02.png
│ │ └── walk_up_03.png
│ │ ├── hero
│ │ ├── walk_down_01.png
│ │ ├── walk_down_02.png
│ │ ├── walk_down_03.png
│ │ ├── walk_left_01.png
│ │ ├── walk_left_02.png
│ │ ├── walk_left_03.png
│ │ ├── walk_right_01.png
│ │ ├── walk_right_02.png
│ │ ├── walk_right_03.png
│ │ ├── walk_up_01.png
│ │ ├── walk_up_02.png
│ │ └── walk_up_03.png
│ │ ├── npc_01
│ │ ├── walk_down_01.png
│ │ ├── walk_down_02.png
│ │ ├── walk_down_03.png
│ │ ├── walk_left_01.png
│ │ ├── walk_left_02.png
│ │ ├── walk_left_03.png
│ │ ├── walk_right_01.png
│ │ ├── walk_right_02.png
│ │ ├── walk_right_03.png
│ │ ├── walk_up_01.png
│ │ ├── walk_up_02.png
│ │ └── walk_up_03.png
│ │ ├── npc_02
│ │ ├── walk_down_01.png
│ │ ├── walk_down_02.png
│ │ ├── walk_down_03.png
│ │ ├── walk_left_01.png
│ │ ├── walk_left_02.png
│ │ ├── walk_left_03.png
│ │ ├── walk_right_01.png
│ │ ├── walk_right_02.png
│ │ ├── walk_right_03.png
│ │ ├── walk_up_01.png
│ │ ├── walk_up_02.png
│ │ └── walk_up_03.png
│ │ ├── npc_03
│ │ ├── walk_down_01.png
│ │ ├── walk_down_02.png
│ │ ├── walk_down_03.png
│ │ ├── walk_left_01.png
│ │ ├── walk_left_02.png
│ │ ├── walk_left_03.png
│ │ ├── walk_right_01.png
│ │ ├── walk_right_02.png
│ │ ├── walk_right_03.png
│ │ ├── walk_up_01.png
│ │ ├── walk_up_02.png
│ │ └── walk_up_03.png
│ │ ├── npc_04
│ │ ├── walk_down_01.png
│ │ ├── walk_down_02.png
│ │ ├── walk_down_03.png
│ │ ├── walk_left_01.png
│ │ ├── walk_left_02.png
│ │ ├── walk_left_03.png
│ │ ├── walk_right_01.png
│ │ ├── walk_right_02.png
│ │ ├── walk_right_03.png
│ │ ├── walk_up_01.png
│ │ ├── walk_up_02.png
│ │ └── walk_up_03.png
│ │ └── npc_05
│ │ ├── walk_down_01.png
│ │ ├── walk_down_02.png
│ │ ├── walk_down_03.png
│ │ ├── walk_left_01.png
│ │ ├── walk_left_02.png
│ │ ├── walk_left_03.png
│ │ ├── walk_right_01.png
│ │ ├── walk_right_02.png
│ │ ├── walk_right_03.png
│ │ ├── walk_up_01.png
│ │ ├── walk_up_02.png
│ │ └── walk_up_03.png
├── fonts
│ ├── Munro-Narrow.ttf
│ ├── Munro-Small.ttf
│ ├── Munro.ttf
│ ├── PressStart2P-Regular.ttf
│ ├── press-start-medium-white.png
│ ├── press-start-medium-white.xml
│ ├── press-start-normal-white.png
│ ├── press-start-normal-white.xml
│ ├── press-start-small-white.png
│ └── press-start-small-white.xml
├── images
│ ├── a_button.png
│ ├── b_button.png
│ ├── background_desert.png
│ ├── background_fall.png
│ ├── background_forest.png
│ ├── background_grass.png
│ ├── background_winter.png
│ ├── crystal.png
│ ├── d_pad_button.png
│ ├── enemy_01.png
│ ├── enemy_02.png
│ ├── enemy_03.png
│ ├── heart_empty.png
│ ├── heart_full.png
│ ├── heart_half.png
│ └── key.png
├── maps
│ ├── sample_indoor.json
│ └── sample_map.json
└── tilesets
│ ├── city.json
│ ├── city.png
│ ├── objects.json
│ ├── objects.png
│ ├── village.json
│ └── village.png
├── components
├── DialogBox
│ ├── DialogBox.jsx
│ └── DialogBox.module.scss
├── GameMenu
│ ├── GameMenu.jsx
│ └── GameMenu.module.scss
├── GameText
│ ├── GameText.jsx
│ └── GameText.module.scss
├── Message
│ ├── Message.jsx
│ └── Message.module.scss
├── MessageBox.jsx
├── ReactWrapper.jsx
└── VirtualGamepad
│ ├── VirtualGamepad.jsx
│ └── VirtualGamepad.module.scss
├── constants.js
├── game
└── scenes
│ ├── BootScene.js
│ ├── GameScene.js
│ ├── LoadAssetsScene.js
│ └── MainMenuScene.js
├── hooks
├── useMutationObserver.jsx
└── useRect.jsx
├── index.css
├── index.js
├── intl
└── en.json
├── setupTests.js
├── utils
├── __tests__
│ └── phaser.test.js
├── phaser.js
├── sceneHelpers.js
└── utils.js
└── zustand
├── assets
├── selectLoadedAssets.js
└── setLoadedAssets.js
├── dialog
├── selectDialog.js
└── setDialog.js
├── game
├── selectGameData.js
└── setGameData.js
├── hero
├── selectHeroData.js
└── setHeroData.js
├── map
├── selectMapData.js
└── setMapData.js
├── menu
├── selectMenu.js
└── setMenu.js
├── store.js
└── text
├── selectText.js
└── setText.js
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.js text eol=crlf
2 | *.jsx text eol=crlf
3 | *.json text eol=crlf
4 | *.md text eol=crlf
5 |
6 | *.png binary
7 | *.jpg binary
8 | *.gif binary
9 | *.mp4 binary
10 | *.webm binary
11 |
--------------------------------------------------------------------------------
/.github/workflows/deploy-to-gh-pages.yml:
--------------------------------------------------------------------------------
1 | name: Deploy to GitHub Pages
2 | on:
3 | workflow_dispatch:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | build-and-deploy:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Checkout
13 | uses: actions/checkout@v3.3.0
14 | with:
15 | persist-credentials: false
16 |
17 | - name: Install and Build 🚧
18 | run: |
19 | npm cache clean --force
20 | npm install
21 | npm run build
22 |
23 | - name: Deploy 🚀
24 | uses: JamesIves/github-pages-deploy-action@v4.4.1
25 | with:
26 | branch: gh-pages
27 | folder: build
28 | clean: true
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 | .idea/*
26 | .idea
27 | .idea/
28 | .env
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Pablo
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Top-Down Phaser Game with React UI Template
2 |
3 | This project is based on a Medium post: https://javascript.plainenglish.io/i-made-a-top-down-game-version-of-my-blog-with-phaser-and-react-faf5c28cf768
4 |
5 |
6 |
7 | ## Try it out: https://blopa.github.io/top-down-react-phaser-game-template/
8 |
9 | # Key Features
10 | - Built with Create React App
11 | - Uses Phaser 3 for game engine
12 | - State management with Zustand
13 | - UI with Material UI and React 18
14 | - CSS Modules
15 | - Uses functional programming style
16 | - Arcade physics
17 | - Automatically resizes game to fit browser window
18 | - Automatically loads Tilesets and assets
19 | - Generates atlas sheets with included script
20 | - Adjustable tile sizes
21 | - Integrates Phaser and React through Zustand
22 | - Dialog system (React-based)
23 | - Game menu (React-based)
24 | - Virtual Gamepad for mobile devices (React-based)
25 | - Includes 2D assets from Kenney.nl
26 |
27 | # How to Use
28 |
29 | ## Load Scene Files
30 | The `getScenesModules` function uses Webpack's [require.context](https://webpack.js.org/guides/dependency-management/#requirecontext) to load all `.js` and `.ts` files from the `/src/assets/games/scenes` directory. Simply add your game scenes there to have them loaded into the game.
31 |
32 | The first scene loaded by Phaser JS is the one defined in the `constants.js` file, in the `BOOT_SCENE_NAME` variable.
33 |
34 | ## Functional Programming
35 | Scene code can be written in a functional style for improved readability, by exporting functions instead of using the `Phaser.Scene` class.
36 |
37 | ```javascript
38 | // Export scene using class-based approach
39 | export default class BootScene extends Scene {
40 | constructor() {
41 | super('BootScene');
42 | }
43 |
44 | preload() {
45 | this.load.image('background', background);
46 | }
47 |
48 | create() {
49 | this.add.image(100, 100, 'background');
50 | }
51 | }
52 | ```
53 |
54 | ```javascript
55 | // Export scene in functional approach
56 | export const scene = {};
57 |
58 | export const key = 'BootScene';
59 |
60 | export function preload() {
61 | scene.load.image('background', background);
62 | }
63 |
64 | export function create() {
65 | scene.add.image(100, 100, 'background');
66 | }
67 | ```
68 |
69 | The exported `scene` object will have all the helper functions of `Phaser.Scene`. While it can still be accessed with `this`, the functional approach is designed to improve code readability.
70 |
71 | This "magic" is made possible by the `prepareScene` function.
72 |
73 | ## Maps
74 | To use Tiled maps, add your Tiled tilesets JSON and images to `/src/assets/tilesets` and your Tiled maps to `/src/assets/maps`. Then start the `LoadAssetsScene` like this:
75 |
76 | ```javascript
77 | this.scene.start('LoadAssetsScene', {
78 | nextScene: 'GameScene', // Scene to load after assets are loaded
79 | assets: {
80 | mapKey: 'sample_map', // Map name, e.g. sample_map.json
81 | },
82 | });
83 | ```
84 |
85 | Any tilesets used in your `sample_map.json` will be automatically loaded from the `/src/assets/tilesets` directory, as long as they are located there.
86 |
87 | ## Other assets
88 | To load other assets such as images, fonts, or atlases, call the `LoadAssetsScene` with the following parameters:
89 |
90 | ```javascript
91 | this.scene.start('LoadAssetsScene', {
92 | nextScene: 'GameScene', // scene to be loaded after the assets are loaded
93 | assets: {
94 | fonts: ['"Press Start 2P"'], // fonts to be loaded
95 | atlases: ['hero'], // atlases to be loaded, must be in `/src/assets/atlases/generated/` as hero.json and hero.png
96 | images: ['background'], // images to be loaded, must be in `/src/assets/images` as background.png
97 | },
98 | });
99 | ```
100 |
101 | ## The 'GameScene'
102 | The `GameScene` file is where the game map is rendered, along with all items, enemies, etc. The `create` function is split into smaller functions for easier readability, which can be found in the `sceneHelpers.js` file.
103 |
104 | ## Virtual Gamepad
105 | The virtual gamepad will be automatically loaded when the game is run on a mobile device. The virtual gamepad is a React component that simulates keyboard keys to control the game, using the `simulateKeyEvent` function found in this [GitHub Gist](https://gist.github.com/GlauberF/d8278ce3aa592389e6e3d4e758e6a0c2).
106 |
107 | ## Dialog System
108 | A dialog box will appear automatically whenever the `state.dialog.messages` variable is populated with messages. To accomplish this, you can call the `setDialogMessagesAction` Zustand setter function.
109 |
110 | ```javascript
111 | setDialogMessages(['hello world', 'hello world 2']);
112 | ```
113 |
114 | # Assets by Kenney.nl:
115 | - https://www.kenney.nl/assets/rpg-urban-pack
116 | - https://www.kenney.nl/assets/roguelike-rpg-pack
117 | - https://www.kenney.nl/assets/pixel-platformer
118 | - https://www.kenney.nl/assets/onscreen-controls
119 | - https://www.kenney.nl/assets/background-elements-redux
120 | - https://kenney.itch.io/creature-mixer
121 |
122 | # License
123 | MIT License
124 |
125 | Copyright (c) 2023 Pablo
126 |
127 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
128 |
129 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
130 |
131 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
132 |
133 | **Free Software, Hell Yeah!**
134 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "top-down-react-phaser-game-template",
3 | "homepage": ".",
4 | "version": "1.0.0",
5 | "private": false,
6 | "devDependencies": {
7 | "@babel/eslint-parser": "^7.19.1",
8 | "@babel/eslint-plugin": "^7.19.1",
9 | "@rtpa/phaser-bitmapfont-generator": "^1.2.2",
10 | "@testing-library/jest-dom": "^5.16.5",
11 | "@testing-library/react": "^13.4.0",
12 | "@testing-library/user-event": "^14.4.3",
13 | "eslint": "^8.34.0",
14 | "eslint-config-airbnb": "^19.0.4",
15 | "eslint-config-standard": "^17.0.0",
16 | "eslint-config-standard-jsx": "^11.0.0",
17 | "eslint-import-resolver-webpack": "^0.13.2",
18 | "eslint-plugin-babel": "^5.3.1",
19 | "eslint-plugin-filenames": "^1.3.2",
20 | "eslint-plugin-flowtype": "^8.0.3",
21 | "eslint-plugin-import": "^2.27.5",
22 | "eslint-plugin-jest": "^27.2.1",
23 | "eslint-plugin-jsx-a11y": "^6.7.1",
24 | "eslint-plugin-prettier": "^4.2.1",
25 | "eslint-plugin-react": "^7.32.2",
26 | "eslint-plugin-react-hooks": "^4.6.0",
27 | "eslint-plugin-unicorn": "^45.0.2",
28 | "eslint-plugin-unused-imports": "^2.0.0",
29 | "free-tex-packer-core": "^0.3.4",
30 | "npm-check-updates": "^16.7.5"
31 | },
32 | "dependencies": {
33 | "@emotion/react": "^11.10.6",
34 | "@emotion/styled": "^11.10.6",
35 | "@mui/icons-material": "^5.11.9",
36 | "@mui/material": "^5.11.9",
37 | "@react-spring/web": "^9.6.1",
38 | "beautiful-react-hooks": "^3.12.2",
39 | "classnames": "^2.3.2",
40 | "cross-env": "^7.0.3",
41 | "is-mobile": "^3.1.1",
42 | "jest": "^27.5.1",
43 | "phaser": "^3.55.2",
44 | "react": "^18.2.0",
45 | "react-dom": "^18.2.0",
46 | "react-helmet": "^6.1.0",
47 | "react-intl": "^6.2.8",
48 | "react-scripts": "^5.0.1",
49 | "sass": "^1.58.3",
50 | "zustand": "^4.3.3"
51 | },
52 | "overrides": {
53 | "react-router-dom": "5.3.4",
54 | "react-error-overlay": "6.0.9"
55 | },
56 | "scripts": {
57 | "generate-bitmap-font": "node scripts/generate-bitmap-font.js",
58 | "generate-atlases": "node scripts/generate-atlas-files.js",
59 | "copy-tileset-data": "node scripts/copy-tileset-data.js",
60 | "start": "cross-env IMAGE_INLINE_SIZE_LIMIT=1 PUBLIC_URL=/ react-scripts start",
61 | "build": "cross-env IMAGE_INLINE_SIZE_LIMIT=1 CI=false react-scripts build",
62 | "test": "react-scripts test",
63 | "eject": "react-scripts eject",
64 | "bump": "ncu -u -t minor",
65 | "clean": "rm node_modules/.cache/.eslintcache"
66 | },
67 | "browserslist": {
68 | "production": [
69 | ">0.2%",
70 | "not dead",
71 | "not op_mini all"
72 | ],
73 | "development": [
74 | "last 1 chrome version",
75 | "last 1 firefox version",
76 | "last 1 safari version"
77 | ]
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
17 |
18 |
22 |
23 |
32 | Game Title
33 |
34 |
35 |
36 |
37 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/scripts/copy-tileset-data.js:
--------------------------------------------------------------------------------
1 | const { readdirSync, readFileSync, writeFileSync } = require('fs');
2 | const path = require('path');
3 |
4 | const TILESETS_PATH = path.resolve(
5 | __dirname,
6 | '..',
7 | 'src',
8 | 'assets',
9 | 'maps'
10 | );
11 |
12 | async function copyTilesetData(mapName = null) {
13 | if (!mapName) {
14 | console.error('No map passed');
15 | return;
16 | }
17 |
18 | const allFiles = [];
19 | let sourceTilesetData = [];
20 | const mapsFolders = await readdirSync(TILESETS_PATH);
21 | // eslint-disable-next-line no-restricted-syntax
22 | for (const tilesetPath of mapsFolders) {
23 | const filePath = path.resolve(TILESETS_PATH, tilesetPath);
24 | // eslint-disable-next-line no-await-in-loop
25 | const spritesFiles = await readdirSync(filePath);
26 | // eslint-disable-next-line no-restricted-syntax
27 | for (const spritesFile of spritesFiles) {
28 | if (spritesFile === `${mapName}.json`) {
29 | // eslint-disable-next-line no-await-in-loop
30 | const jsonData = JSON.parse(await readFileSync(path.resolve(filePath, spritesFile)));
31 | sourceTilesetData = jsonData.tilesets;
32 | } else {
33 | allFiles.push(path.resolve(filePath, spritesFile));
34 | }
35 | }
36 | }
37 |
38 | // eslint-disable-next-line no-restricted-syntax
39 | for (const jsonFile of allFiles) {
40 | // eslint-disable-next-line no-await-in-loop
41 | const jsonData = JSON.parse(await readFileSync(
42 | jsonFile
43 | ));
44 |
45 | jsonData.tilesets = sourceTilesetData;
46 |
47 | // eslint-disable-next-line no-await-in-loop
48 | await writeFileSync(
49 | jsonFile,
50 | JSON.stringify(jsonData, null, 2)
51 | );
52 | }
53 | }
54 |
55 | copyTilesetData(process.argv[2]);
56 |
--------------------------------------------------------------------------------
/scripts/generate-atlas-files.js:
--------------------------------------------------------------------------------
1 | const { readdirSync, readFileSync, writeFileSync } = require('fs');
2 | const { packAsync } = require('free-tex-packer-core');
3 | const path = require('path');
4 |
5 | const SOURCE_SPRITES_PATH = path.resolve(
6 | __dirname,
7 | '..',
8 | 'src',
9 | 'assets',
10 | 'atlases',
11 | 'sources'
12 | );
13 |
14 | const SPRITES_PATH = path.resolve(
15 | __dirname,
16 | '..',
17 | 'src',
18 | 'assets',
19 | 'atlases',
20 | 'generated'
21 | );
22 |
23 | async function generateAtlasFiles(assetName = null) {
24 | let spritesFolders;
25 | if (assetName) {
26 | spritesFolders = [assetName];
27 | } else {
28 | spritesFolders = await readdirSync(SOURCE_SPRITES_PATH);
29 | }
30 |
31 | // eslint-disable-next-line no-restricted-syntax
32 | for (const spritesFolder of spritesFolders) {
33 | // eslint-disable-next-line no-await-in-loop
34 | const spritesFiles = await readdirSync(path.resolve(__dirname, SOURCE_SPRITES_PATH, spritesFolder));
35 | const images = [];
36 | spritesFiles.forEach((spritesFile) => {
37 | images.push({
38 | path: spritesFile,
39 | contents: readFileSync(
40 | path.resolve(__dirname, SOURCE_SPRITES_PATH, spritesFolder, spritesFile)
41 | ),
42 | });
43 | });
44 |
45 | // eslint-disable-next-line no-await-in-loop
46 | await packImages(images, spritesFolder);
47 | }
48 | }
49 |
50 | async function packImages(images, spriteName) {
51 | try {
52 | const files = await packAsync(images, {
53 | allowRotation: false,
54 | removeFileExtension: true,
55 | prependFolderName: false,
56 | exporter: 'Phaser3',
57 | allowTrim: true,
58 | detectIdentical: true,
59 | fixedSize: false,
60 | textureName: spriteName,
61 | });
62 | // eslint-disable-next-line no-restricted-syntax
63 | for (const item of files) {
64 | const fileExt = item.name.split('.').pop();
65 | // eslint-disable-next-line no-await-in-loop
66 | await writeFileSync(
67 | path.resolve(__dirname, SPRITES_PATH, `${spriteName}.${fileExt}`),
68 | item.buffer
69 | );
70 | }
71 | } catch (error) {
72 | console.log(error);
73 | }
74 | }
75 |
76 | generateAtlasFiles(process.argv[2]);
77 |
--------------------------------------------------------------------------------
/scripts/generate-bitmap-font.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const { TextStyle2BitmapFont } = require('@rtpa/phaser-bitmapfont-generator');
3 |
4 | const FONTS_PATH = path.resolve(__dirname, '../src/assets/fonts');
5 |
6 | const sizes = [{
7 | fontSize: '10px',
8 | fileName: 'press-start-small-white',
9 | }, {
10 | fontSize: '18px',
11 | fileName: 'press-start-medium-white',
12 | }, {
13 | fontSize: '20px',
14 | fileName: 'press-start-normal-white',
15 | }];
16 |
17 | const doWork = async () => {
18 | const fontFamily = process.argv[2] || '"Press Start 2P"';
19 |
20 | // eslint-disable-next-line no-restricted-syntax
21 | for (const sizeData of sizes) {
22 | const { fontSize, fileName } = sizeData;
23 | // eslint-disable-next-line no-await-in-loop
24 | await TextStyle2BitmapFont(
25 | {
26 | path: FONTS_PATH,
27 | fileName,
28 | compression: null,
29 | antialias: false,
30 | // Phaser.GameObjects.RetroFont.TEXT_SET1
31 | textSet: ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZÁÂÉÍÕÚÇÃÀÊÓÔÜ[\\]^_`abcdefghijklmnopqrstuvwxyzáâéíõúçãàêóôü{|}~',
32 | textStyle: {
33 | fontFamily,
34 | fontSize,
35 | color: '#ffffff',
36 | // shadow: {
37 | // offsetX: 4,
38 | // offsetY: 4,
39 | // blur: 0,
40 | // fill: true,
41 | // stroke: true,
42 | // color: '#000000',
43 | // },
44 | },
45 | }
46 | );
47 | }
48 |
49 | return process.exit(1);
50 | };
51 |
52 | doWork();
53 |
--------------------------------------------------------------------------------
/scripts/tests.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/scripts/tests.js
--------------------------------------------------------------------------------
/source_files/enemy.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/source_files/enemy.psd
--------------------------------------------------------------------------------
/source_files/game_sample.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/source_files/game_sample.gif
--------------------------------------------------------------------------------
/src/Game.jsx:
--------------------------------------------------------------------------------
1 | import Phaser from 'phaser';
2 | import { useCallback, useEffect, useState } from 'react';
3 | import { IntlProvider } from 'react-intl';
4 | import isMobile from 'is-mobile';
5 |
6 | // Utils
7 | import { calculateGameSize, getScenesModules } from './utils/phaser';
8 | import { isDev } from './utils/utils';
9 |
10 | // Components
11 | import VirtualGamepad from './components/VirtualGamepad/VirtualGamepad';
12 | import ReactWrapper from './components/ReactWrapper';
13 |
14 | // Selectors
15 | import {
16 | selectGameZoom,
17 | selectGameWidth,
18 | selectGameHeight,
19 | selectGameLocale,
20 | selectGameSetters,
21 | selectGameCameraSizeUpdateCallbacks,
22 | } from './zustand/game/selectGameData';
23 |
24 | // Store
25 | import { useGameStore } from './zustand/store';
26 |
27 | // Constants
28 | import {
29 | TILE_WIDTH,
30 | TILE_HEIGHT,
31 | DEFAULT_LOCALE,
32 | MIN_GAME_WIDTH,
33 | GAME_CONTENT_ID,
34 | MIN_GAME_HEIGHT,
35 | RESIZE_THRESHOLD,
36 | RE_RESIZE_THRESHOLD,
37 | } from './constants';
38 | import defaultMessages from './intl/en.json';
39 |
40 | const IS_DEV = isDev();
41 |
42 | function Game() {
43 | const [game, setGame] = useState(null);
44 | const locale = useGameStore(selectGameLocale) || DEFAULT_LOCALE;
45 | const cameraSizeUpdateCallbacks = useGameStore(selectGameCameraSizeUpdateCallbacks);
46 | const [messages, setMessages] = useState(defaultMessages);
47 | const {
48 | setGameZoom,
49 | setGameWidth,
50 | setGameHeight,
51 | setGameCanvasElement,
52 | } = useGameStore(selectGameSetters);
53 |
54 | // Game
55 | const gameWidth = useGameStore(selectGameWidth);
56 | const gameHeight = useGameStore(selectGameHeight);
57 | const gameZoom = useGameStore(selectGameZoom);
58 |
59 | useEffect(() => {
60 | document.documentElement.style.setProperty('--game-zoom', gameZoom);
61 | document.documentElement.style.setProperty('--game-height', gameHeight);
62 | document.documentElement.style.setProperty('--game-width', gameWidth);
63 | }, [gameHeight, gameWidth, gameZoom]);
64 |
65 | useEffect(() => {
66 | async function loadMessages() {
67 | const module = await import(`./intl/${locale}.json`);
68 | setMessages(module.default);
69 | }
70 |
71 | if (locale !== DEFAULT_LOCALE) {
72 | loadMessages();
73 | }
74 | }, [locale]);
75 |
76 | const updateGameGlobalState = useCallback((
77 | gameWidth,
78 | gameHeight,
79 | gameZoom
80 | ) => {
81 | setGameHeight(gameHeight);
82 | setGameWidth(gameWidth);
83 | setGameZoom(gameZoom);
84 | }, [setGameHeight, setGameWidth, setGameZoom]);
85 |
86 | // Create the game inside a useEffect
87 | // to create it only once
88 | useEffect(() => {
89 | // do this otherwise development hot-reload
90 | // will create a bunch of Phaser instances
91 | if (game) {
92 | return;
93 | }
94 |
95 | const { width, height, zoom } = calculateGameSize(
96 | MIN_GAME_WIDTH,
97 | MIN_GAME_HEIGHT,
98 | TILE_WIDTH,
99 | TILE_HEIGHT
100 | );
101 |
102 | const phaserGame = new Phaser.Game({
103 | type: Phaser.AUTO,
104 | title: 'some-game-title',
105 | parent: GAME_CONTENT_ID,
106 | orientation: Phaser.Scale.LANDSCAPE,
107 | localStorageName: 'some-game-title',
108 | width,
109 | height,
110 | zoom,
111 | autoRound: true,
112 | pixelArt: true,
113 | scale: {
114 | autoCenter: Phaser.Scale.CENTER_BOTH,
115 | mode: Phaser.Scale.NONE,
116 | },
117 | scene: getScenesModules(),
118 | physics: {
119 | default: 'arcade',
120 | arcade: {
121 | debug: IS_DEV,
122 | },
123 | },
124 | backgroundColor: '#000000',
125 | });
126 |
127 | updateGameGlobalState(width, height, zoom);
128 | setGame(phaserGame);
129 |
130 | if (IS_DEV) {
131 | window.phaserGame = phaserGame;
132 | }
133 | }, [
134 | game,
135 | updateGameGlobalState,
136 | ]);
137 |
138 | useEffect(() => {
139 | if (game?.canvas) {
140 | setGameCanvasElement(game.canvas);
141 | }
142 | }, [setGameCanvasElement, game?.canvas]);
143 |
144 | useEffect(() => {
145 | if (!game) {
146 | return () => {};
147 | }
148 |
149 | // Create listener to resize the game
150 | // when the window is resized
151 | let timeOutFunctionId;
152 | const resizeDoneCallback = () => {
153 | const scaleGame = () => {
154 | const gameSize = calculateGameSize(
155 | MIN_GAME_WIDTH,
156 | MIN_GAME_HEIGHT,
157 | TILE_WIDTH,
158 | TILE_HEIGHT
159 | );
160 |
161 | // console.log(JSON.stringify(gameSize));
162 | game.scale.setZoom(gameSize.zoom);
163 | game.scale.resize(gameSize.width, gameSize.height);
164 | // game.scale.setGameSize(gameSize.width, gameSize.height);
165 | // game.scale.displaySize.resize(gameSize.width, gameSize.height);
166 | // game.scale.resize(gameSize.width, gameSize.height).getParentBounds();
167 | updateGameGlobalState(gameSize.width, gameSize.height, gameSize.zoom);
168 | // game.canvas.style.width = `${gameSize.width}px`;
169 | // game.canvas.style.height = `${gameSize.height}px`;
170 | cameraSizeUpdateCallbacks.forEach((cameraSizeUpdateCallback) => {
171 | cameraSizeUpdateCallback?.();
172 | });
173 | };
174 |
175 | scaleGame();
176 |
177 | // re-run function after resize is done to re-trigger css calculations
178 | setTimeout(scaleGame, RE_RESIZE_THRESHOLD);
179 | };
180 |
181 | const canvasResizeCallback = () => {
182 | clearTimeout(timeOutFunctionId);
183 | timeOutFunctionId = setTimeout(resizeDoneCallback, RESIZE_THRESHOLD);
184 | };
185 |
186 | // TODO move to the ResizeObserver https://jsfiddle.net/rudiedirkx/p0ckdcnv/
187 | window.addEventListener('resize', canvasResizeCallback);
188 |
189 | return () => {
190 | window.removeEventListener('resize', canvasResizeCallback);
191 | };
192 | }, [
193 | game,
194 | updateGameGlobalState,
195 | cameraSizeUpdateCallbacks,
196 | ]);
197 |
198 | return (
199 |
204 |
208 | {/* this is where the game canvas will be rendered */}
209 |
210 |
211 | {isMobile() && (
212 |
213 | )}
214 |
215 | );
216 | }
217 |
218 | export default Game;
219 |
--------------------------------------------------------------------------------
/src/assets/atlases/generated/coin.json:
--------------------------------------------------------------------------------
1 | {
2 | "textures": [
3 | {
4 | "image": "coin.png",
5 | "format": "RGBA8888",
6 | "size": {
7 | "w": 12,
8 | "h": 24
9 | },
10 | "scale": 1,
11 | "frames": [
12 | {
13 | "filename": "coin_idle_01",
14 | "rotated": false,
15 | "trimmed": true,
16 | "sourceSize": {
17 | "w": 16,
18 | "h": 16
19 | },
20 | "spriteSourceSize": {
21 | "x": 2,
22 | "y": 2,
23 | "w": 12,
24 | "h": 12
25 | },
26 | "frame": {
27 | "x": 0,
28 | "y": 0,
29 | "w": 12,
30 | "h": 12
31 | }
32 | },
33 | {
34 | "filename": "coin_idle_02",
35 | "rotated": false,
36 | "trimmed": true,
37 | "sourceSize": {
38 | "w": 16,
39 | "h": 16
40 | },
41 | "spriteSourceSize": {
42 | "x": 4,
43 | "y": 2,
44 | "w": 8,
45 | "h": 12
46 | },
47 | "frame": {
48 | "x": 0,
49 | "y": 12,
50 | "w": 8,
51 | "h": 12
52 | }
53 | }
54 | ]
55 | }
56 | ],
57 | "meta": {
58 | "app": "http://github.com/odrick/free-tex-packer-core",
59 | "version": "0.3.4"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/assets/atlases/generated/coin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/generated/coin.png
--------------------------------------------------------------------------------
/src/assets/atlases/generated/enemy.json:
--------------------------------------------------------------------------------
1 | {
2 | "textures": [
3 | {
4 | "image": "enemy.png",
5 | "format": "RGBA8888",
6 | "size": {
7 | "w": 14,
8 | "h": 156
9 | },
10 | "scale": 1,
11 | "frames": [
12 | {
13 | "filename": "walk_down_01",
14 | "rotated": false,
15 | "trimmed": true,
16 | "sourceSize": {
17 | "w": 16,
18 | "h": 16
19 | },
20 | "spriteSourceSize": {
21 | "x": 1,
22 | "y": 0,
23 | "w": 14,
24 | "h": 16
25 | },
26 | "frame": {
27 | "x": 0,
28 | "y": 0,
29 | "w": 14,
30 | "h": 16
31 | }
32 | },
33 | {
34 | "filename": "walk_down_03",
35 | "rotated": false,
36 | "trimmed": true,
37 | "sourceSize": {
38 | "w": 16,
39 | "h": 16
40 | },
41 | "spriteSourceSize": {
42 | "x": 1,
43 | "y": 0,
44 | "w": 14,
45 | "h": 16
46 | },
47 | "frame": {
48 | "x": 0,
49 | "y": 16,
50 | "w": 14,
51 | "h": 16
52 | }
53 | },
54 | {
55 | "filename": "walk_left_01",
56 | "rotated": false,
57 | "trimmed": true,
58 | "sourceSize": {
59 | "w": 16,
60 | "h": 16
61 | },
62 | "spriteSourceSize": {
63 | "x": 1,
64 | "y": 0,
65 | "w": 14,
66 | "h": 16
67 | },
68 | "frame": {
69 | "x": 0,
70 | "y": 32,
71 | "w": 14,
72 | "h": 16
73 | }
74 | },
75 | {
76 | "filename": "walk_right_01",
77 | "rotated": false,
78 | "trimmed": true,
79 | "sourceSize": {
80 | "w": 16,
81 | "h": 16
82 | },
83 | "spriteSourceSize": {
84 | "x": 1,
85 | "y": 0,
86 | "w": 14,
87 | "h": 16
88 | },
89 | "frame": {
90 | "x": 0,
91 | "y": 48,
92 | "w": 14,
93 | "h": 16
94 | }
95 | },
96 | {
97 | "filename": "walk_up_01",
98 | "rotated": false,
99 | "trimmed": true,
100 | "sourceSize": {
101 | "w": 16,
102 | "h": 16
103 | },
104 | "spriteSourceSize": {
105 | "x": 1,
106 | "y": 0,
107 | "w": 14,
108 | "h": 16
109 | },
110 | "frame": {
111 | "x": 0,
112 | "y": 64,
113 | "w": 14,
114 | "h": 16
115 | }
116 | },
117 | {
118 | "filename": "walk_up_03",
119 | "rotated": false,
120 | "trimmed": true,
121 | "sourceSize": {
122 | "w": 16,
123 | "h": 16
124 | },
125 | "spriteSourceSize": {
126 | "x": 1,
127 | "y": 0,
128 | "w": 14,
129 | "h": 16
130 | },
131 | "frame": {
132 | "x": 0,
133 | "y": 80,
134 | "w": 14,
135 | "h": 16
136 | }
137 | },
138 | {
139 | "filename": "walk_down_02",
140 | "rotated": false,
141 | "trimmed": true,
142 | "sourceSize": {
143 | "w": 16,
144 | "h": 16
145 | },
146 | "spriteSourceSize": {
147 | "x": 1,
148 | "y": 1,
149 | "w": 14,
150 | "h": 15
151 | },
152 | "frame": {
153 | "x": 0,
154 | "y": 96,
155 | "w": 14,
156 | "h": 15
157 | }
158 | },
159 | {
160 | "filename": "walk_left_02",
161 | "rotated": false,
162 | "trimmed": true,
163 | "sourceSize": {
164 | "w": 16,
165 | "h": 16
166 | },
167 | "spriteSourceSize": {
168 | "x": 1,
169 | "y": 1,
170 | "w": 14,
171 | "h": 15
172 | },
173 | "frame": {
174 | "x": 0,
175 | "y": 111,
176 | "w": 14,
177 | "h": 15
178 | }
179 | },
180 | {
181 | "filename": "walk_right_02",
182 | "rotated": false,
183 | "trimmed": true,
184 | "sourceSize": {
185 | "w": 16,
186 | "h": 16
187 | },
188 | "spriteSourceSize": {
189 | "x": 1,
190 | "y": 1,
191 | "w": 14,
192 | "h": 15
193 | },
194 | "frame": {
195 | "x": 0,
196 | "y": 126,
197 | "w": 14,
198 | "h": 15
199 | }
200 | },
201 | {
202 | "filename": "walk_up_02",
203 | "rotated": false,
204 | "trimmed": true,
205 | "sourceSize": {
206 | "w": 16,
207 | "h": 16
208 | },
209 | "spriteSourceSize": {
210 | "x": 1,
211 | "y": 1,
212 | "w": 14,
213 | "h": 15
214 | },
215 | "frame": {
216 | "x": 0,
217 | "y": 141,
218 | "w": 14,
219 | "h": 15
220 | }
221 | },
222 | {
223 | "filename": "walk_left_03",
224 | "rotated": false,
225 | "trimmed": true,
226 | "sourceSize": {
227 | "w": 16,
228 | "h": 16
229 | },
230 | "spriteSourceSize": {
231 | "x": 1,
232 | "y": 0,
233 | "w": 14,
234 | "h": 16
235 | },
236 | "frame": {
237 | "x": 0,
238 | "y": 32,
239 | "w": 14,
240 | "h": 16
241 | }
242 | },
243 | {
244 | "filename": "walk_right_03",
245 | "rotated": false,
246 | "trimmed": true,
247 | "sourceSize": {
248 | "w": 16,
249 | "h": 16
250 | },
251 | "spriteSourceSize": {
252 | "x": 1,
253 | "y": 0,
254 | "w": 14,
255 | "h": 16
256 | },
257 | "frame": {
258 | "x": 0,
259 | "y": 48,
260 | "w": 14,
261 | "h": 16
262 | }
263 | }
264 | ]
265 | }
266 | ],
267 | "meta": {
268 | "app": "http://github.com/odrick/free-tex-packer-core",
269 | "version": "0.3.4"
270 | }
271 | }
272 |
--------------------------------------------------------------------------------
/src/assets/atlases/generated/enemy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/generated/enemy.png
--------------------------------------------------------------------------------
/src/assets/atlases/generated/hero.json:
--------------------------------------------------------------------------------
1 | {
2 | "textures": [
3 | {
4 | "image": "hero.png",
5 | "format": "RGBA8888",
6 | "size": {
7 | "w": 23,
8 | "h": 134
9 | },
10 | "scale": 1,
11 | "frames": [
12 | {
13 | "filename": "walk_down_01",
14 | "rotated": false,
15 | "trimmed": true,
16 | "sourceSize": {
17 | "w": 16,
18 | "h": 16
19 | },
20 | "spriteSourceSize": {
21 | "x": 2,
22 | "y": 1,
23 | "w": 12,
24 | "h": 15
25 | },
26 | "frame": {
27 | "x": 0,
28 | "y": 0,
29 | "w": 12,
30 | "h": 15
31 | }
32 | },
33 | {
34 | "filename": "walk_down_03",
35 | "rotated": false,
36 | "trimmed": true,
37 | "sourceSize": {
38 | "w": 16,
39 | "h": 16
40 | },
41 | "spriteSourceSize": {
42 | "x": 2,
43 | "y": 1,
44 | "w": 12,
45 | "h": 15
46 | },
47 | "frame": {
48 | "x": 0,
49 | "y": 15,
50 | "w": 12,
51 | "h": 15
52 | }
53 | },
54 | {
55 | "filename": "walk_up_01",
56 | "rotated": false,
57 | "trimmed": true,
58 | "sourceSize": {
59 | "w": 16,
60 | "h": 16
61 | },
62 | "spriteSourceSize": {
63 | "x": 2,
64 | "y": 1,
65 | "w": 12,
66 | "h": 15
67 | },
68 | "frame": {
69 | "x": 0,
70 | "y": 30,
71 | "w": 12,
72 | "h": 15
73 | }
74 | },
75 | {
76 | "filename": "walk_up_03",
77 | "rotated": false,
78 | "trimmed": true,
79 | "sourceSize": {
80 | "w": 16,
81 | "h": 16
82 | },
83 | "spriteSourceSize": {
84 | "x": 2,
85 | "y": 1,
86 | "w": 12,
87 | "h": 15
88 | },
89 | "frame": {
90 | "x": 0,
91 | "y": 45,
92 | "w": 12,
93 | "h": 15
94 | }
95 | },
96 | {
97 | "filename": "walk_left_01",
98 | "rotated": false,
99 | "trimmed": true,
100 | "sourceSize": {
101 | "w": 16,
102 | "h": 16
103 | },
104 | "spriteSourceSize": {
105 | "x": 2,
106 | "y": 1,
107 | "w": 11,
108 | "h": 15
109 | },
110 | "frame": {
111 | "x": 0,
112 | "y": 60,
113 | "w": 11,
114 | "h": 15
115 | }
116 | },
117 | {
118 | "filename": "walk_left_03",
119 | "rotated": false,
120 | "trimmed": true,
121 | "sourceSize": {
122 | "w": 16,
123 | "h": 16
124 | },
125 | "spriteSourceSize": {
126 | "x": 2,
127 | "y": 1,
128 | "w": 11,
129 | "h": 15
130 | },
131 | "frame": {
132 | "x": 0,
133 | "y": 75,
134 | "w": 11,
135 | "h": 15
136 | }
137 | },
138 | {
139 | "filename": "walk_right_01",
140 | "rotated": false,
141 | "trimmed": true,
142 | "sourceSize": {
143 | "w": 16,
144 | "h": 16
145 | },
146 | "spriteSourceSize": {
147 | "x": 3,
148 | "y": 1,
149 | "w": 11,
150 | "h": 15
151 | },
152 | "frame": {
153 | "x": 0,
154 | "y": 90,
155 | "w": 11,
156 | "h": 15
157 | }
158 | },
159 | {
160 | "filename": "walk_right_03",
161 | "rotated": false,
162 | "trimmed": true,
163 | "sourceSize": {
164 | "w": 16,
165 | "h": 16
166 | },
167 | "spriteSourceSize": {
168 | "x": 3,
169 | "y": 1,
170 | "w": 11,
171 | "h": 15
172 | },
173 | "frame": {
174 | "x": 0,
175 | "y": 105,
176 | "w": 11,
177 | "h": 15
178 | }
179 | },
180 | {
181 | "filename": "walk_down_02",
182 | "rotated": false,
183 | "trimmed": true,
184 | "sourceSize": {
185 | "w": 16,
186 | "h": 16
187 | },
188 | "spriteSourceSize": {
189 | "x": 2,
190 | "y": 2,
191 | "w": 12,
192 | "h": 14
193 | },
194 | "frame": {
195 | "x": 0,
196 | "y": 120,
197 | "w": 12,
198 | "h": 14
199 | }
200 | },
201 | {
202 | "filename": "walk_up_02",
203 | "rotated": false,
204 | "trimmed": true,
205 | "sourceSize": {
206 | "w": 16,
207 | "h": 16
208 | },
209 | "spriteSourceSize": {
210 | "x": 2,
211 | "y": 2,
212 | "w": 12,
213 | "h": 14
214 | },
215 | "frame": {
216 | "x": 11,
217 | "y": 60,
218 | "w": 12,
219 | "h": 14
220 | }
221 | },
222 | {
223 | "filename": "walk_left_02",
224 | "rotated": false,
225 | "trimmed": true,
226 | "sourceSize": {
227 | "w": 16,
228 | "h": 16
229 | },
230 | "spriteSourceSize": {
231 | "x": 3,
232 | "y": 2,
233 | "w": 9,
234 | "h": 14
235 | },
236 | "frame": {
237 | "x": 11,
238 | "y": 74,
239 | "w": 9,
240 | "h": 14
241 | }
242 | },
243 | {
244 | "filename": "walk_right_02",
245 | "rotated": false,
246 | "trimmed": true,
247 | "sourceSize": {
248 | "w": 16,
249 | "h": 16
250 | },
251 | "spriteSourceSize": {
252 | "x": 4,
253 | "y": 2,
254 | "w": 9,
255 | "h": 14
256 | },
257 | "frame": {
258 | "x": 11,
259 | "y": 88,
260 | "w": 9,
261 | "h": 14
262 | }
263 | }
264 | ]
265 | }
266 | ],
267 | "meta": {
268 | "app": "http://github.com/odrick/free-tex-packer-core",
269 | "version": "0.3.4"
270 | }
271 | }
272 |
--------------------------------------------------------------------------------
/src/assets/atlases/generated/hero.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/generated/hero.png
--------------------------------------------------------------------------------
/src/assets/atlases/generated/npc_01.json:
--------------------------------------------------------------------------------
1 | {
2 | "textures": [
3 | {
4 | "image": "npc_01.png",
5 | "format": "RGBA8888",
6 | "size": {
7 | "w": 23,
8 | "h": 134
9 | },
10 | "scale": 1,
11 | "frames": [
12 | {
13 | "filename": "walk_down_01",
14 | "rotated": false,
15 | "trimmed": true,
16 | "sourceSize": {
17 | "w": 16,
18 | "h": 16
19 | },
20 | "spriteSourceSize": {
21 | "x": 2,
22 | "y": 1,
23 | "w": 12,
24 | "h": 15
25 | },
26 | "frame": {
27 | "x": 0,
28 | "y": 0,
29 | "w": 12,
30 | "h": 15
31 | }
32 | },
33 | {
34 | "filename": "walk_down_03",
35 | "rotated": false,
36 | "trimmed": true,
37 | "sourceSize": {
38 | "w": 16,
39 | "h": 16
40 | },
41 | "spriteSourceSize": {
42 | "x": 2,
43 | "y": 1,
44 | "w": 12,
45 | "h": 15
46 | },
47 | "frame": {
48 | "x": 0,
49 | "y": 15,
50 | "w": 12,
51 | "h": 15
52 | }
53 | },
54 | {
55 | "filename": "walk_up_01",
56 | "rotated": false,
57 | "trimmed": true,
58 | "sourceSize": {
59 | "w": 16,
60 | "h": 16
61 | },
62 | "spriteSourceSize": {
63 | "x": 2,
64 | "y": 1,
65 | "w": 12,
66 | "h": 15
67 | },
68 | "frame": {
69 | "x": 0,
70 | "y": 30,
71 | "w": 12,
72 | "h": 15
73 | }
74 | },
75 | {
76 | "filename": "walk_up_03",
77 | "rotated": false,
78 | "trimmed": true,
79 | "sourceSize": {
80 | "w": 16,
81 | "h": 16
82 | },
83 | "spriteSourceSize": {
84 | "x": 2,
85 | "y": 1,
86 | "w": 12,
87 | "h": 15
88 | },
89 | "frame": {
90 | "x": 0,
91 | "y": 45,
92 | "w": 12,
93 | "h": 15
94 | }
95 | },
96 | {
97 | "filename": "walk_left_01",
98 | "rotated": false,
99 | "trimmed": true,
100 | "sourceSize": {
101 | "w": 16,
102 | "h": 16
103 | },
104 | "spriteSourceSize": {
105 | "x": 2,
106 | "y": 1,
107 | "w": 11,
108 | "h": 15
109 | },
110 | "frame": {
111 | "x": 0,
112 | "y": 60,
113 | "w": 11,
114 | "h": 15
115 | }
116 | },
117 | {
118 | "filename": "walk_left_03",
119 | "rotated": false,
120 | "trimmed": true,
121 | "sourceSize": {
122 | "w": 16,
123 | "h": 16
124 | },
125 | "spriteSourceSize": {
126 | "x": 2,
127 | "y": 1,
128 | "w": 11,
129 | "h": 15
130 | },
131 | "frame": {
132 | "x": 0,
133 | "y": 75,
134 | "w": 11,
135 | "h": 15
136 | }
137 | },
138 | {
139 | "filename": "walk_right_01",
140 | "rotated": false,
141 | "trimmed": true,
142 | "sourceSize": {
143 | "w": 16,
144 | "h": 16
145 | },
146 | "spriteSourceSize": {
147 | "x": 3,
148 | "y": 1,
149 | "w": 11,
150 | "h": 15
151 | },
152 | "frame": {
153 | "x": 0,
154 | "y": 90,
155 | "w": 11,
156 | "h": 15
157 | }
158 | },
159 | {
160 | "filename": "walk_right_03",
161 | "rotated": false,
162 | "trimmed": true,
163 | "sourceSize": {
164 | "w": 16,
165 | "h": 16
166 | },
167 | "spriteSourceSize": {
168 | "x": 3,
169 | "y": 1,
170 | "w": 11,
171 | "h": 15
172 | },
173 | "frame": {
174 | "x": 0,
175 | "y": 105,
176 | "w": 11,
177 | "h": 15
178 | }
179 | },
180 | {
181 | "filename": "walk_down_02",
182 | "rotated": false,
183 | "trimmed": true,
184 | "sourceSize": {
185 | "w": 16,
186 | "h": 16
187 | },
188 | "spriteSourceSize": {
189 | "x": 2,
190 | "y": 2,
191 | "w": 12,
192 | "h": 14
193 | },
194 | "frame": {
195 | "x": 0,
196 | "y": 120,
197 | "w": 12,
198 | "h": 14
199 | }
200 | },
201 | {
202 | "filename": "walk_up_02",
203 | "rotated": false,
204 | "trimmed": true,
205 | "sourceSize": {
206 | "w": 16,
207 | "h": 16
208 | },
209 | "spriteSourceSize": {
210 | "x": 2,
211 | "y": 2,
212 | "w": 12,
213 | "h": 14
214 | },
215 | "frame": {
216 | "x": 11,
217 | "y": 60,
218 | "w": 12,
219 | "h": 14
220 | }
221 | },
222 | {
223 | "filename": "walk_left_02",
224 | "rotated": false,
225 | "trimmed": true,
226 | "sourceSize": {
227 | "w": 16,
228 | "h": 16
229 | },
230 | "spriteSourceSize": {
231 | "x": 3,
232 | "y": 2,
233 | "w": 9,
234 | "h": 14
235 | },
236 | "frame": {
237 | "x": 11,
238 | "y": 74,
239 | "w": 9,
240 | "h": 14
241 | }
242 | },
243 | {
244 | "filename": "walk_right_02",
245 | "rotated": false,
246 | "trimmed": true,
247 | "sourceSize": {
248 | "w": 16,
249 | "h": 16
250 | },
251 | "spriteSourceSize": {
252 | "x": 4,
253 | "y": 2,
254 | "w": 9,
255 | "h": 14
256 | },
257 | "frame": {
258 | "x": 11,
259 | "y": 88,
260 | "w": 9,
261 | "h": 14
262 | }
263 | }
264 | ]
265 | }
266 | ],
267 | "meta": {
268 | "app": "http://github.com/odrick/free-tex-packer-core",
269 | "version": "0.3.4"
270 | }
271 | }
272 |
--------------------------------------------------------------------------------
/src/assets/atlases/generated/npc_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/generated/npc_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/generated/npc_02.json:
--------------------------------------------------------------------------------
1 | {
2 | "textures": [
3 | {
4 | "image": "npc_02.png",
5 | "format": "RGBA8888",
6 | "size": {
7 | "w": 23,
8 | "h": 125
9 | },
10 | "scale": 1,
11 | "frames": [
12 | {
13 | "filename": "walk_down_01",
14 | "rotated": false,
15 | "trimmed": true,
16 | "sourceSize": {
17 | "w": 16,
18 | "h": 16
19 | },
20 | "spriteSourceSize": {
21 | "x": 2,
22 | "y": 2,
23 | "w": 12,
24 | "h": 14
25 | },
26 | "frame": {
27 | "x": 0,
28 | "y": 0,
29 | "w": 12,
30 | "h": 14
31 | }
32 | },
33 | {
34 | "filename": "walk_down_03",
35 | "rotated": false,
36 | "trimmed": true,
37 | "sourceSize": {
38 | "w": 16,
39 | "h": 16
40 | },
41 | "spriteSourceSize": {
42 | "x": 2,
43 | "y": 2,
44 | "w": 12,
45 | "h": 14
46 | },
47 | "frame": {
48 | "x": 0,
49 | "y": 14,
50 | "w": 12,
51 | "h": 14
52 | }
53 | },
54 | {
55 | "filename": "walk_up_01",
56 | "rotated": false,
57 | "trimmed": true,
58 | "sourceSize": {
59 | "w": 16,
60 | "h": 16
61 | },
62 | "spriteSourceSize": {
63 | "x": 2,
64 | "y": 2,
65 | "w": 12,
66 | "h": 14
67 | },
68 | "frame": {
69 | "x": 0,
70 | "y": 28,
71 | "w": 12,
72 | "h": 14
73 | }
74 | },
75 | {
76 | "filename": "walk_up_03",
77 | "rotated": false,
78 | "trimmed": true,
79 | "sourceSize": {
80 | "w": 16,
81 | "h": 16
82 | },
83 | "spriteSourceSize": {
84 | "x": 2,
85 | "y": 2,
86 | "w": 12,
87 | "h": 14
88 | },
89 | "frame": {
90 | "x": 0,
91 | "y": 42,
92 | "w": 12,
93 | "h": 14
94 | }
95 | },
96 | {
97 | "filename": "walk_left_01",
98 | "rotated": false,
99 | "trimmed": true,
100 | "sourceSize": {
101 | "w": 16,
102 | "h": 16
103 | },
104 | "spriteSourceSize": {
105 | "x": 2,
106 | "y": 2,
107 | "w": 11,
108 | "h": 14
109 | },
110 | "frame": {
111 | "x": 0,
112 | "y": 56,
113 | "w": 11,
114 | "h": 14
115 | }
116 | },
117 | {
118 | "filename": "walk_left_03",
119 | "rotated": false,
120 | "trimmed": true,
121 | "sourceSize": {
122 | "w": 16,
123 | "h": 16
124 | },
125 | "spriteSourceSize": {
126 | "x": 2,
127 | "y": 2,
128 | "w": 11,
129 | "h": 14
130 | },
131 | "frame": {
132 | "x": 0,
133 | "y": 70,
134 | "w": 11,
135 | "h": 14
136 | }
137 | },
138 | {
139 | "filename": "walk_right_01",
140 | "rotated": false,
141 | "trimmed": true,
142 | "sourceSize": {
143 | "w": 16,
144 | "h": 16
145 | },
146 | "spriteSourceSize": {
147 | "x": 3,
148 | "y": 2,
149 | "w": 11,
150 | "h": 14
151 | },
152 | "frame": {
153 | "x": 0,
154 | "y": 84,
155 | "w": 11,
156 | "h": 14
157 | }
158 | },
159 | {
160 | "filename": "walk_right_03",
161 | "rotated": false,
162 | "trimmed": true,
163 | "sourceSize": {
164 | "w": 16,
165 | "h": 16
166 | },
167 | "spriteSourceSize": {
168 | "x": 3,
169 | "y": 2,
170 | "w": 11,
171 | "h": 14
172 | },
173 | "frame": {
174 | "x": 0,
175 | "y": 98,
176 | "w": 11,
177 | "h": 14
178 | }
179 | },
180 | {
181 | "filename": "walk_down_02",
182 | "rotated": false,
183 | "trimmed": true,
184 | "sourceSize": {
185 | "w": 16,
186 | "h": 16
187 | },
188 | "spriteSourceSize": {
189 | "x": 2,
190 | "y": 3,
191 | "w": 12,
192 | "h": 13
193 | },
194 | "frame": {
195 | "x": 0,
196 | "y": 112,
197 | "w": 12,
198 | "h": 13
199 | }
200 | },
201 | {
202 | "filename": "walk_up_02",
203 | "rotated": false,
204 | "trimmed": true,
205 | "sourceSize": {
206 | "w": 16,
207 | "h": 16
208 | },
209 | "spriteSourceSize": {
210 | "x": 2,
211 | "y": 3,
212 | "w": 12,
213 | "h": 13
214 | },
215 | "frame": {
216 | "x": 11,
217 | "y": 56,
218 | "w": 12,
219 | "h": 13
220 | }
221 | },
222 | {
223 | "filename": "walk_left_02",
224 | "rotated": false,
225 | "trimmed": true,
226 | "sourceSize": {
227 | "w": 16,
228 | "h": 16
229 | },
230 | "spriteSourceSize": {
231 | "x": 3,
232 | "y": 3,
233 | "w": 10,
234 | "h": 13
235 | },
236 | "frame": {
237 | "x": 11,
238 | "y": 69,
239 | "w": 10,
240 | "h": 13
241 | }
242 | },
243 | {
244 | "filename": "walk_right_02",
245 | "rotated": false,
246 | "trimmed": true,
247 | "sourceSize": {
248 | "w": 16,
249 | "h": 16
250 | },
251 | "spriteSourceSize": {
252 | "x": 3,
253 | "y": 3,
254 | "w": 10,
255 | "h": 13
256 | },
257 | "frame": {
258 | "x": 11,
259 | "y": 82,
260 | "w": 10,
261 | "h": 13
262 | }
263 | }
264 | ]
265 | }
266 | ],
267 | "meta": {
268 | "app": "http://github.com/odrick/free-tex-packer-core",
269 | "version": "0.3.4"
270 | }
271 | }
272 |
--------------------------------------------------------------------------------
/src/assets/atlases/generated/npc_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/generated/npc_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/generated/npc_03.json:
--------------------------------------------------------------------------------
1 | {
2 | "textures": [
3 | {
4 | "image": "npc_03.png",
5 | "format": "RGBA8888",
6 | "size": {
7 | "w": 23,
8 | "h": 143
9 | },
10 | "scale": 1,
11 | "frames": [
12 | {
13 | "filename": "walk_down_01",
14 | "rotated": false,
15 | "trimmed": true,
16 | "sourceSize": {
17 | "w": 16,
18 | "h": 16
19 | },
20 | "spriteSourceSize": {
21 | "x": 2,
22 | "y": 0,
23 | "w": 12,
24 | "h": 16
25 | },
26 | "frame": {
27 | "x": 0,
28 | "y": 0,
29 | "w": 12,
30 | "h": 16
31 | }
32 | },
33 | {
34 | "filename": "walk_down_03",
35 | "rotated": false,
36 | "trimmed": true,
37 | "sourceSize": {
38 | "w": 16,
39 | "h": 16
40 | },
41 | "spriteSourceSize": {
42 | "x": 2,
43 | "y": 0,
44 | "w": 12,
45 | "h": 16
46 | },
47 | "frame": {
48 | "x": 0,
49 | "y": 16,
50 | "w": 12,
51 | "h": 16
52 | }
53 | },
54 | {
55 | "filename": "walk_up_01",
56 | "rotated": false,
57 | "trimmed": true,
58 | "sourceSize": {
59 | "w": 16,
60 | "h": 16
61 | },
62 | "spriteSourceSize": {
63 | "x": 2,
64 | "y": 0,
65 | "w": 12,
66 | "h": 16
67 | },
68 | "frame": {
69 | "x": 0,
70 | "y": 32,
71 | "w": 12,
72 | "h": 16
73 | }
74 | },
75 | {
76 | "filename": "walk_up_03",
77 | "rotated": false,
78 | "trimmed": true,
79 | "sourceSize": {
80 | "w": 16,
81 | "h": 16
82 | },
83 | "spriteSourceSize": {
84 | "x": 2,
85 | "y": 0,
86 | "w": 12,
87 | "h": 16
88 | },
89 | "frame": {
90 | "x": 0,
91 | "y": 48,
92 | "w": 12,
93 | "h": 16
94 | }
95 | },
96 | {
97 | "filename": "walk_left_01",
98 | "rotated": false,
99 | "trimmed": true,
100 | "sourceSize": {
101 | "w": 16,
102 | "h": 16
103 | },
104 | "spriteSourceSize": {
105 | "x": 2,
106 | "y": 0,
107 | "w": 11,
108 | "h": 16
109 | },
110 | "frame": {
111 | "x": 0,
112 | "y": 64,
113 | "w": 11,
114 | "h": 16
115 | }
116 | },
117 | {
118 | "filename": "walk_left_03",
119 | "rotated": false,
120 | "trimmed": true,
121 | "sourceSize": {
122 | "w": 16,
123 | "h": 16
124 | },
125 | "spriteSourceSize": {
126 | "x": 2,
127 | "y": 0,
128 | "w": 11,
129 | "h": 16
130 | },
131 | "frame": {
132 | "x": 0,
133 | "y": 80,
134 | "w": 11,
135 | "h": 16
136 | }
137 | },
138 | {
139 | "filename": "walk_right_01",
140 | "rotated": false,
141 | "trimmed": true,
142 | "sourceSize": {
143 | "w": 16,
144 | "h": 16
145 | },
146 | "spriteSourceSize": {
147 | "x": 3,
148 | "y": 0,
149 | "w": 11,
150 | "h": 16
151 | },
152 | "frame": {
153 | "x": 0,
154 | "y": 96,
155 | "w": 11,
156 | "h": 16
157 | }
158 | },
159 | {
160 | "filename": "walk_right_03",
161 | "rotated": false,
162 | "trimmed": true,
163 | "sourceSize": {
164 | "w": 16,
165 | "h": 16
166 | },
167 | "spriteSourceSize": {
168 | "x": 3,
169 | "y": 0,
170 | "w": 11,
171 | "h": 16
172 | },
173 | "frame": {
174 | "x": 0,
175 | "y": 112,
176 | "w": 11,
177 | "h": 16
178 | }
179 | },
180 | {
181 | "filename": "walk_down_02",
182 | "rotated": false,
183 | "trimmed": true,
184 | "sourceSize": {
185 | "w": 16,
186 | "h": 16
187 | },
188 | "spriteSourceSize": {
189 | "x": 2,
190 | "y": 1,
191 | "w": 12,
192 | "h": 15
193 | },
194 | "frame": {
195 | "x": 0,
196 | "y": 128,
197 | "w": 12,
198 | "h": 15
199 | }
200 | },
201 | {
202 | "filename": "walk_up_02",
203 | "rotated": false,
204 | "trimmed": true,
205 | "sourceSize": {
206 | "w": 16,
207 | "h": 16
208 | },
209 | "spriteSourceSize": {
210 | "x": 2,
211 | "y": 1,
212 | "w": 12,
213 | "h": 15
214 | },
215 | "frame": {
216 | "x": 11,
217 | "y": 64,
218 | "w": 12,
219 | "h": 15
220 | }
221 | },
222 | {
223 | "filename": "walk_left_02",
224 | "rotated": false,
225 | "trimmed": true,
226 | "sourceSize": {
227 | "w": 16,
228 | "h": 16
229 | },
230 | "spriteSourceSize": {
231 | "x": 2,
232 | "y": 1,
233 | "w": 11,
234 | "h": 15
235 | },
236 | "frame": {
237 | "x": 11,
238 | "y": 79,
239 | "w": 11,
240 | "h": 15
241 | }
242 | },
243 | {
244 | "filename": "walk_right_02",
245 | "rotated": false,
246 | "trimmed": true,
247 | "sourceSize": {
248 | "w": 16,
249 | "h": 16
250 | },
251 | "spriteSourceSize": {
252 | "x": 3,
253 | "y": 1,
254 | "w": 11,
255 | "h": 15
256 | },
257 | "frame": {
258 | "x": 11,
259 | "y": 94,
260 | "w": 11,
261 | "h": 15
262 | }
263 | }
264 | ]
265 | }
266 | ],
267 | "meta": {
268 | "app": "http://github.com/odrick/free-tex-packer-core",
269 | "version": "0.3.4"
270 | }
271 | }
272 |
--------------------------------------------------------------------------------
/src/assets/atlases/generated/npc_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/generated/npc_03.png
--------------------------------------------------------------------------------
/src/assets/atlases/generated/npc_04.json:
--------------------------------------------------------------------------------
1 | {
2 | "textures": [
3 | {
4 | "image": "npc_04.png",
5 | "format": "RGBA8888",
6 | "size": {
7 | "w": 23,
8 | "h": 125
9 | },
10 | "scale": 1,
11 | "frames": [
12 | {
13 | "filename": "walk_down_01",
14 | "rotated": false,
15 | "trimmed": true,
16 | "sourceSize": {
17 | "w": 16,
18 | "h": 16
19 | },
20 | "spriteSourceSize": {
21 | "x": 2,
22 | "y": 2,
23 | "w": 12,
24 | "h": 14
25 | },
26 | "frame": {
27 | "x": 0,
28 | "y": 0,
29 | "w": 12,
30 | "h": 14
31 | }
32 | },
33 | {
34 | "filename": "walk_down_03",
35 | "rotated": false,
36 | "trimmed": true,
37 | "sourceSize": {
38 | "w": 16,
39 | "h": 16
40 | },
41 | "spriteSourceSize": {
42 | "x": 2,
43 | "y": 2,
44 | "w": 12,
45 | "h": 14
46 | },
47 | "frame": {
48 | "x": 0,
49 | "y": 14,
50 | "w": 12,
51 | "h": 14
52 | }
53 | },
54 | {
55 | "filename": "walk_up_01",
56 | "rotated": false,
57 | "trimmed": true,
58 | "sourceSize": {
59 | "w": 16,
60 | "h": 16
61 | },
62 | "spriteSourceSize": {
63 | "x": 2,
64 | "y": 2,
65 | "w": 12,
66 | "h": 14
67 | },
68 | "frame": {
69 | "x": 0,
70 | "y": 28,
71 | "w": 12,
72 | "h": 14
73 | }
74 | },
75 | {
76 | "filename": "walk_up_03",
77 | "rotated": false,
78 | "trimmed": true,
79 | "sourceSize": {
80 | "w": 16,
81 | "h": 16
82 | },
83 | "spriteSourceSize": {
84 | "x": 2,
85 | "y": 2,
86 | "w": 12,
87 | "h": 14
88 | },
89 | "frame": {
90 | "x": 0,
91 | "y": 42,
92 | "w": 12,
93 | "h": 14
94 | }
95 | },
96 | {
97 | "filename": "walk_left_01",
98 | "rotated": false,
99 | "trimmed": true,
100 | "sourceSize": {
101 | "w": 16,
102 | "h": 16
103 | },
104 | "spriteSourceSize": {
105 | "x": 2,
106 | "y": 2,
107 | "w": 11,
108 | "h": 14
109 | },
110 | "frame": {
111 | "x": 0,
112 | "y": 56,
113 | "w": 11,
114 | "h": 14
115 | }
116 | },
117 | {
118 | "filename": "walk_left_03",
119 | "rotated": false,
120 | "trimmed": true,
121 | "sourceSize": {
122 | "w": 16,
123 | "h": 16
124 | },
125 | "spriteSourceSize": {
126 | "x": 2,
127 | "y": 2,
128 | "w": 11,
129 | "h": 14
130 | },
131 | "frame": {
132 | "x": 0,
133 | "y": 70,
134 | "w": 11,
135 | "h": 14
136 | }
137 | },
138 | {
139 | "filename": "walk_right_01",
140 | "rotated": false,
141 | "trimmed": true,
142 | "sourceSize": {
143 | "w": 16,
144 | "h": 16
145 | },
146 | "spriteSourceSize": {
147 | "x": 3,
148 | "y": 2,
149 | "w": 11,
150 | "h": 14
151 | },
152 | "frame": {
153 | "x": 0,
154 | "y": 84,
155 | "w": 11,
156 | "h": 14
157 | }
158 | },
159 | {
160 | "filename": "walk_right_03",
161 | "rotated": false,
162 | "trimmed": true,
163 | "sourceSize": {
164 | "w": 16,
165 | "h": 16
166 | },
167 | "spriteSourceSize": {
168 | "x": 3,
169 | "y": 2,
170 | "w": 11,
171 | "h": 14
172 | },
173 | "frame": {
174 | "x": 0,
175 | "y": 98,
176 | "w": 11,
177 | "h": 14
178 | }
179 | },
180 | {
181 | "filename": "walk_down_02",
182 | "rotated": false,
183 | "trimmed": true,
184 | "sourceSize": {
185 | "w": 16,
186 | "h": 16
187 | },
188 | "spriteSourceSize": {
189 | "x": 2,
190 | "y": 3,
191 | "w": 12,
192 | "h": 13
193 | },
194 | "frame": {
195 | "x": 0,
196 | "y": 112,
197 | "w": 12,
198 | "h": 13
199 | }
200 | },
201 | {
202 | "filename": "walk_up_02",
203 | "rotated": false,
204 | "trimmed": true,
205 | "sourceSize": {
206 | "w": 16,
207 | "h": 16
208 | },
209 | "spriteSourceSize": {
210 | "x": 2,
211 | "y": 3,
212 | "w": 12,
213 | "h": 13
214 | },
215 | "frame": {
216 | "x": 11,
217 | "y": 56,
218 | "w": 12,
219 | "h": 13
220 | }
221 | },
222 | {
223 | "filename": "walk_left_02",
224 | "rotated": false,
225 | "trimmed": true,
226 | "sourceSize": {
227 | "w": 16,
228 | "h": 16
229 | },
230 | "spriteSourceSize": {
231 | "x": 3,
232 | "y": 3,
233 | "w": 9,
234 | "h": 13
235 | },
236 | "frame": {
237 | "x": 11,
238 | "y": 69,
239 | "w": 9,
240 | "h": 13
241 | }
242 | },
243 | {
244 | "filename": "walk_right_02",
245 | "rotated": false,
246 | "trimmed": true,
247 | "sourceSize": {
248 | "w": 16,
249 | "h": 16
250 | },
251 | "spriteSourceSize": {
252 | "x": 4,
253 | "y": 3,
254 | "w": 9,
255 | "h": 13
256 | },
257 | "frame": {
258 | "x": 11,
259 | "y": 82,
260 | "w": 9,
261 | "h": 13
262 | }
263 | }
264 | ]
265 | }
266 | ],
267 | "meta": {
268 | "app": "http://github.com/odrick/free-tex-packer-core",
269 | "version": "0.3.4"
270 | }
271 | }
272 |
--------------------------------------------------------------------------------
/src/assets/atlases/generated/npc_04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/generated/npc_04.png
--------------------------------------------------------------------------------
/src/assets/atlases/generated/npc_05.json:
--------------------------------------------------------------------------------
1 | {
2 | "textures": [
3 | {
4 | "image": "npc_05.png",
5 | "format": "RGBA8888",
6 | "size": {
7 | "w": 23,
8 | "h": 134
9 | },
10 | "scale": 1,
11 | "frames": [
12 | {
13 | "filename": "walk_down_01",
14 | "rotated": false,
15 | "trimmed": true,
16 | "sourceSize": {
17 | "w": 16,
18 | "h": 16
19 | },
20 | "spriteSourceSize": {
21 | "x": 2,
22 | "y": 1,
23 | "w": 12,
24 | "h": 15
25 | },
26 | "frame": {
27 | "x": 0,
28 | "y": 0,
29 | "w": 12,
30 | "h": 15
31 | }
32 | },
33 | {
34 | "filename": "walk_down_03",
35 | "rotated": false,
36 | "trimmed": true,
37 | "sourceSize": {
38 | "w": 16,
39 | "h": 16
40 | },
41 | "spriteSourceSize": {
42 | "x": 2,
43 | "y": 1,
44 | "w": 12,
45 | "h": 15
46 | },
47 | "frame": {
48 | "x": 0,
49 | "y": 15,
50 | "w": 12,
51 | "h": 15
52 | }
53 | },
54 | {
55 | "filename": "walk_up_01",
56 | "rotated": false,
57 | "trimmed": true,
58 | "sourceSize": {
59 | "w": 16,
60 | "h": 16
61 | },
62 | "spriteSourceSize": {
63 | "x": 2,
64 | "y": 1,
65 | "w": 12,
66 | "h": 15
67 | },
68 | "frame": {
69 | "x": 0,
70 | "y": 30,
71 | "w": 12,
72 | "h": 15
73 | }
74 | },
75 | {
76 | "filename": "walk_up_03",
77 | "rotated": false,
78 | "trimmed": true,
79 | "sourceSize": {
80 | "w": 16,
81 | "h": 16
82 | },
83 | "spriteSourceSize": {
84 | "x": 2,
85 | "y": 1,
86 | "w": 12,
87 | "h": 15
88 | },
89 | "frame": {
90 | "x": 0,
91 | "y": 45,
92 | "w": 12,
93 | "h": 15
94 | }
95 | },
96 | {
97 | "filename": "walk_left_01",
98 | "rotated": false,
99 | "trimmed": true,
100 | "sourceSize": {
101 | "w": 16,
102 | "h": 16
103 | },
104 | "spriteSourceSize": {
105 | "x": 2,
106 | "y": 1,
107 | "w": 11,
108 | "h": 15
109 | },
110 | "frame": {
111 | "x": 0,
112 | "y": 60,
113 | "w": 11,
114 | "h": 15
115 | }
116 | },
117 | {
118 | "filename": "walk_left_03",
119 | "rotated": false,
120 | "trimmed": true,
121 | "sourceSize": {
122 | "w": 16,
123 | "h": 16
124 | },
125 | "spriteSourceSize": {
126 | "x": 2,
127 | "y": 1,
128 | "w": 11,
129 | "h": 15
130 | },
131 | "frame": {
132 | "x": 0,
133 | "y": 75,
134 | "w": 11,
135 | "h": 15
136 | }
137 | },
138 | {
139 | "filename": "walk_right_01",
140 | "rotated": false,
141 | "trimmed": true,
142 | "sourceSize": {
143 | "w": 16,
144 | "h": 16
145 | },
146 | "spriteSourceSize": {
147 | "x": 3,
148 | "y": 1,
149 | "w": 11,
150 | "h": 15
151 | },
152 | "frame": {
153 | "x": 0,
154 | "y": 90,
155 | "w": 11,
156 | "h": 15
157 | }
158 | },
159 | {
160 | "filename": "walk_right_03",
161 | "rotated": false,
162 | "trimmed": true,
163 | "sourceSize": {
164 | "w": 16,
165 | "h": 16
166 | },
167 | "spriteSourceSize": {
168 | "x": 3,
169 | "y": 1,
170 | "w": 11,
171 | "h": 15
172 | },
173 | "frame": {
174 | "x": 0,
175 | "y": 105,
176 | "w": 11,
177 | "h": 15
178 | }
179 | },
180 | {
181 | "filename": "walk_down_02",
182 | "rotated": false,
183 | "trimmed": true,
184 | "sourceSize": {
185 | "w": 16,
186 | "h": 16
187 | },
188 | "spriteSourceSize": {
189 | "x": 2,
190 | "y": 2,
191 | "w": 12,
192 | "h": 14
193 | },
194 | "frame": {
195 | "x": 0,
196 | "y": 120,
197 | "w": 12,
198 | "h": 14
199 | }
200 | },
201 | {
202 | "filename": "walk_up_02",
203 | "rotated": false,
204 | "trimmed": true,
205 | "sourceSize": {
206 | "w": 16,
207 | "h": 16
208 | },
209 | "spriteSourceSize": {
210 | "x": 2,
211 | "y": 2,
212 | "w": 12,
213 | "h": 14
214 | },
215 | "frame": {
216 | "x": 11,
217 | "y": 60,
218 | "w": 12,
219 | "h": 14
220 | }
221 | },
222 | {
223 | "filename": "walk_left_02",
224 | "rotated": false,
225 | "trimmed": true,
226 | "sourceSize": {
227 | "w": 16,
228 | "h": 16
229 | },
230 | "spriteSourceSize": {
231 | "x": 3,
232 | "y": 2,
233 | "w": 10,
234 | "h": 14
235 | },
236 | "frame": {
237 | "x": 11,
238 | "y": 74,
239 | "w": 10,
240 | "h": 14
241 | }
242 | },
243 | {
244 | "filename": "walk_right_02",
245 | "rotated": false,
246 | "trimmed": true,
247 | "sourceSize": {
248 | "w": 16,
249 | "h": 16
250 | },
251 | "spriteSourceSize": {
252 | "x": 3,
253 | "y": 2,
254 | "w": 10,
255 | "h": 14
256 | },
257 | "frame": {
258 | "x": 11,
259 | "y": 88,
260 | "w": 10,
261 | "h": 14
262 | }
263 | }
264 | ]
265 | }
266 | ],
267 | "meta": {
268 | "app": "http://github.com/odrick/free-tex-packer-core",
269 | "version": "0.3.4"
270 | }
271 | }
272 |
--------------------------------------------------------------------------------
/src/assets/atlases/generated/npc_05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/generated/npc_05.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/coin/coin_idle_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/coin/coin_idle_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/coin/coin_idle_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/coin/coin_idle_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/enemy/walk_down_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/enemy/walk_down_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/enemy/walk_down_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/enemy/walk_down_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/enemy/walk_down_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/enemy/walk_down_03.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/enemy/walk_left_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/enemy/walk_left_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/enemy/walk_left_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/enemy/walk_left_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/enemy/walk_left_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/enemy/walk_left_03.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/enemy/walk_right_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/enemy/walk_right_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/enemy/walk_right_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/enemy/walk_right_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/enemy/walk_right_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/enemy/walk_right_03.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/enemy/walk_up_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/enemy/walk_up_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/enemy/walk_up_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/enemy/walk_up_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/enemy/walk_up_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/enemy/walk_up_03.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/hero/walk_down_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/hero/walk_down_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/hero/walk_down_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/hero/walk_down_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/hero/walk_down_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/hero/walk_down_03.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/hero/walk_left_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/hero/walk_left_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/hero/walk_left_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/hero/walk_left_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/hero/walk_left_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/hero/walk_left_03.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/hero/walk_right_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/hero/walk_right_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/hero/walk_right_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/hero/walk_right_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/hero/walk_right_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/hero/walk_right_03.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/hero/walk_up_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/hero/walk_up_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/hero/walk_up_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/hero/walk_up_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/hero/walk_up_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/hero/walk_up_03.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_01/walk_down_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_01/walk_down_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_01/walk_down_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_01/walk_down_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_01/walk_down_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_01/walk_down_03.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_01/walk_left_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_01/walk_left_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_01/walk_left_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_01/walk_left_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_01/walk_left_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_01/walk_left_03.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_01/walk_right_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_01/walk_right_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_01/walk_right_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_01/walk_right_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_01/walk_right_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_01/walk_right_03.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_01/walk_up_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_01/walk_up_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_01/walk_up_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_01/walk_up_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_01/walk_up_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_01/walk_up_03.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_02/walk_down_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_02/walk_down_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_02/walk_down_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_02/walk_down_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_02/walk_down_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_02/walk_down_03.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_02/walk_left_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_02/walk_left_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_02/walk_left_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_02/walk_left_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_02/walk_left_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_02/walk_left_03.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_02/walk_right_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_02/walk_right_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_02/walk_right_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_02/walk_right_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_02/walk_right_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_02/walk_right_03.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_02/walk_up_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_02/walk_up_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_02/walk_up_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_02/walk_up_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_02/walk_up_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_02/walk_up_03.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_03/walk_down_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_03/walk_down_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_03/walk_down_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_03/walk_down_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_03/walk_down_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_03/walk_down_03.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_03/walk_left_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_03/walk_left_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_03/walk_left_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_03/walk_left_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_03/walk_left_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_03/walk_left_03.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_03/walk_right_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_03/walk_right_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_03/walk_right_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_03/walk_right_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_03/walk_right_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_03/walk_right_03.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_03/walk_up_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_03/walk_up_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_03/walk_up_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_03/walk_up_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_03/walk_up_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_03/walk_up_03.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_04/walk_down_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_04/walk_down_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_04/walk_down_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_04/walk_down_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_04/walk_down_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_04/walk_down_03.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_04/walk_left_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_04/walk_left_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_04/walk_left_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_04/walk_left_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_04/walk_left_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_04/walk_left_03.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_04/walk_right_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_04/walk_right_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_04/walk_right_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_04/walk_right_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_04/walk_right_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_04/walk_right_03.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_04/walk_up_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_04/walk_up_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_04/walk_up_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_04/walk_up_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_04/walk_up_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_04/walk_up_03.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_05/walk_down_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_05/walk_down_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_05/walk_down_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_05/walk_down_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_05/walk_down_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_05/walk_down_03.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_05/walk_left_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_05/walk_left_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_05/walk_left_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_05/walk_left_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_05/walk_left_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_05/walk_left_03.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_05/walk_right_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_05/walk_right_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_05/walk_right_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_05/walk_right_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_05/walk_right_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_05/walk_right_03.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_05/walk_up_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_05/walk_up_01.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_05/walk_up_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_05/walk_up_02.png
--------------------------------------------------------------------------------
/src/assets/atlases/sources/npc_05/walk_up_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/atlases/sources/npc_05/walk_up_03.png
--------------------------------------------------------------------------------
/src/assets/fonts/Munro-Narrow.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/fonts/Munro-Narrow.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Munro-Small.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/fonts/Munro-Small.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Munro.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/fonts/Munro.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/PressStart2P-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/fonts/PressStart2P-Regular.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/press-start-medium-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/fonts/press-start-medium-white.png
--------------------------------------------------------------------------------
/src/assets/fonts/press-start-medium-white.xml:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/src/assets/fonts/press-start-normal-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/fonts/press-start-normal-white.png
--------------------------------------------------------------------------------
/src/assets/fonts/press-start-small-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/fonts/press-start-small-white.png
--------------------------------------------------------------------------------
/src/assets/fonts/press-start-small-white.xml:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/src/assets/images/a_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/images/a_button.png
--------------------------------------------------------------------------------
/src/assets/images/b_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/images/b_button.png
--------------------------------------------------------------------------------
/src/assets/images/background_desert.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/images/background_desert.png
--------------------------------------------------------------------------------
/src/assets/images/background_fall.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/images/background_fall.png
--------------------------------------------------------------------------------
/src/assets/images/background_forest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/images/background_forest.png
--------------------------------------------------------------------------------
/src/assets/images/background_grass.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/images/background_grass.png
--------------------------------------------------------------------------------
/src/assets/images/background_winter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/images/background_winter.png
--------------------------------------------------------------------------------
/src/assets/images/crystal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/images/crystal.png
--------------------------------------------------------------------------------
/src/assets/images/d_pad_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/images/d_pad_button.png
--------------------------------------------------------------------------------
/src/assets/images/enemy_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/images/enemy_01.png
--------------------------------------------------------------------------------
/src/assets/images/enemy_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/images/enemy_02.png
--------------------------------------------------------------------------------
/src/assets/images/enemy_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/images/enemy_03.png
--------------------------------------------------------------------------------
/src/assets/images/heart_empty.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/images/heart_empty.png
--------------------------------------------------------------------------------
/src/assets/images/heart_full.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/images/heart_full.png
--------------------------------------------------------------------------------
/src/assets/images/heart_half.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/images/heart_half.png
--------------------------------------------------------------------------------
/src/assets/images/key.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/images/key.png
--------------------------------------------------------------------------------
/src/assets/tilesets/city.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/tilesets/city.png
--------------------------------------------------------------------------------
/src/assets/tilesets/objects.json:
--------------------------------------------------------------------------------
1 | {
2 | "columns": 10,
3 | "image": "objects.png",
4 | "imageheight": 80,
5 | "imagewidth": 160,
6 | "margin": 0,
7 | "name": "objects",
8 | "spacing": 0,
9 | "tilecount": 50,
10 | "tiledversion": "1.8.2",
11 | "tileheight": 16,
12 | "tilewidth": 16,
13 | "type": "tileset",
14 | "version": "1.6"
15 | }
16 |
--------------------------------------------------------------------------------
/src/assets/tilesets/objects.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/tilesets/objects.png
--------------------------------------------------------------------------------
/src/assets/tilesets/village.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blopa/top-down-react-phaser-game-template/2fd10e2edcb974ca72128cc493033dbae16a5795/src/assets/tilesets/village.png
--------------------------------------------------------------------------------
/src/components/DialogBox/DialogBox.jsx:
--------------------------------------------------------------------------------
1 | // Components
2 | import MessageBox from '../MessageBox';
3 |
4 | // Styles
5 | import styles from './DialogBox.module.scss';
6 |
7 | function DialogBox({ show = false }) {
8 | return (
9 |
16 | );
17 | }
18 |
19 | export default DialogBox;
20 |
--------------------------------------------------------------------------------
/src/components/DialogBox/DialogBox.module.scss:
--------------------------------------------------------------------------------
1 | .dialog-window {
2 | image-rendering: pixelated;
3 | font-family: "Press Start 2P", serif;
4 | text-transform: uppercase;
5 | background-color: #e2b27e;
6 | border: solid;
7 | position: absolute;
8 | bottom: 5%;
9 | width: 80%;
10 | left: 50%;
11 | transform: translate(-50%, 0%);
12 | padding: calc(var(--game-zoom) * 8px);
13 | min-height: calc((var(--game-height) / 4) * var(--game-zoom) * 1px);
14 | max-height: calc((var(--game-height) / 3) * var(--game-zoom) * 1px);
15 | }
16 |
17 | .dialog-title {
18 | font-weight: bold;
19 | font-size: calc(var(--game-zoom) * 8px);
20 | margin-bottom: calc(var(--game-zoom) * 6px);
21 | }
22 |
23 | .dialog-footer {
24 | cursor: pointer;
25 | text-align: end;
26 | position: absolute;
27 | right: calc(var(--game-zoom) * 6px);
28 | bottom: calc(var(--game-zoom) * 6px);
29 | font-size: calc(var(--game-zoom) * 8px);
30 | }
--------------------------------------------------------------------------------
/src/components/GameMenu/GameMenu.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import classNames from 'classnames';
3 | import { FormattedMessage } from 'react-intl';
4 |
5 | // Store
6 | import { useGameStore } from '../../zustand/store';
7 |
8 | // Constants
9 | import { ARROW_DOWN_KEY, ARROW_UP_KEY, ENTER_KEY } from '../../constants';
10 |
11 | // Selectors
12 | import {
13 | selectMenuItems,
14 | selectMenuOnSelect,
15 | selectMenuPosition,
16 | } from '../../zustand/menu/selectMenu';
17 |
18 | // Utils
19 | import { getTranslationVariables } from '../../utils/utils';
20 |
21 | // Styles
22 | import styles from './GameMenu.module.scss';
23 |
24 | function GameMenu() {
25 | // Menu
26 | const position = useGameStore(selectMenuPosition);
27 | const items = useGameStore(selectMenuItems);
28 | const onSelected = useGameStore(selectMenuOnSelect);
29 |
30 | const [selectedItemIndex, setSelectedItemIndex] = useState(0);
31 |
32 | useEffect(() => {
33 | const handleKeyPressed = (e) => {
34 | switch (e.code) {
35 | case ENTER_KEY: {
36 | onSelected(items[selectedItemIndex]);
37 | break;
38 | }
39 |
40 | case ARROW_UP_KEY: {
41 | if (selectedItemIndex > 0) {
42 | setSelectedItemIndex(
43 | selectedItemIndex - 1
44 | );
45 | }
46 |
47 | break;
48 | }
49 |
50 | case ARROW_DOWN_KEY: {
51 | if (items.length - 1 > selectedItemIndex) {
52 | setSelectedItemIndex(
53 | selectedItemIndex + 1
54 | );
55 | }
56 |
57 | break;
58 | }
59 |
60 | default: {
61 | break;
62 | }
63 | }
64 | };
65 | window.addEventListener('keydown', handleKeyPressed);
66 |
67 | return () => window.removeEventListener('keydown', handleKeyPressed);
68 | }, [items, onSelected, selectedItemIndex]);
69 |
70 | return (
71 |
78 |
79 | {items.map((item, index) => {
80 | const [key, variables] = getTranslationVariables(item);
81 |
82 | return (
83 | // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
84 | - {
90 | setSelectedItemIndex(index);
91 | }}
92 | onClick={() => {
93 | onSelected(key, item);
94 | }}
95 | >
96 |
100 |
101 | );
102 | })}
103 |
104 |
105 | );
106 | }
107 |
108 | export default GameMenu;
109 |
--------------------------------------------------------------------------------
/src/components/GameMenu/GameMenu.module.scss:
--------------------------------------------------------------------------------
1 | .menu-wrapper {
2 | font-family: "Press Start 2P", serif;
3 | font-size: calc(var(--game-zoom) * 10px);
4 | text-transform: uppercase;
5 | position: absolute;
6 | transform: translate(-50%, 0%);
7 |
8 | &.position-center {
9 | min-width: calc(var(--game-zoom) * 160px);
10 | left: 50%;
11 | top: calc((var(--game-height) * var(--game-zoom)) / 2 * 1px);
12 | }
13 |
14 | &.position-left {
15 | //left: calc(((var(--game-zoom) * 95) + ((100vw - (var(--game-width) * var(--game-zoom))) / 2)) * 1px);
16 | }
17 |
18 | &.position-right {
19 | // TODO
20 | }
21 | }
22 |
23 | .menu-items-wrapper {
24 | text-align: center;
25 | padding: 0;
26 | }
27 |
28 | .menu-item {
29 | cursor: pointer;
30 | list-style: none;
31 | background-color: #94785c;
32 | padding: calc(var(--game-zoom) * 5px);
33 | margin-bottom: calc(var(--game-zoom) * 5px);
34 | border: calc(var(--game-zoom) * 1px) solid #79584f;
35 | }
36 |
37 | .selected-menu-item {
38 | font-size: calc(var(--game-zoom) * 11px);
39 | border: calc(var(--game-zoom) * 1px) solid #ddd;
40 | }
--------------------------------------------------------------------------------
/src/components/GameText/GameText.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useMemo, useRef } from 'react';
2 | import { FormattedMessage } from 'react-intl';
3 | import classNames from 'classnames';
4 |
5 | // Selectors
6 | import {
7 | selectGameZoom,
8 | selectGameCanvasElement,
9 | } from '../../zustand/game/selectGameData';
10 |
11 | // Hooks
12 | import useRect from '../../hooks/useRect';
13 |
14 | // Store
15 | import { useGameStore } from '../../zustand/store';
16 |
17 | // Styles
18 | import styles from './GameText.module.scss';
19 |
20 | function GameText({
21 | translationKey,
22 | variables = {},
23 | config = {},
24 | component: Component = 'p',
25 | }) {
26 | // Game
27 | const gameZoom = useGameStore(selectGameZoom);
28 | const canvas = useGameStore(selectGameCanvasElement);
29 | const domRect = useRect(canvas);
30 | const textWrapperRef = useRef(null);
31 |
32 | const {
33 | color = '#FFFFFF',
34 | position = 'center',
35 | top = 0,
36 | size = 10,
37 | } = config;
38 |
39 | const textTop = useMemo(
40 | () => ((domRect?.top || 0) + (top * gameZoom)),
41 | [domRect?.top, gameZoom, top]
42 | );
43 |
44 | const textWidth = useMemo(
45 | () => (((domRect?.left || 0) * 2) + (gameZoom * 10)),
46 | [domRect?.left, gameZoom]
47 | );
48 |
49 | useEffect(() => {
50 | if (textWrapperRef.current) {
51 | textWrapperRef.current.style.setProperty('--game-text-align', position);
52 | textWrapperRef.current.style.setProperty('--game-text-color', color);
53 | textWrapperRef.current.style.setProperty('--game-text-size', size);
54 | textWrapperRef.current.style.setProperty('--game-text-top', textTop);
55 | textWrapperRef.current.style.setProperty('--game-text-width', textWidth);
56 | }
57 | }, [
58 | textWidth,
59 | position,
60 | textTop,
61 | color,
62 | size,
63 | ]);
64 |
65 | return (
66 |
72 |
73 |
77 |
78 |
79 | );
80 | }
81 |
82 | export default GameText;
83 |
--------------------------------------------------------------------------------
/src/components/GameText/GameText.module.scss:
--------------------------------------------------------------------------------
1 | .text-wrapper {
2 | user-select: none;
3 | user-drag: none;
4 | position: absolute;
5 | text-align: var(--game-text-align);
6 | transform: translate(-50%, 0%);
7 | top: calc(var(--game-text-top) * 1px);
8 | width: calc(100% - (var(--game-text-width) * 1px));
9 | }
10 |
11 | .text-position-wrapper {
12 | &.text-center {
13 | left: 50%;
14 | }
15 | }
16 |
17 | .text {
18 | font-size: calc(var(--game-zoom) * var(--game-text-size) * 1px);
19 | font-family: "Press Start 2P", serif;
20 | color: var(--game-text-color);
21 | text-transform: uppercase;
22 | margin: 0;
23 | }
--------------------------------------------------------------------------------
/src/components/Message/Message.jsx:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react';
2 | import { animated, useTransition } from '@react-spring/web';
3 |
4 | // Styles
5 | import styles from './Message.module.scss';
6 |
7 | function Message({
8 | message = '',
9 | trail = 35,
10 | onMessageEnded = () => {},
11 | forceShowFullMessage = false,
12 | }) {
13 | const items = useMemo(
14 | () => [...message.trim()].map((letter, index) => ({
15 | item: letter,
16 | key: index,
17 | })),
18 | [message]
19 | );
20 |
21 | const transitions = useTransition(items, {
22 | trail,
23 | from: { display: 'none' },
24 | enter: { display: '' },
25 | onRest: (status, controller, item) => {
26 | if (item.key === items.length - 1) {
27 | onMessageEnded();
28 | }
29 | },
30 | });
31 |
32 | return (
33 |
34 | {forceShowFullMessage && (
35 |
{message}
36 | )}
37 |
38 | {!forceShowFullMessage && transitions((animatedStyles, { item, key }) => (
39 |
40 | {item}
41 |
42 | ))}
43 |
44 | );
45 | }
46 |
47 | export default Message;
48 |
--------------------------------------------------------------------------------
/src/components/Message/Message.module.scss:
--------------------------------------------------------------------------------
1 | .dialog-message {
2 | text-transform: uppercase;
3 | font-family: "Press Start 2P", serif;
4 | font-size: calc(var(--game-zoom) * 6px);
5 | line-height: calc(var(--game-zoom) * 10px);
6 | }
7 |
--------------------------------------------------------------------------------
/src/components/MessageBox.jsx:
--------------------------------------------------------------------------------
1 | import { animated, useSpring } from '@react-spring/web';
2 | import { useCallback, useEffect, useState } from 'react';
3 | import { useIntl } from 'react-intl';
4 |
5 | // Components
6 | import Message from './Message/Message';
7 |
8 | // Selectors
9 | import {
10 | selectDialogAction,
11 | selectDialogMessages,
12 | selectDialogCharacterName,
13 | } from '../zustand/dialog/selectDialog';
14 | import { selectGameHeight, selectGameZoom } from '../zustand/game/selectGameData';
15 |
16 | // Constants
17 | import { ENTER_KEY, ESCAPE_KEY, SPACE_KEY } from '../constants';
18 |
19 | // Store
20 | import { useGameStore } from '../zustand/store';
21 |
22 | function MessageBox({
23 | showNext = false,
24 | dialogWindowClassname = null,
25 | dialogTitleClassname = null,
26 | dialogFooterClassname = null,
27 | show = false,
28 | }) {
29 | const intl = useIntl();
30 | const gameZoom = useGameStore(selectGameZoom);
31 | const gameHeight = useGameStore(selectGameHeight);
32 | const dialogAction = useGameStore(selectDialogAction);
33 | const dialogMessages = useGameStore(selectDialogMessages);
34 | const characterName = useGameStore(selectDialogCharacterName);
35 |
36 | const [currentMessage, setCurrentMessage] = useState(0);
37 | const [messageEnded, setMessageEnded] = useState(false);
38 | const [forceShowFullMessage, setForceShowFullMessage] = useState(false);
39 | const [shouldShowMessage, setShouldShowMessage] = useState(false);
40 | const dialogDone = currentMessage === dialogMessages.length - 1 && messageEnded;
41 |
42 | const springOnRestCallback = useCallback(({ finished }) => {
43 | setShouldShowMessage(finished && show);
44 | }, [show]);
45 |
46 | const animatedStyles = useSpring({
47 | config: { duration: 250 },
48 | from: { transform: `translate(-50%, ${gameHeight * gameZoom}px)` },
49 | to: { transform: show ? 'translate(-50%, 0%)' : `translate(-50%, ${gameHeight * gameZoom}px)` },
50 | onRest: springOnRestCallback,
51 | });
52 |
53 | useEffect(() => {
54 | if (!show) {
55 | setCurrentMessage(0);
56 | setMessageEnded(false);
57 | setShouldShowMessage(false);
58 | setForceShowFullMessage(false);
59 | }
60 | }, [show]);
61 |
62 | const handleClick = useCallback(() => {
63 | if (!shouldShowMessage) {
64 | return;
65 | }
66 |
67 | if (messageEnded) {
68 | setMessageEnded(false);
69 | setForceShowFullMessage(false);
70 | if (currentMessage < dialogMessages.length - 1) {
71 | setCurrentMessage(currentMessage + 1);
72 | } else {
73 | setCurrentMessage(0);
74 | dialogAction?.();
75 | }
76 | } else {
77 | setMessageEnded(true);
78 | setForceShowFullMessage(true);
79 | }
80 | }, [shouldShowMessage, messageEnded, currentMessage, dialogMessages.length, dialogAction]);
81 |
82 | useEffect(() => {
83 | const handleKeyPressed = (e) => {
84 | if ([ENTER_KEY, SPACE_KEY, ESCAPE_KEY].includes(e.code)) {
85 | handleClick();
86 | }
87 | };
88 | window.addEventListener('keydown', handleKeyPressed);
89 |
90 | return () => window.removeEventListener('keydown', handleKeyPressed);
91 | }, [dialogMessages.length, handleClick]);
92 |
93 | return (
94 |
95 |
96 | {characterName}
97 |
98 | {shouldShowMessage && (
99 | {
104 | setMessageEnded(true);
105 | }}
106 | />
107 | )}
108 | {showNext && (
109 |
113 | {dialogDone ? intl.formatMessage({ id: 'ok' }) : intl.formatMessage({ id: 'next' })}
114 |
115 | )}
116 |
117 | );
118 | }
119 |
120 | export default MessageBox;
121 |
--------------------------------------------------------------------------------
/src/components/ReactWrapper.jsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useMemo, useState } from 'react';
2 | import { useResizeObserver } from 'beautiful-react-hooks';
3 |
4 | // Store
5 | import { useGameStore } from '../zustand/store';
6 |
7 | // Constants
8 | import { OVERLAY_DIV_RESIZE_THRESHOLD } from '../constants';
9 |
10 | // Hooks
11 | import useMutationObserver from '../hooks/useMutationObserver';
12 |
13 | // Components
14 | import DialogBox from './DialogBox/DialogBox';
15 | import GameMenu from './GameMenu/GameMenu';
16 | import GameText from './GameText/GameText';
17 |
18 | // Selectors
19 | import { selectGameCanvasElement } from '../zustand/game/selectGameData';
20 | import { selectDialogMessages } from '../zustand/dialog/selectDialog';
21 | import { selectMenuItems } from '../zustand/menu/selectMenu';
22 | import { selectTexts } from '../zustand/text/selectText';
23 |
24 | function ReactWrapper() {
25 | const canvas = useGameStore(selectGameCanvasElement);
26 | const dialogMessages = useGameStore(selectDialogMessages);
27 | const menuItems = useGameStore(selectMenuItems);
28 | const gameTexts = useGameStore(selectTexts);
29 | // const s = useGameStore((store) => store);
30 | // console.log(s);
31 | const ref = useMemo(() => ({ current: canvas }), [canvas]);
32 | const DOMRect = useResizeObserver(ref, OVERLAY_DIV_RESIZE_THRESHOLD);
33 |
34 | const [mutatedStyles, setMutatedStyles] = useState({});
35 | const defaultStyles = useMemo(() => ({
36 | // backgroundColor: '#fff',
37 | position: 'absolute',
38 | overflow: 'hidden',
39 | ...DOMRect,
40 | }), [DOMRect]);
41 |
42 | const mutationObserverCallback = useCallback((mutations) => {
43 | const { target } = mutations.at(0);
44 |
45 | setMutatedStyles({
46 | marginLeft: target?.style?.marginLeft,
47 | marginTop: target?.style?.marginTop,
48 | });
49 | }, []);
50 |
51 | useMutationObserver(ref, mutationObserverCallback);
52 |
53 | const inlineStyles = useMemo(() => ({
54 | marginLeft: canvas?.style?.marginLeft,
55 | marginTop: canvas?.style?.marginTop,
56 | ...defaultStyles,
57 | ...mutatedStyles,
58 | }), [canvas?.style?.marginLeft, canvas?.style?.marginTop, defaultStyles, mutatedStyles]);
59 |
60 | // const handleWrapperClicked = useCallback((event) => {
61 | // const { clientX, clientY } = event;
62 | //
63 | // canvas.dispatchEvent(new Event('click', {
64 | // clientX,
65 | // clientY,
66 | // }));
67 | // }, [canvas]);
68 |
69 | // TODO maybe this is not needed anymore
70 | // console.log(defaultStyles, mutatedStyles);
71 | // console.log(mutatedStyles);
72 |
73 | return (
74 |
79 | 0} />
80 | {menuItems.length > 0 && (
81 |
82 | )}
83 | {gameTexts.length > 0 && gameTexts.map((text) => {
84 | const { key, variables, config } = text;
85 |
86 | return (
87 |
93 | );
94 | })}
95 |
96 | );
97 | }
98 |
99 | export default ReactWrapper;
100 |
--------------------------------------------------------------------------------
/src/components/VirtualGamepad/VirtualGamepad.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-unresolved, import/no-webpack-loader-syntax */
2 | import { useCallback, useEffect, useRef } from 'react';
3 | import { Geom } from 'phaser';
4 | import classNames from 'classnames';
5 |
6 | // Images
7 | import dPadButton from '../../assets/images/d_pad_button.png';
8 | import aButton from '../../assets/images/a_button.png';
9 | import bButton from '../../assets/images/b_button.png';
10 |
11 | // Utils
12 | import { simulateKeyEvent } from '../../utils/utils';
13 |
14 | // Constants
15 | import {
16 | ENTER_KEY,
17 | SPACE_KEY,
18 | ARROW_UP_KEY,
19 | ARROW_DOWN_KEY,
20 | ARROW_LEFT_KEY,
21 | ARROW_RIGHT_KEY,
22 | } from '../../constants';
23 |
24 | // Styles
25 | import styles from './VirtualGamepad.module.scss';
26 |
27 | function VirtualGamepad() {
28 | // TODO redo this with that answer from stackoverflow
29 | // https://stackoverflow.com/a/70192263/4307769
30 |
31 | const dPadLeftRef = useRef(null);
32 | const dPadRightRef = useRef(null);
33 | const dPadUpRef = useRef(null);
34 | const dPadDownRef = useRef(null);
35 | const aButtonRef = useRef(null);
36 | const bButtonRef = useRef(null);
37 | const eventRef = useRef({});
38 |
39 | const wasAButtonClicked = useCallback((x, y) => {
40 | const { width, x: elX, y: elY } = aButtonRef.current.getBoundingClientRect();
41 | const radius = width / 2;
42 | const circle = new Geom.Circle(
43 | // this is needed because Circles have origin set to 0.5
44 | elX + radius,
45 | elY + radius,
46 | radius
47 | );
48 |
49 | return circle.contains(x, y);
50 | }, [aButtonRef]);
51 |
52 | const wasBButtonClicked = useCallback((x, y) => {
53 | const { width, x: elX, y: elY } = bButtonRef.current.getBoundingClientRect();
54 | const radius = width / 2;
55 | const circle = new Geom.Circle(
56 | // this is needed because Circles have origin set to 0.5
57 | elX + radius,
58 | elY + radius,
59 | radius
60 | );
61 |
62 | return circle.contains(x, y);
63 | }, [bButtonRef]);
64 |
65 | const wasLeftButtonClicked = useCallback((x, y) => {
66 | const { left, right, top, bottom, height } = dPadLeftRef.current.getBoundingClientRect();
67 | const polygon = new Geom.Polygon([
68 | { x: left, y: top },
69 | { x: right - 31, y: top },
70 | { x: right, y: top + (height / 2) },
71 | { x: right - 31, y: bottom },
72 | { x: left, y: bottom },
73 | ]);
74 |
75 | return polygon.contains(x, y);
76 | }, [dPadLeftRef]);
77 |
78 | const wasUpButtonClicked = useCallback((x, y) => {
79 | const { left, right, top, bottom, width } = dPadUpRef.current.getBoundingClientRect();
80 | const polygon = new Geom.Polygon([
81 | { x: left, y: top },
82 | { x: right, y: top },
83 | { x: right, y: bottom - 31 },
84 | { x: left + (width / 2), y: bottom },
85 | { x: left, y: bottom - 31 },
86 | ]);
87 |
88 | return polygon.contains(x, y);
89 | }, [dPadUpRef]);
90 |
91 | const wasRightButtonClicked = useCallback((x, y) => {
92 | const { left, right, top, bottom, height } = dPadRightRef.current.getBoundingClientRect();
93 | const polygon = new Geom.Polygon([
94 | { x: left, y: top + (height / 2) },
95 | { x: left + 31, y: top },
96 | { x: right, y: top },
97 | { x: right, y: bottom },
98 | { x: left + 31, y: bottom },
99 | ]);
100 |
101 | return polygon.contains(x, y);
102 | }, [dPadRightRef]);
103 |
104 | const wasDownButtonClicked = useCallback((x, y) => {
105 | const { left, right, top, bottom, width } = dPadDownRef.current.getBoundingClientRect();
106 | const polygon = new Geom.Polygon([
107 | { x: left + (width / 2), y: top },
108 | { x: right, y: top + 31 },
109 | { x: right, y: bottom },
110 | { x: left, y: bottom },
111 | { x: left, y: top + 31 },
112 | ]);
113 |
114 | return polygon.contains(x, y);
115 | }, [dPadDownRef]);
116 |
117 | const getPressedButton = useCallback((x, y) => {
118 | if (wasLeftButtonClicked(x, y)) {
119 | return [ARROW_LEFT_KEY, dPadLeftRef];
120 | }
121 |
122 | if (wasRightButtonClicked(x, y)) {
123 | return [ARROW_RIGHT_KEY, dPadRightRef];
124 | }
125 |
126 | if (wasUpButtonClicked(x, y)) {
127 | return [ARROW_UP_KEY, dPadUpRef];
128 | }
129 |
130 | if (wasDownButtonClicked(x, y)) {
131 | return [ARROW_DOWN_KEY, dPadDownRef];
132 | }
133 |
134 | if (wasAButtonClicked(x, y)) {
135 | return [SPACE_KEY, aButtonRef];
136 | }
137 |
138 | if (wasBButtonClicked(x, y)) {
139 | return [ENTER_KEY, bButtonRef];
140 | }
141 |
142 | return [];
143 | }, [
144 | wasAButtonClicked,
145 | wasBButtonClicked,
146 | wasUpButtonClicked,
147 | wasDownButtonClicked,
148 | wasLeftButtonClicked,
149 | wasRightButtonClicked,
150 | ]);
151 |
152 | const handleButtonPressed = useCallback((event, type) => {
153 | const { x, y } = event;
154 |
155 | const [pressedButton, element] = getPressedButton(x, y);
156 | if (pressedButton && element) {
157 | simulateKeyEvent(pressedButton, type);
158 | if (type === 'down') {
159 | element.current.classList.add(styles['is-touched']);
160 | } else {
161 | element.current.classList.remove(styles['is-touched']);
162 | }
163 | }
164 | }, [getPressedButton]);
165 |
166 | useEffect(() => {
167 | const handlePointerDown = (event) => {
168 | eventRef.current = event;
169 | handleButtonPressed(event, 'down');
170 | };
171 | document.addEventListener('pointerdown', handlePointerDown);
172 |
173 | const handlePointerUp = (event) => {
174 | handleButtonPressed(eventRef.current, 'up');
175 | eventRef.current = {};
176 | };
177 | document.addEventListener('pointerup', handlePointerUp);
178 |
179 | return () => {
180 | window.removeEventListener('pointerdown', handlePointerDown);
181 | window.removeEventListener('pointerup', handlePointerUp);
182 | };
183 | }, [handleButtonPressed]);
184 |
185 | const handleContextMenuCallback = useCallback((event) => {
186 | event.preventDefault();
187 | event.stopPropagation();
188 | return false;
189 | }, []);
190 |
191 | return (
192 |
193 |
194 |

201 |

208 |

215 |

222 |
223 |
224 |

231 |

238 |
239 |
240 | );
241 | }
242 |
243 | export default VirtualGamepad;
244 |
--------------------------------------------------------------------------------
/src/components/VirtualGamepad/VirtualGamepad.module.scss:
--------------------------------------------------------------------------------
1 | .buttons-wrapper {
2 | margin-top: calc(((var(--game-height) * var(--game-zoom)) - (var(--game-zoom) * 100)) * 1px);
3 | padding: 0 calc(var(--game-zoom) * 15px);
4 | justify-content: space-between;
5 | position: relative;
6 | user-select: none;
7 | user-drag: none;
8 | display: flex;
9 | z-index: 10;
10 | }
11 |
12 | .button {
13 | pointer-events: none;
14 | -webkit-touch-callout: none;
15 | user-select: none;
16 | user-drag: none;
17 | &.is-touched {
18 | filter: saturate(300%) brightness(70%);
19 | }
20 | }
21 |
22 | .a-button {
23 | width: calc(var(--game-zoom) * 40px);
24 | height: calc(var(--game-zoom) * 40px);
25 | }
26 |
27 | .b-button {
28 | width: calc(var(--game-zoom) * 40px);
29 | height: calc(var(--game-zoom) * 40px);
30 | }
31 |
32 | .d-pad-wrapper {
33 | width: calc(var(--game-zoom) * 76px);
34 | height: calc(var(--game-zoom) * 76px);
35 | }
36 |
37 | .d-pad-left {
38 | width: calc(var(--game-zoom) * 38px);
39 | height: calc(var(--game-zoom) * 30.5px);
40 | margin-bottom: calc(var(--game-zoom) * -19px);
41 | }
42 |
43 | .d-pad-right {
44 | transform: rotate(180deg);
45 | width: calc(var(--game-zoom) * 38px);
46 | height: calc(var(--game-zoom) * 30.5px);
47 | margin-bottom: calc(var(--game-zoom) * -19px);
48 | }
49 |
50 | .d-pad-up {
51 | transform: rotate(90deg);
52 | width: calc(var(--game-zoom) * 38px);
53 | height: calc(var(--game-zoom) * 30.5px);
54 | margin-left: calc(var(--game-zoom) * -57px);
55 | }
56 |
57 | .d-pad-down {
58 | transform: rotate(270deg);
59 | width: calc(var(--game-zoom) * 38px);
60 | height: calc(var(--game-zoom) * 30.5px);
61 | margin-left: calc(var(--game-zoom) * -38px);
62 | margin-bottom: calc(var(--game-zoom) * -38px);
63 | }
64 |
--------------------------------------------------------------------------------
/src/constants.js:
--------------------------------------------------------------------------------
1 | export const TILE_WIDTH = 16;
2 | export const TILE_HEIGHT = 16;
3 |
4 | export const MIN_GAME_WIDTH = 25 * TILE_WIDTH; // 400
5 | export const MIN_GAME_HEIGHT = 14 * TILE_HEIGHT; // 224
6 |
7 | export const RESIZE_THRESHOLD = 500;
8 | export const RE_RESIZE_THRESHOLD = 10;
9 | export const OVERLAY_DIV_RESIZE_THRESHOLD = RE_RESIZE_THRESHOLD;
10 |
11 | export const HERO_SPRITE_NAME = 'hero';
12 | export const ENEMY_SPRITE_NAME = 'enemy';
13 | export const COIN_SPRITE_NAME = 'coin';
14 | export const HEART_SPRITE_NAME = 'heart';
15 | export const CRYSTAL_SPRITE_NAME = 'crystal';
16 | export const KEY_SPRITE_NAME = 'key';
17 |
18 | // Game Objects Tiled IDs
19 | export const ENEMY = 1;
20 | export const COIN = 2;
21 | export const HEART = 3;
22 | export const CRYSTAL = 4;
23 | export const KEY = 5;
24 | export const DOOR = 6;
25 |
26 | export const IDLE_FRAME = 'walk_position_02';
27 | export const IDLE_FRAME_POSITION_KEY = 'position';
28 |
29 | // Directions
30 | export const RIGHT_DIRECTION = 'right';
31 | export const LEFT_DIRECTION = 'left';
32 | export const UP_DIRECTION = 'up';
33 | export const DOWN_DIRECTION = 'down';
34 |
35 | export const IGNORED_TILESETS = ['objects'];
36 |
37 | // Keys
38 | export const ENTER_KEY = 'Enter';
39 | export const SPACE_KEY = 'Space';
40 | export const ESCAPE_KEY = 'Escape';
41 | export const ARROW_LEFT_KEY = 'ArrowLeft';
42 | export const ARROW_UP_KEY = 'ArrowUp';
43 | export const ARROW_RIGHT_KEY = 'ArrowRight';
44 | export const ARROW_DOWN_KEY = 'ArrowDown';
45 |
46 | // DOM identifiers
47 | export const GAME_CONTENT_ID = 'game-content';
48 |
49 | export const BOOT_SCENE_NAME = 'BootScene';
50 |
51 | export const DEFAULT_LOCALE = 'en';
52 |
--------------------------------------------------------------------------------
/src/game/scenes/BootScene.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import { Scene } from 'phaser';
3 |
4 | // Utils
5 | import { changeScene } from '../../utils/sceneHelpers';
6 |
7 | // TODO move this somewhere else
8 | import backgroundDesert from '../../assets/images/background_desert.png';
9 | import backgroundFall from '../../assets/images/background_fall.png';
10 | import backgroundForest from '../../assets/images/background_forest.png';
11 | import backgroundGrass from '../../assets/images/background_grass.png';
12 | import backgroundWinter from '../../assets/images/background_winter.png';
13 | import enemy01 from '../../assets/images/enemy_01.png';
14 | import enemy02 from '../../assets/images/enemy_02.png';
15 | import enemy03 from '../../assets/images/enemy_03.png';
16 |
17 | export default class BootScene extends Scene {
18 | constructor() {
19 | super('BootScene');
20 | }
21 |
22 | preload() {
23 | // Preload assets for the splash and title screens
24 | this.load.image('background_desert', backgroundDesert);
25 | this.load.image('background_fall', backgroundFall);
26 | this.load.image('background_forest', backgroundForest);
27 | this.load.image('background_grass', backgroundGrass);
28 | this.load.image('background_winter', backgroundWinter);
29 |
30 | this.load.image('enemy_01', enemy01);
31 | this.load.image('enemy_02', enemy02);
32 | this.load.image('enemy_03', enemy03);
33 | }
34 |
35 | create() {
36 | changeScene(this, 'MainMenuScene', {
37 | fonts: ['"Press Start 2P"'],
38 | });
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/game/scenes/GameScene.js:
--------------------------------------------------------------------------------
1 | // Utils
2 | import {
3 | handleCreateMap,
4 | handleCreateHero,
5 | handleObjectsLayer,
6 | handleHeroMovement,
7 | handleCreateGroups,
8 | handleCreateControls,
9 | handleConfigureCamera,
10 | handleCreateHeroAnimations,
11 | } from '../../utils/sceneHelpers';
12 | import { getSelectorData } from '../../utils/utils';
13 |
14 | // Selectors
15 | import { selectGameSetters } from '../../zustand/game/selectGameData';
16 |
17 | export const key = 'GameScene';
18 |
19 | export const scene = {};
20 |
21 | export function create() {
22 | // scene.input.on('pointerup', (pointer) => {
23 | // console.log('clicky click');
24 | // });
25 | const { addGameCameraSizeUpdateCallback } = getSelectorData(selectGameSetters);
26 |
27 | // All of these functions need to be called in order
28 |
29 | // Create controls
30 | handleCreateControls(scene);
31 |
32 | // Create game groups
33 | handleCreateGroups(scene);
34 |
35 | // Create the map
36 | const customColliders = handleCreateMap(scene);
37 |
38 | // Create hero sprite
39 | handleCreateHero(scene);
40 |
41 | // Load game objects like items, enemies, etc
42 | handleObjectsLayer(scene);
43 |
44 | // Configure the main camera
45 | handleConfigureCamera(scene);
46 | addGameCameraSizeUpdateCallback(() => {
47 | handleConfigureCamera(scene);
48 | });
49 |
50 | // Hero animations
51 | handleCreateHeroAnimations(scene);
52 |
53 | // Handle collisions
54 | scene.physics.add.collider(scene.heroSprite, scene.enemies);
55 | scene.physics.add.collider(scene.heroSprite, customColliders);
56 | }
57 |
58 | export function update(time, delta) {
59 | handleHeroMovement(scene);
60 | scene.heroSprite.update(time, delta);
61 | }
62 |
--------------------------------------------------------------------------------
/src/game/scenes/MainMenuScene.js:
--------------------------------------------------------------------------------
1 | // Constants
2 | import { DOWN_DIRECTION, IDLE_FRAME, IDLE_FRAME_POSITION_KEY } from '../../constants';
3 |
4 | // Utils
5 | import { changeScene } from '../../utils/sceneHelpers';
6 | import { getSelectorData } from '../../utils/utils';
7 |
8 | // Selectors
9 | import { selectHeroSetters } from '../../zustand/hero/selectHeroData';
10 | import { selectMapSetters } from '../../zustand/map/selectMapData';
11 | import { selectMenuSetters } from '../../zustand/menu/selectMenu';
12 |
13 | export const scene = {};
14 |
15 | export const key = 'MainMenuScene';
16 |
17 | export function create() {
18 | const { setMapKey } = getSelectorData(selectMapSetters);
19 | const { setMenuItems, setMenuOnSelect } = getSelectorData(selectMenuSetters);
20 |
21 | setMenuItems(['start_game', 'exit']);
22 | setMenuOnSelect((key, item) => {
23 | if (key === 'start_game') {
24 | handleStartGameSelected();
25 | } else {
26 | setMenuItems([]);
27 | setMenuOnSelect(null);
28 | window.location.reload();
29 | }
30 | });
31 |
32 | const handleStartGameSelected = () => {
33 | setMenuItems([]);
34 | setMenuOnSelect(null);
35 | setMapKey('sample_map');
36 | const {
37 | setHeroPreviousPosition,
38 | setHeroFacingDirection,
39 | setHeroInitialPosition,
40 | setHeroInitialFrame,
41 | } = getSelectorData(selectHeroSetters);
42 |
43 | setHeroFacingDirection(DOWN_DIRECTION);
44 | setHeroInitialFrame(
45 | IDLE_FRAME.replace(IDLE_FRAME_POSITION_KEY, DOWN_DIRECTION)
46 | );
47 | setHeroInitialPosition({ x: 30, y: 42 });
48 | setHeroPreviousPosition({ x: 30, y: 42 });
49 |
50 | changeScene(scene, 'GameScene', {
51 | // fonts: ['"Press Start 2P"'],
52 | atlases: ['hero'],
53 | images: [],
54 | mapKey: 'sample_map',
55 | // mapKey: 'sample_indoor',
56 | });
57 | };
58 | }
59 |
--------------------------------------------------------------------------------
/src/hooks/useMutationObserver.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useMemo } from 'react';
2 |
3 | const useMutationObserver = (
4 | ref,
5 | callback,
6 | options = null
7 | ) => {
8 | const observerOptions = useMemo(() => {
9 | if (options) {
10 | return options;
11 | }
12 |
13 | return {
14 | // characterDataOldValue: true,
15 | // attributeOldValue: true,
16 | // attributeFilter: true,
17 | // characterData: true,
18 | attributes: true,
19 | childList: true,
20 | subtree: true,
21 | };
22 | }, [options]);
23 |
24 | useEffect(() => {
25 | if (ref.current) {
26 | const observer = new MutationObserver(callback);
27 | observer.observe(ref.current, observerOptions);
28 | return () => observer.disconnect();
29 | }
30 |
31 | return () => {};
32 | }, [callback, observerOptions, ref]);
33 | };
34 |
35 | export default useMutationObserver;
36 |
--------------------------------------------------------------------------------
/src/hooks/useRect.jsx:
--------------------------------------------------------------------------------
1 | import { useLayoutEffect, useCallback, useState } from 'react';
2 |
3 | // code by https://gist.github.com/morajabi/523d7a642d8c0a2f71fcfa0d8b3d2846
4 | const useRect = (element) => {
5 | const [rect, setRect] = useState(getRect(element || null));
6 |
7 | const handleResize = useCallback(() => {
8 | if (!element) {
9 | return;
10 | }
11 |
12 | // Update client rect
13 | setRect(getRect(element));
14 | }, [element]);
15 |
16 | useLayoutEffect(() => {
17 | if (!element) {
18 | return () => {};
19 | }
20 |
21 | handleResize();
22 | let resizeObserver = new ResizeObserver(() => handleResize());
23 | resizeObserver.observe(element);
24 |
25 | let intersectionObserver = new IntersectionObserver(() => handleResize());
26 | intersectionObserver.observe(element);
27 |
28 | return () => {
29 | if (!resizeObserver) {
30 | return;
31 | }
32 |
33 | intersectionObserver.disconnect();
34 | resizeObserver.disconnect();
35 | resizeObserver = null;
36 | intersectionObserver = null;
37 | };
38 | }, [handleResize, element]);
39 |
40 | return rect;
41 | };
42 |
43 | function getRect(element) {
44 | if (!element) {
45 | return {
46 | bottom: 0,
47 | height: 0,
48 | left: 0,
49 | right: 0,
50 | top: 0,
51 | width: 0,
52 | };
53 | }
54 |
55 | return element.getBoundingClientRect();
56 | }
57 |
58 | export default useRect;
59 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Press Start 2P';
3 | src: url('assets/fonts/PressStart2P-Regular.ttf') format('opentype'),
4 | url('assets/fonts/PressStart2P-Regular.ttf') format('opentype');
5 | font-weight: normal;
6 | font-style: normal;
7 | }
8 |
9 | body {
10 | margin: 0;
11 | background-color: #2d528b;
12 | }
13 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/jsx-filename-extension */
2 | import ReactDOM from 'react-dom/client';
3 | import { ThemeProvider, createTheme } from '@mui/material/styles';
4 | import { Helmet } from 'react-helmet';
5 | import { Fragment } from 'react';
6 |
7 | // Styles
8 | import './index.css';
9 |
10 | // Components
11 | import Game from './Game';
12 |
13 | const theme = createTheme();
14 | const root = ReactDOM.createRoot(document.getElementById('root'));
15 |
16 | root.render(
17 |
18 |
19 | My Phaser Game
20 |
21 |
22 |
23 |
24 |
25 | );
26 |
27 |
--------------------------------------------------------------------------------
/src/intl/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "game_title": "Game Title",
3 | "start_game": "Start Game",
4 | "exit": "Exit",
5 | "rock": "Rock",
6 | "paper": "Paper",
7 | "scissors": "Scissors",
8 | "attack": "Attack",
9 | "defense": "Defense",
10 | "items": "Items",
11 | "next": "Next",
12 | "ok": "Ok",
13 | "return": "Return",
14 | "dice_1": "Dice 1",
15 | "dice_2": "Dice 2",
16 | "dice_3": "Dice 3",
17 | "run": "Run"
18 | }
19 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/src/utils/__tests__/phaser.test.js:
--------------------------------------------------------------------------------
1 | const {
2 | calculateGameSize,
3 | asyncLoader,
4 | prepareScene,
5 | getScenesModules,
6 | } = require('../phaser');
7 |
8 | describe('calculateGameSize', () => {
9 | beforeEach(() => {
10 | window.innerWidth = 500;
11 | window.innerHeight = 500;
12 | });
13 |
14 | it('calculates the game size correctly', () => {
15 | const result = calculateGameSize(400, 300, 10, 20);
16 | expect(result.zoom).toEqual(1);
17 | expect(result.width).toEqual(400);
18 | expect(result.height).toEqual(300);
19 | });
20 |
21 | it('calculates the zoom correctly', () => {
22 | const result = calculateGameSize(1000, 1000, 10, 20);
23 | expect(result.zoom).toEqual(2);
24 | expect(result.width).toEqual(500);
25 | expect(result.height).toEqual(500);
26 | });
27 |
28 | it('uses the threshold values', () => {
29 | const result = calculateGameSize(400, 300, 10, 20, 0.1, 0.2);
30 | expect(result.width).toEqual(440);
31 | expect(result.height).toEqual(360);
32 | });
33 | });
34 |
35 | describe('asyncLoader', () => {
36 | it('resolves with the result', (done) => {
37 | const plugin = {
38 | on: jest.fn((event, callback) => {
39 | if (event === 'filecomplete') {
40 | // eslint-disable-next-line n/no-callback-literal
41 | callback({ name: 'test', type: 'image' });
42 | }
43 | }),
44 | start: jest.fn(),
45 | };
46 | asyncLoader(plugin)
47 | .then((result) => {
48 | expect(result).toEqual({ name: 'test', type: 'image' });
49 | done();
50 | });
51 | });
52 |
53 | it('rejects with an error', (done) => {
54 | const plugin = {
55 | on: jest.fn((event, callback) => {
56 | if (event === 'loaderror') {
57 | callback(new Error('Load error'));
58 | }
59 | }),
60 | start: jest.fn(),
61 | };
62 | asyncLoader(plugin)
63 | .catch((error) => {
64 | expect(error).toEqual(new Error('Load error'));
65 | done();
66 | });
67 | });
68 | });
69 |
70 | describe('prepareScene', () => {
71 | it('should return the default property of a module if it has a prototype', () => {
72 | const defaultExport = () => {};
73 | const module = { default: defaultExport };
74 | const result = prepareScene(module, './testModule.js');
75 |
76 | expect(result).toBe(defaultExport);
77 | });
78 |
79 | it('should return an object with init method and key/name properties if the module does not have a prototype', () => {
80 | const module = {};
81 | const result = prepareScene(module, './testModule.js');
82 |
83 | expect(result.init).toBeDefined();
84 | expect(result.key).toBeDefined();
85 | expect(result.name).toBeDefined();
86 | });
87 |
88 | it('should use the module key property as the key/name if it is defined', () => {
89 | const key = 'testKey';
90 | const module = { key };
91 | const result = prepareScene(module, './testModule.js');
92 |
93 | expect(result.key).toBe(key);
94 | expect(result.name).toBe(key);
95 | });
96 |
97 | it('should use the filename without extension as the key/name if the module key property is not defined', () => {
98 | const module = {};
99 | const result = prepareScene(module, './testModule.js');
100 |
101 | expect(result.key).toBe('testModule');
102 | expect(result.name).toBe('testModule');
103 | });
104 | });
105 |
106 | describe('getScenesModules', () => {
107 | jest.mock('../phaser.js', () => ({
108 | prepareScene: jest.fn(() => ({})),
109 | }));
110 |
111 | it('should return an array of scenes modules', () => {
112 | require.context = jest.fn(() => ({
113 | keys: jest.fn(() => ['./bootScene.js', './testScene1.js', './testScene2.js']),
114 | }));
115 |
116 | const result = getScenesModules();
117 |
118 | expect(result).toHaveLength(3);
119 | });
120 |
121 | it('should call prepareScene for each scene module', () => {
122 | const contextResolver = {
123 | keys: jest.fn(() => [
124 | './bootScene.js',
125 | './testScene1.js',
126 | './testScene2.js',
127 | ]),
128 | };
129 |
130 | require.context = jest.fn(() => contextResolver);
131 | getScenesModules();
132 |
133 | expect(prepareScene).toHaveBeenCalledWith(contextResolver['./bootScene.js'], './bootScene.js');
134 | expect(prepareScene).toHaveBeenCalledWith(contextResolver['./testScene1.js'], './testScene1.js');
135 | expect(prepareScene).toHaveBeenCalledWith(contextResolver['./testScene2.js'], './testScene2.js');
136 | });
137 |
138 | it('should return the boot scene as the first element of the array', () => {
139 | const contextResolver = {
140 | keys: jest.fn(() => [
141 | './bootScene.js',
142 | './testScene1.js',
143 | './testScene2.js',
144 | ]),
145 | };
146 |
147 | require.context = jest.fn(() => contextResolver);
148 |
149 | const result = getScenesModules();
150 |
151 | expect(result[0]).toBeDefined();
152 | expect(prepareScene).toHaveBeenCalledWith(contextResolver['./bootScene.js'], './bootScene.js');
153 | });
154 | });
155 |
--------------------------------------------------------------------------------
/src/utils/phaser.js:
--------------------------------------------------------------------------------
1 | import { getFileNameWithoutExtension, isObject } from './utils';
2 |
3 | // Constants
4 | import { BOOT_SCENE_NAME } from '../constants';
5 |
6 | export const calculateGameSize = (
7 | width,
8 | height,
9 | tileWidth,
10 | tileHeight,
11 | widthThreshold = 0.5,
12 | heightThreshold = 0.5
13 | ) => {
14 | const widthScale = Math.floor(window.innerWidth / width);
15 | const heightScale = Math.floor(window.innerHeight / height);
16 | const zoom = Math.min(widthScale, heightScale) || 1;
17 |
18 | const newWidth = Math.floor(window.innerWidth / tileWidth) * tileWidth / zoom;
19 | const newHeight = Math.floor(window.innerHeight / tileHeight) * tileHeight / zoom;
20 |
21 | return {
22 | zoom,
23 | width: Math.min(newWidth, Math.floor((width * (1 + widthThreshold)) / tileWidth) * tileWidth),
24 | height: Math.min(newHeight, Math.floor((height * (1 + heightThreshold)) / tileHeight) * tileHeight),
25 | };
26 | };
27 |
28 | // Thanks yannick @ https://phaser.discourse.group/t/loading-audio/1306/4
29 | export const asyncLoader = (loaderPlugin) => new Promise((resolve, reject) => {
30 | loaderPlugin.on('filecomplete', resolve).on('loaderror', reject);
31 | loaderPlugin.start();
32 | });
33 |
34 | export const prepareScene = (module, modulePath) => {
35 | if (Object.getOwnPropertyDescriptor(module.default || {}, 'prototype')) {
36 | return module.default;
37 | }
38 |
39 | function init(data) {
40 | // eslint-disable-next-line no-undefined
41 | if (isObject(module.scene)) {
42 | // when this function is called, "this" will be the scene
43 | // eslint-disable-next-line @babel/no-invalid-this
44 | Object.entries(this).forEach(([key, value]) => {
45 | // eslint-disable-next-line no-param-reassign
46 | module.scene[key] = value;
47 | });
48 | }
49 |
50 | module.init?.(data);
51 | }
52 |
53 | // Object.keys(Scene.prototype).forEach((key) => {
54 | // if (!module[key]) {
55 | // throw new Error(`Missing "${key}" method`);
56 | // }
57 | // });
58 |
59 | const key = module.key || getFileNameWithoutExtension(modulePath);
60 | return {
61 | ...module,
62 | name: key,
63 | key,
64 | init,
65 | };
66 | };
67 |
68 | export const getScenesModules = () => {
69 | // automatically import all scenes from the scenes folder
70 | const contextResolver = require.context('../game/scenes/', true, /\.(js|ts)$/);
71 |
72 | const modulePaths = contextResolver.keys();
73 | const bootScene = modulePaths
74 | .find((modulePath) => getFileNameWithoutExtension(modulePath) === BOOT_SCENE_NAME);
75 | const otherScenes = modulePaths
76 | .filter((modulePath) => getFileNameWithoutExtension(modulePath) !== BOOT_SCENE_NAME);
77 |
78 | return [bootScene, ...otherScenes]
79 | .map((modulePath) => prepareScene(contextResolver(modulePath), modulePath));
80 | };
81 |
--------------------------------------------------------------------------------
/src/utils/utils.js:
--------------------------------------------------------------------------------
1 | import { GameObjects } from 'phaser';
2 | import {
3 | ARROW_RIGHT_KEY,
4 | ARROW_DOWN_KEY,
5 | ARROW_LEFT_KEY,
6 | ARROW_UP_KEY,
7 | TILE_HEIGHT,
8 | TILE_WIDTH,
9 | ENTER_KEY,
10 | SPACE_KEY,
11 | } from '../constants';
12 |
13 | // Store
14 | import { getState } from '../zustand/store';
15 |
16 | export const isObject = (obj) =>
17 | typeof obj === 'object' && obj?.constructor === Object;
18 |
19 | export const isObjectEmpty = (obj) =>
20 | isObject(obj) && Object.keys(obj).length === 0;
21 |
22 | export const isObjectNotEmpty = (obj) =>
23 | isObject(obj) && Object.keys(obj).length > 0;
24 |
25 | /**
26 | * source https://gist.github.com/GlauberF/d8278ce3aa592389e6e3d4e758e6a0c2
27 | * Simulate a key event.
28 | * @param {String} code The code of the key to simulate
29 | * @param {String} type (optional) The type of event : down, up or press. The default is down
30 | */
31 | export const simulateKeyEvent = (code, type = 'down') => {
32 | const keysMap = {
33 | [ENTER_KEY]: 13,
34 | [SPACE_KEY]: 32,
35 | [ARROW_LEFT_KEY]: 37,
36 | [ARROW_UP_KEY]: 38,
37 | [ARROW_RIGHT_KEY]: 39,
38 | [ARROW_DOWN_KEY]: 40,
39 | };
40 |
41 | const event = document.createEvent('HTMLEvents');
42 | event.initEvent(`key${type}`, true, false);
43 | event.code = code;
44 | event.keyCode = keysMap[code];
45 |
46 | document.dispatchEvent(event);
47 | };
48 |
49 | export const getTranslationVariables = (item) => {
50 | if (isObject(item)) {
51 | return [item.key, item.variables];
52 | }
53 |
54 | return [item, {}];
55 | };
56 |
57 | export const getSelectorData = (selector) => selector(getState());
58 |
59 | export const createInteractiveGameObject = (
60 | scene,
61 | x,
62 | y,
63 | width,
64 | height,
65 | origin = { x: 0, y: 0 }
66 | ) => {
67 | const customCollider = new GameObjects.Rectangle(
68 | scene,
69 | x,
70 | y,
71 | width,
72 | height
73 | ).setOrigin(origin.x, origin.y);
74 |
75 | scene.physics.add.existing(customCollider);
76 | customCollider.body.setImmovable(true);
77 |
78 | return customCollider;
79 | };
80 |
81 | // Functions to check if a file exists within Webpack modules
82 | // This might look dumb, but due to the way Webpack works, this is the only way to properly check
83 | // Using a full path as a variable doesn't work because: https://github.com/webpack/webpack/issues/6680#issuecomment-370800037
84 | export const isMapFileAvailable = (file) => {
85 | try {
86 | require.resolveWeak(`../assets/maps/${file}`);
87 | return true;
88 | } catch {
89 | console.error(`Error loading file ${file}`);
90 | return false;
91 | }
92 | };
93 |
94 | export const isImageFileAvailable = (file) => {
95 | try {
96 | require.resolveWeak(`../assets/images/${file}`);
97 | return true;
98 | } catch {
99 | console.error(`Error loading file ${file}`);
100 | return false;
101 | }
102 | };
103 |
104 | export const isTilesetFileAvailable = (file) => {
105 | try {
106 | require.resolveWeak(`../assets/tilesets/${file}`);
107 | return true;
108 | } catch {
109 | console.error(`Error loading file ${file}`);
110 | return false;
111 | }
112 | };
113 |
114 | export const isGeneratedAtlasFileAvailable = (file) => {
115 | try {
116 | require.resolveWeak(`../assets/atlases/generated/${file}`);
117 | return true;
118 | } catch {
119 | console.error(`Error loading file ${file}`);
120 | return false;
121 | }
122 | };
123 |
124 | export const getDegreeFromRadians = (radians) => (radians * (180 / Math.PI));
125 |
126 | export const getRadiansFromDegree = (degree) => (degree * (Math.PI / 180));
127 |
128 | export const rotateRectangleInsideTile = (x, y, width, height, degree) => {
129 | switch (degree) {
130 | case 90: {
131 | return [
132 | TILE_HEIGHT - (y + height),
133 | x,
134 | height,
135 | width,
136 | ];
137 | }
138 |
139 | case 180: {
140 | return [
141 | TILE_WIDTH - (x + width),
142 | TILE_HEIGHT - (y + height),
143 | width,
144 | height,
145 | ];
146 | }
147 |
148 | case 270: {
149 | return [
150 | y,
151 | TILE_WIDTH - (x + width),
152 | height,
153 | width,
154 | ];
155 | }
156 |
157 | default: {
158 | return [x, y, width, height];
159 | }
160 | }
161 | };
162 |
163 | export const isDev = () => process.env.NODE_ENV !== 'production';
164 |
165 | export const getFileNameWithoutExtension = (filePath) => filePath.split('/').pop().split('.').shift();
166 |
--------------------------------------------------------------------------------
/src/zustand/assets/selectLoadedAssets.js:
--------------------------------------------------------------------------------
1 | export const selectLoadedFonts = (state) => state.loadedAssets.fonts;
2 |
3 | export const selectLoadedAtlases = (state) => state.loadedAssets.atlases;
4 |
5 | export const selectLoadedImages = (state) => state.loadedAssets.images;
6 |
7 | export const selectLoadedMaps = (state) => state.loadedAssets.maps;
8 |
9 | export const selectLoadedJSONs = (state) => state.loadedAssets.jsons;
10 |
11 | export const selectAssetsSetters = (state) => state.loadedAssets.setters;
12 |
--------------------------------------------------------------------------------
/src/zustand/assets/setLoadedAssets.js:
--------------------------------------------------------------------------------
1 | export default (set) => ({
2 | addLoadedFont: (font) =>
3 | set((state) => ({
4 | ...state,
5 | loadedAssets: {
6 | ...state.loadedAssets,
7 | // TODO make this a Set()
8 | fonts: [...state.loadedAssets.fonts, font],
9 | },
10 | })),
11 | addLoadedAtlas: (atlas) =>
12 | set((state) => ({
13 | ...state,
14 | loadedAssets: {
15 | ...state.loadedAssets,
16 | // TODO make this a Set()
17 | atlases: [...state.loadedAssets.atlases, atlas],
18 | },
19 | })),
20 | addLoadedImage: (image) =>
21 | set((state) => ({
22 | ...state,
23 | loadedAssets: {
24 | ...state.loadedAssets,
25 | // TODO make this a Set()
26 | images: [...state.loadedAssets.images, image],
27 | },
28 | })),
29 | addLoadedMap: (map) =>
30 | set((state) => ({
31 | ...state,
32 | loadedAssets: {
33 | ...state.loadedAssets,
34 | // TODO make this a Set()
35 | maps: [...state.loadedAssets.maps, map],
36 | },
37 | })),
38 | addLoadedJson: (json) =>
39 | set((state) => ({
40 | ...state,
41 | loadedAssets: {
42 | ...state.loadedAssets,
43 | // TODO make this a Set()
44 | jsons: [...state.loadedAssets.jsons, json],
45 | },
46 | })),
47 | });
48 |
--------------------------------------------------------------------------------
/src/zustand/dialog/selectDialog.js:
--------------------------------------------------------------------------------
1 | export const selectDialogMessages = (state) => state.dialog.messages;
2 |
3 | export const selectDialogAction = (state) => state.dialog.action;
4 |
5 | export const selectDialogCharacterName = (state) => state.dialog.characterName;
6 |
7 | export const selectDialogSetters = (state) => state.dialog.setters;
8 |
--------------------------------------------------------------------------------
/src/zustand/dialog/setDialog.js:
--------------------------------------------------------------------------------
1 | export default (set) => ({
2 | setDialogMessages: (messages) =>
3 | set((state) => ({
4 | ...state,
5 | dialog: {
6 | ...state.dialog,
7 | messages,
8 | },
9 | })),
10 | setDialogAction: (action) =>
11 | set((state) => ({
12 | ...state,
13 | dialog: {
14 | ...state.dialog,
15 | action,
16 | },
17 | })),
18 | setDialogCharacterName: (characterName) =>
19 | set((state) => ({
20 | ...state,
21 | dialog: {
22 | ...state.dialog,
23 | characterName,
24 | },
25 | })),
26 | });
27 |
--------------------------------------------------------------------------------
/src/zustand/game/selectGameData.js:
--------------------------------------------------------------------------------
1 | import { GAME_CONTENT_ID } from '../../constants';
2 |
3 | export const selectGameWidth = (state) => state.game.width;
4 |
5 | export const selectGameHeight = (state) => state.game.height;
6 |
7 | export const selectGameZoom = (state) => state.game.zoom;
8 |
9 | export const selectGameCanvasElement = (state) =>
10 | state.game.canvas || document.querySelector(`#${GAME_CONTENT_ID}`)?.firstChild;
11 |
12 | export const selectGameLocale = (state) => state.game.locale;
13 |
14 | export const selectGameCameraSizeUpdateCallbacks = (state) => state.game.cameraSizeUpdateCallbacks;
15 |
16 | export const selectGameSetters = (state) => state.game.setters;
17 |
--------------------------------------------------------------------------------
/src/zustand/game/setGameData.js:
--------------------------------------------------------------------------------
1 | export default (set) => ({
2 | setGameWidth: (width) =>
3 | set((state) => ({
4 | ...state,
5 | game: {
6 | ...state.game,
7 | width,
8 | },
9 | })),
10 | setGameHeight: (height) =>
11 | set((state) => ({
12 | ...state,
13 | game: {
14 | ...state.game,
15 | height,
16 | },
17 | })),
18 | setGameZoom: (zoom) =>
19 | set((state) => ({
20 | ...state,
21 | game: {
22 | ...state.game,
23 | zoom,
24 | },
25 | })),
26 | setGameCanvasElement: (canvas) =>
27 | set((state) => ({
28 | ...state,
29 | game: {
30 | ...state.game,
31 | canvas,
32 | },
33 | })),
34 | addGameCameraSizeUpdateCallback: (cameraSizeUpdateCallback) => {
35 | set((state) => ({
36 | ...state,
37 | game: {
38 | ...state.game,
39 | // TODO make this a Set()
40 | cameraSizeUpdateCallbacks: [...state.game.cameraSizeUpdateCallbacks, cameraSizeUpdateCallback],
41 | },
42 | }));
43 |
44 | return cameraSizeUpdateCallback;
45 | },
46 | setGameLocale: (locale) =>
47 | set((state) => ({
48 | ...state,
49 | game: {
50 | ...state.game,
51 | locale,
52 | },
53 | })),
54 | });
55 |
--------------------------------------------------------------------------------
/src/zustand/hero/selectHeroData.js:
--------------------------------------------------------------------------------
1 | export const selectHeroFacingDirection = (state) => state.heroData.facingDirection;
2 |
3 | export const selectHeroInitialPosition = (state) => state.heroData.initialPosition;
4 |
5 | export const selectHeroPreviousPosition = (state) => state.heroData.previousPosition;
6 |
7 | export const selectHeroInitialFrame = (state) => state.heroData.initialFrame;
8 |
9 | export const selectHeroInventoryDice = (state) => state.heroData.inventory.dice;
10 |
11 | export const selectHeroEquipedInventoryDice = (state) =>
12 | selectHeroInventoryDice(state).filter((die) => die.equiped);
13 |
14 | export const selectHeroSetters = (state) => state.heroData.setters;
15 |
--------------------------------------------------------------------------------
/src/zustand/hero/setHeroData.js:
--------------------------------------------------------------------------------
1 | export default (set) => ({
2 | setHeroFacingDirection: (facingDirection) =>
3 | set((state) => ({
4 | ...state,
5 | heroData: {
6 | ...state.heroData,
7 | facingDirection,
8 | },
9 | })),
10 | setHeroInitialPosition: (initialPosition) =>
11 | set((state) => ({
12 | ...state,
13 | heroData: {
14 | ...state.heroData,
15 | initialPosition,
16 | },
17 | })),
18 | setHeroPreviousPosition: (previousPosition) =>
19 | set((state) => ({
20 | ...state,
21 | heroData: {
22 | ...state.heroData,
23 | previousPosition,
24 | },
25 | })),
26 | setHeroInitialFrame: (initialFrame) =>
27 | set((state) => ({
28 | ...state,
29 | heroData: {
30 | ...state.heroData,
31 | initialFrame,
32 | },
33 | })),
34 | addHeroInventoryDice: (die) =>
35 | set((state) => ({
36 | ...state,
37 | heroData: {
38 | ...state.heroData,
39 | inventory: {
40 | ...state.heroData.inventory,
41 | // TODO make this a Set()
42 | dice: [...state.heroData.inventory.dice, die],
43 | },
44 | },
45 | })),
46 | });
47 |
--------------------------------------------------------------------------------
/src/zustand/map/selectMapData.js:
--------------------------------------------------------------------------------
1 | export const selectMapKey = (state) => state.mapData.mapKey;
2 |
3 | export const selectTilesets = (state) => state.mapData.tilesets;
4 |
5 | export const selectMapSetters = (state) => state.mapData.setters;
6 |
--------------------------------------------------------------------------------
/src/zustand/map/setMapData.js:
--------------------------------------------------------------------------------
1 | export default (set) => ({
2 | setMapKey: (mapKey) =>
3 | set((state) => ({
4 | ...state,
5 | mapData: {
6 | ...state.mapData,
7 | mapKey,
8 | },
9 | })),
10 | addTileset: (tilesets) =>
11 | set((state) => ({
12 | ...state,
13 | mapData: {
14 | ...state.mapData,
15 | // TODO make this a Set()
16 | tilesets: [...state.mapData.tilesets, tilesets],
17 | },
18 | })),
19 | });
20 |
--------------------------------------------------------------------------------
/src/zustand/menu/selectMenu.js:
--------------------------------------------------------------------------------
1 | export const selectMenuItems = (state) => state.menu.items;
2 |
3 | export const selectMenuPosition = (state) => state.menu.position;
4 |
5 | export const selectMenuOnSelect = (state) => state.menu.onSelect;
6 |
7 | export const selectMenuSetters = (state) => state.menu.setters;
8 |
--------------------------------------------------------------------------------
/src/zustand/menu/setMenu.js:
--------------------------------------------------------------------------------
1 | export default (set) => ({
2 | setMenuItems: (items) =>
3 | set((state) => ({
4 | ...state,
5 | menu: {
6 | ...state.menu,
7 | items,
8 | },
9 | })),
10 | setMenuOnSelect: (onSelect) =>
11 | set((state) => ({
12 | ...state,
13 | menu: {
14 | ...state.menu,
15 | onSelect,
16 | },
17 | })),
18 | setMenuPosition: (position) =>
19 | set((state) => ({
20 | ...state,
21 | menu: {
22 | ...state.menu,
23 | position,
24 | },
25 | })),
26 | addMenuItems: (items) =>
27 | set((state) => ({
28 | ...state,
29 | menu: {
30 | ...state.menu,
31 | // TODO make this a Set()
32 | items: [...state.menu.items, ...items],
33 | },
34 | })),
35 | });
36 |
--------------------------------------------------------------------------------
/src/zustand/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, useStore } from 'zustand';
2 |
3 | // Constants
4 | import { MIN_GAME_HEIGHT, MIN_GAME_WIDTH } from '../constants';
5 |
6 | // Setters
7 | import setLoadedAssets from './assets/setLoadedAssets';
8 | import setGameData from './game/setGameData';
9 | import setHeroData from './hero/setHeroData';
10 | import setDialog from './dialog/setDialog';
11 | import setMapData from './map/setMapData';
12 | import setMenu from './menu/setMenu';
13 | import setText from './text/setText';
14 |
15 | // define the store
16 | const store = createStore((set) => ({
17 | loadedAssets: {
18 | fonts: [],
19 | atlases: [],
20 | images: [],
21 | maps: [],
22 | jsons: [],
23 | setters: setLoadedAssets(set),
24 | },
25 | heroData: {
26 | facingDirection: '',
27 | initialPosition: {},
28 | previousPosition: {},
29 | initialFrame: '',
30 | inventory: {
31 | dice: [],
32 | },
33 | setters: setHeroData(set),
34 | },
35 | mapData: {
36 | mapKey: '',
37 | tilesets: [],
38 | setters: setMapData(set),
39 | },
40 | game: {
41 | width: MIN_GAME_WIDTH,
42 | height: MIN_GAME_HEIGHT,
43 | zoom: 1,
44 | locale: 'en',
45 | cameraSizeUpdateCallbacks: [],
46 | setters: setGameData(set),
47 | },
48 | dialog: {
49 | messages: [],
50 | action: null,
51 | characterName: '',
52 | setters: setDialog(set),
53 | },
54 | menu: {
55 | items: [],
56 | position: 'center',
57 | onSelect: null,
58 | setters: setMenu(set),
59 | },
60 | text: {
61 | texts: [],
62 | setters: setText(set),
63 | },
64 | }));
65 |
66 | export const useGameStore = (selector) => useStore(store, selector);
67 |
68 | export const getState = () => store.getState();
69 |
--------------------------------------------------------------------------------
/src/zustand/text/selectText.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line import/prefer-default-export
2 | export const selectTexts = (state) => state.text.texts;
3 |
4 | export const selectTextSetters = (state) => state.text.setters;
5 |
--------------------------------------------------------------------------------
/src/zustand/text/setText.js:
--------------------------------------------------------------------------------
1 | export default (set) => ({
2 | setTextTexts: (texts) =>
3 | set((state) => ({
4 | ...state,
5 | text: {
6 | ...state.text,
7 | texts,
8 | },
9 | })),
10 | addTextTexts: (texts) =>
11 | set((state) => ({
12 | ...state,
13 | text: {
14 | ...state.text,
15 | // TODO make this a Set()
16 | texts: [...state.text.texts, ...texts],
17 | },
18 | })),
19 | updateTextTexts: (key, variables) =>
20 | set((state) => ({
21 | ...state,
22 | text: {
23 | ...state.text,
24 | texts:
25 | state.text.texts.map((text) => {
26 | if (text.key === key) {
27 | return {
28 | ...text,
29 | variables,
30 | };
31 | }
32 |
33 | return text;
34 | })
35 | ,
36 | },
37 | })),
38 | removeTextTexts: (key) =>
39 | set((state) => ({
40 | ...state,
41 | text: {
42 | ...state.text,
43 | texts: state.text.texts.filter((text) => text.key !== key),
44 | },
45 | })),
46 | });
47 |
--------------------------------------------------------------------------------