Von Schildern und Dialogen

Jaa ich klaue mal die Titelstruktur von Zombiepriester :wink:

Es geht um folgendes:

Ich will bei einem ganz normalen rpg schilder auf der karte verteilen, und beim „drüberlaufen“
eine box mit einer hilfemeldung erscheinen lassen. zB schild „zum wald“ und text „nicht empfehlenswert, waffe zu schwach“ oder so was.
Nun, mir fällt nicht ein wie man das möglichst „proffesionell“ machen könnte. Ich habe daran gedacht das ich bei jedem gameloop durchlauf
die spielerkoordinaten an die Klassse Sign übergebe, die SignListener implementiert - sign listener hätte dann die methode „showSignFromCoords“ oder so was.
aber wenn ich 50 schilder auf der map hätte, dann hätte ich ja 50 if abfragen ob der spieler im bereich jenes 1 bis 50.ten schildes ist…

Also, hat einer idee wie man sowas „besser“ lösen könnte? Oder sogar selber schon mal jemand gemacht und könnte ein wenig code posten?

Vielen Dank!

*** Edit ***

Mir fällt gerade folgendes ein: Man könnte ja ein array von rectangles anlegen die die schildcoordinaten enthalten, (natürlich automatisiert - also neues schild neues rectangle)
aber dann würde ich ja immer noch in einer for loop alle diese rectangles durchlaufen und mit dem spieler rectangle vergleichen… wäre das vielleicht eine akzeptable lösung?

*** Edit 2.0***

ok mir ist noch was aufgefallen: Im prinzip muss ich ja einen eigenen listener schreiben, wie eben der mouseListener auch prüfen kann ob ich „mit der maus über ein objekt fahre“.
aber wie ist das da gelöst? Gibt es dazu code? (ich suche mal, aber vielleicht weiss es ja jemand) Im prinzip müsste ich ja sowas wie einen ObjectOverObject Listener schreiben…

Wenn deine Map in Blocks aufgeteilt wäre ginge es relativ einfach
if(block.hasSign() blabla

Aber da du da wahrscheinlich auch selbst drauf gekommen wärst denke ich dass du keine Blockstruktur hast.
Ich würde es wahrscheinlich auch mit Rectangles oder mit Points machen.
Wobei das bei einer sehr großen Map mit (übertieben) 5000 Schildern wahrscheinlich schon spürbar wäre.
Dann müsste man vlt. immer nur einen Teil der Map laden und nur das berechnen, aber frag mich nicht wie du das machen sollst, nur so ein Gedanke. Bei nur 50 Schildern wäre da wahrscheinlich auch der Aufwand zu groß.

Auch bei einem MouseListener läuft es auf eine einzelne Überprüfung hinaus. Sinngemäß (!) endet das auch bei sowas wie
for (Component c : allComponents) if (c.contains(mousePosition)) sendEventsToListeners();

Aber wirklich nur sinngemäß: Praktischerweise ist die Struktur eines GUIs genau das, was man im allgemeinen Fall als Beschleunigungsstrukur für genau solche Abfragen einsetzt - nämlich eine Baum-Datenstruktur. Bei sowas wie

rootPanel.add(childPanel0);
rootPanel.add(childPanel1);

childPanel0.add(grandChildPanel00);
childPanel0.add(grandChildPanel01);

childPanel1.add(grandChildPanel10);
childPanel1.add(grandChildPanel11);

müssen am Ende ja nicht alle 4 grandChildPanels überprüft werden: Erst wird überprüft, ob die Maus im childPanel0 oder childPanel1 ist, und danach müssen ja nur noch dessen grandChildPanels überprüft werden.

Wenn man wirklich “viele” Punkte überprüfen muss, lohnt sich das, weil die Überprüfung von n Punkten mit einer einfachen Schleife eben Laufzeit O(n) hat, aber wenn man dafür einmal so einen Baum berechnet, hat eine Abfrage nur noch Laufzeit O(logn).

Für diese Bäume gibt es verschiedenste Ausprägungen für verschiedene Fälle. Sowas wie http://en.wikipedia.org/wiki/Quadtree sollte recht leicht zu implementieren sein und grundsätzlich auf deinen Anwendungsfall passsen. (Zum Glück muss der ja nicht (oder selten) aktualisiert werden, weil die Schilder sich wohl nicht sooo oft ändern).

Aber… selbst bei 50 oder auch 100 Schildern würde ich mir da noch nicht sooo viele Gedanken machen. Eine Abfrage wie
for (Sign s : signs) if (s.contains(point)) tuWas();
dauert nur wenige Mikrosekunden. Selbst bei 1000 Schildern würde ich noch vermuten, dass es zeitkritischere Dinge gibt. Aber im Idealfall kann man das ja ganz gut wegabstrahieren. Mit zwei Methoden…

void addSign(Sign s) { ... }
Sign findSignAt(Point p) { ... }

hat man die nötige Funktionalität soweit ich das sehe schon gekapselt. In der ersten Version werden die Schilder einfach mit einer Liste und einer for-Schleife behandelt. Falls es irgendwann mal um 10000 Schilder geht, und zu viel zeit frißt, kann man das ganze durch geeignete Implementierung dieser beiden Methoden auf einen Octree umstellen: Bei addSign wird das Schild in den Octree eingepflegt, und bei findSignAt wird der Octree traversiert.

Ich kenne mich jetzt nicht mit Spieleentwicklung aus, aber statt die Koordinaten immer zu einem festen Zeitpunkt zu schicken ( = jedem Gameloop-Durchlauf) würde ich das mit einem Observer-Muster machen, wenn also die Berechnungen nur durchgeführt werden, wenn sich der Spieler wirklich bewegt. Kann man natürlich auch mit dem Merken und Vergleichen von aktuellen und letzten Koordinaten machen.

Die Logik, was das Schild anzeigen soll, würde ich dann vielleicht an das Rechteck binden. Folgender Pseudocode soll das mal darstellen:

Folgender Code sollte im Spielweltmanager oder ähnlichem liegen.

public void update(final PlayerInfo info) {
   Box currentBox = BoxManager.getBoxByCoordinates(info.getCoordinates());
   currentBox.calculateSign(info);
}

Folgender Code liegt dann beispielsweise in einer Box:

public void calculateSign(final PlayerInfo info) {
   if (info.getCharLevel() < 50) {
      this.getSign().setText("Sorry, Level zu niedrig");
   }
}

Observee wäre der Spieler, Observer wäre die Spielwelt. Die Boxen sollten natürlich dann eine Vererbungshierarchie haben und das Literal im calculateSign() sollte natürlich irgendwo als Konstante hinterlegt werden :slight_smile: