(Godot 引擎)用 Tilemap 瓷砖填充 2D 多边形

2023-12-15

我在 Godot 引擎中遇到一个无法解决的问题: 怎么可能(在代码中)用图块填充 Polygon2D 区域? 我尝试过获得点位置。使用 2D for 循环遍历线的顶点,但我无法理解这一点。

Polygon2D plus TileMap with tiles on the perimeter of the polygon

这是我期待的结果的模型


我有解决方案,有一点“hacky”部分,但我们会解决这个问题。


迭代多边形的线段

首先,这个想法是迭代构成多边形的线段。为此,让我们使用index在范围从0为多边形中的点数。

我们明白了这一点index和点index - 1是我们部分的结束。请注意,索引-1会给我们最后一点。

for index in range(0, polygon.polygon.size()):
    var segment_start = polygon.polygon[index - 1]
    var segment_end = polygon.polygon[index]

我们需要在瓦片地图坐标中工作。所以让我们转换它们,为此我将有一个自定义函数polygon_to_map,我们稍后会回来讨论。

for index in range(0, polygon.polygon.size()):
    var segment_start = polygon_to_map(polygon.polygon[index - 1])
    var segment_end = polygon_to_map(polygon.polygon[index])

然后我们需要获取组成该段的单元格。再次,另一个自定义函数segment会处理的。我们可以迭代单元格,并将它们设置在图块地图上:

for index in range(0, polygon.polygon.size()):
    var segment_start = polygon_to_map(polygon.polygon[index - 1])
    var segment_end = polygon_to_map(polygon.polygon[index])
    var cells = segment(segment_start, segment_end)
    for cell in cells:
        tile_map.set_cell(celle.x, cell.y, 0)

我正在设置瓷砖0,您设置对您有意义的内容。


坐标转换

瓷砖地图有一个方便的world_to_map我们可以用来转换的函数,这意味着我们可以实现我们的自定义函数polygon_to_map就像这样:

func polygon_to_map(vector:Vector2) -> Vector2:
    return tile_map.world_to_map(vector)

但是,这总是返回整数坐标。结果我们就失去了我们的信息segment功能。相反,我将使用它来实现它cell_size:

func polygon_to_map(vector:Vector2) -> Vector2:
    return vector/tile_map.cell_size

迭代该段的单元格

我们必须定义我们的segment功能…

func segment(start:Vector2, end:Vector2) -> Array:
    # ???

为此,我将使用“快速体素遍历算法”。其他画线算法也可以,我喜欢这个。

我经常觉得“快速体素遍历算法”的解释有点难以解析,所以让我们来推导一下。

我们希望以参数化的方式处理该段。让我们从遍历长度作为参数开始。如果遍历的长度是0,我们在start。如果遍历的长度是线段的长度,则我们在end。使用沿线段遍历的长度,我们可以沿线段拾取任何单元格。

事实上,我们定义构成线段的点具有以下形式:

point = start + direction_of_the_segment * traversed_length

看,我们在这里讨论的一切都是关于段的,让我们简单地调用该变量direction, ok?

point = start + direction * traversed_length

好吧,我说我们将用traversed_length,所以我们将有一个循环,如下所示:

func segment(start:Vector2, end:Vector2) -> Array:
    var length = start.distance_to(end)     # (end - start).length()
    var direction = start.direction_to(end) # (end - start).normalized()
    var traversed_length = 0
    while traversed_length < length:
        var cell = start + direction * traversed_length
        # something that increments traversed_length and other stuff

现在,技巧是知道我们需要遍历多少距离才能到达沿 x 轴的下一个整数值,以及需要遍历多少距离才能到达沿 y 轴的下一个整数值。

现在,假设我们有一个函数next2根据方向给出沿轴的下一个值:

var next = next2(start, direction)

我们将讨论以下细节next2 later.

现在,我们需要计算到达下一个值之前需要遍历的长度。让我们使用之前的方程形式:

point = start + direction * traversed_length

但现在,重点将是开始后的下一个,长度就是我们要寻找的

next = start + direction * length_to_next

=>

