adding basis for input recorder, still needs to debug why this isn't working when editor is in the background

This commit is contained in:
Samuel Bellomo 2023-01-11 16:37:40 -05:00
Родитель e50a3d8427
Коммит 95d4ce7ece
22 изменённых файлов: 2330 добавлений и 22 удалений

1
.gitignore поставляемый
Просмотреть файл

@ -89,3 +89,4 @@ crashlytics-build.properties
# Mac file setting
.DS_Store
Basic/ClientDriven_clone_0

Просмотреть файл

@ -201,10 +201,34 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: c521a646e08a84279a7bcf9c775080a4, type: 3}
m_Name:
m_EditorClassIdentifier:
m_ClientPlayerMove: {fileID: 5667156634780145037}
isObjectPickedUp:
m_InternalValue: 0
m_LocalHeldPosition: {x: 0, y: 2.85, z: 0}
--- !u!114 &1465562511556424027
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1116025501350672692}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d35d5308cbb1fcd479dbf0c516db2aa7, type: 3}
m_Name:
m_EditorClassIdentifier:
m_StartRecordingWhenEnabled: 0
m_RecordFrames: 1
m_ReplayOnNewDevices: 1
m_SimulateOriginalTimingOnReplay: 0
m_RecordStateEventsOnly: 0
m_CaptureMemoryDefaultSize: 2097152
m_CaptureMemoryMaxSize: 10485760
m_DevicePath:
m_RecordButtonPath: <Keyboard>/r
m_PlayButtonPath: <Keyboard>/p
m_ChangeEvent:
m_PersistentCalls:
m_Calls: []
--- !u!114 &5667156634780145037
MonoBehaviour:
m_ObjectHideFlags: 0

Просмотреть файл

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a9ba9a3d42bc74ddf8c3d003dc3f81cc
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 544ea342e5333486e9ce814098eedfb8
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8becf87c92a4b4a119ffec85af4b2cf5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5a6dffe792b1b4b2fae63ccb1d441f05
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -0,0 +1,4 @@
{
"displayName": "Input Recorder",
"description": "Shows how to capture and replay input events. Also useful by itself to debug input event sequences."
}

Просмотреть файл

