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:
adminandClaude Fable 5 committed 2026-07-08 08:29:18 +02:00
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 &gt; 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 &gt; 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&#160;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&#160;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)
+14 -21
View File
@@ -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);
}
}
+13 -16
View File
@@ -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)
+5 -6
View File
@@ -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
+9 -10
View File
@@ -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))]
+7 -8
View File
@@ -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
+9 -10
View File
@@ -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]
+9 -12
View File
@@ -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 -11
View File
@@ -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();
}
+6 -14
View File
@@ -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;
+4 -13
View File
@@ -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);