Differences

This shows you the differences between two versions of the page.

Link to this comparison view

pjv:laboratoare:2025:a02 [2025/10/05 11:56]
alexandru.gradinaru
pjv:laboratoare:2025:a02 [2025/10/23 12:06] (current)
andrei.lapusteanu
Line 29: Line 29:
  
 https://​unity.com/​how-to/​architect-game-code-scriptable-objects#:​~:​text=ScriptableObject%20is%20a%20serializable%20Unity,​to%20manage%20changes%20and%20debugging. https://​unity.com/​how-to/​architect-game-code-scriptable-objects#:​~:​text=ScriptableObject%20is%20a%20serializable%20Unity,​to%20manage%20changes%20and%20debugging.
 +
 +=== Resurse Andrei L ===
 +
 +  * Explicatii pattern-uri in Unity (Factory, Pooling, State, Command, Observer, MVC & MVP) [[https://​learn.unity.com/​tutorial/​65e0df08edbc2a2447bf0b98?​uv=2022.3&​projectId=65de084fedbc2a0699d68bfb#​|aici]]
 +  * PDF cu toate pattern-urile (Unity): {{:​pjv:​laboratoare:​2025:​unity_patterns.pdf|}}
 +  * PDF despre Scriptable Objects si arhitectura modulara cu ele (Unity): {{:​pjv:​laboratoare:​2025:​unity_modular_architecture.pdf|}}
 +  * PDF despre guideline-uri de programare C# (Unity): {{:​pjv:​laboratoare:​2025:​unity_cleaner_code.pdf|}}
 +  * Repo cu exemple de pattern-uri (Unity): [[https://​github.com/​Unity-Technologies/​game-programming-patterns-demo|aici]]
  
 </​hidden>​ </​hidden>​
Line 40: Line 48:
     * un mass damage     * un mass damage
     * attack simplu     * attack simplu
 +  * Abilitatile au efecte grafice (particule, text cu -10dmg etc) la aplicare
   * Evenimentele actualizează UI și efectele vizuale prin pooling.   * Evenimentele actualizează UI și efectele vizuale prin pooling.
   * Fiecare personaj se poate deplasa, pentru a evita atacul simplu   * Fiecare personaj se poate deplasa, pentru a evita atacul simplu
Line 56: Line 65:
     * un mass damage: cooldown 3 ture     * un mass damage: cooldown 3 ture
     * attack simplu: cooldown 1 tura     * attack simplu: cooldown 1 tura
-  * Abilitatile au efecte grafice (particule, text cu -10dmg etc) la aplicare 
   * Elemente de design imbunatatit:​ scena/​personaje/​animatii etc   * Elemente de design imbunatatit:​ scena/​personaje/​animatii etc
   * Grid system   * Grid system
Line 74: Line 82:
     * Command     * Command
     * Observer     * Observer
 +      * Cu evenimente C# sau ''​UnityEvent''​
     * Object Pooling     * Object Pooling
-    * MVC (cu ScriptableObjects)+    * MVC 
 +      * Cu scriptable objects sau Event Bus
  
  
Line 606: Line 616:
 </​code>​ </​code>​
  
 +===== Event Bus =====
  
 +Prezentam un pattern pentru decuplare si mai "​loosely coupled"​ decat cele anterioare (un fel de "final boss" 🙂), intrucat folosind aceasta metoda puteti comunica fara nicio referinta (nici macar indirecta, precum in cazul comunicarii via ''​ScriptableObjects''​.
  
 +<note important>​
 +Reiteram faptul ca fiecare pattern are valoare intrinseca si ceea ce face unul sa fie mai "​bun"​ decat altul depinde foarte mult de context. ​
 +  * Referentiarea directa este OK pentru prototipare radpida si proiecte mici
 +  * Event Bus-ul este recomandat pentru proiecte la scara larga
  
 +De asemenea, este important de mentionat faptul ca pe masura ce decuplam sisteme, debugging-ul si urmarirea flow-ului pot avea de suferit (ceea ce face codul este mai obfuscat).
 +</​note>​
  
 +**Event Bus-ul** reprezinta un sistem centralizat care face management de mesaje (ex. evenimente) si este un middle-man intre diversele componente ale unui proiect. Conceptul, este similar cu cel bazat pe ''​ScriptableObjects''​ prezentat anterior, dar nu mai necesita existenta unui obiect concret (''​ScriptableObject''​-ul) si poate defini tipuri de date arbitrare care sunt "​pasate"​ intre componente.
 +
 +La cel mai de baza nivel un **event bus** trebuie sa suporte
 +  * O metoda de **abonare (subscrice)**
 +  * O metoda de **dezabonare (unsubscribe)**
 +  * O metoda de **semnalare eveniment (broadcast/​publish)**
 +
 +Asadar, o componenta A se va abona la un eveninment (asculta), iar componenta B va face broadcast/​publish - moment in care A va executa codul aferent. In acest fel event bus-ul este un **router de mesaje**.
 +
 +Decuplarea reise din faptul ca atat A si B trebuie sa fie "de acord" ce date transfera, dar cine si cum face comunicarea intre ele (event bus-ul) nu este de interes pentru niciuna din componentele aflate in schimbul de mesaje.
 +
 +Inainte de a introduce un exemplu, clarificam pe scurt cateva elemente folosite in constructia event bus-ului.
 +
 +==== Generics (T) ====
 +
 +In C# puteti folosit tipuri de date generice prin type parameter-ul ''​T'',​ care este in esenta un placeholder pentru orice tip de date.
 +
 +<code c#>
 +public class GenericList<​T>​
 +{
 +    private List<​T>​ items = new List<​T>​();​
 +    ​
 +    public void Add(T item) => items.Add(item);​
 +}
 +
 +public class Program
 +{
 +     ​public void Example()
 +     {
 +         var intList = new GenericList<​int>​();​
 +         ​intList.Add(10);​
 +         
 +         var stringList = new GenericList<​string>​();​
 +         ​stringList.Add("​PAJV"​);​
 +     }
 +}
 +</​code>​
 +
 +==== Records (C# 9+) ====
 +
 +Un **record** este un tip special de clasa, folosita in principal pentru structuri de data imutabile (immutable data container). Se pot scrie/​declara foarte concis. Implementeaza in mod automat
 +  * Constructor
 +  * Proprietati
 +  * Check-uri de egalitate
 +  * ''​ToString()''​
 +
 +In esenta, cand este nevoie de un tip de date care poate fi referentiat si este imutabil, un **record** este o alternativa buna.
 +
 +De exemplu:
 +
 +<code c#>
 +public record PlayerHealthChangedEvent(int Current, int Max);
 +</​code>​
 +
 +creeaza o clasa completa cu tot cu constructor,​ 2 proprietati (''​Current'',​ ''​Max''​),​ equality check.
 +
 +==== Singleton ====
 +
 +Event bus-ul propus este un **Singleton** (adeseori considerat de fapt un [[https://​www.geeksforgeeks.org/​system-design/​why-is-singleton-design-pattern-is-considered-an-anti-pattern|anti-pattern]]),​ anume astfel ne asiguram de faptul ca event bus-ul are o singura instanta si este accesibil in mod global. Detaliile de implmenetare ale acestuia sunt omise in laborator.
 +
 +
 +===== Exemplu implementare Event Bus =====
 +
 +Event bus-ul:
 +
 +<code c#>
 +
 +public record OnPlayerDiedSoundEvent(AudioClip SoundToPlay);​
 +public record PlayerHealthChangedEvent(int CurrentHealth,​ int MaxHealth);
 +// ... add more records as neeed ...
 +
 +public class EventBus : Singleton<​EventBus>​
 +{
 +    private readonly Dictionary<​Type,​ List<​object>>​ _subscribers = new();
 +    ​
 +    // Abonare.
 +    public void Subscribe<​T>​(Action<​T>​ listener) where T : class
 +    {
 +        var eventType = typeof(T);
 +        ​
 +        if (!_subscribers.ContainsKey(eventType))
 +        {
 +            _subscribers[eventType] = new List<​object>​();​
 +        }
 +        _subscribers[eventType].Add(listener);​
 +    }
 +    ​
 +    // Dezabonare.
 +    public void Unsubscribe<​T>​(Action<​T>​ listener) where T : class
 +    {
 +        var eventType = typeof(T);
 +        ​
 +        if (_subscribers.TryGetValue(eventType,​ out var subscriber))
 +        {
 +            subscriber.Remove(listener);​
 +        }
 +    }
 +    ​
 +    // Semnalare mesaj.
 +    public void Broadcast<​T>​(T eventData) where T : class
 +    {
 +        var eventType = typeof(T);
 +        ​
 +        if (_subscribers.TryGetValue(eventType,​ out var subscribers))
 +        {
 +            foreach (var subscriber in subscribers)
 +            {
 +                (subscriber as Action<​T>​)?​.Invoke(eventData);​
 +            }
 +        }
 +    }
 +}
 +</​code>​
 +
 +Exemplu clasa ''​Player''​ care face ''​Broadcast'':​
 +
 +<code c#>
 +public class Player : MonoBehaviour
 +{
 +    // Omitted other implementation details...
 +    ​
 +    private void PlayerDamaged() => EventBus.Instance.Broadcast(new PlayerHealthChangedEvent(health,​ maxHealth));​
 +     
 +    private void Die() => EventBus.Instance.Broadcast(new OnPlayerDiedSoundEvent(deathSound));​
 +}
 +</​code>​
 +
 +Exemplu de abonat:
 +
 +<code c#>
 +
 +public class PlayerUI : MonoBehaviour
 +{
 +    [SerializeField] private TextMeshProUGUI text;
 +    ​
 +    private void OnEnable() => EventBus.Instance.Subscribe<​PlayerHealthChangedEvent>​(OnPlayerHealthChanged);​
 +    ​
 +    private void OnDisable() => EventBus.Instance.Unsubscribe<​PlayerHealthChangedEvent>​(OnPlayerHealthChanged);​
 +    ​
 +    private void OnPlayerHealthChanged(PlayerHealthChangedEvent @event) => text.text = $"​Health:​ {@event.CurrentHealth} / {@event.MaxHealth}";​
 +}
 +
 +</​code>​
  
pjv/laboratoare/2025/a02.1759654605.txt.gz · Last modified: 2025/10/05 11:56 by alexandru.gradinaru
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0