The Unity script needs the Editor and the setup listed below. The separate C# companion practice runs in SharpRune without UnityEngine.
Observe the movement that the camera is meant to follow
A camera following a target moved in Update needs the target’s new frame observation. Putting both scripts in unrelated Update callbacks does not establish which one writes first. LateUpdate follows Update work, giving this simple rig a useful phase boundary. It still does not order two camera rigs against each other or solve a target driven on a different clock. Keep one owner for the camera’s final pose and identify whether its target is driven by Update, physics or animation. The code intentionally begins with a directly assigned Transform so discovery and camera-package setup do not hide that central scheduling question.
Retain velocity across smoothing calls
Vector3.SmoothDamp takes its current velocity by reference because that value is part of the smoothing state. Recreating a zero velocity local every frame changes the operation’s behavior; this component keeps one field and resets it on activation. LateUpdate computes target.position plus worldOffset, then assigns the smoother’s returned position. It skips a nonpositive elapsed-time frame and rejects invalid smoothTime configuration. The look direction is checked for both negligible length and near-collinearity with the chosen up axis before LookRotation is used. That guard preserves the previous orientation for a degenerate view instead of pretending every target position defines a unique camera frame.
private void OnEnable() { velocity = Vector3.zero; }
private void LateUpdate()
{
if (target == null || Time.deltaTime <= 0f) return;
if (smoothTime <= 0f || float.IsNaN(smoothTime) || float.IsInfinity(smoothTime)) return;
Vector3 desired = target.position + worldOffset;
transform.position = Vector3.SmoothDamp(transform.position, desired, ref velocity, smoothTime);
Vector3 direction = target.position - transform.position;
if (direction.sqrMagnitude > 0.0001f && Vector3.Cross(direction, Vector3.up).sqrMagnitude > 0.0001f)
transform.rotation = Quaternion.LookRotation(direction, Vector3.up);
}
}The console check verifies observation selection only
The first supplied target observation is two units along X at ground level. Adding the offset produces two, three, minus six. The second observation has moved to X four and Z one; the desired camera position becomes four, three, minus five. The difference is enough to show why following a stale observation creates a different target pose. Nothing in this lab schedules Unity callbacks or implements SmoothDamp. Its integer points avoid misleading timing claims and keep the arithmetic deterministic. Treat the two lines as desired-position checks, then use the engine scene to investigate smoothing, actual camera framing and any visible jitter.
A follow rig still has presentation and physics boundaries
A world-space offset remains aligned with world axes even when the target turns. If the design needs a shoulder offset relative to the actor, that is another coordinate-space contract and should use an appropriate target-space conversion. A smoothly following camera can still clip through a wall because this example performs no obstruction query. A physics-driven target can expose a different update cadence; interpolation and the physics presentation boundary need their own review. Increasing smoothTime may hide some visible motion while adding lag, but it does not diagnose who writes the target or camera. Measure and isolate those causes before tuning a constant.
Practice: identify who writes each pose
Use a simple target mover that changes position during Update and keep all camera-package controllers disabled. Compare the camera’s target observation when following in LateUpdate with a deliberately copied Update version, avoiding a claim of deterministic cross-object Update order from one run. Next disable and re-enable the component while the target continues moving and observe the explicit velocity reset. Finally move worldOffset to another side of the target and verify that it remains a world offset as the target rotates. Record target and camera ownership along with the observed visual effect so another learner can reproduce the same scheduling experiment.
- Hint: a different camera script writing later can overwrite a perfectly correct follow result.
- Hint: begin with a Transform-driven target before testing Rigidbody interpolation.
Practice: test the desired-position function independently
Add observations with a negative target coordinate, a zero offset and a target whose height changes. Predict each component-wise sum before running it. Then design a separate data type or function name for an actor-relative offset rather than silently changing Desired’s meaning. For a broader pure model, return a desired position together with the observation’s sequence number so a caller can reject stale data, but do not name that number a Unity frame unless the engine supplied it. This exercise strengthens a small mathematical boundary while leaving smoothing integration and rendered-camera behavior to the environment that actually owns them.
- Hint: a desired pose is an input to a camera controller, not evidence that it has been reached.
- Hint: preserve units and coordinate-space names when extending the pure data model.
Choose the simplest rig that meets the actual scene requirement
For the baseline, successful setup means the assigned target is observed after Update, the camera owns persistent smoothing state, and no competing writer replaces its pose. A stopped time scale intentionally pauses this scaled-time rig; an unscaled menu camera would be a separate design. Do not promote the scene exercise into a claim of obstacle avoidance, cinematic composition or physics jitter elimination. The supplied engine script has documented API review and syntax validation, while the ordinary C# arithmetic was actually executed. That honest boundary makes the example useful when a later project decides whether a dedicated camera package would better satisfy its production requirements.
Unity Editor example
Set up in Unity 6
- Create a 3D scene with a visible Cube target at the origin and a Camera starting at (0, 3, -6). Attach CameraFollowProbe.cs to that Camera.
- Assign the Cube Transform as target, use worldOffset (0, 3, -6) and smoothTime 0.25. Remove other camera-position writers for the baseline.
- Enter Play mode and move the target with the Scene view Move tool, or use a separate target mover that writes its position in Update. Observe the camera in Game view.
- Disable and re-enable CameraFollowProbe to test the explicit velocity reset. A target moved by Rigidbody physics may need interpolation or another reviewed camera strategy.
- Pause scaled gameplay time only as a separate experiment; this script deliberately skips frames with nonpositive Time.deltaTime.
CameraFollowProbe.cs
Download Unity script ↗using UnityEngine;
[RequireComponent(typeof(Camera))]
public sealed class CameraFollowProbe : MonoBehaviour
{
[SerializeField] private Transform target;
[SerializeField] private Vector3 worldOffset = new Vector3(0f, 3f, -6f);
[SerializeField, Min(0.01f)] private float smoothTime = 0.25f;
private Vector3 velocity;
private void OnEnable() { velocity = Vector3.zero; }
private void LateUpdate()
{
if (target == null || Time.deltaTime <= 0f) return;
if (smoothTime <= 0f || float.IsNaN(smoothTime) || float.IsInfinity(smoothTime)) return;
Vector3 desired = target.position + worldOffset;
transform.position = Vector3.SmoothDamp(transform.position, desired, ref velocity, smoothTime);
Vector3 direction = target.position - transform.position;
if (direction.sqrMagnitude > 0.0001f && Vector3.Cross(direction, Vector3.up).sqrMagnitude > 0.0001f)
transform.rotation = Quaternion.LookRotation(direction, Vector3.up);
}
}
What to observe in Unity
The camera smooths toward target.position plus a world offset after Update and aims at a nondegenerate target direction. It does not avoid walls or prove a jitter-free physics rig.
The console practice below isolates the C# logic. It does not run a Unity scene or emulate Unity engine callbacks.
C# companion practice
using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
var offset = new Point(0, 3, -6);
var beforeMovement = new Point(2, 0, 0);
var afterMovement = new Point(4, 0, 1);
Console.WriteLine(Desired(beforeMovement, offset));
Console.WriteLine(Desired(afterMovement, offset));
Point Desired(Point target, Point delta) => new(target.X + delta.X, target.Y + delta.Y, target.Z + delta.Z);
record Point(int X, int Y, int Z) { public override string ToString() => $"{X},{Y},{Z}"; }
C# practice output
2,3,-6 4,3,-5
LateUpdate orders this camera after Update, not after every possible writer.