Tehnici Web

Proiect JavaScript

Baremul este acum actualizat - am trecut si cerintele de node

Important! Despre punctaj. Pentru majoritatea obiectivelor vedeti un punctaj minim si unul maxim. Punctajul minim se refera la introducerea in forma cea mai simpla a acelui element in proiect. Punctajul creste spre valoarea maxima cu cat obiectivul respectiv este reprezentat intr-un mod mai variat si mai complex. Uneori pentru efecte realmente deosebite (din punct de vedere al creativitatii sau complexitatii) se poate da chiar mai mult decat e trecut ca punctaj maxim (sub forma de bonus).

Acest proiect nu este de sine statator ci este o continuare a proiectului de html si css. Deci functionalitatile implementate trebuie sa fie in concordanta cu tema primului proiect.

Descrierea proiectului si modul de realizare a cerintelor minimale (unde vor fi incluse) trebuie specificate completand formularul de descriere a proiectului de JavaScript. Imi rezerv dreptul de a completa si modifica formularul in cazul in care studentul scrie in mod neclar ce doreste sa realizeze sau sunt multi studenti care au cerinte similare, sau aplicatia descrisa de student seamana prea mult cu aplicatii existente deja pe internet sau implementate in proiectele din anii anteriori. Se va anunta la laborator si pe grupul de Facebook cand consideram ca temele au un enunt final.

Lista cu cerintele eliminatorii o gasiti pe site-ul profesorului titular de curs. Acestea apar si in barem, uneori impartite pe mai multe cerinte mici.

Detalii de implementare pentru aplicatie

Impartirea proiectului pe foldere

Fiecare tip de resursa ar trebui sa fie in folderul sau. Imaginile, de exemplu, in folderul "imagini", fisierele audio in folderul "audio", idem pentru fisierele video. De asemenea, fisierele de configurare in format xml ar putea fi puse intr-un folder numit "configurare" sau "xml". Daca exista si css-uri specifice jocului, acestea vor fi, de exemplu, in folderul css.

Fisiere de configurare xml si obiectul Loader

Un joc in general are nevoie de anumite resurse precum: imagini, fisiere video, fisiere audio etc. Aceste resurse trebuie incarcate inainte de incepe jocul efectiv, pentru ca altfel, cand e nevoie de ele s-ar putea sa nu fi fost inca downloadate de pe server. Un mod eficient de trata problema e sa aveti un obiect special, numit Loader, care are o metoda Loader.parseResources(fisierxml) care se ocupa de incarcarea resurselor din xml si afisarea unui loading screen. Loading screen-ul ar consta dintr-un element progress care creste in valoare la fiecare resursa incarcata. Mai jos aveti un exemplu de fisier xml:

Fisier xml de resurse

<?xml version="1.0" encoding="utf-8" ?>
<resurse>
    <imagini>
        <imagine tip="adversar" w="10" h="10" id="fant_mica" aparitie="multipla">
            img/adversari/fantoma_mica.png
        </imagine>
        <imagine tip="adversar" w="20" h="40" id="fant_mare" aparitie="multipla">
            img/adversari/fantoma_mare.png
        </imagine>
        <imagine tip="adversar" w="20" h="20" id="fant_rea" aparitie="multipla">
            img/adversari/fantoma_rea.png
        </imagine>
        <imagine tip="jucator" w="20" h="20" id="jucator" aparitie="singulara">
            img/jucator/erou.png
        </imagine>
    </imagini>
    <sunete>
        <sunet id="strigat">
            audio/buhuhuhuahahahahaha.ogg
        </sunet>
        <sunet id="valeu">
            audio/jucator/valeu.ogg
        </sunet>
    </sunete>
    <fisiere_video>
        <video id="cutscene">
            video/bataie_fantome.ogv
        </video>
    </fisiere_video>
</resurse>

Pentru acest fisier, de exemplu, vom crea o propietate a Loader-ului numita imagini. Pentru fiecare set de informatii pentru cate o imagine vom crea un element DOM de tip imagine si vom adauga in obiectul imagini o proprietate cu id-ul resursei respective si avand ca valoare obiectul DOM asociat. La fel vom face si pentru audio si video.

