|
#if UNITY_IOS || UNITY_ANDROID || UNITY_EDITOR |
|
using UnityEngine; |
|
using System.Collections; |
|
using UnityEngine.InputSystem.EnhancedTouch; |
|
using UnityEngine.Events; |
|
|
|
public class SpringJointDrag2D_Touch : MonoBehaviour |
|
{ |
|
const int MAX_TOUCH = 10; |
|
|
|
/* Fallback SpringJoint2D config */ |
|
const float DISTANCE = 0.1f; |
|
const float DAMPING_RATIO = 1.0f; |
|
const float FREQUENCY = 1.5f; |
|
|
|
/* Recommended config for the connected */ |
|
const float RECOMMENDED_LINEAR_DRAG = 4.0f; |
|
const float RECOMMENDED_ANGULAR_DRAG = 4.0f; |
|
|
|
[Header("Connected config")] |
|
[SerializeField] LayerMask _DragLayerMask = 1 << 0; |
|
[SerializeField] bool _AnchorToCenterOfMass = false; |
|
[SerializeField] bool _UseRecommendedValues = false; |
|
|
|
[Space(20)] |
|
[SerializeField] SpringJoint2D[] _SpringJoints2D = new SpringJoint2D[MAX_TOUCH]; |
|
[Space] |
|
[SerializeField] LineRenderer[] _LineRenderers = new LineRenderer[MAX_TOUCH]; // Leave empty if you don't want to draw a LineRenderer |
|
[Space] |
|
public UnityEvent<Vector3>[] OnDrag = new UnityEvent<Vector3>[MAX_TOUCH]; |
|
[Space] |
|
public UnityEvent<Vector3>[] OnEndDrag = new UnityEvent<Vector3>[MAX_TOUCH]; |
|
|
|
Coroutine[] _TouchDragCoroutine = new Coroutine[MAX_TOUCH]; |
|
|
|
private void Awake() |
|
{ |
|
CameraConfigCheck(); |
|
|
|
SpringJointConfigCheck(); |
|
|
|
LineRenderersCheck(); |
|
} |
|
|
|
private void OnEnable() |
|
{ |
|
EnhancedTouchSupport.Enable(); |
|
UnityEngine.InputSystem.EnhancedTouch.Touch.onFingerDown += FingerDownAction; |
|
} |
|
|
|
private void OnDisable() |
|
{ |
|
UnityEngine.InputSystem.EnhancedTouch.Touch.onFingerDown -= FingerDownAction; |
|
EnhancedTouchSupport.Disable(); |
|
} |
|
|
|
#region Spring Joint 2D |
|
private void FingerDownAction(Finger finger) |
|
{ |
|
if (finger == null) |
|
{ |
|
Debug.LogWarning("Null finguer!"); |
|
return; |
|
} |
|
|
|
if (!finger.currentTouch.began) |
|
{ |
|
Debug.LogWarning($"Incorrect touch phase: <b>{finger.currentTouch.phase}</b>"); |
|
return; |
|
} |
|
|
|
if (finger.index < 0 || MAX_TOUCH <= finger.index) |
|
{ |
|
Debug.LogWarning($"Finger index <b>{finger.index}</b>: out of range."); |
|
return; |
|
} |
|
|
|
if (_SpringJoints2D.Length <= finger.index) |
|
{ |
|
Debug.LogWarning($"Increase the SpringJoints2D list to play with more than <b>{_SpringJoints2D.Length}</b> fingers"); |
|
return; |
|
} |
|
|
|
Vector3 fingerScreenPoint = finger.screenPosition; |
|
fingerScreenPoint.z = Camera.main.nearClipPlane; |
|
|
|
Vector3 fingerWorldPoint = Camera.main.ScreenToWorldPoint(fingerScreenPoint); |
|
|
|
Collider2D collider2dFound = Physics2D.OverlapPoint(fingerWorldPoint, _DragLayerMask); |
|
|
|
if (!collider2dFound) |
|
{ |
|
Debug.Log($"Nothing touched with finguer index <b>{finger.index}</b>"); |
|
return; |
|
} |
|
|
|
if (!collider2dFound.attachedRigidbody) |
|
{ |
|
Debug.Log($"The <b>{collider2dFound.name}</b> doesn't have a Rigidbody2D. Add this component to make the {this.GetType().Name} work."); |
|
return; |
|
} |
|
|
|
fingerWorldPoint.z = collider2dFound.transform.position.z; |
|
|
|
if (!_SpringJoints2D[finger.index]) |
|
{ |
|
Debug.LogError($"Unexpected unasigned SpringJoint2D at index <b>{finger.index}</b>"); |
|
return; |
|
} |
|
|
|
_SpringJoints2D[finger.index].connectedBody = collider2dFound.attachedRigidbody; |
|
_SpringJoints2D[finger.index].transform.position = fingerWorldPoint; |
|
|
|
if (_AnchorToCenterOfMass) // Set the connected anchor to the point of equilibrium |
|
{ |
|
_SpringJoints2D[finger.index].connectedAnchor = collider2dFound.attachedRigidbody.centerOfMass; |
|
} |
|
else // Set the connected anchor to the mouse position |
|
{ |
|
Vector2 nonRotatedAnchor = (Vector2)fingerWorldPoint - collider2dFound.attachedRigidbody.position; |
|
Vector2 rotatedAnchor = RotateAroundPivot(nonRotatedAnchor, Vector2.zero, -collider2dFound.attachedRigidbody.rotation); |
|
_SpringJoints2D[finger.index].connectedAnchor = rotatedAnchor; |
|
} |
|
|
|
if (_TouchDragCoroutine[finger.index] != null) |
|
{ |
|
Debug.LogError($"Finger index <b>{finger.index}</b>: Coroutine already working!"); |
|
return; |
|
} |
|
|
|
_TouchDragCoroutine[finger.index] = StartCoroutine(TouchSpringJoint2dDrag(_SpringJoints2D[finger.index], finger)); |
|
|
|
OnDrag[finger.index].Invoke(fingerWorldPoint); |
|
} |
|
|
|
private IEnumerator TouchSpringJoint2dDrag(SpringJoint2D springJoint2d, Finger finger) |
|
{ |
|
// Preserve the original body values |
|
float originalDrag = springJoint2d.connectedBody.drag; |
|
float originalAngularDrag = springJoint2d.connectedBody.angularDrag; |
|
|
|
// Use the new values while connected |
|
if (_UseRecommendedValues) |
|
{ |
|
springJoint2d.connectedBody.drag = RECOMMENDED_LINEAR_DRAG; |
|
springJoint2d.connectedBody.angularDrag = RECOMMENDED_ANGULAR_DRAG; |
|
} |
|
|
|
// Performance variables |
|
Camera cam = Camera.main; |
|
Vector3 fingerScreenPoint; |
|
Vector3 fingerWorldPoint = Vector3.zero; |
|
|
|
Vector2 connectedForcePosition; |
|
|
|
while (!finger.currentTouch.ended) |
|
{ |
|
fingerScreenPoint = finger.screenPosition; |
|
fingerScreenPoint.z = cam.nearClipPlane; |
|
|
|
fingerWorldPoint = cam.ScreenToWorldPoint(fingerScreenPoint); |
|
fingerWorldPoint.z = springJoint2d.transform.position.z; |
|
|
|
springJoint2d.transform.position = fingerWorldPoint; |
|
|
|
if (_AnchorToCenterOfMass) |
|
{ |
|
connectedForcePosition = springJoint2d.connectedBody.worldCenterOfMass; |
|
} |
|
else |
|
{ |
|
Vector2 nonRotatedPos = springJoint2d.connectedBody.position + springJoint2d.connectedAnchor; |
|
Vector2 rotatedPos = RotateAroundPivot(nonRotatedPos, springJoint2d.connectedBody.position, springJoint2d.connectedBody.rotation); |
|
connectedForcePosition = rotatedPos; |
|
} |
|
|
|
DrawLineRenderer(finger.index, connectedForcePosition, fingerWorldPoint); |
|
yield return null; |
|
} |
|
|
|
// Restore the original body values |
|
if (springJoint2d.connectedBody) |
|
{ |
|
springJoint2d.connectedBody.drag = originalDrag; |
|
springJoint2d.connectedBody.angularDrag = originalAngularDrag; |
|
springJoint2d.connectedBody = null; |
|
} |
|
|
|
ResetLineRenderer(finger.index); |
|
|
|
OnEndDrag[finger.index].Invoke(fingerWorldPoint); |
|
|
|
_TouchDragCoroutine[finger.index] = null; |
|
} |
|
#endregion |
|
|
|
#region Line renderer |
|
private void DrawLineRenderer(int index, Vector3 startPoint, Vector2 endPoint) |
|
{ |
|
if (_LineRenderers.Length <= index) |
|
{ |
|
return; |
|
} |
|
|
|
LineRenderer lineRenderer = _LineRenderers[index]; |
|
|
|
if (lineRenderer == null) |
|
{ |
|
return; |
|
} |
|
|
|
lineRenderer.positionCount = 2; |
|
|
|
lineRenderer.SetPosition(0, startPoint); |
|
lineRenderer.SetPosition(1, endPoint); |
|
} |
|
|
|
private void ResetLineRenderer(int index) |
|
{ |
|
if (_LineRenderers.Length <= index) |
|
{ |
|
return; |
|
} |
|
|
|
LineRenderer lineRenderer = _LineRenderers[index]; |
|
|
|
if (lineRenderer == null) |
|
{ |
|
return; |
|
} |
|
|
|
lineRenderer.positionCount = 0; |
|
} |
|
#endregion |
|
|
|
#region Configuration check |
|
private void CameraConfigCheck() |
|
{ |
|
if (!Camera.main) |
|
{ |
|
Debug.LogWarning($"Tag your Main Camera. Disabling <b>{this.GetType().Name}</b>"); |
|
this.enabled = false; |
|
return; |
|
} |
|
|
|
if (!Camera.main.orthographic) |
|
{ |
|
Debug.LogWarning($"2D physics only works with a camera with an <b>Orthographic</b> projection. Disabling <b>{this.GetType().Name}</b>"); |
|
this.enabled = false; |
|
return; |
|
} |
|
|
|
if (Camera.main.transform.rotation != Quaternion.identity) |
|
{ |
|
Debug.Log($"2D physics only works with a camera <b>without any rotation</b>. Disabling <b>{this.GetType().Name}</b>"); |
|
this.enabled = false; |
|
return; |
|
} |
|
} |
|
|
|
private void SpringJointConfigCheck() |
|
{ |
|
for (int i = 0; i < _SpringJoints2D.Length; i++) |
|
{ |
|
if (_SpringJoints2D[i] == null) // If there is no SpringJoint2D assigned, create a fallback (plan B) |
|
{ |
|
GameObject go = new GameObject($"Spring Joint 2D Fallback ({i})"); |
|
|
|
Rigidbody2D body = go.AddComponent<Rigidbody2D>(); |
|
body.isKinematic = true; |
|
|
|
_SpringJoints2D[i] = go.AddComponent<SpringJoint2D>(); |
|
_SpringJoints2D[i].autoConfigureDistance = false; |
|
_SpringJoints2D[i].autoConfigureConnectedAnchor = false; |
|
_SpringJoints2D[i].distance = DISTANCE; |
|
_SpringJoints2D[i].dampingRatio = DAMPING_RATIO; |
|
_SpringJoints2D[i].frequency = FREQUENCY; |
|
} |
|
else // Check the assigned |
|
{ |
|
Rigidbody2D rigidbody2D = _SpringJoints2D[i].GetComponent<Rigidbody2D>(); |
|
|
|
if (rigidbody2D) |
|
{ |
|
if (!rigidbody2D.isKinematic) |
|
{ |
|
rigidbody2D.isKinematic = true; |
|
Debug.LogWarning($"<b>{rigidbody2D.name}</b> needs a kinematic Rigidbody2D to work."); |
|
} |
|
} |
|
else |
|
{ |
|
rigidbody2D = _SpringJoints2D[i].gameObject.AddComponent<Rigidbody2D>(); |
|
rigidbody2D.isKinematic = true; |
|
Debug.LogWarning($"<b>{_SpringJoints2D[i].name}</b> need a Rigidbody2D to work."); |
|
} |
|
|
|
if (_SpringJoints2D[i].autoConfigureDistance) |
|
{ |
|
_SpringJoints2D[i].autoConfigureDistance = false; |
|
Debug.LogWarning($"Disabling autoConfigureDistance of <b>{_SpringJoints2D[i].name}</b>"); |
|
} |
|
|
|
if (_SpringJoints2D[i].autoConfigureConnectedAnchor) |
|
{ |
|
_SpringJoints2D[i].autoConfigureConnectedAnchor = false; |
|
Debug.LogWarning($"Disabling autoConfigureConnectedAnchor of <b>{_SpringJoints2D[i].name}</b>"); |
|
} |
|
} |
|
} |
|
} |
|
|
|
private void LineRenderersCheck() |
|
{ |
|
for (int i = 0; i < _LineRenderers.Length; i++) |
|
{ |
|
if (_LineRenderers[i] == null) |
|
{ |
|
Debug.Log($"No LineRenderer asigned in index <b>{i}</b>. The behaviour won't show it"); |
|
return; |
|
} |
|
|
|
if (!_LineRenderers[i].useWorldSpace) |
|
{ |
|
Debug.LogWarning($"Changing to WorldSpace on {_LineRenderers[i].name}"); |
|
_LineRenderers[i].useWorldSpace = true; |
|
} |
|
|
|
ResetLineRenderer(i); |
|
} |
|
} |
|
#endregion |
|
|
|
#region Utils |
|
private Vector2 RotateAroundPivot(Vector2 point, Vector2 pivot, float rotationDegrees) |
|
{ |
|
/* Anti-clockwise rotation */ |
|
|
|
// Convert the rotation angle from degrees to radians |
|
float rotationRadians = rotationDegrees * Mathf.Deg2Rad; |
|
|
|
// Calculate the sin and cos of the rotation angle |
|
float sinTheta = Mathf.Sin(rotationRadians); |
|
float cosTheta = Mathf.Cos(rotationRadians); |
|
|
|
// Calculate the new coordinates after rotating by the given angle |
|
float x = point.x - pivot.x; |
|
float y = point.y - pivot.y; |
|
|
|
float newX = x * cosTheta - y * sinTheta + pivot.x; |
|
float newY = x * sinTheta + y * cosTheta + pivot.y; |
|
|
|
return new Vector2(newX, newY); |
|
} |
|
#endregion |
|
} |
|
#endif |