graphics3
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1018 B |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
@ -0,0 +1,74 @@
|
||||
import { Assets, Texture } from 'pixi.js';
|
||||
import {
|
||||
fetchGameTextureManifest,
|
||||
tryResolveGameAssetFile,
|
||||
type GameTextureManifest,
|
||||
} from './resolveGameAssetUrl';
|
||||
import { getRequiredSpriteKeys } from './spriteMapping';
|
||||
|
||||
export class GameSpriteRegistry {
|
||||
private _manifest: GameTextureManifest | null = null;
|
||||
private _textures = new Map<string, Texture>();
|
||||
private _ready = false;
|
||||
|
||||
private _buildFallbackManifest(keys: string[]): GameTextureManifest {
|
||||
const textures: Record<string, { file: string; kind: string }> = {};
|
||||
for (const key of keys) {
|
||||
let file: string | null = null;
|
||||
if (key.startsWith('terrain.')) file = `tiles/${key}.png`;
|
||||
else if (key.startsWith('prop.')) file = `prop/${key}.png`;
|
||||
else if (key.startsWith('building.')) file = `building/${key}.png`;
|
||||
else if (key.startsWith('enemy.')) file = `enemies/${key}.png`;
|
||||
else if (key.startsWith('npc.') || key.startsWith('hero.')) file = `characters/${key}.png`;
|
||||
if (!file) continue;
|
||||
textures[key] = { file, kind: 'fallback' };
|
||||
}
|
||||
return {
|
||||
version: 0,
|
||||
note: 'Fallback manifest (generated at runtime).',
|
||||
textures,
|
||||
};
|
||||
}
|
||||
|
||||
get ready(): boolean {
|
||||
return this._ready;
|
||||
}
|
||||
|
||||
get manifest(): GameTextureManifest | null {
|
||||
return this._manifest;
|
||||
}
|
||||
|
||||
async loadAll(): Promise<void> {
|
||||
const requiredKeys = getRequiredSpriteKeys();
|
||||
try {
|
||||
this._manifest = await fetchGameTextureManifest();
|
||||
} catch (error) {
|
||||
console.warn('[Assets] Manifest load failed, using fallback manifest.', error);
|
||||
this._manifest = this._buildFallbackManifest(requiredKeys);
|
||||
}
|
||||
for (const key of requiredKeys) {
|
||||
if (!this._manifest.textures[key]) {
|
||||
console.warn(`[Assets] Missing manifest entry for sprite key: ${key}`);
|
||||
}
|
||||
}
|
||||
const entries = Object.entries(this._manifest.textures);
|
||||
await Promise.all(
|
||||
entries.map(async ([key, entry]) => {
|
||||
const url = tryResolveGameAssetFile(entry.file);
|
||||
if (!url) {
|
||||
console.warn(`[Assets] Missing sprite file in bundle: ${entry.file}`);
|
||||
return;
|
||||
}
|
||||
const texture = await Assets.load<Texture>(url);
|
||||
// Pixel-art tiles/props: nearest filtering avoids subpixel seam artifacts on tile borders.
|
||||
texture.source.scaleMode = 'nearest';
|
||||
this._textures.set(key, texture);
|
||||
}),
|
||||
);
|
||||
this._ready = true;
|
||||
}
|
||||
|
||||
getTexture(key: string): Texture | null {
|
||||
return this._textures.get(key) ?? null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,116 @@
|
||||
export const ROAD_SPRITE_KEY = 'terrain.road.v1';
|
||||
const DEFAULT_TERRAIN_KEY = 'terrain.grass.v1';
|
||||
const HERO_SPRITE_KEY = 'hero.player.v1.south';
|
||||
const MEET_PARTNER_SPRITE_KEY = 'hero.meet_partner';
|
||||
|
||||
/** Wilderness rest: separate props (transparent PNG), tent ~30px art height. */
|
||||
export const CAMP_TENT_TEXTURE_KEY = 'prop.camp_tent.v0';
|
||||
export const CAMP_FIRE_TEXTURE_KEY = 'prop.camp_fire.v0';
|
||||
export const CAMP_BAG_TEXTURE_KEY = 'prop.camp_bag.v0';
|
||||
|
||||
const TERRAIN_TEXTURE_BY_KEY: Record<string, string> = {
|
||||
plaza: 'terrain.plaza.v1',
|
||||
road: ROAD_SPRITE_KEY,
|
||||
dirt: 'terrain.dirt.v1',
|
||||
stone: 'terrain.stone.v1',
|
||||
forest_floor: 'terrain.forest_floor.v1',
|
||||
ruins_floor: 'terrain.ruins_floor.v1',
|
||||
canyon_floor: 'terrain.canyon_floor.v1',
|
||||
swamp_floor: 'terrain.swamp_floor.v1',
|
||||
volcanic_floor: 'terrain.volcanic_floor.v1',
|
||||
astral_floor: 'terrain.astral_floor.v1',
|
||||
grass: DEFAULT_TERRAIN_KEY,
|
||||
};
|
||||
|
||||
const OBJECT_TEXTURE_BY_KEY: Record<string, { v0: string; v1: string }> = {
|
||||
tree: { v0: 'prop.tree.v0', v1: 'prop.tree.v1' },
|
||||
rock: { v0: 'prop.rock.v0', v1: 'prop.rock.v1' },
|
||||
cart: { v0: 'prop.cart.v0', v1: 'prop.cart.v1' },
|
||||
barrel: { v0: 'prop.barrel.v0', v1: 'prop.barrel.v1' },
|
||||
bush: { v0: 'prop.bush.v0', v1: 'prop.bush.v1' },
|
||||
mushroom: { v0: 'prop.mushroom.v0', v1: 'prop.mushroom.v1' },
|
||||
leaves: { v0: 'prop.leaves.v0', v1: 'prop.leaves.v1' },
|
||||
stump: { v0: 'prop.stump.v0', v1: 'prop.stump.v1' },
|
||||
bones: { v0: 'prop.bones.v0', v1: 'prop.bones.v1' },
|
||||
ruin: { v0: 'prop.ruin.v0', v1: 'prop.ruin.v1' },
|
||||
};
|
||||
|
||||
const NPC_TEXTURE_BY_TYPE: Record<string, string> = {
|
||||
merchant: 'npc.merchant',
|
||||
armorer: 'npc.armorer',
|
||||
weapon: 'npc.weapon',
|
||||
jeweler: 'npc.jeweler',
|
||||
healer: 'npc.healer',
|
||||
bounty_hunter: 'npc.bounty_hunter',
|
||||
elder: 'npc.elder',
|
||||
quest_giver: 'npc.quest_giver',
|
||||
};
|
||||
|
||||
const BUILDING_TEXTURE_BY_TYPE: Record<string, string> = {
|
||||
'house.quest_giver': 'building.house.v1',
|
||||
'house.merchant': 'building.house.v1',
|
||||
'house.armorer': 'building.house.v1',
|
||||
'house.weapon_smith': 'building.house.v1',
|
||||
'house.jeweler': 'building.house.v1',
|
||||
'house.bounty_hunter': 'building.house.v1',
|
||||
'house.elder': 'building.house.v1',
|
||||
'house.healer': 'building.house.v1',
|
||||
};
|
||||
|
||||
export function terrainToTextureKey(terrain: string): string {
|
||||
return TERRAIN_TEXTURE_BY_KEY[terrain] ?? DEFAULT_TERRAIN_KEY;
|
||||
}
|
||||
|
||||
export function objectToTextureKey(obj: string, variant: number): string | null {
|
||||
const entry = OBJECT_TEXTURE_BY_KEY[obj];
|
||||
if (!entry) return null;
|
||||
return variant > 0.5 ? entry.v1 : entry.v0;
|
||||
}
|
||||
|
||||
export function npcTypeToTextureKey(npcType: string): string | null {
|
||||
return NPC_TEXTURE_BY_TYPE[npcType] ?? null;
|
||||
}
|
||||
|
||||
export function buildingTypeToTextureKey(buildingType: string): string | null {
|
||||
return BUILDING_TEXTURE_BY_TYPE[buildingType] ?? null;
|
||||
}
|
||||
|
||||
export function heroTextureKey(): string {
|
||||
return HERO_SPRITE_KEY;
|
||||
}
|
||||
|
||||
export function meetPartnerTextureKey(): string {
|
||||
return MEET_PARTNER_SPRITE_KEY;
|
||||
}
|
||||
|
||||
export function restCampTextureKeys(): [string, string, string] {
|
||||
return [CAMP_TENT_TEXTURE_KEY, CAMP_FIRE_TEXTURE_KEY, CAMP_BAG_TEXTURE_KEY];
|
||||
}
|
||||
|
||||
/** South-facing sprite per DB template (`enemies.type`); optional until listed in manifest + assets. */
|
||||
export function enemySouthTextureKey(slug: string): string {
|
||||
return `enemy.${slug}.south`;
|
||||
}
|
||||
|
||||
export function getRequiredSpriteKeys(): string[] {
|
||||
const terrainKeys = Object.values(TERRAIN_TEXTURE_BY_KEY);
|
||||
const objectKeys = Object.values(OBJECT_TEXTURE_BY_KEY).flatMap((entry) => [
|
||||
entry.v0,
|
||||
entry.v1,
|
||||
]);
|
||||
const npcKeys = Object.values(NPC_TEXTURE_BY_TYPE);
|
||||
const buildingKeys = Object.values(BUILDING_TEXTURE_BY_TYPE);
|
||||
return [
|
||||
...new Set([
|
||||
...terrainKeys,
|
||||
...objectKeys,
|
||||
...npcKeys,
|
||||
...buildingKeys,
|
||||
HERO_SPRITE_KEY,
|
||||
MEET_PARTNER_SPRITE_KEY,
|
||||
CAMP_TENT_TEXTURE_KEY,
|
||||
CAMP_FIRE_TEXTURE_KEY,
|
||||
CAMP_BAG_TEXTURE_KEY,
|
||||
]),
|
||||
];
|
||||
}
|
||||