游戏引擎学习第271天:生成可行走的点
回顾并为今天的内容设定背景
我们昨天开始编写一些游戏逻辑相关的内容,虽然这部分不是最喜欢的领域,更偏好底层引擎开发,但如果要独立完成一款游戏,游戏逻辑也必须亲自处理。所以我们继续完善这部分内容。事实上,接下来的所有开发工作都将越来越依赖于游戏本身的实现细节,所以推动游戏向前发展也能促进引擎功能的完善。
目前,引擎层面的基础功能大致都已具备,接下来的工作将更多以实际游戏场景为驱动进行优化和调整。比如,看看性能瓶颈、查看功能是否符合实际需求,并据此改进引擎组件。
昨天讨论并着手实现的一个核心想法,是让角色的移动系统建立在明确的位置点(traversable points)之上。这些点代表角色可以“站立”的具体位置。和传统的瓦片地图游戏不同,这些点并不一定直接对应于图像的像素或瓦片格子,它们更多是逻辑概念。不过在美术上我们也需要通过图形强化这些位置的可视感,让玩家明确知道哪些地方可以站立。
从技术层面上讲,我们希望角色只能停在这些特定的点上,而不能停在两个点之间。换句话说,角色的运动是在这些点之间进行切换的,停下来的时候必须是在某个明确的点上。虽然在两个点之间移动时会经过中间位置,但不能在中间随意停下。
目前的实现仍然是传统的方式,角色只是以某种方式在屏幕上移动一个精灵。因此现在的任务就是改进这一移动机制,真正地让角色运动与这些可行走点结合。
同时,也将逐步构建小型世界环境,作为后续的移动测试和图形渲染基础。当前我们已经在世界中绘制了这些可行走点,这些点是附着在实体(entities)上的。每一个实体代表一个可站立的位置,这些实体携带了与之关联的可行走点信息。
接下来将修改主角的移动方式,使其真正基于这些点进行逻辑运动。过程可能会有点混乱,但这是必要的一步。
修改 game_sim_region.h 和 game_world_mode.cpp:将主角的头部和身体分离
我们现在要将角色的“头部”和“身体”逻辑上拆分成两个独立的实体,以便能更细致地控制它们的行为。这样做虽然有些复杂,但能帮助我们更好地处理角色与世界交互的逻辑,比如碰撞、渲染、控制等。
在进入游戏世界模式时,我们每当有玩家加入,都会新增一个英雄实体。原本我们只添加一个单独的“英雄”实体,但现在我们将其拆分成两个实体:一个“英雄身体”和一个“英雄头部”。
在代码中,我们将添加两个不同的实体类型:EntityType_HeroBody
和 EntityType_HeroHead
。这两个实体会分别添加到游戏中。当前设想是由“头部”来响应玩家输入进行控制,而“身体”是用于存储例如生命值等信息,未来熟悉系统或 AI 也会可能以“身体”为目标。
同时,为了避免两个实体在物理上相互碰撞并阻止移动,我们重新定义了碰撞体积:
- “英雄头部”拥有较高的碰撞体积(例如高度为 0.7),表示其漂浮在身体上方;
- “英雄身体”拥有较低的碰撞体积(例如高度为 0.6);
- 中间设置了一个轻微的间距(例如 0.1)以避免重叠导致的碰撞冲突。
为了实现这个逻辑,我们添加了碰撞体偏移 OffsetZ
,让“头部”的碰撞体可以浮在空中,而不是贴地面。这样,虽然两个实体在空间上重叠,它们的碰撞不会干扰彼此。
然后我们修改了控制逻辑,使玩家实际控制的是“头部”实体,而不是“身体”。我们还调整了控制器与实体之间的映射关系,确保控制器能准确对应到“头部”实体上。
在渲染方面:
- “身体”会绘制影子、躯干、披风等;
- “头部”只绘制头部贴图,避免重复绘制;
- 粒子效果代码被移除了,以简化调试,并准备日后整理到更合理的位置。
过程中遇到了一些调试问题:
- 初始时控制器仍绑定在“身体”实体上,导致“头部”无法响应输入;
- 修正后,确保
AddPlayer
函数返回的是“头部”的实体索引,控制器才正确作用于头部; - 同时确保碰撞体逻辑也已调整到位,避免默认的地面碰撞逻辑干扰。
目前头部和身体实体已被正确地创建、控制和渲染,两者逻辑上分离但在空间中保持配合。后续将继续清理和重构实体系统,以更系统地支持复杂角色行为。
运行游戏并移动头部
我们目前已经实现了角色的“头部”可以自由移动,这非常重要。接下来的目标是让“身体”自动跟随“头部”移动,但这个跟随不是简单的同步移动,而是有逻辑约束的。
具体来说,我们希望身体的移动遵循“可通行点”的规则。也就是说,当头部移动时,身体不会直接模仿头部的精确位置移动,而是始终朝着离头部最近的一个“可通行点”移动。这些可通行点是我们预先定义好的世界中允许角色站立的具体位置。
为了实现这一逻辑,我们将做以下处理:
- 每帧检测头部当前位置;
- 根据头部的位置,查找离其最近的“可通行点”;
- 将身体的位置设定为该最近通行点的坐标;
- 这样可以保证身体只停留在合法位置,符合游戏逻辑设计;
- 同时身体的移动将不会是流畅的连续移动,而是离散跳转到指定点。
这个逻辑的好处在于,它使得游戏角色的运动更明确可控,特别是在一些基于网格或离散位置的交互设计中非常实用,例如固定站位、路径判断、战斗区域限制等。
下一步我们将开始编写这部分的逻辑,让身体按照上述原则实时更新自己的位置,以响应头部的移动行为。
修改 game_world_mode.cpp:让身体沿可行走的路径跟随头部
我们目前正在进行一个模拟系统的开发,重点是让“身体”可以跟随“头部”移动。当前阶段的目标不是追求完美实现,而是尽快完成一个基础可运行的版本,即便实现方式比较粗糙。
具体思路如下:
我们在模拟过程中会让“头部”移动。当“身体”执行模拟逻辑时,它需要在场景中的所有实体中进行搜索,不过这次我们不是在寻找跟随对象,而是在寻找具备“可通行点”(traversable points)的碰撞体(collision volume)。每个实体都拥有碰撞体,因此我们可以安全地遍历所有实体的碰撞体数据。
我们对这些碰撞体中的“可通行点”进行逐一检查:
- 遍历每个可通行点(通过体积的 traversable count 和索引);
- 将每个可通行点转换为世界空间坐标(通过实体的变换信息进行转换);
- 获取“头部”的世界位置;
- 对比该可通行点和“头部”位置之间的距离平方值(以避免开方计算);
- 如果该点是当前距离最近的点,则记录该点的位置;
- 另外需要考虑一个高度限制:只考虑Z轴(高度)低于头部的点,防止“身体”移动到头部之上的位置。不过这部分尚未实现,等坐标系统更稳定后再补上。
一旦遍历完成,我们记录下最近的可通行点坐标,并将“身体”的位置强制设置为该坐标,暂时不添加动画效果,只是直接进行位置设定。这是为了确保功能正常运行,后续再添加过渡效果或插值。
为了让身体知道该跟随哪个“头部”,我们在每个实体中设置了一个“head”引用字段,暂时以最简单的方式实现这一机制。每次模拟时,“身体”会读取自己的 head 引用,如果无法找到(比如头部被破坏了),则保持静止。
我们还需要为这个引用字段实现状态保存与读取的功能,因此添加了 load_entity_reference
和 store_entity_reference
的相关调用,以便后续序列化与反序列化工作正常进行。
总之,这一阶段的主要目标是实现:
- 头部可自由移动;
- 身体自动寻找并移动到最靠近头部的、合法的可通行点上;
- 当前实现暂不考虑动画和高度限制,后续迭代会逐步完善这些细节;
- 系统中通过引用机制让身体知道自己的头部是谁,确保能进行位置跟随。
目前重点是保证整个流程“跑通”,后续再优化逻辑结构、动画表现和状态管理。
修改 game_sim_region.cpp:引入 GetSimSpaceTraversable 函数
我们正在继续完善身体跟随头部移动的机制。当前阶段重点是通过模拟区域(sim region)来获取可通行点的世界坐标,并将身体实体移动到这些点中距离头部最近的那个。
在目前的实现中,我们尚未引入实体旋转(rotation)的概念,因此变换逻辑较为简单。我们只需将可通行点的局部位置(P)加上实体的位置(entity.P)就能获得其在世界空间中的绝对坐标。虽然未来需要考虑旋转(比如平台旋转后可通行点的位置也要跟着转),但目前我们暂不处理旋转,只做平移。
我们做的具体操作如下:
- 在模拟区域内,我们创建一个方法,根据一个索引获取对应的可通行点;
- 检查索引是否在合法范围内,避免访问越界;
- 获取该可通行点的局部坐标;
- 将其与实体的坐标相加,得到该点的世界坐标;
- 这个过程未来将扩展为一个支持旋转的完整变换操作。
接下来,我们确保“身体”实体能正确找到对应的“头部”实体。我们在构造实体的逻辑中添加设置,即在创建玩家实体时,将身体的 head
引用指向它的头部实体。具体来说,就是在 AddPlayer
过程中,把头部的实体索引赋值给身体的 head
引用字段。这样一来,身体在模拟过程中就能知道它应该跟随哪个头部。
最后,还调试了一个for循环的错误。我们发现一个死循环问题,原因是循环变量没有正确递增,导致程序卡死。这种问题虽简单但常见,需要注意循环控制变量的处理。
总之,本阶段实现了以下几个关键点:
- 可通行点的世界空间坐标转换方法;
- 身体通过引用能识别并关联到自身对应的头部;
- 修复了循环错误,保证模拟逻辑能正常运行;
- 暂不处理实体旋转,后续将加入对旋转后的坐标变换支持;
我们正在逐步构建一个稳定且具扩展性的角色控制系统。
断点没进来
Head.Index 一直等于0吗
还是不跟着移动
运行游戏,看到身体跟随头部
我们已经实现了身体根据头部移动,并且表现效果完全符合预期。接下来调整了摄像机的行为模式,以使其配合新的系统运行方式。
之前摄像机还是按照旧的方式进行移动,但现在我们希望摄像机保持“基于房间”的模式不变,这样更符合当前逻辑。因此我们更新了设置,让摄像机的行为固定为“room based”(基于房间的视角),这意味着摄像机会锁定在当前房间视角,而不会随实体自由移动。这个设置调整在 config.h
或相关区域完成,通过设置 global_render_camera_room_based
为 true 实现。
完成这些设置后,系统整体运行状态良好。特别值得一提的是,当前身体跟随头部的逻辑不依赖于网格系统,也就是说就算没有明确的网格布置,该系统依然能正常工作。身体只会跟随到实际存在“可通行点”的区域,因此不会出现身体跑到无效区域或悬空位置的情况,这一点也很理想。
此外我们重新启用了碰撞系统,也就是说参与模拟的实体现在再次拥有碰撞检测功能,确保角色之间或角色与环境之间的交互行为是合理的。
总结目前已经实现的关键改动:
- 摄像机行为调整为“基于房间”的模式;
- 摄像机设置改为启动时自动保持房间视角;
- 实现了身体根据头部移动,并严格依赖可通行点;
- 移动逻辑不依赖网格系统,适应性更强;
- 重新启用了实体碰撞检测功能;
整体系统更加完善,行为更符合设计预期,也为未来扩展提供了良好基础。
修改 game.cpp:游戏启动时直接进入游戏而不是播放过场动画
当前我们在程序启动时默认进入了过场动画,但由于我们正在开发游戏本体的逻辑,因此希望在启动时直接进入游戏世界,而不是先播放开场动画。这样可以节省调试和测试的时间,提高开发效率。
默认情况下,游戏状态中的 game_mode
被设置为执行播放开场动画的逻辑(play_intro_cutscene
),但我们希望临时将其替换为直接进入游戏世界的模式。为此,我们找到了对应的状态切换函数 play_world
,这个函数应当负责设置并进入游戏世界,因此我们将 game_mode
初始化时直接调用 play_world
。
在修改完后,我们预期游戏启动后应该直接跳转到游戏世界场景中,而不是先展示片头动画或标题画面。
然而,在实际执行中,发现程序进入游戏世界失败,主要问题出现在 world_mode
是空指针(null),导致游戏世界无法被正确初始化和显示。
我们意识到 play_world
函数虽然被调用了,但其中用于初始化世界模式的操作似乎并没有被成功执行。这是一个需要修复的问题,因为从设计上看 play_world
理应可以被作为初始状态调用。如果不能正常工作,说明当前世界模式的创建和配置过程中存在逻辑缺陷或遗漏。
接下来我们将深入排查 world_mode
为 null 的原因,并确保 play_world
能够在启动阶段正确设置游戏世界,使得游戏在调试阶段能直接进入主内容。这个修改是临时的,仅用于开发期间,后续发布时仍会恢复原始的启动流程。
调试器:跳转到 PlayWorld 函数并进行调试
在我们深入分析游戏启动流程并尝试绕过过场动画,直接进入游戏世界的过程中,发现了一个令人困惑的问题:在尝试手动调用 play_world
来初始化游戏世界时,game_state.world_mode
却没有被正确设置,导致游戏世界并没有被正确载入。
我们仔细回顾了游戏初始化过程中的一系列操作。游戏状态(game_state
)会被初始化,期间分配一些任务结构、暂态模式、环境贴图等等,这些都没有问题。但当我们检查 world_mode
时发现,它只是一个空指针,根本没有被分配或初始化。
从已有逻辑看,play_cutscene
等函数中是会主动设置 game_state.world_mode
的,也就是说,在播放过场动画时,它会在内部创建并指向一个 world_mode
对象。但 play_world
函数中并没有类似的逻辑,它只是创建了 world_mode
的内容(通过 push_struct
),但没有将这个创建的 world_mode
实例赋值给 game_state.world_mode
,因此外部始终拿不到正确的指针。
进一步追踪调试时,我们使用数据断点观察 game_state.world_mode
的写入过程,发现在初始化阶段确实没有任何地方设置了它,直到过场动画开始播放,才被写入。这意味着之前游戏能正常进入世界场景的原因,仅仅是因为走了过场动画逻辑,而非因为 play_world
函数本身正确地设置了世界模式。
我们最后发现,这一切困惑的根源在于 world_mode
实际上是个联合体(union)中的一部分——也就是说它是 game_state.transient_storage
里不同模式之一的重用内存块。当 play_cutscene
被调用时,它会通过 push_struct
创建 world_mode
并正确赋值;而我们在手动调用 play_world
时,仅创建结构体而没有设置指针,导致逻辑失败。
因此,我们要修复这个问题,只需要在 play_world
中显式地将新建的 world_mode
指针赋值给 game_state.world_mode
,与 play_cutscene
的处理方式一致。只有这样,我们才能绕过过场动画并在启动时直接进入游戏世界,从而提升开发效率。
这太离谱了,我们终于搞清楚这个问题为什么“居然还能工作”。实际上,它能正常运行完全是一个巧合,根本不是代码写得对,而是“运气好”。
具体来说,在初始化游戏世界时,我们并没有显式地将新建的 world_mode
指针赋值给 game_state.world_mode
。理论上,这样做的话 game_state.world_mode
应该是个空指针,后续逻辑应该直接崩溃。然而,实际运行中它却“神奇地”工作了。为什么?
通过调试我们发现,push_struct
在分配 world_mode
时,恰巧分配在了与之前一个模式(如 cutscene_mode
)完全相同的内存地址上。这意味着虽然我们没有手动设置指针,但由于内存地址正好一致,game_state.world_mode
实际上仍然指向了那个有效的世界模式对象,仿佛一切都正常。
我们打印出地址验证了这一点:当 cutscene_mode
分配时,它用的是某个地址;后来我们执行 play_world
分配 world_mode
,结果地址完全一样。这说明内存分配器恰好复用了同一个地址,而这个复用行为是不可预测的,也就是一个纯粹的“运气”。
换句话说,代码里少了一行关键赋值,只是因为堆栈分配结构体时内存位置刚好一样,才没暴露出 bug。这是非常危险的行为,因为一旦内存分配策略稍有变化(比如增加新的结构体、改个顺序等),就会导致 game_state.world_mode
指针失效,进而引发程序崩溃或未知行为。
总结:我们发现一个完全依赖偶然的逻辑漏洞,根本原因是缺少显式的指针赋值,而目前运行正常只是因为堆栈内存复用刚好让指针“看起来像是对的”。这个问题必须立即修复,以防未来出现更严重的潜在 bug。
修改 game_world_mode.cpp:在 PlayWorld 中设置 WorldMode
我们发现了一个至关重要但原本完全缺失的代码,这一行代码非常关键,必须明确地加入到初始化逻辑中才能保证游戏世界模式(world mode)被正确设置。
原来程序之所以“能跑”,是因为之前碰巧通过内存复用的巧合,让 game_state.world_mode
指针误打误撞地指向了正确的对象。现在我们已经补上了这条关键赋值语句,系统行为才真正变得可预测、可控。
验证补丁后,一切看起来正常。然后我们尝试重新切换到原来的意图 —— 启动时跳过片头动画(cutscene),直接进入游戏世界。我们关闭 cutscene 后,程序理论上应该立即进入 world mode,因为我们已经将 game mode 设置成了 world,所以应该调用启动 world 的初始化流程。
但此时我们注意到,程序并没有进入 world,而是进入了标题画面(title screen)。这说明虽然我们设置的是 world 模式,但游戏逻辑实际上并没有走进 world 的分支。经过分析后我们意识到:
当前没有玩家存在
这是问题的根源。在没有玩家的情况下,世界模式(world mode)并不会进行后续处理和渲染。换句话说,哪怕模式切换正确,如果没有任何玩家实体被初始化,那么整个游戏世界逻辑也不会“激活”。
总结如下:
- 修复了之前缺失的
game_state.world_mode
赋值逻辑,解决了靠运气运行的问题; - 成功地让程序在启动时绕过 cutscene 逻辑;
- 但发现没有玩家实体的情况下,world mode 不会触发渲染或更新;
- 接下来必须显式初始化玩家,否则即使启动进入 world,画面也会停留在 title screen。
整体过程揭示了初始化流程中多个依赖项之间的微妙耦合:game mode 设置、实体初始化、渲染逻辑都必须正确协调,才能让游戏按预期启动和运行。
修改 game.cpp:强制设置开始按钮为已按下
我们遇到了一个逻辑问题:当进入游戏模式后,如果当前没有任何玩家存在,系统会自动回退到标题画面(Title Screen)。也就是说,游戏世界成功加载,但由于没有英雄实体被创建,游戏立即退回到了社交界面。这是一个在开发和调试阶段非常容易被忽视的逻辑缺口。
具体流程如下:
- 我们将初始模式设置为了“世界模式”(world mode),跳过了过场动画;
- 但由于没有任何玩家加入,游戏在检测时发现没有英雄存在,于是自动退回了标题画面;
- 当前代码的行为是,如果没有玩家或英雄,默认就回退到社交界面/标题画面;
- 因此,需要手动或自动创建一个初始玩家才能避免这种情况。
为了解决这个问题,我们考虑强制在启动时创建一个玩家。其实并不复杂,我们可以模拟一个“按下开始键(Start)”的输入事件,通过伪造一个控制器(controller)输入来触发玩家加入:
- 比如我们可以手动写入一个控制器的状态,让它表现得像玩家按下了“Start”;
- 这种方式虽然有些“硬编码”,但在调试阶段是完全可接受的;
- 当然更理想的方式是将这一行为显式、结构化,比如通过一个统一的“启动游戏并添加玩家”流程来处理。
此外我们还考虑到了:
- 当前只是用于调试目的,所以这种伪造输入的方法也没那么糟糕;
- 长远来看,最好提供一个更加正式和结构化的初始化接口,比如一个明确的
PlayWorldAndAddPlayer()
函数; - 最终结果是,我们通过模拟“Start”输入事件,成功让游戏在启动后保持在游戏世界中,而不会意外回退到标题画面。
总结:
- 原因是进入 world mode 后没有任何英雄,触发了退回逻辑;
- 解决方法是模拟控制器输入,让一个玩家自动加入游戏;
- 虽然这种方法略显临时,但对于开发流程非常有帮助;
- 更优的方案是构建一个结构化的接口来初始化世界并添加玩家。
运行游戏,直接进入游戏界面
我们目前通过一种方式模拟了玩家按下“Start”按钮,从而在游戏启动时自动加入一个英雄角色。这使得即使仍然是从过场动画(cutscene)状态启动,我们也能够直接进入游戏世界,无需显式调用“play world”函数。这种处理方式灵活而高效,非常适合开发阶段快速调试。
具体操作逻辑如下:
- 模拟输入:我们重写了输入系统中的“Start”按钮,使系统认为有玩家按下了开始键;
- 自动加入玩家:这种模拟输入触发了原有的逻辑,即当玩家按下“Start”时,会创建并加入一个新英雄;
- 保留原有流程:尽管依旧从过场动画进入,我们不再强制调用“play world”,而是通过模拟的方式让游戏自然地进入世界状态;
- 调试灵活性提升:这种方法大大提高了我们调试时的效率,不用每次都等待过场动画或进行繁琐操作;
- 兼容性好:因为这种模拟输入方式是利用已有逻辑处理的,所以不会破坏整体结构或流程控制。
接下来,我们开始关注一个新的问题 —— 每帧的Δ时间(Delta Time, 简称DT)表现。这是游戏运行时衡量帧率稳定性和时间同步非常重要的参数。我们希望观察在当前逻辑下,帧间Δ时间的变化趋势。
此外,我们还提到,有人在 GitHub 上报告了与 Δ时间相关的 bug:
- 我们尝试登陆 GitHub,查看相关 issue;
- 找到了“issues”部分,准备浏览用户所反馈的内容;
- 初步线索是用户提到 DT 的某种“错误行为”或“异常表现”。
总之,这一阶段我们完成了以下几项关键操作:
- 利用模拟输入,在启动时强制加入一个玩家,从而顺利进入游戏世界;
- 避免了强制跳过流程的粗暴做法,保留了完整结构;
- 准备排查 Δ时间的相关 bug,提升运行效率和表现稳定性。
修改 win32_game.cpp:修正 GameUpdateHz 的计算
我们发现目前帧时间(Δ时间)计算存在严重问题,系统传入的值与真实刷新率严重不符,误差可能达到两倍以上。这主要是因为系统关闭了垂直同步(vsync),而且在早期阶段为了调试方便,我们人为地绕过或忽略了某些与刷新率相关的机制。
一位开发人员指出了这个问题,并准确捕捉到了这一异常。这属于早期调试阶段留下的遗留问题,最初我们并没有真正实施准确的刷新率控制逻辑,仅仅是“假装”在进行帧同步。这种处理方式在正式开发逻辑前还能容忍,但随着游戏逻辑逐步完善,我们必须切换到真实且精准的帧时间控制机制。
为此,我们进行了以下几个关键调整:
-
启用 vsync 测试环境:我们确认在当前所有测试和开发环境中都应该启用垂直同步(vsync),从而确保帧率与屏幕刷新率同步;
-
修复目标帧秒数(target seconds per frame):我们检查了代码中使用此目标值的位置,并确认该值由一个预设的“game update hertz”推导而来;
-
使用正确的帧率常量:如果 vsync 始终启用,那么我们应使用其对应的刷新频率(例如 60Hz),这意味着每帧时长应为
1.0 / 60.0
秒; -
为后续游戏逻辑打下基础:这是一个至关重要的前置修复步骤。只有确保帧时间的正确性,我们才能在后续实现准确的角色运动、动画播放、物理仿真等核心功能,否则所有时间驱动的逻辑都将出错。
通过这项修复,我们迈出了游戏性能与稳定性控制的重要一步,为接下来的开发工作打下了坚实基础。
运行游戏并以更正确的速度移动
我们发现之前的运行速度略显不稳定,导致画面表现略微抽搐或“跳帧”。这也间接说明过场动画的时序可能不准确。不过目前观察来看,动画效果还算正常,画面节奏与预期差别不大。
当前的过场动画还没有与任何音频轨道进行同步,因此动画播放速度实际上完全取决于我们设置的时长或者说未来音频的长度。因此,只要最终音频素材确定,动画节奏便可以进行适配。目前看到的播放速度也大致符合我们的预期,所以算是稳定下来了,这点令人满意。
另外,在这次改动之后,我们也顺便对主角的运动逻辑做一个简单的测试。我们尝试在世界模式下对主角的身体进行点对点的移动动画,以验证新系统下实体动画表现的正确性与流畅度。
这次测试主要是:
- 检查英雄(主角)身体在多个坐标点之间的移动效果;
- 验证新修复的帧时间控制逻辑是否能正确驱动动画;
- 初步感受整体逻辑是否能匹配未来更复杂的动作系统。
虽然时间有限,还无法完成完整的运动系统构建,但我们已经迈出了关键的第一步。这将为后续更丰富的角色控制与动画表现打下良好基础。
修改 game_world_mode.cpp:使身体朝向可行走点加速
我们在 dot cpp
中的 sim_region
内进行了一些关于角色移动逻辑的实验。目标是让主角身体根据某种逻辑进行点对点的移动测试,以验证动画系统的运作情况。我们尝试采用先前熟悉的运动方式,通过与主角的相对位置来计算移动向量(DP)和加速度(DDP),从而驱动实体运动。
首先,我们复制了之前的一套基于与主角之间距离的加速度计算方法,但对一些逻辑进行了简化。在处理最近点(closest P)时,我们直接用了原始代码段,而不是以往的更复杂处理。但也注意到,如果最近点没有有效找到或距离太大,平方根函数(用于距离归一化)可能会导致无意义的大值。为了避免这种情况,我们设置了一个上限,比如“如果两个对象相距超过1000米,就不移动”,以保证数值安全。
接下来,我们试图通过设置实体的 move_spec
来启动实际的物理运动,并检查是否有加速度和速度更新。过程中发现角色完全没有移动。首先怀疑是碰撞检测导致阻碍,于是将碰撞功能暂时禁用(entity.collision = world_mode.no_collision
),但依旧无效。
进一步调试后,我们检查了 closestP
、mvP
等相关位置数据,发现这些数据本身是有效的,也就是说数学计算是有结果的,DDP
(加速度)也正常。然而最终结果 entity.dp
(速度)却是完全错误的,像是未初始化一样。
经过逐步排查,最终发现是逻辑疏忽:如果两个实体起始位置过近,或者恰好重叠,那么归一化方向向量就会失效(因为模长接近于0或等于0,单位向量无法计算),导致运动方向丢失,因此角色无法获得有效的速度或加速度。
为了解决这个问题,我们重新加回了“最小距离判断”逻辑:如果目标点与实体当前位置距离过近(即在某个极小阈值内),就不尝试进行移动,这样避免了除以零或接近零的问题。
这个过程也再次提醒我们:哪怕是非常基础的物理逻辑,如果缺少边界条件判断,也可能导致整个行为系统失效。因此完善数值处理和特殊情况的容错能力是物理逻辑构建中的关键一环。
运行游戏并看到身体朝头部加速靠近
现在我们实现了基本的角色身体追踪逻辑,并初步测试了角色的自动移动表现。当前虽然动画部分还没有完全处理完善,但我们已经可以观察到追踪行为的基本效果。
最初我们对加速度(acceleration)在整体逻辑中的必要性产生了疑问。因为根据当前的结构,速度的决定因素其实是直接通过位置差计算出的向量,而非由加速度逐帧积分而来。换句话说,我们之前使用的“familiar”代码片段似乎存在冗余,它做了多余的运算,甚至有些部分可能完全没有实际意义,因为底层代码已经自动处理了速度的计算。
进一步优化过程中,我们尝试直接指定 DDP
(加速度)为单位方向向量与目标距离乘积的结果,也就是目标加速度向量。验证之后确认这种方式完全可行,系统能够自动将其转化为合理的速度和位置更新。这一发现表明之前的代码确实设计不合理,存在多余逻辑,我们可以将其精简。
之后我们调整了物理参数中的阻尼系数(drag),增加其数值以使角色身体更加紧密地跟随头部移动。阻尼越高,代表系统会更快地抑制速度变化,从而让身体几乎立即响应头部位置的变化。虽然过高会显得过于僵硬,但适度的提高确实可以带来更紧凑的跟随感。最终达成的运动效果与预期较为一致,能比较自然地表现出“身体跟随头部”的行为。
总结来说,目前:
- 追踪逻辑已经基本完成;
- 冗余的运动代码被剔除;
- 通过调整 drag 参数优化了角色响应;
- 虽未实现动画插值,但运动曲线大体令人满意;
- 角色能够顺畅移动,身体能较好地跟随目标部位。
下一步计划是完善动画系统,使该移动逻辑配合帧动画实现视觉上的连贯动作。此外,可能还需进一步精细调参,以增强整体流畅度和表现力。整体来看当前状态已经进入较为稳定阶段,可作为后续角色控制和动画系统开发的基础。
目前角色的身体追踪逻辑已经基本完成,角色的身体会持续跟随主控制部位(头部)进行移动。由于我们确保了角色只能停留在可通行区域(traversable),因此角色的身体不会在不该停下的地方停止或卡住。这一机制保证了整个移动逻辑的连贯性与物理合理性。
接下来的重点工作将集中在角色动画表现的完善上,尤其是视觉上如何让角色的身体和头部保持自然的连接。例如,目前头部与身体仍可能在视觉上出现“脱节”感(detached head),这是需要解决的视觉问题。为此,需要设计和实现更细腻的过渡动画或骨骼绑定机制,以确保角色各部分运动协调一致。
除动画优化外,还需要增加一个功能:在特定条件下让头部“自动对齐”或“回正”身体的位置,也就是我们希望头部能够自动吸附到合适的状态。实现这个功能的目标是使角色在没有主动输入时,也能逐渐回归到一个合理的站立姿势或者中心位置,从而避免奇怪的姿势或错位。
总结当前状态与下一步目标:
-
当前角色身体只会停留在允许通行的位置,避免非正常停顿;
-
身体可以平滑地追踪头部,移动逻辑基本正确;
-
下一步是完成动画表现,包括:
- 头部与身体的自然连接;
- 动画补间过渡;
- 姿态矫正机制(如自动回正);
-
整体效果已达到可用状态,后续工作将提升视觉表现与使用体验。
继续推进这些方向,将有助于提升角色控制的自然性与游戏整体品质。
“我希望在你停止操作时头部能立刻回到身体上”γ
当前我们已经实现了角色身体在移动时能够平滑地跟随头部移动,这在玩家持续输入控制方向(如按住右键移动)时效果良好,追踪逻辑运行正常。然而,为了使角色移动逻辑更加自然,我们希望在玩家停止输入时,头部能够自动回到一个“中立”或“自然”的位置,也就是靠近身体原本应在的位置。
换句话说,当前逻辑下,头部在玩家操作时可以移动离身体,但一旦操作停止,头部仍保持在先前位置,显得不够自然。我们理想中的效果是:当输入停止时,头部会以一定的速度逐渐回归身体所在的默认位置,实现自然的回弹或归位动作,从而让角色整体姿态始终保持协调统一。
为此我们需要在逻辑中增加一个判断:当没有任何控制输入时(如方向键或手柄摇杆处于静止状态),我们将头部的位置目标重设为身体的中立位置,并启动一个缓动(插值)机制,令其逐帧平滑地靠近这个目标。这种机制有点类似于“弹簧”或“惯性回弹”,既保持了物理感,也增强了操控的手感。
总结目前所需调整与优化方向如下:
- 玩家控制输入时,保持当前头部与身体分离状态的追踪逻辑;
- 玩家停止输入后,触发头部“回正”机制,使其逐渐向身体默认位置靠拢;
- 实现平滑插值,避免突兀的跳跃或闪动;
- 这一机制将显著提升角色姿态自然性和游戏整体体验;
- 后续可进一步配合动画系统,让视觉表现更加统一完整。
当我写事件驱动代码时,经常不得不使用动态内存分配来保留状态直到回调使用。你是如何处理这种情况的?
在编写事件驱动代码时,通常需要使用动态内存分配来保持一些状态,直到回调函数需要它们。在这种情况下,动态内存分配并不一定是坏事,关键在于你如何管理内存。
首先,动态内存分配本身并不是问题,问题在于你是否清楚自己在做什么。常见的解决方式之一是使用“空闲列表”(free list)。空闲列表是一种简单的内存管理方法,通常用于处理固定大小的内存块。当你知道大多数内存分配的大小是固定的,而且分配的次数也比较少时,可以创建一个特定大小的内存池。比如,如果你知道大部分内存分配都是64字节,并且最多需要1000个这样的内存块,你就可以创建一个64字节的空闲列表,并且内存池的总大小为1000个64字节。这样,当你需要内存时,直接从这个内存池中获取,并且在不需要时归还给空闲列表。这样就可以避免常规的内存分配过程,降低内存分配的开销。
这个方法的好处是,不需要复杂的内存分配管理,内存分配和释放的速度非常快,通常只有两到十条指令,且不存在内存碎片问题。你知道你只需要这些特定大小的内存,使用起来高效且没有额外的复杂性。
所以,关键的问题不在于是否要避免动态内存分配,而是在于你是否真正需要一个通用的内存分配器。很多时候,我们使用动态内存分配器(如new和delete)只是因为我们学过它们,但可能没有深入思考过代码的实际需求。可能我们并不需要通用的内存分配器,而是只需要针对特定需求进行简单的内存管理,这样效率更高,代码也更清晰。
总的来说,动态内存分配是可以接受的,问题的关键在于如何通过合理的方式管理内存,避免过度复杂的分配机制,确保代码的高效和可维护。
游戏调试视图是否可以查看像 world mode 这样的结构体?
在调试时,可以通过打印出结构体的内容来查看其具体值。例如,若想查看 world mode
中的内容,可以直接打印出相应的值。对于一个布尔类型的变量,当角色在斜对角方向移动时,可能需要额外的判断和处理,因为斜对角的运动通常涉及更复杂的坐标计算,需要在代码中适当地处理这种情况。
当对角移动时,玩家是否能清楚身体将如何走到对角实体?现在看起来似乎是随机的
对于角色在斜对角方向的移动,当前的表现看起来有些随机,可能会让玩家感到困惑。因此,是否允许斜对角移动还需要进一步考虑。如果决定允许斜对角移动,那么可能会在代码中做出调整,确保在特定的区域内(例如中间区域)触发斜对角的移动,而不是选择直线移动。这样可以避免在斜对角方向时,角色的运动看起来不自然或不一致。实际上,通过一些简单的调整,应该可以轻松实现这一功能,确保玩家在斜对角移动时能够获得更流畅的体验。
修改 game_world_mode.cpp:引入 DesiredDirection,使身体更直接移动到头部
有两种方法可以实现这个目标。当前的方法是通过遍历所有点,选择距离头部位置最近的点。但也可以采取另一种方法:计算一个理想的方向向量,理想方向向量是从身体到头部的向量。通过这个向量,可以确定一个理想的运动方向。
然后,测试时不再是计算点之间的距离,而是测试移动到某个点与当前运动方向的对齐程度。具体来说,就是通过点和目标方向的内积来衡量它们之间的相似性,进而决定哪个点最符合理想方向。为了确保方向一致,可以使用单位向量(归一化后的向量)来进行测试。这样可以更准确地选择与目标方向最接近的点。
修改 game_math.h:引入 NOZ(Normalize 或 Zero)函数
首先,提到的 normalizer_zero
函数是一种自动的 epsilon 检查,它的作用是计算一个向量的长度,如果长度大于某个阈值(epsilon),则归一化该向量,否则返回零向量。这个操作避免了对非常小的向量进行归一化,防止在计算中出现不稳定性。
接下来,介绍了如何计算和使用方向向量。在这个过程中,首先计算的是“从头部到目标点”的向量和“从身体到目标点”的向量。然后,通过内积运算,可以测试“从身体到目标点”的向量与“理想移动方向”之间的对齐程度。内积的结果表示两个向量之间的夹角,取值范围从 -1 到 1,分别表示完全反向、正向和垂直。
然而,内积的值会随着目标距离的增加而被放大,这并不符合期望。为了修正这一问题,需要对这个向量进行归一化,使得结果仅反映角度的对齐程度,而不受距离的影响。
此外,为了提高效率,可以将这一计算放在内层循环中,只在当前点更优时才进行相关计算。最后,结合距离和方向对齐程度,可以优化目标选择,确保选择最近且与理想方向最一致的点。这种方法不仅更准确,还能避免因为距离过远而导致的误差。
运行游戏并测试新移动逻辑
如何筛选出那些大致与头部朝向一致的目标点。目的是通过内积运算来判断目标点的方向与头部朝向的对齐程度。如果目标点的方向与头部朝向的方向大致一致,则该目标点会被认为是一个有效的选择。
同时,提到了一些内存计算的细节,通过解释如何将这一筛选操作应用到整个过程,确保在选择目标点时,优先考虑那些方向对齐的点。而“在中间能成立”的问题似乎指的是某些逻辑或数学操作在特定条件下能正常运行,可能是在解释为什么某些筛选条件能够生效时,给出了对其背景和实现方式的理解。
修改 game_world_mode.cpp:有条件地设置最近点
如何调整选择目标点的策略。首先,提到为了避免总是选择距离最近的点,需要引入一个新的条件判断。这是为了确保选择的点不仅距离近,而且方向上也对齐。
为了解决这个问题,可以使用一个布尔变量来跟踪是否已经选择了一个目标点。若尚未选择,就会使用一个标志位(例如 pickany
),然后通过比较“测试方向”与当前选择的点来决定是否更新目标点。
此外,增加了一个判断条件:如果还没有选择目标点,或者当前测试的方向更合适,则更新选择。这样做是为了确保最终选择的目标点不仅仅是最近的那个,而是最符合预期方向的那个。
总体来说,这是在优化目标点选择的方式,确保选择的目标点符合方向上的要求,而不仅仅是距离上的要求。
运行游戏并看到身体更接近目标点停止
在处理代码时,遇到的问题是如何处理“wiggliness”(波动性),主要是对目标方向的选择和判断逻辑不够精确。当前的思路是,当目标位置发生变化时,系统需要根据当前位置来选择一个新的方向。这个选择需要基于一些测试逻辑,但目前这个测试还没有完全做好,导致有时目标方向无法准确判断。
理想的逻辑是,如果当前没有选定的目标方向,并且所有可能的方向都没有通过测试,那么可以选择一个最接近的方向继续前进。如果没有找到一个通过测试的方向,那么就需要根据具体情况选择一个方向,并调整策略。
随着系统逐步运动,关键点在于判断哪些方向是合适的,哪些方向是无效的,而在目前的实现中,判断逻辑仍然有些不足。下一步需要进一步改进对目标方向的判断,并确保选择的方向符合预期的运动轨迹,从而避免不必要的波动。
修改 game_world_mode.cpp:恢复原来的移动方式
在编写代码时,发现需要更多的时间来完成这一部分,因为有很多因素需要考虑。首先,需要清楚地知道起始点的位置,确保始终能够使用最初的起点。这一点非常重要,因为在进行动画处理时,我们总是需要依赖于最初的起始点。
目前的代码实现还不够完善,不能直接替代现有的逻辑,所以决定暂时保持原有的实现方式。虽然当前的代码还不够好,不能直接替换,但未来有可能进行替换。实现这一步并不困难,关键是确保拥有正确的变量来跟踪运动的状态,以便准确地知道原来的运动轨迹。
此外,还需要处理头部的“回弹”效果,这要求我们在代码中加入相应的处理逻辑。总体来说,接下来的工作会包括调整变量和代码逻辑,以确保动画的正确性和流畅度,最终实现所期望的效果。
这可能是错觉,但看起来身体对齐在瓦片的角落。通常在基于瓦片的游戏中,实体位于瓦片中心。这是有意为之吗?
看起来像是身体在朝着瓦片的角落移动,这可能只是视觉上的错觉。在大多数基于瓦片的游戏中,实体通常占据瓦片的中心位置。实际上,当前显示的是瓦片的可行走点,而不是瓦片的角落,因此瓦片的碰撞体积目前并未包括地面瓦片。
需要注意的是,当前的碰撞检测仅限于“可行走点”,而不涉及瓦片的角落部分。要解决这个问题,可能需要在地面瓦片的碰撞体积上进行调整,确保它能够正确处理实体与地面之间的互动。目前,地面瓦片的碰撞检测尚未完全实现,需要进一步完善。
修改 game_world_mode.cpp:让 MakeSimpleFloorCollision 绘制瓦片
如果将碰撞体积部分加入进去,假设设置了一个体积计数为1,并且为其定义了相关参数,那么就可以实现碰撞体积的正确检测。通过添加这些内容并绘制出来,效果将更加明显。接下来,代码会在启动时自动处理这些设置,这样就能在实际运行中看到碰撞体积的效果。
通过这种方式,可以更清晰地观察到碰撞体积对游戏世界中的实体运动和交互的影响。
运行游戏,看到瓦片并在其上移动
瓦片本身可以清楚地看到,实体确实是站在瓦片的中心位置上的。这也验证了之前关于实体在瓦片中心而非角落的判断是正确的。通过可视化的方式进一步确认了瓦片绘制的位置和实体的站位逻辑是一致的。
此外,在循环过程中还有其他视觉线索可以辅助判断,例如实体与瓦片之间的对齐方式、移动路径的过渡效果,以及在帧更新时位置的稳定性等。这些细节共同说明当前的实现是按照瓦片中心来作为实体的定位基准,从而确保了空间逻辑的一致性和动画表现的自然性。
相关文章:
游戏引擎学习第271天:生成可行走的点
回顾并为今天的内容设定背景 我们昨天开始编写一些游戏逻辑相关的内容,虽然这部分不是最喜欢的领域,更偏好底层引擎开发,但如果要独立完成一款游戏,游戏逻辑也必须亲自处理。所以我们继续完善这部分内容。事实上,接下…...
FlySecAgent:——MCP全自动AI Agent的实战利器
最近,出于对人工智能在网络安全领域应用潜力的浓厚兴趣,我利用闲暇时间进行了深入研究,并成功开发了一款小型轻量化的AI Agent安全客户端FlySecAgent。 什么是 FlySecAgent? 这是一个基于大语言模型和MCP(Model-Contr…...
DAMA车轮图
DAMA车轮图是国际数据管理协会(DAMA International)提出的数据管理知识体系(DMBOK)的图形化表示,它以车轮(同心圆)的形式展示了数据管理的核心领域及其相互关系。以下是基于用户提供的关键词对D…...
使用vue3-seamless-scroll实现列表自动滚动播放
vue3-seamless-scroll组件支持上下左右无缝滚动,单步滚动,并且支持复杂图标的无缝滚动。 核心特性 多方向无缝滚动 支持上下、左右四个方向的自动滚动,通过 direction 参数控制(默认 up),适用于新闻轮播、…...
Scrapyd 详解:分布式爬虫部署与管理利器
Scrapyd 是 Scrapy 官方提供的爬虫部署与管理平台,支持分布式爬虫部署、定时任务调度、远程管理爬虫等功能。本文将深入讲解 Scrapyd 的核心功能、安装配置、爬虫部署流程、API 接口使用,以及如何结合 Scrapy-Redis 实现分布式爬虫管理。通过本文&#x…...
mac环境配置(homebrew版)
文章目录 【环境配置】HomebrewGitJavaMavenMySQLRedisNacosNode.js 【拓展-mac常见问题】mac文件损坏问题mac必装软件(Java开发版)zsh和bash配置文件区别 【参考资料】 查看每个版本可以用命令brew info xxx ps:每一个环境安装完之后都要关掉…...
19、DeepSeek LLM论文笔记
DeepSeek LLM 1. **引言**2、架构3、多步学习率调度器4、缩放定律1.超参数的缩放定律2. 估计最优模型和数据缩放 5、GQA分组查询注意力汇总deepseekDeepSeek LLM 技术文档总结1. **引言**2. **预训练**3. **扩展法则**4. **对齐(Alignment)**5. **评估*…...
基于LLM的6G空天地一体化网络自进化安全框架
摘要 最近出现的6G空天地一体化网络(SAGINs)整合了卫星、空中网络和地面通信,为各种移动应用提供普遍覆盖。然而,SAGINs的高度动态、开放和异构的性质带来了严重的安全问题。构建SAGINs的防御体系面临两个初步挑战:1)…...
【Mac 从 0 到 1 保姆级配置教程 12】- 安装配置万能的编辑器 VSCode 以及常用插件
文章目录 前言安装 VSCode基础配置常用插件1. 通用开发工具2. 编程语言支持3. 数据库工具4. 主题与界面美化5. 效率工具6. Markdown 工具7. 容器开发8. AI 辅助编程9. 团队协作 最后系列教程 Mac 从 0 到 1 保姆级配置教程目录,点击即可跳转对应文章: 【…...
数据库与SQL核心技术解析:从基础到JDBC编程实战
数据库技术作为现代信息系统的核心,贯穿于数据存储、查询优化、事务管理等关键环节。本文将系统讲解数据库基础知识、SQL语言核心操作、索引与事务机制,并结合Java数据库编程(JDBC)实践,助你构建完整的数据库技术体系。…...
JUC并发编程(上)
一、JUC学习准备 核心知识点:进程、线程、并发(共享模型、非共享模型)、并行 预备知识: 基于JDK8,对函数式编程、lambda有一定了解 采用了slf4j打印日志 采用了lombok简化java bean编写 二、进程与线程 进程和线程概念 两者对比…...
postgres--MVCC
PostgreSQL 的 MVCC(Multi-Version Concurrency Control,多版本并发控制) 是其实现高并发和高性能的核心机制,支持多个事务同时读写数据库而无需加锁阻塞。它的核心思想是通过保留数据的多个版本来避免读写冲突,从而提…...
nanodet配置文件分析
以下是针对 NanoDet-Plus-M-1.5x_416 配置文件的逐模块解析,以及调整参数的作用和影响范围: 1. 模型架构(model) Backbone(骨干网络) backbone:name: ShuffleNetV2model_size: 1.5x # 控制网络宽度&…...
【Linux网络】HTTP
应用层协议 HTTP 前置知识 我们上网的所有行为都是在做IO,(我的数据给别人,别人的数据给我)图片。视频,音频,文本等等,都是资源答复前需要先确认我要的资源在哪台服务器上(网络IP&…...
Unity中AssetBundle使用整理(一)
一、AssetBundle 概述 AssetBundle 是 Unity 用于存储和加载游戏资源(如模型、纹理、预制体、音频等)的一种文件格式。它允许开发者将游戏资源打包成独立的文件,在运行时动态加载,从而实现资源的按需加载、更新以及减小初始安装包…...
CMOS内存的地址空间在主内存空间中吗?
CMOS内存(即CMOS RAM)的地址空间不位于主内存地址空间(如0x00000-0xFFFFF)内,而是通过独立的I/O端口地址进行访问,具体如下: 1. CMOS内存的物理存储与地址机制 CMOS RAM芯片通常集成在主板…...
大模型应用中常说的Rerank是什么技术?
Rerank技术详解 一、定义与基本原理 Rerank(重排序)是一种在信息检索系统中用于优化搜索结果排序的技术,其核心目标是通过二次评估和排序候选文档,提升结果的相关性和准确性。其运作机制通常分为两阶段: 初步检索:使用传统方法(如BM25关键词匹配或Embedding向量检索)…...
Python-MCPInspector调试
Python-MCPInspector调试 使用FastMCP开发MCPServer,熟悉【McpServer编码过程】【MCPInspector调试方法】-> 可以这样理解:只编写一个McpServer,然后使用MCPInspector作为McpClient进行McpServer的调试 1-核心知识点 1-熟悉【McpServer编…...
C 语言数据结构基石:揭开数组名的面纱与计算数组大小
各类资料学习下载合集 https://pan.quark.cn/s/8c91ccb5a474 在前面的文章中,我们已经学习了 C 语言一维数组的定义和初始化。我们知道数组是用来存储一系列相同类型数据的集合,并通过下标来访问每个元素。但是,除了通过下标访问单个元素,数组名本身在 C 语言中也…...
Java高频面试之并发编程-15
hello啊,各位观众姥爷们!!!本baby今天又来报道了!哈哈哈哈哈嗝🐶 面试官:as-if-serial 是什么?单线程的程序一定是顺序执行的吗? as-if-serial 规则 定义: …...
MySQL数据库迁移SQL语句指南
MySQL数据库迁移SQL语句指南 一、基础迁移方法 1. 使用mysqldump进行全量迁移 -- 导出源数据库(在命令行执行) mysqldump -u [源用户名] -p[源密码] --single-transaction --routines --triggers --events --master-data2 [数据库名] > migration…...
Vue:生命周期钩子
深入理解 Vue 的钩子函数(生命周期函数) Vue 的钩子函数(生命周期函数)是 Vue 实例在不同阶段自动调用的函数。可以在 Vue 实例的创建、更新、销毁等阶段插入自己的逻辑。 钩子函数的作用 想象一下,Vue 实例的生命周…...
深入理解设计模式之原型模式(Prototype Pattern)
一、为什么需要原型模式? 在传统对象创建方式中,我们通过new关键字直接调用构造函数创建实例。但当遇到以下场景时: 对象初始化需要消耗大量资源(如数据库连接)需要创建的对象与现有实例高度相似希望屏蔽对象创建的复…...
K8S cgroups详解
以下是 Kubernetes 中 cgroups(Control Groups) 的详细解析,涵盖其核心原理、在 Kubernetes 中的具体应用及实践操作: 一、cgroups 基础概念 1. 是什么? cgroups 是 Linux 内核提供的 资源隔离与控制机制,…...
ARMV8 RK3399 u-boot TPL启动流程分析 --start.S
上电后运行的第一支文件:arch/arm/cpu/armv8/start.S CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK1 #include <asm/arch/boot0.h> 跳转到 arch/arm/include/asm/arch-rockchip/boot0.h CONFIG_SPL_BUILD1 b 1f ROCKCHIP_EARLYRETURN_TO_BROMno TINY_FRAMEWORKno …...
【网络原理】数据链路层
目录 一. 以太网 二. 以太网数据帧 三. MAC地址 四. MTU 五. ARP协议 六. DNS 一. 以太网 以太网是一种基于有线或无线介质的计算机网络技术,定义了物理层和数据链路层的协议,用于在局域网中传输数据帧。 二. 以太网数据帧 1)目标地址 …...
保姆级教程|YOLO11改进】【卷积篇】【4】使用RFAConv感受野注意力卷积,重塑空间特征提取,助力高效提点
《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…...
虚幻引擎5-Unreal Engine笔记之常用核心类的继承关系
虚幻引擎5-Unreal Engine笔记之常用核心类的继承关系 code review! 文章目录 虚幻引擎5-Unreal Engine笔记之常用核心类的继承关系1.UE5中常用核心类的继承关系1.1.简化版1.2.plantuml图1.3.plantuml代码1.4.关于大写字母U和A2.1.组件和类的关系,组件也是类吗&…...
力扣2680题解
记录 2025.5.9 题目: 思路: 1.计算初始或值:首先计算数组中所有元素的按位或结果 allOr,这表示在不进行任何左移操作时数组的或值。 2.计算固定或值:在计算 allOr 的同时,计算一个 fixed 值,…...
搭建基于chrony+OpenSSL(NTS协议)多层级可信时间同步服务
1、时间同步服务的层级概念 在绝大多数IT工程师实际工作过程中,针对于局域网的时间同步,遇到最多的场景是根据实际的需求,搭建一个简单的NTP时间同步服务以时间对局域网中的服务器、网络设备、个人电脑等基础设施实现同步授时功能。虽然这样…...
虚拟内存:深入解析与性能优化
文章目录 虚拟内存的概念虚拟内存的实现方式虚拟内存的页面置换算法虚拟内存的性能影响结论 在现代计算机系统中,虚拟内存(Virtual Memory)是一种至关重要的技术,它极大地提高了系统的多任务处理能力和内存利用率。本文将深入探讨…...
元数据和主数据
元数据和主数据是数据管理中的两个关键概念,其核心区别如下: 1. 定义与本质 元数据(Metadata) “关于数据的数据”,用于描述数据的属性、结构、来源、用途等上下文信息。 示例:数据库表的字段名称、数据类型…...
JavaScript事件处理全解析:从基础到最佳实践
在现代Web开发中,事件处理是构建交互式应用的核心技术。JavaScript提供了多种事件绑定方式,每种方法都有其适用场景和特点。本文将深入探讨7种主流的事件绑定方法,通过代码示例和原理分析,帮助开发者选择最合适的解决方案。 一、…...
高级数据结构:线段树
线段树概述 线段树是一种处理区间问题的优越算法,也是算法竞赛的常客。 线段树的特点是,类似于一棵二叉树,将一个序列分解成多个区间并储存在二叉树上。 例如,把区间 [ 1 , 10 ] [1,10] [1,10]作为树的根节点,然后把…...
精讲C++四大核心特性:内联函数加速原理、auto智能推导、范围for循环与空指针进阶
前引:在C语言长达三十余年的演进历程中,每一次标准更新都在试图平衡性能与抽象、控制与安全之间的微妙关系。从C11引入的"现代C"范式开始,开发者得以在保留底层控制能力的同时,借助语言特性大幅提升代码的可维护性与安全…...
用ffmpeg压缩视频参数建议
注意:代码中的斜杠\可以删除 一、基础压缩命令(画质优先) ffmpeg -i input.mp4 \-c:v libx264 -preset slow -crf 23 \ # H.264编码,平衡速度与质量-c:a aac -b:a 128k \ # 音频压缩-vf "scaleif(gt(a,16/9),1920,-2):if(…...
uni-app学习笔记(二)--vue页面代码的构成和新建页面
vue页面的构成 一.template 模板区,主要放html布局,注意,如果是开发uni-app,模板区不要放div,h1等标签了,用了在小程序和app端起不到作用。具体应该使用哪些组件,可在uni-app官网上查看:组件-…...
机器语言程序、汇编语言程序、硬件描述语言程序、编译程序、解释程序和链接程序
程序类型定义与核心特征处理对象 / 输入输出结果所属领域典型例子 / 作用机器语言程序由二进制指令(0/1 序列)构成,可被 CPU 直接执行,与硬件架构强绑定。无(直接执行)无(直接运行)低…...
智能语音助手的未来:从交互到融合
摘要 随着人工智能技术的不断进步,智能语音助手已经成为我们生活中不可或缺的一部分。从简单的语音指令到复杂的多模态交互,语音助手正在经历一场深刻的变革。本文将探讨智能语音助手的发展历程、当前的技术瓶颈以及未来的发展方向,特别是其在…...
Redis从基础到高阶应用:核心命令解析与延迟队列、事务消息实战设计
Redis基础知识 #切换数据库 bd:0>select 2 "OK" bd:2>dbsize "0" #清空数据库 bd:0>flushdb "OK" #设置值 bd:0>set name "lyt" "OK" #查看所有key bd:0>keys *1) "name" #获取key bd:0>get …...
操作系统原理实验报告
操作系统原理课程的实验报告汇总 实验三:线程的创建与撤销 实验环境:计算机一台,内装有VC、office等软件 实验日期:2024.4.11 实验要求: 1.理解:Windows系统调用的基本概念,进程与线程的基…...
Python爬虫实战:研究nodejs aes加密
1. 引言 1.1 研究背景与意义 在当今数字化时代,Web 数据的价值日益凸显。通过爬虫技术获取公开数据并进行分析,能够为企业决策、学术研究等提供有力支持。然而,为了保护数据安全和隐私,许多网站采用了加密技术对数据进行保护,其中 AES 加密是一种常见且安全的加密算法。…...
线程的一些事(2)
在java中,线程的终止,是一种“软性”操作,必须要对应的线程配合,才能把终止落实下去 然而,系统原生的api其实还提供了,强制终止线程的操作,无论线程执行到哪,都能强行把这个线程干掉…...
基于 PostgreSQL 的 ABP vNext + ShardingCore 分库分表实战
🚀 基于 PostgreSQL 的 ABP vNext ShardingCore 分库分表实战 📑 目录 🚀 基于 PostgreSQL 的 ABP vNext ShardingCore 分库分表实战✨ 背景介绍🧱 技术选型🛠️ 环境准备✅ Docker Compose(多库 & 读…...
御网杯2025 Web,Msic,密码 WP
Web YWB_Web_xff 审计代码,发现需要$cip2.2.2.1 使用burpsuite抓包,添加X-Forwarded-For:2.2.2.1 然后得到flag YWB_Web_未授权访问 更加题目描述知道需要admin登录,但是现在是guest。 使用burpsuite抓包 发现cookie里面存在userÿ…...
tensorflow 1.x
简介 TensorFlow:2015年谷歌,支持python、C,底层是C,主要用python。支持CNN、RNN等算法,分CPU TensorFlow/GPU TensorFlow。 TensorBoard:训练中的可视化。 快捷键:shiftenter执行命令,Tab键进…...
[ERTS2012] 航天器星载软件形式化模型驱动研发 —— 对 Scade 语言本身的影响
在《从ERTS学习SCADE发展》中提到,在 ERTS 会议中,Scade团队会在该会议中介绍与Scade相关的工作。在 ERTS 2012 中,Scade 团队介绍了使用Scade作为主要工具,应用在航天器星载软件开发中的相关话题。原材料可参考 《Formal Model D…...
Spring Boot 集成 Flink CDC 实现 MySQL 到 Kafka 实时同步
Spring Boot 集成 Flink CDC 实现 MySQL 到 Kafka 实时同步 📌 项目背景 在大数据实时处理场景中,数据库变更数据的捕获与传输是关键环节。Flink CDC 提供了从 MySQL 等数据库中实时捕获数据变更的能力,并通过 Apache Flink 引擎实现流式处理。 本项目使用 Spring Boot …...
软件体系结构(Software Architecture)
文章目录 1. 分层架构(Layered Architecture)核心逻辑代码示例(伪代码)典型场景优缺点 2. 客户端-服务器(Client-Server)核心逻辑典型交互流程应用场景代码示例(RESTful API)优缺点 …...
RS485和RS232 通信配置
RS232 目前硬件上支持RS232的有以下板卡: LubanCat-5IO底板(含有RS232x2) 7.1. 引脚定义 具体的引脚定义可以参考背面的丝印 LubanCat-5IO底板 引脚定义图 7.2. 跳帽配置 LubanCat-5IO底板 鲁班买5IO底板上的RS485和RS232是共用同一组…...