如何为游戏创建大洲地图

图片

第1部分。SVG和坐标系


直到最近,《 Dragons Abound》游戏中的地图大小都是固定的,并且有些不确定。 我认为它们是“区域”的-不是整个世界的地图,而是重要的部分,例如美国的西海岸或欧洲的一部分。 我对这种比例非常满意,但是我想对游戏进行一些试验,看看是否可以生成整个世界的地图(或至少生成一个更大的地图)。 但是在我开始之前,让我们先讨论一下幻想世界地图。

世界是一个广阔的空间。 大多数幻想“世界”牌甚至与真实尺寸都不尽相同。 以中土世界为例,其中发生了《指环王》行动:


尽管似乎已捕获了一个巨大的世界,但实际上,中土世界是在欧洲的基础上创建的。


也就是说,托尔金世界的“世界”真实地图将比中地球(!)地图大50倍。 实际上,我所见过的大多数幻想世界地图都反映了一个与大陆大小有关的领土:


这似乎是幻想卡片样式中可视化的最大区域。

也就是说,生成真实的“世界地图”的任务可能过于雄心勃勃。 最好以创建大陆(或大陆的一部分)地图为目标。 (不过,将卡片视为“世界”的大小仍然更加方便。)那么卡片应该是多少? 如果当前的《 Dragons Abound》卡具有“亚大陆”大小,那么我们可以假设您需要生成8-10倍大的卡。

在继续进行生成大型地图的任务之前,我需要更好地了解游戏中使用的各种坐标系。 我从Martin O'Leary的源代码中借了很多坐标系,即使您使用它们两年,它们的交互也会使您感到困惑。 通常,我无需进行试验就可以做到,但是很明显,我必须这样做才能生成大型地图。

首先,当前在一个单位正方形内生成区域地图的“世界”。 每个区域地图的坐标从(-0.5,-0.5)到(0.5,0.5),并且原点(0,0)位于该区域的中间。


奇怪的是,与我们在学校几何学中所研究的相比,Y轴是倒置的。 -0.5在地图的顶部,而0.5在底部。 在计算机图形学中,Y轴通常会翻转。 我听说这是由第一台监视器从上到下进行扫描的方式来解释的,也就是说,第一条扫描线在顶部,第二条扫描线在其下方,依此类推,即扫描线的索引Y从顶部的零变为某个正数。下面。 尽管如此,在SVG(可缩放矢量图形)格式中使用了相同的坐标系,这也是Dragons Abound众多的原因。

该坐标系与地图的显示方式无关。 这只是创建世界的无量纲系统-城市位于(0.12875,-0.223),边界从(0.337,0.010)变为(0.333,0.017),依此类推。 尽管我当前的区域地图被限制在0.5到-0.5的范围内,但这并不是坐标系的限制。 我可以创造一个超越这些界限的世界。

下一个坐标系是SVG称为viewbox。 它设置将用于绘制图形的真实坐标。 例如,开始时“无尽的巨龙”将视框设置为坐标(-500,-500),并且其宽度为1000,高度为1000:


(图片中有错别字,Y轴的顶部应该是-500,对不起。)

您可能会注意到,在这种情况下,第一坐标系和第二坐标系之间的转换仅在于将所有乘以1000。 为了绘制对象,游戏会找到该对象的坐标,将其乘以1000,然后在这些坐标中绘制SVG。

也就是说,我可以使用视图框的坐标来绘制从(0,0)到(250,250)的线。 但是实际上,我不想在计算机屏幕上从(0,0)到(250,250)画一条线。 这意味着如果我想在屏幕上的另一点显示地图,那么我将不得不更改所有地图对象的坐标并重新绘制它们。 那将是一项巨大的工作。

为了控制屏幕上显示图形的坐标,SVG具有称为视口的第三坐标系。 视口是页面的一部分,应在其中绘制图形(在网页上,这是<svg>元素)。 它具有宽度,高度和位置。 该位置是视口左上角的坐标。 也就是说,如果我要在视口中显示坐标为(30,100),高度和宽度为800的地图,那么视口坐标系将如下所示:


在SVG中,坐标系的视区和视口相互连接,而SVG本身负责处理它们之间的过渡。 我们只需在视框坐标系中进行绘制,所有渲染的内容都会显示在相应的视口位置。 (创建具有不同长宽比的视图框和视口时会出现一些问题。然后,对象会被剪切或拉伸,具体取决于preserveAspectRatio属性的值。我建议完全不要这样做。)

总结:位于世界坐标(0.10,0.33)的城市以坐标(100,330)绘制,并在(110,764)中显示在屏幕上。

现在您可以理解为什么这会造成混淆!

如果更改每个坐标系会怎样? 假设在第一个坐标系中,我将在每个轴上生成一个范围为-0.25到0.25的世界。 然后,生成的世界将比平常的世界小四倍,并且仅填充视口的中间部分:


(您还会注意到通常被隐藏的边缘伪像。)类似,如果我将第一个坐标系(SK)的大小加倍,我们将看不到大部分地图,因为它将在视口边缘之外。

如果将视图框的大小加倍,会发生什么? 好吧,如果我还将第一个SC和视图框之间的比例加倍(从1000到2000),那么什么都不会改变。 如果该比例保持等于1000,则地图将再次减半。


但是,这次卡的初始区域为1x1。 我们可以再次注意到通常隐藏的边缘处的伪影(例如,森林的凸出部分)。 您还可以看到海洋模式是错误的-我必须对有关视图框大小的一些假设进行硬连线。 此外,指南针似乎不在地图的一角,而是在视图框的一角。

反之亦然,如果将视图框的大小减小一半,则会创建地图缩放效果:


在这里,我们仅看到地图的中间部分。 这不是一种非常方便的缩放方法,因为只有一半的地图显示了一些问题-例如,城市标记“ South Owenson”超出了屏幕范围。 另外,它使字体和其他我不需要的东西的大小加倍。

视图框更有用的方面是更改原点。 到目前为止,视图框已位于地图的中心,但这不是必需的。 例如,我可以通过将视图框居中在地图左侧的点上来向右移动地图:


我们可以再次注意到对边界的影响和其他一些问题,但实际上,地图已移至右侧。 这样做的用处可能并不明显,但可以想象一下,我将生成一张宽度为普通地图宽度两倍的地图。 默认情况下,它看起来像这样:


它看起来像其他任何地图,但实际上,它只是较大地图的中心部分。 也就是说,现在我可以更改视图框以将地图的其他部分传输到概览窗口:


在这里,我将视点移至左侧,因此我们看到了原始视图以东的世界的一部分。 地图上的某些名称已更改,因为Dragons Abound会根据其是否可见来执行某些功能(例如,为对象命名)。 我将需要更改此设置,以便在移动视图框时地图保持不变。 但是,稍后,我将能够在大型地图上移动视图框并生成任何所需区域的区域地图。 也就是说,我可以生成和显示大洲般的大型地图,但是我也可以生成大型地图中各个区域的区域地图。

总结一下:游戏使用三个坐标系。 第一个是针对世界对象的抽象SC。 第二个是视图框,它定义了世界的可见区域。 第三个是视口,它控制将在屏幕上绘制地图的位置。 为了绘制更大的世界,我需要扩展第一个SC。 要在屏幕上显示更多内容,您需要扩展视图框。 我还可以移动视图框以显示大世界的不同部分。

第2部分。


在上一部分中,我探索了坐标系,并学习了如何移动视口SVG以仅显示大世界的各个部分。 但是,这种方法存在一些问题,因为早先我认为对我们而言不可见的所有内容都无关紧要。 在这一部分中,我将消除这些假设,以便可以生成和查看不可变大地图的不同部分。

我在上一部分中描述的名称放置问题在以下两种类型的单张卡上显然很明显:


这是同一个世界,只是视图向左移动:


您可能会注意到地理位置相同,但是许多名称已更改。 由于在两种类型中可见不同的对象,并且创建名称的过程由随机数控制,因此获得了不同的名称。

