スポンサーリンク
Unity C#・スクリプト実装

【Unity】長押し・チャージ攻撃・ドラッグ操作の作り方完全ガイド|旧Input/Input System対応

Unity C#・スクリプト実装

Unityでゲームを作っていると、クリックだけの操作から一歩進んで「長押しでチャージ攻撃」を実装したくなるタイミングが来ますよね。

でも実際にコードを書いてみると、「押しっぱなしの判定がうまく消えない」「離した瞬間のはずなのに反応しない」といったバグにぶつかって、手が止まってしまう方も多いのではないでしょうか。

この記事では、長押し・チャージ攻撃をつまずかずに実装するための考え方と、旧InputとInput System両方でのコードの書き方を、順番に解説していきます。


長押しやチャージは3フェーズで設計する

長押しやチャージ攻撃をUnityで実装するときに一番大事なのは、処理を「押した瞬間」「押し続けている間」「離した瞬間」という3つのタイミングに分けて考えることです。クリックのように一瞬で終わる操作とは違い、時間の経過をともなう操作は、この分け方ができているかどうかで完成度が大きく変わってきます。

実は「押しっぱなしの判定が消えない」「離した瞬間なのに反応しない」といったバグのほとんどは、この3つのタイミングがコードの中でごちゃ混ぜになっていることが原因です。たとえば「押した瞬間にやりたいこと」と「離した瞬間にやりたいこと」が同じif文の中に紛れ込んでいると、意図しないタイミングで処理が走ってしまいます。

コードを書き始める前に、これから作りたい処理が3つのうちどのタイミングに当てはまるのかを、日本語で一文にできるかどうかを確認してみてください。もしうまく言葉にできない場合は、まだ設計が整理しきれていないサインだと考えてよいでしょう。

操作タイプ押した瞬間押し続けている間離した瞬間
クリック即座に実行なしなし
長押し計測開始・フラグON経過時間を加算フラグOFF・結果を確認
チャージ攻撃チャージ時間を0にリセットチャージ時間を加算し上限で制限溜めた時間に応じて攻撃を分岐

この表を見てわかる通り、クリックは「押した瞬間」だけで完結するシンプルな操作です。もしクリックだけの操作でまだ迷う部分がある場合は、先にこちらの記事で基本パターンを押さえておくと、この先の内容がぐっと理解しやすくなります。


旧InputとInput System、結局どっちを使う?

Unityで入力処理を書くとき、実は方式が2種類あります。昔からある旧Input(UnityEngine.Input)と、新しく推奨されているInput Systemです。「どっちを勉強すればいいの?」と迷う方も多いと思いますが、結論から言うと学習や小さなプロトタイプなら旧Input、本格的な開発やキーコンフィグまで見据えるならInput Systemが向いています。

旧Inputは、Input.GetMouseButtonのようなメソッドを使って、Update関数の中で「今この瞬間、ボタンは押されているか?」を毎フレーム問い合わせる方式です。設定が一切いらず、スクリプトを書いてアタッチするだけですぐ動くので、学習段階ではとにかく気軽に試せるのが魅力です。

一方のInput Systemは、「攻撃」や「移動」といった操作をActionとして定義し、そこにキーボードやゲームパッドなどの入力を紐づけていく仕組みです。入力が始まった・終わったというタイミングごとにイベントが発生するので、この記事のテーマである3フェーズの設計とも相性がよく、複数デバイスへの対応やキーコンフィグの実装もしやすくなっています。

開発目的推奨方式理由
学習・小さなサンプル作成旧Input設定の手間がなく、処理の流れを目で追いやすいため
中規模以上のゲーム開発Input Systemゲームパッドやスマホ対応、保守性の面で有利なため
キーコンフィグの実装Input System実行時のキー変更(リバインド)機能が標準で用意されているため

ここではあくまで判断の目安を紹介しましたが、Input Systemの導入手順やエディタでの設定方法までは範囲が広くなるので、詳しくは別記事にまとめています。これから導入する方はあわせてチェックしてみてください。

キーコンフィグ機能まで作りたいなら

「将来的にプレイヤーが自由にキー配置を変えられるようにしたい」という場合は、迷わずInput Systemを選んでおくのがおすすめです。旧Inputでリバインド機能をゼロから作るのはかなり大変なので、最初からInput Systemの仕組みに乗っておいたほうが、あとで作り直す手間を防げます。

さらに、キーボード・ゲームパッド・タッチなど複数のデバイスを1つの仕組みでまとめて管理したい場合は、Input Systemの標準機能だけでなく、専用のアセットを使うという選択肢もあります。

アセットストアで人気の高いRewiredは、デバイスをまたいだ入力管理やキーコンフィグ機能がひとまとめになっているので、本格的に作り込みたい方は候補に入れてみてもよいでしょう。

実行時のリバインド機能を自分で実装してみたい場合は、こちらの記事で具体的な手順を解説しています。


長押しの実装方法【旧Input・Input System】

それでは実際に、「ボタンを何秒押し続けたか」を計測する長押し処理を書いていきましょう。長押しの基本は、押されている時間をコツコツ変数に足していくというシンプルな仕組みです。

旧Inputでの実装

旧Inputでは、押された瞬間・押されている間・離した瞬間をそれぞれ別のメソッドで検知します。この3つを組み合わせることで、長押し時間を計測できます。

  1. Input.GetMouseButtonDown(0)で、押された瞬間に計測時間を0にリセットし、計測中フラグをONにする
  2. Input.GetMouseButton(0)で、押されている間だけTime.deltaTimeを毎フレーム加算していく
  3. Input.GetMouseButtonUp(0)で、離された瞬間に計測を終え、合計時間を確認する
using UnityEngine;

public class HoldInputExample : MonoBehaviour
{
    private float holdTime = 0f;
    private bool isHolding = false;

    private void Update()
    {
        // 押された瞬間:計測時間をリセットしてフラグをONにする
        if (Input.GetMouseButtonDown(0))
        {
            holdTime = 0f;
            isHolding = true;
        }

        // 押し続けられている間:経過時間を加算し続ける
        if (Input.GetMouseButton(0) && isHolding)
        {
            holdTime += Time.deltaTime;
        }

        // 離された瞬間:計測を終了し、結果を確認する
        if (Input.GetMouseButtonUp(0))
        {
            isHolding = false;
            Debug.Log($"長押し終了。合計時間: {holdTime:F2}秒");
        }
    }
}

「押された瞬間」に計測時間を0にリセットする処理を忘れると、前回押したときの時間が残ったままになり、次に押した瞬間から想定外の長押し判定が成立してしまうことがあります。リセット処理は必ず押した瞬間の処理の中に入れておきましょう。

Input Systemでの実装

Input Systemの場合は、毎フレーム時間を足し算していく方法のほかに、押された時刻と離された時刻の差を計算するだけのシンプルな方法が使えます。startedcanceledという2つのコールバックイベントを使い分けます。

using UnityEngine;
using UnityEngine.InputSystem;

public class HoldInputSystemExample : MonoBehaviour
{
    [SerializeField] private InputAction fireAction;
    private float startTime;

    private void OnEnable()
    {
        fireAction.started += OnFireStarted;
        fireAction.canceled += OnFireCanceled;
        fireAction.Enable();
    }

    private void OnDisable()
    {
        fireAction.started -= OnFireStarted;
        fireAction.canceled -= OnFireCanceled;
        fireAction.Disable();
    }

    // ボタンが押された瞬間
    private void OnFireStarted(InputAction.CallbackContext context)
    {
        startTime = Time.time;
    }

    // ボタンが離された瞬間
    private void OnFireCanceled(InputAction.CallbackContext context)
    {
        float holdTime = Time.time - startTime;
        Debug.Log($"長押し終了。合計時間: {holdTime:F2}秒");
    }
}

この方法だと、Update関数の中で毎フレーム計算をする必要がないので、処理の負荷をぐっと抑えられるのもうれしいポイントです。

なお、スマートフォンのタッチ操作を前提にしたUI上の長押しボタンを作りたい場合は、また少し違ったイベントの扱い方が必要になります。そちらは以下の記事で詳しく解説しているので、あわせて参考にしてみてください。


チャージ攻撃の実装方法【旧Input・Input System】

長押しの時間計測ができれば、チャージ攻撃はその応用で作れます。チャージ攻撃とは、ボタンを離した瞬間に、それまで溜めた時間に応じて攻撃の威力や種類を変える仕組みのことです。

