Tile basiertes Game Kolissionserkennung

WieauchImmer23

Cadet 1st Year
Registriert
Aug. 2013
Beiträge
15
Hallo, ich habe jetzt bereits mehrere Tutorials zu diesem Thema durchgelesen und vom Prinzip her ist auch nichts kompliziertes dabei, nur bin ich irgendwie nicht in der Lage es richtig anzuwenden.
Vielleicht hat es auch was damit zu tun, dass ich LibGdx verwende.

Jedenfalls versuche ich zu erkennen wenn ich mit bestimmten Tiles zusammenstoße.
Ja, mein Vorgang ist halt zu schauen welche Properties ein Tile hat auf dem sich mein player befindet und wenn is ein blocked Tile ist soll er zu dem Tile "aligned" also genau dort platziert werden wo das Tile aufhört.
Bei mir hängt aber schon die Erkennung von Kollissionen.

Ich habe mal diese Herangehensweise:

Code:
//@param e the position of the Player
public static CollisionType collidesOnMap(Vector3 e, float width, float height, TiledMapTileLayer levelLayer, String term, AEntity.Facing facing) {
        switch (facing) {
            case RIGHT:
                if (hasCollision(e.x + width, e.y, levelLayer, term))
                    return CollisionType.COLLIDE_RIGHT;
            case LEFT:
                if (hasCollision(e.x, e.y, levelLayer, term))
                    return CollisionType.COLLIDE_LEFT;
            case UP:
                if (hasCollision(e.x, e.y + height, levelLayer, term)) {
                    return CollisionType.COLLIDE_TOP;
                }
            case DOWN:
                if (hasCollision(e.x, e.y, levelLayer, term))
                    return CollisionType.COLLIDE_DOWN;
            default:
                break;
        }
        return CollisionType.COLLIDE_NONE;
    }

public static boolean hasCollision(float x, float y, TiledMapTileLayer levelLayer, String term) {
        float TILE_SIZE = levelLayer.getTileWidth();
        int tileX = (int) (x / TILE_SIZE);
        int tileY = (int) (y / TILE_SIZE);
        if (levelLayer.getCell(tileX, tileY) == null) return false;
        if (levelLayer.getCell(tileX, tileY).getTile().getProperties().containsKey(term)) {
            return true;
        }
        return false;
    }


und diese:

Code:
  public static boolean hasCollision(Vector3 e, float width, float height, TiledMapTileLayer levelLayer, String term, AEntity.Facing facing){
        float TILE_SIZE = levelLayer.getTileWidth();
        int tileX = (int) (e.x / TILE_SIZE);
        int tileY = (int) (e.y / TILE_SIZE);
        if (levelLayer.getCell(tileX, tileY) == null) return false;
        if (!levelLayer.getCell(tileX, tileY).getTile().getProperties().containsKey(term)) {
            return false;
        }

        Rectangle entity = new Rectangle(e.x,e.y,width,height) ;
        Rectangle block= new Rectangle(tileX*TILE_SIZE,tileY*TILE_SIZE,TILE_SIZE,TILE_SIZE) ;
        if(entity.overlaps(block))return true;

        return false;
    }

Ich habe bei beiden das Problem, dass es sofort erkannt wird, wenn ich eine Kolission von oben nach unten "COLLIDE_DOWN" habe und von rechts nach links wird es eigentlich auch sofort erkannt, außer wenn mein player zu weit unten ist, was ich aber damit erklären kann, dass der ursprung des Bildes links unten ist und so nicht kollidiert.
Das eigentliche Problem ist die erkennung von unten nach oben und von links nach rechts, das wird gar nicht erkannt und ich verstehe einfach nicht weshalb.

Ich hab zwar schon mehrere kleine Spiele gemacht mit Kolissionen, jedoch ist das jetzt mein erstes mit libGDX, bei den anderen hatte ich die Kontrolle über die Tiles und hier ist es für mich noch etwas unübersichtlich.

Vielleicht sieht ja irgendjemand was in meinem Code.
 
Mal ganz unabhängig vom Code und dir im konkreten Fall - warum haben so viele Leute nie vom Debugger gehört oder zumindest von primitivem Debugging per cout? Mit sowas zerbricht man sich doch nicht den Kopf und verschwendet Zeit! Einfach Werte an den entscheidenden Stellen rausprinten und schauen, warum es nicht passt. Das wird nicht das letzte Mal sein, dass sowas vorkommt, vor allem wenn die Probleme komplexer werden.
 
Hier stand Unsinn...
 
Zuletzt bearbeitet:
+1 zu Tumbeweed

Ansonsten sieht es so aus, als ob du bei der Kollisionsprüfung nur auf eine Tile prüfst, obwohl der Spieler sich auf mehreren Tiles gleichzeitig befinden kann.
Außerdem glaube ich, dass bei deinem switch-statement ein paar break;s fehlen.
 
Ich sehe das so wie meine beiden Vorgänger:

1. Debugger bemühen
2. breaks pro case einfügen
 
Öhmm ja, nen debugger und println() habe ich wohl verwendet kann die Werte jedoch nicht korrekt interpretieren. Bzw. setze ich wohl die breakpoints falsch.
Und das einzige worauf ich inzwischen gekommen bin ist, dass " if (levelLayer.getCell(tileX, tileY) == null) { return false; }" sehr gerne false wirft wenn mein Facing UP oder RIGHT ist aber bei den anderen beiden praktisch immer passt.

Ich schätze mal stark das meine Berechnung nicht passt, aber ich weiß einfach nicht weshalb, ich habs mir bereits aufgezeichnet und da passts.
Wenn ich eine Kolission rechts bemerken will dann muss ich doch die origin + width nehmen damit ich auf dem Pixel der rechten Seite lande, aber er checkt das erst wenn mein Sprite praktisch genau innerhalb des Blocks ist.

Zur Zeit prüfe ich nur auf ein Tile, ich will zuerstmal das richtig machen.
Die breaks hab ich mit Absicht weggelassen, wenn er eine Kolission erkennt dann returnt die Methode eh und wenn nicht dann gibts halt ne zusätzliche überprüfung.
 
@Darlis: Yap da fehlen paar breaks.
Schöner fände ich eh, wenn man das Enum dazu nutzt, einfach die neue Position zu bestimmen...

Und die Methode collidesOnMap würde ich als Methode direkt ins Enum Facing machen... dann erspart man sich das Switch ganz...
 
WieauchImmer23 schrieb:
Die breaks hab ich mit Absicht weggelassen, wenn er eine Kolission erkennt dann returnt die Methode eh und wenn nicht dann gibts halt ne zusätzliche überprüfung.
1. Bringt es überhaupt etwas, wenn ich mich nach rechts bewege auf eine Kollision nach links zu prüfen?
2. Die Methode überprüft nicht zuverlässig auf alle Kollisionsmöglichkeiten. D.h. der Rückgabewert ist nur für eine bestimmte Situation definiert. "Bewegst sich der Spieler in Richtung x wird entweder eine Kollision in X-Richtung zurückgegeben oder irgendwas anderes." Wenn der Aufrufer dann nur auf CollisionType.COLLIDE_NONE prüft, hast du schon deinen Fehler.
 
Also zum absichtlichen Weglassen der breaks: Dann bräuchtest du auch gar keinen Swtich-Block machen und das Facing wäre egal...
 
Danke, ja habe ich mir inzwischen auch schon gedacht, musste jetzt ja auch nicht 100%ig korrekt sein.

Habe jedenfalls den Fehler gefunden .... ich habe in der aufrufenden Methode bereits früher ein if drin gehabt und dabei nicht wie in hasColission die width bzw height hinzugefügt wodurch ich schon da rausgeworfen wurde.
Vom Prinzip her also stimmen die beiden Methoden.

Jedenfalls würde ich jetzt gerne etwas genauer die Kolission erkennen.

Im Endeffekt überprüfe ich nur ob das eine Tile das andere schneidet.
mein Ansatz:
Code:
        float y = e.y, x = e.x;
        float TILE_SIZE = levelLayer.getTileWidth();
         Vector3 A = e;
        Vector3 B = e.add(width, 0, 0);
        Vector3 C = e.add(0, height, 0);
        Vector3 D = e.add(width, height, 0);

        int tileX = (int) (x / TILE_SIZE);
        int tileY = (int) (y / TILE_SIZE);

        Vector3 tileA = new Vector3(x, y, 0);
        Vector3 tileB = new Vector3(x + TILE_SIZE, y, 0);
        Vector3 tileC = new Vector3(x, y + TILE_SIZE, 0);
        Vector3 tileD = new Vector3(x + TILE_SIZE, y + TILE_SIZE, 0);

        if(!tileisFree(tileX,tileY,levelLayer,term))  return false;

         if(D.x>tileA.x&&D.y>tileA.x)return true  // usw....;

public static boolean tileisFree(int tileX, int tileY, TiledMapTileLayer levelLayer, String term) {
        if (levelLayer.getCell(tileX, tileY) != null) {
            if (levelLayer.getCell(tileX, tileY).getTile().getProperties().containsKey(term)) {
                return false;
            }
        }
        return true;
    }

und dann habe ich noch das zusammengebaut

Code:
 public static boolean intersects(Vector3 player , Vector3 tile,float width,float height,int size) {

        Rectangle entity = new Rectangle(player.x, player.y, width, height);
        Rectangle block = new Rectangle(tile.x, tile.y, size, size);
        if (entity.overlaps(block)) return true;

      return false;
    }

Aber gibts da vielleicht einen besseren Weg? Das wären dann doch zwölf Abfragen und meine bestimmung für die Bounds des Tiles stimmt nicht. Bzw die bestimmung für das x/y des Tiles.
Wie bekomme ich das am Besten?
 
Also ich weiß, dass es nciht die Hilfe ist, die du willst...

aber warum Übergibst du 3 Parameter (player, height, width), die eigentlich zusammengehören?
Fasse diese Dinge doch zu einer Klasse Player zusammen... dort stehen die Koordinaten und die Dimension mit drin...
Und dann kannst du sogar die Methode in die Klasse Player machen - oder in eine Basis-Klasse (sowas wie UIElement ... ka...)
 
LoL, glaubst du ich machn Spiel ohne Player Klasse?^^ Klar habe ich eine nur übergebe ich der Kolissionsüberprüfung einen "geklonten" Vektor, denn wenn ich den vom Player nehmen würde müsste ich auf zu vieles aufpassen wegen LibGdx. Aber ich habe im Endeffekt ja eh noch eine Methode, welcher das Player Obj übergeben wird.

