Input

Unity の Input System の上に構築された、コンテキストスタックとインタラクティブなリバインドを備えたアクションベースの入力。

Unity の Input System 上のアクションマップ・コンテキストスタック・インタラクティブなリバインド · Event Bus へのオプトインなイベント発行 · no-op 差し替え: NullInputService

CGS はキーボード・マウス・ゲームパッドの入力を IInputService を通じて読み取ります — Unity の Input System パッケージの上の薄いレイヤーです。生のキーコードチェックをコード中に散らばせる代わりに、アクションマップを使って作業します: "Gameplay" や "Menu" のような、.inputactions アセットに一度だけ定義する名前付きのコントロールセットです。その上に、このサービスは Unity が標準では提供しない 3 つのものを加えます: どのアクションマップがアクティブかを切り替えるコンテキストスタック、自動保存付きのインタラクティブなリバインド(「キーを押して割り当て直す」)、そして Event Bus への入力イベントのオプトインな発行です。

何よりも先に: プロジェクトは新しい入力バックエンドになっていなければなりません。 Input System パッケージのインストール(CGS の依存マニフェストが自動で行います)では、Active Input Handling というプロジェクト設定は切り替わりません。新規プロジェクトでは Input Manager (Old) のままです — その状態では、デモシーン、実行可能サンプル、そしてあなた自身のアクションマップが、すべてのキー入力を黙って無視します。Welcome ウィンドウ(Tools > Common Game System > Welcome)はこれを検出し、ワンクリックの Enable the new Input System ボタンを表示します。エディタが一度再起動し、入力が動くようになります。手動の場合: Edit > Project Settings > Player > Other Settings > Active Input Handling → "Input System Package (New)" または "Both"。詳細は FAQ を参照してください。

概要

  • アクションベースの読み取り。 アクションを一度検索し(GetAction("Gameplay", "Jump"))、あとは毎フレームポーリングするか、そのイベントを購読します。ゲームプレイコードが特定のキーやボタンに言及することはないので、キーボードとゲームパッドが同じ経路で動きます。
  • コンテキストスタック。 コンテキストを push すると(PushContext("Menu"))、そのアクションマップだけが有効になり、他はすべて無効になります。pop すると前のコンテキストが引き継ぎます。常に最新のコンテキストが勝ちます — ゲームプレイの上に重なるネストしたメニューに、まさに望みどおりの挙動です。
  • インタラクティブなリバインド。 リバインドを開始し、プレイヤーに新しいキーを押させ、完了コールバックを受け取ります。オーバーライドは 1 回の呼び出しで PlayerPrefs に永続化されます。
  • イベント発行(オプトイン)。 アクションの押下を Event Bus に発行するようサービスに頼めば、疎結合なシステムが InputAction 参照を保持せずに入力へ反応できます。

このサービスはオプションの CommonGameSystem.Input アセンブリに入っています。プロジェクトから Input System パッケージを取り除くと、そのアセンブリは自動的に自分をコンパイル対象から外し、CGS の残りはそのままコンパイルされ、起動します。

サービスの取得

Awake で一度解決し、参照をキャッシュします:

using CommonGameSystem.Core;
using UnityEngine;

[DefaultExecutionOrder(100)]
public class MyGameMode : MonoBehaviour
{
    private IInputService _input;

    private void Awake()
    {
        _input = ServiceLocator.Resolve<IInputService>();
    }
}

[DefaultExecutionOrder(100)] 属性は、あなたの Awake が実行される前に、CGS のブートストラッパーがサービス登録を終えていることを保証します。Update() の中で Resolve を決して呼ばないでください — 辞書検索です。一度キャッシュしてください。

API リファレンス

アクションの検索

InputAction GetAction(string actionMapName, string actionName)

たとえば "Gameplay" / "Jump" に対する InputAction を返します。そのマップやアクションが .inputactions アセットに定義されていなければ null を返します。結果はキャッシュしてください — ホットパスでアクションを検索してはいけません。

ポーリング(Update または FixedUpdate で呼ぶ)

T ReadValue<T>(InputAction action)           // Current-frame value (Vector2, float, ...)
bool IsPressed(InputAction action)           // true while held
bool WasPressedThisFrame(InputAction action) // true only on the press frame
bool WasReleasedThisFrame(InputAction action)// true only on the release frame

Event Bus への発行(オプトイン)

IDisposable PublishOnStarted(InputAction action)   // publishes InputActionStartedEvent
IDisposable PublishOnPerformed(InputAction action) // publishes InputActionPerformedEvent
IDisposable PublishOnCanceled(InputAction action)  // publishes InputActionCanceledEvent

各呼び出しはトークンを返します。発行を止めるには破棄します。購読については Event Bus のページを参照してください。

コンテキストスタック

IDisposable PushContext(string actionMapName) // Enable only this map, disable the rest
string CurrentContext { get; }                // Top of the stack (null when empty)
IReadOnlyList<string> ContextStack { get; }   // Bottom-to-top snapshot (allocates; debug use)

PushContext はトークンを返します。トークンを破棄するとそのコンテキストが pop され、その下にあったものが再アクティブ化されます。同じマップ名を 2 回 push すると、2 つの別々のスタックエントリが作られます。

インタラクティブなリバインド

IInputRebindOperation StartInteractiveRebind(
    InputAction action,
    int bindingIndex = -1,            // -1 = the action's first binding
    string controlsExcluding = "Mouse") // comma-separated controls to ignore

