2Dキャラクターを動かそう:移動とジャンプ【Unityスタートガイド12-3】

横スクロールのアクションゲームを作ろうとすると、まず困るのが「キャラクターを左右に動かして、ジャンプさせる」ところ。6-8では3Dのキャラクターを動かしましたが、2DではRigidbody 2DやVector2を使うので、書き方が少し変わります。今回は2Dキャラクターの移動・ジャンプ・接地判定をまとめてご紹介します!

目次

この記事で学べること

  • 2Dキャラクター用のRigidbody 2Dの設定
  • Input Systemの入力で左右に動かす方法
  • ジャンプと、地面に立っているかの判定(接地判定)
  • キャラクターの向きの反転
  • 壁に張り付いてしまう問題の直し方
  • 見下ろし型の2Dゲームでの動かし方

前提知識

3Dと2Dの違い

6-8の3D版PlayerMoverとの主な違いです。

3D2D
物理のコンポーネントRigidbody、Box ColliderなどRigidbody 2D、Box Collider 2Dなど
位置や速度の型Vector3(x, y, z)Vector2(x, y)
上下の軸Y(床の上はXとZで動く)Y(横スクロールならXで左右、Yで上下)
重力の向きY軸のマイナスY軸のマイナス(Rigidbody 2DのGravity Scaleで強さを調整)

3D用と2D用のコンポーネントは混ぜて使えないので、2Dのキャラクターには「2D」が付いたものだけを使いましょう(3-4の記事でも説明しました)。

キャラクターと地面の準備

プロジェクトとキャラクター

  • テンプレートはUniversal 2D
  • Hierarchyで右クリック>2D Object>Sprites>Square(仮のキャラクター。名前はPlayer)
  • PlayerにAdd Component>Physics 2D>Box Collider 2D
  • PlayerにAdd Component>Physics 2D>Rigidbody 2D

Rigidbody 2Dの設定

  • Body Type:Dynamic(物理で動かす)
  • Gravity Scale:3ぐらい(初期値の1だと、ふわっとした落ち方になります)
  • Collision Detection:Continuous(速く動いても地面をすり抜けにくくなる)
  • Interpolate:Interpolate(カメラで追いかけたときのカクつきを減らす)
  • Constraints>Freeze Rotation Z:チェック(キャラクターが転がらないようにする)

地面

  • Hierarchyで右クリック>2D Object>Sprites>Square(名前はGround)
  • Scaleを(20, 1, 1)ぐらいに広げて、Playerより下に置く
  • Add Component>Physics 2D>Box Collider 2D(地面にはRigidbody 2Dは付けなくてOK)

Tilemapで地面を作る場合は、Tilemapの使い方の記事で紹介しているTilemap Collider 2Dを付けます。

地面のレイヤーを作る

接地判定で「地面だけ」を調べるために、地面にレイヤーを設定します。

  • Groundを選び、Inspector右上のLayer>Add Layer…
  • 空いているUser Layerに「Ground」と入力
  • もう一度Groundを選び、LayerをGroundにする

足元の目印(groundCheck)

  • Playerを右クリック>Create Emptyで子オブジェクトを作り、名前をGroundCheckにする
  • Positionを(0, -0.5, 0)にして、キャラクターの足元に置く

移動とジャンプのスクリプト

「PlatformerController2D」という名前でスクリプトを作り、Playerにアタッチします。

using UnityEngine;

[RequireComponent(typeof(Rigidbody2D))]
public class PlatformerController2D : MonoBehaviour
{
    [SerializeField] private float moveSpeed = 6f;
    [SerializeField] private float jumpPower = 12f;

    [Header("接地判定")]
    [SerializeField] private Transform groundCheck;
    [SerializeField] private float groundCheckRadius = 0.15f;
    [SerializeField] private LayerMask groundLayer;

    private InputSystem_Actions inputActions;
    private Rigidbody2D rb;
    private SpriteRenderer spriteRenderer;

    private float moveX;
    private bool jumpRequested;
    private bool isGrounded;

    void Awake()
    {
        inputActions = new InputSystem_Actions();
        rb = GetComponent<Rigidbody2D>();
        spriteRenderer = GetComponent<SpriteRenderer>();
    }

    void OnEnable()
    {
        inputActions.Enable();
    }

    void OnDisable()
    {
        inputActions.Disable();
    }

    void Update()
    {
        // 入力はUpdateで読む
        moveX = inputActions.Player.Move.ReadValue<Vector2>().x;

        // ジャンプは「押した瞬間」をフラグにしておき、FixedUpdateで使う
        if (inputActions.Player.Jump.WasPressedThisFrame() && isGrounded)
        {
            jumpRequested = true;
        }

        // 進む方向に向きを変える
        if (moveX > 0.01f)
        {
            spriteRenderer.flipX = false;
        }
        else if (moveX < -0.01f)
        {
            spriteRenderer.flipX = true;
        }
    }

    void FixedUpdate()
    {
        // 足元に地面があるか調べる
        isGrounded = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, groundLayer);

        // 左右の移動。上下の速さ(落下・ジャンプ)はそのまま残す
        rb.linearVelocity = new Vector2(moveX * moveSpeed, rb.linearVelocity.y);

        // ジャンプ
        if (jumpRequested)
        {
            rb.linearVelocity = new Vector2(rb.linearVelocity.x, 0f);   // 落下中の勢いを消してから
            rb.AddForce(Vector2.up * jumpPower, ForceMode2D.Impulse);
            jumpRequested = false;
        }
    }

    // シーンビューで接地判定の円を表示する(Playerを選んだときだけ)
    void OnDrawGizmosSelected()
    {
        if (groundCheck == null)
        {
            return;
        }

        Gizmos.color = Color.yellow;
        Gizmos.DrawWireSphere(groundCheck.position, groundCheckRadius);
    }
}

Inspectorで次の3つを設定します。

  • Ground Check:子オブジェクトのGroundCheckをドラッグ&ドロップ
  • Ground Check Radius:0.15ぐらい
  • Ground Layer:Ground

再生して、A/Dか左右キーで走り、Spaceでジャンプ出来れば成功!

スクリプトのポイント

  • 入力はUpdate、物理はFixedUpdate
    • WasPressedThisFrame()はそのフレームだけtrueになるので、Updateで受け取ってフラグ(jumpRequested)に残し、FixedUpdateで使います
    • FixedUpdateで直接WasPressedThisFrame()を見ると、押したのにジャンプしないことがあります
  • 左右は速度、ジャンプは力
    • 左右:rb.linearVelocityのxだけを書き換える。yはそのまま残すので、重力で落ちる動きはRigidbody 2Dに任せられます
    • ジャンプ:AddForce(方向 * 強さ, ForceMode2D.Impulse)で、一瞬だけ上向きの力を加えます
  • 接地判定:Physics2D.OverlapCircle
    • 足元に小さな円を置いて、その中にGroundレイヤーのColliderがあるかを調べます
    • 地面の上にいるときだけジャンプ出来るので、空中で何度もジャンプ出来なくなります
  • 向きの反転:flipX
    • SpriteRendererのflipXをtrueにすると、画像が左右反転します
  • [Header(“接地判定”)]
    • Inspectorに見出しを付けて、項目を見やすくするための書き方です
スライム

ジャンプボタンを押しても、たまに反応しない・・・

フェアリー

接地判定の円が地面に届いているか、Playerを選んでシーンビューの黄色い円を見てみてね!足元より少しだけ下にはみ出すぐらいがちょうどいいよ

壁に張り付く問題を直す

空中で壁に向かって左右キーを押し続けると、キャラクターが壁に引っかかって落ちてこないことがあります。壁との間の摩擦が原因です。

  • Projectウィンドウで右クリック>Create>2D>Physics Material 2D(名前はNoFriction)
  • Friction:0
  • Bounciness:0
  • PlayerのBox Collider 2DのMaterialに、NoFrictionをドラッグ&ドロップ

見下ろし型の2Dゲームの場合

上から見下ろすタイプ(RPGのフィールドなど)では、重力を使わずに上下左右に動かします。

  • Rigidbody 2DのGravity Scale:0
  • Constraints>Freeze Rotation Z:チェック
using UnityEngine;

[RequireComponent(typeof(Rigidbody2D))]
public class TopDownMover2D : MonoBehaviour
{
    [SerializeField] private float moveSpeed = 5f;

    private InputSystem_Actions inputActions;
    private Rigidbody2D rb;
    private Vector2 moveInput;

    void Awake()
    {
        inputActions = new InputSystem_Actions();
        rb = GetComponent<Rigidbody2D>();
    }

    void OnEnable()
    {
        inputActions.Enable();
    }

    void OnDisable()
    {
        inputActions.Disable();
    }

    void Update()
    {
        // 斜めが速くならないように、長さを1以下にする
        moveInput = Vector2.ClampMagnitude(inputActions.Player.Move.ReadValue<Vector2>(), 1f);
    }

    void FixedUpdate()
    {
        rb.linearVelocity = moveInput * moveSpeed;
    }
}

Moveの入力のx(左右)とy(上下)を、そのまま2Dの向きとして使えるのが見下ろし型のポイントです。

よくあるエラーと対処法

キャラクターが地面をすり抜けて落ちる

  • 原因:地面にBox Collider 2Dが付いていない、または3D用のBox Colliderを付けている
  • 対処法:地面とPlayerの両方に「2D」の付いたColliderを付ける

ジャンプ出来ない

  • 原因:Ground CheckやGround Layerが設定されていない、地面のLayerがGroundになっていない、または接地判定の円が地面に届いていない
  • 対処法:Inspectorの3つの設定と、地面のLayerを確認する。シーンビューで黄色い円の位置を確認する

空中で何回もジャンプ出来る

  • 原因:Ground LayerにPlayer自身のレイヤーも含まれていて、自分のColliderを「地面」だと判定している
  • 対処法:Ground LayerはGroundだけにする

キャラクターがくるくる回る

  • 原因:Freeze Rotation Zにチェックが入っていない
  • 対処法:Rigidbody 2DのConstraints>Freeze Rotation Zにチェックを入れる

‘Rigidbody2D.velocity’ is obsolete

  • 原因:古い書き方を使っている
  • 対処法:linearVelocityに書き換える

まとめ

  • 2DのキャラクターはRigidbody 2DとBox Collider 2Dなど「2D」のコンポーネントで作る
  • 左右の移動はrb.linearVelocityのxを書き換え、yは重力に任せる
  • ジャンプはUpdateで入力を受けてフラグ、FixedUpdateでAddForce
  • 接地判定はPhysics2D.OverlapCircleとGroundレイヤー
  • 向きの反転はflipX、壁に張り付くなら摩擦0のPhysics Material 2D
  • 見下ろし型はGravity Scale 0で、Moveの入力をそのまま使う

次のステップ

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次