添加新手引导系统
This commit is contained in:
parent
3959389f3e
commit
d5c3c82fec
Binary file not shown.
Binary file not shown.
5
ProjectFish/Config/DefaultDialogue.ini
Normal file
5
ProjectFish/Config/DefaultDialogue.ini
Normal file
@ -0,0 +1,5 @@
|
||||
|
||||
|
||||
[/Script/Dialogue.DialogueSettings]
|
||||
DialogueWidth=125.000000
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
GameDefaultMap=/Game/TopDown/Maps/TopDownMap.TopDownMap
|
||||
EditorStartupMap=/Game/ART/Map/fishing.fishing
|
||||
GlobalDefaultGameMode="/Script/ProjectFish.ProjectFishGameMode"
|
||||
GameInstanceClass=/Game/Gameplay/BP_FishGameInstance.BP_FishGameInstance_C
|
||||
|
||||
[/Script/Engine.RendererSettings]
|
||||
r.DefaultFeature.AutoExposure.ExtendDefaultLuminanceRange=True
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
ProjectFish/Content/DataAssets/Quest/Quest_Tutorial_First.uasset
Normal file
BIN
ProjectFish/Content/DataAssets/Quest/Quest_Tutorial_First.uasset
Normal file
Binary file not shown.
BIN
ProjectFish/Content/DataTable/DT_TutorialSteps.uasset
Normal file
BIN
ProjectFish/Content/DataTable/DT_TutorialSteps.uasset
Normal file
Binary file not shown.
Binary file not shown.
BIN
ProjectFish/Content/Gameplay/BP_FishGameInstance.uasset
Normal file
BIN
ProjectFish/Content/Gameplay/BP_FishGameInstance.uasset
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
ProjectFish/Content/UI/Dialogue/UMG_DialogueText_Widget.uasset
Normal file
BIN
ProjectFish/Content/UI/Dialogue/UMG_DialogueText_Widget.uasset
Normal file
Binary file not shown.
BIN
ProjectFish/Content/UI/Dialogue/UMG_DialogueWindow.uasset
Normal file
BIN
ProjectFish/Content/UI/Dialogue/UMG_DialogueWindow.uasset
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
ProjectFish/Content/UI/Tutorial/UMG_Tutorial_FishingTip.uasset
Normal file
BIN
ProjectFish/Content/UI/Tutorial/UMG_Tutorial_FishingTip.uasset
Normal file
Binary file not shown.
BIN
ProjectFish/Content/UI/Tutorial/UMG_Tutorial_MoveTip.uasset
Normal file
BIN
ProjectFish/Content/UI/Tutorial/UMG_Tutorial_MoveTip.uasset
Normal file
Binary file not shown.
Binary file not shown.
1059
ProjectFish/Docs/TutorialSystemDesign.md
Normal file
1059
ProjectFish/Docs/TutorialSystemDesign.md
Normal file
File diff suppressed because it is too large
Load Diff
619
ProjectFish/Docs/TutorialSystemDesign_Simple.md
Normal file
619
ProjectFish/Docs/TutorialSystemDesign_Simple.md
Normal file
@ -0,0 +1,619 @@
|
||||
# 新手引导系统设计(简化版)
|
||||
|
||||
## 一、核心思路
|
||||
|
||||
**一句话概括**:用一个 `TutorialManager` 管理引导流程,通过 Widget 的 `Tag` 控制显示/隐藏/高亮。
|
||||
|
||||
---
|
||||
|
||||
## 二、只需要3个东西
|
||||
|
||||
### 1. TutorialManager(一个管理类)
|
||||
|
||||
```cpp
|
||||
// TutorialManager.h
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Subsystems/GameInstanceSubsystem.h"
|
||||
#include "TutorialManager.generated.h"
|
||||
|
||||
// 简单的步骤配置
|
||||
USTRUCT(BlueprintType)
|
||||
struct FTutorialStep
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
// 步骤ID
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 StepID = 0;
|
||||
|
||||
// 可见的Widget标签(其他都隐藏)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
TArray<FName> VisibleWidgetTags;
|
||||
|
||||
// 高亮的Widget标签
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
TArray<FName> HighlightWidgetTags;
|
||||
|
||||
// 提示文本(可选)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
FText HintText;
|
||||
|
||||
// 对话资产(可选)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
class UDialogueAsset* DialogueAsset;
|
||||
};
|
||||
|
||||
UCLASS()
|
||||
class PROJECTFISH_API UTutorialManager : public UGameInstanceSubsystem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// 开始引导
|
||||
UFUNCTION(BlueprintCallable, Category = "Tutorial")
|
||||
void StartTutorial();
|
||||
|
||||
// 进入下一步
|
||||
UFUNCTION(BlueprintCallable, Category = "Tutorial")
|
||||
void NextStep();
|
||||
|
||||
// 完成引导
|
||||
UFUNCTION(BlueprintCallable, Category = "Tutorial")
|
||||
void FinishTutorial();
|
||||
|
||||
// 是否在引导中
|
||||
UFUNCTION(BlueprintPure, Category = "Tutorial")
|
||||
bool IsInTutorial() const { return bInTutorial; }
|
||||
|
||||
// 获取当前步骤
|
||||
UFUNCTION(BlueprintPure, Category = "Tutorial")
|
||||
int32 GetCurrentStep() const { return CurrentStepIndex; }
|
||||
|
||||
// 检查Widget是否应该显示
|
||||
UFUNCTION(BlueprintPure, Category = "Tutorial")
|
||||
bool ShouldShowWidget(FName WidgetTag) const;
|
||||
|
||||
// 检查Widget是否应该高亮
|
||||
UFUNCTION(BlueprintPure, Category = "Tutorial")
|
||||
bool ShouldHighlightWidget(FName WidgetTag) const;
|
||||
|
||||
// 隐藏遮罩层
|
||||
UFUNCTION(BlueprintCallable, Category = "Tutorial")
|
||||
void ShowMask(bool bShow);
|
||||
|
||||
protected:
|
||||
// 引导步骤配置(在编辑器中配置)
|
||||
UPROPERTY(EditDefaultsOnly, Category = "Tutorial")
|
||||
TArray<FTutorialStep> TutorialSteps;
|
||||
|
||||
UPROPERTY()
|
||||
bool bInTutorial = false;
|
||||
|
||||
UPROPERTY()
|
||||
int32 CurrentStepIndex = -1;
|
||||
|
||||
private:
|
||||
void ApplyCurrentStep();
|
||||
void BroadcastStepChanged();
|
||||
};
|
||||
```
|
||||
|
||||
```cpp
|
||||
// TutorialManager.cpp
|
||||
#include "TutorialManager.h"
|
||||
|
||||
void UTutorialManager::StartTutorial()
|
||||
{
|
||||
bInTutorial = true;
|
||||
CurrentStepIndex = 0;
|
||||
ApplyCurrentStep();
|
||||
}
|
||||
|
||||
void UTutorialManager::NextStep()
|
||||
{
|
||||
if (!bInTutorial) return;
|
||||
|
||||
CurrentStepIndex++;
|
||||
if (CurrentStepIndex >= TutorialSteps.Num())
|
||||
{
|
||||
FinishTutorial();
|
||||
return;
|
||||
}
|
||||
|
||||
ApplyCurrentStep();
|
||||
}
|
||||
|
||||
void UTutorialManager::FinishTutorial()
|
||||
{
|
||||
bInTutorial = false;
|
||||
CurrentStepIndex = -1;
|
||||
|
||||
// 保存到存档
|
||||
// GameInfoManager->SetTutorialCompleted(true);
|
||||
|
||||
ShowMask(false);
|
||||
}
|
||||
|
||||
bool UTutorialManager::ShouldShowWidget(FName WidgetTag) const
|
||||
{
|
||||
if (!bInTutorial) return true; // 正常模式全显示
|
||||
|
||||
if (!TutorialSteps.IsValidIndex(CurrentStepIndex))
|
||||
return true;
|
||||
|
||||
const FTutorialStep& Step = TutorialSteps[CurrentStepIndex];
|
||||
|
||||
// 如果没有配置,默认显示
|
||||
if (Step.VisibleWidgetTags.Num() == 0)
|
||||
return true;
|
||||
|
||||
// 白名单模式:只显示列表中的
|
||||
return Step.VisibleWidgetTags.Contains(WidgetTag);
|
||||
}
|
||||
|
||||
bool UTutorialManager::ShouldHighlightWidget(FName WidgetTag) const
|
||||
{
|
||||
if (!bInTutorial) return false;
|
||||
|
||||
if (!TutorialSteps.IsValidIndex(CurrentStepIndex))
|
||||
return false;
|
||||
|
||||
const FTutorialStep& Step = TutorialSteps[CurrentStepIndex];
|
||||
return Step.HighlightWidgetTags.Contains(WidgetTag);
|
||||
}
|
||||
|
||||
void UTutorialManager::ApplyCurrentStep()
|
||||
{
|
||||
BroadcastStepChanged();
|
||||
|
||||
if (!TutorialSteps.IsValidIndex(CurrentStepIndex))
|
||||
return;
|
||||
|
||||
const FTutorialStep& Step = TutorialSteps[CurrentStepIndex];
|
||||
|
||||
// 如果有对话,播放对话
|
||||
if (Step.DialogueAsset)
|
||||
{
|
||||
// 调用对话系统播放
|
||||
// DialogueSystem->PlayDialogue(Step.DialogueAsset);
|
||||
}
|
||||
|
||||
// 显示遮罩
|
||||
ShowMask(true);
|
||||
}
|
||||
|
||||
void UTutorialManager::BroadcastStepChanged()
|
||||
{
|
||||
// 通知所有UI刷新
|
||||
// 可以用事件委托或直接遍历所有Widget
|
||||
}
|
||||
|
||||
void UTutorialManager::ShowMask(bool bShow)
|
||||
{
|
||||
// 创建/显示/隐藏遮罩Widget
|
||||
// 遮罩Widget是一个全屏的半透明黑色背景
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Widget上设置Tag
|
||||
|
||||
在UMG编辑器中,给每个需要控制的Widget设置Tag:
|
||||
|
||||
```
|
||||
HomeUI:
|
||||
├─ Btn_Sail (Tag: "Btn_Sail")
|
||||
├─ Btn_Market (Tag: "Btn_Market")
|
||||
├─ Btn_Shop (Tag: "Btn_Shop")
|
||||
└─ Btn_Backpack (Tag: "Btn_Backpack")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Widget蓝图中检查是否显示
|
||||
|
||||
在每个Widget的蓝图中,添加简单逻辑:
|
||||
|
||||
```
|
||||
Event Construct:
|
||||
├─ Get TutorialManager
|
||||
├─ Bind to OnStepChanged Event
|
||||
└─ Call UpdateVisibility()
|
||||
|
||||
UpdateVisibility():
|
||||
├─ For each child widget:
|
||||
│ ├─ Get widget tag
|
||||
│ ├─ TutorialManager->ShouldShowWidget(tag)
|
||||
│ └─ Set Visibility (Visible / Collapsed)
|
||||
└─ Update Highlight state
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、15步引导配置(简单表格)
|
||||
|
||||
在 `TutorialManager` 的 `TutorialSteps` 数组中配置:
|
||||
|
||||
| 步骤 | VisibleWidgetTags | HighlightWidgetTags | 说明 |
|
||||
|------|-------------------|---------------------|------|
|
||||
| 0 | [] | [] | 初始对话(DialogueAsset配置) |
|
||||
| 1 | [Btn_Sail] | [Btn_Sail] | 只显示出航按钮并高亮 |
|
||||
| 2 | [MapNode_0] | [MapNode_0] | 地图只显示一个节点 |
|
||||
| 3 | [] | [] | 进入Loading |
|
||||
| 4 | [MovementHint] | [] | 显示移动提示 |
|
||||
| 5 | [FishingHint] | [] | 显示钓鱼提示 |
|
||||
| 6 | [BattleUI] | [] | 战斗界面(隐藏背包/返航) |
|
||||
| ... | ... | ... | ... |
|
||||
|
||||
**配置方式**:在编辑器的 `Project Settings -> Tutorial Manager` 中直接填表格。
|
||||
|
||||
---
|
||||
|
||||
## 四、UI改造(最简单的方式)
|
||||
|
||||
### 方式1:蓝图中直接判断(推荐)
|
||||
|
||||
在每个Widget的蓝图中:
|
||||
|
||||
```blueprint
|
||||
Event Construct:
|
||||
├─ GetGameInstance
|
||||
├─ GetSubsystem(TutorialManager)
|
||||
├─ Bind Event: OnStepChanged -> UpdateVisibility
|
||||
└─ Call UpdateVisibility
|
||||
|
||||
Function UpdateVisibility:
|
||||
├─ If TutorialManager->IsInTutorial():
|
||||
│ ├─ Set Btn_Sail Visibility: ShouldShowWidget("Btn_Sail")
|
||||
│ ├─ Set Btn_Market Visibility: ShouldShowWidget("Btn_Market")
|
||||
│ └─ ...
|
||||
└─ Else: Show All
|
||||
```
|
||||
|
||||
### 方式2:C++中统一处理
|
||||
|
||||
创建一个简单的基类:
|
||||
|
||||
```cpp
|
||||
// TutorialWidget.h
|
||||
#pragma once
|
||||
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "TutorialWidget.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class UTutorialWidget : public UUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual void NativeConstruct() override;
|
||||
|
||||
protected:
|
||||
// 在子类中填写:Widget名 -> Tag的映射
|
||||
UPROPERTY(EditDefaultsOnly, Category = "Tutorial")
|
||||
TMap<FName, UWidget*> WidgetTagMap;
|
||||
|
||||
UFUNCTION()
|
||||
void UpdateVisibility();
|
||||
|
||||
private:
|
||||
UPROPERTY()
|
||||
class UTutorialManager* TutorialManager;
|
||||
};
|
||||
```
|
||||
|
||||
```cpp
|
||||
// TutorialWidget.cpp
|
||||
#include "TutorialWidget.h"
|
||||
#include "TutorialManager.h"
|
||||
|
||||
void UTutorialWidget::NativeConstruct()
|
||||
{
|
||||
Super::NativeConstruct();
|
||||
|
||||
TutorialManager = GetGameInstance()->GetSubsystem<UTutorialManager>();
|
||||
if (TutorialManager)
|
||||
{
|
||||
// 绑定步骤改变事件
|
||||
// TutorialManager->OnStepChanged.AddDynamic(this, &UTutorialWidget::UpdateVisibility);
|
||||
}
|
||||
|
||||
UpdateVisibility();
|
||||
}
|
||||
|
||||
void UTutorialWidget::UpdateVisibility()
|
||||
{
|
||||
if (!TutorialManager || !TutorialManager->IsInTutorial())
|
||||
{
|
||||
// 正常模式:全部显示
|
||||
for (auto& Pair : WidgetTagMap)
|
||||
{
|
||||
if (Pair.Value)
|
||||
Pair.Value->SetVisibility(ESlateVisibility::Visible);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 引导模式:根据Tag判断
|
||||
for (auto& Pair : WidgetTagMap)
|
||||
{
|
||||
if (!Pair.Value) continue;
|
||||
|
||||
bool bShouldShow = TutorialManager->ShouldShowWidget(Pair.Key);
|
||||
Pair.Value->SetVisibility(bShouldShow ? ESlateVisibility::Visible : ESlateVisibility::Collapsed);
|
||||
|
||||
// 高亮效果
|
||||
bool bShouldHighlight = TutorialManager->ShouldHighlightWidget(Pair.Key);
|
||||
if (bShouldHighlight)
|
||||
{
|
||||
// 添加高亮动画或材质效果
|
||||
// PlayAnimation(HighlightAnim);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
然后让 HomeUI 继承这个基类,在构造函数中填写映射:
|
||||
|
||||
```cpp
|
||||
// HomeUIWidget.cpp
|
||||
void UHomeUIWidget::NativeConstruct()
|
||||
{
|
||||
Super::NativeConstruct();
|
||||
|
||||
// 填写映射
|
||||
WidgetTagMap.Add(TEXT("Btn_Sail"), Btn_Sail);
|
||||
WidgetTagMap.Add(TEXT("Btn_Market"), Btn_Market);
|
||||
WidgetTagMap.Add(TEXT("Btn_Shop"), Btn_Shop);
|
||||
WidgetTagMap.Add(TEXT("Btn_Backpack"), Btn_Backpack);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、特殊情况处理
|
||||
|
||||
### 1. 战斗特殊配置
|
||||
|
||||
不需要新的配置类,直接在战斗开始时检查:
|
||||
|
||||
```cpp
|
||||
// AProjectFishGameMode.cpp
|
||||
void AProjectFishGameMode::StartBattle()
|
||||
{
|
||||
UTutorialManager* Tutorial = GetGameInstance()->GetSubsystem<UTutorialManager>();
|
||||
|
||||
if (Tutorial && Tutorial->IsInTutorial())
|
||||
{
|
||||
int32 Step = Tutorial->GetCurrentStep();
|
||||
|
||||
// 第一场战斗(步骤6)
|
||||
if (Step == 6)
|
||||
{
|
||||
// 使用特定的鱼配置
|
||||
CurrentFish = LoadObject<UFishInfoConfigAsset>(..., TEXT("Tutorial_Fish_01"));
|
||||
|
||||
// 强制掉落
|
||||
bForceDropItem = true;
|
||||
ForcedDropItems.Add(TEXT("Item_Bass"));
|
||||
}
|
||||
// 第二场战斗(步骤9)
|
||||
else if (Step == 9)
|
||||
{
|
||||
CurrentFish = LoadObject<UFishInfoConfigAsset>(..., TEXT("Tutorial_Fish_02"));
|
||||
ForcedDropItems.Add(TEXT("Item_Bass"));
|
||||
ForcedDropItems.Add(TEXT("Item_MagicBook"));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 地图单节点
|
||||
|
||||
```cpp
|
||||
// FishingMapSubSystem.cpp
|
||||
void UFishingMapSubSystem::GenerateMap()
|
||||
{
|
||||
UTutorialManager* Tutorial = GetGameInstance()->GetSubsystem<UTutorialManager>();
|
||||
|
||||
if (Tutorial && Tutorial->IsInTutorial() && Tutorial->GetCurrentStep() == 2)
|
||||
{
|
||||
// 只生成1个节点
|
||||
AllNodes.Empty();
|
||||
UFishingMapNode* Node = NewObject<UFishingMapNode>();
|
||||
Node->NodeType = EMapNodeType::Battle;
|
||||
AllNodes.Add(Node);
|
||||
return;
|
||||
}
|
||||
|
||||
// 正常生成
|
||||
GenerateMapWithConfig(DefaultMapConfig);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 强制拾取奖励
|
||||
|
||||
```cpp
|
||||
// RewardWidget.cpp
|
||||
void URewardWidget::NativeConstruct()
|
||||
{
|
||||
Super::NativeConstruct();
|
||||
|
||||
UTutorialManager* Tutorial = GetGameInstance()->GetSubsystem<UTutorialManager>();
|
||||
|
||||
if (Tutorial && Tutorial->IsInTutorial())
|
||||
{
|
||||
int32 Step = Tutorial->GetCurrentStep();
|
||||
if (Step == 7 || Step == 10) // 两场战斗后
|
||||
{
|
||||
// 隐藏关闭按钮,直到拾取完毕
|
||||
Btn_Close->SetVisibility(ESlateVisibility::Collapsed);
|
||||
bMustCollectAll = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void URewardWidget::OnAllItemsCollected()
|
||||
{
|
||||
if (bMustCollectAll)
|
||||
{
|
||||
Btn_Close->SetVisibility(ESlateVisibility::Visible);
|
||||
// 显示提示:"点击关闭继续"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、遮罩和高亮效果(简单实现)
|
||||
|
||||
### 遮罩Widget(WBP_TutorialMask)
|
||||
|
||||
创建一个简单的UMG:
|
||||
```
|
||||
Canvas Panel (全屏)
|
||||
└─ Image (黑色,透明度0.7)
|
||||
```
|
||||
|
||||
在 TutorialManager 中显示/隐藏它。
|
||||
|
||||
### 高亮效果(3种简单方案)
|
||||
|
||||
**方案1:边框发光**
|
||||
```cpp
|
||||
// 给按钮添加一个Border,设置发光颜色
|
||||
Btn_Sail->SetBorderBrush(HighlightBrush);
|
||||
```
|
||||
|
||||
**方案2:缩放动画**
|
||||
```
|
||||
创建UMG动画:
|
||||
0.0s -> Scale 1.0
|
||||
0.5s -> Scale 1.1
|
||||
1.0s -> Scale 1.0
|
||||
循环播放
|
||||
```
|
||||
|
||||
**方案3:后处理材质**
|
||||
给高亮Widget添加后处理效果(最炫酷,但稍复杂)。
|
||||
|
||||
---
|
||||
|
||||
## 七、完整流程示例
|
||||
|
||||
### 配置TutorialSteps(在编辑器中)
|
||||
|
||||
```cpp
|
||||
TutorialSteps[0]:
|
||||
StepID: 0
|
||||
DialogueAsset: DA_Tutorial_Intro
|
||||
VisibleWidgetTags: []
|
||||
HintText: ""
|
||||
|
||||
TutorialSteps[1]:
|
||||
StepID: 1
|
||||
VisibleWidgetTags: [Btn_Sail]
|
||||
HighlightWidgetTags: [Btn_Sail]
|
||||
HintText: "点击【出航】按钮"
|
||||
|
||||
TutorialSteps[2]:
|
||||
StepID: 2
|
||||
VisibleWidgetTags: [MapNode_0]
|
||||
HighlightWidgetTags: [MapNode_0]
|
||||
HintText: "选择一个地点"
|
||||
|
||||
// ... 继续配置到步骤15
|
||||
```
|
||||
|
||||
### 调用流程
|
||||
|
||||
```cpp
|
||||
// 游戏启动时
|
||||
void AMyGameInstance::Init()
|
||||
{
|
||||
Super::Init();
|
||||
|
||||
UTutorialManager* Tutorial = GetSubsystem<UTutorialManager>();
|
||||
|
||||
// 检查存档
|
||||
bool bCompletedTutorial = GameInfoManager->HasCompletedTutorial();
|
||||
if (!bCompletedTutorial)
|
||||
{
|
||||
Tutorial->StartTutorial();
|
||||
}
|
||||
}
|
||||
|
||||
// 出航按钮点击时
|
||||
void UHomeUIWidget::OnSailButtonClicked()
|
||||
{
|
||||
UTutorialManager* Tutorial = GetGameInstance()->GetSubsystem<UTutorialManager>();
|
||||
|
||||
if (Tutorial && Tutorial->IsInTutorial() && Tutorial->GetCurrentStep() == 1)
|
||||
{
|
||||
// 完成步骤1,进入步骤2
|
||||
Tutorial->NextStep();
|
||||
}
|
||||
|
||||
// 打开地图
|
||||
OpenMapUI();
|
||||
}
|
||||
|
||||
// 地图节点点击时
|
||||
void UMapWidget::OnNodeClicked()
|
||||
{
|
||||
UTutorialManager* Tutorial = GetGameInstance()->GetSubsystem<UTutorialManager>();
|
||||
|
||||
if (Tutorial && Tutorial->IsInTutorial() && Tutorial->GetCurrentStep() == 2)
|
||||
{
|
||||
Tutorial->NextStep();
|
||||
}
|
||||
|
||||
// 进入战斗
|
||||
StartBattle();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、总结
|
||||
|
||||
### 只需要做3件事:
|
||||
|
||||
1. **创建 TutorialManager**(1个类,~200行代码)
|
||||
2. **给UI Widget设置Tag**(在UMG编辑器中操作)
|
||||
3. **配置TutorialSteps表格**(在编辑器中填数据)
|
||||
|
||||
### 改造工作量:
|
||||
|
||||
- ✅ 新增代码:2个文件(TutorialManager + TutorialWidget基类)
|
||||
- ✅ UI改造:继承基类或蓝图中加几行代码
|
||||
- ✅ 特殊逻辑:在现有代码中加if判断
|
||||
- ✅ 总代码量:<500行
|
||||
|
||||
### 优势:
|
||||
|
||||
- 🟢 **极简**:只有1个核心类
|
||||
- 🟢 **直观**:配置就是一个表格
|
||||
- 🟢 **灵活**:可蓝图可C++
|
||||
- 🟢 **低侵入**:不破坏现有结构
|
||||
|
||||
---
|
||||
|
||||
## 九、文件结构
|
||||
|
||||
```
|
||||
Source/ProjectFish/Tutorial/
|
||||
├── TutorialManager.h # 管理类(核心)
|
||||
├── TutorialManager.cpp
|
||||
├── TutorialWidget.h # UI基类(可选)
|
||||
└── TutorialWidget.cpp
|
||||
|
||||
Content/UI/Tutorial/
|
||||
└── WBP_TutorialMask.uasset # 遮罩Widget
|
||||
```
|
||||
|
||||
就这么简单!🎉
|
||||
Binary file not shown.
Binary file not shown.
@ -67,7 +67,7 @@ FText UDialogueAsset::GetNextNodeText()
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("DialogueNode has no output pin"));
|
||||
OnDialogueComplete.Broadcast();
|
||||
}
|
||||
return FText::FromString(TEXT(""));
|
||||
}
|
||||
|
||||
@ -93,6 +93,7 @@ public:
|
||||
};
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FNeedPlayeSelectDelegate, const TArray<FText>&, Options);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDialogueComplete);
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@ -115,6 +116,8 @@ public:
|
||||
public:
|
||||
UPROPERTY(BlueprintAssignable, Category=Dialogue)
|
||||
FNeedPlayeSelectDelegate OnNeedPlayerSelect;
|
||||
UPROPERTY(BlueprintAssignable, Category=Dialogue)
|
||||
FOnDialogueComplete OnDialogueComplete;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Dialogue)
|
||||
TArray<UDialogueRuntimeNode*> NodeDatas;
|
||||
|
||||
@ -17,6 +17,9 @@ class PROJECTFISH_API UPlayerInfoSaveGame : public USaveGame
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(SaveGame, BlueprintReadWrite, meta = (ToolTip = "开启教程模式"))
|
||||
bool TutorialMode = true;
|
||||
|
||||
UPROPERTY(SaveGame, BlueprintReadWrite, meta = (ToolTip = "船只资源"))
|
||||
FPrimaryAssetId ShipAssetID;
|
||||
|
||||
|
||||
@ -3,9 +3,11 @@
|
||||
#include "QuestAsset.h"
|
||||
|
||||
#if WITH_EDITOR
|
||||
#include "DialogueAsset.h"
|
||||
#include "AssetRegistry/AssetRegistryModule.h"
|
||||
#include "AssetRegistry/AssetData.h"
|
||||
#include "Misc/MessageDialog.h"
|
||||
#include "ProjectFish/Gameplay/Subsystem/QuestManager.h"
|
||||
|
||||
void UQuestAsset::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
|
||||
{
|
||||
@ -84,5 +86,31 @@ void UQuestAsset::PostDuplicate(bool bDuplicateForPIE)
|
||||
QuestID = MaxQuestID + 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
void UQuestAsset::OnDialogueComplete()
|
||||
{
|
||||
bDialogueComplete = true;
|
||||
QuestManager->AcceptQuest(this, true);
|
||||
}
|
||||
|
||||
void UQuestAsset::Initialize(class UQuestManager* InQuestManager)
|
||||
{
|
||||
this->QuestManager = InQuestManager;
|
||||
bDialogueComplete = false;
|
||||
if (IsValid(DialogueTrigger))
|
||||
{
|
||||
DialogueTrigger->OnDialogueComplete.AddDynamic(this, &UQuestAsset::OnDialogueComplete);
|
||||
}
|
||||
}
|
||||
|
||||
bool UQuestAsset::Acceptable()
|
||||
{
|
||||
if (IsValid(DialogueTrigger) &&bDialogueComplete)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return AcceptAble;
|
||||
}
|
||||
@ -23,6 +23,11 @@ public:
|
||||
|
||||
virtual void PostDuplicate(bool bDuplicateForPIE) override;
|
||||
#endif
|
||||
UFUNCTION()
|
||||
void OnDialogueComplete();
|
||||
|
||||
void Initialize(class UQuestManager* InQuestManager);
|
||||
bool Acceptable();
|
||||
/** 任务编号ID */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Quest|Basic")
|
||||
int32 QuestID = 0;
|
||||
@ -31,17 +36,20 @@ public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Quest|Basic")
|
||||
FText QuestName;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Quest|Basic", meta = (ToolTip = "当满足所有条件时,是否可接受"))
|
||||
bool AcceptAble = true;
|
||||
|
||||
/** 任务描述 */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Quest|Basic", meta = (MultiLine = true))
|
||||
FText QuestDescription;
|
||||
|
||||
/** 前置任务ID列表() */
|
||||
/** 前置触发任务ID列表() */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Quest|Trigger", meta = (ToolTip = "完成这些任务后才能触发当前任务, 不填写默认激活接受"))
|
||||
TArray<int32> PrerequisiteQuestIDs;
|
||||
|
||||
/** 任务触发条件(预留,可在蓝图中实现) */
|
||||
/** 对话完成后触发 */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Quest|Trigger")
|
||||
FString TriggerCondition;
|
||||
class UDialogueAsset* DialogueTrigger;
|
||||
|
||||
/** 任务目标列表 */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Quest|Objectives")
|
||||
@ -55,4 +63,8 @@ public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Quest|Completion")
|
||||
FString CompletionCondition;
|
||||
|
||||
private:
|
||||
UPROPERTY()
|
||||
class UQuestManager* QuestManager;
|
||||
bool bDialogueComplete;
|
||||
};
|
||||
|
||||
@ -478,5 +478,35 @@ public:
|
||||
int32 QuestID;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FTutorialUIControlParam
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly)
|
||||
TSubclassOf<class UTutorialControlWidget_Base> TutorialControlUIClass;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly)
|
||||
TArray<FName> HiddenUINames;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FTutorialStep: public FTableRowBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Tutorial Asset")
|
||||
FName StepName;
|
||||
//教程对话
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Tutorial Asset")
|
||||
class UDialogueAsset* Dialogue;
|
||||
//教程任务
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Tutorial Asset")
|
||||
class UQuestAsset* Quest;
|
||||
//需要显示的ui
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Tutorial Asset")
|
||||
TSubclassOf<UUserWidget> TutorialUI;
|
||||
//控制ui的显示
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Tutorial Asset")
|
||||
TArray<FTutorialUIControlParam> UIControlParams;
|
||||
};
|
||||
|
||||
|
||||
10
ProjectFish/Source/ProjectFish/Gameplay/FishGameInstance.cpp
Normal file
10
ProjectFish/Source/ProjectFish/Gameplay/FishGameInstance.cpp
Normal file
@ -0,0 +1,10 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "FishGameInstance.h"
|
||||
|
||||
void UFishGameInstance::OnStart()
|
||||
{
|
||||
Super::OnStart();
|
||||
InitTutorialSteps();
|
||||
}
|
||||
22
ProjectFish/Source/ProjectFish/Gameplay/FishGameInstance.h
Normal file
22
ProjectFish/Source/ProjectFish/Gameplay/FishGameInstance.h
Normal file
@ -0,0 +1,22 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "FishGameInstance.generated.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
UCLASS(Blueprintable)
|
||||
class PROJECTFISH_API UFishGameInstance : public UGameInstance
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
/** GameInstace */
|
||||
virtual void OnStart() override;
|
||||
|
||||
UFUNCTION(BlueprintImplementableEvent)
|
||||
void InitTutorialSteps();
|
||||
};
|
||||
@ -17,6 +17,11 @@ void UFishingRodConfigSubsystem::CreateFishingRodInventory(class UShapeAsset* Fi
|
||||
playerInventoryConfig = DuplicateObject<UBagConfigAsset>(PlayerBagConfig, this);
|
||||
}
|
||||
|
||||
void UFishingRodConfigSubsystem::SetFishingRodConfig(UBagConfigAsset* FishingRodConfig)
|
||||
{
|
||||
fishingRodInventoryConfig = FishingRodConfig;
|
||||
}
|
||||
|
||||
UBagConfigAsset* UFishingRodConfigSubsystem::GetFishingRodInventory()
|
||||
{
|
||||
return this->fishingRodInventoryConfig;
|
||||
|
||||
@ -18,6 +18,8 @@ class PROJECTFISH_API UFishingRodConfigSubsystem : public UGameInstanceSubsystem
|
||||
public:
|
||||
UFUNCTION(BlueprintCallable, Category = "PlayerInventorySubsystem")
|
||||
void CreateFishingRodInventory(class UShapeAsset* FishingRodShape, class UBagConfigAsset* PlayerBagConfig);
|
||||
UFUNCTION(BlueprintCallable, Category = "PlayerInventorySubsystem")
|
||||
void SetFishingRodConfig(UBagConfigAsset* FishingRodConfig);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "PlayerInventorySubsystem")
|
||||
UBagConfigAsset* GetFishingRodInventory();
|
||||
|
||||
@ -103,6 +103,27 @@ void UGameInfoManager::CreateGameInfoAndSave(UShapeAsset* ShipContainerShape, US
|
||||
SaveGameInfo();
|
||||
}
|
||||
|
||||
bool UGameInfoManager::IsTutorialMode()
|
||||
{
|
||||
if (!IsValid(PlayerInfo))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return PlayerInfo->TutorialMode;
|
||||
}
|
||||
}
|
||||
|
||||
void UGameInfoManager::SetTutorialMode(bool bTutorialMode)
|
||||
{
|
||||
if (!IsValid(PlayerInfo))
|
||||
{
|
||||
PlayerInfo = NewObject<UPlayerInfoSaveGame>(this);
|
||||
}
|
||||
PlayerInfo->TutorialMode = bTutorialMode;
|
||||
}
|
||||
|
||||
bool UGameInfoManager::IsFishDefeated(int32 FishID)
|
||||
{
|
||||
if (!IsValid(PlayerInfo))
|
||||
|
||||
@ -30,6 +30,12 @@ public:
|
||||
UFUNCTION(BlueprintCallable)
|
||||
void CreateGameInfoAndSave(UShapeAsset* ShipContainerShape, UShapeAsset* PlayerContainerShape);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category= "GameInfo")
|
||||
bool IsTutorialMode();
|
||||
|
||||
UFUNCTION(Blueprintable, Category= "GameInfo")
|
||||
void SetTutorialMode(bool bTutorialMode);
|
||||
|
||||
UFUNCTION(BlueprintPure)
|
||||
bool IsFishDefeated(int32 FishID);
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
#include "QuestManager.h"
|
||||
|
||||
#include "DialogueAsset.h"
|
||||
#include "GameInfoManager.h"
|
||||
#include "AssetRegistry/AssetRegistryModule.h"
|
||||
#include "Engine/AssetManager.h"
|
||||
@ -38,6 +39,7 @@ void UQuestManager::LoadAllQuests()
|
||||
LoadHandle->GetLoadedAssets(Assets); // 获取所有加载成功的资源
|
||||
for (auto questAsset : Assets)
|
||||
{
|
||||
Cast<UQuestAsset>(questAsset)->Initialize(this);
|
||||
QuestAssets.Add(Cast<UQuestAsset>(questAsset));
|
||||
}
|
||||
}
|
||||
@ -76,14 +78,7 @@ bool UQuestManager::AcceptQuest(UQuestAsset* QuestAsset, bool bSaveGameInfo )
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建运行时数据
|
||||
FQuestRuntimeData RuntimeData = CreateRuntimeData(QuestAsset);
|
||||
RuntimeData.State = EQuestState::InProgress;
|
||||
ActiveQuestsMap.Add(QuestAsset->QuestID, RuntimeData);
|
||||
|
||||
// 触发事件
|
||||
OnQuestStateChanged.Broadcast(QuestAsset->QuestID, EQuestState::InProgress);
|
||||
UE_LOG(LogTemp, Warning, TEXT("Quest %s Accepted"), *QuestAsset->QuestName.ToString());
|
||||
AcceptQuestInternal(QuestAsset);
|
||||
|
||||
if (bSaveGameInfo)
|
||||
{
|
||||
@ -94,6 +89,22 @@ bool UQuestManager::AcceptQuest(UQuestAsset* QuestAsset, bool bSaveGameInfo )
|
||||
return true;
|
||||
}
|
||||
|
||||
void UQuestManager::AcceptQuestInternal(UQuestAsset* QuestAsset)
|
||||
{
|
||||
if (QuestAsset)
|
||||
{
|
||||
// 创建运行时数据
|
||||
FQuestRuntimeData RuntimeData = CreateRuntimeData(QuestAsset);
|
||||
RuntimeData.State = EQuestState::InProgress;
|
||||
ActiveQuestsMap.Add(QuestAsset->QuestID, RuntimeData);
|
||||
|
||||
// 触发事件
|
||||
OnQuestStateChanged.Broadcast(QuestAsset->QuestID, EQuestState::InProgress);
|
||||
UE_LOG(LogTemp, Warning, TEXT("Quest %s Accepted"), *QuestAsset->QuestName.ToString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool UQuestManager::FollowQuest(int32 QuestID, bool Follow)
|
||||
{
|
||||
FQuestRuntimeData* RuntimeData = ActiveQuestsMap.Find(QuestID);
|
||||
@ -331,6 +342,8 @@ bool UQuestManager::CanAcceptQuest(UQuestAsset* QuestAsset) const
|
||||
return false;
|
||||
}
|
||||
|
||||
if (QuestAsset->AcceptAble)
|
||||
{
|
||||
// 检查所有前置任务是否完成
|
||||
for (int32 PrerequisiteID : QuestAsset->PrerequisiteQuestIDs)
|
||||
{
|
||||
@ -340,7 +353,9 @@ bool UQuestManager::CanAcceptQuest(UQuestAsset* QuestAsset) const
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return QuestAsset->Acceptable();
|
||||
|
||||
}
|
||||
|
||||
TArray<FQuestSaveData> UQuestManager::GetQuestSaveData() const
|
||||
@ -415,7 +430,6 @@ void UQuestManager::LoadFromSaveData(const TArray<FQuestSaveData>& SaveData)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
CheckAcceptableQuests(true);
|
||||
|
||||
@ -55,10 +55,13 @@ public:
|
||||
UFUNCTION(BlueprintCallable, Category = "Quest")
|
||||
void CheckAcceptableQuests(bool bSaveGameInfo = false);
|
||||
|
||||
/** 接受任务 */
|
||||
/** 检测并接受任务 */
|
||||
UFUNCTION(BlueprintCallable, Category = "Quest")
|
||||
bool AcceptQuest(UQuestAsset* QuestAsset, bool bSaveGameInfo = false);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Quest")
|
||||
void AcceptQuestInternal(UQuestAsset* QuestAsset);
|
||||
|
||||
/** 接受任务 */
|
||||
UFUNCTION(BlueprintCallable, Category = "Quest")
|
||||
bool FollowQuest(int32 QuestID, bool Follow);
|
||||
|
||||
@ -0,0 +1,149 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "TutorialManagerSubsystem.h"
|
||||
|
||||
#include "GameInfoManager.h"
|
||||
#include "QuestManager.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "Blueprint/WidgetBlueprintLibrary.h"
|
||||
#include "Engine/AssetManager.h"
|
||||
#include "Engine/StreamableManager.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "ProjectFish/DataAsset/QuestAsset.h"
|
||||
#include "ProjectFish/Widget/DialogueWidget_Base.h"
|
||||
#include "ProjectFish/Widget/TutorialControlWidget_Base.h"
|
||||
|
||||
void UTutorialManagerSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||
{
|
||||
Super::Initialize(Collection);
|
||||
Collection.InitializeDependency<UGameInfoManager>();
|
||||
|
||||
DialogueUIPath = (TEXT("/Game/UI/Dialogue/UMG_DialogueWindow.UMG_DialogueWindow_C"));
|
||||
}
|
||||
|
||||
void UTutorialManagerSubsystem::InitSteps(TArray<FTutorialStep> Steps)
|
||||
{
|
||||
TutorialSteps = Steps;
|
||||
}
|
||||
|
||||
void UTutorialManagerSubsystem::ApplyCurrentTutorialStep()
|
||||
{
|
||||
if (TutorialSteps.Num() > CurrentTutorialStep )
|
||||
{
|
||||
bTutorialing = true;
|
||||
FTutorialStep CurrentStep = TutorialSteps[CurrentTutorialStep];
|
||||
if (IsValid(CurrentStep.Dialogue))
|
||||
{
|
||||
ShowDialogue();
|
||||
}
|
||||
if (IsValid(CurrentStep.Quest))
|
||||
{
|
||||
AccepAndFollowQuest();
|
||||
}
|
||||
if (IsValid(CurrentStep.TutorialUI))
|
||||
{
|
||||
ShowTutorialUI();
|
||||
}
|
||||
if (CurrentStep.UIControlParams.Num() != 0)
|
||||
{
|
||||
ControlUI();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("所有教程已结束"));
|
||||
GetGameInstance()->GetSubsystem<UGameInfoManager>()->SetTutorialMode(false);
|
||||
}
|
||||
}
|
||||
|
||||
void UTutorialManagerSubsystem::CompleteTutorialStep()
|
||||
{
|
||||
if (bTutorialing)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("教程进行下一步 currentstep = %d"), CurrentTutorialStep);
|
||||
OnStepComplete.Broadcast(CurrentTutorialStep);
|
||||
CurrentTutorialStep++;
|
||||
ApplyCurrentTutorialStep();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
FTutorialStep UTutorialManagerSubsystem::GetCurrentTutorialStep() const
|
||||
{
|
||||
return TutorialSteps[CurrentTutorialStep];
|
||||
}
|
||||
|
||||
void UTutorialManagerSubsystem::SetTutorialCompleteOnLevelLoaded()
|
||||
{
|
||||
LevelLoadedDelegateHandle = FCoreUObjectDelegates::PostLoadMapWithWorld.AddUObject(
|
||||
this,
|
||||
&UTutorialManagerSubsystem::OnLevelLoaded
|
||||
);
|
||||
}
|
||||
|
||||
void UTutorialManagerSubsystem::OnLevelLoaded(UWorld* World)
|
||||
{
|
||||
FCoreUObjectDelegates::PostLoadMapWithWorld.Remove(LevelLoadedDelegateHandle);
|
||||
CompleteTutorialStep();
|
||||
}
|
||||
|
||||
void UTutorialManagerSubsystem::OnDialogueCompleted()
|
||||
{
|
||||
DialogueWidget->RemoveFromParent();
|
||||
CompleteTutorialStep();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void UTutorialManagerSubsystem::ShowDialogue()
|
||||
{
|
||||
FTutorialStep CurrentStep = TutorialSteps[CurrentTutorialStep];
|
||||
UClass* WidgetClass = LoadClass<UUserWidget>(this, *DialogueUIPath);
|
||||
if (WidgetClass)
|
||||
{
|
||||
DialogueWidget = CreateWidget<UDialogueWidget_Base>(GetWorld(), WidgetClass);
|
||||
if (DialogueWidget)
|
||||
{
|
||||
DialogueWidget->DialogueAsset = CurrentStep.Dialogue;
|
||||
DialogueWidget->DialogueAsset->OnDialogueComplete.AddDynamic(this, &UTutorialManagerSubsystem::OnDialogueCompleted);
|
||||
DialogueWidget->AddToViewport(99);
|
||||
UGameplayStatics::GetPlayerController(GetWorld(), 0)->SetShowMouseCursor(true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("dialogue path is error"));
|
||||
}
|
||||
}
|
||||
|
||||
void UTutorialManagerSubsystem::AccepAndFollowQuest()
|
||||
{
|
||||
FTutorialStep CurrentStep = TutorialSteps[CurrentTutorialStep];
|
||||
GetGameInstance()->GetSubsystem<UQuestManager>()->AcceptQuestInternal(CurrentStep.Quest);
|
||||
GetGameInstance()->GetSubsystem<UQuestManager>()->FollowQuest(CurrentStep.Quest->QuestID, true);
|
||||
CompleteTutorialStep();
|
||||
}
|
||||
|
||||
void UTutorialManagerSubsystem::ShowTutorialUI()
|
||||
{
|
||||
FTutorialStep CurrentStep = TutorialSteps[CurrentTutorialStep];
|
||||
UUserWidget* TutorialWidget = CreateWidget<UUserWidget>(GetWorld(), CurrentStep.TutorialUI);
|
||||
TutorialWidget->AddToViewport(99);
|
||||
}
|
||||
|
||||
void UTutorialManagerSubsystem::ControlUI()
|
||||
{
|
||||
FTutorialStep CurrentStep = TutorialSteps[CurrentTutorialStep];
|
||||
|
||||
for (auto ControlParam: CurrentStep.UIControlParams)
|
||||
{
|
||||
TArray<UUserWidget*> FoundWidgets;
|
||||
UWidgetBlueprintLibrary::GetAllWidgetsOfClass(this, FoundWidgets, ControlParam.TutorialControlUIClass, false);
|
||||
for (auto Widget: FoundWidgets)
|
||||
{
|
||||
Cast<UTutorialControlWidget_Base>(Widget)->TutorialHidenUI(ControlParam.HiddenUINames);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Subsystems/GameInstanceSubsystem.h"
|
||||
#include "TutorialManagerSubsystem.generated.h"
|
||||
|
||||
|
||||
struct FTutorialStep;
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnStepComplete, int32, CompleteIndex);
|
||||
/**
|
||||
*
|
||||
*/
|
||||
UCLASS()
|
||||
class PROJECTFISH_API UTutorialManagerSubsystem : public UGameInstanceSubsystem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
/** GameInstanceSubsystem */
|
||||
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category= TutorialSubsystem)
|
||||
void InitSteps(TArray<FTutorialStep> Steps);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category= TutorialSubsystem)
|
||||
void ApplyCurrentTutorialStep();
|
||||
UFUNCTION(BlueprintCallable, Category= TutorialSubsystem)
|
||||
void CompleteTutorialStep();
|
||||
UFUNCTION(BlueprintPure, Category= TutorialSubsystem)
|
||||
FTutorialStep GetCurrentTutorialStep() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category= TutorialSubsystem)
|
||||
void SetTutorialCompleteOnLevelLoaded();
|
||||
void OnLevelLoaded(UWorld* World);
|
||||
protected:
|
||||
|
||||
//显示对话框UI
|
||||
void ShowDialogue();
|
||||
UFUNCTION()
|
||||
void OnDialogueCompleted();
|
||||
//接受任务
|
||||
void AccepAndFollowQuest();
|
||||
//显示教程UI
|
||||
void ShowTutorialUI();
|
||||
//控制指定UI
|
||||
void ControlUI();
|
||||
private:
|
||||
FDelegateHandle LevelLoadedDelegateHandle;
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOnStepComplete OnStepComplete;
|
||||
bool bTutorialing;
|
||||
|
||||
TArray<FTutorialStep> TutorialSteps;
|
||||
int32 CurrentTutorialStep = 0;
|
||||
FString DialogueUIPath;
|
||||
UPROPERTY()
|
||||
class UDialogueWidget_Base* DialogueWidget;
|
||||
};
|
||||
@ -6,6 +6,7 @@ public class ProjectFish : ModuleRules
|
||||
{
|
||||
public ProjectFish(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PrivateDependencyModuleNames.AddRange(new string[] { "Dialogue" });
|
||||
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
OptimizeCode = CodeOptimization.Never;
|
||||
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine",
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "DialogueWidget_Base.h"
|
||||
20
ProjectFish/Source/ProjectFish/Widget/DialogueWidget_Base.h
Normal file
20
ProjectFish/Source/ProjectFish/Widget/DialogueWidget_Base.h
Normal file
@ -0,0 +1,20 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "DialogueAsset.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "DialogueWidget_Base.generated.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
UCLASS(Blueprintable)
|
||||
class PROJECTFISH_API UDialogueWidget_Base : public UUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Dialogue Widget")
|
||||
UDialogueAsset* DialogueAsset;
|
||||
};
|
||||
@ -0,0 +1,5 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "TutorialControlWidget_Base.h"
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "TutorialControlWidget_Base.generated.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
UCLASS(Blueprintable)
|
||||
class PROJECTFISH_API UTutorialControlWidget_Base : public UUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
|
||||
UFUNCTION(BlueprintImplementableEvent)
|
||||
void TutorialHidenUI(const TArray<FName>& HiddenNames);
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user