Issues using a 2D Raycast to detect mouse hover on object colliders - c#

Im currently working on a 2D character selection screen in Unity that should operate similarly to the Mortal Kombat character selection screen. Currently, I have a class called CharacterSelector attached to the main camera. The class holds methods for selection/deselection of characters, hover events, and selection confirmation. I was able to use a RayCast2D to build my character selection method; however, I am running into issues using it for hover events.
In my scene, I have a group of character images that the player can choose from (if they are unlocked). When the player hovers over the character with his/her mouse, the character image should be surrounded by a yellow border. When the user clicks on the desired character, a larger version of the image will popup to the left of the character image group.
Right now, I have the following code for the hover method:
public void onHover(Ray ray, RaycastHit2D hit)
{
if(hit.collider == null)
{
Debug.Log("nothing hit");
}
if (Physics2D.Raycast(ray.origin, ray.direction, Mathf.Infinity))
{
print(hit.collider.name);
}
}
This method belongs to a class that the CharacterSelection class inherits from. The following is on the CharacterSelection class:
class CharacterSelector : Selector
{
Ray ray;
RaycastHit2D hit;
public void Start()
{
ray = Camera.main.ScreenPointToRay(Input.mousePosition);
}
void Update()
{
onHover(ray, hit);
if (Input.GetMouseButtonDown(0))
{
selectCharacter();
}
}
}
Also, all the character images that I am trying to hover over currently have 2D Box Colliders. As of right now, I am unable to get hover operation to work. It does not print the name of the character image to the console. I am using this as a first step to see if Unity recognizes the character image or not. Let me know if I can provide additional information!

Mouse changes position on screen almost every frame. This means your ray should be updated every frame according to mouse position. So I moved the corresponding statement from Start() to Update();
class CharacterSelector : Selector
{
Ray ray;
RaycastHit2D hit;
public void Start()
{
}
void Update()
{
ray = Camera.main.ScreenPointToRay(Input.mousePosition);
onHover(ray, hit);
if (Input.GetMouseButtonDown(0))
{
selectCharacter();
}
}
}
Secondly, if you look in the definition of Physics2D.Raycast you'll find that it gives you back the RaycastHit2D object. This is the object you should check the collider of, not your hit object which should have thrown an NullReferenceException, I don't know why it didn't. So this:
public void onHover(Ray ray, RaycastHit2D hit)
{
hit = Physics2D.Raycast(ray.origin, ray.direction, Mathf.Infinity);
if(hit.collider == null)
{
Debug.Log("nothing hit");
}
else
{
print(hit.collider.name);
}
}
Apart from this you don't really need the hit argument in onHover() function, it can be a local variable in the function. But if you are planning on using your hit variable in CharacterSelector script than you should change the declaration of onHover to: onHover(Ray ray, out RaycastHit2D hit) the out keyword passes the variable by reference instead of a copy of its value.
Also, I think checking if mouse is hovering over an object shouldn't be done in onHover, it's kinda misleading and doesn't seem logical to me. I'd move the body of onHover to another function like RaycastChecker(). I'd call onHover() only if mouse is actually hovering over the sprite. (like in OnCollisionEnter you take it granted that the collision did happen, so should the onHover, I think)

Related

Detecting if 2D Collider is touching Mouse Pointer from Other script in Unity 2D

