仓库:https://gitee.com/mrxiao_com/2d_game_4
回顾
我们昨天做了一些非常有趣的实验。在实验中,我们的目标是实现一个能够在运行时改变的编译时常量的概念。最开始,这个想法纯粹是出于一时的兴趣,觉得这应该是个很有意思的尝试。于是我们进行了实验,看看是否能够做到这一点,结果出乎意料地非常顺利,几乎没有遇到什么困难。
这次实验的成功让我们产生了新的兴趣,觉得应该继续在这个方向上探索。我们决定继续沿着这条路走下去,看看它能带我们去哪里。整体来说,这个过程让我们意识到,很多看似复杂的想法其实在技术上是可以很容易实现的,甚至能带来新的思路和灵感。
我们现在计划进一步开发这个思路,尝试更多的可能性,看看它能为我们带来哪些新颖的解决方案和技术进步。
今天的计划
目前的目标是尝试将现有的调试代码系统转化为一个更通用的系统。这个系统的设计目的是能够轻松地在运行时开启或关闭所有的调试代码,而不需要在游戏编译时将这些代码包含进去,也不需要因此带来任何额外的性能开销。此前从未尝试过这种做法,只是认为它应该是可行的,因此进行了尝试,结果效果不错。接下来,计划进一步探索是否能够将这个系统扩展得更为完善。
之前回顾
昨天所做的工作非常有趣。我们实现了一种方式,可以通过用户界面的操作来重新组合所有内容。具体来说,这个操作会触发游戏重新编译,并使用不同的宏定义,这些宏定义会完全改变游戏的代码。在这种情况下,它控制了调试相机是否开启的切换功能。也就是说,通过这个操作,可以轻松地开关调试相机,而不需要手动更改代码,整个过程变得非常灵活和方便。
我们将扩展调试系统,以便更轻松地编译进出调试功能
现在我们需要做的是,找到一种方法,能够在各种地方轻松地实现这种动态切换的功能。我们已经建立了可以进行更改的基础设施,接下来要思考的是,如何能以最小的成本将这种功能扩展到代码的各个地方。
首先,我们认为最简单的方式就是直接尝试去做。我们可以查找代码中那些需要调试的地方,尤其是那些需要开关切换的地方,然后思考如何让这些地方更容易被更改为可切换的功能。通过这样的方法,我们不仅能够实现动态切换,还能提高效率。
同时,我们也希望能够改进和整理界面。当前的界面比较简陋,只有一个测试用的单选菜单,里面文字重叠,看起来很不美观,也无法实现我们真正需要的功能。因此,在进行这些更改时,界面的改善也应该成为一个重要目标。我们希望通过这次更新,能让界面更加简洁、清晰和实用。
当我们整理出一系列真正需要设置或切换的功能时,这将是一个很好的时机,去重新设计和调整界面的外观和交互方式。最终,我们希望能通过这种方式,实现既能满足需求,又能提供更好用户体验的界面。
可选编译地面块轮廓
我们现在的目标是进入代码,看看如何在现有的结构中实现这一功能。首先,我们已经有一些代码可以进行类似的操作,例如我们已经有了用于显示地面瓦片边界的代码。这是一个很好的例子,说明了我们可能希望能够动态开启或关闭的功能。
根据目前的代码示例,我们可以借鉴现有的做法。比如,在“game_config.h”这个文件中,我们可以新增一个选项,比如“DEBUGUI_GroundChunkOutlines”之类的东西。然后,我们将相关代码放到这个选项中。这样,我们就可以利用相同的方式轻松地切换这个功能的开关。
接下来,我计划继续以类似的方式处理其他部分的代码。比如,我会查找类似的地方,看看是否能发现更多类似的功能需要切换。对于一些我们认为不重要的代码,我可能会忽略掉,而对于重要的部分,我们就根据之前的方法进行相应的调整,确保可以方便地打开或关闭这些功能。
可选编译粒子喷泉
这是另一个例子,我们之前有一个粒子喷泉的功能。这个粒子喷泉在代码中运行着,但我们不清楚它究竟在做什么,或者为什么它会发生、或者它是否没有按预期发生。总之,这也是我们可能希望能够动态开启或关闭的一个功能。
现在有个问题,我们似乎没有看到这个粒子喷泉的效果,我不确定是否因为某种原因我们把它关闭了。接下来,我们需要进一步确认这个粒子喷泉的状态,看看它是否被禁用了,或者是否存在其他配置导致它没有正常显示。
为什么粒子系统不起作用?
我们发现粒子喷泉没有正常渲染,似乎有一些问题。首先,粒子确实在生成和创建过程中没有问题,看起来我们已经通过粒子生成索引来创建了它们。然而,问题在于我们似乎从来没有从粒子中获取到位图,导致这些粒子无法显示。
接下来,我们检查了粒子系统是否正常运行。通过查看代码,发现粒子确实在创建,但它们没有正确显示。我们推测可能是我们在调用位图推送(push bitmap)时出现了问题,这个调用没有按预期工作。可能的原因之一是相关的字体或其他资源缺失,导致粒子无法渲染。
具体来说,粒子的效果是生成了,但是我们无法看到它们的渲染效果,可能是因为位图的推送过程出了问题。也有可能是字体资源或某些设置没有正确加载,导致粒子无法正确显示。
喷泉依赖于过时的字体代码
原来是这个问题,实际上我们需要加载一个特定的字体位图,但是目前我们并没有做到这一点。现在的代码中并没有正确地加载该位图,这导致了粒子效果无法正常显示。
game.cpp: 回到喷泉α
原来问题就出在这里,解决方法就是回到之前的做法,回到“头部喷泉”的实现。这样一来,问题就能得到解决。现在喷泉效果已经恢复正常,粒子效果也能够显示了。
接下来,可以继续按照原计划进行其他操作,不断完善并展示功能。其实,这也启发了我们更多的思路,说明了一些有趣的事情。这些操作让我们更清楚地了解了如何处理类似的情况,并在过程中发现了一些新的有趣点。
游戏覆盖了我们对game config文件的更改。让我们防止这种情况再次发生
问题出在系统会重写某个文件,这样就可能会覆盖我们已经添加的内容。因此,我们需要确保每次修改文件时,都能尊重已经添加的内容,否则就会发生覆盖的情况。
为了避免这种情况,我们需要在修改文件时格外小心,确保不会出现“走偏”或覆盖已有内容的情况。虽然有一些方法可以避免这种问题,甚至可能不需要修改该文件,但我还是决定按照预定的步骤来操作,这样能更清楚地看到整个过程,避免跳到结论。毕竟,我并不确定最正确的解决方法是什么,所以我只是猜测并继续操作,看能否从中找到有效的方案。
在这个过程中,我会进行一些测试,比如粒子测试等,以确保一切能够正常进行。
可选编译粒子网格
在进行调试时,我添加了一个调试选项,用于查看粒子网格。这样就可以清楚地看到粒子效果的状态。接着,我继续检查其他的功能,看看是否还有其他需要处理的部分,确保所有内容都能正常运行,并解决可能出现的问题。
可选编译房间轮廓
我们有很多的调试代码,比如矩形轮廓和空间边界的代码。可以看到,这里有大量的代码,很多调试功能其实已经被关闭了。通过仔细查看,会发现有许多调试代码。大部分这些调试代码之前只有在特定的条件下才能访问,比如通过特定的“if zero”判断。这些代码很容易被遗忘,但它们实际上对我们寻找 bug 或者进行其他操作是非常有用的。
修复过时的DrawRectangle调用
我不确定这段代码现在是否还能正常工作。很可能已经不行了,但具体情况还不清楚。我们来看一下这段代码。首先,绘制矩形的函数似乎不接受四个参数,我不确定它究竟接受多少个参数。可能是这个问题导致代码无法正常工作。
在仔细检查之后,发现我们没有一个函数是直接传递标准矩形的。我们有一些涉及裁剪(clipped)的代码,使用了不同的方式,比如传递蓝色或其他颜色。我们曾经尝试过偶数和奇数处理,但实际上从来没有真的在代码中实现过这些功能。对于超线程的处理,也没有做过明确的实现,可能这些部分已经不再需要了。
另外,我们可能需要移除那些关于偶数和奇数处理的部分。虽然这些部分的存在是为了某些特定的功能,但目前来看并没有实际使用过,可能可以清理掉。
至于矩形裁剪的部分,我们可能应该直接传递裁剪的矩形信息,比如最小值和最大值来确定矩形的范围。看起来这种方法是可以正常工作的,但需要确认代码是否还能运行。代码的运行速度非常慢,可能是因为绘制的位置不对。这个问题可能与我们没有更新投影代码有关,投影代码负责调整坐标系统,确保正确的显示。
所以,我打算暂时关闭这个投影的部分,然后回到光照相关的代码,解决这个问题。接下来,我会进行一些调试,确保一切能正常进行。
可选编译地面块棋盘图案
这一部分的代码是用来让我们能够看到交替的区块,而不会在它们之间绘制额外的分隔线。实际上,它的作用是创建一个棋盘格的模式,这样可以帮助我们更直观地观察不同的区域。
如果我们希望将这个功能集成到配置文件中,我们可以定义一个调试选项,例如 DEBUGUI_GroundChunkCheckerboards
,然后用它来控制棋盘格的显示。这样一来,我们就可以清楚地看到棋盘格模式的效果,这对于调试来说可能会很有帮助。
接着,我们回到裁剪网格的部分,继续检查代码的其他部分。这里还有一部分代码是用于某种行处理(row generation),但目前看来这部分内容并不是我们关心的,因此暂时忽略它,专注于我们需要的功能。
可选编译地面块在DLL重载时重置
这部分代码的调整是完全合理的,事实上,我们之前移除它可能是因为当时认为它的执行速度太慢。但现在的性能已经足够快,因此应该可以重新启用这个功能。
在重新加载时,我们需要确保 make config
也被正确保存。为了暂时避免问题,我决定让游戏的调试代码不再读取 game_config.h
,这样可以更方便地进行调整。
目前来看,重新启用这个功能并不会带来问题,比如在地块加载后,数据能够正常返回。因此,我暂时决定让它保持开启状态。如果以后发现需要额外的控制,可以再添加一个调试开关。不过,考虑到现在的目标是尽可能增加调试功能,我们可以直接在调试界面中加入一个选项,比如 DEBUGGUI_RecomputeGroundChunkOnEXEChange
,用于控制地面声音的重新计算。
接下来,我们继续检查其他部分,回到图形渲染的部分,看看是否还有 #if 0
之类的未启用代码需要调整。
可选编译奇怪的渲染器缓冲区大小
这部分内容主要涉及渲染错误中的缓冲区大小问题。起初,我们认为缓冲区大小是平台层的一部分,无法直接更新。但经过检查后,发现它实际上可以被覆盖,因此可以在调试界面中添加一个选项,例如 DEBUGUI_TestWeirdRrawBufferSize
,用于测试不同的缓冲区大小对渲染的影响。
除此之外,我们还发现了一个关于 “Familiar”(跟随主角的辅助角色) 的功能,它本应跟随主角移动,但目前被关闭了,不清楚具体原因。可能是之前出于某种考虑禁用了它,但现在我们可以重新启用并测试它的行为是否符合预期。
在此过程中,我们主要进行了以下调整:
- 允许覆盖渲染缓冲区大小,并添加调试开关
DEBUGUI_TestWeirdRrawBufferSize
。 - 重新启用 “Familiar” 的跟随功能,并检查其行为是否正常。
接下来,我们将继续检查 #if 0
相关的调试代码,看看是否还有其他功能可以恢复或优化。
可选编译使熟悉的角色跟随英雄的行为
在调试 UI
相关代码时,我们发现了一些未启用或未使用的功能。其中一个是 DEBUGUI_FamiliarFollowsHero
,这是用于让某个角色或对象跟随主角的功能,但目前并未启用,可能是因为它已失效或被移除。
此外,在性能分析器(profiler
)中,还有一部分代码已经不再使用,这些代码并未被调用,因此可以忽略。另一个被发现的功能是 chart_boundary
,这个功能旨在显示某种边界,但目前尚未真正实现,因此在当前阶段它并无实际作用。
我们还注意到 marks
相关代码,可能是用于某种标记或调试信息的显示。这部分功能需要单独处理,可能涉及 UI 显示或者某种逻辑调整。因此,在后续优化时,需要决定是否重新启用、修改或彻底移除这些未使用的功能,以保持代码整洁并提高可维护性。
可选编译双线性光照采样
在调试 UI
过程中,我们检查了 lighting_samples
相关的代码,这部分代码用于在地图中查看光照采样的位置,这个功能很可能是需要的,因此应该保留并启用。
此外,我们还发现了 ssc_clamping
相关的代码,检查后确认这部分代码已不再需要,因此可以忽略。我们还发现了一些函数,它们似乎已经不再被使用,比如 ben_developed_at_foot
及其他相关函数。这些代码与当前的需求无关,因此可以直接移除或忽略。
总的来说,我们正在清理代码库,确保只保留必要的调试 UI 选项,并移除那些已经过时或不再使用的功能,以提高代码的可读性和维护性。
可选编译基于房间的相机运动
我们检查了 摄像机跟随行为 的相关代码,但不确定该路径是否仍然有效,因此需要进一步确认其实际效果。随后,我们查找了 基于规则的摄像机(rule-based camera) 代码,以确定它是否仍然在使用或者是否需要调整。
当前的工作重点是清理和优化代码,确保仅保留必要的功能,并确认所有仍在使用的系统是否正常运行,以防止遗留代码影响整体性能或引发不必要的错误。
基于房间的相机坏了。让我们修复它
我们正在检查摄像机跟随行为的代码,发现之前没有使用它的原因可能是因为缺少必要的信息,比如当前摄像机跟随的实体。不过,我们确实有一个索引可以确定当前的实体,因此理论上应该能够获取该实体的相关数据,比如 px
和 dx
的位置参数。
然而,当前的摄像机系统并没有 abs_tile_x
这样的属性,这可能会带来一定的问题。由于当前系统已经不再使用**瓦片(tile)**的概念,因此我们需要调整摄像机计算方式,使其不依赖于瓦片单位。我们暂时假设房间大小为 9米 x 5米,并在代码中测试这一数值是否合理。
接下来,我们尝试调整摄像机的位置偏移。我们回顾了代码结构,发现世界数据结构提供了一些方法,比如 offset_by
或 MapIntoChunkSpace
,可以用于在世界坐标中进行偏移计算。因此,我们使用 MapIntoChunkSpace
方法来更新摄像机位置,确保新的摄像机位置能够正确计算。
然后,我们在 update_camera
函数中实现了这一步调整,并确保 NewCameraP
通过 MapIntoChunkSpace
计算得到。世界数据 (world
) 可以从 game_state
访问,而偏移量 (offset
) 则由房间宽度来决定。理论上,这样的计算应该可以正确更新摄像机位置。
我们在测试过程中发现摄像机并没有正确移动,经过检查后,发现是因为计算结果没有正确存储,导致摄像机位置没有被更新。修正代码后,我们再次尝试,确保摄像机能够正确响应房间变化。
现在它工作了
我们成功实现了基于房间的摄像机移动,而且**跟随实体(familiar)**的功能也恢复了正常。这意味着摄像机现在能够根据房间的位置进行调整,并且跟随系统也能够正确响应。
接下来,我们继续检查代码,确保所有相关部分都被正确处理。我们再次使用搜索工具来查找未检查的部分,并逐一确认它们的作用。
- **关于“投射方向”和“绘制位图”**的代码,我们不需要关注,因为它们与当前问题无关。
- “投射碰撞检测(speculative collide)” 代码未完成,因此我们暂时不会修改它。
asset_builder
相关的代码 只是之前用于演示如何使用外部库的代码,并未真正应用于当前项目,因此无需处理。
总的来说,我们已经筛选并确认了所有相关代码,确保没有遗漏重要部分。
目前我们已经有足够的项目
我们已经清理并整理了所有的**调试(debug)**相关代码,并确保它们能够被轻松找到。虽然可能还有一些零散的调试代码没有明确标记,比如缺少 #if 0
这样的标注,但目前这个列表已经相当全面。
为了便于管理,我们给所有的调试功能都添加了**DEBUGUI_
**前缀,这样在需要查找时,可以快速定位它们。例如,我们可以直接搜索 DEBUGUI_
,立刻看到它们在所有 Java 类中的位置,特别是在 game_hero
这类核心文件中。
这样做的主要目的是:
- 提高可维护性,让所有调试功能集中管理,避免混乱。
- 快速定位调试代码,当需要调整或删除时,能够立刻找到所有相关代码。
- 确保调试功能的可控性,可以随时启用或禁用,不会影响正式版本的代码。
现在,我们已经对所有的 DEBUGUI_
代码做了一轮检查,并确保它们的存在是有意义的。如果以后需要调整调试功能,可以很方便地修改或扩展。
#define DEBUGUI_UseDebugCamera 1 // bool32
#define DEBUGUI_GroundChunkOutlines 1 // bool32
#define DEBUGUI_ParticleTest 1 // bool32
#define DEBUGUI_ParticleGrid 1 // bool32
#define DEBUGUI_UseSpacesOutlines 1 // bool32
#define DEBUGUI_GroundChunkCheckerboards 1 // bool32
#define DEBUGGUI_RecomputeGroundChunkOnEXEChange 1 // bool32
#define DEBUGUI_TestWeirdRrawBufferSize 1 // bool32
#define DEBUGUI_FamiliarFollowsHero 1 // bool32
#define DEBUGUI_ShowLightingSamples 1 // bool32
#define DEBUGUI_UseRoomBaseCamera 1 // bool32
我们希望知道编译开关的值并将其写入game_config.h
有几个事情可以做。首先,显而易见的是,我们需要了解所有这些值,以便能够在用户界面中展示它们。其次,我们还需要能够将这些值写入文件。
作为第一步,我们进入到 game debug 进行处理。我们可以设想列出所有这些值,并通过我们已经做过的类似方式,很容易将它们展示出来,例如使用单选菜单(radio menu)。
接下来,我们需要保存这些值。为了避免在代码出现错误时丢失它们,我们可以将这些值先保存到一个注释中。然后,写入这些值时,实际的问题是,我们之前只写了一个值,但现在我们需要把所有这些值都写入文件。
这意味着,我们不再仅仅处理一个单一的值,而是需要确保把所有相关的值都按需要的方式保存和输出。
debug_variable结构体将保存开关的名称、值和类型
为了使所有刚才列出的调试变量能够正常工作,首先需要做一个非常简单的步骤,即创建一个列表,列出所有调试变量。
这个列表中的每个条目应该包含几个关键信息。首先,列出每个变量的名称,这样我们可以在后续过程中将其写入文件。其次,需要列出变量的值,以便能够查看当前变量的状态。
目前,我们只需要这些信息,因为这些变量都是基本类型(如整数、浮动值等)。但将来,可能还需要更多信息,例如变量的类型。这样一来,就可以更方便地处理不同类型的变量,如整数、浮动值等。
使用DEBUG_VARIABLE_LISTING宏定义它们
为了方便定义所有调试变量,可以使用宏来简化这个过程。可以定义一个类似于“#define”这样的宏,它的作用是创建一个调试变量列表,并为每个调试变量指定名称和对应的值。
通过使用这个宏,能够快速地将调试变量添加到列表中。例如,可以创建一个宏 DEBUG_VARIABLE_LISTING
,在每次调用时,只需要传递变量的名称,就可以自动处理该变量的名称和对应值。这个宏会将变量名称转化为字符串,并且能够获取到变量的实际值。
实现这个过程的关键是通过宏将调试变量名称和其值硬编码进去,这样每次变量的值变化时,可以直接获取并更新对应的输出。只需将这些调试变量转换成对应格式的列表,然后利用宏快速生成需要的代码。
此外,可以通过搜索所有以 #define
开头的部分,将其修改为调用宏的方式,只需要处理好宏后面的内容即可,从而得到一个干净整齐的调试变量列表,准备好进一步使用。
将变量写入配置文件
现在,如果我们想要将调试变量写入文件,可以通过列表的方式来实现。我们可以想象一个循环,遍历调试变量列表,并将每个变量的名称和值输出到文件中。
首先,在循环中,定义一个索引变量,然后通过该索引从调试变量列表中获取每个变量。每个变量的名称和值都已经存储在列表中,我们可以直接使用它们,而不需要猜测。
具体实现时,首先遍历整个列表,取出每个调试变量的名称和对应的值。然后,我们可以将变量的值写入文件。在输出时,如果需要控制输出的格式,可以使用 size_t
来确定变量的长度,调整输出内容。
当完成变量写入后,可以调整输出的大小,确保写入文件的内容完整。如果需要计算剩余空间,可以通过减去已写入的大小来确定还剩多少空间可以写入。
在操作过程中,要确保正确处理变量的大小并调整输出缓冲区的位置。这意味着在每次写入后,更新写入的位置,确保输出不超过预定的缓冲区大小。
最后,在进行调试时,确保代码逻辑正确,比如计算剩余空间的公式和更新文件写入的位置。如果遇到错误信息,可以根据提示进行相应的修正。
_snprintf_s需要什么参数?
在这个过程中,出现了关于函数参数类型转换的问题,尤其是在处理第三个参数时。看起来第三个参数应该是一个字符串,但出现了类型转换错误,提示无法将一个常量字符指针转换为 size_t
类型。问题的关键在于,printf
函数要求的是一个缓冲区指针和它的大小,而这里出现了类型不匹配的问题。
具体来说,代码中的 temp
是缓冲区的基础指针,at
则是当前写入位置的指针。通过 temp
指针和 at
之间的差值,可以计算剩余空间的大小。但为什么有些地方需要将其转换为 size_t
类型,而有些地方不需要,实际上是因为在某些情况下,缓冲区和大小已经通过函数签名被明确地定义,而在其他地方,它可能是隐式处理的。
问题在于,某些地方可能期望 printf
函数接收的是 size_t
类型而不是字符指针。这是因为 printf
会要求输入的缓冲区大小为 size_t
类型,而不只是单纯的字符指针。这解释了为什么某些代码段中传入的是字符指针,而其他地方却要求类型转换。
为了确保函数正常工作,可以尝试将缓冲区大小明确地转换为 size_t
类型。这是因为 printf
可能需要明确的缓冲区大小,而不是默认的字符指针类型。
总结起来,问题的根本原因可能在于 printf
等库函数在处理缓冲区时,对参数类型的要求不同。虽然一开始没有问题,但当遇到不同类型的数据传递时,便会触发类型不匹配的错误。因此,解决这个问题的方法就是明确传递正确的参数类型,或者进行适当的类型转换。
步骤代码,构建配置文件内容
在这个过程中,首先初始化了一个缓冲区 temp
,并开始向其中写入数据。写入的第一个数据是关于“调试相机”的信息,这表明代码正确地写入了第一个变量。接着,在循环中继续写入其他变量,每次都正确地写入了预期的内容。
接下来,代码将继续循环,直到将所有变量写入缓冲区。完成写入后,程序检查输出结果,确保所有的变量都已经正确写入了数据。然后,它将这些数据保存到一个文件中。
最后,加载该文件时,检查文件的内容,确认它与当前写入的数据一致,没有发生任何变化。经过这一系列操作后,整个过程达到了预期的效果,数据成功地保存并格式正确。
用vs调试 直接打开可执行文件打开对应的代码调试就行
可以不用选择打断点的文件,直接设置函数断点
写入文件成功
现在我们已经能够正确地重写所有的配置变量,这意味着如果需要进行调试,我们也可以轻松地做到。与之前在代码中硬编码变量不同,现在我们可以更灵活地处理这些配置。通过这种方式,不再需要像以前那样手动在代码中添加项目,而是可以更加结构化和简洁地管理这些配置项。这样可以提高代码的可维护性,同时为后续的开发和调试提供更多的便利。
打印变量列表,而不是旧的菜单项
我们可以不再使用菜单项来处理这些变量,而是改用调试变量列表。具体来说,调试变量列表的计数将取代原本的菜单项。这意味着,菜单中的每一项将不再是硬编码的,而是通过从调试变量列表中提取每个变量的名称来动态生成。
这样做是合理的,因为我们将根据调试变量的数量来创建对应的菜单项。当我们右键点击时,应该能够看到大量的调试变量项,每个项都代表了一个调试变量。
这意味着,所有这些变量现在都可以直接从调试变量列表中获取,并且每当点击某个选项时,都会触发相应的操作。这样,通过这种动态生成菜单项的方法,管理和切换调试选项变得更加灵活和高效。
切换调试变量
我们的目标是让每次点击时,选中的选项能够真正被切换。这个实现其实非常简单。基本思路是,当我们查看当前选中的菜单项(hot menu index)时,只需要确保这个索引值小于调试变量列表的数量,表示这是一个有效的选项。
具体步骤是,当鼠标按钮被释放时,就可以知道这个菜单项已经被选中了。然后,我们只需要根据热菜单索引,访问调试变量列表中的对应项,并将该项的值进行切换(即切换它的布尔值)。
这样,调试变量的值就会被成功切换,且下一次重新写入时,会带着新的值。整体过程非常简单,完全是基础操作,并没有什么复杂的地方。
通过这种方式,径向菜单(radio menu)就会被正确连接起来,在理论上,它应该能够按照预期工作,能够在运行时切换这些调试选项。
测试。它有效
现在,通过点击调试使用的相机选项并重新编译,应该能恢复到正常的相机视角。如果切换到例如测试缓冲区大小或者其他特定的选项,也可以恢复到全屏模式。这意味着,现在可以通过直接切换这些选项来动态更改任何变量。
如果不再需要使用基于房间的相机,也可以直接切换掉它。每次切换都会触发重新编译,并且相应的设置会更新。这使得在开发过程中,能够灵活地调整和切换各种调试选项,非常方便且高效。
运行时排除调试代码的一些优点…
我们现在的实现方式相比于直接在程序中使用变量,虽然需要重新编译,但功能更加强大。因为我们可以插入非常昂贵的计算操作,并且可以随时启用或禁用它们,而不会影响性能,也不会引入可能影响程序执行效率的分支判断。同时,我们还能在编译时切换掉整个函数调用,几乎不会有额外的开销。
任何可能影响程序整体结构的部分,比如内联函数,都可以通过这种方式进行灵活调整。例如,我们可以动态地更改使用哪一个版本的内联函数,而这一切都不会产生额外的成本。即使在发布模式(Release Mode)下,程序仍然能够获得正确的性能表现,这无疑是非常令人惊喜的功能。
此外,我们可以随时关闭或启用这些特性。例如,现在粒子系统(可能是粒子喷泉)已经变得有些夸张,因此至少需要关闭网格显示(Grid),因为网格的存在会影响可视化效果。关闭网格后,一切都变得清晰可见,整个系统就可以顺利运行了。这无疑是一个显而易见的优化手段。
说起Release模式好像得重新改
写配置了为什么release没生效呢
打开cmd窗口看看
release模式为什么不去build game.dll
#ifdef NDEBUG //release
char CommandLine[] = “/c cmake --build .\…\out\build\x64-release”;
#else //debug
char CommandLine[] = “/c cmake --build .\…\out\build\x64-debug”;
#endif
加一个依赖防止win32_game 编译失败不去编译game.dll,这个依赖会先编译game.dll 再编译win32_game
…唯一的小问题是,添加一个新开关仍然需要太多步骤
我们面临一个小问题,虽然它很小,但还是有些困扰。具体来说,就是每次我们需要将某些东西与 UI 连接时,都需要经过一定的步骤。这种情况变得有些繁琐,尤其是当我们想要添加新的元素时。如果在游戏中出现类似的需求,举个例子,可能是需要停止绘制某些元素,像是停止绘制所有的“召唤物”之类的东西。
每次要实现这些,我们就得手动添加代码来进行设置。比方说,想要新增一个功能,可能就需要手动加上一段逻辑,去做某些判断和处理。这个过程虽然看起来不复杂,但由于每次都需要重复这些步骤,所以有点令人觉得麻烦。这也是我们想要考虑如何优化这个流程的原因。
我们希望在添加新变量后,列表能够自动升级,但单纯为此解析代码可能过于繁琐
我们希望能够简化调试过程,尤其是当我们需要调整某些 UI 设置时,比如禁用“召唤物”的绘制。理想情况下,我们只希望通过修改一个变量就能完成,而不必每次都进入代码手动添加调试变量的监听功能。虽然这个过程本身并不复杂,也没有造成太大的麻烦,但它还是有点繁琐。
如果要彻底解决这个问题,我们可能会考虑开发一个自动化工具,能够扫描文件并自动更新调试相关的配置,而不是每次手动进行修改。通过这种方式,所有的调试变量会自动更新,避免每次都重复操作。尽管这样做可以节省时间,但这也可能有点过头,感觉像是做了多余的工作。因此,这种自动化的做法虽然能提高效率,但可能还是有些过于复杂了。
让我们把变量列表保存在一个单独的文件中
我考虑到一个比较简便的解决方案,那就是将调试变量放在一个容易找到的位置,这样就不需要每次都在代码中手动添加调试监听。我可能会将这些变量移到一个专门的文件里,比如一个 debug_variables.h
文件,然后在主代码中引入这个文件。这样做不仅能保持代码的整洁,而且也很方便,任何时候需要添加新的调试变量时,只需更新这个文件就好。
这个方法感觉比较简单直观,也不会太麻烦。所以,我觉得这样做是可以接受的。接下来,我打算用几分钟时间处理一些小问题,尤其是一些与收入相关的问题,可能有些细节需要修复。
另外,我也知道接下来需要开始处理一些新的 UI 元素,尤其是要完成我们的无线电菜单和一些新的 UI 屏幕。虽然这些工作很重要,但我决定先集中精力修复当前的几个小问题,等有了更多时间再开始新的 UI 开发。
我们的径向菜单应该允许我们在某项活动激活后不再选择它
目前,在调试菜单中有两个让人烦恼的问题。第一个问题是没有办法避免某些选择,原因是没有类似于内环的概念,导致用户始终会触发选择操作。理想情况下,想要做的改进是,通过调整鼠标位置和菜单中心之间的距离来控制是否触发选择。
具体的做法是,我打算不再使用目前设置热菜单索引的结果,而是将其重置为一个新的变量。然后,我会根据鼠标的位置来判断,如果鼠标位置和菜单中心的距离的平方小于菜单的半径,那么就允许触发选择。如果鼠标位置超出了菜单的半径范围,就不触发选择操作。这样可以避免用户误触发菜单,特别是当鼠标还在菜单的有效区域内时。
换句话说,只有当鼠标靠近菜单中心并且距离在菜单半径范围内时,才会允许选择操作。这样做的目的是为了避免用户在菜单区域外无意中选择了某个选项。
根据值为菜单项着色
我打算首先实现之前提到的功能,并且希望能够指示某些项是否已经设置好了,这样会更加直观。为了实现这一点,可以做一个简单的调整,比如设置一个项目的颜色为灰色,表示它当前没有被激活或设置。
因为我们已经知道调试变量的所有信息,不仅知道它的名字,还知道它的值,所以不仅可以获取它的文本,还可以根据它的当前值来判断是否需要高亮显示。如果变量的值满足某些条件,就可以决定是否高亮显示它。这会帮助更好地可视化哪些选项已经设置,哪些没有设置。
当然,在实现之前,我需要先清理掉一些临时的代码示例,然后重新测试这个功能,确保它能够正常工作并且按预期显示。
测试。菜单在我们停留在中心时应该不再选择任何操作
现在可以看到,调试菜单的功能已经开始正常工作了。刚开始时,我发现设置的逻辑有点反了,应该是检查鼠标是否移动到了菜单半径之外,而不是判断鼠标是否处于菜单内。这个问题我已经修正过来了,现在可以确保只有当鼠标移出菜单半径时,才会触发选择。
现在,用户可以从菜单中选择不同的选项,并且可以看到哪些选项是开启的,哪些是关闭的。例如,如果不希望“Grand Chancery”计算功能运行,只需要将其关闭,这样它就不会再被计算。同样,如果不希望“召唤物”继续跟随,可以将“召唤物跟随”关闭,之后它就不会再跟随了。
现在一切都按预期工作,可以顺利地选择、调节设置,调试代码也变得更加方便,甚至可以缩放视角等操作。总的来说,功能都已正常运行,达到了预期效果。
编译过程有点慢,但我们可以跳过资产构建器的编译
唯一不太满意的地方是编译时间有点慢,尤其是每次编译后的等待时间比较长。为了加快编译速度,我们可以采取一些措施。比如,资产处理器(asset processor)并不需要每次都重新编译,因此可以考虑在构建过程中禁用它,这样可以稍微加快编译过程。
在构建的批处理文件(cmake file)中,我们可以关闭资产处理器,这样编译会更快一些。此外,我们还可以关闭一些其他功能,比如重新编译不必要的部分,这样也能缩短编译时间。不过,虽然这些方法可以加速编译,但它们并不能显著提高刷新时间。
关闭资产处理器可能是一个值得尝试的优化,虽然它可能并不会对编译时间的提升有很大的影响,但至少我们可以看看这样调整后,编译时间会有多少改善。
总体来说,虽然编译速度并没有达到理想的速度,但相比以前,已经有了明显的改善,并且现在的速度也还算可以接受,不算太慢。
去掉影响也不大
我不明白为什么一个单独的if语句在运行时会很昂贵,你能给个例子吗?
我们讨论了在运行时插入调试代码可能带来的性能开销。通过一个具体的例子来演示,最初游戏的渲染非常流畅,帧率表现良好。但是,当开始加入条件语句时,例如一个if
语句来进行调试变量的切换时,帧率明显下降。我们通过不同的方式插入了多个if
语句,用来模拟在运行时执行不同的操作,结果发现,尽管帧率下降,但并不像预期的那样严重。加入更多的条件判断并没有显著影响性能。
我们还进一步测试了将这些调试代码封装在不同的静态变量中,以便在运行时开启或关闭这些判断。测试表明,在这种情况下,性能影响依然不是特别显著,尽管会有一些微小的下降。我们尝试了在代码中插入更复杂的逻辑,例如使用更高代价的操作,但结果也并未引起显著的性能问题。
在讨论了调试代码对性能的影响后,测试表明,在发布模式下,性能比调试模式下更好,运行时的性能更为稳定。即使启用了条件判断,性能的下降也并没有达到一个明显的程度,因此并不需要过度担心。关键是要确保在进行性能测试时,测试方法需要更加精准和严格,避免因代码对齐等因素带来的误差。
最后,虽然加入调试代码可能会稍微影响性能,但它对于调试工作非常重要,尤其是在开发过程中,能够通过这些调试工具确保代码的正确性。在性能优化时,可以通过合理安排调试代码的插入和关闭,来平衡调试与性能之间的关系。
为什么在配置文件中添加“// b32”注释?
我们之前遇到的冲突问题是由于最初考虑到需要某个功能来处理无限运动(infinite motion),因此在文件中加入了一些必要的标记。但是后来决定不再需要这个功能,因此可以去掉相关的b32
。如果以后发现这个功能确实重要,仍然可以重新添加一个工厂方法来处理。所以现在,去掉这些不再需要的部分是完全可以接受的,暂时没有问题。
你能在循环时切换调试选项吗?
可以在循环过程中切换调试选项。为了实现这一点,需要确保在每次循环中输入的鼠标事件能够通过。虽然鼠标输入和其他输入是分开的,但我们需要确保在输入处理过程中,鼠标输入的相关信息能够正常传递。
具体来说,鼠标输入会在一个单独的处理过程中进行处理,但在之后的输入回放时,这些鼠标输入信息可能会被覆盖。所以,如果想在循环中动态地访问这些调试功能,就需要确保鼠标输入能够正确地传递,避免在输入回放时被忽略或覆盖。
将鼠标处理与记录输入分离
为了确保调试编辑在循环中不被记录,需要让鼠标输入在调试过程中直接通过,而不是在回放时被记录或覆盖。为了实现这一点,可以采用如下方式:
首先,需要确保在每次循环中,鼠标输入事件能够通过处理逻辑。这意味着需要创建一个新的输入事件结构,将鼠标按钮和其他输入信息传递到游戏中。在这个过程中,可以考虑使用类似 newinput->mousebuttons
的逻辑来处理输入数据。
然后,需要复制相关的输入数据结构,将这些鼠标事件进行更新。通过复制这些结构,确保鼠标输入在调试过程中实时生效,而不是在之后的输入回放中被记录或覆盖。
但是,在实际操作中,可能会遇到一些限制,导致复制这些输入数据时变得困难。例如,有些情况下不能直接复制某些数据结构,可能需要额外的操作或者对输入的处理方式进行调整。
最终,通过这种方式,可以确保在调试过程中,鼠标输入可以正常通过,允许动态编辑,而不会受到回放机制的干扰。
录制之后播放鼠标菜单可以用
准备在循环时操作调试UI
现在,理论上这种方式应该可以让鼠标输入顺利通过处理逻辑,从而在调试过程中允许鼠标事件实时生效。这样一来,输入事件就可以在调试时被正常处理,而不会被回放机制覆盖或干扰。这意味着调试编辑的过程可以顺利进行,而不会受到录制回放的影响。
增加DrawDebugMainMenu()中的调试菜单半径
计划是在调试菜单中调整一个更明显的颜色,比如粉色,并且增加半径为400,这样能够更清晰地展示菜单的大小和位置。此外,比较时需要使用平方的值来进行长度的比较,以确保它与菜单半径的平方值进行匹配。这部分的细节会留到明天再处理。
尝试在循环时使用调试UI
现在调试菜单的UI部分已经完成。当开始循环时,调试菜单的UI也会显示在上方,可以进行交互。可以改变一些设置,例如更改地面棋盘的样式,调整其他元素。比如,可以让小伙伴跟随角色移动,也可以改变其他视觉效果。这些功能都能正常工作,能够实现预期的效果。
你应该在某个时候为UI添加一个优化开关
可以考虑在UI中添加一个优化开关,这是一个非常好的建议。为了实现这个功能,可以采用#pragma
指令来开关优化,这样可以在需要调试时关闭某些代码部分的优化。当调试时,可以方便地关闭特定部分的优化,而不影响其他部分的性能。这种方式让调试更加灵活,可以帮助更好地排查问题。
难道-O2不会把这些死分支优化掉吗?
为了优化死代码的去除,通常需要确保编译器能够识别出哪些代码是死的。然而,静态变量的存在可能会导致编译器无法正确识别这些分支是否为死代码,因为这些变量可能在其他地方被修改。为了解决这个问题,可以将这些变量声明为volatile
,这样编译器就会知道这些变量的值可能会在运行时改变,从而避免优化掉可能会被使用的代码。
你能有一个选项,允许你重新编译或简单地切换“if”吗?似乎大多数时候你会使用常规的“if”,所以变化是即时的,但在极端情况下你可以轻松地使用重新编译选项
为了实现一个在编译时可以选择切换优化或调试模式的选项,需要解决一个问题:如何在不重新编译的情况下切换这些选项。通常的做法是通过常规的if
语句,确保在大多数情况下切换是即时的,但在极端情况下需要重新编译来实现切换。
然而,问题在于,如果想要在运行时切换这些选项,可能会遇到宏和条件编译的问题。因为在C语言中,#if
不能包含在另一个宏内部,所以需要想办法在代码中实现这一点。为了做到这一点,可能需要使用一些复杂的宏组合,这种方法虽然可行,但编写起来会相对复杂,也可能不太优雅。如果C语言的宏处理器能够更强大一些,或许可以更方便地实现这一功能,但由于宏处理器的限制,这种方法可能不太理想。