Rotation around two game Objects (keep a fixed distance)

Hello everyone , i’m struggling with my code :

i’m basically trying to get a yellow rectangle (controlled by the mouse ) to rotate around the red circle point and facing it aswell …
As you can see in the gif below , its working (at the beginning) , but when i get the mouse closer to the red circle , the rectangle follows it ( due to the use of clampMagnitude i guess) . And i would like to keep a fixed between the two objects (with a defined value) .
I dont want the yellow rectangle to get "close " to the red circle .

If anyone can help me i would be grateful :slight_smile:

ps : the first code is used to manage the rotation around the object and the second one is used to face the circle at runtime .

Script used to rotate around the circle : [u](attached to the “yellow rectangle” game object)[/u]

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FirstTry : MonoBehaviour
{

    [SerializeField] Transform targetObject; // reference to the object we rotate around (the red circle)
    float maxLength = 2.5f;


       void Update()
       {
        Vector3 mousePosition = Input.mousePosition;
        mousePosition.z = 5;

         currentPosition = Camera.main.ScreenToWorldPoint(mousePosition);
    
         currentPosition = center.position + Vector3.ClampMagnitude(currentPosition  - center.position, maxLength);

         transform.position = currentPosition; //set YellowTab position
       }
  
}

Script used to "look at " the circle : (attached to “red circle” game object)

public class LookAtObject : MonoBehaviour
{
    [SerializeField] Transform target;
    Vector2 lastRotation;
    void Update()
    {
        Vector2 direction = target.position - transform.position;
        transform.rotation = Quaternion.FromToRotation(Vector3.down, direction);
    }
}

potableelementaryduckbillcat

( Sorry there is an error in my first message , the “LookAtObject” script is attached to the yellow rectangle game object not to the red circle game object ) :stuck_out_tongue:

if M is mouse position, R is red dot position, and Y is the yellow object that has a pivot in the middle
then you just need d to define a fixed distance, for example d = 2
then

you can get direction between R and M by

var dir = (M.pos - R.pos).normalized;

this is simply a vector that represents that gap from R to M, but rescaled to a length of 1.
Y object should be placed at d times that much from R

Y.pos = R.pos + d * dir; // aka directional offset

its rotation depends on its own position in space, so if its original rotation (identity) was so that it was looking right

var orient = (Y.pos - R.pos).normalized; // aka facing normal
Y.rot = Quaternion.FromToRotation(Vector3.right, orient);

edit: typos
edit2: if something’s opposite to what you wanted, you can always flip things around, see if that works, i.e. B-A instead of A-B, or you can simply put a minus in front of any vector variable.

ok thanks i’m gonna try this :wink:

public class FirstTry : MonoBehaviour
{

      float distanceFromCenter = 5.0f; // "distanceFromCenter " is "d" in your explanation

      private Vector3 currentPosition;
     [SerializeField] Transform center;

         void Update()
         {
            Vector3 mousePosition = Input.mousePosition;
          

           currentPosition = Camera.main.ScreenToWorldPoint(mousePosition);
        var dir = (currentposition - center.position).normalized;

      transform.position = center.position + distanceFromCenter * dir;

         }
}

The code should looks like this ? correct me if im wrong ?

Its working on runtime , but it basically giving me the same resultat as my first try .

That give me a ClampMagnitude “effect” : when i drag the mouse away from the center at the distance defined by “distanceFromCenter” then the Yellow Rectagnle doesnt follow the mouse ( thats good) , but when i get my mouse closer from the center then the rectangle is following while it’s supposed to remain at a "5.0f " distance

Here you go

using UnityEngine;

public class ForumMouse : MonoBehaviour {

  [SerializeField] Vector2 _redDot; // R
  [SerializeField] GameObject _quad; // Y
  [SerializeField] [Min(0.1f)] float _distance; // d

  void Update() {
    var mp = toVector2(Camera.main.ScreenToWorldPoint(Input.mousePosition)); // M
    var dir = (mp - _redDot).normalized;
    var objPos = _redDot + _distance * dir;
    var objOrient = (objPos - _redDot).normalized;
    _quad.transform.localPosition = toVector3(objPos);
    _quad.transform.localRotation = Quaternion.FromToRotation(Vector3.right, toVector3(objOrient));
  }

  Vector2 toVector2(Vector3 v) => new Vector2(v.x, v.y);
  Vector3 toVector3(Vector2 v) => new Vector3(v.x, v.y, 0f);

}

Plop a simple quad object directly into the scene, and dragndrop it to the corresponding field. Hit play and change values.

Also observe these little Vector2 ↔ Vector3 conversion utils, they make your life much easier, and your intent much clearer. Without them weird algebraic bugs will crop up and you also can’t tell which is which at a glance.

And you can now very easily change the whole setup to work in another plane, for example XZ.

edit:
just rewrote it with better var names etc

1 Like

You can also do this if you need an angle for some reason

void Update() {
  var mp = toVector2(Camera.main.ScreenToWorldPoint(Input.mousePosition));
  var dir = (mp - _redDot).normalized;
  var objPos = _redDot + _distance * dir;
  var objOrient = (objPos - _redDot).normalized;
  var angle = Mathf.Atan2(objOrient.y, objOrient.x) * Mathf.Rad2Deg;
  _quad.transform.localPosition = toVector3(objPos);
  _quad.transform.localRotation = Quaternion.Euler(0f, 0f, angle);
}

