diff --git a/Assets/Prefabs/Hider.prefab b/Assets/Prefabs/Hider.prefab index 4563a8d..c340875 100644 --- a/Assets/Prefabs/Hider.prefab +++ b/Assets/Prefabs/Hider.prefab @@ -16,10 +16,6 @@ PrefabInstance: propertyPath: GlobalObjectIdHash value: 708302045 objectReference: {fileID: 0} - - target: {fileID: 4587432968399885263, guid: 97017ed3bb40aff4e885440d9a7336ee, type: 3} - propertyPath: m_Enabled - value: 1 - objectReference: {fileID: 0} - target: {fileID: 5384024074013944205, guid: 97017ed3bb40aff4e885440d9a7336ee, type: 3} propertyPath: m_Name value: Hider diff --git a/Assets/Scripts/FirstPersonController.cs b/Assets/Scripts/FirstPersonController.cs index 1cd5fb8..a0fc930 100644 --- a/Assets/Scripts/FirstPersonController.cs +++ b/Assets/Scripts/FirstPersonController.cs @@ -6,7 +6,7 @@ using UnityEngine.InputSystem; /// /// First-person camera. Drives its OWN dedicated : each frame it snaps -/// to the player's CameraTarget (the same point the ThirdPersonCamera tracks) for position and +/// to the player's dedicated FirstPersonTarget (the eye anchor) for position and /// applies free mouse-look (yaw/pitch) for rotation. While active the player's mesh is hidden so the /// body isn't seen from the inside. Movement stays with the player controller, which reads the /// brain-driven Main Camera forward, so WASD is automatically relative to this view. @@ -21,7 +21,7 @@ using UnityEngine.InputSystem; public class FirstPersonController : MonoBehaviour { [Header("Target")] - [Tooltip("Eye position the camera sits at (the player's CameraTarget). Empty = auto-resolved from the player's \"CameraTarget\" child.")] + [Tooltip("Eye position the camera sits at (the player's FirstPersonTarget). Empty = auto-resolved from the player's \"FirstPersonTarget\" child.")] public Transform cameraTarget; [Tooltip("Renderers hidden while first-person is active (so you don't see your own body). Empty = the player's SkinnedMeshRenderers, auto-resolved.")] @@ -136,9 +136,9 @@ public class FirstPersonController : MonoBehaviour Quaternion rot = Quaternion.Euler(_displayPitch, _displayYaw, 0f); if (cameraTarget) { - // Sit at the shared camera target, then raise/lower the eye by the active pose's first-person + // Sit at the first-person eye anchor, then raise/lower the eye by the active pose's first-person // offset. TransformVector keeps it in the target's local up and scales with the player (so it - // tracks PlayerScaler), without disturbing the target the ThirdPersonCamera shares. + // tracks PlayerScaler). Vector3 pos = cameraTarget.position; float offset = _poseController ? _poseController.FirstPersonCameraYOffset : 0f; if (offset != 0f) @@ -185,7 +185,7 @@ public class FirstPersonController : MonoBehaviour if (!p) return; _player = p; _poseController = p.GetComponent(); - Transform t = p.transform.Find("CameraTarget"); + Transform t = p.transform.Find("FirstPersonTarget"); if (t) cameraTarget = t; hiddenRenderers = p.GetComponentsInChildren(true); _resolved = true; @@ -205,7 +205,7 @@ public class FirstPersonController : MonoBehaviour if (!cameraTarget) { - Transform t = player.transform.Find("CameraTarget"); + Transform t = player.transform.Find("FirstPersonTarget"); if (t) cameraTarget = t; } diff --git a/Assets/Scripts/GameModeManager.cs b/Assets/Scripts/GameModeManager.cs index 3b6a91b..0f1a3b2 100644 --- a/Assets/Scripts/GameModeManager.cs +++ b/Assets/Scripts/GameModeManager.cs @@ -54,6 +54,12 @@ public class GameModeManager : MonoBehaviour /// Argument = the now-active mode. Used by UI (e.g. the paint HUD) to follow the mode. public event System.Action ModeChanged; + /// Raised when the painter the HUD should edit changes - i.e. when the local + /// networked avatar spawns and binds its per-avatar painter. + /// With spawn-on-connect there is no painter at scene start, so the paint HUD follows this + /// to (re)bind its widgets to the live painter once it exists. + public event System.Action PainterChanged; + private readonly List _modes = new List(); private int _activeIndex = -1; @@ -122,6 +128,7 @@ public class GameModeManager : MonoBehaviour // The painter lives on the player (per-avatar), so bind the local one. if (!painter) painter = localPlayer.GetComponentInChildren(true); + PainterChanged?.Invoke(painter); Transform camTarget = localPlayer.transform.Find("CameraTarget"); diff --git a/Assets/Scripts/PaintHud.cs b/Assets/Scripts/PaintHud.cs index 1ba8889..421242f 100644 --- a/Assets/Scripts/PaintHud.cs +++ b/Assets/Scripts/PaintHud.cs @@ -87,19 +87,24 @@ public partial class PaintHud : MonoBehaviour private void OnEnable() { - if (modeManager) modeManager.ModeChanged += OnModeChanged; + if (modeManager) + { + modeManager.ModeChanged += OnModeChanged; + modeManager.PainterChanged += OnPainterChanged; + } } private void OnDisable() { - if (modeManager) modeManager.ModeChanged -= OnModeChanged; + if (modeManager) + { + modeManager.ModeChanged -= OnModeChanged; + modeManager.PainterChanged -= OnPainterChanged; + } } private void Start() { - if (!painter) painter = modeManager ? modeManager.Painter : null; - if (!painter) painter = FindFirstObjectByType(); - _font = LoadFont(); EnsureEventSystem(); @@ -112,18 +117,46 @@ public partial class PaintHud : MonoBehaviour _panel = go; } - WirePanel(_panel.transform); - - // Re-sync every widget when the pipette adopts a sampled material (painter -> HUD). - if (painter) painter.Picked += SyncFromPainter; - - // Restore the saved panel positions + widget values from the previous session. - RestoreHudState(); + // Bind to a painter as soon as one exists. With networked spawn-on-connect there is none + // at scene start; BindLocalPlayer then raises PainterChanged when the local avatar spawns + // and OnPainterChanged wires the widgets. In a non-networked scene the painter is already + // present and binds here immediately. + var p = painter; + if (!p) p = modeManager ? modeManager.Painter : null; + if (!p) p = FindFirstObjectByType(); + if (p) BindPainter(p); // Set initial visibility (the manager's first SwitchTo may have fired before this Start). OnModeChanged(modeManager ? modeManager.ActiveMode : null); } + private void OnPainterChanged(MeshPainter newPainter) + { + if (newPainter) BindPainter(newPainter); + } + + /// + /// (Re)binds the HUD's widgets to a live painter. Called from Start when one already exists + /// (non-networked scene) or from the manager's PainterChanged event when the local networked + /// avatar spawns. Binds exactly once per painter: re-binding the same painter is a no-op, so + /// the deferred network path and the immediate scene path never double-add widget listeners. + /// + private void BindPainter(MeshPainter newPainter) + { + if (!newPainter || newPainter == painter || !_panel) return; + + if (painter) painter.Picked -= SyncFromPainter; + painter = newPainter; + + WirePanel(_panel.transform); + + // Re-sync every widget when the pipette adopts a sampled material (painter -> HUD). + painter.Picked += SyncFromPainter; + + // Restore the saved panel positions + widget values now that the painter exists. + RestoreHudState(); + } + private void OnDestroy() { if (painter) painter.Picked -= SyncFromPainter;