TPS 射击中,相机转向、武器瞄准和弹丸发射并不总在同一时刻对齐。快速转动视角时,武器目标可能仍在追随;松开扳机后,后坐力仍会继续恢复。这些状态如何更新、采用什么参数,共同决定了不同武器的操控表现。
早期原型开发计划确定了这项工作的范围。本文继续展开 TPS 瞄准与射击,解释相机、准星和发射流程的原版机制及其 UE 接入。当前成果在 Lyra 独立原型中验证,尚待合入项目版本,不代表整套搜打撤玩法已经完成。
文章目录

画面中的固定中心提供观察参考,跟随圆圈显示当前武器目标的投影;发射时,还要在瞄准基准上处理散布。导读图右侧分别标出相机与枪口,说明两种空间起点。图中的位置与角度仅作示意,各发射路径采用的参考点在后文分别说明。
此前的《TPS 相机改造(上):机位与构图的量化对齐》和《TPS 相机改造(下):体验验证闭环》主要解决观察与测量问题:如何描述机位、比较构图、检查射击过程中的反馈。本文依据 HD2 的原版机制实现独立计算模块,并将其接入 UE5.7 的 Lyra 原型。
下文从相机配置讲起,再说明瞄准进度、目标追随、后坐力、散布、准星和发射路径。参数表与数值例子会具体展示每一步的输入和输出。公式采用便于阅读的数学表达;浮点逐位比较仍以实际代码的运算顺序和舍入点为准。
当前原型包含 96 个玩家武器参数映射,相机表有 93 条 profile;后坐力默认参数覆盖 95 个映射,默认角散布数据覆盖 85 个映射。普通准星支持 79 个映射,其余 17 个使用明确的回退表现。这些数字分别统计不同模块的支持范围,不代表已经复刻 96 把原版枪械模型,也不代表每把武器都有独占的机位或准星。
蓄力、实体弹丸、追踪和喷射使用各自的发射路径。它们的数值内核与 UE 接入程度并不相同,后文分别说明。本文讨论玩家武器与 TPS 射击流程,暂不覆盖完整的角色移动、动画、伤害系统及炮台、无人机功能。
正文按三个层次展开:第二至七节解释机位、瞄准、后坐力、散布与准星;第八、九节说明普通发射和专用武器路径;第十、十一节讨论 UE 接口与验证范围。配图中的“教学”表示按公式构造的例子,“运行截图”和视频则来自现有 Lyra 原型,两者分别说明计算关系与可见结果。

一、相机、武器目标与发射方向
准星显示涉及相机、武器目标与发射方向三种空间信息。
第一种是相机的位置、朝向与视野角,它决定如何观察世界。第二种是武器的瞄准目标,它会受到操控性能、周期扰动与后坐力状态影响。第三种是一次发射最终采用的方向,它在瞄准基准之上继续施加散布,随后进入射线、弹丸或其他发射路径。
若三者共用 Controller 的一个旋转,就无法分别表达观察、追随与发射。例如,快速向右转动视角时,相机已转到新方向,武器目标仍有滞后;开火后,相机与目标也可能采用不同的反馈。散布则作用于单颗弹丸的方向,以随机相机抖动代替会使整幅画面随采样结果变化。
当前实现的输入、状态与输出如下。
| 层次 | 主要输入 | 持续保存的状态 | 输出与消费者 |
|---|---|---|---|
| 相机选择 | 武器映射、姿态、动作资格、覆盖配置 | 当前选择与切换记录 | 目标 profile,交给相机帧计算 |
| 相机几何 | profile、俯仰、角色根与节点、场景查询 | 根位置、局部偏移、历史视野角 | 相机位置、旋转、FOV |
| 瞄准状态 | 允许瞄准条件、输入、时长参数 | Ready 状态、瞄准进度 | 状态选择、瞄准跟随等模块使用的输入 |
| 目标跟随与扰动 | 目标点、Handling、武器系数、时间步 | 跟随方向、相位、噪声、随机状态 | 当前武器目标及其共享快照 |
| 后坐力 | 已提交射击事件、两组参数、调用方缩放 | 弹簧位置、速度、刚度、待处理事件 | 视角与目标相关的反馈 |
| 散布 | 角宽、模式、射击基底、随机状态 | 随机状态 | 本次弹丸方向 |
| HUD | profile、目标投影、缩放输入 | 平滑乘数与显示历史 | 刻线、中心标记、跟随标记 |
| 发射执行 | 模式、间隔、蓄力与弹丸配置 | 计时器、计数、弹体或喷射粒子 | 实际运动、接触与后续表现 |
AimStat 决定基础瞄准时长,Handling 控制目标追随,散布宽度用于采样弹丸方向,后坐力系数影响持续反馈。比较武器时,分别记录这些参数及其结果,更容易判断差异出在哪一步。
UE 层提供角色、武器与场景输入。原型用同一个 Lyra 代理模型承载多个参数映射,以便在相同视觉资产下比较数值差异。资源名称沿用配置标识;未确认的商品名和译名不作替换。
配置、运行状态与场景输入
以一把普通突击步枪为例,640 RPM、默认角宽和两组后坐力系数属于配置;上次成功发射的截止时间、弹簧位移和随机状态属于运行状态;当前角色位置、枪口节点与场景命中点属于本次计算的外部输入。三者的生命周期不同。
配置通常在选择武器时取得,之后反复读取。运行状态要跨帧保留,不能每次查询参数时重新初始化。场景输入则会随角色、镜头和目标改变,应在规定阶段重新采样。例如,同一份后坐力配置连续使用十次,不意味着每次都从静止弹簧开始;同一初始随机状态连续发射十颗弹丸,也不意味着十次都得到第一颗弹丸的方向。
默认配置与运行时修正也需分别记录。默认值描述未施加额外修正时的计算参数。姿态、伤势、技能等修正是否生效,要检查实际读取这些值的代码。不能从“蹲下后画面稳定了”反推所有稳定性都由后坐力缩放造成:相机参考点、目标扰动输入和 HUD 显示也可能同时变化。
每帧先读取武器配置与外部输入,再推进已有状态并发布结果。调试参数单独保存,避免覆盖配置原值,以便区分配置、输入和更新算法造成的差异。
转向、瞄准与开火的数据依赖
以普通突击步枪为例,角色先向右转动视角,再按住瞄准并连续射击。下表按交互过程列出数据依赖;UE 的输入、Ability、相机与渲染仍由各自回调调度。
| 时刻与操作 | 状态怎样变化 | 这一刻尚不能等同于什么 |
|---|---|---|
| 转动视角 | 观察方向改变,场景查询产生新的期望目标 | 武器目标已经追上新方向 |
| 按下瞄准 | 输入进入资格判断,保存的 Ready 状态可能改变 | 相机过渡、目标追随与进度都已完成 |
| 相机更新 | 选择配置、推进过渡,计算几何与碰撞;目标模块发布当前快照 | 所有射击回调都必定发生在它之后 |
| 按下扳机 | 发射路径检查模式、计时、装填与其他资格 | 已经成功发射 |
| 普通射击处理 | 读取可用目标,逐弹丸采样方向,构造并尝试提交 Lyra Target Data | 所有普通命中都已改为实体弹丸运动 |
| 成功提交 | 保存本次射击所属映射与时间,通知反馈队列 | 后坐力已经在这一回调中完整积分并绘制 |
| 随后的相机更新 | 消费已提交事件、推进弹簧,再计算相关视角与目标反馈 | 本次反馈必然影响刚刚发出的同一颗弹丸 |
| 松开与恢复 | 停止相应发射请求,弹簧与其他持续状态继续更新 | 所有历史状态应立即归零 |
当前 UE 接入中,成功射击先进入队列,随后由相机更新处理这些事件并计算后坐力。因此,这次射击产生的反馈未必作用于刚刚发出的同一颗弹丸。输入回调也可能先于相机更新,此时会读取上一帧仍有效的目标快照。核对射击结果时,需要保留这两个时间关系。
记录一次交互时,应将输入、武器映射、目标快照、成功提交与反馈更新对应起来。准星变化展示最终结果,事件记录则用于核对它来自哪次发射。
二、相机配置与机位计算
profile 参数与单位
相机 profile 保存高、中、低俯仰位置的距离,局部高度、肩侧偏移、俯仰范围、FOV 乘数,以及不同偏移分量的平滑速率。它还包含参考节点和节点偏移。因此,表中的一个高度不能直接读成“相机离地高度”,一个距离也不能直接读成“角色胶囊到镜头的最终距离”。
以当前基础映射常用的几条配置为例。表内距离、偏移按原坐标长度列出,在 UE 适配中按米到厘米转换;数值为方便阅读做了小数整理。
| 状态 | profile | 高/中/低距离 | 局部高度 | 肩侧偏移 | 参考节点偏移 | 俯仰范围 |
|---|---|---|---|---|---|---|
| 站姿 Ready | 2 | 1.75/1.75/1.75 | 0.35 | 0.40 | (0, 0, 1.40) | −75°~75° |
| 蹲姿 Ready | 3 | 0.30/1.00/1.00 | 0.40 | 0.47 | (0, 0, 0.75) | −75°~75° |
| 卧姿 Ready | 4 | 0.10/1.50/1.00 | 0.50 | 0.65 | (0, 0.20, 0.15) | −45°~30° |
| 站姿非 Ready | 43 | 3.50/4.50/3.50 | 0 | 0.60 | (0, 0, 1.80) | −70°~70° |
| 蹲姿非 Ready | 44 | 1.75/2.75/1.75 | 0 | 0.60 | (0, 0, 1.30) | −50°~70° |
| 卧姿非 Ready | 49 | 0.20/4.00/4.00 | 0.40 | 0.60 | (0, 0.50, 0.25) | −30°~30° |
普通突击步枪、泵动霰弹枪、重机枪和轨道武器在这组基础姿态映射中可以引用相同 profile。共享机位时,各武器仍可采用不同的瞄准时长、追随、后坐力与散布参数。
卧姿 profile 的存在也不能单独证明卧倒移动、动画与输入已经完整移植。相机可以先使用卧姿参数进行预览,角色动作链需要另行验收。

