diff --git a/packages/webgal/public/game/scene/demo_parallel_animation.txt b/packages/webgal/public/game/scene/demo_parallel_animation.txt new file mode 100644 index 000000000..796ac524b --- /dev/null +++ b/packages/webgal/public/game/scene/demo_parallel_animation.txt @@ -0,0 +1,31 @@ +changeBg:WebGalEnter.webp -next; +changeFigure:stand.webp -id=figure01 -transform={"position":{"x":1000,"y":720}}; +;演示setAnimation平行执行 +setAnimation:shockwaveIn -target=figure01 -next +setAnimation:move-front-and-back -target=figure01 -parallel +;演示通过-continue接续执行两个常规setTransform正常运作、不被打断 +setTransform:{"position":{"x":-1000}} -duration=5000 -target=figure01 -continue +setTransform:{"position":{"x":1000}} -duration=5000 -target=figure01 +;演示setTransform平行执行 +setTransform:{"position":{"x":-1000},"scale":{"x":0.5},"contrast":0.5} -duration=5000 -target=figure01 -ease=easeOut -next -keep +wait:2000 +setTransform:{"position":{"y":0},"scale":{"y":0.5},"saturation":0} -duration=5000 -target=figure01 -ease=linear -parallel -continue +setTransform:{"position":{"y":-720},"scale":{"y":1},"saturation":1} -duration=5000 -target=figure01 -ease=linear -next +setTransform:{"position":{"x":1000},"scale":{"x":1},"contrast":1} -duration=5000 -target=figure01 -ease=easeIn -parallel; +;演示参数解耦改动后setTempAnimation普通运作是否正常 +setTempAnimation:[{"duration":0}, {"duration":500,"position":{"x":-1000}}, {"duration":500,"position":{"y":720},"scale":{"y":0.5},"saturation":0}, {"duration":500,"position":{"x":-1000, "y":720}}, {"duration":500}, {"duration":500,"position":{"x":-1000},"scale":{"x":0.5},"contrast":0.5}] -target=figure01 +setTempAnimation:[{"duration":0}, {"duration":500,"position":{"x":1000}}, {"duration":500,"position":{"y":720}}, {"duration":500,"position":{"x":1000, "y":720}}, {"duration":500}, {"duration":500,"position":{"x":1000}}] -target=figure01 +;演示参数解耦改动后setTransform的-writeDefault参数是否运作正常 +setTransform:{} -writeDefault -target=figure01 -duration=500 +setTransform:{"position":{"x":1000,"y":720}} -target=figure01 -next; +setAnimation:shockwaveOut -target=figure01 -parallel +;演示setTempAnimation平行执行 +setTempAnimation:[{"duration":0},{"position":{"x":-1000},"scale":{"x":0.5},"contrast":0.5,"duration":5000,"ease":"easeOut"},{"position":{"x":-1000},"scale":{"x":0.5},"contrast":0.5,"duration":2000},{"position":{"x":600},"scale":{"x":1},"contrast":1,"duration":5000,"ease":"easeIn"},{"duration":2000}] -target=figure01 -next; +setTempAnimation:[{"duration":2000},{"position":{"y":0},"scale":{"y":0.5},"saturation":0,"duration":5000,"ease":"linear"},{"position":{"y":-720},"scale":{"y":1},"saturation":1,"duration":5000,"ease":"linear"},{"duration":2000}] -target=figure01 -parallel -continue; +;演示并行执行多条终止时间点不一致的setTransform +setTransform:{"position":{"x":-1000}} -duration=5000 -next -target=figure01; +setTransform:{"position":{"y":0}} -duration=3000 -parallel -target=figure01; +setTransform:{"position":{"x":1000}} -duration=3000 -next -target=figure01; +setTransform:{"position":{"y":-720}} -duration=5000 -parallel -target=figure01; +changeBg: -next; +changeFigure: -id=figure01 diff --git a/packages/webgal/src/Core/Modules/animationFunctions.ts b/packages/webgal/src/Core/Modules/animationFunctions.ts index 1c5fa4471..026b96014 100644 --- a/packages/webgal/src/Core/Modules/animationFunctions.ts +++ b/packages/webgal/src/Core/Modules/animationFunctions.ts @@ -7,6 +7,8 @@ import { baseTransform } from '@/store/stageInterface'; import { generateTimelineObj } from '@/Core/controller/stage/pixi/animations/timeline'; import { WebGAL } from '@/Core/WebGAL'; import PixiStage, { IAnimationObject } from '@/Core/controller/stage/pixi/PixiController'; +import { IUserAnimation } from './animations'; +import { pickBy } from 'lodash'; import { DEFAULT_BG_IN_DURATION, DEFAULT_BG_OUT_DURATION, @@ -18,12 +20,25 @@ import { export function getAnimationObject(animationName: string, target: string, duration: number, writeDefault: boolean) { const effect = WebGAL.animationManager.getAnimations().find((ani) => ani.name === animationName); if (effect) { + const unionKeys = new Set(); + const unionScaleKeys = new Set(); + const unionPositionKeys = new Set(); + effect.effects.forEach((effect) => { + Object.keys(effect).forEach((k) => unionKeys.add(k)); + if (effect.scale) Object.keys(effect.scale).forEach((k) => unionScaleKeys.add(k)); + if (effect.position) Object.keys(effect.position).forEach((k) => unionPositionKeys.add(k)); + }); const mappedEffects = effect.effects.map((effect) => { const targetSetEffect = webgalStore.getState().stage.effects.find((e) => e.target === target); let newEffect; if (!writeDefault && targetSetEffect && targetSetEffect.transform) { - newEffect = cloneDeep({ ...targetSetEffect.transform, duration: 0, ease: '' }); + const targetScale = pickBy(targetSetEffect.transform.scale || {}, (source, key) => unionScaleKeys.has(key)); + const targetPosition = pickBy(targetSetEffect.transform.position || {}, (s, key) => unionPositionKeys.has(key)); + const originalTransform = { ...pickBy(targetSetEffect.transform, (source, key) => unionKeys.has(key)) }; + originalTransform.scale = targetScale; + originalTransform.position = targetPosition; + newEffect = cloneDeep({ ...originalTransform, duration: 0, ease: '' }); } else { newEffect = cloneDeep({ ...baseTransform, duration: 0, ease: '' }); } diff --git a/packages/webgal/src/Core/Modules/perform/performController.ts b/packages/webgal/src/Core/Modules/perform/performController.ts index f303a64b8..e3569fdd2 100644 --- a/packages/webgal/src/Core/Modules/perform/performController.ts +++ b/packages/webgal/src/Core/Modules/perform/performController.ts @@ -19,17 +19,19 @@ export class PerformController { public performList: Array = []; public arrangeNewPerform(perform: IPerform, script: ISentence, syncPerformState = true) { - // 检查演出列表内是否有相同的演出,如果有,一定是出了什么问题 - const dupPerformIndex = this.performList.findIndex((p) => p.performName === perform.performName); - if (dupPerformIndex > -1) { - // 结束并删除全部重复演出 - for (let i = 0; i < this.performList.length; i++) { - const e = this.performList[i]; - if (e.performName === perform.performName) { - e.stopFunction(); - clearTimeout(e.stopTimeout as unknown as number); - this.performList.splice(i, 1); - i--; + if (!perform.isParallel) { + // 检查演出列表内是否有相同的演出,如果有,一定是出了什么问题 + const dupPerformIndex = this.performList.findIndex((p) => p.performName === perform.performName); + if (dupPerformIndex > -1) { + // 结束并删除全部重复演出 + for (let i = 0; i < this.performList.length; i++) { + const e = this.performList[i]; + if (e.performName === perform.performName) { + e.stopFunction(); + clearTimeout(e.stopTimeout as unknown as number); + this.performList.splice(i, 1); + i--; + } } } } @@ -50,7 +52,7 @@ export class PerformController { // perform.isOver = true; if (!perform.isHoldOn) { // 如果不是保持演出,清除 - this.unmountPerform(perform.performName); + this.softUnmountPerformObject(perform); } }, perform.duration); @@ -103,6 +105,25 @@ export class PerformController { } } + public softUnmountPerformObject(perform: IPerform) { + const idx = this.performList.indexOf(perform); + if (idx < 0) return; + perform.stopFunction(); + clearTimeout(perform.stopTimeout as unknown as number); + /** + * 在演出列表里删除演出对象的操作必须在调用 goNextWhenOver 之前 + * 因为 goNextWhenOver 会调用 nextSentence,而 nextSentence 会清除目前未结束的演出 + * 那么 nextSentence 函数就会删除这个演出,但是此时,在这个上下文,i 已经被确定了 + * 所以 goNextWhenOver 后的代码会多删东西,解决方法就是在调用 goNextWhenOver 前先删掉这个演出对象 + * 此问题对所有 goNextWhenOver 属性为真的演出都有影响,但只有 2 个演出有此问题 + */ + this.performList.splice(idx, 1); + if (perform.goNextWhenOver) { + // nextSentence(); + this.goNextWhenOver(); + } + } + public erasePerformFromState(name: string) { webgalStore.dispatch(stageActions.removePerformByName(name)); } diff --git a/packages/webgal/src/Core/Modules/perform/performInterface.ts b/packages/webgal/src/Core/Modules/perform/performInterface.ts index 8e00b2f90..5431e7dd4 100644 --- a/packages/webgal/src/Core/Modules/perform/performInterface.ts +++ b/packages/webgal/src/Core/Modules/perform/performInterface.ts @@ -23,6 +23,8 @@ export interface IPerform { arrangePerformPromise?: Promise; // 跳过由 nextSentence 函数引发的演出回收 skipNextCollect?: boolean; + // + isParallel?: boolean; } // next之后,可以被打断的演出会被打断,不能被打断的演出会继续,阻塞next的演出会阻止next被响应。 diff --git a/packages/webgal/src/Core/controller/stage/pixi/PixiController.ts b/packages/webgal/src/Core/controller/stage/pixi/PixiController.ts index 76ec7f3d6..19d2d7d04 100644 --- a/packages/webgal/src/Core/controller/stage/pixi/PixiController.ts +++ b/packages/webgal/src/Core/controller/stage/pixi/PixiController.ts @@ -10,6 +10,8 @@ import { SCREEN_CONSTANTS } from '@/Core/util/constants'; import { logger } from '@/Core/util/logger'; import { v4 as uuid } from 'uuid'; import { cloneDeep, isEqual } from 'lodash'; +import omitBy from 'lodash/omitBy'; +import isUndefined from 'lodash/isUndefined'; import * as PIXI from 'pixi.js'; import { INSTALLED } from 'pixi.js'; import { GifResource } from './GifResource'; @@ -71,9 +73,9 @@ export default class PixiStage { if (!source) return; const targetScale = target.scale; const targetPosition = target.position; - if (target.scale) Object.assign(targetScale, source.scale); - if (target.position) Object.assign(targetPosition, source.position); - Object.assign(target, source); + if (target.scale) Object.assign(targetScale!, omitBy(source.scale || {}, isUndefined)); + if (target.position) Object.assign(targetPosition!, omitBy(source.position || {}, isUndefined)); + Object.assign(target, omitBy(source, isUndefined)); target.scale = targetScale; target.position = targetPosition; } diff --git a/packages/webgal/src/Core/controller/stage/pixi/animations/generateTransformAnimationObj.ts b/packages/webgal/src/Core/controller/stage/pixi/animations/generateTransformAnimationObj.ts index 1cf2a38af..dbbe8c0ba 100644 --- a/packages/webgal/src/Core/controller/stage/pixi/animations/generateTransformAnimationObj.ts +++ b/packages/webgal/src/Core/controller/stage/pixi/animations/generateTransformAnimationObj.ts @@ -1,5 +1,6 @@ import { AnimationFrame } from '@/Core/Modules/animations'; import { webgalStore } from '@/store/store'; +import { has, pickBy } from 'lodash'; import isNull from 'lodash/isNull'; type AnimationObj = Array; @@ -10,6 +11,7 @@ export function generateTransformAnimationObj( applyFrame: AnimationFrame, duration: number | string | boolean | null, ease: string, + writeFullEffect = true, ): AnimationObj { let animationObj; // 获取那个 target 的当前变换 @@ -25,8 +27,21 @@ export function generateTransformAnimationObj( // 找到 effect if (targetEffect) { - const effectWithDuration = { ...targetEffect!.transform!, duration: 0, ease }; - animationObj.unshift(effectWithDuration); + if (writeFullEffect) { + const effectWithDuration = { ...targetEffect!.transform!, duration: 0, ease }; + animationObj.unshift(effectWithDuration); + } else { + const targetScale = pickBy(targetEffect.transform?.scale || {}, (source, key) => has(applyFrame.scale, key)); + const targetPosition = pickBy(targetEffect.transform?.position || {}, (sr, key) => has(applyFrame.position, key)); + const effectWithDuration = { + ...pickBy(targetEffect.transform || {}, (source, key) => has(applyFrame, key)), + duration: 0, + ease, + }; + effectWithDuration.scale = targetScale; + effectWithDuration.position = targetPosition; + animationObj.unshift(effectWithDuration); + } } else { // 应用默认effect,也就是最终的 effect 的 alpha = 0 版本 const effectWithDuration = { ...applyFrame, alpha: 0, duration: 0, ease }; diff --git a/packages/webgal/src/Core/controller/stage/pixi/animations/timeline.ts b/packages/webgal/src/Core/controller/stage/pixi/animations/timeline.ts index a49d55787..145c5c13b 100644 --- a/packages/webgal/src/Core/controller/stage/pixi/animations/timeline.ts +++ b/packages/webgal/src/Core/controller/stage/pixi/animations/timeline.ts @@ -33,7 +33,7 @@ export function generateTimelineObj( currentDelay += segmentDuration; const { position, scale, ...segmentValues } = segment; // 不能用 scale,因为 popmotion 不能用嵌套 - values.push({ x: position.x, y: position.y, scaleX: scale.x, scaleY: scale.y, ...segmentValues }); + values.push({ x: position?.x, y: position?.y, scaleX: scale?.x, scaleY: scale?.y, ...segmentValues }); // Easing 需要比 values 的长度少一个 if (i > 0) { easeArray.push(stringToEasing(segment.ease)); @@ -74,11 +74,11 @@ export function generateTimelineObj( if (target?.pixiContainer) { // 不能赋值到 position,因为 x 和 y 被 WebGALPixiContainer 代理,而 position 属性没有代理 const { position, scale, ...state } = getStartStateEffect(); - const assignValue = omitBy({ x: position.x, y: position.y, ...state }, isUndefined); + const assignValue = omitBy({ x: position?.x, y: position?.y, ...state }, isUndefined); // @ts-ignore PixiStage.assignTransform(target?.pixiContainer, assignValue); - if (target?.pixiContainer) { - if (!isUndefined(scale.x)) { + if (scale && target?.pixiContainer) { + if (!isUndefined(scale?.x)) { target.pixiContainer.scale.x = scale.x; } if (!isUndefined(scale?.y)) { @@ -101,11 +101,11 @@ export function generateTimelineObj( // 不能赋值到 position,因为 x 和 y 被 WebGALPixiContainer 代理,而 position 属性没有代理 // 不能赋值到 position,因为 x 和 y 被 WebGALPixiContainer 代理,而 position 属性没有代理 const { position, scale, ...state } = getEndStateEffect(); - const assignValue = omitBy({ x: position.x, y: position.y, ...state }, isUndefined); + const assignValue = omitBy({ x: position?.x, y: position?.y, ...state }, isUndefined); // @ts-ignore PixiStage.assignTransform(target?.pixiContainer, assignValue); - if (target?.pixiContainer) { - if (!isUndefined(scale.x)) { + if (scale && target?.pixiContainer) { + if (!isUndefined(scale?.x)) { target.pixiContainer.scale.x = scale.x; } if (!isUndefined(scale?.y)) { diff --git a/packages/webgal/src/Core/gameScripts/setAnimation.ts b/packages/webgal/src/Core/gameScripts/setAnimation.ts index 748b4498a..a9a384c0d 100644 --- a/packages/webgal/src/Core/gameScripts/setAnimation.ts +++ b/packages/webgal/src/Core/gameScripts/setAnimation.ts @@ -20,11 +20,12 @@ export const setAnimation = (sentence: ISentence): IPerform => { target = target !== '' ? target : 'default_id'; const writeDefault = getBooleanArgByKey(sentence, 'writeDefault') ?? false; const keep = getBooleanArgByKey(sentence, 'keep') ?? false; + const parallel = getBooleanArgByKey(sentence, 'parallel') ?? false; const key = `${target}-${animationName}-${animationDuration}`; const performInitName = `animation-${target}`; - WebGAL.gameplay.performController.unmountPerform(performInitName, true); + if (!parallel) WebGAL.gameplay.performController.unmountPerform(performInitName, true); let stopFunction; setTimeout(() => { @@ -56,5 +57,6 @@ export const setAnimation = (sentence: ISentence): IPerform => { blockingNext: () => false, blockingAuto: () => !keep, stopTimeout: undefined, // 暂时不用,后面会交给自动清除 + isParallel: parallel, }; }; diff --git a/packages/webgal/src/Core/gameScripts/setTempAnimation.ts b/packages/webgal/src/Core/gameScripts/setTempAnimation.ts index 8da9635f7..811c05d73 100644 --- a/packages/webgal/src/Core/gameScripts/setTempAnimation.ts +++ b/packages/webgal/src/Core/gameScripts/setTempAnimation.ts @@ -10,6 +10,7 @@ import { baseTransform } from '@/store/stageInterface'; import { IUserAnimation } from '../Modules/animations'; import { getAnimateDuration, getAnimationObject } from '@/Core/Modules/animationFunctions'; import { WebGAL } from '@/Core/WebGAL'; +import { v4 as uuid } from 'uuid'; /** * 设置临时动画 @@ -17,7 +18,7 @@ import { WebGAL } from '@/Core/WebGAL'; */ export const setTempAnimation = (sentence: ISentence): IPerform => { const startDialogKey = webgalStore.getState().stage.currentDialogKey; - const animationName = (Math.random() * 10).toString(16); + const animationName = uuid(); const animationString = sentence.content; let animationObj; try { @@ -31,11 +32,12 @@ export const setTempAnimation = (sentence: ISentence): IPerform => { const target = getStringArgByKey(sentence, 'target') ?? '0'; const writeDefault = getBooleanArgByKey(sentence, 'writeDefault') ?? false; const keep = getBooleanArgByKey(sentence, 'keep') ?? false; + const parallel = getBooleanArgByKey(sentence, 'parallel') ?? false; const key = `${target}-${animationName}-${animationDuration}`; const performInitName = `animation-${target}`; - WebGAL.gameplay.performController.unmountPerform(performInitName, true); + if (!parallel) WebGAL.gameplay.performController.unmountPerform(performInitName, true); let stopFunction = () => {}; setTimeout(() => { @@ -67,5 +69,6 @@ export const setTempAnimation = (sentence: ISentence): IPerform => { blockingNext: () => false, blockingAuto: () => !keep, stopTimeout: undefined, // 暂时不用,后面会交给自动清除 + isParallel: parallel, }; }; diff --git a/packages/webgal/src/Core/gameScripts/setTransform.ts b/packages/webgal/src/Core/gameScripts/setTransform.ts index 149287293..087ae6a97 100644 --- a/packages/webgal/src/Core/gameScripts/setTransform.ts +++ b/packages/webgal/src/Core/gameScripts/setTransform.ts @@ -11,14 +11,14 @@ import { AnimationFrame, IUserAnimation } from '../Modules/animations'; import { generateTransformAnimationObj } from '@/Core/controller/stage/pixi/animations/generateTransformAnimationObj'; import { WebGAL } from '@/Core/WebGAL'; import { getAnimateDuration, getAnimationObject } from '../Modules/animationFunctions'; - +import { v4 as uuid } from 'uuid'; /** * 设置变换 * @param sentence */ export const setTransform = (sentence: ISentence): IPerform => { const startDialogKey = webgalStore.getState().stage.currentDialogKey; - const animationName = (Math.random() * 10).toString(16); + const animationName = uuid(); const animationString = sentence.content; let animationObj: AnimationFrame[]; @@ -27,14 +27,16 @@ export const setTransform = (sentence: ISentence): IPerform => { const writeDefault = getBooleanArgByKey(sentence, 'writeDefault') ?? false; const target = getStringArgByKey(sentence, 'target') ?? '0'; const keep = getBooleanArgByKey(sentence, 'keep') ?? false; + const parallel = getBooleanArgByKey(sentence, 'parallel') ?? false; const performInitName = `animation-${target}`; - WebGAL.gameplay.performController.unmountPerform(performInitName, true); + if (!parallel) WebGAL.gameplay.performController.unmountPerform(performInitName, true); try { const frame = JSON.parse(animationString) as AnimationFrame; - animationObj = generateTransformAnimationObj(target, frame, duration, ease); + // writeDefault时需要完整的当前effect,其他时候不需要 + animationObj = generateTransformAnimationObj(target, frame, duration, ease, writeDefault); console.log('animationObj:', animationObj); } catch (e) { // 解析都错误了,歇逼吧 @@ -44,7 +46,6 @@ export const setTransform = (sentence: ISentence): IPerform => { const newAnimation: IUserAnimation = { name: animationName, effects: animationObj }; WebGAL.animationManager.addAnimation(newAnimation); const animationDuration = getAnimateDuration(animationName); - const key = `${target}-${animationName}-${animationDuration}`; let keepAnimationStopped = false; setTimeout(() => { @@ -82,5 +83,6 @@ export const setTransform = (sentence: ISentence): IPerform => { blockingNext: () => false, blockingAuto: () => !keep, stopTimeout: undefined, // 暂时不用,后面会交给自动清除 + isParallel: parallel, }; }; diff --git a/packages/webgal/src/Stage/MainStage/useSetEffects.ts b/packages/webgal/src/Stage/MainStage/useSetEffects.ts index c5a1790ad..8a096d11c 100644 --- a/packages/webgal/src/Stage/MainStage/useSetEffects.ts +++ b/packages/webgal/src/Stage/MainStage/useSetEffects.ts @@ -2,6 +2,7 @@ import { baseTransform, IEffect, IStageState, ITransform } from '@/store/stageIn import { WebGAL } from '@/Core/WebGAL'; import PixiStage from '@/Core/controller/stage/pixi/PixiController'; +import { isUndefined, omitBy } from 'lodash'; export function setStageObjectEffects(stageState: IStageState) { const effects = stageState.effects; @@ -42,5 +43,5 @@ function convertTransform(transform: ITransform | undefined) { return {}; } const { position, ...rest } = transform; - return { ...rest, x: position.x, y: position.y }; + return omitBy({ ...rest, x: position?.x, y: position?.y }, isUndefined); } diff --git a/packages/webgal/src/store/stageInterface.ts b/packages/webgal/src/store/stageInterface.ts index 5494635d2..9269e7edd 100644 --- a/packages/webgal/src/store/stageInterface.ts +++ b/packages/webgal/src/store/stageInterface.ts @@ -25,41 +25,41 @@ export interface IChooseItem { } export interface ITransform { - alpha: number; - scale: { - x: number; - y: number; + alpha?: number; + scale?: { + x?: number; + y?: number; }; // pivot: { // x: number; // y: number; // }; - position: { - x: number; - y: number; + position?: { + x?: number; + y?: number; }; - rotation: number; - blur: number; - brightness: number; - contrast: number; - saturation: number; - gamma: number; - colorRed: number; - colorGreen: number; - colorBlue: number; - bevel: number; - bevelThickness: number; - bevelRotation: number; - bevelSoftness: number; - bevelRed: number; - bevelGreen: number; - bevelBlue: number; - bloom: number; - bloomBrightness: number; - bloomBlur: number; - bloomThreshold: number; - shockwaveFilter: number; - radiusAlphaFilter: number; + rotation?: number; + blur?: number; + brightness?: number; + contrast?: number; + saturation?: number; + gamma?: number; + colorRed?: number; + colorGreen?: number; + colorBlue?: number; + bevel?: number; + bevelThickness?: number; + bevelRotation?: number; + bevelSoftness?: number; + bevelRed?: number; + bevelGreen?: number; + bevelBlue?: number; + bloom?: number; + bloomBrightness?: number; + bloomBlur?: number; + bloomThreshold?: number; + shockwaveFilter?: number; + radiusAlphaFilter?: number; } /** diff --git a/packages/webgal/src/store/stageReducer.ts b/packages/webgal/src/store/stageReducer.ts index 450efbe38..e95ff7a78 100644 --- a/packages/webgal/src/store/stageReducer.ts +++ b/packages/webgal/src/store/stageReducer.ts @@ -23,6 +23,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import cloneDeep from 'lodash/cloneDeep'; import { commandType } from '@/Core/controller/scene/sceneInterface'; import { STAGE_KEYS } from '@/Core/constants'; +import { isUndefined, omitBy } from 'lodash'; // 初始化舞台数据 @@ -123,7 +124,17 @@ const stageSlice = createSlice({ const effectIndex = state.effects.findIndex((e) => e.target === target); if (effectIndex >= 0) { // Update the existing effect - state.effects[effectIndex].transform = transform; + if (!state.effects[effectIndex].transform) { + state.effects[effectIndex].transform = transform; + } else if (transform) { + const targetScale = state.effects[effectIndex].transform!.scale || {}; + const targetPosition = state.effects[effectIndex].transform!.position || {}; + if (transform.scale) Object.assign(targetScale, omitBy(transform.scale, isUndefined)); + if (transform.position) Object.assign(targetPosition, omitBy(transform.position, isUndefined)); + Object.assign(state.effects[effectIndex].transform!, omitBy(transform, isUndefined)); + state.effects[effectIndex].transform!.scale = targetScale; + state.effects[effectIndex].transform!.position = targetPosition; + } } else { // Add a new effect state.effects.push({