1
\$\begingroup\$

I have an interactive scenery app written in Unity, where users can freely navigate the camera through the scene. What I want to do now, is to let user automatically fly to some "hotspots" with a single key stroke, while still allowing the users to navigate manually.

I tried Animation, but with it, auto-fly and manual navigation are mutually exclusive - I have to disable one in order for the other to work.

Right now, I'm attempting the fall-back solution, where I create some empty game objects with special scripts attached, so that when public script functions are invoked, the game object animates the camera to a pre-defined transformation.

My question being: is there a better solution than my fall-back?

\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

I've only ever seen this solved with the animation being controlled by a script - This can be done in the Update() method or via a tweening plugin like DoTween.

In the Update method you have a few options, including manual movement (manually manipulating transform.position's x, y and z parameters), using transform.position = Vector3.MoveTowards(transform.position, _movementTarget, MaxSpeed * Time.DeltaTime); and transform.position = Vector3.Lerp(_movementStart, _movementTarget, _animationState) where _animationState goes from 0 to 1 to indicate how far through the animation you are. The choice between MoveTowards and Lerp comes down to whether you care more to set the movement speed, or the time it'll take to get to the target.

For rotation, you can also use Transform.LookAt(), Quaternion.Lerp() and Quaterion.LookRotation() however I tend to compute this manually to absolutely ensure the Z rotation is never changed.

In my solution I've gone with the Lerp approach for position, and am manually animating rotation by only lerping the y axis, you could also separately lerp the x rotation axis, that'd be fine, just make sure to never change the z axis.

Movement demonstrated in Unity

using UnityEngine;

namespace Assets
{
    public class MoveMe : MonoBehaviour
    {
        private bool _isMoving = false;
        private Vector3 _movementStart;
        private Vector3 _movementTarget;
        private float _rotationStartEulerY;
        private float _rotationTargetEulerY;
        private const float AnimationSpeedFactor = 2f;
        private float _animationProgress = 0;

        private void Update()
        {
            if (_isMoving)
            {
                _animationProgress += Time.deltaTime * AnimationSpeedFactor; //Animation progress 0..1
                transform.position =
                    Vector3.Lerp(_movementStart, _movementTarget, _animationProgress); //Animate position
                var rot = transform.rotation.eulerAngles;
                transform.rotation = Quaternion.Euler(rot.x,
                    Mathf.Lerp(_rotationStartEulerY, _rotationTargetEulerY, _animationProgress),
                    rot.z); //Animate rotation
                if (_animationProgress >= 1)
                {
                    _isMoving = false; //End animation, player free to move camera manually
                }
            }

            if (Input.GetKeyDown(KeyCode.A)) StartAnimationTowards(new Vector3(0, 1, -2.96f), -51.145f);
            if (Input.GetKeyDown(KeyCode.S)) StartAnimationTowards(new Vector3(-2.88f, 1, -3.52f), 39.054f);
            if (Input.GetKeyDown(KeyCode.D)) StartAnimationTowards(new Vector3(-8.97f, 3.97f, 3.2f), 103.39f);
        }

        private void StartAnimationTowards(Vector3 position, float rotation)
        {
            _isMoving = true;
            _movementStart = transform.position;
            _movementTarget = position;
            _rotationStartEulerY = ClampRotation(transform.rotation.eulerAngles.y);
            _rotationTargetEulerY = ClampRotation(rotation);
            _animationProgress = 0;
        }

        private float ClampRotation(float rotation)
        {
            //Ensures the returned value is between -180 .. 180. This ensures rotation doesn't animate the long way around the circle.
            rotation %= 360;
            return (rotation > 180)
                ? rotation - 360
                : rotation;
        }
    }
}
\$\endgroup\$
0
\$\begingroup\$

I have never tried honestly, but perhaps you could create multiple cinemachines and switching between them via a certain parameter: You have your main virtual camera "MainCam" that follows the user. As the user presses a button or any other trigger event occurs, the parameter "activateCamX" will disable "MainCam" and activate "CamX", which in turn will not follow the player but rather fix the view on a certain location/target.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ This answer would be a good occasion to try your idea so you can test how well it works, so instead of saying "I have never tried" you can say "I tested this and it works great!" 😉 \$\endgroup\$
    – DMGregory
    Commented Sep 24, 2021 at 19:08

You must log in to answer this question.

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