白黒羊

Unity New Input System を使ってみた(テスト・UIToolkitとの兼ね合い編)

リファレンス

具体的な使い方は日本語だとこのUnity Proの記事と、

安心安定のテラシュールブログさんがわかりやすいです。実際に使ったときには、押しっぱなしの状態を取れないから Update 関数を使うとかを参考にさせていただきました。

もちろん、マニュアルの Quick Start Guide が一次情報なので、ここを一通り見ることで New Input System が使えるようになります。

https://docs.unity3d.com/Packages/com.unity.inputsystem@1.1/manual/index.html

かなり便利になっているように思いました。唯一の欠点はXBOX非対応なことかな……?

Rewired も似たようなことができるアセットなので、合わせて検討すると良さそうです。

今回の記事はそれらの基本的な設定が終わった後のテストとUIToolkitとの兼ね合いについてです。

New Input System のテスト

Unity New Input Systemは入力を絡めたテストが書けます。

Input testing _ Input System _ 1.1.1

manifest.json

{
  "dependencies": {
    "com.cysharp.unitask": "https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask",
    "com.neuecc.unirx": "https://github.com/neuecc/UniRx.git?path=Assets/Plugins/UniRx/Scripts",
    "com.unity.2d.pixel-perfect": "5.0.0"
  },
  "testables": [
    "com.unity.inputsystem"
  ]
}

こんな感じで "testables" とその値として "com.unity.inputsystem" を追加することでテストできるようになります。

また、既存の Edit Test アセンブリに Unity.InputSystem と Unity.InputSystem.TestFramework への依存を追加します。統合テストをするならば Unity.InputSystem.IntegrationTests も使えるようです。

あまり意味のないテストですが、こんな感じで PressRelease を使うことでキーボードを押しているかのようなテストができます。

ちなみに Press だと押しっぱなしになってしまうので複数回押したい時は PressAndRelease を使います。

KeyPressedCheckerTest.cs

using NUnit.Framework;
using UnityEngine.InputSystem;

namespace Project.Tests.Editor
{
    public class KeyPressedCheckerTest : InputTestFixture
    {
        private readonly KeyPressedChecker _keyPressedChecker = new();

        [Test]
        public void キーを押している間はIsPressedAnyがTrue()
        {
            var keyboard = InputSystem.AddDevice<Keyboard>();
            
            Press(keyboard.spaceKey);
            Assert.That(_keyPressedChecker.IsPressedAny(), Is.True);

            Release(keyboard.spaceKey);
            Assert.That(_keyPressedChecker.IsPressedAny(), Is.False);
            
            Press(keyboard.aKey);
            Assert.That(_keyPressedChecker.IsPressedAny(), Is.True);
            
            Press(keyboard.leftArrowKey);
            Assert.That(_keyPressedChecker.IsPressedAny(), Is.True);

            Release(keyboard.aKey);
            Assert.That(_keyPressedChecker.IsPressedAny(), Is.True);

            Release(keyboard.leftArrowKey);
            Assert.That(_keyPressedChecker.IsPressedAny(), Is.False);
        }
    }

    public class KeyPressedChecker
    {
        public bool IsPressedAny()
        {
            return Keyboard.current.anyKey.isPressed;
        }
    }
}

InputSystem.AddDevice<Keyboard>() のようなテスト用デバイスの追加は、 [OneTimeSetUp] アトリビュートをつけたメソッドでは動作しなくて(以下のようなエラーメッセージが出ます)、[Test]アトリビュートのついたメソッド内で使うか、 Awake() のような MonoBehaviour のライフサイクルの中に入れてあげるといいみたいです。

---
System.NullReferenceException : Object reference not set to an instance of an object
---
at UnityEngine.InputSystem.LowLevel.InputStateBuffers+DoubleBuffers.GetFrontBuffer (System.Int32 deviceIndex) [0x00001] in [...]/Library/PackageCache/com.unity.inputsystem@1.1.0-pre.5/InputSystem/State/InputStateBuffers.cs:73 
  at UnityEngine.InputSystem.LowLevel.InputStateBuffers.GetFrontBufferForDevice (System.Int32 deviceIndex) [0x00001] in [...]/Library/PackageCache/com.unity.inputsystem@1.1.0-pre.5/InputSystem/State/InputStateBuffers.cs:126 
  at UnityEngine.InputSystem.InputControl.get_currentStatePtr () [0x00000] in [...]/Library/PackageCache/com.unity.inputsystem@1.1.0-pre.5/InputSystem/Controls/InputControl.cs:789 
  at UnityEngine.InputSystem.LowLevel.DeltaStateEvent.From (UnityEngine.InputSystem.InputControl control, UnityEngine.InputSystem.LowLevel.InputEventPtr& eventPtr, Unity.Collections.Allocator allocator) [0x00074] in [...]/Library/PackageCache/com.unity.inputsystem@1.1.0-pre.5/InputSystem/Events/DeltaStateEvent.cs:88 
  at UnityEngine.InputSystem.InputTestFixture.Set[TValue] (UnityEngine.InputSystem.InputControl`1[TValue] control, TValue state, System.Double time, System.Double timeOffset, System.Boolean queueEventOnly) [0x000b4] in [...]/Library/PackageCache/com.unity.inputsystem@1.1.0-pre.5/Tests/TestFixture/InputTestFixture.cs:477 
  at UnityEngine.InputSystem.InputTestFixture.Press (UnityEngine.InputSystem.Controls.ButtonControl button, System.Double time, System.Double timeOffset, System.Boolean queueEventOnly) [0x00001] in [...]/Library/PackageCache/com.unity.inputsystem@1.1.0-pre.5/Tests/TestFixture/InputTestFixture.cs:367 
  at Project.Tests.Runtime.Controller.GridButtonSignalPublisherTest.Input (System.Int32 up, System.Int32 right, System.Int32 down, System.Int32 left) [0x00039] in [...]/Assets/Project/Tests/Runtime/Controller/GridButtonSignalPublisherTest.cs:97 
  at Project.Tests.Runtime.Controller.GridButtonSignalPublisherTest+<右に4つ進んで選択したらx4y0になる>d__9.MoveNext () [0x0003c] in [...]/Assets/Project/Tests/Runtime/Controller/GridButtonSignalPublisherTest.cs:51 
  at UnityEngine.TestTools.TestEnumerator+<Execute>d__6.MoveNext () [0x0004c] in [...]/Library/PackageCache/com.unity.test-framework@1.1.27/UnityEngine.TestRunner/NUnitExtensions/Attributes/TestEnumerator.cs:36

UIToolkit との兼ね合い

1.1以降から UIToolkit とも連携するようになりました。

これまでも勝手に両方を使うことは可能だったのですが、 Unity Editor 2021.2.0b15 以降では以下のようなエラーが出るようになりました。

InvalidOperationException: You are trying to read Input using the UnityEngine.Input class, but you have switched active Input handling to Input System package in Player Settings.

どうやら新InputSystem用のEventSystemを置かないとダメなようです。

シーン内のGameObjectに Add Component して Input System UI Input Module を追加するとエラーが消えて使えるようになります。

すでに EventSystem がシーンにある場合は Replace with InputSystemUIInputModule を選択することで Input System UI Input Module が追加されます。

参考

Resolved – [2021.2.0b15] UIElement breakage with Input System package – Unity Forum