next - start = direction * length_to_next

=>

(next - start) / direction = length_to_next

Thus:

var length_to_next = (next - start)/direction

让我们更新我们的代码(记住我们必须更新length_to_next当我们增加traversed_length):

func segment(start:Vector2, end:Vector2) -> Array:
    var length = start.distance_to(end)     # (end - start).length()
    var direction = start.direction_to(end) # (end - start).normalized()
    var next = next2(start, direction)
    var length_to_next = div2(next - start, direction)
    var traversed_length = 0
    while traversed_length < length:
        var cell = start + direction * traversed_length
        if length_to_next.x < length_to_next.y:
            traversed_length += length_to_next.x
            length_to_next.x = # ??
            length_to_next.y -= length_to_next.x
        else:
            traversed_length += length_to_next.y
            length_to_next.x -= length_to_next.y
            length_to_next.y = # ??

那是什么div2?啊,对了,我们得到它是因为零除以零。当线段完全垂直或水平时,就会发生这种情况。在这种情况下我们想要的值为INF,所以它永远不会更小,并且代码不会进入该分支。为此,您可以这样定义:

func div2(numerator:Vector2, denominator:Vector2) -> Vector2:
    return Vector2(div(numerator.x, denominator.x), div(numerator.y, denominator.y))

func div(numerator:float, denominator:float) -> float:
    return numerator / denominator if denominator != 0 else INF

好吧,我们还有另一个问题。例如,如果我们遍历到 x 上的下一个位置,那么到 x 的下一个位置的长度是多少(y 也是如此)?

回到我们的方程,这次我们假设start is 0:

point = start + direction * length_between

=> 

point = direction * length_between

=> 

point/direction = length_between

点应该是什么?好吧,如果我们从 0 开始,并沿着 方向前进到下一个整数……该点是一个向量-1, 0 or 1根据线段的方向,在其组件上。那就是sign的方向。

因此,我们有(我们不需要div2这次):

var direction_sign = direction.sign()
var length_between = direction_sign/direction

更新我们的代码:

func segment(start:Vector2, end:Vector2) -> Array:
    var length = start.distance_to(end)     # (end - start).length()
    var direction = start.direction_to(end) # (end - start).normalized()
    var direction_sign = direction.sign()
    var next = next2(start, direction)
    var length_to_next = div2(next - start, direction)
    var length_between = direction_sign/direction
    var traversed_length = 0
    while traversed_length < length:
        var cell = start + direction * traversed_length
        if length_to_next.x < length_to_next.y:
            traversed_length += length_to_next.x
            length_to_next.x = length_between.x
            length_to_next.y -= length_to_next.x
        else:
            traversed_length += length_to_next.y
            length_to_next.x -= length_to_next.y
            length_to_next.y = length_between.y

啊,对了,我们该回去了。让我们构建一个结果数组,填充它并返回它:

func segment(start:Vector2, end:Vector2) -> Array:
    var length = start.distance_to(end)     # (end - start).length()
    var direction = start.direction_to(end) # (end - start).normalized()
    var direction_sign = direction.sign()
    var next = next2(start, direction)
    var length_to_next = div2(next - start, direction)
    var length_between = direction_sign/direction
    var traversed_length = 0
    var result = []
    result.append(start)
    while traversed_length < length:
        if length_to_next.x < length_to_next.y:
            traversed_length += length_to_next.x
            length_to_next.x = length_between.x
            length_to_next.y -= length_to_next.x
        else:
            traversed_length += length_to_next.y
            length_to_next.x -= length_to_next.y
            length_to_next.y = length_between.y
        result.append(start + direction * traversed_length)
    return result

这有效吗?是的,这有效。这是“快速体素遍历算法”吗?不完全是。让我们将其称为“慢体素遍历算法”以供将来参考(也因为从这里开始它是mostly优化)。


让我们首先保留一个current向量(这将允许我们进行下一个重构):

