うにてぃブログ

UnityやUnreal Engineの記事を書いていきます

【Unity】マリオみたいなジャンプを再現する方法

Unityで「マリオのようなジャンプ」を再現する方法について解説します。

マリオシリーズのジャンプは、普通のジャンプと比べて非常に気持ちよく設計されています。
その秘密は、ジャンプ中・落下中で重力を動的に変化させている点にあります。

なぜ普通のジャンプではマリオっぽくならないのか?

Unityのデフォルト設定では、重力加速度が -9.8 m/s² に設定されています。
これは現実世界に基づく値ですが、これをそのまま使うとジャンプが重たく、もっさりした挙動になってしまいます。

初代『スーパーマリオブラザーズ』について、
Tom Murphy VII氏による調査では、
重力加速度が現実の–9.8m/s²よりもかなり大きく、約–90m/s²程度であるという分析結果が報告されています。

さらにマリオシリーズでは、ジャンプの各状態に応じて重力の強さ(Gravity Scale)を切り替えることで、非常に直感的な操作感を実現しています。

具体的には、

  • 上昇中:Gravity Scaleを小さくして、ゆっくり上がるようにする
  • ジャンプボタンを離した直後:Gravity Scaleを一気に大きくして、急加速して落ち始める
  • 下降中:さらにGravity Scaleを大きくして、すばやくストンと着地する

この切り替えにより、

  • ジャンプボタンを短く押せば小ジャンプ
  • 長く押し続ければ高いジャンプ
  • 頂点からは素早く落下

という「気持ちいいジャンプ操作」が可能になっています。

比較動画

| 左:本記事の方法で作ったジャンプ | 右:常に同じ重力で作ったジャンプ |

※左のジャンプは、上昇・ボタン離し・下降で重力を切り替えています。
※右のジャンプは、常に一定の重力で制御しているため、もっさりとした動きになっています。


実装の基本方針

マリオジャンプを再現するために、以下のポイントを押さえます。

  • ジャンプ時にしっかりとした初速(上向き速度)を与える
  • 上昇中はGravity Scaleを弱めて、滞空感を出す
  • ジャンプボタンを離した直後はGravity Scaleを強めて、急降下を開始する
  • 下降中はさらにGravity Scaleを強めて、素早く着地させる

今回はさらに、n段ジャンプ(ダブルジャンプ、トリプルジャンプなど)にも対応しています。


コードサンプル

ここでは、上記の方針に沿って実装した、実際のUnity用C#スクリプトを紹介します。
このスクリプトをキャラクターにアタッチするだけで、マリオのようなジャンプ挙動を再現できます。

using System;
using UnityEngine;

[RequireComponent(typeof(Rigidbody2D))]
public class MarioLikeJump : MonoBehaviour
{
    [SerializeField]
    private Rigidbody2D _rigidbody;

    [SerializeField] private float _takeOffVelocity = 17f; 
    [SerializeField] private float _riseGravityScale = 3.5f; 
    [SerializeField] private float _cutGravityScale = 7f; 
    [SerializeField] private float _fallGravityScale = 6f; 
    [SerializeField] private int _maxJumpCount = 2;

    private bool _jumpRequest;
    private bool _jumpHeld;
    private int _jumpCount;
    private float _defaultGravityScale;

#if UNITY_EDITOR
    private void Reset()
    {
        _rigidbody = GetComponent<Rigidbody2D>();
    }
#endif

    private void Awake()
    {
        _defaultGravityScale = _rigidbody.gravityScale;
    }

    private void Update()
    {
        if (Input.GetButtonDown("Jump"))
            _jumpRequest = _jumpHeld = true;

        if (Input.GetButtonUp("Jump"))
            _jumpHeld = false;
    }

    private void FixedUpdate()
    {
        var grounded = Mathf.Abs(_rigidbody.velocity.y) < 0.01f;
        if (grounded)
        {
            _jumpCount = 0;
        }

        if (_jumpRequest)
        {
            if (_jumpCount < _maxJumpCount)
            {
                _rigidbody.velocity = new Vector2(_rigidbody.velocity.x, _takeOffVelocity);
                _jumpCount++;
            }
            _jumpRequest = false;
        }

        if (_rigidbody.velocity.y > 0) // 上昇中
        {
            _rigidbody.gravityScale = _defaultGravityScale * (_jumpHeld ? _riseGravityScale : _cutGravityScale);
        }
        else // 下降中
        {
            _rigidbody.gravityScale = _defaultGravityScale * _fallGravityScale;
        }
    }
}