🚀 UE5.6 is out! 🚀 All my plugins will support it in next update!  

Get it!

Mover的输入机制

avatar`
Yuewu(罗传月武)
Updated: May 28, 2025

摘要

本文介绍Mover2.0中的输入处理机制,什么是输入命令上下文,如何通过上下文传递数据到Mover系统,以及如何自定义InputProducer以操控上下文。

介绍

不像CharacterMovementComponent(继承自PawnMovementComponent),你只需要调用AddInputVector即可驱动角色的移动。

在Mover中,移动输入的处理变得更加灵活,但其代价是用户代码会变得更加底层且复杂。

输入命令上下文

在Mover中,将用户输入传递到Mover系统的主要方式是通过InputCmdContext(输入命令上下文),后文简称为Context。

你可以将Context简单理解为是一个MoverData容器,可以通过蓝图/C++的API对其包含的数据进行增删改查。

这里的MoverData指的是:继承自FMoverDataStructBase的结构体,因此,必须通过C++新增MoverData类型。

Context可以容纳多个类型的输入参数结构体。

什么是InputProducer?

任意实现了MoverInputProducerInterface的UObject可以视为一个InputProducer

MoverComponent内置一个ProduceInput函数,会在每帧调用。

1// Get latest local input prior to simulation step. Called by backend system on owner's instance (autonomous or authority).
2 void ProduceInput(const int32 DeltaTimeMS, FMoverInputCmdContext* Cmd);

同时还持有一个实现了MoverInputProducerInterface的对象的引用。上面的ProduceInput默认实现是通过下面的InputProducer对象来完成具体的输入处理逻辑。这样的设计,允许用户将输入逻辑从Mover系统中解耦。

1 /** Optional object for producing input cmds. Typically set at BeginPlay time. If not specified, defaulted input will be used. */
2 UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Mover, meta = (MustImplement = "/Script/Mover.MoverInputProducerInterface"))
3 TObjectPtr<UObject> InputProducer;
默认情况下,MoverComponent会在Beginplay时,尝试从所属Actor或其组件树中查询实现了该接口的对象。
由于该对象是公开的,且蓝图可读写,你可以随时将任意实现了MoverInputProducerInterface的对象赋值给这个成员即可。

用户只需要关心,如何将输入命令控制参数通过ProduceInput函数传递给InputCmdContext(输入命令上下文)。

实现MoverInputProducerInterface接口

蓝图和C++都可以实现MoverInputProducerInterface

1class IMoverInputProducerInterface : public IInterface
2{
3 GENERATED_BODY()
4
5public:
6 /** Contributes additions to the input cmd for this simulation frame. Typically this is translating accumulated user input (or AI state) into parameters that affect movement. */
7 UFUNCTION(BlueprintNativeEvent)
8 void ProduceInput(int32 SimTimeMs, FMoverInputCmdContext& InputCmdResult);
9};

官方案例

在MoverExample插件中,MoverExamplesCharacter这个Class实现了MoverInputProducerInterface,并演示了如何绑定InputAction,缓存Action的值,并在OnProduceInput中,根据配置,角色当前的状态,InputAction的值,构建了针对于CharacterDefaultInputs。你可以参考它以了解基本的用法,但请参考后文了解如何设计你的InputProducer。

设计你的InputProducer

我推荐通过Component来实现该接口,而不是通过Actor。基于组件开发能提升代码的可移植性。

使用单独的组件,实现业务的控制逻辑,通过组件的继承,从而实现不同的控制策略。

1UCLASS(ClassGroup=GMS, BlueprintType, Blueprintable, meta=(BlueprintSpawnableComponent), DisplayName="GMS Movement System Component(Mover)")
2class GAME_API UMoverControlSystem : public UActorComponent, public IMoverInputProducerInterface
3{
4 GENERATED_BODY()
5public:
6 virtual void ProduceInput_Implementation(int32 SimTimeMs, FMoverInputCmdContext& InputCmdResult) override;
7protected:
8 // Override this function in native class to author input for the next simulation frame. Consider also calling Super method.
9 UFUNCTION(BlueprintNativeEvent, Category="GMS|MovementSystem")
10 FMoverInputCmdContext OnProduceInput(float DeltaMs, FMoverInputCmdContext InputCmd);
11}
1void UMoverControlSystem::ProduceInput_Implementation(int32 SimTimeMs, FMoverInputCmdContext& InputCmdResult)
2{
3 InputCmdResult = OnProduceInput(static_cast<float>(SimTimeMs), InputCmdResult);
4}
5
6FMoverInputCmdContext UMoverControlSystem::OnProduceInput_Implementation(float DeltaMs, FMoverInputCmdContext InputCmdResult)
7{
8 // Your game control logic, this is where you implements your control system, feeding inputs to context.
9 // 你的游戏控制逻辑,在这里实现你的控制系统,将输入传递给context.
10 FCharacterDefaultInputs& CharacterInputs = InputCmdResult.InputCollection.FindOrAddMutableDataByType<FCharacterDefaultInputs>();
11
12 // Constructs inputs depends on your game design.
13 // 根据你的游戏设计,构造合适的默认输ru参数。
14 CharacterInputs.SetMoveInput(EMoveInputType::DirectionalIntent,RawMoveInputValueFromInputAction);
15
16 // Other kinds of inputs (abilities input, dash,double jump etc...)
17 // 其他输入,技能输入,如冲刺,二段跳等。
18 FYourCustomOtherInputs& OtherInputs = InputCmdResult.InputCollection.FindOrAddMutableDataByType<FYourCustomOtherInputs>();
19 OtherInputs.IsDashPressed = RawDashInputValueFromInputAction;
20}
21

你可以通过你的组件衍生类,进行逻辑控制。

构建输入控制系统

默认角色输入

Mover插件已经提供CharacterDefaultInputs,该结构体定义了用于控制MoverComponent的所需的基本输入参数。

1// Data block containing all inputs that need to be authored and consumed for the default Mover character simulation
2USTRUCT(BlueprintType)
3struct MOVER_API FCharacterDefaultInputs : public FMoverDataStructBase
4{
5protected:
6 UPROPERTY(BlueprintReadWrite, Category = Mover)
7 EMoveInputType MoveInputType;
8
9 // Representing the directional move input for this frame. Must be interpreted according to MoveInputType. Relative to MovementBase if set, world space otherwise. Will be truncated to match network serialization precision.
10 UPROPERTY(BlueprintReadWrite, Category = Mover)
11 FVector MoveInput;
12
13public:
14 // Facing direction intent, as a normalized forward-facing direction. A zero vector indicates no intent to change facing direction. Relative to MovementBase if set, world space otherwise.
15 UPROPERTY(BlueprintReadWrite, Category = Mover)
16 FVector OrientationIntent;
17
18 // World space orientation that the controls were based on. This is commonly a player's camera rotation.
19 UPROPERTY(BlueprintReadWrite, Category = Mover)
20 FRotator ControlRotation;
21
22 // Used to force the Mover actor into a different movement mode
23 UPROPERTY(BlueprintReadWrite, Category = Mover)
24 FName SuggestedMovementMode;
25
26 // Specifies whether we are using a movement base, which will affect how move inputs are interpreted
27 UPROPERTY(BlueprintReadWrite, Category = Mover)
28 bool bUsingMovementBase;
29
30 // Optional: when moving on a base, input may be relative to this object
31 UPROPERTY(BlueprintReadWrite, Category = Mover)
32 TObjectPtr<UPrimitiveComponent> MovementBase;
33
34 // Optional: for movement bases that are skeletal meshes, this is the bone we're based on. Only valid if MovementBase is set.
35 UPROPERTY(BlueprintReadWrite, Category = Mover)
36 FName MovementBaseBoneName;
37
38 UPROPERTY(BlueprintReadWrite, Category = Mover)
39 bool bIsJumpJustPressed;
40 UPROPERTY(BlueprintReadWrite, Category = Mover)
41 bool bIsJumpPressed;
42};
43

简而言之,用户可以通过动态地构建FCharacterDefaultInputs结构体来提供输入参数,然后将其传入Context即可。

Mover输入机制.输入处理案例

桥接输入系统

无论InputProducer是通过Actor,还是Component实现,它都需要知道玩家实际的输入Action值,并根据这些Action值去构建最终的输入参数。

你可以在实现了MoverInputProducerInterface的组件(控制系统)上定义所需的InputAction值,并通过EnhancedAction事件回调,将这些值赋值到组件上,或者将其进一步封装。

其他类型的输入

默认参数能够满足角色基本的控制需求,如移动方向,跳跃请求等。但具体的项目,一般还需要各种其他输入,如Dash,Crouch,等Ability输入。

这时候就需要新增继承自FMoverDataStructBase的结构体用于封装所需的输入参数,并将其加入Context。

在MoverExamples中,你就能看到一个示例Ability输入的案例。

1// Data block containing extended ability inputs used by MoverExamples characters
2USTRUCT(BlueprintType)
3struct MOVEREXAMPLES_API FMoverExampleAbilityInputs : public FMoverDataStructBase
4{
5 GENERATED_USTRUCT_BODY()
6
7 UPROPERTY(BlueprintReadWrite, Category = Mover)
8 bool bIsDashJustPressed = false;
9
10 UPROPERTY(BlueprintReadWrite, Category = Mover)
11 bool bIsAimPressed = false;
12
13 UPROPERTY(BlueprintReadWrite, Category = Mover)
14 bool bIsVaultJustPressed = false;
15
16 UPROPERTY(BlueprintReadWrite, Category = Mover)
17 bool bWantsToStartZiplining = false;
18
19 UPROPERTY(BlueprintReadWrite, Category = Mover)
20 bool bWantsToBeCrouched = false;
21};
Mover输入机制.其他输入处理案例

处理输入

你有一些输入参数结构体,你也通过InputProducer将这些参数加入到了Context,接下来干什么?

接下来,你可以通过绑定MoverComponent上的OnPreSimulationTick事件,并在回调中去处理这些输入参数。并添加各种运动效果。

底层细节

在引擎底层,MoverComponent上的ProduceInputNetworkPrediction插件中的NetworkPredictionWorldManager进行调用。

它是一个世界子系统,随着世界的生命周期进行更新。我们会在以后的文章中更深入地去了解这一部分的内容。