游戏引擎学习第205天

发布于:2025-04-06 ⋅ 阅读:(15) ⋅ 点赞:(0)

回顾

我们今天要实现的是一些实体浏览功能,原本是昨天就计划好的,但因为渲染上的一些问题耽搁了一些时间。

实际上,我们遇到的并不是一个真正的bug,尽管我们花了大约40分钟才搞清楚,最终发现它只是渲染方式的一个正常行为,而我自己忘记了这一点。所以,实际上并没有bug,只是我们误解了渲染的方式。不过,幸运的是,解决这个问题不需要修改任何代码,因为根本就没有bug。总的来说,这个小插曲让我们费了一些力气,但也没带来太大问题。

现在问题解决了,我们可以继续进行原定的工作:实现实体浏览功能。对于今天的工作,我打算开始实现昨天计划的功能,而现在可以顺利地进行下去。

当我们结束昨天的工作时,已经把系统搞得差不多了。我们原本以为有问题的部分,最终证明并没有问题。尽管如此,仍然有一些地方可以进行优化和清理。

我们现在要实现的功能是通过鼠标点击,检查当前鼠标指向的实体。这些实体包括了很多不同的对象,比如一些被拆解的游客、一颗漂浮的头部、英雄、树木等。每个实体都有不同的属性,而我们希望能够快速检查这些属性。为了实现这一点,我们的目标是通过鼠标选中某个实体,点击它后能轻松查看该实体的属性。

为此,我们首先需要找到一种方法,判断鼠标是否与某个实体发生了交集。由于游戏采用了3D投影,所以我们必须将鼠标位置从屏幕坐标转换回世界坐标。昨天的直播中,我们已经完成了这部分工作,成功地将鼠标位置与世界坐标对接,能够在游戏中看到鼠标指针跟随鼠标移动并定位到正确位置。

到目前为止,工作进展顺利,后续的步骤应该会相对简单。虽然开始时遇到了一些问题,但通过调整渲染方式,这些问题最终得以解决。所以接下来的任务就是继续开发鼠标选中实体的功能,这应该是一个相对简单的任务。

game.cpp:概述所有实体

为了让功能恢复到之前的状态,首先需要撤销之前的更改,特别是关于渲染状态的部分。之前尝试修改的代码导致了偏移量的错误,因此需要移除这些修改,并确保恢复渲染组的原始设置。这样做可以确保实体的轮廓能够正常显示。

具体步骤是:先回到绘制轮廓的代码,确保每个实体的边界框都能被正确绘制出来。通过调用 PushRectOutline 来绘制每个实体的碰撞矩形。在此过程中,我们不再改变渲染组中的偏移量和缩放值,而是保持它们的原始状态,这样可以避免任何不必要的变动,确保轮廓与实体的位置一致。

通过撤销之前的错误调整,成功恢复了实体的碰撞矩形的绘制,所有实体现在都能正确显示其对应的边界框。这是回到之前的状态的第一步,现在可以继续进行其他的改进或功能实现。
在这里插入图片描述

在这里插入图片描述

考虑让渲染器的工作更加逻辑化

首先,必须弄清楚如何将鼠标位置转化为更合理的方式。现在已经知道,Z轴的偏移实际上并没有被使用,这其实是一个有趣的发现。Z轴偏移不对渲染产生任何影响,因此如果想进行调试查看,就得从另一个角度考虑如何进行操作。这也是为什么觉得渲染器应该改成一种更符合逻辑的方式,而不是目前这种做法的原因。虽然理解为什么要用当前的方式,但从长远来看,这种做法在某些方面会造成困难,特别是在考虑到它的后果时。

目前,已经通过将鼠标位置转化为“米”单位来处理了鼠标位置,已经做了“反投影”处理,将鼠标坐标转换回真实世界坐标。接下来,需要做的就是将鼠标位置进一步反投影到当前正在操作的平面上。这是一个需要注意的步骤,因为在进行反投影时,我们忽略了一个问题,那就是在执行反投影时,并没有考虑到目标平面之上的距离。这导致在反投影时,不能准确得到一个真实的Z值,因为世界坐标中的Z值并没有被正确处理。

这就是为什么之前提到过,这部分的工作显得有些奇怪。我们在处理反投影时,没有考虑目标上方的实际距离,导致了无法得到准确的Z值。这部分代码的原因,实际上是当初我们只用这个反投影来确定从摄像头到某个距离的大小,来计算摄像头的视野范围。这个做法在当时是完全合理的,但随着开发的推进,这种方式就开始产生了问题,因为我们想做的其他操作并不是简单的反向变换,而是需要更复杂的处理。

所以,问题的核心就在于,当我们使用这种反投影方式时,它并没有完全适应其他的需求,导致在后续处理时,很多操作都变得复杂和困难。

game_render_group.cpp:废弃Unproject

我们需要找到一种方式来处理当前的代码,使得新旧代码能够兼容。目标是能够调用旧的代码,并且能够对其进行测试,使得它与新的部分能够正确配合。为了做到这一点,可能需要实现一些转换,具体来说,就是我们希望能实现一个“反向转换”的功能,这样就能确保我们在经过新的处理之后,依然能够返回到旧的状态。

考虑到我们在处理过程中可能会涉及一些问题,譬如偏移量和缩放因素,这些都是我们在整个流程中经常用到的参数,特别是在传递数据时,我们需要确保这些参数的正确性和一致性。与此同时,我们也需要思考是否需要一种“反投影”方法,一种能够忽略偏移量和缩放的反投影,另一种则需要考虑到这些因素。这部分的处理是比较复杂的,需要谨慎考虑。

为了避免过于复杂,我们决定先不处理这一部分,而是先实现一个基本的“反投影”函数,这个函数能够简化当前的操作流程,并且确保基本功能可以正常工作。具体来说,我们会保持当前的投影函数的运作,然后再逐步调整和完善,直到达到更加合理的效果。

在此过程中,重要的一点是要确保鼠标操作和投影处理能够正确绑定。当前,我们的投影函数能够实现鼠标的准确跟随,这也是我们最初预期的效果。接下来的目标是进一步优化和改进,确保代码结构更加清晰、稳定。

先用之前的代码进行测试

在这里插入图片描述

在这里插入图片描述

