From 37b7bd40d215338ac27810d7f4b04afe7d8f4780 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Lemaire?= Date: Wed, 31 Dec 2014 01:00:00 +0100 Subject: [PATCH] Added ship hovering system --- src/scripts/game/Battle.ts | 11 ++- src/scripts/game/BattleLog.ts | 23 +++++ src/scripts/game/events/MoveEvent.ts | 8 ++ src/scripts/{ => game}/specs/Battle.spec.ts | 2 +- .../{ => game}/specs/BattleLog.spec.ts | 43 ++++++++++ src/scripts/{ => game}/specs/Ship.spec.ts | 2 +- src/scripts/view/BattleView.ts | 86 +++++++++++++++++-- src/scripts/view/arena/ShipArenaSprite.ts | 17 ++-- src/scripts/view/widgets/ShipCard.ts | 23 +++++ src/scripts/view/widgets/ShipListItem.ts | 18 +++- 10 files changed, 213 insertions(+), 20 deletions(-) create mode 100644 src/scripts/game/events/MoveEvent.ts rename src/scripts/{ => game}/specs/Battle.spec.ts (98%) rename src/scripts/{ => game}/specs/BattleLog.spec.ts (50%) rename src/scripts/{ => game}/specs/Ship.spec.ts (95%) create mode 100644 src/scripts/view/widgets/ShipCard.ts diff --git a/src/scripts/game/Battle.ts b/src/scripts/game/Battle.ts index 0a7a37a..f7c933b 100644 --- a/src/scripts/game/Battle.ts +++ b/src/scripts/game/Battle.ts @@ -103,10 +103,17 @@ module SpaceTac.Game { // Force an injection of events in the battle log to simulate the initial state // For instance, this may be called after 'start', to use the log subscription system // to initialize a battle UI + // Attributes 'play_order' and 'playing_ship' should be defined before calling this injectInitialEvents(): void { - // TODO Simulate initial ship placement + var log = this.log; + + // Simulate initial ship placement + this.play_order.forEach((ship) => { + log.add(new Events.MoveEvent(ship, ship.arena_x, ship.arena_y)); + }); + // Simulate game turn - this.log.add(new Events.ShipChangeEvent(this.playing_ship, this.playing_ship)); + log.add(new Events.ShipChangeEvent(this.playing_ship, this.playing_ship)); } // Create a quick random battle, for testing purposes diff --git a/src/scripts/game/BattleLog.ts b/src/scripts/game/BattleLog.ts index 3975e48..8115714 100644 --- a/src/scripts/game/BattleLog.ts +++ b/src/scripts/game/BattleLog.ts @@ -6,14 +6,37 @@ module SpaceTac.Game { // Full list of battle events events: Events.BaseLogEvent[]; + // List of subscribers + private subscribers: Function[]; + // Create an initially empty log constructor() { this.events = []; + this.subscribers = []; } // Add a battle event to the log add(event: Events.BaseLogEvent) { this.events.push(event); + + this.subscribers.forEach((subscriber) => { + subscriber(event); + }); + } + + // Subscribe a callback to receive further events + subscribe(callback: (event: Events.BaseLogEvent) => void): Function { + this.subscribers.push(callback); + return callback; + } + + // Unsubscribe a callback + // Pass the value returned by 'subscribe' as argument + unsubscribe(callback: Function): void { + var index = this.subscribers.indexOf(callback); + if (index >= 0) { + this.subscribers.splice(index, 1); + } } } } \ No newline at end of file diff --git a/src/scripts/game/events/MoveEvent.ts b/src/scripts/game/events/MoveEvent.ts new file mode 100644 index 0000000..4e821b9 --- /dev/null +++ b/src/scripts/game/events/MoveEvent.ts @@ -0,0 +1,8 @@ +module SpaceTac.Game.Events { + // Event logged when a ship moves + export class MoveEvent extends BaseLogEvent { + constructor(ship: Ship, x: number, y: number) { + super("move", ship, Target.newFromLocation(x, y)); + } + } +} \ No newline at end of file diff --git a/src/scripts/specs/Battle.spec.ts b/src/scripts/game/specs/Battle.spec.ts similarity index 98% rename from src/scripts/specs/Battle.spec.ts rename to src/scripts/game/specs/Battle.spec.ts index 7b50d64..67a599b 100644 --- a/src/scripts/specs/Battle.spec.ts +++ b/src/scripts/game/specs/Battle.spec.ts @@ -1,4 +1,4 @@ -/// +/// module SpaceTac.Specs { describe("Battle", function () { diff --git a/src/scripts/specs/BattleLog.spec.ts b/src/scripts/game/specs/BattleLog.spec.ts similarity index 50% rename from src/scripts/specs/BattleLog.spec.ts rename to src/scripts/game/specs/BattleLog.spec.ts index e5f10f5..7f1e1d3 100644 --- a/src/scripts/specs/BattleLog.spec.ts +++ b/src/scripts/game/specs/BattleLog.spec.ts @@ -1,3 +1,5 @@ +/// + module SpaceTac.Specs { // Check a single game log event @@ -27,7 +29,34 @@ module SpaceTac.Specs { } } + // Fake event + class FakeEvent extends Game.Events.BaseLogEvent { + constructor() { + super("fake"); + } + } + describe("BattleLog", function () { + it("forwards events to subscribers, until unsubscribe", function () { + var log = new Game.BattleLog(); + var received = []; + var fake = new FakeEvent(); + + var sub = log.subscribe(function (event) { + received.push(event); + }); + + log.add(fake); + expect(received).toEqual([fake]); + + log.add(fake); + expect(received).toEqual([fake, fake]); + + log.unsubscribe(sub); + log.add(fake); + expect(received).toEqual([fake, fake]); + }); + it("logs ship change events", function () { var battle = Game.Battle.newQuickRandom(); expect(battle.log.events.length).toBe(0); @@ -36,5 +65,19 @@ module SpaceTac.Specs { expect(battle.log.events.length).toBe(1); checkEvent(battle.log.events[0], battle.play_order[0], "ship_change", battle.play_order[1]); }); + + it("can receive simulated initial state events", function (){ + var battle = Game.Battle.newQuickRandom(); + + expect(battle.log.events.length).toBe(0); + + battle.injectInitialEvents(); + + expect(battle.log.events.length).toBe(9); + for (var i = 0; i < 8; i++) { + checkEvent(battle.log.events[i], battle.play_order[i], "move", null, battle.play_order[i].arena_x, battle.play_order[i].arena_y); + } + checkEvent(battle.log.events[8], battle.playing_ship, "ship_change", battle.playing_ship); + }); }); } \ No newline at end of file diff --git a/src/scripts/specs/Ship.spec.ts b/src/scripts/game/specs/Ship.spec.ts similarity index 95% rename from src/scripts/specs/Ship.spec.ts rename to src/scripts/game/specs/Ship.spec.ts index 3662fa8..ca90d42 100644 --- a/src/scripts/specs/Ship.spec.ts +++ b/src/scripts/game/specs/Ship.spec.ts @@ -1,4 +1,4 @@ -/// +/// module SpaceTac.Specs { describe("Ship", function(){ diff --git a/src/scripts/view/BattleView.ts b/src/scripts/view/BattleView.ts index 6da2ae6..c04b772 100644 --- a/src/scripts/view/BattleView.ts +++ b/src/scripts/view/BattleView.ts @@ -17,15 +17,30 @@ module SpaceTac.View { // Targetting mode (null if we're not in this mode) targetting: Targetting; + // Card to display current playing ship + card_playing: Widgets.ShipCard; + + // Card to display hovered ship + card_hovered: Widgets.ShipCard; + + // Currently hovered ship + ship_hovered: Game.Ship; + + // Subscription to the battle log + log_subscription: any; + // Init the view, binding it to a specific battle init(player, battle) { this.player = player; this.battle = battle; this.targetting = null; + this.ship_hovered = null; + this.log_subscription = null; } // Create view graphics create() { + var battleview = this; var game = this.game; var player = this.player; @@ -37,28 +52,83 @@ module SpaceTac.View { game.add.existing(this.arena); var arena = this.arena; + this.card_playing = new Widgets.ShipCard(this, 500, 0); + this.card_hovered = new Widgets.ShipCard(this, 500, 300); + game.stage.backgroundColor = 0x000000; // Add ship buttons to UI - this.battle.play_order.forEach(function(ship: Game.Ship, rank: number){ - new Widgets.ShipListItem(ui, 0, rank * 50, ship.getPlayer() === player); + this.battle.play_order.forEach(function (ship: Game.Ship, rank: number) { + new Widgets.ShipListItem(battleview, 0, rank * 50, ship, ship.getPlayer() === player); }); // Add ship sprites to arena - this.battle.play_order.forEach(function(ship: Game.Ship){ - new Arena.ShipArenaSprite(arena, ship); + this.battle.play_order.forEach(function (ship: Game.Ship) { + new Arena.ShipArenaSprite(battleview, ship); }); + + // Subscribe to log events + this.battle.log.subscribe((event) => { + battleview.processBattleEvent(event); + }); + this.battle.injectInitialEvents(); } // Leaving the view, we unbind the battle shutdown() { + if (this.log_subscription) { + this.battle.log.unsubscribe(this.log_subscription); + this.log_subscription = null; + } + + if (this.ui) { + this.ui.destroy(); + this.ui = null; + } + + if (this.arena) { + this.arena.destroy(); + this.arena = null; + } + + if (this.card_playing) { + this.card_playing.destroy(); + this.card_playing = null; + } + + if (this.card_hovered) { + this.card_hovered.destroy(); + this.card_hovered = null; + } + this.battle = null; + } - this.ui.destroy(); - this.ui = null; + // Process a BaseLogEvent + processBattleEvent(event: Game.Events.BaseLogEvent) { + console.log("Battle event", event); + if (event.code == "ship_change") { + // Playing ship changed + this.card_playing.setShip(event.target.ship); + } + } - this.arena.destroy(); - this.arena = null; + // Method called when cursor starts hovering over a ship (or its icon) + cursorOnShip(ship: Game.Ship): void { + this.setShipHovered(ship); + } + + // Method called when cursor stops hovering over a ship (or its icon) + cursorOffShip(ship: Game.Ship): void { + if (this.ship_hovered === ship) { + this.setShipHovered(null); + } + } + + // Set the currently hovered ship + setShipHovered(ship: Game.Ship): void { + this.ship_hovered = ship; + this.card_hovered.setShip(ship); } // Enter targetting mode diff --git a/src/scripts/view/arena/ShipArenaSprite.ts b/src/scripts/view/arena/ShipArenaSprite.ts index adc5f27..4bb62ff 100644 --- a/src/scripts/view/arena/ShipArenaSprite.ts +++ b/src/scripts/view/arena/ShipArenaSprite.ts @@ -1,14 +1,21 @@ -module SpaceTac.Arena { +module SpaceTac.View.Arena { // Ship sprite in the arena (BattleView) - export class ShipArenaSprite extends Phaser.Sprite { - constructor(arena: Phaser.Group, ship: Game.Ship) { - super(arena.game, ship.arena_x, ship.arena_y, "arena-ship"); + export class ShipArenaSprite extends Phaser.Button { + constructor(battleview: BattleView, ship: Game.Ship) { + super(battleview.game, ship.arena_x, ship.arena_y, "arena-ship"); this.scale.set(0.1, 0.1); this.rotation = ship.arena_angle; this.anchor.set(0.5, 0.5); - arena.add(this); + battleview.arena.add(this); + + this.onInputOver.add(() => { + battleview.cursorOnShip(ship); + }); + this.onInputOut.add(() => { + battleview.cursorOffShip(ship); + }); } } } diff --git a/src/scripts/view/widgets/ShipCard.ts b/src/scripts/view/widgets/ShipCard.ts new file mode 100644 index 0000000..08f24b2 --- /dev/null +++ b/src/scripts/view/widgets/ShipCard.ts @@ -0,0 +1,23 @@ +module SpaceTac.View.Widgets { + // Card to display detailed information about a ship + export class ShipCard extends Phaser.Sprite { + // Displayed ship + private ship: Game.Ship; + + // Build an empty ship card + constructor(battleview: BattleView, x: number, y: number) { + super(battleview.game, x, y, "ui-ship-card"); + + this.ship = null; + this.visible = false; + + battleview.ui.add(this); + } + + // Set the currently displayed ship (null to hide) + setShip(ship: Game.Ship) { + this.ship = ship; + this.visible = (ship !== null); + } + } +} \ No newline at end of file diff --git a/src/scripts/view/widgets/ShipListItem.ts b/src/scripts/view/widgets/ShipListItem.ts index 5d3f872..9ac3368 100644 --- a/src/scripts/view/widgets/ShipListItem.ts +++ b/src/scripts/view/widgets/ShipListItem.ts @@ -1,10 +1,22 @@ module SpaceTac.View.Widgets { // One item in a ship list (used in BattleView) export class ShipListItem extends Phaser.Button { + // Reference to the ship game object + private ship: Game.Ship; + // Create a ship button for the battle ship list - constructor(ui: UIGroup, x: number, y: number, owned: boolean) { - super(ui.game, x, y, owned ? 'ui-shiplist-own' : 'ui-shiplist-enemy'); - ui.add(this); + constructor(battleview: BattleView, x: number, y: number, ship:Game.Ship, owned: boolean) { + this.ship = ship; + + super(battleview.game, x, y, owned ? 'ui-shiplist-own' : 'ui-shiplist-enemy'); + battleview.ui.add(this); + + this.onInputOver.add(() => { + battleview.cursorOnShip(ship); + }); + this.onInputOut.add(() => { + battleview.cursorOffShip(ship); + }); } } } \ No newline at end of file