后坐力效果的实现方式

该组件最终修改的是 AFmPlayerController::RotationInput 属性来实现后坐力效果。

核心修改位置

1. FPP后坐力应用(第407-419行)

const FVector DeltaRecoilRotationInVec = CurrentRecoilRotation - LastFrameRecoilRotation;
const FRotator DeltaRecoilRotation(DeltaRecoilRotationInVec.Y, DeltaRecoilRotationInVec.Z, DeltaRecoilRotationInVec.X);
PlayerCtrl->RotationInput += DeltaRecoilRotation;

2. TPP后坐力应用

射击中(第1178-1180行):

PlayerCtrl->RotationInput.Yaw += DeltaYaw;
PlayerCtrl->RotationInput.Pitch += DeltaPitch;

恢复中(第1148-1158行):

playerCtrl->RotationInput.Yaw -= yaw;
playerCtrl->RotationInput.Pitch -= dPitch;

3. Roll校正(第456行)

PlayerCtrl->RotationInput += DeltaRecoilRotation;

RotationInput 的作用机制

flowchart LR
    A[后坐力计算] --> B[RotationInput<br/>累积增量]
    B --> C[PlayerController<br/>每帧处理输入]
    C --> D[应用到相机旋转<br/>CameraManager]
    D --> E[相机视角变化<br/>后坐力效果]
    
    style B fill:#ff9,stroke:#333,stroke-width:3px

RotationInput 是 APlayerController 的成员变量,类型为 FRotator,用于存储待应用的旋转输入增量。

工作原理

阶段操作说明
计算RotationInput += DeltaRecoilRotation将后坐力增量累加到输入缓冲区
应用引擎自动处理APlayerController::UpdateRotation() 每帧读取并应用
效果相机旋转相机根据 RotationInput 调整朝向

为什么使用 RotationInput?

优势

  1. 与玩家输入统一处理

    • 玩家鼠标输入 → RotationInput
    • 后坐力效果 → RotationInput
    • 两者自然融合,不会冲突
  2. 自动同步到相机

    • APlayerCameraManager 会自动读取并应用
    • 无需手动修改相机旋转
  3. 支持输入叠加

    // 玩家向下压枪 + 后坐力向上 = 相互抵消
    RotationInput.Pitch += PlayerInput;    // 玩家输入
    RotationInput.Pitch += RecoilDelta;    // 后坐力

数据流向

sequenceDiagram
    participant W as Weapon
    participant C as RecoilComponent
    participant P as PlayerController
    participant Cam as CameraManager
    
    W->>C: OnWeaponFireStart()
    C->>C: 计算后坐力增量<br/>DeltaRecoilRotation
    
    loop 每帧
        C->>P: RotationInput += Delta
        P->>Cam: UpdateRotation()
        Cam->>Cam: 应用到相机视角
    end
    
    W->>C: OnWeaponFireStop()
    C->>C: 进入恢复状态
    C->>P: RotationInput -= RecoverAmount

总结

该组件通过修改 AFmPlayerController::RotationInput 实现后坐力效果:

  • 本质:将后坐力伪装成”玩家输入”
  • 优势:与玩家真实输入统一处理,自然融合
  • 效果:相机视角根据后坐力增量自动调整

这是一种非侵入式的设计,不需要直接修改相机或角色旋转,而是利用引擎现有的输入处理机制。

后坐力曲线提供的信息

  • 基于射击次数的后坐力数值:曲线的横轴是射击次数(ShotCount),纵轴是后坐力向量(FVector)。这意味着后坐力会随着连续射击的次数而变化(通常模拟后坐力累积或模式)。
  • 垂直和水平分量:返回的 FVector 的 Y 分量通常代表垂直后坐力,Z 分量代表水平后坐力。
  • 最大后坐力参考:通过读取曲线在弹匣最大弹药量处的值,可以获取该武器满弹匣射击时的最大后坐力累积值,用于判断后坐力回复逻辑。
  • 随机范围:RotationRangeCurve 提供了后坐力的随机变化范围,用于增加后坐力的不可预测性。

回答用户的问题:

用户问的是“后坐力曲线提供了什么信息”。

