środa, 19 kwietnia 2017

Na sygnale wraz z kolizją

Jak pamiętamy nad głową bohatera powinna być umieszczona bateria wraz ze wskaźnikiem naładowania. Mimo że koncepcja może ulec zmianie, najpierw ją wykonajmy i oceńmy czy ma sens istnienia w takiej formie. Zadanie dość łatwe ponieważ większość będzie podobnie jak wcześniej, wiec do dzieła.

Na początku tworzymy w pakiecie sprites nową klasę Battery


public class Battery extends Sprite {

wewnątrz potrzebne będą 2 zmiennie: regionu do wyświetlenia, oraz zmiennej w której przechowamy atlas


private TextureRegion batterylevel;
private TextureAtlas atlas;

w konstruktorze przypiszemy obiektowi klasy ten przekazany, oraz ustawimy rozmiar tekstury, który będzie niezmienny dlatego robimy to od razu tutaj.


public Battery(TextureAtlas at) {
    this.atlas = at;
    setBounds(0, 0, 100 / WordCharger.PPM, 50 / WordCharger.PPM);
}

Wyświetlanie baterii

Następnie robimy resztę czynności które potrzebne są do wyświetlenia elementu: są to odnalezienie odpowiedniego rejonu, oraz ustawienie go jako ten do wyświetlenia. Każdy kolor baterii będziemy ustawiać za pomocą jednej z 4 metod. Robimy więc analogiczne kroki dla 4 pozostałych kolorów wskazując odpowiednie miejsca. Współrzędne można sprawdzić w pliku battery_enemy.pack w katalogu assets projektu androidowego.


public void setOrange() {
    batterylevel = atlas.findRegion("red");
    batterylevel = new TextureRegion(batterylevel.getTexture(), 0, 0, 128, 64);
    setRegion(batterylevel);
}

Aktualizacja pozycji baterii

Do szczęścia potrzebujemy jeszcze aktualizować pozycję baterii w miejscu aby znajdowała się nad jego głową. W tym celu w metodzie przekażemy cały obiekt bohatera z którego następnie wyciągniemy potrzebne nam informacje. Współrzędne wyciągamy kolejno z b2dBody bohatera metodą getPosition(). Obiekt umieścimy wyżej o całą wysokość sprita getHeight() od miejsca górnego krańca miejsca y bohatera. Akurat taka wysokość jest odpowiadająca


public void update(BatteryHero hero) {
    setPosition(hero.b2dBody.getPosition().x / 0.7f - getWidth() / 2, hero.b2dBody.getPosition().y / 0.7f + getHeight());
}

Teraz przechodząc do PlayScreen najpierw inicjalizujemy obiekt


private Battery battery;

W konstruktorze tworzymy obiekt i przekazujemy mu wymagany atlas, oraz wstępnie ustalamy przykładowy kolor na pomarańczowy


battery = new Battery(atlas);
battery.setOrange();

Na razie zmiana kolorów ustalona jest na sztywno, w późniejszych etapach gdy będziemy mogli już cos zbierać stworzymy odpowiednie warunki do zmiany koloru.

Obecnie bateria będzie pozostawała jeszcze w miejscu, należy w metodzie update wywołać metodę aktualizującą, wraz z obiektem player z którego wyciągniemy aktualne współrzędne


battery.update(player);

Osiągnięty efekt podążająca nad głową bateria


Kolizja

Obecnie wiemy że następuje jakaś interakcja między obiektami (platformy, notatki) a bohaterem, jest to oczywiste ponieważ nie możemy w tych miejscach stanąć. Teraz kolejnym elementem który chcemy zrealizować jest zbieranie karteczek. Do tego wymagana będzie informacja, kiedy jeden obiekt spotyka na swojej drodze drugi tzw. następuje kolizja. Po identyfikacji co z czym koliduje, będziemy mogli przypisać tym obiektom zachowania jakie chcemy osiągnąć np. spowodujemy aby karteczka znikła. Elementy które dodaliśmy do world mogą zacząć być sprawdzane czy występuje między nimi kolizja. Działa się to na prostej zasadzie sprawdzania czy tzw. Fixture tych obiektów się dotykają.

Najpierw na fixture obu elementów tzn. bohaterowi oraz Wordnote używamy metod dzięki którym będziemy mogli później do tych obiektów odwołać się przy identyfikacji.

W konstruktorze Wordnote wskazując na ten obiekt


fixture.setUserData(this); 

Fixture ale dla klasy dziedziczącej

jest tu pewna poważna rzecz, którą potrzeba uwzględnić

ponieważ obiekt WordNote dziedziczy InteractiveTileObject, a chcemy ustawić metodę setUserData konkretnym klasom, musimy dać możliwość odwołania się do Fixture klasie nadrzędnej. Więc najpierw musimy stworzyć konkretny obiekt i przypisać mu tak stworzone fixture. Modyfikator proctected umożliwi odwołanie się do tego obiektu klasom dziedziczącym. Więc w klasie InteractiveTileObject tworzymy


protected Fixture fixture;

a w konstruktorze przypisujemy wcześniej stworzony już obiekt do fixture


fixture = body.createFixture(fdef);

Jak pamiętamy fixture tworzymy w klasie InteractiveTileObject, co jest powodem że jeżeli w tym miejscu ustawilibyśmy etykietę setUserData, to brakowało by rozróżniania z jakim obiektem mamy do czynienia.

Ustalając w klasie wyżej WordNote tą metodą spowodujemy, że będziemy mogli identyfikować konkretne obiekty.

Teraz w BatteryHero przy tworzeniu Fixture dopisujemy metodę setUserData wraz z etykietką „hero". Hero będzie podstawą naszego warunku, ponieważ to go właśnie chcemy identyfikować czy koliduje z innymi obiektami na mapie.


b2dBody.createFixture(fixtureDef).setUserData("hero");

ustawiamy również parametr isSensor dzięki czemu zbieramy informacje o kontakcie, ale nie generujemy odpowiedzi kolizji.


fixtureDef.isSensor = true;

kolejną ważną rzeczą jest stworzenie abstrakcyjnej metody w InteractiveTileObject. Będzie to metoda która będzie wywoływana przy kontakcie bohatera z warstwą interaktywną (platformy, notatki). Przy czy jest abstrakcyjna, ponieważ definicje jej zrobimy osobno każdemu obiektowi, odpowiednio do zachowania jakie chcemy uzyskać.


public abstract void onHit();

W klasie WordNote natomiast nadpisujemy tą metodę w treści logując o następującej kolizji


@Override
    public void onHit() {
        Gdx.app.log("WordNote", "Collision");
    }

ContactListener

Teraz pora na identyfikacje kontaktów między obiektami. Tworzymy w pakiecie Tools nową klasę WorldContactListener implementującą interfejs ContactListener. ContactListener zawiera metody które są realizowane w momencie kontaktu.


public class WorldContactListener implements ContactListener {

Interfejs ContactListener będzie od nas wymagał, aby zaimplementować wewnątrz 4 metody. Są to odpowiednio:
beginContact – kiedy rozpoczyna się dotyk dwóch fixture
endContact – kiedy kończą
postSolve – zachowania przy rozwiązywaniu wyjścia z kolizji
preSolve - zachowania przy rozwiązywaniu wyjścia z kolizji

Zajmiemy się w głównej mierze beginContact

Najpierw wyciągamy oba fixture które biorą udział w kolizji


Fixture fixA = contact.getFixtureA();
Fixture fixB = contact.getFixtureB();

Identyfikacja obiektów

Sprawdzamy czy chociaż jeden z nich to “hero”


if(fixA.getUserData() == "hero" || fixB.getUserData() == "hero") {

Sprawdzamy które Fixture jest dokładnie czym. Fixture hero przypisujemy fixA lub fixB w zależności czy fixA.getUserData() == "hero". Wystarczy sprawdzenie jednego warunku, ponieważ są tylko 2 opcje.


Fixture hero = fixA.getUserData() == "hero" ? fixA : fixB;

Jeżeli hero == fixA, to obiektem jest fixB, jeżeli nie to automatycznie obiektem jest fixA


Fixture object = hero == fixA ? fixB : fixA;

Wywołanie metody klasy interaktywnej

Teraz pora na sprawdzenie czy obiekt jest klasy interaktywnej i wywołanie na nim metody.

Sprawdzamy czy obiekt nie jest pusty i czy jest klasy InteractiveTileObject. Dokładniej to sprawdzamy metodą isAssignableFrom() czy rozróżniony obiekt drugi poza bohaterem object.getUserData().getClass()) jest oznaczony InteractiveTileObject.class. Może to wystąpić jedynie w przypadku gdy dziedziczy właśnie z tej klasy


if(object.getUserData() != null && InteractiveTileObject.class.isAssignableFrom(object.getUserData().getClass())) {

    }
}

Wewnątrz warunku rzutując object na InteractiveTileObject możemy wywołać abstrakcyjną metodę onHit(). A ta w zależności od implementacji wykona metodę zdefiniowaną bezpośrednio w klasie WordNote.


((InteractiveTileObject) object.getUserData()).onHit();

Wszystko co dzisiaj zrobiliśmy było tylko dla jednego widocznego komunikatu w konsoli. Ale co tam, było warto ;d


Tyle na dziś,
Pozdrawiam

https://github.com/KrzysztofPawlak/WordCharger/tree/wpis14

Brak komentarzy:

Prześlij komentarz