diff --git a/CHANGELOG.md b/CHANGELOG.md index d63ae526..39410486 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,25 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [3001.0.7] - 2025-01-15 +### Added + +- Added `kaplay({ spriteAtlasPadding })` for setting the space between the + sprites in the sprite atlas - @marianyp + + ```js + kaplay({ + spriteAtlasPadding: 10, // 10 pixels of space between each sprite + }); + ``` + ### Changed - Now you cannot pass parameters that are not a component or string to `.use()`. - Otherwise it will throw an error + Otherwise it will throw an error - @lajbel + +### Fixed + +- Fixed a bug where font atlas were working strange - @mflerackers ## [3001.0.6] "Santa Events" - 2024-12-27 diff --git a/src/assets/asset.ts b/src/assets/asset.ts index 99f24a67..a034d6ae 100644 --- a/src/assets/asset.ts +++ b/src/assets/asset.ts @@ -205,7 +205,7 @@ export function load(prom: Promise): Asset { // create assets export type AssetsCtx = ReturnType; -export const initAssets = (ggl: GfxCtx) => { +export const initAssets = (ggl: GfxCtx, spriteAtlasPadding: number) => { const assets = { urlPrefix: "", // asset holders @@ -216,7 +216,12 @@ export const initAssets = (ggl: GfxCtx) => { shaders: new AssetBucket(), custom: new AssetBucket(), music: {} as Record, - packer: new TexPacker(ggl, SPRITE_ATLAS_WIDTH, SPRITE_ATLAS_HEIGHT), + packer: new TexPacker( + ggl, + SPRITE_ATLAS_WIDTH, + SPRITE_ATLAS_HEIGHT, + spriteAtlasPadding, + ), // if we finished initially loading all assets loaded: false, }; diff --git a/src/gfx/texPacker.ts b/src/gfx/texPacker.ts index 1ac1d98a..e7e018bd 100644 --- a/src/gfx/texPacker.ts +++ b/src/gfx/texPacker.ts @@ -1,64 +1,93 @@ import type { ImageSource } from "../types"; - import { type GfxCtx, Texture } from "../gfx"; - import { Quad, Vec2 } from "../math/math"; export default class TexPacker { + private lastTextureId: number = 0; private textures: Texture[] = []; private bigTextures: Texture[] = []; + private texturesPosition: Map = new Map(); private canvas: HTMLCanvasElement; private c2d: CanvasRenderingContext2D; private x: number = 0; private y: number = 0; private curHeight: number = 0; private gfx: GfxCtx; - constructor(gfx: GfxCtx, w: number, h: number) { + private padding: number; + + constructor(gfx: GfxCtx, w: number, h: number, padding: number) { this.gfx = gfx; this.canvas = document.createElement("canvas"); this.canvas.width = w; this.canvas.height = h; this.textures = [Texture.fromImage(gfx, this.canvas)]; this.bigTextures = []; + this.padding = padding; const context2D = this.canvas.getContext("2d"); if (!context2D) throw new Error("Failed to get 2d context"); this.c2d = context2D; } - add(img: ImageSource): [Texture, Quad] { - if (img.width > this.canvas.width || img.height > this.canvas.height) { + + add(img: ImageSource): [Texture, Quad, number] { + const paddedWidth = img.width + this.padding * 2; + const paddedHeight = img.height + this.padding * 2; + + if ( + paddedWidth > this.canvas.width || paddedHeight > this.canvas.height + ) { const tex = Texture.fromImage(this.gfx, img); this.bigTextures.push(tex); - return [tex, new Quad(0, 0, 1, 1)]; + return [tex, new Quad(0, 0, 1, 1), 0]; } + // next row - if (this.x + img.width > this.canvas.width) { + if (this.x + paddedWidth > this.canvas.width) { this.x = 0; this.y += this.curHeight; this.curHeight = 0; } + // next texture - if (this.y + img.height > this.canvas.height) { + if (this.y + paddedHeight > this.canvas.height) { this.c2d.clearRect(0, 0, this.canvas.width, this.canvas.height); this.textures.push(Texture.fromImage(this.gfx, this.canvas)); this.x = 0; this.y = 0; this.curHeight = 0; } + const curTex = this.textures[this.textures.length - 1]; - const pos = new Vec2(this.x, this.y); - this.x += img.width; - if (img.height > this.curHeight) { - this.curHeight = img.height; + const pos = new Vec2(this.x + this.padding, this.y + this.padding); + + this.x += paddedWidth; + + if (paddedHeight > this.curHeight) { + this.curHeight = paddedHeight; } + if (img instanceof ImageData) { this.c2d.putImageData(img, pos.x, pos.y); } else { this.c2d.drawImage(img, pos.x, pos.y); } + curTex.update(this.canvas); + + this.texturesPosition.set(this.lastTextureId, { + position: pos, + size: new Vec2(img.width, img.height), + texture: curTex, + }); + + this.lastTextureId++; + return [ curTex, new Quad( @@ -67,6 +96,7 @@ export default class TexPacker { img.width / this.canvas.width, img.height / this.canvas.height, ), + this.lastTextureId - 1, ]; } free() { @@ -77,4 +107,4 @@ export default class TexPacker { tex.free(); } } -} +} \ No newline at end of file diff --git a/src/kaplay.ts b/src/kaplay.ts index ada17287..07ea0f76 100644 --- a/src/kaplay.ts +++ b/src/kaplay.ts @@ -350,8 +350,7 @@ const kaplay = < tagsAsComponents: true, }, ): TPlugins extends [undefined] ? KAPLAYCtx - : KAPLAYCtx & MergePlugins => -{ + : KAPLAYCtx & MergePlugins => { if (_k.k) { console.warn( "KAPLAY already initialized, you are calling kaplay() multiple times, it may lead bugs!", @@ -467,7 +466,7 @@ const kaplay = < _k.gfx = gfx; const audio = initAudio(); _k.audio = audio; - const assets = initAssets(ggl); + const assets = initAssets(ggl, gopt.spriteAtlasPadding ?? 0); _k.assets = assets; const game = initGame(); _k.game = game; @@ -901,7 +900,7 @@ const kaplay = < // TODO: this should only run once app.run( - () => {}, + () => { }, () => { frameStart(); @@ -964,7 +963,7 @@ const kaplay = < // clear canvas gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT - | gl.STENCIL_BUFFER_BIT, + | gl.STENCIL_BUFFER_BIT, ); // unbind everything @@ -1408,7 +1407,7 @@ const kaplay = < // export everything to window if global is set if (gopt.global !== false) { for (const key in ctx) { - ( window[ key]) = ctx[key as keyof KAPLAYCtx]; + (window[key]) = ctx[key as keyof KAPLAYCtx]; } } diff --git a/src/types.ts b/src/types.ts index 9ddafa2b..3fbf1876 100644 --- a/src/types.ts +++ b/src/types.ts @@ -5458,6 +5458,13 @@ export interface KAPLAYOpt< * @experimental This feature is in experimental phase, it will be fully released in v3001.1.0 */ tagsAsComponents?: boolean; + /** + * Padding used when adding sprites to texture atlas. + * + * @default 0 + * @experimental This feature is in experimental phase, it will be fully released in v3001.1.0 + */ + spriteAtlasPadding?: number; } /**