Czy źle jest „mapować” pozycję myszy na ekranie, aby wykrywanie kolizji działało niezależnie od rozdzielczości?

16

Rozważ grę, której domyślna rozdzielczość to 800 x 600. Obiekty z maskami kolizyjnymi są umieszczane w świecie gry o wymiarach 800 x 600. Maski kolizyjne mogą wykryć zderzenie myszy z nimi.

Rozważmy teraz skalowanie gry do 1024 x 768 (załóżmy, że skalujemy grafikę, po prostu renderując wszystko na warstwę, a następnie skalując całą warstwę jednocześnie). Mamy dwie opcje prawidłowego działania kolizji z myszą w nowej rozdzielczości:

A.) Powiększ świat do 1024 x 768 i odpowiednio skaluj maskę kolizji każdego obiektu.

B.) „Mapuj” pozycję myszy na oryginalnym świecie (800 x 600).

Przez „mapę” rozumiem po prostu przeskaluj pozycję myszy do oryginalnego świata 800x600. Na przykład, jeśli pozycja myszy na ekranie to (1024, 768), to pozycja myszy na świecie to (800, 600).

Teraz, oczywiście, opcja B wymaga znacznie mniej obliczeń i prawdopodobnie jest mniej podatna na błędy geometryczne, ale wydaje mi się to również trochę „hacking”, podobnie jak istnieją nieprzewidziane konsekwencje korzystania z tej metody, którą będzie piekło naprawić później.

Którą metodą powinienem stosować: A, B lub coś innego?

użytkownik3002473
źródło
2
Jak wskazał Classic Thunder, współrzędne wyświetlania nie powinny być takie same jak współrzędne świata lub współrzędne obiektu. Zazwyczaj chcesz mieć transformacje (w zasadzie, jak mówisz, odwzorowania , ale zwykle wykonywane za pomocą matematyki macierzy).
Dan

Odpowiedzi:

38

Zazwyczaj (nawet w przypadku gier 2d) istnieje osobny układ współrzędnych dla gry, który jest niezależny od rozdzielczości, co zwykle określa się mianem przestrzeni świata. Umożliwia to skalowanie do dowolnej rozdzielczości.

Najłatwiejszym sposobem na osiągnięcie tego jest użycie kamery 2D, która jest zasadniczo matrycą, która określa przejście z przestrzeni świata (twoich dowolnych jednostek) do przestrzeni ekranu (pikseli na ekranie). To sprawia, że ​​radzenie sobie z matematyką jest banalne.

Spójrz na poniższy rozdział przewijania kamery XNA 2d - po co używać transformacji macierzowej?

Ułatwia to konwersję między definicjami układu współrzędnych

Aby przejść z ekranu do przestrzeni kosmicznej, wystarczy użyć Vector2.Transform. Jest to powszechnie używane do uzyskiwania położenia myszy na świecie do wybierania obiektów.

Vector2.Transform(mouseLocation, Matrix.Invert(Camera.TransformMatrix));

Aby przejść ze świata do miejsca na ekranie, po prostu postępuj odwrotnie.

Vector2.Transform(mouseLocation, Camera.TransformMatrix);

Korzystanie z matrycy nie ma żadnych wad, ale wymaga trochę nauki.

ClassicThunder
źródło
... i, ogólnie rzecz biorąc, nowoczesny sprzęt jest zaprojektowany do operacji matrycowych za pomocą architektur wektoryzacyjnych, takich jak SSE, NEON itp. Jest to więc mądry wybór - zaoszczędzisz cykli procesora w porównaniu z podejściami niewektoryzowanymi.
Inżynier
1
Łagodne zastrzeżenie: XNA zostało przerwane przez Microsoft, ale Monogame używa tego samego API.
Pharap
1
Jasne, mądrze jest używać macierzy do translacji między dwoma układami współrzędnych. Ale jak to odpowiada na pytanie? Nadal musisz zdecydować, czy chcesz zmapować z systemu A do systemu B lub z B do A i jakie są konsekwencje, jeśli takie istnieją.
mastov
„Czy złym pomysłem jest„ mapowanie ”położenia myszy na ekranie, aby wykrywanie kolizji działało niezależnie od rozdzielczości?” odpowiada: „mądrze jest używać macierzy do tłumaczenia między dwoma układami współrzędnych” ...
ClassicThunder
Odpowiadam również „Musisz jeszcze zdecydować, czy chcesz zmapować system A na system B lub B na A i jakie są konsekwencje, jeśli takie istnieją”. mówiąc, że powinieneś użyć dowolnego, niepowiązanego z rozdzielczością i skalowania. Więc C -> A lub C -> B w zależności od tego, czy A lub B jest rozdzielczością. Oczywiście możesz mieć C równą rozdzielczości podstawowej, dla której projektujesz i skalować stamtąd. Punkt jest wszystkim, matematyka dzieje się w tym samym układzie współrzędnych, a skalujesz tylko do renderowania.
ClassicThunder
2