profile 选择的资格与覆盖优先级
相机选择并非只有“右键按下选 2,松开选 43”。实现会先处理禁止写入或资格不满足的条件,再考虑全局模式、特殊状态和覆盖配置,之后才进入持枪方式与姿态的基础映射。部分覆盖配置还具有不同的优先级。
基础映射表描述正常条件下的配置选择,完整选择器还要处理动作与特殊状态的限制。绕过这些判断直接写入 Ready profile,可能使画面进入瞄准机位,而相关内部状态尚未满足条件。
这一层的迁移原则是保留选择顺序,把 UE 当前角色状态作为输入传入;尚未接通的动作状态输入应说明目前的适配范围。没有证据的原始标记不能仅凭某次画面就命名成“冲刺”“翻滚”或其他动作。
俯仰同时参与距离和视野角计算
设允许俯仰范围为 pitchLow 到 pitchHigh,先归一化当前俯仰:
t = clamp((pitch − pitchLow) / (pitchHigh − pitchLow), 0, 1)
接着按上下半段分别使用平方根曲线:
t ≤ 0.5:k = sqrt(2t),从 low 插值到 middle
t > 0.5:k = sqrt(2(t − 0.5)),从 middle 插值到 high
距离和 FOV 乘数都采用对应半段的两个端点。例如 t=0.25 时,k≈0.7071,并不是简单线性插值时的 0.5。俯仰的归一化比例与距离的插值比例并不相同。
FOV 乘数还要与外部视角输入结合。距离随后按视野角计算一个缩放:
FOVdegrees = fovMultiplier × inputAngleDegrees
distanceScale = (1 − z) + z × 48 / FOVdegrees
distance = distanceScale × 本半段插值得到的距离
z 对应距离与 FOV 的混合参数。z=0 时距离不随这项 FOV 比例改变;z=1 时完整使用 48/FOVdegrees。
当前基础适配的外部视角输入为 60°。若使用 profile 2,FOV 乘数为 1、z=1,则这一几何阶段的距离为 1.75×48/60=1.40 m。表中的 1.75 因而不是最终镜头距离。这个例子只解释距离曲线的输出,尚未包含节点变换、状态混合与碰撞修正。

从局部偏移到世界空间
局部相机目标按以下分量构造:
localTarget = (shoulderSign × side, −distance, height)
随后分别处理角色根、参考节点和局部偏移的历史状态,再变换到世界空间。普通分量平滑采用带限幅的更新:
alpha = clamp(rate × dt, 0, 1)
next = old + alpha × (target − old)
profile 2 的局部速率为 (10,10,5)。在 dt=1/60 s 时,前两轴单次推进剩余距离的约六分之一,第三轴约十二分之一。速率 10 不是“十秒完成”,也不能直接解释为固定的 0.1 秒动画;连续逼近没有统一的完成时刻,限幅还会改变大时间步下的行为。
根位置存在额外的调整分支,不能把整个相机几何都简化为三次普通插值。碰撞修正在候选位置计算完成后执行,并结合碰撞锚点与场景查询调整机位。UE 提供场景查询服务,原型中的查询形状、碰撞过滤与场景资产仍需与数值几何分开验证。
坐标转换同样属于接口。当前适配把原空间 (X,Y,Z) 转为 UE 的 (Y,X,Z)×100。若只换算单位而漏掉轴交换,肩侧偏移就会错误地作用于前后方向。角色胶囊中心也不等于脚底根点,使用脚底锚点时需要扣除胶囊半高,否则再加参考节点高度会造成整体抬升。
机位参数与观察条件

肩侧偏移决定相机位于参考框架的哪一侧,后向距离和局部高度共同影响构图。俯视图展示水平面的两项偏移,前面的姿态参数图展示参考节点与高度,分别对应局部机位计算的不同分量。
修改肩侧偏移时,主要观察角色的横向占屏位置、近处遮挡和枪口到目标的夹角。它不直接决定弹丸散布。修改局部高度时,要保持参考节点、根位置与其他偏移不变,才能判断这一项造成的变化。否则把节点高度和局部高度一起改动,最终画面相近,也无法证明参数解释正确。
相近的人物占屏高度不等于距离与 FOV 相同。较远距离配较小 FOV,可能得到近似的占屏高度,但背景透视、近远物体比例和侧面遮挡仍会不同。画面测量用于比较构图,参数计算用于解释构图,两者需要结合检查。
左肩切换在当前机位计算中改变侧向符号,但这不自动镜像持枪动画,也不改变枪械模型的实际枪口。相机换到左侧后,原本右手持枪的枪口可能仍在右侧。近距离射击需要继续检查实际起点与目标方向,不能仅因相机已经换肩就假设整套武器几何都完成了镜像。
碰撞修正也要与目标距离分开。场景挡住理想镜头位置时,最终机位可能更靠近角色;这不是 profile 被永久改短。障碍消失后,应从保留的状态继续恢复。若直接把碰撞后距离写回配置,下一次进入相同状态就可能从错误距离开始。
相机验证需覆盖固定俯仰机位、俯仰变化、贴墙收缩和离墙恢复。空旷场地的单帧截图只能检查静态机位,无法验证历史平滑与碰撞回退。
三、瞄准时长与相机切换是两套时间状态
AimStat 与基础瞄准时长
瞄准时长由武器的 AimStat 与快、慢两个端点计算。当前内核先把数值转成整数,再归一化并限制上界:
t = min(float(int32(AimStat)) / 100.0, 1.0)
duration = (1 − t) × slow + t × fast
这项计算没有下界 clamp。本文使用当前合法默认配置,不引入负参数测试;移植实现保留原有的上界限制。
这组映射的 fast≈0.09 s、slow=0.50 s。具体例子如下。
| 配置标识 | AimStat | 基础时长 | 读法 |
|---|---|---|---|
| assault_rifle | 65 | 0.2335 s | 在快、慢端点之间按 0.65 插值 |
| pump_shotgun | 65 | 0.2335 s | 与上项相同,不代表其他机制相同 |
| heavy_mg | 0 | 0.5000 s | 使用慢端点 |
| railgun | 35 | 0.3565 s | 在两个端点之间按 0.35 插值 |
这个时长控制瞄准进度,不是相机位置插值时长,也不是武器发射间隔。把 0.2335 秒同时赋给所有平滑模块,会改变原有的状态关系。
上一帧 Ready 与本帧输入
瞄准进度按上一帧保存的 Ready 状态推进:
step = dt / duration
上一帧 Ready:progress = clamp(progress + step, 0, 1)
上一帧非 Ready:progress = clamp(progress − step, 0, 1)
随后才根据当前输入与资格判断新的状态。一次按键既可能改变下一步使用的 Ready,也可能处于仍在回退的进度之中。移植时若先覆写 Ready,再用新值推进进度,就改变了输入生效的时序。
允许进入 Ready 也不是输入为真就足够。武器描述、配置资格、动作输入与保持状态共同参与判断。适配器负责把 UE 输入转换成这些条件,而不是让一个右键布尔值绕过全部资格规则。
相机切换需要保留尚未完成的记录
相机另有切换记录队列。选择变化时,按照旧类型、旧 profile、新类型和新 profile 查找规则;精确匹配之外还有通配规则。记录保存延迟、进度、方向与曲线类型,之后随相机帧推进。
这使“进入瞄准途中松手”与“已经到达瞄准机位再松手”能够沿不同历史继续处理。若每次输入变化都重新创建一条从零开始的固定曲线,就会丢失中途反转的状态。
切换支持阶跃、线性、若干 Bezier 形式,以及其他曲线。例如其中一种五次曲线为:
w(t) = 6t⁵ − 15t⁴ + 10t³
它在 t=0.25 时约为 0.1035,在 t=0.75 时约为 0.8965,起止比线性缓和。但并非所有 profile 切换都使用这条曲线,不同类型也不共享完全相同的限幅行为。
Ready 进度描述瞄准状态的推进,相机切换记录描述机位从旧状态到新状态的过渡。 一次输入可以同时影响两者,但两套状态仍各自保留历史、按各自规则更新。
中途松手与过渡历史
以普通突击步枪的 0.2335 秒基础时长为教学输入,60 Hz 下每次进度增量约为 0.07138。如果此前 Ready 已经成立,且连续推进七步,进度约为 0.4996。此时玩家松手,新的请求将退出 Ready;但处理该帧进度时仍然读取上一帧 Ready。后续才沿非 Ready 分支减少进度。
七步只是这组教学输入的演算长度。相同的 progress≈0.5 可能对应正在进入瞄准,也可能对应刚收到退出请求;实际机位还取决于相机过渡记录。只保留当前进度和按键值,会丢失这部分历史。
相机切换还需要处理自己的中途反转。正在接近 Ready 机位时收到反向请求,不应先跳回起点再播放退出曲线。当前记录保留进度、方向、延迟和来源,用来继续判断。把它改成“每次按键创建一条 0.2 秒动画”,即使完整按下、完整松开的演示正常,中途连按仍可能出现位置或速度不连续。
输入测试还需覆盖短按、长按、姿态切换和动作禁止条件。两个稳定机位的截图只能检查端点配置,过渡历史与中途反转需要连续输入验证。

