#Making a UI for my DOTS game?

1 messages · Page 1 of 1 (latest)

placid zephyr
#

I've searched around and there seems to be a lot of different ways to do this. I'm not sure which of them are actual good ideas - seeing some of the suggestions are pretty old. Even the newer ones i found was from before 1.0 (or mostly atleast).

I have experience with UI Toolkit and wondered if i should just go ahead and do that and run it off a normal MonoBehaviour? Then comes my next question, how would i communicate - i need to send some user input back to my ISystem(s).

I found this code example and was wondering if using World.EntityManager.CreateEntity to create a single entity and then load it with IComponentData is the correct approach? The UI is mostly just the UI components holding the data and can pass it onto a component, but i might later want to track positions of the mouse - does the example in the code below demonstrate a good way of doing that? (adding positions to a DynamicBuffer)?

https://github.com/WAYN-Games/DOTS-Training/blob/DOTS-110/Assets/Scripts/MonoBehaviours/PlayerManager.cs

Sorry if this is a noob question, but i did spend time researching it but right now, i'm not sure what advice/example to follow

GitHub

A repository of all the code to follow he DOTS training series by WAYN Games - DOTS-Training/PlayerManager.cs at DOTS-110 · WAYN-Games/DOTS-Training

#

Making a UI for my DOTS game?

modern niche
#

in my opinion, the right way to do UI for a dots game is to pretend that it's not a dots game for purposes of UI, and then communicate a hopefully-small amount of data between the non-ECS UI side and the ECS side via some mundane mechanism like static variables or similar.

note, by the way, that last I checked UIToolkit had performance issues in builds, and if you're making a dots game, you probably care about that.

placid zephyr
#

