You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Tutorial contexts for IOS/Android/Whatever are now hooked up (console setup ones are still missing). Analytics added for tutorial usage in various places. Added ability to reset tutorial state with -ResetTutorials command-line flag. Cleaned up some unused code (still a lot more to come here!). [CL 2302314 by Thomas Sarkanen in Main branch]
305 lines
10 KiB
C++
305 lines
10 KiB
C++
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "IntroTutorialsPrivatePCH.h"
|
|
#include "STutorialRoot.h"
|
|
#include "SEditorTutorials.h"
|
|
#include "EditorTutorialSettings.h"
|
|
#include "TutorialStateSettings.h"
|
|
#include "AssetEditorManager.h"
|
|
#include "ToolkitManager.h"
|
|
#include "IToolkit.h"
|
|
#include "IToolkitHost.h"
|
|
#include "EngineAnalytics.h"
|
|
#include "Runtime/Analytics/Analytics/Public/Interfaces/IAnalyticsProvider.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "STutorialRoot"
|
|
|
|
void STutorialRoot::Construct(const FArguments& InArgs)
|
|
{
|
|
CurrentTutorial = nullptr;
|
|
CurrentTutorialStage = 0;
|
|
CurrentTutorialStartTime = FPlatformTime::Seconds();
|
|
|
|
ChildSlot
|
|
[
|
|
SNullWidget::NullWidget
|
|
];
|
|
}
|
|
|
|
void STutorialRoot::MaybeAddOverlay(TSharedRef<SWindow> InWindow)
|
|
{
|
|
if(InWindow->HasOverlay())
|
|
{
|
|
// check we dont already have a widget overlay for this window
|
|
TWeakPtr<SEditorTutorials>* FoundWidget = TutorialWidgets.Find(InWindow);
|
|
if(FoundWidget == nullptr)
|
|
{
|
|
TSharedPtr<SEditorTutorials> TutorialWidget = nullptr;
|
|
InWindow->AddOverlaySlot()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+SHorizontalBox::Slot()
|
|
.VAlign(VAlign_Fill)
|
|
.HAlign(HAlign_Fill)
|
|
[
|
|
SAssignNew(TutorialWidget, SEditorTutorials)
|
|
.ParentWindow(InWindow)
|
|
.OnNextClicked(FOnNextClicked::CreateSP(this, &STutorialRoot::HandleNextClicked))
|
|
.OnBackClicked(FSimpleDelegate::CreateSP(this, &STutorialRoot::HandleBackClicked))
|
|
.OnHomeClicked(FSimpleDelegate::CreateSP(this, &STutorialRoot::HandleHomeClicked))
|
|
.OnCloseClicked(FSimpleDelegate::CreateSP(this, &STutorialRoot::HandleCloseClicked))
|
|
.OnGetCurrentTutorial(FOnGetCurrentTutorial::CreateSP(this, &STutorialRoot::HandleGetCurrentTutorial))
|
|
.OnGetCurrentTutorialStage(FOnGetCurrentTutorialStage::CreateSP(this, &STutorialRoot::HandleGetCurrentTutorialStage))
|
|
.OnLaunchTutorial(FOnLaunchTutorial::CreateSP(this, &STutorialRoot::LaunchTutorial))
|
|
]
|
|
];
|
|
|
|
FoundWidget = &TutorialWidgets.Add(InWindow, TutorialWidget);
|
|
|
|
FoundWidget->Pin()->RebuildCurrentContent();
|
|
}
|
|
}
|
|
|
|
TArray<TSharedRef<SWindow>> ChildWindows = InWindow->GetChildWindows();
|
|
for(auto& ChildWindow : ChildWindows)
|
|
{
|
|
MaybeAddOverlay(ChildWindow);
|
|
}
|
|
}
|
|
|
|
void STutorialRoot::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
|
|
{
|
|
TArray<TSharedRef<SWindow>> Windows = FSlateApplication::Get().GetInteractiveTopLevelWindows();
|
|
for(auto& Window : Windows)
|
|
{
|
|
MaybeAddOverlay(Window);
|
|
}
|
|
}
|
|
|
|
void STutorialRoot::SummonTutorialBrowser(TSharedRef<SWindow> InWindow, const FString& InFilter)
|
|
{
|
|
TWeakPtr<SEditorTutorials>* FoundWidget = TutorialWidgets.Find(InWindow);
|
|
if(FoundWidget != nullptr && FoundWidget->IsValid())
|
|
{
|
|
FoundWidget->Pin()->ShowBrowser(InFilter);
|
|
}
|
|
}
|
|
|
|
void STutorialRoot::LaunchTutorial(UEditorTutorial* InTutorial, bool bInRestart, TWeakPtr<SWindow> InNavigationWindow, FSimpleDelegate InOnTutorialClosed, FSimpleDelegate InOnTutorialExited)
|
|
{
|
|
if(InTutorial != nullptr)
|
|
{
|
|
CurrentTutorial = InTutorial;
|
|
|
|
bool bHaveSeenTutorial = false;
|
|
CurrentTutorialStage = bInRestart ? 0 : GetDefault<UTutorialStateSettings>()->GetProgress(CurrentTutorial, bHaveSeenTutorial);
|
|
|
|
// check if we should be launching this tutorial for an asset editor
|
|
if(InTutorial->AssetToUse.IsValid())
|
|
{
|
|
TArray<FString> AssetPaths;
|
|
AssetPaths.Add(InTutorial->AssetToUse.AssetLongPathname);
|
|
FAssetEditorManager::Get().OpenEditorsForAssets(AssetPaths);
|
|
|
|
UObject* Asset = InTutorial->AssetToUse.ResolveObject();
|
|
if(Asset != nullptr)
|
|
{
|
|
TSharedPtr<IToolkit> Toolkit = FToolkitManager::Get().FindEditorForAsset( Asset );
|
|
if(Toolkit.IsValid())
|
|
{
|
|
InNavigationWindow = FSlateApplication::Get().FindWidgetWindow(Toolkit->GetToolkitHost()->GetParentWidget());
|
|
}
|
|
}
|
|
}
|
|
|
|
CurrentTutorialStartTime = FPlatformTime::Seconds();
|
|
|
|
// launch tutorial for all windows we wrap - any tutorial can display over any window
|
|
for(auto& TutorialWidget : TutorialWidgets)
|
|
{
|
|
if(TutorialWidget.Value.IsValid())
|
|
{
|
|
bool bIsNavigationWindow = false;
|
|
if (!InNavigationWindow.IsValid())
|
|
{
|
|
bIsNavigationWindow = TutorialWidget.Value.Pin()->IsNavigationVisible();
|
|
}
|
|
else
|
|
{
|
|
bIsNavigationWindow = (TutorialWidget.Value.Pin()->GetParentWindow() == InNavigationWindow.Pin());
|
|
}
|
|
TutorialWidget.Value.Pin()->LaunchTutorial(bIsNavigationWindow, InOnTutorialClosed, InOnTutorialExited);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void STutorialRoot::CloseAllTutorialContent()
|
|
{
|
|
for (auto& TutorialWidget : TutorialWidgets)
|
|
{
|
|
if (TutorialWidget.Value.IsValid())
|
|
{
|
|
TutorialWidget.Value.Pin()->HideContent();
|
|
}
|
|
}
|
|
}
|
|
|
|
void STutorialRoot::HandleNextClicked(TWeakPtr<SWindow> InNavigationWindow)
|
|
{
|
|
GoToNextStage(InNavigationWindow);
|
|
|
|
for(auto& TutorialWidget : TutorialWidgets)
|
|
{
|
|
if(TutorialWidget.Value.IsValid())
|
|
{
|
|
TSharedPtr<SEditorTutorials> PinnedTutorialWidget = TutorialWidget.Value.Pin();
|
|
PinnedTutorialWidget->RebuildCurrentContent();
|
|
}
|
|
}
|
|
}
|
|
|
|
void STutorialRoot::HandleBackClicked()
|
|
{
|
|
if( FEngineAnalytics::IsAvailable() && CurrentTutorial != nullptr)
|
|
{
|
|
TArray<FAnalyticsEventAttribute> EventAttributes;
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("Context.Tutorial"), FIntroTutorials::AnalyticsEventNameFromTutorial(TEXT(""), CurrentTutorial)));
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("Context.StageIndex"), CurrentTutorialStage));
|
|
|
|
FEngineAnalytics::GetProvider().RecordEvent( TEXT("Rocket.Tutorials.ClickedBackButton"), EventAttributes );
|
|
}
|
|
|
|
GoToPreviousStage();
|
|
|
|
for(auto& TutorialWidget : TutorialWidgets)
|
|
{
|
|
if(TutorialWidget.Value.IsValid())
|
|
{
|
|
TSharedPtr<SEditorTutorials> PinnedTutorialWidget = TutorialWidget.Value.Pin();
|
|
PinnedTutorialWidget->RebuildCurrentContent();
|
|
}
|
|
}
|
|
}
|
|
|
|
void STutorialRoot::HandleHomeClicked()
|
|
{
|
|
if(CurrentTutorial != nullptr)
|
|
{
|
|
GetMutableDefault<UTutorialStateSettings>()->RecordProgress(CurrentTutorial, CurrentTutorialStage);
|
|
GetMutableDefault<UTutorialStateSettings>()->SaveProgress();
|
|
}
|
|
|
|
CurrentTutorial = nullptr;
|
|
CurrentTutorialStage = 0;
|
|
|
|
for(auto& TutorialWidget : TutorialWidgets)
|
|
{
|
|
if(TutorialWidget.Value.IsValid())
|
|
{
|
|
TSharedPtr<SEditorTutorials> PinnedTutorialWidget = TutorialWidget.Value.Pin();
|
|
PinnedTutorialWidget->RebuildCurrentContent();
|
|
}
|
|
}
|
|
}
|
|
|
|
UEditorTutorial* STutorialRoot::HandleGetCurrentTutorial()
|
|
{
|
|
return CurrentTutorial;
|
|
}
|
|
|
|
int32 STutorialRoot::HandleGetCurrentTutorialStage()
|
|
{
|
|
return CurrentTutorialStage;
|
|
}
|
|
|
|
void STutorialRoot::AddReferencedObjects( FReferenceCollector& Collector )
|
|
{
|
|
if(CurrentTutorial != nullptr)
|
|
{
|
|
Collector.AddReferencedObject(CurrentTutorial);
|
|
}
|
|
}
|
|
|
|
void STutorialRoot::GoToPreviousStage()
|
|
{
|
|
if(CurrentTutorial != nullptr)
|
|
{
|
|
int32 PreviousTutorialStage = CurrentTutorialStage;
|
|
if (CurrentTutorialStage > 0)
|
|
{
|
|
CurrentTutorial->HandleTutorialStageEnded(CurrentTutorial->Stages[CurrentTutorialStage].Name);
|
|
}
|
|
|
|
CurrentTutorialStage = FMath::Max(0, CurrentTutorialStage - 1);
|
|
|
|
if (PreviousTutorialStage != CurrentTutorialStage)
|
|
{
|
|
CurrentTutorial->HandleTutorialStageStarted(CurrentTutorial->Stages[CurrentTutorialStage].Name);
|
|
}
|
|
}
|
|
}
|
|
|
|
void STutorialRoot::GoToNextStage(TWeakPtr<SWindow> InNavigationWindow)
|
|
{
|
|
if(CurrentTutorial != nullptr)
|
|
{
|
|
UEditorTutorial* PreviousTutorial = CurrentTutorial;
|
|
int32 PreviousTutorialStage = CurrentTutorialStage;
|
|
if(CurrentTutorialStage < CurrentTutorial->Stages.Num())
|
|
{
|
|
CurrentTutorial->HandleTutorialStageEnded(CurrentTutorial->Stages[CurrentTutorialStage].Name);
|
|
}
|
|
|
|
if(CurrentTutorialStage + 1 >= CurrentTutorial->Stages.Num() && FName(*CurrentTutorial->NextTutorial.AssetLongPathname) != NAME_None)
|
|
{
|
|
TSubclassOf<UEditorTutorial> NextTutorialClass = LoadClass<UEditorTutorial>(NULL, *CurrentTutorial->NextTutorial.AssetLongPathname, NULL, LOAD_None, NULL);
|
|
if(NextTutorialClass != nullptr)
|
|
{
|
|
LaunchTutorial(NextTutorialClass->GetDefaultObject<UEditorTutorial>(), true, InNavigationWindow, FSimpleDelegate(), FSimpleDelegate());
|
|
}
|
|
else
|
|
{
|
|
FSlateNotificationManager::Get().AddNotification(FNotificationInfo(FText::Format(LOCTEXT("TutorialNotFound", "Could not start next tutorial {0}"), FText::FromString(CurrentTutorial->NextTutorial.AssetLongPathname))));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CurrentTutorialStage = FMath::Min(CurrentTutorialStage + 1, CurrentTutorial->Stages.Num() - 1);
|
|
GetMutableDefault<UTutorialStateSettings>()->RecordProgress(CurrentTutorial, CurrentTutorialStage);
|
|
}
|
|
|
|
if (CurrentTutorial != nullptr && CurrentTutorialStage < CurrentTutorial->Stages.Num() && (CurrentTutorial != PreviousTutorial || CurrentTutorialStage != PreviousTutorialStage))
|
|
{
|
|
CurrentTutorial->HandleTutorialStageStarted(CurrentTutorial->Stages[CurrentTutorialStage].Name);
|
|
}
|
|
}
|
|
}
|
|
|
|
void STutorialRoot::HandleCloseClicked()
|
|
{
|
|
// submit analytics data
|
|
if( FEngineAnalytics::IsAvailable() && CurrentTutorial != nullptr && CurrentTutorialStage < CurrentTutorial->Stages.Num() )
|
|
{
|
|
UEditorTutorial* AttractTutorial = nullptr;
|
|
UEditorTutorial* LaunchTutorial = nullptr;
|
|
FString BrowserFilter;
|
|
GetDefault<UEditorTutorialSettings>()->FindTutorialInfoForContext(TEXT("LevelEditor"), AttractTutorial, LaunchTutorial, BrowserFilter);
|
|
|
|
// prepare and send analytics data
|
|
bool const bClosedInitialAttract = (CurrentTutorial == AttractTutorial);
|
|
|
|
FString const CurrentExcerptTitle = bClosedInitialAttract ? TEXT("InitialAttract") : CurrentTutorial->Stages[CurrentTutorialStage].Name.ToString();
|
|
int32 const CurrentExcerptIndex = bClosedInitialAttract ? -1 : CurrentTutorialStage;
|
|
float const CurrentPageElapsedTime = bClosedInitialAttract ? 0.f : (float)(FPlatformTime::Seconds() - CurrentTutorialStartTime);
|
|
|
|
TArray<FAnalyticsEventAttribute> EventAttributes;
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("LastStageIndex"), CurrentExcerptIndex));
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("LastStageTitle"), CurrentExcerptTitle));
|
|
EventAttributes.Add(FAnalyticsEventAttribute(TEXT("TimeSpentInTutorial"), CurrentPageElapsedTime));
|
|
|
|
FEngineAnalytics::GetProvider().RecordEvent( FIntroTutorials::AnalyticsEventNameFromTutorial(TEXT("Rocket.Tutorials.Closed"), CurrentTutorial), EventAttributes );
|
|
}
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE |