Nadeszła prawdziwa jesień. Za oknem zimno, szaro i deszczowo. Idealna atmosfera do pisania kodu! Parę projektów przewija się ostatnio przez ekran mojego monitora, ale to co chcę dziś pokazać to aktualny stan gry tekstowej o której pisałem niedawno.
Gra tekstowa RPG wciąż nie jest kompletna, ale prace zdecydowanie ruszyły do przodu. Zacząłem od drobnych zmian w strukturze kodu. Przede wszystkim jednak dodałem do gry przedmioty. Gracz, może teraz w pomieszczeniach gry znaleźć różne rupiecie, które można podnieść i odłożyć w innym miejscu. Aktualna wersja gry do przetestowania tutaj.
Tym razem też nie będę wklejał całego kodu. Można go zobaczyć zerkając do źródła strony gry. To wciąż jeden plik, więc nie powinno być kłopotu. Postanowiłem też wrzucić ten projekt na moje skromne konto na githubie. Z gita, można sobie pobrać, edytować, bawić się i co tam dusza zapragnie 🙂
Zacznę od zmian w strukturze kodu. Wydzieliłem, część logiki z obiektu Game i wrzuciłem ją do nowego obiektu – View. Jak sama nazwa wskazuje, chodzi o logikę odpowiedzialną za obraz/wygląd, a dokładniej za wyświetlanie tekstu w panelu/konsoli gry. Teraz obiekt Game, tworzy w sobie instancje obiektu View, która później używana jest do sterowania widokiem.
Do obiektu View trafiła, oczywiście, metoda printUpdate, która wyświetla nam główny tekst gry oraz ostrzeżenia. Do tego jest tu nowa metoda printItems, która wypisuje przedmioty znajdujące się w danym pomieszczeniu, ale o tym za moment.
Co do samej metody printUpdate, też przeszła kilka kosmetycznych zmian. Najważniejsza to taka, że teraz przypisuje dwie różne klasy ostrzeżeniom i standardowemu tekstowi gry. W ten sposób łatwiej za pomocą kodu odróżnić te dwa rodzaje powiadomień od siebie. Potrzeba tego wynikła przy implementacji wyświetlania przedmiotów.
Kolejna zmiana to struktura HTML dokumentu. Oprócz kontrolerów ruchu, w dokumencie znajdują się dwie nowe listy. pierwsza to akcje, które może wykonać gracz. Póki mam tylko „podnieś”, czyli akcję odpowiedzialną za podnoszenie przedmiotu. W przyszłości, pojawią się tu inne, np „przeszukaj” czy „zaatakuj”.
Druga lista to ekwipunek, czyli spis wszystkich przedmiotów, które gracz przy sobie posiada.
Obie listy wraz ze strzałkami ruchu zostały obleczone w div, żeby cały layout strony ładnie się opływał 🙂 efektem tego, jest to, że elementy DOMu, są trochę inaczej wyłapywane przez obiekty. Ale to wciąż proste selektory i metody jQuery, więc nikt nie powinien się zgubić.
Czas przejść do sedna tej aktualizacji, czyli przedmiotów. Gracz może teraz w lokacjach gry znaleźć różne mniej lub bardziej przydatne parafernalia.
Obiekty w rooms posiadają nowe pole items. To pole przechowuje tablicę, w której elementy są łańcuchami znaków. Każdy z nich reprezentuje jeden przedmiot, znajdujący się w danej lokacji. Przedmioty mogą zostać podniesione przez gracza poprzez akcję „podnieś” z listy akcji. Lista akcji, jest aktywowana podczas inicjalizacji obiektu Game. Metoda init wywołuje metodę activateActions, która za to odpowiada:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
this.activateActions = function(){ this.playerInterface.find("#akcje li.pickUp").on("click", function(){ var result = that.player.pickUp(); if (result){ that.board.find(".gameInfo").first().find(".itemSpan").remove(); var itemsHere = that.rooms[that.player.currRoom].items; if(!(itemsHere.length === 0)){ that.view.printItems(itemsHere); } that.playerInterface.find("#ekwipunek li").off(); that.playerInterface.find("#ekwipunek li").on("click", function(){ var item = jQuery(this).text(); that.showItemMenu(item); }); } else { that.view.printUpdate("Nie ma tu nic do podniesienia", "warning"); } }); }; |
Dzieje się tu parę rzeczy. Postaram się objaśnić całość w miarę sensownie. Najpierw metoda znajduje odpowiednią akcje (reprezentowana w dokumencie, przez element li z odpowiednia klasą) i przypisuje jej event on click.
Po kliknięciu „podnieś” wywołana zostaje odpowiednia funkcja. Funkcja ta wywołuje nową metodę obiektu Player, pickUp. pickUp Opiszę dokładniej za moment, póki co ważne jest, że jeżeli graczowi uda podnieść się przedmiot, zwracane jest true, jeżeli nie, zwracane jest false. Ten wynik przypisywany jest do zmiennej result. Jeżeli result jest prawdziwe, nazwa przedmiotu usuwana jest z widoku gry, a elementom listy ekwipunku przypisywany jest kolejny event on click. Jeżeli result jest równe false, oznacza to, że żadnego przedmiotu do podniesienia tu nie ma, a gra wyświetla odpowiedni komunikat.
Zanim przejdę do dokładniejszego opisu co dzieje się jak zmienna result równa jest true, muszę przedstawić metodę obiektu View – printItems.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
this.printItems = function(items){ var itemSpan = jQuery("<span class='itemSpan'>"); var text = ""; itemSpan.css({"color":"blue"}); if(items.length == 1) { text = " leży tu: "+items[0]; } else { text = " leżą tu: "; for(i=0;i<items.length;i++){ if(i != items.length-1) { text += items[i] + ", "; } else { text += items[i]; } } } itemSpan.text(text); this.board.find(".gameInfo").first().append(itemSpan); }; |
printItems, jest wywoływane w głównym obiekcie gry z argumentem równym tablicy items lokacji, w której aktualnie znajduje się gracz. Ta metoda, przekształca listę przedmiotów w tekst. Zależnie czy jest tylko jeden, przedmiot czy wiele, odpowiednio formułuje zdanie gramatycznie. Tak utworzony łańcuch znaków jest dopinany przy użyciu elementu span do pierwszego elementu o klasie gameInfo w konsoli gry. Czyli do opisu pomieszczenia w którym aktualnie znajduje się gracz.
Dodany tak element span jest usuwany z opisu gry po kliknięciu w akcję „podnieś”. Obsługuje to kod z metody activateActions głównego obiektu gry. Ten sam kod sprawdza następnie, czy w pomieszczeniu leżą jeszcze jakieś przedmioty. Jeśli tak, printItems jest wywoływane ponownie. W ten sposób tekst wypisujący przedmioty w aktualnej lokacji jest aktualizowany.
Jeszcze jedna rzecz dzieje się w activateActions. Po kliknięciu akcji „podnieś”, wszystkie elementy w liście item, otrzymują event on click. W wyniku którego odpalana jest funkcja, wywołująca metodę showMenu z argumentem, równym tekstowi klikniętego elementu.
Za chwilę opiszę o co chodzi z showMenu ale najpierw kolej na obiekt Player.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
function Player(rooms, inventory) { this.rooms = rooms; this.currRoom = "start"; this.inventory = inventory; this.items = []; this.pickUp = function(){ if(this.rooms[this.currRoom].items.length != 0){ this.items.push(this.rooms[this.currRoom].items[0]); inventory.append("<li class='item'>"+this.rooms[this.currRoom].items[0]+"</li>"); this.rooms[this.currRoom].items.shift(); return true; } else { return false; } }; this.dropItem = function(item){ var index = this.items.indexOf(item); this.items.splice(index,1); this.rooms[this.currRoom].items.push(item); }; } |
Tutaj również parę zmian. Podczas wywołania, obiekt przyjmuje dodatkowy argument. Wskaźnik na element HTML, listę, która będzie wyświetlać przedmioty aktualnie niesione przez gracza. Pierwszy argument to nie jest już tylko łańcuch znaków równy nazwie pierwszego pomieszczenia, a cały obiekt rooms. Do tego w obiekcie mamy nowe pole, tablicę items. Jest to struktura danych która będzie przechowywała informacje o aktualnie niesionych przez postać gracza przedmiotach. No i najważniejsze, czyli dwie nowe metody pickUp oraz dropItem.
pickUp wywoływane jest przez kliknięcie „podnieś”, o czym pisałem wyżej. Metoda ta pobiera tablicę items aktualnego pomieszczenia. Jeżeli tablica jest pusta, metoda zwraca false. W innym wypadku, metoda przypisuje, pierwszy element z tablic pomieszczenia do tablicy items obiektu Player. Ponad to do dokumentu dodany jest nowy element listy reprezentującej ekwipunek gracza. Na koniec pierwszy element tablicy items lokacji jest usuwany, a metoda zwraca wartość true.
Druga metoda obiektu Player – dropItem jest mniej skomplikowana. Opisze ją na koniec, najpierw muszę przeskoczyć do ostatniej z nowości, które chce pokazać mianowicie metody obiektu Game – showMenu.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
this.showItemMenu = function(item){ var html = "<ul id='itemMenu'><li>upuść</li><li>użyj</li><ul>"; var elem = this.playerInterface.find("#ekwipunek li:contains('"+item+"')"); elem.after(html); this.playerInterface.find("#itemMenu").mouseleave(function(){ that.playerInterface.find("#itemMenu").remove(); }); this.playerInterface.find("#itemMenu li:nth-child(1)").on("click",function(){ that.player.dropItem(item); elem.remove(); that.playerInterface.find("#itemMenu").remove(); var itemsHere = that.rooms[that.player.currRoom].items; that.board.find("p.gameInfo").first().find(".itemSpan").remove(); if(!(itemsHere.length === 0)){ that.view.printItems(itemsHere); }; }); this.playerInterface.find("#itemMenu li:nth-child(2)").on("click",function(){ console.log("używam: "+item); that.playerInterface.find("#itemMenu").remove(); }); }; |
Ta metoda wywoływana jest przez kliknięcie na nazwę przedmiotu w liście reprezentującej ekwipunek gracza. Tworzy ona nowy element ul zawierający dwa elementy li. Wartości tekstowe tych elementów to „upuść” oraz „użyj”. Nie trudno się chyba domyślić do czego będą służyć 🙂 Stworzona skryptowo lista dopinana jest za elementem, mającym wartość tekstową równą nazwie przedmiotu, który został kliknięty. Jeżeli gracz zjedzie myszką, z tak utworzonej listy, jest ona usuwana z dokumentu.
Po utworzeniu listy, metoda showMenu, przypisuje pierwszemu i drugiemu elementowi event on click. Element „użyj” jest wciąż w budowie, więc póki co, kliknięcie go utworzy tylko wpis w konsoli, mówiący jaki przedmiot został kliknięty. Za to pierwszy element – „upuść”, już działa.
Jeżeli gracz kliknie „upuść” w menu kontekstowym przedmiotu, kod wykona następujące czynności. Wywołana zostanie metoda dropItem obiektu Player, z argumentem równym nazwie klikniętego wcześniej przedmiotu, a sam element zostanie usunięty z listy reprezentujące ekwipunek. Z dokumentu usunięte zostaje również menu przedmiotu. Ostatecznie skrypt podmienia element itemSpan na taki, który zawiera zaktualizowaną listę przedmiotów.
Na koniec zostało mi już tylko opisanie metody dropItem obiektu Player. Ta prosta metoda, odnajduje w polu items obiektu gracza łańcuch znaków reprezentujący odrzucony przedmiot i usuwa go. Następnie ten sam łańcuch dodany jest do pola items aktualnej lokacji gracza.
Trochę w tym poście „skakałem” po kodzie, ale nie chciałem powtarzać tego co opisałem już wcześniej. A zarazem miałem też na celu, aby logicznie wszystko było spójne. Mam nadzieję, że mi się to udało. W razie czego chętnie odpowiem na wszelkie pytania 🙂 Następny krok to dodanie możliwości używania przedmiotów. To powinno „ożywić” grę.