Skip to content

Commit 07161d9

Browse files
committed
Added rough on screen control that do not depend on UI
1 parent a579bfa commit 07161d9

28 files changed

+1762
-24
lines changed

Assets/Samples/RebindingUI/Game/GameplayManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ void SpawnEnemy()
259259
--m_EnemySpawnCount;
260260

261261
// Rent an enemy from the enemy pool
262-
var enemyComponent = m_EnemyPool.Get();
262+
var enemyComponent = m_EnemyPool.Get(); // TODO Null reference here on domain reload
263263

264264
// Make the enemy spawn on border of visible game area
265265
var orthoSize = gameCamera.orthographicSize;

Assets/Samples/RebindingUI/OnScreen.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using UnityEngine.InputSystem.LowLevel;
2+
3+
namespace UnityEngine.InputSystem.Samples.RebindUI
4+
{
5+
class ActiveDetector : ITouchProcessor
6+
{
7+
private Vector2 m_InitialPosition = Vector2.zero;
8+
private double m_InitialTime = 0;
9+
private bool m_Valid = true;
10+
11+
public void OnTouchBegin(ChangeMonitor context, in TouchState[] touches, int count, int index)
12+
{
13+
if (count == 1)
14+
{
15+
m_InitialPosition = touches[index].position;
16+
m_InitialTime = Time.realtimeSinceStartupAsDouble;
17+
m_Valid = true;
18+
19+
GestureEvent @event = new GestureEvent(
20+
delta: Vector2.zero,
21+
duration: 0.0,
22+
flags: GestureFlags.Active | GestureFlags.PhaseStart,
23+
start: m_InitialPosition
24+
);
25+
context.FireEvent(in @event);
26+
}
27+
else
28+
{
29+
m_Valid = false;
30+
}
31+
}
32+
33+
public void OnTouchEnd(ChangeMonitor context, in TouchState[] touches, int count, int index)
34+
{
35+
if (m_Valid)
36+
{
37+
GestureEvent @event = new GestureEvent(
38+
delta: touches[index].position - m_InitialPosition,
39+
duration: Time.realtimeSinceStartupAsDouble - m_InitialTime,
40+
flags: GestureFlags.Active | GestureFlags.PhaseEnd, start: m_InitialPosition);
41+
context.FireEvent(in @event);
42+
}
43+
}
44+
45+
public void OnTouchMoved(ChangeMonitor context, in TouchState[] touches, int count, int index)
46+
{
47+
// Ignored
48+
}
49+
50+
public void OnTouchCanceled(ChangeMonitor context, in TouchState[] touches, int count, int index)
51+
{
52+
if (m_Valid)
53+
{
54+
m_Valid = false;
55+
56+
GestureEvent @event = new GestureEvent(
57+
delta: touches[index].position - m_InitialPosition,
58+
duration: Time.realtimeSinceStartupAsDouble - m_InitialTime,
59+
flags: GestureFlags.Active | GestureFlags.PhaseCancel,
60+
start: m_InitialPosition);
61+
context.FireEvent(in @event);
62+
}
63+
}
64+
}
65+
}

Assets/Samples/RebindingUI/OnScreen/ActiveDetector.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace UnityEngine.InputSystem.Samples.RebindUI
2+
{
3+
/// <summary>
4+
/// Specifies the geometric shape of the touch clipping area.
5+
/// </summary>
6+
public enum AreaShape
7+
{
8+
/// <summary>
9+
/// A rectangular clipping area is used (default).
10+
/// </summary>
11+
Rectangle = 0,
12+
}
13+
}

