diff --git a/src/boilerplates/webpack-boilerplate/src/config.ts b/src/boilerplates/webpack-boilerplate/src/config.ts index 88efdf2a..5878e590 100644 --- a/src/boilerplates/webpack-boilerplate/src/config.ts +++ b/src/boilerplates/webpack-boilerplate/src/config.ts @@ -12,7 +12,8 @@ export const GameConfig: Phaser.Types.Core.GameConfig = { physics: { default: 'arcade', arcade: { - gravity: { y: 200 } + gravity: { y: 200 }, + debug: true, } }, scene: [MainScene] diff --git a/src/boilerplates/webpack-boilerplate/src/objects/redhat.ts b/src/boilerplates/webpack-boilerplate/src/objects/redhat.ts index 0f0c26c2..d3af7fd2 100644 --- a/src/boilerplates/webpack-boilerplate/src/objects/redhat.ts +++ b/src/boilerplates/webpack-boilerplate/src/objects/redhat.ts @@ -17,8 +17,9 @@ export class Redhat extends Phaser.GameObjects.Image { private initPhysics() { this.scene.physics.world.enable(this); - this.body.setVelocity(100, 200); + this.body.setVelocity(300, 400); this.body.setBounce(1, 1); + this.body.setSize(200,200) this.body.setCollideWorldBounds(true); } } diff --git a/src/games/asteroid/src/config.ts b/src/games/asteroid/src/config.ts index 44815feb..664781fa 100644 --- a/src/games/asteroid/src/config.ts +++ b/src/games/asteroid/src/config.ts @@ -20,7 +20,7 @@ export const GameConfig: Phaser.Types.Core.GameConfig = { physics: { default: 'arcade', arcade: { - debug: false + debug: true } }, backgroundColor: '#000000', diff --git a/src/games/breakout/assets/images/flares.json b/src/games/breakout/assets/images/flares.json new file mode 100644 index 00000000..9b9c2d8a --- /dev/null +++ b/src/games/breakout/assets/images/flares.json @@ -0,0 +1,57 @@ +{"frames": { + + "blue": + { + "frame": {"x":2,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }, + "green": + { + "frame": {"x":132,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }, + "red": + { + "frame": {"x":262,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }, + "white": + { + "frame": {"x":392,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }, + "yellow": + { + "frame": {"x":522,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }}, + "meta": { + "app": "http://www.codeandweb.com/texturepacker", + "version": "1.0", + "image": "flares.png", + "format": "RGBA8888", + "size": {"w":652,"h":132}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:f2781d89823d5a67fc31381af364b421:da82646b19b2f0c08684086824b1e581:71625947cf221c10549b852c13ffedc7$" + } + } \ No newline at end of file diff --git a/src/games/breakout/assets/images/flares.png b/src/games/breakout/assets/images/flares.png new file mode 100644 index 00000000..df04af53 Binary files /dev/null and b/src/games/breakout/assets/images/flares.png differ diff --git a/src/games/breakout/assets/pack.json b/src/games/breakout/assets/pack.json index 028b1494..0faa220b 100644 --- a/src/games/breakout/assets/pack.json +++ b/src/games/breakout/assets/pack.json @@ -6,6 +6,12 @@ "key": "font", "textureURL": "./assets/font/font.png", "fontDataURL": "./assets/font/font.fnt" + }, + { + "type": "atlas", + "key": "flares", + "textureURL": "./assets/images/flares.png", + "atlasURL": "./assets/images/flares.json" } ] } diff --git a/src/games/breakout/src/config.ts b/src/games/breakout/src/config.ts index 474540f4..fbc0ae84 100644 --- a/src/games/breakout/src/config.ts +++ b/src/games/breakout/src/config.ts @@ -1,5 +1,5 @@ -import { BootScene } from './scenes/boot-scene'; -import { GameScene } from './scenes/game-scene'; +import { BootScene } from './scenes/bootScene'; +import { GameScene } from './scenes/gameScene'; export const GameConfig: Phaser.Types.Core.GameConfig = { title: 'Breakout', @@ -14,7 +14,8 @@ export const GameConfig: Phaser.Types.Core.GameConfig = { physics: { default: 'arcade', arcade: { - gravity: { x: 0, y: 0 } + gravity: { x: 0, y: 0 }, + // debug: true, } }, scale: { diff --git a/src/games/breakout/src/interfaces/interfaces.ts b/src/games/breakout/src/interfaces/interfaces.ts index 9333f361..4adc8602 100644 --- a/src/games/breakout/src/interfaces/interfaces.ts +++ b/src/games/breakout/src/interfaces/interfaces.ts @@ -1,4 +1,4 @@ -export interface IRectangleConstructor { +interface IRectangleConstructor { scene: Phaser.Scene; x: number; y: number; diff --git a/src/games/breakout/src/objects/ball.ts b/src/games/breakout/src/objects/ball.ts index 7442068b..29cc199b 100644 --- a/src/games/breakout/src/objects/ball.ts +++ b/src/games/breakout/src/objects/ball.ts @@ -1,8 +1,8 @@ -import { IRectangleConstructor } from '../interfaces/interfaces'; + export class Ball extends Phaser.GameObjects.Rectangle { body: Phaser.Physics.Arcade.Body; - + emitter!: Phaser.GameObjects.Particles.ParticleEmitter; constructor(aParams: IRectangleConstructor) { super( aParams.scene, @@ -16,6 +16,7 @@ export class Ball extends Phaser.GameObjects.Rectangle { this.initRectangle(); this.initPhysics(); + this.initPractices(); this.scene.add.existing(this); } @@ -32,8 +33,21 @@ export class Ball extends Phaser.GameObjects.Rectangle { this.body.setCollideWorldBounds(); } + private initPractices(): void { + const particles = this.scene.add.particles('flares'); + this.emitter = particles.createEmitter({ + frame: 'red', + speed: 100, + scale: { start: 0.1, end: 0 }, + angle: {min: 60, max:100}, + blendMode: 'ADD' + }).stop(); + this.emitter.startFollow(this, 5,5); + } + public applyInitVelocity(): void { this.body.setVelocity(Phaser.Math.RND.between(-200, 200), 200); this.body.speed = 800; + this.emitter.start() } } diff --git a/src/games/breakout/src/objects/brick.ts b/src/games/breakout/src/objects/brick.ts index 12686e27..3c132478 100644 --- a/src/games/breakout/src/objects/brick.ts +++ b/src/games/breakout/src/objects/brick.ts @@ -1,8 +1,8 @@ -import { IRectangleConstructor } from '../interfaces/interfaces'; +import { settings } from '../settings'; export class Brick extends Phaser.GameObjects.Rectangle { body: Phaser.Physics.Arcade.Body; - + isdestroyed!: boolean; constructor(aParams: IRectangleConstructor) { super( aParams.scene, @@ -13,18 +13,55 @@ export class Brick extends Phaser.GameObjects.Rectangle { aParams.fillColor, aParams.fillAlpha ); - + this.isdestroyed = false; this.initRectangle(); - this.initPhysics(); + this.initPhysics(); this.scene.add.existing(this); } private initRectangle(): void { this.setOrigin(0); + this.setScale(0,0); + const x = this.x / (settings.BRICK.WIDTH + settings.BRICK.SPACING); + const y = (this.y - settings.BRICK.MARGIN_TOP) / (settings.BRICK.HEIGHT + settings.BRICK.SPACING); + const WIDTH = settings.LEVELS[settings.currentLevel].WIDTH; + this.scene.tweens.add({ + targets: this, + scaleX: 1, + scaleY: 1, + ease: 'Sine.easeInOut', + duration: 300, + delay: this.scene.tweens.stagger(50, {}), + repeat: 0, + yoyo: false, + onComplete: () => { + this.body.setSize(settings.BRICK.WIDTH, settings.BRICK.HEIGHT); + } + }); + + } + + update(): void { + if(this.y > this.scene.scale.height - 100) + this.destroy(); } private initPhysics(): void { this.scene.physics.world.enable(this); this.body.setImmovable(true); } + + public destroyBrick(): void { + this.isdestroyed = true; + this.setOrigin(0.5,0.5); + this.body.setAccelerationY(150); + this.body.checkCollision.none = true; + this.scene.tweens.add({ + targets: this, + duration: 1000, + yoyo: false, + angle: 360, + repeat: -1, + }); + } } diff --git a/src/games/breakout/src/objects/player.ts b/src/games/breakout/src/objects/player.ts index 28f5e382..d19d8d9c 100644 --- a/src/games/breakout/src/objects/player.ts +++ b/src/games/breakout/src/objects/player.ts @@ -1,4 +1,3 @@ -import { IRectangleConstructor } from '../interfaces/interfaces'; export class Player extends Phaser.GameObjects.Rectangle { body: Phaser.Physics.Arcade.Body; diff --git a/src/games/breakout/src/scenes/bootScene.ts b/src/games/breakout/src/scenes/bootScene.ts new file mode 100644 index 00000000..402dc245 --- /dev/null +++ b/src/games/breakout/src/scenes/bootScene.ts @@ -0,0 +1,61 @@ +export class BootScene extends Phaser.Scene { + private loadingBar: Phaser.GameObjects.Graphics; + private progressBar: Phaser.GameObjects.Graphics; + + constructor() { + super({ + key: 'BootScene' + }); + } + + preload(): void { + // set the background and create loading bar + // this.cameras.main.setBackgroundColor(0x98d687); + this.createLoadingbar(); + + // pass value to change the loading bar fill + this.load.on( + 'progress', + function (value: number) { + this.progressBar.clear(); + this.progressBar.fillStyle(0xfff6d3, 1); + this.progressBar.fillRect( + this.cameras.main.width / 4, + this.cameras.main.height / 2 - 16, + (this.cameras.main.width / 2) * value, + 16 + ); + }, + this + ); + + // delete bar graphics, when loading complete + this.load.on( + 'complete', + function () { + this.progressBar.destroy(); + this.loadingBar.destroy(); + }, + this + ); + + // load out package + this.load.pack('preload', './assets/pack.json', 'preload'); + } + + update(): void { + this.scene.start('GameScene'); + } + + private createLoadingbar(): void { + this.loadingBar = this.add.graphics(); + this.loadingBar.fillStyle(0x5dae47, 1); + this.loadingBar.fillRect( + this.cameras.main.width / 4 - 2, + this.cameras.main.height / 2 - 18, + this.cameras.main.width / 2 + 4, + 20 + ); + this.progressBar = this.add.graphics(); + } +} diff --git a/src/games/breakout/src/scenes/game-scene.ts b/src/games/breakout/src/scenes/gameScene.ts similarity index 76% rename from src/games/breakout/src/scenes/game-scene.ts rename to src/games/breakout/src/scenes/gameScene.ts index 1d885927..e438d0ec 100644 --- a/src/games/breakout/src/scenes/game-scene.ts +++ b/src/games/breakout/src/scenes/gameScene.ts @@ -8,6 +8,7 @@ const BRICK_COLORS: number[] = [0xf2e49b, 0xbed996, 0xf2937e, 0xffffff]; export class GameScene extends Phaser.Scene { private ball: Ball; private bricks: Phaser.GameObjects.Group; + private isBrickVisible!: boolean; private player: Player; private scoreText: Phaser.GameObjects.BitmapText; private highScoreText: Phaser.GameObjects.BitmapText; @@ -23,22 +24,27 @@ export class GameScene extends Phaser.Scene { settings.highScore = settings.score; settings.score = 0; settings.lives = 3; + this.isBrickVisible = false; } create(): void { + // game objects // ------------ // bricks - this.bricks = this.add.group(); + this.bricks = this.add.group({ + /*classType: Brick,*/ + runChildUpdate: true + }); const BRICKS = settings.LEVELS[settings.currentLevel].BRICKS; const WIDTH = settings.LEVELS[settings.currentLevel].WIDTH; const HEIGHT = settings.LEVELS[settings.currentLevel].HEIGHT; for (let y = 0; y < HEIGHT; y++) { for (let x = 0; x < WIDTH; x++) { - this.bricks.add( - new Brick({ + + const brick = new Brick({ scene: this, x: (settings.BRICK.WIDTH + settings.BRICK.SPACING) * x, y: @@ -48,16 +54,39 @@ export class GameScene extends Phaser.Scene { height: settings.BRICK.HEIGHT, fillColor: BRICK_COLORS[BRICKS[y * 14 + x]] }) - ); + this.bricks.add(brick); } } + // wait for the brick init finishes + this.time.delayedCall(50*WIDTH*HEIGHT+600, ()=>{ + this.isBrickVisible = true; + this.bricks.getChildren().map((brick, index) => { + let y = Math.floor(index/WIDTH); + let x = index - y * WIDTH; + this.tweens.add({ + targets: brick, + scaleX: 0.3, + scaleY: 0.3, + ease: 'Sine.easeInOut', + duration: 300, + delay: (x+y) * 50, + yoyo: true, + repeat: -1, + repeatDelay: 5000, + onRepeat: () => { + console.log("loop finished") + } + }); + }) + }, [], this) + // player this.player = new Player({ scene: this, x: +this.game.config.width / 2 - 20, y: +this.game.config.height - 50, - width: 50, + width: 100, height: 10 }); @@ -112,9 +141,9 @@ export class GameScene extends Phaser.Scene { update(): void { this.player.update(); - - if (this.player.body.velocity.x !== 0 && !this.ball.visible) { + if (this.player.body.velocity.x !== 0 && !this.ball.visible && this.isBrickVisible) { this.ball.setPosition(this.player.x, this.player.y - 200); + this.ball.applyInitVelocity(); this.ball.setVisible(true); } @@ -131,17 +160,20 @@ export class GameScene extends Phaser.Scene { this.ball.setPosition(0, 0); this.ball.body.setVelocity(0); this.ball.setVisible(false); + this.ball.emitter.stop(); } } } private ballBrickCollision(ball: Ball, brick: Brick): void { - brick.destroy(); + // brick.destroy(); + brick.destroyBrick(); settings.score += 10; this.events.emit('scoreChanged'); if (this.bricks.countActive() === 0) { // all bricks are gone! + this.scene.restart(); } } diff --git a/src/games/breakout/tsconfig.json b/src/games/breakout/tsconfig.json index 12c795f0..b8a5ff55 100644 --- a/src/games/breakout/tsconfig.json +++ b/src/games/breakout/tsconfig.json @@ -3,7 +3,8 @@ "target": "ES6", "module": "CommonJS", "moduleResolution": "node", - "noImplicitAny": true + "noImplicitAny": true, + "typeRoots": ["*/**/*.interface.ts"] }, "include": ["src/**/*"], "exclude": ["node_modules"] diff --git a/src/games/candy-crush/assets/images/UFO2.png b/src/games/candy-crush/assets/images/UFO2.png new file mode 100644 index 00000000..8f613689 Binary files /dev/null and b/src/games/candy-crush/assets/images/UFO2.png differ diff --git a/src/games/candy-crush/assets/images/flares.json b/src/games/candy-crush/assets/images/flares.json new file mode 100644 index 00000000..9b9c2d8a --- /dev/null +++ b/src/games/candy-crush/assets/images/flares.json @@ -0,0 +1,57 @@ +{"frames": { + + "blue": + { + "frame": {"x":2,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }, + "green": + { + "frame": {"x":132,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }, + "red": + { + "frame": {"x":262,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }, + "white": + { + "frame": {"x":392,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }, + "yellow": + { + "frame": {"x":522,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }}, + "meta": { + "app": "http://www.codeandweb.com/texturepacker", + "version": "1.0", + "image": "flares.png", + "format": "RGBA8888", + "size": {"w":652,"h":132}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:f2781d89823d5a67fc31381af364b421:da82646b19b2f0c08684086824b1e581:71625947cf221c10549b852c13ffedc7$" + } + } \ No newline at end of file diff --git a/src/games/candy-crush/assets/images/flares.png b/src/games/candy-crush/assets/images/flares.png new file mode 100644 index 00000000..df04af53 Binary files /dev/null and b/src/games/candy-crush/assets/images/flares.png differ diff --git a/src/games/candy-crush/assets/pack.json b/src/games/candy-crush/assets/pack.json index d6c0bcbd..573b65e4 100644 --- a/src/games/candy-crush/assets/pack.json +++ b/src/games/candy-crush/assets/pack.json @@ -60,6 +60,12 @@ "type": "image", "key": "starcookie2", "url": "./assets/images/starcookie2.png" + }, + { + "type": "atlas", + "key": "flares", + "textureURL": "./assets/images/flares.png", + "atlasURL": "./assets/images/flares.json" } ] } diff --git a/src/games/candy-crush/src/config.ts b/src/games/candy-crush/src/config.ts index 777b4c1a..4c2bac48 100644 --- a/src/games/candy-crush/src/config.ts +++ b/src/games/candy-crush/src/config.ts @@ -1,5 +1,5 @@ -import { BootScene } from './scenes/boot-scene'; -import { GameScene } from './scenes/game-scene'; +import { BootScene } from './scenes/bootScene'; +import { GameScene } from './scenes/gameScene'; export const GameConfig: Phaser.Types.Core.GameConfig = { title: 'Candy crush', diff --git a/src/games/candy-crush/src/const/const.ts b/src/games/candy-crush/src/const/const.ts index 3e86e5de..e11ea3a4 100644 --- a/src/games/candy-crush/src/const/const.ts +++ b/src/games/candy-crush/src/const/const.ts @@ -11,12 +11,12 @@ export let CONST = { 'croissant', 'cupcake', 'donut', - 'eclair', - 'macaroon', - 'pie', - 'poptart1', - 'poptart2', - 'starcookie1', - 'starcookie2' + // 'eclair', + // 'macaroon', + // 'pie', + // 'poptart1', + // 'poptart2', + // 'starcookie1', + // 'starcookie2' ] }; diff --git a/src/games/candy-crush/src/objects/tile.ts b/src/games/candy-crush/src/objects/tile.ts index 5bdf2759..0817c3bc 100644 --- a/src/games/candy-crush/src/objects/tile.ts +++ b/src/games/candy-crush/src/objects/tile.ts @@ -1,13 +1,69 @@ +import { CONST } from '../const/const'; import { IImageConstructor } from '../interfaces/image.interface'; export class Tile extends Phaser.GameObjects.Image { + public tween: Phaser.Tweens.Tween; + particles!: Phaser.GameObjects.Particles.ParticleEmitter; constructor(aParams: IImageConstructor) { super(aParams.scene, aParams.x, aParams.y, aParams.texture, aParams.frame); // set image settings this.setOrigin(0, 0); this.setInteractive(); - this.scene.add.existing(this); } + + public initTween(){ + this.tween = this.scene.tweens.add({ + targets: this, + scaleX: 0.5, + scaleY: 0.5, + ease: 'Sine.easeInOut', + duration: 500, + delay: (this.x / CONST.tileWidth + this.y / CONST.tileHeight) * 50, + yoyo: true, + repeat: 0, + }); + + } + + public initTweenMatch(){ + this.particles = this.scene.add.particles('flares').createEmitter({ + frame: "red", + lifespan: 500, + speed: { min: 400, max: 600 }, + angle: {min: 40, max: 80}, + gravityY: 300, + scale: { start: 0.1, end: 0 }, + quantity: 2, + blendMode: 'ADD', + follow: this, + followOffset: {x:CONST.tileWidth/2, y: CONST.tileHeight/2} + }).stop(); + + this.setDepth(1); + this.scene.tweens.timeline({ + targets: this, + ease: 'Sine.easeInOut', + duration: 1000, + yoyo: false, + tweens: [{ + scaleX: 1.5, + scaleY: 1.5, + onComplete:()=>{ + this.particles.start(); + } + }, + { + scaleX: 0.5, + scaleY: 0.5, + x: -10, + y: -10, + }], + onComplete:()=>{ + this.destroy(); + this.particles.stop(); + } + }); + } } diff --git a/src/games/candy-crush/src/scenes/boot-scene.ts b/src/games/candy-crush/src/scenes/bootScene.ts similarity index 100% rename from src/games/candy-crush/src/scenes/boot-scene.ts rename to src/games/candy-crush/src/scenes/bootScene.ts diff --git a/src/games/candy-crush/src/scenes/game-scene.ts b/src/games/candy-crush/src/scenes/gameScene.ts similarity index 75% rename from src/games/candy-crush/src/scenes/game-scene.ts rename to src/games/candy-crush/src/scenes/gameScene.ts index 02d9fc02..bb8672e4 100644 --- a/src/games/candy-crush/src/scenes/game-scene.ts +++ b/src/games/candy-crush/src/scenes/gameScene.ts @@ -4,10 +4,13 @@ import { Tile } from '../objects/tile'; export class GameScene extends Phaser.Scene { // Variables private canMove: boolean; + private match: boolean; // Grid with tiles private tileGrid: Tile[][]; + emitter!: Phaser.GameObjects.Particles.ParticleEmitter; + // Selected Tiles private firstSelectedTile: Tile; private secondSelectedTile: Tile; @@ -19,8 +22,11 @@ export class GameScene extends Phaser.Scene { } init(): void { + this.initPractices(); + // Init variables this.canMove = true; + this.match = false; // set background color this.cameras.main.setBackgroundColor(0x78aade); @@ -31,6 +37,7 @@ export class GameScene extends Phaser.Scene { this.tileGrid[y] = []; for (let x = 0; x < CONST.gridWidth; x++) { this.tileGrid[y][x] = this.addTile(x, y); + this.tileGrid[y][x].initTween(); } } @@ -45,6 +52,36 @@ export class GameScene extends Phaser.Scene { this.checkMatches(); } + create(){ + // after 10 seconds create tween + this.time.addEvent({ delay: 10000, callback: ()=>{ + if(!this.match){ + for (let y = 0; y < CONST.gridHeight; y++) { + for (let x = 0; x < CONST.gridWidth; x++) { + this.tileGrid[y][x].initTween(); + } + } + } + }, loop: true, callbackScope: this }) + } + + update(time: number, delta: number): void { + if(!this.firstSelectedTile){ + this.emitter.stop(); + } + } + + initPractices(): void { + const particles = this.add.particles('flares'); + var circle = new Phaser.Geom.Circle(CONST.tileWidth/2, CONST.tileHeight/2, (CONST.tileWidth/2)); + this.emitter = particles.createEmitter({ + frame: 'red', + lifespan: 500, + scale: { start: 0.1, end: 0 }, + emitZone: { type: 'edge', source: circle, quantity: 120} + }) + } + /** * Add a new random tile at the specified position. * @param x @@ -73,13 +110,16 @@ export class GameScene extends Phaser.Scene { * @param event */ private tileDown(pointer: any, gameobject: any, event: any): void { + this.match = true; if (this.canMove) { if (!this.firstSelectedTile) { this.firstSelectedTile = gameobject; + this.emitter.stop(); + this.emitter.start(); + this.emitter.startFollow(this.firstSelectedTile) } else { // So if we are here, we must have selected a second tile this.secondSelectedTile = gameobject; - let dx = Math.abs(this.firstSelectedTile.x - this.secondSelectedTile.x) / CONST.tileWidth; @@ -92,6 +132,12 @@ export class GameScene extends Phaser.Scene { this.canMove = false; this.swapTiles(); } + else{ + this.firstSelectedTile = gameobject; + this.emitter.stop(); + this.emitter.start(); + this.emitter.startFollow(this.firstSelectedTile) + } } } } @@ -100,9 +146,17 @@ export class GameScene extends Phaser.Scene { * This function will take care of the swapping of the two selected tiles. * It will only work, if two tiles have been selected. */ - private swapTiles(): void { + private swapTiles() { if (this.firstSelectedTile && this.secondSelectedTile) { - // Get the position of the two tiles + this.emitter.stop(); + var firstSelectedTile = this.firstSelectedTile; + var secondSelectedTile = this.secondSelectedTile; + if(this.canMove){ + firstSelectedTile = this.secondSelectedTile; + secondSelectedTile = this.firstSelectedTile; + } + + // Get the position of the two tiles let firstTilePosition = { x: this.firstSelectedTile.x, y: this.firstSelectedTile.y @@ -123,28 +177,40 @@ export class GameScene extends Phaser.Scene { // Move them on the screen with tweens this.add.tween({ - targets: this.firstSelectedTile, - x: this.secondSelectedTile.x, - y: this.secondSelectedTile.y, + targets: firstSelectedTile, + x: secondSelectedTile.x, + y: secondSelectedTile.y, ease: 'Linear', + scaleX: 1.5, + scaleY: 1.5, duration: 400, repeat: 0, - yoyo: false + yoyo: false, }); this.add.tween({ - targets: this.secondSelectedTile, - x: this.firstSelectedTile.x, - y: this.firstSelectedTile.y, + targets: secondSelectedTile, + x: firstSelectedTile.x, + y: firstSelectedTile.y, ease: 'Linear', duration: 400, repeat: 0, yoyo: false, + }); + + this.tweens.add({ + targets: [firstSelectedTile], + ease: 'Sine.easeInOut', + duration: 200, + scaleX: 1, + scaleY: 1, + repeat: 0, + delay: 400, + yoyo: false, onComplete: () => { this.checkMatches(); } - }); - + }) this.firstSelectedTile = this.tileGrid[ firstTilePosition.y / CONST.tileHeight ][firstTilePosition.x / CONST.tileWidth]; @@ -164,16 +230,25 @@ export class GameScene extends Phaser.Scene { //Remove the tiles this.removeTileGroup(matches); // Move the tiles currently on the board into their new positions - this.resetTile(); - //Fill the board with new tiles wherever there is an empty spot - this.fillTile(); - this.tileUp(); - this.checkMatches(); + this.time.delayedCall(2000, ()=>{ + for(var i=0; i< 7; i++) { + this.resetTile(); + } + + //Fill the board with new tiles wherever there is an empty spot + this.time.delayedCall(500, ()=>{ + this.fillTile(); + this.tileUp(); + this.checkMatches(); + },[], this) + }, [], this) + } else { // No match so just swap the tiles back to their original position and reset - this.swapTiles(); - this.tileUp(); this.canMove = true; + this.match = false; + this.swapTiles() + this.tileUp(); } } @@ -212,17 +287,17 @@ export class GameScene extends Phaser.Scene { private fillTile(): void { //Check for blank spaces in the grid and add new tiles at that position - for (var y = 0; y < this.tileGrid.length; y++) { - for (var x = 0; x < this.tileGrid[y].length; x++) { - if (this.tileGrid[y][x] === undefined) { - //Found a blank spot so lets add animate a tile there - let tile = this.addTile(x, y); - - //And also update our "theoretical" grid - this.tileGrid[y][x] = tile; + for (var y = 0; y < this.tileGrid.length; y++) { + for (var x = 0; x < this.tileGrid[y].length; x++) { + if (this.tileGrid[y][x] === undefined) { + //Found a blank spot so lets add animate a tile there + let tile = this.addTile(x, y); + //And also update our "theoretical" grid + this.tileGrid[y][x] = tile; + } } } - } + this.match = false; } private tileUp(): void { @@ -243,7 +318,8 @@ export class GameScene extends Phaser.Scene { // Remove the tile from the theoretical grid if (tilePos.x !== -1 && tilePos.y !== -1) { - tile.destroy(); + tile.initTweenMatch(); + this.match = true; this.tileGrid[tilePos.y][tilePos.x] = undefined; } } diff --git a/src/games/coin-runner/assets/images/flares.json b/src/games/coin-runner/assets/images/flares.json new file mode 100644 index 00000000..9b9c2d8a --- /dev/null +++ b/src/games/coin-runner/assets/images/flares.json @@ -0,0 +1,57 @@ +{"frames": { + + "blue": + { + "frame": {"x":2,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }, + "green": + { + "frame": {"x":132,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }, + "red": + { + "frame": {"x":262,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }, + "white": + { + "frame": {"x":392,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }, + "yellow": + { + "frame": {"x":522,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }}, + "meta": { + "app": "http://www.codeandweb.com/texturepacker", + "version": "1.0", + "image": "flares.png", + "format": "RGBA8888", + "size": {"w":652,"h":132}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:f2781d89823d5a67fc31381af364b421:da82646b19b2f0c08684086824b1e581:71625947cf221c10549b852c13ffedc7$" + } + } \ No newline at end of file diff --git a/src/games/coin-runner/assets/images/flares.png b/src/games/coin-runner/assets/images/flares.png new file mode 100644 index 00000000..df04af53 Binary files /dev/null and b/src/games/coin-runner/assets/images/flares.png differ diff --git a/src/games/coin-runner/assets/pack.json b/src/games/coin-runner/assets/pack.json new file mode 100644 index 00000000..56883df2 --- /dev/null +++ b/src/games/coin-runner/assets/pack.json @@ -0,0 +1,27 @@ +{ + "preload": { + "files": [ + { + "type": "image", + "key": "coin", + "url": "./assets/images/coin.png" + }, + { + "type": "image", + "key": "player", + "url": "./assets/images/player.png" + }, + { + "type": "image", + "key": "background", + "url": "./assets/images/background.png" + }, + { + "type": "atlas", + "key": "flares", + "textureURL": "./assets/images/flares.png", + "atlasURL": "./assets/images/flares.json" + } + ] + } +} diff --git a/src/games/coin-runner/src/config.ts b/src/games/coin-runner/src/config.ts index 8082ddd3..f690848a 100644 --- a/src/games/coin-runner/src/config.ts +++ b/src/games/coin-runner/src/config.ts @@ -1,4 +1,5 @@ -import { GameScene } from './scenes/game-scene'; +import { BootScene } from './scenes/bootScene'; +import { GameScene } from './scenes/gameScene'; export const GameConfig: Phaser.Types.Core.GameConfig = { title: 'Coin Runner', @@ -8,7 +9,7 @@ export const GameConfig: Phaser.Types.Core.GameConfig = { height: 576, type: Phaser.AUTO, parent: 'game', - scene: [GameScene], + scene: [BootScene, GameScene], input: { keyboard: true }, diff --git a/src/games/coin-runner/src/interfaces/image.interface.ts b/src/games/coin-runner/src/interfaces/image.interface.ts index 4e54a6db..184b070e 100644 --- a/src/games/coin-runner/src/interfaces/image.interface.ts +++ b/src/games/coin-runner/src/interfaces/image.interface.ts @@ -1,4 +1,4 @@ -export interface IImageConstructor { +interface IImageConstructor { scene: Phaser.Scene; x: number; y: number; diff --git a/src/games/coin-runner/src/objects/coin.ts b/src/games/coin-runner/src/objects/coin.ts index 863a78a9..445b6e35 100644 --- a/src/games/coin-runner/src/objects/coin.ts +++ b/src/games/coin-runner/src/objects/coin.ts @@ -1,4 +1,3 @@ -import { IImageConstructor } from '../interfaces/image.interface'; export class Coin extends Phaser.GameObjects.Image { private centerOfScreen: number; @@ -23,6 +22,16 @@ export class Coin extends Phaser.GameObjects.Image { private initImage(): void { this.setOrigin(0.5, 0.5); + this.setScale(0.01,1); + this.scene.tweens.add({ + targets: this, + scaleX: 1, + flipX: true, + ease: 'Sine.easeInOut', + duration: 1000, + yoyo: true, + repeat: -1 + }); } private initEvents(): void { @@ -64,4 +73,43 @@ export class Coin extends Phaser.GameObjects.Image { this.lastPosition = 'right'; } } + + public playerHitCoin(){ + // emitter + var particles = this.scene.add.particles('flares'); + const emitterUpCoin = particles.createEmitter({ + frame: 'yellow', + x: this.x, + y: this.y, + quantity: 2, + speed: { random: [50, 100] }, + lifespan: { random: [200, 400]}, + scale: { start: 0.2, end: 0 }, + angle: { random: true, start: 0, end: 270 }, + blendMode: 'ADD' + }) + + const xVals = [this.x,100, 300, 100, this.scene.sys.canvas.width / 2] + const yVals = [this.y,200, 100, 150, this.scene.sys.canvas.height - 50] + + this.scene.tweens.addCounter({ + from: 0, + to: 1, + ease: Phaser.Math.Easing.Sine.InOut, + duration: 1000, + onUpdate: tween => { + const v = tween.getValue() + const x = Phaser.Math.Interpolation.CatmullRom(xVals, v) + const y = Phaser.Math.Interpolation.CatmullRom(yVals, v) + + emitterUpCoin.setPosition(x, y) + }, + onComplete: () => { + emitterUpCoin.stop() + this.scene.time.delayedCall(1000, () => { + particles.removeEmitter(emitterUpCoin); + }) + } + }) + } } diff --git a/src/games/coin-runner/src/objects/player.ts b/src/games/coin-runner/src/objects/player.ts index 1fc33d69..765cff5e 100644 --- a/src/games/coin-runner/src/objects/player.ts +++ b/src/games/coin-runner/src/objects/player.ts @@ -1,4 +1,3 @@ -import { IImageConstructor } from '../interfaces/image.interface'; export class Player extends Phaser.GameObjects.Image { private cursors: Phaser.Types.Input.Keyboard.CursorKeys; diff --git a/src/games/breakout/src/scenes/boot-scene.ts b/src/games/coin-runner/src/scenes/bootScene.ts similarity index 96% rename from src/games/breakout/src/scenes/boot-scene.ts rename to src/games/coin-runner/src/scenes/bootScene.ts index f5f5d9dc..14b980bf 100644 --- a/src/games/breakout/src/scenes/boot-scene.ts +++ b/src/games/coin-runner/src/scenes/bootScene.ts @@ -10,7 +10,7 @@ export class BootScene extends Phaser.Scene { preload(): void { // set the background and create loading bar - //this.cameras.main.setBackgroundColor(0x98d687); + this.cameras.main.setBackgroundColor(0x98d687); this.createLoadingbar(); // pass value to change the loading bar fill diff --git a/src/games/coin-runner/src/scenes/game-scene.ts b/src/games/coin-runner/src/scenes/gameScene.ts similarity index 82% rename from src/games/coin-runner/src/scenes/game-scene.ts rename to src/games/coin-runner/src/scenes/gameScene.ts index 830ce12f..e4a7a45a 100644 --- a/src/games/coin-runner/src/scenes/game-scene.ts +++ b/src/games/coin-runner/src/scenes/gameScene.ts @@ -14,12 +14,6 @@ export class GameScene extends Phaser.Scene { }); } - preload(): void { - this.load.image('background', './assets/images/background.png'); - this.load.image('player', './assets/images/player.png'); - this.load.image('coin', './assets/images/coin.png'); - } - init(): void { this.collectedCoins = 0; } @@ -56,6 +50,7 @@ export class GameScene extends Phaser.Scene { color: '#000000' } ); + } update(): void { @@ -70,13 +65,17 @@ export class GameScene extends Phaser.Scene { this.coin.getBounds() ) ) { - this.updateCoinStatus(); + this.updateCoinStatus(); } } private updateCoinStatus(): void { - this.collectedCoins++; - this.coinsCollectedText.setText(this.collectedCoins + ''); + this.coin.playerHitCoin(); + this.time.delayedCall(1000, () => { + + this.collectedCoins++; + this.coinsCollectedText.setText(this.collectedCoins + ''); + }) this.coin.changePosition(); } } diff --git a/src/games/coin-runner/tsconfig.json b/src/games/coin-runner/tsconfig.json index 12c795f0..b8a5ff55 100644 --- a/src/games/coin-runner/tsconfig.json +++ b/src/games/coin-runner/tsconfig.json @@ -3,7 +3,8 @@ "target": "ES6", "module": "CommonJS", "moduleResolution": "node", - "noImplicitAny": true + "noImplicitAny": true, + "typeRoots": ["*/**/*.interface.ts"] }, "include": ["src/**/*"], "exclude": ["node_modules"] diff --git a/src/games/endless-runner/assets/images/flares.json b/src/games/endless-runner/assets/images/flares.json new file mode 100644 index 00000000..9b9c2d8a --- /dev/null +++ b/src/games/endless-runner/assets/images/flares.json @@ -0,0 +1,57 @@ +{"frames": { + + "blue": + { + "frame": {"x":2,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }, + "green": + { + "frame": {"x":132,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }, + "red": + { + "frame": {"x":262,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }, + "white": + { + "frame": {"x":392,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }, + "yellow": + { + "frame": {"x":522,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }}, + "meta": { + "app": "http://www.codeandweb.com/texturepacker", + "version": "1.0", + "image": "flares.png", + "format": "RGBA8888", + "size": {"w":652,"h":132}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:f2781d89823d5a67fc31381af364b421:da82646b19b2f0c08684086824b1e581:71625947cf221c10549b852c13ffedc7$" + } + } \ No newline at end of file diff --git a/src/games/endless-runner/assets/images/flares.png b/src/games/endless-runner/assets/images/flares.png new file mode 100644 index 00000000..df04af53 Binary files /dev/null and b/src/games/endless-runner/assets/images/flares.png differ diff --git a/src/games/endless-runner/assets/pack.json b/src/games/endless-runner/assets/pack.json new file mode 100644 index 00000000..a19db3c0 --- /dev/null +++ b/src/games/endless-runner/assets/pack.json @@ -0,0 +1,12 @@ +{ + "preload": { + "files": [ + { + "type": "atlas", + "key": "flares", + "textureURL": "./assets/images/flares.png", + "atlasURL": "./assets/images/flares.json" + } + ] + } +} diff --git a/src/games/endless-runner/index.html b/src/games/endless-runner/index.html index eaf00987..9600c31e 100644 --- a/src/games/endless-runner/index.html +++ b/src/games/endless-runner/index.html @@ -6,7 +6,7 @@ Endless Runner - +

Endless Runner

diff --git a/src/games/endless-runner/src/config.ts b/src/games/endless-runner/src/config.ts index 70ddd9b7..ed0607f2 100644 --- a/src/games/endless-runner/src/config.ts +++ b/src/games/endless-runner/src/config.ts @@ -1,3 +1,4 @@ +import { BootScene } from './scenes/boot-scene'; import { GameScene } from './scenes/game-scene'; export const GameConfig: Phaser.Types.Core.GameConfig = { @@ -5,7 +6,7 @@ export const GameConfig: Phaser.Types.Core.GameConfig = { url: 'https://github.com/digitsensitive/phaser3-typescript', version: '1.0', type: Phaser.AUTO, - scene: [GameScene], + scene: [BootScene, GameScene], input: { mouse: true }, @@ -22,5 +23,5 @@ export const GameConfig: Phaser.Types.Core.GameConfig = { width: 960, height: 640 }, - backgroundColor: 0x4ac7ff + // backgroundColor: 0x4ac7ff }; diff --git a/src/games/endless-runner/src/scenes/boot-scene.ts b/src/games/endless-runner/src/scenes/boot-scene.ts new file mode 100644 index 00000000..16f52efd --- /dev/null +++ b/src/games/endless-runner/src/scenes/boot-scene.ts @@ -0,0 +1,59 @@ +export class BootScene extends Phaser.Scene { + private loadingBar: Phaser.GameObjects.Graphics; + private progressBar: Phaser.GameObjects.Graphics; + + constructor() { + super({ + key: 'BootScene' + }); + } + + preload(): void { + // create loading bar + this.createLoadingbar(); + // pass value to change the loading bar fill + this.load.on( + 'progress', + function (value: number) { + this.progressBar.clear(); + this.progressBar.fillStyle(0xfff6d3, 1); + this.progressBar.fillRect( + this.cameras.main.width / 4, + this.cameras.main.height / 2 - 16, + (this.cameras.main.width / 2) * value, + 16 + ); + }, + this + ); + + // delete bar graphics, when loading complete + this.load.on( + 'complete', + function () { + this.progressBar.destroy(); + this.loadingBar.destroy(); + }, + this + ); + + // load out package + this.load.pack('preload', './assets/pack.json', 'preload'); + } + + update(): void { + this.scene.start('GameScene'); + } + + private createLoadingbar(): void { + this.loadingBar = this.add.graphics(); + this.loadingBar.fillStyle(0x5dae47, 1); + this.loadingBar.fillRect( + this.cameras.main.width / 4 - 2, + this.cameras.main.height / 2 - 18, + this.cameras.main.width / 2 + 4, + 20 + ); + this.progressBar = this.add.graphics(); + } +} diff --git a/src/games/endless-runner/src/scenes/game-scene.ts b/src/games/endless-runner/src/scenes/game-scene.ts index 5c23a21e..0358354c 100644 --- a/src/games/endless-runner/src/scenes/game-scene.ts +++ b/src/games/endless-runner/src/scenes/game-scene.ts @@ -6,6 +6,8 @@ export class GameScene extends Phaser.Scene { private isPlayerJumping: boolean; private loadingBar: Phaser.GameObjects.Rectangle; private loadingBarTween: Phaser.Tweens.Tween; + private checkTween!: boolean; + private emitter!: Phaser.GameObjects.Particles.ParticleEmitter constructor() { super({ @@ -16,9 +18,13 @@ export class GameScene extends Phaser.Scene { init(): void { this.isPlayerJumping = false; settings.createTowerXPosition = 0; + this.checkTween = false; } create(): void { + + this.initPractices(); + this.loadingBar = this.add .rectangle( 0, @@ -45,7 +51,7 @@ export class GameScene extends Phaser.Scene { .pause(); this.towers = this.add.group(); - + for (let i = 0; i < settings.MAX_ACTIVE_TOWERS; i++) { this.spawnNewTower(); @@ -74,6 +80,37 @@ export class GameScene extends Phaser.Scene { ); // setup input + this.initHandleInput(); + + // setup camera + this.cameras.main.setBounds( + 0, + 0, + +this.game.config.width, + +this.game.config.height + ); + + + this.emitter.startFollow(this.player,settings.BLOCK_WIDTH/2,settings.BLOCK_WIDTH/2); + this.player.body.velocity.y = 1; + + this.cameras.main.startFollow(this.player); + } + + private initPractices(){ + // emitter + const particles = this.add.particles('flares'); + + this.emitter = particles.createEmitter({ + frame: 'red', + speed: 100, + gravityY: 475, + scale: { start: 0.2, end: 0 }, + blendMode: 'ADD' + }); + } + + private initHandleInput(){ this.input.on( 'pointerdown', () => { @@ -84,15 +121,6 @@ export class GameScene extends Phaser.Scene { this ); this.input.on('pointerup', this.playerJump, this); - - // setup camera - this.cameras.main.setBounds( - 0, - 0, - +this.game.config.width, - +this.game.config.height - ); - this.cameras.main.startFollow(this.player); } update(): void { @@ -113,6 +141,31 @@ export class GameScene extends Phaser.Scene { if (this.player.y > this.game.config.height) { this.scene.start('GameScene'); } + + if(this.player.body.velocity.y !=0){ + this.emitter.start(); + }else{ + this.emitter.stop(); + if(!this.checkTween) { + this.checkTween = true; + var tweenUpDown = this.tweens.add({ + targets: this.player, + y: this.player.y - 10, + ease: 'Power1', + duration: 200, + yoyo: true, + repeat : 0, + onComplete:()=>{ + tweenUpDown.remove(); + } + }) + } + } + + if(this.player.body.velocity.y <0){ + this.emitter.setAngle({min: 100, max: 180}); + } + } private spawnNewTower(): void { @@ -153,6 +206,7 @@ export class GameScene extends Phaser.Scene { const playerBody = this.player.body as Phaser.Physics.Arcade.Body; playerBody.setVelocityY(-this.loadingBar.width); this.isPlayerJumping = true; + this.checkTween = false; this.loadingBarTween.stop(); this.loadingBar.width = 0; } diff --git a/src/games/space-invaders/assets/images/flares.json b/src/games/space-invaders/assets/images/flares.json new file mode 100644 index 00000000..9b9c2d8a --- /dev/null +++ b/src/games/space-invaders/assets/images/flares.json @@ -0,0 +1,57 @@ +{"frames": { + + "blue": + { + "frame": {"x":2,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }, + "green": + { + "frame": {"x":132,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }, + "red": + { + "frame": {"x":262,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }, + "white": + { + "frame": {"x":392,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }, + "yellow": + { + "frame": {"x":522,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }}, + "meta": { + "app": "http://www.codeandweb.com/texturepacker", + "version": "1.0", + "image": "flares.png", + "format": "RGBA8888", + "size": {"w":652,"h":132}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:f2781d89823d5a67fc31381af364b421:da82646b19b2f0c08684086824b1e581:71625947cf221c10549b852c13ffedc7$" + } + } \ No newline at end of file diff --git a/src/games/space-invaders/assets/images/flares.png b/src/games/space-invaders/assets/images/flares.png new file mode 100644 index 00000000..df04af53 Binary files /dev/null and b/src/games/space-invaders/assets/images/flares.png differ diff --git a/src/games/space-invaders/assets/pack.json b/src/games/space-invaders/assets/pack.json index bc82e913..0cd944f4 100644 --- a/src/games/space-invaders/assets/pack.json +++ b/src/games/space-invaders/assets/pack.json @@ -22,6 +22,17 @@ "key": "player", "url": "./assets/sprites/player.png" }, + { + "type": "image", + "key": "star", + "url": "https://cdn.shopify.com/s/files/1/3003/0152/files/nebula_star_100x_db655bb7-85f7-433d-8a52-d2e5e7cfbf5d_100x.png?v=1607703030" + }, + { + "type": "atlas", + "key": "flares", + "textureURL": "./assets/images/flares.png", + "atlasURL": "./assets/images/flares.json" + }, { "type": "spritesheet", "key": "octopus", diff --git a/src/games/space-invaders/src/objects/enemy.ts b/src/games/space-invaders/src/objects/enemy.ts index 823b3f25..55681f11 100644 --- a/src/games/space-invaders/src/objects/enemy.ts +++ b/src/games/space-invaders/src/objects/enemy.ts @@ -139,6 +139,18 @@ export class Enemy extends Phaser.GameObjects.Sprite { this.lives -= 1; if (this.lives === 0) { this.setActive(false); + var particles = this.scene.add.particles('flares').createEmitter({ + frame: 'yellow', + x: this.x, + y: this.y, + lifespan: 200, + speed: { min: 10, max: 50 }, + angle: {min: 0, max: 360}, + scale: { start: 0.1, end: 0 }, + quantity: 1, + maxParticles: 10, + blendMode: 'ADD' + }); } else { this.isHurt = true; } diff --git a/src/games/space-invaders/src/objects/player.ts b/src/games/space-invaders/src/objects/player.ts index 820b167d..2ac19a5d 100644 --- a/src/games/space-invaders/src/objects/player.ts +++ b/src/games/space-invaders/src/objects/player.ts @@ -9,6 +9,7 @@ export class Player extends Phaser.GameObjects.Image { private flyingSpeed: number; private lastShoot: number; private shootingKey: Phaser.Input.Keyboard.Key; + private emitter!: Phaser.GameObjects.Particles.ParticleEmitter; public getBullets(): Phaser.GameObjects.Group { return this.bullets; @@ -21,10 +22,26 @@ export class Player extends Phaser.GameObjects.Image { this.initImage(); this.initInput(); this.initPhysics(); - + this.initParticles(); this.scene.add.existing(this); } + private initParticles(){ + var particles = this.scene.add.particles('flares'); + + this.emitter = particles.createEmitter({ + frame: 'red', + lifespan: 200, + speed: { min: 200, max: 300 }, + angle: {min: 80, max: 100}, + gravityY: 300, + scale: { start: 0.1, end: 0 }, + quantity: 1, + blendMode: 'ADD' + }); + this.emitter.startFollow(this,0,6); + } + private initVariables(): void { this.bullets = this.scene.add.group({ runChildUpdate: true diff --git a/src/games/space-invaders/src/scenes/menu-scene.ts b/src/games/space-invaders/src/scenes/menu-scene.ts index 7adc572b..9a802bd2 100644 --- a/src/games/space-invaders/src/scenes/menu-scene.ts +++ b/src/games/space-invaders/src/scenes/menu-scene.ts @@ -17,32 +17,58 @@ export class MenuScene extends Phaser.Scene { } create(): void { + var guideText = this.add.bitmapText( + this.sys.canvas.width / 2 , + this.sys.canvas.height / 2, + 'font', + 'PRESS S TO PLAY', + 8 + ) + .setScale(0,0) + .setOrigin(0.5, 0.5) + .setAlpha(0) + this.bitmapTexts.push( - this.add.bitmapText( - this.sys.canvas.width / 2 - 65, - this.sys.canvas.height / 2, - 'font', - 'PRESS S TO PLAY', - 8 - ) + guideText ); - + var titleText = this.add.bitmapText( + this.sys.canvas.width / 2 - 60, + this.sys.canvas.height / 2 - 40, + 'font', + 'SPACE INVADERS', + 8 + ).setScale(0,0); this.bitmapTexts.push( - this.add.bitmapText( - this.sys.canvas.width / 2 - 60, - this.sys.canvas.height / 2 - 40, - 'font', - 'SPACE INVADERS', - 8 - ) + titleText ); + + this.tweens.add({ + targets: this.bitmapTexts, + ease: 'Power1', + duration: 2000, + angle: 360, + alpha: 1, + scaleX: 1, + scaleY: 1, + yoyo: false, + repeat: 0, + }); } update(): void { if (this.startKey.isDown) { - this.scene.start('HUDScene'); - this.scene.start('GameScene'); - this.scene.bringToTop('HUDScene'); + this.tweens.add({ + targets: this.bitmapTexts, + y: this.cameras.main.height +10, + ease: 'Power1', + duration: 1000, + onComplete: () => { + this.scene.stop(); + this.scene.start('HUDScene'); + this.scene.start('GameScene'); + this.scene.bringToTop('HUDScene'); + } + }); } } diff --git a/src/games/super-mario-land/src/objects/mario.ts b/src/games/super-mario-land/src/objects/mario.ts index aaa6eb85..9983ad02 100644 --- a/src/games/super-mario-land/src/objects/mario.ts +++ b/src/games/super-mario-land/src/objects/mario.ts @@ -64,6 +64,7 @@ export class Mario extends Phaser.GameObjects.Sprite { } update(): void { + console.log('update',this.anims.getTotalFrames()); if (!this.isDying) { this.handleInput(); this.handleAnimations(); diff --git a/src/games/tank/assets/animations/animations.json b/src/games/tank/assets/animations/animations.json new file mode 100644 index 00000000..4863b9b0 --- /dev/null +++ b/src/games/tank/assets/animations/animations.json @@ -0,0 +1,15 @@ +{ + "anims": [ + { + "key": "btn-music", + "frames": { + "typeOfGeneration": "generateFrameNumbers", + "key": "btn-music", + "start": 0, + "end": 1 + }, + "repeat": -1, + "frameRate": 9 + } + ] +} diff --git a/src/games/tank/assets/audio/battle.ogg b/src/games/tank/assets/audio/battle.ogg new file mode 100644 index 00000000..bd2d3b86 Binary files /dev/null and b/src/games/tank/assets/audio/battle.ogg differ diff --git a/src/games/tank/assets/audio/click.ogg b/src/games/tank/assets/audio/click.ogg new file mode 100644 index 00000000..3939e8d9 Binary files /dev/null and b/src/games/tank/assets/audio/click.ogg differ diff --git a/src/games/tank/assets/audio/destroy_0.ogg b/src/games/tank/assets/audio/destroy_0.ogg new file mode 100644 index 00000000..2d29d0eb Binary files /dev/null and b/src/games/tank/assets/audio/destroy_0.ogg differ diff --git a/src/games/tank/assets/audio/destroy_1.ogg b/src/games/tank/assets/audio/destroy_1.ogg new file mode 100644 index 00000000..39075274 Binary files /dev/null and b/src/games/tank/assets/audio/destroy_1.ogg differ diff --git a/src/games/tank/assets/audio/dist_assets_TownTheme.mp3 b/src/games/tank/assets/audio/dist_assets_TownTheme.mp3 new file mode 100644 index 00000000..c85e6a39 Binary files /dev/null and b/src/games/tank/assets/audio/dist_assets_TownTheme.mp3 differ diff --git a/src/games/tank/assets/audio/failed.ogg b/src/games/tank/assets/audio/failed.ogg new file mode 100644 index 00000000..14bbbc48 Binary files /dev/null and b/src/games/tank/assets/audio/failed.ogg differ diff --git a/src/games/tank/assets/audio/fire_0.ogg b/src/games/tank/assets/audio/fire_0.ogg new file mode 100644 index 00000000..06ec4a7a Binary files /dev/null and b/src/games/tank/assets/audio/fire_0.ogg differ diff --git a/src/games/tank/assets/audio/fire_1.ogg b/src/games/tank/assets/audio/fire_1.ogg new file mode 100644 index 00000000..b0884519 Binary files /dev/null and b/src/games/tank/assets/audio/fire_1.ogg differ diff --git a/src/games/tank/assets/audio/menu_track.m4a b/src/games/tank/assets/audio/menu_track.m4a new file mode 100644 index 00000000..5caef139 Binary files /dev/null and b/src/games/tank/assets/audio/menu_track.m4a differ diff --git a/src/games/tank/assets/audio/menu_track.ogg b/src/games/tank/assets/audio/menu_track.ogg new file mode 100644 index 00000000..8f18e8fc Binary files /dev/null and b/src/games/tank/assets/audio/menu_track.ogg differ diff --git a/src/games/tank/assets/audio/pickupammo_sfx.m4a b/src/games/tank/assets/audio/pickupammo_sfx.m4a new file mode 100644 index 00000000..78cf7597 Binary files /dev/null and b/src/games/tank/assets/audio/pickupammo_sfx.m4a differ diff --git a/src/games/tank/assets/audio/pickupammo_sfx.ogg b/src/games/tank/assets/audio/pickupammo_sfx.ogg new file mode 100644 index 00000000..779853ad Binary files /dev/null and b/src/games/tank/assets/audio/pickupammo_sfx.ogg differ diff --git a/src/games/tank/assets/audio/player_death.ogg b/src/games/tank/assets/audio/player_death.ogg new file mode 100644 index 00000000..c94aee23 Binary files /dev/null and b/src/games/tank/assets/audio/player_death.ogg differ diff --git a/src/games/tank/assets/audio/rpgfire_sfx.m4a b/src/games/tank/assets/audio/rpgfire_sfx.m4a new file mode 100644 index 00000000..7f9a7dd4 Binary files /dev/null and b/src/games/tank/assets/audio/rpgfire_sfx.m4a differ diff --git a/src/games/tank/assets/audio/rpgfire_sfx.ogg b/src/games/tank/assets/audio/rpgfire_sfx.ogg new file mode 100644 index 00000000..0222262c Binary files /dev/null and b/src/games/tank/assets/audio/rpgfire_sfx.ogg differ diff --git a/src/games/tank/assets/audio/select_sfx.ogg b/src/games/tank/assets/audio/select_sfx.ogg new file mode 100644 index 00000000..214df9f2 Binary files /dev/null and b/src/games/tank/assets/audio/select_sfx.ogg differ diff --git a/src/games/tank/assets/audio/tankshot_sfx.m4a b/src/games/tank/assets/audio/tankshot_sfx.m4a new file mode 100644 index 00000000..3e553f28 Binary files /dev/null and b/src/games/tank/assets/audio/tankshot_sfx.m4a differ diff --git a/src/games/tank/assets/audio/tankshot_sfx.ogg b/src/games/tank/assets/audio/tankshot_sfx.ogg new file mode 100644 index 00000000..66a813f6 Binary files /dev/null and b/src/games/tank/assets/audio/tankshot_sfx.ogg differ diff --git a/src/games/tank/assets/audio/tc_fire_0.ogg b/src/games/tank/assets/audio/tc_fire_0.ogg new file mode 100644 index 00000000..064c0c51 Binary files /dev/null and b/src/games/tank/assets/audio/tc_fire_0.ogg differ diff --git a/src/games/tank/assets/audio/tc_fire_1.ogg b/src/games/tank/assets/audio/tc_fire_1.ogg new file mode 100644 index 00000000..5a5d278e Binary files /dev/null and b/src/games/tank/assets/audio/tc_fire_1.ogg differ diff --git a/src/games/tank/assets/audio/war_start.ogg b/src/games/tank/assets/audio/war_start.ogg new file mode 100644 index 00000000..7228bf04 Binary files /dev/null and b/src/games/tank/assets/audio/war_start.ogg differ diff --git a/src/games/tank/assets/audio/win.ogg b/src/games/tank/assets/audio/win.ogg new file mode 100644 index 00000000..0b74e720 Binary files /dev/null and b/src/games/tank/assets/audio/win.ogg differ diff --git a/src/games/tank/assets/images/bg.png b/src/games/tank/assets/images/bg.png new file mode 100644 index 00000000..6d83c861 Binary files /dev/null and b/src/games/tank/assets/images/bg.png differ diff --git a/src/games/tank/assets/images/btn_menu.png b/src/games/tank/assets/images/btn_menu.png new file mode 100644 index 00000000..7aef15ad Binary files /dev/null and b/src/games/tank/assets/images/btn_menu.png differ diff --git a/src/games/tank/assets/images/btn_startbattle.png b/src/games/tank/assets/images/btn_startbattle.png new file mode 100644 index 00000000..c495a906 Binary files /dev/null and b/src/games/tank/assets/images/btn_startbattle.png differ diff --git a/src/games/tank/assets/images/curosr.png b/src/games/tank/assets/images/curosr.png new file mode 100644 index 00000000..f6891810 Binary files /dev/null and b/src/games/tank/assets/images/curosr.png differ diff --git a/src/games/tank/assets/images/exit-btn.png b/src/games/tank/assets/images/exit-btn.png new file mode 100644 index 00000000..c056bf52 Binary files /dev/null and b/src/games/tank/assets/images/exit-btn.png differ diff --git a/src/games/tank/assets/images/flares.json b/src/games/tank/assets/images/flares.json new file mode 100644 index 00000000..9b9c2d8a --- /dev/null +++ b/src/games/tank/assets/images/flares.json @@ -0,0 +1,57 @@ +{"frames": { + + "blue": + { + "frame": {"x":2,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }, + "green": + { + "frame": {"x":132,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }, + "red": + { + "frame": {"x":262,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }, + "white": + { + "frame": {"x":392,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }, + "yellow": + { + "frame": {"x":522,"y":2,"w":128,"h":128}, + "rotated": false, + "trimmed": false, + "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128}, + "sourceSize": {"w":128,"h":128}, + "pivot": {"x":0.5,"y":0.5} + }}, + "meta": { + "app": "http://www.codeandweb.com/texturepacker", + "version": "1.0", + "image": "flares.png", + "format": "RGBA8888", + "size": {"w":652,"h":132}, + "scale": "1", + "smartupdate": "$TexturePacker:SmartUpdate:f2781d89823d5a67fc31381af364b421:da82646b19b2f0c08684086824b1e581:71625947cf221c10549b852c13ffedc7$" + } + } \ No newline at end of file diff --git a/src/games/tank/assets/images/flares.png b/src/games/tank/assets/images/flares.png new file mode 100644 index 00000000..df04af53 Binary files /dev/null and b/src/games/tank/assets/images/flares.png differ diff --git a/src/games/tank/assets/images/logo.png b/src/games/tank/assets/images/logo.png new file mode 100644 index 00000000..32a64d4a Binary files /dev/null and b/src/games/tank/assets/images/logo.png differ diff --git a/src/games/tank/assets/images/muzzleflash3.png b/src/games/tank/assets/images/muzzleflash3.png new file mode 100644 index 00000000..525cf221 Binary files /dev/null and b/src/games/tank/assets/images/muzzleflash3.png differ diff --git a/src/games/tank/assets/images/pause-btn.png b/src/games/tank/assets/images/pause-btn.png new file mode 100644 index 00000000..791c8dbc Binary files /dev/null and b/src/games/tank/assets/images/pause-btn.png differ diff --git a/src/games/tank/assets/images/play-btn.png b/src/games/tank/assets/images/play-btn.png new file mode 100644 index 00000000..b2629c41 Binary files /dev/null and b/src/games/tank/assets/images/play-btn.png differ diff --git a/src/games/tank/assets/images/replay-btn.png b/src/games/tank/assets/images/replay-btn.png new file mode 100644 index 00000000..5cb01a2f Binary files /dev/null and b/src/games/tank/assets/images/replay-btn.png differ diff --git a/src/games/tank/assets/images/smoke-puff.png b/src/games/tank/assets/images/smoke-puff.png new file mode 100644 index 00000000..ebf19193 Binary files /dev/null and b/src/games/tank/assets/images/smoke-puff.png differ diff --git a/src/games/tank/assets/images/smoke0.png b/src/games/tank/assets/images/smoke0.png new file mode 100644 index 00000000..4d27a696 Binary files /dev/null and b/src/games/tank/assets/images/smoke0.png differ diff --git a/src/games/tank/assets/pack.json b/src/games/tank/assets/pack.json index ee4c9606..9fd0d75b 100644 --- a/src/games/tank/assets/pack.json +++ b/src/games/tank/assets/pack.json @@ -81,6 +81,131 @@ "type": "image", "key": "treeLarge", "url": "./assets/obstacles/tree-large.png" + }, + { + "type": "image", + "key": "background", + "url": "./assets/images/bg.png" + } + , + { + "type": "image", + "key": "btn-start", + "url": "./assets/images/btn_startbattle.png" + }, + { + "type": "image", + "key": "logo", + "url": "./assets/images/logo.png" + }, + { + "type": "image", + "key": "btn-menu", + "url": "./assets/images/btn_menu.png" + }, + { + "type": "image", + "key": "btn-pause", + "url": "./assets/images/pause-btn.png" + }, + { + "type": "image", + "key": "btn-replay", + "url": "./assets/images/replay-btn.png" + }, + { + "type": "image", + "key": "btn-play", + "url": "./assets/images/play-btn.png" + }, + { + "type": "image", + "key": "curosr", + "url": "./assets/images/curosr.png" + }, + { + "type": "image", + "key": "fire", + "url": "./assets/images/muzzleflash3.png" + }, + { + "type": "image", + "key": "dark-smoke", + "url": "./assets/images/smoke-puff.png" + }, + { + "type": "spritesheet", + "key": "btn-sound", + "url": "./assets/sprites/btn-sound.png", + "frameConfig": { + "frameWidth": 167, + "frameHeight": 156 + } + }, + { + "type": "spritesheet", + "key": "btn-music", + "url": "./assets/sprites/btn-music.png", + "frameConfig": { + "frameWidth": 167, + "frameHeight": 156 + } + }, + { + "type": "json", + "key": "animationJSON", + "url": "./assets/animations/animations.json" + }, + { + "type": "atlas", + "key": "flares", + "textureURL": "./assets/images/flares.png", + "atlasURL": "./assets/images/flares.json" + }, + { + "type": "audio", + "key": "menu_track", + "url": "./assets/audio/menu_track.m4a" + }, + { + "type": "audio", + "key": "select", + "url": "./assets/audio/select_sfx.ogg" + }, + { + "type": "audio", + "key": "click", + "url": "./assets/audio/click.ogg" + }, + { + "type": "audio", + "key": "player-shooter", + "url": "./assets/audio/fire_1.ogg" + }, + { + "type": "audio", + "key": "player-death", + "url": "./assets/audio/player_death.ogg" + }, + { + "type": "audio", + "key": "enemy-death", + "url": "./assets/audio/destroy_1.ogg" + }, + { + "type": "audio", + "key": "battle", + "url": "./assets/audio/battle.ogg" + }, + { + "type": "audio", + "key": "gameover", + "url": "./assets/audio/dist_assets_TownTheme.mp3" + }, + { + "type": "audio", + "key": "menu_start", + "url": "./assets/audio/rpgfire_sfx.ogg" } ] } diff --git a/src/games/tank/assets/sprites/btn-music.png b/src/games/tank/assets/sprites/btn-music.png new file mode 100644 index 00000000..bef4c1dd Binary files /dev/null and b/src/games/tank/assets/sprites/btn-music.png differ diff --git a/src/games/tank/assets/sprites/btn-sound.png b/src/games/tank/assets/sprites/btn-sound.png new file mode 100644 index 00000000..acbc516b Binary files /dev/null and b/src/games/tank/assets/sprites/btn-sound.png differ diff --git a/src/games/tank/assets/sprites/play-pause.png b/src/games/tank/assets/sprites/play-pause.png new file mode 100644 index 00000000..6ffe350e Binary files /dev/null and b/src/games/tank/assets/sprites/play-pause.png differ diff --git a/src/games/tank/src/config.ts b/src/games/tank/src/config.ts index 10a62c2a..607f2f07 100644 --- a/src/games/tank/src/config.ts +++ b/src/games/tank/src/config.ts @@ -1,6 +1,8 @@ -import { BootScene } from './scenes/boot-scene'; -import { GameScene } from './scenes/game-scene'; -import { MenuScene } from './scenes/menu-scene'; +import { BootScene } from './scenes/BootScene'; +import { GameScene } from './scenes/GameScene'; +import GameOverScene from './scenes/game-over-scene/GameOverScene'; +import { MenuScene } from './scenes/menu-scene/MenuScene'; +import { PauseScene } from './scenes/pause-scene/PauseScene'; export const GameConfig: Phaser.Types.Core.GameConfig = { title: 'Tank', @@ -11,7 +13,7 @@ export const GameConfig: Phaser.Types.Core.GameConfig = { zoom: 0.6, type: Phaser.AUTO, parent: 'game', - scene: [BootScene, MenuScene, GameScene], + scene: [BootScene,GameOverScene, MenuScene, GameScene, PauseScene], input: { keyboard: true }, @@ -19,7 +21,7 @@ export const GameConfig: Phaser.Types.Core.GameConfig = { default: 'arcade', arcade: { gravity: { y: 0 }, - debug: false + // debug: true } }, backgroundColor: '#000000', diff --git a/src/games/tank/src/consts/EventKeys.ts b/src/games/tank/src/consts/EventKeys.ts new file mode 100644 index 00000000..d5a20edf --- /dev/null +++ b/src/games/tank/src/consts/EventKeys.ts @@ -0,0 +1,12 @@ +enum EventKeys { + RESTART_GAME = 'restart-game', + CONTINUE = 'continue', + START_GAME = 'start-game', + MUTE_MUSIC = 'mute-music', + UNMUTE_MUSIC = 'unmute-music', + ENEMY_DEATH = 'enemy-death', + PLAYER_SHOOTING = 'player-shooting', + PLAYER_DEATH = 'player-death', +} + +export default EventKeys; \ No newline at end of file diff --git a/src/games/tank/src/consts/SceneKeys.ts b/src/games/tank/src/consts/SceneKeys.ts new file mode 100644 index 00000000..205780c6 --- /dev/null +++ b/src/games/tank/src/consts/SceneKeys.ts @@ -0,0 +1,9 @@ +enum SceneKeys { + BOOT_SCENE = "BootScene", + MENU_SCENE = "MenuScene", + GAME_SCENE = "GameScene", + GAME_OVER_SCENE = "GameOverScene", + PAUSE_SCENE = "PauseScene", +} + +export default SceneKeys; \ No newline at end of file diff --git a/src/games/tank/src/consts/const.ts b/src/games/tank/src/consts/const.ts new file mode 100644 index 00000000..5c8487d0 --- /dev/null +++ b/src/games/tank/src/consts/const.ts @@ -0,0 +1,4 @@ +export let CONST = { + TIME_ROTATE_STEP : 100, + TANK_SPEED : 200, +}; diff --git a/src/games/tank/src/helpers/helpers.ts b/src/games/tank/src/helpers/helpers.ts new file mode 100644 index 00000000..7a428abe --- /dev/null +++ b/src/games/tank/src/helpers/helpers.ts @@ -0,0 +1,35 @@ +import { CONST } from "../consts/const"; +import { Player } from "../objects/Player"; + +export const highScore = function (scene: Phaser.Scene){ + var score = scene.registry.get('score'); + if (localStorage.getItem('highscore')) { + if (localStorage.getItem('highscore') < score) { + localStorage.setItem('highscore', score); + } + } else { + localStorage.setItem('highscore', score); + } + return localStorage.getItem('highscore'); +} + +export const angleParticleBullet = function (angle: number){ + if(angle < 0){ + return angle+360 - 90; + } + return angle - 90; +} + +export const playSound = function (scene: Phaser.Scene, audioKey: string){ + scene.sound.add(audioKey).play; +} + +export const newPlayer = function (scene: Phaser.Scene, x: number, y: number){ + return new Player({ + scene: this, + x: x, + y: y, + texture: 'tankBlue', + rateOfFire: 80, + }); +} \ No newline at end of file diff --git a/src/games/tank/src/interfaces/bullet.interface.ts b/src/games/tank/src/interfaces/IBulletConstructor.d.ts similarity index 70% rename from src/games/tank/src/interfaces/bullet.interface.ts rename to src/games/tank/src/interfaces/IBulletConstructor.d.ts index 5a64b052..f5a5c558 100644 --- a/src/games/tank/src/interfaces/bullet.interface.ts +++ b/src/games/tank/src/interfaces/IBulletConstructor.d.ts @@ -1,8 +1,9 @@ -export interface IBulletConstructor { +interface IBulletConstructor { scene: Phaser.Scene; rotation: number; x: number; y: number; texture: string; frame?: string | number; + damage: number; } diff --git a/src/games/tank/src/interfaces/IButtonConstructor.d.ts b/src/games/tank/src/interfaces/IButtonConstructor.d.ts new file mode 100644 index 00000000..64240d40 --- /dev/null +++ b/src/games/tank/src/interfaces/IButtonConstructor.d.ts @@ -0,0 +1,8 @@ +interface IButtonConstructor { + scene: Phaser.Scene; + x: number; + y: number; + texture: string; + frame?: string | number; + soundPress: string; +} diff --git a/src/games/tank/src/interfaces/image.interface.ts b/src/games/tank/src/interfaces/IImageConstructor.d.ts similarity index 72% rename from src/games/tank/src/interfaces/image.interface.ts rename to src/games/tank/src/interfaces/IImageConstructor.d.ts index 4e54a6db..184b070e 100644 --- a/src/games/tank/src/interfaces/image.interface.ts +++ b/src/games/tank/src/interfaces/IImageConstructor.d.ts @@ -1,4 +1,4 @@ -export interface IImageConstructor { +interface IImageConstructor { scene: Phaser.Scene; x: number; y: number; diff --git a/src/games/tank/src/interfaces/ISpriteConstructor.d.ts b/src/games/tank/src/interfaces/ISpriteConstructor.d.ts new file mode 100644 index 00000000..fb448794 --- /dev/null +++ b/src/games/tank/src/interfaces/ISpriteConstructor.d.ts @@ -0,0 +1,7 @@ +interface ISpriteConstructor { + scene: Phaser.Scene; + x: number; + y: number; + texture: string; + frame?: string | number; +} diff --git a/src/games/tank/src/interfaces/ITankConstructor.d.ts b/src/games/tank/src/interfaces/ITankConstructor.d.ts new file mode 100644 index 00000000..b0b04bf8 --- /dev/null +++ b/src/games/tank/src/interfaces/ITankConstructor.d.ts @@ -0,0 +1,8 @@ +interface ITankConstructor { + scene: Phaser.Scene; + x: number; + y: number; + texture: string; + frame?: string | number; + rateOfFire: number; +} diff --git a/src/games/tank/src/interfaces/IToggleButtonConstructor.d.ts b/src/games/tank/src/interfaces/IToggleButtonConstructor.d.ts new file mode 100644 index 00000000..828350c1 --- /dev/null +++ b/src/games/tank/src/interfaces/IToggleButtonConstructor.d.ts @@ -0,0 +1,9 @@ +interface IToggleButtonConstructor { + scene: Phaser.Scene; + x: number; + y: number; + texture: string; + frame?: string | number; + numberOfFrames: number; + soundPress: string; +} diff --git a/src/games/tank/src/objects/bullet.ts b/src/games/tank/src/objects/bullet.ts index 45ac251a..d95c816f 100644 --- a/src/games/tank/src/objects/bullet.ts +++ b/src/games/tank/src/objects/bullet.ts @@ -1,26 +1,47 @@ -import { IBulletConstructor } from '../interfaces/bullet.interface'; +import { angleParticleBullet } from "../helpers/helpers"; export class Bullet extends Phaser.GameObjects.Image { body: Phaser.Physics.Arcade.Body; - private bulletSpeed: number; + private damage!: number; + + // particle + private fire: Phaser.GameObjects.Particles.ParticleEmitter; + private darkSmoke: Phaser.GameObjects.Particles.ParticleEmitter; constructor(aParams: IBulletConstructor) { super(aParams.scene, aParams.x, aParams.y, aParams.texture); this.rotation = aParams.rotation; + this.damage = aParams.damage; + + this.initVariables(); this.initImage(); + this.initPhysics(); + this.createParticalBullet(); this.scene.add.existing(this); } - private initImage(): void { + update(): void { + this.fire.setAngle(angleParticleBullet(this.angle)) + } + + public getDamage(){ + return this.damage; + } + + public destroyBullet() { + this.fire.stop(); + this.darkSmoke.stop(); + this.destroy(); + } + + private initVariables(){ // variables this.bulletSpeed = 1000; + } - // image - this.setOrigin(0.5, 0.5); - this.setDepth(2); - + private initPhysics(){ // physics this.scene.physics.world.enable(this); this.scene.physics.velocityFromRotation( @@ -30,5 +51,32 @@ export class Bullet extends Phaser.GameObjects.Image { ); } - update(): void {} + private initImage(): void { + // image + this.setOrigin(0.5, 0.5); + this.setDepth(2); + } + + private createParticalBullet(){ + this.fire = this.scene.add.particles('fire').createEmitter({ + speed: { min: 100, max: 200 }, + scale: { start: 0, end: 1, ease: 'Back.easeOut' }, + alpha: { start: 1, end: 0, ease: 'Quart.easeOut' }, + lifespan: 600, + follow: this, + }); + this.fire.reserve(1000); + + this.darkSmoke = this.scene.add.particles('dark-smoke').createEmitter({ + x: this.x, + y: this.y, + speed: { min: 20, max: 100 }, + angle: { min: 0, max: 360}, + scale: { start: 1, end: 0}, + alpha: { start: 0, end: 0.1}, + lifespan: 600, + follow: this, + }); + this.darkSmoke.reserve(1000); + } } diff --git a/src/games/tank/src/objects/button/BaseButton.ts b/src/games/tank/src/objects/button/BaseButton.ts new file mode 100644 index 00000000..02167973 --- /dev/null +++ b/src/games/tank/src/objects/button/BaseButton.ts @@ -0,0 +1,71 @@ + +export class BaseButton extends Phaser.GameObjects.Image { + // variables + protected soundPress: Phaser.Sound.BaseSound; + private isClick: boolean; + + constructor(aParams: IButtonConstructor) { + super(aParams.scene, aParams.x, aParams.y, aParams.texture, aParams.frame); + // variables + this.isClick = false; + this.soundPress = this.scene.sound.add(aParams.soundPress); + this.setInteractive({ useHandCursor: true }); + this.initListenEvent(); + this.scene.add.existing(this); + } + + protected TouchDown(){ + this.scene.tweens.add({ + targets: this, + scaleX: 0.9, + scaleY: 0.9, + ease: 'Sine.easeInOut', + duration: 100, + repeat: 0, + paused: true + }) + } + + protected TouchUp(switchTexture?: Function){ + this.scene.tweens.add({ + targets: this, + scaleX: 1, + scaleY: 1, + ease: 'Sine.easeInOut', + duration: 100, + repeat: 0, + paused: true, + onComplete: () => { + console.log("onPressUp"); + this.isClick = false; + this.handleOnPress(); + switchTexture(); + } + }) + } + + protected initListenEvent(switchTexture? : Function){ + this.on(Phaser.Input.Events.POINTER_UP, () => { + this.TouchUp(switchTexture); + }); + + this.on(Phaser.Input.Events.POINTER_DOWN, () => { + console.log("Phaser.Input.Events.POINTER_DOWN"); + this.isClick = true; + this.TouchDown(); + // play audio click + if(!this.scene.registry.get('muteSound')) + this.soundPress.play(); + }); + + this.on(Phaser.Input.Events.POINTER_OUT, () => { + if(this.isClick) + this.TouchUp(); + }); + } + + public handleOnPress(){ + + } + +} diff --git a/src/games/tank/src/objects/button/normal-button/Button.ts b/src/games/tank/src/objects/button/normal-button/Button.ts new file mode 100644 index 00000000..85087be5 --- /dev/null +++ b/src/games/tank/src/objects/button/normal-button/Button.ts @@ -0,0 +1,71 @@ + +export class Button extends Phaser.GameObjects.Image { + // variables + protected tweenDown: Phaser.Tweens.Tween; + protected tweenUp: Phaser.Tweens.Tween; + protected soundPress: Phaser.Sound.BaseSound; + private isClick: boolean; + + constructor(aParams: IButtonConstructor) { + super(aParams.scene, aParams.x, aParams.y, aParams.texture, aParams.frame); + // variables + this.isClick = false; + this.soundPress = this.scene.sound.add(aParams.soundPress); + this.setInteractive({ useHandCursor: true }); + this.initTween(); + this.initListenEvent(); + this.scene.add.existing(this); + } + update(...args: any[]): void { + + } + protected initTween(){ + this.tweenDown = this.scene.tweens.add({ + targets: this, + scaleX: 0.9, + scaleY: 0.9, + ease: 'Sine.easeInOut', + duration: 100, + repeat: 0, + paused: true + }) + + this.tweenUp = this.scene.tweens.add({ + targets: this, + scaleX: 1, + scaleY: 1, + ease: 'Sine.easeInOut', + duration: 100, + repeat: 0, + paused: true, + onComplete: () => { + this.isClick = false; + this.handleOnPress(); + } + }) + } + + private initListenEvent(){ + this.on(Phaser.Input.Events.POINTER_UP, () => { + this.tweenUp.play(); + }); + + this.on(Phaser.Input.Events.POINTER_DOWN, () => { + this.isClick = true; + this.tweenDown.play(); + // play audio click + if(!this.scene.registry.get('muteSound')) + this.soundPress.play(); + }); + + this.on(Phaser.Input.Events.POINTER_OUT, () => { + if(this.isClick) + this.tweenUp.play(); + }); + } + + public handleOnPress(){ + + } + +} diff --git a/src/games/tank/src/objects/button/normal-button/MenuButton.ts b/src/games/tank/src/objects/button/normal-button/MenuButton.ts new file mode 100644 index 00000000..cc71f5b5 --- /dev/null +++ b/src/games/tank/src/objects/button/normal-button/MenuButton.ts @@ -0,0 +1,9 @@ +import SceneKeys from "../../../consts/SceneKeys"; +import { Button } from "./Button"; + +export class MenuButton extends Button{ + public handleOnPress(){ + this.scene.scene.pause(); + this.scene.scene.launch(SceneKeys.PAUSE_SCENE); + } +} \ No newline at end of file diff --git a/src/games/tank/src/objects/button/normal-button/PlayButton.ts b/src/games/tank/src/objects/button/normal-button/PlayButton.ts new file mode 100644 index 00000000..4087d1d6 --- /dev/null +++ b/src/games/tank/src/objects/button/normal-button/PlayButton.ts @@ -0,0 +1,8 @@ +import EventKeys from "../../../consts/EventKeys"; +import { Button } from "./Button"; + +export class PlayButton extends Button{ + public handleOnPress(){ + this.scene.events.emit(EventKeys.CONTINUE); + } +} \ No newline at end of file diff --git a/src/games/tank/src/objects/button/normal-button/ReplayButton.ts b/src/games/tank/src/objects/button/normal-button/ReplayButton.ts new file mode 100644 index 00000000..97134f68 --- /dev/null +++ b/src/games/tank/src/objects/button/normal-button/ReplayButton.ts @@ -0,0 +1,9 @@ +import EventKeys from "../../../consts/EventKeys"; +import { Button } from "./Button"; + +export class ReplayButton extends Button{ + public handleOnPress(){ + this.scene.events.emit(EventKeys.RESTART_GAME); + console.log("GameScene: "); + } +} \ No newline at end of file diff --git a/src/games/tank/src/objects/button/normal-button/StartButton.ts b/src/games/tank/src/objects/button/normal-button/StartButton.ts new file mode 100644 index 00000000..397e9a95 --- /dev/null +++ b/src/games/tank/src/objects/button/normal-button/StartButton.ts @@ -0,0 +1,21 @@ +import EventKeys from "../../../consts/EventKeys"; +import SceneKeys from "../../../consts/SceneKeys"; +import { Button } from "./Button"; + +export class StartButton extends Button{ + protected initTween(){ + super.initTween(); + this.scene.tweens.add({ + targets: this, + scaleX: 1.2, + scaleY: 1.2, + ease: 'Sine.easeInOut', + duration: 500, + yoyo: true, + repeat: -1, + }) + } + public handleOnPress(){ + this.scene.events.emit(EventKeys.START_GAME); + } +} \ No newline at end of file diff --git a/src/games/tank/src/objects/button/toggle-button/ButtonMusic.ts b/src/games/tank/src/objects/button/toggle-button/ButtonMusic.ts new file mode 100644 index 00000000..433607bd --- /dev/null +++ b/src/games/tank/src/objects/button/toggle-button/ButtonMusic.ts @@ -0,0 +1,24 @@ +import EventKeys from "../../../consts/EventKeys"; +import { ToggleButton } from "./ToggleButton"; + +export class ButtonMusic extends ToggleButton{ + preUpdate(): void { + if(this.scene.registry.get('muteMusic')) + this.setFrame(0); + else{ + this.setFrame(1); + } + } + + protected handerOnPress(){ + const isMuteMusic = this.scene.registry.get('muteMusic'); + + if(!isMuteMusic){ + this.scene.registry.set('muteMusic', true); + this.scene.events.emit(EventKeys.MUTE_MUSIC); + }else{ + this.scene.registry.set('muteMusic', false); + this.scene.events.emit(EventKeys.UNMUTE_MUSIC); + } + } +} \ No newline at end of file diff --git a/src/games/tank/src/objects/button/toggle-button/ButtonSound.ts b/src/games/tank/src/objects/button/toggle-button/ButtonSound.ts new file mode 100644 index 00000000..4c46028e --- /dev/null +++ b/src/games/tank/src/objects/button/toggle-button/ButtonSound.ts @@ -0,0 +1,18 @@ +import { ToggleButton } from "./ToggleButton"; + +export class ButtonSound extends ToggleButton{ + preUpdate(): void { + if(this.scene.registry.get('muteSound')) + this.setFrame(0); + else{ + this.setFrame(1); + } + } + protected handerOnPress(): void { + if(!this.scene.registry.get('muteSound')){ + this.scene.registry.set('muteSound', true); + }else{ + this.scene.registry.set('muteSound', false); + } + } +} \ No newline at end of file diff --git a/src/games/tank/src/objects/button/toggle-button/ToggleButton.ts b/src/games/tank/src/objects/button/toggle-button/ToggleButton.ts new file mode 100644 index 00000000..ea8c26e5 --- /dev/null +++ b/src/games/tank/src/objects/button/toggle-button/ToggleButton.ts @@ -0,0 +1,86 @@ + +export class ToggleButton extends Phaser.GameObjects.Sprite { + protected soundPress!: Phaser.Sound.BaseSound; + + // variables + private tweenDown: Phaser.Tweens.Tween; + private tweenUp: Phaser.Tweens.Tween; + private numberOfFrames!: number; + private isClick: boolean; + + constructor(aParams: IToggleButtonConstructor) { + super(aParams.scene, aParams.x, aParams.y, aParams.texture, aParams.frame); + + // variables + this.scene = aParams.scene; + this.numberOfFrames = aParams.numberOfFrames; + this.isClick = false; + this.soundPress = this.scene.sound.add(aParams.soundPress); + + this.initSprite(); + this.initTween(); + this.onPress(); + this.scene.add.existing(this); + this.setInteractive({ useHandCursor: true }); + } + + private initSprite() { + // sprite + } + private initTween(){ + this.tweenDown = this.scene.tweens.add({ + targets: this, + scaleX: 0.9, + scaleY: 0.9, + ease: 'Sine.easeInOut', + duration: 100, + repeat: 0, + paused: true + }) + + this.tweenUp = this.scene.tweens.add({ + targets: this, + scaleX: 1, + scaleY: 1, + ease: 'Sine.easeInOut', + duration: 100, + repeat: 0, + paused: true, + onComplete: ()=>{ + this.isClick = false; + this.switchTexture(); + this.handerOnPress(); + } + }) + } + + private onPress(){ + this.on(Phaser.Input.Events.POINTER_UP, () => { + this.tweenUp.play(); + }); + + this.on(Phaser.Input.Events.POINTER_DOWN, () => { + this.isClick = true; + this.tweenDown.play(); + if(!this.scene.registry.get('muteSound')) + this.soundPress.play(); + }); + + this.on(Phaser.Input.Events.POINTER_OVER, () => { + if(this.isClick) + this.tweenUp.play(); + }) + } + + private switchTexture() { + let currentFrame = parseInt(this.frame.name); + let nextFrame = currentFrame + 1; + if(nextFrame > this.numberOfFrames - 1) + nextFrame = 0; + this.setFrame(nextFrame); + } + + protected handerOnPress(){ + + } +} diff --git a/src/games/tank/src/objects/enemy.ts b/src/games/tank/src/objects/enemy.ts index 4d5f78d6..46eb5700 100644 --- a/src/games/tank/src/objects/enemy.ts +++ b/src/games/tank/src/objects/enemy.ts @@ -1,21 +1,57 @@ -import { Bullet } from './bullet'; -import { IImageConstructor } from '../interfaces/image.interface'; +import EventKeys from "../consts/EventKeys"; +import { Bullet } from "./Bullet"; +import { Player } from "./Player"; -export class Enemy extends Phaser.GameObjects.Image { +export class Enemy extends Phaser.GameObjects.Container { body: Phaser.Physics.Arcade.Body; // variables private health: number; - private lastShoot: number; - private speed: number; + private nextShoot: number; + private texture: string; + private rateOfFire: number; // children private barrel: Phaser.GameObjects.Image; private lifeBar: Phaser.GameObjects.Graphics; - + private tank: Phaser.GameObjects.Image; + // game objects private bullets: Phaser.GameObjects.Group; + constructor(aParams: ITankConstructor) { + super(aParams.scene, aParams.x, aParams.y); + + this.texture = aParams.texture; + this.rateOfFire = aParams.rateOfFire; + + this.initVariables(); + this.initContainer(); + this.moveEnemy(); + this.scene.add.existing(this); + } + + update(player: Player) : void { + if (player.active && this.active) { + this.shooting(); + + var angle = Phaser.Math.Angle.Between( + this.body.x, + this.body.y, + player.body.x, + player.body.y + ); + + this.getBarrel().angle = + (angle + Math.PI / 2) * Phaser.Math.RAD_TO_DEG; + + } else { + this.destroy(); + this.barrel.destroy(); + this.lifeBar.destroy(); + } + } + public getBarrel(): Phaser.GameObjects.Image { return this.barrel; } @@ -24,37 +60,60 @@ export class Enemy extends Phaser.GameObjects.Image { return this.bullets; } - constructor(aParams: IImageConstructor) { - super(aParams.scene, aParams.x, aParams.y, aParams.texture, aParams.frame); - - this.initContainer(); - this.scene.add.existing(this); + public updateHealth(damage: number): void { + this.initTweenDamage(damage); + if (this.health > 0) { + this.health -= damage; + this.redrawLifebar(); + } else { + this.scene.events.emit(EventKeys.ENEMY_DEATH) + this.health = 0; + // particles + this.initParticlesDeath(); + this.active = false; + } } - private initContainer() { + private initVariables(){ // variables this.health = 1; - this.lastShoot = 0; - this.speed = 100; + this.nextShoot = 0; + } + private initContainer() { // image - this.setDepth(0); + this.tank = this.scene.physics.add.image(0, 0, this.texture) + .setImmovable(true); + this.body = this.tank.body as Phaser.Physics.Arcade.Body; this.barrel = this.scene.add.image(0, 0, 'barrelRed'); this.barrel.setOrigin(0.5, 1); this.barrel.setDepth(1); - + this.lifeBar = this.scene.add.graphics(); this.redrawLifebar(); + // add objects to container + this.add([ + this.tank, + this.lifeBar, + this.barrel + ]); + // game objects - this.bullets = this.scene.add.group({ - /*classType: Bullet,*/ - active: true, - maxSize: 10, - runChildUpdate: true - }); + // game objects + this.bullets = this.scene.add.group({ + /*classType: Bullet,*/ + active: true, + maxSize: 10, + runChildUpdate: true + }); + + // physics + this.scene.physics.world.enable(this); + } + private moveEnemy(){ // tweens this.scene.tweens.add({ targets: this, @@ -68,39 +127,22 @@ export class Enemy extends Phaser.GameObjects.Image { repeatDelay: 0, yoyo: true }); - - // physics - this.scene.physics.world.enable(this); } - update(): void { - if (this.active) { - this.barrel.x = this.x; - this.barrel.y = this.y; - this.lifeBar.x = this.x; - this.lifeBar.y = this.y; - this.handleShooting(); - } else { - this.destroy(); - this.barrel.destroy(); - this.lifeBar.destroy(); - } - } - - private handleShooting(): void { - if (this.scene.time.now > this.lastShoot) { + private shooting(): void { + if (this.scene.time.now > this.nextShoot) { if (this.bullets.getLength() < 10) { this.bullets.add( new Bullet({ scene: this.scene, rotation: this.barrel.rotation, - x: this.barrel.x, - y: this.barrel.y, - texture: 'bulletRed' + x: this.x, + y: this.y, + texture: 'bulletRed', + damage: 0.05 }) ); - - this.lastShoot = this.scene.time.now + 400; + this.nextShoot = this.scene.time.now + this.rateOfFire; } } } @@ -109,23 +151,58 @@ export class Enemy extends Phaser.GameObjects.Image { this.lifeBar.clear(); this.lifeBar.fillStyle(0xe66a28, 1); this.lifeBar.fillRect( - -this.width / 2, - this.height / 2, - this.width * this.health, + -this.tank.width / 2, + this.tank.height / 2, + this.tank.width * this.health, 15 ); this.lifeBar.lineStyle(2, 0xffffff); - this.lifeBar.strokeRect(-this.width / 2, this.height / 2, this.width, 15); + this.lifeBar.strokeRect(-this.tank.width / 2, this.tank.height / 2, this.tank.width, 15); this.lifeBar.setDepth(1); } - public updateHealth(): void { - if (this.health > 0) { - this.health -= 0.05; - this.redrawLifebar(); - } else { - this.health = 0; - this.active = false; - } + private initTweenDamage(damage: number): void { + const xTextHealth = Phaser.Math.Between(this.x - 30, this.x + 30) + const yTextHealth = this.y + const textHealth = this.scene.add.text(xTextHealth, yTextHealth, `-${damage*100/5}`, { + fontFamily: 'Bangers', + fontSize: '50px', + color: '#B21E1E', + }).setOrigin(0.5, 0.5); + + this.scene.tweens.add({ + targets: textHealth, + y: yTextHealth -100, + ease: 'Power1', + duration: 300, + yoyo: false, + repeat: 0, + onComplete: ()=>{ + textHealth.destroy(); + } + }) + } + + private initParticlesDeath(){ + const zone = new Phaser.Geom.Rectangle(-32, -32, 64, 64); + const particles = this.scene.add.particles('fire'); + particles.createEmitter({ + alpha: { start: 1, end: 0 }, + scale: { start: 0.5, end: 2.5 }, + speed: 20, + accelerationY: -300, + angle: { min: -85, max: -95 }, + rotate: { min: -180, max: 180 }, + lifespan: { min: 1000, max: 1100 }, + frequency: 110, + maxParticles: 10, + x: this.x, + y: this.y, + emitZone: { + type: 'random' , + source: zone, + quantity: 10 + } + }); } } diff --git a/src/games/tank/src/objects/obstacles/obstacle.ts b/src/games/tank/src/objects/obstacles/obstacle.ts index e8a429ea..30eef954 100644 --- a/src/games/tank/src/objects/obstacles/obstacle.ts +++ b/src/games/tank/src/objects/obstacles/obstacle.ts @@ -1,4 +1,3 @@ -import { IImageConstructor } from '../../interfaces/image.interface'; export class Obstacle extends Phaser.GameObjects.Image { body: Phaser.Physics.Arcade.Body; diff --git a/src/games/tank/src/objects/player.ts b/src/games/tank/src/objects/player.ts index 67a26b14..4f1eade9 100644 --- a/src/games/tank/src/objects/player.ts +++ b/src/games/tank/src/objects/player.ts @@ -1,57 +1,119 @@ -import { Bullet } from './bullet'; -import { IImageConstructor } from '../interfaces/image.interface'; +import { CONST } from '../consts/const'; +import EventKeys from '../consts/EventKeys'; +import SceneKeys from '../consts/SceneKeys'; +import { Bullet } from './Bullet'; -export class Player extends Phaser.GameObjects.Image { +export class Player extends Phaser.GameObjects.Container { body: Phaser.Physics.Arcade.Body; - // variables private health: number; - private lastShoot: number; + private nextShoot: number; private speed: number; + private texture: string; + private rateOfFire: number; // children private barrel: Phaser.GameObjects.Image; private lifeBar: Phaser.GameObjects.Graphics; - + private tank: Phaser.GameObjects.Image; + // game objects private bullets: Phaser.GameObjects.Group; + private cursor: Phaser.GameObjects.Image; // input - private cursors: Phaser.Types.Input.Keyboard.CursorKeys; - private rotateKeyLeft: Phaser.Input.Keyboard.Key; - private rotateKeyRight: Phaser.Input.Keyboard.Key; - private shootingKey: Phaser.Input.Keyboard.Key; + private moveKeyLeft: Phaser.Input.Keyboard.Key; + private moveKeyRight: Phaser.Input.Keyboard.Key; + private moveKeyUp: Phaser.Input.Keyboard.Key; + private moveKeyDown: Phaser.Input.Keyboard.Key; + + constructor(aParams: ITankConstructor) { + super(aParams.scene, aParams.x, aParams.y); + this.texture = aParams.texture; + this.rateOfFire = aParams.rateOfFire; + + this.initVariables(); + this.initContainer(); + // input mouse + this.initHandleInput(); + + this.scene.add.existing(this); + // set body of container + this.body.setOffset(-this.tank.width/2, -this.tank.height/2); + this.body.setSize(this.tank.width, this.tank.height); + } + + update(): void { + if (this.active) { + if (!this.scene.input.mouse.locked){ + this.cursor.setVisible(false); + } + this.rotateBarrelFollowMoune(); + this.moveCurosrFollowPlayer(); + this.handleInput(); + + } else { + this.destroy(); + this.barrel.destroy(); + this.lifeBar.destroy(); + } + } public getBullets(): Phaser.GameObjects.Group { return this.bullets; } - constructor(aParams: IImageConstructor) { - super(aParams.scene, aParams.x, aParams.y, aParams.texture, aParams.frame); + public updateHealth(damage: number): void { + this.initTweenDamage(damage); - this.initImage(); - this.scene.add.existing(this); - } + if (this.health > 0) { + this.health -= damage; + this.redrawLifebar(); + } else { + this.scene.events.emit(EventKeys.PLAYER_DEATH); + this.health = 0; + this.active = false; + this.cursor.setVisible(false); - private initImage() { + // pause current scene and show scene game over + this.scene.scene.pause(); + this.scene.scene.launch(SceneKeys.GAME_OVER_SCENE); + this.scene.scene.bringToTop(SceneKeys.GAME_OVER_SCENE); + } + } + + private initVariables(){ // variables this.health = 1; - this.lastShoot = 0; - this.speed = 100; + this.nextShoot = 0; + this.speed = CONST.TANK_SPEED; + } + private initContainer() { + // image - this.setOrigin(0.5, 0.5); - this.setDepth(0); - this.angle = 180; - - this.barrel = this.scene.add.image(this.x, this.y, 'barrelBlue'); - this.barrel.setOrigin(0.5, 1); - this.barrel.setDepth(1); + this.tank = this.scene.physics.add.image(0, 0, this.texture); + this.tank.angle = 180; + + this.barrel = this.scene.add.image(0, 0, 'barrelBlue') + .setOrigin(0.5, 1) this.barrel.angle = 180; + this.cursor = this.scene.physics.add.image(this.x, this.y, 'curosr') + .setCollideWorldBounds(true) + .setDisplaySize(64,64) + .setDepth(2) + this.lifeBar = this.scene.add.graphics(); this.redrawLifebar(); + // add objects to container + this.add([ + this.tank, + this.lifeBar, + this.barrel + ]); + // game objects this.bullets = this.scene.add.group({ /*classType: Bullet,*/ @@ -60,126 +122,183 @@ export class Player extends Phaser.GameObjects.Image { runChildUpdate: true }); + // physics + this.scene.physics.world.enable(this); + } + + private initHandleInput(){ // input - this.cursors = this.scene.input.keyboard.createCursorKeys(); - this.rotateKeyLeft = this.scene.input.keyboard.addKey( + this.moveKeyLeft = this.scene.input.keyboard.addKey( Phaser.Input.Keyboard.KeyCodes.A ); - this.rotateKeyRight = this.scene.input.keyboard.addKey( + this.moveKeyRight = this.scene.input.keyboard.addKey( Phaser.Input.Keyboard.KeyCodes.D ); - this.shootingKey = this.scene.input.keyboard.addKey( - Phaser.Input.Keyboard.KeyCodes.SPACE + this.moveKeyUp = this.scene.input.keyboard.addKey( + Phaser.Input.Keyboard.KeyCodes.W + ); + this.moveKeyDown = this.scene.input.keyboard.addKey( + Phaser.Input.Keyboard.KeyCodes.S ); - // physics - this.scene.physics.world.enable(this); + this.scene.input.on(Phaser.Input.Events.POINTER_MOVE, + (pointer: Phaser.Input.Pointer)=>{ + this.cursor.x += pointer.movementX; + this.cursor.y += pointer.movementY; + }, this); + + this.scene.input.on(Phaser.Input.Events.POINTER_DOWN, + (pointer: Phaser.Input.Pointer)=>{ + if (this&&!this.scene?.input.mouse.locked){ + this.cursor.setVisible(true); + this.scene.game.input.mouse.requestPointerLock(); + } + else this.shooting(); + }, this); + + // using unblock mouse + this.scene.input.keyboard.on('keydown-SPACE', + ()=>{ + if (this.scene.input.mouse.locked){ + this.scene.input.mouse.releasePointerLock(); + this.cursor.setVisible(false); + } + }); + + // when player continue playing + this.scene.events.on('resume', + () => { + this.cursor.setVisible(true); + }) } - update(): void { - if (this.active) { - this.barrel.x = this.x; - this.barrel.y = this.y; - this.lifeBar.x = this.x; - this.lifeBar.y = this.y; - this.handleInput(); - this.handleShooting(); - } else { - this.destroy(); - this.barrel.destroy(); - this.lifeBar.destroy(); - } + private rotateBarrelFollowMoune(){ + this.barrel.rotation = Phaser.Math.Angle.Between( + this.x, + this.y, + this.cursor.x, + this.cursor.y + ) + + Math.PI/2; + } + + private moveCurosrFollowPlayer() { + // move cursor + var bodyCurosr = this.cursor.body as Phaser.Physics.Arcade.Body; + bodyCurosr.setVelocity(this.body.velocity.x, this.body.velocity.y) } private handleInput() { // move tank forward // small corrections with (- MATH.PI / 2) to align tank correctly - if (this.cursors.up.isDown) { + if (this.moveKeyUp.isDown) { this.scene.physics.velocityFromRotation( - this.rotation - Math.PI / 2, + this.tank.rotation - Math.PI / 2, this.speed, this.body.velocity ); - } else if (this.cursors.down.isDown) { + } + else if (this.moveKeyDown.isDown) { this.scene.physics.velocityFromRotation( - this.rotation - Math.PI / 2, + this.tank.rotation - Math.PI / 2, -this.speed, this.body.velocity ); - } else { + } + else { this.body.setVelocity(0, 0); } // rotate tank - if (this.cursors.left.isDown) { - this.rotation -= 0.02; - } else if (this.cursors.right.isDown) { - this.rotation += 0.02; - } - - // rotate barrel - if (this.rotateKeyLeft.isDown) { - this.barrel.rotation -= 0.05; - } else if (this.rotateKeyRight.isDown) { - this.barrel.rotation += 0.05; + if (this.moveKeyLeft.isDown) { + this.tank.rotation -= 0.02; + } + else if (this.moveKeyRight.isDown) { + this.tank.rotation += 0.02; } } - private handleShooting(): void { - if (this.shootingKey.isDown && this.scene.time.now > this.lastShoot) { + private shooting(): void { + if (this.scene.time.now > this.nextShoot) { + this.scene.events.emit(EventKeys.PLAYER_SHOOTING); this.scene.cameras.main.shake(20, 0.005); - this.scene.tweens.add({ - targets: this, - props: { alpha: 0.8 }, - delay: 0, - duration: 5, - ease: 'Power1', - easeParams: null, - hold: 0, - repeat: 0, - repeatDelay: 0, - yoyo: true, - paused: false - }); - + this.createTweenWhenShooting(); if (this.bullets.getLength() < 10) { this.bullets.add( new Bullet({ scene: this.scene, rotation: this.barrel.rotation, - x: this.barrel.x, - y: this.barrel.y, - texture: 'bulletBlue' + x: this.x, + y: this.y, + texture: 'bulletBlue', + damage: 0.1 }) ); - - this.lastShoot = this.scene.time.now + 80; + this.nextShoot = this.scene.time.now + this.rateOfFire; } + } } + private createTweenWhenShooting() { + this.scene.tweens.add({ + targets: this, + props: { alpha: 0.8 }, + delay: 0, + duration: 5, + ease: 'Power1', + easeParams: null, + hold: 0, + repeat: 0, + repeatDelay: 0, + yoyo: true, + paused: false + }); + } + private redrawLifebar(): void { this.lifeBar.clear(); this.lifeBar.fillStyle(0xe66a28, 1); this.lifeBar.fillRect( - -this.width / 2, - this.height / 2, - this.width * this.health, + -this.tank.width / 2, + this.tank.height / 2, + this.tank.width * this.health, 15 ); this.lifeBar.lineStyle(2, 0xffffff); - this.lifeBar.strokeRect(-this.width / 2, this.height / 2, this.width, 15); + this.lifeBar.strokeRect( + -this.tank.width / 2, + this.tank.height / 2, + this.tank.width, 15 + ); this.lifeBar.setDepth(1); } - public updateHealth(): void { - if (this.health > 0) { - this.health -= 0.05; - this.redrawLifebar(); - } else { - this.health = 0; - this.active = false; - this.scene.scene.start('MenuScene'); - } + private initTweenDamage(damage: number): void { + const xTextHealth = Phaser.Math.Between(this.x - 30, this.x + 30) + const yTextHealth = this.y; + const textDamage = this.scene.add.text( + xTextHealth, + yTextHealth, + `-${damage*100/5}`, + { + fontFamily: 'Bangers', + fontSize: '50px', + color: '#4A90E2', + } + ) + .setOrigin(0.5, 0.5); + + this.scene.tweens.add({ + targets: textDamage, + y: yTextHealth -100, + ease: 'Power1', + duration: 300, + yoyo: false, + repeat: 0, + onComplete: ()=>{ + textDamage.destroy(); + } + }) } } diff --git a/src/games/tank/src/scenes/boot-scene.ts b/src/games/tank/src/scenes/bootScene.ts similarity index 91% rename from src/games/tank/src/scenes/boot-scene.ts rename to src/games/tank/src/scenes/bootScene.ts index 7fa14cb7..f5971a8f 100644 --- a/src/games/tank/src/scenes/boot-scene.ts +++ b/src/games/tank/src/scenes/bootScene.ts @@ -1,10 +1,12 @@ +import SceneKeys from "../consts/SceneKeys"; + export class BootScene extends Phaser.Scene { private loadingBar: Phaser.GameObjects.Graphics; private progressBar: Phaser.GameObjects.Graphics; constructor() { super({ - key: 'BootScene' + key: SceneKeys.BOOT_SCENE, }); } @@ -41,10 +43,11 @@ export class BootScene extends Phaser.Scene { // load our package this.load.pack('preload', './assets/pack.json', 'preload'); - } + + } update(): void { - this.scene.start('MenuScene'); + this.scene.start(SceneKeys.MENU_SCENE); } private createLoadingGraphics(): void { diff --git a/src/games/tank/src/scenes/game-over-scene/GameOverContainer.ts b/src/games/tank/src/scenes/game-over-scene/GameOverContainer.ts new file mode 100644 index 00000000..14605782 --- /dev/null +++ b/src/games/tank/src/scenes/game-over-scene/GameOverContainer.ts @@ -0,0 +1,230 @@ +import SceneKeys from "../../consts/SceneKeys"; +import { highScore } from "../../helpers/helpers"; +import { ReplayButton } from "../../objects/button/normal-button/ReplayButton"; + +export class GameOverContainer extends Phaser.GameObjects.Container{ + private zone!: Phaser.GameObjects.Zone; + private score!: Phaser.GameObjects.Text; + private gameOverText!: Phaser.GameObjects.Text; + private currentScoreText!: Phaser.GameObjects.Text; + private highScoreText!: Phaser.GameObjects.Text; + private highScore!: Phaser.GameObjects.Text; + private tween!: Phaser.Tweens.Tween; + private replayButton!: ReplayButton; + + constructor(scene: Phaser.Scene, x: number, y: number){ + super(scene, x, y); + this.createUI(); + this.alignUI(); + this.setStartPosition(); + this.createTweens(); + this.setDepth(3); + this.scene.add.existing(this); + } + + update(...args: any[]): void { + this.score.setText( + Math.floor(this.tween.getValue()).toString() + ) + } + + public createTweenClose(){ + const camerasHeight= this.scene.cameras.main.height; + + this.scene.tweens.add({ + targets: [this.currentScoreText, this.highScoreText, this.highScore, this.score], + y: - 200, + ease: 'Power1', + duration: 500, + }); + + this.scene.tweens.add({ + targets: [this.replayButton], + y: camerasHeight + 200, + ease: 'Power1', + duration: 500, + onComplete: () => { + this.scene.scene.stop(); + this.scene.scene.stop(SceneKeys.GAME_SCENE); + this.scene.scene.sendToBack(); + this.scene.scene.stop(SceneKeys.MENU_SCENE); + this.scene.scene.start(SceneKeys.MENU_SCENE); + } + }); + } + + private createUI() { + const camerasWidth= this.scene.cameras.main.width; + const camerasHeight= this.scene.cameras.main.height; + + const background = this.scene.add.rectangle( + 0, + 0, + camerasWidth, + camerasHeight, + 0x000000 + ) + .setOrigin(0,0) + .setAlpha(0.8); + + this.gameOverText = this.scene.add.text( + 0, 0, + 'Game Over', + { + fontFamily: 'Quicksand', + fontSize: '96px' + } + ); + this.currentScoreText = this.scene.add.text( + 0, 0, + `Current score`, + { + fontFamily: 'Quicksand', + fontSize: '48px' + } + ); + + this.highScoreText = this.scene.add.text( + 0, 0, + `High score`, + { + fontFamily: 'Quicksand', + fontSize: '48px' + } + ) + .setOrigin(0.5,0.5) + .setVisible(false) + + this.highScore = this.scene.add.text( + 0, 0, + `${highScore(this.scene)}`, + { + fontFamily: 'Quicksand', + fontSize: '48px' + } + ) + .setOrigin(0.5,0.5) + .setVisible(false) + + this.score = this.scene.add.text( + 0, 0, + `${this.scene.registry.get('score')}`, + { + fontFamily: 'Quicksand', + fontSize: '48px' + } + ) + .setOrigin(0.5,0.5) + .setVisible(false) + + this.replayButton = new ReplayButton({ + scene: this.scene, + x: 0, + y: 0, + texture: 'btn-replay', + soundPress: 'click', + }).setOrigin(0,0) + + this.zone = this.scene.add.zone( + 140, + 90, + camerasWidth - 140*2, + camerasHeight - 90*2 + ) + .setOrigin(0,0); + + this.add([ + this.zone, + this.replayButton, + this.highScoreText, + this.highScore, + this.currentScoreText, + this.score, + this.gameOverText + ]) + } + + private alignUI(){ + Phaser.Display.Align.In.Center( + this.gameOverText, + this.zone, + ); + Phaser.Display.Align.In.Center( + this.score, + this.zone, + ); + Phaser.Display.Align.In.BottomCenter( + this.replayButton, + this.zone, + ); + + Phaser.Display.Align.In.Center( + this.currentScoreText, + this.zone, + ); + + Phaser.Display.Align.In.Center( + this.highScore, + this.zone, + ); + Phaser.Display.Align.In.Center( + this.highScoreText, + this.zone, + ); + } + + private setStartPosition(){ + const camerasHeight = this.scene.cameras.main.height; + this.currentScoreText.setY(camerasHeight + 100); + this.replayButton.setY(camerasHeight + 100); + this.score.setY(300); + this.highScoreText.setY(400); + this.highScore.setY(500); + } + + private createTweens() { + const durationTween = (this.scene.registry.get('score')/100)* 5000; + const score = this.scene.registry.get('score') + this.tween = this.scene.tweens.addCounter({ + from: 0, + to: score, + duration: durationTween, + paused: true + }); + + this.scene.tweens.add({ + targets: this.gameOverText, + y: -100, + ease: 'Power1', + duration: 3000, + delay: 1000, + onComplete() { + this.destroy; + }, + }); + + this.scene.tweens.add({ + targets: this.currentScoreText, + y: 200, + ease: 'Power1', + duration: 5000, + delay: 1000, + onComplete: ()=>{ + this.score.visible = true; + this.tween.resume(); + }, + }); + + this.tween.on("complete", () =>{ + const camerasHeight = this.scene.cameras.main.height; + this.highScoreText.setVisible(true); + this.highScore.setVisible(true); + this.scene.tweens.add({ + targets: this.replayButton, + y: camerasHeight - 300, + ease: 'Power1', + duration: 1000, + }); + }) + } +} \ No newline at end of file diff --git a/src/games/tank/src/scenes/game-over-scene/GameOverScene.ts b/src/games/tank/src/scenes/game-over-scene/GameOverScene.ts new file mode 100644 index 00000000..628c70fe --- /dev/null +++ b/src/games/tank/src/scenes/game-over-scene/GameOverScene.ts @@ -0,0 +1,45 @@ +/* eslint no-undef: 0 */ +/* eslint no-unused-expressions: 0 */ +import 'phaser'; +import EventKeys from '../../consts/EventKeys'; +import SceneKeys from '../../consts/SceneKeys'; +import { GameOverContainer } from './GameOverContainer'; + +export default class GameOverScene extends Phaser.Scene { + private gameOverContainer: GameOverContainer; + + // audio + private audioGameOver: Phaser.Sound.BaseSound; + + constructor() { + super({ + key: SceneKeys.GAME_OVER_SCENE, + }); + } + + create() { + this.gameOverContainer = new GameOverContainer(this, 0,0); + this.add.existing(this.gameOverContainer) + this.initAudio(); + this.createHandleEvents(); + } + + update(time: number, delta: number): void { + this.gameOverContainer.update(time, delta); + } + + private initAudio(){ + this.audioGameOver = this.sound.add('gameover'); + if(!this.registry.get('muteMusic')) + this.audioGameOver.play(); + } + + private createHandleEvents(){ + this.events.on(EventKeys.RESTART_GAME, + ()=>{ + this.audioGameOver.pause(); + this.gameOverContainer.createTweenClose(); + }, this); + } + +} \ No newline at end of file diff --git a/src/games/tank/src/scenes/game-scene.ts b/src/games/tank/src/scenes/game-scene.ts deleted file mode 100644 index 5d72ca77..00000000 --- a/src/games/tank/src/scenes/game-scene.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { Player } from '../objects/player'; -import { Enemy } from '../objects/enemy'; -import { Obstacle } from '../objects/obstacles/obstacle'; -import { Bullet } from '../objects/bullet'; - -export class GameScene extends Phaser.Scene { - private map: Phaser.Tilemaps.Tilemap; - private tileset: Phaser.Tilemaps.Tileset; - private layer: Phaser.Tilemaps.TilemapLayer; - - private player: Player; - private enemies: Phaser.GameObjects.Group; - private obstacles: Phaser.GameObjects.Group; - - private target: Phaser.Math.Vector2; - - constructor() { - super({ - key: 'GameScene' - }); - } - - init(): void {} - - create(): void { - // create tilemap from tiled JSON - this.map = this.make.tilemap({ key: 'levelMap' }); - - this.tileset = this.map.addTilesetImage('tiles'); - this.layer = this.map.createLayer('tileLayer', this.tileset, 0, 0); - this.layer.setCollisionByProperty({ collide: true }); - - this.obstacles = this.add.group({ - /*classType: Obstacle,*/ - runChildUpdate: true - }); - - this.enemies = this.add.group({ - /*classType: Enemy*/ - }); - this.convertObjects(); - - // collider layer and obstacles - this.physics.add.collider(this.player, this.layer); - this.physics.add.collider(this.player, this.obstacles); - - // collider for bullets - this.physics.add.collider( - this.player.getBullets(), - this.layer, - this.bulletHitLayer, - null, - this - ); - - this.physics.add.collider( - this.player.getBullets(), - this.obstacles, - this.bulletHitObstacles, - null, - this - ); - - this.enemies.children.each((enemy: Enemy) => { - this.physics.add.overlap( - this.player.getBullets(), - enemy, - this.playerBulletHitEnemy, - null, - this - ); - this.physics.add.overlap( - enemy.getBullets(), - this.player, - this.enemyBulletHitPlayer, - null - ); - - this.physics.add.collider( - enemy.getBullets(), - this.obstacles, - this.bulletHitObstacles, - null - ); - this.physics.add.collider( - enemy.getBullets(), - this.layer, - this.bulletHitLayer, - null - ); - }, this); - - this.cameras.main.startFollow(this.player); - } - - update(): void { - this.player.update(); - - this.enemies.children.each((enemy: Enemy) => { - enemy.update(); - if (this.player.active && enemy.active) { - var angle = Phaser.Math.Angle.Between( - enemy.body.x, - enemy.body.y, - this.player.body.x, - this.player.body.y - ); - - enemy.getBarrel().angle = - (angle + Math.PI / 2) * Phaser.Math.RAD_TO_DEG; - } - }, this); - } - - private convertObjects(): void { - // find the object layer in the tilemap named 'objects' - const objects = this.map.getObjectLayer('objects').objects as any[]; - - objects.forEach((object) => { - if (object.type === 'player') { - this.player = new Player({ - scene: this, - x: object.x, - y: object.y, - texture: 'tankBlue' - }); - } else if (object.type === 'enemy') { - let enemy = new Enemy({ - scene: this, - x: object.x, - y: object.y, - texture: 'tankRed' - }); - - this.enemies.add(enemy); - } else { - let obstacle = new Obstacle({ - scene: this, - x: object.x, - y: object.y - 40, - texture: object.type - }); - - this.obstacles.add(obstacle); - } - }); - } - - private bulletHitLayer(bullet: Bullet): void { - bullet.destroy(); - } - - private bulletHitObstacles(bullet: Bullet, obstacle: Obstacle): void { - bullet.destroy(); - } - - private enemyBulletHitPlayer(bullet: Bullet, player: Player): void { - bullet.destroy(); - player.updateHealth(); - } - - private playerBulletHitEnemy(bullet: Bullet, enemy: Enemy): void { - bullet.destroy(); - enemy.updateHealth(); - } -} diff --git a/src/games/tank/src/scenes/gameScene.ts b/src/games/tank/src/scenes/gameScene.ts new file mode 100644 index 00000000..fc02c714 --- /dev/null +++ b/src/games/tank/src/scenes/gameScene.ts @@ -0,0 +1,350 @@ +import { Player } from '../objects/Player'; +import { Enemy } from '../objects/Enemy'; +import { Obstacle } from '../objects/obstacles/Obstacle'; +import { Bullet } from '../objects/Bullet'; +import { MenuButton } from '../objects/button/normal-button/MenuButton'; +import SceneKeys from '../consts/SceneKeys'; +import EventKeys from '../consts/EventKeys'; + +export class GameScene extends Phaser.Scene { + private map: Phaser.Tilemaps.Tilemap; + private tileset: Phaser.Tilemaps.Tileset; + private layer: Phaser.Tilemaps.TilemapLayer; + + private player: Player; + private enemies: Phaser.GameObjects.Group; + private obstacles: Phaser.GameObjects.Group; + private textScore!: Phaser.GameObjects.Text; + + private buttonMenu!: MenuButton; + private zone!: Phaser.GameObjects.Zone; + + // audio + private audioBattle: Phaser.Sound.BaseSound; + private audioPlayerShooter: Phaser.Sound.BaseSound; + private audioPlayerDeath: Phaser.Sound.BaseSound; + private audioEnemyDeath: Phaser.Sound.BaseSound; + + constructor() { + super({ + key: SceneKeys.GAME_SCENE + }); + + } + + init(): void { + } + + create(): void { + + this.initAudio(); + this.createUI(); + this.createMap(); + this.initEnemies(); + this.initObstacles(); + this.createObjects(); + this.handlePhysics(); + this.initListenEvents(); + this.setUpCameraFollowPlayer(); + } + + update(): void { + this.player.update(); + this.updateEnemys(); + } + + private initAudio() { + this.audioBattle = this.sound.add('battle'); + this.audioBattle.play(); + this.audioPlayerShooter = this.sound.add('player-shooter'); + this.audioPlayerDeath = this.sound.add('player-death'); + this.audioEnemyDeath = this.sound.add('enemy-death'); + } + + private createUI(){ + const camerasWidth= this.cameras.main.width; + const camerasHeight= this.cameras.main.height; + + this.buttonMenu = new MenuButton({ + scene: this, + x: 0, + y: 0, + texture: "btn-menu", + soundPress: 'click', + }).setScrollFactor(0); + + this.textScore = this.add.text( + 0, + 0, + `Score: ${this.registry.get('score')}`, + { + fontFamily: 'Quicksand', + fontSize: '48px', + color: '#fff' + } + ) + .setScrollFactor(0) + .setOrigin(0, 0); + + this.zone = this.add.zone( + camerasWidth/2, + camerasHeight / 2, + camerasWidth - 40, + camerasHeight - 20 + ) + Phaser.Display.Align.In.TopLeft( + this.buttonMenu, + this.zone + ); + + Phaser.Display.Align.In.TopRight( + this.textScore, + this.zone + ); + this.textScore.setX(this.textScore.x - 25) + } + + private createMap(){ + // create tilemap from tiled JSON + this.map = this.make.tilemap({ key: 'levelMap' }); + + this.tileset = this.map.addTilesetImage('tiles'); + this.layer = this.map.createLayer('tileLayer', this.tileset, 0, 0); + this.layer.setCollisionByProperty({ collide: true }); + } + + private initEnemies(){ + this.enemies = this.add.group({ + /*classType: Enemy*/ + }); + } + + private initObstacles(){ + this.obstacles = this.add.group({ + /*classType: Obstacle,*/ + runChildUpdate: true + }); + } + + private setUpCameraFollowPlayer(){ + this.cameras.main.startFollow(this.player); + } + + private handlePhysics(){ + + this.physics.world.setBounds( + 0, + 0, + this.layer.width, + this.layer.height + ); + + // collider layer and obstacles + this.physics.add.collider(this.player, this.layer); + this.physics.add.collider(this.player, this.obstacles); + + // collider for bullets + this.physics.add.collider( + this.player.getBullets(), + this.layer, + (obj1, obj2) => this.bulletHitLayer(obj1 as Bullet), + null, + this + ); + + this.physics.add.collider( + this.player.getBullets(), + this.obstacles, + (obj1, obj2) =>this.bulletHitObstacles(obj1 as Bullet, obj2 as Obstacle), + null, + this + ); + + this.physics.add.collider( + this.player.getBullets(), + this.enemies, + (obj1, obj2) =>this.playerBulletHitEnemy(obj1 as Bullet, obj2 as Enemy), + null, + this + ); + + this.enemies.children.each((enemy: Enemy) => { + this.physics.add.overlap( + enemy.getBullets(), + this.player, + (obj1, obj2)=>this.enemyBulletHitPlayer(obj1 as Bullet, obj2 as Player), + null + ); + + this.physics.add.collider( + enemy.getBullets(), + this.obstacles, + (obj1, obj2) =>this.bulletHitObstacles(obj1 as Bullet, obj2 as Obstacle), + null + ); + this.physics.add.collider( + enemy.getBullets(), + this.layer, + (obj1, obj2)=>this.bulletHitLayer(obj1 as Bullet), + null + ); + }, this); + } + + private initListenEvents() { + this.events.on('pause', ()=>{ + if(this.input.mouse.locked) + this.input.mouse.releasePointerLock(); + // pause audio + this.audioBattle.pause(); + this.scene.sendToBack(); + }) + + this.events.on('resume', () => { + if(!this.registry.get('muteMusic')&&!this.audioBattle.isPlaying){ + if(this.audioBattle.isPaused) + this.audioBattle.resume(); + else + this.audioBattle.play(); + } + else if(this.audioBattle.isPlaying && this.registry.get('muteMusic')){ + this.audioBattle.pause(); + } + }) + + this.events.on(EventKeys.PLAYER_SHOOTING,()=>{ + if(!this.registry.get('muteSound')) + this.audioPlayerShooter.play(); + }) + + this.events.on(EventKeys.PLAYER_DEATH,()=>{ + if(!this.registry.get('muteSound')) + this.audioPlayerDeath.play(); + }) + + this.events.on(EventKeys.ENEMY_DEATH,()=>{ + if(!this.registry.get('muteSound')) + this.audioEnemyDeath.play(); + }) + + this.events.on("start",()=>{ + this.removeListener(); + }) + + } + + private removeListener(){ + this.events.removeListener(EventKeys.PLAYER_SHOOTING); + this.events.removeListener(EventKeys.PLAYER_DEATH); + this.events.removeListener('enemy_death'); + } + + + private createObjects(): void { + // find the object layer in the tilemap named 'objects' + const objects = this.map.getObjectLayer('objects').objects as any[]; + + objects.forEach((object) => { + switch (object.type) { + case "player": { + this.player = this.createPlayer(object.x, object.y); + break; + } + + case "enemy":{ + let enemy = this.createEnemy(object.x, object.y); + this.enemies.add(enemy); + break; + } + + default:{ + let obstacle = this.createObstacle(object.x, object.y, object.type); + this.obstacles.add(obstacle); + break; + } + } + }); + } + + private createPlayer(x: number, y: number): Player { + return new Player({ + scene: this, + x: x, + y: y, + texture: 'tankBlue', + rateOfFire: 80, + }); + } + + private createEnemy(x: number, y: number): Enemy { + return new Enemy({ + scene: this, + x: x, + y: y, + texture: 'tankRed', + rateOfFire: 1000, + }); + } + + private createObstacle(x: number, y: number, texture: string): Obstacle { + return new Obstacle({ + scene: this, + x: x, + y: y - 40, + texture: texture + }); + } + + private bulletHitLayer(bullet: Bullet): void { + this.createEmitter(bullet.x, bullet.y); + bullet.destroyBullet(); + } + + private bulletHitObstacles(bullet: Bullet, obstacle: Obstacle): void { + this.createEmitter(bullet.x, bullet.y); + bullet.destroyBullet(); + } + + private enemyBulletHitPlayer(bullet: Bullet, player: Player): void { + this.createEmitter(bullet.x, bullet.y); + bullet.destroyBullet(); + player.updateHealth(bullet.getDamage()); + } + + private playerBulletHitEnemy(bullet: Bullet, enemy: Enemy): void { + this.createEmitter(bullet.x, bullet.y); + bullet.destroyBullet(); + this.updateScore(); + enemy.updateHealth(bullet.getDamage()); + } + + private createEmitter(x: number, y: number){ + var particlesBullet = this.add.particles('flares') + .createEmitter({ + frame: 'red', + x: x, + y: y, + lifespan: 500, + speed: { min: 400, max: 600 }, + angle: {min: 0, max: 360}, + scale: { start: 0.1, end: 0 }, + quantity: 2, + blendMode: 'ADD', + }); + + this.time.delayedCall(150, ()=>{ + particlesBullet.remove(); + }, [], this) + } + + private updateScore(){ + this.registry.set('score' ,this.registry.get('score')+1); + this.textScore.setText(`Score: ${this.registry.get('score')}`); + } + + private updateEnemys(){ + this.enemies.children.each((enemy: Enemy) => { + enemy.update(this.player); + }, this); + } +} diff --git a/src/games/tank/src/scenes/menu-scene.ts b/src/games/tank/src/scenes/menu-scene.ts deleted file mode 100644 index ab7c5cf5..00000000 --- a/src/games/tank/src/scenes/menu-scene.ts +++ /dev/null @@ -1,45 +0,0 @@ -export class MenuScene extends Phaser.Scene { - private startKey: Phaser.Input.Keyboard.Key; - private bitmapTexts: Phaser.GameObjects.BitmapText[] = []; - - constructor() { - super({ - key: 'MenuScene' - }); - } - - init(): void { - this.startKey = this.input.keyboard.addKey( - Phaser.Input.Keyboard.KeyCodes.S - ); - this.startKey.isDown = false; - } - - create(): void { - this.bitmapTexts.push( - this.add.bitmapText( - this.sys.canvas.width / 2 - 120, - this.sys.canvas.height / 2, - 'font', - 'PRESS S TO PLAY', - 30 - ) - ); - - this.bitmapTexts.push( - this.add.bitmapText( - this.sys.canvas.width / 2 - 120, - this.sys.canvas.height / 2 - 100, - 'font', - 'TANK', - 100 - ) - ); - } - - update(): void { - if (this.startKey.isDown) { - this.scene.start('GameScene'); - } - } -} diff --git a/src/games/tank/src/scenes/menu-scene/MenuContainer.ts b/src/games/tank/src/scenes/menu-scene/MenuContainer.ts new file mode 100644 index 00000000..d7f5a9a1 --- /dev/null +++ b/src/games/tank/src/scenes/menu-scene/MenuContainer.ts @@ -0,0 +1,130 @@ +import SceneKeys from "../../consts/SceneKeys"; +import { StartButton } from "../../objects/button/normal-button/StartButton"; +import { ButtonMusic } from "../../objects/button/toggle-button/ButtonMusic"; +import { ButtonSound } from "../../objects/button/toggle-button/ButtonSound"; + +export class MenuContainer extends Phaser.GameObjects.Container { + private startButton: StartButton; + private soundButton: ButtonSound; + private musicButton: ButtonMusic; + private logo: Phaser.GameObjects.Image; + private zone!: Phaser.GameObjects.Zone; + + constructor(scene: Phaser.Scene, x: number, y: number){ + super(scene, x, y); + this.createUI(); + this.alignUI(); + this.createTweens(); + } + + public createTweenClose(){ + this.scene.tweens.add({ + targets: [this.soundButton, this.musicButton], + y: this.scene.cameras.main.height + 200, + ease: 'Power1', + duration: 500, + }); + this.scene.tweens.add({ + targets: [this.startButton], + y: -200, + ease: 'Power1', + duration: 500, + onComplete: () => { + this.scene.game.input.mouse.requestPointerLock(); + this.scene.scene.stop(SceneKeys.MENU_SCENE) + this.scene.scene.stop(SceneKeys.GAME_SCENE); + this.scene.scene.start(SceneKeys.GAME_SCENE); + } + }); + } + + public setVisibleBtnStart(){ + this.startButton.setVisible(true); + } + + private createUI(){ + const camerasWidth= this.scene.cameras.main.width; + const camerasHeight= this.scene.cameras.main.height; + + this.scene.add.image(0,0,'background') + .setOrigin(0,0) + .setScale(0.75,0.75); + + this.logo = this.scene.add.image(0,0,'logo'); + this.zone = this.scene.add.zone( + 140, + 90, + camerasWidth - 140*2, + camerasHeight - 90*2 + ) + .setOrigin(0,0); + this.add(this.zone); + // Center the picture in the game + + this.startButton = new StartButton({ + scene: this.scene, + x: 0, + y: 0, + texture: 'btn-start', + soundPress: 'select', + }).setVisible(false); + + + this.soundButton = new ButtonSound({ + scene: this.scene, + x: 0, + y: 0, + texture: 'btn-sound', + frame: 1, + numberOfFrames: 2, + soundPress: 'click', + }) + + this.musicButton = new ButtonMusic({ + scene: this.scene, + x: 0, + y: 0, + texture: 'btn-music', + frame: 1, + numberOfFrames: 2, + soundPress: 'click', + }) + + this.add([ + this.soundButton, + this.musicButton, + this.startButton + ]); + } + + private alignUI(){ + Phaser.Display.Align.In.TopCenter( + this.logo, + this.zone + ); + Phaser.Display.Align.In.Center( + this.startButton, + this.zone + ); + Phaser.Display.Align.In.BottomLeft( + this.soundButton, + this.zone + ); + Phaser.Display.Align.In.BottomRight( + this.musicButton, + this.zone + ); + } + + private createTweens(): void { + this.scene.tweens.add({ + targets: this.startButton, + scaleX: 1.2, + scaleY: 1.2, + ease: 'Sine.easeInOut', + duration: 500, + yoyo: true, + repeat: -1, + }) + } +} \ No newline at end of file diff --git a/src/games/tank/src/scenes/menu-scene/MenuScene.ts b/src/games/tank/src/scenes/menu-scene/MenuScene.ts new file mode 100644 index 00000000..7d4e7181 --- /dev/null +++ b/src/games/tank/src/scenes/menu-scene/MenuScene.ts @@ -0,0 +1,76 @@ +import EventKeys from "../../consts/EventKeys"; +import SceneKeys from "../../consts/SceneKeys"; +import { MenuContainer } from "./MenuContainer"; + +export class MenuScene extends Phaser.Scene { + private menuContainer: MenuContainer; + private menuTrackAudio: Phaser.Sound.BaseSound; + private menuStartAudio: Phaser.Sound.BaseSound; + constructor() { + super({ + key: SceneKeys.MENU_SCENE + }); + } + + init(): void { + this.initGlobalDataManager(); + this.createHandleEvents(); + } + + create(): void { + this.menuContainer = new MenuContainer(this, 0,0); + this.add.existing(this.menuContainer); + this.initAudio(); + } + + update(): void { + } + + private initAudio() { + this.menuTrackAudio = this.sound.add('menu_track'); + this.menuStartAudio = this.sound.add('menu_start'); + this.menuStartAudio.play(); + this.menuStartAudio.on('complete', ()=>{ + this.menuStartAudio.pause(); + this.menuTrackAudio.play(); + this.menuContainer.setVisibleBtnStart(); + }); + } + + private createHandleEvents(): void { + this.events.on(EventKeys.START_GAME, ()=>{ + this.menuTrackAudio.stop(); + this.menuContainer.createTweenClose(); + }, this); + + this.events.on('start', ()=>{ + this.removeListener(); + this.input.mouse.releasePointerLock(); + }, this); + + this.events.on(EventKeys.MUTE_MUSIC, ()=>{ + if(this.menuTrackAudio.isPlaying){ + this.menuTrackAudio.pause(); + } + }) + + this.events.on(EventKeys.UNMUTE_MUSIC, ()=>{ + if(this.menuTrackAudio.isPaused) + this.menuTrackAudio.resume(); + else + this.menuTrackAudio.play(); + }) + } + + private removeListener() { + this.events.removeListener(EventKeys.START_GAME); + this.events.removeListener(EventKeys.MUTE_MUSIC); + this.events.removeListener(EventKeys.UNMUTE_MUSIC); + } + + private initGlobalDataManager(): void { + this.registry.set('score', 0); + this.registry.set('muteSound', false); + this.registry.set('muteMusic', false); + } +} diff --git a/src/games/tank/src/scenes/pause-scene/PauseContainer.ts b/src/games/tank/src/scenes/pause-scene/PauseContainer.ts new file mode 100644 index 00000000..31e3c7c9 --- /dev/null +++ b/src/games/tank/src/scenes/pause-scene/PauseContainer.ts @@ -0,0 +1,169 @@ +import SceneKeys from "../../consts/SceneKeys"; +import { PlayButton } from "../../objects/button/normal-button/PlayButton"; +import { ReplayButton } from "../../objects/button/normal-button/ReplayButton"; +import { ButtonMusic } from "../../objects/button/toggle-button/ButtonMusic"; +import { ButtonSound } from "../../objects/button/toggle-button/ButtonSound"; + +export class PauseContainer extends Phaser.GameObjects.Container{ + private musicButton: ButtonMusic; + private replayButton: ReplayButton; + private playButton: PlayButton; + private soundButton: ButtonSound; + private zone!: Phaser.GameObjects.Zone; + + constructor(scene: Phaser.Scene, x: number, y: number){ + super(scene, x, y); + this.scene = scene; + this.createUI(); + this.alignUI(); + this.createTweenOpen(); + } + + private createUI(){ + const background = this.scene.add.rectangle( + 0, + 0, + this.scene.cameras.main.width, + this.scene.cameras.main.height, + 0x000000 + ) + .setOrigin(0,0) + .setAlpha(0.5); + + this.zone = this.scene.add.zone( + 0, + 140, + 350, + 450 + ) + .setOrigin(0,0); + this.add(this.zone); + + this.musicButton = new ButtonMusic({ + scene: this.scene, + x: 0, + y: 0, + texture: 'btn-music', + frame: 1, + numberOfFrames: 2, + soundPress: 'click', + }) + + this.soundButton = new ButtonSound({ + scene: this.scene, + x: 0, + y: 0, + texture: 'btn-sound', + frame: 1, + numberOfFrames: 2, + soundPress: 'click', + }) + + this.playButton = new PlayButton({ + scene: this.scene, + x: 0, + y: 0, + texture: 'btn-play', + soundPress: 'click', + }) + + this.replayButton = new ReplayButton({ + scene: this.scene, + x: 0, + y: 0, + texture: 'btn-replay', + soundPress: 'click', + }) + + this.add([ + this.musicButton, + this.playButton, + this.replayButton, + this.soundButton + ]); + } + + private alignUI(){ + Phaser.Display.Align.In.TopRight( + this.musicButton, + this.zone + ) + Phaser.Display.Align.In.TopLeft( + this.soundButton, + this.zone + ) + Phaser.Display.Align.In.Center( + this.playButton, + this.zone + ) + + Phaser.Display.Align.In.BottomCenter( + this.replayButton, + this.zone + ) + } + + private createTweenOpen(): void { + // tweens open + this.scene.tweens.timeline({ + ease: 'Back.easeOut', + duration: 300, + tweens:[ + { + targets: this.musicButton, + x: 800, + }, + { + targets: this.soundButton, + x: 600, + }, + { + targets: this.playButton, + x: 750, + }, + { + targets: this.replayButton, + x: 750, + } + ] + }) + } + + public createTweenClose(modeClose: string){ + this.scene.tweens.timeline({ + // targets: [this.btnReplay, this.btnPlay, this.btnSound, this.btnMusic], + ease: 'Power1', + duration: 200, + tweens:[ + { + targets: this.soundButton, + x: -100, + }, + { + targets: this.musicButton, + x: -100, + }, + { + targets: this.playButton, + x: -100, + }, + { + targets: this.replayButton, + x: -100, + } + ], + onComplete: () => { + if(modeClose == 'continue'){ + this.scene.game.input.mouse.requestPointerLock(); + this.scene.scene.resume(SceneKeys.GAME_SCENE); + this.scene.scene.sendToBack(); + }else{ + this.scene.scene.stop(SceneKeys.GAME_SCENE); + this.scene.scene.stop(SceneKeys.MENU_SCENE); + this.scene.scene.start(SceneKeys.MENU_SCENE); + this.scene.scene.stop(); + } + }, + }); + } +} \ No newline at end of file diff --git a/src/games/tank/src/scenes/pause-scene/PauseScene.ts b/src/games/tank/src/scenes/pause-scene/PauseScene.ts new file mode 100644 index 00000000..246aedc0 --- /dev/null +++ b/src/games/tank/src/scenes/pause-scene/PauseScene.ts @@ -0,0 +1,50 @@ +import EventKeys from "../../consts/EventKeys"; +import SceneKeys from "../../consts/SceneKeys"; +import { PauseContainer } from "./PauseContainer"; + +export class PauseScene extends Phaser.Scene { + private pauseContainer: PauseContainer; + + constructor() { + super({ + key: SceneKeys.PAUSE_SCENE, + }); + } + + create(): void { + this.createUI(); + this.createHandleEvents(); + } + + update(): void { + } + + private createUI(){ + this.pauseContainer = new PauseContainer(this, 0,0) + this.pauseContainer.setX(-400); + this.add.existing(this.pauseContainer); + } + + private createHandleEvents(){ + this.events.on(EventKeys.RESTART_GAME, + ()=>{ + this.pauseContainer.createTweenClose("restart") + }, this); + + // create events + this.events.on(EventKeys.CONTINUE, + ()=>{ + this.pauseContainer.createTweenClose("continue") + }, this); + + this.events.on('start', + ()=>{ + this.removeListener(); + }, this); + } + + private removeListener(){ + this.events.removeListener(EventKeys.CONTINUE); + this.events.removeListener(EventKeys.RESTART_GAME); + } +} diff --git a/src/games/tank/task.txt b/src/games/tank/task.txt new file mode 100644 index 00000000..35a2f6aa --- /dev/null +++ b/src/games/tank/task.txt @@ -0,0 +1,5 @@ +Container and Align Chỉnh sửa game Tank, bổ xung 2 popup: Pause & Game Over + + Bổ xung nút Pause trong game + + Bổ xung sound cho shoot & hit + + Pause: Có 2 nút Continue và New Game, Turn on/off Sound + + Game Over: Có Score, High Score và nút New Game diff --git a/src/games/tank/tsconfig.json b/src/games/tank/tsconfig.json index 12c795f0..28f97c03 100644 --- a/src/games/tank/tsconfig.json +++ b/src/games/tank/tsconfig.json @@ -3,8 +3,10 @@ "target": "ES6", "module": "CommonJS", "moduleResolution": "node", - "noImplicitAny": true + "noImplicitAny": true, + "typeRoots": ["*/**/*.d.ts"] }, + "include": ["src/**/*"], "exclude": ["node_modules"] }