yumeの日記: Unity制作 メデューサ・ゲーム #54
メデューサ・ゲーム(仮)
更新なし
--
前回、ダイアログの顔画像は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の方がスマートだったかもしれん。まぁいいか。
まぁ上々。他に少し偏移がぎこちないものがあったが、アニメーション側で微調整できるだろうか?
Unity制作 メデューサ・ゲーム #54 More ログイン