0
\$\begingroup\$

I've been trying to get this working for about 2 hours now and as far as I'm concerned, it is working entirely, other than one thing.

The problem is that when my player is in the air, and they move right and collide with a wall, they simply collide. Simple. But when the player is moving left, and more commonly left and down or left straight after falling off a ledge and hitting a wall, they will almost always become grounded.

What is confusing me is that the left and right move scripts are exactly the same, with a few variables changed so that they are designed to go in opposite directions. My grounded check is also centered, so I don't imagine it could be happening to the left direction due to it being offset.

If anyone could help I would greatly appreciate it! Here is the script:

using UnityEngine;
using System.Collections;

public class Move : MonoBehaviour
{


    public RaycastHit2D groundhit;

    public float groundeddistance;
    public bool grounded;
    public float fallspeed;

    public RaycastHit2D uphit;
    public bool upblocked;
    public float upspeed;
    public float jumpcount;

    public float fallspeedpubset;

    public bool leftblock;
    public bool rightblock;
    public float sidespeed;
    public RaycastHit2D lefthit;
    public RaycastHit2D righthit;
    public float leftreach;
    public float rightreach;

    public bool jumping;

    public bool gravity;
    public float groundedwidth;
    public float upreach;
    public float jumpcountmax;
    public float repeatratejump;


    public bool cantground;

    public float leftclimbcount;
    public float rightclimbcount;
    public float leftclimbcountstoper;
    public float rightclimbcountstoper;

    // Update is called once per frame
    void Update()
    {

        fallspeed = fallspeedpubset;       

        if (cantground == false)
        {
            groundhit = Physics2D.BoxCast(transform.position, new Vector2(transform.localScale.x - groundedwidth, transform.localScale.y), transform.eulerAngles.z, Vector2.down, groundeddistance);
            if (Physics2D.BoxCast(transform.position, new Vector2(transform.localScale.x - groundedwidth, transform.localScale.y), transform.eulerAngles.z, Vector2.down, groundeddistance) && groundhit.transform.tag == "Terrain")
            {
                grounded = true;
            }
            else { grounded = false; }


        }

        if (grounded == false && gravity) { transform.position = new Vector2(transform.position.x, transform.position.y - fallspeed); } else { }


        lefthit = Physics2D.BoxCast(new Vector2(transform.position.x - transform.localScale.x / 2, transform.position.y), new Vector2(0.01f, transform.localScale.y - 0.04f), transform.eulerAngles.z, Vector2.left, leftreach);
        if (Physics2D.BoxCast(new Vector2(transform.position.x - transform.localScale.x / 2, transform.position.y), new Vector2(0.01f, transform.localScale.y - 0.04f), transform.eulerAngles.z, Vector2.left, leftreach) && lefthit.transform.tag == "Terrain")
        {
            leftblock = true;
        }
        else { leftblock = false; }

        righthit = Physics2D.BoxCast(new Vector2(transform.position.x + transform.localScale.x / 2, transform.position.y), new Vector2(0.01f, transform.localScale.y - 0.04f), transform.eulerAngles.z, Vector2.right, rightreach);
        if (Physics2D.BoxCast(new Vector2(transform.position.x + transform.localScale.x / 2, transform.position.y), new Vector2(0.01f, transform.localScale.y - 0.04f), transform.eulerAngles.z, Vector2.right, rightreach) && righthit.transform.tag == "Terrain")
        {
            rightblock = true;
        }
        else { rightblock = false; }

        uphit = Physics2D.BoxCast(new Vector2(transform.position.x, (transform.position.y + transform.localScale.y / 2) + 0.01f), new Vector2(transform.localScale.x - groundedwidth, 0.01f), transform.eulerAngles.z, Vector2.up, upreach);
        if (Physics2D.BoxCast(new Vector2(transform.position.x, (transform.position.y + transform.localScale.y / 2) + 0.01f), new Vector2(transform.localScale.x - groundedwidth, 0.01f), transform.eulerAngles.z, Vector2.up, upreach) && uphit.transform.tag == "Terrain")
        {
            upblocked = true;
        }
        else { upblocked = false; }

        if (Input.GetKey("d") && rightblock == false)
        {
            transform.position = new Vector2(transform.position.x - sidespeed, transform.position.y);
        }
        else if (Input.GetKey("a") && leftblock == false)
        {
            transform.position = new Vector2(transform.position.x + sidespeed, transform.position.y);
        }

        //if(grounded && Input.GetButtonDown("Jump")) { grounded = false; cantground = true; jumping = true;  }
        if (grounded && Input.GetKeyDown("w") && upblocked == false) { InvokeRepeating("Jump", 0, repeatratejump); }
        if (grounded) { rightclimbcount = 0; leftclimbcount = 0; }
        //if (grounded == false && lefthit && Input.GetKey("a") && leftclimbcount <= leftclimbcountstoper && Input.GetKey("w") == false) { gravity = false; leftclimbcount = leftclimbcount + 1; upblocked = true;  } else if(jumping == false) { gravity = true; }
        //if (grounded == false && righthit && Input.GetKey("d") && rightclimbcount <= rightclimbcountstoper && Input.GetKey("w") == false) { gravity = false; rightclimbcount = rightclimbcount + 1; upblocked = true; } else if (jumping == false) { gravity = true; }

    }
    void Jump()
    {
        jumping = true;
        gravity = false;
        fallspeed = 0;
        if (upblocked == false)
            transform.position = new Vector2(transform.position.x, transform.position.y + upspeed);
        jumpcount += 1;
        if (jumpcount >= jumpcountmax) { CancelInvoke(); gravity = true; jumpcount = 0; jumping = false; }

    }
}
\$\endgroup\$
  • \$\begingroup\$ I've tried to clean up your code sample as best as I could (no reason to include empty functions, and commented out variables are not being used). Generally, you want to make your code as minimalist as possible to replicate the same problem you describe. I will try to figure out what is happening, but first I'm going to have to clean up your code a bit more. \$\endgroup\$ – Gnemlock Aug 14 '16 at 0:07
  • \$\begingroup\$ Also, you say the left script and right script are exactly the same. In this one script, I can see you handling both left and right. Is there another script? Or do you mean to say you handle the logic the same, as they are handled in the same script? \$\endgroup\$ – Gnemlock Aug 14 '16 at 0:09
  • \$\begingroup\$ I think I have found the problem. That said, why are you storing a RayCast2D by performing a Physics2D.BoxCast(), before immediately performing the same Physics2D.BoxCast() to check the resulting RayCast2D for an if statement? You literally just stored the value. Why don't you just check the RayCast2D variable, instead of performing the same Physics2D.BoxCast() again? You do this quite a bit (as well as similar instances where you could benefit from storing new Vector2 once instead of calling it twice), which would have some sort of impact on your overall performance. \$\endgroup\$ – Gnemlock Aug 14 '16 at 4:43
  • \$\begingroup\$ For me unity decided that i have to do the boxcast twice. I realise i shouldnt have to do it twice in the if statements but unity seems to force me to. I get invalid object errors if i dont. \$\endgroup\$ – McArthur Alford Aug 14 '16 at 20:04