game_render_group.cpp:将CompleteUnproject功能融入一个新的三维Unproject函数

我们需要完善反投影(unprojected)功能,确保它能够正确处理各种转换,并与现有的系统兼容。首先,我们在实现反投影时,决定将其暂时标记为“未实现”(not implemented),然后逐步完善它。核心目标是实现一个完整的反投影过程,使得屏幕空间的坐标能够正确地转换回世界空间的坐标。

反投影的实现步骤如下:

  1. 屏幕空间到世界空间的转换
    我们首先需要将屏幕空间的坐标(即以像素为单位的坐标)转换为世界空间的坐标。在这一过程中,首先会处理像素坐标的偏移。具体来说,我们将屏幕中心从当前的像素坐标中减去,这样就能得到相对于屏幕中心的坐标。接下来,将其乘以米到像素逆,从而将像素坐标转换为米单位。

  2. Z轴的处理
    在这个过程中,我们没有对Z轴的坐标进行任何转换,因为我们始终假设Z坐标处于相同的空间中,并且它不需要进行转换。Z轴的处理逻辑是,Z轴的值直接作为参数传递,并且在反投影过程中不会发生改变。

  3. 目标点的距离调整
    在转换过程中,我们还需要考虑目标点的距离。在屏幕空间中,我们需要通过减去当前点的Z值(P.Z)来调整距离,并进行除以焦距的操作。这可以帮助我们计算出一个比例因子,用于调整当前点的比例,使得它能够正确地映射到世界空间。

  4. 反投影的最终计算
    最后,我们将得到的2D值进行反投影处理,计算出世界空间中的相应坐标。通过将Z值附加到计算得到的2D坐标上,我们就得到了最终的反投影结果。这个结果表示了在给定的Z距离下,点在世界空间中的位置。

  5. 偏移和缩放的处理
    在整个过程中,我们还需要处理变换的偏移量。在最初,我们会将一个偏移量应用到变换中,而在反投影结束时,我们需要减去这个偏移量,以确保恢复到正确的位置。缩放因子目前并没有实际使用,因此我们可以忽略它,直接进行反投影的计算。

通过这些步骤,我们能够确保反投影的过程能够正确还原出原始的空间坐标,无论投影时使用的具体方式如何。在整个实现过程中,我们尽量避免不必要的复杂操作,确保代码逻辑清晰且高效。最终,反投影功能将能够根据实际的屏幕空间坐标恢复世界空间坐标,满足不同情况下的需求。
在这里插入图片描述

win32_game.cpp:统一输出和输入坐标系统

我们正在进行对反投影(unprojection)功能的进一步调整。之前的做法中,我们使用了不完全合适的坐标转换方式,尤其是在鼠标坐标的处理上存在一些不一致。

首先,我们发现鼠标坐标并不是以像素为单位传入的,而是以其他单位传入,这让处理变得有些复杂。为了简化这个过程,我们希望确保输入和输出坐标系统一致。具体来说,鼠标的X坐标应该直接传入像素值,而Y坐标则需要经过一些转换。由于屏幕坐标系的Y轴方向与像素值的Y轴方向相反,我们需要将鼠标Y坐标转换为屏幕的像素坐标系统。这意味着我们将鼠标的Y坐标用屏幕的高度减去,这样可以确保坐标系统的一致性。
在这里插入图片描述

接下来,反投影的过程可以正常地进行。在新的实现中,鼠标坐标的处理不再依赖于复杂的转换过程,而是直接使用像素值,简化了处理逻辑。我们期望能够直接从鼠标的屏幕坐标恢复出世界空间的坐标,从而实现完整的反投影功能。

现在,通过对变换的逆操作,能够正确地撤销先前的投影变换,将坐标恢复到原始状态。需要注意的是,在这一过程中,我们仍然需要一些调试工作,以确保所有的变换步骤都能正确执行。此外,变换操作中需要处理的位置数据现在是以 v3(向量3D)形式传入,这样我们就可以直接进行计算,得到正确的结果。

最后,在调试过程中,我们将确认所有变换都能按照预期工作,并确保鼠标坐标的转换过程是准确的。这将使得整个反投影过程更加稳定和高效,能够应对各种实际需求。
在这里插入图片描述

在这里插入图片描述

game_render_group.cpp:完成编写Unproject

我们在调试时发现了一个问题,涉及到渲染变换(render transform)和偏移量(offset P)之间的关系。具体来说,在尝试获取变换时,发现我们没有正确地执行某些操作,尤其是在更新偏移量时。仔细检查后,发现问题出在偏移量更新上,原本应该使用 -= 操作符来修改偏移量,但实际上我们没有这样做。

这一问题导致我们在进行变换时没有正确地更新偏移量,从而影响了后续的计算和结果。为了修复这个问题,我们需要确保在变换过程中,偏移量的更新是正确的,特别是在每次处理渲染变换时,需要使用 -= 操作符来减少偏移量,这样才能保证每次变换后的结果是准确的。

总结来说,问题的根本原因在于偏移量的更新没有按照预期进行,需要在相应的位置添加正确的偏移量修改操作,确保变换过程中的所有数据都得到正确的处理。
在这里插入图片描述

在这里插入图片描述

game_math.h:为向量引入operator-=

在调试过程中,发现了一个问题:在更新偏移量时,我们使用了 += 操作符来增加偏移量,但却没有使用 -= 操作符来减少偏移量。这显然是不一致的,因此我们决定修正这一点。为了保持一致性,我们应该在需要减去偏移量的地方使用 -= 操作符,以确保每次更新时都能正确处理偏移量。

另外,我们发现代码中的某些部分的顺序不太合适,特别是有些函数在文件中的位置不太理想。为了避免出现无法引用之前内容的问题,我们决定将某些操作移到文件中更合适的位置。这是为了确保代码的可读性和合理性,避免调用顺序上的问题。

通过这些调整,我们能够确保偏移量的更新更加合理,并且代码的结构更加清晰,避免潜在的引用错误。
在这里插入图片描述

game.cpp:调用新的Unproject

我们决定将“Unprotect old”功能修改为执行常规的反投影(unprotect),这是为了确保偏移量 OffsetP 能够正确地转化为世界坐标系下的鼠标位置(WorldMouse)。接下来,我们要进行完整的变换处理,包括对反投影坐标的计算。

在此过程中,我们注意到有一个潜在问题:变量 我们决定将“UnprotectXY 已经初始化为局部变量,但目前并没有在代码中使用。为了确保代码的完整性和避免潜在错误,我们决定暂时将其占位(stub out)出来,确保一切按预期进行。

目前,已经做了这些调整,接下来需要确保变换的完整性和稳定性,继续调试并完善相关功能。
在这里插入图片描述

在这里插入图片描述

调试器:进入并检查MouseP和PixelsXY

当前我们的唯一目标是让那个小蓝色方块重新显示出来,这是整个调试工作的重点。为此,我们开始逐步检查流程,确保一切正常运行。

首先确认的是鼠标位置 MouseP 是否已经以像素单位传入。从目前的结果来看,确实是像素级别的值,并且当鼠标在屏幕左上区域时,X 不再是负数,这是符合预期的。也就是说,像素坐标系现在是统一且正确的,鼠标坐标不会再因为位于某个象限而出现负值,这说明我们的坐标处理逻辑是正确的,这是一个好迹象。
在这里插入图片描述

然后我们进入反投影处理函数 unprojected,传入的参数包括 XY 坐标和以米为单位的 Z 值,这正是我们所需要的格式。在这个过程中,还确认了 screenCenter(屏幕中心点)的值,这部分也是正确的,符合我们对屏幕中心位置的预期。
在这里插入图片描述

接下来进行的步骤是将传入的 XY 像素坐标转换为以米为单位的空间坐标。这个转换使用了我们之前定义好的屏幕像素到物理世界单位(米)的变换逻辑。现在我们要检查这个转换是否正确执行,也就是确认转换后的值是否合理,是否处于期望范围内。

整体来说,到目前为止的每个步骤都在按照计划推进,坐标传递和变换逻辑都运作良好,系统的反投影路径已经基本构建完成。接下来继续调试,确保所有细节都正确无误,从而最终实现目标 —— 小蓝色方块的正确显示。

调试器:返回到Unproject进行检查

接着我们检查 DistanceAboveTarget 的值是 9,而距离 DistanceToPZ 是 10,所以结果为 10,这使得最终投影的 Z 值被判定在近裁剪平面之外,导致图形无法被渲染出来。这其实就是关键问题所在 —— 我们在反投影时,对 Z 值的还原过程出现了错误。

初步推测是因为在快速实现反投影函数时,有些细节没有处理到位,特别是 Z 方向的深度还原逻辑可能不够准确,导致计算出来的深度位置被裁剪掉。

下一步的重点是重新审视反投影中 Z 值的计算方式,确保它不会落在裁剪平面之外,从而影响物体的正常渲染。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

game_render_group.cpp:查看GetRenderEntityBasisP中的DistanceToPZ计算

现在我们正在梳理 Z 值在反投影(Unproject)中的确切语义,目的是确保它的含义在整个流程中保持一致,避免因为定义模糊导致的计算错误或渲染异常。

当前传入的 Z 值,是由调用方提供的,我们的理解是,它应代表从相机到该点的距离(即摄像机空间中的深度)。如果这是事实,那么我们就应该将这个值直接作为反投影中最终三维坐标的 Z 分量,作为从相机出发的深度值,不再做额外的变换或偏移。

我们回头分析当前的流程,发现目前代码中对 Z 的处理可能掺杂了一些“从目标点出发的偏移”或“相对于某些参考平面的高度”这类概念。而实际上,如果我们的约定是“Z 表示该点距相机的米数”,那么反投影时就应该保留这个约定,把这个 Z 直接还原为相机坐标系下的空间深度。

换句话说:

  • 传入的 Z 就是点到相机的直线距离(单位:米)。
  • 不需要再通过“above target”或其他中心参考点去换算或偏移。
  • 我们只需要将屏幕空间 XY 反投影到世界空间,并把传入的 Z 直接作为深度合成一个最终的 3D 世界坐标即可。

这就意味着问题并不在于反投影结果有误,而是我们之前可能在试图将这个 Z 重新解释为某种相对距离或高度,而不是绝对深度。这种“意义上的混淆”才是导致最终结果出错的根源。

下一步的重点,是在相关函数(特别是反投影核心逻辑)中明确 Z 的语义,并确保不再对它做多余处理,避免“从目标点计算距离”这种多余的解读,只保留它作为摄像机深度这一单一语义。这样才能确保反投影生成的位置是真实的、稳定的、不会被裁剪平面错误处理掉的三维坐标。

Blackboard:如何指定DistanceToPZ

目前我们设定的场景是一个摄像机俯视向下观察场景,而我们定义的 DistanceAboveTarget 是从摄像机到目标平面的距离,这是清晰明确的。

现在传入的 Z 值代表的是我们想要绘制的那个点在 Z 轴方向上的偏移量。从目前的逻辑来看,这个 Z 实际上是 相对于目标平面 的偏移:

  • 如果 P.z 为正,说明该点在目标平面之上。
  • 如果 P.z 为负,说明该点在目标平面之下。

这是因为我们对这个值的处理方式是从 DistanceAboveTarget 上减去这个 P.z 值,从而得到该点在摄像机坐标系中的真实深度。

现在关键问题来了:我们传入的 Z 值到底应该表达什么含义?

我们现在的处理方式是将它视为 相对于目标平面的“Z 屏幕空间”偏移量,而不是某个世界空间中的绝对 Z 值。它仅仅意味着“我距离投影平面多远”,而不意味着我在世界坐标中某个固定高度。

而且在当前的反投影逻辑中,我们没有对这个 Z 做任何翻转操作,直接将它作为偏移参与深度计算。这是符合预期的——我们始终在以屏幕为参考系工作,Z 的语义应该保持一致,不应该混入世界坐标系或其他参考坐标。

所以我们的结论是:

  • Z 值的含义应为“相对于目标平面的摄像机空间偏移”,不是世界坐标中的高度,也不是直接的屏幕 Z 值。
  • 在反投影中,这个 Z 只需被直接传入参与深度计算,不要进行翻转或额外处理。
  • 这种定义的好处是:当我们将鼠标用于定位某个点时,只需传入 Z=0 即可表示“位于目标平面上”;传入正值表示高于目标平面,负值表示低于目标平面。

