3

Im making a grappling gun, that pulls an object towards the player if the layer is LightWeight, and pull the player towards the object if the layer is Ground

            int groundLayer_mask = LayerMask.GetMask("Ground");
            int lightWeightLayer_mask = LayerMask.GetMask("LightWeight");

            //Shoots a raycast, and only works if layer is Ground
            if (Physics.Raycast(Shoulder.transform.position, cam.transform.forward, out raycastHit, float.PositiveInfinity, groundLayer_mask))
            {
                //Hit Something
                debugHitpointTransform.position = raycastHit.point;
                hookshotPosition = raycastHit.point;
                hookShotSize = 0f;
                HookShotTransform.gameObject.SetActive(true);
                HookShotTransform.localScale = Vector3.zero;
                layerHit = 0;

                state = State.HookShotThrown;
            }
            //Shoots a raycast, and only works if layer is LightWeight
            else if (Physics.Raycast(Shoulder.transform.position, cam.transform.forward, out raycastHit, float.PositiveInfinity, lightWeightLayer_mask))
            {
                //Hit Something
                debugHitpointTransform.position = raycastHit.point;
                hookshotPosition = raycastHit.point;
                hookShotSize = 0f;
                HookShotTransform.gameObject.SetActive(true);
                HookShotTransform.localScale = Vector3.zero;
                layerHit = 1;

                state = State.HookShotThrown;
            }

But if im looking in the direction of an object with Ground layer, even if there is one with the layer LightWeight closer than the one with Ground, it goes for the one with Ground cuz i have it's if statement first. Any way to make it either not go through the object infront just cuz the first if is looking for one behind, or to make it prioritize the one infront?

derHugo
  • 83,094
  • 9
  • 75
  • 115

1 Answers1

4

Both your raycast cases basically do the exact same thing, except for the value of layerHit. So either way this is a waste of resources ;)

So in order to be more efficient and also achieve what you want simply include both layers in your layer mask and make only one single raycast against both layers -> it will use whatever it hits first from the given layers.

You can then still check what layer you actually have hit inside the if block.

So I would expose the field to be configurable in the Inspector

// Configure this via the Inspector
// it will be a drop-down from which you can select multiple entries
public LayerMask layers;

private void Awake ()
{
    // if you really want to keep getting these by code
    layers = LayerMask.GetMask("Ground", "LightWeight");
}

And also in general I would also use an enum in order to have some meaningful names like e.g.

public enum HitType
{
    None = -1,
    Ground,
    Lightweight
}

and then use a single ray cast like

int groundLayer_mask = LayerMask.NameToLayer("Ground");
int lightWeightLayer_mask = LayerMask.NameToLayer("LightWeight");

var hitType = HitType.None;

