Lyra项目中的输入系统

我们来详细拆解 Lyra 的输入系统。Lyra 的输入系统非常强大和模块化,是其架构的核心亮点之一。它不仅仅是将按键映射到操作,而是构建了一个与 Gameplay Tag 深度集成的、可扩展的框架。

核心设计思想

Lyra 输入系统的核心思想是:将原始的按键输入,转化为具有语义的 GameplayTag,然后由各个系统(如技能系统、UI系统)根据这些 Tag 来做出响应。 这实现了输入与具体逻辑的解耦


详细流程与步骤

整个输入流程可以分为以下几个关键步骤和组成部分:

第1步:基础输入设置 (IMC)

文件位置: Config/DefaultInput.ini 或 在内容浏览器中搜索 IMC_Default

  1. 输入映射上下文(Input Mapping Context, IMC): 这是一个资源,定义了 按键/输入事件输入动作 之间的映射关系。

  2. 输入动作(Input Action):IA_Move, IA_Jump, IA_Ability_Sprint。这些动作是蓝图和C++中可以识别的逻辑概念,而不是具体的按键。

IMC_Default 中,你会看到类似这样的映射:

  • W / S / A / D 键 -> IA_Move (值为 2D 向量 (X, Y))

  • 空格键 -> IA_Jump (值为 Button Press)

  • 左键点击 -> IA_Ability_Primary (值为 Button Press)

第2步:增强输入与标签绑定

这是 Lyra 输入系统的精髓所在。

  1. LyraInputConfig 数据资产:

    • 位置: 搜索 DA_InputConfig_Character 等。

    • 作用: 这个资产建立了 输入动作Gameplay Tag 之间的桥梁。它为每个 Input Action 分配一个或多个 Gameplay Tag

    • 例如:

  2. LyraInputComponent

    • 位置: 查看 LyraHeroComponent 是如何设置输入的。

    • 作用: 这是一个增强的 Input Component,它知道如何根据 LyraInputConfig 将输入事件与 Gameplay Tag 关联起来。当玩家按下按键时,LyraInputComponent 会查找对应的 Input Action,然后从 LyraInputConfig 中找到与之关联的 Gameplay Tag,并广播这个 Tag。

第3步:处理输入 - LyraHeroComponent

这是输入流程的核心控制器,通常存在于玩家控制的 Pawn 上。

文件: LyraHeroComponent.cpp / .h

它的主要工作流程在 BindInputsInput_AbilityInputTagPressed 等函数中:

  1. 绑定增强输入:

    cpp 复制代码
    // LyraHeroComponent.cpp
    void ULyraHeroComponent::BindInputs(UEnhancedInputComponent* EnhancedInputComponent)
    {
        // ... 绑定每个 Input Action 到对应的处理函数 ...
        EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &ThisClass::Input_Move);
        EnhancedInputComponent->BindAction(InputConfig->InputActions[0].InputAction, ETriggerEvent::Triggered, this, &ThisClass::Input_AbilityInputTagPressed);
        // ...
    }
    复制代码
  2. 处理向量输入(如移动):

    cpp 复制代码
    // LyraHeroComponent.cpp
    void ULyraHeroComponent::Input_Move(const FInputActionValue& InputActionValue)
    {
        APawn* Pawn = GetPawn<APawn>();
        AController* Controller = Pawn ? Pawn->GetController() : nullptr;
        if (Controller) {
            const FVector2D Value = InputActionValue.Get<FVector2D>();
            const FRotator MovementRotation(0.0f, Controller->GetControlRotation().Yaw, 0.0f);
            
            if (Value.X != 0.0f) {
                const FVector MovementDirection = MovementRotation.RotateVector(FVector::RightVector);
                Pawn->AddMovementInput(MovementDirection, Value.X);
            }
            
            if (Value.Y != 0.0f) {
                const FVector MovementDirection = MovementRotation.RotateVector(FVector::ForwardVector);
                Pawn->AddMovementInput(MovementDirection, Value.Y);
            }
        }
    }
    复制代码
  3. 处理技能输入(转换为 Gameplay Tag):

    cpp 复制代码
    // LyraHeroComponent.cpp
    void ULyraHeroComponent::Input_AbilityInputTagPressed(FGameplayTag InputTag)
    {
        // 关键:将携带了 Input Tag 的事件广播出去
        if (AbilitySystemComponent) {
            // 通常这里会调用 AbilitySystemComponent->AbilityLocalInputPressed(...)
            // 但在 Lyra 中,它可能通过一个委托来通知其他组件
            OnAbilityInputPressed.Broadcast(InputTag);
        }
    }
    复制代码
第4步:输入与 Gameplay Ability System (GAS) 的连接

这是输入的最终消费者之一。

  1. LyraGameplayAbility

    • AbilityTags: 每个 Gameplay Ability 蓝图上都有 Ability Tags。例如,一个"冲刺"技能会拥有 Ability.Sprint 标签。

    • InputTag: 在技能的详细信息中,可以指定一个 Input Tag(如 InputTag.Ability.Sprint)。

  2. AbilitySystemComponent 的响应:

    • LyraHeroComponent 广播了 InputTag.Ability.Sprint 后,AbilitySystemComponent 会查找所有:

      • 已激活的 并且

      • Input Tag 与收到的 Tag 匹配的 Gameplay Ability

    • 然后,GAS 会调用该技能的 InputPressed 事件,从而触发技能的执行。


关键代码实现流程(简化版)

以下是一个高度简化的代码流程,展示了从按键到技能触发的核心路径:

