本記事では、ゼルダの伝説の回転斬りのような「溜め攻撃」を、UnityのInput Systemを活用して実装する方法を紹介します。
Hold Interactionではなく、Pressの「押した瞬間(started
)」「離した瞬間(canceled
)」を使ったアプローチです。
Update関数で実装する方法と比べて、イベントドリブンな設計によってコードがシンプルになり、保守性が向上することが期待できます。
1. Input SystemとUpdateによる実装の比較
Updateによる従来方法:
Update関数
で毎フレーム入力を監視し、押下中の経過時間を積算します。仕組みは単純ですが、条件分岐や複数入力が増えるとコードが複雑化しやすい面があります。
Input Systemによる方法:
キーを離した時間(context.canceled
)からキーを入力した時間(context.started
)を引くことで「キーを押していた秒数」を算出することができます。多様な入力デバイス対応や拡張がしやすいです。
Q. なぜHold InteractionではなくPressのcontext.startedとcanceledの差分で計測するのか?
Hold InteractionはアクションのInteractionsをHold
に設定し、Hold Timeを指定するだけで長押し判定が可能です。
しかし、通常攻撃(短押し)と溜め攻撃(長押し)を「同じボタン」で切り替えたい場合には、以下のような課題があります。
- 短押しと長押しを両立しづらい:Hold Interactionで長押しが成立すると
performed
が呼ばれますが、短押し処理(通常攻撃)を押下直後に実行したい場合は、別途の工夫や別アクションが必要になり、実装が煩雑になります。 - 押し始めのタイミングを活かしにくい:押下直後(
started
)で即座に軽い攻撃をする、または溜めに移行するなどの状態管理は、Hold Interaction単体では管理しづらいです。 - 中間的な演出を行う際にもコントロールしづらい:溜め中のキャラクター演出やゲージ管理をリアルタイムに行う場合、最終的に
performed
が呼ばれるまで待たなければいけないため、チャージ途中のロジックが組みにくくなります。
一方で、context.started
とcontext.canceled
を用いて押下開始時刻と離した時刻を差分で計測する方法なら、短押しと長押しを同じアクション内で自然に切り替えられます。また、押している最中に任意の演出や状態遷移を加えやすく、複雑なチャージ攻撃や多段階の強化に発展させる際にも柔軟です。
そのため、「短押しは通常攻撃」「一定時間以上で溜め攻撃」のような二段構成や、溜め中の演出を盛り込みたいシステムには、context.started
/ context.canceled
による独自判定が向いていると言えます。
2. InputSystemのインストールとActionsファイルの作成
InputSystemのインストール

- Window > Package Managerを開く
- 「Package: Unity Registry」を選択
- Input Systemを検索してインストール
InputActionsファイルの作成


Create > Input Actionsでアクションファイルを作成し、「Generated C# Class」にチェックを入れてApplyします。C#スクリプトが生成され、コードから容易に参照可能になります。
3. 左クリック攻撃アクションを追加する

- ActionMapsの「+」から新規マップ(例:Gameplay)を作成
- Actionsの「+」から新規Action(例:ActionLeft)を追加
- ActionTypeを「Button」に、BindingのPathを「LeftButton [Mouse]」へ設定
これで左クリックがActionLeft
としてイベント的に扱えます。
4. InputSystemと入力スクリプトの連携
PlayerInputコンポーネントをアタッチしたGameObject(例:InputManager)を用意し、生成したアクションを紐づけます。
下記スクリプトを追加し、PlayerInputのInspectorでEventsを展開してActionLeft
にAttackLeftPressed
を割り当てれば、「左クリ ックで攻撃」というイベント駆動型の入力処理が実現します。
using UnityEngine;
using UnityEngine.InputSystem;
[RequireComponent(typeof(PlayerInput))]
public class InputManager : MonoBehaviour
{
private float buttonPressStartTime;
private const float specialAttackThreshold = 1.0f; // 1秒以上で溜め攻撃
public void AttackLeftPressed(InputAction.CallbackContext context)
{
if (context.started)
{
buttonPressStartTime = Time.time;
}
else if (context.canceled)
{
float pressDuration = Time.time - buttonPressStartTime;
if (pressDuration > specialAttackThreshold)
{
// 溜め攻撃処理
}
else
{
// 通常攻撃処理
}
}
}
}
5. 溜め攻撃の計算方法
押下開始(started
)時刻と離した(canceled
)時刻の差分で長押し時間を算出し、specialAttackThreshold
を超えていれば溜め攻撃、それ以下なら通常攻撃を行います。
この仕組みにより、Update()
関数で逐次状態をチェックする必要がなく、コードが軽量化されます。
6. 溜め攻撃シグナルの追加とInput Systemの弱点補強
Input Systemは「押下開始」「押下終了」には強いものの、「押され続けている最中の特定の時点」を直接検知する機能は標準で提供していません。
つまり、溜め攻撃可能状態(しきい値到達時)にキャラを光らせたりSEを鳴らしたりするには、別途タイマー的な仕組みが必要になります。
以下は、コルーチンを用いて、押下開始後に一定時間経過したタイミングで「溜め攻撃準備完了」のシグナルを発する例です。
このアプローチで、Input Systemが不得意とする「長押し中間点」の演出を補完できます。
using UnityEngine;
using UnityEngine.InputSystem;
using System.Collections;
[RequireComponent(typeof(PlayerInput))]
public class InputManager : MonoBehaviour
{
private float buttonPressStartTime;
private const float specialAttackThreshold = 1.0f;
private Coroutine specialAttackReadyCoroutine;
private bool specialAttackReadyTriggered = false; // 溜め攻撃準備通知フラグ
public void AttackLeftPressed(InputAction.CallbackContext context)
{
if (context.started)
{
buttonPressStartTime = Time.time;
specialAttackReadyTriggered = false;
if (specialAttackReadyCoroutine != null)
{
StopCoroutine(specialAttackReadyCoroutine);
specialAttackReadyCoroutine = null;
}
specialAttackReadyCoroutine = StartCoroutine(HandleSpecialAttackReady());
}
else if (context.canceled)
{
if (specialAttackReadyCoroutine != null)
{
StopCoroutine(specialAttackReadyCoroutine);
specialAttackReadyCoroutine = null;
}
float pressDuration = Time.time - buttonPressStartTime;
if (pressDuration > specialAttackThreshold)
{
// 溜め攻撃処理(ここで準備完了シグナルが既に発生している想定)
}
else
{
// 通常攻撃処理
}
specialAttackReadyTriggered = false;
}
}
private IEnumerator HandleSpecialAttackReady()
{
yield return new WaitForSeconds(specialAttackThreshold);
if (!specialAttackReadyTriggered)
{
Debug.Log("Special Attack Ready!");
// 溜め攻撃準備完了を知らせる演出をここで行う
specialAttackReadyTriggered = true;
}
}
}
コルーチンを使うことで、中間的なタイミングを自己管理できます。これは、一見遠回りに感じるかもしれませんが、様々な入力アクションが増えた際も、Input Systemによるイベント分離とActions管理のおかげでコード全体は整理しやすくなります。
7. まとめと更なる考察
Input Systemを用いることで、押下開始・終了といった明確なイベントを基盤に、溜め攻撃を実装できました。
ただし、溜め中の中間イベント(しきい値到達時)を扱うには、今回のようにコルーチンなど別手段を用いて補完する必要があります。
「コルーチンを使うなら最初からUpdateでやる方が単純なのでは?」と感じる場合もあるかもしれません。
しかし、長期的な拡張や複数の入力デバイス対応を考慮すると、Input Systemが提供する明確なイベント構造や、Actionsファイルによる直感的な入力マッピング管理は大きな強みとなります。
Updateで押下時間を監視し続ける単純な実装は確かに分かりやすい反面、将来コードが膨らんだときに複雑化しやすい弱点があります。
最終的な選択肢はプロジェクト規模や要求次第です。小規模で完結するならUpdateで十分かもしれませんが、保守性・拡張性・多端末対応を見据えるなら、Input System+補完的な手法(コルーチン)という組み合わせは有力なオプションとなるでしょう。