【Unity】Input Systemの2軸入力を8方向にスナップさせる

こじゃらこじゃら

次のように、ゲームパッドのスティック入力を8方向にスナップさせる方法を知りたいの…

このはこのは

Composite Bindingを使えば簡単にできるわ。Processorを自作すればより高度なスナップ処理も可能だわ。

Input Systemのスティックなどの2軸入力を8方向にスナップさせる方法の解説記事です。

次の方法で実現可能です。

8方向入力を実現する方法
  • 2D Vector Compositeを用いる
  • スナップ処理を行うProcessorを自作する

前者ノーコーディングでできる反面、入力値は必ず0と1に2値化した値となります。

後者コーディングが必要ですが、前者の挙動に加え、例えば方向はスナップさせるが入力値の大きさは連続的にするといった要件にも対応できます。

本記事では、このような2軸入力を8方向にスナップさせる方法を解説していきます。

動作環境
  • Unity 2023.1.0f1
  • Input System 1.6.1

スポンサーリンク

前提条件

予めInput Systemパッケージがインストールされ、有効化されているものとします。

この手順およびInput Systemの基本的な使い方については、以下記事をご覧ください。

また、Input Action経由で入力を受け取るものとします。

Input Actionの使い方は以下記事をご覧ください。

Vector 2 Compositeで8方向にスナップする

Input Systemでは、複数の入力を合成するComposite Bindingという機能があります。

参考:Input Bindings | Input System | 1.6.1

このComposite Bindingのプリセットの一つである2D Vector Compositeを使うと、結果として8方向へのスナップ入力が実現できます。

参考:Input Bindings | Input System | 1.6.1

2D Vector Compositeは4方向の入力を2軸入力(Vector2型)に合成するComposite Bindingですが、ModeDigital Normalizedを設定すると、8方向にスナップされた結果が得られます。

処理としては、各方向の入力値を0と1に2値化して2軸入力に合成し、合成した入力値の大きさを0または1にスナップしています。

スティック入力Vector2型ですが、上下左右の4方向の入力を子のControlとして持っているため、これらを2D Vector Compositeの各方向に指定すれば良いです。

スティックの4方向のControl

4方向ボタン入力の場合

該当するActionのAction TypeValueControl TypeVector 2に設定します。

そして、Actionの+アイコンからAdd Up\Down\Left\Right Compositeを選択し、ModeDigital Normalizedに設定します。

注意

Modeは必ずDigital Normalizedとしてください。これ以外のモードにすると、正しくスナップされなかったり、斜め方向入力が大きくなったりしてしまいます。

参考:Enum Vector2Composite.Mode | Input System | 1.6.1

最後に、各方向のBindingにボタンを割り当てます。例ではWASDキーとしました。

注意

当環境のUnity 2023.1.0f1では、次のようにPathの設定画面でListenボタンなどが隠れてしまう現象を確認しました。

Listenボタン自体は押せる状態になっています。今後のアップデートで修正される可能性があります。

使用例

前述のActionで設定した2軸入力をスクリプトから受け取れば、8方向入力が得られます。

ここでは、次のように取得した入力値に基づいてオブジェクトを移動させるものとします。

MoveExample.cs
using UnityEngine;
using UnityEngine.InputSystem;

public class MoveExample : MonoBehaviour
{
    // 2軸入力を受け取るAction
    [SerializeField] private InputActionProperty _moveAction;
    
    // 移動の速さ
    [SerializeField] private float _speed = 1;

    private void OnDestroy()
    {
        _moveAction.action.Dispose();
    }

    private void OnEnable()
    {
        _moveAction.action.Enable();
    }

    private void OnDisable()
    {
        _moveAction.action.Disable();
    }

    private void Update()
    {
        // 2軸入力読み込み
        var inputValue = _moveAction.action.ReadValue<Vector2>();
        
        // xy軸方向で移動
        transform.Translate(inputValue * (_speed * Time.deltaTime));
    }
}

上記をMoveExample.csという名前でUnityプロジェクトに保存し、移動させたいゲームオブジェクトにアタッチし、インスペクターからActionと移動の速さを設定します。

例では、正方形画像に対して前述のActionを割り当てることとします。

実行結果

8方向にスナップされて移動するようになりました。斜め移動でも上下左右と速さが同じになるように正規化されています。

スティック入力の場合

スティック入力の場合は、スティックの子のControlであるupdownleftrightを2D Vector Compositeの各方向に指定すれば良いです。

例では、左スティックの各方向を割り当てることとします。

実行結果

アナログ入力のスティックでも8方向入力にスナップされていることを確認できました。

分かりやすさのため、スティックの状態を左下のUIに表示しています。

Processorを自作してスナップさせる

ここまで2D Vector Compositeにより2軸入力を8方向にスナップさせる方法を解説しました。

しかしながら、入力値のベクトルの大きさは必ず0または1にスナップされます。例えば、8方向にスナップさせるが、スティックの倒し具合(入力値の大きさ)も反映させたい場合には対応できません。

この場合、自作のProcessorによってスナップ処理を実装することで対処できます。

サンプルスクリプト

以下、2軸の入力値を8方向にスナップさせるProcessorの実装例です。オプションで入力値の大きさを0または1にスナップするか否かを設定できるようにしています。

SnapDirectionProcessor.cs
using UnityEngine;
using UnityEngine.InputSystem;

#if UNITY_EDITOR
using UnityEditor;

[InitializeOnLoad]
#endif
public class SnapDirectionProcessor : InputProcessor<Vector2>
{
    // 入力値の大きさを正規化するかどうか
    public bool digitalNormalized;

    private const string ProcessorName = "SnapDirection";

#if UNITY_EDITOR
    static SnapDirectionProcessor() => Initialize();
#endif

    // Processorの登録処理
    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    static void Initialize()
    {
        // 重複登録すると、Input ActionのProcessor一覧に正しく表示されない事があるため、
        // 重複チェックを行う
        if (InputSystem.TryGetProcessor(ProcessorName) == null)
            InputSystem.RegisterProcessor<SnapDirectionProcessor>(ProcessorName);
    }

    // 独自のProcessorの処理定義
    public override Vector2 Process(Vector2 value, InputControl control)
    {
        // 入力値の角度を取得
        var angle = Mathf.Atan2(value.y, value.x) * Mathf.Rad2Deg;

        // 角度を8方向にスナップさせる
        angle = Mathf.Round(angle / 45) * 45;

        // ベクトルの大きさを決定する
        var magnitude = value.magnitude;
        
        if (digitalNormalized)
            magnitude = magnitude > InputSystem.settings.defaultButtonPressPoint ? 1 : 0;

        // 角度と大きさからベクトルを作成
        return new Vector2(
            Mathf.Cos(angle * Mathf.Deg2Rad),
            Mathf.Sin(angle * Mathf.Deg2Rad)
        ) * magnitude;
    }
}

上記をSnapDirectionProcessor.csという名前でUnityプロジェクトに保存すると、Snap Directionという名前のProcessorが使用可能になります。

方向をスナップさせるが大きさはそのままにする場合

ActionにBindingを追加し、該当するスティック入力などのControl Pathを指定し、Processors右の+アイコンからSnap Directionを選択して自作Processorを追加します。

入力値の大きさはそのまま維持したい場合は、Digital Normalizedのチェックを外します。

実行結果

スティック入力の大きさによって速さを変化させながら、8方向に向きをスナップさせられていることを確認できました。

デジタル入力にスナップさせる設定

ProcessorのDigital Normalizedにチェックを入れれば良いです。

実行結果

Vector 2 Compositeのように入力値の大きさが0と1にスナップされるようになりました。

スクリプトの説明

受け取った入力値の向きを8方向にスナップする処理は以下部分です。

// 入力値の角度を取得
var angle = Mathf.Atan2(value.y, value.x) * Mathf.Rad2Deg;

// 角度を8方向にスナップさせる
angle = Mathf.Round(angle / 45) * 45;

8方向の向きの間隔は45度となるため、Mathf.Roundメソッド除算を駆使して計算しています。

そして、入力値の大きさをVector2.magnituteプロパティから求め、必要なら0と1にスナップする処理を行っています。

// ベクトルの大きさを決定する
var magnitude = value.magnitude;

if (digitalNormalized)
    magnitude = magnitude > InputSystem.settings.defaultButtonPressPoint ? 1 : 0;

閾値には、Input System Packageのボタン閾値の設定値を参照するようにしました。

これにより、角度(向き)と入力値の大きさが計算できたため、次の部分でベクトルを再計算します。

// 角度と大きさからベクトルを作成
return new Vector2(
    Mathf.Cos(angle * Mathf.Deg2Rad),
    Mathf.Sin(angle * Mathf.Deg2Rad)
) * magnitude;

さいごに

2軸の入力を8方向にスナップする方法を解説しました。

Composite Bindingでも実現できますが、Processorを自作して適用するとより自由度の高いスナップ処理が実現できます。

Processorにすることで、あらゆる2軸入力に対して簡単にスナップ処理を適用することが可能になります。

何れの方法も、ゲームロジックと入力ロジックが分離されることにより、コードのメンテナンス性向上が期待できるでしょう。

関連記事

参考サイト

スポンサーリンク