2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

UE5 GameFeaturesプラグインについてのメモ

Last updated at Posted at 2023-03-02

概要

UnrealEngine5にて追加されたGameFeaturesプラグインのメモ書きです。
このプラグインは本編との参照なしで機能の追加/削除ができる仕組みとのことです。
サードパーソンプロジェクトに追加するテストをします。

更新履歴

日付 内容
2023/03/02 初版
2023/03/03 Action追加テストを追記

参考

以下の記事を参考にいたしました、ありがとうございます。
UE公式:Game Features と Modular Gameplay
UE公式:UGameFeaturesSubsystem
UE5 Game Featuresで簡単に依存関係なしのコンポーネントを作ってみる
[UE5] プレイヤーの技などをプラグインで実装できる! GameFeaturesプラグインの紹介
First Look at Unreal Engine 5 Game Features Plugin

環境

Windows10
Visual Studio 2022
UnrealEngine 5.1.1

関連ソース

▼GameFeaturesSubsystem
"\Engine\Plugins\Experimental\GameFeatures\Source\GameFeatures\Public\GameFeaturesSubsystem.h"

▼GameFrameworkComponentManager
"\Engine\Plugins\Experimental\ModularGameplay\Source\ModularGameplay\Public\Components\GameFrameworkComponentManager.h"

準備

[編集]->[プラグイン]からGameFeaturesModularGameplayを有効にします。(エディタ再起動)
gamefeature_plugin.png

その後、PrimaryAssetTypesToScanにエントリを追加しますか? とメッセージがでるのでそのままクリックすると必要な設定が自動で行われます。

GameFeaturesプラグインの作成

サードパーソンプロジェクトにプラグインを追加するテストをします。

追加するプラグインの準備

[編集]->[プラグイン]のウィンドウ左上の[+追加]から新しいプラグインを追加します。

add_plugin.png

ゲーム機能(コンテンツのみ) を選択します(プラグインにてC++を使用する場合は ゲーム機能(C++版) を選択します)。

TestFeature1という名前で作成した場合、以下のようにコンテンツブラウザに表示されます。設定用のデータアセットが自動追加されます。
add_plugin02.png

もし表示されない場合はコンテンツブラウザ右上の設定から[プラグインコンテンツを表示]にチェックを入れます。
setting.png

追加したプラグインに追加するコンポーネントを作成

追加するコンポーネントをテスト作成します。
以下は、Tick処理にデバッグ文字列表示をしただけのコンポーネント(TestComponent)です。
TestComponent.png

追加される側のアクターの修正

追加されるアクターのBP(BP_ThirdPersonCharacter)にて、BeginPlay時に GameFrameworkComponentManagerクラスのAddReceiverで追加コンポーネント受け取りの設定をします。
同時にEndPlay時には RemoveReceiverにて受け取り設定を削除します。
BP_ThirdPersonCharacter.png

プラグインのデータアセットの設定

プラグイン作成時に自動作成されたデータアセットの設定をします。
[Actions]を[Add Components]に設定し、[Component List]を追加します。
[Actor Class]には追加される側のアクタークラス[BP_ThirdPersonCharacter]、[Component Class]には追加するプラグインにあるコンポーネント[TestComponent]を設定します。

DataAsset.png

動作テスト

テスト作成したコンポーネントの切り替えのテストをします。

データアセットでの状態切り替え

データアセットの[現在の状態]を[有効]にすることでプラグイン追加されコンポーネントが追加されます。
これを[ロード済み]か[登録済み]にするとプラグインにて追加されたコンポーネントが削除されます。
[インストール済み]にするとプラグイン自体が無効となります。

Result.png

コンソールコマンドでの状態切り替え

コンソールコマンドでも切り替えが可能です。

[インストール済み]に変更
UnLoadGameFeaturePlugin [PluginName]

[ロード済み]に変更
DeactivateGameFeaturePlugin [PluginName]

