Wie verhindere ich, dass der Charakter meines Plattformspielers auf Wandfliesen schneidet?

9

Derzeit habe ich einen Plattformer mit Kacheln für das Gelände (Grafiken aus Cave Story). Das Spiel wurde mit XNA von Grund auf neu geschrieben, daher verwende ich keine vorhandene Engine oder Physik-Engine.

Die Kachelkollisionen werden ziemlich genau wie in dieser Antwort beschrieben beschrieben (mit einfachem SAT für Rechtecke und Kreise), und alles funktioniert einwandfrei.

Außer wenn der Spieler beim Fallen / Springen gegen eine Wand stößt. In diesem Fall fangen sie eine Fliese und denken, sie hätten einen Boden oder eine Decke erreicht, die eigentlich nicht da ist.

Bild des Charakterfangs

In diesem Screenshot bewegt sich der Player nach rechts und fällt nach unten. Nach der Bewegung werden Kollisionen überprüft - und zunächst stellt sich heraus, dass der Spielercharakter mit dem 3. Plättchen vom Boden aus kollidiert und nach oben gedrückt wird. Zweitens hat er festgestellt, dass er mit dem Plättchen neben sich kollidiert und seitwärts geschoben wurde. Das Endergebnis ist, dass der Spielercharakter glaubt, auf dem Boden zu sein und nicht zu fallen, und auf dem Plättchen „fängt“, solange er darauf läuft .

Ich könnte dies lösen, indem ich stattdessen die Kacheln von oben nach unten definiere, wodurch er reibungslos fällt, aber dann passiert der umgekehrte Fall und er stößt an eine Decke, die nicht vorhanden ist, wenn er gegen die Wand nach oben springt.

Wie soll ich vorgehen, um dieses Problem zu lösen, damit der Spielercharakter einfach so an die Wand fallen kann, wie er sollte?

Doppelgrün
quelle
Ähnlich wie bei dieser Frage? gamedev.stackexchange.com/questions/14486/…
MichaelHouse
@ Byte56 Die Frage ist nicht dieselbe, aber ihre Antwort könnte helfen.
Doppelgreener
Ein Hinweis: Ich hatte in den letzten Wochen sehr wenig Zeit, um mit den Antworten hier zu experimentieren. Ich habe die Auflösung von Weltkollisionen implementiert, wie in der Frage @ Byte56 verknüpft, die separate Fehler bei hohen Geschwindigkeiten aufweist, und konnte die Antworten auf diese Frage noch nicht ausprobieren. Ich werde diese versuchen, wenn ich kann, und entweder eine Antwort akzeptieren, wenn ich kann, oder meine eigene posten, wenn ich sie selbst löse.
Doppelgreener

Antworten:

8

Der einfachste und ausfallsicherere Ansatz besteht darin, einfach nicht auf Kollisionen an verborgenen Kanten zu prüfen. Wenn Sie zwei Wandplättchen direkt übereinander haben, sollten die Unterkante des Oberplättchens und die Oberkante des Unterplättchens nicht auf Kollision mit dem Spieler überprüft werden.

Sie können bestimmen, welche Kanten während eines einfachen Durchlaufs über die Kachelkarte gültig sind, indem Sie nach außen gerichtete Kanten als Flags an jeder Kachelposition speichern. Sie können dies auch einfach zur Laufzeit tun, indem Sie benachbarte Kacheln während der Kollisionserkennung überprüfen.

Es gibt fortgeschrittenere Versionen derselben Technik, die es einfacher und schneller machen, auch nicht quadratische Kacheln zu unterstützen.

Ich empfehle Ihnen, die folgenden Artikel zu lesen. Sie sind anständige einfache Einführungen in die grundlegende Physik und Kollisionserkennung in modernen Plattformspielern (wie Cave Story). Wenn Sie ein eher altmodisches Mario-Feeling bevorzugen, gibt es noch ein paar Tricks, über die Sie sich Sorgen machen müssen. Bei den meisten handelt es sich jedoch eher um Sprünge und Animationen als um Kollisionen.

http://www.metanetsoftware.com/technique/tutorialA.html http://www.metanetsoftware.com/technique/tutorialB.html

