-
Notifications
You must be signed in to change notification settings - Fork 0
Kompleksniji dijelovi koda
Neki bitniji dijelovi samog projekta detaljno objašnjeni i prikazani.
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 se trenutno koristi za resetiranje trenutnog, učitavanje sljedećeg levela, pauziranje i slično.
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

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