@ -0,0 +1,472 @@
using System;
using UnityEngine.Events;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.LowLevel;
////TODO: allow multiple device paths
////TODO: streaming support
////REVIEW: consider this for inclusion directly in the input system
namespace UnityEngine.InputSystem
{
/// <summary>
/// A wrapper component around <see cref="InputEventTrace"/> that provides an easy interface for recording input
/// from a GameObject.
/// </summary>
/// <remarks>
/// This component comes with a custom inspector that provides an easy recording and playback interface and also
/// gives feedback about what has been recorded in the trace. The interface also allows saving and loading event
/// traces.
///
/// Capturing can either be constrained by a <see cref="devicePath"/> or capture all input occuring in the system.
///
/// Replay by default will happen frame by frame (see <see cref="InputEventTrace.ReplayController.PlayAllFramesOneByOne"/>).
/// If frame markers are disabled (see <see cref="recordFrames"/>), all events are queued right away in the first
/// frame and replay completes immediately.
///
/// Other than frame-by-frame, replay can be made to happen in a way that tries to simulate the original input
/// timing. To do so, enable <see cref="simulateOriginalTimingOnReplay"/>. This will make use of <see
/// cref="InputEventTrace.ReplayController.PlayAllEventsAccordingToTimestamps"/>
/// </remarks>
public class InputRecorder : MonoBehaviour
{
/// <summary>
/// Whether a capture is currently in progress.
/// </summary>
/// <value>True if a capture is in progress.</value>
public bool captureIsRunning => m_EventTrace != null && m_EventTrace.enabled;
/// <summary>
/// Whether a replay is currently being run by the component.
/// </summary>
/// <value>True if replay is running.</value>
/// <seealso cref="replay"/>
/// <seealso cref="StartReplay"/>
/// <seealso cref="StopReplay"/>
public bool replayIsRunning => m_ReplayController != null && !m_ReplayController.finished;
/// <summary>
/// If true, input recording is started immediately when the component is enabled. Disabled by default.
/// Call <see cref="StartCapture"/> to manually start capturing.
/// </summary>
/// <value>True if component will start recording automatically in <see cref="OnEnable"/>.</value>
/// <seealso cref="StartCapture"/>
public bool startRecordingWhenEnabled
{
get => m_StartRecordingWhenEnabled;
set
{
m_StartRecordingWhenEnabled = value;
if (value && enabled && !captureIsRunning)
StartCapture();
}
}
/// <summary>
/// Total number of events captured.
/// </summary>
/// <value>Number of captured events.</value>
public long eventCount => m_EventTrace?.eventCount ?? 0;
/// <summary>
/// Total size of captured events.
/// </summary>
/// <value>Size of captured events in bytes.</value>
public long totalEventSizeInBytes => m_EventTrace?.totalEventSizeInBytes ?? 0;
/// <summary>
/// Total size of capture memory currently allocated.
/// </summary>
/// <value>Size of memory allocated for capture.</value>
public long allocatedSizeInBytes => m_EventTrace?.allocatedSizeInBytes ?? 0;
/// <summary>
/// Whether to record frame marker events when capturing input. Enabled by default.
/// </summary>
/// <value>True if frame marker events will be recorded.</value>
/// <seealso cref="InputEventTrace.recordFrameMarkers"/>
public bool recordFrames
{
get => m_RecordFrames;
set
{
if (m_RecordFrames == value)
return;
m_RecordFrames = value;
if (m_EventTrace != null)
m_EventTrace.recordFrameMarkers = m_RecordFrames;
}
}
/// <summary>
/// Whether to record only <see cref="StateEvent"/>s and <see cref="DeltaStateEvent"/>s. Disabled by
/// default.
/// </summary>
/// <value>True if anything but state events should be ignored.</value>
public bool recordStateEventsOnly
{
get => m_RecordStateEventsOnly;
set => m_RecordStateEventsOnly = value;
}
/// <summary>
/// Path that constrains the devices to record from.
/// </summary>
/// <value>Input control path to match devices or null/empty.</value>
/// <remarks>
/// By default, this is not set. Meaning that input will be recorded from all devices. By setting this property
/// to a path, only events for devices that match the given path (as dictated by <see cref="InputControlPath.Matches"/>)
/// will be recorded from.
///
/// By setting this property to the exact path of a device at runtime, recording can be restricted to just that
/// device.
/// </remarks>
/// <seealso cref="InputControlPath"/>
/// <seealso cref="InputControlPath.Matches"/>
public string devicePath
{
get => m_DevicePath;
set => m_DevicePath = value;
}
public string recordButtonPath
{
get => m_RecordButtonPath;
set
{
m_RecordButtonPath = value;
HookOnInputEvent();
}
}
public string playButtonPath
{
get => m_PlayButtonPath;
set
{
m_PlayButtonPath = value;
HookOnInputEvent();
}
}
/// <summary>
/// The underlying event trace that contains the captured input events.
/// </summary>
/// <value>Underlying event trace.</value>
/// <remarks>
/// This will be null if no capture is currently associated with the recorder.
/// </remarks>
public InputEventTrace capture => m_EventTrace;
/// <summary>
/// The replay controller for when a replay is running.
/// </summary>
/// <value>Replay controller for the event trace while replay is running.</value>
/// <seealso cref="replayIsRunning"/>
/// <seealso cref="StartReplay"/>
public InputEventTrace.ReplayController replay => m_ReplayController;
public int replayPosition
{
get
{
if (m_ReplayController != null)
return m_ReplayController.position;
return 0;
}
////TODO: allow setting replay position
}
/// <summary>
/// Whether a replay should create new devices or replay recorded events as is. Disabled by default.
/// </summary>
/// <value>True if replay should temporary create new devices.</value>
/// <seealso cref="InputEventTrace.ReplayController.WithAllDevicesMappedToNewInstances"/>
public bool replayOnNewDevices
{
get => m_ReplayOnNewDevices;
set => m_ReplayOnNewDevices = value;
}
/// <summary>
/// Whether to attempt to re-create the original event timing when replaying events. Disabled by default.
/// </summary>
/// <value>If true, events are queued based on their timestamp rather than based on their recorded frames (if any).</value>
/// <seealso cref="InputEventTrace.ReplayController.PlayAllEventsAccordingToTimestamps"/>
public bool simulateOriginalTimingOnReplay
{
get => m_SimulateOriginalTimingOnReplay;
set => m_SimulateOriginalTimingOnReplay = value;
}
public ChangeEvent changeEvent
{
get
{
if (m_ChangeEvent == null)
m_ChangeEvent = new ChangeEvent();
return m_ChangeEvent;
}
}
public void StartCapture()
{
if (m_EventTrace != null && m_EventTrace.enabled)
return;
CreateEventTrace();
m_EventTrace.Enable();
m_ChangeEvent?.Invoke(Change.CaptureStarted);
}
public void StopCapture()
{
if (m_EventTrace != null && m_EventTrace.enabled)
{
m_EventTrace.Disable();
m_ChangeEvent?.Invoke(Change.CaptureStopped);
}
}
public void StartReplay()
{
if (m_EventTrace == null)
return;
if (replayIsRunning && replay.paused)
{
replay.paused = false;
return;
}
StopCapture();
// Configure replay controller.
m_ReplayController = m_EventTrace.Replay()
.OnFinished(StopReplay)
.OnEvent(_ => m_ChangeEvent?.Invoke(Change.EventPlayed));
if (m_ReplayOnNewDevices)
m_ReplayController.WithAllDevicesMappedToNewInstances();
// Start replay.
if (m_SimulateOriginalTimingOnReplay)
m_ReplayController.PlayAllEventsAccordingToTimestamps();
else
m_ReplayController.PlayAllFramesOneByOne();
m_ChangeEvent?.Invoke(Change.ReplayStarted);
}
public void StopReplay()
{
if (m_ReplayController != null)
{
m_ReplayController.Dispose();
m_ReplayController = null;
m_ChangeEvent?.Invoke(Change.ReplayStopped);
}
}
public void PauseReplay()
{
if (m_ReplayController != null)
m_ReplayController.paused = true;
}
public void ClearCapture()
{
m_EventTrace?.Clear();
}
public void LoadCaptureFromFile(string fileName)
{
if (string.IsNullOrEmpty(fileName))
throw new ArgumentNullException(nameof(fileName));
CreateEventTrace();
m_EventTrace.ReadFrom(fileName);
}
public void SaveCaptureToFile(string fileName)
{
if (string.IsNullOrEmpty(fileName))
throw new ArgumentNullException(nameof(fileName));
m_EventTrace?.WriteTo(fileName);
}
protected void OnEnable()
{
// Hook InputSystem.onEvent before the event trace does.
HookOnInputEvent();
if (m_StartRecordingWhenEnabled)
StartCapture();
}
protected void OnDisable()
{
StopCapture();
StopReplay();
UnhookOnInputEvent();
}
protected void OnDestroy()
{
m_ReplayController?.Dispose();
m_ReplayController = null;
m_EventTrace?.Dispose();
m_EventTrace = null;
}
private bool OnFilterInputEvent(InputEventPtr eventPtr, InputDevice device)
{
// Filter out non-state events, if enabled.
if (m_RecordStateEventsOnly && !eventPtr.IsA<StateEvent>() && !eventPtr.IsA<DeltaStateEvent>())
return false;
// Match device path, if set.
if (string.IsNullOrEmpty(m_DevicePath) || device == null)
return true;
return InputControlPath.MatchesPrefix(m_DevicePath, device);
}
private void OnEventRecorded(InputEventPtr eventPtr)
{
m_ChangeEvent?.Invoke(Change.EventCaptured);
}
private void OnInputEvent(InputEventPtr eventPtr, InputDevice device)
{
if (!eventPtr.IsA<StateEvent>() && !eventPtr.IsA<DeltaStateEvent>())
return;
if (!string.IsNullOrEmpty(m_PlayButtonPath))
{
var playControl = InputControlPath.TryFindControl(device, m_PlayButtonPath) as InputControl<float>;
if (playControl != null && playControl.ReadValueFromEvent(eventPtr) >= InputSystem.settings.defaultButtonPressPoint)
{
if (replayIsRunning)
StopReplay();
else
StartReplay();
eventPtr.handled = true;
}
}
if (!string.IsNullOrEmpty(m_RecordButtonPath))
{
var recordControl = InputControlPath.TryFindControl(device, m_RecordButtonPath) as InputControl<float>;
if (recordControl != null && recordControl.ReadValueFromEvent(eventPtr) >= InputSystem.settings.defaultButtonPressPoint)
{
if (captureIsRunning)
StopCapture();
else
StartCapture();
eventPtr.handled = true;
}
}
}
#if UNITY_EDITOR
protected void OnValidate()
{
if (m_EventTrace != null)
m_EventTrace.recordFrameMarkers = m_RecordFrames;
}
#endif
[SerializeField] private bool m_StartRecordingWhenEnabled = false;
[Tooltip("If enabled, additional events will be recorded that demarcate frame boundaries. When replaying, this allows "
+ "spacing out input events across frames corresponding to the original distribution across frames when input was "
+ "recorded. If this is turned off, all input events will be queued in one block when replaying the trace.")]
[SerializeField] private bool m_RecordFrames = true;
[Tooltip("If enabled, new devices will be created for captured events when replaying them. If disabled (default), "
+ "events will be queued as is and thus keep their original device ID.")]
[SerializeField] private bool m_ReplayOnNewDevices;
[Tooltip("If enabled, the system will try to simulate the original event timing on replay. This differs from replaying frame "
+ "by frame in that replay will try to compensate for differences in frame timings and redistribute events to frames that "
+ "more closely match the original timing. Note that this is not perfect and will not necessarily create a 1:1 match.")]
[SerializeField] private bool m_SimulateOriginalTimingOnReplay;
[Tooltip("If enabled, only StateEvents and DeltaStateEvents will be captured.")]
[SerializeField] private bool m_RecordStateEventsOnly;
[SerializeField] private int m_CaptureMemoryDefaultSize = 2 * 1024 * 1024;
[SerializeField] private int m_CaptureMemoryMaxSize = 10 * 1024 * 1024;
[SerializeField]
[InputControl(layout = "InputDevice")]
private string m_DevicePath;
[SerializeField]
[InputControl(layout = "Button")]
private string m_RecordButtonPath;
[SerializeField]
[InputControl(layout = "Button")]
private string m_PlayButtonPath;
[SerializeField] private ChangeEvent m_ChangeEvent;
private Action<InputEventPtr, InputDevice> m_OnInputEventDelegate;
private InputEventTrace m_EventTrace;
private InputEventTrace.ReplayController m_ReplayController;
private void CreateEventTrace()
{
////FIXME: remaining configuration should come through, too, if changed after the fact
if (m_EventTrace == null || m_EventTrace.maxSizeInBytes == 0)
{
m_EventTrace?.Dispose();
m_EventTrace = new InputEventTrace(m_CaptureMemoryDefaultSize, growBuffer: true, maxBufferSizeInBytes: m_CaptureMemoryMaxSize);
}
m_EventTrace.recordFrameMarkers = m_RecordFrames;
m_EventTrace.onFilterEvent += OnFilterInputEvent;
m_EventTrace.onEvent += OnEventRecorded;
}
private void HookOnInputEvent()
{
if (string.IsNullOrEmpty(m_PlayButtonPath) && string.IsNullOrEmpty(m_RecordButtonPath))
{
UnhookOnInputEvent();
return;
}
if (m_OnInputEventDelegate == null)
m_OnInputEventDelegate = OnInputEvent;
InputSystem.onEvent += m_OnInputEventDelegate;
}
private void UnhookOnInputEvent()
{
if (m_OnInputEventDelegate != null)
InputSystem.onEvent -= m_OnInputEventDelegate;
}
public enum Change
{
None,
EventCaptured,
EventPlayed,
CaptureStarted,
CaptureStopped,
ReplayStarted,
ReplayStopped,
}
[Serializable]
public class ChangeEvent : UnityEvent<Change>
{
}
}
}

