84 lines
3.3 KiB
C#
84 lines
3.3 KiB
C#
using UnityEngine;
|
||
|
||
namespace Cielonos.MainGame.Inventory
|
||
{
|
||
/// <summary>
|
||
/// 提供基于 Luck 和 Curse 的通用随机结果。
|
||
/// 返回值始终位于 0 到 1 之间;具体含义由调用者自行映射。
|
||
/// </summary>
|
||
public static class Randomizer
|
||
{
|
||
private const float MinimumValue = 0f;
|
||
private const float MaximumValue = 1f;
|
||
private const float BiasScale = 0.5f;
|
||
private const float GoodChanceRange = 0.45f;
|
||
private const float RegularStandardDeviation = 0.165f;
|
||
private const float ExtremeStandardDeviation = 0.06f;
|
||
private const float ExtremeGoodMean = 0.9f;
|
||
private const float ExtremeBadMean = 0.1f;
|
||
private const int MaximumNormalRollAttempts = 4;
|
||
|
||
/// <summary>
|
||
/// 获取一个不受属性影响的基础随机值。
|
||
/// </summary>
|
||
public static float Roll01()
|
||
{
|
||
return Random.Range(MinimumValue, MaximumValue);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据 Luck 和 Curse 获取一个表示结果好坏的随机值。
|
||
/// 0 表示最坏结果,1 表示最好结果;两项属性均为 0 时使用中心为 0.5 的正态分布。
|
||
/// </summary>
|
||
public static float RollLuckCurse01(float luck, float curse)
|
||
{
|
||
GetDistributionParameters(luck, curse, out float bias, out float goodChance, out float extremeWeight);
|
||
|
||
if (Random.value < extremeWeight)
|
||
{
|
||
float mean = Random.value < goodChance ? ExtremeGoodMean : ExtremeBadMean;
|
||
return RollTruncatedNormal(mean, ExtremeStandardDeviation);
|
||
}
|
||
|
||
return RollTruncatedNormal(0.5f + 0.25f * bias, RegularStandardDeviation);
|
||
}
|
||
|
||
private static void GetDistributionParameters(float luck, float curse, out float bias, out float goodChance,
|
||
out float extremeWeight)
|
||
{
|
||
float normalizedLuck = Mathf.Clamp01(luck / 100f);
|
||
float normalizedCurse = Mathf.Clamp01(curse / 100f);
|
||
bias = HyperbolicTangent((normalizedLuck - normalizedCurse) / BiasScale);
|
||
goodChance = 0.5f + GoodChanceRange * bias;
|
||
extremeWeight = normalizedLuck * normalizedCurse;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Unity 的 Mathf 未提供 Tanh,因此通过指数定义计算双曲正切。
|
||
/// </summary>
|
||
private static float HyperbolicTangent(float value)
|
||
{
|
||
float exponent = Mathf.Exp(2f * value);
|
||
return (exponent - 1f) / (exponent + 1f);
|
||
}
|
||
|
||
private static float RollTruncatedNormal(float mean, float standardDeviation)
|
||
{
|
||
for (int attempt = 0; attempt < MaximumNormalRollAttempts; attempt++)
|
||
{
|
||
float value = mean + RollStandardNormal() * standardDeviation;
|
||
if (value > MinimumValue && value < MaximumValue) return value;
|
||
}
|
||
|
||
return Mathf.Clamp(mean + RollStandardNormal() * standardDeviation, MinimumValue, MaximumValue);
|
||
}
|
||
|
||
private static float RollStandardNormal()
|
||
{
|
||
float u1 = Mathf.Max(Random.value, float.Epsilon);
|
||
float u2 = Random.value;
|
||
return Mathf.Sqrt(-2f * Mathf.Log(u1)) * Mathf.Cos(2f * Mathf.PI * u2);
|
||
}
|
||
}
|
||
}
|