Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question How to handle circular objects for movement?

Discussion in 'Scripting' started by androidac67, Sep 29, 2023.

  1. androidac67

    androidac67

    Joined:
    Jan 3, 2023
    Posts:
    75
    I have a first-person character controller and I've made many changes to it and have added movement such as slope sliding and climbing, but I've only ever tested my code and controller on flat, square-like surfaces (plus rotated cubes to act as slopes), so when I created a few circular objects like a hollow cylinder and large spheres, movement is very clunky at times because of the different angles associated with a sphere so I'm curious on how others handle this.

    My only thought from what I've seen in other games is to use a box collider on those round objects like so:



    As can be seen in the photo, those long "strips" on the cylinder, my player thinks they're slopes due to the angle and then I get stuck, etc, so I added a box collider which removed all the issue but it looks pretty bad visually when you're just floating in the air on the corners of the collider. Is this still an acceptable approach though?
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    It's really kind of up to you how to approach it. A visible gap in colliders on a Sonic-speed game is less of an issue at speed than on a more slow-paced platformer.

    You can also do things other than whatever default physics you have going... for instance you could cover the placed object with a smoother inverted V ramp, then detect it and move along the direction of that ramp surface (by finding it with raycast)... or even cover it with a smoother curve than just a V, like a flattened inverted U ..
     
  3. androidac67

    androidac67

    Joined:
    Jan 3, 2023
    Posts:
    75
    The one issue that I just thought of was if I have a bunch of these long cylinder objects all lined up together and I was walking over them I'd walk as if I was on a flat surface when rather it should be a bit bumpy once I move from object, to object, not floating like here.

    upload_2023-9-29_9-37-30.png

    And sorry, I don't quite get which inverted ramps you are referring to in terms of using the V or a flattened U ramp.
     
  4. zulo3d

    zulo3d

    Joined:
    Feb 18, 2023
    Posts:
    510
    If you enable 'convex' on the cylinder mesh colliders then a character controller will walk over them without any issues. If your character can't walk over them when convex is enabled then you may need to change how you're handling slopes.
     
  5. androidac67

    androidac67

    Joined:
    Jan 3, 2023
    Posts:
    75
    Yeah convex makes it much much better thanks for the suggestion, I just did some research on the specifics of what it does. On some objects there's still an issue but I just found out that it's to do with my slope sliding code. But I can't find any other sliding code online to compare mine to so I can't pinpoint where I went wrong.
     
  6. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    Well you could always post your code here, along with any other information about how your character controller works so that we might be able to help.

    As it stands we have no idea how it works.
     
  7. androidac67

    androidac67

    Joined:
    Jan 3, 2023
    Posts:
    75
    I believe the code is pretty bad and way too long but I did 90% of it myself so I'm proud of that and it took nearly 2 months of learning and it works perfectly on flat surfaces (including flat but angled surfaces). I have no issues with any of this code when it comes to cube objects or objects with 0f or 90f degree rotation.

    But unfortunately it causes me to get stuck a lot when colliding with round objects. I'm not sure if I should have included my standard controller.Move() code etc in here as well since I already pasted a lot of code currently. The code snippet doesn't include my
    SignedAngle
    code which I use for slopes since it doesn't cause any issues.
     
    Last edited: Sep 29, 2023
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    The key thing to consider is the shape of the collider does not have to have anything to do with the underlying geometry.

    Those are two separate things. Colliders are invisible.

    For instance, in main games the collider on stairs is just a long sloping ramp to simplify collisions and make walking up and down smooth.
     
  9. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    I guess the thing with the CharacterController component is that you should really only make one call to
    CharacterController.Move
    once per frame. This means gathering up all directions you want the player to move in, add them together, and make that single call.

    Also there's a lot of repeated code going on, and your math I believe will always give weird results. If you want the player to slide down the slope, you need to work out the direction of said slope.

    Here's something I threw together to give you an idea of what I mean:
    Code (CSharp):
    1. public class ExamplePlayerController : Monobehaviour
    2. {
    3.    [SerializeField]
    4.    private CharacterController controller;
    5.    
    6.    [SerializeField]
    7.    private float slopeSpeed = 1.0f;
    8.    
    9.    private void Update()
    10.    {
    11.        Vector3 movementInput = this.GetInputMovement();
    12.        Vector3 slidingMovement = this.GetSlidingMovement();
    13.  
    14.        // probably need gravity too
    15.        Vector3 direction = (movementInput + slidingMovement) * Time.deltaTime;
    16.        controller.Move(direction);
    17.    }
    18.  
    19.    private Vector3 GetInputMovement()
    20.    {
    21.        // read input, etc
    22.    }
    23.  
    24.    private Vector3 GetSlidingMovement()
    25.    {
    26.        bool isOnSlope = this.IsOnSlope(out RaycastHit groundHit);
    27.  
    28.        if (isOnSlope == false)
    29.        {
    30.            return Vector3.zero;
    31.        }
    32.  
    33.        Vector3 normal = groundHit.normal;
    34.  
    35.        //is is a directional vector at a right angle to both vectors
    36.        Vector3 right = Vector3.Cross(normal, Vector3.up);
    37.  
    38.        // this now a directional vector pointing in the direction of the slope
    39.        Vector3 slope = Vector3.Cross(normal, right).normalized;
    40.  
    41.        return slope * slopeSpeed;
    42.    }
    43.  
    44.    public bool IsOnSlope(out RaycastHit groundHit)
    45.    {
    46.        RaycastHit groundHit = GetGroundHit(out bool isHit);
    47.  
    48.        if (isHit == false)
    49.        {
    50.            return false;
    51.        }
    52.  
    53.        if (controller.isGrounded)
    54.        {
    55.            Vector3 normal = hit.normal;      
    56.            float slopeAngle = Vector3.Angle(Vector3.up, normal);
    57.            return slopeAngle > controller.slopLimit;
    58.        }
    59.    }
    60.  
    61.    private RaycastHit GetGroundHit(out bool isHit)
    62.    {
    63.        bool isHit = Physics.RayCast(transform.position, Vector3.down, out RaycastHit hit, 2f);
    64.        return hit;
    65.    }
    66. }
    Written in notepad so not tested, but gives you an idea of what I mean.
     
    androidac67 likes this.
  10. androidac67

    androidac67

    Joined:
    Jan 3, 2023
    Posts:
    75
    I think I get the idea of what you mean. I actually do call .Move() once only, I learnt pretty early on that calling it twice was causing so many different issues, but I can already see the difference in your approach compared to mine.

    I'm going to get this working with my code and hopefully I don't get any more of those painful issues with circle objects. But I appreciate you writing this up on notepad and checking out my very long code. Thanks for the help
     
  11. androidac67

    androidac67

    Joined:
    Jan 3, 2023
    Posts:
    75
    I made two quick changes, one was to get rid of an error because
    isHit
    was not returning enough value path so I added a
    return true;
    to it.

    In all of the methods you created that has
    groundHit
    , I get 5 separate errors all with the same message that I have never seen before: The out parameter 'groundHit' must be assigned to before control leaves the current method. I'm doing research to see if I can find out exactly what that error requires but I've never dealt with it before so I can't see what's wrong in the code itself, and there's an error with "isHit" on line 63: cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter.

    The error on line 63 I believe I can fix as I've fixed several of those same errors in my project before but I have no clue about the 5 errors to do with
    groundHit
    . All I can think of is maybe removing all of the out specifier but then I don't think the code would work. I will look into it further.
     
  12. zulo3d

    zulo3d

    Joined:
    Feb 18, 2023
    Posts:
    510
    Here's my solution for creating slippery slopes:

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class SlippyController : MonoBehaviour
    4. {
    5.     CharacterController cc;
    6.     Vector3 playerVelocity;
    7.     RaycastHit hit;
    8.  
    9.     float gravity=30f;
    10.     float slippy=10f; // How slippy are the slopes?
    11.  
    12.     void Start()
    13.     {
    14.         cc=GetComponent<CharacterController>();
    15.     }
    16.  
    17.     void Update()
    18.     {
    19.         Vector3 moveDirection=new Vector3(Input.GetAxis("Horizontal"),0,Input.GetAxis("Vertical"));
    20.         if (cc.isGrounded)
    21.         {
    22.             // slide down slopes:
    23.             if (Physics.SphereCast(transform.position,0.5f,Vector3.down,out hit,5f)) // raycast to get the hit normal
    24.             {
    25.                 Vector3 dir=Vector3.ProjectOnPlane(Vector3.down,hit.normal); // slope direction
    26.                 playerVelocity+=dir*Vector3.Dot(Vector3.down,dir)*gravity*slippy*Time.deltaTime;
    27.             }
    28.             playerVelocity+=moveDirection;
    29.             playerVelocity*=0.95f;   // basic friction
    30.         }
    31.         else
    32.             playerVelocity.y-=gravity*Time.deltaTime;
    33.        
    34.         cc.Move(playerVelocity*Time.deltaTime);
    35.     }
    36. }
    37.  
     
  13. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    It was written with notepad so likely was going to have issues.

    It also wasn't code made to be used verbatim. It was to illustrate a concept that you could use yourself.
     
  14. TheProcessYet_64

    TheProcessYet_64

    Joined:
    Jan 4, 2023
    Posts:
    39
    I've been having similar problems to OP and your code works well, but on sphere objects the controller still get stuck. Is that to be expected?

    Edit: I believe I'm wrong. It seems to be working correctly in all ways
     
    Last edited: Sep 29, 2023
  15. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    3,899
    Unity has bought and released the Kinematic Character Controller for free. This is one fine piece of work handling all the various edge-cases.

    For walking on top of tubes I'd really prefer to manually place primitive (box) colliders only so that you get a bumpy walk. Basically flat tops and some slopes.

    For walking inside a perfectly round tube you could trigger a special handling in the controller which mathematically calculates the player's orientation based on the position relative to the forward axis. The further the player gets away from it the slower he can walk orthogonally to forward and the y position gets increased. But ... this will also be tricky to match with the "outside" world without sudden position snapping or getting stuck when entering/leaving the tube.
     
  16. TheProcessYet_64

    TheProcessYet_64

    Joined:
    Jan 4, 2023
    Posts:
    39
    Sorry just a follow up. The code when it comes to sliding down round objects works absolutely perfectly. I adjusted some of the values just for preference and it's all good.

    How could that slide code be adopted into other controllers like Brackey's or even the very basic Unity docs official controller? I broke down your code line by line so that I understood all of it, and I think the
    playerVelocity+=moveDirection;
    is the part that makes it tricky to integrate with the other controllers. To me it looks really straight forward and simple to use on any script I want but for some reason its a bit tricky.
     
  17. zulo3d

    zulo3d

    Joined:
    Feb 18, 2023
    Posts:
    510
    I can't really answer that as every controller script works differently. But yeah, it would be cool if we could create a slide script and drop it onto our character controller along side other scripts but it seems we can only do one Move per Update or bad things happen. So for now we have to do everything from within a single script.

    Just get hacking and hope for the best! :)