Просмотреть файл

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d35d5308cbb1fcd479dbf0c516db2aa7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -0,0 +1,227 @@
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine.Events;
////TODO: add ability to inspect contents of event traces in a separate window
namespace UnityEngine.InputSystem.Editor
{
/// <summary>
/// A custom inspector for <see cref="InputRecorder"/>. Adds UI elements to store captures in files, to load them from
/// there, and to initiate replays from within the editor. It also shows information for when captures or replays are
/// in progress.
/// </summary>
[CustomEditor(typeof(InputRecorder))]
internal class InputRecorderInspector : UnityEditor.Editor
{
protected void OnEnable()
{
m_DevicePathProperty = serializedObject.FindProperty("m_DevicePath");
m_RecordButtonPath = serializedObject.FindProperty("m_RecordButtonPath");
m_PlayButtonPathProperty = serializedObject.FindProperty("m_PlayButtonPath");
m_RecordFramesProperty = serializedObject.FindProperty("m_RecordFrames");
m_RecordStateEventsOnlyProperty = serializedObject.FindProperty("m_RecordStateEventsOnly");
m_ReplayOnNewDevicesProperty = serializedObject.FindProperty("m_ReplayOnNewDevices");
m_SimulateTimingOnReplayProperty = serializedObject.FindProperty("m_SimulateOriginalTimingOnReplay");
m_CaptureMemoryDefaultSizeProperty = serializedObject.FindProperty("m_CaptureMemoryDefaultSize");
m_CaptureMemoryMaxSizeProperty = serializedObject.FindProperty("m_CaptureMemoryMaxSize");
m_StartRecordingWhenEnabledProperty = serializedObject.FindProperty("m_StartRecordingWhenEnabled");
m_AllInput = string.IsNullOrEmpty(m_DevicePathProperty.stringValue);
m_PlayText = EditorGUIUtility.TrIconContent("PlayButton", "Play the current input capture.");
m_PauseText = EditorGUIUtility.TrIconContent("PauseButton", "Pause the current input playback.");
m_ResumeText = EditorGUIUtility.TrIconContent("PauseButton On", "Resume the current input playback.");
m_StepForwardText = EditorGUIUtility.TrIconContent("d_StepButton", "Play the next input event.");
m_StepBackwardText = EditorGUIUtility.TrIconContent("d_StepLeftButton", "Play the previous input event.");
m_StopText = EditorGUIUtility.TrIconContent("PlayButton On", "Stop the current input playback.");
m_RecordText = EditorGUIUtility.TrIconContent("Animation.Record", "Start recording input.");
var recorder = (InputRecorder)serializedObject.targetObject;
m_OnRecordEvent = _ => Repaint();
recorder.changeEvent.AddListener(m_OnRecordEvent);
}
protected void OnDisable()
{
var recorder = (InputRecorder)serializedObject.targetObject;
recorder.changeEvent.RemoveListener(m_OnRecordEvent);
}
public override void OnInspectorGUI()
{
var recorder = (InputRecorder)serializedObject.targetObject;
using (var scope = new EditorGUI.ChangeCheckScope())
{
var newAllInput = EditorGUILayout.Toggle(m_AllInputText, m_AllInput);
if (!newAllInput)
{
using (new EditorGUI.IndentLevelScope())
{
EditorGUILayout.PropertyField(m_DevicePathProperty, m_DeviceText);
}
}
else if (newAllInput != m_AllInput)
{
m_DevicePathProperty.stringValue = string.Empty;
}
m_AllInput = newAllInput;
EditorGUILayout.PropertyField(m_RecordFramesProperty);
EditorGUILayout.PropertyField(m_RecordStateEventsOnlyProperty);
EditorGUILayout.PropertyField(m_ReplayOnNewDevicesProperty);
EditorGUILayout.PropertyField(m_SimulateTimingOnReplayProperty);
EditorGUILayout.PropertyField(m_StartRecordingWhenEnabledProperty, m_RecordWhenEnabledText);
var defaultSizeInMB = m_CaptureMemoryDefaultSizeProperty.intValue / (1024 * 1024);
var newDefaultSizeInMB = EditorGUILayout.IntSlider(m_DefaultSizeText, defaultSizeInMB, 1, 100);
if (newDefaultSizeInMB != defaultSizeInMB)
m_CaptureMemoryDefaultSizeProperty.intValue = newDefaultSizeInMB * 1024 * 1024;
var maxSizeInMB = m_CaptureMemoryMaxSizeProperty.intValue / (1024 * 1024);
var newMaxSizeInMB = maxSizeInMB < newDefaultSizeInMB
? newDefaultSizeInMB
: EditorGUILayout.IntSlider(m_MaxSizeText, maxSizeInMB, 1, 100);
if (newMaxSizeInMB != maxSizeInMB)
m_CaptureMemoryMaxSizeProperty.intValue = newMaxSizeInMB * 1024 * 1024;
EditorGUILayout.PropertyField(m_RecordButtonPath, m_RecordButtonText);
EditorGUILayout.PropertyField(m_PlayButtonPathProperty, m_PlayButtonText);
if (scope.changed)
serializedObject.ApplyModifiedProperties();
}
EditorGUILayout.Space();
using (new EditorGUILayout.HorizontalScope())
{
////TODO: go-to-next and go-to-previous button
// Play and pause buttons.
EditorGUI.BeginDisabledGroup(recorder.eventCount == 0 || recorder.captureIsRunning);
var oldIsPlaying = recorder.replayIsRunning;
var newIsPlaying = GUILayout.Toggle(oldIsPlaying, !oldIsPlaying ? m_PlayText : m_StopText, EditorStyles.miniButton,
GUILayout.Width(50));
if (oldIsPlaying != newIsPlaying)
{
if (newIsPlaying)
recorder.StartReplay();
else
recorder.StopReplay();
}
if (newIsPlaying && recorder.replay != null && GUILayout.Button(recorder.replay.paused ? m_ResumeText : m_PauseText, EditorStyles.miniButton,
GUILayout.Width(50)))
{
if (recorder.replay.paused)
recorder.StartReplay();
else
recorder.PauseReplay();
}
EditorGUI.EndDisabledGroup();
// Record button.
EditorGUI.BeginDisabledGroup(recorder.replayIsRunning);
var oldIsRecording = recorder.captureIsRunning;
var newIsRecording = GUILayout.Toggle(oldIsRecording, m_RecordText, EditorStyles.miniButton, GUILayout.Width(50));
if (oldIsRecording != newIsRecording)
{
if (newIsRecording)
recorder.StartCapture();
else
recorder.StopCapture();
}
EditorGUI.EndDisabledGroup();
// Load button.
EditorGUI.BeginDisabledGroup(recorder.replayIsRunning);
if (GUILayout.Button("Load"))
{
var filePath = EditorUtility.OpenFilePanel("Choose Input Event Trace to Load", string.Empty, "inputtrace");
if (!string.IsNullOrEmpty(filePath))
recorder.LoadCaptureFromFile(filePath);
}
EditorGUI.EndDisabledGroup();
// Save button.
EditorGUI.BeginDisabledGroup(recorder.eventCount == 0 || recorder.replayIsRunning);
if (GUILayout.Button("Save"))
{
var filePath = EditorUtility.SaveFilePanel("Choose Where to Save Input Event Trace", string.Empty, $"{recorder.gameObject.name}.inputtrace", "inputtrace");
if (!string.IsNullOrEmpty(filePath))
recorder.SaveCaptureToFile(filePath);
}
// Clear button.
if (GUILayout.Button("Clear"))
{
recorder.ClearCapture();
Repaint();
}
EditorGUI.EndDisabledGroup();
}
////TODO: allow hotscrubbing
// Play bar.
EditorGUILayout.IntSlider(recorder.replayPosition, 0, (int)recorder.eventCount);
EditorGUILayout.Space();
using (new EditorGUI.DisabledScope())
{
EditorGUILayout.LabelField(m_InfoText, EditorStyles.miniBoldLabel);
using (new EditorGUI.IndentLevelScope())
{
EditorGUILayout.LabelField($"{recorder.eventCount} events", EditorStyles.miniLabel);
EditorGUILayout.LabelField($"{recorder.totalEventSizeInBytes / 1024} kb captured", EditorStyles.miniLabel);
EditorGUILayout.LabelField($"{recorder.allocatedSizeInBytes / 1024} kb allocated", EditorStyles.miniLabel);
if (recorder.capture != null)
{
var devices = recorder.capture.deviceInfos;
if (devices.Count > 0)
{
EditorGUILayout.LabelField(m_DevicesText, EditorStyles.miniBoldLabel);
using (new EditorGUI.IndentLevelScope())
{
foreach (var device in devices)
{
EditorGUILayout.LabelField(device.layout, EditorStyles.miniLabel);
}
}
}
}
}
}
}
private bool m_AllInput;
private SerializedProperty m_DevicePathProperty;
private SerializedProperty m_RecordButtonPath;
private SerializedProperty m_PlayButtonPathProperty;
private SerializedProperty m_RecordFramesProperty;
private SerializedProperty m_RecordStateEventsOnlyProperty;
private SerializedProperty m_ReplayOnNewDevicesProperty;
private SerializedProperty m_SimulateTimingOnReplayProperty;
private SerializedProperty m_CaptureMemoryDefaultSizeProperty;
private SerializedProperty m_CaptureMemoryMaxSizeProperty;
private SerializedProperty m_StartRecordingWhenEnabledProperty;
private UnityAction<InputRecorder.Change> m_OnRecordEvent;
private GUIContent m_RecordButtonText = new GUIContent("Record Button", "If set, this button will start and stop capture in play mode.");
private GUIContent m_PlayButtonText = new GUIContent("Play Button", "If set, this button will start and stop replay of the current capture in play mode.");
private GUIContent m_RecordWhenEnabledText = new GUIContent("Capture When Enabled", "If true, recording will start immediately when the component is enabled in play mode.");
private GUIContent m_DevicesText = new GUIContent("Devices");
private GUIContent m_AllInputText = new GUIContent("All Input", "Whether to record input from all devices or from just specific devices.");
private GUIContent m_DeviceText = new GUIContent("Device", "Type of device to record input from.");
private GUIContent m_InfoText = new GUIContent("Info:");
private GUIContent m_DefaultSizeText = new GUIContent("Default Size (MB)", "Memory allocate for capture by default. Will automatically grow up to max memory.");
private GUIContent m_MaxSizeText = new GUIContent("Max Size (MB)", "Maximum memory allocated for capture. Once a capture reaches this limit, new events will start overwriting old ones.");
private GUIContent m_PlayText;
private GUIContent m_PauseText;
private GUIContent m_ResumeText;
private GUIContent m_StepForwardText;
private GUIContent m_StepBackwardText;
private GUIContent m_StopText;
private GUIContent m_RecordText;
}
}
#endif

Просмотреть файл

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0bb726b4db024f738c493b8132730571
timeCreated: 1574087726

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: da1a360c18546f34fac39b2c305ac2c9
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -0,0 +1,3 @@
This sample is both a demonstration for how to use [`InputEventTrace`](https://docs.unity3d.com/Packages/com.unity.inputsystem@latest/index.html?subfolder=/api/UnityEngine.InputSystem.LowLevel.InputEventTrace.html) as well as a useful tool by itself in the form of the [`InputRecorder`](./InputRecorder.cs) reusable `MonoBehaviour` component.
One possible way in which you can use this facility, for example, is to record input, save it to disk, and then replay the same input in automation (e.g. in tests or when recording short video snippets of preset gameplay sequences for manual proofing).

Просмотреть файл

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c7efc8d27cb74c05a04933d66f7f8062
timeCreated: 1574262802

Просмотреть файл

@ -0,0 +1,15 @@
{
"name": "Unity.InputSystem.Recorder",
"references": [
"GUID:75469ad4d38634e559750d17036d5f7c"
],
"optionalUnityReferences": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": []
}

Просмотреть файл

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 157556569ac93a94fb6a763f50476339
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -38,7 +38,7 @@ RenderSettings:
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 0}
m_IndirectSpecularColor: {r: 0.21890283, g: 0.16790575, b: 0.67034173, a: 1}
m_IndirectSpecularColor: {r: 0.21890257, g: 0.16790554, b: 0.67034054, a: 1}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
@ -132,7 +132,7 @@ PrefabInstance:
m_Modifications:
- target: {fileID: 4621998539424734916, guid: a6b33b41508134c09957e076f4d53415, type: 3}
propertyPath: m_RootOrder
value: 8
value: 9
objectReference: {fileID: 0}
- target: {fileID: 4621998539424734916, guid: a6b33b41508134c09957e076f4d53415, type: 3}
propertyPath: m_LocalPosition.x
@ -236,7 +236,7 @@ PrefabInstance:
m_Modifications:
- target: {fileID: 4621998539424734916, guid: a6b33b41508134c09957e076f4d53415, type: 3}
propertyPath: m_RootOrder
value: 7
value: 8
objectReference: {fileID: 0}
- target: {fileID: 4621998539424734916, guid: a6b33b41508134c09957e076f4d53415, type: 3}
propertyPath: m_LocalPosition.x
@ -472,7 +472,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 8944336655422409503, guid: f6d148d888ffbf54b9afe9936dfaec1f, type: 3}
propertyPath: m_RootOrder
value: 17
value: 18
objectReference: {fileID: 0}
- target: {fileID: 8944336655422409503, guid: f6d148d888ffbf54b9afe9936dfaec1f, type: 3}
propertyPath: m_LocalPosition.x
@ -516,6 +516,50 @@ PrefabInstance:
objectReference: {fileID: 0}
m_RemovedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: f6d148d888ffbf54b9afe9936dfaec1f, type: 3}
--- !u!1 &728378993
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 728378995}
- component: {fileID: 728378994}
m_Layer: 0
m_Name: ApplicationController
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &728378994
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 728378993}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6ae728ca564c42b8a6a2ee763f7a2741, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!4 &728378995
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 728378993}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 6.0964007, y: -0.3416402, z: -0.77773607}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &796975752
GameObject:
m_ObjectHideFlags: 0
@ -556,7 +600,7 @@ PrefabInstance:
m_Modifications:
- target: {fileID: 4621998539424734916, guid: a6b33b41508134c09957e076f4d53415, type: 3}
propertyPath: m_RootOrder
value: 6
value: 7
objectReference: {fileID: 0}
- target: {fileID: 4621998539424734916, guid: a6b33b41508134c09957e076f4d53415, type: 3}
propertyPath: m_LocalPosition.x
@ -665,7 +709,7 @@ Transform:
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 12
m_RootOrder: 13
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &986711052 stripped
GameObject:
@ -732,7 +776,7 @@ Transform:
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 1
m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &1038740740
MonoBehaviour:
@ -861,7 +905,7 @@ Transform:
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 0
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1265101692
GameObject:
@ -976,7 +1020,7 @@ Transform:
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 15
m_RootOrder: 16
m_LocalEulerAnglesHint: {x: 41.363, y: 45, z: 0}
--- !u!1 &1299635453
GameObject:
@ -1037,7 +1081,7 @@ Transform:
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 4
m_RootOrder: 5
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1368949943 stripped
GameObject:
@ -1071,7 +1115,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 2070925441746177912, guid: a1a802ecaf6775746bb2a929fb554ad8, type: 3}
propertyPath: m_RootOrder
value: 18
value: 19
objectReference: {fileID: 0}
- target: {fileID: 2070925441746177912, guid: a1a802ecaf6775746bb2a929fb554ad8, type: 3}
propertyPath: m_LocalPosition.x
@ -1186,7 +1230,7 @@ PrefabInstance:
m_Modifications:
- target: {fileID: 4621998539424734916, guid: a6b33b41508134c09957e076f4d53415, type: 3}
propertyPath: m_RootOrder
value: 9
value: 10
objectReference: {fileID: 0}
- target: {fileID: 4621998539424734916, guid: a6b33b41508134c09957e076f4d53415, type: 3}
propertyPath: m_LocalPosition.x
@ -1343,7 +1387,7 @@ Transform:
- {fileID: 1997149794}
- {fileID: 474653828}
m_Father: {fileID: 0}
m_RootOrder: 16
m_RootOrder: 17
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1001 &1869134603
PrefabInstance:
@ -1354,7 +1398,7 @@ PrefabInstance:
m_Modifications:
- target: {fileID: 4621998539424734916, guid: a6b33b41508134c09957e076f4d53415, type: 3}
propertyPath: m_RootOrder
value: 11
value: 12
objectReference: {fileID: 0}
- target: {fileID: 4621998539424734916, guid: a6b33b41508134c09957e076f4d53415, type: 3}
propertyPath: m_LocalPosition.x
@ -1415,7 +1459,7 @@ PrefabInstance:
m_Modifications:
- target: {fileID: 4621998539424734916, guid: a6b33b41508134c09957e076f4d53415, type: 3}
propertyPath: m_RootOrder
value: 10
value: 11
objectReference: {fileID: 0}
- target: {fileID: 4621998539424734916, guid: a6b33b41508134c09957e076f4d53415, type: 3}
propertyPath: m_LocalPosition.x
@ -1553,7 +1597,7 @@ Transform:
- {fileID: 1991538230}
- {fileID: 1773263767}
m_Father: {fileID: 0}
m_RootOrder: 3
m_RootOrder: 4
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1991538229
GameObject:
@ -1706,7 +1750,7 @@ Transform:
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 14
m_RootOrder: 15
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &2060465721
GameObject:
@ -1779,7 +1823,7 @@ Transform:
- {fileID: 1422708176}
- {fileID: 796975753}
m_Father: {fileID: 0}
m_RootOrder: 2
m_RootOrder: 3
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1001 &330446109092541879
PrefabInstance:
@ -1838,7 +1882,7 @@ PrefabInstance:
objectReference: {fileID: 2100000, guid: 80a1e6a5d1689444c8e2994a51639e98, type: 2}
- target: {fileID: 330446108690218393, guid: d793abe7ff9aa094eb534e73a82fdab5, type: 3}
propertyPath: m_RootOrder
value: 13
value: 14
objectReference: {fileID: 0}
- target: {fileID: 330446108690218393, guid: d793abe7ff9aa094eb534e73a82fdab5, type: 3}
propertyPath: m_LocalPosition.x
@ -2031,7 +2075,7 @@ PrefabInstance:
m_Modifications:
- target: {fileID: 4621998539424734916, guid: a6b33b41508134c09957e076f4d53415, type: 3}
propertyPath: m_RootOrder
value: 5
value: 6
objectReference: {fileID: 0}
- target: {fileID: 4621998539424734916, guid: a6b33b41508134c09957e076f4d53415, type: 3}
propertyPath: m_LocalPosition.x

