Optimize initial loading performance

This commit is contained in:
2026-06-27 07:49:38 +09:00
parent 88207c3b6f
commit b74332973b
14 changed files with 552 additions and 533 deletions

View File

@@ -1,9 +1,11 @@
import Phaser from 'phaser';
import storyMilitiaUrl from '../../assets/images/story/04-volunteer-militia.png';
import { soundDirector } from '../audio/SoundDirector';
import { equipmentExpToNext, equipmentSlotLabels, equipmentSlots, getItem, type EquipmentSlot } from '../data/battleItems';
import { getUnitClass, terrainRules, type TerrainType } from '../data/battleRules';
import { defaultBattleScenario, getBattleScenario, type BattleScenarioDefinition, type BattleScenarioId } from '../data/battles';
import { getSortieFlow } from '../data/campaignFlow';
import { portraitAssetEntriesForKey, portraitTextureKey, type PortraitAssetEntry } from '../data/portraitAssets';
import {
caoBreakRecruitBonds,
caoBreakRecruitUnits,
@@ -94,6 +96,7 @@ import {
type FirstBattleReport
} from '../state/campaignState';
import { palette } from '../ui/palette';
import { startLazyScene } from './lazyScenes';
type CampTab = 'status' | 'dialogue' | 'visit' | 'supplies' | 'progress';
@@ -254,16 +257,6 @@ type CampaignTimelineChapter = {
nextHints: string[];
};
const portraitKeys: Record<PortraitKey, string> = {
liuBei: 'portrait-liu-bei',
guanYu: 'portrait-guan-yu',
zhangFei: 'portrait-zhang-fei',
zhugeLiang: 'portrait-zhuge-liang',
zhaoYun: 'portrait-zhao-yun',
jiangWei: 'portrait-jiang-wei',
simaYi: 'portrait-sima-yi'
};
const portraitByUnitId: Record<string, PortraitKey> = {
'liu-bei': 'liuBei',
'guan-yu': 'guanYu',
@@ -10028,7 +10021,10 @@ export class CampScene extends Phaser.Scene {
this.selectedDialogueId = this.availableCampDialogues()[0]?.id ?? campDialogues[0].id;
this.selectedVisitId = this.availableCampVisits()[0]?.id ?? campVisits[0].id;
soundDirector.playMusic('militia-theme');
this.ensureCampAssets(() => this.drawCampScene());
}
private drawCampScene() {
const { width, height } = this.scale;
this.add.image(width / 2, height / 2, 'story-militia').setDisplaySize(width * 1.08, height * 1.08).setAlpha(0.62);
this.add.rectangle(0, 0, width, height, 0x06090d, 0.54).setOrigin(0);
@@ -10052,6 +10048,46 @@ export class CampScene extends Phaser.Scene {
this.render();
}
private ensureCampAssets(onReady: () => void) {
const missingPortraits = this.currentUnits()
.map((unit) => {
const portraitKey = portraitByUnitId[unit.id];
return portraitKey ? portraitAssetEntriesForKey(portraitKey)[0] : undefined;
})
.filter((entry): entry is PortraitAssetEntry => entry !== undefined)
.filter((entry, index, entries) => entries.findIndex((candidate) => candidate.textureKey === entry.textureKey) === index)
.filter((entry) => !this.textures.exists(entry.textureKey));
const missingMilitia = !this.textures.exists('story-militia');
if (!missingMilitia && missingPortraits.length === 0) {
onReady();
return;
}
const loadingText = this.add.text(this.scale.width / 2, this.scale.height / 2, 'Camp loading...', {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '22px',
color: '#f5e6b8',
stroke: '#05070a',
strokeThickness: 4
});
loadingText.setOrigin(0.5);
this.load.once('complete', () => {
loadingText.destroy();
onReady();
});
this.load.once('loaderror', (file: Phaser.Loader.File) => {
console.warn(`Failed to load camp asset ${file.key}`);
});
if (missingMilitia) {
this.load.image('story-militia', storyMilitiaUrl);
}
missingPortraits.forEach(({ textureKey, url }) => this.load.image(textureKey, url));
this.load.start();
}
private createFallbackReport(): FirstBattleReport {
const allies = firstBattleUnits.filter((unit) => unit.faction === 'ally');
return {
@@ -12015,14 +12051,14 @@ export class CampScene extends Phaser.Scene {
}
markCampaignStep(flow.campaignStep);
this.campaign = getCampaignState();
this.scene.start('StoryScene', {
void startLazyScene(this, 'StoryScene', {
pages: flow.pages,
nextScene: flow.campaignStep === 'ending-complete' ? 'EndingScene' : 'CampScene'
});
return;
}
if (flow.pages.length > 0) {
this.scene.start('StoryScene', {
void startLazyScene(this, 'StoryScene', {
pages: flow.pages,
nextScene: 'CampScene'
});
@@ -12041,7 +12077,7 @@ export class CampScene extends Phaser.Scene {
}
markCampaignStep(flow.campaignStep);
this.scene.start('StoryScene', {
void startLazyScene(this, 'StoryScene', {
pages: flow.pages,
nextScene: 'BattleScene',
nextSceneData: { battleId: flow.nextBattleId }
@@ -12095,10 +12131,11 @@ export class CampScene extends Phaser.Scene {
});
const portraitKey = portraitByUnitId[unit.id];
const portraitTexture = portraitKey ? portraitTextureKey(portraitKey) : undefined;
const portraitCenterY = y + cardHeight / 2;
const portraitSize = compact ? 40 : 84;
if (portraitKey) {
const portrait = this.track(this.add.image(x + 58, portraitCenterY, portraitKeys[portraitKey]));
if (portraitTexture && this.textures.exists(portraitTexture)) {
const portrait = this.track(this.add.image(x + 58, portraitCenterY, portraitTexture));
portrait.setDisplaySize(portraitSize, portraitSize);
} else {
this.renderUnitFallbackPortrait(x + 58, portraitCenterY, compact ? 20 : 40, unit);
@@ -12427,10 +12464,11 @@ export class CampScene extends Phaser.Scene {
const unitClass = getUnitClass(unit.classKey);
const portraitKey = portraitByUnitId[unit.id];
const portraitTexture = portraitKey ? portraitTextureKey(portraitKey) : undefined;
const portraitFrame = this.track(this.add.rectangle(x + 84, y + 90, 122, 122, 0x0b1219, 0.92));
portraitFrame.setStrokeStyle(2, palette.gold, 0.62);
if (portraitKey) {
const portrait = this.track(this.add.image(x + 84, y + 90, portraitKeys[portraitKey]));
if (portraitTexture && this.textures.exists(portraitTexture)) {
const portrait = this.track(this.add.image(x + 84, y + 90, portraitTexture));
portrait.setDisplaySize(112, 112);
} else {
this.renderUnitFallbackPortrait(x + 84, y + 90, 48, unit);