Separate sprites into their own files
@@ -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.
|
||||
```
|
||||
|
||||
|
After Width: | Height: | Size: 232 B |
|
After Width: | Height: | Size: 230 B |
|
After Width: | Height: | Size: 251 B |
|
After Width: | Height: | Size: 275 B |
|
After Width: | Height: | Size: 236 B |
|
After Width: | Height: | Size: 416 B |
|
After Width: | Height: | Size: 223 B |
|
After Width: | Height: | Size: 198 B |
|
After Width: | Height: | Size: 258 B |
|
After Width: | Height: | Size: 288 B |
|
After Width: | Height: | Size: 372 B |
|
After Width: | Height: | Size: 257 B |
|
After Width: | Height: | Size: 218 B |
|
After Width: | Height: | Size: 232 B |
|
After Width: | Height: | Size: 199 B |
|
After Width: | Height: | Size: 400 B |
|
After Width: | Height: | Size: 370 B |
|
After Width: | Height: | Size: 220 B |
|
After Width: | Height: | Size: 205 B |
|
After Width: | Height: | Size: 350 B |
|
After Width: | Height: | Size: 340 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 339 B |
|
After Width: | Height: | Size: 340 B |
|
After Width: | Height: | Size: 415 B |
|
After Width: | Height: | Size: 450 B |
|
After Width: | Height: | Size: 300 B |
|
After Width: | Height: | Size: 231 B |
|
After Width: | Height: | Size: 383 B |
|
After Width: | Height: | Size: 449 B |
|
After Width: | Height: | Size: 465 B |
|
After Width: | Height: | Size: 241 B |
|
After Width: | Height: | Size: 376 B |
|
After Width: | Height: | Size: 172 B |
|
After Width: | Height: | Size: 428 B |
|
After Width: | Height: | Size: 237 B |
|
After Width: | Height: | Size: 199 B |
|
After Width: | Height: | Size: 248 B |
|
After Width: | Height: | Size: 303 B |
|
After Width: | Height: | Size: 407 B |
|
After Width: | Height: | Size: 274 B |
|
After Width: | Height: | Size: 265 B |
|
After Width: | Height: | Size: 159 B |
|
Before Width: | Height: | Size: 4.9 KiB |
@@ -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`);
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 },
|
||||
};
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
|
||||