Refactor Assets/Scripts: trim restating comments, minor structure cleanups
Sweep over 48 files (Editor/ excluded): remove comments that restate the code, condense class summaries, fix three stale doc statements (HsvColorPicker alpha bar, PaintHud layoutPrefab, MasterVolume FallbackDefault). Inline two single-use wrappers (IsPointerOverUi, BuildModes), drop an unused using, replace a magic literal with the existing ParallaxBase constant, and convert ~10 UnityEngine.Object null checks to the implicit-bool convention. No serialized fields, signatures, RPC/NetworkVariable names, defaults, or #if blocks touched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
eeee5fc2f0
commit
6c03e5d3f6
48 files changed
+289
-481
No files matched your search
@@ -7,26 +7,21 @@ using UnityEditor.SceneManagement;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// <see cref="ConcealmentMeter"/> 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 <see cref="ConcealmentMeter"/> 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 <see cref="maxSlabThickness"/> 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 <see cref="maxSlabThickness"/> 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 <see cref="ConcealmentMeter"/> can detect it in a
|
||||
/// collider's parent and skip the furniture's own concave MeshCollider (exactly like a baked
|
||||
/// <c>ConcealmentVolume</c> does) — the generated convex slabs are the authoritative embedding shape.
|
||||
/// domain reload / play mode / scene save. Runtime-inert: the component exists only so
|
||||
/// <see cref="ConcealmentMeter"/> 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.
|
||||
/// </summary>
|
||||
[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()
|
||||
|
||||
@@ -2,27 +2,19 @@ using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 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. <see cref="EmbeddedPercent"/> 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: <see cref="EmbeddedPercent"/> is the headline value,
|
||||
/// <see cref="visiblePercent"/> its complement.
|
||||
///
|
||||
/// Pipeline:
|
||||
/// 1. <see cref="SkinnedMeshRenderer.BakeMesh(Mesh)"/> -> 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.
|
||||
/// </summary>
|
||||
[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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,19 +4,16 @@ using UnityEngine;
|
||||
/// <summary>
|
||||
/// 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 <see cref="ConcealmentMeter"/> 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 <see cref="ContainsWorld"/>; 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 (<see cref="ContainsWorld"/>) share the SAME mesh-local space, so
|
||||
/// position/rotation/scale are handled uniformly.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class ConcealmentVolume : MonoBehaviour
|
||||
|
||||
@@ -3,16 +3,10 @@ using Unity.Cinemachine;
|
||||
using Chameleon.Components;
|
||||
|
||||
/// <summary>
|
||||
/// First-person camera. Drives its OWN dedicated <see cref="CinemachineCamera"/>: each frame it snaps
|
||||
/// to the player's dedicated <c>FirstPersonTarget</c> (the eye anchor) for position and
|
||||
/// applies free mouse-look (yaw/pitch) for rotation. While active the player's mesh is hidden so the
|
||||
/// body isn't seen from the inside. Movement stays with the player controller, which reads the
|
||||
/// brain-driven Main Camera forward, so WASD is automatically relative to this view.
|
||||
///
|
||||
/// Activation is driven by <see cref="FirstPersonMode"/> via <see cref="SetActive"/>: it raises the
|
||||
/// vcam priority so the CinemachineBrain blends to it, snaps look to the current view (no jump), hides
|
||||
/// the mesh and locks the cursor. While inactive the vcam rests at a low priority and this component
|
||||
/// does nothing.
|
||||
/// First-person camera on its own dedicated <see cref="CinemachineCamera"/>: snaps to the player's eye
|
||||
/// anchor, applies free mouse-look, and hides the player mesh while active. Driven by
|
||||
/// <see cref="FirstPersonMode"/> via <see cref="SetActive"/>; WASD stays view-relative automatically
|
||||
/// because movement reads the brain-driven Main Camera forward.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(CinemachineCamera))]
|
||||
[DisallowMultipleComponent]
|
||||
|
||||
@@ -5,14 +5,10 @@ using UnityEngine.InputSystem;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Free-flying spectator camera. Drives its OWN dedicated <see cref="CinemachineCamera"/> by moving
|
||||
/// this transform with WASD (relative to where it looks), Q/E (or Space/Left Ctrl) for down/up
|
||||
/// and the mouse for look. Collision is a <see cref="CharacterController"/> capsule sized like the
|
||||
/// player's, so the freecam slides along world geometry instead of clipping through walls/floors.
|
||||
///
|
||||
/// Activation is driven by <see cref="FreeCamMode"/> via <see cref="SetActive"/>: it raises the vcam
|
||||
/// priority so the CinemachineBrain blends to it, snaps the rig to the current view (no jump) and
|
||||
/// locks the cursor. While inactive the vcam rests at a low priority and this component does nothing.
|
||||
/// Free-flying spectator camera on its own dedicated <see cref="CinemachineCamera"/>: WASD flight relative
|
||||
/// to the view, Q/E (or Space/Left Ctrl) for down/up, mouse look. A <see cref="CharacterController"/>
|
||||
/// capsule makes it slide along world geometry instead of clipping. Driven by <see cref="FreeCamMode"/>
|
||||
/// via <see cref="SetActive"/>; while inactive this component does nothing.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(CharacterController))]
|
||||
[DisallowMultipleComponent]
|
||||
@@ -194,5 +190,4 @@ public class FreeCamController : MonoBehaviour
|
||||
return Input.GetKey(KeyCode.LeftShift);
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,16 +8,10 @@ using UnityEngine.InputSystem;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Holds a list of <see cref="PlayerPose"/>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 <see cref="PlayableGraph"/>
|
||||
/// (two-input mixer on an <see cref="AnimationPlayableOutput"/>) that crossfades every clip switch over
|
||||
/// <see cref="poseBlendDuration"/>. 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 <see cref="PlayerPose"/>'s
|
||||
/// idle or optional walk/run/fall clip through a two-input mixer <see cref="PlayableGraph"/> 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.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(PlayerCharacterController))]
|
||||
[DisallowMultipleComponent]
|
||||
@@ -115,10 +109,9 @@ public class PlayerPoseController : MonoBehaviour
|
||||
private Quaternion[] _restRot;
|
||||
private Vector3[] _restScale;
|
||||
|
||||
/// <summary>Index of the currently selected pose.</summary>
|
||||
public int CurrentIndex => _currentIndex;
|
||||
|
||||
/// <summary>Locomotion category resolved this frame (which clip the active pose is playing). The owner publishes it for network sync.</summary>
|
||||
/// <summary>Locomotion category resolved this frame; the owner publishes it for network sync.</summary>
|
||||
public Locomotion CurrentLocomotion => _currentLocomotion;
|
||||
|
||||
/// <summary>
|
||||
@@ -219,7 +212,6 @@ public class PlayerPoseController : MonoBehaviour
|
||||
_firstPersonCameraOffset = EaseCameraOffset(_firstPersonCameraOffset, firstPersonTarget);
|
||||
}
|
||||
|
||||
/// <summary>Eases a camera y-offset toward its target at <see cref="cameraOffsetSpeed"/>; 0 snaps.</summary>
|
||||
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)];
|
||||
}
|
||||
|
||||
/// <summary>Maps the controller's current movement state to a locomotion category for clip selection.</summary>
|
||||
private Locomotion ResolveLocalLocomotion()
|
||||
{
|
||||
if (_player.IsMovingOnGround)
|
||||
@@ -375,7 +366,6 @@ public class PlayerPoseController : MonoBehaviour
|
||||
_posePlaying = true;
|
||||
}
|
||||
|
||||
/// <summary>Builds the graph (a 2-input mixer feeding the Animator output) lazily if it isn't alive yet.</summary>
|
||||
private void EnsureGraph()
|
||||
{
|
||||
if (_graph.IsValid())
|
||||
@@ -426,7 +416,6 @@ public class PlayerPoseController : MonoBehaviour
|
||||
ApplyBlendWeights();
|
||||
}
|
||||
|
||||
/// <summary>Advances an in-flight crossfade and drops the outgoing pose once it has fully faded.</summary>
|
||||
private void AdvanceBlend(float dt)
|
||||
{
|
||||
if (!_blending)
|
||||
@@ -446,7 +435,6 @@ public class PlayerPoseController : MonoBehaviour
|
||||
ApplyBlendWeights();
|
||||
}
|
||||
|
||||
/// <summary>Pushes the current blend onto the mixer inputs (smoothstepped while a "from" pose exists).</summary>
|
||||
private void ApplyBlendWeights()
|
||||
{
|
||||
if (!_mixer.IsValid())
|
||||
|
||||
@@ -2,10 +2,9 @@ using UnityEngine;
|
||||
using Unity.Cinemachine;
|
||||
|
||||
/// <summary>
|
||||
/// Feeds mouse input into a <see cref="CinemachineOrbitalFollow"/> so the camera
|
||||
/// orbits the follow target. Mouse movement drives yaw/pitch, the wheel zooms.
|
||||
/// The orbit behaviour depends on <see cref="LookMode"/> (usually set by the
|
||||
/// GameModeManager). Attach to the camera rig (the CinemachineOrbitalFollow).
|
||||
/// Feeds mouse input into a <see cref="CinemachineOrbitalFollow"/>: mouse movement drives yaw/pitch,
|
||||
/// the wheel zooms, middle-mouse pans (paint mode only). Orbit behaviour depends on
|
||||
/// <see cref="LookMode"/> (usually set by the GameModeManager). Attach to the camera rig.
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -32,7 +32,6 @@ public class FrameRateLimiter : MonoBehaviour
|
||||
Apply();
|
||||
}
|
||||
|
||||
/// <summary>Applies the current FPS + VSync settings.</summary>
|
||||
public void Apply()
|
||||
{
|
||||
if (limitFrameRate)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,15 @@ using UnityEngine;
|
||||
using Unity.Cinemachine;
|
||||
|
||||
/// <summary>
|
||||
/// 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 (<c>_HeroCutoutTarget</c>, <c>_HeroCutoutParams</c>)
|
||||
/// that <c>Custom/HeroCutoutLit</c> 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 (<c>_HeroCutoutTarget</c>, <c>_HeroCutoutParams</c>) that <c>Custom/HeroCutoutLit</c> 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 (<see cref="depthFeather"/>) 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
|
||||
/// (<see cref="depthFeather"/>) 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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
private void EnsureRefs()
|
||||
{
|
||||
@@ -209,9 +207,9 @@ public class HeroCutout : MonoBehaviour
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// <see cref="replaceDeoccluderPullIn"/>. Only acts on transitions, so it never fights a value
|
||||
/// changed elsewhere mid-frame.
|
||||
/// </summary>
|
||||
private void ApplyPullInSuppression()
|
||||
{
|
||||
@@ -229,7 +227,6 @@ public class HeroCutout : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Restores the deoccluder's authored pull-in if this driver had suppressed it.</summary>
|
||||
private void RestoreDeoccluderPullIn()
|
||||
{
|
||||
if (_pullInSuppressed && _deoccluder)
|
||||
|
||||
@@ -2,12 +2,11 @@ using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="Fire"/> 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 <see cref="Fire"/> to trigger; the beam disables its renderer once expired.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class HunterLaserBeam : MonoBehaviour
|
||||
|
||||
@@ -20,7 +20,6 @@ public partial class MeshPainter
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Switches the backend at runtime, carrying over all painted maps.</summary>
|
||||
private void SwitchBackend(bool toGpu)
|
||||
{
|
||||
var canonical = new Color32[MAP_COUNT][];
|
||||
@@ -66,7 +65,6 @@ public partial class MeshPainter
|
||||
if (!_backendSeeded) ReseedCanonical(null);
|
||||
}
|
||||
|
||||
/// <summary>Tears down the active backend and sets up the requested one on the given maps.</summary>
|
||||
private void RebuildBackend(bool gpu, Color32[][] canonical)
|
||||
{
|
||||
if (_gpuActive) TeardownGpu();
|
||||
@@ -75,7 +73,6 @@ public partial class MeshPainter
|
||||
_hasLastScreen = false;
|
||||
}
|
||||
|
||||
/// <summary>Seed source for a map: the supplied canonical data when valid, else the material seed.</summary>
|
||||
private Color32[] ResolveSeed(int mi, Color32[][] canonical)
|
||||
{
|
||||
return (canonical != null && canonical[mi] != null && canonical[mi].Length == textureSize * textureSize)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -73,7 +73,6 @@ public partial class MeshPainter
|
||||
/// <summary>True if anyone is listening for replicated stamps (the owner is networked + painting).</summary>
|
||||
private bool WantsStampCapture => OnStampCommitted != null;
|
||||
|
||||
/// <summary>Builds the replicated stamp from the current brush state + resolved placement and raises the event.</summary>
|
||||
private void EmitStamp(Vector3 p, float r, Vector3 u, Vector3 v, Vector3 n)
|
||||
{
|
||||
var handler = OnStampCommitted;
|
||||
|
||||
@@ -381,7 +381,6 @@ public partial class MeshPainter
|
||||
return dst;
|
||||
}
|
||||
|
||||
/// <summary>Bilinear blend of four byte samples (rounded, clamped to 0..255).</summary>
|
||||
private static byte Bilerp(byte c00, byte c10, byte c01, byte c11, float fx, float fy)
|
||||
{
|
||||
float top = c00 + (c10 - c00) * fx;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,16 +7,9 @@ using UnityEngine.InputSystem;
|
||||
using Random = UnityEngine.Random; // System (for Action) also defines Random -> keep the UnityEngine one
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="useGpu"/>: 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 (<see cref="previewMode"/>) 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 (<see cref="useGpu"/>) that paint identically
|
||||
/// and seamlessly across UV seams: the masked square brush is evaluated in 3D local space at the hit.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public partial class MeshPainter : MonoBehaviour
|
||||
@@ -727,7 +720,6 @@ public partial class MeshPainter : MonoBehaviour
|
||||
|
||||
// --- Brush preview ---
|
||||
|
||||
/// <summary>Creates the preview material and the unit quad used by the flat-decal mode.</summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>Raycasts at a screen position and stamps the masked square brush onto the mesh hit point.</summary>
|
||||
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) ---
|
||||
|
||||
/// <summary>True if at least one paint channel is enabled (used to gate the per-frame stroke).</summary>
|
||||
private bool AnyChannelActive()
|
||||
{
|
||||
for (int mi = 0; mi < MAP_COUNT; mi++)
|
||||
@@ -1224,9 +1214,6 @@ public partial class MeshPainter : MonoBehaviour
|
||||
BrushSizeChanged?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>True if the pointer is currently over an interactive UI element (raycast target).</summary>
|
||||
private static bool IsPointerOverUi() => InputUtil.PointerOverUi();
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_bakedMesh && !_baker) Destroy(_bakedMesh); // _baker owns its mesh; only free our own
|
||||
|
||||
@@ -2,17 +2,16 @@ using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// One knob that resizes the whole player. Setting <see cref="Scale"/> drives <c>transform.localScale</c>
|
||||
/// — 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 <see cref="PlayerCharacterController"/> 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 <see cref="PlayerCharacterController"/>
|
||||
/// 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 <c>localScale</c> is previewed live (geometry is the only
|
||||
/// visual part); the speed rescale happens on play. The skinned mesh collider scales correctly because
|
||||
/// <see cref="SkinnedColliderBaker"/> 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 <c>localScale</c> is previewed live; the speed rescale happens on play. The skinned mesh
|
||||
/// collider scales correctly because <see cref="SkinnedColliderBaker"/> divides the player scale back
|
||||
/// out of its bake.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
[RequireComponent(typeof(PlayerCharacterController))]
|
||||
|
||||
@@ -3,15 +3,14 @@ using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="RadarTarget"/> 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 <see cref="RadarTarget"/>).
|
||||
///
|
||||
/// 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 (<see cref="CurrentFade"/>)
|
||||
/// drives the alpha of every (transparent) radar renderer - the disc here, the blip points in <see cref="RadarTarget"/> -
|
||||
/// and scales the disc from <see cref="hiddenScale"/> 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 (<see cref="CurrentFade"/>) drives
|
||||
/// the alpha of every radar renderer - the disc here, the blip points in RadarTarget - and scales the
|
||||
/// disc from <see cref="hiddenScale"/> up to 1. The overlay camera is switched off only once fully faded
|
||||
/// out, so hidden blips keep counting down their lifetime meanwhile.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class Radar : MonoBehaviour
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// A single radar blip spawned by <see cref="Radar"/>. 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: <see cref="forwardGradient"/> 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 <see cref="Radar"/>. 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: <see cref="forwardGradient"/> 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 <see cref="_fade"/> factor, so blips appear/disappear smoothly instead
|
||||
/// of popping. Fade-in and fade-out speeds are independent; the factor persists across <see cref="Refresh"/> 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 <see cref="_fade"/> in on spawn and out at end
|
||||
/// of life (or when its world target vanishes), so blips never pop. The factor persists across
|
||||
/// <see cref="Refresh"/> so a refreshed blip is pulled back to full opacity rather than flickering to
|
||||
/// zero first.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class RadarTarget : MonoBehaviour
|
||||
|
||||
@@ -3,16 +3,15 @@ using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="BakedMesh"/> 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
|
||||
/// <see cref="BakedMesh"/> 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.
|
||||
/// </summary>
|
||||
[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
|
||||
|
||||
@@ -4,26 +4,22 @@ using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="DynamicGI.UpdateEnvironment"/>:
|
||||
/// in this project UpdateEnvironment does NOT produce a valid skybox reflection
|
||||
/// probe (a mirror stays black), so <see cref="RenderSettings.reflectionIntensity"/>
|
||||
/// 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 <see cref="DynamicGI.UpdateEnvironment"/>: in this project
|
||||
/// UpdateEnvironment does NOT produce a valid skybox reflection probe (a mirror stays black), so
|
||||
/// <see cref="RenderSettings.reflectionIntensity"/> 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.)
|
||||
/// </summary>
|
||||
[ExecuteAlways]
|
||||
[DisallowMultipleComponent]
|
||||
|
||||
@@ -6,19 +6,16 @@ using UnityEngine;
|
||||
/// AUTHORITY's avatar stands inside it, it asks the <see cref="MatchManager"/> to start the match.
|
||||
///
|
||||
/// Detection POLLS a server-side physics overlap every frame instead of relying on OnTriggerEnter.
|
||||
/// Why: the authority can be a REMOTE client (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 (<see cref="NetworkPlayerSetup"/>) 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 (<see cref="NetworkPlayerSetup"/>), no Rigidbody, moved by
|
||||
/// direct transform writes. Unity only sends OnTriggerEnter when one of the two colliders has a
|
||||
/// Rigidbody (or is a CharacterController moving via Move()), so a host's own (Move()-driven) avatar
|
||||
/// tripped the trigger but a remote authority never did. An explicit overlap query finds the avatar's
|
||||
/// (baked) collider regardless of HOW it is moved.
|
||||
///
|
||||
/// Server-gated: pure clients query nothing. Only the authority avatar starts the match; 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.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Collider))]
|
||||
[DisallowMultipleComponent]
|
||||
|
||||
@@ -3,15 +3,9 @@ using UnityEngine.UI;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="Selectable"/> (toggle, slider, ...) or a <see cref="UiPointerSurface"/> (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
|
||||
/// (<see cref="Selectable"/> or <see cref="UiPointerSurface"/>) keep their own click/drag behaviour.
|
||||
/// Keeps a slice of the panel on screen and raises it above siblings; canvas-scale/anchor/pivot-agnostic.
|
||||
/// </summary>
|
||||
[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();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,17 +3,11 @@ using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
/// <summary>
|
||||
/// 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; <see cref="SetColor"/>
|
||||
/// still stores the incoming alpha so <see cref="Value"/> round-trips it.
|
||||
///
|
||||
/// The child structure (stable names) comes from the baked HUD prefab. <see cref="Rebind"/>
|
||||
/// (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 <see cref="Rebind"/>, 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 <see cref="SetColor"/> stores it so <see cref="Value"/>
|
||||
/// round-trips it. Child structure (stable names) comes from the baked HUD prefab; <see cref="Rebind"/>
|
||||
/// (re)assigns the runtime gradient sprites (which a prefab cannot store) and (re)hooks the drag relays.
|
||||
/// </summary>
|
||||
public class HsvColorPicker : MonoBehaviour
|
||||
{
|
||||
@@ -49,8 +43,7 @@ public class HsvColorPicker : MonoBehaviour
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="SetColor"/>.
|
||||
/// relays. Idempotent. Call before <see cref="SetColor"/>.
|
||||
/// </summary>
|
||||
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<Vector2> handler)
|
||||
{
|
||||
if (surface.TryGetComponent(out UiPointerSurface pointer))
|
||||
|
||||
@@ -5,14 +5,10 @@ using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// <see cref="WirePanel"/> 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 <see cref="WirePanel"/> 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.
|
||||
/// </summary>
|
||||
public partial class PaintHud
|
||||
{
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.EventSystems;
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
using UnityEngine.InputSystem.UI;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// In-game paint UI. Shows its panels only while the active <see cref="GameMode"/> is
|
||||
/// <see cref="PaintMode"/> (follows the manager's ModeChanged) and binds its widgets directly to the
|
||||
/// live <see cref="MeshPainter"/> fields, so edits apply immediately.
|
||||
///
|
||||
/// The layout (independently draggable sub-panels per channel + a global brush card) comes from the
|
||||
/// authored prefab (<see cref="layoutPrefab"/>). The BINDINGS live in code and are (re)applied at
|
||||
/// runtime by <see cref="WirePanel"/> against stable widget names - the prefab stores only the
|
||||
/// visual layout, never behaviour.
|
||||
/// In-game paint UI. Visible only while the active <see cref="GameMode"/> is <see cref="PaintMode"/>;
|
||||
/// binds its widgets directly to the live <see cref="MeshPainter"/> fields. The prefab stores only the
|
||||
/// visual layout; the bindings live in code, (re)applied by <see cref="WirePanel"/> against stable widget names.
|
||||
/// </summary>
|
||||
[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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches (idempotently) a <see cref="DraggablePanel"/> 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).
|
||||
/// </summary>
|
||||
/// <summary>Attaches (idempotently) a <see cref="DraggablePanel"/> so the whole panel can be
|
||||
/// dragged from any non-widget surface.</summary>
|
||||
private void MakeDraggable(Transform root, string panelName)
|
||||
{
|
||||
var panel = GeometryUtil.FindDeep(root, panelName) as RectTransform;
|
||||
|
||||
@@ -3,19 +3,10 @@ using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Reshapes a UI <see cref="Slider"/>'s response with an <see cref="AnimationCurve"/> so the value runs
|
||||
/// non-linearly between the slider's authored <c>minValue</c>/<c>maxValue</c>. 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.
|
||||
///
|
||||
/// <see cref="Value"/> is the curved output, <see cref="SetOutput"/> positions the handle to show a given
|
||||
/// output (inverting the curve), and <see cref="ValueChanged"/> raises the curved value whenever the
|
||||
/// handle moves - so the component is usable both standalone (wire <see cref="ValueChanged"/>) and from
|
||||
/// code that drives the raw slider (read <see cref="Value"/>).
|
||||
///
|
||||
/// The curve should be monotonic increasing (the default linear is); the inverse used by
|
||||
/// <see cref="SetOutput"/> assumes that.
|
||||
/// Reshapes a UI <see cref="Slider"/>'s response with an <see cref="AnimationCurve"/> 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 <see cref="SetOutput"/> assumes that.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Slider))]
|
||||
[DisallowMultipleComponent]
|
||||
|
||||
@@ -3,10 +3,9 @@ using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(RectTransform))]
|
||||
public class UiPointerSurface : MonoBehaviour, IPointerDownHandler, IDragHandler
|
||||
|
||||
@@ -9,13 +9,13 @@ using UnityEditor.SceneManagement;
|
||||
/// <summary>
|
||||
/// Editor tool that runs V-HACD (approximate convex decomposition via the native <see cref="VHACD"/>
|
||||
/// plugin) on an environment mesh and bakes the resulting convex hulls into SAVED mesh assets + child
|
||||
/// MeshCollider GameObjects — the exact same output plumbing as <see cref="ConcealmentColliderBaker"/>,
|
||||
/// only the decomposition backend differs (V-HACD's voxel solver instead of the surface-clustering
|
||||
/// MeshCollider GameObjects — the same output plumbing as <see cref="ConcealmentColliderBaker"/>, only
|
||||
/// the decomposition backend differs (V-HACD's voxel solver instead of the surface-clustering
|
||||
/// heuristic). Tune the decomposition on the sibling <see cref="VHACD"/> component (its m_parameters),
|
||||
/// then run "Bake VHACD Colliders".
|
||||
///
|
||||
/// The hulls are real child GameObjects referencing hull-mesh assets under
|
||||
/// <c>Assets/Generated/VHACDColliders</c>, so they survive domain reload / play-mode / scene save
|
||||
/// <c>Assets/Generated/VHACDColliders</c>, so they survive domain reload / play mode / scene save
|
||||
/// (unlike an in-memory-only bake).
|
||||
/// </summary>
|
||||
[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);
|
||||
|
||||
@@ -2,10 +2,9 @@ using UnityEngine;
|
||||
using Unity.Netcode;
|
||||
|
||||
/// <summary>
|
||||
/// First-person mode. The player keeps walking (WASD, camera-relative) but the camera moves to the
|
||||
/// player's eye (the CameraTarget) with free mouse-look, and the player's mesh is hidden so the body
|
||||
/// isn't seen from the inside. The orbit camera input is disabled while here so it doesn't fight for
|
||||
/// the cursor; the <see cref="FirstPersonController"/> drives its own dedicated CinemachineCamera.
|
||||
/// 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 <see cref="FirstPersonController"/> drives its own dedicated CinemachineCamera.
|
||||
/// </summary>
|
||||
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<NetworkObject>();
|
||||
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<NetworkPlayerSetup>();
|
||||
if (setup && setup.Role == PlayerRole.Hider && !setup.IsFound)
|
||||
match.ReportHit(netObj.OwnerClientId);
|
||||
}
|
||||
break; // the first non-self hit decides the shot
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
/// <summary>
|
||||
/// Free-fly camera mode. The player is frozen in place, painting is off, and a dedicated
|
||||
/// freecam (<see cref="FreeCamController"/> + 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
|
||||
/// (<see cref="FreeCamController"/> + its own CinemachineCamera) flies through the map, colliding
|
||||
/// with world geometry. Orbit camera input is disabled so it doesn't fight for the cursor.
|
||||
/// </summary>
|
||||
public class FreeCamMode : GameMode
|
||||
{
|
||||
|
||||
@@ -32,7 +32,6 @@ public abstract class GameMode
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Toggles painting on the mesh painter.</summary>
|
||||
protected void SetPainting(bool on)
|
||||
{
|
||||
if (Manager.Painter)
|
||||
|
||||
@@ -107,7 +107,7 @@ public class GameModeManager : MonoBehaviour
|
||||
if (!freeCam) freeCam = FindFirstObjectByType<FreeCamController>(FindObjectsInactive.Include);
|
||||
if (!firstPerson) firstPerson = FindFirstObjectByType<FirstPersonController>(FindObjectsInactive.Include);
|
||||
|
||||
BuildModes();
|
||||
BuildModes(null);
|
||||
}
|
||||
|
||||
private void Start()
|
||||
@@ -125,8 +125,6 @@ public class GameModeManager : MonoBehaviour
|
||||
/// <summary>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.</summary>
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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 <persistentDataPath>/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 (<persistentDataPath>/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";
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -5,12 +5,9 @@ using UnityEngine.InputSystem;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="MatchManager"/> 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 <see cref="MatchManager"/>
|
||||
/// at match start, not here. Testing scaffold only - replace with real lobby/menu UI later.
|
||||
/// </summary>
|
||||
[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)
|
||||
|
||||
@@ -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).</summary>
|
||||
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<bool> 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<ulong> 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.</summary>
|
||||
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.</summary>
|
||||
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.</summary>
|
||||
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<ulong, GameObject> prefabFor, Vector3 at)
|
||||
{
|
||||
var ids = new List<ulong>(_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.</summary>
|
||||
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
|
||||
}
|
||||
|
||||
@@ -5,25 +5,16 @@ using Chameleon.Components;
|
||||
|
||||
/// <summary>
|
||||
/// 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). <see cref="MatchManager"/> 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); <see cref="MatchManager"/> 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 <see cref="NetworkPlayerSetup"/> 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
|
||||
/// <see cref="NetworkPlayerSetup"/> 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:
|
||||
/// <list type="bullet">
|
||||
/// <item><b>Transform</b> - the server Instantiates it at the captured pos/rot; the (server-authoritative)
|
||||
/// NetworkTransform replicates that.</item>
|
||||
/// <item><b>Painting</b> - rides the existing <see cref="PlayerPaintSync"/> 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.</item>
|
||||
/// <item><b>Stance</b> - a single replicated pose index the pose controller reproduces (network-driven),
|
||||
/// then freezes.</item>
|
||||
/// </list>
|
||||
/// Server-owned; three replicated ingredients rebuild it identically on every client: transform
|
||||
/// (server-authoritative NetworkTransform), painting (rides the existing <see cref="PlayerPaintSync"/>
|
||||
/// 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).
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class MatchStatue : NetworkBehaviour
|
||||
|
||||
@@ -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 <see cref="GameModeManager.BindLocalPlayer"/>; 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.
|
||||
/// </summary>
|
||||
[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)
|
||||
|
||||
@@ -9,25 +9,17 @@ using Unity.Services.Multiplayer;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="NetworkManager.StartHost"/>/<c>StartClient</c> 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 <see cref="NetworkManager.StartHost"/>/<c>StartClient</c> 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 <see cref="MatchManager"/>
|
||||
/// 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
|
||||
/// <see cref="Changed"/>/<see cref="SessionsChanged"/> events. The uGUI server browser that drives it lives
|
||||
/// on its own canvas (<see cref="OnlineSessionBrowserUI"/>); 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 <see cref="Changed"/>/<see cref="SessionsChanged"/> events; the
|
||||
/// uGUI browser driving it is <see cref="OnlineSessionBrowserUI"/>. 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.
|
||||
/// </summary>
|
||||
[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<System.Threading.Tasks.Task> body)
|
||||
private async Task RunSessionOpAsync(string failLabel, bool ownsConnection, Func<Task> body)
|
||||
{
|
||||
SetBusy(true);
|
||||
try
|
||||
|
||||
@@ -2,19 +2,11 @@ using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 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 (<see cref="PlayerCharacterController.MeshOffsetTarget"/>) — 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 (<see cref="PlayerCharacterController.MeshOffsetTarget"/>), 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.
|
||||
/// </summary>
|
||||
|
||||
@@ -607,7 +607,7 @@ public class PlayerPaintSync : NetworkBehaviour
|
||||
CopyChunkInto(_snapBuffer, offset, chunk);
|
||||
}
|
||||
|
||||
/// <summary>Copies a received chunk into the reassembly buffer, ignoring out-of-bounds offsets.</summary>
|
||||
// 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;
|
||||
}
|
||||
|
||||
/// <summary>All connected client ids except the given one (server-side).</summary>
|
||||
private ulong[] OtherClientIds(ulong except)
|
||||
{
|
||||
var ids = NetworkManager.ConnectedClientsIds;
|
||||
|
||||
@@ -2,12 +2,10 @@ using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class SpectatorSetup : NetworkBehaviour
|
||||
|
||||
@@ -2,15 +2,12 @@ using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// On-screen match HUD binder: drives the authored <c>MatchHudCanvas</c> 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 <see cref="UnityEngine.UI.GraphicRaycaster"/> 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 <c>MatchHudCanvas</c> widgets to live match state (sub-phase + countdowns, role
|
||||
/// counts, hunter crosshair); creates nothing at runtime. Passive overlay: the canvas has no
|
||||
/// <see cref="UnityEngine.UI.GraphicRaycaster"/> 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.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class MatchHud : MonoBehaviour
|
||||
|
||||
@@ -8,24 +8,16 @@ using UnityEngine.InputSystem;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// uGUI binding for <see cref="OnlineSessionManager"/>. Drives the server-browser canvas - Host / Refresh /
|
||||
/// pick a row / Join / Disconnect plus a status line - and mirrors the manager's state through its
|
||||
/// <see cref="OnlineSessionManager.Changed"/> / <see cref="OnlineSessionManager.SessionsChanged"/> events.
|
||||
/// Lives on the browser canvas; the session logic stays on the NetworkManager's
|
||||
/// <see cref="OnlineSessionManager"/>.
|
||||
/// uGUI binding for <see cref="OnlineSessionManager"/>: Host / Refresh / row select / Join / Disconnect
|
||||
/// plus a status line, mirroring the manager through its <see cref="OnlineSessionManager.Changed"/> /
|
||||
/// <see cref="OnlineSessionManager.SessionsChanged"/> events; the session logic stays on the manager.
|
||||
///
|
||||
/// Selection model: Refresh fills the list with clones of <see cref="entryTemplate"/> (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
|
||||
/// <see cref="GameModeManager.SetPaused"/>). 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 <see cref="panelRoot"/> 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
|
||||
/// <see cref="GameModeManager.SetPaused"/>). 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 <see cref="panelRoot"/> is toggled - this binder
|
||||
/// stays alive on its (always-active) canvas so it keeps reading Esc while the panel is hidden.
|
||||
/// </summary>
|
||||
public class OnlineSessionBrowserUI : MonoBehaviour
|
||||
{
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// A selectable player pose: an idle <see cref="AnimationClip"/> 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.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "NewPose", menuName = "Chameleon/Pose")]
|
||||
public class PlayerPose : ScriptableObject
|
||||
|
||||
Reference in new issue
Block a user