Differences

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

Link to this comparison view

pjv:laboratoare:07 [2018/10/11 01:49]
127.0.0.1 external edit
pjv:laboratoare:07 [2019/10/02 12:57] (current)
alexandru.gradinaru
Line 1: Line 1:
-===== Laboratorul 07. =====+===== Mecanici avansate in 3D =====
  
 +==== Agenti ====
  
 +Pentru a programa inamici sau agenti NPC (Non-Playable Character) se poate folosi aceeasi functionalitate de navigare automata (NavMesh) si componenta de tip NavMeshAgent,​ pentru navigatie, similar cu sectiunea de Navigare automata din laboratorul precedent.
 +
 +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)
 +
 +=== Inamici ===
 +
 +Astfel, pentru inamici putem defini un controller cu un radius de actiune, si un gizmos pentru vizualizare usoara a acestuia in editor.
 +
 +<​code>​
 +public class EnemyController : MonoBehaviour {
 +
 +  public float radius = 2;
 +  ​
 +  void OnDrawGizmosSelected() {
 +    Gizmos.color = Color.red;
 +    Gizmos.DrawWireSphere(transform.position,​ radius);
 +  }
 +
 +
 +}
 +</​code>​
 +
 +{{ :​pjv:​laboratoare:​enemy-radius.png?​500 |}}
 +
 +Pentru ca un inamic sa se miste spre player, atunci cand player-ul intra in raza de actiune putem folosi componenta de NavMeshAgent
 +
 +<​code>​
 +
 +void Update() {
 +  //calculam distanta intre player si inamic
 +  float distance = Vector3.Distance(target.position,​ transform.position);​
 +
 +  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. ​
 +
 +<​code>​
 +
 +public float attackRadius = 1;
 +
 +void Update() {
 +  //calculam distanta intre player si inamic
 +  float distance = Vector3.Distance(target.position,​ transform.position);​
 +
 +  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>​
 +
 +O problema in activitatea agentilor este detectarea player-ului,​ in sensul de referinta. Astfel, avem mai multe variante:
 +  * putem cauta un obiect dupa tag
 +  * 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
 +
 +<​code>​
 +public class PlayerManager : MonoBehaviour {
 + 
 +  public static PlayerManager instance;
 +  public GameObject player;
 +    ​
 +  void Awake()
 +  {
 +    instance = this;
 +  }
 +
 +}
 +</​code>​
 +
 +Folosind varianta simpla cu singleton, putem lua pozitia player-ului de inters, similar cu laboratorul precedent:
 +
 +<​code>​
 +target = PlayerManager.instance.player.transform;​
 +</​code>​
 +
 +<​code>​
 +
 +        //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>​
 +
 +=== 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>​
 +
 +public class DialogManager : MonoBehaviour {
 +  ​
 +  public static DialogManager instance;
 +  public GameObject panel;
 +  public string NPCName;
 +  public List<​string>​ sentences = new List<​string>​();​
 +  ​
 +  Button continueButton;​
 +  Text dialogeTextContainer,​ NPCNameContainer; ​
 +  ​
 +  int lineIndex;
 +    ​
 +  void Awake()
 +  {
 +    //get child components
 +    dialogeTextContainer = ... 
 +    NPCNameContainer = ...
 +    continueButton = ...
 +    continueButton.onClick.AddListener(delegate { ContinueDialog();​ });
 +    ​
 +    instance = this;
 +  }
 +  ​
 +  public void AddNewDialogue(string[] lines, string NPCName)
 +  {
 +    //​initializeaza dialogul curent din NPC
 +    lineIndex=0;​
 +    ...
 +  }
 +  ​
 +  public void showDialog()
 +  {
 +    dialogeTextContainer.text = sentences[lineIndex];​ //afiseaza linia de dialog curenta
 +    NPCNameContainer.text= NPCName;
 +    panel.SetActive(true);​
 +    ​
 +  }
 +  ​
 +  public void ContinueDialog()
 +  {
 +    lineIndex++;​
 +    showDialog();​
 +  }
 +  ​
 +}
 +
 +</​code>​
 +
 +Apoi, in clasa NPC-ului, putem instantia un dialog personalizat pentru NPC-ul respectiv:
 +
 +<​code>​
 +
 +public class NPC : InteractionObject {
 +
 +  public string[] sentences; //se pot configura liniile de dialog in editor
 +  public string name;
 +  ​
 +  public override void Interaction()
 +  {
 +    base.Interaction();​ // se apeleaza metoda parinte
 +
 +    DialogManager.Instance.AddNewDialog(sentences,​ name);
 +  }
 +
 +</​code>​
 +
 +{{ :​pjv:​laboratoare:​dialog.png?​500 |}}
 +
 +=== Quest System ===
 +
 +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
 +
 +Astfel, o abordare sugerata este sa se abstractizeze o clasa de tip Quest, una de tip Goal (obiectiv) si una de tip Recompensa, intrucat exista multe tipuri in care se pot instantia aceste lucruri.
 +
 +Exemple de tipuri de Obiective:
 +  * Kill - kill a bunch of stuff
 +  * Gather - gather stuff for me
 +  * Deliver - deliver my live letter
 +  * etc.
 +
 +Exemple de tipuri de Recompense:
 +  * items
 +  * experience
 +  * gold
 +  * skill
 +  * etc.
 +
 +Exemplu de clasa generica de tip Quest
 +
 +<​code>​
 +public class Quest : MonoBehaviour {
 +
 +  public List<​Goal>​ Goals = new List<​Goal>​();​
 +  public List<​Reward>​ Rewards = new List<​Reward>​();​
 +  public bool completed;
 +  ​
 +  public void CheckGoals() {
 +    completed = Goals.All(g => g.completed);​ //questul este gata cand toate obiectivele sunt complete
 +  }
 +  ​
 +  public void GiveReward() {
 +    //in functie de tipul recompensei se adauga obiecte in inventar, sau se adauga experienta, skill points etc.
 +  }
 +  ​
 +  ​
 +
 +}
 +</​code>​
 +
 +Apoi, un exemplu de un quest concret.
 +
 +<​code>​
 +
 +public class RandomSlayThingsQuest : Quest {
 +
 +  void Start()
 +  {
 +    QuestName = "​Random Slayer";​
 +    Description = "Kill some time";
 +    ​
 +    Goals.Add(new KillGoal( ...));
 +    Goals.Add(new KillGoal( ...));
 +    ​
 +    Rewards.Add(new ItemReward( ...));
 +    Rewards.Add(new ExperienceReward( ...)); ​   ​
 +  ​
 +  }
 +
 +}
 +
 +</​code>​
 +
 +Si un exemplu de Obiectiv
 +
 +<​code>​
 +
 +public class KillGoal : Goal {
 +  ​
 +  bool completed;
 +  int currentAmmount;​
 +  ​
 +  public KillGoal(int enemyId, int ammount) {
 +    ...
 +  }
 +  ​
 +  public override void Init() {
 +    //listen to enemy death event
 +    EnemyManager.onDie += EnemyDied;
 +  }
 +  ​
 +  void EnemyDied(enemy) {
 +    this.currentAmmount++;​
 +    if(this.currentAmmount >= ammount) {
 +      this.completed = true;
 +    }
 +  }
 +  ​
 +  ​
 +}
 +
 +</​code>​
 +
 +In ceea ce priveste cine gestioneaza questul, acesta este de obicei un NPC, deci putem extinde clasa NPC cu cateva lucuri specifice:
 +
 +<​code>​
 +
 +public class QuestNPC : NPC {
 + 
 +
 +  public bool assigned;
 +  public Quest quest;
 +  ​
 +  public override void Interaction()
 +  {
 +    base.Interaction();​ // se apeleaza metoda parinte
 +
 +    if(!assigned) {
 +      //dialog
 +      //assign
 +    }
 +    ​
 +    void CheckQuest() {
 +      if(quest.completed) {
 +        quest.GiveReward();​
 +      }
 +    }
 +  }
 +}
 +
 +</​code>​
 +
 +Gestiunea interfetei de quest pentru player, se poate face similar cu cea de inventar, prezentata in laboratorul precedent.
 +
 +
 +==== Cerinte ====
 +
 +Realizarea unui joc 3D RPG
 +
 +  - Adaugati unul sau mai multi NPC care:
 +      - stiu sa converseze (text) prin raspunsuri la intrebari standard
 +      - pot oferi un quest (quest-urile au obiective si recompense)
 +  - Adaugati unul sau mai multi inamici in scena scriptati astfel incat:
 +      - sa fie animati
 +      - sa se plimbe intr-o proximitate
 +      - la apropierea jucatorului,​ sa il atace 
 +
 +<​hidden>​
 +  ​
 +  ​
 +
 +NPC
 +Agenti de la care se pot prelua/​rezolva quest-uri
 +Sunt animati
 +Pot purta o conversatie cu mai multe fire de dialog si mai multe variante de raspuns
 +</​hidden>​
 +
 +<​hidden>​
 +      - poate schimba arma de atac echipata
 +      - poate consulta quest-ul curent
 +      - poate ataca
 +      - are experienta si level-uri
 +      - in functie de level poata adauga puncte intr-un skilltree
 +      - poate folosi skill-uri/​abilitati speciale
 +  ​
 + 
 +      - 
 +</​hidden>​
 +
 +<​hidden>​
 +(vezi demo UI de la 3dpub)
 +  - coliziuni, proiectile
 +  - interfata grafica pt un fps
 +  - Managing weapons, Navmeshes, navmesh Agents, AI Auto-Attack
 +  - animatie caractere atac, arma, coliziuni atac
 +
 +7: 3d RPG inventory system, quest system, skill system, pathfinding,​Grid Snapping, Managing weapons, Navmeshes, navmesh Agents, AI Auto-Attack
 +
 +QuestLog
 +
 +playerul
 +are abilitati speciale care consuma mana/puncte de abilitate
 +poate prelua unul sau mai multe quest-uri
 +poate cara obiecte (iau un cub dintr-o parte si il pun in alta, ca sa sar pe el)
 +poate interactiona cu NPC (botsi) pentru preluare de quest-uri
 +are un scor in functie de numarul de quest-uri rezolvate
 +este animat (atac, miscare)
 +
 +
 +
 +</​hidden>​
pjv/laboratoare/07.1539211795.txt.gz ยท Last modified: 2018/11/14 22:15 (external edit)
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