我们来详细拆解 Lyra 的输入系统。Lyra 的输入系统非常强大和模块化,是其架构的核心亮点之一。它不仅仅是将按键映射到操作,而是构建了一个与 Gameplay Tag 深度集成的、可扩展的框架。
核心设计思想
Lyra 输入系统的核心思想是:将原始的按键输入,转化为具有语义的 GameplayTag,然后由各个系统(如技能系统、UI系统)根据这些 Tag 来做出响应。 这实现了输入与具体逻辑的解耦。
详细流程与步骤
整个输入流程可以分为以下几个关键步骤和组成部分:
第1步:基础输入设置 (IMC)
文件位置: Config/DefaultInput.ini 或 在内容浏览器中搜索 IMC_Default
-
输入映射上下文(Input Mapping Context, IMC): 这是一个资源,定义了 按键/输入事件 与 输入动作 之间的映射关系。
-
输入动作(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 输入系统的精髓所在。
-
LyraInputConfig数据资产:-
位置: 搜索
DA_InputConfig_Character等。 -
作用: 这个资产建立了 输入动作 和 Gameplay Tag 之间的桥梁。它为每个
Input Action分配一个或多个Gameplay Tag。 -
例如:

-
-
LyraInputComponent:-
位置: 查看
LyraHeroComponent是如何设置输入的。 -
作用: 这是一个增强的
Input Component,它知道如何根据LyraInputConfig将输入事件与 Gameplay Tag 关联起来。当玩家按下按键时,LyraInputComponent会查找对应的Input Action,然后从LyraInputConfig中找到与之关联的Gameplay Tag,并广播这个 Tag。
-
第3步:处理输入 - LyraHeroComponent
这是输入流程的核心控制器,通常存在于玩家控制的 Pawn 上。
文件: LyraHeroComponent.cpp / .h
它的主要工作流程在 BindInputs 和 Input_AbilityInputTagPressed 等函数中:
-
绑定增强输入:
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); // ... } -
处理向量输入(如移动):
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); } } } -
处理技能输入(转换为 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) 的连接
这是输入的最终消费者之一。
-
LyraGameplayAbility:-
AbilityTags: 每个Gameplay Ability蓝图上都有Ability Tags。例如,一个"冲刺"技能会拥有Ability.Sprint标签。 -
InputTag: 在技能的详细信息中,可以指定一个Input Tag(如InputTag.Ability.Sprint)。
-
-
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 实现了一个清晰、灵活且强大的输入处理管道,完美地支持了其复杂的技能系统和模块化设计。要真正掌握,最好的方法是在编辑器中打开这些资源和代码,沿着这个流程一步步追踪和调试。