[Unity] Séparer UI et logique de jeu
Introduction
Lorsque l'on développe une application lourde, un site internet ou un jeu-vidéo de manière classique, nous utilisons souvent des architectures ou des patterns facilitant la séparation des couches comme l'interface utilisateur, les contrôleurs, les modèles d'accès aux données, ...
On peut citer en exemple le modèle MVC (Model - View - Controller) bien connu des développeur Web, MVVM (Model - View - ViewModel) fréquemment utilisé par les développeurs .NET utilisant du XAML. Mais qu'en est-il avec Unity ?
Mise en place dans Unity
Lorsque l'on se lance dans du développement avec Unity, il faut aussi garder cette approche constamment à l'esprit. Voici un exemple de mauvaise pratique qui mélange l'UI et la "logique" d'un script.
Exemple de mauvaise conception
1: using UnityEngine;
2: using UnityEngine.UI;
3:
4: namespace UnityBadSample
5: {
6: public class BadBehavior : MonoBehaviour
7: {
8: public Text CurrentTime;
9: private float _elapsedTime;
10:
11: public void Start()
12: {
13: _elapsedTime = 0;
14: }
15:
16: public void Update()
17: {
18: _elapsedTime += Time.deltaTime;
19:
20: // Update UI
21: CurrentTime.text = _elapsedTime.ToString("N0");
22: }
23: }
24: }
Dans cet exemple très simple, notre script met à jour le temps passé dans un contrôle Text (UI 4.6) placé sur notre scène. C'est quelque chose de très classique seulement ce fonctionnement pose quelques problèmes :
- En cas de changement du type d'affichage de CurrentTime, on doit alors modifier tous les scripts l'utilisant.
- En cas de suppression du contrôle Text dans la scène, nos scripts ne fonctionnent plus. On aurait certes pu faire une vérification pour prendre en compte si l'élément a bien été lié à notre script, mais dans le cas où notre élément est utilisé une dizaine de fois cela devient vite verbeux.
- En cas de changement de l'affichage via un autre script, il faudrait donc maintenir plusieurs liaisons avec le contrôle et en cas de modification ultérieure, cela fait autant de liaisons à refaire.
On voit donc que ce n'est pas vraiment une bonne solution de faire comme ceci.
Exemple de bonne conception
1: using UnityEngine;
2:
3: namespace UnityGoodSample
4: {
5: public class GoodBehavior : MonoBehaviour
6: {
7: private float _elapsedTime;
8:
9: private TimerView _timerView;
10:
11: public void Start()
12: {
13: _elapsedTime = 0;
14: }
15:
16: public void Update()
17: {
18: _elapsedTime += Time.deltaTime;
19:
20: // Update UI with
21: _timerView.UpdateTime(_elapsedTime);
22: }
23: }
24: }
Aucune référence à aucun élément de l'UI n'est présente dans cette classe, seulement une instance vers une nouvelle classe TimerView qui sera chargée de gérer l'affichage.
1: using UnityEngine;
2: using UnityEngine.UI;
3:
4: namespace UnityGoodSample.Views
5: {
6: public class TimerView : MonoBehaviour
7: {
8: public Text CurrentTime;
9:
10: public void UpdateTime(float time)
11: {
12: if (CurrentTime != null)
13: CurrentTime.text = time.ToString("N0");
14: }
15: }
16: }
Une classe unique pour gérer l'affichage du CurrentTime et pleins d'avantages :
- En cas de changement du type d'affichage de CurrentTime, on ne modifie qu'un seul script.
- En cas de suppression du contrôle Text dans la scène, nos scripts continuent de fonctionner.
- En cas de changement de l'affichage via un autre script, il suffit de référencer le script TimerView et d'appeler la méthode UpdateTime().
Conclusion
Comme la plupart des autres type de développements (Web, applicatif, ...) il est important de savoir séparer son code afin de faciliter la maintenabilité de votre jeu-vidéo.
Commentaires