Unityでキャラクターが動く床に乗ると一緒に動く仕組みガイド

2026年10月8日

広告

この記事では、キャラクターが移動する床(Moving Platform)に乗ったとき、床の動きに合わせてキャラクターも一緒に運ばれるようにする3つの方法を紹介します。

※ 模式図です。実際の動きは、ご自身のUnityで確認してください。

2026年10月改訂: Unity 6.6に合わせて、入力処理を Input System に更新し、方法3を FixedJoint から見直しました(理由は方法3の末尾で説明します)。サンプルコードは、ご自身のプロジェクトで必ず動作確認してから使ってください。動作確認の設定と手順は「4. 動作確認の設定と手順」にまとめています。


0. 始める前に(前提条件)

動く床の仕組みでつまずきやすいポイントを先にまとめます。

キー入力でキャラクターを動かす部分は、Input System を使う前提です。入力の取得に自信がない方は、先に 【学習】Input System(入力の取得) を読んでおくと、サンプルコードが読みやすくなります。

  • キャラクターに Rigidbody(または Rigidbody2D)が必要です。 この記事の OnCollision 系の処理は、キャラクターと床の少なくとも一方が「Is Kinematic オフ」の Rigidbody を持っていないと呼ばれません。床は Kinematic なので、キャラクター側に付けます。Transform で動かすキャラクターでも、接触の検出のために付けておく必要があります。
  • CharacterController を使っているキャラクターでは、この記事のコードは動きません。 CharacterController は OnCollisionEnter ではなく OnControllerColliderHit で接触を受け取る仕組みのためです。
  • 床に「MovingPlatform」タグを付けます。 Inspector 上部の Tag から「Add Tag…」で MovingPlatform を作り、床のオブジェクトに設定してください。タグを作らないまま CompareTag("MovingPlatform") を実行するとエラーになります。
  • キャラクターの Collider は1つを前提にしています。 複数の Collider が床に触れるキャラクターでは、足元かどうかの判定が複雑になり、このサンプルのままでは正しく動かないことがあります。

1. 床の作成方法

2D版の場合

  • GameObject > 2D Object > Sprite で床用のスプライトを作ります。
  • Rigidbody2D を追加し、Body Type を Kinematic にします。
  • Box Collider 2D を追加します。
  • タグを MovingPlatform にします。

3D版の場合

  • GameObject > 3D Object > Cube を床として使います(Box Collider は自動で付きます)。
  • Rigidbody を追加し、Is Kinematic にチェックを入れます。
  • タグを MovingPlatform にします。

スケールの注意: 床の見た目を Scale (5, 0.5, 3) のように変形したい場合は、Rigidbody と Collider を持つ床オブジェクトの Scale は (1, 1, 1) のままにして、変形した Cube を子オブジェクトにする構成が安全です。方法1(親子付け)で、スケールが不均等な親の子にすると、キャラクターが歪んで見えることがあります。

床を動かすスクリプト(3D版)

床が動かないと確認できないので、往復する簡単なスクリプトを床に付けます。床の移動は FixedUpdate で MovePosition を使います。 これが物理演算と相性のよい動かし方です。

using UnityEngine;

public class MovingPlatform : MonoBehaviour
{
    public Vector3 moveOffset = new Vector3(5f, 0f, 0f); // 往復する距離と方向
    public float cycleSpeed = 0.5f;                       // 往復の周期の調整値(大きいほど速く往復)

    private Rigidbody rb;
    private Vector3 startPosition;

    void Start()
    {
        rb = GetComponent<Rigidbody>();
        startPosition = rb.position;
    }

    void FixedUpdate()
    {
        float t = Mathf.PingPong(Time.time * cycleSpeed, 1f); // 0→1→0 を繰り返す
        rb.MovePosition(startPosition + moveOffset * t);
    }
}

cycleSpeed は床の移動速度そのものではありません。実際の速さは moveOffset(往復する距離)の大きさにも依存します。同じ cycleSpeed でも、距離が5のときと10のときでは速さが変わります。

この床スクリプトは、2D版では Rigidbody を Rigidbody2D、Vector3 を Vector2 に置き換えるだけで動きます。ただし、キャラクター側の接触処理には2D専用の型とメソッドが必要です(「5. 2D版への読み替え」を参照)。


2. 共通で使う「足元で接触しているか」の判定

タグが一致するだけで反応すると、床の横や下に当たっただけでも「乗った」扱いになってしまいます。接触点の向き(法線)を調べて、足元で触れているときだけ反応させます。以降の3つの方法で共通して使います。

bool IsStandingOn(Collision collision)
{
    foreach (ContactPoint contact in collision.contacts)
    {
        if (contact.normal.y > 0.5f) return true; // 下から支えられている
    }
    return false;
}

性能の補足: collision.contacts は呼ぶたびに配列を作ります。毎フレーム呼ばれる OnCollisionStay で使う場合、慣れてきたら次のように GetContact を使うと、メモリの無駄な割り当てを避けられます。

bool IsStandingOn(Collision collision)
{
    for (int i = 0; i < collision.contactCount; i++)
    {
        if (collision.GetContact(i).normal.y > 0.5f) return true;
    }
    return false;
}

3. 各連動方法

方法1:キャラクターを床の子オブジェクトにする

概要

乗っている間だけ、キャラクターを床の子オブジェクトにします。子は親の動きに自動でついていくので、床の移動も回転もそのまま反映されます。

実装例(3D版)

using UnityEngine;

public class CharacterOnPlatform : MonoBehaviour
{
    private Transform originalParent;   // もともとの親(なければ null)
    private Transform currentPlatform;  // いま乗っている床

    void Start()
    {
        originalParent = transform.parent;
    }

    void OnCollisionStay(Collision collision)
    {
        if (!collision.gameObject.CompareTag("MovingPlatform")) return;

        bool standing = IsStandingOn(collision);

        if (standing && currentPlatform != collision.transform)
        {
            // 足元で触れた:床の子オブジェクトにする
            currentPlatform = collision.transform;
            transform.SetParent(currentPlatform);
        }
        else if (!standing && currentPlatform == collision.transform)
        {
            // 床の側面などに触れているだけ:親子関係を解除する
            transform.SetParent(originalParent);
            currentPlatform = null;
        }
    }

    void OnCollisionExit(Collision collision)
    {
        if (collision.transform == currentPlatform)
        {
            transform.SetParent(originalParent);  // もとの親に戻す
            currentPlatform = null;
        }
    }

    bool IsStandingOn(Collision collision)
    {
        foreach (ContactPoint contact in collision.contacts)
        {
            if (contact.normal.y > 0.5f) return true;
        }
        return false;
    }
}

OnCollisionEnter ではなく OnCollisionStay を使っているのは、「最初は床の横に触れ、あとから上に乗った」場合にも対応するためです。逆に、床の端から落ちて側面に触れ続けているときのように、OnCollisionExit が呼ばれないまま足元の接触だけがなくなる場合があります。そのため、OnCollisionStay の中で「足元でなくなったら解除する」処理も入れています。SetParent(null) ではなく元の親に戻しているのは、キャラクターが最初から別の親の下にいる場合でも、階層を壊さないためです。

Dynamic Rigidbody との相性に注意: 親子付けは、Transform を中心に移動を制御しているキャラクターでは理解しやすい方法です。ただし、Is Kinematic オフの Rigidbody を持つキャラクターでは、親子関係による移動と物理演算が干渉することがあります。震えるだけでなく、床が回転したときや、ジャンプして床から離れるときにも挙動を確認してください。本格的に物理挙動を扱う場合は、別の追従方式を検討します。

床の上で歩くときの注意

親子付けすると、キャラクターは床と一緒に回転します。床が回転する場合、入力による移動を ワールド座標基準(Space.World)で行うと、床の回転に入力方向が引きずられません。

