1
0
Fork 0

Added drone animation when applying effects

This commit is contained in:
Michaël Lemaire 2017-02-15 22:15:31 +01:00
parent 27057bbef9
commit 12ee9b823b
11 changed files with 167 additions and 58 deletions

6
TODO
View file

@ -4,16 +4,14 @@
* Quick loading does not cancel pending "setTimeout"s.
* Drones: add tooltip
* Drones: add hull points and take area damage
* Drones: change the sprite angle for deploy animation
* Drones: add animation for each activation
* Drones: fix not being removed when owner is in statis (owner's turn is skipped)
* More sound effects
* Do not apply effects on ships in stasis
* Add a battle log display
* Organize arena objects and information in layers
* Prevent arena effects information (eg. "shield -36") to overflow out of the arena
* Allow to cancel last moves
* Identify ships in emergency stasis more clearly
* Add more visual effects to weapons, hits and explosions
* Effect should be random in a range (eg. "damage target 50-75")
* Add an overload/cooling system
* Add auto-move to attack
@ -37,4 +35,6 @@
* Map: remove jump links that cross the radius of other systems
* Map: improve performance
* Menu: fix background stars aggregating at right side when the game is not focused
* Missions/quests system
* Main story arc
* Multiplayer

View file

@ -123,5 +123,22 @@ module TS.SpaceTac {
drone.onTurnStart(owner);
expect(removeDrone).toHaveBeenCalledWith(drone);
});
it("logs each activation", function () {
let battle = new Battle();
let ship = new Ship();
ship.fleet.setBattle(battle);
let other = new Ship();
let drone = new Drone(ship);
drone.apply([ship, other]);
drone.apply([]);
drone.apply([other]);
expect(battle.log.events).toEqual([
new DroneAppliedEvent(drone, [ship, other]),
new DroneAppliedEvent(drone, [other])
]);
});
});
}

View file