1. 在 PlayerController 或 Pawn 中设置 IMC:

cpp 复制代码
// MyPlayerController.cpp
void AMyPlayerController::BeginPlay()
{
    Super::BeginPlay();
    
    // 获取 LyraHeroComponent
    if (ULyraHeroComponent* HeroComponent = GetPawn()->FindComponentByClass<ULyraHeroComponent>())
    {
        // 假设 HeroComponent 内部会处理 IMC 的添加和输入绑定
        HeroComponent->InitializePlayerInput(GetLocalPlayer()->GetEnhancedInputLocalPlayerSubsystem());
    }
}
复制代码

2. LyraHeroComponent 的核心输入处理:

cpp 复制代码
// LyraHeroComponent.cpp
void ULyraHeroComponent::InitializePlayerInput(UEnhancedInputLocalPlayerSubsystem* InputSubsystem)
{
    if (!InputSubsystem || !InputConfig) return;

    // 1. 添加输入映射上下文
    InputSubsystem->AddMappingContext(InputMappingContext, Priority);

    // 2. 绑定输入动作 (这通常在 BindInputs 中完成)
    if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(InputComponent))
    {
        BindInputs(EnhancedInputComponent);
    }
}

void ULyraHeroComponent::BindInputs(UEnhancedInputComponent* EnhancedInputComponent)
{
    // 绑定移动
    if (MoveAction)
    {
        EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &ULyraHeroComponent::Input_Move);
    }

    // 动态绑定 InputConfig 中的所有输入动作
    for (const FLyraInputAction& Action : InputConfig->InputActions)
    {
        if (Action.InputAction)
        {
            // 绑定到同一个处理函数,并传入对应的 Input Tag
            EnhancedInputComponent->BindAction(Action.InputAction, ETriggerEvent::Triggered, this, &ULyraHeroComponent::Input_AbilityInputTagPressed, Action.InputTag);
        }
    }
}

// 处理技能输入的核心函数
void ULyraHeroComponent::Input_AbilityInputTagPressed(FGameplayTag InputTag)
{
    // GLOBAL BROADCAST: 任何监听此事件的系统都可以做出反应
    OnAbilityInputPressed.Broadcast(InputTag);

    // SPECIFIC TO GAS: 直接通知 GAS
    if (ULyraAbilitySystemComponent* LyraASC = GetLyraAbilitySystemComponent())
    {
        LyraASC->AbilityInputTagPressed(InputTag);
    }
}
复制代码

3. AbilitySystemComponent 的响应:

cpp 复制代码
// LyraAbilitySystemComponent.cpp
void ULyraAbilitySystemComponent::AbilityInputTagPressed(FGameplayTag InputTag)
{
    // 遍历所有已激活的、被授予的技能
    for (const FGameplayAbilitySpec& AbilitySpec : ActivatableAbilities.Items)
    {
        if (AbilitySpec.Ability && (AbilitySpec.DynamicAbilityTags.HasTagExact(InputTag)))
        {
            // 找到输入标签匹配的技能,触发其"输入按下"事件
            AbilitySpecInputPressed(AbilitySpec);
        }
    }
}
复制代码

总结流程图

text

复制代码
[玩家按下 W 键]
        |
        v
[Enhanced Input 系统] -> 触发 `IA_Move` 动作 (值为 Vector2D)
        |
        v
[LyraInputComponent] -> 调用 `LyraHeroComponent::Input_Move` 函数 -> 驱动 `Pawn` 移动

text

复制代码
[玩家按下 Left Shift 键]
        |
        v
[Enhanced Input 系统] -> 触发 `IA_Ability_Sprint` 动作
        |
        v
[LyraInputComponent] -> 调用 `LyraHeroComponent::Input_AbilityInputTagPressed(InputTag.Ability.Sprint)`
        |
        v
[LyraHeroComponent] -> 广播 `OnAbilityInputPressed(InputTag.Ability.Sprint)` 事件
        |
        v
[LyraAbilitySystemComponent] -> 接收到事件,查找拥有 `InputTag.Ability.Sprint` 的已激活技能
        |
        v
[找到 "Sprint" Gameplay Ability] -> 触发其 `InputPressed` 事件 -> 技能逻辑开始执行(消耗体力,增加移速)

通过这个流程,Lyra 实现了一个清晰、灵活且强大的输入处理管道,完美地支持了其复杂的技能系统和模块化设计。要真正掌握,最好的方法是在编辑器中打开这些资源和代码,沿着这个流程一步步追踪和调试。

相关推荐
铅笔小新z1 小时前
【C++】从理论到实践:类和对象完全指南(中)
开发语言·c++
千疑千寻~1 小时前
【C++】std::move与std::forward函数的区别
开发语言·c++
hansang_IR1 小时前
【记录】四道双指针
c++·算法·贪心·双指针
_OP_CHEN1 小时前
算法基础篇:(十二)基础算法之倍增思想:从快速幂到大数据运算优化
大数据·c++·算法·acm·算法竞赛·倍增思想
Murphy_lx1 小时前
C++ 条件变量
linux·开发语言·c++
xie0510_1 小时前
C++入门
c++
AA陈超1 小时前
ASC学习笔记0027:直接设置属性的基础值,而不会影响当前正在生效的任何修饰符(Modifiers)
c++·笔记·学习·ue5·虚幻引擎
doubao361 小时前
如何在海量文献中高效筛选有价值信息
人工智能·学习·自然语言处理·aigc·ai工具·ai检索
羚羊角uou1 小时前
【C++】智能指针
开发语言·c++