仓库: https://gitee.com/mrxiao_com/2d_game
回顾
现在我们正进行游戏结构的重构,目的是为了更合理地安排游戏的组织方式,模拟玩家周围的实体。我们将这些实体分为两类:一类是当前正在屏幕上显示的实体,另一类是被映射到低频更新空间的实体。前者将进行所有的碰撞检测和游戏逻辑处理,而后者虽然会继续发生一些变化,但更新频率较低。这样,我们可以在游戏中支持大量的自主行为实体,无论是几百个还是几千个,帧率也不会因此变得无法忍受。
在这个过程中,我们还将原本依赖于地图瓦片的碰撞检测方式,改为基于实体的碰撞检测。目前,我们不再有与瓦片地图相关的碰撞,碰撞处理将直接基于实体的交互。
修复玩家往上走一直闪的问题,
忘记修复之前的一个问题了
修复读取缓冲区末尾时的崩溃
每当我们遇到错误时,都应该立即修复。当前的问题是我们可能在无效的位置绘制了位图。首先,检查源行指针发现其值不对,导致我们从位图的末尾读取数据。位图的高度是217,我们似乎正在访问超出位图边界的部分。问题出在偏移代码上,导致我们错误地获取了不合法的位置。
我们在加载位图时采用了BMP格式,该格式从底部向上存储图像数据,因此我们需要从位图的顶部开始处理。为了适应这一点,我们会调整指针,确保我们处理的像素不超出位图的范围。偏移值被计算时,也需要注意避免计算错误。
具体来说,当处理Y坐标时,我们遇到了四舍五入问题。由于浮动的Y坐标是0.5,它在四舍五入时会因为奇偶性或向无穷大的舍入规则导致错误的行为。因此,解决办法是使用宽度和高度的最终值来确保我们不会再遇到超出边界的问题。
通过这种方式,我们可以确保在绘制时不会出现无效的偏移,也避免了浮动坐标带来的问题。最后,为了进一步验证修复是否有效,我们进行了回测,确实发现了导致崩溃的根本原因。
记得删掉这个测试代码
将墙壁转换为实体
目前还没有为墙壁实体添加相关逻辑,接下来的目标是停止检查瓷砖地图,而是直接检查实体。因此,需要将这些墙壁实体添加到实际的实体列表中。为了实现这一点,我们需要明确如何将实体添加或移出这个集合。
然而,管理这些实体集合也带来了一些问题。一个难点在于是否应该为每一个可能存在于地图上的物体(如树木等)创建一个低级别的实体,或者干脆将它们作为休眠实体存储。这样做可能会导致存储浪费,因此还需要进一步考虑是否有更高效的存储方式,或者是否可以通过压缩等方法来解决。
另一个考虑是,环境的可破坏性如何影响实体管理。如果场景允许玩家摧毁树木或突破墙壁,可能需要将这些元素作为实体存储,这样才能跟踪它们的状态。这一问题需要等到游戏设计进一步明确后才能做出决定。
为了简化当前的开发流程,决定从单一屏幕开始,逐步进行开发。这样做的好处是可以减少要处理的实体数量,避免复杂的情况,先集中精力处理当前屏幕上显示的实体。接下来,我们将尝试将这些实体转换为可以管理的对象,看看能得到什么结果。
此外,游戏中的相机移动也是一个需要考虑的因素,但这部分的逻辑还没有完全实现。
提取摄像机移动代码
需要正式化相机移动的过程。当相机移动时,必须处理移除屏幕外的实体,并加载进入屏幕的实体。为此,决定将相机移动的逻辑拆分成一个单独的函数,这样可以在不干扰其他逻辑的情况下处理相机相关的操作。
首先,会创建一个新的函数,例如 setCamera
,这个函数接收游戏状态和一个新的相机位置作为参数。新相机位置将是目标位置,也就是相机要移动到的位置。这样,所有与相机移动相关的数学计算将不再直接在原地进行,而是通过新的相机位置变量进行处理。
接着,将保持当前的数据流,避免大规模改变现有流程。为了简化初期开发,首先会直接在当前逻辑中实现这些改动,确保不会一次性改动太多内容,保持原有的数据流。这样,相机的位置变化就可以通过 setCamera
函数进行更新,而不需要直接操作相机的坐标。
在完成这些改动后,每当相机需要更新位置时,就会调用 setCamera
函数,传入当前的游戏状态和新位置。这样做能够确保所有的计算(如帧偏移等)被正确处理,同时使得相机能够跟随角色的移动而更新。
最终,期望这个新方法能够在角色从屏幕外走到屏幕内时正常工作,相机位置能够准确地更新,并且能在角色走出屏幕后再回到屏幕时继续正常移动。
从高实体集合中移除距离摄像机过远的实体
首先,移动相机时需要做两件事:
- 遍历所有的高频实体(high entities)。这些实体位于“高频实体集合”(hiset)中,并需要根据相机的当前位置来决定哪些实体需要移除。如果实体太远,应该将它们从集合中移除。
- 确定相机位置和每帧的偏移量,然后判断是否应该移除实体。如果实体超出了相机视野的范围,就需要从集合中删除。
目前的实现方式是,使用实体索引遍历所有高频实体。接下来,对每个实体进行检查,决定是否需要删除。虽然这可能会通过多次遍历实现,但以后可能会通过优化合并成一个循环,以减少多次遍历的开销。
这个过程中,首先获取实体数组的长度(高频实体的数量),然后遍历每个实体,根据它们是否在视野内来决定是否移除它们。
关于指针访问的说明
在进行实体处理时,首先需要获取高频实体(high entity),然后获取其位置(P),接着加上每帧的偏移量。接下来,检查该位置是否位于有效的矩形区域内。这是一个需要频繁执行的操作,因此可能会引入一些辅助函数来简化操作,例如一个判断点是否在矩形内的函数。
例如,可以定义一个矩形结构体,其中包含最小值(min)和最大值(max),然后通过此结构体来判断一个点是否在该矩形内。通过这种方式,可以避免每次都手动进行判断,从而提高代码的可读性和效率。
关于矩形打包的说明
矩形的构造可以通过不同的方式进行,因此将矩形的最小值(min)和最大值(max)捆绑在一起是一种常见的做法。这种方式能够方便地构造和处理矩形。例如,通过矩形的中心点(center)和尺寸(dim)来定义矩形的最小值和最大值,而不是直接用最小值和最大值。这样可以使代码更加简洁,并避免不必要的计算。
在构造矩形时,如果要使用矩形的中心点和尺寸,可以通过函数直接生成,而不需要手动计算最小值和最大值。这使得代码更加灵活且易于扩展。
测试矩形内的点时,可以通过检查点的坐标是否在矩形的四个边界之间来实现。具体来说,要确保点的坐标大于或等于矩形的最小值,同时小于矩形的最大值。这样可以确保点在矩形内,但不会包括矩形的边界。
这种方法可以应用于许多不同的矩形构造和测试情况,虽然在开发过程中可能会遇到一些错误,但它提供了一个直观且有效的方式来处理矩形相关的计算。
计算高频实体区域的边界
相机的工作区域是一个以原点为中心的矩形。这个矩形的中心即为相机所在的点,所有的高优先级实体的位置都是相对于相机的原点进行定义的。矩形的大小是通过一个称为“工作集”的区域来表示,工作集的范围由相机所能覆盖的区域决定。实际的问题是决定需要多少个瓦片来构成这个工作区域。
矩形的尺寸通常以瓦片的边长为单位(以米为单位),然后乘以想要的瓦片数,以确定工作集的大小。具体来说,通过调整相机的矩形大小,可能会选择17个瓦片宽和3个瓦片高来定义相机的视野大小,这将覆盖相机的完整区域。
如果某个实体位于相机的矩形内,它就被认为是高优先级实体,并且可以继续保持在活动状态。否则,如果实体位于矩形外,它将不再被视为高优先级实体,而是变成休眠状态。这时,需要修改实体的居住地状态,将其从“高优先级”变为“休眠”或“低优先级”。该逻辑基于实体是否在相机视野内进行判断。
准备将靠近摄像机的实体添加到高实体集合中
在完成了相机视野范围的设定后,接下来的任务是处理实体的位置和状态。当前相机的视野范围应该能够包含那些在视野内的实体,并且在这些实体进入视野时,应该将它们的状态更改为“高优先级实体”(high entity)。与此同时,离开视野的实体应当变为“低优先级实体”或“休眠状态”以减少计算负担。
需要注意的是,在实现这一功能时,首先必须确保相机移动能够正确地调整所管理的实体状态,确保进入视野的实体能够被添加到高优先级实体集合中。而离开视野的实体则需要移除,并转变为低优先级或休眠状态。
此外,考虑到实体与瓦片地图之间的关系,目前尚未决定是否直接在瓦片地图中存储实体。最终的目标是实现一个更加动态的系统,其中瓦片地图不仅仅包含瓦片信息,还能直接管理和存储与之相关的实体。这样做的挑战在于,需要进一步考虑如何在不影响现有系统的情况下实现这一改动。当前的工作方向是逐步过渡至这种新的设计。
另一个需要解决的问题是实体的“浮动”状态。在某些情况下,实体会处于类似“热气球”状态,由重力影响,且可以通过用户输入控制其上升。这个机制与传统的跳跃行为不同,跳跃通常需要按下跳跃按钮,而这种浮动只需保持按住上箭头即可实现。
总体来看,当前的工作集中在优化实体的管理和改进瓦片地图的设计上。接下来,需要着重考虑如何高效地移动实体进出瓦片地图,并逐步实现更灵活的地图和实体互动系统。
将墙壁实体放入瓦片地图
为了让实体状态转换更加顺利,可以通过在瓦片地图上添加一些简单的墙壁实体来实现这一点。具体来说,当设置瓦片地图的值时,可以直接在特定位置添加一个实体,代表墙壁或者其他障碍物。这些实体的大小和位置可以根据瓦片的位置进行设置,确保它们正确地与地图上的元素对齐。
这种做法相对简单,因为我们已经知道瓦片的坐标,这些坐标通常是以绝对值表示的 XY 和 Z 坐标。因此,能够快速地在这些位置上添加实体,尤其是一些静态的墙壁实体。
为了实现这一点,可以通过初始化玩家或者其他小型实体,并将它们放置在地图的边界上,模拟墙壁或者防御的效果。这样做不仅可以帮助测试系统的效果,还能提供一个基本的框架,用来扩展和调整地图上的其他实体位置。
总的来说,通过在瓦片地图上直接添加实体,可以有效简化测试过程,同时为之后的系统调整和优化提供便利。
将InitializePlayer改为AddPlayer
目前的做法是通过直接在代码中添加一个玩家实体,并且初始化它。然而,这样的做法可能并不是最合适的方式,考虑到系统中可能存在其他玩家或实体的处理逻辑。一个可能的优化是,将其处理为一个 Player 类型的实体,这样可以更加灵活地管理实体的创建与使用。
在实际操作时,考虑到可能有多个实体需要添加,可以考虑为每个实体分配一个唯一的实体索引,并通过该索引来管理实体的状态。初始化时可以直接使用玩家或其他实体索引,避免不必要的重复操作。
总之,目前的实现虽然有效,但可以通过对实体的管理方式进行调整,提升系统的可扩展性和灵活性。
添加AddWall函数
在当前的实现中,目的是在游戏地图中添加墙壁。最初,计划通过添加一个函数来实现墙壁的添加,但遇到了一些问题。首先,墙壁添加函数并未被实际调用,因此墙壁并未如预期添加到地图中。通过调整逻辑,确保仅在实际需要时(即确实存在墙壁的情况下)才执行墙壁的添加操作。
此外,墙壁是通过在特定的位置(基于ABS坐标)设置来实现的,目标是将墙壁放置在特定的瓦片位置,并确保它们正确渲染。然而,问题出现在当墙壁添加到地图时,出现了一些无效的显示或绘制错误,可能是由于代码没有修剪或优化,导致多余的区域被渲染,影响了性能和显示效果。
最终,计划将墙壁简化为小方块绘制,并停用对整个瓷砖地图的绘制,以提高效率和清晰度。这一过程的优化目的是减少不必要的计算和渲染,从而提高游戏的性能和可视效果。
立即添加墙壁,而不是等待碰撞检测器
修改之前的一个bug
在这个过程中,我们发现墙壁并没有被正确地添加到高频实体集合(hi-set)中,直到我们实际调用了设置相机的函数。原因是我们一直在等待一个相机跟随的实体,直到玩家加入后,才会触发相机调用。这是因为相机调用会将所有需要的实体映射到正确的位置,实际上在没有调用相机设置之前,这些实体并没有被正确地加载到工作集(hi-set)中。
在这个过程中,当代码运行时,由于我们之前实现了一个系统,当需要查询高频的实体时,它会自动正确地进行映射。碰撞检测器也会检查所有的实体,这使得原本不可见的实体在碰撞检测时被正确加载。问题的核心是碰撞检测器的工作方式,它通过调用将这些实体映射到空间中,从而使得我们最终能够正确地显示所有实体。
为了进一步改进,计划通过修改实体类型结构来区分不同的实体类型(例如玩家或墙壁)。通过这种方式,可以清楚地识别出墙壁和玩家实体,并在绘制时分别处理。对于墙壁,我们会调用绘制矩形的函数,而对于玩家,会调用英雄绘制函数。
此外,还尝试了平滑滚动的功能,并发现可以通过调整相机的刷新频率,达到平滑滚动的效果。这使得相机可以更频繁地更新,提供更好的游戏体验。
最后,虽然在此过程中发生了一些意外情况,但系统表现出了一定的弹性,能够自动适应并解决问题。整体来说,尽管过程复杂且充满挑战,但系统现在能够按预期正确加载实体并处理碰撞检测,达到了预期目标。
将靠近摄像机的实体添加到高实体集合中
现在需要集中精力进行相机移动的设置。目标是将所有处于休眠状态的实体移到活动区域。具体操作包括遍历休眠实体,并根据它们的位置将它们移动到高优先级区域。这里的做法比较粗糙,通过检查这些休眠实体的坐标,特别是它们所在的瓦片位置,将它们搬到靠近玩家的区域。
首先,检查每个休眠实体的坐标,验证它们是否与当前相机位置相匹配,特别是通过对比实体的 AbsTileX
和 AbsTileZ
来判断它们是否处于相同的平面上。如果符合条件,则将这些实体标记为活动实体。
接着,计算这些实体是否位于相机视野的矩形区域内。为了完成这个判断,需要计算出相机视野的边界,使用 MinTileX
和 MaxTileX
等值来确定实体是否在视野范围内。如果实体处于视野内,它们将被移到高优先级区域。
此外,在实现过程中可能出现一些小错误,例如没有考虑世界坐标系的中心,可能导致实体出现一些不正确的环绕行为。但这些问题暂时不会影响系统的核心功能。
总体而言,当前的目标是使得所有符合条件的实体都能够被正确地移入活动区域,确保系统的高效运行。
游戏开始时调用SetCamera
在游戏开始时,首先需要设置相机的位置,以便正确初始化实体的位置和视野范围。希望在设置相机时,能够强制执行实体的变更,确保所有实体在游戏开始时能够正确加载和设置。目标是将相机的位置设置为新相机位置 NewCameraP
,并确保其对应的坐标 ABTileX
和 ABTileY
被正确初始化。
出现的问题是,某些实体并没有在高频集合(High Set)中正确初始化,因此需要重新检查代码,确保相机设置和实体的位置更新是在游戏开始时的最后步骤中完成的。
在调试过程中,发现存在错误的设置调用,导致相机未能正确初始化。调整后,需要确认所有的实体都已经被正确加载,尤其是那些处于休眠状态(Dormant)或墙壁状态(Wall)。此外,需要记得在设置墙壁实体时,确保其状态是否已设置为休眠。即使进行了修改,仍然发现游戏中的状态没有按预期工作。
进一步调试过程中,确认在某些情况下,环绕逻辑(wrapping)未能按预期工作,导致实体没有正确处理。这种问题可能与相机位置的计算有关,因此需要解决这个“环绕”问题。
通过调试,可以观察到,当计时器运行时,还需注意调整定时器的行为,防止它提前执行。在调试结束后,还需对该逻辑进行修复,确保所有实体都能按预期进入高频集合,避免遗漏。
MinTileX中的一些符号不是错误吗?
在这个阶段,遇到的问题是关于世界边缘的处理。我们意识到必须解决“环绕”问题,即如何在世界的边界处理实体或相机的运动。计划是明天先着手处理这个环绕问题,确保地图和实体的行为正确。
在当前的实现中,已经完成了分割一些内容,但接下来的目标是优化存储方式,不再使用重复的数组,而是将实体更合理地存储在瓷砖地图中。这将帮助我们更高效地管理实体,尤其是考虑到游戏世界的巨大尺寸。
解决方案之一是将游戏的起始点设置为世界的中心,而不是一开始就设置在世界的边缘,这样可以避免一些边界问题。随后,将在瓷砖地图中稀疏地存储实体,这有助于提高性能和存储效率。
接下来,计划是专注于解决环绕问题,确保世界的边缘处理得当,并改进实体的存储方式。对于这些工作,已经列出了待办事项,将在明天继续推进。与此同时,QA(质量保证)环节也在进行中,所有的问答都需要在预流之前提出,并且问题应该集中在当天的任务或之前完成的内容。
在调整时,发现了一些关于环绕处理的问题,虽然尝试了进行修复,但仍然存在一些瑕疵。虽然这些问题仍未完全解决,但我们相信在未来的调试中可以进一步修正。
“internal”修饰符应用于SetCamera函数是什么意思?它与非静态和静态函数有什么区别?
在代码中,内部修饰符(internal
)和静态修饰符(static
)的使用有所讨论。internal
修饰符表示函数在编译模块内是可访问的,但不可被其他模块调用,它类似于static
,这意味着该函数仅限于在定义它的文件中被调用。静态修饰符的作用则是告诉编译器该函数不需要记录它的名称,因为它不会被外部调用,从而减少了编译时的符号表大小,提高编译效率。
静态函数的主要优势在于它能够减少生成的目标文件(.obj
文件)的大小,并且优化了链接时间,因为编译器不再需要查找一个庞大的函数表(包含成千上万的函数名)。这种做法特别适用于那些不需要跨文件调用的函数。
如果没有使用static
修饰符,链接器必须处理跨文件的函数调用,尤其是在多个.cpp
文件被编译成.obj
文件后,每个.obj
文件包含对其他文件中函数的调用。这时,链接器需要修补这些调用,确保函数的地址正确,这会增加链接的复杂度。
通过使用static
,编译器能够在每个文件的编译单元中优化函数的处理,避免了不必要的函数导出,也减小了目标文件的大小,提高了编译和链接效率。这是C和C++编译模型中的一种优化手段,虽然这个过程可能显得有些复杂,但它是为了提高程序的性能和编译效率。
总之,static
和internal
的使用是为了确保模块之间的隔离,减少编译和链接过程中的开销,优化程序的构建流程。
为什么我们把墙壁视为实体?
考虑将墙壁视为实体有两个主要原因:
-
统一处理方式:希望能够以一致的方式处理所有具有碰撞信息的对象。通过将墙壁视为实体,可以确保所有需要处理碰撞的对象都可以在一个统一的列表中进行管理。这种方式简化了处理逻辑,并且更容易对所有对象进行空间排序和管理。
-
灵活性和互动性:墙壁作为实体可以具备更多的互动性。例如,墙壁可以被损坏或赋予特殊属性。如果一个具有魔法属性的物体碰到墙壁,那么这面墙壁也可以获得该魔法属性。这样,通过将墙壁定义为实体,可以更方便地进行扩展和增加互动性,使墙壁不仅仅是一个静态的障碍物。
这两点是考虑将墙壁作为实体的原因,尽管这并不意味着最终一定会这样实现,但目前是探索性的一种尝试。
瓦片块和瓦片地图结构会消失,墙壁只会作为实体存储吗?
现在考虑的是,结构将不再作为独立的存在,而墙壁将仅作为实体存储,而不再与瓦片(tile chunk)直接关联。瓦片大块和瓦片地图将继续存在,但倾向于将它们转变为存储休眠实体的容器。这意味着墙壁等实体将被独立处理,而不再作为地图的一部分,而是作为具有特定属性和状态的独立实体来管理。
我们需要做一些酷炫的屏幕震动和屏幕之间的快速平移过渡。
为了使屏幕之间的过渡更加生动和吸引人,计划实现一些有趣的屏幕震动效果和快速切换过渡)。这些过渡效果将帮助增强用户体验,使得切换场景时更加流畅和富有动态感。
会有休眠敌人吗?
此外,预计很快会进入游戏的开发阶段,包括添加生命值等游戏机制。
修复碰撞检测器中的计算错误 [代码更改]
在处理碰撞检测时,发现了一个计算错误,导致玩家能够比预期更远地移动。原本的思路是找到碰撞点并生成一个矢量,然后剪切掉与墙壁相撞的部分。然而,实际操作中,剪切后的矢量被直接应用,导致玩家移动了本不该移动的距离。
为了解决这个问题,修正后的方法是去掉原来的 T 余量,直接使用玩家的位移矢量。这意味着计算出目标位置后,更新玩家位置并重新计算玩家的位移矢量,而不是剪切之前的矢量。通过这种方式,可以避免玩家不应有的额外移动,从而修复了碰撞检测中的错误。
你计划有任何平移摄像机的游戏界面吗?
讨论中提到是否需要在游戏中实现相机平移功能。虽然最初没有计划实现这一功能,但为了确保支持此功能,考虑在某些地方使用相机平移。同时,强调了游戏应该能够在没有平移功能的情况下正常运行,因为有些人希望学习如何实现自由滚动。最终目标是确保游戏能够允许这类功能的实现。