Skip to content

Kompleksniji dijelovi koda

ivan1mikec edited this page Jun 6, 2023 · 9 revisions

Objašnjenja glavnih dijelova koda

Neki bitniji dijelovi samog projekta detaljno objašnjeni i prikazani.

Kretnja igrača

Za svaku platformer igricu vrlo je važno imati igrača koji ima dobro definirane kretnje. Po time se misli na dobru responzivnost i ugodnu kretnju. Vrlo je izazovno postići zadovoljavajuću implementaciju kretnji zbog vrlo velikog broja mogućnosti same implementacije. U ovom projektu mi smo se odlučili na implementaciju koja se fokusira na gravitacijski utjecaj i skaliranje. RigidBody komponenta zaslužna je za fiziku. Prate se stanje kretnje, orijentacija, skok i padanje. Ukoliko je igrač na tlu povjerava se unos podataka, a ako je u zraku onemogućava se ponovno skakanje i dupli skok te se prilagođava gravitacija i padanje samog igrača.

Isječak koda ispod prikazuje nam nekoliko uvjeta unutar if tvrdnje prema kojima se određuje na koji će način gravitacija utjecati na igrača s obzirom na to gdje se nalazi te jeli u zraku ili na podu.

Funkcija SetGravityScale služi kako bi namjestila pomoću parametara utjecaj gravitacije na igrača.

        // If falling and we press the down button
        if (rb.velocity.y < 0 && moveInput.y < 0)
        {
            // Much higher gravity if holding down
            SetGravityScale(Data.gravityScale * Data.fastFallGravityMult);
            // Caps maximum fall speed, so when falling over large distances we don't accelerate to insanely high speeds
            rb.velocity = new Vector2(rb.velocity.x, Mathf.Max(rb.velocity.y, -Data.maxFastFallSpeed));
        }
        else if (isJumpCut)
        {
            // Higher gravity if jump button released
            SetGravityScale(Data.gravityScale * Data.jumpCutGravityMult);
            rb.velocity = new Vector2(rb.velocity.x, Mathf.Max(rb.velocity.y, -Data.maxFallSpeed));
        }
        else if ((isJumping || isJumpFalling) && Mathf.Abs(rb.velocity.y) < Data.jumpHangTimeThreshold)
        {
            SetGravityScale(Data.gravityScale * Data.jumpHangGravityMult);
        }
        else if (rb.velocity.y < 0)
        {
            // Higher gravity if falling
            SetGravityScale(Data.gravityScale * Data.fallGravityMult);
            // Caps maximum fall speed, so when falling over large distances we don't accelerate to insanely high speeds
            rb.velocity = new Vector2(rb.velocity.x, Mathf.Max(rb.velocity.y, -Data.maxFallSpeed));
        }
        else
        {
            // Default gravity if standing on a platform or moving upwards
            SetGravityScale(Data.gravityScale);
        }

Što se tiče brzine kretnje potrebno je definirati maksimalnu brzinu i akceleraciju, kada se igrač kreće u jednom smjeru on će ubrzavati do maksimalne brzine. Kretanje zrakom ima odvojene vrijednosti akceleracije i maksimalne brzine što omogućuje prilagodbu, te veću ili manju kontrolu kretanja u zraku. Ovisno o tome koliko se dugo drži tipka za skok igrač će skočiti više ukoliko je zadržana duže, te niže ukoliko je tipka prije otpuštena.

        if (CanJumpCut()) {
            rb.AddForce(Vector2.down * rb.velocity.y * 0.5f, ForceMode2D.Impulse);
            isJumpCut = true;
        }

Kada je tipka za skok otpuštena dodaje se vektor sile prema dolje magnitude pola trenutne vertikalne brzine.

GameManager

GameManager se trenutno koristi za resetiranje trenutnog, učitavanje sljedećeg levela, pauziranje i slično.

Učitavanje sljedećeg levela

Svaki level u igrici je jedna scena u Unityu. GameManager sadrži metode RestartLevel() i LoadNextLevel() koje pomoću SceneManagera ponovno učitavaju trenutnu ili sljedeću scenu.

Redoslijed scena je definiran u Build settings Primjer

Kako bi ovo radilo potrebno je sve scene koje ne sadrže level postaviti na početak, zatim scene sa levelima i na kraju neka završna scena sa rezultatom. Ovo trenutno funkcionira, ali u slučaju problema ili potrebe za detaljnijim redoslijedom levela, LoadNextLevel() se može promijeniti tako da učitava samo scene sa "Level" u imenu učitavaju. Npr. Level 1, Level 2, Level 2.2 itd.

