Problems with Enemy raycasting/hit detection

Hey guys,

In my research I’ve noticed a lot of posts starting ‘I’m a total noob with c#’ and this is no different. I have zero experience with C#, nor any aptitude for it at all, but I’ve been tasked with creating a simple 2D game for University.

In my game, the player controls a discarded lollipop that needs to get back to the safety of the ice cooler before being melted by the sun. For this to happen I’ve been trying to set up a field of view for the sun, and used a tutorial on YouTube. The tutorial was intended to create a 360 degree field of view, and enable the enemy to detect the player, it then also guided me through how to create gizmos to reflect all that in the viewport.

In the image above you can see the gizmos displaying the field of view, and the code should also draw a line between the circle/sun, and the player. Somewhere, something has gone wrong. I’m not sure if it’s the raycast, or collision, or something else. And I’m not familiar enough with the code to figure it out.

Once complete, the tutorial demonstrated the working code by directing me into debug, where I could see that the ‘Can See Player’ tickbox is checked. But here, nothing happens, as you can see in the picture below.

I would greatly appreciate some insights here.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FieldOfView : MonoBehaviour
{
    public float radius = 5f;
    [Range(1, 360)]public float angle = 180f;
    public LayerMask targetLayer;
    public LayerMask obstructionLayer;

    public GameObject playerRef;

    public bool CanSeePlayer { get; private set; }

    void Start()
    {
        playerRef = GameObject.FindGameObjectWithTag("Player");
        StartCoroutine(FOVCheck());
    }

    private IEnumerator FOVCheck()
    {
        WaitForSeconds wait = new WaitForSeconds(0.2f);

        while (true)
        {
            yield return wait;
            FOVCheck();
        }
    }

    private void FOV()
    {
            Collider2D[] rangeCheck = Physics2D.OverlapCircleAll(transform.position, radius, targetLayer);

            if (rangeCheck.Length > 0)
            {
                Transform target = rangeCheck[0].transform;
                Vector2 directionToTarget = (target.position - transform.position).normalized;

                if (Vector2.Angle(transform.up, directionToTarget) < angle / 2)
                {
                    float distanceToTarget = Vector2.Distance(transform.position, target.position);

                    if (!Physics2D.Raycast(transform.position, directionToTarget, distanceToTarget, obstructionLayer))
                        CanSeePlayer = true;
                    else
                        CanSeePlayer = false;
                }
                else
                    CanSeePlayer = false;
            }
            else if (CanSeePlayer)
                CanSeePlayer = false;
    }

    private void OnDrawGizmos()
    {
        Gizmos.color = Color.white;
        UnityEditor.Handles.DrawWireDisc(transform.position, Vector3.forward, radius);

        Vector3 angle01 = DirectionFromAngle(-transform.eulerAngles.z, -angle / 2);
        Vector3 angle02 = DirectionFromAngle(-transform.eulerAngles.z, angle / 2);

        Gizmos.color = Color.yellow;
        Gizmos.DrawLine(transform.position, transform.position + angle01 * radius);
        Gizmos.DrawLine(transform.position, transform.position + angle02 * radius);

        if (CanSeePlayer)
        {
            Gizmos.color = Color.green;
            Gizmos.DrawLine(transform.position, playerRef.transform.position);
        }
    }

    private Vector2 DirectionFromAngle(float eulerY, float angleInDegrees)
    {
        angleInDegrees += eulerY;

        return new Vector2(Mathf.Sin(angleInDegrees * Mathf.Deg2Rad), Mathf.Cos(angleInDegrees * Mathf.Deg2Rad));
    }
}

Welcome FW! Remember that 100% of people on the planet were (or still are) total noobs in C#.

And yet it is possible to succeed!

I am guessing what is happening is some computation is not quite correct, or some computation is assuming local (relative to GameObject position) versus world (global) coordinates.

The easiest way to track this down is methodically print out what values are going into and coming out of your computations, and reason about where they diverge from your expectations.

To make you feel better, it might be helpful to do a Gizmos() draw but with hard-wired numbers, such as:

Gizmos.DrawLine( new Vector3( -1, 0, 0), new Vector3( +1, 0, 0));

These sorts of things can help you cross verify with your own brain “am I doing this right?!!”

Here is the general case:

You must find a way to get the information you need in order to reason about what the problem is.

Once you understand what the problem is, you may begin to reason about a solution to the problem.

What is often happening in these cases is one of the following:

  • the code you think is executing is not actually executing at all
  • the code is executing far EARLIER or LATER than you think
  • the code is executing far LESS OFTEN than you think
  • the code is executing far MORE OFTEN than you think
  • the code is executing on another GameObject than you think it is
  • you’re getting an error or warning and you haven’t noticed it in the console window

To help gain more insight into your problem, I recommend liberally sprinkling Debug.Log() statements through your code to display information in realtime.

Doing this should help you answer these types of questions:

  • is this code even running? which parts are running? how often does it run? what order does it run in?
  • what are the values of the variables involved? Are they initialized? Are the values reasonable?
  • are you meeting ALL the requirements to receive callbacks such as triggers / colliders (review the documentation)

Knowing this information will help you reason about the behavior you are seeing.

You can also supply a second argument to Debug.Log() and when you click the message, it will highlight the object in scene, such as Debug.Log("Problem!",this);

If your problem would benefit from in-scene or in-game visualization, Debug.DrawRay() or Debug.DrawLine() can help you visualize things like rays (used in raycasting) or distances.

You can also call Debug.Break() to pause the Editor when certain interesting pieces of code run, and then study the scene manually, looking for all the parts, where they are, what scripts are on them, etc.

You can also call GameObject.CreatePrimitive() to emplace debug-marker-ish objects in the scene at runtime.

You could also just display various important quantities in UI Text elements to watch them change as you play the game.

If you are running a mobile device you can also view the console output. Google for how on your particular mobile target, such as this answer or iOS: How To - Capturing Device Logs on iOS or this answer for Android: How To - Capturing Device Logs on Android

Another useful approach is to temporarily strip out everything besides what is necessary to prove your issue. This can simplify and isolate compounding effects of other items in your scene or prefab.

Here’s an example of putting in a laser-focused Debug.Log() and how that can save you a TON of time wallowing around speculating what might be going wrong:

When in doubt, print it out!™

Note: the print() function is an alias for Debug.Log() provided by the MonoBehaviour class.

Heres an example:

Physics.Raycast(Vector3 origin, Vector3 direction, float distance);

Physics.Raycast**(from here, travel this direction, and stop after this distance)**

the above travels until the origin has traveled X meters in the direction of direction.
Physics.Raycast returns a boolean, TRUE or FALSE.
If the raycast has hit something solid (an object with a collider) anywhere along its travel, it will return true. Otherwise, it’ll return false.

Here, and example of getting information of the object that was hit:

Physics.Raycast(Vector3 origin, Vector3 direction, out RaycastHit hit, float distance);

Physics.Raycast__(from here, travel this direction, store the info in this variable, stop after this distance**)**__

an example of how this can be used

var mask = LayerMask.NameToLayer("Enemy");
// if the raycast from the current position, travelling forward, has hit something
if (Physics.Raycast(transform.position, Vector3.forward, out RaycastHit hit, 10))
    // if the object hit was marked as "Enemy" in LayerMask
    if (hit.collider.gameObject.layer == mask)
    {
        // something here
        Debug.Log("Found Enemy");
    }

what that’s doing is casting a ray from X, in the forward position from X, to a maximum distance of 10 meters, and storing the potential object information in the variable RaycastHit (hit).
if the ray hits a solid object, the RaycastHit variable stores some values according to the object.

the keyword out being used just tells the compiler that this variable must be filled with something before that function returns a value.

i hope this explains it a little better for you

Thank you so much for your response. I wonder, is it possible for you to give me an example of a debug.log in the code I posted? I don’t know where it goes, how it’s formatted, or if I need to add anything in order to actually test the code around it? Does debug.log literally just print something in the console if the attached script isn’t functioning?

Thanks again. I’m not at my computer right now, but I will look more into debug.log and how to implement when I go home.

Where do you think the code is executing? Start there! I bet where it is executing might differ from what you think it is.

What are some values of some rays and distances that you might predict? Check those predictions by Debug.Log() output.

See SourGummi’s post for more ideas.

Beyond that if you are not familiar with your program, you should go back and perform Step #2 for learning code. :slight_smile:

Tutorials and example code are great, but keep this in mind to maximize your success and minimize your frustration:

How to do tutorials properly, two (2) simple steps to success:

Step 1. Follow the tutorial and do every single step of the tutorial 100% precisely the way it is shown. Even the slightest deviation (even a single character!) generally ends in disaster. That’s how software engineering works. Every step must be taken, every single letter must be spelled, capitalized, punctuated and spaced (or not spaced) properly, literally NOTHING can be omitted or skipped.

Fortunately this is the easiest part to get right: Be a robot. Don’t make any mistakes.
BE PERFECT IN EVERYTHING YOU DO HERE!!

If you get any errors, learn how to read the error code and fix your error. Google is your friend here. Do NOT continue until you fix your error. Your error will probably be somewhere near the parenthesis numbers (line and character position) in the file. It is almost CERTAINLY your typo causing the error, so look again and fix it.

Step 2. Go back and work through every part of the tutorial again, and this time explain it to your doggie. See how I am doing that in my avatar picture? If you have no dog, explain it to your house plant. If you are unable to explain any part of it, STOP. DO NOT PROCEED. Now go learn how that part works. Read the documentation on the functions involved. Go back to the tutorial and try to figure out WHY they did that. This is the part that takes a LOT of time when you are new. It might take days or weeks to work through a single 5-minute tutorial. Stick with it. You will learn.
Step 2 is the part everybody seems to miss. Without Step 2 you are simply a code-typing monkey and outside of the specific tutorial you did, you will be completely lost. If you want to learn, you MUST do Step 2.

Of course, all this presupposes no errors in the tutorial. For certain tutorial makers (like Unity, Brackeys, Imphenzia, Sebastian Lague) this is usually the case. For some other less-well-known content creators, this is less true. Read the comments on the video: did anyone have issues like you did? If there’s an error, you will NEVER be the first guy to find it.

Beyond that, Step 3, 4, 5 and 6 become easy because you already understand!

you can place debug logs in any section to check whats being doing. pass any string value to it like Debug.Log(“debug log text”).
so if youre doing something that check if X = Y, rather than the code inside your brackets, just type
csharp* *if (!Physics2D.Raycast(transform.position, directionToTarget, distanceToTarget, obstructionLayer)) Debug.Log("Player found!"); else Debug.LogWarning("Can not find player.");* *