1
0
Fork 0

New fleet creation mode for character sheet (allow to change ship model)

This commit is contained in:
Michaël Lemaire 2018-03-20 23:06:39 +01:00
parent 90d24291ce
commit 28e2f889bd
37 changed files with 1187 additions and 562 deletions

View File

@ -24,9 +24,9 @@ Map/story
Character sheet
---------------
* Replace the close icon by a validation icon in creation view
* Allow to change/buy ship model
* Add personality indicators (editable in creation view)
* Improve action and attribute tooltips
* Implement sliders for personality traits
* Center the portraits when there are less than 5
Battle
------

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 849 B

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 802 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 474 B

After

Width:  |  Height:  |  Size: 497 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 777 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -1,5 +1,14 @@
# UI style guidelines
## Shapes
Main UI shapes are:
* long hexagon
* rectangles, optionally with left and/or right side tilted (if both are tilted, it should be symmetrical)
Buttons should be lit by a pure white line in top-left corner (~75% of the border length)
## Color palette
http://paletton.com/#uid=63D0c0kcBwN43YM8AMYhnnWlyeQ

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 136 KiB

View File

@ -143,6 +143,23 @@ module TK.SpaceTac {
return this.model.getActivatedUpgrades(this.level.get(), this.level.getUpgrades());
}
/**
* Refresh the actions and attributes from the bound model
*/
refreshFromModel(): void {
this.updateAttributes();
this.actions.updateFromShip(this);
}
/**
* Change the ship model
*/
setModel(model: ShipModel): void {
this.model = model;
this.level.clearUpgrades();
this.refreshFromModel();
}
/**
* Toggle an upgrade
*/
@ -151,8 +168,7 @@ module TK.SpaceTac {
return;
}
this.level.activateUpgrade(upgrade, on);
this.updateAttributes();
this.actions.updateFromShip(this);
this.refreshFromModel();
}
/**

View File

@ -39,5 +39,32 @@ module TK.SpaceTac.Specs {
level.forceLevel(10);
check.equals(level.get(), 10);
});
test.case("manages upgrades", check => {
let up1 = { code: "test1" };
let up2 = { code: "test2" };
let level = new ShipLevel();
check.equals(level.getUpgrades(), []);
check.equals(level.hasUpgrade(up1), false);
level.activateUpgrade(up1, true);
check.equals(level.getUpgrades(), ["test1"]);
check.equals(level.hasUpgrade(up1), true);
level.activateUpgrade(up1, true);
check.equals(level.getUpgrades(), ["test1"]);
check.equals(level.hasUpgrade(up1), true);
level.activateUpgrade(up1, false);
check.equals(level.getUpgrades(), []);
check.equals(level.hasUpgrade(up1), false);
level.activateUpgrade(up1, true);
level.activateUpgrade(up2, true);
check.equals(level.getUpgrades(), ["test1", "test2"]);
level.clearUpgrades();
check.equals(level.getUpgrades(), []);
});
});
}

View File

@ -108,5 +108,12 @@ module TK.SpaceTac {
hasUpgrade(upgrade: ShipUpgrade): boolean {
return contains(this.upgrades, upgrade.code);
}
/**
* Clear all activated upgrades
*/
clearUpgrades(): void {
this.upgrades = [];
}
}
}

View File

@ -118,7 +118,7 @@ module TK.SpaceTac.UI {
this.layer_borders, this.getWidth() - 112, 0);
this.ship_list.bindToLog(this.log_processor);
this.ship_tooltip = new ShipTooltip(this);
this.character_sheet = new CharacterSheet(this);
this.character_sheet = new CharacterSheet(this, CharacterSheetMode.DISPLAY);
this.layer_sheets.add(this.character_sheet);
// Targetting info
@ -286,7 +286,7 @@ module TK.SpaceTac.UI {
if (this.targetting.active) {
this.validationPressed();
} else if (this.ship_hovered && this.player.is(this.ship_hovered.fleet.player) && this.interacting) {
this.character_sheet.show(this.ship_hovered, CharacterSheetMode.DISPLAY);
this.character_sheet.show(this.ship_hovered);
this.setShipHovered(null);
}
}

View File

