środa, 5 kwietnia 2017

Bubel do ziemi

Mając już naszego bohatera (będę go nazywał bublem) oraz platformy, można powoli już coś z tym zrobić. Bubel zawieszony został trochę w powietrzu, nie bez powodu. Spowodujemy aby zaczęła przyciągać go siła (grawitacja) ku podłożu. Mamy w konstruktorze PlayScreen napisana odpowiednią do tego metodę. Wystarczy tylko zmienić parametry z (0, 0) na przykładowe (0, -10)

world = new World(new Vector2(0, -10), true);

A graficznie reprezentuje się to tak


Efektem będzie przyciąganie ku podłożu bo tak określony jest wektor, będzie on skierowany od punktu początkowego (0, 0). Im większy parametr ustalimy tym siła odpowiednio większa.

Można odpalić aplikację. Okazuje się ze na obecny moment nic się nie zmieniło. Bubel lewituje tam gdzie poprzednio. To czego nam brakuje to ustawienie odśnieżania. Robimy to ze względu, że zaczynają pojawiać się u nas elementy które się poruszają przez działanie różnych sił, co wymaga koniecznych obliczeń. Chcemy także postać mogła być sterowalna, to także wymaga kalkulacji. Ustalamy to parametrem step linią kodu w metodzie update

World.step(1 / 60f, 6, 2);

Parametry które pojawiają się przy określaniu kroku World to kolejno (time step, potem velocity iteration i position iteration). Po kolei wyjaśnijmy. Time step określa czas do upłynięcia do następnej kalkulacji. Ma ono ogromny wpływ na działające siły między obiektami, czy grawitacją. Po prostu wszystko dzieje się szybciej. Velocity, position iteration w głównej mierze określają jak dokładnie mają być przeprowadzane symulacje. W przypadku kolizji 2 elementów muszą być przeprowadzone pewne obliczenia, aby określić w jakie miejsce mają się przemieścić, albo w jaki sposób obrócić aby „wyjść” z momentu kolizji. Im większa ustalona ich wartość tym dokładniejsze są obliczenia (np. większy obszar obiektu jest brany pod uwagę), jednak nie ma nic oczywiście za darmo – robimy to kosztem utracenia na wydajności. Aha, obecne wartości wzięte są z dokumentacji także nic nie uwzględniają specjalnego.

PPM

Na ten moment można zobaczyć powolnie opadającego Bubla. W tym momencie poszukując spotkałem się z pojęciem PPM które jest za to odpowiedzialne. Spróbujmy dotrzeć do zrozumienia co to właściwie jest. Pierwsze co to można spojrzeć rozwinięcie skrótu Pixel per meter. Ale ?#& co, że co jaki meter?? Takie mniej więcej pytanie sobie postawiłem. Odpowiedź jest jednak dosyć banalna. Fizyka gry Box2D zakłada, że używamy identycznych metryk jak w świecie rzeczywistym (metry, kilogramy, sekundy). W ten sposób zakłada się, że 1 pikselowi odpowiada 1 metr w rzeczywistości. Patrząc na nasze ekrany to są tam długości rzędu 1000 pikseli (grę mamy ustalona na 800x400). Tak więc z położenia o współrzędnych (0, 0) do (1024, 0) odzwierciedla rzeczywistą odległość 1024km. Więc trwa to odpowiednio długo. Dokładnie o tym w tym artykule. Należy coś z tym zrobić i odpowiednio to przeskalować.

More Universal

Przy okazji poprawimy jedną rzecz. Niby nieznaczna, ale kiedyś możemy chcieć zmieniać rozdzielczości i dzięki temu zyskamy na czasie i mniejszych problemach. Jeżeli spojrzymy zarówno w klasie PlayScreen i Hud przy tworzeniu widoku ustawiamy osobno rozdzielczość na 800x400. Możemy zrobić to znacznie efektywniej przekazując ją jako stałą zdefiniowaną w klasie głównej WordCharger. Tworzymy więc dwie statyczne finalne zmienne V_WIDTH, V_HEIGHT. Statyczna w tym przypadku oznacza że może istnieć tylko jedna instancja tej zmiennej w całości działania programu. Zaś finalna oznacza że nie można jej zmieniać. Dzięki modyfikatorowi public będzie można odwołać się do nich z dowolnego miejsca

public static final int V_WIDTH = 800;
public static final int V_HEIGHT = 480;

Teraz przechodzimy do klasy PlayScreen i zmieniamy aby wyglądało to w taki sposób

view = new FitViewport(WordCharger.V_WIDTH, WordCharger.V_HEIGHT, camera);

Najpierw musimy wskazać klasę, a po kropce odwołujemy się do zmiennych bądź metod w zależności od obecnych tam modyfikatorów dostępności. Analogicznie robimy w klasie Hud.

Teraz zrobimy skalowanie. Definiujemy w klasie głównej gry WordCharger zmienną którą użyjemy w pozostałych elementach gry.

public static final float PPM = 100;

Użyjemy jej chcąc uzyskać rezultat w którym 1 piksel jest równoważny 100 metrom. Czyli około 100 razy więcej niż poprzednio. Piksel to 1 punkcik na wyświetlanym ekranie. Co jeszcze ważne musi być koniecznie typu float, ponieważ ponownie będziemy wykonywać działania dzielenia i nie chcemy tracić liczb po przecinku.

W klasie PlayScreen zmieniamy

view = new FitViewport(WordCharger.V_WIDTH / WordCharger.PPM, WordCharger.V_HEIGHT / WordCharger.PPM, camera);

renderer = new OrthogonalTiledMapRenderer(map, 1 / 1.3f * (1 / WordCharger.PPM))

Ta linia może być trochę niejasna. 1 / 1.3f * (1 / WordCharger.PPM) wynik jaki tu uzyskamy jest drugim parametrem który przyjmuje konstruktor gdy chcemy uzyskać skalowanie. Z tym że uwzględniamy tu zarówno skalowanie jakie wymagało aby obszar debagowania pokrywał się z rzeczywistym oraz skalowanie które wynika z PPM.

Dalej w klasie WordCharger zmieniamy jeszcze

bdef.position.set((rect.getX() + rect.getWidth() / 2) / WordCharger.PPM,
                    (rect.getY() + rect.getHeight() / 2) / WordCharger.PPM);

shape.setAsBox(rect.getWidth() / 2 / WordCharger.PPM, rect.getHeight() / 2 / WordCharger.PPM);

w klasie BatteryHero zmieniamy

bodyDef.position.set(70 / WordCharger.PPM, 140 / WordCharger.PPM);

shape.setRadius(35 / WordCharger.PPM);

Podsumowując zmieniliśmy wszystkie elementy odpowiedzialne za wyświetlanie.


Sterowanie Bublem

Sterowanie w libGdx wykonuje się na 2 sposoby. Idąc za dokumentacją może być wykonana za pomocą impulsów lub za pomocą siły. Użycie siły jednak jak zauważają, jeżeli nie są skierowane w środek masy tworzą się momenty obrotowe i prędkości kątowe. My jednak tego nie chcemy. Drugim sposobem jest użycie impulsów, które polegają na nadanie prędkości obiektowi w odpowiednim kierunku.

player.b2dBody.applyLinearImpulse(new Vector2(2f, 0), player.b2dBody.getWorldCenter(), true);

applyLinearImpulse potrzebuje kolejno parametrów (wektor impulsu, wektor punktu środka masy, wartość boolean rozbudzania obiektu – aby nie przeprowadzało kalkulacji). Wektor punktu środka masy uzyskamy metodą player.b2dBody.getWorldCenter() co da punkt centralnie w środku obiektu.

Teraz zmieńmy nasze wcześniej utworzone warunki sterowania. Zacznijmy od zmienienia metody inputu na isKeyJustPressed. Różni się od isKeyPressed tym że wykrywa moment kiedy przycisk wciśniemy i puścimy. Samo isKeyPressed wykrywa gdy klawisz jest wciśnięty, co będzie powodowało w momencie przytrzymania ciągły ruch, a to spowoduje wystrzelenie w bok, lub kosmos ;d. Tym drugim sposobem unikamy tego problemu.
Dodatkowo fragmentem player.b2dBody.getLinearVelocity().x <= 2 uzyskujemy maksymalne przesunięcie na osi x przez okres 1 sekundy. Tym sposobem unikamy przypadku ciągłego szybkiego wciskania przycisku, aby postać znów nie wyskoczyła gdzieś w dal.

Tak będzie wyglądać całe sterowanie


if(Gdx.input.isKeyJustPressed(Input.Keys.RIGHT) && player.b2dBody.getLinearVelocity().x <= 2) {
player.b2dBody.applyLinearImpulse(new Vector2(2f, 0), player.b2dBody.getWorldCenter(), true);
}

if(Gdx.input.isKeyJustPressed(Input.Keys.LEFT) && player.b2dBody.getLinearVelocity().x >= -2) {
player.b2dBody.applyLinearImpulse(new Vector2(-2f, 0), player.b2dBody.getWorldCenter(), true);
}

if(Gdx.input.isKeyJustPressed(Input.Keys.UP)) {
player.b2dBody.applyLinearImpulse(new Vector2(0, 5f), player.b2dBody.getWorldCenter(), true);
}


Obecnie można sterować postacią. Na końcu dodamy jeszcze podążanie za nią kamery.

W metodzie update klasy PlayScreen dodajemy

camera.position.x = player.b2dBody.getPosition().x;
camera.position.y = player.b2dBody.getPosition().y;

Co oznacza tyle że obecne położenie zarówno na osi x i y zostaje ustalane na pozycję x i y bohatera.

Można uruchomić grę. To co widzimy postać się porusza. Są właściwie dwa problemy które trzeba będzie rozwiązać. Pierwsze postać zmienia swoje położenie względem środka kamery, do momentu aż znika poza ekran. Drugie niepożądane zachowanie to możliwe wyjście bohatera poza obszar widocznej mapy. Trzeba będzie zająć się tym w przyszłości.



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


Na dziś tyle,
Pozdrawiam.

Brak komentarzy:

Prześlij komentarz