根据代码分析,后坐力曲线(RotationCurve)主要提供了每一发子弹射击时产生的标准后坐力旋转值。这个值是基于当前的射击次数(ShotCount)从曲线中采样得到的。

具体来说:

  1. 输入:射击次数(ShotCount)。
  2. 输出:一个 FVector,其中:
    • Y 分量:代表垂直方向的后坐力。
    • Z 分量:代表水平方向的后坐力。
  3. 用途:
    • 计算当前射击相对于上一发射击的后坐力差值 (Diff)。
    • 这个差值会经过各种系数(如后坐力控制属性、状态乘数、随机范围等)的调整,最终应用到玩家的视角旋转上。
    • 用于计算最大后坐力,以决定后坐力回复的行为。

此外,还有一个 RotationRangeCurve,它提供了后坐力的随机变化范围,用于让每一发的后坐力在标准值附近有一定的随机波动。

构建回答:

我将直接回答用户的问题,指出后坐力曲线提供了基于射击次数的标准后坐力向量,并解释其分量含义和用途。
根据提供的代码,后坐力曲线(RotationCurve)主要提供了基于射击次数的标准后坐力旋转值。

具体来说,它提供了以下信息:

  1. 输入与输出:

    • 输入:当前的射击次数(ShotCount)。
    • 输出:一个 FVector 向量,代表该发子弹的标准后坐力旋转。
  2. 向量分量的含义:

    • Y 分量:代表垂直后坐力(Pitch)。
    • Z 分量:代表水平后坐力(Yaw)。
  3. 主要用途:

    • 计算单发增量:在 GetShotRecoilRotation 函数中,代码通过计算当前射击和上一发射击在曲线上的差值 (Diff = ShotStandardRotation - PrevShotStandardRotation),来获得这一发子弹产生的后坐力增量。这个增量随后会乘以各种系数(如后坐力控制属性、蹲下/趴下乘数、随机范围等)应用到玩家视角上。
    • 计算最大后坐力:在 ShouldWholeReturn 函数中,通过读取曲线在弹匣最大弹药量处的值,可以获取该武器满弹匣射击时的最大后坐力累积值,用于判断后坐力回复逻辑(例如判断当前后坐力是否在预期范围内)。

此外,代码中还使用了 RotationRangeCurve,它提供了后坐力的随机变化范围,用于让每一发的后坐力在标准值附近有一定的随机波动,增加真实感。

后坐力恢复打断

[任务规划完成]
用户问的是”后坐力回复打断的逻辑”。我需要从提供的代码中找到相关的逻辑。

从代码中,我可以看到:

  1. 在 Input_FsmInputMouseChanged 函数中有打断逻辑:
void UCharacterWeaponRecoilComponent::Input_FsmInputMouseChanged(float YawInput, float PitchInput)
{
    if (FppRecoilState == EFppRecoilState::Returning && !mFppRecoilReturn.IsStable())
    {
        FRecoilState RecoilState;
        if (GetRecoilState(RecoilState) && RecoilState.bReturnInterruptByInput && YawInput + PitchInput > RecoilState.ReturnInterruptThreshold)
        {
            mFppRecoilReturn.SetStableExceptRoll();
        }
    }
    else if (FppRecoilState == EFppRecoilState::Shooting || FppRecoilState == EFppRecoilState::Holding)
    {
        AccumulateFireManualRotation += FRotator(PitchInput, YawInput, 0); 
    }
}

这个函数处理了玩家输入打断后坐力恢复的逻辑。

让我详细分析这个逻辑:

  1. 触发条件:

    • 当前状态必须是 EFppRecoilState::Returning(恢复状态)
    • mFppRecoilReturn 不稳定(即还在恢复过程中)
    • 配置允许打断(RecoilState.bReturnInterruptByInput 为 true)
    • 玩家输入超过阈值(YawInput + PitchInput > RecoilState.ReturnInterruptThreshold)
  2. 打断行为:

    • 调用 mFppRecoilReturn.SetStableExceptRoll(),这会停止 Pitch 和 Yaw 的恢复,但保留 Roll 的恢复
  3. 其他情况:

    • 如果在射击或保持状态,玩家的输入会被记录到 AccumulateFireManualRotation

