diff --git a/packages/webgal/package.json b/packages/webgal/package.json index 4127c5380..6a62bf813 100644 --- a/packages/webgal/package.json +++ b/packages/webgal/package.json @@ -1,7 +1,7 @@ { "name": "webgal", "private": true, - "version": "4.5.7", + "version": "4.5.8", "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-webgal": "^0.5.2", + "pixi-live2d-display-webgal": "^0.5.8", "pixi-spine": "^3.1.2", "pixi.js": "^6.3.0", "popmotion": "^11.0.5", diff --git a/packages/webgal/public/game/template/template.json b/packages/webgal/public/game/template/template.json index ea42e4120..f0934b9a3 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.7" + "webgal-version":"4.5.8" } diff --git a/packages/webgal/src/Core/Modules/animationFunctions.ts b/packages/webgal/src/Core/Modules/animationFunctions.ts index 4c77e6e2b..e1cc14658 100644 --- a/packages/webgal/src/Core/Modules/animationFunctions.ts +++ b/packages/webgal/src/Core/Modules/animationFunctions.ts @@ -35,10 +35,12 @@ export function getAnimateDuration(animationName: string) { return 0; } +// eslint-disable-next-line max-params export function getEnterExitAnimation( target: string, type: 'enter' | 'exit', isBg = false, + realTarget?: string, // 用于立绘和背景移除时,以当前时间打上特殊标记 ): { duration: number; animation: { @@ -57,11 +59,11 @@ export function getEnterExitAnimation( setStartState: () => void; tickerFunc: (delta: number) => void; setEndState: () => void; - } | null = generateUniversalSoftInAnimationObj(target, duration); + } | null = generateUniversalSoftInAnimationObj(realTarget ?? target, duration); const animarionName = WebGAL.animationManager.nextEnterAnimationName.get(target); if (animarionName) { logger.debug('取代默认进入动画', target); - animation = getAnimationObject(animarionName, target, getAnimateDuration(animarionName)); + animation = getAnimationObject(animarionName, realTarget ?? target, getAnimateDuration(animarionName)); duration = getAnimateDuration(animarionName); // 用后重置 WebGAL.animationManager.nextEnterAnimationName.delete(target); @@ -77,11 +79,11 @@ export function getEnterExitAnimation( setStartState: () => void; tickerFunc: (delta: number) => void; setEndState: () => void; - } | null = generateUniversalSoftOffAnimationObj(target, duration); + } | null = generateUniversalSoftOffAnimationObj(realTarget ?? target, duration); const animarionName = WebGAL.animationManager.nextExitAnimationName.get(target); if (animarionName) { logger.debug('取代默认退出动画', target); - animation = getAnimationObject(animarionName, target, getAnimateDuration(animarionName)); + animation = getAnimationObject(animarionName, realTarget ?? target, getAnimateDuration(animarionName)); duration = getAnimateDuration(animarionName); // 用后重置 WebGAL.animationManager.nextExitAnimationName.delete(target); diff --git a/packages/webgal/src/Core/constants.ts b/packages/webgal/src/Core/constants.ts new file mode 100644 index 000000000..82132f84e --- /dev/null +++ b/packages/webgal/src/Core/constants.ts @@ -0,0 +1,6 @@ +export const STAGE_KEYS = { + BGMAIN: 'bg-main', + FIG_C: 'fig-center', + FIG_L: 'fig-left', + FIG_R: 'fig-right', +}; diff --git a/packages/webgal/src/Core/controller/stage/pixi/PixiController.ts b/packages/webgal/src/Core/controller/stage/pixi/PixiController.ts index 33b310834..1bd5a72a6 100644 --- a/packages/webgal/src/Core/controller/stage/pixi/PixiController.ts +++ b/packages/webgal/src/Core/controller/stage/pixi/PixiController.ts @@ -3,7 +3,7 @@ import { v4 as uuid } from 'uuid'; import { webgalStore } from '@/store/store'; import { setStage, stageActions } from '@/store/stageReducer'; import cloneDeep from 'lodash/cloneDeep'; -import { IEffect, IFigureAssociatedAnimation } from '@/store/stageInterface'; +import { IEffect, IFigureAssociatedAnimation, IFigureMetadata } from '@/store/stageInterface'; import { logger } from '@/Core/util/logger'; import { isIOS } from '@/Core/initializeScript'; import { WebGALPixiContainer } from '@/Core/controller/stage/pixi/WebGALPixiContainer'; @@ -12,7 +12,7 @@ import 'pixi-spine'; // Do this once at the very start of your code. This regist import { Spine } from 'pixi-spine'; import { SCREEN_CONSTANTS } from '@/Core/util/constants'; // import { figureCash } from '@/Core/gameScripts/vocal/conentsCash'; // 如果要使用 Live2D,取消这里的注释 -// import { Live2DModel, SoundManager } from 'pixi-live2d-display'; // 如果要使用 Live2D,取消这里的注释 +// import { Live2DModel, SoundManager } from 'pixi-live2d-display-webgal'; // 如果要使用 Live2D,取消这里的注释 export interface IAnimationObject { setStartState: Function; @@ -125,6 +125,7 @@ export default class PixiStage { this.effectsContainer = new PIXI.Container(); this.effectsContainer.zIndex = 3; this.figureContainer = new PIXI.Container(); + this.figureContainer.sortableChildren = true; // 允许立绘启用 z-index this.figureContainer.zIndex = 2; this.backgroundContainer = new PIXI.Container(); this.backgroundContainer.zIndex = 0; @@ -493,6 +494,12 @@ export default class PixiStage { this.removeStageObjectByKey(key); } + const metadata = this.getFigureMetadataByKey(key); + if (metadata) { + if (metadata.zIndex) { + thisFigureContainer.zIndex = metadata.zIndex; + } + } // 挂载 this.figureContainer.addChild(thisFigureContainer); const figureUuid = uuid(); @@ -578,6 +585,12 @@ export default class PixiStage { this.removeStageObjectByKey(key); } + const metadata = this.getFigureMetadataByKey(key); + if (metadata) { + if (metadata.zIndex) { + thisFigureContainer.zIndex = metadata.zIndex; + } + } // 挂载 this.figureContainer.addChild(thisFigureContainer); const figureUuid = uuid(); @@ -673,6 +686,12 @@ export default class PixiStage { // this.removeStageObjectByKey(key); // } // + // const metadata = this.getFigureMetadataByKey(key); + // if (metadata) { + // if (metadata.zIndex) { + // thisFigureContainer.zIndex = metadata.zIndex; + // } + // } // // 挂载 // this.figureContainer.addChild(thisFigureContainer); // this.figureObjects.push({ @@ -689,7 +708,23 @@ export default class PixiStage { // const setup = () => { // if (thisFigureContainer) { // (async function () { - // const models = await Promise.all([Live2DModel.from(jsonPath, { autoInteract: false })]); + // let overrideBounds: [number, number, number, number] = [0, 0, 0, 0]; + // const mot = webgalStore.getState().stage.live2dMotion.find((e) => e.target === key); + // if (mot?.overrideBounds) { + // overrideBounds = mot.overrideBounds; + // } + // console.log(overrideBounds); + // const models = await Promise.all([ + // Live2DModel.from(jsonPath, { + // autoInteract: false, + // overWriteBounds: { + // x0: overrideBounds[0], + // y0: overrideBounds[1], + // x1: overrideBounds[2], + // y1: overrideBounds[3], + // }, + // }), + // ]); // // models.forEach((model) => { // const scaleX = stageWidth / model.width; @@ -962,6 +997,11 @@ export default class PixiStage { private getExtName(url: string) { return url.split('.').pop() ?? 'png'; } + + private getFigureMetadataByKey(key: string): IFigureMetadata | undefined { + console.log(key, webgalStore.getState().stage.figureMetaData); + return webgalStore.getState().stage.figureMetaData[key]; + } } function updateCurrentBacklogEffects(newEffects: IEffect[]) { diff --git a/packages/webgal/src/Core/gameScripts/changeFigure.ts b/packages/webgal/src/Core/gameScripts/changeFigure.ts index a662159cd..bad8e7201 100644 --- a/packages/webgal/src/Core/gameScripts/changeFigure.ts +++ b/packages/webgal/src/Core/gameScripts/changeFigure.ts @@ -33,6 +33,8 @@ export function changeFigure(sentence: ISentence): IPerform { let animationFlag: any = ''; let mouthAnimationKey: any = 'mouthAnimation'; let eyesAnimationKey: any = 'blinkAnimation'; + let overrideBounds = ''; + let zIndex = -1; const dispatch = webgalStore.dispatch; for (const e of sentence.args) { @@ -63,6 +65,9 @@ export function changeFigure(sentence: ISentence): IPerform { case 'motion': motion = e.value.toString(); break; + case 'bounds': + overrideBounds = String(e.value); + break; case 'expression': expression = e.value.toString(); break; @@ -92,6 +97,9 @@ export function changeFigure(sentence: ISentence): IPerform { case 'none': content = ''; break; + case 'zIndex': + zIndex = Number(e.value); + break; default: break; } @@ -153,6 +161,9 @@ export function changeFigure(sentence: ISentence): IPerform { const deleteKey2 = `${key}`; webgalStore.dispatch(stageActions.removeEffectByTargetId(deleteKey)); webgalStore.dispatch(stageActions.removeEffectByTargetId(deleteKey2)); + // 重设 figureMetaData,这里是 zIndex,实际上任何键都可以,因为整体是移除那条记录 + dispatch(stageActions.setFigureMetaData([deleteKey, 'zIndex', 0, true])); + dispatch(stageActions.setFigureMetaData([deleteKey2, 'zIndex', 0, true])); } const setAnimationNames = (key: string, sentence: ISentence) => { // 处理 transform 和 默认 transform @@ -208,21 +219,27 @@ export function changeFigure(sentence: ISentence): IPerform { } }; if (isFreeFigure) { - const currentFreeFigures = webgalStore.getState().stage.freeFigure; - /** - * 重设 + * 下面的代码是设置自由立绘的 */ const freeFigureItem: IFreeFigure = { key, name: content, basePosition: pos }; setAnimationNames(key, sentence); - if (motion) { - dispatch(stageActions.setLive2dMotion({ target: key, motion })); + if (motion || overrideBounds) { + dispatch( + stageActions.setLive2dMotion({ target: key, motion, overrideBounds: getOverrideBoundsArr(overrideBounds) }), + ); } if (expression) { dispatch(stageActions.setLive2dExpression({ target: key, expression })); } + if (zIndex > 0) { + dispatch(stageActions.setFigureMetaData([key, 'zIndex', zIndex, false])); + } dispatch(stageActions.setFreeFigureByKey(freeFigureItem)); } else { + /** + * 下面的代码是设置与位置关联的立绘的 + */ const positionMap = { center: 'fig-center', left: 'fig-left', @@ -236,12 +253,17 @@ export function changeFigure(sentence: ISentence): IPerform { key = positionMap[pos]; setAnimationNames(key, sentence); - if (motion) { - dispatch(stageActions.setLive2dMotion({ target: key, motion })); + if (motion || overrideBounds) { + dispatch( + stageActions.setLive2dMotion({ target: key, motion, overrideBounds: getOverrideBoundsArr(overrideBounds) }), + ); } if (expression) { dispatch(stageActions.setLive2dExpression({ target: key, expression })); } + if (zIndex > 0) { + dispatch(stageActions.setFigureMetaData([key, 'zIndex', zIndex, false])); + } dispatch(setStage({ key: dispatchMap[pos], value: content })); } @@ -255,3 +277,16 @@ export function changeFigure(sentence: ISentence): IPerform { stopTimeout: undefined, // 暂时不用,后面会交给自动清除 }; } + +function getOverrideBoundsArr(raw: string): undefined | [number, number, number, number] { + const parseOverrideBoundsResult = raw.split(',').map((e) => Number(e)); + let isPass = true; + parseOverrideBoundsResult.forEach((e) => { + if (isNaN(e)) { + isPass = false; + } + }); + isPass = isPass && parseOverrideBoundsResult.length === 4; + if (isPass) return parseOverrideBoundsResult as [number, number, number, number]; + else return undefined; +} diff --git a/packages/webgal/src/Core/gameScripts/say.ts b/packages/webgal/src/Core/gameScripts/say.ts index 9048c4365..90a2eb7da 100644 --- a/packages/webgal/src/Core/gameScripts/say.ts +++ b/packages/webgal/src/Core/gameScripts/say.ts @@ -9,6 +9,8 @@ import { getSentenceArgByKey } from '@/Core/util/getSentenceArg'; import { textSize, voiceOption } from '@/store/userDataInterface'; import { WebGAL } from '@/Core/WebGAL'; import { compileSentence } from '@/Stage/TextBox/TextBox'; +import { performMouthAnimation } from '@/Core/gameScripts/vocal/vocalAnimation'; +import { match } from '@/Core/util/match'; /** * 进行普通对话的显示 @@ -84,9 +86,62 @@ export const say = (sentence: ISentence): IPerform => { } dispatch(setStage({ key: 'showName', value: showName })); + // 模拟说话 + let performSimulateVocalTimeout: ReturnType | null = null; + let performSimulateVocalDelay = 0; + let pos = ''; + let key = ''; + for (const e of sentence.args) { + if (e.value === true) { + match(e.key) + .with('left', () => { + pos = 'left'; + }) + .with('right', () => { + pos = 'right'; + }) + .endsWith('center', () => { + pos = 'center'; + }); + } + if (e.key === 'figureId') { + key = `${e.value.toString()}`; + } + } + let audioLevel = 80; + const performSimulateVocal = (end = false) => { + let nextAudioLevel = audioLevel + (Math.random() * 60 - 30); // 在 -30 到 +30 之间波动 + // 确保波动幅度不小于 5 + if (Math.abs(nextAudioLevel - audioLevel) < 5) { + nextAudioLevel = audioLevel + Math.sign(nextAudioLevel - audioLevel) * 5; + } + // 确保结果在 25 到 100 之间 + audioLevel = Math.max(15, Math.min(nextAudioLevel, 100)); + const currentStageState = webgalStore.getState().stage; + const figureAssociatedAnimation = currentStageState.figureAssociatedAnimation; + const animationItem = figureAssociatedAnimation.find((tid) => tid.targetId === key); + const targetKey = key ? key : `fig-${pos}`; + if (end) { + audioLevel = 0; + } + performMouthAnimation({ + audioLevel, + OPEN_THRESHOLD: 50, + HALF_OPEN_THRESHOLD: 25, + currentMouthValue: 0, + lerpSpeed: 1, + key: targetKey, + animationItem, + pos, + }); + if (!end) performSimulateVocalTimeout = setTimeout(performSimulateVocal, 50); + }; // 播放一段语音 if (vocal) { playVocal(sentence); + } else if (key || pos) { + performSimulateVocalDelay = len * 250; + performSimulateVocal(); } const performInitName: string = getRandomPerformName(); @@ -98,10 +153,14 @@ export const say = (sentence: ISentence): IPerform => { return { performName: performInitName, - duration: sentenceDelay + endDelay, + duration: sentenceDelay + endDelay + performSimulateVocalDelay, isHoldOn: false, stopFunction: () => { WebGAL.events.textSettle.emit(); + if (performSimulateVocalTimeout) { + performSimulateVocal(true); + clearTimeout(performSimulateVocalTimeout); + } }, blockingNext: () => false, blockingAuto: () => true, diff --git a/packages/webgal/src/Stage/MainStage/useSetBg.ts b/packages/webgal/src/Stage/MainStage/useSetBg.ts index 542ea447a..a292c0dd4 100644 --- a/packages/webgal/src/Stage/MainStage/useSetBg.ts +++ b/packages/webgal/src/Stage/MainStage/useSetBg.ts @@ -40,13 +40,15 @@ export function useSetBg(stageState: IStageState) { function removeBg(bgObject: IStageObject) { WebGAL.gameplay.pixiStage?.removeAnimationWithSetEffects('bg-main-softin'); const oldBgKey = bgObject.key; - bgObject.key = 'bg-main-off'; + bgObject.key = 'bg-main-off' + String(new Date().getTime()); + const bgKey = bgObject.key; + const bgAniKey = bgObject.key + '-softoff'; WebGAL.gameplay.pixiStage?.removeStageObjectByKey(oldBgKey); - const { duration, animation } = getEnterExitAnimation('bg-main-off', 'exit', true); - WebGAL.gameplay.pixiStage!.registerAnimation(animation, 'bg-main-softoff', 'bg-main-off'); + const { duration, animation } = getEnterExitAnimation('bg-main-off', 'exit', true, bgKey); + WebGAL.gameplay.pixiStage!.registerAnimation(animation, bgAniKey, bgKey); setTimeout(() => { - WebGAL.gameplay.pixiStage?.removeAnimation('bg-main-softoff'); - WebGAL.gameplay.pixiStage?.removeStageObjectByKey('bg-main-off'); + WebGAL.gameplay.pixiStage?.removeAnimation(bgAniKey); + WebGAL.gameplay.pixiStage?.removeStageObjectByKey(bgKey); }, duration); } diff --git a/packages/webgal/src/Stage/MainStage/useSetFigure.ts b/packages/webgal/src/Stage/MainStage/useSetFigure.ts index 9f9d91c47..425323a93 100644 --- a/packages/webgal/src/Stage/MainStage/useSetFigure.ts +++ b/packages/webgal/src/Stage/MainStage/useSetFigure.ts @@ -192,11 +192,12 @@ function removeFig(figObj: IStageObject, enterTikerKey: string, effects: IEffect return; } const oldFigKey = figObj.key; - figObj.key = figObj.key + '-off'; - WebGAL.gameplay.pixiStage?.removeStageObjectByKey(oldFigKey); + const figLeaveAniKey = oldFigKey + '-off'; + figObj.key = oldFigKey + String(new Date().getTime()) + '-off'; const figKey = figObj.key; + WebGAL.gameplay.pixiStage?.removeStageObjectByKey(oldFigKey); const leaveKey = figKey + '-softoff'; - const { duration, animation } = getEnterExitAnimation(figKey, 'exit'); + const { duration, animation } = getEnterExitAnimation(figLeaveAniKey, 'exit', false, figKey); WebGAL.gameplay.pixiStage!.registerPresetAnimation(animation, leaveKey, figKey, effects); setTimeout(() => { WebGAL.gameplay.pixiStage?.removeAnimation(leaveKey); diff --git a/packages/webgal/src/Stage/TextBox/IMSSTextbox.tsx b/packages/webgal/src/Stage/TextBox/IMSSTextbox.tsx index b6179a4a0..9381649cc 100644 --- a/packages/webgal/src/Stage/TextBox/IMSSTextbox.tsx +++ b/packages/webgal/src/Stage/TextBox/IMSSTextbox.tsx @@ -4,6 +4,7 @@ import { WebGAL } from '@/Core/WebGAL'; import { ITextboxProps } from './types'; import useApplyStyle from '@/hooks/useApplyStyle'; import { css } from '@emotion/css'; +import { textSize } from '@/store/userDataInterface'; export default function IMSSTextbox(props: ITextboxProps) { const { @@ -16,11 +17,13 @@ export default function IMSSTextbox(props: ITextboxProps) { isFirefox: boolean, fontSize, miniAvatar, + isHasName, showName, font, textDuration, isUseStroke, textboxOpacity, + textSizeState, } = props; const applyStyle = useApplyStyle('Stage/TextBox/textbox.scss'); @@ -39,8 +42,68 @@ export default function IMSSTextbox(props: ITextboxProps) { WebGAL.events.textSettle.off(settleText); }; }, []); - let allTextIndex = 0; + const nameElementList = showName.map((line, index) => { + const textline = line.map((en, index) => { + const e = en.reactNode; + let style = ''; + let tips = ''; + let style_alltext = ''; + let isEnhanced = false; + if (en.enhancedValue) { + isEnhanced = true; + const data = en.enhancedValue; + 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; + } + } + } + const styleClassName = ' ' + css(style, { label: 'showname' }); + const styleAllText = ' ' + css(style_alltext, { label: 'showname' }); + if (isEnhanced) { + return ( + + + {e} + {e} + {isUseStroke && {e}} + + + ); + } + return ( + + + {e} + {e} + {isUseStroke && {e}} + + + ); + }); + return ( +
+ {textline} +
+ ); + }); const textElementList = textArray.map((line, index) => { const textLine = line.map((en, index) => { const e = en.reactNode; @@ -49,7 +112,6 @@ export default function IMSSTextbox(props: ITextboxProps) { let style_alltext = ''; if (en.enhancedValue) { const data = en.enhancedValue; - console.log(data); for (const dataElem of data) { const { key, value } = dataElem; switch (key) { @@ -159,7 +221,7 @@ export default function IMSSTextbox(props: ITextboxProps) { miniAvatar )} - {showName !== '' && ( + {isHasName && ( <>
-
- {showName.split('').map((e, i) => { - return ( - - - {e} - {e} - {isUseStroke && {e}} - - - ); - })} -
+ {nameElementList}
- {showName.split('').map((e, i) => { - return ( - - - {e} - {e} - {isUseStroke && {e}} - - - ); - })} + {nameElementList}
)} @@ -214,6 +253,7 @@ export default function IMSSTextbox(props: ITextboxProps) { flexFlow: 'column', overflow: 'hidden', paddingLeft: '0.1em', + lineHeight: textSizeState === textSize.medium ? '2.2em' : '2em', // 不加的话上半拼音可能会被截断,同时保持排版整齐 }} > {textElementList} diff --git a/packages/webgal/src/Stage/TextBox/TextBox.tsx b/packages/webgal/src/Stage/TextBox/TextBox.tsx index fb349516d..9e33b072d 100644 --- a/packages/webgal/src/Stage/TextBox/TextBox.tsx +++ b/packages/webgal/src/Stage/TextBox/TextBox.tsx @@ -60,14 +60,15 @@ export const TextBox = () => { size = getTextSize(stageState.showTextSize) + '%'; textSizeState = stageState.showTextSize; } - const lineLimit = match(userDataState.optionData.textSize) + const lineLimit = match(textSizeState) .with(textSize.small, () => 3) .with(textSize.medium, () => 2) .with(textSize.large, () => 2) .default(() => 2); // 拆字 const textArray = compileSentence(stageState.showText, lineLimit); - const showName = stageState.showName; + const isHasName = stageState.showName !== ''; + const showName = compileSentence(stageState.showName, lineLimit); const currentConcatDialogPrev = stageState.currentConcatDialogPrev; const currentDialogKey = stageState.currentDialogKey; const miniAvatar = stageState.miniAvatar; @@ -79,6 +80,7 @@ export const TextBox = () => { isText={isText} textDelay={textDelay} showName={showName} + isHasName={isHasName} currentConcatDialogPrev={currentConcatDialogPrev} fontSize={size} currentDialogKey={currentDialogKey} @@ -135,7 +137,7 @@ export function compileSentence(sentence: string, lineLimit: number, ignoreLineL * @param sentence */ export function splitChars(sentence: string) { - if (!sentence) return []; + if (!sentence) return ['']; const words: string[] = []; let word = ''; let cjkFlag = isCJK(sentence[0]); @@ -248,6 +250,9 @@ function parseString(input: string): Segment[] { } } + // 我也不知道为什么,不加这个就会导致在 Enhanced Value 处于行首时故障 + // 你可以认为这个代码不明所以,但是不要删除 + result.unshift({ type: SegmentType.String, value: '' }); return result; } @@ -264,7 +269,7 @@ function parseEnhancedString(enhanced: string): KeyValuePair[] { while ((match = regex.exec(enhanced)) !== null) { result.push({ key: match[1], - value: match[2].trim(), + value: match[2].replace(/~/g, ':').trim(), }); } diff --git a/packages/webgal/src/Stage/TextBox/legacy-themes/legacy-standard/StandardTextbox.tsx b/packages/webgal/src/Stage/TextBox/legacy-themes/legacy-standard/StandardTextbox.tsx index 33ca4f88b..465b918bb 100644 --- a/packages/webgal/src/Stage/TextBox/legacy-themes/legacy-standard/StandardTextbox.tsx +++ b/packages/webgal/src/Stage/TextBox/legacy-themes/legacy-standard/StandardTextbox.tsx @@ -15,6 +15,7 @@ export default function StandardTextbox(props: ITextboxProps) { isFirefox, fontSize, miniAvatar, + isHasName, showName, font, textDuration, @@ -38,7 +39,25 @@ export default function StandardTextbox(props: ITextboxProps) { WebGAL.events.textSettle.off(settleText); }; }, []); - + const nameElementList = showName.map((e,index)=>{ + let prevLength = currentConcatDialogPrev.length; + if (index < prevLength) { + return ( + + {e} + {e} + {isUseStroke && {e}} + + ); + } + return ( + + {e} + {e} + {isUseStroke && {e}} + + ); + }); const textElementList = textArray.map((e, index) => { // if (e === '
') { // return
; @@ -83,7 +102,6 @@ export default function StandardTextbox(props: ITextboxProps) { }); const padding = isHasMiniAvatar ? 500 : undefined; - const isHasName = showName !== ''; let paddingTop = isHasName ? undefined : 15; if (textSizeState === textSize.small && !isHasName) { paddingTop = 35; @@ -114,19 +132,9 @@ export default function StandardTextbox(props: ITextboxProps) {
{miniAvatar !== '' && miniAvatar}
- {showName !== '' && ( -
- {showName.split('').map((e, i) => { - return ( - - - {e} - {e} - {isUseStroke && {e}} - - - ); - })} + {isHasName && ( +
+ {nameElementList}
)}
{
); }); + const showNameArray = compileSentence(backlogItem.currentStageState.showName, 3, true); + const showNameArray2 = showNameArray.map((line)=>{ + return line.map((c) => { + return c.reactNode; + }); + }); + const showNameArrayReduced = mergeStringsAndKeepObjects(showNameArray2); + const nameElementList = showNameArrayReduced.map((line,index)=>{ + return ( +
+ {line.map((e, index) => { + if (e === '
') { + return
; + } else { + return e; + } + })} +
+ ); + }); const singleBacklogView = (
{
) : null}
-
{backlogItem.currentStageState.showName}
+
{nameElementList}
{showTextElementList} diff --git a/packages/webgal/src/UI/Menu/Options/TextPreview/TextPreview.tsx b/packages/webgal/src/UI/Menu/Options/TextPreview/TextPreview.tsx index d6d9e0459..4e8bbc4d3 100644 --- a/packages/webgal/src/UI/Menu/Options/TextPreview/TextPreview.tsx +++ b/packages/webgal/src/UI/Menu/Options/TextPreview/TextPreview.tsx @@ -23,6 +23,9 @@ export const TextPreview = (props: any) => { const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent); const previewText = t('textPreview.text'); const previewTextArray = compileSentence(previewText, 3); + const showNameText = t('textPreview.title'); + const showNameArray = compileSentence(showNameText, 3); + const isHasName = showNameText !== ''; const Textbox = IMSSTextbox; @@ -30,7 +33,8 @@ export const TextPreview = (props: any) => { textArray: previewTextArray, isText: true, textDelay: textDelay, - showName: t('textPreview.title'), + isHasName: isHasName, + showName: showNameArray, currentConcatDialogPrev: '', fontSize: size, currentDialogKey: '', diff --git a/packages/webgal/src/UI/getTextSize.ts b/packages/webgal/src/UI/getTextSize.ts index bdad545c2..85824bc0b 100644 --- a/packages/webgal/src/UI/getTextSize.ts +++ b/packages/webgal/src/UI/getTextSize.ts @@ -1,11 +1,11 @@ export function getTextSize(size: number) { switch (size) { case 0: - return 150; + return 155; case 1: return 205; case 2: - return 240; + return 230; default: return 205; } diff --git a/packages/webgal/src/config/info.ts b/packages/webgal/src/config/info.ts index dc89641af..f3171e664 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.7', + version: 'WebGAL 4.5.8', contributors: [ // 现在改为跳转到 GitHub 了 ], diff --git a/packages/webgal/src/store/stageInterface.ts b/packages/webgal/src/store/stageInterface.ts index 51273975c..e1f7633b7 100644 --- a/packages/webgal/src/store/stageInterface.ts +++ b/packages/webgal/src/store/stageInterface.ts @@ -108,6 +108,7 @@ export interface IRunPerform { export interface ILive2DMotion { target: string; motion: string; + overrideBounds?: [number, number, number, number]; } export interface ILive2DExpression { @@ -115,6 +116,12 @@ export interface ILive2DExpression { expression: string; } +export interface IFigureMetadata { + zIndex?: number; +} + +type figureMetaData = Record; + /** * @interface IStageState 游戏舞台数据接口 */ @@ -158,6 +165,7 @@ export interface IStageState { enableFilm: string; isDisableTextbox: boolean; replacedUIlable: Record; + figureMetaData: figureMetaData; } /** diff --git a/packages/webgal/src/store/stageReducer.ts b/packages/webgal/src/store/stageReducer.ts index 37c9abb1a..964f2a6b3 100644 --- a/packages/webgal/src/store/stageReducer.ts +++ b/packages/webgal/src/store/stageReducer.ts @@ -5,6 +5,7 @@ import { IEffect, + IFigureMetadata, IFreeFigure, ILive2DExpression, ILive2DMotion, @@ -16,6 +17,7 @@ import { import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import cloneDeep from 'lodash/cloneDeep'; import { commandType } from '@/Core/controller/scene/sceneInterface'; +import { STAGE_KEYS } from '@/Core/constants'; // 初始化舞台数据 @@ -56,6 +58,7 @@ export const initState: IStageState = { enableFilm: '', isDisableTextbox: false, replacedUIlable: {}, + figureMetaData: {}, }; /** @@ -92,6 +95,15 @@ const stageSlice = createSlice({ }, updateEffect: (state, action: PayloadAction) => { const { target, transform } = action.payload; + // 如果找不到目标,不能设置 transform + const activeTargets = [ + STAGE_KEYS.BGMAIN, + STAGE_KEYS.FIG_C, + STAGE_KEYS.FIG_L, + STAGE_KEYS.FIG_R, + ...state.freeFigure.map((figure) => figure.key), + ]; + if (!activeTargets.includes(target)) return; // 尝试找到待修改的 Effect const effectIndex = state.effects.findIndex((e) => e.target === target); if (effectIndex >= 0) { @@ -154,16 +166,17 @@ const stageSlice = createSlice({ } }, setLive2dMotion: (state, action: PayloadAction) => { - const { target, motion } = action.payload; + const { target, motion, overrideBounds } = action.payload; const index = state.live2dMotion.findIndex((e) => e.target === target); if (index < 0) { // Add a new motion - state.live2dMotion.push({ target, motion }); + state.live2dMotion.push({ target, motion, overrideBounds }); } else { // Update the existing motion state.live2dMotion[index].motion = motion; + state.live2dMotion[index].overrideBounds = overrideBounds; } }, setLive2dExpression: (state, action: PayloadAction) => { @@ -182,6 +195,24 @@ const stageSlice = createSlice({ replaceUIlable: (state, action: PayloadAction<[string, string]>) => { state.replacedUIlable[action.payload[0]] = action.payload[1]; }, + /** + * 设置 figure 元数据 [立绘 key, metadata key, 值, 是否重设] + * @param state + * @param action + */ + setFigureMetaData: (state, action: PayloadAction<[string, keyof IFigureMetadata, any, undefined | boolean]>) => { + // 立绘退出,重设 + if (action.payload[3]) { + if (state.figureMetaData[action.payload[0]]) delete state.figureMetaData[action.payload[0]]; + } else { + console.log('yeah'); + // 初始化对象 + if (!state.figureMetaData[action.payload[0]]) { + state.figureMetaData[action.payload[0]] = {}; + } + state.figureMetaData[action.payload[0]][action.payload[1]] = action.payload[2]; + } + }, }, }); diff --git a/packages/webgal/vite.config.ts b/packages/webgal/vite.config.ts index 725a227b0..0fb7f4bbe 100644 --- a/packages/webgal/vite.config.ts +++ b/packages/webgal/vite.config.ts @@ -58,6 +58,6 @@ export default defineConfig({ }, }, build: { - sourcemap: true, + // sourcemap: true, }, }); diff --git a/releasenote.md b/releasenote.md index 6a9a3ac66..10bcad7e3 100644 --- a/releasenote.md +++ b/releasenote.md @@ -8,36 +8,21 @@ #### 新功能 -新增参数,用于控制 “在本条语句的演出结束后,执行下一条”。 +文本拓展语法对角色名称生效 -#### 修复 - -4.5.7 修复:无法从状态中获取到对应 key 的变量时,返回 {key} 以避免变换不生效 - -修复了 `-concat` 选项动画在新行中的错误。 - -修复了样式定义时 ruby 不显示的问题。 - -修复了 intro 的 hold 问题。 - -修复了 say 延迟计算问题。 - -修复了 restoreScene 中的竞争状态。 +模拟口型同步 -修复了删除已关闭的自由图形的问题。 +允许修改 Live2D 绘制范围 -修复了变量值处理的问题。 +允许设定立绘的 z-index -修复了 setVar 进行 compile 字符串时异常的问题。 - -增强了 getValueFromState。 - -调整了对 `style-alltext` 键的正则匹配,使其可以触发文本框的样式修改。 +#### 修复 -修复了变量正则表达式的问题。 +修复了文本增强语法在首行不生效的问题 -修复了 logo 图片的问题。 +优化为立绘应用效果的性能 +优化立绘进出场效果的性能 ## Release Notes @@ -50,36 +35,21 @@ #### New Features -Added a new parameter to control "execute the next statement after the performance of this statement is finished". - -#### Bug Fixes - -4.5.7 Fix: When the corresponding key variable cannot be obtained from the state, return {key} to avoid ineffective transformation. - -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. +Text extension syntax now affects character names -Fixed the hold problem of intro. +Simulate lip sync -Fixed the say delay calculation problem. +Allow modification of Live2D drawing range -Fixed a race condition in restoreScene. +Allow setting the z-index of the character sprite -Fixed an issue with deleting closed free figures. +#### Fixes -Fixed the variable value handling problem. +Fixed the issue where text enhancement syntax did not take effect on the first line -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. +Optimized the performance of applying effects to character sprites +Optimized the performance of character sprite entry and exit effects ## リリースノート @@ -92,32 +62,18 @@ Fixed the logo image issue. #### 新機能 -「このステートメントの演出終了後に次のステートメントを実行する」を制御するための新しいパラメータが追加されました。 - -#### 修正 - -4.5.7 修正:状態から対応する key の変数が取得できない場合、{key} を返すようにし、変換が無効になるのを回避しました +テキスト拡張文法がキャラクタ名に有効になった -`-concat` オプションのアニメーションが新しい行でエラーになる問題を修正しました。 +口パク同期 -スタイルが定義されているときにルビが表示されない問題を修正しました。 +Live2D の描画範囲変更が可能になった -イントロのホールド問題を修正しました。 +立ち絵の z-index 設定が可能になった -say 遅延計算の問題を修正しました。 - -restoreScene の競合状態を修正しました。 - -閉じたフリーフィギュアを削除する際の問題を修正しました。 - -変数値処理の問題を修正しました。 - -setVar が文字列をコンパイルする際の例外を修正しました。 - -getValueFromState を強化しました。 +#### 修正 -`style-alltext` キーの正規表現マッチングを調整し、テキストボックスのスタイル変更をトリガーできるようにしました。 +テキスト拡張文法が先頭行に有効にならない問題を修正 -変数正規表現の問題を修正しました。 +立ち絵にエフェクトを適用する時のパフォーマンスを最適化 -ロゴ画像の問題を修正しました。 +立ち絵の登場・退場エフェクトのパフォーマンスを最適化 diff --git a/yarn.lock b/yarn.lock index 3e78f0d07..ef0adc787 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4274,10 +4274,10 @@ pixi-filters@^4.2.0: "@pixi/filter-twist" "4.2.0" "@pixi/filter-zoom-blur" "4.2.0" -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== +pixi-live2d-display-webgal@^0.5.8: + version "0.5.8" + resolved "https://registry.yarnpkg.com/pixi-live2d-display-webgal/-/pixi-live2d-display-webgal-0.5.8.tgz#1ff7b5016559ff56a5cfaa96abeac84c878a2bbf" + integrity sha512-yekmPckXykqziyFcmmgYlZU4ad42rRGGmUFvrm96YJrdRIi+V9uv/xKOrRBMxD/tr3Sckqp6hOr9Wi3UX6NNLw== dependencies: gh-pages "^4.0.0"