func segment(start:Vector2, end:Vector2) -> Array:
    var length = start.distance_to(end)     # (end - start).length()
    var direction = start.direction_to(end) # (end - start).normalized()
    var direction_sign = direction.sign()
    var next = next2(start, direction)
    var length_to_next = div2(next - start, direction)
    var length_between = direction_sign/direction
    var traversed_length = 0
    var result = []
    var current = start
    while traversed_length < length:
        if length_to_next.x < length_to_next.y:
            current += direction * length_to_next.x
            traversed_length += length_to_next.x
            length_to_next.x = length_between.x
            length_to_next.y -= length_to_next.x
        else:
            current += direction * length_to_next.y
            traversed_length += length_to_next.y
            length_to_next.x -= length_to_next.y
            length_to_next.y = length_between.y
        result.append(current)
    return result

在我的测试中,这个版本有时会跳过图块。

现在,我们有current,有traversed_length不太重要。为了删除它,我们将累加长度,而不是减去(这样我们每个周期都会做得更少):

func segment(start:Vector2, end:Vector2) -> Array:
    var length = start.distance_to(end)     # (end - start).length()
    var direction = start.direction_to(end) # (end - start).normalized()
    var direction_sign = direction.sign()
    var next = next2(start, direction)
    var length_to_next = div2(next - start, direction)
    var length_between = direction_sign/direction
    var result = []
    var current = start
    result.append(current)
    while length_to_next.x < length or length_to_next.y < length:
        if length_to_next.x < length_to_next.y:
            current.x += direction_sign.x
            length_to_next.x += length_between.x
        else:
            current.y += direction_sign.y
            length_to_next.y += length_between.y
        result.append(current)
    return result

这种作品。舍入过程中出现了一些混乱。请参阅“hacky”部分。

让我们将长度缩小length(就好像我们的原始参数是从 0 到 1 一样)。我们也可以摆脱direction,当我们这样做时,内联next。通过这样做,我们不需要平方根。我们也不需要div2不再了。

func segment(start:Vector2, end:Vector2) -> Array:
    var difference = end - start
    var direction_sign = difference.sign()
    var length_to_next = (next2(start, direction_sign) - start)/difference
    var length_between = direction_sign/difference
    var result = []
    var current = start
    result.append(current)
    while length_to_next.x < 1 or length_to_next.y < 1:
        if length_to_next.x < length_to_next.y:
            current.x += direction_sign.x
            length_to_next.x += length_between.x
        else:
            current.y += direction_sign.y
            length_to_next.y += length_between.y
        result.append(current)
    return result

如果您更喜欢更接近论文“快速体素遍历算法”的命名法,请重命名length_between to tDelta, length_to_next to tMax, and direction_sign to step。另一个区别是我假设网格大小是1.


On next和“hacky”部分

我们需要一个next2函数,它看起来像这样:

func next2(vector:Vector2, direction:Vector2) -> Vector2:
    return Vector2(next(vector.x, direction.x), next(vector.y, direction.y))

And a next功能。对于“慢体素遍历算法”来说,是这样的:

func next(value:float, direction:float) -> float:
    if direction > 0:
        return max(ceil(value), floor(value + 1.0))

    return min(floor(value), ceil(value - 1.0))

那里发生了什么事?好吧,如果value不是整数,那么ceil or floor根据direction就足够了。然而,如果value是整数,我们要加或减1。按照它的写法,我不需要检查该值是否是整数。

但请注意,我说的是“慢体素遍历算法”。对于“快速体素遍历算法”我通过实验发现它应该是:

func next(value:float, direction:float) -> float:
    if direction > 0:
        return max(ceil(value), floor(value + 1.0))

    return floor(value)

在远离这个问题之后,我找到了一个更好的版本:

func next(value:float, direction:float) -> float:
    return floor(value) + clamp(sign(direction), 0, 1)

我不知道为什么会这样。然而,据我所知,这是因为我们正在增加direction_sign.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

