2D Platformer - Kollisionserkennung bei "schräger" Kollision

Hallo,

Gegeben sei das folgende Beispiel (Es handelt sich hier im Screenshot um 2 direkt aufeinander folgende Frames!):

  • Das Blaue Rechteck ist die Spielfigur.
  • Das schwarze Viereck ist Teil der Umgebung (also eine Art Stein, Wand o.ä.), durch welche der Spieler nicht hindurchgehen können soll.
  • Die Roten Vierecke/Rechtecke sind Rectangles, welche für die Kollisionserkennung benutzt werden. Die Spielfigur besitzt 4 Rectangles (oben, unten, rechts, links) und das schwarze Viereck nur 1 Rectangle, welcher von der Größe her identisch mit dem schwarzen Viereck an sich ist.

Bei der Spielfigur werden die 4 Rectangles in folgender Reihenfolge geprüft:

  • Prüfung auf Kollision an der linken Seite der Spielfigur
  • Prüfung auf Kollision an der rechten Seite der Spielfigur
  • Prüfung auf Kollision an der oberen Seite der Spielfigur
  • Prüfung auf Kollision an der unterenSeite der Spielfigur

Mein Problem ist nun der Fall, wie er auf dem Screenshot zu sehen ist. Die Kollisionserkennung funktioniert nicht richtig, wenn die Spielfigur von einer schrägen Richtung das den schwarzen Block knallt, da sich hier nun das linke und das untere Rectangle der Spielfigur im schwarzen Block befinden und mit diesem “kollidieren”. Das Problem hierbei ist nun wie ich feststellen soll, ob die Figur sozusagen von der rechten Seite des schwarzen Blocks mit dem Block kollidierte oder zuerst von oben? Das muss ich ja deshalb wissen, um die Spielfigur entsprechend entweder oben auf den schwarzen Block positionieren zu können oder eben rechts neben dem Block.
Aber selbst wenn ich dies wüsste: Was wäre für den Fall, falls die Spielfigur mit dem linken und unteren Rectangle gleichzeitg im selben Spieltick/Spielupdate das schwarze Viereck betritt? Was dann?

Das Problem tritt besonders dann häufig auf, wenn die Anzahl der Pixel, mit der sich die Spielfigur pro Spieltick/Spielupdate fortbewegt, größer ist, als die Breite bzw. Höhe der Rectangles der Spielfigur.

Meine einzige Idee ist gewesen zu sagen, dass man die Spielfigur nur maximal mit der Breite bzw. Höhe der Rectangles der Spielfigur bewegt pro Spieltick/Spielupdate. Aber was, wenn mir diese Bewegung dann zu langsam ist? Spieltick/Spielupdates pro Sekunde erhöhen? Derzeit habe ich 60 Spieltick/Spielupdates pro Sekunde und 60 FPS. Würde ich erstere erhöhen/verringern, dann hätte ich ja sozusagen kein Async aktiviert, was ich aber aktiviert haben möchte, um Tearing zu vermeiden.

Kann jemand helfen?
Ist meine Kollisionserkennung generell komplett falsch?

ich verstehe das Problem irgendwie nicht ganz.
selbst wenn man links und unten gleichzeitig kollidiert:

links -> collision -> nach rechts setzen bzw return
unten -> ebenfalls collision -> nach oben setzen bzw return

so sollte es doch klappen?..

[QUOTE=Jack159]Das Problem hierbei ist nun wie ich feststellen soll, ob die Figur sozusagen von der rechten Seite des schwarzen Blocks mit dem Block kollidierte oder zuerst von oben? Das muss ich ja deshalb wissen, um die Spielfigur entsprechend entweder oben auf den schwarzen Block positionieren zu können oder eben rechts neben dem Block.
Aber selbst wenn ich dies wüsste: Was wäre für den Fall, falls die Spielfigur mit dem linken und unteren Rectangle gleichzeitg im selben Spieltick/Spielupdate das schwarze Viereck betritt? Was dann?
[/QUOTE]

Nur auf Basis des Bildes ist es schwer, etwas dazu zu sagen.

Was genau der Grund dafür ist, dass der Spieler aus 4 boxen besteht, weiß ich nicht so genau, aber ich nehme an, dass die Begründung irgendwas mit „Da kann man einfach ‚intersects‘ aufrufen“ zu tun hat :wink:

