みなさんこんにちは! トイロジックプログラマのOです! 普段はUE4を利用してゲームに登場するギミックの実装を行っています。今回はアクションゲームでよくみる「一定の経路に沿って移動するアクター」を簡単に量産するためのコンポーネントの実装について紹介したいと思います。

コンポーネントクラスの準備

UCLASS(BlueprintType, meta = (BlueprintSpawnableComponent))
class SAMPLE_API USplineMovementComponent : public USceneComponent
{
	GENERATED_BODY()

public:
	// コンストラクタ
	USplineMovementComponent();
	// 開始処理
	void BeginPlay() override;
	// 更新処理
	void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

protected:
	// スプラインを持つアクターの参照
	UPROPERTY(EditAnywhere)
		FComponentReference SplineReference;

	// 移動速度
	UPROPERTY(EditAnywhere)
		float MoveSpeed { 100.0f };

	// 補間速度
	UPROPERTY(EditAnywhere)
		float InterpSpeed{ 100.0f };

private:
	// 移動用スプラインコンポーネント
	TWeakObjectPtr SplineComponent;

	// スプライン上での移動距離
	float CurrentMovedSplineDistance{ 0.0f };
};

移動経路として使うスプラインの参照や移動速度などをBP側に公開しておきます。C++でコンポーネントを作成する際は、meta情報としてBlueprintSpawnableComponentを指定おくとBPアクターのコンポーネントの追加から実装したコンポーネントを追加できるようになります。

移動処理の実装

void USplineMovementComponent::BeginPlay()
{
	Super::BeginPlay();

	// スプラインアクターの参照からスプラインコンポーネントを取得
	SplineComponent = Cast(SplineReference.GetComponent(nullptr));
}

void USplineMovementComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

	// スプラインコンポーネントが設定されていなければ何もせずreturn
	if (!SplineComponent.IsValid())
	{
		return;
	}

	// コンポーネントを所持するアクターのポインタがなければ取得する
	if (OwnerActor == nullptr)
	{
		OwnerActor = GetOwner();
	}

	// 今回のフレームで移動する距離を算出
	float AddDistance = MoveSpeed * DeltaTime;

	// 現在の移動距離に上で算出した距離を足す
	CurrentMovedSplineDistance += AddDistance;

	// スプラインの全長を求める
	float SplineLength = SplineComponent->GetSplineLength();
	// 移動距離がスプラインの全長を超えていたらループさせる
	CurrentMovedSplineDistance = FMath::Fmod(CurrentMovedSplineDistance, SplineLength);

	// 移動距離からスプライン上のワールド座標を算出
	FVector TargetLocation = SplineComponent->GetLocationAtDistanceAlongSpline(CurrentMovedSplineDistance, ESplineCoordinateSpace::World);

	// 所持アクターの現在座標からTargetLocationに向けて補間をかけて移動させる
	FVector NextLocation = FMath::VInterpConstantTo(OwnerActor->GetActorLocation(), TargetLocation, DeltaTime, InterpSpeed);

	// 所持アクターに計算した座標に移動させる
	OwnerActor->SetActorLocation(NextLocation);
}

基本的にTickComponent内で移動距離を加算し、その移動距離からスプライン上の座標を求め、その座標に向けて補間をかけて移動する処理となっています。今回はスプライン上をループするような処理になっていますが、ループせず止めたい場合はアクターの座標をスプライン終端の距離を見て移動を止める処理を入れるとよいかなと思います。

実際にBPに設定して動かしてみる

床のメッシュを持ったアクターにSplineComponentを追加してパラメータを設定すると以下のように動きます。


コンポーネントとして実装したので移動床だけでなくほかのアクターにも移動処理を簡単に追加できます。

最後に

いかがでしたでしょうか?今回は非常にシンプルな形でスプライン移動を実装しましたが、必要に応じてさらに機能を追加することもできると思います。例えば……

  • 親アクター側から移動速度を変更できるようにする
  • 親アクターがスプラインと同じ方向を向くよう回転する
  • 移動中に別のスプライン情報を受け取って経路を変える

といった感じです。このような経路に沿って移動する処理は様々な箇所で使うことが多いので、コンポーネントとして実装し、汎用的に使えるよう実装すると非常に便利かなと思います!

トイロジックではこういった実装や技術について、社内勉強会などで日々共有を行っています。スキルアップを目指したい!ゲームに関する知識を深めたい!という皆様のエントリーをお待ちしています!