add camera rig, click-to-move locomotion, and click interaction
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]>
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NightclubArcadia.Cinematics
|
||||
{
|
||||
/// <summary>
|
||||
/// Scores registered POIs and writes bias onto the camera anchor.
|
||||
/// </summary>
|
||||
public sealed class CameraPointOfInterestDirector : MonoBehaviour
|
||||
{
|
||||
static readonly List<CameraPointOfInterest> Registered = new List<CameraPointOfInterest>();
|
||||
|
||||
[SerializeField] CameraRigAnchor anchor;
|
||||
[SerializeField] CameraStateDirector stateDirector;
|
||||
[SerializeField] float poiSmoothTime = 1f;
|
||||
[SerializeField] float poiReleaseTime = 1.4f;
|
||||
[SerializeField] float maxHeadingAngle = 100f;
|
||||
|
||||
Vector3 poiVelocity;
|
||||
float distanceVelocity;
|
||||
CameraPointOfInterest activePoi;
|
||||
|
||||
public bool HasActivePoi => activePoi != null;
|
||||
|
||||
public static void Register(CameraPointOfInterest poi)
|
||||
{
|
||||
if (poi != null && !Registered.Contains(poi))
|
||||
{
|
||||
Registered.Add(poi);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Unregister(CameraPointOfInterest poi)
|
||||
{
|
||||
Registered.Remove(poi);
|
||||
}
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (anchor == null)
|
||||
{
|
||||
anchor = GetComponent<CameraRigAnchor>();
|
||||
}
|
||||
|
||||
if (stateDirector == null)
|
||||
{
|
||||
stateDirector = GetComponent<CameraStateDirector>();
|
||||
}
|
||||
}
|
||||
|
||||
void LateUpdate()
|
||||
{
|
||||
if (anchor == null || stateDirector == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var follow = anchor.FollowTarget;
|
||||
if (follow == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!stateDirector.AllowsPointOfInterest)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (stateDirector.Current == CameraState.ManualPan)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
activePoi = FindBestPoi(follow);
|
||||
if (activePoi == null)
|
||||
{
|
||||
ReleasePoi();
|
||||
return;
|
||||
}
|
||||
|
||||
var toPoi = activePoi.Target.position - follow.position;
|
||||
toPoi.y = 0f;
|
||||
var targetBias = Vector3.ClampMagnitude(toPoi, activePoi.MaxBias);
|
||||
|
||||
anchor.PoiBias = Vector3.SmoothDamp(
|
||||
anchor.PoiBias,
|
||||
targetBias,
|
||||
ref poiVelocity,
|
||||
poiSmoothTime);
|
||||
|
||||
anchor.DistanceBias = Mathf.SmoothDamp(
|
||||
anchor.DistanceBias,
|
||||
activePoi.ExtraDistance,
|
||||
ref distanceVelocity,
|
||||
poiSmoothTime);
|
||||
}
|
||||
|
||||
CameraPointOfInterest FindBestPoi(Transform follow)
|
||||
{
|
||||
CameraPointOfInterest best = null;
|
||||
var bestScore = 0f;
|
||||
var forward = follow.forward;
|
||||
|
||||
foreach (var poi in Registered)
|
||||
{
|
||||
if (poi == null || !poi.isActiveAndEnabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var offset = poi.Target.position - follow.position;
|
||||
offset.y = 0f;
|
||||
var distance = offset.magnitude;
|
||||
if (distance > poi.TriggerRadius || distance <= 0.01f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var angle = Vector3.Angle(forward, offset);
|
||||
if (angle > maxHeadingAngle)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var score = poi.Weight * (1f - distance / poi.TriggerRadius);
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
best = poi;
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
void ReleasePoi()
|
||||
{
|
||||
if (anchor == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
anchor.PoiBias = Vector3.SmoothDamp(
|
||||
anchor.PoiBias,
|
||||
Vector3.zero,
|
||||
ref poiVelocity,
|
||||
poiReleaseTime);
|
||||
|
||||
anchor.DistanceBias = Mathf.SmoothDamp(
|
||||
anchor.DistanceBias,
|
||||
0f,
|
||||
ref distanceVelocity,
|
||||
poiReleaseTime);
|
||||
|
||||
if (anchor.PoiBias.sqrMagnitude < 0.0001f && Mathf.Abs(anchor.DistanceBias) < 0.01f)
|
||||
{
|
||||
activePoi = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user