因此,在这个设计下,反投影的核心逻辑将更加简洁、统一,也更容易进行调试与推理,不会因混淆不同坐标系而出错。这也是我们想要的行为。
在这里插入图片描述

game_render_group.cpp:将ZMeters重命名为ZMetersFromTargetPlane

我们这里所说的 Z 值,其实表示的是“距离目标投影平面的米数”,也就是这个点在摄像机空间中,相对于目标平面前后偏移了多少米。换句话说,这个 Z 是一个相对值,它不是世界空间中的高度,也不是绝对的深度坐标,而是针对投影平面的一种局部偏移。

举个例子:

  • 如果我们传入 Z = 0,就表示我们想要绘制的这个点正好落在目标平面上。
  • 如果传入 Z > 0,则表示该点在目标平面之上,靠近摄像机。
  • 如果 Z < 0,就表示它在目标平面之下,更远离摄像机。

这个值在反投影时会直接参与计算,被加到以 distance_to_target 为基础的深度上,从而得出该点实际在摄像机空间的 Z 坐标。

但问题来了:如果我们不小心传入了一个比较大的正值,比如 10,那么它就会离目标平面很近,甚至可能会落在摄像机近平面前面,也就是被“裁剪”掉(因为超出了视锥体可视范围)。所以像我们这样传一个 Z=10 其实就是在告诉程序“这个点离投影平面有10米那么近”,但由于摄像机的近平面可能只有0.1米或更小,这个点会被立即裁剪掉,也就是被 OpenGL 或 DirectX 所谓的 “near clip plane” 给剔除。

因此我们要注意:

  • Z 值不应该随意乱设,它要符合当前视锥的可视范围;
  • 如果我们只是想放置一个在目标平面上的点,传 Z=0 是最稳妥的;
  • 一旦 Z 设得太大,虽然数学上是能算出结果的,但从渲染管线角度,它很可能会因为落在近平面前而被裁剪掉,导致什么都看不到。

所以总结来说:

  • 我们的 Z 表示“相对于投影平面的偏移量”,单位是米;
  • Z 越大越靠近摄像机,越小(负数)就越远离;
  • 如果我们看到某个点“消失”了,可能就是 Z 太大,被裁剪了;
  • 程序没有错,是我们对这个值的意义把握不准确,今后只要清楚其代表的是 投影平面偏移量 就不会再搞混了。
    在这里插入图片描述

game.cpp:设置LocalZ = 3.0f

我们之前传入的 Z 值是 10 米,这种设置在当前系统中根本不可能正常工作。这个 Z 值是本地坐标系中相对于目标投影平面的距离,表示这个点比目标平面高出 10 米。问题是,在我们的投影设置中,这种距离太大了,已经远远超过了视锥体的近平面范围。

换句话说,Z = 10 表示这个点非常靠近摄像机,甚至近到已经超出了可视空间的最前面,因此会被裁剪掉,也就是被“Near Clip Plane” 给剔除。这样一来,我们无论如何都无法在屏幕上看到这个点或对应的图形。

相比之下,Z = 3 这样的值就是合理的。它仍然是在目标平面之上,但没有近到被视锥体剔除的程度,所以可以被正常渲染出来。这也是为什么我们只要稍微调小一点 Z 的值,就能看到预期效果的原因。

所以总结如下:

  • 本地 Z 值等于 10 米太大了,超出了摄像机近平面范围,因此无效;
  • 这种情况下图形会被完全裁剪,导致“什么都看不见”;
  • 适当的 Z 值应该控制在视锥范围内,例如 3 米是合理的;
  • 问题本质不在于渲染系统错误,而是我们对 Z 含义的误用;
  • 修正方法就是根据实际需求传入合理的 Z 偏移值,避免超出裁剪范围。

这个问题的发现也帮助我们更准确地理解了投影坐标中 Z 值的语义和使用边界。

在这里插入图片描述

在这里插入图片描述

运行游戏并设置,现在它可以正常工作

在调试过程中,遇到的一个问题是,由于涉及到二维半立体的场景(2.5D),导致了在处理投影转换时,像素值的转换和计算过程显得不太直观,且有些不理想的地方。原本的想法是通过解锁像素(unprotect map)来正确地转换,但是实现过程中,还是会有一些问题。主要是因为2.5D场景的特殊性,这种场景与传统的3D场景有所不同,需要特别注意如何处理坐标的转换和映射。

目前,这种情况是第一次处理2.5D的坐标系问题,而以往在3D场景中处理坐标要更为直接和清晰。在这个过程中,虽然已经能部分解决问题,但还没有完全找到一种合适的方式来将各种参数合理地组合并规范化。预计在未来的开发过程中,可能会对如何定义和使用这些参数形成更为明确的规范,从而减少错误发生的机会,比如像昨天遇到的那种低级错误。

总体来说,尽管已经取得了一些进展,但仍有一些细节上的问题需要调整和改进,特别是在坐标转换和参数处理的方式上。在后续的调试和开发中,需要逐步建立起对2.5D场景的处理逻辑的清晰理解。
在这里插入图片描述

game.cpp:测试MouseP是否在实体平面上

在当前的调试过程中,目标是通过正确的投影(unproject)来计算鼠标在场景中的位置。考虑到当前的变换(transform)已经设定好了,因此不需要再添加额外的偏移量。实际上,关键是在计算过程中传递的偏移量应当只包括总的差异偏移(differential offset),而不再涉及额外的复杂计算。若能正确地处理这些偏移量,理论上就能得到正确的本地坐标。

接下来,虽然已经能够处理基本的投影,但还是需要一个额外的步骤,确保代码可以在单一实体上进行调试。因此,暂时保持调试模式,设置一个实体索引(例如10),这样就可以通过单个实体来观察计算的结果并确保它正确。

在实现过程中,需要注意的是:为了在渲染器中准确地展示这些坐标,必须确保能够在正确的偏移量上进行渲染。幸运的是,由于当前的坐标系已经设定好了,可以将偏移量设置为零,然后直接使用计算出来的本地偏移量。这样,就能够确保渲染出的图形符合预期,且渲染系统能够智能地处理坐标变换和显示。