Pentru fiecare tip de resursa vom avea si cate o functie de tip get care primeste id-ul resursei:

  • Loader.getImage(id_xml) - va intoarce elementul DOM asociat si penttru imaginile cu atributul aparitie="multipla" va crea un element nou de tip imagine (in asteptare). Facem acest lucru deoarece dorim sa avem aceeasi imagine de mai multe ori in pagina. Daca aparitie="singulara" inseamna ca stim ca imaginea o sa apara o singura data asa ca nu e nevoie sa creem mai multe elemente DOM de acelasi fel.
  • Loader.getAudio(id_xml) - va intoarce elementul audio asociat (fara a mai crea altul)
  • Loader.getVideo(id_xml) - va intoarce elementul video asociat (fara a mai crea altul, decat daca vreti sa rulati in paralel acelasi videoclip de mai multe ori)

In urma executiei functiei parseResources() obiectul Loader ar trebui sa aiba pentru fiecare tip de date cate un obiect in care proprietatile sunt egale cu id-urile elementelor iar valorile egale cu obiectele asociate.

De asemenea dorim sa avem stocate diverse date referitoare la joc; date pe care nu am dori sa le hardcodam (sa le scriem valorile efective in cod). Pentru acest lucru veti folosi fisiere xml.

Un exemplu de fisier de date e fisierul de mesaje. Putem avea mai multe cazuri in care vrem sa dam acelasi mesaj (de exemplu, mesajul care anunta jucatorul caa a pierdut jocul e dat si cand a ajuns cu sanatatea la zero dar si cand a atins marginea chenarului jocului). Am vrea sa nu il hardcodam"" pentru ca poate mai tarziu dorim sa il schimbam si ar trebui sa modificam fiecare aparitie a sa in cod. De asemenea, daca vrem sa putem seta jocul pentru diverse limbi, am vrea o metoda rapida si eficienta de a obtine mesajul potrivit pentru fiecare situatie (deci nu sa facem un set de if-uri care verifica limba setata, in cazul fiecarui mesaj de afisat)

Fisier xml de mesaje

<?xml version="1.0" encoding="utf-8" ?>
<lang>
    <mesaje lang="ro">
        <mesaj id="fail">
            Ai dat-o-n bara.
        </mesaj>
        <mesaj id="castig">
            Yaaaaay ai castigat!
        </mesaj>
        <mesaj id="pierdut">
            Hopa, murisi, ma?!
        </mesaj>
    </mesaje>
    <mesaje lang="en">
        <mesaj id="fail">
            You failed.
        </mesaj>
        <mesaj id="castig">
            You somehow won....
        </mesaj>
        <mesaj id="pierdut">
            You died!
        </mesaj>
    </mesaje>
</lang>

Pentru a incarca mesajele Loader-ul va avea o metoda Loader.parseMessages(). Aceasta poate primi limba ca parametru sau limba e setata in functia Loader.init() (metoda de initializare a loaderului). Loaderul va incarca doar mesajele limbii setate. Va avea o metoda Loader.getMessage(id) care va returna mesajul.

Fisier xml de setari
<?xml version="1.0" encoding="utf-8" ?>
<setari>
    <nivele>
        <incepator>
            <nr_adversari>10</nr_adversari>
            <viata_adversari>100</viata_adversari>
            <viteza_jucator>80</viteza_jucator>
        </incepator>
        <mediu>
            <nr_adversari>20</nr_adversari>
            <viata_adversari>200</viata_adversari>
            <viteza_jucator>70</viteza_jucator>
        </mediu>
        <avansat>
            <nr_adversari>30</nr_adversari>
            <viata_adversari>300</viata_adversari>
            <viteza_jucator>70</viteza_jucator>
        </avansat>
    </nivele>
    <gloante>
        <glont id="g0">
            <nume>glont de baza</nume>
            <imagine>im_g0</imagine><!-- id-ul imaginii corespunzatoare -->
            <putere>10</putere>
            <viteza>200</viteza>
        </glont>
        <glont id="g2">
            <nume>glont mega</nume>
            <imagine>im_g1</imagine><!-- id-ul imaginii corespunzatoare -->
            <putere>50</putere>
            <viteza>100</viteza>
        </glont>
        <glont id="g3">
            <nume>super glont</nume>
            <imagine>im_g2</imagine><!-- id-ul imaginii corespunzatoare -->
            <putere>100</putere>
            <viteza>300</viteza>
        </glont>
    </gloante>
    <harti>
        <jucator>j</jucator>
        <turn>t</turn>
        <zid>#</zid>
        <liber>0</liber>
        <harta id="h1">
            <linie>000000t000######000000t</linie>
            <linie>0000000000000##000##000</linie>
            <linie>000#####000000000#####0</linie>
            <linie>00000#000000t00000000#0</linie>
            <linie>0j000#000#####00t0000#0</linie>
            <linie>000000t0000000000#####0</linie>
            <linie>00##00000###00000000000</linie>
            <linie>00##0000000000t00000000</linie>
        </harta>
        <harta id="h2">
            <linie>00t0000####00000000000t</linie>
            <linie>0###00t000000##00000000</linie>
            <linie>00000##############0000</linie>
            <linie>00000#0000tttt0000#0000</linie>
            <linie>0t00t#0t00tttt00t0#0000</linie>
            <linie>0##00#t00000000000#0000</linie>
            <linie>00#00###0#####0####0t00</linie>
            <linie>j0#00000000000000000000</linie>
        </harta>
        ...
    </harti>     ...
