using System; using UnityEngine; using MoreMountains.Tools; using System.Collections; using UnityEngine.SceneManagement; using UnityEngine.UI; namespace MoreMountains.Tools { /// /// Add this component to an object and it will show a healthbar above it /// You can either use a prefab for it, or have the component draw one at the start /// [AddComponentMenu("More Mountains/Tools/GUI/MMHealthBar")] public class MMHealthBar : MonoBehaviour { /// the possible health bar types public enum HealthBarTypes { Prefab, Drawn, Existing } /// the possible timescales the bar can work on public enum TimeScales { UnscaledTime, Time } [MMInformation("Add this component to an object and it'll add a healthbar next to it to reflect its health level in real time. You can decide here whether the health bar should be drawn automatically or use a prefab.",MoreMountains.Tools.MMInformationAttribute.InformationType.Info,false)] /// whether the healthbar uses a prefab or is drawn automatically [Tooltip("whether the healthbar uses a prefab or is drawn automatically")] public HealthBarTypes HealthBarType = HealthBarTypes.Drawn; /// defines whether the bar will work on scaled or unscaled time (whether or not it'll keep moving if time is slowed down for example) [Tooltip("defines whether the bar will work on scaled or unscaled time (whether or not it'll keep moving if time is slowed down for example)")] public TimeScales TimeScale = TimeScales.UnscaledTime; [Header("Select a Prefab")] [MMInformation("Select a prefab with a progress bar script on it. There is one example of such a prefab in Common/Prefabs/GUI.",MoreMountains.Tools.MMInformationAttribute.InformationType.Info,false)] /// the prefab to use as the health bar [Tooltip("the prefab to use as the health bar")] public MMProgressBar HealthBarPrefab; [Header("Existing MMProgressBar")] /// the MMProgressBar this health bar should update [Tooltip("the MMProgressBar this health bar should update")] public MMProgressBar TargetProgressBar; [Header("Drawn Healthbar Settings ")] [MMInformation("Set the size (in world units), padding, back and front colors of the healthbar.",MoreMountains.Tools.MMInformationAttribute.InformationType.Info,false)] /// if the healthbar is drawn, its size in world units [Tooltip("if the healthbar is drawn, its size in world units")] public Vector2 Size = new Vector2(1f,0.2f); /// if the healthbar is drawn, the padding to apply to the foreground, in world units [Tooltip("if the healthbar is drawn, the padding to apply to the foreground, in world units")] public Vector2 BackgroundPadding = new Vector2(0.01f,0.01f); /// the rotation to apply to the MMHealthBarContainer when drawing it [Tooltip("the rotation to apply to the MMHealthBarContainer when drawing it")] public Vector3 InitialRotationAngles; /// if the healthbar is drawn, the color of its foreground [Tooltip("if the healthbar is drawn, the color of its foreground")] public Gradient ForegroundColor = new Gradient() { colorKeys = new GradientColorKey[2] { new GradientColorKey(MMColors.BestRed, 0), new GradientColorKey(MMColors.BestRed, 1f) }, alphaKeys = new GradientAlphaKey[2] {new GradientAlphaKey(1, 0),new GradientAlphaKey(1, 1)}}; /// if the healthbar is drawn, the color of its delayed bar [Tooltip("if the healthbar is drawn, the color of its delayed bar")] public Gradient DelayedColor = new Gradient() { colorKeys = new GradientColorKey[2] { new GradientColorKey(MMColors.Orange, 0), new GradientColorKey(MMColors.Orange, 1f) }, alphaKeys = new GradientAlphaKey[2] { new GradientAlphaKey(1, 0), new GradientAlphaKey(1, 1) } }; /// if the healthbar is drawn, the color of its border [Tooltip("if the healthbar is drawn, the color of its border")] public Gradient BorderColor = new Gradient() { colorKeys = new GradientColorKey[2] { new GradientColorKey(MMColors.AntiqueWhite, 0), new GradientColorKey(MMColors.AntiqueWhite, 1f) }, alphaKeys = new GradientAlphaKey[2] { new GradientAlphaKey(1, 0), new GradientAlphaKey(1, 1) } }; /// if the healthbar is drawn, the color of its background [Tooltip("if the healthbar is drawn, the color of its background")] public Gradient BackgroundColor = new Gradient() { colorKeys = new GradientColorKey[2] { new GradientColorKey(MMColors.Black, 0), new GradientColorKey(MMColors.Black, 1f) }, alphaKeys = new GradientAlphaKey[2] { new GradientAlphaKey(1, 0), new GradientAlphaKey(1, 1) } }; /// the name of the sorting layer to put this health bar on [Tooltip("the name of the sorting layer to put this health bar on")] public string SortingLayerName = "UI"; /// the delay to apply to the delayed bar if drawn [Tooltip("the delay to apply to the delayed bar if drawn")] public float Delay = 0.5f; /// whether or not the front bar should lerp [Tooltip("whether or not the front bar should lerp")] public bool LerpFrontBar = true; /// the speed at which the front bar lerps [Tooltip("the speed at which the front bar lerps")] public float LerpFrontBarSpeed = 15f; /// whether or not the delayed bar should lerp [Tooltip("whether or not the delayed bar should lerp")] public bool LerpDelayedBar = true; /// the speed at which the delayed bar lerps [Tooltip("the speed at which the delayed bar lerps")] public float LerpDelayedBarSpeed = 15f; /// if this is true, bumps the scale of the healthbar when its value changes [Tooltip("if this is true, bumps the scale of the healthbar when its value changes")] public bool BumpScaleOnChange = true; /// the duration of the bump animation [Tooltip("the duration of the bump animation")] public float BumpDuration = 0.2f; /// the animation curve to map the bump animation on [Tooltip("the animation curve to map the bump animation on")] public AnimationCurve BumpAnimationCurve = AnimationCurve.Constant(0,1,1); /// the mode the bar should follow the target in [Tooltip("the mode the bar should follow the target in")] public MMFollowTarget.UpdateModes FollowTargetMode = MMFollowTarget.UpdateModes.LateUpdate; /// if this is true, the drawn health bar will be nested below the MMHealthBar [Tooltip("if this is true, the drawn health bar will be nested below the MMHealthBar")] public bool NestDrawnHealthBar = false; /// if this is true, a MMBillboard component will be added to the progress bar to make sure it always looks towards the camera [Tooltip("if this is true, a MMBillboard component will be added to the progress bar to make sure it always looks towards the camera")] public bool Billboard = false; [Header("Death")] /// a gameobject (usually a particle system) to instantiate when the healthbar reaches zero [Tooltip("a gameobject (usually a particle system) to instantiate when the healthbar reaches zero")] public GameObject InstantiatedOnDeath; [Header("Offset")] [MMInformation("Set the offset (in world units), relative to the object's center, to which the health bar will be displayed.",MoreMountains.Tools.MMInformationAttribute.InformationType.Info,false)] /// the offset to apply to the healthbar compared to the object's center [Tooltip("the offset to apply to the healthbar compared to the object's center")] public Vector3 HealthBarOffset = new Vector3(0f,1f,0f); [Header("Display")] [MMInformation("Here you can define whether or not the healthbar should always be visible. If not, you can set here how long after a hit it'll remain visible.",MoreMountains.Tools.MMInformationAttribute.InformationType.Info,false)] /// whether or not the bar should be permanently displayed [Tooltip("whether or not the bar should be permanently displayed")] public bool AlwaysVisible = true; /// the duration (in seconds) during which to display the bar [Tooltip("the duration (in seconds) during which to display the bar")] public float DisplayDurationOnHit = 1f; /// if this is set to true the bar will hide itself when it reaches zero [Tooltip("if this is set to true the bar will hide itself when it reaches zero")] public bool HideBarAtZero = true; /// the delay (in seconds) after which to hide the bar [Tooltip("the delay (in seconds) after which to hide the bar")] public float HideBarAtZeroDelay = 1f; protected MMProgressBar _progressBar; protected MMFollowTarget _followTransform; protected float _lastShowTimestamp = 0f; protected bool _showBar = false; protected Image _backgroundImage = null; protected Image _borderImage = null; protected Image _foregroundImage = null; protected Image _delayedImage = null; protected bool _finalHideStarted = false; /// /// On Start, creates or sets the health bar up /// protected virtual void Awake() { Initialization(); } /// /// On enable, initializes the bar again /// protected void OnEnable() { _finalHideStarted = false; SetInitialActiveState(); } /// /// Forces the bar into its initial active state (hiding it if AlwaysVisible is false) /// public virtual void SetInitialActiveState() { if (!AlwaysVisible && (_progressBar != null)) { ShowBar(false); } } /// /// Shows or hides the bar by changing its object's active state /// /// public virtual void ShowBar(bool state) { _progressBar.gameObject.SetActive(state); } /// /// Whether or not the bar is currently active /// /// public virtual bool BarIsShown() { return _progressBar.gameObject.activeInHierarchy; } /// /// Initializes the bar (handles visibility, parenting, initial value /// public virtual void Initialization() { _finalHideStarted = false; if (_progressBar != null) { ShowBar(AlwaysVisible); return; } switch (HealthBarType) { case HealthBarTypes.Prefab: if (HealthBarPrefab == null) { Debug.LogWarning(this.name + " : the HealthBar has no prefab associated to it, nothing will be displayed."); return; } _progressBar = Instantiate(HealthBarPrefab, transform.position + HealthBarOffset, transform.rotation) as MMProgressBar; SceneManager.MoveGameObjectToScene(_progressBar.gameObject, this.gameObject.scene); _progressBar.transform.SetParent(this.transform); _progressBar.gameObject.name = "HealthBar"; break; case HealthBarTypes.Drawn: DrawHealthBar(); UpdateDrawnColors(); break; case HealthBarTypes.Existing: _progressBar = TargetProgressBar; break; } if (!AlwaysVisible) { ShowBar(false); } if (_progressBar != null) { _progressBar.SetBar(100f, 0f, 100f); } } /// /// Draws the health bar. /// protected virtual void DrawHealthBar() { GameObject newGameObject = new GameObject(); SceneManager.MoveGameObjectToScene(newGameObject, this.gameObject.scene); newGameObject.name = "HealthBar|"+this.gameObject.name; if (NestDrawnHealthBar) { newGameObject.transform.SetParent(this.transform); } _progressBar = newGameObject.AddComponent(); _followTransform = newGameObject.AddComponent(); _followTransform.Offset = HealthBarOffset; _followTransform.Target = this.transform; _followTransform.FollowRotation = false; _followTransform.InterpolatePosition = false; _followTransform.InterpolateRotation = false; _followTransform.UpdateMode = FollowTargetMode; Canvas newCanvas = newGameObject.AddComponent(); newCanvas.renderMode = RenderMode.WorldSpace; newCanvas.transform.localScale = Vector3.one; newCanvas.GetComponent().sizeDelta = Size; if (!string.IsNullOrEmpty(SortingLayerName)) { newCanvas.sortingLayerName = SortingLayerName; } GameObject container = new GameObject(); container.transform.SetParent(newGameObject.transform); container.name = "MMProgressBarContainer"; container.transform.localScale = Vector3.one; GameObject borderImageGameObject = new GameObject(); borderImageGameObject.transform.SetParent(container.transform); borderImageGameObject.name = "HealthBar Border"; _borderImage = borderImageGameObject.AddComponent(); _borderImage.transform.position = Vector3.zero; _borderImage.transform.localScale = Vector3.one; _borderImage.GetComponent().sizeDelta = Size; _borderImage.GetComponent().anchoredPosition = Vector3.zero; GameObject bgImageGameObject = new GameObject(); bgImageGameObject.transform.SetParent(container.transform); bgImageGameObject.name = "HealthBar Background"; _backgroundImage = bgImageGameObject.AddComponent(); _backgroundImage.transform.position = Vector3.zero; _backgroundImage.transform.localScale = Vector3.one; _backgroundImage.GetComponent().sizeDelta = Size - BackgroundPadding*2; _backgroundImage.GetComponent().anchoredPosition = -_backgroundImage.GetComponent().sizeDelta/2; _backgroundImage.GetComponent().pivot = Vector2.zero; GameObject delayedImageGameObject = new GameObject(); delayedImageGameObject.transform.SetParent(container.transform); delayedImageGameObject.name = "HealthBar Delayed Foreground"; _delayedImage = delayedImageGameObject.AddComponent(); _delayedImage.transform.position = Vector3.zero; _delayedImage.transform.localScale = Vector3.one; _delayedImage.GetComponent().sizeDelta = Size - BackgroundPadding*2; _delayedImage.GetComponent().anchoredPosition = -_delayedImage.GetComponent().sizeDelta/2; _delayedImage.GetComponent().pivot = Vector2.zero; GameObject frontImageGameObject = new GameObject(); frontImageGameObject.transform.SetParent(container.transform); frontImageGameObject.name = "HealthBar Foreground"; _foregroundImage = frontImageGameObject.AddComponent(); _foregroundImage.transform.position = Vector3.zero; _foregroundImage.transform.localScale = Vector3.one; _foregroundImage.color = ForegroundColor.Evaluate(1); _foregroundImage.GetComponent().sizeDelta = Size - BackgroundPadding*2; _foregroundImage.GetComponent().anchoredPosition = -_foregroundImage.GetComponent().sizeDelta/2; _foregroundImage.GetComponent().pivot = Vector2.zero; if (Billboard) { _progressBar.gameObject.AddComponent(); } _progressBar.LerpDecreasingDelayedBar = LerpDelayedBar; _progressBar.LerpForegroundBar = LerpFrontBar; _progressBar.LerpDecreasingDelayedBarSpeed = LerpDelayedBarSpeed; _progressBar.LerpForegroundBarSpeedIncreasing = LerpFrontBarSpeed; _progressBar.ForegroundBar = _foregroundImage.transform; _progressBar.DelayedBarDecreasing = _delayedImage.transform; _progressBar.DecreasingDelay = Delay; _progressBar.BumpScaleOnChange = BumpScaleOnChange; _progressBar.BumpDuration = BumpDuration; _progressBar.BumpScaleAnimationCurve = BumpAnimationCurve; _progressBar.TimeScale = (TimeScale == TimeScales.Time) ? MMProgressBar.TimeScales.Time : MMProgressBar.TimeScales.UnscaledTime; container.transform.localEulerAngles = InitialRotationAngles; _progressBar.Initialization(); } /// /// On Update, we hide or show our healthbar based on our current status /// protected virtual void Update() { if (_progressBar == null) { return; } if (_finalHideStarted) { return; } UpdateDrawnColors(); if (AlwaysVisible) { return; } if (_showBar) { ShowBar(true); float currentTime = (TimeScale == TimeScales.UnscaledTime) ? Time.unscaledTime : Time.time; if (currentTime - _lastShowTimestamp > DisplayDurationOnHit) { _showBar = false; } } else { if (BarIsShown()) { ShowBar(false); } } } /// /// Hides the bar when it reaches zero /// /// The hide bar. protected virtual IEnumerator FinalHideBar() { _finalHideStarted = true; if (InstantiatedOnDeath != null) { GameObject instantiatedOnDeath = Instantiate(InstantiatedOnDeath, this.transform.position + HealthBarOffset, this.transform.rotation); SceneManager.MoveGameObjectToScene(instantiatedOnDeath.gameObject, this.gameObject.scene); } if (HideBarAtZeroDelay == 0) { _showBar = false; ShowBar(false); yield return null; } else { _progressBar.HideBar(HideBarAtZeroDelay); } } /// /// Updates the colors of the different bars /// protected virtual void UpdateDrawnColors() { if (HealthBarType != HealthBarTypes.Drawn) { return; } if (_progressBar.Bumping) { return; } if (_borderImage != null) { _borderImage.color = BorderColor.Evaluate(_progressBar.BarProgress); } if (_backgroundImage != null) { _backgroundImage.color = BackgroundColor.Evaluate(_progressBar.BarProgress); } if (_delayedImage != null) { _delayedImage.color = DelayedColor.Evaluate(_progressBar.BarProgress); } if (_foregroundImage != null) { _foregroundImage.color = ForegroundColor.Evaluate(_progressBar.BarProgress); } } /// /// Updates the bar /// /// Current health. /// Minimum health. /// Max health. /// Whether or not we should show the bar. public virtual void UpdateBar(float currentHealth, float minHealth, float maxHealth, bool show) { // if the healthbar isn't supposed to be always displayed, we turn it on for the specified duration if (!AlwaysVisible && show) { _showBar = true; _lastShowTimestamp = (TimeScale == TimeScales.UnscaledTime) ? Time.unscaledTime : Time.time; } if (_progressBar != null) { _progressBar.UpdateBar(currentHealth, minHealth, maxHealth) ; if (HideBarAtZero && _progressBar.BarTarget <= 0) { StartCoroutine(FinalHideBar()); } if (BumpScaleOnChange) { _progressBar.Bump(); } } } } }