@ -0,0 +1,67 @@
module TK.SpaceTac.UI {
/**
* Character personality traits editor
*/
export class CharacterPersonality {
private view: BaseView
private background: UIImage
private name: UIText
private ship?: Ship
constructor(builder: UIBuilder, x: number, y: number) {
this.view = builder.view;
this.background = builder.image("character-personality-background", x, y);
builder = builder.in(this.background);
builder.in(builder.image("character-section-title", 0, 0, false)).text("Pilot", 80, 45, { color: "#dce9f9", size: 32 });
this.name = builder.in(builder.image("character-name-display", 430, 50, true)).text("", 0, 0, { size: 28 });
builder.button("character-name-button", 664, 0, () => this.renamePersonality(), "Rename personality");
builder.text("AVAILABLE SOON !", 690, 528, { size: 20, color: "#a7b3db" });
builder.styled({ size: 24, color: "#dbeff9" }, builder => {
builder.image("character-personality-trait-base", 420, 198, true);
builder.text("Courageous", 144, 140);
builder.text("Wise", 725, 140);
builder.image("character-personality-trait-base", 420, 316, true);
builder.text("Kind", 144, 268);
builder.text("Resilient", 725, 268);
builder.image("character-personality-trait-base", 420, 444, true);
builder.text("Shrewd", 144, 388);
builder.text("Funny", 725, 388);
});
}
/**
* Change the content to display a ship's personality
*/
displayShip(ship: Ship) {
let builder = new UIBuilder(this.view);
this.ship = ship;
builder.change(this.name, ship.name || "");
}
/**
* Open a dialog to rename the ship's personality
*/
renamePersonality(): void {
if (!this.ship) {
return;
}
let ship = this.ship;
UITextDialog.ask(this.view, "Choose a name for this ship's personality", ship.name || undefined).then(name => {
if (bool(name)) {
ship.name = name;
this.displayShip(ship);
}
});
}
}
}

View File

@ -7,7 +7,7 @@ module TK.SpaceTac.UI.Specs {
test.case("displays fleet and ship information", check => {
let view = testgame.view;
check.patch(view, "getWidth", () => 1240);
let sheet = new CharacterSheet(view);
let sheet = new CharacterSheet(view, CharacterSheetMode.DISPLAY);
check.equals(sheet.x, -1240);
@ -17,40 +17,38 @@ module TK.SpaceTac.UI.Specs {
let ship2 = fleet.addShip();
ship2.name = "Ship 2";
sheet.show(ship1, undefined, false);
sheet.show(ship1, false);
check.equals(sheet.x, 0);
check.equals(sheet.group_portraits.length, 2);
check.equals(sheet.text_name.text, "Ship 1");
check.equals(sheet.text_name && sheet.text_name.text, "Ship 1");
let portrait = as(Phaser.Button, sheet.group_portraits.getChildAt(1));
portrait.onInputUp.dispatch();
check.equals(sheet.text_name.text, "Ship 2");
check.equals(sheet.text_name && sheet.text_name.text, "Ship 2");
});
test.case("controls global interactivity state", check => {
let sheet = new CharacterSheet(testgame.view);
let sheet = new CharacterSheet(testgame.view, CharacterSheetMode.EDITION);
check.equals(sheet.isInteractive(), false, "no ship");
let ship = new Ship();
ship.critical = true;
sheet.show(ship, CharacterSheetMode.EDITION);
sheet.show(ship);
check.equals(sheet.isInteractive(), false, "critical ship");
ship.critical = false;
sheet.show(ship, CharacterSheetMode.EDITION);
sheet.show(ship);
check.equals(sheet.isInteractive(), true, "normal ship");
sheet.show(ship, CharacterSheetMode.DISPLAY);
sheet = new CharacterSheet(testgame.view, CharacterSheetMode.DISPLAY);
sheet.show(ship);
check.equals(sheet.isInteractive(), false, "interactivity disabled");
sheet.show(ship, CharacterSheetMode.DISPLAY);
sheet.show(ship);
check.equals(sheet.isInteractive(), false, "interactivity stays disabled");
sheet.show(ship, CharacterSheetMode.EDITION);
check.equals(sheet.isInteractive(), true, "interactivity reenabled");
});
});
});

View File

