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,118 @@
|
||||
using UnityEngine;
|
||||
using Unity.Netcode;
|
||||
|
||||
/// <summary>
|
||||
/// First-person mode. The player keeps walking (WASD, camera-relative) but the camera moves to the
|
||||
/// player's eye (the CameraTarget) with free mouse-look, and the player's mesh is hidden so the body
|
||||
/// isn't seen from the inside. The orbit camera input is disabled while here so it doesn't fight for
|
||||
/// the cursor; the <see cref="FirstPersonController"/> drives its own dedicated CinemachineCamera.
|
||||
/// </summary>
|
||||
public class FirstPersonMode : GameMode
|
||||
{
|
||||
public override string DisplayName => "First Person";
|
||||
|
||||
public override void Enter()
|
||||
{
|
||||
// Player stays fully controllable; only the view changes.
|
||||
SetPlayerControl(true);
|
||||
SetPainting(false);
|
||||
SetOrbitInputEnabled(false);
|
||||
|
||||
if (Manager.FirstPerson)
|
||||
Manager.FirstPerson.SetActive(true);
|
||||
}
|
||||
|
||||
public override void Exit()
|
||||
{
|
||||
// Modes are Enter-only, so first-person must undo its own camera takeover (and unhide the mesh)
|
||||
// here; the next mode's Enter restores the player/painter/look-mode state.
|
||||
if (Manager.FirstPerson)
|
||||
Manager.FirstPerson.SetActive(false);
|
||||
|
||||
SetOrbitInputEnabled(true);
|
||||
if (Manager.CameraInput)
|
||||
{
|
||||
// Carry the first-person facing into the orbit camera so the next orbit mode (Paint / Player
|
||||
// Control) keeps the current view angle instead of snapping back to the orbit's last-used angle.
|
||||
// Camera.main still shows the first-person view this frame (the brain hasn't blended yet).
|
||||
Manager.CameraInput.SyncToView();
|
||||
}
|
||||
}
|
||||
|
||||
// --- Hunter shooting -------------------------------------------------------------------------------
|
||||
// A hunter in first person shoots a single ray from the camera centre on left-click (with a cooldown),
|
||||
// but only during the SEEK sub-phase. A hit on an un-found hider is reported to the server, which
|
||||
// validates and marks the hider found. Gated on the local avatar actually being a hunter role variant,
|
||||
// so the legacy lobby first-person view never shoots.
|
||||
|
||||
private float _nextShootTime;
|
||||
private static readonly RaycastHit[] _hits = new RaycastHit[32];
|
||||
private static bool _hitsSaturated; // log-once: a saturated buffer can silently drop the true blocker
|
||||
|
||||
// Cached per bound avatar: CanShoot runs every Tick during a match.
|
||||
private PlayerCharacterController _setupSource;
|
||||
private NetworkPlayerSetup _setup;
|
||||
|
||||
public override void Tick()
|
||||
{
|
||||
if (!CanShoot()) return;
|
||||
if (Time.time < _nextShootTime) return;
|
||||
if (!InputUtil.LeftClickedThisFrame()) return;
|
||||
|
||||
_nextShootTime = Time.time + Mathf.Max(0f, MatchManager.Instance.ShootCooldown);
|
||||
TryShoot();
|
||||
}
|
||||
|
||||
private bool CanShoot()
|
||||
{
|
||||
var match = MatchManager.Instance;
|
||||
if (!match || !match.MatchActive || match.LocalSubPhase != MatchManager.MatchSubPhase.Seek) return false;
|
||||
var player = Manager.Player;
|
||||
if (!player || !Manager.FirstPerson) return false;
|
||||
|
||||
if (player != _setupSource)
|
||||
{
|
||||
_setupSource = player;
|
||||
_setup = player.GetComponent<NetworkPlayerSetup>();
|
||||
}
|
||||
return _setup && _setup.ScopedModes && _setup.Role == PlayerRole.Hunter;
|
||||
}
|
||||
|
||||
private void TryShoot()
|
||||
{
|
||||
var match = MatchManager.Instance;
|
||||
Transform cam = Manager.FirstPerson.transform; // FP vcam sits at the eye, facing the look direction
|
||||
float range = Mathf.Max(0f, match.ShootRange);
|
||||
|
||||
int count = Physics.RaycastNonAlloc(cam.position, cam.forward, _hits, range, ~0, QueryTriggerInteraction.Ignore);
|
||||
if (count == _hits.Length && !_hitsSaturated)
|
||||
{
|
||||
_hitsSaturated = true;
|
||||
Debug.LogWarning($"FirstPersonMode: shot raycast buffer saturated ({count}); the nearest blocker may be missed.");
|
||||
}
|
||||
if (count == 0) return;
|
||||
|
||||
// Nearest-first so the shot stops at the first solid thing it meets (a wall blocks; a nearer avatar
|
||||
// shields a farther one). The hunter's own body is skipped so looking down doesn't self-block.
|
||||
System.Array.Sort(_hits, 0, count, RaycastDistanceComparer.Instance);
|
||||
|
||||
NetworkObject self = Manager.Player ? Manager.Player.GetComponent<NetworkObject>() : null;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var netObj = _hits[i].collider.GetComponentInParent<NetworkObject>();
|
||||
if (netObj && self && netObj == self) continue;
|
||||
if (!netObj || !netObj.IsPlayerObject) return; // first solid hit is world geometry -> blocked
|
||||
|
||||
var setup = netObj.GetComponent<NetworkPlayerSetup>();
|
||||
if (setup && setup.Role == PlayerRole.Hider && !setup.IsFound)
|
||||
match.ReportHit(netObj.OwnerClientId);
|
||||
return; // the first non-self hit decides the shot
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class RaycastDistanceComparer : System.Collections.Generic.IComparer<RaycastHit>
|
||||
{
|
||||
public static readonly RaycastDistanceComparer Instance = new RaycastDistanceComparer();
|
||||
public int Compare(RaycastHit a, RaycastHit b) => a.distance.CompareTo(b.distance);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user