UE4 Loading screen 模块

本文参考:
https://github.com/ue4plugins/LoadingScreen
https://wiki.unrealengine.com/Loading_Screen

因为openlevel是阻塞式加载场景, 为了在切地图的时候,有loading图, 所以需要异步线程显示loading图.
LoadingScreen 就是做这个工作的.

使用loading screen时, 必须注意以下几点:

  1. 必须在launch game模式下运行.


    image.png

    image.png
  2. 必须关闭Pre-Loading Screen Movie Player插件


    image.png
  3. FLoadingScreenAttributes 中Movies必须有内容.(在window下是任何字符串都行, 即使没有对应的资源也可以. 在andorid和Apple下必须要有相应的资源. )如果没有, loading就不会显示


    image.png

在使用MoviePlayer的时候, 通过接口获得:

IGameMoviePlayer* GetMoviePlayer()
{
    if (!IsMoviePlayerEnabled() || GUsingNullRHI)
    {
        return FNullGameMoviePlayer::Get();
    }
    else
    {
        return FDefaultGameMoviePlayer::Get();
    }
}

这里关键的函数IsMoviePlayerEnabled, 即在编辑器模式是永远不能启动FDefaultGameMoviePlayer类的.
而FNullGameMoviePlayer类是什么都不做的. 这个就解释了为什么loading screen必须在非editor模式下运行.

bool IsMoviePlayerEnabled()
{
    bool bEnabled = !GIsEditor && !IsRunningDedicatedServer() && !IsRunningCommandlet() && GUseThreadedRendering;

#if !UE_BUILD_SHIPPING
    bEnabled &= !FParse::Param(FCommandLine::Get(), TEXT("NoLoadingScreen"));
#endif

    return bEnabled;
}

在启动loadingscreen时候, 启动方法:

GetMoviePlayer()->SetupLoadingScreen(...);

手动停止LoadingScreen方法:

GetMoviePlayer()->StopMovie()

在游戏启动(或者editor启动)的时候, 就确定了是哪个类进行创建.

void CreateMoviePlayer()
{
    // Do not create the movie player if it already exists
    if(!GetMoviePlayer())
    {
        if (!IsMoviePlayerEnabled() || GUsingNullRHI)
        {
            return FNullGameMoviePlayer::Create();
        }
        else
        {
            return FDefaultGameMoviePlayer::Create();
        }
    }
}
image.png

在FDefaultGameMoviePlayer init的时候, 注册了一个核心回调函数:PreLoadMap
该函数就是在loadmap之前回调处理各种事件用了, 其实如果想实现一个自己的loadingscreen, 就可以创建一个线程, 利用该回调, 进行自身的处理.

void FDefaultGameMoviePlayer::Initialize(FSlateRenderer& InSlateRenderer, TSharedPtr TargetRenderWindow)
{
    ... 179行
    // Add a delegate to start playing movies when we start loading a map
    FCoreUObjectDelegates::PreLoadMap.AddRaw( this, &FDefaultGameMoviePlayer::OnPreLoadMap );
}

当游戏运行的时候:


image.png

这个函数就是在启动的时候播放一个启动动画.

void FDefaultGameMoviePlayer::SetupLoadingScreenFromIni()
{
    // We may have already setup a movie from a startup module
    if( !LoadingScreenAttributes.IsValid() )
    {
        // fill out the attributes
        FLoadingScreenAttributes LoadingScreen;

        bool bWaitForMoviesToComplete = false;
        // Note: this code is executed too early so we cannot access UMoviePlayerSettings because the configs for that object have not been loaded and coalesced .  Have to read directly from the configs instead
        GConfig->GetBool(TEXT("/Script/MoviePlayer.MoviePlayerSettings"), TEXT("bWaitForMoviesToComplete"), bWaitForMoviesToComplete, GGameIni);
        GConfig->GetBool(TEXT("/Script/MoviePlayer.MoviePlayerSettings"), TEXT("bMoviesAreSkippable"), LoadingScreen.bMoviesAreSkippable, GGameIni);

        LoadingScreen.bAutoCompleteWhenLoadingCompletes = !bWaitForMoviesToComplete;

        TArray StartupMovies;
        GConfig->GetArray(TEXT("/Script/MoviePlayer.MoviePlayerSettings"), TEXT("StartupMovies"), StartupMovies, GGameIni);

        if (StartupMovies.Num() == 0)
        {
            StartupMovies.Add(TEXT("Default_Startup"));
        }

        // double check that the movies exist
        // We dont know the extension so compare against any file in the directory with the same name for now
        // @todo New Movie Player: movies should have the extension on them when set via the project settings
        TArray ExistingMovieFiles;
        IFileManager::Get().FindFiles(ExistingMovieFiles, *(FPaths::ProjectContentDir() + TEXT("Movies")));

        bool bHasValidMovie = false;
        for(const FString& Movie : StartupMovies)
        {
            bool bFound = ExistingMovieFiles.ContainsByPredicate(
                [&Movie](const FString& ExistingMovie)
                {
                    return ExistingMovie.Contains(Movie);
                });

            if(bFound)
            {
                bHasValidMovie = true;
                LoadingScreen.MoviePaths.Add(Movie);
            }
        }

        if(bHasValidMovie)
        {
            // These movies are all considered safe to play in very early startup sequences
            LoadingScreen.bAllowInEarlyStartup = true;

            // now setup the actual loading screen
            SetupLoadingScreen(LoadingScreen);
        }
    }
}