当前的步骤看起来是合理的,但仍需验证其有效性。还需要在“反投影”(unproject)过程中过一遍,以确保所计算的结果确实是期望中的值。此外,尽管目前实现的投影逻辑似乎是正确的,但在实际调试过程中可能还需要进一步细化和优化。
在这里插入图片描述

发现偏移的很多

在这里插入图片描述

调试半天发现一个问题

在这里插入图片描述

在这里插入图片描述

operator-= 写的有问题
在这里插入图片描述

在这里插入图片描述

运行游戏并注意到比例有些偏差

目前,经过反投影(unproject)后,得到的结果仍然有一些偏差,特别是在缩放比例上,鼠标的位置计算略有误差。尽管如此,整体上已经非常接近正确的结果。通过反投影的过程,计算的坐标值基本上是准确的,但仍然存在一些细微的偏差,导致结果并未完全符合预期。因此,需要进一步的调整和优化,尤其是在缩放方面,以确保反投影的结果更加精确,最终达到正确的效果。

game.cpp:重新指定Unproject,不使用ZMetersFromTargetPlane

在处理投影和反投影时,遇到了一个不太直观的问题。当使用“目标平面上Z米”作为直接传递值时,数学计算出现了不匹配的情况。具体来说,虽然XY坐标的反转是正常的,但对于Z值的处理却变得复杂,因为Z值并不直接与屏幕空间中的Z值相对应。通常,在3D场景中,Z值是与摄像机相关的,而在这个过程中,我们需要一种方法来正确地处理屏幕空间中的Z坐标。

当传递Z值时,如果我们将其设为从目标平面起的距离,那么实际上这就是我们正在处理的Z值。但是,实际的反投影过程并没有直接给出正确的Z值,导致反转的数学操作不完全符合预期。这就意味着,如果我们直接传递Z值,可能无法保证反投影结果的准确性,尤其是在3D变换时。

目前的处理方式虽然方便,但存在不可逆性,感觉上有些不合理。理想情况下,应该更具一致性地处理这些值,特别是在做反投影时,Z值应该按照屏幕空间中的坐标来处理,这样才能保持一致性。然而,这种方法可能会变得更加复杂,特别是在处理不同变换时。

如果避免使用Z值,直接使用已知的偏移量P值(即实体的实际偏移量),反投影的结果会更加符合预期,且计算过程会更加简洁。这个方法看起来更加合理,但当前的实现方式并没有完全按照这种逻辑来处理,导致了某些不必要的复杂性。

目前的问题虽然看似难以立即修复,但这已经成为了一个需要关注并修复的潜在问题,预计在未来会进行相应的调整。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

运行游戏并看到我们正确地做了反转

现在我们处理流程终于正确了,因为我们已经在反投影的过程中做了准确的反转处理。当我们把鼠标移动到某个实体上时,能够正确地高亮显示,这一部分的逻辑是正常的,一切运行良好。

不过,我们对当前的变换处理方式还是不太满意。虽然可以工作,但总感觉现在这种处理方式太随意了,数学计算不够严谨,变换系统有点松散,缺乏一个“真正意义上”的变换框架。我们开始怀疑是否应该把这个部分处理得更规范一点,真正引入完整严谨的变换体系,而不是继续使用目前这种有些“放任”的方式。

我们开始考虑是不是应该更认真地看待空间变换的部分,比如引入一个真正的坐标变换系统(类似于完整的矩阵变换),这样会让整个反投影、投影过程更可控、也更可逆。当前的实现方式虽然可以达到目的,但感觉过于“即用即改”,数学关系不够统一,这可能会在后续带来更多不可预期的问题。

在这段尝试过程中,还一度不小心做了些错误的改动,比如误删了几个元素,随后又及时撤销。清理掉那些误操作后,现在的渲染结果又恢复了正常,能正确地围绕目标绘制标记。

总的来说,目前系统运行是正常的,鼠标指向可以高亮实体,但从架构层面,我们对变换系统的设计仍然不太满意,后续可能会重新设计这一部分,使其更加严谨、统一、易于调试和维护。这个问题虽然暂时不会立即修复,但已经列入待改进的事项列表中。
在这里插入图片描述

运行游戏并测试这些轮廓的效果

现在我们可以正确地处理所有实体,并能够选择任何一个目标实体进行交互。之前绘制的测试用矩形其实已经没有必要保留了,那只是用来验证流程的调试工具,现在我们可以把它删掉,简化逻辑。

这样一来,当前的系统就只保留了能够在必要时高亮目标实体的核心功能,这是我们期望的行为。

我们还想继续验证一点:当我们改变 Z(也就是相对高度或者说 Z 轴方向上的位置)时,这套机制是否仍然有效。我们进行了测试,结果显示非常精确,变换在不同高度下也依然能正确地处理高亮判断。

另外,我们还做了一个进一步的验证,就是看看在对 Z 做乘法变化(也就是说,对实体的位置进行缩放或堆叠操作)时,系统是否还能保持正确的行为。从反馈来看,确实没有问题,实体依然能够被精确高亮,说明当前的坐标计算和反投影逻辑对这种空间变化处理得很好。

接下来我们进行了更多尝试,比如在空间中上下移动多个实体,在不同层级进行操作。从表现来看,这些交互都运行良好,感觉相当不错,整体功能也逐渐完善。

总结来看,目前这套实体交互系统的表现是令人满意的,无论在空间变换还是定位精度方面都符合预期。后续可以继续基于这个基础,扩展更多高级交互或视觉反馈功能。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

实现具有清晰概念的真实投影与反投影

总的来说,我们对目前的实现方式并不太满意。我们认为应该把一个更正式的“真实投影和反投影机制”的设计列入待办事项清单。虽然这件事并不一定要立刻完成,也许可以等到后续开发中再处理,但我们觉得必须要建立起一种更加严谨的处理方式,明确“project(投影)”和“unproject(反投影)”的具体含义和行为。

如果最终必须保留现在这种实现方式,也不是完全不能接受——只要能够对其进行详尽清晰的文档说明,彻底定义“2.5D”系统中坐标转换的语义和规则,我们也可以接受这一处理思路。

