对各部位执行不同的动态管理 (Web)

最終更新: 2020年1月10日

在这个页面上,以为了实装左右手可以并行管理不同的动态,
增加LAppModel.cubismMotionManager拥有的数量并将其嵌入LAppModel.update的情况为例,
看一看管理和创建动态时的注意事项等。

增加CubismMotionManager的原因

CubismMotionManager是为了应对渐变,支持临时播放多个动态的功能。
但是,开始播放的API会淡出正在播放的动态并退出,
所以没有保持并行播放动态的功能。

要解决此问题,请准备多个动态管理器副本,
通过分离要播放的动态管理器,实现了并行动态播放。

增加CubismMotionManager

追加动态管理器

首先,我们看一下程序端的实装。
首先要做的是向模型追加一个动态管理类。
我们将CubismMotionManager追加到范例LAppModel。
在构造函数和release函数中追加生成和释放处理。

export class LAppModel extends CubismUserModel {
  
/*省略*/
 
    /**
     * 构造函数
     */
    public constructor() 
    {
        super();

/*省略*/
       
        this._rightArmMotionManager = new CubismMotionManager(); // <<<追加!
        this._leftArmMotionManager = new CubismMotionManager();  // <<<追加!
    }
    _rightArmMotionManager: CubismMotionManager;    /// <<< 追加!
    _leftArmMotionManager: CubismMotionManager;     /// <<< 追加!

}

更新处理

还要追加到Update处理中,使播放的动态影响参数。

public update(): void
{

/*省略*/

  	//--------------------------------------------------------------------------
    this._model.loadParameters();   // 读取上次保存的状态
    if(this._motionManager.isFinished())
    {
        // 如果没有动态播放,则从待机动态开始随机播放
        this.startRandomMotion(LAppDefine.MotionGroupIdle, LAppDefine.PriorityIdle);
        
    }
    else
    {
        motionUpdated = this._motionManager.updateMotion(this._model, deltaTimeSeconds);    // 更新动态
    }
    motionUpdated != this._rightArmMotionManager.updateMotion(this._model, deltaTimeSeconds);   // <追加
    motionUpdated != this._leftArmMotionManager.updateMotion(this._model, deltaTimeSeconds);    // <追加
    this._model.saveParameters(); // 保存状态
    //--------------------------------------------------------------------------

/*省略*/

}

在LAppModel.update内部追加时,请在_model.loadParameters()和_model.saveParameters()之间设置updateMotion。

此外,如果您同时播放多个动态,则可能会从两个或多个动态更新相同的参数值。
在这种情况下,稍后执行的updateMotion的内容优先。

开始播放

然后,可以通过复制和修改startMotion系统的成员函数的形式,使用追加的动态管理器来指示动态播放。
请注意,startHandMotion函数将动态管理器作为参数指定。
在以下示例中,仅播放.motion3.json指定的动态(由preLoad函数读取的动态)。

// 通过从StartMotion复制创建
public startHandMotion(targetManage: CubismMotionManager, group: string, no: number, priority: number): CubismMotionQueueEntryHandle
{
    if(priority == LAppDefine.PriorityForce)
    {
        targetManage.setReservePriority(priority);
    }
    else if(!targetManage.reserveMotion(priority))
    {
        if(this._debugMode)
        {
            LAppPal.printLog("[APP]can't start motion.");
        }
        return InvalidMotionQueueEntryHandleValue;
    }
    
    const fileName: string = this._modelSetting.getMotionFileName(group, no);

    //ex) idle_0
    let name: string = Utiles.CubismString.getFormatedString("{0}_{1}", group, no);
    let motion: CubismMotion = this._motions[name];
    let autoDelete = false;

    if(motion == null)
    {
        // 如果没有被preLoad读取,则不播放
        return InvalidMotionQueueEntryHandleValue;
    }

    if(this._debugMode)
    {
        LAppPal.printLog("[APP]start motion: [{0}_{1}]", group. no);
    }
    return targetmanage.startMotionPriority(motion, autoDelete, priority);
}

