diff --git a/TODO b/TODO index 30496d7..68d9a9f 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,4 @@ * UI: Use a common component class, and a layer abstraction -* UI: Fix tooltip sometimes being enormous * Character sheet: add tooltips (on values, slots and equipments) * Character sheet: add initial character creation * Character sheet: disable interaction during battle (except for loot screen) @@ -16,6 +15,7 @@ * All things displayed in battle should be updated from LogProcess forwarding, not from current game state * Drones: add tooltip * Drones: add hull points and take area damage +* Show power usage/recovery in action bar, on action hover * More sound effects * Add a battle log display * Organize arena objects and information in layers diff --git a/src/ui/BaseView.ts b/src/ui/BaseView.ts index 5b2955c..cdc5080 100644 --- a/src/ui/BaseView.ts +++ b/src/ui/BaseView.ts @@ -12,6 +12,9 @@ module TS.SpaceTac.UI { // Input and key bindings inputs: InputManager; + // Animations + animations: Animations; + // Timing timer: Timer; @@ -49,6 +52,9 @@ module TS.SpaceTac.UI { // Notifications this.messages = new Messages(this); + // Animations + this.animations = new Animations(this.game.tweens); + // Input manager this.inputs = new InputManager(this); diff --git a/src/ui/battle/ActionBar.ts b/src/ui/battle/ActionBar.ts index b56a5f2..d688c5f 100644 --- a/src/ui/battle/ActionBar.ts +++ b/src/ui/battle/ActionBar.ts @@ -95,7 +95,7 @@ module TS.SpaceTac.UI { setInteractive(interactive: boolean) { this.interactive = interactive; - Animation.setVisibility(this.game, this.icon_waiting, !this.interactive, 100); + this.battleview.animations.setVisible(this.icon_waiting, !this.interactive, 100); } /** diff --git a/src/ui/battle/ActionIcon.ts b/src/ui/battle/ActionIcon.ts index 6000734..129bac8 100644 --- a/src/ui/battle/ActionIcon.ts +++ b/src/ui/battle/ActionIcon.ts @@ -162,7 +162,7 @@ module TS.SpaceTac.UI { // Set the selected state on this icon setSelected(selected: boolean) { this.selected = selected; - Animation.setVisibility(this.game, this.layer_selected, this.selected, 300); + this.battleview.animations.setVisible(this.layer_selected, this.selected, 300); } // Update the active status, from the action canBeUsed result @@ -170,7 +170,7 @@ module TS.SpaceTac.UI { var old_active = this.active; this.active = !this.action.checkCannotBeApplied(this.ship); if (force || (this.active != old_active)) { - Animation.setVisibility(this.game, this.layer_active, this.active, 500); + this.battleview.animations.setVisible(this.layer_active, this.active, 500); this.game.tweens.create(this.layer_icon).to({ alpha: this.active ? 1 : 0.3 }, 500).start(); this.input.useHandCursor = this.active; } @@ -181,7 +181,7 @@ module TS.SpaceTac.UI { var old_fading = this.fading; this.fading = this.active && (this.action.checkCannotBeApplied(this.ship, remaining_ap) != null); if (this.fading != old_fading) { - Animation.setVisibility(this.game, this.layer_active, this.active && !this.fading, 500); + this.battleview.animations.setVisible(this.layer_active, this.active && !this.fading, 500); } } } diff --git a/src/ui/battle/ActionTooltip.ts b/src/ui/battle/ActionTooltip.ts index a8f0683..8324545 100644 --- a/src/ui/battle/ActionTooltip.ts +++ b/src/ui/battle/ActionTooltip.ts @@ -70,9 +70,9 @@ module TS.SpaceTac.UI { this.shortcut.setText(""); } - Animation.fadeIn(this.game, this, 200, 0.9); + this.bar.battleview.animations.show(this, 200, 0.9); } else { - Animation.fadeOut(this.game, this, 200); + this.bar.battleview.animations.hide(this, 200); } } } diff --git a/src/ui/battle/Arena.ts b/src/ui/battle/Arena.ts index d98ce94..d88909e 100644 --- a/src/ui/battle/Arena.ts +++ b/src/ui/battle/Arena.ts @@ -167,7 +167,7 @@ module TS.SpaceTac.UI { if (animate) { sprite.position.set(drone.owner.arena_x, drone.owner.arena_y); sprite.rotation = drone.owner.arena_angle; - let move_duration = Animation.moveInSpace(sprite, drone.x, drone.y, angle); + let move_duration = Animations.moveInSpace(sprite, drone.x, drone.y, angle); this.game.tweens.create(sprite.radius).from({ alpha: 0 }, 500, Phaser.Easing.Cubic.In, true, move_duration); return move_duration + 500; diff --git a/src/ui/battle/ArenaShip.ts b/src/ui/battle/ArenaShip.ts index 5309561..4ab4253 100644 --- a/src/ui/battle/ArenaShip.ts +++ b/src/ui/battle/ArenaShip.ts @@ -1,6 +1,9 @@ module TS.SpaceTac.UI { // Ship sprite in the arena (BattleView) export class ArenaShip extends Phaser.Group { + // Link to the view + battleview: BattleView; + // Link to displayed ship ship: Ship; @@ -25,10 +28,10 @@ module TS.SpaceTac.UI { // Create a ship sprite usable in the Arena constructor(parent: Arena, ship: Ship) { super(parent.game); - let battleview = parent.battleview; + this.battleview = parent.battleview; this.ship = ship; - this.enemy = this.ship.getPlayer() != battleview.player; + this.enemy = this.ship.getPlayer() != this.battleview.player; // Add ship sprite this.sprite = new Phaser.Button(this.game, 0, 0, "ship-" + ship.model + "-sprite"); @@ -58,7 +61,11 @@ module TS.SpaceTac.UI { this.addChild(this.effects); // Handle input on ship sprite - Tools.setHoverClick(this.sprite, () => battleview.cursorOnShip(ship), () => battleview.cursorOffShip(ship), () => battleview.cursorClicked()); + Tools.setHoverClick(this.sprite, + () => this.battleview.cursorOnShip(ship), + () => this.battleview.cursorOffShip(ship), + () => this.battleview.cursorClicked() + ); // Set location this.position.set(ship.arena_x, ship.arena_y); @@ -67,7 +74,7 @@ module TS.SpaceTac.UI { // Set the hovered state on this ship // This will toggle the hover effect setHovered(hovered: boolean) { - Animation.setVisibility(this.game, this.hover, hovered, 200); + this.battleview.animations.setVisible(this.hover, hovered, 200); } // Set the playing state on this ship @@ -84,7 +91,7 @@ module TS.SpaceTac.UI { this.displayEffect("stasis", false); } this.frame.alpha = dead ? 0.5 : 1.0; - Animation.setVisibility(this.game, this.stasis, dead, 400); + this.battleview.animations.setVisible(this.stasis, dead, 400); } /** @@ -94,7 +101,7 @@ module TS.SpaceTac.UI { */ moveTo(x: number, y: number, facing_angle: number, animate = true): number { if (animate) { - return Animation.moveInSpace(this, x, y, facing_angle, this.sprite); + return Animations.moveInSpace(this, x, y, facing_angle, this.sprite); } else { this.x = x; this.y = y; diff --git a/src/ui/battle/ShipListItem.ts b/src/ui/battle/ShipListItem.ts index cedd720..9c54a11 100644 --- a/src/ui/battle/ShipListItem.ts +++ b/src/ui/battle/ShipListItem.ts @@ -1,6 +1,9 @@ module TS.SpaceTac.UI { // One item in a ship list (used in BattleView) export class ShipListItem extends Phaser.Button { + // Reference to the view + view: BattleView; + // Reference to the ship game object ship: Ship; @@ -28,6 +31,7 @@ module TS.SpaceTac.UI { // Create a ship button for the battle ship list constructor(list: ShipList, x: number, y: number, ship: Ship, owned: boolean) { super(list.battleview.game, x, y, owned ? "battle-shiplist-own" : "battle-shiplist-enemy"); + this.view = list.battleview; this.ship = ship; @@ -104,7 +108,7 @@ module TS.SpaceTac.UI { // Set the hovered status setHovered(hovered: boolean) { - Animation.setVisibility(this.game, this.layer_hover, hovered, 200); + this.view.animations.setVisible(this.layer_hover, hovered, 200); } } } diff --git a/src/ui/battle/ShipTooltip.ts b/src/ui/battle/ShipTooltip.ts index ff504f1..d7ac043 100644 --- a/src/ui/battle/ShipTooltip.ts +++ b/src/ui/battle/ShipTooltip.ts @@ -118,9 +118,9 @@ module TS.SpaceTac.UI { this.stasis.visible = !ship.alive; - Animation.fadeIn(this.game, this, 200); + this.battleview.animations.show(this, 200); } else { - Animation.fadeOut(this.game, this, 200); + this.battleview.animations.hide(this, 200); } } diff --git a/src/ui/character/CharacterFleetMember.ts b/src/ui/character/CharacterFleetMember.ts index a5faeef..4813b55 100644 --- a/src/ui/character/CharacterFleetMember.ts +++ b/src/ui/character/CharacterFleetMember.ts @@ -33,7 +33,7 @@ module TS.SpaceTac.UI { */ setSelected(selected: boolean) { this.loadTexture(selected ? "character-ship-selected" : "character-ship"); - Animation.setVisibility(this.game, this.levelup, this.ship.getAvailableUpgradePoints() > 0, 200); + this.sheet.view.animations.setVisible(this.levelup, this.ship.getAvailableUpgradePoints() > 0, 200); } /** diff --git a/src/ui/common/Animation.spec.ts b/src/ui/common/Animation.spec.ts deleted file mode 100644 index 23d06cd..0000000 --- a/src/ui/common/Animation.spec.ts +++ /dev/null @@ -1,17 +0,0 @@ -module TS.SpaceTac.UI.Specs { - describe("Animation", () => { - let testgame = setupEmptyView(); - - it("animates rotation", function () { - let obj = { rotation: -Math.PI * 2.5 }; - let tween = testgame.ui.tweens.create(obj); - let result = Animation.rotationTween(tween, Math.PI * 0.25, 1, Phaser.Easing.Linear.None); - expect(result).toEqual(750); - expect(tween.generateData(4)).toEqual([ - { rotation: -Math.PI * 0.25 }, - { rotation: 0 }, - { rotation: Math.PI * 0.25 }, - ]); - }); - }); -} diff --git a/src/ui/common/Animations.spec.ts b/src/ui/common/Animations.spec.ts new file mode 100644 index 0000000..d453661 --- /dev/null +++ b/src/ui/common/Animations.spec.ts @@ -0,0 +1,36 @@ +module TS.SpaceTac.UI.Specs { + describe("Animations", () => { + let testgame = setupEmptyView(); + + it("shows and hides objects", function () { + let obj = { visible: false, alpha: 0.5 }; + + expect(testgame.baseview.animations.simulate(obj, 'alpha')).toEqual([]); + + testgame.baseview.animations.show(obj); + + expect(obj.visible).toBe(true); + expect(obj.alpha).toBe(0); + expect(testgame.baseview.animations.simulate(obj, 'alpha')).toEqual([0, 0.25, 0.5, 0.75, 1]); + + obj.alpha = 1; + testgame.baseview.animations.hide(obj); + + expect(obj.visible).toBe(true); + expect(obj.alpha).toBe(1); + expect(testgame.baseview.animations.simulate(obj, 'alpha')).toEqual([1, 0.75, 0.5, 0.25, 0]); + }); + + it("animates rotation", function () { + let obj = { rotation: -Math.PI * 2.5 }; + let tween = testgame.ui.tweens.create(obj); + let result = Animations.rotationTween(tween, Math.PI * 0.25, 1, Phaser.Easing.Linear.None); + expect(result).toEqual(750); + expect(tween.generateData(4)).toEqual([ + { rotation: -Math.PI * 0.25 }, + { rotation: 0 }, + { rotation: Math.PI * 0.25 }, + ]); + }); + }); +} diff --git a/src/ui/common/Animation.ts b/src/ui/common/Animations.ts similarity index 60% rename from src/ui/common/Animation.ts rename to src/ui/common/Animations.ts index 8eeb37a..da3c124 100644 --- a/src/ui/common/Animation.ts +++ b/src/ui/common/Animations.ts @@ -7,35 +7,82 @@ module TS.SpaceTac.UI { }; /** - * Utility functions for animation + * Interface of an object that may be shown/hidden, with opacity transition. */ - export class Animation { + interface IAnimationFadeable { + alpha: number; + visible: boolean; + } - // Display an object, fading in using opacity - static fadeIn(game: Phaser.Game, obj: PIXI.DisplayObject, duration: number = 1000, alpha: number = 1): void { + /** + * Manager of all animations. + * + * This is a wrapper around phaser's tweens. + */ + export class Animations { + private tweens: Phaser.TweenManager; + + constructor(tweens: Phaser.TweenManager) { + this.tweens = tweens; + } + + /** + * Create a tween on an object. + * + * If a previous tween is running for this object, it will be stopped, and a new one will be created. + */ + private createTween(obj: any): Phaser.Tween { + this.tweens.removeFrom(obj); + let result = this.tweens.create(obj); + return result; + } + + /** + * Simulate the tween currently applied to an object's property + * + * This may be heavy work and should only be done in testing code. + */ + simulate(obj: any, property: string, points = 5, duration = 1000): number[] { + let tween = first(this.tweens.getAll().concat((this.tweens)._add), tween => tween.target === obj && !tween.pendingDelete); + if (tween) { + return [obj[property]].concat(tween.generateData(points - 1).map(data => data[property])); + } else { + return []; + } + } + + /** + * Display an object, with opacity transition + */ + show(obj: IAnimationFadeable, duration = 1000, alpha = 1): void { if (!obj.visible) { obj.alpha = 0; obj.visible = true; } - var tween = game.tweens.create(obj); + + let tween = this.createTween(obj); tween.to({ alpha: alpha }, duration); tween.start(); } - // Hide an object, fading out using opacity - static fadeOut(game: Phaser.Game, obj: PIXI.DisplayObject, duration: number = 1000): void { - var tween = game.tweens.create(obj); + /** + * Hide an object, with opacity transition + */ + hide(obj: IAnimationFadeable, duration = 1000): void { + let tween = this.createTween(obj); tween.to({ alpha: 0 }, duration); tween.onComplete.addOnce(() => obj.visible = false); tween.start(); } - // Set visibility of an object, using either fadeIn or fadeOut - static setVisibility(game: Phaser.Game, obj: PIXI.DisplayObject, visible: boolean, duration: number = 1000): void { + /** + * Set an object visibility, with opacity transition + */ + setVisible(obj: IAnimationFadeable, visible: boolean, duration = 1000): void { if (visible) { - Animation.fadeIn(game, obj, duration); + this.show(obj, duration); } else { - Animation.fadeOut(game, obj, duration); + this.hide(obj, duration); } } @@ -77,7 +124,7 @@ module TS.SpaceTac.UI { static moveInSpace(obj: PhaserGraphics, x: number, y: number, angle: number, rotated_obj = obj): number { if (x == obj.x && y == obj.y) { let tween = obj.game.tweens.create(rotated_obj); - let duration = Animation.rotationTween(tween, angle, 0.3); + let duration = Animations.rotationTween(tween, angle, 0.3); tween.start(); return duration; } else { diff --git a/src/ui/map/StarSystemDisplay.ts b/src/ui/map/StarSystemDisplay.ts index ecd8855..9ed6244 100644 --- a/src/ui/map/StarSystemDisplay.ts +++ b/src/ui/map/StarSystemDisplay.ts @@ -116,7 +116,7 @@ module TS.SpaceTac.UI { // LOD let detailed = focus && level == 2; - this.children.forEach(child => Animation.setVisibility(this.game, child, detailed, 300)); + this.children.forEach(child => this.view.animations.setVisible(child, detailed, 300)); } } } diff --git a/src/ui/map/UniverseMapView.ts b/src/ui/map/UniverseMapView.ts index 5f9a40f..bc08dc1 100644 --- a/src/ui/map/UniverseMapView.ts +++ b/src/ui/map/UniverseMapView.ts @@ -128,9 +128,9 @@ module TS.SpaceTac.UI { let angle = Math.atan2(location.y, location.x); this.button_jump.scale.set(location.star.radius * 0.002, location.star.radius * 0.002); this.button_jump.position.set(location.star.x + location.x + 0.02 * Math.cos(angle), location.star.y + location.y + 0.02 * Math.sin(angle)); - Animation.setVisibility(this.game, this.button_jump, true, 300); + this.animations.setVisible(this.button_jump, true, 300); } else { - Animation.setVisibility(this.game, this.button_jump, false, 300); + this.animations.setVisible(this.button_jump, false, 300); } } @@ -180,7 +180,7 @@ module TS.SpaceTac.UI { */ doJump() { if (this.player.fleet.location && this.player.fleet.location.type == StarLocationType.WARP && this.player.fleet.location.jump_dest) { - Animation.setVisibility(this.game, this.button_jump, false, 300); + this.animations.setVisible(this.button_jump, false, 300); let dest_location = this.player.fleet.location.jump_dest; let dest_star = dest_location.star;