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.

public class EnemyController : MonoBehaviour {

  public float radius = 2;
  
  void OnDrawGizmosSelected() {
    Gizmos.color = Color.red;
    Gizmos.DrawWireSphere(transform.position, radius);
  }


}

Pentru ca un inamic sa se miste spre player, atunci cand player-ul intra in raza de actiune putem folosi componenta de NavMeshAgent

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.
  }

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.

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
    }
 
  }
  

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
public class PlayerManager : MonoBehaviour {
 
  public static PlayerManager instance;
  public GameObject player;
    
  void Awake()
  {
    instance = this;
  }

}

Folosind varianta simpla cu singleton, putem lua pozitia player-ului de inters, similar cu laboratorul precedent:

target = PlayerManager.instance.player.transform;
        //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);
	}

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.

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.

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();
  }
  
}

Apoi, in clasa NPC-ului, putem instantia un dialog personalizat pentru NPC-ul respectiv:

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);
  }

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

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.
  }
  
  

}

Apoi, un exemplu de un quest concret.

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( ...));    
  
  }

}

Si un exemplu de Obiectiv

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;
    }
  }
  
  
}

In ceea ce priveste cine gestioneaza questul, acesta este de obicei un NPC, deci putem extinde clasa NPC cu cateva lucuri specifice:

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();
      }
    }
  }
}

Gestiunea interfetei de quest pentru player, se poate face similar cu cea de inventar, prezentata in laboratorul precedent.

Cerinte

Realizarea unui joc 3D RPG

  1. Adaugati unul sau mai multi NPC care:
    1. stiu sa converseze (text) prin raspunsuri la intrebari standard
    2. pot oferi un quest (quest-urile au obiective si recompense)
  2. Adaugati unul sau mai multi inamici in scena scriptati astfel incat:
    1. sa fie animati
    2. sa se plimbe intr-o proximitate
    3. la apropierea jucatorului, sa il atace
pjv/laboratoare/07.txt ยท Last modified: 2019/10/02 12:57 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