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函数。