niedziela, 2 kwietnia 2017

Jak naprawiać oraz wyświetlanie obszaru debug bohatera

Dzisiaj trochę dla odmiany w zupełnie innej kolejności. Najpierw naprawimy to co ostatnio nabroiliśmy. Tutaj chciałbym wam pokazać jak wyglądał proces takiego szukania rozwiązania. W zasadzie każdy to chyba wie, ale i tak to opiszę.

Pierwszy wniosek jaki można wysnuć to, że problem musi leżeć gdzieś w obiekcie b2dr, dosyć to proste bo jest to obszar odpowiedzialny za renderowanie wraz z debugowaniem (zielona pomocnicza ramka, aby wiadomo było jaki obszar uwzględniamy).

Co należy wcześniej zauważyć w konstruktorze PlayScreen początkowo ustawiliśmy dla obiektu renderer (dla TiledMap) skalowanie 1 / 1.3f. w konstruktorze również zainicjowaliśmy nowy obiekt Box2DDebugRenderer b2dr. Czyli co, mamy 2 rożne obiekty do renderowania. Jeden ma informacje o skali, drugi jej nie ma na ten moment. Spójrzy teraz w metodę render. Tam dla obiektu renderer następuje wywołanie metody wyświetlającej render. Dla b2dr również zostaje wywołana metoda render. Ale zaraz, zaraz, a gdzie jest skalowanie? Dalej patrząc przyjmuje ona 2 parametry (world i camera.combined). World za bardzo nie ma jak się czepić ponieważ jest to tylko kontener z elementami, za to można czepić się kamery, bo to ona jest odpowiedzialna za wyświetlanie. Mamy. No dobra to jest wyjaśnienie, ale skąd wiadomo ze można było czepić się akurat kamery?

3 metody poszukiwania rozwiazania

Wersja Lucky Luke
Najpierw można próbować szczęścia, tak jak było to w moim wypadku. Czasem bezskutecznie. Do obiektu b2dr próbowałem użyć metodę do skalowania dopisałem więc .scale. Genialne co? Nic jednak takiego nie było.
Wersja dla mających czas
Można na danym obiekcie kliknąć trzymając klawisz Ctrl, powinno przejść do całości kodu danej klasy. Można wtedy przejrzeć wszystkie metody i konstruktory czy nie ma tam gdzieś takiej możliwości. Często jest to konieczne.
Wersja ratuj! (najskuteczniejsza)
Wpisujemy w Google słowa kluczowe, bądź błędy które sprawiają nam problem. Im lepiej, dokładniej to określisz tym szybciej odnajdziesz co potrzeba. Gdzieś kiedyś słyszałem i utkwiło to w głowie, ze każdy problem jaki masz już prawdopodobnie ktoś inny rozwiązał. Czasem trzeba stosować pewne analogie, akurat do twojego problemu.

Po wpisaniu w google scale debug libgdx. Wystarczy przejrzeć kilka kilka odnośników i coś się znajdzie. Rozwiązanie czekało pod tytułem „libgdx – Box debug draw not correct”. Jest cała masa stron internetowych gdzie można zadawać pytania i odpowiadać. Jednak w większości przypadków wystarczają już istniejące odpowiedzi. Należy wspomnieć tu o najstarszej i największej takiej stronie stackoverflow. Jak to mówią jest to po prostu MUST TO HAVE.

Adaptacja do naszego problemu

Na stronie znajdowała się akurat taka linia kodu dla innej gry

b2dr.render(world, cam.combined.cpy().scale(PPM, PPM, 1));

coś pięknego, bierzemy. A u nas zmieniamy

b2dr.render(world, camera.combined.scale(1 / 1.3f, 1 / 1.3f, 0));

Przy czym przy skalowaniu należy podać skalowanie na x, y, z. Z wymiaru nie mamy, więc po prostu można dopisać 0. Można to sprawdzić najeżdżając myszą nad metodę scale wraz z przytrzymanym Ctrl. Lub kliknąć wraz z Ctrl przechodząc do treści metody.


 Upragniony efekt. Idealna otoczka wokół platform, skrzyni i podłoża.