using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerMovement : MonoBehaviour
{
    public float speed = 5f;

    void Update()
    {
        float horizontal = 0f;
        if (Keyboard.current.dKey.isPressed || Keyboard.current.rightArrowKey.isPressed) horizontal += 1f;
        if (Keyboard.current.aKey.isPressed || Keyboard.current.leftArrowKey.isPressed) horizontal -= 1f;

        Vector3 movement = new Vector3(horizontal * speed * Time.deltaTime, 0f, 0f);
        transform.Translate(movement, Space.World); // ワールド基準で移動
    }
}

以前のUnityでは Input.GetAxis("Horizontal") で入力を取得していましたが、Unity 6 では Input System を使います。詳しくは Input System でキーボード移動 をご覧ください。

なお、Translate の第2引数を省略した場合は Space.Self(キャラクター自身の向き基準)になります。親子付けした状態では、床が回るとキャラクターの「右」の向きも一緒に回る点に注意してください。

メリット・デメリット

  • メリット: 実装がシンプルで、床の移動と回転の両方に自動でついてくる。
  • デメリット: 床の回転・スケールの影響を受ける。Rigidbody で動くキャラクターでは、親子関係と物理演算が干渉することがある。

方法2:床の移動量をキャラクターに加算する

概要

床が前フレームから動いた分(差分)を計算し、その分だけキャラクターの位置をずらします。キャラクターの回転やスケールには影響しません。

実装例(3D版)

using UnityEngine;

public class CharacterOnMovingPlatform : MonoBehaviour
{
    private Transform platform;
    private Vector3 lastPlatformPosition;

    void OnCollisionStay(Collision collision)
    {
        if (!collision.gameObject.CompareTag("MovingPlatform")) return;

        bool standing = IsStandingOn(collision);

        if (standing && platform != collision.transform)
        {
            // 足元で触れた:この床の移動量を追いかけ始める
            platform = collision.transform;
            lastPlatformPosition = platform.position;
        }
        else if (!standing && platform == collision.transform)
        {
            // 側面などに触れているだけ:追従をやめる
            platform = null;
        }
    }

    void OnCollisionExit(Collision collision)
    {
        if (collision.transform == platform)
        {
            platform = null;
        }
    }

    void LateUpdate()
    {
        if (platform == null) return;

        Vector3 platformMovement = platform.position - lastPlatformPosition;
        transform.position += platformMovement;
        lastPlatformPosition = platform.position;
    }

    bool IsStandingOn(Collision collision)
    {
        foreach (ContactPoint contact in collision.contacts)
        {
            if (contact.normal.y > 0.5f) return true;
        }
        return false;
    }
}

ポイント:LateUpdate と床の更新タイミング

LateUpdate は、通常の Update がすべて終わったあとに呼ばれます。Update や Transform で動く床なら、床の位置が確定してから差分を反映できるので、1フレーム遅れによるカクつきを避けられます。

ただし、この記事の床は FixedUpdate で Rigidbody を動かしています。物理演算の更新間隔と描画フレームの間隔は同じではないため、床の Rigidbody の Interpolate 設定によって、床の Transform が更新されるタイミングが変わります。LateUpdate を使えば必ず同期する、というわけではありません。カクつく場合は、床の Rigidbody の Interpolate 設定を変えて見え方を確認してください。

メリット・デメリット

  • メリット: キャラクターの回転・スケールに影響しない。仕組みが分かりやすい。
  • デメリット: 床の回転には追従しません(位置の移動量だけを加算するため)。Rigidbody で動くキャラクターの transform.position を直接書き換えるのは物理演算と衝突しやすいので、その場合は方法3を検討します。

方法3:物理ステップで床の移動量を加算する(Rigidbodyキャラクター向け・応用)

概要

Rigidbody で動くキャラクター向けに、物理演算と同じタイミング(FixedUpdate)で、床の Rigidbody の移動量を加算する方法です。方法2の考え方を、物理演算の流れに合わせた形です。

この方法は応用です。 Is Kinematic オフの Rigidbody の位置を直接書き換えるため、接触や速度と食い違うことがあります。床の移動量をいつ確定させるか、キャラクター自身の移動とどう組み合わせるか、ジャンプして床を離れる瞬間をどう扱うかまで含めて、お使いのキャラクターで必ず動作確認してください。

実装例(3D版)

using UnityEngine;

public class RigidbodyOnPlatform : MonoBehaviour
{
    private Rigidbody rb;
    private Rigidbody platformRb;
    private Vector3 lastPlatformPosition;

    void Start()
    {
        rb = GetComponent<Rigidbody>();
    }

    void OnCollisionStay(Collision collision)
    {
        if (!collision.gameObject.CompareTag("MovingPlatform")) return;

        bool standing = IsStandingOn(collision);

        if (standing && platformRb != collision.rigidbody)
        {
            // 足元で触れた:この床の移動量を追いかけ始める
            platformRb = collision.rigidbody;
            lastPlatformPosition = platformRb.position;
        }
        else if (!standing && platformRb == collision.rigidbody)
        {
            // 側面などに触れているだけ:追従をやめる
            platformRb = null;
        }
    }

    void OnCollisionExit(Collision collision)
    {
        if (collision.rigidbody == platformRb)
        {
            platformRb = null;
        }
    }

    void FixedUpdate()
    {
        if (platformRb == null) return;

        Vector3 platformMovement = platformRb.position - lastPlatformPosition;
        rb.position += platformMovement; // 床が動いた分だけキャラクターを動かす
        lastPlatformPosition = platformRb.position;
    }

    bool IsStandingOn(Collision collision)
    {
        foreach (ContactPoint contact in collision.contacts)
        {
            if (contact.normal.y > 0.5f) return true;
        }
        return false;
    }
}

床の移動量を加えるだけなので、FixedJoint のようにキャラクター自身の移動入力を妨げることはありません。キャラクターの歩行は、AddForce など通常の Rigidbody の動かし方で組み合わせられます。ただし、ジャンプして床から離れた瞬間に、床の速度は引き継がれません。床の動きに合わせた慣性まで再現したい場合は、別途設計が必要です。

補足: 物理演算は一定間隔で進むため、床の動きが反映されるまでに最大で物理1ステップ分(初期設定で0.02秒)の遅れが出ることがあります。気になる場合は、床とキャラクターの Rigidbody の Interpolate 設定を見直してください。

なぜ FixedJoint を使わないのか

以前の版では、キャラクターに FixedJoint(2Dは FixedJoint2D)を付けて床と結合する方法を紹介していました。しかし FixedJoint は、2つの Rigidbody の相対的な位置と回転を完全に固定します。つまり、乗った瞬間から歩くことも、ジャンプすることも、向きを変えることもできなくなります。動く床の上でキャラクターを操作するゲームには向かないため、この記事では取り上げないことにしました。


4. 動作確認の設定と手順

コードを貼り付けただけでは、正しく動いているか判断できません。次の設定でシーンを組み、確認項目を順に試してください。

3D版の設定

オブジェクト必要な設定
床(MovingPlatform)Rigidbody(Is Kinematic オン)、Box Collider、タグ MovingPlatform、MovingPlatform スクリプト
プレイヤーRigidbody(Is Kinematic オフ)、Collider(1つ)、追従スクリプト(方法1〜3のどれか1つ)、移動スクリプト
地面Collider(床の端から降りたときに着地する場所)

追従スクリプトは、方法1〜3のうち1つだけをプレイヤーに付けてください。複数を同時に付けると、動きが二重に反映されます。

確認する項目

  1. 床の上に立つ: 床の上にキャラクターを置き、床が動き始めても落ちずに一緒に運ばれる。
  2. 床の上で歩く: 左右に歩いて、入力どおりに動ける。
  3. 床の端から降りる: 端から歩いて降りたあと、床が離れても引きずられない。
  4. 床の側面に触れる: 地面に立った状態で床の側面に当たっても、床に引っ張られない。
  5. 再び乗る: もう一度床の上に乗ると、また一緒に運ばれる。
  6. 床が回転する場合(方法1): 床を回転させたとき、キャラクターの入力方向や見た目がおかしくならない。

