using System; using System.Collections.Generic; using System.Linq; using TMPro; using UnityEngine; using UnityEngine.EventSystems; public class TargetLevelProbabilityPanel : MonoBehaviour { public GameObject singleLevelProbabilityPanel; public GameObject targetTitleText; private GameObject titleText; public List singleLevelPanelsObjs = new List(); public List singleLevelPanels = new List(); private int panelNum = 0; public void IntializePanels(int levelNum, string titleName) { // initialize target level probability panel size float defaultWidth = singleLevelProbabilityPanel.GetComponent().sizeDelta.x; float defaultLevelHeight = singleLevelProbabilityPanel.GetComponent().sizeDelta.y; float titleHeight = targetTitleText.GetComponent().sizeDelta.y; float averageProbability = 1f / levelNum; float lastLevelProbability = 1f - averageProbability * (levelNum - 1); // Debug.Log("averageProbability: " + averageProbability); transform.GetComponent().sizeDelta = new Vector2(defaultWidth, (defaultLevelHeight * levelNum) + titleHeight); // create title text titleText = Instantiate(targetTitleText, transform); titleText.GetComponent().text = titleName; // create and initialize single level probability panels for (int i = 0; i < levelNum; i++) { int tempIndex = i; singleLevelPanelsObjs.Add(Instantiate(singleLevelProbabilityPanel, transform)); singleLevelPanels.Add(singleLevelPanelsObjs[i].GetComponent()); singleLevelPanels[i].InitializeLevelProbabilityPanel(i, i == levelNum - 1 ? lastLevelProbability : averageProbability); //add onValueChanged event to slider and input field singleLevelPanels[i].probabilitySlider.onValueChanged.AddListener((value) => OnProbabilityValueChange(value, tempIndex)); singleLevelPanels[i].inputField.onEndEdit.AddListener((value) => OnProbabilityValueChange(value, tempIndex)); } panelNum = levelNum; } /// /// Adds an event trigger entry to an event trigger. /// /// The event trigger object. /// The event trigger type. /// The event handler method to execute. private void AddEventTrigger(GameObject gameObject, EventTriggerType triggerType, System.Action action) { EventTrigger eventTrigger = gameObject.GetComponent(); if (eventTrigger == null) { eventTrigger = gameObject.AddComponent(); } EventTrigger.Entry entry = new EventTrigger.Entry(); entry.eventID = triggerType; entry.callback.AddListener(new UnityEngine.Events.UnityAction(action)); eventTrigger.triggers.Add(entry); } /// /// On Probability Slider Value Change.Adjust other sliders' value to make sure the total value is 1. /// /// change to this value /// changed panel index private void OnProbabilityValueChange(T value, int exceptedIndex) { float changedValue = 0f; switch (value) { case float floatValue: changedValue = floatValue; break; case string stringValue: changedValue = float.Parse(stringValue); // limit the value between 0 and 1 if(changedValue>1 && changedValue <=100) { changedValue /= 100; }else if(changedValue>100) { changedValue = 1; } break; default: Debug.LogError("Invalid value type!"); throw new ArgumentException("Unsupported value type"); } Debug.Log(value); float newTotalValue = 0; // new total probability value without locked panel int unlockedPanelNum = 0; int remainCorrectionNum = panelNum; float extraValue = 0; // the extra value after probability value changed float maxLimitValue = 1; // the max value of unlocked panel permitted float[] correctionValues = new float[panelNum]; // calculate total probability value for (int i = 0; i < panelNum; i++) { // disable slider listener singleLevelPanels[i].probabilitySlider.onValueChanged.RemoveAllListeners(); if (singleLevelPanels[i].UnLocked) { unlockedPanelNum++; newTotalValue += (i == exceptedIndex ? changedValue : singleLevelPanels[i].ProbabilityValue); } else { // delete locked probability value from maxLimitValue maxLimitValue -= singleLevelPanels[i].ProbabilityValue; } } // only have one panel if (panelNum == 1) { singleLevelPanels[exceptedIndex].SetProbability(1); enableSliderListener(); return; } //only one panel is unlocked if (unlockedPanelNum == 1) { // limit this panel value at maxLimitValue singleLevelPanels[exceptedIndex].SetProbability(maxLimitValue); enableSliderListener(); return; } // Calculate the average correction value extraValue = newTotalValue - maxLimitValue; // initialize correction value to each panel correctionValues = Enumerable.Repeat(extraValue / (unlockedPanelNum - 1), panelNum).ToArray(); // make sure all probability value is not less than 0 and equal to 1 int iterationCount = 0; while (remainCorrectionNum > 0) { iterationCount++; (correctionValues, remainCorrectionNum) = reCalculateCorrectionValues(correctionValues, exceptedIndex, changedValue, extraValue, maxLimitValue); // protect the infinite loop if (iterationCount >= 100) { Debug.LogError("Infinite loop detected!"); break; } } // applicate the value to all unlocked panels excepted the changed one applyCorrectionValue(correctionValues); enableSliderListener(); } /// /// enable all sliders' onValueChanged action listener /// private void enableSliderListener() { for (int i = 0; i < panelNum; i++) { int tempIndex = i; singleLevelPanels[i].probabilitySlider.onValueChanged.AddListener((value) => OnProbabilityValueChange(value, tempIndex)); } } /// /// calculate the correction value to each panel,while the total value is not equal to 1 /// private (float[], int) reCalculateCorrectionValues(float[] correctionValues, int exceptedIndex, float value, float extraValue, float maxLimitValue) { // the number of panels which need to be corrected in next iteration int remainCorrectionNum = 0; List reCorrectionIndex = new List(); // the index of the last panel which need to be corrected in this iteration int lastReCorrectionIndex = 0; // another extra value after correctionValue applicate to probability value which value also less than 0 // this value will be added to the panel which value is bigger. float underZeroExtraValueAfterCorrected = 0; for (int i = 0; i < panelNum; ++i) { // if the panel is the changed one if (i == exceptedIndex) { // limit the value under maxLimitValue correctionValues[i] = value > maxLimitValue ? (singleLevelPanels[i].ProbabilityValue - maxLimitValue) : (singleLevelPanels[i].ProbabilityValue - value); continue; } // if the panel is locked if (singleLevelPanels[i].IsLocked) { correctionValues[i] = 0; continue; } // if the panel is unlocked // and the probability value is less than 0 or bigger than maxLimitValue if (singleLevelPanels[i].ProbabilityValue < 0 || singleLevelPanels[i].ProbabilityValue > maxLimitValue) { // correct this panel value to 0 or maxLimitValue correctionValues[i] = singleLevelPanels[i].ProbabilityValue; } // probability value will be correct to 0 else if (singleLevelPanels[i].ProbabilityValue == correctionValues[i]) { // that's good keep this correction value, and add it to the extraValue underZeroExtraValueAfterCorrected += correctionValues[i]; } // probability value will be correct over the limit (bigger than maxLimitValue or less than 0) else if (singleLevelPanels[i].ProbabilityValue - correctionValues[i] < 0 || singleLevelPanels[i].ProbabilityValue - correctionValues[i] > maxLimitValue) { // should be corrected to 0 or maxLimitValue,and add it to the extraValue underZeroExtraValueAfterCorrected += singleLevelPanels[i].ProbabilityValue; correctionValues[i] = singleLevelPanels[i].ProbabilityValue; } // left panels correction value which need to be re-corrected else { lastReCorrectionIndex = i; reCorrectionIndex.Add(i); } } foreach (int index in reCorrectionIndex) { // calculate the average correction value float newAverageCorrectionValue = (extraValue - underZeroExtraValueAfterCorrected) / reCorrectionIndex.Count; // if the panel is the last one which need to be corrected. // Avoidance of floating-point rounding error, the last panel correction value will be the extraValue minus the sum of the other panels correction value if (index == lastReCorrectionIndex) { correctionValues[index] = extraValue - (newAverageCorrectionValue * (reCorrectionIndex.Count - 1)); // still out of limit after correction, correct it to 0 and activate next iteration if (singleLevelPanels[index].ProbabilityValue - correctionValues[index] < 0 || singleLevelPanels[index].ProbabilityValue - correctionValues[index] > maxLimitValue) { correctionValues[index] = singleLevelPanels[index].ProbabilityValue; remainCorrectionNum++; } } else { correctionValues[index] = newAverageCorrectionValue; // still out of limit after correction, correct it to 0 and activate next iteration if (singleLevelPanels[index].ProbabilityValue - correctionValues[index] < 0 || singleLevelPanels[index].ProbabilityValue - correctionValues[index] > maxLimitValue) { correctionValues[index] = singleLevelPanels[index].ProbabilityValue; remainCorrectionNum++; } } } return (correctionValues, remainCorrectionNum); } /// /// applicate correction value to each panel /// private void applyCorrectionValue(float[] correctionValues) { for (int i = 0; i < panelNum; i++) { /* if (singleLevelPanels[i].ProbabilityValue - correctionValues[i] < 0) { Debug.LogWarning("Probability value is less than 0"); Debug.Log(i); Debug.Log(singleLevelPanels[i].ProbabilityValue); Debug.Log(correctionValues[i]); }*/ singleLevelPanels[i].SetProbability(singleLevelPanels[i].ProbabilityValue - correctionValues[i]); } } }