0
\$\begingroup\$

In my game, I have one object that rotates around another using transform.RotateAround, and I need to change the radius of its orbit.

The behavior now is that the orbiting object moves to the larger radius, but it also changes its height.

Both when running this code the first time or when changing the radius value at runtime, I want it to move to the new radius like it does now, but also to keep the current height.

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

public class RotateAroundPoint : MonoBehaviour
{
    public Transform objectToRotateAround;
    public Vector3 axis;
    public float rotationSpeed;
    public float moveToRadiusSpeed;
    [SerializeField] private float radius;

    private float lastRadius;

    private void Start()
    {
        ApplyRadius();
    }

    private void Update()
    {
        if (!Mathf.Approximately(radius, lastRadius))
        {
            ApplyRadius();
        }

        transform.RotateAround(objectToRotateAround.position, axis, rotationSpeed * Time.deltaTime);
    }

    private void ApplyRadius()
    {
        Vector3 direction = (transform.position - objectToRotateAround.position).normalized;
        Vector3 targetPosition = objectToRotateAround.position + direction * radius;

        float step = moveToRadiusSpeed * Time.deltaTime;
        transform.position = Vector3.MoveTowards(transform.position, targetPosition, step);

        if (Mathf.Abs(radius - lastRadius) < 0.001f)
        {
            transform.position = targetPosition;
            lastRadius = radius;
        }
    }
}

I tried to change the code in the Update to this:

Vector3 direction = (transform.position - objectToRotateAround.position).normalized;

float currentDistance = Vector3.Distance(transform.position, objectToRotateAround.position);

float currentHeight = Mathf.Sqrt(Mathf.Pow(currentDistance, 2f) - Mathf.Pow(radius, 2f));

Vector3 newPosition = objectToRotateAround.position + direction * radius;

float step = moveToRadiusSpeed * Time.deltaTime;
transform.position = Vector3.MoveTowards(transform.position, newPosition + Vector3.up * currentHeight, step);


if (Mathf.Abs(radius - lastRadius) < 0.001f)
{
    transform.position = newPosition + Vector3.up * currentHeight;
    lastRadius = radius;
}

But this makes the orbiting object just to move up quickly and without stopping. It does rotate, but not around the target (objectToRotateAround).

\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

I would solve this by turning the one GameObject into two with a parent-child relationship.

  1. Place the parent on the center of the orbit
  2. Use the transform.rotation of the parent to control the direction where the child appears
  3. Use the transform.localPosition of the child to control the radius/height.
  4. Any renderers, colliders etc. would be on the child, of course.
\$\endgroup\$
2
  • \$\begingroup\$ this is working but if I'm changing the child height then also the parent will start moving with the child. i also wanted to add some more features. \$\endgroup\$
    – Daniel Lip
    Commented Aug 8, 2023 at 18:22
  • \$\begingroup\$ @OledNeduda "if I'm changing the child height then also the parent will start moving with the child" - that should not happen. If you have both the parent and the child selected, then it might appear that way in the Unity editor, because the gizmo shows the center of the selected objects. But if you look at the parent transform component, you will see that it didn't change its position. "i also wanted to add some more features." - a more detailed description of your requirements in your question would lead to more applicable answers. \$\endgroup\$
    – Philipp
    Commented Aug 8, 2023 at 18:24
0
\$\begingroup\$

You need to change your ApplyRadius method to change the offset only in this horizontal plane.

private void ApplyRadius()
{
    // Get offset between this object and its target.
    Vector3 offset = transform.position - objectToRotateAround.position;

    // Extract just the height, leaving the horizontal offset in the XZ plane.
    float height = offset.y;
    offset.y = 0f;

    // Use Pythagorean Theorem to get the adjacent side that,
    // combined with height as our opposite side,
    // makes our hypotenuse match the desired total radius.
    float horizontalRadius = Mathf.sqrt(radius * radius - height * height);

    // Construct our new offset and apply it.
    offset = offset.normalized * horizontalRadius;
    offset.y = height;

    Vector3 targetPosition = objectToRotateAround.position + offset;

    // ... the rest of your code

If your "height" can be non-vertical, you can do the same thing with an arbitrary axis like so (here I assume other code ensures axis is unit length, so we don't redundantly re-normalize it every frame)

private void ApplyRadius()
{
    // Get offset between this object and its target.
    Vector3 offset = transform.position - objectToRotateAround.position;

    // Extract component parallel to axis, leaving offset in rotation plane.
    float height = Vector3.Dot(offset, axis);
    offset -= axis * height;

    // Use Pythagorean Theorem to get the adjacent side that,
    // combined with height as our opposite side,
    // makes our hypotenuse match the desired total radius.
    float horizontalRadius = Mathf.sqrt(radius * radius - height * height);

    // Construct our new offset and apply it.
    offset = offset.normalized * horizontalRadius;
    offset += axis * height;

    Vector3 targetPosition = objectToRotateAround.position + offset;

    // ... the rest of your code

However, note that you can accumulate errors using RotateAround. Myself, I would implement this in a way where I calculate the position with a little trig, so I know I'll maintain my target orbit exactly.

[SerializeField] Transform objectToRotateAround;
[SerializeField] Vector3 axis;
[SerializeField] float targetRadius;
[SerializeField] float degreesPerSecond;
[SerializeField] float moveToRadiusSpeed;

float _angleRadians;
Quaternion _rotationSpace;

// Gets current displacement from the pivot object
// in a coordinate space where the rotation axis is +z.
Vector3 GetRotationSpaceOffset() {
    return Quaternion.Inverse(_rotationSpace) 
           * (transform.position - objectToRotateAround.position);
}

// Update axis and angle on start-up / if axis is changed in Inspector.
void OnValidate() {
    _rotationSpace = Quaternion.LookRotation(axis);
    if (objectToRotateAround != null) {
        var offset = GetRotationSpaceOffset();
        angleRadians = Mathf.Atan2(offset.y, offset.x);
    }
}

void Start() {
    // Initialize rotation coordinate space and angle.
    OnValidate();
}

void Update() {
    // Get our offset from the pivot in a coordinate space where our
    // rotation axis is along the z+ direction.
    var offset = GetRotationSpaceOffset();

    // As before, extract the component parallel to the axis.
    float height = offset.z;
    offset.z = 0f;    
    
    // Blend our current radius toward the target.
    float radius = Mathf.MoveTowards(offset.magnitude, targetRadius, 
                                     moveToRadiusSpeed * Time.deltaTime);

    // Advance our angle, and keep it wrapped close to zero for best precision.
    _angleRadians += degreesPerSecond * Mathf.Deg2Rad * Time.deltaTime;
    _angleRadians = _angleRadians % (2 * Mathf.PI);

    // Construct a unit vector in our rotation plane at that angle.
    offset = new Vector3(Mathf.Cos(_angleRadians), Mathf.Sin(_angleRadians), 0);
 
    // Extend it to our desired orbit radius and offset parallel to the axis.
    offset *= radius;
    offset.z = height;

    // Map offset back to world space and apply it to get our new position.
    transform.position = objectToRotateAround.position + _rotationSpace * offset;
}
\$\endgroup\$
2
  • \$\begingroup\$ doesn't work. the last code at the bottom one makes the object to flicker and not rotating around. \$\endgroup\$
    – Daniel Lip
    Commented Aug 8, 2023 at 15:51
  • \$\begingroup\$ Sounds like you're still specifying your rotation speed in degrees instead of radians. You can add a Math.Deg2Rad if you'd prefer to work in degrees. I've shown this change in an edit. \$\endgroup\$
    – DMGregory
    Commented Aug 8, 2023 at 16:39

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .