Search code examples
c#unity-game-engineangleraycasting

C# Unity 3D - Is there a way to make custom raycast shapes?


I have a ship that will shoot targets, but cannons on the right side should never try to shot at targets on the left side of the ship. Thus I have created sectors using the SignedAngle function, this works fine for testing, but its also kind of broken as you can see from the visualization below. I have tried using boxcast but alas it also doesnt work for this use case.

img1

The above image visualizes what my script does, but this is not a solution to my problem. As targets close to the side and front of the ship will be outside the sector, for clarification what I mean, see picture 3. img2

This second image shows what happens when we increase the angle, we can now detect more targets, but we have 2 big incorrect sectors marked in red which shouldnt be there. img3 Correct

Finally, this is how I think it should look, its still a cone, but the big difference is that it starts with a wide bottom, thus it resolves the problem Im having with the current SignedAngle function which determines everything from a single point in the middle.

This is the script for assigning targets to the correct list according to which sector they are in:

foreach (Transform target in EnemyListManager.instance.enemyShips.ToArray())
        {

            if (Vector3.Distance(transform.position, target.position) > ship.mainGunCaliber.range)
                continue;

            Vector3 toTarget = target.position - transform.position;
            print(Vector3.SignedAngle(hullParent.forward, toTarget, Vector3.up));

            if (Vector3.SignedAngle(hullParent.forward, toTarget, Vector3.up) >= bowMinAngle &&
                Vector3.SignedAngle(hullParent.forward, toTarget, Vector3.up) <= bowMaxAngle)
            {
                if (!bowTargets.Contains(target))
                {
                    RemoveFromOthers(target);
                    bowTargets.Add(target);
                    print("added target to Bow");
                }
                continue;
            }
            if (Vector3.SignedAngle(hullParent.forward, toTarget, Vector3.up) >= sbMinAngle &&
                Vector3.SignedAngle(hullParent.forward, toTarget, Vector3.up) <= sbMaxAngle)
            {
                if (!sbTargets.Contains(target))
                {
                    RemoveFromOthers(target);
                    sbTargets.Add(target);
                    print("added target to SB");

                }
                continue;

            }
            if (Vector3.SignedAngle(-hullParent.forward, toTarget, Vector3.up) >= aftMinAngle &&
                Vector3.SignedAngle(-hullParent.forward, toTarget, Vector3.up) <= aftMaxAngle)
            {
                if (!aftTargets.Contains(target))
                {
                    RemoveFromOthers(target);
                    aftTargets.Add(target);
                    print("added target to Aft");

                }
                continue;
            }
            if (Vector3.SignedAngle(hullParent.forward, toTarget, Vector3.up) >= psMinAngle &&
                Vector3.SignedAngle(hullParent.forward, toTarget, Vector3.up) <= psMaxAngle)
            {
                if (!psTargets.Contains(target))
                {
                    RemoveFromOthers(target);
                    psTargets.Add(target);
                    print("added target to PS");

                }

            }
        }

Any help would be appreciated on how to tackle this problem!

Thank you.


Solution

  • There are a couple options I can think of.

    1. You can use 4 (or more, for up-down range as well) points to measure the angles from instead of the single point from the center that I'd previously suggested.

    enter image description here

    Your code, for the starboard side, would then be more like (I think, I'm nautically inclined so I may be wrong about the labelling lol):

    if (Vector3.SignedAngle(markerBowStarboard.right, toTarget, Vector3.up) >= sbMinAngle &&
        Vector3.SignedAngle(markerAftStarboard.right, toTarget, Vector3.up) <= sbMaxAngle)
    {
        if (!sbTargets.Contains(target))
        {
            RemoveFromOthers(target);
            sbTargets.Add(target);
            print("added target to SB");
    
        }
        continue;
    }
    
    1. You could try to adapt something from this "ConeCast" extension method someone made. It still has the issue of being from the central point, but you may be able to tweak it to start the cast, for example, further left of the ship and check that the target is on the right of the ship (convert the target global position to the local reference frame of the ship and check x/y is >0/<0). It's sort of just trading one manual calculation for another, though.
    2. Stealing from rustyBucketBay's answer a bit, you could use the larger of the SphereCast cylinders and use some trig to rule out ships that are less than a distance from the ship based on the cosine of the angle illustrated here:

    enter image description here

    But again, it's sort of just trading one calculation for another.

    1. You could create a conic cylinder model, don't render it but give it a MeshCollider, and then when objects enter the object (OnColliderEnter, I think?) you can add them to that direction's list and remove when they exit (OnColliderExit maybe?). You may need to do some more checking on when it is fully inside the conic cylinder though, because I think OnColliderExit (or whatever it's called) is triggered when the model stops colliding with the actual mesh, regardless of whether it is inside or outside of the object.

    I'm not super sure I like any of those that much, but I'm surprised that there isn't a native "ConeCast" that can take near- and far-plane dimensions... but then I guess I'm just saying to Unity "I don't wanna do it, you do it" lol.