sh1’s diary

プログラミング、読んだ本、資格試験、ゲームとか私を記録するところ

Unity ダメージ表示のアニメーションを(Final Fantasy 5, 6)っぽくやる

最初に成果物を提出。こんな感じになりました。それっぽく見えるとうれしいですが、ROM を解析して厳密にやったわけではないです。

f:id:shikaku_sh:20200221162806g:plain:w500
このポロポロ感がほしかった

データの詳細は、記事の最後に今回のサンプルデータを公開しているので、そちらをご確認ください。

実装したことの流れ

  1. 画面のクリック。(エフェクト開始の基点のこと)
  2. ランダムにダメージ値を生成する。
    • 一文字する Image として生成して、Canvas 上に並ぶようにする。
    • 画像は0-9までの数字だけを扱う。
  3. 一文字ずつ飛び跳ねるアニメーションを実行する。(跳ねる回数は2回)
  4. アニメーションが終了したら、ゲームオブジェクトを Destroy する。

こんな感じがよいのではないかと思いました。

前回に書いた「FF5 のモンスターを倒した演出」に続いてになりますが、今回は「FF4、FF5のダメージ表示アニメーションをUnityで再現する」という記事が元ネタになっています。

すでに先駆者が居られたわけですが、(個人的にですが)作り込みに満足できなかったので、自分でもやってみた感じです。

いくらかブラッシュアップできていたら、うれしく思うところです。

数字を画像表示する方法について

フォントではなくて、数字の画像 (png) を使ってどうやって表示するのか、というところは以下の記事が参考になりました。

SpriteNo というクラスを基本にしている仕組みで、Image 型と SpriteRenderer 型をサポートしています。賢い作り方だと思いました。

自分は、Image クラスを基本に作ろうと思っていたので参考にさせていただきました。変更点のひとつに、スプライトの並びに自分自身の横幅が計算に含まれていないように思いました。なので、width を入れてあげるとこうなりました。

private void UpdateLayouts()
{
    var textLenght = _Text.Length;

    for (var i = 0; i < _Components.Count; i++)
    {
        var position = Vector3.zero;
        var width = _Components[i].GetComponent<RectTransform>()?.sizeDelta.x ?? 0;

        switch (_LayoutType)
        {
            case LayoutType.Center:
                position.x = (i - (textLenght - 1) / 2.0F) * width * _Span;
                break;
            case LayoutType.Left:
                position.x = i * width * _Span;
                break;
            case LayoutType.Right:
                position.x = -(textLenght - 1 - i) * width * _Span;
                break;
        }

        _Components[i].transform.localPosition = position;
    }
}

上記記事のとおりすすめると、ここで画像による数字の表示に成功するわけです。一応使用した画像はつぎのですが、お手製なので正確には違っているんじゃないかと思います。

f:id:shikaku_sh:20200221163000p:plain
ドットの数字 8x8

画像の分割

これもたぶん初めてだったので、メモしておきます。

インポートした画像の Assets の設定を開きます。

f:id:shikaku_sh:20200221164330p:plain:h400
設定の例

  • Sprite Mode = Multiple
  • Filter Mode = Point (no filter)
  • Sprite Editor を開く
  • Slice を選択
  • Type = Grid By Cell Size
    • Pxel Size X=8, Y=8
  • Slice を選択

f:id:shikaku_sh:20200221164400p:plain:w500
スライスの例

最終的な Assets はこんな感じで、うまくできました。

f:id:shikaku_sh:20200221164436p:plain:h180
バラバラになった

アニメーションの実装

FF5 はダメージ表示がコミカルです。上の桁の数字から順番に時間差で飛び跳ねます。(2回だけ)

これをどのように実装するとよいのか考えたのですが、(初の)アニメーションを使ってみようと思いました。

AssetsAnimationAnimation Controller を 追加します。

  • Animation (BoundAnimation)
  • Animation Controller (BoundAnimationController)

シーンの中の Canvas に空の Game Object を追加して、BoundAnimation を Add Component してやります。

Animation Controller のほうは、アニメーターの画面を開いて、Entry から BoundAnimation に繋ぐだけの単純なアニメーションのシナリオを設定します。

最後に BoundAnimation をダブルクリックしてアニメーションの画面を開いて、(シーンの Game Object が選択された状態で)Add Property GameObject: Anchored Position.Y を次の表のように「Curves」から設定します。