特に3と4は、足元でなくなったときの解除処理が正しく働いているかの確認になります。ここで問題が出る場合は、「2. 共通で使う『足元で接触しているか』の判定」と、各方法の OnCollisionStay の処理を見直してください。


5. 2D版への読み替え

2D版でも基本的な考え方は同じですが、衝突のイベントや型は2D専用のものを使います。3D版のコードは、次のように置き換えると2D版になります。

3D版2D版
OnCollisionStay(Collision collision)OnCollisionStay2D(Collision2D collision)
OnCollisionExit(Collision collision)OnCollisionExit2D(Collision2D collision)
RigidbodyRigidbody2D
Vector3(Rigidbody の位置・MovePosition)Vector2
ContactPointContactPoint2D
bool IsStandingOn(Collision collision)bool IsStandingOn(Collision2D collision)

足元判定の contact.normal.y > 0.5f は2Dでも同じ書き方です。参考として、方法1の2D版の完成形を載せます。

using UnityEngine;

public class CharacterOnPlatform2D : MonoBehaviour
{
    private Transform originalParent;
    private Transform currentPlatform;

    void Start()
    {
        originalParent = transform.parent;
    }

    void OnCollisionStay2D(Collision2D collision)
    {
        if (!collision.gameObject.CompareTag("MovingPlatform")) return;

        bool standing = IsStandingOn(collision);

        if (standing && currentPlatform != collision.transform)
        {
            currentPlatform = collision.transform;
            transform.SetParent(currentPlatform);
        }
        else if (!standing && currentPlatform == collision.transform)
        {
            transform.SetParent(originalParent);
            currentPlatform = null;
        }
    }

    void OnCollisionExit2D(Collision2D collision)
    {
        if (collision.transform == currentPlatform)
        {
            transform.SetParent(originalParent);
            currentPlatform = null;
        }
    }

    bool IsStandingOn(Collision2D collision)
    {
        foreach (ContactPoint2D contact in collision.contacts)
        {
            if (contact.normal.y > 0.5f) return true;
        }
        return false;
    }
}

6. 適用可能な移動方式と選び方

方法1:親子付け方法2:移動量を加算方法3:物理ステップで加算(応用)
実装の簡単さ◎○△
床の回転に追従○×(位置のみ)×(位置のみ)
Transform で動くキャラクター○○△
Rigidbody で動くキャラクター△(干渉することがある)△(物理と衝突しやすい)○(要検証)
床を離れるときの慣性引き継がない引き継がない引き継がない
  • Transform で動かすキャラクター なら、まず方法1か方法2を選びます。床が回転するなら方法1です。
  • Rigidbody で動かすキャラクター は、どの方法でも物理演算との兼ね合いが出ます。方法3を試しつつ、操作スクリプトとの組み合わせで必ず動作確認してください。

7. まとめ

  • 動く床の連動には、キャラクターに Rigidbody、床に Kinematic Rigidbody、床にタグが必要です。
  • 「乗った」判定は、タグだけでなく接触の法線(足元かどうか)まで確認します。さらに、OnCollisionStay の中で「足元でなくなったら解除する」処理を入れると、床の側面に触れ続けたときの取りこぼしを防げます。
  • 床は FixedUpdate + MovePosition で動かし、キャラクター側は動かし方に合わせて方法を選びます。
  • Transform 移動のキャラクターには方法1(親子付け)または方法2(移動量加算)が向いています。Rigidbody 移動のキャラクターに使う方法3は応用で、動作確認が欠かせません。
  • 方法2は、床の更新タイミング(Interpolate 設定を含む)に注意して、LateUpdate で反映します。
  • コードを組んだら、「4. 動作確認の設定と手順」の項目で、乗る・歩く・降りる・側面に触れる・再び乗る、を順に確認してください。

関連記事

訪問数 897 回, 今日の訪問数 1回

広告

Unity

Posted by hidepon