游戏引擎学习第294天:增加手套
准备战斗
我们正在进行的是第294天的开发,目前暂时没有特别确定要做的内容,但我们决定继续研究移动模式相关的部分。虽然一些小型实体系统已经在运行,但并不确定最终效果如何。
今天我们决定实现一个全新的功能:战斗系统。这是游戏中的一项核心机制,此前我们只在早期简单尝试过类似“投掷剑”的效果,尚未真正地把攻击机制整合进来。现在,借助已有的“脑系统”,实现起来会更容易一些。
首先检查一下当前的状态,一切运行正常。我们角色可以跳跃、移动,但敌人蛇形实体太长了,不利于调试,因此将它们缩短以便测试。我们的目标是让主角可以攻击这些敌人。
我们已经有一些基本的实体属性,比如血量,玩家和敌人目前都有3点生命值,用小红块表示。敌人移动速度目前太快,之后需要调整。但首要任务是:让玩家可以攻击敌人。
为此,我们设计了一个机制:
- 玩家角色会有一个“魔法手套”作为攻击的实体
- 这个手套是一个独立的实体,浮动在玩家身体附近。
- 攻击通过按下方向键发起,方向决定攻击的朝向。
- 移动仍由WASD控制,方向键专用于攻击。
- 手套会在攻击时做出类似“弧线滑动”的动作,也就是一次快速的挥击。
- 不同类型的手套未来可以带来不同的攻击方式,目前先做一个基础版本。
为实现这一点,我们计划:
- 添加一个手套实体,并让它始终跟随玩家身体部分的位置,而非像头和身体那样分离运动。
- 设置该手套在不攻击时静止悬浮在身体旁边,攻击时执行快速的滑动动作。
- 可能加入模糊效果或粒子特效,表现出手套滑动时的“轨迹”或“能量感”。
- 后续还可通过更换手套种类带来不同视觉风格或攻击效果。
我们在程序中会从 AddPlayer
函数开始着手,为角色添加这个手套实体。这是整个战斗系统开发的第一步。
总之,今天的目标是实现攻击系统的雏形,为后续的战斗逻辑、碰撞检测和动画表现打下基础。通过这个“魔法手套”,玩家将能主动参与战斗,打击敌人,并进一步推动游戏机制的完善。
game_world_mode.cpp:让 AddPlayer 初始化一个手套并调用 AddPiece 添加它
我们现在要实现“魔法手套”实体,并且可以看到代码中还保留了许多之前的残余设置,比如碰撞器(Collider)相关的标志位。这些旧的标志和碰撞逻辑现在已经到了该被重新整理和规范的时候。
目前的碰撞系统有些混乱,比如有些实体设置了碰撞器却不会参与碰撞,这种设定可能本身就是冗余的。如果实体不会发生碰撞,那它压根就不需要一个碰撞组件。而如果存在某些特殊情况,比如一个实体只在另一方有碰撞标志时才参与碰撞,可能又需要一种更复杂的双向判断逻辑。
不过现在我们已经具备了足够多的系统组件和实体行为逻辑,可以正式对这些碰撞标志的意义做出清晰定义,并决定哪些是必须保留的,哪些可以舍弃。
接下来,手套作为新的实体需要被添加进游戏世界中,并且被指定一个“思维槽位”(brain slot),它的类型为“Hero Glove”,并且会与主角共享一个脑ID,表示它是由主角的逻辑系统控制的。
除了设置基本的实体参数之外,我们还需要为它指定一个图形资源。我们检查了一下现有的素材资源包,在 file formats
中找到了一些可以使用的资源:比如影子(shadow)、树(tree)、石头(rock)、草丛(grass_tuft)、地砖(stone)、头部(head)、披风(cape)、字体(font)等等。
虽然理想状态下我们希望使用一个专属的手套资源,但在没有专用资源的情况下,临时用“石头”图形来代替手套。这其实是之前我们也用过的一个方法,所以目前继续采用。我们会复制一份“石头”资源,让它作为手套的临时代替物。
完成资源指定之后,我们将这个“手套实体”生成在主角附近,处于主角身体旁边,并浮在地面以上,具体高度和悬浮逻辑后续再调整。
至此,英雄的手套实体已经创建完毕,具备独立的逻辑控制能力、可以参与碰撞系统(或不参与,取决于最终定义),并带有图形资源用于可视化展示。接下来还需要进一步完善攻击时的动作表现和手套与主角之间的动态位置同步逻辑。
game_brain.h:在 brain_hero 中添加手套
我们需要在大脑系统(brain system)中为手套预留一个槽位,因为主角需要能够“感知”自己的手套。手套的行为应该由主角的逻辑控制器统一管理,因此手套必须作为主角的一个附属组件存在于其大脑逻辑结构中。
接下来也注意到了碰撞相关的处理。当前有一类叫做“身体碰撞”(body collision)的设置,暗示手套或其他部位可能会与游戏世界发生物理交互。我们需要明确这些实体的碰撞属性,比如:
- 手套是否参与与怪物的碰撞;
- 手套是否与主角自身发生碰撞;
- 手套攻击时是否临时开启碰撞检测;
- 是否手套的碰撞逻辑只在攻击动作执行时才生效。
这一切意味着我们需要在设计碰撞逻辑时更加细致,可能为不同类型的碰撞行为设置更明确的标志和状态条件。例如:
- 正常状态下手套漂浮在主角身边,不参与任何碰撞;
- 攻击触发时,临时启用其与敌人的碰撞检测,用以判断是否命中;
- 攻击结束后,重新关闭碰撞状态,回到普通漂浮逻辑。
总之,现在是理清和统一碰撞系统的绝佳时机,确保逻辑清晰、实体行为一致。手套不仅仅是一个视觉效果的装饰物,它还将成为游戏中重要的战斗工具,必须在逻辑上与主角行为严密绑定,并具备独立交互能力。
game_world_mode.cpp:让 PlayWorld 设置手套不发生碰撞
我们现在还不确定手套的碰撞行为会如何发挥作用,因此目前先以一个占位实体(dummy)来处理,后续再根据实际需要进行完善。我们之所以需要这样做,是因为最终必须能够判断手套是否与其他对象发生了碰撞,比如敌人,这对于攻击逻辑非常关键。
系统中有一项设计约束:在构建实体时,资源注册(例如图形资源)必须按照相同的顺序进行“开启”和“关闭”。我们之前为此添加了一条断言(assertion),很好地帮助我们捕捉到了顺序不一致的问题,否则这个容易被忽略的细节可能导致运行时异常。也就是说,在创建手套实体的时候,资源注册必须是成对对称进行的,先打开、再关闭,顺序必须一致。
接着我们检查了当前是否正确加载了实体资源。发现手套目前设置的图形资源是“rock”(岩石),但似乎该资源并不存在,导致无法显示。于是我们尝试改用“sword”(剑)资源来代替。初步加载后发现显示的剑太大了,显然不适合作为手套的表现资源,所以我们调整其显示大小,使之更合理,最终成功让一个代表手套的图形出现在主角旁边。
此时,虽然图形上看起来只是一个“愚蠢的石头”在漂浮,但它已经作为手套的初步实体被创建出来并显示出来了。下一步,我们的目标是让这个手套实体能跟随主角的位置,并在我们按下方向键时执行“挥击”动作。
这意味着我们要:
- 在游戏逻辑中让手套保持和主角同步的相对位置;
- 监听方向输入,在接收到攻击命令时,驱动手套执行一次带有动画的挥击行为;
- 同时配合碰撞检测判断是否击中目标。
这是我们正式开始实现手套攻击系统的关键一步。
game_brain.cpp:让 ExecuteBrain 将手套放置在英雄身体旁边
我们需要为英雄的大脑(brain)系统添加逻辑,让手套根据英雄的朝向正确地出现在身体旁边。具体来说,我们需要在 game_brain.cpp
的 ExecuteBrain
函数中,对手套的位置进行设置,使其随着英雄移动并保持在身体某个固定偏移的位置。
首先,在大脑逻辑中,我们可以通过实体部分(entity parts)获取与英雄关联的手套实体。如果存在手套实体,我们就可以对其进行位置更新。
我们的做法是:当英雄存在手套时,查询身体(body)的位置,并基于该位置设置手套的位置。手套应当相对身体保持一个固定的偏移。例如:
gloveP = bodyP + V3(0.5f, 0.0f, 0.5f);
初始设置中,我们将手套放在身体的右上方。偏移暂时为常数,只为了让手套在视觉上出现在合理位置。之后,我们希望该偏移能根据角色的朝向方向(facing direction)动态变化,从而使手套始终出现在角色面朝的那一侧。
目前,角色的朝向是以一个标量角度(angle)表示的。在逻辑中,我们可以直接使用这个角度,通过正余弦函数来计算偏移方向,从而实现让手套根据角度进行位置调整。这种方式可以让手套在角色旋转时自然地环绕主角,比如从右侧顺时针转到左侧时,手套会动态地随着朝向变化而移动。
测试中,我们先使用固定偏移值,观察手套是否随角色位置移动,确保基本逻辑可行。结果显示:手套实体能够跟随身体实体同步移动,并保持在身体一侧,位置也基本合理。如果我们日后替换为真正的手套图形资源,它将漂浮在主角旁边,看起来会更加自然。
总结目前阶段完成的内容:
- 在大脑系统中添加了对手套实体的控制逻辑;
- 获取主角朝向和身体位置;
- 使用固定偏移将手套放置在身体一侧;
- 验证了手套实体随主角移动而漂浮的效果;
- 为后续实现基于朝向角度的动态偏移打下基础。
接下来计划:将固定偏移改为根据朝向角度自动计算,并逐步实现挥击动作逻辑。
game_brain.cpp:让 ExecuteBrain 根据英雄的朝向偏移手套位置
我们当前的目标是让手套根据角色的朝向自动出现在正确的位置。为实现这一点,我们从角色当前的朝向角度出发,构建一个面向该角度的向量,并将其用于计算手套在 XY 平面上的偏移量。我们计划将这个偏移向量加到身体位置上,再加上一个用于高度浮动的 Z 轴偏移量,从而确定手套的三维位置。
目前角色的朝向是一个标量角度值(以弧度计),我们已经有了一个用于从该角度构造单位向量的方法。具体做法如下:
- 获取角色身体的朝向角度;
- 使用三角函数计算出这个角度所对应的二维单位向量(
cos(angle), sin(angle)
),这个向量指向角色面对的方向; - 使用这个单位向量乘以一个长度值(如 0.6)来作为手套的水平方向偏移;
- 垂直方向(Z 轴)上的偏移仍需确定,目前尚未最终确定 Z 轴渲染和深度处理的方案。
在 Z 轴问题上,我们之前反复调整了许多次深度偏移规则和渲染顺序,但始终没有形成最终规范。现在借由“手套”的例子,正好是推动我们彻底完成 Z 高度和碰撞系统设计的好时机。我们可以把这两个长期悬而未决的核心系统——Z轴定位规范和碰撞检测系统——作为当前阶段的开发重点,彻底定下来。
于是我们接下来实现了两个变量 armX
和 armY
,它们代表手套相对于角色朝向的横向偏移。这种做法允许我们根据朝向自动调整手套在角色身体的哪一边。
总结本阶段关键进展:
- 从角色朝向角度生成面向向量;
- 利用该向量计算手套的 XY 平面偏移;
- 计划结合 Z 轴偏移,使手套在 3D 空间内正确悬浮;
- 意识到当前碰撞系统和 Z 渲染系统尚未完善,并决定以此为契机推进这两个系统的最终确定;
- 为手套系统引入了一个与角度关联的动态偏移逻辑,后续可以结合动画或交互进行拓展。
下一步将继续完善 Z 轴处理逻辑,并将碰撞系统与实体状态更好地结合。
之前忘记加2Pi
黑板讲解:计算相对于朝向角度的角度
我们正在实现一个系统,让手套可以根据角色的朝向自动出现在其身体的侧面。为此,我们首先明确了角色朝向角度的表示方式。我们使用标准的弧度制将角色面朝的方向表示为一个角度,其中:
- 面朝正右为 0 弧度(0 度);
- 面朝正上为 π/2 弧度(90 度);
- 面朝正左为 π 弧度(180 度);
- 面朝正下为 3π/2 弧度(270 度);
- 也可以用 τ(tau,等于 2π)来表示整圆,以 1/4 τ、1/2 τ、3/4 τ 来标记四个象限。
为了使手套始终位于角色身体的一侧(例如角色的右手边),我们不能直接使用角色的面朝方向来决定手套的偏移方向。因为直接使用当前朝向会让手套出现在面朝的正前方,而这不是我们想要的效果。
为了解决这个问题,我们将角色当前朝向角度加上 3/4 τ(即 270 度),这个角度偏移可以把手套从正前方转移到角色的右侧位置。
验证方法如下:
- 首先关闭这个角度修正,可以看到手套出现在角色正前方;
- 添加 3/4 τ 的偏移后,手套会自动移到角色的右边,不管角色转向哪个方向,手套始终在其一侧。
然后我们调整了手套的距离。默认情况下,单位偏移长度是 1 个单位,但这让手套看起来距离角色太远了。因此我们将其调整为约 0.5 个单位,让手套更贴近角色身体,使视觉上更加合理。
总结本阶段的关键内容:
- 使用弧度表示角色朝向,并以 τ 为单位对角度进行统一理解;
- 通过加上 3/4 τ 的方式,让手套始终出现在角色的右侧;
- 视觉验证了修正是否正确;
- 进一步优化偏移长度,让手套距离角色合适,不显得突兀。
下一步可能会继续细化这个偏移逻辑,例如根据角色的动画状态或动作动态调整位置,同时也可以引入高度的 Z 偏移。
运行游戏并看到“手套”出现
现在我们已经实现了让“手套”(目前是一个临时用的奇怪岩石模型)固定在角色身体侧边的位置,并随着角色的朝向变化而同步调整其相对位置。随着角色移动或旋转,手套的位置也会随之更新,效果上已经达到了“装备在身侧”的视觉需求。
下一步我们要实现的是让这个“手套”能够进行挥击攻击——即进行一个弧形的攻击动作,并且需要实现与其他物体的碰撞检测,判断是否击中了目标,例如角色面前的某个敌人或物体。
我们已经具备了一些基础数据来支持这一功能。当前在系统中已经存在与武器使用方向相关的输入数据,之前为剑攻击设定的方向被称为 dSword
,表示玩家在特定时刻试图攻击的方向。这一变量可以用于确定当前的挥击方向,也能帮助我们得知角色当下的朝向。在设置角色朝向时,我们已经将该方向输入转换为角度,并用于计算朝向向量。
不过,这里存在一个潜在的低效处理方式:我们是先通过 atan2
将方向向量转换为角度(用于处理角色面朝方向),然后在使用该角度时又使用 cos
和 sin
重新将其转换回向量。也就是说,方向数据从向量变为角度后又还原为向量,形成不必要的重复计算。但由于当前主要聚焦在功能实现上,这个效率问题暂时不会处理。
总之,当前阶段我们得到了如下关键点:
- 已实现手套模型跟随角色并根据朝向摆放;
- 准备实现攻击时的挥击动作;
dSword
变量记录了攻击输入的方向,可用于驱动挥击行为;- 已知角色当前朝向并能进行角度计算;
- 后续需要实现碰撞系统来判断是否击中敌人或物体;
- 存在一定的角度与向量来回转换的低效逻辑,未来可优化。
下一步的工作包括构建一个真实的攻击轨迹,并在攻击过程中实时检测碰撞,判断是否命中目标实体。这样角色挥动“手套”时才能造成有效打击反馈。
game_brain.h:为 entity_movement_mode 添加 MovementMode_AttackSwipe(攻击挥击模式)
我们现在要实现一个“挥击”动作,也就是控制手套进行一次攻击性地挥动。这是一个与角色运动模式(movement mode)相关的行为,类似于之前已有的“跳跃”机制,但这次是“攻击”或“挥击”而非移动穿越区域。
我们已经定义了实体的“运动模式”,接下来计划增加一种新的模式,比如叫做 swipe
或 attack
,用于表示当前处于攻击动作中。在进入这种状态后,手套将在一段时间内锁定,不能立即重新挥击,也不能改变攻击方向,直到当前的攻击动作自然完成。这种设计避免了玩家快速连击造成的不自然视觉效果,并符合实际战斗中的“攻击需要时间”这一设定。
为了实现这一机制,我们准备将手套实体设置成攻击模式。期间,该手套将执行一个弧形轨迹的挥击动作。虽然它不像跳跃那样会占用新的空间区域或移动位置,但依然需要类似的逻辑处理来驱动动作流程。
此外,我们还提到了一个结构性改动的可能性。目前处理运动模式逻辑的相关代码可能集中在某些特定的地方(如 brain
模块),但其实这些逻辑本质上属于通用的实体行为,完全可以被提取到通用的实体模拟代码中,比如 simulate_entity
。这样做可以统一实体行为管理逻辑,提高代码模块化程度,使后续添加更多动作模式(如游泳、攀爬、翻滚等)时更加容易。
尽管当前还未决定是否进行这个重构,但明确这一方向是为了后续开发的清晰性和可扩展性。
总结当前核心内容:
- 准备新增一种实体运动模式用于挥击攻击;
- 挥击动作为一个时间段内的弧形运动,过程中无法再次挥击或改变方向;
- 攻击行为与跳跃机制类似,受限于动作时间;
- 当前的代码可能迁移至通用的实体逻辑处理区,以实现更好的架构分离;
- 接下来将开始具体实现该挥击行为的轨迹模拟与状态控制。
game_world_mode.cpp:在 UpdateAndRenderWorld 中实现 AttackSwipe
现在我们为实体增加了一种新的运动模式,即“攻击挥击模式”(attack swipe mode)。在这模式中,实体(此处为手套)执行一次攻击性动作,该动作类似跳跃模式的处理流程,即在特定的运动周期结束后返回到默认的“站立模式”(planted)。
整个逻辑设计如下:
-
进入挥击模式:
一旦某个条件触发攻击(例如玩家按键操作),我们将实体的 movement mode 设置为attack_swipe
,进入挥击状态。 -
处理挥击状态:
当实体处于attack_swipe
模式时,会执行一个特殊的轨迹行为。这类似跳跃的处理流程,即:- 判断是否仍处于攻击动作期间(通过某种参数化方法确定,类似 T movement phase);
- 若攻击动作完成,则将 movement mode 设置回
planted
,即恢复为静止状态; - 若仍在攻击中,则继续执行挥击轨迹更新逻辑。
-
参数化攻击轨迹:
现在我们需要一种机制来定义挥击动作的轨迹参数。也就是说,挥击应该遵循某种“弧线”或“圆弧路径”,而不是线性移动。这需要设计一套参数,用来描述当前挥击处于整个动作的哪一阶段(例如起始点、中间、结束点等),以便正确地更新实体位置。 -
“came_from”参数问题:
提到了当前系统中某些参数,比如came_from
可能已经不再合适。因为攻击动作本身并不是“从一个地方跳到另一个地方”的移动逻辑,而是一种“原地弧形运动”,因此像came_from
这样描述位置变更的属性,在此上下文下可能不适用,未来可能考虑移除或替换。 -
轨迹实现策略:
下一步关键任务就是确定如何参数化挥击轨迹,比如:- 使用角度与半径定义圆弧路径;
- 根据时间线性或非线性插值生成挥击轨迹;
- 将挥击路径的控制权交由一个专用函数,根据当前“攻击帧”或“进度”来更新位置;
- 与碰撞检测系统集成,在轨迹范围内感知是否打中了敌人或其他对象。
总结要点:
- 添加了新的
attack_swipe
运动模式; - 模式内执行一次弧形运动并限制再次发起攻击;
- 动作结束后重置为
planted
模式; - 正在设计参数化的攻击轨迹机制;
- 某些旧的状态参数(如 came_from)已不再适用;
- 下一步是实现轨迹模拟与碰撞响应联动机制。
黑板讲解:参数化挥击弧线
我们现在要实现的是手套进行左右交替的挥击动作,即每次攻击时,手套从身体一侧划向另一侧,下一次攻击时再从另一侧划回来,形成“左-右-左-右”的攻击节奏。
当前的处理方式是:
-
明确动作方向与方式:
攻击挥击是一种围绕角色身体某个轴心(可能是身体中心)的相对空间的旋转运动。因此手套的运动不是简单的线性移动,而是围绕主角进行的一段“弧形插值运动”。 -
定义左右切换:
每次攻击应该记录当前挥击是向左还是向右,并在下次攻击时自动切换方向,从而产生交替的挥击效果。这个逻辑将形成一个简单的状态切换系统:- 初始状态为向一侧挥击;
- 每次攻击完成后将方向状态切换;
- 下次攻击根据该状态决定挥击路径。
-
参数化角度插值:
在攻击挥击模式中,使用一个时间因子tMovement
来控制动作进度,从 0 到 1 表示一次挥击的全过程。使用该进度值去插值起始角度和结束角度,从而形成顺滑的旋转轨迹。例如:angle = Lerp(startAngle, endAngle, tMovement);
然后用这个角度再通过正余弦计算出手套当前应该处于的偏移向量:
offset.x = cos(angle) * radius; offset.y = sin(angle) * radius;
-
保持可复用性:
虽然这种运动轨迹和逻辑可以抽象为一个可重用的攻击动作模块,但当前还是优先选择直接硬编码实现逻辑,之后再逐步提取出通用机制。这种做法可以快速验证效果,也便于在视觉表现上调试优化。 -
后续扩展方向:
- 可以为每次攻击设定一个
swipeSide
状态,控制是向左挥还是向右挥; - 还可以引入速度调节参数,影响插值速率;
- 进一步整合碰撞检测模块,在挥击路径上检测是否命中目标;
- 将该系统封装为组件或行为模块,供其他实体使用。
- 可以为每次攻击设定一个
总的来说:
- 挥击动作采用角度插值控制;
- 每次攻击方向左右交替;
- 插值角度用来生成当前帧的偏移;
- 当前阶段使用硬编码快速实现;
- 未来考虑封装为通用攻击组件。
game_entity.h:为实体添加 SwipeAngleStart 和 SwipeAngleTarget
在实体的移动机制中,会包含关于挥击动作的角度参数,比如挥击的起始角度(swipe angle start)和目标角度(swipe angle target)。在挥击过程中,角色的朝向是固定的,即使玩家按了不同的方向键,朝向也不会发生变化,保证挥击动作的连贯性和稳定性。
挥击角度会在起始角度和目标角度之间插值,形成从一侧到另一侧的平滑挥动效果。这样设计的目的是让挥击动作既明确又流畅,不会因为输入变化导致挥击姿势混乱。
虽然目前对代码结构和放置位置还没有完全确定,整体思路是让挥击相关的代码随着开发进展逐步自然地稳定下来,便于后续维护和调整。当前还在观察和评估这种设计是否合理,以及是否适合敌人或其他实体的行为逻辑。
总结来说:
- 挥击的起始角度和目标角度作为参数存在;
- 角色朝向在挥击过程中保持不变,确保动作稳定;
- 挥击角度在两个值之间平滑过渡;
- 代码结构和设计仍在不断调整和完善中,追求合理的代码组织和复用;
- 设计过程带有一定试错和反复优化的性质。
game_entity.h:为 entity_movement_mode 添加 MovementMode_AngleOffset 和 MovementMode_AngleAttackSwipe
我们考虑用攻击挥击模式来控制手臂的位置,设计一个相对于角色朝向的角度偏移和挥击角度。这个挥击角度会基于当前面向方向,通过插值逐渐变化,实现挥击动作的平滑过渡。
具体来说,我们会设置一个“角度偏移”的移动模式,作为手臂(比如手套)的初始定位方式。手臂的朝向默认和身体的朝向保持一致,这样手臂总是跟随身体方向对齐。在攻击挥击阶段,会将手臂的当前角度从起始角度插值到目标角度,通过一个参数(比如tMovement)来控制插值进度。
实现上,手臂实体的位置(entity P)是基于身体的位置再加上根据当前朝向和角度偏移计算出来的相对位移。这个位移会动态调整,反映挥击动作的弧线轨迹。
挥击动作期间,手臂的朝向不会随玩家按键方向即时改变,直到挥击动作完成才允许朝向更新,这避免了动作中断或方向混乱。
挥击结束后,移动模式会从“攻击挥击”切换回“角度偏移”,保证手臂回归到身体的基础位置和状态。同时,挥击角度的起点和终点会根据左手或右手交替切换,实现左右手交替挥击的效果。
此外,系统会在实体创建时初始化手臂的移动模式和起始角度,保证手臂在初始时正确摆放。
整体设计上,我们先写出明确、具体的代码逻辑,后续再优化成更加模块化和复用性强的形式。在这个过程中,需要合理管理插值参数(tMovement),负责更新挥击进度,确保动作连贯。
总结要点:
- 设定“角度偏移”作为手臂的基础移动模式;
- 手臂朝向默认与身体朝向一致,攻击时保持朝向固定;
- 通过插值参数实现挥击角度从起始到目标的平滑过渡;
- 挥击完成后,切换回基础移动模式,手臂回归默认位置;
- 初始化阶段设定手臂的移动模式和起始角度,保证初始状态正确;
- 挥击左右手交替切换,增加动作丰富度;
- 插值参数tMovement由系统周期性更新,控制挥击进度;
- 设计先写明确代码,后续逐步重构成复用代码。
game_brain.cpp:在 ExecuteBrain 中提供启动攻击的功能
我们要实现 D 剑的攻击发起流程。我们进入大脑逻辑中,在进行 D 剑初始化时,首先设置 attacked = false
。接下来,在攻击发生的逻辑中,如果真的进行了攻击,我们就把 attacked
设置为 true
。在攻击开始时,我们需要设置朝向的方向。
但考虑之后,我们发现设置朝向的更合适位置是在攻击真正发生之后。这是因为我们总是应该以“头部”的朝向为准。因此,逻辑上应该是这样的:如果尝试进行攻击,我们首先检查“手套”的运动模式是否是 angle offset
模式。如果不是该模式,我们就把 attacked
设置为 false
,也就是说攻击并未发生。
具体来说,如果当前没有装备手套、手套处于静止状态,或者手套无法攻击,那么就表示攻击没有发生。只有在手套具备攻击能力的前提下,攻击才真正成立。这也意味着 attacked = true
的前提是我们有手套并且它处于可攻击状态。
当攻击真正进入执行阶段后,我们再设置朝向方向。如果我们在攻击过程中,并且我们拥有一个手套,而且攻击确实发生了,那么我们就把手套的运动模式设置为一个与角度相关的偏移模式(angle swipe 之类的)。我们还要设定起始角度为当前角度(虽然变量名不是叫 current angle
,但含义是如此)。
然后我们需要设定目标角度。这个目标角度可能是 75 或 -25,具体取决于攻击方向。考虑到方向的连贯性,如果我们从 25 转向 -25,看起来会更加合理,因此我们设定起始角度为 25,目标角度为 -25。
但这也依赖于手套当前所处的位置。如果手套在角色左侧,那它应该向右挥动;如果在右侧,则应向左挥动。因此我们需要判断手套当前的角度是处于 0 的哪一侧,然后根据结果选择目标角度是 -25 还是 25,从而实现从当前方向向相反方向的攻击动作。这样可以让手套从一侧扫向另一侧,形成连贯的攻击动画与逻辑。
为了方便调试把随机偏移去掉
奇怪不对 得把break去掉
运行游戏并尝试挥击攻击
我们正在尝试实现预期的攻击机制。不过目前还存在一些问题和细节需要调整。
首先,攻击动作的速度太慢了,所以我们需要大幅提升挥击的速度。进入 movement mode
的相关配置后,找到攻击挥击(swipe)的部分,将其速度调快,调整后效果明显改善,动作更加迅速流畅。甚至可能还需要再稍微加快一点,最后找到了一个比较合适的速度,使得攻击效果符合预期。
接下来,我们注意到挥击动作本身虽然不错,但还有一个小问题:攻击时拳头没有足够地向前伸出,显得力度不足。为了提升视觉和手感效果,我们打算在挥击时加入一些手臂的伸展动作。
这个实现起来相对简单,因为我们已经有一个“位移(displacement)”的概念用于描述部位运动范围。虽然挥击动作和位移不是完全相同的概念,但可以利用已有的结构来扩展动作效果。具体做法是,在手套执行挥击动作时,通过在实体内部增加位移相关的逻辑,让手臂在攻击过程中有一个自然的前伸,这样拳击动作会显得更有力、更真实。
另外,这部分逻辑我们原本就需要在实体中进行处理,所以在已有的实体控制逻辑中添加手臂扩展的运动会非常自然,并不会破坏原有结构。最终可以通过调整位移参数与挥击角度结合,实现一个既快速又有冲击力的攻击动作。整体效果更接近预期的“出拳”表现。
game_entity.h:为实体添加 AngleCurrentDistance、AngleBaseDistance 和 AngleSwipeDistance
我们现在需要引入一个基于角度的距离控制机制,比如可以命名为 angle_based_distance
或 angle_swipe_distance
。这样可以使手套在进行挥击动作时,不仅仅是角度发生变化,同时也在距离上有所位移,形成一种沿角度轨迹的外伸或回收动作,使攻击显得更加真实自然。
为实现这一点,我们还需要一个变量来记录“当前距离”,可以称为 current_distance
。这就允许我们实时控制挥击动作过程中手套与角色本体之间的距离变化。
比如,当前硬编码为 0.5 的这个距离值,其实应该被替换成 entity_angle_current_distance
,即实体当前的极坐标距离值。整个系统的运动实际上就是在使用极坐标系进行控制——一个角度配合一个距离值,决定了手套在空间中的位置。
这种做法的好处是可以通过角度与距离的组合实现手臂的自然伸缩与旋转,不再局限于单纯角度的旋转变化。我们可以控制挥击过程中手套的“远近”状态,例如:攻击开始时手套略微向前延伸,攻击结束时再收回,这样能增强攻击的动态表现。
整体逻辑如下:
- 定义
angle_swipe_distance
来设定攻击动作时手套的最大位移范围。 - 引入
current_distance
用于在动画过程中实时更新手套的实际位置。 - 用极坐标的方式,结合角度与距离,动态计算手套位置,实现更自然的出拳动作。
这种方式提供了更高的控制精度与更强的表现力,适用于构建更加丰富、生动的攻击动画。
game_world_mode.cpp:让 UpdateAndRenderWorld 参数化挥击弧线
我们正在实现一种基于角度的挥击距离系统,用于控制攻击动作中手套的前后位移效果。其核心目标是模拟出拳过程中手臂从身体伸出再收回的动态过程。
我们所定义的 entity_angle_current_distance
表示当前手套距离角色身体的实际位置,单位可为某种比例或世界坐标长度。这实际上表示的是手套从角色身体延伸出去的程度。
我们希望这种距离变化呈现出一种先增大后减小的自然曲线,也就是攻击时先向外推出,完成攻击后再回收。因此我们不会使用简单的线性插值(lerp),而是采用类似“抛物线”或“三角函数”的插值方式。可以称之为“parabola interpolation”或类似的命名方式。其基本逻辑是根据攻击动画的时间参数 T,在最中间阶段达到最大距离,两端则逐渐减小,形成攻击动作的自然起伏感。
parabola(抛物线)
在实现细节上,这个插值函数将接收以下参数:
- 当前时间 T(从 0 到 1)
- 起始距离(base distance)
- 最大挥击距离(swipe distance)
函数的输出就是 entity_angle_current_distance
,表示在动画进行到某一时刻时手套应当处于多远的位置。插值方式会在动画中段达到峰值,然后逐渐返回原始距离。
然后我们还需要在初始化角色时,设置好这些关键参数:
angle_base_distance
:初始默认的手套距离,比如设置为 0.5,表示静止状态下手套稍微前伸;angle_swipe_distance
:攻击时的最大手套伸展距离,比如设置为 1.0,表示在出拳时手套会完全向前推出;angle_current_distance
:当前手套与角色之间的实时距离,初始化时也设为 0.5。
通过这种方式,我们实现了手套在攻击动作中的平滑、动态位移,不再只是角度的改变,而是结合了“角度+距离”双维度的极坐标运动模型,使出拳动作更加真实有力。这个结构也为后续更多复杂的动作提供了扩展基础。
game_math.h:引入 Sin01
我们现在需要一个“抛物线”函数(parabola function),或者说是某种非线性插值函数,用于控制动画中的插值效果。尽管不一定非要使用严格意义上的抛物线函数,但我们希望实现一种“中间值大、两端值小”的插值曲线,使攻击动作中的手臂或手套能够在中段伸展最远,开始和结束阶段则较近,从而更具自然感。
我们注意到之前的做法是直接在插值中嵌入非线性逻辑,但这样不够通用。因此更好的方式是:对 T
值(时间进度,范围为 0 到 1)进行一个“形状变换”(mapping),将其变成一个经过加工的 T,再传给现有的 lerp
插值函数。
也就是说,我们可以将原本的:
lerp(start, end, T)
改为:
lerp(start, end, mappedT)
其中 mappedT = parabolaMap(T)
。这样就可以通用于所有 lerp
类型的函数,无论它们是对标量、向量还是其他结构进行插值,也无需为不同数据类型再编写新的插值逻辑函数。
这种 parabolaMap(T)
函数的目标是在 T 为 0 或 1 时返回 0,在 T 为 0.5 时返回 1,构造一个标准的“山峰型”插值曲线。一个简单的实现方式可以是:
parabolaMap(T) = 4 * T * (1 - T)
这个表达式在 T 为 0.5 时达到最大值 1,T 趋近于 0 或 1 时值为 0,形成一个对称的拱形曲线,效果类似于抛物线。
这样处理后,只要我们在插值前用这个函数处理 T,就可以让任何位置、角度、缩放等属性的插值具有动态起伏感。例如:
let curvedT = parabolaMap(T)
let result = lerp(startValue, endValue, curvedT)
通过这种方式,我们增强了插值逻辑的表现力,同时保持了接口的一致性和函数的通用性,不必为不同类型重写插值函数。最终用于控制攻击中手套的前伸和回缩,使动作显得更自然,更具物理感。
黑板讲解:余弦弧线
我们正在调整挥击动作中的非线性插值函数,以使手套(或手臂)从身体出发时形成更自然的“伸出—回收”运动。最初尝试使用抛物线形式来实现这种插值,但后来意识到抛物线并不一定最合适,可能更适合使用归一化后的余弦或正弦函数曲线。
目标是生成一个插值曲线,其行为如下:
- 在 T=0 和 T=1 时返回 0(即手套靠近身体)
- 在 T=0.5 时返回 1(即手套完全伸出)
- 整体曲线呈现平滑拱形,即先快后慢地伸出,再慢后快地回收
于是我们尝试使用余弦函数构建一个归一化的曲线,即将 T
从区间 [0,1]
映射为余弦或正弦函数的一个完整半周期。初始想法是使用类似:
mappedT = cos(T * π)
但发现方向搞反了,因为 cos(0) = 1
,而 cos(π) = -1
,这并不能满足我们希望 T=0 和 T=1 对应 0 的要求。后来进一步调整,意识到其实更符合预期的是一个标准的正弦曲线的前半段,即:
mappedT = sin(T * π)
这样当:
T = 0
时,sin(0) = 0
T = 0.5
时,sin(π/2) = 1
T = 1
时,sin(π) = 0
完全满足我们需要的插值特性。
随后我们对这个函数进行了测试。首先尝试让函数始终返回 0,观察系统是否固定在最靠近身体的位置(0.5),结果正常;再尝试让函数始终返回 1,观察是否固定在完全伸出位置(1.0),也正常。这表明插值系统整体逻辑没有问题,问题出在映射函数本身的定义上。
确认用 sin(T * π)
后,插值结果就符合预期了:从靠近身体位置缓慢地向外伸出,到达中点后再缓慢回收,形成一个对称平滑的运动轨迹。
最终的插值形式如下:
let curvedT = sin(T * π)
let result = lerp(baseDistance, swipeDistance, curvedT)
通过这种结构,我们构建出了一个具有自然起伏节奏的攻击动作。手套运动不再是线性推进和回收,而是呈现真实的惯性和弹性节奏,显著增强了动画的流畅性和表现力。这个函数也具有良好的通用性,可用于其他需要动态缓进缓出的插值场景。
运行游戏并测试挥击效果
我们当前对挥击动画中的插值函数进行了进一步的调整和思考。在上一阶段中,我们采用了一个正弦函数 sin(t * π)
来控制手套从身体伸出再收回的动态过程,该函数具有理想的0-1-0曲线,能够很好地控制动作的自然节奏。
但我们发现这个函数虽然行为正确,但有几个问题需要进一步处理:
-
命名不准确:虽然实现是正弦函数,但命名还叫 “parabola”,这显然不符合实际,因此我们打算重命名为更准确的名称,比如
sine01
或类似表示“从0到1的正弦曲线”的名称。 -
性能考虑:正弦函数相比抛物线函数在计算上开销更大。在性能敏感的环境下,使用类似抛物线的函数(如
4 * t * (1 - t)
)可能更合适,虽然运动曲线略有不同。 -
动作幅度问题:目前设定的 baseDistance(基础距离)是 0.5,swipeDistance(最大挥击距离)是 1.0,插值区间是从0.5到1.0,但我们观察到动画动作看起来可能还不够有张力或者太平缓。因此我们考虑调整 baseDistance 更低,比如 0.3,使得挥击动作更明显。同时根据不同手套的特性,还可以个别设置 swipeDistance,比如一些手套有更大的挥击范围。
-
参数归属问题:这些参数(baseDistance、swipeDistance、currentDistance)应该归属于实体(entity)本身,而不是硬编码或全局共享。因为不同手套或者不同角色可能有各自不同的攻击属性,所以更合理的做法是将这些参数作为实体的成员变量进行设置和管理。
-
下一阶段:碰撞检测
我们的逻辑部分已经差不多完成,下一步计划是将碰撞检测系统与手套的运动轨迹挂钩。这样,当手套挥动时可以检测是否与敌人、障碍物等发生接触,并触发对应的攻击、命中反馈等逻辑。 -
玩法层面的思考:
我们注意到这个系统的设计本身就带来一些有趣的策略性。在此游戏中,攻击动作(手套)和角色的移动方向是分离的,也就是说,玩家攻击时需要考虑头部和身体的位置关系与朝向,这与传统动作游戏有所不同。这种机制增加了操作复杂度和策略性,会对战斗节奏产生独特影响。 -
未来可拓展性:
在目前系统的基础上,我们还可以加入更多细节和表现力,例如:- 手套挥击时浮动动作(比如“上下 bob 动”)
- 根据不同手套类型设置不同的插值函数(更窄/更宽的攻击轨迹)
- 更复杂的轨迹曲线(例如三角波、ease-in-ease-out、弹性曲线等)
综上,目前系统基础已构建完成,后续将继续围绕碰撞检测与更丰富的动画表现展开,进一步提高游戏动作的响应感与沉浸感。
game_math.h:引入 Triangle01(三角形插值函数)
我们希望实现一个更加简单且高效的函数,用于控制挥击动作的插值过程。这次我们尝试的是一个 线性的三角函数(Triangle Wave),用于在 t
从 0 变化到 1 的过程中,创建一个先上升后下降的过渡曲线。
具体思路如下:
-
目标行为
- 当
t = 0
时,结果是 0。 - 当
t = 0.5
时,结果是 1(达到峰值)。 - 当
t = 1
时,结果又变回 0。 - 中间插值以线性方式进行,不需要正弦或抛物线。
- 当
-
实现方式
-
首先将
t
乘以 2,这样就得到了一个从 0 到 2 的值:result = t * 2;
-
然后判断这个值是否超过 1,如果超过 1,说明已经处于下降阶段,我们就用
2 - result
得到镜像值,从而形成对称三角波:if (result > 1) {result = 2 - result; }
-
最终这个
result
就是我们要插入 lerp 的t
值,使得插值先升再降。
-
-
优点
- 相比正弦或抛物线函数,这种实现方式计算更快,不涉及三角函数。
- 行为明确且简单,峰值固定在中间,便于控制动画节奏。
- 可用于任何需要“起伏”样式的运动轨迹,比如伸手、攻击、弹跳等。
-
应用场景
- 在手套挥击动作中作为当前距离的插值控制函数。
- 可被泛化使用在其它周期性或对称性变化的动画中。
- 与 lerp 函数组合使用,生成如:
lerp(baseDistance, swipeDistance, triangleT(t))
-
示例代码伪写
function triangleT(t) {let result = t * 2;if (result > 1) {result = 2 - result;}return result; }
总的来说,这种方法非常适合我们当前的使用场景,既高效又能提供视觉上自然的出击-收回效果,并且更容易控制攻击幅度和时长。后续我们可以在不同手套上使用不同的插值函数,以增加游戏的策略性和手感差异。
game_world_mode.cpp:让 UpdateAndRenderWorld 调用 Triangle01 并运行游戏测试
我们尝试将插值函数替换为三角波函数后,当前的挥击动作看起来更自然了一些,手套从身体中心伸出再收回的运动更符合期望的攻击轨迹。不过,整体效果可能还不够“激进”或“不够夸张”,可能还需要进一步调整插值函数的形状,增加动作的剧烈程度或变化幅度。
当前函数的表现
- 使用的是一个简单的 三角波函数,控制
t
从 0 到 1 之间的插值值,使其先升后降,形成一个短暂冲刺、再快速收回的效果。 - 相比之前尝试的正弦函数或抛物线函数,这个三角波的响应更直接,来得快、回得快,适合快速的挥击动画。
当前的调整反馈
- 三角波的视觉效果比前面的正弦或抛物线更自然,挥击动作的动态更清晰、紧凑。
- 但是动作幅度或者速度感可能还不够明显,也就是说,变化的“尖锐度”不够强烈,需要一个更“陡”的插值曲线。
- 考虑下一步可能需要对该函数进一步偏移中点或调整时间轴的非线性压缩,来控制峰值出现时间或波峰斜率。
未来改进方向
-
可以尝试自定义插值函数,比如:
- 提前达到峰值后迅速衰减;
- 或者调整成非对称三角形,类似锯齿波;
- 甚至使用分段插值,控制不同阶段的加速度。
-
另外可以针对不同类型的手套,设置不同的挥击函数,让每种攻击方式都具有不同的动态风格和节奏感。
总的来说,当前这个实现虽然简单,但已经初步实现了动态攻击动作的插值控制。下一步可以围绕“如何让动作更有力、更明显”来进行微调和实验。接下来进入问答环节。
Q&A
它会支持任天堂的能量手套吗?
我们暂时不会支持任天堂的 Power Glove,主要原因如下:
- 没有该设备,因此无法进行实测或开发调试。
- 技术限制:该设备是老旧的外设,要让它与现代 PC 兼容,需要具备相当高的软硬件破解与电路改造能力,例如剪线、焊接、电路接驳等。
- 缺乏驱动支持:Power Glove 并没有现代系统的驱动,因此即使硬件连接成功,还需要编写底层接口或驱动,才能读取其动作数据。
- 投入产出不成比例:为一个极为小众的输入设备做深度支持,性价比过低。
至于游戏机制部分:
- “酱油少年”是否能实现连锁攻击(chain attack),这一点可以根据游戏设计逻辑进行扩展。
- 如果设计中包含“连击系统”,可以进一步实现连续输入手势或动作引发不同阶段的攻击,如轻击→重击→旋转击,或时间窗口内连打形成连续动作。
- 此类机制适合增加游戏的深度与手感反馈,也能强化“拳套”作为武器的打击感和策略性。
总结来说,目前暂不支持 Power Glove,但连锁攻击机制是可以设计和实现的,后续若加入更多外设支持,也需评估技术可行性与实际意义。
会加入连击吗?例如“砍砍刺”?
在当前的设计中,不会存在“劈砍 / 刺击 / 横斩”等多种攻击方式之间的切换,原因如下:
- 攻击操作并不依赖多个攻击按钮。攻击的输入方式主要是方向性输入,即玩家按下方向来指定攻击的方向,而不是通过按下不同的按钮来触发不同类型的攻击动作。
- 游戏中不存在传统意义上的“攻击按钮组合”(比如常见格斗游戏中的“X X Y”连击),因此也就没有实现基于按键种类的攻击变化。
不过,虽然没有多种攻击按钮,但仍然存在基于输入节奏与模式的攻击差异:
- 单击攻击按钮与双击攻击按钮,可能会被识别为不同的攻击意图,从而触发不同的攻击行为或动画。
- 这种机制带来一定的输入节奏策略性,但本质上还是围绕少量输入信号(方向 + 攻击节奏)展开。
总结:
- 不支持通过不同按键执行劈、刺、斩等不同攻击方式。
- 攻击类型的变化主要依据输入节奏(如单击、双击)和方向控制,而非按钮组合。
- 未来可能会在编码层面加入某种“动作识别逻辑”,用于解析玩家的输入模式,从而触发不同的效果。
你如何看待切换为模拟相邻房间而不是使用当前的模拟缓冲区(simulation apron)?
关于切换到模拟相邻房间而不是使用模拟“围裙”(simulation apron)的思路,我们的想法是:
- 可能更倾向于将“围裙”设定为相邻的房间区域,但不想把它明确硬编码为相邻房间。
- 这样做的原因是为了保持灵活性,比如房间大小可能会变动。如果硬编码为“模拟下一间房”,那么在房间结构不明确或房间是平滑滚动、连续延伸的情况下,代码逻辑就会变得模糊和不易维护。
- 因此,“围裙”作为一个抽象区域概念,暂时是最合适的方案。模拟区域代码本身并不关心我们具体怎么去定义和收集这些区域,我们可以在后期灵活决定具体的实现细节。
- 这个决策可以一直拖到游戏开发的后期,也不会对现有代码造成太大影响,保持开放性更好。
至于“伙伴”或“随从”(familiar)的攻击方式:
- 目前尚未明确描述,但通常会根据具体设计需求来定,可能会有独立的攻击机制或者辅助攻击方式。
- 需要进一步设计伙伴的攻击行为、触发条件和表现形式。
总结就是,我们在模拟游戏世界的区域时,倾向于用灵活且抽象的“围裙”概念,避免过早硬编码相邻房间关系,保持代码和设计的可扩展性;伙伴的攻击机制还需具体设计。
这个熟悉者(familiar)预期如何攻击?是发射投射物还是滑行击打敌人?
关于伙伴(familiars)的设计,目前主要考虑是他们可能会有某种投射物攻击或者滑行动作来物理接触敌人,但实际上还没有深入思考过具体细节。伙伴会出现在游戏中,但不是设计重点,相关内容也还未深入开发。
伙伴之所以在这段代码实现中出现,主要是因为它们是一个大家都熟悉且容易理解的概念,方便用来测试和验证系统功能是否支持这类角色的存在和运作。
后续可能会有更详细的设计说明发布,帮助理解游戏主角和相关系统的更多细节。
关于是否会有和手套匹配的“靴子”,目前没有明确提及。
关于手套的粒子特效,我们会不会像之前一样使用头部资源来做?
关于手套的粒子效果,我们会参考之前用头部资源做粒子系统的方法,但不会完全一样。粒子系统的基础原理是相同的,但手套的粒子效果不需要那么复杂的格子网格系统,整体会更简单一些。
手套的粒子效果会比之前演示的粒子系统更轻量,主要是因为需求不同,不需要那么多复杂的处理。总体来说思路相似,但实现会更简洁直接。
我在想,如果在挥击过程中可以改变面向方向,并由此延长挥击时间,甚至变成一个无限旋转的攻击,会不会是个很酷的机制?
在想一个很酷的机制,能不能通过挥动手套的同时改变面向方向,从而延长挥动动作,甚至变成一个几乎无尽的旋转拳击。手套的控制方式会根据不同的手套种类有所不同,之后还想尝试给它加入“挥动后的控制”(after touch)机制。不过现在还太早,不确定哪些机制最终会进入游戏,因为这些东西更靠感觉,需要实际玩一玩才能判断到底好不好玩。
关于《Planescape: Torment》有没有玩过的问题没有回答。
你玩过《异域镇魂曲》(Planescape: Torment)吗?主角真的需要一个类似 Morte 的熟悉者
游戏中的英雄确实非常需要一个像Mort风格的召唤物。其实如果要选召唤物,更偏好的是《星球大战:旧共和国》(KOTOR)里的HK-47,比起Mort更喜欢HK-47的风格。
攻击是否可以被打断?例如攻击未激活的实体,或被敌人从背后攻击后跳跃反击(类似早期《塞尔达》)?
关于攻击是否会打断动作,比如攻击一个不可打断的实体,或者有人从背后攻击玩家,玩家是否会向相反方向跳开——一般来说攻击不会打断玩家动作,除非是非常特殊的攻击。攻击可能会打断敌人的动作,比如敌人在做某事时会被击退或受影响。玩家有时也可能被击退,但大多数敌人通常不会有这种能力,因为频繁被击退会很烦人。不过可能会有一些特殊的手套赋予玩家这种击退敌人甚至被击退的能力,有了这种能力可以更频繁地使用这种效果。
为什么不把手套的挥击动作交给游戏的美术或动画师来做?
关于是否将手套挥击动作交给游戏的艺术家或动画师来制作,考虑到如果没有动画师就不存在动画,若有动画师也需要权衡。即使有动画师,也不一定要完全依赖动画,因为动画本身不是参数化的,没有逻辑,只是固定的动作序列。对于玩家跳跃动作或手套挥击这种关键动作,用固定动画的方式表现是有限的,可能不够灵活。希望动作有更多逻辑和参数化的表现,比如不同的手套根据其属性能有不同的挥击表现。因此,即便游戏中有动画师负责其他动画(比如怪物攻击),跳跃和手套挥击这类关键动作还是倾向于用代码手工控制,实现更灵活、更动态的表现,而不是完全依赖预设动画。
相关文章:
游戏引擎学习第294天:增加手套
准备战斗 我们正在进行的是第294天的开发,目前暂时没有特别确定要做的内容,但我们决定继续研究移动模式相关的部分。虽然一些小型实体系统已经在运行,但并不确定最终效果如何。 今天我们决定实现一个全新的功能:战斗系统。这是游…...
[架构之美]从PDMan一键生成数据库设计文档:Word导出全流程详解(二十)
[架构之美]从PDMan一键生成数据库设计文档:Word导出全流程详解(二十) 一、痛点 你是否经历过这些场景? 数据库字段频繁变更,维护文档耗时费力用Excel维护表结构,版本混乱难以追溯手动编写Word文档&#…...
5个开源MCP服务器:扩展AI助手能力,高效处理日常工作
AI大语言模型(如Claude、GPT)尽管强大,但其原生形态仅限于文本对话,无法直接与外部世界交互。这一局限严重制约了AI在实际应用场景中的价值发挥 - 无法主动获取实时数据、无法操作外部系统、无法访问用户私有资源。 MCPÿ…...
服务器的基础知识
什么是服务器 配置牛、运行稳、价格感人的高级计算机,家用电脑不能比拟的。 服务器的组成:电源、raid卡、网卡、内存、cpu、主板、风扇、硬盘。 服务器的分类 按计算能力分类 超级计算机 小型机AIX x86服务器(服务器cpu架构) …...
bisheng系列(二)- 本地部署(前后端)
一、导读 环境:Ubuntu 24.04、open Euler 23.03、Windows 11、WSL 2、Python 3.10 、bisheng 1.1.1 背景:需要bisheng二开商用,故而此处进行本地部署,便于后期调试开发 时间:20250519 说明:bisheng前后…...
华为ODgolang后端一面面经
MySQL死锁是怎么产生的? 假如有一条SQL语句执行了非常久,你会怎么优化呢? explain 索引什么情况下会失效? InnoDB和MyISAM引擎的区别是什么? 为什么是三次握手 避免历史连接 同步双方初始序列号避免资源浪费 为什么…...
UE5 GAS框架解析内部数据处理机制——服务器与客户端
当, gas通过点击鼠标光标触发事件时,内部的处理机制。 当通过点击事件,命中中目标时, 可获取到对应的TargetData 目标数据。处理相应的操作。 仅有本地的客户端的情况下。命中并不会有什么异常。 当存在服务器时, 服…...
《打造第二大脑》
序 第二大脑,前景无限 2025/05/08 发表想法 是的说的太对了,关键是之前自己一直在找如何能避免出现此问题的方法,今天终于看到了本书所讲的的内容 原文:我们耗费了无数的时间阅读、倾听和观摩他人提供的处世原则、思考方式以及生活…...
Word2Vec详解
目录 Word2Vec 一、Word2Vec 模型架构 (一)Word2Vec 的核心理念 (二)Word2Vec 的两种架构 (三)负采样与层次 Softmax (四)Word2Vec 的优势与局限 二、Word2Vec 预训练及数据集…...
[特殊字符] Word2Vec:将词映射到高维空间,它到底能解决什么问题?
一、在 Word2Vec 之前,我们怎么处理语言? 在 Word2Vec 出现之前,自然语言处理更多是“工程方法”,例如字符串匹配、关键词提取、正则规则...。但这些表示通常缺乏语义,词与词之间看不出任何联系以及非常浅显。当然,技术没有好坏,只有适合的场景。例如: 关键词匹配非常…...
anythingLLM支持本地大模型嵌入知识库后进行api调用
anythingLLM 可以使用本地大模型,并且可以嵌入知识库(Knowledge Base),通过 API 调用该知识库。 ✅ 一、anythingLLM 的基本架构 anythingLLM 是一个支持多种本地大模型(如 LLaMA、Qwen、ChatGLM 等)的开…...
PHP 实现连续子数组的最大和、整数中1出现的次数
在编程面试和实际应用中,处理数组和整数的常见问题之一是求解连续子数组的最大和以及计算整数中1出现的次数。本文将详细介绍如何使用 PHP 实现这两个问题的解决方案。 连续子数组的最大和 连续子数组的最大和问题要求找到一个数组中的连续子数组,使得…...
面试题之进程 PID 分配与回收算法:从理论到 Linux 内核实现
总结: 在操作系统中,进程 PID(Process Identifier)的分配与回收是核心功能之一。本文深入剖析了三种主流算法:位图法、空闲链表法和位图 哈希表组合法,并结合 Linux 内核源码探讨其优化思路。通过时间复杂…...
Pyro:基于PyTorch的概率编程框架
Pyro:基于PyTorch的概率编程框架 **Pyro:基于PyTorch的概率编程框架**基础讲解**一、Pyro核心模块****1. 入门与基础原语****2. 推理算法****3. 概率分布与变换****4. 神经网络与优化****5. 效应处理与工具库** **二、扩展应用与社区贡献****1. 特定领域…...
API Gateway REST API 集成 S3 服务自定义 404 页面
需求分析 使用 API Gateway REST API 可以直接使用 S3 作为后端集成对外提供可以访问的 API. 而当访问的 URL 中存在无效的桶, 或者不存在的对象时, API Gateway 默认回向客户端返回 200 状态码. 而实际上这并不是正确的响应, 本文将介绍如何自定义返回 404 错误页面. 基本功…...
02-前端Web开发(JS+Vue+Ajax)
介绍 在前面的课程中,我们已经学习了HTML、CSS的基础内容,我们知道HTML负责网页的结构,而CSS负责的是网页的表现。 而要想让网页具备一定的交互效果,具有一定的动作行为,还得通过JavaScript来实现。那今天,我们就来讲…...
visual studio code中的插件都是怎么开发的?用的什么编程语言?
目录 开发VS Code插件的编程语言 开发VS Code插件的步骤 学习资源 Visual Studio Code(VS Code)是一款流行的开源代码编辑器,由微软开发,支持多种编程语言。它的一个重要特性是可以通过插件(Extensions)来扩展其功能。这些插件可以增加新的语言支持、主题、调试器以及…...
python第30天
知识点回顾: 导入官方库的三种手段导入自定义库/模块的方式导入库/模块的核心逻辑:找到根目录(python解释器的目录和终端的目录不一致) 作业:自己新建几个不同路径文件尝试下如何导入 浙大疏锦行-CSDN博客 from lib.ut…...
【数据仓库面试题合集③】实时数仓建模思路与实践详解
实时数据仓库已经成为各大企业构建核心指标监控与业务实时洞察的基础能力。面试中,关于实时建模的题目频繁出现,尤其聚焦于建模思路、宽表设计、状态管理、乱序处理等方面。本文整理典型题目及答题思路,帮助你应对相关考察。 一、建模原则与数仓分层认知 1. 实时数仓与离线…...
kotlin Android AccessibilityService 无障碍入门
安卓的无障碍模式可以很好的进行自动化操作以帮助视障人士自动化完成一些任务。 无障碍可以做到,监听屏幕变化,朗读文本,定位以及操作控件等。 以下从配置到代码依次进行无障碍设置与教程。 一、配置 AndroidManifest.xml 无障碍是个服务…...
精益数据分析(69/126):最小可行化产品(MVP)的设计、验证与数据驱动迭代
精益数据分析(69/126):最小可行化产品(MVP)的设计、验证与数据驱动迭代 在创业旅程中,从需求洞察到产品落地的关键一跃是打造最小可行化产品(MVP)。今天,我们结合《精益…...
JVM频繁FullGC:面试通关“三部曲”心法
想象一下,你的Java应用程序是一个繁忙的工厂,JVM堆内存就是工厂的仓库和车间。垃圾收集(GC)就像工厂的清洁工,负责清理不再需要的废料(无用对象),腾出空间让新的生产(对象…...
Scala语言基础与函数式编程详解
Scala语言基础与函数式编程详解 本文系统梳理Scala语言基础、函数式编程核心、集合与迭代器、模式匹配、隐式机制、泛型与Spark实战,并对每个重要专业术语进行简明解释,配合实用记忆口诀与典型代码片段,助你高效学习和应用Scala。 目录 Scal…...
大语言模型 13 - 从0开始训练GPT 0.25B参数量 MiniMind2 补充 训练开销 训练步骤 知识蒸馏 LoRA等
写在前面 GPT(Generative Pre-trained Transformer)是目前最广泛应用的大语言模型架构之一,其强大的自然语言理解与生成能力背后,是一个庞大而精细的训练流程。本文将从宏观到微观,系统讲解GPT的训练过程,…...
【NLP】37. NLP中的众包
众包的智慧:当“无数人”帮你训练AI 当我们谈论构建大语言模型时,脑海中浮现的往往是服务器、GPU 和Transformer,而很少想到成千上万的普通人也在默默贡献力量。 这背后依赖的机制就是:众包(Crowdsourcing࿰…...
数据分析入门指南:从历史到实践
在信息爆炸的时代,数据分析已经成为各行各业不可或缺的技能,无论是商业决策、医疗研究,还是社会科学,数据分析都在其中扮演着关键角色。本文将带你深入了解数据分析的历史、定义、流程、数据来源与处理、常用工具,并通…...
大语言模型 12 - 从0开始训练GPT 0.25B参数量 MiniMind2 补充 训练开销 训练步骤 知识蒸馏 LoRA等
写在前面 GPT(Generative Pre-trained Transformer)是目前最广泛应用的大语言模型架构之一,其强大的自然语言理解与生成能力背后,是一个庞大而精细的训练流程。本文将从宏观到微观,系统讲解GPT的训练过程,…...
精益数据分析(68/126):数据透视表实战与解决方案验证——从问卷分析到产品落地的关键跨越
精益数据分析(68/126):数据透视表实战与解决方案验证——从问卷分析到产品落地的关键跨越 在创业的移情阶段,通过问卷调查获取数据后,如何深入分析数据并验证解决方案的可行性?今天,我们结合《…...
Cursor 模型深度分析:区别、优缺点及适用场景
Cursor 模型深度分析:区别、优缺点及适用场景 在AI辅助编程领域,Cursor凭借其多模型架构和智能上下文感知能力,成为开发者提升效率的核心工具。不同模型在代码生成、逻辑推理、多模态处理等方面存在显著差异,本文将结合技术特性与…...
LightRAG 由入门到精通
LightRAG 由入门到精通 作者:王珂 邮箱:49186456qq.com 文章目录 LightRAG 由入门到精通简介一、LightRAG Server1.1 安装 LightRAG Server1.2 LightRAG Server 和 WebUI1.2.1 配置 LightRAG Server1.2.2 启动 LightRAG Server1.2.3 使用 Docker 加载 …...
【Spring Boot 整合 MongoDB 完整指南】
目录 Spring Boot 整合 MongoDB 完整指南1. 添加依赖2. 配置 MongoDB 连接application.properties 方式:application.yml 方式:3. 创建实体类(映射MongoDB中的文档,相当于MySQL的表)4. 创建 Repository 接口完成简单操作5. 使用 MongoTemplate 进行复杂操作6. 高级配置配置…...
prisma连接非关系型数据库mongodb并简单使用
prisma连接非关系型数据库如mongodb数据库并简单使用 安装 mongodbPrisma连接mongodb改造目录结构写一个model增查查多个查单个分页排序改改多个删单个多个最后代码进度安装 mongodb 社区版下载 副本集模式文档 可以百度下安装副本集模式,因为prisma要用事务。 如果你觉得安装…...
深度强化学习 | 基于SAC算法的移动机器人路径跟踪(附Pytorch实现)
目录 0 专栏介绍1 软性演员-评论家SAC算法2 基于SAC算法的路径跟踪2.1 SAC网络设计2.2 动作空间设计2.3 奖励函数设计 3 算法仿真 0 专栏介绍 本专栏以贝尔曼最优方程等数学原理为根基,结合PyTorch框架逐层拆解DRL的核心算法(如DQN、PPO、SAC)逻辑。针对机器人运动…...
VS中将控制台项目编程改为WINDOWS桌面程序
有时候因为误操作,建立了控制台项目,但是实际上想建立桌面程序。那么应该如何改过来呢? 一共要修改两个地方,修改步骤如下: 第一处修改地点: 将C/C下面的预处理器选项中,将原本的_CONSOLE修改…...
从API到UI:直播美颜SDK中的滤镜与贴纸功能开发与落地方案详解
时下,滤镜和贴纸功能,已经成为主播们展现个性、增强互动的“必备神器”。那么,这些功能背后的技术实现到底有多复杂?如何从API到UI构建一个流畅、灵活的美颜SDK呢?本文将从底层原理到前端实现,全面解析这两…...
vue3与springboot交互-前后分离【验证element-ui输入的内容】
系列文章目录 提示:帮帮志会陆续更新非常多的IT技术知识,希望分享的内容对您有用。本章分享的是node.js和vue的使用。前后每一小节的内容是存在的有:学习and理解的关联性。【帮帮志系列文章】:每个知识点,都是写出代码…...
VS2017编译librdkafka 2.1.0
VS2017编译librdkafka 2.1.0 本篇是 Windows系统编译Qt使用的kafka(librdkafka)系列中的其中一篇,编译librdkafka整体步骤大家可以参考: Windows系统编译Qt使用的kafka(librdkafka) 由于项目需要,使用kafka,故自己编译了一次,编译的过程,踩了太多的坑了,特写了本篇…...
DeepSeek 赋能数字孪生:重构虚实共生的智能未来图景
目录 一、数字孪生技术概述1.1 数字孪生的概念1.2 技术原理剖析1.3 应用领域与价值 二、DeepSeek 技术解读2.1 DeepSeek 的技术亮点2.2 与其他模型的对比优势 三、DeepSeek 赋能数字孪生3.1 高精度建模助力3.2 实时数据处理与分析3.3 智能分析与预测 四、实际案例解析4.1 垃圾焚…...
谷歌前CEO TED演讲解析:AI 红利的三年窗口期与行业重构
谷歌前CEO埃里克施密特在2025年TED演讲中提出的"AI红利仅剩3年窗口期"观点,揭示了AI技术从算力、需求到监管的全局性变革。以下是基于演讲内容及关联数据的深度分析: 谷歌前CEO TED演讲解析:AI红利的三年窗口期与行业重构 一、算…...
数据仓库面试题合集②】ETL 设计与调度策略详解
📌 面试官为什么爱问 ETL 与调度? ETL 与调度是数据链路的“输血管道”,它的设计直接决定了数据处理的稳定性、扩展性与时效性。面试中此类问题侧重考察: 数据流设计是否合理 对任务依赖与失败容错的认知 是否具备复杂调度 DAG 设计经验 是否理解增量/全量策略、分区机制…...
前端入职总结
负责的工作内容,遇到的问题,怎么解决, 技能组溢出 问题一:溢入溢出bug 互斥实现的核心逻辑 状态管理: selectedOverflowGroups:存储当前选中的溢出技能组ID(数字字符串数组) sel…...
易境通海外仓系统:一件代发全场景数字化解决方案
随着全球经济一体化和消费升级,一件代发业务的跨境电商市场规模持续增长。然而,一件代发的跨境运营也面临挑战,传统海外仓管理模式更因效率低下、协同困难成为业务扩张的瓶颈。 一、一件代发跨境运营痛点 1、多平台协同:卖家往往…...
C#接口的setter或getter的访问性限制
有时候只想对外提供getter,但是属性的赋值又必须是setter,此时,可以限制setter的访问性。例如,假设有一个自定义字典(MyDict)属性,该属性我只希望外部能够访问,但是设置必须在内部,则可提供如下…...
云计算与大数据进阶 | 26、解锁云架构核心:深度解析可扩展数据库的5大策略与挑战(下)
在数据库的世界里,面对数据如潮水般的增长难题,聪明的工程师早已准备了五大扩展方案来应对,它们就像五把钥匙,以破解着不同场景下的性能困局。 上回书云计算与大数据进阶 | 26、解锁云架构核心:深度解析可扩展数据库的…...
SID 2025上的天马,用“好屏”技术重构产业叙事
作为全球最具影响力的显示行业盛会,SID国际显示周不仅是技术比拼的舞台,更是未来产业方向的风向标。SID 2025上的技术密度与产业动态,再一次验证了这一定律。 Micro-LED、柔性OLED、裸眼3D、量子点、透明显示等新技术在SID 2025集中亮相&…...
深入理解 Hadoop 核心组件 Yarn:架构、配置与实战
一、Hadoop 三大件概述 Hadoop 作为大数据领域的基石,其核心由三大组件构成: HDFS(分布式文件系统):负责海量数据的分布式存储,通过数据分块和副本机制保障可靠性,是大数据存储的基础设施。 …...
Linux云计算训练营笔记day11(Linux CentOS7)
Linux云计算 云计算是一种服务,是通过互联网按需提供计算资源的服务模式 程序员写代码的,部署上线项目 买服务器(一台24小时不关机的电脑,为客户端提供服务) 20万 买更多的服务器 Linux(命令) windows(图形化) 就业岗位: 云计算工程师 li…...
2025年AI与网络安全的终极博弈:冲击、重构与生存法则
引言 2025年,生成式AI的推理速度突破每秒千万次,网络安全行业正经历前所未有的范式革命。攻击者用AI批量生成恶意代码,防御者用AI构建智能护盾,这场技术军备竞赛正重塑行业规则——60%的传统安全岗位面临转型,70%的防…...
Hadoop中 8020、9000、50070 端口用途的详细对比
Hadoop 端口用途对比教程 1. 端口用途总览 Hadoop 的核心服务(如 NameNode、DataNode、ResourceManager 等)通过不同的端口对外提供服务。不同版本中,部分端口号可能发生变化,尤其是 Hadoop 3.x 对部分默认端口进行了调整。 端口Hadoop 2.x (2.7.7)Hadoop 3.x (3.1.3)协议…...
HLS学习
文章目录 前言一、hls是什么二、m3u8文件格式说明 前言 在工作,需要跟m3u8的格式进行打交道,所以就去学习了一些相关的内容。本文是相关的笔记。 一、hls是什么 HTTP Live Streaming,缩写为HLS,是由苹果公司提出基于HTTP的流媒体…...