if (Physics.Raycast(Shoulder.transform.position, cam.transform.forward, out raycastHit, float.PositiveInfinity, layers))
{
    //Hit Something
    debugHitpointTransform.position = raycastHit.point;
    hookshotPosition = raycastHit.point;
    hookShotSize = 0f;
    HookShotTransform.gameObject.SetActive(true);
    HookShotTransform.localScale = Vector3.zero;

    if(raycastHit.collider.gameObject.layer == groundLayer_mask) hitType = HitType.Ground;
    else if(raycastHit.collider.gameObject.layer == lightWeightLayer_mask) hitType = HitType.Lightweight;

    state = State.HookShotThrown;
}
derHugo
  • 83,094
  • 9
  • 75
  • 115
  • 1
    @Brain_Grunt yes exactly like that. Basically an `enum` still is an `int` underlaying (by default .. you can even change the underlying type to e.g. `byte`, `uint` etc) .. but this way you have a specific name for your values and don't have to remember all the time what exactly `0` or `1` actually stands for. Since these are compile time constant though you could again use a `switch` which would be more efficient – derHugo Dec 17 '21 at 14:56
  • 1
    @Brain_Grunt where, how exactly are you trying to use it? currently it would only exist in the method that does the raycast .. unless you create a class field for it instead of using `var` – derHugo Dec 17 '21 at 15:12
  • 1
    @Brain_Grunt yeah in that case either pass it on as a parameter to your method or store it in some class field – derHugo Dec 17 '21 at 15:50
  • 1
    In general btw in Unity you want to use `Debug.Log` instead of `Console.WriteLine` ;) – derHugo Dec 17 '21 at 15:51
  • how do i store it in a class field? – TerazikMubaloo Dec 17 '21 at 15:54
  • 1
    By not using `var` but in class level have a `private HitType hitType;` and then rather assign that one – derHugo Dec 17 '21 at 15:56
  • so adding ```public class HitTypeClass { private HitType hitType; }``` and replacing ```var hitType = HitType.None;``` with ```hitType = HitType.None;```? – TerazikMubaloo Dec 17 '21 at 16:04
  • 1
    You don't need a class for this. in your class where you do the raycast and previously where using the `private int layerHit;` now you simply replace that by `private HitType hitType;` .. and just remove the line `var hitType = HitType.None;` – derHugo Dec 17 '21 at 16:15
  • 1
    now im getting an error in unity on the line ```public LayerMask layers = LayerMask.GetMask("Ground", "LightWeight");``` the error being ```NameToLayer is not allowed to be called from a MonoBehaviour constructor (or instance field initializer), call it in Awake or Start instead. Called from MonoBehaviour 'HugoMovement'. See "Script Serialization" page in the Unity Manual for further details.``` – TerazikMubaloo Dec 17 '21 at 16:24
  • 1
    Ah then just remove that and only do `public LayerMask layers;` and simply select your layers in the Inspector. If you really want to do it via code then move that to e.g. `private void Awake (){ layers = LayerMask.GetMask("Ground", "LightWeight"); }` – derHugo Dec 17 '21 at 16:27
  • it wasnt actually changing the hitType so i just moved the part ```state = State.HookShotThrown;``` to inside the if statement ```if (raycastHit.collider.gameObject.layer == groundLayer_mask)``` to check if maybe it wasnt detecting the layer of the gameObjects being hit, and i was right. Meaning theres something wrong with those if statements or the part making ```groundLayer_mask``` and ```lightWeightLayer_mask```. – TerazikMubaloo Dec 17 '21 at 17:45
  • do you know any way that would probably work to actually make it detect the layers? – TerazikMubaloo Dec 17 '21 at 20:13
  • 2
    works for me with `void Update(){Vector3 dir;if (Input.GetMouseButtonDown(0)) dir = Vector3.forward;else if (Input.GetMouseButtonDown(1)) dir = Vector3.right;else return;int gm = LayerMask.NameToLayer("Default");int lwm = LayerMask.NameToLayer("Water");int layers = 1 << gm | 1 << lwm;if (Physics.Raycast(transform.position, dir, out RaycastHit raycastHit, float.PositiveInfinity, layers)){if (raycastHit.collider.gameObject.layer == gm) Debug.Log("ground"); else if (raycastHit.collider.gameObject.layer == lwm) Debug.Log("lightweight");}}` in new scene with 3 cubes. – Ruzihm Dec 17 '21 at 20:56
  • 1
    where cube 1 has the script attached, cube 2 is forward of cube 1 with Water layer, cube 3 is right of cube 1 with Default layer. also works if `layers` is instead a field configured in the editor to include Default and Water layers. new project* hence the default layers used – Ruzihm Dec 17 '21 at 20:58
  • @Ruzihm just tried that, but its only working when the layer is anything but the 2 i specified, i tried starting from a backed up version of the script without all this tinkering, slowly adding parts, and i noticed the problem is probably because of ```int layers = 1 << gm | 1 << lwm;```. So do i have to change the number in that line depending on what layer number my layers im using are on? – TerazikMubaloo Dec 18 '21 at 10:21
  • @Ruzihm nvm, i think ive pretty much figured it out – TerazikMubaloo Dec 18 '21 at 11:02
  • 1
    @Brain_Grunt [please explain](https://i.redd.it/7khgt29lp1121.jpg). No, the numbers should not need to change. – Ruzihm Dec 18 '21 at 14:59
  • So i fixed it by just copying the the code you said in the comment starting in ```works for me with...``` and just changed the layers you wrote with the ones i wanted, i think the problem was that i had forgoten to specify the layers in the editor, cuz i couldnt see any other difference. – TerazikMubaloo Dec 18 '21 at 15:37