MENU

【Unity】最強のゲームクリエイターに俺はなる | Interactiveな水の実装(2D)

はいーーーー「ゴジラ-1.0」をもう一回観てきたFORM_07でーーーす




っぱIMAXレーザーなんよ。










前回↓は近況報告でしたが、、、




【Unity】最強のゲームクリエイターに俺はなる | 近況報告 - FORM_07’s blog








予告通り今回は2Dゲームにおける水面(波打ち)の実装についてやっていきましょう!







今回実装していくのはこんなかんじの水です。


こりゃすごい


有料アセットと比べても遜色ない挙動だと思います。







それではやっていきましょう!



準備(オブジェクト設定)

Sprite Shape生成


まず、水の実装をSprite Shapeという機能を使って行っていくので軽く説明します。







Sprite Shapeというのは簡単に言うと変形可能なSpriteのことです。







水面が波打つ際の形状変化を表現するためにSprite Shapeを用いていきます。







とりあえず「Hierarchy上で右クリック」→「2D object」→「Sprite Shape」→「Closed Shape」で、オブジェクトを生成しましょう。











すると、、、








↑こんな見た目のオブジェクトが生成されて、Inspectorを確認するとSprite Shape RendererSprite Shape Controllerというコンポーネントがアタッチされているのがわかると思います。



Element設定


次にSprite Shape ControllerコンポーネントEdit Splineを選択します(SceneビューのToolsからも選択できます)。




そうするとSprite内にElement(丸いやつ)が4つ表示されるので、どれか1つ選択してElement Inspectorを表示させてください。







0~3番目のElementPositionをそれぞれ(x, y) = (-1, -1), (-1, 0), (1, 0), (1, -1)に設定します。




(Sprite Shapeの仕様で、左下のElementから時計回りに0からIndex(番号)が振られています)







Tangent ModeHeightはすべてのElement共通でそれぞれLinear、0.1に調整します。











ここまでの操作で↑こんなかんじになったらOKです。



Collider設定


そしたらBox Collider 2DコンポーネントをアタッチしてIs Triggerにチェックをいれます。






C#でInteractiveな水面を実装する


さて、ここからはプログラミングの力でSprite ShapeをInteractiveにしていきましょう!


コード

using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.U2D;

public class Water : MonoBehaviour
{
    private SpriteShapeController sc;
    private Spline spline;
    private float scale;
    private Vector2 basePointPos;
    private int n;
    private float[] vec;
    [SerializeField] private float interval = 0.45f;
    [SerializeField] private float maxWave = 0.5;
    [SerializeField] private float minWave = 0.05f;
    [SerializeField] private float k = 0.09f;
    [SerializeField] private float waterPow = 0.05f;
    [SerializeField] private float waveRate = 0.65f;
    [SerializeField] private float waveTime = 0.08f;

    private void Start()
    {
        sc = GetComponent<SpriteShapeController>();
        spline = sc.spline;
        basePointPos = spline.GetPosition(1);
        scale = transform.localScale.x;
        n = (int)(-basePointPos.x * scale * 2 / interval) + 1;
        vec = new float[n-2];
        for (int i = 0; i < n-2; i++) vec[i] = 0;
        Vector3 p = basePointPos;
        for (int i = 2; i < n; i++)
        {
            p += interval * Vector3.right / scale;
            spline.InsertPointAt(i, p);
            spline.SetTangentMode(i, ShapeTangentMode.Continuous);
            spline.SetLeftTangent(i, 0.1f * interval * Vector3.left / scale);
            spline.SetRightTangent(i, 0.1f * interval * Vector3.right / scale);
            spline.SetHeight(i, 0.1f);
        }
    }

    private void FixedUpdate()
    {
        for (int i = 2; i < n; i++)
        {
            Vector3 pos = spline.GetPosition(i);
            vec[i-2] -= k * pos.y;
            vec[i-2] *= 0.9f;
            pos += vec[i-2] * Vector3.up;
            spline.SetPosition(i, pos);
        }
    }

    private async void WaveCreate(Collider2D collision)
    {
        int i = (int)((collision.transform.position.x - transform.position.x - basePointPos.x * scale) / interval) + 1;
        if (i < 2) i = 2;
        else if (i > n - 1) i = n - 1;
        float y_collisionVelocity = collision.GetComponent<Rigidbody2D>().velocity.y;
        Vector3 pos = spline.GetPosition(i);
        Vector3 dv = pos + waterPow * y_collisionVelocity * Vector3.up;
        if (dv.y > maxWave) dv = new(pos.x, maxWave, 0);
        else if (dv.y < -maxWave) dv = new(pos.x, -maxWave, 0);
        spline.SetPosition(i, dv);
        int l = i;
        int r = i;
        float y = dv.y;
        if (y * y != maxWave * maxWave) y *= waveRate * Random.Range(0.8f, 1.2f);
        while (true)
        {
            if (y * y < minWave * minWave) return;
            l--;
            if (l > 1)
            {
                pos = spline.GetPosition(l);
                spline.SetPosition(l, new(pos.x, y, 0));
            }
            r++;
            if (r < n)
            {
                pos = spline.GetPosition(r);
                spline.SetPosition(r, new(pos.x, y, 0));
            }
            if (l <= 1 && r >= n) return;
            await UniTask.Delay((int)(waveTime * 1000));
            y *= waveRate * Random.Range(0.8f, 1.2f);
        }
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (!collision.GetComponent<Rigidbody2D>()) return;
        WaveCreate(collision);
    }

    private void OnTriggerExit2D(Collider2D collision)
    {
        if (!collision.GetComponent<Rigidbody2D>()) return;
        WaveCreate(collision);
    }
}


見ての通りとても短いです。


このコードをアタッチしたら完成!



はい!お疲れ様でした!


なにかと敬遠しがちな波打つ水面の実装ですが、こんなに簡単にアセットなしでできちゃいました。



これでゲームのクオリティを一段階アップできるね!やった!




次回からはゲーム開発の続きをしていきたいと思います!