Kachelbasiertes Kartenkollisionserkennungsproblem

7

Ich arbeite an einem kachelbasierten Mario-Klon.

Dies funktioniert gut beim Gehen und Fallen. Aber wenn der Spieler in die Nähe einer Wand springt und nach rechts in die Luft geht, bleibt der Spieler an der Wand hängen. Das Spieler-Sprite fällt erneut, wenn der Spieler den Schlüssel loslässt.

Das Setup ist ziemlich einfach und ich kann das Problem nicht finden. Die Karte wird als 2D-Array mit Kartenblöcken aufgebaut. Ein Block kann fest sein oder nicht. Der Spieler kann sich nicht durch feste Objekte bewegen, duh ..

In der Spielschleife:

  • Der Standort des Spielers wird aktualisiert (Schwerkraft, Bewegung usw.).
  • Überprüfen Sie die Karte auf Kollisionen. Wenn eine Kollision im Y gefunden wird, wird der Standort des Spielers so aktualisiert, dass er über oder unter dem Block liegt (abhängig von der Richtung des Spielers), und das Kollisionsfeld wird mit dem neuen Standort aktualisiert. Dann der gleiche Vorgang für das X.
  • Das Kollisionsfeld wird auf den neuen Speicherort (einen freien Speicherort) aktualisiert. Das Feld ist so eingestellt, dass es etwas höher ist, wenn der Block unter dem Spieler überprüft, ob er gelandet ist. Dies dient dazu, den Status des Spielers vom fliegenden Sprite zum Leerlauf zu ändern.

Ich habe auch versucht, die X- und Y-Prüfung so umzuschalten, dass der Player auf der X-Linie bewegt wird. Wenn sich der Spieler dann bewegt, wird die Bewegung sehr verzögert. Wenn ich den Knopf drücke und loslasse, um mich zu bewegen, bewegt sich der Spieler schneller, aber in Spannfuttern. Sehr trippy ..

Hat jemand den Fehler gesehen oder kann mir dafür einen besseren Kollisionsalgorithmus geben?

UPDATE (Code wurde nicht aktualisiert)

Ich habe die x- und y-Prüfmethode ausgetauscht und die isonland-Variable implementiert. Wenn Sie also gegen Wände gehen und springen, funktioniert dies perfekt. Erst jetzt, wenn der Spieler springt, wird Mario bei der Landung zurückgesetzt. Dies liegt daran, dass die X-Check-Methode zuerst die Position von Mario anpasst.

Wie kann ich das lösen?

Aktualisierungsmethode für Kartenklassen:

public void update(int timeElapsed) {
        //update entities
        for(Entity entity : _mapEntities) {
            entity.update(timeElapsed);
        }

        //update objects
        for(MapObject mapObt : _mapObjects) {
            mapObt.update(timeElapsed);
        }

        //check for collisions
        checkMapCollision();
    }

Aktualisierungsmethode für Entitäten (abstrakt):

public void update(int timeElapsed) {
        _velocity = new Vector2d(0.0F, 0.0F);

        //add gravity
        _velocity.y = Map._GRAVITY_PER_SEC * timeElapsed;
    }

Mario (erweitert Entität) Update Methos:

@Override
    public void update(int timeElapsed) {
        super.update(timeElapsed);

        if(_state == STATES.IDLE) {

        } else if(_isMoving) {
            _marioSmallWalk.update(timeElapsed);
        }

        if(_state == STATES.JUMPING) {
            setVelocityY(getVelocity().y + _jumpSpeed);

            _jumpSpeed += _JUMP_DECREASE * timeElapsed;

            //falling?
            if(getVelocity().y > 0) {
                setState(STATES.FALLING);
            }
        } 

        if(_isMoving) {
            double walkSpd = (_WALK_SPEED_SEC * timeElapsed);

            if(getFacing() == FACING.LEFT) {
                walkSpd = -walkSpd;
            }

            setVelocityX(getVelocity().x + walkSpd);
        }

        //falling?
        if(getVelocity().y > (Map._GRAVITY_PER_SEC * timeElapsed) + 1.0F) {
            setState(STATES.FALLING);
        }

        setPosition((int)(getX() + getVelocity().x), (int)(getY() + getVelocity().y));
    }

