游戏引擎学习第270天:生成可行走的点

发布于:2025-05-11 ⋅ 阅读:(10) ⋅ 点赞:(0)

回顾并为今天的内容定下基调

今天的计划虽然还不完全确定,可能会做一些内存分析,也有可能暂时不做,因为目前并没有特别迫切的需求。最终我们会根据当下的状态随性决定,重点是持续推动项目的进展,无论是 memory 方面还是其他内容。

由于操作系统出现过问题,需要重启机器,因此运行速度稍微有些迟缓,但目前一切已经恢复正常,游戏也在顺利运行。调试界面中,我们可以通过展开查看更多详细信息,也可以保持最小化状态查看概览。

今天的整体节奏比较自由,没有特别硬性的任务安排。我们将按照当下的想法进行推进,灵活调整方向。这是一个开放创作的过程,我们有多种可选路径可以探索,从不同角度推动游戏的设计和开发。

运行当前版本的游戏时,感觉是时候开始整理和优化当前的结构了。经过前面的设计梳理,我们现在已经比较清楚整个系统的构成和各部分的职责关系。是时候回头把一些内容梳理得更清晰,进一步加强模块的可控性与可维护性。

值得一提的是,尽管我们采用的是房间式摄像机设计,并未加入大量平滑滚动机制,这可能不太符合一些特别偏好“流畅滚动”的用户期望,但目前的结构是为了实现我们更清晰、更可控的世界映射与交互而设计的。这是一种有意为之的设计取舍,服务于我们整体的系统规划目标。

设计:基于房间的摄像机移动方式

游戏中不会加入任何形式的“钻洞”机制,至少在设计层面完全排除了这种可能。如果真的想在屏幕之间做平滑滚动,那倒是可以接受,但游戏本身不会采用那种允许玩家处在两个屏幕之间的体验方式。整个游戏的核心理念是以“房间”为单位构建世界,即使玩家处在室外区域,也必须明确地处在某一个房间中,而不是漂浮在两个房间之间。

所以,设计中不存在那种可以自由来回穿梭两个区域的方式。举个例子,如果玩家向上移动,不能像现在这样从一个区域“滑过去”进入另一个区域,必须要等走到屏幕边缘,触发房间切换后,才能进入另一个房间。这是整个游戏世界规则的一部分,是不可协商的原则。

当然,如果其他游戏想要使用平滑滚动机制,这种做法是可以借鉴的,之前实现的滚动系统也可以作为参考。但在这个项目中,不会采用这种方案。

接下来的工作可能会集中在整理并清理世界相关的代码部分。目前游戏运行时的“世界模式”模块中,第一次进入时会做初始化工作。当前的代码结构中已经具备基本的世界生成机制,当检测到没有初始化时就会进行初始化,并完成一系列设定。

我们曾经有过一个“AddWall”的函数,用于在世界中添加墙体元素,之前不太记得这个函数是如何被组织和管理的。现在再次查看,发现这部分功能被归类在“PlayWorld”的流程中,也就是当进入“PlayWorld”状态时,会进行世界的随机生成并开始游戏流程。

从逻辑上看,这些初始化和生成流程已经被合理地抽离出来,整个流程是:进入游戏 → 检测初始化状态 → 执行世界生成 → 开始运行。整体结构清晰,模块职责明确,说明前期的重构工作已经达成了目标,后续可以继续优化和整理这些系统化的部分。

编辑 game_world_mode.cppgame_asset.cppgame.cpp:删除地面区块

首先要做的一件事是,彻底移除“ground chunks”(地面区块)的概念。这个系统将不再被使用,因此需要从代码中完全删除相关内容。这项清理工作的重要性在于,它不仅可以简化整体系统的复杂度,还能为将来的渲染系统优化扫清障碍。

过去为了支持 ground chunks,我们在渲染系统中做出了一些妥协,比如在后台任务中执行渲染,而现在看来这些机制已经不再必要。既然不再计划在后台进行渲染,就可以把这些为支持 ground chunks 而引入的妥协全部移除,进而大幅简化渲染相关的代码。这对于未来的维护和扩展来说是非常有益的。

因此,我们着手移除所有与 ground chunks 有关的逻辑,包括:

  • 删除所有 ground chunk 的数据结构和存储代码;
  • 清理 transient state(瞬态状态)中保存 ground chunk 缓冲区的部分;
  • 去除重新加载 x 文件时重新计算 ground chunk 的逻辑;
  • 从相关的缓冲池、处理队列中移除它们的使用痕迹;
  • 删除相关变量如 groundBufferCount 及其更新逻辑。

不过,在这个过程中,需要注意保留低优先级任务队列(low priority queue),因为这部分不仅用于 ground chunks,也用于其他资源的加载功能。虽然现在不再使用 ground chunks,但加载系统仍然需要这些队列,所以不能一并删除。

完成这些清理之后,程序可以正常运行,不再依赖 ground chunks,系统结构更加干净。接下来一个可以进行的小任务是修复贴图相关的一些问题,作为一个轻松的改动,也可能会比较有趣。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

运行游戏,发现画面没有变化