我注意到打断逻辑需要满足多个严格条件。玩家必须在后坐力恢复阶段,且输入强度超过预设阈值。这种设计确保了只有在玩家主动且显著地移动视角时,才会中断后坐力恢复过程。
根据代码,后坐力回复打断的逻辑主要在 Input_FsmInputMouseChanged 函数中实现:

打断逻辑核心代码

void UCharacterWeaponRecoilComponent::Input_FsmInputMouseChanged(float YawInput, float PitchInput)
{
    if (FppRecoilState == EFppRecoilState::Returning && !mFppRecoilReturn.IsStable())
    {
        FRecoilState RecoilState;
        if (GetRecoilState(RecoilState) && 
            RecoilState.bReturnInterruptByInput && 
            YawInput + PitchInput > RecoilState.ReturnInterruptThreshold)
        {
            mFppRecoilReturn.SetStableExceptRoll();
        }
    }
    else if (FppRecoilState == EFppRecoilState::Shooting || 
             FppRecoilState == EFppRecoilState::Holding)
    {
        AccumulateFireManualRotation += FRotator(PitchInput, YawInput, 0); 
    }
}

打断流程图

flowchart TD
    A[玩家鼠标输入] --> B{当前状态判断}
    
    B -->|Returning状态| C{是否还在恢复中?}
    B -->|Shooting/Holding状态| D[记录到AccumulateFireManualRotation]
    
    C -->|是| E{配置允许打断?}
    C -->|否| F[不处理]
    
    E -->|bReturnInterruptByInput = true| G{输入超过阈值?}
    E -->|false| F
    
    G -->|YawInput + PitchInput > Threshold| H[调用SetStableExceptRoll]
    G -->|否| F
    
    H --> I[停止Pitch和Yaw恢复]
    I --> J[保留Roll继续恢复]
    
    style H fill:#ff6b6b
    style I fill:#ffd93d
    style J fill:#6bcf7f

详细说明

1. 触发条件(必须同时满足)

条件说明
状态检查FppRecoilState == EFppRecoilState::Returning
恢复未完成!mFppRecoilReturn.IsStable()
配置允许RecoilState.bReturnInterruptByInput == true
输入强度YawInput + PitchInput > RecoilState.ReturnInterruptThreshold

2. 打断行为

调用 mFppRecoilReturn.SetStableExceptRoll() 后:

  • ✅ 停止 Pitch 恢复:垂直后坐力不再自动回正
  • ✅ 停止 Yaw 恢复:水平后坐力不再自动回正
  • ⚠️ 保留 Roll 恢复:Roll 角度继续校正(避免相机倾斜累积)

3. 设计意图

graph LR
    A[射击停止] --> B[进入Holding状态]
    B --> C[保持一段时间]
    C --> D[进入Returning状态]
    D --> E[自动恢复后坐力]
    E --> F{玩家主动输入?}
    F -->|是| G[打断恢复<br/>玩家接管控制]
    F -->|否| H[继续自动恢复]
    
    style G fill:#ff6b6b

核心思想:

  • 后坐力恢复是为了让相机自动回到射击前的位置
  • 但如果玩家主动移动鼠标(压枪或瞄准其他目标),说明玩家想自己控制相机
  • 此时应该立即停止自动恢复,让玩家完全接管相机控制
  • Roll 例外:Roll 角度是异常倾斜,玩家通常不会主动调整,所以继续自动校正

4. 输入记录

在 Shooting 或 Holding 状态下,玩家输入会被记录:

AccumulateFireManualRotation += FRotator(PitchInput, YawInput, 0);

这个累积值用于:

  • 计算射击过程中的总旋转变化
  • 在 CalculateWholeDiff() 中判断是否应该完全恢复后坐力
  • 区分”后坐力导致的旋转”和”玩家主动输入导致的旋转”

5. 相关配置参数

在 FRecoilState 结构中:

bool bReturnInterruptByInput;          // 是否允许输入打断恢复
float ReturnInterruptThreshold;        // 打断的输入阈值

这些参数可以在武器配置中针对不同武器或瞄准状态进行定制。