Search Unity

How do you make a simple method to cast rays from a box collider?

Discussion in 'Scripting' started by PhantomFox128, Jan 17, 2022.

  1. PhantomFox128

    PhantomFox128

    Joined:
    May 22, 2019
    Posts:
    157
    I'm trying to make a custom 2D physics controller using raycasts. I followed this tutorial and it works, but it seems like there's a lot to do for just a few things, although that might just be me (programming really isn't my strong suite). The video also combines input and gravity into one script while calculating physics and movement in another and I'd rather split those up and combine them in a way where they're not so dependent on each other, so I'm attempting to take what I learned and try to recreate just the physics from scratch starting with combining physics and gravity into one independent script.

    To start things off though, I'm just trying to figure out how to cast rays from the player's box collider. The way the video does it (I think) is by making some Vector2's, making a variable that gets the colliders bounds and then I think they assign a position for each side. It seems kinda self-explanatory, but it also does seem like a lot and I'm wondering if that can be trimmed down at all. As for the actual casting of the ray, I sorta get the syntax for it, but I'm lost on how to actually cast it from the collider itself.

    Sorry if this seems like unnecessary, beginner related stuff, I just tend to get a little overwhelmed by this sort of thing since it seems like there's a lot to do for something that I think would be relatively basic.
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,742
    These two things have me scratching my head:

    You're trying to remake a production physics / collision subsystem ... and you're surprised it's a lot?!

    Physics systems are complex.

    Any reason you're not just using the physics system that Unity gives you? Odds are it can do what you need.
     
  3. PhantomFox128

    PhantomFox128

    Joined:
    May 22, 2019
    Posts:
    157
    Shows how little I know! My only other experience with platformer physics is from other engines that have most of it built-in so you only need a few lines of code at most. As for not using the physics system, the general consensus that I find is that Unity's physics don't fit the tight, platformer feel that games in the genre are known for, so most users argue that it's best to code your own. Supposedly you can pull it off with Unity's physics (there's a couple high profile games I heard of that did it), but I can't find many resources that help in that regard.
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,742
    Agree with that... physics is not always the best solution. Now I understand what you're thinking.

    As for starting the raycast exactly on the collider, that's probably not necessary.

    When I've used raycasting to do my own custom platforming, I've always bound it closely to the stateful behavior, coding up specific casts for each state, like walking, jumping, attacking, etc.

    It's also nice because you can put a MonoBehaviour on the platforms telling what they're made of, so from the one raycast above the player can know if he's on ice or sand or whatever.
     
    Last edited: Jan 18, 2022
  5. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,496
    "Unity's physics".

    I presume you mean using a rigidbody physics engine and using Dynamic bodies and their collision response for a basic platformer isn't a perfect fit?

    I know you said you read what others said but I'm curious to know what's you think is specific about Unity that makes it wrong or a bad fit? In the end, use a Kinematic body and there's no collision response at all and you have every query under the sun (to detect your environment) you could hope to want apart from pixel-perfect collision-detection.

    This is the part I never understand about this broad statement i.e. saying Unity specifically and not clarifying what part is bad. Sorry if this sounds overly "defensive", I'm trying not to sound that way but there's also so much provided out of the box that I'd love to know what simple mechanic isn't possible or is just plain difficult to understand. It may well be that discovery of features is the real culprit here i.e. not enough high quality tutorials that are easy to find.

    I can only assume you mean the rigidbody dynamic collision response but the problem with how you and others say it is that other devs read it and don't know specifically what you mean and then it becomes known that "Unity Physics" and platformers are a bad fit.

    I encounter devs who say this based upon one thing too, gravity and how it feels (floaty) and this is kind of insane as it's super easy to change how a downward force is applied, sometimes it's just a bad rendering scale etc.

    A good proportion of the time it's because the dev wants a whole bunch of mechanics but doesn't yet understand how to use the physics system itself. Sorry if this sounds overly critical and isn't aimed at you here, but often it's very true and it's easy to blame software for it. It's certainly not "a few high profile" games that use 2D physics for platformers though.
     
  6. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,496
    I'll add this separately due to the size of my post above:

    You say this but two things are apparent: you're an inexperience programmer (nothing wrong with that whatsoever) and you're trying to write a character controller yourself which is something only a moderately experienced programmer should attempt, especially one with more physics experience.

    Add in a Rigidbody2D and an appropriate collider and it's ready to go. Add forces to it related to your input or set velocity of it if you wish and it'll move around and collide with things. Add input to add a jump and it'll jump. All you've done here is read input and tell the Rigidbody2D how to move. You've not had to write a character controller. Get comfortable with that then go onto more advanced stuff.

    It is super simple to do simple stuff. What is hard is running before you walk if I'm honest; writing a character controller or copying someone else's from scratch isn't a good place to start and you're brave to even try. :)

    I'd say get comfy with the basics, tweak behaviours as you see fit such as stronger gravity or gravity that works less when jumping and more when falling (super easy) to get the platformer "feel" you want. In a few lines of code, here's how that looks BTW:


    In the end, I think a lot of what devs talk about is "feel" and that's often just tweaking stuff. Character controllers have their place but that's when you want to define your own collision response and other behaviours such as sliding, grabbing, moving platforms etc. That takes more experience and understanding queries very well.

    I'll add, I always loved this GDC video about some of the issues:
     
  7. PhantomFox128

    PhantomFox128

    Joined:
    May 22, 2019
    Posts:
    157
    No worries! I'm largely just relaying what I've heard.

    Many users say that the built-in physics tend to simulate reality whereas most platformers tend to not do that, though as mentioned earlier, it's possible. I personally haven't had much success with it so far, although that could be me not knowing how to progress. I'm going for a game feel that's similar to something like Shantae/Kirby where you have full control of the player at all times and any "forces" are instantaneous (no acceleration or the like, at least for now). Best feeling jump I can get with it involves raising the gravity and jump power by quite a lot, which isn't really a bad thing, it's just not really the kind of "feel" I'm looking for. Also in my case, tapping the jump button causes the player to go way too high and I can't find a way to reduce it without slowing them down a ton. I also use 'transform.Translate' for my movement and from what I can find, that doesn't seem to work well with built-in physics. Again, it could probably be just me not knowing what to do.

    Speaking of, one the main things I see for opting to go with a custom controller is that the physics system doesn't play nice with more advanced stuff like slopes or wall jumping. I don't know how true that is, my game won't have any of that anyway, so I couldn't care less. Would like to implement moving platformers and slide turns though.

    This could also be part of the problem. Most tutorials I find on the matter are maybe a little too simplistic and don't go into anything beyond the basics.
     
  8. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,496
    I guess then it's just the wording used when they say "built-in physics". Built-in physics includes bodies, colliders, joints, simulation, collision-detection, collision-response, queries etc. If you then use these, you're using "built-in physics". I think the problem is that devs use the generic word "physics" to mean anything. Sometimes it's "movement", sometimes "collision response" and this is born out of not knowing the specifics therefore leads to broad statements like this which, I believe, are very misleading to other devs reading them.

    What you're actually referring to is Rigidbody2D collision response i.e. what a Rigidbody2D does in Dynamic body mode so that's a specific thing in a specific mode and isn't all of "built-in physics"! :)

    Whilst this might possibly sound pedantic, it can lead to devs reading it and seeing, "don't use built-in physics" then reaching for much more complex things. Ironically those things get 90% of their functionality from "built-in physics" i.e. Kinematic bodies, colliders and a huge set of queries to detect their environment. Certainly, "real" responses for friction on slopes are often not the behaviour you want and so a CharacterController will help there and other similar behaviours. The thing is, this is using a Kinematic body and all the rest of "built-in physics". :)

    Don't. The whole point of a Rigidbody2D is to write to the Transform. Never modify it, always go through its API. If it's Kinematic then use MovePosition and MoveRotation. If you're running physics during the FixedUpdate then set the body to use interpolation so it updates the Transform per-frame. Alternately, go into the Physics 2D settings and select the Simulation Mode to be per-frame then it's run per-frame and no interpolation is needed nor is using FixedUpdate but read the docs there about caveats.

    As an example, in my physics examples repo I created this "top down" Kinematic controller that moves and slides around the environment; it's designed to show the move/slide part, not be a full character controller because it's just an example. I use queries to detect the surroundings and move appropriately explicitly. I would never describe this as not using "built-in physics".



    Hope that info is useful.
     
    PhantomFox128 likes this.
  9. PhantomFox128

    PhantomFox128

    Joined:
    May 22, 2019
    Posts:
    157
    So, after doing some research, I guess what I'm doing is using a Kinematic body to allow me greater control over things like gravity and jumping while letting the physics handle everything else. I actually managed to get some custom gravity working, but I'm unsure if the fall speed is accumulating over time or if it's staying consistent. It looks like it's consistent, but the debugger has me thinking otherwise. I'm also still trying to figure out raycasts for collision checks, which sort of brings me back to my original question.

    I've managed to at least get a ray cast from the center of the player's box collider, but I'm unsure what the correct way is to use it as ground check. Last time I tried I almost got it to work, but it counted as grounded when the cast hit the ground, not the collider, which caused the player to "hover" in the air.
    I'm also unsure how to cast multiple rays at once since that's a common thing for better collision detection.

    Quick sidenote: the reason why I used transform.Translate is because it's the only movement option I tried that hasn't given me any issues. Everything else usually either throws an error or gives me some weird movement effect like teleporting to a new position. The only other thing that's worked so far is using Rigidbody2D.position which I guess works, but I can't move the player around in the scene view without them warping back to where they were while falling. Granted, I tried moving it to FixedUpdate after adding a Time.deltaTime to it, so maybe that's normal?
     
  10. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,496
    Teleporting instantly to a new position is exactly what modifying the Transform does. It's also not compatible with interpolation either. Use MovePosition which works with interpolation and causes the Kinematic body to move through the world to the position you specify.

    If you're not using these because of problems then these problems are what you are creating. These are basic functions that work perfectly fine so best to figure out what you're doing wrong and not use a completely bad way of doing things.

    Not much can go wrong with MovePosition. What can go wrong is assuming it does something it doesn't such as moving to that position there and then, it doesn't, it moves there in the next simulation step. You always have to have in your head that FixedUpdate is not equal to Update.

    A 2D Kinematic body can have velocity, you can set the velocity, add to it for things like gravity etc. If you're explicitly positioning it then you cannot use its velocity because it should be obvious that by setting position, you're NOT integrating velocity to give position, you're just stomping a position down. If you want to set position then you need to maintain a Vector2 velocity yourself and use it to calculate position.
     
  11. PhantomFox128

    PhantomFox128

    Joined:
    May 22, 2019
    Posts:
    157
    Took me awhile to figure it out, but I've now managed to take what code I have, changed it to use MovePosition and it's working as far as I can tell!

    Still trying to figure out my initial issue though:

    Update on that: I have the horizontal movement in it's own script with physics/gravity in it's own as well and it seems like movement flat out doesn't work while the physics script is active.
     
  12. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,496
    Maybe post the script in question and show the relevant Rigidbody2D component assuming it's not just a stock Kinematic body.
     
  13. PhantomFox128

    PhantomFox128

    Joined:
    May 22, 2019
    Posts:
    157
    Movement script:
    Code (CSharp):
    1. public class Movement : MonoBehaviour
    2. {
    3.     public float walkSpeed = 10f;
    4.     public float runSpeed = 18f;
    5.  
    6.     Rigidbody2D rb;
    7.  
    8.     void Start()
    9.     {
    10.         rb = GetComponent<Rigidbody2D>();
    11.     }
    12.  
    13.     void FixedUpdate()
    14.     {
    15.         Walk();
    16.         Run();
    17.     }
    18.  
    19.     public void Walk()
    20.     {
    21.         float horizontalInput = Input.GetAxisRaw("Horizontal");
    22.         rb.MovePosition(rb.position + new Vector2(horizontalInput, rb.velocity.y) * walkSpeed * Time.deltaTime);
    23.     }
    24.  
    25.     public void Run()
    26.     {
    27.         if (Input.GetKey(KeyCode.LeftShift))
    28.         {
    29.             float horizontalInput = Input.GetAxisRaw("Horizontal");
    30.             rb.MovePosition(rb.position + new Vector2(horizontalInput, rb.velocity.y) * runSpeed * Time.deltaTime);
    31.         }
    32.     }
    33. }
    Physics script:
    Code (CSharp):
    1. public class Physics : MonoBehaviour
    2. {
    3.     public float gravity = 30;
    4.     public float maxFallSpeed = 30;
    5.     public LayerMask groundMask;
    6.     public float rayLength = 5f;
    7.  
    8.     [HideInInspector]
    9.     public bool isGrounded;
    10.  
    11.     private Vector2 velocity;
    12.     private Rigidbody2D rb;
    13.     private BoxCollider2D hitbox;
    14.  
    15.     void Start()
    16.     {
    17.         rb = GetComponent<Rigidbody2D>();
    18.         hitbox = GetComponent<BoxCollider2D>();
    19.     }
    20.  
    21.     void Update()
    22.     {
    23.         RaycastHit2D raycastHit = Physics2D.Raycast(hitbox.bounds.center, Vector2.down, rayLength, groundMask);
    24.         Debug.DrawRay(hitbox.bounds.center, Vector2.down * rayLength, Color.red);
    25.  
    26.         if (raycastHit)
    27.         {
    28.             isGrounded = true;
    29.         }
    30.         else
    31.         {
    32.             isGrounded = false;
    33.         }
    34.     }
    35.  
    36.    private void FixedUpdate()
    37.     {
    38.         if (!isGrounded)
    39.         {
    40.             rb.MovePosition(rb.position + (new Vector2(rb.velocity.x, -gravity) * Time.deltaTime));
    41.         }
    42.         else
    43.         {
    44.             rb.MovePosition(rb.position + (new Vector2(rb.velocity.x, 0)));
    45.         }
    46.     }
    47. }
    Few things: The Rigidbody2D is just the standard kinematic body with Continuous collision detection.
    I'm fairly certain the reason why I can't move while the physics script is active has to do with the Vector2 in the FixedUpdate. I've tried a few things already, but no luck so far.
    I'm pretty sure I'm also doing collisions incorrectly because it goes by the raycast and not the player's box collider. I'm assuming I have to get the colliders bounds, but I'm not sure what to do after that.
    I'm aware I'm probably doing a lot of other things wrong.