sh1’s diary

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

Unity モンスターを倒したエフェクト(Final Fantasy 5)っぽくやる

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

f:id:shikaku_sh:20200219152101g:plain:w500
気持ちいい消え方ー

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

実装したことの流れ

下記は、私が FF5 の敵を倒す演出を見て思って実装した内容。“らしい”かもしれませんが、実際どうなのかは知らないです。

  1. 敵を倒す。(エフェクト開始の基点のこと。ボタンの押下)
  2. 敵の色味を半透明の紫色に変更。
    • 一定値以上の色(黒い線の部分)は色を半透明にするだけ。ピクセルHSV の V(明度の黒っぽさ)で評価して、基準値以下は半透明にするだけとした。
    • 半透明は一定値をすべてのピクセルにセットする。ただし、a の値が 0x00(透明)の部分は無視する。
  3. 指定秒数で、敵の透明度を更に下げる。
  4. 指定秒数で、敵のピクセルが少しずつ霧のようにとけて消える演出 1 をする。
    • 同時に指定秒秒数で、透明度を更にさげて 0x00 にする。
  5. エフェクトが完了したイベントを通知しておく。

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

紫色にするポイント

画像処理をする際は、ピクセルデータの配列にアクセスする必要があります。

var color = TargetTexture2D.GetPixels32();
var width = TargetTexture.width;
var height = TargetTexture.height;

GetPixels メソッドを実行する際は、Assets のインポートデータの設定で「Read/Write Enabled」が有効になっている必要があります。(png ファイルなので Format もついでに指定してあげました)

f:id:shikaku_sh:20200220093054p:plain:h400
画像処理には必須の設定

ただし、この設定は注意が必要でインポートデータの使用するモリー量が2倍になるみたいです。詳細は公式の「テクスチャ - Advanced settings」を参照すると以下のコメントがあります。なので、このサンプルのように比較的小さなサイズの画像を対象にしたほうが推奨のエフェクトといえるかもしれないです。

このボックスをチェックすると、スクリプト関数 (Texture2D.SetPixels、Texture2D.GetPixels、その他の Texture2D 関数など) からテクスチャデータにアクセスが可能になります。ただし、テクスチャデータのコピーが作成され、テクスチャアセットに必要なメモリ量が 2 倍になるため、絶対に必要な場合にのみこのプロパティーを使用してください。この機能は非圧縮テクスチャと DXT 圧縮テクスチャに対してのみ有効です。他のタイプの圧縮テクスチャを読み込むことはできません。このボックスは、デフォルトでは無効になっています。

for (int y = 0; y < _ImageHeight; y++)
{
    for (int x = 0; x < _ImageWidth; x++)
    {
        var c = _Pixels[(_ImageWidth * y) + x];

        // ピクセルが透明のとき
        if (c.a == 0x00) continue;

        // 明度
        var v = GetBrightness(c);
        var vp = v / 255.0F; // 明度の割合

        if (v > LineBrightness) // 線と区別するライン
        {
            // 紫色を基準に明度の割合を当てる
            c.r = (byte)(0xEE * vp);
            c.g = (byte)(0x66 * vp);
            c.b = (byte)(0xFF * vp);
        }

        // すこし薄くする
        c.a = DefeatedWhiteStart;

        _Pixels[(_ImageWidth * y) + x] = c;
    }
}

private byte GetBrightness(Color32 color)
{
    if (color.r >= color.g && color.r >= color.b) return color.r;
    if (color.g >= color.r && color.g >= color.b) return color.g;
    return color.b;
}

GetBrightness は System.Drawing - GetBrightness メソッドのコードを参考にしてもよかったかも。

ともかく、黒色の部分以外は紫色 #EE6FF を基準にして、ピクセルの明度にあわせて色味を落とす設定にしてみたのが、一番しっくりしました。

FF5 は紫色に変えるときに白い部分が目立たなくなるなどの特徴があると思ったので、周囲の色をいくらか評価している気もしました。

敵を霧のように消す演出

この部分は、FF5 のやりかたと異なります。私は、斜線で消しました。

f:id:shikaku_sh:20200219152604p:plain:w400
穴が空いた瞬間

FF5 は、最初になんらかのパターンのあるランダム(っぽい)空白の穴がいくつも出現します。その穴が、次第に上方向と若干左右に広がって消えていきます。

f:id:shikaku_sh:20200219152655p:plain:w400
山の形をつくって消える

特徴として、最後は二等辺三角形のような山型が残ります。大きな部位も小さな部位も最後まで残って(大きな部位ほどよく消える)、最後の瞬間までなんとなく形状が残っている気がします。(気のせいかも)

「God is in the details」というやつで、パッっと消すだけよりは、なんか“らしい”演出があったほうが幸せですね。

この演出にはコルーチンを利用してみました。こうした演出には向いている気がしました。

その他のポイント

Color は特にコメントがなかったけど、Mesh.colors だと次のように「パフォーマンス上の理由から、colors32 の使用を検討してください」とコメントされています。

For performance reasons, consider using colors32 instead. This will avoid byte-to-float conversions in colors, and use less temporary memory.

特に理由がなければ、Color32 を使ったほうがよいかもしれません。

サンプル

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

エフェクトがデフォルトの今、画像処理でエフェクトをかけるのも微妙な気もしたけど、2D だしね。細部ではまだやれることもあるんじゃないかと。

参考

FINAL FANTASY V Original Sound Track Remaster Version

FINAL FANTASY V Original Sound Track Remaster Version