IsCompletedIsCanceledResultBindingPath、そして Completed イベントを持つ操作トークンを返します。一度に実行できるリバインドは 1 つだけです — 2 つ目を開始すると InvalidOperationException がスローされます。プレイヤーがタイムアウト(デフォルトは 5 秒。InputServiceOptions.RebindTimeoutSeconds で設定可能)内に何も押さなければ、リバインドは自らキャンセルされます。

バインディングの保存と復元

void SaveBindingOverrides()                           // Persist all overrides to PlayerPrefs
void LoadBindingOverrides()                           // Re-apply saved overrides
void ResetBindingOverrides(InputAction action = null) // Clear overrides (null = all actions)

デバイスの照会

bool IsDeviceConnected<TDevice>() where TDevice : InputDevice // Any such device present?
TDevice GetDevice<TDevice>() where TDevice : InputDevice      // First matching device, or null

たとえば IsDeviceConnected<Gamepad>() は、ゲームパッド用のボタンプロンプトを表示すべきかどうかを教えてくれます。

完全な例

移動をポーリングし、ジャンプの押下を Event Bus に発行し、ゲームのポーズ時にコンテキストを切り替えるプレイヤーコントローラー:

using System;
using CommonGameSystem.Core;
using UnityEngine;
using UnityEngine.InputSystem;

[DefaultExecutionOrder(100)]
public class PlayerController : MonoBehaviour
{
    [SerializeField] private float _moveSpeed = 5f;

    private IInputService _input;
    private InputAction _moveAction;
    private InputAction _jumpAction;
    private IDisposable _jumpEvents;
    private IDisposable _gameplayToken;
    private IDisposable _menuToken;

    private void Awake()
    {
        _input = ServiceLocator.Resolve<IInputService>();
        _moveAction = _input.GetAction("Gameplay", "Move");
        _jumpAction = _input.GetAction("Gameplay", "Jump");
    }

    private void Start()
    {
        // Publish Jump presses to the event bus.
        _jumpEvents = _input.PublishOnPerformed(_jumpAction);

        // Enable the Gameplay action map. Keep the token so we can
        // pop the context when the game pauses.
        _gameplayToken = _input.PushContext("Gameplay");
    }

    private void Update()
    {
        var move = _input.ReadValue<Vector2>(_moveAction);
        transform.Translate(move * _moveSpeed * Time.deltaTime);
    }

    public void OnPause()
    {
        _gameplayToken?.Dispose();               // Pop Gameplay
        _menuToken = _input.PushContext("Menu"); // Push Menu
    }

    public void OnResumeGame()
    {
        _menuToken?.Dispose();                   // Pop Menu
        _gameplayToken = _input.PushContext("Gameplay");
    }

    private void OnDestroy()
    {
        _jumpEvents?.Dispose();
        _menuToken?.Dispose();
        _gameplayToken?.Dispose();
    }
}

"Gameplay""Menu" のマップ、そして "Move""Jump" のアクションは、あなた自身の .inputactions アセットから来ます — サービスはあなたのアセットを読むのであって、マップを作ってくれることは決してありません。

オフにする

ServiceLocator.Replace<IInputService>(new NullInputService());

null 実装が入っている状態では:

呼び出し結果
ReadValue<T>()default(T)Vector2.zero0f、…)
IsPressed() / WasPressedThisFrame() / WasReleasedThisFrame()false
GetAction()null
PushContext() / PublishOn*()空のトークン(何もしない)
StartInteractiveRebind()すでにキャンセル済みのスタブ操作
Save/Load/ResetBindingOverrides()no-op
IsDeviceConnected() / GetDevice()false / null

カットシーン専用モード、ヘッドレスサーバー、テストハーネスに使ってください。それ以外のすべて(UI、シーンフロー)は、入力なしでもコンパイルされ、動作します。

よくある落とし穴

  • 新規プロジェクトで入力が死んでいる = バックエンドが違う。 キーボードやマウスに何も反応しないなら、まず Active Input Handling を確認してください — このページ冒頭のコールアウトと FAQ を参照。

  • コンテキストトークンは新しいものから pop されます。 PushContext() が返したトークンを(通常は OnDestroy で)破棄すると、そのコンテキストが pop されます。オブジェクトが順不同で破棄されてもサービスはうまく対処しますが、トップにないコンテキストの pop はログに残りません。

  • アクションマップはあなたのアセットから来ます。 サービスはマップやアクションを作りません。"Gameplay"、"Menu"、"Dialogue" などは、あなたの .inputactions アセットに定義してください。

  • インタラクティブなリバインドは一度に 1 つ。 リバインドの実行中に StartInteractiveRebind() を呼ぶと InvalidOperationException がスローされます。1 つがアクティブな間は、設定 UI の他のリバインドボタンを無効化してください。

  • インタラクションの処理はあなたのアセットが支配します。 デッドゾーン、ホールドインタラクション、マルチタップ — すべて Inspector で .inputactions アセット上に設定します。サービスはガイダンス的なデフォルト(InputServiceOptions.StickDeadzoneMin = 0.125f など)を公開しますが、あなたのアセットの設定を上書きすることは決してありません。

関連ページ

  • Event Bus — このサービスが発行できる入力イベント: InputActionStartedEventInputActionPerformedEventInputActionCanceledEventInputContextPushedEventInputContextPoppedEventInputDeviceConnectedEventInputDeviceDisconnectedEventInputRebindStartedEventInputRebindCompletedEvent
  • UI Framework — パネルの開閉に合わせて入力コンテキストを自動で push/pop
  • Logger — コンテキストの push/pop、デバイスの接続、リバインドのタイムアウトは Input カテゴリでログに記録
  • FAQ — Active Input Handling の修正手順、ステップバイステップ