现在我们重新运行程序后,虽然视觉上看起来一切都和之前一样,但实际上所有与 ground chunk(地面区块)相关的系统已经被彻底移除,这是一个非常好的进展。

接下来我们开始整理整个 world wrap(世界封装)相关的逻辑,目的是将当前的框架结构进一步收紧和优化,为后续正式的游戏代码实现做好准备。当前的结构虽然基本可用,但还不够清晰和整洁,适合进入实战阶段的开发。

这个整理工作不仅包括功能层面的调整,也涉及到代码结构的重新梳理,比如明确模块之间的职责划分、精简不再需要的接口,以及对可能存在的冗余或过度抽象进行精简。此外,还会考虑未来游戏设计需求所需的扩展能力,确保这个世界系统可以稳定支撑整个游戏流程。

与此同时,加载了 mischief(调试与设计相关的可视化工具),这个加载过程稍显缓慢,是因为当前环境中包含了大量的数据、元素或状态配置。这些内容是之前调试或原型过程中积累下来的,也正说明目前系统已经积累了丰富的状态,因此这次整理工作也变得尤为关键。

总体来看,现在的目标非常明确:在彻底移除旧机制后,进一步清理整理整个世界系统逻辑,为即将进入的游戏功能实现阶段打好基础。

黑板讲解:基于图块(tile)或网格单元(cell)的移动与组合逻辑

我们现在的目标是正式建立“房间”这一概念。在设计上,每个房间都将由明确的网格单元构成,也就是说,整个世界会被划分为清晰可见的网格格子,每个格子将成为基本单位。这种设计是有意为之的,后续游戏玩法将高度依赖于这个网格结构。

我们不会把“房间”概念固定到某个特定的数据结构里,也不强制统一每个房间的尺寸。由于摄像机支持缩放,我们可以灵活设置显示范围。不过,我们会定义一个“网格大小”(例如一个格子为 32 像素),并确保游戏中所有元素都在这个基础上运行。这种网格系统会成为环境的首要结构,所有内容都需要严格对齐到这些格子上。

接下来我们会创建实际的网格实体,也就是说,每一个格子都会成为一个实体,保存关于该格子的信息。虽然这可能会带来效率问题(比如同屏会有几百个实体),但我们决定接受这种低效。因为我们想最大限度地实现游戏系统的组合性和交互性,希望让游戏中所有的事物都能模拟、响应和互动,哪怕是地面本身也会作为一个实体参与模拟。

我们明确地做出这个设计选择,并愿意为此承担代价,比如更高的内存占用、更大的运行负担、可能无法在性能较低的平台上运行等问题。我们接受这个不完美,是为了实现更加丰富而复杂的游戏交互系统。

因此,我们将开始实施这一变化。虽然乍看之下并不是特别大的变动,其实这与我们最初开发贴图系统时的方式类似,只是现在会以实体的形式来实现这些网格。未来我们甚至可能会探索不规则网格的可能性,但当前的重点是确保我们可以以明确的方式创建和管理这些网格。

我们首先会从 world mode 模块入手,看之前用于创建墙体的 add_wall 函数。这个函数目前是以“屏幕”为单位创建房间,并通过参数定义房间的宽高(比如 17x9)。我们会把这个逻辑扩展为逐个格子创建实体,每个格子都清楚地表明自己的位置,并能承载各种交互信息。

此前我们还曾尝试用一些“空间实体”等方式来模拟玩家的移动,但现在我们已经确定移动系统的设计方向:玩家只能处于某一个具体的格子内,不能停留在两个格子之间。也就是说,玩家的每一步移动都必须是从一个格子到另一个格子,不能有“夹在中间”的状态。

这将促使我们摒弃之前那种基于向量自由移动的方式,转而使用更精确的格子移动系统。这也是接下来我们要重点构建和实现的部分。整体来看,我们现在进入了一个更成熟的阶段,从试验过渡到实际的系统构建。


示例:构建一个 5×3 的房间网格

我们希望构建一个房间,它是一个 5 格宽、3 格高的矩形,每个格子是一个实体。这些实体可以承载地形信息,比如是否可以通行、是否有物体等。

第一步:定义网格大小

我们定义每个格子为 32×32 像素:

const int tile_size = 32;
第二步:创建网格实体

我们创建一个循环,为每一个格子生成一个“网格实体”:

for (int y = 0; y < 3; ++y) {
    for (int x = 0; x < 5; ++x) {
        EntityID tile = CreateEntity();

        SetComponent(tile, Position, {
            .x = x * tile_size,
            .y = y * tile_size
        });

        SetComponent(tile, TileInfo, {
            .walkable = true,
            .type = TILE_FLOOR
        });
    }
}

上面代码中,每个格子都是一个实体,拥有两个组件:

  • Position:表明其在世界中的像素位置;
  • TileInfo:包含逻辑信息,比如是否可以通行、当前地形类型。

移动示例

假设玩家现在在 (2,1) 这个格子中,想要向右移动一格。系统逻辑会是这样的:

int target_x = current_x + 1;
int target_y = current_y;

