From 54569887094c2380ebc303320da8d7aa3de1a2cf Mon Sep 17 00:00:00 2001 From: Evan Date: Wed, 13 Nov 2024 20:56:06 -0800 Subject: [PATCH] Create StructureLayer --- resources/images/AnchorIcon.png | Bin 143 -> 136 bytes src/client/graphics/GameRenderer.ts | 2 + src/client/graphics/layers/StructureLayer.ts | 143 +++++++++++++++++++ src/client/graphics/layers/UnitLayer.ts | 51 ------- 4 files changed, 145 insertions(+), 51 deletions(-) create mode 100644 src/client/graphics/layers/StructureLayer.ts diff --git a/resources/images/AnchorIcon.png b/resources/images/AnchorIcon.png index 75a031b755f362df6d790a2b95f57245f1600b5f..78bdfe73e870d468d8ff1c8d7afe63d6148b8124 100644 GIT binary patch literal 136 zcmeAS@N?(olHy`uVBq!ia0vp^oFL4>1|%O$WD@{VjKx9jP7LeL$-D$|oIG6|Lo|Yy zPB_TLV8G$@_P>4AVTTPiZ#LxJa^B>0jxivQpV8p}gZYt$^h@$v9y@f4F%}28J29*~C-V}>@$__Y4ABTq zPC3Bxf1y*)i69LbHa4~iN;4#kr1dy1`)qNVz_w`3vDE@cbuE1U@E2Klayf=6{T6HP p_Z7NnTwqpYafs#OVum$r45iK@90rFczX2M`;OXk;vd$@?2>?sCDgpoi diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index aae132898..7a248fd5c 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -16,6 +16,7 @@ import { UIState } from "./UIState"; import { BuildMenu } from "./layers/radial/BuildMenu"; import { UnitLayer } from "./layers/UnitLayer"; import { BuildValidator } from "../../core/game/BuildValidator"; +import { StructureLayer } from "./layers/StructureLayer"; export function createRenderer(canvas: HTMLCanvasElement, game: Game, eventBus: EventBus, clientID: ClientID): GameRenderer { @@ -63,6 +64,7 @@ export function createRenderer(canvas: HTMLCanvasElement, game: Game, eventBus: const layers: Layer[] = [ new TerrainLayer(game), new TerritoryLayer(game, eventBus), + new StructureLayer(game, eventBus), new UnitLayer(game, eventBus), new NameLayer(game, game.config().theme(), transformHandler, clientID), new UILayer(eventBus, game, clientID, transformHandler), diff --git a/src/client/graphics/layers/StructureLayer.ts b/src/client/graphics/layers/StructureLayer.ts new file mode 100644 index 000000000..6e98fe681 --- /dev/null +++ b/src/client/graphics/layers/StructureLayer.ts @@ -0,0 +1,143 @@ +import { Colord } from "colord"; +import { Theme } from "../../../core/configuration/Config"; +import { Unit, UnitEvent, Cell, Game, Tile, UnitType } from "../../../core/game/Game"; +import { bfs, dist, euclDist } from "../../../core/Util"; +import { Layer } from "./Layer"; +import { EventBus } from "../../../core/EventBus"; + +import anchorIcon from '../../../../resources/images/AnchorIcon.png'; + +export class StructureLayer implements Layer { + private canvas: HTMLCanvasElement; + private context: CanvasRenderingContext2D; + private imageData: ImageData; + private anchorImage: HTMLImageElement; + private anchorImageLoaded: boolean = false; + + + private theme: Theme = null; + + constructor(private game: Game, private eventBus: EventBus) { + this.theme = game.config().theme(); + this.loadAnchorImage(); + } + + private loadAnchorImage() { + this.anchorImage = new Image(); + this.anchorImage.onload = () => { + this.anchorImageLoaded = true; + }; + this.anchorImage.src = anchorIcon; + } + + shouldTransform(): boolean { + return true; + } + + tick() { + } + + init(game: Game) { + this.canvas = document.createElement('canvas'); + this.context = this.canvas.getContext("2d"); + + this.imageData = this.context.getImageData(0, 0, this.game.width(), this.game.height()); + this.canvas.width = this.game.width(); + this.canvas.height = this.game.height(); + this.context.putImageData(this.imageData, 0, 0); + this.initImageData(); + + this.eventBus.on(UnitEvent, e => this.onUnitEvent(e)); + } + + initImageData() { + this.game.forEachTile((tile) => { + const index = (tile.cell().y * this.game.width()) + tile.cell().x; + const offset = index * 4; + this.imageData.data[offset + 3] = 0; + }); + } + + renderLayer(context: CanvasRenderingContext2D) { + this.context.putImageData(this.imageData, 0, 0); + context.drawImage( + this.canvas, + -this.game.width() / 2, + -this.game.height() / 2, + this.game.width(), + this.game.height() + ); + } + + private handlePortEvent(event: UnitEvent) { + if (!this.anchorImageLoaded) return; + + // Create a temporary canvas to process the anchor icon + const tempCanvas = document.createElement('canvas'); + const tempContext = tempCanvas.getContext('2d'); + tempCanvas.width = this.anchorImage.width; + tempCanvas.height = this.anchorImage.height; + + // Draw the anchor icon to the temporary canvas + tempContext.drawImage(this.anchorImage, 0, 0); + const iconData = tempContext.getImageData(0, 0, tempCanvas.width, tempCanvas.height); + + // Calculate position to center the icon on the port + const cell = event.unit.tile().cell(); + const startX = cell.x - Math.floor(tempCanvas.width / 2); + const startY = cell.y - Math.floor(tempCanvas.height / 2); + + bfs(event.unit.tile(), euclDist(event.unit.tile(), 8)) + .forEach(t => this.paintCell(t.cell(), this.theme.borderColor(event.unit.owner().info()), 255)); + + bfs(event.unit.tile(), euclDist(event.unit.tile(), 6)) + .forEach(t => this.paintCell(t.cell(), this.theme.territoryColor(event.unit.owner().info()), 255)); + // Process each pixel of the icon + for (let y = 0; y < tempCanvas.height; y++) { + for (let x = 0; x < tempCanvas.width; x++) { + const iconIndex = (y * tempCanvas.width + x) * 4; + const alpha = iconData.data[iconIndex + 3]; + + if (alpha > 0) { // Only process non-transparent pixels + const targetX = startX + x; + const targetY = startY + y; + + // Check if the target pixel is within the game bounds + if (targetX >= 0 && targetX < this.game.width() && + targetY >= 0 && targetY < this.game.height()) { + + // Color the pixel using the unit owner's colors + this.paintCell( + new Cell(targetX, targetY), + this.theme.borderColor(event.unit.owner().info()), + alpha + ); + } + } + } + } + } + + onUnitEvent(event: UnitEvent) { + switch (event.unit.type()) { + case UnitType.Port: + this.handlePortEvent(event); + break; + } + } + + paintCell(cell: Cell, color: Colord, alpha: number) { + const index = (cell.y * this.game.width()) + cell.x; + const offset = index * 4; + this.imageData.data[offset] = color.rgba.r; + this.imageData.data[offset + 1] = color.rgba.g; + this.imageData.data[offset + 2] = color.rgba.b; + this.imageData.data[offset + 3] = alpha; + } + + clearCell(cell: Cell) { + const index = (cell.y * this.game.width()) + cell.x; + const offset = index * 4; + this.imageData.data[offset + 3] = 0; // Set alpha to 0 (fully transparent) + } +} \ No newline at end of file diff --git a/src/client/graphics/layers/UnitLayer.ts b/src/client/graphics/layers/UnitLayer.ts index 5520f4085..3f2d8ae75 100644 --- a/src/client/graphics/layers/UnitLayer.ts +++ b/src/client/graphics/layers/UnitLayer.ts @@ -70,52 +70,6 @@ export class UnitLayer implements Layer { ); } - private handlePortEvent(event: UnitEvent) { - if (!this.anchorImageLoaded) return; - - // Create a temporary canvas to process the anchor icon - const tempCanvas = document.createElement('canvas'); - const tempContext = tempCanvas.getContext('2d'); - tempCanvas.width = this.anchorImage.width; - tempCanvas.height = this.anchorImage.height; - - // Draw the anchor icon to the temporary canvas - tempContext.drawImage(this.anchorImage, 0, 0); - const iconData = tempContext.getImageData(0, 0, tempCanvas.width, tempCanvas.height); - - // Calculate position to center the icon on the port - const cell = event.unit.tile().cell(); - const startX = cell.x - Math.floor(tempCanvas.width / 2); - const startY = cell.y - Math.floor(tempCanvas.height / 2); - - bfs(event.unit.tile(), euclDist(event.unit.tile(), 8)) - .forEach(t => this.paintCell(t.cell(), this.theme.territoryColor(event.unit.owner().info()), 255)); - // Process each pixel of the icon - for (let y = 0; y < tempCanvas.height; y++) { - for (let x = 0; x < tempCanvas.width; x++) { - const iconIndex = (y * tempCanvas.width + x) * 4; - const alpha = iconData.data[iconIndex + 3]; - - if (alpha > 0) { // Only process non-transparent pixels - const targetX = startX + x; - const targetY = startY + y; - - // Check if the target pixel is within the game bounds - if (targetX >= 0 && targetX < this.game.width() && - targetY >= 0 && targetY < this.game.height()) { - - // Color the pixel using the unit owner's colors - this.paintCell( - new Cell(targetX, targetY), - this.theme.borderColor(event.unit.owner().info()), - alpha - ); - } - } - } - } - } - onUnitEvent(event: UnitEvent) { switch (event.unit.type()) { case UnitType.TransportShip: @@ -124,11 +78,6 @@ export class UnitLayer implements Layer { case UnitType.Destroyer: this.handleDestroyerEvent(event); break; - case UnitType.Port: - this.handlePortEvent(event); - break; - default: - throw Error(`event for unit ${event.unit.type()} not supported`); } }