// 通过startRandomMotion复制创建
public startRandomRightHandMotion(group: string, priority: number): CubismMotionQueueEntryHandle
{
    if(this._modelSetting.getMotionCount(group) == 0)
    {
        return InvalidMotionQueueEntryHandleValue;
    }

    let no: number = Math.floor(Math.random() * this._modelSetting.getMotionCount(group));
    
    return this.startHandMotion(this._rightArmMotionManager, group, no, priority);
}

// 通过startRandomMotion复制创建
public startRandomLeftHandMotion(group: string, priority: number): CubismMotionQueueEntryHandle
{
    if(this._modelSetting.getMotionCount(group) == 0)
    {
        return InvalidMotionQueueEntryHandleValue;
    }

    let no: number = Math.floor(Math.random() * this._modelSetting.getMotionCount(group));

    return this.startHandMotion(this._leftArmMotionManager, group, no, priority);
}

动态播放为模型追加了重叠检测,使其通过单击动作。

public onTap(x: number, y: number): void
{
    if(LAppDefine.DebugLogEnable)
    {
        LAppPal.printLog("[APP]tap point: {x: {0} y: {1}}", x.toFixed(2), y.toFixed(2));
    }

    for(let i: number = 0; i < this._models.getSize(); i++)
    {
        if(this._models.at(i).hitTest(LAppDefine.HitAreaNameHead, x, y))
        {
            if(LAppDefine.DebugLogEnable)
            {
                LAppPal.printLog("[APP]hit area: [{0}]", LAppDefine.HitAreaNameHead);
            }
            this._models.at(i).setRandomExpression();
        }
        else if(this._models.at(i).hitTest(LAppDefine.HitAreaNameBody, x, y))
        {
            if(LAppDefine.DebugLogEnable)
            {
                LAppPal.printLog("[APP]hit area: [{0}]", LAppDefine.HitAreaNameBody);
            }
            this._models.at(i).startRandomMotion(LAppDefine.MotionGroupTapBody, LAppDefine.PriorityNormal);
        }

        // 从这里追加
        else if(this._models.at(i).hitTest("Right", x, y))
        {
            if(LAppDefine.DebugLogEnable)
            {
                LAppPal.printLog("[APP]hit area: [{0}]", LAppDefine.HitAreaNameBody);
            }
            this._models.at(i).startRandomRightHandMotion("Right", LAppDefine.PriorityForce);
        }
        else if(this._models.at(i).hitTest("Left", x, y))
        {
            if(LAppDefine.DebugLogEnable)
            {
                LAppPal.printLog("[APP]hit area: [{0}], LAppDefine.HitAreaNameBody");
            }
            this._models.at(i).startRandomLeftHandMotion("Left", LAppDefine.PriorityForce);
        }
        // 追加到这里
    }
}

负责参数的分布

如更新处理所示,如果多个要播放的动态之间的参数重复,则后面执行的更新内容将被优先执行,参数将被覆盖。
因此,在使用多个动态管理器时,需要决定哪个动态管理器负责哪个部位的动作、
以及负责哪个参数操作。

上述视频中,刚刚退出播放优先顺序较高的动态管理器(负责左臂和右臂的动态)后,通过优先顺序较低的动态管理器(负责模型全体闲置动态)更新了左臂和右臂的参数。
如果您希望将优先顺序较高的动态管理器更新的参数保持在刚刚退出动态播放后的状态,这不是一个理想的表达方式。
当优先顺序较低的动态管理器播放的动态数据中不打算更新的参数设置参考值时,就会出现这种现象。
此时,通过将要更新的参数对各动态数据进行分开,可以将优先顺序较高的动态管理器更新的参数保持在刚刚退出播放后的状态。

针对是否分开各参数或以覆盖为前提,
在创建动态之前明确定义规范非常重要,以免进行巨大的修正。

关于本报道,敬请提出您的意见及要求。