mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 09:10:42 +00:00
move single-unit warship selection box to WebGL SelectionBoxPass
UnitSelectionEvent now forwards to view.setSelectedUnit(unit.id()) in mountWebGLDebugRenderer; the renderer's SelectionBoxPass draws the animated stippled outline on the GPU. UILayer still tracks selectedUnit for game-logic readers (the click handlers) but no longer paints to canvas2D for it. Drops drawSelectionBox + lastSelectionBoxCenter (~50 LOC) plus the per-tick single-unit redraw in tick(). Multi-selection stays on canvas2D — SelectionBoxPass is single-unit only. Test update: replaces the now-dead drawSelectionBox spy with a selectedUnit state assertion + a deselect case.
This commit is contained in:
@@ -43,6 +43,7 @@ import {
|
||||
MouseMoveEvent,
|
||||
MouseUpEvent,
|
||||
TickMetricsEvent,
|
||||
UnitSelectionEvent,
|
||||
} from "./InputHandler";
|
||||
import { endGame, startGame, startTime } from "./LocalPersistantStats";
|
||||
import { terrainMapFileLoader } from "./TerrainMapFileLoader";
|
||||
@@ -345,6 +346,23 @@ function mountWebGLDebugRenderer(
|
||||
view.showMoveIndicator(tx, ty, firstUnit.owner().smallID());
|
||||
});
|
||||
|
||||
// Single-unit warship selection box: forward UnitSelectionEvent to the
|
||||
// renderer's SelectionBoxPass. Multi-selection (event.units.length > 0)
|
||||
// stays canvas2D for now — SelectionBoxPass only supports one unit.
|
||||
eventBus.on(UnitSelectionEvent, (e) => {
|
||||
if (!e.isSelected) {
|
||||
view.setSelectedUnit(null);
|
||||
return;
|
||||
}
|
||||
if ((e.units ?? []).length > 0) {
|
||||
// Multi-selection: drop any prior single highlight; canvas2D draws
|
||||
// the multi outlines in UILayer.
|
||||
view.setSelectedUnit(null);
|
||||
return;
|
||||
}
|
||||
view.setSelectedUnit(e.unit?.id() ?? null);
|
||||
});
|
||||
|
||||
return { builder: new WebGLFrameBuilder(view), syncCamera };
|
||||
}
|
||||
|
||||
|
||||
@@ -44,13 +44,6 @@ export class UILayer implements Layer {
|
||||
{ x: number; y: number; size: number }
|
||||
> = new Map();
|
||||
|
||||
// Keep track of previous selection box position for cleanup
|
||||
private lastSelectionBoxCenter: {
|
||||
x: number;
|
||||
y: number;
|
||||
size: number;
|
||||
} | null = null;
|
||||
|
||||
// Visual settings for selection
|
||||
private readonly SELECTION_BOX_SIZE = 6; // Size of the selection box (should be larger than the warship)
|
||||
|
||||
@@ -71,14 +64,10 @@ export class UILayer implements Layer {
|
||||
}
|
||||
|
||||
tick() {
|
||||
// Update the selection animation time
|
||||
// Update the selection animation time (only used by the multi-selection
|
||||
// boxes — the single-unit box is now drawn by the WebGL SelectionBoxPass).
|
||||
this.selectionAnimTime = (this.selectionAnimTime + 1) % 60;
|
||||
|
||||
// If there's a selected warship, redraw to update the selection box animation
|
||||
if (this.selectedUnit && this.selectedUnit.type() === UnitType.Warship) {
|
||||
this.drawSelectionBox(this.selectedUnit);
|
||||
}
|
||||
|
||||
// Animate multi-selected warships
|
||||
for (const unit of this.multiSelectedWarships) {
|
||||
if (unit.isActive()) {
|
||||
@@ -354,50 +343,29 @@ export class UILayer implements Layer {
|
||||
* When event.isSelected is false it clears all selection state.
|
||||
*/
|
||||
private onUnitSelection(event: UnitSelectionEvent) {
|
||||
if (event.isSelected) {
|
||||
// Always clear single-selection outline first
|
||||
if (this.lastSelectionBoxCenter) {
|
||||
const { x, y, size } = this.lastSelectionBoxCenter;
|
||||
this.clearSelectionBox(x, y, size);
|
||||
this.lastSelectionBoxCenter = null;
|
||||
}
|
||||
// selectedUnit is always reset regardless of lastSelectionBoxCenter
|
||||
this.selectedUnit = null;
|
||||
// Always clear previous multi-selection boxes
|
||||
for (const [, center] of this.multiSelectionBoxCenters) {
|
||||
this.clearSelectionBox(center.x, center.y, center.size);
|
||||
}
|
||||
this.multiSelectionBoxCenters.clear();
|
||||
this.multiSelectedWarships = [];
|
||||
// Clear previous multi-selection boxes (the single-unit box is now drawn
|
||||
// by the WebGL SelectionBoxPass — see ClientGameRunner.mountWebGLDebugRenderer
|
||||
// which forwards this event to view.setSelectedUnit).
|
||||
for (const [, center] of this.multiSelectionBoxCenters) {
|
||||
this.clearSelectionBox(center.x, center.y, center.size);
|
||||
}
|
||||
this.multiSelectionBoxCenters.clear();
|
||||
this.multiSelectedWarships = [];
|
||||
this.selectedUnit = null;
|
||||
|
||||
if ((event.units ?? []).length > 0) {
|
||||
// Multi-selection
|
||||
this.multiSelectedWarships = event.units;
|
||||
for (const unit of this.multiSelectedWarships) {
|
||||
if (unit.isActive()) {
|
||||
this.drawSelectionBoxMulti(unit);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Single selection
|
||||
this.selectedUnit = event.unit;
|
||||
if (event.unit && event.unit.type() === UnitType.Warship) {
|
||||
this.drawSelectionBox(event.unit);
|
||||
if (!event.isSelected) return;
|
||||
|
||||
if ((event.units ?? []).length > 0) {
|
||||
// Multi-selection — canvas2D draws the per-unit outlines.
|
||||
this.multiSelectedWarships = event.units;
|
||||
for (const unit of this.multiSelectedWarships) {
|
||||
if (unit.isActive()) {
|
||||
this.drawSelectionBoxMulti(unit);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Deselect everything
|
||||
if (this.lastSelectionBoxCenter) {
|
||||
const { x, y, size } = this.lastSelectionBoxCenter;
|
||||
this.clearSelectionBox(x, y, size);
|
||||
this.lastSelectionBoxCenter = null;
|
||||
}
|
||||
this.selectedUnit = null;
|
||||
for (const [, center] of this.multiSelectionBoxCenters) {
|
||||
this.clearSelectionBox(center.x, center.y, center.size);
|
||||
}
|
||||
this.multiSelectionBoxCenters.clear();
|
||||
this.multiSelectedWarships = [];
|
||||
// Single selection — state only; WebGL draws the box.
|
||||
this.selectedUnit = event.unit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -471,41 +439,6 @@ export class UILayer implements Layer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a selection box around the given unit
|
||||
*/
|
||||
public drawSelectionBox(unit: UnitView) {
|
||||
if (!unit || !unit.isActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.theme === null) throw new Error("missing theme");
|
||||
const selectionColor = unit.owner().territoryColor().lighten(0.2);
|
||||
const centerX = this.game.x(unit.tile());
|
||||
const centerY = this.game.y(unit.tile());
|
||||
|
||||
// Clear previous box if unit moved
|
||||
if (
|
||||
this.lastSelectionBoxCenter &&
|
||||
(this.lastSelectionBoxCenter.x !== centerX ||
|
||||
this.lastSelectionBoxCenter.y !== centerY)
|
||||
) {
|
||||
this.clearSelectionBox(
|
||||
this.lastSelectionBoxCenter.x,
|
||||
this.lastSelectionBoxCenter.y,
|
||||
this.lastSelectionBoxCenter.size,
|
||||
);
|
||||
}
|
||||
|
||||
this.paintSelectionBoxAt(centerX, centerY, selectionColor);
|
||||
|
||||
this.lastSelectionBoxCenter = {
|
||||
x: centerX,
|
||||
y: centerY,
|
||||
size: this.SELECTION_BOX_SIZE,
|
||||
};
|
||||
}
|
||||
|
||||
paintCell(x: number, y: number, color: Colord, alpha: number) {
|
||||
if (this.context === null) throw new Error("null context");
|
||||
this.clearCell(x, y);
|
||||
|
||||
@@ -36,7 +36,7 @@ describe("UILayer", () => {
|
||||
expect(ui["context"]).not.toBeNull();
|
||||
});
|
||||
|
||||
it("should handle unit selection event", () => {
|
||||
it("tracks the selected unit on single-unit selection (rendering is WebGL)", () => {
|
||||
const ui = new UILayer(game, eventBus, transformHandler);
|
||||
ui.redraw();
|
||||
const unit = {
|
||||
@@ -46,8 +46,27 @@ describe("UILayer", () => {
|
||||
owner: () => ({}),
|
||||
};
|
||||
const event = { isSelected: true, unit };
|
||||
ui.drawSelectionBox = vi.fn();
|
||||
ui["onUnitSelection"](event as UnitSelectionEvent);
|
||||
expect(ui.drawSelectionBox).toHaveBeenCalledWith(unit);
|
||||
// selectedUnit is held for game-logic callers (the click handlers). The
|
||||
// visual selection box is now drawn by WebGL SelectionBoxPass — wired
|
||||
// from ClientGameRunner via view.setSelectedUnit(unit.id()).
|
||||
expect(ui["selectedUnit"]).toBe(unit);
|
||||
});
|
||||
|
||||
it("clears selection on deselect", () => {
|
||||
const ui = new UILayer(game, eventBus, transformHandler);
|
||||
ui.redraw();
|
||||
const unit = {
|
||||
type: () => "Warship",
|
||||
isActive: () => true,
|
||||
tile: () => ({}),
|
||||
owner: () => ({}),
|
||||
};
|
||||
ui["onUnitSelection"]({ isSelected: true, unit } as UnitSelectionEvent);
|
||||
ui["onUnitSelection"]({
|
||||
isSelected: false,
|
||||
unit: null,
|
||||
} as unknown as UnitSelectionEvent);
|
||||
expect(ui["selectedUnit"]).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user