Layer-Based Procedural Generation for Infinite Worlds

runevision
21 Sept 201312:17

Summary

TLDR本文介绍了一种无边界无限世界的程序生成方法。通过使用滚动窗口和2D数组,可以创建一个在任何方向上无限延伸的世界。作者探讨了在无限世界中进行程序生成时需要考虑的问题,如如何在不同层级上加载和调整点的位置,以及如何计算世界之间的连接。这种方法允许即使在没有边界的情况下,也能实现复杂度较高的程序生成。

Takeaways

  • 🌐 无限世界可以通过滚动窗口的方式在2D数组上实现。
  • 🔄 世界可以无限扩展,通过在不同层级加载细节来实现。
  • 🔍 在无限世界中,无法一次性获取所有邻居信息,因为总会有新的邻居出现。
  • 📊 通过分层生成,可以在不知道整个画面的情况下进行复杂的程序生成。
  • 🔢 每一层的生成依赖于前一层的信息,确保相邻单元格的数据一致性。
  • 🔄 层与层之间需要加载相邻单元格的信息,以便调整和优化生成的点。
  • 🌐 通过三角剖分和选择性连接,可以创建自然且不完全连接的世界。
  • 🔄 随着层数的增加,对周围环境的信息了解越多,但最远路径只能在低抽象层级加载。
  • 🔄 游戏开发中,除了上述方法,还可以使用完全解耦的层,它们可以有不同的网格分辨率。
  • 🔄 加载新层时,旧层会升级或卸载,形成一个动态的加载和卸载过程。
  • 🔄 这种方法允许在没有边界的情况下进行高复杂度的程序生成。

Q & A

  • 什么是无限世界中的程序生成?

    -无限世界中的程序生成是一种技术,它允许创建一个理论上无限大的游戏世界,通过滚动窗口的方式来探索,这个窗口随着玩家的移动而移动。

  • 在程序生成中,如何处理无限世界中的边界问题?

    -在无限世界中,没有明确的边界,因此需要一种方法来动态加载和卸载世界的不同部分。这通常涉及到在玩家接近当前加载区域的边缘时,加载新的区域,并在玩家远离时卸载旧的区域。

  • 如何确保在无限世界中生成的点分布均匀?

    -为了确保点分布均匀,需要在生成随机点后进行调整。这包括了解邻近单元格中的点,以避免点过于接近。通过这种方式,可以确保点在整个无限世界中分布得相对均匀。

  • 在程序生成的无限世界中,如何确定世界的大小?

    -在无限世界中,世界的大小不是固定的,而是根据玩家的位置和视野动态确定的。世界的大小取决于玩家当前加载区域的层级,以及这个区域与邻近区域的连接情况。

  • 什么是程序生成中的层级结构?

    -程序生成中的层级结构是一种组织和处理数据的方法,其中每个层级依赖于更低层级的数据。例如,一个层级可能需要知道邻近所有八个单元格的数据才能进行计算,这样可以确保生成的数据是连贯的。

  • 在无限世界中,如何实现不同世界之间的连接?

    -为了实现不同世界之间的连接,首先进行点的三角剖分,然后选择一个子集来形成连接。这样可以确保世界之间有路径可以相互到达,但不是每个世界都直接相连,有时可能需要通过第三个世界来实现转移。

  • 在程序生成中,如何处理不同层级之间的数据依赖?

    -在程序生成中,每个层级的数据依赖于更低层级的数据。这意味着在进行高层级的数据计算之前,必须先加载并处理所有依赖的低层级数据。这种依赖关系决定了加载区域需要扩展的范围。

  • 在无限世界中,如何实现动态的加载和卸载?

    -动态加载和卸载是通过在玩家移动时加载新的层级和卸载旧的层级来实现的。随着玩家的移动,最外层的单元格被加载到新的层级,而已经加载的单元格则升级到更高的层级。当单元格移出视野范围时,它们会被逐步卸载。

  • 在程序生成的无限世界中,如何处理不同层级的分辨率?

    -在某些情况下,可以使用完全解耦的层级,这些层级可以有不同的网格分辨率。每一层都可以独立地请求信息,而不需要关心其他层的内部实现细节,这样可以处理不同分辨率的数据。

  • 在无限世界中,如何确保邻近单元格之间的连接?

    -为了确保邻近单元格之间的连接,可以在每个邻近单元格中选择一个世界,使其与当前单元格中的某个世界相连。这样可以确保玩家可以从任何世界到达其他世界,尽管可能需要通过一个中间世界。

