UE 增强输入系统

开启增强输入系统

在UE5.1以后是默认开启的了,可以通过项目设置->输入查看。如果没有,去插件那里勾选一下。

简单使用

首先需要一个 Input Action,创建完先不用动


然后一个输入上下文 Input Mapping Context。

目前我们只需要设置一下这个Q键

首先是蓝图中的使用

  • 如果使用了虚幻的服务器,增强输入的添加映射需要在主玩家身上调用。否则不需要这一步。
  • IMC Test 是上面创建的输入映射上下文引用。
  • 可以通过Add/ Remove Mapping Context 动态的添加和删除
  • 直接在蓝图中敲 IA_Test 就能把触发事件搞出来
  • 最终效果按Q键打印文本

如果是在C++里面可以这么写。实现虚函数 SetupPlayerInputComponent

void ACwlBasePlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);

    if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
    {
        EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &ACwlBasePlayer::Move);
        // ...
    }
}

void ACwlBasePlayer::Move(const FInputActionValue& Value)
{
    FVector2D MovementVector = Value.Get<FVector2D>();
    // ...
}

触发器


点击添加触发器可以看到若干选项,我们全部用一遍。然后通过打印事件的触发来看效果

3.1 不选触发器和Down

如果啥也不选。触发效果为,按下的时候Start,然后一直触发Trigger,松开的时候 Complete。Elapsed Second是按下按键的时候开始计时,Triggered Second 是触发Trigger后开始计时
【下移 Down】和啥也不选的表现一样,这里翻译有点诡异。区别是这里可以选一个【驱动阈值】。

我们可以看这个值默认是0.5f。通过 IsActuated 接口判断传入的值是否能够足够引起触发器的兴趣。

// InputTriggers.h
UCLASS(Abstract, Blueprintable, EditInlineNew, CollapseCategories, Config = Input, defaultconfig, configdonotcheckdefaults)
class ENHANCEDINPUT_API UInputTrigger : public UObject
{
    GENERATED_BODY()
public:
    UPROPERTY(EditAnywhere, Config, BlueprintReadWrite, Category = "Trigger Settings")
    float ActuationThreshold = 0.5f;
    // ...

    /*
    * Is the value passed in sufficiently large to be of interest to the trigger.
    * This is a helper function that implements the most obvious (>=) interpretation of the actuation threshold.
    */
    UFUNCTION(BlueprintCallable, Category="Trigger")
    bool IsActuated(const FInputActionValue& ForValue) const { return ForValue.GetMagnitudeSq() >= ActuationThreshold * ActuationThreshold; }
}

然后这个传入值的计算如下。

    float GetMagnitudeSq() const
    {
        switch (GetValueType())
        {
        case EInputActionValueType::Boolean:
        case EInputActionValueType::Axis1D:
            return Value.X * Value.X;
        case EInputActionValueType::Axis2D:
            return Value.SizeSquared2D();
        case EInputActionValueType::Axis3D:
            return Value.SizeSquared();
        }

        checkf(false, TEXT("Unsupported value type for input action value!"));
        return 0.f;
    }

3.2 Pressed 和 Released

【已按下 Pressed】Trigger事只会触发一次,其他都和上面的Down一样。
【已松开 Released】按下的时候一直触发 OnGoing,松开的时候触发 Trigger,然后是 Completed。

3.3 点按 Tap

点按释放时间阈值:时间小于这个值才算点按
受时间膨胀影响:这个好理解。调用时间膨胀接口的时候,时间的计算方式。

一次正确的点按操作应该是
Start -> On Going ... -> Trigger ... -> Completed
Start,然后一直On Going,松手的时候 Trigger, Completed

如果你按的太久了
Start,然后一直On Going,然后直接Cancel

3.4 长按 Hold

保存时间阈值:时间大于这个值才是长按
为一次性:触发一次,还是每次到达阈值都触发。Hold And Released 感觉就是默认勾选了是一次性的 Hold。
受时间膨胀影响:同上

Start -> On Going -> Cancel
按下的时候触发Start,然后一直触发 On Going,然后没到达阈值的时候松开。触发Cancel
Start -> On Going ... -> Trigger ... -> Completed
按下的时候触发Start,然后一直触发 On Going,到达阈值一直触发 Trigger,直到松开的时候触发 Completed
当勾选一次性时候,Trigger只会触发一次,然后马上 Completed

3.5 长按和松开 Hold And Released

