Sieciowe Technologie Mobilne Laboratorium 1 Tworzenie wieloplatformowych aplikacji mobilnych przy użyciu biblioteki PhoneGap. Łukasz Kamiński
Wstęp do zajęć 1-4 Celem zajęć laboratoryjnych 1-4 będzie samodzielnie przygotowanie prostej gry na urządzenia mobilne. Wykorzystane zostaną następujące technologie: 1. JavaScript jako język programowania, 2. Element <canvas> do rysowania poszczególnych elementów gry, 3. Phonegap jako narzędzie zapewniające wieloplatformowość oraz dostęp do wybranych funkcji urządzenia mobilnego. Ponieważ efektem zajęć będzie stosunkowo duża aplikacja (przypuszczalnie ok. 500-700 linii kodu), wymagane będzie zapewnienie przejrzystości kodu z użyciem technik symulowania klas w języku JavaScript (ze wskazaniem na bibliotekę Classy wszystkie przykłady w instrukcjach będą wykorzystywały właśnie Classy). Poza zapoznaniem się z technologiami omawianymi w ramach przedmiotu, istotnym celem będzie sprawdzenie ich wydajności na urządzeniach mobilnych. Na zajęciach odpowiemy sobie na pytanie, czy technologie webowe w obecnym kształcie mogą być alternatywą dla rozwiązań natywnych przy tworzeniu dynamicznych aplikacji o średnim poziomie złożoności (takich jak proste gry). Poszczególne laboratoria będą kontynuacją laboratoriów poprzednich, dlatego konieczne jest zachowanie stanu pracy i dokończenie niewykonanych zadań w domu. Wykonanie zadań będzie wymagało przygotowania odpowiedniego zestawu zasobów, przede wszystkim w postaci plików graficznych. Aby oszczędzić trochę czasu, warto to zadanie zrealizować zawczasu, w domu. Gra będzie nosić nazwę MonsterShooter. Na środku ekranu ma się znajdować nieruchomy bohater. W jego kierunku zmierzają pojawiające się w losowych miejscach przy krańcu ekranu potwory. Kliknięcie na ekran dotykowy powoduje oddanie strzału w kierunku wskazywanym przez kompas/magnetometr urządzenia. Przy realizacji poszczególnych zadań będziemy utrzymywali podział kodu na dwa pliki źródłowe.
W pierwszym z nich zawarty będzie kod zawierający klasy prostej biblioteki, którą każdy ze studentów w trakcie laboratoriów wytworzy. Drugi będzie zawierał kod samej gry MonsterShooter, wykorzystujący klasy zawarte w bibliotece. Biblioteka będzie zawierała następujący zestaw klas: Na zielono zaznaczono klasy, które zaimplementowane zostaną w ramach pierwszych zajęć, na niebiesko klasy z zajęć drugich, natomiast na pomarańczowo klasy do zaimplementowania na trzecim laboratorium. Klasa MonsterShooter będzie stopniowo rozbudowywana, wraz ze zwiększeniem możliwości samej biblioteki. Największy stopień złożoności osiągnie na ostatnim, czwartym laboratorium, na którym stworzona zostanie dodatkowo klasa Actor. Laboratorium 1 1.1. Przygotowanie plików.html i.js Pracę zaczniemy bez używania biblioteki PhoneGap jej obsługę wprowadzimy na kolejnych zajęciach. Przygotuj następujące pliki.js: 1. classy.js do pobrania ze strony https://github.com/mitsuhiko/classy (opcjonalne, możesz również symulować użycie klas samodzielnie, z wykorzystaniem prototypów), 2. lib.js w tym pliku będziesz implementował prostą bibliotekę do tworzenia gier, 3. game.js tutaj będzie znajdowała się właściwa gra. Następnie stwórz plik index.html, w którym zamieść odwołania do plików JavaScript, zdefiniuj element <canvas> oraz metodę JavaScript wywoływaną przy załadowaniu strony: <html> <head> <meta charset="utf-8" /> <meta name="format-detection" content="telephone=no" />
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, targetdensitydpi=device-dpi" /> <link rel="stylesheet" type="text/css" href="css/index.css" /> <script type="text/javascript" src="js/classy.js"></script> <script type="text/javascript" src="js/lib.js"></script> <script type="text/javascript" src="js/game.js"></script> <title>monster shooter!</title> </head> <body onload="kamyk.monstershooter.letsgo();"> <h1 id="title"> Monster shooter! </h1> <canvas id="kanwa" width="400" height="400"> Brak obsługi canvasa! </canvas> </body> </html> 1.2. Implementacja klasy ImageLoader Pierwszą klasę dostajesz w prezencie! Jej zadaniem jest zapewnienie ładowania obrazków na potrzeby gry. Zamieść jej kod w pliku lib.js. /** * Klasa obsługująca ładowanie obrazków. var kamyk = {; kamyk.imageloader = Class.$extend({ /**"Konstruktor" init : function(whowaitsformyjob) { //Liczba obrazków, które załadowano this.numloaded = 0; /* * Obiekt, który oczekuje na załadowanie wszystkich obrazków * Musi implementować metodę onloaderready(imageloader) this.waiting = whowaitsformyjob; //Adresy obrazków this.urls = []; //Same obrazki this.images = []; //Obiekty oczekujące załadowania poszczególnych obrazków. this.imagereceivers = []; /**Dodaje url obrazka do wczytania i zwraca jego identyfikator. * Opcjonalnie można zdefiniować obiekt oczekujący załadowania tego konkretnego obrazka - * powinien on zawierać metodę "setimage(img)". addurl: function(imageurl, imagereceiver) { this.imagereceivers.push(imagereceiver); return this.urls.push(imageurl) - 1;
/** * Rozpoczyna ładowanie obrazków. startloading: function() { this.numloaded = 0; var that = this; var loadedfuncwrapper = function() { that. imageloaded(); ; for (var i = 0; i < this.urls.length; i++) { this.images[i] = new Image(); this.images[i].onload = loadedfuncwrapper; this.images[i].src = this.urls[i]; /** * Zwraca i-ty obrazek. getimage: function(ind) { return this.images[ind]; /** * Wywoływane po załadowaniu każdego z obrazków. imageloaded: function() { this.numloaded++; var isready = (this.numloaded === this.urls.length); if(isready === true) { this. finishedloading(); /** * Wywoływane po załadowaniu wszystkich obrazków. finishedloading: function() { for (var i = 0; i < this.urls.length; i++) { if (this.imagereceivers[i]!== undefined) { this.imagereceivers[i].setimage(this.getimage(i)); this.waiting.onloaderready(this); ); 1.3. Implementacja klasy Game Stwórz w pliku lib.js kolejną klasę Game. Powinna zawierać następujące właściwości: 1. Konstruktor. Jego zadania to: 1. Pozyskanie obiektu typu canvas: this.kanwa = document.getelementbyid(canvasid);
2. Uzyskanie kontekstu graficznego: this.context = this.kanwa.getcontext("2d"); 3. Uzyskanie tzw. focusu : this.kanwa.setattribute('tabindex', '0'); this.kanwa.focus(); 4. Stworzenie obiektu ładującego obrazy: this.imageloader = new kamyk.imageloader(this); 5. Stworzenie i inicjalizacja innych pomocnych pól takich jak wysokość, szerkość, itp. 2. Zestaw metod o charakterze abstrakcyjnym (które będą implementowane przez klasę MonsterShooter, pochodną klasy Game): 1. initresources() odpowiedzialna za przygotowanie zasobów na potrzeby gry 2. update() odpowiedzialna za aktualizację stanu gry w każdym obiegu pętli gry, 3. render() odpowiedzialna za rysowanie stanu gry w każdym obiegu pętli gry. Metody abstrakcyjne można w uproszczony sposób symulować, rzucając wyjątek: kamyk.abstractexception = "Zapomniałeś zaimplementować tę metodę w podklasie!"; //(...) //klasa Game kamyk.game = Class.$extend({ //(...) initresources: function() { throw kamyk.abstractexception; update: function(dt) { throw kamyk.abstractexception; render: function() { throw kamyk.abstractexception; //(...) );//koniec klasy Game 3. Metodę onloaderready(), która zostanie wywołana przez obiekt klasy ImageLoader po załadowaniu wszystkich obrazków. W najprostszym wariancie, metoda ta powinna ustawiać odpowiednią zmienną logiczną, która będzie sprawdzana przez metody realizujące pętlę gry. 4. Metody odpowiedzialne za rozpoczęcie (start()) oraz zatrzymanie (stop()) gry. Metoda start() powinna wywołać metodę initresources() oraz rozpocząć główną pętlę gry.
Pętla gry nie powinna wykonywać żadnych działań, dopóki obiekt ładujący obrazki nie poinformuje o tym, że załadował wszystkie zasoby. Pętlę gry można realizować przy pomocy metod setinterval()/settimeout(), przekazując metodę realizującą pojedynczy obieg pętli jako parametr. Zalecanym rozwiązaniem jest jednak użycie metody requestanimationframe(). Ponieważ na starszych przeglądarkach metoda ta może nie być obsługiwana, zaleca się stosowanie na wszelki wypadek poniższego wypełnienia (polyfill). W obu przypadkach należy uważać na omówione na wykładzie zagadnienie uciekającego this. //polyfill by Erik Möller //http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating //http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ (function() { var lasttime = 0; var vendors = ['webkit', 'moz']; for(var x = 0; x < vendors.length &&!window.requestanimationframe; ++x) { window.requestanimationframe = window[vendors[x]+'requestanimationframe']; window.cancelanimationframe = window[vendors[x]+'cancelanimationframe'] window[vendors[x] +'CancelRequestAnimationFrame']; if (!window.requestanimationframe) window.requestanimationframe = function(callback, element) { var currtime = new Date().getTime(); var timetocall = Math.max(0, 16 - (currtime - lasttime)); var id = window.settimeout(function() { callback(currtime + timetocall); timetocall); lasttime = currtime + timetocall; return id; ; if (!window.cancelanimationframe) window.cancelanimationframe = function(id) { cleartimeout(id); ; ()); 1.4. Implementacja klasy MonsterShooter W pliku game.js zaimplementuj pierwszą wersję swojej gry. Na obecnym etapie ma wyświetlać obrazek tła oraz ruszający się w dowolny sposób inny obrazek. Szkielet klasy, którą należy rozbudować, powinien wyglądać w sposób następujący: kamyk.monstershooter = {; kamyk.monstershooter.monstershooter = kamyk.game.$extend({ init : function(canvasid, width, height) { this.$super(canvasid, width, height); initresources: function() {
//użycie obiektu ładującego obrazki this.backgroundimgindex = this.imageloader.addurl("img/background.jpg", undefined); this.imageloader.startloading(); render: function() { this.context.drawimage(this.imageloader.getimage(this.backgroundimgindex),0,0,this.width, this.height); ); update: function(dt) { ); 1.5. Implementacja funkcji startującej (letsgo()) Na koniec, w pliku game.js zdefiniuj metodę o nazwie letsgo(), takiej samej, jakiej użyłeś w pliku.html stworzonym na samym początku laboratorium. Warto na tym etapie pracy złamać zasadę, która odradza definiowanie zmiennych globalnych. Przechowaj referencję do obiektu gry w formie zmiennej globalnej, a ułatwi to dostęp do tego obiektu z konsoli przeglądarki czy narzędzi takich jak FireBug, tym samym upraszczając proces poszukiwania ew. błędów. var gra = undefined; kamyk.monstershooter.letsgo = function() { gra = new kamyk.monstershooter.monstershooter("kanwa", 400, 400); gra.start(); ZADANIE Zadanie do wykonania polega na wyświetleniu z wykorzystaniem obiektu <canvas> prostej sceny, zawierającej obrazek tła oraz poruszający się w dowolny sposób inny obrazek. Punktacja: 1 pkt. - rysowanie nieruchomych obrazków 2 pkt. - rysowanie z animacją jeden z obrazków porusza się po ekranie 2 pkt. - implementacja kodu zgodnie z wymaganiami z instrukcji: podział na bibliotekę i grę właściwą, symulowanie klas w JavaScript