This shows you the differences between two versions of the page.
pjv:laboratoare:2022:04 [2022/11/10 14:01] alexandru.gradinaru |
pjv:laboratoare:2022:04 [2022/11/23 13:12] (current) alexandru.gradinaru |
||
---|---|---|---|
Line 1: | Line 1: | ||
===== Programarea obiectelor ===== | ===== Programarea obiectelor ===== | ||
- | |||
- | todo: raycast pentru selectia obiectelor - point and click mers la ele | ||
- | |||
==== Cerinte ===== | ==== Cerinte ===== | ||
- | <note important>Pentru simplitate puteti porni de la scena de MOBA creata in Lab3, dar nu este obligatoriu</note> | + | <note important>Pentru simplitate, puteti 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 (strength, dexterity 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 ==== | ==== Interactiunea cu obiectele ==== | ||
Line 70: | Line 67: | ||
} | } | ||
</code> | </code> | ||
+ | |||
+ | La fel de bine aceste interactiuni pot fi detectate folosind sistemul de colizuni din Unity | ||
+ | > **Physics Events** | ||
+ | |||
+ | <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); } | ||
+ | |||
+ | /* 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) { } | ||
+ | |||
+ | </code> | ||
+ | |||
Astfel, toate obiectele ce vor avea interactiuni, vor mosteni aceasta clasa. | Astfel, toate obiectele ce vor avea interactiuni, vor mosteni aceasta clasa. | ||
Line 82: | Line 104: | ||
//mecanica | //mecanica | ||
... | ... | ||
+ | PlayerManager.instance.score += value; | ||
| | ||
//distrugem obiectul | //distrugem obiectul | ||
Line 102: | Line 125: | ||
{{ :pjv:laboratoare:gizmosselected.png?direct&750 |}} | {{ :pjv:laboratoare:gizmosselected.png?direct&750 |}} | ||
+ | === Referinta globala la player === | ||
+ | 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 | ||
+ | |||
+ | <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 interes: | ||
+ | |||
+ | <code> | ||
+ | target = PlayerManager.instance.player.transform; | ||
+ | </code> | ||
+ | |||
+ | 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. | ||
+ | |||
+ | <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> | ||
+ | |||
+ | |||
+ | |||
+ | === 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 |}} | ||
+ | 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) | ||
+ | |||
+ | 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> | ||
+ | |||
+ | 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 asta, trebuie implementata o functie astfel incat sa se poata actualiza pozitia targetului curent. | ||
+ | |||
+ | <code> | ||
+ | |||
+ | 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 | ||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | if(Input.GetMouseButtonDown(0)) //la apasarea click stanga | ||
+ | { | ||
+ | target=null; //dezactiveaza target-ul | ||
+ | } | ||
+ | |||
+ | IEnumerator FollowTarget() | ||
+ | { | ||
+ | while(target!=null) { | ||
+ | agent.SetDestination(target); | ||
+ | yield return null; | ||
+ | } | ||
+ | yield return 0; | ||
+ | } | ||
+ | </code> | ||
=== Quest System === | === Quest System === | ||
Line 229: | Line 400: | ||
</code> | </code> | ||
- | |||
- | |||
- | === 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 |}} | ||
- | 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) | ||
- | |||
- | |||