Assets/Samples/RebindingUI/OnScreen/AreaShape.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
using System;
2+
using System.Text;
3+
using Unity.Properties;
4+
using UnityEngine.InputSystem.LowLevel;
5+
using UnityEngine.InputSystem.OnScreen;
6+
7+
namespace UnityEngine.InputSystem.Samples.RebindUI
8+
{
9+
abstract class ChangeMonitor : ITouchMonitor
10+
{
11+
public delegate void GestureEventHandler(in GestureEvent gestureEvent); // ref readonly not available until C# 12.0
12+
13+
protected ChangeMonitor(int maxTouches)
14+
{
15+
m_Touches = new TouchState[maxTouches];
16+
Reset(Rect.zero, AreaShape.Rectangle, null);
17+
}
18+
19+
public virtual void Reset(Rect bounds, AreaShape shape, ChangeMonitor.GestureEventHandler handler)
20+
{
21+
m_Handler = handler;
22+
m_Bounds = bounds;
23+
m_GestureFlags = GestureFlags.None;
24+
m_Count = 0;
25+
}
26+
27+
protected bool Contains(in TouchState touch)
28+
{
29+
switch (m_Shape)
30+
{
31+
case AreaShape.Rectangle:
32+
{
33+
// TODO Caching opportunities here
34+
var display = Display.displays[touch.displayIndex];
35+
var width = display.renderingWidth;
36+
var height = display.renderingHeight;
37+
var absoluteBounds = new Rect(
38+
x: m_Bounds.xMin * width,
39+
y: m_Bounds.yMin * height,
40+
width: m_Bounds.width * width,
41+
height: m_Bounds.height * height);
42+
return absoluteBounds.Contains(touch.position);
43+
}
44+
}
45+
46+
return false;
47+
}
48+
49+
protected int FindTouch(int touchId)
50+
{
51+
for (var i = 0; i < m_Count; ++i)
52+
if (m_Touches[i].touchId == touchId)
53+
return i;
54+
return -1;
55+
}
56+
57+
public void FireEvent(in GestureEvent gestureEvent)
58+
{
59+
m_Handler.Invoke(in gestureEvent);
60+
}
61+
62+
public ref readonly TouchState this[int index] => ref m_Touches[index]; // TODO Make sure no defensive copy is created
63+
64+
//set => SetValue(key, value);
65+
protected Rect m_Bounds;
66+
protected AreaShape m_Shape;
67+
private GestureFlags m_GestureFlags;
68+
private GestureEventHandler m_Handler;
69+
protected readonly TouchState[] m_Touches;
70+
protected int m_Count;
71+
72+
public abstract void NotifyControlStateChanged(InputControl control, double time, InputEventPtr eventPtr,
73+
long monitorIndex);
74+
75+
public abstract void NotifyTimerExpired(InputControl control, double time, long monitorIndex,
76+
int timerIndex);
77+
}
78+
79+
// TODO This class is doing way too much error checking. Underlying implementation should be correct but
80+
// there seem to be some kind of bug resulting in duplicate callbacks.
81+
internal class ChangeMonitor<TProcessor> : ChangeMonitor, ITouchMonitor
82+
where TProcessor : ITouchProcessor
83+
{
84+
private TProcessor m_Processor;
85+
86+
public ChangeMonitor(int maxTouches, TProcessor processor)
87+
: base(maxTouches)
88+
{
89+
m_Processor = processor;
90+
}
91+
92+
/*public static ChangeMonitor<TProcessor> Create(int maxTouches, TProcessor processor)
93+
{
94+
return new ChangeMonitor<TProcessor>(maxTouches, processor);
95+
}*/
96+
97+
public override void Reset(Rect bounds, AreaShape shape, ChangeMonitor.GestureEventHandler handler)
98+
{
99+
// Make sure we cancel any pending touches. This way processor do not need a reset method.
100+
for (var i = m_Count; i != 0; --i)
101+
m_Processor.OnTouchCanceled(this, m_Touches, m_Count, m_Count - 1);
102+
103+
// Reset base
104+
base.Reset(bounds, shape, handler);
105+
}
106+
107+
public override void NotifyControlStateChanged(InputControl control, double time, InputEventPtr eventPtr, long monitorIndex)
108+
{
109+
// Ignore state change if it is not a touch state
110+
if (!OnScreenUnsafeHelpers.TryGetStateCopy<TouchState>(eventPtr, TouchState.Format, out var state))
111+
return;
112+
113+
// Intentionally left commented to aid debugging
114+
//Debug.Log($"monitorIndex: {monitorIndex}, id: {state.touchId}, phase: {state.phase}, position: {state.position}");
115+
116+
// Delegate touch phase events
117+
int index;
118+
switch (state.phase)
119+
{
120+
case TouchPhase.Began:
121+
// Only add the point for tracking if we do not violate max point constraint.
122+
if (FindTouch(state.touchId) < 0 && m_Count < m_Touches.Length && Contains(state))
123+
{
124+
index = m_Count++;
125+
m_Touches[index] = state;
126+
127+
m_Processor.OnTouchBegin(this, m_Touches, m_Count, index);
128+
}
129+
break;
130+
131+
case TouchPhase.Ended:
132+
index = FindTouch(state.touchId);
133+
if (index < 0)
134+
break;
135+
136+
m_Processor.OnTouchEnd(this, m_Touches, m_Count, index);
137+
138+
// Remove point
139+
if (index != m_Count - 1)
140+
{
141+
Array.Copy(m_Touches, index + 1,
142+
m_Touches, index, m_Count - index - 1);
143+
}
144+
--m_Count;
145+
break;
146+
147+
case TouchPhase.Canceled:
148+
index = FindTouch(state.touchId);
149+
if (index < 0)
150+
break;
151+
152+
m_Processor.OnTouchCanceled(this, m_Touches, m_Count, index);
153+
154+
// TODO Need to remove or will we get end?!
155+
break;
156+
157+
case TouchPhase.Moved:
158+
index = FindTouch(state.touchId);
159+
if (index < 0)
160+
{
161+
if (m_Count < m_Touches.Length && Contains(state))
162+
{
163+
// TODO Handle as an addition
164+
}
165+
166+
break; // Unexpected or not tracked by this processor
167+
}
168+
169+
m_Touches[index] = state;
170+
171+
// TODO Handle capture
172+
173+
m_Processor.OnTouchMoved(this, m_Touches, m_Count, index);
174+
break;
175+
176+
case TouchPhase.Stationary: // Not used by touchscreen
177+
case TouchPhase.None: // Default initialized (no meaning)
178+
default:
179+
break;
180+
}
181+
}
182+
183+
public override void NotifyTimerExpired(InputControl control, double time, long monitorIndex, int timerIndex) {}
184+
}
185+
}