四、Handling、目标追随与持续扰动
目标追随更新的是方向框架

画面显示圆圈与中心的分离,计算保存方向及其历史。右图分别标出旧方向、期望方向和本步插值结果,对应下方的更新公式。由于投影还受相机、目标距离和扰动影响,圈点像素间距不能直接换算为方向误差。
玩家转动视角后,期望目标立即改变,武器当前目标则按 Handling 计算追随速率。设:
t = clamp(Handling / 100, 0, 1)
A = 1 + 19t²
B = 1 + 0.5t
rate = 3.1400001 × A × (Ready ? B : 1)
alpha = dt × rate
Handling 通过平方项影响速率,数值翻倍并不对应速率翻倍。Ready 状态再应用 B 系数,是否在本步对齐仍取决于最终插值权重。
| 配置标识 | Handling | 非 Ready 速率,约 | Ready 速率,约 |
|---|---|---|---|
| assault_rifle | 59 | 23.91 | 30.96 |
| assault_rifle_ap | 47 | 16.32 | 20.15 |
| pump_shotgun | 65 | 28.35 | 37.56 |
| heavy_mg | 0 | 3.14 | 3.14 |
| railgun | 35 | 10.45 | 12.28 |
以普通突击步枪的 Ready 状态为例,60 Hz 下 alpha≈0.516。这是方向插值的权重,不是目标每帧移动 0.516 米。
实际计算以参考原点、旧目标和新目标构造方向框架,然后进行最短路径四元数插值;两方向足够接近时使用归一化线性形式,alpha≥1 则直接采用新目标。目标距离在输出构造中另行处理。因此,用世界空间坐标直接 Lerp(oldPoint,newPoint,alpha) 并不是同一算法。
这一区别在快速横扫或近距离目标上很明显。位置插值会穿过两目标之间的弦线,方向插值描述的则是从观察原点出发的朝向变化。
周期扰动和随机噪声各有自己的状态
目标扰动不仅使用一条正弦曲线。它保存幅度、频率、相位,以及横纵两个噪声状态。幅度目标由调用方输入、持枪者参数和武器系数共同构成,并经过平滑;频率则按独立的系数计算。
每个噪声轴先随机游走,再进行滤波。省略误差极小时直接取目标值的分支,主要更新为:
rawNext = clamp(raw + (2u − 1) × 10 × dt, −1, 1)
filteredNext = filtered + clamp(7 × dt, 0, 1) × (rawNext − filtered)
u 是本次随机数,噪声还保留上一阶段的状态,因此相邻帧的偏移彼此相关。逐帧将独立的 Random(-1,1) 加到屏幕坐标,会产生高频跳动,无法复现这段连续漂移。
相位按频率与时间步推进,之后周期项、噪声项和弹簧项共同形成偏移。用便于阅读的符号写为:
x = springX + (0.5sin(phaseX) + noiseX) × amplitudeY × K
y = springY + (cos(phaseY + π) + noiseY + 1) × amplitudeY × K
target = candidate + (Right × x + Up × y) × distance
K≈0.005555556,是内核固定比例。已恢复的代码在这两式中都读取同一个 amplitudeY;两个轴使用同一幅度是已恢复的读取关系,不应根据变量名将第一式改为另一分量。频率也分别读取调用方输入与持枪者系数:横向为 0.5×F×wielder2,纵向为 F×wielder3。F 并不是所有武器共享的最终振荡频率。
扰动最终乘以目标距离,其世界空间位移随距离扩大。HUD 再将世界目标投影到屏幕,得到可见位移;该参数不能直接读成固定像素数。
UE 固定步调度与输入采样
原型的瞄准反馈适配器按 60 Hz 推进跟随、扰动与 HUD 乘数,渲染层再插值显示。补步时使用最近一次相机采样,不能凭空恢复没有采到的鼠标输入历史。这是 UE 的调度策略,不是对原版调度器的完整复刻。
持枪者幅度初值、反馈启动时刻,以及把 UE 移动速度映射到某些运动输入的可选实验,也属于适配层。默认参数已经存在,并不代表原游戏每一项动态生产者都已接通。
比较 Handling 前需确认目标追随已启用。参数表、功能开关与运行状态应一并记录;参数面板只能证明当前显示的配置,实际更新还需运行记录确认。
Handling 参与幅度平滑
目标跟随和扰动幅度使用不同的更新公式。后者先计算幅度目标向量 T 与当前向量 A 的长度,再决定这一步允许移动多远。将主要关系整理为:
ratio = |T| == 0 ? 0 : min(1, (|T| − |A|) / |T|)
blend = (Handling / 100) × ratio + (1 − ratio)
step = [0.35 × (1 − blend) + 2 × blend] × dt
delta = T − A
若 |delta|≤step,本次可以到达目标;否则沿差值方向最多移动 step。非正步长还有保持当前值的分支。这里限制的是幅度向量本次变化长度,不是对每个轴独立使用同一个百分比插值。
ratio 只有上界限制,不能为了让表达式“更规范”就补上下界,也不能把所有幅度变化统一替换为 FInterpTo。增长与衰减时的长度关系不同,会影响这一项。
例如取 |T|=1,|A|=0,Handling=59,可得 ratio=1,blend=0.59,60 Hz 下 step≈0.02206。这个例子说明幅度建立需要逐步推进;不等于准星每帧移动 0.02206 像素,也不等于完整扰动恰好在若干帧后到达稳定状态。
当前原型还以姿态和可选运动输入构造幅度、频率的上游值。站姿的幅度端点为 1 和 4,蹲姿为 0.75 和 3,频率输入端点为 3 和 12;将 UE 速度归一化到 0~1 的运动适配默认是可选项。这些端点来自固定条件范围;原版如何根据运动状态生成输入,尚未完整恢复。因此,参数端点与用 UE 速度提供输入的适配策略需要分别记录。
相机目标与枪口参考点
目标跟随需要一个参考原点,扰动还可能使用另一个原点。当前适配允许从视线查询取得候选表面点,跟随以该查询原点计算;扰动距离则可从角色的枪口节点计算。两者并非总是相同的坐标。
这能解释近处目标为什么不能统一用一个固定远点替代。如果目标实际在两米外,却始终按数百米的距离放大扰动,屏幕和枪口关系就可能出现夸张偏差。当前候选查询会在命中表面时使用表面点,这是明确的 UE 场景策略。
节点基底同样影响偏移方向。恢复的算法需要对应发射节点的轴;当前原型常用相机 Right、Up,并保留测试节点轴的诊断路径。数学内核正确,不代表 Lyra 枪口动画节点与原版节点已经等价。应同时记录采用哪个原点、哪个基底,固定屏幕偏移无法修正这类空间关系差异。

视频 1:四组武器参数的转向与准星跟进。
这段运行画面依次使用步枪、重机枪、bolt_action_rifle 样枪映射与标准手枪的参数,展示慢转、快转、停转及开火时的准星变化。模型沿用 Lyra 样枪;画面中的武器名称不代表四套原版模型已完成迁移。前两组连射后还有弹匣耗尽与装填动作,因此尾部不能全部作为无干扰的后坐力恢复过程。跟进速率的含义仍以本章公式为准,视频本身不证明数值等价。
五、后坐力:射击事件怎样进入两组弹簧
每次射击写入速度,弹簧随后持续演化
每个后坐力槽位保存 A、B 两组弹簧状态,包括横纵位移、速度和刚度。射击事件向速度写入冲击,之后由固定步积分持续更新;松开扳机只停止新事件,已有弹簧状态继续恢复。一次 AddPitch 无法表达这段持续过程。
其核心更新可概括为:
h = 1/60 s
vNext = v − (35v + k × p) × h
pNext = p + vNext × h
p 是当前角位移,v 是速度,35 为这里使用的阻尼系数,k 为当前刚度。更新先算新速度,再用新速度推进位置。若改成先用旧速度推进位置,离散系统就变了。
帧时间先限在 0~1 秒,再累计并消耗固定步。位移长度超过约 0.001745329 rad 时进入活动分支,目标刚度为 150/Scale;Scale 非正时使用保护值 150,非活动分支的目标则为 75。每个固定步的刚度变化量分别限制在约 0.833333 和 1.666667,不能立刻跳到目标。部分分支还限制纵向负位移。因而,只画一条标准阻尼振子曲线不足以解释全部恢复行为。
五个后坐力系数的作用
每组射击参数包含 C0…C4。其中前两项进入横纵基础强度,第三项参与横向符号选择,后两项分别控制对应轴的随机变化。它们是速度注入公式的系数,不是五个最终镜头角度。
设调用方缩放为 s,三个随机数为 u0,u1,u2,可写为:
X0 = s × C0
Y0 = s × C1
X = X0 × [1 + (1 − C3) + u0 × C3]
Y = Y0 × [1 + (1 − C4) + u1 × C4]
sign = C2 ≥ u2 − 0.5 ? +1 : −1
X = sign × X × π/180
Y = Y × π/180
最后的角度换算说明了为什么不能把表中 C1=20 直接解释为“每发抬头 20°”。它还要经历缩放、随机项、速度写入和多次积分。
下面列出几种默认配置的两组五元系数。
| 配置标识 | B 组:C0,C1,C2,C3,C4 | A 组:C0,C1,C2,C3,C4 |
|---|---|---|
| assault_rifle | 7,10,0,0.1,1 | 5,20,0,0.1,1 |
| pump_shotgun | 40,40,0,0.1,1 | 50,200,0.5,0.1,1 |
| heavy_mg | 50,80,0,0.1,1 | 15,30,0,0.1,1 |
| railgun | 10,25,0,0.1,1 | 3,25,0,0.1,1 |
以普通突击步枪 B 组为例,取教学输入 s=1,u0=u1=u2=0.5,横向注入换算前为 7×1.95=13.65,纵向为 10×1.5=15。这些是送入后续弹簧的计算值,不能当成画面最终角度。教学输入只为展示公式,不代表某次真实射击恰好使用了这三个随机数。
两组采用不同的速度写入规则:A 组替换对应速度,B 组仅在新速度绝对值大于旧值时替换;当新的横向冲击与已有横向位移方向相反,且已有位移绝对值大于 0.01 时,新横向注入还会乘 0.0625。每次射击先处理 B、再处理 A,随机数的消耗顺序也随之确定。
多槽输出的逐轴选择
当前反馈计算会在多个槽位间,分别对每个轴选择绝对值最大的位移响应,而不是把所有槽位求和。横轴和纵轴因此可能来自不同槽位。若用一个全局弹簧替代全部槽位,切枪或连续事件重叠时就无法保留同样的历史。
调用方缩放同样不能随意命名。代码中存在受伤标记影响的缩放,原型也有用于补足上游输入的全局增益。不能把所有缩放统一写成“蹲下减后坐力”,也不能把一个适配用常量宣传成原版完整的角色状态计算。
UE 侧在射击成功提交后通知后坐力队列,保存映射、事件序号和逻辑时间。按下扳机、被拒绝的尝试与实际发射应分开,尤其是蓄力未达门槛、追踪尚未锁定等情况。否则会出现没有发射弹体却产生完整后坐力的错误反馈。
连射改变的是当前状态,而不只是次数
第一发之前若弹簧静止,射击注入从零速度开始。第二发到来时,第一发已经留下位移、速度与刚度;新输入要与这些旧状态比较。B 组的新速度可能不足以替换旧速度,A 组则会替换,反方向横向输入还可能被减弱。因此,两发的结果通常不能用“第一发曲线复制两次后相加”得到。
松手后没有新注入,弹簧仍持续执行恢复。恢复过程由当前位移、速度、刚度目标和分支限制共同决定,无法仅用一个回正时长描述。若在松手时把速度清零,或者把位移按一条额外曲线拉回中心,就在原弹簧之外添加了第二套恢复逻辑。
比较武器时也不能只按 C1 大小排序。重机枪与普通步枪的两组系数差异、射击节拍和持续状态会共同作用;纵向强度大的武器,不一定在任意时刻都有更大的最终屏幕位移。
UE 默认帧合批与实验逐发时间轴
内核支持五个槽位,当前玩家试验场的成功射击主要写入第 0 槽。其他槽位的存在不代表原版所有装备关联都已接通。伤势缩放的输入、槽位归属和部分角色状态仍由适配器提供。
默认路径按相机帧集中消费已经排队的射击事件,然后推进本帧弹簧。若一帧内集中到达两发,两次速度写入会先后发生,再进行积分;它不保证两次写入之间还原了准确的原版间隔。
实验时间轴另外保存事件时间,并按固定步重新安排或回放事件。它用于检查时序差异,默认关闭,也不能写成原版完整调度已经恢复。逻辑发射时间、后坐力消费时间与显示时间因此需要分别保留。
30 FPS 与 120 FPS 都采用 1/60 秒子步时,积分步长相同,事件注入的子步却仍可能不同。比较这两种帧率,需要同时检查注入序列和随机状态,才能区分积分误差与调度差异。

六、散布:角宽、随机分布与弹丸方向
完整角宽与最大半角
散布配置提供横纵角宽和模式。下面的宽度以毫弧度参与方向计算,是轴向完整角宽,最大偏离通常对应其一半;它不是屏幕像素,也不是目标距离处的米数。
| 配置标识 | 横向完整角宽 | 纵向完整角宽 | 默认模式 |
|---|---|---|---|
| assault_rifle | 2 mrad | 2 mrad | 0 |
| assault_rifle_ap | 1 mrad | 1 mrad | 0 |
| heavy_mg | 5 mrad | 5 mrad | 0 |
| railgun | 0.1 mrad | 0.1 mrad | 0 |
| pump_shotgun | 150 mrad | 120 mrad | 0 |
pump_shotgun 的散布行存在,不代表它的普通发射数据所有字段都可直接使用。散布是方向内核的支持范围,发射弹丸数是否有效属于另一个模块。
U² 径向采样
模式 0 每次消耗两个随机数 U,V:
r = U²
φ = 2πV
sampleX = 0.5 + 0.5r cosφ
sampleY = 0.5 + 0.5r sinφ
θx = 0.001 × WidthX × (sampleX − 0.5)
θy = 0.001 × WidthY × (sampleY − 0.5)
合并两步可得:
θx = 0.0005 × WidthX × U² cosφ
θy = 0.0005 × WidthY × U² sinφ
这里用的是 U²,而圆盘面积均匀采样通常用 sqrt(U)。两者会产生不同的弹着密度,不能因为都画成圆形分布就互换。
取 U=0.5,V=0,普通突击步枪横向偏角为 0.00025 rad。忽略弹道、目标变化和其他扰动,在 100 米处对应约 2.5 厘米横向偏移。其横向最大偏角为 1 mrad,同样的近似下约为 10 厘米。前者是一个示例样本,后者是角宽给出的边界,含义不同。
横纵宽度不相等时,分布会在两个轴上拉伸。非零模式则有不同的采样关系,不能统称为同一个圆盘分布。
随机状态和方向基底也是输入
随机数使用持续保存的 64 位状态:
seedNext = seed × 0x5851F42D4C957F2D + 0x14057B7EF767814F
u = float(seedNext >> 32) × 2⁻³²
状态运算按 64 位回绕。一次模式 0 采样消耗两次更新,多弹丸也会继续消耗后续状态。只记录初始 seed,却改变弹丸数量或随机调用次序,仍然不能复现同一组结果。
方向应用还依赖外部提供的基底,并按确定顺序绕不同轴旋转。把同样的角度加到 UE Rotator 上,不保证与原有轴顺序一致。
当前默认散布未添加瞄准或奔跑的通用缩放系数。动态修正需另行确认来源与执行条件;在默认角宽、采样分布和基底映射尚未核对时增加缩放,会使误差来源难以区分。
散布与瞄准漂移的独立观察

观察散布时,需要分别记录准星显示与弹丸方向。圆圈显示目标投影,外层刻线按 HUD 规则布局;成功发射时才在对应基准上采样弹丸方向。下面两种实验分别检查持续漂移与随机散布。
设一次发射前的武器基准方向为 D,散布计算得到 D'。连续射击时,下一次使用的 D 本身已经可能因为转向跟随、扰动或后坐力而变化。最终弹着图的宽度因此混合了“每发基准改变”与“围绕基准的随机偏离”。
验证默认散布时,先固定基准方向、距离、映射和样本数量,保存每发散布前后的方向。验证完整射击体验时,再启用动态目标与后坐力。这样可以分别检查采样分布和持续反馈,避免将两类变化混入同一张弹着图。
单发与多弹丸也要区分。一轮多弹丸可以共享一次发射时的基准,再按每颗弹丸消耗随机样本;连续多发则会在不同时间读取新的基准。即使都记录到十个落点,两种情况的基准采样时刻也不同。
分布对照除了最大范围,还应检查中心密度、轴向比例和随机消耗。只比较均值与最大半径,可能漏掉 U² 被误换成面积均匀采样的问题。对同一随机序列,应先比较角度,再比较基底旋转后的方向,最后检查场景命中;这样可以定位究竟是采样、坐标还是碰撞产生差异。

