Files
adminandClaude Fable 5 6c03e5d3f6 Refactor Assets/Scripts: trim restating comments, minor structure cleanups
Sweep over 48 files (Editor/ excluded): remove comments that restate the
code, condense class summaries, fix three stale doc statements
(HsvColorPicker alpha bar, PaintHud layoutPrefab, MasterVolume
FallbackDefault). Inline two single-use wrappers (IsPointerOverUi,
BuildModes), drop an unused using, replace a magic literal with the
existing ParallaxBase constant, and convert ~10 UnityEngine.Object null
checks to the implicit-bool convention. No serialized fields, signatures,
RPC/NetworkVariable names, defaults, or #if blocks touched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 08:29:18 +02:00

80 lines
3.6 KiB
C#

using Unity.Netcode;
using UnityEngine;
/// <summary>
/// Sits on the Lobby's "Start Match" trigger volume (a BoxCollider with isTrigger on). When the match
/// AUTHORITY's avatar stands inside it, it asks the <see cref="MatchManager"/> to start the match.
///
/// Detection POLLS a server-side physics overlap every frame instead of relying on OnTriggerEnter.
/// Why: the authority can be a REMOTE client, whose server-side avatar is a NetworkTransform-driven
/// replica - CharacterController disabled (<see cref="NetworkPlayerSetup"/>), no Rigidbody, moved by
/// direct transform writes. Unity only sends OnTriggerEnter when one of the two colliders has a
/// Rigidbody (or is a CharacterController moving via Move()), so a host's own (Move()-driven) avatar
/// tripped the trigger but a remote authority never did. An explicit overlap query finds the avatar's
/// (baked) collider regardless of HOW it is moved.
///
/// Server-gated: pure clients query nothing. Only the authority avatar starts the match, only during
/// the lobby phase, once. The trigger lives in the additively-loaded Lobby and is destroyed when the
/// lobby unloads, so a re-match gets a fresh instance with its guard reset.
/// </summary>
[RequireComponent(typeof(Collider))]
[DisallowMultipleComponent]
public class StartMatchTrigger : MonoBehaviour
{
private Collider _collider;
private readonly Collider[] _overlap = new Collider[32];
private bool _started;
private bool _overlapSaturated; // log-once: a full buffer could silently drop the authority's avatar
private void Awake() => _collider = GetComponent<Collider>();
private void Update()
{
if (_started) return;
var nm = NetworkManager.Singleton;
if (!nm || !nm.IsServer) return; // detection + start are server-side
var match = MatchManager.Instance;
if (!match || match.Phase != MatchManager.MatchPhase.Lobby) return;
int count = OverlapTrigger();
if (count == _overlap.Length && !_overlapSaturated)
{
_overlapSaturated = true;
Debug.LogWarning($"StartMatchTrigger: overlap buffer saturated ({count}); the authority avatar may be missed.", this);
}
for (int i = 0; i < count; i++)
{
var netObj = _overlap[i].GetComponentInParent<NetworkObject>();
if (!netObj || !netObj.IsPlayerObject) continue; // must be a player avatar
if (!match.IsMatchAuthority(netObj.OwnerClientId)) continue; // ...the authority's
_started = true;
match.StartMatch();
return;
}
}
// Overlaps the trigger's own volume. Uses the oriented box for a BoxCollider (the authored shape);
// falls back to the collider's world AABB for any other collider type. Triggers are ignored so the
// query skips this volume itself and reports only solid avatar colliders (e.g. the baked mesh).
private int OverlapTrigger()
{
if (_collider is BoxCollider box)
{
Vector3 center = transform.TransformPoint(box.center);
Vector3 scale = transform.lossyScale;
Vector3 half = new Vector3(
Mathf.Abs(box.size.x * scale.x),
Mathf.Abs(box.size.y * scale.y),
Mathf.Abs(box.size.z * scale.z)) * 0.5f;
return Physics.OverlapBoxNonAlloc(
center, half, _overlap, transform.rotation, ~0, QueryTriggerInteraction.Ignore);
}
Bounds b = _collider.bounds;
return Physics.OverlapBoxNonAlloc(
b.center, b.extents, _overlap, Quaternion.identity, ~0, QueryTriggerInteraction.Ignore);
}
}