Rotation Issue

Hello all. I’m trying to develop a Ranger Aiming System in my game, and I’m currently on the final step of trying to get the player to rotate, before he enters the Ranger Aiming State (my architecture follows the State Machine model, for context), to wherever the Cinemachine FreeLook camera is pointing at. The goal is to get the player to rotate in direction of wherever the Cinemachine FreeLook Camera is pointing at in my game scene, when we are about to access the Ranger Aiming State, but right now whilst it’s not random, it’s not working as expected at all, and I’m guessing it’s a mathematical issue…

I asked ChatGPT for a little bit of help, and whilst we got somewhat better results, I feel like sometimes it’s still random, so we developed 2 checks, one where the camera finds something by hitting a Raycast (where the player is expected to rotate to whatever the camera hit), and one where the camera can’t find anything 100 meters from where it started (in this case, the player points at wherever the camera’s forward is), but still… it’s a little random (again, it’s a Cinemachine FreeLook Camera)

Can someone kindly guide me through this problem? Here’s what I developed so far:

        // TEST - Player Ranger Aiming State:
        private void InputReader_HandleRangerAimingEvent()
        {
            if (stateMachine.Fighter.GetCurrentWeaponConfig().GetIsRangerAimWeapon())
            {
                RotatePlayerToCameraDirection();
                stateMachine.SwitchState(new PlayerRangerAimingState(stateMachine));
            }
        }

        // TEST - Rotating the Player before entering the Ranger Aiming Phase:
        private void RotatePlayerToCameraDirection()
        {
            if (stateMachine.MainFreeLookCamera != null)
            {
                Vector3 rayOrigin = stateMachine.MainFreeLookCamera.transform.position;
                Vector3 rayDirection = stateMachine.MainFreeLookCamera.transform.forward;

                Ray ray = new Ray(rayOrigin, rayDirection);
                if (Physics.Raycast(ray, out RaycastHit hit)) // if the camera fired a raycast that hit something, point the player to it
                {
                    Vector3 targetDirection = (hit.point - stateMachine.transform.position).normalized; // (Player) --> (hit.point), literally
                    targetDirection.y = 0;

                    targetDirection = -targetDirection;
                 
                    Quaternion targetRotation = Quaternion.LookRotation(targetDirection, Vector3.up);
                    targetRotation = NormalizeRotation(targetRotation);
                    stateMachine.transform.rotation = targetRotation;
                    Debug.Log($"Rotating player to match the Raycast Direction: {targetDirection}");
                }
                else // if the camera fired a raycast that hit nothing, set the player's rotation to match that of the camera
                {
                    Vector3 fallbackDirection = stateMachine.MainFreeLookCamera.transform.forward; // direction of the camera, forward
                    fallbackDirection.y = 0;

                    fallbackDirection = -fallbackDirection;
                 
                    Quaternion fallbackRotation = Quaternion.LookRotation(fallbackDirection, Vector3.up);
                    fallbackRotation = NormalizeRotation(fallbackRotation);
                    stateMachine.transform.rotation = fallbackRotation;

                    Debug.Log($"Rotating player to match the fallback camera direction: {fallbackDirection}");
                }
            }
            else Debug.Log($"MainFreeLookCamera is not assigned in PlayerStateMachine");
        }

        private Quaternion NormalizeRotation(Quaternion rotation)
        {
            Vector3 euler = rotation.eulerAngles;
            euler.x = NormalizeAngle(euler.x);
            euler.y = NormalizeAngle(euler.y);
            euler.z = NormalizeAngle(euler.z);
            return Quaternion.Euler(euler);
        }

        private float NormalizeAngle(float angle)
        {
            if (angle > 180) angle -= 360;
            if (angle < -180) angle += 360;
            return angle;
        }

I also introduced a Normalizer to deal with Unity’s Internal workings with Euler Angles, but that wasn’t enough. Please send help

(and yes, the logic can be wrong. I’m not sure at this point in time, to be honest)

@Gregoryl can you please have a look at this and see if you can help me out? That would be much appreciated

Manipulating Euler angles is almost always going to give you issues. Here’s why:

All about Euler angles and rotations, by StarManta:

https://starmanta.gitbooks.io/unitytipsredux/content/second-question.html

Instead of rotating the player, just make it look where you want with Transform.LookAt( Vector3)

If you need it to be smooth, then smoothly move the Vector3 to where you want.

I went through the article, and then tried simplifying it to its absolute best form, but it still failed…:

        // TEST - Player Ranger Aiming State:
        private void InputReader_HandleRangerAimingEvent()
        {
            if (stateMachine.Fighter.GetCurrentWeaponConfig().GetIsRangerAimWeapon())
            {
                RotatePlayerToCameraDirection();
                stateMachine.SwitchState(new PlayerRangerAimingState(stateMachine));
            }
        }

        // TEST - Rotating the Player before entering the Ranger Aiming Phase:
        private void RotatePlayerToCameraDirection()
        {
            if (stateMachine.MainFreeLookCamera != null)
            {
                Vector3 rayOrigin = stateMachine.MainFreeLookCamera.transform.position;
                Vector3 rayDirection = stateMachine.MainFreeLookCamera.transform.forward;

                Ray ray = new Ray(rayOrigin, rayDirection);
                if (Physics.Raycast(ray, out RaycastHit hit)) // if the camera fired a raycast that hit something, point the player to it
                {
                    Vector3 targetDirection = (hit.point - stateMachine.transform.position).normalized; // (Player) --> (hit.point), literally
                    targetDirection.y = 0; // keep the player straight

                    stateMachine.transform.LookAt(targetDirection);
                    Debug.Log($"Rotating player to match the Raycast Direction: {targetDirection}");
                }
                else // if the camera fired a raycast that hit nothing, set the player's rotation to match that of the camera
                {
                    Vector3 fallbackDirection = stateMachine.MainFreeLookCamera.transform.forward.normalized; // direction of the camera, forward
                    fallbackDirection.y = 0;

                    stateMachine.transform.LookAt(fallbackDirection);
                    Debug.Log($"Rotating player to match the fallback camera direction: {fallbackDirection}");
                }
            }
            else Debug.Log($"MainFreeLookCamera is not assigned in PlayerStateMachine");
        }

        private Quaternion NormalizeRotation(Quaternion rotation)
        {
            Vector3 euler = rotation.eulerAngles;
            euler.x = NormalizeAngle(euler.x);
            euler.y = NormalizeAngle(euler.y);
            euler.z = NormalizeAngle(euler.z);
            return Quaternion.Euler(euler);
        }

        private float NormalizeAngle(float angle)
        {
            if (angle > 180) angle -= 360;
            if (angle < -180) angle += 360;
            return angle;
        }

Any better suggestions?

Um… the obvious one? Debug it and fix it? :slight_smile:

By debugging you can find out exactly what your program is doing so you can fix it.

Use the above techniques to get the information you need in order to reason about what the problem is.

You can also use Debug.Log(...); statements to find out if any of your code is even running. Don’t assume it is.

Once you understand what the problem is, you may begin to reason about a solution to the problem.

All debugs in my current code are responsive, so I know for a fact that it’s not a coding issue as it is a mathematical issue :slight_smile: - regardless, I will see how I can debug it further, although believe me I have no clue how at this point in time

“Responsiveness” is not the point of debugging.

Debugging is to give you insight into what your code is actually doing when it is NOT doing what you expect.

You will know you have finished debugging when you have reached a point of:

  • this one piece of code should do A and ALL the values inside it make me think it should do A, but instead this code is doing B… why is it doing B?

I have debugged this thing until I got burned out, and I still couldn’t find a solution… I’ll continue tomorrow, but any extra help is always appreciated, because when it comes to Rotations I genuinely get clueless with Quaternions

Understood. Me too. This is why I never use them and why I wrote above in my very first response:

It really works. It really is 1000x simpler than rotations. Seriously.

Here’s an example that mentions “camera” but it applies to ALL transforms:

The simplest way to do it is to think in terms of two Vector3 points in space:

  1. where the camera is LOCATED
  2. what the camera is LOOKING at
private Vector3 WhereMyCameraIsLocated;
private Vector3 WhatMyCameraIsLookingAt;

void LateUpdate()
{
  cam.transform.position = WhereMyCameraIsLocated;
  cam.transform.LookAt( WhatMyCameraIsLookingAt);
}

Then you just need to update the above two points based on your GameObjects, no need to fiddle with rotations. As long as you move those positions smoothly, the camera will be nice and smooth as well, both positionally and rotationally.

