#Stat system implementation issues
1 messages · Page 1 of 1 (latest)
StatSystem.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class StatSystem
{
// Variables
// Events
public event EventHandler<StatChangeArgs> OnValueChange = delegate { };
public event EventHandler<StatChangeArgs> OnMaxChange = delegate { };
// Stat type
public StatType statType { get; }
// Stat vars
public float value { get; private set; }
public float maxValue { get; private set; }
// Instantiation tools
// Instance creation
public StatSystem(StatType type, float max) {
this.statType = type;
this.maxValue = max;
}
// Value init
public void Max() {
this.value = maxValue;
}
// Stat management
// Type check
public bool IsSameType(StatType checkType) {
return this.statType.Equals(checkType);
}
// Value
public void ModifyValue(object sender, StatChangeArgs changeArgs) {
if(IsSameType(changeArgs.statType)) {
ModifyStatValue(changeArgs.value);
}
}
public void ModifyStatValue(float changeAmount) {
this.value = Mathf.Min(value + changeAmount, maxValue);
OnValueChange(this, new StatChangeArgs(statType, changeAmount));
}
public void ModifyMax(object sender, StatChangeArgs changeArgs) {
if(IsSameType(changeArgs.statType)) {
ModifyStatMax(changeArgs.value);
}
}
public void ModifyStatMax(float changeAmount) {
this.maxValue = changeAmount;
OnMaxChange(this, new StatChangeArgs(statType, changeAmount));
}
}
StatType.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class StatType
{
// Vars
public string typeName { get; }
// Instantiation
public StatType(string typeName) {
this.typeName = typeName;
}
// Functions
// Equality
public bool Equals(StatType other) {
return string.Equals(this.typeName, other.typeName);
}
public float SetMaxFunction() {
return 0;
}
}
one of the classes that creates an instance of StatSystem and modifies it
@languid vector so I'd rescript StatSystem to be StatSystem<T>, where value and maxValue are of type T?
What's your problem exactly? Are you just asking how you should structure everything?
Essentially, yeah - I got hung up on how to change how ModifyStatValue and ModifyStatMax work for different StatTypes
Then yeah, you're always going to know the stat's value type when you're changing it, so you can probably just hold them in a Stat<T> generic where T would be the value (float, boolean, integer, etc), instead of making a whole inheritance structure for different stats
Well, for my stats system I just derive them all from the general utility and functions that all stats would use instead of making a whole separate class.
Rather, keep the stats in a hierarchy which each stat derives the general methods from.
There are many ways to skin a cat, in general
I may be misunderstanding something, but in that case how'd I refer to each Stat as a different component (or would I need to at all)? At least right now, each of the 'more advanced' StatSystems (HealthSystem, TimeSystem, etc.) subscribes a method in their StatSystems to an event called by my pickup collection behavior* - also, my UI implementation depends on referring to them as different components
i apologise for my naming conventions btw
player.StatManager.GetStat<int>("Health").value
player.StatManager.GetStat<int>("Health").modifiers
player.StatManager.GetStat<int>("Health").GetModifiedValue()
``` is kind of how I'd access these things personally
so in this theoretical implementation, would StatManager contain a list of Stat<T> objects - GetStat<>() being the method* that retrieves a specific stat from the list?
Tried scimming over everything since I can't dedicate the time needed for help, but it seems you should create a class for each type of system that modifies a Stat and use the flyweight pattern. That way, when you grab a time pickup, your time stat class (whatever you name it) will call its own ModifyStatValue method and change it. Your health stat class will have its own implementation of the method as well. This means you only need to pass in the changeAmount parameter and the stat in question will update accordingly . . .
Well, generics can be a bit awkward, so you may have to specifically implement a dedicated list of Stat<T> for each different T, but yeah
If you wanted to use coroutines and buffs, I would probably suggest having a container and storing modifications to stats as objects.
Random's suggestion is probably ideal by the way, having each stat able to define it's own behaviour is powerful
It can be a bit clutter-ey having a script for all your stats though
At least as I understand it that's what I've been trying to do already, I'm probably just not implementing it properly? idk
when a pickup is collected, CollectBehavior triggers an event (which each of the ____Systems subscribe their StatSystems to) and passes some data through
what I'm hearing is I need to 'move' some of the important bits of StatSystem (ModifyStatValue, etc) to the more specific classes so I can change the way they behave there
abstract class Stat<T>
{
T value
List<Modifier<T>> modifiers
abstract T GetModified()
}
class HealthStat : Stat<int>
{
// implementation
}
Yeah, with generics or not. You should derive the base class you got there.
Most of those methods can be static too
so, at least what I'm thinking
abstract class Stat<T>
{
// The value and it's max (the max is for UI stuff mostly)
T value
T maxValue
// All the modifiers/effects acting on the stat
List<StatModifier<T>> modifiers
// Methods that change the value or the max
abstract void changeValue()
abstract void changeMax()
}
// some examples
class HealthStat : Stat<int> { /* implementation not shown */ }
class TimeStat : Stat<float> { /* implementation not shown */ }
class AmmoStat : Stat<int> { /* implementation not shown */ }
Yeah, that looks good.
Yeah, give or take
I'm assuming that this is also applicable to, say, damage and damage types?
You may also want to make value and maxValue properties with a getter/setter instead of explicitly defining changeValue and changeMax
so ```cs
T value { get; set; }
T maxValue { get; set; }
But that's all code style more than anything
ye
when they're properties I believe they can be made abstract, so you will need to implement them in the derived classes
as far as handling pickups goes, how would I specify the Stat the pickup intends to modify?
The modifier container idea requires some checking when you add and remove from it, but overall I use something similar as it helps keeping track of what's being added or reduced.
Yeah the pickups would have a similar architecture with their own OnPickup(Entity grabber) or something like that
would they contain an instance of Stat<T> themselves?
Well, you would have all the stats on a playermanager, right?
void PickUp()
{
PlayerManager obj = interactingEntity.GetComponent<PlayerManager>();
bool wasPickedUp = obj.PickUpItem(item.GetCopy());
if(wasPickedUp)
{
Destroy(gameObject);
}
}
Not unless they have some stat. If you think about it, right? when a player collides with a pickup, the pickup should be able to find a reference to some stat for modification. That means that entities that are able to pick up that powerup should always share a field of some sort
You'd access the gameObject with a collidier or some interaction script, and apply it to your playerstats
Not exactly sure how I'd handle that, though. Thinking about ammo, a pickup may be for an ammo type (or for a specific weapon!) that the player doesn't have
Just give players the stat, set max amount to 0?
Maybe, but that may raise some issues - especially because weapons aren't set in stone, they're procedural
So, your item would contain some data inside this gameobject lying around, the normal way to handle this is to have a collider event for when your player touches this item. You'll have eventdata from the object and could be able to reference to back to the player.
You can also maybe have the stats as a mono and use GetComponentInChildren<[AmmoType]>() on the player?
hmm...
what about UUIDs, since I've been thinking of using that to identify weapons to begin with?
If you're making weapons with similar base types but with different stats, sure.
yep, that's exactly what I'm doing
So yeah, with the StatModifier objects, each weapon contains one for each stat which you would then send to each stat container on the player.
and you'd update your stats on equip and de-equip. It's how I did a system previously, but changed it quite a bit later on.
The weapon itself can also contain the stat
There's quite a few ways, but ultimately when I started to introducing items that say increase a stat by a percent, it became harder to keep track of original values and such.
Which I resolve in a update function between stat changes.
I presume I'd add a UUID field to the Stat class, then?
You wouldn't need a unique ID on stats; stick them on equipment, but not necessarily consumables.
I see, so only things like AmmoStat would implement the UUID field
Eh, I dont think you need unique ammo unless you're adding stats to those too, but it wouldn't hurt.
And by stats, I mean if one type of ammo has different stats, then yeah.
I mean, at least the way I'm seeing it, most weapons would refer to specific, common ammo types - plasma, kinetic, whatever - some weapons may not need ammo at all, and others may refer to specific types generated by the weapon itself
It makes sense on the weapons because you can say have a plasma gun with +5 recoil and another with +10 firing speed, but if ammo is just a component to use the weapon, they don't necessarily have to be unique.
You'd have different types of ammo, sure, but plasma ammo can be used in all plasma weapons, ect.
yeah, my thoughts exactly
so if I call an event in some Stat<T>, how'd I pass that event's info up to its listeners? for example, allowing the UI to listen in on the player's HealthStat? it seems to me like this would get kind of complicated if I have to create a new List for each T
Honestly, skip the generics. Lmao
I'm more used to Java, and I had a small problem recently using some generics that would be otherwise easily solvable with Java, but instead I had to implement some interface and do some callback shenanigans with it just so I could avoid some cast comparisons.
Reflection I think it was called?
...should I just replace everything with floats?
Up to you, floats probably better if you want to introduce ceiling or floor functionality in your equations.
I mean, if I have x% modifiers, yeah probably
Generics is nice if you want to have like rpg stats like strength being a whole number, but having an attack speed stat that's a float.
Yeah, not going to implement RPG stats
I guess one remaining question I have is if StatModifiers are on timers (via coroutines) how do I manage removing them from the list once they expire?
You'd have to reference the stats with a buffmanager of sorts, and when they expire do like a callback.
I think that's what I did at the start, but I started to introduce a bunch of stats and changed it up a bit.
public class AttributeEffect : ActiveEffectObject
{
//Need to prevent duplicate values eventually
[SerializeField] private List<StatModifier> statModifiers;
[SerializeField] private float duration;
private List<StatModifier> modsToRemove = new List<StatModifier>();
private void OnValidate()
{
}
public override void ExecuteEffect(EntityManager entity)
{
foreach(StatModifier mod in statModifiers)
{
entity.stats[mod.statType].AddModifier(mod);
modsToRemove.Add(mod);
}
entity.StartCoroutine(RemoveBuff(entity, modsToRemove, duration));
}
private static IEnumerator RemoveBuff(EntityManager entity, List<StatModifier> modsToRemove, float duration)
{
yield return new WaitForSeconds(duration);
foreach (StatModifier mod in modsToRemove)
{
entity.stats[mod.statType].RemoveModifier(mod);
}
}
}```
My attribute duration potion class basically.
I see
I have two lists for specific calculation reasons, but you can just remove by object source. I think I did that originally but I ran into errors for some reason I forget.
But the class itself should remove the stat when the coroutine ends
Just need a reference to the player/stats
and I do like a update function everytime I remove/add stuff instead of just deducting so stuff like % items will recalculate
I see
or at least, I think I do
anyways, huge thanks to you all, this was immensely helpful!
yeah, no problem. This stuff is fun ^^