@ -10,7 +10,7 @@ module TK.SpaceTac.UI {
*/
export class CharacterSheet extends Phaser.Image {
// Global sheet mode
mode: CharacterSheetMode = CharacterSheetMode.DISPLAY
mode: CharacterSheetMode
// Parent view
view: BaseView
@ -18,20 +18,20 @@ module TK.SpaceTac.UI {
// UI components builder
builder: UIBuilder
// Close/validate button
close_button: UIButton
// X positions
xshown = 0
xhidden = -2000
// Groups
group_level: Phaser.Group
group_portraits: Phaser.Group
group_attributes: Phaser.Image
group_actions: Phaser.Image
group_upgrades: Phaser.Group
// Buttons
close_button: UIButton
rename_button: UIButton
// Currently displayed fleet
fleet?: Fleet
@ -39,33 +39,33 @@ module TK.SpaceTac.UI {
ship?: Ship
// Variable data
personality?: CharacterPersonality
image_portrait: Phaser.Image
text_model: Phaser.Text
text_description: Phaser.Text
text_name: Phaser.Text
text_name?: Phaser.Text
text_level: Phaser.Text
text_upgrade_points: Phaser.Text
valuebar_experience: ValueBar
constructor(view: BaseView, onclose?: Function) {
constructor(view: BaseView, mode: CharacterSheetMode, onclose?: Function) {
super(view.game, 0, 0, view.getImageInfo("character-sheet").key, view.getImageInfo("character-sheet").frame);
if (!onclose) {
onclose = () => this.hide();
}
this.view = view;
this.builder = new UIBuilder(view, this).styled({ color: "#e7ebf0", size: 16, shadow: true });
this.mode = mode;
this.builder = new UIBuilder(view, this).styled({ color: "#dce9f9", size: 16, shadow: true });
this.xhidden = -this.view.getWidth();
this.x = this.xhidden;
this.inputEnabled = true;
if (!onclose) {
onclose = () => this.hide();
}
this.close_button = this.builder.button("character-close-button", 1920, 0, onclose, "Close the character sheet");
this.close_button.anchor.set(1, 0);
this.image_portrait = this.builder.image("translucent", 435, 271, true);
this.builder.image("character-entry", 28, 740);
this.builder.image("character-entry", 24, 740);
this.group_portraits = this.builder.group("portraits", 90, 755);
@ -75,27 +75,55 @@ module TK.SpaceTac.UI {
let description_bg = this.builder.image("character-ship-description", 434, 654, true);
this.text_description = this.builder.in(description_bg).text("", 0, 0, { color: "#a0afc3", width: 510 });
this.group_attributes = this.builder.image("character-ship-column", 30, 30);
this.group_actions = this.builder.image("character-ship-column", 698, 30);
this.group_attributes = this.builder.image("character-ship-column-left", 28, 28);
this.group_actions = this.builder.image("character-ship-column-right", 698, 28);
let name_bg = this.builder.image("character-name-display", 434, 940, true);
this.text_name = this.builder.in(name_bg).text("", 0, 0, { size: 28 });
this.rename_button = this.builder.button("character-name-button", 656, 890, () => this.renamePersonality(), "Rename personality");
let points_bg = this.builder.image("character-level-upgrades", 582, 986);
this.group_level = this.builder.group("level");
let points_bg = this.builder.in(this.group_level).image("character-level-upgrades", 582, 986);
this.builder.in(points_bg, builder => {
builder.text("Upgrade points", 46, 10, { center: false, vcenter: false });
builder.image("character-upgrade-point", 147, 59, true);
});
this.text_upgrade_points = this.builder.in(points_bg).text("", 106, 60, { size: 28 });
let level_bg = this.builder.image("character-level-display", 434, 1032, true);
let level_bg = this.builder.in(this.group_level).image("character-level-display", 434, 1032, true);
this.text_level = this.builder.in(level_bg).text("", 0, 4, { size: 28 });
this.valuebar_experience = this.builder.in(level_bg).valuebar("character-level-experience", -level_bg.width * 0.5, -level_bg.height * 0.5);
this.group_upgrades = this.builder.group("upgrades");
if (this.mode == CharacterSheetMode.CREATION) {
this.builder.in(this.builder.image("character-section-title", 180, 30, false)).text("Ship", 80, 45, { color: "#dce9f9", size: 32 });
this.personality = new CharacterPersonality(this.builder, 950, 30);
this.close_button = this.builder.button("character-validate-creation", 140, 930, onclose,
"Validate the team, and start the campaign", undefined, {
hover_bottom: true,
text: "Validate team",
text_x: 295,
text_y: 57,
text_style: { size: 32, color: "#fff3df" }
}
);
this.builder.in(this.builder.image("character-creation-help", 970, 680), builder => {
builder.text("Compose your initial team by choosing a model for each ship, and customize the name and personality of the Artificial Intelligence pilot",
405, 150, { color: "#a3bbd9", size: 22, width: 500 });
});
this.builder.button("character-model-prev", 216, 500, () => this.changeModel(-1), "Select previous model", undefined, { center: true });
this.builder.button("character-model-next", 654, 500, () => this.changeModel(1), "Select next model", undefined, { center: true });
this.group_level.visible = false;
this.group_upgrades.visible = false;
} else {
this.text_name = this.builder.in(this.builder.image("character-name-display", 434, 940, true)).text("", 0, 0, { size: 28 });
this.close_button = this.builder.button("character-close-button", 1920, 0, onclose, "Close the character sheet");
this.close_button.anchor.set(1, 0);
}
this.refreshUpgrades();
this.refreshAttributes();
this.refreshActions();
@ -109,20 +137,22 @@ module TK.SpaceTac.UI {
}
/**
* Open a dialog to rename the ship's personality
* Change the ship model
*/
renamePersonality(): void {
if (!this.ship) {
return;
}
let ship = this.ship;
changeModel(offset: number): void {
if (this.mode == CharacterSheetMode.CREATION && this.ship) {
let models = ShipModel.getDefaultCollection();
UITextDialog.ask(this.view, "Choose a name for this ship's personality", ship.name || undefined).then(name => {
if (bool(name)) {
ship.name = name;
this.refreshShipInfo();
let idx = models.map(model => model.code).indexOf(this.ship.model.code) + offset;
if (idx < 0) {
idx = models.length - 1;
} else if (idx >= models.length) {
idx = 0;
}
});
this.ship.setModel(models[idx]);
this.refresh();
}
}
/**
@ -132,13 +162,17 @@ module TK.SpaceTac.UI {
if (this.ship) {
let ship = this.ship;
this.builder.change(this.image_portrait, `ship-${ship.model.code}-portrait`);
this.text_name.setText(ship.name || "");
if (this.text_name) {
this.text_name.setText(ship.name || "");
}
if (this.personality) {
this.personality.displayShip(ship);
}
this.text_model.setText(ship.model.name);
this.text_level.setText(`Level ${ship.level.get()}`);
this.text_description.setText(ship.model.getDescription());
this.text_upgrade_points.setText(`${ship.getAvailableUpgradePoints()}`);
this.valuebar_experience.setValue(ship.level.getExperience(), ship.level.getNextGoal());
this.rename_button.visible = this.mode == CharacterSheetMode.CREATION;
}
}
@ -149,7 +183,7 @@ module TK.SpaceTac.UI {
let builder = this.builder.in(this.group_upgrades);
builder.clear();
if (!this.ship) {
if (!this.ship || this.mode == CharacterSheetMode.CREATION) {
return;
}
let ship = this.ship;
@ -196,7 +230,7 @@ module TK.SpaceTac.UI {
center: true,
vcenter: true,
size: 28,
color: ship.level.get() >= (i + 1) ? "#e7ebf0" : "#808285"
color: ship.level.get() >= (i + 1) ? "#dce9f9" : "#293038"
});
});
@ -225,7 +259,7 @@ module TK.SpaceTac.UI {
let builder = this.builder.in(this.group_attributes);
builder.clear();
builder.text("Attributes", 74, 20, { color: "#a0afc3" });
builder.text("Attributes", 74, 20, { color: "#a3bbd9" });
if (this.ship) {
let ship = this.ship;
@ -248,7 +282,7 @@ module TK.SpaceTac.UI {
let builder = this.builder.in(this.group_actions);
builder.clear();
builder.text("Actions", 74, 20, { color: "#a0afc3" });
builder.text("Actions", 74, 20, { color: "#a3bbd9" });
if (this.ship) {
let ship = this.ship;
@ -265,20 +299,20 @@ module TK.SpaceTac.UI {
}
/**
* Update the fleet sidebar
* Refresh the fleet display
*/
updateFleet(fleet: Fleet) {
if (fleet !== this.fleet || fleet.ships.length != this.group_portraits.length) {
destroyChildren(this.group_portraits);
this.fleet = fleet;
private refreshFleet(): void {
destroyChildren(this.group_portraits);
if (this.fleet) {
let builder = this.builder.in(this.group_portraits);
fleet.ships.forEach((ship, idx) => {
this.fleet.ships.forEach((ship, idx) => {
let button: UIButton;
button = new CharacterPortrait(ship).draw(builder, 64 + idx * 140, 64, () => {
if (button) {
builder.select(button);
this.ship = ship;
this.refreshShipInfo();
this.refreshActions();
this.refreshAttributes();
@ -303,16 +337,18 @@ module TK.SpaceTac.UI {
/**
* Show the sheet for a given ship
*/
show(ship: Ship, mode: CharacterSheetMode = CharacterSheetMode.DISPLAY, animate = true, sound = true) {
show(ship: Ship, animate = true, sound = true) {
this.ship = ship;
this.mode = mode;
this.fleet = ship.fleet;
this.refreshShipInfo();
this.refreshUpgrades();
this.refreshAttributes();
this.refreshActions();
this.updateFleet(ship.fleet);
if (ship.fleet !== this.fleet || ship.fleet.ships.length != this.group_portraits.length) {
this.refreshFleet();
}
if (sound) {
this.view.audio.playOnce("ui-dialog-open");
@ -343,7 +379,12 @@ module TK.SpaceTac.UI {
*/
refresh() {
if (this.ship) {
this.show(this.ship, this.mode, false, false);
this.refreshShipInfo();
this.refreshUpgrades();
this.refreshAttributes();
this.refreshActions();
this.refreshFleet();
}
}
}

View File

@ -19,8 +19,8 @@ module TK.SpaceTac.UI {
this.built_fleet.addShip(new Ship(null, MissionGenerator.generateCharacterName(), models[1]));
this.built_fleet.credits = this.built_fleet.ships.length * 1000;
this.character_sheet = new CharacterSheet(this, () => this.validateFleet());
this.character_sheet.show(this.built_fleet.ships[0], CharacterSheetMode.CREATION, false);
this.character_sheet = new CharacterSheet(this, CharacterSheetMode.CREATION, () => this.validateFleet());
this.character_sheet.show(this.built_fleet.ships[0], false);
this.getLayer("characters").add(this.character_sheet);
}

View File

@ -58,18 +58,35 @@ module TK.SpaceTac.UI {
* Button options
*/
export type UIButtonOptions = {
// Centering
center?: boolean
// Name of the hover picture (by default, the button name, with "-hover" appended)
hover_name?: string
// Name of the "on" picture (by default, the button name, with "-on" appended)
on_name?: string
// Whether "hover" picture should stay near the button (otherwise will be on top)
hover_bottom?: boolean
// Whether "on" picture should stay near the button (otherwise will be on top)
on_bottom?: boolean
// Text content
text?: string
text_x?: number
text_y?: number
// Text content style override
text_style?: UITextStyleI
}
/**
* Main UI builder tool
*/
export class UIBuilder {
private view: BaseView
view: BaseView
private game: MainUI
private parent: UIContainer
private text_style: UITextStyle
@ -201,6 +218,10 @@ module TK.SpaceTac.UI {
let result = new Phaser.Button(this.game, x, y, info.key, undefined, null, info.frame, info.frame);
result.name = name;
if (options.center) {
result.anchor.set(0.5);
}
let clickable = bool(onclick);
result.input.useHandCursor = clickable;
if (clickable) {
@ -216,7 +237,7 @@ module TK.SpaceTac.UI {
let on_info = this.view.getImageInfo(options.on_name || (name + "-on"));
if (on_info.exists) {
on_mask = new Phaser.Image(this.game, 0, 0, on_info.key, on_info.frame);
on_mask.name = "*on*";
on_mask.name = options.on_bottom ? "on" : "*on*";
on_mask.visible = false;
result.addChild(on_mask);
}
@ -236,7 +257,7 @@ module TK.SpaceTac.UI {
let hover_mask: Phaser.Image | null = null;
if (hover_info.exists) {
hover_mask = new Phaser.Image(this.game, 0, 0, hover_info.key, hover_info.frame);
hover_mask.name = "*hover*";
hover_mask.name = options.hover_bottom ? "hover" : "*hover*";
hover_mask.visible = false;
result.addChild(hover_mask);
}
@ -268,6 +289,10 @@ module TK.SpaceTac.UI {
}, 100);
}
if (options.text) {
this.in(result).text(options.text, options.text_x || 0, options.text_y || 0, options.text_style);
}
this.add(result);
return result;
}

View File

@ -113,7 +113,7 @@ module TK.SpaceTac.UI {
this.button_options = builder.button("map-options", 1628, 0, () => this.showOptions(), "Game options");
});
this.character_sheet = new CharacterSheet(this);
this.character_sheet = new CharacterSheet(this, CharacterSheetMode.EDITION);
this.layer_overlay.add(this.character_sheet);
this.conversation = new MissionConversationDisplay(this);
@ -165,7 +165,7 @@ module TK.SpaceTac.UI {
this.backToRouter();
} else {
this.setZoom(this.zoom);
this.character_sheet.updateFleet(this.player.fleet);
this.character_sheet.refresh();
this.player_fleet.updateShipSprites();
}
}
@ -310,7 +310,7 @@ module TK.SpaceTac.UI {
openShop(): void {
let location = this.session.getLocation();
if (this.interactive && location && location.shop) {
this.character_sheet.show(this.player.fleet.ships[0], CharacterSheetMode.EDITION);
this.character_sheet.show(this.player.fleet.ships[0]);
}
}