Deduplication pass (44 extractions): - New SmoothedLook: shared smoothed yaw/pitch look tick + view seeding (FirstPersonController, FreeCamController) - New CharacterTeleport: disable-CC-around-teleport idiom (rescue, owner position apply, free-cam sync) - New GeometryUtil: RescaleToUnitScale + TransformBounds (concealment meter/volume, skinned collider baker) - Paint family: canonical map read/write shared between Net and Persistence, snapshot decode, backend stroke dispatch, RebuildBackend, ResolveSeed, chunked RPC send/gate/copy, CamoKnobs/HeightKnobs aggregates, SanitizeFileKey, NewMaskTexture, AdoptAlbedo, FindSlider - Player/modes: GameMode SetPlayerControl/SetPainting/SetOrbitInputEnabled, InputUtil.GameplayKeyPressed, NearestClimbHit, ComputeXRay delegates to ComputeResolutionFreeze, FingerIK TryInPlaneAngle - Match/misc: UnhookSessionCallbacks, SendEmptyNamedMessage, SwapScenes, RunSessionOpAsync, CovarianceEigen, CollectClusterPoints, MatchHud GUIStyle cache, HsvColorPicker rehook, VoxelCenter Documentation pass: remove 6 triplicated doc blocks and 2 restating inline comments left after the previous reduction. Behavior-neutral; hot-path rasterizer loops intentionally untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
194 lines
6.9 KiB
C#
194 lines
6.9 KiB
C#
using UnityEngine;
|
|
using Unity.Cinemachine;
|
|
#if ENABLE_INPUT_SYSTEM
|
|
using UnityEngine.InputSystem;
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Free-flying spectator camera. Drives its OWN dedicated <see cref="CinemachineCamera"/> by moving
|
|
/// this transform with WASD (relative to where it looks), Q/E (or Space/Left Ctrl) for down/up
|
|
/// and the mouse for look. Collision is a <see cref="CharacterController"/> capsule sized like the
|
|
/// player's, so the freecam slides along world geometry instead of clipping through walls/floors.
|
|
///
|
|
/// Activation is driven by <see cref="FreeCamMode"/> via <see cref="SetActive"/>: it raises the vcam
|
|
/// priority so the CinemachineBrain blends to it, snaps the rig to the current view (no jump) and
|
|
/// locks the cursor. While inactive the vcam rests at a low priority and this component does nothing.
|
|
/// </summary>
|
|
[RequireComponent(typeof(CharacterController))]
|
|
[DisallowMultipleComponent]
|
|
public class FreeCamController : MonoBehaviour
|
|
{
|
|
[Header("Flight")]
|
|
[Tooltip("Base flight speed (units/second).")]
|
|
public float moveSpeed = 6f;
|
|
|
|
[Tooltip("Speed multiplier while the sprint key (Left Shift) is held.")]
|
|
public float sprintMultiplier = 3f;
|
|
|
|
[Header("Look")]
|
|
[Tooltip("Horizontal look sensitivity (degrees of yaw per pixel).")]
|
|
public float horizontalSensitivity = 0.2f;
|
|
|
|
[Tooltip("Vertical look sensitivity (degrees of pitch per pixel).")]
|
|
public float verticalSensitivity = 0.15f;
|
|
|
|
[Tooltip("Invert vertical look (mouse up tilts the camera down).")]
|
|
public bool invertY = false;
|
|
|
|
[Tooltip("Pitch is clamped to +/- this angle (degrees) so the camera never flips over.")]
|
|
[Range(1f, 89.9f)]
|
|
public float maxPitch = 89f;
|
|
|
|
[Header("Smoothing")]
|
|
[Tooltip("Acceleration/deceleration smoothing for flight, in seconds. 0 = instant (snappy starts/stops); higher = floatier ramp-up and coast-down.")]
|
|
[Range(0f, 1f)]
|
|
public float moveSmoothTime = 0.12f;
|
|
|
|
[Tooltip("Look smoothing, in seconds. 0 = raw mouse (instant); small values take the edge off jitter without feeling laggy.")]
|
|
[Range(0f, 0.3f)]
|
|
public float lookSmoothTime = 0.04f;
|
|
|
|
[Header("Camera")]
|
|
[Tooltip("Priority the dedicated CinemachineCamera takes while active (must beat the gameplay camera).")]
|
|
public int activePriority = 100;
|
|
|
|
[Tooltip("Priority the dedicated CinemachineCamera rests at while inactive (must lose to the gameplay camera).")]
|
|
public int inactivePriority = -100;
|
|
|
|
CharacterController _controller;
|
|
CinemachineCamera _vcam;
|
|
SmoothedLook _look; // yaw/pitch accumulator + smoothing shared with first person
|
|
Vector3 _moveVelocity; // smoothed world-space flight velocity
|
|
Vector3 _moveVelocityRef;
|
|
bool _ignoredPlayer;
|
|
|
|
void Awake()
|
|
{
|
|
_controller = GetComponent<CharacterController>();
|
|
_vcam = GetComponent<CinemachineCamera>();
|
|
if (!_vcam)
|
|
Debug.LogError("[FreeCamController] No CinemachineCamera on this GameObject; freecam cannot go live.", this);
|
|
else
|
|
_vcam.Priority = inactivePriority;
|
|
|
|
// Dormant until FreeCamMode enters; nothing reads input or moves the rig meanwhile.
|
|
enabled = false;
|
|
}
|
|
|
|
/// <summary>Enter/leave freecam. Raises/lowers the vcam priority, snaps to the live view and
|
|
/// manages the cursor. Called by <see cref="FreeCamMode"/>.</summary>
|
|
public void SetActive(bool active)
|
|
{
|
|
if (_vcam)
|
|
_vcam.Priority = active ? activePriority : inactivePriority;
|
|
|
|
if (active)
|
|
{
|
|
IgnorePlayerCollision();
|
|
SyncToCurrentView();
|
|
InputUtil.SetCursorLocked(true);
|
|
}
|
|
else
|
|
{
|
|
InputUtil.SetCursorLocked(false);
|
|
}
|
|
|
|
enabled = active;
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
LookTick();
|
|
MoveTick();
|
|
}
|
|
|
|
void LookTick()
|
|
{
|
|
Vector2 delta = InputUtil.ReadMouseDelta() * MouseSensitivity.Multiplier;
|
|
_look.Tick(delta, horizontalSensitivity, verticalSensitivity, invertY, maxPitch, lookSmoothTime);
|
|
|
|
// Rotation is unconstrained by the CharacterController; only position is.
|
|
transform.rotation = _look.Rotation;
|
|
}
|
|
|
|
void MoveTick()
|
|
{
|
|
Vector3 dir = ReadMoveDirection();
|
|
if (dir.sqrMagnitude > 1f)
|
|
dir.Normalize();
|
|
|
|
float speed = moveSpeed * (SprintHeld() ? sprintMultiplier : 1f);
|
|
Vector3 targetVelocity = dir * speed;
|
|
|
|
// SmoothDamp eases accel/decel and is already dt-correct; 0 collapses to instant.
|
|
_moveVelocity = moveSmoothTime > 0f
|
|
? Vector3.SmoothDamp(_moveVelocity, targetVelocity, ref _moveVelocityRef, moveSmoothTime)
|
|
: targetVelocity;
|
|
|
|
_controller.Move(_moveVelocity * Time.deltaTime);
|
|
}
|
|
|
|
/// <summary>Place the rig at the currently rendered view so going live produces no visible jump.</summary>
|
|
void SyncToCurrentView()
|
|
{
|
|
var cam = Camera.main;
|
|
if (!cam) return;
|
|
|
|
CharacterTeleport.Move(_controller, transform, cam.transform.position, cam.transform.rotation);
|
|
|
|
_look.SyncTo(transform.rotation, maxPitch);
|
|
_moveVelocity = Vector3.zero;
|
|
_moveVelocityRef = Vector3.zero;
|
|
}
|
|
|
|
/// <summary>Stop the freecam capsule from being blocked by the (frozen) player's capsule.</summary>
|
|
void IgnorePlayerCollision()
|
|
{
|
|
if (_ignoredPlayer) return;
|
|
_ignoredPlayer = true;
|
|
|
|
var player = FindFirstObjectByType<PlayerCharacterController>();
|
|
if (player)
|
|
{
|
|
var playerCC = player.GetComponent<CharacterController>();
|
|
// Only an enabled capsule can take part in a collision-ignore (a spectator's body has its CC
|
|
// disabled, and there is nothing to collide with there anyway).
|
|
if (playerCC && playerCC.enabled)
|
|
Physics.IgnoreCollision(_controller, playerCC, true);
|
|
}
|
|
}
|
|
|
|
Vector3 ReadMoveDirection()
|
|
{
|
|
Vector3 dir = Vector3.zero;
|
|
#if ENABLE_INPUT_SYSTEM
|
|
var kb = Keyboard.current;
|
|
if (kb != null)
|
|
{
|
|
if (kb.wKey.isPressed) dir += transform.forward;
|
|
if (kb.sKey.isPressed) dir -= transform.forward;
|
|
if (kb.dKey.isPressed) dir += transform.right;
|
|
if (kb.aKey.isPressed) dir -= transform.right;
|
|
if (kb.eKey.isPressed || kb.spaceKey.isPressed) dir += Vector3.up;
|
|
if (kb.qKey.isPressed || kb.leftCtrlKey.isPressed) dir -= Vector3.up;
|
|
}
|
|
#else
|
|
dir += transform.forward * Input.GetAxisRaw("Vertical");
|
|
dir += transform.right * Input.GetAxisRaw("Horizontal");
|
|
if (Input.GetKey(KeyCode.E) || Input.GetKey(KeyCode.Space)) dir += Vector3.up;
|
|
if (Input.GetKey(KeyCode.Q) || Input.GetKey(KeyCode.LeftControl)) dir -= Vector3.up;
|
|
#endif
|
|
return dir;
|
|
}
|
|
|
|
bool SprintHeld()
|
|
{
|
|
#if ENABLE_INPUT_SYSTEM
|
|
return Keyboard.current != null && Keyboard.current.leftShiftKey.isPressed;
|
|
#else
|
|
return Input.GetKey(KeyCode.LeftShift);
|
|
#endif
|
|
}
|
|
|
|
}
|