Hello, I have been working with Unity’s ECS for some time now and I am wondering what are your practices of syncing the ECS based gameplay with Unity’s UI.
For UI that is based on specific events happening in the game this approach has worked for me so far:
public class SomeSystemData : IComponentData
{
public Action PlayerDied;
}
And here’s the managed system itself:
public partial class SomeSystem : SystemBase
{
private EntityQuery _playerQuery;
protected override void OnCreate()
{
EntityManager.AddComponentObject(SystemHandle, new SomeSystemData());
_playerQuery = new EntityQueryBuilder(Allocator.Temp).WithAll<PlayerTag>().WithNone<AliveTag>().Build(this);
RequireForUpdate(_playerQuery);
}
// Player died
protected override void OnStartRunning()
{
EntityManager.GetComponentObject<SomeSystemData>(SystemHandle).PlayerDied?.Invoke();
}
protected override void OnUpdate()
{
}
}
Right now this UI MonoBehaviour is coupled to SomeSystem but the idea here would be to try and get some interface-like component that ECS systems would change/interact with so there’s no direct link between UI and gameplay systems. The biggest downside right now is the Update method which tries to initialize the MonoBehaviour if it failed initializing at Start method. This probably could be prevented by using custom bootstraps/world creation to ensure the correct order of creation and initialization.
I would be interested to see what are your approaches to creating UI in the new ECS framework!
Author UI as data (e.g. via EntityManager.AddComponentObject), and query on it via managed (SystemBase) system.
This is by far most reliable way to communicate for the UI.
Basically its one-way ECS → MonoBehaviour;
Pros:
Required data can be directly queried / accessed from entities in a system, just as usual;
UI updates only when present (authored);
Can be reactive if subscriptions are performed in OnCreate / OnDestroy (cleanup)
(e.g. Enabled can be used to toggle logic)
Cons:
Managed, obviously. But at the same time MonoBehaviours already are, so no big deal.
E.g.
public class SomeMonoBehaviourUI : MonoBehaviour {
// Author SomeMonoBehaviourUI as managed component to the required World first
...
public void DoSomething(UIData data);
}
// Then in a system
public partial class SomeUISystem : SystemBase {
protected override void OnUpdate() {
...
// E.g. if (!_playerDeadQuery.IsEmpty)
// Or even better -> use filter in OnCreate alike RequireForUpdate
Entities.ForEach((in SomeMonoBehaviourUI ui) => {
...
ui.DoSomething(data);
// Or even better, perform logic on the UI component from the system directly,
// without passing data, e.g. show / hide, start animations etc.
}).WithoutBurst().Run();
}
}
If you’re troubled about decoupling - you don’t actually need it in this case.
All data is already decoupled from UI - its stored on the ECS side.
To add more features, you’d just write different systems with different logic.
@VergilUa this seems like a very natural approach to creating hybrid UI for ECS - it reduces most of the boilerplate code and coupling issues, thank you.
One important thing to remember is to clean up the entity on reloading/quitting the scene so the systems won’t run into NullReferenceExceptions.
This might be fine to enable or disable ui, but we often need to know if some user interface is visible, or if it’s showing or hiding, in BurstCompatible code. So we’ve just made an ECS layer for knowing/controlling the ui, that we call ViewSystem.
Every ui element has an entity and some states components. For instance, the Ui for showing subtitles is called SubtitleView has an entity composed like this:
View
ViewObject
Canvas
CanvasGroup
etc.
SubtitleView
FixedString64 TextId
SubtitleViewObject
TMProUGUI Text
ViewIsHidden
What has an “Object” suffix is a component object, all the others are IComponentData. So basically, another ECS system could do:
SetComponent on SubtitleView to change the text
AddComponent ViewShow on the view entity to trigger the showing of the view
This way the simulation is clean, and somewhere after we apply the new text to the text mesh pro component, and the new alpha value to the Canvas Group. It also allows other systems to know if Subtitles are currently displayed by querying SubtitleView and ViewIsShown tags, all in bursted code.
@alexandre-fiset Your solution sounds really interesting. Would you be able to post small code examples that show how that approach works? I’m fairly new to DOTS. Specifically I’m unsure how using SetComponent on SubtitleView would change the text, but I’d be really interested to see how the approach works as a whole.