视频 2:普通武器射击与参数映射,截取原片前 18 秒。
这段画面用于观察普通射击、弹药消耗和靶面痕迹。该版本面板中的目标扰动与方向跟进处于关闭状态,不能用它验证上一节的跟进机制,也不能仅凭靶面痕迹推断完整散布概率分布。后部的特殊武器样枪片段未收入本段;它们需要用对应执行后端的独立画面说明。
七、目标投影与准星布局
三种可见元素承担不同职责
当前 HUD 将外层刻线、屏幕中心标记与目标跟随标记分开处理。外层刻线由 profile 决定数量、角度、图形资源与扩张方式;中心标记提供屏幕参考;跟随标记读取目标快照的投影。
普通 profile 还包含类型和资格标记。部分元素受 Ready 状态限制,部分常显,部分隐藏;不支持普通路径的特殊配置进入回退。因此,各武器的显示元素应按对应 profile 构造,其尺寸也不能统一解释为原版散布范围。
外层扩张使用最大轴偏差
HUD 先平滑一个缩放乘数:
mNext = m + clamp(30dt, 0, 1) × (mTarget − m)
再根据目标相对中心的投影偏差计算 extent:
deviation = max(abs(screenX − centerX), abs(screenY − centerY))
extent = max(base × multiplier, deviation + padding)
t = clamp(extent / normalization, 0, 1)
response = (1 − t) + t × responseEndpoint
第一式取横纵偏差中的最大值。目标位于中心右上方 (20,20) 时,结果为 20;欧氏距离则约为 28.28。因此,extent 在这里表示最大轴偏差参与计算后的布局范围。
例如普通 profile 0 的 base=32,padding=0,normalization=100,responseEndpoint=0.75。在乘数为 1、目标最大轴偏差为 40 个界面坐标单位时:
extent = max(32,40) = 40
t = 0.4
response = 0.6 + 0.4×0.75 = 0.9
profile 9 则使用 base=16,padding=10,normalization=64,responseEndpoint=0.25,并具有三条刻线。两者改变的不只是基础大小,还有偏差留白与响应曲线。原始界面坐标到实际像素还经过分辨率与 UI 变换,表中 32 不能直接视为所有窗口中的 32 像素。

图形资源与状态计算要分别核对
多条刻线按索引读取各自资源,而不是读取第一张贴图后复制成任意条数。部分配置还区分刻线布局角和贴图自身旋转。看见三条刻线,并不能仅凭常见准星样式推断三张图都应朝外旋转。
描边尺寸也有独立规则。恢复了资源键与尺寸,并不等于已经恢复原 GPU 合成、全部资格条件与特殊 HUD。当前原型中的 Slate 排序、世界到屏幕投影和部分中心元素组合仍属于 UE 适配,应与普通布局内核的支持范围分开。
显示层保存前后两个样本,采用约一个固定时间步的延后插值,以减少渲染帧率变化时的跳动。这里不能再次推进随机数、相位或后坐力;否则,同一帧的准星绘制与射击计算可能读取不同的目标。
目标快照因此附带武器身份、映射和时效检查。切枪后旧快照应失效,不能让新枪继续使用上一把枪的目标。这个问题在单枪演示中不明显,在快速轮换映射时却会直接表现为准星突然跳动或弹丸沿旧方向发射。
准星截图中的坐标与显示元素

图中橙色标注指向固定屏幕中心,蓝色标注指向跟随圆圈,灰色标注指向外层刻线。在这个快速转向的采样时刻,圆圈已经偏到中心左侧,外层刻线仍围绕自己的布局中心展开。这三者分别对应中心参考、目标投影和 HUD 布局范围;圈点间距不能直接当作弹丸散布。
这里使用同一帧的完整画幅和局部放大,是为了辨认显示元素。它没有给出目标运动的完整历史;转向与急停的变化见后文视频 3 的同步输入对照。
在 profile 0 的教学例子里,最大轴偏差为 40,因此 extent 取 40;如果目标回到偏差 10,乘数仍为 1,extent 就由基础值 32 控制。目标偏差减小后,外层范围仍受基础值限制。profile 9 的 padding 则使相同偏差保留额外留白。
当窗口大小或 DPI 改变时,应先检查界面坐标到像素的变换,而不是修改原配置的 base。直接修改 base 会使准星在其他窗口尺寸下出现错误缩放。投影 y 轴、刻线布局角和纹理旋转符号也应分别校验;横纵坐标都看似居中,并不能证明旋转正确。
当前显示插值对普通准星状态进行平滑,但不负责修复失效目标。身份变化、时间倒退或过大时间跳变时,需要按适配规则重置显示历史,避免在不同武器的目标之间产生无效插值。
八、射击模式、蓄力与弹丸运动
模式、节拍和弹丸数分别判断
普通发射首先区分自动、半自动和连发计数条件。当前范围内,模式 1 允许自动持续发射;模式 2 要求本轮计数为零;模式 3 要求计数小于配置上限。普通突击步枪列出 [1,2,3],连发上限为 3。
模式判断之外还有发射间隔:
interval = 60 / RPM
普通突击步枪为 640 RPM,对应 0.09375 秒;重机枪为 600 RPM,对应约 0.1 秒。输入可以一直保持,但实际成功发射必须等待节拍和其他资格条件。
弹丸数也不是模式数。部分霰弹武器一次发射多个弹丸,需要多次方向采样;不能把一次成功射击通知变成每颗弹丸都重复注入完整的后坐力。相反,数据中弹丸数为零的条目也不能自动解释成“发射零颗”或擅自补成一颗。
当前 pump_shotgun 的普通数据行虽然保留 80 RPM、0.75 秒间隔,但弹丸数为零,属于这条普通数据路径的未支持值。其散布和后坐力参数仍可单独分析,但普通发射路径尚未具备完整的有效参数。
Lyra 仍承担输入、Ability、装填和部分弹药流程。恢复的节拍接进了试验场发射,但不是原版完整武器生命周期。半自动的截止时间保存在 Pawn 适配状态中,也不应因 Ability 重建或临时切换就被无条件清空。
半自动、连发与自动射击的输入时序
半自动的约束是一次按下对应一次发射机会。若在冷却期间按下,被拒绝后继续按住,当前原型不会在冷却结束时自动补射。若保留该请求并在冷却结束后自动发射,就会改变半自动的一次按下、一次发射机会的输入规则。
连发模式则在一次按下中保存成功提交的次数。达到三发上限后,即使继续按住,也不能再开始第二组。当前 UE 策略在松开时截断未完成的连发,需要重新按下才能开始下一组。这个输入策略必须与恢复的计数门分开说明,不能称为原版全部输入行为。
自动射击还面临时钟漂移。若每次回调都把下次时间设为 now+interval,回调晚到的误差会不断累积。可选连续时钟使用预定截止时间推进:
本次预定时间 = nextDeadline
nextDeadline += interval
若截止时间已到,尝试处理本次射击
640 RPM 的教学时间序列可为 0,0.09375,0.18750,0.28125…。某次回调在 0.10 秒才到,第二次预定时间仍是 0.09375,第三次仍是 0.18750,不会整体顺延成 0.19375。
补处理也必须有预算。当前实验路径一次最多处理四个到期事件,保留后续截止时间;松开时只补处理严格早于释放边沿的到期项,不在释放后创建新发射。装填、取消和切换映射则停止,不使用同一套补处理规则。
连续时钟属于默认关闭的单机实验选项,普通 Lyra Blueprint 生命周期仍然存在。上述时间序列解释该选项的工作方式;默认启动后的运行结果需按实际启用路径核对。
蓄力的三个时间阈值
蓄力武器使用最小、普通、上限三个阈值,还区分正在推进的计时器与释放时锁存的时长。
| 配置标识 | 最小阈值 | 普通阈值 | 上限阈值 |
|---|---|---|---|
| plasma_rifle_charge | 0.01 s | 1.00 s | 1.00 s |
| plasma_pistol | 0.01 s | 0.75 s | 3.00 s |
| arc_thrower | 1.00 s | 1.10 s | 1.20 s |
| plasma_blaster | 1.00 s | 2.50 s | 2.60 s |
| railgun | 0.45 s | 0.50 s | 3.00 s |
在普通分支中,计时推进受到普通阈值限制;特定模式允许越过它,但不能因此自行添加原代码没有的统一上限截断。释放资格还要同时检查最小时长、动作资格与冷却条件。通过释放门只说明允许继续处理,不等于弹体已经成功创建。
锁存时长决定采用哪个区段。低于普通阈值时,可以写成:
w = clamp((latched − minimum) / (ordinary − minimum), 0, 1)
factor = (1 − w) × left + w
高于普通阈值时使用另一段:
w = clamp((latched − ordinary) / (upper − ordinary), 0, 1)
factor = (1 − w) + w × right
实现还处理端点相等时的分母退化。不同输出读取不同的左右因子对,不能把所有 factor 都命名为伤害倍率。
以轨道武器为例,最小 0.45 秒、普通 0.50 秒。在低段锁存 0.475 秒时 w=0.5;第 0 组因子对的左值为 0.7,该输出因子为 0.85。上段锁存 1.75 秒时 w=0.5;第 1 组因子对的右值为 2.5,该输出为 1.75。这里沿用因子组编号,因为尚不能把每个消费者都命名成已确认的业务量。两个例子不能拼成一条统一“威力曲线”。
实时计时器还会参与另一条后坐力缩放关系:
recoilFactor = 1 + clamp(current / ordinary, 0, 1)
因此,释放时锁存值与当前值都有用途。只保留一个 chargePercent 会丢失状态。弹丸类型的区段映射也需要单独读取;类型值为零的回退含义不能直接解释为“不发射”。
蓄力释放与时长锁存
当前 UE 在按下时保存映射、模式和开始时间,建立固定步计数。持有过程中持续检查映射和模式是否变化、是否出现禁止发射标记,以及蓄力参数是否仍然有效。资格失效时取消本次蓄力,并等待释放消耗这次输入,不在切换回来后恢复旧进度。
释放时先推进到当前可计算的计时点,再检查释放门。低于最小阈值时不发射;满足门槛后才进入对应路径。电弧武器进入放电处理,其他已接入配置仍可使用 Lyra 射击代理。释放后清理本轮计时器与蓄力显示。
分段因子与弹丸类型规则已经完成数值实现,当前 Lyra 代理则接通了蓄力输入和释放后的射击。各因子到伤害、穿透或弹丸属性的连接尚未全部完成,因此演示中的进度条与射击画面主要用于检查输入资格和代理发射。
过载伤害、爆炸和自伤不在当前验收范围内。upper=3 s 在这里用于对应分段计算,实际过载后果还需检查后续消费者。

