using System.Collections.Generic; using Unity.Netcode; using UnityEngine; /// /// Обычный MonoBehaviour (не сетевой) — вся логика чисто серверная. /// Повесь на тот же объект, что и RoundManager (или на любой объект сцены). /// Хранит архив записей, спавнит клонов на старте раунда и тикает их в FixedUpdate. /// public class GhostRoundManager : MonoBehaviour { public static GhostRoundManager Current { get; private set; } [SerializeField] private GameObject ghostPrefab; // префаб клона (см. чеклист настройки) [SerializeField] private int maxGhostsPerPlayer = 3; // сколько прошлых раундов помним на игрока // ClientId -> записи его прошлых раундов private readonly Dictionary> archive = new Dictionary>(); private readonly List activeGhosts = new List(); private int roundTick; private bool roundRunning; private bool IsServer => NetworkManager.Singleton != null && NetworkManager.Singleton.IsServer; private void Awake() { Current = this; } private void OnDestroy() { if (Current == this) Current = null; } /// Вызывается из RoundManager.StartRoundWithDelay после RespawnAllPlayers. public void ServerStartRound() { if (!IsServer) return; roundTick = 0; activeGhosts.Clear(); // Спавним клонов из архива foreach (KeyValuePair> _kvp in archive) { foreach (GhostRecording _recording in _kvp.Value) { if (_recording.positions.Count == 0) continue; GameObject _go = Instantiate(ghostPrefab, _recording.positions[0], _recording.rotations[0]); _go.GetComponent().Spawn(); GhostPlayer _ghost = _go.GetComponent(); _ghost.Init(_recording); activeGhosts.Add(_ghost); } } // Включаем запись у всех живых игроков foreach (NetworkClient _client in NetworkManager.Singleton.ConnectedClientsList) { _client.PlayerObject?.GetComponent()?.BeginRecording(); } roundRunning = true; } private void FixedUpdate() { if (!IsServer || !roundRunning) return; if (RoundManager.Current == null || !RoundManager.Current.IsRoundActive) return; foreach (GhostPlayer _ghost in activeGhosts) { if (_ghost != null && _ghost.IsSpawned) _ghost.Step(roundTick); } roundTick++; } /// Полностью очищает архив прошлых раундов (для рестарта матча). public void ClearArchive() { if (!IsServer) return; archive.Clear(); } /// Вызывается из RoundManager.EndRound первой строкой (до WaitForSeconds). public void ServerEndRound() { if (!IsServer || !roundRunning) return; roundRunning = false; // Убираем клонов со сцены foreach (GhostPlayer _ghost in activeGhosts) { if (_ghost != null && _ghost.IsSpawned) _ghost.NetworkObject.Despawn(); } activeGhosts.Clear(); // Останавливаем запись и складываем раунд в архив foreach (NetworkClient _client in NetworkManager.Singleton.ConnectedClientsList) { GhostRecorder _recorder = _client.PlayerObject?.GetComponent(); if (_recorder == null || _recorder.Recording == null) continue; _recorder.StopRecording(); if (!archive.TryGetValue(_client.ClientId, out List _list)) { _list = new List(); archive[_client.ClientId] = _list; } _list.Add(_recorder.Recording); if (_list.Count > maxGhostsPerPlayer) _list.RemoveAt(0); } } }