// 查找对应的目标格子实体
EntityID target_tile = GetTileAt(target_x, target_y);

if (GetComponent(target_tile, TileInfo).walkable) {
    MovePlayerTo(target_tile);
}

玩家只能从一个格子“跳跃”到另一个格子,而不会处于两个格子之间。动画上可以通过摄像机平滑处理,但逻辑上始终是离散的网格跳跃。


模拟效果

  • 每一个网格都可以有事件、地形、道具;
  • 比如 (4,2) 的格子可以是陷阱,只要角色踏入就触发效果;
  • 后续也可以给每个格子绑定触发器、环境变化等,使整个世界成为一个互动的模拟系统。

这个做法虽然增加了实体数量(5×3 就是 15 个实体),但也带来了极大的扩展性,比如我们可以对每一块地面进行独立处理,让游戏玩法更加复杂且有趣。

编辑 game_sim_region.hgame_world_mode.cpp:将 EntityType_Space 改为 EntityType_Floor,将世界构建方式从“挖空”转变为“堆叠”

我们当前在重新整理游戏世界的底层表示方式,具体目标是构建一个以“格子(Grid)”为基础的、离散化的空间逻辑系统。我们不再采用以前用“space”实体来代表可移动区域的方式,因为那种抽象已经不再符合我们的设计思路。以下是我们所做调整和设计意图的详细总结:


一、实体与地面概念重构

  • 以往我们使用一个叫做 space 的实体类型,表示可通行区域;
  • 现在我们将这种类型更名为 floor,它更准确地描述了“玩家可以站立的地方”;
  • 未来它可能会被替代为一个带有 Traversable(可穿越)标志的普通实体类型,而不是单独的实体类别;
  • floor 是我们世界中的基础组成单元,每一个格子都会对应一个 floor 实体。

二、房间生成逻辑调整

  • 我们将通过嵌套循环来生成一个房间中的每一个格子;
  • 这些格子都是以 (x, y) 坐标为中心的位置进行偏移的,构建时会考虑居中布局;
  • 比如对于 17 × 9 的房间,会以 x = -8 到 8y = -4 到 4 的范围来遍历生成实体。
for (int y = -4; y <= 4; ++y) {
    for (int x = -8; x <= 8; ++x) {
        EntityID tile = CreateEntity();
        SetPosition(tile, (x * tile_size), (y * tile_size));
        SetFlag(tile, Traversable); // 设置为可站立
    }
}
  • 每一个实体都代表一个具体的地块,具备明确的物理位置与逻辑功能。

三、碰撞系统精简

  • 之前我们使用了 standard_room_collision,现在改为更通用的 standard_floor_collision
  • 实际上,我们不再需要对 floor 类型的格子进行实体间碰撞处理,因为它们是用于“站立”而非“阻挡”的;
  • 因此,这些实体不会设置碰撞盒,仅用于逻辑判断和渲染。

四、渲染处理

  • 为了验证生成的格子是否正确,我们为每个 floor 实体绘制一个描边矩形,用于视觉调试;
  • 使用之前已有的 PushRectOutline 方法来显示每一个 tile 的边框;
  • 这个操作只用于开发期的可视化调试,后续可能会被真实地形资源所替换。

五、实体数量控制

  • 由于我们现在每一个格子都生成一个实体,这会大幅增加实体总数;
  • 暂时我们减少了初始化时的房间数量(screen count),只保留一个初始房间,避免超出实体上限;
  • 未来我们会将实体总数限制改为动态分配,而非硬编码上限。

六、删除旧系统

  • 旧的 ground chunk 系统已被完全移除;
  • 地面格子现在由 floor 实体逐个构建生成;
  • 这样可以让地面也成为游戏逻辑中的一部分,实现高度可组合的互动设计。

七、设计意图总结

  • 我们选择牺牲一定的性能与内存占用,换取极高的可扩展性与逻辑一致性;
  • 每一个可交互单位(无论是角色、物品还是地面)都被视为实体,均可被统一管理;
  • 这种“全实体”的方式能够最大化游戏系统的组合潜力和未来扩展能力。

这种架构将使我们的游戏逻辑更清晰、行为更一致,并且为实现复杂地形、动态地图和更多可交互元素打下了坚实的基础。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

运行游戏,发现可以选中地板图块

我们现在已经成功创建了所有网格格子实体,并能够在系统中选中它们。系统正常地将这些格子可视化显示出来,一切运行良好。这意味着我们已经有效地将每个网格格子初始化,并且能够通过当前的调试或显示机制清楚地看到它们。

此外,我们可以利用已有的系统工具来显示这些网格格子的位置,进一步验证它们的生成是否准确。这一过程顺利完成,视觉上也确认了格子的创建确实按预期发生。这种可视化方式对后续调试与开发非常有帮助,使我们可以直观观察整个网格布局的实际分布效果。

总的来说,这一步已经完成得非常顺利,基础的格子实体系统已经建立,并可以通过图形方式清晰地呈现,具备了进一步开发与逻辑实现的基础。

在这里插入图片描述

编辑 game_world_mode.cpp:绘制图块

