Search Unity

How to ray-cast through objects.

Discussion in 'Scripting' started by Epic-Username, Aug 8, 2015.

  1. Epic-Username

    Epic-Username

    Joined:
    May 24, 2015
    Posts:
    339
    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:

    Code (CSharp):
    1.         if (Physics.Raycast (CrosshairRay, out hit, PickUpDistance)) {
    2.  
    3.             if(hit.collider.gameObject.tag == "tag1"){
    4.  
    5. }
    6.  
    7.                 if(hit.collider.gameObject.tag == "tag2"){
    8.  
    9. }
    10.      
    11.         }
     
  2. Ironmax

    Ironmax

    Joined:
    May 12, 2015
    Posts:
    890
  3. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    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:
    Code (csharp):
    1. if (Physics.Raycast(Camera.main.ScreenPointToRay(location), out hit, Mathf.Infinity, 1 << LayerMask.NameToLayer("Terrain")))
     
  4. Epic-Username

    Epic-Username

    Joined:
    May 24, 2015
    Posts:
    339
    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?
     
    Last edited: Aug 8, 2015
  5. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    Just use http://docs.unity3d.com/ScriptReference/Physics.RaycastAll.html 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).
    Code (csharp):
    1. 1 << LayerMask.NameToLayer("Terrain")
    It's worth googling and looking up information on bitwise operations and "flags" if you're interested.
     
    Last edited: Aug 8, 2015
    dradb, kdrkrndr and Kiwasi like this.
  6. Ironmax

    Ironmax

    Joined:
    May 12, 2015
    Posts:
    890
    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.
     
  7. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    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.
     
  8. Epic-Username

    Epic-Username

    Joined:
    May 24, 2015
    Posts:
    339
    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.
     
  9. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    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.
     
    Last edited: Aug 8, 2015
  10. Epic-Username

    Epic-Username

    Joined:
    May 24, 2015
    Posts:
    339
    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.
     
  11. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    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.

    http://docs.unity3d.com/ScriptReference/Collider.Raycast.html

    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.
     
  12. Epic-Username

    Epic-Username

    Joined:
    May 24, 2015
    Posts:
    339
    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:
    Code (CSharp):
    1.         RaycastHit hit;
    2.         Ray CrosshairRay = new Ray(transform.position,transform.forward,1 << LayerMask.NameToLayer("Terrain"));
    3.        
    4.         Debug.DrawRay (transform.position, transform.forward * PickUpDistance);
    5.         if (Physics.Raycast (CrosshairRay, out hit, PickUpDistance)) {
    6.  
    7.             if(hit.collider.gameObject.tag == "Block")
    8.             CurrentBlockSensor.transform.position = new Vector3(hit.collider.gameObject.transform.position.x, hit.collider.gameObject.transform.position.y, hit.collider.gameObject.transform.position.z);
    9.  
    10.             if(Input.GetMouseButtonDown(1)){
    11.  
    12.                 if(hit.collider.gameObject.name == "Top"){
    13.                     Debug.Log("t");
    14.                     Instantiate(Block,new Vector3(CurrentBlockSensor.transform.position.x,Mathf.RoundToInt(CurrentBlockSensor.transform.position.y) + 1,CurrentBlockSensor.transform.position.z),Quaternion.Euler(0,0,0));
    15.                 }else if(hit.collider.gameObject.name == "Right"){
    16.                     Debug.Log("r");
    17.                     Instantiate(Block,new Vector3(CurrentBlockSensor.transform.position.x,CurrentBlockSensor.transform.position.y,Mathf.RoundToInt(CurrentBlockSensor.transform.position.z) + 1),Quaternion.Euler(0,0,0));
    18.                 }else if(hit.collider.gameObject.name == "Left"){
    19.                     Debug.Log("l");
    20.                     Instantiate(Block,new Vector3(CurrentBlockSensor.transform.position.x,CurrentBlockSensor.transform.position.y,Mathf.RoundToInt(CurrentBlockSensor.transform.position.z) - 1),Quaternion.Euler(0,0,0));
    21.                 }else if(hit.collider.gameObject.name == "Front"){
    22.                     Debug.Log("f");
    23.                     Instantiate(Block,new Vector3(Mathf.RoundToInt(CurrentBlockSensor.transform.position.x) + 1,CurrentBlockSensor.transform.position.y,CurrentBlockSensor.transform.position.z),Quaternion.Euler(0,0,0));
    24.                 }else if(hit.collider.gameObject.name == "Back"){
    25.                     Debug.Log("b");
    26.                     Instantiate(Block,new Vector3(Mathf.RoundToInt(CurrentBlockSensor.transform.position.x) - 1,CurrentBlockSensor.transform.position.y,CurrentBlockSensor.transform.position.z),Quaternion.Euler(0,0,0));
    27.                 }else if(hit.collider.gameObject.name == "Bottom"){
    28.                     Debug.Log("bo");
    29.                     Instantiate(Block,new Vector3(CurrentBlockSensor.transform.position.x,Mathf.RoundToInt(CurrentBlockSensor.transform.position.y) - 1,CurrentBlockSensor.transform.position.z),Quaternion.Euler(0,0,0));
    30.                 }
    31.                
    32.                 //Instantiate(Block,new Vector3(Mathf.RoundToInt(position.x), Mathf.RoundToInt(position.y), Mathf.RoundToInt(position.z)), hit.collider.gameObject.transform.rotation);
    33.             }
    34.             if (hit.collider.tag == "Block") {
    35.                     if(Input.GetMouseButton(0)){
    36.                     Destroy(hit.collider.gameObject);
    37.                 }
    38.             }
    39.         }
     
  13. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    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.
     
  14. Epic-Username

    Epic-Username

    Joined:
    May 24, 2015
    Posts:
    339
    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.
     
  15. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
  16. Epic-Username

    Epic-Username

    Joined:
    May 24, 2015
    Posts:
    339
  17. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    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.

     
  18. Epic-Username

    Epic-Username

    Joined:
    May 24, 2015
    Posts:
    339
    So how would i put this into effect with my current code?
     
  19. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    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.
     
  20. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    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.
     
  21. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    That seems like it's going to happen anyways, as he has 6 colliders to correspond to each "direction" of each cube. My suggestion that he uses the normal of the raycast impact to determine where to generate a new cube is sort of premised around him using normal cubes and deleting all of his 6 "side" objects, otherwise the offset in the math is going to be different depending on the direction. I have absolutely no idea how voxels work, and how that would be different than what I've suggested though.
     
  22. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Look it up if you are curious. Its quite fascinating.

    In a nutshell voxels work by combining all of the cubes into a single mesh, and only producing the faces that are actually visible to the player. Each time a cube is moved the local chunk of mesh is regenerated.

    Its a far more preformant system then doing physics for 50K cubes, without it mine craft would be impossible.
     
  23. Epic-Username

    Epic-Username

    Joined:
    May 24, 2015
    Posts:
    339

    I use this code on each chunk of blocks which makes it efficient:
    Code (CSharp):
    1.     void OnTriggerEnter(Collider other){
    2.         if (other.gameObject.CompareTag ("Player") || other.gameObject.CompareTag ("Sensor")) {
    3.             stay = true;
    4.             foreach (Transform obj in transform) {
    5.                 obj.GetComponentInChildren<MeshRenderer> ().enabled = true;
    6.                 obj.GetComponentInChildren<BoxCollider> ().enabled = true;
    7.             }
    8.         }
    9.     }
    10.     void OnTriggerExit(Collider other){
    11.         if (other.gameObject.CompareTag ("Player") || other.gameObject.CompareTag ("Sensor")) {
    12.             stay = false;
    13.         }
    14.     }
    15.     void OnBecameVisible(){
    16.         foreach (Transform obj in transform) {
    17.             obj.GetComponentInChildren<MeshRenderer> ().enabled = true;
    18.             obj.GetComponentInChildren<BoxCollider> ().enabled = true;
    19.         }
    20.     }
    21.     void OnBecameInvisible(){
    22.         if (stay == false) {
    23.             foreach (Transform obj in transform) {
    24.                 obj.GetComponentInChildren<MeshRenderer> ().enabled = false;
    25.                 obj.GetComponentInChildren<BoxCollider> ().enabled = false;
    26.             }
    27.         }
    28.     }
    I could probably render more then 1m blocks with unity using this code, but when it first start it generates the world and from all the memory and processing required to render at start it would crash, if only unity could pause and take a break if it senses too much memory or processing then continue.

    The problem with this code is that you can see the world updating as you look around i need to program some sort of predictive rendering system later in game.
     
    Last edited: Aug 8, 2015
  24. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    I actually did something similar using math in my cube-generator project that I linked earlier, though I think I quit before I finished it. Essentially in my project, I generated the triangles that made up the faces of the cubes myself- making "tile" objects (single sided only), each cube class object having six tile objects in an array. I did it that way so that I could "slant" the top tile in any direction and have the sides adjust by re-arranging the triangles and shutting off the display of the triangles that now stuck up beyond the top tile's geometry, and keep themselves looking pretty.

    After I finished all of the basic stuff, like being able to generate new cubes by clicking on others, deleting clicked cubes, texture placement on individual "tiles", etc... I moved onto ways to optimize things, and one of those was detecting if the cube's triangles were pressed up against other geometry and having them disable themselves when they were. The "move cube" event would propagate from the moving cube to connected cubes (connected before and after the move) forcing all of those affected to re-examine their faces to see if they needed to be visible.

    Looking back over that project a few minutes ago after not having touched it for so long made me realize how far I've come though. It's so rough, and unnecessarily complicated!
     
    Kiwasi likes this.
  25. Epic-Username

    Epic-Username

    Joined:
    May 24, 2015
    Posts:
    339
    Thanks this helped alot:

    Code (CSharp):
    1. if(Input.GetMouseButtonDown(1)){
    2.                    
    3.                     Instantiate(Block,new Vector3(hit.collider.gameObject.transform.position.x,hit.collider.gameObject.transform.position.y,hit.collider.gameObject.transform.position.z) + hit.normal,Quaternion.Euler(0,0,0));
    4.                    
    5.                 }
     
  26. Epic-Username

    Epic-Username

    Joined:
    May 24, 2015
    Posts:
    339
    Is there anyway i can improve the performance of my game, all i'm doing to improve performance is not rendering blocks off screen and also the blocks disappear too early so you see empty chunks everywhere until you walk over to them how do i fix this?
     
  27. RelayRay

    RelayRay

    Joined:
    Mar 28, 2019
    Posts:
    2
    Orrrr you could set the layer of the object to IgnoreRaycast (builtIn)
     
  28. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    All of this is built-in functionality, and the OP explicitly stated that the IgnoreRaycast approach wasn't an option (nor would it be anyways IMO).

    Truly not to be an ass here, but if you're going to necro ridiculously old threads, please be sure that the information you're adding is worth notifying everyone involved. I obviously don't remember this thread four years later, so I had to spend a few minutes re-reading it for context to reply, and I'm a pretty busy person. Necro-ing isn't inherently evil, but it makes prudence all of the more important.