实体弹丸使用自己的积分参数
对于物理积分配置,弹体保存速度、重力倍率、质量、阻力和子步数。普通突击步枪的一条默认配置为:初速度 900 m/s、重力倍率 1、质量字段数值约 0.0045、阻力字段数值约 4.27649×10⁻⁶,子步数 1。本文不额外推断质量与阻力字段的原始物理单位;两者按当前恢复公式配套使用,共同决定减速,不能只把阻力数值复制到 UE 的另一个阻力接口。
忽略分支保护,将其一步更新写为:
a = gravity − normalize(v) × (|v|² × drag / mass)
pNext = p + (v + 0.5a × h) × h
vNext = v + a × h
这里的位置项使用本步起始速度与半个加速度项。重力常量约为 9.8199997 m/s²,并按配置倍率应用。
仅考虑重力、忽略阻力时,在 900 m/s 初速下飞行 100 米约需 0.111 秒,下落约 6.06 厘米。这个教学估算用于解释单位和量级,不是实际弹道的验收结果;实际计算还包括阻力、子步与接触。
弹丸位置更新不负责决定伤害、穿透或完整生命周期。UE 的扫线、接触对象、视觉模型与回收策略是另外的消费者。积分核通过对照,也不能自动推出所有接触后果等价。
普通命中查询、弹道预览与实体抛射的接入范围
当前普通 cartridge 路径在采样方向后仍执行 Lyra 命中查询,再处理目标数据与提交。开启弹道预览时,可以另外画出沿积分参数运动的轨迹,但它没有因此取代普通命中的判定。预览轨迹与墙面相交,仅表示积分轨迹的可见结果,不能证明伤害判定采用了弹丸飞行时间。
榴弹发射器映射使用独立实体抛射路径:从代理枪口出生,以默认速度初始化,按 UE 的 120 Hz 固定调度调用积分核,再对本步前后位置进行场景扫线。碰撞后回收,另有 8 秒的 UE 生命周期上限;没有接入爆炸伤害。
为保留 Lyra 的提交与枪口反馈,抛射路径使用延后的占位目标数据,同时避免普通远处命中、贴花与伤害表现。该路径使用独立的弹体执行与接触处理,普通射线不再承担本次命中判定。
子步数还需要与总时间步一起解释。配置要求 N 个子步时,每个子步使用 H/N,总共推进 H。若每个子步都错误地使用 H,弹体在一帧内就被推进 N 倍时间。当前实体抛射的场景扫线覆盖固定步前后端点,不应进一步宣传为原版物理服务的全部连续碰撞行为。
当前接入包含三种路径:普通射击方向与命中处理、积分轨迹预览,以及有时间和接触限制的实体抛射。三者分别记录完成范围,视频也按对应路径说明。

九、电弧、激光、追踪与喷射的专用路径
电弧候选的方向门与距离门
电弧候选范围中的方向阈值使用:
threshold = 1 − SpreadAngle / 180
weight = (dot − threshold) / (1 − threshold)
limit = [(1 − weight) × SpreadDistance + weight × Distance] × scale × ratio
它没有把角度先转弧度再取余弦。若按常见圆锥查询直接换成 cos(angle),候选边界就不一样。方向点积通过权重进一步影响允许距离,正前方与边缘并非只比较同一个半径。
这段计算也不能替代完整目标选择。场景候选的枚举、遮挡检查与排序仍需有自己的输入与证据。电弧逐步推进会限制本次位移不能超过剩余距离;步长不再为正时由调用方结束,不应让弹体反向跨过终点。
以蓄力电弧首击配置为例,SpreadAngle=3,SpreadDistance=40,Distance=55,scale 和 ratio 先取 1。阈值约为 0.983333。候选点积为 0.99 时,weight 约为 0.4,允许距离约为 46 米;正前方点积为 1 时,则为 55 米。阈值对应的几何角度约为 10.48°,因此配置值 3 不能直接命名为实际半角 3°。
当前 UE 用场景重叠查询取得候选,再依次检查方向门、距离门与可见性。没有证据表明原枚举在这一处做最近距离排序,所以当前实现也没有自行增加“总选最近敌人”的规则。不同查询顺序仍可能选择不同对象,这属于场景服务边界。
连锁的下一跳从当前命中点继续查询,排除已访问对象,并结合入射方向和缩放后的距离门。当前蓄力电弧还提供额外跳数的 UE 演示参数;它可以让画面展示多个目标,不代表原版升级、默认跳数和完整动态修正都已恢复。
电弧线条的折弯、亮度变化与淡出使用独立的视觉随机数,不消耗玩法选择和射击散布的随机状态。修改视觉分段数不应改变选敌结果。放电成功也允许没有目标,此时可以有可见的放电表现,但不能把一条视觉折线经过敌人当成实际连锁命中。

激光需要区分预热、发射资格与温度
连续激光相关计算保存预热量和温度。预热量在活动时增加并受门槛限制,不活动时衰减;只有达到规定门槛后,温度才沿对应发射分支增长。普通冷却、替代冷却与保持状态又有不同条件。
如果把预热和温度合成一个进度条,就难以表达“已经停止发射,但仍处在另一种冷却状态”。同样,存在温度内核不代表原版 Beam、能量消耗、命中与过热后的整套行为都已经接通。
当前 LAS-98 演示已经接入预热与持续光束。预热按 60 Hz 更新,使用门槛 100、增长 200;在持续合格输入下,名义上约 0.5 秒达到发射门。回调频率与预热步长分开,不能把更频繁的回调当成更多次发射。
达到门槛后,先沿共享观察方向查询焦点,再从代理枪口指向焦点执行光束查询。视觉端点使用枪口查询的实际命中位置。这样相机看得见目标、枪口却被近处障碍遮挡时,光束仍会在近处停下。该两段查询是当前 UE 接入方式,不代表原版场景服务已完全迁移。
持续持有最多维持一个光束视觉对象,随枪口与碰撞端点更新;松手、切换映射或取消时销毁。当前光束使用自建材质表现,不能称为原版特效。温度、过热、完整弹药和伤害并未因预热接通而一并完成。
电弧按放电事件处理候选和连锁,激光则持续更新查询与视觉对象。两者分别保留时间状态、场景结果和资源生命周期;光束刷新不产生一颗普通 cartridge。

独立追踪状态与 UE 接入
追踪路径保存出生随机状态、阶段计时器、导引状态和控制历史,再按配置更新方向与位移。转向与位置更新的先后顺序属于算法的一部分:当前恢复路径中,位移使用本步转向前保存的方向,不能直接用刚算出的新朝向替换。
不同配置的导引启用时刻也不同。部分配置在出生后立即进入相关计时路径,另一些需要后续事件启用。统一在出生时启用追踪,会改变早期偏转与过渡阶段。
调度按配置间隔推进,当前适配保留严格大于的步进条件、每帧最多四步的限制及剩余时间;追踪控制保留独立的导引计算,不能由 UE 的 HomingAccelerationMagnitude 常量替代。便携内核对照与实际移动目标测试,分别检查数值状态和游戏中的行为。
当前接入六套 mode 2 配置,覆盖智能手枪、智能导弹和相应导弹发射器映射。UE 中的候选标签、5° 搜索锥、500 米范围、可见性判断和 8 秒回收上限是明示的适配选择;占位目标与锁定标记也不是原版资源。mode 1 激光制导不在当前接入范围。

