diff --git a/Assets/Scripts/Components/Concealment/ConcealmentColliderBaker.cs b/Assets/Scripts/Components/Concealment/ConcealmentColliderBaker.cs index 9b3eb3c..1b604f4 100644 --- a/Assets/Scripts/Components/Concealment/ConcealmentColliderBaker.cs +++ b/Assets/Scripts/Components/Concealment/ConcealmentColliderBaker.cs @@ -7,26 +7,21 @@ using UnityEditor.SceneManagement; #endif /// -/// Editor tool that decomposes an environment mesh (typically an open-shell / non-watertight -/// furniture mesh) into a small set of CONVEX MeshColliders via surface clustering, so -/// can treat the furniture as "embeddable" through its -/// convex-collider ClosestPoint path WITHOUT a single convex hull filling the open compartments. +/// Editor tool that decomposes an environment mesh (typically open-shell furniture) into a small set +/// of CONVEX MeshColliders via surface clustering, so can treat the +/// furniture as "embeddable" through its convex-collider ClosestPoint path WITHOUT a single convex +/// hull filling the open compartments. /// -/// Approach (no voxels, no native plugin — "approximate convex decomposition" on the surface): -/// 1. Weld coincident vertices so connectivity is detected across split-normal/UV seams. -/// 2. Grow connected triangle regions into SLABS: a region keeps absorbing edge-adjacent -/// triangles while it stays thinner than along its flattest -/// axis (PCA minor axis). This spans both faces of one board (they are within a board's -/// thickness) but refuses to jump the gap to the next board (that would thicken the region). -/// 3. Convex-hull each slab into its own convex collider. Boards become thin convex slabs; -/// the empty space between boards is covered by no collider, so it stays hollow. +/// Approach (approximate convex decomposition on the surface — no voxels, no native plugin): weld +/// coincident vertices so connectivity survives split-normal/UV seams, grow edge-connected triangle +/// regions into SLABS kept thinner than along their PCA minor axis +/// (spans both faces of one board, refuses to jump the gap to the next), then convex-hull each slab. +/// The empty space between boards is covered by no collider, so it stays hollow. /// /// The colliders are real child GameObjects referencing SAVED hull-mesh assets, so they survive -/// domain reload / play-mode / scene save (unlike an in-memory-only bake). -/// -/// Runtime-inert: the component exists only so can detect it in a -/// collider's parent and skip the furniture's own concave MeshCollider (exactly like a baked -/// ConcealmentVolume does) — the generated convex slabs are the authoritative embedding shape. +/// domain reload / play mode / scene save. Runtime-inert: the component exists only so +/// can detect it in a collider's parent and skip the furniture's own +/// concave MeshCollider — the generated convex slabs are the authoritative embedding shape. /// [DisallowMultipleComponent] public class ConcealmentColliderBaker : MonoBehaviour @@ -240,7 +235,7 @@ public class ConcealmentColliderBaker : MonoBehaviour AssetDatabase.SaveAssets(); EditorUtility.SetDirty(this); - UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(gameObject.scene); + EditorSceneManager.MarkSceneDirty(gameObject.scene); Debug.Log($"[ConcealmentColliderBaker] '{name}': baked {made} convex collider(s)" + (dropped > 0 ? $" (+{dropped} smaller slab(s) dropped by Max Colliders={maxColliders})" : "") + @@ -262,7 +257,7 @@ public class ConcealmentColliderBaker : MonoBehaviour EditorUtility.SetDirty(this); if (gameObject.scene.IsValid()) - UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(gameObject.scene); + EditorSceneManager.MarkSceneDirty(gameObject.scene); } private static void EnsureFolder() diff --git a/Assets/Scripts/Components/Concealment/ConcealmentMeter.cs b/Assets/Scripts/Components/Concealment/ConcealmentMeter.cs index cb66bf0..ca7f733 100644 --- a/Assets/Scripts/Components/Concealment/ConcealmentMeter.cs +++ b/Assets/Scripts/Components/Concealment/ConcealmentMeter.cs @@ -2,27 +2,19 @@ using System.Collections.Generic; using UnityEngine; /// -/// Standalone volumetric concealment meter for the painted character. -/// -/// Each evaluation bakes the character's current (deformed) skinned mesh, voxelizes its -/// SOLID volume, and reports what fraction of that volume sits INSIDE environment colliders -/// (= embedded / hidden) versus exposed. is the headline value, +/// Standalone volumetric concealment meter for the painted character. Each evaluation bakes the +/// current (deformed) skinned mesh, voxelizes its SOLID volume, and reports the fraction embedded +/// inside environment colliders: is the headline value, /// its complement. /// -/// Pipeline: -/// 1. -> mesh in the renderer's LOCAL space, then the -/// player's world scale is divided back out (BakeMesh bakes that scale IN; see PlayerScaler) so the -/// convention world = transform.localToWorldMatrix holds at ANY size — the same fixup SkinnedColliderBaker -/// applies, and the unit-scale mesh MeshPainter borrows from it. -/// 2. Column ray-stabbing in local space: each (x,z) grid column collects the Y where the mesh -/// surface crosses it; parity (odd # of crossings below a voxel center) marks interior voxels. -/// This is the same XZ triangle rasterization MeshPainter.GetIslandMask uses for UV islands. -/// 3. Each interior voxel center -> world; tested against broadphase-filtered environment -/// colliders: convex primitives / convex MeshCollider via ClosestPoint, Terrain via -/// SampleHeight, optional concave MeshCollider via raycast parity. +/// Pipeline: BakeMesh with the player's world scale divided back out (see Evaluate), then column +/// ray-stabbing in local space — odd # of surface crossings below a voxel center marks it interior +/// (same XZ triangle rasterization MeshPainter.GetIslandMask uses) — then each interior voxel center +/// is tested in world space against broadphase-filtered environment colliders: convex via +/// ClosestPoint, Terrain via SampleHeight, optional concave MeshCollider via raycast parity. /// -/// Surface-watertightness caveat: parity assumes a closed mesh. After the Blender merge-by-distance -/// the paintman rig is effectively watertight; small leaks only perturb the percentage slightly. +/// Watertightness caveat: parity assumes a closed mesh. After the Blender merge-by-distance the +/// paintman rig is effectively watertight; small leaks only perturb the percentage slightly. /// [DisallowMultipleComponent] public class ConcealmentMeter : MonoBehaviour @@ -134,12 +126,11 @@ public class ConcealmentMeter : MonoBehaviour targetRenderer.BakeMesh(_baked); - // BakeMesh freezes the bone deformation — which already carries the player's world scale (PlayerScaler) - // — into the mesh. Everything below treats the bake as unit-scale LOCAL geometry and raises it to world - // via targetRenderer.localToWorldMatrix, which re-applies that same scale. Divide it back out first, - // else a scaled player is measured at scale² (voxel world positions, broadphase box AND gizmos), which - // also corrupts EmbeddedPercent. No-op at scale 1; the uniform divide is rotation-independent, which the - // player's uniform scale guarantees. Mirrors SkinnedColliderBaker's identical fixup. + // BakeMesh freezes the bone deformation — which already carries the player's world scale + // (PlayerScaler) — into the mesh, and everything below re-applies that scale by raising the + // bake to world via localToWorldMatrix, so a scaled player would be measured at scale², + // corrupting EmbeddedPercent. Divide it back out first. No-op at scale 1; the player's + // uniform scale makes the divide rotation-independent. Mirrors SkinnedColliderBaker's fixup. GeometryUtil.RescaleToUnitScale(_baked, targetRenderer.transform.lossyScale, _vbuf); // mesh.vertices/.triangles return fresh arrays per call - at 10 evals/s that is steady GC @@ -361,7 +352,7 @@ public class ConcealmentMeter : MonoBehaviour TerrainData td = terr.terrainData; float lx = p.x - tp.x, lz = p.z - tp.z; if (lx < 0f || lz < 0f || lx > td.size.x || lz > td.size.z) continue; - if (p.y <= tp.y + terr.SampleHeight(p)) return true; // below the surface + if (p.y <= tp.y + terr.SampleHeight(p)) return true; } } diff --git a/Assets/Scripts/Components/Concealment/ConcealmentVolume.cs b/Assets/Scripts/Components/Concealment/ConcealmentVolume.cs index e07cf2b..820fe4f 100644 --- a/Assets/Scripts/Components/Concealment/ConcealmentVolume.cs +++ b/Assets/Scripts/Components/Concealment/ConcealmentVolume.cs @@ -4,19 +4,16 @@ using UnityEngine; /// /// Editor-baked solid/enclosed voxel volume for a single (typically non-convex, non-watertight) /// environment mesh. Runtime lookup is an O(1) grid test, so can -/// treat furniture as "embeddable" without per-voxel raycast parity (which is unreliable on the -/// open-shell furniture meshes of asset kits). +/// treat furniture as "embeddable" without per-voxel raycast parity (unreliable on the open-shell +/// furniture meshes of asset kits). /// -/// Bake (editor only): voxelizes the source mesh in LOCAL space and marks a voxel as INSIDE when -/// either classifier fires: -/// * Generalized Winding Number > 0.5 -> robustly captures SOLID material even for open shells. -/// * Exterior flood-fill cannot reach it -> captures fully ENCLOSED air cavities. -/// The union is conservative in the safe direction: an unreachable solid OR an enclosed pocket both -/// count as concealment; open regions (seat surface, under a thin table) correctly read as exposed. +/// The bake marks a voxel INSIDE when either classifier fires: generalized winding number > 0.5 +/// (captures SOLID material, robust for open shells) OR unreachable by exterior flood-fill +/// (captures fully ENCLOSED air cavities). The union errs in the safe direction — solid or enclosed +/// both count as concealment; open regions (seat surface, under a thin table) read as exposed. /// -/// Query is done via ; bake and query share the SAME mesh-local space -/// (verts at bake time, transform.InverseTransformPoint at query time), so position/rotation/scale -/// are handled uniformly. +/// Bake and query () share the SAME mesh-local space, so +/// position/rotation/scale are handled uniformly. /// [DisallowMultipleComponent] public class ConcealmentVolume : MonoBehaviour diff --git a/Assets/Scripts/Components/Controller/FirstPersonController.cs b/Assets/Scripts/Components/Controller/FirstPersonController.cs index 7bd5367..6644c20 100644 --- a/Assets/Scripts/Components/Controller/FirstPersonController.cs +++ b/Assets/Scripts/Components/Controller/FirstPersonController.cs @@ -3,16 +3,10 @@ using Unity.Cinemachine; using Chameleon.Components; /// -/// First-person camera. Drives its OWN dedicated : each frame it snaps -/// to the player's dedicated FirstPersonTarget (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 via : 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. +/// First-person camera on its own dedicated : snaps to the player's eye +/// anchor, applies free mouse-look, and hides the player mesh while active. Driven by +/// via ; WASD stays view-relative automatically +/// because movement reads the brain-driven Main Camera forward. /// [RequireComponent(typeof(CinemachineCamera))] [DisallowMultipleComponent] diff --git a/Assets/Scripts/Components/Controller/FreeCamController.cs b/Assets/Scripts/Components/Controller/FreeCamController.cs index f57e525..37e1a69 100644 --- a/Assets/Scripts/Components/Controller/FreeCamController.cs +++ b/Assets/Scripts/Components/Controller/FreeCamController.cs @@ -5,14 +5,10 @@ using UnityEngine.InputSystem; #endif /// -/// Free-flying spectator camera. Drives its OWN dedicated 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 capsule sized like the -/// player's, so the freecam slides along world geometry instead of clipping through walls/floors. -/// -/// Activation is driven by via : 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. +/// Free-flying spectator camera on its own dedicated : WASD flight relative +/// to the view, Q/E (or Space/Left Ctrl) for down/up, mouse look. A +/// capsule makes it slide along world geometry instead of clipping. Driven by +/// via ; while inactive this component does nothing. /// [RequireComponent(typeof(CharacterController))] [DisallowMultipleComponent] @@ -194,5 +190,4 @@ public class FreeCamController : MonoBehaviour return Input.GetKey(KeyCode.LeftShift); #endif } - } diff --git a/Assets/Scripts/Components/Controller/PlayerPoseController.cs b/Assets/Scripts/Components/Controller/PlayerPoseController.cs index db819ab..ceca823 100644 --- a/Assets/Scripts/Components/Controller/PlayerPoseController.cs +++ b/Assets/Scripts/Components/Controller/PlayerPoseController.cs @@ -8,16 +8,10 @@ using UnityEngine.InputSystem; #endif /// -/// Holds a list of s plus the active index. The pose system owns ALL character -/// animation (the Animator is left empty): standing / airborne / wall / ceiling holds the active pose's -/// idle clip, walking/sprinting on the ground plays its optional walk/run clip (unset = idle held). -/// -/// One key cycles poses (wrapping); sprinting in any state snaps back to the default pose, held -/// continuously so the reset shows up immediately. Clips play through a tiny -/// (two-input mixer on an ) that crossfades every clip switch over -/// . Each pose also carries its own CharacterController capsule (eased, -/// so a low pose can crawl under furniture) and per-pose camera y-offsets (third-person framing and -/// first-person eye). +/// Owns ALL character animation (the Animator is left empty): plays the active 's +/// idle or optional walk/run/fall clip through a two-input mixer that +/// crossfades every clip switch. One key cycles poses, sprinting resets to the default pose; each pose +/// also carries its own eased CharacterController capsule and per-pose camera y-offsets. /// [RequireComponent(typeof(PlayerCharacterController))] [DisallowMultipleComponent] @@ -115,10 +109,9 @@ public class PlayerPoseController : MonoBehaviour private Quaternion[] _restRot; private Vector3[] _restScale; - /// Index of the currently selected pose. public int CurrentIndex => _currentIndex; - /// Locomotion category resolved this frame (which clip the active pose is playing). The owner publishes it for network sync. + /// Locomotion category resolved this frame; the owner publishes it for network sync. public Locomotion CurrentLocomotion => _currentLocomotion; /// @@ -219,7 +212,6 @@ public class PlayerPoseController : MonoBehaviour _firstPersonCameraOffset = EaseCameraOffset(_firstPersonCameraOffset, firstPersonTarget); } - /// Eases a camera y-offset toward its target at ; 0 snaps. private float EaseCameraOffset(float current, float target) { return cameraOffsetSpeed > 0f @@ -261,7 +253,6 @@ public class PlayerPoseController : MonoBehaviour return poses[Mathf.Clamp(_currentIndex, 0, poses.Count - 1)]; } - /// Maps the controller's current movement state to a locomotion category for clip selection. private Locomotion ResolveLocalLocomotion() { if (_player.IsMovingOnGround) @@ -375,7 +366,6 @@ public class PlayerPoseController : MonoBehaviour _posePlaying = true; } - /// Builds the graph (a 2-input mixer feeding the Animator output) lazily if it isn't alive yet. private void EnsureGraph() { if (_graph.IsValid()) @@ -426,7 +416,6 @@ public class PlayerPoseController : MonoBehaviour ApplyBlendWeights(); } - /// Advances an in-flight crossfade and drops the outgoing pose once it has fully faded. private void AdvanceBlend(float dt) { if (!_blending) @@ -446,7 +435,6 @@ public class PlayerPoseController : MonoBehaviour ApplyBlendWeights(); } - /// Pushes the current blend onto the mixer inputs (smoothstepped while a "from" pose exists). private void ApplyBlendWeights() { if (!_mixer.IsValid()) diff --git a/Assets/Scripts/Components/Controller/ThirdPersonOrbitInput.cs b/Assets/Scripts/Components/Controller/ThirdPersonOrbitInput.cs index b85607c..355f9f9 100644 --- a/Assets/Scripts/Components/Controller/ThirdPersonOrbitInput.cs +++ b/Assets/Scripts/Components/Controller/ThirdPersonOrbitInput.cs @@ -2,10 +2,9 @@ using UnityEngine; using Unity.Cinemachine; /// -/// Feeds mouse input into a so the camera -/// orbits the follow target. Mouse movement drives yaw/pitch, the wheel zooms. -/// The orbit behaviour depends on (usually set by the -/// GameModeManager). Attach to the camera rig (the CinemachineOrbitalFollow). +/// Feeds mouse input into a : mouse movement drives yaw/pitch, +/// the wheel zooms, middle-mouse pans (paint mode only). Orbit behaviour depends on +/// (usually set by the GameModeManager). Attach to the camera rig. /// [RequireComponent(typeof(CinemachineOrbitalFollow))] [DisallowMultipleComponent] @@ -346,5 +345,4 @@ public class ThirdPersonOrbitInput : MonoBehaviour // Offset is camera-local (x=right, y=up); the extension translates eye + look-at together -> true pan. _camOffset.Offset = new Vector3(_pan.x, _pan.y, 0f); } - } diff --git a/Assets/Scripts/Components/FrameRateLimiter.cs b/Assets/Scripts/Components/FrameRateLimiter.cs index fbfc23f..f9db245 100644 --- a/Assets/Scripts/Components/FrameRateLimiter.cs +++ b/Assets/Scripts/Components/FrameRateLimiter.cs @@ -32,7 +32,6 @@ public class FrameRateLimiter : MonoBehaviour Apply(); } - /// Applies the current FPS + VSync settings. public void Apply() { if (limitFrameRate) diff --git a/Assets/Scripts/Components/FurShellRenderer.cs b/Assets/Scripts/Components/FurShellRenderer.cs index 7dee4ad..0686901 100644 --- a/Assets/Scripts/Components/FurShellRenderer.cs +++ b/Assets/Scripts/Components/FurShellRenderer.cs @@ -3,17 +3,14 @@ using UnityEngine.Rendering; namespace Chameleon.Components { - // Drives the shell-texturing fur look for FurShell.shader. Draws the source mesh _shellCount times - // via GPU instancing (Graphics.RenderMeshInstanced); every instance is one shell layer and reads its - // index from SV_InstanceID in the shader to pick its extrusion height. Put this next to a MeshFilter - // (static mesh) OR on an animated character's SkinnedMeshRenderer that already carries a - // SkinnedColliderBaker - then the fur borrows that component's per-frame baked pose and follows the - // animation. Assign the FurShell material either way. + // Shell-texturing fur for FurShell.shader: draws the source mesh _shellCount times via GPU instancing + // (Graphics.RenderMeshInstanced); each instance is one shell layer and picks its extrusion height from + // SV_InstanceID in the shader. Put this next to a MeshFilter (static mesh) OR on a skinned avatar whose + // SkinnedColliderBaker provides the per-frame baked pose the fur follows. // - // This component is the SINGLE source of truth for the fur look: every tunable shader parameter is a - // serialized field here and gets pushed via a MaterialPropertyBlock. The material's own inspector - // sliders are therefore only template defaults - they are overridden live. (Use the "Copy Values From - // Material" context menu to pull a material you already tuned into these fields.) + // SINGLE source of truth for the fur look: every tunable shader parameter is a serialized field here, + // pushed via a MaterialPropertyBlock, so the material's own inspector sliders are only template + // defaults. (Use the "Copy Values From Material" context menu to pull in a hand-tuned material.) // // The draw is issued from RenderPipelineManager.beginCameraRendering (not Update/LateUpdate) so it // renders reliably for EVERY camera pass - including the Scene View and edit-mode repaints, where @@ -80,11 +77,9 @@ namespace Chameleon.Components [SerializeField] Vector2 _windDirection = new Vector2(1f, 0f); [Header("X-Ray Reveal")] - // The sibling MeshPainter's body see-through reveal is mirrored onto the fur so the strands glow WITH - // the reveal instead of hiding it under opaque fur (colour/pulse are pulled from the painter, in phase - // with the body). The fur draws up to _shellCount stacked TRANSPARENT shells, so that additive glow - // composites once per overlapping layer and blows out; this scales the per-shell glow down to tame the - // stacking so the total reads at roughly the body's intensity. Lower = subtler. + // The fur draws up to _shellCount stacked TRANSPARENT shells, so the mirrored X-ray glow composites + // once per overlapping layer and blows out; this scales the per-shell glow down so the total reads + // at roughly the body's intensity. Lower = subtler. [SerializeField, Range(0f, 1f)] float _xrayFurStrength = 0.2f; [Header("Rendering")] @@ -265,18 +260,16 @@ namespace Chameleon.Components _mpb.SetFloat(FurObjScaleId, transform.lossyScale.y); // Mirror the sibling painter's live body X-ray reveal onto the fur so the strands glow WITH the - // reveal (see-through camo) instead of hiding it under opaque fur. Same colour/pulse/flash the - // body uses => in phase. ToRenderColor matches the raw MPB upload to Material.SetColor's gamma - // handling (same convention as the fur tint above). Off (alpha 0) when no painter or not revealed - // => the shader's additive term is zero, so the fur is unchanged. + // reveal instead of hiding it under opaque fur. Same colour/pulse the body uses => in phase. + // ToRenderColor matches the raw MPB upload to Material.SetColor's gamma handling (same + // convention as the fur tint above). Off (alpha 0) when no painter or not revealed => the + // shader's additive term is zero, so the fur is unchanged. if (_painter) { _mpb.SetColor(XRayColorId, ToRenderColor(_painter.CurrentXRayColor)); _mpb.SetFloat(XRayPulseSpeedId, _painter.XRayPulseSpeed); _mpb.SetFloat(XRayPulseStrengthId, _painter.XRayPulseStrength); _mpb.SetFloat(XRayPulseShapeId, _painter.XRayPulseShape); - // Per-shell glow scale: the body writes the reveal once (opaque) but the fur stacks many - // transparent shells that each composite it, so scale it down to keep the total near the body. _mpb.SetFloat(XRayFurStrengthId, _xrayFurStrength); } } diff --git a/Assets/Scripts/Components/HeroCutout.cs b/Assets/Scripts/Components/HeroCutout.cs index 43585d1..df22422 100644 --- a/Assets/Scripts/Components/HeroCutout.cs +++ b/Assets/Scripts/Components/HeroCutout.cs @@ -2,16 +2,15 @@ using UnityEngine; using Unity.Cinemachine; /// -/// Drives the world-space "hero cutout": occluder geometry inside a cone from the third-person camera -/// (apex) to the player is clipped away, punching a see-through funnel toward the character. Each late -/// frame this feeds two global shader properties (_HeroCutoutTarget, _HeroCutoutParams) -/// that Custom/HeroCutoutLit reads. +/// World-space "hero cutout": occluder geometry inside a cone from the third-person camera (apex) to +/// the player is clipped away, punching a see-through funnel toward the character. Each late frame this +/// feeds two globals (_HeroCutoutTarget, _HeroCutoutParams) that Custom/HeroCutoutLit reads. /// -/// Arming is deliberately simple: ONE ray from the camera toward the player - blocked = armed, clear -/// view = occluders stay solid. The cut volume lives entirely in the shader: everything between camera -/// and player (within the cone) is cut, geometry behind the player stays solid, and the far end fades -/// across a soft depth band () instead of a hard plane. The cone radius grows -/// with camera distance, so the hole keeps a constant on-screen size at every occluder depth. +/// Arming is deliberately simple: ONE ray camera -> player; blocked = armed, clear view = occluders stay +/// solid. The cut volume lives entirely in the shader: everything between camera and player (within the +/// cone) is cut, geometry behind the player stays solid, and the far end fades across a soft depth band +/// () instead of a hard plane. The cone tapers toward the camera, so the hole +/// keeps a constant on-screen size at every occluder depth. /// /// Active only while the third-person orbit vcam is the live Cinemachine camera; disarming clears the /// global flag (also the zero-initialised default), so scenes without this driver render cutout @@ -193,9 +192,8 @@ public class HeroCutout : MonoBehaviour } /// - /// Lazily resolves the orbit vcam and its Cinemachine Deoccluder (they may live in a scene that - /// loads after this driver). Captures the deoccluder's authored obstacle-avoidance state once so it - /// can be restored later. + /// Lazily resolves the orbit vcam and its Deoccluder (they may live in a scene that loads after this + /// driver); captures the deoccluder's authored obstacle-avoidance state once so it can be restored. /// private void EnsureRefs() { @@ -209,9 +207,9 @@ public class HeroCutout : MonoBehaviour } /// - /// Switches the deoccluder's obstacle avoidance (the camera pull-in) off while this driver replaces - /// it with the cutout, or restores the authored state when the replacement is turned off. Only - /// toggles on transitions, so it never fights a value changed elsewhere mid-frame. + /// Toggles the deoccluder's obstacle avoidance (the camera pull-in) off/back per + /// . Only acts on transitions, so it never fights a value + /// changed elsewhere mid-frame. /// private void ApplyPullInSuppression() { @@ -229,7 +227,6 @@ public class HeroCutout : MonoBehaviour } } - /// Restores the deoccluder's authored pull-in if this driver had suppressed it. private void RestoreDeoccluderPullIn() { if (_pullInSuppressed && _deoccluder) diff --git a/Assets/Scripts/Components/HunterLaserBeam.cs b/Assets/Scripts/Components/HunterLaserBeam.cs index d543c07..caad03e 100644 --- a/Assets/Scripts/Components/HunterLaserBeam.cs +++ b/Assets/Scripts/Components/HunterLaserBeam.cs @@ -2,12 +2,11 @@ using UnityEngine; using UnityEngine.Rendering; /// -/// Momentary world-space laser beam for a hunter's shot. Purely visual (no networking): a LineRenderer drawn -/// from the muzzle to the hit point that fades IN fast then OUT over its lifetime, animating both opacity -/// (via per-vertex colour alpha, read by the Chameleon/Laser shader) and thickness (via widthMultiplier). -/// Self-contained: it builds its own LineRenderer child + an additive material at runtime, so it can be added -/// to a hunter avatar purely in code with no prefab authoring. Call to trigger; the beam -/// recycles itself (disables the renderer) once it expires. +/// Momentary world-space laser beam for a hunter's shot. Purely visual (no networking): a LineRenderer +/// from muzzle to hit point that fades IN fast then OUT, animating opacity (per-vertex colour alpha, read +/// by the Chameleon/Laser shader) and thickness (widthMultiplier). Self-contained: builds its own +/// LineRenderer child + additive material at runtime, so it can be added to an avatar purely in code with +/// no prefab authoring. Call to trigger; the beam disables its renderer once expired. /// [DisallowMultipleComponent] public sealed class HunterLaserBeam : MonoBehaviour diff --git a/Assets/Scripts/Components/MeshPainter/MeshPainter.Backend.cs b/Assets/Scripts/Components/MeshPainter/MeshPainter.Backend.cs index 1c5eb62..36ee9bf 100644 --- a/Assets/Scripts/Components/MeshPainter/MeshPainter.Backend.cs +++ b/Assets/Scripts/Components/MeshPainter/MeshPainter.Backend.cs @@ -20,7 +20,6 @@ public partial class MeshPainter } } - /// Switches the backend at runtime, carrying over all painted maps. private void SwitchBackend(bool toGpu) { var canonical = new Color32[MAP_COUNT][]; @@ -66,7 +65,6 @@ public partial class MeshPainter if (!_backendSeeded) ReseedCanonical(null); } - /// Tears down the active backend and sets up the requested one on the given maps. private void RebuildBackend(bool gpu, Color32[][] canonical) { if (_gpuActive) TeardownGpu(); @@ -75,7 +73,6 @@ public partial class MeshPainter _hasLastScreen = false; } - /// Seed source for a map: the supplied canonical data when valid, else the material seed. private Color32[] ResolveSeed(int mi, Color32[][] canonical) { return (canonical != null && canonical[mi] != null && canonical[mi].Length == textureSize * textureSize) diff --git a/Assets/Scripts/Components/MeshPainter/MeshPainter.Gpu.cs b/Assets/Scripts/Components/MeshPainter/MeshPainter.Gpu.cs index 12de2fd..beb7912 100644 --- a/Assets/Scripts/Components/MeshPainter/MeshPainter.Gpu.cs +++ b/Assets/Scripts/Components/MeshPainter/MeshPainter.Gpu.cs @@ -85,7 +85,7 @@ public partial class MeshPainter if (!DeferInitialSeed) SeedGpuMap(mi, canonical); // deferred: RTs stay unseeded until the snapshot re-seed // Color maps render to the display RT; the MS map exposes its packed final RT. - AssignMapTexture(mi, gm.final != null ? gm.final : gm.display); + AssignMapTexture(mi, gm.final ? gm.final : gm.display); } return true; } @@ -133,7 +133,7 @@ public partial class MeshPainter { SeedGpuMap(mi, canonical); var gm = _gpu[mi]; - AssignMapTexture(mi, gm.final != null ? gm.final : gm.display); + AssignMapTexture(mi, gm.final ? gm.final : gm.display); } } @@ -426,11 +426,11 @@ public partial class MeshPainter Graphics.Blit(gm.display, gm.temp, _dilateMat, 0); var swap = gm.display; gm.display = gm.temp; gm.temp = swap; - if (gm.final == null) AssignMapTexture(mi, gm.display); + if (!gm.final) AssignMapTexture(mi, gm.display); } // MS: pack the work layout (R=metallic, G=smoothness) into the URP final layout. - if (gm.final != null) PackMs(gm); + if (gm.final) PackMs(gm); } GL.sRGBWrite = _prevSRGB; diff --git a/Assets/Scripts/Components/MeshPainter/MeshPainter.Net.cs b/Assets/Scripts/Components/MeshPainter/MeshPainter.Net.cs index d26f897..1dc2e25 100644 --- a/Assets/Scripts/Components/MeshPainter/MeshPainter.Net.cs +++ b/Assets/Scripts/Components/MeshPainter/MeshPainter.Net.cs @@ -73,7 +73,6 @@ public partial class MeshPainter /// True if anyone is listening for replicated stamps (the owner is networked + painting). private bool WantsStampCapture => OnStampCommitted != null; - /// Builds the replicated stamp from the current brush state + resolved placement and raises the event. private void EmitStamp(Vector3 p, float r, Vector3 u, Vector3 v, Vector3 n) { var handler = OnStampCommitted; diff --git a/Assets/Scripts/Components/MeshPainter/MeshPainter.Persistence.cs b/Assets/Scripts/Components/MeshPainter/MeshPainter.Persistence.cs index d150c4e..ef82013 100644 --- a/Assets/Scripts/Components/MeshPainter/MeshPainter.Persistence.cs +++ b/Assets/Scripts/Components/MeshPainter/MeshPainter.Persistence.cs @@ -381,7 +381,6 @@ public partial class MeshPainter return dst; } - /// Bilinear blend of four byte samples (rounded, clamped to 0..255). private static byte Bilerp(byte c00, byte c10, byte c01, byte c11, float fx, float fy) { float top = c00 + (c10 - c00) * fx; diff --git a/Assets/Scripts/Components/MeshPainter/MeshPainter.Pick.cs b/Assets/Scripts/Components/MeshPainter/MeshPainter.Pick.cs index 2f3728f..b2e28c2 100644 --- a/Assets/Scripts/Components/MeshPainter/MeshPainter.Pick.cs +++ b/Assets/Scripts/Components/MeshPainter/MeshPainter.Pick.cs @@ -296,7 +296,7 @@ public partial class MeshPainter for (int i = 0; i < count; i++) { TerrainLayer L = layers[i]; - if (L == null) continue; + if (!L) continue; float w = weights[0, 0, i]; if (w <= 0f) continue; @@ -437,7 +437,7 @@ public partial class MeshPainter private static Color SampleTexel(Texture tex, Vector2 uv, bool srgb) { Texture2D t2d = tex as Texture2D; - if (t2d != null && t2d.isReadable) + if (t2d && t2d.isReadable) return t2d.GetPixelBilinear(uv.x, uv.y); int w = Mathf.Max(1, tex.width), h = Mathf.Max(1, tex.height); diff --git a/Assets/Scripts/Components/MeshPainter/MeshPainter.Setup.cs b/Assets/Scripts/Components/MeshPainter/MeshPainter.Setup.cs index 492cee0..63b8821 100644 --- a/Assets/Scripts/Components/MeshPainter/MeshPainter.Setup.cs +++ b/Assets/Scripts/Components/MeshPainter/MeshPainter.Setup.cs @@ -260,8 +260,8 @@ public partial class MeshPainter { _materialInstance.EnableKeyword("_PARALLAXMAP"); _materialInstance.EnableKeyword("_NORMALMAP"); - if (GetMatFloat(ParallaxId, 0.02f) < 0.02f) - SetMatFloat(ParallaxId, 0.02f); + if (GetMatFloat(ParallaxId, ParallaxBase) < ParallaxBase) + SetMatFloat(ParallaxId, ParallaxBase); } } diff --git a/Assets/Scripts/Components/MeshPainter/MeshPainter.cs b/Assets/Scripts/Components/MeshPainter/MeshPainter.cs index 3549c1d..55a6fb3 100644 --- a/Assets/Scripts/Components/MeshPainter/MeshPainter.cs +++ b/Assets/Scripts/Components/MeshPainter/MeshPainter.cs @@ -7,16 +7,9 @@ using UnityEngine.InputSystem; using Random = UnityEngine.Random; // System (for Action) also defines Random -> keep the UnityEngine one /// -/// Live mesh painting in Play mode. Three paint maps are driven at once (each toggleable): albedo -/// (_BaseMap), an HDRP-style mask map (R = metallic, G = occlusion, A = smoothness; one texture bound -/// to BOTH _MetallicGlossMap and _OcclusionMap) and emission (_EmissionMap). Two backends, switchable -/// live via : GPU shader painting into RenderTextures (fast, default) and CPU -/// rasterization (identical result). Both paint seamlessly across UV seams because the brush is -/// evaluated in 3D local space around the hit point, capturing triangles on both sides of a seam. -/// -/// The brush is a square stamp in the surface tangent plane shaped by a grayscale mask (mask value = -/// per-texel opacity); an empty mask list falls back to a procedural circle (classic round brush). -/// A non-destructive preview () shows size + mask at the hover point. +/// Live Play-mode mesh painting of albedo, an HDRP-style mask map (metallic/occlusion/smoothness) +/// and emission, with live-switchable GPU/CPU backends () that paint identically +/// and seamlessly across UV seams: the masked square brush is evaluated in 3D local space at the hit. /// [DisallowMultipleComponent] public partial class MeshPainter : MonoBehaviour @@ -727,7 +720,6 @@ public partial class MeshPainter : MonoBehaviour // --- Brush preview --- - /// Creates the preview material and the unit quad used by the flat-decal mode. private void SetupPreview() { if (!previewShader) previewShader = Shader.Find("Hidden/MeshPaintPreview"); @@ -860,7 +852,7 @@ public partial class MeshPainter : MonoBehaviour return; // Don't paint through the in-game UI (a HUD click must not also stamp the mesh behind it). - if (pressed && IsPointerOverUi()) + if (pressed && InputUtil.PointerOverUi()) pressed = false; // Brush preview at the hover point - independent of painting AND of the pipette, so the @@ -946,7 +938,6 @@ public partial class MeshPainter : MonoBehaviour _hasLastScreen = true; } - /// Raycasts at a screen position and stamps the masked square brush onto the mesh hit point. private void PaintAtScreen(Vector2 screenPos) { if (!TryComputeBrushPlacement(screenPos, out Vector3 pLocal, out float radiusLocal, @@ -1132,7 +1123,6 @@ public partial class MeshPainter : MonoBehaviour // --- Channels (shared by both backends) --- - /// True if at least one paint channel is enabled (used to gate the per-frame stroke). private bool AnyChannelActive() { for (int mi = 0; mi < MAP_COUNT; mi++) @@ -1224,9 +1214,6 @@ public partial class MeshPainter : MonoBehaviour BrushSizeChanged?.Invoke(); } - /// True if the pointer is currently over an interactive UI element (raycast target). - private static bool IsPointerOverUi() => InputUtil.PointerOverUi(); - private void OnDestroy() { if (_bakedMesh && !_baker) Destroy(_bakedMesh); // _baker owns its mesh; only free our own diff --git a/Assets/Scripts/Components/PlayerScaler.cs b/Assets/Scripts/Components/PlayerScaler.cs index aa523b0..ea20d62 100644 --- a/Assets/Scripts/Components/PlayerScaler.cs +++ b/Assets/Scripts/Components/PlayerScaler.cs @@ -2,17 +2,16 @@ using UnityEngine; /// /// One knob that resizes the whole player. Setting drives transform.localScale -/// — which Unity already propagates to the visible mesh, the CharacterController capsule, the per-pose -/// capsules (written as local values) and the camera target — and additionally rescales the movement, -/// jump, slide and wall tunables on proportionally, so a smaller -/// or larger player still moves at the same body-relative pace instead of zipping or crawling. Without -/// this the speeds (units/second, not geometry) stay put and a half-size player feels twice as fast. +/// (mesh, CharacterController capsule, per-pose capsules and camera target follow automatically) and +/// rescales the movement/jump/slide/wall tunables on +/// proportionally — the speeds are units/second, not geometry, so without this a half-size player feels +/// twice as fast. /// -/// The authored values on the controller ARE the scale-1 design values: tune them normally there. They are -/// captured as the base in Awake and multiplied by Scale at runtime, so the serialized design values are -/// never overwritten on disk. In the editor only localScale is previewed live (geometry is the only -/// visual part); the speed rescale happens on play. The skinned mesh collider scales correctly because -/// divides the player scale back out of its bake. +/// The authored controller values ARE the scale-1 design values: they are captured as the base in Awake +/// and multiplied by Scale at runtime, so the serialized values are never overwritten on disk. In the +/// editor only localScale is previewed live; the speed rescale happens on play. The skinned mesh +/// collider scales correctly because divides the player scale back +/// out of its bake. /// [DisallowMultipleComponent] [RequireComponent(typeof(PlayerCharacterController))] diff --git a/Assets/Scripts/Components/Radar.cs b/Assets/Scripts/Components/Radar.cs index 424081f..eb99292 100644 --- a/Assets/Scripts/Components/Radar.cs +++ b/Assets/Scripts/Components/Radar.cs @@ -3,15 +3,14 @@ using UnityEngine; /// /// Sits on the "Radar" overlay object (a disc rendered by the Radar Camera in front of the main camera). -/// Spawns "Target" blips - clones of the child "Target Template" - each of which continuously points from -/// the reference camera toward a world-space transform (visualising the bearing camera -> target) and then -/// destroys itself after a given lifetime. See for the per-blip behaviour. +/// Spawns "Target" blips - clones of the child "Target Template" - each pointing from the reference +/// camera toward a world-space transform until its lifetime elapses (see ). /// -/// The whole radar overlay is only shown while it is useful: at least one live target, first-person is active, -/// AND the match is in its Seek phase. Instead of snapping on/off it fades: a shared 0..1 factor () -/// drives the alpha of every (transparent) radar renderer - the disc here, the blip points in - -/// and scales the disc from up to 1. Fade-in and fade-out speeds are independent. The overlay -/// camera is switched off only once fully faded out, so hidden blips keep counting down their lifetime meanwhile. +/// Shown only while useful: at least one live target, first-person active, AND the match in its Seek +/// phase. Instead of snapping on/off it fades: a shared 0..1 factor () drives +/// the alpha of every radar renderer - the disc here, the blip points in RadarTarget - and scales the +/// disc from up to 1. The overlay camera is switched off only once fully faded +/// out, so hidden blips keep counting down their lifetime meanwhile. /// [DisallowMultipleComponent] public sealed class Radar : MonoBehaviour diff --git a/Assets/Scripts/Components/RadarTarget.cs b/Assets/Scripts/Components/RadarTarget.cs index 0e8585a..86f0456 100644 --- a/Assets/Scripts/Components/RadarTarget.cs +++ b/Assets/Scripts/Components/RadarTarget.cs @@ -1,17 +1,16 @@ using UnityEngine; /// -/// A single radar blip spawned by . Each frame it orients itself so its forward (+Z, where -/// the "Point" marker sits) faces the direction from the reference camera to a world-space target, visualising -/// the bearing camera -> target. Tracks continuously (the camera turns / the target moves) and self-destructs -/// once its lifetime elapses - or earlier if its target is destroyed. The point's base colour and size encode -/// the two things the flat radar disc can't show via position alone: tints it by -/// how far the target sits ahead of / behind the camera, and its scale shrinks with camera -> target distance. +/// A single radar blip spawned by . Each frame it orients its forward (+Z, where the +/// "Point" marker sits) along the bearing reference camera -> world target, and self-destructs once its +/// lifetime elapses - or earlier if the target is destroyed. The point encodes the two things the flat +/// disc can't show via position alone: tints it by how far the target sits +/// ahead of / behind the camera, and its scale shrinks with camera -> target distance. /// -/// On top of the radar-wide fade, each blip fades in when it spawns and fades out at the end of its life (or when -/// its world target vanishes) via its own factor, so blips appear/disappear smoothly instead -/// of popping. Fade-in and fade-out speeds are independent; the factor persists across so a -/// refreshed blip is pulled back to full opacity rather than flickering to zero first. +/// On top of the radar-wide fade, each blip ramps its own in on spawn and out at end +/// of life (or when its world target vanishes), so blips never pop. The factor persists across +/// so a refreshed blip is pulled back to full opacity rather than flickering to +/// zero first. /// [DisallowMultipleComponent] public sealed class RadarTarget : MonoBehaviour diff --git a/Assets/Scripts/Components/SkinnedColliderBaker.cs b/Assets/Scripts/Components/SkinnedColliderBaker.cs index d61d68d..372901e 100644 --- a/Assets/Scripts/Components/SkinnedColliderBaker.cs +++ b/Assets/Scripts/Components/SkinnedColliderBaker.cs @@ -3,16 +3,15 @@ using UnityEngine; /// /// Sole owner of the animated character's collider bake: keeps a non-convex MeshCollider in sync with -/// the SkinnedMeshRenderer's current pose every frame, independent of any paint/game mode. MeshPainter's -/// own per-frame bake sits behind its PaintingEnabled gate, so the collider would otherwise freeze in -/// player-control / insect mode. When this component is present MeshPainter delegates to it (it stops -/// baking and only borrows for its GPU raster path), so the deformed-pose -/// collider stays fresh for everyone - paint raycasts, physics, future seeker / line-of-sight queries. +/// the SkinnedMeshRenderer's current pose every frame, independent of any paint/game mode (MeshPainter's +/// own bake sits behind its PaintingEnabled gate, so the collider would otherwise freeze in +/// player-control / insect mode). When present, MeshPainter delegates to it and only borrows +/// for its GPU raster path. /// /// Bakes in the renderer's LOCAL space (BakeMesh, scale = 1) - the same convention MeshPainter and /// ConcealmentMeter use (world = transform.localToWorldMatrix). Runs at a negative execution order so -/// the collider is fresh before MeshPainter (and other consumers) read it in the same frame. Owns the -/// baked Mesh (creates + destroys it); MeshPainter never destroys a borrowed mesh. +/// the collider is fresh before consumers read it in the same frame. Owns the baked Mesh (creates + +/// destroys it); MeshPainter never destroys a borrowed mesh. /// [DisallowMultipleComponent] [DefaultExecutionOrder(-50)] @@ -66,13 +65,12 @@ public class SkinnedColliderBaker : MonoBehaviour _skinned.BakeMesh(_baked); // BakeMesh freezes the bone deformation — which already carries this transform's world scale — - // into the mesh. The MeshCollider below then applies the same lossyScale a SECOND time, so a - // scaled player (see PlayerScaler) would get a collider half/double the size of the visible - // mesh. Divide the scale back out so the baked mesh is true scale-1 local geometry (the contract - // this class already documents): the MeshCollider re-applies it exactly once (matching the - // renderer), and MeshPainter / ConcealmentMeter — which raise the baked mesh to world via - // localToWorldMatrix — stay correct at any player scale. No-op (and untouched) at scale 1; the - // uniform divide is rotation-independent, which the player's uniform scale guarantees. + // into the mesh, and the MeshCollider below applies the same lossyScale a SECOND time, so a + // scaled player (see PlayerScaler) would get a collider half/double the visible mesh. Divide the + // scale back out so the baked mesh is true scale-1 local geometry: the MeshCollider re-applies it + // exactly once, and MeshPainter / ConcealmentMeter (world via localToWorldMatrix) stay correct at + // any player scale. No-op at scale 1; the uniform divide is rotation-independent, which the + // player's uniform scale guarantees. GeometryUtil.RescaleToUnitScale(_baked, transform.lossyScale, _vbuf); _collider.sharedMesh = null; // force a physics re-cook of the new geometry diff --git a/Assets/Scripts/Components/SkyboxEnvironmentUpdater.cs b/Assets/Scripts/Components/SkyboxEnvironmentUpdater.cs index d743164..4e44da6 100644 --- a/Assets/Scripts/Components/SkyboxEnvironmentUpdater.cs +++ b/Assets/Scripts/Components/SkyboxEnvironmentUpdater.cs @@ -4,26 +4,22 @@ using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; /// -/// Builds the environment reflection from the current Skybox in memory (renders -/// the sky into a cubemap and assigns it as the Custom reflection), so -/// smooth/metallic materials reflect the sky WITHOUT a baked LightingData.asset. -/// Runs in Edit and Play mode ([ExecuteAlways]); drop it on one object per scene -/// (or a prefab that lives in every scene) and you never have to assign a foreign -/// LightingData again — also works for runtime-loaded / procedural scenes. +/// Builds the environment reflection from the current Skybox in memory (renders the sky into a cubemap +/// assigned as the Custom reflection), so smooth/metallic materials reflect the sky WITHOUT a baked +/// LightingData.asset. Runs in Edit and Play mode ([ExecuteAlways]); drop it on one object per scene — +/// also works for runtime-loaded / procedural scenes. /// -/// Why a rendered cubemap and not : -/// in this project UpdateEnvironment does NOT produce a valid skybox reflection -/// probe (a mirror stays black), so -/// had nothing to scale. Rendering the sky into a Custom reflection cubemap gives a -/// real reflection that intensity can actually drive. +/// Why a rendered cubemap and not : in this project +/// UpdateEnvironment does NOT produce a valid skybox reflection probe (a mirror stays black), so +/// had nothing to scale. Rendering the sky into a +/// Custom reflection cubemap gives a real reflection that intensity can actually drive. /// -/// The skybox AMBIENT probe still comes from UpdateEnvironment — but it only convolves a -/// non-zero probe once the sky has actually been rendered this session. Straight after a -/// domain reload / scene load / play-start the sky hasn't rendered yet, so the probe is -/// ZERO and the Environment intensity multiplier has nothing to scale ("doesn't take -/// effect" until a manual re-enable). Fix: do one synchronous full-screen sky render to -/// "warm" the sky right before UpdateEnvironment — deterministic, no waiting on renders. -/// (RenderToCubemap does NOT warm the ambient path; a normal Camera.Render does.) +/// The skybox AMBIENT probe still comes from UpdateEnvironment — but it only convolves a non-zero probe +/// once the sky has actually been rendered this session. Straight after a domain reload / scene load / +/// play-start the sky hasn't rendered yet, so the probe is ZERO and the Environment intensity multiplier +/// has nothing to scale ("doesn't take effect" until a manual re-enable). Fix: one synchronous +/// full-screen sky render "warms" the sky right before UpdateEnvironment — deterministic, no waiting on +/// renders. (RenderToCubemap does NOT warm the ambient path; a normal Camera.Render does.) /// [ExecuteAlways] [DisallowMultipleComponent] diff --git a/Assets/Scripts/Components/StartMatchTrigger.cs b/Assets/Scripts/Components/StartMatchTrigger.cs index 4f0024b..57210fb 100644 --- a/Assets/Scripts/Components/StartMatchTrigger.cs +++ b/Assets/Scripts/Components/StartMatchTrigger.cs @@ -6,19 +6,16 @@ using UnityEngine; /// AUTHORITY's avatar stands inside it, it asks the 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 (a dedicated server with no host player hands authority to -/// the first client). On the server that client's avatar is a NetworkTransform-driven replica - its -/// CharacterController is disabled () and it carries no Rigidbody, so it -/// is 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()); a transform-moved non-Rigidbody collider -/// against a static trigger fires nothing. That is exactly why 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, so start works for a host and a dedicated server alike. +/// Why: the authority can be a REMOTE client, whose server-side avatar is a NetworkTransform-driven +/// replica - CharacterController disabled (), 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; a passing -/// non-authority avatar is ignored. Runs only while in the lobby phase and stops once it has started a -/// match. The trigger lives in the additively-loaded Lobby and is destroyed when the lobby unloads, so a -/// re-match (back in the lobby) gets a fresh instance with its guard reset. +/// 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. /// [RequireComponent(typeof(Collider))] [DisallowMultipleComponent] diff --git a/Assets/Scripts/Components/UI/DraggablePanel.cs b/Assets/Scripts/Components/UI/DraggablePanel.cs index 481be5c..9a0fc85 100644 --- a/Assets/Scripts/Components/UI/DraggablePanel.cs +++ b/Assets/Scripts/Components/UI/DraggablePanel.cs @@ -3,15 +3,9 @@ using UnityEngine.UI; using UnityEngine.EventSystems; /// -/// Makes a UI panel draggable. Attach to the panel itself: it follows the pointer from any of the panel's -/// non-interactive surface (title bar, padding, labels, background) but ignores presses that begin on a -/// widget - a (toggle, slider, ...) or a (the -/// colour-picker field) - so those keep their own click/drag behaviour. It keeps a slice of the panel on -/// screen and raises it above its siblings while interacting. -/// -/// Works under any canvas scale (it tracks the pointer in the drag area's local space and applies the -/// movement delta) and under any anchor/pivot (the on-screen clamp is derived from the panel's real -/// corners in its parent, not from a fixed top-left assumption). +/// Makes a UI panel draggable from any non-interactive surface; presses that begin on a widget +/// ( or ) keep their own click/drag behaviour. +/// Keeps a slice of the panel on screen and raises it above siblings; canvas-scale/anchor/pivot-agnostic. /// [RequireComponent(typeof(RectTransform))] public class DraggablePanel : MonoBehaviour, IPointerDownHandler, IBeginDragHandler, IDragHandler, IEndDragHandler @@ -40,8 +34,6 @@ public class DraggablePanel : MonoBehaviour, IPointerDownHandler, IBeginDragHand public void OnPointerDown(PointerEventData eventData) { - // Raise the panel when pressing its own surface. (A press that lands on a widget is consumed by - // that widget's own handler and never reaches us, so this rarely needs the guard - but it is cheap.) if (!StartedOnWidget(eventData)) BringToFront(); } diff --git a/Assets/Scripts/Components/UI/HsvColorPicker.cs b/Assets/Scripts/Components/UI/HsvColorPicker.cs index add6353..f56a00f 100644 --- a/Assets/Scripts/Components/UI/HsvColorPicker.cs +++ b/Assets/Scripts/Components/UI/HsvColorPicker.cs @@ -3,17 +3,11 @@ using UnityEngine; using UnityEngine.UI; /// -/// Compact runtime HSV colour picker: a saturation/value square, a hue bar and a live swatch. The -/// square is a pure-hue base with two static gradient overlays on top (white-out -> saturation, -/// black-out -> value), so only the base tint changes per hue - no per-frame texture rebuilds. Hue is -/// never lost while dragging the square (the square only edits S/V, the bar only edits H). Alpha is -/// edited separately by a standalone slider in the HUD, not by this picker; -/// still stores the incoming alpha so round-trips it. -/// -/// The child structure (stable names) comes from the baked HUD prefab. -/// (re)assigns the runtime gradient sprites (which a prefab cannot store) and (re)hooks the drag -/// relays. A leftover alpha bar from a pre-split prefab is hidden by , so no -/// re-bake is required. +/// Runtime HSV colour picker: SV square (pure-hue base + static gradient overlays, so no per-frame +/// texture rebuilds; edits only S/V) plus hue bar (edits only H), so hue is never lost while dragging. +/// Alpha is edited by a separate HUD slider, but stores it so +/// round-trips it. Child structure (stable names) comes from the baked HUD prefab; +/// (re)assigns the runtime gradient sprites (which a prefab cannot store) and (re)hooks the drag relays. /// public class HsvColorPicker : MonoBehaviour { @@ -49,8 +43,7 @@ public class HsvColorPicker : MonoBehaviour /// /// Re-finds the child widgets, (re)assigns the runtime gradient sprites and (re)hooks the drag - /// relays. Idempotent (unsubscribe-then-subscribe), so it is safe whether the structure was just - /// built in code or instantiated from a prefab. Call before . + /// relays. Idempotent. Call before . /// public void Rebind() { @@ -87,7 +80,6 @@ public class HsvColorPicker : MonoBehaviour Refresh(); } - // Idempotently hooks the handler to the surface's drag relay (unsubscribe-then-subscribe). private static void Rehook(Component surface, Action handler) { if (surface.TryGetComponent(out UiPointerSurface pointer)) diff --git a/Assets/Scripts/Components/UI/PaintHud/PaintHud.Library.cs b/Assets/Scripts/Components/UI/PaintHud/PaintHud.Library.cs index 183e5fd..2b3614c 100644 --- a/Assets/Scripts/Components/UI/PaintHud/PaintHud.Library.cs +++ b/Assets/Scripts/Components/UI/PaintHud/PaintHud.Library.cs @@ -5,14 +5,10 @@ using UnityEngine; using UnityEngine.UI; /// -/// Binder for the "Paintings" library panel. The panel's visual layout (Panel_Paintings, the scrollable -/// list, an entry template, and the Save / Confirm dialogs) is authored inside the PaintHudPanel layout -/// prefab - this code only finds those objects by their stable names and wires the behaviour, exactly like -/// does for the brush widgets. The list is populated by cloning the authored -/// Lib_EntryTemplate per saved slot. Save opens a name dialog (an existing name is overwritten, with a -/// warning shown while typing); Load applies the selected slot immediately; Delete goes through a -/// confirmation dialog. Loading replaces the current painting and, when networked, re-broadcasts the -/// loaded state to every other peer. +/// Binder for the "Paintings" library panel, authored in the PaintHudPanel layout prefab and wired here +/// by stable names (like for the brush widgets). The list clones Lib_EntryTemplate +/// per saved slot; Save overwrites by name (warned while typing), Load applies immediately (re-broadcast +/// to peers when networked), Delete confirms. /// public partial class PaintHud { diff --git a/Assets/Scripts/Components/UI/PaintHud/PaintHud.cs b/Assets/Scripts/Components/UI/PaintHud/PaintHud.cs index 5793510..343fa93 100644 --- a/Assets/Scripts/Components/UI/PaintHud/PaintHud.cs +++ b/Assets/Scripts/Components/UI/PaintHud/PaintHud.cs @@ -1,20 +1,14 @@ using System; using UnityEngine; using UnityEngine.UI; -using UnityEngine.EventSystems; #if ENABLE_INPUT_SYSTEM using UnityEngine.InputSystem.UI; #endif /// -/// In-game paint UI. Shows its panels only while the active is -/// (follows the manager's ModeChanged) and binds its widgets directly to the -/// live fields, so edits apply immediately. -/// -/// The layout (independently draggable sub-panels per channel + a global brush card) comes from the -/// authored prefab (). The BINDINGS live in code and are (re)applied at -/// runtime by against stable widget names - the prefab stores only the -/// visual layout, never behaviour. +/// In-game paint UI. Visible only while the active is ; +/// binds its widgets directly to the live fields. The prefab stores only the +/// visual layout; the bindings live in code, (re)applied by against stable widget names. /// [DisallowMultipleComponent] public partial class PaintHud : MonoBehaviour @@ -119,7 +113,6 @@ public partial class PaintHud : MonoBehaviour // (in BindPainter) overwrites them. The reset button restores exactly these. CaptureAuthoredLayout(); - // The paint library now lives inside the authored layout prefab; bind its widgets like the brush panel. BindLibrary(_panel ? _panel.transform : null); // Bind as soon as a painter exists. With networked spawn-on-connect there is none at scene @@ -443,11 +436,8 @@ public partial class PaintHud : MonoBehaviour return s; } - /// - /// Attaches (idempotently) a to the whole panel so it can be dragged - /// from any of its non-interactive surface (header, padding, labels, background); the component - /// ignores presses that start on a widget (toggle, slider, colour picker). - /// + /// Attaches (idempotently) a so the whole panel can be + /// dragged from any non-widget surface. private void MakeDraggable(Transform root, string panelName) { var panel = GeometryUtil.FindDeep(root, panelName) as RectTransform; diff --git a/Assets/Scripts/Components/UI/SliderCurve.cs b/Assets/Scripts/Components/UI/SliderCurve.cs index f219ab7..80defcf 100644 --- a/Assets/Scripts/Components/UI/SliderCurve.cs +++ b/Assets/Scripts/Components/UI/SliderCurve.cs @@ -3,19 +3,10 @@ using UnityEngine; using UnityEngine.UI; /// -/// Reshapes a UI 's response with an so the value runs -/// non-linearly between the slider's authored minValue/maxValue. The curve maps the handle -/// position (x, 0..1 - linear in screen space) to a value fraction (y, 0..1) that is then scaled to the -/// slider's range. The slider's own min/max stays the single source of truth for the range; this only -/// bends the mapping inside it - so dropping the component onto an existing slider needs no range re-entry. -/// -/// is the curved output, positions the handle to show a given -/// output (inverting the curve), and raises the curved value whenever the -/// handle moves - so the component is usable both standalone (wire ) and from -/// code that drives the raw slider (read ). -/// -/// The curve should be monotonic increasing (the default linear is); the inverse used by -/// assumes that. +/// Reshapes a UI 's response with an mapping handle +/// position (x, 0..1) to a value fraction (y, 0..1) scaled to the slider's own minValue/maxValue, which +/// stays the single source of truth for the range. The curve must be monotonic increasing: the inverse +/// used by assumes that. /// [RequireComponent(typeof(Slider))] [DisallowMultipleComponent] diff --git a/Assets/Scripts/Components/UI/UiPointerSurface.cs b/Assets/Scripts/Components/UI/UiPointerSurface.cs index c3bb9d5..883e631 100644 --- a/Assets/Scripts/Components/UI/UiPointerSurface.cs +++ b/Assets/Scripts/Components/UI/UiPointerSurface.cs @@ -3,10 +3,9 @@ using UnityEngine; using UnityEngine.EventSystems; /// -/// Turns a pointer press/drag on a UI graphic into a normalized (x, y) position in -/// [0,1] within its RectTransform (origin bottom-left). Added in code to the colour -/// picker's saturation/value field and hue bar so they react to both clicking and -/// dragging. The graphic on the same object must be a raycast target. +/// Turns a pointer press/drag on a UI graphic into a normalized (x, y) position in [0,1] within its +/// RectTransform (origin bottom-left). Used by the colour picker's SV field and hue bar. The graphic +/// on the same object must be a raycast target. /// [RequireComponent(typeof(RectTransform))] public class UiPointerSurface : MonoBehaviour, IPointerDownHandler, IDragHandler diff --git a/Assets/Scripts/Components/VHACDColliderBaker.cs b/Assets/Scripts/Components/VHACDColliderBaker.cs index 2cc2691..50077f0 100644 --- a/Assets/Scripts/Components/VHACDColliderBaker.cs +++ b/Assets/Scripts/Components/VHACDColliderBaker.cs @@ -9,13 +9,13 @@ using UnityEditor.SceneManagement; /// /// Editor tool that runs V-HACD (approximate convex decomposition via the native /// plugin) on an environment mesh and bakes the resulting convex hulls into SAVED mesh assets + child -/// MeshCollider GameObjects — the exact same output plumbing as , -/// only the decomposition backend differs (V-HACD's voxel solver instead of the surface-clustering +/// MeshCollider GameObjects — the same output plumbing as , only +/// the decomposition backend differs (V-HACD's voxel solver instead of the surface-clustering /// heuristic). Tune the decomposition on the sibling component (its m_parameters), /// then run "Bake VHACD Colliders". /// /// The hulls are real child GameObjects referencing hull-mesh assets under -/// Assets/Generated/VHACDColliders, so they survive domain reload / play-mode / scene save +/// Assets/Generated/VHACDColliders, so they survive domain reload / play mode / scene save /// (unlike an in-memory-only bake). /// [DisallowMultipleComponent] @@ -143,7 +143,7 @@ public class VHACDColliderBaker : MonoBehaviour { string raw; PrefabStage stage = PrefabStageUtility.GetPrefabStage(gameObject); - if (stage != null) + if (stage) { string ap = stage.assetPath; if (ap.StartsWith("Assets/")) ap = ap.Substring("Assets/".Length); diff --git a/Assets/Scripts/GameMode/FirstPersonMode.cs b/Assets/Scripts/GameMode/FirstPersonMode.cs index 982a559..21028da 100644 --- a/Assets/Scripts/GameMode/FirstPersonMode.cs +++ b/Assets/Scripts/GameMode/FirstPersonMode.cs @@ -2,10 +2,9 @@ using UnityEngine; using Unity.Netcode; /// -/// 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 drives its own dedicated CinemachineCamera. +/// First-person mode. The player keeps walking, but the camera moves to the eye (CameraTarget) with +/// free mouse-look and the player's mesh is hidden. Orbit camera input is disabled so it doesn't fight +/// for the cursor; the drives its own dedicated CinemachineCamera. /// public class FirstPersonMode : GameMode { @@ -13,7 +12,6 @@ public class FirstPersonMode : GameMode public override void Enter() { - // Player stays fully controllable; only the view changes. SetPlayerControl(true); SetPainting(false); SetOrbitInputEnabled(false); @@ -113,16 +111,16 @@ public class FirstPersonMode : GameMode for (int i = 0; i < count; i++) { var netObj = _hits[i].collider.GetComponentInParent(); - if (netObj && self && netObj == self) continue; // skip own body + if (netObj && self && netObj == self) continue; - end = _hits[i].point; // first non-self solid hit stops the beam here (wall or avatar) + end = _hits[i].point; if (netObj && netObj.IsPlayerObject) { var setup = netObj.GetComponent(); if (setup && setup.Role == PlayerRole.Hider && !setup.IsFound) match.ReportHit(netObj.OwnerClientId); } - break; // the first non-self hit decides the shot + break; } } diff --git a/Assets/Scripts/GameMode/FreeCamMode.cs b/Assets/Scripts/GameMode/FreeCamMode.cs index 8294c94..95d82e8 100644 --- a/Assets/Scripts/GameMode/FreeCamMode.cs +++ b/Assets/Scripts/GameMode/FreeCamMode.cs @@ -1,8 +1,7 @@ /// -/// Free-fly camera mode. The player is frozen in place, painting is off, and a dedicated -/// freecam ( + its own CinemachineCamera) takes over: fly the -/// camera through the map with WASD/Q/E and the mouse, colliding with world geometry like the -/// player. The orbit camera input is disabled while here so it doesn't fight for the cursor. +/// Free-fly camera mode. The player is frozen and painting is off while a dedicated freecam +/// ( + its own CinemachineCamera) flies through the map, colliding +/// with world geometry. Orbit camera input is disabled so it doesn't fight for the cursor. /// public class FreeCamMode : GameMode { diff --git a/Assets/Scripts/GameMode/GameMode.cs b/Assets/Scripts/GameMode/GameMode.cs index 08effe0..6f17679 100644 --- a/Assets/Scripts/GameMode/GameMode.cs +++ b/Assets/Scripts/GameMode/GameMode.cs @@ -32,7 +32,6 @@ public abstract class GameMode } } - /// Toggles painting on the mesh painter. protected void SetPainting(bool on) { if (Manager.Painter) diff --git a/Assets/Scripts/GameMode/GameModeManager.cs b/Assets/Scripts/GameMode/GameModeManager.cs index bb364ce..21ba78f 100644 --- a/Assets/Scripts/GameMode/GameModeManager.cs +++ b/Assets/Scripts/GameMode/GameModeManager.cs @@ -107,7 +107,7 @@ public class GameModeManager : MonoBehaviour if (!freeCam) freeCam = FindFirstObjectByType(FindObjectsInactive.Include); if (!firstPerson) firstPerson = FindFirstObjectByType(FindObjectsInactive.Include); - BuildModes(); + BuildModes(null); } private void Start() @@ -125,8 +125,6 @@ public class GameModeManager : MonoBehaviour /// Registers the modes for the current context. Without a role (no networked avatar yet, /// or a non-networked scene) the full set is built from whichever rigs are present. With a role the /// set is scoped to that side and ordered so index 0 is its intended start mode. Add new modes here. - private void BuildModes() => BuildModes(null); - private void BuildModes(PlayerRole? role) { _modes.Clear(); @@ -480,7 +478,7 @@ public class GameModeManager : MonoBehaviour for (int i = 0; i < avatars.Count; i++) { var avatar = avatars[i]; - if (!avatar || avatar.IsOwner) continue; // skip the local player's own avatar + if (!avatar || avatar.IsOwner) continue; var col = avatar.BodyCollider; if (col && col.enabled) Physics.IgnoreCollision(capsule, col, ignore); } diff --git a/Assets/Scripts/Helper/GameSettings.cs b/Assets/Scripts/Helper/GameSettings.cs index 6dd8271..e9110b5 100644 --- a/Assets/Scripts/Helper/GameSettings.cs +++ b/Assets/Scripts/Helper/GameSettings.cs @@ -2,14 +2,10 @@ using System; using System.IO; using UnityEngine; -/// Shared JSON-backed store for the player-facing game settings, written human-readable -/// to /Settings.json. Replaces the former per-value PlayerPrefs storage. -/// Loaded lazily on first access and cached in memory (reads are plain field access, so the -/// per-frame Multiplier reads from the camera controllers never touch the disk). Each change is -/// written immediately (settings edits are rare user actions), so a hard-killed build keeps them. -/// -/// The typed domain facades (MouseSensitivity, MasterVolume) own the ranges/clamping and read/write -/// through here; this class stays domain-agnostic and only persists the raw values. +/// Shared JSON-backed store for player-facing game settings (/Settings.json). +/// Loaded lazily and cached in memory, so per-frame reads never touch the disk; each change is +/// written immediately (settings edits are rare), so a hard-killed build keeps them. The typed +/// facades (MouseSensitivity, MasterVolume) own the ranges/clamping; this class only persists raw values. public static class GameSettings { private const string FileName = "Settings.json"; diff --git a/Assets/Scripts/Helper/MasterVolume.cs b/Assets/Scripts/Helper/MasterVolume.cs index e40c772..cfcc61c 100644 --- a/Assets/Scripts/Helper/MasterVolume.cs +++ b/Assets/Scripts/Helper/MasterVolume.cs @@ -12,7 +12,7 @@ public static class MasterVolume /// The default is the level currently authored on the mixer (its exposed-parameter value /// converted back to linear), so a fresh install starts at the mixer's own setting instead of - /// an arbitrary constant. Falls back to FallbackDefault if the mixer/param is unavailable. + /// an arbitrary constant. Falls back to 0.5 if the mixer/param is unavailable. public static float Default => TryReadMixerLinear(out float linear) ? linear : .5f; // Must match the exposed parameter name authored on the AudioMixer asset. diff --git a/Assets/Scripts/Network/Debug/NetworkBootstrap.cs b/Assets/Scripts/Network/Debug/NetworkBootstrap.cs index a894b5f..db5a165 100644 --- a/Assets/Scripts/Network/Debug/NetworkBootstrap.cs +++ b/Assets/Scripts/Network/Debug/NetworkBootstrap.cs @@ -5,12 +5,9 @@ using UnityEngine.InputSystem; #endif /// -/// Dev-only connection harness for local multiplayer testing across the junction-mirror clones: one -/// instance clicks Host and the others click Client (loopback 127.0.0.1 via the configured -/// UnityTransport). Everyone spawns as the neutral Player prefab into the Lobby; roles (Hunter/Hider) -/// are rolled and applied by when the match starts, not here. Hotkeys: -/// H = Host, C = Client, J = Server, K = Shutdown. Purely a testing scaffold - replace with real -/// lobby/menu UI later. Attach to the same GameObject as the NetworkManager. +/// Dev-only connection harness for local multiplayer testing: Host/Client/Server over loopback via the +/// configured UnityTransport (hotkeys H/C/J, K = Shutdown). Roles are rolled by +/// at match start, not here. Testing scaffold only - replace with real lobby/menu UI later. /// [RequireComponent(typeof(NetworkManager))] [DisallowMultipleComponent] @@ -41,7 +38,7 @@ public class NetworkBootstrap : MonoBehaviour if (TypingGuard.IsTyping) return; // don't fire H/C/J/K while typing in a text field #if ENABLE_INPUT_SYSTEM var kb = Keyboard.current; - if (kb == null || _net == null) return; + if (kb == null || !_net) return; if (!_net.IsListening) { if (kb.hKey.wasPressedThisFrame) _net.StartHost(); @@ -57,7 +54,7 @@ public class NetworkBootstrap : MonoBehaviour private void OnGUI() { - if (!showGui || _net == null) return; + if (!showGui || !_net) return; GUILayout.BeginArea(new Rect(10, 10, 220, 160), GUI.skin.box); if (!_net.IsListening) diff --git a/Assets/Scripts/Network/MatchManager.cs b/Assets/Scripts/Network/MatchManager.cs index 8210a95..9cb5b92 100644 --- a/Assets/Scripts/Network/MatchManager.cs +++ b/Assets/Scripts/Network/MatchManager.cs @@ -307,7 +307,7 @@ public class MatchManager : MonoBehaviour // Disable NGO auto-spawn (decided purely by PlayerPrefab != null); keep the reference and spawn // manually once a floored scene exists. Runtime-only mutation - no asset diff. - if (_net != null) + if (_net) { _playerPrefab = _net.NetworkConfig.PlayerPrefab; _net.NetworkConfig.PlayerPrefab = null; @@ -337,7 +337,6 @@ public class MatchManager : MonoBehaviour if (Instance == this) Instance = null; } - // Unhooks the per-session callbacks and named-message handlers (shared by OnDisable and OnNetStopped). private void UnhookSessionCallbacks() { _net.OnClientConnectedCallback -= OnClientConnected; @@ -455,7 +454,7 @@ public class MatchManager : MonoBehaviour private void HookSceneManager() { - if (_hooked || _net == null || _net.SceneManager == null) return; + if (_hooked || !_net || _net.SceneManager == null) return; _net.SceneManager.OnSceneEvent += OnSceneEvent; _hooked = true; } @@ -483,7 +482,7 @@ public class MatchManager : MonoBehaviour // the load/respawn routine covers it (the PlayerObject guard in SpawnFor prevents a double spawn). private void OnClientConnected(ulong clientId) { - if (_net == null || !_net.IsServer) return; + if (!_net || !_net.IsServer) return; // Dedicated-server case: the first client to connect becomes the match authority (a host already // claimed it at server start). Told via named message so it reads Esc locally. @@ -514,7 +513,7 @@ public class MatchManager : MonoBehaviour // dedicated-server match stays controllable. private void OnClientDisconnected(ulong clientId) { - if (_net == null || !_net.IsServer) return; + if (!_net || !_net.IsServer) return; // A hider that leaves mid-match can never be found - drop it so "all hiders found" can still trigger. _pendingHiders.Remove(clientId); @@ -545,7 +544,7 @@ public class MatchManager : MonoBehaviour // the server, so it records its own identity directly; a pure client reports it to the server by message. private void OnLocalClientConnected(ulong clientId) { - if (_net == null || clientId != _net.LocalClientId) return; + if (!_net || clientId != _net.LocalClientId) return; if (_net.IsServer) RegisterIdentity(_net.LocalClientId, PersistentPlayerId.Value); else SendIdentityToServer(PersistentPlayerId.Value); @@ -554,7 +553,7 @@ public class MatchManager : MonoBehaviour // Client -> server: report this install's persistent id so the server can map it to our ephemeral clientId. private void SendIdentityToServer(string persistentId) { - var cmm = _net != null ? _net.CustomMessagingManager : null; + var cmm = _net ? _net.CustomMessagingManager : null; if (cmm == null || string.IsNullOrEmpty(persistentId)) return; // Grows up to the cap if a future id source is longer than the initial buffer (persistent ids are short). using (var writer = new FastBufferWriter(256, Allocator.Temp, 1024)) @@ -568,7 +567,7 @@ public class MatchManager : MonoBehaviour private void OnIdentityMessage(ulong senderClientId, FastBufferReader reader) { reader.ReadValueSafe(out string persistentId); - if (_net == null || !_net.IsServer) return; + if (!_net || !_net.IsServer) return; RegisterIdentity(senderClientId, persistentId); } @@ -605,8 +604,8 @@ public class MatchManager : MonoBehaviour /// Safe to call repeatedly (only acts once, while still in the lobby phase). public void StartMatch() { - if (_net == null || !_net.IsServer) return; - if (Phase != MatchPhase.Lobby) return; // double-start / not-in-lobby guard + if (!_net || !_net.IsServer) return; + if (Phase != MatchPhase.Lobby) return; if (!hunterPrefab || !hiderPrefab) { Debug.LogError("[MatchManager] Role prefabs not assigned - cannot start match.", this); @@ -730,7 +729,6 @@ public class MatchManager : MonoBehaviour } } - // Waits up to 'seconds' of server real time, returning early the moment 'until' (if supplied) is true. private IEnumerator WaitPhase(float seconds, System.Func until) { _skipPhase = false; // drop a stale debug-skip from the previous phase; only this phase may set it @@ -760,7 +758,7 @@ public class MatchManager : MonoBehaviour [ContextMenu("Next Sub-Phase (Debug)")] public void DebugSkipToNextSubPhase() { - if (_net == null) return; + if (!_net) return; if (_net.IsServer) { DebugSkipServer(); return; } if (_isLocalAuthority) SendEmptyNamedMessage(MsgDebugSkip, NetworkManager.ServerClientId); } @@ -768,7 +766,7 @@ public class MatchManager : MonoBehaviour // Server-only. Flags the running phase-wait loop to break early so RunMatchPhases moves to the next sub-phase. private void DebugSkipServer() { - if (_net == null || !_net.IsServer) return; + if (!_net || !_net.IsServer) return; if (_subPhase == MatchSubPhase.None) return; // nothing running to skip _skipPhase = true; Debug.Log($"[MatchManager] DEBUG: skipping the rest of {_subPhase}.", this); @@ -777,7 +775,7 @@ public class MatchManager : MonoBehaviour // Server handler: an authority client asked to skip to the next sub-phase. private void OnDebugSkipMessage(ulong senderClientId, FastBufferReader reader) { - if (_net == null || !_net.IsServer) return; + if (!_net || !_net.IsServer) return; if (!IsMatchAuthority(senderClientId)) return; DebugSkipServer(); } @@ -859,7 +857,7 @@ public class MatchManager : MonoBehaviour // applied paint cutoff - exactly the condition the per-client reveal uses to un-hide the body. private bool LocalClientReady(List ids) { - var sm = _net != null ? _net.SpawnManager : null; + var sm = _net ? _net.SpawnManager : null; if (sm == null) return false; for (int i = 0; i < ids.Count; i++) { @@ -873,7 +871,7 @@ public class MatchManager : MonoBehaviour // Server -> every other client: the target ids + holding phase + generation for this barrier round. private void BroadcastBeginLoad(MatchSubPhase activePhase) { - var cmm = _net != null ? _net.CustomMessagingManager : null; + var cmm = _net ? _net.CustomMessagingManager : null; if (cmm == null) return; _loadRecipients.Clear(); @@ -932,7 +930,7 @@ public class MatchManager : MonoBehaviour // Client -> server: this machine finished loading (echoes the generation so a stale reply is ignored). private void SendReadyToServer(int generation) { - var cmm = _net != null ? _net.CustomMessagingManager : null; + var cmm = _net ? _net.CustomMessagingManager : null; if (cmm == null) return; using (var writer = new FastBufferWriter(sizeof(int), Allocator.Temp)) { @@ -945,19 +943,17 @@ public class MatchManager : MonoBehaviour private void OnReadyMessage(ulong senderClientId, FastBufferReader reader) { reader.ReadValueSafe(out int generation); - if (_net == null || !_net.IsServer) return; + if (!_net || !_net.IsServer) return; if (generation != _loadGeneration) return; // stale ack from a previous barrier round _readyClients.Add(senderClientId); } - // Server-only. Applies the sub-phase to the server's own mirror and broadcasts it to every other client. private void SetPhaseServer(MatchSubPhase phase, float duration) { ApplyPhaseLocal(phase, duration, matchMode); BroadcastPhase(phase, duration, matchMode, 0, true); } - // Sets this machine's sub-phase mirror + countdown + mode (followers poll LocalSubPhase). // Shared by the server's SetPhaseServer and a client's OnPhaseMessage handler. private void ApplyPhaseLocal(MatchSubPhase phase, float duration, MatchMode mode) { @@ -987,7 +983,7 @@ public class MatchManager : MonoBehaviour // (which applied it directly in SetPhaseServer, so it must not double-apply via loopback). private void BroadcastPhase(MatchSubPhase phase, float duration, MatchMode mode, ulong targetClient, bool toAll) { - var cmm = _net != null ? _net.CustomMessagingManager : null; + var cmm = _net ? _net.CustomMessagingManager : null; if (cmm == null) return; using (var writer = new FastBufferWriter(sizeof(byte) + sizeof(float) + sizeof(byte), Allocator.Temp)) { @@ -1023,7 +1019,7 @@ public class MatchManager : MonoBehaviour // locally (no loopback message, mirroring SetPhaseServer). Hiders and spectators never receive a ping. private void BroadcastRadarPings() { - if (_net == null || !_net.IsServer || _pendingHiders.Count == 0) return; + if (!_net || !_net.IsServer || _pendingHiders.Count == 0) return; _radarPingIds.Clear(); foreach (ulong hiderClientId in _pendingHiders) @@ -1067,7 +1063,7 @@ public class MatchManager : MonoBehaviour /// directly. No-op outside the Seek phase. public void RequestManualPing() { - if (_net == null || _subPhase != MatchSubPhase.Seek) return; + if (!_net || _subPhase != MatchSubPhase.Seek) return; if (_net.IsServer) { ServerManualPing(_net.LocalClientId); return; } SendEmptyNamedMessage(MsgManualPing, NetworkManager.ServerClientId); @@ -1077,7 +1073,7 @@ public class MatchManager : MonoBehaviour // ServerManualPing validates it is a live hider during Seek. private void OnManualPingMessage(ulong senderClientId, FastBufferReader reader) { - if (_net == null || !_net.IsServer) return; + if (!_net || !_net.IsServer) return; ServerManualPing(senderClientId); } @@ -1099,7 +1095,7 @@ public class MatchManager : MonoBehaviour private void SpawnRadarBlipsLocal() { var radar = Radar.Instance; - var spawnMgr = _net != null ? _net.SpawnManager : null; + var spawnMgr = _net ? _net.SpawnManager : null; if (!radar || spawnMgr == null) return; for (int i = 0; i < _radarPingIds.Count; i++) if (spawnMgr.SpawnedObjects.TryGetValue(_radarPingIds[i], out var no) && no) @@ -1113,7 +1109,7 @@ public class MatchManager : MonoBehaviour reader.ReadValueSafe(out float lifetime); reader.ReadValueSafe(out int count); var radar = Radar.Instance; - var spawnMgr = _net != null ? _net.SpawnManager : null; + var spawnMgr = _net ? _net.SpawnManager : null; for (int i = 0; i < count; i++) { reader.ReadValueSafe(out ulong netObjId); @@ -1128,7 +1124,7 @@ public class MatchManager : MonoBehaviour /// applies directly. No-op outside the seek phase. public void ReportHit(ulong targetClientId) { - if (_net == null || _subPhase != MatchSubPhase.Seek) return; + if (!_net || _subPhase != MatchSubPhase.Seek) return; if (_net.IsServer) { @@ -1152,7 +1148,7 @@ public class MatchManager : MonoBehaviour private void OnReportHitMessage(ulong senderClientId, FastBufferReader reader) { reader.ReadValueSafe(out ulong targetClientId); - if (_net == null || !_net.IsServer) return; + if (!_net || !_net.IsServer) return; if (_subPhase != MatchSubPhase.Seek) return; if (!_pendingHiders.Contains(targetClientId)) return; // not a live (un-found) hider if (!IsHunter(senderClientId)) return; // only a hunter may shoot @@ -1186,7 +1182,7 @@ public class MatchManager : MonoBehaviour if (hitRevealDelay > 0f) yield return new WaitForSeconds(hitRevealDelay); _revealingHiders.Remove(hiderClientId); - if (_net == null || !_net.IsServer) yield break; + if (!_net || !_net.IsServer) yield break; _pendingHiders.Remove(hiderClientId); @@ -1280,7 +1276,6 @@ public class MatchManager : MonoBehaviour _conversionRecords.Clear(); } - // Server-only. Despawns every live conversion statue (match ending / back to lobby) and drops the list. private void DespawnConversionStatues() { for (int i = 0; i < _statues.Count; i++) @@ -1291,7 +1286,6 @@ public class MatchManager : MonoBehaviour _statues.Clear(); } - // True if the client's current avatar is a Hunter role variant (used to gate shooting server-side). private bool IsHunter(ulong clientId) { if (!_net.ConnectedClients.TryGetValue(clientId, out var c) || !c.PlayerObject) return false; @@ -1303,7 +1297,7 @@ public class MatchManager : MonoBehaviour /// neutral lobby Player (including any spectator-held mid-match joiners). Only acts while in a match. public void EndMatch() { - if (_net == null || !_net.IsServer) return; + if (!_net || !_net.IsServer) return; if (Phase != MatchPhase.InMatch) return; // only endable mid-match // Stop the sub-phase machine if the match is being ended early (authority's Back-to-Lobby) and clear @@ -1329,8 +1323,6 @@ public class MatchManager : MonoBehaviour Debug.Log("[MatchManager] Back in lobby.", this); } - // Unloads one gameplay scene (if loaded), then additively loads the next (shared by the match - // start/end sequences). private IEnumerator SwapScenes(string unloadSceneName, string loadSceneName) { var sm = _net.SceneManager; @@ -1366,9 +1358,8 @@ public class MatchManager : MonoBehaviour _net.SceneManager.OnSceneEvent -= OnEvt; } - // Despawns each connected client's current avatar (if any) and spawns the prefab chosen by - // prefabFor at the given position. Used for both lobby->match (role variants) and match->lobby - // (neutral Player); the latter also gives spectator-held joiners their first avatar. + // Used for both lobby->match (role variants) and match->lobby (neutral Player); the latter also + // gives spectator-held joiners their first avatar. private void RespawnAll(System.Func prefabFor, Vector3 at) { var ids = new List(_net.ConnectedClientsIds); @@ -1437,7 +1428,7 @@ public class MatchManager : MonoBehaviour { get { - if (_net == null) return false; + if (!_net) return false; if (_net.IsServer) return IsMatchAuthority(_net.LocalClientId); return _isLocalAuthority; } @@ -1448,7 +1439,7 @@ public class MatchManager : MonoBehaviour /// the authority or no match is running. public void RequestEndMatch() { - if (_net == null) return; + if (!_net) return; if (_net.IsServer) { EndMatch(); return; } // EndMatch itself guards Phase == InMatch if (_isLocalAuthority) RequestEndMatchFromServer(); } @@ -1480,7 +1471,7 @@ public class MatchManager : MonoBehaviour // so a single dummy byte is written). private void SendEmptyNamedMessage(string messageName, ulong targetClientId) { - var cmm = _net != null ? _net.CustomMessagingManager : null; + var cmm = _net ? _net.CustomMessagingManager : null; if (cmm == null) return; using (var writer = new FastBufferWriter(sizeof(byte), Allocator.Temp)) { @@ -1506,7 +1497,7 @@ public class MatchManager : MonoBehaviour // Server handler: an authority client asked to end the match. private void OnRequestEndMatchMessage(ulong senderClientId, FastBufferReader reader) { - if (_net == null || !_net.IsServer) return; + if (!_net || !_net.IsServer) return; if (!IsMatchAuthority(senderClientId)) return; EndMatch(); // guards Phase == InMatch itself } diff --git a/Assets/Scripts/Network/MatchStatue.cs b/Assets/Scripts/Network/MatchStatue.cs index f5081c0..a32b5c9 100644 --- a/Assets/Scripts/Network/MatchStatue.cs +++ b/Assets/Scripts/Network/MatchStatue.cs @@ -5,25 +5,16 @@ using Chameleon.Components; /// /// A frozen, painted "statue" of a hider at the exact spot / stance / painting it held the moment it was -/// CONVERTED into a hunter (Conversion mode only). captures those on the -/// conversion and spawns one statue per converted hider at the start of the Resolution phase, so every -/// player sees where each hider was caught as a faithful representation (same skinned body, camo painting -/// and fur) - independent of the now-hunter avatar, which has long since moved on. +/// CONVERTED into a hunter (Conversion mode only); captures those on the +/// conversion and spawns one statue per converted hider at Resolution start. Purely presentational (no +/// controller / collision / role) and a dedicated prefab, NOT a avatar - +/// so it never pollutes the scoreboard, per-client visibility matrix or resolution X-ray reveal. /// -/// Purely presentational: no controller, no collision, no gameplay role. It is a dedicated prefab (NOT a -/// avatar), so it never pollutes the scoreboard, per-client visibility -/// matrix or resolution X-ray reveal - those all key off NetworkPlayerSetup, which the statue lacks. -/// -/// Server-owned. Three replicated ingredients rebuild it identically on every client: -/// -/// Transform - the server Instantiates it at the captured pos/rot; the (server-authoritative) -/// NetworkTransform replicates that. -/// Painting - rides the existing snapshot transfer; this object -/// drives the push explicitly (server loads the captured snapshot, then broadcasts it), and PlayerPaintSync -/// suppresses its own auto pull/publish for a statue receiver so the two never race. -/// Stance - a single replicated pose index the pose controller reproduces (network-driven), -/// then freezes. -/// +/// Server-owned; three replicated ingredients rebuild it identically on every client: transform +/// (server-authoritative NetworkTransform), painting (rides the existing +/// snapshot transfer, pushed explicitly by the server - PlayerPaintSync suppresses its own auto +/// pull/publish for a statue receiver so the two never race) and stance (a replicated pose index the +/// pose controller reproduces, then freezes). /// [DisallowMultipleComponent] public class MatchStatue : NetworkBehaviour diff --git a/Assets/Scripts/Network/NetworkPlayerSetup.cs b/Assets/Scripts/Network/NetworkPlayerSetup.cs index 2eb60ac..7d41d84 100644 --- a/Assets/Scripts/Network/NetworkPlayerSetup.cs +++ b/Assets/Scripts/Network/NetworkPlayerSetup.cs @@ -7,7 +7,6 @@ using Chameleon.Components; /// Per-avatar network glue on the spawned player. The local owner binds the scene singletons /// (GameModeManager, cameras) to itself via ; remote /// avatars stop simulating so their transform is driven purely by the NetworkTransform replication. -/// Position-sync milestone: remote avatars still show their mesh/idle pose, just no local control. /// [RequireComponent(typeof(PlayerCharacterController))] [DisallowMultipleComponent] @@ -297,7 +296,6 @@ public class NetworkPlayerSetup : NetworkBehaviour SetBodyRenderers(visible); } - // Toggles every body renderer and records the applied state for the change-guard above. private void SetBodyRenderers(bool visible) { if (_bodyRenderers != null) diff --git a/Assets/Scripts/Network/OnlineSessionManager.cs b/Assets/Scripts/Network/OnlineSessionManager.cs index 61f2530..755ea30 100644 --- a/Assets/Scripts/Network/OnlineSessionManager.cs +++ b/Assets/Scripts/Network/OnlineSessionManager.cs @@ -9,25 +9,17 @@ using Unity.Services.Multiplayer; using UnityEngine; /// -/// Online hosting/joining over Unity Gaming Services so players can connect across the internet -/// without port forwarding. Uses the unified Multiplayer Services "Sessions" API, which bundles -/// Relay (NAT-free messaging via Unity's relay servers) + Lobby (the public server list) + anonymous -/// Authentication behind single calls, and configures + starts the NGO transport itself - so we do -/// NOT call /StartClient here; CreateSessionAsync starts -/// the host and JoinSession* starts the client. +/// Online hosting/joining over Unity Gaming Services (unified Multiplayer "Sessions" API: Relay + Lobby + +/// anonymous Authentication behind single calls). The Sessions API configures and starts the NGO transport +/// itself, so we do NOT call /StartClient here - +/// CreateSessionAsync starts the host, JoinSession* starts the client; the usual NetworkManager callbacks +/// then fire exactly as for loopback hosting, so MatchManager, spawning and authority are unaffected. +/// Note the UGS "session" (the public server-list entry) is distinct from the in-game "Lobby" scene. /// -/// Terminology: the UGS "session" is the entry in the public server list (network layer). It is a -/// different thing from the in-game "Lobby" scene (the pre-match staging area that -/// loads after the connection is up). This component does not touch the match flow at all - the usual -/// NetworkManager callbacks fire exactly as they do for loopback hosting, so MatchManager, spawning and -/// authority are unaffected. -/// -/// This is the logic layer only: state + async host/refresh/join/disconnect calls, surfaced through the -/// / events. The uGUI server browser that drives it lives -/// on its own canvas (); this component carries no UI of its own. -/// Requires the project to be linked to a UGS cloud project (Project Settings > Services) with Relay, -/// Lobby and Authentication (Anonymous) enabled; otherwise sign-in fails with a clear status message. -/// Attach to the same GameObject as the NetworkManager. +/// Logic layer only, surfaced through the / events; the +/// uGUI browser driving it is . Requires the project to be linked to a +/// UGS cloud project (Project Settings > Services) with Relay, Lobby and Authentication (Anonymous) +/// enabled; otherwise sign-in fails with a clear status message. /// [RequireComponent(typeof(NetworkManager))] [DisallowMultipleComponent] @@ -122,7 +114,7 @@ public class OnlineSessionManager : MonoBehaviour Changed?.Invoke(); } - private async System.Threading.Tasks.Task EnsureSignedInAsync() + private async Task EnsureSignedInAsync() { if (UnityServices.State != ServicesInitializationState.Initialized) { @@ -156,7 +148,7 @@ public class OnlineSessionManager : MonoBehaviour // Shared busy/sign-in/error skeleton around one session operation; the body carries the // op-specific statuses and state. ownsConnection = the op starts NGO (host/join), so a timeout that // left the transport half-up must tear it down. - private async System.Threading.Tasks.Task RunSessionOpAsync(string failLabel, bool ownsConnection, Func body) + private async Task RunSessionOpAsync(string failLabel, bool ownsConnection, Func body) { SetBusy(true); try diff --git a/Assets/Scripts/Network/PlayerAnimationSync.cs b/Assets/Scripts/Network/PlayerAnimationSync.cs index 5dbb55c..fed93d9 100644 --- a/Assets/Scripts/Network/PlayerAnimationSync.cs +++ b/Assets/Scripts/Network/PlayerAnimationSync.cs @@ -2,19 +2,11 @@ using Unity.Netcode; using UnityEngine; /// -/// Replicates the visual body state that the disabled-on-remote controller / pose system would -/// otherwise drive: the owner publishes its selected pose index, resolved locomotion category -/// (idle / walk / run / fall) AND the wall-lean / ground-raise / ceiling mesh offset, so a remote -/// avatar shows the same pose, walk/run/fall animation and leans into walls / raises off the ground -/// like the owner — not just a sliding, upright idle. -/// -/// Body position/rotation come from the NetworkTransform; this only carries the small extra state on -/// top. Pose index and locomotion change rarely (a pose cycle, a locomotion transition), so those -/// NetworkVariables send a delta only when the value actually changes. The mesh offset is the visual -/// model's child-local position () — a tiny -/// vector the owner eases continuously; the remote eases onto the replicated value so it doesn't -/// tick-step (the body itself is already interpolated by the NetworkTransform). -/// +/// Replicates the visual body state the disabled-on-remote controller / pose system would otherwise +/// drive: the owner publishes its pose index, resolved locomotion category and the wall-lean / +/// ground-raise mesh offset (), so a remote +/// avatar animates like the owner instead of sliding in an upright idle. Body position/rotation come +/// from the NetworkTransform; the remote eases onto the replicated mesh offset so it doesn't tick-step. /// Runs after the PlayerPoseController (execution order 100) so the owner reads this frame's resolved /// state; remote avatars apply via OnValueChanged / per-frame easing, independent of frame order. /// diff --git a/Assets/Scripts/Network/PlayerPaintSync.cs b/Assets/Scripts/Network/PlayerPaintSync.cs index b39a1ba..81c03bf 100644 --- a/Assets/Scripts/Network/PlayerPaintSync.cs +++ b/Assets/Scripts/Network/PlayerPaintSync.cs @@ -607,7 +607,7 @@ public class PlayerPaintSync : NetworkBehaviour CopyChunkInto(_snapBuffer, offset, chunk); } - /// Copies a received chunk into the reassembly buffer, ignoring out-of-bounds offsets. + // Out-of-bounds offsets are ignored rather than thrown on. private static void CopyChunkInto(byte[] buffer, int offset, byte[] chunk) { if (offset >= 0 && offset + chunk.Length <= buffer.Length) @@ -693,7 +693,6 @@ public class PlayerPaintSync : NetworkBehaviour _uploadBuffer = null; } - /// All connected client ids except the given one (server-side). private ulong[] OtherClientIds(ulong except) { var ids = NetworkManager.ConnectedClientsIds; diff --git a/Assets/Scripts/Network/SpectatorSetup.cs b/Assets/Scripts/Network/SpectatorSetup.cs index 43b6820..dd623d3 100644 --- a/Assets/Scripts/Network/SpectatorSetup.cs +++ b/Assets/Scripts/Network/SpectatorSetup.cs @@ -2,12 +2,10 @@ using Unity.Netcode; using UnityEngine; /// -/// Per-avatar glue for a mid-match SPECTATOR. The spectator object is deliberately empty - no body, no -/// renderer, no collider, no controller - because the free-fly camera is a separate, persistent scene -/// object (in Main) that drives its own rig off Camera.main and needs nothing from this avatar. So the -/// spectator only has to exist as the owner's player object and flip the local mode set to free-cam-only. -/// The owner does that here; remote peers do nothing (there is no body to show or simulate). When the -/// host ends the match, MatchManager respawns this client as a neutral lobby Player. +/// Per-avatar glue for a mid-match SPECTATOR. The spectator object is deliberately empty (no body, +/// renderer, collider or controller) - the free-fly camera is a separate, persistent scene object in +/// Main, so this only has to exist as the owner's player object and flip the local mode set to +/// free-cam-only; remote peers do nothing. MatchManager respawns it as a lobby Player at match end. /// [DisallowMultipleComponent] public class SpectatorSetup : NetworkBehaviour diff --git a/Assets/Scripts/Network/UI/MatchHud.cs b/Assets/Scripts/Network/UI/MatchHud.cs index 9fd068f..954b34a 100644 --- a/Assets/Scripts/Network/UI/MatchHud.cs +++ b/Assets/Scripts/Network/UI/MatchHud.cs @@ -2,15 +2,12 @@ using TMPro; using UnityEngine; /// -/// On-screen match HUD binder: drives the authored MatchHudCanvas widgets from live match state - -/// the current sub-phase + countdown (top centre) and a hunter crosshair during the seek phase. The visual -/// layout lives in the scene; this component creates nothing at runtime, it only binds. Attach it to the -/// MatchHudCanvas and wire the references in the Inspector. -/// -/// Passive overlay: the authored canvas has no and every graphic -/// is raycastTarget=false, so it never consumes pointer events, and its sortingOrder (90) sits below PaintHud's -/// (100) so tool panels always win. There is no change event for the phase/timer, so the values are polled once -/// per frame; the visible strings are only rebuilt when the displayed second / phase actually changes. +/// Binds the authored MatchHudCanvas widgets to live match state (sub-phase + countdowns, role +/// counts, hunter crosshair); creates nothing at runtime. Passive overlay: the canvas has no +/// and every graphic is raycastTarget=false, and its +/// sortingOrder (90) sits below PaintHud's (100) so tool panels always win. There is no change event for +/// the phase/timer, so values are polled per frame; the visible strings are only rebuilt when the +/// displayed second / phase actually changes. /// [DisallowMultipleComponent] public class MatchHud : MonoBehaviour diff --git a/Assets/Scripts/Network/UI/OnlineSessionBrowserUI.cs b/Assets/Scripts/Network/UI/OnlineSessionBrowserUI.cs index e9d32c1..678abf5 100644 --- a/Assets/Scripts/Network/UI/OnlineSessionBrowserUI.cs +++ b/Assets/Scripts/Network/UI/OnlineSessionBrowserUI.cs @@ -8,24 +8,16 @@ using UnityEngine.InputSystem; #endif /// -/// uGUI binding for . Drives the server-browser canvas - Host / Refresh / -/// pick a row / Join / Disconnect plus a status line - and mirrors the manager's state through its -/// / events. -/// Lives on the browser canvas; the session logic stays on the NetworkManager's -/// . +/// uGUI binding for : Host / Refresh / row select / Join / Disconnect +/// plus a status line, mirroring the manager through its / +/// events; the session logic stays on the manager. /// -/// Selection model: Refresh fills the list with clones of (each a clickable row -/// showing name + player count). Clicking a row selects it; Join connects to the selected session. While -/// connected, only Disconnect is interactable. -/// -/// Overlay behaviour: the panel is always shown in Main (not connected). Once a connection is up it hides; -/// in the lobby/map Esc toggles it back as a modal pause overlay (cursor freed, local gameplay suspended via -/// ). The Disconnect button is context-sensitive: in Main (not -/// connected) it is an application "Exit" (quits the build, or stops play mode in the editor) so the player -/// can leave without Alt+F4; on the map it becomes "Back to Lobby" for the match authority (ends the match); -/// everyone else (in the lobby, or a non-authority client on the map) gets a plain disconnect. Only the -/// visual is toggled - this binder stays alive on its (always-active) canvas so it -/// keeps reading Esc while the panel is hidden. +/// The panel is always shown in Main (not connected); once a connection is up it hides, and in the +/// lobby/map Esc toggles it back as a modal pause overlay (cursor freed, gameplay suspended via +/// ). The Disconnect button is context-sensitive: in Main it is an +/// application "Exit"; on the map it becomes "Back to Lobby" for the match authority (ends the match); +/// everyone else gets a plain disconnect. Only the visual is toggled - this binder +/// stays alive on its (always-active) canvas so it keeps reading Esc while the panel is hidden. /// public class OnlineSessionBrowserUI : MonoBehaviour { diff --git a/Assets/Scripts/ScriptableObject/PlayerPose.cs b/Assets/Scripts/ScriptableObject/PlayerPose.cs index 3eca126..22e52fe 100644 --- a/Assets/Scripts/ScriptableObject/PlayerPose.cs +++ b/Assets/Scripts/ScriptableObject/PlayerPose.cs @@ -1,14 +1,9 @@ using UnityEngine; /// -/// A selectable player pose: an idle held while the player stands still, -/// plus optional walk and run clips that the pose system plays itself (the Animator stays empty) while -/// moving on the ground, its own CharacterController capsule dimensions (so a low pose like a ball can -/// crawl under furniture the default standing capsule is too tall for), and a camera-target y-offset that -/// frames the pose without moving the body. -/// -/// Named PlayerPose (not Pose) to avoid shadowing the built-in UnityEngine.Pose struct, mirroring -/// why the controller is PlayerCharacterController and not just "PlayerController". +/// A selectable player pose: idle/walk/run/fall clips (played by the pose system itself; the Animator +/// stays empty), per-pose CharacterController capsule dimensions, and camera-target y-offsets. +/// Named PlayerPose (not Pose) to avoid shadowing the built-in UnityEngine.Pose struct. /// [CreateAssetMenu(fileName = "NewPose", menuName = "Chameleon/Pose")] public class PlayerPose : ScriptableObject