Wie auch immer: Wenn die Figur sich genau diagonal bewegt, und genau die Ecken aufeinander treffen, dann gibt es eben kein „zuerst“ oder „danach“. Man hat nur einen ungültigen Zustand, und muss sich überlegen, wie man den behebt. Grundsätzlich hat mymaksimus ja schon was angedeutet, aber wie man das im Einzelfall ausimplementiert, muss man sich überlegen. Grundsätzlich könnte man die Entscheidung, ob man „zuerst oben“ oder „zuerst rechts“ aufgetroffen ist, ggf. nach der Größe der Überschneidung treffen (und ich denke, dass man, wenn beide gleich groß sind, einfach eins von beidem wählen kann, da dürfte der Unterschied nicht so groß sein).

Das zweite, was du angedeutet hast, ist ein sehr allgemeines Problem. Man unterscheidet da ggf. zwischen „diskreter Kollisionserkennung“ und „kontinuierlicher Kollisionserkennung“. Bei der ersten prüft man nur an einzelnen Positionen, ob eine Kollision aufgetreten ist (so, wie du es im Moment machst). Das Problem ist eben, dass bei einer schnellen Bewegung (d.h. bei einem großen Schritt) ggf. kleine Hindernisse einfach „durchflogen“ werden. Bei der kontinuierlichen Kollisionserkennung würde man tatsächlich ausrechnen, ob irgendwann während des Schrittes eine Kollision auftritt. Im skizzierten Fall sollte das noch nicht sooo schwierig sein: Ein Ansatz wäre, dass man die Bewegung der Eckpunkte der Spieler-Box als Linien auffasst, und ausrechnet, ob eine dieser Linien irgendeine Kante der Hinternis-Box schneidet. (Und das tut sie, selbst wenn der Spieler in diesem Schritt komplett durch das Hindernis durchfliegt. Dann muss man nur noch den umgekehrten Fall testen, sonst könnte man ein Problem bekommen, wenn das Hindernis kleiner ist, als der Spieler). Damit kann man nicht nur ausrechnen, OB sich die Objekte berühren, sondern auch genau WANN (während des Zeitschritts), was für eine „gute“ Kollisionsantwort hilfreich sein kann. (Ich finde ja ganz allgemein, dass die Kollisionsantwort das eigentlich schwierige ist…)

[QUOTE=mymaksimus]ich verstehe das Problem irgendwie nicht ganz.
selbst wenn man links und unten gleichzeitig kollidiert:

links → collision → nach rechts setzen bzw return
unten → ebenfalls collision → nach oben setzen bzw return

so sollte es doch klappen?..[/QUOTE]

Das dachte ich zunächst auch. So wird es auch in vielen aller Tutorials erklärt. Das ist aber nur die halbe Lösung.
Das einfachste zu erklärende Problem wäre aber (wie Marco13 bereits sagte), wenn sich deine Spielfigur mit z.B. 20 Pixel pro Spielupdate nach rechts bewegt und dabei an eine 1 Pixel breite Wand vorbeiläuft. Wenn du Pech hast, dann „überpringt“ die Spielfigur diese 1 Pixel dünne Wand einfach…

[QUOTE=Marco13;91326]

Was genau der Grund dafür ist, dass der Spieler aus 4 boxen besteht, weiß ich nicht so genau, aber ich nehme an, dass die Begründung irgendwas mit „Da kann man einfach ‚intersects‘ aufrufen“ zu tun hat :wink:

Ein Ansatz wäre, dass man die Bewegung der Eckpunkte der Spieler-Box als Linien auffasst, und ausrechnet, ob eine dieser Linien irgendeine Kante der Hinternis-Box schneidet. (Und das tut sie, selbst wenn der Spieler in diesem Schritt komplett durch das Hindernis durchfliegt. Dann muss man nur noch den umgekehrten Fall testen, sonst könnte man ein Problem bekommen, wenn das Hindernis kleiner ist, als der Spieler). Damit kann man nicht nur ausrechnen, OB sich die Objekte berühren, sondern auch genau WANN (während des Zeitschritts), was für eine „gute“ Kollisionsantwort hilfreich sein kann. (Ich finde ja ganz allgemein, dass die Kollisionsantwort das eigentlich schwierige ist…)[/QUOTE]

Hälst du meine Variante mit den 4 Rectangles bei der Spielfigur für falsch? Habe sie nur aus einem Tutorial übernommen. Bin mir auch unsicher, ob dies überhaupt so notwenig ist…
Dein Vorschlag mit der Linie klingt kompliziert und so ganz habe ich ihn nicht verstanden. Was klar ist, dass ich von x bis x+newx eine Linie Zeichne und diese auf Kollision mit der Wand prüfe. Aber woher weiß ich dann von welcher Seite sich die Spielfigur nähert?