このツールの使い方は「マニュアル - アニメーションカーブの使用」が参考になりました。

f:id:shikaku_sh:20200221163841p:plain:w400
もとのサイズが8なので16跳ねる設定

パスの引き方は「Adobe - パスとシェイプ」などを参考にしました。ポイントだったのは、アンカーポイントを右クリックして、「Break」にすることで方向線を別々に制御できます。(放物線を描くためには、おそらく必須だと思います)

f:id:shikaku_sh:20200221163506p:plain:w400
なかなか気づかなかった

最後にアニメーション終了の位置に CompleteAnimation のメソッドを登録しておきました。対応するクラスは、以下のようなものを用意しておきます。

f:id:shikaku_sh:20200221163936p:plain:w300
シンプルにメソッドをコールしてるだけです

public class SpriteAnimation : MonoBehaviour
{
    public int Length { get; set; }
    public int No { get; set; }

    public event EventHandler Completed;

    public void Initialize(int no, int length)
    {
        Length = length;
        No = no;
    }

    public void CompleteAnimation()
    {
        Completed?.Invoke(this, EventArgs.Empty);
    }
}

アニメーションの実装 (Script)

以下のコードを SpriteNo(基底クラス)に追加しました。

[SerializeField]
private RuntimeAnimatorController _AnimationController = null;

[SerializeField]
[Range(0.1F, 5.0F)]
private float _AnimationSpeed = 1.0F;

[SerializeField]
[Range(0.0F, 1.0F)]
private float _AnimationDelaySeconds = 0.14F;

[SerializeField]
[Range(0.0F, 5.0F)]
private float _DestoryDelaySeconds = 0.8F;

public void ClearText()
{
    _Components.Where(component => component != null)
                                    .ToList()
                                    .ForEach(component => DestroyImmediate(component.gameObject));
    _Components.Clear();
}

private void UpdateComponents()
{
    UpdateSprites();
    UpdateLayouts();

    if (_AnimationController != null)
    {
        AttachAnimation();
    }
}

private void AttachAnimation()
{
    for (var i = 0; i < _Components.Count; i++)
    {
        StartCoroutine(this.DelayAction(i * _AnimationDelaySeconds, (int no, int length) =>
        {
            var animation = _Components[no].gameObject.AddComponent<SpriteAnimation>();
            var animator = _Components[no].gameObject.AddComponent<Animator>();

            animation.Initialize(no + 1, _Components.Count());
            animator.runtimeAnimatorController = _AnimationController;
            animator.speed = _AnimationSpeed;
            animator.enabled = true;

            // 末尾のデータ
            if (no + 1 == length)
            {
                animation.Completed += (sender, args) =>
                {
                    StartCoroutine(this.DelayAction(_DestoryDelaySeconds, () =>
                    {
                        ClearText();
                        Destroy(transform.gameObject);
                    }));
                };
            }

        }, i, _Components.Count));
    }
}

こうしておくと、一文字ずつ実行するエフェクトとロジックを切り分けしておくことができるので、好ましいと思いました。

プロパティをいじってみる

f:id:shikaku_sh:20200221163415p:plain:w250
ディレイなしにする

DelaySeconds を 0.0F にしてやると、同時に数字が動くようになるので FF6 っぽいアニメーションになります。

f:id:shikaku_sh:20200221163119g:plain
これはこれでいいね

ただ、これでいいなら、パック化できそうなど、軽量化する工夫の余地はありそうです。

FF5, 6 に対応、かつ、ちょっとした変更ならプロパティで対応できたし、アニメーションの雰囲気はアニメーションカーブで変更に対応できそうです。

カラーの変更に対応することで、回復(緑)やクリティカル(黄)なども表現しやすくなります。

f:id:shikaku_sh:20200221163330p:plain:w500
これなら回復しそうですわ

ラグナロクオンラインみたいに文字が伸びたり薄くなって消えたりするには、アニメーションカーブのプロパティを追加してやるとよさそうですね。

ガチくんの FF5 コンテンツがおもしろかったので、いろいろ遊んでしまった。

サンプル

今回のサンプルは GitHub に公開しています。

f:id:shikaku_sh:20200221164743p:plain
ナッツイーター

参考

Unity ゲームエフェクト マスターガイド

Unity ゲームエフェクト マスターガイド

  • 作者:秋山 高廣
  • 発売日: 2019/07/19
  • メディア: 単行本(ソフトカバー)