Unityを触り始めた頃って、つい何でもかんでも Update() に書いてしまいがちですよね。タッチ入力の処理、スワイプ判定、プレイヤーの移動、エフェクトの再生…全部ひとつのスクリプトに押し込むと、最初は動いていても、だんだん「どこを直せばいいのかわからない巨大スクリプト(Godクラス)」になってしまいます。

特にスマホ向けのゲームでありがちなのが、「入力処理」と「ゲームのロジック」をベタ書きで混ぜてしまうパターンです。例えば「スワイプしたらプレイヤーを動かす」処理を、プレイヤーのスクリプトの Update() に直接書いてしまうと、入力方式を変えたいだけでもプレイヤーのコードをいじる羽目になります。

そこで今回は、「スワイプを検知するだけ」に責務を絞ったコンポーネントを用意して、
ゲーム側とは「イベント(シグナル)」でゆるく連携するスタイルを紹介します。

【Unity】タッチ操作をスマートに分離!「SwipeDetector」コンポーネント

ここで作る SwipeDetector コンポーネントは、

  • タッチ(またはマウス)で指を押した位置と離した位置を記録
  • 一定距離以上動いていたらスワイプと判定
  • 上下左右の4方向を判定して、C#イベントで通知

という、かなり「入力検知」に特化した小さなコンポーネントです。
プレイヤーやUIは、このイベントを購読して「スワイプされたらこう動く」というロジックだけを書けばOK、という構成にしていきましょう。

フルコード:SwipeDetector.cs


using System;
using UnityEngine;

/// <summary>スワイプ方向を表す列挙体</summary>
public enum SwipeDirection
{
    None,
    Up,
    Down,
    Left,
    Right
}

/// <summary>
/// タッチ(またはマウス)によるスワイプを検知し、
/// 上下左右の方向をイベントで通知するコンポーネント。
/// ゲームロジックとはイベント経由で疎結合に連携するのがポイント。
/// </summary>
public class SwipeDetector : MonoBehaviour
{
    [Header("スワイプ判定設定")]
    [SerializeField]
    private float minSwipeDistance = 50f;
    // 画面上で「この距離以上」動いたらスワイプとみなす(ピクセル相当)

    [SerializeField]
    private float maxSwipeTime = 1.0f;
    // この時間以内の操作だけをスワイプとして扱う(長押しと区別したい場合に使用)

    [Header("入力モード")]
    [SerializeField]
    private bool useMouseAsTouch = true;
    // PC上でテストしやすいように、マウスドラッグでもスワイプとして扱うかどうか

    // スワイプ開始位置(スクリーン座標)
    private Vector2 _startPosition;
    // スワイプ開始時刻
    private float _startTime;
    // 現在タッチ中かどうか
    private bool _isSwiping;

    /// <summary>
    /// スワイプが完了したときに発火するイベント。
    /// 引数は検知されたスワイプ方向。
    /// </summary>
    public event Action<SwipeDirection> OnSwipeDetected;

    private void Update()
    {
        // 実行環境によって入力を切り替える
        if (Input.touchSupported && Input.touchCount > 0)
        {
            HandleTouchInput();
        }
        else if (useMouseAsTouch)
        {
            HandleMouseInput();
        }
    }

    /// <summary>
    /// タッチ入力からスワイプを検知
    /// </summary>
    private void HandleTouchInput()
    {
        Touch touch = Input.GetTouch(0);

        switch (touch.phase)
        {
            case TouchPhase.Began:
                _isSwiping = true;
                _startPosition = touch.position;
                _startTime = Time.time;
                break;

            case TouchPhase.Moved:
            case TouchPhase.Stationary:
                // 移動中 or 押しっぱなし中は特に何もしない
                break;

            case TouchPhase.Ended:
            case TouchPhase.Canceled:
                if (_isSwiping)
                {
                    EvaluateSwipe(touch.position, Time.time - _startTime);
                }
                _isSwiping = false;
                break;
        }
    }

    /// <summary>
    /// マウス入力からスワイプを検知(エディタ/PC用)
    /// </summary>
    private void HandleMouseInput()
    {
        // 左クリック押し始め
        if (Input.GetMouseButtonDown(0))
        {
            _isSwiping = true;
            _startPosition = Input.mousePosition;
            _startTime = Time.time;
        }

        // 左クリックを離したとき
        if (Input.GetMouseButtonUp(0))
        {
            if (_isSwiping)
            {
                Vector2 endPosition = Input.mousePosition;
                float duration = Time.time - _startTime;
                EvaluateSwipe(endPosition, duration);
            }
            _isSwiping = false;
        }
    }