查看代码,我发现几乎所有对象都根据其可见性来命名。 但是只有一个例外,这使得很难为所有后续对象命名。 在我们的案例中,例外情况是《 龙腾》只产生可见的海岸线。 造成这种情况的原因非常令人困惑。 实际上,整个世界边缘都有一条“海岸线”,但是创建这条直线会破坏程序逻辑的一部分,因为它包含整个世界。 为了避免这种情况,我只生成了可见的海岸线。 现在,地图可以扩展到远远超出视口的位置,此解决方案看起来并不理想。 相反,当我靠近地图的真实边缘时,我需要停止生成海岸线。 (我仍然离开屏幕以隐藏问题的边缘。)

消除此问题后,两张卡上的名称保持不变:


和:


未来的另一注:如果我使用交互功能来更改地图对象的名称,则此更改将不会以其当前形式重新创建,甚至可能无法再现。 值得考虑。

如果仔细查看前一张地图,您会发现地图下部中部附近的海洋区域带有悬挂标签“ Meb Island”。 之所以发生这种情况,是因为“ 巨龙无疆”实际上认为海洋是一个岛屿。 我不会详述技术细节,但是当它们超出地图时,很难将岛屿与湖泊区分开。 该算法使我对不可见海岸线的生成所做的更改感到困惑,并且为了避免此类问题,需要对此进行修复。

现在,让我们将地图的大小增加四倍,并在地图窗口中仅显示地图的四分之一:


