Separate sprites into their own files

This commit is contained in:
2026-02-25 15:33:00 -03:00
parent 8867e36489
commit 36fc3869a2
54 changed files with 143 additions and 16 deletions
+30 -2
View File
@@ -31,8 +31,6 @@ license: `free to use with attribution`
https://butterymilk.itch.io/tiny-wonder-farm-asset-pack
(https://web.archive.org/web/20260204234306/https://butterymilk.itch.io/tiny-wonder-farm-asset-pack)
file: assets/sprites/player.png
license:
```
@@ -50,3 +48,33 @@ the sprites and add something new!
You can't resell the sprites, even if changes were made. You cannot use it in any projects related to NFT."
```
# Farmer's delight
https://github.com/vectorwing/FarmersDelight
License:
```
MIT License
Copyright (c) 2020 vectorwing
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 232 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

+78 -3
View File
@@ -1,4 +1,5 @@
import { copy } from "@std/fs";
import { createCanvas, loadImage } from "@gfx/canvas-wasm";
const BUILD_FOLDER = "build";
@@ -7,8 +8,82 @@ function clear_folder() {
Deno.mkdir(BUILD_FOLDER);
}
function build_assets() {
copy("assets", `${BUILD_FOLDER}/assets`);
function build_fonts() {
copy(`assets/fonts`, `${BUILD_FOLDER}/assets/fonts`);
}
function next_power_of_two(value: number): number {
return Math.pow(2, Math.ceil(Math.log2(value)));
}
const SPRITE_SIZE = 16;
function calculate_atlas_size(count: number) {
const raw_sprites_per_side = Math.ceil(Math.sqrt(count));
const raw_size = raw_sprites_per_side * SPRITE_SIZE;
const size = next_power_of_two(raw_size);
return {
sprites_per_side: size / SPRITE_SIZE,
size,
};
}
async function build_atlas_from_folder(folder: string) {
let sprite_count = 0;
for (const entry of Deno.readDirSync(folder)) {
console.assert(entry.isFile && entry.name.endsWith(".png"));
sprite_count += 1;
}
const atlas = calculate_atlas_size(sprite_count);
const canvas = createCanvas(atlas.size, atlas.size);
const ctx = canvas.getContext("2d");
const atlas_info: Record<string, { x: number; y: number }> = {};
// purple and black missing texture at x:0 y:0 wow !
ctx.fillStyle = "magenta";
ctx.fillRect(0, 0, 8, 8);
ctx.fillRect(8, 8, 8, 8);
ctx.fillStyle = "black";
ctx.fillRect(8, 0, 8, 8);
ctx.fillRect(0, 8, 8, 8);
let index = 1;
for (const entry of Deno.readDirSync(folder)) {
const sprite = await loadImage(`${folder}/${entry.name}`);
const row = Math.floor(index / atlas.sprites_per_side);
const column = index % atlas.sprites_per_side;
ctx.drawImage(sprite, column * SPRITE_SIZE, row * SPRITE_SIZE);
index += 1;
const id = `bworld:${entry.name.replace(".png", "")}`;
atlas_info[id] = { x: column, y: row };
}
Deno.writeFileSync(`${BUILD_FOLDER}/${folder}.png`, canvas.toBuffer());
Deno.writeTextFileSync(`${BUILD_FOLDER}/${folder}.json`, JSON.stringify(atlas_info));
}
async function build_sprites() {
for (const entry of Deno.readDirSync("assets/sprites")) {
if (entry.name.endsWith(".png")) {
copy(`assets/sprites/${entry.name}`, `${BUILD_FOLDER}/assets/sprites/${entry.name}`);
} else if (entry.isDirectory) {
await build_atlas_from_folder(`assets/sprites/${entry.name}`);
}
}
}
async function build_assets() {
Deno.mkdir(`${BUILD_FOLDER}/assets`, { recursive: true });
copy("assets/ASSETS.md", `${BUILD_FOLDER}/assets/ASSETS.md`);
build_fonts();
Deno.mkdir(`${BUILD_FOLDER}/assets/sprites`, { recursive: true });
await build_sprites();
}
async function build_client() {
@@ -23,7 +98,7 @@ async function build_client() {
async function build() {
const now = performance.now();
clear_folder();
build_assets();
await build_assets();
await build_client();
console.log(`Built in ${(performance.now() - now).toFixed(2)}ms`);
}
+7 -1
View File
@@ -1,4 +1,4 @@
type Asset = HTMLImageElement | HTMLAudioElement | string;
type Asset = HTMLImageElement | HTMLAudioElement | string | Record<string, unknown>;
export class AssetManager {
static instance = new AssetManager();
@@ -47,6 +47,12 @@ export class AssetManager {
this.assets[key] = await resp.text();
};
this.loading_promises.push(promise());
} else if (src.endsWith(".json")) {
const promise = async () => {
const resp = await fetch(src);
this.assets[key] = await resp.json();
};
this.loading_promises.push(promise());
}
}
+4 -1
View File
@@ -62,9 +62,12 @@ if (!canvas) {
InputManager.initialize(canvas);
AssetManager.instance.load("bworld:assets_text", "/assets/ASSETS.md");
AssetManager.instance.load("bworld:textures", "/assets/sprites/textures.png");
AssetManager.instance.load("bworld:textures_info", "/assets/sprites/textures.json");
AssetManager.instance.load("bworld:player", "/assets/sprites/player.png");
AssetManager.instance.load("bworld:roguelike", "/assets/sprites/roguelike.png");
AssetManager.instance.load("bworld:tiny_town", "/assets/sprites/tiny_town.png");
AssetManager.instance.load("bworld:ui", "/assets/sprites/ui.png");
await AssetManager.instance.load_all();
+4 -1
View File
@@ -32,7 +32,10 @@ export function create_player() {
// TODO: actual player ids
player.add(new PlayerInventory(player.id));
player.get(PlayerInventory)!.container.add_item(new ItemStack("bworld:pickaxe", 1, 1));
player.get(PlayerInventory)!.container.add_item(new ItemStack("bworld:bomb", 5));
player.get(PlayerInventory)!.container.add_item(new ItemStack("bworld:sword", 1, 1));
player.get(PlayerInventory)!.container.add_item(new ItemStack("bworld:axe", 1, 1));
player.get(PlayerInventory)!.container.add_item(new ItemStack("bworld:hoe", 1, 1));
player.get(PlayerInventory)!.container.add_item(new ItemStack("bworld:watering_can", 1, 1));
player.get(PlayerInventory)!.container.add_item(new ItemStack("bworld:bomb", 64));
player.add(new Camera(-100, -100));
return player;
+1 -1
View File
@@ -107,7 +107,7 @@ export function draw_nine_slice(
export function draw_item(ctx: CanvasRenderingContext2D, item: ItemStack, x: number, y: number) {
const sprite_region = get_sprite_region(item.type_id);
ctx.drawImage(
AssetManager.instance.get("bworld:tiny_town"),
AssetManager.instance.get("bworld:textures"),
sprite_region.x * 16,
sprite_region.y * 16,
16,
-5
View File
@@ -4,8 +4,3 @@ export interface SpriteRegion {
x: number;
y: number;
}
export const ITEM_SPRITES: Record<string, SpriteRegion> = {
"bworld:pickaxe": { x: 7, y: 9 },
"bworld:bomb": { x: 9, y: 8 },
};
+6 -2
View File
@@ -1,4 +1,5 @@
import { ITEM_SPRITES, SpriteRegion } from "./constants.ts";
import { AssetManager } from "../client/assets.ts";
import { SpriteRegion } from "./constants.ts";
export function point_inside_rec(
point_x: number,
@@ -14,6 +15,9 @@ export function point_inside_rec(
point_y < rec_y + rec_h;
}
type TexturesInfo = Record<string, { x: number; y: number }>;
export function get_sprite_region(id: string): SpriteRegion {
return ITEM_SPRITES[id] ?? { x: 0, y: 0 };
const textures_info = AssetManager.instance.get<TexturesInfo>("bworld:textures_info");
return textures_info?.[id] ?? { x: 0, y: 0 };
}
+1
View File
@@ -15,6 +15,7 @@
},
"imports": {
"$/": "./",
"@gfx/canvas-wasm": "jsr:@gfx/canvas-wasm@^0.4.2",
"@std/fs": "jsr:@std/fs@^1.0.23",
"marked": "npm:marked@^17.0.3"
}
Generated
+12
View File
@@ -1,12 +1,23 @@
{
"version": "5",
"specifiers": {
"jsr:@gfx/canvas-wasm@~0.4.2": "0.4.2",
"jsr:@std/encoding@1.0.5": "1.0.5",
"jsr:@std/fs@^1.0.23": "1.0.23",
"jsr:@std/internal@^1.0.12": "1.0.12",
"jsr:@std/path@^1.1.4": "1.1.4",
"npm:marked@^17.0.3": "17.0.3"
},
"jsr": {
"@gfx/canvas-wasm@0.4.2": {
"integrity": "d653be3bd12cb2fa9bbe5d1b1f041a81b91d80b68502761204aaf60e4592532a",
"dependencies": [
"jsr:@std/encoding"
]
},
"@std/encoding@1.0.5": {
"integrity": "ecf363d4fc25bd85bd915ff6733a7e79b67e0e7806334af15f4645c569fefc04"
},
"@std/fs@1.0.23": {
"integrity": "3ecbae4ce4fee03b180fa710caff36bb5adb66631c46a6460aaad49515565a37",
"dependencies": [
@@ -32,6 +43,7 @@
},
"workspace": {
"dependencies": [
"jsr:@gfx/canvas-wasm@~0.4.2",
"jsr:@std/fs@^1.0.23",
"npm:marked@^17.0.3"
]