|
using UnityEngine; |
|
using UnityEngine.InputSystem; |
|
|
|
public class InputManager : MonoBehaviour |
|
{ |
|
// the class generates from the InputActions asset (your name can vary) |
|
private TouchInputs touchInputs; |
|
// tracks which object is currently actively receiving touch input |
|
private InputReceiver currentReceiver; |
|
// the last touch position that was recorded |
|
private Vector2 lastTouchPosition; |
|
|
|
private void Awake() { |
|
touchInputs = new(); |
|
} |
|
|
|
private void Start() { |
|
// we subscribe to the start/cancel events of the Button action |
|
touchInputs.CoreActions.PrimaryTouch.started += OnPrimaryTouchStarted; |
|
touchInputs.CoreActions.PrimaryTouch.canceled += OnPrimaryTouchCancelled; |
|
// and the performed event of the position tracking PassThrough Value action |
|
touchInputs.CoreActions.PrimaryTouchPosition.performed += OnPrimaryTouchPositionChanged; |
|
|
|
// NOTE: The PassThrough action will not raise the started/cancelled events |
|
} |
|
|
|
private void OnEnable() => touchInputs.Enable(); |
|
|
|
private void OnDisable() => touchInputs.Disable(); |
|
|
|
// Here we check if we have a current reciever, and if not ray-cast the touch position in screen-space to locate |
|
// which object should be notified it is recieving touch inputs. Since we use Physics.Raycast() the object MUST |
|
// have a collider. We also return whether this is the first touch of the new object or not. |
|
private InputReceiver FindTouchReceiver(Vector2 touchPosition, Camera raycastCamera, out bool isFirstTouch) { |
|
// if we already have a receiver, return it |
|
// NOTE: We may need to reset this in some cases if the receiver is destroyed or |
|
// if there was a focus change in the game (or the app was minimized/suspended). |
|
if (currentReceiver) { |
|
// TODO: Correctly handle focus loss and re-acquisition and destroyed objects |
|
isFirstTouch = false; |
|
return currentReceiver; |
|
} |
|
|
|
isFirstTouch = true; |
|
if (raycastCamera) { |
|
Ray ray = raycastCamera.ScreenPointToRay(touchPosition); |
|
if (Physics.Raycast(ray, out var hit)) { |
|
return hit.transform.GetComponent<InputReceiver>(); |
|
} |
|
} else { |
|
Debug.Log("No camera provided found to raycast from."); |
|
} |
|
return null; |
|
} |
|
|
|
private void OnPrimaryTouchPositionChanged(InputAction.CallbackContext context) { |
|
// we can read the Vector2 value from the event state and use it to maintain the last touch position |
|
lastTouchPosition = context.ReadValue<Vector2>(); |
|
if (currentReceiver) { |
|
currentReceiver.OnTouchPositionUpdated(lastTouchPosition); |
|
} |
|
} |
|
|
|
private void OnPrimaryTouchStarted(InputAction.CallbackContext context) { |
|
// we can read the initial position of the touch event (this works because we use a Modifier in the action binding) |
|
lastTouchPosition = context.ReadValue<Vector2>(); |
|
var receiver = FindTouchReceiver(lastTouchPosition, Camera.main, out var isFirstTouch); |
|
if (receiver) { |
|
if(isFirstTouch) { // dispatch touch even as either an first touch, or touch position update |
|
currentReceiver = receiver; |
|
receiver.OnTouchStarted(lastTouchPosition); |
|
} else { |
|
currentReceiver.OnTouchPositionUpdated(lastTouchPosition); |
|
} |
|
} |
|
} |
|
|
|
// When the touch input ends, this even will be raised. NOTABLY, we don't try to read the touch input position |
|
// from the context, as it will always be (0,0) here. Instead, we rely on the lastTouchPosition to notify the |
|
// receiver of the last location of the touch input. |
|
private void OnPrimaryTouchCancelled(InputAction.CallbackContext context) { |
|
if (currentReceiver) { |
|
currentReceiver.OnTouchStopped(lastTouchPosition); |
|
} |
|
currentReceiver = null; |
|
} |
|
} |