Outlines

00:00

🌐 无限世界的程序生成

介绍了如何通过滚动窗口的方式实现一个无限世界的程序生成。通过2D数组来模拟无限世界,并且讨论了在没有边界的情况下进行程序生成时需要考虑的问题。提出了一种抽象的环形世界概念,并通过不同层次的细节加载来展示如何在无限世界中实现局部的细节展示。

05:06

🔍 程序生成的实现细节

详细讨论了在无限世界中进行程序生成时的实现细节。包括如何调整随机生成的点以避免过于集中,以及如何计算世界之间的连接。介绍了一种分层的方法,每一层都需要加载相邻层的信息,以确保生成的点和连接是合理的。还提到了如何通过三角剖分来确定连接点,以及如何确保每个相邻的单元格之间至少有一个连接。

10:06

🔄 层次加载与卸载

描述了在游戏开发中如何实现层次的加载和卸载。随着玩家的移动,新的层次被加载,而已经加载的层次会根据其在层次结构中的位置进行升级或降级。当单元格移出视野时,它们会被卸载到更低的层次,直到完全卸载。这种方法允许无限世界的程序生成,同时保持了对周围环境的有限了解。

Mindmap

Keywords

💡程序生成

程序生成(Procedural Generation)是一种在游戏设计中常用的技术,它通过算法自动创建内容,如地形、关卡等。在视频中,程序生成用于创建一个无限的世界,这个世界通过滚动窗口的方式展现,玩家可以在其中无限探索。

💡无限世界

无限世界(Infinite World)指的是一个理论上没有边界的游戏环境,玩家可以在其中无限地探索。视频中提到,通过程序生成技术,可以创建出这样的世界,即使在实际的硬件限制下,也可以通过技术手段模拟出无限的感觉。

💡2D数组

2D数组是一种数据结构,它由行和列组成,可以用来存储和操作二维数据。在视频中,2D数组被用来表示无限世界的地图,每个单元格内可能包含随机生成的点,这些点代表世界中的特定元素。

💡滚动窗口

滚动窗口(Rolling Window)是一种技术,它允许在有限的视窗内查看无限或非常大的数据集。在视频中,滚动窗口用于实现无限世界的探索,玩家的视野随着移动而滚动,从而给人一种世界无限延伸的错觉。

💡层级生成

层级生成(Layered Generation)是一种程序生成的方法,它通过多个层次来构建复杂的结构。在视频中,每个层级包含更多的信息,但需要依赖于相邻层级的数据。这种方法使得即使在无限世界中,也能有效地管理和生成内容。

💡随机点

随机点(Random Points)是程序生成中常用的元素,它们在指定的区域内随机分布。视频中提到,在无限世界的最外层单元格中,会生成三个随机点,这些点是构建世界的基础。

💡调整点

调整点(Adjusting Points)是指在程序生成过程中,对随机生成的点进行位置调整,以确保它们在空间中分布得更加均匀。视频中提到,为了实现这一点,需要考虑相邻单元格中的点,以避免点过于集中或分散。

💡连接点

连接点(Connecting Points)是指在程序生成的世界中,将不同的元素或区域连接起来。视频中提到,通过三角化和选择最短路径的方法,可以确定哪些点之间应该建立连接,从而构建出世界间的通道。

💡细节层次

细节层次(Level of Detail)是指在程序生成中,不同层级的数据包含的信息量。在视频中,随着层级的增加,每个单元格包含的信息越多,从而能够更详细地描述世界。

💡加载与卸载

加载与卸载(Loading and Unloading)是指在程序生成的世界中,根据玩家的位置动态地加载和卸载数据。视频中提到,随着玩家的移动,新层级的单元格被加载,而已经离开视野的单元格则被卸载,这样可以保持游戏的流畅性。

Highlights

介绍了如何通过滚动窗口实现无限世界的程序生成。

讨论了无限世界程序生成时不需要边界的考虑因素。

提出了一种抽象的环形世界概念,通过路径连接各个部分。

解释了在无限世界中,由于缺乏完整画面,无法获取所有邻居信息的问题。

描述了一种分层的程序生成方法,每层可以包含更多信息。

说明了最外层单元格在零层时创建随机点列表的方法。

讨论了如何调整随机点以避免过于集中的问题。

解释了第一层如何调整点,以及为何需要加载邻近单元格的信息。

介绍了第二层如何计算世界的半径,并确保使用调整后的点。

讨论了如何通过三角化和选择子集来确定点之间的连接。

