Unityを触り始めた頃によくあるパターンとして、プレイヤー入力の処理をすべて Update() にベタ書きしてしまう、というものがあります。

例えば「ボタンを押したらジャンプ」「長押ししたらチャージ攻撃」みたいな処理を、1つのスクリプトの Update() の中に if 文を大量に並べてしまうと、

  • どのボタンがどの機能を担当しているのか分かりにくい
  • 同じような入力判定を別のオブジェクトでも使い回したくてもコピペ地獄になる
  • テストやデバッグのために一部だけ無効化したいのに、巨大なスクリプトを毎回開いて編集する必要がある

といった問題が出てきます。

そこでこの記事では、「ボタンの短押し・長押し判定」だけに責務を絞った TapHoldCheck コンポーネント を用意して、

  • 短く押したら A のアクション
  • 長く押したら B のアクション

といった分岐を、どの GameObject にも簡単に追加できる形にしてみましょう。コンポーネントを小さく分割しておくと、プレイヤーだけでなく敵やギミックにも「長押し判定」をそのまま流用できるようになります。

【Unity】短押しと長押しをキレイに分離!「TapHoldCheck」コンポーネント

フルコード(TapHoldCheck.cs)


using System;
using UnityEngine;
using UnityEngine.Events;

/// <summary>
/// ボタンの「短押し/長押し」を判定するコンポーネント。
/// 入力そのものは「bool(押しているかどうか)」で受け取り、
/// 長押し時間の閾値やイベント発火だけを担当します。
/// 
/// - 短押し: 押し始めてから longPressThreshold 未満で離したとき
/// - 長押し: 押し始めてから longPressThreshold 以上で離したとき
/// 
/// 入力ソース(キーボード、マウス、ゲームパッド、新InputSystemなど)は
/// 別コンポーネントで取得し、このクラスには「押している/押していない」
/// の情報だけを渡す設計にしておくと、再利用性が高くなります。
/// </summary>
public class TapHoldCheck : MonoBehaviour
{
    [Header("長押し判定の設定")]
    [Tooltip("この秒数以上押し続けたら「長押し」とみなします。")]
    [SerializeField] private float longPressThreshold = 0.5f;

    [Header("入力状態(外部からセットする想定)")]
    [Tooltip("現在ボタンが押されているかどうか。外部スクリプトから SetPressed() を呼んで更新します。")]
    [SerializeField] private bool isPressed = false;

    [Header("イベント")]
    [Tooltip("短押しだったときに呼ばれるイベント")]
    [SerializeField] private UnityEvent onShortPress;
    [Tooltip("長押しだったときに呼ばれるイベント")]
    [SerializeField] private UnityEvent onLongPress;
    [Tooltip("押し始めた瞬間に呼ばれるイベント(任意)")]
    [SerializeField] private UnityEvent onPressStarted;
    [Tooltip("押している間、毎フレーム呼ばれるイベント(任意)")]
    [SerializeField] private UnityEvent onPressing;

    // 内部状態
    private bool wasPressedPreviousFrame = false; // 前フレームで押されていたか
    private float pressStartTime = 0f;            // 押し始めた時間(Time.time)

    /// <summary>
    /// 外部から「押されているかどうか」をセットするための関数。
    /// 例: 入力コンポーネントの Update から呼び出す。
    /// </summary>
    /// <param name="pressed">現在押されているなら true</param>
    public void SetPressed(bool pressed)
    {
        isPressed = pressed;
    }

    private void Update()
    {
        // 1. 押し始めを検出
        if (!wasPressedPreviousFrame && isPressed)
        {
            // 押し始めた瞬間
            pressStartTime = Time.time;
            onPressStarted?.Invoke();
        }

        // 2. 押している間に毎フレーム呼ぶイベント(任意)
        if (isPressed)
        {
            onPressing?.Invoke();
        }

        // 3. 離した瞬間を検出
        if (wasPressedPreviousFrame && !isPressed)
        {
            float pressDuration = Time.time - pressStartTime;

            if (pressDuration >= longPressThreshold)
            {
                // 長押し
                onLongPress?.Invoke();
            }
            else
            {
                // 短押し
                onShortPress?.Invoke();
            }
        }

        // 状態を次フレーム用に保存
        wasPressedPreviousFrame = isPressed;
    }

