Programarea personajelor

Cerinte

Puteti porni de la scenele create in Lab 2

Realizati o scena de joc similara cu un MOBA (ex Dota 2, LoL, Smite etc.)

  • Mediul:
    • un teren cu 3 lane-uri
    • turnuri pe fiecare lane (vedeti punctele rosii/albastre de pe harta) - ar trebui sa fie diferentiate pe echipe (de ex un steag de culoare deferita, sau o culoare de material deferita etc.)
    • cate o baza pentru fiecare player (de ex un steag de culoare deferita, sau o culoare de material deferita etc.)

  • Playerul are 2 moduri de control. Schimbul intre cele doua moduri se poate face folosind o tasta, sau la anumite evenimente definite de voi
    1. Navigatie: o camera 3rd person isometric sau top-down (similar moba - dota, lol etc.) care sa permita miscarea personajului pe harta in mod automat catre pozitia selectata cu mouse-ul
    2. Combat mode: o camera 1st person
  • Inamici: acestia sunt generati din timp in timp de la bazele fiecarei echipe si se misca automat pe unul din lane-uri catre baza celuilalt player

Momentan nu sunt necesare interactiuni. Acest laborator vizeaza doar miscarea/controlul personajelor si al inimicilor. Vom aborda in laboratoare viitoare intaractiuni dintre obiecte/inamici etc.

Puteti folosi mecanica de tras implementata in laboratorul 2.

Documentatie video


Documentatie extinsa text


Sumar documentatie

Controlul personajelor in 3D

Pentru miscarea si controlul caracterelor in 3D sunt posibile mai multe abordari.

CharacterController

CharacterController este o componenenta ce poate fi adaugata pentru a putea controla personajul. Aceasta componenta nu foloseste elemente de fizica in nici nu fel, dar vine atasat cu un collider de baza Capsule Collider care nu permite intrarea in alte collidere. Acest lucru simplifica foarte mult scriptarea caracterului.

Character Controller pune la dispozitie doua metode pentru miscarea caracterului:

  • SimpleMove(Vector3 speed) care va misca caracterul tinand cont de gravitatia definita, dar in care axa Y este ignorata
// Move forward / backward
        Vector3 forward = transform.TransformDirection(Vector3.forward);
        float curSpeed = speed * Input.GetAxis("Vertical");
        controller.SimpleMove(forward * curSpeed);
  • Move(Vector3 motion) care va muta caracterul in functie de paracmetrul motion, care reprezinta miscarea absoluta, astfel ca acceleratii de genul gravitatiei vor trebui implementate manual.
   moveDirection = new Vector3(Input.GetAxis("Horizontal"), 0.0f, Input.GetAxis("Vertical"));
            moveDirection = transform.TransformDirection(moveDirection);
            moveDirection = moveDirection * speed;
 
            if (Input.GetButton("Jump"))
            {
                moveDirection.y = jumpSpeed;
            }
        }
 
        // Apply gravity
        moveDirection.y = moveDirection.y - (gravity * Time.deltaTime);
 
        // Move the controller
        controller.Move(moveDirection * Time.deltaTime);

In cazul functiilor puse la dispozitie de Character Controller, miscarile sunt constranse de coliziuni in mod automat si este destul de permisiva (spre exemplu va putea urca autmat rampe sau scari).

Rigidbody

Componenta de Rigidbody practic determina ca obiectul pe care e plasata sa se comporte ca un corp fizic, care interactioneaza cu mediul si asupra caruia se pot aplica forte fizice. Implicit, componenta Rigidbody este afectata de gravitatia definita ca prametru.

Exista 4 moduri in care se poate aplica o forta:

  • Force: adauga o forta continua si dependenta de masa
  • Acceleration: adauga o acceleratia continua independenta de masa
  • Impulse: adauga o forta instanta si dependenta de masa
  • VelocityChange: adauga o velocitate instanta independenta de masa

Pentru a adauga o forta exista de asemenea mai multe metode, dar cea standard este AddForce(Vector3 force, ForceMode mode = ForceMode.Force).

rb.AddForce(10.0f * Vector3.up);

if (Input.GetButtonDown("Jump") && _isGrounded)
{
  rb.AddForce(Vector3.up * Mathf.Sqrt(JumpHeight * -2f * Physics.gravity.y), ForceMode.VelocityChange);
}

Pentru a misca un caracter folosind inputul, de regula se foloseste functia MovePosition. Aceasta functie va incerca sa mute caracterul la pozitia respectiva, tinand cont de coliziuni. De asemenea, nu va influienta cu nimic procesarile fizice in desfasurare aplicate asupra RigidBody-ului, comenzile de miscare ale caracterului fiind separate oarecum de mecanica de simulare a interactiunilor fizice.

Vector3 dirVector = new Vector3 (Input.GetAxis ("Horizontal"), 0, Input.GetAxis ("Vertical")).normalized;
GetComponent <Rigidbody> ().MovePosition (transform.position + dirVector * Time.deltaTime);