I am working on a project using Unity2D, the goal is to be able to reference other main scripts to gain information from. In this specific case, I need to detect if the mouse pointer is touching a collider, however, from a separate script.
Usually, I would be able to create a boolean, and on mouse over set it to true, on mouse exit set it to false, like this:
bool isHovered;
void OnMouseEnter() {
isHovered = true;
}
void OnMouseExit() {
isHovered = false;
}
However, in the script, instead of doing this for each individual script, I would like to reference another script, like this:
public GameManager g;
void Update() {
if (g.IsTouchingMouse(gameObject)) { //Code }
}
But there's multiple problems with this. In my game manager class, I would need something like this
public bool IsTouchingMouse(gameObject g) { return value }
Which there is multiple issues with this, because I don't have a way to register the OnMouseEnter and OnMouseExit events for those objects on another script, and I don't have a way to store the values for every single gameObject to ensure this will work for every object without having to manually modify this script.
I'm looking for two things, #1, how can I detect mouseovers on objects from scripts who's parents are not that gameObject, two, are there any ideas on how I would go about creating a function to return this variable instantly?
Somewhat annoying, but I figured out a solution a few minutes after posting. So I will share it here.
public bool IsTouchingMouse(GameObject g)
{
Vector2 point = Camera.main.ScreenToWorldPoint(Input.mousePosition);
return g.GetComponent<Collider2D>().OverlapPoint(point);
}
What this code is basically doing, is making a function that takes a gameObject as an input, creates a vector2 based on the position of the mouse cursor in world space, and then returns weather or not the 2D collider that the object contains is actually touching this imaginary point, the variable "point" should be interchangable with any 2D world space location. I was pretty much overcomplicating the entire issue.
Find two resources you like and import them into Unity's Assets and set their Texture Type to Cursor
Create a new script and mount it to an empty object.
Open the script, create three public variables of type Texture2D, return to Unity and drag your favorite pointer map to the variable window.
To set the object label, we judge which pointer to change by customizing the label; so first set the label for the object. For example, the ground, I added the Ground tag to it, the wall added the Wall tag; the column added the Cylinder tag.
Write a method to change the mouse pointer, where the main API for changing the pointer is Cursor.SetCursor()
void setCursorTexture()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);//Define the ray pointed by the mouse in the game window
RaycastHit hitInfo; //Information of ray collision
if (Physics.Raycast(ray, out hitInfo))//Determine whether to hit the object
{
// switch pointer
switch (hitInfo.collider.gameObject.tag)
{
case "Ground":
Cursor.SetCursor(groundCr, new Vector2(16, 16), CursorMode.Auto);
break;
case "Wall":
Cursor.SetCursor(wallCr, new Vector2(16, 16), CursorMode.Auto);
break;
case "Cylinder":
Cursor.SetCursor(CylinderCr, new Vector2(16, 16), CursorMode.Auto);
break;
default:
Cursor.SetCursor(null,Vector2.zero,CursorMode.Auto);
break;
}
}
}
Implement this method in the Update() method called every frame.
END (you can go to run the program) Thank you and hope to help you
This method works without exception. To solve the problem try the following method:
public Collider2D collider; // target Collider
void Update()
{
var mouseRay = Camera.main.ScreenPointToRay(Input.mousePosition);
if (collider.bounds.IntersectRay(mouseRay)) Debug.Log("Mouse on collider..");
}
For New Input System?
But Input.mousePosition gives a new system error in Unity. To solve this problem, call the mouse position as shown below:
var mousePos = Mouse.current.position.ReadValue();
You can also check if (new input system enabled) is active as below:
#if ENABLE_INPUT_SYSTEM
var mousePos = Mouse.current.position.ReadValue();
#else
var mousePos = Input.mousePosition;
#endif
then follow the direction of the camera ray:
var mouseRay = Camera.main.ScreenPointToRay(mousePos);

Unity touch/mouse collide instead of MouseDown()

I have a sprite which at the moment I detect the mouse click on it.
However I really need to detect when the finger or mouse touches or moves across the sprite because the finger/mouse click event will occur somewhere else on the screen and not on top of the sprite.
public class Hand : MonoBehaviour
{
private void OnMouseDown()
{
if (Input.GetMouseButtonDown(0))
{
this.transform.gameObject.SetActive(false);
}
}
}
Update:
I tried to detect both touching the sprite and the mouse is down but I dont get the button clicked. I can remove the && Input.GetMouseButtonDown(0) and it will work if the mouse moves over but I want both mouse/finger over the sprite and the pressed down.
private void Update()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, 1000) && Input.GetMouseButtonDown(0))
hit.collider.GetComponent<Button>().onClick.Invoke();
}
There is 2 possible solution :
First: Instead of just using sprites, use button and assign it to world space canvas, in
this case touch should work if you assign canva's event camera to main camera.
2nd :If u just want to use sprites, then assign a collider to it, and use raycast
Ray ray = Camera.main.ScreenPointToRay(Input.GetTouch(i).position);
if (Physics.Raycast(ray,out hit,1000))
hit.collider.gameobject.GetComponent<Button>.onClick().Invoke();
Edit : The above code will work if you have a button component attached to your sprite. if not
Just call something like this
if(hit.collider.gameobject.GetComponenet<Sprite>())
DoWhateverYouWant()
Use the EventSystem interfaces to detect any kind mouse interaction with UI elements.
The EventSystem interfaces are listed here.
For example, if you want to detect when the mouse hovers over a sprite, you would add a script to the sprite object like so:
using UnityEngine.EventSystems;
using UnityEngine;
public class MouseOverHandler: MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
public void OnPointerEnter(PointerEventData data){
Debug.Log("MouseEnter");
}
public void OnPointerExit(PointerEventData data){
Debug.Log("MouseExit");
}
}
Note that the EventSystem works with the raycaster on the associated sprite's canvas. So if sprites are sitting on top of eachother, they can block raycasts and so the PointEnter event only gets triggered on the topmost sprite

