[Spine]女の子の視線を誘導する

タッチ方向に視線を移動させる

Live2Dのデモムービーなどでよくあるタッチ方向に視線が誘導される女の子を作ってみる。だいぶ力技なのでこれが正解なのかは解らない。こういった視線誘導は恐らくLive2Dの方が得意なのではないかと思う。だが今回あえてSpineでやってみる。

下準備

まず女の子を描く。パーツは画像の通りに分けている。瞬きをさせたいので顔のパーツに目玉分の穴を開けている。目が閉じた時用のまぶたを別で用意する。

呼吸のアニメーションを作る

Spineに取り込んで、とりあえず女の子に呼吸させるので体を少し上下に動かす。body_bressとかいう名前で適当にそれ用のボーンを作り、上下移動させる。体のメッシュをbody_bressボーンに少しウェイトを付けつつ、body_bressボーンをトランスフォームコンストレイントで首のボーンに連動させることで下層の顔パーツ全体も上下する。これでなんとなく女の子が息づいてるかのように見える。あと適当に髪の毛を揺らしておく。

瞬きアニメーションを作る

呼吸のアニメーションを作ったら、次は瞬きアニメーションを作る。新規でアニメーションを作り、顔パーツにメッシュを割って瞬き用のボーンを作りウェイトを付ける。瞬き用ボーンは目の上下に2つ作り、トランスフォーム・コンストレイントでトランスフォームを−50とかにして片方のボーンと逆の動きをさせる。

先にアニメーションを付けてからウェイト調整した方がやりやすいかもしれない。

顔のメッシュを調整できたら、まぶたを調整する。まぶたを瞬きに応じて変形させて、閉じた瞬間だけ閉じた時用のまぶたに切替える。なので1つのスロットにイメージを2つ格納して、オンオフで画像を切り替える。今回はline_upスロットにふたつのまぶたイメージを格納している。

一回の瞬きは8フレにして、そのうち3フレは目を閉じた状態にしている。一瞬なのでまぶたのメッシュをいじった際の形の微妙さはさほど気にならない。

とりあえず瞬きができた。

視線を誘導する

続いて視線誘導をするために縦方向ボーンと横方向ボーンを作る。右目左目で移動する幅が横も縦も違うので、縦横のボーンを分けないと目玉が辺な位置に移動してしまう。

ためしに横方向ボーンを縦方向にもズラしてみる。このように左右の目で影響度が違うので片方の目がズレてしまう。

プレビューでアニメーション登録

プレビューパネルを開いて瞬きと呼吸のアニメーションがどんな感じにブレンドされているか確認できる。ここで決めるトラック番号はあくまで仮なので、ここで決めた設定がUnity上で影響することはない。

Unityで編集作業

過去記事でSpineデータのUnity取り込み方法は記述したので、基本的なことはそれを見返すとして、とりあえずSpineデータをUnityにぶっこむ。ぶっこんで色々設定する。

UnityでSpineのアニメーションを呼び出すにはSkeletonAnimationコンポーネントを呼び出して、SetAnimation関数を使う。第一引数がトラック番号。第二引数がアニメーション名、第三引数がリピートするかどうか。今回使うアニメーションは2つなので、トラック0と1を仕様する。

skeletonAnim = girl.GetComponent<SkeletonAnimation>();
skeletonAnim.state.SetAnimation (0, "wink", true);//0トラックにwinkアニメーションを呼ぶ
skeletonAnim.state.SetAnimation (1, "bress_anim", true);//1トラックにbress_animアニメーションを呼ぶ

目玉と首と顔の移動はゆっくりにしたいのでLeap関数を使う。Leap関数に関しては過去記事でも取り上げている。ここまで超ざっくりとした説明だが、スクリプトを以下に記載しておく。だいぶゴリ押しなスクリプトである。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Spine;//忘れずに!
using Spine.Unity;//忘れずに!

public class spine_girl : MonoBehaviour
{
    public GameObject girl;//キャラオブジェクト
    SkeletonAnimation skeletonAnim;//spineのSkeletonAnimation
    public GameObject neck;//首ボーン
    public GameObject face;//顔ボーン
    public GameObject t_yoko;//視線誘導 横ボーン
    public GameObject t_tate;//視線誘導 縦ボーン
    float yoko_y;//t_yokoの縦座標の初期値を取得 この数値で固定する
    float tate_x;//t_tateの横座標の初期値を取得 この数値で固定する
    Vector3 neckAngle;//首の初期角度
    Vector3 faceAngle;//顔の初期角度


