diff --git a/Assets/Scripts/Camera/CameraDirector.cs b/Assets/Scripts/Camera/CameraDirector.cs
index ecc6e50..c30d6f2 100644
--- a/Assets/Scripts/Camera/CameraDirector.cs
+++ b/Assets/Scripts/Camera/CameraDirector.cs
@@ -5,36 +5,49 @@ using UnityEngine.InputSystem;
namespace Jankenbots.Prototype
{
///
- /// JANKENBOTS — client-local camera director. Owns four Cinemachine cameras and
- /// lets the local player cycle between them; the CinemachineBrain on this Camera
- /// blends smoothly on every switch.
+ /// JANKENBOTS — client-local camera director. Owns four Cinemachine cameras, lets
+ /// the local player cycle between them and orbit/zoom the follow cams; the
+ /// CinemachineBrain on this Camera blends smoothly on every switch.
///
- /// • AvatarFollow — 3rd-person chase on YOUR character (on-foot).
- /// • RobotFollow — chase cam on the bot (driving; the robot-follow cam).
+ /// • AvatarFollow — orbitable 3rd-person cam on YOUR character (on-foot).
+ /// • RobotFollow — orbitable chase cam on the bot (driving).
/// • StandView — fixed view from your player stand, looking at the bot.
/// • CinematicOrbit— slow auto-orbit of the bot (spectate / flavor).
///
- /// The default mode auto-follows the control state: on foot → AvatarFollow, seated
- /// (driving) → RobotFollow — reading the SAME seat flag as the control handoff
- /// (). Press V to cycle manually; claiming
- /// or leaving a seat re-asserts the sensible default.
+ /// LOOK / ZOOM (Avatar & Robot only): hold RIGHT MOUSE and drag to orbit
+ /// (yaw/pitch), scroll wheel to zoom in/out (out to so
+ /// you can pull back and see the arena). RMB-drag keeps the cursor free for the
+ /// Claim/Leave buttons until the walk-up interaction exists.
///
- /// The vcams are intentionally "bare" (no procedural body/aim) — the director poses
- /// their transforms each LateUpdate from the live targets, and the Brain just blends
- /// between whichever is prioritised. Cameras are purely client-local, never networked.
+ /// Default mode auto-follows the control state (on foot → Avatar, seated → Robot),
+ /// reading the same seat flag as the control handoff ().
+ /// Press V to cycle manually. Cameras are purely client-local, never networked.
///
[RequireComponent(typeof(Camera))]
public class CameraDirector : MonoBehaviour
{
public enum Mode { AvatarFollow, RobotFollow, StandView, CinematicOrbit }
- [Header("Framing")]
- public Vector3 avatarOffset = new Vector3(0f, 4f, -6f);
- public Vector3 robotOffset = new Vector3(0f, 8f, -12f);
- public float orbitRadius = 20f;
- public float orbitHeight = 9f;
- public float orbitSpeed = 18f; // deg/sec
- public float lookHeight = 1.2f; // aim a bit above the target pivot
+ [Header("Follow distances")]
+ public float avatarDistance = 8f;
+ public float robotDistance = 16f;
+
+ [Header("Look / zoom (Avatar & Robot)")]
+ [Tooltip("Degrees of orbit per pixel of mouse drag (while holding RMB).")]
+ public float lookSensitivity = 0.18f;
+ [Tooltip("Metres of zoom per scroll notch.")]
+ public float zoomSensitivity = 4f;
+ public float minDistance = 3f;
+ [Tooltip("How far you can pull the camera back. Big so you can survey the arena.")]
+ public float maxDistance = 80f;
+ public float minPitch = -8f;
+ public float maxPitch = 78f;
+ public float lookHeight = 1.2f; // aim a bit above the target pivot
+
+ [Header("Cinematic orbit")]
+ public float orbitRadius = 22f;
+ public float orbitHeight = 10f;
+ public float orbitSpeed = 16f; // deg/sec
Transform _bot, _standOverlook;
CinemachineCamera _avatar, _robot, _stand, _orbit;
@@ -42,18 +55,23 @@ namespace Jankenbots.Prototype
Mode _mode = Mode.AvatarFollow;
bool _userOverride;
bool _lastSeated;
- float _orbitAngle;
+
+ // Orbit state shared by the two follow cams (player-controlled).
+ float _yaw;
+ float _pitch = 18f;
+ float _dist = 8f;
+ float _cineAngle;
+
PlayerAvatar _local;
void Start()
{
if (GetComponent() == null) gameObject.AddComponent();
- // Retire the old bespoke bot-only follow cam if it's still on this camera.
var legacy = GetComponent();
if (legacy != null) legacy.enabled = false;
- var b = GameObject.Find("Bot"); if (b != null) _bot = b.transform;
- var s = GameObject.Find("StandPoint_0"); if (s != null) _standOverlook = s.transform;
+ var b = GameObject.Find("Bot"); if (b != null) _bot = b.transform;
+ var s = GameObject.Find("StandPoint_0"); if (s != null) _standOverlook = s.transform;
var holder = new GameObject("CameraRigs").transform;
_avatar = Make(holder, "CM_AvatarFollow");
@@ -85,31 +103,50 @@ namespace Jankenbots.Prototype
Apply(_mode);
}
- // A seat claim/leave re-asserts the sensible default (clears a manual override).
+ // Seat claim/leave re-asserts the sensible default (clears a manual override).
if (_local != null && _local.IsSeatedLocal != _lastSeated)
{
_lastSeated = _local.IsSeatedLocal;
_userOverride = false;
}
-
if (!_userOverride && _local != null)
{
Mode want = _local.IsSeatedLocal ? Mode.RobotFollow : Mode.AvatarFollow;
if (want != _mode) { _mode = want; Apply(_mode); }
}
+
+ // Look / zoom input — only for the orbitable follow cams.
+ if (_mode == Mode.AvatarFollow || _mode == Mode.RobotFollow)
+ {
+ var m = Mouse.current;
+ if (m != null)
+ {
+ if (m.rightButton.isPressed)
+ {
+ Vector2 d = m.delta.ReadValue();
+ _yaw += d.x * lookSensitivity;
+ _pitch = Mathf.Clamp(_pitch - d.y * lookSensitivity, minPitch, maxPitch);
+ }
+ float scroll = m.scroll.ReadValue().y;
+ if (Mathf.Abs(scroll) > 0.01f)
+ _dist = Mathf.Clamp(_dist - Mathf.Sign(scroll) * zoomSensitivity, minDistance, maxDistance);
+ }
+ }
}
void LateUpdate()
{
- if (_local != null) PoseFollow(_avatar, _local.transform, avatarOffset);
+ if (_local != null) PoseOrbit(_avatar, _local.transform);
if (_bot != null)
{
- PoseFollow(_robot, _bot, robotOffset);
+ PoseOrbit(_robot, _bot);
- _orbitAngle += orbitSpeed * Time.deltaTime;
- Vector3 off = Quaternion.Euler(0f, _orbitAngle, 0f) * new Vector3(0f, orbitHeight, -orbitRadius);
- PoseFollow(_orbit, _bot, off);
+ _cineAngle += orbitSpeed * Time.deltaTime;
+ Vector3 focus = _bot.position + Vector3.up * lookHeight;
+ Vector3 cineOff = Quaternion.Euler(0f, _cineAngle, 0f) * new Vector3(0f, orbitHeight, -orbitRadius);
+ _orbit.transform.position = focus + cineOff;
+ _orbit.transform.rotation = Quaternion.LookRotation(focus - _orbit.transform.position);
if (_standOverlook != null)
{
@@ -120,11 +157,14 @@ namespace Jankenbots.Prototype
}
}
- void PoseFollow(CinemachineCamera vc, Transform target, Vector3 worldOffset)
+ // Player-controllable orbit: spherical offset from the target driven by _yaw/_pitch/_dist.
+ void PoseOrbit(CinemachineCamera vc, Transform target)
{
- Vector3 pos = target.position + worldOffset;
+ Vector3 focus = target.position + Vector3.up * lookHeight;
+ Vector3 dir = Quaternion.Euler(_pitch, _yaw, 0f) * Vector3.back;
+ Vector3 pos = focus + dir * _dist;
vc.transform.position = pos;
- vc.transform.rotation = Quaternion.LookRotation((target.position + Vector3.up * lookHeight) - pos);
+ vc.transform.rotation = Quaternion.LookRotation(focus - pos);
}
PlayerAvatar FindLocal()
@@ -138,6 +178,10 @@ namespace Jankenbots.Prototype
{
for (int i = 0; i < _all.Length; i++)
_all[i].Priority = ((int)m == i) ? 20 : 0;
+
+ // Reset zoom to the mode's default distance on switch (keep the look angle).
+ if (m == Mode.AvatarFollow) _dist = avatarDistance;
+ else if (m == Mode.RobotFollow) _dist = robotDistance;
}
}
}