游戏引擎学习第54天
仓库: https://gitee.com/mrxiao_com/2d_game
回顾
我们现在正专注于在游戏世界中放置小实体来代表所有的墙。这些实体围绕着世界的每个边缘。我们有活跃的实体,这些实体位于玩家的视野中,频繁更新,而那些离玩家较远的实体则以较低的频率运行,以减少计算资源消耗。我们需要开始优化我们的碰撞检测系统,以便处理这些实体更高效地。例如,即使它们在屏幕外,其他实体也会与它们碰撞。我们正在逐步实现这个系统,尽量减少错误,确保游戏体验流畅。
移除休眠实体的理由
我们开始制作系统,以便当摄像机移动时,我们能够动态地调整实体residence的位置。我们移除了不再需要的高频实体。与此同时,低频实体不再包括在我们模型中,因为它们没有必要的更新频率。我们现在只专注于高频实体,并删除所有休眠实体,简化了我们的实体管理系统。我们认为,这种调整能够减少复杂性,并使我们的系统更加高效。整体上,我们做出的改变使得游戏逻辑更加直观且不需要多余的管理。
关于解耦低实体和高实体数组的想法
我们现在有了一种添加实体的方法,这些实体可以从低状态开始,然后移动到高状态。这意味着大多数实体在世界生成时都是低状态的,而只有少数实体才会转移到高状态。这种分类方法帮助管理实体的活动和模拟,从而提高效率和性能。
我们开始考虑如何更好地划分实体集。一个有趣的思路是,高实体集只包含离玩家最近的有限数量的实体,而低实体则包含更多的远离玩家的实体。这种方式有助于将实体按需要移动到高集,从而优化性能。
另外,我们讨论了在高实体中是否包含低实体的问题。一种方法是复制低实体数据到高实体中,但这可能会增加内存开销。因此,前向和后向指针的方式可能更为合适,即记录低实体是否在高实体中,从而避免过度复制和不必要的内存访问。
总体而言,我们在思考如何管理实体时,正在寻找一种既能保持游戏性能,又能简化实体管理的方法。
默认情况下只在低实体列表中创建对象
我们现在只专注于在低频理想环境中创造一些东西,并开始处理列表中的项目。我们会有一个金融列表,包含高频和低频账户。基本上,我们保持低状态,并且只把低频的数据提升到高频时才使用。为了防止错误,我们需要确保所有索引都是正确的类型。我们应该确保代码路径总是有效,避免在运行时引入错误。如果高频表的空间不足,我们可以降级所有数据至低频。最终,所有操作的目的是将实体从低频提升至高频,确保代码的稳定性和正确性。
关于高实体数组内存的备注
我们在代码中处理了将实体从低频状态转换为高频状态的问题。具体来说:
-
映射到相机空间
我们首先将实体的位置从世界坐标系中的瓷砖空间映射到相机空间。通过计算瓷砖空间与相机位置的差值,我们可以将实体在世界坐标系的位置转换为相机坐标系中的位置。 -
性能限制
我们需要为高频实体设置一个固定的性能预算,不能无限制地增加高频实体的数量。高频实体的数量受每帧CPU可处理的时间限制,意味着这个列表必须是有限的。我们将确保这个限制能够满足实时处理的需求。 -
高频实体的管理
- 转换为高频实体:
如果实体没有高频索引,我们将其添加到高频实体列表中,并记录其新位置、速度和其他属性。同时,在低频实体数据中保存其对应的高频索引。 - 处理高频实体列表:
高频实体列表是有限的,并且用于管理当前活动、接近玩家的实体。我们根据性能预算决定能够容纳多少个高频实体。
- 转换为高频实体:
-
处理低频转换
如果需要将实体从高频状态恢复为低频状态,我们会将其从高频列表中移除:- 列表管理:
如果高频实体位于列表的末尾,我们只需将高频实体数量减一即可。
如果实体不在列表的末尾,我们需要将列表中的最后一个实体移动到被移除的实体位置,以填补空白,确保列表连续。 - 性能优化:
为了保证常数时间的操作,我们考虑直接标记被移除的实体,而不留下空白。我们也可能使用一个“空闲列表”(free list)来管理被回收的位置。
- 列表管理:
-
处理逻辑
我们在编写逻辑时特别关注以下几点:- 确保实体状态转换操作是高效的,避免在高频状态管理中出现性能瓶颈。
- 确保列表操作不会留下空白,从而导致处理时间增加。
- 未来的内存管理优化将主要围绕如何更高效地维护高频实体列表以及内存回收问题。
-
整体策略
- 高频实体的管理:固定大小的列表,用于保存当前需要高频处理的实体。
- 低频实体:主要用于存储不需要实时处理的实体,节约性能开销。
- 性能预算:根据CPU时间限制,动态调整高频实体数量,确保游戏性能稳定。
最终,我们需要通过实时测试确定高频实体的具体上限,以及最优的列表管理策略。
思考使用空闲列表来代替压缩数组
我们在处理实体时,存在一个固定的预算限制,能够处理的实体数量是有限的。例如,如果我们最多只能处理256个实体,那么在迭代这些实体时,我们可以跳过那些被标记为“已处理”的实体。即使跳过它们会造成一定的性能损失,但我们并不关心,因为只要所有实体都已填满,我们就必须在预算内完成处理。
一种合理的方案是维护一个空闲列表,用来追踪已经被移除或不再活跃的实体。当一个高频实体被移除时,我们将它的索引添加到空闲列表中。这样,当需要分配一个新的位置时,只需从空闲列表中取出一个索引即可。这种方法简单明了,且符合实际需求。
另外一种方法是在移除某个实体时,将数组中的最后一个实体移动到当前被移除的位置,然后将实体计数减1。这种方法可以确保数组在内存中保持紧凑,避免留下“空洞”。但这种操作需要执行一次实体数据的复制,并且需要更新指向被移动实体的引用,确保所有引用都正确指向新位置。
两种方法各有优缺点:
- 使用空闲列表:无需移动数据,操作简单,适合场景中不需要数组紧凑的情况,因为我们已经限定了数组大小,确保了处理能力能够覆盖最大需求。
- 移动数组元素:保持数组的紧凑性,但需要进行数据复制和引用调整。
考虑到我们已经有了固定的预算,数组的大小也早已受限,因此保持数组的紧凑性并非必要。从性能和操作的简便性出发,维护一个空闲列表似乎是更合适的选择。
实现数组压缩
我们决定在当前情况下保持数组的紧凑性,以确保数据的完整性和可管理性。
当移除一个实体时,处理流程可以分为两种情况:
-
移除的是数组中的最后一个实体:
这种情况下操作非常简单,直接将计数减1,即可完成移除。 -
移除的不是最后一个实体:
这种情况下,我们需要进行重新映射,通过将数组中的最后一个实体移动到被移除的位置,确保数组不出现空隙。
具体的处理步骤如下:
- 首先,确定数组中被移除的目标实体。
- 如果被移除的实体不是最后一个实体,我们将数组中的最后一个实体移动到当前被移除的位置。
- 移动之后,需要更新对应的索引信息:
- 被移动实体的索引(高实体索引)需要指向新位置。
- 所有引用到该实体的低级实体也需要更新,确保它们指向移动后的新位置。
这一流程可以确保数组的整体紧凑性,同时保持所有索引和指向引用的正确性。
此外,当我们将高频实体转换为低频实体时,需要将高频索引清除。这是因为该实体已经被标记为低频,失去了对应的高频索引。在所有情况下,操作完成后,都会确保高实体数据和低实体索引的一致性。
整体来看,这种方法既能保持数组的紧凑性,又能确保所有实体的引用关系正确更新,避免了出现“悬空引用”或数据不一致的问题。
将 GetEntity 更改为 GetLowEntity 和 GetHighEntity
这段内容探讨了实体管理的实现思路以及操作过程,特别关注“低频实体”和“高频实体”之间的转换与调用逻辑。主要内容总结如下:
-
实体的获取与调用
实体管理的核心是通过方法获取“低频实体”,而不太关注“高频实体”的获取调用。整体设计假定大部分操作是针对低频实体的,“获取高频实体”可能不需要单独实现。这简化了整体逻辑。 -
返回结果的处理
获取实体的方法需要有一个清晰的返回值逻辑:- 如果实体存在,则返回对应实体;
- 如果不存在,则返回默认值,例如 0。
这种方式确保调用方可以根据返回结果判断实体是否有效。
-
索引与结果管理
在管理实体索引时,特别是处理“第一个有效位置”和“最后一个位置”时,需要考虑如何返回正确结果。通过明确地返回索引或者 0,可以简化实体的映射管理。 -
方法的对称性
设计时确保获取低频实体和高频实体的方法是对称的,即两个方法在逻辑上可以互相补充。例如,低频实体的调用与高频实体的调用应该有一致的接口和返回方式,保持设计的简洁性与可维护性。 -
实体状态转换
- 低频实体可以被提升为高频实体。
- 在某些情况下,低频实体可能会替代或填补高频实体的位置。
这种状态转换是实体管理的重要部分,确保资源得到有效利用,并避免无效的操作或重复占用。
-
简化与重构
整体上强调逻辑的简化,移除了不必要的情况和复杂处理。通过“低频到高频”的设计,避免过度关心无关部分,使得管理过程更加清晰明了。 -
灵活性与可扩展性
保留设计的灵活性,允许未来根据需求添加或扩展功能,例如同时提供“低频实体”和“高频实体”的获取方法。当前逻辑确保最基本的需求得到满足,同时具备一定的可扩展性。
总之,这段内容围绕实体的获取、索引管理与状态转换展开,强调逻辑的简洁性和对称性,同时保证返回结果的合理性,最终形成高效的实体管理方案。
将 AddEntity 更改为 AddLowEntity,并更新 AddWall
我们需要实现的是实体的添加操作,现在我们决定始终添加一个低频实体。这意味着,所有的实体都会首先以低频形式存在。在进行实体的添加时,我们需要确保所添加的实体始终符合一定的条件,例如索引范围需要在合法的低熵数组大小以内。
在添加新实体的过程中,首先我们进行断言,验证当前的索引值小于低熵数组的大小,以确保不会越界。接下来,我们对该实体进行初始化操作,确保它处于正确的初始状态。
虽然索引曾经是一个重要的标识,但现在低频实体的索引逐渐显得不那么重要了。在大多数情况下,实体索引对逻辑的作用变弱了,因为我们有了更加统一和抽象的管理方式。这使得外部调用者无需过多关心实体的具体索引,而只需要通过逻辑接口进行访问即可。
然而,仍然有一些情况下,我们需要知道索引的范围,尤其是在处理指针前后引用等操作时。这种情况下,索引信息仍然不可或缺。总体上,这里的实体操作都是基于低频实体进行的,这一层的抽象结构使得实体的管理更加流畅和有序。
我们也在考虑实体访问的效率问题。虽然目前实现的逻辑已经可以正常工作,但我们感觉在某些场景中,返回实体指针而不是索引可能会更加高效。然而,这种小的优化并不会对整体系统造成太大影响,所以暂时保持现状也是可以接受的。
综上所述,我们完成了低频实体的添加操作,确保了索引的有效性和实体的初始化。同时,我们也逐步淡化了索引在高层逻辑中的作用,进一步简化了实体管理的复杂性。
关于重构/重写代码的说明
在开发和编写代码的过程中,我们经常会多次反复地重写代码。这是一种常见且必要的工作方式,就像塑造黏土一样,我们需要不断调整和修改,直到最终形成理想的形态。在最初的阶段,我们并不追求完美的结果,而是通过多次尝试来逐步接近最终目标。
我们习惯于先快速地将代码写出来,然后根据实际需求和效果进行修改和重构。这种反复的过程是代码质量提升的关键。我们不会害怕重新编写代码,因为快速的重写不会耗费太多时间,也不会带来额外的成本。相反,这样的反复修改能够帮助我们发现更好的解决方案,并最终产出更高质量的结果。
我们需要保持灵活的态度,不断尝试不同的实现方式,直到代码的表现和逻辑让我们感到满意。这种方法不仅能够让代码更加完善,还能在实践中锻炼我们的思维和解决问题的能力。通过快速地迭代和调整,我们能够避免僵化地坚持某个不成熟的设计,而是始终保持代码的灵活性和可维护性。
在这样的过程中,代码重写并不是浪费时间的行为,而是追求高质量成果的必经之路。只有通过反复地打磨和优化,我们才能最终形成真正符合需求的代码,实现稳定和高效的功能。
更新 AddPlayer 和 MovePlayer
在编写代码时,我们常常需要不断调整和优化代码的各个部分。在处理不同的实体和索引时,我们必须仔细考虑如何优化和提高性能。对于添加的每个实体,我们首先不关心任何内部细节,只需确保这些实体能够正确地进行交互。
我们使用循环来遍历高和低的索引,确保每个实体都能准确地落入其预定位置。这个过程就是为了能够处理碰撞检测、任务执行等实际需求。我们需要对每个索引进行检查,以确保它们是独立的,不会与自己发生碰撞。
此外,代码中的一些部分已经经过了多次优化,例如将高索引与低索引组合在一起,以提高代码的执行效率。这些调整让代码更易于维护和扩展,从而产生更高质量的结果。尽管过程可能反复且繁琐,但这种反复的工作能够最终提升代码的性能和可读性。
最终,确保代码能够根据实际需求调整,并尽可能减少无效操作,这是提高开发效率的关键。通过反复地优化和调整,我们可以在不增加性能开销的情况下,确保代码的质量和稳定性。
更新 SetCamera,将residence地从高频转移到低频实体
我们需要遍历高频实体。这样的做法是完全可以的。我们不需要担心,因为我们知道在高下的所有物体都是高的。我们所做的和之前的操作一样,但是现在我们希望能够在循环中改变人的residence。之所以这么做,是因为这样可以让迭代更加简单,不会那么棘手。我们通过检查这些高实体,如果它们不在特定的矩形区域内,就将它们从集合中移出。这样做的原因是它使迭代非常顺利,不需要使用其他方法来处理它们。整体来看,这个循环非常简单,我们检查矩形区域,找到不在其中的高频实体,并移出它们,同时我们也能从这些高实体中获取低索引。这种方法使得迭代非常简单,并且不会频繁发生。这就是一个很有效率的循环。
一刻的顿悟;描述在变动的数组上迭代时遇到的问题
我们需要处理一个特殊情况,即当某个人走出边界时。这种情况下,我们需要从集合中移除这些人。在实现中,我们希望这两个子例程互相意识到,以保持迭代的简单性。我们担心的是如果只是简单的迭代,会导致数组被改变,因此无法正常工作。因此,我们希望将这些家伙捆绑在一起,使得他们在代码中的意义更大。
首先,我们检查这些高实体,如果它们不在特定的矩形区域内,则将它们从集合中移除。我们不希望直接改变实体索引,而是通过一个有效的函数来完成这项操作。然后,我们会处理低频的实体,在它们的区域内进行类似的操作。这样做的目的是保持高频和低频实体的操作一致,同时避免直接修改数组。我们希望在循环中每个人都能被正确的处理,而不因数组变异而出现问题。这种方法使得迭代变得更为简单且高效。整体来看,这就是我们处理这个问题的方式。
处理控制实体
我们正在控制实体的方式。当我们添加一个玩家时,我们检查控制器的低索引。如果低索引等于零,那么我们正在开始控制一个玩家。如果它不等于零,则正在处理已经控制的玩家实体。通过调用高亲和力函数,我们可以在高频段中获取和设置实体。为了确保不返回空指针,我们可能需要添加一个断言。这样做的目的是确保只有有效的低索引才会被处理。这种处理方式帮助我们保持游戏状态的有效性和一致性。
调试崩溃问题
我们需要清理处理方式并检查边界条件来确保正确创建高频实体。当前在快速移动操作时存在问题,可能是由于未正确处理环绕问题或位置调整不当。这些问题需要在后续的调试中解决,以确保高频实体的生成和位置的准确性。虽然今晚可能无法完全解决这些问题,但明天我们可以继续进行修复工作。
调试缺失墙壁和定位问题
我们需要审视代码中出现的问题,例如低频实体和高频实体的管理。发现低频实体计数不正确,可能是因为射线计数操作被重复调用。我们修正了这个问题,并重新分配了实体索引和指针,以确保它们被正确地处理。这样,我们能够使渲染调用更加有效率,改善空间分区,优化整个系统。这些改进使代码结构更为合理,增强了整个引擎的运行效率,使其向更具表现力的状态迈进。尽管还需要一些调整,但整体上的状态正在好转。
将低频实体和高频实体数组的大小设为2的幂次方有什么优势?
关于实体的模拟,可能需要提前做好准备,确保可以顺利进行交流。这种方法可能会带来一定的优势,例如提高性能或流动性。我们倾向于使用2的幂次,这种方式通常能有效提升性能。
目前尚不确定具体规模的大小,因为这将完全基于性能需求。对于高频实体,我们会评估能支持的数量,预计可能会达到相当高的水平。然而,模拟这些实体的复杂度可能会限制规模,因此实际支持的数量可能会低于预期。相比之下,低频实体的数量预计会非常大,同时它们的属性和存储方式可能会占用游戏内存的大部分。这是我们的初步假设。
总结来看,低阶实体及其相关属性预计将在内存分配中占据主要比例,我们需要重点关注如何优化存储和管理以提升整体性能。
如果这是一个2D横向滚动平台游戏,你会如何设计控制和移动的过程?
这是一个第三人称的游戏。如果这是一个二维横向卷轴平台游戏,控制和移动的创建过程与目前的游戏其实没有太大差别。目前控制部分还没有完成,因此可以等到正式处理这些内容时再讨论。
当前我们只是使用了一个临时的控制机制,可以让角色移动,但这并不是最终的手感。我们计划先完成动画的制作,然后将动画和移动系统结合在一起,确保手感符合预期。
在碰撞检测方面,目前的设置中,重力只在角色起跳时生效。而如果是平台游戏,重力通常会成为一个持续性的问题,需要更精细的处理。除此之外,代码的大部分逻辑是相似的,没有太大的区别。整体流程中,核心逻辑保持一致,只是针对不同类型的游戏需求进行了适当的调整。
你觉得今天的代码会在优化过程中被重写吗?
在优化过程中,当前的内容可能会被重写。现在的主要目标是将所有的系统和逻辑安排好,以便明确规范和设计要求。当前的阶段主要是对代码布局进行整理,确保所有功能模块和接口清晰明了。通过这一阶段的工作,可以为接下来的开发奠定基础。
最终的解决方案和系统将基于当前的工作进行构建。在最初的几周内,主要任务是搭建框架并确认所有系统如何协同工作。如果没有一个基础框架,单独开发系统可能会导致它们在集成时难以很好地配合,甚至会出现重大问题。因此,首先创建一个能够合理融合的基础版本是关键。
接下来,在这个基础版本上对各个系统进行逐步迭代和优化,最终将它们调整到理想状态。通过这种方式,可以确保所有模块在开发和优化过程中始终能够保持一致,并最终形成一个完整、协调的整体系统。
难道不应该更容易保持一个空实体插槽列表来在其中添加实体,而不是总是移动和更改其中一个指针吗?
关于如何构建这些系统,有一种建议是使用一个包含固定大的结构,通过一个空槽列表来管理空位,将新元素添加到空槽中,而不是总是移动条目和修改指针。这种方法可能会更容易维护。
在选择具体方法时,对于两种方式的优劣尚未有明确的偏好。我们认为两者在本质上是相似的,并没有哪一种明显优于另一种。之所以选择当前的方法,是因为它的复杂性较低。具体来说,不需要为实体定义“空”的概念,因此在遍历实体时,不需要频繁检查当前槽位是否实际包含实体。
这种设计将复杂性集中在删除代码中,而不是将复杂性分散到整个系统的其他部分。这种方法能够更好地控制复杂性传播,因而被认为是一个更优的选择。不过,具体效果还需要在实际应用中进行验证和调整。
你是否了解游戏对象存储中的插槽映射(slot maps)?你是否会在这个项目中使用它们?我们在我们的引擎中使用它们。
关于slot maps的使用,目前对这个术语不太熟悉,需要进一步描述才能明确。如果能详细描述slot maps的定义和功能,就可以更确定地回答是否会在这个项目中使用它们。
slot maps的讨论涉及到游戏对象的存储管理,但由于对具体概念的理解尚不明确,所以暂时无法确认是否适合当前的项目。不过,如果slot maps能够提供更高效的对象管理方案,尤其是在复杂对象结构中可能产生优势的话,它确实值得考虑。
目前,需要更多关于slot maps的细节说明,以便更好地评估它们在系统中的潜在应用价值和适用性。这种方法的选择最终还是要看它是否能为项目的架构和性能带来实际的优化。