Inną opcją byłoby: Przy każdym wejściowym zdarzeniu ruchu myszy przesuń kursor myszy w grze o liczbę pikseli gry odpowiadającą liczbie pikseli w zdarzeniu myszy. Dzieje się tak naturalnie w przypadku gier 3D, które blokują prawdziwy wskaźnik myszy na środku i obracają kierunek celowania o wartość odpowiadającą ruchowi myszy na wejściu, ale można to zrobić w ten sam sposób, przesuwając duszka reprezentującego kursor myszy w grze.

Oczywiście musisz zignorować wszelkie zdarzenia ruchu myszy wejściowej spowodowane przez wypaczenie go do środka, a jeśli gra ma jakieś opóźnienie wejściowe w ogóle, brak reakcji kursora będzie najbardziej niepokojącym i zauważalnym aspektem.

Częściowo, jakie rozwiązanie zastosujesz, zależy od tego, jak ważna jest pozycja myszy w grze. Jeśli jest to RTS, a gracz po prostu klika, aby wybrać jednostki, prawdopodobnie możesz po prostu wybrać jedną z A lub B, która jest łatwiejsza. Jeśli jest to strzelanka odgórna, a mysz bezpośrednio kontroluje ruchy postaci, prawdopodobnie będziesz potrzebować bardziej dogłębnego rozwiązania, które ogranicza zakres zmienności sposobu poruszania się postaci na podstawie nie tylko rozdzielczości, ale także rzeczy takich jak ruch myszy prędkość. Jeśli zamiast tego mysz steruje kierowaniem, będziesz potrzebować innego rozwiązania itp.

Losowo 832
źródło
0

Odpowiedź ClassicThundera jest poprawna, ale chciałbym podać przykład alternatywnego / prostszego sposobu osiągnięcia pożądanego efektu. Jest to prostsze rozwiązanie do szybkiego prototypowania, przypadków, w których nie masz dostępu do w pełni funkcjonalnej biblioteki, lub w przypadkach, gdy nie masz dostępu do GPU (np. W systemach wbudowanych).

Aby wykonać wspomniane mapowanie, możesz użyć następującej funkcji (zakładając, że zostanie zdefiniowana w klasie statycznej Helper):

static float Map(float value, float fromLow, float fromHigh, float toLow, float toHigh)
{
    return ((value - fromLow) / (fromHigh - fromLow) * (toHigh - toLow)) + toLow;
}

(Nie określiłeś języka, ale widzę, że znasz trochę C #, więc mój przykład jest w C #).

Następnie możesz użyć tej funkcji w następujący sposób:

float mouseXInWorld = Helper.Map(Mouse.X, 0, Screen.Width - 1, camera.Bounds.X, camera.Bounds.X + camera.Bounds.Width - 1);
float mouseYInWorld = Helper.Map(Mouse.Y, 0, Screen.Height - 1, camera.Bounds.Y, camera.Bounds.Y + camera.Bounds.Height - 1);

Gdzie camera.Boundsjest prostokąt reprezentujący obszar świata, który może zobaczyć kamera (tj. Obszar rzutowany na ekran).

Jeśli masz klasę Vectorlub Point, możesz jeszcze bardziej uprościć ten proces, tworząc 2D odpowiednik funkcji mapy, na przykład:

static Vector Map(Vector value, Rectangle fromArea, Rectangle toArea)
{
    Vector result = new Vector();
    result.X = Map(value.X, fromArea.X, fromArea.X + fromArea.Width - 1, toArea.X, toArea.X + toArea.Width - 1);
    result.Y = Map(value.Y, fromArea.Y, fromArea.Y + fromArea.Height - 1, toArea.Y, toArea.Y + toArea.Height - 1);
    return result;
}

Co sprawiłoby, że Twój kod mapowania byłby prostą linią:

Vector mousePosInWorld = Map(Mouse.Pos, Screen.Bounds, camera.Bounds);
Pharap
źródło
-1

Opcja (C): zmień rozdzielczość ekranu z powrotem na 800x600.

Nawet jeśli tego nie zrobisz, rozważ to jako eksperyment myślowy. W takim przypadku to do wyświetlacza należy zmiana rozmiaru grafiki w celu dopasowania do jego fizycznego rozmiaru, a następnie do odpowiedzialności systemu operacyjnego za dostarczenie zdarzeń wskaźnika w rozdzielczości 800x600.

Myślę, że wszystko zależy od tego, czy twoja grafika to bitmapa czy wektor. Jeśli są to mapy bitowe, a ty renderujesz do bufora 800 x 600, to tak, znacznie łatwiej jest zmienić mapowanie myszy w przestrzeń ekranu i zignorować rzeczywistą rozdzielczość ekranu. Jednak dużym minusem tego jest to, że skalowanie może wyglądać brzydko, szczególnie jeśli robisz blokową grafikę w stylu „8-bit”.

pjc50
źródło