-
-
Save CodingDino/200642f83f5bc51ab2146c7192cc2fe0 to your computer and use it in GitHub Desktop.
Gameplay Programming Example: In Stardust Survivors, we required a robust buff and debuff system as we use buffs for most of our ability upgrades throughout the game. This code sample shows the BuffList, which handles all buffs for an Entity in the game (either Players or Enemies). The BuffList tracks when new buffs are added or removed and uses…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Bounder.Framework; | |
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
public class BuffList : MonoBehaviour | |
{ | |
private EntityEffectHandler effectHandler = null; | |
private List<Buff> buffs = new List<Buff>(); | |
private bool buffListDirty = false; | |
public Entity targetEntity { get; private set; } = null; | |
public BaseAbility targetAbility { get; private set; } = null; | |
// ******************************************************************** | |
#region Unity Functions | |
// ******************************************************************** | |
private void Awake() | |
{ | |
targetAbility = GetComponentInParent<BaseAbility>(); | |
targetEntity = GetComponentInParent<Entity>(); | |
effectHandler = targetEntity.GetComponentInChildren<EntityEffectHandler>(); | |
targetEntity.onDeath += OnEntityDeath; | |
} | |
// ******************************************************************** | |
private void OnEnable() | |
{ | |
// Don't use MarkDirty() as it will try to access targetAbility entity which has not been set up yet | |
// This is okay as during initialisation those sub-buff-lists will mark themselves dirty anyway! | |
buffListDirty = true; | |
} | |
// ******************************************************************** | |
private void FixedUpdate() | |
{ | |
// FixedUpdate since it doesn't have to happen too often | |
if (buffListDirty) | |
{ | |
UpdateStatsFromBuffs(); | |
UpdateEntityFromBuffs(); | |
buffListDirty = false; | |
} | |
} | |
// ******************************************************************** | |
#endregion | |
// ******************************************************************** | |
// ******************************************************************** | |
#region Public Functions | |
// ******************************************************************** | |
public List<T> GetBuffsOfType<T>() where T : Buff | |
{ | |
List<T> typedBuffs = new List<T>(); | |
foreach (var buff in buffs) | |
{ | |
if (buff is T) | |
{ | |
typedBuffs.Add(buff as T); | |
} | |
} | |
return typedBuffs; | |
} | |
// ******************************************************************** | |
public bool ConditionPresent(ConditionBuff.ConditionType _condition) | |
{ | |
foreach (var buff in buffs) | |
{ | |
ConditionBuff conditionBuff = buff as ConditionBuff; | |
if (conditionBuff && conditionBuff.condition == _condition) | |
{ | |
return true; | |
} | |
} | |
return false; | |
} | |
// ******************************************************************** | |
public void UpdateStatsFromBuffs() | |
{ | |
// Get stat buffs from main buff list | |
List<StatBuff> statBuffs = GetBuffsOfType<StatBuff>(); | |
// Pull stat buffs from entity if this is an ability | |
if (targetAbility) | |
{ | |
statBuffs.AddRange(targetEntity.buffList.GetBuffsOfType<StatBuff>()); | |
} | |
// Ensure our active buffs are sorted by priority | |
// TODO: Ensure this is sorting in the order we want - early to late | |
statBuffs.Sort((x, y) => x.applicationOrder.CompareTo(y.applicationOrder)); | |
// Send the buffs to the ability or the entity | |
if (targetAbility) | |
{ | |
// Send to ability | |
targetAbility.UpdateStatsFromBuffs(statBuffs); | |
} | |
else | |
{ | |
// Send to entity | |
targetEntity.UpdateStatsFromBuffs(statBuffs); | |
} | |
} | |
// ******************************************************************** | |
public void UpdateEntityFromBuffs() | |
{ | |
foreach (var buff in buffs) | |
{ | |
ConditionBuff conditionBuff = buff as ConditionBuff; | |
if (conditionBuff && conditionBuff.haltsMovement) | |
{ | |
targetEntity.allowMovement = false; | |
return; | |
} | |
} | |
targetEntity.allowMovement = true; | |
} | |
// ******************************************************************** | |
public void RemoveBuff(Buff _buff) | |
{ | |
if(_buff != null) | |
{ | |
buffs.Remove(_buff); | |
_buff.CleanupBuff(); | |
effectHandler.RemoveBuffEffects(_buff); | |
MarkDirty(); | |
} | |
} | |
// ******************************************************************** | |
public void MarkDirty() | |
{ | |
buffListDirty = true; | |
// If this is an entity's buff, we must mark all of it's abilities dirty as | |
// well in case one of the entity stat buffs affects them | |
if (targetAbility == null) | |
{ | |
// NOTE: This will only be null if this object is while the buffList is being | |
// added. If that is the case, then the relevant Ability buffs will be marking themselves | |
// as dirty anyway so we skip this step | |
if (targetEntity != null) | |
{ | |
List<BaseAbility> abilities = targetEntity.GetAbilities(); | |
foreach (var ability in abilities) | |
{ | |
ability.buffList.MarkDirty(); | |
} | |
} | |
} | |
} | |
// ******************************************************************** | |
public bool HasActiveCondition(ConditionBuff.ConditionType _conditionType) | |
{ | |
foreach (var buff in buffs) | |
{ | |
ConditionBuff conditionBuff = buff as ConditionBuff; | |
if (conditionBuff) | |
{ | |
if (conditionBuff.condition == _conditionType) | |
return true; | |
} | |
} | |
return false; | |
} | |
// ******************************************************************** | |
public Buff AddBuff(Buff _newBuffPrefab, Entity _sourceEntity, BaseAbility _sourceAbility) | |
{ | |
BuffDuplicateAction action = BuffDuplicateAction.DUPLICATE; | |
Buff newBuff = null; | |
Buff buffToUpdate = null; | |
// Check all existing buffs in this list | |
foreach (Buff existingBuff in buffs) | |
{ | |
// Are the buffs the same buff type? | |
if (_newBuffPrefab.CompareBuffType(existingBuff)) | |
{ | |
// Same buff type. | |
// Is the action stricter than the action we already have? | |
if (_newBuffPrefab.sameBuffType > action) | |
{ | |
// It is, apply this action for now and record the related buff | |
action = _newBuffPrefab.sameBuffType; | |
buffToUpdate = existingBuff; | |
} | |
// Same type, is it the same source entity type? | |
if (existingBuff.CompareSourceEntityType(_sourceEntity)) | |
{ | |
// Same source entity type. | |
// Is the action stricter than the action we already have? | |
if (_newBuffPrefab.sameSourceEntityType > action) | |
{ | |
// It is, apply this action for now and record the related buff | |
action = _newBuffPrefab.sameSourceEntityType; | |
buffToUpdate = existingBuff; | |
} | |
// Same entity type, is it the same source ability type? | |
if (existingBuff.CompareSourceAbilityType(_sourceAbility)) | |
{ | |
// Same source entity type. | |
// Is the action stricter than the action we already have? | |
if (_newBuffPrefab.sameSourceAbilityType > action) | |
{ | |
// It is, apply this action for now and record the related buff | |
action = _newBuffPrefab.sameSourceAbilityType; | |
buffToUpdate = existingBuff; | |
} | |
} | |
// Same entity type, is it the same exact instance? | |
if (existingBuff.CompareSourceEntity(_sourceEntity)) | |
{ | |
// Same exact entity instance. | |
// Is the action stricter than the action we already have? | |
if (_newBuffPrefab.sameSourceEntityInstance > action) | |
{ | |
// It is, apply this action for now and record the related buff | |
action = _newBuffPrefab.sameSourceEntityInstance; | |
buffToUpdate = existingBuff; | |
} | |
// Same exact entity instace, is it the same exact source ability instance? | |
if (existingBuff.CompareSourceAbilityType(_sourceAbility)) | |
{ | |
// Same exact source ability instance. | |
// Is the action stricter than the action we already have? | |
if (_newBuffPrefab.sameSourceAbilityInstance > action) | |
{ | |
// It is, apply this action for now and record the related buff | |
action = _newBuffPrefab.sameSourceAbilityInstance; | |
buffToUpdate = existingBuff; | |
} | |
} | |
} | |
} | |
} | |
} | |
// Perform action chosen | |
switch(action) | |
{ | |
case BuffDuplicateAction.DUPLICATE: | |
{ | |
newBuff = ObjectPool.RequestGenericObject(_newBuffPrefab,transform); | |
newBuff.transform.parent = transform; | |
newBuff.ApplyBuff(this, _sourceEntity, _sourceAbility); | |
buffs.Add(newBuff); | |
if (!effectHandler) | |
Debug.Log("Cannot find effect handler for buff list on " + transform.parent.name); | |
effectHandler.AddBuffEffects(newBuff); | |
MarkDirty(); | |
break; | |
} | |
case BuffDuplicateAction.STACK_AND_REFRESH: | |
{ | |
buffToUpdate.AddStack(); | |
buffToUpdate.RefreshDuration(); | |
MarkDirty(); | |
break; | |
} | |
case BuffDuplicateAction.STACK: | |
{ | |
buffToUpdate.AddStack(); | |
MarkDirty(); | |
break; | |
} | |
case BuffDuplicateAction.REFRESH: | |
{ | |
buffToUpdate.RefreshDuration(); | |
MarkDirty(); | |
break; | |
} | |
case BuffDuplicateAction.NO_DUPLICATES: | |
{ | |
// Do nothing! | |
break; | |
} | |
default: | |
{ | |
// Error, we should not reach this | |
break; | |
} | |
} | |
if (newBuff) | |
return newBuff; | |
else | |
return buffToUpdate; | |
} | |
// ******************************************************************** | |
public void RemoveAllBuffs() | |
{ | |
foreach (var buff in buffs) | |
{ | |
buff.CleanupBuff(); | |
effectHandler.RemoveBuffEffects(buff); | |
} | |
buffs.Clear(); | |
MarkDirty(); | |
} | |
// ******************************************************************** | |
public void OnEntityDeath(Entity _entity) | |
{ | |
RemoveAllBuffs(); | |
} | |
// ******************************************************************** | |
#endregion | |
// ******************************************************************** | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment