运行游戏,为今天的开发设定初始场景
目前我们其实已经有点不记得今天具体该做什么了。上次我们当时实现了“脑子”(Brains)系统,让角色可以跳来跳去。我们还做了一个的实验,有人问我们是否可以将自己的头部与附近其他东西进行交换,那部分还挺有趣的。
虽然现在没法很明确地说今天要实现哪些功能,但有很多方向我们可以继续深入。与其做一个提前准备好的流程介绍,不如直接打开代码库看情况。现在感觉最有价值的方向,是继续完善“脑子(brain)”相关的逻辑,也许是让怪物动起来,比如之前提到过的小蛇状怪物,能自由移动一类的,也挺有意思。
现在游戏里,只有主角(hero)是真正使用完整移动代码的实体,其他任何角色其实都没有加入实际移动逻辑。所以我们可以先从让怪物动起来入手。之前那个“怪物”只是一个静止的躯干,现在我们可以给它添加移动能力。它要往哪儿移动其实无所谓,能动就行。
现在来看,我们可以进入脑子相关的代码部分。基本上,目前逻辑是这样的:如果当前实体所在的格子是可通行的,而且我们“扎根”在那儿,就会触发跳跃。所以其实我们可以很快做出一些测试逻辑。
看起来我们上次已经开始实现 Familiar(小跟班)和 Monstar(怪物)了,所以现在就先把这两个实体都搞定。我们可以继续为它们添加功能。
修改 game_brain.cpp
:让 ExecuteBrain
能够移动 Monstar
我们现在可以让 Monstar(怪物)真正地动起来。我们设定了,如果它是一个 Monstar,而 Monstar 实际上没有“头”之类的结构,它只有一个“身体”,那我们就从中提取出 Monstar 的相关部件。
由于 Monstar 只有一个身体,我们先判断这个身体是否有效(valid),因为如果它完全没有任何有效部分,目前我们就不想让它执行任何逻辑。
在确认它有效之后,我们的做法是:获取它当前位置加上一些随机偏移量后的位置附近的可通行区域(traversable)。目前我们暂时不加入随机性,只是硬编码一个偏移,比如它当前位置右边的一个单位。我们从这个偏移方向寻找最接近的可通行区域。
接着,如果当前的 movement mode 是“Planted”(扎根状态),我们就可以继续执行跳跃逻辑。我们会在这个新的位置上执行一次“事务性占用(transactional occupy)”,这是我们目前用来移动实体的机制。老实说,目前我们也不确定这个操作会造成什么具体结果,但目前看起来没问题。
然后我们需要一个变量来存储这个“可通行区域”的引用,我们已经把它加上了。
还有几个关键的调整:
首先,我们需要确保 Monstar 被正确地初始化并分配到了一个有效的“占用位置(occupying)”。如果没有这个,就会导致崩溃。实际上我们刚刚运行时也确实崩溃了,错误信息说明:在没有指定占用位置的情况下尝试了占用逻辑。
这就和我们之前处理主角时遇到的情况一样。主角初始化的时候会有一段逻辑设置 occupying = standing_on
,即开始站在某个位置上。我们现在也需要对 Monstar 做类似处理,确保它一开始就有明确的占用位置。
接下来就需要我们在初始化 Monstar 实体时,手动为它指定一个 standing_on
,并用它来设置 occupying
,这样它就可以正常运行、移动而不会触发崩溃。这个步骤在主角的创建逻辑里已经存在,我们要做的是将它移植到 Monstar 的初始化流程中。
把这部分拷贝到Monstar 里面
修改 game_world_mode.cpp
:让 AddMonstar
接收一个 traversable_reference
参数
我们确实创建了一个 Monstar 的大脑(brain),之前没有特别留意到。之所以要这样做,是因为我们之前实现了一个可以和其他实体“交换头部”的机制,而该机制只允许与拥有大脑的实体进行交换。因此,为了兼容这个逻辑,我们才为 Monstar 创建了大脑。
接下来我们面临的主要问题是:我们需要一种方式来指定 Monstar 实体在世界中的起始站立位置(standing on),从而设置其占用位置(occupying),否则在运行期间尝试进行占用操作时会出现错误。
我已经将 standing_on
变量添加到了相应结构中,并尝试在创建实体时对其赋值。虽然理论上我们也可以在创建该类型实体时自动判断其位置并赋值,但我觉得现在还不需要进行这一步,因此先暂时跳过。主要原因是,在创建世界地图的过程中,我们还需要对“实体放置逻辑”的整体结构进行整理和完善,所以现在还不是深入处理的最佳时机。
因此,我们目前的做法是在创建 Monstar 时,手动指定其占用位置(occupying)。也就是说,在调用创建函数时,我们就需要明确指定 Monstar 应该出现在地图的哪个位置。
从大的结构来看,我们在创建房间(如通过 AddStandardRoom
)时,其实是掌握了所有房间结构和放置信息的。然而我们没有将这些信息很好地返回出来。理想的做法是,我们应该让 AddStandardRoom
这类函数在返回时附带更多数据,例如:房间的关键节点、空地信息、站立点等。这样就可以直接利用这些数据来初始化像 Monstar 这样的实体的位置。
接下来的步骤应该是:修改 standard_room
结构,让它包含类似“站立点(StandingOn)”的成员。然后在添加房间时就顺便返回这个信息,并在创建 Monstar 的时候传入这些数据,完成初始位置的正确设置。这样一来,Monstar 就不会出现“无占用位置”的错误,同时也为之后更复杂的实体初始化和行为逻辑打下基础。
修改 game_world_mode.cpp
:让 AddStandardRoom
调用 AddMonstar
我们在每次创建标准房间(standard room)时,其实都可以直接在里面创建 Monstar(怪物)或 Familiar(伙伴)等实体。为了让这个过程更合理,我们将 AddMonstar
和 AddFamiliar
的调用移出封装函数外部,并在房间生成过程中判断是否在特定位置生成实体。
例如,我们可以设置一个判断条件:当生成房间的坐标是(-2,-2)时,就在这个房间中生成 Monstar。当然,我们也可以以后设置更多条件添加 Familiar,但暂时先不做这部分。
为了实现这个,我们需要在生成实体时,保留其所站位置的引用(traversable_reference
)。我们做法是:先将将要放置实体的位置保存在一个 traversable_reference
变量中,然后在调用 AddMonstar
时,将其作为参数传递进去。这样就能确保 Monstar 初始化时就拥有正确的 occupying
位置。
具体来说,traversable_reference 本身就是一个结构体,里面包含了实体引用(entity_reference)以及一个 index。我们手动设置 entity_index 等于 Monstar 的 ID,index 默认设置为 0 就好。然后将这个结构赋值给 standing_on
。
在调用 AddMonstar
时,我们也把 world_position
(即 Monstar 所在的世界位置)一并传入。当前的调用顺序要求我们必须在调用 AddMonstar
之前,就构造好相关数据结构,因此相关代码放置位置也作了相应调整。
尽管目前的逻辑还稍显粗糙,我们后续会对这些初始化、放置流程进行清理和结构优化,使其更合理。比如将 AddPiece
之类的渲染相关逻辑移出到专门的实体模块中,而不是混在房间初始化逻辑里。
下一步,我们暂时注释掉脑逻辑部分的行为处理代码,先验证 Monstar 是否能正确初始化和出现在预定位置,确保它的 occupying
设置成功,之后再回来处理其大脑逻辑(例如移动、行为等)。这样我们可以更清晰地分阶段调试和验证系统功能。
运行游戏,发现 Monstar 没有正确初始化
Monstar 虽然已经生成出来,但我们可以明显看出它并没有被正确初始化。判断依据是它脚下的格子没有呈现蓝色高亮。按照之前的设计,如果实体正确设置了它所“占据”的格子(occupying
),这个格子应该会以蓝色显示,表示有单位站在上面。
因此我们接下来要排查 Monstar 初始化过程中,是否确实完成了 occupying
的设置。我们之前已经尝试在创建实体时,将其“站立”的位置通过 traversable_reference
结构设置进去,并将该结构传递给 AddMonstar
。但现在看来,这个设置似乎没有生效。
我们当前还没有系统化处理“世界创建时”的实体放置流程,所以目前只是临时以较简单的方式将 Monstar 放到房间里。这种方式与运行时动态生成实体的方式可能存在差异,而我们未来理想的状态是,运行时和初始化时的实体创建流程应该尽量保持一致,避免出现不同逻辑路径导致的 bug。
不过现在我们还没完全设计好统一的世界构建系统,因此暂时先不深入处理这个问题。当前目标只是尽快确认 Monstar 是否能在初始化时正确占据一个格子。所以我们继续检查 AddMonstar
这个函数的实现逻辑,确认它是否有把 occupying
正确设置为我们传入的 traversable_reference
。
接下来的步骤:
- 重新回顾并检查
AddMonstar
内部的逻辑,确认occupying
字段是否有正确赋值。 - 如果赋值了,但仍未看到蓝色方格,可能要确认相关渲染逻辑是否根据
occupying
的设置去改变格子颜色。 - 确保 Monstar 被放置到的格子本身也是有效的 traversable(可通行)类型,否则系统可能忽略其高亮逻辑。
- 最后,如果所有数据设置都正确,也要确认渲染更新顺序是否同步了状态。
目前这个初始化问题暂时还是比较局部的,等我们之后设计并统一创建实体和世界的完整接口时,再来系统性地处理这些问题。现在的重点是确保 Monstar 的实体数据状态和占据逻辑可以生效,便于后续开发其 AI 行为逻辑。
修改 game_world.cpp
:让 PackEntityReference
在没有 SimRegion 且没有 GetHashFromID
时保持 Index 不变
我们当前在给实体(Monstar)设置其站立的格子(standing_on
)时,代码确实有执行这一步,设置了正确的 entity_index
。理论上,如果设置成功,在渲染阶段应该能看到其下方格子被点亮。但显然这并未生效,说明设置可能没有被正确“打包”(pack)进世界数据中。
经过进一步排查,在 world.cpp
中处理实体打包(packing)时,我们发现了关键问题:
在打包过程中,如果某个实体所在的模拟区域(sim_region
)不存在,当前的逻辑会将该实体引用的索引值设置为 0
。这就意味着,即使之前我们已经在 AddMonstar
的时候设置了 standing_on
指向一个合法的 traversable 实体,打包时也会把它覆盖掉,导致关联失效。
这个行为显然不合理。正确的逻辑应当是:
- 如果存在
sim_region
,我们就根据实体 ID 去计算哈希(get hash from ID
)并设置索引; - 如果不存在
sim_region
,我们应该保留之前的 index 值,而不是强行设置为 0。
因此,我们做出调整:
将判断逻辑改为,只有当有 sim_region
时才修改 entity_index
,否则保持其原样。这种做法更符合预期,可以确保在创建实体时手动设置的引用信息不会被打包过程清除。
这个修复可以让 Monstar 的 standing_on
正确保持我们在初始化阶段赋予的值,从而让渲染系统正确识别其位置并显示蓝色高亮。同时也为后续实现更复杂的实体状态管理打下基础。
运行游戏,看到 Monstar 现在能正确占据一个格子
问题已经成功解决,现在可以看到 Monstar 正确地占据了一个格子。具体来说:
我们原本在初始化 Monstar 时,设置了它所站立的位置(standing_on
),但由于在后续世界打包(pack)过程中,如果该实体所处的模拟区域(sim_region
)不存在,程序会把引用的实体索引(entity_index
)重设为 0,导致之前设置的信息被覆盖,从而无法在渲染时正确显示它所站立的格子(通常是蓝色高亮)。
修复方案是修改打包逻辑:只有在存在 sim_region
的情况下才重新设置 entity_index
,否则保持原值不变。这样就不会破坏我们在初始化时手动设置的关联信息。
修改之后,Monstar 的 standing_on
字段保持有效,它确实被识别为占据了一个可通行格子。渲染系统因此能正确点亮其下方区域,验证了这一修复已生效。
这也意味着我们现在可以更稳定地初始化非玩家实体的位置,并使其在世界中正确“着陆”,为后续如 AI 逻辑、移动机制等打下基础。接下来可以继续推进怪物的行为实现或进一步完善实体创建流程。
修改 game_brain.cpp
:启用 Monstar 的移动逻辑,运行游戏并观察它的移动
我们现在已经可以恢复之前的脑代码逻辑并进行测试,看看是否如预期般生效。测试显示一切运行良好:Monstar 成功从其初始位置跳跃到了其他位置,说明行为逻辑已经正常执行。
当前的实现中,为了测试方便,我们临时禁用了碰撞体(collision volume),因此 Monstar 能够自由跳跃,不会受到任何阻碍。这种状态正是我们所期望的,验证了行为代码的生效路径没有问题。
尽管这个 Monstar 的行为非常简单,甚至可以说是无趣的——只是不断跳动,并没有复杂的逻辑或目标——但这并不是问题。目前阶段的目标并不是构建一个复杂的怪物 AI,而是验证:
- 实体能否被正确创建并定位在世界中;
- 行为代码能否成功控制实体的动作(如跳跃);
- 空间占用和运动系统能否响应这些动作;
- 初始化逻辑、打包机制是否稳定可用。
以上验证都已成功,这为后续进一步开发更复杂的行为逻辑(如路径规划、追踪玩家、避障等)打下了基础。接下来可以考虑:
- 引入基本的移动目标逻辑;
- 添加简单的状态机来控制行为;
- 恢复或重新启用碰撞系统;
- 丰富怪物的视觉表现和交互方式。
目前结果非常理想,测试代码稳定生效,系统基本流程闭环已打通。
可以看到Monstar 跑过去了
修改 game_world_mode.h
:为 game_mode_world
添加 random_series GameEntropy
在游戏的 game_world_mode
代码结构中,我们希望引入一个新的熵源:一个专门用于游戏行为逻辑的随机数序列,命名为 game_entropy
。这个 game_entropy
将作为游戏逻辑(如怪物行为、AI 决策、事件生成等)所使用的随机源。
虽然技术上讲,我们完全可以使用现有的 effects_entropy
来承担这部分职责,但出于结构清晰性和潜在的可控性考虑,目前阶段我们决定将其分离,也就是说:
effects_entropy
专注于用于视觉、音效等效果类的随机需求;game_entropy
专门用于游戏逻辑层面的随机需求,例如 AI 决策或非决定性行为路径。
具体做法是,在初始化 effects_entropy
的同时,我们也将对 game_entropy
进行初始化,使其成为 game_mode_world
状态的一部分。
这样做的好处包括:
- 便于调试与可控性,例如我们可以为游戏行为设置种子来复现实验性状态;
- 保持游戏逻辑与渲染逻辑的解耦,避免它们之间因共享随机源而产生非预期关联;
- 为将来的并行处理或模块化架构奠定基础。
接下来,游戏中任何需要使用随机性的行为逻辑(例如怪物移动的随机跳跃方向)都可以从 game_entropy
中获取随机值,而不是混用其他随机源,保持逻辑清晰。
修改 game_world_mode.cpp
:让 PlayWorld
初始化 GameEntropy
并用它来生成房间
我们现在对游戏逻辑使用的随机数系统进行了进一步调整和整合。
我们在初始化时已经添加了 game_entropy
,现在要做的事情是确保游戏中用到的随机序列统一来自这个 game_entropy
,这样可以保持逻辑一致性并增强可控性。
具体来说,我们将所有需要随机性的行为逻辑部分(例如怪物 AI 的移动决策、行为选择等)中使用的 random_series
实例绑定到 world_mode.game_entropy
上。换句话说:
random_series series = world_mode->game_entropy;
通过这一调整,我们确保了在整个游戏逻辑过程中,所有的非决定性行为都基于同一个随机源,避免出现由于多个独立随机序列引起的状态漂移或无法复现的问题。
这一改变的好处包括:
- 一致性:所有游戏逻辑中的随机性行为具有统一的来源;
- 可测试性和可重现性:可以通过设置固定种子来复现复杂的逻辑流程;
- 结构清晰:渲染相关的
effects_entropy
与游戏逻辑的game_entropy
各自独立,不再混用。
这样,今后在实现更多 AI 或行为模式时,我们都可以从 series
(也就是 game_entropy
)中获取随机值,使整个系统逻辑清晰、统一且可维护。
修改 game_brain.cpp
:让 ExecuteBrain
使用 GameEntropy
设置 Monstar 的移动方向,并接收 game_mode_world *WorldMode
参数
我们打算为游戏逻辑增加一个统一的随机源,用来驱动诸如怪物的移动等行为。为此,我们使用 world_mode
中的 game_entropy
来生成随机方向偏移,让怪物可以随机朝任意方向跳跃。
在实现过程中,我们遇到了一些问题。我们希望在更新“脑”(Brains)的时候访问 world_mode
,以便获取 game_entropy
,但在代码中 world_mode
无法被识别为已定义类型。这导致编译错误。
经过排查,发现问题的根源是由于文件命名和类型命名不一致。例如:
- 文件名为
world_mode.h
; - 类型名却叫做
game_world_mode
; - 我们尝试在
brain.cpp
中包含并使用world_mode.h
里的定义,但由于命名混淆,导致编译器无法正确识别类型。
更令人困惑的是,world_mode.h
已经在 brain.cpp
中包含,而且路径也是正确的,但由于类型和文件命名混乱,误以为未定义。
我们确认了:
brain.cpp
确实包含了world_mode.h
;world_mode.h
中有game_world_mode
类型;- 问题源于我们对类型名和文件名的混用和理解偏差。
最终我们决定:
- 把
game_world_mode
正确传入脑更新逻辑; - 调整函数调用处,让其显式传入
game_world_mode
实例; - 明确命名规范,避免未来类似问题,比如文件名和类型名尽量统一。
此外,我们对命名表达出一些困惑:为什么类型叫 game_world_mode
而文件叫 world_mode.h
?这种命名不统一使得代码阅读和维护更加困难,是我们未来需要整理和清理的地方。虽然当前能暂时运行起来,但这类结构问题迟早需要系统性解决。
看着 Monstar 随机跳来跳去
目前我们的系统基本达到了预期效果。现在在“脑”逻辑中,我们只需简单指定从哪里跳到哪里,底层的代码就会处理跳跃的具体实现。这使得逻辑更为清晰简洁,也更易于扩展。
另外,由于怪物已经成功占据了一个方格,现有的“占用检测机制”开始发挥作用:我们无法走到怪物所处的位置,怪物也不能走到我们的位置。这是因为我们实现了事务性的方格占用机制,在移动过程中确保不会发生相互穿插或重叠。从目前的运行情况看,该机制表现正常。
不过,目前怪物的移动显得速度有些过快,这个问题后续可能需要进行速度调节,以优化游戏体验。同时,怪物的外观显示也不完善,像是“断头的怪物”,这也正是我们目前的实现逻辑中未定义的部分,暂时没有具体设定,后续还需进一步开发和美化。
此外,在添加房间(Room)时,我们注意到一个潜在问题:不应该在不能实际站立的地方(如树木或障碍物位置)生成地面方格(Ground Points),否则会引发逻辑冲突。对此有两个解决方向:
- 避免在障碍物区域生成地面方格;
- 或者,确保所有障碍物(如树木)都明确占用其所在的方格。
这种改进虽然不是当前最紧急的事项,但为了系统完整性和未来的稳定性,值得在后续迭代中优先考虑。
总体来看,我们已经迈出了一大步,现在的系统能正确处理基本的实体放置与路径阻挡逻辑,为后续的怪物 AI、寻路等机制打下了坚实基础。
修改 game_world_mode.cpp
:引入 struct standard_room
,让 AddStandardRoom
返回它,并指定其占据的格子
我们想让树木占据特定的格子,所以在添加标准房间时,可以简单地先实现一个临时的方案,避免系统崩溃。每当添加标准房间时,会有一个类似“遍历引用(traversable reference)”的结构来表示房间内的地面格子,比如一个17×9的格子数组。我们会用偏移量(offset)来定位这些格子,比如offsetX加8,offsetY加4,然后将这些格子标记为“占据中”。
这样,当我们添加怪物时,可以指定怪物在房间内的具体位置。例如调用“添加怪物”的时候,就可以传入房间的某个格子位置,使怪物出现在正确的位置。
接下来,在添加其他元素(比如墙壁或树木)时,也可以用类似的方法,将这些元素“放置”在已有的遍历格子上。这样做的理由是,我们可能希望这些墙或树是可破坏的,或者可以通过烧毁、移动、挖掘等手段改变它们的位置和状态,因此即使是阻挡的对象,也需要先创建可遍历的格子,然后在上面放置物体,而不是直接不创建格子。
只有那些绝对不能站立的地方,比如山中间或者未开发区域,才不会创建遍历格子。我们默认任何能够移动或改变阻挡物的地方都应该有可遍历的格子。
在具体实现时,添加墙壁的代码会改成基于房间结构的格子遍历,而不再使用之前的AB样式的X、Y坐标遍历。用房间数据里的行列数量循环遍历房间的所有格子,然后通过房间位置的偏移,计算出实际世界坐标。这样能更自然地和房间结构绑定。
总结来说,我们的方案是先创建全覆盖的遍历格子,表示所有可能站立的位置,然后再将树、墙等对象放到这些格子上,这样既方便后续动态修改,也保证了基础的地形和实体布局的一致性和灵活性。
修改 game_world_mode.cpp
:让 AddWall
接收 world_position P
和 traversable_reference StandingOn
,并指定树木占据格子
我们让 AddWall
的实现方式和之前保持一致,即传入一个世界坐标(World Position),而不是以前那种复杂的参数结构。接着就可以移除旧的处理逻辑,使代码更加简洁统一。
这一修改之后,我们的功能效果应该和之前一样,运行结果也确实如此,这说明改动是成功的。接下来,我们需要做的就是在添加墙体时,也传入它所“站立”的可遍历格子(traversal reference),就像之前处理怪物(Monstar)时那样。
由于墙体站在这个格子上,只需要简单地将该实体的占用状态(occupation)记录在格子中,也就是说,把实体与可遍历格子的占用关系关联起来。
通过从房间结构中取出对应位置的格子引用,我们就能在实体被添加时,确保该位置被正确地标记为“已被占据”。
这样一来,树木或墙体将会正确地占据它们所在的格子,避免其他实体(比如玩家或怪物)穿越它们,同时也为后续可能的动态交互(比如破坏或移动墙体)打下良好的结构基础。整个世界空间的实体布局和遍历状态将更加统一、清晰、可维护。
运行游戏,发现 Monstar 无法跳上树木,但我们当前可以跳过它们
我们现在已经实现了让墙体或树木等静态物体正确地占据格子,这样一来,怪物(Monstar)已经被限制在特定位置,无法任意移动,这部分效果已经正确实现并验证。
但还有一个问题尚未解决:我们仍然可以穿越一些被占据的位置,跳跃到它们后面的地方。也就是说,尽管格子被占据了,但我们可以直接越过它们。这是因为当前系统中还没有对“跳跃距离”的限制机制。我们可以无限制地向任何方向跳跃,无论中间有什么障碍物,跳跃的路径都不会被阻挡。
目前缺失的机制包括:
- 最大跳跃距离限制:没有设定玩家或怪物能跳多远。
- 路径阻挡判定:没有判断路径中是否存在阻碍跳跃的对象,比如墙体或高地等。
- 是否应该检测跳跃路径上的碰撞:比如跳跃中途如果“头部”碰到什么东西,是否应该阻止跳跃,目前也未定义这类碰撞行为。
虽然这些功能我们已有相关代码支持,只是暂时还未启用。我们已有控制碰撞检测的模块,实现起来并不复杂。例如已有可开启的高度检测逻辑,可以判断是否在跳跃过程中与障碍物相撞。
下一步工作建议:
- 为跳跃行为设定一个明确的最大跳跃半径(如基于菱形或圆形区域)。
- 启用路径检测机制,确保跳跃路径中的任何阻挡元素都会中止该行为。
- 根据跳跃实体的形态(如有“头部”碰撞箱)决定是否开启额外的垂直方向检测。
这些限制将有助于构建更具策略性和真实感的移动系统,防止跳跃穿透障碍带来的不合理现象,并为更复杂的战斗和环境互动奠定基础。
修改 game_world_mode.cpp
:让玩家的头部与树木发生碰撞
目前我们已经尝试为某个角色开启碰撞体积,通过这种方式,角色在移动过程中会被阻挡,无法继续穿越实体,这从技术角度来看是有效的。然而,这种处理方式是否合理,目前还没有明确的结论。我们在思考是否真的要依赖碰撞体积来限制跳跃路径,还是将碰撞体积仅用于特定的交互行为,例如实体之间发生接触、攻击或反应等情况,而不是用于路径阻挡。
我们目前的倾向是将碰撞体积保留用于实际“事件交互”用途,例如被击中、发生交战等,而不用于基础路径判断。因为跳跃过程中的实体,例如“头部”,本质上可能是“浮动”的,也就是说它不会实质阻挡路径,只是一个表现层效果。
这个问题引出了另一个当前存在的技术问题:角色跳跃过程中会出现“头部卡住”的现象,即在移动中角色头部发生粘滞,表现为不流畅或被强行停住。我们推测这是由于之前改动了控制器系统相关的逻辑(controller code),或者是在整合相关行为逻辑时(例如组合构建 composite build)未彻底调试好相互关系。
因此,接下来我们计划:
- 评估碰撞用途:暂不使用碰撞体积来限制路径跳跃,仅用于真正的物理或逻辑交互(如攻击触发)。
- 完善跳跃控制逻辑:跳跃的路径阻断和限制应当通过路径系统本身来判断,而不是依赖碰撞体积。
- 排查头部粘滞问题:进一步检查控制器系统代码,尤其是跳跃与位移的整合逻辑,查找角色在移动时为何出现卡顿或粘滞现象,尤其是在复合行为处理(如合并控制器逻辑)后可能存在的断裂。
这将有助于使整个跳跃与碰撞系统更加合理,也为后续功能(如可破坏墙体、浮动单位、攻击范围等)打下坚实的基础。
修改 game_brain.cpp
:停止 ExecuteBrain
检查删除标志并移除不必要的控制器代码
目前我们在分析跳跃控制与角色粘滞的问题,重点放在脑控制系统(brain)部分的逻辑。以下是对当前控制逻辑和问题的详细整理:
控制逻辑相关的模块说明与调整
初始化部分判断逻辑:
- 判断是否为 analog 控制类型(类比手柄或输入设备);
- 之前包含了对“是否被删除”的检查,但目前判断这是多余的,因为 brain 是在游戏循环前执行的,所以我们决定去掉该判断。
控制器数据处理:
设定了 hero 的 DDP(速度或方向变化向量);
包含一段“交换控制权”的代码,但这部分目前被判定为不重要,可忽略;
保留了
d_sword
(武器状态?)的处理,因为后续代码中仍有使用;删除了原先的
change_lane
(切换通道)逻辑,因其被改为使用空格键处理;recenter_timer
(重置计时器)的用法进行了调整:- 改为先赋值再在下方做 countdown,而不是同时进行递减和使用;
- 避免重复操作带来的状态混乱;
角色头部“粘滞”的分析入口:
- 有逻辑在尝试获取“头部最接近可通行点”(
get_closest_traversable
),但我们认为这一判断可能不必要; - 后续判断 recenter_timer 是否归零,若是,则根据头部当前位置与目标点之间的 delta DDP 决定是否进行跳跃或移动;
- 如果当前按键方向速度(Khan hero DDP)小于某阈值,就触发 no_push 状态;
- 如果没有“推力”且计时器允许,角色将被移动到新的位置。
- 有逻辑在尝试获取“头部最接近可通行点”(
当前遇到的问题与怀疑
- “no_push” 状态似乎并未正常工作,角色经常在无输入状态下依然卡在某些位置,导致“粘滞”;
- 怀疑 DDP 的判定与实际物理状态之间存在偏差,可能是 DDP 没有被正确重置或初始化,或者是 spring(弹簧)系统的应用方式有误;
- 此外,“头部”浮动状态的控制尚未完全完善,对跳跃行为是否能触发、持续时间等因素未做精确控制;
- 控制器中判断轴向是否有按键输入、是否重心居中等逻辑需要更清晰地拆分处理,以防止状态机互相冲突。
初步改进计划
- 清理无效判断与多余逻辑;
- 对 no_push 的判定进行调试与日志验证;
- 明确 recenter_timer 的状态变化节奏,避免递减与使用顺序混乱;
- 验证
get_closest_traversable
是否真有必要,是否多余调用,避免干扰逻辑清晰度; - 优化 DDP 判定阈值,避免角色在几乎零速时仍被判定为正在“推动”状态;
- 未来计划将角色行为与控制器状态做更加分离的状态同步,以提高整体清晰度与可维护性。
这些优化将有助于解决当前跳跃动作异常、角色位置粘滞等问题,同时也会为更复杂的交互行为打好基础。
修改 game_brain.cpp
:为了调查头部卡住的问题,添加 DEBUG_B32
和 DEBUG_VALUE
变量,并将头部的 UnitMaxAccelVector
设置为 false
当前我们聚焦于角色控制过程中“粘滞”和减速的问题,尤其是与 no_push
逻辑、DDP(方向速度)判断,以及 recenter(居中)机制之间的关系。以下是本阶段的详细总结:
一、调试输出添加与状态观察
我们决定不再依赖猜测,而是通过添加调试信息来明确系统在每一帧的状态:
使用
DebugBoolean
显示状态变量:- 增加
timer_is_up
和no_push
的可视化调试输出; - 验证
timer_is_up
是否按预期为true
; - 检查
no_push
是否在应为true
的时候是false
。
- 增加
增加对当前 DDP 值的显示:
- 尤其关注目标轴向上的 DDP 值;
- 如果该值为负(例如 -1),说明确实存在速度输入,
no_push
理应为false
; - 防止在仍有输入的情况下错误触发 recenter 动作。
调试 Recener 执行情况:
- 新增变量来记录是否执行了 recenter;
- 输出该变量以验证逻辑是否在错误时间被触发。
结果表明,整体判断逻辑运行基本正常:
timer_is_up == true
时只有在no_push == true
并且 DDP 小于阈值的情况下才会进行 recenter;- 当前的粘滞现象并非 recenter 被错误触发所致。
二、初步排查:角色减速/粘滞的可能原因
头部 MovementSpec 的干扰
- 怀疑是否是之前存在的问题重现,即
head
的 movement spec 配置引发了异常; - 计划再次检查
head
的 movement spec 是否与主角逻辑冲突,尤其是速度设定、加速度或 drag(阻力)等参数。
- 怀疑是否是之前存在的问题重现,即
move_entity
函数内逻辑分析:- 检查是否存在 DDP 被额外处理的逻辑;
- 发现在
move_entity
中,如果启用了unit_max_accel_vector
会修改 DDP; - 当前我们希望完全手动控制该值,因此决定禁止使用
unit_max_accel_vector
。
保留对速度与阻力的作用:
- 认为 speed 与 drag 这两个物理效果依旧有效且合理,不应被禁用;
- 确认没有其他代码在非预期位置改变 DDP。
三、结论与后续工作方向
- 调试验证现阶段 recenter 与 no_push 判断逻辑运作良好;
- 问题极有可能源于 head movement spec 中残留旧设定或默认物理参数的影响;
move_entity
函数中的默认行为需进一步裁剪,避免隐式 DDP 修改干扰外部控制器传入的值;- 后续需要深入检查和统一
hero
与head
控制逻辑,确保两者不冲突、不重复处理加速度、速度或位置; - 考虑加入更直观的调试信息,例如当前 entity 的 speed、drag 值、是否在移动、控制器状态等,用于实时分析行为表现。
这些调整和验证将帮助我们定位并解决角色控制中潜在的物理或逻辑错误,提高控制器输入响应的稳定性和准确性。
运行游戏,触发与 MaxEntityVelocity
相关的断言
我们发现某个实体的速度异常高,甚至有可能违反了设定的总速度限制。分析中指出,虽然这个实体每秒的 DDP 达到了 72,但因为存在阻力(drag),是否真的有影响还不确定。
我们本应在 DDP 初始化时将其设置为 0,且整个过程中其值都应保持合理范围,因此在代码中对 DDP 的设定应该不会导致问题。按理说,调用该逻辑时,DDP 应该始终从 0 开始,不应出现突兀的高加速度。
进一步推测,如果出现过高的加速度,很可能是弹簧机制本身所导致的。除此之外,并没有发现其他机制会引发类似的异常加速。
此外,还讨论到,我们其实并不希望该段代码对其他部分状态造成修改。尽管如此,某种程度的 drag 效果是我们想保留的,因为它带来了一些物理反馈上的“趣味性”。
总结而言,异常加速度的问题很可能源自弹簧系统的物理计算,DDP 的初始化流程没有发现明显错误。我们希望保留 drag 效果,但避免不必要的状态变更,确保整个系统在物理模拟上依然合理。
修改 game_sim_region.cpp
:让 MoveEntity
不再使用 move_spec
我们将当前的加速度乘以了 30,同时又添加了一定的阻力(drag),但这样做其实并不合适。因为当前的加速度计算中已经包含了 drag 的影响,再额外叠加 drag 并不合理。我们认为,这些物理运动相关的处理逻辑应当被统一考虑,而不是分散在不同的部分。
原本在模拟区域(sim region)中的移动逻辑代码显得多余且不合时宜,事实上这段代码已经带来了不少问题,因此决定将其彻底移除。我们不再保留 move_spec
的概念,把它视为无效处理,如果真的需要类似的功能,以后可以在外部以其他方式单独处理。有关 drag 等效果,也会放在一个更合适的位置中处理,不再混入已有逻辑。
经过检查后确认,原 drag 是 move_spec
中唯一保留的内容。我们计划暂时将 drag 相关的处理代码单独提出来,可能稍后还会进一步移动位置。目前代码中除了移动相关的部分,应该不会再出现错误。
整体来看,我们对移动逻辑做出了一次较大的重构:删除了无效或多余的封装逻辑,简化了代码结构,明确了 drag 等物理量的处理位置,以便提升系统的可维护性和行为一致性。
修改 game_brain.cpp
:取消实体使用 move_spec
我们决定彻底移除 spec
,这也意味着实体本身不再需要拥有 movement_spec
。所有与其相关的定义和引用都已清理,整套旧的运动规格机制正式废弃。
当前的状态下,跳跃器(hopper)仍然可以正常进行跳跃操作,这说明基本的物理行为逻辑仍在起作用。然而,头部实体现在无法移动,因为我们暂时还没有真正将加速度(DDP)应用到它的行为逻辑中。
这说明,我们已经剥离掉了旧的移动控制逻辑,处于一个过渡阶段:跳跃逻辑仍然有效,而更一般化的运动机制还未接入新的加速度应用系统。下一步需要重新建立一个更清晰、更合理的加速度驱动方式,将 DDP 正确地作用到所有需要响应物理运动的实体上。整个流程正在朝向去中心化、更灵活的物理行为控制体系演进。
修改 game_brain.cpp
:改变 ExecuteBrain
设置头部加速度 ddP
的方式
我们回到实际执行加速度应用的部分,对之前被删除的一些代码进行了恢复和重构。
首先,我们重新明确了运动中的两个部分:一个是玩家的主动移动意图,另一个是居中力(re-centering force)。主动移动需要受到最大加速度限制,而居中力作为一种系统内部产生的“拉回”机制,不应受限于玩家输入的最大加速度。
我们通过计算 DDP(加速度),确保它是单位向量,从而避免对角线移动比水平或垂直移动更快的情况。接着,我们将这个方向向量乘以预设的移动速度(movement speed),得到真正的加速度矢量。
在这个过程中,我们对 DDP 做了如下处理:
- 如果 DDP 的长度大于 1,就将其归一化为单位长度;
- 将归一化后的 DDP 乘以移动速度,得到玩家期望的加速度;
- 居中力的应用是单独处理的,不受单位长度限制,也不会被归一化;
- 拖拽力(drag)是另外独立作用在速度(DP)上的阻尼效应。
我们将拖拽系数(drag coefficient)直接应用在速度上,并且考虑未来可以把它变成一个可调参数,例如通过调试工具或其他方式进行微调。
在代码的重构过程中,我们还将所有不必要的耦合逻辑从运动代码中分离出来,把一些用于游戏性调整的部分放到了更自由的外层逻辑中,以方便将来更具“手感”的调试和测试。
调试中我们发现拖拽力过大,导致角色完全无法移动。分析后确认是因为加速度的叠加方式写错了——我们使用了“等于”赋值而不是“加上”——所以原本希望与之前的力量叠加的加速度被覆盖掉了。修复为 +=
后,角色终于可以正常移动,整个加速度逻辑开始发挥预期效果。
总之,我们成功将玩家控制加速度、居中力、拖拽力三者分离,并建立了清晰的应用路径,使得运动逻辑更加清晰、合理,同时保留了未来进行微调和非线性控制的空间。
运行游戏,发现已经修复了之前的 bug
现在我们似乎也已经修复了之前的那个 bug,这是个好消息。
目前大部分想要处理的内容已经完成。时间还剩下大约三分钟,于是我们考虑是否可以回到之前“熟悉的头部漂浮”效果部分,开始重新处理那块内容。
在此之前,我们清理了一些调试代码。这些调试输出目前已经不再需要,所以被移除,保持代码干净简洁。当前逻辑暂时保留原样,准备继续开发下一个目标。
修改 game_world_mode.cpp
:让 AddStandardRoom
调用 AddFamiliar
,并让 AddFamiliar
接收 world_position P
和 traversable_reference StandingOn
首先,我们在熟悉体(familiar)的代码中需要真正地创建一个熟悉体。之前已经设置好了相关结构,但实际上并没有执行添加动作。
接着在逻辑中加入添加熟悉体的步骤,例如在某个房间的某个位置(room P)添加一个持续三轮的熟悉体,并进行编译。
接下来是熟悉体的构造,其将会接收一个 p
值作为其初始位置,就像之前一样处理位置赋值。
然后提到了一个“排序”(sort)的问题,这虽然一开始可能并不明显,但它引出了一个关键点:我们需要决定如何处理漂浮物体(floating objects)在占用格子上的逻辑。
也就是说,当熟悉体是漂浮状态时,我们要明确其是否真正“占用”它所在的格子。这个设计决定会对游戏中物体的碰撞、移动等系统产生影响。
目前先继续往下推进,之后再结合这些因素进一步完善熟悉体行为的整体逻辑。
运行游戏,看到 Familiar 正确占据它的格子
熟悉体目前会占据一个格子,因此角色本体无法进入它所在的位置。目前我们还没有对“头部”该如何处理做出明确的定义,这部分稍后再完善。
接下来需要实现熟悉体的头部也能移动的功能。为此,首先需要引入一个概念,让熟悉体具备可移动的能力。这意味着需要为熟悉体添加运动逻辑,并定义其运动行为如何运作。
初步的设想是建立某种机制,让熟悉体在空间中具备自己的位置和目标移动逻辑。这将包括处理其自身的 DDP(加速度变化向量)、运动速度、可能的拖拽力(drag),以及与主角之间的距离关系,以便实现自动跟随或漂浮等行为。
总体目标是使熟悉体既可以在物理空间中漂浮移动,又不会干扰主角的行动路径,还能表现出一种智能或拟人的行动方式。这需要后续进一步定义熟悉体的运动参数、行为规则和更新频率等。
修改 game_entity.h
:从 entity_flags
中移除 Moveable
标志
我们决定按照之前的设想处理实体的属性标志,比如 collides
(可碰撞)、movable
(可移动)、deleted
(已删除)等标记。其中,movable
被认为已经不再有必要,因此我们决定将其移除,代码中也做了相应修改。
至于 collides
,我们目前还不确定它是否仍然必要,因此暂时保留,等待后续具体情况再做决定。考虑到整体系统设计的发展方向,这些标志的管理方式可能还需要进一步思考和调整。
接着我们继续推进逻辑。当前的策略是:如果一个实体(例如熟悉体)请求了位移(有 DDP 值),那我们就执行它的移动;否则就不处理。这个逻辑简单直接,有助于快速推进熟悉体的运动系统。
到目前为止,熟悉体的控制系统已经逐渐成型,移动逻辑也趋于合理。接下来的计划是在下一个开发周期继续完善熟悉体的行为和状态处理逻辑,最终实现其完整功能。我们离目标更近一步了。现在时间到了,进入问答环节。
问答环节
为什么有时候纹理加载不正常?
我们发现当前纹理没有正确下载的问题可能与早期的实现方式有关。在大约四五十期之前,我们曾尝试实现多线程纹理下载机制,这本来在理论上应该是可行的,但在实际测试中表现非常不稳定。
虽然在某些特定机器上,比如我们当前使用的这台,偶尔是可以正常工作的,但它仍然存在失败的可能。而在其他机器上,失败的频率就更高了。这说明多线程纹理下载在我们的引擎架构下并不是一个可靠的方案。
因此,我们最终决定放弃这种方式,并明确未来正式发布的游戏中将不会使用多线程方式下载纹理。现在出现纹理加载失败的问题,很可能是之前尝试过多线程下载留下的影响或残留逻辑。接下来需要全面清理和替换掉这些不稳定的实现,以确保最终版本的稳定性和兼容性。
为什么决定将游戏设计为基于网格的?
将游戏设计为基于网格的系统,这是一个纯粹的设计决策,不属于当前开发内容的讨论范畴。
另外,之前有出现过警报声,不过那是无关紧要的外部环境噪音,和当前的开发工作无关。
至于游戏本身的网格化设计,它的确对很多系统产生了影响,包括碰撞检测、实体位置管理、路径规划、渲染精度等。虽然这不是当前要详细讨论的内容,但它作为基础架构的一部分,已经被明确确立,并将持续影响后续的开发流程和功能扩展。
拿了四年 MIS 学位之后你觉得再学 C 还有必要吗?
提到“MIS”的时候,指的是“管理信息系统(Management Information Systems)”。这是一个结合商业管理与信息技术的学科领域,专注于利用信息技术来支持组织的运营、管理和决策过程。
虽然完成了四年的MIS学位课程,但对于“MIS到底是什么”依然感到模糊或难以概括,这是一个常见现象。因为MIS本身是跨学科的,课程内容既包括编程、数据库、系统分析,也包括项目管理、商业流程、战略决策支持等内容,涵盖面非常广泛。
MIS的核心是:
- 信息技术的应用:理解和使用计算机系统、网络、数据库和软件来处理企业数据。
- 业务流程优化:通过技术手段改进企业的效率和效益。
- 数据驱动决策:利用信息系统收集、分析和呈现数据,支持管理层的战略决策。
- 沟通桥梁作用:作为IT部门与业务部门之间的纽带,理解技术语言也理解业务需求。
尽管拥有学历,但由于该领域的实际应用复杂、内容广泛,加之课程设置可能偏理论或缺乏项目经验,导致很多人对其本质仍感困惑。需要在实际工作或深入项目中不断积累经验,才能真正理解MIS在企业中的作用和价值。
技术上来说,如果游戏玩法围绕格子和朝向设计,是不是其实不需要碰撞检测了?
游戏系统中虽然角色身体的移动是基于格子(tile-based)的,但我们仍然保留碰撞检测系统,其原因如下:
- 需要检测抛射物是否命中目标,例如箭矢、子弹、魔法等效果都依赖精确的碰撞判断。
- 游戏中的“头部”或其他部件是可以自由移动的,并不受格子限制,因此这些部分的运动需要连续空间的碰撞检测支持。
- 整个游戏系统的底层并非严格基于格子,而只是角色身体的路径选择或占位行为采用格子化逻辑。
- 即使格子化处理能简化某些逻辑,像是攻击判定、实体重叠、物理推拉等内容依然需要细粒度的空间判定。
因此,碰撞检测系统必须保留,尽管它可能不会再用于控制格子上的移动决策,但仍然是战斗、物理交互、动画响应等多项核心机制的基础。该系统未来的作用会更偏向于实现真实感交互与非格子元素的动态行为处理。
游戏中运行的 18 个线程是干嘛的?
在游戏运行时,线程的使用情况大致如下:
我们默认不会显式设置最大线程数,但可以通过观察进程来了解线程的实际使用状况:
主线程:处理所有游戏逻辑和部分渲染工作,是游戏运行的核心线程。
两个后台加载线程:专门用于资源(如美术素材)的异步加载,避免阻塞主线程,提高流畅度。
软件光栅化线程(若启用):如果启用了软件渲染而不是使用 OpenGL,那么会额外启用多个渲染线程来并行处理图像绘制工作。例如在一台多核机器上,会使用 6 个渲染线程来分担渲染负载。
操作系统附加线程:Windows 系统会附带生成大量线程,包括:
- 音频线程:用于播放声音,即使只使用一个输出通道,也会被系统分配多个声音线程。
- 内核相关线程:可能与操作系统服务、驱动程序(如 AMD 显卡驱动)等相关。
- 其他系统服务线程:无法完全控制,也没有办法避免,这是 Windows 操作系统自身行为的一部分。
在调试工具中查看线程时可以发现:
- 多数线程处于闲置状态,仅在需要时执行工作。
- 若启用软件渲染并解除暂停状态,可以观察到多个渲染线程活跃运行,这是为了提高 CPU 多核利用率。
地面喷溅系统会被什么替代?是靠不同的地块类型和无缝纹理吗?
游戏的实际视觉设计并不希望使用无缝纹理,因此不会简单地用不同类型的无缝纹理来替换原本的地面板系统。经过对整体视觉效果的权衡与实际表现的验证,地面板(ground slats)在当前美术风格下并不合适。
具体来说:
- 游戏视觉风格具有特定方向性,不是追求写实或一致性纹理的无缝效果,而是强调独特性与手工质感。
- 无缝纹理虽然技术上看起来“整洁”,但在艺术表达上容易削弱设计语言,使画面缺乏个性或辨识度。
- 当前地面板的表现方式不符合设定的美术风格需求,因此被排除。
- 地面处理方式将根据具体关卡和环境设定,使用具有风格化特征的材质或贴图组合进行替代,而不是以纯粹技术方式(如无缝拼接)处理。
总体来说,地面视觉呈现将优先考虑艺术风格一致性与视觉设计需求,而不是依赖通用、无缝的纹理解决方案。
线程图的块的跳动不是很正常呢
check 了一遍跟绘图没关系
为什么你不在类 UNIX 系统上编程?
我们主要在 Windows 平台上进行游戏开发,原因非常实际且基于当前游戏行业的现状:
市场占有率决定开发平台:目前 Linux 平台的玩家数量极少,占比不足 1%,绝大多数玩家都在使用 Windows 系统。这意味着如果选择在 Linux 上开发游戏,会导致绝大部分潜在用户得不到良好支持,这对游戏的推广和销量是致命的。
用户体验与兼容性要求:我们必须对玩家实际使用的环境有深入了解,只有在 Windows 上进行开发、测试和调试,才能确保游戏在该平台上表现稳定。如果对 Windows 系统不熟悉,可能会导致游戏中出现严重 bug,进而在玩家社区(如 Steam 论坛)中受到大量差评。
工具链和开发环境不友好:Linux 在游戏开发方面缺乏高质量的工具支持。尽管它在服务器管理、Web 开发等方面有优势,但游戏开发尤其是涉及图形和 GPU 调试的部分,Linux 的支持非常有限。大多数专业的游戏工具和调试器都只支持 Windows。
GPU 调试是核心难点:游戏对 GPU 的依赖极强,渲染性能调试和驱动支持对开发者来说至关重要。而在 Linux 上,这类工具要么不完善,要么不可用,这使得开发效率大打折扣。
与 Windows 深度耦合是无奈的现实:虽然我们并不喜欢必须与 Windows 系统深度绑定的现状,但必须承认目前游戏行业的开发和销售都严重依赖 Windows 生态,这是事实。我们只能尊重并接受现实,除非未来市场格局发生重大变化。
期待未来改变:我们当然希望未来 Linux 能占据更多游戏市场份额,如果哪天有 50% 的玩家在 Linux 上玩游戏,那将非常理想。但以目前来看,还远远没达到那个阶段。
总结来说,出于玩家数量、工具支持、调试需求以及市场现实的多重考虑,我们不得不以 Windows 为主要开发平台。这不是出于偏好,而是基于对效率和成品质量的负责。
为什么多线程处理在这游戏里不太可行?是有啥复杂性或限制吗?
我们使用多线程确实运行良好,多线程系统在当前的开发架构中表现稳定且高效。
我们主要将多线程用于以下几个方面:
资源加载:我们专门开设了两个后台线程用于加载资源,比如艺术资源(图像、纹理等),这些线程独立于主线程运行,在不干扰主游戏逻辑的前提下完成磁盘读取等任务,避免加载卡顿。
软件渲染:当启用软件光栅化渲染器(也就是不使用 OpenGL 的时候),我们会创建多个线程用于渲染工作。通常是 6 个渲染线程,这些线程根据 CPU 核心数量动态调节,用于并行处理画面绘制任务,从而提升渲染效率。这样主线程可以专注于游戏逻辑,而渲染线程则高效完成像素绘制。
主游戏线程:游戏的主逻辑还是在一个主线程中运行,负责处理输入、更新游戏状态、执行游戏行为等。这个主线程在需要时也参与渲染任务。
线程资源管理:我们特意限制了线程总数,保留了足够的系统资源供 OBS(用于直播)等软件使用,确保系统整体运行流畅。
兼容性处理:虽然多线程系统工作良好,但我们也注意到了平台差异带来的不确定性。例如在纹理下载时尝试过多线程,但发现某些机器会出现不稳定甚至崩溃的情况,因此我们选择在最终版本中回退为单线程方式来确保兼容性和稳定性。
Windows 系统线程杂讯:除了我们自己创建的线程,Windows 系统会额外生成一些后台线程,比如声音播放、系统服务、驱动线程(如 AMD 驱动线程)等。这些线程不由我们控制,但我们在设计时已经考虑它们对性能的影响。
总结来说,多线程系统在我们的使用中表现出良好的扩展性和稳定性,尤其在资源加载和渲染方面发挥了重要作用。我们通过合理的线程管理、兼容性测试和平台特性考量,避免了常见的多线程陷阱,确保开发流程顺利进行。
你当年刚开始编程时是怎么在 Windows 上搞定一切的?毕竟 MSDN 现在已经没啥参考价值了
当我们刚开始在 Windows 平台进行开发时,情况比现在要简单得多,主要体现在以下几个方面:
API 更加简洁统一:当时的 Windows 平台主要使用的是 Win32 API。虽然这个 API 并不完美,但至少是一个结构清晰、可控的接口集合,功能集中、文档较全,不至于让人摸不着头脑。相比之下,今天的 Windows API 系统已经变得非常混乱,不同子系统有不同风格、语言绑定、技术框架,彼此之间互不兼容或难以集成。
开发门槛更低:Win32 API 足够小巧,一个开发者可以在合理的时间内掌握它的大部分内容,因此构建完整的应用程序是可实现的目标。现在的 Windows 开发框架例如 UWP(Windows RT)、WinUI、C++/CX、C++/WinRT、.NET、COM、WPF 等等,不仅种类繁多,而且每一种都有自己的配置、限制、语言要求和运行环境要求,导致学习成本急剧上升。
现代 Windows API 复杂混乱:现在很多微软推荐的 API 都是面向特定框架或者运行时设计的(比如 Windows Runtime),要求开发者在配置线程池、事件循环等底层结构时遵循非常复杂的接口设计。而这些接口往往只适用于某些语言(比如 C# 或 C++/CX),并不直接支持 C 或经典的 C++,让习惯于低层系统编程的开发者感到非常困惑与抵触。
文档支持退化:如今的 MSDN(现改名为 Microsoft Learn)已经不再是高质量、系统性的文档平台。许多 API 的文档内容缺失、不准确,甚至直接链接到错误的内容页。搜索体验也大不如前,往往查不到真正需要的底层信息,很多时候还不如直接反编译库或查看旧书籍来的有效。
微软开发方向不再服务于专业开发者:微软现在的开发工具和文档体系越来越倾向于服务“轻量化开发者”(如使用 Unity 的初学者或 .NET Web 开发者),而不再面向传统意义上的系统级程序员或性能敏感的开发者。结果就是现在的 Windows 平台既不清晰也不高效,很多核心机制隐藏在厚重抽象之下,给专业开发带来巨大阻力。
总结来说,过去的 Windows API 虽然原始、繁琐但“可理解”,而现在的 Windows 开发生态变得“混乱且不透明”。我们当初能够顺利上手开发,是因为那个时代的接口更加简洁、结构明确、文档清晰,而这些优势如今已经大幅削弱。对于今天刚入门的人来说,如果不了解这一背景,很容易陷入现代微软生态带来的困惑与误导。
一段关于 Windows API 的“垃圾大爆炸”的评论
过去学习 Windows 编程环境相比现在要简单得多,具体体现在以下几个方面:
首先,Windows 的 API 体系当时相对整洁,不像现在这般杂乱无章。可以形象地说,现在的情况像是“疯人院里墙上到处乱涂乱抹的污秽”,而以前则是清晰且条理分明的代码环境,学习起来更容易上手,开发体验更顺畅。
其次,当时的 MSDN 文档质量较高,内容准确且维护得比较及时。它以光盘形式提供,可以直接离线查看,查找信息方便,内容覆盖面也合理,没有现在这样庞杂、零散且缺乏有效维护的情况。由于 API 数量有限,所以掌握起来并不困难,学习曲线较为平缓。
第三,我们能够学会 Windows 编程,主要是因为当时处于一个合适的时代和环境,API 体系规模适中、文档完善,整体是“可掌控的”,没有被过度复杂化,开发门槛相对较低。
虽然现在依然可以使用旧有的、相对简单的 Windows 编程方式,避免使用微软推出的一些复杂、低效的新框架和 API,但未来可能不会一直如此。微软近年来推出了诸如绑定到 Windows Store、强制使用复杂且不友好的新 API 等措施,似乎在削弱 Windows 作为游戏开发平台的地位,这种趋势让人担忧传统的 Windows 编程方式可能会逐渐消失。
总的来说,曾经的 Windows 编程环境清晰、可学、实用,帮助我们顺利入门和开发,而现在的状况让人感到困惑和无奈,也让人担心未来是否还能像以前那样在 Windows 上自由高效地开发游戏。
你刚才提到多线程处理有点问题?
关于多线程的问题,主要是OpenGL中在不同线程间下载纹理时会出现不稳定的情况。多线程本身是没有问题的,整体多线程工作正常,但OpenGL的上下文(context)不能在多个线程间可靠地共享或操作,这导致在渲染线程以外的线程中下载纹理时会出问题,表现为不稳定或者失败。
这并不是多线程技术本身的问题,而是OpenGL对多线程上下文管理的限制所致。尽管如此,这个问题对整体影响不大,不会严重影响游戏开发或运行。因此,虽然我们避免在不同线程中同时操作OpenGL上下文,但多线程的其他部分依然能正常工作,没有大的阻碍。
你怎么看用 Python 来开发应用?是积极的还是消极的?
关于学习和使用Python来开发应用程序,没有特别的看法,因为并没有使用Python。对于提到的“dynasty and came”没有明确的回应或相关内容。整体来说,对Python的使用和学习没有实际经验和意见。
MSDN 当年是放在两张 CD 上的
MSDN以前是通过很多光盘发放的,曾经会收到一大叠光盘,其中包含了大量的文档资料,还有一些是Windows的调试版本或带有调试功能的版本。具体有多少光盘是纯文档,不太记得了。总体来说,MSDN以光盘形式提供了大量开发资源和文档。
你平时主要用哪些编程语言?
我们平时主要使用的编程语言就是C和C++,没有使用其他语言。