Unityを触り始めた頃って、つい何でもかんでも Update() の中に書いてしまいがちですよね。
移動処理、ジャンプ、アニメーション、当たり判定、UI更新…全部1つのスクリプトに押し込んでしまうと、
- どこを直せばいいか分からない
- 機能追加のたびに巨大な
if文が増えていく - シーンごと・プレハブごとに微妙に違う挙動を分岐で書き始めて地獄
といった「Godクラス」状態になりやすいです。
今回は「梯子(はしご)を登る」という、よくあるゲーム要素を
専用のコンポーネントとして切り出してみましょう。
プレイヤーの通常移動とは別に、
- 梯子エリアに入ったら重力をオフ
- 上下入力だけを受け付けてY軸のみ移動
- 梯子エリアを出たら重力をオンに戻す
といった挙動を、LadderClimber コンポーネントで完結させていきます。
【Unity】梯子エリアでだけ重力オフ&縦移動!「LadderClimber」コンポーネント
ここでは、Rigidbody を使った2D/3D共通で扱いやすい、シンプルな梯子昇降コンポーネントを紹介します。
キャラクター本体には通常の移動コンポーネント(横移動やジャンプなど)を付けたまま、
梯子に入っている間だけこの LadderClimber が「縦移動モード」を担当するイメージです。
フルコード:LadderClimber.cs
using UnityEngine;
using UnityEngine.InputSystem; // 新Input Systemを利用する場合
/// <summary>
/// 梯子エリア内では重力を無効化し、
/// 上下入力によるY軸移動のみを行うコンポーネント。
///
/// ・Rigidbody 必須
/// ・梯子側には "Ladder" タグ付きの Trigger Collider を用意
/// </summary>
[RequireComponent(typeof(Rigidbody))]
public class LadderClimber : MonoBehaviour
{
[Header("移動設定")]
[SerializeField] private float climbSpeed = 3f; // 梯子を登る速度(上下共通)
[Header("入力設定")]
[SerializeField] private bool useNewInputSystem = true;
[SerializeField] private string verticalAxisName = "Vertical";
// 旧Input Manager使用時の縦入力軸名(デフォルト: "Vertical")
[Header("デバッグ表示")]
[SerializeField] private bool showDebugLog = false;
// 内部状態
private Rigidbody rb;
private bool isOnLadder = false; // 現在梯子エリア内かどうか
private float originalGravityScale; // 元の重力スケール(2D風に扱うためYだけ固定)
private Vector3 originalGravity; // Rigidbodyの元の重力(3D物理向け)
// 新InputSystem用の入力値
private float verticalInput = 0f;
private void Awake()
{
rb = GetComponent<Rigidbody>();
// 物理演算の重力設定を保存しておく
originalGravity = Physics.gravity;
// Rigidbodyには「重力を使うか」のフラグしかないので、
// 疑似的に重力スケールを扱うため、自分で値を保持しておくイメージ
originalGravityScale = 1f;
}
private void OnEnable()
{
// 新InputSystemを使う場合は、PlayerInputなどから
// このコンポーネントの OnMove(InputAction.CallbackContext) を呼ぶ構成がオススメです。
// ここでは「自前でActionを持つ」まではやらず、フック用の関数だけ用意しています。
}
private void Update()
{
// 梯子に乗っていないときは何もしない
if (!isOnLadder) return;
// 入力の取得
float inputY = 0f;
if (useNewInputSystem)
{
// 新Input Systemの場合:
// PlayerInputからイベントで受け取った値をそのまま使う
inputY = verticalInput;
}
else
{
// 旧Input Managerの場合:
// Project Settings > Input Manager の "Vertical" などから取得
inputY = Input.GetAxisRaw(verticalAxisName);
}
// 梯子上では、Y方向の速度のみを制御する
Vector3 velocity = rb.velocity;
velocity.y = inputY * climbSpeed;
rb.velocity = velocity;
}
/// <summary>
/// 新Input System用の入力ハンドラ。
/// PlayerInput の Action (例: "Move") に
/// このメソッドをイベントとして登録して使う想定。
///
/// 例:
/// - PlayerInput > Events > Move(performed/canceled) に
/// LadderClimber.OnMove をアサイン
/// </summary>
/// <param name="context">入力コンテキスト</param>
public void OnMove(InputAction.CallbackContext context)
{
// 上下方向のみを取り出す(Vector2/Vector3どちらでもOK)
Vector2 value = context.ReadValue<Vector2>();
verticalInput = value.y;
if (showDebugLog)
{
Debug.Log($"[LadderClimber] OnMove verticalInput = {verticalInput}");
}
}
private void OnTriggerEnter(Collider other)
{
// 梯子エリアに入ったかどうかをタグで判定
if (!other.CompareTag("Ladder")) return;
EnterLadder();
}
private void OnTriggerExit(Collider other)
{
// 梯子エリアから出たかどうかをタグで判定
if (!other.CompareTag("Ladder")) return;
ExitLadder();
}
/// <summary>
/// 梯子エリアに入ったときの処理。
/// 重力を無効化し、Y軸のみの移動モードにする。
/// </summary>
private void EnterLadder()
{
if (isOnLadder) return; // 二重呼び出し防止
isOnLadder = true;
// 現在の速度を少し抑える(急な落下を止めるため)
Vector3 v = rb.velocity;
v.x = 0f; // 横移動を止める(必要なければコメントアウト)
rb.velocity = v;
// 重力をオフ
rb.useGravity = false;
// 物理エンジン全体の重力は変更せず、個別のRigidbodyのみに適用
Physics.gravity = Vector3.zero;
if (showDebugLog)
{
Debug.Log("[LadderClimber] Enter Ladder: Gravity OFF");
}
}
/// <summary>
/// 梯子エリアから出たときの処理。
/// 重力を元に戻し、通常の移動モードに戻す。
/// </summary>
private void ExitLadder()
{
if (!isOnLadder) return;
isOnLadder = false;
// 重力をオンに戻す
rb.useGravity = true;
// グローバル重力を元に戻す
Physics.gravity = originalGravity * originalGravityScale;
// 梯子から降りた直後に、少しだけ下向きの速度を与えて
// ふわっと浮かないようにするのもアリ
Vector3 v = rb.velocity;
if (v.y > 0f) v.y = 0f;
rb.velocity = v;
if (showDebugLog)
{
Debug.Log("[LadderClimber] Exit Ladder: Gravity ON");
}
}
/// <summary>
/// 外部から「強制的に梯子モードを終了させたい」とき用のAPI。
/// 例: ダメージを受けたら梯子から落とす、など。
/// </summary>
public void ForceExitLadder()
{
ExitLadder();
}
}
ポイント
[RequireComponent(typeof(Rigidbody))]で、Rigidbody 付け忘れを防止[SerializeField] privateでインスペクタから調整可能&カプセル化OnTriggerEnter/Exitで「Ladder」タグのコライダーに入っているかどうかを判定- 梯子上では Y速度だけ を上書きし、X/Zは他のコンポーネントに任せる方針
使い方の手順
① プレイヤーキャラクターの準備
- プレイヤーの GameObject に以下をアタッチしておきます。
Rigidbody(Use Gravity: ON)- 通常の移動スクリプト(任意)
LadderClimber(今回のスクリプト)
- プレイヤーに Collider(例: CapsuleCollider)を付け、
「Is Trigger」はオフ(通常の当たり判定用)にしておきます。
② 梯子エリアの作成
- シーン上に空の GameObject を作成し、名前を
LadderAreaなどにする。 - そこに BoxCollider などのコライダーを追加し、Is Trigger を ON にする。
- この GameObject の Tag を “Ladder” に設定する。
- Tag に “Ladder” がなければ、インスペクタ上部の Tag プルダウンから「Add Tag…」で追加。
- コライダーのサイズを、梯子の範囲(プレイヤーが登ってほしい領域)に合わせて調整する。
これで、プレイヤーが LadderArea の Trigger に入ると LadderClimber が反応します。
③ 入力の設定(新Input System or 旧Input Manager)
新Input Systemを使う場合(推奨)
- プロジェクトに
Input Systemパッケージを導入し、PlayerInput を使える状態にしておきます。 - プレイヤーの GameObject に
PlayerInputコンポーネントを追加。 - Input Actions アセット内に「Move」などのアクション(タイプ: Value, Control Type: Vector2)を作成。
PlayerInputの Events モードで、- Move (performed)
- Move (canceled)
に
LadderClimberのOnMove関数をアサインします。LadderClimberのインスペクタで Use New Input System にチェックを入れる。
旧Input Managerを使う場合
- Project Settings > Input Manager で “Vertical” 軸が設定されていることを確認。
LadderClimberのインスペクタで- Use New Input System のチェックを外す
- Vertical Axis Name に “Vertical” を指定(デフォルトのままでOK)
④ 実際の使用例
- プレイヤーキャラクター
横移動は別コンポーネント(例:CharacterMover)に任せつつ、
梯子に入ったときだけLadderClimberがY軸移動を担当します。
これにより、プレイヤーの移動ロジックを「地上」「空中」「梯子」と分離しやすくなります。 - 敵キャラクター
敵にもLadderClimberを付けておけば、
パスファインディングやAI側からOnMoveを直接呼ぶことで、
敵が梯子を登ってくるような挙動も簡単に作れます。 - 動く足場+梯子
エレベーターのような動く足場に梯子エリアを一緒に乗せておけば、
プレイヤーは動きながら梯子を登ることもできます。
梯子エリアは単なる Trigger Collider なので、足場ごと動かしてもOKです。
メリットと応用
LadderClimber をコンポーネントとして分離しておくと、
- 「梯子に入ったときだけ重力オフ+縦移動」という責務が明確で、コードが読みやすい
- プレイヤーと敵、NPCなど、どのキャラにも同じ挙動を簡単に付け回せる
- レベルデザイナーは「Ladder」タグ付きの Trigger を置くだけで梯子を追加できる
- 梯子の高さや位置を変えても、スクリプト側は一切変更不要
といったメリットがあります。
プレハブ管理の観点でも、
- プレイヤープレハブに
LadderClimberを1つ付けておくだけ - 梯子は「LadderArea」プレハブを量産してシーンにポンポン置くだけ
という構成にできるので、シーン追加やレベルデザインのスピードがかなり上がります。
改造案:梯子の頂上・下端で自動的に降りる
例えば、「梯子の一番上まで登ったら自動で梯子モードを解除したい」という場合、
Update() の中でY座標をチェックして ForceExitLadder() を呼ぶような拡張ができます。
/// <summary>
/// 梯子の上下端に到達したら自動的に梯子モードを終了する例。
/// ladderTopY / ladderBottomY はインスペクタで設定しておく。
/// </summary>
[SerializeField] private float ladderTopY = 5f;
[SerializeField] private float ladderBottomY = 0f;
private void LateUpdate()
{
if (!isOnLadder) return;
float y = transform.position.y;
// 上端を超えたら梯子モード終了
if (y >= ladderTopY)
{
ForceExitLadder();
}
// 下端より下に行ったら梯子モード終了
else if (y <= ladderBottomY)
{
ForceExitLadder();
}
}
このように、小さな責務ごとにコンポーネントを分けておくと、
「梯子の端で降りる」「途中でジャンプで飛び降りる」「特定の梯子だけ速度を変える」などの
拡張がしやすくなります。
ぜひ、自分のプロジェクトに合わせて LadderClimber を育ててみてください。