    /// <summary>
    /// 開始位置・終了位置・経過時間からスワイプかどうかを判定し、
    /// スワイプなら方向を計算してイベントを発火する。
    /// </summary>
    /// <param name="endPosition">スワイプ終了位置(スクリーン座標)</param>
    /// <param name="duration">スワイプにかかった時間(秒)</param>
    private void EvaluateSwipe(Vector2 endPosition, float duration)
    {
        // 長すぎる操作はスワイプ扱いしない(長押しなど)
        if (duration > maxSwipeTime)
        {
            return;
        }

        Vector2 delta = endPosition - _startPosition;
        float distance = delta.magnitude;

        // 距離が足りない場合はスワイプではない
        if (distance < minSwipeDistance)
        {
            return;
        }

        // どの方向が強いかで上下左右を判定
        SwipeDirection direction;

        // X方向とY方向、どちらの絶対値が大きいかで「横スワイプか縦スワイプか」を決定
        if (Mathf.Abs(delta.x) > Mathf.Abs(delta.y))
        {
            // 横方向のスワイプ
            direction = delta.x > 0 ? SwipeDirection.Right : SwipeDirection.Left;
        }
        else
        {
            // 縦方向のスワイプ
            direction = delta.y > 0 ? SwipeDirection.Up : SwipeDirection.Down;
        }

        // イベント購読者がいれば通知
        OnSwipeDetected?.Invoke(direction);
    }

    /// <summary>
    /// 外部からスワイプ検知イベントを購読しやすくするためのヘルパー。
    /// 例: swipeDetector.AddListener(dir => Debug.Log(dir));
    /// </summary>
    /// <param name="listener">スワイプ方向を受け取るコールバック</param>
    public void AddListener(Action<SwipeDirection> listener)
    {
        OnSwipeDetected += listener;
    }

    /// <summary>
    /// 購読解除用のヘルパー。
    /// </summary>
    public void RemoveListener(Action<SwipeDirection> listener)
    {
        OnSwipeDetected -= listener;
    }
}

使い方の手順

  1. コンポーネントを配置する
    • 空の GameObject を作成して名前を「InputManager」などにします。
    • SwipeDetector スクリプトをそのオブジェクトにアタッチします。
    • インスペクターで
      • Min Swipe Distance(例: 50〜100)
      • Max Swipe Time(例: 0.8〜1.0)
      • Use Mouse As Touch(PCテスト時はチェックON)

      をお好みで調整します。

  2. プレイヤーやUI側でイベントを購読する
    例として、スワイプ方向にプレイヤーを1マス動かすコンポーネントを作ります。
    
    using UnityEngine;
    
    /// <summary>
    /// SwipeDetector からのスワイプ方向を受け取り、
    /// プレイヤーをグリッド状に移動させる簡単な例。
    /// </summary>
    [RequireComponent(typeof(Transform))]
    public class SwipeMovePlayer : MonoBehaviour
    {
        [SerializeField]
        private SwipeDetector swipeDetector;
        // Hierarchy 上の SwipeDetector をドラッグ&ドロップでセット
    
        [SerializeField]
        private float moveStep = 1f;
        // 1回のスワイプでどれだけ動くか
    
        private void Awake()
        {
            if (swipeDetector == null)
            {
                // 近くの SwipeDetector を自動取得(なければ手動でセットしてもOK)
                swipeDetector = FindAnyObjectByType<SwipeDetector>();
            }
        }
    
        private void OnEnable()
        {
            if (swipeDetector != null)
            {
                swipeDetector.AddListener(OnSwiped);
            }
        }
    
        private void OnDisable()
        {
            if (swipeDetector != null)
            {
                swipeDetector.RemoveListener(OnSwiped);
            }
        }
    
        private void OnSwiped(SwipeDirection direction)
        {
            Vector3 offset = Vector3.zero;
    
            switch (direction)
            {
                case SwipeDirection.Up:
                    offset = Vector3.up * moveStep;
                    break;
                case SwipeDirection.Down:
                    offset = Vector3.down * moveStep;
                    break;
                case SwipeDirection.Left:
                    offset = Vector3.left * moveStep;
                    break;
                case SwipeDirection.Right:
                    offset = Vector3.right * moveStep;
                    break;
            }
    
            transform.position += offset;
        }
    }
    
    • プレイヤーの GameObject に SwipeMovePlayer をアタッチ。
    • SwipeDetector フィールドに、先ほど作った InputManager の SwipeDetector をドラッグして関連付けます。
  3. 敵や動く床など、別のオブジェクトでも再利用する
    例えば「スワイプした方向にだけ動く床」を作る場合は、別のコンポーネントで同じようにイベントを購読するだけです。
    
    using UnityEngine;
    
    /// <summary>
    /// スワイプ方向に応じて ON/OFF を切り替える床の例。
    /// 上スワイプで有効、下スワイプで無効にする。
    /// </summary>
    public class SwipeTogglePlatform : MonoBehaviour
    {
        [SerializeField]
        private SwipeDetector swipeDetector;
    
        [SerializeField]
        private GameObject platformVisual;
        // 実際の床の見た目(SpriteRenderer 付きオブジェクトなど)
    
        private void Awake()
        {
            if (swipeDetector == null)
            {
                swipeDetector = FindAnyObjectByType<SwipeDetector>();
            }
        }
    
        private void OnEnable()
        {
            if (swipeDetector != null)
            {
                swipeDetector.AddListener(OnSwiped);
            }
        }
    
        private void OnDisable()
        {
            if (swipeDetector != null)
            {
                swipeDetector.RemoveListener(OnSwiped);
            }
        }
    
        private void OnSwiped(SwipeDirection direction)
        {
            if (direction == SwipeDirection.Up)
            {
                platformVisual.SetActive(true);
            }
            else if (direction == SwipeDirection.Down)
            {
                platformVisual.SetActive(false);
            }
        }
    }
    

    同じ SwipeDetector から、複数のオブジェクトが好き勝手にイベントを受け取れるので、入力とゲームロジックをきれいに分離できます。

  4. UIのデバッグ用にログを出しておく
    開発中は、スワイプが正しく検知されているか確認したい場面が多いので、専用の「ログ表示コンポーネント」を1つ作っておくと便利です。
    
    using UnityEngine;
    
    /// <summary>
    /// スワイプ方向をコンソールにログ出力するだけのデバッグ用コンポーネント。
    /// </summary>
    public class SwipeLogger : MonoBehaviour
    {
        [SerializeField]
        private SwipeDetector swipeDetector;
    
        private void Awake()
        {
            if (swipeDetector == null)
            {
                swipeDetector = FindAnyObjectByType<SwipeDetector>();
            }
        }
    
        private void OnEnable()
        {
            if (swipeDetector != null)
            {
                swipeDetector.AddListener(LogSwipe);
            }
        }
    
        private void OnDisable()
        {
            if (swipeDetector != null)
            {
                swipeDetector.RemoveListener(LogSwipe);
            }
        }
    
        private void LogSwipe(SwipeDirection direction)
        {
            Debug.Log($"Swipe detected: {direction}");
        }
    }
    

    こうして「検知」「移動」「可視化」をバラしておくことで、それぞれの責務がはっきりします。

メリットと応用

SwipeDetector を入力専用のコンポーネントとして切り出すメリットはかなり大きいです。

  • プレハブがシンプルになる
    プレイヤーのプレハブから「入力処理」を追い出せるので、プレイヤー側は「どう動くか」だけに集中できます。
    入力方式をスワイプからボタンに変えたくなったときも、プレイヤーのスクリプトを触らずに済みます。
  • レベルデザインが楽になる
    スワイプに反応するギミック(動く床、敵のパターン切り替え、カメラの切り替えなど)を、それぞれ独立したコンポーネントとして作れるので、ステージごとに「どのスワイプで何が起こるか」を組み合わせるだけでレベルデザインができます。
  • テストとデバッグがしやすい
    入力のテストは SwipeDetector だけ見ればよく、ゲームロジックのバグとは切り分けて考えられます。
    逆に「スワイプ検知はOKだが、プレイヤーが動かない」という場合は、プレイヤー側だけを確認すればいいので、原因の切り分けがかなり楽になります。

応用として、例えば「一定時間内に連続スワイプしたら必殺技」みたいな仕様も、SwipeDetector のイベントを別のコンポーネントで監視するだけで実現できます。

最後に、「スワイプの強さ(距離)に応じて移動量を変える」改造案の例を載せておきます。
SwipeDetector 側で距離も一緒に通知する設計に変えるのもアリですが、シンプルに「生の入力座標から自前で計算する」方法もあります。


/// <summary>
/// スワイプ方向だけでなく、スワイプ距離に応じて移動量を変える例。
/// SwipeDetector の OnSwipeDetected とは別に、
/// 生の入力座標を使って独自に距離を計算するパターン。
/// </summary>
private void MoveWithSwipePower(Vector2 startScreenPos, Vector2 endScreenPos)
{
    // スクリーン座標の差分
    Vector2 delta = endScreenPos - startScreenPos;

    // 距離に応じて 0〜1 に正規化(任意のスケール)
    float maxExpectedDistance = 300f; // 想定する最大スワイプ距離
    float power = Mathf.Clamp01(delta.magnitude / maxExpectedDistance);

    // ワールド座標での移動方向(ここでは単純に右方向にのみ適用)
    Vector3 moveDir = Vector3.right;

    // スワイプの強さで移動量をスケーリング
    float maxMoveDistance = 5f;
    Vector3 moveOffset = moveDir * (maxMoveDistance * power);

    transform.position += moveOffset;
}

このように、「入力を検知するコンポーネント」「入力に反応して動くコンポーネント」 を分けておくと、仕様変更や拡張にかなり強い構成になります。
巨大な Update() に書き足していくのではなく、小さな責務のコンポーネントを組み合わせるスタイルを意識してみてください。