Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: dynamic load live2d lib #611

Open
wants to merge 4 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions packages/webgal/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -215,8 +215,37 @@
</script>
<script type="module" src="/src/main.tsx"></script>
<!--<script type="module" src="/src/Core/util/resize.ts"></script>-->
<!--<script src="lib/live2d.min.js"></script>-->
<!--<script src="lib/live2dcubismcore.min.js"></script>-->
<script>
function loadScript(url) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = url;
script.onload = () => resolve(`Loaded: ${url}`);
script.onerror = (error) => reject(`Failed to load: ${url}`);
document.head.appendChild(script);
});
}

async function loadLive2D() {
try {
// 尝试加载 Live2D SDK,
// 只有在用户自行取得 Live2D 许可并放到下面的目录时,这里才可能加载成功。
// 本项目 **没有** 引入 Live2D SDK
// Attempt to load the Live2D SDK.
// This will only succeed if the user has obtained a Live2D license and placed it in the directory below.
// This project **does not** include the Live2D SDK.
// Live2D SDK の読み込みを試みます。
// ユーザーが Live2D ライセンスを取得し、以下のディレクトリに配置した場合のみ、読み込みが成功します。
// このプロジェクトには Live2D SDK は**含まれていません**
await loadScript('lib/live2d.min.js');
await loadScript('lib/live2dcubismcore.min.js');
console.log('Both Live2D scripts loaded successfully.');
} catch (error) {
console.error(error);
}
}
loadLive2D();
</script>
<script>
let enterPromise = new Promise(res => window.enterPromise = res);
let renderPromise = new Promise(res => window.renderPromise = res);
Expand Down
291 changes: 160 additions & 131 deletions packages/webgal/src/Core/controller/stage/pixi/PixiController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ export default class PixiStage {
*/
private MAX_TEX_COUNT = 10;

private isLive2dAvailable: undefined | boolean = undefined;
private figureCash: any;
private live2DModel: any;
private soundManager: any;
public constructor() {
const app = new PIXI.Application({
backgroundAlpha: 0,
Expand Down Expand Up @@ -160,6 +164,7 @@ export default class PixiStage {
this.callLoader();
};
reload();
this.initialize().then(() => {});
}

public getFigureObjects() {
Expand Down Expand Up @@ -508,137 +513,143 @@ export default class PixiStage {
* @param jsonPath
*/
// eslint-disable-next-line max-params
// public addLive2dFigure(key: string, jsonPath: string, pos: string, motion: string, expression: string) {
// let stageWidth = this.stageWidth;
// let stageHeight = this.stageHeight;
// logger.debug('Using motion:', motion);
//
// figureCash.push(jsonPath);
//
// const loader = this.assetLoader;
// // 准备用于存放这个立绘的 Container
// const thisFigureContainer = new WebGALPixiContainer();
//
// // 是否有相同 key 的立绘
// const setFigIndex = this.figureObjects.findIndex((e) => e.key === key);
// const isFigSet = setFigIndex >= 0;
//
// // 已经有一个这个 key 的立绘存在了
// if (isFigSet) {
// this.removeStageObjectByKey(key);
// }
//
// const metadata = this.getFigureMetadataByKey(key);
// if (metadata) {
// if (metadata.zIndex) {
// thisFigureContainer.zIndex = metadata.zIndex;
// }
// }
// // 挂载
// this.figureContainer.addChild(thisFigureContainer);
// this.figureObjects.push({
// uuid: uuid(),
// key: key,
// pixiContainer: thisFigureContainer,
// sourceUrl: jsonPath,
// sourceType: 'live2d',
// sourceExt: 'json',
// });
// // eslint-disable-next-line @typescript-eslint/no-this-alias
// const instance = this;
//
// const setup = () => {
// if (thisFigureContainer) {
// (async function () {
// 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;
// const scaleY = stageHeight / model.height;
// const targetScale = Math.min(scaleX, scaleY) * 1.5;
// const targetWidth = model.width * targetScale;
// // const targetHeight = model.height * targetScale;
//
// model.scale.set(targetScale);
// model.anchor.set(0.5);
// model.position.x = stageWidth / 2;
// model.position.y = stageHeight / 1.2;
//
// if (pos === 'left') {
// model.position.x = targetWidth / 2;
// }
// if (pos === 'right') {
// model.position.x = stageWidth - targetWidth / 2;
// }
//
// let motionToSet = motion;
// let animation_index = 0;
// let priority_number = 3;
// // var audio_link = voiceCash.pop();
//
// // model.motion(category_name, animation_index, priority_number,location.href + audio_link);
// /**
// * 检查 Motion 和 Expression
// */
// const motionFromState = webgalStore.getState().stage.live2dMotion.find((e) => e.target === key);
// const expressionFromState = webgalStore.getState().stage.live2dExpression.find((e) => e.target === key);
// if (motionFromState) {
// motionToSet = motionFromState.motion;
// }
// instance.updateL2dMotionByKey(key, motionToSet);
// model.motion(motionToSet, animation_index, priority_number);
// let expressionToSet = expression;
// if (expressionFromState) {
// expressionToSet = expressionFromState.expression;
// }
// instance.updateL2dExpressionByKey(key, expressionToSet);
// model.expression(expressionToSet);
// // @ts-ignore
// if (model.internalModel.eyeBlink) {
// // @ts-ignore
// model.internalModel.eyeBlink.blinkInterval = 1000 * 60 * 60 * 24; // @ts-ignore
// model.internalModel.eyeBlink.nextBlinkTimeLeft = 1000 * 60 * 60 * 24;
// }
//
// // lip-sync is still a problem and you can not.
// SoundManager.volume = 0; // @ts-ignore
// if (model.internalModel.angleXParamIndex !== undefined) model.internalModel.angleXParamIndex = 999; // @ts-ignore
// if (model.internalModel.angleYParamIndex !== undefined) model.internalModel.angleYParamIndex = 999; // @ts-ignore
// if (model.internalModel.angleZParamIndex !== undefined) model.internalModel.angleZParamIndex = 999;
// thisFigureContainer.addChild(model);
// });
// })();
// }
// };
//
// /**
// * 加载器部分
// */
// const resourses = Object.keys(loader.resources);
// this.cacheGC();
// if (!resourses.includes(jsonPath)) {
// this.loadAsset(jsonPath, setup);
// } else {
// // 复用
// setup();
// }
// }
public addLive2dFigure(key: string, jsonPath: string, pos: string, motion: string, expression: string) {
if (this.isLive2dAvailable !== true) return;
try {
let stageWidth = this.stageWidth;
let stageHeight = this.stageHeight;
logger.debug('Using motion:', motion);

this.figureCash.push(jsonPath);

const loader = this.assetLoader;
// 准备用于存放这个立绘的 Container
const thisFigureContainer = new WebGALPixiContainer();

// 是否有相同 key 的立绘
const setFigIndex = this.figureObjects.findIndex((e) => e.key === key);
const isFigSet = setFigIndex >= 0;

// 已经有一个这个 key 的立绘存在了
if (isFigSet) {
this.removeStageObjectByKey(key);
}

const metadata = this.getFigureMetadataByKey(key);
if (metadata) {
if (metadata.zIndex) {
thisFigureContainer.zIndex = metadata.zIndex;
}
}
// 挂载
this.figureContainer.addChild(thisFigureContainer);
this.figureObjects.push({
uuid: uuid(),
key: key,
pixiContainer: thisFigureContainer,
sourceUrl: jsonPath,
sourceType: 'live2d',
sourceExt: 'json',
});
// eslint-disable-next-line @typescript-eslint/no-this-alias
const instance = this;

const setup = (stage: PixiStage) => {
if (thisFigureContainer) {
(async function () {
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([
stage.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;
const scaleY = stageHeight / model.height;
const targetScale = Math.min(scaleX, scaleY) * 1.5;
const targetWidth = model.width * targetScale;
// const targetHeight = model.height * targetScale;

model.scale.set(targetScale);
model.anchor.set(0.5);
model.position.x = stageWidth / 2;
model.position.y = stageHeight / 1.2;

if (pos === 'left') {
model.position.x = targetWidth / 2;
}
if (pos === 'right') {
model.position.x = stageWidth - targetWidth / 2;
}

let motionToSet = motion;
let animation_index = 0;
let priority_number = 3;
// var audio_link = voiceCash.pop();

// model.motion(category_name, animation_index, priority_number,location.href + audio_link);
/**
* 检查 Motion 和 Expression
*/
const motionFromState = webgalStore.getState().stage.live2dMotion.find((e) => e.target === key);
const expressionFromState = webgalStore.getState().stage.live2dExpression.find((e) => e.target === key);
if (motionFromState) {
motionToSet = motionFromState.motion;
}
instance.updateL2dMotionByKey(key, motionToSet);
model.motion(motionToSet, animation_index, priority_number);
let expressionToSet = expression;
if (expressionFromState) {
expressionToSet = expressionFromState.expression;
}
instance.updateL2dExpressionByKey(key, expressionToSet);
model.expression(expressionToSet);
// @ts-ignore
if (model.internalModel.eyeBlink) {
// @ts-ignore
model.internalModel.eyeBlink.blinkInterval = 1000 * 60 * 60 * 24; // @ts-ignore
model.internalModel.eyeBlink.nextBlinkTimeLeft = 1000 * 60 * 60 * 24;
}

// lip-sync is still a problem and you can not.
stage.soundManager.volume = 0; // @ts-ignore
if (model.internalModel.angleXParamIndex !== undefined) model.internalModel.angleXParamIndex = 999; // @ts-ignore
if (model.internalModel.angleYParamIndex !== undefined) model.internalModel.angleYParamIndex = 999; // @ts-ignore
if (model.internalModel.angleZParamIndex !== undefined) model.internalModel.angleZParamIndex = 999;
thisFigureContainer.addChild(model);
});
})();
}
};

/**
* 加载器部分
*/
const resourses = Object.keys(loader.resources);
this.cacheGC();
if (!resourses.includes(jsonPath)) {
this.loadAsset(jsonPath, () => setup(this));
} else {
// 复用
setup(this);
}
} catch (error) {
console.error('Live2d Module err: ' + error);
this.isLive2dAvailable = false;
}
}

public changeModelMotionByKey(key: string, motion: string) {
// logger.debug(`Applying motion ${motion} to ${key}`);
Expand Down Expand Up @@ -844,6 +855,24 @@ export default class PixiStage {
const index = this.lockTransformTarget.findIndex((name) => name === targetName);
if (index >= 0) this.lockTransformTarget.splice(index, 1);
}

private async initialize() {
// 动态加载 figureCash
try {
const { figureCash } = await import('@/Core/gameScripts/vocal/conentsCash');
this.figureCash = figureCash;
const { Live2DModel, SoundManager } = await import('pixi-live2d-display-webgal');
this.live2DModel = Live2DModel;
this.soundManager = SoundManager;
} catch (error) {
this.isLive2dAvailable = false;
console.info('live2d lib load failed', error);
}
if (this.isLive2dAvailable === undefined) {
this.isLive2dAvailable = true;
console.info('live2d lib load success');
}
}
}

function updateCurrentBacklogEffects(newEffects: IEffect[]) {
Expand Down
2 changes: 1 addition & 1 deletion packages/webgal/src/Stage/MainStage/useSetFigure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,5 +227,5 @@ function addFigure(type?: 'image' | 'live2D' | 'spine', ...args: any[]) {
*/
function addLive2dFigure(...args: any[]) {
// @ts-ignore
// return WebGAL.gameplay.pixiStage?.addLive2dFigure(...args);
return WebGAL.gameplay.pixiStage?.addLive2dFigure(...args);
}
Loading