Differences

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

Link to this comparison view

pjv:laboratoare:2022:04 [2022/11/10 14:00]
alexandru.gradinaru
pjv:laboratoare:2022:04 [2022/11/23 13:12] (current)
alexandru.gradinaru
Line 1: Line 1:
-===== Programarea ​interfetelor grafice ​=====+===== Programarea ​obiectelor ​=====
  
 ==== Cerinte ===== ==== Cerinte =====
  
-<note important>​Pentru simplitate puteti porni de la scena de MOBA creata in Lab3, dar nu este obligatoriu</​note>​+<note important>​Pentru simplitateputeti porni de la scena de MOBA creata in Lab3, dar nu este obligatoriu</​note>​
  
 Realizati o scena care sa contina: Realizati o scena care sa contina:
  
-  * Environment:​ teren cu copaci +  * Personaj (player)
-  * Player+
     * Controlabil     * Controlabil
-    * Are un healthbar afisat +    * Are viata (health) 
-    * Are cel putin 2 atribute ​(de ex strength si dexterity+    * Are un XP care creste la diverse evenimente ​(de ex elimina un inamic, elimina un turn, rezolva un quest etc.
-    * Are un inventar ​cu obiecte +    * Poate interactiona ​cu obiecte ​(e un corp solid) 
-      accesibil pe tasta '​i'​ +    Poate ataca inimicii 
-      * obiectele se pot adauga, utiliza sau elimina din inventar +  Minim 1 obiect ​de tip pick-up care pot reface ​viata personajului, ​implementat cu collider
-      ​obiectele sunt de cel putin 2 tipuri:  +  * Inamici care se misca pe harta (de ex patruleaza intr-o zona, sau se misca spre baza inamica) 
-        ​consumabil: de ex la apasarea pe el se poate recupera din viata personajului +    * In momentul in care detecteaza player-ul (intr-o anumita raza de exemplu) se intorc ​cu fata catre player ​si il urmaresc, implementat custom (fara collider) 
-        - echipabil: de ex la o apasare se echipeaza - cresc anumite atribute ale playerului (strengthdexterity etc.) - la o alta apasare se dezechipeaza - scad atributele definite pe item +    * Au viata (health) 
-  * Inamici care se misca pe harta +    * Pot muri - cand mor ofera experienta personajului
-    * Inamicii trebuie sa aiba healthbar deasupa capului +
-  * Un minimap ​cu buline rosii pentru inamici si bulina albastra pentru ​player +
- +
  
 +Bonus:
 +  * Quest system (fara interfata grafica)
  
 ==== Documentatie video ===== ==== Documentatie video =====
Line 37: Line 34:
 ==== Sumar documentatie ===== ==== Sumar documentatie =====
  
 +todo: raycast pentru selectia obiectelor - point and click mers la ele
 +==== Interactiunea cu obiectele ====
  
-==== Elemente ​de GUI ====+Interactiunea cu obiectele in spatiul 3D poate fi extrem ​de complexa, intrucat exista foarte multe forme de interactiuni:​ inamici, obiecte de pickup, deschidere de usi, activare de manivele etc. Fiecare dintre aceste interactiuni are specificul ei, dar abstractizand,​ putem deduce ca fiecare interactiune se intampla intr-o anumita raza si cu un anumit punct de interactiune. Pentru a defini usor aceste lucruri, putem crea o clasa generica denumita ''​InteractionObject''​ cu o metoda abstracta (virtuala) ce defineste interactiunea in detaliu.
  
-Se pot folosi atat obiecte plasate in scena, cat si partea de Canvas, in ceea ce priveste interfata grafica.+<​code>​ 
 +public class InteractionObject : MonoBehaviour {
  
-=== FPS Gun ===+  public float radius ​1f; 
 +  public Transform interactionPoint;​ 
 +  Transform interactionObject;​ 
 +  bool done false;
  
-Astfelspre exemplu pentru un FPS single player, in general nu avem nevoie ​de caracter, ci doar de maini, sau eventual doar o arma animata. +  //metoda abstractaspeficica fiecarui tip de interactiuni 
-Pentru a controla usor acest aspect se poate importa un obiect texturat pentru arma, care se va atasa camerei 3D disponibile in scena si se va configura astfel incat sa arate cat mai natural.+  ​public virtual void Interaction () 
 +  { 
 +  
 +  }
  
-{{ :​pjv:​laboratoare:​gun.png?​direct&​750 |}}+  void Update () 
 +  ​{ 
 +      float distance = Vector3.Distance(interactionObject.position,​ interactionPoint.position);
  
-Pentru a crea un crosshair sau o tinta se poata adauga un disc sau o imagine ​cu transparenta in centrul canvas-ului. ​+      if (distance <= radius && !done) // avem interactiune ​cu obiectul, pot sa afisez informatii: de ex "Press E to use" 
 +      { 
 +        done = true; 
 +        Interaction();​ 
 +      } 
 +    } 
 +  }
  
-{{ :​pjv:​laboratoare:​crosshair.png?​direct&​750 |}}+} 
 +</​code>​
  
-Bineinteles,​ se poate crea un crosshair animat si folosind ​4 dreptunghiuri simple.+La fel de bine aceste interactiuni pot fi detectate ​folosind ​sistemul de colizuni din Unity 
 +> **Physics Events**
  
-{{ :​pjv:​laboratoare:​crosshair2.png?​direct&​750 |}}+<code c#> 
 +/* Both objects have to have a Collider and one object has to have a Rigidbody for these Events to work */ 
 +private void OnCollisionEnter(Collision hit) Debug.Log(gameObject.name + " just hit " + hit.gameObject.name);​ } 
 +private void OnCollisionStay(Collision hit) Debug.Log(gameObject.name + " is hitting " + hit.gameObject.name); ​} 
 +private void OnCollisionExit(Collision hit) { Debug.Log(gameObject.name + " stopped hitting " + hit.gameObject.name); ​}
  
-=== Healthbar ===+/* Trigger must be checked on one of the Colliders */ 
 +private void OnTriggerEnter(Collider hit) { Debug.Log(gameObject.name + " just hit " + hit.name); } 
 +private void OnTriggerStay(Collider hit) { Debug.Log(gameObject.name + " is hitting " + hit.name); } 
 +private void OnTriggerExit(Collider hit) { Debug.Log(gameObject.name + " stopped hitting " + hit.name); } 
 +  
 +/* For 2D Colliders just add 2D to the Method name and the Parameter Type */ 
 +private void OnCollisionEnter2D(Collision2D hit) { } 
 +private void OnCollisionStay2D(Collision2D hit) { } 
 +private void OnCollisionExit2D(Collision2D hit) { } 
 +private void OnTriggerEnter2D(Collider2D hit) { } 
 +private void OnTriggerStay2D(Collider2D hit) { } 
 +private void OnTriggerExit2D(Collider2D hit) { }
  
-Pentru reprezentarea healthbar-ului sau a altor elemente de tip bara, se poate folosi un element de UI (gameobject) de tip Slider, asa cum este reprezentat in imaginea de mai sus.+</​code>​
  
 +
 +Astfel, toate obiectele ce vor avea interactiuni,​ vor mosteni aceasta clasa.
 +Spre exemplu pentru un obiect de pickup putem avea urmatoarea secventa:
 <​code>​ <​code>​
-GetComponent.<​Slider>​().value myHealth;+public class PickupObject : InteractionObject { 
 + 
 +  public override void Interaction() 
 +  { 
 +    base.Interaction(); // se apeleaza metoda parinte, in caz ca avem ceva generic 
 +     
 +    //​mecanica 
 +    ​... 
 +    PlayerManager.instance.score +value; 
 +     
 +    //distrugem obiectul 
 +    Destroy(gameObject); 
 +     
 +  } 
 +}
 </​code>​ </​code>​
  
