Code Design - Character's sheet

Hello!
I’m developing a Tower Defense and I created the TowerData script where containts every tower attribute.

I would like keep attributes generics without writing by hand things like

[SerializeField] float _attackDamage;
[SerializeField] float _attackRange;
[SerializeField] float _attackIntervalSeconds;

instead something like (SerializableDictionary is a class I wrote to serialize Dictionary. You can consider it like a normal Dictionary):

[SerializeField] SerializableDictionary<Attribute, float> _attributes;

And Attribute is this simple class

[CreateAssetMenu(fileName = "Attribute", menuName = "Tower Defense/Attribute")]
public class Attribute : ScriptableObject { }

I would use Attribute just to create them by Unity and assign to every Tower Datasheet.
In this way I could assign only needed Attributes without having to manually write and assign to everytower, also who has only Auras.

However the problem come when I want add Splash damage to a Tower’s Attack, cause the Attribute would have no “float” value, instead it would have a “float, float” value having “AreaRadius, DamageReduction”.

How would you do this?

I hope I wrote in right section, with this new Unity forum it’s difficult for me to orientate. However this is not a bug or a problem, only ideas exchange :slight_smile:

If the game doesn’t exist yet then you are thinking way too far ahead.

For any given attribute, somehow that value has to make it into the correct spot in your game before it can be useful.

Numbers by themselves are utterly meaningless.

Make the game enough that you CAN have splash damage, then expose the splash damage parameter as some kind of attribute.

Until then all the stuff people type here is going to just be meaningless noise.

In Unity, ScriptableObjects are generally used for this sort of configuration stuff. Here’s some more reading:

ScriptableObject usage in RPGs:

Usage as a shared common data container:

The game has not be released, but it’s under developing and actually tower attack is working how it’s in the first example:

float _attackDamage;
float _attackIntervalSeconds;
float _attackRange;

and so on. I would like to know if anyone could have a better way to do it. I had the “Attribute” idea but it didn’t work with Splash Damage.

Thank you for SOs explaining, I know and I love them. They are so usefull, expecially using them how Austin shown in 2017.

I add a point. Using Attribute Scriptable Object’s class could give the fast way to coding AttributeUpgrade doing this:

public class AttributeUpgrade : ScriptableObject
{
    [SerializeField] Attribute _upgradedAttribute;
    [SerializeField] float _upgradeValue;

    public void ApplyUpgradeTo(Tower tower)
    {
        tower.Data.Attributes[_upgradedAttribute] += tower.Data.Attributes[_upgradedAttribute];
    }
}

I am not certain I understand what you are doing but so far it appears that you want a Dictionary to contain your attributes BUT you only support float values, is that the case?

I haven’t used ScriptableObjects yet, my current project doesn’t support them, so I could be completely mistaken. You want to get one of these SOs where the value is generally a float right? And you can’t get a value of two floats that way?

If so doesn’t the solution sound quite limited? If not then I’ve misread things and “never mind” :slight_smile:

Perhaps consider encapsulating the value of an attribute in an object too, perhaps abstracted by an interface.

Last time I did something like this, though for stats, an individual stat looked something like this:

[System.Serializable]
public sealed class StatComponent
{
	[SerializeReference]
	private IStatType _statType;
	
	[SerializeReference]
	private IStatValue _statValue;
	
	public IStatType StatType => _statType;
	
	public IStatValue StatValue => _statValue;
}

// usage
// in stats container
public bool TryGetStatValueOfType<T>(IStatType statType, out T statValue)
{
	statValue = null;
	
	foreach (var statComponent in _statComponents)
	{
		bool statTypeMatch = statComponent.StatType.Equals(statType);
		if (statTypeMatch == true && statComponent.StatValue is T cast)
		{
			statValue = cast;
		}
	}
	
	return statValue != null;
}

Little awkward in the usage, but there may be a more elegant way to do it.

1 Like

What is the Dictionary doing here? Couldn’t the data just live in the Attribute itself?

I thought to do something like this, I just didn’t like the need to “cast” the value to the type I wish cause the low performance.
Did it works well to you?

Admittedly in my project looking up stats is not a code hot-path. They’re only on the player and they only have… four stats so far.

In a tower-defense game I can definitely see performance being a concern. At the very least, it’s something to test with some profiling.

1 Like

I thought to add value in Attribute class. I didn’t do it cause I want a universal Attribute usable to every Tower.

AttackDamage Attribute has to be the every tower’s AttackDamage. If I add value in Attribute class I’ll have to create a ScriptableObject AttackDamage Attribute for every Tower I instantiate in game.

Have you thought to something different I couldn’t see? (I’m not so good with english, so I want to highlight I’m not sarcastic or annoying, I’m just asking about your idea XD)

Oh, I assumed you wanted to use the Attribute ScriptableObjects as shared values. So you’d have a 5 Damage Attribute, and every tower that did 5 damage used the same attribute object.

If these are just markers for values, then I think you’re just making very, very complicated float fields?

The thing I’m doing are tower that have same base value for everything

ex:
Every Arrow Tower has 5 base attack damage
Every Cannon Tower has 8 base attack damage

Then, every Arrow Tower can be upgraded independently from other Arrow Towers.

I think the explicit code could be help me to be clearer:

[System.Serializable]
public class EntityData : ScriptableObject
{
    [field: SerializeField] public SerializableDictionary<Attribute, float> Attributes { get; set; }
}

public abstract class GameEntity : MonoBehaviour
{
    [SerializeField] protected EntityData _baseDatas;
}

public class Tower : GameEntity
{
    [SerializeField] EntityData _currentDatas;
    
    private void OnEnable()
    {
        _currentDatas = Instantiate(_baseDatas);
        // Apply every personal upgrade
     }
}    

In this way every upgrade apply to upgraded tower’s currentDatas.

Actually I’m doing the same thing, but EntityData class is:

public class EntityData : ScriptableObject
{
    [field: SerializeField] public float AttackDamage { get; set; }
    [field: SerializeField] public float AttackRange { get; set; }
    [field: SerializeField] public float AttackIntervalSeconds { get; set; }
    // And so on with other attributes
}

And I have to code every Attribute upgrade.

There is GameEntity abstract class cause Monster class too can have upgrades wave by wave, but they have common upgrades, not for single monster instance.