我们现在希望将这些地板格子更“永久性”地绘制出来,而不是仅在某些调试或特定模式下才可见。目前虽然已经成功生成并可以通过系统可视化地看到这些格子,但它们的绘制仍是临时性的,属于调试辅助性质,比如通过碰撞框(collision rectangles)来表示。

现在的目标是将这些格子始终绘制出来,使它们在游戏运行时也能一直可见。虽然还未使用正式的美术资源,但接下来的计划是为这些地板格子配上真实的图像资源,让它们具备真正的视觉表现力。在此之前,先继续用碰撞框的方式进行占位显示,作为过渡阶段。

这也意味着我们正在逐步从仅依靠调试工具的开发阶段,迈向真正的游戏视觉和系统集成阶段,为日后完整的地面渲染和交互铺设好基础。这个过程确认了格子的可见性、存在性与绘制逻辑的一致性,同时也准备好了后续替换美术资源的接口。
在这里插入图片描述

在这里插入图片描述

运行游戏,看到新的地板图块,开始思考如何使用它们

目前我们已经能够清晰地看到所有地板格子了,这比之前有了明显的改进,但还没有完全实现我们真正想要的功能。接下来的目标是让角色能够基于这些格子进行明确的位置判断和移动——也就是说,角色需要知道自己正处于哪一个格子上,或者是否已经离开了某个格子。

这就要求我们重新思考这些地板格子的用途和逻辑。以往我们使用“可通行(traversable)”这个概念,是基于空白空间的,那些可以在连续移动中穿过的区域。而现在的思路不同了:我们希望这些地板格子是角色明确“站在其上”的单位,是一个个具象的、离散的位置,而不是连续空间中的一部分。

基于这个新思路,我们需要对角色与这些格子的关系进行重新定义。例如,角色应当在逻辑上明确知道自己当前在哪个格子,或者是否从一个格子跳到了另一个格子。这不仅影响角色移动的判断逻辑,也影响后续的交互系统、AI导航系统、甚至动画和视觉表现。

此外,还要引入“高度”概念。也就是说,每个地板格子将具有一个“高度”值,代表其在空间中的立体位置。接下来的重点是思考这个高度值的意义:是否允许角色在格子之间因为高度差异而无法移动?例如,某个格子如果高出相邻格子太多,角色是否就不能直接跳上去?这些问题都需要在整个逻辑流程中逐步明确。

因此,整个系统的重点正在从简单的二维平面空间转向一个具有高度和逻辑判断的结构化格子系统。后续将逐步把这种格子的逻辑贯穿整个角色移动、交互和渲染管线,实现完整的空间感知与行为系统。

编辑 game_world_mode.cpp:根据图块类型有条件地设置 Traversable(可通行)标志

当前碰撞系统的逻辑是:每个物体都引用一个“碰撞体积”(collision volume),碰撞体积中包含了实体的物理范围信息。在处理角色移动时,系统会通过检测角色当前或即将移动到的位置是否存在碰撞体积,并依据这些体积来判断是否可以移动。如果目标区域的碰撞体积被标记为可以通行,那么角色就可以自由移动过去。当前的角色移动方式正是基于这种机制完成的。

但是,现在我们正在考虑一种新的处理方式:不再默认所有格子都可以通行,而是根据具体位置决定哪些格子具有通行属性。例如,在构建房间时,原本是将整个房间区域内所有地板格子统一标记为可以行走,但现在我们可以加入一些逻辑判断,比如:

  • 如果某个格子的 offset_x 小于 -3 或大于某个值,那么就不给它添加“可通行”的标志;
  • 反之,如果满足特定条件,则为其设置“可通行”标志。

这样一来,就可以实现空间上的限制,构建出不可通行区域。角色在尝试移动到这些区域时,碰撞系统会检测到该区域没有设置为“可通行”,于是阻止角色移动过去。这种方式可以更精细地控制角色的活动范围,形成更复杂的场景结构,比如墙体、悬崖、障碍物等。

这种设计使得整个世界的构建更加灵活,并允许我们引入更多空间结构,比如平台跳跃、梯度地形、多层地图等。这也为未来实现高度差逻辑(例如角色不能直接跳上比自己高太多的格子)打下基础。后续将进一步完善这种“格子可达性”的判定机制,使其不仅能表达通行与否,还能支持更复杂的移动条件和互动行为。
在这里插入图片描述

在这里插入图片描述

运行游戏,观察可通行标志的效果

当前的移动和碰撞逻辑存在一些需要重新审视和改进的地方。

目前,在没有物理障碍物阻挡的情况下,角色依然无法继续向某些方向移动。表现为:角色在视觉上似乎可以前进,但实际上被“卡住”了,无法穿越某些区域。这种现象是由于地面格子没有设置为“可通行”导致的。虽然表面上没有障碍,但在逻辑层面,系统没有将那些格子标记为可达,角色因此无法移动过去。

进一步分析发现,当前的格子系统没有良好的选择工具来处理“多个实体重叠”的情况,导致难以直观判断某个区域的实际状态。尤其在存在多个实体叠加时,交互和调试都变得困难。对于这些问题,目前还没有很好地解决办法。

