Preskoči na sadržaj
FORTRONIQ

MODUL 3 · RAZVOJ OKRUŽENJA VIRTUALNE STVARNOSTI

Nastavna jedinica 3.2

Programska logika: nacrti, događaji i varijable

Scena bez logike je samo maketa. U ovoj jedinici gradi se ponašanje virtualnog laboratorija VR-1: događaji, varijable, funkcije, grananja i poruke među objektima, u nacrtima i u C# skriptama, uz ispis i otklanjanje pogrešaka.

Moći ću:
  • objasniti načelo događaj – uvjet – radnja na primjeru sklapanja robota
  • razlikovati klasu, instancu i komponentu u nacrtima i u skriptama
  • odabrati odgovarajući događaj i izbjeći nepotrebnu logiku u svakom okviru
  • definirati varijable odgovarajućeg tipa i dosega te napisati jednostavnu funkciju
  • povezati objekte izravnom referencom, sučeljem ili dispečerom događaja
  • koristiti ispis, zapisnik i crtanje za otklanjanje pogrešaka
  • izračunati vrijeme koje logika troši po okviru i udio u budžetu procesora

Događaj, uvjet, radnja

Kad korisnik u laboratoriju VR-1 prinese kotač osovini motora i pusti ga, nešto mora odlučiti je li kotač na pravom mjestu, pričvrstiti ga, zabilježiti napredak i, kad je postavljeno svih dvanaest dijelova, dopustiti pokretanje robota. To je programska logika. Gotovo sva logika u VR aplikaciji ima isti oblik:

događaj → uvjet → radnja

Događaj je nešto što se dogodilo: pokrenula se scena, pritisnut je okidač, dio je ušao u područje drugog dijela, isteklo je vrijeme. Uvjet provjerava treba li reagirati: je li dio blizu ciljnog mjesta, je li sklop već gotov. Radnja mijenja stanje: pričvrsti dio, uveća brojač, pusti zvuk, prikaže natpis.

Logika se može pisati na dva načina. U okruženju s vizualnim programiranjem (npr. Unreal Engine) koriste se nacrti (engl. Blueprint): grafovi u kojima su naredbe čvorovi (engl. nodes), a redoslijed i podaci spojeni su žicama. U okruženju s C# skriptama (npr. Unity) ista se logika piše kao tekst u klasama koje se dodaju objektima. Pojmovi su isti: događaj, varijabla, funkcija, grananje, petlja. Tko razumije jedan način, drugi svlada brzo.

Nacrti su pregledni za početnike i za kratku logiku uz scenu; skripte su preglednije kad logike ima mnogo, kad se radi u timu uz upravljanje inačicama i kad treba precizno pratiti promjene. Ovo je procjena iz prakse, a ne pravilo — mnogi projekti kombiniraju oboje.

Klase, instance i komponente

Nacrt ili skripta opisuje klasu (engl. class) — predložak ponašanja. Svaki objekt te klase u sceni je instanca (engl. instance). Robot MR-1 ima dvanaest dijelova, ali ne treba dvanaest nacrta: dovoljan je jedan nacrt dijela, npr. BP_Dio, i dvanaest instanci koje se razlikuju vrijednostima varijabli (naziv dijela, ciljni položaj, mreža za prikaz).

Unutar objekta ponašanje je složeno od komponenti (engl. components): mreža za prikaz, kolizijski oblik, zvuk, komponenta za hvatanje. U okruženju s nacrtima komponente se dodaju u prozoru nacrta, a logika se piše u grafu događaja istog nacrta. U okruženju s C# skriptama objekt u sceni nosi komponente, a svaka skripta je također komponenta koja se na objekt dodaje u inspektoru.

Za VR-1 dovoljan je mali skup klasa:

  • BP_VRPijun — korisnik: kamera, dva kontrolera, ruke (jedinica 3.3);
  • BP_Dio — jedan dio robota s varijablama za naziv i ciljni položaj;
  • BP_TockaInteresa — točka interesa s natpisom i panelom (jedinica 3.4);
  • BP_UpraviteljSklopa — jedna instanca u sceni koja broji postavljene dijelove i odlučuje kad je sklop gotov.

Zapamtite

Ako se isti komad logike kopira u više nacrta, to je znak da ga treba izdvojiti u zajedničku klasu, komponentu ili funkciju. Kopirana logika znači da se svaka ispravka mora napraviti na više mjesta — i da će se na jednom zaboraviti.