-De asemenea se mai pot folosi doua dreptunghiuri suprapuse: unul care reprezinta viata totala, ​si unul care reprezinta viata curentacel din urma fiind scalar relativ ​la valoare vietii personajului.+Pentru a controla ​mai bine zona de actiune (radius) ​si punctul de interes pentru un obiect de interactiune (InteractionObject),​ se poate defini o functie de editor, atunci cand obiectul este selectat. In exemplul de mai jos, la selectarea obiectului se va afisa o sfera wireframe de culoare alba.
  
-{{ :​pjv:​laboratoare:​2022:​healthbar.png?200 |}}+<​code>​ 
 +void OnDrawGizmosSelected () 
 +{ 
 +  Gizmos.color = Color.white;​ 
 +  Gizmos.DrawWireSphere(interactionPoint.position,​ radius); 
 +}  
 +</​code>​
  
-=== Minimap ===+{{ :​pjv:​laboratoare:​gizmosselected.png?​direct&​750 |}}
  
-Pentru crearea unui minimap se foloseste de regula o camera suplimentara,​ plasata top-down deasupra scenei.+=== Referinta globala la player ===
  
-{{ :​pjv:​laboratoare:minimap1.png?direct&350 |}}+O problema in programarea interactiunilor este detectarea player-ului,​ in sensul de referinta. Astfel, avem mai multe variante: 
 +  * putem cauta un obiect dupa tag/nume etc. 
 +  * intr-o variabila target putem referentia ​direct ​player-ul (dar asta inseamna ca la fiecare agent trebuie mapat) 
 +  * putem folosi un singleton in care se tine referentiaza playerul si poate fi accesat de oriunde
  