角色的移动逻辑当前是:检测角色前方区域是否包含“可通行”的碰撞体积,如果有,就允许继续移动。但是这种设计是假定空间是“连续”的,也就是角色是穿越一个开放的区域进行移动。然而,这样的连续空间移动方式在当前设计下已经不再合适。

接下来的目标是:改变思路,不再依赖于“空白区域是否可以通行”来决定移动路径,而是将地面格子视为“可落脚点”。也就是说:

  • 地面格子是一个个明确的“可停留点”;
  • 每次移动是从一个“落点”移动到另一个;
  • 判断是否可以移动,不再是检查空间是否空,而是看目标方向上是否存在另一个有效落点。

这种设计类似“跳格子”或“点对点导航”的逻辑,角色不会穿越空白区域,而是一步步“跳”到相邻的落点上。这种方式也更容易支持高度差(Elevation)、平台、障碍、路径限制等复杂场景。

但要实现这一目标,必须对现有的移动系统进行重构:

  • 改变当前依赖空白空间检测的移动判断逻辑;
  • 为每个地面格子提供明确定义的“位置点”,并定义其与其他格子的邻接关系;
  • 修改角色的移动控制,使其只能在这些“位置点”之间移动;
  • 保留原有的碰撞逻辑用于处理与其他实体或障碍物的交互。

最终,这种新的设计将使角色移动更具结构化,也更有助于构建复杂的地图与机制,比如跳跃、单向通道、平台升降等功能。而在当前阶段,为了逐步实现这一目标,将开始在已有逻辑中添加必要的判断和调整,逐步推进系统的重构过程。

编辑 game_sim_region.h:引入 sim_entity_traversable_point

当前的设计思路是将“可通行点”(traversable points)作为每个实体的一部分,进一步扩展现有的碰撞体积(collision volume)逻辑。碰撞体积组通常包含一系列的规则,用于描述物体如何与环境发生碰撞,而“可通行点”则是另一个概念,专门用来标记哪些位置是角色可以站立、移动到的区域。

思路是,除了碰撞体积外,每个实体可以有一组“可通行点”,这些点可以是具体的位置,角色如果想要移动到这些位置,需要确认目标位置是否是“可通行”的。也就是说,如果某个位置被标记为“可通行点”,角色就可以移动到该位置;如果不是,则无法移动过去。

这种设计的灵感来源于将每个实体的可移动性与具体的点关联起来,而不是仅仅依赖于整个区域的“空旷”或“通行”的状态。这意味着每个实体可以包含多个“可通行点”,而这些点是具体的、可以站立的地方,角色可以在这些点之间移动。例如,大多数情况下,这些“可通行点”会落在一个网格上,但系统也应该具备灵活性,以便在以后需要时可以支持非网格化的移动,允许角色在更多样化的环境中移动。

这一设计的优点是更加结构化和灵活,能够应对未来可能需要的复杂环境,比如平台、阶梯、斜坡等地形类型。每个实体可以在其内部定义多个“可通行点”,角色可以在这些点之间移动。为了实现这一目标,首先需要将“可通行点”与每个实体的碰撞体积组合起来,并确保每个位置的移动逻辑能正确识别这些点。

总之,目标是通过这种方式让角色的移动更加明确,避免之前那种基于“空白区域”通行的模糊性,转而使角色的移动依赖于具体的、定义明确的“可通行点”。这种方法也为将来扩展和创建更复杂的场景提供了灵活性。
在这里插入图片描述

在这里插入图片描述

黑板讲解:蛇形路径构造

在当前的设计中,为了让角色能够在不同的区域内进行移动,我们需要引入一个新的概念:可通行点(traversable points)。这些点并不仅仅是“可以穿过的区域”,而是明确的角色可以站立并移动到的地方。也就是说,这些可通行点是与实体关联的,每个实体都可以有多个这样的点,角色可以在这些点之间进行移动。

为了实现这一点,必须对现有的碰撞逻辑做一些调整。当前的碰撞处理方式是基于“碰撞体积”,即通过检查是否能穿越某个区域来决定角色是否能移动。然而,新的想法是,不仅要检测碰撞体积,还要增加“可通行点”的概念,只有在这些点存在的情况下,角色才能继续移动。

举个例子,如果我们想让角色沿着一条“蛇形路径”移动,这时就需要根据路径的具体形状来定义多个可通行点,而不是简单地依赖于传统的空旷区域。这种方式能让角色的移动更加灵活和有趣,允许在复杂的路径上进行更精细的控制。

为了实现这个目标,首先需要在每个实体中定义一个“可通行点”的集合。这些点代表了角色可以站立和移动的地方,而碰撞体积则用于标记那些不可穿越的区域。在代码实现上,现有的碰撞逻辑会被调整,以便处理这些新的可通行点。

在进行碰撞处理时,当前的实现方式较为原始,并且存在很多可以改进的地方。然而,由于这是一个原型阶段,现阶段不会对这些代码进行过多的修改,暂时会保持现有的状态。当前的重点是确保引入“可通行点”的概念,并在角色移动时根据这些点来判断是否能够继续前进。

