For runtime binding, the manual suggestions having seperate data source classes that the UI uses. This basically ends up being an MVVM pattern, which is great. But it isn’t quite clicking with me what the intended workflow/setup for this would be in a project.
My personal recommendation is to use the MVVM pattern for binding, even though it’s entirely optional.
In most games/applications I work in, I prefer to have a strong separation between the model and the view. Having a layer in between makes it easier to reason about what and how the UI will consume.
Some of the advantages of this approach:
By exposing exactly what the UI needs in a view model, you can more easily ensure that the binding paths used stay as short as possible. The shorter the better for performance.
You can more easily implement the change tracking interfaces that we provide without having to change your model.
You can more easily decide when the view needs to be updated and also when and how the model should be updated (whether you will be assigning the data directly or using a command pattern).
You can create a view model that takes data from multiple sources.
The view model can perform the necessary data conversions and cache them.
All of this is achievable using the model directly of course, but it requires to instrument your model carefully and potentially expose change tracking on your model, which is not always desirable.
I’ve worked a lot on implementing a lot of UIs where the data that can change at a very high pace, sometimes multiple times per frame and this pattern has often help avoid doing extra work.
But at the end of the day, it depends on the project you are working on, the size of it, how the designer and the programmers work together, etc.
As for the workflow, personally again, I typically define the bindings in Uxml as much as I can (you can use the data-source-type to help with auto-complete in the UI Builder) and then set the dataSource through code. The UI Builder only supports asset references for data sources because we don’t have a reliable way to target arbitrary data and I also prefer not to use ScriptableObject for data sources to avoid mutating the data through bindings.
Right, MVVM seems like the recommend way and makes sense to me (for all the reasons you listed).
Maybe I wasn’t real clear in my original question, I am wondering more about how you go about managing the ViewModels (where do you create them, and setup their sources/models). And how do you update them to reflect the Model data.
Super simple classic example, health for the player or like progress of smelting something, or the like. Cases where one Model is updating the value of another Model. And you want to see those changes in the UI.
I’d say this is all very project dependant and down to individual implementation details. For a game along the lines of Minecraft, where would already be a substantial amount of pure data separate from the game’s view, your UI would be given access to points of data to hook into, for example.
Other games where there is less, likely separate pieces of data, this data can just be hosted as a pure C# class in a monobehaviour, which either does the binding or provides the data to the UI for it to bind itself to the UI. This has been the approach in my current project, where acts like crafting/refining are a one-and-done operation, so I only need the data during the period of time that the UI overlay is open.
Okay let me ask a more concret example. Lets say I have this class
public class Refinery : MonoBehavior
{
// The number of things to refine.
public int RefineCount {get;set;}
private void Update()
{
// Code here to decrease RefineCount every x seconds until 0...
}
}
How would you setup a ViewModel for this so that you can see the current RefineCount, and have the UI update when the value changes. And be able to increment the count from the UI.
I’d just be writing a tutorial on how to use the runtime binding API then. And those already exist on the manual. So follow those and start playing with the API. It’s going to be more informative than any of us can put into a forum post.
I am not asking how to bind to it, I understand I can just bind to the Refinery component directly. And I already read all the documentation on runtime binding.
I’m trying to understand how you would implement the class that implements INotifyBindablePropertyChanged. A RefineryViewModel if you will.
public class RefineryViewModel : INotifyBindablePropertyChanged
{
private int _refineCount;
public event EventHandler<BindablePropertyChangedEventArgs> propertyChanged;
[CreateProperty]
public int RefineCount
{
get => _refineCount;
set
{
if (_refineCount== value)
return;
_refineCount= value;
Notify();
}
}
private void Notify([CallerMemberName] string property = "")
{
propertyChanged?.Invoke(this, new BindablePropertyChangedEventArgs(property));
}
}
Lets say it is something like this (taken from the manual). How do you change the RefineCount on the RefineryViewModel to match the value in Refinery? Or do you use a different approach?
What? The binding system is literaly not the ViewModel… a ViewModel is always its own class, that is literally the point of having one. It lets you implement UI-only logic and seperate it from the raw data.
Use data sources as buffers between data and UI: Whenever possible, implement data sources as intermediaries between your data and the UI, instead of directly using the data. This approach offers several benefits:
Provide better control over the data flow and facilitates tracking changes originating from the UI. It allows you to manage when and how the data is updated.
Centralize all UI data in one location, simplifying data access and reducing complexity throughout the application.
Maintain the cleanliness and efficiency of the original data, eliminating the need for additional instrumentation on your types and ensuring data integrity.
Fair point, but I honestly don’t know how you’d do that with the runtime binding API without pretty much always manually polling the raw data, which isn’t easy to do from a plain C# class.
To that end I’ve always just had my data in a plain C# object, bindings and all, and bind to that directly. In the end the result is the same.
So @martinpa_unity this brings me back to asking how to setup a ViewModel. Maybe you can share an example, or show how to do it with the class I poste above?
public class RefineryViewModel : INotifyBindablePropertyChanged
{
private readonly Refinery _refinery;
public RefineryViewModel(Refinery refinery)
{
_refinery = refinery;
}
[CreateProperty]
public int RefineCount
{
get => _refinery.RefineCount;
set
{
if (_refineCount == value)
return;
_refineCount= value;
Notify();
}
}
private void Notify([CallerMemberName] string property = "")
{
propertyChanged?.Invoke(this, new BindablePropertyChangedEventArgs(property));
}
}
Because the default binding update mode does it in every Update, so I can only see it working if you hold onto the original raw data object and your view model only acts as an intermediary for calls, but this seems like a lot of boilerplate.
Maybe you can share an example, or show how to do it with the class I poste above?
I would do something similar to what @spiney199 does above, except I wouldn’t even keep a reference to the Refinery, just straight data that the view will use. For simple examples, it looks like a lot of boilerplate and unnecessary and… I agree.
This sort of pattern makes more sense when the data for a given UI screen is spread across multiple MonoBehaviours and managers. You can either instrument all the MonoBehaviours/managers or you can do it only for the data that will be used.
For a simple example, if you have the concept of guilds in your game, you could have something like:
A ScriptableObject containing the information about a given guild (i.e. the player who created it, the guild name, the level of the guild, its members, etc.)
A manager MonoBehaviour that will take care of the interactions between guilds (i.e. allies, enemies, etc.)
A GuildViewModel that will be used to display a guild screen, showing the guild information and its associated interactions.
In that particular scenario, you could have a MonoBehaviour on the game object that contains the UIDocument. That component could keep a reference to both the guild information and the interaction manager and populate the view model with the current data in its Update method.
Now, let’s say you decide to convert your interaction manager to a entities system. You will have to update the way your update the view model data, but since the UI only cares about the view model, the existing UI will continue to work without any changes.
In a lot of projects that I worked on, the view models consisted of something like:
One or more SetData(/**/) methods where the data would be transferred and compared to previously cached data.
The UI would have access to read-only data through a getter.
Relevant changes coming from the UI would create commands through the setter that would be processed in order by some managers.
A lot of times, we used one kind or another of source generation to get rid of the boilerplate, but otherwise, if I would use this pattern a lot, I would create a base class that abstracts most of it away.
I see, so the way to keep the ViewModel up to date is to literally just sync it in an Update loop. Alrighty, I just wasn’t sure if there was some other way that was intended. But that’s fine I am just glad for the clarification. Thanks!