diff --git a/packages/webgal/src/Core/Modules/stage/stageInterface.ts b/packages/webgal/src/Core/Modules/stage/stageInterface.ts index 217362291..a19b5e15e 100644 --- a/packages/webgal/src/Core/Modules/stage/stageInterface.ts +++ b/packages/webgal/src/Core/Modules/stage/stageInterface.ts @@ -97,8 +97,49 @@ export const baseTransform: ITransform = { radiusAlphaFilter: 0, }; +/** + * 立绘的预设位置,left / right 为靠边定位,其余均为中心定位 + */ +export const FIGURE_POSITIONS = ['center', 'left', 'right', 'left13', 'right13', 'left14', 'right14'] as const; + +export type IFigurePosition = (typeof FIGURE_POSITIONS)[number]; + +export const FIGURE_KEYS = FIGURE_POSITIONS.map((position) => `fig-${position}`); + +export const figureStateKeyByPosition = { + center: 'figName', + left: 'figNameLeft', + right: 'figNameRight', + left13: 'figNameLeft13', + right13: 'figNameRight13', + left14: 'figNameLeft14', + right14: 'figNameRight14', +} as const satisfies Record; + +/** + * 计算立绘的基准 X 坐标 + */ +export function getFigureBaseX(position: IFigurePosition, stageWidth: number, targetWidth: number): number { + switch (position) { + case 'left': + return targetWidth / 2; + case 'right': + return stageWidth - targetWidth / 2; + case 'left13': + return stageWidth / 3; + case 'right13': + return (stageWidth * 2) / 3; + case 'left14': + return stageWidth / 4; + case 'right14': + return (stageWidth * 3) / 4; + default: + return stageWidth / 2; + } +} + export interface IFreeFigure { - basePosition: 'left' | 'center' | 'right'; + basePosition: IFigurePosition; name: string; key: string; } @@ -169,6 +210,10 @@ export interface IStageState { figName: string; // 立绘_中 文件地址(相对或绝对) figNameLeft: string; // 立绘_左 文件地址(相对或绝对) figNameRight: string; // 立绘_右 文件地址(相对或绝对) + figNameLeft13: string; // 立绘_左 1/3 文件地址(相对或绝对) + figNameRight13: string; // 立绘_右 1/3 文件地址(相对或绝对) + figNameLeft14: string; // 立绘_左 1/4 文件地址(相对或绝对) + figNameRight14: string; // 立绘_右 1/4 文件地址(相对或绝对) // 自由立绘 freeFigure: Array; figureAssociatedAnimation: Array; diff --git a/packages/webgal/src/Core/Modules/stage/stageStateManager.ts b/packages/webgal/src/Core/Modules/stage/stageStateManager.ts index b963d794c..8ce8b35a6 100644 --- a/packages/webgal/src/Core/Modules/stage/stageStateManager.ts +++ b/packages/webgal/src/Core/Modules/stage/stageStateManager.ts @@ -5,6 +5,7 @@ import { STAGE_KEYS } from '@/Core/constants'; import { baseBlinkParam, baseFocusParam } from '@/Core/live2DCore'; import { baseTransform, + FIGURE_KEYS, IEffect, IFigureMetadata, IFreeFigure, @@ -41,6 +42,10 @@ export const initState: IStageState = { figName: '', figNameLeft: '', figNameRight: '', + figNameLeft13: '', + figNameRight13: '', + figNameLeft14: '', + figNameRight14: '', freeFigure: [], figureAssociatedAnimation: [], isRead: false, @@ -143,9 +148,7 @@ export class StageStateManager { const activeTargets = [ STAGE_KEYS.STAGE_MAIN, STAGE_KEYS.BGMAIN, - STAGE_KEYS.FIG_C, - STAGE_KEYS.FIG_L, - STAGE_KEYS.FIG_R, + ...FIGURE_KEYS, ...state.freeFigure.map((figure) => figure.key), ]; if (!activeTargets.includes(target)) return; diff --git a/packages/webgal/src/Core/constants.ts b/packages/webgal/src/Core/constants.ts index c3c692857..a24cb2848 100644 --- a/packages/webgal/src/Core/constants.ts +++ b/packages/webgal/src/Core/constants.ts @@ -1,9 +1,6 @@ export const STAGE_KEYS = { STAGE_MAIN: 'stage-main', BGMAIN: 'bg-main', - FIG_C: 'fig-center', - FIG_L: 'fig-left', - FIG_R: 'fig-right', }; export const WEBGAL_NONE = 'none'; diff --git a/packages/webgal/src/Core/controller/stage/pixi/PixiController.ts b/packages/webgal/src/Core/controller/stage/pixi/PixiController.ts index da395ffcd..c2bef0054 100644 --- a/packages/webgal/src/Core/controller/stage/pixi/PixiController.ts +++ b/packages/webgal/src/Core/controller/stage/pixi/PixiController.ts @@ -1,4 +1,11 @@ -import { IEffect, IFigureAssociatedAnimation, IFigureMetadata, ITransform } from '@/Core/Modules/stage/stageInterface'; +import { + getFigureBaseX, + IEffect, + IFigureAssociatedAnimation, + IFigureMetadata, + IFigurePosition, + ITransform, +} from '@/Core/Modules/stage/stageInterface'; import { Live2D, WebGAL } from '@/Core/WebGAL'; import { baseBlinkParam, baseFocusParam, BlinkParam, FocusParam } from '@/Core/live2DCore'; import { isIOS } from '@/Core/initializeScript'; @@ -563,7 +570,7 @@ export default class PixiStage { * @param url 立绘图片url * @param presetPosition */ - public addFigure(key: string, url: string, presetPosition: 'left' | 'center' | 'right' = 'center') { + public addFigure(key: string, url: string, presetPosition: IFigurePosition = 'center') { const loader = this.assetLoader; // 准备用于存放这个立绘的 Container const thisFigureContainer = new WebGALPixiContainer(); @@ -623,15 +630,7 @@ export default class PixiStage { if (targetHeight < this.stageHeight) { thisFigureContainer.setBaseY(this.stageHeight / 2 + (this.stageHeight - targetHeight) / 2); } - if (presetPosition === 'center') { - thisFigureContainer.setBaseX(this.stageWidth / 2); - } - if (presetPosition === 'left') { - thisFigureContainer.setBaseX(targetWidth / 2); - } - if (presetPosition === 'right') { - thisFigureContainer.setBaseX(this.stageWidth - targetWidth / 2); - } + thisFigureContainer.setBaseX(getFigureBaseX(presetPosition, this.stageWidth, targetWidth)); thisFigureContainer.pivot.set(0, this.stageHeight / 2); thisFigureContainer.addChild(figureSprite); this.notifyTargetReferenceBoxChanged(key); @@ -657,7 +656,7 @@ export default class PixiStage { * @param jsonPath */ // eslint-disable-next-line max-params - public addLive2dFigure(key: string, jsonPath: string, pos: string) { + public addLive2dFigure(key: string, jsonPath: string, pos: IFigurePosition) { if (Live2D.isAvailable !== true) return; try { let stageWidth = this.stageWidth; @@ -741,13 +740,7 @@ export default class PixiStage { baseY = stageHeight / 2 + (stageHeight - targetHeight) / 2; } thisFigureContainer.setBaseY(baseY); - if (pos === 'center') { - thisFigureContainer.setBaseX(stageWidth / 2); - } else if (pos === 'left') { - thisFigureContainer.setBaseX(targetWidth / 2); - } else if (pos === 'right') { - thisFigureContainer.setBaseX(stageWidth - targetWidth / 2); - } + thisFigureContainer.setBaseX(getFigureBaseX(pos, stageWidth, targetWidth)); thisFigureContainer.pivot.set(0, stageHeight / 2); diff --git a/packages/webgal/src/Core/controller/stage/pixi/spine.ts b/packages/webgal/src/Core/controller/stage/pixi/spine.ts index 22cd07a2a..f1f34aa80 100644 --- a/packages/webgal/src/Core/controller/stage/pixi/spine.ts +++ b/packages/webgal/src/Core/controller/stage/pixi/spine.ts @@ -5,6 +5,7 @@ import * as PIXI from 'pixi.js'; import PixiStage from '@/Core/controller/stage/pixi/PixiController'; import { logger } from '@/Core/util/logger'; import { stageStateManager } from '@/Core/Modules/stage/stageStateManager'; +import { getFigureBaseX, IFigurePosition } from '@/Core/Modules/stage/stageInterface'; // utils/loadPixiSpine.ts // @ts-ignore let pixiSpineModule: typeof import('pixi-spine') | null = null; @@ -56,7 +57,7 @@ export async function addSpineFigureImpl( this: PixiStage, key: string, url: string, - presetPosition: 'left' | 'center' | 'right' = 'center', + presetPosition: IFigurePosition = 'center', ) { const spineId = `spine-${url}`; // 准备用于存放这个立绘的 Container @@ -161,15 +162,7 @@ export async function addSpineFigureImpl( if (targetHeight < this.stageHeight) { thisFigureContainer.setBaseY(this.stageHeight / 2 + (this.stageHeight - targetHeight) / 2); } - if (presetPosition === 'center') { - thisFigureContainer.setBaseX(this.stageWidth / 2); - } - if (presetPosition === 'left') { - thisFigureContainer.setBaseX(targetWidth / 2); - } - if (presetPosition === 'right') { - thisFigureContainer.setBaseX(this.stageWidth - targetWidth / 2); - } + thisFigureContainer.setBaseX(getFigureBaseX(presetPosition, this.stageWidth, targetWidth)); thisFigureContainer.pivot.set(0, this.stageHeight / 2); thisFigureContainer.addChild(figureSprite); 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 4046064bc..be97f163f 100644 --- a/packages/webgal/src/Core/controller/stage/pixi/syncPixiStageState.ts +++ b/packages/webgal/src/Core/controller/stage/pixi/syncPixiStageState.ts @@ -1,4 +1,5 @@ -import type { IEffect, IStageState, ITransform } from '@/Core/Modules/stage/stageInterface'; +import type { IEffect, IFigurePosition, IStageState, ITransform } from '@/Core/Modules/stage/stageInterface'; +import { FIGURE_KEYS, FIGURE_POSITIONS, figureStateKeyByPosition } from '@/Core/Modules/stage/stageInterface'; import type { IResolvedStageCommitOptions } from '@/Core/Modules/stage/stageStateManager'; import { DEFAULT_BG_OUT_DURATION } from '@/Core/constants'; import { WebGAL } from '@/Core/WebGAL'; @@ -11,7 +12,7 @@ import { applyTransformToPixiContainer } from '@/Core/controller/stage/pixi/stag interface ISyncFigureSlotPayload { key: string; sourceUrl: string; - position: 'left' | 'center' | 'right'; + position: IFigurePosition; stageState: IStageState; skipAnimation: boolean; } @@ -92,15 +93,15 @@ function syncBg(stageState: IStageState, skipAnimation: boolean) { } function syncFigures(stageState: IStageState, skipAnimation: boolean) { - syncFigureSlot({ key: 'fig-center', sourceUrl: stageState.figName, position: 'center', stageState, skipAnimation }); - syncFigureSlot({ key: 'fig-left', sourceUrl: stageState.figNameLeft, position: 'left', stageState, skipAnimation }); - syncFigureSlot({ - key: 'fig-right', - sourceUrl: stageState.figNameRight, - position: 'right', - stageState, - skipAnimation, - }); + for (const position of FIGURE_POSITIONS) { + syncFigureSlot({ + key: `fig-${position}`, + sourceUrl: stageState[figureStateKeyByPosition[position]], + position, + stageState, + skipAnimation, + }); + } for (const fig of stageState.freeFigure) { syncFigureSlot({ key: fig.key, sourceUrl: fig.name, position: fig.basePosition, stageState, skipAnimation }); @@ -110,12 +111,7 @@ function syncFigures(stageState: IStageState, skipAnimation: boolean) { if (!currentFigures) return; const freeFigureKeys = new Set(stageState.freeFigure.map((fig) => fig.key)); for (const existFigure of [...currentFigures]) { - if ( - existFigure.key === 'fig-left' || - existFigure.key === 'fig-center' || - existFigure.key === 'fig-right' || - existFigure.key.endsWith('-off') - ) { + if (FIGURE_KEYS.includes(existFigure.key) || existFigure.key.endsWith('-off')) { continue; } if (!freeFigureKeys.has(existFigure.key)) { @@ -130,7 +126,8 @@ function syncFigureSlot({ key, sourceUrl, position, stageState, skipAnimation }: const softInAniKey = `${key}-softin`; const currentFigure = pixiStage.getStageObjByKey(key); - if (sourceUrl !== '') { + // 旧存档中可能没有新增位置的字段,这里同时容错 undefined + if (sourceUrl) { if (currentFigure?.sourceUrl === sourceUrl) return; if (currentFigure) { removeFig(currentFigure, softInAniKey, { effects: stageState.effects, skipAnimation }); @@ -243,7 +240,7 @@ function addBg(key: string, url: string) { } } -function addFigure(key: string, url: string, position: 'left' | 'center' | 'right') { +function addFigure(key: string, url: string, position: IFigurePosition) { const pixiStage = WebGAL.gameplay.pixiStage; if (!pixiStage) return; const baseUrl = window.location.origin; diff --git a/packages/webgal/src/Core/gameScripts/changeFigure.ts b/packages/webgal/src/Core/gameScripts/changeFigure.ts index 54c07859d..559f3f641 100644 --- a/packages/webgal/src/Core/gameScripts/changeFigure.ts +++ b/packages/webgal/src/Core/gameScripts/changeFigure.ts @@ -1,8 +1,13 @@ import { ISentence } from '@/Core/controller/scene/sceneInterface'; import { IPerform } from '@/Core/Modules/perform/performInterface'; import cloneDeep from 'lodash/cloneDeep'; -import { getBooleanArgByKey, getNumberArgByKey, getStringArgByKey } from '@/Core/util/getSentenceArg'; -import { IFreeFigure, IStageState, ITransform } from '@/Core/Modules/stage/stageInterface'; +import { + getBooleanArgByKey, + getFigurePositionFromArgs, + getNumberArgByKey, + getStringArgByKey, +} from '@/Core/util/getSentenceArg'; +import { figureStateKeyByPosition, IFreeFigure } from '@/Core/Modules/stage/stageInterface'; import { AnimationFrame, IUserAnimation } from '@/Core/Modules/animations'; import { generateTransformAnimationObj } from '@/Core/controller/stage/pixi/animations/generateTransformAnimationObj'; import { assetSetter, fileType } from '@/Core/util/gameAssetsAccess/assetSetter'; @@ -29,21 +34,7 @@ export function changeFigure(sentence: ISentence): IPerform { } // 根据参数设置指定位置 - let pos: 'center' | 'left' | 'right' = 'center'; - let mouthAnimationKey = 'mouthAnimation'; - let eyesAnimationKey = 'blinkAnimation'; - const leftFromArgs = getBooleanArgByKey(sentence, 'left') ?? false; - const rightFromArgs = getBooleanArgByKey(sentence, 'right') ?? false; - if (leftFromArgs) { - pos = 'left'; - mouthAnimationKey = 'mouthAnimationLeft'; - eyesAnimationKey = 'blinkAnimationLeft'; - } - if (rightFromArgs) { - pos = 'right'; - mouthAnimationKey = 'mouthAnimationRight'; - eyesAnimationKey = 'blinkAnimationRight'; - } + const pos = getFigurePositionFromArgs(sentence) || 'center'; // id 与 自由立绘 let key = getStringArgByKey(sentence, 'id') ?? ''; @@ -127,22 +118,8 @@ export function changeFigure(sentence: ISentence): IPerform { isUrlChanged = false; } } - } else { - if (pos === 'center') { - if (stageStateManager.getCalculationStageState().figName === sentence.content) { - isUrlChanged = false; - } - } - if (pos === 'left') { - if (stageStateManager.getCalculationStageState().figNameLeft === sentence.content) { - isUrlChanged = false; - } - } - if (pos === 'right') { - if (stageStateManager.getCalculationStageState().figNameRight === sentence.content) { - isUrlChanged = false; - } - } + } else if (stageStateManager.getCalculationStageState()[figureStateKeyByPosition[pos]] === sentence.content) { + isUrlChanged = false; } /** * 处理 Effects @@ -265,21 +242,10 @@ export function changeFigure(sentence: ISentence): IPerform { /** * 下面的代码是设置与位置关联的立绘的 */ - const positionMap = { - center: 'fig-center', - left: 'fig-left', - right: 'fig-right', - }; - const dispatchMap: Record = { - center: 'figName', - left: 'figNameLeft', - right: 'figNameRight', - }; - - key = positionMap[pos]; + key = `fig-${pos}`; setAnimationNames(key, sentence); postFigureStateSet(); - stageStateManager.setStage(dispatchMap[pos], content); + stageStateManager.setStage(figureStateKeyByPosition[pos], content); } return { diff --git a/packages/webgal/src/Core/gameScripts/say.ts b/packages/webgal/src/Core/gameScripts/say.ts index 0319e8b68..aadaa00d9 100644 --- a/packages/webgal/src/Core/gameScripts/say.ts +++ b/packages/webgal/src/Core/gameScripts/say.ts @@ -4,7 +4,7 @@ import { playVocal } from './vocal'; import { webgalStore } from '@/store/store'; import { useTextAnimationDuration, useTextDelay } from '@/hooks/useTextOptions'; import { getRandomPerformName } from '@/Core/Modules/perform/performController'; -import { getBooleanArgByKey, getStringArgByKey } from '@/Core/util/getSentenceArg'; +import { getBooleanArgByKey, getFigurePositionFromArgs, getStringArgByKey } from '@/Core/util/getSentenceArg'; import { textSize, voiceOption } from '@/store/userDataInterface'; import { WebGAL } from '@/Core/WebGAL'; import { compileSentence } from '@/Stage/TextBox/TextBox'; @@ -89,13 +89,7 @@ export const say = (sentence: ISentence): IPerform => { // 模拟说话 let performSimulateVocalTimeout: ReturnType | null = null; - let pos: '' | 'center' | 'left' | 'right' = ''; - const leftFromArgs = getBooleanArgByKey(sentence, 'left') ?? false; - const rightFromArgs = getBooleanArgByKey(sentence, 'right') ?? false; - const centerFromArgs = getBooleanArgByKey(sentence, 'center') ?? false; - if (leftFromArgs) pos = 'left'; - if (rightFromArgs) pos = 'right'; - if (centerFromArgs) pos = 'center'; + const pos = getFigurePositionFromArgs(sentence); let key = getStringArgByKey(sentence, 'figureId') ?? ''; diff --git a/packages/webgal/src/Core/gameScripts/vocal/index.ts b/packages/webgal/src/Core/gameScripts/vocal/index.ts index a519f9914..2af8b5108 100644 --- a/packages/webgal/src/Core/gameScripts/vocal/index.ts +++ b/packages/webgal/src/Core/gameScripts/vocal/index.ts @@ -1,6 +1,6 @@ import { ISentence } from '@/Core/controller/scene/sceneInterface'; import { logger } from '@/Core/util/logger'; -import { getBooleanArgByKey, getNumberArgByKey, getStringArgByKey } from '@/Core/util/getSentenceArg'; +import { getFigurePositionFromArgs, getNumberArgByKey, getStringArgByKey } from '@/Core/util/getSentenceArg'; import { IStageState } from '@/Core/Modules/stage/stageInterface'; import { audioContextWrapper, @@ -29,11 +29,7 @@ export const playVocal = (sentence: ISentence) => { let currentStageState: IStageState; currentStageState = stageStateManager.getCalculationStageState(); - let pos: 'center' | 'left' | 'right' = 'center'; - const leftFromArgs = getBooleanArgByKey(sentence, 'left') ?? false; - const rightFromArgs = getBooleanArgByKey(sentence, 'right') ?? false; - if (leftFromArgs) pos = 'left'; - if (rightFromArgs) pos = 'right'; + let pos = getFigurePositionFromArgs(sentence) || 'center'; let key = getStringArgByKey(sentence, 'figureId') ?? ''; diff --git a/packages/webgal/src/Core/util/getSentenceArg.ts b/packages/webgal/src/Core/util/getSentenceArg.ts index 7d8ebf07c..629c9b9ab 100644 --- a/packages/webgal/src/Core/util/getSentenceArg.ts +++ b/packages/webgal/src/Core/util/getSentenceArg.ts @@ -1,4 +1,5 @@ import { ISentence } from '@/Core/controller/scene/sceneInterface'; +import { FIGURE_POSITIONS, IFigurePosition } from '@/Core/Modules/stage/stageInterface'; import { toSafeBoolean, toSafeNumber, toSafeString } from './toSafeType'; export function getSentenceArgByKey(sentence: ISentence, argKey: string): null | string | boolean | number { @@ -23,3 +24,10 @@ export function getStringArgByKey(sentence: ISentence, argKey: string): string | const argValue = getSentenceArgByKey(sentence, argKey); return toSafeString(argValue); } + +/** + * 从参数中获取立绘的预设位置,没有指定位置时返回空字符串 + */ +export function getFigurePositionFromArgs(sentence: ISentence): IFigurePosition | '' { + return FIGURE_POSITIONS.find((position) => getBooleanArgByKey(sentence, position)) ?? ''; +} diff --git a/packages/webgal/src/Core/util/syncWithEditor/runtime/targetTransformBaseline.ts b/packages/webgal/src/Core/util/syncWithEditor/runtime/targetTransformBaseline.ts index e703e72c0..219e84909 100644 --- a/packages/webgal/src/Core/util/syncWithEditor/runtime/targetTransformBaseline.ts +++ b/packages/webgal/src/Core/util/syncWithEditor/runtime/targetTransformBaseline.ts @@ -1,17 +1,11 @@ import cloneDeep from 'lodash/cloneDeep'; import { STAGE_KEYS } from '@/Core/constants'; -import { baseTransform } from '@/Core/Modules/stage/stageInterface'; +import { baseTransform, FIGURE_KEYS } from '@/Core/Modules/stage/stageInterface'; import type { IStageState, ITransform } from '@/Core/Modules/stage/stageInterface'; import type { TransformBaselineQueryResultPayload } from '@/types/editorPreviewProtocol'; import type { FastPreviewResult } from './previewSyncSceneCommand'; -const FIXED_TARGETS = new Set([ - STAGE_KEYS.STAGE_MAIN, - STAGE_KEYS.BGMAIN, - STAGE_KEYS.FIG_C, - STAGE_KEYS.FIG_L, - STAGE_KEYS.FIG_R, -]); +const FIXED_TARGETS = new Set([STAGE_KEYS.STAGE_MAIN, STAGE_KEYS.BGMAIN, ...FIGURE_KEYS]); type BaselineRevisionState = | {