そういうのがいいブログ

アプリ個人開発 まるブログ

アプリ開発覚え書き

【Unity】Humanoid型アバターのマッスル値をスクリプトから変更してポーズ変更する

はじめに

Humanoid型アバターの姿勢変更について調べたメモです。

Humanoid型アバターの姿勢を変更する方法として
私が把握しているのは次の2つです。
・マッスル値を変更する
・ボーンの角度を変更する

今回はマッスル値による姿勢の変更について書きます。

ちなみにボーンの角度変更はこちらで可能です。

marumaro7.hatenablog.com

マッスル値とは?

Unity上ではmusclesという値です。

マッスル値は、文字通り筋肉の収縮を制御するイメージです。
-1 ~ +1の値となっており、人間の骨格ルールが適用されたものになっています。

例えば、左膝(Left Lower Leg Stretch)の設定を変えた場合はこのようになります。

・マッスル値 -1
 限界まで曲げる

・マッスル値 0
  -1と+1の中間

・マッスル値 +1
 限界まで伸ばす

このように -1 〜 +1 の値の範囲であれば、
骨格的にありえないポーズにならないようにしてくれる仕組みです。

ちなみに使いどころはあまり無いかと思いますが、
-1 〜 +1を超えた入力もできます。
(例)マッスル値 -1.62
 足がヤバい方向に・・・
 



マッスル値の種類ですが、こちらの95個から構成されています。
gist.github.com

スクリプトからのマッスル値変更

では、スクリプトからマッスル値を変更していきます。

具体例

サンプルから載せます。
ボタンを押すことで現状のポーズを取得し、
スライダーで左足のマッスル値を-1~+1の範囲で変更しています。

前提
アバターが設置されている
アバターにAnimatorが設定されている
・Animatorにアニメーションが設定されている
・ボタンが設置されている
・スライダーが設置されている

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class MusclesTest : MonoBehaviour
{
    public Slider slider;//操作するスライダーを割り当て スライダーの値は-1~+1の範囲設定にして下さい。
    public Animator animator;//対象のキャラクターのAnimatorコンポーネントを割り当て

    private HumanPoseHandler humanPoseHandler;
    private HumanPose humanpose;

    private bool isSetPose = false;//ポーズがセットされたかどうか
   
    //この関数をボタンに割り付け
    public void AAA()
    {
        humanPoseHandler = new HumanPoseHandler(animator.avatar, animator.transform);
        humanpose = new HumanPose();

        humanPoseHandler.GetHumanPose(ref humanpose);//現状のマッスル値を取得   なぜかアバターのY方向が180度回転する トランスフォームの値が変わるわけではない
        humanPoseHandler.SetHumanPose(ref humanpose);//マッスル値をセット      GetHumanPoseで回転→SetHumanPose回転しない→GetHumanPoseで回転するので回転を相殺するため GetHumanPoseを2回呼ぶ  ※GetHumanPoseを連続しても変わらない
        humanPoseHandler.GetHumanPose(ref humanpose);//現状のマッスル値を取得

        animator.enabled = false;//Animatorをオフ Animatorで実行しているアニメーションが優先されるためオフにする
        isSetPose = true;//マッスル値を取得してからtrueにする
    }

    
    void Update()
    {
        if (isSetPose == true)
        {
            //マッスル値の値を変更
            //今回は配列[21]でLeft Upper Leg Front-Backを動かす。
            //musclesの配列番号の対応はこちらを参考
            //https://gist.github.com/neon-izm/0637dac7a29682de916cecc0e8b037b0#file-humantrait-musclenames-csv
            humanpose.muscles[21] = Mathf.Clamp(slider.value, -1.0f, 1.0f);

            humanPoseHandler.SetHumanPose(ref humanpose);//マッスル値をセット
        }       
    }
}

このコードで動かしたものがこちら

処理流れ

処理の流れです。
マッスル値は、アバターが持っている
HumanPoseHandlerの中のHumanPoseの中に入っています。

なので、下記のように処理します。
1.アバターからHumanPoseHandlerを取得
2.HumanPoseHandlerからHumanPoseを取得
3.HumanPoseが持っているマッスル値を変更
4.変更したマッスル値を適用

1.アバターからHumanPoseHandlerを取得

HumanPoseHandlerを取得するのに必要な
第一引数:Avatar
第二引数:Transform
の情報をアバターに割りついているAnimatorから取得します。

HumanPoseHandler humanPoseHandler = new HumanPoseHandler(animator.avatar, animator.transform);

2.HumanPoseHandlerからHumanPoseを取得

HumanPoseHandlerからポーズ情報を取得します。
取得はGetHumanPosed(引数はHumanPose型)で行います。

humanPoseHandler.GetHumanPose(ref humanpose);

refは参照渡しの意味なので、これを実行することでhumanposeの変数が更新されます。

3.HumanPoseが持っているマッスル値を変更

マッスル値の変更は下記で変更しています。

humanpose.muscles[マッスル値の配列番号] =  マッスル値の値;



今回のサンプルコードでは、
配列番号[21]のLeft Upper Leg Front-Back のマッスル値を変更しています。
また、Mathf.Clampクラスを使用し、 下限の値と上限の値を設定しています。
(スライダーの範囲設定を-1~+1にしているので、
今回はMatuf.Clampは無くても同じ挙動になります。)

humanpose.muscles[21] = Mathf.Clamp(slider.value, -1.0f, 1.0f);

4.変更したマッスル値を適用

最後にHumanPoseHandlerに設定したポーズ情報を反映します。
反映は、SetHumanPose(引数はHumanPose型)で行います。

humanPoseHandler.SetHumanPose(ref humanpose);

refは参照渡しの意味なので、これを実行することでhumanposeの変数が更新されます。


追記 マッスル値列挙型

マッスル値の列挙型を載せておきます。

// マッスル値の列挙型
    public enum Muscle_TYPE
    {
        SpineFrontBack = 0,
        SpineLeftRight = 1,
        SpineTwistLeftRight = 2,
        ChestFrontBack = 3,
        ChestLeftRight = 4,
        ChestTwistLeftRight = 5,
        UpperChestFrontBack = 6,
        UpperChestLeftRight = 7,
        UpperChestTwistLeftRight = 8,
        NeckNodDownUp = 9,
        NeckTiltLeftRight = 10,
        NeckTurnLeftRight = 11,
        HeadNodDownUp = 12,
        HeadTiltLeftRight = 13,
        HeadTurnLeftRight = 14,
        LeftEyeDownUp = 15,
        LeftEyeInOut = 16,
        RightEyeDownUp = 17,
        RightEyeInOut = 18,
        JawClose = 19,
        JawLeftRight = 20,
        LeftUpperLegFrontBack = 21,
        LeftUpperLegInOut = 22,
        LeftUpperLegTwistInOut = 23,
        LeftLowerLegStretch = 24,
        LeftLowerLegTwistInOut = 25,
        LeftFootUpDown = 26,
        LeftFootTwistInOut = 27,
        LeftToesUpDown = 28,
        RightUpperLegFrontBack = 29,
        RightUpperLegInOut = 30,
        RightUpperLegTwistInOut = 31,
        RightLowerLegStretch = 32,
        RightLowerLegTwistInOut = 33,
        RightFootUpDown = 34,
        RightFootTwistInOut = 35,
        RightToesUpDown = 36,
        LeftShoulderDownUp = 37,
        LeftShoulderFrontBack = 38,
        LeftArmDownUp = 39,
        LeftArmFrontBack = 40,
        LeftArmTwistInOut = 41,
        LeftForearmStretch = 42,
        LeftForearmTwistInOut = 43,
        LeftHandDownUp = 44,
        LeftHandInOut = 45,
        RightShoulderDownUp = 46,
        RightShoulderFrontBack = 47,
        RightArmDownUp = 48,
        RightArmFrontBack = 49,
        RightArmTwistInOut = 50,
        RightForearmStretch = 51,
        RightForearmTwistInOut = 52,
        RightHandDownUp = 53,
        RightHandInOut = 54,
        LeftThumb1Stretched = 55,
        LeftThumbSpread = 56,
        LeftThumb2Stretched = 57,
        LeftThumb3Stretched = 58,
        LeftIndex1Stretched = 59,
        LeftIndexSpread = 60,
        LeftIndex2Stretched = 61,
        LeftIndex3Stretched = 62,
        LeftMiddle1Stretched = 63,
        LeftMiddleSpread = 64,
        LeftMiddle2Stretched = 65,
        LeftMiddle3Stretched = 66,
        LeftRing1Stretched = 67,
        LeftRingSpread = 68,
        LeftRing2Stretched = 69,
        LeftRing3Stretched = 70,
        LeftLittle1Stretched = 71,
        LeftLittleSpread = 72,
        LeftLittle2Stretched = 73,
        LeftLittle3Stretched = 74,
        RightThumb1Stretched = 75,
        RightThumbSpread = 76,
        RightThumb2Stretched = 77,
        RightThumb3Stretched = 78,
        RightIndex1Stretched = 79,
        RightIndexSpread = 80,
        RightIndex2Stretched = 81,
        RightIndex3Stretched = 82,
        RightMiddle1Stretched = 83,
        RightMiddleSpread = 84,
        RightMiddle2Stretched = 85,
        RightMiddle3Stretched = 86,
        RightRing1Stretched = 87,
        RightRingSpread = 88,
        RightRing2Stretched = 89,
        RightRing3Stretched = 90,
        RightLittle1Stretched = 91,
        RightLittleSpread = 92,
        RightLittle2Stretched = 93,
        RightLittle3Stretched = 94
    }

GetHumanPoseをするとキャラクターの向きが180度反転します。
・キャラクターのトランスフォームの値は変わっていない
・humanpose.bodyRotationでキャラクターの回転情報?を見てみると
 同じポーズでもGetHumanPoseをするたびに数値が変わっていました。

今は無理矢理2回呼び出して修正している感じです。
改善点ご存知の方がおられましたらご指導お願い致します。

余談

アバターの設定をミスったときによくみるこの中腰のポーズ

これはマッスル値の値がすべて0のときのポーズになっています。

ずっと「なんでこのポーズなんだろう?」と思っていたので謎が解けてスッキリしました。

Unity本を出版しました!

突然ですが、Unity本を出版しました!
こちらを読むことで、スクリプトの基礎固めができます!
現在、kindle unlimitedで読み放題設定中です。今のうちにどうぞ!


もっと早く教えてほしかった!Unity C#入門

他の記事

marumaro7.hatenablog.com

参考

zenn.dev

docs.unity3d.com

docs.unity3d.com