首页/手游/《InsideUE5》GameFeatures架构(三)初始化

《InsideUE5》GameFeatures架构(三)初始化

机机复机机,机机何其多引言上篇基础用法的文章其实是有点水的,当然这绝对不是因为我懒,而是因为的用法就是如此的简单,嗯,一定是这样!...

机机复机机,机机何其多引言

《InsideUE5》GameFeatures架构(三)初始化

上篇基础用法的文章其实是有点水的,当然这绝对不是因为我懒,而是因为的用法就是如此的简单,嗯,一定是这样! 我个人的习惯是常常会花巨量的篇幅在尝试努力解释清楚一个事物背后的概念和设计动机,继而再努力尝试去推演出应该如何正确的使用它,更佳姿势是什么。如果文章只是在对着API描绘这个是干什么用的,而不去解释背后的Why,那就只是浮于表层的了解。而我相信只有在深层的机制原理理解之上,才有足够的信心能够用好一个模块。希望一起学习一起共勉。

核心概念

在解释各种机制之前,,还是得向简明扼要的梳理一遍会遇见的各种关键类和名词术语缩写,一是避免歧义,二当然是为了省事,缩写比较方便。

现在如果对这些概念是云里雾里也没有关系,后文会细细讲述。此时只要记住各个缩写代表的是什么东西就行了。

简易理解

我知道到这里,可能还是会有朋友对这个框架运行机制有些模糊。这里我画了一张简易的示意图。用简单的话来说,就是创建不同的GF,每个GF里都有一个同名的GFD,每个GFD描述了这个GF要执行的动作列表。我们可以扩充这个以符合我们的 *** 需要。 我们的操作是在各GF的GFD里配置上各种。GF激活的时候,会执行::ing();GF取消激活的时候,执行:: ating()。因此一个GFA典型的动作发生在ing和ating这两个回调上,正如和相对于Actor的作用一样。前文也说过,我们可以继承下来进行自定义扩展,GFA中最重要的一个就是,其重要性大概会占整个GFA体系的一半,因此后面也会花更多篇章来解释。

初始化

每次在讲解一个新模块的时候,总会感觉初始的时候有点千头万绪难以掐头入手。但久病成医,也渐渐摸索出一些门路。在以往的文章里也曾经说过,可以以时空的视角,从时间和空间的两个维度来理解一个系统。在时间上关心最开始是什么样的、中间经过哪些步骤、最后是什么结果;在空间上关心定义了哪些数据、数据是怎么被配置和修改的、数据流向哪里。如果能理清这二者的作用反应过程,也就差不多理解了其架构和逻辑流转方式。因此对于也是如此,想要理解它的机制,首先要理解它是怎么从无到有一步步初始化起来的。

一,最初初始化

框架的使用过程其实涉及到了有好几个类,首先框架的核心管理类是em,它是继承自,这意味着它是跟随着引擎启动的。在的时候无所谓,但是在情况下,我们要注意,em内部管理的GF状态是跟着引擎编辑器一起的。因此即使你停止PIE播放,GF插件也依然是保持加载状态的。因此在实践中,我会推崇大家自己手动在游戏中通过API来激活或反激活GF,而不是利用编辑器上的UI按钮。

更先开始运行的当然是重载的的方法调用。

void UGameFeaturesSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
    const FSoftClassPath& PolicyClassPath = GetDefault<UGameFeaturesSubsystemSettings>()->GameFeaturesManagerClassName;
    UClass* SingletonClass = nullptr;
    if (!PolicyClassPath.IsNull())
    {
        SingletonClass = LoadClass<UGameFeaturesProjectPolicies>(nullptr, *PolicyClassPath.ToString());
    }
    if (SingletonClass == nullptr)
    {
        SingletonClass = UDefaultGameFeaturesProjectPolicies::StaticClass();
    }
    GameSpecificPolicies = NewObject<UGameFeaturesProjectPolicies>(this, SingletonClass);//创建策略对象

    //注册UAssetManager回调,以便之后配置GFD的PrimaryAssetType
    UAssetManager::CallOrRegister_OnAssetManagerCreated(FSimpleMulticastDelegate::FDelegate::CreateUObject(this, &ThisClass::OnAssetManagerCreated));
    //注册控制台命令,有:istGameFeaturePlugins,LoadGameFeaturePlugin,DeactivateGameFeaturePlugin,UnloadGameFeaturePlugin
    IConsoleManager::Get().RegisterConsoleCommand(...);
}

注意:GF状态在编辑器模式下依然存续,因此可能会跨游戏依然存在,要记得自己取消激活状态。

二,创建GF加载策略

如上文代码所述,内部会创建一个策略对象来负责决定哪些GF应该加载,哪些是被禁用的,也可用来决定在服务端和客户端是否应该允许加载某些插件。 引擎已经内建实现了一个默认的策略对象,默认是会加载所有的GF插件。如果我们需要自定义自己的策略,比如某些GF插件只是用来测试的,后期要关闭掉,就可以继承重载一个自己的策略对象,如。在里面可以实现自己的过滤器和判断逻辑。

三,配置GF加载策略

一般的应用场景是,根据自己游戏的需要灵活的定义额外的配置项,比如游戏版本、补丁修复、游戏活动、AB测试等。举个例子,假设2.0版本的游戏要禁用掉以前1.0版本搞活动时的一个GF插件,可以继承定义一个我们自己的对象,然后在Init里的过滤器里实现自己的筛选逻辑,比如截图里就示例了根据里的键来指定版本号,然后对比。这里要注意的是,要先在 *** 设置里配置上 Keys,才能把文件里的自定义键识别解析到.里,才可以进行后续的判断。至于要添加什么键,就看各位自己的 *** 需要了。

在GF.文件里可以加上自定义的项:

在代码里获取项的值进行判断:

UCLASS()
class LEARNGF_API UMyGameFeaturesProjectPolicies : public UGameFeaturesProjectPolicies
{
    GENERATED_BODY()
public:
    //~UGameFeaturesProjectPolicies interface
    virtual void InitGameFeatureManager() override;
    virtual void GetGameFeatureLoadingMode(bool& bLoadClientData, bool& bLoadServerData) const override;
    virtual TArray<FPrimaryAssetId> GetPreloadAssetListForGameFeature(const UGameFeatureData* GameFeatureToLoad) const { return TArray<FPrimaryAssetId>(); }
    virtual bool IsPluginAllowed(const FString& PluginURL) const { return true; }
    //~End of UGameFeaturesProjectPolicies interface
};
//自定义的写法,和UDefaultGameFeaturesProjectPolicies的默认写法差不多
void UMyGameFeaturesProjectPolicies::InitGameFeatureManager()
{
    auto AdditionalFilter = [&](const FString& PluginFilename, const FGameFeaturePluginDetails& PluginDetails, FBuiltInGameFeaturePluginBehaviorOptions& OutOptions) -> bool
    {   //可以自己写判断逻辑
        if (const FString* myGameVersion = PluginDetails.AdditionalMetadata.Find(TEXT("MyGameVersion")))
        {
            float verison = FCString::Atof(**myGameVersion);
            if (verison > 2.0)
            {
                return true;
            }
        }
        return false;
    };
    UGameFeaturesSubsystem::Get().LoadBuiltInGameFeaturePlugins(AdditionalFilter);  //加载内建的所有可能为GF的插件
}

再来说一下对象里面比较重要的几个方法:

四,GFS设置 -

关于 *** 设置里GF的设置,是由对象来定义的。里面目前有两个键比较重要:

当然里面也有别的键可以自己配置,甚至可以自己扩展新的配置项。

五,GFD Asset 配置

在创建完对象之后,会开始在内部配置来识别GFD对象。强烈依赖AM来识别加载资产,因此的配置里一定要添加GFD的主资产类型,此项一般来说在创建完GF后默认就已配置好。但如果你是手动迁移的GF插件到一个新 *** ,就要好好检查一下了。

只有配置了上述选项,才能在后续正确的加载该GFD资产:

如果深究代码,体现该逻辑的地方就在于,但是也请注意是在后续GF的状态机的时候加载,这个时候提前亮相只是说明跟之后的关联。而且只有通过的的GF插件,之后才有机会被尝试加载。

TSharedPtr<FStreamableHandle> UGameFeaturesSubsystem::LoadGameFeatureData(const FString& GameFeatureToLoad)
{
    UAssetManager& LocalAssetManager = UAssetManager::Get();
    IAssetRegistry& LocalAssetRegistry = LocalAssetManager.GetAssetRegistry();
    FAssetData GameFeatureAssetData = LocalAssetRegistry.GetAssetByObjectPath(FName(*GameFeatureToLoad));
    if (GameFeatureAssetData.IsValid())
    {
        FPrimaryAssetId AssetId = GameFeatureAssetData.GetPrimaryAssetId();
        // Add the GameFeatureData itself to the primary asset list
        LocalAssetManager.RegisterSpecificPrimaryAsset(AssetId, GameFeatureAssetData);  //如果之前没配置,就会失败

        // LoadPrimaryAsset will return a null handle if the AssetID is already loaded. Check if there is an existing handle first.
        TSharedPtr<FStreamableHandle> ReturnHandle = LocalAssetManager.GetPrimaryAssetHandle(AssetId);
        if (ReturnHandle.IsValid())
        {
            return ReturnHandle;
        }
        else
        {
            return LocalAssetManager.LoadPrimaryAsset(AssetId);
        }
    }
    return nullptr;
}

