#A recommended way to handle UI manipulation alongside ECS

1 messages · Page 1 of 1 (latest)

bronze edge
#

I have been playing around with Entities for the last week or so, and I keep running into a situation where I have no idea of a "right" way to update my ui from a system.

Currently, I am having to keep the ui outside of a sub-scene (as it seems unsupported for them and won't render), manually create an entity for it with a global World, and then register the elements as managed components. This indeed allows me to query the managed components via something like Entities.ForEach and update them in systems however, this feels very weird to me.

Is there an actual recommended way to handle ui from systems?

merry dagger
#

This is part of the broader problem of coordinating ECS and GameObjects. Generally we say it's preferable for ECS to manipulate GameObjects than the other way around. So ultimately what you want is entities with managed components (classes implementing IComponentData) that contain references to GO's and MB's, e.g.

public class MyComp : IComponentData
{
    public GameObject MyHUD;
    public UIComponent MyMinimap;
}

Be clear that managed components are relatively inefficient to access and they can't be accessed in Burst-compiled code (but generally you're not dealing with them at great scale anyway, so it's often not a big deal).

The tricky part is how to set up these references in your scenes. In a subscene, you can put a prefab in the GO fields of the managed components, and then at runtime you can instantiate this prefab and store the new instance in a GO field of a managed component. This is the simplest way to hook up an entity with a GO.

Unfortunately, it's much harder to hook up an entity in your subscene with a GO from outside the subscene. Unity has no support for cross-scene references, so you instead have to hook them up at runtime. There's no clear best way to do this, and all solutions are a little ugly. The simplest way is to call methods like GameObject.Find from an init/spawning system. These are very expensive calls, of course, but you normally need to call them once after loading your scene.

It is all admittedly weird, so generally I'd recommend minimizing these entity-GO relationships. Like perhaps you only need one Entity-GO relationship for your entire UI: all communication from ECS to GO-land passes through this one GO.

btw, if you put UI GO's in a subscene, they get baked into entities, but the UI components get ignored by baking (because we have no ECS equivalents). So what actually gets loaded with the scene is a bunch of do-nothing entities.

prime patrol
# merry dagger This is part of the broader problem of coordinating ECS and GameObjects. General...

You hit on a couple of pain points with hybrid and the current version of ECS so I was wondering if you could speak to any plans for improvements here.

Unfortunately, it's much harder to hook up an entity in your subscene with a GO from outside the subscene. Unity has no support for cross-scene references, so you instead have to hook them up at runtime. There's no clear best way to do this, and all solutions are a little ugly. The simplest way is to call methods like GameObject.Find from an init/spawning system. These are very expensive calls, of course, but you normally need to call them once after loading your scene.

The problem with something like GameObject.Find isn't just performance. It is also very brittle as it relies on magic strings. This might be okay for top-level manager-like objects, but once you start needing things like entities following an animated character's bones, it isn't a great solution. Are there any plans to make this easier and less error prone? If so, can you share any details on what this would look like?

btw, if you put UI GO's in a subscene, they get baked into entities, but the UI components get ignored by baking (because we have no ECS equivalents). So what actually gets loaded with the scene is a bunch of do-nothing entities.

Are there plans to improve this UX? At a minimum, there should be some kind of warning when adding a MonoBehaviour that doesn't have a Baker defined for it. But even if the MonoBehaviour does have a Baker defined, it is unclear and unintuitive to new users that Update() is never going to run.

shadow quail
merry dagger
# prime patrol You hit on a couple of pain points with hybrid and the current version of ECS so...

Yes, magic strings aren't great, so I'd only recommend that for very simple cases. A bit better is to use guids, something like this https://github.com/Unity-Technologies/guid-based-reference (Like I said, proper cross-scene reference support would solve this, which is really a general Unity problem not specific to ECS. ECS just makes it more acute.)

Some additional warnings might be a good idea, I'll pass that along.

GitHub

A component for giving Game Objects a GUID and a class to create references to objects in any Scene by GUID - GitHub - Unity-Technologies/guid-based-reference: A component for giving Game Objects a...

merry dagger
# shadow quail I would be interested why this is the recommended way? We flipped it around and ...

When you access entities from monobehaviours, you encounter a few problems:

  1. How do you get reference to the proper entity world? Yes, you can use World.DefaultGameObjectInjectionWorld, but that's not valid if you're using Netcode or in other scenarios where you're using additional worlds. You can certainly work around this, but it's not really a supported pattern.
  2. Even if you get a hold of the right world, you won't be able to safely schedule jobs that don't conflict with jobs scheduled by the systems because you don't have any equivalent of the system Dependency property.
merry dagger
#

So especially in cases where most of your game logic is built in ECS, you're better off accessing MB's from systems rather than accessing entities from MB's. If entities are used for a small feature, this may be less of a problem, but I'd still recommend keeping communication one-way: if a MB wants something to happen in ECS-land, it sets some state that a system reads and responds to accordingly.

severe kayak
merry dagger
# severe kayak What you're suggesting amounts to polluting game logic with UI code, which is al...

I'm not suggesting the ECS has to drive everything that happens in MB-land, just that ECS should be in charge of hooking entities together with MB's and handling all ECS-MB communication. For a UI composed of many objects, this could be a single MB that gets its state updated by a system. Simplest example:

  1. player score is an entity component
  2. UI is exposed to ECS through a single MB that is referenced by some entity component
  3. every frame, a system copies the score from the entity component to the MB
  4. when the UI MB's update, they use this value to change the displayed score
versed barn
#

What we do with UI interaction - event like system. MB and Entities worlds completely separate. And we have UIBridge (main thread system in initialization group after destroy and before change ECB) system which handles entity events from ECB and touches UIManager instance. And when player hit buttons (input or UI buttons as they are processed through our custom input handler and every UI button can be keyboard button for example and we have config where you map keys to actions)- they're create another direction entities events. In addition UI have state object which contains required data and partially refresh by specific bit flags when something changed (like population, resources amount etc. we push event with bit flags which is changed) and GameStateUpdateHandler system responsible for populating actions which should be performed by ECS world that frame

elder bone
#

We have devised an architecture where we have UI checking systems and UI update systems:

  • The checking systems perform bursted and scheduled jobs that execute all the calculations necessary for UI and in the end effectively check if the UI should update. If it should, the checking system produces a signal, that will make the UI update system run.
  • The UI update system now runs. It accesses the UI via a singleton pattern (so we don't have to link the monobehaviours and ECS world through components or anything), and just applies the new data (which is already calculated in the checking system). So the update system functions purely as a pass-data-along system with as little calculations being performed in there as possible.
shadow quail
elder bone