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