Бесконечная 3D Пещера в Единстве

8

Мы с другом надеемся создать игру в Unity, в которой вы летите через бесконечную 3D-пещеру, которая может крутиться и крутиться в любом направлении (хотя, очевидно, не до такой степени, что развороты сделать невозможно). Мы думали о создании ряда туннельных «кусочков», каждый из которых искривлял определенное количество, и порождал каждый в конце того, что было раньше.

Но мы понятия не имеем, как сделать так, чтобы горловина одной части туннеля всегда идеально совмещалась (и в положении, и в повороте) с концом предыдущего. Может кто-нибудь предложить какой-либо совет, как этого добиться?

Правильно ли мы идем по этому пути, или есть лучший способ процедурно создать пещеру? Бонусные очки: Было бы замечательно, если бы пещера могла измениться в диаметре и / или форме, хотя это было бы просто соусом.

richardmherndon
источник

Ответы:

6

Когда дело доходит до игрового дизайна, редко бывает «правильный путь» или «неправильный путь». Есть много способов решить эту проблему, но вот несколько возможных подходов для исследования:

  • Заставьте части туннеля начинаться и заканчиваться только в определенных направлениях; например только по осям. Тогда вам просто нужно отслеживать смещение от начала до конца сегмента вместе с перечислениями, описывающими направление движения в начале и конце сегмента. Таким образом, вам не нужно беспокоиться о вращении ваших туннельных сеток, если вы всегда выбираете следующую так, чтобы она начиналась в том же направлении, в котором заканчивалась последняя.

  • Сделайте так, чтобы каждый сегмент начинался с начала локального пространства модели с туннелем, проходящим вдоль определенной оси (+ X, + Z или -Z было бы наиболее логичным выбором, но все модели должны использовать один и тот же), затем Сохраните положение конца туннеля и конечное направление движения таким образом, чтобы следующая сетка могла быть правильно преобразована. (Матрица преобразования, вероятно, является самым простым способом хранения этой информации, но вы также можете использовать вектор смещения + кватернион, двойной кватернион, смещение + новые базисные векторы, смещение + повороты Эйлера и т. д.)

  • Процедурно сгенерируйте свою пещеру, передавая новые данные вершин в несколько ячеек. Вы можете сделать это с помощью Meshкласса . При создании новых данных вершин, возможно, самый простой способ - выбрать точку где-то примерно в том же направлении, что и предыдущий сегмент пещеры, а затем позволить центру пещеры двигаться к этой точке. Затем вы можете использовать цилиндрические координаты, чтобы процедурно создать детали на стенах пещеры. Думайте об этом как о выдавливании конца цилиндра, а затем индивидуальном перемещении каждой вершины ближе или дальше от центра этого цилиндра.

Любое решение, использующее готовые сегменты, потребует от вас убедиться, что все сетки имеют одинаковую форму и диаметр вокруг центра туннеля, но вы можете обойти это несколько, если сегменты перекрываются в некоторой степени, и каждый сегмент расширяется на концах. Если все сделано правильно, для игрока не должно быть слишком очевидно, что есть шов.

С другой стороны, если вы будете использовать полностью процедурно-сгенерированную геометрию, у вас будет больше работы, чтобы гарантировать, что вы не создадите участки, которые невозможно пройти, и у вас могут возникнуть проблемы с обнаружением столкновений.

Имейте в виду, что в любой «бесконечной» игре вы должны знать об ограничениях представлений с плавающей запятой. Если игрок слишком далеко отходит от мирового происхождения, становится легко потерять точность в вычислениях с плавающей запятой (например, когда два больших значения вычитаются друг из друга). Чтобы избежать этого, вы можете сделать так, чтобы мир двигался вокруг игрока, а не игрок двигался по миру, но обычно легче просто проверять положение игрока время от времени, и, если они находятся слишком далеко от начала координат, восстанавливать мир с игроком в или около начала.

bcrist
источник
2
+1 специально для комментария «нет правильного пути» (хотя мне придется немного не согласиться: есть много, много неправильных путей ...)
Стивен Стадницки
Спасибо! Мы закончили тем, что использовали несколько различных туннельных фрагментов с заранее известными конечными местоположениями и направлениями, установили маркеры в этих местах / углах и поместили каждый новый кусок относительно маркера предыдущего фрагмента. Было бы круто сделать что-то более сложное, но на данный момент более законное процедурное поколение выходило за пределы нашего диапазона навыков и временных ограничений. Еще раз спасибо!
Ричардмхерндон
3

Вот техника, с которой я экспериментировал недавно. Мой прототип RenderMonkey показывает часть каньона в стиле бесплодных земель, но тот же принцип должен работать в пещерах.

Идея состоит в том, чтобы начать с плиток, которые являются общими, совершенно скучными, с простыми предсказуемыми краями, чтобы их было легко выстраивать без швов или разрывов:

Тусклая, предсказуемая плитка

Эти начальные плитки могут быть формами, которые вы смоделировали, или процедурно сгенерированными макаронными трубками цилиндрической геометрии (эта форма является вариантом по предложению bcrist и Steven Stadnicki). Использование созданных моделей упрощает обработку произвольной топологии, например путей ветвления, или точек интереса, таких как открытые каверны. Это все еще возможно с чисто процедурной (см. Предложение Gyroninja о методах metaball), но сложно.

Как только плитка помещена в мир, сместите ее вершины, используя шумовые функции, применяемые в мировом пространстве. Это сохраняет связь и бесшовность между плитками (поскольку совпадающие вершины имеют одинаковый входной сигнал в мировом пространстве и получают одинаковый выходной сигнал смещения), но при этом каждая плитка выглядит уникальной и органичной:

Это интереснее

Текстура и нормали также применяются в мировом пространстве - здесь используется трипланарное картирование - так, чтобы соседние плитки были полностью бесшовными, без хитрых ограничений развертывания в УФ.

Тоже интереснее

Надежда состоит в том, что подобная техника дает вам простоту планирования и управления дизайном уровня мозаичной карты без видимого повторения или механически выглядящей структуры в воспроизводимом результате.

Вы можете использовать сетку с низким разрешением, используя только компоненты низкочастотного шума, применяемые для создания представления столкновения. Как отмечает bcrist, вам необходимо контролировать максимальную амплитуду шума относительно радиуса и резкости поворотов туннеля, чтобы он никогда не сжимался полностью.

Еще одно замечание: если ваша пещера действительно бесконечна, вам, возможно, придется периодически «пересекать» ее, когда игрок продвигается все дальше и дальше от начала координат. Поскольку числа с плавающей запятой теряют точность при больших величинах, физические объекты и артефакты рендеринга могут заползать на огромные расстояния. Если вы сделаете это, вы захотите, чтобы ваш шум в мировом пространстве был периодическим в больших масштабах, с периодом, точно соответствующим вашему центрирующему смещению, чтобы вы не встретили швы после повторного центрирования.

Д.М.Григорий
источник
2

Вы можете смоделировать свою пещеру как последовательность точек, каждая из которых имеет соответствующий размер, с линиями, соединяющими их. Затем обработайте каждую точку и линию как Metaballs и metacylinders. Это дает вам базовую форму для вашей пещеры, к которой вы можете начать добавлять вариации, например, путем случайного смещения вершин.

Gyroninja
источник
2

Вот еще один подход к процессуальной генерации, который еще не был явно упомянут: скины сплайнов. Вы можете использовать версию Hermite Splines(которые обеспечивают кривую, интерполирующую позиции и касательные) для определения кривых: когда пришло время генерировать новый сегмент, просто выберите позицию (примерно в направлении предыдущего сегмента, как говорит bcrist) и направление (примерно в том же направление - например, в некотором четко определенном конусе предыдущего направления), затем используйте новую позицию + направление и ваше предыдущее положение + направление, чтобы построить новый «позвоночник» для вашей пещеры. Получив эту основную цепь, вы можете придать ей цилиндрическую форму: определите положения и касательные (например) 10 точек вдоль кривой, используйте эти положения / касательные, чтобы найти ортогональную «рамку», а затем используйте эти рамки для строить цилиндрические сегменты. Одна маленькая предосторожность с этим, что пещера не может кривая слишком много, иначе вы можете столкнуться с проблемами самопересечения.

РЕДАКТИРОВАТЬ: Вот грубая разбивка псевдокода алгоритма:

Parameters:
  L = (average) segment length,
  V = segment length variation,
  R = cylinder radius,
  T = segment angular variation
  S = number of 'rings' per segment

Setup:
Choose an initial point P_0 and direction D_0 (for concreteness' sake, these can be
the origin and the X axis).  Set P_prev and D_prev to these values.
Initialize u_prev to be the Y axis and v_prev to be the Y and Z axes.
  (Note that (D_prev, u_prev, v_prev) form a mutually-orthogonal 'coordinate frame')

Generate a segment (do this as many times as you want):
{
  Choose a (temporary) direction D within a cone of size T around the previous direction D_prev
  Choose a segment length L_cur = at random from within the range [L-V, L+V].
  Set the current terminal point P_cur to P_prev+D*L_cur - this is the position
  we'll interpolate to
  Set the current terminal direction D_cur to a direction chosen at random from
  within a cone of size T around the previous direction.  (There are good ways
  of doing this - if you look back through gamedev.SE you should find some)
  'Build' the Hermite spline H that goes from (P_prev, D_prev) to (P_cur, D_cur)

  Now, skin that spline:
  for ( i = 1; i <= S; i++ ) {
    find the position P of the hermite spline H at t=i/S
    find the direction D of the spline at t (this will be just the derivative)
    'transport' the orthogonal frame to the new spot: for instance,
      v_new = D x u_prev
      u_new = v_new x D
    (note that this keeps u_new, v_new close to their old values, and orthogonal
    to each other and to D)
    Use the previous and current frames and positions to build a cylindrical 'ring':
    For theta from 0 to 2pi {
      find the points (P+(u_new, v_new, D) * (cos theta, sin theta, 0))
      and connect them to their counterparts from the previous ring
      (note that that multiplication is a matrix-vector multiply)
    }
    update u_prev and v_prev to u_new and v_new
  }
  update the other prev variables to their 'new' values
}

Это явно очень грубый псевдокод; если есть какая-то неясная часть, просто дайте мне знать, и я постараюсь объяснить, но будет сложно охватить все детали без огромного дампа кода ...

Стивен Стадницки
источник
(Между прочим, если вы хотите использовать псевдокод для этого подхода, дайте мне знать; мне нужно было сделать что-то похожее на предыдущей работе, поэтому я решил проработать все мелкие детали.)
Стивен Стадницки
Мне было бы любопытно увидеть вашу реализацию; Я тоже когда-то делал нечто похожее, но вместо этого использовал трехмерные кубические кривые Безье.
bcrist