Search Unity

  1. We are migrating the Unity Forums to Unity Discussions. On July 12, the Unity Forums will become read-only. On July 15, Unity Discussions will become read-only until July 18, when the new design and the migrated forum contents will go live. Read our full announcement for more information and let us know if you have any questions.

Question Making a character orbit around another character or object via Rigidbody

Discussion in 'Scripting' started by FireStriker0, May 10, 2023.

  1. FireStriker0


    May 10, 2023
    I'm trying to make a Legend of Zelda-esque game, with the same kind of in-combat movement.

    I'm trying to do it purely with Rigidbody, but unfortunately, nothing seems to have worked smoothly - transform.LookAt() didn't yield any positive results.

    Current code is as follows. Thank you:

    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    4. public class Movement : MonoBehaviour
    5. {
    6.     internal Rigidbody rb;
    8.     private float x, y, z;
    10.     //Float Scalar for the Vector Movement Speed
    11.     public float vel;
    12.     private float angleVel;
    14.     private float hAxis, vAxis;
    16.     internal Vector3 moveVec;
    18.     //Boolean to control whether or not Movement is possible, based on other states.
    19.     public bool bCanWalk;
    21.     void Start()
    22.     {
    23.         rb = GetComponent<Rigidbody>();
    24.         rb.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationZ;
    25.     }
    26.     void Update()
    27.     {
    28.         //Move once per frame
    29.         Move();
    30.     }
    32.     void Move()
    33.     {
    34.         //If the Character is even able to walk, then do the following:
    35.         if (bCanWalk)
    36.         {
    37.             //If the Game Object to which this Script is attached is tagged as "Player", then do the following:
    38.             if (gameObject.tag == "Player")
    39.             {
    40.                 //Refresh the Player's Movement
    41.                 RefreshPlayerMove();
    42.             }
    43.         }
    44.     }
    46.     void RefreshPlayerMove()
    47.     {
    48.         y = rb.velocity.y;
    49.         hAxis = Input.GetAxisRaw("Horizontal");
    50.         vAxis = Input.GetAxisRaw("Vertical");
    52.         switch (hAxis)
    53.        {
    54.             case -1:
    55.             case 1:
    56.                 angleVel += vel;
    57.                 x = (float) Math.Sin(angleVel) + hAxis * vel;
    58.                 z = (float) Math.Cos(angleVel) + vAxis * vel;
    59.                 break;
    60.             default:
    61.                 angleVel = 0;
    62.                 x = 0;
    63.                 z = vAxis * vel;
    64.                 break;
    65.        }
    67.         SetMoveVec();
    69.         rb.velocity = moveVec;
    70.     }
    72.     private void SetMoveVec()
    73.    {
    74.         moveVec.x = x;
    75.         moveVec.y = y;
    76.         moveVec.z = z;
    77.     }
    78. }
    Last edited: May 10, 2023
  2. Yoreki


    Apr 10, 2019
    Why? I also have troubles seeing how the title relates to doing Zelda with rigidbodies. Maybe im thinking of the older games, but what do you need this orbiting feature for? A bit of context would be helpful, or potentially a video of what exactly you are imagining.

    Also, absolutely please use code tags. Plaintext code is very hard to read.
  3. FireStriker0


    May 10, 2023
    From what I've seen and from my tests, Rigidbodies achieve most of what I want character movement-wise without being subjected to collision and acceleration errors the way directly modifying the Transform object is (My previous attempts at modifying the Transform for movement resulted in the player moving way too quickly, and always made the player go out-of-bounds when he hit the wall. P.S. I'm using quads for the walls, but I need to in order to accomplish some of the other design goals of my game). By all means, however, if there's something wrong with Rigidbodies, please tell me what they are and what I should do instead without resulting in the same issues.

    Allow me to elaborate: I'm trying to make the in-combat movement system found in 3D close combat games and systems, e.g. the one found in Legend of Zelda Ocarina of Time and onward, in which you can move towards and away from your targeted enemy, and trying to move "Left" or "Right" results in you moving in a circle around the target clockwise or counter-clockwise, respectively, thus effectively "orbiting" the target. I also want it to happen instantly/instantaneously, meaning no acceleration or de-acceleration, hence why I'm using GetInputRaw().

    In short, I'm trying to add Legend of Zelda: Ocarina of Time Z-Targeting, and it's not working. I added screenshots for reference.

    Additionally, what's currently happening is that the player is able to freely move around the target as if he wasn't locked on. I tried slapping on transform.LookAt() in a separate script, but it didn't work as intended - it did initially, but the more I moved around, the greater there was a "loss of numerical significance" that resulted in the player basically going past the target (Don't know how else to describe it).

    I understand that Z-targetting effectively revolves around Unit Circles, Slerps, and SOHCAHTOA, but something just isn't "clicking", especially in the context of Rigidbodies; I tried many different code combinations and permutations, and this is still the most optimal one, unfortunately.

    Apologies for the plain-text code dump; already corrected. Let me know if there's anything else I can clarify, and thank you very much for the response.

  4. Yoreki


    Apr 10, 2019
    I did not want to imply that there is something intrinsically wrong with rigidbodies. The alternative to rigidbody based movement however is a character controller, not directly manipulating the transform position. This also handles collisions accordingly.

    Both rigidbodies and character controllers start at two extreme points: perfect physics / fluidity but little control vs. perfect control but little fluidity. Any game wants to reach a point somewhere inbetween. The question of which to start with is usually determined by which end of the scale that point is closer to. For the majority of games the correct starting point is a character controller, but there is valid usecases for rigidbody based movement aswell.

    You appear to be directly setting the values of the velocity vector which, to my knowledge, is not intended outside of 2D games. Usually when people talk about a rigidbody based movement controller they actually apply forces to the model. This involves a lot of overhead to make it controllable, but grants the above mentioned perfect fluidity to the approach. Some physics based racing games make use of this (acceleration, deceleration, drag, friction, ..) as well as ragdolls, which usually are just supposed to collapse or fly off into some direction realistically. For what you are doing a character controller based approach seems more reasonable, since you only rely on the rigidbody for gravity, but want those snappy controls. Overwriting the velocity values directly is usually bad if you want a physics based approach, since then you dont get the physics at all. Any forced applied to the character would be instantly overwritten after all.

    I never implemented z-targeting. Intuitively i would calculate a vector from the player to the enemy and then a vector that is orthogonal to that and parallel to the x-axis. I would then use these two directions for my forwards/backwards and left/right movement instead. Take this with a grain of salt, as again i never implemented such a thing.
    Last edited: May 11, 2023
  5. spiney199


    Feb 11, 2021
    The direction you want to 'orbit' around with will just be the cross product of the direction towards the target, and that of 'up' (Vector3.up, for example). That way, regardless of where the player stands, moving 'side to side' will always be in a direction that moves around the target.
  6. FireStriker0


    May 10, 2023
    Well, that's kind of the thing; using forces to accomplish my intended tasks seems to be too resource-intensive, and I want to keep things like movement as simple and as clear-cut as possible in that vein. With rigidbodies, I'm able to get fine control and fluidity.

    Hmm, that sounds pretty close to what spiney199 suggested, and the results weren't fruitful. I'll make another post in that regard in a sec.
  7. FireStriker0


    May 10, 2023
    I think I understand what you're saying, but I tried to implement your suggestion, and it didn't get the results I seek.

    For reference: I already have Dot and Cross Product-calculating code to determine relative orientation between 2 objects, e.g. if a character is in front of, to the left of, behind, etc. another character.

    When I implemented your solution, it had no change on the horizontal movement - the player was still moving Forward, Backward, to the Left and to the Right without any reference to the enemy, just like before.

    Code was as follows:

    Code (CSharp):
    1.     using System;
    2.     using UnityEngine;
    4.     public class Movement : MonoBehaviour
    5.     {
    6.         internal Rigidbody rb;
    8.         private float x, y, z;
    10.         //Float Scalar for the Vector Movement Speed
    11.         public float vel;
    12.         private float angleVel;
    14.         private float hAxis, vAxis;
    16.         internal Vector3 moveVec;
    18.         //Boolean to control whether or not Movement is possible, based on other states.
    19.         public bool bCanWalk;
    21.         //Enemy to which the player is locked-on.
    22.         public GameObject target;
    24.         void Start()
    25.         {
    26.             rb = GetComponent<Rigidbody>();
    27.             rb.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationZ;
    28.         }
    29.         void Update()
    30.         {
    31.             //Move once per frame
    32.             Move();
    33.         }
    35.         void Move()
    36.         {
    37.             //If the Character is even able to walk, then do the following:
    38.             if (bCanWalk)
    39.             {
    40.                 //If the Game Object to which this Script is attached is tagged as "Player", then do the following:
    41.                 if (gameObject.tag == "Player")
    42.                 {
    43.                     //Refresh the Player's Movement
    44.                     RefreshPlayerMove();
    45.                 }
    46.             }
    47.         }
    49.         void RefreshPlayerMove()
    50.     {
    51.         y = rb.velocity.y;
    52.         hAxis = Input.GetAxisRaw("Horizontal");
    53.         vAxis = Input.GetAxisRaw("Vertical");
    55.         Vector3 targetterToTarget = transform.position - target.transform.position;
    56.         float crossProd = MathHelper.LeftOrRightDotH(target.transform.forward.normalized, targetterToTarget.normalized, target.transform.up.normalized);
    58.         switch (hAxis)
    59.         {
    60.             case -1:
    61.             case 1:
    62.                 angleVel += vel;
    63.                 x = crossProd + hAxis * vel;
    64.                 z = crossProd + vAxis * vel;
    66.                 //Near-identical result as above, slightly slower.
    67.                 /*x = hAxis * vel;
    68.                 z = vAxis * vel;*/
    69.                 break;
    70.             default:
    71.                 angleVel = 0;
    72.                 x = 0;
    73.                 z = vAxis * vel;
    74.                 break;
    75.         }
    77.         SetMoveVec();
    79.         rb.velocity = moveVec;
    80.     }
    82.         private void SetMoveVec()
    83.        {
    84.             moveVec.x = x;
    85.             moveVec.y = y;
    86.             moveVec.z = z;
    87.         }
    88.     }
    Code (CSharp):
    1. using UnityEngine;
    3. public class MathHelper : MonoBehaviour
    4. {
    5.     public static float FrontOrBackDotH(Vector3 targetForwardNormalized, Vector3 targetterToTargetNormalized)
    6.     {
    7.         float frontOrBackVar = (targetForwardNormalized.x * targetterToTargetNormalized.x) +
    8.                                (targetForwardNormalized.y * targetterToTargetNormalized.y) +
    9.                                (targetForwardNormalized.z * targetterToTargetNormalized.z);
    12.         return frontOrBackVar;
    13.     }
    15.     public static float LeftOrRightDotH(Vector3 targetForwardNormalized, Vector3 targetterToTargetNormalized, Vector3 targetUpNormalized)
    16.     {
    17.         Vector3 crossUpOrDown;
    19.         crossUpOrDown.x = (targetForwardNormalized.y * targetterToTargetNormalized.z) -
    20.                           (targetForwardNormalized.z * targetterToTargetNormalized.y);
    22.         crossUpOrDown.y = (targetForwardNormalized.z * targetterToTargetNormalized.x) -
    23.                           (targetForwardNormalized.x * targetterToTargetNormalized.z);
    25.         crossUpOrDown.z = (targetForwardNormalized.x * targetterToTargetNormalized.y) -
    26.                           (targetForwardNormalized.y * targetterToTargetNormalized.x);
    28.         float upOrDownVar = FrontOrBackDotH(crossUpOrDown, targetUpNormalized);
    31.         return upOrDownVar;
    32.     }
    33. }
    Using Unity's Vector3.Cross() function also didn't change anything. Any other ideas/suggestions?
    Last edited: Jun 22, 2023
  8. spiney199


    Feb 11, 2021
    Honestly, you should be using tools like Debug.DrawRay and Gizmos to visualise the directions to help debug this yourself.

    Also what is the point of those helper methods when we already have Vector3.Dot and Vector3.Cross that do the job perfectly? You're reinventing the wheel with the potential of doing it wrong.

    You are also getting things mixed up. Dot product != cross product. They are entirely different things. This:
    float crossProd = MathHelper.LeftOrRightDotH
    is completely wrong as you are (potentially) getting a dot product, and yet you've named the local member a cross product???

    Refer to the docs and use these methods, not your helper methods:

    Once again the cross product (not the dot product) between the up and the direction to the target (so long as you continually update said direction) will always be perpendicular to both. It also doesn't matter what what the cross product is facing, as you can just multiply it by your input axis to flip it if needed.

    Also your math helper class doesn't need to be a monobehaviour, it can just be a static class. But as noted, it's pointless in this situation.
  9. Kurt-Dekker


    Mar 16, 2013
    From what I've learned from over four decades of gamedev, Rigidbodies and Transforms and Collisions are just tools.

    Alone they "achieve" nothing. It's up to you to glue them together in meaningful ways.

    What you describe sounds like Dark Souls combat "lock on," at least that's what I'm familiar with.

    This is a mode-based controller, where inputs are filtered differently when you are locked on versus free. It may be helpful to construct it as two separate controllers to keep things simple in each controller. Then just make a simple button flick to swap between the two modes and lock onto an enemy. At least that's how I would start it out.

    There's a bazillion tutorials for dark souls combat lock on, go check them out.

    Screen Shot 2023-06-21 at 8.58.00 PM.png

    There's probably ones for the Zelda game you quote as well.

    When doing a tutorial, keep these two simple steps in mind:

    Tutorials and example code are great, but keep this in mind to maximize your success and minimize your frustration:

    How to do tutorials properly, two (2) simple steps to success:

    Step 1. Follow the tutorial and do every single step of the tutorial 100% precisely the way it is shown. Even the slightest deviation (even a single character!) generally ends in disaster. That's how software engineering works. Every step must be taken, every single letter must be spelled, capitalized, punctuated and spaced (or not spaced) properly, literally NOTHING can be omitted or skipped.

    Fortunately this is the easiest part to get right: Be a robot. Don't make any mistakes.

    If you get any errors, learn how to read the error code and fix your error. Google is your friend here. Do NOT continue until you fix your error. Your error will probably be somewhere near the parenthesis numbers (line and character position) in the file. It is almost CERTAINLY your typo causing the error, so look again and fix it.

    Step 2. Go back and work through every part of the tutorial again, and this time explain it to your doggie. See how I am doing that in my avatar picture? If you have no dog, explain it to your house plant. If you are unable to explain any part of it, STOP. DO NOT PROCEED. Now go learn how that part works. Read the documentation on the functions involved. Go back to the tutorial and try to figure out WHY they did that. This is the part that takes a LOT of time when you are new. It might take days or weeks to work through a single 5-minute tutorial. Stick with it. You will learn.

    Step 2 is the part everybody seems to miss. Without Step 2 you are simply a code-typing monkey and outside of the specific tutorial you did, you will be completely lost. If you want to learn, you MUST do Step 2.

    Of course, all this presupposes no errors in the tutorial. For certain tutorial makers (like Unity, Brackeys, Imphenzia, Sebastian Lague) this is usually the case. For some other less-well-known content creators, this is less true. Read the comments on the video: did anyone have issues like you did? If there's an error, you will NEVER be the first guy to find it.

    Beyond that, Step 3, 4, 5 and 6 become easy because you already understand!
    Yoreki likes this.
  10. FireStriker0


    May 10, 2023
    Hmm, the rest of my post didn't make it through. I wanted to say that either I need to try something else, or I'm not implementing your proposed solution correctly.

    For the MathHelper class, the MonoBehaviour reference was from an earlier iteration of the code in which I was comparing Unity's dot and cross Product functions with my own. Besides the fact that I implemented them to learn and understand the math behind them, the Unity Profiler also indicated that mine was 1% faster on the CPU. Also, they worked for my aforementioned use case vis a vis relative orientation.

    Anyway, using Unity's cross product function brought me a bit closer to what I seek, although the player now moves around the target far too fast and wildly (Plus, with greater distance and only in 1 direction, even though that shouldn't be the case, given the code).

    It's been a while since I last looked at my project, but since then, I did find one tutorial that seems promising. Hopefully, it and your suggestions will both bear fruit soon.
  11. spiney199


    Feb 11, 2021
    You honestly don't need to know the maths behind them this early. I sure as hell don't know most of the maths behind this stuff, nor about quaternions, but I can definitely move and rotate stuff without issue using Unity's provided methods and understanding the overarching principles.

    This is completely pointless premature optimisation and is a massive waste of your time. Do not bother with this.

    What you think the code does has no bearing on what the code actually does. This is where you debug to figure out what's not working as expected, and use this information to work towards a solution.

    Remember, using Debug.DrawRay, Gizmos, or similar, to visualise the direction of vectors is invaluable to understanding what is and isn't working as expected.
    Yoreki likes this.
  12. Yoreki


    Apr 10, 2019
    You use rigidbodies if you want physically accurate results. For that you must work with forces aswell. You are using a rigidbody to then simply set rb.velocity directly, overwriting all physics calculations that may have been done in the background. When i wrote that rigidbodies give you fluidity, that refered to physical accuracy. You slide on ice. You accelerate while falling. You bounce off bouncy physics materials. Stuff like that. However, for these benefits you have to move using forces, namely this means you will have to stop using forces aswell. This means there is a huge overhead in trying to get snappy and accurate controls.
    My point stands. Snappy controls, which 90% of games want, are best achieved using a character controller. You can simply map your inputs to your own velocity vector and use that to move. This gives you perfect control, but you will have to add a bit of fluidity manually, like not stopping when you slide over ice.

    Everything else is something else and can be done by both approaches. Both can do anything. The effort to get there is simply larger when you chose the starting point further from the desired goal.

    Spiney already touched on this, but not only is it premature optimization, it's irrelevant. Dont get me wrong, if there is literally free speed gains, there is no reason not to take them. However, modern CPUs are way stronger than what you need for most games. Optimizations, if necessary, usually only matter if you can move certain code into a different order of magnitude of performance. So for example, if you can make something that runs in quadratic time O(n²) run in constant time O(1). Performance gains of 1% or even 10% do not matter in practice, unless they are free. Readability, maintainability and scalability are far, far more important. I cannot stress this enough. If your game lacks 20% performance, you simply wait a year and the average hardware will have made up for that. There will still be people who cant play it fluidly, but the same is true if you optimize it by 20%.
    Imagine collision detection. Naively this would involve a quadratic check between all things that could possibly collide. So for 10 objects that's 100 checks. For 2500 objects that's over 6 million checks. Optimizing this by 10% does nothing, nor does it change the way it scales. In reality we use data structures like Octrees. This involves building a whole new data structure, and still is way, way faster. Orders of magnitudes faster, actually, as the tree can now be navigated in O(Log n), which scales insanely good over an increase in N. If you got a tangible performance problem, that's the kind of optimization you care about.
    It's never, ever, about squeezing out 5% more performance.

    Optimizations are a highly advanced topic. Dont deal with it, if you dont have to. While im not saying that's a good practice, we can get away with pretty wasteful programming nowadays.
    spiney199 likes this.
  13. FireStriker0


    May 10, 2023
    Does that mean making characters move just by modifying RigidBody.velocity is very resource-intensive, especially when compared to a Character Controller? I want to make sure I understand correctly.
  14. spiney199


    Feb 11, 2021
    That is not what they said at all.

    They're saying when you start setting
    yourself you enter the world of having to calculate everything yourself and lose out on most of what the physics system offers: doing the maths for you.

    You're way too focused on performance and optimisation and wasting your time on it. You really shouldn't be worrying about this until you have a solid understanding of Unity.
    Yoreki likes this.
  15. FireStriker0


    May 10, 2023
    Update: I tried your suggestion again, from a clean(er) slate, and it got me closer to the results I seek. Code is as follows:

    Code (CSharp):
    1.         using System;
    2.         using UnityEngine;
    4.         public class Movement : MonoBehaviour
    5.         {
    6.             internal Rigidbody rb;
    8.             private float x, y, z;
    10.             //Float Scalar for the Vector Movement Speed
    11.             public float vel;
    12.             private float angleVel;
    14.             private float hAxis, vAxis;
    16.             internal Vector3 moveVec;
    18.             //Boolean to control whether or not Movement is possible, based on other states.
    19.             public bool bCanWalk;
    21.             //Enemy to which the player is locked-on.
    23.     public GameObject target;
    25.     internal Vector3 targetterToTarget;
    27.     private Vector3 crossProd;
    29.             void Start()
    30.             {
    31.                 rb = GetComponent<Rigidbody>();
    32.                 rb.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationZ;
    33.             }
    34.             void Update()
    35.             {
    36.                 //Move once per frame
    37.                 Move();
    38.             }
    40.             void Move()
    41.             {
    42.                 y = rb.velocity.y;
    43.         hAxis = Input.GetAxisRaw("Horizontal");
    44.         vAxis = Input.GetAxisRaw("Vertical");
    46.         targetterToTarget = transform.position - target.transform.position;
    47.         crossProd = MathHelper.CrossProd(target.transform.up.normalized, targetterToTarget.normalized);
    49.         switch (hAxis)
    50.         {
    51.             case -1:
    52.             case 1:
    53.                 x = crossProd.x * -hAxis * vel;
    54.                 z = crossProd.z * -hAxis * vel;
    55.                 break;
    56.             default:
    57.                 angleVel = 0;
    58.                 x = 0;
    59.                 z = vAxis * vel;
    60.                 break;
    61.         }
    63.         SetMoveVec();
    65.         //Temporary fix to Problem #2
    66.         if (transform.localEulerAngles.y != 0 || rb.angularVelocity !=
    67.         {
    68.             rb.angularVelocity =;
    69.             rb.rotation = Quaternion.Euler(0, 0, 0);
    70.         }
    73.         rb.velocity = moveVec;
    74.             }
    76.             private void SetMoveVec()
    77.            {
    78.                 moveVec.x = x;
    79.                 moveVec.y = y;
    80.                 moveVec.z = z;
    81.             }
    82.         }
    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    4. public static class MathHelper
    5. {
    6.     public static Vector3 CrossProd(Vector3 targetForwardNormalized, Vector3 targetterToTargetNormalized)
    7.     {
    8.         Vector3 crossProdVar;
    10.         crossProdVar.x =  (targetForwardNormalized.y * targetterToTargetNormalized.z) -
    11.                           (targetForwardNormalized.z * targetterToTargetNormalized.y);
    13.         crossProdVar.y =  (targetForwardNormalized.z * targetterToTargetNormalized.x) -
    14.                           (targetForwardNormalized.x * targetterToTargetNormalized.z);
    16.         crossProdVar.z =  (targetForwardNormalized.x * targetterToTargetNormalized.y) -
    17.                           (targetForwardNormalized.y * targetterToTargetNormalized.x);
    20.         return crossProdVar;
    21.     }
    22. }
    However, there are 3 problems with the current iteration of the code:

    1. Rotation isn't perfectly circular, but spiral-bound (Meaning the player moves in a spiral around his target, and moves further away from the target every revolution)

    2. A Yaw of 0.001 degrees gets applied each revolution, despite rotation being unaffected.

    3. Y height briefly increases at times, meaning sometimes, the Y position of the Player briefly increases by 1.9999999 before coming back to the previous position.

    Any thoughts on these issues? #3 isn't too big of a big deal, and I somewhat already solved #2, but maybe you know of a better way? Problem #1 is the highest priority, however; I read that such issues arise from increasingly greater precision being lost, and that rounding must occur to stymie this phenomenon, but doing so using Mathf.Round() just made the player revolve around the target in a rounded octagonal manner.