总的来说,一切看起来都不错(在地图上有一个有趣的河流系统,一个大湖,但是您可以看到这些城市非常稀有。发生这种情况的原因是Dragons Abound产生了10到20个城市。这个间隔非常适合正常规模的世界,但是很糟糕,当大小是原来的四倍时,则需要根据世界的相对大小来更改间隔,可能需要在多个地方进行。

解决问题后,这是同一张卡:


现在,在地图上,城市和城镇的数量增加了,这是另一个问题。 您可以在地图边缘看到很多额外的名称,例如,左下角的Nanmrummombrook,Marwynnley和Noyewood。 发生这种情况是因为放置代码方法试图将它们放置在可见的位置。 以前,此过程无需担心屏幕外的标签,因为在区域大小的地图中通常可以看到整个世界。 但是现在屏幕上可能有城市和其他物体。 因此,我需要向标签放置过程添加逻辑,该逻辑不要尝试为不可见的地图对象创建标签。


现在,图片更加合乎逻辑了。 在右侧,Cumden在地图上几乎不可见,但标签仍位于可见位置。

有一个方面在大型地图上没有立即引起注意:世界上的地点数量没有改变。 尽管地图(在某种意义上)已经扩大了4倍,但其总面积仍然受到相同位置数的限制。 地图生成的初始阶段是使用具有恒定位置数的Voronoi图覆盖整个世界。 也就是说,当地图变大时,Voronoi的像元也会变大。

根据地图的大小来缩放地点数量是合乎逻辑的,但不幸的是,《 龙腾飞》的执行速度对地点数量的依赖性远不如线性,即生成具有大量地点的地图可能要花费很多时间。 这是具有四倍分辨率(位置数)的卡的示例:


添加的位置会更改生成过程,因此地形与上面显示的地图不同,但是可以在其上看到海岸上添加的详细信息。

幸运的是,在分析生成大型卡的性能时,我注意到大部分处理器时间都被明显的问题所占用。 调试后,我消除了最令人不安的问题,这使我可以创建更多地图。 这是整个4x卡:


放大25%。 看起来大概是Chrome可以显示的最大地图大小。 世界生成过程可以处理较大的地图,但是试图显示它们,浏览器崩溃。 从这个意义上讲,Firefox似乎更具功能。 它可以显示比原始大小大9倍的卡片。 这是此类地图的一部分-我将其保留为完整尺寸,因此您可以在单独的窗口中打开它,以更好地了解尺寸和细节。


Firefox能够生成这种大小的地图,但是我只能在浏览器窗口的最大大小下截取屏幕截图。 我具有将地图另存为PNG文件的功能,但是它只能保存地图的显示部分。 我认为您可以滚动地图,捕获单个屏幕并将它们连接在一起,但这会很费时间。

最好的解决方案是保存SVG本身,以便可以在Inkscape之类的程序中打开它。

我曾经能够将SVG地图剪切并粘贴到Inkscape中,但是用于世界地图的SVG太大,以至于当我尝试剪切浏览器时会崩溃! 幸运的是,我找到了FileSaver.js ,您可以使用它将SVG直接保存到文件中,然后在Inkscape中打开它,从而创建一个非常大的图像。

至少在理论上。 当我尝试在Inkscape中打开这些地图时,遇到了两个问题。

第一个问题是Inkscape的假设与Chrome和Firefox的假设在如何打开SVG方面有所不同。 特别是,如果未在路径中指定填充色,则浏览器将假定没有填充;否则,将显示为空白。 Inkscape假定轮廓填充为黑色。 因此,当我在Inkscape中打开保存的SVG时,它几乎是全黑的,因为地图的最上层不包含填充色。 可以通过在必要的位置指定“填充:无”来解决此问题,以使轮廓在浏览器和Inkscape中均等显示。

第二个问题是Inkscape在处理遮罩时出错。 Inkscape似乎仅用一个元素创建蒙版,而处理多个元素的蒙版效果很差。 Dragons Abound创建了具有多个元素的许多蒙版。 您可以通过将每个游戏蒙版的所有元素分组到一个(可选)分组元素中来解决此问题。

第三个问题与图像和其他可下载资源有关。 在原始SVG中,对它们的引用以相对形式表示,例如“ images / background0.png”。 我的源代码排列方式使我使用单独的Web服务器可以在指定位置找到这些资源。 当我使用相同的SVG并在Inkscape中打开它时,这些相对路径被视为URL“文件:”,并且Inkscape搜索相对于SVG保存文件夹的资源。 通过将SVG保存到在正确位置已经有资源的文件夹中,可以轻松解决此问题。 它可以是Web服务器使用的相同根文件夹,也可以是沿着相同(相对)路径存在资源副本的其他位置。

第四个问题是字体。 Dragons Abound使用网络字体和本地存储的字体。 两者均为WOFF2格式。 在浏览器中,使用CSS字体系列样式将它们应用于文本,并且在生成地图之前,所有可能的字体都将上载到网页上以供使用。 当在游戏中打开相同文件时,它将在系统字体目录中搜索字体,并且看起来无法以任何方式指定其他字体目录。 一个简单的解决方案(至少在我正在开发的机器上)是将游戏使用的字体安装到系统字体目录中。 但是,它并不像看起来那样简单,因为字体名称必须匹配,并且在Windows上没有简单的方法来更改字体名称。 但是,当然,这种方案不适用于未安装所有必需字体的计算机。 一种更可移植的解决方案是在地图中嵌入SVG字体 。 这将在我的TODO列表中。

最后,我来到了这个地图生成界面:


范围输入字段指定世界的总大小,其中1x1是源地图的大小。 vbx(视图框)的大小确定了地图上显示的世界片段的大小; 在屏幕截图中,它的值也为1x1,即地图将显示整个世界。 vbx中心字段指定地图中心在世界上的位置; 0,0是世界的中心。 最后,SVG参数指定每1个视图框大小单位的屏幕像素数; 当值为775时,屏幕上将显示775x775像素的1x1地图。 当我创建一个很大的地图时,这很方便。 通过将参数设置为较低的值(例如150像素),我可以在屏幕上整体容纳一张大地图。

通过更改这六个参数,我可以控制世界的大小以及在地图上显示的世界的一部分。 “生成”按钮的工作原理与您可能猜测的完全相同。 “显示”按钮仅显示世界的一部分,也就是说,我可以生成一个世界,然后显示其各个部分,无需重新生成世界即可更改视图框参数。 (程序员最好将其实现为缩放和滚动。)“保存PNG”按钮将可见地图另存为PNG文件。 保存SVG按钮可保存整个地图的SVG文件。 “测试”按钮用于运行测试代码,该代码在开发各种功能时会发生变化。

现在,我可以生成并反映大世界的所有部分,接下来我可以继续将土地的形状调整为更大的地图。

第3部分。寿司表格


在上一部分进行了各种更改之后,现在我可以生成比以前大得多的世界(最多多8倍)并将其保存为大图形图像:


(在单独的窗口中打开图像,以在Flickr中以全分辨率4800x2400查看地图。)

我使用创建区域地图的相同程序生成这些地图。 上面显示的地图具有相当规则的大陆形状和一些有趣的外岛。 但是,这主要取决于运气。 这是另一张地图:


这张卡只是来自岛屿和瑞士寿司奶酪的混乱。

这是另一个示例,介于前两张卡之间。 这并不完全现实,但对于幻想环境可能会很有趣:


这是一块巨大的大陆土地,但有很多奇怪的土地形式,总的来说,世界看起来并不十分“真实”。 (尽管对于某人来说,这样的世界似乎非常适合幻想。)那么“世界”的地图应该具有哪些形式?

我见过的大多数幻想世界地图都代表一个大岛大陆(周围有小岛),例如这张安德伦地图:


或该大陆的半岛,如本安戈伦地图所示


有时,地图完全由陆地或几个岛屿组成,但更可能是该规则的例外。

首先,让我们找出“岛屿”大陆的产生。 在我的游戏中发现,考虑到地图的大小,已经有一个函数可以在地图上生成一个大的中心岛,因此它应该适合于生成大陆的主要形状。 噪音和其他孤岛将照顾其余的人。


我没想到在这张地图上会有大片中央海,但这是一个令人惊喜的惊喜。 这是另一个示例:


中心岛功能的问题在于,它以适合我所显示的正方形地图的圆开始,但不适用于矩形地图。 (下面是一些失真很小的示例,因此基本形式更加清晰可见。)


可以通过遮罩寿司而不是通过地图大小采取带椭圆(变形)的圆(而不是圆形)来更正此问题:


这些中部岛屿已按比例缩放以填满地图,但在很多情况下,对于大陆式地图,我们需要在整个大陆周围留下“边界”。 两个参数控制着沿X和Y轴填充孤岛的地图的大小。


这是相同的边界管理系统,但逻辑上存在更多失真:


可以看出,地图的东部和西部仍然是海洋。 (您可以在一个单独的窗口中打开地图以进行更仔细的研究。)这意味着该地图显示了整个世界(可以连接其左右边缘)或可以连接到另一幅地图的世界的一部分,该另一幅地图也具有来自相应边缘的海洋。

细心研究过以前的地图的读者可能已经注意到,海洋和陆地的模式停止在地图的中间。以前,我的地图只有1x1的大小,因此海洋和陆地图案的大小都适应了这些地图。在较大的地图上,我需要在地图上手动平铺图案,因此我添加了此功能。 (SVG中有一种方法可以执行模式平铺,但是在Chrome中它包含一个错误,因此我无法使用它。)这是一个好功能,因为现在我可以使用较小的陆地和海洋模式,这些模式会自动平铺。我不知道为什么我以前没有意识到!

因此,现在岛屿各大洲都运转良好,我们将继续实施“半岛”各大洲-地图,其中该大陆从其边缘出现在地图上。


在这种情况下,该大陆不能在三个边缘塌陷。但是,此类地图的主要特征是,它们在地图上显示的大陆与地图外的土地之间具有重要的陆地联系。

在地图外部提供这种连接的最简单方法是在生成过程中设置低海平面。因此,我们将增加在地图上显示的土地面积,这会增加大块土地和沿地图边缘出现土地(而不是海洋)的可能性。


当然,这并不能保证这块土地会非常有趣,并且确实会统一起来:


可以使用同一代岛屿大陆来创建单个大陆的相似性,但同时将岛屿移动到地图边缘。您得到的是这样的:


可以看出,该大陆(主要是)是中央岛屿,向右上移。由于这是一个大陆,并且不需要保持严格的岛状形状,因此可以为形状增加更多的变形。


显然,还有许多其他方法可以产生救济,但是至少这两种方法使我有机会在大陆范围内产生最常见的土地形式。

细心的读者可以在许多大陆的地图上看到条纹形式的奇特森林形式。下次,我将开始处理引起这些奇怪现象的风模型和生物群落问题。

第4部分。风模型


如前所述,《巨龙》地图上的大陆面积开始显示出不切实际的天气和生物群落模式。此示例显示森林沿盛行风向排列:


原因不在破损的代码中。相反,天气和生物群落模型太简单了,从规模上看,这变得显而易见。为了解决这些问题,我首先修改了风模型。

我希望我的风模型能更好地反映地球风的动态:哈德利细胞信风等。这样的动态有助于摆脱大陆地图上奇怪的天气模式。但是,当添加它们时,再次显示出对《龙腾飞》风模型的痛苦不满,它缓慢而又过于复杂。 (在此处了解风模型的初始实现。)经过几天的思考,我认为大多数问题都归结为以下事实:游戏地图以Voronoi图的形式呈现。 (或者更确切地说,是Voronoi图Delaunay三角剖分法。)生成地形时,它具有许多优势-与噪声结合使用,可以创建看起来自然的土地块。这就是为什么它经常被用来产生救济的原因。但是,由于各个三角形具有不同的大小和方向,因此任何计算(包括使用相邻相邻单元的风模型)都变得非常复杂。通过均匀分布的相同区域的网格对风进行建模将更加容易。此外,风模型很可能不必像陆地一样详细。

但我不想完全放弃Voronoi图,这是基础龙的大量存在。 (至少,这将需要重写几乎整个程序!)相反,我想尝试将地图绑定到统一网格,在其中运行风模型,然后进行反向绑定。如果来回复制的损失不太大,则可以使风模型更快,更轻松。

我应该使用哪个网格?理想情况下,网格应由与其相邻的等距区域组成。并且此描述就像六边形的网格。


实际上,六边形网格是将平面分成相等区域最佳方法

下一步是确定如何在程序中表示六边形网格。我在网络上搜索了一下以寻求帮助,每个链接都使我回到了有关阿米特·帕特尔(Amit Patel 六边形网格页面哈伯瑞翻译)。可能有必要从此开始;如果要查找有关实现游戏机制的信息,则最好先浏览Red Blob Games网站。阿米特(Amit)比我解释得更好,所以如果不清楚,请阅读他的页面。

首先要选择的是存储六边形网格的方法。最简单的方法是将其存储为二维数组,然后需要将网格单元绑定到数组的功能。这里有很多选项(请阅读Amit的页面),但是我将使用他所谓的奇数-r:


每个像元中的数字是二维数组中像元的索引。(图像是从阿米特(Amit)的页面上窃取。在他的页面上,它们是交互式的,因此我建议您进行实验。)

选择之后,现在我必须学习如何将索引附加到六边形网格上。例如,如果我正在寻找一个六边形的单元格(3,3),那么它的邻居将是什么?如果每个像元的宽度为5像素,那么像元(3,3)中心的坐标是什么?依此类推。处理这个可能很困难,所以我很高兴阿米特(Amit)为我做了。

假设我们可以从Amit窃取我们所需的一切,那么我首先需要弄清楚如何在地图上排列六边形。在这个阶段,我还不需要数组,我可以假装它是一个数组,看看六边形在哪里。如果我知道六边形的位置,则只需将地图的宽度除以它们之间的水平距离即可得到列数,然后垂直进行同样的操作即可得到行数,然后在每个位置绘制一个六边形:


这些六边形比我将在风模型中使用的六边形大得多,但它们告诉我所有东西都放置正确。

在这里,我打开地图的边缘,仅画出中心六边形和边界,以检查是否真的关闭了整个地图:


顶部和底部在地图之外,但是如果我不错过地图的一部分,那么在国外设有多个牢房并不重要。

下一步是为六边形网格创建一个数组,并将所有Delaunay三角形对齐到相应的六边形。由于Javascript不支持数组的负索引,因此我需要将单元格(0,0)从地图的中心移到右上角。完成此操作后,我遍历所有Delaunay三角形,并将它们添加到六边形网格的相应单元格中。我可以通过着色包含陆地的六边形来验证这一点:


为了确定某个单元格是否为陆地,我对落入该单元格的所有位置的平均高度进行了平均。您可能会注意到,即使在有土地的情况下,某些沿海六边形的平均值也低于零。您还可以使用六角形中所有位置的最大高度:


在这种情况下,搜索是在相反的方向上进行的-如果六角形中有任何陆地,则将其标记为陆地。哪个更好取决于您的需求。

无论如何,我可以通过减小六边形的尺寸来提高精度:


现在,海岸已经变得越来越好,但是出现了一个新问题-许多内部六边形不被认为是陆地。发生这种情况的原因是,当六边形变得足够小时,其中一些内部根本没有Delaunay三角形。因此,它们没有“高度”。 (这也说明了Delaunay三角形的不规则性。)

您可以使用此解决方案-将缺失的三角形的高度作为其相邻像素的平均值或最大相邻像素的高度。


通常,当六边形和位置之间的绑定不是一对一时,需要进行校正以填充丢失的信息。

现在,我已经在地图上放置了六边形网格,我们可以开始实现风模型了。风模型的主要思想是模拟某些风(商风),并将其分布在整个地图上,直到达到静止状态。在六边形水平(或位置水平,如果我在Delaunay三角形上进行),这包括两个阶段:(1)我们汇总进入当前六边形的所有风,(2)确定总风如何离开六边形。

第一阶段非常简单。每个六边形都有六个邻居,并且每个这些六边形都起作用。如果我们将进入六边形的每条风视为矢量,则单元格中的总风将是这些矢量的总和。强度相等的两个完全相反的风彼此抵消。

第二阶段(确定总风如何离开六边形)需要考虑。最简单的情况是风直接吹过六角形:


在这种情况下,我们希望风不会改变而移动到下一个单元格。(在这里,红色矢量是原始风,蓝色是传播风的矢量。)

但是,如果风不直接吹向相邻的单元格怎么办?


在这种情况下,顺理成章的是,风的一部分吹到正上方的六边形中,另一部分吹到与之相反的位置的单元中,并且比例应取决于箭头指向的方向。在我们的情况下,风的主要部分将移至上部单元,而较少移至相邻单元。

还必须确定散风的方向。一种选择是保持原始风向:


看来这是最现实的选择,但是还有另一种选择-根据相交的六边形的边缘来改变风的方向:


这种方法的准确性较差,但有一个优点:传入的风将始终处于六个方向之一,这可以简化计算。

在考虑地形时会出现另一个困难。一座山在风中升起怎么办?


在这种情况下,一部分风会越过山脉(可能会产生降水),但是一部分风会转向两侧。


因此,风从六边形流出的方式取决于其方向以及相邻单元格中的地形。

现在让我们谈谈如何表示向量。有两个主要选项。首先,向量可以表示为X和Y值,例如:


如果我们绘制一个从(0,0)开始的向量,则(X,Y)是端点的坐标。这样的记录使得对向量求和非常容易。我们只求所有值(X,Y)的总和即可得到一个新的向量:


另一个选择是使用向量的角度和长度:


以这种形式,容易执行诸如旋转矢量或改变其长度的操作。
对于风模型中所需的大多数操作,第一种选择更好,但是在某些情况下,第二种选择更好,因此在必要时在它们之间进行切换将很方便。为了不浪费时间,我为Javascript寻找了一个向量库,并且Victor.js完全出现,因此我利用了它。

首先,将风矢量添加到每个六边形,看看是否可以可视化它:


到目前为止看起来不错。

下一步是检查我是否可以正确划分风矢量并将其分配到下一个单元格。首先,您需要计算通向其他像元的角度。我在Amit的页面上再次找到了答案


也就是说,0度的向量指向右边的六边形,60度指向右下角的六角形,依此类推。指向这两个方向之间的向量在两个像元之间成比例地分配-也就是说,在30度角的向量将在右边的像元和底部到右边的像元之间平均分配。每个矢量都位于两个相邻单元的面的角之间的某个位置,因此只需看一下风矢量的角度,即可发现它落在两个六边形的中心角之间,然后按比例将其划分为这两个六边形。

例如,如果风矢量的角度为22度:


那么值的38/60传播到右侧的单元格,向量值的22/60传播到右下方的单元格。如果矢量以一对X和Y值形式表示,则可以通过将原始矢量的每个值乘以一个分数(例如乘以22/60),然后将其添加到新六边形的风矢量中来进行分布。

为了测试这一点,我可以将风向不同的方向排列,并将它们放置在地图的顶部和侧面,看看它们是否可以在地图上正确传播。当风碰撞时,应将其合并并以增加的速度选择平均方向:


在这里,我们看到风沿着对角线汇合并合并吹向底角。

下一步是考虑土地对风的影响。当然,真实的风模型非常复杂,但是我主要对陆地地理如何影响地表风感兴趣。在最简单的层面上,这是高地和低地对风向和风速的影响。我尝试了许多不同的方法,但最终我确定了两个简单的规则

  1. 风避开障碍物。
  2. 风在上升时会变慢,然后加速而下降。

当风吹入较高高度的单元格(例如,进入山峰的单元格)时,会发生障碍。发生这种情况时,我查看风向其中吹入的两个单元,并更改风的角度,使其更多地指向两个六边形中的较低者。角度变化的幅度取决于两个像元之间的高度差,因此当风吹到两个相邻的有山的像元中时,风向变化不大,但是如果风吹成一个有山的像元和一个平原的像元,那么它将更多地转向空的像元:


风的强度可以调节。在上面的地图上,他太强大了,这将导致许多不切实际的生物群落的出现。这是更合乎逻辑的含义:


由浮雕引起的风的大部分运动仍然存在,但是强风的大间隙和低谷变小了。

增强真实感的另一个功能是风的散布。例如,上面的地图显示,风向西恰在Breeches市上方。尽管它吹得很远,但它并没有像我们期望的那样消散。当吹来的风遇到另一种空气时,通常会随风一起拉。为了模拟这一点,我可以将每个六边形中吹出的一小部分风重新分配给所有相邻的单元格。这是上面散布很小的地图的样子:


如您所见,马裤上的风已经开始逐渐散去。

此操作确定了大部分风向。第二部分是上升时风的减速和下降时风的减速。我可以通过查看风所来自的单元的相对高度,风所吹入的单元的高度,并在必要时加快/降低速度来实现这一点。

外观如下:


现在我们看到,吹过岛屿中部高山的部分风被切断了。反之亦然-岛的西部出现了几股新风,那里的空气从相对高的土地流向大海。 (这是沿海的微风!虽然不是真的:那里的机制不同。)

现在,我可以在现有的降水算法中替代新的风。这是一个比较(左侧是旧风,右侧是新风):


(单击图像可查看大图。)显然,风模型之间存在差异。在两张地图上,风都是从东方吹来的。地图中心附近的山脉使风向南,导致大量降雨,并在山脉以南形成沼泽和森林。在下部,风吹没有任何干扰,在岛的东半部形成了森林。在原始的风模型中,足够的风经过中央山脉和沼泽,在岛的西部建立了一片森林。在新模型中,大部分风被切断,在远处的山上形成草丛。

旧模型的随机参数(在给定的间隔内)具有可变性,并且这些参数的某种组合可能会给出更像新地图的图片。但实际上,我们不需要重现旧模型的确切行为,只需重现令人信服的外观模型即可。

所有这些的目的是加速和简化风的产生,以便可以将新的风行为添加到大陆地图。我有做吗 我分析了原始的风模型和新的基于六边形的模型。事实证明,新模型比原始模型(!)快15-20倍。这是非常重要的加速,对卡的影响很小。实验清楚表明,该模型对六边形的大小并不特别敏感,因此,如有必要,我可以通过增加单元格的大小来进一步加速算法。

下次,我们将使用新的风模型来实现大陆尺度的风型,然后将它们与降水模型和生物群落联系起来。

Source: https://habr.com/ru/post/zh-CN429620/


All Articles