Я создал систему, похожую на ту, что вам нужна в 3D. У меня есть короткое видео, демонстрирующее простую механику этого здесь и сообщение в блоге здесь .
Вот небольшой подарок, который я сделал из механики давления за невидимой стеной (играли на высокой скорости):
Позвольте мне объяснить соответствующие данные, чтобы дать представление о некоторых особенностях системы. В существующей системе каждый блок воды содержит следующие данные в 2 байта:
//Data2 Data
//______________________________ _____________________________________
//|0 |0 |000 |000 | |0 |0 |000 |000 |
//|Extra|FlowOut|Active|Largest| |HasSource|IsSource|Direction|Height|
//------------------------------ -------------------------------------
Height
это количество воды в кубе, подобное вашему давлению, но моя система имеет всего 8 уровней.
Direction
это направление потока. При принятии решения о том, куда будет течь вода, вероятнее всего, она продолжит движение в текущем направлении. Это также используется для быстрого обратного отслеживания потока до его исходного куба, когда это необходимо.
IsSource
указывает, является ли этот куб исходным кубом, что означает, что в нем никогда не заканчивается вода. Используется для источника рек, родников и т. Д. Куб слева в GIF выше, например, является исходным кубом.
HasSource
указывает, подключен ли этот куб к исходному кубу. При подключении к источнику кубы будут пытаться использовать источник для получения большего количества воды, прежде чем искать другие «более полные» кубы, не являющиеся источниками.
Largest
говорит этому кубу, каков самый большой поток между ним и его исходным кубом. Это означает, что если вода течет через узкий зазор, это ограничивает поток в этот куб.
Active
это счетчик. Когда этот куб имеет активный поток, проходящий через него, в него или из него, активный увеличивается. В противном случае активный случайным образом уменьшается. Как только активность достигнет нуля (имеется в виду не активна), количество воды в этом кубе начнет уменьшаться. Этот вид действует как испарение или впитывание в землю. ( Если у вас есть поток, вы должны иметь отлив! )
FlowOut
указывает, связан ли этот куб с кубом, который находится на краю мира. Как только путь к краю света проложен, вода стремится выбрать этот путь перед любым другим.
Extra
это дополнительный бит для будущего использования.
Теперь, когда мы знаем данные, давайте посмотрим на общий обзор алгоритма. Основная идея системы состоит в том, чтобы расставить приоритеты в направлении потока вниз и наружу. Как я объясняю в видео, я работаю снизу вверх. Каждый слой воды обрабатывается по одному уровню по оси Y. Кубы для каждого уровня обрабатываются случайным образом, каждый куб будет пытаться вытягивать воду из своего источника на каждой итерации.
Кубы потока вытягивают воду из источника, следуя направлению потока обратно вверх, пока они не достигнут куба источника или куба потока без родителя. Сохранение направления потока в каждом кубе упрощает отслеживание пути к источнику и обход связанного списка.
Псевдокод для алгоритма выглядит следующим образом:
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()
Основные правила расширения потока, где (упорядочено по приоритету):
- Если куб ниже имеет меньше воды, стекают
- Если в соседнем кубе на том же уровне меньше воды, течь в боковом направлении.
- Если у куба выше меньше воды И кубик источника выше, чем у куба выше, поднимитесь вверх.
Я знаю, это довольно высокий уровень. Но это трудно , чтобы получить более подробно , не получая путь в детали.
Эта система работает довольно хорошо. Я могу легко заполнить ямы водой, которые переливаются, чтобы продолжаться наружу. Я могу заполнить U-образные туннели, как вы видите в GIF выше. Однако, как я уже сказал, система неполна, и я еще не все решил. Я давно не работал над системой потоков (я решил, что она не нужна для альфы, и отложил ее). Тем не менее, проблемы, с которыми я столкнулся, когда я отложил их, где:
Бассейны . При получении большого объёма воды указатели от ребенка к родителю похожи на безумный беспорядок любого случайного куба, выбранного для направления в любом направлении. Как заполнить ванну глупой нитью. Если вы хотите слить ванну, следует ли вам идти по пути глупой струны обратно к ее источнику? Или просто взять то, что ближе? Поэтому в ситуациях, когда кубы находятся в большом пуле, они, скорее всего, должны просто игнорировать свои родительские потоки и извлекать все, что находится над ними. Я придумал некоторый основной рабочий код для этого, но у меня никогда не было элегантного решения, которым я мог бы быть доволен.
Несколько родителей . Дочерний поток может легко обслуживаться несколькими родительскими потоками. Но ребенок, имеющий указатель на одного из родителей, не допустит этого. Это можно исправить, используя достаточное количество битов, чтобы учесть бит для каждого возможного родительского направления. И, вероятно, изменение алгоритма для случайного выбора пути в случае нескольких родителей. Но я так и не нашел время, чтобы проверить и выяснить, какие другие проблемы могут это выявить.