</setari>

Fisierul de setari poate contine descrierile nivelelor, ale obiectelor din joc, pozitii initiale, harti predefinite.

Namespace-ul jocului

Jocul trebuie sa aiba propriul sau namespace, dar namespace-urile in Javascript sunt simbolizate prin obiecte. Deci cititi in laborator despre diversele moduri de definire a namespace-urilor si alegeti o metoda care sa se potriveasca bine cu ce aveti de facut.

Fisierul de constante

Ideal ar fi sa evitam hardcodarea valorilor. Daca scriem valorile direct, poate vrem apoi sa le modificam si trebuie sa cautam intr-un cod imens un numar (pe care poate nu-l mai tinem minte sau oricum si daca ni-l amintim, cu find mai fasim cateva pana dam de el). Astfel vom defini un fisier de constante, numit constante.js care va contine toate acele date care nu se potriveau prea bine in fisierele xml.

Cum fisierul de constante va fi folosit de catre modulul principal al jocului, va trebui inclus in html inaintea modulului principal. Deci fisierul de constante va fi cel care se va ocupa de crearea namespace-ului jocului in care va defini constantele.

Avem cel putin 2 variante de definire al lor. De mentionat este ca nu putem folosi const pentru ca nu putem creea proprietati constante ale unui obiect (cel putin nu in mod direct)

Daca obiectul corespunzator jocului il construim cu functia constructor sau ii definim o clasa prin class, atunci cel mai sigur mod e sa ne definim "constantele" ca variabile locale cu valori date, pentru care definim niste getteri.

Mai jos aveti exemplu pentru functia constructor: function Joc(){
    var NR_MILISECUNDE_UPDATE=100;
    var DEBUG_MODE=true;//de exemplu o variabila care ne spune daca sa afisam anumite mesaje de debug
    //etc.
    
    this.getNrMilisUpdate=function()
    {
        return NR_MILISECUNDE_UPDATE;
    }
    this.getDebugMode()=function()
    {
        return DEBUG_MODE;
    }
}

Putem realiza acelasi lucru si in stilul ES6 (dar nu va functiona pe browsere vechi): class Joc{
    //...
    get NR_MILISECUNDE_UPDATE(){return 100;}
    get DEBUG_MODE(){return true;}
    //...
}

Sau putem defini obiectul cu object literal. Personal consider ca e mai buna aceasta varianta, deoarece clase definim pentru cazul in care dorim mai multe instante de acelasi tip. Dar in cazul nostru obiectul joc e unic. joc=(function(){
    var NR_MILISECUNDE_UPDATE=100;
    var DEBUG_MODE=true;
    //...
    return {
            getNrMilisUpdate:function()
            {
                return NR_MILISECUNDE_UPDATE;
            },
            getDebugMode:function()
            {
                return DEBUG_MODE;
            }
            //...
        }
    })();

Clase custom pentru entitatile aplicatiei

De exemplu, pentru o aplicatie-joc, sa ne imaginam ca avem pe ecran niste adversari cu care se lupta cu jucatorul. Fiecare adversar are niste caracteristici, de exemplu: viteza, viata etc. Fara sa grupam toate caractersticile intr-un obiect am ajunge sa avem vector de viteze, vector de vieti etc. Si sa recuperam caracteristicile cu ajutorul unui indice insa asta ar complica mult lucrurile. Cand vrem sa stergem un astfel de obiect, trebuie sa ii stergem proprietatile din toti acei vectori. Arm mai putea memora datele chiar in obiectul DOM asociat, creand proprietati noi. Insa daca faceti asta puteti avea probleme cu obiectele respective; puteti suprascrie din gresala o proprietate default sau browserul sa nu le trateze cum trebuie din cauza acelor proprietati in plus. Doar in dataset ar putea fi adaugate proprietati dar si aici suntem limitati de faptul ca dataset cuprinde doar proprietati cu valori String, deci nu am putea sa memoram referinte la obiecte, de exemplu. Asadar cea mai eleganta metoda si cea care ne ajuta sa evitam eventuale bug-uri este sa cream obiecte (fie prin clasa/functie constructor, fie prin object literal) pentru fiecare entitate a jocului: jucator, adversar, obstacol etc.

Daca insa pentru anumite elementele nu avem proprietati in plus fata de cele oricum oferite de interfata DOM, nu mai e necesar sa facem clase pentru ele. De exemplu daca un obstacol trebuie lovit de 3 ori ca sa dispara atunci avem proprietate in plus pentru ca trebuie sa stim de cte ori a fost lovit pana in momentul curent. Dar daca obstacolul doar exista pe ecran si jucatorul nu poate sa se deplaseze peste el, atunci singurele proprietati care ne intereseaza sunt pozitia si dimensiunea, iar aceastea oricum sunt memorate in stilul obiectului DOM, deci in acest caz nu ar mai fi necesara crearea unei clase pentru el.

Pentru o aplicatie de procesare a datelor din pagina, de exemplu una care proceseaza datele unor produse, am dori sa grupam toate acele date intr-un singur obiect.

Pentru aplicatii care schimba tema site-ului putem avea obiecte referitoare la datele afisate si setarile utilizatorului pentru tema respectiva.

Detalii de implementare pentru cei care aleg sa faca o aplicatie de tip joc

Pentru o structurare mai buna a jocului va propun urmatoarele idei de organizare si implementare.

Ecranul de setari

Nu folositi trimiterea datelor prin submit pentru realizarea acestui task. Valorile se vor prelua direct din inputuri. Veti avea un container cu minim urmatoarele tipuri de inputuri:

  • input de tip range (de exemplu poate seta numarul de vieti, numarul de adversari, dimensiunea unui element pe ecran, numarul de secunde de game-play etc.)
  • input de tip text (de exemplu pentru numele utilizatorului sau pentru un motto, sau pentru a scrie ceva intr-un anume format, cum ar fi un cheatcode)
  • checkbox-uri (de exemplu pentru a seta daca sa apara ceva sau nu pe ecran, sau pentru cazul in care dorim sa avem valori multiple pentru o propritate, cum ar fi cazul in care avem mai multe tipuri de adversari din care putem alege oricate categorii)
  • grup de radiobuttons (de exemplu pentru a seta nivelul de joc, pentru a alege o culoare de background, pentru a alege o anume harta, un anume tip de adversari, limba jocului, modul de calculare al scorului etc.)
  • select simplu (idem radiobuttons)
  • select multiplu (pentru valori multiple ale unui camp; vezi exemplul de la checkbox)
  • textarea (poate fi folosit pentru ceva asemenator cu cazurile de la inputul de tip text, sau pentru selectarea unor valori multiple daca le separam printr-un caracter special, de exemplu, culorile adversarilor: "red;green;blue")

Tot in container va fi si un buton. La click pe acest buton se vor prelua datele din inputuri si jocul va porni cu setarile

Inputurile de tip text si textarea-urile vor avea si un placeholder care explica formatul datelor. Datele introduse de utilizator vor fi verificate cu ajutorul RegExp.

Functia de update a jocului

