OperationStarfall — Pickups & Inventory
een modulair pickups en inventory systeem waarmee het team snel nieuwe power ups kan toevoegen zonder de kerncode aan te passen.
features
- Direct vs. indirect pickups: sommige effecten treden direct op (bijv. heal), andere worden opgeslagen in de inventory en kun je later gebruiken.
- Uitbreidbaar met 1 regel: nieuwe pickups voeg je toe via een mapping (enum → effect‑klasse).
- Veelgebruikte effect typen:
- Instant effect (bijv. health)
- Timed effect (automatisch starten/stoppen met timer)
- Usage‑based effect (maximaal N keer te gebruiken)
- Ontkoppeld ontwerp: inventory kent geen details van de effecten. Alles verloopt via events.
Health pickup: “Health stijgt direct na oppakken.”
Growth pickup: “Avatar verdubbelt 5s in grootte en keert terug naar normaal.”
Inventory use: “Voorraad gebruiken vanuit inventory (indirect).”
Hoe het werkt
- Een pickup (CollectPickup) detecteert een botsing met de speler en roept
Inventory.AddItem(...)aan. - Bij “Direct” effecten: de inventory vuurt het event
OnItemActivatedaf met de naam van het item. - Het
EffectSystemluistert naar dat event, maakt dynamisch de juiste effect‑component aan (via een mapping), en roeptApply(). - Effect‑klassen regelen zelf hun leven:
HealthEffectvoert direct een actie uit en verwijdert zichzelf.TimedEffectstart een timer (coroutine), eindigt automatisch en verwijdert zichzelf.UsageEffecttelt het aantal gebruiken en verwijdert zichzelf na de laatste keer.
Code highlights
Context: event gedreven, data‑gedreven mapping, en effect klassen die zichzelf beheren (self‑contained). Snippets zijn ingekort voor leesbaarheid.
- Event → Dynamisch effect aanmaken (EffectSystem) Waarom goed: effecten registreren is 1 regel in de mapping. Inventory en effecten blijven losjes gekoppeld.
public class EffectSystem : MonoBehaviour
{
private Inventory _inventory;
// Enum → Type mapping: voeg hier 1 regel toe voor een nieuwe pickup
private Dictionary _itemToEffectMap = new(){
{PickupNames.HealthEffect, typeof(HealthEffect)},
{PickupNames.SpeedBoostEffect, typeof(TimedEffect)},
{PickupNames.ShieldEffect, typeof(UsageEffect)},
{PickupNames.DamageEffect, typeof(UsageEffect)},
{PickupNames.TimedEffect, typeof(TimedEffect)},
{PickupNames.UsageEffect, typeof(UsageEffect)},
{PickupNames.GrowthEffect, typeof(GrowthEffect)},
};
void Start()
{
_inventory = GetComponent();
if (_inventory != null)
_inventory.OnItemActivated.AddListener(AddEffect);
}
void AddEffect(PickupNames targetEffect)
{
var effectType = _itemToEffectMap[targetEffect];
var newEffect = gameObject.AddComponent(effectType) as BaseEffect;
newEffect?.Apply();
}
}
- Inventory — direct vs. indirect en voorraadbeheer Waarom goed: duidelijke scheiding tussen “nu toepassen” en “opslaan voor later”, met limieten en nette logging.
public class Inventory : MonoBehaviour
{
[SerializeField] private int maxItemCount = 10;
private Dictionary _itemInventory = new();
public UnityEvent OnItemActivated;
public void AddItem(PickupEffectType pickupType, PickupNames itemName, int amount)
{
if (pickupType == PickupEffectType.Direct)
{
UseItem(itemName); // direct toepassen
return;
}
StoreItem(itemName, amount); // anders opslaan
}
private void StoreItem(PickupNames itemName, int amount)
{
if (_itemInventory.ContainsKey(itemName) && _itemInventory[itemName] >= maxItemCount)
return;
_itemInventory[itemName] = GetItemCount(itemName) + amount;
}
public bool UseItemFromInventory(PickupNames itemName)
{
if (GetItemCount(itemName) <= 0) return false;
_itemInventory[itemName]--;
if (_itemInventory[itemName] <= 0) _itemInventory.Remove(itemName);
UseItem(itemName);
return true;
}
private void UseItem(PickupNames itemName) => OnItemActivated?.Invoke(itemName);
public int GetItemCount(PickupNames itemName) => _itemInventory.TryGetValue(itemName, out var c) ? c : 0;
}
- TimedEffect — start/stop met coroutine Waarom goed: generiek patroon voor tijdelijke buffs. Herbruikbaar: alleen
OnEffectStart/Endoverschrijven.
public class TimedEffect : BaseEffect
{
[SerializeField] protected float duration = 5f;
protected Coroutine timerCoroutine;
private bool isEffectActive;
public override void Apply()
{
if (isEffectActive) return;
OnEffectStart();
timerCoroutine = StartCoroutine(EffectTimer());
}
protected virtual void OnEffectStart()
{
isEffectActive = true;
// start logica (bijv. stats verhogen)
}
protected virtual void OnEffectEnd()
{
isEffectActive = false;
Remove(); // verwijder component
}
private IEnumerator EffectTimer()
{
yield return new WaitForSeconds(duration);
OnEffectEnd();
}
public override void Remove()
{
if (timerCoroutine != null) StopCoroutine(timerCoroutine);
base.Remove();
}
}
Voorbeeld van een concrete pickup op TimedEffect: GrowthEffect (avatar 2x zo groot voor de duur).
public class GrowthEffect : TimedEffect
{
private Vector3 originalScale;
protected override void OnEffectStart()
{
base.OnEffectStart();
originalScale = transform.localScale;
transform.localScale = originalScale * 2f;
}
protected override void OnEffectEnd()
{
transform.localScale = originalScale;
base.OnEffectEnd();
}
}