真正播放play Movie的函数:

void FDefaultGameMoviePlayer::OnPreLoadMap(const FString& LevelName)
{
    FCoreUObjectDelegates::PostLoadMapWithWorld.RemoveAll(this);

    if( PlayMovie() )
    {
        FCoreUObjectDelegates::PostLoadMapWithWorld.AddRaw(this, &FDefaultGameMoviePlayer::OnPostLoadMap );
    }
}

在执行PlayMovie时候, 会根据LoadingScreenAttributes.MoviePaths初始化ActiveMovieStreamer, 如果ActiveMovieStreamer是无效, 则启动失败,这就要求一定要设置MoviePaths.

bool FDefaultGameMoviePlayer::PlayMovie()
{
    bool bBeganPlaying = false;

    // Allow systems to hook onto the movie player and provide loading screen data on demand 
    // if it has not been setup explicitly by the user.
    if ( !LoadingScreenIsPrepared() )
    {
        OnPrepareLoadingScreenDelegate.Broadcast();
    }

    if (LoadingScreenIsPrepared() && !IsMovieCurrentlyPlaying() && FPlatformMisc::NumberOfCores() > 1)
    {
        check(LoadingScreenAttributes.IsValid());
        bUserCalledFinish = false;
        
        LastPlayTime = FPlatformTime::Seconds();

        ActiveMovieStreamer.Reset();
        if (MovieStreamingIsPrepared())
        {
            for (TSharedPtr MovieStreamer : MovieStreamers)
            {
                if (MovieStreamer->Init(LoadingScreenAttributes.MoviePaths, LoadingScreenAttributes.PlaybackType))
                {
                    ActiveMovieStreamer = MovieStreamer;
                    if (MovieViewportWeakPtr.IsValid())
                    {
                        MovieViewportWeakPtr.Pin()->SetViewportInterface(MovieStreamer->GetViewportInterface().ToSharedRef());
                    }
                    break;
                }
            }
        }

        if (ActiveMovieStreamer.IsValid())
        {
            MovieStreamingIsDone.Set(MovieStreamingIsPrepared() ? 0 : 1);
            LoadingIsDone.Set(0);
            IsMoviePlaying = true;

            UserWidgetDPIScaler->SetDPIScale(GetViewportDPIScale());
            
            UserWidgetHolder->SetContent(LoadingScreenAttributes.WidgetLoadingScreen.IsValid() ? LoadingScreenAttributes.WidgetLoadingScreen.ToSharedRef() : SNullWidget::NullWidget);
            VirtualRenderWindow->Resize(MainWindow.Pin()->GetClientSizeInScreen());
            VirtualRenderWindow->SetContent(LoadingScreenContents.ToSharedRef());
        
            {
                FScopeLock SyncMechanismLock(&SyncMechanismCriticalSection);
                SyncMechanism = new FSlateLoadingSynchronizationMechanism(WidgetRenderer);
                SyncMechanism->Initialize();
            }

            bBeganPlaying = true;
        }

        //Allow anything that set up this LoadingScreenAttribute to know the loading screen is now displaying
        if (bBeganPlaying)
        {
            OnMoviePlaybackStarted().Broadcast();
        }
    }

    return bBeganPlaying;
}

在window下MovieStreamer->Init执行的是FMediaFoundationMovieStreamer::Init, 所以, 可以随点填

bool FMediaFoundationMovieStreamer::Init(const TArray& MoviePaths, TEnumAsByte inPlaybackType)
{
    if (MoviePaths.Num() == 0)
    {
        return false;
    }

    MovieIndex = 0;
    PlaybackType = inPlaybackType;
    StoredMoviePaths = MoviePaths;

    MovieViewport->SetTexture(nullptr);

    OpenNextMovie();

    return true;
}

在android下调用FAndroidMediaPlayerStreamer::Init, 所以必须有对应的资源文件

bool FAndroidMediaPlayerStreamer::Init(const TArray& MoviePaths, TEnumAsByte inPlaybackType)
{
    {
        FScopeLock Lock(&MovieQueueCriticalSection);
        MovieQueue.Append(MoviePaths);
    }
    return StartNextMovie();
}

在Apple下也必须有对应的资源文件:

bool FAVPlayerMovieStreamer::Init(const TArray& MoviePaths, TEnumAsByte inPlaybackType)
{
    // 
    // Initializes the streamer for audio and video playback of the given path(s).
    // NOTE: If multiple paths are provided, it is expect that they be played back seamlessly.
    UE_LOG(LogMoviePlayer, Log, TEXT("FAVMoviePlayer init. Path count = %d..."), MoviePaths.Num());

    // Add the given paths to the movie queue
    MovieQueue.Append(MoviePaths);

    // Play the next movie in the queue
    return StartNextMovie();
}

而且,在XXXMovieStreamer::Init的时候, 就会把对应的视频播放.

你可能感兴趣的:(UE4 Loading screen 模块)