Home > Software design >  Weird Raycast Behavior with Unity 2D in C#
Weird Raycast Behavior with Unity 2D in C#

Time:04-23

I'm having problems where a raycast2D I am doing in Unity for casting spells in a game is behaving weirdly. It doesn't always go in the direction it is supposed to, and it doesn't always respect the max distance I set for it.

The code is a communication between two scripts, but I don't think that is the problem.

snippet from the first script that preps the spell:

private void PrepCastSpell()
    {
        if (thoughtBox.activeSelf == false && dialogueBox.activeSelf == false)
        { 
            if (selectedSpell == 0)
            {

                if (currentCastDownTime != 0)
                {

                    failSpell.PlayOneShot(failSpellClip, 1f);


                }
                else if (currentCastDownTime == 0f)
                {


                    currentSpellTemplate.castSpell();

                }
            }
            else
            {
                if (currentCastDownTime != 0)
                {

                    failSpell.PlayOneShot(failSpellClip, 1f);


                }
                else if (currentCastDownTime == 0f)
                {
                    currentSpellTemplate.mousePos = Camera.main.ScreenToWorldPoint(playerControls.PlayerActions.MousePosition.ReadValue<Vector2>());

                    currentSpellTemplate.castSpell();

                }
            }

        }
    }

snippet from the second script that contains the cast

public virtual void castSpell()
    {

        
        int layerMask = LayerMask.GetMask("Scenery", "Enemy");

        heroTransform = hero.transform.position;
        direction = (mousePos - heroTransform).normalized;


        RaycastHit2D hit = Physics2D.Raycast(heroTransform, direction, rayCastDistance, layerMask);

        if (hit.collider != null)
        {

            gameObject.transform.position = hit.transform.position;
            gameObject.GetComponent<Animator>().SetBool("isCasting", true);


            point = hit.point;

            if (hit.collider.CompareTag("Enemy") == true)
            {

                hit.collider.GetComponent<NPCHealth>().damageNPCHealth(damageAmount);


            }

            else if (hit.collider.CompareTag("Scenery") == true)
            {

                Debug.Log("hit Something : "   hit.collider.name   " spell should be "   spellHolderScript.currentSpell   ". Collision occurred at "   hit.point);

            }

            currentCastDownTime = castDownTime;
            spellHolderScript.currentCastDownTime = currentCastDownTime;
            spellHolderScript.globalCastDownTime = globalCastDownTime;

            spellIconMask.fillAmount = 1f;

        }

        else 

        {

            point = heroTransform   direction * rayCastDistance;

            spellHolderScript.fizzleSpellAnim.SetBool("isCasting", true);
            spellHolderScript.fizzleSpell.transform.position = point;



            spellHolderScript.globalCastDownTime = globalCastDownTime;
            spellHolderScript.currentCastDownTime = castDownTime;
            currentCastDownTime = castDownTime;

            if (fizzleSpellCastDownTime == 0)
            {
                
                fizzleSpellCastDownTime = fizzleSpellDownTimeMax;

            }

            spellIconMask.fillAmount = 1f;

            Debug.Log("Nothing hit. Fizzlespell activating and moved to "   point );

        }




    }

I don't get any errors and I'm not 100% certain what is going on. I can provide further information if needed.

CodePudding user response:

I actually just answered a similar question over here.

The issue I think you're probably having is here:

heroTransform = hero.transform.position;
direction = (mousePos - heroTransform).normalized;

You're trying to get a unit vector pointing from the hero to the mouse, but your mouse is in screen coordinates and your hero is in world coordinates.

If you get a reference to the camera, like Camera camera = Camera.main then you can convert the hero's position from world coordinates to screen coordinates. Once you're in a common coordinate frame you can do the comparisons.

You should make the camera a class member and just cache that reference in Start unless you're switching cameras for some reason.

CodePudding user response:

Okay so a friend and I buckled down and worked on this, and a lot of the suggested advice actually made things worse because of stuff I programmed in extrapolating off of that advice. I'm not saying the advice was unwelcome or bad, but I just think the scripts were too complex on their interactions to easily answer the question.

First, the camera.main commands didn't really need to be on most of the stuff involved in the code. The one place I needed it was in the command I rolled back to instead of using what I posted here, which was

mousePosition = Camera.main.ScreenToWorldPoint(new Vector3(Mouse.current.position.ReadValue().x, Mouse.current.position.ReadValue().y, 0f));

That works with the "new" inputsystem and returns a very weird high number if you don't use screentoworldpoint.

A lot of the problem was because I didn't know how to do the math for vectors and polar coords.

