Skip to content

Commit f6586c4

Browse files
Merge pull request #572 from OpenWebGAL/dev
4.5.8
2 parents a20255e + 70e631d commit f6586c4

File tree

23 files changed

+384
-158
lines changed

23 files changed

+384
-158
lines changed

packages/webgal/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "webgal",
33
"private": true,
4-
"version": "4.5.7",
4+
"version": "4.5.8",
55
"scripts": {
66
"dev": "vite --host --port 3000",
77
"build": "cross-env NODE_ENV=production tsc && vite build --base=./",
@@ -21,7 +21,7 @@
2121
"mitt": "^3.0.0",
2222
"modern-css-reset": "^1.4.0",
2323
"pixi-filters": "^4.2.0",
24-
"pixi-live2d-display-webgal": "^0.5.2",
24+
"pixi-live2d-display-webgal": "^0.5.8",
2525
"pixi-spine": "^3.1.2",
2626
"pixi.js": "^6.3.0",
2727
"popmotion": "^11.0.5",
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
22
"name":"Default Template",
3-
"webgal-version":"4.5.7"
3+
"webgal-version":"4.5.8"
44
}

packages/webgal/src/Core/Modules/animationFunctions.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,12 @@ export function getAnimateDuration(animationName: string) {
3535
return 0;
3636
}
3737

