【Unity】DOTweenで自作のジャンプを実装する

今回は Unity のフリーのアセットの DOTween を使ってジャンプ動作を実装例の紹介をしたいと思います。

概要

元々 DOTween にはジャンプ動作を実行するための DOJump と DOLocalJump が存在しますが、割とフワッとした動きなので 2Dゲームで硬質なものが地面に落ちて何度かバウンドするみたいな動作にちょっと使いずらい感じだったりします。

そこで今回はこの DOJump のようなオブジェクトをジャンプさせるための自作の DOJump を実装します。

作例

今回実装する DOJump を使用すると以下のような動作となります。

実装コード

JumpParam.cs

ジャンプパラメーターを表すクラスです。

public readonly struct JumpParam
{
    // 相対的な終了位置
    public readonly Vector3 DeltaPos;
    // ジャンプの高さ
    public readonly float Height;
    // 実行時間
    public readonly float Duration;

    public JumpParam(Vector3 delta, float hiegth, float duration)
    {
        DeltaPos = delta;
        Height = hiegth;
        Duration = duration;
    }

    public JumpParam(float delta_x, float delta_y, float hiegth, float duration)
    {
        DeltaPos = new(delta_x, delta_y);
        Height = hiegth;
        Duration = duration;
    }

    public static JumpParam operator /(JumpParam param, float rate)
    {
        return new JumpParam(param.DeltaPos / rate, 
            param.Height / rate, param.Duration / rate);
    }
    public static JumpParam operator *(JumpParam param, float rate)
    {
        return new JumpParam(param.DeltaPos * rate,
            param.Height * rate, param.Duration * rate);
    }

    /// <summary>
    /// 現在のオブジェクトの値と指定した割合に応じた位置を計算します。
    /// </summary>
    public Vector3 CalcPos(float per)
    {
        // 縦方向
        float frac = Mathf.Clamp01(per);
        float y = Height * 4.0f * frac * (1.0f - frac);
        y += DeltaPos.y * per;

        // 横方向
        float x = DeltaPos.x * per;
        float z = DeltaPos.z * per;

        return new Vector3(x, y, z);
    }

    public override string ToString()
    {
        return $"Vec3={DeltaPos}, Height={Height}, Dration={Duration}";
    }
}

TweenParamExtensions.cs

拡張メソッドを定義します。ここに Trasnform.DOJump を実装します。

public static class TweenParamExtensions
{
    // TrasnformにDOJumpを追加する
    public static Tween DOJump(this Transform target, params JumpParam[] jumpParams)
    {
        if (jumpParams == null || jumpParams.Length == 0)
        {
            throw new ArgumentException($"{nameof(jumpParams)} is null or empty.");
        }

        // 開始位置
        Vector3 startPos = target.localPosition;
        // 現在のジャンプパラメータ
        JumpParam cuurentParam = jumpParams[0];
        // 進行度 0~100% (0~1.0f)
        float per = 0;

        var seq = DOTween.Sequence();
        //.SetLink(target)
        //.SetRelative();

        seq.Append(DOTween.To(() => per
        , value =>
        {
            per = value;
            target.localPosition = startPos + cuurentParam.CalcPos(per);
        }
        , 1
        , cuurentParam.Duration)
            .SetEase(Ease.Linear));

        for (int i = 1; i < jumpParams.Length; i++)
        {
            int pp = i;
            float duration = jumpParams[pp].Duration;
            seq.AppendCallback(() =>
            {
                per = 0;
                cuurentParam = jumpParams[pp];
                startPos = target.localPosition;
            });
            seq.Append(DOTween.To(() => per
            , value =>
            {
                per = value;
                target.localPosition = startPos + cuurentParam.CalcPos(per);
            }
            , 1
            , duration)
                .SetEase(Ease.Linear));
        }

        return seq;
    }
}

使い方

使い方は先にジャンプパラメータを作成してそのパラメーターを DOJump に設定します。

// ジャンプパラメータの作成
JumpParam j1 =
    new JumpParam(
        UniRandom.Range2Inv(0.75f, 1.25f), // x: -0.75~1.25 or 0.75~1.25 の範囲
        UniRandom.Range2Inv(0.25f, 0.75f), // y: -0.05~0.75 or 0.25~0.75 の範囲
        Rand.Range(1.0f, 1.2f),
        Rand.Range(0.5f, 0.7f));
        
// ジャンプの実行
transform.DOJump(j1, j1 * 0.5f, j1 * 0.25f)

ただし、このままだと処理が終わっても削除されない、アニメーションの管理ができないなどの問題があるため実際には以下のように DOTween.Sequence に組み込んで使用します。

Transform _transform;
SpriteRenderer _spriteRenderer;
Tween _tween;

void Awake()
{
    _transform = transform;
    this.SetComponent(ref _spriteRenderer);
}

public void Play()
{
    _spriteRenderer.sprite = _sprites.PickupOne();

    _tween.Kill();
    gameObject.SetActive(true);

    // ジャンプパラメータの作成
    JumpParam j1 =
        new JumpParam(
            UniRandom.Range2Inv(0.75f, 1.25f), // x: -0.75~1.25 or 0.75~1.25 の範囲
            UniRandom.Range2Inv(0.25f, 0.75f), // y: -0.05~0.75 or 0.25~0.75 の範囲
            Rand.Range(1.0f, 1.2f),
            Rand.Range(0.5f, 0.7f));

    var seq = DOTween.Sequence();
    // ジャンプの実行
    seq.Append(_transform.DOJump(j1, j1 * 0.5f, j1 * 0.25f));
    // 少し待機した後にオブジェクトを削除
    seq.AppendInterval(0.5f);
    seq.OnComplete(() => Destroy(gameObject));
    // 
    seq.SetLink(this);
    _tween = seq;
}

これでジャンプ実行後に少し待機してオブジェクトを削除される冒頭の作例のような動作になります。