Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
RayOfIdeas committed Jan 25, 2024
1 parent a5761b5 commit cc2b751
Show file tree
Hide file tree
Showing 4 changed files with 319 additions and 2 deletions.
143 changes: 143 additions & 0 deletions CameraController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace TouchSimulatorForUnity
{
public class CameraController : MonoBehaviour
{
[Header("Zoom")]
[SerializeField]
float orthographicSize = 12;

[SerializeField]
float orthographicSizeMin = 6;

[SerializeField]
float orthographicSizeMax = 20;

[Header("Move")]
[SerializeField]
Transform borderRight;

[SerializeField]
Transform borderLeft;

[SerializeField]
Transform borderTop;

[SerializeField]
Transform borderBottom;

Vector2 currentPointerPos, lastPointerPos;
bool isTapping, isTappingSecond;

void Awake()
{
Camera.main.orthographicSize = orthographicSize;

#if UNITY_EDITOR
var touchSimulator = FindFirstObjectByType<TouchSimulator>();
touchSimulator.OnPositionInput += OnPositionInput;
touchSimulator.OnTapInput += OnTapInput;
touchSimulator.OnTapSecondInput += OnTapSecondInput;
touchSimulator.OnPinchInput += OnPinchInput;
#endif

void OnPositionInput(Vector2 pos)
{
lastPointerPos = currentPointerPos;
currentPointerPos = pos;

if (isTapping && !isTappingSecond)
{
var currentPointerPosWorld = Camera.main.ScreenToWorldPoint(currentPointerPos);
var lastPointerPosWorld = Camera.main.ScreenToWorldPoint(lastPointerPos);
var direction = currentPointerPosWorld - lastPointerPosWorld;
MoveBy(-direction);
}
}

void OnTapInput(bool isTapping)
{
this.isTapping = isTapping;
if (isTapping)
{

}
else
{
lastPointerPos = Vector2.zero;
currentPointerPos = Vector2.zero;
}
}

void OnTapSecondInput(bool isTapping)
{
this.isTappingSecond = isTapping;
}

void OnPinchInput(Vector2 primaryCurrent, Vector2 secondaryCurrent, Vector2 primaryLast, Vector2 secondaryLast)
{
if (!isTapping || !isTappingSecond)
return;

var lastPinchPos = new Vector2(
(primaryLast.x + secondaryLast.x) / 2,
(primaryLast.y + secondaryLast.y) / 2);
var lastPosWorld = Camera.main.ScreenToWorldPoint(lastPinchPos);

var currentPinchDistance = Vector2.Distance(primaryCurrent, secondaryCurrent);
var lastPinchDistance = Vector2.Distance(primaryLast, secondaryLast);
var newOrthographicSize = lastPinchDistance * Camera.main.orthographicSize / currentPinchDistance;
var lastOrthographicSize = Camera.main.orthographicSize;
SetOrthographicSize(newOrthographicSize);

var currentPinchPos = new Vector2(
(primaryCurrent.x + secondaryCurrent.x) / 2,
(primaryCurrent.y + secondaryCurrent.y) / 2);
var currentPosWorld = Camera.main.ScreenToWorldPoint(currentPinchPos);

var scaleMarginDirection = lastPosWorld - currentPosWorld;
MoveBy(scaleMarginDirection * newOrthographicSize / lastOrthographicSize);
}
}

public void SetOrthographicSize(float newOrthographicSize)
{
Camera.main.orthographicSize = newOrthographicSize;
Camera.main.orthographicSize = Mathf.Clamp(Camera.main.orthographicSize, orthographicSizeMin, orthographicSizeMax);
ConstrainPosition();
}

public void MoveBy(Vector2 direction)
{
Camera.main.transform.position = new(
Camera.main.transform.position.x + direction.x,
Camera.main.transform.position.y + direction.y,
Camera.main.transform.position.z);

ConstrainPosition();
}

void ConstrainPosition()
{
var aspectRatio = Screen.height / Screen.width;
var maxPos = new Vector2(
borderRight.position.x - Camera.main.orthographicSize / 2,
borderTop.position.y - (Camera.main.orthographicSize / 2 * aspectRatio)
);
var minPos = new Vector2(
borderLeft.position.x + Camera.main.orthographicSize / 2,
borderBottom.position.y + (Camera.main.orthographicSize / 2 * aspectRatio)
);

Camera.main.transform.position = new(
Mathf.Clamp(Camera.main.transform.position.x, minPos.x, maxPos.x),
Mathf.Clamp(Camera.main.transform.position.y, minPos.y, maxPos.y),
Camera.main.transform.position.z
);
}
}
}
35 changes: 33 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,33 @@
# TouchSimulatorForUnity
Simulate multi-touch using mouse in Unity
# Touch Simulator For Unity
Simulate multi-touch using mouse in Unity Editor.

![Alt Text](TouchSimulator.gif)

## Dependencies
This script is using Unity's InputSystem package.

## How to Use
1. Import `TouchSimulator.cs` to your Unity project.
2. Create a `Canvas` with its `RenderMode` to `Screen Space - Overlay`
3. The component `Canvas Scaler`'s `UI Scale Mode` to `Scale With Screen Size`
4. Add `TouchSimulator` to the canvas.
5. As the child of the canvas, create an empty gameObject, and name it "Touch". As the children of Touch, make two empty gameObjects, and name them "Primary" and "Secondary".

Canvas
└ Touch
└ Primary
└ Secondary
You may want to add `Image` component to see them.

6. In `TouchSimulator`, assign Touch to `Touch` field, Primary to `Pinch Primary`, and Secondary to `Pinch Secondary`.
7. Subscribe to any of `TouchSimulator`'s delegates:
- `OnTapInput`
- `OnTapSecondInput`
- `OnDeltaInput`
- `OnPositionInput`
- `OnPinchInput`

For more reference, check the example `CameraController.cs` which is used to control the camera movement in 2D game.

For more reference on how to actually implement pinch input in Unity, check this article:
https://www.arcaneshift.com/blog/2023/06/19/pinch-and-scroll-with-unitys-new-input-system/
143 changes: 143 additions & 0 deletions TouchSimulator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
using System;
using UnityEngine;
using UnityEngine.InputSystem;

namespace TouchSimulatorForUnity
{
public class TouchSimulator : MonoBehaviour
{
[Header("One Touch")]
public Transform touch;

[Header("Pinch")]
public float pinchInitialRadius = 100;
public float pinchInitialAngle = 0;

[Header("Pinch Components")]
public Transform pinchPrimary;
public Transform pinchSecondary;
public bool isPinchPrimaryTapping;
public bool isPinchSecondaryTapping;

Vector2 mouseLastPos, primaryLastPos, secondaryLastPos;
bool isCursorWarped, isTapping, isTappingSecond;

public event Action<bool> OnTapInput;
public event Action<bool> OnTapSecondInput;
public event Action<Vector2> OnDeltaInput;
public event Action<Vector2> OnPositionInput;
public event Action<Vector2, Vector2, Vector2, Vector2> OnPinchInput;
Vector2 GetMousePosOffset() => Mouse.current.position.value - new Vector2(Screen.width / 2, Screen.height / 2);

void Awake()
{
#if !UNITY_EDITOR
gameObject.SetActive(false);
#endif

pinchPrimary.localPosition = new(0, pinchInitialRadius);
pinchSecondary.localPosition = new(0, -pinchInitialRadius);
touch.transform.localEulerAngles = new(0, 0, pinchInitialAngle);
}

void Update()
{
HandleTapInput();
HandleTapSecondInput();
HandleDeltaInput();
HandlePositionInput();
HandlePinch();

void HandleTapInput()
{
if (Mouse.current.leftButton.isPressed && !isTapping)
{
isTapping = true;
OnTapInput?.Invoke(true);
}
else if (!Mouse.current.leftButton.isPressed && isTapping)
{
isTapping = false;
OnTapInput?.Invoke(false);
}
}

void HandleTapSecondInput()
{
if (Mouse.current.rightButton.isPressed && !isTappingSecond)
{
isTappingSecond = true;
OnTapSecondInput?.Invoke(true);
}
else if (!Mouse.current.rightButton.isPressed && isTappingSecond)
{
isTappingSecond = false;
OnTapSecondInput?.Invoke(false);
}
}

void HandleDeltaInput()
{
OnDeltaInput?.Invoke(Mouse.current.delta.value);
}

void HandlePositionInput()
{
OnPositionInput?.Invoke(Mouse.current.position.value);
}

void HandlePinch()
{
isPinchPrimaryTapping = Mouse.current.leftButton.isPressed;
isPinchSecondaryTapping = Mouse.current.rightButton.isPressed;

if (isPinchPrimaryTapping && isPinchSecondaryTapping)
{
if (!isCursorWarped)
{
WarpCursor();
}
else
{
pinchPrimary.localPosition = GetMousePosOffset() - (Vector2)touch.localPosition;
pinchSecondary.localPosition = -pinchPrimary.localPosition;
OnPinchInput?.Invoke(
pinchPrimary.position,
pinchSecondary.position,
primaryLastPos,
secondaryLastPos);
}

primaryLastPos = pinchPrimary.position;
secondaryLastPos = pinchSecondary.position;
}
else
{
touch.localPosition = new(
Mouse.current.position.value.x - Screen.width / 2,
Mouse.current.position.value.y - Screen.height / 2
);

ResetUI();
}

mouseLastPos = Mouse.current.position.value;

void ResetUI()
{
pinchPrimary.localPosition = new(0, pinchInitialRadius);
pinchSecondary.localPosition = new(0, -pinchInitialRadius);
touch.localEulerAngles = Vector3.zero;
isCursorWarped = false;
}

void WarpCursor()
{
isCursorWarped = true;
var warpPos = touch.localPosition + pinchPrimary.localPosition + new Vector3(Screen.width / 2, Screen.height / 2, 0);
Mouse.current.WarpCursorPosition(warpPos);
}
}
}
}
}
Binary file added TouchSimulator.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit cc2b751

Please sign in to comment.