piątek, 21 kwietnia 2017

Zbijamy notki

Po tym jak mamy już możliwość wykrywania konkretnych kolizji, czas dodać pierwsze zachowania jakie będą po tym następowały. Gdzieś tam na początku pisałem o planie, aby po zebraniu tabliczki z słowem pojawił się jakiś dialog/komunikat z możliwością sprawdzenia, zapamiętania bądź odgadnięcia słówek. Pytaniem jest czy nie zabija to w jakiś sposób dynamiki rozgrywki. Jest to ten moment gdzie trzeba zatrzymać się, trochę pokminić i sprecyzować logikę gry. Następny wpis właśnie temu będzie poświęcony. Póki co zrobimy aby obiekt kolizji znikał, z resztą i tak będzie to elementem pośrednim tego łańcucha zdarzeń.

Wizja

Najpierw może odgórna wizja jak to powinno wyglądać. Ponieważ jest wiele obiektów z którymi kolidujemy powinniśmy, jakoś rozróżniać czy obiekt ma podlegać kolizji czy nie. Osiągniemy to tworząc kategorię które mogą między sobą kolidować w zależności jak sobie to zdefiniujemy. Po co ? Weźmy sytuację gdzie nastąpiła kolizja i jakiś element powinien zniknąć, a co za tym idzie nie powinno więcej tych kolizji już występować. Wczytując mapę mamy całą warstwę obiektów, co więcej ustawiamy że bohater się z nią zderza. Obiekty z którymi się zderzyliśmy powinniśmy jakoś naznaczać, aby nie było powtórnie z nimi kolizji. To co się robi w praktyce w tym momencie to zmienia kategorię danego obiektu. Np. na taką z którą nie możemy kolidować. A teraz wszystko powoli.

Kategorie filtra

Tworzymy najpierw zmienne finalne które będą dzieliły na kategorie. Tworzymy więc w klasie głównej gry WordCharger


public static final short DEFAULT_BIT = 1;
public static final short BATTERY_HERO_BIT = 2;
public static final short WORDNOTE_BIT = 4;
public static final short DESTROYED_BIT = 8

Kategorie w swojej budowie wymaga podania wartości bitowej, typu zmiennego short, którego zakres pozwala nam osiągnąć przedział [-32768, 32767]. Wielkość typu short jest ustalona na 2 bajty. Przeliczając wielkość bajta na bity, jednemu bajtu przypada 8 bitów, więc z 2 bajtów robi się tym sposobem 16 możliwych bitów. Każdy taki bit to odrębna kategoria którą możemy stworzyć. Ponieważ każdy bit jest przesunięty w ciągu w lewo, zamieniając to na wartość dziesiętną uzyskujemy właśnie te wartości widziane powyżej.

0000000000000001
0000000000000010
0000000000000100
0000000000001000

uzyskanie kolejnych wartości dziesiętnej jest możliwe dzięki potęgowaniu dwójki do kolejnych potęg z zakresu (1-16). Przy czym pierwsza wartość będzie równa 1, a ostatnia 65536. Daje nam to pokrycie całego zakresu zaczynając od przedziału ujemnego.

Ustawianie kategorii i masek

Każdemu obiektowi teraz powinniśmy poustawiać kategorie. Potrzebujemy więc na początku ustawiać filtry dla fixture bohatera. Najpierw przypiszemy fixture bohaterowi kategorię wcześniej zdefiniowaną. Ważna kolejność, dlatego koniecznie filter należy umieścić po definicji fixture. Więc w metodzie defineBatteryHero dopisujemy


fixtureDef.filter.categoryBits = WordCharger.BATTERY_HERO_BIT;

Drugą sprawą jest ustawić maski. Maski służą ustalaniu z jakimi kategoriami może kolidować dany fixture.


fixtureDef.filter.maskBits = WordCharger.DEFAULT_BIT | WordCharger.WORDNOTE_BIT;

w ten sposób określamy że nasz bohater, a dokładnie jego fixture może kolidować z kategorią normalną oraz kategorią oznaczoną WORDNOTE_BIT.
 
Teraz w klasie InteractiveTileObject tworzymy metodę ustawiającą kategorię filtra. Robimy to w tym miejscu ponieważ będzie to metoda uniwersalna dla klas dziedziczącej po niej. Metoda w parametrze powinna przyjmować wybrana kategorie którą chcemy przypisać.


public void setCategoryFilter(short filterBit) { // TODO
    Filter filter = new Filter();
    filter.categoryBits = filterBit;
    fixture.setFilterData(filter);
}