-Camera special creata va trebui sa afiseze scena intr-o textura. Pentru acest lucru vom folosi un asset de tip ''​Render texture'' ​(//Assets -> Create -> Render Texture//pe care o vom atasa ca Target in camera nou creata.+<​code>​ 
 +public class PlayerManager : MonoBehaviour { 
 +  
 +  public static PlayerManager instance; 
 +  public GameObject player; 
 +     
 +  void Awake() 
 +  { 
 +    instance = this; 
 +  }
  
-{{ :​pjv:​laboratoare:​minimap3.png?​direct&​350 |}}+} 
 +</​code>​
  
-Pasul urmator este sa afisam Minimap-ul. Pentru acest lucruin canvas vom crea o imagine si vom lega textura in care salvam imaginile ​de la camera la aceasta.+Folosind varianta simpla cu singletonputem lua pozitia player-ului ​de interes:
  
-{{ :​pjv:​laboratoare:​minimap4.png?​direct&​350 |}}+<​code>​ 
 +target = PlayerManager.instance.player.transform;​ 
 +</​code>​
  
-Rezultatul poate fi ibunatatit prin plasarea acestuia intr-un loc potrivit adaugarea unei masti sau a unei borduri si asa mai departe.+Astfel, putem efectua usor operatii care tin de player ​de exemplu putem orienta inamicii sau un npc cu fata catre player, in momentul ​unei interactiuni.
  
-{{ :​pjv:​laboratoare:​minimap7.png?​direct&​750 |}}+<​code>​
  
-=== Layers ​=== +        //Roteste cu 90 grade 
 +        void RotateN() { 
 +         ​Vector3 currentRotation ​transform.rotation;​ 
 +         ​Vector3 wantedRotation ​currentRotation * Quaternion.AngleAxis(-90,​ Vector3.up);​ 
 +         ​transform.rotation ​Quaternion.Slerp(currentRotation,​ wantedRotation,​ Time.deltaTime * rotationSpeed);​ 
 +        } 
 +        //Roteste inamicul cu fata catre player  
 + void FaceTarget () 
 +
 + Vector3 direction ​(target.position - transform.position).normalized;​ 
 + Quaternion lookRotation ​Quaternion.LookRotation(new Vector3(direction.x,​ 0, direction.z));​ 
 + transform.rotation ​Quaternion.Slerp(transform.rotation,​ lookRotation,​ Time.deltaTime * 5f); 
 +
 +</​code>​
  
-Pentru a defini sau actualiza Layere se paote merge la Edit -> Project Settings -> Tags and Layers. 
-Putem folosi Layere pentru a delimita diverse lucruri, de exemplu care obiecte se vad pe carema, pentru care se calculeaza coliziunea etc. 
  
-Putem simplifica geometria din minimap folosind Layere si primitive simplificate. 
  
-{{ https://​blog.theknightsofunity.com/​wp-content/​uploads/​2016/​04/​desktop.png |}}+=== Inamici ===
  
-De exemplu se poata adauga un obiect copil pentru ​caracter sau obietele care vreau sa apara pe harta. Setam Layerul ​de Minimap ​pentru ​obiect.+Astfel, ​pentru ​inamici putem defini un controller cu un radius ​de actiune, si un gizmos ​pentru ​vizualizare usoara a acestuia in editor.
  
-{{ https://​blog.theknightsofunity.com/​wp-content/​uploads/​2016/​04/​0f762c1f-bc89-4dd7-af2e-04b11abe7520.png |}}+<​code>​ 
 +public class EnemyController : MonoBehaviour ​{
  
-Adaugam si cativa inamici cu alta culoare+  public float radius = 2; 
 +   
 +  void OnDrawGizmosSelected() { 
 +    Gizmos.color = Color.red;​ 
 +    Gizmos.DrawWireSphere(transform.position,​ radius); 
 +  }
  
-{{ https://​blog.theknightsofunity.com/​wp-content/​uploads/​2016/​04/​c57d0c80-c9e3-42b4-9a0d-6efa9d7a68ca.png |}} 
  
-Urmatorul pas este sa configuram camera principala astfel incat sa nu afiseze aceste primitive.+
 +</​code>​
  
-{{ http://​blog.theknightsofunity.com/​wp-content/​uploads/​2016/​04/​5b252849-280c-4322-9115-7e20fb71de52.png |}}+{{ :pjv:​laboratoare:​enemy-radius.png?500 |}} 
 +Diferenta este ca acesti agenti vor raspunde automat la anumite evenimente:​ 
 +  * inamicii de obicei incep sa interactioneze atunci cand player-ul intra intr-o anumita raza de actiune 
 +  * NPC-urile interactoneaza la fel, bazate pe o raza de actiune sau efectiv interactiune directa (click)
  
-Apoi dezactivam toate celelalte layere si lasam doar Minimap pentru camera care face Render to Texture din setarea ​de Culling Mask+Pentru ca un inamic sa se miste spre player, atunci cand player-ul intra in raza de actiune putem folosi componenta de NavMeshAgent
  
-{{ https://​blog.theknightsofunity.com/​wp-content/​uploads/​2016/​04/​da459b5f-c6ad-4217-a2e8-938f418535b7.png |}}+<​code>​
  
-Rezultatul este simplificat ca geometrie ​si urmeaza pozitiile personajelor+void Update() { 
 +  //calculam distanta intre player ​si inamic 
 +  float distance = Vector3.Distance(target.position,​ transform.position);​
  
-{{ https://blog.theknightsofunity.com/wp-content/uploads/2016/04/3b117f6d-153f-4df6-a96c-51487c410209.png |}}+  if(distance <= radius) 
 +  ​{ 
 +    ​//misca agentul pana la player 
 +    agent.SetDestination(target.position);​ 
 +     
 +    ​//in momentul in care intra in raza de atac, ataca 
 +  
 +  } else { 
 +    ​// agentul se misca in treaba lui patruleaza etc. 
 +  ​} 
 +</​code>​ 
 +   
 +Mai departe, se poate folosi o alta distanta, pentru a determina raza de atac. Un inamic poate avea atac melee (de aproape) sau de la o anumita distanta. ​
  
-==== Crearea de animatii pentru obiecte de UI====+<​code>​
  
-Crearea animatiilor se poate face folosind utilitarul de animatie (Window > Animation), similar ca si pentru restul animatiilor. +public float attackRadius = 1;
-Similar, se poate folosi Animator Controller pentru a gestiona starile (spre exemplu tras cu arma).+
  
 +void Update() {
 +  //calculam distanta intre player si inamic
 +  float distance = Vector3.Distance(target.position,​ transform.position);​
  
-=== GUI ===+  if(distance <radius) 
 +  { 
 +    //misca agentul pana la player 
 +    agent.SetDestination(hit.point);​ 
 +     
 +    //in momentul in care intra in raza de atac, ataca 
 +    if(distance <attack)  
 +    { 
 +      //ataca 
 +    } 
 +  
 +  } 
 +   
 +</​code>​
  
-Pentru afisarea elementelor de interfata relative ​la spatiul scenei se poate folosi atat un canvascat si mai multe instante de canvas.+In multe cazuri avem actiuni mai complexe ​la mouse click dreapta: cum ar fi atacarea unui inamic, sau preluarea unui obiect etc, caz un care obiectele se pot misca in scena. Pentru astatrebuie implementata o functie astfel incat sa se poata actualiza pozitia targetului curent.
  
-{{ https://​www.stevestreeting.com/​images/​healthbarsdemo_thumb.png |}}+<​code>​
  
-Astfel, o varianta este sa atasati un canvas de tip WorldSpace la obiectele care au nevoie de elemente de interfata grafica (de exemplu health bar).+Transform target = null;
  
 +if(Input.GetMouseButtonDown(1)) //la apasarea click dreapta
 +{
 +  Ray mouseClickRay = camera.ScreenPointToRay(Input.mousePosition);​ //creaza o raza printr-un punct de pe ecran
 +  RaycastHit hit;
 +  ​
 +  if(Physics.Raycast(mouseClickRay,​ out hit))
 +  {
 +    target = hit.transform;​
 +    StartCoroutine(FollowTarget());​ //follow target pana la destinatie
 +  }
  
-{{ https://​www.stevestreeting.com/​images/​healthbarobj.png |}} +}
-{{ :​pjv:​laboratoare:​2020:​localcanvas.png?​direct&​750 |}} +
-{{ :​pjv:​laboratoare:​2020:​localcanvas-wspace.png |}}+
  
-O alta varianta este prin scriptarea unui singur element de canvas referentiat si transpunerea lui relativ la obiectul curent ​(util de exemplu in cazul dialogului sau a elementelor care nu se pot afisa de mai multe ori in acelasi timp) +if(Input.GetMouseButtonDown(0)) //la apasarea click stanga
-<​code>​ +
-var target : Transform;​ +
-  +
-function Update ​()+
 { {
-     var wantedPos ​Camera.main.WorldToViewportPoint ​(target.position); +  target=null; //​dezactiveaza target-ul 
-     transform.position = wantedPos;+
 + 
 +IEnumerator FollowTarget() 
 +
 +  while(target!=null) { 
 +    agent.SetDestination(target);  
 +    yield return null; 
 +  } 
 +  yield return 0;
 } }
 </​code>​ </​code>​
-=== Inventar === 
  
-Pentru a crea un sistem de inventar avem nevoie in primul rand de date atasate fiecarui obiect, cum ar fi nume, icon, atribute etc. Putem realiza acest lucru usor prin obiecte scriptabile (Scriptable Objects). Obiectele Scriptable sunt containere de date ce nu trebuie sa fie atasate la un GameObject intr-o scena. Ele pot fi salvate ca asset-uri in bibliteca proiectului ca mai apoi sa poata fi utilizate.+=== Quest System ===
  
-Obiectele scriptabile se definesc prin crearea unui script ​ce mosteneste clasa ScriptableObject.+In ceea ce priveste quest-urile,​ sunt foarte multe posibilitati de abordare, dar in general implica urmatoarele elemente: 
 +  * obiectivele quest-ului 
 +  * recompensele 
 +  * cine gestioneaza quest-ul
  
-Pentru a instantia un obiect scriptabil avem doua variante: +Astfel, o abordare sugerata este sa se abstractizeze o clasa de tip Quest, una de tip Goal (obiectivsi una de tip Recompensa, intrucat exista multe tipuri in care se pot instantia aceste lucruri.
-  * prin script: SciptableObject.CreateInstance<​MyScriptableObjectClass>​() +
-  * din mediu: CreateAssetMenu - adauga o intrare noua in meniul ​de create asset +
-<​code>​+
  
-[CreateAssetMenu(fileName = "New Item", menuName = "​Inventory/​Item",​ order = 1)] +Exemple de tipuri de Obiective
-public class Item ScriptableObject { +  * Kill - kill a bunch of stuff 
-    new public string name = "New MyScriptableObject";​ //​suprascrie atributul name +  * Gather - gather stuff for me 
-    ​public string objectName = "New MyScriptableObject";​ +  * Deliver - deliver my live letter 
-    ​public bool colorIsRandom = false; +  * etc.
-    ​public Color thisColor = Color.white; +
-    public Sprite icon; +
-    public Vector3[] spawnPoints;​ +
-+
-</​code>​+
  
-Intrucat exista atribute implicite pentru un obiect scriptabil (e.g. name), putem folosi variabile diferite (e.g. objectName) sau putem suprascrie definirea acestui atribut prin folosirea metodei ''​new''​ (new public string name).+Exemple de tipuri de Recompense:​ 
 +  * items 
 +  * experience 
 +  * gold 
 +  * skill 
 +  * etc.
  
-Mai departe, pentru un inventar vom avea nevoie ​de o lista de obiecte gestionabile. Pentru acest lucru vom face un script de gestiune pentru inventar (e.g InventoryManager) care gestioneaza adaugarea, eliminarea si interogarea inventarului. Pentru o accesare mai usoara si mai facila, ideal ar fi ca acest inventorymanager sa fie un Singleton. Pentru a avea un singleton trebuie sa ne asiguram ca avem o singura instanta creata pentru acest script atunci cand e accesat.+Exemplu ​de clasa generica ​de tip Quest
  
 <​code>​ <​code>​
-public class InventoryManager ​: MonoBehaviour {+public class Quest : MonoBehaviour {
  
-  ​// singleton +  public ​List<​Goal>​ Goals = new List<​Goal>​(); 
-  ​public ​static InventoryManager instance; +  public List<RewardRewards ​= new List<Reward>()
-  void Awake() +  public bool completed;
-    instance = this; +
-  } +
-   +
-  //lista de obiecte +
-  public List<Itemitems = new List<Item>();+
   ​   ​
-  ​//metode pentru gestionare +  public void CheckGoals() { 
-  ​public void Add(Item item) { +    ​completed = Goals.All(g => g.completed); //questul este gata cand toate obiectivele sunt complete
-    ​items.Add(item);+
   }   }
   ​   ​
-  public void Remove(Item item) { +  public void GiveReward() { 
-    ​items.Remove(item);​+    ​//in functie de tipul recompensei se adauga obiecte in inventar, sau se adauga experienta, skill points etc.
   }   }
 +  ​
 +  ​
  
 } }
 </​code>​ </​code>​
  
-Fiind definit ca un singleton, putem accesa acum foarte usor gestionarea inventarului:​ +Apoi, un exemplu de un quest concret.
-<​code>​ +
-Inventory.instance.Add(item);​ +
-Inventory.instance.Remove(item);​ +
-</​code>​+
  
-Inca un element util in gestionarea inventarului este definirea unei metode de a notifica atunci cand s-a produs o modificare in inventar. Pentru acest lucru putem folosi ''​Delegates''​. Un Delegate este un pointer la o metode. Aceasta ne permite sa tratam metoda ca o variabila și sa o folosim pentru un callback. Cand este apelata, acesta notifica toate metodele care fac referire la delegate. Astfel putem definit o variabila pentru evenimentul de schimbare. 
 <​code>​ <​code>​
-  public delegate void OnInventoryChanged();​ 
-  public OnInventoryChanged onInventoryChangedCallback;​ 
-  ​ 
- //​metode pentru gestionare 
-  public void Add(Item item) { 
-    ... 
-    onInventoryChangedCallback.Invoke();​ //notifica despre modificare 
-  } 
-  ​ 
-  public void Remove(Item item) { 
-    ... 
-    onInventoryChangedCallback.Invoke();​ //notifica despre ​ modificare 
-  } 
-</​code>​ 
-Urmatorul pas este crearea unei interfete grafice si legarea interfetei de functionalitatea InventoryManger-ului. 
-Pentru interfata grafica, putem folosi, ca si pana acum, canvas-ul oferit de Unity, structurat astfel incat sa avem un panou general pentru inventar, si mai multe slot-uri pentru obiectele din acesta. Pe fiecare slot putem defini urmatoarele aspecte: 
-  * un gameobject 
-  * un buton de accesare : pentru folosirea obiectului, echipare etc. 
-  * un buton de eliminare : pentru eliminarea din inventar 
-  * o imagine: icon pentru obiectul in inventar 
-  * script de gestionare: pentru fiecare slot putem avea un script de gestionare care va actualiza interfata slotului respectiv (nume, icon etc). 
  
-{{ :pjv:​laboratoare:​2020:​inventory.png |}} +public class RandomSlayThingsQuest ​Quest {
-{{ :​pjv:​laboratoare:​2020:​inventory-grid_layout.png |}}+
  
-Pentru sloturile de inventar este indicat sa folositi un prefab sau un template. +  void Start() 
- +  { 
-Interfata grafica a inventarului are nevoie si de un script de gestionareAstfel vom aveam un script care asculta ​(subscribe) evenimentul definit ​(''​delegate''​la actualizarea inventarului,​ si actualizeaza fiecare slot din interfata grafica: +    QuestName = "​Random Slayer";​ 
- +    Description = "Kill some time"; 
-<​code>​ +     
-void Start() { +    Goals.Add(new KillGoal...)); 
-  ​inventory = Inventory.instance+    ​Goals.Add(new KillGoal( ...)); 
-  ​inventory.onInventoryChangedCallback += UpdateUI//definesc o metoda ca se apeleaza la aparitia unui eveniment delegat+     
 +    Rewards.Add(new ItemReward( ...))
 +    ​Rewards.Add(new ExperienceReward( ...))   
   ​   ​
-  ​slots = GetComponentsInChildren<​InventorySlot>​();​ //fiecare slot din inventar +  }
-+
- +
-void UpdateUI() { +
-   +
-  //​actualizare fiecare slot +
-  for(i=0; i < slots.Length;​ i++) +
-  { +
-    if(i<​iventory.items.Count) slots[i].AddItem(..) +
-    else slots[i].RemoveItem(..) +
-  ​+
  
 } }
-</​code>​ 
  
-Bineinteles,​ in inventar se pot pune diferse restrictii si interactiuni (cum ar fi dimensiunea maxima a inventarului) 
-<​code>​ 
-bool AddItem() { 
- 
-if(items.Count >= space) 
-//no more room 
-return false 
- 
-else return true; 
-} 
- 
-if(Inventory.instance.Add(item)) Destroy(gameObject);​ 
 </​code>​ </​code>​
  
-Similar se poate face si gestiunea altor interfet: de quest pentru player, de echipament / arma, skilltree etc. +Si un exemplu ​de Obiectiv
- +
-=== NPC === +
- +
-In ceea ce priveste NPC-urile, acestea de obicei interactioneaza cu player-ul prin dialog: oferirea de informatii, gossip, quest-uri etc. +
- +
-Sistemele de dialog si de quest-uri pot fi foarte complexe, iar posibilitatile nelimitate. In continuare voi prezenta cateva lucruri de baza prezente in aceste sisteme. +
- +
-Din punct de vedere programatic,​ NPC-urile sunt tot un tip de obiect cu care se poate interactiona,​ astfe ca se poate folosi aceasi paradigma din laboratorul precedent, de InteractionObject. +
- +
-Primul punct important in interactiunea cu NPC-urile este sistemul de dialog. +
- +
-=== Dialog System === +
- +
-Pentru crearea unui sistem de dialog, se folosesc elemente de UI Canvas: Panel, Button, Image etc. +
- +
-{{ :​pjv:​laboratoare:​dialog-panel.png?​500 |}}  +
- +
-Mai departe, putem face un DialogManager,​ tot sub format singleton, pentru a putea referentia elementele de UI mai usor, o singura data. In acest manager putem configura diverse linii de dialog, numele NPC-ului care sa apara in caseta de dialog etc. De asemenea, va trebui sa tinem minte si in ce moment, sau la care linie de dialog ne aflam.+
  
 <​code>​ <​code>​
  
-public class DialogManager ​MonoBehaviour ​{+public class KillGoal ​Goal {
   ​   ​
-  ​public static DialogManager instance+  ​bool completed
-  ​public GameObject panel; +  ​int currentAmmount;
-  public string NPCName; +
-  public List<​string>​ sentences = new List<​string>​();+
   ​   ​
-  ​Button continueButton;​ +  public ​KillGoal(int enemyIdint ammount) {
-  Text dialogeTextContainer,​ NPCNameContainer;​  +
-   +
-  int lineIndex;​ +
-     +
-  void Awake() +
-  { +
-    //get child components +
-    dialogeTextContainer = ...  +
-    NPCNameContainer = ... +
-    continueButton = ... +
-    continueButton.onClick.AddListener(delegate { ContinueDialog();​ }); +
-     +
-    instance = this; +
-  } +
-   +
-  ​public ​void AddNewDialogue(string[] linesstring NPCName) +
-  ​{ +
-    //​initializeaza dialogul curent din NPC +
-    lineIndex=0;​+
     ...     ...
   }   }
   ​   ​
-  public void showDialog() +  public ​override ​void Init() { 
-  ​+    //listen to enemy death event 
-    ​dialogeTextContainer.text = sentences[lineIndex]; ​//afiseaza linia de dialog curenta +    ​EnemyManager.onDie +EnemyDied;
-    ​NPCNameContainer.textNPCName; +
-    panel.SetActive(true);​ +
-    ​+
   }   }
   ​   ​
-  ​public ​void ContinueDialog() +  void EnemyDied(enemy) { 
-  ​+    ​this.currentAmmount++; 
-    ​lineIndex++; +    ​if(this.currentAmmount >= ammount
-    ​showDialog();+      this.completed = true; 
 +    }
   }   }
 +  ​
   ​   ​
 } }
Line 336: Line 372:
 </​code>​ </​code>​
  
-Apoiin clasa NPC-ului, putem instantia un dialog personalizat pentru ​NPC-ul respectiv:+In ceea ce priveste cine gestioneaza questulacesta este de obicei un NPC, deci putem extinde clasa NPC cu cateva lucuri specifice:
  
 <​code>​ <​code>​
  
-public class NPC InteractionObject ​{+public class QuestNPC ​NPC { 
 + 
  
-  public ​string[] sentences//se pot configura liniile de dialog in editor +  public ​bool assigned
-  public ​string name;+  public ​Quest quest;
   ​   ​
   public override void Interaction()   public override void Interaction()
Line 349: Line 386:
     base.Interaction();​ // se apeleaza metoda parinte     base.Interaction();​ // se apeleaza metoda parinte
  
-    ​DialogManager.Instance.AddNewDialog(sentences, name);+    ​if(!assigned) { 
 +      //dialog 
 +      //assign 
 +    } 
 +     
 +    void CheckQuest() { 
 +      if(quest.completed) { 
 +        quest.GiveReward(); 
 +      } 
 +    }
   }   }
 +}
  
 </​code>​ </​code>​
  
-{{ :​pjv:​laboratoare:​dialog.png?​500 |}}+
  
pjv/laboratoare/2022/04.1668081603.txt.gz · Last modified: 2022/11/10 14:00 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