    #region 便利な公開メソッド(任意で使用)

    /// <summary>
    /// 長押しの閾値を動的に変更したい場合に使用します。
    /// 例: 難易度によって調整したいときなど。
    /// </summary>
    public void SetLongPressThreshold(float seconds)
    {
        longPressThreshold = Mathf.Max(0f, seconds);
    }

    /// <summary>
    /// 現在の押下時間を返します。
    /// 押されていないときは 0 を返します。
    /// </summary>
    public float GetCurrentPressDuration()
    {
        if (!isPressed) return 0f;
        return Time.time - pressStartTime;
    }

    #endregion
}

入力取得用のサンプルコンポーネント(任意)

上の TapHoldCheck は「押されているかどうか」を SetPressed() で受け取る設計にしています。
ここでは、キーボードのスペースキーを入力ソースにする簡単な例を載せておきます。


using UnityEngine;

/// <summary>
/// キーボードのスペースキー入力を TapHoldCheck に渡すだけのシンプルなコンポーネント。
/// 入力ソースと判定ロジックを分離しておくと、
/// 後から「ゲームパッドのボタンに変える」「UIボタンに変える」といった変更がしやすくなります。
/// </summary>
[RequireComponent(typeof(TapHoldCheck))]
public class SpaceKeyTapInput : MonoBehaviour
{
    [Tooltip("監視するキー。デフォルトは Space。")]
    [SerializeField] private KeyCode targetKey = KeyCode.Space;

    private TapHoldCheck tapHoldCheck;

    private void Awake()
    {
        tapHoldCheck = GetComponent<TapHoldCheck>();
    }

    private void Update()
    {
        // キーが押されているかどうかを取得
        bool pressed = Input.GetKey(targetKey);

        // TapHoldCheck に渡す
        tapHoldCheck.SetPressed(pressed);
    }
}

アクション実装のサンプル(プレイヤー用)

短押しで「ジャンプ」、長押しで「チャージ攻撃」を行う例です。


using UnityEngine;

/// <summary>
/// TapHoldCheck のイベントに紐づけるためのサンプルアクション。
/// プレイヤーの GameObject にアタッチして使います。
/// </summary>
public class PlayerTapActions : MonoBehaviour
{
    [Header("ジャンプ設定")]
    [SerializeField] private float jumpForce = 5f;
    [SerializeField] private Rigidbody playerRigidbody;

    [Header("チャージ攻撃設定")]
    [SerializeField] private float chargePower = 10f;

    private void Reset()
    {
        // 自動で Rigidbody を見つけてセットしておく(ある場合のみ)
        if (playerRigidbody == null)
        {
            playerRigidbody = GetComponent<Rigidbody>();
        }
    }

    /// <summary>
    /// 短押し時のジャンプ処理(TapHoldCheck.onShortPress に登録)
    /// </summary>
    public void DoJump()
    {
        if (playerRigidbody == null)
        {
            Debug.LogWarning("PlayerTapActions: Rigidbody が設定されていません。ジャンプできません。");
            return;
        }

        // 上方向に力を加えるシンプルなジャンプ
        playerRigidbody.AddForce(Vector3.up * jumpForce, ForceMode.VelocityChange);
    }

    /// <summary>
    /// 長押し時のチャージ攻撃処理(TapHoldCheck.onLongPress に登録)
    /// </summary>
    public void DoChargeAttack()
    {
        // ここでは例として、前方向に大きく吹っ飛ぶだけの処理にしておきます。
        if (playerRigidbody == null)
        {
            Debug.LogWarning("PlayerTapActions: Rigidbody が設定されていません。チャージ攻撃できません。");
            return;
        }

        Vector3 forward = transform.forward;
        playerRigidbody.AddForce(forward * chargePower, ForceMode.VelocityChange);

        Debug.Log("チャージ攻撃発動!");
    }
}