0
\$\begingroup\$

Your not working out the origin in Physics2D.BoxCast() correctly.

Using Unity, it is important to remember that traditional 2D refers to the bottom-left corner as its position or origin, while traditional 3D and Physics2D.Boxcast refers to the dead-centre as its position or origin.

It is hard to tell whether you are specifically working in 2D, or working in 3D set up like 2D, because your origin logic seems to account for 2D along the x-axis, and 3D along the y-axis. I will be assuming that you need to account for having a 2D-based origin; If your object is based in 3D, and its transform.position points to its dead-centre, you should not have to apply an offset at all.

From what I can find, online, Physics2D.BoxCast is not known to be user-friendly, especially for beginners. While the Unity API page is not very informative, I found this tutorial on YouTube that gives you a good idea of how it works.

Let's take a look at a broken up version of your code, to more accurately convey the logical issue:


Movement Logic

Firstly, you are calling the function Physics2D.BoxCast(Vector2 origin, Vector2 size, float angle, Vector2 direction, float distance) to check for collision. I will be focusing on the first Vector2 you send in, the origin, as this is where the logical error lies. Remember, the Vector2 origin determines the centre of the collider box.

Moving Right

float x = transform.position.x + (transform.localScale.x / 2);
float y = transform.position.y;
Vector2 origin = new Vector2(x, y);

So you correctly move into the center of your object along the x-axis, but fail to adjust at all for the y-axis. This gives you a collision box like this:

The problem is, the origin of your player should always be the same. It is hard to know exactly how you have it set up, and different users have their preferences in accounting for origin and offsets, but traditionally it would look something like this:

Basic diagram of a Physics2D.BoxCast() at origin of (x + width/2, y)

As you can see, the box covers the bottom half of the player, and reaches that same distance underneath the player. This will result in collisions with the ground if the player is still within a distance of half height, even if the player is not actually touching the ground. In turn, even though this logic appears to work, you will be triggering grounded far earlier than you probably intend to.

Moving Left

float x = transform.position.x - (transform.localScale.x / 2);
float y = transform.position.y;
Vector2 origin = new Vector2(x, y);

In this case, we still have the same logical problem along the y-axis. However, since you have deducted the offset from your x value, you also move the center further out to the left. This gives you something like this:

Basic diagram of a Physics2D.BoxCast() at origin of (x - width/2, y)

As you can see, by moving the x offset in the opposite direction, your collider box does not cover the player at all. This means that moving left will not only result in some fairly premature collisions, but during this movement there is no collision check on the player, at all!

The solution

The solution is quite simple. Correctly using offsets, we can determine the correct origins to use. To correctly centre the collider around the player, we use an origin value of new Vector2((transform.position.x + transform.scale.x/2), (transform.position.y + transform.scale.y/2)).

I think you might be intending to deliberately check the area above, below, to the right and to the left of the player. I'm not quite sure why you would be opting to check collision in this way, and to elaborate and further delve into your code I would have to go into far more discussion than is suitable for this forum, but these are the origins you would use for calling the Physics2D.BoxCast() on the spaces around the player:

Vector2 leftOrigin = new Vector2(transform.position.x - (transform.scale.x/2)), 
                                (transform.position.y + (transform.scale.y/2));

Vector2 rightOrigin = new Vector2(transform.position.x + (transform.scale.x * 1.5), 
                                 (transform.position.y + (transform.scale.y/2));

Vector2 upOrigin = new Vector2(transform.position.x + (transform.scale.x/2)), 
                              (transform.position.y + (transform.scale.y * 1.5));

Vector2 downOrigin = new Vector2(transform.position.x + (transform.scale.x/2)), 
                                (transform.position.y - (transform.scale.y/2));

As a final note, you will notice that rightOrigin and leftOrigin use *1.5 instead of /2. That is because we have to move over the player by an entire scale *1 to be on the other side, and /2 is the same as saying *0.5. I feel this is worth mentioning for the point of clarity, though it should be common sense.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.