These two scripts allow for touch-screen pinching and double-tapping to move forward and backward between different cameras that are focused on different Hotspots - behaviour popularised by The Room series of mobile games.
- Set the "Movement method" to "None"
- Create a new Tag named CameraHotspot
- For any Hotspot you want to be able to focus on with its own camera, create either a SimpleCamera or Advanced Third Person Camera (available from AC's Downloads page), and attach the Camera Zoom component to it.
- In this Camera Zoom component, assign the Hotspot it is intended for, as well as (optionally) a camera to switch to if when pinching out in the "Exit Camera" field.
- Create an ActionList asset named "SwitchCamera", and define a GameObject parameter named "New camera".
- In the ActionList, create a "Camera: Switch" Action with a 2s transition, and override the "New camera" field with the parameter.
- Add the GestureHotspot component to either a scene object, or a spawned prefab e.g. a custom EventSystem assigned in the Menu Manager
- In this component, assign "SwitchCamera" as the "Switch Camera Action List", and tweak the other fields to suit
You should now be able to double-click / double-tap Hotspots to transition to their associated camera, as well as screen-pinch / scroll the middle-mouse button to transition back and forth.
CameraZoom.cs:
using UnityEngine;
namespace AC
{
public class CameraZoom : MonoBehaviour
{
#region Variables
[SerializeField] private Hotspot associatedHotspot = null;
[SerializeField] private _Camera exitCamera = null;
private Camera _camera;
private float originalFOV;
private float targetZoom;
private LerpUtils.FloatLerp zoomLerp = new LerpUtils.FloatLerp ();
private const float zoomSpeed = 5f;
#endregion
#region UnityStandards
private void Start ()
{
if (associatedHotspot != null)
{
associatedHotspot.gameObject.tag = GestureHotspot.cameraHotspotTag;
}
_camera = GetComponentInChildren <Camera> ();
originalFOV = _camera.fieldOfView;
targetZoom = 0f;
}
private void OnEnable ()
{
EventManager.OnSwitchCamera += OnSwitchCamera;
}
private void OnDisable ()
{
EventManager.OnSwitchCamera -= OnSwitchCamera;
}
private void Update ()
{
if (KickStarter.mainCamera.attachedCamera.Camera == _camera)
{
_camera.fieldOfView = zoomLerp.Update (_camera.fieldOfView, originalFOV + targetZoom, zoomSpeed);
}
}
#endregion
#region PublicFunctions
public void SetZoomAmount (float amount)
{
targetZoom = amount;
}
#endregion
#region PrivateFunctions
private void OnSwitchCamera (_Camera fromCamera, _Camera toCamera, float transitionTime)
{
if (toCamera == _camera)
{
targetZoom = 0f;
zoomLerp.Reset ();
_camera.fieldOfView = originalFOV;
}
}
#endregion
#region GetSet
public _Camera ExitCamera
{
get
{
return exitCamera;
}
}
#endregion
}
}
GestureHotspot.cs:
#if (UNITY_IOS || UNITY_ANDROID) && !UNITY_EDITOR
#define MOBILE_INPUT
#endif
using UnityEngine;
using System.Collections.Generic;
namespace AC
{
public class GestureHotspot : MonoBehaviour
{
#region Variables
[Header("Pinching")]
[SerializeField] private float pinchThreshold = 0.2f;
[SerializeField] private float doubleTapWindow = 0.3f;
[SerializeField] private float sphereCastRadius = 0.2f;
[Header("Zooming")]
[SerializeField] private AnimationCurve zoomCurve = new AnimationCurve (new Keyframe(-1, -1), new Keyframe(1, 1));
[SerializeField] private float zoomFactor = 3f;
[Header ("Interactions")]
[SerializeField] private ActionListAsset switchCameraActionList = null;
private float doubleTimeActive;
private LayerMask hotspotLayerMask;
private Vector2 lastTapPosition;
public const string cameraHotspotTag = "CameraHotspot";
private bool isPinching = false;
private float startPinchDistance;
private CameraZoom activeCameraZoom;
#endregion
#region UnityStandards
private void Start ()
{
hotspotLayerMask = 1 << LayerMask.NameToLayer (KickStarter.settingsManager.hotspotLayer);
}
private void OnEnable ()
{
EventManager.OnSwitchCamera += OnSwitchCamera;
}
private void OnDisable ()
{
EventManager.OnSwitchCamera -= OnSwitchCamera;
}
private void Update ()
{
if (doubleTimeActive > 0f)
{
doubleTimeActive -= Time.deltaTime;
}
if (!KickStarter.stateHandler.IsInGameplay ())
{
isPinching = false;
return;
}
if (Input_StartPinch)
{
isPinching = true;
startPinchDistance = 0f;
}
else if (Input_EndPinch)
{
isPinching = false;
}
if (isPinching)
{
float currentPinchDistance = Input_PinchDistance;
if (startPinchDistance == 0f)
{
startPinchDistance = currentPinchDistance;
}
float pinchDistance = (currentPinchDistance - startPinchDistance) / Screen.dpi;
float relativePinchDistance = Mathf.Clamp (pinchDistance / pinchThreshold, -1f, 1f);
if (activeCameraZoom != null)
{
SetZoomAmount (-relativePinchDistance);
}
if (relativePinchDistance >= 1f)
{
ActivateHotspot (new Vector2 (ACScreen.width / 2f, ACScreen.height / 2f), true);
}
else if (relativePinchDistance <= -1f)
{
ExitCurrentCamera ();
}
return;
}
else if (activeCameraZoom != null)
{
SetZoomAmount (0f);
}
if (Input_Tap)
{
if (doubleTimeActive > 0f)
{
doubleTimeActive = 0f;
ActivateHotspot (lastTapPosition, true);
}
else
{
doubleTimeActive = doubleTapWindow;
ActivateHotspot (lastTapPosition, false);
}
}
}
#endregion
#region PrivateFunctions
private void ActivateHotspot (Vector2 screenPosition, bool cameraHotspots = false)
{
Ray ray = KickStarter.CameraMain.ScreenPointToRay (screenPosition);
RaycastHit raycastHit;
if (cameraHotspots)
{
if (Physics.SphereCast (ray, sphereCastRadius, out raycastHit, KickStarter.settingsManager.hotspotRaycastLength, hotspotLayerMask))
{
if (raycastHit.collider.tag != cameraHotspotTag) return;
Hotspot hotspot = raycastHit.collider.GetComponent<Hotspot> ();
if (hotspot != null)
{
hotspot.RunUseInteraction ();
}
}
}
else
{
if (Physics.Raycast (ray, out raycastHit, KickStarter.settingsManager.hotspotRaycastLength, hotspotLayerMask))
{
if (raycastHit.collider.tag == cameraHotspotTag) return;
Hotspot hotspot = raycastHit.collider.GetComponent<Hotspot> ();
if (hotspot != null)
{
hotspot.RunUseInteraction ();
}
}
}
}
private void OnSwitchCamera (_Camera fromCamera, _Camera toCamera, float transitionTime)
{
SetZoomAmount (0f);
activeCameraZoom = toCamera.GetComponent<CameraZoom> ();
AdvancedThirdPersonCamera fromTPCam = fromCamera as AdvancedThirdPersonCamera;
AdvancedThirdPersonCamera toTPCam = toCamera as AdvancedThirdPersonCamera;
if (fromTPCam != null && toTPCam == null)
{
fromTPCam.BeginAutoMove (1f, fromTPCam.GenerateTargetAngles (toCamera.Camera.transform), false);
}
}
private void SetZoomAmount (float amount)
{
if (activeCameraZoom != null)
{
float zoomAmount = zoomCurve.Evaluate (amount);
activeCameraZoom.SetZoomAmount (zoomAmount * zoomFactor);
}
}
private void ExitCurrentCamera ()
{
if (activeCameraZoom != null && switchCameraActionList != null)
{
_Camera exitCamera = activeCameraZoom.ExitCamera;
if (exitCamera != null)
{
List<ActionParameter> newParameterValues = new List<ActionParameter> ();
ActionParameter newParameter = new ActionParameter (switchCameraActionList.DefaultParameters[0]);
newParameter.SetValue (exitCamera.gameObject);
newParameterValues.Add (newParameter);
switchCameraActionList.AssignParameterValues (newParameterValues);
switchCameraActionList.Interact ();
}
}
}
#endregion
#region GetSet
private bool Input_Tap
{
get
{
#if MOBILE_INPUT
if (Input.touchCount == 1 && Input.GetTouch (0).phase == TouchPhase.Began)
{
lastTapPosition = Input.GetTouch (0).position;
return true;
}
#else
if (Input.GetMouseButtonDown (0))
{
lastTapPosition = Input.mousePosition;
return true;
}
#endif
return false;
}
}
private float Input_PinchDistance
{
get
{
#if MOBILE_INPUT
return (Input.GetTouch (0).position - Input.GetTouch (1).position).magnitude;
#else
return Input.mousePosition.y;
#endif
}
}
private bool Input_StartPinch
{
get
{
if (isPinching) return false;
#if MOBILE_INPUT
return Input.touchCount == 2 && Input.GetTouch (1).phase == TouchPhase.Began;
#else
return Input.GetMouseButtonDown (2);
#endif
}
}
private bool Input_EndPinch
{
get
{
if (!isPinching) return false;
#if MOBILE_INPUT
return (Input.touchCount != 2);
#else
return !Input.GetMouseButton (2);
#endif
}
}
#endregion
}
}