使い方の手順

  1. コンポーネントの配置
    • 空の GameObject(またはプレイヤーの GameObject)を用意します。
    • そこに TapHoldCheck コンポーネントを追加します。
    • キーボード入力を使いたい場合は、同じオブジェクトに SpaceKeyTapInput も追加します。
  2. アクション用スクリプトを追加
    • プレイヤーにジャンプやチャージ攻撃をさせたい場合は、PlayerTapActions をプレイヤーの GameObject に追加します。
    • playerRigidbody にプレイヤーの Rigidbody をアサインするか、Reset() で自動セットされるようにしておきます。
  3. イベントの紐づけ
    • TapHoldCheck を選択し、インスペクターの On Short PressOn Long Press を確認します。
    • それぞれの「+」ボタンを押して、PlayerTapActions が付いている GameObject をドラッグ&ドロップします。
    • On Short Press には PlayerTapActions.DoJump を、On Long Press には PlayerTapActions.DoChargeAttack を選択します。
  4. 実行してテスト
    • 再生ボタンを押して、スペースキーを「軽くタップ」するとジャンプ。
    • スペースキーを longPressThreshold(デフォルト 0.5 秒)以上押し続けてから離すと、チャージ攻撃が発動します。
    • longPressThreshold の値を変えれば、どれくらいの長さで長押しとみなすかを簡単に調整できます。

同じ要領で、例えば

  • 敵キャラ:短押しで「通常弾」、長押しで「溜め撃ち」
  • 動く床:短押しで「一瞬だけ動く」、長押しで「一定時間動き続ける」
  • UIボタン:短押しで「決定」、長押しで「詳細情報を表示」

といった挙動も、TapHoldCheck のイベントに別のアクションを紐づけるだけで実現できます。

メリットと応用

TapHoldCheck をコンポーネントとして分離しておくと、次のようなメリットがあります。

  • プレハブがシンプルになる
    「入力の種類」と「押し方の判定」と「実際のアクション」がきれいに分かれるので、
    プレイヤー、敵、ギミックなど、どのプレハブにも同じ構造で組み込めます。
  • レベルデザインが楽になる
    デザイナーやレベルデザイナーは、インスペクター上で On Short Press / On Long Press
    それぞれのアクションをドラッグ&ドロップで割り当てるだけで済みます。
  • 入力ソースの差し替えが容易
    キーボードからゲームパッド、新InputSystem、UIボタンなど、入力の取り方を変えたいときは
    SpaceKeyTapInput のような「入力取得コンポーネント」だけ差し替えればOKです。
  • テストがしやすい
    TapHoldCheck 単体をテストしたい場合は、エディタ上で SetPressed(true/false)
    呼び出すだけの簡単なデバッグ用スクリプトを作ればよく、巨大な God クラスを触る必要がなくなります。

応用として、「長押し中にゲージを伸ばす」ような UI を作るのも簡単です。
例えば、TapHoldCheck の onPressing に以下のような関数を登録しておくと、押している間だけゲージが増えます。


using UnityEngine;
using UnityEngine.UI;

public class ChargeGaugeUI : MonoBehaviour
{
    [SerializeField] private Image gaugeFillImage;
    [SerializeField] private TapHoldCheck tapHoldCheck;
    [SerializeField] private float maxChargeTime = 2f; // 何秒でゲージMAXにするか

    private void Reset()
    {
        tapHoldCheck = GetComponent<TapHoldCheck>();
    }

    /// <summary>
    /// TapHoldCheck.onPressing に登録しておくと、
    /// 長押ししている間だけゲージが徐々に増えていきます。
    /// </summary>
    public void UpdateGaugeWhilePressing()
    {
        if (gaugeFillImage == null || tapHoldCheck == null) return;

        float duration = tapHoldCheck.GetCurrentPressDuration();
        float normalized = Mathf.Clamp01(duration / maxChargeTime);
        gaugeFillImage.fillAmount = normalized;
    }
}

このように、「長押し判定」という1つの責務だけを切り出したコンポーネントを用意しておくと、
ゲーム全体の設計がすっきりして、後からの拡張や調整もかなり楽になります。
ぜひプロジェクトの中で、入力関連の処理をコンポーネント単位に分割していってみてください。