using UnityEngine; using Unity.Netcode; using Unity.Netcode.Components; using System; public class PlayerHealth : NetworkBehaviour { [SerializeField] private int maxHealth = 100; public NetworkVariable currentHealth = new NetworkVariable( 100, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server ); public NetworkVariable isDead = new NetworkVariable(false); public event Action HealthChanged; public event Action Died; private bool isServer; // Короткое окно после респавна, когда вход в яму не засчитывается — страховка на случай, // если серверная позиция коллайдера не успела выйти из ямы (сетевая задержка). [SerializeField] private float pitImmunityDuration = 1f; private float pitImmuneUntil; public bool IsPitImmune => Time.time < pitImmuneUntil; public void Init(bool _isServer) { isServer = _isServer; currentHealth.OnValueChanged += OnHealthChanged; if(isServer) { currentHealth.Value = maxHealth; } HealthChanged?.Invoke(currentHealth.Value, maxHealth); } private void OnHealthChanged(int _previousValue, int _newValue) { HealthChanged?.Invoke(_newValue, maxHealth); if(_newValue <= 0) { Die(); } } public void TakeDamage(int _damageAmount) { if (!isServer) return; if(isDead.Value) return; PlayerShield _shield = GetComponent(); if (_shield != null && _shield.IsShieldUp) return; currentHealth.Value = Mathf.Max(0, currentHealth.Value - _damageAmount); } public override void OnNetworkDespawn() { currentHealth.OnValueChanged -= OnHealthChanged; } private void Die() { if(!isServer) return; GetComponent()?.SetShieldState(false); isDead.Value = true; OnDiedClientRpc(); } public void Respawn(Vector3 _spawnPosition) { if(!isServer) return; GetComponent()?.SetShieldState(false); isDead.Value = false; currentHealth.Value = maxHealth; pitImmuneUntil = Time.time + pitImmunityDuration; RespawnClientRpc(_spawnPosition); } public void PushCurrentHealthValue() { HealthChanged?.Invoke(currentHealth.Value, maxHealth); } [ClientRpc] private void OnDiedClientRpc() { Debug.Log($"Player {OwnerClientId} died"); SetPhysicsAndVisualsActive(false); Died?.Invoke(); } [ClientRpc] private void RespawnClientRpc(Vector3 _spawnPosition) { SetPhysicsAndVisualsActive(true); // Позицию задаёт ТОЛЬКО владелец — он авторитет над NetworkTransform (Owner authority). // На сервере/не-владельце трогать transform бессмысленно: NetworkTransform всё равно // перезапишет его синхронизацией от владельца. Серверную позицию догонит RoundManager // через WaitForPlayersAtSpawn перед стартом записи (иначе запись стартует в яме). if (!IsOwner) return; Teleport(_spawnPosition); } // Телепорт без интерполяции. CharacterController кэширует свою позицию, // поэтому его надо выключить на время установки трансформа. private void Teleport(Vector3 _spawnPosition) { CharacterController _controller = GetComponent(); if (_controller != null) _controller.enabled = false; NetworkTransform _netTransform = GetComponent(); if (_netTransform != null) _netTransform.Teleport(_spawnPosition, transform.rotation, transform.localScale); else transform.position = _spawnPosition; if (_controller != null) _controller.enabled = true; } private void SetPhysicsAndVisualsActive(bool _isActive) { foreach (Collider collider in GetComponentsInChildren()) collider.enabled = _isActive; foreach (MeshRenderer meshRenderer in GetComponentsInChildren()) meshRenderer.enabled = _isActive; } }