-
Paolo.Brasolin authoredPaolo.Brasolin authored
spear.ts 3.35 KiB
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 | undefined;
body: Phaser.Physics.Arcade.Body;
constructor(
scene: FightScene,
source: Phaser.GameObjects.Sprite,
target: Phaser.GameObjects.Sprite | undefined,
) {
super(scene, scene.player.x, scene.player.y, "spear");
this.play({ key: "spearAni", repeat: -1 });
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.hitGround.bind(this));
this.body.setBounce(0, 0.2); // TODO: bounce only at small angles
if (this.target) {
this.shootTarget();
} else {
this.shootGround();
}
}
shootTarget() {
const theta = this.calculateSuccessfulLaunchAngle(this.source, this.target);
if (theta) {
this.body.setVelocity(SPEED * Math.cos(theta), SPEED * Math.sin(theta));
this.scene.physics.add.overlap(
this,
this.target,
this.hitTarget.bind(this),
);
} else {
console.error("Cannot hit foe. :(");
}
}
hitTarget() {
this.scene.physics.world.removeCollider(this);
// TODO: bounce?
this.destroy();
this.target.flee();
}
shootGround() {
const theta = Math.random() * 2 * Math.PI;
this.body.setVelocity(SPEED * Math.cos(theta), SPEED * Math.sin(theta));
}
hitGround() {
// this.scene.physics.world.remove(this.body);
this.body.setEnable(false);
this.play({ key: "spearHitAni", repeat: -1, frameRate: 48 });
this.setRotation(this.rotation - Math.PI / 2);
this.scene.tweens.add({
targets: this,
alpha: 0,
ease: "Linear",
delay: 500,
duration: 1500,
onComplete: this.destroy.bind(this),
});
}
preUpdate(time, delta): void {
super.preUpdate(time, delta); // NOTE: this preserves sprite animations
if (this.body.enable) this.alignToVelocity();
}
alignToVelocity() {
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;