0、前言
ue5更新中,除了渲染技术上的更新,程序功能方面也有迭代。
1、对比
以FPS模板为例,ue4中通过修改配置文件,将输入事件与字符串对应,并将对应函数与字符串绑定。
void AFPSCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
// set up gameplay key bindings
check(PlayerInputComponent);
// Bind jump events
PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);
PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);
// Bind fire event
PlayerInputComponent->BindAction("Fire", IE_Pressed, this, &AFPSCharacter::OnFire);
// Enable touchscreen input
EnableTouchscreenMovement(PlayerInputComponent);
PlayerInputComponent->BindAction("ResetVR", IE_Pressed, this, &AFPSCharacter::OnResetVR);
// Bind movement events
PlayerInputComponent->BindAxis("MoveForward", this, &AFPSCharacter::MoveForward);
PlayerInputComponent->BindAxis("MoveRight", this, &AFPSCharacter::MoveRight);
// We have 2 versions of the rotation bindings to handle different kinds of devices differently
// "turn" handles devices that provide an absolute delta, such as a mouse.
// "turnrate" is for devices that we choose to treat as a rate of change, such as an analog joystick
PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);
PlayerInputComponent->BindAxis("TurnRate", this, &AFPSCharacter::TurnAtRate);
PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);
PlayerInputComponent->BindAxis("LookUpRate", this, &AFPSCharacter::LookUpAtRate);
}
这样做有几个缺点,首先是自定义按键实现不方便,这套配置文件的方案几乎没用。
其次,将输入事件配置为全局,没有替换方法,例如我在开车时,空格键应该对应手刹而非Jump。
2、增强输入组件
ue5自然也带来了更好的解决方案,叫做UEnhancedInputComponent。
这套新系统底层逻辑上和ue4区别不大说这个组件前,但开始前要先了解几个概念。
- Action事件:只有按下和抬起状态如Jump,Fire,在EnhancedInput中被digital类型代替
- Axis事件:包含-1,到1中间的状态,类比为摇杆,扳机,在EnhancedInput中被float和Vector事件代替
- Subsystem:一种由引擎控制构造和销毁的对象,根据不同的outer对象有着不同的寿命周期
- LocalPlayer:代表本地玩家,这里只关注它包含了响应玩家输入的逻辑。
现在可以开始看EnhancedInput增强输入组件了,首先是它带来的几个对象。
- UInputAction,对应配置文件中的字符串,但并非像他的名字一样只对应Action事件
- UInputMappingContext,用来将按键与InputAction对应
想要应用这些对象到输入系统,首先将目光给到项目设置中的Input选项,将DefaultClass里的两个类型替换成UEnhancedInput,应用增强输入系统。
其次APawn::SetupPlayerInputComponent上面,这个函数会在被Controller拥有时调用,用作将输入组件的事件与自身功能绑定。
void AFPS_TestCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent);
如果项目设置中正确调整的话,这里的Component可以被Cast为UEnhancedInputComponent,然后调用BindAction即可。
四个参数对应为:
- UInputAction:想要相应的IA
- 触发条件:具体包含的条件自己看源码注释吧,解释起来比较麻烦
- 被通知的对象,可以不是自己
- 调用的函数,要求这个函数有一个FInputActionValue类型的参数
整体的绑定逻辑与动态多播委托的绑定类似。
void AFPS_TestCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
if (UEnhancedInputComponent* Enhanced = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
{
//Jumping
Enhanced->BindAction(JumpAction, ETriggerEvent::Triggered, this, &ACharacter::Jump);
//这里的ETriggerEvent::Completed为松开空格键时调用
Enhanced->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping);
//Moving
Enhanced->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AFPS_TestCharacter::Move);
//Looking
Enhanced->BindAction(LookAction, ETriggerEvent::Triggered, this, &AFPS_TestCharacter::Look);
}
}
至此,IA与角色的功能完成了绑定。下一步便是将输入与IA绑定在一起。
ue5的FPS模板增加了一个捡起武器的小功能,我们可以透过这个功能的实现,了解到增强输入系统的强大之处。
找到武器蓝图,会发现在碰撞事件后面很简单的调用一个函数。
void UTP_WeaponComponent::AttachWeapon(AFPS_TestCharacter* TargetCharacter)
{
Character = TargetCharacter;
if (Character == nullptr)
{
return;
}
// Attach the weapon to the First Person Character
FAttachmentTransformRules AttachmentRules(EAttachmentRule::SnapToTarget, true);
AttachToComponent(Character->GetMesh1P(), AttachmentRules, FName(TEXT("GripPoint")));
// switch bHasRifle so the animation blueprint can switch to another animation set
Character->SetHasRifle(true);
// Set up action bindings
if (APlayerController* PlayerController = Cast<APlayerController>(Character->GetController()))
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
{
// Set the priority of the mapping to 1, so that it overrides the Jump action with the Fire action when using touch input
Subsystem->AddMappingContext(FireMappingContext, 1);
}
if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerController->InputComponent))
{
// Fire
EnhancedInputComponent->BindAction(FireAction, ETriggerEvent::Triggered, this, &UTP_WeaponComponent::Fire);
}
}
}
翻译成人话的话,有以下几步
- 安全检查
- 将StaticMesh附加到玩家角色上,附加过程不是本章重点,但很有意思
- 通过被挂在的角色获取LocalPlayer
- 获取本地玩家身上挂载的增强输入子系统UEnhancedInputLocalPlayerSubsystem
- 将一套新的IA按键映射添加(而非覆盖)到输入系统
- 将开火功能与新的IA按键映射绑定
逐步分析:
1、安全检查无需多言
2、附加Mesh非本章重点
3、获取LocalPlayer,Pawn类型可以被Controller拥有,而PlayerController有一个对应的LocalPlayer
4、前面提到,LocalPlayer负责响应玩家输入,一次需要将按键与IA的映射添加给他
5、控制器知道了鼠标左键为Fire,那么需要知道Fire对应的功能函数
可以注意到,在AddMappingContext后面有一个数字,这个数字控制了这套映射的优先级,假如说行走状态下的跳跃键和驾驶状态下的手刹建重合,那么只需要按照堆栈的思想,提高新加入的映射的优先级,即可实现不删除原先的映射的前提下添加新的映射,当退出驾驶状态时将驾驶功能映射移除掉即可。
而玩家的移动处理之类的映射,可以看角色的BeginPlay函数。