Fix first-person camera target and paint HUD rebind after networked spawn
Point the first-person camera at the dedicated FirstPersonTarget anchor (eye height) instead of the shared CameraTarget, in both the runtime SetPlayer rebind and the editor ResolveReferences fallback. Rebind the paint HUD to the locally spawned avatar's MeshPainter via a new GameModeManager.PainterChanged event. Since the spawn-on-connect refactor, no player exists at PaintHud.Start(), so the HUD bound to a null painter (no brush masks shown, color/settings were no-ops). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
69e185d969
commit
e04868f9c0
4 files changed
+58
-22
No files matched your search
@@ -6,7 +6,7 @@ using UnityEngine.InputSystem;
|
||||
|
||||
/// <summary>
|
||||
/// First-person camera. Drives its OWN dedicated <see cref="CinemachineCamera"/>: each frame it snaps
|
||||
/// to the player's <c>CameraTarget</c> (the same point the ThirdPersonCamera tracks) for position and
|
||||
/// 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.
|
||||
@@ -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<PlayerPoseController>();
|
||||
Transform t = p.transform.Find("CameraTarget");
|
||||
Transform t = p.transform.Find("FirstPersonTarget");
|
||||
if (t) cameraTarget = t;
|
||||
hiddenRenderers = p.GetComponentsInChildren<SkinnedMeshRenderer>(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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.</summary>
|
||||
public event System.Action<GameMode> ModeChanged;
|
||||
|
||||
/// <summary>Raised when the painter the HUD should edit changes - i.e. when the local
|
||||
/// networked avatar spawns and <see cref="BindLocalPlayer"/> 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.</summary>
|
||||
public event System.Action<MeshPainter> PainterChanged;
|
||||
|
||||
private readonly List<GameMode> _modes = new List<GameMode>();
|
||||
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<MeshPainter>(true);
|
||||
PainterChanged?.Invoke(painter);
|
||||
|
||||
Transform camTarget = localPlayer.transform.Find("CameraTarget");
|
||||
|
||||
|
||||
+45
-12
@@ -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<MeshPainter>();
|
||||
|
||||
_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<MeshPainter>();
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// (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.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
Reference in new issue
Block a user