(Godot 引擎)用 Tilemap 瓷砖填充 2D 多边形 的相关文章

  • 【Godot】行为树(一)了解与设计行为树代码

    行为树介绍 行为树是个节点树 xff0c 父节点通过不断遍历子节点 xff0c 根据不同类型的节点执行不同的分支 最终调用叶节点执行功能 行为树也不难理解 xff0c 他就像代码逻辑一样 xff0c 只是用节点的方式展现出来 xff0c 而
  • 【Godot 4.0】一个简单的匿名方法的使用lambda

    Godot 4 0 beta3 Godot 4 0 中添加了 lambda 表达式 xff0c 匿名方法等很多方便的特性 xff0c 这里我写个用于扫描目录下所有文件的功能 可以看到代码非常简洁 span class token keywo
  • Godot Engine:GDScript 4.X中语法的变化(2020年8月4日 更新)

    文章目录 4 X版 GDScript范例支持注解属性 xff08 Properties xff09 的定义格式await关键字代替yield加入super关键字去除了多级调用问题小结 4 X版 GDScript范例 支持注解 从4 x开始
  • Godot 4 应用 - 图形绘制

    花了两天时间 做了一个初步的图形软件效果 先占个坑 以后再叙
  • Godot 4 源码分析 - 文件读入编码处理

    今天需要读入xml文件进行处理 结果读入一个带中文的文件时 出错了 当然程序还能运行 但编译器一直报错 而且XML解析也不正确 单步调试发现读入的内容出现乱码 具体逻辑 String FileAccess get as text bool
  • 戈多3.2.1。刷新查询时无法更改此状态。使用 call_deferred() 或 set_deferred() 来更改监控状态

    在我的 2D 游戏中 玩家能够摧毁箱子 具有两种碰撞形状的物体 当被摧毁时 板条箱会产生也具有碰撞形状的物品 但是当调用以下函数时 Godot控制台中会显示许多类似的错误 Code func on Crate item dropped co
  • 合并多个精灵节点?

    例如 假设我有 2 个精灵节点 但也可以超过 2 个 如下所示 每个人都有自己独立的图像我想要的是将它们组合起来并用单个图像创建一个新的精灵节点 在工具模式下 like this 也许可以通过使用Image 毫无疑问涉及计算 或者也许使用一
  • 在 Area2D 中覆盖 KinematicBody2D 运动?

    I m trying to create a windy area within which the player would be pushed continuously to the left lt 到目前为止 这就是我想出的Windy
  • 如何在 GDScript 中实现结构?

    GDScript 中是否有相当于 C 结构 类的东西 例如 struct Player string Name int Level 戈多3 1 1gdscript不支持structs 但使用可以实现类似的结果classes dict or
  • 如何在Godot 4.0游戏引擎中实现可组合的角色/技能系统?

    我目前正在使用 Godot 尝试 MOBA 风格游戏的原型 我正在努力寻找一种管理角色及其技能的方法 所有角色都将具有相似的属性 姓名 生命值 奔跑速度 力量等 然而 所有角色的技能都会有所不同 尽管有些角色非常相似 例如基于投射物的技能将
  • Godot 监听来自同一场景的多个实例的信号

    我有以下场景 玩家 敌人 攻击 当攻击与敌人发生碰撞时 敌人会发出 onHit 信号 播放器监听该信号并反弹 这一切都运行良好 但现在如果我复制敌人 因此有多个敌人场景 我如何收听所有敌人的信号 有没有办法获取场景的所有实例并连接到它们的所
  • 如何获取另一个场景godot中的节点?

    我正在制作一个具有多个场景的游戏 需要有 get node 来自另一个场景的节点 变量 并且我不知道如何从另一个场景获取节点 如果我理解正确的话 您想连接到来自另一个场景中的节点的信号 直接方法会起作用 const bullet prelo
  • 如何在 Godot 中将字符串转换为枚举?

    使用 Godot 3 4 我的枚举设置为 enum STRENGTH DEXTERITY CONSTITUTION INTELLIGENCE WISDOM CHARISMA 我希望能够使字符串 STRENGTH 返回枚举值 0 我希望下面的
  • 如何在戈多中使刚体跳跃而不赋予其飞行能力

    我本来会使用运动体 但我想将现实生活中的物理添加到我的 2d 对象中 但似乎我可以通过多次按向上键来飞行 extends RigidBody2D var velocity Vector2 ZERO const GRAVITY 35 cons
  • 如何克服 Godot 将按钮字体更改为默认颜色的问题?

    我正在使用 Godot 4 我在容器中手动创建了很多按钮 我在主场景中设置了一个颜色变量 tempcol 当我单击带有 tempcol 设置的按钮时 比如 Color Red 按钮的字体颜色更改为白色 似乎是默认字体颜色 但是当我单击另一个
  • 同一节点的碰撞检测和重叠检测? [第2部分]

    的延续上一个问题 https stackoverflow com questions 71608423 collision detection and overlapping detection in same node 71622366
  • 如何应用着色器并仅生成图像一次?

    我正在尝试将像素化着色器应用于我的纹理 并且我只需要将其应用一次 之后我可以一遍又一遍地重复使用我的着色器生成的图像作为纹理 而不必每次都进行计算 那么我如何拍摄一些图像 gt 应用着色器并在每次游戏加载时仅渲染它们一次 gt 并将它们用作
  • 关闭 Godot 中的游戏

    我正在使用 Godot 创建网页游戏 为了关闭游戏 我尝试使用 get tree quit 如果我在 IDE 上使用它 它就可以工作 当我在我的服务器上尝试它时 导出项目后 它不起作用 我确信导出设置没问题 我怎样才能关闭游戏 并且 如何添
  • Godot:检测 Area2D 内部的“鼠标按下”和 Area2D 外部的“鼠标向上”

    我想检测 Area2D 内部的鼠标单击 并按住 然后检测 Area2D 内部或外部的鼠标释放 这是我到目前为止所拥有的 extends Area2D PickArea func input event viewport event shap
  • Godot 3d 得到向前矢量

    我想知道是否有办法获取 godot 3d 中空间节点的前向向量 统一起来 这就是transform forward Godot 给了我一个旋转向量 但我不知道如何将其转换为方向向量 戈多版本的transform forward是什么 前进是