说明了如何确保每个邻近单元格至少有一个世界与当前单元格相连。

描述了随着层数增加,对周围环境信息的了解也会增加。

提到了在更高层次上计算星球内部信息的方法。

介绍了一种不同层次完全解耦的方法,具有不同的网格分辨率。

讨论了加载新层时的动态窗口和卸载过程。

强调了这种方法允许无限世界的程序生成,尽管没有边界。

Transcripts

play00:01

I'm going to talk a bit more about how

play00:04

to do procedural generation for an

play00:07

infinite world back in May I wrote a

play00:11

blog post called rolling grids for

play00:13

infinite worlds that talk about how you

play00:15

can have an infinite world with

play00:20

procedural generation where you

play00:22

basically have a 2d array that you can

play00:26

treat as infinite by having a scrolling

play00:30

a rolling window into your world that

play00:33

Scrolls together with whether characters

play00:36

and I'm going to go a bit into some of

play00:40

the considerations here that you'll have

play00:44

to consider when doing procedural

play00:47

generation that basically doesn't have

play00:49

any boundaries so what we're looking at

play00:55

here is some kind of circle worlds

play01:01

they're bit abstract at this stage put

play01:03

them in to represent a kind of worlds

play01:06

that are connected by some pathways that

play01:11

you can use to travel between them and

play01:15

this world is extends infinitely in all

play01:20

directions so I could I can scroll this

play01:27

world here and it basically goes on

play01:30

forever in any direction and there's

play01:40

many things like this we can do all

play01:42

kinds of interesting things with

play01:44

procedural generation which are not that

play01:49

triggered by themselves for something

play01:52

like this if it had bounds you could

play01:55

throw in a bunch of random points and

play01:57

give them some radiuses and make sure

play02:03

they don't overlap adjust as necessary

play02:04

and then find some good way to connect

play02:07

them and once you've handled all that

play02:10

for all the points then you're done but

play02:13

the problem is when

play02:14

it's infinite then you never have the

play02:18

whole picture so you never have all the

play02:22

neighbors available because they'll

play02:24

always be some extra neighbors outside

play02:26

of the currently loaded bounds so let's

play02:29

try to look at how this is implemented

play02:33

if we zoom out sufficiently we can see

play02:37

that things are loaded in different

play02:42

levels of detail in the folder but the

play02:48

fill out we get from the position of the

play02:51

camera a player or whatever you use to

play02:53

to define as the center of the currently

play02:55

loaded part of the world the felt the

play02:58

fill out you go from there the less

play03:00

information we have so let's try to turn

play03:05

on some the grid that we're using so

play03:13

this is the grid that this procedural

play03:18

generation operation and it actually

play03:23

eats the way it works is that the

play03:28

generation works in civil layers so each

play03:33

layer can have more information but it

play03:37

requires that all the neighboring then

play03:40

not the 8 neighbors are loaded up to the

play03:46

previous layer so let's take it from the

play03:50

beginning in this case the outermost

play03:53

cells in the grid the left right and top

play03:57

and bottom they are loaded to the level

play04:02

of zero and that means in this case that

play04:06

in each of these cells I create a list

play04:11

of three random points within the cell

play04:14

so that's all we have unless 0 in this

play04:18

these outermost cells now in the next

play04:21

layer the problem is for these random

play04:25

points since they're random they can end

play04:27

up being

play04:28

very close to each other and for this

play04:30

thing we want things to be not very

play04:33

evenly but at least a little evenly

play04:36

distributed and it doesn't work well if

play04:39

some points a way too close to each

play04:41

other in that case so we want to adjust

play04:43

the points but we cannot do that as long

play04:47

as we don't know the neighboring points

play04:49

in the other cells if we only looked and

play04:53

adjusted the points compared to other

play04:55

points in the same cell then they might

play04:57

just end up being very close to points

play04:59

in the neighboring cells so before we

play05:05

can make the adjustments we have to know

play05:08

about the points in all the neighboring

play05:10

cells as well so we have a layer one

play05:15

that's that takes care justing the

play05:17

points but layer one requires four cell

play05:21

to be at layer one it first requires

play05:23

that all the neighboring cells are

play05:24

loaded two layer zero and once that that

play05:30

is the case we can adjust these points

play05:34

and see the old positions amount in gray

play05:38

and the new adjusted ones a mark in

play05:40

white the next layer layer one we can

play05:46

begin to calculate Regis's for the

play05:49

worlds the radiuses requires that we

play05:54

