Detect tap on a sphere in unity - unity3d

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.

Related

Rotate object to target, while being rotated to match the terrain slope

In the image above
the red vector is the spider's forward vector
the blue vector is the vector representing the direction between the spider and it's target
In the code below, orientation is a vector that's representing the normal of the terrain, so that the spider gets aligned to it:
Vector3 orientation = GetTerrainNormal();
Quaternion rotationNeeded = Quaternion.FromToRotation(Vector3.up, orientation);
transform.rotation = Quaternion.RotateTowards(
transform.rotation,
rotationNeeded,
RotationSpeed * Time.deltaTime
);
My issue is that I cannot manage to make the spider face its target... When I add any code that would make it rotate towards it, then it's not aligned with the terrain's normals anymore, it says straight...
So basically, how can I make the spider rotate on the Y world axis (I think), while still then being rotated to match the slope?
Full answer
In case it helps someone else, here's the full answer:
Vector3 orientation = GetTerrainNormal();
Vector3 directionToTarget = (target.position - transform.position).Y(0);
float d = Vector3.Dot(directionToTarget, orientation);
directionToTarget -= d * orientation;
if (directionToTarget.sqrMagnitude > 0.00001f) {
directionToTarget.Normalize();
Quaternion rotationNeeded = Quaternion.LookRotation(directionToTarget, orientation);
transform.rotation = Quaternion.RotateTowards(
transform.rotation,
rotationNeeded,
xRotationSpeed * Time.deltaTime
);
}
This answer on the unity forums was extremely helpful: https://forum.unity.com/threads/look-at-object-while-aligned-to-surface.515743/
Try this
Vector3 directionToTarget = target.transform.position - transform.position;
Quaternion rotationNeeded = Quaternion.LookRotation(directionToTarget, orientation);
First of all, I'm not sure why you need a code to orient the spider manually to the terrain. You can make the spider a Rigidbody and the Unity engine will take care of it for you.
Regardless, you want to rotate the spider around the local Y-Axis (this will keep the current orientation).
You can do this using transform.LookAt() (referring to the blue vector in the picture) (documented here) and passing the up vector as the 2nd argument.

Math: How to spin a wheel by drag&drop (Canvas, 2D)?

I'm struggling with probably simple math to spin/rotate a wheel using drag&drop.
There is a Radial Layout in a Canvas (Unity UI) and it can already be rotated by setting a property called StartAngle that is in a range from 0-360. In this Radial there are items, so the StartAngle is for the first item and places all the child elements around the layout radius.
I want to implement drag & drop for the items so that you can drag a child around and the Radial will spin accordingly (infinitely).
Right now, I have this as a starting point:
public void OnDrag(PointerEventData eventData)
{
var delta = eventData.delta.x * Time.deltaTime;
var newAngle = radialLayout.StartAngle + delta;
if (newAngle >= 360)
newAngle = newAngle - 360;
else if (newAngle < 0)
newAngle = Mathf.Abs(360 - newAngle);
radialLayout.StartAngle = newAngle;
}
It kind of works but doesn't feel very smooth. This is for mobile/touch, so I want both the X and Y delta of the drag operation to be taken into account. Apparently, the y delta is not considered in my example and I have no idea how to incorporate this correctly. The user might do a linear drag & drop on either axis or he/she might also do like a circular drag movement.
So how can I map mouse movement to a rotation angle from 0-360 so that it feels good?
Edit: Thanks for the help, I did it like this now:
public void OnDrag(PointerEventData eventData)
{
// Note the "Head-Minus-Tale rule for Vector subtraction, see http://www.maths.usyd.edu.au/u/MOW/vectors/vectors-3/v-3-7.html
// vSourceToDestination = vDestination - vSource;
// First, we draw a vector from the center point of the radial to the point where we started dragging
var from = dragStartPoint - (Vector2)radialLayout.transform.position;
// Next, we draw a vector from the center point of the radial to the point we are currently dragging on
var to = eventData.position - (Vector2)radialLayout.transform.position;
// Now, we calculate the angle between these two:
var dragAngle = Vector2.SignedAngle(from, to);
// Lerping makes movement fast at the beginning slow at the end
var lerpedAngle = Mathf.Round(Mathf.LerpAngle(radialLayout.StartAngle, dragAngle, 0.5f));
radialLayout.StartAngle = lerpedAngle;
}
I don't know all of your code and types but I would have an idea. I can't test this right now and can not garant that it even works like this but I hope the idea gets clear.
I would probably rather use something like
// This is the vector from the center of the object to the mouse/touch position
// (in screen pixel space)
var touchDirection = eventData.position - Camera.main.WorldToScreenPoint(transform.position);
// angle between the Up (Y) axis and this touchDirection
// for the angle the length of the up vector doesn't matter so there is
// no need to convert it to pixel space
var targetAngle = Vector2.SignedAngle(Vector2.up, touchDirection);
// since the returned angle might be negative wrap it to get values 0-360
if(targetAngle < 0) targetAngle += 360;
// Now either simply use Lerp
// this would simply interpolate each frame to the middle of both values
// the result is a fast movement at the beginning and a very slow at the end
radialLayout.StartAngle = Mathf.Lerp(radialLayout.StartAngle, targetAngle, 0.5f);
// or maybe use a fixed speed like 30°/second
var difference = targetAngle - radialLayout.StartAngle;
radialLayout.StartAngle += Mathf.Sign(difference) * Mathf.Min(30f * Time.deltaTime, Mathf.Abs(difference));
Typed on smartphone but I hope the idea gets clear