Componenta Rigidbody este una foarte precisa din punct de vedere al coliziunilor, deci ofera mai mult control in acest sens, dar necesita si mai multa programare chiar si pentru lucruri simple, cum ar fi urcatul scarilor sau pe o rampa.

O mentiune importanta este ca Unity foloseste o separatie a procesarii functiilor de update in ceea ce priveste frame-ul. Astfel update-urile de procesare a fizicii sunt separate de update-urile de procesare a frame-ului:

  • exista o functie de FixedUpdate, care se apeleaza la un framerate fix si care se apeleaza inainte de fiecare `update de fizica`. In aceasta metoda este recomandat sa se aplice toate fortele necesare simularii fizice pentru a nu afecta simluarea facandu-o dependenta de framerate
  • functia clasica de Update, care duce la actualizarea si afisarea frame-ului curent: in aceasta functie se poate prelua inputul pentru a fi consistent

O problema generala care apare la preluarea inputului este normalizarea.

 Vector2 dirVector = new Vector2 (Input.GetAxis ("Horizontal"), Input.GetAxis ("Vertical"));

versus

 Vector2 dirVector = new Vector2 (Input.GetAxis ("Horizontal"), Input.GetAxis ("Vertical")).normalized;

Daca inputul nu este normalizat, caracterul se va misca cu o viteza mai mare atunci cand se misca in fi

Rotatia camerei

Pentru rotatia camerei exista mai multe variante, dar una dintre cele mai indiacate este sa retineti intotdeauna rotatia actuala a camerei, pentru a evita probleme legate de rotatii multiple compuse.

O varianta simpla:

    private float yaw = 0.0f;
    private float pitch = 0.0f;

    void Update () {
        yaw += speedH * Input.GetAxis("Mouse X");
        pitch -= speedV * Input.GetAxis("Mouse Y");

        transform.eulerAngles = new Vector3(pitch, yaw, 0.0f);
    }

Pentru navigarea automata a personajelor pana la un punct pe harta, indiferent ca e vorba de un personaj controlat sau un agent inteligent, avem nevoie in primul rand de generarea unor zone in care acestea sa se poata misca. Aceste zone poarta numele de Navigation Mesh sau NavMesh, prescurtat. In Unity se pot genera automat acest NavMesh folosind utilitarul de Navigation Bake: Window > AI > Navigation > Bake.

De mentionat este ca un navmesh se genereaza folosind elementele statice ale scenei. Pentru a marca un obiect ca fiind static, trebuie bifat in inspector casuta de Static.

Mai mult, meniul de Navigation, permite detalierea fiecarui obiect cu privire la navigatie, astfel incat se poate specifica daca un obiect e static sau daca e Walkable sau nu.

Crearea (Bake) NavMesh-ului este statica, in sensul ca odata creata ea nu se mai modifica la runtime. Astfel, daca avem obiecte mobile, care influienteaza NavMesh-ul, nu le putem declara statice si nu le putem include in procesul de Bake. Pentru acestea exista o componenta Nav Mesh Obstacle care altereaza dinamic NavMesh-ul.

Rezultatul este o zona in care agentii sau jucatorii pot naviga folosind functii puse la dispozitie de unity pentru deplasare.

Agenti

Pentru a programa o miscare automata pe harta se poate folosi aceasta functionalitate de navigare automata (NavMesh) si componenta de tip NavMeshAgent.

Inamici

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

Miscarea personajului la o destinatie point-and-click

Pentru a misca efectivul jucatorul, trebuie sa folosim sistemul de navigare al Unity. Pentru asta se poate folosi pachetul UnityEngine.AI, vom adauga o componenta de tip Nav Mesh Agent si vom initializa un agent.

using UnityEngine.AI;

void Start() {
   agent = GetComponent<NavMeshAgent>();
}

Pentru a controla jucatorul cu mouse-ul, avem nevoie de destinatia la care trebuie personajul sa navigheze. Acest lucru putem sa il aflam cu un RayCast.

if(Input.GetMouseButtonDown(0)) //la apasarea click stanga
{
  Ray mouseClickRay = camera.ScreenPointToRay(Input.mousePosition); //creaza o raza printr-un punct de pe ecran
  RaycastHit hit;
  
  if(Physics.Raycast(mouseClickRay, out hit))
  {
    //misca player-ul pana la destinatie
    agent.SetDestination(hit.point); 
  }

}

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.

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

Pentru a avea o miscare completa, camera trebuie sa urmareasca jucatorul. Astfel, putem plasa camera ca si child al player-ului, sau putem actualiza pozitia folosind un script ca in exemplul de mai jos.

public Transform target;
public Vector3 offset;
public float height = 2f;
public float zoom=10f;

void LateUpdate() //se apeleaza imediat dupa update
{
  transform.position = target.position - offset * zoom; //actualizeaza pozitia
  transform.LookAt(target.position + Vector3.up * height); //seteaza directia camerei inspre player
}
pjv/laboratoare/2022/03.txt ยท Last modified: 2022/11/10 15:05 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