Lazy free look orbiting

I’m trying to implement a lazy free look/orbit camera follow target that would effectively limit the camera to orbiting the player only when the player applies input. Here’s the general plan:

In theory, as long as the follow target updates after the player but before Cinemachine (via script execution order), this should be a simple matter of getting the look rotation from the camera to the player and applying that, along with the player’s position, to the follow target.

The issue is that I get stuck on steps 3-4, where the camera recenters to a calculated rotation. Rather than visually remaining where it was, as a fulcrum, the camera stiffly moves with the player. It slightly does what I intended, but it’s almost imperceptible. The free look’s heading definition is target forward. But, it almost feels like it isn’t recentering at all (although, I know it actually is). This indicates that the calculated rotation isn’t correct, possibly due to script timing.

The script’s execution order is after the player and before Cinemachine. And, I’ve tried all three Cinemachine update modes with the same result.

Here’s what I’ve come up with so far:

using UnityEngine;

namespace OhiraKyou {

	public class LazyCameraTarget : MonoBehaviour {

		/// <summary>
		/// Normally set equal to this.transform, but more versatile; assign in the inspector
		/// </summary>
		[SerializeField]
		private Transform _transformToMove;

		[SerializeField]
		private Transform _playerTransform;

		[SerializeField]
		private Transform _cameraTransform;

		private void LateUpdate () {
			// Follow the target
			_transformToMove.position = _playerTransform.position;

			// Calculate and apply look rotation from camera to player
			_transformToMove.rotation = Quaternion.LookRotation((playerTransform - cameraTransform.position).normalized, _playerTransform.up);
		}

	}

}

If you have any suggestions, please post them.

Hi @OhiraKyou ,

What you’re doing looks fine, and I don’t think script ordering is an issue here. If the FreeLook is following stiffly, it might be because it’s trying to maintain the desired distance from target. I would need to see the inspector for the FreeLook.

Let me just confirm my understanding of the camera behaviour you’re looking for:

  • If the player orbits around the camera, the camera should stay put, simply orienting itself to look at the player.
  • If the player moves out of the orbit, then the camera should first re-orient to face the player, then move so that it’s once again at the correct orbiting distance.

Is that right? If so, is the problem that the second step is happening too stiffly? That you’d like a dead zone for camera distance?

Your understanding in those two points is correct. The camera should maintain its distance (this is fine, the stiffness is desired here) while looking at the player, only orbiting if the player provides input.

Here’s a rough simulation of what I’m trying to accomplish, created by manually orbiting the camera: https://gfycat.com/violetkindarabianwildcat

So, I figured I would need to simply feed the free look a target whose forward vector was the direction from the camera to the player so that it wouldn’t need to orbit by default due to already being technically behind its target, regardless of where the player’s actual transform happens to be facing.

But, here’s what it actually does (or, rather, doesn’t do): https://gfycat.com/unevenverifiableankolewatusi

Here’s the inspector for the free look and camera target (editor screenshots taken while moving in-game): Cinemachine - Lazy camera inspector - Imgur

I’ve also tried using the main camera itself as the target’s camera transform reference instead of the free look’s transform; same result.

  1. Why are you using a FreeLook? Why not just an ordinary vcam with an orbital transposer?
  2. In your hand-baked example, the character seemed to be moving radially around the camera, so the camera didn’t need to move. In the CM example, the character looked as though it were moving in a straight line, not radially around the camera. So the camera followed. Is that what’s happening?

Also I’d like to see the damping values in the FL’s rigs

Looking more closely here, I see that we can improve the situation, even with the character’s current movement. Try this:

  • Set the vcam’s Binding Mode to Target Local with World Up
  • Pump up the Body’s X/Y damping as high as it will go - even higher if necessary
  • Make the Z damping nice and snappy

This will encourage the vcam to only move towards or away from the LazyFollow target.

1 Like
  1. The player can look up and down as well by supplying input, and I control the free look’s variables via script for complex actions like swimming.

  2. Halfway through the simulated example, I move forward and back again, and the camera follows. Think of the camera as a wheelbarrow that the player is tugging along by stiff handles. If the person pulling the wheelbarrow walks around it, the wheelbarrow rotates. If the person walks backward into the wheelbarrow, it moves backward the same amount. If the person moves forward, the wheelbarrow moves forward the same amount. If the person moves perpendicularly to the wheelbarrow, the wheelbarrow will rotate until it starts getting pulled by the person. These are all theoretical products of defining the target’s forward vector by the camera to player transform direction.