Događaji i tok izvršavanja

Razvojno okruženje samo poziva određene događaje. Nazivi se razlikuju po okruženju i inačici, ali funkcija je ista:

  • početak — jednom, kad objekt nastane ili scena krene (u nacrtima najčešće BeginPlay, u C# skriptama Start ili Awake): postavljanje početnih vrijednosti, traženje referenci;
  • svaki okvir (engl. tick, update) — 90 puta u sekundi pri 90 Hz: samo ono što se stvarno mijenja iz okvira u okvir, npr. glatko praćenje;
  • ulazni događaji — pritisak i otpuštanje tipke preko ulaznih akcija (jedinica 3.3);
  • sudar i preklapanje (engl. hit, overlap) — kolizijski oblik dijela uđe u područje ciljnog mjesta;
  • mjerač vremena (engl. timer) — ponavljanje u zadanom razmaku, npr. 10 puta u sekundi;
  • vlastiti događaji (engl. custom event) — koje definirate sami, npr. „Dio postavljen".

U nacrtu postoje dvije vrste žica. Izvršne žice određuju redoslijed: od događaja, preko grananja, do radnji. Podatkovne žice dovode vrijednosti u čvorove: udaljenost, naziv, brojač. Čvor koji nema ulaznu izvršnu žicu nikad se ne izvrši, bez obzira na to koliko je podataka spojeno na njega — to je najčešća početnička greška.

DogađajDio postavljen Grananjena pravom mjestu? PostaviPostavljeno + 1 IspisPostavljeno / 12 GrananjePostavljeno = 12? FunkcijaPokreni robota istina istina laž: dio se vrati Postavljeno podatkovna žica
Slika 3.2.1 — Nacrt događaja „Dio postavljen": tirkizne izvršne žice određuju redoslijed, iscrtkane podatkovne žice dovode vrijednost varijable.

Pažnja

Događaj svakog okvira je najskuplje mjesto za logiku. Traženje objekata u sceni, učitavanje ili ispis u svakom okviru brzo pojede budžet procesora od 9,0 ms. Ako nešto ne mora biti glatko, koristite događaj preklapanja, ulazni događaj ili mjerač vremena. Izvođenje svakim okvirom obično se može isključiti u postavkama objekta; naziv postavke ovisi o inačici.

Varijable i tipovi

Varijabla (engl. variable) je imenovano mjesto za vrijednost. Svaka ima tip koji određuje što može sadržavati i doseg (engl. scope) koji određuje tko je vidi: lokalna varijabla postoji samo za vrijeme izvođenja jedne funkcije, varijabla klase postoji dok postoji objekt, a javna ili uređiva varijabla može se mijenjati u uređivaču za svaku instancu posebno. Tako se istom nacrtu BP_Dio za svaku instancu upiše drugi naziv i drugi ciljni položaj.

Varijabla (VR-1)TipPočetnoNamjena
NazivDijelatekst—natpis i ispis, npr. „Kotač lijevi"
CiljniPolozajtransformacijaiz sklopapoložaj i orijentacija dijela u sklopljenom robotu
JePostavljenlogička (bool)lažda se isti dio ne broji dvaput
Postavljenocijeli broj (int)1brojač u upravitelju sklopa; šasija, koja nosi utore ostalih dijelova, od početka se računa kao postavljena
UkupnoDijelovacijeli broj (int)12uvjet završetka sklopa
Tolerancijadecimalni broj (float)1,5 cmnajveća dopuštena udaljenost od cilja
Dijelovipolje referenci (array)12 elemenatapopis svih dijelova za petlju

Dobra praksa je broj koji se može promijeniti (tolerancija, ukupni broj dijelova, brzina) držati u varijabli, a ne upisan izravno u čvor ili izraz. Tada se mijenja na jednom mjestu i vidi se što znači.

Funkcije, grananja i petlje

Funkcija (engl. function) je imenovani komad logike s ulazima i izlazima. Primjer za VR-1: funkcija JeNaCilju prima trenutačni i ciljni položaj dijela, a vraća istinu ako je dio dovoljno blizu i dovoljno poravnat. Udaljenost se računa kao duljina razlike dvaju položaja:

d = √((x − xc)² + (y − yc)² + (z − zc)²)   ≤   15 mm  i  kutno odstupanje ≤ 10°

Vrijednosti 15 mm i 10° odabrane su za ovaj primjer; stvarne tolerancije odredite probom u naočalama. Premale tolerancije frustriraju korisnika jer dio „neće sjesti", prevelike omogućuju da kotač sjedne na krivu osovinu.

Grananje (engl. branch, u skriptama if) usmjerava izvršavanje prema uvjetu. Petlja (engl. loop) ponavlja radnju, npr. za svaki element polja Dijelovi vrati dio na početni položaj na stolu kad korisnik pritisne gumb „Ispočetka". Petlje u događaju svakog okvira treba izbjegavati.

Ista logika brojača u C# skripti mogla bi izgledati ovako. Kratki primjer za razumijevanje; provjerite ga u vlastitoj inačici okruženja prije uporabe.

using UnityEngine;

public class UpraviteljSklopa : MonoBehaviour
{
    public int ukupnoDijelova = 12;
    int postavljeno = 1;   // šasija se računa kao postavljena
    public event System.Action SklopGotov;

    public void DioPostavljen(string naziv)
    {
        postavljeno++;
        Debug.Log("Postavljeno " + postavljeno + "/" + ukupnoDijelova + ": " + naziv);
        if (postavljeno == ukupnoDijelova) SklopGotov?.Invoke();
    }
}

Komunikacija među objektima

Dio robota zna kad je postavljen, ali brojač je u upravitelju sklopa, natpis u sučelju, a zvuk u trećem objektu. Postoje tri uobičajena načina da objekti razgovaraju:

  • Izravna referenca — dio ima varijablu koja pokazuje na upravitelja i izravno poziva njegovu funkciju. Jednostavno, ali dio tada „zna" za upravitelja i ne radi bez njega.
  • Sučelje (engl. interface) — dogovoreni skup funkcija koje više različitih klasa može ostvariti. Pozivatelj ne mora znati o kojoj je klasi riječ, dovoljno je da objekt ostvaruje sučelje. Ovako se u jedinici 3.4 grade točke interesa.
  • Dispečer događaja (engl. event dispatcher; u C# skriptama događaj ili delegat) — objekt objavi da se nešto dogodilo, a svi koji su se prijavili dobiju obavijest. Objavitelj ne zna tko sluša.
BP_Dio12 instanci DispečerDio postavljen Upraviteljbrojač 0 ... 12 Sučeljenatpis napretka Zvukklik pri spoju objavi: ne zna tko sluša prijava pri početku
Slika 3.2.2 — Dispečer događaja: dio samo objavi da je postavljen, a upravitelj, sučelje i zvuk reagiraju svaki na svoj način.

Za mali projekt kao što je VR-1 dobra je praksa: izravna referenca kad je veza stalna i jedna na jednu, sučelje kad više različitih objekata treba istu vrstu poziva, a dispečer kad jednu obavijest treba više slušatelja. Isto vrijedi i za vezu sa simulacijom robota u jedinici 3.6: most prema simulatoru samo se prijavi na događaj, a ostatak aplikacije za njega ne zna.

Ispis i otklanjanje pogrešaka

Logika rijetko radi iz prve. Alati za pronalaženje pogrešaka (engl. debugging) postoje u svakom okruženju:

  • Ispis (engl. print) — čvor koji prikaže tekst na zaslonu i zapiše ga u zapisnik; u C# skriptama poziv za zapis u konzolu. Najbrži način da se vidi je li se događaj uopće dogodio i s kojom vrijednošću.
  • Zapisnik (engl. log) — datoteka i prozor s porukama, upozorenjima i greškama. Ostaje i nakon izlaska iz pregleda.
  • Točka prekida (engl. breakpoint) — zaustavi izvršavanje na odabranom čvoru ili retku i pokaže vrijednosti varijabli u tom trenutku.
  • Crtanje za otklanjanje pogrešaka (engl. debug draw) — privremene crte, sfere i strelice u sceni: kamo pokazuje zraka, koliki je radijus hvatanja, gdje je ciljni položaj dijela.

U naočalama tekst ispisan na zaslonu često se ne vidi ili se vidi samo na monitoru računala. Zato je dobra praksa u sceni VR-1 imati mali 3D panel uz rub stola koji prikazuje nekoliko zadnjih poruka i broj sličica u sekundi, a u izgradnji za predaju taj se panel isključi jednom varijablom.

Pažnja

Ispis u svakom okviru zatrpava zapisnik i usporava aplikaciju. Ispisujte na događaj (dio postavljen, dio pušten), a ne stalno. Prije izgradnje za predaju uklonite ili isključite ispise za otklanjanje pogrešaka.

Računski prikaz: koliko logika troši po okviru

Budžet procesora za VR-1 je 9,0 ms po okviru. Ako N objekata izvodi logiku u svakom okviru i svaki troši t mikrosekundi, logika troši N · t po okviru. Ako se ista provjera izvodi mjeračem vremena učestalosti fm, prosječno opterećenje po okviru pada u omjeru fm / f:

tokvir = N · t / 1000  [ms]  ·  tmjerač = N · t · fm / (1000 · f)

Opterećenje procesora logikomRačunski prikaz

Računa koliko vremena po okviru troši logika koja se izvodi u svakom okviru i koliko bi trošila ista provjera izvedena mjeračem vremena.

Logika u svakom okviru—
Udio budžeta procesora—
Prosječno uz mjerač vremena—
Poziva u sekundi (svaki okvir)—

Za 12 dijelova i 6 točaka interesa (18 objekata) uz 15 µs po objektu logika troši 0,27 ms, odnosno 3 % budžeta procesora. Ako je logika loše napisana, npr. svaki okvir pretražuje cijelu scenu i troši 150 µs, isti broj objekata troši 2,7 ms ili 30 % budžeta — a to je vrijeme koje nedostaje fizici i pripremi iscrtavanja. Provjera mjeračem vremena 10 puta u sekundi smanjuje prosječno opterećenje devet puta. Vremena po objektu ovdje su pretpostavke za primjer; stvarne vrijednosti daje profiler (jedinica 3.7).

Ključni pojmovi

PojamZnačenje
nacrt (engl. Blueprint)vizualni program sastavljen od čvorova povezanih izvršnim i podatkovnim žicama
čvor (engl. node)jedna naredba ili vrijednost u nacrtu
događaj (engl. event)pokretač logike: početak, okvir, ulaz, preklapanje, mjerač vremena, vlastiti događaj
varijabla (engl. variable)imenovano mjesto za vrijednost određenog tipa i dosega
funkcija (engl. function)imenovani komad logike s ulazima i izlazima
klasa i instanca (engl. class, instance)predložak ponašanja i pojedini objekt u sceni izrađen po njemu
sučelje (engl. interface)dogovoreni skup funkcija koje različite klase mogu ostvariti
dispečer događaja (engl. event dispatcher)objava događaja na koju se mogu prijaviti proizvoljni slušatelji
točka prekida (engl. breakpoint)mjesto na kojem se izvršavanje zaustavi radi pregleda vrijednosti

Provjeri se

Kratka provjera prije prelaska na sljedeću jedinicu. Odgovor se potvrđuje klikom, a obrazloženje se prikazuje odmah.

Pitanje 1 od 4

U nacrtu je čvor za uvećanje brojača spojen podatkovnom žicom na varijablu, ali se nikad ne izvrši. Koji je najvjerojatniji uzrok?

Čvor s izvršnim ulazom izvrši se samo kad do njega dođe izvršna žica od nekog događaja; podatkovne žice same ne pokreću izvršavanje.

Pitanje 2 od 4

Robot MR-1 ima 12 dijelova s istim ponašanjem. Koje je rješenje najbolje?

Jedna klasa s uređivim varijablama (naziv, ciljni položaj) daje jedno mjesto za ispravke, a instance se razlikuju samo podacima.

Pitanje 3 od 4

Više objekata treba reagirati kad je dio postavljen, a dio ne smije ovisiti o njima. Koji način komunikacije odabrati?

Dispečer omogućuje da objavitelj ne zna tko sluša, a proizvoljan broj slušatelja reagira na istu obavijest.

Pitanje 4 od 4

18 objekata izvodi logiku u svakom okviru, svaki po 150 µs. Koliki je udio budžeta procesora od 9,0 ms?

18 · 150 µs = 2700 µs = 2,7 ms; 2,7 / 9,0 = 0,30, odnosno 30 % budžeta.

Odgovoreno 0 od 4.

Vježbe uz jedinicu

Gradivo ove jedinice priprema za sljedeće vježbe: