├── .gitattributes
├── .gitignore
├── .prettierrc
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
├── readme
└── myimage.gif
├── src
├── assets
│ ├── icons
│ │ ├── favicon.ico
│ │ ├── icons-192.png
│ │ └── icons-512.png
│ ├── img
│ │ ├── car-body.png
│ │ ├── car-wheel.png
│ │ ├── gas.png
│ │ ├── grass.png
│ │ ├── restart-btn.png
│ │ ├── sky.png
│ │ ├── tile.png
│ │ └── wholes-small.png
│ ├── terrain.svg
│ ├── textureAtlas.json
│ ├── textureAtlas.png
│ └── textureAtlas.tps
├── index.html
├── pwa
│ ├── manifest.json
│ └── sw.js
└── scripts
│ ├── customCanvas.ts
│ ├── game.ts
│ ├── objects
│ ├── bridge.ts
│ ├── car.ts
│ ├── gas.ts
│ ├── restart.ts
│ ├── terrain.ts
│ └── terrainDynamic.ts
│ └── scenes
│ ├── backgroundScene.ts
│ ├── guiScene.ts
│ ├── mainScene.ts
│ └── preloadScene.ts
├── tsconfig.json
├── typings
├── custom.d.ts
└── phaser.d.ts
└── webpack
├── webpack.common.js
├── webpack.dev.js
└── webpack.prod.js
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.js linguist-detectable=false
2 | *.html linguist-detectable=false
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /dist
3 | /.cache
4 | /.vscode
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 2,
3 | "semi": false,
4 | "singleQuote": true,
5 | "printWidth": 120
6 | }
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Yannick Deubel (https://github.com/yandeu); Project Url: https://github.com/yandeu/phaser-project-template
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 | # Phaser 3 + Matter.js example
2 |
3 | 
4 | 
5 | 
6 |
7 | ## Car on a curved terrain crosses a bridge
8 |
9 | Build with Phaser 3 using the [typescript phaser-project-template](https://github.com/yandeu/phaser-project-template#readme)
10 |
11 | ## Play
12 |
13 | [Play the game](https://s3.eu-central-1.amazonaws.com/phaser3-typescript/car-on-curved-tarrain/index.html)
14 |
15 | 
16 |
17 | ## Features
18 |
19 | - PWA
20 | - Using WebGL 2
21 |
22 | ## About
23 |
24 | To build this example I used Phaser 3 with the physics engine Matter.js.
25 | To make Matter work, it needs two additional libraries, **poly-decomp** and **pathseg**.
26 |
27 | Reading the source code of some classes (especially [Body](http://brm.io/matter-js/docs/files/src_body_Body.js.html#l436), [Bodies](http://brm.io/matter-js/docs/files/src_factory_Bodies.js.html#l102) and [Composites](http://brm.io/matter-js/docs/files/src_factory_Composites.js.html#l230)) of Matter and the [Phaser3-docs](https://photonstorm.github.io/phaser3-docs/Phaser.Physics.Matter.html) helped a lot.
28 |
29 | The mainScene takes a while to start and to restart. This is because I use [Matter.Svg.pathToVertices](http://brm.io/matter-js/docs/classes/Svg.html) to transform a SVG Path to an array of vectors at runtime. In a production game, I probably would only include the array of vectors in the game.
30 |
31 | ## Credits
32 |
33 | https://opengameart.org/content/free-off-road-racing-truck
34 | https://opengameart.org/content/monstertruck
35 | https://opengameart.org/content/sky-backdrop
36 |
37 | ## License
38 |
39 | The MIT License (MIT) 2019 - [Yannick Deubel](https://github.com/yandeu). Please have a look at the [LICENSE](LICENSE) for more details.
40 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "phaser-project-template",
3 | "version": "3.16.2",
4 | "description": "Phaser 3 starter template with TypeScript and webpack.",
5 | "homepage": "https://github.com/yandeu/phaser-project-template#readme",
6 | "main": "index.js",
7 | "scripts": {
8 | "start": "webpack-dev-server --config webpack/webpack.dev.js",
9 | "build": "webpack --config webpack/webpack.prod.js"
10 | },
11 | "keywords": [
12 | "Phaser",
13 | "Phaser 3",
14 | "Phaser3",
15 | "html5 game",
16 | "TypeScript",
17 | "webpack",
18 | "starter"
19 | ],
20 | "author": {
21 | "name": "Yannick",
22 | "url": "https://github.com/yandeu"
23 | },
24 | "repository": {
25 | "type": "git",
26 | "url": "https://github.com/yandeu/phaser-project-template.git"
27 | },
28 | "license": "MIT",
29 | "devDependencies": {
30 | "clean-webpack-plugin": "^1.0.1",
31 | "copy-webpack-plugin": "^4.6.0",
32 | "html-webpack-plugin": "^3.2.0",
33 | "ts-loader": "^5.4.5",
34 | "typescript": "^3.4.5",
35 | "webpack": "^4.30.0",
36 | "webpack-cli": "^3.3.1",
37 | "webpack-dev-server": "^3.3.1",
38 | "webpack-merge": "^4.2.1",
39 | "webpack-obfuscator": "^0.18.0",
40 | "workbox-webpack-plugin": "^3.6.3"
41 | },
42 | "dependencies": {
43 | "pathseg": "^1.2.0",
44 | "phaser": "~3.16.1",
45 | "poly-decomp": "^0.3.0"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/readme/myimage.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandeu/phaser3-matter-car-on-curved-terrain/530284ec4e032affa2f1fc9fae85550564fac221/readme/myimage.gif
--------------------------------------------------------------------------------
/src/assets/icons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandeu/phaser3-matter-car-on-curved-terrain/530284ec4e032affa2f1fc9fae85550564fac221/src/assets/icons/favicon.ico
--------------------------------------------------------------------------------
/src/assets/icons/icons-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandeu/phaser3-matter-car-on-curved-terrain/530284ec4e032affa2f1fc9fae85550564fac221/src/assets/icons/icons-192.png
--------------------------------------------------------------------------------
/src/assets/icons/icons-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandeu/phaser3-matter-car-on-curved-terrain/530284ec4e032affa2f1fc9fae85550564fac221/src/assets/icons/icons-512.png
--------------------------------------------------------------------------------
/src/assets/img/car-body.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandeu/phaser3-matter-car-on-curved-terrain/530284ec4e032affa2f1fc9fae85550564fac221/src/assets/img/car-body.png
--------------------------------------------------------------------------------
/src/assets/img/car-wheel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandeu/phaser3-matter-car-on-curved-terrain/530284ec4e032affa2f1fc9fae85550564fac221/src/assets/img/car-wheel.png
--------------------------------------------------------------------------------
/src/assets/img/gas.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandeu/phaser3-matter-car-on-curved-terrain/530284ec4e032affa2f1fc9fae85550564fac221/src/assets/img/gas.png
--------------------------------------------------------------------------------
/src/assets/img/grass.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandeu/phaser3-matter-car-on-curved-terrain/530284ec4e032affa2f1fc9fae85550564fac221/src/assets/img/grass.png
--------------------------------------------------------------------------------
/src/assets/img/restart-btn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandeu/phaser3-matter-car-on-curved-terrain/530284ec4e032affa2f1fc9fae85550564fac221/src/assets/img/restart-btn.png
--------------------------------------------------------------------------------
/src/assets/img/sky.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandeu/phaser3-matter-car-on-curved-terrain/530284ec4e032affa2f1fc9fae85550564fac221/src/assets/img/sky.png
--------------------------------------------------------------------------------
/src/assets/img/tile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandeu/phaser3-matter-car-on-curved-terrain/530284ec4e032affa2f1fc9fae85550564fac221/src/assets/img/tile.png
--------------------------------------------------------------------------------
/src/assets/img/wholes-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandeu/phaser3-matter-car-on-curved-terrain/530284ec4e032affa2f1fc9fae85550564fac221/src/assets/img/wholes-small.png
--------------------------------------------------------------------------------
/src/assets/terrain.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
490 |
--------------------------------------------------------------------------------
/src/assets/textureAtlas.json:
--------------------------------------------------------------------------------
1 | {
2 | "textures": [
3 | {
4 | "image": "textureAtlas.png",
5 | "format": "RGBA8888",
6 | "size": {
7 | "w": 1092,
8 | "h": 493
9 | },
10 | "scale": 1,
11 | "frames": [
12 | {
13 | "filename": "sky",
14 | "rotated": false,
15 | "trimmed": false,
16 | "sourceSize": {
17 | "w": 576,
18 | "h": 360
19 | },
20 | "spriteSourceSize": {
21 | "x": 0,
22 | "y": 0,
23 | "w": 576,
24 | "h": 360
25 | },
26 | "frame": {
27 | "x": 1,
28 | "y": 1,
29 | "w": 576,
30 | "h": 360
31 | }
32 | },
33 | {
34 | "filename": "wholes-small",
35 | "rotated": false,
36 | "trimmed": false,
37 | "sourceSize": {
38 | "w": 512,
39 | "h": 288
40 | },
41 | "spriteSourceSize": {
42 | "x": 0,
43 | "y": 0,
44 | "w": 512,
45 | "h": 288
46 | },
47 | "frame": {
48 | "x": 579,
49 | "y": 1,
50 | "w": 512,
51 | "h": 288
52 | }
53 | },
54 | {
55 | "filename": "gas",
56 | "rotated": false,
57 | "trimmed": false,
58 | "sourceSize": {
59 | "w": 171,
60 | "h": 201
61 | },
62 | "spriteSourceSize": {
63 | "x": 0,
64 | "y": 0,
65 | "w": 171,
66 | "h": 201
67 | },
68 | "frame": {
69 | "x": 579,
70 | "y": 291,
71 | "w": 171,
72 | "h": 201
73 | }
74 | },
75 | {
76 | "filename": "car-body",
77 | "rotated": false,
78 | "trimmed": false,
79 | "sourceSize": {
80 | "w": 278,
81 | "h": 100
82 | },
83 | "spriteSourceSize": {
84 | "x": 0,
85 | "y": 0,
86 | "w": 278,
87 | "h": 100
88 | },
89 | "frame": {
90 | "x": 1,
91 | "y": 363,
92 | "w": 278,
93 | "h": 100
94 | }
95 | },
96 | {
97 | "filename": "tile",
98 | "rotated": false,
99 | "trimmed": false,
100 | "sourceSize": {
101 | "w": 60,
102 | "h": 20
103 | },
104 | "spriteSourceSize": {
105 | "x": 0,
106 | "y": 0,
107 | "w": 60,
108 | "h": 20
109 | },
110 | "frame": {
111 | "x": 1,
112 | "y": 465,
113 | "w": 60,
114 | "h": 20
115 | }
116 | },
117 | {
118 | "filename": "car-wheel",
119 | "rotated": false,
120 | "trimmed": false,
121 | "sourceSize": {
122 | "w": 100,
123 | "h": 100
124 | },
125 | "spriteSourceSize": {
126 | "x": 0,
127 | "y": 0,
128 | "w": 100,
129 | "h": 100
130 | },
131 | "frame": {
132 | "x": 281,
133 | "y": 363,
134 | "w": 100,
135 | "h": 100
136 | }
137 | },
138 | {
139 | "filename": "restart-btn",
140 | "rotated": false,
141 | "trimmed": false,
142 | "sourceSize": {
143 | "w": 189,
144 | "h": 61
145 | },
146 | "spriteSourceSize": {
147 | "x": 0,
148 | "y": 0,
149 | "w": 189,
150 | "h": 61
151 | },
152 | "frame": {
153 | "x": 383,
154 | "y": 363,
155 | "w": 189,
156 | "h": 61
157 | }
158 | },
159 | {
160 | "filename": "grass",
161 | "rotated": false,
162 | "trimmed": false,
163 | "sourceSize": {
164 | "w": 49,
165 | "h": 38
166 | },
167 | "spriteSourceSize": {
168 | "x": 0,
169 | "y": 0,
170 | "w": 49,
171 | "h": 38
172 | },
173 | "frame": {
174 | "x": 383,
175 | "y": 426,
176 | "w": 49,
177 | "h": 38
178 | }
179 | }
180 | ]
181 | }
182 | ],
183 | "meta": {
184 | "app": "https://www.codeandweb.com/texturepacker",
185 | "version": "3.0",
186 | "smartupdate": "$TexturePacker:SmartUpdate:13c8cca7e588a9016c12c8563c96801d:3bdd4c42a9e0f7ee3ad0d0f913ab8714:2b5604bc88faefd55ddac16e9bce1532$"
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/src/assets/textureAtlas.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandeu/phaser3-matter-car-on-curved-terrain/530284ec4e032affa2f1fc9fae85550564fac221/src/assets/textureAtlas.png
--------------------------------------------------------------------------------
/src/assets/textureAtlas.tps:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | fileFormatVersion
5 | 4
6 | texturePackerVersion
7 | 4.12.0
8 | autoSDSettings
9 |
10 |
11 | scale
12 | 1
13 | extension
14 |
15 | spriteFilter
16 |
17 | acceptFractionalValues
18 |
19 | maxTextureSize
20 |
21 | width
22 | -1
23 | height
24 | -1
25 |
26 |
27 |
28 | allowRotation
29 |
30 | shapeDebug
31 |
32 | dpi
33 | 72
34 | dataFormat
35 | phaser
36 | textureFileName
37 |
38 | flipPVR
39 |
40 | pvrCompressionQuality
41 | PVR_QUALITY_NORMAL
42 | atfCompressData
43 |
44 | mipMapMinSize
45 | 32768
46 | etc1CompressionQuality
47 | ETC1_QUALITY_LOW_PERCEPTUAL
48 | etc2CompressionQuality
49 | ETC2_QUALITY_LOW_PERCEPTUAL
50 | dxtCompressionMode
51 | DXT_PERCEPTUAL
52 | jxrColorFormat
53 | JXR_YUV444
54 | jxrTrimFlexBits
55 | 0
56 | jxrCompressionLevel
57 | 0
58 | ditherType
59 | PngQuantLow
60 | backgroundColor
61 | 0
62 | libGdx
63 |
64 | filtering
65 |
66 | x
67 | Linear
68 | y
69 | Linear
70 |
71 |
72 | shapePadding
73 | 0
74 | jpgQuality
75 | 80
76 | pngOptimizationLevel
77 | 1
78 | webpQualityLevel
79 | 101
80 | textureSubPath
81 |
82 | atfFormats
83 |
84 | textureFormat
85 | png
86 | borderPadding
87 | 0
88 | maxTextureSize
89 |
90 | width
91 | 2048
92 | height
93 | 2048
94 |
95 | fixedTextureSize
96 |
97 | width
98 | -1
99 | height
100 | -1
101 |
102 | algorithmSettings
103 |
104 | algorithm
105 | MaxRects
106 | freeSizeMode
107 | Best
108 | sizeConstraints
109 | AnySize
110 | forceSquared
111 |
112 | maxRects
113 |
114 | heuristic
115 | Best
116 |
117 | basic
118 |
119 | sortBy
120 | Best
121 | order
122 | Ascending
123 |
124 | polygon
125 |
126 | alignToGrid
127 | 1
128 |
129 |
130 | dataFileNames
131 |
138 | multiPack
139 |
140 | forceIdenticalLayout
141 |
142 | outputFormat
143 | RGBA8888
144 | alphaHandling
145 | ClearTransparentPixels
146 | contentProtection
147 |
148 | key
149 |
150 |
151 | autoAliasEnabled
152 |
153 | trimSpriteNames
154 |
155 | prependSmartFolderName
156 |
157 | autodetectAnimations
158 |
159 | globalSpriteSettings
160 |
161 | scale
162 | 1
163 | scaleMode
164 | Smooth
165 | extrude
166 | 1
167 | trimThreshold
168 | 1
169 | trimMargin
170 | 1
171 | trimMode
172 | Trim
173 | tracerTolerance
174 | 200
175 | heuristicMask
176 |
177 | defaultPivotPoint
178 | 0.5,0.5
179 | writePivotPoints
180 |
181 |
182 | individualSpriteSettings
183 |
289 | fileList
290 |
291 | img
292 |
293 | ignoreFileList
294 |
295 | replaceList
296 |
297 | ignoredWarnings
298 |
299 | commonDivisorX
300 | 1
301 | commonDivisorY
302 | 1
303 | packNormalMaps
304 |
305 | autodetectNormalMaps
306 |
307 | normalMapFilter
308 |
309 | normalMapSuffix
310 |
311 | normalMapSheetFileName
312 |
313 | exporterProperties
314 |
315 |
316 |
317 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
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 | <%= htmlWebpackPlugin.options.gameName %>
27 |
50 |
51 |
52 |
53 |
54 |
loading...
55 |
56 |
57 |
58 |
59 |
60 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/src/pwa/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "2d Car",
3 | "name": "My Cool Phaser 3 Game",
4 | "icons": [
5 | {
6 | "src": "./assets/icons/icons-192.png",
7 | "type": "image/png",
8 | "sizes": "192x192"
9 | },
10 | {
11 | "src": "./assets/icons/icons-512.png",
12 | "type": "image/png",
13 | "sizes": "512x512"
14 | }
15 | ],
16 | "start_url": "./index.html",
17 | "background_color": "#e6e6e6",
18 | "display": "fullscreen",
19 | "orientation": "landscape",
20 | "scope": "./",
21 | "theme_color": "#000000"
22 | }
23 |
--------------------------------------------------------------------------------
/src/pwa/sw.js:
--------------------------------------------------------------------------------
1 | workbox.precaching.precacheAndRoute(__precacheManifest)
2 |
--------------------------------------------------------------------------------
/src/scripts/customCanvas.ts:
--------------------------------------------------------------------------------
1 | declare global {
2 | interface Window {
3 | RENDERING_CONTEXT: 'CANVAS' | 'WEBGL' | 'WEBGL2'
4 | }
5 | }
6 | window.RENDERING_CONTEXT = 'CANVAS'
7 |
8 | export default class CustomCanvas {
9 | private _type: number
10 | private _canvas: HTMLCanvasElement | undefined
11 | private _context: CanvasRenderingContext2D | WebGLRenderingContext | null | undefined
12 |
13 | public get type() {
14 | return this._type
15 | }
16 |
17 | public get canvas() {
18 | return this._canvas
19 | }
20 |
21 | public get context() {
22 | return this._context
23 | }
24 |
25 | constructor() {
26 | let myCustomCanvas: HTMLCanvasElement = document.getElementById('myCustomCanvas')
27 | let myCustomContext: CanvasRenderingContext2D | WebGLRenderingContext | null = null
28 |
29 | console.log(typeof myCustomCanvas)
30 | if (myCustomCanvas) {
31 | const contextCreationConfig = {
32 | alpha: false,
33 | depth: false,
34 | antialias: true,
35 | premultipliedAlpha: true,
36 | stencil: true,
37 | preserveDrawingBuffer: false,
38 | failIfMajorPerformanceCaveat: false,
39 | powerPreference: 'default'
40 | }
41 |
42 | let WEBGL2Supported =
43 | // @ts-ignore
44 | typeof window.WebGL2RenderingContext !== undefined && window.WebGL2RenderingContext ? true : false
45 | let WEBGLSupported =
46 | // @ts-ignore
47 | typeof window.WebGLRenderingContext !== undefined && window.WebGLRenderingContext ? true : false
48 |
49 | // @ts-ignore
50 | if (WEBGL2Supported) myCustomContext = myCustomCanvas.getContext('webgl2', contextCreationConfig)
51 | // @ts-ignore
52 | if (!myCustomContext && WEBGLSupported)
53 | myCustomContext = myCustomCanvas.getContext('webgl', contextCreationConfig)
54 |
55 | if (myCustomContext) {
56 | // @ts-ignore
57 | if (WEBGL2Supported && myCustomContext instanceof WebGL2RenderingContext) window.RENDERING_CONTEXT = 'WEBGL2'
58 | if (WEBGLSupported && myCustomContext instanceof WebGLRenderingContext) window.RENDERING_CONTEXT = 'WEBGL'
59 | }
60 | }
61 | this._type = myCustomContext ? Phaser.WEBGL : Phaser.CANVAS
62 | this._canvas = myCustomCanvas ? myCustomCanvas : undefined
63 | // @ts-ignore
64 | this._context = myCustomContext ? myCustomContext : undefined
65 |
66 | console.log(`Rendering: ${window.RENDERING_CONTEXT}`)
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/scripts/game.ts:
--------------------------------------------------------------------------------
1 | import 'phaser'
2 |
3 | // poly-decomp and pathseg are required by matterjs
4 | // @ts-ignore
5 | import decomp from 'poly-decomp'
6 | // @ts-ignore
7 | window.decomp = decomp
8 | // @ts-ignore
9 | import 'pathseg'
10 |
11 | import MainScene from './scenes/mainScene'
12 | import PreloadScene from './scenes/preloadScene'
13 | import GuiScene from './scenes/guiScene'
14 | import BackgroundScene from './scenes/backgroundScene'
15 | import CustomCanvas from './customCanvas'
16 |
17 | const ratio = Math.max(window.innerWidth / window.innerHeight, window.innerHeight / window.innerWidth)
18 | const DEFAULT_HEIGHT = 720
19 | const DEFAULT_WIDTH = ratio * DEFAULT_HEIGHT
20 |
21 | const customCanvas = new CustomCanvas()
22 |
23 | const config: GameConfig = {
24 | type: customCanvas.type,
25 | canvas: customCanvas.canvas,
26 | // @ts-ignore
27 | context: customCanvas.context,
28 | backgroundColor: '#ffffff',
29 | scale: {
30 | mode: Phaser.Scale.FIT,
31 | autoCenter: Phaser.Scale.CENTER_BOTH,
32 | width: DEFAULT_WIDTH,
33 | height: DEFAULT_HEIGHT
34 | },
35 | scene: [PreloadScene, BackgroundScene, MainScene, GuiScene],
36 | physics: {
37 | default: 'matter',
38 | matter: {
39 | gravity: {
40 | y: 1
41 | },
42 | debug: false,
43 | debugBodyColor: 0x000000
44 | }
45 | }
46 | }
47 |
48 | window.addEventListener('load', () => {
49 | let game = new Phaser.Game(config)
50 | })
51 |
--------------------------------------------------------------------------------
/src/scripts/objects/bridge.ts:
--------------------------------------------------------------------------------
1 | export default class Bridge {
2 | constructor(scene: Phaser.Scene, x: number = 0, y: number = 0, width: number = 1000, numberOfPlanks: number = 18) {
3 | let woodPlanksPositions = []
4 |
5 | let group = scene.matter.world.nextGroup(true)
6 |
7 | // creates a new wood plank
8 | const newWoodPlank = (x: number, y: number) => {
9 | return scene.matter.add.image(x, y, 'atlas', 'tile').setBody(
10 | { type: 'rectangle' },
11 | {
12 | collisionFilter: { group: group },
13 | label: 'bridgePlank',
14 | chamfer: 5,
15 | density: 0.005,
16 | friction: 0.6
17 | //frictionAir: 0.05
18 | }
19 | )
20 | }
21 |
22 | // calculate the positions of all wood planks
23 | for (let i = 0; i < numberOfPlanks; i++) woodPlanksPositions.push({ x: x + (i * width) / numberOfPlanks, y: y })
24 |
25 | // make all the wood planks
26 | let woodPlanks = woodPlanksPositions.map(pos => newWoodPlank(pos.x, pos.y))
27 |
28 | // attaching each plank to the next one
29 | for (let i = 0; i < woodPlanks.length - 1; i++) {
30 | scene.matter.add.constraint(woodPlanks[i], woodPlanks[i + 1], 10, 0.75, {
31 | pointA: { x: 20, y: 0 },
32 | pointB: { x: -20, y: 0 }
33 | })
34 | }
35 |
36 | // attaching the first plank to the left side
37 | scene.matter.add.worldConstraint(woodPlanks[0], 0, 1, {
38 | pointA: { x: x, y: y },
39 | pointB: { x: -30, y: 0 }
40 | })
41 |
42 | // attaching the last plank to the left side
43 | scene.matter.add.worldConstraint(woodPlanks[woodPlanks.length - 1], 0, 1, {
44 | pointA: { x: x + width, y: y },
45 | pointB: { x: 30, y: 0 }
46 | })
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/scripts/objects/car.ts:
--------------------------------------------------------------------------------
1 | import Restart from './restart'
2 |
3 | export default class Car {
4 | readonly MAX_SPEED = 0.75
5 | readonly MAX_SPEED_BACKWARDS = this.MAX_SPEED * 0.75
6 | readonly ACCELERATION = this.MAX_SPEED / 130
7 | readonly ACCELERATION_BACKWARDS = this.ACCELERATION * 0.75
8 |
9 | bodies: any[] = []
10 | gas = {
11 | right: false,
12 | left: false
13 | }
14 | wheelsDown = {
15 | rear: false,
16 | front: false
17 | }
18 | private _scene: Phaser.Scene
19 |
20 | constructor(
21 | scene: Phaser.Scene,
22 | x: number,
23 | y: number,
24 | width: number = 278,
25 | height: number = 100,
26 | wheelSize: number = 50,
27 | wheelOffset: { x: number; y: number } = { x: 62, y: 62 }
28 | ) {
29 | this._scene = scene
30 |
31 | const wheelBase = wheelOffset.x,
32 | wheelAOffset = -width * 0.5 + wheelBase,
33 | wheelBOffset = width * 0.5 - wheelBase,
34 | wheelYOffset = wheelOffset.y
35 |
36 | const density = 0.001
37 | const friction = 0.9
38 | // @ts-ignore
39 | const Matter = Phaser.Physics.Matter.Matter
40 |
41 | let group = scene.matter.world.nextGroup(true)
42 |
43 | let body = scene.matter.add.image(x, y, 'atlas', 'car-body')
44 | body.setBody(
45 | {
46 | type: 'rectangle',
47 | width: width,
48 | height: height
49 | },
50 | {
51 | label: 'carBody',
52 | collisionFilter: {
53 | group: group
54 | },
55 | chamfer: {
56 | radius: height * 0.5
57 | },
58 | density: 0.002
59 | }
60 | )
61 |
62 | let wheelA = scene.matter.add.image(x + wheelAOffset, y + wheelYOffset, 'atlas', 'car-wheel')
63 | wheelA.setBody(
64 | {
65 | type: 'circle',
66 | radius: wheelSize
67 | },
68 | {
69 | label: 'wheelRear',
70 | collisionFilter: {
71 | group: group
72 | },
73 | friction,
74 | density
75 | }
76 | )
77 |
78 | let wheelB = scene.matter.add.image(x + wheelBOffset, y + wheelYOffset, 'atlas', 'car-wheel')
79 | wheelB.setBody(
80 | {
81 | type: 'circle',
82 | radius: wheelSize
83 | },
84 | {
85 | label: 'wheelFront',
86 | collisionFilter: {
87 | group: group
88 | },
89 | friction,
90 | density
91 | }
92 | )
93 |
94 | let axelA = scene.matter.add.constraint(body.body, wheelA.body, 0, 0.2, {
95 | pointA: { x: wheelAOffset, y: wheelYOffset }
96 | })
97 |
98 | let axelB = scene.matter.add.constraint(body.body, wheelB.body, 0, 0.2, {
99 | pointA: { x: wheelBOffset, y: wheelYOffset }
100 | })
101 |
102 | this.bodies = [body.body, wheelA.body, wheelB.body]
103 | }
104 |
105 | update() {
106 | // @ts-ignore
107 | const Matter = Phaser.Physics.Matter.Matter
108 | const carBody = this.bodies[0]
109 | const wheelRear = this.bodies[1]
110 | const wheelFront = this.bodies[2]
111 |
112 | // restart the game if the car falls
113 | if (carBody.position.y > 3000) Restart.restart(this._scene)
114 |
115 | let angularVelocity = 0.005
116 |
117 | if (this.gas.right) {
118 | let newSpeed = wheelRear.angularSpeed <= 0 ? this.MAX_SPEED / 10 : wheelRear.angularSpeed + this.ACCELERATION
119 | if (newSpeed > this.MAX_SPEED) newSpeed = this.MAX_SPEED
120 | Matter.Body.setAngularVelocity(wheelRear, newSpeed)
121 | Matter.Body.setAngularVelocity(wheelFront, newSpeed)
122 | // if (!this.wheelsDown.rear && !this.wheelsDown.front) Matter.Body.setAngularVelocity(carBody, -angularVelocity)
123 | } else if (this.gas.left) {
124 | let newSpeed =
125 | wheelRear.angularSpeed <= 0
126 | ? this.MAX_SPEED_BACKWARDS / 10
127 | : wheelRear.angularSpeed + this.ACCELERATION_BACKWARDS
128 | if (newSpeed > this.MAX_SPEED_BACKWARDS) newSpeed = this.MAX_SPEED_BACKWARDS
129 |
130 | Matter.Body.setAngularVelocity(wheelRear, -newSpeed)
131 | // if (!this.wheelsDown.rear && !this.wheelsDown.front) Matter.Body.setAngularVelocity(carBody, angularVelocity)
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/scripts/objects/gas.ts:
--------------------------------------------------------------------------------
1 | import Car from './car'
2 |
3 | export default class Gas {
4 | constructor(scene: Phaser.Scene, car: Car) {
5 | // left gas
6 | scene.add
7 | .image(20, scene.cameras.main.height - 20, 'atlas', 'gas')
8 | .setScrollFactor(0)
9 | .setOrigin(0, 1)
10 | .setInteractive()
11 | .on('pointerdown', () => (car.gas.left = true))
12 | .on('pointerover', () => (car.gas.left = true))
13 | .on('pointerup', () => (car.gas.left = false))
14 | .on('pointerout', () => (car.gas.left = false))
15 |
16 | // right gas
17 | scene.add
18 | .image(scene.cameras.main.width - 20, scene.cameras.main.height - 20, 'atlas', 'gas')
19 | .setScrollFactor(0)
20 | .setOrigin(1, 1)
21 | .setInteractive()
22 | .on('pointerdown', () => (car.gas.right = true))
23 | .on('pointerover', () => (car.gas.right = true))
24 | .on('pointerup', () => (car.gas.right = false))
25 | .on('pointerout', () => (car.gas.right = false))
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/scripts/objects/restart.ts:
--------------------------------------------------------------------------------
1 | export default class Restart {
2 | private _scene: Phaser.Scene
3 |
4 | constructor(scene: Phaser.Scene) {
5 | this._scene = scene
6 | }
7 |
8 | addRestartButton() {
9 | // add the restart button
10 | this._scene.add
11 | .image(this._scene.cameras.main.width / 2, 15, 'atlas', 'restart-btn')
12 | .setOrigin(0.5, 0)
13 | .setScrollFactor(0)
14 | .setInteractive()
15 | .on('pointerdown', () => {
16 | Restart.restart(this._scene)
17 | })
18 | }
19 |
20 | static restart(scene: Phaser.Scene) {
21 | let loadingScreen = document.getElementById('loading-screen')
22 | if (loadingScreen) {
23 | loadingScreen.style.display = 'flex'
24 | }
25 | scene.time.addEvent({
26 | delay: 250,
27 | callback: () => {
28 | const mainScene = scene.scene.get('MainScene')
29 | mainScene.scene.restart()
30 | const GuiScene = scene.scene.get('GuiScene')
31 | GuiScene.scene.restart()
32 | }
33 | })
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/scripts/objects/terrain.ts:
--------------------------------------------------------------------------------
1 | export default class Terrain {
2 | constructor(scene: Phaser.Scene, path: any, x: number, y: number) {
3 | // @ts-ignore
4 | const Matter = Phaser.Physics.Matter.Matter
5 |
6 | let pathElement = document.createElementNS('http://www.w3.org/2000/svg', 'path')
7 | pathElement.setAttributeNS(null, 'd', path)
8 | pathElement.setAttributeNS(null, 'id', 'path3780')
9 |
10 | var vertexSets: { x: number; y: number }[][] = []
11 | vertexSets.push(Matter.Svg.pathToVertices(pathElement, 30))
12 |
13 | const minMax = (items: number[]) => {
14 | return items.reduce(
15 | (acc, val) => {
16 | acc.high = isNaN(acc.high) || val > acc.high ? val : acc.high
17 | acc.low = isNaN(acc.low) || val < acc.low ? val : acc.low
18 | return acc
19 | },
20 | { high: NaN, low: NaN }
21 | )
22 | }
23 |
24 | let points = {
25 | x: minMax(vertexSets[0].map(bla => bla.x)),
26 | y: minMax(vertexSets[0].map(bla => bla.y))
27 | }
28 |
29 | const normalizeVertices = (vertices: { x: number; y: number }[]) => {
30 | return vertices.map(point => {
31 | return { x: point.x - points.x.low, y: point.y - points.y.low }
32 | })
33 | }
34 |
35 | vertexSets[0] = normalizeVertices(vertexSets[0])
36 |
37 | let PADDING = 200
38 | let LINE_HEIGHT = 25
39 | let WIDTH = points.x.high - points.x.low
40 | let HEIGHT = points.y.high - points.y.low
41 |
42 | //vertexSets[0].unshift({ x: vertexSets[0][0].x, y: vertexSets[0][0].y + 500 })
43 | //vertexSets[0].push({ x: vertexSets[0][vertexSets.length - 1].x, y: vertexSets[0][vertexSets.length - 1].y + 500 })
44 |
45 | // create the terrain
46 | let terrain = scene.add.graphics({ x: x, y: y })
47 | terrain.fillStyle(0x685339)
48 | terrain.beginPath()
49 | vertexSets[0].forEach(c => {
50 | terrain.lineTo(Math.round(c.x), Math.round(c.y))
51 | })
52 | terrain.closePath()
53 | terrain.fillPath()
54 |
55 | const mask = terrain.createGeometryMask()
56 |
57 | // create the wholes in the terrain
58 | let wholes = scene.add.tileSprite(x, y, WIDTH, HEIGHT, 'atlas', 'wholes-small')
59 | wholes.setOrigin(0)
60 | wholes.setScale(2, 2)
61 | wholes.setMask(mask)
62 |
63 | // create the grass layer
64 | let grass = scene.add.graphics({ x: x, y: y })
65 | grass.lineStyle(LINE_HEIGHT, 0xadea53)
66 | grass.beginPath()
67 | vertexSets[0].forEach(c => {
68 | grass.lineTo(Math.round(c.x), Math.round(c.y))
69 | })
70 | grass.closePath()
71 | grass.strokePath()
72 |
73 | // plant the grass
74 | vertexSets[0].forEach(point => {
75 | if (Math.random() < 0.15) scene.add.image(point.x + x, point.y + y - 15, 'grass')
76 | })
77 |
78 | let terrainBody: any = scene.matter.add.fromVertices(
79 | WIDTH / 2 + x,
80 | HEIGHT / 2 + y,
81 | vertexSets,
82 | {
83 | label: 'terrain',
84 | isStatic: true,
85 | friction: 0.7
86 | },
87 | true,
88 | //@ts-ignore
89 | 0.01,
90 | 1
91 | )
92 |
93 | // get offset of center of mass
94 | // and set the terrain to its correct position
95 | // https://github.com/liabru/matter-js/issues/211#issuecomment-184804576
96 | let centerOfMass = Matter.Vector.sub(terrainBody.bounds.min, terrainBody.position)
97 | Matter.Body.setPosition(terrainBody, { x: Math.abs(centerOfMass.x) + x, y: Math.abs(centerOfMass.y) + y })
98 | }
99 | update() {}
100 | }
101 |
--------------------------------------------------------------------------------
/src/scripts/objects/terrainDynamic.ts:
--------------------------------------------------------------------------------
1 | interface DynamicTextures {
2 | x1: number
3 | x2: number
4 | type: 'TileSprite' | 'Graphics' | 'Image' | 'Sprite'
5 | texture:
6 | | Phaser.GameObjects.TileSprite
7 | | Phaser.GameObjects.Graphics
8 | | Phaser.GameObjects.Image
9 | | Phaser.GameObjects.Sprite
10 | }
11 |
12 | export default class Terrain {
13 | private dynamicTextures: DynamicTextures[] = []
14 | private _scene: Phaser.Scene
15 |
16 | constructor(scene: Phaser.Scene, path: any, x: number, y: number, terrainIndex?: number) {
17 | this._scene = scene
18 | // @ts-ignore
19 | const Matter = Phaser.Physics.Matter.Matter
20 |
21 | let pathElement = document.createElementNS('http://www.w3.org/2000/svg', 'path')
22 | pathElement.setAttributeNS(null, 'd', path)
23 | pathElement.setAttributeNS(null, 'id', 'path3780')
24 |
25 | var vertexSets: { x: number; y: number }[][] = []
26 | let PATH_LENGTH = 36
27 | vertexSets.push(Matter.Svg.pathToVertices(pathElement, PATH_LENGTH))
28 |
29 | //console.log(JSON.stringify(vertexSets))
30 |
31 | const minMax = (items: number[]) => {
32 | return items.reduce(
33 | (acc, val) => {
34 | acc.high = isNaN(acc.high) || val > acc.high ? val : acc.high
35 | acc.low = isNaN(acc.low) || val < acc.low ? val : acc.low
36 | return acc
37 | },
38 | { high: NaN, low: NaN }
39 | )
40 | }
41 |
42 | let points = {
43 | x: minMax(vertexSets[0].map(point => point.x)),
44 | y: minMax(vertexSets[0].map(point => point.y))
45 | }
46 |
47 | vertexSets[0].push({
48 | x: vertexSets[0][vertexSets[0].length - 1].x,
49 | y: vertexSets[0][vertexSets[0].length - 1].y + points.y.high + 360
50 | })
51 | vertexSets[0].unshift({
52 | x: vertexSets[0][0].x,
53 | y: vertexSets[0][0].y + points.y.high + 500
54 | })
55 |
56 | points = {
57 | x: minMax(vertexSets[0].map(point => point.x)),
58 | y: minMax(vertexSets[0].map(point => point.y))
59 | }
60 |
61 | const normalizeVertices = (vertices: { x: number; y: number }[]) => {
62 | return vertices.map(point => {
63 | return { x: point.x - points.x.low, y: point.y - points.y.low }
64 | })
65 | }
66 |
67 | vertexSets[0] = normalizeVertices(vertexSets[0])
68 |
69 | let PADDING = 200
70 | let LINE_HEIGHT = 25
71 | let WIDTH = points.x.high - points.x.low
72 | let HEIGHT = points.y.high - points.y.low
73 |
74 | // create the terrain
75 | let terrain = scene.add.graphics({ x: x, y: y })
76 | terrain.fillStyle(0x685339)
77 | terrain.beginPath()
78 | vertexSets[0].forEach(c => {
79 | terrain.lineTo(Math.round(c.x), Math.round(c.y))
80 | })
81 | terrain.closePath()
82 | terrain.fillPath()
83 |
84 | const mask = terrain.createGeometryMask()
85 |
86 | const V = vertexSets[0]
87 |
88 | // create the wholes in the terrain
89 | for (let i = 0; i < Math.ceil(WIDTH / 1024); i++) {
90 | let xx = x + i * 1024
91 |
92 | // get the highest value from all vertices inside the x range of the tilesprite
93 | let points = V.filter(point => {
94 | return point.x > i * 1024 && point.x < i * 1024 + 1024
95 | })
96 |
97 | let low = minMax(points.map(p => p.y)).low
98 | let skip = Math.floor(low / (288 * 2))
99 | let yy = y
100 | yy = skip * 288 * 2
101 |
102 | let wholes = scene.add.tileSprite(xx, yy, 512, Math.min(HEIGHT, 1024 /*288 * 2 * 2*/), 'atlas', 'wholes-small')
103 | wholes.setOrigin(0)
104 | wholes.setScale(2, 2)
105 | wholes.setMask(mask)
106 | this.dynamicTextures.push({ x1: xx, x2: xx + 1024, texture: wholes, type: 'TileSprite' })
107 | }
108 |
109 | terrain.setVisible(false)
110 |
111 | // create the grass layer
112 | const divide = Math.round(1024 / PATH_LENGTH)
113 | //let grass: Phaser.GameObjects.Graphics
114 | for (let i = 1; i < V.length - 1; i += divide) {
115 | let x1 = V[i].x + x
116 | let x2 = V[Math.min(i + divide, V.length - 2)].x + x
117 |
118 | let grass = scene.add.graphics({ x: x1, y: y })
119 | grass.lineStyle(LINE_HEIGHT, 0xadea53)
120 | grass.beginPath()
121 |
122 | for (let j = i === 1 ? 1 : -1; j < divide; j++) {
123 | if (i + j > V.length - 1) break
124 | grass.lineTo(Math.round(V[i + j].x - x1 + x), Math.round(V[i + j].y))
125 | }
126 |
127 | // grass.closePath()
128 | grass.strokePath()
129 |
130 | // let debug = true
131 | // if (debug) {
132 | // let t = `${Math.random()}`
133 | // grass.generateTexture(t)
134 | // let bla = scene.add.sprite(400, 300, t)
135 | // console.log(bla.width, bla.height)
136 | // }
137 |
138 | this.dynamicTextures.push({
139 | x1: Math.min(x1, x2),
140 | x2: Math.max(x1, x2),
141 | texture: grass,
142 | type: 'Graphics'
143 | })
144 | //scene.physics.add.existing(grass)
145 | }
146 |
147 | // plant the grass
148 | let plants = 0
149 | vertexSets[0].forEach(point => {
150 | // for (let i = 0; i < 150; i++) {
151 | // let point = vertexSets[0][i]
152 | if (Math.random() < 0.15) {
153 | plants++
154 | let grass = scene.add.image(point.x + x, point.y + y - 15, 'atlas', 'grass')
155 | this.dynamicTextures.push({ x1: grass.x, x2: grass.x, texture: grass, type: 'Image' })
156 | }
157 | })
158 | // }
159 |
160 | let terrainBody: any = scene.matter.add.fromVertices(
161 | WIDTH / 2 + x,
162 | HEIGHT / 2 + y,
163 | vertexSets,
164 | {
165 | label: 'terrain',
166 | isStatic: true,
167 | friction: 0.7
168 | },
169 | true,
170 | //@ts-ignore
171 | 0.01,
172 | 1
173 | )
174 |
175 | // get offset of center of mass
176 | // and set the terrain to its correct position
177 | // https://github.com/liabru/matter-js/issues/211#issuecomment-184804576
178 | let centerOfMass = Matter.Vector.sub(terrainBody.bounds.min, terrainBody.position)
179 | Matter.Body.setPosition(terrainBody, { x: Math.abs(centerOfMass.x) + x, y: Math.abs(centerOfMass.y) + y })
180 |
181 | this.dynamicTextures.forEach(child => {
182 | // @ts-ignore
183 | //console.log(child.type, child.texture.width, child.texture.height)
184 | //if (child.type === 'Graphics') console.log(child.texture)
185 | })
186 | }
187 |
188 | update() {
189 | let x1 = this._scene.cameras.main.worldView.x
190 | let x2 = x1 + this._scene.cameras.main.worldView.width
191 |
192 | const isVisible = (points: DynamicTextures): boolean => {
193 | const case1 = points.x1 > x1 && points.x1 < x2
194 | const case2 = points.x2 > x1 && points.x2 < x2
195 | const case3 = points.x1 < x1 && points.x2 > x2
196 | return case1 || case2 || case3
197 | }
198 |
199 | let countVisible = 0
200 | this.dynamicTextures.forEach(whole => {
201 | if (whole.texture.visible) countVisible++
202 | if (isVisible(whole)) {
203 | if (!whole.texture.visible) {
204 | whole.texture.setVisible(true)
205 | }
206 | } else {
207 | if (whole.texture.visible) whole.texture.setVisible(false)
208 | }
209 | })
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/src/scripts/scenes/backgroundScene.ts:
--------------------------------------------------------------------------------
1 | export default class GuiScene extends Phaser.Scene {
2 | constructor() {
3 | super({ key: 'BackgroundScene' })
4 | }
5 |
6 | create() {
7 | // blue background
8 | // this.add
9 | // .graphics({ x: 0, y: 0 })
10 | // .fillGradientStyle(0x43c1d8, 0x43c1d8, 0x91eef5, 0x91eef5)
11 | // .fillRect(0, 0, this.cameras.main.width, this.cameras.main.height)
12 | // .setScrollFactor(0)
13 | this.cameras.main.setBackgroundColor('#4ba9e4')
14 | let bg = this.add
15 | .sprite(0, 0, 'atlas', 'sky')
16 | .setScrollFactor(0)
17 | .setOrigin(0)
18 | .setScale(3)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/scripts/scenes/guiScene.ts:
--------------------------------------------------------------------------------
1 | import Restart from '../objects/restart'
2 | import Gas from '../objects/gas'
3 | import MainScene from './mainScene'
4 |
5 | export default class GuiScene extends Phaser.Scene {
6 | fpsText: FpsText
7 |
8 | constructor() {
9 | super({ key: 'GuiScene' })
10 | }
11 |
12 | create() {
13 | // @ts-ignore
14 | const mainScene: MainScene = this.scene.get('MainScene')
15 |
16 | new Restart(this).addRestartButton()
17 | new Gas(this, mainScene.car)
18 |
19 | this.fpsText = new FpsText(this)
20 |
21 | this.add
22 | .text(this.cameras.main.width - 10, 10, `Phaser: v${Phaser.VERSION}`, { color: 'black', fontSize: 28 })
23 | .setOrigin(1, 0)
24 | .setScrollFactor(0)
25 |
26 | this.add
27 | .text(this.cameras.main.width - 10, 10 + 28 + 8, `Render: ${window.RENDERING_CONTEXT}`, {
28 | color: 'black',
29 | fontSize: 28
30 | })
31 | .setOrigin(1, 0)
32 | .setScrollFactor(0)
33 | }
34 | update() {
35 | this.fpsText.update(this)
36 | }
37 | }
38 |
39 | class FpsText extends Phaser.GameObjects.Text {
40 | constructor(scene: Phaser.Scene) {
41 | super(scene, 10, 10, '', { color: 'black', fontSize: 28 })
42 | scene.add.existing(this)
43 | this.setOrigin(0)
44 | this.setScrollFactor(0)
45 | }
46 |
47 | public update(scene: Phaser.Scene) {
48 | this.setText(`fps: ${Math.round(scene.game.loop.actualFps)}`)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/scripts/scenes/mainScene.ts:
--------------------------------------------------------------------------------
1 | import Bridge from '../objects/bridge'
2 | //import Terrain from '../objects/terrain'
3 | import Terrain from '../objects/terrainDynamic'
4 | import Car from '../objects/car'
5 | import Gas from '../objects/gas'
6 | import Restart from '../objects/restart'
7 | import { Scene } from 'phaser'
8 |
9 | export default class MainScene extends Phaser.Scene {
10 | car: Car
11 | terrains: Terrain[]
12 |
13 | constructor() {
14 | super({ key: 'MainScene' })
15 | }
16 |
17 | create() {
18 | //this.matter.add.mouseSpring({})
19 |
20 | const path1 =
21 | 'M -10.837105,399.91531 C -10.837105,399.91531 360.95089,618.41855 633.74796,640.65452 933.27973,665.06975 1194.6484,497.3518 1498.1559,507.62844 1727.0472,515.37863 1898.3125,665.99098 2109.7652,650.73232 2479.5945,624.04507 2737.3742,278.22805 3107.1744,251.27988 3425.2674,283.59165 3926.4207,433.05255 4489.1112,512.4571 4597.6107,527.76814 4722.879,469.1338 4833.3691,482.03476 4868.8117,486.17313 4939.2658,567.09253 4974.475,571.28218 5639.6416,650.43241 6256.8855,671.72797 6588.8994,674.43736 6943.2645,673.42027 7277.3722,403.89083 7743.1187,388.41574 8023.5431,379.09821 8167.268,733.32913 8445.1661,682.98562 9142.1521,556.72082 9382.633,653.43022 9660.1193,642.80088 9803.5716,637.30582 10351.968,200.05637 10670.53,200.2844 10999.895,200.52023 11134.798,-19.470908 11345.368,0.88878878 11664.594,31.754353 12026.765,223.6594 12296.609,161.811 12838.032,37.716588 12837.788,533.42456 12847.221,532.71357'
22 | const path2 =
23 | 'M 11617.063,3528.4898 C 11621.249,3524.7808 11611.68,3447.0568 11669.83,3358.5231 11865.52,3060.5827 11575.859,3311.6521 11343.718,3201.6923 11139.351,3104.8886 10961.733,3006.1663 10729.49,2913.6747 10396.606,2781.1024 10101.548,2541.1218 9731.5518,2514.1701 9413.2902,2546.4861 8911.8712,2695.9665 8348.8826,2775.3815 8240.3255,2790.6945 8114.9908,2732.0525 8004.4422,2744.9552 7968.9808,2749.094 7713.9634,2870.0174 7678.7355,2874.2076 7013.2163,2953.3682 6587.2685,2808.9798 6247.9814,2937.3829 5829.5541,3056.3456 5559.1438,2666.801 5093.1504,2651.3239 4812.5774,2642.0051 4668.7763,2996.2823 4390.7309,2945.9322 3693.3755,2819.6509 3452.7672,2916.373 3175.1339,2905.7423 3031.6055,2900.2465 2546.7924,2588.6333 2228.0616,2588.8613 1898.5226,2589.0972 1699.6737,2243.384 1488.9922,2263.7464 1169.5965,2294.616 807.23392,2486.5461 537.2465,2424.6896 -4.4640133,2300.579 -4.2193389,2796.3516 -13.657313,2795.6406'
24 |
25 | new Bridge(this, -960 - 200, 740, 960, 18)
26 | new Bridge(this, 10003 + 2500, 542, 960, 18)
27 | const terrain1 = new Terrain(this, path1, -200, 350, 1)
28 | const terrain2 = new Terrain(this, path2, 10826 - 81 + 2500, 5 + 375, 2)
29 | this.terrains = [terrain1, terrain2]
30 | this.car = new Car(this, 490, 650)
31 |
32 | // check of the rear wheels are touching the ground
33 | this.matter.world.on('collisionactive', (collisions: any, b: any, c: any) => {
34 | this.car.wheelsDown = { rear: false, front: false }
35 | collisions.pairs.forEach((pair: any) => {
36 | let labels: string[] = [pair['bodyA'].label, pair['bodyB'].label]
37 | if (labels.includes('wheelRear')) {
38 | this.car.wheelsDown.rear = true
39 | }
40 | if (labels.includes('wheelFront')) {
41 | this.car.wheelsDown.front = true
42 | }
43 | })
44 | })
45 |
46 | // listen to pointerdown click events and show world coordinates
47 | // this.input.addListener('pointerdown', (pointer: Phaser.Input.Pointer) => {
48 | // console.log(pointer.worldX, pointer.worldY)
49 | // })
50 |
51 | // hide the loading screen
52 | let loadingScreen = document.getElementById('loading-screen')
53 | if (loadingScreen) loadingScreen.style.display = 'none'
54 | }
55 |
56 | update() {
57 | //console.log(this.cameras.main.worldView.x, this.cameras.main.worldView.width)
58 | this.terrains.forEach(terrain => {
59 | terrain.update()
60 | })
61 |
62 | // fix the camera to the car
63 | const carBody = this.car.bodies[0]
64 | this.cameras.main.centerOn(carBody.position.x + 300, carBody.position.y - 100)
65 |
66 | // set the smooth zoom
67 | const wheelRear = this.car.bodies[1]
68 | const currentZoom = this.cameras.main.zoom
69 | let zoom = 1 - wheelRear.angularVelocity / 1.65
70 | if (zoom > currentZoom + currentZoom * 0.0022) zoom = currentZoom + currentZoom * 0.0022
71 | else if (zoom < currentZoom - currentZoom * 0.0022) zoom = currentZoom - currentZoom * 0.0022
72 | if (zoom > 1) zoom = 1
73 | if (zoom < 0.6) zoom = 0.6
74 | this.cameras.main.setZoom(zoom)
75 |
76 | this.car.update()
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/scripts/scenes/preloadScene.ts:
--------------------------------------------------------------------------------
1 | export default class PreloadScene extends Phaser.Scene {
2 | constructor() {
3 | super({ key: 'PreloadScene' })
4 | }
5 |
6 | preload() {
7 | // this.load.image('tile', 'assets/img/tile.png')
8 | // this.load.image('gas', 'assets/img/gas.png')
9 | // this.load.image('car', 'assets/img/car-small.png')
10 | // this.load.image('wheel', 'assets/img/wheel-small.png')
11 | // this.load.image('restart', 'assets/img/restart-btn.png')
12 | // this.load.image('wholes-small', 'assets/img/wholes-small.png')
13 | // this.load.image('grass', 'assets/img/grass.png')
14 | // this.load.image('background', 'assets/img/background.png')
15 | this.load.multiatlas('atlas', 'assets/textureAtlas.json', 'assets')
16 | }
17 |
18 | create() {
19 | this.scene.start('BackgroundScene')
20 | this.scene.start('MainScene')
21 | this.scene.start('GuiScene')
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "strict": true,
6 | "noImplicitAny": true,
7 | "esModuleInterop": true,
8 | "allowUnreachableCode": false,
9 | "sourceMap": true,
10 | "strictPropertyInitialization": false,
11 | "typeRoots": ["node_modules/@types", "typings"],
12 | "lib": ["dom", "es2015.promise", "es5", "esnext"]
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/typings/custom.d.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yandeu/phaser3-matter-car-on-curved-terrain/530284ec4e032affa2f1fc9fae85550564fac221/typings/custom.d.ts
--------------------------------------------------------------------------------
/webpack/webpack.common.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const CopyWebpackPlugin = require('copy-webpack-plugin')
3 | const HtmlWebpackPlugin = require('html-webpack-plugin')
4 | const { InjectManifest } = require('workbox-webpack-plugin')
5 |
6 | module.exports = {
7 | entry: './src/scripts/game.ts',
8 | output: {
9 | filename: 'game.bundle.js',
10 | path: path.resolve(__dirname, '../dist')
11 | },
12 | resolve: {
13 | extensions: ['.ts', '.tsx', '.js']
14 | },
15 | module: {
16 | rules: [{ test: /\.tsx?$/, loader: 'ts-loader' }]
17 | },
18 | optimization: {
19 | splitChunks: {
20 | cacheGroups: {
21 | commons: {
22 | test: /[\\/]node_modules[\\/]/,
23 | name: 'vendors',
24 | chunks: 'all',
25 | filename: '[name].bundle.js'
26 | }
27 | }
28 | }
29 | },
30 | plugins: [
31 | new HtmlWebpackPlugin({ gameName: 'My Phaser Game', template: 'src/index.html' }),
32 | new CopyWebpackPlugin([{ from: 'src/assets', to: 'assets' }, { from: 'src/pwa', to: '' }]),
33 | new InjectManifest({
34 | swSrc: path.resolve(__dirname, '../src/pwa/sw.js')
35 | })
36 | ]
37 | }
38 |
--------------------------------------------------------------------------------
/webpack/webpack.dev.js:
--------------------------------------------------------------------------------
1 | const merge = require('webpack-merge')
2 | const common = require('./webpack.common')
3 |
4 | const dev = {
5 | mode: 'development',
6 | devtool: 'inline-source-map',
7 | devServer: {
8 | open: true,
9 | noInfo: true
10 | }
11 | }
12 |
13 | module.exports = merge(common, dev)
14 |
--------------------------------------------------------------------------------
/webpack/webpack.prod.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const merge = require('webpack-merge')
3 | const common = require('./webpack.common')
4 | const JavaScriptObfuscator = require('webpack-obfuscator')
5 | const CleanWebpackPlugin = require('clean-webpack-plugin')
6 |
7 | const prod = {
8 | mode: 'production',
9 | output: {
10 | filename: 'game.[contenthash].js'
11 | },
12 | optimization: {
13 | splitChunks: {
14 | cacheGroups: {
15 | commons: {
16 | filename: '[name].[contenthash].js'
17 | }
18 | }
19 | }
20 | },
21 | plugins: [
22 | new CleanWebpackPlugin(['dist/*.js'], { root: path.resolve(__dirname, '../') }),
23 | new JavaScriptObfuscator(
24 | {
25 | rotateStringArray: true,
26 | stringArray: true,
27 | // stringArrayEncoding: 'base64', // disabled by default
28 | stringArrayThreshold: 0.75
29 | },
30 | ['vendors.*.js']
31 | )
32 | ]
33 | }
34 |
35 | module.exports = merge(common, prod)
36 |
--------------------------------------------------------------------------------