Sisteme UI avansate

Cerinte

Realizati o scena care sa contina:

  • Personaj
    • Controlabil
    • Are un healthbar afisat
    • Are cel putin 2 atribute (de ex strength si dexterity)
    • Are un inventar cu obiecte
      • accesibil pe tasta 'i'
      • obiectele se pot adauga, utiliza sau elimina din inventar
      • obiectele sunt de cel putin 2 tipuri:
        1. consumabil: de ex la apasarea pe el se poate recupera din viata personajului; dispare dupa consum
        2. echipabil: de ex la o apasare se echipeaza - cresc anumite atribute ale playerului (strength, dexterity etc.) - la o alta apasare se dezechipeaza - scad atributele definite pe item

Documentatie video

Inregistrare pe teams

Documentatie extinsa text

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.

ScriptableObjects

ScriptableObject este o clasă Unity serializabilă care vă permite să stocați cantități mari de date partajate independent de instanțe de script. Utilizarea ScriptableObjects facilitează gestionarea modificărilor și depanarea. Puteți construi un nivel de comunicare flexibilă între diferitele sisteme din jocul dvs., astfel încât să fie mai ușor de gestionat să le schimbați și să le adaptați pe parcursul producției, precum și reutilizarea componentelor.

Obiectele scriptabile se definesc prin crearea unui script ce mosteneste clasa ScriptableObject.

Pentru a instantia un obiect scriptabil avem doua variante:

  • prin script: SciptableObject.CreateInstance<MyScriptableObjectClass>()
  • din mediu: CreateAssetMenu - adauga o intrare noua in meniul de create asset
[CreateAssetMenu(fileName = "New Item", menuName = "Inventory/Item", order = 1)]
public class Item : ScriptableObject {
    new public string name = "New MyScriptableObject"; //suprascrie atributul name
    public string objectName = "New MyScriptableObject";
    public bool colorIsRandom = false;
    public Color thisColor = Color.white;
    public Sprite icon;
    public Vector3[] spawnPoints;
    
    // Called when the item is pressed in the inventory
	public virtual void Use ()
	{
		// Use the item
		// Something may happen
	}

	// Call this method to remove the item from inventory
	public void RemoveFromInventory ()
	{
		Inventory.instance.Remove(this);
	}
}

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

Mai mult, putem crea obiecte cu actiuni specifice: de ex obiect consumabil sau echipabil.

using UnityEngine;
 
/* An Item that can be consumed. So far that just means regaining health */
 
[CreateAssetMenu(fileName = "New Item", menuName = "Inventory/Consumable")]
public class Consumable : Item {
 
	public int healthGain;		// How much health?
 
	// This is called when pressed in the inventory
	public override void Use()
	{
		// Heal the player
		PlayerStats playerStats = Player.instance.playerStats;
		playerStats.Heal(healthGain);
 
		Debug.Log(name + " consumed!");
 
		RemoveFromInventory();	// Remove the item after use
	}
 
}

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.

public class InventoryManager : MonoBehaviour {

  // singleton
  public static InventoryManager instance;
  void Awake() {
    instance = this;
  }
  
  //lista de obiecte
  public List<Item> items = new List<Item>();
  
  //metode pentru gestionare
  public void Add(Item item) {
    items.Add(item);
  }
  
  public void Remove(Item item) {
    items.Remove(item);
  }

}

Fiind definit ca un singleton, putem accesa acum foarte usor gestionarea inventarului:

Inventory.instance.Add(item);
Inventory.instance.Remove(item);

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.

  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
  }

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

Pentru sloturile de inventar este indicat sa folositi un prefab sau un template.

public class InventorySlot : MonoBehaviour {
 
	public Image icon;
	public Button removeButton;
 
	Item item;	// Current item in the slot
 
	// Add item to the slot
	public void AddItem (Item newItem)
	{
		item = newItem;
 
		icon.sprite = item.icon;
		icon.enabled = true;
		removeButton.interactable = true;
	}
 
	// Clear the slot
	public void ClearSlot ()
	{
		item = null;
 
		icon.sprite = null;
		icon.enabled = false;
		removeButton.interactable = false;
	}
 
	// If the remove button is pressed, this function will be called.
	public void RemoveItemFromInventory ()
	{
		Inventory.instance.Remove(item);
	}
 
	// Use the item
	public void UseItem ()
	{
		if (item != null)
		{
			item.Use();
		}
	}
 
}

Interfata grafica a inventarului are nevoie si de un script de gestionare. Astfel vom aveam un script care asculta (subscribe) evenimentul definit (delegate) la actualizarea inventarului, si actualizeaza fiecare slot din interfata grafica:

void Start() {
  inventory = Inventory.instance;
  inventory.onInventoryChangedCallback += UpdateUI; //definesc o metoda ca se apeleaza la aparitia unui eveniment delegat
  
  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(..)
  } 

}

Bineinteles, in inventar se pot pune diferse restrictii si interactiuni (cum ar fi dimensiunea maxima a inventarului)

bool AddItem() {

if(items.Count >= space)
//no more room
return false

else return true;
}

if(Inventory.instance.Add(item)) Destroy(gameObject);

Similar se poate face si gestiunea altor interfet: de quest pentru player, de echipament / arma, skilltree etc.

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

pjv/laboratoare/2023/12.txt · Last modified: 2023/12/18 16:35 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