Car Collision Return Force - 3D Car Game

As per my game requirements, I was giving manual force when two cars collide with each other and move back.
So I want the correct code that can justify this. Here is the example, collision response that I want to get:
As per my understanding, I have written this code:
Vector3 reboundDirection = Vector3.Normalize(transform.position - other.transform.position);
reboundDirection.y = 0f;
int i = 0;
while (i < 3)
{
myRigidbody.AddForce(reboundDirection * 100f, ForceMode.Force);
appliedSpeed = speed * 0.5f;
yield return new WaitForFixedUpdate();
i++;
}
I am moving, my cars using this code:
//Move the player forward
appliedSpeed += Time.deltaTime * 7f;
appliedSpeed = Mathf.Min(appliedSpeed, speed);
myRigidbody.velocity = transform.forward * appliedSpeed;
Still, as per my observation, I was not getting, collision response in the proper direction. What is the correct way for getting above image reference collision response?
Until you clarify why you have use manual forces or how you handle forces generated by Unity Engine i would like to stress one problem in your approach. You calculate direction based on positions but positions are the center of your cars. Therefore, you are not getting a correct direction as you can see from the image below:
You calculate the direction between two pivot or center points therefore, your force is a bit tilted in left image. Instead of this you can use ContactPoint and then calculate the direction.
As more detailed information so that OP can understand what i said! In the above image you can see the region with blue rectangle. You will get all the contact points for the corresponding region using Collision.contacts
then calculate the center point or centroid like this
Vector3 centroid = new Vector3(0, 0, 0);
foreach (ContactPoint contact in col.contacts)
{
centroid += contact.point;
}
centroid = centroid / col.contacts.Length;
This is the center of the rectangle to find the direction you need to find its projection on your car like this:
Vector3 projection = gameObject.transform.position;
projection.x = centroid.x;
gameObject.GetComponent<Rigidbody>().AddForce((projection - centroid )*100, ForceMode.Impulse);
Since i do not know your set up i just got y and z values from car's position but x value from centroid therefore you get a straight blue line not an arrow tilted to left like in first image even in the case two of second image. I hope i am being clear.

Unity 3D Ball Follow the finger

I am trying to create a 3d game like ketchapp ball race, in which the cube slides along a road, and the left right movement is controlled using touch
The problem I am facing is that the touch senstivity seems to react different on different devices, due to which I am not able to calculate the left-right displacement for all devices.
This is how I am calculating the left-right displacement of the cube:
Vector2 touchDeltaPosition = Input.GetTouch(0).deltaPosition ;
transform.Translate(touchDeltaPosition.x * .1f * Time.deltaTime, 0, 0);
However this is not working properly all device . Any help will be highly appreciated
See this answer: https://stackoverflow.com/a/25740565/10063126
Basically, ScreenToWorldPoint was used.
World position is computed; not screen touch position.
But you have to manually solve for delta position.
Example:
Vector3 currPos = Input.mousePosition;
Vector3 startPos = Camera.main.ScreenToWorldPoint(currPos);
Vector3 endPos = Camera.main.ScreenToWorldPoint(prevPos);
Vector3 deltaPos = endPos - startPos;
transform.Translate(deltaPos.x * sensitivity * Time.deltaTime, 0, 0);
prevPos = currPos;
How about using 2 buttons on your screen one for left control and one for right control and then when the left button is pressed you can give a value to go left and same for right button. This way your ball's movement will be independent from the touched position's X value.

How to determine thickness vs width? (Using Raycasting)

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.