Wewnątrz tworzymy nowy filter, oraz od razu temu obiektowi przypisujemy to co zostało przesłane (filterBit) przy wywołaniu tej metody. Bierzemy w następnym kroku fixture, do którego mamy dostęp jako że jest to pole obowiązujące w całej klasie i ustawiamy stworzony filter metodą setFilterData. setFilterData jest jedną z zdefiniowanych odgórnie metod którą posiada ten „klocek libGDX” Filter.

Zmienne nastroje notki

Pora przejść do klasy WordNote i tu osobno już sprecyzować jakiej dokładnie kategorii chcemy ten obiekt przypisać.


setCategoryFilter(WordCharger.WORDNOTE_BIT);

od tego momentu od utworzenia WordNote będzie właśnie tej kategorii. My chcemy zmienić tą kategorię podczas kolizji. Dlatego w metodzie onHit() dopisujemy


setCategoryFilter(WordCharger.DESTROYED_BIT);

odpalając w tym momencie uzyskamy następujący rezultat
 


Co widzimy? Nastąpiła kolizja z WordNote. Liczy się tylko jeden z komunikatów, drugi dla testów był akurat wynikiem kolizji z tą notką wyżej.  Jak widać postać może wejść w pole gdzie znajduje się obiekt. Brak tu kolizji. Jest to właśnie efekt działania filtra. Po kolizji zmieniliśmy kategorię filtra WordNote na DESTROYED_BIT, a wracając wcześniej do kodu gdzie ustalaliśmy maski dla fixture bohatera określiliśmy aby kolizja mogła występować tylko dla kategorii z etykietką DEFAULT_BIT oraz WORDNOTE_BIT. Dla przypomnienia powtórnie odpowiedzialna ta sama linia kodu, aby nie szukać


fixtureDef.filter.maskBits = WordCharger.DEFAULT_BIT | WordCharger.WORDNOTE_BIT;

Jesteśmy już w stanie wyłączać kolizję pomiędzy obiektami. Jednak co dalej będziemy chcieli to aby po kolizji obiekt zniknął.

Wyciąganie pojedynczej komórki

Przechodząc do samej tabliczki wiemy że jest ona częścią całej zdefiniowanej jednej warstwy z pliku mapy TiledMap. My jednak nie chcemy niszczyć całej warstwy, a poszczególne elementy. Należy powiedzieć w tym miejscu, że cała mapa jest złożona z pojedynczych komórek, tak przecież tworzyliśmy ją w edytorze stawiając „stemple” kafelek. Wczytując mapę do obiektu TiledMap nie utraciliśmy możliwości odwoływania się do poszczególnych kafelek. Podczas kontaktu w trakcie kolizji wiemy właściwie jaka jest dokładna pozycja body elementów biorących w nich udział. Dzięki czemu możemy odpowiednio przeliczyć i określić w ten sposób, która to kafelka. Tworzymy więc metodę zwracającą konkretną kafelke


public TiledMapTileLayer.Cell getCell() {
        
}
       

Wewnątrz na początku odwołujemy się do warstwy, w naszym przypadku jest to warstwa 4, licząc od 0 będzie 3;


TiledMapTileLayer layer = (TiledMapTileLayer) map.getLayers().get(3);

Biorąc pozycję x i dzieląc ją na długość kafelki uzyskamy, która to jest kafelka. Dodatkowo należy jeszcze uwzględnić skalowanie które kiedyś zrobiliśmy. Należy cały ten proces odwrócić. Jest to nic innego jak odwrócenie ułamka 70 / WordCharger.PPM. Dodatkowo rzutujemy na typ (int), kafelki są całymi liczbami, a nie typem zmiennoprzecinkowym float. Całość zwracamy jako efekt wywołania całej metody przy pomocy słowa kluczowego return.


return layer.getCell((int) (body.getPosition().x * WordCharger.PPM / 70),
                (int)(body.getPosition().y * WordCharger.PPM / 70));

Wstawianie pustego kafla

Czas skorzystać z dobrodziejstwa które udało się stworzyć. Przechodząc do klasy WordNote w metodzie onHit dopisujemy


getCell().setTile(null); 

Odpalając grę i kolidując z notką efekt następujący



Z notatki pozostał tylko no cóż … obszar debugownia. Co mówi że obiekt istnieje ale brak kolizji i ustawiliśmy tile na null. Muszę się przyznać że już fajnie popykać pozbijając trochę notek ;d 

Tyle na dziś

Pozdrawiam

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

Brak komentarzy:

Prześlij komentarz