关于确定哪些系统影响许多其他系统,并尽早将其固定下来
目前我们的游戏开发已经进入了一个关键阶段,我们觉得是时候来彻底解决 Z(深度)相关的问题了。之前我们在 Z 轴的处理上做了一些尝试,但始终没有一个明确的定论,尤其是在我们这种“2.5D”风格的游戏环境下,Z 的行为到底该怎么定义、怎么渲染,一直没有最终决定。
现在我们对 Z 的需求理解更加清晰了。因此这周的主要目标是集中处理这个问题,把渲染器的 Z 系统打磨到一个让我们满意的状态。希望能够做到让它看起来就像是一个可以用来实际发售游戏的版本,也就是说,之后不需要大幅重构、推翻现在的代码设计。
我们已经完成了很多和实体系统有关的重要设计,并且这些系统会随着项目推进不断发展完善。剩下的主要问题里,Z 的处理是我们迟迟没有定下来的部分之一。之前的一些实验性质的代码现在变成了“残留物”,需要我们整理清理,把它们变成一个正式而稳定的系统。
考虑到之后很多游戏逻辑和视觉表现都会依赖这套 Z 系统,我们必须在现在就做好足够的验证和打磨工作。如果 Z 的设计在之后被推翻,我们可能不得不重写大量基于它的代码,比如实体位置计算、图层排序、视觉遮挡效果等等,这会造成大量工作被浪费。因此我们希望在彻底依赖它之前就把它打磨好,避免未来返工。
当然在开发过程中,有些系统的影响范围比较小,哪怕后来更改也不会产生太大影响,那种情况下即使改动频繁也无所谓。但 Z 系统属于核心机制,一旦依赖范围广泛,再做修改代价就很高。
所以我们现在的目标就是,把这个 Z 系统整理清楚、打磨完整,确保未来的开发能够在它的基础上稳定进行。这是本周的重点任务。
对某些游戏设计决策可能代价高昂的看法
比如说一个典型的例子是“角色在三维世界中移动的速度”这个参数。这个看似简单的设计选择,其实牵扯到了整个游戏资源流的架构:角色移动速度与数据流媒体的加载速率直接相关。如果角色的移动速度设置得太快,而底层的媒体(比如硬盘、蓝光等)无法及时提供必要的世界数据资源,那么玩家会遇到加载延迟或“前方无数据”的严重问题。
而如果角色移动速度设定得太慢,所有美术资源可能都会被建模得过于精细,远远超出了实际所需。这样一来,假如我们在开发后期决定角色需要移动得更快,就必须重新简化这些高精度模型,使它们满足流媒体加载的速度限制。这会导致大量资源工作返工。
虽然我们可以依赖一些自动化工具链来做一定程度的调整,但在本质上,一些核心设计决策一旦做出,它们会对整个项目的资源架构、系统逻辑、渲染机制带来深远的影响。而另一些则影响较小,可以轻松更改。我们目前面对的 Z 轴系统就属于前者 —— 它影响到所有实体的位置定义、渲染排序、画面遮挡、交互逻辑等多个系统,是一个全局性依赖很强的核心部分。
我们在测试代码中已经能看到许多临时代码在 Z 的处理上非常“混乱”和“硬编码”,这不是一个可以长期依赖的结构。现在是一个很好的时间节点来彻底解决 Z 的逻辑,把它变成一个稳定清晰的系统。
我们接下来的目标包括:
明确 Z 轴用于悬停判定的行为
我们希望每一个实体始终都有一个明确的 X、Y、Z 坐标,而这个 Z 坐标要能准确反映它在屏幕上应该处于哪个视觉层级。引入“二维切片 Z 层”的概念
除了实体自己的 Z,我们还希望能有一种“Z 层”的系统,用来支持房间的垂直堆叠。即便整体是一个二维画面,但我们可以通过这种方式表现出“上下楼层”的关系。切换楼层时进行透视缩放
在单个房间中,我们不会对实体做任何透视缩放处理,因为所有美术资源都是正交投影的,如果缩放反而会让画面变得奇怪。但在房间与房间之间切换时(比如玩家从一层跳到二层),我们希望能加一点透视缩放来营造空间感,这种效果不会破坏原有画面,还能增强体验。在所有渲染相关的代码中清晰整合这个系统
无论是实体渲染、碰撞检测、图层遮挡还是悬停交互,我们都要把新的 Z 系统集成进去,确保之后不需要再重构这部分逻辑。
我们认为,现在已经进行了足够的实验和探索,对 Z 的需求和实现方式有了足够的了解,可以做出理性的决策并且稳定下来,避免未来出现大规模的系统返工。
回顾当前情况,并为确定Z轴做好准备
我们目前已经完成了多个基础系统,达成了几个关键进展:
- 已经实现了多个生物(creatures)的基本行为逻辑。
- 玩家可以在多个方向上发动攻击,角色的站立状态与行为逻辑也初步建立。
- 场景中放置了类似“石板”这样的物体,用于测试和交互。
- 我们已经不再依赖规则的网格系统,可以更自由地放置和定位物体。
接下来需要重点处理的两个问题分别是:
- Z 轴系统的完整实现
- 碰撞检测系统的完善
目前碰撞检测还处于非常初步的阶段,功能较为粗糙,需要后续补足。但当前的重点是先处理 Z 轴系统。
关于 Z 轴的当前状态:
目前我们只在一个特定场景中使用了基于透视的 Z 系统:
- 当角色从一个房间移动到另一个房间时,画面会向外拉远一点再拉近,模拟了一种“上下移动”的效果。
- 这个效果给人的感觉是角色从一个空间爬升到另一个空间,从视觉上具备一定的深度感。
- 但这只是目前唯一一个使用了“透视 Z”系统的地方,整个游戏系统还没有广泛整合这套机制。
下一步的目标:
为了更深入地完善 Z 轴逻辑,我们计划构建一个新的测试场景 —— 一个能够模拟“上下楼”过程的楼梯间结构。通过这个结构,我们可以测试和定义:
- 如何在两个房间之间实现垂直移动。
- 如何处理在垂直结构中角色的视觉位置变化。
- 如何调整透视缩放,以实现合理的空间层级感。
实现步骤大致如下:
- 搭建两个房间,一个在上方,一个在下方。
- 在它们之间放置一个类似楼梯的结构,用一系列“跳跃点”(或称为“跃点”)连接两个空间。
- 玩家角色可以逐步跳跃这些点,从下层移动到上层或反向移动。
- 这些跃点可能使用特殊物体或标记进行表示,起到连接空间的作用。
附加情况:
在测试过程中我们注意到一个小问题:
- 跟随角色的“跟班”(类似宠物或熟人单位)不断地围绕角色移动,可能对测试流程造成干扰。
- 虽然系统中原本有一个关闭跟班行为的机制,但似乎目前已经失效,不再起作用。
这个问题可能稍后也需要处理,以便在构建新测试场景时保持更简洁的环境。
总的来说,当前任务是设计并实现一套完整的跨房间 Z 层级移动机制,并将其整合进渲染系统,使未来的所有位置逻辑、空间跳跃、视觉表现都建立在这套可靠的基础之上。这样可以避免未来在这些系统上进行重复重构,为后续的大量游戏逻辑开发打下稳定基础。
game_world_mode.cpp:让 AddStandardRoom 生成一些楼梯
我们开始着手构建一个用于测试 Z 轴高度变化的房间结构,重点是模拟类似楼梯或坡道的环境,以便验证并完善 Z 轴的处理逻辑。整个过程涉及以下几个关键步骤和问题:
世界生成部分的修改
我们首先进入了世界生成系统,准备从基础房间结构出发进行修改。原本存在一个所谓的“标准房间”概念,这个概念其实并不太合理,但暂时我们以其为基础进行测试构建。
我们在生成逻辑中加入了一个“楼梯结构”的初步尝试。基本思路如下:
- 固定一个特定的
offset_x
值(例如 10),作为楼梯所在的列。 - 对
offset_y
的值进行范围限定(如 4 到 8),这段范围表示楼梯的竖直跨度。 - 在这段范围内,根据
offset_y
的值计算offset_z
,即高度偏移。每增加一个offset_y
,对应的 Z 值也增加一小段高度,模拟阶梯逐级上升的效果。
具体实现时,我们先使用一个简单的线性增长关系,比如 (offset_y - 4) * 0.25
表示高度递增。不过后来意识到这个偏移量不需要减去 4,直接使用 offset_y
就能获得所需高度。
初步测试与调试
我们发现一开始高度变化并没有如预期那样在视觉上体现出来。进一步调查后发现:
- 渲染系统当前并没有真正使用
offset_z
来偏移实体或地面的显示位置,因此高度变化没有被表现出来。 - 此外,之前的变量
offset_x
、offset_y
是相对偏移,而不是绝对位置,这导致我们判断条件失效,需要进行修正。
修正后重新测试,确认:
- 高度偏移的计算生效了,角色确实能沿着我们设置的“阶梯”区域逐步上升。
- 但上升的幅度仍不够明显,于是将阶梯高度的比例因子调大,比如将 Z 偏移计算改为更陡的梯度,提升视觉效果。
- 同时也调整了随机偏移的范围,使得地形更贴近“楼梯”形状,而不是随机起伏的地形。
对 Y 轴方向的确认
测试过程中,我们也对 Y 轴方向做了重新确认:
- 当前系统中 Y 轴是向上增长的(即数值增加表示更靠近屏幕顶部)。
- 这和部分系统的默认坐标方向(例如某些图形系统 Y 向下)相反,因此在处理空间逻辑时需要格外注意。
- 初始设置中的 Y 值递增所导致的视觉变化,与实际期望存在差异,这也是我们后续要调整的一个关键点。
最终状态
通过不断测试和微调,我们现在已经能够:
- 在房间中构建出一条向上的坡道或阶梯路径;
- 正确使用 Z 偏移值让角色在视觉上逐渐上升;
- 识别并修正了生成逻辑中的坐标偏移问题;
- 理解并确认了 Y 轴方向的定义;
- 通过调整偏移值,控制了坡道的陡峭程度。
后续计划
我们会继续基于此测试场景扩展:
- 构建更多垂直空间结构,模拟楼层或平台之间的跳跃;
- 优化渲染系统,使其更加自然地反映 Z 层级;
- 在后续添加碰撞和实体交互逻辑时,也将考虑 Z 层级对行为的影响;
- 确保未来所有需要基于位置进行判断和显示的系统,都能正确处理 Z 值。
这个阶段的目标是彻底稳定 Z 坐标的定义和用法,确保不会在未来因为逻辑不一致而出现错误或返工。
运行游戏,穿越偏移的楼梯
我们已经完成了初步的测试,在游戏中实现了基于 Z 轴偏移的“跳跃”效果,并成功模拟了角色逐步上升至更高楼层的场景。具体表现如下:
当前实现效果
- 当我们角色沿着设定的路径移动时,可以看到角色逐渐“跳上”一个新高度,表现为明显的层级提升;
- 如果踩在一条特定的链条上,角色就能跳跃到上一层,而如果踩在另一条链条上则无法跳上去,说明高度变化的判定逻辑生效;
- 整体来看,这种“爬升”过程非常接近真实楼梯或跳跃的效果,成功模拟出了“从一层跳到另一层”的体验;
- 角色的视觉反馈也随之改变,明显能看出角色在不同高度层级之间的转换。
下一步目标
当前场景中虽然成功实现了跳跃至上层,但我们还没有在高层放置新的房间。接下来的工作是:
- 在上方添加新的房间结构,以便在角色成功跳上去之后,可以看到新的空间内容;
- 当前的房间结构仍是水平方向排列(左右方向延展),这在垂直层级测试中没有太大意义;
- 我们希望能将房间 垂直堆叠,使得角色跳上去之后真正进入一个“新楼层”,而不是只是在原有房间中做视觉上的高度变化;
- 实现后,我们将能够测试角色在多个 Z 层之间上下移动的完整过程,包括房间之间的过渡、碰撞、渲染逻辑等。
小结
目前我们已经具备:
- 使用 Z 偏移创建高度差路径的能力;
- 判断并执行跳跃上层逻辑的机制;
- 验证渲染系统对 Z 层的响应;
- 角色位置随 Z 值变化而发生跳跃行为的实际效果。
接下来需要完善房间结构,使其支持真正的垂直布局,并确保房间切换或显示逻辑可以正确处理上下关系,为后续完整的空间系统奠定基础。
game_world_mode.cpp:让 PlayWorld 生成更多层的房间
我们现在已经构建好了两个房间,并准备进行垂直连接的测试。接下来的目标是确保这两个房间之间始终存在一个“向上的门”,从而允许角色在空间上从下层房间进入上层房间。我们具体做了如下设置和调整:
垂直门设置逻辑
- 明确了“向上的门”的方向值是
2.5
; - 取消了原先的随机门方向生成逻辑,避免门生成在水平(左右)方向;
- 取而代之,强制将门的方向设定为“向上”;
- 这样可以确保在房间构建过程中,每次都会生成一个位于上方的门,保证空间之间可以垂直连接。
当前实现思路
- 在世界生成器中对房间的门口方向进行固定设置;
- 避免使用
random.choice
之类的随机逻辑生成门方向,转而使用硬编码方式指定方向为“上”; - 通过设定门口方向为
2.5
,系统将自动将新房间生成在当前房间的上方; - 这样角色在游戏中只要走到上方的门口,就可以触发进入新房间的逻辑,从而实现上下楼层的跳转。
意图与效果
- 本次修改的主要目的是为了验证 垂直空间过渡 的可行性;
- 通过设定“总是向上的门”,我们可以快速测试角色从一层进入二层、从一个房间爬到另一个更高房间的表现;
- 这一逻辑为后续的多层地图结构、上下楼梯系统打下基础;
- 同时也可以与之前实现的 Z 值跳跃逻辑结合,测试角色在视觉和物理空间中的一致性表现。
总结
我们已经构建了垂直结构的基础,通过强制房间之间总是存在一个向上的通道(门),配合已有的高度跳跃逻辑,能够实现角色从一个房间向上跳跃并进入另一个更高层房间的效果。这标志着空间设计从平面布局向立体布局迈出了关键一步。后续将进一步测试房间切换、碰撞检测以及渲染一致性等方面的表现。
运行游戏,看到多层房间
目前我们成功实现了从下层房间跳跃至上层房间的功能,但在可视化表现方面还存在明显的问题,需要进一步调整和优化。以下是详细的总结:
可视化显示问题
- 当前的顶部房间图层在渲染时没有进行淡出(fade out)处理;
- 由于缺少透明度或层级的视觉处理,导致上下房间同时渲染时重叠严重;
- 这使得画面变得混乱、不易辨认,整体视觉体验较差;
- 渲染优先级似乎没有正确应用在上层结构上,缺乏层级感与空间分离。
实际测试结果
- 实际操作中我们成功实现了跳跃过程,角色可以从下层连续跳上去,进入上方房间;
- 通过连续四次跳跃,角色完成了从起始层到上层的爬升;
- 在跳跃过程中,能明显看到上方房间逐渐出现在视野中,说明房间加载逻辑是有效的;
- 跳跃完成后,角色已经完全处于上层房间内部,可以自由移动;
- 尽管空间逻辑正常,但视觉呈现因为图层未处理而影响体验。
额外的烦扰因素
- 当前系统中存在大量跟随角色的“随从”或“召唤物”实体;
- 它们不断跟随角色移动,造成视觉上的杂乱;
- 并且在测试功能过程中,这些实体干扰了跳跃和观察的专注度;
- 后续可能需要临时关闭或限制这些实体的活动范围,以便集中测试房间跳跃和视觉渲染功能。
后续优化方向
图层淡出处理:
- 需要为不同高度的房间图层添加透明度变化或遮罩逻辑;
- 可根据 Z 值动态调整图层的透明度,确保上层房间不会完全遮挡下层视觉。
视觉清晰度增强:
- 应优化房间之间的过渡动画或边界处理;
- 可能引入视觉提示,如阴影、色彩渐变等,使房间区分更明显。
随从控制机制:
- 添加选项控制随从数量或开关;
- 避免其在测试中干扰玩家行为或观察。
小结
我们已实现基础的垂直跳跃与房间切换功能,角色能够通过连续跳跃进入上方房间。系统逻辑基本稳定,但视觉呈现尚未完善,尤其是顶部房间图层未正确淡出,导致界面混乱。同时过多的随从也造成一定干扰。下一步将集中优化渲染层级、视觉透明度处理,并考虑加入调试状态下的简化显示选项,以提高整体调试效率与用户体验。
game_world_mode.cpp:扩展 TraversablePoint 的矩形填满瓦片并添加轮廓
目前我们正在对可视化层级以及可行走区域的清晰度进行改善,具体内容如下:
当前主要问题
- 地板区域(或称“可行走点”)在视觉上非常难以辨认;
- 上下层之间的深度区分不明显,特别是底部图层缺乏淡出处理,导致画面显得混乱;
- 可行走区域的渲染采用了较小的矩形区域,不易看清楚角色所处的位置;
- 使用
push_rect_outline
绘制边框后,发现边框未进行淡出处理,与整体视觉风格不一致; - 判断可能是全局 alpha(透明度)未被正确继承或渲染逻辑未遵循当前 alpha 设定。
改进措施
1. 放大可行走区域矩形
- 将原先代表“站立点”的小矩形进行了扩大,使其更接近地板砖的比例;
- 逐步尝试了不同尺寸,从 0.4 到 0.99 再到 1.2,目的是确保矩形间保持合理间距同时让可行走区域更加醒目;
- 扩大之后可行走区域变得更加清晰,便于观察角色移动路径和站立位置。
2. 添加边框以增强可视化
- 除了填充矩形以外,还尝试添加矩形边框,使用黑色轮廓提升识别度;
- 这一步骤进一步增强了地板图形的视觉边界,有助于在上下层重叠时保持分辨性;
- 但发现轮廓未随层级淡出变化,怀疑是渲染逻辑中未遵循全局 alpha 设置。
3. 排查 Alpha 淡出异常
- 测试中发现其他图形元素(如上层房间内容)确实按照预期淡出;
- 唯独新绘制的矩形边框未进行透明度衰减,推测是绘制函数未正确继承
global_alpha
; - 下一步将检查渲染逻辑中是否手动重置或忽略了全局透明度;
- 如果确认是未遵循
global_alpha
,解决方法也非常简单,只需补上相关 alpha 设置。
整体进展与阶段性目标
- 当前通过跳跃操作已能成功上楼,角色能够准确进入上层房间;
- 可行走区域视觉上已有明显提升,逻辑功能符合预期;
- 下一步主要工作包括修复淡出异常问题、清理渲染输入数据结构,以及完善渲染管线;
- 目标是建立一个清晰、结构分明、视觉统一的空间切换系统,确保多层房间之间的过渡自然、观察清晰。
小结
已完成从可行走点扩大、添加边框、跳跃逻辑测试等一系列改进,有效提升了上下层空间的识别度和实用性。当前主要遗留问题为边框未遵循透明度渲染,需进一步修正 global_alpha
在绘制函数中的继承与应用逻辑。整体来看,系统已经初具可用性,进入了渲染细节和可视化体验优化阶段。
game_render_group.cpp:让 PushRect 将颜色乘以 GlobalAlpha
我们发现当前绘制矩形边框(push_rect_outline
)时未遵循全局透明度(global alpha
)的设置,导致图层之间的淡出效果不生效。尽管其他图层如房间结构、背景元素等都正确地随层级进行透明度衰减,但矩形边框仍然以不透明方式显示,破坏了整体的视觉层级感。
为了修复这个问题,我们采用了一个非常直接的处理方式:
修复方法
在绘制矩形边框时,将当前颜色的 alpha 分量与全局 alpha 相乘,具体实现逻辑为:
- 当调用
push_rect_outline
绘制边框时,我们读取当前全局 alpha; - 将此 alpha 乘到边框颜色上,统一透明度;
- 这样不管图层深度是多少,边框颜色都会根据当前层级进行正确的淡出处理;
- 整个图层系统的视觉一致性由此得到保障。
示例处理逻辑(伪代码概念)
# 获取全局 alpha
effective_alpha = current_color.alpha * global_alpha
# 设置颜色为乘积后的 alpha
final_color = Color(current_color.r, current_color.g, current_color.b, effective_alpha)
# 绘制边框
push_rect_outline(position, size, final_color)
效果反馈
- 处理完成后,底层的可行走区域边框与上层的淡出一致;
- 可视化效果更加统一,界面不再出现突兀的不透明元素;
- 用户在多层之间跳跃、观察结构时,视觉引导更加自然流畅;
- 整个透明度系统逻辑更加健壮,避免未来类似问题。
总结
此处的问题本质上是绘制指令未正确继承渲染管线中的透明度状态,属于渲染系统中常见的细节疏漏。通过将全局 alpha 正确应用到局部绘制颜色上,问题已顺利解决。这一改动不仅提升了视觉一致性,也为后续更多 UI 与空间元素的分层渲染打下了基础。系统将持续进行细节打磨,确保所有图层在透明度、排序、结构上的协同性。
运行游戏,发现整体效果不错,但需要更明确地指定Z轴
当前整体状态已经趋于稳定,全局透明度处理已经正常生效,图层之间的淡出与视觉排序基本一致,整体视觉表现良好。但仍有几个关键问题需要处理,特别是与Z轴排序与角色跳跃逻辑相关的问题。
当前存在的问题与观察
Z轴排序错误
- 某些实体使用了硬编码的Z值,这些值导致它们被错误地排序到地面层以下;
- 正常情况下,这些实体应该出现在地面图块之上,而不是被遮挡;
- 修复Z值排序是接下来的重点任务之一。
跳跃逻辑缺乏物理限制
- 当前系统没有限制角色从一个图块跳跃到高度落差极大的另一个图块;
- 举例:由于左侧没有阻挡图块,可以直接从较高处跳跃到底层,这是不合理的;
- 需要添加物理或逻辑规则来限制垂直落差过大的非法跳跃行为。
楼梯结构带来的设计启发
- 实现了楼梯之后,空间感更明显,也暴露了原有的移动逻辑问题;
- 例如部分路径在空间结构上显得突兀或不连贯;
- 有必要在空间结构上引入更严格的可通行逻辑。
下层图层的渲染策略
- 当前的淡出策略对上层有效,但下层并未淡出;
- 实际上,下层在某些场景中(如地面存在破洞)仍需清晰可见;
- 因此决定不对下层图层应用淡出处理,以保持清晰度与空间深度感。
预期修改方向
修正Z轴排序逻辑
- 清理所有带有hack Z值的对象;
- 建立统一的Z轴排序依据,确保所有实体根据其空间位置正确渲染;
引入跳跃限制机制
- 添加高度差限制,禁止过大高度差之间的直接跳跃;
- 或增加中间判定逻辑,如台阶、坠落判断等;
渲染策略改进
- 上层图层继续保持alpha淡出,强调空间层级;
- 下层图层保持不淡出,以支持“向下可见”的设计,例如楼梯、地洞等;
测试与验证结构完整性
- 增加用于测试的“开洞”区域,以验证是否可以透过楼面看清下层结构;
- 进一步确认多层结构在视觉表现上的可理解性。
总结
系统已具备多层空间基本雏形,楼梯与跳跃行为也基本运作正常。当前的主要任务是解决Z值排序错误、跳跃逻辑过于宽松的问题,并对不同层级的图层透明度策略做出合理区分。随着结构的逐步完善,空间的连贯性与可视化清晰度将显著提升,为后续内容扩展与交互打下坚实基础。
game_world_mode.cpp:让 AddStandardRoom 生成地面的一个洞
我们正在处理游戏世界中房间生成和层级显示的问题,目标是实现一个“敲击”机制,确保在特定位置不生成任何内容,从而避免某些区域出现不合适的地面或物体。
细节描述和问题分析
敲击区域定义
- 通过判断全局偏移量 offset_x 是否在 $$-5, -3] 区间内,来控制是否阻止该区域生成内容;
- 这样做可能会不小心把角色站立的地板给去掉,存在潜在的错误风险;
- 同时还考虑了 asset_y 和 offset_y 的条件,比如 asset_y ≥ -3 且 offset_y = -2,来决定是否允许相关内容生成。
站立点的特殊处理
- 由于部分区域被禁止生成内容,角色尝试站立这些位置时会出现问题,导致游戏逻辑异常;
- 需要额外处理这些站立点,避免因地面消失而引发BUG。
怪物和物体位置调整
- 有些怪物(如蛇和其他怪物)的位置需要手动调整以避免出现在“洞”或空缺区域内;
- 例如将怪物所在的房间编号、坐标向上或向外移动,确保其不阻挡视线或掉落到空洞中。
视角与显示层级
- 希望能够透过“洞”看到下层的世界;
- 需要调整视角偏移量,特别是offset_y,来让视野能够覆盖到较高或较低的层级;
- 设定offset_y在 0 到 -1 区间以达到想要的显示效果。
新增额外楼层
- 计划在当前楼层之上增加一个额外的楼层;
- 这样可以实现当玩家往上爬时,看到上层内容,同时上层透明度逐渐淡出显示下层结构;
- 需要在房间列表中添加第三个房间,并确保楼层间连接和门的逻辑正常。
门的生成与层级排序
- 目前对门的Z轴排序和生成逻辑不完全确定,可能存在逻辑冲突或错误;
- 需要继续调试门的生成机制,确保玩家能够顺利通过楼层之间的通路。
目前阶段目标与计划
- 实现特定区域的“敲击”功能,避免在指定区域生成地面和物体;
- 调整怪物和物体位置,防止它们出现在不合理的空洞或重叠位置;
- 设定视角偏移,确保玩家能够从当前层级透视到下层或上层,增强空间感;
- 在楼层系统中添加新的楼层,实现多层楼空间的渲染与交互;
- 继续排查门的生成及排序问题,确保楼层间能顺利衔接。
整体目标是通过精细调整生成规则、物体布局和视角偏移,完善多层空间的表现和交互,使得玩家能够在有“洞”的多层房间结构中,流畅地上下移动并保持良好的视觉体验。
运行游戏,从洞口往下看
我们发现如果不限制玩家在楼梯上的跳跃行为,游戏体验会变得很糟糕,容易选错目标。因此,必须确保玩家不能从楼梯上随意跳下,这样能避免意外触发错误操作。
目前能够看到楼梯下方的另一层,并且希望这层能有淡出效果,但淡出效果还没有做到理想状态,切换时显得不自然,视觉体验还需改进。
此外,摄像机的缩放和视角变动有些异常,尤其是头部动作带来的影响,导致摄像机表现不稳定,后续需要排查并优化摄像机的运动和位置。
最后注意到游戏中蛇的行为异常,蛇竟然沿着整个楼梯向上移动,这种情况不符合预期,需要进一步调整怪物的路径和行为逻辑,防止它们做出不合理的动作。
game_world_mode.cpp:让 UpdateAndRenderWorld 把瓦片画成绿色
当前的眼睛形状标记颜色非常刺眼,尤其是那种橙色,在大面积方格上看起来很不舒服。为了改善视觉体验,决定将站立位置的标记颜色换成更容易辨识的颜色,同时调整其他眼睛标记为更柔和的颜色,比如50%灰色,避免过于抢眼。
考虑到环境是森林,还计划给标记稍微加一点绿色的色调,让整体看起来更自然、更和谐,增添一些绿色的小点缀,使画面显得更生动但不过于刺眼。总体目标是让标记既清晰又不至于让人视觉疲劳。
运行游戏,思考向下绘制那么远的问题
现在情况逐渐明朗,我们发现了一些需要改进的地方。首先,角色不能跳到某些低于容许高度的地方,这点是好的,避免了不合理的跳跃。
接下来,需要调整画面中层级的渐隐效果,目前的渐隐似乎做得有些过度,不希望渐隐范围过大,因为那样就得模拟更多在更深层区域的人物和物体,增加复杂度。不过这也有趣,因为想象玩家下楼进入地牢,能看到下方两层的场景,比如看到一个魔法剑,体验感会更好。
因此,考虑渐隐效果时,可能不应该完全渐隐到透明,而是让透明度降低到某个非零值,保持一定的可见度,这样既能突出当前层级,也能模糊显示下方较深层的内容,让玩家感受到空间深度,但不会完全看不见底层细节。
game_world_mode.cpp:让 UpdateAndRenderWorld 扩展 FadeBottomStartZ 和 FadeBottomEndZ
我们决定让底层的渐隐效果更早开始,也就是说从较近的位置就开始变得模糊,但不会很快完全消失,而是一直延续到更深的地方,形成一种朦胧的雾气感。在地牢场景中,这种效果可以让画面渐渐变暗,甚至变成黑色,营造出阴暗、神秘的氛围,这样看起来会更有趣也更真实。
为了测试效果,我们尝试堆叠多个房间,比如连续堆叠六个房间,形成一个巨大的垂直空间堆叠,虽然没有特别的实际用途,但这样可以更好地观察渐隐和空间叠加的效果。
然后,进入这个堆叠的房间区域,准备继续观察和调整细节。
性能分析:注意 BeginSim 和 UpdateAndRenderWorld 执行较慢
我们注意到在游戏运行过程中存在一些“慢”的角色或系统组件,尤其是在 BeginSim
、Update
和 RenderWorlds
阶段表现出明显的延迟。为此,我们决定查明到底是哪些部分导致了性能瓶颈,找出这些“慢”的因素并进行优化,从而确保整体运行更加顺畅。
尤其在 Debug 模式下,这些问题尤为明显。虽然 Release 模式运行非常快,即使在当前状态下也表现良好,但我们并不倾向于总是使用 Release 模式进行开发调试,因为很多人虽然习惯直接构建发布版本,我们更喜欢使用 Debug 模式进行开发以便更清晰地跟踪问题。因此我们更希望在 Debug 构建中也能获得良好的性能。
怀疑的一种可能性是某些无谓的“便利性操作”,例如重复将数据清零或其他额外操作,这些在 Release 中被优化掉,而在 Debug 中仍然存在,因此造成性能差异。如果我们能够将这些不必要的处理剔除,就能明显改善 Debug 构建的运行速度,提升开发体验。
在表现方面,渐隐效果已经达到了预期的视觉效果——我们能清楚地看到从当前楼层向下延伸的多层空间,每层的透明度随着深度增加而降低,形成类似雾气的视觉层次。这种效果非常酷,也很有新意,尤其是在洞口处观察时非常震撼。我们能看到当前所在的楼层,然后是下一层,再下一层,甚至还能看到某个敌人正在最底层活动,带来极强的空间感和视觉冲击力。
这种设计不仅增强了画面表现力,也为玩法提供了新思路。比如说,玩家可以通过这种“视觉穿透”提前看到下一层的关键物品或敌人,像是一个魔法剑静静地躺在远处某层,这为策略性提供了极大帮助。
这是大多数 2D 游戏中罕见的视觉体验,基本没有游戏实现过如此立体而通透的多层渲染效果。我们觉得这非常值得保留,尤其在“坑”类场景中表现特别好,未来可以应用于更多场景中作为视觉设计的一部分。
当前的状态让人满意,蛇也已经下到洞里,空间叠加效果清晰可见。后续准备在剩余的二十分钟内,尝试对系统做一些性能优化,进一步提升 Debug 模式下的运行效率,避免每次都依赖 Release 构建,这样开发过程就会更流畅。
使用 build.bat 切换到 -O2 优化后,游戏运行达到60帧每秒
我们注意到在构建设置为 Release 模式后,游戏运行可以轻松达到 60 帧每秒,并且消耗的处理时间极低。帧时间几乎全部被操作系统的等待占用,说明程序本身运行效率非常高,基本不占用 CPU 时间。这与 Debug 模式下的表现形成鲜明对比,在 Debug 模式中,我们甚至无法稳定维持 60 帧每秒。
这一巨大差异说明 Debug 模式下某个操作或模块的开销极高,影响了整个游戏的性能。我们计划调查到底是哪一部分在 Debug 模式下造成了如此大的性能瓶颈。初步设想是可能存在某些在 Debug 下未优化的计算或操作,比如某些临时缓冲区、零初始化、大量断言逻辑等,如果能替换或屏蔽这些“开销重”的部分,我们可能可以仅优化这一小块代码,而不是整个系统,从而让 Debug 模式也可以接近 Release 的运行效率。
如果成功,我们在开发过程中就可以使用 Debug 模式调试,而不必频繁依赖 Release 构建,这将极大提高开发效率和灵活性。
此外,我们还希望保持当前的场景状态不变,让所有需要后续解决的问题保持可见状态,这样本周剩下的工作就有明确的目标和上下文,非常适合作为继续开发的基础。
当前在 Release 构建下效果非常流畅,完全达到 60 帧。而如果切换回软件渲染(software renderer),表现就会大幅下降,因为软件渲染性能本身很差。我们想看看在这种极端模式下性能有多差,从而了解各渲染路径的负担和瓶颈。
接下来我们计划定位 Debug 模式的性能瓶颈,并尝试以小范围优化的方式解决它,同时保持现有的渲染结构和调试便利性不变。整体策略是在保留完整功能和可调试性的前提下,提高 Debug 模式的可用性和响应速度。
性能分析:切换到软件渲染器
软件渲染器的表现非常糟糕,完全无法处理屏幕上出现的大量重叠绘制(overdraw)内容。我们可以理解,因为软件渲染器本身处理效率低,如果屏幕上有多个图层叠加绘制,它的处理能力就很快达到极限。
理论上,我们可以通过优化来让它“勉强跑起来”。例如,我们可以对瓦片(tiles)进行包围盒剔除(box culling),提前判断哪些瓦片区域在视野外,就不进行绘制,从而减少不必要的计算。这是后期可能会回头做的内容,作为一个游戏完成后的“有趣的”优化项目:看看是否能让软件渲染器也稳定运行在 60 帧每秒。
然而当前状态下,一旦画面中堆叠了过多图层,对软件渲染器来说负担就太重。令人意外的是,我们实际上甚至还没有对“填充矩形”的操作做任何优化,这意味着现阶段的绘制路径都走的是低效处理流程,甚至没有进入本该更快的优化通道。所以我们无法得出软件渲染器到底有多慢的准确结论。
尽管如此,我们可以合理推测:即使填充操作被优化了,软件渲染器仍然无法支撑这样多层级的渲染。它本质上就不是为这种复杂情况设计的,无法高效处理多层次透明绘制或深度穿透的可视化内容。
总之,我们清楚地认识到:当前这种叠层过多的渲染结构对软件渲染器来说是不可承受之重,即便做了优化,也依然可能力有不逮。不过从技术挑战角度看,未来完成游戏后可以作为趣味挑战来尝试提升其表现。
win32_game.cpp:在 Win32DisplayBufferInWindow 中添加更多的 BEGIN_BLOCK 和 END_BLOCK 代码块
现在的主要目标是弄清楚具体是哪些部分导致性能消耗如此之高。
首先,确认了一点:begin_sim
这一块是时间开销非常大的地方。为了更深入了解,我们对代码进行分段分析。比如,frame_display
这个指标表面上看是等待操作系统的时间(VSync 同步),但实际上并不全是,它也包含了 OpenGL 的渲染指令排序时间,因此我们不能简单地将它等同于“等待操作系统”。
接下来,我们决定做一个更加精细的时间分块测量。我们在 win32_display_buffer_in_window
的相关渲染函数中加入了多个 begin_block
和 end_block
,分别记录:
- 交换缓冲区的耗时(Swap buffer)
- OpenGL 渲染命令的耗时
- 其他各个模块的耗时情况
这可以帮助我们真正看到哪个步骤到底花了多少时间,比原来粗略地看整体执行时间要有价值得多。
然后进行了完整的重编译和重启(因为这些分析是在平台层中添加的),观察到以下结果:
- OpenGL 渲染命令占用了大约 30% 的帧时间
- 显示缓冲区(Swap buffer)大约占用了 14%
begin_sim
部分接近 20%update_and_render_world
部分占据了大约 25%
这些数据帮助我们识别出了几个重点关注区域。尤其是 begin_sim
和 update_and_render_world
,这两个函数在逻辑模拟与渲染方面负担较重,因此决定进一步把这些模块也再拆成更细粒度的时间片段,以明确具体是哪些子任务导致了高负载。
总体来说,这是一次针对性能瓶颈的系统性分析准备工作,目的是定位 debug 模式下严重掉帧的根本原因,并寻求优化方法,从而不必每次都依赖 release 构建来维持 60 帧每秒。
game_world_mode.cpp:在 UpdateAndRenderWorld 中添加更多的 BEGIN_BLOCK 和 END_BLOCK 代码块
我们继续对 update_and_render_world
这一函数进行细化分析。这个函数目前是一个整体计时的块,但并不能清晰反映具体耗时点,因此我们决定进一步拆解其内部流程。
首先,在“执行 AI 逻辑”相关的部分添加了一个新的计时块,标记为 execute_brains_and_work
。这一块主要用于记录所有“思考”或决策逻辑所占的时间。通过这个分段,我们可以明确了解 NPC 或敌人等实体的行为计算到底消耗了多少资源。
然后,在处理其他实体(例如运动、碰撞、状态更新等)的部分,再加了一个计时块,标记为 simulate_entities
。这样我们就可以区分出“智能行为”与“基础模拟”在性能消耗上的差别。
如果有需要,还可以将这些部分提取成独立函数,不仅让结构更清晰,也便于后续做更细致的调试与优化。
通过这些调整,我们能够获得更加精细的性能数据,有助于定位性能瓶颈,特别是在 debug 模式下提升运行效率。目标是尽量不依赖 release 模式也能保持较高帧率,从而在开发过程中能更灵活、更高效地调试和测试。
运行游戏,查看性能计时,发现大脑处理部分耗时不多
现在查看性能分析结果后,发现 begin_sim
和 simulate_entities
相关部分的性能开销较大。首先可以明确的是,AI 行为逻辑(brains)并不是造成性能瓶颈的主要原因。从分析图中可以看出,AI 执行时间占比非常小,几乎可以忽略不计,这至少排除了一个可能的问题源头。
相反,排在最前面的几个耗时操作才是当前影响程序运行速度的关键因素。它们占据了大约整个可执行程序时间的 70% 左右。这些部分通常包含一些比较重量级的调度、渲染前准备、数据结构遍历、实体状态更新等流程。在 debug 模式下,这些操作本身由于缺乏优化,加之编译器并未进行任何代码简化处理,所以显得尤其低效。
至于某些如 deep_begin
等系统层级或框架调用,由于其本身在 debug 构建下就较为沉重,并且缺乏优化处理,因此我们也无法直接改善其性能。
总结而言,当前的性能瓶颈主要集中在渲染与实体模拟逻辑的高层部分,而不是 AI 或底层调度逻辑。后续优化方向应聚焦于减少这些重度部分的调用频率、缩减计算量,或者对其中部分流程引入轻量化替代方案,以提升整体帧率表现,特别是在 debug 构建环境中。
game_debug.cpp:让 DrawTopClocksList 计算并显示累计百分比
我们想到一个不错的调试改进方案,并决定立刻实现它,而不是只停留在“这可能会很酷”的想法上。
我们在调试信息的显示部分,尤其是用于展示各项操作耗时百分比的代码里,加入了一个新的视角:累计百分比(cumulative percentage)。这个想法是这样的——在原有每项操作各自所占用时间比例的基础上,逐项累加,显示出到当前为止所有操作所占的总执行时间百分比。这能帮助我们更直观地看出哪些操作加起来占据了绝大多数的执行时间,从而快速定位瓶颈。
为了实现这个,我们首先在循环体中引入一个累加变量 running_sum
,它从零开始。每次遍历一个操作项时,就将该项的时间百分比加到 running_sum
上,并将其输出。这样在打印调试信息时,除了原有的单项百分比之外,还能看到一个逐步上升的累计值。
例如:
- 第一项显示为“占 21%”,那么
running_sum
就是 21%; - 第二项加上后累计是 41%;
- 再下一项后累计变成 57%;
- 以此类推,直到累计接近 100%。
这个新增的调试信息能让我们更清楚地看到,某些前几项可能就占了 70%、80% 的执行时间,而后面的几乎可忽略。这样我们在优化时能更聚焦,而不是盲目分析所有函数。
除此之外,我们也在考虑如何让这种调试输出方式不变得臃肿或对性能造成影响。比如,我们计划避免在每一帧都进行大量字符串拼接或格式化操作,并将其设计得尽量简洁有效。
总之,这是一个能提升调试体验和优化效率的小改动,感觉非常值得保留并继续扩展。
game_debug.cpp:让 DrawTopClocksList 调用 AddTooltip 显示这个百分比
我们本来实现过一个可以显示悬浮提示文本的功能,于是决定回头把这个功能用在调试信息上,让我们可以在鼠标悬停在某个区域时,显示该区域对应的累计执行时间百分比,增强交互性和可视化效果。
我们开始回顾旧代码,发现悬浮文本应该是基于一个矩形区域判断的。在我们画出一条条调试条目时,每一个 TextOut
操作应该会返回一个文本所占的矩形区域。我们希望拿到这个矩形,然后在鼠标在其上方时显示相应提示。
我们的计划是:
- 用
TextOut
绘制每条调试信息并获取其返回的矩形区域; - 检查当前鼠标位置是否在该矩形内;
- 如果在,就调用
AddTooltip
,显示像“当前累计时间百分比为 XX%”这样的提示。
我们很确定 TextOut
应该返回一个矩形区域——毕竟绘制了文本,自然也就知道它的布局范围。但是经过检查,发现 TextOut
实际上没有返回这个区域。我们很不理解这一点,因为从多年开发经验来看,这是个理所当然的行为。我们追踪代码逻辑,看到 TextOut
在内部通过合并所有字形的边界来构造了一个矩形范围,但却没有将其返回。
于是我们决定动手修复这个问题,让 TextOut
把构造好的文本矩形返回回来。
在修复后,我们再次运行程序,并尝试在调试信息上方悬停鼠标,希望能看到悬浮提示。但功能没有如预期那样工作,鼠标悬停并没有触发提示显示。
我们接着排查,查看是否是因为鼠标事件检测出错。确认鼠标坐标和矩形检测逻辑都存在,并且 AddTooltip
的调用也确实发生了,但仍然无效。我们开始怀疑可能与图形变换有关,比如是否在某处对绘图坐标应用了变换,导致鼠标与矩形的位置不再对应。
不过此处逻辑并没有明显的变换操作,所以也不确定问题出在哪。我们准备进一步调试这个功能,但暂时还没有一个明确的线索。
总之,虽然理论上所有条件都满足,但悬浮提示功能并未生效。我们准备继续追踪这个问题,看看是哪个环节出现了逻辑偏差。整体目标是,让调试信息交互更丰富,快速理解每个部分的性能占比。
调试器:进入 DrawTopClocksList,调查 TextRect 无效的原因
我们调试悬浮提示文本时,注意到一个关键问题:矩形范围 text_rect
从未检测为“命中”,也就是说,鼠标从未被识别为在该矩形内。因此我们推测 text_rect
本身可能根本无效。
我们断点查看这个 text_rect
的值,果然发现它不是一个有效的矩形,看起来像是一个默认的“反转无穷大矩形”,这通常用于初始化时表示空值。这个发现说明我们绘制文本时,根本没有正确记录下绘制区域的范围,或者说没有构造出正确的文本边界矩形。
我们追踪相关逻辑,来看 TextOut
内部是如何生成这个矩形的。代码中我们看到:
- 矩形变量初始化为反转无穷大矩形(即没有任何范围);
- 接着遍历要绘制的字符(code points);
- 但过程中一直在绘制空格字符;
- 而在渲染空格时,逻辑并不会真正生成字形(glyph),也就不会有 bitmap 或实际边界;
- 所以最终矩形区域仍然是初始化状态,未被更新;
- 因为没有真正“绘制”出任何字符,也就没有将任何有效字形范围合并进矩形。
也就是说,如果一段文本只是由空格组成,或者其中某些字符由于某些原因未渲染成功,那么就不会生成有效的矩形。
我们意识到这其实是不合理的,因为在 push_bitmap
阶段,实际已经获取了要渲染的字形信息,只不过这部分逻辑并没有返回任何关于边界的信息,而我们可以在那里顺手记录下 bitmap 的尺寸、位置等信息来形成矩形范围,成本是极低的。
问题在于,我们当初设计时为了让渲染和测量共享同一个路径,没有让 push_bitmap
返回任何信息。但事实上,在执行 push_bitmap
的时候,已经拥有了构造矩形所需的全部信息——却没有利用起来。
这暴露出一个设计上的小问题:出于结构统一的考虑,忽略了一个容易获取的副产物(渲染区域),从而导致我们在调试悬浮提示等功能时遇到很多困难。
最终我们得出结论:应当在渲染阶段(比如 push_bitmap
)记录并返回位图的位置和大小,用于构造文本边界矩形,这样就可以正确处理文本区域判断了。这个问题看似小,但对于调试工具的准确性和交互体验有直接影响。
game_debug.cpp:让 DrawTopClocksList 根据 GetTextSize 设置 TextRect
我们遇到了一些对自己能力的怀疑,虽然感到可能在某些地方做得不够聪明,但决定不去深究这种“蠢不蠢”的问题,而是选择积极面对,以“我们是赢家”的态度继续前进。带着这种略带自嘲又释然的情绪,我们继续分析代码逻辑。
当前的目标是利用 get_text_size
来获取文本的尺寸,以便构建正确的文本矩形,从而支持悬浮提示的显示。但过程中我们发现:
get_text_size
这个函数并不像我们一开始想象的那样,接受三个参数;- 实际上它只接受两个参数:一个动态字符串类型(dynamic string)和一个普通字符串(string);
- 它并不关心文本的位置,因为尺寸是跟内容有关,与绘制位置无关;
- 所以我们不能直接用它来返回绘制位置;
- 获取完尺寸之后,如果我们要在屏幕上构建完整的矩形,仍然需要自己手动将尺寸加到文本绘制起点上进行偏移运算;
- 换句话说,
get_text_size
只提供宽高,我们还得配合绘制起始点计算出左上角和右下角,才能形成一个真正有效的矩形区域。
这一过程说明,如果我们想要准确处理悬浮提示区域检测,单纯用文本尺寸还不够,必须结合具体位置进行偏移。同时也揭示出当前工具接口可能在设计上略有欠缺,不够方便地提供了位置 + 尺寸的完整信息。
尽管过程有点混乱,但整体思路是清晰的,我们正逐步厘清接口功能和数据流,并考虑如何以最少代价获得所需信息。面对一些不尽如人意的细节设计,我们选择理解它、包容它,并尝试用合理的方式弥补。这就是一种典型的工程思维路径:自我调整、工具反思、边做边修。
game_debug_ui.cpp:新增带有 v2 At 参数的 GetTextSize 版本
我们意识到目前缺少一个实用的工具函数来完成“获取带位置偏移的文本矩形”这一操作,而这实际上是非常常用且必要的功能。既然每次绘制文本时都会涉及位置位移的计算,那么将这个位移计算封装成一个统一的工具函数就显得尤为合理。
如果不做这样的封装,就相当于在每次需要的时候都重新实现一遍相同的数学运算逻辑,而这些计算我们其实早已在绘制逻辑中做过。不断重复这些操作既浪费资源,也容易出错,因此我们决定将已有的偏移处理逻辑保留下来,并将其整合为一个可复用的工具函数。
通过实现这个函数,我们就可以随时方便地得到一个准确的、带位置偏移的文本矩形,进而在需要的地方(例如悬浮提示功能)进行命中检测、区域判断等操作,而无需再去手动重复计算尺寸和位置。
完成这一步之后,我们验证了效果,现在结果明显更好了。可以清晰地看出我们想要的“分界点”或区域划分逻辑已经生效,文本与对应的矩形区域精确匹配,功能如期运作。整个流程更加合理、实用性更强,同时也为后续系统性功能的扩展打下了基础。
game_debug_ui.cpp:让 AddTooltip 绘制背景
我们发现当前的提示框(tool tip)缺乏背景渲染,看起来不够清晰直观,因此决定为提示框增加一个背景矩形,以提升可读性与视觉层次感。
目前文本是通过 text_out
函数绘制的,相关代码已经正确计算了文本边界(text bounds),但问题在于,虽然有了文本的尺寸和位置数据,却并没有实际绘制对应的背景矩形。换句话说,文本直接显示在透明的界面上,缺乏与背景的视觉分离,显得非常突兀。
我们准备在现有 text_out
的代码段中加入一个背景矩形的渲染操作。绘制方式参考已有的 UI 元素风格,比如使用一个透明度适中的矩形作为背景层。这种背景通常是浅色或深色的半透明框,能起到突出的效果,同时不会遮挡下层界面的重要信息。
此外,为了保证渲染顺序的正确性,也考虑了 Z 轴排序的问题(即层级关系)。目前提示框可能处在一个不够靠前的图层上,导致它被其他元素遮挡或不够显眼。我们计划调整其 transform 或绘制时的 Z 值,使其始终处于顶层,优先显示。
我们也意识到当前系统在 Z 轴排序的管理方面还有不少可以改进的地方,未来可能需要更加明确的图层机制或排序策略,以便所有 UI 元素都能按照预期的层级关系进行正确的叠加显示。
总结来说,我们添加了提示框的背景矩形,使其视觉表现更清晰,并考虑了渲染顺序和图层优先级问题,为更完善的 UI 交互体验打下基础。
game_debug.cpp:让 ToolTipTransform 显示在所有内容之上
我们注意到当前的 UI 渲染层级系统并不是特别理想,尤其是在处理提示框(tool tip)等临时性元素时显得过于繁琐。虽然系统允许通过某种工具函数进行坐标变换和图层控制,但这些流程过于正式化,而我们真正想要的其实只是实现一种简单的优先级规则——比如提示框应该始终在所有其他元素之上。
目前实现中,这种“在所有之上”的逻辑似乎是硬编码在某些排序结构中的,例如通过某种 escape 值来决定提示框的显示顺序。不过具体实现并不清晰,可能是为了快速达到效果而直接在排序逻辑中插入了提示框的优先级判断。
在视觉效果方面,当前的提示框已经加上了背景矩形,能更好地与下方内容区分开来。虽然目前的样式已经比原来清晰很多,但仍然存在一些小问题,比如背景区域偏小、透明度不够或颜色太浅。因此,我们决定对提示框背景进行进一步优化。
具体操作包括:
- 增加提示框背景矩形的圆角半径,让它显得更加圆润自然;
- 扩大背景区域的边界范围,使文本与边缘之间留出更多空间;
- 使用更深一些的颜色或更高的透明度值,增强可读性;
- 调整绘制函数
push_rect
的调用,给定更合适的v2
参数用于控制矩形大小和位置。
这些修改都是为了提升提示信息的视觉效果和可读性,使用户在使用过程中能更直观地接收到界面反馈,而不必为浮动信息的可见性而困扰。
整体来说,我们简化了图层控制逻辑,使提示信息始终处于上层显示,并增强了提示框的 UI 视觉风格,确保其具备更好的可读性与交互体验。
运行游戏,查看提示框
现在我们已经能够更清晰地看到提示信息区域扩大后的效果,提示内容也更易于阅读。借助这些改进,我们能更直观地浏览性能数据,并快速识别程序中耗时最多的部分。
从分析图中可以看出,一旦执行流程进入 DebugEnd
,程序总时间的 80% 左右已经消耗掉了。而 DebugEnd
本身是我们无法优化的部分,因此可以得出结论:真正的优化目标在其之前的内容里。这意味着,大量性能瓶颈并不在调试收尾阶段,而是在更靠前的执行逻辑中。
另外值得注意的是,在关闭性能分析界面时,程序运行时间明显下降,这说明大量调试相关代码在非活跃状态下并不会造成性能负担。这进一步表明,调试信息处理本身并不是最主要的瓶颈。
从当前数据来看,SimulateEntities
是最明显的性能热点。这部分的运行时间占比非常高,因此极具优化潜力,应该优先考虑对其进行性能改进。
在细分分析中:
GetClosestReversible
的性能瓶颈已知,属于空间查询操作,因此可以使用空间加速结构(例如八叉树、KD 树、网格划分等)来显著提升性能。OpenGLRenderCommands
部分在调试模式下比较耗时,虽然这部分可能不容易优化,但由于涉及遍历、排序等操作,仍可尝试挖掘是否存在冗余处理。BeginSim
这一函数较为疑惑,它在调试模式下表现出高开销,但在非调试编译时几乎完全消失。推测这部分耗时可能来自于数据的复制操作,但也不排除是其他更隐蔽的逻辑导致,比如指针连接或缓存错失等。
为此,有必要深入 BeginSim
函数,明确到底是哪一部分代码占据了大量时间:
- 是复制结构体时的内存操作消耗?
- 还是连接实体指针过程中的某些隐藏计算?
- 是否涉及一些在调试模式下才启用的耗时校验逻辑?
这些都是需要进一步测试验证的方向。
总的来说,目前的分析已经帮助我们:
- 明确了主要性能瓶颈集中在
SimulateEntities
前段; - 排除了
DebugEnd
和调试渲染信息作为主要问题; - 明确了若干可优化方向,比如空间查询加速、图形命令精简;
- 确立了下一步的调查重点,即深入分析
BeginSim
的具体耗时点。
后续的优化工作将围绕这些结论展开,逐步定位并解决核心性能问题。
game_sim_region.cpp:在 BeginSim 中为 SimUnpack 添加 TIMED_BLOCK
我们可以先不急于将这一部分设为已知代码块,而是通过更灵活的方式采样性能数据。具体来说,我们可以在 SimUnpack
阶段内手动插入一个新的时间采样点。
这么做的目的,是为了获取一个干净的基准点,避免当前时间测量中包含之前残留的无效或干扰数据。通过这种方式,能够更精确地识别 SimUnpack
本身的实际耗时,而不会被其他部分的测量误差干扰。
也就是说:
- 在进入
SimUnpack
时,手动插入一个新的TimeBlock
,确保该区域的计时是独立的; - 这种方式实际上类似于插入一个额外的性能采样点,用于对照和分析;
- 可以在不破坏现有逻辑的前提下,帮助我们更快找到潜在的性能瓶颈;
- 这对判断
SimUnpack
中是否存在非预期延迟尤其有用,特别是在调试模式中采样不稳定的情况下。
整体而言,这是一个非常直接且有效的分析手段,有助于我们更清晰地定位问题发生的具体阶段。接下来可以基于这些新的采样点进一步验证性能瓶颈是否确实来源于 SimUnpack
,或者是否还有其他环节被误判为热点。
game_sim_region.cpp:让 BeginSim 中的 PushArray 调用不清空数据(NoClear),并确认所有必要数据已初始化
通过观察与分析,我们发现 BeginSim
占用了约 16% 的执行时间,却没有实际执行太多有意义的操作。这表明其开销可能来自一些底层的内存管理操作,比如大规模内存清理。
初步怀疑集中在 Clear
操作上,因为它可能正在清理一块非常大的内存区域,例如 arena 上的大量堆栈数据结构。为此,我们进行了一个实验:尝试将一部分内存不再使用 Clear
清理,而是手动初始化,以观察性能是否提升。
具体步骤如下:
确定哪些数据结构不再依赖自动清零:
- 包括
entities
、brains
、max_out_of_bounds_radius
、max_entity_velocity
、bounds
、update_bounds
、entity_count
、max_brain_count
等数组或变量; - 明确这些内容在
Unpack
过程中都会被显式赋值,因此可避免重复清理;
- 包括
将它们的内存分配方式改为
no_clear
;对所有这些字段逐一检查,确保每一个都在初始化路径中被完全赋值,避免出现未定义行为;
最终保留了哈希表(
hashes
)的清零逻辑,因为这些结构依赖初始化为零的特性以实现正确的查找逻辑,不可跳过;验证后确认:
- 阵列类结构可以通过手动赋值跳过清零,从而节省大量无谓的内存操作时间;
- 哈希类结构仍需保留清理,避免逻辑错误。
结果表明:
- 在跳过大部分
Clear
操作之后,性能明显改善; - 核心瓶颈确实来源于大量不必要的内存清理操作;
- 有选择地精细控制内存初始化逻辑,可以显著减少运行时开销;
- 而那些确实依赖于清零行为的数据结构,仍需谨慎保留其初始化过程。
结论是,合理剖析和拆解内存使用路径,并配合手动初始化手段,有效降低了 BeginSim
的性能负担,从而使得整体系统表现更加高效。
会出现下面的错误不知道什么原因
性能分析:BeginSim 执行时间显著减少
经过一系列优化之后,当前的性能表现有了显著提升:
- 将
BeginSim
中的数组清除操作从原来的默认Clear
改为手动初始化,使其在调试模式下的耗时从原本的 10%-15% 降低到了 0.84%; - 说明在未优化编译状态下,自动生成的清零逻辑性能极差,尤其是处理大数组时,导致了大量不必要的资源消耗;
- 而在优化编译状态下,编译器生成的清零逻辑效率更高,因此不会表现出这么严重的性能瓶颈。
完成这项优化后,整体帧率在关闭所有调试信息的情况下可以达到 30FPS,但仍未达到理想的 60FPS。
下一步聚焦在两个主要问题:
1. SimulateEntities
函数开销仍然非常高:
- 这是当前最大的问题点,占用了大量总执行时间;
- 初步判断其内部逻辑复杂或迭代密集,可能存在算法效率或数据访问路径上的瓶颈;
- 需要进一步深入分析它的实现过程,找出耗时最集中的子模块进行针对性优化。
2. DebugCollision
占比上升:
- 在开启调试信息后,该模块的资源占用显著增加;
- 虽然可以暂时通过关闭调试碰撞显示来回避问题,但这不是一个根本解决方案;
- 可能需要增加一个机制,用于在运行中动态控制是否启用碰撞调试信息,以便在开发时灵活使用;
- 也可能需要改进碰撞调试的渲染或数据获取逻辑,减少其对 CPU/GPU 的压力。
当前结论:
- 通过避免不必要的数组清零操作,在调试模式下成功解决了
BeginSim
的性能瓶颈; - 当前主要瓶颈已经转移到了实体模拟模块和碰撞调试模块;
- 接下来需要重点分析
SimulateEntities
的内部逻辑,找出造成低帧率的核心问题; - 若能解决该模块的性能开销,预计有机会进一步提升到 60FPS;
- 此次优化成果显著,表明调试模式下对内存操作的控制对性能影响极大。
整体而言,这是一次非常成功的性能调优阶段,下一步进入深入算法与逻辑层面的分析和重构。
game_world_mode.cpp:在 UpdateAndRenderWorld 中为实体渲染和实体物理添加 BEGIN_BLOCK 和 END_BLOCK
在进一步分析 SimulateEntities
的性能瓶颈时,我们对内部逻辑进行了模块化分块,并初步标记和归类各个子任务,以便定位最耗时部分。当前的拆解和分析如下:
模块划分与初步观察:
敌人渲染逻辑(Enemy Render Code)
- 涉及多个
PushBitmap
调用; - 渲染敌人相关的位图可能在调试模式下占用较多性能;
- 有可能是
GetBestMatchBitmapFrom...
类型的函数参与其中,虽然这些可能已被剔除出主循环,但仍需要确认。
- 涉及多个
能量/实体物理处理(Energy / Entity Physics)
- 涉及实体的移动逻辑与碰撞处理;
- 包含多个移动函数调用,如
MoveEntity
等; - 属于实体模拟的核心部分,极可能是主要性能瓶颈之一。
调试相关逻辑(Debug Code)
- 仍有调试代码在循环中执行;
- 部分可能是视觉调试信息,如碰撞盒、位图标识等;
- 若无条件执行,会对性能造成稳定性拖累,尤其在帧数下降明显时。
接下来的目标:
逐一添加时间采样点(Time Blocks):
- 通过在每个模块前后加入性能采样区块(如
TIMED_BLOCK()
); - 明确每一小段逻辑的时间占用;
- 从而识别是物理处理、渲染、还是调试信息成为了最大的性能负担。
- 通过在每个模块前后加入性能采样区块(如
进一步确认是否有位图匹配逻辑残留:
- 检查是否仍有
GetBestMatchBitmapFrom...
类型的匹配函数在主模拟逻辑中被调用; - 如果存在,可以考虑提前缓存或延迟处理。
- 检查是否仍有
优化策略准备:
如果渲染逻辑耗时严重,可考虑:
- 减少每帧更新的数量;
- 使用位图缓存机制;
- 延迟非关键帧渲染;
如果物理计算为主因:
- 可通过空间划分结构加速(如网格或八叉树);
- 精简碰撞判定逻辑;
若调试代码仍在拖慢帧率:
- 提供一个集中开关;
- 或者基于状态跳过不必要的调试绘制。
总结:
已经完成了对 SimulateEntities
内部的初步结构性划分,将核心逻辑(渲染、物理、调试)分别标记,为接下来的精准采样与优化做好了准备。此分析对于后续性能瓶颈识别与目标优化极为关键,下一步将依赖采样数据做出针对性修改。
性能分析:实体渲染占用了大部分时间
我们重新打开性能分析器后,观察到实体渲染(Entity Rendering)部分消耗了绝大多数时间,几乎占据了整个 SimulateEntities
中的“狮子份额”。相比之下,其它如物理计算或其他模拟逻辑在时间占比上都比较分散且较小,不是当前优化的重点。
当前发现与现象:
实体渲染时间占比极高:
- 与其它子模块相比,实体渲染明显拖慢了帧率;
- 这是当前性能瓶颈中最显著的部分。
物理和模拟逻辑(如 Energy Physics)占比较小:
- 虽然仍在运行,但几乎没有显著的性能影响;
- 优化意义不大,可暂时不考虑。
SimRegion
中存在过度精细的计时点(Granular timing blocks):- 在一些内循环中存在粒度过细的
timing block
; - 这些可能影响程序整洁性且无分析必要,需移除。
- 在一些内循环中存在粒度过细的
针对渲染部分的进一步调查:
调用次数与实际耗时对不上:
GetBestMatchAssetFrom
被调用了 366 次;- 虽然这个函数会遍历整个资源系统(理论上开销大),但却在分析器中几乎不耗时;
- 反观实体渲染代码,调用频率未必高,却占用大量时间;
- 暗示实体渲染中可能有隐藏的、低效的操作。
怀疑渲染流程中存在低效处理:
- 当前每次都清空 vector,并进行匹配;
- 很可能这个过程内部涉及了频繁的内存分配、释放或其他重复计算。
下一步计划:
- 准备将渲染逻辑区块继续拆分;
- 精确定位究竟是哪一部分拖慢了性能;
- 移动性能区块(timing block)的位置,将大段渲染逻辑细分为多个子块;
- 检查是否是像 vector 清理、位图匹配等局部操作存在性能问题。
初步判断:
当前程序的最大瓶颈很明确是实体渲染逻辑,尽管调用了不少资源匹配函数(如 GetBestMatchAssetFrom
),但真正耗时的点未必是这些匹配调用,而可能是一些“看似无害”的步骤中隐藏了资源消耗(如向量的重建、位图处理、数据复制等)。
因此,接下来要做的不是盲目优化匹配函数,而是将渲染部分的逻辑进一步细化监控,识别真正的性能黑洞。这个过程至关重要,将为接下来的性能提升提供明确方向。
game_world_mode.cpp:将实体渲染代码块调整为仅查看 Piece 列表
通过将渲染逻辑中的 piece list(图像元素列表)部分临时移除来单独观察其性能影响后,我们确认以下几点:
实验目的:
目的是排查是否是 push bitmap(推送图像位图)操作导致渲染性能问题。通过只屏蔽掉与 piece list 有关的渲染逻辑,可以将这部分从性能分析中剥离,进而判断其对整体性能的影响。
实验结果:
Entity Rendering(实体渲染)依然消耗大量时间:
- 即使 piece list 的渲染部分被移除,实体渲染模块整体占用的时间几乎没有下降;
- 说明 push bitmap 调用本身并不是当前性能瓶颈所在;
- piece list 渲染处理几乎可以忽略不计,不是导致问题的核心。
Piece Rendering(元素渲染)占比为零:
- 进一步印证这块逻辑开销极小;
- 并未对整体帧率造成明显影响;
- 可以暂时从优化目标中移除。
推论与结论:
性能瓶颈并不在图像位图的推送流程;
渲染模块仍存在其他更严重的问题,比如:
- 内存处理、资源切换或某种重复计算;
- 或者其他代码块在循环中被频繁调用,但在分析器中没有准确标注出来。
下一步建议:
将实体渲染的其他逻辑进一步拆分为更细粒度的时间分析区块;
分别测试:
- 向量清空与重建;
- 条件判断及分支跳转;
- asset 匹配或状态变更流程;
逐步排查出真正消耗最多时间的细节操作。
总之,这一步实验确认了 piece list 并不是瓶颈,我们可以放心地将注意力转向实体渲染模块中的其他子流程,继续排查更深层次的性能隐患。
game_world_mode.cpp:在 UpdateAndRenderWorld 中为碰撞渲染、向量清除和生命点渲染添加 BEGIN_BLOCK 和 END_BLOCK
我们继续深入定位实体渲染部分的性能瓶颈。当前策略是逐步隔离每个可疑的子部分,通过注释或屏蔽的方式单独观察各自对性能的影响,从而精确找出真正的问题所在。
当前排查思路与步骤:
已经确认的无影响部分:
- piece list(图像元素渲染);
- push bitmap 操作;
- 它们被临时屏蔽后,对整体性能几乎没有影响,可排除。
进一步的隔离与测试:
接下来对实体渲染流程中其余部分进行细分屏蔽,分别测试每一块的性能开销:
vector.clear()
操作;- 图像渲染(piece rendering);
- 碰撞调试渲染(collision rendering);
- 任意其他逻辑或显示(如状态渲染、路径渲染等)。
操作方式:
- 使用注释或条件判断来临时屏蔽特定逻辑块;
- 每次只屏蔽一块,对比性能剖析图,观察帧时间与占用率变化;
- 以此反复迭代、缩小范围,最终锁定根本原因。
当前的关键思路总结:
- 定位渲染瓶颈的策略是系统性隔离分析,不是主观猜测;
- 每一块代码都被逐一剥离以进行量化对比;
- 目的是通过排除法快速锁定引起实体渲染耗时高的“元凶”。
后续步骤建议:
- 在当前的屏蔽测试中继续推进,尤其对
rectangle
相关的渲染调用进行独立测试; - 重点查看是否是某类 UI 或 debug 元素导致重复绘制或 GPU pipeline 开销;
- 结合使用 profile 工具,确保可视化反馈与预期一致。
整体来说,这是一次极其细致的微观性能剖析过程,通过结构化排查逐步压缩搜索空间,目标是彻底找出那些在 debug 模式下造成渲染瓶颈的隐藏成本代码。
性能分析:碰撞渲染耗时最高
目前运行状态已恢复,并加载性能分析器观察结果。从最新的性能图上可以明显看出:
性能瓶颈已明确定位:
- 碰撞调试渲染(collision rendering) 是当前帧时间占用的最大开销源;
- 所有其他子系统相较之下几乎耗时极少或可以忽略;
- 可以确定此部分代码为当前性能瓶颈的**“主要拖慢者”**。
分析结论:
碰撞渲染代码正在消耗大量的处理资源,极有可能是由于以下一种或多种原因:
- 绘制调用次数过多(每个碰撞体都绘制调试框);
- 图形 API 调用未批处理或重复构造指令;
- 数据访问不优化,例如每帧遍历大量碰撞体;
- 调试逻辑中包含非必要的昂贵计算;
- 使用了半透明、抗锯齿等图形效果,但未进行合适降级处理。
后续方向建议:
局部屏蔽进一步验证
可将碰撞调试渲染临时屏蔽,观察帧率是否明显提升,进一步验证其“主要瓶颈”地位。细分逻辑块剖析
将碰撞调试渲染中每一个绘制调用或逻辑判断细分成多个计时段,逐一测定:- 碰撞边框绘制;
- 图形状态设置(如线宽、颜色);
- 每帧数据构建流程;
- 图元 push 操作;
- 其他额外的调试信息(如标签、ID 显示等)。
优化策略准备
- 降低绘制频率(如每 2 帧更新一次);
- 简化调试图形(如不绘制复杂路径、减少透明区域);
- 加入 toggle 控制可视化;
- 使用更高效的数据结构(如空间索引加速调试体筛选);
- 批量提交绘制指令,减少状态切换。
目前瓶颈已精准定位,后续只需在该区域继续剖析和优化,即可显著提升整体性能,尤其是在 Debug 模式下的帧率体验。
game_world_mode.cpp:在 UpdateAndRenderWorld 中为体积渲染和可通行区域渲染添加 BEGIN_BLOCK 和 END_BLOCK
目前我们已经明确识别出性能瓶颈,并准备着手进一步细化分析以找出真正的开销源。以下是对当前流程的详细总结:
当前状态总结:
- 已确认**碰撞调试渲染(collision rendering)**是主要性能瓶颈。
- 下一步进入更精细的剖析阶段,尝试进一步分解渲染逻辑,找出最具体的开销点。
当前操作内容:
分离并测试两大子模块:
- 体积(volumes)渲染逻辑;
- 线框(line)渲染与遍历(traversals)逻辑;
- 假设其中之一可能为当前帧耗时的根本原因;
- 判断标准是渲染调用在视觉上似乎不应存在(例如体积不可见或当前未启用渲染)却仍产生显著性能消耗。
理论假设:
如果体积渲染部分并没有实际绘制内容,却仍产生开销,可能是:
- 渲染函数被错误调用;
- 图形状态配置复杂;
- 循环未能及时退出或有冗余检查;
- 数据结构访问不当;
如果线框渲染/遍历部分开销高,可能是:
- 每帧在遍历大量调试对象;
- 遍历逻辑嵌套深,或包含复杂判断与绘制;
- 调试渲染使用了非加速路径或高成本图元。
意图与目标:
- 通过对这两个部分分别打时间标记并观察性能图,确认究竟是哪一块导致资源异常消耗;
- 找出“看上去应该便宜、但实际上非常昂贵”的代码段;
- 接下来将有针对性地进行替换、跳过或优化操作。
后续建议方向:
如果确定某一块为主要问题源:
- 短期措施:添加可开关宏、在非必要时跳过该块逻辑;
- 中期优化:将绘制逻辑做更精细的区域裁剪或按需调度;
- 长期改进:重构该部分逻辑与数据流,避免不必要的资源占用。
我们目前处于定位精细化开销的关键阶段,已经缩小到调试渲染中最可能的两个分支。下一步结果将直接指向具体函数或数据结构,便于精准优化。
game_world_mode.cpp:暂时关闭可通行区域的 PushRectOutline 调用(#if 0)
当前代码本身非常直接,但这类代码在开启优化编译时运行速度会显著提升,这是完全可以理解的。
优化动作与效果:
- 当前渲染流程中对
PushRect
的调用次数被五倍增加,每次遍历都会带来大量调用; - 基于此,进行了 临时取消部分调用 的尝试,以测试渲染性能对其的敏感程度;
- 测试结果表明,这一步确实大幅减少了
traversable
(可遍历元素)渲染部分的耗时; - 但整体来看,simulate entities(实体模拟)仍然是当前最大的性能瓶颈,渲染的提升还不足以解决全部问题。
当前性能情况:
traversable rendering
(可遍历对象渲染)耗时已显著下降;simulate entities
仍然开销偏高,尽管优化后已有明显改善;- 帧率已经接近目标的 60 FPS,系统整体运行状况明显变好,但还有一些关键点未完全压缩;
初步结论:
- 渲染流程中的重复调用是性能开销关键之一;
- 渲染调用优化有效,但仅靠该优化还无法完全消除瓶颈;
- 模拟逻辑的整体结构或执行路径仍需进一步拆解与分析,可能还包含其他未被识别的热点代码;
- 编译优化的差异也显著影响表现,调试模式下的性能数据不能完全代表优化后的最终状态。
后续优化建议方向:
- 进一步拆解 simulate entities 内部结构,找出具体是哪个部分最耗时;
- 尝试使用条件渲染与跳帧策略,避免不必要的计算;
- 调研实体模拟中是否有重复计算、无效更新或内存频繁分配等问题;
- 考虑引入缓存与状态标记机制,避免每帧重新初始化数据;
- 最终,在优化构建模式下再次评估系统运行效率,以确认调试下的性能问题不会误导整体判断。
总结:
通过临时屏蔽部分 PushRect
调用,有效降低了可遍历对象渲染的负载,系统性能接近目标帧率。但核心问题依然集中在实体模拟模块,接下来应深入该模块的具体逻辑,找出耗时路径,并结合编译优化做最终的性能收敛。
考虑仅为这些可通行区域绘制例程启用优化,或者将 PushRect 函数展开为更明确的代码
目前有另一个可以尝试的选项,就是仅对特定部分代码开启编译优化,或者将某段代码展开成更显式、更直接的形式,从而降低执行耗时。
当前状况与判断:
- 虽然渲染层面的优化已经取得了成效,但 实体模拟(simulate entities)仍然耗时偏高;
- 当前对性能的表现还不完全满意,预期中应进一步降低开销;
- 可能还需要做一些额外的拆解或重构来进一步压缩这部分的处理时间;
- 是否所有处理逻辑都实际必要也值得商榷,但那是一个相对独立的问题,暂时不展开。
可行的优化手段与策略:
选择性启用编译器优化(如
#pragma optimize
等):- 在保持调试友好的同时,提升关键路径代码的执行效率;
- 特别适用于像
simulate_entities
这种频繁调用的大函数。
将关键路径代码展开为显式操作:
- 减少临时变量创建、避免抽象调用;
- 避免自动清零、复制等编译器在非优化下产生的低效指令;
- 尽量避免不必要的函数调用与容器操作。
后续行动方向明确:
- 接下来需要更细致地拆解
simulate_entities
,进一步剖析其性能瓶颈; - 识别内部是否存在隐藏的高频次操作、重复计算或不必要的逻辑路径;
- 如果有条件,也可以对照 Release 编译状态再一次验证表现差异,排除调试编译带来的误导。
- 接下来需要更细致地拆解
总体结论:
目前已经基本识别出几个主要性能瓶颈,渲染部分处理成效显著,但核心模拟逻辑仍需更多细化优化。
后续需结合局部优化与代码结构调整手段,进一步压缩关键路径耗时,确保系统能稳定保持高帧率运行。整体进展良好,性能提升路径也已逐步清晰。
问答环节
调试器:演示 MSVC 优化构建时调试信息的不足
我们不建议在发布(release)模式下开启调试,因为即使有特殊开关支持在发布模式下调试,实际效果通常也很糟糕,体验非常不顺畅。
具体来说,发布模式的编译器会进行大量优化,这导致调试信息非常有限或者混乱,程序中很多变量的实际值和位置都难以准确获得。比如我们想查看某个指针指向的内容,调试器常常显示“不知道指针在哪”,根本找不到该变量的位置。即使知道变量存在,调试器也无法准确定位它的内存地址或寄存器中的位置,导致调试变得非常麻烦和不可靠。
虽然存在一些编译开关,试图增强发布模式下的调试信息,但效果并不好,调试信息仍然很不完整,无法像调试(debug)模式那样清晰地看到变量的真实值。发布模式里的调试信息往往不能正确反映编译器实际生成的代码状态,导致变量和值的对应关系混乱不堪。
在调试模式下,调试信息完整且准确,每次停下来都能看到变量的准确数值,调试体验好很多,因此我们更倾向于使用调试模式进行排查。
即使调试器能通过寄存器等信息“推断”变量的值,也非常繁琐,必须手动查找和分析每个寄存器内容,耗时且容易出错。发布模式的调试信息往往只是寄存器里的一堆数据,缺乏明确的符号关联,不能直接看出变量含义。
这种情况特别不适合教学演示,因为当变量信息缺失或混乱时,观众无法直观理解程序的运行状态,调试过程变得晦涩难懂,影响教学效果。
总结来说,发布模式调试因为优化和信息缺失问题,无法保证变量信息准确性和调试便捷性,所以通常不建议打开调试开关,调试模式依然是进行调试和教学的最佳选择。
我没注意到优化因为缓冲,但听到了有关清除代码臃肿的问题,修复方法是什么?
之前提到清理(clear)操作过于臃肿,导致性能问题。解决方法就是停止清理实体(entity)和大脑(brain)数组,也就是说直接不对这两个数组执行清除操作,从而避免不必要的开销。
在同一段代码区域内,改动就是不再担心这两个数组的清理,跳过它们的清空过程。
另外,有提到将“核标志”(nuclear flag)传递给已分配的内存区域,这可能是为了标记或管理这块内存的状态,确保内存分配的正确性和有效管理。
总体来说,主要通过避免频繁且冗余的清理操作,优化了性能,同时用标志位管理分配的内存,提高了程序效率。
远处楼层变透明看起来不太对,因为能看到透过它们
目前看起来效果不太对劲,因为透明部分看起来透视感太强,不太自然。
我们会记住这个问题,不过目前还不急于完全解决。因为接下来还有很多时间可以调整和优化这部分效果。
具体来说,不必把透明效果限制成一个全局的 alpha 值(整体透明度),也可以采用全局颜色调制(color modulation)的方式来处理透明度,这样灵活性更大。
这周的重点是把画面元素放入不同的 z 层(深度层),也就是对元素进行分层渲染。
当完成了这个分层工作后,就可以自由地控制这些元素的渲染方式,包括透明度和颜色调节,想怎么处理就怎么处理,效果会更加丰富和灵活。
总结来说,目前先不纠结透明效果的细节,等完成了深度分层后,有足够的时间和空间来调整透明表现,实现更理想的视觉效果。
从洞口往下看更透明的楼层时,可以看到角色。猜测你可能想用“雾色”混合,而不是简单透明?
有人建议在观察更透明的图层时,可以看到下面的角色或其他圆形图层,透视效果比较明显。猜测可能需要把这些透明层和雾效(fog color)混合处理,使得透明层能更自然地融合到整体画面中。
基本思路是用雾的颜色来调节透明层的颜色,通过颜色调制(color modulation)来实现这种混合效果,这样可以让透明层看起来更加协调,不显得突兀。
实际上,我们甚至不一定非要让元素完全渐隐(fade out),如果愿意,也可以选择不做渐隐效果。
不过目前还是会等到具体实现时再看效果如何,再决定具体的处理方式。
总体来说,采用用雾的颜色来调节透明层的颜色,是一个合理且自然的方案,能够更好地处理透明层和画面之间的关系。
LLVM 能在 Windows上用吗?
尝试用 LLVM 构建 Windows 游戏时遇到了问题,至少一个月前尝试时,LLVM 不能很好地支持 Windows 的某些接口规范(比如 Win32 API),导致无法正常编译通过。具体来说,Windows 上的接口定义和规范跟 LLVM 默认处理的不太一样,如果不修改接口声明或者换用不同的包含文件(include file)来定义这些接口,LLVM 就不能顺利工作。
另外,Windows 平台上还没有完全替代微软链接器(linker)的方案。即使用 LLVM 编译器,也必须依赖微软的链接器来生成最终的可执行文件,这意味着不能完全摆脱微软的工具链。
目前只能同时使用微软的链接器和 LLVM 编译器,这带来了管理和维护上的麻烦,因为实际上要维护两套工具链,而这并不理想。
理想的情况是未来能完全用 LLVM 代替 Visual Studio 的工具链,这样就能彻底摆脱微软的依赖,这样的转变会更受欢迎,也更方便。
但目前来看,LLVM 在 Windows 平台的支持还不够完善,暂时还无法做到完全替代微软的编译和链接工具。
多层房间且每层有实体持续执行,会不会很重/很耗性能?有考虑性能解决方案或限制关卡吗?
关于游戏中存在多个房间层级,并且每个层级都有实体同时激活和存在,是否会导致性能开销很大或者系统负担沉重的问题,目前看来并不是很严重。
在发布模式下,游戏仍然可以稳定运行在60帧每秒,性能表现非常流畅,尚未遇到明显的性能瓶颈或者资源限制。
因此,在尚未发现性能上的真正瓶颈之前,不会主动限制游戏设计或对当前系统进行优化。
只有当性能确实达到极限,开始显著影响运行效率时,才会着手针对性地优化,并提出具体的解决方案或改进思路。
总结来说,目前硬件和引擎性能足够强大,能够支持多个房间和实体同时存在,不需要提前为了性能问题限制游戏的设计自由度。
有办法只对代码某段启用优化吗?没有的话,你会怎么处理?
目前编译器没有办法指示只对代码的某一特定部分单独应用优化。也就是说,不能告诉编译器“只优化这段代码”,这种精细化控制是不存在的。
不过,有一种方法可以做到“禁止优化”某一部分代码:通过使用编译器的 pragma 指令(编译指令),可以关闭整个程序的优化,然后在想要优化的代码区域再用 pragma 指令重新开启优化。
听起来很巧妙,但这方法有一个很大的限制:在关闭优化的区域里,编译器不会对函数进行内联(inline),即不会自动把被调用的函数代码插入到调用点,从而失去了很多重要的优化机会。
理想中我们希望能够告诉编译器:“只针对这个函数开启最高级别的优化,同时把所有相关的内联函数也一并优化”,这样这个函数能达到极致的性能,而其他代码保持不变。但实际上编译器并不支持这种操作。
总结来说,目前只能通过关闭和开启优化的方式来局部控制优化,但这样做会导致函数内联失效,无法实现理想的“仅优化某函数且包括所有相关内联函数”的效果,因此这个需求在现有编译器中无法很好满足。
Clang++ 更糟。老实说,为什么 PDB 文件不能直接告诉你“你在这条指令,这个值在这个寄存器/地址”?
关于为什么 PDB(程序调试数据库)不能简单地告诉我们“当前指令对应的值存储在哪个寄存器或内存地址”,主要有两个原因:
第一,取决于编译器在优化过程中跟踪变量位置的能力。如果编译器没有设计成在优化时持续追踪每个变量在程序中的具体位置和状态,也就是说,优化时没有保留“线索”或“面包屑”去标记变量的源代码意义,那么调试信息就无法准确地告诉调试器变量在哪里。这样就相当于调试器必须做一项庞大的逆向工程工作才能还原变量的位置,这非常复杂且难以实现。
第二,调试信息格式本身的大小和复杂度限制。程序往往包含成千上万甚至几十万行代码,每一行代码的每一个作用域内变量都可能随时被移动或者重定位。调试信息需要详细记录变量在不同代码区间内的位置变化,比如在哪个区间变量存储在寄存器,在哪个区间存储在内存,这种“范围标记”和“位置切换”需要大量信息。如果调试格式设计不够规范或高效,数据量会变得庞大且难以管理。
据传 PDB 和 DWARF 这些常用调试格式可能在规范上并不完美,没有很好地解决这些问题,导致它们在实际使用中表现欠佳。
总结来说,PDB 不能直接精确告诉变量位置,是因为编译器优化时未充分跟踪变量状态,加上调试信息格式复杂且庞大,这两个因素共同制约了调试信息的准确性和完整性。