Jedenfalls gehts jetzt:
Code:
public static CollisionType collidesOnMap2(Vector3 e, float width, float height, TiledMapTileLayer levelLayer, String term, AEntity.Facing facing) {
        switch (facing) {
            case RIGHT:
                if (hasCollision(e, width, height, levelLayer, term, facing)) {
                    return CollisionType.COLLIDE_RIGHT;
                }
            case LEFT:
                if (hasCollision(e, width, height, levelLayer, term, facing))
                    return CollisionType.COLLIDE_LEFT;
            case UP:
                if (hasCollision(e, width, height, levelLayer, term, facing))
                    return CollisionType.COLLIDE_TOP;

            case DOWN:
                if (hasCollision(e, width, height, levelLayer, term, facing))
                    return CollisionType.COLLIDE_DOWN;
        }
        return CollisionType.COLLIDE_NONE;
    }
 public static boolean hasCollision(Vector3 e, float width, float height, TiledMapTileLayer levelLayer, String term, AEntity.Facing facing) {
        float y = e.y, x = e.x;
        float TILE_SIZE = levelLayer.getTileWidth();
      

        int tileX = (int) (x / TILE_SIZE);
        int tileY = (int) (y / TILE_SIZE);

        Vector3 tileA = new Vector3(x, y, 0);

        tileA.x = tileX * TILE_SIZE;
        tileA.y = tileY * TILE_SIZE;
        if (intersects(e, tileA, width, height, 32) && !tileisFree(tileX, tileY, levelLayer, term)) {
            return true;
        }

        x+=width;
         tileX = (int) (x / TILE_SIZE);
        if (intersects(e, tileA, width, height, 32) && !tileisFree(tileX, tileY, levelLayer, term)) {
            return true;
        }
        x-=width;
        y+=height;
        tileY = (int) (y / TILE_SIZE);
        tileX = (int) (x / TILE_SIZE);
        if (intersects(e, tileA, width, height, 32) && !tileisFree(tileX, tileY, levelLayer, term)) {
            return true;
        }

        x+=width;
        tileX = (int) (x / TILE_SIZE);
        if (intersects(e, tileA, width, height, 32) && !tileisFree(tileX, tileY, levelLayer, term)) {
            return true;
        }
        return false;
    }

 public static boolean intersects(Vector3 player, Vector3 tile, float width, float height, int size) {
        Rectangle entity = new Rectangle(player.x, player.y, width, height);
        Rectangle block = new Rectangle(tile.x, tile.y, size, size);
         return (entity.overlaps(block))      ;
    }

Hat vielleicht sonst jemand eine Idee wie ich es "schöner" machen kann? Ich muss es schließlich noch so verwenden, dass es mir möglich ist den Player dem soliden Tile zu alignen. Zumindest die Erkennung funktioniert auf meiner random Map "komplett" korrekt.


@1668mib
Wie genau hast du dein voriges Posting gemeint? Verstehe nicht wie du meinst, dass man das Enum verwendet um die Pos zu bestimmen und in wie fern erspare ich mir dann das switch() ?
 
Ein Enum in Java kann auch Methoden enthalten. Du rufst dann auf dem Enum die Methode auf, und je nach konkretem Wert des Enums wird die entsprechende Methode für den Fall aufgerufen... <-- aber das ist ja egal, weil du ja jetzt unabhängig von facing prüfst...

Und wenn du eine Player-Klasse hast, warum übergibst du dann nicht diese?
Also ich würde sehr versuchen, die elends langen Parameter-Listen deiner Methoden zu verkleinern... dazu gehört auch, nicht genutzte Parameter zu entfernen, wie z.B. facing bei hasCollision...

Warum die breaks in deinem switch fehlen, ist mir auch noch nicht klar... das geile ist:
Wenn facing RIGHT ist, wird entweder 1 mal (bei treffer) oder sonst wird 4 mal die selbe Prüfung auf Kollison oben, links, rechts und unten gemacht.
Bei LEFT hat analog nur drei Mal...
bei TOP analog nur zwei Mal...

Im Übrigen ändert sich ja je nach case-Block eh nur der Wert der Rückgabe, nicht aber die Art der Brechnung... also könnte man auch einfach prüfen ob es eine Kollision gibt, und wenn ja, abhängig vom konkreten Wert von Facing den CollisionType zurückgeben...
 
Ja, das ein Enum einen Konstruktor und Methoden enthalten kann ist mir klar, mir ist aber noch immer nicht klar in wie fern mir das einen Vorteil bringt?

Wie gesagt hatte am Anfang Probleme aber ja, ich werde die Player Klasse übergeben.

......Wird gerichtet....

Habe ich inzwischen schon gemacht, habe die Methode halt nur kopiert.

Gibt es vielleicht sonst irgendeinen Bug/Logikfehler der heraussticht? Bei den Tests hat bisher alles gestimmt.

Im Endeffekt werde ich jetzt eben das zurückgegebene Enum nehmen und davon abhängig dann nochmal die x/y Pos des Tiles bestimmen und dann den Player dementsprechend setzen.
 
Zurück
Oben