パスワードを忘れた? アカウント作成
15037759 journal
日記

yumeの日記: Unity制作 メデューサ・ゲーム #54

日記 by yume

メデューサ・ゲーム(仮)
更新なし
--

前回、ダイアログの顔画像はSpineで組み立てて書き出し……という工程をやっていて
「それなら初めからSpineスケルトンをここに当てはめればいいんじゃないか?」ということに気がついた。
仮に右目を少し細く……というような修正をしたくなったら、今のやり方だとすべてのPNGを書き出し直さなきゃならん。
それにちょっとしたアニメーションもできるようになると、表現の幅も広がりそうだ。

やらなきゃいけないことは:
A1. ダイアログに沿うようにスケルトンを円形に切り抜く
A2. それをUI上に正しく配置する

できればやりたいことは
B1. 偏移を少しだけ滑らかに(現状はパッと切り替わるだけなので)
B2. どうせなら瞬きとかさせてもいいな。興奮してる顔なら息遣いとかもあっていいか?

A1.
さてまずこれが厄介だ。Spineにはマスク機能があるが、このマスクは二重にかけることができない(重なりさえしなければ複数置くことはできるけど)
すでに瞳のためにマスクを使っているので、Spine上でマスクをかけるのはもう無理だ。
となるとUnity側でマスクをかけなければならない。

SpineをUnityにインポートすると、Spineが用意したGameObjectをそのままヒエラルキーに配置できる。
その中のコンポーネントにSkeleton Mecanimがあるが、これの設定にUnityのSpriteのような「Mask Interaction」がある。これをInside Maskに設定し、
親オブジェクトにSprite Maskを設定するとうまくいった。
これはようするにShaderLabのStencilを使っているようで、マテリアルを直接いじっても同じことができるようだ。

A2.
ダイアログはUIなので、普通にSkeletonをUIに順序よく配置するためには、Sorting Groupなんかをうまく使っていかなければならないようだ。
少し苦戦したが、最終的には:
・EmoteCircleというCanvasオブジェクトを用意、それにSorting Groupをアタッチしておく
・EmoteCircleの中に、Circle(下地の円)、Actors(Mistyとかが入る)、CircleOutLine(枠線)を置き、それぞれのSortingLayerを-1、0、1とする。
というだけでなんとかなった。

B1
「crossfade」というメソッドが、そのままアニメーションを指定時間の長さでうまくブレンドしてくれた。
というかこれすごいな。Spine上ではアニメーションを設定してない(1フレームでポーズを設定しただけ)なのに勝手にヌルッと動く。

B2
まばたきはこれまで散々やってきたので、そんなに難しくはないだろう。
第二話のステージ7で、ピクセルピープルがみんなでわーわー言うシーンがあるので、それをまずサンプルとしてみる。

さて、これらのことを実行するスクリプト「EmoteAnimation」を書いた。次のようになった。

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

/// <summary>
/// ダイアログの顔画像部分のアニメーションを管理
/// </summary>
public class EmoteAnimation : MonoBehaviour
{
        /// <summary>
        /// Actorのアニメーターのリスト
        /// </summary>
        [SerializeField] private List<Animator> actors = default;

        /// <summary>
        /// 現在表示されているActor
        /// </summary>
        [SerializeField] private Animator nowActor = default;

        /// <summary>
        /// Emoteアニメーションの偏移時間(正規時間)
        /// </summary>
        private readonly float fadeDuration = 0.2f;

        /// <summary>
        /// 表情アニメーションのディレクトリ
        /// </summary>
        private readonly string emoteDirectory = "Emotes/";

        /// <summary>
        /// Emoteを実行する。
        /// </summary>
        public void DoEmote(string actorName, string emote)
        {
                Animator actor = SelectActor(actorName);
                if (CheckActorSwitch(actor)) //Actorが切り替わったなら即再生
                {
                        actor.Play($"{emoteDirectory}{emote}");
                }
                else //Actorが同じなら、フェード再生
                {
                        actor.CrossFade($"{emoteDirectory}{emote}", fadeDuration);
                }
        }

        /// <summary>
        /// EmoteをFadeなしで即座に実行する。
        /// </summary>
        /// <param name="actorName"></param>
        /// <param name="emote"></param>
        public void DoEmoteInstant(string actorName, string emote)
        {
                Animator actor = SelectActor(actorName);
                CheckActorSwitch(actor);
                actor.Play($"{emoteDirectory}{emote}");
        }

        /// <summary>
        /// Actorが切り替わったかをチェック。
        /// </summary>
        /// <param name="actor">次のActor</param>
        /// <returns>切り替わったならTrue</returns>
        private bool CheckActorSwitch(Animator actor)
        {
                if (actor.gameObject != nowActor.gameObject)
                {
                        nowActor.gameObject.SetActive(false);
                        actor.gameObject.SetActive(true);
                        nowActor = actor;
                        return true;
                }
                return false;
        }

        /// <summary>
        /// 表示するActorを決定する
        /// </summary>
        /// <param name="actorName"></param>
        private Animator SelectActor(string actorName)
        {
                for (int i = 0; i < actors.Count; i++)
                {
                        if(actors[i].name == actorName)
                        {
                                return actors[i];
                        }
                }
                return null;
        }
}

DoEmoteInstantはアニメーションの偏移があるとかえって都合が悪いとき(ピクセルピープルは複数の人間を1つのスケルトンで動かしてるので、スプライト切り替えがFadeだとうまくないパターンが1つあった)のために用意した。
大元を辿ると、まず
おうさま PixelPeople Watson しかし それも もうあんしん[@]ゆうしゃさまが われわれの せかいを[@]あんぜんに ほかん してくれる
こういうテキストがあって、タブで4つのテキストに区切られている。このうち2つめがActorで、3つ目がEmoteだ。
DoEmoteInstantを呼び出す場合は、Emoteの前に*をつける
みんな PixelPeople *ExcitePeople わーっ ゆうしゃ! ゆうしゃ!
それをダイアログ側のスクリプトが判定し、ダイアログ側がDoEmoteかDoEmoteInstantかを選ぶ……ということにしたが、どちらかというとダイアログ側からはとにかくEmoteのstringを投げて、EmoteAnimation側で*が入ってるかどうかを判定した方がよかったかな? 判定のためのクッションのようなメソッドが一つあって、それだけがPublicの方がスマートだったかもしれん。まぁいいか。

結果1
結果2

まぁ上々。他に少し偏移がぎこちないものがあったが、アニメーション側で微調整できるだろうか?

この議論は賞味期限が切れたので、アーカイブ化されています。 新たにコメントを付けることはできません。
typodupeerror

クラックを法規制強化で止められると思ってる奴は頭がおかしい -- あるアレゲ人

読み込み中...