Unity GameOject .SetActive(false) when clicked or touched

I wish to detect if a GameObject is touched or clicked and deactivate it. I have followed a Youtube tutorial but cant get it to work.
I get the error Cannot convert from UnityEngine.Ray to UnityEngine.Vector2
I have the script attached to the object.
public class Hand : MonoBehaviour {
void Update() {
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit2D hit2D;
if (Input.GetMouseButtonDown(0)) {
if (Physics2D.Raycast(ray, out hit2D)) {
transform.gameObject.SetActive(false);
}
}
}
}
You are doing it wrong.
When a click is detected you are disabling the object. Unity has a very handy way of dealing with this problem. Here is the code for it:
private void OnMouseDown()
{
if (Input.GetMouseButtonDown(0))
{
this.SetActive(false);
}
}
Physics2D.Raycast has no version with Ray parameters; and is not really intended to work with the Z axis, which a camera-to-world cast needs, either.
Use OnMouseDown() instead. If on the clicked object itself, as transform.gameObject.SetActive(false); indicates to be the case, no further action is needed.
If for an external object, you can get the mouse world position with var worldMousePoint
= (Vector2)Camera.main.ScreenPointWorldPoint(Input.mousePosition);, and then check for overlap on a target Collider2D with worldMousePoint == targetCollider2D.ClosestPoint(worldMousePoint).

2D Raycast does not correctly detect overlapping colliders and ignores Sorting Layer

I want to be able to interact with my gameobjects while organising my sprites with Sorting Layer.
Right now, Sorting Layer do not affect my Raycast/collider situation and only change the visual appearance of my order.
In my Test scene I have two sprites: a Background and one Object.
Both have a 2D Box Collider component.
My Raycast script is suppose to detect on which Object my mouse is hovering on; either the Background (BG) or the Object, which lays on top of the BG.
As long as the BG collider is enabled in the inspector, my script will ONLY recognize the BG, even if the other object lays on top of my BG (visually and according to the Sorting Layer) (both Z-positions = 0, Sorting Layer Default (BG: 0, Object 1)).
The collider have the correct size and if I disable or remove the Collider from the BG, my script will detect the other Object just fine.
The only way to make sure that my Object gets detected, while my BG collider is activated is by changing the Objects Z position to a position between my BG and my Camera (between smaller 0 and greater -10)
I tried to:
this is a fresh 2D project, so my cameras Projection is set to Orthograph, my BG and Object use Sprite Renderer and is use 2D collider
change the "Order in Layer", back and forth, including negative numbers.
add a Foreground (and Background) "Sorting Layer"
(both options only had a visual effect and did not affect the Raycast output)
change the positions in the hierarchy.
removing and re-adding the colliders. (!) I observed that the 2D Box collider I added last, will always be behind every other colliders. This is something I can work with for now. But will become a problem later in the future, when the project gets bigger.
I want to be able to interact with items and doors etc. (clicks and reactions to mouse hovering) in my 2D click adventure. If 2D Raycast is not the way to go to archive this, I would be glad to know about the proper technique to use in this situation.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RayCast : MonoBehaviour
{
[SerializeField] private Vector2 mousePosWorld2D;
RaycastHit2D hit;
void Update()
{
mousePosWorld2D = (Vector2)Camera.main.ScreenToWorldPoint(Input.mousePosition);
hit = Physics2D.Raycast(mousePosWorld2D, Vector2.zero);
if (hit.collider)
{
Debug.Log("Found collider: " + hit.collider.name);
}
else
{
Debug.Log("No collider found.");
}
}
}
I expected the "Sorting Layer" or "Order in Layer" to have an impact on my Raycast, while changing the Z position does nothing. But it is the other way around. Sorting Layer only have a visual effect and moving my Object closer to the camera (Z position) helps to detect the right collider.
How do I solve the problem?
What gets hit first from a Ray has only to do with how close the collider is to the camera, and nothing with the order in the Hierarchy or the SortinLayers.
To find the "highest" RaycastTarget based on the SortingLayer, I created the Script below.
We fire a Raycast and iterate over all the Colliders that we hit, by using a foreach-loop. Then the function will return the GameObject with the highest SortingLayer. Note: If there is more than one GameObject with the highest SortingLayer, the function will return the last one it found.
using UnityEngine;
public class Raycast : MonoBehaviour
{
// The name of the relevant Layer
[SerializeField] string layerToCheck = "Default";
void Update()
{
Vector2 mousePosWorld2D = (Vector2)Camera.main.ScreenToWorldPoint(Input.mousePosition);
GameObject result = GetHighestRaycastTarget(mousePosWorld2D);
// Do Stuff example:
Debug.Log($"Highest Layer: {result}");
if (Input.GetMouseButtonDown(0)
&& result != null)
{
Destroy(result);
}
}
// Get highest RaycastTarget based on the Sortinglayer
// Note: If multiple Objects have the same SortingLayer (e.g. 42) and this is also the highest SortingLayer, then the Function will return the last one it found
private GameObject GetHighestRaycastTarget(Vector2 mousePos)
{
GameObject topLayer = null;
RaycastHit2D[] hit = Physics2D.RaycastAll(mousePos, Vector2.zero);
foreach (RaycastHit2D ray in hit)
{
SpriteRenderer spriteRenderer = ray.transform.GetComponent<SpriteRenderer>();
// Check if RaycastTarget has a SpriteRenderer and
// Check if the found SpriteRenderer uses the relevant SortingLayer
if (spriteRenderer != null
&& spriteRenderer.sortingLayerName == layerToCheck)
{
if (topLayer == null)
{
topLayer = spriteRenderer.transform.gameObject;
}
if (spriteRenderer.sortingOrder >= topLayer.GetComponent<SpriteRenderer>().sortingOrder)
{
topLayer = ray.transform.gameObject;
}
}
}
return topLayer;
}
}
Have you tried with the script that it's in the Unity official documentation?
https://docs.unity3d.com/ScriptReference/Physics.Raycast.html
using UnityEngine;
// C# example.
public class ExampleClass : MonoBehaviour
{
void Update()
{
// Bit shift the index of the layer (8) to get a bit mask
int layerMask = 1 << 8;
// This would cast rays only against colliders in layer 8.
// But instead we want to collide against everything except layer 8. The ~ operator does this, it inverts a bitmask.
layerMask = ~layerMask;
RaycastHit hit;
// Does the ray intersect any objects excluding the player layer
if (Physics.Raycast(transform.position, transform.TransformDirection(Vector3.forward), out hit, Mathf.Infinity, layerMask))
{
Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * hit.distance, Color.yellow);
Debug.Log("Did Hit");
}
else
{
Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * 1000, Color.white);
Debug.Log("Did not Hit");
}
}
}