Dla tych co dotrwali obiecuje że to jednorazowe tak rozległe wyjaśnienie. Będzie pojawiać się tylko w formie dlaczego.

Chochliki (Sprites)

Stwórzmy teraz dla porządku nowy pod pakiet dla tzw. Sprites (duszki, chochliki). W grafice komputerowej mówi się tak na pojedyncze obiekty animowane które mogą poruszać się po ekranie oraz można nimi sterować. Mogą to być zarówno bohaterowie jak i przeciwnicy. Termin pojawił się już w latach 70, są to czasy gdzie powszechne były komputery 8-bitowe. Dzięki skalowaniu i nakładaniu sprit-ów na siebie uzyskiwano efekty światłocieni, co jak na lata 70 dawało sporo większe możliwości tworzenia bardziej dynamicznych gier. Same techniki korzystające z sprit-ów znacznie poprawiły możliwości graficzne komputerów, gdzie procesory jak na tamte czasy były dziesiątki tysięcy razy wolniejsze niż te obecne.

Wewnątrz pakietu tworzymy nową klasę BatteryHero – będzie to postać naszego bohatera. Po nazwie klasy dopisujemy extends Sprite. O extends było już we wpisie przy zagadnieniu Rozszerzamy klasy.
Wewnątrz na początku inicjujemy dwie zmienne World world i Body b2dBody. World ponownie jako pojemnik na obiekty którymi można zarządzać, a b2dBody jako ciało które posiada wiele stanów (kształty itp.).

public World world;
public Body b2dBody;

Konstruktor Chochlików

Kolejną rzeczą będzie stworzenie konstruktora. Czyli wszystko co ma być utworzone w momencie powołania do życia BatteryHero (klasy). Tu chcielibyśmy zdefiniować wygląd, typ i inne. Można wszystko wpisać w wewnątrz, ale można dla większej przejrzystości przenieść to do osobnej metody, a w konstruktorze zostawić tylko jej wywołanie.

Wewnątrz konstruktora do którego przekazujemy obiekt world

public BatteryHero(World world) {
        this.world = world;
        defineBatteryHero();
}

Pojawia się tutaj słówko this. Co w tym miejscu to nam mówi. Musimy najpierw spojrzeć na 2 elementy tu obecne. Pierwszy jest u góry klasy gdzie inicjujemy World world. Drugi element znajdujemy patrzący na konstruktor ten powyżej, ma on przekazany obiekt (World world). Zastanówmy się nad sensem tej machinacji ;d. Przekazując obiekt z miejsca, w naszym przypadku gdzie będziemy tworzyć bohatera. Na razie ku ścisłości on nie istnieje, jest to tylko jego opis i możliwe zachowania. Tworzyć go będziemy nie w wewnątrz tej klasy tylko z innego miejsca. W miejscu gdzie będziemy tworzyć bohatera (prawdopodobnie PlayScreen) obowiązuje pojemnik world z elementami którymi możemy zarządzać. Chcemy aby nasz bohater był dodany do tego pojemnika. Więc przekazujemy pojemnik world do klasy bohatera BatteryHero. Ta klasa mając ten pojemnik może dodać bohatera do wewnątrz. I o to właśnie chodzi.

Mając w konstruktorze

this.world = world;

analogia
z tej klasy = przekazane przez konstruktor;

należy widzieć to w sposób następujący
this.world jest tym miejscem zarezerwowanym u góry klasy. W to miejsce przypisujemy ten obiekt który został przekazany w konstruktorze (World world). Od tego miejsca to wewnątrz tej klasy pod obiektem world jest to samo co w PlayScreen.   

Definiowanie bohatera

metodę umieszczamy gdzieś poniżej