Pauziranje

Metode Pause() i Unpause() pauziraju i nastavljaju igru, to rade tako da postave Time.timeScale na 0 ili 1. Timescale kontrolira koliko brzo vrijeme prolazi u odnosu na početno stvarno vrijeme. Kada je postavljeno na 0 vrijeme ne prolazi, a svi dijelovi igrice koji koriste Time.deltaTime (direktno ili u pozadini) time su pauzirani. Također se postavlja varijabla isPaused koja se može kasnije koristiti i aktivira/deaktivira ekran koji prikazuje da je igra pauzirana.

    public void Pause()
    {
        pausedScreen.SetActive(true);
        Time.timeScale = 0;
        isPaused = true;
    }

    public void Unpause()
    {
        pausedScreen.SetActive(false);
        Time.timeScale = 1;
        isPaused = false;
    }

Enemy patrol skripta

Skripta EnemyPatrol odgovorna je za implementaciju patrolnog ponašanja neprijatelja. Ova skripta omogućuje neprijateljima da se kreću naprijed-natrag između definiranih patrolnih točaka, stvarajući dinamično i zanimljivo igranje. Skripta koristi dvije reference transformacije, "leftEdge" i "rightEdge," koje definiraju granice patrole za neprijatelja. Ove točke ograničavaju kretanje neprijatelja, sprječavajući ih da odlutaju izvan navedenog područja.

   [Header ("Patrol Points")]
    [SerializeField] private Transform leftEdge;
    [SerializeField] private Transform rightEdge;

Referenca "enemy" transformacije koristi se za pristup neprijateljskom GameObjectu za primjenu promjena kretanja i animacije.

    [Header("Enemy")]
    [SerializeField] private Transform enemy;
   

Skripta uključuje parametar "speed", koji određuje brzinu kojom se neprijatelj kreće između patrolnih točaka. Podešavanjem ovog parametra možemo kontrolirati brzinu kretanja neprijatelja kako bi postigli željenu ravnotežu igranja.

   [Header("Movement parameters")]
    [SerializeField] private float speed;
   

Awake():

  • Dodjeljuje početnu ljestvicu neprijatelja varijabli "initScale", dopuštajući pravilne promjene smjera.
 private void Awake()
    {
        initScale = enemy.localScale;
        
    }
   

Update():

  • Provjerava trenutni smjer neprijatelja.
  • Ako se neprijatelj kreće lijevo i nije stigao do lijevog ruba patrole, nastavlja kretanje lijevo.
  • Ako se neprijatelj kreće lijevo i dostigao je lijevi rub patrole, aktivira promjenu smjera.
  • Ako se neprijatelj kreće udesno i nije dosegao desni rub patrole, nastavlja kretanje udesno.
  • Ako se neprijatelj kreće desno i dostigao je desni rub patrole, aktivira promjenu smjera.
  private void Update()
    {
        if (movingLeft)
        {
            if (enemy.position.x >= leftEdge.position.x)
            MoveInDirection(-1);
            else
            {
                DirectionChange();
            }
        }

        else
        {
            if (enemy.position.x <= rightEdge.position.x)
            MoveInDirection(1);
            else
            {
                DirectionChange();
            }
        }

    }

DirectionChange():

  • Postavlja boolean parametar "movement" u animatoru na false, pokazujući da je neprijatelj u stanju mirovanja.
  • Povećava idleTimer za praćenje trajanja stanja mirovanja.
  • Ako idleTimer premaši idleDuration, pokreće promjenu smjera prebacivanjem booleove vrijednosti "movingLeft".
  private void DirectionChange()
    {
        anim.SetBool("movement", false);

        idleTimer += Time.deltaTime; 

        if (idleTimer > idleDuration)
        {
            movingLeft = !movingLeft;
        }
      
    }

MoveInDirection(int _direction):

  • resetira idleTimer.
  • Postavlja boolean parametar "movement" u animatoru na true, pokazujući da je neprijatelj u stanju kretanja.
  • Podešava enemy local scale kako bi okrenuo u smjer kretanja na temelju parametra _direction.
  • Pomiče neprijatelja vodoravno koristeći Time.deltaTime, _direction i definiranu brzinu.
     private void MoveInDirection(int _direction)
    {
        idleTimer = 0;
        anim.SetBool("movement", true);
        //Debug.Log("prije " + enemy.localScale.x);
        //make enemy face right direction
        enemy.localScale = new Vector3(Mathf.Abs(enemy.localScale.x) * _direction, initScale.y, initScale.z);

        //Debug.Log("poslije " + enemy.localScale.x);
        //move enemy
        enemy.position = new Vector3(enemy.position.x + Time.deltaTime * _direction*speed
            , enemy.position.y, enemy.position.z); 


    }

