Układ Współrzędnych - jak skierować obiekt po najkrótszej linii z punktu A do punktu B w C#

0

Witam. Jestem w trakcie tworzenia gry. Opis mojego projektu można zobaczyć tutaj:

Otóż w grze posiadam jednostki o współrzędnych posX oraz posY (dwa wymiary).

Jednostki poruszają się za pomocą metody move:

public class Objects
    {
        public string ship_name; // name of a ship
        public bool is_detected; // is unit detected by any friendly unit?
        public double ship_posX; // position X on map
        public double ship_posY; // position Y on map 

public void move(int heading, int speed)
        {
            if (speed > 0)
            {
                if (heading == 0)
                {
                    if (ship_posY > 2) ship_posY -= speed; // ship is heading NORTH
                    if (ship_posY <= 3) speed = 0; // gdy okręt dopłynie do krawędzi mapy i jest wciąż skierowany w jej kierunku, to wyzeruj jego prędkość
                }
                if (heading == 45) // ship is heading NORTH-EAST
                {
                    if (ship_posX < 8910) ship_posX += speed;
                    if (ship_posY > 2) ship_posY -= speed;
                    if (ship_posY <= 2) speed = 0; // gdy okręt dopłynie do krawędzi mapy i jest wciąż skierowany w jej kierunku, to wyzeruj jego prędkość
                }
                if (heading == 90)
                {
                    if (ship_posX < 8910) ship_posX += speed; // ship is heading EAST
                    if (ship_posX >= 8908) speed = 0; // gdy okręt dopłynie do krawędzi mapy i jest wciąż skierowany w jej kierunku, to wyzeruj jego prędkość
                }
                if (heading == 135) // ship is heading SOUTH-EAST
                {
                    if (ship_posX < 8910) ship_posX += speed;
                    if (ship_posX >= 8908) speed = 0; // gdy okręt dopłynie do krawędzi mapy i jest wciąż skierowany w jej kierunku, to wyzeruj jego prędkość
                    if (ship_posY < 6410) ship_posY += speed;
                }
                if (heading == 180)
                {
                    if (ship_posY < 6410) ship_posY += speed; // ship is heading SOUTH
                }
                if (heading == 225) // ship is heading SOUTH-WEST
                {
                    if (ship_posX > 2) ship_posX -= speed;
                    if (ship_posX <= 2) speed = 0; // gdy okręt dopłynie do krawędzi mapy i jest wciąż skierowany w jej kierunku, to wyzeruj jego prędkość
                    if (ship_posY < 6410) ship_posY += speed;
                }
                if (heading == 270)
                {
                    if (ship_posX > 2) ship_posX -= speed; // ship is heading WEST
                    if (ship_posX <= 2) speed = 0; // gdy okręt dopłynie do krawędzi mapy i jest wciąż skierowany w jej kierunku, to wyzeruj jego prędkość
                }
                if (heading == 315) // ship is heading NORTH-WEST
                {
                    if (ship_posX > 2) ship_posX -= speed;
                    if (ship_posX <= 2) speed = 0; // gdy okręt dopłynie do krawędzi mapy i jest wciąż skierowany w jej kierunku, to wyzeruj jego prędkość
                    if (ship_posY > 2) ship_posY -= speed;
                    if (ship_posY <= 2) speed = 0; // gdy okręt dopłynie do krawędzi mapy i jest wciąż skierowany w jej kierunku, to wyzeruj jego prędkość
                }
            }
        } // end of void move
}

Klasę wykorzystuję w następujący sposób:

public Objects[] unit; // w public partial class Form1 : Form

// a to poniżej w Form Load (gdy uruchamia się gra):

unit = new Objects[100];
            for (int i = 0; i < 100; i++)
            {
                unit[i] = new Objects();
            }

// przesuwanie jednostek, w Timer1, co sekundę:

unit[0].move(unit[0].ship_heading, unit[0].ship_current_speed);

// Współrzędne X odczytuje się ze zmiennej: unit[0].ship_posX
// Współrzędne Y odczytuje się ze zmiennej: unit[0].ship_posY


Zastanawiam się od wielu dni, jak sprawić, aby okręt płynął prostym kursem prosto do wyznaczonego punktu (może sam wyznaczać kurs, jeśli jest do tego jakiś wzór)?

title

Czytałem coś na temat Algorytmu Dijkstry, ale nie potrafię tego zastosować w swoim projekcie mając moje nazwy zmiennych. Widziałem w innych grach, że jednostki po kliknięciu w określony punkt mapy gnają prosto w jego kierunku. Sposób na ominięcie przeszkód nie będzie teraz tematem do dyskusji, niech jednostki przesuwają się kursem prostym w kierunku celu.

