267 lines
7.2 KiB
JavaScript
267 lines
7.2 KiB
JavaScript
import {
|
|
FIGHTER_ANIMATION_OPTIONS,
|
|
FIGHTER_FRAME_HEIGHT,
|
|
FIGHTER_FRAME_WIDTH,
|
|
KILL_HEAL_EFFECT_FRAME_RATE,
|
|
KILL_HEAL_EFFECT_FRAMES,
|
|
SELECTED_FIGHTER_OUTLINE_ALPHA,
|
|
SELECTED_FIGHTER_OUTLINE_GAP,
|
|
SELECTED_FIGHTER_OUTLINE_WIDTH,
|
|
} from "../constants.js";
|
|
|
|
const SOURCE_ALPHA_THRESHOLD = 8;
|
|
const HEAL_EFFECT_PATH = "assets/effects/heal/Heal_Effect.png";
|
|
const HEAL_EFFECT_KEY = "kill-heal-effect";
|
|
const HEAL_EFFECT_ANIMATION_KEY = `${HEAL_EFFECT_KEY}-anim`;
|
|
|
|
export function fighterSheetKey(skin, action) {
|
|
return `${skin.key}-${action}`;
|
|
}
|
|
|
|
export function fighterAnimationKey(skin, action) {
|
|
return `${fighterSheetKey(skin, action)}-anim`;
|
|
}
|
|
|
|
export function fighterOutlineSheetKey(skin, action) {
|
|
return `${fighterSheetKey(skin, action)}-outline`;
|
|
}
|
|
|
|
export function fighterOutlineSheetKeyFromSheetKey(sheetKey) {
|
|
return `${sheetKey}-outline`;
|
|
}
|
|
|
|
export function fighterAttackEffectKey(skin) {
|
|
return `${skin.key}-attack-effect`;
|
|
}
|
|
|
|
export function fighterAttackEffectAnimationKey(skin) {
|
|
return `${fighterAttackEffectKey(skin)}-anim`;
|
|
}
|
|
|
|
export function fighterProjectileKey(skin) {
|
|
return `${skin.key}-projectile`;
|
|
}
|
|
|
|
export function healEffectKey() {
|
|
return HEAL_EFFECT_KEY;
|
|
}
|
|
|
|
export function healEffectAnimationKey() {
|
|
return HEAL_EFFECT_ANIMATION_KEY;
|
|
}
|
|
|
|
export function preloadFighterSheets(scene, skins) {
|
|
scene.load.spritesheet(healEffectKey(), HEAL_EFFECT_PATH, {
|
|
frameWidth: FIGHTER_FRAME_WIDTH,
|
|
frameHeight: FIGHTER_FRAME_HEIGHT,
|
|
});
|
|
|
|
skins.forEach((skin) => {
|
|
Object.entries(skin.animations).forEach(([action, animation]) => {
|
|
scene.load.spritesheet(
|
|
fighterSheetKey(skin, action),
|
|
`${skin.assetRoot}/${animation.file}`,
|
|
{ frameWidth: FIGHTER_FRAME_WIDTH, frameHeight: FIGHTER_FRAME_HEIGHT },
|
|
);
|
|
});
|
|
|
|
preloadCombatAssets(scene, skin);
|
|
});
|
|
}
|
|
|
|
export function createFighterAnimations(scene, skins) {
|
|
skins.forEach((skin) => {
|
|
Object.entries(skin.animations).forEach(([action, animation]) => {
|
|
const key = fighterAnimationKey(skin, action);
|
|
|
|
if (!scene.anims.exists(key)) {
|
|
const { frameRate, repeat } = FIGHTER_ANIMATION_OPTIONS[action];
|
|
|
|
scene.anims.create({
|
|
key,
|
|
frames: scene.anims.generateFrameNumbers(fighterSheetKey(skin, action), {
|
|
start: 0,
|
|
end: animation.frames - 1,
|
|
}),
|
|
frameRate,
|
|
repeat,
|
|
});
|
|
}
|
|
|
|
createFighterOutlineSheet(scene, skin, action, animation.frames);
|
|
});
|
|
|
|
createAttackEffectAnimation(scene, skin);
|
|
});
|
|
|
|
createHealEffectAnimation(scene);
|
|
}
|
|
|
|
function preloadCombatAssets(scene, skin) {
|
|
const projectile = skin.combat?.projectile;
|
|
const attackEffect = skin.combat?.attackEffect;
|
|
|
|
if (projectile) {
|
|
scene.load.image(fighterProjectileKey(skin), `${skin.assetRoot}/${projectile.file}`);
|
|
}
|
|
|
|
if (attackEffect) {
|
|
scene.load.spritesheet(
|
|
fighterAttackEffectKey(skin),
|
|
`${skin.assetRoot}/${attackEffect.file}`,
|
|
{ frameWidth: FIGHTER_FRAME_WIDTH, frameHeight: FIGHTER_FRAME_HEIGHT },
|
|
);
|
|
}
|
|
}
|
|
|
|
function createAttackEffectAnimation(scene, skin) {
|
|
const attackEffect = skin.combat?.attackEffect;
|
|
|
|
if (!attackEffect) {
|
|
return;
|
|
}
|
|
|
|
const key = fighterAttackEffectAnimationKey(skin);
|
|
|
|
if (scene.anims.exists(key)) {
|
|
return;
|
|
}
|
|
|
|
scene.anims.create({
|
|
key,
|
|
frames: scene.anims.generateFrameNumbers(fighterAttackEffectKey(skin), {
|
|
start: 0,
|
|
end: attackEffect.frames - 1,
|
|
}),
|
|
frameRate: attackEffect.frameRate ?? 14,
|
|
repeat: 0,
|
|
});
|
|
}
|
|
|
|
function createHealEffectAnimation(scene) {
|
|
if (scene.anims.exists(healEffectAnimationKey())) {
|
|
return;
|
|
}
|
|
|
|
scene.anims.create({
|
|
key: healEffectAnimationKey(),
|
|
frames: scene.anims.generateFrameNumbers(healEffectKey(), {
|
|
start: 0,
|
|
end: KILL_HEAL_EFFECT_FRAMES - 1,
|
|
}),
|
|
frameRate: KILL_HEAL_EFFECT_FRAME_RATE,
|
|
repeat: 0,
|
|
});
|
|
}
|
|
|
|
function createFighterOutlineSheet(scene, skin, action, frameCount) {
|
|
const key = fighterOutlineSheetKey(skin, action);
|
|
|
|
if (scene.textures.exists(key)) {
|
|
return;
|
|
}
|
|
|
|
const sourceTexture = scene.textures.get(fighterSheetKey(skin, action));
|
|
const sourceImage = sourceTexture?.getSourceImage?.();
|
|
|
|
if (!sourceImage) {
|
|
return;
|
|
}
|
|
|
|
const sheetWidth = FIGHTER_FRAME_WIDTH * frameCount;
|
|
const sheetHeight = FIGHTER_FRAME_HEIGHT;
|
|
const sourceCanvas = document.createElement("canvas");
|
|
sourceCanvas.width = sheetWidth;
|
|
sourceCanvas.height = sheetHeight;
|
|
|
|
const sourceContext = sourceCanvas.getContext("2d", { willReadFrequently: true });
|
|
sourceContext.drawImage(sourceImage, 0, 0);
|
|
|
|
const sourceData = sourceContext.getImageData(0, 0, sheetWidth, sheetHeight).data;
|
|
const outlineCanvas = document.createElement("canvas");
|
|
outlineCanvas.width = sheetWidth;
|
|
outlineCanvas.height = sheetHeight;
|
|
|
|
const outlineContext = outlineCanvas.getContext("2d");
|
|
const outlineImage = outlineContext.createImageData(sheetWidth, sheetHeight);
|
|
const outlineData = outlineImage.data;
|
|
const gapMask = new Uint8Array(sheetWidth * sheetHeight);
|
|
const outerMask = new Uint8Array(sheetWidth * sheetHeight);
|
|
const outlineAlpha = Math.round(SELECTED_FIGHTER_OUTLINE_ALPHA * 255);
|
|
|
|
for (let frameIndex = 0; frameIndex < frameCount; frameIndex += 1) {
|
|
const frameLeft = frameIndex * FIGHTER_FRAME_WIDTH;
|
|
|
|
for (let y = 0; y < FIGHTER_FRAME_HEIGHT; y += 1) {
|
|
for (let x = 0; x < FIGHTER_FRAME_WIDTH; x += 1) {
|
|
const sourceIndex = ((y * sheetWidth) + frameLeft + x) * 4;
|
|
|
|
if (sourceData[sourceIndex + 3] <= SOURCE_ALPHA_THRESHOLD) {
|
|
continue;
|
|
}
|
|
|
|
markOutlineMasks(gapMask, outerMask, sheetWidth, frameLeft, x, y);
|
|
}
|
|
}
|
|
}
|
|
|
|
paintOutlinePixels(outlineData, gapMask, outerMask, outlineAlpha);
|
|
outlineContext.putImageData(outlineImage, 0, 0);
|
|
scene.textures.addSpriteSheet(key, outlineCanvas, {
|
|
frameWidth: FIGHTER_FRAME_WIDTH,
|
|
frameHeight: FIGHTER_FRAME_HEIGHT,
|
|
});
|
|
}
|
|
|
|
function markOutlineMasks(gapMask, outerMask, sheetWidth, frameLeft, sourceX, sourceY) {
|
|
const outerRadius = SELECTED_FIGHTER_OUTLINE_GAP + SELECTED_FIGHTER_OUTLINE_WIDTH;
|
|
|
|
for (
|
|
let offsetY = -outerRadius;
|
|
offsetY <= outerRadius;
|
|
offsetY += 1
|
|
) {
|
|
const targetY = sourceY + offsetY;
|
|
|
|
if (targetY < 0 || targetY >= FIGHTER_FRAME_HEIGHT) {
|
|
continue;
|
|
}
|
|
|
|
for (
|
|
let offsetX = -outerRadius;
|
|
offsetX <= outerRadius;
|
|
offsetX += 1
|
|
) {
|
|
const targetX = sourceX + offsetX;
|
|
|
|
if (targetX < 0 || targetX >= FIGHTER_FRAME_WIDTH) {
|
|
continue;
|
|
}
|
|
|
|
const maskIndex = (targetY * sheetWidth) + frameLeft + targetX;
|
|
const distance = Math.max(Math.abs(offsetX), Math.abs(offsetY));
|
|
|
|
outerMask[maskIndex] = 1;
|
|
|
|
if (distance <= SELECTED_FIGHTER_OUTLINE_GAP) {
|
|
gapMask[maskIndex] = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function paintOutlinePixels(outlineData, gapMask, outerMask, outlineAlpha) {
|
|
for (let maskIndex = 0; maskIndex < outerMask.length; maskIndex += 1) {
|
|
if (!outerMask[maskIndex] || gapMask[maskIndex]) {
|
|
continue;
|
|
}
|
|
|
|
const outlineIndex = maskIndex * 4;
|
|
|
|
outlineData[outlineIndex] = 255;
|
|
outlineData[outlineIndex + 1] = 255;
|
|
outlineData[outlineIndex + 2] = 255;
|
|
outlineData[outlineIndex + 3] = outlineAlpha;
|
|
}
|
|
}
|