Automatically managed by Ultimate's Influence System
DO NOT modify manually
Prevents accumulation issues
modifierBonus
Temporary effects (buffs/debuffs)
Manually managed by gameplay code
Not affected by formula system
Cleared when effects expire
totalValue
Final calculated result
Use for ALL gameplay logic
Automatically updated
Read-only property
Correct Usage Patterns
Reading Values for Gameplay Logic
// ALWAYS use totalValue for gameplay calculations
float damage = strength.totalValue * weaponMultiplier;
float defense = armor.totalValue;
bool canAfford = gold.totalValue >= itemCost;
bool canCastSpell = mana.currentValue >= spellCost;
// For Vitals, use currentValue vs totalValue
if (health.currentValue <= 0)
{
HandleDeath();
}
// Check percentage for UI bars
float healthPercent = health.GetPercentage(); // 0-1 range
healthBar.fillAmount = healthPercent;
Modifying Attribute Values
// ModifyValue() - For gameplay changes (triggers regeneration)
health.ModifyValue(-damageAmount); // Take damage
mana.ModifyValue(-spellCost); // Cast spell
gold.ModifyValue(questReward); // Add gold
experience.ModifyValue(xpGain); // Gain XP
// SetValue() - For administrative changes (no regeneration)
health.SetValue(100f); // Load save data
health.FillToMax(); // Admin heal/cheat
level.SetValue(savedLevel); // Load character level
// baseValue - For permanent upgrades
strength.baseValue += 2; // Permanent stat increase
health.baseValue += 50; // Equipment bonus (permanent)
Key Rule: Use ModifyValue() for gameplay changes that should trigger regeneration. Use SetValue() for administrative changes that shouldn't.
Common Mistakes to Avoid
These patterns will cause bugs or missing functionality!
// WRONG - Using baseValue for gameplay (misses bonuses!)
float damage = strength.baseValue * weaponMultiplier; // Missing formula + modifier bonuses!
// WRONG - Using old 'value' property (doesn't exist)
float health = healthAttr.value; // Compile error - use totalValue or currentValue
// WRONG - Manually modifying formulaBonus
strength.formulaBonus += 10; // Will be overwritten by Ultimate's Influence System!
// WRONG - Using GetCurrentValue() for non-Vitals
float str = strength.GetCurrentValue(); // Use totalValue instead
// WRONG - Direct assignment without proper methods
health.currentValue = 100; // Use SetValue() or ModifyValue() instead
Automatic Regeneration System
Regeneration is completely automatic for Vital attributes with canRegenerate = true.
How It Works
1Damage Occurs:health.ModifyValue(-30f)
2Delay Starts: Waits for regenerationDelay seconds
3Regeneration Begins: Heals at regenerationRate per second
4Auto Stops: When reaching max or taking more damage
Configuration
// Enable and configure regeneration
health.canRegenerate = true;
health.regenerationRate = 2.5f; // 2.5 HP per second
health.regenerationDelay = 3f; // 3 second delay after damage
// Check regeneration state
bool isRegenerating = health.IsRegenerating;
// NO MANUAL CALLS NEEDED - IT'S AUTOMATIC!
Best Practice: Let the system handle regeneration automatically. Just use ModifyValue() for damage and healing.
Practical Examples
Combat System
public class CombatSystem : MonoBehaviour
{
[Header("Cached References")]
private SimpleRuntimeAttribute health;
private SimpleRuntimeAttribute mana;
private SimpleRuntimeAttribute strength;
private SimpleRuntimeAttribute defense;
void Start()
{
var attributes = GetComponent<CharacterAttributes>();
// Cache references for performance
health = attributes.health;
mana = attributes.mana;
strength = attributes.strength;
defense = attributes.defense;
}
public void PerformAttack(CombatSystem target)
{
// Calculate damage using totalValue (includes all bonuses)
float baseDamage = strength.totalValue * 2f;
float targetDefense = target.defense.totalValue;
float finalDamage = Mathf.Max(1f, baseDamage - targetDefense);
// Deal damage - triggers regeneration automatically
target.health.ModifyValue(-finalDamage);
Debug.Log($"Dealt {finalDamage:F1} damage");
// Check for death
if (target.health.IsAtMin())
{
HandleDeath(target);
}
}
public void CastSpell(float manaCost, float spellPower)
{
// Check if can cast
if (mana.currentValue < manaCost)
{
Debug.Log("Not enough mana!");
return;
}
// Consume mana
mana.ModifyValue(-manaCost);
// Spell effects based on totalValue
float finalPower = spellPower * intelligence.totalValue;
// ... perform spell
}
private void HandleDeath(CombatSystem target)
{
Debug.Log($"{target.name} died!");
// Death logic
}
}
Buff/Debuff System
public class EffectSystem : MonoBehaviour
{
private CharacterAttributes attributes;
private List<ActiveEffect> activeEffects = new List<ActiveEffect>();
void Start()
{
attributes = GetComponent<CharacterAttributes>();
}
public void ApplyBuff(string attributeName, float amount, float duration)
{
var attribute = attributes.GetRuntimeAttribute(attributeName);
if (attribute == null) return;
// Apply temporary modifier
attribute.modifierBonus += amount;
// Track for removal
var effect = new ActiveEffect(attribute, amount, duration);
activeEffects.Add(effect);
Debug.Log($"Applied +{amount} {attributeName} for {duration}s");
}
void Update()
{
// Update active effects
for (int i = activeEffects.Count - 1; i >= 0; i--)
{
var effect = activeEffects[i];
effect.timeLeft -= Time.deltaTime;
if (effect.timeLeft <= 0)
{
// Remove effect
effect.attribute.modifierBonus -= effect.amount;
activeEffects.RemoveAt(i);
Debug.Log($"Effect expired on {effect.attribute.attributeName}");
}
}
}
}
[System.Serializable]
public class ActiveEffect
{
public SimpleRuntimeAttribute attribute;
public float amount;
public float timeLeft;
public ActiveEffect(SimpleRuntimeAttribute attr, float amt, float duration)
{
attribute = attr;
amount = amt;
timeLeft = duration;
}
}
UI Update System
public class AttributeUI : MonoBehaviour
{
[Header("UI Elements")]
public Slider healthBar;
public Text healthText;
public Text strengthText;
public Text statusText;
[Header("Performance")]
public float updateInterval = 0.1f; // Limit UI update frequency
private CharacterAttributes attributes;
private float lastUIUpdate;
void Start()
{
attributes = GetComponent<CharacterAttributes>();
// Subscribe to events for immediate updates
attributes.health.OnValueChanged += UpdateHealthUI;
attributes.strength.OnValueChanged += UpdateStrengthUI;
}
void Update()
{
// Throttled UI updates for performance
if (Time.time - lastUIUpdate >= updateInterval)
{
UpdateAllUI();
lastUIUpdate = Time.time;
}
}
private void UpdateHealthUI()
{
var health = attributes.health;
// Update bar and text
healthBar.value = health.GetPercentage();
healthText.text = $"{health.currentValue:F0}/{health.totalValue:F0}";
// Show regeneration status
if (health.IsRegenerating)
{
statusText.text = "Regenerating...";
statusText.color = Color.green;
}
else if (health.IsAtMin())
{
statusText.text = "Critical!";
statusText.color = Color.red;
}
else
{
statusText.text = "";
}
}
private void UpdateStrengthUI()
{
var str = attributes.strength;
// Show value with bonus breakdown
string text = str.totalValue.ToString("F0");
if (str.formulaBonus != 0 || str.modifierBonus != 0)
{
text += " (";
text += str.baseValue.ToString("F0");
if (str.formulaBonus != 0) text += $"+{str.formulaBonus:F0}";
if (str.modifierBonus != 0) text += $"+{str.modifierBonus:F0}";
text += ")";
}
strengthText.text = $"STR: {text}";
}
private void UpdateAllUI()
{
// Clear dirty flags after update
foreach (var attr in attributes.GetAllRuntimeAttributes().Values)
{
if (attr.IsDirty)
{
attr.ClearDirtyFlag();
}
}
}
}
Save/Load System
[System.Serializable]
public class SaveData
{
public Dictionary<string, float> baseValues = new Dictionary<string, float>();
public Dictionary<string, float> currentValues = new Dictionary<string, float>();
}
public class SaveSystem : MonoBehaviour
{
public SaveData CreateSaveData()
{
var data = new SaveData();
var attributes = GetComponent<CharacterAttributes>();
foreach (var kvp in attributes.GetAllRuntimeAttributes())
{
var attr = kvp.Value;
// Save base values (persistent)
data.baseValues[attr.attributeName] = attr.baseValue;
// Save current values for Vitals
if (attr.behaviorType == SimpleBehaviorType.Vital)
{
data.currentValues[attr.attributeName] = attr.currentValue;
}
}
return data;
}
public void LoadSaveData(SaveData data)
{
var attributes = GetComponent<CharacterAttributes>();
// Load base values
foreach (var kvp in data.baseValues)
{
var attr = attributes.GetRuntimeAttribute(kvp.Key);
if (attr != null)
{
attr.baseValue = kvp.Value; // Use SetValue() to avoid regeneration
}
}
// Load current values for Vitals
foreach (var kvp in data.currentValues)
{
var attr = attributes.GetRuntimeAttribute(kvp.Key);
if (attr != null && attr.behaviorType == SimpleBehaviorType.Vital)
{
attr.SetValue(kvp.Value); // Use SetValue() to avoid triggering regen
}
}
// Recalculate formulas if using Ultimate's Influence System
var orchestrator = GetComponent<InfluenceSystem>();
if (orchestrator != null)
{
orchestrator.RecalculateAll();
}
Debug.Log("Game loaded successfully!");
}
}
Debug and Testing
Inspector Testing
Generated components include testing tools:
// Context menu methods available in editor
[ContextMenu("Log All Attributes")]
[ContextMenu("Debug Active Regeneration")]
[ContextMenu("Set Attribute Value")] // Uses inspector test values