Hi all.
I really struggle with myself about situations illustrated below:
Suppose I have different text UIs represented by one model(data). data can be Gem. Diamond. Score, Coin, Profile data, Name, AvatarId, Game Results, Levels Completed, etc.
-Most developers say that you should not use MV* patterns in games.
-Most unity developers say that you should use SOs only for immutable data.
I am confused about that. Do you think MV* is bad design pattern for this situations (Several UIs with one data)?
There are different approaches to represent and change text uis with that model:
1- Using ScriptableObjects or reference objects for that model and use it inside TextUI monobehaviour.
2- Using event based approach (OnValueChanged). It is very well and can decouple model with views but there is a problem. I need to initialize the text at first when it becomes active. so how can I handle it?
It can be so complex when I use events for every field that I need to access them to other classes.
I need to add a bunch of eventListeners for every text UIs.
I can remove dependency between classes completely and use only events whenever I want.
For example:
public class ClassA{
void Func(){
classB.Func2(); // remove it and instead, use event.
}
}
//-----------------------------------------------------------
public class ClassA{
GameEvent _event;
void Func(){
_event.Raise();
}
}
So I need another monobehaviour to initialize the text with the model when it is active?
3- Using MV* patterns.
4- Combine model and UI together. (when we have only one UI and one model)
public class EventListener : MonoBehaviour
{
[SerializeField] private GameEvent _gameEvent;
[SerializeField] private UnityEvent<int> _response; // I know it has to change to be serialized :)
private void OnEnable()
{
_gameEvent.RegisterListener(OnEventRaised);
}
private void OnDisable()
{
_gameEvent.UnRegisterListener(OnEventRaised);
}
public void OnEventRaised(int value)
{
_response.Invoke(value);
}
}
public class CustomText : MonoBehaviour
{
[SerializeField] private Text _text;
private void OnEnable()
{
}
public void ChangeText(int value) // It is called using response event
{
_text.text = value.ToString();
}
}
It depends, if you’re working on a simple, small project, where performance is not a consideration, use whatever works for you. But be prepared that all the abstractions will come back and stab you in the back once your project grows and become really heavy (in terms of frame budget).
In performance-sensitive project we don’t measure the code in maintainability we measure it in how many milliseconds it eats up from our frame budget. This is true for the UI as well, given that Unity UI is usually abysmal when it comes to performance.
But of course if you make a table top game or a card game or such, it probably won’t be a problem.
I’m personally(!) always conscious about performance so I wouldn’t use such thing in an Update loop, but otherwise (updating the UI in every N seconds) is perfectly okay. And the event-based solution is especially okay. Just make sure your event is created as early as possible because it creates garbage for the first time (also make sure that you use the same one as much as possible).
Then most unity developers are wrong. SOs are useful to create run-time link between disconnected systems as well. Also they can serve as pieces of serialized code as well. Since they are assets, you can create modular slot-systems with them.
When you use event based approach, why do you need to use mutable SOs referred in mono objects?!
When SOs changed, I can raise an event and all listeners do something unless I do not have any events and want to access them directly.
public class CustomText:MonoBehaviour{
[SerializeField]
private Text _text;
[SerializeField]
private SOVariable SOVariable;
public void Update(){
_text.text=SOVariable.value.ToString();
}
}
SOs usually used (in this context) to decouple two different systems. You can do the same with interface classes as well (which are separate from interfaces, obviously), but you have the advantage that the designers can use these SOs in the editor without knowing a thing about C#. They can reference other game objects and/or other components on game objects or other SOs.
Yes I know I can or can not. Please say one example like my example below.
You said that performance is vital for you. So how do you update your text based on SO data without events? and if you have events like that, why do you need mutable SOs?
I said mutable SO not immutable. I know about convenience of designers.
public class CustomText:MonoBehaviour{
[SerializeField]
private Text _text;
[SerializeField]
private SOVariable SOVariable;
public void Update(){
_text.text=SOVariable.value.ToString();
}
}
Does the text changes on every frame? If not, you really should not update it in every frame.
I would build it on the other way around and would link the UI element in the SOVariable implementation and when the value changes, I would update the text once. Do you really need this MonoBehaviour? Will you implement more functionality here? Because just for the link between these two, you really don’t need it. Just reference the text element in the SOVariable. Or if it’s a general class, extend it and make a TextUpdater or whatever.
No. It does not change in every frame. I said if I use scriptableObjects without any events, I need to use Update method to change the text.
UI element is a text UI(Text). How do you link it in scriptable object variable.
I have several texts that change when one data changes. Also I need to initialize them with that data when they become active in panels.
Is it your meaning about linking ui element inside SO?
public class TextUI:MonoBehaviour{
[SerializeField]
private Text _text;
[SerializeField]
private SO so;
private void OnEnable(){
so.Add(_text);
}
private void OnDisable(){
so.Remove(_text);
}
public void ChangeText(int value){
_text.text=value.ToString();
}
}
public class SO:ScriptableObject{
private int _value;
public int Value{
get{
return _value;
}
set{
_value=value;
_textList.ForEach((t)=>t.ChangeText(_value));
}
}
public void Add(Text text){}
public void Remove(Text text){}
}
If I guess right, it causes data completely depends on text ui and vice versa!
And seriously, there’s absolutely no reason to avoid common patterns, they just need to be ported with some adjustments and they’ll be ready for game dev or whatever you like. It’s not that we’re writing low-level high performance code in which you do weird stuff to save some micro or nanoseconds.
Well, he asked me from performance stand point. And if you’re updating a handful of texts with string operations in Update methods you will end up with a problem very soon. Yeah, it’s scary, I know.
But you probably missed the “whatever works for you” part.
Though your suggestions only improve performance by so little but decreases maintainability tenfold. Our game is written around maintainability first performance second(ish) or shared first maybe. Our performance is still very, very,good. And since its maintainable refactoring is easy, and performance is a perfect valid reason for refactor
OP: What you are doing is not real MVVM, real MVVM has no UI elements in model, you then have a layer that binds the UI unaware model to the UI. This can be doen with reflection etc, or you use one of the existing MVVM frameworks
It will have a initial cost to setup the commutation between model and UI but after that it should be a neglectable perfomance difference.