Die Karte
Ich erstelle ein kachelbasiertes Rollenspiel mit Javascript, verwende Perlin-Rauschhöhenkarten und weise dann einen Kacheltyp basierend auf der Höhe des Rauschens zu.
Die Karten sehen am Ende ungefähr so aus (in der Minikartenansicht).
Ich habe einen ziemlich einfachen Algorithmus, der den Farbwert aus jedem Pixel im Bild extrahiert und ihn in eine Ganzzahl (0-5) umwandelt, abhängig von seiner Position zwischen (0-255), die einer Kachel im Kachelwörterbuch entspricht. Dieses 200x200-Array wird dann an den Client übergeben.
Die Engine ermittelt dann die Kacheln aus den Werten im Array und zeichnet sie auf die Zeichenfläche. Am Ende habe ich interessante Welten mit realistisch aussehenden Merkmalen: Berge, Meere usw.
Als nächstes wollte ich eine Art Mischalgorithmus anwenden, der dazu führt, dass sich Kacheln nahtlos in ihre Nachbarn einfügen, wenn der Nachbar nicht vom gleichen Typ ist. Die obige Beispielkarte ist das, was der Spieler in seiner Minikarte sieht. Auf dem Bildschirm sehen sie eine gerenderte Version des durch das weiße Rechteck markierten Abschnitts. Hier werden die Kacheln mit ihren Bildern und nicht als einfarbige Pixel gerendert.
Dies ist ein Beispiel dafür, was der Benutzer auf der Karte sehen würde, aber es ist nicht derselbe Ort, den das obige Ansichtsfenster zeigt!
In dieser Ansicht möchte ich, dass der Übergang stattfindet.
Der Algorithmus
Ich habe einen einfachen Algorithmus entwickelt, der die Karte innerhalb des Ansichtsfensters durchläuft und ein weiteres Bild über jeder Kachel rendert, vorausgesetzt, es befindet sich neben einer Kachel unterschiedlichen Typs. (Ändern Sie die Karte nicht! Rendern Sie nur einige zusätzliche Bilder.) Die Idee des Algorithmus bestand darin, die Nachbarn der aktuellen Kachel zu profilieren:
Dies ist ein Beispielszenario für das, was die Engine möglicherweise rendern muss, wobei die aktuelle Kachel mit dem X markiert ist.
Ein 3x3-Array wird erstellt und die umgebenden Werte werden eingelesen. In diesem Beispiel würde das Array also so aussehen.
[
[1,2,2]
[1,2,2]
[1,1,2]
];
Meine Idee war es dann, eine Reihe von Fällen für die möglichen Kachelkonfigurationen auszuarbeiten. Auf einer sehr einfachen Ebene:
if(profile[0][1] != profile[1][1]){
//draw a tile which is half sand and half transparent
//Over the current tile -> profile[1][1]
...
}
Welches ergibt dieses Ergebnis:
Das funktioniert als Übergang von [0][1]
nach [1][1]
, aber nicht von [1][1]
nach [2][1]
, wo eine harte Kante bleibt. Also dachte ich mir, dass in diesem Fall eine Eckplatte verwendet werden müsste. Ich habe zwei 3x3-Sprite-Blätter erstellt, von denen ich dachte, dass sie alle möglichen Kombinationen von Kacheln enthalten, die benötigt werden könnten. Dann habe ich dies für alle Kacheln im Spiel repliziert (Die weißen Bereiche sind transparent). Dies ergibt 16 Kacheln für jeden Kacheltyp (Die mittleren Kacheln auf jedem Spritesheet werden nicht verwendet.)
Das ideale Ergebnis
Mit diesen neuen Kacheln und dem richtigen Algorithmus würde der Beispielabschnitt folgendermaßen aussehen:
Jeder Versuch, den ich gemacht habe, ist jedoch fehlgeschlagen, es gibt immer einen Fehler im Algorithmus und die Muster enden seltsam. Ich kann nicht alle Fälle richtig machen und insgesamt scheint es eine schlechte Art zu sein, dies zu tun.
Eine Lösung?
Wenn also jemand eine alternative Lösung anbieten könnte, wie ich diesen Effekt erzeugen könnte oder in welche Richtung der Profilerstellungsalgorithmus geschrieben werden soll, wäre ich sehr dankbar!
quelle
Antworten:
Die Grundidee dieses Algorithmus besteht darin, in einem Vorverarbeitungsschritt alle Kanten zu finden und dann die richtige Glättungskachel entsprechend der Kantenform auszuwählen.
Der erste Schritt wäre, alle Kanten zu finden. In dem Beispiel unter den Randfliesen mit einem X gekennzeichnet sind , sind alle grünen Fliesen mit einem tan Fliese als eine oder mehrere ihrer acht benachbarten Fliesen. Bei verschiedenen Geländetypen kann diese Bedingung dazu führen, dass ein Plättchen ein Randplättchen ist, wenn es Nachbarn mit einer niedrigeren Geländezahl hat.
Sobald alle Kantenplättchen erkannt wurden, müssen Sie als Nächstes das richtige Glättungsplättchen für jedes Randplättchen auswählen. Hier ist meine Darstellung Ihrer Glättungskacheln.
Beachten Sie, dass es tatsächlich nicht so viele verschiedene Arten von Kacheln gibt. Wir brauchen die acht äußeren Kacheln von einem der 3x3-Quadrate, aber nur die vier Eckquadrate vom anderen, da die geraden Kacheln bereits im ersten Quadrat gefunden werden. Dies bedeutet, dass es insgesamt 12 verschiedene Fälle gibt, zwischen denen wir unterscheiden müssen.
Wenn wir nun eine Randkachel betrachten, können wir bestimmen, in welche Richtung sich die Grenze dreht, indem wir die vier Kacheln der nächsten Nachbarn betrachten. Wenn Sie eine Randkachel wie oben mit X markieren, haben wir die folgenden sechs verschiedenen Fälle.
Diese Fälle werden verwendet, um die entsprechende Glättungskachel zu bestimmen, und wir können die Glättungskacheln entsprechend nummerieren.
Es gibt immer noch die Wahl zwischen a oder b für jeden Fall. Dies hängt davon ab, auf welcher Seite sich das Gras befindet. Eine Möglichkeit, dies festzustellen, könnte darin bestehen, die Ausrichtung der Grenze zu verfolgen. Die wahrscheinlich einfachste Möglichkeit besteht darin, eine Kachel neben der Kante auszuwählen und zu sehen, welche Farbe sie hat. Das Bild unten zeigt die beiden Fälle 5a) und 5b), die unterschieden werden können, indem beispielsweise die Farbe der oberen rechten Kachel überprüft wird.
Die endgültige Aufzählung für das ursprüngliche Beispiel würde dann so aussehen.
Und nach Auswahl der entsprechenden Randkachel würde der Rand ungefähr so aussehen.
Abschließend könnte ich sagen, dass dies funktionieren würde, solange die Grenze etwas regelmäßig ist. Genauer gesagt müssen Randkacheln, die nicht genau zwei Randkacheln als Nachbarn haben, separat behandelt werden. Dies tritt bei Randkacheln am Rand der Karte auf, die einen einzelnen Randnachbarn haben, und bei sehr schmalen Geländestücken, bei denen die Anzahl der benachbarten Randkacheln drei oder sogar vier betragen kann.
quelle
Das folgende Quadrat repräsentiert eine Metallplatte. In der oberen rechten Ecke befindet sich eine "Wärmeentlüftung". Wir können sehen, wie die Metallplatte bei konstanter Temperatur dieses Punktes an jedem Punkt gegen eine konstante Temperatur konvergiert und in der Nähe der Oberseite von Natur aus heißer ist:
Das Problem, die Temperatur an jedem Punkt zu finden, kann als "Randwertproblem" gelöst werden. Der einfachste Weg, die Wärme an jedem Punkt zu berechnen, besteht darin, die Platte als Gitter zu modellieren. Wir kennen die Punkte auf dem Gitter bei konstanter Temperatur. Wir stellen die Temperatur aller unbekannten Punkte auf Raumtemperatur ein (als ob die Entlüftung gerade erst eingeschaltet worden wäre). Wir lassen dann die Wärme über die Platte verteilen, bis wir Konvergenz erreichen. Dies geschieht durch Iteration: Wir iterieren durch jeden (i, j) Punkt. Wir setzen Punkt (i, j) = (Punkt (i + 1, j) + Punkt (i-1, j) + Punkt (i, j + 1) + Punkt (i, j-1)) / 4 [es sei denn Punkt (i, j) hat eine Wärmeentlüftung mit konstanter Temperatur]
Wenn Sie dies auf Ihr Problem anwenden, ist es sehr ähnlich, nur durchschnittliche Farben anstelle von Temperaturen. Sie würden wahrscheinlich ungefähr 5 Iterationen benötigen. Ich schlage vor, ein 400x400-Raster zu verwenden. Das sind 400x400x5 = weniger als 1 Million Iterationen, die schnell sein werden. Wenn Sie nur 5 Iterationen verwenden, müssen Sie sich wahrscheinlich keine Gedanken darüber machen, ob Punkte eine konstante Farbe haben, da sie sich nicht zu stark von ihrem Original verschieben (tatsächlich können nur Punkte innerhalb des Abstands 5 von der Farbe durch die Farbe beeinflusst werden). Pseudocode:
quelle
Ok, die ersten Gedanken sind also, dass die Automatisierung einer perfekten Lösung des Problems einige ziemlich fleischige Interpolationsmathematik erfordert. Aufgrund der Tatsache, dass Sie vorgerenderte Kachelbilder erwähnen, gehe ich davon aus, dass die vollständige Interpolationslösung hier nicht gerechtfertigt ist.
Auf der anderen Seite führt das Fertigstellen der Karte von Hand zu einem guten Ergebnis ... aber ich gehe auch davon aus, dass ein manueller Vorgang zum Beheben von Störungen ebenfalls keine Option ist.
Hier ist ein einfacher Algorithmus, der kein perfektes Ergebnis liefert, der jedoch aufgrund des geringen Aufwands sehr lohnend ist.
Anstatt zu versuchen, JEDE Randkachel zu mischen (was bedeutet, dass Sie entweder zuerst das Ergebnis der Mischung der benachbarten Kacheln kennen müssen - Interpolation, oder Sie müssen die gesamte Karte mehrmals verfeinern und können sich nicht auf vorgenerierte Kacheln verlassen). Warum nicht Fliesen in einem abwechselnden Schachbrettmuster mischen?
Dh nur die Kacheln in der Matrix oben mischen?
Unter der Annahme, dass die einzigen zulässigen Wertschritte einzeln sind, müssen Sie nur wenige Kacheln entwerfen ...
Insgesamt gibt es 16 Muster. Wenn Sie die Rotations- und Reflexionssymmetrie nutzen, gibt es noch weniger.
'A' wäre eine einfache [1] Kachel. 'D' wäre eine Diagonale.
An den Ecken der Kacheln treten kleine Diskontinuitäten auf, die jedoch im Vergleich zu dem von Ihnen angegebenen Beispiel geringfügig sind.
Wenn ich kann, werde ich diesen Beitrag später mit Bildern aktualisieren.
quelle
Ich habe mit etwas Ähnlichem herumgespielt, es wurde aus mehreren Gründen nicht beendet; Aber im Grunde würde es eine Matrix von 0 und 1 brauchen, wobei 0 der Boden und 1 eine Wand für eine Labyrinthgeneratoranwendung in Flash ist. Da AS3 JavaScript ähnelt, ist das Umschreiben in JS nicht schwierig.
Grundsätzlich wird dabei jede Kachel von links nach rechts, von oben nach unten überprüft und davon ausgegangen, dass die Randkacheln immer 1 sind. Ich habe mir auch die Freiheit genommen, die Bilder als Datei zu exportieren, um sie als Schlüssel zu verwenden:
Dies ist unvollständig und wahrscheinlich ein hackiger Weg, um dies zu erreichen, aber ich dachte, es könnte von Nutzen sein.
Bearbeiten: Screenshot des Ergebnisses dieses Codes.
quelle
Ich würde ein paar Dinge vorschlagen:
Es spielt keine Rolle, was die "mittlere" Kachel ist, oder? es könnte 2 sein, aber wenn alle anderen 1 sind, würde es 1 zeigen?
Es ist nur wichtig, was die Ecken sind, wenn es einen Unterschied in den unmittelbaren Nachbarn oben oder seitlich gibt. Wenn alle unmittelbaren Nachbarn 1 sind und eine Ecke 2 ist, wird 1 angezeigt.
Ich würde wahrscheinlich alle möglichen Kombinationen von Nachbarn vorberechnen und ein 8-Index-Array erstellen, wobei die ersten vier die Werte der oberen / unteren Nachbarn und die zweite die Diagonalen angeben:
Kanten [N] [E] [S] [W] [NE] [SE] [SW] [NW] = welcher Versatz auch immer in Sprite versetzt wird
In Ihrem Fall ist also [2] [2] [1] [1] [2] [2] [1] [1] = 4 (das 5. Sprite).
In diesem Fall wäre [1] [1] [1] [1] 1, [2] [2] [2] [2] 2, und der Rest müsste ausgearbeitet werden. Die Suche nach einer bestimmten Kachel wäre jedoch trivial.
quelle