Ostatnio skończyliśmy na
etapie poruszania postacią. Powinniśmy wrócić jeszcze do tego zagadnienia
ponieważ ruch kamery odbiega od tego jak byśmy sobie życzyli. Środek kamery
przemieszcza się niezgodnie z bohaterem w inny obszar ekranu. Potrzeba na
początku znaleźć przyczynę takiego zachowania.
Odpowiedzią okazała się
różnica pomiędzy obszarem debugowania bohatera, a jego pozycją rzeczywistą.
Wniosek przychodzi z prostego faktu że pobieramy współrzędne bohatera i tam
ustalamy pozycję kamery. Wystarczy odpowiednio przeskalować pozycje kamery, aby
te obszary się zgadzały
camera.position.x = player.b2dBody.getPosition().x / 1.3f; camera.position.y = player.b2dBody.getPosition().y / 1.3f;
Czas na wiosenne porządki
W klasie PlayScreen
obecnie znajduje się mnóstwo kodu który można trochę lepiej zorganizować, aby uzyskać
więcej przejrzystości. Co można zrobić to całe tworzenie world przy użyciu
tiledMap przenieść do osobnej klasy. Podobnie jak ma to miejsce w przypadku
bohatera BatteryHero, całość definicji znajduje się w osobnej klasie, a my
tylko tworzymy obiekt tego typu w klasie PlayScreen.
Tworzymy nowy pod pakiet
Tools, a w wewnątrz niego nową klasę B2WorldCreator. Wewnątrz tworzymy pusty
konstruktor z dwoma przekazanymi parametrami (World world, TiledMap map).
public B2WorldCreator(World world, TiledMap map) { }
World jest nam potrzebny aby mieć możliwość mieć dostęp do zarządzania obiektami. TiledMap jest oczywiście wczytaną mapą z pliku na której będziemy operować.
Przechodzimy do klasy
PlayScreen i wycinamy wszystko począwszy od linii kodu widocznej niżej, aż do
końca pętli w której ustawaliśmy właściwości warstwy podłoża mapy
BodyDef bdef = new BodyDef(); … for (MapObject object : map.getLayers().get(2).getObjects().getByType(RectangleMapObject.class)) { … }
Wklejamy to w konstruktor B2WorldCreator, a w klasie PlayScreen w miejscu z którego wycięliśmy kod tworzymy obiekt
new B2WorldCreator(world, map);
Całe te manewry powoduje że cała zawartość klasy B2WorldCreator jest w tym momencie wykonywana. Jednak działanie pozostaje takie same jak przed zmianą z małą różnicą innego uporządkowania kodu. Przy większej ilości kodu lepszy komfort pracy jeżeli to dobrze zorganizujemy będzie jeszcze bardziej odczuwalny.
Zwalnianie zasobów
Ponieważ gry używają całą
masę zasobów (obrazki, dzwięki itp.) należy pomyślec nad ich zwalnianiem w
momencie gdy nie są potrzebne. Przykładem może być sytuacja gdy minimalizujemy
grę, wtedy raczej nie chcemy aby coś ciężkiego wykonywało się dalej w tle. W
przypadku małej ilości pamięci ram może niepotrzebnie spowalniać telefon czy
inne urządzenie. Pozbywamy się obiektów manualnie w metodzie dispose wskazując
kolejno na obiekty do odwołania. W linku można
poczytać jakie zasoby wymagają zwalniania. Więc kolejno
@Override public void dispose() { map.dispose(); renderer.dispose(); world.dispose(); b2dr.dispose(); }
Przy czym zostało nam zwolnienie jeszcze hud, ale nie dokonamy tego tak od razu ponieważ nie ma zaimplementowanej wewnątrz odpowiedniej do tego metody. Wszystkie powyższe elementy jako gotowe klocki libGdx do budowania posiadały zaimplementowane metodę dispose(). Nasz hud który sami utworzyliśmy jednak jej nie ma.
Możemy w klasie Hud
wrzucić
public void dispose() { stage.dispose(); }
Co spowoduje zniszczenie obiektu stage wraz z wszystkimi jego zasobami.
Interfejs Disposable
Albo można użyć do tego
interfejsu. Interfejs zawiera wewnątrz zdefiniowane metody, ale bez danych.
Ponieważ metoda dispose jest używana w wielu miejscach w całości biblioteki,
twórcy wrzucili ją właśnie jako interfejs który możemy wielokrotnie użyć.
Wewnątrz znajduje się jej definicja.
Najpierw usuwamy więc
poprzednio utworzoną metodę dispose()
W klasie Hud po nazwie
dopisujemy implements i nazwę interfejsu. Teraz wygląda to tak
public class Hud implements Disposable {
W tym momencie powinno podkreślić nam linię początku klasy co by wskazywało na błąd. Najeżdżając myszą na kod wyświetli chmurkę z podpowiedzią o konieczności implementacji metody dispose(). Klikamy tym razem na linii, tak aby wyświetliła się czerwona żarówka z sugestiami rozwiązania problemu. Wybieramy Implement methods i zaznaczamy metodę dispose.
stage.dispose();
Teraz w PlayScreen
dopisujemy w miejscu zwalniania zasobów dodatkowo
hud.dispose();
Można przetestować,
wszystko działa jak poprzednio.
Można również zadać sobie
pytanie jaka idea stoi za stosowaniem interfejsów. Ze znalezionych informacji
wynika, że tworzy się je na etapie modelowania projektu. Więc ustalasz sobie
jak powinny wyglądać używane później metody w konkretnym programie. Dzięki temu
w późniejszych fazach jest o wiele łatwiej się poruszać po całości kodu i nie
trzeba pamiętać nazw metod.
Inne obiekty
Kolejnym punktem do osiągnięcia
jest zrobienie grupy przedmiotów z którymi gracz może przeprowadzać interakcje.
Zaczniemy od stworzenia małych tabliczek z wyrazami obcymi, które można
zbierać. Późnij dorobimy do tego okno dialogowe gdzie będzie trzeba coś z tym
zrobić.
Pora więc dodać kolejną warstwę do mapy Tiled przechodzimy więc do edytora. Dodajemy warstwę Tile Layer oraz Object Layer, obie można nazwać wordnote. I powtarzamy te same kroki jak wcześniej dla ground, niezbędne czynności opisane w obu tych postach link i link2. Dołączam również obrazek który został wykonany na potrzeby projektu w programie Inkscape
Można używać bez
ograniczeń, lub jeżeli wolisz znajdź/stwórz coś własnego.
Efekt
Gdy mamy już to gotowe należy
dodać ponownie do folderu assets projektu nowo wyedytowany plik untitled.tmx
nadpisując stary. Nie należy zapomnieć o także dodaniu nowego pliku graficznego
który użyliśmy na mapie tiled.
Możliwe problemy z plikiem tiled po edycji
Jeżeli w tym miejscu po
uruchomieniu pokaże wam się wyjątek GdxRuntimeException oznacza to źle wskazane
miejsce pliku obrazka. Otwieramy więc unitiled.tmx i edytujemy
Widzimy że źródło obrazka
znajduje się gdzieś poza projektem. Zmieniamy na same “note.png”, w tym
momencie będzie odwoływać się do tego samego folderu assets w którym jest plik
tiled.
<image source="note.png" trans="ff00ff" width="70" height="70"/>
Kolejną ważną rzeczą jest
aby przy zapisywaniu pliku tiled mieć w edytorze zahaczone wszystkie warstwy. Inaczej efekt jak poniżej
Iterujemy po raz drugi
Aby uwzględnić dodanie
nowych tabliczek na mapie, należałoby ponownie przeiterować pętlą for po odpowiedniej
warstwie. Całość znajduje się w klasie B2WorldCreator. Po skopiowaniu wystarczy
podmienić jedynie nr tej warstwy. U mnie patrząc w plik untiled.tmx warstwa
obiektów wordnote jest warstwą 5. Należy pamiętać ze liczymy od 0, więc
prawidłowo odwoływać powinniśmy się do warstwy 5-1 = 4. Po uruchomieniu działa
tak jak powinno, następuje kolizja również z notkami.
Abstrakcyjne interaktywne obiekty
Zakładamy że będziemy
tworzyć w przyszłości więcej warstw obiektów o podobnym zachowaniu. Jak
pamiętamy pierwsza pętla odpowiadała za podłoża które są raczej statyczne.
Reszta warstw chcemy raczej aby dawało coś się z nimi zrobić. Mówi się na takie
warstwy że są to warstwy interaktywne. Jako że warstwy interaktywne będą
posiadać duża część kodu powtarzalną, można wziąć ten fragment i przenieść do
osobnej klasy. Dzięki temu nie trzeba tego pisać za każdym razem, a jedynie
stworzyć taki obiekt który będzie fragmentem składowym.
Tworzymy więc nową klase
w pakiecie sprites i nazywamy ją InteractiveTileObject.
public abstract class InteractiveTileObject { private World world; private TiledMap map; private TiledMapTile tile; private Rectangle bounds; private Body body; public InteractiveTileObject(World world, TiledMap map, Rectangle bounds) { this.world = world; this.map = map; this.bounds = bounds; } }
Nic to nowego po prostu będą to takie same obiekty podobnie jak to było w klasie B2WorldCreator. Można z resztą się temu przyjrzeć. Część z nich zdefiniujemy tutaj, a część zostanie przekazana przez konstruktor. Całą zawartość pętli gdzie wskazaliśmy warstwę wordnote wycinamy i przenosimy do konstruktora InteractiveTileObject. Dodtkowo musimy ponownie utworzyć nowe obiekty
BodyDef bdef = new BodyDef(); PolygonShape shape = new PolygonShape(); FixtureDef fdef = new FixtureDef();
Miejsca gdzie używamy
rect zastępujemy bounds przekazanymi przez konstruktor. Tu po prostu inaczej to
nazwaliśmy.
Gotowa klasa wygląda tak
public abstract class InteractiveTileObject { private World world; private TiledMap map; private TiledMapTile tile; private Rectangle bounds; private Body body; public InteractiveTileObject(World world, TiledMap map, Rectangle bounds) { this.world = world; this.map = map; this.bounds = bounds; BodyDef bdef = new BodyDef(); PolygonShape shape = new PolygonShape(); FixtureDef fdef = new FixtureDef(); bdef.type = BodyDef.BodyType.StaticBody; bdef.position.set((bounds.getX() + bounds.getWidth() / 2) / WordCharger.PPM, (bounds.getY() + bounds.getHeight() / 2) / WordCharger.PPM); body = world.createBody(bdef); shape.setAsBox(bounds.getWidth() / 2 / WordCharger.PPM, bounds.getHeight() / 2 / WordCharger.PPM); fdef.shape = shape; body.createFixture(fdef); } }
Dodając przy nazwie slowo kluczowe abstract, powoduje to że nie może być ona reprezentowana pod postacią obiektów. Ponieważ obiekt który wyświetlamy zawiera znacznie więcej parametrów, nie powinno dać się go wyświetlać. Klasa abstrakcyjna jest jakby częścią innej klasy która może już przyjmować jakąś postać. Inaczej mówi się że jest uogólnieniem pewnej grupy obiektów, co znaczy tyle że zawiera zestaw parametrów które są wspólne dla innych grup.
Klasa WordNote rozszerzająca klasę abstrakcyjną
Stwórzmy teraz obiekt
który będzie zawierał właśnie tą klasę abstrakcyjną. Zrobić to chcemy dla
tabliczek z słówkami. W pakiecie sprites tworzymy nową klasę WordNote
public class WordNote extends InteractiveTileObject { public WordNote(World world, TiledMap map, Rectangle bounds) { super(world, map, bounds); } }
Słowem extends mówimy, że cała ta klasa z części składa się z abstrakcyjnej klasy InteractiveTileObject. W konstruktorze przekazane zostały parametry z których będziemy korzystać. Metodą super przekazujemy te parametry dalej, bo tak naprawdę to klasa InteractiveTileObject ich wymagała. To ona jest odpowiedzialna za tą część. Tylko tyle w tej klasie. Widać teraz jak szybko można tworzyć kolejne takie klasy.
Teraz petla powinna być pusta.
Pora stworzyć wewnątrz nowy obiekt
new WordNote(world, map, rect);
To wszystko. Testujemy I działa jak poprzednio. Efekt niby ten sam, ale kolejne tworzenie innych obiektów będzie błyskawiczne.
https://github.com/KrzysztofPawlak/WordCharger/tree/wpis11
Dzisiaj dodatkowo nauczyłem
prezentować czytelniej code snippets przy użyciu fajnej stronki http://hilite.me/ efekt zresztą widać, w porównaniu
z wpisami wcześniejszymi. Polecam jak będziecie coś pisać.
Na dziś tyle,
Pozdrawiam
Brak komentarzy:
Prześlij komentarz