1 using wrapper object like this (like scriptablevariables etc)
Benefit is decoupling user from other classes like Character
// CombatDataVariable.cs
using UnityEngine;
[CreateAssetMenu(menuName = "Combat/CombatDataVariable")]
public class CombatDataVariable : ScriptableObject
{
public CombatDataValue Value;
public event System.Action OnCombatDataChanged;
public void SetValue(CombatDataValue newValue)
{
Value = newValue;
OnCombatDataChanged?.Invoke();
}
}
// CombatDataValue.cs
[System.Serializable]
public class CombatDataValue
{
public int Damage;
public float Range;
// Add other combat-related data here
}
// Example usage in another class
public class Character : MonoBehaviour
{
[SerializeField] private CombatDataVariable combatDataVariable;
private void Start()
{
combatDataVariable.OnCombatDataChanged += UpdateCombat;
UpdateCombat();
}
private void UpdateCombat()
{
// Access data as needed
int damage = combatDataVariable.Value.Damage;
Debug.Log($"Updated Damage: {damage}");
}
}
2 using main wrapper object like
// CharacterData.cs
[System.Serializable]
public class CharacterData
{
public WeaponData WeaponData;
}
// WeaponData.cs
[System.Serializable]
public class WeaponData
{
public CombatData CombatData;
}
// CombatData.cs
[System.Serializable]
public class CombatData
{
public int Damage;
public float Range;
}
// Example usage in Character.cs
public class Character : MonoBehaviour
{
[SerializeField] private CharacterData characterData;
private void Start()
{
Debug.Log($"Weapon Damage: {characterData.WeaponData.CombatData.Damage}");
}
}
Benefit is no need boilarplate code to write variables
3 manual sync
// Character.cs
public class Character : MonoBehaviour
{
private MeleeCombat meleeCombat;
private RangedCombat rangedCombat;
public void ChangeWeapon(Weapon weapon)
{
meleeCombat.SetWeapon(weapon);
rangedCombat.SetWeapon(weapon);
}
}
No benefit i believe because we write extra code and decouple a lot of classes
4 using events
// EquipmentController.cs
using UnityEngine;
using System;
public class EquipmentController : MonoBehaviour
{
public event Action<Weapon> OnEquipmentChanged;
public void EquipWeapon(Weapon newWeapon)
{
OnEquipmentChanged?.Invoke(newWeapon);
}
}
// CombatData.cs
[System.Serializable]
public class CombatData
{
public int Damage;
public float Range;
public void UpdateWeaponData(Weapon weapon)
{
Damage = weapon.Damage;
Range = weapon.Range;
}
}
// Example usage in another class
public class Character : MonoBehaviour
{
[SerializeField] private EquipmentController equipmentController;
[SerializeField] private CombatData combatData;
private void OnEnable()
{
equipmentController.OnEquipmentChanged += ChangeWeapon;
}
private void OnDisable()
{
equipmentController.OnEquipmentChanged -= ChangeWeapon;
}
private void ChangeWeapon(Weapon weapon)
{
combatData.UpdateWeaponData(weapon);
Debug.Log($"Weapon updated: Damage = {combatData.Damage}, Range = {combatData.Range}");
}
}
Of course, it would be a little ridiculous to say that I always use this. Because they all have their uses.
But what is your favorite method in general?
Is there another method you use other than these?
While researching the reason why my code collapsed architecturally after a while, I realized that it always exploded due to this data sharing.
Is there a resource you can share so that I can learn how to build the right architecture for this?
I looked at ECS, it actually solves all my problems, if I understand correctly, all the data is in one place and accessible to everyone. However, I don’t know if it is ready to make a 100% game and write all the code on it.
oh im suprised you said this, i found this variables so useful to communucate between scripts without coupling them. (maybe because im using di framework)
Oh actually that was my least favorite but after you wrote it it started to make more sense
The right architecture depends on the project and really the only person who can be work out what needs to be done is the person working on it.
I’ve stopped trying to hard to anticipate what architecture I’ll need, particularly when working something I haven’t tackled before. Keeping the initial implementation small and applying it to an actual use-case will expose any issues and make the way forward pretty clear.
ECS is a completely different way of writing code so pretty much everything you will have learnt so far will be out the window.
This is very good advice, thanks. I’m probably a little lacking in refactoring, I should focus on that.
But after learning, is it possible to make a game entirely on ecs? Since I don’t have any problems with performance, only its architecture appeals to me. So if I write some of my code in normal monobehaviours and some in ecs, I don’t think it will be very useful for me.
I heard that animator and ui were missing, but I think the animation package was released in beta.
The key takeaway I think was to test often and early.
It’s pretty easy to get lost in the annals of coding a system’s architecture. On more than one occasion I finished a system, went to use said system, and found it didn’t even do what I needed it to in the end. Or it did too much and was too complicated to use on your consumer-side code.
ECS/DOTS is a means to get more performance at scale. It’s not really about architecture, or at the very least you are at the whims of the architecture it enforces due to the way it works. I haven’t used it, but far as I’m aware the hybrid approach is really the only way forward.
Other parts of Unity such as Burst and Jobs that it depends on can also be used on their own.
There are plenty of ECS libraries which aren’t made for rotating gazillion cubes and don’t require months/years to learn them. MorpehECS is my favorite, LeoECS, DragonECS are also a good options for beginners.
They are classes friendly (even if struct based), OOP friendly and can coexist with usual Unity and usual libraries.
Even if you just put Animator and Transform into a component you will still get a lot of ECS benefits. Actually the good practice is to not use ECS for literally everything. Hierarchies, Trees, Graphs, StateMachines, UiManagers etc. are things that you better write as class and put into a component which you can access from systems.
I tend to just use C# events manually most of the time in practice.
I really like how the event keyword makes it impossible for external parties to raise the event, or clear the delegate’s invocation list. With something like the event bus pattern I find it really troublesome how easy it is to make a copy-paste error, and end up raising some completely unrelated event by mistake
Although, I don’t think C#'s events are perfect either. They can lead to bugs quite easily when used inside long classes.
Because of this, I do think a messaging pattern implementation where components can just implement interfaces, and the framework will automatically subscribe them to get notified of the relevant events during OnEnable, and unsubscribe them during OnDisable, can be a superior solution overall.
But in order to be able to utilize this approach everywhere in the project, basically all objects would need to managed by, or hooked up, to the framework that handles this automatic subscribing and unsubscribing. Often this is not the case, unless the project was built with this pattern in mind from the very beginning.
The convenient thing with just using C# events is that they can always be used in all plain old C# objects and components, regardless of the context.