effect runtime instances + clean up improvements
This commit is contained in:
parent
ccda634b89
commit
9a7939b404
828
Assets/--Fundamentals/AbilitiesAndEffectsModifiers.txt
Normal file
828
Assets/--Fundamentals/AbilitiesAndEffectsModifiers.txt
Normal file
@ -0,0 +1,828 @@
|
||||
# Unity ARPG Ability Modifier System Guide
|
||||
|
||||
A comprehensive guide for implementing runtime ability modifiers in Unity ARPG games using ScriptableObjects. This system allows you to modify ability values and behaviors dynamically through equipment, talents, buffs, and other game systems.
|
||||
|
||||
## Table of Contents
|
||||
1. [System Overview](#system-overview)
|
||||
2. [Base Architecture](#base-architecture)
|
||||
3. [Value-Based Modifiers](#value-based-modifiers)
|
||||
4. [Behavior-Changing Modifiers](#behavior-changing-modifiers)
|
||||
5. [Modifier Manager Integration](#modifier-manager-integration)
|
||||
6. [Usage Examples](#usage-examples)
|
||||
7. [Advanced Examples](#advanced-examples)
|
||||
8. [Best Practices](#best-practices)
|
||||
|
||||
## System Overview
|
||||
|
||||
The modifier system works by:
|
||||
- Creating ScriptableObject-based modifiers that can be applied to runtime ability instances
|
||||
- Maintaining references to original ability data for recalculation
|
||||
- Supporting multiple modifier sources (equipment, talents, buffs, etc.)
|
||||
- Allowing both value changes and behavior modifications
|
||||
- Providing clean cleanup and removal mechanisms
|
||||
|
||||
## Base Architecture
|
||||
|
||||
### Base Modifier ScriptableObject
|
||||
|
||||
```csharp
|
||||
public abstract class BaseAbilityModifier : ScriptableObject
|
||||
{
|
||||
[Header("Modifier Info")]
|
||||
public string modifierName;
|
||||
public string description;
|
||||
public ModifierSource source; // Equipment, Talent, Buff, etc.
|
||||
public int priority = 0; // For ordering multiple modifiers
|
||||
|
||||
[SerializeField] private string _guid;
|
||||
public string GUID => _guid;
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_guid))
|
||||
_guid = System.Guid.NewGuid().ToString();
|
||||
}
|
||||
|
||||
// Apply the modifier to an ability
|
||||
public abstract void ApplyToAbility(BaseAbility ability);
|
||||
|
||||
// Remove the modifier from an ability (for temporary modifiers)
|
||||
public abstract void RemoveFromAbility(BaseAbility ability);
|
||||
|
||||
// Apply to effects within an ability
|
||||
public virtual void ApplyToEffect(BaseEffect effect) { }
|
||||
public virtual void RemoveFromEffect(BaseEffect effect) { }
|
||||
|
||||
// Check if this modifier can be applied to the given ability
|
||||
public virtual bool CanApplyTo(BaseAbility ability) => true;
|
||||
|
||||
public virtual BaseAbilityModifier CreateRuntimeInstance()
|
||||
{
|
||||
return Instantiate(this);
|
||||
}
|
||||
}
|
||||
|
||||
public enum ModifierSource
|
||||
{
|
||||
Equipment,
|
||||
Talent,
|
||||
Buff,
|
||||
Debuff,
|
||||
SetBonus,
|
||||
Enchantment
|
||||
}
|
||||
```
|
||||
|
||||
## Value-Based Modifiers
|
||||
|
||||
### Damage Modifier
|
||||
|
||||
```csharp
|
||||
[CreateAssetMenu(fileName = "New Damage Modifier", menuName = "Modifiers/Damage Modifier")]
|
||||
public class DamageModifier : BaseAbilityModifier
|
||||
{
|
||||
[Header("Damage Modifications")]
|
||||
public float flatDamageBonus = 0f;
|
||||
public float damageMultiplier = 1f;
|
||||
public List<GameTag> affectedAbilityTags = new List<GameTag>();
|
||||
|
||||
public override void ApplyToAbility(BaseAbility ability)
|
||||
{
|
||||
// Check if ability has any of the required tags
|
||||
if (affectedAbilityTags.Count > 0)
|
||||
{
|
||||
bool hasTag = false;
|
||||
foreach (var tag in affectedAbilityTags)
|
||||
{
|
||||
if (ability.abilityTags.Contains(tag))
|
||||
{
|
||||
hasTag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasTag) return;
|
||||
}
|
||||
|
||||
// Apply to all damage effects in the ability
|
||||
foreach (var effect in ability.abilityEffects)
|
||||
{
|
||||
if (effect is DamageEffect damageEffect)
|
||||
{
|
||||
ApplyToDamageEffect(damageEffect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyToDamageEffect(DamageEffect damageEffect)
|
||||
{
|
||||
damageEffect.baseDamage = (damageEffect.baseDamage + flatDamageBonus) * damageMultiplier;
|
||||
}
|
||||
|
||||
public override void RemoveFromAbility(BaseAbility ability)
|
||||
{
|
||||
// For value modifiers, removal usually means recalculating from base
|
||||
// This is handled by the modifier manager
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Cooldown Modifier
|
||||
|
||||
```csharp
|
||||
[CreateAssetMenu(fileName = "New Cooldown Modifier", menuName = "Modifiers/Cooldown Modifier")]
|
||||
public class CooldownModifier : BaseAbilityModifier
|
||||
{
|
||||
[Header("Cooldown Modifications")]
|
||||
public float cooldownReduction = 0f; // Flat reduction in seconds
|
||||
public float cooldownMultiplier = 1f; // Multiplicative (0.8 = 20% faster)
|
||||
|
||||
public override void ApplyToAbility(BaseAbility ability)
|
||||
{
|
||||
float newCooldown = (ability.cooldown - cooldownReduction) * cooldownMultiplier;
|
||||
ability.cooldown = Mathf.Max(0f, newCooldown); // Don't go below 0
|
||||
}
|
||||
|
||||
public override void RemoveFromAbility(BaseAbility ability)
|
||||
{
|
||||
// Handled by recalculation in modifier manager
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Mana Cost Modifier
|
||||
|
||||
```csharp
|
||||
[CreateAssetMenu(fileName = "New Mana Cost Modifier", menuName = "Modifiers/Mana Cost Modifier")]
|
||||
public class ManaCostModifier : BaseAbilityModifier
|
||||
{
|
||||
[Header("Mana Cost Modifications")]
|
||||
public float flatCostReduction = 0f;
|
||||
public float costMultiplier = 1f; // 0.5 = 50% cost, 1.2 = 20% more expensive
|
||||
|
||||
public override void ApplyToAbility(BaseAbility ability)
|
||||
{
|
||||
float newCost = (ability.manaCost - flatCostReduction) * costMultiplier;
|
||||
ability.manaCost = Mathf.Max(0f, newCost);
|
||||
}
|
||||
|
||||
public override void RemoveFromAbility(BaseAbility ability)
|
||||
{
|
||||
// Handled by recalculation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Behavior-Changing Modifiers
|
||||
|
||||
### Behavior Modifier
|
||||
|
||||
```csharp
|
||||
[CreateAssetMenu(fileName = "New Behavior Modifier", menuName = "Modifiers/Behavior Modifier")]
|
||||
public class BehaviorModifier : BaseAbilityModifier
|
||||
{
|
||||
[Header("Behavior Changes")]
|
||||
public bool addPiercing = false;
|
||||
public int additionalProjectiles = 0;
|
||||
public bool convertToChain = false;
|
||||
public int maxChainTargets = 3;
|
||||
|
||||
public override void ApplyToAbility(BaseAbility ability)
|
||||
{
|
||||
if (ability is ProjectileAbility projectile)
|
||||
{
|
||||
ApplyToProjectile(projectile);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyToProjectile(ProjectileAbility projectile)
|
||||
{
|
||||
if (addPiercing)
|
||||
{
|
||||
projectile.piercing = true;
|
||||
projectile.maxPierceTargets = Mathf.Max(projectile.maxPierceTargets, 3);
|
||||
}
|
||||
|
||||
if (additionalProjectiles > 0)
|
||||
{
|
||||
projectile.projectileCount += additionalProjectiles;
|
||||
}
|
||||
|
||||
if (convertToChain)
|
||||
{
|
||||
// Add chain behavior component or effect
|
||||
var chainEffect = CreateChainEffect();
|
||||
projectile.abilityEffects.Add(chainEffect);
|
||||
}
|
||||
}
|
||||
|
||||
private BaseEffect CreateChainEffect()
|
||||
{
|
||||
// Create and return a chain effect instance
|
||||
// This would be your chain lightning effect or similar
|
||||
return null; // Placeholder - implement based on your effect system
|
||||
}
|
||||
|
||||
public override void RemoveFromAbility(BaseAbility ability)
|
||||
{
|
||||
// Complex behavior changes might need special removal logic
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Effect Addition Modifier
|
||||
|
||||
```csharp
|
||||
[CreateAssetMenu(fileName = "New Effect Addition Modifier", menuName = "Modifiers/Effect Addition Modifier")]
|
||||
public class EffectAdditionModifier : BaseAbilityModifier
|
||||
{
|
||||
[Header("Effect Addition")]
|
||||
public List<BaseEffect> effectsToAdd = new List<BaseEffect>();
|
||||
public bool replaceExistingEffects = false;
|
||||
|
||||
public override void ApplyToAbility(BaseAbility ability)
|
||||
{
|
||||
if (replaceExistingEffects)
|
||||
{
|
||||
// Clear existing effects and add new ones
|
||||
ability.abilityEffects.Clear();
|
||||
}
|
||||
|
||||
// Add new effects
|
||||
foreach (var effect in effectsToAdd)
|
||||
{
|
||||
var runtimeEffect = effect.CreateRuntimeInstance();
|
||||
ability.abilityEffects.Add(runtimeEffect);
|
||||
}
|
||||
}
|
||||
|
||||
public override void RemoveFromAbility(BaseAbility ability)
|
||||
{
|
||||
// Handled by recalculation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Modifier Manager Integration
|
||||
|
||||
Add this to your existing `AbilityKeyBinder` class:
|
||||
|
||||
```csharp
|
||||
public class AbilityKeyBinder : MonoBehaviour
|
||||
{
|
||||
// ... existing code ...
|
||||
|
||||
[Header("Modifier System")]
|
||||
private List<BaseAbilityModifier> activeModifiers = new List<BaseAbilityModifier>();
|
||||
private BaseAbility originalAbilityData; // Store reference to original SO
|
||||
|
||||
// Apply a modifier to the current ability
|
||||
public void ApplyModifier(BaseAbilityModifier modifier)
|
||||
{
|
||||
if (ability == null || !modifier.CanApplyTo(ability)) return;
|
||||
|
||||
var runtimeModifier = modifier.CreateRuntimeInstance();
|
||||
activeModifiers.Add(runtimeModifier);
|
||||
RecalculateAbility();
|
||||
}
|
||||
|
||||
public void RemoveModifier(BaseAbilityModifier modifier)
|
||||
{
|
||||
for (int i = activeModifiers.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (activeModifiers[i].GUID == modifier.GUID)
|
||||
{
|
||||
DestroyImmediate(activeModifiers[i]);
|
||||
activeModifiers.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
RecalculateAbility();
|
||||
}
|
||||
|
||||
public void RemoveModifiersBySource(ModifierSource source)
|
||||
{
|
||||
for (int i = activeModifiers.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (activeModifiers[i].source == source)
|
||||
{
|
||||
DestroyImmediate(activeModifiers[i]);
|
||||
activeModifiers.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
RecalculateAbility();
|
||||
}
|
||||
|
||||
public void ClearAllModifiers()
|
||||
{
|
||||
foreach (var modifier in activeModifiers)
|
||||
{
|
||||
if (modifier != null)
|
||||
DestroyImmediate(modifier);
|
||||
}
|
||||
activeModifiers.Clear();
|
||||
RecalculateAbility();
|
||||
}
|
||||
|
||||
private void RecalculateAbility()
|
||||
{
|
||||
if (ability == null || originalAbilityData == null) return;
|
||||
|
||||
// Recreate the runtime instance from scratch
|
||||
CleanupAbilityAndEffectsClones();
|
||||
this.ability = originalAbilityData.CreateRuntimeInstance();
|
||||
|
||||
if (this.ability is ComboAbility comboAbility)
|
||||
{
|
||||
SetupComboAbility(comboAbility);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetupRegularAbility();
|
||||
}
|
||||
|
||||
// Apply all modifiers in priority order
|
||||
var sortedModifiers = activeModifiers.OrderBy(m => m.priority).ToList();
|
||||
foreach (var modifier in sortedModifiers)
|
||||
{
|
||||
if (isComboAbility)
|
||||
{
|
||||
foreach (var comboAb in comboAbilities)
|
||||
{
|
||||
modifier.ApplyToAbility(comboAb);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
modifier.ApplyToAbility(ability);
|
||||
}
|
||||
}
|
||||
|
||||
// Update UI
|
||||
RefreshUI();
|
||||
}
|
||||
|
||||
private void RefreshUI()
|
||||
{
|
||||
if (abilityBindInstance != null)
|
||||
{
|
||||
OnManaChanged(mana.currentValue);
|
||||
OnHealthChanged(health.currentValue);
|
||||
|
||||
if (isComboAbility)
|
||||
abilityBindInstance.ForceUpdateOnComboAbility(GetCurrentAbility());
|
||||
}
|
||||
}
|
||||
|
||||
public void BindAbility(BaseAbility ability)
|
||||
{
|
||||
originalAbilityData = ability; // Store reference to original
|
||||
|
||||
// Clear existing modifiers when binding new ability
|
||||
ClearAllModifiers();
|
||||
|
||||
// ... rest of existing BindAbility code ...
|
||||
}
|
||||
|
||||
// Modified cleanup to include modifiers
|
||||
private void CleanupAbilityAndEffectsClones()
|
||||
{
|
||||
// ... existing cleanup code ...
|
||||
|
||||
// Don't clear activeModifiers here - they're managed separately
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
// Cleanup modifiers
|
||||
foreach (var modifier in activeModifiers)
|
||||
{
|
||||
if (modifier != null)
|
||||
DestroyImmediate(modifier);
|
||||
}
|
||||
activeModifiers.Clear();
|
||||
|
||||
// ... existing cleanup code ...
|
||||
}
|
||||
|
||||
// Utility methods
|
||||
public List<BaseAbilityModifier> GetActiveModifiers() => new List<BaseAbilityModifier>(activeModifiers);
|
||||
public bool HasModifierOfType<T>() where T : BaseAbilityModifier => activeModifiers.Any(m => m is T);
|
||||
public int GetModifierCount() => activeModifiers.Count;
|
||||
}
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Equipment System Integration
|
||||
|
||||
```csharp
|
||||
public class Equipment : MonoBehaviour
|
||||
{
|
||||
[Header("Equipment Modifiers")]
|
||||
public List<BaseAbilityModifier> equipmentModifiers;
|
||||
|
||||
private AbilityKeyBinder[] abilityBinders;
|
||||
|
||||
void Start()
|
||||
{
|
||||
abilityBinders = GetComponentsInChildren<AbilityKeyBinder>();
|
||||
}
|
||||
|
||||
public void OnEquip()
|
||||
{
|
||||
foreach (var binder in abilityBinders)
|
||||
{
|
||||
foreach (var modifier in equipmentModifiers)
|
||||
{
|
||||
binder.ApplyModifier(modifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnUnequip()
|
||||
{
|
||||
foreach (var binder in abilityBinders)
|
||||
{
|
||||
binder.RemoveModifiersBySource(ModifierSource.Equipment);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Talent System Integration
|
||||
|
||||
```csharp
|
||||
public class TalentNode : MonoBehaviour
|
||||
{
|
||||
[Header("Talent Effects")]
|
||||
public List<BaseAbilityModifier> talentModifiers;
|
||||
public List<GameTag> affectedAbilityTags;
|
||||
|
||||
private bool isUnlocked = false;
|
||||
|
||||
public void UnlockTalent()
|
||||
{
|
||||
if (isUnlocked) return;
|
||||
|
||||
isUnlocked = true;
|
||||
var player = FindObjectOfType<PlayerController>();
|
||||
var abilityBinders = player.GetComponentsInChildren<AbilityKeyBinder>();
|
||||
|
||||
foreach (var binder in abilityBinders)
|
||||
{
|
||||
foreach (var modifier in talentModifiers)
|
||||
{
|
||||
// Only apply if ability matches talent requirements
|
||||
if (AbilityMatchesTalent(binder.Ability))
|
||||
{
|
||||
binder.ApplyModifier(modifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void LockTalent()
|
||||
{
|
||||
if (!isUnlocked) return;
|
||||
|
||||
isUnlocked = false;
|
||||
var player = FindObjectOfType<PlayerController>();
|
||||
var abilityBinders = player.GetComponentsInChildren<AbilityKeyBinder>();
|
||||
|
||||
foreach (var binder in abilityBinders)
|
||||
{
|
||||
foreach (var modifier in talentModifiers)
|
||||
{
|
||||
binder.RemoveModifier(modifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool AbilityMatchesTalent(BaseAbility ability)
|
||||
{
|
||||
if (affectedAbilityTags.Count == 0) return true;
|
||||
|
||||
return affectedAbilityTags.Any(tag => ability.abilityTags.Contains(tag));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Buff/Debuff System
|
||||
|
||||
```csharp
|
||||
public class BuffManager : MonoBehaviour
|
||||
{
|
||||
[System.Serializable]
|
||||
public class ActiveBuff
|
||||
{
|
||||
public BaseAbilityModifier modifier;
|
||||
public float duration;
|
||||
public float timeRemaining;
|
||||
|
||||
public bool IsExpired => timeRemaining <= 0f;
|
||||
}
|
||||
|
||||
private List<ActiveBuff> activeBuffs = new List<ActiveBuff>();
|
||||
private AbilityKeyBinder[] abilityBinders;
|
||||
|
||||
void Start()
|
||||
{
|
||||
abilityBinders = GetComponentsInChildren<AbilityKeyBinder>();
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
for (int i = activeBuffs.Count - 1; i >= 0; i--)
|
||||
{
|
||||
activeBuffs[i].timeRemaining -= Time.deltaTime;
|
||||
|
||||
if (activeBuffs[i].IsExpired)
|
||||
{
|
||||
RemoveBuff(activeBuffs[i]);
|
||||
activeBuffs.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyBuff(BaseAbilityModifier modifier, float duration)
|
||||
{
|
||||
var buff = new ActiveBuff
|
||||
{
|
||||
modifier = modifier,
|
||||
duration = duration,
|
||||
timeRemaining = duration
|
||||
};
|
||||
|
||||
activeBuffs.Add(buff);
|
||||
|
||||
foreach (var binder in abilityBinders)
|
||||
{
|
||||
binder.ApplyModifier(modifier);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveBuff(ActiveBuff buff)
|
||||
{
|
||||
foreach (var binder in abilityBinders)
|
||||
{
|
||||
binder.RemoveModifier(buff.modifier);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearAllBuffs()
|
||||
{
|
||||
foreach (var buff in activeBuffs)
|
||||
{
|
||||
RemoveBuff(buff);
|
||||
}
|
||||
activeBuffs.Clear();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Examples
|
||||
|
||||
### Set Bonus Modifier
|
||||
|
||||
```csharp
|
||||
[CreateAssetMenu(fileName = "New Set Bonus", menuName = "Modifiers/Set Bonus")]
|
||||
public class SetBonusModifier : BaseAbilityModifier
|
||||
{
|
||||
[Header("Set Bonus")]
|
||||
public int requiredPieces = 2;
|
||||
public List<GameTag> setTags;
|
||||
|
||||
[Header("Bonus Effects")]
|
||||
public float damageBonus = 0.2f;
|
||||
public bool addNewEffect = false;
|
||||
public BaseEffect bonusEffect;
|
||||
public bool transformAbilityType = false;
|
||||
public AbilityTransformation transformation;
|
||||
|
||||
[System.Serializable]
|
||||
public class AbilityTransformation
|
||||
{
|
||||
public string newName;
|
||||
public Sprite newIcon;
|
||||
public float newCooldown;
|
||||
public Color newEffectColor = Color.white;
|
||||
}
|
||||
|
||||
public override void ApplyToAbility(BaseAbility ability)
|
||||
{
|
||||
// Check if ability has any set tags
|
||||
bool hasSetTag = setTags.Any(tag => ability.abilityTags.Contains(tag));
|
||||
if (!hasSetTag) return;
|
||||
|
||||
// Apply damage bonus to all damage effects
|
||||
foreach (var effect in ability.abilityEffects)
|
||||
{
|
||||
if (effect is DamageEffect damageEffect)
|
||||
{
|
||||
damageEffect.baseDamage *= (1f + damageBonus);
|
||||
}
|
||||
}
|
||||
|
||||
// Add new effect if specified
|
||||
if (addNewEffect && bonusEffect != null)
|
||||
{
|
||||
var newEffect = bonusEffect.CreateRuntimeInstance();
|
||||
ability.abilityEffects.Add(newEffect);
|
||||
}
|
||||
|
||||
// Transform ability appearance/properties
|
||||
if (transformAbilityType && transformation != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(transformation.newName))
|
||||
ability.name = transformation.newName;
|
||||
|
||||
if (transformation.newIcon != null)
|
||||
ability.icon = transformation.newIcon;
|
||||
|
||||
if (transformation.newCooldown > 0)
|
||||
ability.cooldown = transformation.newCooldown;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Conditional Modifier
|
||||
|
||||
```csharp
|
||||
[CreateAssetMenu(fileName = "New Conditional Modifier", menuName = "Modifiers/Conditional Modifier")]
|
||||
public class ConditionalModifier : BaseAbilityModifier
|
||||
{
|
||||
[Header("Conditions")]
|
||||
public ConditionType conditionType;
|
||||
public float healthThreshold = 0.5f;
|
||||
public float manaThreshold = 0.3f;
|
||||
|
||||
[Header("Conditional Effects")]
|
||||
public BaseAbilityModifier lowHealthModifier;
|
||||
public BaseAbilityModifier lowManaModifier;
|
||||
public BaseAbilityModifier criticalConditionModifier;
|
||||
|
||||
public enum ConditionType
|
||||
{
|
||||
LowHealth,
|
||||
LowMana,
|
||||
CriticalCondition
|
||||
}
|
||||
|
||||
public override void ApplyToAbility(BaseAbility ability)
|
||||
{
|
||||
var binder = FindObjectOfType<AbilityKeyBinder>(); // In real implementation, pass this as parameter
|
||||
if (binder == null) return;
|
||||
|
||||
bool conditionMet = false;
|
||||
BaseAbilityModifier modifierToApply = null;
|
||||
|
||||
switch (conditionType)
|
||||
{
|
||||
case ConditionType.LowHealth:
|
||||
conditionMet = binder.Health.GetPercentage() <= healthThreshold;
|
||||
modifierToApply = lowHealthModifier;
|
||||
break;
|
||||
|
||||
case ConditionType.LowMana:
|
||||
conditionMet = binder.Mana.GetPercentage() <= manaThreshold;
|
||||
modifierToApply = lowManaModifier;
|
||||
break;
|
||||
|
||||
case ConditionType.CriticalCondition:
|
||||
conditionMet = binder.Health.GetPercentage() <= healthThreshold &&
|
||||
binder.Mana.GetPercentage() <= manaThreshold;
|
||||
modifierToApply = criticalConditionModifier;
|
||||
break;
|
||||
}
|
||||
|
||||
if (conditionMet && modifierToApply != null)
|
||||
{
|
||||
modifierToApply.ApplyToAbility(ability);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Scaling Modifier
|
||||
|
||||
```csharp
|
||||
[CreateAssetMenu(fileName = "New Scaling Modifier", menuName = "Modifiers/Scaling Modifier")]
|
||||
public class ScalingModifier : BaseAbilityModifier
|
||||
{
|
||||
[Header("Scaling Properties")]
|
||||
public ScalingType scalingType;
|
||||
public float baseValue = 1.0f;
|
||||
public float scalingPerLevel = 0.1f;
|
||||
public float scalingPerStat = 0.05f;
|
||||
public StatType scalingStat;
|
||||
|
||||
public enum ScalingType
|
||||
{
|
||||
PlayerLevel,
|
||||
StatValue,
|
||||
Both
|
||||
}
|
||||
|
||||
public enum StatType
|
||||
{
|
||||
Intelligence,
|
||||
Strength,
|
||||
Dexterity
|
||||
}
|
||||
|
||||
public override void ApplyToAbility(BaseAbility ability)
|
||||
{
|
||||
float scalingMultiplier = CalculateScaling();
|
||||
|
||||
// Apply scaling to damage effects
|
||||
foreach (var effect in ability.abilityEffects)
|
||||
{
|
||||
if (effect is DamageEffect damageEffect)
|
||||
{
|
||||
damageEffect.baseDamage *= scalingMultiplier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float CalculateScaling()
|
||||
{
|
||||
float multiplier = baseValue;
|
||||
|
||||
// Get player stats (implement based on your stat system)
|
||||
var playerStats = FindObjectOfType<PlayerStats>();
|
||||
if (playerStats == null) return multiplier;
|
||||
|
||||
switch (scalingType)
|
||||
{
|
||||
case ScalingType.PlayerLevel:
|
||||
multiplier += playerStats.Level * scalingPerLevel;
|
||||
break;
|
||||
|
||||
case ScalingType.StatValue:
|
||||
float statValue = GetStatValue(playerStats, scalingStat);
|
||||
multiplier += statValue * scalingPerStat;
|
||||
break;
|
||||
|
||||
case ScalingType.Both:
|
||||
multiplier += playerStats.Level * scalingPerLevel;
|
||||
float statVal = GetStatValue(playerStats, scalingStat);
|
||||
multiplier += statVal * scalingPerStat;
|
||||
break;
|
||||
}
|
||||
|
||||
return multiplier;
|
||||
}
|
||||
|
||||
private float GetStatValue(PlayerStats stats, StatType statType)
|
||||
{
|
||||
// Implement based on your stat system
|
||||
switch (statType)
|
||||
{
|
||||
case StatType.Intelligence: return stats.Intelligence;
|
||||
case StatType.Strength: return stats.Strength;
|
||||
case StatType.Dexterity: return stats.Dexterity;
|
||||
default: return 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Modifier Ordering
|
||||
- Use priority values to control the order of modifier application
|
||||
- Apply flat bonuses before multiplicative bonuses
|
||||
- Apply base stat modifiers before percentage modifiers
|
||||
|
||||
### 2. Performance Considerations
|
||||
- Cache calculations when possible
|
||||
- Only recalculate when modifiers change
|
||||
- Use object pooling for frequently created/destroyed modifiers
|
||||
- Consider using structs for simple value modifiers
|
||||
|
||||
### 3. Data Organization
|
||||
- Group related modifiers in folders
|
||||
- Use consistent naming conventions
|
||||
- Document complex modifier interactions
|
||||
- Create template modifiers for common use cases
|
||||
|
||||
### 4. Error Handling
|
||||
- Always check for null references
|
||||
- Validate modifier compatibility before application
|
||||
- Provide fallback values for edge cases
|
||||
- Log modifier application for debugging
|
||||
|
||||
### 5. Testing
|
||||
- Test modifier stacking behavior
|
||||
- Verify cleanup occurs properly
|
||||
- Test edge cases (zero values, negative values)
|
||||
- Test modifier removal and reapplication
|
||||
|
||||
### 6. Designer-Friendly Features
|
||||
- Provide clear tooltips and descriptions
|
||||
- Use enums for predefined values
|
||||
- Create custom property drawers for complex modifiers
|
||||
- Include example configurations
|
||||
|
||||
This system provides a robust foundation for creating complex ability modification systems in Unity ARPG games, allowing for deep character customization through equipment, talents, buffs, and other game mechanics.
|
@ -1,5 +0,0 @@
|
||||
- enter game
|
||||
- select/create character
|
||||
- play one run (run loop)
|
||||
- get meta upgrades/unlocks
|
||||
- repeat
|
@ -1,7 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: db47a2ea3025964489674c0b9000a7e9
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,3 +0,0 @@
|
||||
- classes learn different skills / upgrades each run
|
||||
- Improve dropped items&crafting in general
|
||||
-
|
@ -1,7 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 385170ab9eab05f4a815fad0b8c0616b
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,7 +0,0 @@
|
||||
- enter riftraids / rift breaks
|
||||
- kill stuff
|
||||
- grab loot
|
||||
- equip stuff
|
||||
- level up
|
||||
- unlock spells
|
||||
- repeat
|
@ -1,7 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f265d49c4cab0ac4c84c6c30252ada97
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -78,7 +78,7 @@ Material:
|
||||
- _Mode: 0
|
||||
- _OcclusionStrength: 1
|
||||
- _Parallax: 0.02
|
||||
- _Rotation: 5.8921123
|
||||
- _Rotation: 13.959704
|
||||
- _SmoothnessTextureChannel: 0
|
||||
- _SpecularHighlights: 1
|
||||
- _SrcBlend: 1
|
||||
|
@ -1506,7 +1506,10 @@ PrefabInstance:
|
||||
m_RemovedComponents: []
|
||||
m_RemovedGameObjects: []
|
||||
m_AddedGameObjects: []
|
||||
m_AddedComponents: []
|
||||
m_AddedComponents:
|
||||
- targetCorrespondingSourceObject: {fileID: 5522025452902236869, guid: 0adad741209f574499c301afb4817e98, type: 3}
|
||||
insertIndex: -1
|
||||
addedObject: {fileID: 1847919222}
|
||||
m_SourcePrefab: {fileID: 100100000, guid: 0adad741209f574499c301afb4817e98, type: 3}
|
||||
--- !u!1001 &1633082251
|
||||
PrefabInstance:
|
||||
@ -1748,6 +1751,40 @@ PrefabInstance:
|
||||
m_AddedGameObjects: []
|
||||
m_AddedComponents: []
|
||||
m_SourcePrefab: {fileID: 100100000, guid: 27856c31c3afe6244a0f17a4fef0905a, type: 3}
|
||||
--- !u!1 &1847919218 stripped
|
||||
GameObject:
|
||||
m_CorrespondingSourceObject: {fileID: 5522025452902236869, guid: 0adad741209f574499c301afb4817e98, type: 3}
|
||||
m_PrefabInstance: {fileID: 1610184334}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!114 &1847919222
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1847919218}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 474bcb49853aa07438625e644c072ee6, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.UniversalAdditionalLightData
|
||||
m_UsePipelineSettings: 1
|
||||
m_AdditionalLightsShadowResolutionTier: 2
|
||||
m_CustomShadowLayers: 0
|
||||
m_LightCookieSize: {x: 1, y: 1}
|
||||
m_LightCookieOffset: {x: 0, y: 0}
|
||||
m_SoftShadowQuality: 0
|
||||
m_RenderingLayersMask:
|
||||
serializedVersion: 0
|
||||
m_Bits: 1
|
||||
m_ShadowRenderingLayersMask:
|
||||
serializedVersion: 0
|
||||
m_Bits: 1
|
||||
m_Version: 4
|
||||
m_LightLayerMask: 1
|
||||
m_ShadowLayerMask: 1
|
||||
m_RenderingLayers: 1
|
||||
m_ShadowRenderingLayers: 1
|
||||
--- !u!1 &1915668138
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -2586,6 +2623,10 @@ PrefabInstance:
|
||||
propertyPath: m_AnchoredPosition.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3520239619285888459, guid: 7cf303e1116e7fb46ba92e7d73321eeb, type: 3}
|
||||
propertyPath: m_AnchoredPosition.x
|
||||
value: -42.50067
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3641119934303568818, guid: 7cf303e1116e7fb46ba92e7d73321eeb, type: 3}
|
||||
propertyPath: m_AnchorMax.y
|
||||
value: 0
|
||||
@ -2882,6 +2923,10 @@ PrefabInstance:
|
||||
propertyPath: m_AnchoredPosition.y
|
||||
value: -600
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7560246081832902789, guid: 7cf303e1116e7fb46ba92e7d73321eeb, type: 3}
|
||||
propertyPath: m_AnchoredPosition.x
|
||||
value: 42.50067
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8244038924219432218, guid: 7cf303e1116e7fb46ba92e7d73321eeb, type: 3}
|
||||
propertyPath: m_AnchorMax.y
|
||||
value: 0
|
||||
|
@ -209,7 +209,7 @@ public class AbilityKeyBinder : MonoBehaviour
|
||||
public void BindAbility(BaseAbility ability)
|
||||
{
|
||||
if (this.ability != null && this.ability.name.ToLower().Contains("clone"))
|
||||
Destroy(this.ability);
|
||||
CleanupAbilityAndEffectsClones(); //cleanup before rebind
|
||||
|
||||
this.ability = ability.CreateRuntimeInstance();
|
||||
|
||||
@ -219,7 +219,13 @@ public class AbilityKeyBinder : MonoBehaviour
|
||||
combo = comboAbility;
|
||||
for (int i = 0; i < combo.comboChain.Count; i++)
|
||||
{
|
||||
effects.Clear();
|
||||
comboAbilities.Add(combo.comboChain[i].CreateRuntimeInstance());
|
||||
for (int j = 0; j < combo.comboChain[i].abilityEffects.Count; j++)
|
||||
{
|
||||
effects.Add(combo.comboChain[i].abilityEffects[j].CreateRuntimeInstance());
|
||||
}
|
||||
comboAbilities[i].abilityEffects = new List<BaseEffect>(effects);
|
||||
}
|
||||
combo.comboChain = comboAbilities;
|
||||
abilityBindInstance.ForceUpdateOnComboAbility(GetCurrentAbility());
|
||||
@ -245,26 +251,43 @@ public class AbilityKeyBinder : MonoBehaviour
|
||||
abilityBindInstance.SetUnlocked(unlocked);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
private void OnDestroy() //cleanup all clone effects and ability scriptables
|
||||
{
|
||||
CleanupAbilityAndEffectsClones();
|
||||
|
||||
}
|
||||
|
||||
private void CleanupAbilityAndEffectsClones()
|
||||
{
|
||||
if (isComboAbility)
|
||||
{
|
||||
for (int i = comboAbilities.Count - 1; i >= 0; i--)
|
||||
{
|
||||
Destroy(comboAbilities[i]);
|
||||
for (int j = comboAbilities[i].abilityEffects.Count - 1; j >= 0; j--)
|
||||
{
|
||||
if (comboAbilities[i].abilityEffects[j].name.ToLower().Contains("clone"))
|
||||
Destroy(comboAbilities[i].abilityEffects[j]);
|
||||
}
|
||||
if (comboAbilities[i].name.ToLower().Contains("clone"))
|
||||
{
|
||||
Destroy(comboAbilities[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = this.ability.abilityEffects.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (this.ability.abilityEffects[i].name.ToLower().Contains("clone"))
|
||||
Destroy(this.ability.abilityEffects[i]);
|
||||
}
|
||||
|
||||
effects.Clear();
|
||||
|
||||
if (this.ability != null && this.ability.name.ToLower().Contains("clone"))
|
||||
Destroy(this.ability);
|
||||
|
||||
for (int i = effects.Count - 1; i >= 0; i--)
|
||||
{
|
||||
Destroy(effects[i]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void AdvanceCombo()
|
||||
|
Loading…
x
Reference in New Issue
Block a user