Skip to content

Commit e71492f

Browse files
authored
FIX: ISXB-1319 Input recorder game view focus (#2407)
1 parent 2255883 commit e71492f

5 files changed

Lines changed: 110 additions & 4 deletions

File tree

Packages/com.unity.inputsystem/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
99

1010
### Fixed
1111

12+
- Fixed `InputRecorder` playback not starting when the Game View is not focused in the Editor [ISXB-1319](https://jira.unity3d.com/browse/ISXB-1319)
1213
- Fixed a `NullReferenceException` thrown when removing all action maps [UUM-137116](https://jira.unity3d.com/browse/UUM-137116)
1314
- Simplified default setting messaging by consolidating repetitive messages into a single HelpBox.
1415
- Fixed a `NullPointerReferenceException` thrown in `InputManagerStateMonitors.FireStateChangeNotifications` logging by adding validation [UUM-136095].

Packages/com.unity.inputsystem/Documentation~/Events.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,9 @@ trace.Dispose();
223223

224224
Dispose event traces after use, so that they do not leak memory on the unmanaged (C++) memory heap.
225225

226+
> [!NOTE]
227+
> **Keyboard text input is not replayed to UI text fields.** Keyboard state (key presses) is captured and replayed correctly and remains accessible via `Keyboard.current`. However, there is a known limitation with character delivery to UI Framework components (uGUI `InputField` or UI Toolkit `TextField`). These components receive text through a separate native pipeline that is not fed by event replay. As a result, text typed into UI text fields during recording will not appear during playback.
228+
226229
You can also write event traces out to files/streams, load them back in, and replay recorded streams.
227230

228231
```CSharp

Packages/com.unity.inputsystem/InputSystem/Runtime/Events/InputEventTrace.cs

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1072,6 +1072,9 @@ public class ReplayController : IDisposable
10721072
private double m_StartTimeAsPerRuntime;
10731073
private int m_AllEventsByTimeIndex = 0;
10741074
private List<InputEventPtr> m_AllEventsByTime;
1075+
#if UNITY_EDITOR
1076+
private bool m_EditorEventPassthroughActive;
1077+
#endif
10751078

10761079
internal ReplayController(InputEventTrace trace)
10771080
{
@@ -1088,12 +1091,52 @@ public void Dispose()
10881091
{
10891092
InputSystem.onBeforeUpdate -= OnBeginFrame;
10901093
finished = true;
1091-
1094+
#if UNITY_EDITOR
1095+
StopEditorEventPassthrough();
1096+
#endif
10921097
foreach (var device in m_CreatedDevices)
10931098
InputSystem.RemoveDevice(device);
10941099
m_CreatedDevices = default;
10951100
}
10961101

1102+
#if UNITY_EDITOR
1103+
private void StartEditorEventPassthrough()
1104+
{
1105+
if (!m_EditorEventPassthroughActive)
1106+
{
1107+
m_EditorEventPassthroughActive = true;
1108+
InputManager.StartEditorEventPassthrough();
1109+
}
1110+
}
1111+
1112+
private void StopEditorEventPassthrough()
1113+
{
1114+
// Clean up any pending deferred stop.
1115+
InputSystem.onAfterUpdate -= DeferredStopEditorEventPassthrough;
1116+
1117+
if (m_EditorEventPassthroughActive)
1118+
{
1119+
m_EditorEventPassthroughActive = false;
1120+
InputManager.StopEditorEventPassthrough();
1121+
}
1122+
}
1123+
1124+
// Defers passthrough stop to after the current update so events already queued
1125+
// in the native buffer are still processed with passthrough active.
1126+
private void ScheduleStopEditorEventPassthrough()
1127+
{
1128+
if (!m_EditorEventPassthroughActive)
1129+
return;
1130+
InputSystem.onAfterUpdate += DeferredStopEditorEventPassthrough;
1131+
}
1132+
1133+
private void DeferredStopEditorEventPassthrough()
1134+
{
1135+
InputSystem.onAfterUpdate -= DeferredStopEditorEventPassthrough;
1136+
StopEditorEventPassthrough();
1137+
}
1138+
1139+
#endif
10971140
/// <summary>
10981141
/// Replay events recorded from <paramref name="recordedDevice"/> on device <paramref name="playbackDevice"/>.
10991142
/// </summary>
@@ -1249,6 +1292,9 @@ public ReplayController Rewind()
12491292
public ReplayController PlayAllFramesOneByOne()
12501293
{
12511294
finished = false;
1295+
#if UNITY_EDITOR
1296+
StartEditorEventPassthrough();
1297+
#endif
12521298
InputSystem.onBeforeUpdate += OnBeginFrame;
12531299
return this;
12541300
}
@@ -1267,6 +1313,9 @@ public ReplayController PlayAllFramesOneByOne()
12671313
public ReplayController PlayAllEvents()
12681314
{
12691315
finished = false;
1316+
#if UNITY_EDITOR
1317+
StartEditorEventPassthrough();
1318+
#endif
12701319
try
12711320
{
12721321
while (MoveNext(true, out var eventPtr))
@@ -1311,6 +1360,9 @@ public ReplayController PlayAllEventsAccordingToTimestamps()
13111360

13121361
// Start playback.
13131362
finished = false;
1363+
#if UNITY_EDITOR
1364+
StartEditorEventPassthrough();
1365+
#endif
13141366
m_StartTimeAsPerFirstEvent = -1;
13151367
m_AllEventsByTimeIndex = -1;
13161368
InputSystem.onBeforeUpdate += OnBeginFrame;
@@ -1381,6 +1433,11 @@ private void Finished()
13811433
{
13821434
finished = true;
13831435
InputSystem.onBeforeUpdate -= OnBeginFrame;
1436+
#if UNITY_EDITOR
1437+
// Defer passthrough stop so events already queued in the native buffer
1438+
// this frame are still processed with passthrough active.
1439+
ScheduleStopEditorEventPassthrough();
1440+
#endif
13841441
m_OnFinished?.Invoke();
13851442
}
13861443

Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -492,8 +492,45 @@ public bool runPlayerUpdatesInEditMode
492492
set => m_RunPlayerUpdatesInEditMode = value;
493493
}
494494

495+
/// <summary>
496+
/// Ref-counted flag that bypasses Game View focus gating for event processing.
497+
/// When greater than zero, events are processed as if the Game View has focus,
498+
/// regardless of actual focus state. This affects event routing, disabled-device
499+
/// discard, and UI module processing.
500+
/// </summary>
501+
/// <remarks>
502+
/// Use <see cref="StartEditorEventPassthrough"/> / <see cref="StopEditorEventPassthrough"/>
503+
/// to manage this counter. Follows the same pattern as
504+
/// <c>AssetDatabase.StartAssetEditing/StopAssetEditing</c>.
505+
/// </remarks>
506+
/// <seealso cref="StartEditorEventPassthrough"/>
507+
/// <seealso cref="StopEditorEventPassthrough"/>
508+
private int m_EditorEventPassthroughCount;
509+
510+
internal bool isEditorEventPassthroughActive => m_EditorEventPassthroughCount > 0;
511+
512+
/// <summary>
513+
/// Signals that events should bypass Game View focus gating. Ref-counted:
514+
/// each call must be balanced by a corresponding <see cref="StopEditorEventPassthrough"/>.
515+
/// </summary>
516+
internal static void StartEditorEventPassthrough()
517+
{
518+
++InputSystem.s_Manager.m_EditorEventPassthroughCount;
519+
}
520+
521+
/// <summary>
522+
/// Signals that the caller no longer needs events to bypass Game View focus gating.
523+
/// Decrements the ref count started by <see cref="StartEditorEventPassthrough"/>.
524+
/// </summary>
525+
internal static void StopEditorEventPassthrough()
526+
{
527+
if (InputSystem.s_Manager != null && InputSystem.s_Manager.m_EditorEventPassthroughCount > 0)
528+
--InputSystem.s_Manager.m_EditorEventPassthroughCount;
529+
}
530+
495531
#endif // UNITY_EDITOR
496532

533+
497534
private bool gameIsPlaying =>
498535
#if UNITY_EDITOR
499536
(m_Runtime.isInPlayMode && !m_Runtime.isEditorPaused) || m_RunPlayerUpdatesInEditMode;
@@ -504,7 +541,7 @@ public bool runPlayerUpdatesInEditMode
504541

505542
private bool gameHasFocus =>
506543
#if UNITY_EDITOR
507-
m_RunPlayerUpdatesInEditMode || applicationHasFocus || gameShouldGetInputRegardlessOfFocus;
544+
m_RunPlayerUpdatesInEditMode || applicationHasFocus || gameShouldGetInputRegardlessOfFocus || isEditorEventPassthroughActive;
508545
#else
509546
applicationHasFocus || gameShouldGetInputRegardlessOfFocus;
510547
#endif
@@ -3372,7 +3409,12 @@ private unsafe void ProcessEventBuffer(InputUpdateType updateType, ref InputEven
33723409

33733410
// If device is disabled, we let the event through only in certain cases.
33743411
// Removal and configuration change events should always be processed.
3412+
// During replay, allow events through for devices disabled due to background
3413+
// focus loss — the replay intentionally re-injects events for those devices.
33753414
if (device != null && !device.enabled &&
3415+
#if UNITY_EDITOR
3416+
!isEditorEventPassthroughActive &&
3417+
#endif
33763418
currentEventType != DeviceRemoveEvent.Type &&
33773419
currentEventType != DeviceConfigurationEvent.Type &&
33783420
(device.m_DeviceFlags & (InputDevice.DeviceFlags.DisabledInRuntime |
@@ -3411,7 +3453,6 @@ private unsafe void ProcessEventBuffer(InputUpdateType updateType, ref InputEven
34113453
#endif
34123454
if (!shouldProcess)
34133455
{
3414-
// Skip event if PreProcessEvent considers it to be irrelevant.
34153456
m_InputEventStream.Advance(false);
34163457
continue;
34173458
}

Packages/com.unity.inputsystem/InputSystem/Runtime/Plugins/UI/InputSystemUIInputModule.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -997,7 +997,11 @@ private bool shouldIgnoreFocus
997997
// if running in the background is enabled, we already have rules in place what kind of input
998998
// is allowed through and what isn't. And for the input that *IS* allowed through, the UI should
999999
// react.
1000-
get => explictlyIgnoreFocus || InputRuntime.s_Instance.runInBackground;
1000+
get => explictlyIgnoreFocus || InputRuntime.s_Instance.runInBackground
1001+
#if UNITY_EDITOR
1002+
|| InputSystem.s_Manager.isEditorEventPassthroughActive
1003+
#endif
1004+
;
10011005
}
10021006

10031007
/// <summary>

0 commit comments

Comments
 (0)