Uneori avem interactiuni intre elementele care se misca pe ecran. De exemplu vrem sa vedem coliziunea intre ele. Daca fiecare dintre ele are propria sa functie de miscare asociata (conform exemplului dat la laborator) atunci, daca verificam in cadrul functiei de miscare, detectam coliziunea de 2 ori pentru 2 obiecte care s-au intersectat. Cel mai simplu mod de a trata problema esta sa avem o functie joc.update(), care fi a fost apelata cu setInterval, fie se autopeleaza printr-un setTimeout si care se ocupa de toate calculele si repozitionarile obiectelor de pe pagina. Astfel ar putea verifica fara probleme coliziunile intr-un grup de n elemente (verificand intai primul cu cele de la al 2-lea incolo, apoi pe al doilea cu cele de la al 3-lea incolo si tot asa.

In plus uneori vrem sa avem si un cronometrru pentru joc (sa stim de cand a inceput jocul). Functia de update ar putea sa se ocupe usor si de aceasta functionalitate. Deci in general ar face toate calculele care tin de animatii si de trecerea timpului. Functia de update ar trebui apelata destul de des, daca se ocupa de animatii, de exemplu undeva intre 100-200 milisecunde. Daca in schimb se ocupa de elemente care nu trebuie actulizate foarte des, cum e cronometrul care s-ar actualiza oricum la fiecare secunda, e ok sa fie apelata si undeva intre 500-1000 milisecunde.

Folosirea sprite-urilor

Sprite-ul reprezinta o imagine mare cu mai multe sub-imagini in interiorul sau. In joc acestea se "decupeaza" (folosind proprietatea clip). Motivul folosirii unui sprite in locul mai multor imagini mici este ca un singur transfer cu imaginea mare poate dura mai putin decat toate transferurile imaginilor mici.

Un debugger live

Uneori vrem sa vedem cum se schimba valorile unor variabile in timp real, fara a pune breakpoints. Astfel am avea nevoie de un obiect MyDebugger cu metoda MyDebugger.setWatch(text,referinta). Iar cand avem in cod o variabila pe care vrem sa o urmarim, pur si simplu o transmitem ca parametru metodei setWatch() cu un text asociat care sa ne ajute sa identificam variabila in zona de afisare. Pentru variabilele urmarite ar trebui sa avem un tabel, sau macar un div cu randuri de forma text_setat=valoare in care sa urmarim cum se schimba valorile.

Resurse utile pentru aplicatie

Imagini gratuite:

Sprite-uri gratuite

Cursoare gratuite

Fisiere audio gratuite

Barem proiect

Baremul este extensibil, in sensul ca daca in urma discutiilor cu studentii decidem ca anumite elemente pe care oricum le-ar fi implemntat ar trebui punctate si ele, se pot adauga optiuni noi in barem. Baremul insa nu se va schimba in privinta elementelor deja existente.

In barem sunt trecute denumirile proprietatilor si metodelor din DOM, dar se puncteaza si daca ati folosit echivalentele din jQuery

Cand lucrati la joc, alegeti din barem elementele pe care doriti sa le implementati. Punctajul total depaseste cu mult punctul alocat deci aveti o mare libertate de alegere. Si evident, se puncteaza si se aduna la nota si elementele care depasesc punctajul alocat.

Elementele din categoria functionalitati sunt cele care se puncteaza cel mai mult si in rezolvarea acestora sigur vor fi cuprinse mai multe elemente de sintaxa javascript.

Elementele eliminatorii (cerintele minimale) trebuie implementate in totalitate. Obiectivele recomandate sunt elemente foarte importante care apar des la examen. Elementele optionale (fara marcaj) se refera la elemente mai putin importante (tot pot sa apara la examen dar sunt incluse mai rar in subiecte) sau sunt functionalitati specifice unui anume timp de aplicatie (si deci nu se aplica tuturor proiectelor). Punctajele sunt intre un minim si un maxim. Punctajul minim se ofera pe un obiectiv implementat la nivel minim de complexitate sau daca e pus in aplicatie "doar ca sa fie" ( de exemplu un buton care la click afiseaza o alerta cu mesajul "ai dat click" va primi un punctaj minim). Punctajul maxim se obtine mai greu si se aplicat atunci cand obiectivul e la cote mari de complexitate si are rol important in proiect. In general studentii iau un punctaj undeva pe la mijloc pentru fiecare obiectiv (deci tineti cont de acest aspect daca doriti sa va faceti suma punctelor. Ideal e sa lucrati astfel incat sa depasiti un pic 1punctajul alocat proiectului ca sa fiti siguri ca obtineti nota maxima deoarece e posibil sa considerati o complexitate mai mare pentru un element si sa ii estimati un punctaj mai mare, sau sa va incurcati la prezentare si sa pierdeti punctele pe acel obiectiv).

Modularizare si organizare proiect