以下の内容はhttps://bravememo.hatenablog.com/entry/2024/11/24/014817より取得しました。


【Unity】Input Systemで毎フレームなにかしらのInputActionを行う

はじめに

環境 unity 2021.3

コード

InputTestFixtureの中身をunity test frameworkを使用しない形に置き換えたものです

入力実行の流れとしてはInputEventPtr作成→InputSystem.Queueに積む→ InputSystem.Update()で入力実行になります

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine.InputSystem.Controls;
using Unity.Collections;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.InputSystem.Utilities;
using UnityEngine.InputSystem;
using UnityEngine;
using TouchPhase = UnityEngine.InputSystem.TouchPhase;

/// <summary>
/// テスト入力を行うクラス
/// </summary>
public class InputTester
{
    /// <summary>
    /// InputAction実行
    /// Perform the input action without having to know what it is bound to.
    /// </summary>
    /// <param name="action">An input action that is currently enabled and has controls it is bound to.</param>
    /// <remarks>
    /// Blindly triggering an action requires making a few assumptions. Actions are not built to be able to trigger
    /// without any input. This means that this method has to generate input on a control that the action is bound to.
    /// Note that this method has no understanding of the interactions that may be present on the action and thus
    /// does not know how they may affect the triggering of the action.
    /// </summary>
    /// <param name="action"></param>
    public void PressTrigger(InputAction action, bool queueEventOnly = false)
    {
        if (!CanTrigger(action))
        {
            return;
        }
        var controls = action.controls;
        // See if we have a button we can trigger.
        for (var i = 0; i < controls.Count; ++i)
        {
            if (!(controls[i] is ButtonControl button))
                continue;

            Set(button, 1, queueEventOnly: queueEventOnly);

            return;
        }

        // See if we have an axis we can slide a bit.
        for (var i = 0; i < controls.Count; ++i)
        {
            if (!(controls[i] is AxisControl axis))
                continue;

            Set(axis, axis.ReadValue() + 0.01f, queueEventOnly: queueEventOnly);

            return;
        }
    }

    public void ReleaseTrigger(InputAction action, bool queueEventOnly = false)
    {
        if (!CanTrigger(action))
        {
            return;
        }
        var controls = action.controls;
        // See if we have a button we can trigger.
        for (var i = 0; i < controls.Count; ++i)
        {
            if (!(controls[i] is ButtonControl button))
                continue;

            Set(button, 0, queueEventOnly: queueEventOnly);

            return;
        }

        // See if we have an axis we can slide a bit.
        for (var i = 0; i < controls.Count; ++i)
        {
            if (!(controls[i] is AxisControl axis))
                continue;

            Set(axis, 0, queueEventOnly: queueEventOnly);

            return;
        }
    }

    /// <summary>
    /// ボタンを押す
    /// </summary>
    /// <param name="button"></param>
    /// <param name="time"></param>
    /// <param name="timeOffset"></param>
    /// <param name="queueEventOnly"></param>
    public void Press(ButtonControl button, double time = -1, double timeOffset = 0, bool queueEventOnly = false)
    {
        Set(button, 1, time, timeOffset, queueEventOnly: queueEventOnly);
    }
    /// <summary>
    /// ボタンを離す
    /// </summary>
    /// <param name="button"></param>
    /// <param name="time"></param>
    /// <param name="timeOffset"></param>
    /// <param name="queueEventOnly"></param>
    public void Release(ButtonControl button, double time = -1, double timeOffset = 0, bool queueEventOnly = false)
    {
        Set(button, 0, time, timeOffset, queueEventOnly: queueEventOnly);
    }

    public void PressAndRelease(ButtonControl button, double time = -1, double timeOffset = 0, bool queueEventOnly = false)
    {
        Press(button, time, timeOffset, queueEventOnly: true);  // This one is always just a queue.
        Release(button, time, timeOffset, queueEventOnly: queueEventOnly);
    }

    public void Click(ButtonControl button, double time = -1, double timeOffset = 0, bool queueEventOnly = false)
    {
        PressAndRelease(button, time, timeOffset, queueEventOnly: queueEventOnly);
    }

    /// <summary>
    /// Set the control with the given <paramref name="path"/> on <paramref name="device"/> to the given <paramref name="state"/>
    /// by sending a state event with the value to the device.
    /// </summary>
    /// <param name="device">Device on which to find a control.</param>
    /// <param name="path">Path of the control on the device.</param>
    /// <param name="state">New state for the control.</param>
    /// <param name="time">Timestamp to use for the state event. If -1 (default), current time is used (see <see cref="InputTestFixture.currentTime"/>).</param>
    /// <param name="timeOffset">Offset to apply to the current time. This is an alternative to <paramref name="time"/>. By default, no offset is applied.</param>
    /// <param name="queueEventOnly">If true, no <see cref="InputSystem.Update"/> will be performed after queueing the event. This will only put
    /// the state event on the event queue and not do anything else. The default is to call <see cref="InputSystem.Update"/> after queuing the event.
    /// Note that not issuing an update means the state of the device will not change yet. This may affect subsequent Set/Press/Release/etc calls
    /// as they will not yet see the state change.
    ///
    /// Note that this parameter will be ignored if the test is a <c>[UnityTest]</c>. Multi-frame
    /// playmode tests will automatically process input as part of the Unity player loop.</param>
    /// <typeparam name="TValue">Value type of the control.</typeparam>
    /// <example>
    /// <code>
    /// var device = InputSystem.AddDevice("TestDevice");
    /// Set&lt;ButtonControl&gt;(device, "button", 1);
    /// Set&lt;AxisControl&gt;(device, "{Primary2DMotion}/x", 123.456f);
    /// </code>
    /// </example>
    public void Set<TValue>(InputDevice device, string path, TValue state, double time = -1, double timeOffset = 0,
        bool queueEventOnly = false)
        where TValue : struct
    {
        if (device == null)
            throw new ArgumentNullException(nameof(device));
        if (string.IsNullOrEmpty(path))
            throw new ArgumentNullException(nameof(path));

        var control = (InputControl<TValue>)device[path];
        Set(control, state, time, timeOffset, queueEventOnly);
    }

    /// <summary>
    /// Set the control to the given value by sending a state event with the value to the
    /// control's device.
    /// </summary>
    /// <param name="control">An input control on a device that has been added to the system.</param>
    /// <param name="state">New value for the input control.</param>
    /// <param name="time">Timestamp to use for the state event. If -1 (default), current time is used (see <see cref="InputTestFixture.currentTime"/>).</param>
    /// <param name="timeOffset">Offset to apply to the current time. This is an alternative to <paramref name="time"/>. By default, no offset is applied.</param>
    /// <param name="queueEventOnly">If true, no <see cref="InputSystem.Update"/> will be performed after queueing the event. This will only put
    /// the state event on the event queue and not do anything else. The default is to call <see cref="InputSystem.Update"/> after queuing the event.
    /// Note that not issuing an update means the state of the device will not change yet. This may affect subsequent Set/Press/Release/etc calls
    /// as they will not yet see the state change.
    ///
    /// Note that this parameter will be ignored if the test is a <c>[UnityTest]</c>. Multi-frame
    /// playmode tests will automatically process input as part of the Unity player loop.</param>
    /// <typeparam name="TValue">Value type of the given control.</typeparam>
    /// <example>
    /// <code>
    /// var gamepad = InputSystem.AddDevice&lt;Gamepad&gt;();
    /// Set(gamepad.leftButton, 1);
    /// </code>
    /// </example>
    public void Set<TValue>(InputControl<TValue> control, TValue state, double time = -1, double timeOffset = 0, bool queueEventOnly = false)
        where TValue : struct
    {
        if (control == null)
            throw new ArgumentNullException(nameof(control));
        if (!control.device.added)
            throw new ArgumentException(
                $"Device of control '{control}' has not been added to the system", nameof(control));


        void SetUpAndQueueEvent(InputEventPtr eventPtr)
        {
            eventPtr.time = (time >= 0 ? time : InputState.currentTime) + timeOffset;
            control.WriteValueIntoEvent(state, eventPtr);
            InputSystem.QueueEvent(eventPtr);
        }

        // Touchscreen does not support delta events involving TouchState.
        if (control is TouchControl)
        {
            using (StateEvent.From(control.device, out var eventPtr))
                SetUpAndQueueEvent(eventPtr);
        }
        else
        {
            // We use delta state events rather than full state events here to mitigate the following problem:
            // Grabbing state from the device will preserve the current values of controls covered in the state.
            // However, running an update may alter the value of one or more of those controls. So with a full
            // state event, we may be writing outdated data back into the device. For example, in the case of delta
            // controls which will reset in OnBeforeUpdate().
            //
            // Using delta events, we may still grab state outside of just the one control in case we're looking at
            // bit-addressed controls but at least we can avoid the problem for the majority of controls.
            using (DeltaStateEvent.From(control, out var eventPtr))
                SetUpAndQueueEvent(eventPtr);
        }

        if (!queueEventOnly)
            InputSystem.Update();
    }

