Quantcast
Channel: historia Inc –株式会社ヒストリア
Viewing all articles
Browse latest Browse all 987

[UE5][C++]EnhancedInputで独自のInputTriggerを作る~UIカーソル高速移動編~

$
0
0
執筆バージョン: Unreal Engine 5.0

ハローアンリアル!エンジニアの片平です!

UE4.26から実装された Enhanced Input プラグインですが、
入力周りの痒い所に手が届く、非常に有用なプラグインとなっております。

拡張もしやすい設計となっているため、今回はUI操作に活用した例をご紹介します。

具体的には動画のようにパッドで上下のキーを押し続けるとカーソルが高速移動する InputTrigger を作成します。


なお、Enhanced Inputプラグインは最新のUE5.0.1で「Beta」となっております。
今後のアップデートによっては仕様が変更される可能性があります。ご留意ください

Enhanced Input導入

Enhanced Inputプラグインの導入に関しては、本記事の趣旨ではないため割愛します。

下記のページが参考になるかと思います。

Enhanced Input プラグイン

入力値 – Unreal Engine 5 Documentation

UIを作成

ThirdPersonTemplateから作り始めました。

EnhanceInputをセットアップし、簡単なメニュー画面を作成しました。

パッドの上下キーで星座を選択できます。

InputMappingContextについて

InputMappingContext はキャラクター操作時に使用する「ActionInputContext」と、
メニュー操作時に使用する「UIInputContext」に分けています。

上記の2つを画像のようなBPで切り替えて使用しています。

このとき、ModifyContextOptionsの Ignore All Pressed Keys Until Release をTrueにしないと、
Contextが切り替わった瞬間に、現在押しているボタンのTriggerが走ってしまいます。

例えば、メニューを開くボタンと閉じるボタンを同じボタンに設定しているときに

メニューを開く → メニューを閉じる → メニューを開く → メニューを閉じる → メニューを開く → (以下略)

という感じで無限ループとなるため、このような使い方をするときは必ず Ignore All Pressed Keys Until Release を Trueにしましょう。

UIへの繋ぎこみ

UIへの繋ぎこみは、インターフェースを使用しています。

InputTriggerの拡張

InputTriggerについて

InputTrigger (UInputTrigger)とは入力に対して、
どのような条件で Trigger する(入力イベントのTriggerdを呼ぶ)かという処理が実装されたクラスです。

デフォルトでは以下のようなものが用意されています。

Down … 押している間、常にTriggerする
Hold … 設定時間以上押したらTriggerする
Pressed … 押した時に一度だけTriggerする
Released … 話した時にTriggerする。
Pulse … 押している間、一定周期でTriggerする

Pressedでのカーソル移動の問題点

カーソル移動に使用する Down と Up の InputAction には Triggers に Pressed を設定しています。

これでは星座のように大量に選択肢があるメニューの場合、十字キーを無駄に連打させる必要があります。

プレイヤーを腱鞘炎にさせないためにも、カーソル君には長押しで高速移動してもらいたいです。

独自のInputTriggerを作成する

C++で以下のようなコードを追加します。

#pragma once

#include "CoreMinimal.h"
#include "InputTriggers.h"
#include "BlogEIInputTriggerHoldInterval.generated.h"

UCLASS(Abstract, Config = Input)
class UBlogEIInputTriggerTimedBase : public UInputTrigger
{
	GENERATED_BODY()

protected:

	UPROPERTY(BlueprintReadWrite, Category = "Trigger Settings")
		float HeldDuration = 0.0f;

	virtual ETriggerState UpdateState_Implementation(const UEnhancedPlayerInput* PlayerInput, FInputActionValue ModifiedValue, float DeltaTime) override;

public:

	virtual FString GetDebugState() const override { return HeldDuration ? FString::Printf(TEXT("Held:%.2f"), HeldDuration) : FString(); }
};


UCLASS(NotBlueprintable, meta = (DisplayName = "Hold And Accelerate"))
class UBlogEIInputTriggerHoldAndAccelaerate : public UBlogEIInputTriggerTimedBase
{
	GENERATED_BODY()

	bool bTriggered = false;

	float LastTriggerElapsedTime = 0.0f;

protected:

