Bestimmen, ob Spieler B im Sichtfeld von Spieler A ist

  • Allgemein

Es gibt 11 Antworten in diesem Thema. Der letzte Beitrag () ist von LaMiy.

    Bestimmen, ob Spieler B im Sichtfeld von Spieler A ist

    Moin!

    Ich schreibe derzeit ein kleines Spiel (eine Art Tower-Defense) und bastle wie in der Überschrift beschrieben derzeit an einer Methode, die feststellen soll, ob der eine Spieler im SIchtfeld eines anderen Spieler auftaucht.
    Meine Spieler-Klasse besitzt X- und Y-Koordinate sowie eine Rotation um die Z-Achse (Yaw). Sollte einfach sein, da 2D. Denkste.

    Mein Ansatz:

    Quellcode

    1. private bool PlayerSeesPlayer(Player playerA, Player playerB)
    2. {
    3. Vector2 line = playerA.Vector2 - playerB.Vector2;
    4. float yaw = DegToRad(playerA.Yaw);
    5. Vector2 yawLine = new Vector2((float)Math.Sin(yaw), (float)Math.Cos(yaw));
    6. line.Normalize();
    7. yawLine.Normalize();
    8. float angle = (float)Math.Acos(DotProduct(line,yawLine));
    9. float degree = RadToDeg(angle);
    10. return Math.Abs(degree) < 90;
    11. }
    12. private float DegToRad(float deg) { return (float)(deg * (Math.PI / 180f)); }
    13. private float RadToDeg(float rad) { return (float)(rad * (180f / Math.PI)); }
    14. private float DotProduct(Vector2 v1, Vector2 v2) { return (v1.X * v2.X) + (v1.Y * v2.Y); }


    So dachte ich mir das:
    (Entsprechendes Bild im Anhang, Variablen aus meinem Quellcode entsprechend benannt)
    1. Erstelle einen Vektor, der quasi die Luftlinie zwischen beiden Spielern angibt. (line)
    2. Erstelle einen Vektor, der in die Blickrichtung von SpielerA zeigt. (yawLine)
    3. Normalisiere beide Vektoren, um den acos bilden zu können.
    4. Bilde den Winkel zwischen der Luftlinie und der Blickrichtung (angle)
    (4.1 Bilde den Acos aus dem Skalarprodukt beider Vektoren)
    5. Übersetze zurück in Grad (degree)
    6. Wenn der Betrag des Winkels kleiner als 90° ist, sollte sich SpielerB in einem 180° Sichtfeld von SpielerA befinden.

    Problem:
    Meine Methode erkennt nur Spieler in >einer< Hälfte des Sichtfeldes von SpielerA, in der anderen "erkennt" er Spieler, die sich eigentlich auf der gegenüberliegenden Seite hinter ihm befinden.

    -> Irgendwas mache ich hier falsch, komme aber auch nach Stunden der Recherche und des Nachrechnens nicht darauf. (Ist auch mein erstes Projekt dieser Art)

    Es würde mich sehr freuen, wenn mir hier jemand helfen könnte! Trigonometrie und Vektorräume sind nicht gerade mein Fachgebiet, aber ich arbeite dran :).

    [Btw.: Da das hier in erster Linie ein mathematisches Problem ist, hab' ich es als "Allgemein" gekennzeichnet. Ich selbst schreibe in C#, jedoch sollte sich meine Methode, sobald fertig, auch in anderen Sprachen nutzen lassen]

    MfG,
    X-Zat / Mo
    Bilder
    • problem.png

      4,08 kB, 354×217, 138 mal angesehen
    Ich bin mir nicht sicher, aber eins steht fest:
    Der Winkel ist nicht der acos vom Skalarprodukt, sondern der acos vom Quotienten aus Skalarprodukt und Produkt der Längen (alpha = acos( dotProduct(VA, VB) / (VA.length * VB.length)))
    »There's no need to "teach" atheism. It's the natural result of education without indoctrination.« — Ricky Gervais
    @X-Zat Löse das zunächst grafisch. Und mach Dir ein lokales Koordinatensystem, wo der eine Spieler im Ursprung und der andere auf einer Achse liegt
    oder
    mach eine Achse durch beide Spieler und leg den Ursprung in den Mittelpunkt davon.
    Dies hat keinen Einfluss auf die Lösung, vereinfacht aber die Herangehensweise.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    also ich verstehe ein Sichtfeld als 2 Winkel, einen kleineren, einen größeren, und was dazwischen liegt, wird gesehen.
    Nun bestimme den Vektor-Winkel von Spieler1 zu Spieler2, und guck, ob er dazwischen liegt.

    Den Winkel eines Punktes zur X-Achse erhälst du mit Math.Atan2(X, Y)

    Bei der Zwischen-Bestimmung taucht dann das Problem mittm Nulldurchgang auf, also ein Spieler kann ja ein Sichtfeld von 270° - 90° haben, solch könnte man dann zB als 2 Sichtfelder berechnen: von 270-360 und von 0-90.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()

    Hi
    besser: Gar nicht erst den Winkel bestimmen, sondern eine Konstante bestimmen, mit der das ganze gerechnet wird.

    Wenn du dir das anschaust: A ist der Ausgangspunkt, P ist der Zielpunkt, also der Punkt, für den überprüft werden soll, ob er innerhalb des Sichtfelds ist, d ist der Vektor von A nach P, v der Blickrichtungsvektor (seine Länge sei einfach mal als 1 angenommen). Das Sichtfeld ist durch den Winkel α definiert. Was jetzt gesucht ist, ist, ob der Betrag (es sollen ja beide Richtungen untersucht werden!, d.h. links der Blickrichtung soll < 0°, rechts der Blickrichtung soll > 0° sein) des Winkels (der Winkel soll zwischen -180° und 180° liegen) zwischen d und v kleiner oder gleich α/2 ist. Was nun gilt, wenn β der Winkel zwischen v und d ist:
    cos(β) = A/H = (v dot d/|d|)
    Deine Hypotenusenverhältnis H ist d/|d|, dein Ankathetenverhältnis A ist v. Jetzt gilt aber, dass
    cos(a) <= cos(b) <=> a <= b, wenn a, b in [-90°, 90°] liegen, oder?
    Berechnet man nun also c = cos(α/2) vorweg, muss man nur noch überprüfen, ob
    cos(β) <= c
    ist. cos(β) konnte man aber mithilfe der Vektorfunktionalität effizient berechnen und die Wurzel durch die Normalisierung kann man sogar auch noch eliminieren:
    (v dot d/|d|) = (v dot d)/|d|
    <=>
    m = v dot d
    m * m <= c * c * (d1 * d1 + d2 * d2)

    Und schon sind alle Performancefresser raus. Multiplikation statt Math.Pow, Math.Sqrt durch Quadrierung eliminiert und der Betrag wird auch gleich eingerechnet.

    Zumindest, wenn ich mich jetzt nicht völlig täusche (bin z.Z. etwas unkonzentriert, so viel Stress :D) müsste es der Cosinus sein. Was jetzt allerdings nicht berücksichtigt ist, ist, dass der Punkt auch "hinter" A liegen könnte. Das kann man einfach verhindern, indem man abfragt, ob m > 0 ist.

    Gruß
    ~blaze~
    Wow, danke für eure Antworten!

    Visualisiert habe ich das ganze mal und ja, das hilft schon dabei, sich nur das wesentliche anzuschauen und zu berücksichtigen.

    @blaze: Deinen Ansatz werde ich gerne mal testen, scheint mir schlüssig und schaut gut aus!

    Mittlerweile habe ich eine funktionierende Methode zusammengeschustert (durch trial&error), jedoch möchte ich sie gerne so gut es geht verbessern in Hinsicht auf Performance, Sonderfälle und Präzision. Da ich gerade nicht an meinem Rechner daheim sitze, werde ich versuchen, dran zu denken, euch meinen jetzigen Code auch nochmal zu zeigen ;)

    Gruß,
    X-Zat / Mo
    So, da ich nun wieder am eigenen Rechner sitze, meine Methode (die soweit ganz gut funktioniert):

    Quellcode

    1. private const float fovDegree = 45f; //Größe des Winkels (des Sichtfeldes) link/rechts der Blickrichtung
    2. public static bool PlayerSeesPlayer(Player playerA, Player playerB)
    3. {
    4. Vector2 line = (playerA.Vector2 - playerB.Vector2);
    5. float yaw = DegToRad(playerA.Yaw-90);
    6. Vector2 yawLine = new Vector2((float)Math.Sin(yaw), (float)Math.Cos(yaw));
    7. line.Normalize();
    8. yawLine.Normalize();
    9. float angle = (float)Math.Atan2(yawLine.Y - line.Y, yawLine.X - line.X);
    10. float degree = RadToDeg(angle);
    11. return Math.Abs(degree) >= 90 - fovDegree / 2f && Math.Abs(degree) <= 90 + fovDegree / 2f;
    12. }


    Es war der Arkustangens, nicht der Arkuskosinus :/ Wie gesagt, mit Trigonometrie wurde ich bisher noch nicht sonderlich warm.
    Da ich mein Projekt ohnehin komplett neu schreiben werde, werde ich dabei auch direkt blaze's Ansatz einbauen und sehr gerne testen!

    Gruß,
    X-Zat / Mo