I am using touch input to do a pinch function. The pinching works but I would like to detect if the two fingers are pinching in (coming closer to each other) or pinching out (moving farther from each other). How does one do this?
I have already tried storing the current ratio to previous ratio in order to check in the next iteration but it fluctuates since I am storing it at every frame. Also, what if the user never lifts off his fingers, how does that work then?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.EnhancedTouch;
using Touch = UnityEngine.InputSystem.EnhancedTouch.Touch;
public class DetectTouch: MonoBehaviour {
//Touch
int prevtouchCount;
float firstDistance;
float originalZoom;
float currentZoom;
float distance;
float ratio;
float previousRatio;
void Update () {
//Touch
if (Touch.activeFingers.Count == 2) {
Touch touch1 = Touch.activeFingers[0].currentTouch;
Touch touch2 = Touch.activeFingers[1].currentTouch;
distance = Vector2.Distance (touch1.screenPosition, touch2.screenPosition);
if (prevtouchCount != 2) {
firstDistance = distance;
originalZoom = Camera.main.fieldOfView;
}
ratio = distance / firstDistance;
if (ratio < previousRatio) {
Debug.Log("NOT PINCHING");
previousRatio = ratio;
} else {
Debug.Log("PINCHING");
previousRatio = ratio;
}
}
}
}
For the overall resulting pinch direction it would be simply
if(ratio < 1f)
{
Debug.Log("pinch in");
}
else if(ratio > 1f)
{
Debug.Log("pinch out");
}
Or you go directly by the distance and do
if(distance > firstDistance)
{
Debug.Log("pinch in");
}
else if(distance < firstDistance)
{
Debug.Log("pinch out");
}
If you rather wanted it frame-wise regardless of the overall result I think you should actually be fine doing
if(ratio > previousRatio)
{
Debug.Log("pinch in");
}
else if(ratio < previousRatio)
{
Debug.Log("pinch out");
}
previousRatio = ratio;
or again could directly use the distance and do
if(distance > previousDistance)
{
Debug.Log("pinch in");
}
else if(distance < previousDistance)
{
Debug.Log("pinch out");
}
previousDistance = distance;
Related
I am trying to convert pinch to zoom from old input to new Input system using EnhancedTouch.Touch. Unfortunately I am clueless as to how to move forward from here? I am getting error as Operator '==' cannot be applied to operands of type 'TouchPhase' and 'TouchPhase'. How do I fix this?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem.EnhancedTouch;
using Touch = UnityEngine.InputSystem.EnhancedTouch.Touch;
public class ZoomTouch : MonoBehaviour {
public Camera camera;
float touchDist = 0;
float lastDist = 0;
public void Update () {
if (Input.touchCount == 2) {
Touch touch1 = Touch.activeFingers[0].currentTouch;
Touch touch2 = Touch.activeFingers[0].currentTouch;
if (touch1.phase == TouchPhase.Began && touch2.phase == TouchPhase.Began) {
lastDist = Vector2.Distance (touch1.position, touch2.position);
}
if (touch1.phase == TouchPhase.Moved && touch2.phase == TouchPhase.Moved) {
float newDist = Vector2.Distance (touch1.position, touch2.position);
touchDist = lastDist - newDist;
lastDist = newDist;
camera.fieldOfView += touchDist * 0.1f;
}
}
}
}
I hope this code works. However, I did not have a touch device to test it. This simple code works, takes the distance between the present and the past, and zooms in on their delta.
public float lastDistance;
public float distance;
void Update()
{
if (Input.touchCount < 2) return;
var touch1 = Input.GetTouch(0);
var touch2 = Input.GetTouch(1);
distance = Vector2.Distance(touch1.position, touch1.position);
if (Mathf.Abs(lastDistance-distance) >= 10) lastDistance = distance; // for avoiding sharp zooms on first touch
Camera.main.fieldOfView += distance-lastDistance * 0.1f;
lastDistance = distance;
}
I am trying to use zoom with two fingers but unfortunately whenever I place two fingers together, the starting value of camera.fieldOfView changes drastically. As in it is not able to read the current value. I feel it goes to some other value. It should start from the current value. What am I doing wrong here?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem.EnhancedTouch;
using Touch = UnityEngine.InputSystem.EnhancedTouch.Touch;
public Camera camera;
float touchDist = 0;
float lastDist = 0;
public float maxAngle = 60;
public float minAngle = 30;
private void Start()
{
EnhancedTouchSupport.Enable();
}
public void Update()
{
if (Touch.activeFingers.Count == 2)
{
Touch activeTouch = Touch.activeFingers[0].currentTouch;
Touch touch1 = Touch.activeFingers[0].currentTouch;
Touch touch2 = Touch.activeFingers[1].currentTouch;
if (touch1.phase == UnityEngine.InputSystem.TouchPhase.Began && touch2.phase == UnityEngine.InputSystem.TouchPhase.Began)
{
lastDist = Vector2.Distance(touch1.screenPosition, touch2.screenPosition);
}
if (touch1.phase == UnityEngine.InputSystem.TouchPhase.Moved && touch2.phase == UnityEngine.InputSystem.TouchPhase.Moved)
{
float newDist = Vector2.Distance(touch1.screenPosition, touch2.screenPosition);
touchDist = lastDist - newDist;
lastDist = newDist;
// Your Code Here
camera.fieldOfView += touchDist * 0.05f;
if (camera.fieldOfView > maxAngle)
{
camera.fieldOfView = maxAngle;
}
if (camera.fieldOfView < minAngle)
{
camera.fieldOfView = minAngle;
}
}
}
}
}
That is my code to try and simulate a swipe gesture so when I build to mobile I know it will work. Nothing is being logged and I am confused on why it seems to not work. I want it to print out in the console that I either swiped RTL (Right to Left) or LTR (Left to right). I do not see what I am doing wrong.
void Update()
{
if (Input.GetMouseButtonDown(0))
{
startPosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
}
if (Input.GetMouseButtonUp(0))
{
endPosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
}
if (startPosition != endPosition && startPosition != Vector3.zero && endPosition != Vector3.zero)
{
float deltaX = endPosition.x - startPosition.x;
float deltaY = endPosition.y - startPosition.y;
if ((deltaX > 5.0f || deltaX < -5.0f) && (deltaY >= -1.0f || deltaY <= 1.0f))
{
if (startPosition.x < endPosition.x)
{
print("LTR");
}
else
{
print("RTL");
}
}
startPosition = endPosition = Vector3.zero;
}
}
I can spot some few problems in your code. It's not a good idea to compare Vector3 with == or !=. Approximate comparison is fine. You are using Input.GetMouseButtonDown on a mobile platform.
You need to use Input.touches to do this. Loop over it, store the beginning position in TouchPhase.Began and then the end position in TouchPhase.Ended. You can then use both variables to figure it which direction the finger went.
The code below detects swipe direction even when the finger is not yet released with the help of TouchPhase.Moved. You can disable that by enabling the detectSwipeOnlyAfterRelease boolean variable. You can also modify SWIPE_THRESHOLD for sensitivity.
public class SwipeDetector : MonoBehaviour
{
private Vector2 fingerDown;
private Vector2 fingerUp;
public bool detectSwipeOnlyAfterRelease = false;
public float SWIPE_THRESHOLD = 20f;
// Update is called once per frame
void Update()
{
foreach (Touch touch in Input.touches)
{
if (touch.phase == TouchPhase.Began)
{
fingerUp = touch.position;
fingerDown = touch.position;
}
//Detects Swipe while finger is still moving
if (touch.phase == TouchPhase.Moved)
{
if (!detectSwipeOnlyAfterRelease)
{
fingerDown = touch.position;
checkSwipe();
}
}
//Detects swipe after finger is released
if (touch.phase == TouchPhase.Ended)
{
fingerDown = touch.position;
checkSwipe();
}
}
}
void checkSwipe()
{
//Check if Vertical swipe
if (verticalMove() > SWIPE_THRESHOLD && verticalMove() > horizontalValMove())
{
//Debug.Log("Vertical");
if (fingerDown.y - fingerUp.y > 0)//up swipe
{
OnSwipeUp();
}
else if (fingerDown.y - fingerUp.y < 0)//Down swipe
{
OnSwipeDown();
}
fingerUp = fingerDown;
}
//Check if Horizontal swipe
else if (horizontalValMove() > SWIPE_THRESHOLD && horizontalValMove() > verticalMove())
{
//Debug.Log("Horizontal");
if (fingerDown.x - fingerUp.x > 0)//Right swipe
{
OnSwipeRight();
}
else if (fingerDown.x - fingerUp.x < 0)//Left swipe
{
OnSwipeLeft();
}
fingerUp = fingerDown;
}
//No Movement at-all
else
{
//Debug.Log("No Swipe!");
}
}
float verticalMove()
{
return Mathf.Abs(fingerDown.y - fingerUp.y);
}
float horizontalValMove()
{
return Mathf.Abs(fingerDown.x - fingerUp.x);
}
//////////////////////////////////CALLBACK FUNCTIONS/////////////////////////////
void OnSwipeUp()
{
Debug.Log("Swipe UP");
}
void OnSwipeDown()
{
Debug.Log("Swipe Down");
}
void OnSwipeLeft()
{
Debug.Log("Swipe Left");
}
void OnSwipeRight()
{
Debug.Log("Swipe Right");
}
}
Thanks to Programmer, I used his suggestion and wrote a small component which works both with mouse and touch. The mouse will allow you to debug app on PC. I also added time threshold in seconds, since swipe cannot be too long.
using System;
using UnityEngine;
using UnityEngine.Events;
public class SwipeManager : MonoBehaviour {
public float swipeThreshold = 50f;
public float timeThreshold = 0.3f;
public UnityEvent OnSwipeLeft;
public UnityEvent OnSwipeRight;
public UnityEvent OnSwipeUp;
public UnityEvent OnSwipeDown;
private Vector2 fingerDown;
private DateTime fingerDownTime;
private Vector2 fingerUp;
private DateTime fingerUpTime;
private void Update () {
if (Input.GetMouseButtonDown(0)) {
this.fingerDown = Input.mousePosition;
this.fingerUp = Input.mousePosition;
this.fingerDownTime = DateTime.Now;
}
if (Input.GetMouseButtonUp(0)) {
this.fingerDown = Input.mousePosition;
this.fingerUpTime = DateTime.Now;
this.CheckSwipe();
}
foreach (Touch touch in Input.touches) {
if (touch.phase == TouchPhase.Began) {
this.fingerDown = touch.position;
this.fingerUp = touch.position;
this.fingerDownTime = DateTime.Now;
}
if (touch.phase == TouchPhase.Ended) {
this.fingerDown = touch.position;
this.fingerUpTime = DateTime.Now;
this.CheckSwipe();
}
}
}
private void CheckSwipe() {
float duration = (float)this.fingerUpTime.Subtract(this.fingerDownTime).TotalSeconds;
if (duration > this.timeThreshold) return;
float deltaX = this.fingerDown.x - this.fingerUp.x;
if (Mathf.Abs(deltaX) > this.swipeThreshold) {
if (deltaX > 0) {
this.OnSwipeRight.Invoke();
//Debug.Log("right");
} else if (deltaX < 0) {
this.OnSwipeLeft.Invoke();
//Debug.Log("left");
}
}
float deltaY = fingerDown.y - fingerUp.y;
if (Mathf.Abs(deltaY) > this.swipeThreshold) {
if (deltaY > 0) {
this.OnSwipeUp.Invoke();
//Debug.Log("up");
} else if (deltaY < 0) {
this.OnSwipeDown.Invoke();
//Debug.Log("down");
}
}
this.fingerUp = this.fingerDown;
}
}
Modified Developper's approach for a more precise controller (and less code! =D ) :
using System;
using UnityEngine;
using UnityEngine.Events;
using Utilities;
public class SwipeManager : MonoBehaviour {
public float swipeThreshold = 40f;
public float timeThreshold = 0.3f;
public UnityEvent onSwipeLeft;
public UnityEvent onSwipeRight;
public UnityEvent onSwipeUp;
public UnityEvent onSwipeDown;
private Vector2 _fingerDown;
private DateTime _fingerDownTime;
private Vector2 _fingerUp;
private DateTime _fingerUpTime;
private void Update () {
if (Input.GetMouseButtonDown(0)) {
_fingerDown = Input.mousePosition;
_fingerUp = Input.mousePosition;
_fingerDownTime = DateTime.Now;
}
if (Input.GetMouseButtonUp(0)) {
_fingerDown = Input.mousePosition;
_fingerUpTime = DateTime.Now;
CheckSwipe();
}
foreach (var touch in Input.touches) {
if (touch.phase == TouchPhase.Began) {
_fingerDown = touch.position;
_fingerUp = touch.position;
_fingerDownTime = DateTime.Now;
}
if (touch.phase == TouchPhase.Ended) {
_fingerDown = touch.position;
_fingerUpTime = DateTime.Now;
CheckSwipe();
}
}
}
private void CheckSwipe() {
var duration = (float)_fingerUpTime.Subtract(_fingerDownTime).TotalSeconds;
var dirVector = _fingerUp - _fingerDown;
if (duration > timeThreshold) return;
if (dirVector.magnitude < swipeThreshold) return;
var direction = dirVector.Rotation(180f).Round();
print(direction);
if (direction >= 45 && direction < 135) onSwipeUp.Invoke();
else if (direction >= 135 && direction < 225) onSwipeRight.Invoke();
else if (direction >= 225 && direction < 315) onSwipeDown.Invoke();
else if (direction >= 315 && direction < 360 || direction >= 0 && direction < 45) onSwipeLeft.Invoke();
}
}
I searched for the same thing, and found it reasonable to create asset for easy swipe detection, and share it with community. So here it is on github. My solution supports different usecases, including: 8-directions swipe detection, 4-directions, 2-directions (left-right or up-down), swipes on hexagonal grid. All listed is included as a presets, but also you can configure it to detect any number of Vector3 directions. So ti`s really flexible. Also, you can try WebGL build or see video tutorial. If you try it, please, let me know (via youtube comment, or see Contacts section on github), was it suitable for your case, and was it comfortable enough.
Try this Out.
I hope this helps.
void Update(){
if (Input.GetMouseButtonDown(0)){
startPosition = Input.mousePosition;
}
if (Input.GetMouseButtonUp(0)){
float swipe = startPosition.x - Input.mousePosition.x;
}
if (swipe < 0)
{
print("LTR");
} else{
print("RTL");
}
}
}
}
I have created a script that makes an enemy deal damage over time to a player from a raycast but it is making Unity crash once I'm in the required range for the enemy to move and deal damage. Anyone know why?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyMove : MonoBehaviour
{
public Transform target;
public Transform player;
public float enemySpeed;
public int moveTrigger = 1;
public bool isAttacking;
public int AttackTirgger;
public float distanceFromPlayer;
void Update()
{
distanceFromPlayer = Vector3.Distance(target.transform.position, player.transform.position);
if (distanceFromPlayer <= 10 && moveTrigger == 1)
{
transform.LookAt(target);
if (!isAttacking)
StartCoroutine(EnemyDamage());
}
if (distanceFromPlayer < 10 && moveTrigger == 1 && distanceFromPlayer > 3)
{
transform.Translate(Vector3.forward * enemySpeed * Time.deltaTime);
}
}
IEnumerator EnemyDamage()
{
isAttacking = true;
while (distanceFromPlayer <= 10)
{ // in range
RaycastHit PlayerHit;
if (Physics.Raycast(target.transform.position, target.transform.forward, out PlayerHit))
{
Target target = PlayerHit.transform.GetComponent<Target>();
if (target != null)
{
GlobalHealth.playerHealth -= 1;
yield return new WaitForSeconds(2);
}
}
}
isAttacking = false; // out of range
yield return null;
}
}
If I try to explain what your code does (in pseudo code) :
if(distance_to_target < 10)
{
lookAt(taget)
move_forward() //so you get closer from the target
while(distance < 10)
{
do_stuff() //the stuff doesn't change distance
}
}
When your distance became smaller than 10, and due to your transform.Translate() it stay < 10 forever, so here you have a while(true) → makes unity crash
So I've been following a tutorial on making a 2D game in Unity (I'm a complete newbie, this is my first contact with programming), and I wanted to add a feature to the game(bold).
The "heart system" that I added works correctly (the number of empty hearts is equal to damage taken by player), but it caused my player to transform his position in a wierd way. You can see, that there are boundries set (maxHeight =3,2, minHeight=-3,2), and the value of his movement as well (Yincrement = 3.2) and yet, after pressing up or down arrowkeys he seems to change Y position by around 4.67.
Here's the player script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class Player : MonoBehaviour
{
private Vector2 targetPos;
public float Yincrement;
public float speed;
public float maxHeight;
public float minHeigth;
public int health = 3;
public int numOfHearts;
public Image[] hearts;
public Sprite heartFull;
public Sprite heartEmpty;
public GameObject effect;
public Image healthDisplay;
private void Update()
{
for (int i = 0; i < hearts.Length; i++)
{
if (i < health)
{
hearts[i].sprite = heartFull;
}
else
{
hearts[i].sprite = heartEmpty;
if (i < numOfHearts)
{
hearts[i].enabled = true;
}
else
{
hearts[i].enabled = false;
}
}
if (health <= 0)
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
transform.position = Vector2.MoveTowards(transform.position, targetPos, speed * Time.deltaTime);
if (Input.GetKeyDown(KeyCode.UpArrow) && transform.position.y < maxHeight)
{
Instantiate(effect, transform.position, Quaternion.identity);
targetPos = new Vector2(transform.position.x, transform.position.y + Yincrement);
}
else if (Input.GetKeyDown(KeyCode.DownArrow) && transform.position.y > minHeigth)
{
Instantiate(effect, transform.position, Quaternion.identity);
targetPos = new Vector2(transform.position.x, transform.position.y - Yincrement);
}
}
}
}
Main Issue
The main problem is your for loop. It should only be used for updating the UI - not for multiple times calling your movement and Scene reload! You should close your for loop earlier.
for (int i = 0; i < hearts.Length; i++)
{
// you can also reduce these to single lines
hearts[i].enabled = i < numOfHearts;
if(i < numOfHearts) hearts[i].sprite = i < health ? heartFull : heartEmpty;
} // <-- ALREADY CLOSE IT HERE
if(health <= 0) ...
...
Position clamping
Your clamping of the position is extremely insecure! Imagine the current position being 3.1 which is still < maxHeight so you add the Yincrement once and it results in a maximum possible height of 6.3! That's not what you wanted.
You should clamp directly on the targetPosition not on the current transform.position. You could for example use Mathf.Clamp for making sure the targetPos.y always stays within the given range.
Also since both cases do something very similar I would reduce this to only one movement using a simply int variable for setting the direction:
...
transform.position = Vector2.MoveTowards(transform.position, targetPos, speed * Time.deltaTime);
var move = 0;
var targetPosY = targePos.y;
if (Input.GetKeyDown(KeyCode.UpArrow) && targetPosY < maxHeight)
{
move = 1;
}
else if (Input.GetKeyDown(KeyCode.DownArrow) && targetPosY > minHeigth)
{
move = -1;
}
// Handle the movement according to the direction
// in one single code block for both directions
if(move != 0)
{
Instantiate(effect, transform.position, Quaternion.identity);
// increase/decrease the targetPosY according to the move direction
targetPosY += move * Yincrement;
// Now make sure it is within the range
targetPosY = Mathf.Clamp(targetPosY, minHeight, maxHeight);
// finally assign the new target position
targetPos = new Vector2(transform.position.x, targetPosY);
}
Could you please specify in detail what your game is about and what you are trying to do in each part of the script. I might be able to help you then. Also, if this is your first contact with programming, this is way to advanced. Start with something simpler and first understand the basic concepts of programming before moving on. Here is a good tutorial series to learn c# programming for absolute beginners.
https://www.youtube.com/watch?v=pSiIHe2uZ2w
I am not sure what you are trying to do but why is your movement control within your for loop. That might be why your are messing up. Try removing all of this code out of the for loop.
transform.position = Vector2.MoveTowards(transform.position, targetPos, speed * Time.deltaTime);
if (Input.GetKeyDown(KeyCode.UpArrow) && transform.position.y < maxHeight)
{
Instantiate(effect, transform.position, Quaternion.identity);
targetPos = new Vector2(transform.position.x, transform.position.y + Yincrement);
}
else if (Input.GetKeyDown(KeyCode.DownArrow) && transform.position.y > minHeigth)
{
Instantiate(effect, transform.position, Quaternion.identity);
targetPos = new Vector2(transform.position.x, transform.position.y - Yincrement);
}
Also this code should be after you check if the player has moved.
transform.position = Vector2.MoveTowards(transform.position, targetPos, speed * Time.deltaTime);
Here is a tip to make movement a little easier. Use transform.translate instead of Vector2.MoveTowards. Transform.translate takes in 3 floats and changes you player's position by the three floats represented as a vector.
Here is an example
transform.translate(0,2,0);
This will change the players y position by 2. I thing your code should look like this.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class Player : MonoBehaviour
{
private Vector2 targetPos;
public float Yincrement;
public float speed;
public float maxHeight;
public float minHeigth;
public int health = 3;
public int numOfHearts;
public Image[] hearts;
public Sprite heartFull;
public Sprite heartEmpty;
public GameObject effect;
public Image healthDisplay;
private void Update()
{
for (int i = 0; i < hearts.Length; i++)
{
if (i < health)
{
hearts[i].sprite = heartFull;
}
else
{
hearts[i].sprite = heartEmpty;
if (i < numOfHearts)
{
hearts[i].enabled = true;
}
else
{
hearts[i].enabled = false;
}
}
}
if (health <= 0)
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
if (Input.GetKeyDown(KeyCode.UpArrow) && transform.position.y < maxHeight)
{
transform.Translate(0,Yincrement,0)
}
else if (Input.GetKeyDown(KeyCode.DownArrow) && transform.position.y > minHeigth)
{
Instantiate(effect, transform.position, Quaternion.identity);
transform.Translate(0,Yincrement,0);
}
}
}
After I receive your reply, I possibly could help but there are no guarantees because I only have about a years worth of experience too.