追踪锁定、转向权重与速度控制
发射前锁定与弹体出生后的导引是两套状态。锁定器保存候选对象、积累进度和是否锁定;弹体出生后保存自己的姿态、控制器历史、阶段计时器与目标引用。玩家移开准星导致锁定器重置,不应被自动解释为所有已发射弹体同时销毁。
当前锁定积累时间分别为:智能手枪约 0.1 秒,智能导弹约 0.75 秒,普通 FAF 配置约 1 秒,helghast 配置约 0.5 秒。离开候选或切换配置会重置锁定;这是当前 UE 输入和场景适配的行为范围。
弹体导引的参数则来自另一份配置。下表的 Initial、Limit 与 Growth 是导引控制链中的目标量。只有确认读取这些参数时的单位,才能判断它们是否对应物理速度或加速度。当前端口在米制运动计算中使用这些参数;Gain 是计算中的转向增益,不能直接读成“每秒最多转多少度”。
| 配置 | Initial | Limit | Growth | Gain0/Gain1 | 角度归一化阈值 |
|---|---|---|---|---|---|
| smart_pistol_default | 90 | 90 | 200 | 30/30 | 45° |
| smart_pistol_missile_default | 10 | 100 | 200 | 15/18 | 45° |
| smart_pistol_missile_alternate | 10 | 100 | 200 | 15/18 | 45° |
| faf_missile_launcher_default | 10 | 100 | 200 | 10/13 | 45° |
| faf_missile_launcher_helghast_default | 10 | 100 | 200 | 15/18 | 45° |
| faf_missile_launcher_helghast_alternate | 10 | 80 | 200 | 30/35 | 45° |
角度权重首先比较当前朝向与目标方向:
dotLimited = clamp(dot(heading,targetDirection), 0, 1)
angle = acos(dotLimited)
weight = clamp(angle / radians(angleThreshold), 0, 1)
turnGain = weight × Gain0 + (1 − weight) × Gain1
这里点积下限是 0,不是 −1。目标落到后半球时,会按这条分支的限制处理,不能据此把 angle 当成完整的 0~180° 夹角。45° 参数用来归一化权重,并不是最大允许追踪夹角或硬性转向速度。
以智能导弹默认配置为例,目标偏角 22.5° 时 weight 为 0.5,turnGain 为 16.5;偏角达到或超过阈值时采用 Gain0 对应端点。实际方向还要经过当前阶段的四元数计算,不能把 16.5 直接加到 UE Rotator。
导引中的控制目标随弹体年龄增加:
candidate = Initial + Age × Growth
target = candidate < Initial ? Initial : min(Limit,candidate)
Initial=10,Limit=100,Growth=200 时,年龄 0.2 秒的目标值为 50,0.45 秒达到 100。但实际控制输出不直接等于这个目标。它还经过保存历史的反馈计算:
error = target − output
integralNext = integral + error × dt
derivative = (error − previousError) / dt
outputNext = Kp × error + Ki × integralNext + Kd × derivative
当前六套配置的 Kp=0.01,Ki=1,Kd≈0.0001。这里直接计算新的 output,不执行 output += 三项之和。误差与积分跨步保存,后续输出依赖这段控制历史。
用明确的教学前状态 output=0,integral=0,previousError=0,取 target=10,dt=0.016666,新输出约为 0.32666。这个例子只说明反馈公式,真实弹体的出生初始化和阶段条件还要单独执行,不能据此画出实际第一步轨迹。
位置再使用这个输出与本步保存的前向推进。姿态更新后,本步位移仍使用保存的旧前向。这样的位置与旋转更新顺序,正是 UE 通用 Homing 接口不能直接替代的部分。

追踪补步、时间余数与预算
这组配置的间隔原值约为 0.016666 秒。调度先累积时间,在 remainder>interval 时才执行一步,一次最多四步;每次减去 interval,而不是执行后把余数归零。
当余数恰好等于间隔时,严格大于条件还不会执行。浮点数值和比较方式因此都属于契约,不能因为通常接近 60 Hz 就统一替换成 >=1/60。
如果一帧累计约六个间隔,预算只允许推进四步,剩余时间保留下来。发布变换也区分预算耗尽情况,不能假设插值比例总在 0~1 并擅自限制。实际场景中如何控制长帧和显示延迟,应在适配层决定,而不改写已经确认的数学分支。
目标消失后,当前刷新路径在弹体前方 1000 米构造备用点并继续导引。目标引用失效、停止导引、进入到达分支与生命周期回收分别处理,保留各自的状态转换。
喷射粒子的出生与运动
喷射配置的首发射器保存出生速度范围、随发射器年龄变化的出生曲线,以及粒子年龄、寿命和阻尼曲线。默认速度范围的例子如下。
| 配置标识 | 首发射器速度范围 | 资源关系 |
|---|---|---|
| flamethrower | 28~35 m/s | 与 flamer_pistol 引用同一份对应配置 |
| flamer_pistol | 28~35 m/s | 数据映射存在,不等于已列入本轮 UE 三例测试 |
| heavy_flamethrower | 33~50 m/s | 使用另一份对应配置 |
| flamethrower_ripley | 28~35 m/s | 范围相同,资源引用不同 |
出生速度先在范围内采样,再乘出生曲线。曲线在发射器年龄 0 时约为 0.3667,在约 0.0278 秒内线性升至 1,之后保持 1。例如 28~35 m/s 的配置,在发射器刚启动时对应约 10.27~12.83 m/s 的出生速度,而不是立刻达到完整范围。这里的年龄属于发射器,不是每颗粒子都重新等待 0.0278 秒。
粒子推进依次更新年龄、应用阻尼、更新速度分量、推进位置并处理到期。顺序改变会影响轨迹,不能只把最小、最大速度移进 Niagara 后便称为数值迁移完成。
阻尼可写成:
q = dampingCurve(age / life)
base = clamp((60 − 6.5q) / 60, 0, 1)
factor = base^(60dt)
v = v × factor
阻尼曲线在归一化年龄 0 时为 0,在约 0.4231 时为 0.3334,在 1 时达到 1,分段线性连接。随后再处理规定的 vZ += 14×dt。坐标轴与符号需要随整个空间契约一起解释,不能只看一个加号就给它命名为现实世界的向上浮力。
固定物体接触还区分法向和切向。令 inward=−dot(v,n),只有朝向表面的分量为正时才计算响应:
tangent = v + inward × n
scale = max(0, 1 − friction × inward / |tangent|)
vNext = restitution × inward × n + tangent × scale
切向长度为零时采用单独保护分支。摩擦降低切向速度,恢复系数控制法向回弹;UE 场景提供的法线与材质值仍是适配输入,不能因为响应公式已对照就认定所有材质接触一致。
喷射的接触查询还有独立节拍和准入条件。其中 0.125 秒对应查询条件;粒子出生由另一组调度规则控制。若误用它控制出生间隔,会同时改变粒子数量和接触响应。
UE 原型按 120 Hz 固定积分,松开后停止产生新粒子,已经产生的粒子继续到期;数量调度、方向锥采样、场景扫线、固定接触材质和橙色轨迹点属于适配。当前恢复与接入的是首发射器数值路径,没有覆盖所有子发射器、点燃、伤害、弹药和热量消耗。本轮验证覆盖粒子运动路径,尚未覆盖完整喷火武器功能。
粒子与发射器的生命周期
发射器保存是否继续生成粒子的状态,每颗粒子分别保存自己的剩余寿命。松开时应只关闭出生,不能立刻删除全部在途粒子;最后一颗到期后,发射器才可以回收。切换武器后的清理也应沿同样的所有权关系执行。
当前 UE 为每颗粒子选择约 0.5~1 秒寿命,一步最多新生一颗,活动数量上限为 256。这些是现有实现的适配值,不是从首发射器速度表推导出来的原版数量规则。它们控制资源和演示规模,不能用于说明原版喷射密度。
当前场景接触使用固定步前后端点扫线;对于固定物体,再在零间隙条件下调用已确认的响应公式,材质输入取摩擦 0.9、恢复系数 0.05。移动物体、复杂接触缓存与原查询服务不因此完成。
源码保留了 0.125 秒查询节拍内核,但当前 UE 在每个固定步执行扫线,尚未采用原节拍服务。因此,这里的碰撞检测间隔不能记为 0.125 秒。验证需分别确认数值函数、实际调用路径和生命周期。
验证这类路径时,应同时观察松手之后的残留和接触之后的运动。如果只展示按住期间的橙色点,就看不出粒子是否到期、是否还在继续出生,以及碰撞是否错误地产生了普通子弹命中。