public void defineBatteryHero() {
        BodyDef bodyDef = new BodyDef();
        bodyDef.position.set(70, 140);
        bodyDef.type = BodyDef.BodyType.DynamicBody;
        b2dBody = world.createBody(bodyDef);

        FixtureDef fixtureDef = new FixtureDef();
        CircleShape shape = new CircleShape();
        shape.setRadius(35);

        fixtureDef.shape = shape;
        b2dBody.createFixture(fixtureDef);
}

Nazwa metody powinna sugerować jakąś czynność którą wykonuje. Idealnie jeżeli zarówno zmienne jak i nazwy metod mówią wszystko co trzeba, a komentarze stają się zbędne. Określa się to wtedy mianem clean code i są to dobre praktyki programistyczne. Metoda jest publiczna i zwraca typ void (czyli nic nie zwraca). Czasem metody zwracają po wykonaniu jakiś wynik, wtedy w miejscu void określa się jakiego typu ma być wynik (przykładowo może to być String, czyli napis), a w ciele całej metody, bądź na końcu powinna być linia kodu z słowem kluczowym return

return zmienna;

Wnętrze metody zróbmy na szybko ponieważ dużo elementów pojawiło się już wcześniej podczas wyświetlania tiledMap. Tworzymy najpierw korpus. Ustawiamy jego pozycję na 70 na x, 140 na y. Co ważne ustalamy w ten sposób punkt środka.


Ustawiamy typ jako dynamiczny, ponieważ chcemy aby bohater się poruszał. Tworzymy ciało b2dBody w pojemniku world o zadanym kształcie.

Kolejno ustalamy stan obiektu, w tym przypadku jego kształt. Tym razem kształt ustalony został na okręg. Powód prosty, po prostu sylwetka bardziej przypomina kształt owalu, oraz biorąc pod uwagę jakiś zakres ruchu postaci (przebieranie nogami i rękoma) przybiera to bardziej kształt okręgu niż kwadratu.

Takie pierwsze ilustrujące to skojarzenie


Żródło: http://www.wiking.edu.pl/upload/jezyk_polski/images/renesans.jpg

Metodą setRadius() ustawiamy promień koła jaki chcemy uzyskać. Jak pamiętamy poprzednio kafelki które wykorzystaliśmy przy mapach miały one rozmiar 70x70 pikseli. Więc chcąc uzyskać taką szerokość i wysokość podajemy połowę jako promień koła.


Przypisujemy stanom fixture kształt koła. W końcu te stany tworzymy dla obiektu postaci b2dBody (ciała do wyświetlenia).

Wyświetlanie bohatera (debug)

Aby wyświetlić bohatera na razie tylko jako obszar debugowania (tekstury dołożymy później) należy zainicjować go w PlayScreen. Inicjalizujemy więc na początku klasy obiekt klasy BatteryHero (to co dzisiaj zrobiliśmy).

Private BatteryHero player;

Następnie w konstruktorze i tu kluczowe, poniżej miejsca gdzie utworzyliśmy nowy obiekt World piszemy

player = new BatteryHero(world);

Koniecznie poniżej, ponieważ przy tworzeniu playera chcemy przekazać mu obiekt World. Obiekt musi więc zatem istnieć już wcześniej. W innym przypadku odwołamy się tylko do zarezerwowanego obszaru, co robimy na początku klasy. Inaczej mówiąc jest to obszar pusty. Spowoduje to błąd NullPointerException, co znaczy tyle że próbujemy odwołać się do pustego miejsca. Kod wykonuje się linia po linii dlatego kolejność jest taka ważna.

Efekt po uruchomieniu


Udało się dzisiaj przygotować już pewne podstawy pod bohatera. Nie wygląda może jeszcze zbyt przyjaźnie, ale to już tylko kwestia czasu. W następnym wpisie będzie trzeba trochę dostosować odpowiednio do tego wyświetlanie, może udać się wykonać sterowanie tą kuleczką ;d 

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

Tyle na dziś,

Do zobaczenia wkrótce

Brak komentarzy:

Prześlij komentarz