// Copyright (c) 2015 - 2023 Doozy Entertainment. All Rights Reserved. // This code can only be used under the standard Unity Asset Store End User License Agreement // A Copy of the EULA APPENDIX 1 is available at http://unity3d.com/company/legal/as_terms using System.Collections; using System.Collections.Generic; using System.Linq; using Doozy.Runtime.Common.Events; using Doozy.Runtime.Common.Extensions; using Doozy.Runtime.Common.Utils; using Doozy.Runtime.Reactor.ScriptableObjects; using UnityEngine; using UnityEngine.Events; // ReSharper disable MemberCanBePrivate.Global namespace Doozy.Runtime.Reactor { /// /// Calculates the arithmetic mean of all the referenced Progressors progress values. /// Useful if you need to combine the progress of multiple Progressors into one. /// This calculates the mean value of all the referenced Progressors progress values. /// [AddComponentMenu("Doozy/Reactor/Progressor Group")] public class ProgressorGroup : MonoBehaviour { #if UNITY_EDITOR [UnityEditor.MenuItem("GameObject/Doozy/Reactor/Progressor Group", false, 6)] private static void CreateComponent(UnityEditor.MenuCommand menuCommand) { GameObjectUtils.AddToScene(false, true); } #endif private const float TOLERANCE = 0.001f; [SerializeField] protected float Progress; [SerializeField] private List Progressors = new List(); [SerializeField] private List ProgressTargets; [SerializeField] protected FloatEvent OnProgressChanged = new FloatEvent(); [SerializeField] protected FloatEvent OnProgressIncremented = new FloatEvent(); [SerializeField] protected FloatEvent OnProgressDecremented = new FloatEvent(); [SerializeField] protected UnityEvent OnProgressReachedOne = new UnityEvent(); [SerializeField] protected UnityEvent OnProgressReachedZero = new UnityEvent(); /// Progressors sources that are used to calculate the mean progress value for this ProgressorGroup public List progressors { get { Initialize(); return Progressors; } } /// Progress targets that get updated by this ProgressorGroup when activated public List progressTargets { get { Initialize(); return ProgressTargets; } } /// Current Progress public float progress { get => Progress; private set { Progress = Mathf.Clamp01(value); OnProgressChanged?.Invoke(Progress); } } /// /// Fired when the Progress value changed. /// Returns the new progress value. /// public FloatEvent onProgressChanged => OnProgressChanged; /// /// Fired when the Progress value increased. /// Returns the difference between the new and the previous progress value. /// Example: if the previous progress value was 0.5 and the new progress value is 0.7, the returned value will be 0.2 /// public FloatEvent onProgressIncremented => OnProgressIncremented; /// /// Fired when the Progress value decreased. /// Returns the difference between the new and the previous progress value. /// Example: if the previous progress value was 0.5 and the new progress value is 0.3, the returned value will be -0.2 /// public FloatEvent onProgressDecremented => OnProgressDecremented; /// /// Fired when the Progress value reached 1. /// Returns the new progress value (1). /// public UnityEvent onProgressReachedOne => OnProgressReachedOne; /// /// Fired when the Progress value reached 0. /// Returns the new progress value (0). /// public UnityEvent onProgressReachedZero => OnProgressReachedZero; private Coroutine updateCoroutine { get; set; } /// Initialization flag public bool initialized { get; set; } public void Initialize() { if (initialized) return; ProgressTargets ??= new List(); Progressors ??= new List(); initialized = true; } /// Update the progress value based on the Progressors sources public void UpdateProgress() { if (Progressors.Count == 0) return; //no progressors to update from float totalProgress = 0; int progressorsCount = 0; for (int i = 0; i < Progressors.Count; i++) { if (Progressors[i] == null) continue; totalProgress += Progressors[i].progress; progressorsCount++; } float newProgress = totalProgress / progressorsCount; //calculate the mean progress value if (newProgress < TOLERANCE) newProgress = 0; //if the new progress value is less than the tolerance, set it to 0 if (newProgress > 1 - TOLERANCE) newProgress = 1; //if the new progress value is greater than 1 minus the tolerance, set it to 1 if (newProgress.Approximately(progress, TOLERANCE)) //check if the progress value changed return; //if not, exit float previousProgress = progress; //store the previous progress value progress = newProgress; //set the new progress value if (previousProgress < progress) { //progress was incremented OnProgressIncremented?.Invoke(progress - previousProgress); } else if (previousProgress > progress) { //progress was decremented OnProgressDecremented?.Invoke(previousProgress - progress); } if (progress.Approximately(1, TOLERANCE)) { //if the progress value reached 1 OnProgressReachedOne?.Invoke(); } else if (progress.Approximately(0, TOLERANCE)) { //if the progress value reached 0 OnProgressReachedZero?.Invoke(); } ProgressTargets.RemoveNulls(); ProgressTargets.ForEach(t => t.UpdateTarget(this)); } private void Awake() { initialized = false; progress = -1; Initialize(); } private void OnEnable() { Progressors ??= new List(); StartUpdate(); } private void OnDisable() { StopUpdate(); } private void StartUpdate() { if (updateCoroutine != null) return; updateCoroutine = StartCoroutine(UpdateCoroutine()); } private void StopUpdate() { if (updateCoroutine == null) return; StopCoroutine(updateCoroutine); updateCoroutine = null; } private IEnumerator UpdateCoroutine() { float updateInterval = ReactorSettings.GetRuntimeTickInterval(); var wait = new WaitForSecondsRealtime(updateInterval); while (true) { yield return wait; UpdateProgress(); } } /// Remove null and duplicate targets private void ValidateTargets() => ProgressTargets = progressTargets.Where(t => t != null).Distinct().ToList(); } }