diff --git a/packages/webgal/package.json b/packages/webgal/package.json index 29290e283..1d2f930a2 100644 --- a/packages/webgal/package.json +++ b/packages/webgal/package.json @@ -1,7 +1,7 @@ { "name": "webgal", "private": true, - "version": "4.5.4", + "version": "4.5.5", "scripts": { "dev": "vite --host --port 3000", "build": "cross-env NODE_ENV=production tsc && vite build --base=./", diff --git a/packages/webgal/public/game/scene/demo_zh_cn.txt b/packages/webgal/public/game/scene/demo_zh_cn.txt index 263138674..c6230e31b 100644 --- a/packages/webgal/public/game/scene/demo_zh_cn.txt +++ b/packages/webgal/public/game/scene/demo_zh_cn.txt @@ -13,7 +13,7 @@ miniAvatar:miniavatar.png; changeFigure:stand2.png -right -next; {egine} 是使用 Web 技术开发的引擎,因此在网页端有良好的表现。 -v2.wav; 由于这个特性,如果你将 {egine} 部署到服务器或网页托管平台上,玩家只需要一串链接就可以开始游玩! -v3.wav; -setAnimation:move-front-and-back -target=fig-left -next; +setAnimation:move-front-and-back -target=fig-left -continue; 听起来是不是非常吸引人? -v4.wav; changeFigure:none -right -next; setTransform:{"position": {"x": 500,"y": 0}} -target=fig-left -next; diff --git a/packages/webgal/public/game/template/template.json b/packages/webgal/public/game/template/template.json index 58e0b8552..742428439 100644 --- a/packages/webgal/public/game/template/template.json +++ b/packages/webgal/public/game/template/template.json @@ -1,4 +1,4 @@ { "name":"Default Template", - "webgal-version":"4.5.4" + "webgal-version":"4.5.5" } diff --git a/packages/webgal/src/Core/Modules/perform/performController.ts b/packages/webgal/src/Core/Modules/perform/performController.ts index b26f55d9d..cfdc46903 100644 --- a/packages/webgal/src/Core/Modules/perform/performController.ts +++ b/packages/webgal/src/Core/Modules/perform/performController.ts @@ -35,13 +35,11 @@ export class PerformController { if (!perform.isHoldOn) { // 如果不是保持演出,清除 this.unmountPerform(perform.performName); - if (perform.goNextWhenOver) { - // nextSentence(); - this.goNextWhenOver(); - } } }, perform.duration); + if (script.args.find((e) => e.key === 'continue' && e.value === true)) perform.goNextWhenOver = true; + this.performList.push(perform); } @@ -52,6 +50,10 @@ export class PerformController { if (!e.isHoldOn && e.performName === name) { e.stopFunction(); clearTimeout(e.stopTimeout as unknown as number); + if (e.goNextWhenOver) { + // nextSentence(); + this.goNextWhenOver(); + } this.performList.splice(i, 1); i--; } @@ -62,6 +64,10 @@ export class PerformController { if (e.performName === name) { e.stopFunction(); clearTimeout(e.stopTimeout as unknown as number); + if (e.goNextWhenOver) { + // nextSentence(); + this.goNextWhenOver(); + } this.performList.splice(i, 1); i--; /** @@ -89,7 +95,7 @@ export class PerformController { private goNextWhenOver() { let isBlockingAuto = false; - this.performList.forEach((e) => { + this.performList?.forEach((e) => { if (e.blockingAuto()) // 阻塞且没有结束的演出 isBlockingAuto = true; diff --git a/packages/webgal/src/Core/controller/gamePlay/nextSentence.ts b/packages/webgal/src/Core/controller/gamePlay/nextSentence.ts index 2699ef181..97c72b5a8 100644 --- a/packages/webgal/src/Core/controller/gamePlay/nextSentence.ts +++ b/packages/webgal/src/Core/controller/gamePlay/nextSentence.ts @@ -72,6 +72,7 @@ export const nextSentence = () => { isGoNext = true; } if (!e.skipNextCollect) { + // 由于提前结束使用的不是 unmountPerform 标准 API,所以不会触发两次 nextSentence e.stopFunction(); clearTimeout(e.stopTimeout as unknown as number); WebGAL.gameplay.performController.performList.splice(i, 1); @@ -80,6 +81,7 @@ export const nextSentence = () => { } } if (isGoNext) { + // 由于不使用 unmountPerform 标准 API,这里需要手动收集一下 isGoNext nextSentence(); } }; diff --git a/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts b/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts index eb07f7684..db7d6d0ca 100644 --- a/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts +++ b/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts @@ -4,7 +4,7 @@ import { logger } from '../../util/logger'; import { IStageState } from '@/store/stageInterface'; import { restoreScene } from '../scene/restoreScene'; import { webgalStore } from '@/store/store'; -import { getValueFromState } from '@/Core/gameScripts/setVar'; +import { getValueFromStateElseKey } from '@/Core/gameScripts/setVar'; import { strIf } from '@/Core/controller/gamePlay/strIf'; import { nextSentence } from '@/Core/controller/gamePlay/nextSentence'; import cloneDeep from 'lodash/cloneDeep'; @@ -25,7 +25,7 @@ export const whenChecker = (whenValue: string | undefined): boolean => { if (e.match(/true/) || e.match(/false/)) { return e; } - return getValueFromState(e).toString(); + return getValueFromStateElseKey(e); } else return e; }) .reduce((pre, curr) => pre + curr, ''); @@ -59,8 +59,8 @@ export const scriptExecutor = () => { if (contentExp !== null) { contentExp.forEach((e) => { - const contentVarValue = getValueFromState(e.replace(/(? { + if (WebGAL.sceneManager.lockSceneWrite) { + return; + } + WebGAL.sceneManager.lockSceneWrite = true; // 场景写入到运行时 - sceneFetcher(entry.sceneUrl).then((rawScene) => { - WebGAL.sceneManager.sceneData.currentScene = sceneParser(rawScene, entry.sceneName, entry.sceneUrl); - WebGAL.sceneManager.sceneData.currentSentenceId = entry.continueLine + 1; // 重设场景 - logger.debug('现在恢复场景,恢复后场景:', WebGAL.sceneManager.sceneData.currentScene); - nextSentence(); - }); + sceneFetcher(entry.sceneUrl) + .then((rawScene) => { + WebGAL.sceneManager.sceneData.currentScene = sceneParser(rawScene, entry.sceneName, entry.sceneUrl); + WebGAL.sceneManager.sceneData.currentSentenceId = entry.continueLine + 1; // 重设场景 + logger.debug('现在恢复场景,恢复后场景:', WebGAL.sceneManager.sceneData.currentScene); + WebGAL.sceneManager.lockSceneWrite = false; + nextSentence(); + }) + .catch((e) => { + logger.error('场景调用错误', e); + WebGAL.sceneManager.lockSceneWrite = false; + }); }; diff --git a/packages/webgal/src/Core/gameScripts/intro.tsx b/packages/webgal/src/Core/gameScripts/intro.tsx index cdbf65e23..fd984ae4c 100644 --- a/packages/webgal/src/Core/gameScripts/intro.tsx +++ b/packages/webgal/src/Core/gameScripts/intro.tsx @@ -127,7 +127,6 @@ export const intro = (sentence: ISentence): IPerform => { if (!isHold) { timeout = setTimeout(() => { WebGAL.gameplay.performController.unmountPerform(performName); - setTimeout(nextSentence, 0); }, baseDuration); } } diff --git a/packages/webgal/src/Core/gameScripts/playVideo.tsx b/packages/webgal/src/Core/gameScripts/playVideo.tsx index 1d137fe14..f57e41fdd 100644 --- a/packages/webgal/src/Core/gameScripts/playVideo.tsx +++ b/packages/webgal/src/Core/gameScripts/playVideo.tsx @@ -4,7 +4,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; import styles from '@/Stage/FullScreenPerform/fullScreenPerform.module.scss'; import { webgalStore } from '@/store/store'; -import { nextSentence } from '@/Core/controller/gamePlay/nextSentence'; import { getRandomPerformName, PerformController } from '@/Core/Modules/perform/performController'; import { getSentenceArgByKey } from '@/Core/util/getSentenceArg'; import { WebGAL } from '@/Core/WebGAL'; @@ -55,7 +54,6 @@ export const playVideo = (sentence: ISentence): IPerform => { isOver = true; e.stopFunction(); WebGAL.gameplay.performController.unmountPerform(e.performName); - nextSentence(); } } }; diff --git a/packages/webgal/src/Core/gameScripts/say.ts b/packages/webgal/src/Core/gameScripts/say.ts index 5267c3476..9048c4365 100644 --- a/packages/webgal/src/Core/gameScripts/say.ts +++ b/packages/webgal/src/Core/gameScripts/say.ts @@ -8,6 +8,7 @@ import { getRandomPerformName, PerformController } from '@/Core/Modules/perform/ import { getSentenceArgByKey } from '@/Core/util/getSentenceArg'; import { textSize, voiceOption } from '@/store/userDataInterface'; import { WebGAL } from '@/Core/WebGAL'; +import { compileSentence } from '@/Stage/TextBox/TextBox'; /** * 进行普通对话的显示 @@ -50,7 +51,9 @@ export const say = (sentence: ISentence): IPerform => { // 计算延迟 const textDelay = useTextDelay(userDataState.optionData.textSpeed); // 本句延迟 - const sentenceDelay = textDelay * sentence.content.length; + const textNodes = compileSentence(sentence.content, 3); + const len = textNodes.reduce((prev, curr) => prev + curr.length, 0); + const sentenceDelay = textDelay * len; for (const e of sentence.args) { if (e.key === 'fontSize') { diff --git a/packages/webgal/src/Core/gameScripts/setVar.ts b/packages/webgal/src/Core/gameScripts/setVar.ts index 7cec5c2dc..3bc391ff5 100644 --- a/packages/webgal/src/Core/gameScripts/setVar.ts +++ b/packages/webgal/src/Core/gameScripts/setVar.ts @@ -39,13 +39,21 @@ export const setVar = (sentence: ISentence): IPerform => { // 将变量替换为变量的值,然后合成表达式字符串 const valExp2 = valExpArr .map((e) => { - if (e.match(/\$?[.a-zA-Z]/)) { - return String(getValueFromState(e.trim())); - } else return e; + if (!e.trim().match(/^[a-zA-Z_$][a-zA-Z0-9_.]*$/)) { + // 检查是否是变量名,不是就返回本身 + return e; + } + const _r = getValueFromStateElseKey(e.trim()); + return typeof _r === 'string' ? `'${_r}'` : _r; }) .reduce((pre, curr) => pre + curr, ''); - const exp = compile(valExp2); - const result = exp(); + let result = ''; + try { + const exp = compile(valExp2); + result = exp(); + } catch (e) { + logger.error('expression compile error', e); + } webgalStore.dispatch(targetReducerFunction({ key, value: result })); } else if (valExp.match(/true|false/)) { if (valExp.match(/true/)) { @@ -54,11 +62,13 @@ export const setVar = (sentence: ISentence): IPerform => { if (valExp.match(/false/)) { webgalStore.dispatch(targetReducerFunction({ key, value: false })); } + } else if (valExp.length === 0) { + webgalStore.dispatch(targetReducerFunction({ key, value: '' })); } else { if (!isNaN(Number(valExp))) { webgalStore.dispatch(targetReducerFunction({ key, value: Number(valExp) })); } else { - webgalStore.dispatch(targetReducerFunction({ key, value: valExp })); + webgalStore.dispatch(targetReducerFunction({ key, value: getValueFromStateElseKey(valExp) })); } } if (setGlobal) { @@ -79,10 +89,13 @@ export const setVar = (sentence: ISentence): IPerform => { }; }; -type BaseVal = string | number | boolean; +type BaseVal = string | number | boolean | undefined; +/** + * 取不到时返回 undefined + */ export function getValueFromState(key: string) { - let ret: any = 0; + let ret: any; const stage = webgalStore.getState().stage; const userData = webgalStore.getState().userData; const _Merge = { stage, userData }; // 不要直接合并到一起,防止可能的键冲突 @@ -92,7 +105,19 @@ export function getValueFromState(key: string) { ret = userData.globalGameVar[key]; } else if (key.startsWith('$')) { const propertyKey = key.replace('$', ''); - ret = get(_Merge, propertyKey, 0) as BaseVal; + ret = get(_Merge, propertyKey, undefined) as BaseVal; } return ret; } + +/** + * 取不到时返回 key + */ +export function getValueFromStateElseKey(key: string) { + const valueFromState = getValueFromState(key); + if (valueFromState === null || valueFromState === undefined) { + logger.warn('valueFromState result null, key = ' + key); + return key; + } + return valueFromState; +} diff --git a/packages/webgal/src/Core/util/coreInitialFunction/infoFetcher.ts b/packages/webgal/src/Core/util/coreInitialFunction/infoFetcher.ts index f807cd369..333d9f7b9 100644 --- a/packages/webgal/src/Core/util/coreInitialFunction/infoFetcher.ts +++ b/packages/webgal/src/Core/util/coreInitialFunction/infoFetcher.ts @@ -34,19 +34,28 @@ export const infoFetcher = (url: string) => { gameConfig.forEach((e) => { const { command, args } = e; if (args.length > 0) { - let res: any = args[0].trim(); - if (/^(true|false)$/g.test(args[0])) { - res = !!res; - } else if (/^[0-9]+\.?[0-9]+$/g.test(args[0])) { - res = Number(res); - } + if (args.length > 1) { + dispatch( + setGlobalVar({ + key: command, + value: args.join('|'), + }), + ); + } else { + let res: any = args[0].trim(); + if (/^(true|false)$/g.test(args[0])) { + res = !!res; + } else if (/^[0-9]+\.?[0-9]+$/g.test(args[0])) { + res = Number(res); + } - dispatch( - setGlobalVar({ - key: command, - value: res, - }), - ); + dispatch( + setGlobalVar({ + key: command, + value: res, + }), + ); + } } }); diff --git a/packages/webgal/src/Stage/TextBox/IMSSTextbox.tsx b/packages/webgal/src/Stage/TextBox/IMSSTextbox.tsx index 12cde162a..b6179a4a0 100644 --- a/packages/webgal/src/Stage/TextBox/IMSSTextbox.tsx +++ b/packages/webgal/src/Stage/TextBox/IMSSTextbox.tsx @@ -71,12 +71,12 @@ export default function IMSSTextbox(props: ITextboxProps) { let delay = allTextIndex * textDelay; allTextIndex++; let prevLength = currentConcatDialogPrev.length; - if (currentConcatDialogPrev !== '' && index >= prevLength) { + if (currentConcatDialogPrev !== '' && allTextIndex >= prevLength) { delay = delay - prevLength * textDelay; } const styleClassName = ' ' + css(style); const styleAllText = ' ' + css(style_alltext); - if (index < prevLength) { + if (allTextIndex < prevLength) { return ( e.key === 'ruby'); + if (rubyKvPair) { + ruby = rubyKvPair.value; + } } else { ruby = enhance; } diff --git a/packages/webgal/src/UI/Logo/Logo.tsx b/packages/webgal/src/UI/Logo/Logo.tsx index c8ed9b799..a1b3eb6b8 100644 --- a/packages/webgal/src/UI/Logo/Logo.tsx +++ b/packages/webgal/src/UI/Logo/Logo.tsx @@ -34,7 +34,7 @@ const Logo: FC = () => { currentLogoIndex.set(0); currentTimeOutId.set(setTimeout(nextImg, animationDuration)); } - }, [isEnterGame, logoImage]); + }, [isEnterGame]); const currentLogoUrl = currentLogoIndex.value === -1 ? '' : logoImage[currentLogoIndex.value]; return ( diff --git a/packages/webgal/src/config/info.ts b/packages/webgal/src/config/info.ts index 9edbbfe1e..0cac928f3 100644 --- a/packages/webgal/src/config/info.ts +++ b/packages/webgal/src/config/info.ts @@ -1,21 +1,6 @@ export const __INFO = { - version: 'WebGAL 4.5.4', + version: 'WebGAL 4.5.5', contributors: [ - { username: 'Mahiru', link: 'https://github.com/MakinoharaShoko' }, - { username: 'Hoshinokinya', link: 'https://github.com/hshqwq' }, - { username: 'Junbo Xiong', link: 'https://github.com/C6H5-NO2' }, - { username: 'lykl', link: 'https://github.com/lykl' }, - { username: 'SakuraSnow', link: 'https://github.com/sliyoxn' }, - { username: 'bcqsd', link: 'https://github.com/bcqsd' }, - { username: 'Yuji Sakai', link: 'https://github.com/generalfreed' }, - { username: 'Iara', link: 'https://github.com/labiker' }, - { username: '22', link: 'https://github.com/nini22P' }, - { username: '德布罗煜', link: 'https://github.com/ch1ny' }, - { username: 'Mike Zhou', link: 'https://github.com/mikezzb' }, - { username: 'Murasame0721', link: 'https://github.com/Murasame0721' }, - { username: 'loliko', link: 'https://github.com/loliko114514' }, - { username: 'IdrilK', link: 'https://github.com/IdrilK' }, - { username: 'callofblood', link: 'https://github.com/callofblood' }, - { username: 'lyle', link: 'https://github.com/lylelove' }, + // 现在改为跳转到 GitHub 了 ], }; diff --git a/packages/webgal/src/hooks/useConfigData.ts b/packages/webgal/src/hooks/useConfigData.ts index be2a23b41..830976794 100644 --- a/packages/webgal/src/hooks/useConfigData.ts +++ b/packages/webgal/src/hooks/useConfigData.ts @@ -25,14 +25,13 @@ const useConfigData = () => { setEbg(titleUrl); break; } - /** - * TODO:Game_Logo 是个数组,并且改变后会造成进入游戏界面重新渲染,以后再考虑如何处理 - */ - // case 'Game_Logo': { - // const logoUrlList = [assetSetter(val, fileType.background)]; - // webgalStore.dispatch(setLogoImage(logoUrlList)); - // break; - // } + + case 'Game_Logo': { + const logos = val.split('|'); + const logoUrlList = logos.map((val) => assetSetter(val, fileType.background)); + webgalStore.dispatch(setLogoImage(logoUrlList)); + break; + } case 'Title_bgm': { const bgmUrl = assetSetter(val, fileType.bgm); diff --git a/packages/webgal/src/store/stageReducer.ts b/packages/webgal/src/store/stageReducer.ts index 0eb86aa94..37c9abb1a 100644 --- a/packages/webgal/src/store/stageReducer.ts +++ b/packages/webgal/src/store/stageReducer.ts @@ -137,8 +137,17 @@ const stageSlice = createSlice({ const newFigure = action.payload; const index = currentFreeFigures.findIndex((figure) => figure.key === newFigure.key); if (index >= 0) { - currentFreeFigures[index].basePosition = newFigure.basePosition; - currentFreeFigures[index].name = newFigure.name; + if (newFigure.name === '') { + // 删掉立绘和相关的动画 + currentFreeFigures.splice(index, 1); + const figureAssociatedAnimationIndex = state.figureAssociatedAnimation.findIndex( + (a) => a.targetId === newFigure.key, + ); + state.figureAssociatedAnimation.splice(figureAssociatedAnimationIndex, 1); + } else { + currentFreeFigures[index].basePosition = newFigure.basePosition; + currentFreeFigures[index].name = newFigure.name; + } } else { // 新加 if (newFigure.name !== '') currentFreeFigures.push(newFigure); diff --git a/releasenote.md b/releasenote.md index e4a4f65fd..f1ff0bcff 100644 --- a/releasenote.md +++ b/releasenote.md @@ -8,13 +8,34 @@ #### 新功能 -允许使用脚本修改标题画面等游戏配置选项 - -允许使用变量获取一部分引擎状态 +新增参数,用于控制 “在本条语句的演出结束后,执行下一条”。 #### 修复 -为部分字符添加转义 +修复了 `-concat` 选项动画在新行中的错误。 + +修复了样式定义时 ruby 不显示的问题。 + +修复了 intro 的 hold 问题。 + +修复了 say 延迟计算问题。 + +修复了 restoreScene 中的竞争状态。 + +修复了删除已关闭的自由图形的问题。 + +修复了变量值处理的问题。 + +修复了 setVar 进行 compile 字符串时异常的问题。 + +增强了 getValueFromState。 + +调整了对 `style-alltext` 键的正则匹配,使其可以触发文本框的样式修改。 + +修复了变量正则表达式的问题。 + +修复了 logo 图片的问题。 + ## Release Notes @@ -27,13 +48,34 @@ #### New Features -Allow using scripts to modify game configuration options such as title screen - -Allow using variables to get some engine states +Added a new parameter to control "execute the next statement after the performance of this statement is finished". #### Bug Fixes -Added escaping for some characters +Fixed an animation error with the `-concat` option in a new line. + +Fixed an issue where ruby was not displayed when a style was defined. + +Fixed the hold problem of intro. + +Fixed the say delay calculation problem. + +Fixed a race condition in restoreScene. + +Fixed an issue with deleting closed free figures. + +Fixed the variable value handling problem. + +Fixed an exception when setVar compiled a string. + +Enhanced getValueFromState. + +Adjusted the regular expression matching for the `style-alltext` key to allow it to trigger text box style modifications. + +Fixed the variable regular expression problem. + +Fixed the logo image issue. + ## リリースノート @@ -46,33 +88,30 @@ Added escaping for some characters #### 新機能 -スクリプトを使用してタイトル画面などのゲーム設定オプションを変更できるようになりました - -変数を使用して一部のエンジン状態を取得できるようになりました +「このステートメントの演出終了後に次のステートメントを実行する」を制御するための新しいパラメータが追加されました。 #### 修正 -一部の文字のエスケープを追加しました +`-concat` オプションのアニメーションが新しい行でエラーになる問題を修正しました。 +スタイルが定義されているときにルビが表示されない問題を修正しました。 +イントロのホールド問題を修正しました。 +say 遅延計算の問題を修正しました。 +restoreScene の競合状態を修正しました。 -[//]: # () +閉じたフリーフィギュアを削除する際の問題を修正しました。 -[//]: # (#### Nouvelles fonctionnalités) +変数値処理の問題を修正しました。 -[//]: # () -[//]: # (Prise en charge partielle des styles CSS pour le texte des boîtes de dialogue) +setVar が文字列をコンパイルする際の例外を修正しました。 -[//]: # () -[//]: # (Personnalisation de l'interface des choix) +getValueFromState を強化しました。 -[//]: # () -[//]: # (#### Corrections) +`style-alltext` キーの正規表現マッチングを調整し、テキストボックスのスタイル変更をトリガーできるようにしました。 -[//]: # () -[//]: # (Fuite de mémoire avec l'effet de pluie) +変数正規表現の問題を修正しました。 -[//]: # () -[//]: # (Conflit lors de l'appel simultané de plusieurs callScene ou changeScene) +ロゴ画像の問題を修正しました。