I tak oto z popołudniowego dłubania i eksperymentowania z kodem narodził się nowy pomysł – Gra tekstowa RPG. Póki co, projekt jest w powijakach, ale czuję, że może być przy nim sporo zabawy i satysfakcji. Tym razem odchodzę od elementu canvas. Przyczyna: nie ma tu żadnej grafiki. A to dlatego, że tematem projektu jest oldschoolowa gra przygodowa, zwana czasem tekstówką. Tego typu gry, są tak stare, że nawet ja ledwo pamiętam konkretne tytuły 🙂
Przy tworzeniu gry, pomagam sobie biblioteką jQuer. Jest ona nie zastąpiona, jeśli chodzi o manipulowania DOM-em. Wczesna wersja beta gry, do pogrania tutaj.
Idea mojej gry jest prosta. Gracz porusza się po wirtualnym świecie przy pomocy strzałek, symbolizujących kierunki geograficzne, a program wyświetla na ekranie tekst opisujący ten świat. Po najechaniu myszką na jedna ze strzałek, grafika podświetli się. Jeżeli gracz może poruszyć się w tym kierunku, strzałka zrobi się zielona, w przeciwnym wypadku, kolor podświetlenia będzie czerwony.
Jeżeli gracz kliknie na strzałkę, reprezentującą prawidłowy kierunek, wykona ruch. Jego postać przeniesie się do innej lokacji, której opis wyświetli się w głównym oknie gry.
Nie będę opisywał całego kodu HTML i CSS. Można go bez problemu zobaczyć w źródle strony. Skupię się tylko na najważniejszych elementach programu. Tych póki co mam dwa: obiekt rooms oraz obiekt Game. Stworzyłem też obiekt Player, ale póki co posiada on tylko jedno pole.
Zacznijmy od rooms:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
var rooms = { kuchnia: { desc: "Jesteś w kuchni. Panuje tu półmrok, na środku pomieszczenia stoi stary drewniany stół, pokryty kurzem i pajęczynami. Na stole leżą naczynia i sztućce, jakby porzucone w trakcie posiłku. Z kuchni są dwa wyjścia, jedno na południe i jedno wschód.", pn: null, pd: "start", wsch: "sypialnia", zch: null, }, start: { desc: "Jesteś na korytarzu. Stare deski skrzypią, gdy niepewnie stawiasz kroki. Na końcu korytarza dostrzegasz uchylone drzwi", pn: "kuchnia", pd: null, wsch: null, zch: null, }, sypialnia: { desc: "Jesteś w sypialni. Przez okno wpadają czerwone promienie zachodzącego słońca, przy sciane stoi wiekowe łóżko z baldachimem, zasłonięte pożółkłą firanką. Przez moment wydaje Ci sie że widzisz ruch za zwiewnym materiałem, ale to chyba tylko twoja wyobraźnia. Półka z książkami stojąca przy południowej ścianie wydaje Ci sie podejrzana. Po dłuższych oględzinach okazuje się, że skrywa tajne przejscie. Możesz też iść na zachód, do kuchni.", pn: null, pd: "skrytka", wsch: null, zch: "kuchnia", }, skrytka: { desc: "Znalazłeś tajną skrytkę! trochę tu ciasno. Na podłodze widzisz wydrapane dziwne znaki, oprócz tego pomieszczenie jest puste. Jest tu sekretne przejście prowadzące spowrotem na korytarz. Da sie je otworzyć tylko z tej strony.", pd: null, wsch: null, zch: "start", pn: "sypialnia", } }; |
Ten obiekt nie posiada konstruktora, to po prostu zestaw danych. Reprezentują one spis wszystkich lokacji w grze. Pola w obiekcie rooms, to inne obiekty. Każdy przechowuje informacje o danym miejscu w grze. Obiekty lokacji posiadają pięć pól: desc, pd, wsch, zch oraz pn. desc zawiera opis danej lokacji, pozostałe cztery to kierunki geograficzne. Ich wartości, to nazwy pomieszczeń do których dany kierunek prowadzi. Jeżeli taka wartość wynosi null oznacza to, że z danej lokacji, nie można iść w tym kierunku.
W ten sposób powstała sieć lokacji. Może nie widać tego od razu, ale ten obiekt to mapa mojej gry. Warto też zwrócić uwagę, że nie każda droga, jest dwukierunkowa. Możemy przejść z tajnego pokoju na korytarz, ale w drugą stronę już nie. To dlatego, że wartość zch w obiekcie skrytka wskazuje na korytarz, ale wartość wsch obiektu korytarz wynosi null.
W przyszłości, do tych obiektów, można dodać więcej informacji. Na przykład przedmioty znajdujące się w danej lokalizacji lub przebywające tu postaci czy też potwory.
Następnie deklaruję konstruktor obiektu Player.
1 2 3 |
function Player(currRoom) { this.currRoom = currRoom; }; |
Ten obiekt ma na celu przechowywać wszystkie informacje o postaci, sterowanej przez gracza. Póki co jest niewielki, jedyne pole, które posiada, służy do przetrzymywania łańcucha znaków, będzie to nazwa lokacji w której aktualnie znajduje się potsać. Używając tego pola, program, będzie odnajdywał odpowiedni obiekt, po nazwie, w obiekcie rooms.
W przyszłości planuje dodać tutaj więcej pól. Na przykład punkty życia lub many, noszone przedmioty, statystyki bądź umiejętności.
Ostatni obiekt to Game. Tradycyjnie, jest to element, który kontroluje logikę całej gry. A oto kod:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
function Game(rooms, board, controls) { var that = this; this.board = board; this.rooms = rooms; this.controls = controls; this.player = new Player("start"); this.init = function() { this.printUpdate(this.rooms[this.player.currRoom].desc, "normal"); this.activateButtons(); }; this.printUpdate = function(text, type){ if(type!="warning"){ board.children().css({"color":"grey"}); } var para = jQuery("<p class='gameInfo'>"); switch(type){ case "normal": para.css({"color":"white"}); break; case "warning": para.css({"color":"red"}); break; } para.css({"display":"none"}); para.text(text); this.board.prepend(para); para.toggle(400); }; this.activateButtons = function(){ var direction; this.controls.on("mouseenter", function(){ direction = jQuery(this).attr("src"); newDirection = direction.split(".")[0]; if(that.rooms[that.player.currRoom][newDirection] != null){ jQuery(this).attr("src", newDirection+"T.png"); } else { jQuery(this).attr("src", newDirection+"F.png"); } }); this.controls.on("mouseout", function(){ jQuery(this).attr("src", direction); }); this.controls.on("click", function(){ that.changeRoom(that.rooms[that.player.currRoom][newDirection]); jQuery(this).trigger("mouseout"); jQuery(this).trigger("mouseenter"); }); }; this.changeRoom = function(newRoom){ if(newRoom != null){ this.player.currRoom = newRoom; this.printUpdate(that.rooms[that.player.currRoom].desc, "normal"); } else { this.printUpdate("Nie możesz iść w tym kierunku", "warning"); } }; } |
Konstruktor Game przyjmuje trzy argumenty rooms, board oraz controls. Pierwszy to obiekt z listą lokacji, dwa kolejne to odnośniki do elementów HTML na stronie. board do diva, na którym będą wyświetlane przez gre teksty a controls do obrazków strzałek.
Na początku deklaruję zmienną that do której przypisuję wartość this obiektu. Będzie potrzebna przy obsłudze zdarzeń. Następne trzy pola otrzymują wartości parametrów przekazanych podczas tworzenia obiektu. Ostatnie pole, player, będzie instancją obiektu Player, któremu jako argument, przekazujemy wartość „start”. Zakładam, że tak będzie zawsze nazwana początkowa lokacja w obiekcie rooms. Chociaż przyznam szczerze, trochę mi zgrzyta to rozwiązanie, więc pewnie jeszcze popracuje nad tym kawałkiem.
Następnie mamy metody. Póki co tylko cztery. Dla czytelności opiszę je od myślników.
- init – Metoda, która odpala całą grę. Dzieją się tu dwie rzeczy, najpierw wywoływana jest metoda printUpdate, która wypisze opis pomieszczenia startowego, a następnie metoda activateButtons, która spowoduje, że strzałki zaczną reagować na działanie gracza.
- printUpdate – Ta metoda, odpowiedzialna jest za wypisywanie opisów na planszy gry. przyjmuje jako argumenty dwie wartości (Oba parametry przekazywane są w formie łańcuchów znaków). Pierwsza wartość to tekst, który ma wypisać, a druga to typ powiadomienia. Jeżeli typ powiadomienia to niew „warning” to metoda wyszarza wszystkie poprzednie powiadomienia, tak aby gracz, wiedział, które jest aktualne. Następnie tworzony jest nowy paragraf HTML do którego przypisuję odpowiedni styl (w zależności od typu powiadomienia, będzie ono miało różny kolor). Do paragrafu dodaje też styl display: none, ponieważ chcę aby ładnie się animował na ekranie. Dopiero po wstawieniu paragrafu do DOMu, na plansze, odpala się animacja, która pokazuje tekst.
- activateButtons – Ta metoda aktywuje przyciski. Po najechaniu na strzałkę, zapisywany jest adres obrazka. Pliki mają takie same nazwy jak kierunki w obiektach lokacji. Po odcięciu rozszerzenia pliku, program wie, na który kierunek najechał gracz. Następuje sprawdzenie czy jest to kierunek prowadzący z obecnej lokacji gracza do innej lokacji czy może trafia na wartość null. Obrazek podmieniany jest na odpowiednie podświetlenie. Jeśli gracz zjedzie myszką ze strzałki, to obrazek otrzymuje z powrotem oryginalna wartość src. Jeżeli gracz kliknie na którąś ze strzałek, zostanie odpalona metoda changeRoom, której przekazany jest łańcuch znaków, na który wskazuje wybrany kierunek z aktualnego położenia gracza. Następnie symulowane są wyjście myszki ze strzałki, i powrót. Robię to po to aby po zmianie lokalizacji kolor strzałki pokazywał aktualną możliwość ruchu.
- changeRoom – Ta metoda działa bardzo prosto, pobiera jeden argument, łańcuch znaków. Jeżeli ten łańcuch nie jest równy null to przypisywany jest instancji obiektu Player wewnątrz Game. Następnie zostaje wywołana metoda printUpdate z argumentem równym polu desc nowej lokacji gracza. Jeżeli przekazany argument jest równy null, również wywoływana jest printUpdate tym razem wypisując informacje o tym, że gracz, nie może poruszyć się w danym kierunku. Lokacja gracza, nie zmienia się
No i na tę chwile to wszystko. Pozostaje tylko utworzyć instancje Game i wywołać jej metodę init. Tak jak pisałem, to dopiero pierwszy szkic tej aplikacji. Wersja ta była napisana trochę „na czuja”, bez konkretnego planu. Wszystko możliwe, że gdy zacznę zastanawiać się nad dodaniem nowych funkcji, wyjdą dziury w tym setupie.
Nowe funkcje na pewno się przydadzą, w obecnej formie, to nawet nie jest jeszcze gra. Można by ewentualnie stworzyć interaktywną nowelę 🙂 Przede wszystkim brakuje celu, ale tym zajmę już niedługo 🙂