here's the relevant code on the object I use to cycle through the spells and manage stuff the individual spells shouldn't manage:

private void PrepCastSpell()
    {
        if (thoughtBox.activeSelf == false && dialogueBox.activeSelf == false)
        {
            if (selectedSpell == 0)
            {

                if (currentCastDownTime != 0)
                {

                    failSpell.PlayOneShot(failSpellClip, 1f);


                }
                else if (currentCastDownTime == 0f)
                {
                    //This spell has no range and requires no values other than being at 0 on currentCastDownTime.

                    currentSpellInstantiateTemplate.castSpell();

                }
            }
            else
            {
                if (currentCastDownTime != 0)
                {

                    failSpell.PlayOneShot(failSpellClip, 1f);


                }
                else if (currentCastDownTime == 0f)
                {

                    mousePosition = Camera.main.ScreenToWorldPoint(new Vector3(Mouse.current.position.ReadValue().x, Mouse.current.position.ReadValue().y, 0f));

                    currentSpellInstantiateTemplate.mousePos = new Vector2(mousePosition.x, mousePosition.y);

                    currentSpellInstantiateTemplate.castSpell();

                }

            }
        }

    }

(don't ask me why I made mousePosition a vector3 when mousPos is a vector2 and I could have just snagged all the relevant values on it instead since the mouse's position is a vector2, as well. Probably need to clean that up.)

here's the code on the spell template:

public virtual void castSpell()
    {
        int layerMask = LayerMask.GetMask("Scenery", "Enemy");
        heroTransform = hero.transform.position;
        direction = (mousePos - heroTransform).normalized;


        RaycastHit2D hit = Physics2D.Raycast(heroTransform, direction, rayCastDistance, layerMask);


        if (hit.collider != null)
        {

            gameObject.transform.position = hit.transform.position;
            gameObject.GetComponent<Animator>().SetBool("isCasting", true);


            if (hit.collider.CompareTag("Enemy") == true)
            {

                hit.collider.GetComponent<NPCHealth>().damageNPCHealth(damageAmount);

            }

            else if (hit.collider.CompareTag("Scenery") == true)
            {
                  //this just has the spell animation cast on the scenery so it's unecessary because that isn't handled here.

            }
            else
            {
                //this is the statement appearing when a collider not on the layermask gets hit, it should never show up.

            }

            currentCastDownTime = castDownTime;
            spellHolderScript.currentCastDownTime = currentCastDownTime;
            spellHolderScript.globalCastDownTime = globalCastDownTime;
            spellIconMask.fillAmount = 1f;

        }


        else

        {
            point = new Vector3(heroTransform.x   (direction.x * rayCastDistance), heroTransform.y   (direction.y * rayCastDistance), 0f);

            spellHolderScript.fizzleSpell.transform.position = point;
            spellHolderScript.fizzleSpellAnim.SetBool("isCasting", true);


            spellHolderScript.globalCastDownTime = globalCastDownTime;
            spellHolderScript.currentCastDownTime = castDownTime;
            currentCastDownTime = castDownTime;

            if (fizzleCastDownTime == 0)
            {

                fizzleCastDownTime = fizzleDownTimeMax;

            }

            spellIconMask.fillAmount = 1f;

        }

    }

I've tried to clean this up and show only the relevant code to help anyone that stumbles on this. I took out the debug.logs and those checked a ton of stuff to make sure everything was working. But are kind of useless for me to add here.

This was the biggest issue that led to my problems because I was doing the math entirely wrong and this is the correct way to calculate where the spell needs to fire when the mouse button was getting clicked closer than the ray cast distance maximum. This only shows up on the script for one of the spells and not the others and I was having problems with it:


            mouseDistance = Vector2.Distance(heroTransform, mousePos);
            if (mouseDistance < rayCastDistance)
            {
                point = new Vector3(mousePos.x, mousePos.y, 0f);


            }
            else
            {
                point = new Vector3(heroTransform.x   (direction.x * rayCastDistance), heroTransform.y   (direction.y * rayCastDistance), 0f);
                
            }

This was a complex problem with a lot of potential pitfalls for a novice programmer like me so hopefully the samples of code and the (poor) explanation I am capable of giving help anyone else with a similar problem.

When comparing a vector2 and a vector3 in c# for Unity in most cases where you are declaring a vector2 the vector2 will grab the x and y values from the vector3 and ignore the z value.

A vector2 can implicitly be converted to a vector3 as well, the z axis just reads as 0 in that case.

https://docs.unity3d.com/ScriptReference/Vector2-operator_Vector2.html

Anyway, thank you for helping me and hopefully I can pay it forward by showing what I implemented to fix the problem.

  • Related