1
\$\begingroup\$

I'm currently writing an InputManager using Unity's "new" input system. The InputManager receives all player input via SendMessage() and disseminates it to the appropriate objects (player, UI, et cetera). In an attempt to adhere to SRP (single-responsibility principle), I want as much of the message massaging to occur in the InputManager as possible.

For player movement in the InputManager, I have the following:

public void OnMovement(InputValue input)
{
    if (playerControl == null)
        playerControl = player.GetComponent<PlayerControl>();

    playerControl.OnMovement(input, playerMovementSpeed, cmCam.gameObject.transform.localEulerAngles);
}

on the PlayerControl script, I have:

void Update()
{
    var q = Quaternion.Euler(cmCam.transform.localEulerAngles);
    q.x = 0;
    this.gameObject.transform.localPosition += q * moveVec * Time.deltaTime * playerMovementSpeed;
}

public void OnMovement(InputValue input, float speed, Vector3 cameraRotationAngle)
{
    playerMovementSpeed = speed;

    Vector2 inputVec = input.Get<Vector2>();

    // Must multiply quat * vec | vec * quat causes an exception
    //moveVec = Quaternion.Euler(cameraRotationAngle) * new Vector3(inputVec.x, 0, inputVec.y);
    moveVec = new Vector3(inputVec.x, 0, inputVec.y);
}

I'm using the Quaternion of the CinemachineCamera so that as the camera rotates, directional movement accounts for it without having to stop and restart movement. At the same time, because it's a pseudo-isometric view, I want the x-value of the camera to be ignored (otherwise forward/backward movement has a skipping to it).

The current code works as-is, but querying and modifying the camera's returned transform value every frame via update seems less than ideal. I can't measure the performance impact because I can't figure out another way to do it.

Is there a way to make some sort of callback value that will give me `Quaternion(w, 0, y, z)' of that camera's transform every frame?

Ignore the cameraRotationAngle in the signature of playerControl.OnMovement - that was part of my initial attempt, but the value isn't updated until movement stops and restarts, so that will likely be removed.

\$\endgroup\$
1
\$\begingroup\$

modifying the cameras transform every frame via update seems less than ideal.

Then I have good news for you: you're not doing that. The code you've shown does not modify the camera's transform component in any way shape or form.

Is there a way to make some sort of callback value that will give me `Quaternion(0, y, z, w)' of that camera's transform every frame?

First, that is not what you want. Zeroing out the x component of a quaternion is not a way to remove the pitch component of the rotation. Instead, this can add a twisting roll to the camera.

Here's a gif that demonstrates this. On the left is the source object for the rotation, which I show pitching down then yawing through a full 360 degrees. On the right there's are two copies of the object, in blue and red. The blue copy is using the correct flattening code I show below. The red copy is just setting quaternion.x = 0. You can see that while they're equivalent at yaw = 0, as we rotate around the y, the red copy develops a rolling twist - deviating more than 6 degrees from level, as shown in the transform properties on the right. This error can grow much larger if the source pitch is greater.

Demo gif

It looks like what you want is a camera-relative flattening method, as shown here:

Camera relative movement is pushing into/off the ground instead of parallel

If you'd like to make the syntax look like a member method call, you can do that with an extension method:

public static class TransformExtensions {
    public static Quaternion GetPlanarRotation(this Transform transform, Vector3 planeNormal = default) {
        if (planeNormal == default) planeNormal = Vector3.up;

        return Quaternion.LookRotation(planeNormal, -transform.forward) * Quaternion.Euler(90, 0, 0);
    }
}

Then you can just say...

var q = cmCam.transform.GetPlanarRotation();
transform.localPosition += q * moveVec * Time.deltaTime * playerMovementSpeed;
\$\endgroup\$
8
  • \$\begingroup\$ as always, thank you. Just a note, I didn't mean modifying the camera's transform object, but taking the camera's transform value and modifying that returned value {get} to use for the calculation. \$\endgroup\$ Nov 23 at 17:13
  • \$\begingroup\$ If you want something that's not stored, then it's pretty natural that you'll need to do a computation to get it, no? \$\endgroup\$
    – DMGregory
    Nov 23 at 17:19
  • \$\begingroup\$ Yes, definitely a typical method - I was mostly just interested to see if such a thing could be done. As an aside, for a free-floating camera, I'd agree that zeroing out one of the Vector components of a Quaternion would be... bad. In this instance, only yaw and pitch can actually change, and the localized Vector of movement is "static" for lack of a better word (from the perspective of the camera). Manual manipulation of Quaternions is rightly frowned upon, but in this instance it's a good tool. The GetPlanarRotation() method costs DOUBLE the processor time - a whopping extra 0.004ms LOL. \$\endgroup\$ Nov 23 at 17:54
  • 1
    \$\begingroup\$ Even when using only yaw and pitch as inputs, you can get roll out when using the method that you're showing. I can add a gif later to demonstrate. \$\endgroup\$
    – DMGregory
    Nov 23 at 17:57
  • 1
    \$\begingroup\$ Updated with an example. I showed a relatively mild case here, but the error can easily exceed 20 degrees if your input pitch is steeper. \$\endgroup\$
    – DMGregory
    Nov 24 at 1:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.