[有効]に変更
LoadGameFeaturePlugin [PluginName]

[登録済み]に変更
UnloadAndKeepRegisteredGameFeaturePlugin [PluginName]

その他のコンソールコマンド

ListGameFeaturePlugins
GameFeatureプラグインの現在の状態をアウトプットログにリスト出力します。

ReleaseGameFeaturePlugin [PluginName]
GameFeatureプラグインを解放します。UnLoadGameFeaturePluginとの違いは設定のデータアセットは残るらしいです。実行するとプラグインの状態は[インストール済み]になります。

CancelGameFeaturePlugin [PluginName]
GameFeatureプラグインの状態変更のキャンセルを試みます。

C++ での状態切り替え

C++での状態変更は UGameFeaturesSubsystem にアクセスをして行います。
準備として *.Build.csにGameFeaturesモジュールを追加します。

MyProject.Build.cs
public class MyProject : ModuleRules
{
	public MyProject(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
	
		PublicDependencyModuleNames.AddRange(new string[] {
			"Core",
			"CoreUObject",
			"Engine",
			"InputCore",
			"GameFeatures"      // ←追加
		});

...省略...

GetPluginURLByName でプラグインのURLを取得してから対応メソッドで切り替えを行います。
以下C++での状態変更サンプルコード。

状態変更サンプルコード
#include "GameFeaturesSubsystem.h"

// プラグインURLを取得
FString _PluginURL;
UGameFeaturesSubsystem::Get().GetPluginURLByName(TEXT("TestFeature1"), _PluginURL);

// 指定プラグインをロードしてアクティブにする([有効]にする)
UGameFeaturesSubsystem::Get().LoadAndActivateGameFeaturePlugin(_PluginURL, FGameFeaturePluginLoadComplete());

// 指定プラグインをデアクティブにする([ロード済み]にする)
UGameFeaturesSubsystem::Get().DeactivateGameFeaturePlugin(_PluginURL, FGameFeaturePluginLoadComplete());

// 指定プラグインをアンロードする(第2引数trueで[登録済み]/falseで[インストール済み]にする)
UGameFeaturesSubsystem::Get().UnloadGameFeaturePlugin(_PluginURL, true);


プラグインの状態の判断ができるメソッドもあります。
IsGameFeaturePluginInstalled IsGameFeaturePluginRegistered IsGameFeaturePluginLoaded はプラグイン状態" 以降 "の判定になることに注意が必要です。

例えば、現在が[有効]状態なら[インストール済み][登録済み][ロード済み]全てで true が返ってきます。

状態判断サンプルコード
#include "GameFeaturesSubsystem.h"

// プラグインURLを取得
FString _PluginURL;
UGameFeaturesSubsystem::Get().GetPluginURLByName(TEXT("TestFeature1"), _PluginURL);

// プラグインがインストール状態か(またはそれ以降か)を判断する
bool _Ret1 = UGameFeaturesSubsystem::Get().IsGameFeaturePluginInstalled(_PluginURL);
// プラグインが登録済み状態か(またはそれ以降か)を判断する
bool _Ret2 = UGameFeaturesSubsystem::Get().IsGameFeaturePluginRegistered(_PluginURL);
// プラグインがロード済み状態か(またはそれ以降か)を判断する
bool _Ret3 = UGameFeaturesSubsystem::Get().IsGameFeaturePluginLoaded(_PluginURL);
// プラグインが有効状態かを判断する
bool _Ret4 = UGameFeaturesSubsystem::Get().IsGameFeaturePluginActive(_PluginURL);

細かい状態を取得するには GetPluginState で取得ができるようです。定義は
"Engine\Plugins\Experimental\GameFeatures\Source\GameFeatures\Public\GameFeatureTypes.h"
にあります。

GetPluginStateのサンプルコード
// プラグインのステートマシン状態を取得する
EGameFeaturePluginState _State = UGameFeaturesSubsystem::Get().GetPluginState(_PluginURL);

FString _StateStr = UE::GameFeatures::ToString(_State);
UE_LOG(LogTemp, Log, TEXT("%s"), *_StateStr);

Actionの追加テスト

UGameFeatureActionを継承してアクションを追加することによって拡張ができるようです。
テストとしてアクターを生成するアクションを作成してみます。

Actionクラスの作成

UE5サンプルのLyraGameにある
LyraStarterGame\Source\LyraGame\GameFeatures\GameFeatureAction_WorldActionBase.h .cpp を基底クラスに利用します。

GameFeatureAction_WorldActionBase.h
#pragma once

#include "Containers/Map.h"
#include "GameFeatureAction.h"
#include "GameFeaturesSubsystem.h"
#include "Misc/AssertionMacros.h"
#include "Misc/CoreMiscDefines.h"
#include "UObject/UObjectGlobals.h"

#include "GameFeatureAction_WorldActionBase.generated.h"

class FDelegateHandle;
class UGameInstance;
class UObject;
struct FGameFeatureActivatingContext;
struct FGameFeatureDeactivatingContext;
struct FGameFeatureStateChangeContext;
struct FWorldContext;

/**
 * Base class for GameFeatureActions that wish to do something world specific.
 */
UCLASS(Abstract)
class UGameFeatureAction_WorldActionBase : public UGameFeatureAction
{
	GENERATED_BODY()

public:
	//~ Begin UGameFeatureAction interface
	virtual void OnGameFeatureActivating(FGameFeatureActivatingContext& Context) override;
	virtual void OnGameFeatureDeactivating(FGameFeatureDeactivatingContext& Context) override;
	//~ End UGameFeatureAction interface

private:
	void HandleGameInstanceStart(UGameInstance* GameInstance, FGameFeatureStateChangeContext ChangeContext);

	/** Override with the action-specific logic */
	virtual void AddToWorld(const FWorldContext& WorldContext, const FGameFeatureStateChangeContext& ChangeContext) PURE_VIRTUAL(UGameFeatureAction_WorldActionBase::AddToWorld,);

private:
	TMap<FGameFeatureStateChangeContext, FDelegateHandle> GameInstanceStartHandles;
};
GameFeatureAction_WorldActionBase.cpp
#include "GameFeatureAction_WorldActionBase.h"

#include "Containers/Array.h"
#include "Containers/IndirectArray.h"
#include "Delegates/Delegate.h"
#include "Engine/Engine.h"
#include "Engine/GameInstance.h"
#include "Engine/World.h"
#include "GameFeaturesSubsystem.h"

#include UE_INLINE_GENERATED_CPP_BY_NAME(GameFeatureAction_WorldActionBase)

void UGameFeatureAction_WorldActionBase::OnGameFeatureActivating(FGameFeatureActivatingContext& Context)
{
	GameInstanceStartHandles.FindOrAdd(Context) = FWorldDelegates::OnStartGameInstance.AddUObject(this, 
		&UGameFeatureAction_WorldActionBase::HandleGameInstanceStart, FGameFeatureStateChangeContext(Context));

	// Add to any worlds with associated game instances that have already been initialized
	for (const FWorldContext& WorldContext : GEngine->GetWorldContexts())
	{
		if (Context.ShouldApplyToWorldContext(WorldContext))
		{
			AddToWorld(WorldContext, Context);
		}
	}
}

void UGameFeatureAction_WorldActionBase::OnGameFeatureDeactivating(FGameFeatureDeactivatingContext& Context)
{
	FDelegateHandle* FoundHandle = GameInstanceStartHandles.Find(Context);
	if (ensure(FoundHandle))
	{
		FWorldDelegates::OnStartGameInstance.Remove(*FoundHandle);
	}
	
}

void UGameFeatureAction_WorldActionBase::HandleGameInstanceStart(UGameInstance* GameInstance, FGameFeatureStateChangeContext ChangeContext)
{
	if (FWorldContext* WorldContext = GameInstance->GetWorldContext())
	{
		if (ChangeContext.ShouldApplyToWorldContext(*WorldContext))
		{
			AddToWorld(*WorldContext, ChangeContext);
		}
	}
}

GameFeatureAction_WorldActionBaseを継承してアクターを生成するアクションクラスUGameFeatureAction_ActorTestを作成します。

GameFeatureAction_ActorTest.h
#pragma once

#include <CoreMinimal.h>
#include "./GameFeatureAction_WorldActionBase.h"
#include "GameFeatureAction_ActorTest.generated.h"



USTRUCT(BlueprintType, Category = "GameFeatureAction_ActorTest | ActorSetting")
struct FGFA_ActorSetting
{
	GENERATED_BODY()

public:
	// アクタークラス
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Setting")
	TSoftClassPtr<AActor> ActorClass;

	// 生成位置
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Setting")
	FTransform Transform;
};


// GameFeatureActionテスト
UCLASS(meta = (DisplayName = "Add Actor (Test)"))
class MYPROJECT_API UGameFeatureAction_ActorTest final : public UGameFeatureAction_WorldActionBase
{
	GENERATED_BODY()

public:
	// 設定
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Setting")
	FGFA_ActorSetting ActorSetting;


	// 始末
	virtual void OnGameFeatureDeactivating(FGameFeatureDeactivatingContext &Context) override;

	// ワールドに出す
	virtual void AddToWorld(const FWorldContext& WorldContext, const FGameFeatureStateChangeContext& ChangeContext) override;


private:
	TWeakObjectPtr<AActor> SpawnedActor;

};
GameFeatureAction_ActorTest.cpp
#include "GameFeatureAction_ActorTest.h"
#include <Components/GameFrameworkComponentManager.h>
#include "Kismet/GameplayStatics.h"


#ifdef UE_INLINE_GENERATED_CPP_BY_NAME
#include UE_INLINE_GENERATED_CPP_BY_NAME(GameFeatureAction_ActorTest)
#endif

// 始末
void UGameFeatureAction_ActorTest::OnGameFeatureDeactivating(FGameFeatureDeactivatingContext &Context)
{
	Super::OnGameFeatureDeactivating(Context);
	
	if(SpawnedActor.IsValid()){
		SpawnedActor->Destroy();
	}
	
	SpawnedActor = nullptr;
}

// ワールドに出す
void UGameFeatureAction_ActorTest::AddToWorld(const FWorldContext &WorldContext, const FGameFeatureStateChangeContext& ChangeContext)
{
	UWorld*				_World = WorldContext.World();
	UGameInstance*		_GameInstance = WorldContext.OwningGameInstance;

	if( (_GameInstance == nullptr)||(_World == nullptr) || (!_World->IsGameWorld()) ){
		return;
	}
	
	const auto& [_ActorClass, _Transform] = ActorSetting;
	if( _ActorClass.IsNull()){
		return;
	}
	
	// ロード
	TSubclassOf<AActor> _SpawnActorClass = _ActorClass.LoadSynchronous();
	// スポーン
	SpawnedActor = _World->SpawnActor<AActor>(_SpawnActorClass, _Transform);
}

テストアクターとデータ設定

新たにプラグイン FeatureTest2を作成し、テスト用アクター TestActorを作成します。
Feature2_TestActor.png

データアセットは以下のように Action に Add Actor (Test)が増えているのでこれを指定、ActorClass には 上記で作成したテストアクターを指定します。
Feature2_TestFeature2.png

実行結果

結果として以下のようにキューブのアクターが追加されます。プラグインを有効以外にするとアクターは始末されます。
Result2.png

アクター生成時に同期ロードをしているので実用には改造が必要だと思われます。

まとめ

機能実装のアクションは GameFeatureAction クラスで拡張可能なようで、UI Widget や GameplayAbility なども追加可能なようにできるようです。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?