Poruszanie myszą - top down
Piątek, 06 Października 2023, 09:10
Czas czytania 4 minuty, 39 sekund
Zgodne z GM:
W tym artykule dowiesz się, jak zrobić poruszanie typu "kliknij i podejdź", jak w grach typu Diablo, czy wszlekiego rodzaju Point and Clickach.
Skorzystamy z funkcji: mp_linear_step, mp_potential_step, move_towards_point.
W tym kursie chciałbym wam przedstawić różne podejścia do sterowania postacią za pomocą myszy w grze typu top-down (z widokiem z góry).
Stwórzcie nowy projekt, a w nim dwa sprite'y, o rozmiarach 32x32. Ja skorzystałem z gotowej paczki z kenney.nl. Jeśli używacie grafik w innym rozmiarze, warto je przeskalować - założeniem tego artykułu jest korzystanie z domyślnej gamemakerowej siatki 32x32 do ustawiania obiektów, gdyż jest to też artykuł wyjściowy dla kolejnego wpisu, dotyczącego wyrównywania do siatki.
Tworzymy dwa obiekty:
-
-
Sterowanie myszą będzie działać na tej zasadzie, że po kliknięciu zapamiętamy gdzie jest miejsce docelowe i tak długo, jak do niego nie dojdziemy, postać będzie się tam przemieszczać.
W evencie
koddestx = x;
desty = y;Ustalamy, że miejsce docelowe jest takie samo, jak obecne położenie instancji obiektu gracza.
Następnie, dodajemy event
Wskazówka:
Zdarzenia myszy bez prefixu Global oznaczają kliknięcie na instancji obiektu i działają tylko jeśli obiekt posiada maskę kolizji o rozmiarze przynajmniej 1x1 pikseli (sprawdzana jest kolizja z czubkiem kursora). Prefix global "łapie" wszystkie kliknięcia, nie sprawdzając w ogóle kolizji.
W tym evencie ustawiamy docelowe miejsce zapamiętane w zmiennych destx/desty na obecne współrzędne myszy:
koddestx = mouse_x;
desty = mouse_y;
Następnie, wciąż w obiekcie obj_player, dodajemy event
mp_linear_step - liniowe dojście
Najprostszą funkcją dojścia do danego miejsca będzie funkcja
kodif (x != destx or y != desty) {
mp_linear_step(destx, desty, 4, false);
}
Uwaga!
Chociaż gracz zatrzyma się, gdy trafi na ścianę, jesty x/y nie będą równe docelowym - i chociaż realnie nie ma ruchu, wciąż będzie trwało sprawdzanie czy jest on możliwy (np. usunięcie ściany spowodowałoby kontynuację).
Aby warunek dotarcia na miejsce
kodif (x != destx or y != desty) {
mp_linear_step(destx, desty, 4, false);
/* nowy warunek */
if (x = xprevious and y = yprevious) { /* jeśli od poprzedniej klatki ruszyliśmy się 0 pikseli */
destx = x; /* zmieniamy miejsce docelowe na obecne */
desty = y;
}
}
mp_potential_step
Aby omijać przeszkody, można skorzystać z funkcji
kodif (x != destx or y != desty) {
mp_potential_step(destx, desty, 4, false);
}
Wskazówka:
Jeśli punkt docelowy będzie znajdował się w miejscu, do którego nie ma dostępu (zamknięte pomieszczenie), postać zacznie krążyć kółka wokół najbliższej ściany która prowadzi do tego miejsca. Aby zatrzymać ostatecznie postać, można założyć, że czas dojścia do danego miejsca nie powinien wynosić więcej niż 2-3x więcej klatek, niż odległość w linii prostej, podzielona przez prędkość -
move_towards_point
Ostatnia funkcja, która zgodnie z dokumentacją być może wydawała by się najbardziej oczywista, wybrana została nieprzypadkowo jako ostatnia. Problem z move_towards_point jest bowiem taki, że ta funkcja porusza postać wyłącznie o podaną przez nas prędkość, więc w sytuacji, gdy zostaje mniej pikseli do zrobienia (np. prędkość równa się 10, a został 1 piksel), postać "przeskoczy" punkt docelowy (poruszając się o te wybrane 10 pikseli, a więc o 9 za daleko).
kodif (x != destx or y != desty) {
move_towards_point(destx, desty, 4); /* porusz się z prędkością 4 pikseli */
}
Oczywiście i temu da się zaradzić - wytarczy sprawdzić, czy odległość do punktu docelowego jest mniejsza niż wybrana przez nas prędkość:
kodif (x != destx or y != desty) {
if (point_distance(x, y, destx, desty) <= 4) {
/* jeśli zostało mniej lub równo 4 piksele, ustaw postać w miejscu docelowym*/
x = destx;
y = desty;
} else {
move_towards_point(destx, desty, 4);
}
}
Sztuczka polega na tym, że skoro zostało nie więcej niż 4 piksele, jakikolwiek ruch sprawi wrażenie, że wykonaliśmy po prostu kruszy ruch i dotarliśmy na miejsce - dla ludzkiego oka będzie nie do zauważenia.
Warunek o dystansie jest pierwszy, aby zapobiec wykonaniu się funkcji move, oraz aby nie wykonać dodatkowego ruchu już po niej (w tej sytuacji bowiem postać ruszyła by się najpierw o 4 piksele, a potem o dodatkowe między 0.1 a 4 piksele, co w skrajnym przypadku dałoby aż 8 pikseli i było widoczne, jako "przyciągnięcie").
Stwórzcie nowy projekt, a w nim dwa sprite'y, o rozmiarach 32x32. Ja skorzystałem z gotowej paczki z kenney.nl. Jeśli używacie grafik w innym rozmiarze, warto je przeskalować - założeniem tego artykułu jest korzystanie z domyślnej gamemakerowej siatki 32x32 do ustawiania obiektów, gdyż jest to też artykuł wyjściowy dla kolejnego wpisu, dotyczącego wyrównywania do siatki.
Tworzymy dwa obiekty:
-
obj_player
-
obj_wall
, któremu zaznaczamy "solid".Sterowanie myszą będzie działać na tej zasadzie, że po kliknięciu zapamiętamy gdzie jest miejsce docelowe i tak długo, jak do niego nie dojdziemy, postać będzie się tam przemieszczać.
W evencie
Create
obj_player:koddestx = x;
desty = y;Ustalamy, że miejsce docelowe jest takie samo, jak obecne położenie instancji obiektu gracza.
Następnie, dodajemy event
Mouse > Global > Global Left Pressed
, a więc oznaczający kliknięcie gdziekolwiek w całym obszarze gry i tylko sam moment kliknięcia (nie przytrzymywanie).Wskazówka:
Zdarzenia myszy bez prefixu Global oznaczają kliknięcie na instancji obiektu i działają tylko jeśli obiekt posiada maskę kolizji o rozmiarze przynajmniej 1x1 pikseli (sprawdzana jest kolizja z czubkiem kursora). Prefix global "łapie" wszystkie kliknięcia, nie sprawdzając w ogóle kolizji.
W tym evencie ustawiamy docelowe miejsce zapamiętane w zmiennych destx/desty na obecne współrzędne myszy:
koddestx = mouse_x;
desty = mouse_y;
Następnie, wciąż w obiekcie obj_player, dodajemy event
Step
, w którym tworzymy kod wykonywany, gdy obecna pozycja x lub y nie jest pozycją docelową. Przedstawię 3 przykładowe warianty funkcji, których można tutaj użyć, chociaż bardziej zaawansowani użytkownicy zapewne znaleźli by kolejne rozwiązania.mp_linear_step - liniowe dojście
Najprostszą funkcją dojścia do danego miejsca będzie funkcja
mp_linear_step
. Linear, czyli liniowa, spowoduje dojście do danego punktu po linii prostej. Pierwsze dwa argumenty to pozycja x,y docelowego miejsca, trzeci to prędkość na klatkę obrazu, a ostatni mówi nam, czy sprawdzać kolizje z wszystkimi dowolnymi obiektami (true), czy tylko z takimi typu solid (nie wszystkie - false). W naszym przykładzie czwarty argument ma więc wartość false
, bowiem chcemy sprawdzać kolizje z obiektem obj_wall
z zaznaczoną flagą "solid", a nie z dowolnym obiektem. Funkcje mp_ mają też tę zaletę, że jeśli dystans do punktu docelowego jest mniejszy niż wybrana prędkość, to poruszą one postać o tę mniejszą wartość.kodif (x != destx or y != desty) {
mp_linear_step(destx, desty, 4, false);
}
Uwaga!
Chociaż gracz zatrzyma się, gdy trafi na ścianę, jesty x/y nie będą równe docelowym - i chociaż realnie nie ma ruchu, wciąż będzie trwało sprawdzanie czy jest on możliwy (np. usunięcie ściany spowodowałoby kontynuację).
Aby warunek dotarcia na miejsce
if (x == destx or y == desty)
w opisanym powyżej przypadku zaczął być spełniany, zatrzymanie można wykryć w taki sposób:kodif (x != destx or y != desty) {
mp_linear_step(destx, desty, 4, false);
/* nowy warunek */
if (x = xprevious and y = yprevious) { /* jeśli od poprzedniej klatki ruszyliśmy się 0 pikseli */
destx = x; /* zmieniamy miejsce docelowe na obecne */
desty = y;
}
}
mp_potential_step
Aby omijać przeszkody, można skorzystać z funkcji
mp_potential_step
. Przyjmuje podobne argumenty jak poprzednia, ale stara się "omijać" przeszkody, znajdując drogę naokoło.kodif (x != destx or y != desty) {
mp_potential_step(destx, desty, 4, false);
}
Wskazówka:
Jeśli punkt docelowy będzie znajdował się w miejscu, do którego nie ma dostępu (zamknięte pomieszczenie), postać zacznie krążyć kółka wokół najbliższej ściany która prowadzi do tego miejsca. Aby zatrzymać ostatecznie postać, można założyć, że czas dojścia do danego miejsca nie powinien wynosić więcej niż 2-3x więcej klatek, niż odległość w linii prostej, podzielona przez prędkość -
time = point_distance(x, y, destx, desty) * spd * 3;
. Można by w momencie kliknięcia myszą ustawiać alaram, który po tylu klatkach kończy ruch - ale dokładne ustawienie zależy od typu gry. Najlepiej po prostu nie robić takich pułapek, lub pusty obszar wypełnić niewidocznym, rozciągniętym obiektem typu solid.move_towards_point
Ostatnia funkcja, która zgodnie z dokumentacją być może wydawała by się najbardziej oczywista, wybrana została nieprzypadkowo jako ostatnia. Problem z move_towards_point jest bowiem taki, że ta funkcja porusza postać wyłącznie o podaną przez nas prędkość, więc w sytuacji, gdy zostaje mniej pikseli do zrobienia (np. prędkość równa się 10, a został 1 piksel), postać "przeskoczy" punkt docelowy (poruszając się o te wybrane 10 pikseli, a więc o 9 za daleko).
kodif (x != destx or y != desty) {
move_towards_point(destx, desty, 4); /* porusz się z prędkością 4 pikseli */
}
Oczywiście i temu da się zaradzić - wytarczy sprawdzić, czy odległość do punktu docelowego jest mniejsza niż wybrana przez nas prędkość:
kodif (x != destx or y != desty) {
if (point_distance(x, y, destx, desty) <= 4) {
/* jeśli zostało mniej lub równo 4 piksele, ustaw postać w miejscu docelowym*/
x = destx;
y = desty;
} else {
move_towards_point(destx, desty, 4);
}
}
Sztuczka polega na tym, że skoro zostało nie więcej niż 4 piksele, jakikolwiek ruch sprawi wrażenie, że wykonaliśmy po prostu kruszy ruch i dotarliśmy na miejsce - dla ludzkiego oka będzie nie do zauważenia.
Warunek o dystansie jest pierwszy, aby zapobiec wykonaniu się funkcji move, oraz aby nie wykonać dodatkowego ruchu już po niej (w tej sytuacji bowiem postać ruszyła by się najpierw o 4 piksele, a potem o dodatkowe między 0.1 a 4 piksele, co w skrajnym przypadku dałoby aż 8 pikseli i było widoczne, jako "przyciągnięcie").