-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a5761b5
commit cc2b751
Showing
4 changed files
with
319 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} | ||
} | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.