mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 12:32:21 +00:00
Improved structure legibility (#332)
Updated structure icon border rendering to improve legibility, I often find that I miss important enemy structures like SAM launchers as they share a similar icon to many other structures. Supported shapes: - Square (SAM, Missile) - Round (City, Port) - Hexagon (Defense post) - Diamond (not used, square rotated 45deg) 
This commit is contained in:
@@ -12,15 +12,30 @@ import cityIcon from "../../../../resources/images/buildings/cityAlt1.png";
|
||||
import { GameView, UnitView } from "../../../core/game/GameView";
|
||||
import { Cell, UnitType } from "../../../core/game/Game";
|
||||
import { GameUpdateType } from "../../../core/game/GameUpdates";
|
||||
import { euclDistFN } from "../../../core/game/GameMap";
|
||||
import {
|
||||
euclDistFN,
|
||||
manhattanDistFN,
|
||||
rectDistFN,
|
||||
hexDistFN,
|
||||
} from "../../../core/game/GameMap";
|
||||
|
||||
const underConstructionColor = colord({ r: 150, g: 150, b: 150 });
|
||||
const reloadingColor = colord({ r: 255, g: 0, b: 0 });
|
||||
|
||||
type DistanceFunction = typeof euclDistFN;
|
||||
|
||||
enum UnitBorderType {
|
||||
Round,
|
||||
Diamond,
|
||||
Square,
|
||||
Hexagon,
|
||||
}
|
||||
|
||||
interface UnitRenderConfig {
|
||||
icon: string;
|
||||
borderRadius: number;
|
||||
territoryRadius: number;
|
||||
borderType: UnitBorderType;
|
||||
}
|
||||
|
||||
export class StructureLayer implements Layer {
|
||||
@@ -35,26 +50,31 @@ export class StructureLayer implements Layer {
|
||||
icon: anchorIcon,
|
||||
borderRadius: 8.525,
|
||||
territoryRadius: 6.525,
|
||||
borderType: UnitBorderType.Round,
|
||||
},
|
||||
[UnitType.City]: {
|
||||
icon: cityIcon,
|
||||
borderRadius: 8.525,
|
||||
territoryRadius: 6.525,
|
||||
borderType: UnitBorderType.Round,
|
||||
},
|
||||
[UnitType.MissileSilo]: {
|
||||
icon: missileSiloIcon,
|
||||
borderRadius: 9.5,
|
||||
territoryRadius: 6,
|
||||
borderRadius: 8.525,
|
||||
territoryRadius: 6.525,
|
||||
borderType: UnitBorderType.Square,
|
||||
},
|
||||
[UnitType.DefensePost]: {
|
||||
icon: shieldIcon,
|
||||
borderRadius: 9.5,
|
||||
territoryRadius: 6,
|
||||
borderRadius: 8.525,
|
||||
territoryRadius: 6.525,
|
||||
borderType: UnitBorderType.Hexagon,
|
||||
},
|
||||
[UnitType.SAMLauncher]: {
|
||||
icon: SAMMissileIcon,
|
||||
borderRadius: 10,
|
||||
territoryRadius: 6,
|
||||
borderRadius: 8.525,
|
||||
territoryRadius: 6.525,
|
||||
borderType: UnitBorderType.Square,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -68,6 +88,7 @@ export class StructureLayer implements Layer {
|
||||
icon: SAMMissileReloadingIcon,
|
||||
borderRadius: 8.525,
|
||||
territoryRadius: 6.525,
|
||||
borderType: UnitBorderType.Square,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -141,6 +162,51 @@ export class StructureLayer implements Layer {
|
||||
return unitType in this.unitConfigs;
|
||||
}
|
||||
|
||||
private drawBorder(
|
||||
unit: UnitView,
|
||||
borderColor: Colord,
|
||||
config: UnitRenderConfig,
|
||||
distanceFN: DistanceFunction,
|
||||
) {
|
||||
// Draw border and territory
|
||||
for (const tile of this.game.bfs(
|
||||
unit.tile(),
|
||||
distanceFN(unit.tile(), config.borderRadius, true),
|
||||
)) {
|
||||
this.paintCell(
|
||||
new Cell(this.game.x(tile), this.game.y(tile)),
|
||||
borderColor,
|
||||
255,
|
||||
);
|
||||
}
|
||||
|
||||
for (const tile of this.game.bfs(
|
||||
unit.tile(),
|
||||
distanceFN(unit.tile(), config.territoryRadius, true),
|
||||
)) {
|
||||
this.paintCell(
|
||||
new Cell(this.game.x(tile), this.game.y(tile)),
|
||||
unit.type() == UnitType.Construction
|
||||
? underConstructionColor
|
||||
: this.theme.territoryColor(unit.owner().info()),
|
||||
130,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private getDrawFN(type: UnitBorderType) {
|
||||
switch (type) {
|
||||
case UnitBorderType.Round:
|
||||
return euclDistFN;
|
||||
case UnitBorderType.Diamond:
|
||||
return manhattanDistFN;
|
||||
case UnitBorderType.Square:
|
||||
return rectDistFN;
|
||||
case UnitBorderType.Hexagon:
|
||||
return hexDistFN;
|
||||
}
|
||||
}
|
||||
|
||||
private handleUnitRendering(unit: UnitView) {
|
||||
const unitType = unit.constructionType() ?? unit.type();
|
||||
let iconType = unitType;
|
||||
@@ -157,17 +223,16 @@ export class StructureLayer implements Layer {
|
||||
|
||||
if (!config || !icon) return;
|
||||
|
||||
const drawFunction = this.getDrawFN(config.borderType);
|
||||
// Clear previous rendering
|
||||
for (const tile of this.game.bfs(
|
||||
unit.tile(),
|
||||
euclDistFN(unit.tile(), config.borderRadius, true),
|
||||
drawFunction(unit.tile(), config.borderRadius, true),
|
||||
)) {
|
||||
this.clearCell(new Cell(this.game.x(tile), this.game.y(tile)));
|
||||
}
|
||||
|
||||
if (!unit.isActive()) {
|
||||
return;
|
||||
}
|
||||
if (!unit.isActive()) return;
|
||||
|
||||
let borderColor = this.theme.borderColor(unit.owner().info());
|
||||
if (unitType == UnitType.SAMLauncher && unit.isSamCooldown()) {
|
||||
@@ -176,30 +241,7 @@ export class StructureLayer implements Layer {
|
||||
borderColor = underConstructionColor;
|
||||
}
|
||||
|
||||
// Draw border and territory
|
||||
for (const tile of this.game.bfs(
|
||||
unit.tile(),
|
||||
euclDistFN(unit.tile(), config.borderRadius, true),
|
||||
)) {
|
||||
this.paintCell(
|
||||
new Cell(this.game.x(tile), this.game.y(tile)),
|
||||
borderColor,
|
||||
255,
|
||||
);
|
||||
}
|
||||
|
||||
for (const tile of this.game.bfs(
|
||||
unit.tile(),
|
||||
euclDistFN(unit.tile(), config.territoryRadius, true),
|
||||
)) {
|
||||
this.paintCell(
|
||||
new Cell(this.game.x(tile), this.game.y(tile)),
|
||||
unit.type() == UnitType.Construction
|
||||
? underConstructionColor
|
||||
: this.theme.territoryColor(unit.owner().info()),
|
||||
130,
|
||||
);
|
||||
}
|
||||
this.drawBorder(unit, borderColor, config, drawFunction);
|
||||
|
||||
const startX = this.game.x(unit.tile()) - Math.floor(icon.width / 2);
|
||||
const startY = this.game.y(unit.tile()) - Math.floor(icon.height / 2);
|
||||
|
||||
@@ -320,7 +320,7 @@ export class GameMapImpl implements GameMap {
|
||||
export function euclDistFN(
|
||||
root: TileRef,
|
||||
dist: number,
|
||||
center: boolean,
|
||||
center: boolean = false,
|
||||
): (gm: GameMap, tile: TileRef) => boolean {
|
||||
if (!center) {
|
||||
return (gm: GameMap, n: TileRef) => gm.euclideanDist(root, n) <= dist;
|
||||
@@ -341,8 +341,63 @@ export function euclDistFN(
|
||||
export function manhattanDistFN(
|
||||
root: TileRef,
|
||||
dist: number,
|
||||
center: boolean = false,
|
||||
): (gm: GameMap, tile: TileRef) => boolean {
|
||||
return (gm: GameMap, n: TileRef) => gm.manhattanDist(root, n) <= dist;
|
||||
if (!center) {
|
||||
return (gm: GameMap, n: TileRef) => gm.manhattanDist(root, n) <= dist;
|
||||
} else {
|
||||
return (gm: GameMap, n: TileRef) => {
|
||||
const rootX = gm.x(root) - 0.5;
|
||||
const rootY = gm.y(root) - 0.5;
|
||||
const dx = Math.abs(gm.x(n) - rootX);
|
||||
const dy = Math.abs(gm.y(n) - rootY);
|
||||
return dx + dy <= dist;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function rectDistFN(
|
||||
root: TileRef,
|
||||
dist: number,
|
||||
center: boolean = false,
|
||||
): (gm: GameMap, tile: TileRef) => boolean {
|
||||
if (!center) {
|
||||
return (gm: GameMap, n: TileRef) => {
|
||||
const dx = Math.abs(gm.x(n) - gm.x(root));
|
||||
const dy = Math.abs(gm.y(n) - gm.y(root));
|
||||
return dx <= dist && dy <= dist;
|
||||
};
|
||||
} else {
|
||||
return (gm: GameMap, n: TileRef) => {
|
||||
const rootX = gm.x(root) - 0.5;
|
||||
const rootY = gm.y(root) - 0.5;
|
||||
const dx = Math.abs(gm.x(n) - rootX);
|
||||
const dy = Math.abs(gm.y(n) - rootY);
|
||||
return dx <= dist && dy <= dist;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function hexDistFN(
|
||||
root: TileRef,
|
||||
dist: number,
|
||||
center: boolean = false,
|
||||
): (gm: GameMap, tile: TileRef) => boolean {
|
||||
if (!center) {
|
||||
return (gm: GameMap, n: TileRef) => {
|
||||
const dx = Math.abs(gm.x(n) - gm.x(root));
|
||||
const dy = Math.abs(gm.y(n) - gm.y(root));
|
||||
return dx <= dist && dy <= dist && dx + dy <= dist * 1.5;
|
||||
};
|
||||
} else {
|
||||
return (gm: GameMap, n: TileRef) => {
|
||||
const rootX = gm.x(root) - 0.5;
|
||||
const rootY = gm.y(root) - 0.5;
|
||||
const dx = Math.abs(gm.x(n) - rootX);
|
||||
const dy = Math.abs(gm.y(n) - rootY);
|
||||
return dx <= dist && dy <= dist && dx + dy <= dist * 1.5;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function andFN(
|
||||
|
||||
Reference in New Issue
Block a user