Kartenklasse CheckMapCollision-Methode:

public void checkMapCollision() {
        //enteties move so check it
        for(Entity entity : _mapEntities) {
            //get the corners
            Rectangle bounds = entity.getBounds();
            Block[] corners = getCornerBlocks(bounds);
            Vector2d dir = entity.getDirection();

            //moving down
            if(dir.y > 0) {
                if(corners[2].isSolid() || corners[3].isSolid()) {
                    Rectangle blkBounds = null;

                    if(corners[2].isSolid()) {
                        blkBounds = corners[2].getBounds();
                    } else {
                        blkBounds = corners[3].getBounds();
                    }

                    entity.setPositionY(blkBounds.y);
                }
            } else {
                if(corners[0].isSolid() || corners[1].isSolid()) {
                    Rectangle blkBounds = null;

                    if(corners[0].isSolid()) {
                        blkBounds = corners[0].getBounds();
                    } else {
                        blkBounds = corners[1].getBounds();
                    }

                    entity.setPositionY(blkBounds.y + blkBounds.height + bounds.height);
                }
            }

            bounds = entity.getBounds();
            corners = getCornerBlocks(bounds);

            //moving to the right
            if(dir.x > 0) {
                if(corners[1].isSolid() || corners[3].isSolid()) {
                    Rectangle blkBounds;

                    if(corners[1].isSolid()) {
                        blkBounds = corners[1].getBounds();
                    } else {
                        blkBounds = corners[3].getBounds();
                    }

                    entity.setPositionX(blkBounds.x - (bounds.width-entity.getCurrentSprite().getOffsetX())-1);
                }
            } else {
                if(corners[0].isSolid() || corners[2].isSolid()) {
                    Rectangle blkBounds;

                    if(corners[0].isSolid()) {
                        blkBounds = corners[0].getBounds();
                    } else {
                        blkBounds = corners[2].getBounds();
                    }

                    entity.setPositionX(blkBounds.x + blkBounds.width + (bounds.width/2));
                }
            }

            bounds = new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height+1);
            corners = getCornerBlocks(bounds);

            //moving down
            if(dir.y > 0) {
                if(corners[2].isSolid() || corners[3].isSolid()) {
                    Rectangle blkBounds = null;

                    if(corners[2].isSolid()) {
                        blkBounds = corners[2].getBounds();
                    } else {
                        blkBounds = corners[3].getBounds();
                    }

                    entity.landed();
                    System.out.println("landed");
                }
            }
        }
    }
Sven van Zoelen
quelle
Sind Sie sicher, dass Sie nach dem Erkennen einer Kollision darauf reagieren? Es hört sich so an, als würde das Zeichen nicht aus dem Objekt zurückgeschoben und stattdessen dort angehalten, wo es sich befindet (aber ich habe den Code nicht vollständig gelesen).
James
Ja, da bin ich mir sicher. Das entity.setPositionX()oder entity.setPositionY()wird nach der Kollisionsprüfung aufgerufen. Wenn ich ohne zu springen gegen die Wand gehe, wird der Spieler richtig zurückgeschoben.
Sven van Zoelen

Antworten:

8

Ihr Sprite-Charakter bleibt an den Schnittpunkten zwischen zwei Kacheln hängen. Das folgende Diagramm zeigt die Situation zu Beginn Ihrer Kollisionsroutine.

Kollision mit einer Wand

Mario wurde aufgrund der Spielerkontrolle nach rechts verschoben. Dadurch wird das Sprite in den drei Kacheln platziert. Anschließend führen Sie die Kollisionserkennung nach unten Y durch. Beachten Sie den Punkt im blauen Kreis: Dadurch wird eine Kollision mit dem zweiten Block erkannt und versucht, diese zu beheben, indem die Y-Koordinate auf die Obergrenze dieses Blocks gesetzt wird, wodurch Mario effektiv angehalten wird in der Luft.

Sobald der Spieler den Schlüssel loslässt, um Mario nach rechts zu bewegen, bewegt sich das Sprite nicht mehr in die Wand, und der Punkt, an dem zuvor eine Kollision festgestellt wurde, registriert keinen Treffer. Dadurch kann Mario zu Boden fallen.

Die schnelle und schmutzige Lösung für dieses Problem besteht darin, zuerst X-Kollisionen zu lösen und die Schwerkraft nur anzuwenden, wenn sich der Spieler nicht am Boden befindet. Wenn Sie die Schwerkraft beibehalten würden, würde das gleiche Problem beim Herumlaufen auftreten (dies ist das "trippige" Verhalten, das Sie bereits nach dem Vertauschen des X- und Y-Checks gesehen haben;)).

Geist
quelle
Vielen Dank! Ich habe die x- und y-Prüfung getauscht und eine isOnLand-Prüfung implementiert. So kann der Spieler springen und gegen die Wand gleiten. Jetzt ist ein weiteres Problem aufgetreten, wenn der Spieler neben der Wand steht und springt. Wenn er dann landet, wird das x angepasst (aufgrund der Kollision des Bodens) und der Spieler wird in die Wand gesetzt.
Sven van Zoelen
Ich kann mir die mögliche Codestruktur aufgrund Ihres Kommentars nur schwer vorstellen. Warum muss X angepasst werden, wenn Mario nach einem Sprung mit dem Boden kollidiert? Ich würde denken, dass dies nur eine Anpassung in Y erfordert, wobei X unberührt bleibt.
Geist
1

Ich habe herausgefunden, was ich falsch gemacht habe.

Der Schlüssel liegt darin, wann Sie die Position aktualisieren. Was ich falsch gemacht habe, war, dass ich die Methode "zuerst die Positionen ändern und dann überprüfen und anpassen" durchgeführt habe und sie in der Reihenfolge ausführen, überprüfen und dann aktualisieren musste.

Mein vorheriger Code hat Folgendes getan:

  • Aktualisieren Sie die Positionen von Objekten mit Geschwindigkeit.
  • Suchen Sie nach Kollisionen und aktualisieren Sie die Positionen der Entitäten.

Dies führte dazu, dass nach dem Sprung des Spielers Marios Position in der Aktualisierungsmethode geändert wurde, sodass sich die Kollisionsbox in den Bodenplättchen befand. Dann änderte die Kollisionsmethode die X-Position in das x der linken unteren Ecke, und dann bewegte das y sie über die Bodenkachel. Dies bedeutet, dass Mario jedes Mal, wenn der Spieler von einem Sprung landet, um einige Pixel zurückgeschoben wird.

Ich habe versucht, die X- und Y-Prüfmethoden auszutauschen, aber dann ändert sich das Problem einfach in etwas anderes (die Wandumarmung).

Nun habe ich die Lösung wie folgt geändert:

  • Aktualisieren Sie die Geschwindigkeit der Entitäten.
  • Kollisionsmethode
    • Versuchen Sie, das Objekt mit der Geschwindigkeit auf dem X zu platzieren. Wenn dies fehlschlägt, ändern Sie es so gut wie möglich.
    • Versuchen Sie, das Objekt mit der Geschwindigkeit auf dem Y zu platzieren. Wenn dies fehlschlägt, ändern Sie es so gut wie möglich.

Jetzt wird das X zuerst überprüft / geändert und dann wird das Kollisionsfeld mit dem neuen Entitätsspeicherort aktualisiert. Dann wird das Y überprüft / geändert.

Das funktioniert wie ein Zauber :)

Sven van Zoelen
quelle
Können Sie einige Beispiele nennen? Es ist schwer zu verstehen, was Sie getan haben
Gyozo Kudor