diff --git a/packages/webgal/package.json b/packages/webgal/package.json index efc4d84c2..077558189 100644 --- a/packages/webgal/package.json +++ b/packages/webgal/package.json @@ -1,7 +1,7 @@ { "name": "webgal", "private": true, - "version": "4.5.2", + "version": "4.5.3", "scripts": { "dev": "vite --host --port 3000", "build": "cross-env NODE_ENV=production tsc && vite build --base=./", @@ -21,7 +21,7 @@ "mitt": "^3.0.0", "modern-css-reset": "^1.4.0", "pixi-filters": "^4.2.0", - "pixi-live2d-display": "^0.4.0", + "pixi-live2d-display-webgal": "^0.5.2", "pixi-spine": "^3.1.2", "pixi.js": "^6.3.0", "popmotion": "^11.0.5", diff --git a/packages/webgal/public/game/scene/demo_zh_cn.txt b/packages/webgal/public/game/scene/demo_zh_cn.txt index cac93a536..263138674 100644 --- a/packages/webgal/public/game/scene/demo_zh_cn.txt +++ b/packages/webgal/public/game/scene/demo_zh_cn.txt @@ -25,6 +25,7 @@ pixiPerform:snow; pixiInit; WebGAL:接下来介绍一些新版本功能! WebGAL:比如这个[注](zhù)[音](yīn)功能,可以为游戏带来更好的体验! +WebGAL:我们也支持了[文本拓展语法](style=color:#B5495B\;),可以为[文](wen)[本](ben)带来[富文本支持](style-alltext=font-style:italic\; style=color:#66327C\;)、交互等特性。 WebGAL:新版本添加了特性:获取用户输入,你要尝试一下吗? choose:尝试一下:userInput|算了吧:toNextPart; diff --git a/packages/webgal/public/game/template/Stage/Choose/choose.scss b/packages/webgal/public/game/template/Stage/Choose/choose.scss new file mode 100644 index 000000000..4040078bc --- /dev/null +++ b/packages/webgal/public/game/template/Stage/Choose/choose.scss @@ -0,0 +1,54 @@ +.Choose_Main { + position: absolute; + width: 100%; + height: 100%; + display: flex; + flex-flow: column; + justify-content: center; + align-items: center; + z-index: 13; + background: rgba(0, 0, 0, 0.05); +} + +.Choose_item { + font-family: "WebgalUI", serif; + cursor: pointer; + min-width: 50%; + padding: 0.25em 1em 0.25em 1em; + font-size: 265%; + color: #8E354A; + text-align: center; + border-radius: 4px; + border: 3px solid rgba(0, 0, 0, 0); + box-shadow: 0 0 25px rgba(0, 0, 0, 0.25); + background: rgba(255, 255, 255, 0.65); + margin: 0.25em 0 0.25em 0; + transition: background-color 0.5s, border 0.5s, font-weight 0.5s, box-shadow 0.5s; + + &:hover { + background: rgba(255, 255, 255, 0.9); + box-shadow: 0 0 25px rgba(0, 0, 0, 0.35); + border: 3px solid #8E354A; + } +} + +.Choose_item_disabled { + font-family: "WebgalUI", serif; + cursor: not-allowed; + min-width: 50%; + padding: 0.25em 1em 0.25em 1em; + font-size: 265%; + color: rgba(142, 53, 74, 0.5); + text-align: center; + border-radius: 4px; + border: 3px solid rgba(0, 0, 0, 0); + box-shadow: 0 0 25px rgba(0, 0, 0, 0.25); + background: rgba(255, 255, 255, 0.5); + margin: 0.25em 0 0.25em 0; + transition: background-color 0.5s, border 0.5s, font-weight 0.5s, box-shadow 0.5s; +} + +.Choose_item_outer { + color: #000; + min-width: 50%; +} diff --git a/packages/webgal/public/game/template/template.json b/packages/webgal/public/game/template/template.json index e19dcfe29..3a596b65c 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.2" + "webgal-version":"4.5.3" } diff --git a/packages/webgal/src/Core/Modules/scene.ts b/packages/webgal/src/Core/Modules/scene.ts index 306f358cc..1f72069f1 100644 --- a/packages/webgal/src/Core/Modules/scene.ts +++ b/packages/webgal/src/Core/Modules/scene.ts @@ -27,6 +27,7 @@ export class SceneManager { public settledScenes: Array = []; public settledAssets: Array = []; public sceneData: ISceneData = cloneDeep(initSceneData); + public lockSceneWrite = false; public resetScene() { this.sceneData.currentSentenceId = 0; diff --git a/packages/webgal/src/Core/WebGAL.ts b/packages/webgal/src/Core/WebGAL.ts index 1b0a10a15..0874d828e 100644 --- a/packages/webgal/src/Core/WebGAL.ts +++ b/packages/webgal/src/Core/WebGAL.ts @@ -1,3 +1,7 @@ import { WebgalCore } from '@/Core/webgalCore'; export const WebGAL = new WebgalCore(); + +// 调试,不调试给去掉 +// @ts-ignore +// window.WebGAL = WebGAL; diff --git a/packages/webgal/src/Core/controller/scene/callScene.ts b/packages/webgal/src/Core/controller/scene/callScene.ts index 4aa1141dc..ead6e3bd1 100644 --- a/packages/webgal/src/Core/controller/scene/callScene.ts +++ b/packages/webgal/src/Core/controller/scene/callScene.ts @@ -13,6 +13,10 @@ import { WebGAL } from '@/Core/WebGAL'; * @param sceneName 场景名称 */ export const callScene = (sceneUrl: string, sceneName: string) => { + if (WebGAL.sceneManager.lockSceneWrite) { + return; + } + WebGAL.sceneManager.lockSceneWrite = true; // 先将本场景压入场景栈 WebGAL.sceneManager.sceneData.sceneStack.push({ sceneName: WebGAL.sceneManager.sceneData.currentScene.sceneName, @@ -20,15 +24,21 @@ export const callScene = (sceneUrl: string, sceneName: string) => { continueLine: WebGAL.sceneManager.sceneData.currentSentenceId, }); // 场景写入到运行时 - sceneFetcher(sceneUrl).then((rawScene) => { - WebGAL.sceneManager.sceneData.currentScene = sceneParser(rawScene, sceneName, sceneUrl); - WebGAL.sceneManager.sceneData.currentSentenceId = 0; - // 开始场景的预加载 - const subSceneList = WebGAL.sceneManager.sceneData.currentScene.subSceneList; - WebGAL.sceneManager.settledScenes.push(sceneUrl); // 放入已加载场景列表,避免递归加载相同场景 - const subSceneListUniq = uniqWith(subSceneList); // 去重 - scenePrefetcher(subSceneListUniq); - logger.debug('现在调用场景,调用结果:', WebGAL.sceneManager.sceneData); - nextSentence(); - }); + sceneFetcher(sceneUrl) + .then((rawScene) => { + WebGAL.sceneManager.sceneData.currentScene = sceneParser(rawScene, sceneName, sceneUrl); + WebGAL.sceneManager.sceneData.currentSentenceId = 0; + // 开始场景的预加载 + const subSceneList = WebGAL.sceneManager.sceneData.currentScene.subSceneList; + WebGAL.sceneManager.settledScenes.push(sceneUrl); // 放入已加载场景列表,避免递归加载相同场景 + const subSceneListUniq = uniqWith(subSceneList); // 去重 + scenePrefetcher(subSceneListUniq); + logger.debug('现在调用场景,调用结果:', WebGAL.sceneManager.sceneData); + WebGAL.sceneManager.lockSceneWrite = false; + nextSentence(); + }) + .catch((e) => { + logger.error('场景调用错误', e); + WebGAL.sceneManager.lockSceneWrite = false; + }); }; diff --git a/packages/webgal/src/Core/controller/scene/changeScene.ts b/packages/webgal/src/Core/controller/scene/changeScene.ts index 8b1a431e4..67df0879a 100644 --- a/packages/webgal/src/Core/controller/scene/changeScene.ts +++ b/packages/webgal/src/Core/controller/scene/changeScene.ts @@ -13,16 +13,26 @@ import { WebGAL } from '@/Core/WebGAL'; * @param sceneName 场景名称 */ export const changeScene = (sceneUrl: string, sceneName: string) => { + if (WebGAL.sceneManager.lockSceneWrite) { + return; + } + WebGAL.sceneManager.lockSceneWrite = true; // 场景写入到运行时 - sceneFetcher(sceneUrl).then((rawScene) => { - WebGAL.sceneManager.sceneData.currentScene = sceneParser(rawScene, sceneName, sceneUrl); - WebGAL.sceneManager.sceneData.currentSentenceId = 0; - // 开始场景的预加载 - const subSceneList = WebGAL.sceneManager.sceneData.currentScene.subSceneList; - WebGAL.sceneManager.settledScenes.push(sceneUrl); // 放入已加载场景列表,避免递归加载相同场景 - const subSceneListUniq = uniqWith(subSceneList); // 去重 - scenePrefetcher(subSceneListUniq); - logger.debug('现在切换场景,切换后的结果:', WebGAL.sceneManager.sceneData); - nextSentence(); - }); + sceneFetcher(sceneUrl) + .then((rawScene) => { + WebGAL.sceneManager.sceneData.currentScene = sceneParser(rawScene, sceneName, sceneUrl); + WebGAL.sceneManager.sceneData.currentSentenceId = 0; + // 开始场景的预加载 + const subSceneList = WebGAL.sceneManager.sceneData.currentScene.subSceneList; + WebGAL.sceneManager.settledScenes.push(sceneUrl); // 放入已加载场景列表,避免递归加载相同场景 + const subSceneListUniq = uniqWith(subSceneList); // 去重 + scenePrefetcher(subSceneListUniq); + logger.debug('现在切换场景,切换后的结果:', WebGAL.sceneManager.sceneData); + WebGAL.sceneManager.lockSceneWrite = false; + nextSentence(); + }) + .catch((e) => { + logger.error('场景调用错误', e); + WebGAL.sceneManager.lockSceneWrite = false; + }); }; diff --git a/packages/webgal/src/Core/controller/scene/sceneFetcher.ts b/packages/webgal/src/Core/controller/scene/sceneFetcher.ts index 7fec084cd..c66c04cf1 100644 --- a/packages/webgal/src/Core/controller/scene/sceneFetcher.ts +++ b/packages/webgal/src/Core/controller/scene/sceneFetcher.ts @@ -5,10 +5,15 @@ import axios from 'axios'; * @param sceneUrl 场景文件路径 */ export const sceneFetcher = (sceneUrl: string) => { - return new Promise((resolve) => { - axios.get(sceneUrl).then((response) => { - const rawScene: string = response.data.toString(); - resolve(rawScene); - }); + return new Promise((resolve, reject) => { + axios + .get(sceneUrl) + .then((response) => { + const rawScene: string = response.data.toString(); + resolve(rawScene); + }) + .catch((e) => { + reject(e); + }); }); }; diff --git a/packages/webgal/src/Core/gameScripts/choose/choose.module.scss b/packages/webgal/src/Core/gameScripts/choose/choose.module.scss index a5691c717..4040078bc 100644 --- a/packages/webgal/src/Core/gameScripts/choose/choose.module.scss +++ b/packages/webgal/src/Core/gameScripts/choose/choose.module.scss @@ -24,6 +24,12 @@ background: rgba(255, 255, 255, 0.65); margin: 0.25em 0 0.25em 0; transition: background-color 0.5s, border 0.5s, font-weight 0.5s, box-shadow 0.5s; + + &:hover { + background: rgba(255, 255, 255, 0.9); + box-shadow: 0 0 25px rgba(0, 0, 0, 0.35); + border: 3px solid #8E354A; + } } .Choose_item_disabled { @@ -42,9 +48,7 @@ transition: background-color 0.5s, border 0.5s, font-weight 0.5s, box-shadow 0.5s; } -.Choose_item:hover { - //font-weight: bold; - background: rgba(255, 255, 255, 0.9); - box-shadow: 0 0 25px rgba(0, 0, 0, 0.35); - border: 3px solid #8E354A; +.Choose_item_outer { + color: #000; + min-width: 50%; } diff --git a/packages/webgal/src/Core/gameScripts/choose/index.tsx b/packages/webgal/src/Core/gameScripts/choose/index.tsx index ac1de2098..297507738 100644 --- a/packages/webgal/src/Core/gameScripts/choose/index.tsx +++ b/packages/webgal/src/Core/gameScripts/choose/index.tsx @@ -11,6 +11,8 @@ import { PerformController } from '@/Core/Modules/perform/performController'; import { useSEByWebgalStore } from '@/hooks/useSoundEffect'; import { WebGAL } from '@/Core/WebGAL'; import { whenChecker } from '@/Core/controller/gamePlay/scriptExecutor'; +import useApplyStyle from '@/hooks/useApplyStyle'; +import { Provider } from 'react-redux'; class ChooseOption { /** @@ -56,16 +58,42 @@ class ChooseOption { export const choose = (sentence: ISentence): IPerform => { const chooseOptionScripts = sentence.content.split('|'); const chooseOptions = chooseOptionScripts.map((e) => ChooseOption.parse(e)); + + // eslint-disable-next-line react/no-deprecated + ReactDOM.render( + + + , + document.getElementById('chooseContainer'), + ); + return { + performName: 'choose', + duration: 1000 * 60 * 60 * 24, + isHoldOn: false, + stopFunction: () => { + // eslint-disable-next-line react/no-deprecated + ReactDOM.render(
, document.getElementById('chooseContainer')); + }, + blockingNext: () => true, + blockingAuto: () => true, + stopTimeout: undefined, // 暂时不用,后面会交给自动清除 + }; +}; + +function Choose(props: { chooseOptions: ChooseOption[] }) { const fontFamily = webgalStore.getState().userData.optionData.textboxFont; const font = fontFamily === textFont.song ? '"思源宋体", serif' : '"WebgalUI", serif'; const { playSeEnter, playSeClick } = useSEByWebgalStore(); + const applyStyle = useApplyStyle('Stage/Choose/choose.scss'); // 运行时计算JSX.Element[] const runtimeBuildList = (chooseListFull: ChooseOption[]) => { return chooseListFull .filter((e, i) => whenChecker(e.showCondition)) .map((e, i) => { const enable = whenChecker(e.enableCondition); - const className = enable ? styles.Choose_item : styles.Choose_item_disabled; + const className = enable + ? applyStyle('Choose_item', styles.Choose_item) + : applyStyle('Choose_item_disabled', styles.Choose_item_disabled); const onClick = enable ? () => { playSeClick(); @@ -78,33 +106,14 @@ export const choose = (sentence: ISentence): IPerform => { } : () => {}; return ( -
- {e.text} +
+
+ {e.text} +
); }); }; - // eslint-disable-next-line react/no-deprecated - ReactDOM.render( -
{runtimeBuildList(chooseOptions)}
, - document.getElementById('chooseContainer'), - ); - return { - performName: 'choose', - duration: 1000 * 60 * 60 * 24, - isHoldOn: false, - stopFunction: () => { - // eslint-disable-next-line react/no-deprecated - ReactDOM.render(
, document.getElementById('chooseContainer')); - }, - blockingNext: () => true, - blockingAuto: () => true, - stopTimeout: undefined, // 暂时不用,后面会交给自动清除 - }; -}; + + return
{runtimeBuildList(props.chooseOptions)}
; +} diff --git a/packages/webgal/src/Core/gameScripts/pixi/performs/cherryBlossoms.ts b/packages/webgal/src/Core/gameScripts/pixi/performs/cherryBlossoms.ts index 99e8de986..e8f74811a 100644 --- a/packages/webgal/src/Core/gameScripts/pixi/performs/cherryBlossoms.ts +++ b/packages/webgal/src/Core/gameScripts/pixi/performs/cherryBlossoms.ts @@ -76,7 +76,7 @@ const pixicherryBlossoms = (cherryBlossomsSpeed: number) => { // 同じ画面上の桜の数を制御します // 控制同屏花数 if (bunnyList.length >= 200) { - bunnyList.unshift(); + bunnyList.shift()?.destroy(); container.removeChild(container.children[0]); } } diff --git a/packages/webgal/src/Core/gameScripts/pixi/performs/rain.ts b/packages/webgal/src/Core/gameScripts/pixi/performs/rain.ts index 1cae9588d..81a86365c 100644 --- a/packages/webgal/src/Core/gameScripts/pixi/performs/rain.ts +++ b/packages/webgal/src/Core/gameScripts/pixi/performs/rain.ts @@ -24,7 +24,7 @@ const pixiRain = (rainSpeed: number, number: number) => { container.scale.x = 1; container.scale.y = 1; // container.rotation = -0.2; - const bunnyList: any = []; + const bunnyList: PIXI.Sprite[] = []; // 监听动画更新 function ticker(delta: number) { // 获取长宽,用于控制雪花出现位置 @@ -59,17 +59,20 @@ const pixiRain = (rainSpeed: number, number: number) => { container.addChild(bunny); // 控制每片雨点 bunnyList.push(bunny); + + // 控制同屏雨点数 + if (bunnyList.length >= 2500) { + bunnyList.shift()?.destroy(); + container.removeChild(container.children[0]); + } } // 雨点落下 for (const e of bunnyList) { + // @ts-ignore e['dropSpeed'] = e['acc'] * 0.01 + e['dropSpeed']; + // @ts-ignore e.y += delta * rainSpeed * e['dropSpeed'] * 1.1 + 3; } - // 控制同屏雨点数 - if (bunnyList.length >= 2500) { - bunnyList.unshift(); - container.removeChild(container.children[0]); - } } WebGAL.gameplay.pixiStage?.registerAnimation( { setStartState: () => {}, setEndState: () => {}, tickerFunc: ticker }, diff --git a/packages/webgal/src/Core/gameScripts/pixi/performs/snow.ts b/packages/webgal/src/Core/gameScripts/pixi/performs/snow.ts index ac958a93c..1c54db7ee 100644 --- a/packages/webgal/src/Core/gameScripts/pixi/performs/snow.ts +++ b/packages/webgal/src/Core/gameScripts/pixi/performs/snow.ts @@ -68,7 +68,7 @@ const pixiSnow = (snowSpeed: number) => { } // 控制同屏雪花数 if (bunnyList.length >= 500) { - bunnyList.unshift(); + bunnyList.shift()?.destroy(); container.removeChild(container.children[0]); } } diff --git a/packages/webgal/src/Core/util/prefetcher/assetsPrefetcher.ts b/packages/webgal/src/Core/util/prefetcher/assetsPrefetcher.ts index f6701dff0..f3176f0cd 100644 --- a/packages/webgal/src/Core/util/prefetcher/assetsPrefetcher.ts +++ b/packages/webgal/src/Core/util/prefetcher/assetsPrefetcher.ts @@ -8,6 +8,12 @@ import { WebGAL } from '@/Core/WebGAL'; * @param assetList 场景资源列表 */ export const assetsPrefetcher = (assetList: Array) => { + // @ts-ignore + // 未必要移除,加载到内存里也有用 + // if (window?.isElectron) { + // return; + // } + for (const asset of assetList) { // 判断是否已经存在 const hasPrefetch = WebGAL.sceneManager.settledAssets.includes(asset.url); @@ -19,7 +25,11 @@ export const assetsPrefetcher = (assetList: Array) => { newLink.setAttribute('href', asset.url); const head = document.getElementsByTagName('head'); if (head.length) { - head[0].appendChild(newLink); + try { + head[0].appendChild(newLink); + } catch (e) { + console.log('预加载出错', e); + } } WebGAL.sceneManager.settledAssets.push(asset.url); } diff --git a/packages/webgal/src/Stage/TextBox/IMSSTextbox.tsx b/packages/webgal/src/Stage/TextBox/IMSSTextbox.tsx index 0c3ec545f..12cde162a 100644 --- a/packages/webgal/src/Stage/TextBox/IMSSTextbox.tsx +++ b/packages/webgal/src/Stage/TextBox/IMSSTextbox.tsx @@ -3,6 +3,7 @@ import { ReactNode, useEffect } from 'react'; import { WebGAL } from '@/Core/WebGAL'; import { ITextboxProps } from './types'; import useApplyStyle from '@/hooks/useApplyStyle'; +import { css } from '@emotion/css'; export default function IMSSTextbox(props: ITextboxProps) { const { @@ -41,7 +42,29 @@ export default function IMSSTextbox(props: ITextboxProps) { let allTextIndex = 0; const textElementList = textArray.map((line, index) => { - const textLine = line.map((e, index) => { + const textLine = line.map((en, index) => { + const e = en.reactNode; + let style = ''; + let tips = ''; + let style_alltext = ''; + if (en.enhancedValue) { + const data = en.enhancedValue; + console.log(data); + for (const dataElem of data) { + const { key, value } = dataElem; + switch (key) { + case 'style': + style = value; + break; + case 'tips': + tips = value; + break; + case 'style-alltext': + style_alltext = value; + break; + } + } + } // if (e === '
') { // return
; // } @@ -51,6 +74,8 @@ export default function IMSSTextbox(props: ITextboxProps) { if (currentConcatDialogPrev !== '' && index >= prevLength) { delay = delay - prevLength * textDelay; } + const styleClassName = ' ' + css(style); + const styleAllText = ' ' + css(style_alltext); if (index < prevLength) { return ( - + {e} - {e} - {isUseStroke && {e}} + {e} + {isUseStroke && {e}} ); } return ( - + {e} - {e} - {isUseStroke && {e}} + {e} + {isUseStroke && {e}} ); @@ -98,7 +123,6 @@ export default function IMSSTextbox(props: ITextboxProps) { ); }); - return ( <> {isText && ( diff --git a/packages/webgal/src/Stage/TextBox/TextBox.tsx b/packages/webgal/src/Stage/TextBox/TextBox.tsx index 37340ad54..f833fddec 100644 --- a/packages/webgal/src/Stage/TextBox/TextBox.tsx +++ b/packages/webgal/src/Stage/TextBox/TextBox.tsx @@ -13,6 +13,11 @@ const userAgent = navigator.userAgent; const isFirefox = /firefox/i.test(userAgent); const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent); +export interface EnhancedNode { + reactNode: ReactNode; + enhancedValue?: { key: string; value: string }[]; +} + export const TextBox = () => { const [isShowStroke, setIsShowStroke] = useState(true); @@ -93,28 +98,31 @@ function isCJK(character: string) { return !!character.match(/[\u4e00-\u9fa5]|[\u0800-\u4e00]|[\uac00-\ud7ff]/); } -export function compileSentence(sentence: string, lineLimit: number, ignoreLineLimit?: boolean): ReactNode[][] { +export function compileSentence(sentence: string, lineLimit: number, ignoreLineLimit?: boolean): EnhancedNode[][] { // 先拆行 const lines = sentence.split('|'); // 对每一行进行注音处理 const rubyLines = lines.map((line) => parseString(line)); const nodeLines = rubyLines.map((line) => { - const ln: ReactNode[] = []; + const ln: EnhancedNode[] = []; line.forEach((node, index) => { match(node.type) .with(SegmentType.String, () => { const chars = splitChars(node.value as string); - ln.push(...chars); + // eslint-disable-next-line max-nested-callbacks + ln.push(...chars.map((c) => ({ reactNode: c }))); }) .endsWith(SegmentType.Link, () => { - const val = node.value as LinkValue; - const rubyNode = ( - - {val.text} - {val.link} - + const val = node.value as EnhancedValue; + const enhancedNode = ( + + + {val.text} + {val.ruby} + + ); - ln.push(rubyNode); + ln.push({ reactNode: enhancedNode, enhancedValue: val.values }); }); }); return ln; @@ -199,14 +207,15 @@ enum SegmentType { Link = 'SegmentType.Link', } -interface LinkValue { +interface EnhancedValue { text: string; - link: string; + ruby: string; + values: { key: string; value: string }[]; } interface Segment { type: SegmentType; - value: string | LinkValue; + value?: string | EnhancedValue; } function parseString(input: string): Segment[] { @@ -218,8 +227,15 @@ function parseString(input: string): Segment[] { if (match[1]) { // 链接部分 const text = match[2]; - const link = match[3]; - result.push({ type: SegmentType.Link, value: { text, link } }); + const enhance = match[3]; + let parsedEnhanced: KeyValuePair[] = []; + let ruby = ''; + if (enhance.match(/style=|tips=|ruby=/)) { + parsedEnhanced = parseEnhancedString(enhance); + } else { + ruby = enhance; + } + result.push({ type: SegmentType.Link, value: { text, ruby, values: parsedEnhanced } }); } else { // 普通文本 const text = match[0]; @@ -229,3 +245,23 @@ function parseString(input: string): Segment[] { return result; } + +interface KeyValuePair { + key: string; + value: string; +} + +function parseEnhancedString(enhanced: string): KeyValuePair[] { + const result: KeyValuePair[] = []; + const regex = /(\S+)=(.*?)(?=\s+\S+=|\s*$)/g; + let match: RegExpExecArray | null; + + while ((match = regex.exec(enhanced)) !== null) { + result.push({ + key: match[1], + value: match[2].trim(), + }); + } + + return result; +} diff --git a/packages/webgal/src/Stage/TextBox/types.ts b/packages/webgal/src/Stage/TextBox/types.ts index 2a7b8c06d..58f2211de 100644 --- a/packages/webgal/src/Stage/TextBox/types.ts +++ b/packages/webgal/src/Stage/TextBox/types.ts @@ -1,7 +1,7 @@ -import { ReactNode } from 'react'; +import { EnhancedNode } from '@/Stage/TextBox/TextBox'; export interface ITextboxProps { - textArray: ReactNode[][]; + textArray: EnhancedNode[][]; textDelay: number; currentConcatDialogPrev: string; currentDialogKey: string; diff --git a/packages/webgal/src/UI/Backlog/Backlog.tsx b/packages/webgal/src/UI/Backlog/Backlog.tsx index 5b19a2460..3b9ba4c02 100644 --- a/packages/webgal/src/UI/Backlog/Backlog.tsx +++ b/packages/webgal/src/UI/Backlog/Backlog.tsx @@ -7,7 +7,7 @@ import { setVisibility } from '@/store/GUIReducer'; import { logger } from '@/Core/util/logger'; import { ReactNode, useEffect, useMemo, useRef, useState } from 'react'; import useTrans from '@/hooks/useTrans'; -import { compileSentence, splitChars } from '@/Stage/TextBox/TextBox'; +import { compileSentence, EnhancedNode, splitChars } from '@/Stage/TextBox/TextBox'; import useSoundEffect from '@/hooks/useSoundEffect'; import { WebGAL } from '@/Core/WebGAL'; @@ -28,7 +28,12 @@ export const Backlog = () => { for (let i = 0; i < WebGAL.backlogManager.getBacklog().length; i++) { const backlogItem = WebGAL.backlogManager.getBacklog()[i]; const showTextArray = compileSentence(backlogItem.currentStageState.showText, 3, true); - const showTextArrayReduced = mergeStringsAndKeepObjects(showTextArray); + const showTextArray2 = showTextArray.map((line) => { + return line.map((c) => { + return c.reactNode; + }); + }); + const showTextArrayReduced = mergeStringsAndKeepObjects(showTextArray2); const showTextElementList = showTextArrayReduced.map((line, index) => { return (
diff --git a/packages/webgal/src/config/info.ts b/packages/webgal/src/config/info.ts index 6219fb6b8..1c705a9d3 100644 --- a/packages/webgal/src/config/info.ts +++ b/packages/webgal/src/config/info.ts @@ -1,5 +1,5 @@ export const __INFO = { - version: 'WebGAL 4.5.2', + version: 'WebGAL 4.5.3', contributors: [ { username: 'Mahiru', link: 'https://github.com/MakinoharaShoko' }, { username: 'Hoshinokinya', link: 'https://github.com/hshqwq' }, diff --git a/releasenote.md b/releasenote.md index eec73f24e..c2d22a975 100644 --- a/releasenote.md +++ b/releasenote.md @@ -8,21 +8,15 @@ #### 新功能 -解析器增加前后空值剪切和;的转义 +为对话框中文本提供部分应用样式的支持 -在生产环境去除调试输出,提高性能 - -添加试验性快速预览 - -支持使用 Spine 做背景 +支持了选项界面的自定义 #### 修复 -UI 自定义可以支持小头像不存在的情况 - -提高唇形同步性能 +下雨特效的内存泄露 -提高日语翻译质量 +同时调用多个 callScene 或 changeScene 的冲突问题 ## Release Notes @@ -35,57 +29,39 @@ UI 自定义可以支持小头像不存在的情况 #### New Features -Parser adds trimming of leading and trailing whitespace and escaping of ; +Added support for partial style application for text in dialog boxes. -Remove debug prints in production builds, improving performance - -Add experimental fast preview - -Support using Spine for backgrounds +Added support for custom option interfaces. #### Fixes -UI customization can handle the case where small portraits are missing +Fixed a memory leak in the rain effect. -Improved performance of lip syncing +Fixed a conflict issue when calling multiple callScene or changeScene simultaneously. -Improved Japanese translation quality #### 新機能 -パーサーに前後空白トリムと;のエスケープを追加 - -本番環境でデバッグ出力を削除し、パフォーマンスを向上 +対話ボックスで一部のスタイルを適用できるようになった -試験的な高速プレビューを追加 - -Spine を背景として使用することをサポート +オプション画面のカスタマイズに対応した #### 修正 -UI カスタマイズでアバターが存在しない場合をサポート - -リップシンクのパフォーマンスを向上 +雨エフェクトのメモリリークを修正 -日本語翻訳の品質を向上 +複数の callScene または changeScene を同時に呼び出した際の競合の問題を修正 #### Nouvelles fonctionnalités -L'analyseur ajoute le rognage des espaces vides avant et après et l'échappement des ; - -Suppression de la sortie de débogage en environnement de production, amélioration des performances +Prise en charge partielle des styles CSS pour le texte des boîtes de dialogue -Ajout d'un aperçu rapide expérimental - -Prise en charge de Spine pour les arrière-plans +Personnalisation de l'interface des choix #### Corrections -La personnalisation de l'interface utilisateur peut prendre en charge les cas où il n'y a pas de petite icône - -Amélioration des performances de synchronisation labiale - -Amélioration de la qualité de la traduction japonaise +Fuite de mémoire avec l'effet de pluie +Conflit lors de l'appel simultané de plusieurs callScene ou changeScene diff --git a/yarn.lock b/yarn.lock index 59ecb8b07..89362fd36 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4255,10 +4255,10 @@ pixi-filters@^4.2.0: "@pixi/filter-twist" "4.2.0" "@pixi/filter-zoom-blur" "4.2.0" -pixi-live2d-display@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/pixi-live2d-display/-/pixi-live2d-display-0.4.0.tgz#f0628d1bb71765c042b85288344ef72d7b557fce" - integrity sha512-xeYC6y4Y0Bxe9ksWNlGFZC1rII/MPrzPQK7t1c3ubA8RhkOISIqHJl38fNumXqhGEs+yItmgDOkFT+9dsyGDjA== +pixi-live2d-display-webgal@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/pixi-live2d-display-webgal/-/pixi-live2d-display-webgal-0.5.2.tgz#a9153e39540a92293b91ccff8b106908415cfc52" + integrity sha512-XWI9Ev4OOB0cmve5RPdKP22LjjB7RdF2GkgRv55A3hEJgMvueuU4IZ5wBDuLYqHwiVxeWA/gTMycj7vyDCyn1g== dependencies: gh-pages "^4.0.0"