How to ray-cast through objects.

I have these two blocks on top of each other they are exactly the same except with different tags. When i create a ray-cast i try to get 1 of the 2 objects depending on which tag i tell it to get, sometimes it cant get it because there on top of each other. How do i create a ray-cast to check both of the objects i cant use the ignore ray-cast layer because they both have to be checked. Im currently using this code:

        if (Physics.Raycast (CrosshairRay, out hit, PickUpDistance)) {

            if(hit.collider.gameObject.tag == "tag1"){

}

                if(hit.collider.gameObject.tag == "tag2"){

}
     
        }

Hi, you can use the physic matrix system with layers Unity - Manual: Layer-based collision detection

Set them to different layers and use a layer mask to choose which one you’re aiming for at the moment you fire the raycast- you can set it to ignore specific layers or to only find objects in certain layers (or use some combination of layers), depending on which approach you want. There’s no need to change the layers of the objects themselves programmatically or to use the layer collision matrix at all, just make sure they aren’t the same layer as one-another and you’re set.

Here’s an example of me aiming only for a “Terrain” layer and ignoring everything else- it uses a binary “bit mask” which may be confusing as first, but well worth learning:

if (Physics.Raycast(Camera.main.ScreenPointToRay(location), out hit, Mathf.Infinity, 1 << LayerMask.NameToLayer("Terrain")))
8 Likes

Whats a “bit mask”? and also with the way i set my code up i need to check both objects in the same update for it to work.
Would it be efficient to cast 2 rays in the same update?

Just use Unity - Scripting API: Physics.RaycastAll instead and then iterate through all of the hit results, in that case.

You should still use the layermask though IMO (keep them in the same layer if you RaycastAll), to keep from getting a billion results to iterate through.

A “bit mask” or “flags” is a binary sequence like “01001011101001” and each spot is basically its own flag (a boolean value, true or false).

In terms of the layers, each layer is its own spot in that binary sequence, and the zero or one says whether it should be detected or ignored. Something like “1 << 8” makes an 8 digit binary number like “10000000”, in other words “1 moved 8 places to the left”. We read them right to left, so it would be “the eighth flag is true, and all of the rest are false”, or in this case “the eighth layer should be detected, and ignore the rest”. I used the LayerMask.NameToLayer() in order to get the layer number of the layer name “Terrain” in my game, and then used that to push a 1 that many spaces to the left, which creates a bitmask that tells the raycast to ignore all layers but that one (the eighth layer).

1 << LayerMask.NameToLayer("Terrain")

It’s worth googling and looking up information on bitwise operations and “flags” if you’re interested.

3 Likes

If box nr1. are in a different layer then box 2, you can setup your ray so only box1 gets hit, no matter if it stays in-front or not, you do this in the physics sections. You set up a rule for the layer with your ray on, and the layer with box 1 on, so they both are in the collision matrix, box2 will be ignored.

You’re missing what he was asking for. He says he wants to hit both boxes- at first I thought he wanted to be able to actively select which box he was raycasting against at any given point, which he can do with layers easily (without the collision matrix), but he just stated that he wants to be able to tell if he’s hitting none or either or both with a single raycast, which means RaycastAll instead (and they can be on the same layer). Neither of those are assisted by flipping options in the collision matrix unless you can do it programmatically and reset it when you finish, and that’s overcomplicating this problem like crazy.

The problem with RaycastAll is that i have more then 50k objects in my scene and i have a long ray, i think you can see why that’s a problem. I just want a ray to detect 2 objects and no more.

As I said:

All you have to do is put those two on a different layer than most of the other stuff and use a layermask to avoid exactly that problem.

EDIT: If you want to be tricky/clever, you can also make a new empty GameObject as a child of each of those two objects, giving them a new (identical) collider to their parent, and changing the layer to something new (different than their parents). When you RaycastAll, you can choose the layermask for the layer the children are on (they’ll be the only two objects in the scene on that layer) and then do “hit.transform.parent” to refer to the parent of the child object you hit. It’s a bit roundabout, but it’ll keep you from having to change the layers of the main objects themselves, if you were wanting to keep them in their current layers for some other reason.

there’s a problem with that, all the 50k objects are clones.

I suppose i could change its layer when the ray hits it making it different from the others and then setting it back when the ray is not on it, but i don’t know how to change objects layer through a script and detect if an object is being hit by a ray.

Okay, your idea with the changing layers on hit is not going to work, so if changing layers before the raycast is out of the question, then the best way to do what you want is likely to keep or have access to a collection of the objects that you want to check the raycast against- “targets”. Pull out references to each of the colliders on the “targets”, and then iterate over them, using the Raycast function on the collider itself, not the Physics one. That will check if the ray is connecting with that target in particular.

If it’s hitting it, you can then do whatever. That said, if you have access to a list of targets beforehand, you can also just use my suggestion of adding a child object with a different layer and identical collider to the “targets”, then RaycastAll with a layermask of the children’s layer. That approach would require a bit more set-up time, but be far more efficient than raycasting once per object every single frame.

i,ve never worked with collider ray-casts before how would i go about setting it up?

I think it would help if i describe my situation a bit better: i have a minecraft-like game I,m trying to create the block placing system, i thought of the idea of creating a prefab(“BlockSensor”)with 6 planes on each side named “top” “left” “front” etc. i move this “BlockSensor” Prefab on whatever block the player is looking at and when right clicked it will create a block depending on which side your looking at, and this is how I,m doing it:

        RaycastHit hit;
        Ray CrosshairRay = new Ray(transform.position,transform.forward,1 << LayerMask.NameToLayer("Terrain"));
       
        Debug.DrawRay (transform.position, transform.forward * PickUpDistance);
        if (Physics.Raycast (CrosshairRay, out hit, PickUpDistance)) {

            if(hit.collider.gameObject.tag == "Block")
            CurrentBlockSensor.transform.position = new Vector3(hit.collider.gameObject.transform.position.x, hit.collider.gameObject.transform.position.y, hit.collider.gameObject.transform.position.z);

            if(Input.GetMouseButtonDown(1)){

                if(hit.collider.gameObject.name == "Top"){
                    Debug.Log("t");
                    Instantiate(Block,new Vector3(CurrentBlockSensor.transform.position.x,Mathf.RoundToInt(CurrentBlockSensor.transform.position.y) + 1,CurrentBlockSensor.transform.position.z),Quaternion.Euler(0,0,0));
                }else if(hit.collider.gameObject.name == "Right"){
                    Debug.Log("r");
                    Instantiate(Block,new Vector3(CurrentBlockSensor.transform.position.x,CurrentBlockSensor.transform.position.y,Mathf.RoundToInt(CurrentBlockSensor.transform.position.z) + 1),Quaternion.Euler(0,0,0));
                }else if(hit.collider.gameObject.name == "Left"){
                    Debug.Log("l");
                    Instantiate(Block,new Vector3(CurrentBlockSensor.transform.position.x,CurrentBlockSensor.transform.position.y,Mathf.RoundToInt(CurrentBlockSensor.transform.position.z) - 1),Quaternion.Euler(0,0,0));
                }else if(hit.collider.gameObject.name == "Front"){
                    Debug.Log("f");
                    Instantiate(Block,new Vector3(Mathf.RoundToInt(CurrentBlockSensor.transform.position.x) + 1,CurrentBlockSensor.transform.position.y,CurrentBlockSensor.transform.position.z),Quaternion.Euler(0,0,0));
                }else if(hit.collider.gameObject.name == "Back"){
                    Debug.Log("b");
                    Instantiate(Block,new Vector3(Mathf.RoundToInt(CurrentBlockSensor.transform.position.x) - 1,CurrentBlockSensor.transform.position.y,CurrentBlockSensor.transform.position.z),Quaternion.Euler(0,0,0));
                }else if(hit.collider.gameObject.name == "Bottom"){
                    Debug.Log("bo");
                    Instantiate(Block,new Vector3(CurrentBlockSensor.transform.position.x,Mathf.RoundToInt(CurrentBlockSensor.transform.position.y) - 1,CurrentBlockSensor.transform.position.z),Quaternion.Euler(0,0,0));
                }
               
                //Instantiate(Block,new Vector3(Mathf.RoundToInt(position.x), Mathf.RoundToInt(position.y), Mathf.RoundToInt(position.z)), hit.collider.gameObject.transform.rotation);
            }
            if (hit.collider.tag == "Block") {
                    if(Input.GetMouseButton(0)){
                    Destroy(hit.collider.gameObject);
                }
            }
        }

Ahh, I see. Rather pointless, I’m sorry to say, and overcomplicating the heck out of a simple problem.

If you’re placing blocks, then you know exactly what the “normals” at the point of a raycast impact would be. You don’t need 6 objects per block, only 1, a cube collider, and when you raycast the ray will hit a straight, flat surface and report back the exact angle of the point where contact occurred (this is called a “normal”). Based on whether the normal is facing Vector3.right, Vector3.up, etc… you can tell exactly which side it was, and where a new block needs to be generated.

I actually did EXACTLY this with a project I was working on a couple of years ago. Let me look around and see if I can find it, and it may give you some ideas for how to do more than just this.

Sounds easy enough to implement, but how would i check the “normals” which by your description i’m guessing means the angle of raycast hit.

It’s a part of the RaycastHit, so if your hit is called “hit”, then it’s “hit.normal”.

by “angle” do you mean “suface”, and what does hit.normal return, vector3?

In Unity, the normal of a surface is basically the perpendicular line in the direction that it’s “facing”, so imagine it as a little arrow sticking straight out from an object at 90 degrees. And yes, it returns a Vector3, but the Vector3 is a “direction” and not a “position”. To get the exact position that the impact occurs, you would use “hit.point” instead, but that’s not necessary here.

So how would i put this into effect with my current code?

Since it’s a normal for a perfectly uniform cube object, it’s going to give perfect Vector3 directions (up, down, left, right) which correspond to Vector3(0,0,1) Vector3(0,1,0) etc… That being the case, you can just take the position of the block that was hit and then instantiate a new block in that position + the normal direction (so 14, 5, 5 would become 14, 6, 5 if you clicked on top), assuming all blocks use the default 1x1x1 measurements anyways (no upscaling or downscaling) like default cube primitives.

You can make things more accurate, and complicated, by dynamically determining the size of the block and multiplying the normal by that (so 0, 1, 0 becomes 0, 10, 0 for blocks 10 units wide), then add that to the “hit block”'s position and instantiate a new block there. You could also round the x, y, z values of the normal to the nearest whole numbers so that your blocks don’t need to be perfectly straight or w/e- but that’s all up to you and how you’re doing things.

Anyways, here’s the project I did- it’s not really made to just be imported into an existing project- load it into a blank one please. A few notes: it’s completely generated in code, so the scene looks empty until you hit Play. It was also created with the old GUI system (which was horrendous) and I only made it as a math experiment a few years ago, when I first started using Unity. For whatever reason, the “selected cube” highlighting doesn’t appear to be working, but keep in mind that clicking a cube will select it and allow the functions in the bar to be applied to it, like moving it around and such. It might help you and it might not.

Out of curiosity, is 50K objects actually working for you? Most Mine Craft likes don’t use actual cubes, they use a voxel system.

You don’t have to change it if its working, but I thought I’d point it out. The performance benefits of using a voxel system instead of individual cubes will be amazing. Of course it probably invalidates most of your existing code base as well.