Das „einfachste“ fände ich wäre folgendes:
Nehmen wir an ich bewege meine Spielfigur immer mit genau 5 Pixel pro Spielupdate. Die Lösung wäre nun pro Spielupdate in einer Schleife von 1-5 sozusagen eine „1 Pixel pro Spielupdate Bewegung“ zu simulieren und zu prüfen ob es dabei zu einer Kollision kommt. Also so:
Collision(CurrentX+1);
Collision(CurrentX+2);
Collision(CurrentX+3);
Collision(CurrentX+4);
Collision(CurrentX+5);
Ich weiß aber nicht, ob das etwas zu aufwändig wäre?

Das Problem wäre dabei aber auch wieder folgendes Beispiel:
Aktuell bewegt sich meine Spielfigur nicht und im nächsten Spielupdate wird sie um +5 in X-Richtung und um -20 in Y-Richtung (sie springt z.b. gerade) bewegt. Wie soll diese beiden Werte in einer Schleife korrekt hochzählen?
Ich muss mich ja schrittweise den 5 bzw den 20 annähern. Aber in welchem Verhältnis?

Ich hoffe man kann mir noch folgen. Aber je länger man sich damit beschäftigt desto komplizierter wird es^^

[quote=Jack159]Das dachte ich zunächst auch. So wird es auch in vielen aller Tutorials erklärt. Das ist aber nur die halbe Lösung.
Das einfachste zu erklärende Problem wäre aber (wie Marco13 bereits sagte), wenn sich deine Spielfigur mit z.B. 20 Pixel pro Spielupdate nach rechts bewegt und dabei an eine 1 Pixel breite Wand vorbeiläuft. Wenn du Pech hast, dann „überpringt“ die Spielfigur diese 1 Pixel dünne Wand einfach…[/quote]

das hat dann aber nix mit der ecke zu tun, das geht auch wenn du nur mit einer seite dich der 1px breiten wand näherst…

Ich denke mal dieses „Verhältniss“ Ist dann deine Genauigkeit. Wenn du jeden Pixel prüfen willst dann halt immer um 1, wen du jeden halben pixel prüfen
willst 0,5… :smiley:

[QUOTE=mymaksimus]
Ich denke mal dieses “Verhältniss” Ist dann deine Genauigkeit. Wenn du jeden Pixel prüfen willst dann halt immer um 1, wen du jeden halben pixel prüfen
willst 0,5… :D[/QUOTE]

So meinte ich das nicht.

Beispiel 1:
Derzeit steht deine Spielfigur still und regungslos da und bewegt sich nicht. Nun drückst du 1x die rechte Pfeiltaste (für die Bewegung nach rechts) und deine Spielfigur bewegt sich auf einen Schlag 5 Pixel weiter nach rechts. Um heir jetzt eine korrekte Kollisionsprüfung einzubauen, müsste man einfach eine Schleife durchlaufen, in der man sich Schrittweise den 5 Pixeln, die man sich nach rechts bewegen will, annähern. Also erst bewegt man sozusagen intern die Figur um 1 Pixel nach rechts, dann 2 Pixel, dann 3 Pixel… usw. Bis 5 Pixel eben. Somit können keine Objekte durch zu schnelles Bewegen übergangen werden.
Hier ist alles klar.

Beispiel 2:
Derzeit steht deine Spielfigur still und regungslos da und bewegt sich nicht. Nun drückst du gleichzeitig 1x die rechte Pfeiltaste (für Bewegungs nach Rechts) und 1x die Leertaste (für das nach oben springen). Deine Figur wird sich also nun in die Richtung rechts/oben (also schräg) Bewegen. Für die Bewegung nach rechts nehmen wir wieder 5 Pixel und für den Sprung nehmen wir 20 Pixel. Nach diesem Spielzug hat sich deine Spielfigur also um 5 Pixel nach rechts und um 20 Pixel nach oben bewegt.
Das Problem ist hier nun: Wie näherst du dich diesen 5 und 20 Pixeln?
Verstehst du wie ich das meine?

Da du deine Figur sowieso nicht um 20 Pixel in einem Frame bewegen willst… (davon gehe ich mal aus?..) wird es bestimmt reichen wie bei der bewegung in eine
Richtung eben erst die eine dann die andere zu checken. Wenn nicht, könntest du auch einfach die Bewegungen in einer “WArteschlange” oder so speichern, und NACHEINANDER
TEILWEISE abarbeiten, oder du könntest vektoren verwenden, um wieder nur eine Richtung zu haben, oder oder oder…

