UnityでLive2Dモデルの頭を撫でる方法

Live2d

突然ですが、Live2Dで作ったモデルの頭を撫でたいなと思った事は皆さん
ありますよね?

この記事では、Unityにおいて、マウスをドラッグすることでLive2Dモデルの頭を撫でる動作を実際に使用しているコードを見せながら紹介したいと思います。

また、今回の方法については、
こちらの記事(http://miracle-usausa-nurse.com/program/488/)
を参考にさせていただきましたので、興味のある方は是非ご一読ください。

とはいえ、コードを公開されているわけではありませんので、動作に違いがある事をご了承ください。

サンプル

実際に作ったものを載せておきます。うまく動作しない場合はごめんなさい。

頭頂部あたりで、マウスカーソルが手のアイコンに代わると思うので、右クリックを押したままドラッグしてみてください。(無表情で頭しか動いてないのでなんか怖い)

注意点

今回コードを紹介するにあたり、Live2D Cubism SDKやUnity等の基本的な使用方法などは割愛しておりますので、わからない方は他サイトを参考にしてください。

実際に使用しているコードを一部修正して表示しているため、実際には動作しない場合や、コード漏れや不必要な部分や不格好な箇所、理解しないまま利用しているコード等がありますので、ご自身の環境でご利用の際は適宜変更を行ってください。

コードを作成するにあたり、参考にさせて頂いたサイト等を自身でも把握しきれていないため、引用した箇所の参考文献の記載があまりないことをご了承ください。

また、プログラミング、Live2d、イラストにつきましては素人であり、独学とChatGPT等に依存しながら制作しているため、環境の違いによってはうまく動作しない恐れがありますのでご了承ください。

用意する物

非常にざっくりですが、特に大事な物をご紹介します。

・Live2dモデル

・手のイラストやアイコン(1つはLive2dモデルに組み込んでおきます)

・当たり判定用アートメッシュ(https://docs.live2d.com/cubism-editor-manual/hittest/)

・Live2dモデルの頭を撫でるためのパラメーター動作

パラメーター設定例。手のイラストは頭の回転(角度X、角度Y)に合わせて動くように設定してあります。あえて大振りに設定してあるので、実際に動かす際はパラメーターの可動範囲を半分以下に制限して使用します。

スクリプトの全体

早速コードの全体を載せておきます。今回は「頭を撫でる.cs」という名称でunity内にスクリプトを作製し、動かしたいモデルのプレハブにセットしております。

using UnityEngine;
using Live2D.Cubism.Framework.Raycasting;
using Live2D.Cubism.Core;
using Live2D.Cubism.Framework;
using Live2D.Cubism.Rendering;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;

public class 頭を撫でる : MonoBehaviour
{
    public CubismModel _model;//対象の2Dモデル。アニメーション用
    public CubismRenderer _cubismRenderer;//2Dモデルのメッシュ。頭の座標の取得用
    public GameObject modelObject;//2Dモデルを格納しているオブジェクト。今回は座標やサイズをここで管理している。
    public GameObject Txt1;
    public GameObject HandIcon;//頭に接触している時に表示されるアイコン
    public GameObject NadeNadeHand;//頭を撫でている時に表示される手のイラスト(live2dモデル内のパーツとして存在)


    void Start()
    {
        NadeNadeHand.SetActive(false);//手のイラストを最初は非表示にする。
        Cursor.visible = true;//念のため、マウスカーソルは最初から表示されている状態を指定する。
    }
    void IconMove()
    {
        ///マウスカーソルにアイコン画像が追従するようにする。
       
        //マウス座標の取得
        var mousePos = Input.mousePosition;
        //スクリーン座標をワールド座標に変換
        var worldPos = Camera.main.ScreenToWorldPoint(new Vector3(mousePos.x, mousePos.y, 10f));
        //ワールド座標を自身の座標に設定
        HandIcon.transform.position = worldPos;
    }
    void IconChange()
    {
        ///マウスカーソル、アイコン、イラストの表示切替え
     
        ////CubismRaycasterスクリプト(モデル本体に取り付ける。)の情報を取得。
        var raycaster = GetComponent<CubismRaycaster>();
        // 重なっていても4つまで同時に判定できる接触判定情報を取得(なぜ4つなのかは不明。)
        var results = new CubismRaycastHit[4];
        // マウス座標と接触の判定
        var ray = Camera.main.ScreenPointToRay(Input.mousePosition);//マウスカーソルの当たり判定
        var hitCount = raycaster.Raycast(ray, results);//何個当たっているかを取得

        // 文字列として対象を取得
        string resultsText = "None"; //カーソルと対象が接触していない場合、"None"と定義する。
        for (var i = 0; i < hitCount; i++)//正直分かってない。カーソルと対象が接触している場合、対象の名前を返してくれる。
        {
            resultsText = results[i].Drawable.name;

        }
        if (resultsText == "HitAreaHead")//頭とカーソルが接触している時
        {

            if (Input.GetMouseButton(0))//撫でているとき
            {
                HandIcon.SetActive(false);//手アイコン消す

                NadeNadeHand.SetActive(true);//手のイラストを出す


            }
            else//撫でていない時
            {
                HandIcon.SetActive(true);//手アイコン出す

                NadeNadeHand.SetActive(false);//手のイラストを消す

            }

        }
        else//頭非接触時
        {
            //マウスカーソルが頭から離れても撫で判定を継続出来る用にする。
            if (NadeNadeHand.activeSelf&&Input.GetMouseButton(0))//撫でる事を継続している時。
            {

         
                NadeNadeHand.SetActive(true);//手のイラストを消す
            }
            else
            {
                NadeNadeHand.SetActive(false);
            }
            HandIcon.SetActive(false);//手アイコン消す

        }


        if (NadeNadeHand.activeSelf || HandIcon.activeSelf)//手のイラストもしくは手アイコンが表示されている時、
        {
            Cursor.visible = false;///カーソルを非表示
        }
        else//それ以外の時
        {
            Cursor.visible = true;///カーソルを表示
        }
    }
    void NadeNadeAnime()
    {


        //頭を撫でる際に変更するパラメータの指定(アニメーション作製)
        //[]の中にはlive2dモデルに設定している、動かしたいパラメータが上から何番目にあるかを0始まりで数えて代入する
        //今回は頭の首振り、回転、撫でた時の手の動き情報を取得している。
        var head_X = _model.Parameters[0];//角度X
        var head_Y = _model.Parameters[1];//角度Y
        var head_Z = _model.Parameters[2];//角度Z


        //頭のパーツの座標情報を取り出す。
        ///頭のパーツのメッシュ
        var mesh = _cubismRenderer.Mesh;
        //// メッシュを囲う矩形の中心(頭の座標)モデル全体の中心からの距離だと思われる。
        Vector3 meshPosition = mesh.bounds.center;
        Vector3 modelPosition = modelObject.transform.position;
        Vector3 mousePosition = Input.mousePosition;//マウスの座標を取得
        var WorldmeshPosition = meshPosition + modelPosition;
        var screenToWorldPointPosition = Camera.main.ScreenToWorldPoint(mousePosition);//マウスの座標をカメラを中心とした座標(今回は画面中央)に変換

        var Position_centaer = (screenToWorldPointPosition + WorldmeshPosition);//頭とマウスの距離


        if (NadeNadeHand.activeSelf)///撫でている時のアニメーションの制御。
        {
            //首を振り過ぎない用に各パラメータの範囲を制限※好みで調整
            if (Position_centaer.x >= 10)
            {
                Position_centaer.x = 10;
            }
            if (Position_centaer.x <= -10)
            {
                Position_centaer.x = -10;
            }
            if (Position_centaer.y >= 10)
            {
                Position_centaer.y = 10;
            }
            if (Position_centaer.y <= -10)
            {
                Position_centaer.y = -10;
            }

            //パラメータに反映
            head_X.Value = Position_centaer.x;
            head_Y.Value = Position_centaer.y * 1.5f;//好みで調整
            head_Z.Value = Position_centaer.x;//首の傾きはx軸の数値で代用。好みで調整
        }
        else//撫でていない時、顔の武向きを2fかけて正面に戻す。好みで調整
        {
            head_X.Value = Mathf.Lerp(head_X.Value, 0, Time.deltaTime * 2f);
            head_Y.Value = Mathf.Lerp(head_Y.Value, 0, Time.deltaTime * 2f);
            head_Z.Value = Mathf.Lerp(head_Z.Value, 0, Time.deltaTime * 2f);
        }
    }
    private void Update()
    {
        ///アイコンの追従を実行
        IconMove();
        ///カーソルの切り替えを実行
        IconChange();

    }
    private void LateUpdate()
    {

        ///パラメーターの反映
        NadeNadeAnime();
    }
}

情報の取得

コードの中で以下の部分について、細かい解説を行います。

public CubismModel _model;//対象の2Dモデル。アニメーション用
public CubismRenderer _cubismRenderer;//2Dモデルのメッシュ。頭の座標の取得用
public GameObject modelObject;//2Dモデルを格納しているオブジェクト。今回は座標やサイズをここで管理している。
public GameObject HandIcon;//頭に接触している時に表示されるアイコン
public GameObject NadeNadeHand;//頭を撫でている時に表示される手のイラスト(live2dモデル内のパーツとして存在)

これらの変数は、コード内で使用するために必要なオブジェクトとデータを格納するために使用されています。それぞれの変数の役割について説明します。

  1. _model:
    • この変数は2Dモデルを表します。
    • アニメーションを制御するために使用されます。
  2. _cubismRenderer:
    • 2Dモデルのメッシュやレンダリングに関連する情報を格納するための変数です。
    • 頭の座標情報を取得するために使用されます。(今回は”HitAreaHead”という名前のアートメッシュを指定)。
  3. modelObject:
    • 2Dモデルを格納しているオブジェクトを表します。
    • このオブジェクトを使用して、モデルの座標やサイズを管理しています。
    • 実際は必要ないかもしれません。
  4. HandIcon:
    • 頭に接触している時に表示されるアイコンを表します。
    • ユーザーが頭部にカーソルを合わせたときに表示されます。
  5. NadeNadeHand:
    • 頭を撫でている時に表示される手のイラストを表します。
    • Live2Dモデル内のパーツとして存在し、ユーザーが頭部を撫でた際に表示されます。

実際のヒエラルキーはこのようになっております。
_modelにはモデル、modelObjectにはLive2dを
_cubismRendererには、Drawables内にある頭と判定するアートメッシュを
セットしています。コードの一番最初にusingでlive2d関連をインポートし忘れるとエラーが出るので注意。

HandIconにはネットで拾ってきた手の画像(掴むのフリーアイコン2※改変したもの)を、
NadeNadeHandにはLive2dモデルの一部として使用している手のイラストに該当しているDrawables内のアートメッシュ(今回はArtMesh22)をセットしております。

マウスカーソルへの追従

void IconMove()
{
    ///マウスカーソルにアイコン画像が追従するようにする。

    //マウス座標の取得
    var mousePos = Input.mousePosition;
    //スクリーン座標をワールド座標に変換
    var worldPos = Camera.main.ScreenToWorldPoint(new Vector3(mousePos.x, mousePos.y, 10f));
    //ワールド座標を手のアイコンの座標に設定
    HandIcon.transform.position = worldPos;
}

こちらでマウスカーソルに手のアイコンが追従するためのコードを記載しています。

Input.mousePositionで取得できるマウス座標はスクリーン座標ですので、ワールド座標に変換することで、手のアイコンの座標と合うようになります。なぜ10fなのかは忘れました。

こちらのコードだけではマウスカーソルと手のアイコンが常に重なって表示されるため、以下のように表示を切り替える必要があります。

カーソルの切り替え

void IconChange()
{
    ///マウスカーソル、アイコン、イラストの表示切替え

    ////CubismRaycasterスクリプト(モデル本体に取り付ける。)の情報を取得。
    var raycaster = GetComponent<CubismRaycaster>();
    // 重なっていても4つまで同時に判定できる接触判定情報を取得(なぜ4つなのかは不明。)
    var results = new CubismRaycastHit[4];
    // マウス座標と接触の判定
    var ray = Camera.main.ScreenPointToRay(Input.mousePosition);//マウスカーソルの当たり判定
    var hitCount = raycaster.Raycast(ray, results);//何個当たっているかを取得

    // 文字列として対象を取得
    string resultsText = "None"; //カーソルと対象が接触していない場合、"None"と定義する。
    for (var i = 0; i < hitCount; i++)//正直分かってない。カーソルと対象が接触している場合、対象の名前を返してくれる。
    {
        resultsText = results[i].Drawable.name;

    }
    if (resultsText == "HitAreaHead")//頭とカーソルが接触している時
    {

        if (Input.GetMouseButton(0))//撫でているとき
        {
            HandIcon.SetActive(false);//手アイコン消す

            NadeNadeHand.SetActive(true);//手のイラストを出す


        }
        else//撫でていない時
        {
            HandIcon.SetActive(true);//手アイコン出す

            NadeNadeHand.SetActive(false);//手のイラストを消す

        }

    }
    else//頭非接触時
    {
        //マウスカーソルが頭から離れても撫で判定を継続出来る用にする。
        if (NadeNadeHand.activeSelf&&Input.GetMouseButton(0))//撫でる事を継続している時。
        {


            NadeNadeHand.SetActive(true);//手のイラストを消す
        }
        else
        {
            NadeNadeHand.SetActive(false);
        }
        HandIcon.SetActive(false);//手アイコン消す

    }


    if (NadeNadeHand.activeSelf || HandIcon.activeSelf)//手のイラストもしくは手アイコンが表示されている時、
    {
        Cursor.visible = false;///カーソルを非表示
    }
    else//それ以外の時
    {
        Cursor.visible = true;///カーソルを表示
    }
}

この部分でマウスカーソルと各イラストの表示切替を行っております。公式サイトのコードを理解せずそのまま利用している部分があるので間違っているかもしれませんが、マウスカーソルが接触したモデルのパーツ名を取得し、その名称が対象の物(今回はHitAreaHead)ならカーソルと画像の表示、非表示を切り替え、さらに接触した状態で右クリックを押していれば、さらに画像が切り替わるといった感じです。また、何も接触していなければ”None”という文字列を取得するようにしています。
ポイントとしては、右クリックを押したままカーソル位置が頭から離れてもカーソルと画像が同時に現れないようにするため、手の画像が表示されて無ければカーソルが表示できるという条件を設定しております。

注意点として、CubismRaycastableがセットされているオブジェクトのみが名前が取得できる対象となります。詳しくは公式サイトをご覧ください。
(https://docs.live2d.com/cubism-sdk-tutorials/hittest/)

モーションの設定

    void NadeNadeAnime()
    {


        //頭を撫でる際に変更するパラメータの指定(アニメーション作製)
        //[]の中にはlive2dモデルに設定している、動かしたいパラメータが上から何番目にあるかを0始まりで数えて代入する
        //今回は頭の首振り、回転、撫でた時の手の動き情報を取得している。
        var head_X = _model.Parameters[0];//角度X
        var head_Y = _model.Parameters[1];//角度Y
        var head_Z = _model.Parameters[2];//角度Z


        //頭のパーツの座標情報を取り出す。
        ///頭のパーツのメッシュ
        var mesh = _cubismRenderer.Mesh;
        //// メッシュを囲う矩形の中心(頭の座標)モデル全体の中心からの距離だと思われる。
        Vector3 meshPosition = mesh.bounds.center;
        Vector3 modelPosition = modelObject.transform.position;
        Vector3 mousePosition = Input.mousePosition;//マウスの座標を取得
        var WorldmeshPosition = meshPosition + modelPosition;
        var screenToWorldPointPosition = Camera.main.ScreenToWorldPoint(mousePosition);//マウスの座標をカメラを中心とした座標(今回は画面中央)に変換

        var Position_centaer = (screenToWorldPointPosition + WorldmeshPosition);//頭とマウスの距離


        if (NadeNadeHand.activeSelf)///撫でている時のアニメーションの制御。
        {
            //首を振り過ぎない用に各パラメータの範囲を制限※好みで調整
            if (Position_centaer.x >= 10)
            {
                Position_centaer.x = 10;
            }
            if (Position_centaer.x <= -10)
            {
                Position_centaer.x = -10;
            }
            if (Position_centaer.y >= 10)
            {
                Position_centaer.y = 10;
            }
            if (Position_centaer.y <= -10)
            {
                Position_centaer.y = -10;
            }

            //パラメータに反映
            head_X.Value = Position_centaer.x;
            head_Y.Value = Position_centaer.y * 1.5f;//好みで調整
            head_Z.Value = Position_centaer.x;//首の傾きはx軸の数値で代用。好みで調整
        }
        else//撫でていない時、顔の武向きを2fかけて正面に戻す。好みで調整
        {
            head_X.Value = Mathf.Lerp(head_X.Value, 0, Time.deltaTime * 2f);
            head_Y.Value = Mathf.Lerp(head_Y.Value, 0, Time.deltaTime * 2f);
            head_Z.Value = Mathf.Lerp(head_Z.Value, 0, Time.deltaTime * 2f);
        }
    }

この部分ではドラッグした際の動きを設定しております。
_model.Parameters[]の[]内の数値はLive2dモデルのパラメータの並びに依存するので各自で調整してください。今回は頭の動きを設定してあるパラメータ(首振り上下左右、首の傾き)にさらに手の動きを付け加えた箇所を利用しています。対象のパラメータに物理演算や他のスクリプトによる制御を設定しているとうまく動作しない恐れがあります(検証中)。

モデルやカーソルの座標は直接取得すると基準が違うため、カメラ座標、つまり画面中央からどのくらい離れているかを両方の基準に設定し、互いがどれくらいX座標、Y座標がそれぞれ離れているかでパラメータの値が変動するようになっております。細かい調整は各自のモデルと相談してください。

モデルに反映

    private void Update()
    {  
        ///アイコンの追従を実行
        IconMove();
        ///カーソルの切り替えを実行
        IconChange();

    }
    private void LateUpdate()
    {
        ///パラメーターの反映
        NadeNadeAnime();
    }

最後にモデルに反映していきます。それぞれを関数に格納しているので、private void Update()などで実行していきます。

NadeNadeAnime()、つまりLive2dのアニメーションのためのパラメーターの変更を、private void LateUpdate()を作製し、その中で実行します。

今回WebGLに出力する際に発覚したのですが、Live2dモデルをpublic CubismModelで取得し、パラメーターの変更をprivate void Update()上で反映させるとUnity上では動くのに、実際に出力すると動かないという現象が起きました。

公式によると、UnityのアニメーションはUpdate()の後に処理されるため、Update()内でパラメータの値を変更するとアニメーションに上書きされてしまうからだそうです。https://docs.live2d.com/cubism-sdk-tutorials/about-parameterupdating-of-model/

ですので、private void Update()上でパラメーターを反映させるようにしておきましょう。

最後に

これらを参考にしていただければ、とりあえず頭を撫でられるようになると思います。後は撫でた時にキャラが目をつぶるようになったり、もっと全体に動きが反映されるようにするとさらに良くなると思います。

また、応用すれば色んな所(意味深)を触れると思いますので、セクハラは程々に。

以上、UnityでLive2Dモデルの頭を撫でる方法でした。

タイトルとURLをコピーしました