    public void Move(InputControl<Vector2> positionControl, Vector2 position, Vector2? delta = null, double time = -1, double timeOffset = 0, bool queueEventOnly = false)
    {
        Set(positionControl, position, time: time, timeOffset: timeOffset, queueEventOnly: true);

        var deltaControl = (Vector2Control)positionControl.device.TryGetChildControl("delta");
        if (deltaControl != null)
            Set(deltaControl, delta ?? position - positionControl.ReadValue(), time: time, timeOffset: timeOffset, queueEventOnly: true);

        if (!queueEventOnly)
            InputSystem.Update();
    }


    public void BeginTouch(int touchId, Vector2 position, bool queueEventOnly = false, Touchscreen screen = null,
        double time = -1, double timeOffset = 0, byte displayIndex = 0)
    {
        SetTouch(touchId, TouchPhase.Began, position, 1, queueEventOnly: queueEventOnly, screen: screen, time: time, timeOffset: timeOffset, displayIndex: displayIndex);
    }

    public void BeginTouch(int touchId, Vector2 position, float pressure, bool queueEventOnly = false, Touchscreen screen = null,
        double time = -1, double timeOffset = 0)
    {
        SetTouch(touchId, TouchPhase.Began, position, pressure, queueEventOnly: queueEventOnly, screen: screen, time: time, timeOffset: timeOffset);
    }


    public void MoveTouch(int touchId, Vector2 position, Vector2 delta = default, bool queueEventOnly = false,
        Touchscreen screen = null, double time = -1, double timeOffset = 0)
    {
        SetTouch(touchId, TouchPhase.Moved, position, 1, delta, queueEventOnly: queueEventOnly, screen: screen, time: time, timeOffset: timeOffset);
    }

    public void MoveTouch(int touchId, Vector2 position, float pressure, Vector2 delta = default, bool queueEventOnly = false,
        Touchscreen screen = null, double time = -1, double timeOffset = 0)
    {
        SetTouch(touchId, TouchPhase.Moved, position, pressure, delta, queueEventOnly, screen: screen, time: time, timeOffset: timeOffset);
    }


    public void EndTouch(int touchId, Vector2 position, Vector2 delta = default, bool queueEventOnly = false,
        Touchscreen screen = null, double time = -1, double timeOffset = 0, byte displayIndex = 0)
    {
        SetTouch(touchId, TouchPhase.Ended, position, 1, delta, queueEventOnly: queueEventOnly, screen: screen, time: time, timeOffset: timeOffset, displayIndex: displayIndex);
    }

    public void EndTouch(int touchId, Vector2 position, float pressure, Vector2 delta = default, bool queueEventOnly = false,
        Touchscreen screen = null, double time = -1, double timeOffset = 0)
    {
        SetTouch(touchId, TouchPhase.Ended, position, pressure, delta, queueEventOnly, screen: screen, time: time, timeOffset: timeOffset);
    }


    public void CancelTouch(int touchId, Vector2 position, Vector2 delta = default, bool queueEventOnly = false,
        Touchscreen screen = null, double time = -1, double timeOffset = 0)
    {
        SetTouch(touchId, TouchPhase.Canceled, position, delta, queueEventOnly: queueEventOnly, screen: screen, time: time, timeOffset: timeOffset);
    }

    public void CancelTouch(int touchId, Vector2 position, float pressure, Vector2 delta = default, bool queueEventOnly = false,
        Touchscreen screen = null, double time = -1, double timeOffset = 0)
    {
        SetTouch(touchId, TouchPhase.Canceled, position, pressure, delta, queueEventOnly, screen: screen, time: time, timeOffset: timeOffset);
    }

    public void SetTouch(int touchId, TouchPhase phase, Vector2 position, Vector2 delta = default,
        bool queueEventOnly = true, Touchscreen screen = null, double time = -1, double timeOffset = 0)
    {
        SetTouch(touchId, phase, position, 1, delta: delta, queueEventOnly: queueEventOnly, screen: screen, time: time,
            timeOffset: timeOffset);
    }

    public void SetTouch(int touchId, TouchPhase phase, Vector2 position, float pressure, Vector2 delta = default, bool queueEventOnly = true,
        Touchscreen screen = null, double time = -1, double timeOffset = 0, byte displayIndex = 0)
    {
        if (screen == null)
        {
            screen = Touchscreen.current;
            if (screen == null)
                screen = InputSystem.AddDevice<Touchscreen>();
        }

        InputSystem.QueueStateEvent(screen, new TouchState
        {
            touchId = touchId,
            phase = phase,
            position = position,
            delta = delta,
            pressure = pressure,
            displayIndex = displayIndex,
        }, (time >= 0 ? time : InputState.currentTime) + timeOffset);
        if (!queueEventOnly)
            InputSystem.Update();
    }

    public static bool CanTrigger(InputAction action)
    {
        if (action == null)
        {
            return false;
        }


        if (!action.enabled)
        {
            Debug.Log($"Action '{action}' must be enabled in order to be able to trigger it");
            return false;
        }

        var controls = action.controls;
        if (controls.Count == 0)
        {
            Debug.Log($"Action '{action}' must be bound to controls in order to be able to trigger it");
            return false;
        }

        return true;
    }
}

使用例

using UnityEngine;
using UnityEngine.InputSystem;

public class InputTest : MonoBehaviour
{
    //とりあえずInspectorから設定
    public PlayerInput input;
    public InputTester InputTester = new InputTester();
    private InputAction preInputAction = null;

    public void Start()
    {
        //バックグラウンドで自動入力受付できるようにする
        InputSystem.settings.backgroundBehavior = InputSettings.BackgroundBehavior.IgnoreFocus;
        InputSystem.settings.editorInputBehaviorInPlayMode = InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView;
    }

    void Update()
    {
        //ランダム再生
        var r = Random.Range(0, input.currentActionMap.actions.Count);
        //適当なInputAction実行
        InputTester.PressTrigger(input.currentActionMap.actions[r],true);
        preInputAction = input.currentActionMap.actions[r];
       
        //前フレームで押したInputActionを離す
        if (preInputAction != null)
        {
            InputTester.ReleaseTrigger(preInputAction,true);
        }
        //入力更新はプロジェクトの更新処理を使用するので呼ぶ必要なし
        //InputSystem.Update();
        //ログ出力
        for (var i = 0; i < input.currentActionMap.actions.Count; i++)
        {
            if (input.currentActionMap.actions[i].WasPressedThisFrame())
            {
                Debug.Log($"押された {input.currentActionMap.actions[i].name}");
            }
            if (preInputAction != null && preInputAction.WasReleasedThisFrame())
            {
                Debug.Log($"離された {preInputAction.name}");
            }
        }

        /*
        InputTester.PressTrigger(input.currentActionMap.actions[r])[f:id:Brave345:20241124014513g:plain];
        preInputAction = input.currentActionMap.actions[r];
       
        //前フレームで押したInputActionを離す
        if (preInputAction != null)
        {
            InputTester.ReleaseTrigger(preInputAction);
        }


        //ログ出力
        for (var i = 0; i < input.currentActionMap.actions.Count; i++)
        {
          //InputSystem.Update();が複数回実行されてこの判定を通らなくなってしまう
            if (input.currentActionMap.actions[i].WasPressedThisFrame())
            {
                Debug.Log($"押された {input.currentActionMap.actions[i].name}");
            }
            if (preInputAction != null && preInputAction.WasReleasedThisFrame())
            {
                Debug.Log($"離された {preInputAction.name}");
            }
        }
         
         * */
    }
}

毎フレーム適当にInputAction が実行できました

;




以上の内容はhttps://bravememo.hatenablog.com/entry/2024/11/24/014817より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14