保存时间阈值:
Start -> On Going -> Cancel
按下的时候触发Start,然后一直触发 On Going,没到阈值前松手触发Cancel

Start -> On Going-> Trigger -> Completed
按下的时候触发Start,然后一直触发 On Going,到达阈值还不放,继续On Going。松手的时候 Trigger 一次,然后 Completed 一次。

3.6 脉冲 Pulse

例如,间隔:1,触发限制:5。那就触发Start -> Trigger -> 每1s触发一次Trigger,最多5次。在这期间On Going事件一直触发。
触发限制0表示无线次。

修改器

先简单测试,按键触发是Q键

  • 我们用最简单的negtive测试。按下原来是1,添加修改器后输出变成-1
  • 如果Input Mapping Context 里面也填了一个 negtive,负负得正了。

如果换为2D,同时nagtive修改器。会变成这样,同时说明单按键的时候Y是没有作用了。

LogBlueprintUserMessages: [BP_TestPlayerController_C_0] X=0.000 Y=0.000
LogBlueprintUserMessages: [BP_TestPlayerController_C_0] X=-1.000 Y=-0.000

其他的。直接鼠标挪到修改器上面,找到对应类,然后搜索 ::ModifyRaw_Implementation 刚刚好是个
所以

否定(取反) Negate

勾选x, y, z 勾了就取反,很好理解

/*
* Negate
*/

FInputActionValue UInputModifierNegate::ModifyRaw_Implementation(const UEnhancedPlayerInput* PlayerInput, FInputActionValue CurrentValue, float DeltaTime)
{
    return CurrentValue.Get<FVector>() * FVector(bX ? -1.f : 1.f, bY ? -1.f : 1.f, bZ ? -1.f : 1.f);
}

标量 Scalar

直接乘一个缩放值好理解

FInputActionValue UInputModifierScalar::ModifyRaw_Implementation(const UEnhancedPlayerInput* PlayerInput, FInputActionValue CurrentValue, float DeltaTime)
{
    // Don't try and scale bools
    if (ensureMsgf(CurrentValue.GetValueType() != EInputActionValueType::Boolean, TEXT("Scale modifier doesn't support boolean values.")))
    {
        return CurrentValue.Get<FVector>() * Scalar;
    }
    return CurrentValue;
}

Scale by Delta Time

/*
* Scale by Delta Time
*/

FInputActionValue UInputModifierScaleByDeltaTime::ModifyRaw_Implementation(const UEnhancedPlayerInput* PlayerInput, FInputActionValue CurrentValue, float DeltaTime)
{
    // Don't try and scale bools
    if (ensureMsgf(CurrentValue.GetValueType() != EInputActionValueType::Boolean, TEXT("Scale By Delta Time modifier doesn't support boolean values.")))
    {
        return CurrentValue.Get<FVector>() * DeltaTime;
    }
    return CurrentValue;
};

死区

需要填入上下阈值,还有轴类型还是圆形。
写的很复杂,其实就是最大最小限制。

/*
* Dead zones
*/

FInputActionValue UInputModifierDeadZone::ModifyRaw_Implementation(const UEnhancedPlayerInput* PlayerInput, FInputActionValue CurrentValue, float DeltaTime)
{
    EInputActionValueType ValueType = CurrentValue.GetValueType();
    if (ValueType == EInputActionValueType::Boolean)
    {
        return CurrentValue;
    }

    auto DeadZoneLambda = [this](const float AxisVal)
    {
        // We need to translate and scale the input to the +/- 1 range after removing the dead zone.
        return FMath::Min
            (1.f, 
                (
                    FMath::Max(0.f, FMath::Abs(AxisVal) - LowerThreshold) 
                    / 
                    (UpperThreshold - LowerThreshold)
                )
            ) // min括号
            * FMath::Sign(AxisVal);
    };

    FVector NewValue = CurrentValue.Get<FVector>();
    switch (Type)
    {
    case EDeadZoneType::Axial: // 轴
        NewValue.X = DeadZoneLambda(NewValue.X);
        NewValue.Y = DeadZoneLambda(NewValue.Y);
        NewValue.Z = DeadZoneLambda(NewValue.Z);
        break;
    case EDeadZoneType::Radial: // 径向 更平滑
        if (ValueType == EInputActionValueType::Axis3D)
        {
            NewValue = NewValue.GetSafeNormal() * DeadZoneLambda(NewValue.Size());
        }
        else if (ValueType == EInputActionValueType::Axis2D)
        {
            NewValue = NewValue.GetSafeNormal2D() * DeadZoneLambda(NewValue.Size2D());
        }
        else
        {
            NewValue.X = DeadZoneLambda(NewValue.X);
        }
        break;
    }

    return NewValue;
};