know the positions of these worlds as

play05:58

well as the neighboring walls but it has

play06:00

to be the final the adjusted positions

play06:02

not the initial ones so we need to have

play06:05

all the neighboring cells loaded to

play06:07

layer one before we can calculate the

play06:10

radiuses in layer two and then we can do

play06:14

that lesson randomness as well they get

play06:17

different sizes and and depending on the

play06:22

radiuses of of each other they can think

play06:28

and have different sizes and now the

play06:34

next layer requires that we already know

play06:36

the radiuses of all the worlds

play06:40

and then we can begin to calculate

play06:42

connections between them what I do in

play06:48

this case is at first to Dylan a

play06:55

triangulation of the points and that's

play06:59

the points in this cell and all the

play07:01

neighboring cells in order to figure out

play07:04

which of the points will look natural to

play07:09

connect but only use this subset of

play07:11

those I don't want all the connections

play07:18

to form triangles I want things to be a

play07:20

bit less connected so you might

play07:23

sometimes have to take a bit of a detour

play07:26

in order to get from from one world to

play07:28

another so use a subset I basically make

play07:32

sure all the worlds within the cell are

play07:37

connected to each other but not not that

play07:41

every planet is connected to every other

play07:44

one but just that you can get from any

play07:46

one to the others through maybe a third

play07:49

one and then I also make sure that

play07:53

there's a for each of the neighboring

play07:57

cells there's at least one planet in or

play08:00

one world in that cell that's connected

play08:03

to the current cell some planet in the

play08:07

current cell so that there's some way to

play08:09

get to the neighboring cells but only

play08:11

one pass that goes between the cells and

play08:17

I do that by basically choosing the

play08:23

shortest path that can connect the

play08:27

planet in in the neighboring cell and

play08:28

this cell and that's it then we best the

play08:36

more layers we have loaded the the more

play08:38

information we have about about the

play08:40

surroundings and eventually I calculate

play08:44

some information within the planets

play08:46

themselves but that's actually a bit

play08:48

unrelated to to what we're discussing

play08:50

here and that's something that would be

play08:52

used for

play08:53

when going even more in details and zoom

play08:55

in on one planet and and it's used to

play09:00

whatever is actually going on with that

play09:02

planet itself but that's not needed in

play09:05

in this context or skip going into

play09:07

childhood with that but but that's it

play09:12

and an approach with layers that depends

play09:18

on lower layers basically means that you

play09:22

can have infinite a procedural

play09:25

generation of quite high complexity even

play09:29

though you don't have any bounds it just

play09:33

means that the more dependencies you

play09:36

have between some local data and some

play09:41

data that's located around it the more

play09:45

of this kinds of dependencies you have

play09:47

the more layers you have to have and the

play09:49

further out the the loaded area will

play09:53

have to extend but but the furthest

play09:57

paths can be loaded only at a very low

play09:59

abstraction level so that's that's the

play10:06

basics here so these layers are kind of

play10:09

symbol in that they all use the same

play10:12

grid size and know about each other it's

play10:15

a fairly simple pattern here where each

play10:17

cell just depends on the neighboring

play10:20

eight other cells but for the game I'm

play10:27

developing that's one of the approaches

play10:30

I'm using I'm also using a different

play10:32

approach with completely decoupled

play10:34

layers that can have different grid

play10:36

resolutions and where each layer is

play10:39

basically unaware of the internal

play10:42

implementation details of the other

play10:44

layers and they just ask each other for

play10:47

information up from some given point in

play10:50

on our layer and it doesn't care about

play10:52

what cells are loaded it can basically

play10:54

treat other layers as just infinite but

play10:59

that's something for another talk before

play11:03

we stop here let's just look at the

play11:05

loading while

play11:06

assumed out so basically as you move new

play11:13

layers are new cells layer a loaded in

play11:19

both everything from layer 0 at the

play11:24

outmost to and the end the things that

play11:27

already loaded at LaCie or get get

play11:29

upgraded to layer 1 and so on so the

play11:34

sender cells that are at the topmost

play11:37

layer you get a shifting window of those

play11:41

and as they move out of the window again

play11:44

they get unloaded to lower layers

play11:46

progressively until they're completely

play11:49

unloaded and this can just go on and on

play12:12

that's it

play12:14

you

Rate This

5.0 / 5 (0 votes)

Related Tags
程序生成无限世界游戏开发层次结构随机点连接算法动态加载游戏设计技术解析空间布局虚拟世界
Do you need a summary in English?