Skip to content

Instantly share code, notes, and snippets.

@CodingDino
Last active December 22, 2023 15:39
Show Gist options
  • Save CodingDino/200642f83f5bc51ab2146c7192cc2fe0 to your computer and use it in GitHub Desktop.
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…
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