UhiyamaLAB個人開発者の創作備忘録

【Unity】InputSystemを使って「溜め攻撃」を超シンプルに実装する方法

2024/04/052024/12/08ゲーム開発Unity

本記事では、ゼルダの伝説の回転斬りのような「溜め攻撃」をUnityで実装する際に、Input Systemを活用して「押した瞬間(started)」「離した瞬間(canceled)」をイベント的に扱う手法を紹介します。
従来Update()で常時入力を監視する方法と比べて、イベントドリブンな設計によってコードがシンプルになり、保守性が向上することが期待できます。


  1. Input Systemによる実装は有効なのか?
  2. InputSystemのインストールとActionsファイルの作成
  3. 左クリック攻撃アクションを追加する
  4. InputSystemと入力スクリプトの連携
  5. 溜め攻撃の計算方法
  6. 溜め攻撃シグナルの追加とInput Systemの弱点補強
  7. まとめと更なる考察

1. Input SystemとUpdateによる実装の比較

Updateによる従来方法:
Update()で毎フレーム入力を監視し、押下中の経過時間を積算します。単純な仕組みですが、条件分岐や入力が増えるとコードが複雑化し、保守性が低下する恐れがあります。

Input Systemによる方法:
context.startedcontext.canceledで押下開始・終了のイベントを取得し、必要な時点でのみ処理を行えます。長押し時間の計算は押下開始時刻と離した時刻の差分で簡潔に実装でき、コードがスッキリします。

結論:
短期的・小規模な実装ならUpdateで十分な場合もありますが、長期的な拡張や多様な入力デバイス対応、コード整理を重視するなら、Input Systemは有効な選択肢となります。


2. InputSystemのインストールとActionsファイルの作成

InputSystemのインストール

Input Systemのインストール手順

  1. Window > Package Managerを開く
  2. 「Package: Unity Registry」を選択
  3. Input Systemを検索してインストール

InputActionsファイルの作成

InputActionsの作成

InputActionsのC#クラス生成

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


3. 左クリック攻撃アクションを追加する

攻撃ボタンの登録

  1. ActionMapsの「+」から新規マップ(例:Gameplay)を作成
  2. Actionsの「+」から新規Action(例:ActionLeft)を追加
  3. ActionTypeを「Button」に、BindingのPathを「LeftButton [Mouse]」へ設定

これで左クリックがActionLeftとしてイベント的に扱えます。


4. InputSystemと入力スクリプトの連携

PlayerInputコンポーネントをアタッチしたGameObject(例:InputManager)を用意し、生成したアクションを紐づけます。
下記スクリプトを追加し、PlayerInputのInspectorでEventsを展開してActionLeftAttackLeftPressedを割り当てれば、「左クリックで攻撃」というイベント駆動型の入力処理が実現します。


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+補完的な手法(コルーチン)という組み合わせは有力なオプションとなるでしょう。

この記事をシェアする