Hier geistern ein paar ziemlich … „interessante“ … Ideen rum :smiley:

Die Sache mit den 4 Rechtecken ist erstmal nicht „falsch“ oder „richtig“. Wenn sie hilft, das Ziel zu erreichen, kann sie ja kaum pauschal „falsch“ sein. Allerdings leuchtet mir der Grund nicht ganz ein. Wie hoch/breit sind diese Rechtecke? 1 Pixel? Was kann man in diesem Fall mit diesen Rechtecken machen, was man nicht auch mit einer Linie machen könnte?

Die „einfachste“ Lösung, die du jetzt vorgeschlagen hast, ist gar nicht so unüblich - zumindest in einer ähnlichen Form.

Aber erstmal kurz vorneweg: Du hast einen Schritt der Größe (+5, -20) als problematisch bezeichnet, aber das wäre es nicht mal unbedingt: Allgemein müßte/sollte man für solche Berechnungen ohnehin float/double verwenden. Dann könnte man den Schritt z.B. in 20 Teilschritte zerlegen, jeden mit der Größe (5.0/20.0, -1.0). Aber da lauert auch schon die entscheidende Frage: Wie viele Teilschritte soll man machen? Man hätte einen Trade-off zwischen Genauigkeit und Effizienz.

Man geht ja davon aus, dass man zum Zeitpunkt t einen Zustand hat, wo KEINE Kollision vorliegt. Dann macht man einen Schritt, und hat dann einen Zustand zum Zeitpunkt t+1.0 (also einen Zeitschritt später), wo EINE Kollision vorliegt. Und jetzt will man möglichst genau rausfinden, wann die Kollision „angefangen“ hat. Eine übliche Lösung ist deswegen eine binäre Suche:

Man schaut nach, ob beim Zustand t+0.5 eine Kollision vorliegt (also „in der Mitte“ der beiden Zustände). Falls nicht, weiß man, dass die Kollision irgendwo zwischen t+0.5 und t+1.0 angefangen haben muss. Dann überprüft man den Zeitpunkt t+0.75. Wenn dort keine Kollision vorliegt, muss der Kollisionszeitpunkt irgendwo zwischen t+0.75 und t+1.0 liegen. So schachtelt man den echte Zeitpunkt ziemlich schnell ein.

Der andere Ansatz, mit der Linie und den Schnittpunkten, ist nicht unbedingt sooo kompliziert.

Aber woher weiß ich dann von welcher Seite sich die Spielfigur nähert?

Man kennt ja die Bewegungsrichtung. Die Linie ist ja definiert durch Start- und Endpunkt. Und der Schnittunkt wird berechnet als ein Wert zwischen 0 und 1, wobei 0 dem Startpunkt und 1 dem Endpunkt entspricht.

Vielleicht bastel’ ich da heute Abend mal eine Kleinigkeit, weiß aber noch nicht, ob das alles zeitlich hinhaut (eigentlich ist meine TODO-Liste übervoll…)

*** Edit ***

Joa, hab’ mal was gebastelt. Das ist natürlich nur eine schnell hingehackte Demo, aber verdeutlicht vielleicht die Idee (und enthält vielleicht ein paar praktische Snippets… ich hatte schonmal angefangen, diese ganzen „Intersection“-Sachen (die ich schon 1000 mal gebraucht und 100 mal geschrieben habe) zu sammeln, mit der (sich selbst nicht ganz ernst nehmenden) Vision, schrittweise auf einen Java-Port von http://www.geometrictools.com/ hinzuarbeiten, aber … man kommt ja zu nichts mehr… wenn man dauernd Forenbeiträge beantwortet :smiley: )).

Wenn man die Maus bewegt, verändert man die „neue“ Position die das Spieler-Objekt haben soll.
Wenn man die Maus draggt, verändert man die „aktuelle“ Position des Spieler-Objekts.

Es zeichnet die Linien zwischen den Eckpunkten des Spieler-Objekts an der alten und der neuen Position, und die Schnittpunkte dieser Linien mit irgendeiner Kante des „Hindernisses“. Wenn es eine Kollision gibt, wird ausgegeben, wo die war und wann sie stattgefunden hat.

Nochmal: Das ist NUR eine kleine Demo. Um sowas direkt in einem Spiel mit vielen Objekten zu machen, sollte man auf die ganze „convenience-methoden“ verzichten, die im Moment aus dem (absolut minimalistischen!) „GameObject“-Interface die für die Kollisionserkennung benötigten Informationen zusammensuchen, und stattdessen die „GameObjects“ schon mit den passenden Infos ausstatten.

package bytewelt.cd;

import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

interface GameObject
{
    double getMinX();
    double getMinY();
    double getMaxX();
    double getMaxY();
}

class SimpleGameObject implements GameObject
{
    private double minX;
    private double minY;
    private double maxX;
    private double maxY;
    
    public SimpleGameObject(
        double minX, double minY, 
        double maxX, double maxY)
    {
        this.minX = minX;
        this.minY = minY;
        this.maxX = maxX;
        this.maxY = maxY;
    }

    @Override
    public double getMinX()
    {
        return minX;
    }

    @Override
    public double getMinY()
    {
        return minY;
    }

    @Override
    public double getMaxX()
    {
        return maxX;
    }

    @Override
    public double getMaxY()
    {
        return maxY;
    }
    
}

public class CollisionDetection
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                createAndShowGUI();
            }
        });
    }
    
    private static void createAndShowGUI()
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Component collisionDetectionPanel =
            new CollisionDetectionPanel();
        f.getContentPane().add(collisionDetectionPanel);
        f.setSize(800,800);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
}

