using System; using System.Collections.Generic; using System.IO; using System.Text; using UnityEngine; using Bansonic; #if UNITY_EDITOR using UnityEditor; using UnityEditor.SceneManagement; using UnityEditorInternal; #endif public class equipmentRandomTestButton : MonoBehaviour { private const string EditorOutputFolder = "Assets/Resources/so/uEquip"; [Serializable] public class GeneratedStatValue { public string statName; public float value; } [Serializable] public class GeneratedEffectValue { public equipmentSO.EquipmentSpecialEffectType effectType; public string displayName; public float value; public Sprite displayIcon; public int skillId; } [Header("Source")] public equipmentRandomConfigSO randomConfig; public equipmentSO sourceEquipment; public bool saveGeneratedAssetInEditor = true; public equipBag targetEquipBag; [Header("Generated Preview")] public equipmentSO lastGeneratedEquipment; public List generatedBasicAttributes = new List(); public List generatedTypeSameEffects = new List(); public List generatedSpecialEffects = new List(); public List generatedIllusionEffects = new List(); [TextArea(4, 12)] public string lastGenerationSummary; public string lastGeneratedAssetPath; [ContextMenu("Generate Equipment Preview")] public void GenerateEquipmentPreview() { GenerateEquipmentSO(); } [ContextMenu("Refresh Equipment List")] public void RefreshEquipmentList() { if (targetEquipBag != null) { targetEquipBag.Rebuild(); } equipBag[] bags = FindObjectsOfType(true); if (bags == null) { return; } for (int i = 0; i < bags.Length; i++) { if (bags[i] != null && bags[i] != targetEquipBag) { bags[i].Rebuild(); } } } [ContextMenu("Generate Equipment SO")] public void GenerateEquipmentSO() { generatedBasicAttributes.Clear(); generatedTypeSameEffects.Clear(); generatedSpecialEffects.Clear(); generatedIllusionEffects.Clear(); lastGenerationSummary = string.Empty; lastGeneratedEquipment = null; lastGeneratedAssetPath = string.Empty; Debug.Log("[equipmentRandomTestButton] GenerateEquipmentSO invoked.", this); if (randomConfig == null || sourceEquipment == null) { lastGenerationSummary = "Missing randomConfig or sourceEquipment."; Debug.LogWarning("[equipmentRandomTestButton] Missing randomConfig or sourceEquipment."); return; } randomConfig.NormalizeRuntimeData(); #if UNITY_EDITOR if (!Application.isPlaying) { EditorUtility.SetDirty(randomConfig); } #endif equipmentSO generated = Instantiate(sourceEquipment); generated.name = equipmentGenerator.GenerateUniqueEquipmentName(sourceEquipment != null ? sourceEquipment.skillType : 0); generated.hideFlags = HideFlags.None; generated.level = 0; generated.sa_skillID = 0; generated.ia_skillID = 0; generated.enableTypeSameEffects = sourceEquipment != null && sourceEquipment.enableTypeSameEffects; generated.typeSameEffects = Array.Empty(); ResetBasicGains(generated); GenerateBasicAttributes(generated); GenerateTypeSameEffects(generated); GenerateSpecialEffects(generated); GenerateIllusionEffects(generated); lastGeneratedEquipment = PersistGeneratedEquipment(generated); if (lastGeneratedEquipment != null) { gItemGet.display(gItemGet.FromEquipment(lastGeneratedEquipment, 1)); } BuildSummary(generated); RefreshEquipmentList(); MarkHostDirty(); } private void GenerateBasicAttributes(equipmentSO generated) { if (generated == null) { return; } var candidates = new List<(string name, equipmentSO.EquipmentStatTuning tuning, equipmentRandomConfigSO.RandomBasicAttributeType type)> { ("MaxHp", generated.maxHp, equipmentRandomConfigSO.RandomBasicAttributeType.MaxHp), ("Attack", generated.attack, equipmentRandomConfigSO.RandomBasicAttributeType.Attack), ("MaxMana", generated.maxMana, equipmentRandomConfigSO.RandomBasicAttributeType.MaxMana), ("DamageResistance", generated.damageResistance, equipmentRandomConfigSO.RandomBasicAttributeType.DamageResistance), ("ScoreEfficiency", generated.scoreEfficiency, equipmentRandomConfigSO.RandomBasicAttributeType.ScoreEfficiency) }; candidates.RemoveAll(entry => entry.tuning == null || !randomConfig.TryGetBasicRange(entry.type, out _, out _)); int count = Mathf.Clamp(randomConfig.randomBasicAttributeCount, 0, candidates.Count); for (int i = 0; i < count; i++) { int pickIndex = UnityEngine.Random.Range(0, candidates.Count); var picked = candidates[pickIndex]; candidates.RemoveAt(pickIndex); randomConfig.TryGetBasicRange(picked.type, out float minValue, out float maxValue); float rolledValue = RollBiasedValue(minValue, maxValue); picked.tuning.basicGain = rolledValue; generatedBasicAttributes.Add(new GeneratedStatValue { statName = picked.name, value = rolledValue }); } MarkEquipmentDirty(generated); } private void GenerateSpecialEffects(equipmentSO generated) { if (generated == null) { return; } int effectCount = RollSpecialEffectCount(); if (effectCount <= 0) { return; } HashSet selectedBasicTypes = GetSelectedBasicAttributeTypes(generated); List valuePool = BuildSpecialEffectPool(selectedBasicTypes, generated.enableTypeSameEffects); if (valuePool.Count == 0) { generated.specialEffects = Array.Empty(); generated.sa_skillID = 0; MarkEquipmentDirty(generated); return; } for (int i = 0; i < effectCount; i++) { int pickIndex = UnityEngine.Random.Range(0, valuePool.Count); equipmentSO.EquipmentSpecialEffectType effectType = valuePool[pickIndex]; if (!TryMapToSpecialRange(effectType, out equipmentRandomConfigSO.RandomSpecialEffectType rangeType)) { continue; } if (!randomConfig.TryGetSpecialRange(rangeType, out float minValue, out float maxValue)) { continue; } generatedSpecialEffects.Add(new GeneratedEffectValue { effectType = effectType, displayName = effectType.ToString(), value = RollBiasedValue(minValue, maxValue), displayIcon = null, skillId = 0 }); } ResolveDuplicateEffects(generatedSpecialEffects); TryGenerateSpecialSkill(generated); generated.specialEffects = BuildGeneratedEffectArray(generatedSpecialEffects); MarkEquipmentDirty(generated); } private void GenerateTypeSameEffects(equipmentSO generated) { if (generated == null) { return; } generated.typeSameEffects = Array.Empty(); HashSet selectedBasicTypes = GetSelectedBasicAttributeTypes(generated); if (selectedBasicTypes.Count == 0) { return; } var generatedEffects = new List(); foreach (equipmentSO.EquipmentSpecialEffectType effectType in selectedBasicTypes) { if (!TryMapToSpecialRange(effectType, out equipmentRandomConfigSO.RandomSpecialEffectType rangeType)) { continue; } if (!randomConfig.TryGetTypeSameEffectRange(rangeType, out float minValue, out float maxValue)) { continue; } float rolledValue = RollBiasedValue(minValue, maxValue); generatedEffects.Add(new equipmentSO.EquipmentSpecialEffect { effectType = effectType, value = rolledValue }); generatedTypeSameEffects.Add(new GeneratedEffectValue { effectType = effectType, displayName = effectType.ToString(), value = rolledValue, displayIcon = null, skillId = 0 }); } generated.typeSameEffects = generatedEffects.ToArray(); MarkEquipmentDirty(generated); } private void TryGenerateSpecialSkill(equipmentSO generated) { if (generated == null || generatedSpecialEffects.Count == 0) { generated.sa_skillID = 0; return; } if (UnityEngine.Random.value > randomConfig.sesp) { generated.sa_skillID = 0; return; } if (!randomConfig.TryGetSpecialSkillId(generated.skillType, out int skillId) || skillId <= 0) { generated.sa_skillID = 0; return; } int replaceIndex = UnityEngine.Random.Range(0, generatedSpecialEffects.Count); generatedSpecialEffects.RemoveAt(replaceIndex); generatedSpecialEffects.Add(new GeneratedEffectValue { effectType = equipmentSO.EquipmentSpecialEffectType.Skill, displayName = "Skill", value = 0f, displayIcon = null, skillId = skillId }); generated.sa_skillID = skillId; } private List BuildSpecialEffectPool( HashSet selectedBasicTypes, bool alignToSelectedBasicTypes) { var pool = new List(); if (alignToSelectedBasicTypes && selectedBasicTypes != null && selectedBasicTypes.Count > 0) { foreach (equipmentSO.EquipmentSpecialEffectType effectType in selectedBasicTypes) { if (!TryMapToSpecialRange(effectType, out equipmentRandomConfigSO.RandomSpecialEffectType rangeType)) { continue; } if (randomConfig.TryGetSpecialRange(rangeType, out _, out _)) { pool.Add(effectType); } } return pool; } equipmentSO.EquipmentSpecialEffectType[] allTypes = { equipmentSO.EquipmentSpecialEffectType.MaxHp, equipmentSO.EquipmentSpecialEffectType.Attack, equipmentSO.EquipmentSpecialEffectType.MaxMana, equipmentSO.EquipmentSpecialEffectType.DamageResistance, equipmentSO.EquipmentSpecialEffectType.ScoreEfficiency }; for (int i = 0; i < allTypes.Length; i++) { if (!TryMapToSpecialRange(allTypes[i], out equipmentRandomConfigSO.RandomSpecialEffectType rangeType)) { continue; } if (randomConfig.TryGetSpecialRange(rangeType, out _, out _)) { pool.Add(allTypes[i]); } } return pool; } private void GenerateIllusionEffects(equipmentSO generated) { if (generated == null || generated.illusionEffects == null || generated.illusionEffects.Length == 0) { return; } generated.ia_skillID = 0; int effectCount = Mathf.Min(RollSpecialEffectCount(), generated.illusionEffects.Length); List skillPool = new List(); List valuePool = new List(); for (int i = 0; i < generated.illusionEffects.Length; i++) { equipmentSO.EquipmentSpecialEffect effect = generated.illusionEffects[i]; if (effect == null) { continue; } if (effect.effectType == equipmentSO.EquipmentSpecialEffectType.Skill) { skillPool.Add(effect); } else if (TryMapToIllusionRange(effect.effectType, out _)) { valuePool.Add(effect); } } for (int slotIndex = 0; slotIndex < effectCount; slotIndex++) { bool shouldGenerateSkill = skillPool.Count > 0 && UnityEngine.Random.value <= GetIllusionSkillChance(slotIndex); if (shouldGenerateSkill) { if (!randomConfig.TryGetIllusionSkillId(out int illusionSkillId) || illusionSkillId <= 0) { shouldGenerateSkill = false; } else { int pickSkillIndex = UnityEngine.Random.Range(0, skillPool.Count); equipmentSO.EquipmentSpecialEffect skillEffect = skillPool[pickSkillIndex]; skillPool.RemoveAt(pickSkillIndex); generatedIllusionEffects.Add(new GeneratedEffectValue { effectType = skillEffect.effectType, displayName = "Skill", value = 0f, displayIcon = null, skillId = illusionSkillId }); generated.ia_skillID = illusionSkillId; continue; } } if (valuePool.Count == 0) { break; } int pickValueIndex = UnityEngine.Random.Range(0, valuePool.Count); equipmentSO.EquipmentSpecialEffect effect = valuePool[pickValueIndex]; valuePool.RemoveAt(pickValueIndex); if (!TryMapToIllusionRange(effect.effectType, out equipmentRandomConfigSO.RandomIllusionEffectType rangeType)) { slotIndex--; continue; } if (!randomConfig.TryGetIllusionRange(rangeType, out float minValue, out float maxValue)) { slotIndex--; continue; } generatedIllusionEffects.Add(new GeneratedEffectValue { effectType = effect.effectType, displayName = effect.effectType.ToString(), value = RollBiasedValue(minValue, maxValue), displayIcon = null, skillId = 0 }); } generated.illusionEffects = BuildGeneratedEffectArray(generatedIllusionEffects); MarkEquipmentDirty(generated); } private int RollSpecialEffectCount() { float roll = UnityEngine.Random.value; if (roll <= randomConfig.se01p) { return 1; } if (roll <= randomConfig.se01p + randomConfig.se02p) { return 2; } return 3; } private float GetIllusionSkillChance(int index) { switch (index) { case 0: return randomConfig.ies01; case 1: return randomConfig.ies02; case 2: return randomConfig.ies03; case 3: return randomConfig.ies04; default: return 0f; } } private static bool TryMapToSpecialRange( equipmentSO.EquipmentSpecialEffectType effectType, out equipmentRandomConfigSO.RandomSpecialEffectType rangeType) { switch (effectType) { case equipmentSO.EquipmentSpecialEffectType.MaxHp: rangeType = equipmentRandomConfigSO.RandomSpecialEffectType.MaxHp; return true; case equipmentSO.EquipmentSpecialEffectType.Attack: rangeType = equipmentRandomConfigSO.RandomSpecialEffectType.Attack; return true; case equipmentSO.EquipmentSpecialEffectType.MaxMana: rangeType = equipmentRandomConfigSO.RandomSpecialEffectType.MaxMana; return true; case equipmentSO.EquipmentSpecialEffectType.DamageResistance: rangeType = equipmentRandomConfigSO.RandomSpecialEffectType.DamageResistance; return true; case equipmentSO.EquipmentSpecialEffectType.ScoreEfficiency: rangeType = equipmentRandomConfigSO.RandomSpecialEffectType.ScoreEfficiency; return true; default: rangeType = default; return false; } } private static bool TryMapToIllusionRange( equipmentSO.EquipmentSpecialEffectType effectType, out equipmentRandomConfigSO.RandomIllusionEffectType rangeType) { switch (effectType) { case equipmentSO.EquipmentSpecialEffectType.MaxHp: rangeType = equipmentRandomConfigSO.RandomIllusionEffectType.MaxHp; return true; case equipmentSO.EquipmentSpecialEffectType.Attack: rangeType = equipmentRandomConfigSO.RandomIllusionEffectType.Attack; return true; case equipmentSO.EquipmentSpecialEffectType.MaxMana: rangeType = equipmentRandomConfigSO.RandomIllusionEffectType.MaxMana; return true; case equipmentSO.EquipmentSpecialEffectType.DamageResistance: rangeType = equipmentRandomConfigSO.RandomIllusionEffectType.DamageResistance; return true; case equipmentSO.EquipmentSpecialEffectType.ScoreEfficiency: rangeType = equipmentRandomConfigSO.RandomIllusionEffectType.ScoreEfficiency; return true; case equipmentSO.EquipmentSpecialEffectType.HitManaRecovery: rangeType = equipmentRandomConfigSO.RandomIllusionEffectType.HitManaRecovery; return true; case equipmentSO.EquipmentSpecialEffectType.HitDamageMultiplier: rangeType = equipmentRandomConfigSO.RandomIllusionEffectType.HitDamageMultiplier; return true; case equipmentSO.EquipmentSpecialEffectType.HpLoseBase: rangeType = equipmentRandomConfigSO.RandomIllusionEffectType.HpLoseBase; return true; default: rangeType = default; return false; } } private void BuildSummary(equipmentSO generated) { StringBuilder builder = new StringBuilder(); builder.AppendLine("Basic Attributes:"); for (int i = 0; i < generatedBasicAttributes.Count; i++) { builder.AppendLine($"- {generatedBasicAttributes[i].statName}: {generatedBasicAttributes[i].value:0.####}"); } builder.AppendLine("Special Effects:"); for (int i = 0; i < generatedSpecialEffects.Count; i++) { GeneratedEffectValue effect = generatedSpecialEffects[i]; if (effect.effectType == equipmentSO.EquipmentSpecialEffectType.Skill) { builder.AppendLine($"- Skill: {effect.skillId}"); } else { builder.AppendLine($"- {effect.effectType}: {effect.value:0.####}"); } } builder.AppendLine("Type Same Effects:"); for (int i = 0; i < generatedTypeSameEffects.Count; i++) { GeneratedEffectValue effect = generatedTypeSameEffects[i]; builder.AppendLine($"- {effect.effectType}: {effect.value:0.####}"); } builder.AppendLine("Illusion Effects:"); for (int i = 0; i < generatedIllusionEffects.Count; i++) { GeneratedEffectValue effect = generatedIllusionEffects[i]; if (effect.effectType == equipmentSO.EquipmentSpecialEffectType.Skill) { builder.AppendLine($"- Skill"); } else { builder.AppendLine($"- {effect.effectType}: {effect.value:0.####}"); } } if (generated != null && generated.sa_skillID > 0) { builder.AppendLine($"sa_skillID: {generated.sa_skillID}"); } if (generatedBasicAttributes.Count == 0 && generatedTypeSameEffects.Count == 0 && generatedSpecialEffects.Count == 0 && generatedIllusionEffects.Count == 0) { builder.AppendLine(); builder.AppendLine("No values generated."); builder.AppendLine($"randomBasicAttributeCount={randomConfig.randomBasicAttributeCount}"); builder.AppendLine($"basicAttributeRangesDefined={(randomConfig.basicAttributeRanges == null ? 0 : randomConfig.basicAttributeRanges.Count)}"); builder.AppendLine($"specialEffectsDefined={(sourceEquipment.specialEffects == null ? 0 : sourceEquipment.specialEffects.Length)}"); builder.AppendLine($"illusionEffectsDefined={(sourceEquipment.illusionEffects == null ? 0 : sourceEquipment.illusionEffects.Length)}"); } lastGenerationSummary = builder.ToString().TrimEnd(); Debug.Log("[equipmentRandomTestButton] Generated equipment preview:\n" + lastGenerationSummary, this); } private void ResetBasicGains(equipmentSO targetEquipment) { if (targetEquipment == null) { return; } if (targetEquipment.maxHp != null) targetEquipment.maxHp.basicGain = 0f; if (targetEquipment.attack != null) targetEquipment.attack.basicGain = 0f; if (targetEquipment.maxMana != null) targetEquipment.maxMana.basicGain = 0f; if (targetEquipment.damageResistance != null) targetEquipment.damageResistance.basicGain = 0f; if (targetEquipment.scoreEfficiency != null) targetEquipment.scoreEfficiency.basicGain = 0f; } private void MarkEquipmentDirty(equipmentSO targetEquipment) { #if UNITY_EDITOR if (!Application.isPlaying && targetEquipment != null) { EditorUtility.SetDirty(targetEquipment); AssetDatabase.SaveAssets(); } #endif } private equipmentSO PersistGeneratedEquipment(equipmentSO generated) { if (generated == null) { return null; } #if UNITY_EDITOR if (!Application.isPlaying && saveGeneratedAssetInEditor) { EnsureEditorOutputFolderExists(); string assetPath = AssetDatabase.GenerateUniqueAssetPath($"{EditorOutputFolder}/{generated.name}.asset"); AssetDatabase.CreateAsset(generated, assetPath); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); lastGeneratedAssetPath = assetPath; return AssetDatabase.LoadAssetAtPath(assetPath); } #endif return equipmentGenerator.RegisterGeneratedEquipment(generated); } private static void WriteGeneratedEffectsBack( equipmentSO.EquipmentSpecialEffect[] targetEffects, List generatedEffects) { if (targetEffects == null) { return; } for (int i = 0; i < targetEffects.Length; i++) { equipmentSO.EquipmentSpecialEffect target = targetEffects[i]; if (target == null) { continue; } target.value = 0f; } for (int i = 0; i < generatedEffects.Count; i++) { GeneratedEffectValue generated = generatedEffects[i]; if (generated.effectType == equipmentSO.EquipmentSpecialEffectType.Skill) { continue; } for (int j = 0; j < targetEffects.Length; j++) { equipmentSO.EquipmentSpecialEffect target = targetEffects[j]; if (target == null) { continue; } if (target.effectType == generated.effectType) { target.value = generated.value; break; } } } } private static equipmentSO.EquipmentSpecialEffect[] BuildGeneratedEffectArray(List generatedEffects) { if (generatedEffects == null || generatedEffects.Count == 0) { return Array.Empty(); } var results = new List(generatedEffects.Count); for (int i = 0; i < generatedEffects.Count; i++) { GeneratedEffectValue generated = generatedEffects[i]; results.Add(new equipmentSO.EquipmentSpecialEffect { effectType = generated.effectType, value = generated.effectType == equipmentSO.EquipmentSpecialEffectType.Skill ? 0f : generated.value }); } return results.ToArray(); } private HashSet GetSelectedBasicAttributeTypes(equipmentSO targetEquipment) { var selected = new HashSet(); if (targetEquipment == null) { return selected; } if (targetEquipment.maxHp != null && !Mathf.Approximately(targetEquipment.maxHp.basicGain, 0f)) selected.Add(equipmentSO.EquipmentSpecialEffectType.MaxHp); if (targetEquipment.attack != null && !Mathf.Approximately(targetEquipment.attack.basicGain, 0f)) selected.Add(equipmentSO.EquipmentSpecialEffectType.Attack); if (targetEquipment.maxMana != null && !Mathf.Approximately(targetEquipment.maxMana.basicGain, 0f)) selected.Add(equipmentSO.EquipmentSpecialEffectType.MaxMana); if (targetEquipment.damageResistance != null && !Mathf.Approximately(targetEquipment.damageResistance.basicGain, 0f)) selected.Add(equipmentSO.EquipmentSpecialEffectType.DamageResistance); if (targetEquipment.scoreEfficiency != null && !Mathf.Approximately(targetEquipment.scoreEfficiency.basicGain, 0f)) selected.Add(equipmentSO.EquipmentSpecialEffectType.ScoreEfficiency); return selected; } #if UNITY_EDITOR private static void EnsureEditorOutputFolderExists() { if (AssetDatabase.IsValidFolder(EditorOutputFolder)) { return; } if (!AssetDatabase.IsValidFolder("Assets/Resources")) { AssetDatabase.CreateFolder("Assets", "Resources"); } if (!AssetDatabase.IsValidFolder("Assets/Resources/so")) { AssetDatabase.CreateFolder("Assets/Resources", "so"); } if (!AssetDatabase.IsValidFolder(EditorOutputFolder)) { AssetDatabase.CreateFolder("Assets/Resources/so", "uEquip"); } } #endif private void MarkHostDirty() { #if UNITY_EDITOR if (!Application.isPlaying) { EditorUtility.SetDirty(this); if (gameObject != null && gameObject.scene.IsValid()) { EditorSceneManager.MarkSceneDirty(gameObject.scene); } InternalEditorUtility.RepaintAllViews(); } #endif } private static float RollBiasedValue(float minValue, float maxValue) { if (Mathf.Approximately(minValue, maxValue)) { return AvoidZero(minValue, maxValue, minValue); } if (maxValue < minValue) { float cached = minValue; minValue = maxValue; maxValue = cached; } float span = maxValue - minValue; float roll = UnityEngine.Random.value; if (roll <= 0.05f) { return AvoidZero(minValue, maxValue, UnityEngine.Random.Range(minValue, minValue + span * 0.1f)); } if (roll >= 0.95f) { return AvoidZero(minValue, maxValue, UnityEngine.Random.Range(maxValue - span * 0.1f, maxValue)); } float gaussian = SampleClampedGaussian01(); return AvoidZero(minValue, maxValue, Mathf.Lerp(minValue, maxValue, gaussian)); } private static float SampleClampedGaussian01() { float u1 = Mathf.Clamp01(UnityEngine.Random.value); float u2 = Mathf.Clamp01(UnityEngine.Random.value); u1 = Mathf.Max(u1, 1e-6f); float standardNormal = Mathf.Sqrt(-2f * Mathf.Log(u1)) * Mathf.Cos(2f * Mathf.PI * u2); float centered = 0.5f + standardNormal * 0.18f; return Mathf.Clamp01(centered); } private static void ResolveDuplicateEffects(List effects) { if (effects == null || effects.Count <= 1) { return; } var bestByType = new Dictionary(); for (int i = 0; i < effects.Count; i++) { GeneratedEffectValue candidate = effects[i]; if (!bestByType.TryGetValue(candidate.effectType, out GeneratedEffectValue existing)) { bestByType[candidate.effectType] = candidate; continue; } bestByType[candidate.effectType] = ChoosePreferred(existing, candidate); } effects.Clear(); foreach (GeneratedEffectValue value in bestByType.Values) { effects.Add(value); } } private static GeneratedEffectValue ChoosePreferred(GeneratedEffectValue left, GeneratedEffectValue right) { bool leftNegative = left.value < 0f; bool rightNegative = right.value < 0f; if (leftNegative && rightNegative) { return left.value <= right.value ? left : right; } if (!leftNegative && !rightNegative) { return left.value >= right.value ? left : right; } return Mathf.Abs(left.value) >= Mathf.Abs(right.value) ? left : right; } private static float AvoidZero(float minValue, float maxValue, float value) { if (!Mathf.Approximately(value, 0f)) { return value; } if (minValue > 0f || maxValue < 0f) { return value; } float epsilon = Mathf.Min(0.0001f, Mathf.Max(Mathf.Abs(minValue), Mathf.Abs(maxValue), 0.0001f)); return UnityEngine.Random.value < 0.5f ? -epsilon : epsilon; } }