平滑

FInputActionValue UInputModifierSmooth::ModifyRaw_Implementation(const UEnhancedPlayerInput* PlayerInput, FInputActionValue CurrentValue, float DeltaTime)
{
    uint8 SampleCount = 1;//KeyState->SampleCountAccumulator;    // TODO: Need access to axis sample count accumulator here.

    // TODO: This could be fired multiple times if modifiers are badly set up, breaking sample count/deltatime updates.

    if(AverageValue.GetMagnitudeSq() != 0.f)
    {
        TotalSampleTime += DeltaTime;
        Samples += SampleCount;
    }

    if (DeltaTime < 0.25f)
    {
        if (Samples > 0 && TotalSampleTime > 0.0f)
        {
            // this is seconds/sample
            const float AxisSamplingTime = TotalSampleTime / Samples;
            check(AxisSamplingTime > 0.0f);

            if (CurrentValue.GetMagnitudeSq() && SampleCount > 0)
            {
                ZeroTime = 0.0f;
                if (AverageValue.GetMagnitudeSq())
                {
                    // this isn't the first tick with non-zero mouse movement
                    if (DeltaTime < AxisSamplingTime * (SampleCount + 1))
                    {
                        // smooth mouse movement so samples/tick is constant
                        CurrentValue *=  DeltaTime / (AxisSamplingTime * SampleCount);
                        SampleCount = 1;
                    }
                }

                AverageValue = CurrentValue * (1.f / SampleCount);
            }
            else
            {
                // no mouse movement received
                if (ZeroTime < AxisSamplingTime)
                {
                    // zero mouse movement is possibly because less than the mouse sampling interval has passed
                    CurrentValue = AverageValue.ConvertToType(CurrentValue) * (DeltaTime / AxisSamplingTime);
                }
                else
                {
                    ClearSmoothedAxis();
                }

                ZeroTime += DeltaTime;      // increment length of time we've been at zero
            }
        }
    }
    else
    {
        // if we had an abnormally long frame, clear everything so it doesn't distort the results
        ClearSmoothedAxis();
    }

    // TODO: FortPlayerInput clears the sample count accumulator here!
    //KeyState->SampleCountAccumulator = 0;

    return CurrentValue;
}

响应曲线-指数

/*
* Response curves
*/

FInputActionValue UInputModifierResponseCurveExponential::ModifyRaw_Implementation(const UEnhancedPlayerInput* PlayerInput, FInputActionValue CurrentValue, float DeltaTime)
{
    FVector ResponseValue = CurrentValue.Get<FVector>();
    switch (CurrentValue.GetValueType())
    {
    case EInputActionValueType::Axis3D:
        ResponseValue.Z = CurveExponent.Z != 1.f ? FMath::Sign(ResponseValue.Z) * FMath::Pow(FMath::Abs(ResponseValue.Z), CurveExponent.Z) : ResponseValue.Z;
        //[[fallthrough]];
    case EInputActionValueType::Axis2D:
        ResponseValue.Y = CurveExponent.Y != 1.f ? FMath::Sign(ResponseValue.Y) * FMath::Pow(FMath::Abs(ResponseValue.Y), CurveExponent.Y) : ResponseValue.Y;
        //[[fallthrough]];
    case EInputActionValueType::Axis1D:
        ResponseValue.X = CurveExponent.X != 1.f ? FMath::Sign(ResponseValue.X) * FMath::Pow(FMath::Abs(ResponseValue.X), CurveExponent.X) : ResponseValue.X;
        break;
    }
    return ResponseValue;
};

响应曲线-用户自定义

FInputActionValue UInputModifierResponseCurveUser::ModifyRaw_Implementation(const UEnhancedPlayerInput* PlayerInput, FInputActionValue CurrentValue, float DeltaTime)
{
    FVector ResponseValue = CurrentValue.Get<FVector>();
    switch (CurrentValue.GetValueType())
    {
    case EInputActionValueType::Axis3D:
        ResponseValue.Z = ResponseZ ? ResponseZ->GetFloatValue(ResponseValue.Z) : 0.0f;
        //[[fallthrough]];
    case EInputActionValueType::Axis2D:
        ResponseValue.Y = ResponseY ? ResponseY->GetFloatValue(ResponseValue.Y) : 0.0f;
        //[[fallthrough]];
    case EInputActionValueType::Axis1D:
    case EInputActionValueType::Boolean:
        ResponseValue.X = ResponseX ? ResponseX->GetFloatValue(ResponseValue.X) : 0.0f;
        break;
    }
    return ResponseValue;
};

视野缩放

/*
* FOV
*/

FInputActionValue UInputModifierFOVScaling::ModifyRaw_Implementation(const UEnhancedPlayerInput* PlayerInput, FInputActionValue CurrentValue, float DeltaTime)
{
    const APlayerController* PC = PlayerInput->GetOuterAPlayerController();

    if (!PC)
    {
        return CurrentValue;
    }

    const float FOVAngle = PC->PlayerCameraManager ? PC->PlayerCameraManager->GetFOVAngle() : 1.f;
    float Scale = FOVScale;

    switch(FOVScalingType)
    {
    case EFOVScalingType::Standard:
        // TODO: Fortnite falls back to old style FOV scaling for mouse input. Presumably for back compat, but this needs checking.
        if (PC->PlayerCameraManager)
        {
            // This is the proper way to scale based off FOV changes.
            const float kPlayerInput_BaseFOV = 80.0f;
            const float BaseHalfFOV = kPlayerInput_BaseFOV * 0.5f;
            const float HalfFOV = FOVAngle * 0.5f;
            const float BaseTanHalfFOV = FMath::Tan(FMath::DegreesToRadians(BaseHalfFOV));
            const float TanHalfFOV = FMath::Tan(FMath::DegreesToRadians(HalfFOV));

            check(BaseTanHalfFOV > 0.0f);
            Scale *= (TanHalfFOV / BaseTanHalfFOV);
        }
        break;
    case EFOVScalingType::UE4_BackCompat:
        Scale *= FOVAngle;
        break;
    default:
        checkf(false, TEXT("Unsupported FOV scaling type '%s'"), *UEnum::GetValueAsString(TEXT("EnhancedInput.EFovScalingType"), FOVScalingType));
        break;
    }

    return CurrentValue * Scale;
}

到世界空间

左手系 右手系

/*
* ToWorldSpace axis swizzling
*/

FInputActionValue UInputModifierToWorldSpace::ModifyRaw_Implementation(const UEnhancedPlayerInput* PlayerInput, FInputActionValue CurrentValue, float DeltaTime)
{
    FVector Converted = CurrentValue.Get<FVector>();
    switch (CurrentValue.GetValueType())
    {
    case EInputActionValueType::Axis3D:
        // Input Device Z = World Forward (X), Device X = World Right (Y), Device Y = World Up (Z)
        Converted = FVector(Converted.Z, Converted.X, Converted.Y);
        break;
    case EInputActionValueType::Axis2D:
        // Swap axes so Input Device Y axis becomes World Forward (X), Device X becomes World Right (Y)
        Swap(Converted.X, Converted.Y);
        break;
    case EInputActionValueType::Axis1D:
    case EInputActionValueType::Boolean:
        // No conversion required
        break;
    }
    return FInputActionValue(CurrentValue.GetValueType(), Converted);
}

拌合输入轴值

/*
* Generic Axis swizzling
*/ 

FInputActionValue UInputModifierSwizzleAxis::ModifyRaw_Implementation(const UEnhancedPlayerInput* PlayerInput, FInputActionValue CurrentValue, float DeltaTime)
{
    FVector Value = CurrentValue.Get<FVector>();
    switch (Order)
    {
    case EInputAxisSwizzle::YXZ:
        Swap(Value.X, Value.Y);
        break;
    case EInputAxisSwizzle::ZYX:
        Swap(Value.X, Value.Z);
        break;
    case EInputAxisSwizzle::XZY:
        Swap(Value.Y, Value.Z);
        break;
    case EInputAxisSwizzle::YZX:
        Value = FVector(Value.Y, Value.Z, Value.X);
        break;
    case EInputAxisSwizzle::ZXY:
        Value = FVector(Value.Z, Value.X, Value.Y);
        break;
    }
    return FInputActionValue(CurrentValue.GetValueType(), Value);
}

自定义Input Modify

蓝图版本

创建一个InputModify蓝图,然后重写接口即可

C++版本

直接抄第4点的代码即可。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