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.
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