using UnityEngine; using System; namespace SLSUtilities.General { /// /// 伪随机分布 (Pseudo-Random Distribution, PRD) 计算器。 /// 给定目标概率 P,算法会得出一个常数 C。 /// 第一次判定的概率为 C,第二次为 2C,第 N 次为 NC。 /// 在判定成功后,概率回归为 C。 /// [Serializable] public class StableProbability { [SerializeField, Range(0, 1)] private float _probability; /// /// 设定的目标概率 (0.0 ~ 1.0)。 /// 每次更改时将自动重新计算常量 C。 /// public float Probability { get => _probability; set { float clamped = Mathf.Clamp01(value); if (Mathf.Abs(_probability - clamped) > 0.0001f) { _probability = clamped; C = GetCFromP(_probability); //Reset(); } } } public float C { get; private set; } public int N { get; private set; } = 1; public StableProbability() { _probability = 0f; C = 0f; N = 1; } public StableProbability(float probability) { _probability = Mathf.Clamp01(probability); C = GetCFromP(_probability); N = 1; } /// /// 进行一次概率判定。 /// /// 如果为 true,则在失败时不增加 N,从而保持每次判定的实际概率不变;如果为 false,则在失败时增加 N,使得每次判定的实际概率逐渐接近 1。 /// 判定是否成功。如果成功,累积次数归 1;如果失败,次数递增。 public bool Roll(bool retainRealProbability = false) { if (_probability <= 0f) return false; if (_probability >= 1f) return true; float currentProb = N * C; if (UnityEngine.Random.value <= currentProb) { N = 1; return true; } else { if (!retainRealProbability) N++; return false; } } /// /// 重置当前的失败次数积累。 /// public void Reset() { N = 1; } /// /// 强制重新计算自身的C值(通常用于反序列化之后) /// public void RecalculateC() { C = GetCFromP(_probability); } /// /// 基于目标概率 P 计算常量 C。(基于二分查找算法) /// public static float GetCFromP(float p) { if (p <= 0f) return 0f; if (p >= 1f) return 1f; float low = 0f; // C 的绝对上限不能超过 P 本身 float high = p; float mid = 0f; // 20次迭代二分查找精度足够 for (int i = 0; i < 20; i++) { mid = (low + high) / 2f; float pTested = SimulatePFromC(mid); if (Mathf.Abs(pTested - p) < 0.0001f) break; if (pTested < p) low = mid; else high = mid; } return mid; } /// /// 给定常量 C,计算出整体期望表现出的概率 P。 /// private static float SimulatePFromC(float C) { if (C <= 0f) return 0f; float expectedTrials = 0f; float waitProbability = 1f; // 到目前为止仍然未触发的概率 int maxN = Mathf.CeilToInt(1f / C); for (int n = 1; n <= maxN; n++) { float pSuccess = Mathf.Min(n * C, 1f); float pSuccessOnN = pSuccess * waitProbability; waitProbability *= (1f - pSuccess); expectedTrials += n * pSuccessOnN; } return 1f / expectedTrials; } } }