623 lines
15 KiB
C#
623 lines
15 KiB
C#
using UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
using System.Collections.Generic;
|
|
|
|
namespace CW.Common
|
|
{
|
|
/// <summary>This component combines finger and mouse and keyboard inputs into a single interface.</summary>
|
|
[HelpURL(CwShared.HelpUrlPrefix + "CwInputManager")]
|
|
[AddComponentMenu(CwShared.ComponentMenuPrefix + "Input Manager")]
|
|
public class CwInputManager : MonoBehaviour
|
|
{
|
|
public enum AxisGesture
|
|
{
|
|
HorizontalDrag,
|
|
VerticalDrag,
|
|
Twist,
|
|
HorizontalPull,
|
|
VerticalPull
|
|
}
|
|
|
|
[System.Serializable]
|
|
public struct Axis
|
|
{
|
|
public int FingerCount;
|
|
public bool FingerInvert;
|
|
public AxisGesture FingerGesture;
|
|
public float FingerSensitivity;
|
|
|
|
public KeyCode KeyNegative;
|
|
public KeyCode KeyPositive;
|
|
public KeyCode KeyNegativeAlt;
|
|
public KeyCode KeyPositiveAlt;
|
|
public float KeySensitivity;
|
|
|
|
public Axis(int fCount, bool fInvert, AxisGesture fGesture, float fSensitivty, KeyCode kNegative, KeyCode kPositive, KeyCode kNegativeAlt, KeyCode kPositiveAlt, float kSensitivity)
|
|
{
|
|
FingerCount = fCount;
|
|
FingerInvert = fInvert;
|
|
FingerGesture = fGesture;
|
|
FingerSensitivity = fSensitivty;
|
|
KeyNegative = kNegative;
|
|
KeyPositive = kPositive;
|
|
KeyNegativeAlt = kNegativeAlt;
|
|
KeyPositiveAlt = kPositiveAlt;
|
|
KeySensitivity = kSensitivity;
|
|
}
|
|
|
|
public float GetValue(float delta)
|
|
{
|
|
var value = 0.0f;
|
|
var fingers = GetFingers(true, true);
|
|
var scale = 1.0f;
|
|
|
|
value -= CwInput.GetKeyIsHeld(KeyNegative) == true ? KeySensitivity * delta : 0.0f;
|
|
value += CwInput.GetKeyIsHeld(KeyPositive) == true ? KeySensitivity * delta : 0.0f;
|
|
|
|
value -= CwInput.GetKeyIsHeld(KeyNegativeAlt) == true ? KeySensitivity * delta : 0.0f;
|
|
value += CwInput.GetKeyIsHeld(KeyPositiveAlt) == true ? KeySensitivity * delta : 0.0f;
|
|
|
|
if (FingerCount > 0 && fingers.Count == FingerCount)
|
|
{
|
|
if (FingerInvert == true && fingers[0].Index >= 0)
|
|
{
|
|
scale = -1.0f;
|
|
}
|
|
|
|
switch (FingerGesture)
|
|
{
|
|
case AxisGesture.HorizontalDrag: value += GetAverageDeltaScaled(fingers).x * FingerSensitivity * scale; break;
|
|
case AxisGesture.VerticalDrag: value += GetAverageDeltaScaled(fingers).y * FingerSensitivity * scale; break;
|
|
case AxisGesture.Twist: value += GetAverageTwistRadians(fingers) * FingerSensitivity; break;
|
|
case AxisGesture.HorizontalPull: value += GetAveragePullScaled(fingers).x * FingerSensitivity * delta * scale; break;
|
|
case AxisGesture.VerticalPull: value += GetAveragePullScaled(fingers).y * FingerSensitivity * delta * scale; break;
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
}
|
|
|
|
[System.Serializable]
|
|
public struct Trigger
|
|
{
|
|
public bool UseFinger;
|
|
public bool UseMouse;
|
|
public KeyCode UseKey;
|
|
|
|
public Trigger(bool uFinger, bool uMouse, KeyCode uKey)
|
|
{
|
|
UseFinger = uFinger;
|
|
UseMouse = uMouse;
|
|
UseKey = uKey;
|
|
}
|
|
|
|
public bool WentDown(Finger finger)
|
|
{
|
|
if (UseFinger == true && finger.Index >= 0 && finger.Down == true)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (UseMouse == true && finger.Index == MOUSE_FINGER_INDEX && finger.Down == true)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (UseKey != KeyCode.None && finger.Index == HOVER_FINGER_INDEX && CwInput.GetKeyWentDown(UseKey) == true)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public bool IsDown(Finger finger)
|
|
{
|
|
if (UseFinger == true && finger.Index >= 0 && finger.Up == false)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (UseMouse == true && finger.Index == MOUSE_FINGER_INDEX && finger.Up == false)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (UseKey != KeyCode.None && finger.Index == HOVER_FINGER_INDEX && CwInput.GetKeyIsHeld(UseKey) == true)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public bool WentUp(Finger finger, bool useAnyFinger = false)
|
|
{
|
|
if (useAnyFinger == true && finger.Up == true)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (UseFinger == true && finger.Index >= 0 && finger.Up == true)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (UseMouse == true && finger.Index == MOUSE_FINGER_INDEX && finger.Up == true)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (UseKey != KeyCode.None && finger.Index == HOVER_FINGER_INDEX && CwInput.GetKeyWentUp(UseKey) == true)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public abstract class Link
|
|
{
|
|
public Finger Finger;
|
|
|
|
public static T Find<T>(List<T> links, Finger finger)
|
|
where T : Link, new()
|
|
{
|
|
if (links != null)
|
|
{
|
|
foreach (var link in links)
|
|
{
|
|
if (link.Finger == finger)
|
|
{
|
|
return link;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public static T Create<T>(ref List<T> links, Finger finger)
|
|
where T : Link, new()
|
|
{
|
|
var link = Find(links, finger);
|
|
|
|
if (link == null)
|
|
{
|
|
if (links == null)
|
|
{
|
|
links = new List<T>();
|
|
}
|
|
|
|
link = new T();
|
|
|
|
link.Finger = finger;
|
|
|
|
links.Add(link);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("Link already exists!");
|
|
}
|
|
|
|
return link;
|
|
}
|
|
|
|
public static void ClearAll<T>(List<T> links)
|
|
where T : Link
|
|
{
|
|
if (links != null)
|
|
{
|
|
foreach (var link in links)
|
|
{
|
|
link.Clear();
|
|
}
|
|
|
|
links.Clear();
|
|
}
|
|
}
|
|
|
|
public static void ClearAndRemove<T>(List<T> links, T link)
|
|
where T : Link
|
|
{
|
|
if (link != null)
|
|
{
|
|
link.Clear();
|
|
|
|
if (links != null)
|
|
{
|
|
links.Remove(link);
|
|
}
|
|
}
|
|
}
|
|
|
|
public virtual void Clear()
|
|
{
|
|
}
|
|
}
|
|
|
|
public class Finger
|
|
{
|
|
public int Index;
|
|
public float Pressure;
|
|
public bool Down;
|
|
public bool Up;
|
|
public float Age;
|
|
public bool StartedOverGui;
|
|
public Vector2 StartScreenPosition;
|
|
public Vector2 ScreenPosition;
|
|
public Vector2 ScreenPositionOld;
|
|
public Vector2 ScreenPositionOldOld;
|
|
public Vector2 ScreenPositionOldOldOld;
|
|
|
|
public float SmoothScreenPositionDelta
|
|
{
|
|
get
|
|
{
|
|
if (Up == false)
|
|
{
|
|
return Vector2.Distance(ScreenPositionOldOld, ScreenPositionOld);
|
|
}
|
|
|
|
return Vector2.Distance(ScreenPositionOldOld, ScreenPosition);
|
|
}
|
|
}
|
|
|
|
public Vector2 GetSmoothScreenPosition(float t)
|
|
{
|
|
if (Up == false)
|
|
{
|
|
return Hermite(ScreenPositionOldOldOld, ScreenPositionOldOld, ScreenPositionOld, ScreenPosition, t);
|
|
}
|
|
|
|
return Vector2.LerpUnclamped(ScreenPositionOldOld, ScreenPosition, t);
|
|
}
|
|
}
|
|
|
|
/// <summary>Fingers that began touching the screen on top of these UI layers will be ignored.</summary>
|
|
public LayerMask GuiLayers { set { guiLayers = value; } get { return guiLayers; } } [SerializeField] private LayerMask guiLayers = 1 << 5;
|
|
|
|
/// <summary>This event will tell you when a finger begins touching the screen.</summary>
|
|
public static event System.Action<Finger> OnFingerDown;
|
|
|
|
/// <summary>This event will tell you when a finger has begun, is, or has just stopped touching the screen.</summary>
|
|
public static event System.Action<Finger> OnFingerUpdate;
|
|
|
|
/// <summary>This event will tell you when a finger stops touching the screen.</summary>
|
|
public static event System.Action<Finger> OnFingerUp;
|
|
|
|
public const int MOUSE_FINGER_INDEX = -1;
|
|
|
|
public const int HOVER_FINGER_INDEX = -1337;
|
|
|
|
private static List<RaycastResult> tempRaycastResults = new List<RaycastResult>(10);
|
|
|
|
private static PointerEventData tempPointerEventData;
|
|
|
|
private static EventSystem tempEventSystem;
|
|
|
|
private static List<Finger> fingers = new List<Finger>();
|
|
|
|
private static List<Finger> filteredFingers = new List<Finger>();
|
|
|
|
private static Stack<Finger> pool = new Stack<Finger>();
|
|
|
|
public static List<Finger> Fingers
|
|
{
|
|
get
|
|
{
|
|
return fingers;
|
|
}
|
|
}
|
|
|
|
public static float ScaleFactor
|
|
{
|
|
get
|
|
{
|
|
var dpi = Screen.dpi;
|
|
|
|
if (dpi <= 0)
|
|
{
|
|
dpi = 200.0f;
|
|
}
|
|
|
|
return 200.0f / dpi;
|
|
}
|
|
}
|
|
|
|
public static List<Finger> GetFingers(bool ignoreStartedOverGui = false, bool ignoreHover = false)
|
|
{
|
|
filteredFingers.Clear();
|
|
|
|
foreach (var finger in fingers)
|
|
{
|
|
if (ignoreStartedOverGui == true && finger.StartedOverGui == true)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (ignoreHover == true && finger.Index == HOVER_FINGER_INDEX)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
filteredFingers.Add(finger);
|
|
}
|
|
|
|
return filteredFingers;
|
|
}
|
|
|
|
public static bool PointOverGui(Vector2 screenPosition, int guiLayers = 1 << 5)
|
|
{
|
|
return RaycastGui(screenPosition, guiLayers).Count > 0;
|
|
}
|
|
|
|
/// <summary>This method gives you all UI elements under the specified screen position, where element 0 is the first/top one.</summary>
|
|
public static List<RaycastResult> RaycastGui(Vector2 screenPosition, int guiLayers = 1 << 5)
|
|
{
|
|
tempRaycastResults.Clear();
|
|
|
|
var currentEventSystem = EventSystem.current;
|
|
|
|
if (currentEventSystem != null)
|
|
{
|
|
// Create point event data for this event system?
|
|
if (currentEventSystem != tempEventSystem)
|
|
{
|
|
tempEventSystem = currentEventSystem;
|
|
|
|
if (tempPointerEventData == null)
|
|
{
|
|
tempPointerEventData = new PointerEventData(tempEventSystem);
|
|
}
|
|
else
|
|
{
|
|
tempPointerEventData.Reset();
|
|
}
|
|
}
|
|
|
|
// Raycast event system at the specified point
|
|
tempPointerEventData.position = screenPosition;
|
|
|
|
currentEventSystem.RaycastAll(tempPointerEventData, tempRaycastResults);
|
|
|
|
// Loop through all results and remove any that don't match the layer mask
|
|
if (tempRaycastResults.Count > 0)
|
|
{
|
|
for (var i = tempRaycastResults.Count - 1; i >= 0; i--)
|
|
{
|
|
var raycastResult = tempRaycastResults[i];
|
|
var raycastLayer = 1 << raycastResult.gameObject.layer;
|
|
|
|
if ((raycastLayer & guiLayers) == 0)
|
|
{
|
|
tempRaycastResults.RemoveAt(i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return tempRaycastResults;
|
|
}
|
|
|
|
public static Vector2 GetAveragePosition(List<Finger> fingers)
|
|
{
|
|
var total = Vector2.zero;
|
|
|
|
foreach (var finger in fingers)
|
|
{
|
|
total += finger.ScreenPosition;
|
|
}
|
|
|
|
return fingers.Count == 0 ? total : total / fingers.Count;
|
|
}
|
|
|
|
public static Vector2 GetAverageOldPosition(List<Finger> fingers)
|
|
{
|
|
var total = Vector2.zero;
|
|
|
|
foreach (var finger in fingers)
|
|
{
|
|
total += finger.ScreenPositionOld;
|
|
}
|
|
|
|
return fingers.Count == 0 ? total : total / fingers.Count;
|
|
}
|
|
|
|
public static Vector2 GetAveragePullScaled(List<Finger> fingers)
|
|
{
|
|
var total = Vector2.zero;
|
|
|
|
foreach (var finger in fingers)
|
|
{
|
|
total += finger.ScreenPosition - finger.StartScreenPosition;
|
|
}
|
|
|
|
return fingers.Count == 0 ? total : total * ScaleFactor / fingers.Count;
|
|
}
|
|
|
|
public static Vector2 GetAverageDeltaScaled(List<Finger> fingers)
|
|
{
|
|
var total = Vector2.zero;
|
|
|
|
foreach (var finger in fingers)
|
|
{
|
|
total += finger.ScreenPosition - finger.ScreenPositionOld;
|
|
}
|
|
|
|
return fingers.Count == 0 ? total : total * ScaleFactor / fingers.Count;
|
|
}
|
|
|
|
public static float GetAverageTwistRadians(List<Finger> fingers)
|
|
{
|
|
var total = 0.0f;
|
|
var center = GetAveragePosition(fingers);
|
|
var oldCenter = GetAverageOldPosition(fingers);
|
|
|
|
foreach (var finger in fingers)
|
|
{
|
|
total += GetDeltaRadians(finger, center, oldCenter);
|
|
}
|
|
|
|
return fingers.Count == 0 ? total : total / fingers.Count;
|
|
}
|
|
|
|
/// <summary>If your component uses this component, then make sure you call this method at least once before you use it (e.g. from <b>Awake</b>).</summary>
|
|
public static void EnsureThisComponentExists()
|
|
{
|
|
if (Application.isPlaying == true && CwHelper.FindAnyObjectByType<CwInputManager>() == null)
|
|
{
|
|
new GameObject(typeof(CwInputManager).Name).AddComponent<CwInputManager>();
|
|
}
|
|
}
|
|
|
|
protected virtual void Update()
|
|
{
|
|
// Remove previously up fingers, or mark them as up in case the up event isn't read correctly
|
|
for (var i = fingers.Count - 1; i >= 0; i--)
|
|
{
|
|
var finger = fingers[i];
|
|
|
|
if (finger.Up == true)
|
|
{
|
|
fingers.RemoveAt(i); pool.Push(finger);
|
|
}
|
|
else
|
|
{
|
|
finger.Up = true;
|
|
}
|
|
}
|
|
|
|
// Update real fingers
|
|
if (CwInput.GetTouchCount() > 0)
|
|
{
|
|
for (var i = 0; i < CwInput.GetTouchCount(); i++)
|
|
{
|
|
int id; Vector2 position; float pressure; bool set;
|
|
|
|
CwInput.GetTouch(i, out id, out position, out pressure, out set);
|
|
|
|
AddFinger(id, position, pressure, set);
|
|
}
|
|
}
|
|
// If there are no real touches, simulate some from the mouse?
|
|
else if (CwInput.GetMouseExists() == true)
|
|
{
|
|
var mouseSet = false;
|
|
var mouseUp = false;
|
|
|
|
for (var i = 0; i < 5; i++)
|
|
{
|
|
mouseSet |= CwInput.GetMouseIsHeld(i);
|
|
mouseUp |= CwInput.GetMouseWentUp(i);
|
|
}
|
|
|
|
AddFinger(HOVER_FINGER_INDEX, CwInput.GetMousePosition(), 0.0f, true);
|
|
|
|
if (mouseSet == true || mouseUp == true)
|
|
{
|
|
AddFinger(MOUSE_FINGER_INDEX, CwInput.GetMousePosition(), 1.0f, mouseSet);
|
|
}
|
|
}
|
|
|
|
// Events
|
|
foreach (var finger in fingers)
|
|
{
|
|
if (finger.Down == true && OnFingerDown != null) OnFingerDown .Invoke(finger);
|
|
if ( OnFingerUpdate != null) OnFingerUpdate.Invoke(finger);
|
|
if (finger.Up == true && OnFingerUp != null) OnFingerUp .Invoke(finger);
|
|
}
|
|
}
|
|
|
|
private Finger FindFinger(int index)
|
|
{
|
|
foreach (var finger in fingers)
|
|
{
|
|
if (finger.Index == index)
|
|
{
|
|
return finger;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private void AddFinger(int index, Vector2 screenPosition, float pressure, bool set)
|
|
{
|
|
var finger = FindFinger(index);
|
|
|
|
if (finger == null)
|
|
{
|
|
finger = pool.Count > 0 ? pool.Pop() : new Finger();
|
|
|
|
finger.Index = index;
|
|
finger.Down = true;
|
|
finger.Age = 0.0f;
|
|
|
|
finger.StartedOverGui = PointOverGui(screenPosition, guiLayers);
|
|
finger.StartScreenPosition = screenPosition;
|
|
finger.ScreenPositionOld = screenPosition;
|
|
finger.ScreenPositionOldOld = screenPosition;
|
|
finger.ScreenPositionOldOldOld = screenPosition;
|
|
|
|
fingers.Add(finger);
|
|
}
|
|
else
|
|
{
|
|
finger.Down = false;
|
|
finger.Age += Time.deltaTime;
|
|
|
|
finger.ScreenPositionOldOldOld = finger.ScreenPositionOldOld;
|
|
finger.ScreenPositionOldOld = finger.ScreenPositionOld;
|
|
finger.ScreenPositionOld = finger.ScreenPosition;
|
|
}
|
|
|
|
finger.Pressure = pressure;
|
|
finger.ScreenPosition = screenPosition;
|
|
finger.Up = set == false;
|
|
}
|
|
|
|
private static Vector2 Hermite(Vector2 a, Vector2 b, Vector2 c, Vector2 d, float t)
|
|
{
|
|
var mu2 = t * t;
|
|
var mu3 = mu2 * t;
|
|
var x = HermiteInterpolate(a.x, b.x, c.x, d.x, t, mu2, mu3);
|
|
var y = HermiteInterpolate(a.y, b.y, c.y, d.y, t, mu2, mu3);
|
|
|
|
return new Vector2(x, y);
|
|
}
|
|
|
|
private static float HermiteInterpolate(float y0,float y1, float y2,float y3, float mu, float mu2, float mu3)
|
|
{
|
|
var m0 = (y1 - y0) * 0.5f + (y2 - y1) * 0.5f;
|
|
var m1 = (y2 - y1) * 0.5f + (y3 - y2) * 0.5f;
|
|
var a0 = 2.0f * mu3 - 3.0f * mu2 + 1.0f;
|
|
var a1 = mu3 - 2.0f * mu2 + mu;
|
|
var a2 = mu3 - mu2;
|
|
var a3 = -2.0f * mu3 + 3.0f * mu2;
|
|
|
|
return(a0*y1+a1*m0+a2*m1+a3*y2);
|
|
}
|
|
|
|
private static float GetRadians(Vector2 screenPosition, Vector2 referencePoint)
|
|
{
|
|
return Mathf.Atan2(screenPosition.x - referencePoint.x, screenPosition.y - referencePoint.y);
|
|
}
|
|
|
|
private static float GetDeltaRadians(Finger finger, Vector2 referencePoint, Vector2 lastReferencePoint)
|
|
{
|
|
var a = GetRadians(finger.ScreenPositionOld, lastReferencePoint);
|
|
var b = GetRadians(finger.ScreenPosition, referencePoint);
|
|
var d = Mathf.Repeat(a - b, Mathf.PI * 2.0f);
|
|
|
|
if (d > Mathf.PI)
|
|
{
|
|
d -= Mathf.PI * 2.0f;
|
|
}
|
|
|
|
return d;
|
|
}
|
|
}
|
|
} |