38+
// eslint-disable-next-line max-params
3839
export function getEnterExitAnimation(
3940
target: string,
4041
type: 'enter' | 'exit',
4142
isBg = false,
43+
realTarget?: string, // 用于立绘和背景移除时,以当前时间打上特殊标记
4244
): {
4345
duration: number;
4446
animation: {
@@ -57,11 +59,11 @@ export function getEnterExitAnimation(
5759
setStartState: () => void;
5860
tickerFunc: (delta: number) => void;
5961
setEndState: () => void;
60-
} | null = generateUniversalSoftInAnimationObj(target, duration);
62+
} | null = generateUniversalSoftInAnimationObj(realTarget ?? target, duration);
6163
const animarionName = WebGAL.animationManager.nextEnterAnimationName.get(target);
6264
if (animarionName) {
6365
logger.debug('取代默认进入动画', target);
64-
animation = getAnimationObject(animarionName, target, getAnimateDuration(animarionName));
66+
animation = getAnimationObject(animarionName, realTarget ?? target, getAnimateDuration(animarionName));
6567
duration = getAnimateDuration(animarionName);
6668
// 用后重置
6769
WebGAL.animationManager.nextEnterAnimationName.delete(target);
@@ -77,11 +79,11 @@ export function getEnterExitAnimation(
7779
setStartState: () => void;
7880
tickerFunc: (delta: number) => void;
7981
setEndState: () => void;
80-
} | null = generateUniversalSoftOffAnimationObj(target, duration);
82+
} | null = generateUniversalSoftOffAnimationObj(realTarget ?? target, duration);
8183
const animarionName = WebGAL.animationManager.nextExitAnimationName.get(target);
8284
if (animarionName) {
8385
logger.debug('取代默认退出动画', target);
84-
animation = getAnimationObject(animarionName, target, getAnimateDuration(animarionName));
86+
animation = getAnimationObject(animarionName, realTarget ?? target, getAnimateDuration(animarionName));
8587
duration = getAnimateDuration(animarionName);
8688
// 用后重置
8789
WebGAL.animationManager.nextExitAnimationName.delete(target);

packages/webgal/src/Core/constants.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const STAGE_KEYS = {
2+
BGMAIN: 'bg-main',
3+
FIG_C: 'fig-center',
4+
FIG_L: 'fig-left',
5+
FIG_R: 'fig-right',
6+
};

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

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { v4 as uuid } from 'uuid';
33
import { webgalStore } from '@/store/store';
44
import { setStage, stageActions } from '@/store/stageReducer';
55
import cloneDeep from 'lodash/cloneDeep';
6-
import { IEffect, IFigureAssociatedAnimation } from '@/store/stageInterface';
6+
import { IEffect, IFigureAssociatedAnimation, IFigureMetadata } from '@/store/stageInterface';
77
import { logger } from '@/Core/util/logger';
88
import { isIOS } from '@/Core/initializeScript';
99
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
1212
import { Spine } from 'pixi-spine';
1313
import { SCREEN_CONSTANTS } from '@/Core/util/constants';
1414
// import { figureCash } from '@/Core/gameScripts/vocal/conentsCash'; // 如果要使用 Live2D,取消这里的注释
15-
// import { Live2DModel, SoundManager } from 'pixi-live2d-display'; // 如果要使用 Live2D,取消这里的注释
15+
// import { Live2DModel, SoundManager } from 'pixi-live2d-display-webgal'; // 如果要使用 Live2D,取消这里的注释
1616

1717
export interface IAnimationObject {
1818
setStartState: Function;
@@ -125,6 +125,7 @@ export default class PixiStage {
125125
this.effectsContainer = new PIXI.Container();
126126
this.effectsContainer.zIndex = 3;
127127
this.figureContainer = new PIXI.Container();
128+
this.figureContainer.sortableChildren = true; // 允许立绘启用 z-index
128129
this.figureContainer.zIndex = 2;
129130
this.backgroundContainer = new PIXI.Container();
130131
this.backgroundContainer.zIndex = 0;
@@ -493,6 +494,12 @@ export default class PixiStage {
493494
this.removeStageObjectByKey(key);
494495
}
495496

497+
const metadata = this.getFigureMetadataByKey(key);
498+
if (metadata) {
499+
if (metadata.zIndex) {
500+
thisFigureContainer.zIndex = metadata.zIndex;
501+
}
502+
}
496503
// 挂载
497504
this.figureContainer.addChild(thisFigureContainer);
498505
const figureUuid = uuid();
@@ -578,6 +585,12 @@ export default class PixiStage {
578585
this.removeStageObjectByKey(key);
579586
}
580587

588+
const metadata = this.getFigureMetadataByKey(key);
589+
if (metadata) {
590+
if (metadata.zIndex) {
591+
thisFigureContainer.zIndex = metadata.zIndex;
592+
}
593+
}
581594
// 挂载
582595
this.figureContainer.addChild(thisFigureContainer);
583596
const figureUuid = uuid();
@@ -673,6 +686,12 @@ export default class PixiStage {
673686
// this.removeStageObjectByKey(key);
674687
// }
675688
//
689+
// const metadata = this.getFigureMetadataByKey(key);
690+
// if (metadata) {
691+
// if (metadata.zIndex) {
692+
// thisFigureContainer.zIndex = metadata.zIndex;
693+
// }
694+
// }
676695
// // 挂载
677696
// this.figureContainer.addChild(thisFigureContainer);
678697
// this.figureObjects.push({
@@ -689,7 +708,23 @@ export default class PixiStage {
689708
// const setup = () => {
690709
// if (thisFigureContainer) {
691710
// (async function () {
692-
// const models = await Promise.all([Live2DModel.from(jsonPath, { autoInteract: false })]);
711+
// let overrideBounds: [number, number, number, number] = [0, 0, 0, 0];
712+
// const mot = webgalStore.getState().stage.live2dMotion.find((e) => e.target === key);
713+
// if (mot?.overrideBounds) {
714+
// overrideBounds = mot.overrideBounds;
715+
// }
716+
// console.log(overrideBounds);
717+
// const models = await Promise.all([
718+
// Live2DModel.from(jsonPath, {
719+
// autoInteract: false,
720+
// overWriteBounds: {
721+
// x0: overrideBounds[0],
722+
// y0: overrideBounds[1],
723+
// x1: overrideBounds[2],
724+
// y1: overrideBounds[3],
725+
// },
726+
// }),
727+
// ]);
693728
//
694729
// models.forEach((model) => {
695730
// const scaleX = stageWidth / model.width;
@@ -962,6 +997,11 @@ export default class PixiStage {
962997
private getExtName(url: string) {
963998
return url.split('.').pop() ?? 'png';
964999
}
1000+
1001+
private getFigureMetadataByKey(key: string): IFigureMetadata | undefined {
1002+
console.log(key, webgalStore.getState().stage.figureMetaData);
1003+
return webgalStore.getState().stage.figureMetaData[key];
1004+
}
9651005
}
9661006

9671007
function updateCurrentBacklogEffects(newEffects: IEffect[]) {

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

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ export function changeFigure(sentence: ISentence): IPerform {
3333
let animationFlag: any = '';
3434
let mouthAnimationKey: any = 'mouthAnimation';
3535
let eyesAnimationKey: any = 'blinkAnimation';
36+
let overrideBounds = '';
37+
let zIndex = -1;
3638
const dispatch = webgalStore.dispatch;
3739

3840
for (const e of sentence.args) {
@@ -63,6 +65,9 @@ export function changeFigure(sentence: ISentence): IPerform {
6365
case 'motion':
6466
motion = e.value.toString();
6567
break;
68+
case 'bounds':
69+
overrideBounds = String(e.value);
70+
break;
6671
case 'expression':
6772
expression = e.value.toString();
6873
break;
@@ -92,6 +97,9 @@ export function changeFigure(sentence: ISentence): IPerform {
9297
case 'none':
9398
content = '';
9499
break;
100+
case 'zIndex':
101+
zIndex = Number(e.value);
102+
break;
95103
default:
96104
break;
97105
}
@@ -153,6 +161,9 @@ export function changeFigure(sentence: ISentence): IPerform {
153161
const deleteKey2 = `${key}`;
154162
webgalStore.dispatch(stageActions.removeEffectByTargetId(deleteKey));
155163
webgalStore.dispatch(stageActions.removeEffectByTargetId(deleteKey2));
164+
// 重设 figureMetaData,这里是 zIndex,实际上任何键都可以,因为整体是移除那条记录
165+
dispatch(stageActions.setFigureMetaData([deleteKey, 'zIndex', 0, true]));
166+
dispatch(stageActions.setFigureMetaData([deleteKey2, 'zIndex', 0, true]));
156167
}
157168
const setAnimationNames = (key: string, sentence: ISentence) => {
158169
// 处理 transform 和 默认 transform
@@ -208,21 +219,27 @@ export function changeFigure(sentence: ISentence): IPerform {
208219
}
209220
};
210221
if (isFreeFigure) {
211-
const currentFreeFigures = webgalStore.getState().stage.freeFigure;
212-
213222
/**
214-
* 重设
223+
* 下面的代码是设置自由立绘的
215224
*/
216225
const freeFigureItem: IFreeFigure = { key, name: content, basePosition: pos };
217226
setAnimationNames(key, sentence);
218-
if (motion) {
219-
dispatch(stageActions.setLive2dMotion({ target: key, motion }));
227+
if (motion || overrideBounds) {
228+
dispatch(
229+
stageActions.setLive2dMotion({ target: key, motion, overrideBounds: getOverrideBoundsArr(overrideBounds) }),
230+
);
220231
}
221232
if (expression) {
222233
dispatch(stageActions.setLive2dExpression({ target: key, expression }));
223234
}
235+
if (zIndex > 0) {
236+
dispatch(stageActions.setFigureMetaData([key, 'zIndex', zIndex, false]));
237+
}
224238
dispatch(stageActions.setFreeFigureByKey(freeFigureItem));
225239
} else {
240+
/**
241+
* 下面的代码是设置与位置关联的立绘的
242+
*/
226243
const positionMap = {
227244
center: 'fig-center',
228245
left: 'fig-left',
@@ -236,12 +253,17 @@ export function changeFigure(sentence: ISentence): IPerform {
236253

237254
key = positionMap[pos];
238255
setAnimationNames(key, sentence);
239-
if (motion) {
240-
dispatch(stageActions.setLive2dMotion({ target: key, motion }));
256+
if (motion || overrideBounds) {
257+
dispatch(
258+
stageActions.setLive2dMotion({ target: key, motion, overrideBounds: getOverrideBoundsArr(overrideBounds) }),
259+
);
241260
}
242261
if (expression) {
243262
dispatch(stageActions.setLive2dExpression({ target: key, expression }));
244263
}
264+
if (zIndex > 0) {
265+
dispatch(stageActions.setFigureMetaData([key, 'zIndex', zIndex, false]));
266+
}
245267
dispatch(setStage({ key: dispatchMap[pos], value: content }));
246268
}
247269

@@ -255,3 +277,16 @@ export function changeFigure(sentence: ISentence): IPerform {
255277
stopTimeout: undefined, // 暂时不用,后面会交给自动清除
256278
};
257279
}
280+
281+
function getOverrideBoundsArr(raw: string): undefined | [number, number, number, number] {
282+
const parseOverrideBoundsResult = raw.split(',').map((e) => Number(e));
283+
let isPass = true;
284+
parseOverrideBoundsResult.forEach((e) => {
285+
if (isNaN(e)) {
286+
isPass = false;
287+
}
288+
});
289+
isPass = isPass && parseOverrideBoundsResult.length === 4;
290+
if (isPass) return parseOverrideBoundsResult as [number, number, number, number];
291+
else return undefined;
292+
}

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

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { getSentenceArgByKey } from '@/Core/util/getSentenceArg';
99
import { textSize, voiceOption } from '@/store/userDataInterface';
1010
import { WebGAL } from '@/Core/WebGAL';
1111
import { compileSentence } from '@/Stage/TextBox/TextBox';
12+
import { performMouthAnimation } from '@/Core/gameScripts/vocal/vocalAnimation';
13+
import { match } from '@/Core/util/match';
1214

1315
/**
1416
* 进行普通对话的显示
@@ -84,9 +86,62 @@ export const say = (sentence: ISentence): IPerform => {
8486
}
8587
dispatch(setStage({ key: 'showName', value: showName }));
8688

89+
// 模拟说话
90+
let performSimulateVocalTimeout: ReturnType<typeof setTimeout> | null = null;
91+
let performSimulateVocalDelay = 0;
92+
let pos = '';
93+
let key = '';
94+
for (const e of sentence.args) {
95+
if (e.value === true) {
96+
match(e.key)
97+
.with('left', () => {
98+
pos = 'left';
99+
})
100+
.with('right', () => {
101+
pos = 'right';
102+
})
103+
.endsWith('center', () => {
104+
pos = 'center';
105+
});
106+
}
107+
if (e.key === 'figureId') {
108+
key = `${e.value.toString()}`;
109+
}
110+
}
111+
let audioLevel = 80;
112+
const performSimulateVocal = (end = false) => {
113+
let nextAudioLevel = audioLevel + (Math.random() * 60 - 30); // 在 -30 到 +30 之间波动
114+
// 确保波动幅度不小于 5
115+
if (Math.abs(nextAudioLevel - audioLevel) < 5) {
116+
nextAudioLevel = audioLevel + Math.sign(nextAudioLevel - audioLevel) * 5;
117+
}
118+
// 确保结果在 25 到 100 之间
119+
audioLevel = Math.max(15, Math.min(nextAudioLevel, 100));
120+
const currentStageState = webgalStore.getState().stage;
121+
const figureAssociatedAnimation = currentStageState.figureAssociatedAnimation;
122+
const animationItem = figureAssociatedAnimation.find((tid) => tid.targetId === key);
123+
const targetKey = key ? key : `fig-${pos}`;
124+
if (end) {
125+
audioLevel = 0;
126+
}
127+
performMouthAnimation({
128+
audioLevel,
129+
OPEN_THRESHOLD: 50,
130+
HALF_OPEN_THRESHOLD: 25,
131+
currentMouthValue: 0,
132+
lerpSpeed: 1,
133+
key: targetKey,
134+
animationItem,
135+
pos,
136+
});
137+
if (!end) performSimulateVocalTimeout = setTimeout(performSimulateVocal, 50);
138+
};
87139
// 播放一段语音
88140
if (vocal) {
89141
playVocal(sentence);
142+
} else if (key || pos) {
143+
performSimulateVocalDelay = len * 250;
144+
performSimulateVocal();
90145
}
91146

92147
const performInitName: string = getRandomPerformName();
@@ -98,10 +153,14 @@ export const say = (sentence: ISentence): IPerform => {
98153

99154
return {
100155
performName: performInitName,
101-
duration: sentenceDelay + endDelay,
156+
duration: sentenceDelay + endDelay + performSimulateVocalDelay,
102157
isHoldOn: false,
103158
stopFunction: () => {
104159
WebGAL.events.textSettle.emit();
160+
if (performSimulateVocalTimeout) {
161+
performSimulateVocal(true);
162+
clearTimeout(performSimulateVocalTimeout);
163+
}
105164
},
106165
blockingNext: () => false,
107166
blockingAuto: () => true,

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,15 @@ export function useSetBg(stageState: IStageState) {
4040
function removeBg(bgObject: IStageObject) {
4141
WebGAL.gameplay.pixiStage?.removeAnimationWithSetEffects('bg-main-softin');
4242
const oldBgKey = bgObject.key;
43-
bgObject.key = 'bg-main-off';
43+
bgObject.key = 'bg-main-off' + String(new Date().getTime());
44+
const bgKey = bgObject.key;
45+
const bgAniKey = bgObject.key + '-softoff';
4446
WebGAL.gameplay.pixiStage?.removeStageObjectByKey(oldBgKey);
45-
const { duration, animation } = getEnterExitAnimation('bg-main-off', 'exit', true);
46-
WebGAL.gameplay.pixiStage!.registerAnimation(animation, 'bg-main-softoff', 'bg-main-off');
47+
const { duration, animation } = getEnterExitAnimation('bg-main-off', 'exit', true, bgKey);
48+
WebGAL.gameplay.pixiStage!.registerAnimation(animation, bgAniKey, bgKey);
4749
setTimeout(() => {
48-
WebGAL.gameplay.pixiStage?.removeAnimation('bg-main-softoff');
49-
WebGAL.gameplay.pixiStage?.removeStageObjectByKey('bg-main-off');
50+
WebGAL.gameplay.pixiStage?.removeAnimation(bgAniKey);
51+
WebGAL.gameplay.pixiStage?.removeStageObjectByKey(bgKey);
5052
}, duration);
5153
}
5254

0 commit comments

Comments
 (0)