From ed9e94e3e69022bb5af704778e98fb66fadd03f0 Mon Sep 17 00:00:00 2001 From: Mahiru Date: Sat, 19 Sep 2026 00:26:46 +0800 Subject: [PATCH 1/5] =?UTF-8?q?fix:=20=E5=88=A0=E9=99=A4=E4=B8=80=E4=BA=9B?= =?UTF-8?q?=E5=9C=A8=E6=94=B9=E9=80=A0=E5=86=85=E6=A0=B8=E5=90=8E=E4=B8=8D?= =?UTF-8?q?=E9=9C=80=E8=A6=81=E7=9A=84=20setTimeout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Core/Modules/perform/performController.ts | 3 +- .../src/Core/controller/gamePlay/autoPlay.ts | 1 + .../src/Core/controller/gamePlay/fastSkip.ts | 1 + .../controller/stage/pixi/PixiController.ts | 183 ++++++++--------- .../src/Core/controller/stage/pixi/spine.ts | 193 +++++++++--------- .../stage/pixi/syncPixiStageState.ts | 2 + .../controller/storage/storageController.ts | 1 + packages/webgal/src/Core/gameScripts/end.ts | 3 +- .../webgal/src/Core/gameScripts/intro.tsx | 3 + .../webgal/src/Core/gameScripts/playVideo.tsx | 54 ++--- packages/webgal/src/Core/gameScripts/say.ts | 1 + .../src/Core/gameScripts/vocal/index.ts | 3 + .../Core/gameScripts/vocal/vocalAnimation.ts | 3 + .../Core/util/prefetcher/assetsPrefetcher.ts | 1 + .../Core/util/prefetcher/scenePrefetcher.ts | 1 + .../util/syncWithEditor/previewSyncRuntime.ts | 2 + .../runtime/embeddedPreviewBootstrap.ts | 1 + 17 files changed, 234 insertions(+), 222 deletions(-) diff --git a/packages/webgal/src/Core/Modules/perform/performController.ts b/packages/webgal/src/Core/Modules/perform/performController.ts index 6531a3397..7306d94df 100644 --- a/packages/webgal/src/Core/Modules/perform/performController.ts +++ b/packages/webgal/src/Core/Modules/perform/performController.ts @@ -169,7 +169,7 @@ export class PerformController { perform.isStarted = true; perform.startFunction?.(); - // 时间到后自动清理演出 + // 演出持续 duration 毫秒后自动回收;同步清理会使演出刚启动就结束。 const stopTimeout = setTimeout(() => { // perform.stopFunction(); // perform.isOver = true; @@ -332,6 +332,7 @@ export class PerformController { }); if (isBlockingNext) { // 有阻塞,提前结束 + // 其他演出仍阻塞推进,间隔重试以等待其结束,避免同步递归占住主线程。 setTimeout(this.goNextWhenOver, 100); } else { continueSentence(); diff --git a/packages/webgal/src/Core/controller/gamePlay/autoPlay.ts b/packages/webgal/src/Core/controller/gamePlay/autoPlay.ts index 575ae7d41..e6056a4d8 100644 --- a/packages/webgal/src/Core/controller/gamePlay/autoPlay.ts +++ b/packages/webgal/src/Core/controller/gamePlay/autoPlay.ts @@ -71,6 +71,7 @@ const autoPlay = () => { } // nextSentence(); if (WebGAL.gameplay.autoTimeout === null) { + // 保留自动阅读的停留时间,到时再检查能否推进,不能同步连续跳句。 WebGAL.gameplay.autoTimeout = setTimeout(autoNextSentence, autoPlayDelay); } }; diff --git a/packages/webgal/src/Core/controller/gamePlay/fastSkip.ts b/packages/webgal/src/Core/controller/gamePlay/fastSkip.ts index f593e5bea..76ab59a5f 100644 --- a/packages/webgal/src/Core/controller/gamePlay/fastSkip.ts +++ b/packages/webgal/src/Core/controller/gamePlay/fastSkip.ts @@ -63,6 +63,7 @@ export const startTemporaryFast = (duration = 150) => { clearTemporaryFast(); if (WebGAL.gameplay.fastInterval !== null) stopFast(); WebGAL.gameplay.isFast = true; + // 临时快进状态持续 duration 毫秒,供此期间的演出判断是否跳过动画。 temporaryFastTimeout = setTimeout(() => { if (WebGAL.gameplay.fastInterval === null) { WebGAL.gameplay.isFast = false; diff --git a/packages/webgal/src/Core/controller/stage/pixi/PixiController.ts b/packages/webgal/src/Core/controller/stage/pixi/PixiController.ts index c07b833ab..07e1bf37b 100644 --- a/packages/webgal/src/Core/controller/stage/pixi/PixiController.ts +++ b/packages/webgal/src/Core/controller/stage/pixi/PixiController.ts @@ -185,7 +185,7 @@ export default class PixiStage { this.backgroundContainer, ); this.currentApp = app; - // loader 防死 + // 每 500ms 兜底尝试推进加载队列;同步递归会阻塞主线程,也无法等待 loader 结束忙碌。 const reload = () => { setTimeout(reload, 500); this.callLoader(); @@ -372,34 +372,31 @@ export default class PixiStage { // 完成图片加载后执行的函数 const setup = () => { - // TODO:找一个更好的解法,现在的解法是无论是否复用原来的资源,都设置一个延时以让动画工作正常! - - setTimeout(() => { - const texture = loader.resources?.[url]?.texture; - if (texture && this.getStageObjByUuid(bgUuid)) { - /** - * 重设大小 - */ - const originalWidth = texture.width; - const originalHeight = texture.height; - const scaleX = this.stageWidth / originalWidth; - const scaleY = this.stageHeight / originalHeight; - const targetScale = Math.max(scaleX, scaleY); - const bgSprite = new PIXI.Sprite(texture); - bgSprite.scale.x = targetScale; - bgSprite.scale.y = targetScale; - bgSprite.anchor.set(0.5); - bgSprite.position.y = this.stageHeight / 2; - thisBgContainer.setBaseX(this.stageWidth / 2); - thisBgContainer.setBaseY(this.stageHeight / 2); - thisBgContainer.pivot.set(0, this.stageHeight / 2); - - // 挂载 - thisBgContainer.addChild(bgSprite); - this.notifyTargetReferenceBoxChanged(key); - this.requestRender(); - } - }, 0); + // 对象已同步入表,资源就绪后直接挂载;动画由 commit 后的演出启动。 + const texture = loader.resources?.[url]?.texture; + if (texture && this.getStageObjByUuid(bgUuid)) { + /** + * 重设大小 + */ + const originalWidth = texture.width; + const originalHeight = texture.height; + const scaleX = this.stageWidth / originalWidth; + const scaleY = this.stageHeight / originalHeight; + const targetScale = Math.max(scaleX, scaleY); + const bgSprite = new PIXI.Sprite(texture); + bgSprite.scale.x = targetScale; + bgSprite.scale.y = targetScale; + bgSprite.anchor.set(0.5); + bgSprite.position.y = this.stageHeight / 2; + thisBgContainer.setBaseX(this.stageWidth / 2); + thisBgContainer.setBaseY(this.stageHeight / 2); + thisBgContainer.pivot.set(0, this.stageHeight / 2); + + // 挂载 + thisBgContainer.addChild(bgSprite); + this.notifyTargetReferenceBoxChanged(key); + this.requestRender(); + } }; /** @@ -448,43 +445,40 @@ export default class PixiStage { // 完成加载后执行的函数 const setup = () => { - // TODO:找一个更好的解法,现在的解法是无论是否复用原来的资源,都设置一个延时以让动画工作正常! - - setTimeout(() => { - console.debug('start loaded video: ' + url); - const video = document.createElement('video'); - const videoResource = new PIXI.VideoResource(video); - videoResource.src = url; - videoResource.source.preload = 'auto'; - videoResource.source.muted = true; - videoResource.source.loop = true; - videoResource.source.autoplay = true; - videoResource.source.src = url; - // @ts-ignore - const texture = PIXI.Texture.from(videoResource); - if (texture && this.getStageObjByUuid(bgUuid)) { - /** - * 重设大小 - */ - texture.baseTexture.resource.load().then(() => { - const originalWidth = videoResource.source.videoWidth; - const originalHeight = videoResource.source.videoHeight; - const scaleX = this.stageWidth / originalWidth; - const scaleY = this.stageHeight / originalHeight; - const targetScale = Math.max(scaleX, scaleY); - const bgSprite = new PIXI.Sprite(texture); - bgSprite.scale.x = targetScale; - bgSprite.scale.y = targetScale; - bgSprite.anchor.set(0.5); - bgSprite.position.y = this.stageHeight / 2; - thisBgContainer.setBaseX(this.stageWidth / 2); - thisBgContainer.setBaseY(this.stageHeight / 2); - thisBgContainer.pivot.set(0, this.stageHeight / 2); - thisBgContainer.addChild(bgSprite); - this.notifyTargetReferenceBoxChanged(key); - }); - } - }, 0); + // 对象已同步入表,资源就绪后直接挂载;动画由 commit 后的演出启动。 + console.debug('start loaded video: ' + url); + const video = document.createElement('video'); + const videoResource = new PIXI.VideoResource(video); + videoResource.src = url; + videoResource.source.preload = 'auto'; + videoResource.source.muted = true; + videoResource.source.loop = true; + videoResource.source.autoplay = true; + videoResource.source.src = url; + // @ts-ignore + const texture = PIXI.Texture.from(videoResource); + if (texture && this.getStageObjByUuid(bgUuid)) { + /** + * 重设大小 + */ + texture.baseTexture.resource.load().then(() => { + const originalWidth = videoResource.source.videoWidth; + const originalHeight = videoResource.source.videoHeight; + const scaleX = this.stageWidth / originalWidth; + const scaleY = this.stageHeight / originalHeight; + const targetScale = Math.max(scaleX, scaleY); + const bgSprite = new PIXI.Sprite(texture); + bgSprite.scale.x = targetScale; + bgSprite.scale.y = targetScale; + bgSprite.anchor.set(0.5); + bgSprite.position.y = this.stageHeight / 2; + thisBgContainer.setBaseX(this.stageWidth / 2); + thisBgContainer.setBaseY(this.stageHeight / 2); + thisBgContainer.pivot.set(0, this.stageHeight / 2); + thisBgContainer.addChild(bgSprite); + this.notifyTargetReferenceBoxChanged(key); + }); + } }; /** @@ -542,36 +536,34 @@ export default class PixiStage { }); // 完成图片加载后执行的函数 const setup = () => { - // TODO:找一个更好的解法,现在的解法是无论是否复用原来的资源,都设置一个延时以让动画工作正常! - setTimeout(() => { - const texture = loader.resources?.[url]?.texture; - if (texture && this.getStageObjByUuid(figureUuid)) { - /** - * 重设大小 - */ - const originalWidth = texture.width; - const originalHeight = texture.height; - const scaleX = this.stageWidth / originalWidth; - const scaleY = this.stageHeight / originalHeight; - const targetScale = Math.min(scaleX, scaleY); - const figureSprite = new PIXI.Sprite(texture); - figureSprite.scale.x = targetScale; - figureSprite.scale.y = targetScale; - figureSprite.anchor.set(0.5); - figureSprite.position.y = this.stageHeight / 2; - const targetWidth = originalWidth * targetScale; - const targetHeight = originalHeight * targetScale; - thisFigureContainer.setBaseY(this.stageHeight / 2); - if (targetHeight < this.stageHeight) { - thisFigureContainer.setBaseY(this.stageHeight / 2 + (this.stageHeight - targetHeight) / 2); - } - thisFigureContainer.setBaseX(getFigureBaseX(presetPosition, this.stageWidth, targetWidth)); - thisFigureContainer.pivot.set(0, this.stageHeight / 2); - thisFigureContainer.addChild(figureSprite); - this.notifyTargetReferenceBoxChanged(key); - this.requestRender(); + // 对象已同步入表,资源就绪后直接挂载;动画由 commit 后的演出启动。 + const texture = loader.resources?.[url]?.texture; + if (texture && this.getStageObjByUuid(figureUuid)) { + /** + * 重设大小 + */ + const originalWidth = texture.width; + const originalHeight = texture.height; + const scaleX = this.stageWidth / originalWidth; + const scaleY = this.stageHeight / originalHeight; + const targetScale = Math.min(scaleX, scaleY); + const figureSprite = new PIXI.Sprite(texture); + figureSprite.scale.x = targetScale; + figureSprite.scale.y = targetScale; + figureSprite.anchor.set(0.5); + figureSprite.position.y = this.stageHeight / 2; + const targetWidth = originalWidth * targetScale; + const targetHeight = originalHeight * targetScale; + thisFigureContainer.setBaseY(this.stageHeight / 2); + if (targetHeight < this.stageHeight) { + thisFigureContainer.setBaseY(this.stageHeight / 2 + (this.stageHeight - targetHeight) / 2); } - }, 0); + thisFigureContainer.setBaseX(getFigureBaseX(presetPosition, this.stageWidth, targetWidth)); + thisFigureContainer.pivot.set(0, this.stageHeight / 2); + thisFigureContainer.addChild(figureSprite); + this.notifyTargetReferenceBoxChanged(key); + this.requestRender(); + } }; /** @@ -973,6 +965,7 @@ export default class PixiStage { resolve(); }; + // 资源加载失败或尺寸通知缺失时也要结束等待,避免参考尺寸查询永久挂起。 timeoutId = window.setTimeout(resolveAndCleanup, timeoutMs); waiters.add(resolveAndCleanup); }); diff --git a/packages/webgal/src/Core/controller/stage/pixi/spine.ts b/packages/webgal/src/Core/controller/stage/pixi/spine.ts index f1f34aa80..6a897240f 100644 --- a/packages/webgal/src/Core/controller/stage/pixi/spine.ts +++ b/packages/webgal/src/Core/controller/stage/pixi/spine.ts @@ -94,80 +94,79 @@ export async function addSpineFigureImpl( // 完成图片加载后执行的函数 const setup = async () => { - setTimeout(() => { - console.log('Setting up Spine' + key + url); - if (!pixiSpine) { - // 无法加载 'pixi-spine',跳过 Spine 相关逻辑 - logger.warn(`Spine module not loaded. Skipping Spine figure: ${key}`); - return; - } + // 对象已同步入表,资源就绪后直接挂载;动画由 commit 后的演出启动。 + console.log('Setting up Spine' + key + url); + if (!pixiSpine) { + // 无法加载 'pixi-spine',跳过 Spine 相关逻辑 + logger.warn(`Spine module not loaded. Skipping Spine figure: ${key}`); + return; + } - const { Spine } = pixiSpine; - const spineResource: any = spineLoader!.resources?.[spineId]; - if (spineResource && this.getStageObjByUuid(figureUuid)) { - const figureSpine = new Spine(spineResource.spineData); - const spineBounds = figureSpine.getLocalBounds(); - const spineCenterX = spineBounds.x + spineBounds.width / 2; - const spineCenterY = spineBounds.y + spineBounds.height / 2; - figureSpine.pivot.set(spineCenterX, spineCenterY); - figureSpine.interactive = false; + const { Spine } = pixiSpine; + const spineResource: any = spineLoader!.resources?.[spineId]; + if (spineResource && this.getStageObjByUuid(figureUuid)) { + const figureSpine = new Spine(spineResource.spineData); + const spineBounds = figureSpine.getLocalBounds(); + const spineCenterX = spineBounds.x + spineBounds.width / 2; + const spineCenterY = spineBounds.y + spineBounds.height / 2; + figureSpine.pivot.set(spineCenterX, spineCenterY); + figureSpine.interactive = false; - const motionFromState = stageStateManager.getViewStageState().live2dMotion.find((e) => e.target === key); - let animationToPlay = ''; - if (motionFromState?.skin) { - if (!applySpineSkin(figureSpine, motionFromState.skin)) { - logger.warn(`Spine skin not found: ${motionFromState.skin} on ${key}`); - } + const motionFromState = stageStateManager.getViewStageState().live2dMotion.find((e) => e.target === key); + let animationToPlay = ''; + if (motionFromState?.skin) { + if (!applySpineSkin(figureSpine, motionFromState.skin)) { + logger.warn(`Spine skin not found: ${motionFromState.skin} on ${key}`); } + } - if ( - motionFromState && - figureSpine.spineData.animations.find((anim: any) => anim.name === motionFromState.motion) - ) { - // 使用状态中指定的动画 - animationToPlay = motionFromState.motion; - } else if (figureSpine.spineData.animations.length > 0) { - // 播放默认动画(第一个动画) - animationToPlay = figureSpine.spineData.animations[0].name; - } + if ( + motionFromState && + figureSpine.spineData.animations.find((anim: any) => anim.name === motionFromState.motion) + ) { + // 使用状态中指定的动画 + animationToPlay = motionFromState.motion; + } else if (figureSpine.spineData.animations.length > 0) { + // 播放默认动画(第一个动画) + animationToPlay = figureSpine.spineData.animations[0].name; + } - if (animationToPlay) { - figureSpine.state.setAnimation(0, animationToPlay, false); - figureSpine.autoUpdate = true; - const stageObj = this.getStageObjByUuid(figureUuid); - if (stageObj) { - if (stageObj.spineAnimation) { - stageObj.spineAnimation = animationToPlay; - } + if (animationToPlay) { + figureSpine.state.setAnimation(0, animationToPlay, false); + figureSpine.autoUpdate = true; + const stageObj = this.getStageObjByUuid(figureUuid); + if (stageObj) { + if (stageObj.spineAnimation) { + stageObj.spineAnimation = animationToPlay; } } + } - /** - * 重设大小 - */ - const originalWidth = figureSpine.width; - const originalHeight = figureSpine.height; - const scaleX = this.stageWidth / originalWidth; - const scaleY = this.stageHeight / originalHeight; - const targetScale = Math.min(scaleX, scaleY); - const figureSprite = new PIXI.Sprite(); - figureSprite.addChild(figureSpine); - figureSprite.scale.x = targetScale; - figureSprite.scale.y = targetScale; - figureSprite.anchor.set(0.5); - figureSprite.position.y = this.stageHeight / 2; - const targetWidth = originalWidth * targetScale; - const targetHeight = originalHeight * targetScale; - thisFigureContainer.setBaseY(this.stageHeight / 2); - if (targetHeight < this.stageHeight) { - thisFigureContainer.setBaseY(this.stageHeight / 2 + (this.stageHeight - targetHeight) / 2); - } - thisFigureContainer.setBaseX(getFigureBaseX(presetPosition, this.stageWidth, targetWidth)); - thisFigureContainer.pivot.set(0, this.stageHeight / 2); - thisFigureContainer.addChild(figureSprite); - this.notifyTargetReferenceBoxChanged(key); + /** + * 重设大小 + */ + const originalWidth = figureSpine.width; + const originalHeight = figureSpine.height; + const scaleX = this.stageWidth / originalWidth; + const scaleY = this.stageHeight / originalHeight; + const targetScale = Math.min(scaleX, scaleY); + const figureSprite = new PIXI.Sprite(); + figureSprite.addChild(figureSpine); + figureSprite.scale.x = targetScale; + figureSprite.scale.y = targetScale; + figureSprite.anchor.set(0.5); + figureSprite.position.y = this.stageHeight / 2; + const targetWidth = originalWidth * targetScale; + const targetHeight = originalHeight * targetScale; + thisFigureContainer.setBaseY(this.stageHeight / 2); + if (targetHeight < this.stageHeight) { + thisFigureContainer.setBaseY(this.stageHeight / 2 + (this.stageHeight - targetHeight) / 2); } - }, 0); + thisFigureContainer.setBaseX(getFigureBaseX(presetPosition, this.stageWidth, targetWidth)); + thisFigureContainer.pivot.set(0, this.stageHeight / 2); + thisFigureContainer.addChild(figureSprite); + this.notifyTargetReferenceBoxChanged(key); + } }; /** @@ -227,40 +226,38 @@ export async function addSpineBgImpl(this: PixiStage, key: string, url: string) const { Spine } = pixiSpine; const spineResource: any = spineLoader!.resources?.[spineId]; - // TODO:找一个更好的解法,现在的解法是无论是否复用原来的资源,都设置一个延时以让动画工作正常! - setTimeout(() => { - if (spineResource && this.getStageObjByUuid(bgUuid)) { - const bgSpine = new Spine(spineResource.spineData); - const transY = spineResource?.spineData?.y ?? 0; - /** - * 重设大小 - */ - const originalWidth = bgSpine.width; // TODO: 视图大小可能小于画布大小,应提供参数指定视图大小 - const originalHeight = bgSpine.height; // TODO: 视图大小可能小于画布大小,应提供参数指定视图大小 - const scaleX = this.stageWidth / originalWidth; - const scaleY = this.stageHeight / originalHeight; - logger.debug('bgSpine state', bgSpine.state); - // TODO: 也许应该使用 setAnimation 播放初始动画 - if (bgSpine.spineData.animations.length > 0) { - // 播放首个动画 - bgSpine.state.setAnimation(0, bgSpine.spineData.animations[0].name, true); - } - const targetScale = Math.max(scaleX, scaleY); - const bgSprite = new PIXI.Sprite(); - bgSprite.addChild(bgSpine); - bgSprite.scale.x = targetScale; - bgSprite.scale.y = targetScale; - bgSprite.anchor.set(0.5); - bgSprite.position.y = this.stageHeight / 2; - thisBgContainer.setBaseX(this.stageWidth / 2); - thisBgContainer.setBaseY(this.stageHeight / 2); - thisBgContainer.pivot.set(0, this.stageHeight / 2); - - // 挂载 - thisBgContainer.addChild(bgSprite); - this.notifyTargetReferenceBoxChanged(key); + // 对象已同步入表,资源就绪后直接挂载;动画由 commit 后的演出启动。 + if (spineResource && this.getStageObjByUuid(bgUuid)) { + const bgSpine = new Spine(spineResource.spineData); + const transY = spineResource?.spineData?.y ?? 0; + /** + * 重设大小 + */ + const originalWidth = bgSpine.width; // TODO: 视图大小可能小于画布大小,应提供参数指定视图大小 + const originalHeight = bgSpine.height; // TODO: 视图大小可能小于画布大小,应提供参数指定视图大小 + const scaleX = this.stageWidth / originalWidth; + const scaleY = this.stageHeight / originalHeight; + logger.debug('bgSpine state', bgSpine.state); + // TODO: 也许应该使用 setAnimation 播放初始动画 + if (bgSpine.spineData.animations.length > 0) { + // 播放首个动画 + bgSpine.state.setAnimation(0, bgSpine.spineData.animations[0].name, true); } - }, 0); + const targetScale = Math.max(scaleX, scaleY); + const bgSprite = new PIXI.Sprite(); + bgSprite.addChild(bgSpine); + bgSprite.scale.x = targetScale; + bgSprite.scale.y = targetScale; + bgSprite.anchor.set(0.5); + bgSprite.position.y = this.stageHeight / 2; + thisBgContainer.setBaseX(this.stageWidth / 2); + thisBgContainer.setBaseY(this.stageHeight / 2); + thisBgContainer.pivot.set(0, this.stageHeight / 2); + + // 挂载 + thisBgContainer.addChild(bgSprite); + this.notifyTargetReferenceBoxChanged(key); + } }; /** diff --git a/packages/webgal/src/Core/controller/stage/pixi/syncPixiStageState.ts b/packages/webgal/src/Core/controller/stage/pixi/syncPixiStageState.ts index de5d59d3c..685c1e0f4 100644 --- a/packages/webgal/src/Core/controller/stage/pixi/syncPixiStageState.ts +++ b/packages/webgal/src/Core/controller/stage/pixi/syncPixiStageState.ts @@ -229,6 +229,7 @@ function removeBg(bgObject: IStageObject, skipAnimation: boolean): number { pixiStage.removeStageObjectByKey(oldBgKey); const { duration, animation } = getExitAnimation('bg-main-off', true, bgKey); pixiStage.registerAnimation(animation, bgAniKey, bgKey); + // 保留退出对象直到退场时长结束;同步销毁会让退场动画不可见。 setTimeout(() => { pixiStage.removeAnimation(bgAniKey); pixiStage.removeStageObjectByKey(bgKey); @@ -256,6 +257,7 @@ function removeFig(figObj: IStageObject, enterTikerKey: string, skipAnimation: b // 退出对象的 key 带时间戳,永远不在 effects 白名单里,因此与背景一样走普通动画通道即可 const { duration, animation } = getExitAnimation(figLeaveAniKey, false, figKey); pixiStage.registerAnimation(animation, leaveKey, figKey); + // 保留退出对象直到退场时长结束;同步销毁会让退场动画不可见。 setTimeout(() => { pixiStage.removeAnimation(leaveKey); pixiStage.removeStageObjectByKey(figKey); diff --git a/packages/webgal/src/Core/controller/storage/storageController.ts b/packages/webgal/src/Core/controller/storage/storageController.ts index 962e73d45..e35d593ce 100644 --- a/packages/webgal/src/Core/controller/storage/storageController.ts +++ b/packages/webgal/src/Core/controller/storage/storageController.ts @@ -49,6 +49,7 @@ function debounce(func: (...args: T[]) => K, wait: number) { function context(...args: T[]): K { clearTimeout(timeout); let ret!: K; + // 等连续调用停止 wait 毫秒后只执行最后一次,合并频繁的存储写入。 timeout = setTimeout(() => { ret = func.apply(context, args); }, wait); diff --git a/packages/webgal/src/Core/gameScripts/end.ts b/packages/webgal/src/Core/gameScripts/end.ts index b9d1ebf0d..eab20f9fb 100644 --- a/packages/webgal/src/Core/gameScripts/end.ts +++ b/packages/webgal/src/Core/gameScripts/end.ts @@ -20,7 +20,8 @@ export const end = (sentence: ISentence): IPerform => { const dispatch = webgalStore.dispatch; // 重新获取初始场景 const sceneUrl: string = assetSetter('start.txt', fileType.scene); - // 为了在 scriptExecutor 自增 sentenceId 后再重置场景 + // scriptExecutor 会在命令返回后自增 sentenceId,必须在此之后重置,避免重置后又变为 1。 + // 移除延时需要先让执行器显式处理结束流程,与 Pixi 是否同步无关。 setTimeout(() => { WebGAL.sceneManager.resetScene(); }, 5); diff --git a/packages/webgal/src/Core/gameScripts/intro.tsx b/packages/webgal/src/Core/gameScripts/intro.tsx index 99a7a37a5..f131d5bce 100644 --- a/packages/webgal/src/Core/gameScripts/intro.tsx +++ b/packages/webgal/src/Core/gameScripts/intro.tsx @@ -82,6 +82,7 @@ export const intro = (sentence: ISentence): IPerform => { // 由于用户操作,相当于时间向前推进,这时候更新这个演出的预计完成时间 baseDuration -= delayTime; if (setBlockingStateTimeout) clearTimeout(setBlockingStateTimeout); + // 按用户推进后的剩余展示时长解除阻塞,不能立即跳过尚未展示的内容。 setBlockingStateTimeout = setTimeout(() => { isBlocking = false; }, baseDuration); @@ -132,6 +133,7 @@ export const intro = (sentence: ISentence): IPerform => { if (timeout) clearTimeout(timeout); // 如果 Hold 了,自然不要自动结束 if (!isHold) { + // 用户提前推进后,按缩短的剩余时长结束非 hold 演出。 timeout = setTimeout(() => { WebGAL.gameplay.performController.unmountPerform(performName); }, baseDuration); @@ -167,6 +169,7 @@ export const intro = (sentence: ISentence): IPerform => { isHoldOn: false, startFunction: () => { isBlocking = true; + // 等文字按 animationDelay 展示完并经过结尾等待后,才允许继续推进。 setBlockingStateTimeout = setTimeout(() => { isBlocking = false; }, baseDuration); diff --git a/packages/webgal/src/Core/gameScripts/playVideo.tsx b/packages/webgal/src/Core/gameScripts/playVideo.tsx index bb93f1d0a..083521c4f 100644 --- a/packages/webgal/src/Core/gameScripts/playVideo.tsx +++ b/packages/webgal/src/Core/gameScripts/playVideo.tsx @@ -21,6 +21,7 @@ export const playVideo = (sentence: ISentence): IPerform => { let isOver = false; let skipVideo = () => {}; const restoreVolumeAndUnmount = () => { + isOver = true; WebGAL.events.fullscreenDbClick.off(skipVideo); /** * 恢复音量 @@ -55,35 +56,34 @@ export const playVideo = (sentence: ISentence): IPerform => {