Skip to content

Instantly share code, notes, and snippets.

@robc
Created February 19, 2018 11:00
Show Gist options
  • Save robc/c02597dcd56085bbf5a7c179101c02ea to your computer and use it in GitHub Desktop.
Save robc/c02597dcd56085bbf5a7c179101c02ea to your computer and use it in GitHub Desktop.
Touch Input Code from PocketDogfights - this was integrated into a wider input framework, which handles taking a number of calls from this and translating it into player input.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class InputController : MonoBehaviour
{
public System.Action<bool> OnSetDirectionIndicatorVisibility;
public System.Action<float> OnSetDirectionIndicatorHeading;
public System.Action OnMenuMoveUp;
public System.Action OnMenuMoveDown;
public System.Action OnMenuMoveLeft;
public System.Action OnMenuMoveRight;
public System.Action OnMenuSelect;
public System.Action OnMenuBack;
public System.Action OnPlayerPressedPause;
private InputControllerMode inputMode;
private bool shouldShowDirectionIndicator;
private bool includePrimaryFireForBurst;
private float realTimeAtEndOfPreviousFrame;
private List<ITouchMouseInput> touchMouseHandlers;
private List<IGamepadGameplayAction> gameplayControlHandlers;
private List<IGamepadMenuAction> menuControlHandlers;
void Awake()
{
gameplayControlHandlers = new List<IGamepadGameplayAction>();
menuControlHandlers = new List<IGamepadMenuAction>();
touchMouseHandlers = new List<ITouchMouseInput>();
shouldShowDirectionIndicator = false;
inputMode = InputControllerMode.HandleMenuInputs;
#if (UNITY_IPHONE || UNITY_ANDROID || UNITY_WP8 || UNITY_METRO) && !UNITY_EDITOR
touchMouseHandlers.Add(new TouchInputController());
shouldShowDirectionIndicator = true;
#elif (UNITY_IPHONE || UNITY_ANDROID || UNITY_WP8) && UNITY_EDITOR
touchMouseHandlers.Add(new MouseTouchInputController());
shouldShowDirectionIndicator = true;
#endif
#if UNITY_METRO || UNITY_WEBPLAYER || UNITY_STANDALONE
touchMouseHandlers.Add(new MouseInputController());
#endif
#if UNITY_EDITOR || UNITY_STANDALONE || UNITY_WEBPLAYER || UNITY_METRO || UNITY_ANDROID
KeyboardMapper keyboardMapper = new KeyboardMapper("Keyboard");
gameplayControlHandlers.Add(keyboardMapper);
menuControlHandlers.Add(keyboardMapper);
#endif
}
void Start()
{
if (ControllerMappingService.Instance.SelectedMapper != null)
{
gameplayControlHandlers.Add(ControllerMappingService.Instance.SelectedMapper);
menuControlHandlers.Add(ControllerMappingService.Instance.SelectedMapper);
}
ControllerMappingService.Instance.OnSelectedMapperDidChange += OnSelectedMapperDidChange;
realTimeAtEndOfPreviousFrame = Time.realtimeSinceStartup;
}
public void ConfigurePlayer(GameObject player)
{
PlayerRotation = player.GetComponent<SimplePlaneRotation>();
PlayerWeapons = player.GetComponent<PlayerGunner>();
}
void Update()
{
float realDeltaTime = Time.realtimeSinceStartup - realTimeAtEndOfPreviousFrame;
ControllerMappingService.Instance.CheckIfSelectedMapperIsAvailable();
#if UNITY_IPHONE
iCadeBinding.updateState();
#endif
if (inputMode == InputControllerMode.HandlePlayerInputs)
{
includePrimaryFireForBurst = false;
bool hasDoneMovement = false;
bool isFiringPrimaryFire = false;
bool hasFiredSecondaryBurst = false;
for (int index = 0; index < touchMouseHandlers.Count; index++)
{
touchMouseHandlers[index].Update(realDeltaTime);
if (touchMouseHandlers[index].IsMovementActive() && !hasDoneMovement)
{
float newHeading = touchMouseHandlers[index].DestinationHeading();
PlayerRotation.TurnPlaneToNewHeading(newHeading);
hasDoneMovement = true;
if (shouldShowDirectionIndicator)
{
if (OnSetDirectionIndicatorVisibility != null)
OnSetDirectionIndicatorVisibility(true);
if (OnSetDirectionIndicatorHeading != null)
OnSetDirectionIndicatorHeading(newHeading);
}
}
else if (shouldShowDirectionIndicator)
{
if (OnSetDirectionIndicatorVisibility != null)
OnSetDirectionIndicatorVisibility(false);
}
isFiringPrimaryFire |= touchMouseHandlers[index].IsPrimaryFireActive();
hasFiredSecondaryBurst |= touchMouseHandlers[index].HasFiredSecondaryBurst();
if (hasFiredSecondaryBurst) includePrimaryFireForBurst = true;
}
for (int index = 0; index < gameplayControlHandlers.Count; index++)
{
if (!hasDoneMovement)
{
float horizontalInput = 0f, verticalInput = 0f;
if (gameplayControlHandlers[index].IsSupportingDirectionalControls() &&
PreferencesController.Instance.ControlType == GamepadControlType.Directional)
{
verticalInput = gameplayControlHandlers[index].VerticalAxis();
horizontalInput = gameplayControlHandlers[index].HorizontalAxis();
if (!Mathf.Approximately(verticalInput, 0f) || !Mathf.Approximately(horizontalInput, 0f))
PlayerRotation.TurnPlaneToNewHeading(MathUtilities.NormaliseAngle(Mathf.Atan2(horizontalInput, verticalInput) * Mathf.Rad2Deg));
}
else
{
horizontalInput = gameplayControlHandlers[index].HorizontalAxis();
PlayerRotation.TurnYawByYawDelta(horizontalInput, Time.smoothDeltaTime, !Mathf.Approximately(horizontalInput, 0f));
}
hasDoneMovement = !(Mathf.Approximately(horizontalInput, 0f) && Mathf.Approximately(verticalInput, 0f));
}
if (gameplayControlHandlers[index].HasPressedPause() && OnPlayerPressedPause != null)
{
OnPlayerPressedPause();
return;
}
isFiringPrimaryFire |= gameplayControlHandlers[index].IsPrimaryFireActive();
hasFiredSecondaryBurst |= gameplayControlHandlers[index].HasFiredSecondaryBurst();
}
if (isFiringPrimaryFire) PlayerWeapons.StartPrimaryAutoFire();
else PlayerWeapons.StopPrimaryAutoFire();
/**
* Handles secondary fire. If we're on touch, then we fire a single burst
* (which falls back to primary if no ammo's available). For controllers,
* we use a secondary auto fire - which properly factors in the reload delays.
*/
if (includePrimaryFireForBurst && hasFiredSecondaryBurst)
PlayerWeapons.FireSecondaryBurstIfAvailable(true);
else if (!includePrimaryFireForBurst)
{
if (hasFiredSecondaryBurst) PlayerWeapons.StartSecondaryAutoFire();
else PlayerWeapons.StopSecondaryAutoFire();
}
}
else if (inputMode == InputControllerMode.HandleMenuInputs)
{
for (int index = 0; index < menuControlHandlers.Count; index++)
{
menuControlHandlers[index].UpdateMenuState(realDeltaTime);
if (menuControlHandlers[index].HasPressedMenuUp() && OnMenuMoveUp != null)
OnMenuMoveUp();
else if (menuControlHandlers[index].HasPressedMenuDown() && OnMenuMoveDown != null)
OnMenuMoveDown();
else if (menuControlHandlers[index].HasPressedMenuLeft() && OnMenuMoveLeft != null)
OnMenuMoveLeft();
else if (menuControlHandlers[index].HasPressedMenuRight() && OnMenuMoveRight != null)
OnMenuMoveRight();
if (menuControlHandlers[index].HasPressedMenuSelect() && OnMenuSelect != null)
OnMenuSelect();
else if (menuControlHandlers[index].HasPressedMenuBack() && OnMenuBack != null)
OnMenuBack();
else if (menuControlHandlers[index].HasPressedMenuBack() && OnPlayerPressedPause != null)
OnPlayerPressedPause();
}
}
realTimeAtEndOfPreviousFrame = Time.realtimeSinceStartup;
}
public InputControllerMode InputMode
{
get { return inputMode; }
set
{
inputMode = value;
if (value == InputControllerMode.HandleMenuInputs)
{
for (int index = 0; index < touchMouseHandlers.Count; index++)
touchMouseHandlers[index].ClearAllInputs();
if (PlayerWeapons != null) PlayerWeapons.StopPrimaryAutoFire();
if (PlayerRotation != null) PlayerRotation.enabled = false;
if (shouldShowDirectionIndicator && OnSetDirectionIndicatorVisibility != null)
OnSetDirectionIndicatorVisibility(false);
}
}
}
public SimplePlaneRotation PlayerRotation
{
get; set;
}
public PlayerGunner PlayerWeapons
{
get; set;
}
private void OnSelectedMapperDidChange(AbstractControllerMapper removedMapper, AbstractControllerMapper newMapper)
{
if (removedMapper != null)
{
if (gameplayControlHandlers.Contains(removedMapper)) gameplayControlHandlers.Remove(removedMapper);
if (menuControlHandlers.Contains(removedMapper)) menuControlHandlers.Remove(removedMapper);
}
if (newMapper != null)
{
gameplayControlHandlers.Add(newMapper);
menuControlHandlers.Add(newMapper);
}
}
}
public enum InputControllerMode
{
HandlePlayerInputs,
HandleMenuInputs
}
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class TouchInputController : ITouchMouseInput
{
private const float AUTOFIRE_THRESHOLD = 0.166666667f;
private TouchDetails moveTouch;
private List<TouchDetails> activeFireTouches;
private List<TouchDetails> fireTouchesEndedThisFrame;
private List<Vector3> positionsOfAllEndedTouches;
private float computedHeading;
private bool isOutsideRange;
private float sqrDeadzone;
public TouchInputController()
{
activeFireTouches = new List<TouchDetails>();
positionsOfAllEndedTouches = new List<Vector3>();
fireTouchesEndedThisFrame = new List<TouchDetails>();
sqrDeadzone = Deadzone * Deadzone;
}
private float Deadzone
{
get
{
if (Screen.dpi < 250.0f)
return 15.0f;
else if (Screen.dpi < 300.0f)
return 20.0f;
else
return 30.0f;
}
}
private TouchDetails FireTouchForFingerId(int fingerId)
{
for (int index = 0; index < activeFireTouches.Count; index++)
{
if (activeFireTouches[index].FingerId == fingerId)
return activeFireTouches[index];
}
return null;
}
private void UpdateMovementTouch(Touch touch, float delta)
{
if (moveTouch == null || moveTouch.FingerId != touch.fingerId) return;
moveTouch.Update(touch, delta);
Vector2 destinationVector = moveTouch.Current - moveTouch.Origin;
if (!isOutsideRange) isOutsideRange = destinationVector.sqrMagnitude > sqrDeadzone;
computedHeading = MathUtilities.NormaliseAngle(Mathf.Atan2(destinationVector.x, destinationVector.y) * Mathf.Rad2Deg);
}
#region ITouchMouseInput methods
public void Update(float delta)
{
fireTouchesEndedThisFrame.Clear();
positionsOfAllEndedTouches.Clear();
int numberOfTouches = Input.touchCount;
for (int index = 0; index < numberOfTouches; index++)
{
Touch touch = Input.GetTouch(index);
if (moveTouch == null && touch.phase == TouchPhase.Began)
{
moveTouch = new TouchDetails(touch.fingerId, touch.position);
isOutsideRange = false;
}
else if (moveTouch != null && moveTouch.FingerId == touch.fingerId)
{
UpdateMovementTouch(touch, delta);
if (touch.phase == TouchPhase.Canceled || touch.phase == TouchPhase.Ended)
{
positionsOfAllEndedTouches.Add(new Vector3(moveTouch.Current.x, moveTouch.Current.y, 0));
/**
* Checks the ending position of the movement touch to see if we should
* treat it as a firing action. This will be the case if the touch never
* left the deadzone area, and ended prior to the AUTOFIRE_THRESHOLD period.
*/
Vector2 destinationVector = moveTouch.Current - moveTouch.Origin;
if (destinationVector.sqrMagnitude < sqrDeadzone &&
moveTouch.Duration < AUTOFIRE_THRESHOLD &&
!isOutsideRange)
{
fireTouchesEndedThisFrame.Add(moveTouch);
}
moveTouch = null;
}
}
else
{
TouchDetails firingTouch = FireTouchForFingerId(touch.fingerId);
if (firingTouch != null)
{
firingTouch.Update(touch, delta);
if (touch.phase == TouchPhase.Canceled || touch.phase == TouchPhase.Ended)
{
activeFireTouches.Remove(firingTouch);
fireTouchesEndedThisFrame.Add(firingTouch);
positionsOfAllEndedTouches.Add(new Vector3(firingTouch.Current.x, firingTouch.Current.y, 0));
}
}
else
{
firingTouch = new TouchDetails(touch.fingerId, touch.position);
activeFireTouches.Add(firingTouch);
}
}
}
}
public void ClearAllInputs()
{
if (moveTouch != null)
moveTouch = null;
activeFireTouches.Clear();
}
public bool IsMovementActive()
{
return (moveTouch != null && isOutsideRange);
}
public float DestinationHeading()
{
return computedHeading;
}
public bool IsPrimaryFireActive()
{
bool moveTouchActive = (moveTouch != null && moveTouch.Duration > AUTOFIRE_THRESHOLD);
float maxFireTouchDuration = 0;
for (int index = 0; index < activeFireTouches.Count; index++)
maxFireTouchDuration = Mathf.Max(maxFireTouchDuration, activeFireTouches[index].Duration);
return (moveTouchActive || maxFireTouchDuration > AUTOFIRE_THRESHOLD);
}
public bool HasFiredSecondaryBurst()
{
float maxFireTouchDuration = 0f;
for (int index = 0; index < fireTouchesEndedThisFrame.Count; index++)
maxFireTouchDuration = Mathf.Max(maxFireTouchDuration, fireTouchesEndedThisFrame[index].Duration);
return (maxFireTouchDuration > 0f && maxFireTouchDuration < AUTOFIRE_THRESHOLD);
}
public List<Vector3> InputsEndedInLastFrame()
{
return positionsOfAllEndedTouches;
}
#endregion
}
#region TouchDetail Class - implementation of the details we want to track for each touch
public class TouchDetails
{
private int fingerId;
private Vector2 origin;
private Vector2 current;
private float duration;
public TouchDetails(int fingerId, Vector2 origin)
{
this.fingerId = fingerId;
this.origin = origin;
this.current = origin;
this.duration = 0f;
}
public void Update(Touch touch, float delta)
{
// Watchdog. We shouldn't be processing this touch with this object
// if the finger Id's don't match!
if (touch.fingerId != this.fingerId) return;
this.duration += delta;
this.current = touch.position;
}
public int FingerId
{
get { return fingerId; }
}
public Vector2 Origin
{
get { return origin; }
}
public Vector2 Current
{
get { return current; }
}
public float Duration
{
get { return duration; }
}
}
#endregion
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment