Camera: Cinemachine-based rig with an anchor, a state director, point-of- interest framing, pan input, and dialogue framing volumes. Player: click-to-move over the NavMesh, a pointer input layer, and a motion arbiter that reconciles direct stick input with navigation-driven movement. The pure math is isolated in NightclubArcadia.Locomotion.Math with an EditMode suite, so the arbitration rules are testable without a scene. Interaction: IClickInteractable plus a bridge that routes pointer clicks to it. Supporting project settings: adds the Cinemachine package, the Interactable and Ground tags the raycasts depend on, and NavMesh agent dimensions matched to the player capsule (radius 0.3, height 1.8, climb 0.25). Co-Authored-By: Claude Opus 5 <[email protected]>
86 lines
2.4 KiB
C#
86 lines
2.4 KiB
C#
using Unity.Cinemachine;
|
|
using UnityEngine;
|
|
|
|
namespace NightclubArcadia.Cinematics
|
|
{
|
|
/// <summary>
|
|
/// Proxy transform the follow camera tracks. Combines player follow,
|
|
/// POI bias, manual pan offset, and distance bias.
|
|
/// </summary>
|
|
public sealed class CameraRigAnchor : MonoBehaviour
|
|
{
|
|
[SerializeField] Transform followTarget;
|
|
[SerializeField] CinemachinePositionComposer composer;
|
|
[SerializeField] float followSmoothTime = 0.12f;
|
|
[SerializeField] float distanceSmoothTime = 0.8f;
|
|
|
|
float defaultCameraDistance = 10f;
|
|
float distanceVelocity;
|
|
Vector3 followVelocity;
|
|
|
|
public Vector3 PoiBias { get; set; }
|
|
public Vector3 ManualPanOffset { get; set; }
|
|
public float DistanceBias { get; set; }
|
|
|
|
public Transform FollowTarget => followTarget;
|
|
|
|
void Start()
|
|
{
|
|
if (composer == null)
|
|
{
|
|
composer = FindFirstObjectByType<CinemachinePositionComposer>();
|
|
}
|
|
|
|
if (composer != null)
|
|
{
|
|
defaultCameraDistance = composer.CameraDistance;
|
|
}
|
|
}
|
|
|
|
void LateUpdate()
|
|
{
|
|
if (followTarget == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var targetPosition = followTarget.position + PoiBias + ManualPanOffset;
|
|
transform.position = Vector3.SmoothDamp(
|
|
transform.position,
|
|
targetPosition,
|
|
ref followVelocity,
|
|
followSmoothTime);
|
|
|
|
if (composer != null)
|
|
{
|
|
var targetDistance = defaultCameraDistance + DistanceBias;
|
|
composer.CameraDistance = Mathf.SmoothDamp(
|
|
composer.CameraDistance,
|
|
targetDistance,
|
|
ref distanceVelocity,
|
|
distanceSmoothTime);
|
|
}
|
|
}
|
|
|
|
public void Nudge(Vector3 worldDelta)
|
|
{
|
|
ManualPanOffset += worldDelta;
|
|
}
|
|
|
|
public void ClampManualPan(float maxDistance)
|
|
{
|
|
if (followTarget == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var offset = ManualPanOffset;
|
|
offset.y = 0f;
|
|
if (offset.magnitude > maxDistance)
|
|
{
|
|
ManualPanOffset = offset.normalized * maxDistance;
|
|
}
|
|
}
|
|
}
|
|
}
|