Assets/Samples/RebindingUI/OnScreen/ChangeMonitor.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using System;
2+
3+
namespace UnityEngine.InputSystem.Samples.RebindUI
4+
{
5+
public enum PredefinedCurve
6+
{
7+
Linear,
8+
Quatratic,
9+
Cubic,
10+
}
11+
12+
public static class CurveExtensions
13+
{
14+
/// <summary>
15+
/// Apply response curve shaping to vlaue <paramref name="v"/>.
16+
/// </summary>
17+
/// <param name="v">The normalized value to be transformed.</param>
18+
/// <param name="curve">The response curve.</param>
19+
/// <returns>Transformed normalized value.</returns>
20+
/// <exception cref="ArgumentOutOfRangeException">If <paramref name="curve"/> is outside valid range.</exception>
21+
public static float Transform(float x, PredefinedCurve curve)
22+
{
23+
switch (curve)
24+
{
25+
case PredefinedCurve.Linear:
26+
return x;
27+
case PredefinedCurve.Quatratic:
28+
return x * x;
29+
case PredefinedCurve.Cubic:
30+
return x * x * x;
31+
default:
32+
throw new ArgumentOutOfRangeException(nameof(curve));
33+
}
34+
}
35+
36+
/// <summary>
37+
/// Apply response curve shaping to vector <paramref name="v"/>.
38+
/// </summary>
39+
/// <remarks>The provided vector is first transformed to polar form, then the vector length is scaled
40+
/// before it is finally converted back to euclidean space.</remarks>
41+
/// <param name="v">A normalized Euclidean vector to be transformed.</param>
42+
/// <param name="curve">The response curve.</param>
43+
/// <returns>Transformed normalized Euclidean vector.</returns>
44+
public static Vector2 Transform(Vector2 v, PredefinedCurve curve)
45+
{
46+
var r = Transform(v.magnitude, curve);
47+
var theta = Mathf.Atan2(v.y, v.x);
48+
return new Vector2(r * Mathf.Cos(theta), r * Mathf.Sin(theta));
49+
}
50+
}
51+
}

Assets/Samples/RebindingUI/OnScreen/Curve.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)