Reorganize scripts into subfolders by responsibility
Move scripts under Assets/Scripts/ into Components, GameMode, Helper, Network, Enum, Struct and ScriptableObject folders. Pure file moves (plus folder .meta files); no code changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
382bfb36df
commit
e23fae0c50
126 files changed
+112
No files matched your search
@@ -0,0 +1,184 @@
|
||||
using UnityEngine;
|
||||
using Unity.Cinemachine;
|
||||
|
||||
/// <summary>
|
||||
/// First-person camera. Drives its OWN dedicated <see cref="CinemachineCamera"/>: each frame it snaps
|
||||
/// to the player's dedicated <c>FirstPersonTarget</c> (the eye anchor) for position and
|
||||
/// applies free mouse-look (yaw/pitch) for rotation. While active the player's mesh is hidden so the
|
||||
/// body isn't seen from the inside. Movement stays with the player controller, which reads the
|
||||
/// brain-driven Main Camera forward, so WASD is automatically relative to this view.
|
||||
///
|
||||
/// Activation is driven by <see cref="FirstPersonMode"/> via <see cref="SetActive"/>: it raises the
|
||||
/// vcam priority so the CinemachineBrain blends to it, snaps look to the current view (no jump), hides
|
||||
/// the mesh and locks the cursor. While inactive the vcam rests at a low priority and this component
|
||||
/// does nothing.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(CinemachineCamera))]
|
||||
[DisallowMultipleComponent]
|
||||
public class FirstPersonController : MonoBehaviour
|
||||
{
|
||||
[Header("Target")]
|
||||
[Tooltip("Eye position the camera sits at (the player's FirstPersonTarget). Empty = auto-resolved from the player's \"FirstPersonTarget\" child.")]
|
||||
public Transform cameraTarget;
|
||||
|
||||
[Tooltip("Renderers hidden while first-person is active (so you don't see your own body). Empty = the player's SkinnedMeshRenderers, auto-resolved.")]
|
||||
public Renderer[] hiddenRenderers;
|
||||
|
||||
[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("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;
|
||||
|
||||
CinemachineCamera _vcam;
|
||||
PlayerCharacterController _player; // body whose yaw we pin to the camera while active
|
||||
PlayerPoseController _poseController; // supplies the active pose's per-pose first-person eye offset
|
||||
SmoothedLook _look; // yaw/pitch accumulator + smoothing shared with the freecam
|
||||
bool _resolved;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
_vcam = GetComponent<CinemachineCamera>();
|
||||
if (!_vcam)
|
||||
Debug.LogError("[FirstPersonController] No CinemachineCamera on this GameObject; first-person cannot go live.", this);
|
||||
else
|
||||
_vcam.Priority = inactivePriority;
|
||||
|
||||
ResolveReferences();
|
||||
|
||||
// Dormant until FirstPersonMode enters; nothing reads input or moves the rig meanwhile.
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
/// <summary>Enter/leave first-person. Raises/lowers the vcam priority, hides the player mesh, snaps
|
||||
/// look to the live view and manages the cursor. Called by <see cref="FirstPersonMode"/>.</summary>
|
||||
public void SetActive(bool active)
|
||||
{
|
||||
if (_vcam)
|
||||
_vcam.Priority = active ? activePriority : inactivePriority;
|
||||
|
||||
SetMeshHidden(active);
|
||||
|
||||
if (active)
|
||||
{
|
||||
SyncToCurrentView();
|
||||
InputUtil.SetCursorLocked(true);
|
||||
ApplyTransform(); // place at the eye on the same frame so the blend starts from there
|
||||
if (_player) _player.FaceYaw = _look.DisplayYaw; // body faces the camera from frame one
|
||||
}
|
||||
else
|
||||
{
|
||||
InputUtil.SetCursorLocked(false);
|
||||
if (_player) _player.FaceYaw = null; // restore movement-facing
|
||||
}
|
||||
|
||||
enabled = active;
|
||||
}
|
||||
|
||||
void LateUpdate()
|
||||
{
|
||||
// LateUpdate so the player has already moved the CameraTarget this frame.
|
||||
LookTick();
|
||||
ApplyTransform();
|
||||
if (_player) _player.FaceYaw = _look.DisplayYaw; // keep the body's yaw locked to the camera
|
||||
}
|
||||
|
||||
void LookTick()
|
||||
{
|
||||
Vector2 delta = InputUtil.ReadMouseDelta() * MouseSensitivity.Multiplier;
|
||||
_look.Tick(delta, horizontalSensitivity, verticalSensitivity, invertY, maxPitch, lookSmoothTime);
|
||||
}
|
||||
|
||||
void ApplyTransform()
|
||||
{
|
||||
Quaternion rot = _look.Rotation;
|
||||
if (cameraTarget)
|
||||
{
|
||||
// Sit at the first-person eye anchor, then raise/lower the eye by the active pose's first-person
|
||||
// offset. TransformVector keeps it in the target's local up and scales with the player (so it
|
||||
// tracks PlayerScaler).
|
||||
Vector3 pos = cameraTarget.position;
|
||||
float offset = _poseController ? _poseController.FirstPersonCameraYOffset : 0f;
|
||||
if (offset != 0f)
|
||||
pos += cameraTarget.TransformVector(Vector3.up * offset);
|
||||
transform.SetPositionAndRotation(pos, rot);
|
||||
}
|
||||
else
|
||||
transform.rotation = rot;
|
||||
}
|
||||
|
||||
/// <summary>Seed look from the currently rendered view so going live produces no visible jump.</summary>
|
||||
void SyncToCurrentView()
|
||||
{
|
||||
var cam = Camera.main;
|
||||
if (cam)
|
||||
_look.SyncTo(cam.transform.rotation, maxPitch);
|
||||
else
|
||||
_look.Snap(); // no camera: keep the current angles but still snap the smoothed state
|
||||
}
|
||||
|
||||
void SetMeshHidden(bool hidden)
|
||||
{
|
||||
if (hiddenRenderers == null) return;
|
||||
foreach (var r in hiddenRenderers)
|
||||
if (r) r.enabled = !hidden;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explicitly binds this rig to the locally-owned networked player (overriding the scene-wide
|
||||
/// auto-resolve, which could otherwise latch onto a remote avatar). Called by the GameModeManager
|
||||
/// when the local player spawns.
|
||||
/// </summary>
|
||||
public void SetPlayer(PlayerCharacterController p)
|
||||
{
|
||||
if (!p) return;
|
||||
_player = p;
|
||||
_poseController = p.GetComponent<PlayerPoseController>();
|
||||
Transform t = p.transform.Find("FirstPersonTarget");
|
||||
if (t) cameraTarget = t;
|
||||
hiddenRenderers = p.GetComponentsInChildren<SkinnedMeshRenderer>(true);
|
||||
_resolved = true;
|
||||
}
|
||||
|
||||
void ResolveReferences()
|
||||
{
|
||||
if (_resolved) return;
|
||||
_resolved = true;
|
||||
|
||||
var player = FindFirstObjectByType<PlayerCharacterController>(FindObjectsInactive.Include);
|
||||
if (!player) return;
|
||||
_player = player;
|
||||
|
||||
if (!_poseController)
|
||||
_poseController = player.GetComponent<PlayerPoseController>();
|
||||
|
||||
if (!cameraTarget)
|
||||
{
|
||||
Transform t = player.transform.Find("FirstPersonTarget");
|
||||
if (t) cameraTarget = t;
|
||||
}
|
||||
|
||||
if (hiddenRenderers == null || hiddenRenderers.Length == 0)
|
||||
hiddenRenderers = player.GetComponentsInChildren<SkinnedMeshRenderer>(true);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user