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:55]
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.
   * Fiecare personaj se poate deplasa, pentru a evita atacul simplu   * Fiecare personaj se poate deplasa, pentru a evita atacul simplu
   * Pentru fiecare tura    * Pentru fiecare tura 
Line 47: Line 57:
     * La finalizarea turei se ruleaza/​aplica abilitatile sau mutarile alese/​acumulate     * La finalizarea turei se ruleaza/​aplica abilitatile sau mutarile alese/​acumulate
     * Inamicii realizeaza actiuni random     * Inamicii realizeaza actiuni random
-  * Evenimentele actualizează UI și efectele vizuale prin pooling. 
  
 Pentru simplitate, toate interactiunile se pot realiza prin butoane de comanda, dar este necesar sa folositi un sistem de evenimente pentru decuplarea responsabilitatilor. Pentru simplitate, toate interactiunile se pot realiza prin butoane de comanda, dar este necesar sa folositi un sistem de evenimente pentru decuplarea responsabilitatilor.
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.1759654536.txt.gz · Last modified: 2025/10/05 11:55 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