Add northern story art lazy loading
This commit is contained in:
@@ -2,23 +2,6 @@ import Phaser from 'phaser';
|
||||
import guanYuPortraitUrl from '../../assets/images/portraits/guan-yu.png';
|
||||
import liuBeiPortraitUrl from '../../assets/images/portraits/liu-bei.png';
|
||||
import zhangFeiPortraitUrl from '../../assets/images/portraits/zhang-fei.png';
|
||||
import { battleMapAssets } from '../data/battleMapAssets';
|
||||
import storyChaosUrl from '../../assets/images/story/01-yellow-turban-chaos.png';
|
||||
import storyLiuBeiUrl from '../../assets/images/story/02-liu-bei-resolve.png';
|
||||
import storyThreeHeroesUrl from '../../assets/images/story/03-three-heroes-meet.png';
|
||||
import storyMilitiaUrl from '../../assets/images/story/04-volunteer-militia.png';
|
||||
import storySortieUrl from '../../assets/images/story/05-first-sortie.png';
|
||||
import storyYellowPursuitUrl from '../../assets/images/story/06-yellow-turban-pursuit.png';
|
||||
import storyAntiDongPassUrl from '../../assets/images/story/07-anti-dong-pass.png';
|
||||
import storyXuzhouHandoverUrl from '../../assets/images/story/08-xuzhou-handover.png';
|
||||
import storyWanderingRoadUrl from '../../assets/images/story/09-wandering-road.png';
|
||||
import storyWolongCottageUrl from '../../assets/images/story/10-wolong-cottage.png';
|
||||
import storyRedCliffsFireUrl from '../../assets/images/story/11-red-cliffs-fire.png';
|
||||
import storyYizhouMountainPassUrl from '../../assets/images/story/12-yizhou-mountain-pass.png';
|
||||
import storyNorthernExpeditionUrl from '../../assets/images/story/13-northern-expedition.png';
|
||||
import storyNanzhongPacificationUrl from '../../assets/images/story/14-nanzhong-pacification.png';
|
||||
import storyYilingBaidiAftermathUrl from '../../assets/images/story/15-yiling-baidi-aftermath.png';
|
||||
import storyQishanRenewedOffensiveUrl from '../../assets/images/story/16-qishan-renewed-offensive.png';
|
||||
import titleBackgroundUrl from '../../assets/images/taoyuan-oath-title.png';
|
||||
import guanYuActionSheetUrl from '../../assets/images/units/unit-guan-yu-actions.png';
|
||||
import guanYuUnitSheetUrl from '../../assets/images/units/unit-guan-yu.png';
|
||||
@@ -34,6 +17,7 @@ import rebelActionSheetUrl from '../../assets/images/units/unit-rebel-actions.pn
|
||||
import rebelUnitSheetUrl from '../../assets/images/units/unit-rebel.png';
|
||||
import zhangFeiActionSheetUrl from '../../assets/images/units/unit-zhang-fei-actions.png';
|
||||
import zhangFeiUnitSheetUrl from '../../assets/images/units/unit-zhang-fei.png';
|
||||
import { storyBackgroundAssets } from '../data/storyAssets';
|
||||
|
||||
type UnitDirection = 'south' | 'east' | 'north' | 'west';
|
||||
|
||||
@@ -89,31 +73,11 @@ export class BootScene extends Phaser.Scene {
|
||||
|
||||
preload() {
|
||||
this.load.image('title-taoyuan', titleBackgroundUrl);
|
||||
this.load.image('story-chaos', storyChaosUrl);
|
||||
this.load.image('story-liu-bei', storyLiuBeiUrl);
|
||||
this.load.image('story-three-heroes', storyThreeHeroesUrl);
|
||||
this.load.image('story-militia', storyMilitiaUrl);
|
||||
this.load.image('story-sortie', storySortieUrl);
|
||||
this.load.image('story-yellow-pursuit', storyYellowPursuitUrl);
|
||||
this.load.image('story-anti-dong', storyAntiDongPassUrl);
|
||||
this.load.image('story-xuzhou', storyXuzhouHandoverUrl);
|
||||
this.load.image('story-wandering', storyWanderingRoadUrl);
|
||||
this.load.image('story-wolong', storyWolongCottageUrl);
|
||||
this.load.image('story-red-cliffs', storyRedCliffsFireUrl);
|
||||
this.load.image('story-yizhou', storyYizhouMountainPassUrl);
|
||||
this.load.image('story-northern', storyNorthernExpeditionUrl);
|
||||
this.load.image('story-nanzhong', storyNanzhongPacificationUrl);
|
||||
this.load.image('story-yiling-baidi', storyYilingBaidiAftermathUrl);
|
||||
this.load.image('story-qishan-renewed', storyQishanRenewedOffensiveUrl);
|
||||
this.load.image('story-resolve', storyLiuBeiUrl);
|
||||
this.load.image('story-militia', storyBackgroundAssets['story-militia']);
|
||||
this.load.image('portrait-liu-bei', liuBeiPortraitUrl);
|
||||
this.load.image('portrait-guan-yu', guanYuPortraitUrl);
|
||||
this.load.image('portrait-zhang-fei', zhangFeiPortraitUrl);
|
||||
|
||||
Object.entries(battleMapAssets).forEach(([key, url]) => {
|
||||
this.load.image(key, url);
|
||||
});
|
||||
|
||||
sourceUnitSheets.forEach(({ key, url, actionKey, actionUrl }) => {
|
||||
this.load.spritesheet(key, url, {
|
||||
frameWidth: unitSheetFrameSize,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Phaser from 'phaser';
|
||||
import { soundDirector } from '../audio/SoundDirector';
|
||||
import { storyBackgroundAssets } from '../data/storyAssets';
|
||||
import { prologuePages, type PortraitKey, type StoryPage } from '../data/scenario';
|
||||
import { palette } from '../ui/palette';
|
||||
|
||||
@@ -31,6 +32,7 @@ export class StoryScene extends Phaser.Scene {
|
||||
private nameText?: Phaser.GameObjects.Text;
|
||||
private bodyText?: Phaser.GameObjects.Text;
|
||||
private progressText?: Phaser.GameObjects.Text;
|
||||
private loadingText?: Phaser.GameObjects.Text;
|
||||
|
||||
constructor() {
|
||||
super('StoryScene');
|
||||
@@ -44,17 +46,86 @@ export class StoryScene extends Phaser.Scene {
|
||||
this.transitioning = false;
|
||||
}
|
||||
|
||||
getDebugState() {
|
||||
const page = this.pages[this.pageIndex];
|
||||
|
||||
return {
|
||||
scene: this.scene.key,
|
||||
ready: this.loadingText === undefined && this.background !== undefined,
|
||||
pageIndex: this.pageIndex,
|
||||
totalPages: this.pages.length,
|
||||
currentPageId: page?.id,
|
||||
background: page?.background,
|
||||
backgroundReady: page ? this.textures.exists(page.background) : false,
|
||||
activeTexture: this.background?.texture.key ?? null
|
||||
};
|
||||
}
|
||||
|
||||
create() {
|
||||
const { width, height } = this.scale;
|
||||
this.add.rectangle(0, 0, width, height, 0x0b0d12).setOrigin(0);
|
||||
this.background = this.add.image(width / 2, height / 2, this.pages[0].background);
|
||||
this.drawSceneShade(width, height);
|
||||
this.drawDialogPanel(width, height);
|
||||
this.showPage(0, true);
|
||||
this.createFallbackStoryTexture();
|
||||
this.loadingText = this.add.text(width / 2, height / 2, '장면을 준비하는 중...', {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '22px',
|
||||
color: '#f5e6b8',
|
||||
stroke: '#05070a',
|
||||
strokeThickness: 4
|
||||
});
|
||||
this.loadingText.setOrigin(0.5);
|
||||
|
||||
this.input.on('pointerdown', () => this.advance());
|
||||
this.input.keyboard?.on('keydown-SPACE', () => this.advance());
|
||||
this.input.keyboard?.on('keydown-ENTER', () => this.advance());
|
||||
this.ensureStoryBackgroundsLoaded(() => {
|
||||
this.loadingText?.destroy();
|
||||
this.loadingText = undefined;
|
||||
this.background = this.add.image(width / 2, height / 2, this.resolveBackgroundKey(this.pages[0].background));
|
||||
this.drawSceneShade(width, height);
|
||||
this.drawDialogPanel(width, height);
|
||||
this.showPage(0, true);
|
||||
|
||||
this.input.on('pointerdown', () => this.advance());
|
||||
this.input.keyboard?.on('keydown-SPACE', () => this.advance());
|
||||
this.input.keyboard?.on('keydown-ENTER', () => this.advance());
|
||||
});
|
||||
}
|
||||
|
||||
private ensureStoryBackgroundsLoaded(onReady: () => void) {
|
||||
const missingKeys = Array.from(new Set(this.pages.map((page) => page.background))).filter((key) => !this.textures.exists(key));
|
||||
const loadableKeys = missingKeys.filter((key) => storyBackgroundAssets[key]);
|
||||
|
||||
missingKeys
|
||||
.filter((key) => !storyBackgroundAssets[key])
|
||||
.forEach((key) => console.warn(`Missing story background asset for ${key}`));
|
||||
|
||||
if (loadableKeys.length === 0) {
|
||||
onReady();
|
||||
return;
|
||||
}
|
||||
|
||||
this.load.once('complete', onReady);
|
||||
this.load.once('loaderror', (file: Phaser.Loader.File) => {
|
||||
console.warn(`Failed to load story background ${file.key}`);
|
||||
});
|
||||
loadableKeys.forEach((key) => this.load.image(key, storyBackgroundAssets[key]));
|
||||
this.load.start();
|
||||
}
|
||||
|
||||
private createFallbackStoryTexture() {
|
||||
if (this.textures.exists('story-fallback')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const graphics = this.make.graphics({ x: 0, y: 0 });
|
||||
graphics.fillStyle(0x10131a, 1);
|
||||
graphics.fillRect(0, 0, 1280, 720);
|
||||
graphics.fillStyle(0x1d2530, 1);
|
||||
graphics.fillRect(0, 0, 1280, 180);
|
||||
graphics.lineStyle(4, palette.gold, 0.5);
|
||||
graphics.beginPath();
|
||||
graphics.moveTo(120, 600);
|
||||
graphics.lineTo(1160, 120);
|
||||
graphics.strokePath();
|
||||
graphics.generateTexture('story-fallback', 1280, 720);
|
||||
graphics.destroy();
|
||||
}
|
||||
|
||||
private drawSceneShade(width: number, height: number) {
|
||||
@@ -197,13 +268,14 @@ export class StoryScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
const { width, height } = this.scale;
|
||||
const source = this.textures.get(textureKey).getSourceImage();
|
||||
const safeTextureKey = this.resolveBackgroundKey(textureKey);
|
||||
const source = this.textures.get(safeTextureKey).getSourceImage();
|
||||
const imageWidth = source.width;
|
||||
const imageHeight = source.height;
|
||||
const coverScale = Math.max(width / imageWidth, height / imageHeight);
|
||||
|
||||
this.tweens.killTweensOf(this.background);
|
||||
this.background.setTexture(textureKey);
|
||||
this.background.setTexture(safeTextureKey);
|
||||
this.background.setAlpha(1);
|
||||
this.background.setScale(coverScale * 1.07);
|
||||
this.background.setPosition(width / 2 - 16, height / 2);
|
||||
@@ -220,6 +292,10 @@ export class StoryScene extends Phaser.Scene {
|
||||
});
|
||||
}
|
||||
|
||||
private resolveBackgroundKey(textureKey: string) {
|
||||
return this.textures.exists(textureKey) ? textureKey : 'story-fallback';
|
||||
}
|
||||
|
||||
private advance() {
|
||||
if (this.transitioning) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user