Dodam tylko, że układ współrzędnych nie jest taki idealnie jak się uczyliśmy w szkole, gdyż nie ma tu wartości ujemnych. (ikony jednostek to PictureBoxy (o nazwie PictureBoxObjectX, gdzie X to numer jednostki), które są ustawione na innym PictureBoxMainMap, do którego jest wczytywana mapa gry). Jednostki są przesuwane po mapie co sekundę za pomocą timera poleceniem:

PictureBoxObject0.Location = new Point(x, y);

Ponieważ mapa gry to PictureBox, nie może mieć wartości ujemnych, dlatego:
a) lewy górny róg mapy ma współrzędne (1,1)
b) prawy górny róg mapy ma współrzędne (9000, 1)
c) lewy dolny róg mapy ma współrzędne (1, 6420)
d) prawy dolny róg mapy ma współrzędne (9000, 6420)

Przesuwając wskaźnik myszy po obrazie w paincie też na tej samej zasadzie wyświetlane są współrzędne i działa to w ten sam sposób.

Mam nadzieję, że w miarę czytelnie objaśniłem swój problem?

1

Jeśli jest tak jak na rysunku, i Potrzebujesz kolejnych wartości x i y, takich aby dostać się z punktu do punktu, to Znajdź równania parametryczne prostej przechodzącej przez dwa punkty[1]. Trzeba tylko pamiętać o odpowiednim dobraniu przedziału zmienności parametru (żeby poruszać się od A do B).
[1] https://pl.wikipedia.org/wiki/Prosta#Trzy_punkty_na_prostej

1

Ponieważ dotyczy to powierzchni kuli, wyrzucę lekturę na tematy: ortodroma, loksodroma i co tam wikipedia podpowie. Jedna jest najłatwiejsza w nawigacji (stały kurs), druga jest najkrótsza (ale kurs zmienny)

0

Z tego co widać, masz silnie zdyskretyzowany heading (wielokrotność 45 stopni). Wyznacz ciągły heading jako atan2(deltay, deltax), gdzie deltax i deltay to różnica pozycji docelowej i startowej odpowiednio po x i y. Mając wyznaczony ciągły heading dla całej podróży (z punktu A do punktu B) możesz wyznaczyć sekwencję pól po których ma się poruszać, a która aproksymuje wektor podróży. Możesz to zrobić wyznaczając w sposób ciągły współrzędne punktu, w którym okręt ma się znaleźć po każdym ruchu, a następnie aproksymując je przybliżając do najbliższego pola. Zależy też jaką długość ma u Ciebie jednostka pola.

0
GutekSan napisał(a):

Z tego co widać, masz silnie zdyskretyzowany heading (wielokrotność 45 stopni). Wyznacz ciągły heading jako atan2(deltay, deltax), gdzie deltax i deltay to różnica pozycji docelowej i startowej odpowiednio po x i y. Mając wyznaczony ciągły heading dla całej podróży (z punktu A do punktu B) możesz wyznaczyć sekwencję pól po których ma się poruszać, a która aproksymuje wektor podróży. Możesz to zrobić wyznaczając w sposób ciągły współrzędne punktu, w którym okręt ma się znaleźć po każdym ruchu, a następnie aproksymując je przybliżając do najbliższego pola. Zależy też jaką długość ma u Ciebie jednostka pola.

Gutek. Tak. Jest wielokrotność, bo wtedy najłatwiej mi obliczyć jak ma się okręt przesuwać :). Co do Twojego rozwiązania: Nie wiem, czy dobrze zrozumiałem to, co masz na myśli, ale próbując to zaimplementować wyszło mi coś takiego:

double x1, y1, x2, y2, deltax, deltay, result;

            x1 = 4761; 
            y1 = 1689; 

            x2 = 4025; 
            y2 = 1168;

            deltax = x1 - x2; 
            deltay = y1 - y2;
            
            result = Math.Atan2(deltax, deltay);

            label29.Text = result.ToString(); // daje wynik 0.95480 (i wiele liczb po przecinku)

Jeśli wszystko jest dobrze, to i tak nie wiem, jak to 0.95480 wykorzystać. Myślałem, że wartość wyjdzie mi około 300 stopni, biorąc pod uwagę współrzędne jednego punktu i drugiego. U mnie jednostka pola ma długość 1 piksel, ale to tylko na chwilę obecną, bo w grze 1px = 1 mila morska. Docelowo okręty będą płynąć wolniej, bo przy powyższym kodzie jeśli ustawię prędkość na 21, to płyną 21 pikseli (mil) NA SEKUNDĘ, a mam to zmienić by było na godzinę, więc pewnie będzie coś takiego później:

if (ship_posX > 2) ship_posX = ship_posX -(speed/3600);
0
MariuszPoz napisał(a):

Jeśli wszystko jest dobrze, to i tak nie wiem, jak to 0.95480 wykorzystać. Myślałem, że wartość wyjdzie mi około 300 stopni, biorąc pod uwagę współrzędne jednego punktu i drugiego.

Po pierwsze, napisałem atan2(deltay, deltax), nie atan2(deltax,deltay). Prawidłowy wynik to 0.616.

Po drugie, to jest wynik w radianach. Jeśli pomnożysz to przez 180/Pi (współczynnik konwersji) to dostaniesz wynik w stopniach - ok. 35. Dlaczego nie ok. 300? To zależy od przyjętego radialnego układu współrzędnych i kierunku osi Y. 35 stopni jest w układzie, w którym kąty są liczone od osi X w kierunku przeciwnym do wskazówek zegara, a os Y jest skierowana do góry Jak przyjmiesz układ z osią Y skierowaną w dół to wyjdzie Ci ok 325 stopni. Ale ta konwersja nie jest Ci potrzebna, przyjmij że heading = result (w tym przykładzie 0.616..)

U mnie jednostka pola ma długość 1 piksel, ale to tylko na chwilę obecną, bo w grze 1px = 1 mila morska. Docelowo okręty będą płynąć wolniej, bo przy powyższym kodzie jeśli ustawię prędkość na 21, to płyną 21 pikseli (mil) NA SEKUNDĘ, a mam to zmienić by było na godzinę, więc pewnie będzie coś takiego później:

if (ship_posX > 2) ship_posX = ship_posX -(speed/3600);

Heading i speed definiują Ci razem wektor prędkości. Wyznacz składowe X i Y tego wektora jako:

speed_x = speed*cos(heading);
speed_y = speed*sin(heading);
// t to jednostka czasu co którą uaktualniasz pozycję.
ship_posX = ship_posX + speed_x*t;
ship_posY = ship_posY + speed_y*t;

Przykładowo, dla danych które podałeś, po czasie t=3 j.cz,, przy prędkości 21 pixeli/j.cz.. nowa pozycja statku będzie:

ship_posX = ship_posX + (int) 21*cos(0.616)*3 = ship_posX + (int) 51.42 = ship_posX + 51;
ship_posY = ship_posY + (int) 21*sin(0.616)*3 = ship_poxY + (int) 36.4 = ship_poxY + 36;
0

ŚWIETNIE. Jak na razie z braku czasu i przez zajmowanie się dzieckiem udało mi się zaimplementować tylko (w osobnym projekcie) jak obliczyć kąt z jednego punktu do drugiego, ale tak, jak wspomniałeś, wychodzi mi około 35 stopni. A czy biorąc pod uwagę, że ten mój układ współrzędnych jest trochę "odwrócony" tzn Y w dół rośnie, jak przeliczyć poprawnie kąt, bo jeśli odejmę wynik od 360:

          double  x1 = 4761
          double  y1 = 1689

           double x2 = 4025
           double y2 = 1168

            double deltax = x1 - x2; // 736
            double deltay = y1 - y2; // 521

            double result = 360-((Math.Atan2(deltay, deltax)) * 180 / Math.PI); // 360 minus wynik

to w tym konkretnym przypadku wyjdzie mi właśnie około 325 stopni. Lecz jeżeli zamienię zmienne miejscami:

          double  x1 = 4025
          double  y1 = 1168

           double x2 = 4761
           double y2 = 1689

            double deltax = x1 - x2; // -736
            double deltay = y1 - y2; // -521

            double result = 360-((Math.Atan2(deltay, deltax)) * 180 / Math.PI); // 360 minus wynik

, to wykorzystując ten sam kod dla result da mi wynik 504 stopnie. Jest jakiś sposób aby zawsze wychodziło poprawnie? Może jakieś instrukcje warunkowe zastosować trzeba? Gdybym jeszcze odjął 360 od 504 to wyjdzie mi 144, ale nie wiem, czy to właściwy kąt i czy stosując jakoś instrukcje warunkowe mogę zawsze otrzymać poprawny kąt dla mojego "układy współrzędnego", gdzie nie ma wartości ujemnych. Proszę o pomoc najpierw z tym problemem. Po rozwiązaniu tego, spróbuję zabrać się za następne.

0

Powtarzam: nie rób konwersji na stopnie. Trzymaj heading (=result) w radianach, bo potem i tak go będziesz używał w radianach.
Jeśli masz wątpliwość czy dostałeś właściwy kąt, zawsze pozostaje rysunek na kartce i narysowanie sobie wykresu sin i cos od 0 do 2 PI. Pamiętaj, że wszystkie funkcje trygonometryczne są okresowe, czyli jakikolwiek a > 2pi jest tożsamy z a-2pi

EDIT:
Jeszcze co do kierunku osi Y. Zmieniając zwrot osi Y zmień także kierunek liczenia kątów, z przeciwnego do ruchu wskazówek zegara, na zgodny. Wtedy właściwym wynikiem będzie właśnie te 35 stopni, a nie 325.

1 użytkowników online, w tym zalogowanych: 0, gości: 1