关键是需要明确标准,让整个系统逻辑更加严格和一致,避免在后续开发中产生混乱或误用。这就是目前这部分的全部内容,我们已经把这个问题记录下来,作为将来需要改善的方向。

game.cpp:仅在鼠标悬停在实体上时推送轮廓

在这个特定的情况下,我们采取了一个更合理的处理方式:仅在某个实体被高亮选中时才绘制它的矩形轮廓。这种做法比之前始终绘制要更加理性和清晰。因此我们调整了逻辑,让游戏在正常运行时不会频繁渲染不必要的调试信息,而只有当我们需要突出显示某个实体时,才会绘制出对应的碰撞体轮廓。

这样处理之后,我们可以在游戏正常进行的同时,灵活地使用鼠标移动来高亮显示不同的实体,观察它们的碰撞体信息。

接下来,我们准备进一步处理一些功能。

在这里插入图片描述

在这里插入图片描述

game.cpp:手动提供 introspect HOT_ELEMENT的功能

我们希望实现一个更直观的方式来显示实体信息。虽然我们之前并没有为实体实现过太多复杂功能,但现在希望加入一种调试辅助的展示方式:当我们移动光标到某个实体对应的体积区域上时,可以自动显示该实体的详细信息。

为此我们打算引入一种“热元素(hot element)”的调试机制,也就是说,在调试过程中临时记录下当前被选中的实体信息,并在之后统一渲染出来。我们会在代码中设置一个变量(如 DEBUG_DEGIN_HOT_ELEMENT),用来指向当前选中的实体指针或索引,然后在渲染流程中调用渲染逻辑,将其对应的调试信息打印出来。

由于模拟区域(Sim Region)中实体的生命周期是短暂的,仅存在于模拟执行期间,所以我们不能推迟对实体状态的捕捉。因此,不能简单地存指针,需要在调试的热元素记录中立即复制实体的所有关键状态,包括:

  • 实体类型
  • 存储索引
  • 可更新状态
  • 标志位
  • 可行走高度
  • 生命值数组(hitpoints)
  • 其他相关字段

这些数据需要在被选中时立即提取出来,否则模拟过后实体就会消失,调试信息也就失效。

由于当前使用的 C 或 C++ 语言并不支持反射和自动结构体遍历,因此必须手动书写输出每一个字段的代码。这导致每次要调试一个复杂的结构体时都需要重复造轮子,非常繁琐。虽然可以通过元编程系统缓解这一问题,但目前仍未普遍引入,因此暂时只能手动列出所有字段,并按需组织成数组、属性名等形式来显示。

在 hitpoints(生命值)字段处理时,还需要遍历数组并显示每一个生命值项,同时借助 debug_begin_array / debug_end_array 辅助函数组织好结构。

虽然目前只是一个调试界面的构想,计划后续再真正实现,但基础结构已经明确并准备好 stub(占位实现)。接下来还将继续完善 unprojected 相关逻辑的功能支持。整个目标是构建一个更好用的调试工具,便于实时查看游戏中的实体状态,增强可视化调试效率。
在这里插入图片描述

game_platform.h:#define 所有这些新的DEBUG宏

在进行调试时,首先需要进入到游戏平台的调试代码区域。在这个区域中,所有与调试相关的代码都集中在一起。接下来,我们会添加一些预定义的宏(如 #define),用于暂时创建一些调试结构和占位符。这些宏目前不会实际执行任何功能,只是作为框架来编译代码,确保在后续实际实现时不会出错。

这些宏的定义完成后,它们将不会对程序产生实际影响,只是占位待激活。等到我们真正需要启用这些调试功能时,只需激活这些宏即可开始查看调试信息。通过这种方式,可以确保代码在构建时不会被跳过,调试框架得以准备好,待后续进一步完善和启用。
在这里插入图片描述

运行游戏并看到我们仍然在高亮显示,但调试菜单不再工作

目前,鼠标高亮功能已经启用,但这些调试调用并没有实际的效果。接下来计划是在明天使这些调试功能真正生效。

另外,旧的调试功能不再工作了,无法再像之前一样点击这些调试元素。其原因是我们更改了鼠标输入的处理方式,彻底改变了鼠标输入的实现方式。这种变化实际上是一个好的改变。通过查看鼠标输入的处理逻辑,可以看到我们是在特定的地方创建了鼠标的坐标(mouse P)。这实际上只是鼠标坐标的初始化,之后的传递和使用都是通过其他部分进行的。

这种改变并不完全符合预期,因为所有的绘制操作发生在正交投影中,且我们之前设置的投影参数没有考虑到这种情况。如果当时选择了更宽泛、更拉伸的投影方式,那么即便是修改鼠标输入的实现方式,绘制操作仍然会按照之前的方式进行,导致鼠标位置的计算结果错误。
在这里插入图片描述

在这里插入图片描述

game_debug.cpp:对MouseP进行Unproject

实际需要做的是将鼠标的速度转换为反投影(unproject),也就是根据当前的变换(transform)将鼠标的位置从屏幕空间转换回世界空间。这是希望实现的目标。

当前的实现中,由于我们没有正确实现反投影功能,所以运行时会触发断言错误。为了让系统正常工作,应该立即实现这个功能,这样一来,所有的操作都能通过统一的路径来执行。不管是在调试时使用正交投影叠加显示,还是在投影情况下渲染世界空间,最终都应该调用反投影函数,将鼠标位置转换回渲染的世界空间。无论是正交屏幕空间还是透视世界空间,使用这种统一的方式可以保证一致性和正确性。
在这里插入图片描述

在这里插入图片描述

game_render_group.cpp:计算UnprojectXY

在此过程中,目标是为正交视图(orthographic views)重新计算转换。首先,检查当前的实现,发现我们实际上是复制并粘贴了先前的代码。这段代码与之前使用的代码完全相同,唯一不同的是,meterstopixels 在此情况下被定义为1,这也正是之前调试时没有效果的原因。

在调试时,我们没有使用正确的转换因子,因此鼠标的位置没有正确转换。为了修正这一点,需要乘以 meterstopixels 的逆,即通过反转之前的计算过程,解决数学公式。通过这种方式,可以得到正确的反投影结果,最终得到我们想要的鼠标坐标(X 和 Y)。这就是反投影的核心实现。

经过这一步,就能够正确地获取鼠标位置并进行对象选择。
在这里插入图片描述

在这里插入图片描述

运行游戏并看到我们的选取功能恢复

现在,整个系统正在逐步构建成一个完整的调试界面系统。虽然目前已经能够正确处理一些基本操作,但仍有一些问题需要进一步改进。例如,调试菜单上方时,我们不希望选择到被菜单遮挡的敌人等对象。为了防止这种情况,需要做一些调整,确保在调试菜单或界面上方时,用户不会误选被覆盖的元素。

这种调试界面系统本质上非常复杂,涉及到多个层次和细节的处理,确实需要一些时间才能完善。不过,一旦完成,就能为调试过程提供非常稳定和高效的支持。随着系统的逐步完善,功能也会逐渐变得更加完整和一致。

我不太清楚为什么不直接像在3D中一样做Z数学运算

不太明白是什么原因让不能像在3D中那样准确地处理Z值的数学运算。似乎在处理过程中,有一些限制或考虑因素让无法直接按照3D中的常规方法来做。需要进一步理清这些问题,找出影响的原因并解决。

Blackboard:Z的概念化

在3D中,所有的点在世界中的投影方式是比较直接的,所有的物体和它们的坐标都可以通过变换进行处理,Z轴的处理相对简单,XYZ轴都按照相同的方式来处理。然而,在2D情况下,处理Z值就变得复杂了,因为在2D中,物体在屏幕上的投影和它们在三维空间中的位置不同。

举个例子,当一个人物的头部和身体都位于同一位置时,基于3D的投影方法会根据人物与相机的距离以及它们的相对位置来计算Z值的影响。但是如果我们使用简单的2D方法来绘制这些元素,我们就会遇到问题:当从上方看到人物时,人物的头部和身体看起来应该是重叠的,然而3D的处理方法无法直接应用,因为它忽略了物体在Z轴方向上的实际位置。

为了处理这个问题,需要将Z值理解为两个部分:一个是物体相对于地面(碰撞检测等用途)的Z值,另一个则是物体在2D图像中的Z偏移量。具体来说,物体在3D世界中的位置需要按照实际的Z值来进行变换,而物体的各个部位(例如头部、身体等)的Z偏移则要通过2D处理,不能简单地经过任何其他变换,只需要进行必要的缩放操作来调整物体的大小。

这就导致了一个两步的变换过程:物体的基础变换(包括X、Y、Z坐标)是按照实际的3D变换来处理的,而物体各部件的Z偏移需要通过2D方式处理,而不是直接与物体的其他变换参数结合起来。这也是导致问题出现的原因之一,因为在先前的代码中,忘记了这种2D处理方式,导致传递的Z值并没有按照预期处理,错误地认为Z值会影响到物体的位置,实际上它只是一个偏移量。

通过这种方式,解决了Z值处理的问题,使得2D绘图和碰撞检测能够正确地工作。

你打算在哪里绘制实体属性的标签?

标签会被绘制在调试层级中。调试层级用于组织和展示与实体相关的所有信息,包括实体的属性和其他调试信息。这些标签将以一种结构化的方式显示,以便于调试和查看每个实体的详细信息。

你决定明天的直播要做什么“很棒的事”了吗?

对于明天的直播,决定做什么事情有些犹豫。虽然有些想法,但最终觉得还是应该继续原定计划,保持专注,按照之前的安排来进行工作。

好奇你决定自己设计游戏引擎,是为了提高自己的技能,还是对过去看到的中间件解决方案没有信任?

决定自己设计一个游戏引擎的原因,主要有两个方面:一是提升自己的技能水平,二是对于过去使用过的中间件解决方案存在一些不信任的原因。虽然有些功能,例如实时编码调试等,是现有中间件没有的,但中间件在许多方面仍然不够灵活,例如在Unity中,整个游戏的流式加载并没有被原生支持,开发过程中需要处理复杂的包管理,这使得开发过程变得繁琐。因此,虽然使用像Unity或GameMaker这样的现成引擎可以更快速地开发游戏,并且节省了大量的开发时间,但对于那些想要做出创新并推动边界的人来说,使用自定义的引擎可能会更适合,因为现有的引擎通常无法满足这些需求。

例如,当前在工作中使用的Melly Rocket项目,与Unity或Unreal都没有直接关系,因为所做的工作与这些现有引擎的功能完全不同。换句话说,使用这些引擎只会浪费时间并增加额外的成本。因此,在某些特定情况下,开发自定义引擎可以避免使用现有引擎所带来的各种局限性。

此外,开发自己的引擎也有其他目的。一个原因是鼓励那些对编程有兴趣的人参与其中,展示其实这些功能的实现并不复杂。通过这样的方式,可以让更多人认识到,自己设计引擎或开发插件并不像看上去那样困难,并且能够激励人们创造一些独特的功能或者为现有的引擎做出有意义的扩展。例如,开发一些Unity插件,或者为Unreal引擎做一些增强,使得它能做现有引擎做不到的事情。

最终,了解引擎的工作原理,哪怕是在使用现有引擎的情况下,也是非常有价值的,因为这能让你清楚地知道如何在这些工具上进行深度定制,做出独一无二的创意。

你玩过《魔兽争霸3》或其中的任何模组吗?我需要一些帮助来复制游戏中的一些移动机制,但是没有人理解我描述的内容。。其实我认为这应该是个简单的事

对于这个问题,回答是并没有玩过《魔兽争霸3》,曾经玩过《魔兽争霸1》和《魔兽争霸2》并且都通关了,但《魔兽争霸3》没玩过。玩过这些游戏后,感到有些疲惫,因为已经玩过了很多即时战略游戏,包括《命令与征服》系列、《魔兽争霸》系列、原版《星际争霸》以及《全面战争》等等。玩这些游戏的时候已经很投入了,到了《魔兽争霸3》出来的时候,觉得已经对这类游戏产生了厌倦感,所以没有继续玩下去。虽然《魔兽争霸3》可能是其中最优秀的一款,但当时已经对这类游戏感到厌倦,因此没有再继续参与其中。

如果以完整的3D渲染并旋转所有精灵(例如,按摄像机角度旋转),会有效吗?

讨论的是如何将2D精灵渲染为3D效果,尝试通过调整相机角度并旋转精灵来模拟3D效果。提到了一些关键点:

  1. 由于通常涉及的是3D项目,因此不太了解2.5D的实现方式。实际上,当前正在进行的项目中也有类似的需求,但并不完全是2.5D风格,而是更接近传统的2D和3D元素的结合。

  2. 在处理这种2.5D效果时,可能需要将精灵的角度调整为与相机角度一致。例如,若将相机角度设置为45度,精灵也需要旋转45度,这样视觉效果才会正确。如果只是将相机设置为俯视图(Top-down),而旋转精灵,则会导致精灵被压缩,从而产生不想要的效果,尤其是在正交视图下。

  3. 结论是,相机和精灵需要保持一定的关系,角度应对称。比如,如果相机是45度角,精灵也应旋转45度,这样才能保证渲染效果自然。而如果相机角度是80度,精灵则需要以20度的角度进行旋转,总之,它们的角度必须相加到90度,保持相对一致。

我理解艺术是以3/4视角绘制的。相机是倾斜角度还是俯视角度?

讨论的是游戏的视角设计,特别是参考了《塞尔达传说》和《以撒的结合》这类游戏的设计理念。以下是具体总结:

  1. 游戏视角:目前的游戏采用的是类似《塞尔达传说》地下城的视角,主要是俯视图(top-down),即相机指向正下方。这种视角让玩家能看到角色和环境的顶部视角,符合经典的2D游戏风格。

  2. 精灵方向:尽管相机是俯视的,精灵(角色和物体)的方向并不是直接从上方看,而是稍微调整,使得它们看起来像是面向玩家。这样做主要是为了视觉上的吸引力,尽管理论上可能并不是最符合现实物理规律的做法,但这种做法在玩家中是可以接受的。

  3. 设计灵感:这种设计灵感部分来源于经典的《塞尔达传说》以及其后期的《以撒的结合》。这两款游戏采用了类似的视角,尽管在精灵的朝向上有一些不一致,比如有些物体是俯视的,而有些物体(比如岩石)是侧视的。玩家的大脑会自动适应这种不一致的视角,通常不会觉得不自然。

  4. 游戏设计的合理性:尽管这种方法在视觉和物理上并不完全一致,但它能够为玩家提供清晰的游戏视角,且不影响游戏体验。因此,设计者认为这种方式是合理的,并且自己也很喜欢这种经典的风格。

  5. 对比3D视角:对于3D版本的《塞尔达》,它采用了3D角度,这意味着玩家可以从不同的角度观察世界,而不是仅仅从正上方看。虽然3D视角提供了更多的自由度,但这种俯视图的2D风格依然有其独特的魅力和可行性。

3DS上的《塞尔达》游戏采用了3D以角度显示的2D

讨论的是2D和3D视角的选择,尤其是在构建游戏时如何选择视觉风格。以下是总结的要点:

  1. 对2D与3D结合的看法:对采用2D精灵但从稍微倾斜的角度呈现(如伪3D效果)并不是特别喜爱,认为这种方式有些怪异。主要的原因是这种方法会让视觉呈现有些扭曲,虽然不排斥这种风格,但感觉它有点不自然。

  2. 对完全3D的倾向:如果要在游戏中呈现这种略微倾斜的效果,考虑到实际构建几何体的复杂性,认为不如直接选择完全的3D方式,这样反而更加符合逻辑。对于那些试图模仿3D效果的2D图形设计方式,认为可能并没有什么额外的好处,反而增加了复杂性。

  3. 选择3D的优势:如果已经决定要构建这种具有几何体感的游戏世界,那么直接选择3D进行开发更为合理,省去了在2D图形中进行模拟的麻烦。3D方式能更加直观地表达空间关系,避免了不必要的视觉错乱。

总的来说,认为如果要表达空间感和视角的变化,直接选择3D会更合适,因为这能提供更自然和一致的视觉效果,而不是将2D图形扭曲成伪3D效果。

我正在制作一个完整的3D RTS,使用带有操纵杆的鼠标,这样可以通过操纵杆调整视角,同时仍然使用鼠标进行单位选择。你认为这是一个无法实现的产品吗?如果你为了玩一个游戏需要买一只新鼠标,你会说“不”?

讨论的是是否制作一个3D即时战略游戏(RTS),该游戏结合了鼠标和内置摇杆,允许玩家通过摇杆调整视角。以下是总结的要点:

  1. 关于外设的看法:提到这种设计方案时,认为大多数情况下,想要让玩家购买外设是一个困难的任务。玩家通常更倾向于使用已有的标准设备,如鼠标和键盘,而不愿意购买额外的硬件,尤其是如果这种硬件不具有广泛的普及性和必要性。

  2. 对这个设计的理解:从编程的角度来看,这个问题并不涉及到技术上的难题。更多的是关于用户体验和硬件支持的问题,而这些问题往往涉及市场接受度和玩家对新外设的兴趣。虽然这个设计有趣,但能否成为一个受欢迎的产品,可能并不是程序员能决定的。

  3. 对游戏产品设计的建议:虽然这种鼠标和摇杆结合的方式听起来有趣,但是否能成功作为一个产品推出,更多地取决于市场需求和玩家的接受度,而非技术实现本身。

你最终玩了《母亲》系列吗?它仍然在你的待办事项中吗?

讨论到玩《Mother》系列时,提到这个游戏系列一直在待办事项清单上,已经有很长时间了。虽然目前没有时间去玩,但一旦有空闲时间,确实很想去尝试。然而,由于时间有限,这可能永远也无法实现。如果有机会,肯定会尝试玩这个系列。
在这里插入图片描述

我跳过了100集,所以不太明白调试选择器的问题。是只有命中框深度的问题,还是位置也有问题?

在讨论调试选择器的问题时,问题并不在于碰撞盒的深度,而是与Z轴的处理有关。实际上,所有的操作都正常进行,但问题出在绘制时,我们传递的Z值被丢弃了,而这正是它应该被丢弃的地方。虽然这种行为是符合预期的,但它仍然显得有些误导。至于用户列表的问题,原本是可以正常工作的,但现在似乎出了问题,原因不明。总的来说,所有的疑问已经解决,问题也得到了澄清。


网站公告

今日签到

点亮在社区的每一天
去签到