随机推荐

  • 如何使用 Pooled Spring bean 而不是 Singleton bean?

    出于效率原因 我有兴趣限制同时使用 Spring 应用程序上下文的 bean 的线程数量 我不希望无限我的处理线程数limited记忆 我已经发现here spring 文档 一种通过以 EJB 样式池化 bean 来实现此目的的方法 方法
  • 添加相同类型的自定义解析器时,默认参数解析器会发生什么情况?

    所以我想将最大可分页大小值限制为 10 示例值 我可以这样做 Configuration public class MvcConfiguration extends WebMvcConfigurerAdapter Override publ
  • 如何将 Selenium WebDriver 嵌入为 WPF 控件?

    有没有办法嵌入WebDriver驱动程序到 WPF 窗口 类似于 WPFWebBrowser控制 或者 有没有一种方法可以在WebBrowser控制自己 到目前为止 只能创建一个新的WebDriver窗口 与应用程序中的任何其他 WPF 窗
  • SQL Server 连接最新 2 个条目

    我知道帖子的标题很糟糕 但请听我说完 前几天工作中出现了这样的问题 虽然我找到了解决方法 但这个问题仍然困扰着我 假设 Stackoverflow 只有 3 个表 Users username Comments comment creati
  • 如何为现有 Azure WebApp 添加 Application Insights

    我在 Azure 中有 10 个 Web 应用程序 但我无法为它们配置 Application Insights 因为我还没有使用它们保存项目 当我在 VisualStudio 中创建新的 WebApp 时 可以为应用程序添加 Applic
  • Java:检测三击而不触发双击

    我有一个 JTable 我想在双击单元格时调用一个函数 并在三次单击单元格时调用另一个函数 当单元格被三次单击时 我不想调用双击功能 我现在拥有的是 mgrdAlarm 是 JTable mgrdAlarm addMouseListener
  • 多路 FunDeps 以及与重叠实例的一致性:(为什么)这有效?

    这是旧栗子的变体 我写它的时候以为它不会起作用 但它确实起作用了 还是它很狡猾 在 GHC 8 6 5 中 LANGUAGE MultiParamTypeClasses FlexibleInstances FlexibleContexts
  • 实体框架数据库种子不种子

    我对 EF 还很陌生 我正在尝试覆盖Seed我的自定义初始化程序中的方法 使用 MVC 4 问题是当 EF 创建数据库时 我没有发现任何初始记录插入到我的数据库中Admins桌子 这是我的代码 namespace FP Domain Con
  • Ionic 3 错误 没有 AppVersion 的提供程序

    我正在使用 Ionic 3 延迟加载 我收到此错误 但似乎找不到我的方法的错误 错误 没有 AppVersion 的提供者 I have 设置 module ts import NgModule from angular core impo
  • 子类上的重复生成器序列休眠

    我按照这篇文章来解决我最初的问题 在 Hibernate 中的子类上指定每个表的不同序列 但现在我得到一个例外 调用init方法失败 嵌套异常是 java lang IllegalArgumentException 重复的生成器名称 idg
  • 在设计 C# 类库时,什么时候应该选择继承而不是接口? [关闭]

    Closed 这个问题是基于意见的 目前不接受答案 我有一个号码Processor类将执行两种截然不同的操作 但从通用代码中调用 控制反转 情况 我想知道在决定它们是否都应该继承时 我应该认识到 或为您的用户认识到 哪些设计考虑因素Base
  • 使用 sed 替换文本

    我在通过 sed 替换脚本中的修改日期时遇到问题 我得到的最后修改日期是这样的 olddate grep m1 Built script sh cut c 22 29 我通过以下方式获取当前日期 newdate date d m y 基本上
  • 如何在实体框架代码优先方法中使用表值函数?

    我正在使用实体框架开发一个项目 现在我遇到了一种情况 我需要使用表值函数 它返回包含 2 列的表 因此我搜索了很多 我开始知道我们在数据库优先方法中使用表值函数虽然我首先需要在代码中使用它 情况是这样的 我有一个有两列的表格 Table1
  • 流畅的 nHibernate、Hi-Lo 表,使用约定每行实体

    有没有办法通过约定指定用于 Hi Lo 值的表 每个实体都有一个每行条目 同时仍然让 nHibernate 创建表结构 我想复制 Phil Haydon 博客上的内容here 但无需手动管理表 就目前情况而言 只有当您已经在表中为 Tabl
  • PostgreSQL:如何从 Unix 纪元转换为日期?

    声明给了我日期和时间 如何修改该语句 使其仅返回日期 而不返回时间 SELECT to timestamp TRUNC CAST epoch ms AS bigint 1000 You use to timestamp函数 然后将时间戳转换
  • 如何缩小 rand() 中的数字?

    以下代码每秒输出一个随机数 int main srand time NULL Seeds number generator with execution time while true int rawRand rand std cout l
  • VB.Net 隐藏标签页

    我在这里看到了一些关于如何在选项卡控件中隐藏选项卡的讨论 但它们似乎都是用 C 或某种变体编写的 我还没见过 vb net 的 我不会做 C 我想要做的是隐藏或禁用所有一些选项卡 直到用户登录 我已经解决了登录和注销问题 我需要做的就是添加
  • 在 UIScrollView 上显示大型 UIView 的最佳方式是什么?

    我有一个大UIView 它的大小可变 它可能大于 5000x5000size 我用它在上面画线 圈UIBezierPath 我还添加一些view就在上面 这些中的每一个subview的包含buttons textview labels et
  • Visual Studio 2015 调试自定义控件

    我将自定义控件编译为 DLL 这些控件是使用 Visual Studio 2012 开发的 并且部署到生产环境中没有任何问题 当应用程序加载时 这些控件将使用反射作为 插件 加载 当我使用 Visual Studio 2015 打开解决方案
  • (Godot 引擎)用 Tilemap 瓷砖填充 2D 多边形

    我在 Godot 引擎中遇到一个无法解决的问题 怎么可能 在代码中 用图块填充 Polygon2D 区域 我尝试过获得点位置 使用 2D for 循环遍历线的顶点 但我无法理解这一点 这是我期待的结果的模型 我有解决方案 有一点 hacky