2017-09-24 22:23:22 +00:00
|
|
|
module TK.SpaceTac.UI {
|
2017-02-15 21:15:31 +00:00
|
|
|
interface PhaserGraphics {
|
2017-06-25 22:29:21 +00:00
|
|
|
x: number
|
|
|
|
y: number
|
|
|
|
rotation: number
|
2017-02-15 21:15:31 +00:00
|
|
|
};
|
|
|
|
|
2017-02-15 16:41:24 +00:00
|
|
|
/**
|
2017-04-10 17:38:33 +00:00
|
|
|
* Interface of an object that may be shown/hidden, with opacity transition.
|
2017-02-15 16:41:24 +00:00
|
|
|
*/
|
2017-04-10 17:38:33 +00:00
|
|
|
interface IAnimationFadeable {
|
2017-06-25 22:29:21 +00:00
|
|
|
alpha: number
|
|
|
|
visible: boolean
|
|
|
|
input?: { enabled: boolean }
|
|
|
|
changeStateFrame?: Function
|
|
|
|
freezeFrames?: boolean
|
2017-04-10 17:38:33 +00:00
|
|
|
}
|
2015-02-20 00:00:00 +00:00
|
|
|
|
2017-04-10 17:38:33 +00:00
|
|
|
/**
|
|
|
|
* Manager of all animations.
|
|
|
|
*
|
|
|
|
* This is a wrapper around phaser's tweens.
|
|
|
|
*/
|
|
|
|
export class Animations {
|
2018-05-15 14:57:45 +00:00
|
|
|
private tweens: Phaser.Tweens.TweenManager
|
2017-10-01 16:33:48 +00:00
|
|
|
private immediate = false
|
2017-04-10 17:38:33 +00:00
|
|
|
|
2018-05-15 14:57:45 +00:00
|
|
|
constructor(tweens: Phaser.Tweens.TweenManager) {
|
2017-04-10 17:38:33 +00:00
|
|
|
this.tweens = tweens;
|
|
|
|
}
|
|
|
|
|
2017-10-01 16:33:48 +00:00
|
|
|
/**
|
|
|
|
* Set all future animations to be immediate (and synchronous)
|
|
|
|
*
|
|
|
|
* This is mostly useful in tests
|
|
|
|
*/
|
|
|
|
setImmediate(immediate = true): void {
|
|
|
|
this.immediate = immediate;
|
|
|
|
}
|
|
|
|
|
2017-04-10 17:38:33 +00:00
|
|
|
/**
|
2018-05-15 14:57:45 +00:00
|
|
|
* Kill previous tweens from an object
|
2017-04-10 17:38:33 +00:00
|
|
|
*/
|
2018-05-15 14:57:45 +00:00
|
|
|
killPrevious(obj: object): void {
|
|
|
|
// TODO Only updated properties
|
|
|
|
this.tweens.killTweensOf(obj);
|
2017-04-10 17:38:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Simulate the tween currently applied to an object's property
|
|
|
|
*
|
|
|
|
* This may be heavy work and should only be done in testing code.
|
|
|
|
*/
|
2018-05-15 14:57:45 +00:00
|
|
|
simulate(obj: any, property: string, points = 5): number[] {
|
|
|
|
this.tweens.preUpdate();
|
|
|
|
let tween = first(this.tweens.getTweensOf(obj), tween => tween.isPlaying());
|
2017-04-10 17:38:33 +00:00
|
|
|
if (tween) {
|
2018-05-15 14:57:45 +00:00
|
|
|
let tween_obj = tween;
|
|
|
|
tween_obj.update(0, 0);
|
|
|
|
return range(points).map(i => {
|
|
|
|
tween_obj.seek(i / (points - 1));
|
|
|
|
return obj[property];
|
|
|
|
});
|
2017-04-10 17:38:33 +00:00
|
|
|
} else {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Display an object, with opacity transition
|
|
|
|
*/
|
|
|
|
show(obj: IAnimationFadeable, duration = 1000, alpha = 1): void {
|
2018-05-15 14:57:45 +00:00
|
|
|
this.killPrevious(obj);
|
|
|
|
|
2015-02-20 00:00:00 +00:00
|
|
|
if (!obj.visible) {
|
|
|
|
obj.alpha = 0;
|
|
|
|
obj.visible = true;
|
|
|
|
}
|
2017-04-10 17:38:33 +00:00
|
|
|
|
2018-05-15 14:57:45 +00:00
|
|
|
let onComplete: Function | undefined;
|
|
|
|
if (obj.input) {
|
|
|
|
let input = obj.input;
|
|
|
|
onComplete = () => {
|
|
|
|
input.enabled = true
|
|
|
|
obj.freezeFrames = false;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-10-01 16:33:48 +00:00
|
|
|
if (duration && !this.immediate) {
|
2018-05-15 14:57:45 +00:00
|
|
|
this.tweens.add({
|
|
|
|
targets: obj,
|
|
|
|
alpha: alpha,
|
|
|
|
duration: duration,
|
|
|
|
onComplete: onComplete
|
|
|
|
})
|
2017-05-23 16:42:55 +00:00
|
|
|
} else {
|
2017-06-13 15:39:56 +00:00
|
|
|
obj.alpha = alpha;
|
2018-05-15 14:57:45 +00:00
|
|
|
if (onComplete) {
|
|
|
|
onComplete();
|
2017-06-25 22:29:21 +00:00
|
|
|
}
|
2017-04-25 20:54:23 +00:00
|
|
|
}
|
2015-02-20 00:00:00 +00:00
|
|
|
}
|
|
|
|
|
2017-04-10 17:38:33 +00:00
|
|
|
/**
|
|
|
|
* Hide an object, with opacity transition
|
|
|
|
*/
|
2017-06-13 15:39:56 +00:00
|
|
|
hide(obj: IAnimationFadeable, duration = 1000, alpha = 0): void {
|
2018-05-15 14:57:45 +00:00
|
|
|
this.killPrevious(obj);
|
|
|
|
|
2017-06-25 22:29:21 +00:00
|
|
|
if (obj.changeStateFrame) {
|
|
|
|
obj.changeStateFrame("Out");
|
|
|
|
obj.freezeFrames = true;
|
|
|
|
}
|
|
|
|
|
2017-04-25 20:54:23 +00:00
|
|
|
if (obj.input) {
|
|
|
|
obj.input.enabled = false;
|
|
|
|
}
|
|
|
|
|
2018-05-15 14:57:45 +00:00
|
|
|
let onComplete = () => obj.visible = alpha > 0;
|
|
|
|
|
2017-10-01 16:33:48 +00:00
|
|
|
if (duration && !this.immediate) {
|
2018-05-15 14:57:45 +00:00
|
|
|
this.tweens.add({
|
|
|
|
targets: obj,
|
|
|
|
alpha: alpha,
|
|
|
|
duration: duration,
|
|
|
|
onComplete: onComplete
|
|
|
|
});
|
2017-05-23 16:42:55 +00:00
|
|
|
} else {
|
2017-06-13 15:39:56 +00:00
|
|
|
obj.alpha = alpha;
|
2018-05-15 14:57:45 +00:00
|
|
|
onComplete();
|
2017-05-23 16:42:55 +00:00
|
|
|
}
|
2015-02-20 00:00:00 +00:00
|
|
|
}
|
|
|
|
|
2017-04-10 17:38:33 +00:00
|
|
|
/**
|
|
|
|
* Set an object visibility, with opacity transition
|
|
|
|
*/
|
2017-06-13 15:39:56 +00:00
|
|
|
setVisible(obj: IAnimationFadeable, visible: boolean, duration = 1000, alphaon = 1, alphaoff = 0): void {
|
2015-02-20 00:00:00 +00:00
|
|
|
if (visible) {
|
2017-06-13 15:39:56 +00:00
|
|
|
this.show(obj, duration, alphaon);
|
2015-02-20 00:00:00 +00:00
|
|
|
} else {
|
2017-06-13 15:39:56 +00:00
|
|
|
this.hide(obj, duration, alphaoff);
|
2015-02-20 00:00:00 +00:00
|
|
|
}
|
|
|
|
}
|
2017-02-14 00:30:50 +00:00
|
|
|
|
2017-05-17 20:59:25 +00:00
|
|
|
/**
|
|
|
|
* Get a toggle on visibility
|
|
|
|
*/
|
2017-05-23 16:42:55 +00:00
|
|
|
newVisibilityToggle(obj: IAnimationFadeable, duration = 1000, initial = true): Toggle {
|
|
|
|
let result = new Toggle(() => this.setVisible(obj, true, duration), () => this.setVisible(obj, false, duration));
|
|
|
|
this.setVisible(obj, initial, 0);
|
|
|
|
return result;
|
2017-05-17 20:59:25 +00:00
|
|
|
}
|
|
|
|
|
2017-07-20 23:09:17 +00:00
|
|
|
/**
|
|
|
|
* Add an asynchronous animation to an object.
|
|
|
|
*/
|
2018-05-15 14:57:45 +00:00
|
|
|
addAnimation<T extends object>(obj: T, properties: Partial<T>, duration: number, ease = "Linear", delay = 0, loop = 1, yoyo = false): Promise<void> {
|
2017-07-20 23:09:17 +00:00
|
|
|
return new Promise((resolve, reject) => {
|
2018-05-15 14:57:45 +00:00
|
|
|
this.killPrevious(obj);
|
|
|
|
|
|
|
|
this.tweens.add(merge<object>({
|
|
|
|
targets: obj,
|
|
|
|
ease: ease,
|
|
|
|
duration: duration,
|
|
|
|
delay: delay,
|
|
|
|
loop: loop - 1,
|
|
|
|
onComplete: resolve,
|
|
|
|
yoyo: yoyo
|
|
|
|
}, properties));
|
2017-12-04 18:12:06 +00:00
|
|
|
|
|
|
|
// By security, if the tween is destroyed before completion, we resolve the promise using the timer
|
|
|
|
Timer.global.schedule(delay + duration, resolve);
|
2017-07-20 23:09:17 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-11-30 00:31:54 +00:00
|
|
|
/**
|
|
|
|
* Catch the player eye with a blink effect
|
|
|
|
*/
|
|
|
|
async blink(obj: any, alpha_on = 1, alpha_off = 0.3, times = 3): Promise<void> {
|
|
|
|
if (obj.alpha != alpha_on) {
|
|
|
|
await this.addAnimation(obj, { alpha: alpha_on }, 150);
|
|
|
|
}
|
|
|
|
for (let i = 0; i < times; i++) {
|
|
|
|
await this.addAnimation(obj, { alpha: alpha_off }, 150);
|
|
|
|
await this.addAnimation(obj, { alpha: alpha_on }, 150);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-14 00:30:50 +00:00
|
|
|
/**
|
|
|
|
* Interpolate a rotation value
|
|
|
|
*
|
|
|
|
* This will take into account the 2*pi modulo
|
|
|
|
*
|
|
|
|
* Returns the duration
|
|
|
|
*/
|
2018-05-15 14:57:45 +00:00
|
|
|
rotationTween(obj: Phaser.GameObjects.Components.Transform, dest: number, speed = 1, easing = "Cubic.easeInOut"): number {
|
2017-02-14 00:30:50 +00:00
|
|
|
// Immediately change the object's current rotation to be in range (-pi,pi)
|
2018-05-15 14:57:45 +00:00
|
|
|
let value = UITools.normalizeAngle(obj.rotation);
|
|
|
|
obj.setRotation(value);
|
2017-02-14 00:30:50 +00:00
|
|
|
|
|
|
|
// Compute destination angle
|
2017-05-04 23:19:28 +00:00
|
|
|
dest = UITools.normalizeAngle(dest);
|
2017-02-14 00:30:50 +00:00
|
|
|
if (value - dest > Math.PI) {
|
|
|
|
dest += 2 * Math.PI;
|
|
|
|
} else if (value - dest < -Math.PI) {
|
|
|
|
dest -= 2 * Math.PI;
|
|
|
|
}
|
2017-05-04 23:19:28 +00:00
|
|
|
let distance = Math.abs(UITools.normalizeAngle(dest - value)) / Math.PI;
|
2017-02-14 00:30:50 +00:00
|
|
|
let duration = distance * 1000 / speed;
|
|
|
|
|
2018-05-15 14:57:45 +00:00
|
|
|
// Tween
|
|
|
|
this.addAnimation(obj, { rotation: dest }, duration, easing);
|
2017-02-14 00:30:50 +00:00
|
|
|
|
|
|
|
return duration;
|
|
|
|
}
|
2017-02-15 21:15:31 +00:00
|
|
|
|
2017-08-17 17:51:22 +00:00
|
|
|
/**
|
|
|
|
* Move an object linearly to another position
|
|
|
|
*
|
|
|
|
* Returns the animation duration.
|
|
|
|
*/
|
2018-05-15 14:57:45 +00:00
|
|
|
moveTo(obj: Phaser.GameObjects.Components.Transform, x: number, y: number, angle: number, rotated_obj = obj, ease = true): number {
|
|
|
|
let duration_rot = this.rotationTween(rotated_obj, angle, 0.5);
|
2017-08-17 17:51:22 +00:00
|
|
|
let duration_pos = arenaDistance(obj, { x: x, y: y }) * 2;
|
2018-05-15 14:57:45 +00:00
|
|
|
this.addAnimation(obj, { x: x, y: y }, duration_pos, ease ? "Quad.easeInOut" : "Linear");
|
2017-08-17 17:51:22 +00:00
|
|
|
return Math.max(duration_rot, duration_pos);
|
|
|
|
}
|
|
|
|
|
2017-02-15 21:15:31 +00:00
|
|
|
/**
|
|
|
|
* Make an object move toward a location in space, with a ship-like animation.
|
|
|
|
*
|
|
|
|
* Returns the animation duration.
|
|
|
|
*/
|
2018-05-15 14:57:45 +00:00
|
|
|
moveInSpace(obj: Phaser.GameObjects.Components.Transform, x: number, y: number, angle: number, rotated_obj = obj): number {
|
2017-02-15 21:15:31 +00:00
|
|
|
if (x == obj.x && y == obj.y) {
|
2018-05-15 14:57:45 +00:00
|
|
|
this.killPrevious(obj);
|
|
|
|
return this.rotationTween(rotated_obj, angle, 0.5);
|
2017-02-15 21:15:31 +00:00
|
|
|
} else {
|
2018-05-15 14:57:45 +00:00
|
|
|
this.killPrevious(obj);
|
|
|
|
this.killPrevious(rotated_obj);
|
2017-02-15 21:15:31 +00:00
|
|
|
let distance = Target.newFromLocation(obj.x, obj.y).getDistanceTo(Target.newFromLocation(x, y));
|
|
|
|
let duration = Math.sqrt(distance / 1000) * 3000;
|
|
|
|
let curve_force = distance * 0.4;
|
|
|
|
let prevx = obj.x;
|
|
|
|
let prevy = obj.y;
|
2018-05-15 14:57:45 +00:00
|
|
|
let xpts = [obj.x, obj.x + Math.cos(rotated_obj.rotation) * curve_force, x - Math.cos(angle) * curve_force, x];
|
|
|
|
let ypts = [obj.y, obj.y + Math.sin(rotated_obj.rotation) * curve_force, y - Math.sin(angle) * curve_force, y];
|
|
|
|
let fobj = { t: 0 };
|
|
|
|
this.tweens.add({
|
|
|
|
targets: [fobj],
|
|
|
|
t: 1,
|
|
|
|
duration: duration,
|
|
|
|
ease: "Sine.easeInOut",
|
|
|
|
onUpdate: () => {
|
|
|
|
obj.setPosition(
|
|
|
|
Phaser.Math.Interpolation.CubicBezier(fobj.t, xpts[0], xpts[1], xpts[2], xpts[3]),
|
|
|
|
Phaser.Math.Interpolation.CubicBezier(fobj.t, ypts[0], ypts[1], ypts[2], ypts[3]),
|
|
|
|
)
|
|
|
|
if (prevx != obj.x || prevy != obj.y) {
|
|
|
|
rotated_obj.setRotation(Math.atan2(obj.y - prevy, obj.x - prevx));
|
|
|
|
}
|
|
|
|
prevx = obj.x;
|
|
|
|
prevy = obj.y;
|
2017-02-15 21:15:31 +00:00
|
|
|
}
|
2018-05-15 14:57:45 +00:00
|
|
|
})
|
2017-02-15 21:15:31 +00:00
|
|
|
return duration;
|
|
|
|
}
|
|
|
|
}
|
2015-02-20 00:00:00 +00:00
|
|
|
}
|
|
|
|
}
|