チャージ時間で攻撃を分岐させる考え方

チャージ攻撃を作るときは、溜めた時間をどう攻撃に反映させるかを先に決めておくと、コードがすっきりまとまります。代表的なのは、時間を段階で区切って攻撃を切り替える方法です。

チャージ時間発動する攻撃
0.3秒未満通常攻撃
0.3秒以上〜1.5秒未満中チャージ攻撃
1.5秒以上最大チャージ攻撃

段階を分けずに、溜めた時間に応じて威力をなめらかに変化させたい場合は、Mathf.Clamp01で時間を0〜1の割合に変換したうえで、Mathf.Lerpを使って最小威力と最大威力の間を補間する方法が使えます。段階的な演出をしたいか、じわっと変化する手触りにしたいかで、どちらを選ぶか決めるとよいでしょう。

旧Inputでの実装

基本的な流れは長押しと同じですが、離した瞬間に時間をチェックして攻撃を分岐させる処理が加わります。

  1. 押された瞬間にチャージ時間を0にリセットする
  2. 押されている間、時間を加算しつつ、上限値でクランプ(制限)する
  3. 離された瞬間に、溜めた時間を確認して攻撃を分岐させる
  4. 攻撃実行後、チャージ時間を0に戻す
using UnityEngine;

public class ChargeAttackOldInput : MonoBehaviour
{
    [SerializeField] private float weakChargeTime = 0.3f;
    [SerializeField] private float strongChargeTime = 1.5f;
    [SerializeField] private float maxChargeTime = 2.0f;

    private float chargeTime = 0f;
    private bool isCharging = false;

    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            chargeTime = 0f;
            isCharging = true;
        }

        if (Input.GetMouseButton(0) && isCharging)
        {
            chargeTime += Time.deltaTime;
            chargeTime = Mathf.Min(chargeTime, maxChargeTime);
        }

        if (Input.GetMouseButtonUp(0) && isCharging)
        {
            isCharging = false;

            if (chargeTime < weakChargeTime)
            {
                PerformNormalAttack();
            }
            else if (chargeTime < strongChargeTime)
            {
                PerformMediumChargeAttack();
            }
            else
            {
                PerformStrongChargeAttack();
            }

            chargeTime = 0f;
        }
    }

    private void PerformNormalAttack() => Debug.Log("通常攻撃発動!");
    private void PerformMediumChargeAttack() => Debug.Log("中チャージ攻撃発動!");
    private void PerformStrongChargeAttack() => Debug.Log("最大チャージ攻撃発動!");
}

Input Systemでの実装(Hold InteractionかPressイベントか)

Input Systemでチャージ攻撃を作る場合、2つのアプローチがあります。1つは公式機能のHold Interaction、もう1つはstarted/canceledを自前で使うPressイベント方式です。この2つは得意な場面がはっきり分かれています。

Hold Interactionは、「一定時間ボタンを押し続けたら成立する」という単純な操作、たとえば扉を開けるとかアイテムを拾うといった動作に向いています。設定したHold Timeが経過するとperformedイベントが発火する仕組みです。

using UnityEngine;
using UnityEngine.InputSystem;

public class GetHoldExample : MonoBehaviour
{
    [SerializeField] private InputActionReference _hold;

    private void Awake()
    {
        if (_hold == null) return;

        _hold.action.performed += OnHold;
        _hold.action.Enable();
    }

    private void OnHold(InputAction.CallbackContext context)
    {
        Debug.Log("Hold Interactionが成立(長押し判定達成)!");
    }
}

一方で、「同じボタンで短押しは通常攻撃、長押しはチャージ攻撃」というように、離すタイミングで結果を変えたい場合はPressイベント方式のほうが扱いやすくなります。ボタンを押した瞬間に構えモーションを始めたり、溜めている最中の演出を細かく制御したりしたい場合も、こちらの方式が向いています。

using UnityEngine;
using UnityEngine.InputSystem;

public class ChargeAttackInputSystem : MonoBehaviour
{
    private float buttonPressStartTime;

