using System.Collections; using Unity.BossRoom.Gameplay.GameplayObjects; using Unity.BossRoom.Gameplay.GameplayObjects.Character; using Unity.BossRoom.Infrastructure; using Unity.BossRoom.Utils; using Unity.Netcode; using UnityEngine; using UnityEngine.Assertions; namespace Unity.BossRoom.Gameplay.UI { /// /// Class designed to only run on a client. Add this to a world-space prefab to display health or name on UI. /// /// /// Execution order is explicitly set such that it this class executes its LateUpdate after any Cinemachine /// LateUpdate calls, which may alter the final position of the game camera. /// [DefaultExecutionOrder(300)] public class UIStateDisplayHandler : NetworkBehaviour { [SerializeField] bool m_DisplayHealth; [SerializeField] bool m_DisplayName; [SerializeField] UIStateDisplay m_UIStatePrefab; // spawned in world (only one instance of this) UIStateDisplay m_UIState; RectTransform m_UIStateRectTransform; bool m_UIStateActive; [SerializeField] NetworkHealthState m_NetworkHealthState; [SerializeField] NetworkNameState m_NetworkNameState; ServerCharacter m_ServerCharacter; ClientPlayerAvatarNetworkAnimator m_ClientPlayerAvatarNetworkAnimator; NetworkAvatarGuidState m_NetworkAvatarGuidState; [SerializeField] IntVariable m_BaseHP; [Tooltip("UI object(s) will appear positioned at this transforms position.")] [SerializeField] Transform m_TransformToTrack; Camera m_Camera; Transform m_CanvasTransform; // as soon as any HP goes to 0, we wait this long before removing health bar UI object const float k_DurationSeconds = 2f; [Tooltip("World space vertical offset for positioning.")] [SerializeField] float m_VerticalWorldOffset; [Tooltip("Screen space vertical offset for positioning.")] [SerializeField] float m_VerticalScreenOffset; Vector3 m_VerticalOffset; // used to compute world position based on target and offsets Vector3 m_WorldPos; void Awake() { m_ServerCharacter = GetComponent(); } public override void OnNetworkSpawn() { if (!NetworkManager.Singleton.IsClient) { enabled = false; return; } var cameraGameObject = GameObject.FindWithTag("MainCamera"); if (cameraGameObject) { m_Camera = cameraGameObject.GetComponent(); } Assert.IsNotNull(m_Camera); var canvasGameObject = GameObject.FindWithTag("GameCanvas"); if (canvasGameObject) { m_CanvasTransform = canvasGameObject.transform; } Assert.IsNotNull(m_CanvasTransform); Assert.IsTrue(m_DisplayHealth || m_DisplayName, "Neither display fields are toggled on!"); if (m_DisplayHealth) { Assert.IsNotNull(m_NetworkHealthState, "A NetworkHealthState component needs to be attached!"); } m_VerticalOffset = new Vector3(0f, m_VerticalScreenOffset, 0f); // if PC, find our graphics transform and update health through callbacks, if displayed if (TryGetComponent(out m_ClientPlayerAvatarNetworkAnimator) && TryGetComponent(out m_NetworkAvatarGuidState)) { m_BaseHP = m_NetworkAvatarGuidState.RegisteredAvatar.CharacterClass.BaseHP; m_TransformToTrack = m_ClientPlayerAvatarNetworkAnimator.Animator.transform; if (m_DisplayHealth) { m_NetworkHealthState.HitPointsReplenished += DisplayUIHealth; m_NetworkHealthState.HitPointsDepleted += RemoveUIHealth; } } if (m_DisplayName) { DisplayUIName(); } if (m_DisplayHealth) { DisplayUIHealth(); } } void OnDisable() { if (!m_DisplayHealth) { return; } if (m_NetworkHealthState != null) { m_NetworkHealthState.HitPointsReplenished -= DisplayUIHealth; m_NetworkHealthState.HitPointsDepleted -= RemoveUIHealth; } } void DisplayUIName() { if (m_NetworkNameState == null) { return; } if (m_UIState == null) { SpawnUIState(); } m_UIState.DisplayName(m_NetworkNameState.Name); m_UIStateActive = true; } void DisplayUIHealth() { if (m_NetworkHealthState == null) { return; } if (m_UIState == null) { SpawnUIState(); } m_UIState.DisplayHealth(m_NetworkHealthState.HitPoints, m_BaseHP.Value); m_UIStateActive = true; } void SpawnUIState() { m_UIState = Instantiate(m_UIStatePrefab, m_CanvasTransform); // make in world UI state draw under other UI elements m_UIState.transform.SetAsFirstSibling(); m_UIStateRectTransform = m_UIState.GetComponent(); } void RemoveUIHealth() { StartCoroutine(WaitToHideHealthBar()); } IEnumerator WaitToHideHealthBar() { yield return new WaitForSeconds(k_DurationSeconds); m_UIState.HideHealth(); } /// /// Moving UI objects on LateUpdate ensures that the game camera is at its final position pre-render. /// void LateUpdate() { if (m_UIStateActive && m_TransformToTrack) { // set world position with world offset added m_WorldPos.Set(m_TransformToTrack.position.x, m_TransformToTrack.position.y + m_VerticalWorldOffset, m_TransformToTrack.position.z); m_UIStateRectTransform.position = m_Camera.WorldToScreenPoint(m_WorldPos) + m_VerticalOffset; } } public override void OnDestroy() { base.OnDestroy(); if (m_UIState != null) { Destroy(m_UIState.gameObject); } } } }