using Unity.Netcode; using UnityEngine; using UnityEngine.SceneManagement; public sealed class PlayerSpawnController : NetworkBehaviour, IPlayerSystem { [Header("Start Room Spawn")] [SerializeField, Min(0f)] private float edgePadding = 0.75f; [SerializeField, Min(0f)] private float heightAboveFloor = 0.15f; private readonly NetworkVariable assignedSpawnPosition = new( Vector3.zero, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server); private readonly NetworkVariable hasAssignedSpawnPosition = new( false, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server); private int assignedSceneHandle = -1; public void Initialize(PlayerFacade facade) { } public override void OnNetworkSpawn() { assignedSpawnPosition.OnValueChanged += HandleSpawnPositionChanged; hasAssignedSpawnPosition.OnValueChanged += HandleSpawnAssignmentChanged; SceneManager.sceneLoaded += HandleSceneLoaded; if (IsServer) TryAssignStartRoomPosition(SceneManager.GetActiveScene()); if (hasAssignedSpawnPosition.Value) ApplySpawnPosition(assignedSpawnPosition.Value); } public override void OnNetworkDespawn() { assignedSpawnPosition.OnValueChanged -= HandleSpawnPositionChanged; hasAssignedSpawnPosition.OnValueChanged -= HandleSpawnAssignmentChanged; SceneManager.sceneLoaded -= HandleSceneLoaded; } private void HandleSceneLoaded(Scene scene, LoadSceneMode _) { if (IsServer) TryAssignStartRoomPosition(scene); if (IsOwner && hasAssignedSpawnPosition.Value) ApplySpawnPosition(assignedSpawnPosition.Value); } private void TryAssignStartRoomPosition(Scene scene) { if (!scene.IsValid() || !scene.isLoaded || scene.handle == assignedSceneHandle) return; StartRoomZone startRoom = FindStartRoomInScene(scene); if (startRoom == null || !startRoom.TryGetRandomSpawnPosition( edgePadding, heightAboveFloor, out Vector3 position)) { return; } hasAssignedSpawnPosition.Value = false; assignedSpawnPosition.Value = position; hasAssignedSpawnPosition.Value = true; assignedSceneHandle = scene.handle; } private static StartRoomZone FindStartRoomInScene(Scene scene) { foreach (GameObject rootObject in scene.GetRootGameObjects()) { StartRoomZone startRoom = rootObject.GetComponentInChildren(true); if (startRoom != null) return startRoom; } return null; } private void HandleSpawnPositionChanged(Vector3 _, Vector3 position) { if (hasAssignedSpawnPosition.Value) ApplySpawnPosition(position); } private void HandleSpawnAssignmentChanged(bool _, bool isAssigned) { if (isAssigned) ApplySpawnPosition(assignedSpawnPosition.Value); } private void ApplySpawnPosition(Vector3 position) { if (!IsOwner) return; CharacterController characterController = GetComponent(); bool restoreCharacterController = characterController != null && characterController.enabled; if (restoreCharacterController) characterController.enabled = false; transform.position = position; Physics.SyncTransforms(); if (restoreCharacterController) characterController.enabled = true; } }