class CollisionDetectionPanel extends JPanel implements MouseMotionListener
{
    private GameObject obstacle;
    private GameObject player;
    private GameObject playerNextStep;
    
    CollisionDetectionPanel()
    {
        obstacle = new SimpleGameObject(400, 200, 450, 600);
        player = new SimpleGameObject(100, 300, 200, 400);
        playerNextStep = player;
        addMouseMotionListener(this);
    }
    
    
    @Override
    protected void paintComponent(Graphics gr)
    {
        super.paintComponent(gr);
        Graphics2D g = (Graphics2D)gr;
        
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, getWidth(),  getHeight());
        
        g.setRenderingHint(
            RenderingHints.KEY_ANTIALIASING, 
            RenderingHints.VALUE_ANTIALIAS_ON);
        
        g.setColor(Color.DARK_GRAY);
        g.fill(asRectangle(obstacle));
        
        g.setColor(Color.BLUE);
        g.fill(asRectangle(player));
        
        g.setColor(new Color(0,0,255,128));
        g.fill(asRectangle(playerNextStep));
        
        // Obtain the corner points of the player object
        // and of the player object after it has done
        // the next step
        List<Point2D> corners = getCorners(player);
        List<Point2D> cornersNextStep = getCorners(playerNextStep);
        
        // Compute the lines that describe the movement
        // of the corners
        List<Line2D> cornerMovements = new ArrayList<Line2D>();
        for (int i=0; i<4; i++)
        {
            Point2D c = corners.get(i);
            Point2D n = cornersNextStep.get(i);
            Line2D cornerMovement = new Line2D.Double(c,n);
            cornerMovements.add(cornerMovement);
        }
        
        g.setColor(Color.GRAY);
        for (Line2D cornerMovement : cornerMovements)
        {
            g.draw(cornerMovement);
        }
        
        
        // Note: Doing the collision detection in the paintComponent method 
        // is of course NOT appropriate in general. This is only a demo, 
        // and it is done here so that the points can be painted easily
        
        // Obtain the lines that describe the
        // border of the obstacle
        List<Line2D> obstacleLines = getLines(obstacle);
        
        double firstCollisionTime = Double.MAX_VALUE;
        int firstCollisionCorner = -1;
        int firstCollisionBorder = -1;
        