I am using Raycast in unity and i want to get the position of an object when i click on it but i does not work and i don't know what i am doing wrong

So when I am clicking on the object in the game I get this error...
NullReferenceExceptionm : Object reference not set to an instance of an object
JumpDestination.Update () (at Assets/Scripts/JumpDestination.cs.:12)
I don't know what I am doing wrong,how can I fix it?
I want to get the position of the hited object.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class JumpDestination : MonoBehaviour {
private RaycastHit hit;
public float jumpMaxDistance;
void Update(){
Physics.Raycast (Camera.main.ScreenPointToRay (Input.mousePosition), out hit, jumpMaxDistance);
if (hit.collider.gameObject.tag == "RichPoint") {
print (hit.collider.transform.position);
}
}
}
I don't know what I am doing wrong,how can I fix it? I want to get the
position of the hited object.
3 things you did wrong:
1.You did not check if mouse is pressed before raycasting.
2.You did not check if Physics.Raycast hit anything before printing the object's position.
3.You defined the hit variable outside a function. Not a good idea because it will still store the old object the mouse hit. Declare that in the update function.
FIX:
void Update()
{
//Check if mouse is clicked
if (Input.GetMouseButtonDown(0))
{
RaycastHit hit;
//Get ray from mouse postion
Ray rayCast = Camera.main.ScreenPointToRay(Input.mousePosition);
//Raycast and check if any object is hit
if (Physics.Raycast(rayCast, out hit, jumpMaxDistance))
{
//Check which tag is hit
if (hit.collider.CompareTag("RichPoint"))
{
print(hit.collider.transform.position);
}
}
}
}
Regardless, this answer was made to show you what you did wrong. You should not be using this. Use Unity's new EventSystems for this. Check the 5.For 3D Object (Mesh Renderer/any 3D Collider) from this answer for proper way to detect clicked object.

Categories