    public void OnAttack(InputAction.CallbackContext context)
    {
        if (context.started)
        {
            buttonPressStartTime = Time.time;
        }
        else if (context.canceled)
        {
            float pressDuration = Time.time - buttonPressStartTime;
            Debug.Log($"総押下時間: {pressDuration:F2}秒");
        }
    }
}

ワンポイント

Hold Interactionの「Press Point」や進捗の取得方法などの細かい設定値は、Unityのバージョンによって変わることがあります。実際に使う際は、エディタ上の表示や公式ドキュメントで最新の仕様を確認しておくと安心です。

よくある誤解・実装時の注意点

ここまで長押しとチャージ攻撃の作り方を見てきましたが、実際に組み込むときにつまずきやすいポイントをいくつか補足しておきます。ここで紹介する内容は、これまでの手順の中では説明しきれなかった、実装後に起こりやすいトラブルです。

ボタンの外で指を離したときのキャンセル漏れ

スマートフォンなどのタッチ操作では、ボタンを押したまま指をスライドさせて画面の外側で離すと、通常なら発生するはずの「離した瞬間」のイベントが検知されないことがあります。

このケースを放置すると、指を離しているのに内部ではチャージがずっと続いていたり、連射が止まらなかったりする不具合につながります。

対策として、入力状態をまとめてリセットする関数をあらかじめ用意しておき、オブジェクトが無効化されたタイミングや、想定外のキャンセル条件が発生したタイミングで必ず呼び出すようにしておきましょう。

private void ResetInputState()
{
    isCharging = false;
    chargeTime = 0f;
}

1つのスクリプトに全部の役割を詰め込まない

入力の検知、チャージ時間の計算、UIの表示更新、実際の攻撃処理を、すべて1つのスクリプトにまとめて書いてしまうと、あとから修正したいときにどこを直せばいいのか分かりにくくなります。

役割ごとに処理を分けておくと、見通しがよくなり、修正やデバッグもぐっと楽になります。

  • 入力検知:ボタンが押された・離されたことを検知するだけの役割
  • 状態管理:チャージ時間やフラグなどの数値を計算・保持する役割
  • UI表示:ゲージや画面表示を変更するだけの役割
  • 実行:実際に攻撃を発生させたり、エフェクトを再生したりする役割

最初から完璧に分ける必要はありませんが、処理が増えてきたタイミングで一度見直してみると、コードの扱いやすさが変わってくるはずです。


よくある質問

Q
短押しと長押しを同じボタンで両立させたい場合、閾値は何秒にすればいいですか?
A
明確な正解はありませんが、0.2〜0.3秒程度を境目にすると、プレイヤーが「軽く押した」つもりの操作を長押し扱いにしてしまう誤爆を防ぎやすくなります。アクション性の高いゲームほど短めに、じっくり操作するタイプのゲームほど長めに設定する傾向があるので、実際にプレイして手触りを確認しながら微調整するのがおすすめです。
Q
スマホとPCで同じ長押し・チャージのコードを使い回すことはできますか?
A
旧Inputの場合、マウスのクリックとタッチ操作は別々のAPIとして扱われる場面が多く、そのままではコードを完全に使い回せないことがあります。一方でInput Systemは、デバイスごとの違いをActionという共通の窓口でまとめて扱える設計になっているため、マウスでもタッチでも同じ処理を流用しやすくなっています。複数デバイスへの対応をあらかじめ視野に入れているなら、Input Systemを選んでおくと後々の手間を減らせます。
Q
チャージの最大時間はどのくらいに設定するのが目安ですか?
A
テンポよく遊べるアクションゲームなら1〜2秒程度、じっくり戦略を練るタイプのゲームなら3秒以上でも違和感なく成立します。最大時間を長くしすぎると、プレイヤーが「待たされている」と感じやすくなるので、まずは短めに設定し、実際にプレイした感触を見ながら少しずつ伸ばしていく方が失敗しにくいです。

※当サイトはアフィリエイト広告を利用しています。リンクを経由して商品を購入された場合、当サイトに報酬が発生することがあります。

※本記事に記載しているAmazon商品情報(価格、在庫状況、割引、配送条件など)は、執筆時点のAmazon.co.jp上の情報に基づいています。
最新の価格・在庫・配送条件などの詳細は、Amazonの商品ページをご確認ください。

スポンサーリンク