using DG.Tweening; using Unity.Netcode; using UnityEngine; [RequireComponent(typeof(NetworkObject))] public sealed class NetworkDoorController : NetworkBehaviour { [Header("Visual")] [SerializeField] private Transform doorVisual; [SerializeField] private Vector3 openLocalPositionOffset = new(0f, 2.5f, 0f); [SerializeField] private Vector3 openLocalRotationOffset; [SerializeField, Min(0.01f)] private float animationDuration = 0.65f; [SerializeField] private Ease animationEase = Ease.OutCubic; private readonly NetworkVariable isOpen = new( false, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server); private Vector3 closedLocalPosition; private Vector3 closedLocalEulerAngles; private Tween doorTween; public bool IsOpen => isOpen.Value; private void Awake() { if (doorVisual == null) doorVisual = transform; closedLocalPosition = doorVisual.localPosition; closedLocalEulerAngles = doorVisual.localEulerAngles; } public override void OnNetworkSpawn() { isOpen.OnValueChanged += HandleOpenStateChanged; ApplyVisualState(isOpen.Value, true); } public override void OnNetworkDespawn() { isOpen.OnValueChanged -= HandleOpenStateChanged; } private void OnDisable() { doorTween?.Kill(); } public void SetOpen(bool shouldOpen) { if (!IsServer) return; isOpen.Value = shouldOpen; ApplyVisualState(shouldOpen, false); } public void Toggle() { SetOpen(!isOpen.Value); } [ContextMenu("Preview/Open")] private void PreviewOpen() => PreviewVisual(true); [ContextMenu("Preview/Close")] private void PreviewClose() => PreviewVisual(false); [ContextMenu("Preview/Toggle")] private void PreviewToggle() => PreviewVisual(doorVisual != null && doorVisual.localPosition == closedLocalPosition); private void PreviewVisual(bool shouldOpen) { ApplyVisualState(shouldOpen, !Application.isPlaying); } private void HandleOpenStateChanged(bool _, bool currentValue) => ApplyVisualState(currentValue, false); private void ApplyVisualState(bool shouldOpen, bool immediately) { if (doorVisual == null) return; doorTween?.Kill(); Vector3 targetPosition = closedLocalPosition + (shouldOpen ? openLocalPositionOffset : Vector3.zero); Vector3 targetRotation = closedLocalEulerAngles + (shouldOpen ? openLocalRotationOffset : Vector3.zero); if (immediately) { doorVisual.localPosition = targetPosition; doorVisual.localEulerAngles = targetRotation; return; } doorTween = DOTween.Sequence() .Join(doorVisual.DOLocalMove(targetPosition, animationDuration)) .Join(doorVisual.DOLocalRotate(targetRotation, animationDuration)) .SetEase(animationEase) .SetLink(gameObject); } }