Sean Middleditch
quelle
Können Sie klarstellen, wie Sie einige Kanten während einer Kollision ignorieren können? Normalerweise sehe ich Kollisionen als Schnittpunkt zweier Rechtecke (der Spielergrenzen und der Kachelgrenzen). Wenn Sie sagen , dass Sie die Kollision nicht mit einzelnen Kanten prüfen sollen, bedeutet dies, dass der Kollisionsalgorithmus kein Schnittpunkt zwischen Rechteck und Rechteck ist, sondern etwas anderes? Ihre Beschreibung ist sinnvoll, aber ich bin mir nicht sicher, wie die Implementierung aussehen würde.
David Gouveia
Ja, machen Sie einfach eine Kollision mit Rechtecklinien. Nur vereinfacht, da die Linie immer achsenausgerichtet ist. Sie können Ihren Box-Box-Check einfach zerlegen.
Sean Middleditch
5

Hier die Reihenfolge, in der ich Dinge tun würde ....

1) Speichern Sie das aktuelle X, Y des Zeichens

2) Bewegen Sie die X-Richtung

3) Überprüfen Sie alle Ecken des Zeichens, indem Sie die Kacheldaten abrufen, und überprüfen Sie, ob sie fest sind, indem Sie Folgendes tun: (X und Y ist die Zeichenposition)

  • für oben links: Boden (X / Fliesenbreite), Boden (Y / Fliesenhöhe);
  • für oben rechts: Etage ((X + Zeichenbreite) / Kachelbreite), Etage (Y / Kachelhöhe);
  • für unten links: Boden (X + / Fliesenbreite), Boden ((Y + Zeichenhöhe) / Fliesenhöhe);
  • für unten rechts: Etage ((X + Zeichenbreite) / Fliesenbreite), Etage ((Y + Zeichenhöhe) / Fliesenhöhe);

... um korrekte Kacheldaten aus Kartendaten zu erhalten

4) Wenn eine der Kacheln fest ist, müssen Sie X an die bestmögliche Position zwingen, indem Sie das gespeicherte X wiederherstellen und ...

  • Wenn Sie sich nach links bewegen: X = Boden (X / Fliesenbreite) * Fliesenbreite;
  • Wenn Sie sich nach rechts bewegen: X = ((Etage ((X + Zeichenbreite) / Kachelbreite) + 1) * Kachelbreite) - Zeichenbreite;

5) Wiederholen Sie 2-4 mit Y.

Wenn dein Charakter größer als ein Plättchen ist, musst du mehr Plättchen überprüfen. Dazu müssen Sie eine Schleife erstellen und tileHeight zur Position des Charakters hinzufügen, um eine Kachel zu erhalten

int characterBlockHeight = 3;

// check all tiles and intermediate tiles down the character body
for (int y = 0; y < characterBlockHeight - 1; y++) {
    tile = getTile(floor(characterX / tileWidth), floor((characterY + (y * tileHeight)) / tileHeight));
    ... check tile OK....
}

// finally check bottom
tile = getTile(floor(characterX / tileWidth), floor((characterY + characterHeight) / tileHeight);
... check tile OK....

Wenn das Zeichen breiter als 1 Block ist, machen Sie dasselbe, aber ersetzen Sie Zeichen Y, Zeichenhöhe, Kachelhöhe usw. durch Zeichen X, Zeichenbreite usw.

John
quelle
2

Was ist, wenn Sie im Rahmen der Bewegungsmethode des Spielers nach einer möglichen Kollision gesucht und eine Bewegung abgelehnt haben, die dazu geführt hat, dass der Spieler in ein anderes Objekt gerutscht ist? Dies würde den Spieler daran hindern, in einen Block zu gelangen, und daher könnte er sich nicht auf dem darunter liegenden Block befinden.

Irgendein Typ
quelle
1

Ich bin in einem Spiel, an dem ich gerade arbeite, auf fast genau das gleiche Problem gestoßen. Die Art und Weise, wie ich es gelöst habe, bestand darin, den Bereich des überlappenden Teils beim Testen auf Kollisionen zu speichern und diese Kollisionen dann basierend auf ihrer Priorität zu behandeln (größter Bereich zuerst) und dann jede Kollision zu überprüfen, um festzustellen, ob sie bereits von einem früheren gelöst wurde Handhabung.

In Ihrem Beispiel wäre der Bereich der Kollision auf der x-Achse größer als die auf der y-Achse, sodass er zuerst behandelt wird. Bevor dann versucht wird, die Kollision mit der y-Achse zu behandeln, wird überprüft, ob sie nach dem Verschieben auf der x-Achse nicht mehr kollidiert. :) :)

