@@ -14,21 +14,15 @@ Live2DManager.prototype.loadModel = async function(modelPath, options = {}) {
1414 this . teardownPersistentExpressions ( ) ;
1515 this . initialParameters = { } ;
1616
17- // 尝试还原之前覆盖的 updateParameters,避免旧引用在新模型上报错
17+ // 还原 coreModel.update 覆盖
1818 try {
19- const mm = this . currentModel . internalModel && this . currentModel . internalModel . motionManager ;
20- if ( mm ) {
21- if ( this . _mouthOverrideInstalled && typeof this . _origUpdateParameters === 'function' ) {
22- try { mm . updateParameters = this . _origUpdateParameters ; } catch ( _ ) { }
23- }
24- if ( mm && mm . expressionManager && this . _mouthOverrideInstalled && typeof this . _origExpressionUpdateParameters === 'function' ) {
25- try { mm . expressionManager . updateParameters = this . _origExpressionUpdateParameters ; } catch ( _ ) { }
26- }
19+ const coreModel = this . currentModel . internalModel && this . currentModel . internalModel . coreModel ;
20+ if ( coreModel && this . _mouthOverrideInstalled && typeof this . _origCoreModelUpdate === 'function' ) {
21+ coreModel . update = this . _origCoreModelUpdate ;
2722 }
2823 } catch ( _ ) { }
2924 this . _mouthOverrideInstalled = false ;
30- this . _origUpdateParameters = null ;
31- this . _origExpressionUpdateParameters = null ;
25+ this . _origCoreModelUpdate = null ;
3226 // 同时移除 mouthTicker(若曾启用过 ticker 模式)
3327 if ( this . _mouthTicker && this . pixi_app && this . pixi_app . ticker ) {
3428 try { this . pixi_app . ticker . remove ( this . _mouthTicker ) ; } catch ( _ ) { }
@@ -329,80 +323,83 @@ Live2DManager.prototype.loadModel = async function(modelPath, options = {}) {
329323// 不再需要预解析嘴巴参数ID,保留占位以兼容旧代码调用
330324Live2DManager . prototype . resolveMouthParameterId = function ( ) { return null ; } ;
331325
332- // 安装覆盖:在 motion 参数更新后强制写入口型参数
326+ // 安装覆盖:覆盖 coreModel.update 方法,在 SDK 程序化动画之后强制写入参数
327+ // 这是最可靠的方式,因为 coreModel.update 是在所有参数修改之后、渲染之前调用的
333328Live2DManager . prototype . installMouthOverride = function ( ) {
334- if ( ! this . currentModel || ! this . currentModel . internalModel || ! this . currentModel . internalModel . motionManager ) {
329+ if ( ! this . currentModel || ! this . currentModel . internalModel ) {
335330 throw new Error ( '模型未就绪,无法安装口型覆盖' ) ;
336331 }
337332
338- const mm = this . currentModel . internalModel . motionManager ;
339-
340- // 如果之前装过在其他模型上,先尝试还原
341- try {
342- if ( this . _mouthOverrideInstalled ) {
343- if ( typeof this . _origUpdateParameters === 'function' ) {
344- try { mm . updateParameters = this . _origUpdateParameters ; } catch ( _ ) { }
345- }
346- if ( mm . expressionManager && typeof this . _origExpressionUpdateParameters === 'function' ) {
347- try { mm . expressionManager . updateParameters = this . _origExpressionUpdateParameters ; } catch ( _ ) { }
348- }
349- this . _mouthOverrideInstalled = false ;
350- this . _origUpdateParameters = null ;
351- this . _origExpressionUpdateParameters = null ;
352- }
353- } catch ( _ ) { }
333+ const internalModel = this . currentModel . internalModel ;
334+ const coreModel = internalModel . coreModel ;
335+
336+ if ( ! coreModel ) {
337+ throw new Error ( 'coreModel 不可用' ) ;
338+ }
354339
355- if ( typeof mm . updateParameters !== 'function' ) {
356- throw new Error ( 'motionManager.updateParameters 不可用' ) ;
340+ // 如果之前装过,先还原
341+ if ( this . _mouthOverrideInstalled && typeof this . _origCoreModelUpdate === 'function' ) {
342+ try { coreModel . update = this . _origCoreModelUpdate ; } catch ( _ ) { }
343+ this . _origCoreModelUpdate = null ;
357344 }
358345
359- // 绑定原函数并覆盖
360- const orig = mm . updateParameters . bind ( mm ) ;
361- mm . updateParameters = ( coreModel , now ) => {
362- const updated = orig ( coreModel , now ) ;
346+ // 口型参数列表(这些参数不会被常驻表情覆盖)
347+ const lipSyncParams = [ 'ParamMouthOpenY' , 'ParamMouthForm' , 'ParamMouthOpen' , 'ParamA' , 'ParamI' , 'ParamU' , 'ParamE' , 'ParamO' ] ;
348+
349+ // 保存原始的 coreModel.update 方法
350+ const origCoreModelUpdate = coreModel . update ? coreModel . update . bind ( coreModel ) : null ;
351+ this . _origCoreModelUpdate = origCoreModelUpdate ;
352+
353+ // 缓存参数索引,避免每帧查询
354+ const mouthParamIndices = { } ;
355+ for ( const id of [ 'ParamMouthOpenY' , 'ParamO' ] ) {
363356 try {
364- const mouthIds = [ 'ParamMouthOpenY' , 'ParamO' ] ;
365- for ( const id of mouthIds ) {
357+ const idx = coreModel . getParameterIndex ( id ) ;
358+ if ( idx >= 0 ) mouthParamIndices [ id ] = idx ;
359+ } catch ( _ ) { }
360+ }
361+
362+ // 覆盖 coreModel.update 方法
363+ // 在调用原始 update 之前写入参数(因为 update 会将参数应用到模型)
364+ coreModel . update = ( ) => {
365+ try {
366+ // 1. 强制写入口型参数(使用索引直接设置)
367+ for ( const [ id , idx ] of Object . entries ( mouthParamIndices ) ) {
366368 try {
367- if ( coreModel . getParameterIndex ( id ) !== - 1 ) {
368- coreModel . setParameterValueById ( id , this . mouthValue , 1 ) ;
369- }
369+ coreModel . setParameterValueByIndex ( idx , this . mouthValue ) ;
370370 } catch ( _ ) { }
371371 }
372- } catch ( _ ) { }
373- return updated ;
374- } ;
375- this . _origUpdateParameters = orig ; // 保存可还原的实现(已绑定)
376-
377- // 也覆盖 expressionManager.updateParameters,防止表情参数覆盖嘴巴
378- if ( mm . expressionManager && typeof mm . expressionManager . updateParameters === 'function' ) {
379- const origExp = mm . expressionManager . updateParameters . bind ( mm . expressionManager ) ;
380- mm . expressionManager . updateParameters = ( coreModel , now ) => {
381- const updated = origExp ( coreModel , now ) ;
382- try {
383- const mouthIds = [ 'ParamMouthOpenY' , 'ParamO' ] ;
384- for ( const id of mouthIds ) {
385- try {
386- if ( coreModel . getParameterIndex ( id ) !== - 1 ) {
387- coreModel . setParameterValueById ( id , this . mouthValue , 1 ) ;
372+
373+ // 2. 强制写入常驻表情参数(跳过口型参数)
374+ if ( this . persistentExpressionParamsByName ) {
375+ for ( const name of ( this . persistentExpressionNames || [ ] ) ) {
376+ const params = this . persistentExpressionParamsByName [ name ] ;
377+ if ( Array . isArray ( params ) ) {
378+ for ( const p of params ) {
379+ // 跳过口型参数
380+ if ( lipSyncParams . includes ( p . Id ) ) continue ;
381+ try {
382+ const idx = coreModel . getParameterIndex ( p . Id ) ;
383+ if ( idx >= 0 ) {
384+ coreModel . setParameterValueByIndex ( idx , p . Value ) ;
385+ }
386+ } catch ( _ ) { }
388387 }
389- } catch ( _ ) { }
388+ }
390389 }
391- } catch ( _ ) { }
392- return updated ;
393- } ;
394- this . _origExpressionUpdateParameters = origExp ;
395- } else {
396- this . _origExpressionUpdateParameters = null ;
397- }
398-
399- // 若此前使用了 ticker 覆盖,确保移除
400- if ( this . _mouthTicker && this . pixi_app && this . pixi_app . ticker ) {
401- try { this . pixi_app . ticker . remove ( this . _mouthTicker ) ; } catch ( _ ) { }
402- this . _mouthTicker = null ;
403- }
390+ }
391+ } catch ( e ) {
392+ // 静默处理错误
393+ }
394+
395+ // 调用原始的 update 方法(将参数应用到模型顶点)
396+ if ( origCoreModelUpdate ) {
397+ origCoreModelUpdate ( ) ;
398+ }
399+ } ;
404400
405401 this . _mouthOverrideInstalled = true ;
402+ console . log ( '已安装参数覆盖(口型 + 常驻表情),使用 coreModel.update 前置覆盖方式' ) ;
406403} ;
407404
408405// 设置嘴巴开合值(0~1)
0 commit comments