Ich habe ein System erstellt, das dem in 3D ähnelt, nach dem Sie suchen. Ich habe ein kurzes Video die einfache Mechanik davon demonstriert hier und eine Blog - Post hier .
Hier ist eine kleine Beschreibung der Druckmechanik hinter einer unsichtbaren Wand (gespielt mit hoher Geschwindigkeit):
Lassen Sie mich die Daten erläutern, um einen Eindruck von einigen Funktionen des Systems zu erhalten. In dem gegenwärtigen System enthält jeder Wasserblock das Folgende in 2 Bytes:
//Data2 Data
//______________________________ _____________________________________
//|0 |0 |000 |000 | |0 |0 |000 |000 |
//|Extra|FlowOut|Active|Largest| |HasSource|IsSource|Direction|Height|
//------------------------------ -------------------------------------
Height
ist die Wassermenge im Würfel, ähnlich wie Ihr Druck, aber mein System hat nur 8 Ebenen.
Direction
ist die Richtung, in die der Fluss fließt. Bei der Entscheidung, wohin das Wasser als nächstes fließen soll, ist es wahrscheinlicher, dass es in die aktuelle Richtung fließt. Dies wird auch verwendet, um einen Flow bei Bedarf schnell bis zu seinem Quellcube zurückzuverfolgen.
IsSource
Gibt an, ob es sich bei diesem Würfel um einen Quellwürfel handelt. Dies bedeutet, dass ihm niemals das Wasser ausgeht. Wird für die Quelle von Flüssen, Quellen usw. verwendet. Der Würfel links im GIF oben ist beispielsweise ein Quellwürfel.
HasSource
Gibt an, ob dieser Cube mit einem Quellcube verbunden ist. Wenn Würfel an eine Quelle angeschlossen sind, versuchen sie, auf die Quelle zu tippen, um mehr Wasser zu erhalten, bevor sie nach anderen "volleren" Würfeln suchen, die keine Quelle sind.
Largest
teilt diesem Würfel mit, welcher Fluss zwischen ihm und seinem Quellwürfel am größten ist. Das heißt, wenn Wasser durch einen engen Spalt fließt, wird der Durchfluss zu diesem Würfel begrenzt.
Active
ist ein Zähler. Wenn dieser Würfel einen aktiven Fluss durchläuft, zu ihm oder von ihm, wird active inkrementiert. Ansonsten wird active zufällig dekrementiert. Sobald Aktiv Null erreicht (dh nicht aktiv), wird die Wassermenge in diesem Würfel verringert. Diese Art von Handlungen wirken wie Verdampfen oder Einweichen in den Boden. ( Wenn Sie Strömung haben, sollten Sie Ebbe haben! )
FlowOut
Gibt an, ob dieser Würfel mit einem Würfel am Rand der Welt verbunden ist. Sobald ein Weg zum Rand der Welt gefunden ist, wählt das Wasser diesen Weg eher als jeden anderen.
Extra
ist ein zusätzliches Bit für die zukünftige Verwendung.
Nachdem wir die Daten kennen, sehen wir uns eine allgemeine Übersicht über den Algorithmus an. Die Grundidee des Systems besteht darin, das Ab- und Abfließen zu priorisieren. Wie ich im Video erkläre, arbeite ich von unten nach oben. Jede Wasserschicht wird auf der y-Achse stufenweise abgearbeitet. Die Würfel für jedes Level werden nach dem Zufallsprinzip verarbeitet. Jeder Würfel versucht bei jeder Iteration, Wasser aus seiner Quelle zu ziehen.
Strömungswürfel ziehen Wasser aus ihrer Quelle, indem sie ihrer Strömungsrichtung folgen, bis sie einen Quellwürfel oder einen Strömungswürfel ohne übergeordnetes Element erreichen. Durch das Speichern der Flussrichtung in jedem Cube wird das Verfolgen des Pfads zur Quelle so einfach wie das Durchlaufen einer verknüpften Liste.
Der Pseudocode für den Algorithmus lautet wie folgt:
for i = 0 to topOfWorld //from the bottom to the top
while flowouts[i].hasitems() //while this layer has flow outs
flowout = removeRandom(flowouts[i]) //select one randomly
srcpath = getPathToParent(flowout) //get the path to its parent
//set cubes as active and update their "largest" value
//also removes flow from the source for this flow cycle
srcpath.setActiveAndFlux()
//now we deal with regular flow
for i = 0 to topOfWorld //from the bottom to the top
while activeflows[i].hasitems() //while this layer has water
flowcube = removeRandom(activeflows[i]) //select one randomly
//if the current cube is already full, try to distribute to immediate neighbors
flowamt = 0
if flowcube.isfull
flowamt = flowcube.settleToSurrounding
else
srcpath = getPathToParent(flowcube) //get the path to its parent
flowamt = srcpath.setActiveAndFlux()
flowcube.addflow(flowamt)
//if we didn't end up moving any flow this iteration, reduce the activity
//if activity is 0 already, use a small random chance of removing flow
if flowamt == 0
flowcube.reduceActive()
refillSourceCubes()
Die Grundregeln zum Erweitern eines Ablaufs lauten (geordnet nach Priorität):
- Wenn der Würfel darunter weniger Wasser hat, fließen Sie nach unten
- Wenn der benachbarte Würfel auf demselben Niveau weniger Wasser hat, fließen Sie seitlich.
- Wenn der darüberliegende Würfel weniger Wasser enthält UND der Quellwürfel höher als der darüberliegende Würfel ist, fließen Sie nach oben.
Ich weiß, das ist ziemlich hoch. Aber es ist schwer , mehr ins Detail zu bekommen , ohne sie Weg ins Detail.
Dieses System funktioniert ziemlich gut. Ich kann leicht Wassergruben auffüllen, die überlaufen, um nach außen zu gelangen. Ich kann U-förmige Tunnel füllen, wie Sie oben im GIF sehen. Wie gesagt, das System ist unvollständig und ich habe noch nicht alles ausgearbeitet. Ich habe lange nicht mehr am Flow-System gearbeitet (ich entschied, dass es für Alpha nicht benötigt wurde und stellte es in die Warteschleife). Die Probleme, mit denen ich mich befasste, als ich es auf Eis legte, waren jedoch:
Pools . Wenn Sie einen großen Wasserbecken bekommen, sind die Zeiger von Kind zu Eltern wie eine verrückte Sauerei, egal welcher zufällige Würfel ausgewählt wurde, um in welche Richtung zu fließen. Als würde man eine Badewanne mit einer dummen Schnur füllen. Wenn Sie die Wanne entleeren möchten, sollten Sie dem Pfad der dummen Schnur zurück zu ihrer Quelle folgen? Oder solltest du einfach nehmen, was am nächsten ist? In Situationen, in denen sich Cubes in einem großen Pool befinden, sollten sie wahrscheinlich nur die übergeordneten Flows ignorieren und von allem, was sich über ihnen befindet, ziehen. Ich hatte einen grundlegenden Arbeitscode dafür, aber nie eine elegante Lösung, mit der ich zufrieden sein konnte.
Mehrere Eltern . Ein untergeordneter Stream kann leicht von mehr als einem übergeordneten Stream gespeist werden. Aber das Kind, das einen Zeiger auf einen einzelnen Elternteil hat, würde das nicht zulassen. Dies kann behoben werden, indem genügend Bits verwendet werden, um ein Bit für jede mögliche übergeordnete Richtung zuzulassen. Und wahrscheinlich wird der Algorithmus so geändert, dass bei mehreren Elternteilen zufällig ein Pfad ausgewählt wird. Aber ich bin nie dazu gekommen, um zu testen und zu sehen, welche anderen Probleme dies aufdecken könnte.