Skip to content

Commit 1d753ca

Browse files
Merge pull request #912 from OpenWebGAL/fix-spine-skins
fix: add skin support
2 parents bc40c51 + 2dfafcb commit 1d753ca

6 files changed

Lines changed: 109 additions & 6 deletions

File tree

packages/webgal/src/Core/controller/stage/pixi/PixiController.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,62 @@ export default class PixiStage {
859859
}
860860
}
861861

862+
public changeSpineSkinByKey(key: string, skin: string) {
863+
if (!skin) return;
864+
865+
const target = this.figureObjects.find((e) => e.key === key && !e.isExiting);
866+
if (target?.sourceType !== 'spine') return;
867+
868+
const container = target.pixiContainer;
869+
if (!container) return;
870+
const sprite = container.children[0] as PIXI.Container;
871+
if (sprite?.children?.[0]) {
872+
const spineObject = sprite.children[0];
873+
// @ts-ignore
874+
const skeleton = spineObject.skeleton;
875+
// @ts-ignore
876+
const skeletonData = skeleton?.data ?? spineObject.spineData;
877+
const skinObject =
878+
// @ts-ignore
879+
skeletonData?.findSkin?.(skin) ??
880+
// @ts-ignore
881+
skeletonData?.skins?.find((item: any) => item.name === skin);
882+
883+
if (!skeleton || !skinObject) {
884+
logger.warn(`Spine skin not found: ${skin} on ${key}`);
885+
return;
886+
}
887+
888+
try {
889+
// @ts-ignore
890+
if (typeof skeleton.setSkinByName === 'function') {
891+
// @ts-ignore
892+
skeleton.setSkinByName(skin);
893+
} else {
894+
// @ts-ignore
895+
skeleton.setSkin(skinObject);
896+
}
897+
} catch (error) {
898+
// @ts-ignore
899+
skeleton.setSkin?.(skinObject);
900+
}
901+
902+
// @ts-ignore
903+
if (typeof skeleton.setSlotsToSetupPose === 'function') {
904+
// @ts-ignore
905+
skeleton.setSlotsToSetupPose();
906+
} else {
907+
// @ts-ignore
908+
skeleton.setupPoseSlots?.();
909+
}
910+
911+
// @ts-ignore
912+
spineObject.state?.apply?.(skeleton);
913+
// @ts-ignore
914+
skeleton.updateWorldTransform?.();
915+
}
916+
}
917+
862918
public changeModelExpressionByKey(key: string, expression: string) {
863919
// logger.debug(`Applying expression ${expression} to ${key}`);
864920
const target = this.figureObjects.find((e) => e.key === key && !e.isExiting);

packages/webgal/src/Core/controller/stage/pixi/spine.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,13 @@ export async function addSpineFigureImpl(
111111
figureSpine.pivot.set(spineCenterX, spineCenterY);
112112
figureSpine.interactive = false;
113113

114-
// 检查状态中是否有指定的动画
115114
const motionFromState = webgalStore.getState().stage.live2dMotion.find((e) => e.target === key);
116115
let animationToPlay = '';
116+
if (motionFromState?.skin) {
117+
if (!applySpineSkin(figureSpine, motionFromState.skin)) {
118+
logger.warn(`Spine skin not found: ${motionFromState.skin} on ${key}`);
119+
}
120+
}
117121

118122
if (
119123
motionFromState &&
@@ -277,3 +281,40 @@ export async function addSpineBgImpl(this: PixiStage, key: string, url: string)
277281
await setup();
278282
}
279283
}
284+
285+
function applySpineSkin(spineObject: any, skinName: string) {
286+
// @ts-ignore
287+
const skeleton = spineObject.skeleton;
288+
// @ts-ignore
289+
const skeletonData = skeleton?.data ?? spineObject.spineData;
290+
const skin =
291+
// @ts-ignore
292+
skeletonData?.findSkin?.(skinName) ??
293+
// @ts-ignore
294+
skeletonData?.skins?.find((item: any) => item.name === skinName);
295+
if (!skeleton || !skin) {
296+
return false;
297+
}
298+
try {
299+
// @ts-ignore
300+
if (typeof skeleton.setSkinByName === 'function') {
301+
// @ts-ignore
302+
skeleton.setSkinByName(skinName);
303+
} else {
304+
// @ts-ignore
305+
skeleton.setSkin(skin);
306+
}
307+
} catch (error) {
308+
// @ts-ignore
309+
skeleton.setSkin?.(skin);
310+
}
311+
// @ts-ignore
312+
if (typeof skeleton.setSlotsToSetupPose === 'function') {
313+
// @ts-ignore
314+
skeleton.setSlotsToSetupPose();
315+
} else {
316+
// @ts-ignore
317+
skeleton.setupPoseSlots?.();
318+
}
319+
return true;
320+
}

packages/webgal/src/Core/gameScripts/changeFigure.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export function changeFigure(sentence: ISentence): IPerform {
5252

5353
// live2d 或 spine 相关
5454
let motion = getStringArgByKey(sentence, 'motion') ?? '';
55+
const skin = getStringArgByKey(sentence, 'skin') ?? '';
5556
let expression = getStringArgByKey(sentence, 'expression') ?? '';
5657
const boundsFromArgs = getStringArgByKey(sentence, 'bounds') ?? '';
5758
let bounds = getOverrideBoundsArr(boundsFromArgs);
@@ -234,7 +235,7 @@ export function changeFigure(sentence: ISentence): IPerform {
234235
focus = focus ?? cloneDeep(baseFocusParam);
235236
zIndex = Math.max(zIndex, 0);
236237
blendMode = blendMode ?? 'normal';
237-
dispatch(stageActions.setLive2dMotion({ target: key, motion, overrideBounds: bounds }));
238+
dispatch(stageActions.setLive2dMotion({ target: key, motion, skin, overrideBounds: bounds }));
238239
dispatch(stageActions.setLive2dExpression({ target: key, expression }));
239240
dispatch(stageActions.setLive2dBlink({ target: key, blink }));
240241
dispatch(stageActions.setLive2dFocus({ target: key, focus }));
@@ -243,8 +244,8 @@ export function changeFigure(sentence: ISentence): IPerform {
243244
} else {
244245
// 当 url 没有发生变化时,即没有新立绘替换
245246
// 应当保留旧立绘的状态,仅在需要时更新
246-
if (motion || bounds) {
247-
dispatch(stageActions.setLive2dMotion({ target: key, motion, overrideBounds: bounds }));
247+
if (motion || skin || bounds) {
248+
dispatch(stageActions.setLive2dMotion({ target: key, motion, skin, overrideBounds: bounds }));
248249
}
249250
if (expression) {
250251
dispatch(stageActions.setLive2dExpression({ target: key, expression }));

packages/webgal/src/Stage/MainStage/useSetFigure.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ export function useSetFigure(stageState: IStageState) {
2626
*/
2727
useEffect(() => {
2828
for (const motion of live2dMotion) {
29+
if (motion.skin) {
30+
WebGAL.gameplay.pixiStage?.changeSpineSkinByKey(motion.target, motion.skin);
31+
}
2932
WebGAL.gameplay.pixiStage?.changeModelMotionByKey(motion.target, motion.motion);
3033
}
3134
}, [live2dMotion]);

packages/webgal/src/store/stageInterface.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ export interface IRunPerform {
177177
export interface ILive2DMotion {
178178
target: string;
179179
motion: string;
180+
skin?: string;
180181
overrideBounds?: [number, number, number, number];
181182
}
182183

packages/webgal/src/store/stageReducer.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,16 +244,17 @@ const stageSlice = createSlice({
244244
}
245245
},
246246
setLive2dMotion: (state, action: PayloadAction<ILive2DMotion>) => {
247-
const { target, motion, overrideBounds } = action.payload;
247+
const { target, motion, skin, overrideBounds } = action.payload;
248248

249249
const index = state.live2dMotion.findIndex((e) => e.target === target);
250250

251251
if (index < 0) {
252252
// Add a new motion
253-
state.live2dMotion.push({ target, motion, overrideBounds });
253+
state.live2dMotion.push({ target, motion, skin, overrideBounds });
254254
} else {
255255
// Update the existing motion
256256
state.live2dMotion[index].motion = motion;
257+
state.live2dMotion[index].skin = skin;
257258
state.live2dMotion[index].overrideBounds = overrideBounds;
258259
}
259260
},

0 commit comments

Comments
 (0)