@ -32,30 +32,32 @@ module TS.SpaceTac {
}
/**
* Call a function for each ship in radius.
* Filter the list of ships in radius.
*/
forEachInRadius(ships: Ship[], callback: (ship: Ship) => any) {
ships.forEach(ship => {
if (ship.isInCircle(this.x, this.y, this.radius)) {
callback(ship);
}
});
filterShipsInRadius(ships: Ship[]): Ship[] {
return ships.filter(ship => ship.isInCircle(this.x, this.y, this.radius));
}
/**
* Apply the effects on a single ship.
* Apply the effects on a list of ships
*
* This does not check if the ship is in range.
* This does not check if the ships are in range.
*/
singleApply(ship: Ship) {
this.effects.forEach(effect => effect.applyOnShip(ship));
apply(ships: Ship[], log = true) {
if (ships.length > 0) {
let battle = this.owner.getBattle();
if (battle && log) {
battle.log.add(new DroneAppliedEvent(this, ships));
}
ships.forEach(ship => this.effects.forEach(effect => effect.applyOnShip(ship)));
}
}
/**
* Called when the drone is first deployed.
*/
onDeploy(ships: Ship[]) {
this.forEachInRadius(ships, ship => this.singleApply(ship));
this.apply(this.filterShipsInRadius(ships));
}
/**
@ -89,7 +91,7 @@ module TS.SpaceTac {
*/
onTurnEnd(ship: Ship) {
if (this.duration > 0 && ship.isInCircle(this.x, this.y, this.radius) && contains(this.inside_at_start, ship)) {
this.singleApply(ship);
this.apply([ship]);
}
}
@ -99,7 +101,7 @@ module TS.SpaceTac {
onShipMove(ship: Ship) {
if (this.duration > 0 && ship.isInCircle(this.x, this.y, this.radius)) {
if (add(this.inside, ship)) {
this.singleApply(ship);
this.apply([ship]);
}
} else {
remove(this.inside, ship);

View file

@ -18,9 +18,9 @@ module TS.SpaceTac.Equipments {
expect(drone.duration).toBe(1);
ship.setAttribute("hull_capacity", 100);
ship.setValue("hull", 85);
drone.singleApply(ship);
drone.apply([ship]);
expect(ship.getValue("hull")).toBe(95);
drone.singleApply(ship);
drone.apply([ship]);
expect(ship.getValue("hull")).toBe(100);
});
});

View file

@ -0,0 +1,21 @@
/// <reference path="BaseLogEvent.ts"/>
module TS.SpaceTac {
/**
* Event logged when a drone applies its effects
*/
export class DroneAppliedEvent extends BaseLogEvent {
// Pointer to the drone
drone: Drone;
// List of impacted ships
ships: Ship[];
constructor(drone: Drone, ships: Ship[]) {
super("droneapply", drone.owner);
this.drone = drone;
this.ships = ships;
}
}
}

View file

@ -139,22 +139,38 @@ module TS.SpaceTac.UI {
this.battleview.gameui.audio.playOnce("battle-ship-change");
}
/**
* Find an ArenaDrone displaying a Drone.
*/
findDrone(drone: Drone): ArenaDrone | null {
return first(this.drone_sprites, sprite => sprite.drone == drone);
}
/**
* Spawn a new drone
*
* Return the duration of deploy animation
*/
addDrone(drone: Drone): number {
if (!any(this.drone_sprites, sprite => sprite.drone == drone)) {
addDrone(drone: Drone, animate = true): number {
if (!this.findDrone(drone)) {
let sprite = new ArenaDrone(this.battleview, drone);
let angle = Math.atan2(drone.y - drone.owner.arena_y, drone.x - drone.owner.arena_x);
this.addChild(sprite);
this.drone_sprites.push(sprite);
sprite.position.set(drone.owner.arena_x, drone.owner.arena_y);
this.game.tweens.create(sprite.position).to({ x: drone.x, y: drone.y }, 1800, Phaser.Easing.Sinusoidal.InOut, true, 200);
this.game.tweens.create(sprite.radius.scale).from({ x: 0.01, y: 0.01 }, 1800, Phaser.Easing.Linear.None, true, 200);
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);
this.game.tweens.create(sprite.radius).from({ alpha: 0 }, 500, Phaser.Easing.Cubic.In, true, move_duration);
return move_duration + 500;
} else {
sprite.position.set(drone.x, drone.y);
sprite.rotation = angle;
return 0;
}
return 2000;
} else {
console.error("Drone added twice to arena", drone);
return 0;
@ -163,7 +179,7 @@ module TS.SpaceTac.UI {
// Remove a destroyed drone
removeDrone(drone: Drone): void {
let sprite = first(this.drone_sprites, sprite => sprite.drone == drone);
let sprite = this.findDrone(drone);
if (sprite) {
remove(this.drone_sprites, sprite);
sprite.destroy();

View file

@ -7,27 +7,52 @@ module TS.SpaceTac.UI {
drone: Drone;
// Sprite
sprite: Phaser.Button;
sprite: Phaser.Image;
// Radius
radius: Phaser.Graphics;
// Activation effect
activation: Phaser.Graphics;
constructor(battleview: BattleView, drone: Drone) {
super(battleview.game);
this.drone = drone;
this.radius = new Phaser.Graphics(this.game, 0, 0);
this.radius.lineStyle(3, 0xe9f2f9, 0.5);
this.radius.lineStyle(2, 0xe9f2f9, 0.3);
this.radius.beginFill(0xe9f2f9, 0.0);
this.radius.drawCircle(0, 0, drone.radius * 2);
this.radius.endFill();
this.addChild(this.radius);
this.sprite = new Phaser.Button(this.game, 0, 0, `battle-actions-deploy-${drone.code}`);
this.activation = new Phaser.Graphics(this.game, 0, 0);
this.activation.lineStyle(2, 0xe9f2f9, 0.7);
this.activation.beginFill(0xe9f2f9, 0.0);
this.activation.drawCircle(0, 0, drone.radius * 2);
this.activation.endFill();
this.activation.visible = false;
this.addChild(this.activation);
this.sprite = new Phaser.Image(this.game, 0, 0, `battle-actions-deploy-${drone.code}`);
this.sprite.anchor.set(0.5, 0.5);
this.sprite.scale.set(0.1, 0.1);
this.addChild(this.sprite);
}
/**
* Start the activation animation
*
* Return the animation duration
*/
setApplied(): number {
this.activation.scale.set(0.001, 0.001);
this.activation.visible = true;
let tween = this.game.tweens.create(this.activation.scale).to({ x: 1, y: 1 }, 500);
tween.onComplete.addOnce(() => this.activation.visible = false);
tween.start();
return 500;
}
}
}

View file

@ -19,10 +19,6 @@ module TS.SpaceTac.UI {
// Effects display
effects: Phaser.Group;
// Previous position
private prevx;
private prevy;
// Create a ship sprite usable in the Arena
constructor(parent: Arena, ship: Ship) {
super(parent.game);
@ -56,17 +52,15 @@ module TS.SpaceTac.UI {
Tools.setHoverClick(this.sprite, () => battleview.cursorOnShip(ship), () => battleview.cursorOffShip(ship), () => battleview.cursorClicked());
// Set location
this.prevx = ship.arena_x;
this.prevy = ship.arena_y;
this.position.set(ship.arena_x, ship.arena_y);
}
update() {
if (this.prevx != this.x || this.prevy != this.y) {
/*if (this.prevx != this.x || this.prevy != this.y) {
this.sprite.rotation = Math.atan2(this.y - this.prevy, this.x - this.prevx);
}
this.prevx = this.x;
this.prevy = this.y;
this.prevy = this.y;*/
}
// Set the hovered state on this ship
@ -88,24 +82,7 @@ module TS.SpaceTac.UI {
*/
moveTo(x: number, y: number, facing_angle: number, animate = true): number {
if (animate) {
if (x == this.x && y == this.y) {
let tween = this.game.tweens.create(this.sprite);
let duration = Animation.rotationTween(tween, facing_angle, 0.3);
tween.start();
return duration;
} else {
let distance = Target.newFromLocation(this.x, this.y).getDistanceTo(Target.newFromLocation(x, y));
var tween = this.game.tweens.create(this);
let duration = Math.sqrt(distance / 1000) * 3000;
let curve_force = distance * 0.4;
tween.to({
x: [this.x + Math.cos(this.sprite.rotation) * curve_force, x - Math.cos(facing_angle) * curve_force, x],
y: [this.y + Math.sin(this.sprite.rotation) * curve_force, y - Math.sin(facing_angle) * curve_force, y]
}, duration, Phaser.Easing.Sinusoidal.InOut);
tween.interpolation((v, k) => Phaser.Math.bezierInterpolation(v, k));
tween.start();
return duration;
}
return Animation.moveInSpace(this, x, y, facing_angle, this.sprite);
} else {
this.x = x;
this.y = y;

View file

@ -161,15 +161,13 @@ module TS.SpaceTac.UI {
this.ship_hovered = ship;
this.arena.setShipHovered(ship);
this.ship_list.setHovered(ship);
this.ship_tooltip.setShip(ship);
if (this.targetting) {
if (ship) {
this.targetting.setTargetShip(ship);
} else {
this.targetting.unsetTarget();
}
this.ship_tooltip.setShip(null);
} else {
this.ship_tooltip.setShip(ship);
}
}

View file

@ -79,6 +79,8 @@ module TS.SpaceTac.UI {
this.processDroneDeployedEvent(event);
} else if (event instanceof DroneDestroyedEvent) {
this.processDroneDestroyedEvent(event);
} else if (event instanceof DroneAppliedEvent) {
this.processDroneAppliedEvent(event);
} else if (event.code == "effectadd" || event.code == "effectduration" || event.code == "effectdel") {
this.processEffectEvent(event);
}
@ -183,5 +185,14 @@ module TS.SpaceTac.UI {
private processDroneDestroyedEvent(event: DroneDestroyedEvent): void {
this.view.arena.removeDrone(event.drone);
}
// Drone applied
private processDroneAppliedEvent(event: DroneAppliedEvent): void {
let drone = this.view.arena.findDrone(event.drone);
if (drone) {
let duration = drone.setApplied();
this.delayNextEvents(duration);
}
}
}
}

View file

@ -1,4 +1,11 @@
module TS.SpaceTac.UI {
interface PhaserGraphics {
x: number;
y: number;
rotation: number;
game: Phaser.Game;
};
/**
* Utility functions for animation
*/
@ -60,5 +67,40 @@ module TS.SpaceTac.UI {
return duration;
}
/**
* Make an object move toward a location in space, with a ship-like animation.
*
* Returns the animation duration.
*/
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);
tween.start();
return duration;
} else {
let distance = Target.newFromLocation(obj.x, obj.y).getDistanceTo(Target.newFromLocation(x, y));
var tween = obj.game.tweens.create(obj);
let duration = Math.sqrt(distance / 1000) * 3000;
let curve_force = distance * 0.4;
tween.to({
x: [obj.x + Math.cos(rotated_obj.rotation) * curve_force, x - Math.cos(angle) * curve_force, x],
y: [obj.y + Math.sin(rotated_obj.rotation) * curve_force, y - Math.sin(angle) * curve_force, y]
}, duration, Phaser.Easing.Sinusoidal.InOut);
tween.interpolation((v, k) => Phaser.Math.bezierInterpolation(v, k));
let prevx = obj.x;
let prevy = obj.y;
tween.onUpdateCallback(() => {
if (prevx != obj.x || prevy != obj.y) {
rotated_obj.rotation = Math.atan2(obj.y - prevy, obj.x - prevx);
}
prevx = obj.x;
prevy = obj.y;
});
tween.start();
return duration;
}
}
}
}