edit:
rewrite

whoops made an error, var orient line should be
in fact, I’ll rewrite everything for clarity
edit: done

This is working perfectly

You are a genius

Thanks you very much , i will try to understand your code better , not just copy pasting it :slight_smile:

here’s an image to help you understand what’s going on

first we get that red arrow from R to M
it is of arbitrary length, who knows where the mouse is, right
then we normalize that, that means it’s divided by its length (so it becomes unit)
so we get dir, that little red arrow next to R
then we multiply that with our distance 2 and get the light blue arrow
but to actually get to that point you need to account for the position of R itself
and that’s the dashed line, that’s what you get when you add the two
now that we have Y’s position, we get light blue arrow from R to Y again
by doing (R - Y).normalized
can you see how we can optimize this code and not do things twice?
anyway from that point on, we have a facing normal and we can find the angle

edit:
haven’t shown the angle properly, it’s actually measured 90 degrees further to the right for this particular case. absolute angles are always measured from the positive X line.

var objOrient = -dir;

try it.

this change is good because you want to avoid normalization (and similarly magnitude) as much as possible. it’s not the slowest thing ever, but it’s the slowest thing among other stuff we did with that code, including quaternion shenanigans. do note that these things are also unavoidable, so it’s not a bad code, we just tend to minimize it.

also try not to use transform.position and transform.rotation directly if you can help it. these two have their uses, but if you can, use localPosition and localRotation instead.

just fooling around

using UnityEngine;

public class ForumMouse : MonoBehaviour {

  [SerializeField] Vector2 _pos;
  [SerializeField] GameObject _quad;
  [SerializeField] [Min(0.1f)] float _distance;

  void Update() {
    var mp = toVector2(Camera.main.ScreenToWorldPoint(Input.mousePosition));
    var dir = (mp - _pos).normalized;
    var orient = -dir;
    var angle = Mathf.Atan2(orient.y, orient.x);
    var cs = polar(2f * angle);
    var objPos = _pos + _distance * cs.x * cs.x * cs.x * new Vector2(dir.x, -dir.y);
    _quad.transform.localPosition = toVector3(objPos);
    _quad.transform.localRotation = Quaternion.Euler(0f, 0f, -180f * cs.y);
  }

  Vector2 toVector2(Vector3 v) => new Vector2(v.x, v.y);
  Vector3 toVector3(Vector2 v) => new Vector3(v.x, v.y, 0f);

  Vector2 polar(float angle) => new Vector2(Mathf.Cos(angle), Mathf.Sin(angle));

}

The “toVector2D” allow to convert implicitly 3d coordinate to 2d coordinate , right ?

It does convert from vector3 to vector2 by transplanting coordinates in such a way to make them consistent with the XY plane. I.e. removing the last, z coordinate is all it does.

It’s not implicit if you have to call it explicitly. Implicit conversion is a “silent” one (i.e. seemingly non-provoked by your code). Internally, Unity declares implicit conversion between the two, which is okay when you want Vector2 → Vector3, but by someone’s mistake also does this in the opposite direction, which is against common logic (and official recommendations), because in this case you also implicitly lose z. Just try disabling those two functions, and the code will likely still work, but completely bugged.

When you work in 2D but still use 3D API, the best you can do is to make little converters such as these, so you can maximize your control over explicit conversions. In other words, do not rely on implicit ones, even though they “work”. I wish there was a way to simply disable them, because it’s much better to have a compiler error than a deeply nested error in math.

Btw I could’ve done this

Vector2 toVector2(Vector3 v) => (Vector2)v;
Vector3 toVector3(Vector2 v) => (Vector3)v;

That would be explicit conversion. But I opted against it, because it shows intent with more clarity, and allows you to switch things around. Maybe you want new Vector3(v.x, 0f, v.y) for example.

This would be implicit, it does the same thing as above (because both Vector types declare such implicit operators, it’s not something you can normally do in C#).

Vector2 toVector2(Vector3 v) => v;
Vector3 toVector3(Vector2 v) => v;

Hopefully you can see why this is a bad idea throughout your code. You can suddenly lose some information, without any mean to tell what and where.

Here’s a conversion to XZ plane and back, in this scenario you can’t just cast from one to another.

Vector2 toVector2(Vector3 v) => new Vector2(v.x, v.z);
Vector3 toVector3(Vector2 v) => new Vector3(v.x, 0f, v.y);

Finally, observe that I sanitize my coordinates on input, work entirely in 2D, then convert back to 3D only when it’s done and I need to use API to send them back.

var mp = toVector2(Camera.main.ScreenToWorldPoint(Input.mousePosition)); // convert mouse to 2D
var dir = (mp - _redDot).normalized; // everything is 2D here
var objPos = _redDot + _distance * dir; // everything is 2D here
var objOrient = -dir; // everything is 2D here
_quad.transform.localPosition = toVector3(objPos); // 3D
_quad.transform.localRotation = Quaternion.FromToRotation(Vector3.right, toVector3(objOrient)); // 3D