六,加载解析GF.

在上文的::er()最后一步是em::Get().();,会遍历当前 *** 目录下所有插件,然后尝试加载其中的GF插件:

void UGameFeaturesSubsystem::LoadBuiltInGameFeaturePlugins(FBuiltInPluginAdditionalFilters AdditionalFilter)
{
    TArray<TSharedRef<IPlugin>> EnabledPlugins = IPluginManager::Get().GetEnabledPlugins();
    for (const TSharedRef<IPlugin>& Plugin : EnabledPlugins)//遍历所有插件
    {
        LoadBuiltInGameFeaturePlugin(Plugin, AdditionalFilter);
    }
}

而正是加载GF插件最关键的函数:

void UGameFeaturesSubsystem::LoadBuiltInGameFeaturePlugin(const TSharedRef<IPlugin>& Plugin, FBuiltInPluginAdditionalFilters AdditionalFilter)
{
    const FString& PluginDescriptorFilename = Plugin->GetDescriptorFileName();
    if (!PluginDescriptorFilename.IsEmpty() && FPaths::ConvertRelativePathToFull(PluginDescriptorFilename).StartsWith(GetDefault<UGameFeaturesSubsystemSettings>()->BuiltInGameFeaturePluginsFolder) && FPaths::FileExists(PluginDescriptorFilename))//写死了GF必须待在“GameFeatures”目录下
    {
        const FString PluginURL = TEXT("file:") + PluginDescriptorFilename;
        if (GameSpecificPolicies->IsPluginAllowed(PluginURL))   //Policy是否允许该插件
        {
            FGameFeaturePluginDetails PluginDetails;
            if (GetGameFeaturePluginDetails(PluginDescriptorFilename, PluginDetails))//读取json的uplugin文件信息
            {
                FBuiltInGameFeaturePluginBehaviorOptions BehaviorOptions;
                bool bShouldProcess = AdditionalFilter(PluginDescriptorFilename, PluginDetails, BehaviorOptions);//进行过滤器判定
                if (bShouldProcess)
                {
                    UGameFeaturePluginStateMachine* StateMachine = GetGameFeaturePluginStateMachine(PluginURL, true);//创建状态机
                    const EBuiltInAutoState InitialAutoState = (BehaviorOptions.AutoStateOverride != EBuiltInAutoState::Invalid) ? BehaviorOptions.AutoStateOverride : PluginDetails.BuiltInAutoState;
                    const EGameFeaturePluginState DestinationState = ConvertInitialFeatureStateToTargetState(InitialAutoState);
                    if (StateMachine->GetCurrentState() >= DestinationState)
                    {
                        // If we're already at the destination or beyond, don't transition back
                        LoadGameFeaturePluginComplete(StateMachine, MakeValue());
                    }
                    else
                    {
                        StateMachine->SetDestinationState(DestinationState, FGameFeatureStateTransitionComplete::CreateUObject(this, &ThisClass::LoadGameFeaturePluginComplete));//更新到目标的初始状态
                    }
                    if (!GameFeaturePluginNameToPathMap.Contains(Plugin->GetName()))
                    {
                        GameFeaturePluginNameToPathMap.Add(Plugin->GetName(), PluginURL);
                    }
                }
            }
        }
    }
}

在通过的初步只根据文件名判定之后,就可以进行下一步的插件信息加载解析。只有解析了GF.之后,才有足够的键值信息来判断这个GF插件是否应该加载或者是禁用掉。GF插件的识别是从解析.文件开始的。我们可以手动编辑这个json文件来详细的描述GF插件。这里有几个键值得一说:

本文转载自互联网,如有侵权,联系邮箱删除:25538@qq.com!

相关文章

CVCV资讯网

为您提供重点新闻资讯,优质的产品,以及全面的百科知识!无论是国内外时事、社会热点、还是知识问答,我们都将第一时间为您报道。