Search Unity

Getting FreeLook Camera to jump behind player

Discussion in 'Cinemachine' started by DinostabOMG, Oct 16, 2019.

  1. DinostabOMG

    DinostabOMG

    Joined:
    Jan 4, 2014
    Posts:
    26
    Hello, I'm trying to set up a feature where a camera will instantly jump to behind the player, like a lot of 3rd person games do when you press the right control stick, for example.

    I came up with something that basically works, but it doesn't provide for a lot of control. Basically the goal is to reposition the camera so that its `transform.forward`, projected onto the ground plane, is the same as the player's `transform.forward`, also projected onto the ground plane.


    (You can see this work when the camera rotates but the mouse is not moving)

    The difficulty I'm having is that it seems that the only way I've found to swing the camera around is by overriding `CinemachineCore.AxisInputDelegate`. This works, but the output is not predictable - or at least I don't know how to predict it.

    What I currently do is, get the signed angle between the camera and the desired position, which determines if I need to swing left or right. Then I override the axis input with my custom function. Then I start a coroutine, which tests whether the camera is inside a tolerance, or if the dot product of `camera.transform.forward` and `player.transform.forward` is less than the previous frame (which means we've overshot) or if it's greater than a tolerance value, say `0.95f`. Then I return the axis input function to its original reference.

    The custom input function basically just multiplies a speed value times the sign of the original angle (left or right). It ignores the Y axis.

    Like I said, this does work, but some problems with this:
    • I can't set a duration. Normally I would let the user set a duration, but because I am rotating some amount that I don't seem to be able to control per frame, I don't know how long it's going to take. I can set a speed multiplier, but this is still not optimal.
    • The motion is robotic. I have introduced an animation curve, but the problem is determining the input value of the curve because I don't have the total duration. What I'm doing instead is using the progress from the starting dot product to `1f`, but this makes it hard to control because small values will cause very little change between frames, and curving sections are more abrupt.
    • Eventually I would like to override the Y axis too, to get the camera to a specified distance behind the player, or at least a specified distance on the curve that bridges the three rigs. However, because the method starts and stops based on guessing and checking the X value, I can't couple the end time of the Y adjustment to the end time of the X adjustment. In other words, maybe I'll be at the right angle well before or well after I'm at the right distance.
    • Its unclear how the output value is consumed. I haven't been able to determine what value corresponds to one degree of movement, for example, and I think it is not intended to enable this - probably for smoothing purposes.
    Any thoughts on how to remedy this? Any possibility this functionality will be introduced into the controller? Or am I missing some silver bullet?
     
  2. Gregoryl

    Gregoryl

    Unity Technologies

    Joined:
    Dec 22, 2016
    Posts:
    7,711
    FreeLook does have this built-in. Look at the "Recentering" portions of the Axis Control section of the FreeLook inspector:

    upload_2019-10-17_10-5-44.png

    Provided that your binding mode isn't SimpleFollow, you can set it up to recenter to be behind the target's forward axis, which I think is what you want. That's what "Target Forward" heading definition means. You can set the recentering time to take as long as you want.

    You can manually trigger a recentering by calling the FreeLook's m_XAxis (or m_YAxis)'s RecenterNow() method. EDIT: instead of m_XAxis and m_YAxis, use FreeLook.m_RecenterToTargetHeading and FreeLook.m_YAxisRecentering.

    What recentering does is to smoothly lerp the Axis.Value member to the "centered" value, which is chosen depending on the heading. If you don't use the built-in recentering, you can write your own code to do the same thing. No need to go through CinemachineCore.AxisInputDelegate.

    For SimpleFollow or if your heading definition is not TargetForward, the "recentered" value will not necessarily be 0. In that case you will have to manually calculate an appropriate value to move towards, as you have done. But then all you need to do is lerp the axis.Value member directly. To make it less mechanical, you can use this function: https://docs.unity3d.com/ScriptReference/Mathf.SmoothDamp.html
     
    Last edited: Oct 18, 2019
    AlexLeonardKrea and DinostabOMG like this.
  3. DinostabOMG

    DinostabOMG

    Joined:
    Jan 4, 2014
    Posts:
    26
    Thanks for wading through my post. That was the issue - I did have it set to SimpleFollow binding mode. Actually, the player's control scheme is sometimes set to camera space, and so most of the other schemes cause a feedback loop that causes the camera to spin uncontrollably for most of these modes. But World Space does work! Thanks again.
     
  4. DinostabOMG

    DinostabOMG

    Joined:
    Jan 4, 2014
    Posts:
    26
    Just a follow-up, if you happen to know. I've tried this code:

    Code (CSharp):
    1.  
    2.                 Debug.Log("Recenter!");
    3.                 if (centerX)
    4.                 {
    5.                     cam.m_XAxis.m_Recentering.m_RecenteringTime = 0.1f;
    6.                     cam.m_XAxis.m_Recentering.m_WaitTime = 0f;
    7.                     cam.m_XAxis.m_Recentering.m_enabled = true;
    8.                     cam.m_XAxis.m_Recentering.RecenterNow();
    9.                     Debug.Log("\tX");
    10.                 }
    11.                 if (centerY)
    12.                 {
    13.                     cam.m_YAxis.m_Recentering.m_RecenteringTime = 0.1f;
    14.                     cam.m_YAxis.m_Recentering.m_WaitTime = 0f;
    15.                     cam.m_YAxis.m_Recentering.m_enabled = true;
    16.                     cam.m_XAxis.m_Recentering.RecenterNow();
    17.                     Debug.Log("\tY");
    18.                 }
    19.                 //cam.m_RecenterToTargetHeading.m_enabled = true;
    20.                 //cam.m_RecenterToTargetHeading.RecenterNow();
    And it appears to do nothing, However, if I uncomment the last two lines I do get the camera swinging back around (although the Y axis doesn't seem to change). Even though I do get the Debug logs. The inspector values (including for `enabled`) don't seem to change either. Am I missing something? upload_2019-10-18_2-54-46.png
     
  5. Gregoryl

    Gregoryl

    Unity Technologies

    Joined:
    Dec 22, 2016
    Posts:
    7,711
    Yes, I think I misled you, sorry about that. While the axes do have Recentering members, in the FreeLook, for historical reasons, they're not used. That's why setting them has no effect. Instead, use FreeLook.m_RecenterToTargetHeading and FreeLook.m_YAxisRecentering. Then it should work.
     
    sniffle63, chzwhz and DinostabOMG like this.
  6. DinostabOMG

    DinostabOMG

    Joined:
    Jan 4, 2014
    Posts:
    26
    Ah, got it. That works, thanks.
     
    Gregoryl likes this.