In the CM example, I wouldn’t be able to orbit around the camera if I tried, because the camera remained fixed in place relative to the target.

Damping is 0 on all rigs. Recentering works fine when the target is the player transform itself. Although, of course, that puts the camera directly behind the player at all times, which is not the goal here.

With a high x/y damping, a bit of yaw damping, and some z damping, the behavior starts to look similar. But, a moderate z damping causes the player to easily outrun the camera, and a low z damping causes this: https://gfycat.com/masculinegrimyasp Both cause the camera to drift constantly, which could be a result of the damping causing a sort of feedback loop with the target’s forward vector affecting the camera’s heading, which then affects the target’s forward vector, creating a cycle.

Also, ideally, I’d like to avoid interpolation entirely. The behavior should be stiff and responsive, like a wheelbarrow.

I don’t get why the low Z damping would cause that. Zero Z damping should force the distance between camera and target to be constant. I think you can overdrive the X/Y damping through script. Make it ridiculously high - essentially infinite - and put zero Z damping. What does that give?

I wrote a script to set x/y damping to float.MaxValue, leaving all other damping values at 0. The camera didn’t follow at all, and, as a result, was able to orbit thin air. Here’s a gif: https://gfycat.com/tepiddefiniteiberianchiffchaff

The 180 degree rotations while moving where done by CM. The orbits while stationary were done by me.

And, here is the script:

using Cinemachine;
using UnityEngine;

namespace OhiraKyou {

   public class LazyCameraTest : MonoBehaviour {

       [SerializeField]
       private CinemachineFreeLook _targetFreeLook;

       private void Start () {
           SetDamping(0);
           SetDamping(1);
           SetDamping(2);
       }

       private void SetDamping (int rigIndex) {
           CinemachineVirtualCamera vCam = _targetFreeLook.GetRig(rigIndex);
           CinemachineOrbitalTransposer transposer = vCam.GetCinemachineComponent<CinemachineOrbitalTransposer>();
           transposer.m_XDamping = float.MaxValue;
           transposer.m_YDamping = float.MaxValue;
       }

   }

}

Is this beginning to look like what you want?
Install the scene in a project that has the latest CM, press play, and drag the player around in the scene view. The FreeLook will follow it, I think in the way you need.
There’s no Body damping at all. You can try nudging up the Z damping to make it a little more squishy if you like.

3252958–250513–ohirakyu.unitypackage (48 Bytes)

@OhiraKyou Just checking in… all good here?

Sorry for the delay. The package you attached seems to be empty; it’s 48 bytes.

Oops, sorry about that, didn’t notice. Here it is again. Import it into an empty project, open the scene, press play, and drag the player around from a top-down scene view.

One thing to note: dynamically re-orienting the target, as your script does, implies that the user can no longer free-look horizontally. As soon as any nonzero value is placed in the X-axis (or the bias), it goes into a feedback loop and spins out of control.

3259410–251263–ohirakyou.unitypackage (508 KB)

Yeah, that’s what I was going for.

Looks like the issue was as I expected in post #7 and as you just mentioned: it’s the feedback loop. With the x value set to 0, Lock To Target With World Up behaves mostly the way I want.

Being able to manually orbit the camera via input is vitally important as well. I just hadn’t ironed out the details yet, because I wanted to get the automatic camera behavior down before adding in the manual stuff. But, since having a value from any source causes the loop, it threw me off. Knowing that, it was easy enough to set the x axis value to 0 while testing and then move on to correcting for manual input.

My solution is to only rotate the target while input is not being supplied and to then reset the x axis value to 0 while rotating. Thus, the target lets the camera accumulate an offset via its x value when the player is manually controlling the camera and then essentially applies that offset to its orientation via its next usual direction calculation cycle (which recalculates orientation before resetting x to 0), during which input is not being supplied.

