I am working on a 2D Game. I am detecting when an object collides with the player, but I am getting a little stuck trying to find which side it is colliding on. I want to be able to rotate the player to face either Left, Right, Up, or Down towards the object it collided with. How can I find which side the object collided on so that I can then rotate the player accordingly?
I am using a Rigidbody2D on the player and 2D Colliders, I am able to find the point that it collides with the player using Collider2D.ClosestPoint() but am unsure how to proceed. Thank you for the help.
So once you have the hit point
var hitPoint = collider.ClosestPoint();
// Instead of the transform.position the collider.bounds.center is often more accurate
var playerPosition = collider.bounds.center;
You could e.g. take the direction between those and check where it hits relative to the player like e.g.
var dir = hitPoint - playerPosition;
var angle = Vector2.SignedAngle(transform.right, dir);
// Now check the angle
if(Maths.Abs(angle) <= 45)
{
Debug.Log("hit right");
}
else if(angle > 45 && angle <= 135)
{
Debug.Log("hit top");
}
else if(angle < 45 && angle >= -135)
{
Debug.Log("hit bottom");
}
else
{
Debug.Log("hit left");
}
The simplest solution I see is that you first get the size of the player 2D collider with BoxCollider2D.size. Then you create a simple block of if statements to check on which side is the hit point based on the player position.
Something like this should work:
var size = collider.size;
var hitPoint = collider.ClosestPoint();
var playerPosition = transform.position;
if (hitPoint.x >= playerPosition + size.x/2)
// hit on the right
else if (hitPoint.y >= playerPosition + size.y/2)
// hit on the top
else if (hitPoint.x <= playerPosition - size.x/2)
// hit on the left
else if (hitPoint.y <= playerPosition - size.y/2)
// hit on the bottom
Note that there may be some problems in the edge cases. You reorder the if statements based on your wanted behaviour.
Hope this help.
Related
I need to find the angle between the swipe in Unity3D. In the update function I'm using the following code:
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
fingerup=Input.mousePosition;
fingerdown=Input.mousePosition;
}
if (Input.GetMouseButtonUp(0))
{
fingerdown=Input.mousePosition;
CheckSwipe();
}
}
and in the CheckSwipe function I'm doing this:
CheckSwipe()
{
var directionvector = fingerup-fingerdown;
if(directionvector.magnitude>SwipeThreshold)
{
/* I need to find the angle between the fingerup and the fingerdown vectors and based on that angle I need to check if the user swiped left, right, up or down. */
/*if (angle>= 45 && angle< 135) onSwipeUp.Invoke();
else if (angle>= 135 && angle < 225) onSwipeRight.Invoke();
else if (angle>= 225 && angle< 315) onSwipeDown.Invoke();
else if (angle>= 315 && angle< 360 || angle >= 0 && angle< 45) onSwipeLeft.Invoke();*/
}
}
How can I get the angle of swipe? I tried various solutions, but it was not working. Any help from any one is appreciated. Thanks in advance.
First thing, during button mouse up event, you should assign value to fingerup variable, not down.
If this doesn't fix your issue, I came up with idea to use trigonometry to calculate the angle:
if (Input.GetMouseButtonDown(0))
{
fingerup = Input.mousePosition;
fingerdown = Input.mousePosition;
}
if (Input.GetMouseButtonUp(0))
{
fingerup = Input.mousePosition;
double angle = Mathf.Atan((fingerup.x - fingerdown.x) / (fingerup.y - fingerdown.y)) * Mathf.Rad2Deg;
if (fingerup.y < fingerdown.y) angle += 180;
if (fingerup.y > fingerdown.y && fingerup.x < fingerdown.x) angle += 360;
Debug.Log("fingerDownPos : " + fingerdown + " fingerUpPos: " + fingerup + " angle: " + angle);
}
Probably it can be written better, but I had no other idea but to use ifs and add angles depending on start and end. Anyway, it's working, and should be a good start.
Edit: your question is about an angle, but I just realized, that since you need only the general direction of a swipe (left, right, up, down) you don't need to calculate angle. Just substract fingerup.x - fingerdown.x and fingerup.y - fingerdown.y Compare which absolute value of this is bigger. If abs from vertical is bigger than horizontal, then it will be either up or down, up if the substraction is greater than 0. You get the idea how to do the rest.
I am working on a small mini-game that requires the rotation of a cube 90 degrees in the appropriate direction based on the direction you swipe. So you could swipe up and it would rotate up 90 degrees, and them immediately after, swipe left, and it would swipe 90 degrees to the left from your current rotation (so it would stay rotated up 90 degrees as well). I feel like this should be really simple, but it's giving me a ton of trouble.
I would like to use Lerp/Slerp so that the rotation looks nice, though it isn't entirely necessary. The way I currently have it implemented, each time I call my "SlerpRotateLeft()" function for example, it only rotates to the exact same exact rotation relative to the world each time (instead of the current rotation + 90 degrees in the correct direction).
I have been reading up on Quaternions and Euler angles all day, but I'm still not entirely sure what my problem is.
I am currently using states to determine when the object is currently rotating and in what direction, though I feel like I may be overcomplicating it. Any possible solution to this problem (where you can swipe in a particular direction, in any order, in succession, to rotate a cube 90 degrees in that particular direction). Previously, I attempted to use coroutines, but those didn't have the desired effect either (and I was unable to reset them).
Here is my class. It works and you can test it by dropping the script into any cube object in-editor, but it doesn't work as intended. You will see what my problem is by testing it (I recommend placing an image on the cube's front face to track which one it is). I'm not sure if I explained my problem properly, so please let me know if any more information is needed.
****UPDATE: I have accepted #Draco18s's answer as correct, because their solution worked. However, I did not completely understand the solution, or how to store the value. I found an answer to a similar question that also used Transform.Rotate, and stored the value, which helped clear the solution up. The key seemed to be storing it in a GameObject instead of in a Quaternion like I originally thought. I thought I should provide this code in case anyone stumbles upon this and is equally confused, though you may not need the swipe detection:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Rotater : MonoBehaviour
{
private GameObject endRotation;
//SWIPE VARIABLES
public Vector2 touchStart = new Vector2(0, 0);
public Vector2 touchEnd = new Vector2(0, 0);
public Vector2 currentSwipe = new Vector2(0, 0);
public Vector2 currentSwipeNormal = new Vector2(0, 0);
// Use this for initialization
void Start()
{
endRotation = new GameObject();
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
touchStart = new Vector2(Input.mousePosition.x, Input.mousePosition.y);
//Debug.Log("Touched at: " + touchStart);
}
if (Input.GetMouseButtonUp(0))
{
touchEnd = new Vector2(Input.mousePosition.x, Input.mousePosition.y);
//Get Swipe Vector information
currentSwipe = new Vector2(touchEnd.x - touchStart.x, touchEnd.y - touchStart.y);
//Normalize Swipe Vector
currentSwipeNormal = currentSwipe;
currentSwipeNormal.Normalize();
//Swipe up
if (currentSwipeNormal.y > 0 && currentSwipeNormal.x > -0.5 && currentSwipeNormal.x < 0.5)
{
endRotation.transform.Rotate(-Vector3.left, 90, Space.World);
}
//Swipe down
if (currentSwipeNormal.y < 0 && currentSwipeNormal.x > -0.5 && currentSwipeNormal.x < 0.5)
{
endRotation.transform.Rotate(Vector3.left, 90, Space.World);
}
//Swipe left
if (currentSwipeNormal.x < 0 && currentSwipeNormal.y > -0.5 && currentSwipeNormal.y < 0.5)
{
endRotation.transform.Rotate(Vector3.up, 90, Space.World);
}
//Swipe right
if (currentSwipeNormal.x > 0 && currentSwipeNormal.y > -0.5 && currentSwipeNormal.y < 0.5)
{
endRotation.transform.Rotate(-Vector3.up, 90, Space.World);
}
}
LerpRotate();
}
void LerpRotate()
{
transform.rotation = Quaternion.Lerp(transform.rotation, endRotation.transform.rotation, Time.deltaTime * 10);
}
}
Use Transform.RotateAround
You're encountering an issue where you take the current Euler angles and try and add/subtract 90, which does not necessarily correlate to the desired position, due to the rotated nature of the rotated reference frame.
But using RotateAround, you can pass in the global Up, Left, and Forward vectors, which is what you're trying to do.
I have made some code that should make my player move into the direction of the finger:
if (Input.touchCount == 1 && Input.GetTouch(0).phase == TouchPhase.Moved)
{
Vector2 pos = Camera.main.ScreenToWorldPoint(new Vector3(Input.GetTouch(0).position.x, Input.GetTouch(0).position.y, 0));
if (pos.x < rb.position.x)
{
movehorizontal = -1;
}
if(pos.x > rb.position.x)
{
movehorizontal = 1;
}
if (pos.y < rb.position.z)
{
movevertical = -1;
}
if(pos.y > rb.position.z)
{
movevertical = 1;
}
}
Vector3 movement = new Vector3(movehorizontal, 0.00f, movevertical)*speed;
It's a 3D game, with a top-view, so my player starts at 0,0,0 and only moves along the x and z axis. My camera is positionated on 0,10,3. The following works on the x axis, so when my finger touches on the right side it goes to the right, if on the left to the left, but no matter where I touch it, it will only move to the front and not to the bottom of my screen.
I tried debugging, but the instructions werent working at the time.
screentoWorldPoint should be stored as a vector3. also since the camera is 10 units away from your plane the last parameter should be 10.
edit, that will only work for a cam pointing straight down. this code should work regardless of the camera angle.
Vector3 pos = Camera.main.ScreenToWorldPoint(new Vector3(Input.GetTouch(0).position.x, Input.GetTouch(0).position.y, 1f));
Vector3 pointDelta = pos - Camera.main.transform.position;
float multiplier = -Camera.main.transform.position.y / pointDelta.y;
pos = Camera.main.transform.position + pointDelta * multiplier;
finally these lines should compare the z values to each other
if (pos.z < rb.position.z)
if(pos.z > rb.position.z)
make those changes and let us know if any other problems still exist
Visual aids
Thickness vs width: here
Please view the short gif.
Thickness here is different from width as there are multiple walls as there are outer and inner cylinders. Thickness is the measurement of the distance between the outer/inner wall of any side of the cylinder where as thickness is the distance from one end to the other encompassing the hollow space between.
Quick synopsis on the gifs provided
-On every click the origin point (blue) and destination point (orange) orbs are created to denote where the user clicks and the interpreted end point used to calculate the distance (displayed on the GUI).
The origin defines where the user clicks on the surface of an objects collider and the destination defines the point, perpendicular with the world Y axis of the origin, where a second ray cast towards the first ray, hits the other side of the collider.
Current:
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
//obtain the vector where the ray hit the collider.
hitPoint = hit.point; //origin point
//offset the ray, keeping it along the XZ plane of the hit
Vector3 offsetDirection = -1 * hit.normal;
//offset a long way, minimum thickness of the object
ray.origin = hit.point + offsetDirection * 100;
//point the ray back at the first hit point
ray.direction = (hit.point - ray.origin).normalized;
//raycast all, because there might be other objects in the way
RaycastHit[] hits = Physics.RaycastAll(ray);
foreach (RaycastHit h in hits)
{
if (h.collider == hit.collider)
{
hitBack = h.point; //destination point
}
}
}
Currently, width is the functionality in place. I want to calculate thickness without having to go inside of an object (as seen in the gif).
Amazing reference
http://answers.unity3d.com/questions/386698/detecting-how-many-times-a-raycast-collides-with-a.html
This guy basically had the same question as me and has a solution that could possibly work. I'm not sure how Linecasting works vs Raycasting.
Keep:
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
//obtain the vector where the ray hit the collider.
hitPoint = hit.point; //origin point
//offset the ray, keeping it along the XZ plane of the hit
Vector3 offsetDirection = -1 * hit.normal;
//offset a long way, minimum thickness of the object
ray.origin = hit.point + offsetDirection * 100;
//point the ray back at the first hit point
ray.direction = (hit.point - ray.origin).normalized;
Replace:
//raycast all, because there might be other objects in the way
RaycastHit[] hits = Physics.RaycastAll(ray);
foreach (RaycastHit h in hits)
{
if (h.collider == hit.collider)
{
hitBack = h.point; //destination point
}
}
With (credits to MirrorMirror's insightful post, and #ryemoss for his instrumental advice and assistance):
int counter = 0;
bool calculating = false; //set this to true on click
Vector3 Point, PreviousPoint, Goal, Direction;
Point = ray.origin;
Goal = hit.point;
Direction = ray.direction;
PreviousPoint = Vector3.zero;
while (calculating == true)
{
counter++;
RaycastHit hit2;
if (Physics.Linecast(Point, Goal, out hit2))
{
if(counter > 100)
{
hitBack = hitPoint;
counter = 0;
calculating = false;
break;
}
PreviousPoint = hit2.point;
Point = hit2.point + (Direction / 10000f);
}
else
{
if (PreviousPoint == Vector3.zero)
hitBack = hitPoint;
else
hitBack = PreviousPoint;
calculating = false;
counter = 0;
}
}
Linecast vs Raycast
With a raycast you set the start point, the direction, and the distance to check in that direction, with a linecast you simply set start and end points and it checks between those 2 points.
So, if you know the end destination specifically, use linecast, if you want to check in a specific direction but have no specific end point, use raycast.
Solution
First, use the initial raycast to obtain the first point, hit.point. Then, set the ray.origin to a point in world space outside the collider (the collider of the object we first collided with to obtain hit.point), and set the ray.direction to face the ray back at the first point, hit.point.
Finally, use a while loop to create a new linecast, at ray.origins new position (updated each time through the while loop until a linecast reaches hit.point), each time a collision with the object occurs until a linecast reaches hit.point. Once hit.point has been reached, it means every surface of the object was hit and on each hit, a new line was created until a line reached the first initial point, hit.point. To calculate thickness, take the distance between the first hit, hit.point, and the hit previous to the reverse linecast hitting hit.point, PreviousPoint.
UPDATE
1-Revise the code to properly handle 1-sided objects (ex: Planes).
2-Added counter to prevent special cases in which calculation not possible.
3-Improve readability.
I want to detect a tap on sphere. I have searched on google and many approaches like ontriggerenter() and using ray cast etc were found.
But what I want to get some values also that where user has touched on ball.
Like if the center of a sphere is x=0, y=0. then I should got positive x and y when user touch upper right corner of sphere.
It is not necessary to get exactly this value, Value can be anything but by these value I should be able to know that user has tapped on one of these 8 portions of ball.
upper right near center of circle
lower right near center of circle
upper left near center of circle
lower left near center of circle
upper right near corner of circle
lower right near corner of circle
upper left near corner of circle
lower left near corner of circle
You can use the methods you found, with raycasting you will get the exact position of the tap. Then you have to transform the centerpoint and the tap position of the sphere to screenspace coordinates. Then you can get the difference between those vectors to get the direction and distance from the center.
PS: Do circles have corners?
you need to have a GameObject with a Collider attatched to it which is set to "Is Trigger". And a script containing following code.
void OnMouseDown()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
//hits for sure, because we are in the click event
Physics.Raycast(ray, out hit);
Vector3 hitOnScreen = Camera.main.WorldToScreenPoint(hit.point);
Vector3 centerOnScreen = Camera.main.WorldToScreenPoint(transform.position);
Vector3 offset = hitOnScreen - centerOnScreen;
if(offset.x > 0 && offset.y > 0)
{
Debug.Log("upper right");
}else if(offset.x < 0 && offset.y > 0)
{
Debug.Log("upper left");
}else if(offset.x > 0 && offset.y < 0)
{
Debug.Log("lower right");
} else //if(offset.x < 0 && offset.y < 0)
{
Debug.Log("lower left");
}
}
For the distance to the center it is getting more complicated.
We have to get a vector perpendicular to the from the camera to the center and scale this to the propper size. If we add this to the center and transform it into screenspace we will have the maximum magnitude a point on the surface of the sphere could reach.
Vector3 perpendicular = Vector3.Cross(transform.position - Camera.main.transform.position, Vector3.one);
Vector3 maxDistToCenter = transform.position + (perpendicular.normalized * (transform.lossyScale.x * 0.5f));
Vector3 maxDistToCenterOnScreen = Camera.main.WorldToScreenPoint(maxDistToCenter);
float maxDist = (maxDistToCenterOnScreen - centerOnScreen).magnitude;
float dist = offset.magnitude;
if (dist < maxDist * 0.5f) // 50% of the way not the area
{
Debug.Log("inner 50%");
}
else
{
Debug.Log("outer 50%");
}
Combining both grant you the ability to evaluate any point on the sphere in angle and distance to the center compared with the maximum distance.
If you just want to apply force to the point clicked, you just need a ridgit body and raycasting. This oficial Unity tutorial will explay it.
Maybe you have to combine both answers, but I think the Unity tutorial is sufficient for your needs.
PS: Please ask questwions with the final goal you want to reach and not just a step you think is needed to get the job done.
You dont need to se raycasts, you can use "OnMouseDown".
Documentation here: http://docs.unity3d.com/ScriptReference/MonoBehaviour.OnMouseDown.html
Note: your object needs a collider in order for this to work.