【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 RendererとSprite Shape Controllerというコンポーネントがアタッチされているのがわかると思います。
Element設定
次にSprite Shape ControllerコンポーネントのEdit Splineを選択します(SceneビューのToolsからも選択できます)。
そうするとSprite内にElement(丸いやつ)が4つ表示されるので、どれか1つ選択してElement Inspectorを表示させてください。
0~3番目のElementのPositionをそれぞれ(x, y) = (-1, -1), (-1, 0), (1, 0), (1, -1)に設定します。
(Sprite Shapeの仕様で、左下のElementから時計回りに0からIndex(番号)が振られています)
Tangent Mode、HeightはすべてのElement共通でそれぞれLinear、0.1に調整します。
ここまでの操作で↑こんなかんじになったらOKです。
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); } }
見ての通りとても短いです。
このコードをアタッチしたら完成!
はい!お疲れ様でした!
なにかと敬遠しがちな波打つ水面の実装ですが、こんなに簡単にアセットなしでできちゃいました。
これでゲームのクオリティを一段階アップできるね!やった!
次回からはゲーム開発の続きをしていきたいと思います!