Wie Objekte am besten anklickbar machen ?

Status
Nicht offen für weitere Antworten.

Cola_Colin

Top Contributor
Ich schreibe derzeit die Grundlagen für ein paar 2D Spiele in Java.
Hierzu möchte ich meiner Objekt Klasse, die die Basis aller Objekte der Spielwelt zur Verfügung stellt die Funktionen von MouseListener zur Verfügung stellen. Das heißt ich würde gerne wissen, wann der Mauszeiger eine Objekte betritt/verlässt/es anklickt/etc.

Dieses Problem hab ich früher unter Delphi mit der Kollisionsabfrage gelöst. 1 Pixel Objekt Mauspunkt gegen alle Objekte der Spielwelt sozusagen. Klappt zwar, kommt mir aber irgendwie plump vor.
Macht es also Sinn, diese Basisklasse meiner Spielweltobjekte von MouseAdapter erben zu lassen ? Und wie krieg ich dann raus, wann die Events ausgelöst werden müssen ?
Meine Objekte zeichnen sich als Image mit drawImage abhängig von Größe und Position. Ich vermute mal das es keine Möglichkeit gibt, ausser zu testen ob das angeklickte Pixel sich auf einem nicht transparenten Pixel eines Objekts befindet ?
 
Ja, sowas kann man beliebig groß aufziehen. Die von MouseAdapter erben zu lassen würde IMHO nicht so viel Sinn ergeben. Wenn du wirklich einen MouseListener verwenden willst, und nicht ein eigenes Interface, dann stellen sich etliche Fragen, die sich bei Swing-Components (für die MouseListener eigentlich gedacht sind) nicht auftauchen. Das fängt bei solchen Fragen an wie "Welche Events werden wo und wie und ann geworfen, wenn man über zwei Objekten ist, die sich überschneiden?", und hört bei solchen Fragen wie "Was liefert mouseEvent.getSource() wenn der Event von deinem eigenen Grafik-Ding kommt?" noch lange nicht auf.
 
Zeichnest Du Deine grafischen Objekte auf ein JComponent? Dann kannst Du dort einen MouseListener drauf legen. Über das MouseEvent bekommst Du ja die Cursor-Position (x,y). Du musst dann nur noch prüfen, welches Objekt sich an dieser Position befindet.

Ich würde jedes grafische Element als Klasse kapseln. Diese Klasse könnte neben einer paint() Methode zum Zeichnen auch ein Methode getShape(), der den Shape des grafischen Objekts liefert. Du kannst dann einfach mit Shape#contains(Point) prüfen, ob der Punkt des MouseEvents im Shape liegt. Auf diese Weise kannst Du das grafische Objekt zu einem MouseEvent bestimmen und damit dann irgendwas machen.

Du müsstest Dich dann evtl. noch wie Marco13 schon sagte um überlappende Objekte kümmern.


Eine andere (einfachere) Möglichkeit wäre, wenn jedes grafische Objekt selbst ein JComponent ist. Dann könntest Du das Event-Handling von Swing benutzen und direkt auf die grafischen Komponenten einen MouseListener hängen. Allerdings hast Du dann das Problem, dass das Shape (also die Bounding Box) von JComponents immer rechteckig sein muss. Das wäre wohl in vielen Fällen ein No-Go.
 
Zeichnest Du Deine grafischen Objekte auf ein JComponent? Dann kannst Du dort einen MouseListener drauf legen. Über das MouseEvent bekommst Du ja die Cursor-Position (x,y). Du musst dann nur noch prüfen, welches Objekt sich an dieser Position befindet.

Ja, das wäre der... "übliche" Ansatz - das ganze dann zusammen mit einem [c]interface Clickable[/c] oder so, da kann man schon einiges machen. Etwas ... "fummeliger" wird's aber evtl. aufgrund der Anforderung, dass auch MouseEntered und MouseExited-Events für die eigenen Objekte geworfen werden sollen. (Eine pragmatische Lösung ist nicht schwer: Man müßte innerhalb der Haupt-JComponent dann mit-tracken, über welchen Objekten man sich gerage befindet, und bei jeder Mausbewegung ensprechend den Änderungen Events rauswerfen - aber ob und wie man das dann strukturell gut und effizient machen kann, muss man sich eben überlegen...)
 
Man könnte es recht einfach mit einem boolean hovering lösen. Der o.g. Mechanismus setzt die Variable jeweils auf true oder false. MouseEntered, MouseExited resultiert dann aus dem Übergang hovering FALSE -> TRUE bzw. TRUE -> FALSE.

Aber Du hast schon recht, man muss gucken, wie man das ganze performant hinkriegt, vor allem wenn Repaints mit ins Spiel kommen und man "viele" grafische Komponenten hat.
 
Ok, dann werd ich die Mausposition von dem Component nutzen, auf dem ich zeichne.
Folgend Events und Ansätze:

-> Mausklick/down/release
Hier würde ich testen müssen, auf welchem Objekt der Klick ausgelöst wurde.
Dazu würde ich die x,y Koordinate des Klicks auf dem Panel nehmen. Nun muss ich testen, welches meiner Spielobjekte(ja die sind gekabselt und werden von einer anderen Klasse in ner Liste verwaltet. Diese Klasse kriegt auch zuerst die Events des Panels mit) von diesem Punkt angeklickt wird. Dazu teste ich, ob der Klickpunkt in der BoundingBox eines Objektes liegt und wenn dem so ist, ob der betroffene Punkt Alpha ist oder nicht. (Wie macht man das in Java ? Im Speicher ein Bild erstellen, auf das zeichnen und dann getPixel() ?)
Überlappende Objekte sind dank einer Z Koordinate meiner Objekte kein Problem, denk ich.

-> Mouse enter/leave
Hier würde ich der verwaltenden Klasse ein Feld geben, in dem das Objekt drinne ist, über dem aktuell die Maus ist, bei jeder Mausbewegung muss das neugetestet werden. Danach werden dann Events verschickt.

Verbesserungsvorschläge ? 🙂

@byte:
Wie genau verhält sich das mit den Shapes ? Die Klasse kenn ich noch nicht, bin eben ziemlich neu in Java 😉
Laut der Api-Doku kann man damit Formen verarbeiten, aber ich vermute das es nicht trivial ist so einen PathIterator von einem Bild zu kriegen ?
 
Dazu teste ich, ob der Klickpunkt in der BoundingBox eines Objektes liegt und wenn dem so ist, ob der betroffene Punkt Alpha ist oder nicht. (Wie macht man das in Java ?
Am besten eben wegabstrahiert: Mit einem interface
Code:
interface Clickable
{
   boolean isHitBy(int x, int y);
}
das dann aut unterschiedliche Arten implementiert sein kann. Bei einem Objekt, das irgendwie als BufferedImage mit Alpha-Kanal gespeichert ist, wäre es
Code:
class ClickableImage implements Clickable
{
    private int positionX, positionY = ...
    private BufferedImage image = ...

    public boolean isHitBy(int x, int y)
    {
        // Ggf. erst einen schnellen bounding-box-check, und ggf. false zurückliefern,
        // ansonsten:
        int rgb = bufferedImage.getRGB(x-positionX,y-positionY);
        return containsSomeAlphaComponent(rgb); // Ein paar bit-abfragen...
    }
}
Wenn man eine Klasse hat, für die es schon ein fertiges Shape gibt, wäre das
Code:
...
    public boolean isHitBy(int x, int y)
    {
        return theShape.contains(x-positionX,y-positionY);
    }
- einfach anders implementiert, aber nach außen ändert sich nichts.



-> Mouse enter/leave
Hier würde ich der verwaltenden Klasse ein Feld geben, in dem das Objekt drinne ist,

Die Maus kann ggf. auch in mehreren Objekten sein... Das meinte ich mit "da muss man sich was überlegen..." 😉
 
Der Mauspunkt (ein Pixel) kann zumindest so wie ich das verstehe immer nur eine Position haben und auf dieser Position ist immer nur ein Objekt zu sehen, eventuell sind darunter andere, aber die interessieren dann nicht.

Ok dann kommt das in ein Interface im Moment verwende ich noch einfach nur Image, aber das lässt sich ja schnell ändern. Muss dann nur schaun, weil die Positionsbestimmung auf dem gezeichneten Image nicht ganz so einfach ist, da ich nur das Grundbild speichere. Beim zeichnen wird es dann noch rotiert und gestrecht. Aber da find ich schon ne Möglichkeit.
 
Status
Nicht offen für weitere Antworten.

Zurück
Oben