Czas na kolejną aktualizację tekstówki pisanej w Angularze. Tym razem do gry dodane zostały przedmioty. Gracz może je znaleźć pozostawione w niektórych lokacjach. Jest też szansa, że pokonane potwory zostawią po sobie jakiś łup, który będzie można zebrać.
Zdobyte przedmioty można przenieść z jednej lokacji do drugiej i podnieść później.
Aktualną wersję gry przetestować można klikając obrazek powyżej. Jak zawsze, na githubie dostępny jest też pełny kod.
Będę skupiał się na najważniejszych zmianach ponieważ prawie cały projekt usiany jest drobnymi ulepszeniami/poprawkami. Na pewno nie uda się wspomnieć o wszystkim. Zachęcam do przestudiowania plików źródłowych na własną rękę. Jeśli coś będzie nie jasne, pytaj w komentarzach. Na wszystkie pytania postaram się odpowiedzieć 🙂 .
Nie ma co owijać w bawełnę, przechodzę prosto do kodu. Pierwszą zmianą, która od razu rzuca się w oczy są nowe widoki w prawym panelu gry. Obsługa tych widoków zawarta jest w pliku config.js. Oto jego treść:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
angular.module('textRPG'). config(['$routeProvider', function config($routeProvider) { $routeProvider. when('/player', { templateUrl: 'src/app/stats/stats.html', controller: 'statsController', controllerAs: 'ctrl', }). when('/inventory', { templateUrl: 'src/app/inventory/inventory.html', controller: 'inventoryCtrl', controllerAs: 'ctrl', }). when('/equipment', { templateUrl: 'src/app/equipment/equipment.html', controller: 'equipmentCtrl', controllerAs: 'ctrl', }). otherwise('/player'); } ]); |
W grze istnieją trzy widoki. Są to kolejno player, inventory oraz equipment. To wewnątrz config deklaruję gdzie znajdują się szablony html oraz kontrolery dla każdego z nich. Jak widać i tutaj korzystam ze składni controllerAs, dzięki czemu nie będę musiał korzystać ze scope.
Kontrolery oraz szablony znajdują się w osobnych podfolderach w głównym folderze projektowym. Póki co jedynie inventory posiada jakąś konkretną zawartość, dwa pozostałe to tylko sztuczna treść.
Do zmiany widoku używam prostej funkcji changeView, znajdującej się w głównym kontrolerze:
1 2 3 |
this.changeView = function(view){ $location.url("/"+view); } |
Nie ma tu nic skomplikowanego. Metoda przyjmuje jeden parametr, a jej wyłączne działanie to zmiana urla, za pomocą angularowego serwisu location. changeView wywoływane jest przez kliknięcie nowych przycisków znajdujących się w głównym pliku html, zaraz pod panelem ze zmiennym widokiem:
1 2 3 4 5 6 7 8 9 |
<div> <div id="interface" ng-view> </div> <input type="button" value="Stats" ng-click="main.changeView('player')"> <input type="button" value="Inventory" ng-click="main.changeView('inventory')"> <input type="button" value="Equiped" ng-click="main.changeView('equipment')"> </div> </div> |
I tutaj sprawa jest bardzo prosta. Klikniecie przycisku wywołuje metodę changeView z odpowiednim parametrem. Dzięki tym nowym fragmentom w kodzie, gracz może bez problemu zmieniać widoki na takie które aktualnie go interesują.
Skoro to jest już jasne, pora zaprezentować pierwszy gotowy widok czyli inventory. Jest to część aplikacji, która odpowiada za to, jakie przedmioty gracz ma akurat przy sobie. Tak wygląda zawartość szablonu url do widoku inventory:
1 2 3 4 5 6 7 |
<p>Your gold: {{ctrl.gold}}</p> <p>Items in your Backpack:</p> <ul id='inventory'> <li ng-repeat="item in ctrl.inventory track by $index"> {{item.amount}} {{item.name}} <input ng-disabled="main.checkAction()" type="button" value="Use" ng-click=""> <input ng-disabled="main.checkAction()" type="button" value="Drop" ng-click="ctrl.drop(item.name)"> </li> </ul> |
Znajdują się tu dwa główne elementy pobierające dane z kontrolera przypisanego do widoku w pliku config.js. Pierwszy z tych elementów wypisuje ilość złota, posiadanego przez gracza. Drugi element to lista, w której znajduje się element li zawierający dyrektywę ng-repeat. Angular stworzy element listy dla każdego elementu znajdującego się w obiekcie inventory. Elementy te to kolejne obiekty, przypisane do klucza równego nazwie przedmiotu. Te podobiekty zawierają dwa pola, pierwsze z nich to nazwa przedmiotu a drugie to ilość posiadana w tej chwili przez postać. Chociaż taki obiekt nie pojawia się dosłownie w kodzie, przykładowo wyglądał by tak:
1 2 3 4 |
"troll tooth" : { "name": "troll tooth", "amount" : 3 } |
dyrektywa ng-repeat, wyświetliła by takie pole głównego obiektu inventory jako 3 troll tooth. Oznaczałoby to, że gracz posiada przy sobie 3 trollowe zęby 🙂 .
Oprócz informacji o tym jaki przedmiot posiada gracz i w jakiej ilości, w elemencie li znajdują się też dwa przyciski. Pierwszy z nich drop, wywołuje funkcję pozwalającą na wyrzucenie przedmiotu na ziemie, drugi use, póki co nic nie robi 🙂 (jego funkcjonalność dodam w kolejnej aktualizacji) .
Metoda drop, znajduje się wraz z innymi informacjami w kontrolerze przypisanym do widoku. Znajduje się on w pliku inventotyCtrl.js, którego zawartość wygląda tak:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
angular.module('textRPG') .controller('inventoryCtrl', ['playerService',function(playerService){ this.inventory = playerService.inventory; this.gold = playerService.gold; this.player = playerService; this.drop = function(item){ if(this.inventory[item].amount > 1){ this.inventory[item].amount -= 1; } else { delete this.inventory[item]; } this.player.drop(item); } }]); |
Jak widać, nie ma tu zbyt wiele kodu. Póki co jedyne trzy zmienne pobierane są bezpośrednio z serwisu player. Są to obiekt zawierający pod obiekty reprezentujące posiadane przez gracza przedmioty, pole z informacją ile złota posiada gracz oraz bezpośredni wskaźnik na serwis gracza. Potrzebuje je tutaj określić, aby móc dostać się do tych informacji w widoku.
W kontrolerze jest też jedna funkcja drop. Przyjmuje ona jeden parametr, nazwę wyrzucanego przedmiotu. Metoda sprawdza, czy gracz posiada więcej niż jeden przedmiot tego rodzaju, jeśli tak, ilość ta zmniejszana jest o jeden. W przeciwnym wypadku, pole przypisane do klucza przedmiotu usuwane jest całkowicie.
Na koniec wywoływana jest metoda drop serwisu player. Powoduje ona, że gracz wykona akcję wyrzucania przedmiotu. Działa to dokładnie tak samo jak atak czy opuszczenie lokacji, więc nie będę tego dokładnie opisywał. Jedyna nowość to to, że wyrzucony przedmiot trafia do specjalnej tablicy, którą pętla gry sprawdza na bieżąco, jeżeli coś w niej znajdzie, przypisuje ten przedmiot do aktualnej lokacji.
Zanim przejdę do tego jak gracz może podnieść przedmioty, opiszę mechanizm powodujący, że pokonani przeciwnicy pozostawiają po sobie łup.
Pierwsza ważna zmiana znajduje się w serwisie monstersService. Jest to obiekt o nazwie monsterLoot i wygląda tak:
1 2 3 4 |
this.monsterLoot = { 'goblin': [{'item':'Goblin Ear','chance':0.9},{'item':'Rock','chance':0.5}], 'troll': [{'item':'Troll Tooth','chance':0.5},{'item':'Health Potion','chance':0.5},{'item':'Warhammer','chance':0.3}], } |
Klucze pól w tym obiekcie to nazwy potworów, które można spotkać w grze. Przypisane do nich wartości to tablice, których elementy to obiekty. Każdy obiekt reprezentuje jeden przedmiot, który potwór może ‚wyrzucić’ oraz jaka jest na to szansa. Przykładowo goblin może pozostawić po sobie ucho goblina (dość spora szansa) oraz kamień (już mniejsza szansa, ale wciąż dość wysoka).
Generowanie łupu ma miejsce w metodzie znajdującej się w tym serwisie, i która wygląda tak:
1 2 3 4 5 6 7 8 9 10 |
this.generateLoot = function(monster){ var lootArray = []; for (var i = 0; i < this.monsterLoot[monster].length; i++) { var currLootTable = this.monsterLoot[monster][i]; if(Math.random() < currLootTable.chance){ lootArray.push(currLootTable.item); } } return lootArray; } |
I tutaj nie powinno być żadnego zaskoczenia. Metoda w parametrze otrzymuje informacje o tym jaki potwór został pokonany i przechodzi przez odpowiednią tablicę w obiekcie monsterLoot. Następnie losowo sprawdzane jest czy potwór dany przedmiot pozostawi. Jeżeli wylosowana liczba będzie mniejsza niż szansa na dany łup, trafia on do odpowiedniej tablicy, która później przekazywana jest poza funkcje. Zawartość tej tablicy trafia potem do odpowiedniego pola widocznego przez główną pętle gry.
Z tego miejsca, pętla przenosi przedmioty, a właściwie tylko ich nazwy do nowego pola w reprezentującym aktualną lokację obiekcie. działa to prawie identycznie jak przypisywanie potworów do lokacji, więc też pominę ten kod.
Ostatni krok to dodanie do głównego widoku odpowiednich przycisków, które pozwolą na podniesienie leżących w lokacji gracza przedmiotów. I ten kod powinien wyglądać znajomo:
1 2 |
<p ng-hide="main.getCurrItems().length === 0">items:</p> <input ng-disabled="main.checkAction()" ng-repeat="item in main.getCurrItems() track by $index" type="button" value="Get {{item}}" ng-click="main.pickUp(item)"> |
Metoda getCurrItems zwraca listę znajdujących się w danej lokacji przedmiotów. Dla każdego z nich tworzony jest nowy przycisk. Naciśnięcie go wywoła funkcję pickUp, której działanie jest odwrotnością funkcji drop. Wywołuje to akcje z serwisu gracza, której efektem jest przerzucenie przedmiotu z obiektu lokacji do inwentarza gracza.
Jeżeli gracz posiada już przedmiot danego typu, jego ilość zwiększana jest o jeden. Jeżeli takiego przedmiotu nie ma jeszcze w inwenatarzu, do obiektu inventory dodawane jest nowe pole. Nie będę wklejał odpowiedzialnego za to kodu, ponieważ są to tak naprawdę drobne funkcję rozsiane po serwisach. Zrozumienie ich nie powinno sprawić nikomu trudności. Zachęcam do przestudiowania kodu na własną rękę. Na wszelki wypadek napiszę jeszcze raz, jeżeli coś jednak nie będzie do końca jasne (może mi się tylko wydaje, że piszę zrozumiały kod 🙂 ), daj znać w komentarzu. Chętnie wyjaśnię wszelkie nieścisłości.
No i to wszystko na dziś. Kolejny krok to dodanie możliwości tych przedmiotów, ale to już w kolejnym wpisie.
Jeżeli chcesz być na bieżąco z postami na blogu zachęcam do polubienia mojej strony na facebooku. Zawsze zamieszczam tam informacje o wszystkich nowościach. Jest to też dobre miejsce na kontakt ze mną. Na wszystkie pytania zawsze odpowiem :).