发现一个vscode Log 断点的用法
回顾
我们正在继续推进工作,之前做了一些测试和清理工作,但还有一件事没有完成,因此我们还没有完全回到功能平衡的状态。昨天我们已经为实体做了空间划分,所以接下来的目标是继续完成这部分工作,并将之前黄色矩形替换成实际的树木或其他更好的图形。我们打算利用这一时间完成其他任务,因为有时得顺应流程。
我们之前离开的地方是在编写代码时,还没有为实体进行空间划分,虽然我们已经开始着手处理这个问题,但实际的代码还没完成。现在需要继续编写实际的空间查询代码,这些查询应该能够通过我们的空间分区加速。现在正是开始真正使用这些分区并进行实际操作的时候了。
game.cpp: 按块加载实体,而不是按瓦片
我们之前的做法是遍历世界中的所有瓷砖,检查这些瓷砖是否位于摄像机的视野范围内,然后将这些瓷砖加入工作集。现在,我们打算改变方法,改为按块进行操作。具体来说,我们希望检查哪些块与摄像机的视野重叠,然后将这些块加入工作集。
虽然现在这样做已经足够了,但未来我们可能会更精细地检查每个块内的实体,看看它们是否与摄像机的视野匹配。对于块的操作,我们希望避免频繁的移动,因为这样会导致不必要的性能消耗。因此,我们应该更加仔细地规划这一过程,确保在需要时才进行块内实体的检查。
为了更清楚地表达这一点,将会画一个图表来帮助说明。
Blackboard: 将实体拉入工作集
在当前的工作中,我们设想了一种情况,其中摄像机的视野范围是一个矩形,围绕着摄像机的位置展开。我们希望通过块的方式来处理这些矩形区域,并确保每个块与摄像机的视野对齐。
目前,我们的目标是确保所有与摄像机视野重叠的块都会被拉入工作集。当我们将实体加入工作集时,我们会跳过那些不在摄像机视野范围内的实体,从而避免将不必要的实体加入进来。
我们不希望频繁地将实体在不同区域之间来回移动,因此要确保我们的检查与对齐方式一致,这样可以避免不必要的性能损耗。当前的重点是通过保守的覆盖范围来处理每个块,确保那些重叠的块被处理并加入到工作集。
此外,当前的代码只是一个初步的实验,目的是确保流程的正确性,而不是立即优化代码的效率。因此,我们不进行大量的测试,而是确保每个模块都能正常连接流动,待流程正确建立后,再开始对每个部分进行更详细的实现和优化。
在未来,只有当我们确认这些工作流程已经稳定后,才会花时间进行优化和细化,否则我们可能会因为不确定是否需要调整结构而浪费时间。
game.cpp: 解决 CameraBounds 问题
首先考虑了将相机位置映射到不同的区域。需要做的就是将相机的界限映射到一个块空间中,具体来说是计算每个块的偏移量并检查哪些实体位于这些块内。
首先,我们需要确保相机位置更新正确,并根据相机带的矩形边界来调整相应的实体。我们通过循环检查这些块和其中的实体,确保每个块内的实体都能正确更新和加载。代码实现上,使用了循环来遍历块的x、y坐标并检查这些块是否包含相机视野中的区域。
为了高效管理块和实体的访问,使用了映射方法来获取每个块的位置,并在每个块内处理实体。如果一个块中没有实体或没有必要的更新,我们允许跳过该块,避免不必要的操作。
在具体代码实现时,采用了一个for循环来遍历每个块并提取其中的实体。每当遇到下一个块时,系统会继续遍历直到找到需要更新的实体。该方法通过映射和检查每个块的范围来确保正确加载和更新实体,避免了对无效区域的处理。
通过这样的循环处理,我们能够有效管理相机视野中的所有实体,确保只有视野内的实体被加载和更新。
创建一个低级别调用的 MakeEntityHighFrequency 版本
引入 GetCameraSpaceP
-
内联操作:首先提到了一些内联操作,意思是将某些处理直接嵌入到流程中,而不通过独立的函数调用,这样可以提高效率,避免过多的层次调用。
-
低实体和摄像机位置:在处理低频实体时,这些实体被传递并且可能需要通过指针进行操作。这些低频实体的处理涉及到计算摄像机的位置或某些其他与之相关的空间信息。
-
计算空间:提到了通过摄像机的位置来计算空间间隔,之前的某些计算(可能是关于摄像机和实体的距离)被嵌入到当前的操作中。
-
游戏状态:游戏的状态管理非常关键,需要根据摄像机位置和当前的游戏状态来执行相应的计算和调整。
-
简化的调用:其中一部分内容提到的是简化一些较低层次的调用,这可以减少复杂性,确保代码更加简洁和高效。
-
结果和调试:最后,提到这些调整会产生特定的结果,并且会将这些操作转化为实际的效果,通过视觉或状态来验证。
继续编写这些版本的 MakeEntityHighFrequency
在 SetCamera 中使用这些函数
以下是对上述内容的详细总结:
-
分解成多个部分:系统现在已经被分解为多个部分,这使得操作更加清晰和易于处理。每一部分的检查和计算都被单独进行,从而提高了灵活性和处理的简便性。
-
抓取相机间隔:在完成所有必要的检查后,可以准确获取到相机的间隔(即相机空间的p值)。这一步是对相机的状态进行的检查,以确保后续操作能够基于当前的相机位置进行。
-
低实体的操作:在完成相机间隔的计算之后,系统会抓取低实体,并且使用之前的检查(比如频率和面积检查)。这些检查确保了低实体的有效性和它们是否符合后续操作的条件。
-
检查矩形:对实体进行检查时,特别是检查它是否位于指定的矩形内。这个检查确保了实体的位置和空间关系符合预期,从而可以确定它是否可以进入高层空间。
-
高层空间的转移:如果低实体符合条件,它将被转移到高层空间。这意味着实体从低频状态被提升为高频状态,准备进行更高层次的处理。
-
避免重复计算:为了提高效率,已经计算过的值(比如相机空间p)不会再次进行计算,避免了不必要的资源浪费。
-
映射到区块空间:在整个处理流程中,空间的映射过程会将物理空间转换为区块空间(chunk space),使得后续的空间管理和操作更加高效。
-
最大角的获取:此后,系统会获取最大角,确保空间的管理和操作能够涵盖整个区域,处理过程完整且精确。
-
低频与高频的顺序:在整个操作中,低实体是先处理的,而高频实体则在确认低实体符合条件后被处理。这样确保了从低频到高频的顺序转换符合逻辑。
game_math.h: 引入 GetMinCorner, GetMaxCorner 和 GetCenter
-
矩形的定义与处理:在数学库中,矩形被定义出来,虽然目前对这些矩形的处理可能并不复杂,但通过这种方式能提供一定的灵活性。尽管这个处理看起来比较基础,但其主要目的是为了方便后续的操作。
-
获取矩形的角落:为了更好地处理矩形,首先需要获取矩形的主角和最大角。获取这些角落的数据将帮助进行后续的空间计算和处理。
-
代码的简洁性:提到在代码中直接提取矩形的角落信息,可以使得代码更加清晰易懂。这样做的目的是为了让代码结构更加简洁,方便开发和调试。
-
矩形的中心计算:通过获取矩形的最小角和最大角,可以计算出矩形的中心位置。矩形的中心是通过最小角和最大角的中点来计算的,这可以通过将两者的值相加并除以2来得到。
-
增加灵活性:通过这种方式来计算矩形中心,提供了更多的灵活性,使得在后续的操作中能够方便地使用这些矩形数据。
-
优化后的处理:使用这些方法后,可以有效地进行矩形数据的处理,使得开发人员可以在后续的操作中灵活地获取和使用矩形相关信息,从而提升整体代码的可扩展性和可维护性。
总结来说,这段内容强调了如何通过定义和处理矩形,特别是通过获取矩形的角落和中心位置,来提高代码的灵活性和可操作性。这种方法使得后续的空间计算更加直观和高效。
调试: 单步调试查看发生了什么
在当前的工作中,可能存在一些调试工作需要完成,因为昨天写了很多代码,而今天虽然代码量不大,但仍然做了大量的编写工作。在这个过程中,可能会遇到一些bug,因此需要逐步排查,确保问题得到解决。
首先,需要进行断言检查,确保插入操作的正确性。问题可能出现在尝试将低频实体转换为高频实体的过程中,而这一过程本应进行检查以防止错误。存在疑问的是,为什么这个操作会生成高频实体,而实际情况可能并不是预期的。
需要关注的关键问题是为什么会发生这种情况,并通过逐步分析来找出原因,特别是函数在执行过程中是否做了正确的检查,确保实体的索引和转换操作没有问题。
game.cpp: 修复 MakeEntityHighFrequency 中的断言
经过一段时间的调试,发现问题并不是一个bug,而是一个断言没有通过。原本以为有一个bug存在,但实际情况是断言失败,导致错误发生。问题出在试图断言某个值为零,而实际操作中并不需要这个值是零,因为后续会创建一个新的值。这是导致问题的根本原因,解决方案是确保断言的正确性。
查看 AddWall
当前的任务是调试空间划分系统,确保实体能够正确地进入高频空间。首先需要检查空间划分的实现,确认实体是否按预期正确添加。虽然实体应该成功加入系统,但它们可能被添加到错误的位置。因此,需要验证空间划分是否按预期工作,确保实体被放置到正确的位置。调试过程中也要检查是否存在其他类似的错误,确保整个过程的正确性。
game_world.cpp: 查看 Subtract 和 ChunkPositionFromTilePosition
在调试过程中,首先确认了是否有其他事情遗漏。接下来,检查了在进行某些操作前,是否可以先查看一下Subtract的实现。通过分析差异计算函数,确认了Subtract操作是如何进行的。具体来说,Subtract的函数通过计算块位置与其他坐标的差异来得到结果。操作的关键在于通过匹配块与米的大小,再将差值乘以合适的尺度。这个操作符合预期,结果看起来是正确的。
调试: 单步调试进入 AddWall
在调试过程中,首先检查了实体类型墙的添加过程,并确认墙的位置是零零零,这非常方便,且没有改变现有代码。接着,我们试图确定该实体在块中的位置,预期结果为零零零,偏移量也为零,但对于这种结果是否合逻辑,有些疑问。可能存在一个问题,即瓦片可能会偏离中心,尤其是在收缩的边界时,因此需要进一步考虑瓷砖或块的对齐方式。为了确保对齐问题的连贯性,在继续之前决定在代码中进行规划和标注。最终,偏移量计算为零,实体位置和宽度高度也设置完成。
game.cpp: 在 AddLowEntity 中调用 ChangeEntityLocation 并传递 world_position P
在当前的工作中,发现一个问题:实体应该被插入到空间分区中,但似乎没有进行这样的操作。空间分区需要显式调用,以便实体能够正确地添加到其中。这是因为空间分区不会自动感知位置的变化,因此必须手动调用并确保每个实体都被插入到正确的位置。
解决方案是,当添加一个新实体时,必须将其位置传递给空间分区。这是一个基本要求。为了实现这一点,可以将实体的位置提供给相关的空间分区函数,并确保每次添加实体时,都按照预期处理它们的位置。这样,实体就可以在正确的位置开始工作,并与空间分区交互。
通过这种方式,能够确保所有的实体都正确地映射到空间分区中,从而避免了位置管理的遗漏。
将 world_position P 传递给 AddPlayer,并完成空间分区的实现
在当前的设计中,发现了一个问题:在添加实体时,需要将其位置正确地传递给空间分区。为了处理这个问题,我们决定在添加实体时,将它们的世界位置传递到空间分区中。这对于玩家和其他实体都适用,确保它们被正确地处理和更新。
此外,当实体的状态发生变化时,特别是玩家的移动时,我们需要确保实体的位置能够更新。具体来说,当玩家移动时,实体的位置会发生变化,这时我们需要调用更新函数,并将实体的新位置传递给相应的系统。
目前,唯一能移动的实体是玩家,因此,更新动作主要是针对玩家的。当玩家位置变化时,我们需要更新其在空间中的位置,并将其传递给空间分区系统。此外,我们还发现,当前的方法存在潜在问题,尤其是对于新的实体,可能没有正确处理它们的相关状态。因此,在处理这些实体时,需要确保每次都有正确的处理逻辑,尤其是在位置和状态更新方面。
在实际操作中,我们需要决定如何处理低频实体,并确保在某些情况下这些实体不需要额外的分配或处理。例如,可以通过某种方式跳过低频实体的分配,避免不必要的计算,从而提高效率。
总体来说,当前的目标是确保实体在正确的位置,并确保所有必要的更新和分配步骤都得到妥善处理。同时,需要避免冗余的操作,尤其是在处理低频实体时。
运行游戏并查看发生了什么
还是没有墙
不知道什么原因只能继续往下看
当前的空间分区功能尚未完全深入研究,也未彻底排查其中的错误。我们只是试图理清当前的状况,但仍然有一些问题需要解决。尽管如此,我们的进展有所改善,这是一种积极的信号。
目前的一些观察包括:
- 瓦片未正确显示在预期的位置。这表明可能存在逻辑上的缺陷。
- 在测试时,画面显示的瓦片排列存在明显错误,其中一些瓦片未按预期出现。
- 当尝试调整视角时,未能正确渲染实体。这进一步表明渲染逻辑存在问题。
初步判断,以上问题可能是系统逻辑中的缺陷导致的,因此需要检查相关的逻辑实现。
- 首先,需要分析瓦片渲染的位置为何与预期不符。
- 其次,检查渲染实体的逻辑,特别是实体显示是否被错误的逻辑屏蔽或遗漏。
尽管目前存在这些问题,但系统已有部分功能在正确运行。接下来的工作将集中在逐步修复这些逻辑缺陷,确保各部分功能能够正确协作。
在这些问题解决后,功能整体将更加完善。虽然还有一些问题需要解决,但方向是明确的。
调试: 单步调试进入 AddPlayer
我们运行了当前的程序,并尝试在游戏世界中添加一个玩家。游戏世界的初始状态是以摄像机当前位置为基础设置的。检查后发现摄像机的位置稍微偏离了原点,但这没有问题,因此玩家会被放置在当前的摄像机位置。
接下来,我们继续将玩家添加到游戏中。在此过程中,我们准备了相关参数,包括玩家的角色定义。当前的实体数据初始化完成后,系统会生成实体的移情指数(即实体相关的某些指标),由于当前场景中包含了大量的墙壁和其他物体,因此移情指数非常高。此外,游戏中也创建了大量的房间,这是预期中的结果。
最终,我们将新添加的玩家定义为英雄角色,并为对应的实体分配低频状态。随后将继续对玩家实体进行更多的处理,以确保其在游戏中正确运作。
game.cpp: 调整 AddLowEntity
我们决定花时间优化代码逻辑,简化对低频实体的访问流程。当前是通过数组索引反复获取低频实体,但这种方式显得繁琐,因此改为直接通过实体索引访问低频实体,这样可以使代码更加简洁和高效。
在调整过程中,对实体的类型属性进行了赋值和初始化操作,并尝试通过优化后的方式再次访问低频实体,确保新的实现逻辑能够正常工作。同时,还更新了实体的某些指标。然而,在测试过程中,发现了一些问题,比如尝试访问零索引实体时会引发错误,因为这并不是有效的实体对象。
针对这一问题,我们调整了逻辑,避免对无效索引的实体进行操作。完成修复后,再次尝试添加目标实体,确认优化后的代码逻辑可以正常运行。最终,调整的方式使代码更加清晰,同时保证了实体访问的效率和准确性。
调试: 继续调试 AddPlayer
我们对最近的操作进行了检查,发现了一些问题。添加低频实体后发现,程序在执行过程中出现了不该有的情况。在传递了位置参数后,发现位置被错误地操作了。我们意识到这种操作是不正确的,必须避免修改已经传递的参数。
错误的修改行为需要立即修正,因为这会对整体的系统逻辑造成影响。接下来,我们清理了这些错误操作,并再次运行程序以观察后续是否还有问题。
运行过程中,新的问题显现出来。系统在某些地方的块操作似乎不正常,我们开始排查具体原因。块的逻辑处理似乎存在缺陷,因此需要继续深入分析,确认问题根源。我们会针对这个问题进行修复,确保整个系统的运行逻辑正常化。
game_world.cpp: 调整 ChangeEntityLocation
在处理某个函数时,遇到了一些问题,导致无法顺利退出函数。最初尝试通过设定条件来解决问题,但这种方法并未如预期般有效。尝试过的一些做法反而显得过于复杂,导致逻辑上出现混乱,甚至让人觉得有些愚蠢。
随后,决定放弃复杂的策略,转而采用一种简单的方法:当无法找到目标时,直接设置一个标识“未找到”。尽管这种方式看起来不太优雅,也许并不完美,但至少理论上是可行的。
最后,虽然实现的方法并不完美,但至少能够实现预期功能,并且可以继续向前推进。
调试一个Bug
调试: 单步调试 OffsetAndCheckFrequencyByArea 中的循环
目前在调试过程中,遇到了一些问题。虽然已经通过添加低频实体来进行处理,但在检查高频实体时发现依然存在问题。程序并没有按照预期完成整个循环,导致一些实体未正确处理。
问题的根源可能是在移除高频实体时出现了偏差。特别是,在实体从高频状态移除时,相关的计数没有减少,导致系统中仍然存在错误的状态。此外,当实体在创建时,其状态不符合预期,出现了奇怪的行为。
进一步分析后发现,应该始终保证低频实体转换成高频实体时,实体的状态与系统逻辑保持一致。解决方案包括重新审视和调整高频和低频实体的转换条件,并确保相关的计数和状态更新正确。
game.cpp: 在 OffsetAndCheckFrequencyByArea 中添加一个断言
我们正在遍历实体,并尝试对它们进行偏移,以检查它们是否仍然在边界内。过程中发现有一些实体不应该出现在当前位置,因此需要进一步处理。
为了防止类似问题再次发生,我们决定加入断言来捕捉这些情况。具体方法如下:
- 提前处理问题:在主逻辑运行之前进行检查,通过断言确保数据一致性,这样可以更早发现潜在问题。
- 断言机制:在将实体降级为低频实体时,确保关联的数据指针正确对应。例如,断言低频实体的索引能够正确指向与其对应的高频实体索引。
- 命名区分:由于涉及两个不同的索引(高频实体索引和低频实体索引),为了避免混淆,建议对变量命名进行改进,使其更直观地表示数据的来源和意义。
在实现中,我们通过断言验证以下内容:
- 低频实体的引用指针是否正确回指到对应的高频实体索引。
- 索引的一致性是否符合预期。
调试: 单步调试并触发此断言
我们正在处理实体的状态转换时,发现一个问题:有些实体已经移动出当前范围,但仍然保留在高频实体数组中,而它们的高频索引却指向了错误的内容。这表明某些数据指针没有正确更新,可能引发潜在问题。
问题的原因主要在于代码逻辑的复杂性。我们正在处理两种不同的能量状态(高频和低频),这些状态之间的转换增加了系统复杂性,同时需要更加精细的逻辑控制。
设计这样一个系统的原因是为了展示如何解决复杂的场景并构建复杂的逻辑结构。然而,与较简单的实现相比,这种复杂性需要额外的开发和调试时间。任何复杂功能的引入都伴随着一定的代价,例如需要更高的开发精力和时间成本。
关键点如下:
- 问题排查:在代码中发现仍保留在高频数组中的实体,需要检查其状态是否正确,以及是否有错误的指针或索引关联。
- 复杂性的代价:更复杂的系统设计可以提供更高的灵活性和功能性,但这需要付出额外的调试和开发代价。
- 逻辑结构清晰:在复杂系统中,清晰的逻辑和严格的验证(例如断言机制)非常重要,可以有效捕捉潜在错误并确保系统的稳定性。
虽然更复杂的设计会带来挑战,但通过充分考虑和验证,可以实现更强大的功能和更灵活的系统架构。
不过我好像能移动玩家触发断点
game.cpp: 引入 ValidateEntityPairs
我们正在处理实体验证逻辑,目标是确保实体状态的正确性,特别是高频实体与低频实体之间的关系能够准确对应。
为了解决这个问题,计划如下:
-
增加验证机制:引入一个验证函数,专门检查高频实体和低频实体之间的关联性。验证的逻辑是:遍历所有高频实体,确保每个高频实体对应的低频实体能够正确指向它们的来源。如果发现不匹配,则立即捕获错误。
-
函数设计:
- 设计一个验证函数,命名为
ValidateEntityPairs
。 - 该函数的作用是循环遍历所有高频实体,并对每个实体的低频关联进行检查。
- 验证的结果通过布尔值返回,以便在其他地方使用。
- 设计一个验证函数,命名为
-
断言的使用:在代码的多个关键步骤添加断言,验证函数的输出,确保在开发和调试模式下及时捕获问题。这些断言可以在发布模式下移除,不会影响性能。
-
多次验证的策略:
- 在关键的逻辑入口点(如高频和低频实体切换操作之前)进行验证,确保输入状态正确。
- 在复杂逻辑操作后再次验证,确认操作结果没有破坏数据结构的一致性。
-
增量逻辑的完善:注意在循环操作中正确管理索引的增量,确保所有实体都被正确遍历并处理。
通过这种方式,我们能够以较小的开发代价提高系统的健壮性,并且可以更轻松地捕捉和解决潜在错误,特别是在复杂逻辑中。
总结:通过引入验证机制和合理的断言位置,以及对索引增量的管理,可以实现实体系统的可靠性提升。这种方法既保持了复杂逻辑的可操作性,又降低了错误发生的可能性。
触发了断言
调试: SetCamera 中的 MakeEntityHighFrequency 例程有问题
我们发现当前的代码存在问题,具体来说是索引的高频实体对应关系被破坏了。通过验证程序失败的情况,可以确定问题来源于某段代码逻辑。这说明,在高频实体索引与低频实体之间关系发生了错误,导致数据对应关系失效。
接下来需要进一步分析这段代码的具体逻辑,从而找到问题的根本原因。此外,为了避免类似问题的发生,我们可以通过以下方式优化代码设计:
- 防御性编程:在关键部分增加验证代码,以确保数据的完整性和一致性。例如,在数据变更时实时验证索引关系,或者在代码流程结束后进行全局检查。
- 延迟处理方式:防御性编程的设计可以根据需求灵活处理,而不是预先为所有可能的错误添加检查。这种“懒惰评估”的方法,可以减少不必要的工作量,提升开发效率。
- 代码调试与最终优化:在开发阶段,更多时间会用于发现和修复当前问题,而在最终优化阶段,可以加入更多防御性逻辑,以提高代码的健壮性。
总结来说,遇到问题后,我们通过验证定位到了问题的来源,这为后续的修复提供了明确方向。同时,可以在设计中适当加入防御性代码,平衡调试时间和代码维护成本,从而更高效地确保系统的稳定性和可靠性。
game.cpp: 传递正确的索引给此例程
在当前逻辑中,我们通过遍历低优先级实体集合,并逐一检查其对应的高优先级索引是否为零。如果索引值为零,则说明该实体未被关联为高优先级,此时需要判断它是否在某个矩形范围内。如果满足条件,则将其转化为高优先级实体。
问题的来源在于,我们在处理过程中使用了错误的索引值。这导致了实际逻辑中的索引混乱。为了避免这种问题,需要明确区分以下两种情况:
- NDC(Normalized Device Coordinates,标准化设备坐标) 和 实际实体索引 的区分。
- 块内索引 和全局索引的正确区分。
在修复过程中,进一步理清了低优先级实体的索引值,并确保从块中正确提取索引。此外,通过对索引的明确划分,可以准确处理低优先级实体与高优先级实体之间的关系。
最终,通过调整逻辑,问题得到解决。这种修复流程说明了在复杂系统中清晰索引管理的重要性,同时强调了在程序中避免模糊或错误的引用对维护代码稳定性的意义。
玩家不能移动继续找这个是什么问题导致的
没更新相机的位置导致的
发现另外的问题
运行游戏并注意空间分区基本正常工作
目前看起来唯一的问题是空间划分设置不正确,经过调整后,它不再阻碍我们的进程。尽管我们没有做验证工作,但可以确认空间划分至少在名义上是有效的,也就是说,它已经正确设置好。现在可以说我们的代码结构和基础设施已经完成,接下来的重点是处理一些痛点问题,避免将来再次遇到类似的阻碍。
目前,我们不再专注于验证空间划分的有效性,而是更关注于在有限的时间内处理剩余的任务。例如,我们估计还有大约十分钟的时间继续进行工作。这段时间将用于确保系统能够按预期运行。
game.h 和 game.cpp: 测试新资产
接下来,我们将继续测试新的资产是否有效。首先,计划将绿色的元素替换为黄色的树木。虽然目前对于实体的尺寸是否合适并不确定,比如角色仍然过大,但暂时不进行调整,等到真正需要处理实体时再进行修改。
为了进行测试,我们将载入树木的资源,并在测试目录中进行验证。对于实体,如果它是英雄类型,则保持当前的绘制方式;否则,将改为绘制位图。由于目前缺少偏移量,位图会绘制在错误的位置,但之后会根据热点调整正确的位置。
接下来,我们将使用地面坐标和Z轴进行对齐,确保所有的点和位图对齐。虽然现在位图没有显示线条,暂时只能通过声音来确认,之后会处理这些问题,调整位图的对齐方式,以确保它正确显示。
总之,当前的任务是继续绘制树木,并逐步清理代码,使其更加干净和高效。
运行游戏并发现新树木绘制在错误的位置
目前存在一些树,但由于它们被画在了错误的位置,导致难以辨认。为了修复这一点,树应该绘制在正确的位置。此外,可能需要移除背景中的树,这样能提高可视性和效果。这些改动看起来是有意义的,至少从当前情况来看,这些调整是合适的。
game.cpp: 停止绘制纹理背景
当前的任务是修复树的绘制问题。首先,背景不再绘制,接着通过缓冲区进行绘制操作。代码的语法有些混乱,可能需要回到之前的做法,因为之前的方法在编译器中表现更好。计划是将树绘制在正确的位置,首先需要定义树的高度和中心点。最终目标是确保树能够在其正确的中心点位置绘制出来。
资产: 在 GIMP 中找到树木的基点
当前任务是明确树的绘制对齐点,通过打开艺术资源文件进行检查。使用GIMP软件查看树的底边中心点,并确定其像素坐标为 40.5, 79.5。为了方便记忆,将其简化为 40, 80,并设置为树的对齐参考点。这一中心点将作为树在游戏中的底边对齐位置,用于确保绘制的树与地面正确对齐。
game.cpp: 设置树木的对齐方式并进行 DrawRectangle
当前工作是调整树的显示与位置匹配,明确绘制矩形的位置,以帮助观察其实际显示效果。已经确定了树的对齐点,现在在树的下方添加了矩形,以便清楚地看到它的边界范围。
在绘制过程中遇到问题,原因是单位未正确转换——从米转换到像素的关系未应用。为了修正这个问题,需要在绘制时将相关坐标乘以“米到像素”的比例因子。这确保了显示的矩形能够准确反映实际的树形状和位置。
同时,通过缩小绘制的矩形以更好地对比,能帮助清晰地观察各个部分,例如每段墙的位置和大小。调整后的方法能更直观地看到游戏场景中的对象布局和相对位置,从而为后续的改进提供参考。
运行游戏并观察新树木已正确放置
目前系统在实现上显得更具条理性。树木已经被正确地绘制到场景中,虽然现在它们的分布还没有经过优化,但这种布局类似于经典的城镇地图游戏风格。接下来可以考虑未来优化树的分布,使场景更加自然。
矩形的绘制调用可以被去除,因为它只是用于调试显示,现在已经没有必要存在。整个渲染看起来更为直观合理,系统的滑动滚动功能也正常运行,屏幕的基础扩展逻辑得以实现。
未来需要更新屏幕基础功能并对其进行验证。同时,逐步完善空间划分系统的结构,为完整版本的实现打下基础。
接下来可以开始处理敌人的内容,并将其集成到场景中,使所有能量管道的各个阶段都更加连贯,清楚地展现系统的工作原理及其缩放行为。同时还需引入一些简单的机制,例如在场景中放置怪物并对其进行测试。
一个改进方向是实现缩放功能,以便更好地查看和调试场景的整体内容。通过一个像素化的调试视图,可以快速查看所有实体的位置和大小。这对于在复杂场景中排查问题非常有帮助。
暂时可以考虑一种快速导航的功能,使得在世界中可以迅速移动,而不受碰撞系统的限制,这样可以更方便地对场景进行检查。当前的实现可以维持现状,以后再加入此类调试功能。
问: 对于缩小视图,能否通过修改 MetersToPixels 使事物在缩小时变小?
目前的进展已经很好,接下来的一些任务可以留到明天完成。计划包括处理排序功能,以确保所有的精灵能够正确启动,并考虑实现更多功能,例如绘制多棵不同的树,以丰富场景的多样性。当前的状态非常稳定,下一步的方向已经明确。
此外,未来可以考虑引入缩放功能以便更好地调试和观察场景。缩放的实现相对简单,只需在绘图时调整米到像素的比率即可实现。但是当前的渲染系统还不支持精灵的缩放,仅仅通过调整米到像素的比率会导致精灵在视觉上更靠近而不是缩小。因此,全面的缩放功能需要改进渲染系统以支持比例缩放。
另一个需要解决的问题是,目前的渲染仅绘制高频实体(即靠近玩家的实体)。为了更好地调试,需要添加一个循环,绘制包括低频实体在内的所有实体。这样可以显示远离相机的区域内容,而不仅仅是围绕玩家附近的内容。这对于正常的游戏运行是合适的,但对于调试来说不够全面,因此需要通过渲染循环覆盖低频实体来解决此问题。
这些优化将使系统更具扩展性和灵活性,同时更方便进行全面调试和未来的功能扩展。
问: 什么是 inline v2?
关于“内联v2”的讨论明确了其功能,即通过简单的输入创建一个向量。例如,“v2{5 3}”可以表示一个向量,其中x的值为5。然而,这种方式被认为不够直观,也存在兼容性问题,例如对旧版本编译器的不支持。此外,这种方法在美观和代码结构上也显得不够合理,最终决定放弃使用。
这一决定是基于对代码整洁性和可维护性的考虑,因为该方法被认为既冗余又不符合预期效果。放弃后代码库得以保持一致性,同时避免了可能引入的困惑和潜在问题。这种选择反映了对代码质量和设计简洁性的追求。
在其他功能上,系统仍在持续开发中,涉及到跳跃等动态效果的调试和优化。例如,角色的跳跃能力得到了测试,显示出很高的弹跳能力。这一方面需要进一步调整参数以符合预期行为,同时为整体玩法提供更多可能性和灵活性。
通过这些调整和优化,系统将更加稳定和高效,为后续功能扩展打下坚实基础。
问: 为什么使用 Emacs 而不是 Visual Studio 的 IDE?
对于为何选择Emacs或其他编辑器而非Visual Studio,有两个主要原因:
-
跨平台的一致性和熟悉性:
我们在多个平台上编程,而不仅限于Windows。因此,使用支持多个平台的编辑器非常重要,比如在Windows、Linux和macOS上都能无缝运行的编辑器。这种一致性避免了在每个操作系统上都需要适应和学习不同编辑器的麻烦。此外,电子邮件传真等工具使用起来更加直观和熟悉,这使得在不同环境中切换时更加方便高效。 -
动态代码重载的兼容性问题:
在Visual Studio中进行编辑时,需要做许多复杂的自定义操作才能实现动态代码重载的正常工作。例如,需要设置许多定制的构建步骤,而且通常需要对项目进行多次构建才能达到预期效果。这些步骤既耗时又繁琐,极大地增加了开发过程的复杂性。此外,Visual Studio对构建的某些限制和约定也可能与实际需求相冲突,增加了开发者的负担。
相较之下,使用其他编辑器能够直接控制构建过程,只需明确告诉它需要的操作即可,无需应对复杂的限制和规则。这种简洁高效的方式,更加符合需求,减少了不必要的时间浪费。
问: Emacs 比 Vim 更好,还是只是个人偏好?
关于使用某个编辑器的选择,并非完全基于偏好,而是与学习经历和熟悉程度密切相关。以下是详细的总结:
-
学习经历和熟悉度:
最初学习时,接触的是Emacs,而不是其他编辑器。并非有意选择,而是因为那时所掌握和能够使用的工具就是Emacs。当时甚至对Vim或Vi并不了解,也没有机会尝试这些工具。因此,编辑器的选择更多是基于学习路径,而不是经过对比后的偏好。 -
熟练程度:
经过长期使用Emacs,现在对其操作已经非常熟悉,速度也很快。在这种情况下,再去学习和适应其他编辑器(比如Vi或Vim)需要花费额外的时间和精力,即使那些编辑器可能在某些方面更优秀。基于效率的考量,不愿意花时间重新适应一个新工具。 -
效率优先:
即便其他编辑器可能稍微提升速度或者提供某些优势,但这种提升相对于切换工具所需的时间和精力来说并不划算。因此,选择继续使用熟悉的编辑器,而不是迁移到一个可能稍有优势但需要重新学习的工具。 -
对编辑器的客观看法:
对不同编辑器并没有明确的好坏判断,也没有认为Emacs一定优于Vim或其他编辑器。选择的原因完全是基于个人学习经历和熟悉程度,而非技术上的优劣比较。
总结来说,编辑器的选择更多是因为熟悉和高效,而不是刻意比较后的结果。这种选择反映了个人学习路径对工具使用的深远影响。
问: 你认为代码运行速度是否是衡量代码好坏的最佳标准,还是你也看重简洁性或可扩展性?
关于代码性能、简单性和可扩展性的权衡,重点如下:
-
代码运行速度的重要性:
代码的运行速度并不是衡量代码好坏的一个指标,而是一个基本的要求。只有当代码运行速度得到保证后,才会开始考虑其他方面的价值,如简单性和可扩展性。如果代码运行速度无法满足需求,那么无论代码结构多么简单或可扩展,都无法被认为是优秀的代码。 -
简单性与性能的优先级:
性能是最基础的条件。相比之下,简单性和可扩展性是性能保证之后才值得关注的特性。如果代码的性能不足,再简单或扩展性再好也无意义。 -
现代代码的性能问题:
现如今,很多代码的性能远远不足。开发者更多关注开发过程的便利性,而忽视了代码运行效率,导致如今的计算机运行速度比过去更慢,尽管硬件性能不断提升。这反映出在软件开发中,性能已经被严重低估甚至忽视。 -
开发文化的变化:
曾经,高性能代码是一种标准,但如今的开发文化似乎倾向于牺牲性能以追求开发的便利性和维护的简单性。这种趋势需要纠正,至少在当前的环境中,性能应该被优先考虑。 -
优化的必要性:
当前的开发文化还远未达到过度关注优化的地步。实际上,现在需要更多关注性能和优化。对于优化的关注应该增加10倍、20倍甚至更多,只有在整体软件性能得到显著提升之后,才可能开始担忧优化过度的问题。 -
未来的可能性:
如果未来重新形成了一种高度关注性能的开发文化,并且普遍出现了优化过度的情况,那时可以开始讨论在性能和其他特性之间找到更好的平衡。但就目前而言,性能优化应该被大幅度地提升到开发的优先级列表中。
总结来看,性能是代码的基本要求,当前的软件开发文化需要更加强调性能优化,只有在普遍达到高性能的基础上,才会考虑如何在性能与简单性、可扩展性之间找到平衡。
问: 你会觉得看深层嵌套的 for 循环让你烦恼吗?
总结内容如下:
-
高深嵌套循环的处理:
- 嵌套的for循环有时让人感到不耐烦,尤其是当需要处理复杂的代码时。但通常来说,这并不算是个大问题。
-
处理代码的难度:
- 显示窗口变大后,代码的可读性受到影响,导致难以处理和编辑代码。
- 尽管如此,为了能够清晰地传播信息并向观众展示如何实现某些功能,做出这样的妥协是必要的。
-
工作方式的适应:
- 尽管遇到显示和可读性的问题,还是尽最大努力去处理这些困难,确保项目能够按计划进行和展示给观众。
问: 之前看 ROM 破解时,看到寄存器和内存实时更新非常酷。你觉得 C 的调试能否做到同样效果?
-
ROM黑客的实时观察:
- 观看寄存器和内存的实时更新非常酷,给人一种直观感受,尤其是在调试过程中。这种方式能够更清晰地展示程序的运行状态。
-
C语言的“库”:
- 关于C语言的讨论,提到通过某种机制可能能像ROM黑客一样实现对程序运行状态的监控和显示。虽然这种方式可能有用,但实际效果还要取决于具体的实现方式。
-
模拟器的优势:
- 虽然某些工具可能提供类似的功能,但模拟器通常有独特的优势。模拟器能够向开发者展示程序运行的各个方面,帮助发现错误并提供清晰的调试信息,这在调试过程中非常有用。
问: 在写世界生成算法之前,能否先加入一个合适的 PRNG?
-
随机数生成器的使用:
- 目前使用的是一个随机数表,这些随机数的质量很高,可能比自定义的生成器生成的随机数质量更好。虽然使用的是随机数表,但计划将来使用一个更合适的随机数生成器。
-
随机数生成的数量:
- 在处理大规模数据时,考虑到会涉及到大量的随机数,存在一个问题:如何确保随机数的索引不会超过随机数表的大小。这意味着需要确保表的大小足够,以容纳所需的随机数。
-
表的大小与复制次数:
- 不记得实际复制了多少次随机数表,虽然可能复制了超过两千次,考虑到这么多屏幕和数据处理,可能会遇到随机数索引超出表大小的情况。
问: 你是否有需要使用递归函数的时刻?
-
递归函数的使用:
- 虽然递归函数可以很方便地利用堆栈来管理内存,但在使用时会有一些问题。递归函数容易让人对堆栈和内存管理产生紧张感,特别是在处理大量数据或复杂操作时。
-
递归的优点:
- 尽管如此,递归函数的一个优点是它能够快速依赖于堆栈,利用堆栈的自动内存管理功能,使得开发过程更加便捷和高效。
问: 当你说“渲染器”时,具体指的是什么?如果你现在的绘图没有渲染,那么我们从“绘图”到“渲染”是在哪个点发生的?
总结内容如下:
-
渲染的定义:
- 渲染不仅仅是将位图从一个地方复制到另一个地方。真正的渲染是满足一定标准的过程,涉及更复杂的图形操作,如颜色的改变、旋转、缩放等。
-
当前的渲染限制:
- 目前系统只能执行基本的操作,比如将一个矩形复制到另一个矩形,且只能更改阿尔法值。没有能力进行旋转、缩放等复杂的图形操作。
-
渲染的期望:
- 理想中的渲染系统应包括多种功能,如颜色变化、旋转、缩放,并能够进行合成处理,例如处理精灵背后的内容,解决遮挡问题等。这样才能做到更复杂的渲染效果。
-
渲染系统的目标:
- 目标是创建一个系统来处理所有这些操作,而不仅仅是简单的位图复制。虽然技术上这可以称作渲染,但这只是一个非常基础的版本,远不能算作一个完整的渲染过程。
问: 你如何区分很多人所谓的“过早优化”和确保良好设计以保证高性能?这两者的差异似乎很微妙。我觉得为高性能设计就是组织代码并以不拖慢代码的方式编写,避免未来优化的障碍,而过早优化则是因为觉得某个东西会变慢就去优化,而没有证据支持。
总结内容如下:
-
过早优化与高性能设计的区别:
- 过早优化是指在未确定实际需求之前,就开始优化某些部分,认为它们可能会影响性能,但这种优化缺乏实际证据和需求。举例来说,假设在没有明确需要时,过早地投入大量时间去计算最有效的空间分割,仅仅因为认为它会提高性能,这是不成熟的优化。
- 高性能设计则是通过合理组织代码和编写方式,避免不必要的性能瓶颈。这种设计基于对未来需求的理解,而不是单纯的假设。例如,提前知道将会处理大量查询,设计一个基本有效的空间分区结构,从而确保系统能够处理大量数据。
-
空间分区的实际应用:
- 在设计一个系统时,可能会需要实现一个空间分区结构。虽然这个结构可能并不完美,但它是为了应对可能的大量查询而提前设计的。即使当前没有完美的解决方案,知道空间分区结构的基本框架也能帮助确保未来需要时能够适应变化,而不需要重新设计整个引擎。
-
处理细节与避免过度优化:
- 在进行设计时,要了解细节,但不要过度陷入细节之中。重要的是在需要时进行优化,但不要在没有充分理由和证据的情况下过早优化。最好的做法是暴露出所有必要的细节,但避免与复杂的细节过度斗争,直到真正需要时再处理这些问题。
-
总结:
- 高性能设计应该是在知道需求的基础上,进行合理的结构规划和优化。而过早优化是在没有实际需求和证据支持的情况下,提前做出优化,这通常是低效且没有意义的。
问: 你会为这个游戏制作一个关卡/瓦片地图编辑器,以便更轻松地创建关卡吗?
这款游戏采用程序生成方式,没有预设的关卡。每次玩游戏时,关卡内容都会根据程序动态生成,因此每次游戏体验都是不同的,不存在固定的预设关卡。
问: 你有什么算法处理三个或更多实体的碰撞?
讨论中提到的碰撞算法主要涉及到多个实体之间的碰撞检测。对于处理多个实体的碰撞,算法并不需要在每次检测中都严格迭代所有的实体,而是可以在相对较短的时间(如每30秒的1/30秒)内通过高效的方式进行处理。问题可能来自对迭代次数的疑问,理解时需要考虑到时间的限制以及优化后的处理方式。
Blackboard: 每 1/30 秒移动
在讨论碰撞检测时,时间长度(如三十分之一秒,即约33毫秒)在计算中的重要性被提到。对于游戏中的实体,通常的移动距离在这一小段时间内不会太远,因此处理碰撞的迭代次数不需要过多。即便是面对多个怪物,通常只需要处理前四个碰撞检测就足够,因为大部分时间里,角色不会频繁与多个怪物发生碰撞。
在游戏角色的移动过程中,当角色与某个怪物发生碰撞时,移动方向会被截断。如果角色遇到更多怪物,他的移动可能会被完全限制,导致无法继续前进。因此,在某些情况下,避免多次迭代和复杂碰撞处理是可行的,处理过多的碰撞检测可能会被视为过度优化。
总结来说,复杂的碰撞检测迭代并不总是必要,实际处理时可以根据情况简化算法,从而提高效率。
问: 你计划使用某种日志记录进行调试吗?
计划中一旦进入更复杂的调试阶段,日志记录和调试支持会被加入。目前,由于没有遇到很多难以解决的调试问题,尚未添加日志记录功能。现阶段的 bug 通常能在几分钟内被揭示和解决,因此暂时还不需要为调试问题投入过多资源。
问: 当你从 Chunk 列表中移除 LowEntity 时,交换另一个实体进去,这不会影响引用吗?
当从数据块列表中移除一个低频实体时,交换另一个实体到它的位置不会引起引用混乱。因为列表中的所有实体都是通过引用进行管理,而数据块列表只是一个存储这些实体引用的数组。没有直接指向实体的引用,所以这个过程不会影响引用的有效性。因此,应该没有问题。
仓库: https://gitee.com/mrxiao_com/2d_game