This mostly worked. However, I noticed some strange anomalies involving the free look’s bottom rig. While the camera was looking up (from the bottom rig), it jerked on releasing the input, and the orbit was off-center. After some testing, I came to the conclusion that it was caused by the bottom rig’s target offset (0, 1, 0), which I originally assigned to make looking upward easier. I fixed this by setting the offset to 0 and bumping up the middle and bottom rig’s Screen Y to smoothly compensate. With this, the jerking and orbit offset issues were entirely resolved.

Here’s a gif that includes the buggy offset behavior at the end: https://gfycat.com/EllipticalAcademicHart
And, here’s a gif of the fixed version, which uses Screen Y instead: https://gfycat.com/HeartfeltOrderlyCoati

Thanks for the help!

I like your camera a lot. The “wheelbarrow” idea is a very good one, and has a nice human follow feel to it - especially if you give it just a little z damping. Currently CM isn’t expressive enough to make this without extra scripting. I’m going to think about how to make this easier to implement.

Thanks for being such an awesome early adopter, and for your valuable input on these forums.

No problem! Your active support makes it a lot easier to dig into.

@OhiraKyou I’ve implemented a new Transposer binding mode, SimpleFollow, that seems to be doing a pretty good job at giving the results we’ve been discussing. It even handles X-axis rotation correctly. Care to give it a spin and tell me what you think? I’m really liking this camera :slight_smile:

Import the package into an empty project, run the scene, and drag the player around in the scene view from above. Check out the FreeLook settings: so nice and simple!

3260539–251383–ohirakyou.unitypackage (567 KB)

That’s pretty awesome! Much more convenient to implement.

Something that immediately stuck out, however, is that the heading and recentering settings are hidden on selecting the simple follow binding mode. I actually use these settings in my custom implementation to bring the camera around the back of the player’s character when they’re doing something that necessitates seeing what’s in front of the character (e.g., while charging forward).

Here are the relevant chunks of code that basically illustrate what I’m doing:

private void EnableRecentering () {
   _freeLook.m_RecenterToTargetHeading.m_enabled = true;
}

private void DisableRecentering () {
   _freeLook.m_RecenterToTargetHeading.m_enabled = false;
}


// ... in an update function in the camera's state machine script ...

// Recenter
if (CurrentCameraMode == CameraMode.LazyFollow) {

   // Reposition the target, and align it to the camera-to-player direction
   _lazyTarget.Align();

   // While charging, the player needs to see what's in front of the character. So, full recentering is required.
   // Since the lazy target is still moving, even if it isn't rotating, we can use the position delta heading.
   // This is a rough approximation of the forward vector, but it will have to do.
   _freeLook.m_Heading.m_HeadingDefinition = CinemachineOrbitalTransposer.Heading.HeadingDefinition.PositionDelta;

   // Determine if this enhanced recentering is needed (for intense actions) or if the lazy alignment is sufficient
   if (_playerMover.Charging || _playerMover.UnderwaterCharging) {
       EnableRecentering();
   } else {
       DisableRecentering();
   }

} else {

   // Other camera modes (active, passive, and follow only) follow the player instead of the lazy target.
   // In these cases, the target forward heading is always correct.
   // Since target forward is much more responsive than delta position, it should be used when available.
   _freeLook.m_Heading.m_HeadingDefinition = CinemachineOrbitalTransposer.Heading.HeadingDefinition.TargetForward;
   EnableRecentering();

}

Yes, I see how you’re using it. However, the heading and recentering settings are not hidden for nothing. In fact they make no sense in SimpleFollow, since the heading is defined by the camera’s current position. Any deviation from that will result in feedback spin. Likewise, the X offset is ignored in this setting, as are X and Y damping. And since the heading is constant (always 0) recentering makes no sense either, unless you change modes. You can still set these values from script, but in SimpleFollow mode those settings will have no effect.

I’m wondering if for your use-case it might be better to use multiple vcams for the different states and blend between them.

My original reason for not blending to a separate virtual camera while charging is that I didn’t want the free look to return to its pre-charge position after the player stops charging, as I assume it would, when it blends back to the free look. If I have to match the position of two cameras, it becomes less convoluted to simply make the state-specific changes I need to a single camera through scripting instead.

If there was a simple way to map a separate virtual camera’s position to a free look’s orbit on swap, so that the player-to-camera angle doesn’t change, I’d consider it.