Believe me, I want to integrate a ‘transform.LookAt()’ to eliminate Quaternions, but I’m baffled on how to even integrate this into my code… I’m trying my best from my side, but still failing…

I’ll try again from scratch, and if it all goes down to the worst of all, I’ll re-paste the original code of this post

First you need a transform that you will “point at” things by setting the +Z direction to it.

Ideally that is just the thing (in your case the player’s head)

If it isn’t, then you may need to do some creative parenting to come up with a transform that makes things look +Z direction.

Then just use the code already posted above to point that wherever you want.

You probably want to make a blank scene and understand this with a simple arrow that you aim at stuff, otherwise who knows what all is going on in your current setup. It could be that your player’s animations are overriding things.

I did go through a little in the inspector, and realized that the freelook cam was always the state driven cam in my project, so I did a few changes in my code to match with it, and tried to clean and simplify it:

        // TEST - Rotating the Player before entering the Ranger Aiming Phase:
        private void RotatePlayerToCameraDirection()
        {
            // Replace 'MainFreeLookCamera' with 'StateDrivenCamera'
            if (stateMachine.StateDrivenCamera != null)
            {
                Transform playerTransform = stateMachine.transform; // Assuming the player's transform is the parent of the head

                Vector3 cameraForward = stateMachine.StateDrivenCamera.transform.forward;
                cameraForward.y = 0f; // Keep the rotation on the XZ plane

                playerTransform.forward = cameraForward;

                Debug.Log($"Rotating player to match the StateDrivenCamera forward direction: {cameraForward}");
            }
            else
            {
                Debug.Log($"StateDrivenCamera is not assigned in PlayerStateMachine");
            }
        }

But now comes the really important question, what’s the best way to get the rotation to use in this block of code? Because from experience, my State Driven Camera won’t do the trick with this one

In other words, how do I create an empty gameObject that rotates with the freelook camera, when the freelook camera itself shows no value changes in rotation?

My best idea was to get the translation of the freelook cam, and use that as a starting point. But because the FreeLook cam shows no rotation, I can’t tell which direction it’s looking at either…

I solved it:

        // TEST - Rotating the Player before entering the Ranger Aiming Phase:
        private void RotatePlayerAwayFromCameraDirection()
        {
            if (stateMachine.MainFreeLookCamera != null)
            {
                Vector3 cameraDirection = (stateMachine.MainFreeLookCamera.transform.position - stateMachine.transform.position).normalized;
                Vector3 awayDirection = -cameraDirection;
                awayDirection.y = 0; // Keep the player standing straight up

                Quaternion targetRotation = Quaternion.LookRotation(awayDirection, Vector3.up);
                targetRotation = NormalizeRotation(targetRotation);
                stateMachine.transform.rotation = targetRotation;
                Debug.Log($"Rotating player to match the camera direction: {cameraDirection}");
            }
            else Debug.Log($"MainFreeLookCamera is not assigned in PlayerStateMachine");
        }

        private Quaternion NormalizeRotation(Quaternion rotation)
        {
            Vector3 euler = rotation.eulerAngles;
            euler.x = NormalizeAngle(euler.x);
            euler.y = NormalizeAngle(euler.y);
            euler.z = NormalizeAngle(euler.z);
            return Quaternion.Euler(euler);
        }

        private float NormalizeAngle(float angle)
        {
            if (angle > 180) angle -= 360;
            if (angle < -180) angle += 360;
            return angle;
        }

You can simplify it further:

  • Forget that it’s a FreeLook or a StateDrivenCamera - just use Camera.main. That way it will work no matter what CM camera is driving things.
  • Get rid of NormalizeRotation(), it’s useless and costly.
  • Don’t bother normalizing your directions, LookRotation doesn’t require it.

See how simple it can be:

void RotateToCameraDirection()
{
    var targetDirection = Camera.main.transform.forward;
    targetDirection.y = 0; // put on XZ plane
    if (targetDirection.sqrMagnitude > 0.001f)
        transform.rotation = Quaternion.LookRotation(targetDirection, Vector3.up);
}
3 Likes

That was… much simpler than expected, and it works perfectly. Thank you @Gregoryl :slight_smile:

The next step of this maze is to find a way to get the Aim Reticle to point more accurately on where the arrow is exactly aiming at. I don’t know how I’ll do this, but right now it’s a little off, mainly because it aims straight forward from the Rangers Aiming VCam, which is a little off from where the arrow instantiates at, which is from the Players’ Right hand transform. Any ideas how to do this?

Edit: Solved it

    /// <summary>
    /// Get the position of the Aim Reticle, from the player's initiation point (his Right Hand Transform),
    /// and in the direction of the Cinemachine Virtual Camera, the 'RangerAimingCamera' in 'PlayerStateMachine.cs'
    /// (which is the 'RangerAimingVirtualCamera' under the Player on the hierarchy)
    /// </summary>
    private void UpdateAimReticlePosition()
    {
        Vector3 arrowOrigin = stateMachine.RightHandTransform.position;
        Vector3 arrowDirection = stateMachine.rangerAimingCamera.transform.forward;

        Camera MainCamera = Camera.main;

        if (Physics.Raycast(arrowOrigin, arrowDirection, out RaycastHit hit, maxDistance))
        {
            Vector3 predictedHitPoint = hit.point;
            Vector3 screenPosition = MainCamera.ScreenToWorldPoint(predictedHitPoint);

            stateMachine.AimReticle.transform.position = screenPosition;
        }
    }

Glad you solved it. One thing I should mention here, in case you trip over it later. When you make a calculation that depends on the camera position, for 100% accuracy you must make sure to do this after Cinemachine has positioned the camera, otherwise you will be working with a stale position from the previous frame.

The camera is positioned very late in the frame, in CinemachineBrain.LateUpdate (and CinemachineBrain has a late execution order). For code that absolutely needs to run after this happens, you can add a handler to CinemachineCore.CameraUpdatedEvent, which is invoked every frame immediately after the camera has been positioned.

1 Like

So… something equivalent to this in ‘Enter’ and ‘Exit’…?:

    // Done here, instead of 'Tick()', because Cinemachine is a LATE Update
        CinemachineCore.CameraUpdatedEvent.RemoveListener(UpdateAimReticlePosition); // Update the position of the Aim Reticle, from player's Right Hand (shooting point), relative to the Ranger Aiming Virtual Camera

and update the function to this…?:

private void UpdateAimReticlePosition(CinemachineBrain brain)
    {
        // Only update the reticle if the aiming camera is active
        if (stateMachine.rangerAimingCamera != null && stateMachine.rangerAimingCamera.m_Priority >= 11)
        {
            Vector3 arrowOrigin = stateMachine.RightHandTransform.position;
            Vector3 arrowDirection = stateMachine.rangerAimingCamera.transform.forward;

            Camera MainCamera = Camera.main;

            if (Physics.Raycast(arrowOrigin, arrowDirection, out RaycastHit hit, maxDistance))
            {
                Vector3 predictedHitPoint = hit.point;
                Vector3 screenPosition = MainCamera.WorldToScreenPoint(predictedHitPoint);

                stateMachine.AimReticle.transform.position = screenPosition;
            }
        }
    }

Not much has changed here, just the initial if statement and the delegate-required parameter as an argument at the top, and that’s about it

Results will take a while for me to get used to, because I feel like it’s floating a little abnormally for some reason, but I’ll investigate it again now, before I go to finding ways to get into Ranger Aiming State from the back of a horse

and this one is very challenging, because the camera is a child of the player, and the horse movement is VERY, VERY SHAKY, and I’m a little baffled on how to go around this one

The entire point is, be able to perform ranged attacks from the back of a moving horse

Seems reasonable.

As for the horse, making the camera an actual child of the player sounds… well… shaky.

If you want a steady aiming camera even while the player is shaking around, consider having an invisible proxy object that smoothly tracks the player (perhaps at some fixed offset to the horse’s root, which presumably isn’t shaking). Use that object as a target for your vcam.

I’m already running into constrained rotation issues, and because I didn’t program the horse, I have no clue why that’s the case. I’ll have to contact MalberS (the producer of that horse) and ask him why

Although my code, for now, is literally a copy-paste of what happens when the player is on foot. The only difference is, the Reticle shakes like crazy, because of the bumpy ride

And when I fire the arrow, I’m guessing it never goes forward because it hits the bow because of how the animation sets their positioning up… Somehow, the arrow needs to be super fast to even have a chance of actually being fired from the back of a horse

A new set of challenges