        Point2D location = new Point2D.Double();
        for (int i=0; i<4; i++)
        {
            Line2D cornerMovement = cornerMovements.get(i);
            for (int j=0; j<4; j++)
            {
                Line2D obstacleLine = obstacleLines.get(j);
                
                // Check 
                // - each line that describes the movement of a corner 
                // - and each line that describes an obstacle border 
                // for intersections
                
                Point2D collisionPoint = 
                    Intersection.computeIntersectionSegmentSegment(
                        cornerMovement, obstacleLine, location);
                if (collisionPoint != null)
                {
                    double cx = collisionPoint.getX(); 
                    double cy = collisionPoint.getY(); 
                    g.setColor(Color.RED);
                    g.fill(new Ellipse2D.Double(
                        cx-3, cy-3, 6, 6));
                 
                    // The location.getX() value is the relative
                    // position (between 0 and 1) on the line
                    // that describes the movement of the corner
                    double collisionTime = location.getX();
                    if (collisionTime < firstCollisionTime)
                    {
                        firstCollisionTime = collisionTime;
                        firstCollisionCorner = i;
                        firstCollisionBorder = j;
                    }
                }
            }
        }
        
        
        if (firstCollisionCorner != -1)
        {
            g.setColor(Color.BLACK);
            g.drawString("First collision:", 20, 40);
            g.drawString("Corner "+firstCollisionCorner, 20, 60);
            g.drawString("hits border "+firstCollisionBorder, 20, 80);
            g.drawString("at time "+firstCollisionTime, 20, 100);
        }
        
    }
    
    private static List<Point2D> getCorners(GameObject gameObject)
    {
        List<Point2D> corners = new ArrayList<Point2D>();
        for (int c=0; c<4; c++)
        {
            corners.add(getCorner(gameObject, c));
        }
        return corners;
    }
    
    private static List<Line2D> getLines(GameObject gameObject)
    {
        List<Line2D> lines = new ArrayList<Line2D>();
        for (int c=0; c<4; c++)
        {
            lines.add(getLine(gameObject, c));
        }
        return lines;
    }
    
    private static Line2D getLine(GameObject gameObject, int index)
    {
        return new Line2D.Double(
            getCorner(gameObject, index), 
            getCorner(gameObject, (index+1)%4));
    }
    
    private static Point2D getCorner(GameObject gameObject, int index)
    {
        double x0 = gameObject.getMinX();
        double y0 = gameObject.getMinY();
        double x1 = gameObject.getMaxX();
        double y1 = gameObject.getMaxY();
        switch (index)
        {
            case 0 : return new Point2D.Double(x0, y0);
            case 1 : return new Point2D.Double(x0, y1);
            case 2 : return new Point2D.Double(x1, y1);
            case 3 : return new Point2D.Double(x1, y0);
        }
        return null;
    }
    
    private static Rectangle2D asRectangle(GameObject gameObject)
    {
        Rectangle2D r = new Rectangle2D.Double(
            gameObject.getMinX(), gameObject.getMinY(),
            gameObject.getMaxX() - gameObject.getMinX(),
            gameObject.getMaxY() - gameObject.getMinY());
        return r;
    }

    @Override
    public void mouseMoved(MouseEvent e)
    {
        playerNextStep = new SimpleGameObject(
            e.getX(), e.getY(), 
            e.getX()+player.getMaxX()-player.getMinX(),
            e.getY()+player.getMaxY()-player.getMinY());
        repaint();
    }
    
    @Override
    public void mouseDragged(MouseEvent e)
    {
        player = new SimpleGameObject(
            e.getX(), e.getY(), 
            e.getX()+player.getMaxX()-player.getMinX(),
            e.getY()+player.getMaxY()-player.getMinY());
        repaint();
    }
    
}




class Intersection
{
    /**
     * Epsilon for floating point computations
     */
    private static final double epsilon = 1e-6f;

    /**
     * Computes the intersection of the specified line segments and returns 
     * the intersection point, or <code>null</code> if the line segments do 
     * not intersect.
     * 
     * @param line0 The first line segment
     * @param line1 The second line segment
     * @param location Optional location that stores the 
     * relative location of the intersection point on 
     * the given line segments
     * @return The intersection point, or <code>null</code> if 
     * there is no intersection.
     */
    public static Point2D computeIntersectionSegmentSegment( 
        Line2D line0, Line2D line1, Point2D location)
    {
        return computeIntersectionSegmentSegment(
            line0.getX1(), line0.getY1(), line0.getX2(), line0.getY2(),
            line1.getX1(), line1.getY1(), line1.getX2(), line1.getY2(),
            location);
    }
    
    /**
     * Computes the intersection of the specified line segments and returns 
     * the intersection point, or <code>null</code> if the line segments do 
     * not intersect.
     * 
     * @param s0x0 x-coordinate of point 0 of line segment 0
     * @param s0y0 y-coordinate of point 0 of line segment 0
     * @param s0x1 x-coordinate of point 1 of line segment 0
     * @param s0y1 y-coordinate of point 1 of line segment 0
     * @param s1x0 x-coordinate of point 0 of line segment 1
     * @param s1y0 y-coordinate of point 0 of line segment 1
     * @param s1x1 x-coordinate of point 1 of line segment 1
     * @param s1y1 y-coordinate of point 1 of line segment 1
     * @param location Optional location that stores the 
     * relative location of the intersection point on 
     * the given line segments
     * @return The intersection point, or <code>null</code> if 
     * there is no intersection.
     */
    public static Point2D computeIntersectionSegmentSegment( 
        double s0x0, double s0y0,
        double s0x1, double s0y1,
        double s1x0, double s1y0,
        double s1x1, double s1y1,
        Point2D location)
    {
        if (location == null)
        {
            location = new Point2D.Double();
        }
        Point2D result = computeIntersectionLineLine(
            s0x0, s0y0, s0x1, s0y1, s1x0, s1y0, s1x1, s1y1, location);
        if (location.getX() >= 0 && location.getX() <= 1.0 && 
            location.getY() >= 0 && location.getY() <= 1.0)
        {
            return result;
        }
        return null;
    }
    

    /**
     * Computes the intersection of the specified lines and returns the 
     * intersection point, or <code>null</code> if the lines do not 
     * intersect.
     * 
     * Ported from 
     * http://www.geometrictools.com/LibMathematics/Intersection/
     *     Wm5IntrSegment2Segment2.cpp
     * 
     * @param s0x0 x-coordinate of point 0 of line segment 0
     * @param s0y0 y-coordinate of point 0 of line segment 0
     * @param s0x1 x-coordinate of point 1 of line segment 0
     * @param s0y1 y-coordinate of point 1 of line segment 0
     * @param s1x0 x-coordinate of point 0 of line segment 1
     * @param s1y0 y-coordinate of point 0 of line segment 1
     * @param s1x1 x-coordinate of point 1 of line segment 1
     * @param s1y1 y-coordinate of point 1 of line segment 1
     * @param location Optional location that stores the 
     * relative location of the intersection point on 
     * the given line segments
     * @return The intersection point, or <code>null</code> if 
     * there is no intersection.
     */
    public static Point2D computeIntersectionLineLine( 
        double s0x0, double s0y0,
        double s0x1, double s0y1,
        double s1x0, double s1y0,
        double s1x1, double s1y1,
        Point2D location)
    {
        double dx0 = s0x1 - s0x0;
        double dy0 = s0y1 - s0y0;
        double dx1 = s1x1 - s1x0;
        double dy1 = s1y1 - s1y0;

        double len0 = Math.sqrt(dx0*dx0+dy0*dy0); 
        double len1 = Math.sqrt(dx1*dx1+dy1*dy1); 
        
        double dir0x = dx0 / len0;
        double dir0y = dy0 / len0;
        double dir1x = dx1 / len1;
        double dir1y = dy1 / len1;
        
        double c0x = s0x0 + dx0 * 0.5;
        double c0y = s0y0 + dy0 * 0.5;
        double c1x = s1x0 + dx1 * 0.5;
        double c1y = s1y0 + dy1 * 0.5;
        
        double cdx = c1x - c0x;
        double cdy = c1y - c0y;
        
        double dot = dotPerp(dir0x, dir0y, dir1x, dir1y);
        if (Math.abs(dot) > epsilon)
        {
            double dot0 = dotPerp(cdx, cdy, dir0x, dir0y);
            double dot1 = dotPerp(cdx, cdy, dir1x, dir1y);
            double invDot = 1.0/dot;
            double s0 = dot1*invDot;
            double s1 = dot0*invDot;
            if (location != null)
            {
                double n0 = (s0 / len0) + 0.5;
                double n1 = (s1 / len1) + 0.5;
                location.setLocation(n0, n1);
            }
            double x = c0x + s0 * dir0x;
            double y = c0y + s0 * dir0y;
            return new Point2D.Double(x,y);
        }
        return null;
    }
    
    /**
     * Returns the perpendicular dot product, i.e. the length
     * of the vector (x0,y0,0)x(x1,y1,0).
     * 
     * @param x0 Coordinate x0
     * @param y0 Coordinate y0
     * @param x1 Coordinate x1
     * @param y1 Coordinate y1
     * @return The length of the cross product vector
     */
    private static double dotPerp(double x0, double y0, double x1, double y1)
    {
        return x0*y1 - y0*x1;
    }
    
}

Ich habe das Problen nun (denke und hoffe ich^^) gelöst.

Vorher danke nochmal an alle, die hier mitgeholfen haben!

@ Marco13:
Mit deinem Beispielprogramm konnte ich leider nicht viel anfangen bzw. es auf mein Problem anwenden…Trozdem danke für deine Mühe :wink:

Ich habe mich wohl zu sehr in das Problem reingesteigert und nur unnötig kompliziert gedacht.
Zunächst habe ich die 4 Rectangles in der Spielfigur durch einfache Linien ersetzt, da Rectangles hierfür eigentlich schon „zuviel“ sind. Dann habe ich die Maße der oben und unteren Linien geändert. Im Startpost wie auf dem Screen dort zu sehen, gingen diese noch komplett bis zum Rand und waren gleichauf mit den seitlichen Linien bzw. dort noch Rectangles. Dies sorgte z.B. für den Fehler, dass manchmal bei einer Bewegung und Kollision nach Rechts fälschlicherweise eine Kollision nach Unten oder Oben erkannt wurde. Die Maße habe ich so geändert, dass die Linien an allen 4 Seiten jeweils 2 Pixel links und rechts Platz haben. Das ganze sieht also nun so aus:

