238 lines
7.2 KiB
TypeScript
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}`);
|
|
}
|