十、Lyra 接入与状态生命周期
参数映射与武器实例身份分开
一把 Lyra 样枪可以临时选择不同的参数映射,但它仍有自己的 UE 实例身份。共享目标快照同时检查映射与实例,后坐力事件保存发射时的映射,不能在消费事件时改读“当前选中的枪”。
例如 A 枪提交一发射击后立刻切到 B 枪,若队列只保存“有一发射击”,下一帧就可能读取 B 的后坐力参数。保存发射时的身份与逻辑时间,才能让 A 的剩余反馈继续按 A 的参数处理。
Ability 的生命周期也不应成为所有武器状态的生命周期。半自动截止时间、弹簧历史和待处理事件放在持续的适配状态里,是为了避免 Ability 结束或换装时意外重置。
目标状态的共享与更新
相机、HUD 和射击都可能读取武器目标。目标在规定阶段计算一次,其他模块读取同一份结果。HUD 绘制本身不能再推进一次噪声,射击查询也不能为了取得方向重新生成一组相位。
这还要求处理首次初始化、时间倒退、过大的时间跳变和武器身份变化。它们属于状态生命周期问题,单独比较一帧公式通常发现不了。
渲染插值可以改变显示时刻,不能改变模拟时刻。固定步数值一致与最终屏幕像素一致也必须分开:动态输入按相机帧采样,投影还依赖当帧相机、窗口和 UI 变换。共同时间点的相位、噪声与随机状态对照通过,不等于任意渲染帧的最终画面逐位相同。
发射路径的选择与返回结果
普通射击、蓄力释放、追踪弹体和喷射不能在成功进入专用路径后,又继续触发普通 cartridge 或 hitscan。否则画面上出现一个追踪弹体,目标却可能已经被普通射线提前命中;喷射粒子尚未接触,常规伤害流程就已经执行。
专用路径应分别返回是否接管请求、是否成功发射,并明确何时通知后坐力。追踪尚未锁定、蓄力未达到释放门槛,或喷射已经停止出生时,调用方保留对应结果,不再补发普通子弹。
原型反馈的隔离
样枪原有的镜头抖动、散布和 HUD 若继续叠加,会让恢复参数的作用难以判断。对比时应明确各自开关,避免后坐力执行两次、散布采样两次,或一个准星显示恢复目标、另一条射线仍沿 Lyra 默认方向。
测试面板中的距离缩放、高度补偿、全局增益和运动实验开关应保留为调试或适配参数。它们不能被混入原始武器参数表,更不能为了视觉接近而逐枪调整,再宣称默认数据已经直接驱动画面。
步枪与重机枪的交互对照
视频 3:步枪与重机枪的目标追随对照。左侧 assault_rifle,右侧 heavy_mg,按正式输入第 0 帧对齐,完整画幅等比例并排。两侧使用同一个 Lyra 步枪代理模型,对比的是恢复参数驱动的响应,不是两种原版武器模型。
两侧分别从独立进程开始,在相同位置、站姿和初始朝向下稳定 3 秒,再重放同一套 12 秒输入:0–2 秒静止,2–5 秒以 12°/秒慢转,5–7 秒以 75°/秒快转,7–10 秒急停恢复,最后继续静止 2 秒。两侧各 720 帧的状态记录确认,输入与实际 yaw 轨迹一致,角色位置保持不变。
相机通过现有锁定开关保持 Ready,目标追随与呼吸扰动均开启,全程不射击。步枪与重机枪的 Handling 分别为 59 和 0,Ready 跟进系数分别约为 30.9604 和 3.14,未增加演示倍率。系数是跟进内核的控制量,不是镜头转速;两侧呼吸扰动的相位与随机状态序列也已核对一致。
视频展示了快转时的圈点分离和急停后的恢复过程:重机枪在同一输入下表现出更大的滞后,停止后回归也更慢。圆圈同时包含呼吸扰动,圈点像素距离不能直接视为纯追随角误差;精确恢复时间仍须先定义阈值,再分析对应状态,本文不从视频估算原版恢复秒数。
视频由 UE 按固定 1/60 秒步长逐帧渲染后编码为 60 fps,属于离线捕获,不代表实时运行性能。两侧转向共 186°,会离开初始 8 米靶并经过不同距离的场景几何,因此也不是全程固定目标距离的实验。Ready 锁定验证的是当前相机条件下的目标追随,不代表物理右键输入、完整动画资格链或原版画面已经等价。
| 环节 | assault_rifle | heavy_mg | 需要得出的区别 |
|---|---|---|---|
| 基础 Ready 机位 | profile 2 | profile 2 | 共享机位,不能为了突出武器差异另造偏移 |
| 基础瞄准时长 | 0.2335 s | 0.5 s | 状态进度不同,不等于共享机位参数改变 |
| Ready 跟随速率 | 约 30.96 | 约 3.14 | 相同目标变化下,追随响应不同 |
| 默认完整角宽 | 2/2 mrad | 5/5 mrad | 基准相同条件下,随机方向范围不同 |
| 默认射击间隔 | 0.09375 s | 约 0.1 s | 实际节拍仍取决于启用路径与输入资格 |
| 两组后坐力系数 | 读取步枪两组参数 | 读取重机枪两组参数 | 持续反馈要结合旧弹簧状态计算 |
表中还列出瞄准时长、散布和射击节拍,便于区分各参数的作用。上方视频保持 Ready 且全程未射击,仅验证目标追随;瞄准进度、散布与连续射击需在各自的测试条件下检查。增大散布只会改变弹丸随机方向,不能代替目标追随的滞后。
后坐力恢复和切换映射需要另行测试:停止发射后弹簧继续更新;新发射读取新参数,旧事件保留发射时的参数。换装还需检查目标快照的身份与半自动截止时间。这些行为不包含在上方的未射击视频中。
视频保留了功能开关和参数面板。数值核查仍需读取对应状态与事件记录,准星位置本身不足以证明其他模块的执行结果。
状态保留与失效条件
切换武器时,各类状态按所有者、用途与失效条件分别处理。
| 状态 | 持续使用的理由 | 失效或结束条件 |
|---|---|---|
| 相机历史与切换记录 | 保持正在进行的过渡 | 按选择器与切换规则处理,不以每次按键清空 |
| 已提交射击事件 | 仍需产生所属武器的反馈 | 被消费或随所属 Pawn 生命周期结束 |
| 后坐力弹簧 | 松手后仍有恢复过程 | 功能停用或相应状态生命周期重置,不能由普通松手直接归零 |
| 目标快照 | 输入与显示可读取最近完成的结果 | 映射、实例、时效或有效标记不满足 |
| 半自动截止时间 | 防止重按或 Ability 重建绕过间隔 | 到期后不再阻挡;不是到期自动补射 |
| 本轮连发计数 | 限制一次输入的成功次数 | 按本轮输入结束策略处理 |
| 蓄力计时与显示 | 描述这次持有输入 | 释放、取消或身份条件改变 |
| 追踪锁定进度 | 表达对当前候选的积累 | 候选、配置或锁定输入条件改变 |
| 已出生的追踪弹体 | 自己拥有飞行与导引历史 | 到达相关分支、碰撞或 UE 生命周期上限 |
| 已出生的喷射粒子 | 松手后仍继续运动 | 各自到期;发射器待残留结束后回收 |
表中列出当前实现的适配策略。仍被后续阶段使用的状态继续保留;身份或阶段变化后已不再有效的结果则失效。具体清理条件由状态所有者决定。
按数据层定位误差
如果武器目标转向迟缓,先检查 Handling 是否读取正确、跟随是否启用、方向基底与时间步是否正确,再查动态上游输入。相机距离可能只改变可见偏差,不应作为追随误差的补偿参数。
准星反向移动时,应检查坐标轴、投影 y 方向、偏移基底和纹理旋转。连续射击的偏移异常累积时,应检查弹簧历史、注入顺序、事件合并与随机序列,再核对系数。松开扳机后在途粒子全部消失,则应检查所有权和销毁路径。三类问题分别对应空间转换、持续状态与生命周期。
同一武器在不同帧率下响应不一致时,应依次比较输入采样、事件预定时间、固定步状态、显示插值与最终投影,从最早出现差异的阶段定位原因。

十一、验证范围与后续工作
当前 UE 原型已接入相机、瞄准状态、目标反馈、后坐力、散布和普通 HUD,部分特殊武器使用专用发射路径。验证分为以下五层,各层分别记录输入、输出和适用范围。
| 验证层 | 检查内容 | 不能据此推出 |
|---|---|---|
| 参数对应 | 映射、默认值、单位与消费者是否正确 | 动态上游已经全部恢复 |
| 单步数值 | 相同输入与前状态下的下一状态 | 连续轨迹和全部调用顺序一致 |
| 连续状态 | 时间步、随机消耗、切换与历史是否持续一致 | UE 场景与视觉资产一致 |
| UE 功能 | 实际输入是否进入目标模块,发射是否选择正确路径 | 所有武器与完整战斗规则等价 |
| 画面与交互 | 构图、投影、刻线、实际运动是否符合预期 | 原始渲染与网络执行完整恢复 |
普通准星已检查多种映射、状态及切枪保护。追踪测试涵盖指定条件下的移动目标、失锁后继续导引与回收;喷射测试涵盖按住、松开、接触和残留到期。这些结果只适用于各自的测试配置与适配条件,全部武器的完整功能仍需另行验收。
中间状态与验证证据
相机至少保留本次选择、切换来源和几何结果,目标模块保留期望目标、跟随目标与扰动后目标,射击保留散布前后方向,后坐力保留事件归属与弹簧前后状态。公开文章不必展示所有日志,但这些中间结果应能支持图中的结论。
例如最终射线偏右,可以先比较散布前方向。若它已经偏右,继续查目标跟随、扰动和枪口基底;若散布前正确、散布后不同,再查随机消耗和角宽。若两个方向都正确而命中不同,则查场景起点、范围、过滤和接触。跳过中间状态直接调整准星,会混淆显示误差与发射误差。
参数缺失也需要保留明确结果。当前未取得某映射的有效散布或普通弹丸数时,应记录采用回退或拒绝,而不是默认复制上一把枪。支持列表必须与实际运行路径对应,不能仅按配置数组中的条目数统计支持范围。
特殊武器的验证也按计算过程展开:电弧检查候选条件、目标选择与遮挡,再看线条表现;激光检查预热条件、枪口查询的命中点,以及松手后表现对象是否销毁;追踪检查阶段切换、反馈和失锁处理;喷射检查粒子生成、接触与到期。数值记录、场景查询和画面各自保留证据,便于查清某段演示验证了什么。
96 映射、95 后坐力默认值、85 默认散布和 79 普通准星分别是数据与功能覆盖统计。缺项可能进入回退、专用路径或未支持分支,必须按模块说明。试验场中轮换了所有映射,也不能据此认定所有武器的伤害、装填、弹药、动画和特殊能力都经过验收。
这套实现沿各阶段传递状态:相机确定观察方式,武器目标按操控性能追随并叠加持续扰动,成功射击向后坐力写入事件,散布为具体弹丸生成方向,HUD 显示目标投影与布局。武器差异由这些模块的参数共同形成,也可以按阶段分别检查。
后续还要补齐动态输入的计算与接入,扩展特殊武器的完整生命周期,并检查动画、枪口、弹道、伤害与网络状态如何衔接。炮台与无人机也有自己的目标选择、转向、供弹和返回状态,需要在后续文章中单独说明其与玩家武器的职责差异。
AI 协作复盘
AI 协助整理参数由哪些模块读取、演算数值例子,并编排机制图、截图和视频。人工核对主要检查字段含义、执行顺序和素材版本。字段名未必能说明单位,常用公式也可能与实际分支不同;旧视频是否启用了当前功能,同样需要查记录。文中的数值实现、UE 适配和运行验证分别说明,结论以对应代码和记录为依据。