Ok thanks. I've used it before where the performance was too small for me to find/register it - so i'm a bit curious as to where the performance issue lies (perhaps it's some component that i did not use?). My current "game" is a simulation with many thousands of vector calculations each frame - adding a UI on top is only for changing settings - so interacting with the UI can lag without it being a problem.

When you say static variables - is that the way to go? Is that better than holding a reference in managed code (MonoBehaviour) to an Entity with a DynamicBuffer on it - and then reading that in an ISystem?

I was hoping there would be some kind of class/interface that would transform data on the fly - kinda like Baking, but instead runtime and continually?

modern niche
#

you can't really hold references to entities, because they move around under you

placid zephyr
ruby cloak
#

I am literally in a middle of creating one more 😅

modern niche
#

yeah it'll move. you could do that, but it seems more expensive and complicated than a static variable to me

ruby cloak
#

my suggestion would be - don't make UI in pure ECS. Keep a layer between ECS and UI. And use best practices of ECS with ECS and best practices of GUI with GUI.

#

I'v tried multiple times and it just awful

placid zephyr
#

Ok thanks - so if i take what both of you say and merge it with my knowledge of UI Toolkit - i would just make a UI Document on a MonoBehaviour and communicate through a static variable (a struct i guess?)

ruby cloak
#

I am developping a UI framework atm which will is built on MVVM Toolkit with data binding for uxml

#

so if you wait for a while, you might have a ready solution

#

😅

#

but generally it's like this:
I have a Mono view hierarchy (similiar to uGUI), it just holds uxml assets reference and other View related stuff (localization table) or whatever you decide to serialize

#

this View is initialized from user code. For ECS case it should be System of specific world and tie that hierarchy to IMessenger and IServiceProvider

#

so you register all required service classes in provider

#

initialize hierarchy, which obtains all required references about world and that's about it

#

no need for static

#

so that would be multiplayer/coop friendly

#

also, since it's game object hierarchy and it's enclosed one (does not rely on any other references in a scene), it can be stored as prefab and instantiated from system as well

#

or even subscene

placid zephyr
#

i never tried uGUI - i only just started using Unity and only made one game so far. i'm a web developer when im at work so i kinda know of the MVVM pattern 🙂 sounds good - i'll check it out

#

btw i was pounding chat gpt - and even though its DOTS knowledge is older - it actually ended up telling me to create a static/singleton EventManager to relay the data back and fourth

ruby cloak
#

doubt it'll suggest any good

#

I tried asking it similiar stuff

#

and it ended up very meh

distant saffron
#

I dont know if its any help but I use probably a really terrible way

#

I use a SystemBase to capure all the UI button inputs and things like that

#

I have a gameobject in the scene with a script/list for all the UI references I need

#

then I get that GO in the SystemBase then I create delegate events methods within the Systembase that activate onbutton press or whatever

hollow locust
modern niche
#

it was a while ago, i'll try to find the exact reference

hollow locust
modern niche
#

i would think instance would be faster

placid zephyr
#

Chat-GPT gave me an event class:

public class UIManager : MonoBehaviour {
    public UnityEngine.UI.Text scoreText;
    public UnityEngine.UI.Button scoreButton;

    private void Start() {
        scoreButton.onClick.AddListener(() => {
            EventManager.instance.onScoreChanged?.Invoke(1);
        });
    }
}

and told me to use it in a JobComponentSystem - i guess i can use ISystem instead:

public class MySystem : JobComponentSystem {
    private int score;
    protected override void OnCreate() {
        EventManager.instance.onScoreChanged += UpdateScore;
    }
    private void UpdateScore(int value) {
        score += value;
        Debug.Log("Score: " + score);
    }
    protected override void OnDestroy() {
        EventManager.instance.onScoreChanged -= UpdateScore;
    }
}

Honestly, to me it doesn't look that bad, and really close to what you suggested - but perhaps my limited knowledge fails to see the problems...

#

as an example of course

#

how would events work in an ISystem? could i subscribe in the OnCreated and then have it call a method to update a variable that is read in OnUpdate?

distant saffron
#

managed stuff is better in SystemBase

modern niche
#

events + ecs = 😦

#

events don't scale, and ecs is all about scale, so it makes sense that they don't play well together

#

static variables don't scale either, but fortunately you can talk to them them from any non-bursted c#, so ecs can't get in the way of them

distant saffron
#
 GameObjStoof = GameObject.Find("GameManagerStuff");

        var gameobjrefs = GameObjStoof.GetComponent<GameObjectRefs>();

        ExitScreenGO = gameobjrefs.ExitScreenGO;      
        PlaceTurretGO = gameobjrefs.PlaceTurret;
        DestroyTurretGO = gameobjrefs.DestroyTurret;
     
        var currbotton = DestroyTurretGO.GetComponentInChildren<Button>();
        currbotton.onClick.AddListener(delegate { DestroyTurretMeth(); });

     ```
ruby cloak
#

Isolate ECS and event driven UI

#

and use layer inbetween

distant saffron
#

thats what I do in Oncreate in a systembase and then have the methods down below

#

its pretty terrible but it works for now

modern niche
distant saffron
hollow locust
# modern niche events don't scale, and ecs is all about scale, so it makes sense that they don'...

For this I would like to feedback something. Since ecs is all about scale, it also bring the performance issue to update UI. Currently I write system with idiomatic foreach ChangeFilter to update UI will be slower and since ChangeFilter is per chunk instead of entity. It seems like currently there's no good solution to update UI per entity only meanwhile keep similar entities into same chunk at the same time.

modern niche
#

i don't quite follow. surely you wouldn't want a ui for every entity, because there could be way too many entities. so what is actually the mapping?

#

how many entities do you actually have ui for?

hollow locust
modern niche
#

oh. yeah i wouldn't do that with any kind of normal ui system at all

distant saffron
#

maybe something like billboards/decals with render to texture

modern niche
#

yeah something in that general vein, i'm not sure exactly what the best thing is

oblique marlin
#
modern niche
#

yes! except it'll be probably weird in srp or something.

oblique marlin
#

Although if we have to also display a name (text) over each unit, I'm not yet sure how I'd approach it

modern niche
#

but that basic approach is exactly right imo

#

i would argue if you really have a ton of units the text won't be readable anyway

late blade
modern niche
#

it seems i was out of date about UIToolkit. here's the description i got for the performance comparison between uitoolkit and ugui: Current situation is that it's never as simple as "A is faster than B". It depends on the platform and the UI use-case. On an RTX 4090 desktop with 12 cores, and a 1000+ item inventory scroll area in your game, UITK will win hands down. On a 2001 Android shit-phone, and 3 buttons on screen, uGUI will win (not "hands down", but still win).

placid zephyr
#

Wow nice guys - i went to eat dinner and a lot has happened! I know the chat bot won't make great code - i'm not gonna copy/paste it - but the idea to use a static instance was the same i heard from you so it does make some sense at least 😄 The UI in my case is only used if you want to change the parameters of the simulation, so loosing a tiny bit on the interaction is OK. As for caching the reference, i agree 🙂 Also since this example was event based - i might be able to not take any performance hit when the UI isn't interacted with 🙂

When i started on DOTS i was afraid that there would be no or little help to find, but you guys are great ❤️

placid zephyr
lapis abyss
#

it can lead to some really bad memory states

#

personally I just have 1 game panel -> systembase

#

i avoid try to avoid mb/go where possible as personally i find with UIToolkit there is little need for them

#

but there is really no right solution atm for UI, i've seen half a dozen different approaches now

#

one of the main advantages of using a system though is I can spin up debug worlds and have 2 sets of UI side by side

#

for debugging multiplayer

#

basically playing 2 copies of the game at the same time

#

whereas this becomes significantly harder, more complicated and requiring custom code when using singletons or MBs

modern niche
#

^ this is the problem with statics, yeah

placid zephyr
#

I've never used a singleton in my unity code before - i only did it now because i didn't know what the right approach would be. In my non DOTS game i use ScriptableObjects for references

modern niche
#

that said my workflow for multiplayer is usually just to have 2 checkouts and 2 editors open, and i promise myself to only edit in one and sync across with git

placid zephyr
#

I still don't understand what i should do, besides use a static instance with events?

modern niche
#

then you never have to make a build

placid zephyr
lapis abyss
modern niche
#

no i still do it then

#

you could argue i'm a maniac

#

but when the project is 300GB, it takes also longer to make a build, so the win is bigger

lapis abyss
#

My big issue with build testing atm is it takes us 40 min to make a build

modern niche
#

this fixes that 🙂

lapis abyss
#

Yeah

#

I do really need to upgrade to 64gb of ram

modern niche
#

yeah you need a lot of ram and an extra ssd, but those have conveniently gotten cheaper lately

placid zephyr
lapis abyss
#

And using system base I can have a base class that sets up everything, makes it much easier

#

The only system base i use in project is ui, input and system groups

#

Oh the other thing is, I don't think dual editors is a great setup for qa, art, design etc

#

Which is really what I'm building this workflow for

modern niche
#

wait those people need to have ui's talking to both worlds at once?

lapis abyss
#

If they're testing multiplayer bugs, syncing animations etc it's a lot nicer if they can have 2 instances side by side

#

Rather than needing to get a second dev or qa

modern niche
#

hmm. yeah, i'm coming around. anyway @placid zephyr i guess the answer is it's a topic of current research 🙂

#

but i think at least for prototyping a singleplayer game you won't be unhappy about static vars

placid zephyr
#

Well i guess i'll create one system for talking to the UI and receive updates - does anyone here use dependency injection frameworks for DOTS?

lapis abyss
#

Definitely (using static) . All the stuff I'm talking about is also only really applicable to a studio. Individuals and small teams won't really benefit over the extra work to setup.

placid zephyr
#

I honestly don't like singletons - i don't like the mess they create, but i could promise myself to only use it in the UI manager and in the ONE ISystem that handles it

lapis abyss
placid zephyr
#

but then - why create a singleton at all - why not just grab the EventManager in my system directly

#

no i guess not, and i wouldn't need one for this project anyways 🙂

#

i can find a gameobject in my ISystem and interact with it like that, right?

#

and since that doesn't move - i can refernce it in the OnCreate?

distant saffron
#

like tertle said its not advisable, and he's the expert

placid zephyr
#

ok wait - what is not advisable - listening on events or referencing a script attached to a gameobject?

distant saffron
#

using an ISystem to reference gameobjects or any managed type data

placid zephyr
#

i'm so confused - what is an alternative then? static properties/singleton?

distant saffron
#

SystemBase

placid zephyr
#

ok i guess i have some reading to do - only know about ISystem

distant saffron
#

oh yeah SystemBase basically came before ISystem

placid zephyr
#

so from a noob perspective - why/when is SystemBase superior to ISystem?

distant saffron
#

that was used for everything before, and now I presume its staying around for anything that can't be bursted

placid zephyr
#

aha - but you can create an ISystem without burst - but SystemBase is still better/safer?

#

for non-burst that is

distant saffron
#

yeah ISystem has extra optimizations

#

anything that doesn't involve pure entities and has special data types, like gameobjects, unique class structures, I don't know would do better in SystemBase

modern niche
#

the thing you're not supposed to do with isystem is have managed fields

#

you're totally allowed to have managed code and manipulate managed objects that you don't shove into the system instance

#

other notable differences: systembase means you can be a class, which means you can inherit, where isystems have to be structs

#

also, we haven't made generic isystems work yet

distant saffron
#

yeah I guess you can't do this with ISystems csharp abstract partial class SpawnRandomObjectsSystemBase<T> : SystemBase where T : unmanaged, IComponentData, ISpawnSettings {

modern niche
#

correct

distant saffron
#

the physics samples do some quirky stuff, I though inheritance wasn't supposed to be a thing in DOTS 🤨

lapis abyss
#

Physics has had pseudo inheritance since day 1

#

With their Collider setup

distant saffron
#

yeah, its interesting code I wouldn't have thought to do anything like that

hollow locust
ruby cloak
placid zephyr
#

Ok. I guess for the sake of convension i will use SystemBase for stuff that interfaces with managed code and schedule(.Run) on the main thread to be safe 🙂

placid zephyr
#

But i could use FindObjectOfType<T> instead i guess

ruby cloak
#

besides

#

it's ECS we talk about

#

we barely have any game objects in a scene

#

😅

placid zephyr
#

Yeah. I'm still new to this. Just needed ECS for a simulation. And i need ui to adjust parameters. That is all

distant saffron
#

I mean it doesn't really matter, but there is another thing it being a string name and you get that wrong and can end up with errors and problems

#

but yeah just do what is better for you as long as it works, better to get something that works first

placid zephyr
#

It works now 🙂 I got a slider that controls the speed of the simulation - the only problem is, i used to read the speed in the OnCreate and store it in a private field on the move job, not i have to get the IDataComponent each update and lookup the speed - i can no longer cache it.... But i don't see how else i can change a member variable in my ISystem?

  • I have a SystemBase that gets a reference to my managed UI and if it detects a change it will modify a component that holds the speed.
  • Then i have an ISystem job that moves my cells, it used to read the speed OnCreate, now it has to check it each OnUpdate to see if the value of that component changed...

There is no way i can modify a property on my ISystem from my SystemBase ? Would it be possible and sensible (if possible) to have my SystemBase, when the speed changes, find my ISystem and modify the Speed property direcly instead?

oblique marlin
#

Instead of storing public variables in systems, it's recommended to store them in a singleton component

One system would write speed to the singleton, and another would read speed from the singleton

placid zephyr
#

yes that is what i'm doing

#

but i need extra stuff to do it each OnUpdate:

#
    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        var deltaTime = math.min(0.05f, SystemAPI.Time.DeltaTime);
        var properties = SystemAPI.GetSingletonEntity<WorldProperties>();
        var worlProp = SystemAPI.GetComponent<WorldProperties>(properties);

        new MoveCellJob
        {
            DeltaTime = deltaTime * worlProp.Speed,
            MinX = -(_dimension.x / 2),
            MaxX = (_dimension.x / 2),
            MinY = -(_dimension.y / 2),
            MaxY= (_dimension.y / 2),
        }.ScheduleParallel();
    }

Just wondering if there is a big overhead in first GetSingleton - then GetComponent

#

previously i read the speed in the OnCreate... but ok - it's not that big a deal - probably gets compiled to something nice with the burst compiler anyways

lapis abyss
#

why can't you just make that a single SystemAPI.GetSingleton<WorldProperties> call?

placid zephyr
#

Let's say you have 3 Components on an Entity and you create an Aspect to combine them - then somewhere you have a system that only needs to read or write one property on one of those 3 components. That system could use the Aspect or the Component itself - would it be slower to use the aspect - does it load/fetch/initialize more, than if i only used the Component?

lapis abyss
#

it would be slower to use the aspect because as you say it would fetch all 3 components

#

would it matter? probably not

placid zephyr
#

ok thank you 🙂

late kite
#

I just wanted to attempt to sum this up. This is my understand, please correct me if i'm wrong:
option 1) System base, on startup Fetches UI GO/managed components and registers actions to onclick/input events
option 2) System base, on startup registers events in an intermediate static class. UI actions modify the static instance/fires events (I guess you could also just store static values and always be checking in SystemBase update instead of using events, but that seems bad)

Note: the delegate methods in your SystemBase process the event and create your desired ECS components.

ruby cloak
#

I'd really suggest not to come up with brand new approaches and instead use existing GUI patterns

#

Unity did that with uGUI and now they had to make UITK 😅

late kite
#

Well I’m new so I don’t really know conventional unity GUI patterns, but I thought this was specific to how to get data between GUI and ECS, leaving your GUI stuff basically alone

ruby cloak
#

in industry they tie GUI with business logic

modern niche
#

I guess you could also just store static values and always be checking in SystemBase update instead of using events, but that seems bad
what seems bad about this?

late kite
#

Doing lots of checks to determine if you should run some code seems wasteful when an event will run the code only when it needs to be. Especially if it isn’t a frequent case

modern niche
#

in general, i would not assume that. events and delegates are surprisingly expensive, and checking stuff once a frame is surprisingly cheap until there's quite a bit of it. but it does vary with the particulars

ruby cloak
#

they are almost same as direct call

modern niche
#

they are GC objects, and they also prevent inlining and often introduce dependent memory accesses, icache misses, etc; but anyway, my general assumption with this stuff is that it's unlikely to take much time any which way

ruby cloak
#

I had literally opposite explanation from someone else but on C# discord 😅

modern niche
#

what runtime was the c# discord talking about? .net core?

ruby cloak
#

don't think it was specified, more like just general

modern niche
#

.net core is 3x faster than mono and does all kinds of smart things with this stuff to mitigate these issues

#

(this is why there is a multi-year effort going on to reduce unity's dependency on mono implementation details so as to switch to .net core, aka .net 5/6/7/etc)

ruby cloak
#

yeah, .net 6.0 is way too good

modern niche
#

anyway, so that's that. and then on the burst side, if you use the burst equivalent of delegates, which is function pointers, you also ruin burst's ability to vectorize because it can't see across the dependent call. so you really do lose quite a bit in many contexts when you pass functions around.

ruby cloak
#

which I don't even consider too much as UI

#

since it's more like world space entity