Preserve the first touch when the active input device changes
This commit is contained in:
@@ -31,9 +31,15 @@ namespace MagnetismHeroes.Tests
|
||||
Saved = Copy(save); return true;
|
||||
}
|
||||
}
|
||||
private GameApp _app; private MemoryStore _store; private Touchscreen _touchscreen;
|
||||
private GameApp _app; private MemoryStore _store; private Touchscreen _touchscreen, _otherTouchscreen;
|
||||
private InputSettings _originalInputSettings, _testInputSettings;
|
||||
private HideFlags _originalSettingsFlags;
|
||||
private readonly System.Collections.Generic.List<string> _touchTrace = new System.Collections.Generic.List<string>();
|
||||
private void TraceTouch(UnityEngine.InputSystem.EnhancedTouch.Finger finger)
|
||||
{
|
||||
var touch = finger.lastTouch;
|
||||
_touchTrace.Add($"{Time.frameCount},{finger.screen.deviceId},{Touchscreen.current?.deviceId},{touch.touchId},{touch.phase},{touch.screenPosition.x:R},{touch.screenPosition.y:R}");
|
||||
}
|
||||
[UnitySetUp] public IEnumerator SetUp()
|
||||
{
|
||||
// Batch tests have no focused Game View; route synthetic pointers into the actual player loop.
|
||||
@@ -44,6 +50,11 @@ namespace MagnetismHeroes.Tests
|
||||
_testInputSettings.editorInputBehaviorInPlayMode = InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView;
|
||||
_testInputSettings.backgroundBehavior = InputSettings.BackgroundBehavior.IgnoreFocus;
|
||||
InputSystem.settings = _testInputSettings;
|
||||
UnityEngine.InputSystem.EnhancedTouch.EnhancedTouchSupport.Enable();
|
||||
_touchTrace.Clear(); _touchTrace.Add("frame,sourceDevice,currentDevice,touchId,phase,pointerX,pointerY");
|
||||
UnityEngine.InputSystem.EnhancedTouch.Touch.onFingerDown += TraceTouch;
|
||||
UnityEngine.InputSystem.EnhancedTouch.Touch.onFingerMove += TraceTouch;
|
||||
UnityEngine.InputSystem.EnhancedTouch.Touch.onFingerUp += TraceTouch;
|
||||
foreach (var listener in Object.FindObjectsByType<AudioListener>(FindObjectsSortMode.None)) listener.enabled = false;
|
||||
foreach (var camera in Object.FindObjectsByType<Camera>(FindObjectsSortMode.None)) camera.enabled = false;
|
||||
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/MagnetismHeroes/UnityRuntime/Presentation/Prefabs/GameApp.prefab");
|
||||
@@ -52,7 +63,18 @@ namespace MagnetismHeroes.Tests
|
||||
}
|
||||
[UnityTearDown] public IEnumerator TearDown()
|
||||
{
|
||||
if (_touchscreen != null) InputSystem.RemoveDevice(_touchscreen); Object.Destroy(_app.gameObject);
|
||||
UnityEngine.InputSystem.EnhancedTouch.Touch.onFingerDown -= TraceTouch;
|
||||
UnityEngine.InputSystem.EnhancedTouch.Touch.onFingerMove -= TraceTouch;
|
||||
UnityEngine.InputSystem.EnhancedTouch.Touch.onFingerUp -= TraceTouch;
|
||||
UnityEngine.InputSystem.EnhancedTouch.EnhancedTouchSupport.Disable();
|
||||
var evidence = System.Environment.GetEnvironmentVariable("MAGNETISM_TOUCH_EVIDENCE");
|
||||
if (!string.IsNullOrEmpty(evidence) && _touchTrace.Count > 1)
|
||||
{
|
||||
System.IO.Directory.CreateDirectory(evidence);
|
||||
System.IO.File.WriteAllLines(System.IO.Path.Combine(evidence, TestContext.CurrentContext.Test.Name + ".csv"), _touchTrace);
|
||||
}
|
||||
if (_touchscreen != null) InputSystem.RemoveDevice(_touchscreen);
|
||||
if (_otherTouchscreen != null) InputSystem.RemoveDevice(_otherTouchscreen); Object.Destroy(_app.gameObject);
|
||||
InputSystem.settings = _originalInputSettings; _originalInputSettings.hideFlags = _originalSettingsFlags;
|
||||
Object.Destroy(_testInputSettings); yield return null;
|
||||
}
|
||||
@@ -71,6 +93,91 @@ namespace MagnetismHeroes.Tests
|
||||
private void QueueTouch(int id, UnityEngine.InputSystem.TouchPhase phase, Vector2 position) =>
|
||||
InputSystem.QueueStateEvent(_touchscreen, new TouchState { touchId = id, phase = phase, position = position });
|
||||
|
||||
[UnityTest] public IEnumerator FirstAndSecondDragAfterAnotherTouchscreenBothUseTheActualPointerDelta()
|
||||
{
|
||||
_touchscreen = InputSystem.AddDevice<Touchscreen>(); _otherTouchscreen = InputSystem.AddDevice<Touchscreen>();
|
||||
_app.StartStage(1); yield return null; Physics.SyncTransforms();
|
||||
InputSystem.QueueStateEvent(_otherTouchscreen, new TouchState { touchId = 201, phase = UnityEngine.InputSystem.TouchPhase.Began, position = new Vector2(5, 5) });
|
||||
InputSystem.QueueStateEvent(_otherTouchscreen, new TouchState { touchId = 201, phase = UnityEngine.InputSystem.TouchPhase.Ended, position = new Vector2(5, 5) });
|
||||
yield return null; yield return null;
|
||||
Assert.That(Touchscreen.current, Is.SameAs(_otherTouchscreen));
|
||||
var camera = JsonUtility.ToJson(_app.Session.Camera); var id = _app.Session.Board.Balls.Single().id;
|
||||
for (int attempt = 0; attempt < 2; attempt++)
|
||||
{
|
||||
Physics.SyncTransforms(); var start = BallPoint(); var end = start + new Vector2(-40, 0);
|
||||
var before = BoardPresenter.ToVector(_app.Session.Board.Balls.Single().position);
|
||||
var ray = _app.Presenter.Camera.ScreenPointToRay(start); Physics.Raycast(ray, out var hit, 100);
|
||||
var plane = new Plane(_app.Presenter.Camera.transform.forward, hit.point);
|
||||
Assert.That(plane.Raycast(ray, out var from), Is.True);
|
||||
var endRay = _app.Presenter.Camera.ScreenPointToRay(end); Assert.That(plane.Raycast(endRay, out var to), Is.True);
|
||||
var expected = before + endRay.GetPoint(to) - ray.GetPoint(from);
|
||||
QueueTouch(202 + attempt, UnityEngine.InputSystem.TouchPhase.Began, start);
|
||||
QueueTouch(202 + attempt, UnityEngine.InputSystem.TouchPhase.Moved, end);
|
||||
QueueTouch(202 + attempt, UnityEngine.InputSystem.TouchPhase.Ended, end);
|
||||
yield return null; yield return null;
|
||||
Assert.That(_app.Session.Board.Balls.Single().id, Is.EqualTo(id));
|
||||
Assert.That(Vector3.Distance(BoardPresenter.ToVector(_app.Session.Board.Balls.Single().position), expected), Is.LessThan(.001f), "Both the first and second real queued drag must retain Began and the pointer direction.");
|
||||
Assert.That(JsonUtility.ToJson(_app.Session.Camera), Is.EqualTo(camera)); Assert.That(_app.Session.Board.CountedCount, Is.Zero);
|
||||
yield return new WaitForSecondsRealtime(1.5f);
|
||||
}
|
||||
}
|
||||
|
||||
[UnityTest] public IEnumerator SwitchedTouchscreenBoundaryDragAndHeldReleaseCommitTheSameLastSafePosition()
|
||||
{
|
||||
_touchscreen = InputSystem.AddDevice<Touchscreen>(); _otherTouchscreen = InputSystem.AddDevice<Touchscreen>();
|
||||
_store.Saved.completedStageIds = new[] { 1 };
|
||||
for (int attempt = 0; attempt < 2; attempt++)
|
||||
{
|
||||
_store.Saved.latestFreePlay = new FreePlayState { sourceStageId = 1,
|
||||
balls = new[] { new BallState { id = "native-drag-0", position = new Position3(-6.8f, 4, 0) } },
|
||||
groups = new[] { new GroupState { id = "native-drag-group", ballIds = new[] { "native-drag-0" } } },
|
||||
camera = new CameraState { target = new Position3(-6.8f, 3.35f, 0), rotation = new Rotation4(0, 0, 0, 1), distance = 14 } };
|
||||
_app.Initialize(_store); _app.ContinueFreePlay(); yield return null; Physics.SyncTransforms();
|
||||
InputSystem.QueueStateEvent(_otherTouchscreen, new TouchState { touchId = 1, phase = UnityEngine.InputSystem.TouchPhase.Began, position = new Vector2(5, 5) });
|
||||
InputSystem.QueueStateEvent(_otherTouchscreen, new TouchState { touchId = 1, phase = UnityEngine.InputSystem.TouchPhase.Ended, position = new Vector2(5, 5) });
|
||||
yield return null; yield return null;
|
||||
var saved = JsonUtility.ToJson(_store.Saved); var camera = JsonUtility.ToJson(_app.Session.Camera);
|
||||
var start = BallPoint(); var end = start - new Vector2(Screen.width * 193f / 2400f, 0);
|
||||
QueueTouch(1, UnityEngine.InputSystem.TouchPhase.Began, start);
|
||||
QueueTouch(1, UnityEngine.InputSystem.TouchPhase.Moved, end);
|
||||
yield return null; yield return null;
|
||||
var preview = _app.Presenter.Balls.Single().Value.transform.position;
|
||||
Assert.That(preview.x, Is.EqualTo(-8).Within(.004f)); Assert.That(preview.y, Is.EqualTo(4).Within(.004f)); Assert.That(preview.z, Is.EqualTo(0).Within(.004f));
|
||||
yield return new WaitForSecondsRealtime(.7f);
|
||||
Assert.That(_app.Presenter.Balls.Single().Value.transform.position, Is.EqualTo(preview));
|
||||
Assert.That(JsonUtility.ToJson(_store.Saved), Is.EqualTo(saved), "Camera assistance and a held pointer cannot save a preview.");
|
||||
QueueTouch(1, UnityEngine.InputSystem.TouchPhase.Ended, end); yield return null; yield return null;
|
||||
Assert.That(_app.Session.Board.Balls.Single().id, Is.EqualTo("native-drag-0"));
|
||||
Assert.That(_app.Session.Board.Groups.Single().ballIds, Is.EqualTo(new[] { "native-drag-0" }));
|
||||
Assert.That(Vector3.Distance(BoardPresenter.ToVector(_app.Session.Board.Balls.Single().position), preview), Is.LessThan(.001f));
|
||||
Assert.That(JsonUtility.ToJson(_app.Session.Camera), Is.EqualTo(camera));
|
||||
Assert.That(_store.Saved.completedStageIds, Is.EqualTo(new[] { 1 })); Assert.That(_store.Saved.puzzleStates, Is.Empty);
|
||||
yield return new WaitForSecondsRealtime(1.5f);
|
||||
}
|
||||
}
|
||||
|
||||
[UnityTest] public IEnumerator AnotherTouchscreenWithTheSameIdCannotMovePinchOrEndTheHeldFriend()
|
||||
{
|
||||
_touchscreen = InputSystem.AddDevice<Touchscreen>(); _otherTouchscreen = InputSystem.AddDevice<Touchscreen>();
|
||||
_app.StartStage(1); yield return null; Physics.SyncTransforms();
|
||||
var start = BallPoint(); var saved = JsonUtility.ToJson(_store.Saved); var camera = JsonUtility.ToJson(_app.Session.Camera);
|
||||
QueueTouch(1, UnityEngine.InputSystem.TouchPhase.Began, start); yield return null; yield return null;
|
||||
QueueTouch(1, UnityEngine.InputSystem.TouchPhase.Moved, start + new Vector2(-40, 0)); yield return null; yield return null;
|
||||
var preview = _app.Presenter.Balls.Values.Single().transform.position;
|
||||
foreach (var phase in new[] { UnityEngine.InputSystem.TouchPhase.Began, UnityEngine.InputSystem.TouchPhase.Moved, UnityEngine.InputSystem.TouchPhase.Ended })
|
||||
{
|
||||
InputSystem.QueueStateEvent(_otherTouchscreen, new TouchState { touchId = 1, phase = phase, position = start + new Vector2(100, 30) });
|
||||
InputSystem.QueueStateEvent(_otherTouchscreen, new TouchState { touchId = 2, phase = phase, position = start + new Vector2(200, 30) });
|
||||
yield return null; yield return null;
|
||||
Assert.That(_app.Presenter.Balls.Values.Single().transform.position, Is.EqualTo(preview));
|
||||
Assert.That(_app.Orbit.IsAssisting, Is.True); Assert.That(JsonUtility.ToJson(_store.Saved), Is.EqualTo(saved));
|
||||
}
|
||||
QueueTouch(1, UnityEngine.InputSystem.TouchPhase.Canceled, start + new Vector2(-40, 0)); yield return null; yield return null;
|
||||
Assert.That(_app.Session.Board.CollectedBallIds, Is.Empty); Assert.That(JsonUtility.ToJson(_store.Saved), Is.EqualTo(saved));
|
||||
Assert.That(JsonUtility.ToJson(_app.Session.Camera), Is.EqualTo(camera));
|
||||
Assert.That(_app.Presenter.Balls.Values.Single().transform.position, Is.EqualTo(BoardPresenter.ToVector(_app.Session.Board.Balls.Single().position)));
|
||||
}
|
||||
|
||||
[UnityTest] public IEnumerator FailedMoveCameraAndPauseSavesShowAVisibleNoticeAndRecoveryClearsIt()
|
||||
{
|
||||
_app.StartStage(1); yield return null;
|
||||
|
||||
@@ -33,13 +33,15 @@ namespace MagnetismHeroes.UnityRuntime.Interaction
|
||||
private int _fingerId = -1;
|
||||
private bool _waitForAllTouchesReleased;
|
||||
private int _pinchFirstId = -1, _pinchSecondId = -1;
|
||||
private Touchscreen _touchscreen;
|
||||
private readonly struct TouchContact
|
||||
{
|
||||
public readonly Touchscreen Screen;
|
||||
public readonly int Id;
|
||||
public readonly UnityEngine.InputSystem.TouchPhase Phase;
|
||||
public readonly Vector2 Position;
|
||||
public TouchContact(int id, UnityEngine.InputSystem.TouchPhase phase, Vector2 position)
|
||||
{ Id = id; Phase = phase; Position = position; }
|
||||
public TouchContact(Touchscreen screen, int id, UnityEngine.InputSystem.TouchPhase phase, Vector2 position)
|
||||
{ Screen = screen; Id = id; Phase = phase; Position = position; }
|
||||
public bool InProgress => Phase == UnityEngine.InputSystem.TouchPhase.Began || Phase == UnityEngine.InputSystem.TouchPhase.Moved || Phase == UnityEngine.InputSystem.TouchPhase.Stationary;
|
||||
}
|
||||
private readonly Queue<TouchContact> _touchEvents = new Queue<TouchContact>();
|
||||
@@ -186,10 +188,11 @@ namespace MagnetismHeroes.UnityRuntime.Interaction
|
||||
|
||||
private void RecordTouch(Finger finger)
|
||||
{
|
||||
if (!AcceptInput || finger.screen != Touchscreen.current) return;
|
||||
if (!AcceptInput) return;
|
||||
// Copy each official history callback before later events overwrite the native touch slot.
|
||||
// Input System makes this device current AFTER its EnhancedTouch callbacks.
|
||||
var touch = finger.lastTouch;
|
||||
_touchEvents.Enqueue(new TouchContact(touch.touchId, touch.phase, touch.screenPosition));
|
||||
_touchEvents.Enqueue(new TouchContact(finger.screen, touch.touchId, touch.phase, touch.screenPosition));
|
||||
}
|
||||
|
||||
private void ProcessTouches()
|
||||
@@ -252,11 +255,19 @@ namespace MagnetismHeroes.UnityRuntime.Interaction
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!AcceptInput) { _touchEvents.Clear(); _touches.Clear(); return; }
|
||||
if (!AcceptInput) { _touchEvents.Clear(); _touches.Clear(); _touchscreen = null; return; }
|
||||
bool hadTouches = _touchEvents.Count > 0, suppressRestOfFrame = false;
|
||||
while (_touchEvents.Count > 0)
|
||||
{
|
||||
var touch = _touchEvents.Dequeue(); _touches[touch.Id] = touch;
|
||||
var touch = _touchEvents.Dequeue();
|
||||
if (_touchscreen == null)
|
||||
{
|
||||
if (touch.Phase != UnityEngine.InputSystem.TouchPhase.Began) continue;
|
||||
_touchscreen = touch.Screen;
|
||||
}
|
||||
// Touch IDs are local to a device; another device cannot replace a held finger.
|
||||
if (touch.Screen != _touchscreen) continue;
|
||||
_touches[touch.Id] = touch;
|
||||
if (!suppressRestOfFrame)
|
||||
{
|
||||
bool ownsGesture = touch.Id == _fingerId || (_gesture == Gesture.Pinch && (touch.Id == _pinchFirstId || touch.Id == _pinchSecondId));
|
||||
@@ -265,6 +276,7 @@ namespace MagnetismHeroes.UnityRuntime.Interaction
|
||||
ProcessTouches(); suppressRestOfFrame = ownsGesture && canceled;
|
||||
}
|
||||
if (!touch.InProgress) _touches.Remove(touch.Id);
|
||||
if (_touches.Count == 0) _touchscreen = null;
|
||||
}
|
||||
if (suppressRestOfFrame) _waitForAllTouchesReleased = _touches.Count > 0;
|
||||
if (hadTouches || _touches.Count > 0) return;
|
||||
@@ -284,13 +296,13 @@ namespace MagnetismHeroes.UnityRuntime.Interaction
|
||||
UnityEngine.InputSystem.EnhancedTouch.Touch.onFingerMove += RecordTouch;
|
||||
UnityEngine.InputSystem.EnhancedTouch.Touch.onFingerUp += RecordTouch;
|
||||
}
|
||||
private void OnApplicationPause(bool paused) { if (paused) { Cancel(); _touchEvents.Clear(); _touches.Clear(); } }
|
||||
private void OnApplicationPause(bool paused) { if (paused) { Cancel(); _touchEvents.Clear(); _touches.Clear(); _touchscreen = null; } }
|
||||
private void OnDisable()
|
||||
{
|
||||
UnityEngine.InputSystem.EnhancedTouch.Touch.onFingerDown -= RecordTouch;
|
||||
UnityEngine.InputSystem.EnhancedTouch.Touch.onFingerMove -= RecordTouch;
|
||||
UnityEngine.InputSystem.EnhancedTouch.Touch.onFingerUp -= RecordTouch;
|
||||
EnhancedTouchSupport.Disable(); Cancel(); _touchEvents.Clear(); _touches.Clear();
|
||||
EnhancedTouchSupport.Disable(); Cancel(); _touchEvents.Clear(); _touches.Clear(); _touchscreen = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user