Reject background penetration and preserve canceled touch ownership
This commit is contained in:
@@ -58,6 +58,59 @@ namespace MagnetismHeroes.Tests
|
||||
}
|
||||
private void Drag(Vector2 start, Vector2 end) { _app.Input.Begin(start, false); _app.Input.Move(end); _app.Input.End(end); }
|
||||
private void Tap(Vector2 point) { _app.Input.Begin(point, false); _app.Input.End(point); }
|
||||
private void DragTo(Vector3 destination)
|
||||
{
|
||||
Physics.SyncTransforms(); var start = BallPoint();
|
||||
var camera = _app.Presenter.Camera; Physics.Raycast(camera.ScreenPointToRay(start), out var hit, 100);
|
||||
var offset = hit.point - _app.Presenter.Balls.Values.Single().transform.position;
|
||||
Drag(start, camera.WorldToScreenPoint(destination + offset));
|
||||
}
|
||||
|
||||
[UnityTest] public IEnumerator PinchReleaseOutsideRestoresCameraAndSuppressesRemainingTouch()
|
||||
{
|
||||
_touchscreen = InputSystem.AddDevice<Touchscreen>(); _app.StartStage(1); yield return null; Physics.SyncTransforms();
|
||||
var point = BallPoint(); var second = point + new Vector2(90, 0); var initial = _app.Session.Camera.distance;
|
||||
InputSystem.QueueStateEvent(_touchscreen, new TouchState { touchId = 71, phase = UnityEngine.InputSystem.TouchPhase.Began, position = point });
|
||||
InputSystem.QueueStateEvent(_touchscreen, new TouchState { touchId = 72, phase = UnityEngine.InputSystem.TouchPhase.Began, position = second }); yield return null; yield return null;
|
||||
InputSystem.QueueStateEvent(_touchscreen, new TouchState { touchId = 72, phase = UnityEngine.InputSystem.TouchPhase.Moved, position = second + new Vector2(80, 0) }); yield return null; yield return null;
|
||||
Assert.That(_app.Orbit.Snapshot().distance, Is.LessThan(initial)); var saved = JsonUtility.ToJson(_store.Saved);
|
||||
InputSystem.QueueStateEvent(_touchscreen, new TouchState { touchId = 72, phase = UnityEngine.InputSystem.TouchPhase.Ended, position = new Vector2(-1, second.y) }); yield return null; yield return null;
|
||||
Assert.That(_app.Orbit.Snapshot().distance, Is.EqualTo(initial)); Assert.That(JsonUtility.ToJson(_store.Saved), Is.EqualTo(saved));
|
||||
InputSystem.QueueStateEvent(_touchscreen, new TouchState { touchId = 71, phase = UnityEngine.InputSystem.TouchPhase.Moved, position = point + new Vector2(70, 20) }); yield return null; yield return null;
|
||||
Assert.That(_app.Orbit.Snapshot().rotation, Is.EqualTo(_app.Session.Camera.rotation));
|
||||
Assert.That(_app.Session.Board.CollectedBallIds, Is.Empty);
|
||||
}
|
||||
|
||||
[UnityTest] public IEnumerator CanceledDragCannotBeInheritedByANewFingerInTheSameFrame()
|
||||
{
|
||||
_touchscreen = InputSystem.AddDevice<Touchscreen>(); _app.StartStage(1); yield return null; Physics.SyncTransforms();
|
||||
var point = BallPoint(); var initial = _app.Session.Board.Balls.Single().position;
|
||||
InputSystem.QueueStateEvent(_touchscreen, new TouchState { touchId = 81, phase = UnityEngine.InputSystem.TouchPhase.Began, position = point }); yield return null; yield return null;
|
||||
InputSystem.QueueStateEvent(_touchscreen, new TouchState { touchId = 81, phase = UnityEngine.InputSystem.TouchPhase.Moved, position = point + new Vector2(70, 20) }); yield return null; yield return null;
|
||||
var saved = JsonUtility.ToJson(_store.Saved);
|
||||
InputSystem.QueueStateEvent(_touchscreen, new TouchState { touchId = 81, phase = UnityEngine.InputSystem.TouchPhase.Canceled, position = point + new Vector2(70, 20) });
|
||||
InputSystem.QueueStateEvent(_touchscreen, new TouchState { touchId = 82, phase = UnityEngine.InputSystem.TouchPhase.Began, position = point + new Vector2(140, 30) }); yield return null; yield return null;
|
||||
Assert.That(_app.Presenter.Balls.Values.Single().transform.position, Is.EqualTo(new Vector3(initial.x, initial.y, initial.z)));
|
||||
InputSystem.QueueStateEvent(_touchscreen, new TouchState { touchId = 82, phase = UnityEngine.InputSystem.TouchPhase.Ended, position = point + new Vector2(140, 30) }); yield return null; yield return null;
|
||||
Assert.That(_app.Session.Board.Balls.Single().position, Is.EqualTo(initial)); Assert.That(_app.Session.Board.CollectedBallIds, Is.Empty);
|
||||
Assert.That(JsonUtility.ToJson(_store.Saved), Is.EqualTo(saved));
|
||||
}
|
||||
|
||||
[UnityTest] public IEnumerator BackgroundInteriorDropIsRejectedAndSurfaceContactRemainsPlayable()
|
||||
{
|
||||
_app.StartStage(1); yield return null; DragTo(new Vector3(1.8f, 0.8f, 0)); yield return null;
|
||||
Assert.That(_app.Session.Board.CollectedBallIds.Length, Is.EqualTo(1));
|
||||
_app.Orbit.Orbit(new Vector2(360, 0)); _app.Session.CommitCamera(_app.Orbit.Snapshot());
|
||||
DragTo(new Vector3(1.8f, 0.65f, 1.1f)); yield return null; Physics.SyncTransforms();
|
||||
var initial = _app.Session.Board.Balls.Single().position;
|
||||
Assert.That(initial.y, Is.EqualTo(0.65f).Within(0.001)); Assert.That(initial.z, Is.EqualTo(1.1f).Within(0.001)); BallPoint();
|
||||
var saved = JsonUtility.ToJson(_store.Saved); var camera = _app.Orbit.Snapshot();
|
||||
DragTo(new Vector3(1.8f, -0.4f, 1.1f)); yield return null;
|
||||
Assert.That(_app.Session.Board.Balls.Single().position, Is.EqualTo(initial), "The scene interior must not become a committed drop.");
|
||||
Assert.That(_app.Presenter.Balls.Values.Single().transform.position, Is.EqualTo(new Vector3(initial.x, initial.y, initial.z)));
|
||||
Assert.That(_app.Orbit.Snapshot().rotation, Is.EqualTo(camera.rotation)); Assert.That(JsonUtility.ToJson(_store.Saved), Is.EqualTo(saved));
|
||||
Physics.SyncTransforms(); BallPoint();
|
||||
}
|
||||
|
||||
[UnityTest] public IEnumerator InspectorButtonsNavigateAndHomeKeepsACameraAndListener()
|
||||
{
|
||||
|
||||
@@ -59,11 +59,14 @@ namespace MagnetismHeroes.UnityRuntime.Interaction
|
||||
|
||||
public void End(Vector2 point, bool canceled = false)
|
||||
{
|
||||
bool inside = point.x >= 0 && point.x < Screen.width && point.y >= 0 && point.y < Screen.height;
|
||||
if (canceled || !inside) { Cancel(); return; }
|
||||
if (canceled || !IsInside(point)) { Cancel(); return; }
|
||||
if (_gesture == Gesture.Friend)
|
||||
{
|
||||
if (_dragged) _app.CommitMove(_groupId, BoardPresenter.ToPosition(_previewDelta));
|
||||
if (_dragged)
|
||||
{
|
||||
if (!_presenter.CanDropGroup(_groupId)) { Cancel(); return; }
|
||||
_app.CommitMove(_groupId, BoardPresenter.ToPosition(_previewDelta));
|
||||
}
|
||||
else _app.CountBall(_ballId);
|
||||
}
|
||||
else if (_gesture == Gesture.Orbit && _dragged) _app.Session.CommitCamera(_orbit.Snapshot());
|
||||
@@ -97,23 +100,49 @@ namespace MagnetismHeroes.UnityRuntime.Interaction
|
||||
return hits.Count > 0;
|
||||
}
|
||||
|
||||
private static bool IsInside(Vector2 point) => point.x >= 0 && point.x < Screen.width && point.y >= 0 && point.y < Screen.height;
|
||||
|
||||
private static UnityEngine.InputSystem.Controls.TouchControl FindTouch(Touchscreen screen, int id)
|
||||
{
|
||||
foreach (var touch in screen.touches) if (touch.touchId.ReadValue() == id) return touch;
|
||||
return null;
|
||||
}
|
||||
|
||||
private void FinishSingleTouch(Touchscreen screen)
|
||||
{
|
||||
var touch = FindTouch(screen, _fingerId);
|
||||
if (touch == null || touch.phase.ReadValue() != UnityEngine.InputSystem.TouchPhase.Ended) { Cancel(); return; }
|
||||
var point = touch.position.ReadValue(); Move(point); End(point);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!AcceptInput) return;
|
||||
var touchscreen = Touchscreen.current;
|
||||
if (touchscreen != null)
|
||||
{
|
||||
if (_gesture == Gesture.Pinch)
|
||||
{
|
||||
foreach (var touch in touchscreen.touches)
|
||||
{
|
||||
var id = touch.touchId.ReadValue();
|
||||
if ((id == _pinchFirstId || id == _pinchSecondId) && touch.phase.ReadValue() == UnityEngine.InputSystem.TouchPhase.Canceled)
|
||||
{ Cancel(); _waitForAllTouchesReleased = true; return; }
|
||||
}
|
||||
}
|
||||
var active = new List<UnityEngine.InputSystem.Controls.TouchControl>();
|
||||
foreach (var touch in touchscreen.touches) if (touch.press.isPressed) active.Add(touch);
|
||||
if (_fingerId != -1 && !active.Exists(touch => touch.touchId.ReadValue() == _fingerId))
|
||||
{
|
||||
// Finish the original pointer before any new pointer can inherit its preview.
|
||||
FinishSingleTouch(touchscreen); _waitForAllTouchesReleased = active.Count > 0; return;
|
||||
}
|
||||
if (_gesture == Gesture.Pinch)
|
||||
{
|
||||
Vector2 release = _last; bool ended = false, canceled = false;
|
||||
foreach (int id in new[] { _pinchFirstId, _pinchSecondId })
|
||||
{
|
||||
var touch = FindTouch(touchscreen, id);
|
||||
if (touch == null) { canceled = true; continue; }
|
||||
var phase = touch.phase.ReadValue();
|
||||
if (phase == UnityEngine.InputSystem.TouchPhase.Canceled) canceled = true;
|
||||
if (phase == UnityEngine.InputSystem.TouchPhase.Ended)
|
||||
{ release = touch.position.ReadValue(); ended = true; canceled |= !IsInside(release); }
|
||||
}
|
||||
if (canceled || ended)
|
||||
{ End(release, canceled); _waitForAllTouchesReleased = active.Count > 0; return; }
|
||||
}
|
||||
// A canceled/finished pinch cannot restart when a third finger joins a remaining finger.
|
||||
if (_waitForAllTouchesReleased && _gesture == Gesture.None && active.Count > 0) return;
|
||||
if (active.Count >= 2)
|
||||
@@ -130,7 +159,6 @@ namespace MagnetismHeroes.UnityRuntime.Interaction
|
||||
}
|
||||
if (_waitForAllTouchesReleased)
|
||||
{
|
||||
if (_gesture == Gesture.Pinch) End(_last);
|
||||
if (active.Count == 0) { _waitForAllTouchesReleased = false; _gesture = Gesture.None; _fingerId = -1; }
|
||||
return;
|
||||
}
|
||||
@@ -144,16 +172,6 @@ namespace MagnetismHeroes.UnityRuntime.Interaction
|
||||
else Move(point);
|
||||
return;
|
||||
}
|
||||
if (_fingerId != -1)
|
||||
{
|
||||
bool canceled = false;
|
||||
Vector2 releasePoint = _last;
|
||||
foreach (var touch in touchscreen.touches)
|
||||
if (touch.touchId.ReadValue() == _fingerId)
|
||||
{ canceled = touch.phase.ReadValue() == UnityEngine.InputSystem.TouchPhase.Canceled; releasePoint = touch.position.ReadValue(); break; }
|
||||
if (!canceled) Move(releasePoint);
|
||||
End(releasePoint, canceled); return;
|
||||
}
|
||||
}
|
||||
var mouse = Mouse.current;
|
||||
if (mouse == null) return;
|
||||
|
||||
@@ -50,6 +50,26 @@ namespace MagnetismHeroes.UnityRuntime.Presentation
|
||||
PositionBadges();
|
||||
}
|
||||
|
||||
public bool CanDropGroup(string groupId)
|
||||
{
|
||||
Physics.SyncTransforms();
|
||||
foreach (var ball in _balls.Values.Where(ball => ball.GroupId == groupId))
|
||||
{
|
||||
var sphere = ball.GetComponent<SphereCollider>();
|
||||
var scale = sphere.transform.lossyScale;
|
||||
float radius = sphere.radius * Mathf.Max(Mathf.Abs(scale.x), Mathf.Abs(scale.y), Mathf.Abs(scale.z));
|
||||
foreach (var blocker in Physics.OverlapSphere(sphere.transform.TransformPoint(sphere.center), radius, ~0, QueryTriggerInteraction.Ignore))
|
||||
{
|
||||
// Board friends and their face/count decorations are not background obstacles.
|
||||
if (blocker.transform.IsChildOf(transform) || blocker.attachedRigidbody != null) continue;
|
||||
if (Physics.ComputePenetration(sphere, sphere.transform.position, sphere.transform.rotation,
|
||||
blocker, blocker.transform.position, blocker.transform.rotation, out _, out float depth) && depth > 0.001f)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void PositionBadges()
|
||||
{
|
||||
if (_board == null) return;
|
||||
|
||||
Reference in New Issue
Block a user