Unityを触り始めた頃は、とりあえず何でもかんでも Update() に書きがちですよね。移動、ジャンプ、カメラ、UI更新、入力処理…すべてが1つのスクリプトに詰め込まれた「Godクラス」になってしまうと、だんだん何がどこで動いているのか分からなくなります。

特に「しゃがみ」機能は、移動処理と一緒にベタ書きされやすい代表例です。

  • Update() の中で入力チェックと高さ変更を全部やってしまう
  • 押している間だけしゃがむ処理と、トグル(押すたびに切り替え)処理が混在する
  • 別の入力システムに変えたいときに地獄を見る

こうなると、ちょっとした仕様変更(「しゃがみはトグル式にしたい」「立ち状態に戻せないようにしたい」など)にも大きな影響が出てしまいます。

そこでこの記事では、「しゃがみ機能」だけを1つのコンポーネントに切り出し、ボタンを押すたびに「しゃがみ / 立ち」を切り替えるシンプルなトグル式のコンポーネント ToggleCrouch を作ってみましょう。

【Unity】ボタン一発でしゃがみトグル!「ToggleCrouch」コンポーネント

今回の ToggleCrouch コンポーネントは、次のような役割にきっちり分割します。

  • 入力の検知:しゃがみボタンが「押された瞬間」を検知
  • 状態管理:今が「しゃがみ中」か「立ち状態」かを保持
  • 見た目と当たり判定の変更:キャラクターの高さやコライダーのサイズを変更

移動やカメラ制御とは切り離しているので、プレイヤーだけでなく「しゃがみ可能な敵キャラ」や「しゃがむギミック付きのオブジェクト」にもそのまま流用できます。

フルソースコード


using UnityEngine;
using UnityEngine.InputSystem; // 新Input System用

/// <summary>
/// ボタンを押すたびに「しゃがみ / 立ち」を切り替えるコンポーネント。
/// - Transformのスケールを変更して見た目の高さを変える
/// - CapsuleCollider があれば高さも自動調整
/// - 新Input Systemの InputAction からトグル入力を受け取る
/// 
/// ※キャラクターの移動処理とは分けてアタッチする想定です。
/// </summary>
[RequireComponent(typeof(Transform))]
public class ToggleCrouch : MonoBehaviour
{
    // --- 設定項目(インスペクターから調整) ---

    [Header("しゃがみ入力")]
    [Tooltip("しゃがみトグル用の InputAction。\n" +
             "Action Type: Button を想定しています。")]
    [SerializeField] private InputActionReference crouchAction;

    [Header("スケール設定")]
    [Tooltip("立ち状態のローカルスケール(初期値を自動取得することも可能)。")]
    [SerializeField] private Vector3 standingScale = Vector3.one;

    [Tooltip("しゃがみ状態のローカルスケール。Yだけ小さくするのが一般的です。")]
    [SerializeField] private Vector3 crouchingScale = new Vector3(1f, 0.5f, 1f);

    [Header("コライダー設定(任意)")]
    [Tooltip("CapsuleCollider を自動で探すかどうか。")]
    [SerializeField] private bool autoFindCapsuleCollider = true;

    [Tooltip("しゃがみ時の高さ倍率(CapsuleCollider.height に掛ける倍率)。")]
    [SerializeField, Range(0.1f, 1f)] private float crouchHeightMultiplier = 0.5f;

    [Tooltip("高さ変更をスムーズに補間するかどうか。false にすると即座に切り替え。")]
    [SerializeField] private bool smoothTransition = true;

    [Tooltip("スムーズに切り替える場合の補間速度。")]
    [SerializeField, Min(0.1f)] private float transitionSpeed = 10f;

    // --- 内部状態 ---

    // 現在しゃがみ状態かどうか
    public bool IsCrouching { get; private set; }

    // Transform と CapsuleCollider の参照
    private Transform _cachedTransform;
    private CapsuleCollider _capsule;

    // 立ち状態のコライダー高さを保存
    private float _standingColliderHeight;
    private Vector3 _standingColliderCenter;

    // 現在の目標スケール(補間用)
    private Vector3 _targetScale;

    private void Awake()
    {
        _cachedTransform = transform;

        // 立ち状態のスケールを自動取得したい場合はここで上書きもあり
        // ただしインスペクターで明示的に設定したいことも多いので、
        // 今回は「インスペクターの設定を優先」する仕様にしています。
        if (standingScale == Vector3.one)
        {
            standingScale = _cachedTransform.localScale;
        }

        _targetScale = standingScale;

        // CapsuleCollider を自動取得
        if (autoFindCapsuleCollider)
        {
            _capsule = GetComponent<CapsuleCollider>();
        }

        if (_capsule != null)
        {
            _standingColliderHeight = _capsule.height;
            _standingColliderCenter = _capsule.center;
        }

        // InputAction の有効化
        if (crouchAction != null && crouchAction.action != null)
        {
            // ボタンが押された瞬間にコールバック
            crouchAction.action.performed += OnCrouchActionPerformed;
            if (!crouchAction.action.enabled)
            {
                crouchAction.action.Enable();
            }
        }
        else
        {
            Debug.LogWarning(
                $"[ToggleCrouch] crouchAction が設定されていません。インスペクターで InputActionReference を割り当ててください。",
                this);
        }
    }

    private void OnDestroy()
    {
        // アンフックを忘れると、Play停止時などにエラーが出ることがあります
        if (crouchAction != null && crouchAction.action != null)
        {
            crouchAction.action.performed -= OnCrouchActionPerformed;
        }
    }

    private void Update()
    {
        // スムーズ遷移モードなら、現在スケールを目標スケールへ補間
        if (smoothTransition)
        {
            _cachedTransform.localScale = Vector3.Lerp(
                _cachedTransform.localScale,
                _targetScale,
                Time.deltaTime * transitionSpeed
            );
        }
        else
        {
            // 補間しない場合は常に目標スケールをそのまま適用
            _cachedTransform.localScale = _targetScale;
        }
    }

    /// <summary>
    /// InputAction の performed イベントから呼ばれるコールバック。
    /// ボタンが押されたタイミングでしゃがみ状態をトグルします。
    /// </summary>
    /// <param name="ctx">InputAction.CallbackContext</param>
    private void OnCrouchActionPerformed(InputAction.CallbackContext ctx)
    {
        // ボタンが押された瞬間だけを扱いたいので、押下状態の確認を行う
        // (Action Type: Button の場合、performed は押下時に1回だけ呼ばれます)
        if (!ctx.ReadValueAsButton())
        {
            return;
        }

        Toggle();
    }

    /// <summary>
    /// しゃがみ状態をトグル(切り替え)します。
    /// 外部スクリプトからも呼べるように public にしています。
    /// </summary>
    public void Toggle()
    {
        if (IsCrouching)
        {
            Stand();
        }
        else
        {
            Crouch();
        }
    }

    /// <summary>
    /// しゃがみ状態に移行します。
    /// </summary>
    public void Crouch()
    {
        IsCrouching = true;
        _targetScale = crouchingScale;

        UpdateColliderForCrouch(true);
    }

    /// <summary>
    /// 立ち状態に戻します。
    /// </summary>
    public void Stand()
    {
        IsCrouching = false;
        _targetScale = standingScale;

        UpdateColliderForCrouch(false);
    }

    /// <summary>
    /// CapsuleCollider の高さ・中心をしゃがみ状態に合わせて変更します。
    /// </summary>
    /// <param name="toCrouch">true: しゃがみ / false: 立ち</param>
    private void UpdateColliderForCrouch(bool toCrouch)
    {
        if (_capsule == null) return;

        if (toCrouch)
        {
            // 高さを縮める
            _capsule.height = _standingColliderHeight * crouchHeightMultiplier;

            // 中心を少し下げる(単純に半分下げる例)
            float centerOffset = (_standingColliderHeight - _capsule.height) * 0.5f;
            _capsule.center = _standingColliderCenter - new Vector3(0f, centerOffset, 0f);
        }
        else
        {
            // 元の値に戻す
            _capsule.height = _standingColliderHeight;
            _capsule.center = _standingColliderCenter;
        }
    }

#if UNITY_EDITOR
    private void OnValidate()
    {
        // Editor 上でパラメータをいじったときに、
        // standingScale が (0,0,0) になってしまう事故を軽減
        if (standingScale == Vector3.zero)
        {
            standingScale = Vector3.one;
        }

        if (crouchingScale == Vector3.zero)
        {
            crouchingScale = new Vector3(1f, 0.5f, 1f);
        }
    }
#endif
}

使い方の手順

  1. Input Action を準備する
    Unity6 では新しい Input System が標準なので、まずはしゃがみ用のアクションを用意します。
    • Assets > Create > Input Actions で Input Actions アセットを作成
    • 例えば Player というアクションマップを作り、その中に Crouch というアクションを追加
    • Action Type: Button に設定し、キーボードなら CtrlC、ゲームパッドなら B などをバインド
    • アセットを保存し、PlayerInput コンポーネントなどから有効化しておきます(またはスクリプトから Enable() してもOK)
  2. プレイヤー(またはキャラ)にコンポーネントを追加
    例として、FPS視点のプレイヤーキャラクターに適用してみます。
    • プレイヤーの GameObject(例: Player)を選択
    • Add Component から ToggleCrouch を追加
    • CapsuleCollider を使っている場合は自動で検出されます(autoFindCapsuleCollider が ON のままでOK)
  3. InputActionReference を割り当てる
    • 作成した Input Actions アセットを選択し、インスペクターで該当アクション(Player/Crouch など)を確認
    • ToggleCrouchCrouch Action フィールドに、InputActionReference をドラッグ&ドロップ
    • もし InputActionReference を持つ ScriptableObject を別途作っている場合は、それを割り当ててもOKです
  4. パラメータを微調整してテスト
    • Standing Scale:立ち状態のスケール。通常は (1, 1, 1) のままでOK
    • Crouching Scale:しゃがみ時のスケール。例:(1, 0.5, 1)
    • Crouch Height Multiplier:コライダーの高さ倍率。0.5 なら半分に
    • Smooth Transition:ONにすると、にゅるっとしゃがむアニメっぽい動きになります
    • Transition Speed:補間速度。大きくするとキビキビ、小さくするとゆっくり

    再生してしゃがみボタンを押すと、押すたびに「しゃがみ / 立ち」が切り替わるのが確認できます。

プレイヤー以外にも、例えば次のような使い方ができます。

  • 敵キャラ:プレイヤーの攻撃に合わせてしゃがむギミックを作り、AI側から Toggle() を呼ぶ
  • 動く床:床が一定タイミングで低くなる演出として、実は床オブジェクトに ToggleCrouch をつけてスケールだけ使う
  • 隠れオブジェクト:プレイヤーが近づいてボタンを押すと、オブジェクトが「しゃがむ(縮む)」ことで隠し通路が現れる

メリットと応用

ToggleCrouch を分離したことで、次のようなメリットがあります。

  • 移動ロジックからしゃがみ機能を切り離せる
    プレイヤーの移動スクリプトは「移動」に専念でき、しゃがみの有無に関係なくシンプルに保てます。
  • プレハブの再利用性が高い
    しゃがみ可能なキャラのプレハブを1つ作っておけば、敵・NPC・マルチプレイ用キャラなどにポンポン複製できます。
  • レベルデザインが楽になる
    「ここはしゃがまないと通れない通路」「ここだけしゃがみ禁止」などのギミックを、ToggleCrouch の ON/OFF で簡単に制御できます。
  • 入力仕様の変更に強い
    ボタンを長押しでしゃがむタイプに変えたいときも、ToggleCrouch の入力部分だけ差し替えればOKです。

応用として、例えば「天井に頭をぶつけているときは立ち上がれない」といった制限を追加することもできます。
下のような小さな関数を追加し、Stand() の前にチェックを挟むだけで実現できます。


    /// <summary>
    /// 頭上に障害物がないかをチェックし、立ち上がっても安全かどうかを返します。
    /// CapsuleCollider を使っている前提の簡易実装です。
    /// </summary>
    private bool CanStandUp()
    {
        if (_capsule == null) return true;

        // 立ち状態の高さ分だけ上方向にチェックする
        float checkDistance = _standingColliderHeight - _capsule.height;
        if (checkDistance <= 0f) return true;

        Vector3 origin = _cachedTransform.position + Vector3.up * (_capsule.height * 0.5f);
        Ray ray = new Ray(origin, Vector3.up);

        // 頭上に何かあるかどうか
        bool hit = Physics.SphereCast(
            ray,
            _capsule.radius * 0.9f, // 少しだけ小さめに
            checkDistance,
            ~0,                     // すべてのレイヤーを対象
            QueryTriggerInteraction.Ignore
        );

        return !hit;
    }

この関数を利用して、Stand() 内で if (!CanStandUp()) return; のようにすれば、天井が低い場所では無理に立ち上がらない安全なしゃがみシステムに発展させられます。

こうした小さなコンポーネントの積み重ねで、「巨大な1ファイル」ではなく「役割ごとに分割された気持ちいいプロジェクト」を目指していきましょう。