diff --git a/src/events/gameMaster/assetManager/sprite/update.ts b/src/events/gameMaster/assetManager/sprite/update.ts index 1b1f7f3..eb7d89a 100644 --- a/src/events/gameMaster/assetManager/sprite/update.ts +++ b/src/events/gameMaster/assetManager/sprite/update.ts @@ -5,12 +5,20 @@ import { SpriteAction } from '#entities/spriteAction' import sharp from 'sharp' import fs from 'fs' +interface SpriteImage { + url: string + offset: { + x: number + y: number + } +} + type Payload = { id: UUID name: string spriteActions: Array<{ action: string - sprites: string[] + sprites: SpriteImage[] originX: number originY: number frameRate: number @@ -74,12 +82,12 @@ export default class SpriteUpdateEvent extends BaseEvent { } } - private async calculateMaxWidth(sprites: string[]): Promise { + private async calculateMaxWidth(sprites: SpriteImage[]): Promise { if (!sprites.length) return 0 const widths = await Promise.all( sprites.map(async (base64) => { - const uri = base64.split(';base64,').pop() + const uri = base64.url.split(';base64,').pop() if (!uri) return 0 const imgBuffer = Buffer.from(uri, 'base64') @@ -91,12 +99,12 @@ export default class SpriteUpdateEvent extends BaseEvent { return Math.max(...widths) } - private async calculateMaxHeight(sprites: string[]): Promise { + private async calculateMaxHeight(sprites: SpriteImage[]): Promise { if (!sprites.length) return 0 const heights = await Promise.all( sprites.map(async (base64) => { - const uri = base64.split(';base64,').pop() + const uri = base64.url.split(';base64,').pop() if (!uri) return 0 const imgBuffer = Buffer.from(uri, 'base64') @@ -108,46 +116,69 @@ export default class SpriteUpdateEvent extends BaseEvent { return Math.max(...heights) } - private async generateSpriteSheet(sprites: string[], spriteId: string, action: string): Promise { + private async generateSpriteSheet(sprites: SpriteImage[], spriteId: string, action: string): Promise { try { // Skip if no sprites if (!sprites.length) return true // Process all base64 images to buffers and get their dimensions const imageData = await Promise.all( - sprites.map(async (base64) => { - const uri = base64.split(';base64,').pop() + sprites.map(async (sprite) => { + const uri = sprite.url.split(';base64,').pop() if (!uri) throw new Error('Invalid base64 image') const buffer = Buffer.from(uri, 'base64') const metadata = await sharp(buffer).metadata() - return { buffer, width: metadata.width ?? 0, height: metadata.height ?? 0 } + return { + buffer, + width: metadata.width ?? 0, + height: metadata.height ?? 0, + offsetX: sprite.offset?.x ?? 0, + offsetY: sprite.offset?.y ?? 0 + } }) ) - // Find the largest dimensions - const maxWidth = Math.max(...imageData.map((data) => data.width)) - const maxHeight = Math.max(...imageData.map((data) => data.height)) + // Find the largest dimensions including offsets + const effectiveDimensions = imageData.map(({ width, height, offsetX, offsetY }) => ({ + width: width + Math.abs(offsetX), + height: height + Math.abs(offsetY), + top: offsetY >= 0 ? offsetY : 0, + bottom: offsetY < 0 ? Math.abs(offsetY) : 0 + })) + const maxWidth = Math.max(...effectiveDimensions.map(d => d.width)) + const maxHeight = Math.max(...effectiveDimensions.map(d => d.height)) + const maxTop = Math.max(...effectiveDimensions.map(d => d.top)) + const maxBottom = Math.max(...effectiveDimensions.map(d => d.bottom)) - console.log(maxWidth, maxHeight) + // Calculate total height needed to accommodate all sprites with their offsets + const totalHeight = maxHeight + maxTop + maxBottom // Extend all images to match the largest dimensions without resizing const resizedBuffers = await Promise.all( - imageData.map(async ({ buffer, width, height }) => { - // Calculate padding to center the sprite - const topPadding = 0 // This is always 0. We don't need to calculate it - const bottomPadding = Math.ceil((maxHeight - height) / 2) - const leftPadding = Math.ceil((maxWidth - width) / 2) - const rightPadding = Math.ceil((maxWidth - width) / 2) + imageData.map(async ({ buffer, width, height, offsetX, offsetY }) => { + // Calculate the position of the image within the frame + const left = offsetX >= 0 ? offsetX : 0 + // Position from bottom edge while respecting offsetY + const verticalOffset = totalHeight - height - (offsetY >= 0 ? offsetY : 0) - return await sharp(buffer) - .extend({ - top: topPadding, - bottom: bottomPadding, - left: leftPadding, - right: rightPadding, + // Create a blank canvas of the maximum size + return await sharp({ + create: { + width: maxWidth, + height: totalHeight, + channels: 4, background: { r: 0, g: 0, b: 0, alpha: 0 } - }) - .toBuffer() + } + }) + .composite([ + { + input: buffer, + left: left, + top: verticalOffset + } + ]) + .png() + .toBuffer() }) ) @@ -155,7 +186,7 @@ export default class SpriteUpdateEvent extends BaseEvent { const spriteSheet = await sharp({ create: { width: maxWidth * sprites.length, - height: maxHeight, + height: totalHeight, channels: 4, background: { r: 0, g: 0, b: 0, alpha: 0 } }