简而言之,通过将“可通行点”引入到每个实体中,可以让角色在更复杂的地形和路径上进行移动,而不是仅仅依赖于空旷区域的判断。这种方法为未来的扩展和更复杂的场景提供了更多可能性,尤其是在角色路径规划和移动的精度方面。

考虑将实体的位置进行规范化

为了实现新的功能,需要对现有的代码进行一些调整,特别是与碰撞体积和可通行单元相关的部分。主要目标是让代码更具语义性,并去除与“可通行单元”相关的处理。这意味着在移动逻辑中,将不再考虑传统意义上的“可通行单元”,而是要专注于具体的站立点,这些站立点是角色可以在其上移动并停留的位置。

具体来说,当前的碰撞逻辑依赖于是否可以穿越某个区域,判断是否可以进入某个单元格。然而,在新设计中,角色的移动是基于固定的“可通行点”,并且当角色决定从一个点移动到另一个点时,他们必须明确经过那个目标点。这些点是“已知”的、固定的,并且与地形的其他部分没有直接的关系。因此,角色的每一步移动都严格遵循规则,不允许通过空白空间或无支持的区域。

这也意味着不再需要传统的重力或自由下落的概念。角色的垂直位置(Z轴坐标)将始终和所站立的点一致,所以在游戏规则中不再有“自由下落”或“漂浮”的情况。实际上,可以删除与重力和支持实体的相关代码,因为这些概念已经不再适用。

总结来说,新的设计将移动逻辑转变为基于可通行点的明确路径,而不是传统的穿越空旷区域。这种方法不仅简化了物理引擎的复杂性,也让游戏规则变得更加明确和可控,角色的每一步都会是一个明确的行动,确保了游戏的规则性和一致性。

编辑 game_sim_region.cpp.h:移除重力(Gravity)、Z轴支撑(ZSupported)和移动实体(MoveEntity)中的 Traversable 相关代码

为了简化碰撞处理和角色移动逻辑,现有的重力和支持性相关的概念将被去除。之前涉及的“重力”和“是否受支持”的标志将不再需要,因为角色的移动将不再受到这些因素的影响。角色的每次移动都是基于固定的“可通行点”,并且只有在没有碰到障碍物的情况下才能移动到这些点上。这个过程不再涉及到重力或“自由下落”的情况。

具体来说,原本用于判断是否可以通过空旷空间或移动的“可通行”标志也将被移除。现在,任何可以移动的空间,都可以通过计算碰撞体积与其他实体的碰撞来处理,只要没有碰到障碍物,角色就可以前往该点。

在碰撞处理代码中,之前为了计算角色能否继续移动到一个区域,我们会使用一个“t_max”值来确定移动的最大距离。现在,所有的移动将假设最大距离是1,这样就简化了计算过程,避免了之前那种需要判断移动到哪个空白区域的复杂性。

所以,碰撞处理代码将变得更加简单,只需要关注“是否能发生碰撞”这一点。当两个实体发生碰撞时,代码只需要检测是否有障碍物阻挡角色的移动,而不再需要考虑“可通行区域”或“空白空间”的问题。

总的来说,这些改动的核心目标是让角色的移动逻辑更加简洁和明确,去除不必要的复杂性和冗余的计算。通过这种方式,角色的每次移动都是确定的,只要没有碰到障碍物,角色就可以前往指定的目标位置。

在这里插入图片描述

在这里插入图片描述

编辑 game_world_mode.cpp:引入 MakeSimpleFloorCollision(构建简单地板碰撞)

目前需要移除实体的“可通行”标志,因为新的设计将不再依赖该标志。现在,我们将通过修改碰撞检测系统来实现角色在特定的“可站立点”之间移动。为此,首先需要在创建“地面碰撞”或“碰撞区域”时,将“可站立点”添加到这些区域内。

要实现这一点,首先需要调整现有的“简单地面碰撞”函数,使其更加具体,加入“可站立点”的概念。这意味着在创建这些碰撞区域时,需要特别标记出“可站立”的位置。为此,我们可以扩展现有的功能,创建一个新的函数,命名为“make_simple_traversable”或类似的名称,以便在处理地面碰撞时也考虑到这些可站立点。

接下来,在处理这些区域时,需要关注“可通行区域”中的位置。每个“可站立点”都会有一个相应的位置,并且这些点将影响角色的移动。具体来说,当创建一个“可站立区域”时,其总量将包括这些可站立点,而该点的位置可能会根据需要进行调整,比如稍微向下偏移。

目前,我们不需要对这些区域进行碰撞体积处理,因为这部分逻辑暂时不需要处理。虽然以后可能会希望为这些区域添加碰撞体积(例如某些区域可能高于其他区域),但目前可以暂时不考虑。

在绘制这些区域时,也需要显示出这些“可站立点”。为了实现这一点,可以在绘制“碰撞体积”的时候,添加一个新的绘制函数,用于显示这些“可站立点”。这样,我们可以直观地看到每个区域中可站立的具体位置。为此,绘制函数需要接受这些点的位置,并将其显示在屏幕上,帮助我们理解和调试这一过程。

