From ddc99e34982e64b65c6c703b8c8b53c47e49f909 Mon Sep 17 00:00:00 2001 From: Paolo Brasolin <paolo.brasolin@eurac.edu> Date: Mon, 14 Mar 2022 21:21:19 +0100 Subject: [PATCH] feat: #fe refactor spear into its own class --- frontend/src/js/fight_scene.ts | 124 ++++++++++----------------------- frontend/src/js/foe.ts | 7 ++ frontend/src/js/main.ts | 4 +- frontend/src/js/spear.ts | 84 ++++++++++++++++++++++ 4 files changed, 129 insertions(+), 90 deletions(-) create mode 100644 frontend/src/js/spear.ts diff --git a/frontend/src/js/fight_scene.ts b/frontend/src/js/fight_scene.ts index cc5cc1f..786eb9f 100644 --- a/frontend/src/js/fight_scene.ts +++ b/frontend/src/js/fight_scene.ts @@ -1,22 +1,20 @@ import "phaser"; import Foe from "./foe"; +import Spear from "./spear"; import backend from "./backend"; // TODO: write interfaces -import nr from "newton-raphson-method"; import levenshtein from "damerau-levenshtein"; export default class FightScene extends Phaser.Scene { foes: Array<Foe>; - spears: Array<Phaser.GameObjects.Sprite>; ground: Phaser.Types.Physics.Arcade.ImageWithStaticBody; player: Phaser.Types.Physics.Arcade.SpriteWithDynamicBody; constructor() { super("fight"); this.foes = []; - this.spears = []; } preload() { @@ -164,11 +162,39 @@ export default class FightScene extends Phaser.Scene { initAndBindGuessPreview(this); } - update() { - // TODO: re-enable - // this.spears.forEach((spear) => { - // spear.setRotation(spear.body.velocity.angle()); - // }); + showMissMessage() { + const message = this.add + .sprite(this.cameras.main.width / 2, this.cameras.main.height / 2, "miss") + .setScale(1); + message.play({ key: "missing", repeat: 1 }); + message.on("animationcomplete", () => { + message.anims.remove("miss"); + message.destroy(); + }); + } + + showHitMessage() { + const message = this.add + .sprite(this.cameras.main.width / 2, this.cameras.main.height / 2, "hit") + .setScale(1); + message.play({ key: "hit", repeat: 1 }); + message.on("animationcomplete", () => { + message.anims.remove("hit"); + message.destroy(); + }); + } + + shootSpear(enemy: Phaser.GameObjects.Sprite, hit: boolean) { + const scene = this; + if (!hit) { + this.showMissMessage(); + } else { + this.showHitMessage(); + // TODO: ew. + scene.foes.splice(scene.foes.indexOf(enemy), 1); // FIXME + } + + new Spear(this, this.player, enemy); } } @@ -250,86 +276,6 @@ function initAndBindGuessPreview(scene: FightScene) { ); } -function shootSpear( - enemy: Phaser.GameObjects.Sprite, - hit: boolean, - scene: FightScene, -) { - if (!hit) { - const message = scene.add - .sprite( - scene.cameras.main.width / 2, - scene.cameras.main.height / 2, - "miss", - ) - .setScale(1); - message.play({ key: "missing", repeat: 1 }); - message.on("animationcomplete", () => { - message.anims.remove("miss"); - message.destroy(); - }); - } else { - const message = scene.add - .sprite( - scene.cameras.main.width / 2, - scene.cameras.main.height / 2, - "hit", - ) - .setScale(1); - message.play({ key: "hit", repeat: 1 }); - message.on("animationcomplete", () => { - message.anims.remove("hit"); - message.destroy(); - }); - // TODO: ew. - scene.foes.splice(scene.foes.indexOf(enemy), 1); // FIXME - } - - const spear = scene.add.sprite(scene.player.x, scene.player.y, "spear"); - scene.spears.push(spear); - scene.physics.world.enable(spear); - scene.physics.add.collider(spear, scene.ground); - spear.body.setBounce(0.2); - - const dx = scene.player.x - enemy.x; - const dy = 0; - const v = 450; // MAGIC NUMBER - const w = 100; - const g = 200; - // TODO: maybe introduce damping - // TODO: expand and use analytic derivative - const f = (theta) => - 2 * dy * Math.pow(w - v * Math.cos(theta), 2) + - 2 * v * Math.sin(theta) * (w - v * Math.cos(theta)) * dx + - g * Math.pow(dx, 2); - - const theta = nr(f, Math.PI, { - verbose: true, - maxIterations: 100, - }); - - const t = dx / (w - v * Math.cos(theta)); - - if (theta) { - spear.body.setVelocity(v * Math.cos(theta), v * Math.sin(theta)); - } - - scene.physics.add.overlap(spear, enemy, (player, nemico) => { - scene.physics.world.removeCollider(this); - // TODO: fancy bounce - scene.spears.splice(scene.spears.indexOf(spear), 1); - spear.destroy(); - // TODO: refactor into flee method - nemico.play(nemico.species + "_run"); - nemico.flipX = false; - nemico.body.setVelocity(-200, 0); - setTimeout(() => nemico.destroy(), 2000); - }); - - spear.scale = 2; - spear.anims.play("spearAni"); -} - function submitTranscription(transcription: string, scene: FightScene) { if (scene.foes.length < 1) return; @@ -354,7 +300,7 @@ function submitTranscription(transcription: string, scene: FightScene) { const hit = similarity >= 0.9; const enemy = match; - shootSpear(enemy.animalSprite, hit, scene); + scene.shootSpear(enemy.animalSprite, hit); } function gameStart(scene: any) { diff --git a/frontend/src/js/foe.ts b/frontend/src/js/foe.ts index 14c62fb..ccf208c 100644 --- a/frontend/src/js/foe.ts +++ b/frontend/src/js/foe.ts @@ -53,6 +53,13 @@ class Foe { .setInteractive(); this.animalSprite.flipX = true; + this.animalSprite.flee = function () { + this.play(this.species + "_run"); + this.flipX = false; + this.body.setVelocity(-200, 0); + setTimeout(() => this.destroy(), 2000); // TODO: disappear offscreen + }; + this.scene.physics.add.collider(this.animalSprite, this.scene.ground); setAnimation(this.animalSprite, this.species + "_walk"); diff --git a/frontend/src/js/main.ts b/frontend/src/js/main.ts index 4687c97..cadc460 100644 --- a/frontend/src/js/main.ts +++ b/frontend/src/js/main.ts @@ -2,6 +2,8 @@ import * as Phaser from "phaser"; import FightScene from "./fight_scene"; +export const GRAVITY_Y = 200; + const config = { type: Phaser.AUTO, width: 1200, @@ -12,7 +14,7 @@ const config = { physics: { default: "arcade", arcade: { - gravity: { y: 200 }, + gravity: { y: GRAVITY_Y }, debug: true, }, }, diff --git a/frontend/src/js/spear.ts b/frontend/src/js/spear.ts new file mode 100644 index 0000000..c017b72 --- /dev/null +++ b/frontend/src/js/spear.ts @@ -0,0 +1,84 @@ +import "phaser"; +import FightScene from "./fight_scene"; + +import { GRAVITY_Y } from "./main"; +import newtonRaphson from "newton-raphson-method"; // TODO: TS signatures + +const SPEED = 450; + +class Spear extends Phaser.Physics.Arcade.Sprite { + source: Phaser.GameObjects.Sprite; + target: Phaser.GameObjects.Sprite; + body: Phaser.Physics.Arcade.Body; + + constructor( + scene: FightScene, + source: Phaser.GameObjects.Sprite, + target: Phaser.GameObjects.Sprite, + ) { + super(scene, scene.player.x, scene.player.y, "spear"); + scene.add.existing(this); + + this.setScale(3); + + this.source = source; + this.target = target; + + //scene.physics.world.enableBody(this, Phaser.Physics.Arcade.DYNAMIC_BODY); + this.body = new Phaser.Physics.Arcade.Body(scene.physics.world, this); + scene.physics.world.add(this.body); + scene.physics.add.collider(this, scene.ground); + this.body.setBounce(0, 0.2); // TODO: bounce only at small angles + + const theta = this.calculateSuccessfulLaunchAngle(source, target); + + if (theta) { + this.body.setVelocity(SPEED * Math.cos(theta), SPEED * Math.sin(theta)); + } else { + console.error("Cannot hit foe."); + } + + scene.physics.add.overlap(this, this.target, (_, hitTarget) => { + scene.physics.world.removeCollider(this); + this.destroy(); + hitTarget.flee(); + }); + + // this.anims.play("spearAni"); + } + + preUpdate(): void { + const velocity = this.body.velocity as Phaser.Math.Vector2; + this.setRotation(velocity.angle()); + } + + calculateSuccessfulLaunchAngle( + source: Phaser.GameObjects.Sprite, + target: Phaser.GameObjects.Sprite, + ): number | undefined { + const dx = source.x - target.x; + const dy = source.y - target.y; + const v = SPEED; // NOTE: this is a MAGIC NUMBER + const w = target.body.velocity.x; + const g = GRAVITY_Y; + + // TODO: air drag + // TODO: damp x velocity on impact + + // NOTE: this is an implicit function to solve numerically for finding launch angle + const f = (theta: number) => + 2 * dy * Math.pow(w - v * Math.cos(theta), 2) + + 2 * v * Math.sin(theta) * (w - v * Math.cos(theta)) * dx + + g * Math.pow(dx, 2); + + // TODO: expand and use analytic derivative for better precision + const theta = newtonRaphson(f, Math.PI, { + verbose: true, + maxIterations: 100, + }); + + return typeof theta == "number" ? theta : undefined; + } +} + +export default Spear; -- GitLab