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

348 lines
11 KiB
TypeScript

import type Phaser from 'phaser';
import { releaseTextureSource } from '../render/loaderLifecycle';
export type ExplorationCharacterDirection = 'south' | 'east' | 'north' | 'west';
export type ExplorationCharacterMotion = 'idle' | 'walk';
export const explorationCharacterFrameSize = 192;
export const explorationCharacterIdleFrameCount = 8;
export const explorationCharacterWalkFrameCount = 8;
export const explorationCharacterFramesPerDirection =
explorationCharacterIdleFrameCount + explorationCharacterWalkFrameCount;
export const explorationCharacterTextureKeyByUnitTextureKey = {
'unit-liu-bei': 'exploration-liu-bei',
'unit-guan-yu': 'exploration-guan-yu',
'unit-zhang-fei': 'exploration-zhang-fei',
'unit-shu-officer': 'exploration-shu-officer',
'unit-shu-infantry': 'exploration-shu-infantry'
} as const;
export const explorationCharacterNamedTextureKeyFallbacks = {
'exploration-jian-yong': 'unit-shu-officer',
'exploration-mi-zhu': 'unit-shu-officer',
'exploration-zhuge-liang': 'unit-shu-officer',
'exploration-huang-quan': 'unit-shu-officer',
'exploration-zhuo-recruiting-clerk': 'unit-shu-officer',
'exploration-zhuo-villager': 'unit-shu-infantry',
'exploration-zhuo-quartermaster': 'unit-shu-officer',
'exploration-zou-jing': 'unit-shu-officer'
} as const;
export type ExplorationCharacterUnitTextureKey =
keyof typeof explorationCharacterTextureKeyByUnitTextureKey;
export type ExplorationCharacterBaseTextureKey =
(typeof explorationCharacterTextureKeyByUnitTextureKey)[ExplorationCharacterUnitTextureKey];
export type ExplorationCharacterNamedTextureKey =
keyof typeof explorationCharacterNamedTextureKeyFallbacks;
export type ExplorationCharacterTextureKey =
| ExplorationCharacterBaseTextureKey
| ExplorationCharacterNamedTextureKey;
export type ExplorationCharacterAssetInfo = {
unitTextureKey: ExplorationCharacterUnitTextureKey;
key: ExplorationCharacterTextureKey;
url: string;
frameSize: number;
frameWidth: number;
frameHeight: number;
format: 'webp';
};
export const explorationCharacterUnitTextureKeys = Object.freeze(
Object.keys(
explorationCharacterTextureKeyByUnitTextureKey
) as ExplorationCharacterUnitTextureKey[]
);
export const explorationCharacterTextureKeys = Object.freeze(
[
...Object.values(explorationCharacterTextureKeyByUnitTextureKey),
...Object.keys(explorationCharacterNamedTextureKeyFallbacks)
] as ExplorationCharacterTextureKey[]
);
const explorationCharacterDirections = [
'south',
'east',
'north',
'west'
] as const satisfies readonly ExplorationCharacterDirection[];
const explorationCharacterSheetRows: Record<ExplorationCharacterDirection, number> = {
south: 0,
east: 1,
north: 2,
west: 3
};
const explorationCharacterIdleFrameRate = 6;
const explorationCharacterWalkFrameRate = 10;
const explorationCharacterSheetModules = import.meta.glob(
'../../assets/images/exploration/characters/exploration-*.webp',
{
eager: true,
import: 'default'
}
) as Record<string, string>;
function textureKeyFromPath(path: string) {
const fileName = path.split('/').pop() ?? path;
return fileName.replace(/\.webp$/i, '');
}
const explorationCharacterSheetUrlsByKey = new Map(
Object.entries(explorationCharacterSheetModules).map(([path, url]) => [
textureKeyFromPath(path),
url
])
);
const explorationCharacterTextureKeySet = new Set<ExplorationCharacterTextureKey>(
explorationCharacterTextureKeys
);
const unitTextureKeyByExplorationCharacterTextureKey = new Map<
ExplorationCharacterBaseTextureKey,
ExplorationCharacterUnitTextureKey
>(
Object.entries(explorationCharacterTextureKeyByUnitTextureKey).map(
([unitTextureKey, textureKey]) => [
textureKey,
unitTextureKey as ExplorationCharacterUnitTextureKey
]
)
);
export function explorationCharacterTextureKeyForUnitTextureKey(
unitTextureKey: string
): ExplorationCharacterBaseTextureKey | undefined {
if (!(unitTextureKey in explorationCharacterTextureKeyByUnitTextureKey)) {
return undefined;
}
return explorationCharacterTextureKeyByUnitTextureKey[
unitTextureKey as ExplorationCharacterUnitTextureKey
];
}
export function explorationCharacterAssetInfo(
textureKeyOrUnitTextureKey: string
): ExplorationCharacterAssetInfo | undefined {
const key = explorationCharacterTextureKeyForSourceKey(textureKeyOrUnitTextureKey);
const url = key ? explorationCharacterSheetUrlsByKey.get(key) : undefined;
const unitTextureKey = key ? fallbackUnitTextureKeyForExplorationTextureKey(key) : undefined;
if (!key || !url || !unitTextureKey) {
return undefined;
}
return {
unitTextureKey,
key,
url,
frameSize: explorationCharacterFrameSize,
frameWidth: explorationCharacterFrameSize,
frameHeight: explorationCharacterFrameSize,
format: 'webp'
};
}
export function explorationCharacterAssetInfos(
textureKeysOrUnitTextureKeys: Iterable<string> = explorationCharacterUnitTextureKeys
) {
const assets: ExplorationCharacterAssetInfo[] = [];
const seenTextureKeys = new Set<ExplorationCharacterTextureKey>();
Array.from(new Set(textureKeysOrUnitTextureKeys)).forEach((textureKeyOrUnitTextureKey) => {
const asset = explorationCharacterAssetInfo(textureKeyOrUnitTextureKey);
if (!asset || seenTextureKeys.has(asset.key)) {
return;
}
seenTextureKeys.add(asset.key);
assets.push(asset);
});
return assets;
}
export function hasExplorationCharacterAsset(textureKeyOrUnitTextureKey: string) {
return explorationCharacterAssetInfo(textureKeyOrUnitTextureKey) !== undefined;
}
function explorationCharacterTextureKeyForSourceKey(
textureKeyOrUnitTextureKey: string
): ExplorationCharacterTextureKey | undefined {
return explorationCharacterTextureKeyForUnitTextureKey(textureKeyOrUnitTextureKey) ??
(
explorationCharacterTextureKeySet.has(
textureKeyOrUnitTextureKey as ExplorationCharacterTextureKey
)
? textureKeyOrUnitTextureKey as ExplorationCharacterTextureKey
: undefined
);
}
function fallbackUnitTextureKeyForExplorationTextureKey(
textureKey: ExplorationCharacterTextureKey
): ExplorationCharacterUnitTextureKey | undefined {
const baseUnitTextureKey = unitTextureKeyByExplorationCharacterTextureKey.get(
textureKey as ExplorationCharacterBaseTextureKey
);
if (baseUnitTextureKey) {
return baseUnitTextureKey;
}
if (textureKey in explorationCharacterNamedTextureKeyFallbacks) {
return explorationCharacterNamedTextureKeyFallbacks[
textureKey as ExplorationCharacterNamedTextureKey
];
}
return undefined;
}
export function explorationCharacterAnimationKey(
textureKey: ExplorationCharacterTextureKey,
motion: ExplorationCharacterMotion,
direction: ExplorationCharacterDirection
) {
return `${textureKey}-${motion}-${direction}`;
}
export function explorationCharacterFrameFor(
motion: ExplorationCharacterMotion,
direction: ExplorationCharacterDirection,
frameIndex = 0
) {
const rowStart =
explorationCharacterSheetRows[direction] * explorationCharacterFramesPerDirection;
const motionStart = motion === 'walk' ? explorationCharacterIdleFrameCount : 0;
const motionFrameCount = motion === 'walk'
? explorationCharacterWalkFrameCount
: explorationCharacterIdleFrameCount;
const normalizedFrameIndex = Math.max(
0,
Math.min(motionFrameCount - 1, Math.trunc(frameIndex))
);
return rowStart + motionStart + normalizedFrameIndex;
}
export function applyExplorationCharacterMotion(
sprite: Phaser.GameObjects.Sprite,
textureKey: ExplorationCharacterTextureKey,
motion: ExplorationCharacterMotion,
direction: ExplorationCharacterDirection,
reducedMotion = false
) {
if (!sprite.scene.textures.exists(textureKey)) {
return false;
}
if (reducedMotion && motion === 'idle') {
sprite.anims.stop();
sprite.setFrame(explorationCharacterFrameFor('idle', direction));
return true;
}
const animationKey = explorationCharacterAnimationKey(
textureKey,
motion,
direction
);
if (
sprite.anims.currentAnim?.key !== animationKey ||
!sprite.anims.isPlaying
) {
sprite.play(animationKey);
}
return true;
}
export function ensureExplorationCharacterAnimations(
scene: Phaser.Scene,
textureKeysOrUnitTextureKeys: Iterable<string>
) {
explorationCharacterAssetInfos(textureKeysOrUnitTextureKeys).forEach(({ key }) => {
if (!scene.textures.exists(key)) {
return;
}
explorationCharacterDirections.forEach((direction) => {
const idleFrames = Array.from(
{ length: explorationCharacterIdleFrameCount },
(_, frameIndex) => ({
key,
frame: explorationCharacterFrameFor('idle', direction, frameIndex)
})
);
const walkFrames = Array.from(
{ length: explorationCharacterWalkFrameCount },
(_, frameIndex) => ({
key,
frame: explorationCharacterFrameFor('walk', direction, frameIndex)
})
);
const idleAnimationKey = explorationCharacterAnimationKey(key, 'idle', direction);
const walkAnimationKey = explorationCharacterAnimationKey(key, 'walk', direction);
if (!scene.anims.exists(idleAnimationKey)) {
scene.anims.create({
key: idleAnimationKey,
frames: idleFrames,
frameRate: explorationCharacterIdleFrameRate,
repeat: -1
});
}
if (!scene.anims.exists(walkAnimationKey)) {
scene.anims.create({
key: walkAnimationKey,
frames: walkFrames,
frameRate: explorationCharacterWalkFrameRate,
repeat: -1
});
}
});
});
}
export function releaseExplorationCharacterTextures(
scene: Phaser.Scene,
keepTextureKeysOrUnitTextureKeys: Iterable<string> = []
) {
const keepTextureKeys = new Set(
Array.from(keepTextureKeysOrUnitTextureKeys)
.map((textureKey) => explorationCharacterTextureKeyForSourceKey(textureKey))
.filter((key): key is ExplorationCharacterTextureKey => key !== undefined)
);
return releaseExplorationCharacterTextureKeys(
scene,
explorationCharacterTextureKeys.filter((textureKey) => !keepTextureKeys.has(textureKey))
);
}
export function releaseExplorationCharacterTextureKeys(
scene: Phaser.Scene,
textureKeysOrUnitTextureKeys: Iterable<string>
) {
let releasedTextureCount = 0;
const textureKeys = new Set(
Array.from(textureKeysOrUnitTextureKeys)
.map((textureKey) => explorationCharacterTextureKeyForSourceKey(textureKey))
.filter((key): key is ExplorationCharacterTextureKey => key !== undefined)
);
textureKeys.forEach((textureKey) => {
releaseExplorationCharacterAnimations(scene, textureKey);
if (releaseTextureSource(scene, textureKey)) {
releasedTextureCount += 1;
}
});
return releasedTextureCount;
}
function releaseExplorationCharacterAnimations(
scene: Phaser.Scene,
textureKey: ExplorationCharacterTextureKey
) {
explorationCharacterDirections.forEach((direction) => {
(['idle', 'walk'] as ExplorationCharacterMotion[]).forEach((motion) => {
const animationKey = explorationCharacterAnimationKey(textureKey, motion, direction);
if (scene.anims.exists(animationKey)) {
scene.anims.remove(animationKey);
}
});
});
}