	virtual ETriggerState UpdateState_Implementation(const UEnhancedPlayerInput* PlayerInput, FInputActionValue ModifiedValue, float DeltaTime) override;

public:
	//ホールド時間閾値
	UPROPERTY(EditAnywhere, Config, BlueprintReadWrite, Category = "Trigger Settings")
		float HoldTimeThreshold = 0.5f;

	//最大インターバル時間
	UPROPERTY(EditAnywhere, Config, BlueprintReadWrite, Category = "Trigger Settings")
		float MaxTriggerIntervalTime = 0.25f;

	//最小インターバル時間
	UPROPERTY(EditAnywhere, Config, BlueprintReadWrite, Category = "Trigger Settings")
		float MinTriggerIntervalTime = 0.03f;

	//加速時間
	UPROPERTY(EditAnywhere, Config, BlueprintReadWrite, Category = "Trigger Settings")
		float AccelerationTime = 2.0f;
};

#include "BlogEIInputTriggerHoldInterval.h"
#include "Kismet/KismetMathLibrary.h"

ETriggerState UBlogEIInputTriggerTimedBase::UpdateState_Implementation(const UEnhancedPlayerInput* PlayerInput, FInputActionValue ModifiedValue, float DeltaTime)
{
	ETriggerState State = ETriggerState::None;

	// Transition to Ongoing on actuation.
	if (IsActuated(ModifiedValue))
	{
		State = ETriggerState::Ongoing;
		HeldDuration += DeltaTime;	// TODO: When attached directly to an Action this will tick N times a frame where N is the number of evaluated (actively held) mappings.
	}
	else
	{
		// Reset duration
		HeldDuration = 0.0f;
	}

	return State;
}

ETriggerState UBlogEIInputTriggerHoldAndAccelaerate::UpdateState_Implementation(const UEnhancedPlayerInput* PlayerInput, FInputActionValue ModifiedValue, float DeltaTime)
{
	// UBlogEIInputTriggerTimedBaseでHeldDurationを更新
	ETriggerState State = Super::UpdateState_Implementation(PlayerInput, ModifiedValue, DeltaTime);

	//経過時間から現在のインターバル時間を決定する
	float Alpha = FMath::Clamp(HeldDuration - HoldTimeThreshold, 0, AccelerationTime) / AccelerationTime;
	float TriggerInterval = FMath::Lerp(MaxTriggerIntervalTime, MinTriggerIntervalTime, Alpha);

	//押している時間がThresholdを超えていて、間隔が設定以上ならTrigger
	bTriggered = (HeldDuration >= HoldTimeThreshold) && (LastTriggerElapsedTime >= TriggerInterval);
	if (bTriggered)
	{
		//経過時間をリセット
		LastTriggerElapsedTime = 0.0f;
		return ETriggerState::Triggered;
	}

	LastTriggerElapsedTime = LastTriggerElapsedTime + DeltaTime;

	return State;
}

これで新たにHold And Accelerateという、
「一定時間押した後に、だんだん早くなる周期でTriggerする」InputTriggerが作成されます。

なお、UBlogEIInputTriggerTimedBase については、
InputTriggers.h に UInputTriggerTimedBase という同様の処理をするクラスが実装されていますが、
MinimalAPIで実装されており継承ができないため、新たに実装しています。

新たに作成した Hold And Accelerate を Down と Up の InputAction に追加登録します。

完成!

これでパッドの上下キーを長押しすると、
カーソルが高速移動するようになりました!

ハクスラとかで100個単位で装備を選択するなどのシチュエーションがあるゲームには必須ですね!

まとめ

EnhancedInputは古代の谷Lyla Starter Gameでも使用されており、
UE5ではある程度スタンダードな機能になっていくのではないかなと予想しています。

拡張することでUIの独特な入力にも対応できそうなので、使ってみてはいかがでしょうか?

UE5のUIの入力周りは CommonUI も気になるところですが…

The post [UE5][C++]EnhancedInputで独自のInputTriggerを作る~UIカーソル高速移動編~ first appeared on historia Inc - 株式会社ヒストリア.


Viewing all articles
Browse latest Browse all 987

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>