diff --git a/front/src/Phaser/Companion/Companion.ts b/front/src/Phaser/Companion/Companion.ts new file mode 100644 index 00000000..ab9b8be8 --- /dev/null +++ b/front/src/Phaser/Companion/Companion.ts @@ -0,0 +1,196 @@ +import Sprite = Phaser.GameObjects.Sprite; +import Container = Phaser.GameObjects.Container; +import { lazyLoadResource } from "./CompanionTexturesLoadingManager"; +import { PlayerAnimationDirections, PlayerAnimationTypes } from "../Player/Animation"; + +export class Companion extends Container { + public sprites: Map; + + private delta: number; + private invisible: boolean; + private target: { x: number, y: number }; + + constructor( + scene: Phaser.Scene, + x: number, + y: number + ) { + super(scene, x, y); + + this.delta = 0; + this.invisible = true; + this.target = { x, y }; + this.sprites = new Map(); + + const animal = ["dog1", "dog2", "dog3", "cat1", "cat2", "cat3"]; + const random = Math.floor(Math.random() * animal.length); + + lazyLoadResource(this.scene.load, animal[random]).then(resource => { + this.addResource(resource); + this.invisible = false; + }) + + this.scene.physics.world.enableBody(this); + + this.getBody().setImmovable(true); + this.getBody().setCollideWorldBounds(true); + this.setSize(16, 16); + this.getBody().setSize(16, 16); + this.getBody().setOffset(0, 8); + + this.setDepth(-1); + + scene.game.events.addListener('step', this.step.bind(this)); + scene.add.existing(this); + } + + public setTarget(x: number, y: number) { + this.target = { x, y }; + } + + private step(time: any, delta: any) { + if (typeof this.target === 'undefined') return; + + this.delta += delta; + if (this.delta < 256) { + return; + } + this.delta = 0; + + const xDist = this.target.x - this.x; + const yDist = this.target.y - this.y; + + let direction: PlayerAnimationDirections; + let type: PlayerAnimationTypes; + + const distance = Math.sqrt(Math.pow(Math.abs(xDist), 2) + Math.pow(Math.abs(yDist), 2)); + + if (distance < 16) { + type = PlayerAnimationTypes.Idle; + this.getBody().stop(); + } else { + type = PlayerAnimationTypes.Walk; + + const xDir = xDist / Math.max(Math.abs(xDist), 1); + const yDir = yDist / Math.max(Math.abs(yDist), 1); + + const speed = 256; + this.getBody().setVelocity(Math.min(Math.abs(xDist * 2), speed) * xDir, Math.min(Math.abs(yDist * 2), speed) * yDir); + } + + if (Math.abs(xDist) > Math.abs(yDist)) { + if (xDist < 0) { + direction = PlayerAnimationDirections.Left; + } else { + direction = PlayerAnimationDirections.Right; + } + } else { + if (yDist < 0) { + direction = PlayerAnimationDirections.Up; + } else { + direction = PlayerAnimationDirections.Down; + } + } + + this.setDepth(this.y); + this.playAnimation(direction, type); + } + + private playAnimation(direction: PlayerAnimationDirections, type: PlayerAnimationTypes): void { + if (this.invisible) return; + + for (const [resource, sprite] of this.sprites.entries()) { + sprite.play(`${resource}-${direction}-${type}`, true); + } + } + + private addResource(resource: string, frame?: string | number): void { + const sprite = new Sprite(this.scene, 0, 0, resource, frame); + + this.add(sprite); + + this.getAnimations(resource).forEach(animation => { + this.scene.anims.create(animation); + }); + + this.scene.sys.updateList.add(sprite); + this.sprites.set(resource, sprite); + } + + private getAnimations(resource: string): Phaser.Types.Animations.Animation[] { + return [ + { + key: `${resource}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Idle}`, + frames: this.scene.anims.generateFrameNumbers(resource, {frames: [1]}), + frameRate: 10, + repeat: 1 + }, + { + key: `${resource}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Idle}`, + frames: this.scene.anims.generateFrameNumbers(resource, {frames: [4]}), + frameRate: 10, + repeat: 1 + }, + { + key: `${resource}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Idle}`, + frames: this.scene.anims.generateFrameNumbers(resource, {frames: [7]}), + frameRate: 10, + repeat: 1 + }, + { + key: `${resource}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Idle}`, + frames: this.scene.anims.generateFrameNumbers(resource, {frames: [10]}), + frameRate: 10, + repeat: 1 + }, + { + key: `${resource}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Walk}`, + frames: this.scene.anims.generateFrameNumbers(resource, {frames: [0, 1, 2]}), + frameRate: 15, + repeat: -1 + }, + { + key: `${resource}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Walk}`, + frames: this.scene.anims.generateFrameNumbers(resource, {frames: [3, 4, 5]}), + frameRate: 15, + repeat: -1 + }, + { + key: `${resource}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Walk}`, + frames: this.scene.anims.generateFrameNumbers(resource, {frames: [6, 7, 8]}), + frameRate: 15, + repeat: -1 + }, + { + key: `${resource}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Walk}`, + frames: this.scene.anims.generateFrameNumbers(resource, {frames: [9, 10, 11]}), + frameRate: 15, + repeat: -1 + } + ] + } + + private getBody(): Phaser.Physics.Arcade.Body { + const body = this.body; + + if (!(body instanceof Phaser.Physics.Arcade.Body)) { + throw new Error('Container does not have arcade body'); + } + + return body; + } + + public destroy(): void { + for (const sprite of this.sprites.values()) { + if (this.scene) { + this.scene.sys.updateList.remove(sprite); + } + } + + if (this.scene) { + this.scene.game.events.removeListener('step', this.step.bind(this)); + } + + super.destroy(); + } +} diff --git a/front/src/Phaser/Companion/CompanionTextures.ts b/front/src/Phaser/Companion/CompanionTextures.ts new file mode 100644 index 00000000..84eaf38f --- /dev/null +++ b/front/src/Phaser/Companion/CompanionTextures.ts @@ -0,0 +1,14 @@ +export interface CompanionResourceDescriptionInterface { + name: string, + img: string, + behaviour: "dog" | "cat" +} + +export const COMPANION_RESOURCES: CompanionResourceDescriptionInterface[] = [ + { name: "dog1", img: "resources/characters/pipoya/Dog 01-1.png", behaviour: "dog" }, + { name: "dog2", img: "resources/characters/pipoya/Dog 01-2.png", behaviour: "dog" }, + { name: "dog3", img: "resources/characters/pipoya/Dog 01-3.png", behaviour: "dog" }, + { name: "cat1", img: "resources/characters/pipoya/Cat 01-1.png", behaviour: "cat" }, + { name: "cat2", img: "resources/characters/pipoya/Cat 01-2.png", behaviour: "cat" }, + { name: "cat3", img: "resources/characters/pipoya/Cat 01-3.png", behaviour: "cat" }, +] diff --git a/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts b/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts new file mode 100644 index 00000000..7023a34d --- /dev/null +++ b/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts @@ -0,0 +1,30 @@ +import LoaderPlugin = Phaser.Loader.LoaderPlugin; +import { TextureError } from "../../Exception/TextureError"; +import { COMPANION_RESOURCES, CompanionResourceDescriptionInterface } from "./CompanionTextures"; + +export const loadAll = (loader: LoaderPlugin): CompanionResourceDescriptionInterface[] => { + const resources = COMPANION_RESOURCES; + + resources.forEach((resource: CompanionResourceDescriptionInterface) => { + loader.spritesheet(resource.name, resource.img, { frameWidth: 32, frameHeight: 32, endFrame: 12 }); + }); + + return resources; +} + +export const lazyLoadResource = (loader: LoaderPlugin, name: string): Promise => { + const resource = COMPANION_RESOURCES.find(item => item.name === name); + + if (typeof resource === 'undefined') { + throw new TextureError(`Texture '${name}' not found!`); + } + + if (loader.textureManager.exists(resource.name)) { + return Promise.resolve(resource.name); + } + + return new Promise(resolve => { + loader.spritesheet(resource.name, resource.img, { frameWidth: 32, frameHeight: 32, endFrame: 12 }); + loader.once(`filecomplete-spritesheet-${resource.name}`, () => resolve(resource.name)); + }); +} diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index 64ba56d0..b7a2f7b4 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -2,7 +2,7 @@ import {PlayerAnimationDirections} from "./Animation"; import {GameScene} from "../Game/GameScene"; import {UserInputEvent, UserInputManager} from "../UserInput/UserInputManager"; import {Character} from "../Entity/Character"; - +import {Companion} from "../Companion/Companion"; export const hasMovedEventName = "hasMoved"; export interface CurrentGamerInterface extends Character{ @@ -13,6 +13,7 @@ export interface CurrentGamerInterface extends Character{ export class Player extends Character implements CurrentGamerInterface { private previousDirection: string = PlayerAnimationDirections.Down; private wasMoving: boolean = false; + private companion?: Companion; constructor( Scene: GameScene, @@ -28,6 +29,20 @@ export class Player extends Character implements CurrentGamerInterface { //the current player model should be push away by other players to prevent conflict this.getBody().setImmovable(false); + + this.addCompanion(); + } + + addCompanion(): void { + this.companion = new Companion(this.scene, this.x, this.y); + } + + move(x: number, y: number) { + super.move(x, y); + + if (this.companion) { + this.companion.setTarget(this.x, this.y); + } } moveUser(delta: number): void {