回顾我们在模拟区域更改方面的进展
目前我们正在进行游戏的架构调整,目标是建立一个引擎架构。我们正在实施的一个关键变化是引入模拟区域的概念,这样我们可以创建非常大的游戏世界,而这些世界的跨度不必受限于单个浮点变量。
通过这种方式,我们可以将游戏世界分成多个小的区域,每个区域可以独立模拟。玩家周围的区域会频繁更新,而远离玩家的区域则可以较少更新,这样不仅能优化性能,也能保持游戏的真实性和可扩展性。
在进行这些架构调整的过程中,我们正在逐步解决两个主要的扩展问题。第一是如何有效管理大规模的游戏世界,第二是如何处理不同区域更新频率的问题。我们希望这些调整能够有效解决这些问题,但目前仍在测试阶段,结果尚未完全确定。
目前,我们正在修复编译错误并整理代码,将原本分散的功能整合到模拟系统中,以便能在多个区域内同时运行这些功能。接下来,我们将继续进行调试,确保系统在实际运行时能够正确执行,最终实现更为复杂的区域模拟和更新机制。
为实体行为代码创建一个单独的文件
目前,我们正在将代码组织成不同的文件,以便更好地管理游戏中的实体行为。考虑到我们可能会涉及大量的实体相关代码,计划将所有关于实体的行为逻辑放在一个单独的文件中。这样做的目的是为了方便查看和管理,同时使得每个部分的代码功能更加清晰。
虽然将代码分成不同的文件并不影响实际的开发过程,因为编译时所有代码最终都会汇总成一个整体,但这种文件的划分有助于心理上的管理。我们希望能够迅速浏览并清晰地图解代码的结构。
目前,我们正在创建一个名为“game_entity.cpp”的文件,其中将包含实现实体所需的所有代码。这个名字可能会改变,但它代表的是所有与实体相关的功能代码。这个文件的内容将包含更新调用和其他与实体行为相关的代码。
在渲染部分,我们的目标是直接在同一区域内进行操作。这意味着,在渲染时,玩家周围的区域会被打开进行模拟,而远离玩家的区域则不需要进行渲染处理。对于这些区域,我们不会进行任何渲染输出,这些部分的处理将在后期进行优化和调整。
总的来说,当前的工作重点是整理和模块化代码,确保实体和渲染部分的行为能够按照预期运行,同时为后续的调试和优化打下基础。
修改 DrawHitpoints,使其在 sim_entity 上操作,而不是 low_entity
目前,正在进行的改动涉及将系统从使用“低频实体”转换为使用统一的实体格式。过去,游戏中有一种针对“低频实体”的存储格式,主要用于存储一些不常更新的敌人信息。然而,现在所有的实体都已经统一成一个格式,不再区分高频实体和低频实体。
在这种新的设计下,所有实体都被视为统一的对象,除了背包中的物品。低频更新的实体(如曾经的低实体)也转变为普通的实体,这简化了代码结构。这样做的目的是为了消除之前存储中不再需要的高频实体,并将所有剩余的实体统一管理。
这个改动应该能够顺利实施,当前的游戏状态不再需要处理高频实体,只剩下最终存储的实体。这样一来,所有与高频实体相关的复杂性都被移除,系统变得更加简洁和高效。
将更新玩家的代码与控制器处理代码分开
上面讨论的内容主要涉及处理控制器输入和玩家角色模拟的逻辑。首先,处理控制器输入并不再需要初始化某些数组,这意味着可以直接跳过不必要的操作。目标是改进玩家和英雄的处理方式,使得这些操作更加理智和有效。
我们不再在解析控制器的代码中直接进行玩家更新,而是希望通过更加清晰的结构将控制器输入和英雄模拟分开处理。控制器输入需要一种逻辑,能够更有效地管理多个控制器,甚至在同一个系统中处理不同的输入设备,比如键盘和游戏手柄同时控制同一个玩家。
另外,考虑到可能需要将不同的控制器请求(如移动、跳跃等)保存并应用到英雄上,整个处理过程将变得更加灵活。控制器请求将在一帧内积累,并且每个玩家的请求会被保留以便后续使用。对于每个控制器的输入,都会记录其对应的实体索引和控制请求,并最终通过一个统一的处理逻辑应用到英雄上。
另外,在实际操作时,存在两种方法来处理这些请求。一种是通过索引映射来快速查找并处理控制器输入,另一种是通过循环结构遍历所有控制器请求并逐一应用。通过这种方式,可以确保每个控制器的请求被有效地传递到相应的英雄上。
最终目标是将控制器输入和英雄模拟的处理更加模块化,使得代码更易于维护和扩展。在未来可能需要根据游戏需求调整这些结构,确保系统能够灵活应对不同的控制方式和输入设备。
在开发过程中,代码的结构不断变化,过程中采取了逐步解决问题的方式,首先决定从简化开始,不立即追求复杂的优化方案。通过使用指针、简化索引管理以及减少不必要的操作,逐渐回归到简单高效的编程方式,保持代码的清晰性和可维护性。虽然过程中涉及了很多复杂的逻辑和约束条件,但最终目标是确保代码在满足所有需求的同时,保持尽可能简洁。
我们使用了实体管理系统,其中每个实体都可以通过存储索引来访问,并进行相应的操作,如设置位置、控制状态等。涉及到的操作包括指针的使用、跳跃机制的处理以及实体间的互动。尽管在过程中有时需要处理复杂的参数和条件,但我们尽量避免过度设计,专注于简化实现,并在之后的阶段根据需求做进一步的调整。
通过这一过程,展示了在满足功能需求的同时,如何保持代码的简洁性以及解决实际开发中的复杂问题。最终,目的是通过不断调整和优化,确保代码的可扩展性和高效性。
修复更多的编译错误
在代码调整中,我们正在逐步处理一些未定义的标识符和冗余部分的清理工作。首先,移除了不再需要的旧代码片段,专注于处理同一区域的相关逻辑,简化了整体逻辑结构。比如,一些高频实体相关的变量已经不再需要,因此被清理掉。
对于未声明的变量问题,比如“sim region”(模拟区域)和“entity”(实体)的标识符,我们计划稍后定义并解决相关问题。在此过程中,通过明确变量的作用范围和上下文来优化代码,例如“实体z”直接被定义为对应的区域变量。
此外,在优化的过程中,我们逐步简化代码,使其看起来更像是直观、易于书写的逻辑。代码调整后,原本依赖复杂约束的部分变得更加简洁。这种方式既保留了必要的功能性,又去除了不必要的复杂性。
对于函数调用,我们调整了传递的参数。例如,明确同一区域参数的传递,而不再依赖整个游戏状态。在函数签名和参数管理方面,我们不断优化,确保传递的参数与函数逻辑紧密相关。
最后,对于未处理的功能如“move entity”(移动实体)操作,我们已经明确了其需要依赖的同一区域参数。接下来的工作会进一步确保所有依赖项的合理性,同时清理掉无关的代码。通过这种逐步优化的方式,代码的可维护性和可读性显著提高。
TODO:将 sim_entity 重命名为 entity
在代码设计中,原本的“sim entity”(模拟实体)名称被频繁使用,因此进行了命名上的优化,将其重命名为“entity”(实体)。原因在于当前代码逻辑中,主要处理的实体类型是用于同一区域的主要实体,而其他部分的逻辑更多是为了能量存储的目的服务,作用相对次要。
为了提高代码的可读性和直观性,这一改名反映了其在当前结构中的核心地位。原本的命名意图是为了更明确地区分区域内和其他场景的实体,但在实际使用中,过于复杂的命名反而降低了代码的易用性。
在调整过程中,发现“entity”这个名字在逻辑中频繁被输入和调用。多次输入“sim entity”时容易出错,并且这种复杂的命名已经对流畅的开发产生了阻碍,因此直接简化为“entity”显得更符合实际需求。
这一重命名的结果是,不仅提升了代码的整洁度,也减少了重复定义名称的情况。同时,这种调整让代码更贴近其实际功能,使得代码开发和维护更高效。未来,在其他模块或功能中,也可以考虑遵循类似的简化命名原则,以保持一致性和简洁性。
修复更多的编译错误
当前我们正在处理arena相关的部分。arena的逻辑被留到最后进行处理,因为需要额外讨论其具体实现的细节。目前,主要聚焦于绘制实体的生命值(hit points)。我们决定仍然只绘制实体本身的生命值,不做额外的复杂处理。
在更新相关逻辑时,涉及了排序更新和熟悉度更新,尤其是怪物或其他需要处理的实体。这些更新操作实际上已经独立于实体本身,成为单独的逻辑模块,从而使代码结构更加清晰。
随后,我们添加了用于绘制生命值的代码逻辑。通过条件语句实现了绘制点的功能,使其能够直观地反映实体的生命值状态。经过测试,绘制的效果和其他逻辑看起来都运行正常。
接下来,只剩下arena部分的逻辑需要完善。同时,还有两个需要特别关注的更新逻辑未完成。总的来说,当前代码结构已经趋于合理,重点任务逐步缩小到特定的功能模块。这种分步优化方法有助于提高开发效率,同时减少代码混乱的风险。
将 game_entity 引入构建中
现在,我们将继续实现更新调用的逻辑。为了完成这部分内容,我们已经有一个“game_entity.cpp”文件。接下来,我们会将这game_entity.cpp到构建中,并将它们包含进去。
如前所述,所有内容会被编译成一个整体的块,而不是多个独立的文件。因此,我们将这些组件引入并整合到现有的结构中,以确保更新逻辑的完整性和一致性。
修复 UpdateFamiliar
现在所有的内容已经导入,我们需要将已有的旧代码移植到新环境中。目前的实现应该是可行的,我们不再需要旧的游戏状态逻辑,而是直接使用相同的区域。通过这一过程,我们实现了对相同实体的直接访问,不再需要额外的提取操作。
目前的处理方式是对所有内容进行循环遍历,确保所有实体都在同一区域内进行操作。这种方法暂时可以满足需求,但并不是最终的解决方案。为了提高效率,未来计划通过空间查询的方式来替代这种遍历,以实现更高效的查询和处理。
接下来的步骤中,我们会处理区域内所有实体的相关逻辑,例如计算能量总数或判断需要跟随的对象。这种设计虽然可以正常运行,但为了优化系统,需要将查询逻辑整合到统一的地方,从而在需要时加快操作速度。
当前的实现方式还保留了对最接近实体的指针,而不再存储冗余的低值、高值等数据,这大大简化了代码逻辑。通过清理旧代码,我们逐步实现了对系统的优化。相关的功能,如调用移动实体的方法以更新位置,也已经被纳入。
最后,旧代码中一些复杂的处理方式和指针操作已经被移除。现在的逻辑更加清晰,默认的移动规格被重新调整,相关实体数据的处理也从繁琐的流程中独立出来,进一步提升了代码的可读性和维护性。
修复 UpdateMonstar 和 UpdateSword
第二步是保留这些操作逻辑,目前还没有完成这部分代码的编写,但实现起来相对简单。明确的是,会使用相同的区域逻辑,与其他调用类似,例如剑的移动操作。
之前的剑移动逻辑已经实现,并且没有涉及特别复杂的内容,仅仅是一个实体的移动操作。随着时间的推移,该实体会逐渐失去作用,甚至被视为不存在。目前还没有这个特性,因此需要设计一种方法来处理实体的“消亡”状态。
下一步的重点是在同一区域中跟踪实体,为此需要一种机制来确定实体是否应该存在。可以通过断言机制来实现,例如添加断言以验证某些实体的状态。此外,还需要确保移除了对低值、高值等不必要数据的依赖,因为这些数据已经被新的实现所取代。
随着优化完成,目前所有相关操作均基于相同的区域逻辑。当前代码逻辑已经回到了之前的运行状态,为接下来的扩展和优化提供了一个稳定的基础。
概述 sim 区域代码的剩余 TODO
目前还有几项任务需要完成,主要集中在game_sim.cpp的部分以及模拟区域的处理上。针对这些内容,有一些必须完成的工作可以通过推断得出。
首先,需要清理哈希表,以确保数据结构保持整洁和高效。清理工作完成后,还需要继续解决其他问题,具体包括进一步完善模拟区域的逻辑以及优化现有的功能实现。这些步骤是接下来的重点目标,必须逐步完成以实现系统的完整性和可靠性。
使用临时存储为 SimArena
当前我们主要讨论如何处理和划分内存,特别是针对游戏开发过程中瞬态区域的使用和优化。首先,我们需要一个地方来存储相同区域中的数据,这在设计上是首次引入的内容。
为了实现这一点,我们将内存划分为两个主要部分:永久块PermanentStorage和过渡块TransientStorage。永久块用于存储需要在帧之间持久化的数据,比如游戏状态和实体。这部分数据通常与游戏保存相关。而过渡块则是一个临时的内存区域,主要用作计算过程中的便笺簿。过渡块的设计理念是简化内存管理,只在计算时使用,完成后直接清理,不涉及复杂的分配和释放逻辑。
过渡块的使用方式是,每当需要计算时,从这个块中划分出一部分内存,执行计算操作,完成后将其释放,而无需关心具体的清理流程。通过这种方式,可以避免传统指针管理中复杂的内存分配和释放逻辑,以及由此引发的潜在性能问题和内存泄漏。这种方法也避免了类似垃圾回收语言中频繁内存回收的开销。
在具体实现中,我们使用memory_arena的方式来管理过渡块。初始化时,我们将过渡内存空间划分并提供给memory_arena。每次处理开始时,将memory_arena指向瞬态内存的底部,完成后直接重新初始化,避免任何形式的清理操作。分配和释放的成本被降到最低,同时也确保了内存使用的高效和简洁。
接下来,根据需要进一步划分过渡块的具体使用逻辑,比如为特定的模拟区域分配内存。当前的实现已能够支持瞬态区域的基本需求,而后续的优化将在更多功能需求出现时展开。总的来说,这种内存管理方式显著简化了开发过程中对内存的处理,同时提高了系统的可靠性和效率。
修复剩余的编译错误
我们开始解决当前问题。
首先,需要检查相关的风险点。通过审视当前的逻辑,我们注意到游戏状态中存储了一些低频实体,而这些实体的处理需要特别的注意。游戏状态被设计为一个内存区域,应该返回一个明确的值,用以表示分配的实体。
关键问题:
- 游戏状态目前需要存储低频实体,但长期目标是减少直接依赖它的使用。
- 返回的区域需要在实体分配时保持一致。
- 在身份识别时,返回值必须明确并能够确认分配的实体。
解决思路:
- 调整逻辑以确保返回的区域始终一致,尤其在处理低频实体时。
- 修改实现方式,使得最终目标是减少对整个游戏状态对象的直接传递。
- 返回值不仅需要指向分配的实体,还需在多次调用中确保其可靠性。
通过这些调整,我们可以优化系统,使其更高效并更容易扩展。
处理清空哈希表的 TODO
首先,我们需要做一些改动,特别是在哈希表的处理上。我们已经进行了一些重大的代码变更,但由于时间的限制,可能有一些遗漏之处需要检查。每个改动都需要逐步确认,以确保所有步骤都得到了正确的实施。
主要任务和思路:
- 哈希表清理:我们需要确保清理哈希表的功能能够正常工作。在代码中,可能需要调用ZeroSize的操作来清除哈希表,这样做能够确保在后续的代码中不会留下任何未处理的内容。
- 零大小调用:为了简化清理过程,可以通过内嵌的清理调用来清除所有字节,特别是在哈希表的大小为零时。这种方法会依次处理每个字节,直到全部清除。
- 实现细节:
- 逐个字节清除:对于哈希表中的每个字节,我们会将其清除为零。
- 宏调用:为了便于反复使用,我们可以创建一个宏来调用这个清理操作,确保它在需要时可以被多次调用。
- 性能优化:通过这种方式,我们也可以确保清理操作的效率,特别是在处理更大的数据块时。
- 后续检查:所有的改动都需要逐步回顾,确保没有遗漏的地方。特别是对于哈希表的清理操作,需要再次验证它是否已经清除。
通过这些步骤,我们能有效管理哈希表的内容并优化程序性能,同时确保代码的准确性和完整性。
添加注释
开始调试代码
我们已经完成了所有已知必须做的事情,接下来是时候再次检查一下当前进展。接下来,我们将继续调试代码并查看执行结果。我们从初始化模拟区域开始,设置了世界指针和模拟原点,随后处理实体和哈希表条目。
首先,我们清空了哈希表并初始化了模拟区域的基础结构,确保实体可以正确存储在模拟空间内。然后,我们通过获取世界差异来计算实体相对于模拟空间的位置,并检查它是否在相机视野内。接下来,我们将实体添加到模拟区域并存储其信息。
在添加实体后,我们通过哈希表检索实体,并确保所有存储的实体可以通过其存储索引重新访问。我们逐步遍历哈希表,检查每一个条目的存储情况,确保实体能够按照预期在模拟区域中正确映射和存储。
最终,所有实体都正确地映射到了它们各自的存储位置,并且模拟空间中的操作顺利进行。
修改 GetHashFromStorageIndex 代码,使调试时更易于检查
我们打算对代码进行一些调整,主要目的是为调试和验证提供更多便利。
首先,这次是我们第一次进入这段代码,我们决定继续深入分析代码运行的情况。为了便于调试和观察,我们计划在某些地方设置断点。此外,我们还决定启动代码后,直接观察代码的执行流程和关键值的计算过程。
为了更好地检查相关的值,我们需要对现有代码进行一些调整。之前由于编写代码的方式限制,当前无法直接查看一些关键值,例如哈希索引以及偏移计算后的结果。为了改进这一点,我们希望在执行偏移计算之后,能够清楚地看到哈希索引值。
接着,我们计划分步骤计算一些关键变量,例如Hash掩码和哈希索引,这样不仅有助于验证代码是否正确,也能够在调试中更方便地观察每一步计算的中间结果。
这样做的原因是,即使编译器最终会自动完成这些替换工作,我们还是希望在调试时能够清晰地了解变量的变化过程,以确保代码逻辑是正确的。
具体来说,我们的目标是:
- 检查偏移处理后的哈希索引值,确保计算无误。
- 单独计算并验证Hash掩码值,确保这些关键变量的正确性。
- 提供更清晰的变量和流程记录,使后续调试更加方便。
总而言之,这些改动的目的在于提高代码的可调试性,让我们在验证逻辑时更加直观和高效,同时确保关键计算过程正确无误。
继续单步调试代码
我们执行代码,重新运行到当前的位置。我们计算了一个Hash掩码,用来屏蔽掉我们不需要的部分,确保只保留需要的位。接着,我们计算了一个哈希索引,这也是基于Hash掩码的正确部分,验证其值是否合理。
通过索引访问哈希表时,我们发现对应的条目为空,这符合预期。随后,我们将数据写回哈希表条目,无论条目原本是空还是已有数据,都将被覆盖为当前的存储索引。这种逻辑确保了条目内容始终符合预期。
在这个过程中,如果发现有实体被初始化,我们将覆盖该实体的数据,确保所有的字段都被更新为当前需要的数据内容。这通常包括基本信息,例如对撞体的相关数据。在加载实体引用时,检查索引是否非零,并在必要时加载附加引用,例如武器等数据。
对于实体数据,我们继续将其添加到模拟集内,逐个处理所有实体。当一个实体具备额外引用(例如剑)时,我们会在检测到的情况下加载该引用数据。
完成添加后,我们会查看已加载的实体是否符合预期,例如检查位置是否合理。如果发现问题,可以设置断点进一步调试,确保加载的实体数据有效。
最后,所有实体加载完成后,我们验证总数,例如相机区域内的实体数目是否符合预期。对于绘制代码,我们进入下一步处理,完成整体流程。
发现一个 bug - 忘记更新实体指针
我们开始对该区域内的实体进行循环操作。然而,我们意识到遗漏了一段关键的代码。具体来说,循环内的实体指针没有被正确更新。每次通过循环时,这个指针需要更新为当前迭代的实体,否则后续操作可能出现问题。
发现这一问题后,我们立即修复,确保实体指针在循环内的每次迭代都能正确指向相应的实体数据。随后重新编译代码,以避免因指针未更新而导致的逻辑错误。
对于已完成的部分,我们不再过多关注,因为已经详细检查过其逻辑,当前主要集中在解决这一指针更新的问题。
向 IsCanonical 中添加 epsilon 来防止断言错误
在代码逻辑中,我们需要对精度做一些灵活调整,而不是严格限定数值完全匹配。当前的比较逻辑要求结果非常接近,但稍微放宽这一要求会更为灵活,因此我们需要引入一个 epsilon 值,用于判断数值是否在一定容差范围内。这样,即便数值偏离理想值稍许,仍然可以认为是符合预期的。
为了实现这一点,可以通过允许一个范围,例如设定为负数到正数加上 epsilon 的范围,只要数值落在这个范围内,就可以认为它是有效的。虽然这种方式并不是最优的解决方案,但在现阶段可以暂时采用,后续再对浮点数精度问题进行深入优化和确认。
在调整后,这段逻辑看起来工作正常,测试中对偏离范围的情况进行了适配处理。下一步,我们需要观察这些更改是否对英雄实体的行为或状态产生影响,进一步验证调整是否有效。确保浮点运算尽可能准确,同时在实际应用中平衡精度和灵活性。
调试相机定位
在检查摄像机位置时,发现摄像机并未正确居中。目前尚不清楚为什么摄像机会对准现在的位置。由于原点的具体位置不明确,可能存在某种缺陷。为更清楚地了解情况,决定绘制一个矩形来标示原点的位置,以便提供一个参考点。
绘制方法是通过绘制一个亮黄色的小矩形(例如10x10像素)来标示当前计算的原点位置。这个原点是相对于摄像机的世界坐标,需要获取世界坐标的原点并将其与区域的原点进行差值计算,从而确定矩形的绘制位置。
在实现时,需要调用相关函数获取世界原点的位置,然后通过计算其与当前摄像机区域的差值来确定绘制坐标。初步实现后发现原点似乎位于左上角,进一步验证后确认绘制的点确实是原点。
然而,由于时间限制,未能继续深入分析,因此决定暂时停止工作,并在后续时间内继续完成相关任务。
添加 TODO,因为今天要停下来
大多数情况下,工作都进展顺利,接近完成,但还有两件事需要处理。
首先,要弄清楚原点的位置及其工作方式,这可能就是它应该如何运作的原因。需要进一步思考原点的具体情况。
其次,遇到的问题是在添加英雄时,由于没有正确设置位置,导致了一些问题。具体而言,当前世界中并没有位置为实体(例如剑)分配,这就造成了问题。必须解决无效位置的问题。
这两个问题是需要优先处理的。虽然这些问题的解决看似并不复杂,但它们对于系统的正常运行至关重要。处理这些问题后,工作进展会顺利很多。
总体来说,问题的规模比预期的小,解决过程也没有想象中那么麻烦,因此感到很高兴和满意。
如果我们不偏离太多,哪一周已经积累了足够的代码基础来实现一个简单且完全功能的游戏?
大多数情况下,工作进展顺利,因此距离完成目标已经很接近。然而,仍然有两项任务需要完成:一是弄清楚原点的位置及其原因,二是处理未安置的实体。原点问题可能是系统的预期行为,但仍需进一步确认。此外,添加英雄时出现的问题可能是因为缺少实体位置的设定,尤其是剑这种实体尚未在世界中定位,导致问题的发生。
当前的工作涉及到对模拟区域的优化和调试。对于我们所做的编程来说,尽管看起来已经积累了不少时间,但距离实现完全功能的游戏仍有较长的路要走。以目前的进度,编程时间大约为65到70小时,这相当于大约两周的工作量。因此,尽管已经有了不错的进展,但仍未达到完全功能的状态。对引擎的工作状态已经基本满意,未来几周会继续优化,并逐步实现更多功能。
关于高频和低频实体,以下方案是否可行?使用一个实体结构体,所有实体存储在一个数组中,若实体在相机视野内,则频繁更新该实体,若在相机视野外,则使用低频更新?
当前的解决方案是将所有实体存储在一个数组中,并根据相机范围来决定是否进行高频或低频更新。如果实体处于相机范围内,会进行频繁更新;如果不在相机范围内,则使用低频更新。这种方法目前在使用,但考虑到实体的复杂性,仍有一些需要改进的地方。
尽管可以直接在模拟区域中指向实体数组并在其中进行更新,但不建议这样做。这是因为直接操作entity结构可能不符合未来的需求,尤其是随着游戏复杂度的增加,实体的行为和处理逻辑将变得更加复杂。虽然对于简单的实体来说,这种方法是可行的,但对于游戏中的敌人和复杂的玩法机制,这种方法可能不足以满足需求。
目前的做法是将实体存储在一个结构中,并在必要时复制到另一个位置,以便在后续的处理阶段使用。这种方法保持了灵活性,但随着游戏进展,可能会引入更多的步骤和更复杂的结构。最终目标是确保能够应对更复杂的实体和游戏逻辑,因此保留两个存储状态的灵活性是非常重要的。
这种做法虽然可以应对目前的需求,但未来可能会根据游戏的复杂性进行调整,以适应更多的功能和更精细的实体管理。
能否给出一个关于实体如何工作的high level概述?我有时理解一些,但这一周我有些跟不上。
我们的世界被分为两部分:地图部分和逻辑部分。地图部分是通过一个稀疏地图进行存储,这样可以只存储那些实际存在内容的区域,而不是存储整个世界的所有空白区域。地图由多个瓦片块组成,这些瓦片块分布在不同的区域,根据需要加载和卸载。
逻辑部分则是一个实体数组,包含了游戏中的所有实体。每个实体在数组中有相应的位置,存储了该实体的各种信息。当我们需要更新某个区域时,我们选择一个矩形区域,将其转化为一个“模拟区域”。这个区域可能包含多个瓦片块,瓦片块中存储了实体数据。
为了高效管理这些实体,我们将实体存储在一个名为“实体数组”的数据结构中。每个实体会在其中一个或多个瓦片块中被引用,通过索引连接到这些块。每当模拟一个区域时,我们首先从实体数组中提取出该区域内的实体,将它们加载到内存中进行计算,完成模拟后再将它们写回原位置。
这种存储和处理方式允许我们管理一个非常大的世界,同时保持高效的内存使用,因为只会加载和模拟当前需要的部分。所有的模拟计算都是在浮点坐标系下进行,并且我们通过指针和索引机制来快速查找和更新实体的数据。这种方法使得游戏能够处理大量实体而不需要担心内存消耗和性能问题。
我不太确定短期/临时/瞬态内存的定义是什么?它是仅用于单帧的内存,还是生命周期较长的?如果是长期使用的内存,如何确保不会发生内存覆盖,导致正在使用的内存被覆盖?比如一个持续多帧的自动攻击。
Transient memory在游戏中只持续一个框架的时间,通常用于临时存储和处理数据。在每一帧中,游戏的更新和渲染都会发生,这些过程会覆盖当前的Transient memory,不会进行读取,因此Transient memory不会持久化。每当开始模拟时,之前的数据会被完全覆盖,而不会留下任何痕迹。Transient memory仅在框架的开始和结束之间存在,一旦处理完成,就会释放,允许其他部分使用这些资源。
对于每个模拟区域的更新时间,这取决于它们的状态和需要更新的内容。如果一个区域的模拟比较简单,可能不需要很长时间来更新,而复杂的区域可能需要多帧的分辨率。为了保证效率,Transient memory通常只会在一个框架内使用,不会跨帧持久存在。
如何确定每个模拟区域更新的时间,如果它们不是每帧都更新的话?
每个实体会存储它自己的时钟,并在更新时根据需要更新该时钟。当模拟区域或其他需要更新的区域进行时间处理时,实体的时钟会被更新,以确保实体的状态反映出正确的时间进度。每个实体的时钟会随着时间流逝而更新,这样可以使游戏中的模拟保持一致,尽管具体的实现细节仍在完善中。
这个实验阶段的工作如何融入最终代码中?我们会重新使用大部分代码,还是会从头开始?
在实验阶段所做的工作将在最终代码中逐步被改进和提升。尽管大部分工作会被保留,但一些系统可能需要完全重写。例如,如果某些部分如渲染错误假设还没有完全实现,就需要开发完整的系统来替代。目前的工作是持续改进和完善,尽管一些部分可能看似简单,但需要不断调整和优化。最终,代码会升级到工业级别,经过详细的修复和检查,确保系统稳定高效。
虽然过程中的某些部分可能需要从零开始,但整体上不会删除现有的代码,而是持续向前推进。未来的目标是处理更多的动画和声音相关的工作,同时解决模拟区域的挑战。尽管会遇到一些困难,但整个过程仍然是积极的,且对最终成果充满信心。对于接下来的工作,有可能会面临略微调整的时间表
仓库:https://gitee.com/mrxiao_com/2d_game