Files
heros_web/src/game/data/unitAssets.ts

238 lines
7.2 KiB
TypeScript

import Phaser from 'phaser';
export type UnitDirection = 'south' | 'east' | 'north' | 'west';
const unitIdleFrameCount = 8;
const unitWalkFrameCount = 8;
export const unitBaseFramesPerDirection = unitIdleFrameCount + unitWalkFrameCount;
export const unitSheetFrameSize = 313;
const unitWalkFrameRate = 10;
const unitSheetRows: Record<UnitDirection, number> = {
south: 0,
east: 1,
north: 2,
west: 3
};
type UnitSheetAsset = {
key: string;
url: string;
actionKey: string;
actionUrl: string;
};
const unitSheetModules = import.meta.glob('../../assets/images/units/unit-*.png', {
eager: true,
import: 'default'
}) as Record<string, string>;
function textureKeyFromPath(path: string) {
const fileName = path.split('/').pop() ?? path;
return fileName.replace(/\.png$/i, '');
}
const unitSheetUrls = Object.fromEntries(
Object.entries(unitSheetModules).map(([path, url]) => [textureKeyFromPath(path), url])
);
const unitSheets = Object.entries(unitSheetUrls)
.filter(([key]) => !key.endsWith('-actions'))
.map(([key, url]) => ({
key,
url,
actionKey: `${key}-actions`,
actionUrl: unitSheetUrls[`${key}-actions`]
}))
.filter((sheet): sheet is UnitSheetAsset => Boolean(sheet.actionUrl))
.sort((left, right) => left.key.localeCompare(right.key));
const unitSheetAssetsByKey = new Map(unitSheets.map((sheet) => [sheet.key, sheet]));
const pendingUnitTextureKeys = new Set<string>();
export function unitTextureVariantKeys(baseKey: string) {
const prefix = `${baseKey}-`;
const keys = unitSheets
.map((sheet) => sheet.key)
.filter((key) => key === baseKey || key.startsWith(prefix))
.sort();
return keys.length > 0 ? keys : [baseKey];
}
export function hasUnitSheetAsset(key: string) {
return unitSheetAssetsByKey.has(key);
}
export function unitBaseSheetsReady(scene: Phaser.Scene, keys: Iterable<string>) {
return Array.from(keys).every((key) => scene.textures.exists(key));
}
export function waitForUnitBaseSheets(scene: Phaser.Scene, keys: Iterable<string>, onReady: () => void) {
waitForUnitBaseSheetsAttempt(scene, Array.from(new Set(keys)), onReady);
}
export function loadUnitBaseSheets(scene: Phaser.Scene, keys: Iterable<string>, onReady: () => void) {
const uniqueKeys = Array.from(new Set(keys));
if (unitBaseSheetsReady(scene, uniqueKeys)) {
onReady();
return;
}
const pendingLoads: Array<{ key: string; url: string }> = [];
uniqueKeys.forEach((key) => {
const sheet = unitSheetAssetsByKey.get(key) ?? missingUnitSheet(key);
if (!scene.textures.exists(sheet.key) && !pendingUnitTextureKeys.has(sheet.key)) {
pendingUnitTextureKeys.add(sheet.key);
pendingLoads.push({ key: sheet.key, url: sheet.url });
}
});
if (pendingLoads.length <= 0) {
waitForUnitBaseSheets(scene, uniqueKeys, onReady);
return;
}
const clearPendingLoads = () => {
pendingLoads.forEach(({ key }) => pendingUnitTextureKeys.delete(key));
};
scene.load.once('complete', () => {
clearPendingLoads();
const missing = uniqueKeys.filter((key) => !scene.textures.exists(key));
if (missing.length > 0) {
console.warn(`Failed to load unit base sheet textures: ${missing.join(', ')}`);
}
onReady();
});
scene.load.once('loaderror', clearPendingLoads);
pendingLoads.forEach(({ key, url }) => {
scene.load.spritesheet(key, url, {
frameWidth: unitSheetFrameSize,
frameHeight: unitSheetFrameSize
});
});
scene.load.start();
}
export function loadUnitSheets(scene: Phaser.Scene, keys: Iterable<string>, onReady: () => void) {
const uniqueKeys = Array.from(new Set(keys));
if (unitSheetsReady(scene, uniqueKeys)) {
onReady();
return;
}
const pendingLoads: Array<{ key: string; url: string }> = [];
uniqueKeys.forEach((key) => {
const sheet = unitSheetAssetsByKey.get(key) ?? missingUnitSheet(key);
if (!scene.textures.exists(sheet.key) && !pendingUnitTextureKeys.has(sheet.key)) {
pendingUnitTextureKeys.add(sheet.key);
pendingLoads.push({ key: sheet.key, url: sheet.url });
}
if (!scene.textures.exists(sheet.actionKey) && !pendingUnitTextureKeys.has(sheet.actionKey)) {
pendingUnitTextureKeys.add(sheet.actionKey);
pendingLoads.push({ key: sheet.actionKey, url: sheet.actionUrl });
}
});
if (pendingLoads.length <= 0) {
waitForUnitSheets(scene, uniqueKeys, onReady);
return;
}
const clearPendingLoads = () => {
pendingLoads.forEach(({ key }) => pendingUnitTextureKeys.delete(key));
};
scene.load.once('complete', () => {
clearPendingLoads();
waitForUnitSheets(scene, uniqueKeys, onReady);
});
scene.load.once('loaderror', clearPendingLoads);
pendingLoads.forEach(({ key, url }) => {
scene.load.spritesheet(key, url, {
frameWidth: unitSheetFrameSize,
frameHeight: unitSheetFrameSize
});
});
scene.load.start();
}
function unitSheetsReady(scene: Phaser.Scene, keys: Iterable<string>) {
return Array.from(keys).every((key) => scene.textures.exists(key) && scene.textures.exists(`${key}-actions`));
}
function waitForUnitBaseSheetsAttempt(scene: Phaser.Scene, keys: string[], onReady: () => void, attempts = 0) {
if (unitBaseSheetsReady(scene, keys)) {
onReady();
return;
}
if (attempts > 200) {
const missing = keys.filter((key) => !scene.textures.exists(key));
console.warn(`Timed out waiting for unit base sheet textures: ${missing.join(', ')}`);
onReady();
return;
}
scene.time.delayedCall(50, () => waitForUnitBaseSheetsAttempt(scene, keys, onReady, attempts + 1));
}
function waitForUnitSheets(scene: Phaser.Scene, keys: string[], onReady: () => void, attempts = 0) {
if (unitSheetsReady(scene, keys)) {
onReady();
return;
}
if (attempts > 200) {
const missing = keys.filter((key) => !scene.textures.exists(key) || !scene.textures.exists(`${key}-actions`));
throw new Error(`Timed out waiting for unit sheet textures: ${missing.join(', ')}`);
}
scene.time.delayedCall(50, () => waitForUnitSheets(scene, keys, onReady, attempts + 1));
}
export function ensureUnitAnimations(scene: Phaser.Scene, keys: Iterable<string>) {
Array.from(new Set(keys)).forEach((key) => {
if (!scene.textures.exists(key)) {
return;
}
(['south', 'east', 'north', 'west'] as UnitDirection[]).forEach((direction) => {
const frames = Array.from({ length: unitWalkFrameCount }, (_, frameIndex) => ({
key,
frame: unitSheetRows[direction] * unitBaseFramesPerDirection + unitIdleFrameCount + frameIndex
}));
const idleFrames = [
{
key,
frame: unitSheetRows[direction] * unitBaseFramesPerDirection
}
];
const walkAnimationKey = `${key}-walk-${direction}`;
const idleAnimationKey = `${key}-idle-${direction}`;
if (!scene.anims.exists(walkAnimationKey)) {
scene.anims.create({
key: walkAnimationKey,
frames,
frameRate: unitWalkFrameRate,
repeat: -1
});
}
if (!scene.anims.exists(idleAnimationKey)) {
scene.anims.create({
key: idleAnimationKey,
frames: idleFrames,
frameRate: 1,
repeat: -1
});
}
});
});
}
function missingUnitSheet(key: string): never {
throw new Error(`Missing unit sheet asset for ${key}`);
}