Nick Badal
quelle
1

Eine einfache, aber nicht perfekte Lösung besteht darin, die Form der Player-Kollisionsbox so zu ändern, dass sie kein Quadrat ist. Schneiden Sie die Ecken ab, um ihn achteckig oder ähnlich zu machen. Auf diese Weise rutscht er ab, wenn er mit einer Fliese wie der in der Wand kollidiert und nicht erwischt wird. Dies ist nicht perfekt, da Sie immer noch bemerken können, dass er ein wenig an Ecken hängen bleibt, aber er bleibt nicht hängen und ist sehr einfach zu implementieren.

Amplify91
quelle
1

Ein sehr ähnliches Problem wurde in diesem Tutorial behandelt: Einführung in die Spieleentwicklung . Der relevante Teil des Videos ist um 1:44 Uhr und wird mit Diagrammen und Codefragmenten erklärt.

Hinweis 14-tilemap.py weist das Beschneidungsproblem auf, das jedoch in 15-blocker-sides.py behoben ist

Ich kann nicht genau erklären, warum das letzte Tutorial-Beispiel nicht abgeschnitten wird, während Ihr Code funktioniert, aber ich denke, es hat etwas damit zu tun:

  • Bedingte Kollisionsreaktion basierend auf dem Kacheltyp (welche Kanten tatsächlich aktiv sind).
  • Der Spieler fällt immer. Obwohl der Spieler auf einem Plättchen zu ruhen scheint, fällt der Spieler tatsächlich wiederholt durch das Plättchen und wird dann nach oben zurückgeschoben. (Das self.restingMitglied im Code wird nur verwendet, um zu überprüfen, ob der Spieler springen kann. Trotzdem kann ich den Spieler nicht dazu bringen, einen "Doppelsprung" von einer Wand zu machen. Theoretisch könnte es jedoch möglich sein.)
Leftium
quelle
0

Eine andere Methode (mit der ich die Frage Byte56 beantwortet habe) wäre zu überprüfen, ob die Kollisionsauflösung das Zeichen an eine leere Stelle bringt oder nicht. In Ihrem Problem erhalten Sie also eine Kollision von der Decke des inneren Rechtecks, um es nach oben zu verschieben, was illegal wäre (da Sie immer noch mit einer anderen Kachel kollidieren). Stattdessen verschieben Sie es nur, wenn Sie sich in einem freien Bereich befinden (z. B. wie die Kollision von der oberen Kachel Sie nach links bewegen würde). Sobald Sie diese Kollision gefunden haben, sind Sie fertig.

Die Antwort, die ich gab, hatte Code, aber es wurde zu kompliziert. Ich würde alle Kollisionen innerhalb dieses Zeitrahmens verfolgen und nur die nehmen, die zu einem freien Speicherplatz führen. Wenn es jedoch keine gab, habe ich die Position des Charakters auf die letzte Position zurückgesetzt. Das ist jedoch sehr hässlich. Vielleicht möchten Sie lieber etwas wie das ursprüngliche Mario implementieren, bei dem er sich nur in eine Richtung bewegt, oder etwas, wenn keine Auflösung des freien Speicherplatzes vorliegt ist möglich. Sie können auch diese Liste der Kollisionsauflösungen sortieren und nur mit der kürzesten Entfernung in den freien Raum wechseln (ich denke, diese Lösung wäre am besten geeignet, obwohl ich dafür nicht codiert habe).

Jeff
quelle
0

Ich habe nur 3 SAT in 3D gemacht, also verstehe ich das vielleicht nicht richtig. Wenn ich Ihr Problem verstehe, kann dies daran liegen, dass Kontakte mit einer Kontaktachse hergestellt werden, die nicht möglich sein sollten, was dazu führt, dass sich der Spieler so verhält, als gäbe es einen Boden. Eine Problemumgehung könnte darin bestehen, stattdessen eine Kreisform für Ihren Charakter zu verwenden. Dann erhalten Sie nur Kontaktachsen in Richtung der x-Achse, wenn Sie gegen eine Wand stoßen.

Mikael Högström
quelle