Egal von welcher Richtung die Spielfigur nun gegen eine Wand knallt, es wird lediglich nur exakt 1 Linie mit der Wand kollidieren. Natürlich gibt es hier noch den Spezialfall, dass die Spielfigur ja genau mit der Lücke an eine der Ecken gegen eine Ecke einer Wand knallt. Dieser „Fehler“ dürfte aber bei einem Bereich von 2 Pixeln derartig selten auftreten und zudem wenn er auftritt, auch nicht auffallen.

Außerdem musste noch der große Fehler behoben werden, dass wenn sich die Spielfigur mit z.B. 20 Pixeln pro Spielupdate nach rechts bewegt, keine 1 Pixel dünne Wand „überspringt“. Das habe ich so gelöst, dass ich (in dem Beispiel) jede einzelne Bewegung der Spielfigur (= 20 Pixel pro Spielupdate hier im Beispiel) intern von 1-20 Pixeln simuliere und im 1-Pixelschritt auf eine Kollision prüfe und dann ggf. entsprechend handle.

Und im Falle einer Kollision ist die Platzierung der Spielfigur eigentlich total simpel. Ich weiß nicht, wieso ich vorher so lange so kompliziert gedacht habe :smiley: :

  • Kollision an der rechten Seite: Setze Spielfigur direkt links neben der Wand
  • Kollision an der linken Seite: Setze Spielfigur direkt rechtsneben der Wand
  • Kollision oben: Setze Spielfigur direkt unter die Wand
  • Kollision unten: Setze Spielfigur direkt über die Wand

Und hier das ganze als Code (nur die Kollision):


// Die object-Liste enthält alle Wand-Blöcke. Die Methode an sich wird in der Player.java (Spielfigurklasse) aufgerufen.
// VelX ist die aktuelle Bewegung in x-Richtung. VelY analog.

	private void Collision(LinkedList<GameObject> object) {
		for(int i=0; i < handler.object.size(); i++) {
			
			GameObject tempObject = handler.object.get(i);
			
			if(tempObject.getId() == ObjectId.Block) {
			
				Block block = (Block) tempObject;
				
				float preX = getX();
				boolean collision = false;
				
				// Rechts
				for(int x = 0; x < getVelX(); x++) {

					setX(getX() + x);
					

					if(getRightLine().intersects(block.getBounds())) {
						System.out.println("KOLLISION RECHTS");
						setVelX(0);
						setX((float) (block.getX() - getWidth()));
						collision = true;
						break;
					}

					setX(getX() - x);
				}
				
				
				if(collision == false)
					setX(preX);
				
				
				
				
				// Links
				if(collision == false)
				for(int x = 0; x > getVelX(); x--) {
					
					setX(getX() + x);
					
					if(getLeftLine().intersects(block.getBounds())) {
						System.out.println("KOLLISION LINKS");
						setVelX(0);
						setX((float) (block.getX() + block.getWidth()));
						collision = true;
						break;
					}

					setX(getX() - x);
				}
				
				if(collision == false)
					setX(preX);
				
				

				float preY = getY();
				
				// Oben
				if(collision == false)
				for(int y = 0; y >= getVelY(); y--) {
					
					setY(getY() + y);

					if(getTopLine().intersects(block.getBounds())) {
						System.out.println("KOLLISION OBEN");
						setVelY(0);
						setY((float) (block.getY() + block.getHeight()));
						collision = true;
						break;
					}
					
					setY(getY() - y);
				}
				
				if(collision == false)
					setY(preY);
				
				
				// Unten			
				if(collision == false) 
				for(int y = 0; y <= getVelY(); y++) {
					
					setY(getY() + y);

					if(getDownLine().intersects(block.getBounds())) {
						System.out.println("KOLLISION UNTEN");
						setVelY(0);
						setY((float) (block.getY() - getHeight()));
						collision = true;
						falling = false;
						jumping = false;
						break;
					}else {
						falling = true;
					}
					
					setY(getY() - y);
				
				}

				if(collision == false)
					setY(preY);
				
			}
		}
	}

Genau das haben wir dir eigentlich die ganze Zeit versucht zu erklären :smiley:
aber gut, schön das es nun klappt :wink: