UE官方示例项目Lyra Game拆解(四)

本次我们梳理Lyra的技能激活流程

首先从技能组件UAbilitySystemComponent开始,该组件的初始化我们之前已经提到过,是在ALyraGameState的构造函数中初始化的。
在这里插入图片描述
在该文件中有两种处理输入的函数,这里解释下这两种函数的区别的应用场景:
AbilitySpecInputPressed/Released: 直接关联到具体的技能实例(AbilitySpec),处理绑定的输入动作ID,当与技能绑定的具体输入按键(如键盘按键或手柄按钮)被按下或释放时触发。由父类AbilitySystemComponent管理,通过输入ID直接绑定到技能实例。
AbilityInputTagPressed/Released: 基于GameplayTag的抽象输入系统,与具体按键无关。当输入动作映射到某个标签(如Input.Attack)时触发,无论实际按键如何绑定。实现跨设备/按键配置的输入逻辑(例如不同平台的“跳跃”动作统一由Input.Jump标签处理),支持复杂的技能逻辑(如连击、蓄力技能通过标签状态判断阶段),供多个技能或系统共享输入状态(如UI提示根据标签是否按下改变),通常由输入子系统或标签管理器广播,技能通过订阅标签响应。

我们着重用注释解释后者的内部逻辑如下:
在这里插入图片描述
在这里插入图片描述
以上函数主要作用就是将玩家输入加入到不同的队列中批量处理,我们继续看具体处理输入的函数
在这里插入图片描述
从这个函数的形参,可以猜测出该函数是在Tick中处理的,具体内部逻辑如下

//处理技能输入
void ULyraAbilitySystemComponent::ProcessAbilityInput(float DeltaTime, bool bGamePaused)
{
	if (HasMatchingGameplayTag(TAG_Gameplay_AbilityInputBlocked))
	{
		ClearAbilityInput();
		return;
	}

	static TArray<FGameplayAbilitySpecHandle> AbilitiesToActivate;
	AbilitiesToActivate.Reset();

	//@TODO: See if we can use FScopedServerAbilityRPCBatcher ScopedRPCBatcher in some of these loops

	//
	// 批量处理所有持续按下的技能
	//
	for (const FGameplayAbilitySpecHandle& SpecHandle : InputHeldSpecHandles)
	{
		if (const FGameplayAbilitySpec* AbilitySpec = FindAbilitySpecFromHandle(SpecHandle))
		{
			if (AbilitySpec->Ability && !AbilitySpec->IsActive())
			{
				const ULyraGameplayAbility* LyraAbilityCDO = Cast<ULyraGameplayAbility>(AbilitySpec->Ability);
				if (LyraAbilityCDO && LyraAbilityCDO->GetActivationPolicy() == ELyraAbilityActivationPolicy::WhileInputActive)
				{
					AbilitiesToActivate.AddUnique(AbilitySpec->Handle);//加入激活队列
				}
			}
		}
	}

	//
	// 批量处理所有按下激活的技能
	//
	for (const FGameplayAbilitySpecHandle& SpecHandle : InputPressedSpecHandles)
	{
		if (FGameplayAbilitySpec* AbilitySpec = FindAbilitySpecFromHandle(SpecHandle))
		{
			if (AbilitySpec->Ability)
			{
				AbilitySpec->InputPressed = true;

				if (AbilitySpec->IsActive())
				{
					// 技能已激活 传递输入事件
					AbilitySpecInputPressed(*AbilitySpec);
				}
				else
				{
					const ULyraGameplayAbility* LyraAbilityCDO = Cast<ULyraGameplayAbility>(AbilitySpec->Ability);

					if (LyraAbilityCDO && LyraAbilityCDO->GetActivationPolicy() == ELyraAbilityActivationPolicy::OnInputTriggered)
					{
						AbilitiesToActivate.AddUnique(AbilitySpec->Handle);//加入激活队列
					}
				}
			}
		}
	}

	//
	// Try to activate all the abilities that are from presses and holds.
	// We do it all at once so that held inputs don't activate the ability
	// and then also send a input event to the ability because of the press.
	//尝试激活所有队列中的技能
	for (const FGameplayAbilitySpecHandle& AbilitySpecHandle : AbilitiesToActivate)
	{
		TryActivateAbility(AbilitySpecHandle);
	}

	//
	// 批量处理所有按下释放的技能
	//
	for (const FGameplayAbilitySpecHandle& SpecHandle : InputReleasedSpecHandles)
	{
		if (FGameplayAbilitySpec* AbilitySpec = FindAbilitySpecFromHandle(SpecHandle))
		{
			if (AbilitySpec->Ability)
			{
				AbilitySpec->InputPressed = false;

				if (AbilitySpec->IsActive())
				{
					// Ability is active so pass along the input event.
					AbilitySpecInputReleased(*AbilitySpec);
				}
			}
		}
	}

	//
	// Clear the cached ability handles.
	//
	InputPressedSpecHandles.Reset();
	InputReleasedSpecHandles.Reset();
}

我们继续跟代码,找到Tick驱动的地方,调用栈如下

  • APlayerController::TickActor
  • APlayerController::TickPlayerInput
  • UPlayerInput::ProcessInputStack
  • ALyraPlayerController::PostProcessInput
  • ULyraAbilitySystemComponent::ProcessAbilityInput
    以上就是处理玩家输入的流程。接下来我们去找玩家发起输入的地方将整个流程串联起来,我们跟踪ULyraAbilitySystemComponent组件中获取输入的函数,看看他的调用栈。我们跟踪ULyraAbilitySystemComponent的AbilityInputTagPressed函数找到了ULyraHeroComponent组件,看名字就知道这玩意儿是跟角色相关的,我们找到它初始化的地方是直接被挂载蓝图B_Hero_ShooterMannequin上通过蓝图初始化的
    在这里插入图片描述
    同样,该蓝图也是通过资产配置加载
    在这里插入图片描述
    我们再次回到ULyraHeroComponent组件,在其InitializePlayerInput函数中绑定了用户输入,代码如下:
void ULyraHeroComponent::InitializePlayerInput(UInputComponent* PlayerInputComponent)
{
	check(PlayerInputComponent);

	const APawn* Pawn = GetPawn<APawn>();
	if (!Pawn)
	{
		return;
	}

	const APlayerController* PC = GetController<APlayerController>();
	check(PC);

	const ULyraLocalPlayer* LP = Cast<ULyraLocalPlayer>(PC->GetLocalPlayer());
	check(LP);

	UEnhancedInputLocalPlayerSubsystem* Subsystem = LP->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>();
	check(Subsystem);

	Subsystem->ClearAllMappings();

	if (const ULyraPawnExtensionComponent* PawnExtComp = ULyraPawnExtensionComponent::FindPawnExtensionComponent(Pawn))
	{
		if (const ULyraPawnData* PawnData = PawnExtComp->GetPawnData<ULyraPawnData>())
		{
			if (const ULyraInputConfig* InputConfig = PawnData->InputConfig)
			{
				for (const FInputMappingContextAndPriority& Mapping : DefaultInputMappings)
				{
					if (UInputMappingContext* IMC = Mapping.InputMapping.Get())
					{
						if (Mapping.bRegisterWithSettings)
						{
							if (UEnhancedInputUserSettings* Settings = Subsystem->GetUserSettings())
							{
								Settings->RegisterInputMappingContext(IMC);
							}
							
							FModifyContextOptions Options = {};
							Options.bIgnoreAllPressedKeysUntilRelease = false;
							// Actually add the config to the local player							
							Subsystem->AddMappingContext(IMC, Mapping.Priority, Options);
						}
					}
				}

				// The Lyra Input Component has some additional functions to map Gameplay Tags to an Input Action.
				// If you want this functionality but still want to change your input component class, make it a subclass
				// of the ULyraInputComponent or modify this component accordingly.
				ULyraInputComponent* LyraIC = Cast<ULyraInputComponent>(PlayerInputComponent);
				if (ensureMsgf(LyraIC, TEXT("Unexpected Input Component class! The Gameplay Abilities will not be bound to their inputs. Change the input component to ULyraInputComponent or a subclass of it.")))
				{
					// Add the key mappings that may have been set by the player
					LyraIC->AddInputMappings(InputConfig, Subsystem);

					// This is where we actually bind and input action to a gameplay tag, which means that Gameplay Ability Blueprints will
					// be triggered directly by these input actions Triggered events. 
					TArray<uint32> BindHandles;
					LyraIC->BindAbilityActions(InputConfig, this, &ThisClass::Input_AbilityInputTagPressed, &ThisClass::Input_AbilityInputTagReleased, /*out*/ BindHandles);

					LyraIC->BindNativeAction(InputConfig, LyraGameplayTags::InputTag_Move, ETriggerEvent::Triggered, this, &ThisClass::Input_Move, /*bLogIfNotFound=*/ false);
					LyraIC->BindNativeAction(InputConfig, LyraGameplayTags::InputTag_Look_Mouse, ETriggerEvent::Triggered, this, &ThisClass::Input_LookMouse, /*bLogIfNotFound=*/ false);
					LyraIC->BindNativeAction(InputConfig, LyraGameplayTags::InputTag_Look_Stick, ETriggerEvent::Triggered, this, &ThisClass::Input_LookStick, /*bLogIfNotFound=*/ false);
					LyraIC->BindNativeAction(InputConfig, LyraGameplayTags::InputTag_Crouch, ETriggerEvent::Triggered, this, &ThisClass::Input_Crouch, /*bLogIfNotFound=*/ false);
					LyraIC->BindNativeAction(InputConfig, LyraGameplayTags::InputTag_AutoRun, ETriggerEvent::Triggered, this, &ThisClass::Input_AutoRun, /*bLogIfNotFound=*/ false);
				}
			}
		}
	}

	if (ensure(!bReadyToBindInputs))
	{
		bReadyToBindInputs = true;
	}
 
	UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(const_cast<APlayerController*>(PC), NAME_BindInputsNow);
	UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(const_cast<APawn*>(Pawn), NAME_BindInputsNow);
}

它是通过输入配置绑定的,输入配置如下
在这里插入图片描述
以上就是Lyra使用技能的整个流程了。

<think>好的,我现在需要帮助用户解决在UE5.5的Lyra示例项目中集成UnLua的问题。首先,我需要了解UnLua是什么,以及它在UE项目中的作用。UnLua是一个用于Unreal Engine的Lua脚本插件,允许开发者使用Lua语言进行游戏逻辑的编写,提高开发效率和灵活性。 接下来,我需要考虑Lyra项目的结构。根据用户提供的引用,Lyra项目采用了模块化设计,各个功能模块独立存在,比如InputConfig、GAS(Gameplay Ability System)等。用户之前的问题中提到的引用[1]和[2]提到了输入配置和角色配置,这些可能与集成UnLua时需要处理的部分有关。 用户想将UnLua集成到Lyra中,可能需要修改项目的构建文件(如.Build.cs),添加UnLua的模块依赖。引用[4]中提到Lyra项目的结构是头文件和cpp文件混放在一起,并且Build.cs中的路径代码有限,所以需要确保UnLua的路径正确包含在项目中。 另外,UnLua需要与UE的蓝图和C++代码交互,可能需要修改Lyra的启动模块,初始化Lua环境。例如,在LyraGame模块的启动函数中初始化UnLua,并设置Lua文件的加载路径。同时,需要处理Lyra原有的GameplayTag、InputAction等系统与Lua脚本的绑定,可能需要通过UnLua的接口将蓝图中的逻辑替换为Lua脚本。 还需要考虑动画系统的集成,引用[3]提到Lyra的动画蓝图较为复杂,如果用户希望用Lua控制动画逻辑,可能需要将动画蓝图中的部分功能迁移到Lua脚本中,这涉及到动画蓝图和Lua之间的通信。 在测试阶段,需要确保所有修改后的模块能够正确编译,并且Lua脚本能够被正确加载和执行。可能遇到的问题包括模块依赖缺失、路径配置错误、或Lua与C++之间的数据类型转换问题。此时需要参考UnLua的官方文档,检查每一步是否正确配置,并利用日志输出调试信息。 总结步骤大致为:获取UnLua插件并放入项目目录,修改项目构建文件,配置启动模块初始化Lua,调整输入和游戏逻辑绑定,处理动画系统,最后测试并调试。需要提醒用户注意版本兼容性,尤其是UE5.5和UnLua的版本是否匹配,以及遵循Lyra的模块化设计原则,避免破坏原有结构。</think>### UE5.5 Lyra示例项目集成UnLua的步骤指南 #### 1. **环境准备** - 确保已下载UnLua插件(兼容UE5.5版本),可从GitHub仓库或UnLua官网获取。 - 将UnLua插件放置在Lyra项目的`Plugins`目录下(若没有则手动创建)。 #### 2. **修改项目构建配置** - 在Lyra项目的`.Build.cs`文件中添加UnLua模块依赖: ```csharp // LyraGame.Build.cs PublicDependencyModuleNames.AddRange(new string[] { "UnLua", // 添加UnLua模块 "LyraGame", "GameplayAbilities", ... }); ``` - 引用[4]提到Lyra的Build.cs路径配置较为简单,需确认插件路径已正确包含。 #### 3. **初始化Lua环境** - 在Lyra的**启动模块**(如`LyraGame`)中添加UnLua初始化代码: ```cpp // LyraGame.cpp #include "UnLua.h" void FLyraGameModule::StartupModule() { FUnLuaModule::Get().Startup(); // 初始化UnLua } ``` - 设置Lua脚本默认加载路径(如`Content/Script`)。 #### 4. **绑定输入与GAS到Lua** - **输入映射**:根据引用[1],Lyra使用`InputConfig`结构体关联输入与GameplayTag。可在Lua中重写输入响应逻辑: ```lua -- Input.lua local function OnInputAction(PlayerController, InputAction) if InputAction == "Jump" then -- Lua处理跳跃逻辑 end end BindInput(OnInputAction) ``` - **GAS集成**:引用[2]提到Lyra通过`AbilitySystemComponent`管理技能。通过UnLua覆写Ability激活逻辑: ```lua -- Ability.lua function ActivateAbility(Ability) if Ability:GetTag() == "Attack" then -- Lua实现攻击逻辑 end end ``` #### 5. **动画系统适配** - 根据引用[3],Lyra的动画系统通过动画蓝图控制。可将部分动画逻辑迁移到Lua: ```lua -- AnimBP.lua function UpdateAnimParams(Speed, Direction) AnimGraph:SetFloatParam("Speed", Speed) AnimGraph:SetFloatParam("Direction", Direction) end ``` #### 6. **测试与调试** - 在编辑器中启用UnLua插件(`Edit > Plugins > UnLua`)。 - 创建测试Lua脚本(如`Content/Script/Test.lua`),并在关卡蓝图中调用: ```lua Print("UnLua Integration Success!") ``` - 检查日志输出,确保无`Lua_State`初始化错误或路径问题。 #### 7. **注意事项** - **版本兼容性**:确认UnLua插件支持UE5.5,否则需从源码编译适配版本。 - **模块化设计**:避免直接修改Lyra核心模块,建议新建`LyraLua`模块管理集成代码[^2][^4]。 - **性能优化**:高频逻辑(如Tick事件)建议保留C++实现,Lua用于业务逻辑。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值