├── .gitignore ├── .vscode └── settings.json ├── src ├── pwa │ ├── sw.js │ └── manifest.json ├── favicon.ico ├── assets │ ├── img │ │ ├── bee.png │ │ ├── coin.png │ │ ├── goal.png │ │ ├── slime.png │ │ ├── player.png │ │ ├── background.png │ │ ├── controls.png │ │ ├── tile-left.png │ │ ├── tile-right.png │ │ ├── tile-middle.png │ │ └── tile-single.png │ ├── spine │ │ ├── boy.png │ │ ├── raw │ │ │ ├── spineplayer-ess.spine │ │ │ └── boy │ │ │ │ ├── blue │ │ │ │ ├── images │ │ │ │ │ ├── Body.png │ │ │ │ │ ├── face1.png │ │ │ │ │ ├── face2.png │ │ │ │ │ ├── LeftArm.png │ │ │ │ │ ├── Leftleg.png │ │ │ │ │ ├── RightArm.png │ │ │ │ │ └── RightLeg.png │ │ │ │ └── boy-blue.json │ │ │ │ └── green │ │ │ │ ├── images │ │ │ │ ├── Body.png │ │ │ │ ├── LeftArm.png │ │ │ │ ├── Leftleg.png │ │ │ │ ├── face1.png │ │ │ │ ├── face2.png │ │ │ │ ├── RightArm.png │ │ │ │ └── RightLeg.png │ │ │ │ └── boy.json │ │ ├── boy.atlas │ │ └── boy.json │ └── icons │ │ ├── icons-192.png │ │ └── icons-512.png ├── components │ ├── coins │ │ ├── coinGroup.ts │ │ └── coinSingle.ts │ ├── tiles │ │ ├── tilesGroup.ts │ │ └── tilesSingle.ts │ ├── phaserVersionText.ts │ ├── controls │ │ ├── controlsSprite.ts │ │ └── controls.ts │ ├── background.ts │ ├── miniMap.ts │ ├── enemies │ │ ├── enemyClass.ts │ │ ├── bee.ts │ │ ├── slime.ts │ │ └── enemiesGroup.ts │ ├── goalSprite.ts │ ├── map.ts │ ├── levelText.ts │ ├── player │ │ ├── playerSpine.ts │ │ └── player.ts │ └── levels.ts ├── scenes │ ├── preloadScene.ts │ └── mainScene.ts ├── index.html └── game.ts ├── .prettierrc ├── screenshots ├── nexus6.png └── nexus6-640x360.png ├── typings └── custom.d.ts ├── webpack ├── webpack.dev.js ├── webpack.prod.js └── webpack.common.js ├── tsconfig.json ├── package.json ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist 3 | /.cache -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.enabled": true 3 | } 4 | -------------------------------------------------------------------------------- /src/pwa/sw.js: -------------------------------------------------------------------------------- 1 | workbox.precaching.precacheAndRoute(__precacheManifest) 2 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "semi": false, 4 | "singleQuote": true, 5 | "printWidth": 120 6 | } 7 | -------------------------------------------------------------------------------- /screenshots/nexus6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/screenshots/nexus6.png -------------------------------------------------------------------------------- /src/assets/img/bee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/assets/img/bee.png -------------------------------------------------------------------------------- /src/assets/img/coin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/assets/img/coin.png -------------------------------------------------------------------------------- /src/assets/img/goal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/assets/img/goal.png -------------------------------------------------------------------------------- /src/assets/img/slime.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/assets/img/slime.png -------------------------------------------------------------------------------- /src/assets/spine/boy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/assets/spine/boy.png -------------------------------------------------------------------------------- /src/assets/img/player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/assets/img/player.png -------------------------------------------------------------------------------- /src/assets/img/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/assets/img/background.png -------------------------------------------------------------------------------- /src/assets/img/controls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/assets/img/controls.png -------------------------------------------------------------------------------- /src/assets/img/tile-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/assets/img/tile-left.png -------------------------------------------------------------------------------- /src/assets/img/tile-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/assets/img/tile-right.png -------------------------------------------------------------------------------- /screenshots/nexus6-640x360.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/screenshots/nexus6-640x360.png -------------------------------------------------------------------------------- /src/assets/icons/icons-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/assets/icons/icons-192.png -------------------------------------------------------------------------------- /src/assets/icons/icons-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/assets/icons/icons-512.png -------------------------------------------------------------------------------- /src/assets/img/tile-middle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/assets/img/tile-middle.png -------------------------------------------------------------------------------- /src/assets/img/tile-single.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/assets/img/tile-single.png -------------------------------------------------------------------------------- /src/assets/spine/raw/spineplayer-ess.spine: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/assets/spine/raw/spineplayer-ess.spine -------------------------------------------------------------------------------- /src/assets/spine/raw/boy/blue/images/Body.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/assets/spine/raw/boy/blue/images/Body.png -------------------------------------------------------------------------------- /src/assets/spine/raw/boy/blue/images/face1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/assets/spine/raw/boy/blue/images/face1.png -------------------------------------------------------------------------------- /src/assets/spine/raw/boy/blue/images/face2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/assets/spine/raw/boy/blue/images/face2.png -------------------------------------------------------------------------------- /src/assets/spine/raw/boy/green/images/Body.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/assets/spine/raw/boy/green/images/Body.png -------------------------------------------------------------------------------- /src/assets/spine/raw/boy/blue/images/LeftArm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/assets/spine/raw/boy/blue/images/LeftArm.png -------------------------------------------------------------------------------- /src/assets/spine/raw/boy/blue/images/Leftleg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/assets/spine/raw/boy/blue/images/Leftleg.png -------------------------------------------------------------------------------- /src/assets/spine/raw/boy/blue/images/RightArm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/assets/spine/raw/boy/blue/images/RightArm.png -------------------------------------------------------------------------------- /src/assets/spine/raw/boy/blue/images/RightLeg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/assets/spine/raw/boy/blue/images/RightLeg.png -------------------------------------------------------------------------------- /src/assets/spine/raw/boy/green/images/LeftArm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/assets/spine/raw/boy/green/images/LeftArm.png -------------------------------------------------------------------------------- /src/assets/spine/raw/boy/green/images/Leftleg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/assets/spine/raw/boy/green/images/Leftleg.png -------------------------------------------------------------------------------- /src/assets/spine/raw/boy/green/images/face1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/assets/spine/raw/boy/green/images/face1.png -------------------------------------------------------------------------------- /src/assets/spine/raw/boy/green/images/face2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/assets/spine/raw/boy/green/images/face2.png -------------------------------------------------------------------------------- /src/assets/spine/raw/boy/green/images/RightArm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/assets/spine/raw/boy/green/images/RightArm.png -------------------------------------------------------------------------------- /src/assets/spine/raw/boy/green/images/RightLeg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yandeu/phaser3-typescript-platformer-example/HEAD/src/assets/spine/raw/boy/green/images/RightLeg.png -------------------------------------------------------------------------------- /typings/custom.d.ts: -------------------------------------------------------------------------------- 1 | interface TilesConfig { 2 | type: string 3 | texture: string 4 | x: number 5 | y: number 6 | } 7 | 8 | interface MapSize { 9 | x: number 10 | y: number 11 | width: number 12 | height: number 13 | } 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/coins/coinGroup.ts: -------------------------------------------------------------------------------- 1 | import CoinSingle from './coinSingle' 2 | 3 | export default class CoinGroup extends Phaser.GameObjects.Group { 4 | constructor(scene: Phaser.Scene, tiles: TilesConfig[]) { 5 | super(scene) 6 | 7 | tiles.forEach(tile => { 8 | this.add(new CoinSingle(scene, tile)) 9 | }) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/components/tiles/tilesGroup.ts: -------------------------------------------------------------------------------- 1 | import TilesSingle from './tilesSingle' 2 | 3 | export default class TilesGroup extends Phaser.GameObjects.Group { 4 | constructor(scene: Phaser.Scene, tiles: TilesConfig[]) { 5 | super(scene) 6 | 7 | tiles.forEach(tile => { 8 | this.add(new TilesSingle(scene, tile.x, tile.y, tile.texture)) 9 | }) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/components/phaserVersionText.ts: -------------------------------------------------------------------------------- 1 | export default class PhaserVersionText extends Phaser.GameObjects.Text { 2 | constructor(scene: Phaser.Scene, x: number, y: number, text: string) { 3 | super(scene, x, y, text, { 4 | color: '#000000', 5 | fontSize: 24 6 | }) 7 | scene.add.existing(this) 8 | this.setOrigin(1, 0).setScrollFactor(0) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /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"] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/tiles/tilesSingle.ts: -------------------------------------------------------------------------------- 1 | export default class TilesSingle extends Phaser.Physics.Arcade.Sprite { 2 | constructor(scene: Phaser.Scene, x: number, y: number, texture: string) { 3 | super(scene, x, y, texture) 4 | 5 | this.setOrigin(0, 0) 6 | 7 | scene.add.existing(this) 8 | scene.physics.add.existing(this, true) 9 | 10 | // this allows the player to jump through a tile from below 11 | this.body.checkCollision.down = false 12 | this.body.checkCollision.right = false 13 | this.body.checkCollision.left = false 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/pwa/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Platformer PWA", 3 | "name": "Phaser 3 Platformer Example", 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/components/controls/controlsSprite.ts: -------------------------------------------------------------------------------- 1 | export default class ControlsSprite extends Phaser.GameObjects.Image { 2 | type: string 3 | constructor(scene: Phaser.Scene, x: number, y: number, config: any) { 4 | super(scene, y, x, 'controls') 5 | scene.add.existing(this) 6 | 7 | this.setX(x) 8 | .setY(y) 9 | .setAlpha(0.1) 10 | .setRotation(config.rotation) 11 | .setScrollFactor(0) 12 | .setScale(1.25) 13 | 14 | this.type = config.type 15 | 16 | // hide control on non-touch devices 17 | if (!scene.sys.game.device.input.touch) this.setAlpha(0) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/background.ts: -------------------------------------------------------------------------------- 1 | export default class Background extends Phaser.GameObjects.TileSprite { 2 | constructor(scene: Phaser.Scene) { 3 | super(scene, 0, 0, 0, 0, 'background') 4 | scene.add.existing(this) 5 | 6 | this.setOrigin(0.5).setScrollFactor(0) 7 | } 8 | 9 | adjustPosition() { 10 | const imgHeight = 648 11 | this.setScale(this.scene.cameras.main.height / imgHeight) 12 | this.x = this.scene.cameras.main.centerX 13 | this.y = this.scene.cameras.main.centerY 14 | this.width = this.scene.cameras.main.width 15 | } 16 | 17 | parallax() { 18 | this.tilePositionX = this.scene.cameras.main.worldView.x * 0.2 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/miniMap.ts: -------------------------------------------------------------------------------- 1 | import { Map } from './map' 2 | import Player from './player/player' 3 | 4 | export default class MiniMap { 5 | camera: Phaser.Cameras.Scene2D.BaseCamera 6 | constructor(scene: Phaser.Scene, x: number, y: number, width: number, height: number, map: Map) { 7 | this.camera = scene.cameras 8 | .add(x, y, width, height) 9 | .setZoom(1 / 8) 10 | .setName('mini') 11 | .setBounds(map.size.x, map.size.y, map.size.width, map.size.height) 12 | .setBackgroundColor(0x81bdd2ff) 13 | .setAlpha(0.75) 14 | } 15 | 16 | setIgnore(gameObject: any[]) { 17 | gameObject.forEach(obj => { 18 | this.camera.ignore(obj) 19 | }) 20 | } 21 | 22 | update(player: Player) { 23 | this.camera.scrollX = player.x 24 | this.camera.scrollY = player.y 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/components/enemies/enemyClass.ts: -------------------------------------------------------------------------------- 1 | export default class EnemyClass extends Phaser.Physics.Arcade.Sprite { 2 | protected _dead: boolean = false 3 | 4 | constructor(scene: Phaser.Scene, x: number, y: number, texture: string) { 5 | super(scene, x, y, texture) 6 | scene.add.existing(this) 7 | scene.physics.add.existing(this) 8 | } 9 | 10 | get dead() { 11 | return this._dead 12 | } 13 | 14 | set dead(dead: boolean) { 15 | this._dead = dead 16 | } 17 | 18 | protected removeEnemy() { 19 | this.dead = true 20 | 21 | this.anims.stop() 22 | //@ts-ignore 23 | this.body.setVelocityX(0) 24 | 25 | this.scene.tweens.add({ 26 | targets: this, 27 | delay: 2000, 28 | duration: 600, 29 | alpha: 0, 30 | onComplete: () => { 31 | this.destroy() 32 | } 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/enemies/bee.ts: -------------------------------------------------------------------------------- 1 | import EnemyClass from './enemyClass' 2 | export default class BeeSprite extends EnemyClass { 3 | constructor(scene: Phaser.Scene, x: number, y: number) { 4 | super(scene, x, y, 'bee') 5 | scene.add.existing(this) 6 | scene.physics.add.existing(this) 7 | 8 | scene.anims.create({ 9 | key: 'fly', 10 | frames: scene.anims.generateFrameNumbers('bee', { start: 0, end: 1 }), 11 | frameRate: 8, 12 | repeat: -1 13 | }) 14 | this.play('fly') 15 | 16 | //@ts-ignore 17 | this.body.setVelocityX(-120) 18 | this.setOrigin(0.5, 1) 19 | this.body.setSize(80, 135) 20 | this.body.setOffset((this.width - 80) / 2, 30) 21 | } 22 | 23 | update() {} 24 | 25 | kill() { 26 | if (this.dead) return 27 | this.body.setSize(80, 40) 28 | this.setFrame(2) 29 | this.removeEnemy() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/components/enemies/slime.ts: -------------------------------------------------------------------------------- 1 | import EnemyClass from './enemyClass' 2 | export default class SlimeSprite extends EnemyClass { 3 | constructor(scene: Phaser.Scene, x: number, y: number) { 4 | super(scene, x, y, 'slime') 5 | scene.add.existing(this) 6 | scene.physics.add.existing(this) 7 | 8 | scene.anims.create({ 9 | key: 'crawl', 10 | frames: scene.anims.generateFrameNumbers('slime', { start: 0, end: 4 }), 11 | frameRate: 6, 12 | yoyo: true, 13 | repeat: -1 14 | }) 15 | this.play('crawl') 16 | 17 | //@ts-ignore 18 | this.body.setVelocityX(-60) 19 | this.setOrigin(0.5, 1) 20 | this.setScale(1) 21 | this.body.setSize(this.width - 40, this.height - 20) 22 | this.body.setOffset(20, 20) 23 | } 24 | 25 | update() {} 26 | 27 | kill() { 28 | if (this.dead) return 29 | this.setFrame(5) 30 | this.removeEnemy() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/components/goalSprite.ts: -------------------------------------------------------------------------------- 1 | export default class GoalSprite extends Phaser.Physics.Arcade.Sprite { 2 | private _loadNextLevel: boolean = false 3 | constructor(scene: Phaser.Scene, tilesConfig: TilesConfig) { 4 | super(scene, tilesConfig.x, tilesConfig.y + 14, 'goal') 5 | scene.add.existing(this) 6 | scene.physics.add.existing(this) 7 | 8 | this.setImmovable(true) 9 | // @ts-ignore 10 | this.body.setAllowGravity(false) 11 | this.setOrigin(0, 0.5) 12 | } 13 | 14 | get loadNextLevel() { 15 | return this._loadNextLevel 16 | } 17 | 18 | nextLevel(scene: Phaser.Scene, level: number) { 19 | if (this._loadNextLevel) return 20 | this._loadNextLevel = true 21 | 22 | scene.cameras.main.fadeOut() 23 | scene.time.addEvent({ 24 | delay: 2000, 25 | callback: () => { 26 | scene.scene.restart({ level: level += 1 }) 27 | } 28 | }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phaser3-typescript-platformer-example", 3 | "version": "3.16.2", 4 | "description": "Phaser 3 TypeScript Platformer Example", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack-dev-server --config webpack/webpack.dev.js", 8 | "build": "webpack --config webpack/webpack.prod.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "clean-webpack-plugin": "^1.0.0", 15 | "copy-webpack-plugin": "^4.6.0", 16 | "html-webpack-plugin": "^3.2.0", 17 | "ts-loader": "^5.3.3", 18 | "typescript": "^3.3.1", 19 | "webpack": "^4.28.4", 20 | "webpack-cli": "^3.2.1", 21 | "webpack-dev-server": "^3.1.14", 22 | "webpack-merge": "^4.2.1", 23 | "webpack-obfuscator": "^0.18.0", 24 | "workbox-webpack-plugin": "^3.6.3" 25 | }, 26 | "dependencies": { 27 | "phaser": "^3.16.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/coins/coinSingle.ts: -------------------------------------------------------------------------------- 1 | export default class CoinSingle extends Phaser.Physics.Arcade.Sprite { 2 | private collecting: boolean = false 3 | 4 | constructor(scene: Phaser.Scene, config: TilesConfig) { 5 | super(scene, config.x + 48, config.y + 48, config.texture) 6 | scene.add.existing(this) 7 | scene.physics.add.existing(this) 8 | 9 | this.setImmovable() 10 | this.setScale(1.5) 11 | // @ts-ignore 12 | this.body.setAllowGravity(false) 13 | 14 | scene.anims.create({ 15 | key: 'spin', 16 | frames: scene.anims.generateFrameNames('coin'), 17 | frameRate: 16, 18 | repeat: -1 19 | }) 20 | this.play('spin') 21 | } 22 | 23 | collect() { 24 | if (this.collecting) return 25 | this.collecting = true 26 | this.scene.tweens.add({ 27 | targets: this, 28 | alpha: 0, 29 | y: this.y - 100, 30 | duration: 500, 31 | ease: 'Power2', 32 | onComplete: this.destroy.bind(this) 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/scenes/preloadScene.ts: -------------------------------------------------------------------------------- 1 | export default class PreloadScene extends Phaser.Scene { 2 | constructor() { 3 | super({ 4 | key: 'PreloadScene' 5 | }) 6 | } 7 | 8 | preload() { 9 | const images = ['tile-left', 'tile-middle', 'tile-right', 'tile-single', 'controls', 'background', 'goal'] 10 | images.forEach(img => { 11 | this.load.image(img, `assets/img/${img}.png`) 12 | }) 13 | this.load.spritesheet('player', 'assets/img/player.png', { frameHeight: 165, frameWidth: 120 }) 14 | this.load.spritesheet('coin', 'assets/img/coin.png', { frameHeight: 42, frameWidth: 42 }) 15 | this.load.spritesheet('bee', 'assets/img/bee.png', { frameHeight: 100, frameWidth: 128 }) 16 | this.load.spritesheet('slime', 'assets/img/slime.png', { frameHeight: 68, frameWidth: 112 }) 17 | this.load.setPath('assets/spine') 18 | // @ts-ignore 19 | this.load.spine('boy', 'boy.json', 'boy.atlas') 20 | } 21 | 22 | create() { 23 | this.scene.start('MainScene') 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Yannick Deubel (https://github.com/yandeu); Project Url: https://github.com/yandeu/phaser3-typescript-platformer-example 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 | -------------------------------------------------------------------------------- /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/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[\\/]|[\\/]src[\\/]plugins[\\/]/, 23 | name: 'vendors', 24 | chunks: 'all', 25 | filename: '[name].bundle.js' 26 | } 27 | } 28 | } 29 | }, 30 | plugins: [ 31 | new HtmlWebpackPlugin({ template: 'src/index.html' }), 32 | new CopyWebpackPlugin([ 33 | { from: 'src/assets', to: 'assets' }, 34 | { from: 'src/pwa', to: '' }, 35 | { from: 'src/favicon.ico', to: '' } 36 | ]), 37 | new InjectManifest({ 38 | swSrc: path.resolve(__dirname, '../src/pwa/sw.js'), 39 | exclude: [/\/spine\/raw\/*/] 40 | }) 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Phaser 3 TypeScript Platformer Example 2 | 3 | [![dependencies Status](https://david-dm.org/yandeu/phaser3-typescript-platformer-example/status.svg?style=flat-square)](https://david-dm.org/yandeu/phaser3-typescript-platformer-example) 4 | ![GitHub](https://img.shields.io/github/license/yandeu/phaser3-typescript-platformer-example.svg?style=flat-square) 5 | ![GitHub package.json version](https://img.shields.io/github/package-json/v/yandeu/phaser3-typescript-platformer-example.svg?style=flat-square) 6 | ![GitHub last commit](https://img.shields.io/github/last-commit/yandeu/phaser3-typescript-platformer-example.svg?style=flat-square) 7 | 8 | Built with the [**typescript phaser-project-template**](https://github.com/yandeu/phaser-project-template#readme) starter. 9 | 10 | ## Play 11 | 12 | [Play the game](https://s3.eu-central-1.amazonaws.com/phaser3-typescript/platformer-example/index.html) (Add it to the homescreen to test the PWA functionality) 13 | 14 | [![phaser3-typescript-platformer](screenshots/nexus6-640x360.png)](https://s3.eu-central-1.amazonaws.com/phaser3-typescript/platformer-example/index.html) 15 | 16 | ## How To Use 17 | 18 | To clone and run this game, you'll need [Git](https://git-scm.com) and [Node.js](https://nodejs.org/en/download/) (which comes with [npm](http://npmjs.com)) installed on your computer. From your command line: 19 | 20 | ```bash 21 | # Clone this repository 22 | $ git clone --depth 1 https://github.com/yandeu/phaser3-typescript-platformer-example.git phaser3-platformer-example 23 | 24 | # Go into the repository 25 | $ cd phaser3-platformer-example 26 | 27 | # Install dependencies 28 | $ npm install 29 | 30 | # Start the local development server (on port 8080) 31 | $ npm start 32 | 33 | # Ready for production? 34 | # Build the production ready code to the /dist folder 35 | $ npm run build 36 | ``` 37 | -------------------------------------------------------------------------------- /src/components/map.ts: -------------------------------------------------------------------------------- 1 | import Levels from './levels' 2 | export class Map { 3 | info: TilesConfig[] 4 | size: MapSize 5 | 6 | public static calcCurrentLevel(currentLevel: number) { 7 | const MAX_LEVELS = Levels.length 8 | return currentLevel % MAX_LEVELS 9 | } 10 | 11 | constructor(currentLevel: number) { 12 | const TILE_SIZE = 96 13 | const config: any = { 14 | '[': { 15 | type: 'tile', 16 | texture: 'tile-left' 17 | }, 18 | '/': { 19 | type: 'tile', 20 | texture: 'tile-middle' 21 | }, 22 | ']': { 23 | type: 'tile', 24 | texture: 'tile-right' 25 | }, 26 | G: { 27 | type: 'goal', 28 | texture: 'goal' 29 | }, 30 | O: { 31 | type: 'coin', 32 | texture: 'coin' 33 | }, 34 | S: { 35 | type: 'enemy', 36 | texture: 'slime' 37 | }, 38 | B: { 39 | type: 'enemy', 40 | texture: 'bee' 41 | }, 42 | P: { 43 | type: 'player', 44 | texture: 'player' 45 | } 46 | } 47 | 48 | const map = Levels[Map.calcCurrentLevel(currentLevel)] 49 | 50 | // the player can jump a bit higher than the map's height 51 | const paddingTop = 4 * TILE_SIZE 52 | 53 | this.size = { 54 | x: 0, 55 | y: 0, 56 | width: map[0].length * TILE_SIZE, 57 | height: map.length * TILE_SIZE + paddingTop 58 | } 59 | this.info = [] 60 | 61 | map.forEach((row, y) => { 62 | for (let i = 0; i < row.length; i++) { 63 | const tile = row.charAt(i) 64 | const x = i 65 | if (tile !== ' ') { 66 | let info = { ...config[tile.toString()], x: x * TILE_SIZE, y: y * TILE_SIZE + paddingTop } 67 | this.info.push(info) 68 | } 69 | } 70 | }) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/components/levelText.ts: -------------------------------------------------------------------------------- 1 | export default class LevelText extends Phaser.GameObjects.Text { 2 | constructor(public _scene: Phaser.Scene, level: number) { 3 | super(_scene, 0, 0, `Level: ${level + 1}`, { 4 | color: '#324951', 5 | fontSize: 56, 6 | fontWeight: 'bold', 7 | fontStyle: 'bold' 8 | }) 9 | _scene.add.existing(this) 10 | 11 | this.setScrollFactor(0) 12 | .setOrigin(0.5, 0) 13 | .setAlpha(0.5) 14 | .startTween() 15 | } 16 | 17 | adjustPosition() { 18 | this.x = this._scene.cameras.main.width / 2 19 | this.y = this._scene.cameras.main.height / 2 - 50 20 | } 21 | 22 | private tweensAsync = (config: { [key: string]: any }): Promise<{}> => { 23 | return new Promise(resolve => { 24 | this.scene.tweens.add({ 25 | ...config, 26 | onComplete: () => { 27 | if (config.onComplete) config.onComplete() 28 | resolve() 29 | } 30 | }) 31 | }) 32 | } 33 | 34 | private async startTween() { 35 | await this.tweensAsync({ 36 | targets: this, 37 | scaleX: 1.5, 38 | scaleY: 1.5, 39 | yoyo: true, 40 | delay: 500, 41 | duration: 200, 42 | onComplete: () => console.log('tween 1 completed') 43 | }) 44 | 45 | await this.tweensAsync({ 46 | targets: this, 47 | y: 10, 48 | scaleX: 0.5, 49 | scaleY: 0.5, 50 | ease: 'Sine.easeInOut', 51 | delay: 500, 52 | duration: 400, 53 | onComplete: () => console.log('tween 2 completed') 54 | }) 55 | 56 | this.setFontSize(28) 57 | this.setScale(1) 58 | 59 | await this.tweensAsync({ 60 | targets: this, 61 | alpha: 0, 62 | delay: 2000, 63 | duration: 400, 64 | onComplete: () => console.log('tween 3 completed') 65 | }) 66 | 67 | this.destroy() 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/components/enemies/enemiesGroup.ts: -------------------------------------------------------------------------------- 1 | import BeeSprite from './bee' 2 | import SlimeSprite from './slime' 3 | import EnemyClass from './enemyClass' 4 | 5 | export default class EnemiesGroup extends Phaser.GameObjects.Group { 6 | tiles: TilesConfig[] 7 | TILE_SIZE = 96 8 | constructor(scene: Phaser.Scene, tilesConfig: TilesConfig[]) { 9 | super(scene) 10 | 11 | this.tiles = tilesConfig.filter(tile => tile.type === 'tile') 12 | let enemyTypes = tilesConfig.filter(tile => tile.type === 'enemy') 13 | 14 | let enemies: Array = [] 15 | enemyTypes.forEach(enemy => { 16 | switch (enemy.texture) { 17 | case 'bee': 18 | enemies.push(new BeeSprite(scene, enemy.x, enemy.y)) 19 | break 20 | case 'slime': 21 | enemies.push(new SlimeSprite(scene, enemy.x, enemy.y)) 22 | break 23 | } 24 | }) 25 | this.addMultiple(enemies) 26 | } 27 | 28 | update() { 29 | // check if the enemy should change its direction 30 | // @ts-ignore 31 | this.children.iterate((enemy: BeeSprite | SlimeSprite) => { 32 | if (enemy.dead) return 33 | 34 | let enemyIsMovingRight = enemy.body.velocity.x >= 0 35 | 36 | let hasGroundDetection = this.tiles.filter(tile => { 37 | let enemyPositionX = enemyIsMovingRight ? enemy.body.right : enemy.body.left 38 | let x = enemyPositionX + 32 > tile.x && enemyPositionX - 32 < tile.x + this.TILE_SIZE 39 | let y = 40 | enemy.body.bottom + this.TILE_SIZE / 2 > tile.y && 41 | enemy.body.bottom + this.TILE_SIZE / 2 < tile.y + this.TILE_SIZE 42 | return x && y 43 | }) 44 | 45 | if (hasGroundDetection.length === 0) { 46 | //@ts-ignore 47 | enemy.body.setVelocityX(enemy.body.velocity.x * -1) 48 | enemy.setFlipX(!enemyIsMovingRight) 49 | } 50 | }, null) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/assets/spine/boy.atlas: -------------------------------------------------------------------------------- 1 | 2 | boy.png 3 | size: 512,128 4 | format: RGBA8888 5 | filter: Linear,Linear 6 | repeat: none 7 | blue/images/Body 8 | rotate: false 9 | xy: 99, 2 10 | size: 95, 124 11 | orig: 95, 124 12 | offset: 0, 0 13 | index: -1 14 | blue/images/LeftArm 15 | rotate: false 16 | xy: 414, 102 17 | size: 24, 24 18 | orig: 24, 24 19 | offset: 0, 0 20 | index: -1 21 | blue/images/Leftleg 22 | rotate: true 23 | xy: 331, 101 24 | size: 25, 28 25 | orig: 25, 28 26 | offset: 0, 0 27 | index: -1 28 | blue/images/RightArm 29 | rotate: false 30 | xy: 361, 102 31 | size: 25, 24 32 | orig: 25, 24 33 | offset: 0, 0 34 | index: -1 35 | blue/images/RightLeg 36 | rotate: false 37 | xy: 196, 43 38 | size: 28, 31 39 | orig: 28, 31 40 | offset: 0, 0 41 | index: -1 42 | blue/images/face1 43 | rotate: true 44 | xy: 196, 12 45 | size: 29, 26 46 | orig: 29, 26 47 | offset: 0, 0 48 | index: -1 49 | blue/images/face2 50 | rotate: false 51 | xy: 300, 100 52 | size: 29, 26 53 | orig: 29, 26 54 | offset: 0, 0 55 | index: -1 56 | green/images/Body 57 | rotate: false 58 | xy: 2, 2 59 | size: 95, 124 60 | orig: 95, 124 61 | offset: 0, 0 62 | index: -1 63 | green/images/LeftArm 64 | rotate: false 65 | xy: 388, 102 66 | size: 24, 24 67 | orig: 24, 24 68 | offset: 0, 0 69 | index: -1 70 | green/images/Leftleg 71 | rotate: false 72 | xy: 270, 97 73 | size: 28, 29 74 | orig: 28, 29 75 | offset: 0, 0 76 | index: -1 77 | green/images/RightArm 78 | rotate: false 79 | xy: 440, 102 80 | size: 24, 24 81 | orig: 24, 24 82 | offset: 0, 0 83 | index: -1 84 | green/images/RightLeg 85 | rotate: false 86 | xy: 238, 96 87 | size: 30, 30 88 | orig: 30, 30 89 | offset: 0, 0 90 | index: -1 91 | green/images/face1 92 | rotate: false 93 | xy: 196, 101 94 | size: 40, 25 95 | orig: 40, 25 96 | offset: 0, 0 97 | index: -1 98 | green/images/face2 99 | rotate: false 100 | xy: 196, 76 101 | size: 40, 23 102 | orig: 40, 23 103 | offset: 0, 0 104 | index: -1 105 | -------------------------------------------------------------------------------- /src/components/player/playerSpine.ts: -------------------------------------------------------------------------------- 1 | import Player from './player' 2 | 3 | export default class PlayerSpine { 4 | spine: any 5 | 6 | constructor( 7 | scene: Phaser.Scene, 8 | x: number, 9 | y: number, 10 | spine: string = 'boy', 11 | animation: string = 'idle', 12 | play: boolean = true 13 | ) { 14 | // @ts-ignore 15 | this.spine = scene.add.spine(x, y, spine, animation, play) 16 | this.spine.customParams = { 17 | animation, 18 | isKilling: false 19 | } 20 | this.spine.play(this.spine.customParams.animation, true) 21 | this.spine.setMix('run', 'idle', 0.3) 22 | this.spine.setMix('idle', 'run', 0.3) 23 | this.spine.setMix('jump', 'run', 0.2) 24 | this.spine.setMix('run', 'jump', 0.3) 25 | this.spine.setMix('idle', 'jump', 0.3) 26 | this.spine.setMix('jump', 'idle', 0.2) 27 | this.spine.setMix('jump', 'kill', 0.2) 28 | this.spine.setMix('kill', 'idle', 0.2) 29 | this.setSkin('blue') 30 | } 31 | 32 | getAttachments() { 33 | return this.spine.skeleton.skin.attachments 34 | } 35 | 36 | getSlots() { 37 | return this.spine.skeleton.slots 38 | } 39 | 40 | setAttachment(slotName: string, attachmentName: string) { 41 | this.spine.skeleton.setAttachment(slotName, attachmentName) 42 | } 43 | 44 | setSkin(newSkin: string) { 45 | this.spine.setSkin(null) 46 | this.spine.setSkinByName(newSkin) 47 | } 48 | 49 | setAnimation(animation: string, loop: boolean = false) { 50 | if (this.spine.customParams.animation !== animation) { 51 | this.spine.customParams.animation = animation 52 | this.spine.play(animation, loop) 53 | } 54 | } 55 | 56 | update(player: Player) { 57 | if (!player || !player.body) return 58 | 59 | // spine position 60 | this.spine.x = player.body.center.x 61 | this.spine.y = player.body.bottom + 8 62 | 63 | // spine animation 64 | if (player.body.blocked.down) { 65 | this.spine.customParams.isKilling = false 66 | const animation = Math.abs(player.body.velocity.x) >= 10 ? 'run' : 'idle' 67 | this.setAnimation(animation, true) 68 | } 69 | if (!player.body.blocked.down) { 70 | const animation = this.spine.customParams.isKilling ? 'kill' : 'jump' 71 | this.setAnimation(animation) 72 | } 73 | 74 | // spine flip 75 | if (player.flipX !== this.spine.flipX) this.spine.flipX = player.flipX 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/components/controls/controls.ts: -------------------------------------------------------------------------------- 1 | import ControlsSprite from './controlsSprite' 2 | 3 | export default class Controls { 4 | leftIsDown: boolean 5 | rightIsDown: boolean 6 | upIsDown: boolean 7 | buttons: { [key: string]: ControlsSprite } = {} 8 | 9 | private _width = 192 10 | private _height = 192 11 | private _scene: Phaser.Scene 12 | private _config: { type: string; rotation: number }[] 13 | 14 | constructor(scene: Phaser.Scene) { 15 | this._scene = scene 16 | 17 | this._config = [ 18 | { 19 | type: 'left', 20 | rotation: 1.5 * Math.PI 21 | }, 22 | { 23 | type: 'right', 24 | rotation: 0.5 * Math.PI 25 | }, 26 | { 27 | type: 'up', 28 | rotation: 0 29 | } 30 | ] 31 | this._config.forEach(el => { 32 | this.buttons[el.type] = new ControlsSprite(scene, 0, 0, el) 33 | }) 34 | } 35 | 36 | adjustPositions() { 37 | let width = this._scene.cameras.main.width 38 | let height = this._scene.cameras.main.height 39 | this.buttons.left.x = 130 40 | this.buttons.left.y = height - 130 41 | this.buttons.right.x = 130 * 3 42 | this.buttons.right.y = height - 130 43 | this.buttons.up.x = width - 130 44 | this.buttons.up.y = height - 130 45 | } 46 | 47 | update() { 48 | this.leftIsDown = false 49 | this.rightIsDown = false 50 | this.upIsDown = false 51 | 52 | let pointers = [this._scene.input.pointer1, this._scene.input.pointer2] 53 | let buttons = [this.buttons.left, this.buttons.right, this.buttons.up] 54 | 55 | // check which pointer pressed which button 56 | pointers.forEach(pointer => { 57 | if (pointer.isDown) { 58 | console.log(pointer.x, pointer.y) 59 | let hit = buttons.filter(btn => { 60 | let x = btn.x - this._width / 2 < pointer.x && btn.x + this._width / 2 > pointer.x 61 | let y = btn.y - this._height / 2 < pointer.y && btn.y + this._height / 2 > pointer.y 62 | return x && y 63 | }) 64 | if (hit.length === 1) { 65 | switch (hit[0].type) { 66 | case 'left': 67 | this.leftIsDown = true 68 | break 69 | case 'right': 70 | this.rightIsDown = true 71 | break 72 | case 'up': 73 | this.upIsDown = true 74 | break 75 | } 76 | } 77 | } 78 | }) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/components/player/player.ts: -------------------------------------------------------------------------------- 1 | import Controls from '../controls/controls' 2 | import PlayerSpine from './playerSpine' 3 | 4 | export default class Player extends Phaser.Physics.Arcade.Sprite { 5 | private _dead: boolean = false 6 | private _halt: boolean = false 7 | private mapSize: MapSize 8 | playerSpine: PlayerSpine 9 | 10 | constructor(scene: Phaser.Scene, player: TilesConfig, mapSize: MapSize, level: number) { 11 | super(scene, player.x, player.y, player.texture) 12 | scene.add.existing(this) 13 | scene.physics.add.existing(this) 14 | 15 | this.scene = scene 16 | this.mapSize = mapSize 17 | 18 | // scene.anims.create({ 19 | // key: 'walk', 20 | // frames: scene.anims.generateFrameNames('player'), 21 | // frameRate: 8, 22 | // repeat: -1 23 | // }) 24 | // this.play('walk') 25 | 26 | this.setVisible(false) 27 | 28 | this.setOrigin(0, 1) 29 | this.setDragX(1500) 30 | this.body.setSize(70, 132) 31 | this.body.setOffset(25, 24) 32 | 33 | let theSkin = level % 2 == 0 ? 'blue' : 'green' 34 | this.playerSpine = new PlayerSpine(scene, this.body.center.x, this.body.bottom) 35 | this.playerSpine.setSkin(theSkin) 36 | } 37 | 38 | kill() { 39 | this._dead = true 40 | 41 | // animate the camera if the player dies 42 | this.scene.cameras.main.shake(500, 0.025) 43 | this.scene.time.addEvent({ 44 | delay: 500, 45 | callback: () => this.scene.scene.restart() 46 | }) 47 | } 48 | 49 | killEnemy() { 50 | this.playerSpine.spine.customParams.isKilling = true 51 | this.setVelocityY(-600) 52 | } 53 | 54 | halt() { 55 | this.body.enable = false 56 | this._halt = true 57 | } 58 | 59 | update(cursors: any, controls: Controls) { 60 | if (this._halt || this._dead) return 61 | 62 | // check if out of camera and kill 63 | if (this.body.right < this.mapSize.x || this.body.left > this.mapSize.width || this.body.top > this.mapSize.height) 64 | this.kill() 65 | 66 | // controls left & right 67 | if (cursors.left.isDown || controls.leftIsDown) { 68 | this.setVelocityX(-500) 69 | this.setFlipX(true) 70 | } else if (cursors.right.isDown || controls.rightIsDown) { 71 | this.setVelocityX(550) 72 | this.setFlipX(false) 73 | } 74 | // controls up 75 | if ((cursors.up.isDown || cursors.space.isDown || controls.upIsDown) && this.body.blocked.down) { 76 | this.setVelocityY(-1250) 77 | } 78 | 79 | // update spine animation 80 | this.playerSpine.update(this) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/components/levels.ts: -------------------------------------------------------------------------------- 1 | type Level = string[] 2 | 3 | // prettier-ignore 4 | const emptyLevel: Level = [ 5 | ' ', 6 | ' ', 7 | ' ', 8 | ' ', 9 | ' ', 10 | ' ', 11 | ' ', 12 | ' ' 13 | ] 14 | 15 | // prettier-ignore 16 | const level0: Level = [ 17 | ' S ', 18 | ' [////] ', 19 | ' ', 20 | ' P G ', 21 | '[/////] [///]', 22 | ] 23 | 24 | // prettier-ignore 25 | const level1:Level=[ 26 | ' B ', 27 | ' [////] ', 28 | ' O O ', 29 | ' S B S S G ', 30 | '[//] [////] [////] [//////] ', 31 | ' O O ', 32 | ' P B ', 33 | '[///] [//] ', 34 | ] 35 | 36 | // prettier-ignore 37 | const level2: Level =[ 38 | ' P [//] G ', 39 | '[/] O B O [/] ', 40 | ' [//] [//] ', 41 | ' S B B ', 42 | ' O [//] B [///] ', 43 | ' O [///] [] O O ', 44 | '[] ', 45 | ' [/] [/] [/] ' 46 | ] 47 | 48 | // prettier-ignore 49 | const level3: Level = [ 50 | ' O ', 51 | ' O O ', 52 | ' [///] O [////] ', 53 | ' [///] S [//] ', 54 | ' [///] [///] ', 55 | ' S ', 56 | ' [///] O G ', 57 | ' O O [////] ', 58 | ' P S B ', 59 | '[//////] [///] [////] ' 60 | ] 61 | 62 | // prettier-ignore 63 | const level4: Level = [ 64 | ' O O S ', 65 | ' [//////] B G ', 66 | ' O [/////] [///////] [////] ', 67 | ' ', 68 | ' P S B B O S S S ', 69 | '[////////] [///////] [/] [////////////////////] ' 70 | ] 71 | 72 | const Levels: Level[] = [level0, level1, level2, level3, level4] 73 | export default Levels 74 | -------------------------------------------------------------------------------- /src/assets/spine/raw/boy/blue/boy-blue.json: -------------------------------------------------------------------------------- 1 | { 2 | "bones": [ 3 | { 4 | "name": "root" 5 | } 6 | ], 7 | "slots": [ 8 | { 9 | "name": "LeftArm", 10 | "bone": "root", 11 | "attachment": "LeftArm" 12 | }, 13 | { 14 | "name": "RightArm", 15 | "bone": "root", 16 | "attachment": "RightArm" 17 | }, 18 | { 19 | "name": "Leftleg", 20 | "bone": "root", 21 | "attachment": "Leftleg" 22 | }, 23 | { 24 | "name": "RightLeg", 25 | "bone": "root", 26 | "attachment": "RightLeg" 27 | }, 28 | { 29 | "name": "Body", 30 | "bone": "root", 31 | "attachment": "Body" 32 | }, 33 | { 34 | "name": "face1", 35 | "bone": "root", 36 | "attachment": "face1" 37 | } 38 | ], 39 | "animations": { 40 | "animation": {} 41 | }, 42 | "skeleton": { 43 | "images": "/home/yannick/Desktop/phaser3-typescript-platformer-example/src/assets/spine/raw/boy/blue/images" 44 | }, 45 | "skins": { 46 | "default": { 47 | "Body": { 48 | "Body": { 49 | "y": 4.265338700000001, 50 | "x": 113.36451500000001, 51 | "height": 123.97377, 52 | "width": 94.983428 53 | } 54 | }, 55 | "RightArm": { 56 | "RightArm": { 57 | "y": -25.856284499999994, 58 | "x": 146.6527175, 59 | "height": 24.478809, 60 | "width": 24.846913 61 | } 62 | }, 63 | "LeftArm": { 64 | "LeftArm": { 65 | "y": -25.856284499999994, 66 | "x": 75.4595505, 67 | "height": 24.478809, 68 | "width": 24.289479 69 | } 70 | }, 71 | "RightLeg": { 72 | "RightLeg": { 73 | "y": -56.98650849999999, 74 | "x": 134.315356, 75 | "height": 30.725257, 76 | "width": 28.17213 77 | } 78 | }, 79 | "face1": { 80 | "face1": { 81 | "y": -1.7439650000000029, 82 | "x": 120.57568150000002, 83 | "height": 25.712538, 84 | "width": 29.281701 85 | } 86 | }, 87 | "Leftleg": { 88 | "Leftleg": { 89 | "y": -58.39922249999999, 90 | "x": 94.209451, 91 | "height": 27.899825, 92 | "width": 25.28968 93 | } 94 | } 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /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 | Platformer PWA 27 | 28 | 29 | 64 | 65 | 66 | 67 |
68 |

loading...

69 |
70 | 71 |
72 | 73 | 80 | 81 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/game.ts: -------------------------------------------------------------------------------- 1 | import 'phaser' 2 | import MainScene from './scenes/mainScene' 3 | import PreloadScene from './scenes/preloadScene' 4 | // @ts-ignore 5 | import SpineWebGLPlugin from './plugins/SpineWebGLPlugin' 6 | 7 | type scaleMode = 'FIT' | 'SMOOTH' 8 | 9 | const DEFAULT_WIDTH: number = 1280 10 | const DEFAULT_HEIGHT: number = 720 11 | const MAX_WIDTH: number = DEFAULT_WIDTH * 1.5 12 | const MAX_HEIGHT: number = DEFAULT_HEIGHT * 1.5 13 | let SCALE_MODE: scaleMode = 'SMOOTH' // FIT OR SMOOTH 14 | 15 | window.addEventListener('load', () => { 16 | const config: GameConfig = { 17 | type: Phaser.WEBGL, 18 | backgroundColor: '#ffffff', 19 | parent: 'phaser-game', 20 | scale: { 21 | // The game will be scaled manually in the resize() 22 | mode: Phaser.Scale.NONE, 23 | width: DEFAULT_WIDTH, 24 | height: DEFAULT_HEIGHT 25 | }, 26 | plugins: { 27 | scene: [{ key: 'SpineWebGLPlugin', plugin: SpineWebGLPlugin, start: true, sceneKey: 'spine' }] 28 | }, 29 | scene: [PreloadScene, MainScene], 30 | physics: { 31 | default: 'arcade', 32 | arcade: { 33 | debug: false, 34 | gravity: { y: 2500 } 35 | } 36 | } 37 | } 38 | 39 | const game = new Phaser.Game(config) 40 | 41 | // the custom resize function 42 | const resize = () => { 43 | const w = window.innerWidth 44 | const h = window.innerHeight 45 | 46 | let width = DEFAULT_WIDTH 47 | let height = DEFAULT_HEIGHT 48 | let maxWidth = MAX_WIDTH 49 | let maxHeight = MAX_HEIGHT 50 | let scaleMode = SCALE_MODE 51 | 52 | let scale = Math.min(w / width, h / height) 53 | let newWidth = Math.min(w / scale, maxWidth) 54 | let newHeight = Math.min(h / scale, maxHeight) 55 | 56 | let defaultRatio = DEFAULT_WIDTH / DEFAULT_HEIGHT 57 | let maxRatioWidth = MAX_WIDTH / DEFAULT_HEIGHT 58 | let maxRatioHeight = DEFAULT_WIDTH / MAX_HEIGHT 59 | 60 | // smooth scaling 61 | let smooth = 1 62 | if (scaleMode === 'SMOOTH') { 63 | const maxSmoothScale = 1.15 64 | const normalize = (value: number, min: number, max: number) => { 65 | return (value - min) / (max - min) 66 | } 67 | if (width / height < w / h) { 68 | smooth = 69 | -normalize(newWidth / newHeight, defaultRatio, maxRatioWidth) / (1 / (maxSmoothScale - 1)) + maxSmoothScale 70 | } else { 71 | smooth = 72 | -normalize(newWidth / newHeight, defaultRatio, maxRatioHeight) / (1 / (maxSmoothScale - 1)) + maxSmoothScale 73 | } 74 | } 75 | 76 | // resize the game 77 | game.scale.resize(newWidth * smooth, newHeight * smooth) 78 | 79 | // scale the width and height of the css 80 | game.canvas.style.width = newWidth * scale + 'px' 81 | game.canvas.style.height = newHeight * scale + 'px' 82 | 83 | // center the game with css margin 84 | game.canvas.style.marginTop = `${(h - newHeight * scale) / 2}px` 85 | game.canvas.style.marginLeft = `${(w - newWidth * scale) / 2}px` 86 | } 87 | window.addEventListener('resize', event => { 88 | resize() 89 | }) 90 | resize() 91 | }) 92 | -------------------------------------------------------------------------------- /src/assets/spine/raw/boy/green/boy.json: -------------------------------------------------------------------------------- 1 | { 2 | "bones": [ 3 | { 4 | "name": "root" 5 | } 6 | ], 7 | "slots": [ 8 | { 9 | "name": "LeftArm", 10 | "bone": "root", 11 | "attachment": "LeftArm" 12 | }, 13 | { 14 | "name": "RightArm", 15 | "bone": "root", 16 | "attachment": "RightArm" 17 | }, 18 | { 19 | "name": "Leftleg", 20 | "bone": "root", 21 | "attachment": "Leftleg" 22 | }, 23 | { 24 | "name": "RightLeg", 25 | "bone": "root", 26 | "attachment": "RightLeg" 27 | }, 28 | { 29 | "name": "Body", 30 | "bone": "root", 31 | "attachment": "Body" 32 | }, 33 | { 34 | "name": "face1", 35 | "bone": "root", 36 | "attachment": "face1" 37 | }, 38 | { 39 | "name": "face2", 40 | "bone": "root", 41 | "attachment": "face2" 42 | } 43 | ], 44 | "animations": { 45 | "animation": {} 46 | }, 47 | "skeleton": { 48 | "images": "/home/yannick/Desktop/phaser3-typescript-platformer-example/src/assets/spine/raw/boy/images" 49 | }, 50 | "skins": { 51 | "default": { 52 | "Body": { 53 | "Body": { 54 | "y": 6.02301030000001, 55 | "x": 1.2767983000000047, 56 | "height": 123.76136, 57 | "width": 94.820693 58 | } 59 | }, 60 | "RightArm": { 61 | "RightArm": { 62 | "y": -25.683121, 63 | "x": 34.71175100000001, 64 | "height": 24.43687, 65 | "width": 24.396829 66 | } 67 | }, 68 | "face2": { 69 | "face2": { 70 | "y": 1.0744315000000029, 71 | "x": 8.584681500000006, 72 | "height": 22.840469, 73 | "width": 40.211536 74 | } 75 | }, 76 | "LeftArm": { 77 | "LeftArm": { 78 | "y": -25.683121, 79 | "x": -36.67465433088239, 80 | "height": 24.43687, 81 | "width": 24.024965 82 | } 83 | }, 84 | "RightLeg": { 85 | "RightLeg": { 86 | "y": -56.12224700000001, 87 | "x": 21.352553, 88 | "height": 30.425424, 89 | "width": 29.802297 90 | } 91 | }, 92 | "face1": { 93 | "face1": { 94 | "y": -0.23050349999999753, 95 | "x": 8.584681500000006, 96 | "height": 25.450339, 97 | "width": 40.211536 98 | } 99 | }, 100 | "Leftleg": { 101 | "Leftleg": { 102 | "y": -57.676764999999996, 103 | "x": -16.624955, 104 | "height": 28.8391, 105 | "width": 27.687299 106 | } 107 | } 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /src/scenes/mainScene.ts: -------------------------------------------------------------------------------- 1 | import { Map } from '../components/map' 2 | import TilesGroup from '../components/tiles/tilesGroup' 3 | import Player from '../components/player/player' 4 | import CoinGroup from '../components/coins/coinGroup' 5 | import BeeSprite from '../components/enemies/bee' 6 | import EnemiesGroup from '../components/enemies/enemiesGroup' 7 | import GoalSprite from '../components/goalSprite' 8 | import Controls from '../components/controls/controls' 9 | import LevelText from '../components/levelText' 10 | import Background from '../components/background' 11 | import MiniMap from '../components/miniMap' 12 | import PhaserVersionText from '../components/phaserVersionText' 13 | 14 | export default class MainScene extends Phaser.Scene { 15 | player: Player 16 | tilesGroup: TilesGroup 17 | cursors: Phaser.Input.Keyboard.CursorKeys 18 | background: Background 19 | enemiesGroup: EnemiesGroup 20 | controls: Controls 21 | goal: GoalSprite 22 | level: number 23 | miniMap: MiniMap 24 | constructor() { 25 | super({ 26 | key: 'MainScene' 27 | }) 28 | } 29 | 30 | init(props: { level?: number }) { 31 | const { level = 0 } = props 32 | this.level = Map.calcCurrentLevel(level) 33 | } 34 | 35 | create() { 36 | const map = new Map(this.level) 37 | 38 | this.cameras.main.setBackgroundColor('#ade6ff') 39 | this.cameras.main.fadeIn() 40 | 41 | this.cameras.main.setBounds(map.size.x, map.size.y, map.size.width, map.size.height) 42 | this.physics.world.setBounds(map.size.x, map.size.y, map.size.width, map.size.height) 43 | 44 | this.input.addPointer(1) 45 | this.cursors = this.input.keyboard.createCursorKeys() 46 | 47 | this.background = new Background(this) 48 | this.tilesGroup = new TilesGroup(this, map.info.filter((el: TilesConfig) => el.type === 'tile')) 49 | this.goal = new GoalSprite(this, map.info.filter((el: TilesConfig) => el.type === 'goal')[0]) 50 | this.player = new Player(this, map.info.filter((el: TilesConfig) => el.type === 'player')[0], map.size, this.level) 51 | this.enemiesGroup = new EnemiesGroup(this, map.info) 52 | const coinGroup = new CoinGroup(this, map.info.filter((el: TilesConfig) => el.type === 'coin')) 53 | this.controls = new Controls(this) 54 | const levelText = new LevelText(this, this.level) 55 | const phaserVersion = new PhaserVersionText(this, 0, 0, `Phaser v${Phaser.VERSION}`) 56 | 57 | this.cameras.main.startFollow(this.player) 58 | 59 | this.physics.add.collider(this.tilesGroup, this.player) 60 | this.physics.add.collider(this.tilesGroup, this.enemiesGroup) 61 | // @ts-ignore 62 | this.physics.add.overlap(this.player, this.enemiesGroup, (player: Player, enemy: BeeSprite) => { 63 | if (enemy.dead) return 64 | if (enemy.body.touching.up && player.body.touching.down) { 65 | player.killEnemy() 66 | enemy.kill() 67 | } else { 68 | player.kill() 69 | } 70 | }) 71 | //@ts-ignore 72 | this.physics.add.overlap(this.player, coinGroup, (player, coin) => coin.collect()) 73 | //@ts-ignore 74 | this.physics.add.overlap(this.player, this.goal, (player: Player, goal: GoalSprite) => { 75 | player.halt() 76 | goal.nextLevel(this, this.level) 77 | }) 78 | 79 | this.miniMap = new MiniMap( 80 | this, 81 | 10, 82 | 10, 83 | Math.min(map.size.width / 8, (map.size.height / 8) * 2.5), 84 | map.size.height / 8, 85 | map 86 | ) 87 | this.miniMap.setIgnore([ 88 | this.background, 89 | levelText, 90 | this.controls.buttons.up, 91 | this.controls.buttons.left, 92 | this.controls.buttons.right, 93 | phaserVersion 94 | ]) 95 | this.miniMap.update(this.player) 96 | 97 | // remove the loading screen 98 | let loadingScreen = document.getElementById('loading-screen') 99 | if (loadingScreen) { 100 | loadingScreen.classList.add('transparent') 101 | this.time.addEvent({ 102 | delay: 1000, 103 | callback: () => { 104 | // @ts-ignore 105 | loadingScreen.remove() 106 | } 107 | }) 108 | } 109 | 110 | // the resize function 111 | const resize = () => { 112 | this.controls.adjustPositions() 113 | phaserVersion.x = this.cameras.main.width - 15 114 | phaserVersion.y = 15 115 | this.background.adjustPosition() 116 | levelText.adjustPosition() 117 | } 118 | 119 | this.scale.on('resize', (gameSize: any) => { 120 | this.cameras.main.width = gameSize.width 121 | this.cameras.main.height = gameSize.height 122 | //this.cameras.resize(gameSize.width, gameSize.height) 123 | resize() 124 | }) 125 | resize() 126 | } 127 | 128 | update() { 129 | this.background.parallax() 130 | this.controls.update() 131 | this.enemiesGroup.update() 132 | this.player.update(this.cursors, this.controls) 133 | this.miniMap.update(this.player) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/assets/spine/boy.json: -------------------------------------------------------------------------------- 1 | { 2 | "skeleton": { 3 | "hash": "4ZwtcB8EyJhEwrE7WLNNp+McTMc", 4 | "spine": "3.7.87", 5 | "width": 97.45, 6 | "height": 142.36, 7 | "images": "./boy/", 8 | "audio": "/home/yannick/Desktop/phaser3-typescript-platformer-example/src/assets/spine" 9 | }, 10 | "bones": [ 11 | { "name": "root" }, 12 | { "name": "body", "parent": "root", "length": 92.83, "rotation": 89.53, "x": -0.2, "y": 26.66 }, 13 | { "name": "rightArm", "parent": "root", "length": 24.9, "rotation": -41.27, "x": 28.07, "y": 56.07 }, 14 | { "name": "leftArm", "parent": "root", "length": 27.15, "rotation": -140.71, "x": -31.14, "y": 54.93 }, 15 | { "name": "leftLeg", "parent": "root", "length": 24.7, "rotation": -118.88, "x": -15.48, "y": 22.07 }, 16 | { "name": "rightLeg", "parent": "root", "length": 23.39, "rotation": -65.79, "x": 20.04, "y": 23.98 } 17 | ], 18 | "slots": [ 19 | { "name": "LeftArm", "bone": "leftArm", "attachment": "leftArm" }, 20 | { "name": "RightArm", "bone": "rightArm", "attachment": "rightArm" }, 21 | { "name": "Leftleg", "bone": "leftLeg", "attachment": "leftLeg" }, 22 | { "name": "RightLeg", "bone": "rightLeg", "attachment": "rightLeg" }, 23 | { "name": "Body", "bone": "body", "attachment": "boy" }, 24 | { "name": "Faces", "bone": "body", "attachment": "face1" } 25 | ], 26 | "skins": { 27 | "default": {}, 28 | "blue": { 29 | "Body": { 30 | "boy": { "name": "blue/images/Body", "x": 54.76, "y": 0.67, "rotation": -89.53, "width": 95, "height": 124 } 31 | }, 32 | "Faces": { 33 | "face1": { "name": "blue/images/face1", "x": 48.09, "y": -8.39, "rotation": -89.31, "width": 29, "height": 26 }, 34 | "face2": { "name": "blue/images/face2", "x": 49.01, "y": -9.12, "rotation": -89.53, "width": 29, "height": 26 } 35 | }, 36 | "LeftArm": { 37 | "leftArm": { 38 | "name": "blue/images/LeftArm", 39 | "x": 8.89, 40 | "y": -0.55, 41 | "rotation": 140.71, 42 | "width": 24, 43 | "height": 24 44 | } 45 | }, 46 | "Leftleg": { 47 | "leftLeg": { 48 | "name": "blue/images/Leftleg", 49 | "x": 5.17, 50 | "y": 0.56, 51 | "rotation": 129.35, 52 | "width": 25, 53 | "height": 28 54 | } 55 | }, 56 | "RightArm": { 57 | "rightArm": { 58 | "name": "blue/images/RightArm", 59 | "x": 7.91, 60 | "y": -1.51, 61 | "rotation": 41.27, 62 | "width": 25, 63 | "height": 24 64 | } 65 | }, 66 | "RightLeg": { 67 | "rightLeg": { 68 | "name": "blue/images/RightLeg", 69 | "x": 3.45, 70 | "y": -3.23, 71 | "rotation": 51.63, 72 | "width": 28, 73 | "height": 31 74 | } 75 | } 76 | }, 77 | "green": { 78 | "Body": { 79 | "boy": { "name": "green/images/Body", "x": 54.76, "y": 0.67, "rotation": -89.53, "width": 95, "height": 124 } 80 | }, 81 | "Faces": { 82 | "face1": { 83 | "name": "green/images/face1", 84 | "x": 48.09, 85 | "y": -8.39, 86 | "rotation": -89.31, 87 | "width": 40, 88 | "height": 25 89 | }, 90 | "face2": { "name": "green/images/face2", "x": 49.01, "y": -9.12, "rotation": -89.53, "width": 40, "height": 23 } 91 | }, 92 | "LeftArm": { 93 | "leftArm": { 94 | "name": "green/images/LeftArm", 95 | "x": 8.89, 96 | "y": -0.55, 97 | "rotation": 140.71, 98 | "width": 24, 99 | "height": 24 100 | } 101 | }, 102 | "Leftleg": { 103 | "leftLeg": { 104 | "name": "green/images/Leftleg", 105 | "x": 5.17, 106 | "y": 0.56, 107 | "rotation": 129.35, 108 | "width": 28, 109 | "height": 29 110 | } 111 | }, 112 | "RightArm": { 113 | "rightArm": { 114 | "name": "green/images/RightArm", 115 | "x": 7.91, 116 | "y": -1.51, 117 | "rotation": 41.27, 118 | "width": 24, 119 | "height": 24 120 | } 121 | }, 122 | "RightLeg": { 123 | "rightLeg": { 124 | "name": "green/images/RightLeg", 125 | "x": 3.45, 126 | "y": -3.23, 127 | "rotation": 51.63, 128 | "width": 30, 129 | "height": 30 130 | } 131 | } 132 | } 133 | }, 134 | "animations": { 135 | "idle": { 136 | "bones": { 137 | "body": { 138 | "translate": [ 139 | { "time": 0, "x": 0, "y": 0 }, 140 | { "time": 0.8333, "x": 0, "y": -4 }, 141 | { "time": 1.6667, "x": 0, "y": 0 } 142 | ] 143 | }, 144 | "leftArm": { 145 | "rotate": [{ "time": 0, "angle": 0 }, { "time": 0.8333, "angle": 13.35 }, { "time": 1.6667, "angle": 0 }], 146 | "translate": [ 147 | { "time": 0, "x": 0, "y": 0 }, 148 | { "time": 0.8333, "x": 0, "y": -6 }, 149 | { "time": 1.6667, "x": 0, "y": 0 } 150 | ] 151 | }, 152 | "rightArm": { 153 | "rotate": [{ "time": 0, "angle": 0 }, { "time": 0.8333, "angle": -13.2 }, { "time": 1.6667, "angle": 0 }], 154 | "translate": [ 155 | { "time": 0, "x": 0, "y": 0 }, 156 | { "time": 0.8333, "x": 0, "y": -6 }, 157 | { "time": 1.6667, "x": 0, "y": 0 } 158 | ] 159 | } 160 | } 161 | }, 162 | "jump": { 163 | "bones": { 164 | "body": { 165 | "rotate": [{ "time": 0, "angle": 0 }, { "time": 0.0667, "angle": -3.6 }, { "time": 0.3333, "angle": -1.2 }], 166 | "translate": [ 167 | { "time": 0, "x": 0, "y": 0 }, 168 | { "time": 0.0667, "x": 0, "y": -6 }, 169 | { "time": 0.3333, "x": 0, "y": 2 } 170 | ] 171 | }, 172 | "leftArm": { 173 | "rotate": [{ "time": 0, "angle": 0 }, { "time": 0.0667, "angle": 16.8 }, { "time": 0.3333, "angle": -59.13 }], 174 | "translate": [ 175 | { "time": 0, "x": 0, "y": 0 }, 176 | { "time": 0.0667, "x": 4, "y": -10 }, 177 | { "time": 0.3333, "x": 4, "y": 0 } 178 | ] 179 | }, 180 | "rightArm": { 181 | "rotate": [{ "time": 0, "angle": 0 }, { "time": 0.0667, "angle": -10.8 }, { "time": 0.3333, "angle": 58.37 }], 182 | "translate": [ 183 | { "time": 0, "x": 0, "y": 0 }, 184 | { "time": 0.0667, "x": 0.55, "y": -8.55 }, 185 | { "time": 0.3333, "x": -2, "y": 0 } 186 | ] 187 | }, 188 | "leftLeg": { 189 | "rotate": [ 190 | { "time": 0, "angle": -0.34 }, 191 | { "time": 0.0667, "angle": -10.8 }, 192 | { "time": 0.3333, "angle": 13.04 } 193 | ], 194 | "translate": [{ "time": 0, "x": 0, "y": 0 }, { "time": 0.3333, "x": 4, "y": 0 }] 195 | }, 196 | "rightLeg": { 197 | "rotate": [{ "time": 0, "angle": 0 }, { "time": 0.0667, "angle": -6 }, { "time": 0.3333, "angle": -35.82 }] 198 | } 199 | } 200 | }, 201 | "kill": { 202 | "slots": { "Faces": { "attachment": [{ "time": 0, "name": "face2" }] } }, 203 | "bones": { 204 | "leftArm": { 205 | "rotate": [ 206 | { "time": 0, "angle": 0 }, 207 | { "time": 0.1667, "angle": 9.6 }, 208 | { "time": 0.3333, "angle": 0 }, 209 | { "time": 0.6, "angle": -67.72 }, 210 | { "time": 0.7667, "angle": 0 } 211 | ], 212 | "translate": [ 213 | { "time": 0.3333, "x": 0, "y": 0 }, 214 | { "time": 0.6, "x": 4, "y": 0 }, 215 | { "time": 0.7667, "x": 0, "y": 0 } 216 | ] 217 | }, 218 | "rightArm": { 219 | "rotate": [ 220 | { "time": 0, "angle": 0 }, 221 | { "time": 0.3333, "angle": 64.43 }, 222 | { "time": 0.6, "angle": 6.96 }, 223 | { "time": 0.7667, "angle": 0 } 224 | ], 225 | "translate": [ 226 | { "time": 0, "x": 0, "y": 0 }, 227 | { "time": 0.3333, "x": -2, "y": 1.03 }, 228 | { "time": 0.7667, "x": 0, "y": 0 } 229 | ] 230 | }, 231 | "body": { 232 | "rotate": [ 233 | { "time": 0, "angle": 0 }, 234 | { "time": 0.3333, "angle": 4.13 }, 235 | { "time": 0.5333, "angle": -1.41 }, 236 | { "time": 0.7667, "angle": 3.39 } 237 | ], 238 | "translate": [ 239 | { "time": 0, "x": 0, "y": 0 }, 240 | { "time": 0.3333, "x": 0, "y": 1.37 }, 241 | { "time": 0.7667, "x": 0, "y": 0 } 242 | ] 243 | } 244 | } 245 | }, 246 | "run": { 247 | "bones": { 248 | "leftArm": { 249 | "rotate": [ 250 | { "time": 0, "angle": 34.89 }, 251 | { "time": 0.2667, "angle": -13.36 }, 252 | { "time": 0.5333, "angle": 34.89 } 253 | ], 254 | "translate": [ 255 | { "time": 0, "x": 4.47, "y": -2.24 }, 256 | { "time": 0.2667, "x": 6, "y": 0 }, 257 | { "time": 0.5333, "x": 4.47, "y": -2.24 } 258 | ] 259 | }, 260 | "rightArm": { 261 | "rotate": [ 262 | { "time": 0, "angle": 16.13 }, 263 | { "time": 0.2667, "angle": -31.32 }, 264 | { "time": 0.5333, "angle": 16.13 } 265 | ] 266 | }, 267 | "rightLeg": { 268 | "rotate": [ 269 | { "time": 0, "angle": -9.94 }, 270 | { "time": 0.2667, "angle": -74.75 }, 271 | { "time": 0.5333, "angle": -9.94 } 272 | ], 273 | "translate": [ 274 | { "time": 0, "x": 0, "y": 0 }, 275 | { "time": 0.2667, "x": -35.91, "y": 1.68 }, 276 | { "time": 0.5333, "x": 0, "y": 0 } 277 | ] 278 | }, 279 | "leftLeg": { 280 | "rotate": [ 281 | { "time": 0, "angle": -29.58 }, 282 | { "time": 0.2667, "angle": 64.02 }, 283 | { "time": 0.5333, "angle": -29.58 } 284 | ], 285 | "translate": [ 286 | { "time": 0, "x": 0, "y": 0 }, 287 | { "time": 0.2667, "x": 34.21, "y": 4 }, 288 | { "time": 0.5333, "x": 0, "y": 0 } 289 | ] 290 | }, 291 | "body": { 292 | "rotate": [{ "time": 0, "angle": -3.6 }], 293 | "translate": [ 294 | { "time": 0, "x": 0, "y": 0 }, 295 | { "time": 0.1333, "x": 0, "y": 4 }, 296 | { "time": 0.2667, "x": 0, "y": 0 }, 297 | { "time": 0.4, "x": 0, "y": 4 }, 298 | { "time": 0.5333, "x": 0, "y": 0 } 299 | ] 300 | } 301 | } 302 | } 303 | } 304 | } 305 | --------------------------------------------------------------------------------