总的来说,当前的目标是将“可站立点”的概念整合到碰撞处理系统中,确保角色能够在这些特定的点之间移动,而不再依赖原来复杂的“可通行”逻辑。这将简化移动系统,明确每个角色移动的规则,并且为未来可能的扩展(例如非网格化的可站立点)做好准备。
在这里插入图片描述

在这里插入图片描述

运行游戏,角色持续向上移动,开始调查原因

目前的问题是在角色的垂直方向(Z轴)上不断上升,而其他方向的运动是正常的。问题可能出在“地面点”的计算上。首先,需要确认如何计算角色的“地面点”。在代码中,可以看到涉及到“玩家delta”和“地面测试”的部分,但目前不清楚具体是如何计算Z轴位移的。

一开始,可能存在一个“处理重叠”的概念,但目前我们并没有使用这一部分,因此决定先移除它,以简化问题的排查。接着,怀疑是某种“偏移”导致了角色垂直方向的运动。通过对代码进行调试,可以看到,在角色的移动过程中,Z轴的位移值不应该发生变化。

进一步检查时,发现“玩家delta”的Z分量可能正在无意间被修改,导致角色发生了上升运动。因此,决定暂时将“玩家delta”的Z分量设置为零,观察是否能够消除上升的运动。通过这种方式,发现问题的确是出在Z轴的加速度上,但目前并没有明确的理由表明为何会出现这种现象。

继续追踪代码,尤其是加速度的部分,检查是否有意或无意地将加速度设置为正值,导致角色沿Z轴上升。通过进一步调试,确认Z轴的速度和加速度没有被正确设置为零,导致角色仍然出现了向上的运动。

总之,问题的根本原因在于Z轴的加速度未被正确处理,造成了角色不应有的垂直运动。需要仔细审查加速度的设置过程,确保Z轴的运动被正确抑制,以避免出现不希望的上升运动。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

编辑 game_world_mode.cpp:取消空格键触发跳跃

问题的根源在于按下空格键启动游戏时,空格键触发了跳跃代码,从而导致了不希望出现的垂直运动。空格键的功能不仅仅是开始游戏,还意外触发了角色的跳跃操作,导致了垂直方向的加速度。这就是我们看到角色不断上升的原因。

目前的问题可以归结为一个初始的跳跃动作,即按下空格键后,角色会进行一次跳跃,产生了垂直的速度。这段跳跃代码在没有被正确处理时,会在启动时自动触发。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

运行游戏,角色保持在地板上

空格键的按下被错误地解释为跳跃动作,这导致了游戏启动时角色自动触发了跳跃代码,从而产生了不必要的垂直运动。这种情况看起来有些荒谬,但至少现在已经找到了原因。

问答环节

那么这种基于图块的移动方式是怎样的?角色是否仍能在图块内自由移动,还是会“对齐”到图块?

关于基于“瓦片”的移动方式,问题是角色是否仍然能够自由移动,或者是否会强制贴合到某个方块上。最终的答案是,这两种方式都不是实际的实现方式。

我们什么时候会解决坐标系统的问题?

计划是首先清理路径算法部分,然后整理存储系统,假设坐标系统也会随着整理一并得到完善和整理。

那楼梯井呢?以前楼梯的移动是无缝的,现在房间是锁定的,楼梯该怎么处理?

楼梯的使用设计是,在楼梯之间移动时,玩家仍然能够穿越它们,但不能在上下楼梯时改变方向。这种设计是刻意的,因为大多数情况下,一旦下楼后就无法再上去。因此,玩家在使用楼梯时需要做出选择并坚持这一选择。

有没有打算换成 Visual Studio Code,而不是完整的 IDE?我记得现在完整的调试器已经包括在里面了

关于是否将代码视频化,首先不太清楚为什么需要这样做。其次,提到Linux版本时,有人表示它并没有展示所有功能,比如汇编部分等。所以不确定是否有更好的Windows版本需要验证。如果没有明显的优越性,那么就没有必要做出这样的改动。

将每个图块都设为一个实体,有什么优点或权衡?

将每个地砖(tile)视为一个实体的原因在于,希望每个地砖能够像其他任何实体一样,参与所有的交互和变换。例如,地砖也应该能受到魔法、重力、变形等效果的影响。通过这种方式,无论是地面上的魔法浮空、改变重量或是其他效果,地砖都能按照相同的规则反应,从而让游戏更具互动性和趣味性。

此外,使用每个地砖作为一个实体的好处是可以实现更灵活的关卡设计,避免了传统网格限制,可以创建非规则的关卡布局。而如果强制使用网格,就无法实现这种灵活性。因此,选择不强制使用网格,而是将每个地砖当作一个独立的实体,可以更充分地利用现代计算机的处理能力,尤其是在2D游戏中,充分发挥处理器的计算能力去做有趣的模拟和交互,而不是仅仅进行3D渲染等资源密集型操作。

然而,这样做的代价是,虽然可以让游戏设计更加自由和丰富,但也放弃了使用网格带来的优化。例如,网格化可以帮助我们压缩存储,减少内存带宽和计算时间的浪费,使得游戏运行更加高效。但决定不使用网格,而将每个地砖当作独立实体,意味着牺牲了一定的组织性和存储效率。

你是把实体分配在堆上还是栈上?是堆上吧?那你能在栈上分配这么多东西吗?

关于是否将实体分配在堆栈上或堆上,首先要澄清的是堆和栈的区别。栈是为函数调用分配的内存区域,而堆是用于动态内存分配的区域。在这种情况下,实体并没有分配在函数调用的栈上,因为它们的生命周期与函数调用栈的生命周期不匹配。

实际上,实体并没有真正分配在堆上。堆是虚拟内存系统中的一部分,通常使用虚拟内存分配(如malloc)来动态分配内存。但在这里,实际上没有使用传统意义上的堆,因为我们并没有调用堆分配函数(如malloc)。

我们使用的是一个自定义的内存栈,它并不是与函数调用相关的栈,而是一个我们自己在内存中管理的栈。这意味着,实体分配的是一个内存块,类似于栈的结构,并通过虚拟内存系统进行管理。所以,实体实际上是从我们手动分配的内存块中按需分配的,而不是传统的堆或栈。

你觉得这个新的网格地板系统在处理不同高度的可通行实体时,会不会和纹理贴图(texture splats)发生冲突?

关于是否会因引入新的非网格实体系统和纹理切片而引发问题,特别是在希望实体具有可变高度的情况下:

不会存在这类问题。之前的纹理切片渲染方式已经被废除,因此不会再使用那种渲染方法。这也是为何我们删除了旧的“地块”(ground trunk)系统。我们曾使用所谓的“ground chunks”(大块区域)来组织地形和纹理映射,但由于该方式限制了可变高度和非网格结构的表达能力,现在已经放弃了。

新的系统将不再依赖固定的网格和切片纹理,而是采用更灵活的渲染与数据管理方式,使得每个实体都能拥有不同的尺寸、形状甚至动态表现,进而实现更丰富的表现力与更自由的空间组织。因此,设计上的这些调整正是为了避免这类问题,而不是制造新问题。

既然现代 CPU 经常会重排序或合并写操作,那它们是如何使用内存映射寄存器的?还能用来 bit-bang 串行协议吗?

在现代CPU中,通常会对写操作进行重排序或合并处理。然而,在面对内存映射寄存器(memory-mapped registers)时,情况变得更复杂和特殊。

CPU的内存管理单元(MMU)会识别不同类型的内存页面,例如某些页面会被标记为“写合并”(write-combining),而另一些则不会。这意味着CPU在访问这些内存时的行为取决于这些页面的具体属性,尤其是在页面被映射用于硬件I/O而不是普通程序数据时。

在与硬件通信的场景中(比如实现串行协议或设备驱动),我们必须非常清楚当前操作的是哪类内存,并且明确CPU是否可以自由优化访问顺序。为了保证访问顺序正确,CPU提供了内存屏障(memory fence)和指令序列屏障(instruction barriers)等机制,可以明确指示:某些内存操作不得被重排序到某些屏障指令之前或之后。这些屏障命令通常会告诉CPU不要在重排序缓冲区中跨越某个屏障对读写操作进行优化。

因此,在实现依赖严格顺序的通信协议或硬件控制时,需要开发者非常了解其所运行的CPU架构和内存子系统,合理使用内存屏障和同步机制来防止无意的指令重排序。

在早期的计算机架构中,情况要简单得多,因为当时的CPU没有缓存(cache)、也没有乱序执行(out-of-order execution),所以内存访问顺序天然是严格遵循程序顺序的。但在现代体系结构中,必须显式处理这些细节,以确保正确的系统行为。

如果主角走到比自己高的地形后面,渲染时该怎么处理被遮挡的问题?

目前对于渲染方面的计划还不确定,特别是当主角走到一个比他高的地形后面,被该地形遮挡的情况。目前还没有明确的处理方式,也不确定是否会经常遇到这种场景。对这种遮挡关系要如何呈现,暂时没有具体方案,也不太确定最终会采用哪种渲染方式。这部分渲染的逻辑可能需要根据后续实际需求和效果再做决定。总之,目前对于这类遮挡问题的渲染策略尚未定案。

这个系统是否能解决多个实体堆叠时碰撞区域卡住的问题,例如两个主角加载在同一位置?

目前确实存在多个角色在同一位置加载时可能会发生卡住或重叠的问题,例如两个角色被加载在同一个位置时可能会互相堆叠。但目前并没有着手去改进碰撞检测器的部分,当前的重点是让游戏系统按最终设想的方式正常运行。也就是说,现在并不是在尝试提高碰撞系统的质量,而是优先调整游戏结构和流程,使其朝向最终设计目标发展。等到系统框架稳定之后,才会对碰撞检测等具体细节进行进一步完善和优化。最终会确保这些问题被正确处理,但目前这部分还不是重点。


网站公告

今日签到

点亮在社区的每一天
去签到