Melee enemy skripta

Skripta MeleeEnemy bitna je komponenta ove Unity igre. Ova skripta definira ponašanje neprijatelja, dopuštajući mu da otkrije i napadne lik igrača kada dođe unutar određenog dometa. Skripta koristi različite parametre, reference i metode kako bi omogućila željeno ponašanje neprijatelja.

Parametri napada: attackCooldown: Vrijeme trajanja koje mora proći između uzastopnih napada neprijatelja. range: Udaljenost na kojoj neprijatelj može otkriti lik igrača i započeti napad. damage: Količina štete nanesene liku igrača kada je uspješno pogođen neprijateljskim napadom.

    [Header("Attack Parameters")]
    [SerializeField] private float attackCooldown;
    [SerializeField] private float range;
    [SerializeField] private int damage;

Parametri sudara:

colliderDistance: množitelj koji određuje udaljenost između neprijatelja i ishodišta sudara. boxCollider: komponenta BoxCollider2D spojena na neprijatelja, koja se koristi za otkrivanje sudara.

 [Header("Collider Parameters")]
    [SerializeField] private float colliderDistance;
    [SerializeField] private BoxCollider2D boxCollider;
   

Awake(): Dohvaća nadređenu komponentu transformacije i dodjeljuje je referenci "odparents". Dohvaća komponentu Animatora i dodjeljuje je referenci "anim". Dohvaća komponentu EnemyPatrol iz nadređenog objekta i dodjeljuje je referenci "enemyPatrol".

   private void Awake()
    {

        odparents = transform.parent.GetComponent<Transform>();
        anim = GetComponent<Animator>();
        enemyPatrol = GetComponentInParent<EnemyPatrol>();
        
    }
   

Update():

Ažurira vrijednost cooldownTimera na temelju proteklog vremena. Provjerava je li lik igrača u dometu. Ako CooldownTimer premaši AttackCooldown vrijednost, pokreće animaciju napada i resetira CooldownTimer. Ako komponenta enemyPatrol postoji, omogućuje je ili onemogućuje je na temelju prisutnosti igrača.

  private void Update()
    {
        cooldownTimer += Time.deltaTime;
        if (PlayerInSight())
        {
            
            if (cooldownTimer >= attackCooldown)
            {
                print("true");
                cooldownTimer = 0;
                anim.SetTrigger("meleeatck");
                
            }

        }

        if (enemyPatrol != null)
        {
            enemyPatrol.enabled = !PlayerInSight();
        }


    }
   

PlayerInSight():

Izvodi BoxCast u određenom smjeru i rasponu kako bi otkrio igrača. Ako je lik igrača otkriven unutar navedenog raspona, dodjeljuje komponentu PlayerController referenci "player" i vraća vrijednost true. U suprotnom, vraća false.

  private bool PlayerInSight()
    {
        RaycastHit2D hit = Physics2D.BoxCast(boxCollider.bounds.center + transform.right * range * odparents.localScale.x *colliderDistance,new Vector3(boxCollider.bounds.size.x * range, boxCollider.bounds.size.y, boxCollider.bounds.size.z
           ) ,0,Vector2.left, 0);
        if (hit.transform!= null)
        {
           
        }
        
        if (hit.transform != null && hit.transform.CompareTag("Player"))
        {
            player = hit.transform.GetComponent<PlayerController>();
            return true;
        }

        return false;
    }

OnDrawGizmos():

Vizualizira domet neprijateljskog napada u Unity Editoru pomoću Gizmosa. Crta kocku koja predstavlja domet napada oko neprijatelja koristeći navedene parametre.

 private void OnDrawGizmos()
    {
        Gizmos.color = Color.red;
        Gizmos.DrawWireCube (boxCollider.bounds.center + transform.right*range * odparents.localScale.x * colliderDistance, new Vector3(boxCollider.bounds.size.x * range, boxCollider.bounds.size.y, boxCollider.bounds.size.z
           ));
    }

DamagePlayer():

Nanosi štetu igraču ako je u dometu napada neprijatelja.

  private void DamagePlayer()
    {
        if (PlayerInSight())
        {
            player.TakeDamage(damage);
        }

    }

Clone this wiki locally