    void Start()
    {
        yoko_y = t_yoko.transform.position.y;
        tate_x = t_tate.transform.position.x;

        neckAngle = neck.transform.eulerAngles;
        faceAngle = face.transform.eulerAngles;

        skeletonAnim = girl.GetComponent<SkeletonAnimation>();
        skeletonAnim.state.SetAnimation (0, "wink", true);//0トラックにwinkアニメーションを呼ぶ
        skeletonAnim.state.SetAnimation (1, "bress_anim", true);//1トラックにbress_animアニメーションを呼ぶ
   }

  void Update()
  {
    if (Input.GetMouseButton(0))//タッチ開始
    {
      Vector3 moupos = Camera.main.ScreenToWorldPoint(Input.mousePosition);//タッチ座標をワールド座標に変換

      //タッチ座標とt_yokoの位置を同期する
      //t_yokoの可動域はx座標を-3~1、y座標は固定。
      //タッチ座標が上限を超えた場合、上限値に固定する。
      if (moupos.x > 1)
      {
        t_yoko.transform.position = new Vector2(Mathf.Lerp(t_yoko.transform.position.x, 1, Time.deltaTime*3), yoko_y);
      }
      else if (moupos.x < - 3)
      {
        t_yoko.transform.position = new Vector2(Mathf.Lerp(t_yoko.transform.position.x, -3, Time.deltaTime*3), yoko_y);
      }
      else
      {
        t_yoko.transform.position = new Vector2(Mathf.Lerp(t_yoko.transform.position.x, moupos.x, Time.deltaTime*3), yoko_y);
      }

      //タッチ座標とt_tateの位置を同期する
      //t_tateの可動域はy座標を-3~2、x座標は固定。
      //タッチ座標が上限を超えた場合、上限値に固定する。
      //ついでに首と顔の向きも変更する。変更する数値はmoupos.yを流用する。
      if (moupos.y > 2)
      {
        t_tate.transform.position = new Vector2(tate_x,Mathf.Lerp(t_tate.transform.position.y, 2, Time.deltaTime*3));
        neck.transform.eulerAngles = new Vector3(neckAngle.x,neckAngle.y,Mathf.LerpAngle(neck.transform.eulerAngles.z,neckAngle.z -1, Time.deltaTime*3));
        face.transform.eulerAngles = new Vector3(faceAngle.x,faceAngle.y,Mathf.LerpAngle(face.transform.eulerAngles.z,faceAngle.z -2, Time.deltaTime*3));
      }
      else if (moupos.y < - 3)
      {
        t_tate.transform.position = new Vector2(tate_x,Mathf.Lerp(t_tate.transform.position.y, -3, Time.deltaTime*3));
        neck.transform.eulerAngles = new Vector3(neckAngle.x,neckAngle.y,Mathf.LerpAngle(neck.transform.eulerAngles.z,neckAngle.z +1.5f, Time.deltaTime*3));
        face.transform.eulerAngles = new Vector3(faceAngle.x,faceAngle.y,Mathf.LerpAngle(face.transform.eulerAngles.z,faceAngle.z +3, Time.deltaTime*3));
      }
      else
      {
        t_tate.transform.position = new Vector2(tate_x,Mathf.Lerp(t_tate.transform.position.y, moupos.y, Time.deltaTime*3));
        neck.transform.eulerAngles = new Vector3(neckAngle.x,neckAngle.y,Mathf.LerpAngle(neck.transform.eulerAngles.z,neckAngle.z + -(moupos.y)/2, Time.deltaTime*3));
        face.transform.eulerAngles = new Vector3(faceAngle.x,faceAngle.y,Mathf.LerpAngle(face.transform.eulerAngles.z,faceAngle.z + -(moupos.y), Time.deltaTime*3));
      }
    }
  }
}

一応完成したのがこれだ。ドラッグ方向に視線が移動している。かつ呼吸と瞬きのアニメーションがループされている。これにプラスして2.5Dメッシュ変形とかやりたいのだが、めちゃくちゃ修正が難しそうなのでやらない。

コメント