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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Rays don't immediately detect collide, DrawLine delayed by one frame

Discussion in 'Scripting' started by tfishell, Dec 29, 2017.

  1. tfishell

    tfishell

    Joined:
    Nov 20, 2017
    Posts:
    97
    I'm having two issues which may be related.

    I'm trying to use raycasts to detect collisions and alter movement accordingly (only y-axis/up or down movement is allowed if x-axis/left or right rays are colliding, and vice versa). I have two scripts, one (CharacterMove.cs) to move the character using MoveTowards() (based on what constraints are or aren't sent by the second script), and the second (CharacterCollide.cs) to create the rays and look for collisions. Any collisions detected by the rays in Collide.cs is sent to Move.cs, and movement is supposed to be restricted accordingly in Move.cs.

    There are currently two issues, both related to accuracy:

    • The DrawLine lines meant to represent the rays are delayed by one frame, like the drawings are lagging behind ever so slightly. (They draw accurately on the last frame of movement.) It's not a big deal but I'd like to know why this is. Maybe because of the order that things happen - redraw the sprites first then drawing the DrawLine - or because the method that contains DrawLine isn't called until a click happens (the coroutine MoveCharacter in Move.cs calls CheckCollisions in Collide.cs), thus slightly delaying the information needed to accurately update the DrawLine method. https://i.imgur.com/H84k3RN.png
    • The rays appear to go past the collider they hit for a brief period of time, before the scripts/methods detect the hit and stop movement. This may be standard behavior, I'm not sure. If not, I'm guessing it also has to do with the order of methods and scripts and how they interact, maybe with quick changes in movement too. Since I'm using two rays for left and right movement and flipping back and forth (depending on if the click is left or right to the character), maybe when a quick change happens close to a collider, the rays may not update quick enough to detect the collider right away. https://i.imgur.com/gcFxTxu.png

    I tried to create a something resembling a flowchart that may help explain this (something I've only done once or twice before), though I understand if it's too confusing - https://i.imgur.com/ZWMQhAf.png

    CharacterMove.cs:
    Code (CSharp):
    1.     void Update()
    2.     {
    3.         //if mouse is clicked onscreen
    4.         if (Input.GetMouseButtonUp(0))
    5.         {
    6.             //stop the current click and coroutine
    7.             StopCoroutine("MoveCharacter");
    8.             //get the clicked pos
    9.             Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
    10.             clickHappened = true;
    11.             //start new coroutine
    12.             StartCoroutine("MoveCharacter", mousePos);
    13.         }
    14.     }
    15.  
    16.     public IEnumerator MoveCharacter(Vector3 mousePos)
    17.     {
    18.        
    19.         //store current clicked position in variable
    20.         endPos = mousePos;
    21.  
    22.         //if char hasn't reached the clicked position
    23.         while (transform.position != endPos)
    24.         {
    25.  
    26.             objStopMoveScript.checkCollisions();
    27.             //if char isn't hitting an object
    28.             if (!hittingAnObject)
    29.             {
    30.                 //move towards the clicked position (as normal)
    31.                 transform.position = Vector3.MoveTowards(transform.position, endPos, speed * Time.deltaTime);
    32.                 //yield return null;
    33.             }
    34.  
    35.             //else if char is hitting object (info is sent by ObjectStopMovement script)
    36.             else if (hittingAnObject)
    37.             {
    38.                 //if an x ray is hitting
    39.                 if (rayBeingSent == 0 || rayBeingSent == 1)
    40.                 //don't move anymore to the left or right
    41.                 {
    42.                     transform.position = Vector3.MoveTowards(transform.position, new Vector3(transform.position.x, endPos.y), speed * Time.deltaTime);
    43.                 }
    44.                 //else if a y ray is hitting
    45.                 else if (rayBeingSent == 2 || rayBeingSent == 3)
    46.                 {
    47.                     Debug.Log(rayBeingSent);
    48.                     transform.position = Vector3.MoveTowards(transform.position, new Vector3(endPos.x, transform.position.y), speed * Time.deltaTime);
    49.                 }
    50.             }
    51.             //check and return nothing
    52.             yield return null;
    53.         }
    54.        
    55.         clickHappened = false;
    56.     }
    CharacterCollide.cs
    Code (CSharp):
    1.  
    2.     public void Start()
    3.     {
    4.         //initalize charCollider and character move script
    5.         charCollider = character.GetComponent<CircleCollider2D>();
    6.         charMoveScript = character.GetComponent<CharacterMove>();
    7.         //initalize the array and index total
    8.         rays = new RaycastHit2D[4];
    9.     }
    10.  
    11.    
    12.     //create a ray when called
    13.     RaycastHit2D CreateRaycasts(Vector2 rayCoordinates, Vector2 rayDirection)
    14.     {
    15.         //return the ray information
    16.         return Physics2D.Raycast(rayCoordinates, rayDirection, rayLength, whatToCollide);
    17.     }
    18.  
    19.     //draw the x-axis rays (when called below)
    20.     void DrawXLines(Vector2 _topOrigin, Vector2 _bottomOrigin, int leftIsNegOne, Color color)
    21.     {
    22.         Debug.DrawLine(_topOrigin, new Vector2(_topOrigin.x + rayLength * leftIsNegOne, _topOrigin.y), color);
    23.         Debug.DrawLine(_bottomOrigin, new Vector2(_bottomOrigin.x + rayLength * leftIsNegOne, _bottomOrigin.y), color);
    24.     }
    25.  
    26.     public void checkCollisions()
    27.     {
    28.         //if click was farther left than the current pos
    29.         if (charMoveScript.endPos.x < transform.position.x)
    30.         {
    31.             //shoot the horizontal rays to the left
    32.             topOrigin = new Vector2(charCollider.bounds.min.x, charCollider.bounds.max.y);
    33.             bottomOrigin = new Vector2(charCollider.bounds.min.x, charCollider.bounds.min.y);
    34.             xDirection = Vector2.left;
    35.         }
    36.         //else shoot horizontal rays to the right
    37.         else if (charMoveScript.endPos.x > transform.position.x)
    38.         {
    39.             topOrigin = new Vector2(charCollider.bounds.max.x, charCollider.bounds.max.y);
    40.             bottomOrigin = new Vector2(charCollider.bounds.max.x, charCollider.bounds.min.y);
    41.             xDirection = Vector2.right;
    42.         }
    43.  
    44.         //if click was below current pos
    45.         if (charMoveScript.endPos.y < transform.position.y)
    46.         {
    47.             //start the vertical rays from the bottom of the char and point them down
    48.             leftOrigin = new Vector2(charCollider.bounds.min.x, charCollider.bounds.min.y);
    49.             rightOrigin = new Vector2(charCollider.bounds.max.x, charCollider.bounds.min.y);
    50.             yDirection = Vector2.down;
    51.         }
    52.         //else start rays from the top of the char and point them up
    53.         else if (charMoveScript.endPos.y > transform.position.y)
    54.         {
    55.             leftOrigin = new Vector2(charCollider.bounds.min.x, charCollider.bounds.max.y);
    56.             rightOrigin = new Vector2(charCollider.bounds.max.x, charCollider.bounds.max.y);
    57.             yDirection = Vector2.up;
    58.         }
    59.  
    60.         //constantly create new rays as the char moves
    61.         xTop = CreateRaycasts(topOrigin, xDirection);
    62.         xBottom = CreateRaycasts(bottomOrigin, xDirection);
    63.         yLeft = CreateRaycasts(leftOrigin, yDirection);
    64.         yRight = CreateRaycasts(rightOrigin, yDirection);
    65.  
    66.         //add (and update) rays
    67.         rays[0] = xTop;
    68.         rays[1] = xBottom;
    69.         rays[2] = yLeft;
    70.         rays[3] = yRight;
    71.  
    72.         //draw the rays, called at the bottom (ONLY X-AXIS RAYS FOR NOW)
    73.         DrawXLines(topOrigin, bottomOrigin, -1, Color.red);
    74.  
    75.         //loop through the List of rays
    76.         RayLoop();
    77.     }
    78.  
    79.     void RayLoop()
    80.     {
    81.         for (var i = 0; i < rays.Length; i++)
    82.         {
    83.             if (rays[i].collider != null)
    84.             {
    85.                 //bool is set to true, send to CharMoveClampScript;
    86.                 charMoveScript.hittingAnObject = true;
    87.                 charMoveScript.rayBeingSent = i;
    88.  
    89.                 //return so that only that specific ray affects movement, and not all of them at once
    90.                 return;
    91.             }
    92.             else if (rays[i].collider == null)
    93.             {
    94.                 //bool is set to false, send to CharMoveClampScript;
    95.                 charMoveScript.hittingAnObject = false;
    96.             }
    97.         }
    98.     }
    99. }
    100.  
    This might be a good time for me to learn how the order of methods can affect their execution and accuracy onscreen; likewise with multiple scripts interacting with each other. I thought everything in the scripts would be run through in milliseconds and "gather" and "organize" all the appropriate information, positions, etc. before the frame was drawn (especially with so little on the screen here), and thus accuracy wasn't an issue, but I guess I'm kinda wrong there.
     
  2. tfishell

    tfishell

    Joined:
    Nov 20, 2017
    Posts:
    97
    Sorry to bump, but could anyone give me an idea of what is happening? It's not a huge deal but I'd like to know what is going on. Even some links might be helpful.
     
  3. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    9,000
    Kurt-Dekker and tfishell like this.
  4. Chris-Trueman

    Chris-Trueman

    Joined:
    Oct 10, 2014
    Posts:
    1,256
    Physics checks that you want to be accurate should be done in FixedUpdate as it will have all the correct/accurate positions of the colliders as they get updated at that point in execution. FixedUpdate is the point in the update loop that the physics system updates, but it doesn't work at the same frequency of the Update loop. So you will get misses when you should get a hit because its not sync'd when you do checks outside of FixedUpdate.
     
    tfishell likes this.
  5. tfishell

    tfishell

    Joined:
    Nov 20, 2017
    Posts:
    97
    Thank you for responding. I'll try both suggestions tomorrow evening, but just so you are aware, I'm not using a Rigidbody 2D and am not using any physics other than Physics2D.Raycast (as far as I'm aware).

    EDIT: https://unity3d.com/learn/tutorials/topics/physics/physics-best-practices does say "Don’t use use Raycasts inside a FixedUpdate() function, sometimes even inside an Update() may be an overkill."
     
    Last edited: Dec 30, 2017
  6. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    Many rules are not so black n white. That's my experience/learning with time in Unity. Casting a handful of rays or less inside FixedUpdate isn't such a huge deal , imo. If you don't have to always do it, then don't.. be sensible, like with anything.
    If you need them only at certain times, choose that over always doing it. :)
     
    tfishell likes this.
  7. Chris-Trueman

    Chris-Trueman

    Joined:
    Oct 10, 2014
    Posts:
    1,256
    I think that statement isn't for using it in FixedUpdate in general but using it every FixedUpdate. Raycasts can be expensive so make use of layers and setup masking with the raycast so it costs less to use.
     
    tfishell likes this.
  8. tfishell

    tfishell

    Joined:
    Nov 20, 2017
    Posts:
    97
    I put basically the entirety of my CharacterCollide/raycasting script inside FixedUpdate because I wasn't sure what all needed or didn't need to be in there. Using a bool to check, most of the code only runs if the coroutine in the CharacterMove script has started.

    Unfortunately nothing has really changed. The rays are still one frame behind and, more importantly, the rays don't adjust movement immediately when they hit a collider.

    https://imgur.com/TPLM6cw

    I don't know if the involvement of a coroutine is affecting things, or two scripts interacting causes the timing to be off. I'll experiment with putting everything into one script.

    Code (CSharp):
    1.    public void FixedUpdate()
    2.     {
    3.         if (charMoveScript.movingCoroutineOn)
    4.         {
    5.             Debug.Log("fixedUpdate is happening!");
    6.  
    7.             //if click was farther left than the current pos
    8.             if (charMoveScript.endPos.x < transform.position.x)
    9.             {
    10.                 //shoot the horizontal rays to the left
    11.                 topOrigin = new Vector2(charCollider.bounds.min.x, charCollider.bounds.max.y);
    12.                 bottomOrigin = new Vector2(charCollider.bounds.min.x, charCollider.bounds.min.y);
    13.                 xDirection = Vector2.left;
    14.             }
    15.             //else shoot horizontal rays to the right
    16.             else if (charMoveScript.endPos.x > transform.position.x)
    17.             {
    18.                 topOrigin = new Vector2(charCollider.bounds.max.x, charCollider.bounds.max.y);
    19.                 bottomOrigin = new Vector2(charCollider.bounds.max.x, charCollider.bounds.min.y);
    20.                 xDirection = Vector2.right;
    21.             }
    22.  
    23.             //if click was below current pos
    24.             if (charMoveScript.endPos.y < transform.position.y)
    25.             {
    26.                 //start the vertical rays from the bottom of the char and point them down
    27.                 leftOrigin = new Vector2(charCollider.bounds.min.x, charCollider.bounds.min.y);
    28.                 rightOrigin = new Vector2(charCollider.bounds.max.x, charCollider.bounds.min.y);
    29.                 yDirection = Vector2.down;
    30.             }
    31.             //else start rays from the top of the char and point them up
    32.             else if (charMoveScript.endPos.y > transform.position.y)
    33.             {
    34.                 leftOrigin = new Vector2(charCollider.bounds.min.x, charCollider.bounds.max.y);
    35.                 rightOrigin = new Vector2(charCollider.bounds.max.x, charCollider.bounds.max.y);
    36.                 yDirection = Vector2.up;
    37.             }
    38.  
    39.             //RAYS CREATED HERE constantly create new rays as the char moves
    40.             xTop = Physics2D.Raycast(topOrigin, xDirection, rayLength, whatToCollide);
    41.             xBottom = Physics2D.Raycast(bottomOrigin, xDirection, rayLength, whatToCollide);
    42.             yLeft = Physics2D.Raycast(leftOrigin, yDirection, rayLength, whatToCollide);
    43.             yRight = Physics2D.Raycast(rightOrigin, yDirection, rayLength, whatToCollide);
    44.  
    45.             //add (and update) rays
    46.             rays[0] = xTop;
    47.             rays[1] = xBottom;
    48.             rays[2] = yLeft;
    49.             rays[3] = yRight;
    50.  
    51.             //draw the horizontal rays left or right
    52.             if (xDirection == Vector2.left)
    53.             {
    54.                 Debug.DrawLine(topOrigin, new Vector2(topOrigin.x - rayLength, topOrigin.y), Color.red);
    55.                 Debug.DrawLine(bottomOrigin, new Vector2(bottomOrigin.x - rayLength, bottomOrigin.y), Color.red);
    56.             }
    57.             else if (xDirection == Vector2.right)
    58.             {
    59.                 Debug.DrawLine(topOrigin, new Vector2(topOrigin.x + rayLength, topOrigin.y), Color.red);
    60.                 Debug.DrawLine(bottomOrigin, new Vector2(bottomOrigin.x + rayLength, bottomOrigin.y), Color.red);
    61.             }
    62.  
    63.             for (var i = 0; i < rays.Length; i++)
    64.             {
    65.                 if (rays[i].collider != null)
    66.                 {
    67.                     //bool is set to true, send to charMoveScript;
    68.                     charMoveScript.charColliding = true;
    69.                     charMoveScript.rayBeingSent = i;
    70.  
    71.                     Debug.Log(i);
    72.                     //return so that only that specific ray affects movement, and not all of them at once
    73.                     return;
    74.                 }
    75.                 else if (rays[i].collider == null)
    76.                 {
    77.                     charMoveScript.charColliding = false;
    78.                 }
    79.             }
    80.         }
    81.     }
    82. }
    Yeah that's kinda what I figure. I guess that can be a blessing or a curse depending on the project. :p
     
  9. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,131
    Can you rule out, that it is related to debug.drawline ? I.e are the coordinates correct in the frame in question, just the visual representation is “late” and off? You could also try to draw into the gameview with linerenderer...

    Not sure what happens in coroutines, but this could lead to a frame delay. Check unity script execution order...
     
    tfishell likes this.
  10. tfishell

    tfishell

    Joined:
    Nov 20, 2017
    Posts:
    97
    Thanks for the tip, I've never used LineRenderer before but I think I figured that out.

    I'm not 100% sure, but it looks like the rays themselves may actually be delayed. The linerenderer lines up with the DrawLine lines, and both visual representations are off by one frame (the character's collider moves faster by 1 frame; like the visual representations are at frame 19 whereas the collider is at frame 20). They move through the collider for one frame.

    Maybe somebody can check out these screenshots: https://imgur.com/a/FuCn6

    Maybe I'll need an alternative to a coroutine to move the character so collisions are accurate.

    Or maybe I'm being too picky? In any case, I'll spend more time tomorrow trying things out.
     
  11. GroZZleR

    GroZZleR

    Joined:
    Feb 1, 2015
    Posts:
    3,201
    Looks like a race condition to me. Your character moves to an invalid position (first), then the collision script checks and says stop moving (second) and the next frame the character can no longer move. Optimal solution is to raycast before you move, find out how far you can possibly move and set the position from the raycast data (or lack thereof) itself. That way the integrity of the simulation is always perfect.
     
    tfishell likes this.
  12. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    The actual collider position queried is only updated after fixedupdate runs, therefore you experience things out of time, which you might perceive as lag?
     
    tfishell likes this.
  13. MickM

    MickM

    Joined:
    Nov 19, 2012
    Posts:
    166
    My understanding is the fixed update requirement is mostly important for applying forces etc. The downside with doing raycasts etc for your movement in there is it is running at a slower framerate than your actual movement code (depending on your fixed timestep).
    eg. If your physics is running at 50 fps but your game is running at 100 fps, you are only checking for collisions every second movement.

    Also is there a reason you are using a coroutine for movement instead of just checking the clickHappened flag inside update and calling a move method if applicable (containing the code inside the while loop and the arrival check/flag reset.)

    Regarding the ray lag - that is because you are calling check collisions inside your move script which does the collision check and draws the ray THEN returns to the move script and moves the character. This is why the rays look like they are lagging; they are drawn from the character's pre-move position.

    I may have misunderstood your comments regarding the debug ray intersection so disregard the below if it isnt applicable.
    The visualisation of your rays may be misleading you - the images you link note that the ray goes through the wall; this is because you are drawing the ray to the full length of the raycast so it WILL go through the wall when the ray hits it.

    If you are expecting different behaviour (eg. not penetrating the wall) you should set the length of the ray to be the ray length OR the the endpoint to the first contact point (whichever is the smaller distance).
     
    Last edited: Dec 31, 2017
    tfishell likes this.
  14. tfishell

    tfishell

    Joined:
    Nov 20, 2017
    Posts:
    97
    Sounds like I'm best off reworking this from scratch, maybe using a single script - rough idea: detect the mouseclick, store the position, if the click was in a collider subtract the distance in the collider from the full distance, then move the rest of the distance.

    Cheers, I'll get started on that tomorrow; I'm sure I'll have question(s) but I'll create a separate thread(s). (Currently it's 1 am where I am.)
     
  15. MickM

    MickM

    Joined:
    Nov 19, 2012
    Posts:
    166
    It isnt a race condition; the movement coroutine first checks for collisions then only moves if there was no collision.
    The point regarding setting the movement distance based off the raycast collision point is a good one for consistent stopping at obstacles though.

    The perceived graphical disparity however is due to the debug rays being drawn from the initial position, prior to the movement. There is nothing wrong with this - it is accurately drawing the raycast (which occurs prior to movement). Just the reason for the confusion.
     
    tfishell likes this.
  16. MickM

    MickM

    Joined:
    Nov 19, 2012
    Posts:
    166
    Additionally: I dont believe you need to start from scratch but one thing I would advise is slightly refactor the script interactions. Is there a reason the character collide script needs to be a monobehaviour? If there is no requirement to have it on a game object, you can just make it a pure C# script and create an instance of it within your character move script.

    Additionally, the collide script is tightly coupled to the move script and is changing variables from outside it.
    You could quite easily change it so your check collisions functions returns some information; all you need is a boolean and an integer (for collision flag and ray number). This could be done with a simple two element array.
    Amending it like this means you can make the hittingAnObject and rayBeingSent (rayHittingAnObject?) to be private and is probably a better practice.
     
    tfishell likes this.
  17. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Debug collider position vs transform position.
     
    tfishell likes this.
  18. tfishell

    tfishell

    Joined:
    Nov 20, 2017
    Posts:
    97
    Just to answer these...
    To be honest, somebody else helped me figure out the movement script and iirc they gave me an example with the coroutine, so I didn't want to fix what wasn't broken. I'm trying to learn Unity/C# outside of this project, but it's a little bit at a time (and can feel overwhelming at times).

    Again, just another case of me not wanting to mess around with what I don't really understand.

    Looking at https://docs.unity3d.com/ScriptReference/MonoBehaviour.html , "When you use C#, you must explicitly derive from MonoBehaviour. When you use UnityScript (a type of JavaScript), you do not have to explicitly derive from MonoBehaviour." I deleted ": Monobehavior" just now and lost access to "gameObject" and "transform".
     
  19. MickM

    MickM

    Joined:
    Nov 19, 2012
    Posts:
    166
    No worries, if you are learning programming at the same time it will be harder.

    This is because those are variables you have direct access to based off inheriting from Monobehaviour; you can still use the UnityEngine objects but those 'freebies' arent available. You would need to pass them in to the class (possibly as part of the constructor).

    I wouldnt worry about it at this stage of the learning but I would also recommend doing a few 'pure' C# tutorials (outside Unity) so you understand how it all works.
    Hopefully the issue behind the original question is clear now though.
    And happy new year eh!
     
    tfishell likes this.
  20. tfishell

    tfishell

    Joined:
    Nov 20, 2017
    Posts:
    97
    I do have a C# book I am going through and am following Brackeys' C# tutorials (but not every day; I've split up the learning into Unity, C#, and related math).

    I kind of understand the issue - as far as the delay in the raycasts - but I'll need to browse through the posts again to try to understand/comprehend it better. (A bigger issue I'm having now is that the vertical raycasts sometimes won't detect a collider when moving down, letting the player move through a collider; but that needs its own post if I can't figure it out in a week or so. :p)

    Happy New Year too! (almost; I'm on EST)
     
  21. tfishell

    tfishell

    Joined:
    Nov 20, 2017
    Posts:
    97
    Just to respond to this: I believe I tried that a few hours ago (Update and move method), and the movement happened instantaneously between frames. So it looks like the coroutine / yield return is ensuring that the movement continues over a period of time. (I'm sure there's another way to do this sans coroutine, but for now I'm gonna try to figure out and squash a more important bug.)
     
  22. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,131
    Hi there, I think you are stuck too long with this. My recommendation is to simplify and then gradually increase complexity.

    One player Sprite with simple axis move script (transform.position) and ray caster against obstacles (box colliders)

    As suggested earlier, you could also log the frame and the positions at key positions in your scripts to find out an explanation for the “unexpected” behavior.

    If you can’t solve it, you can attach a minimal unityackage and I offer to take a look, when I find time (next few days)
     
    tfishell likes this.
  23. tfishell

    tfishell

    Joined:
    Nov 20, 2017
    Posts:
    97
    Thing is I'm relatively happy with the movement system (not so bothered by my original issue) and it doesn't seem too complex, but I feel in over my head with the bug I've come across.

    So maybe I'll go back to basics and try a different move system, something like you or my post at #14 suggested.
     
  24. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    Like @MickM stated, there's technically nothing wrong with it at all. It's just the visualization that causes the confusion. You actually want exactly those rays, as they're showing the information of the state that allowed your character to move at all.
    (There are a few other bugs though).

    If you drew the rays that you seem to expect from what I've read in this thread, those would rather be the rays that should be used in the next frame and so on.

    In other words, if there was a way to pause mid-update and have the engine-renderers keep doing their job, you could see that it's valid information that's being drawn.

    Unfortunately that's not possible afaik, but you could fake that effect (for a better understanding only - that's not supposed to be an improvement! ) by waiting a short time until you execute the movement. For instance, yield a few frames before the actual movement is executed.
    The result should be that the rays are correct except for the frame that finally moves the objects - then it'd be what you can currently see and describe as "lagging behind" - which isn't true of course.

    Another option would be to just draw what you expect - the rays for the next frame. Though if the information for the movement in the next frame is also captured in the current frame, this might introduce other problems. So you'd have to do both: evaluate collisions of the current frame and draw the information for the next frame - that would be what you expect.

    In regards to the other bugs you mentioned, such as
    One reason for that is the stop-movement part. If the collision ray is ray1 or ray2 , you stop movement in X-direction but you just keep moving into y-direction even though that could be blocked as well.

    Another issue that appears to happen is that you're not guaranteed to line up with the actual box-collider. Sometimes you line up, sometimes you stop too early. This is also due to the stop-movement logic, you should move the remaining distance as well.

    Last but not least, it's a good yet trivial approach (I know it's just for learning). But it's worth noting that you could have a thin obstacle that happens to be centered below/above/right/left of your character's collider - that wouldn't be detected as the checks depend on a few rays instead of shapes (2D) and volumens (3D). You could just run into those obstacles.
     
    tfishell likes this.
  25. tfishell

    tfishell

    Joined:
    Nov 20, 2017
    Posts:
    97
    (Thanks to everyone who took the time to write and tried to help; I think it's good for newbies to show gratitude, especially when it takes time to write. Unfortunately, a sizable portion was out of my realm of comprehension at this point - even stuff most here might consider simple - but I did read it all. I probably should have asked for some pseudo-code for some examples.)

    I think I may go back and start from scratch (and see if there's a better approach to take), but for this version of 2D movement, here is what I have script-wise and seems to be working decently, despite some persistent bugs.

    Basically, the CharMove script I took out the coroutine and am using now Update to MoveTowards based on mouseclicks of course. If there is a click, the CharCollide script starts creating and updating rays looking for movement (I'm using separate arrays for x and y rays, which I think is better than 1 array). If an x Ray hit, xCollider in CharMove affects movement (only y movement allowed); if a y Ray hits, yCollider affects movement (only x movement allowed); otherwise any movement happens.

    Gif of movement: https://imgur.com/a/Do182

    main part of CharMove Script:
    Code (CSharp):
    1.  
    2.     void Update()
    3.     {
    4.  
    5.         if (Input.GetMouseButtonUp(0))
    6.         {
    7.             //get the clicked pos
    8.             mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
    9.             clickHappened = true;
    10.             moving = true;
    11.         }
    12.         //store current clicked position in variable
    13.         endPos = mousePos;
    14.  
    15.         //clamp position, so movement stays within camera boundaries
    16.         endPos.x = Mathf.Clamp(endPos.x, boundingBox.xMin, boundingBox.xMax);
    17.         endPos.y = Mathf.Clamp(endPos.y, boundingBox.yMin, boundingBox.yMax);
    18.         endPos.z = Mathf.Clamp(endPos.z, 0, 0); //keep the z-depth consistant so char never goes behind cam
    19.  
    20.         //if char hasn't reached the clicked position
    21.         if (transform.position != endPos && clickHappened)
    22.         {
    23.             //if char isn't hitting an object
    24.             if (xColliding == true)
    25.             {
    26.                 transform.position = Vector3.MoveTowards(transform.position, new Vector3(transform.position.x, endPos.y), Time.deltaTime * speed);
    27.             }
    28.             else if (yColliding == true)
    29.             {
    30.                 transform.position = Vector3.MoveTowards(transform.position, new Vector3(endPos.x, transform.position.y), Time.deltaTime * speed);
    31.             }
    32.             else if (!xColliding && !yColliding)
    33.             {
    34.                 //move towards the clicked position (as normal)
    35.                 transform.position = Vector3.MoveTowards(transform.position, endPos, Time.deltaTime * speed);
    36.             }
    37.         }
    38.         else if (transform.position == endPos && clickHappened)
    39.         {
    40.             clickHappened = false;
    41.             moving = false;
    42.             Debug.Log("done!");
    43.         }
    44.     }
    45. }
    main part of CharCollide Script:
    Code (CSharp):
    1.  
    2.     public void Update()
    3.     {
    4.         if (charMoveScript.moving)
    5.         {
    6.             //if click was farther left than the current pos
    7.             if (charMoveScript.mousePos.x < transform.position.x)
    8.             {
    9.                 xOrigin = new Vector2(charCollider.bounds.min.x, charCollider.bounds.max.y);
    10.                 xOrigin2 = new Vector2(charCollider.bounds.min.x, charCollider.bounds.min.y);
    11.                 //shoot the horizontal rays to the left
    12.                 xDirection = Vector2.left;
    13.             }
    14.             //if click was farther right than current pos
    15.             else if (charMoveScript.mousePos.x > transform.position.x)
    16.             {
    17.                 xOrigin = new Vector2(charCollider.bounds.max.x, charCollider.bounds.max.y);
    18.                 xOrigin2 = new Vector2(charCollider.bounds.max.x, charCollider.bounds.min.y);
    19.                 //shoot the horizontal rays to the right
    20.                 xDirection = Vector2.right;
    21.             }
    22.  
    23.             //if click was below current pos
    24.             if (charMoveScript.mousePos.y < transform.position.y)
    25.             {
    26.                 yOrigin = new Vector2(charCollider.bounds.min.x, charCollider.bounds.min.y);
    27.                 yOrigin2 = new Vector2(charCollider.bounds.max.x, charCollider.bounds.min.y);
    28.                 //start the vertical rays from the bottom of the char and point them down
    29.                 yDirection = Vector2.down;
    30.             }
    31.             //if click was above current pos
    32.             else if (charMoveScript.mousePos.y > transform.position.y)
    33.             {
    34.                 yOrigin = new Vector2(charCollider.bounds.min.x, charCollider.bounds.max.y);
    35.                 yOrigin2 = new Vector2(charCollider.bounds.max.x, charCollider.bounds.max.y);
    36.                 //Debug.Log("going UP!");
    37.                 yDirection = Vector2.up;
    38.             }
    39.        
    40.             //constantly create new rays as the char moves
    41.             xRay = Physics2D.Raycast(xOrigin, xDirection, xLength, whatToCollide);
    42.             xRay2 = Physics2D.Raycast(xOrigin2, xDirection, xLength, whatToCollide);
    43.             yRay = Physics2D.Raycast(yOrigin, yDirection, yLength, whatToCollide);
    44.             yRay2 = Physics2D.Raycast(yOrigin2, yDirection, yLength, whatToCollide);
    45.  
    46.             //add (and update) rays
    47.             xArray[0] = xRay;
    48.             xArray[1] = xRay2;
    49.             yArray[0] = yRay;
    50.             yArray[1] = yRay2;
    51.  
    52.             //---DRAWING RAYS
    53.             Debug.DrawLine(xOrigin, new Vector2(xOrigin.x + xLength * xDirection.x, xOrigin.y), Color.red);
    54.             Debug.DrawLine(xOrigin2, new Vector2(xOrigin2.x + xLength * xDirection.x, xOrigin2.y), Color.red);
    55.  
    56.             Debug.DrawLine(yOrigin, new Vector2(yOrigin.x, yOrigin.y + yLength * yDirection.y), Color.cyan);
    57.             Debug.DrawLine(yOrigin2, new Vector2(yOrigin2.x, yOrigin2.y + yLength * yDirection.y), Color.green);
    58.  
    59.             //loop through x rays
    60.             for (var i = 0; i < xArray.Length; i++)
    61.             {
    62.                 if (xArray[i].collider != null)
    63.                 {
    64.                     charMoveScript.xColliding = true;
    65.                     return; //return exits the current method, break exits the loop
    66.                 }
    67.                 else if (xArray[i].collider == null)
    68.                 {
    69.                     charMoveScript.xColliding = false;
    70.                 }
    71.             }
    72.             //loop through y rays
    73.             for (var h = 0; h < yArray.Length; h++)
    74.             {
    75.                 if (yArray[h].collider != null)
    76.                 {
    77.                     charMoveScript.yColliding = true;
    78.                     return;
    79.                 }
    80.                 else if (yArray[h].collider == null)
    81.                 {
    82.                     charMoveScript.yColliding = false;
    83.                 }
    84.             }
    85.         }
    86.     }