Просмотреть файл

@ -0,0 +1,14 @@
using System;
using UnityEngine;
namespace Unity.Netcode.Samples
{
public class ApplicationController : MonoBehaviour
{
void Awake()
{
DontDestroyOnLoad(gameObject);
Application.runInBackground = true;
}
}
}

Просмотреть файл

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6ae728ca564c42b8a6a2ee763f7a2741
timeCreated: 1673282028

Просмотреть файл

@ -17,7 +17,7 @@ MonoBehaviour:
m_MaxEventBytesPerUpdate: 5242880
m_MaxQueuedEventsPerUpdate: 1000
m_CompensateForScreenOrientation: 0
m_BackgroundBehavior: 1
m_BackgroundBehavior: 2
m_EditorInputBehaviorInPlayMode: 0
m_DefaultDeadzoneMin: 0.125
m_DefaultDeadzoneMax: 0.925
@ -29,6 +29,7 @@ MonoBehaviour:
m_TapRadius: 5
m_MultiTapDelayTime: 0.75
m_DisableRedundantEventsMerging: 0
m_ShortcutKeysConsumeInputs: 0
m_iOSSettings:
m_MotionUsage:
m_Enabled: 0

Просмотреть файл

@ -0,0 +1,16 @@
{
"MonoBehaviour": {
"Version": 4,
"EnableBurstCompilation": true,
"EnableOptimisations": true,
"EnableSafetyChecks": false,
"EnableDebugInAllBuilds": false,
"UsePlatformSDKLinker": false,
"CpuMinTargetX32": 0,
"CpuMaxTargetX32": 0,
"CpuMinTargetX64": 0,
"CpuMaxTargetX64": 0,
"CpuTargetsX64": 72,
"OptimizeFor": 0
}
}