Compare commits

..

8 Commits

Author SHA1 Message Date
f33feaf558 Merge remote-tracking branch 'remotes/origin/dev'
# Conflicts:
#	ProjectFish/Plugins/Dialogue/Binaries/Win64/UnrealEditor-Dialogue.dll
#	ProjectFish/Plugins/Dialogue/Binaries/Win64/UnrealEditor-DialogueEditor.dll
2025-11-26 17:24:05 +08:00
0526f25058 更新新手引导系统 2025-11-26 16:58:17 +08:00
a83eeacbc5 修复bug 2025-11-25 14:21:55 +08:00
1a5c391707 修复新手引导bug 2025-11-25 13:34:51 +08:00
7929dfa4f2 更新显示鱼市按钮 2025-11-24 18:21:18 +08:00
d5b1e8b304 更新新手引导逻辑 2025-11-24 17:36:15 +08:00
9711095c25 更新引导系统 2025-11-24 17:31:52 +08:00
d5c3c82fec 添加新手引导系统 2025-11-24 10:58:30 +08:00
96 changed files with 2411 additions and 38 deletions

View File

@ -0,0 +1,5 @@
[/Script/Dialogue.DialogueSettings]
DialogueWidth=125.000000

View File

@ -1,7 +1,8 @@
[/Script/EngineSettings.GameMapsSettings] [/Script/EngineSettings.GameMapsSettings]
GameDefaultMap=/Game/TopDown/Maps/TopDownMap.TopDownMap GameDefaultMap=/Game/TopDown/Maps/TopDownMap.TopDownMap
EditorStartupMap=/Game/ART/Map/fishing.fishing EditorStartupMap=/Game/Maps/Home.Home
GlobalDefaultGameMode="/Script/ProjectFish.ProjectFishGameMode" GlobalDefaultGameMode="/Script/ProjectFish.ProjectFishGameMode"
GameInstanceClass=/Game/Gameplay/BP_FishGameInstance.BP_FishGameInstance_C
[/Script/Engine.RendererSettings] [/Script/Engine.RendererSettings]
r.DefaultFeature.AutoExposure.ExtendDefaultLuminanceRange=True r.DefaultFeature.AutoExposure.ExtendDefaultLuminanceRange=True

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View 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
```
### 方式2C++中统一处理
创建一个简单的基类:
```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);
// 显示提示:"点击关闭继续"
}
}
```
---
## 六、遮罩和高亮效果(简单实现)
### 遮罩WidgetWBP_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
```
就这么简单!🎉

View File

@ -67,7 +67,7 @@ FText UDialogueAsset::GetNextNodeText()
} }
else else
{ {
UE_LOG(LogTemp, Warning, TEXT("DialogueNode has no output pin")); OnDialogueComplete.Broadcast();
} }
return FText::FromString(TEXT("")); return FText::FromString(TEXT(""));
} }

View File

@ -93,6 +93,7 @@ public:
}; };
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FNeedPlayeSelectDelegate, const TArray<FText>&, Options); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FNeedPlayeSelectDelegate, const TArray<FText>&, Options);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDialogueComplete);
/** /**
* *
*/ */
@ -115,6 +116,8 @@ public:
public: public:
UPROPERTY(BlueprintAssignable, Category=Dialogue) UPROPERTY(BlueprintAssignable, Category=Dialogue)
FNeedPlayeSelectDelegate OnNeedPlayerSelect; FNeedPlayeSelectDelegate OnNeedPlayerSelect;
UPROPERTY(BlueprintAssignable, Category=Dialogue)
FOnDialogueComplete OnDialogueComplete;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Dialogue) UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Dialogue)
TArray<UDialogueRuntimeNode*> NodeDatas; TArray<UDialogueRuntimeNode*> NodeDatas;

View File

@ -84,8 +84,8 @@ TSet<int32> UContainerInfo::GetOverlapOtherItem(FContainerItem Item)
if (Item.IsSlotUsing(FIntPoint(x, y))) if (Item.IsSlotUsing(FIntPoint(x, y)))
{ {
if (ContainerShape->IsSlotActive(Item.ContainerStartPos.X + x, Item.ContainerStartPos.Y + y) && if (ContainerShape->IsSlotActive(Item.ContainerStartPos.X + x, Item.ContainerStartPos.Y + y) && SlotItems.Contains(FIntPoint(x + Item.ContainerStartPos.X, y + Item.ContainerStartPos.Y))
SlotItems[FIntPoint(x + Item.ContainerStartPos.X, y + Item.ContainerStartPos.Y)] != -1 ///SlotItems[FIntPoint(x + Item.ContainerStartPos.X, y + Item.ContainerStartPos.Y)] != -1
) )
{ {
OverlapItems.Add(SlotItems[FIntPoint(x + Item.ContainerStartPos.X, y + Item.ContainerStartPos.Y)] ); OverlapItems.Add(SlotItems[FIntPoint(x + Item.ContainerStartPos.X, y + Item.ContainerStartPos.Y)] );

View File

@ -2,3 +2,21 @@
#include "PlayerInfoSaveGame.h" #include "PlayerInfoSaveGame.h"
#include "ProjectFish/Gameplay/Subsystem/GameInfoManager.h"
void UPlayerInfoSaveGame::BeginDestroy()
{
Super::BeginDestroy();
}
void UPlayerInfoSaveGame::SetShipContainerItems(TArray<FContainerItemSaveData> NewItems)
{
ShipContainerItems = NewItems;
if (GetOuter())
{
GetOuter()->GetWorld()->GetGameInstance()->GetSubsystem<UGameInfoManager>()->SaveGameInfo();
OnShipContainerUpdate.Broadcast();
}
}

View File

@ -9,6 +9,7 @@
#include "ProjectFish/Quest/QuestTypes.h" #include "ProjectFish/Quest/QuestTypes.h"
#include "PlayerInfoSaveGame.generated.h" #include "PlayerInfoSaveGame.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnShipContainerUpdate);
/** /**
* *
*/ */
@ -17,6 +18,11 @@ class PROJECTFISH_API UPlayerInfoSaveGame : public USaveGame
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
virtual void BeginDestroy() override;
UPROPERTY(SaveGame, BlueprintReadWrite, meta = (ToolTip = "开启教程模式"))
bool TutorialMode = true;
UPROPERTY(SaveGame, BlueprintReadWrite, meta = (ToolTip = "船只资源")) UPROPERTY(SaveGame, BlueprintReadWrite, meta = (ToolTip = "船只资源"))
FPrimaryAssetId ShipAssetID; FPrimaryAssetId ShipAssetID;
@ -28,7 +34,7 @@ public:
FPrimaryAssetId ShipContainerShapeID; FPrimaryAssetId ShipContainerShapeID;
// 船舱物品存档数据 // 船舱物品存档数据
UPROPERTY(SaveGame, BlueprintReadWrite, meta = (ToolTip = "船舱物品数据")) UPROPERTY(SaveGame, BlueprintReadOnly, meta = (ToolTip = "船舱物品数据"))
TArray<FContainerItemSaveData> ShipContainerItems; TArray<FContainerItemSaveData> ShipContainerItems;
UPROPERTY(SaveGame, BlueprintReadWrite, meta = (ToolTip = "仓库资源")) UPROPERTY(SaveGame, BlueprintReadWrite, meta = (ToolTip = "仓库资源"))
@ -52,4 +58,12 @@ public:
//已战胜的鱼 //已战胜的鱼
UPROPERTY(SaveGame, BlueprintReadWrite, meta = (ToolTip = "被击败的鱼")) UPROPERTY(SaveGame, BlueprintReadWrite, meta = (ToolTip = "被击败的鱼"))
TArray<int32> Fish_defeated_IDS; TArray<int32> Fish_defeated_IDS;
public:
UPROPERTY(BlueprintAssignable, Category = "PlayerInfoSaveGame")
FOnShipContainerUpdate OnShipContainerUpdate;
protected:
UFUNCTION(BlueprintCallable, Category= "PlayerInfoSaveGame")
void SetShipContainerItems(TArray<FContainerItemSaveData> NewItems);
}; };

View File

@ -3,9 +3,11 @@
#include "QuestAsset.h" #include "QuestAsset.h"
#if WITH_EDITOR #if WITH_EDITOR
#include "DialogueAsset.h"
#include "AssetRegistry/AssetRegistryModule.h" #include "AssetRegistry/AssetRegistryModule.h"
#include "AssetRegistry/AssetData.h" #include "AssetRegistry/AssetData.h"
#include "Misc/MessageDialog.h" #include "Misc/MessageDialog.h"
#include "ProjectFish/Gameplay/Subsystem/QuestManager.h"
void UQuestAsset::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) void UQuestAsset::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{ {
@ -84,5 +86,31 @@ void UQuestAsset::PostDuplicate(bool bDuplicateForPIE)
QuestID = MaxQuestID + 1; QuestID = MaxQuestID + 1;
} }
} }
#endif #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;
}

View File

@ -23,6 +23,11 @@ public:
virtual void PostDuplicate(bool bDuplicateForPIE) override; virtual void PostDuplicate(bool bDuplicateForPIE) override;
#endif #endif
UFUNCTION()
void OnDialogueComplete();
void Initialize(class UQuestManager* InQuestManager);
bool Acceptable();
/** 任务编号ID */ /** 任务编号ID */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Quest|Basic") UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Quest|Basic")
int32 QuestID = 0; int32 QuestID = 0;
@ -31,17 +36,20 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Quest|Basic") UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Quest|Basic")
FText QuestName; FText QuestName;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Quest|Basic", meta = (ToolTip = "当满足所有条件时,是否可接受"))
bool AcceptAble = true;
/** 任务描述 */ /** 任务描述 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Quest|Basic", meta = (MultiLine = true)) UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Quest|Basic", meta = (MultiLine = true))
FText QuestDescription; FText QuestDescription;
/** 前置任务ID列表 */ /** 前置触发任务ID列表 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Quest|Trigger", meta = (ToolTip = "完成这些任务后才能触发当前任务, 不填写默认激活接受")) UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Quest|Trigger", meta = (ToolTip = "完成这些任务后才能触发当前任务, 不填写默认激活接受"))
TArray<int32> PrerequisiteQuestIDs; TArray<int32> PrerequisiteQuestIDs;
/** 任务触发条件(预留,可在蓝图中实现) */ /** 对话完成后触发 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Quest|Trigger") UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Quest|Trigger")
FString TriggerCondition; class UDialogueAsset* DialogueTrigger;
/** 任务目标列表 */ /** 任务目标列表 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Quest|Objectives") UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Quest|Objectives")
@ -55,4 +63,8 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Quest|Completion") UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Quest|Completion")
FString CompletionCondition; FString CompletionCondition;
private:
UPROPERTY()
class UQuestManager* QuestManager;
bool bDialogueComplete;
}; };

View File

@ -478,5 +478,44 @@ public:
int32 QuestID; int32 QuestID;
}; };
USTRUCT(BlueprintType)
struct FTutorialUIControlParam
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TSubclassOf<class UTutorialControlWidget_Base> TutorialControlUIClass;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TArray<FName> HiddenUINames;
// UPROPERTY(EditAnywhere, BlueprintReadOnly)
// TArray<FName> ShowUINames;
};
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;
//引导task
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Tutorial Asset")
TSubclassOf<class UTutorialTask_Base> TutorialTaskClass;
UPROPERTY()
UTutorialTask_Base* TutorialTaskObject;
};

View 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();
}

View 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();
};

View File

@ -17,6 +17,11 @@ void UFishingRodConfigSubsystem::CreateFishingRodInventory(class UShapeAsset* Fi
playerInventoryConfig = DuplicateObject<UBagConfigAsset>(PlayerBagConfig, this); playerInventoryConfig = DuplicateObject<UBagConfigAsset>(PlayerBagConfig, this);
} }
void UFishingRodConfigSubsystem::SetFishingRodConfig(UBagConfigAsset* FishingRodConfig)
{
fishingRodInventoryConfig = FishingRodConfig;
}
UBagConfigAsset* UFishingRodConfigSubsystem::GetFishingRodInventory() UBagConfigAsset* UFishingRodConfigSubsystem::GetFishingRodInventory()
{ {
return this->fishingRodInventoryConfig; return this->fishingRodInventoryConfig;

View File

@ -18,6 +18,8 @@ class PROJECTFISH_API UFishingRodConfigSubsystem : public UGameInstanceSubsystem
public: public:
UFUNCTION(BlueprintCallable, Category = "PlayerInventorySubsystem") UFUNCTION(BlueprintCallable, Category = "PlayerInventorySubsystem")
void CreateFishingRodInventory(class UShapeAsset* FishingRodShape, class UBagConfigAsset* PlayerBagConfig); void CreateFishingRodInventory(class UShapeAsset* FishingRodShape, class UBagConfigAsset* PlayerBagConfig);
UFUNCTION(BlueprintCallable, Category = "PlayerInventorySubsystem")
void SetFishingRodConfig(UBagConfigAsset* FishingRodConfig);
UFUNCTION(BlueprintPure, Category = "PlayerInventorySubsystem") UFUNCTION(BlueprintPure, Category = "PlayerInventorySubsystem")
UBagConfigAsset* GetFishingRodInventory(); UBagConfigAsset* GetFishingRodInventory();

View File

@ -24,9 +24,10 @@ void UGameInfoManager::NewSaveGame()
void UGameInfoManager::SaveGameInfo() void UGameInfoManager::SaveGameInfo()
{ {
if (!IsValid(PlayerInfo.Get())) if (!IsValid(PlayerInfo))
{ {
PlayerInfo = NewObject<UPlayerInfoSaveGame>(this);
PlayerInfo = NewObject<UPlayerInfoSaveGame>(GetGameInstance());
} }
// 保存任务数据 // 保存任务数据
if (UQuestManager* QuestManager = GetGameInstance()->GetSubsystem<UQuestManager>()) if (UQuestManager* QuestManager = GetGameInstance()->GetSubsystem<UQuestManager>())
@ -34,7 +35,7 @@ void UGameInfoManager::SaveGameInfo()
PlayerInfo->QuestSaveData = QuestManager->GetQuestSaveData(); PlayerInfo->QuestSaveData = QuestManager->GetQuestSaveData();
} }
if (UGameplayStatics::SaveGameToSlot(PlayerInfo.Get(), SaveGameSlotName, 0)) if (UGameplayStatics::SaveGameToSlot(PlayerInfo, SaveGameSlotName, 0))
{ {
UE_LOG(LogTemp, Warning, TEXT("存档保存成功")); UE_LOG(LogTemp, Warning, TEXT("存档保存成功"));
} }
@ -49,7 +50,7 @@ bool UGameInfoManager::LoadGameInfo()
if (UGameplayStatics::DoesSaveGameExist(SaveGameSlotName, 0)) if (UGameplayStatics::DoesSaveGameExist(SaveGameSlotName, 0))
{ {
PlayerInfo = Cast<UPlayerInfoSaveGame>(UGameplayStatics::LoadGameFromSlot(SaveGameSlotName, 0)); PlayerInfo = Cast<UPlayerInfoSaveGame>(UGameplayStatics::LoadGameFromSlot(SaveGameSlotName, 0));
PlayerInfo->Rename(nullptr, this);
// 加载任务数据 // 加载任务数据
if (UQuestManager* QuestManager = GetGameInstance()->GetSubsystem<UQuestManager>()) if (UQuestManager* QuestManager = GetGameInstance()->GetSubsystem<UQuestManager>())
{ {
@ -74,7 +75,8 @@ bool UGameInfoManager::LoadGameInfo()
void UGameInfoManager::CreateGameInfo(UContainerInfo* ShipContainer, UContainerInfo* PlayerContainer) void UGameInfoManager::CreateGameInfo(UContainerInfo* ShipContainer, UContainerInfo* PlayerContainer)
{ {
PlayerInfo = NewObject<UPlayerInfoSaveGame>(this); if (!IsValid(PlayerInfo))
PlayerInfo = NewObject<UPlayerInfoSaveGame>(GetGameInstance());
PlayerInfo->ShipContainerItems = ShipContainer->GetSaveData(); PlayerInfo->ShipContainerItems = ShipContainer->GetSaveData();
PlayerInfo->ShipContainerShapeID = ShipContainer->ContainerShape->GetPrimaryAssetId(); PlayerInfo->ShipContainerShapeID = ShipContainer->ContainerShape->GetPrimaryAssetId();
@ -93,7 +95,7 @@ void UGameInfoManager::CreateGameInfoAndSave(UShapeAsset* ShipContainerShape, US
PlayerContainer->InitContainerByShape(PlayerContainerShape); PlayerContainer->InitContainerByShape(PlayerContainerShape);
//创建要保存的额存档信息 //创建要保存的额存档信息
PlayerInfo = NewObject<UPlayerInfoSaveGame>(this); PlayerInfo = NewObject<UPlayerInfoSaveGame>(GetGameInstance());
PlayerInfo->ShipContainerItems = ShipContainer->GetSaveData(); PlayerInfo->ShipContainerItems = ShipContainer->GetSaveData();
PlayerInfo->ShipContainerShapeID = ShipContainer->ContainerShape->GetPrimaryAssetId(); PlayerInfo->ShipContainerShapeID = ShipContainer->ContainerShape->GetPrimaryAssetId();
@ -103,6 +105,27 @@ void UGameInfoManager::CreateGameInfoAndSave(UShapeAsset* ShipContainerShape, US
SaveGameInfo(); SaveGameInfo();
} }
bool UGameInfoManager::IsTutorialMode()
{
if (!IsValid(PlayerInfo))
{
return true;
}
else
{
return PlayerInfo->TutorialMode;
}
}
void UGameInfoManager::SetTutorialMode(bool bTutorialMode)
{
if (!IsValid(PlayerInfo))
{
PlayerInfo = NewObject<UPlayerInfoSaveGame>(GetGameInstance());
}
PlayerInfo->TutorialMode = bTutorialMode;
}
bool UGameInfoManager::IsFishDefeated(int32 FishID) bool UGameInfoManager::IsFishDefeated(int32 FishID)
{ {
if (!IsValid(PlayerInfo)) if (!IsValid(PlayerInfo))
@ -116,7 +139,7 @@ void UGameInfoManager::AddDefeatedFish(int32 FishID)
{ {
if (!IsValid(PlayerInfo)) if (!IsValid(PlayerInfo))
{ {
PlayerInfo = NewObject<UPlayerInfoSaveGame>(this); PlayerInfo = NewObject<UPlayerInfoSaveGame>(GetGameInstance());
} }
PlayerInfo->Fish_defeated_IDS.Add(FishID); PlayerInfo->Fish_defeated_IDS.Add(FishID);
SaveGameInfo(); SaveGameInfo();

View File

@ -3,6 +3,7 @@
#pragma once #pragma once
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "DialogueAsset.h"
#include "ProjectFish/Data/PlayerInfoSaveGame.h" #include "ProjectFish/Data/PlayerInfoSaveGame.h"
#include "Subsystems/GameInstanceSubsystem.h" #include "Subsystems/GameInstanceSubsystem.h"
#include "GameInfoManager.generated.h" #include "GameInfoManager.generated.h"
@ -30,14 +31,22 @@ public:
UFUNCTION(BlueprintCallable) UFUNCTION(BlueprintCallable)
void CreateGameInfoAndSave(UShapeAsset* ShipContainerShape, UShapeAsset* PlayerContainerShape); void CreateGameInfoAndSave(UShapeAsset* ShipContainerShape, UShapeAsset* PlayerContainerShape);
UFUNCTION(BlueprintPure, Category= "GameInfo")
bool IsTutorialMode();
UFUNCTION(Blueprintable, Category= "GameInfo")
void SetTutorialMode(bool bTutorialMode);
UFUNCTION(BlueprintPure) UFUNCTION(BlueprintPure)
bool IsFishDefeated(int32 FishID); bool IsFishDefeated(int32 FishID);
UFUNCTION(BlueprintCallable) UFUNCTION(BlueprintCallable)
void AddDefeatedFish(int32 FishID); void AddDefeatedFish(int32 FishID);
TObjectPtr<class UPlayerInfoSaveGame> GetPlayerInfo() {return PlayerInfo;}
protected: protected:
UPROPERTY(BlueprintReadOnly) UPROPERTY(BlueprintReadWrite)
TObjectPtr<class UPlayerInfoSaveGame> PlayerInfo; class UPlayerInfoSaveGame* PlayerInfo;
static FString SaveGameSlotName; static FString SaveGameSlotName;
}; };

View File

@ -2,6 +2,7 @@
#include "QuestManager.h" #include "QuestManager.h"
#include "DialogueAsset.h"
#include "GameInfoManager.h" #include "GameInfoManager.h"
#include "AssetRegistry/AssetRegistryModule.h" #include "AssetRegistry/AssetRegistryModule.h"
#include "Engine/AssetManager.h" #include "Engine/AssetManager.h"
@ -38,6 +39,7 @@ void UQuestManager::LoadAllQuests()
LoadHandle->GetLoadedAssets(Assets); // 获取所有加载成功的资源 LoadHandle->GetLoadedAssets(Assets); // 获取所有加载成功的资源
for (auto questAsset : Assets) for (auto questAsset : Assets)
{ {
Cast<UQuestAsset>(questAsset)->Initialize(this);
QuestAssets.Add(Cast<UQuestAsset>(questAsset)); QuestAssets.Add(Cast<UQuestAsset>(questAsset));
} }
} }
@ -76,14 +78,7 @@ bool UQuestManager::AcceptQuest(UQuestAsset* QuestAsset, bool bSaveGameInfo )
return false; return false;
} }
// 创建运行时数据 AcceptQuestInternal(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());
if (bSaveGameInfo) if (bSaveGameInfo)
{ {
@ -94,6 +89,22 @@ bool UQuestManager::AcceptQuest(UQuestAsset* QuestAsset, bool bSaveGameInfo )
return true; 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) bool UQuestManager::FollowQuest(int32 QuestID, bool Follow)
{ {
FQuestRuntimeData* RuntimeData = ActiveQuestsMap.Find(QuestID); FQuestRuntimeData* RuntimeData = ActiveQuestsMap.Find(QuestID);
@ -331,6 +342,8 @@ bool UQuestManager::CanAcceptQuest(UQuestAsset* QuestAsset) const
return false; return false;
} }
if (QuestAsset->AcceptAble)
{
// 检查所有前置任务是否完成 // 检查所有前置任务是否完成
for (int32 PrerequisiteID : QuestAsset->PrerequisiteQuestIDs) 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 TArray<FQuestSaveData> UQuestManager::GetQuestSaveData() const
@ -415,7 +430,6 @@ void UQuestManager::LoadFromSaveData(const TArray<FQuestSaveData>& SaveData)
} }
} }
} }
CheckAcceptableQuests(true); CheckAcceptableQuests(true);

View File

@ -55,10 +55,13 @@ public:
UFUNCTION(BlueprintCallable, Category = "Quest") UFUNCTION(BlueprintCallable, Category = "Quest")
void CheckAcceptableQuests(bool bSaveGameInfo = false); void CheckAcceptableQuests(bool bSaveGameInfo = false);
/** 接受任务 */ /** 检测并接受任务 */
UFUNCTION(BlueprintCallable, Category = "Quest") UFUNCTION(BlueprintCallable, Category = "Quest")
bool AcceptQuest(UQuestAsset* QuestAsset, bool bSaveGameInfo = false); bool AcceptQuest(UQuestAsset* QuestAsset, bool bSaveGameInfo = false);
UFUNCTION(BlueprintCallable, Category = "Quest")
void AcceptQuestInternal(UQuestAsset* QuestAsset);
/** 接受任务 */ /** 接受任务 */
UFUNCTION(BlueprintCallable, Category = "Quest") UFUNCTION(BlueprintCallable, Category = "Quest")
bool FollowQuest(int32 QuestID, bool Follow); bool FollowQuest(int32 QuestID, bool Follow);

View File

@ -0,0 +1,174 @@
// 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/Gameplay/TutorialSystem/TutorialTask_Base.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 )
{
UE_LOG(LogTemp, Warning, TEXT("执行 CurrentTutorialStep = %d || name = %s "), CurrentTutorialStep, *GetCurrentTutorialStep().StepName.ToString() );
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();
}
if (IsValid(CurrentStep.TutorialTaskClass))
{
CreateTask();
}
}
else
{
UE_LOG(LogTemp, Warning, TEXT("所有教程已结束"));
GetGameInstance()->GetSubsystem<UGameInfoManager>()->SetTutorialMode(false);
}
}
void UTutorialManagerSubsystem::CompleteTutorialStep()
{
if (bTutorialing)
{
if (TutorialTaskObject)
{
TutorialTaskObject->BeforTutorialComplete();
TutorialTaskObject->ConditionalBeginDestroy();
TutorialTaskObject = nullptr;
}
if (TutorialWidget)
{
TutorialWidget->RemoveFromViewport();
TutorialWidget = nullptr;
}
UE_LOG(LogTemp, Warning, TEXT(" 教程进行下一步"));
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];
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)->TutorialHideUI(ControlParam.HiddenUINames);
//Cast<UTutorialControlWidget_Base>(Widget)->TutorialShowUI(ControlParam.ShowUINames);
}
}
}
void UTutorialManagerSubsystem::CreateTask()
{
FTutorialStep CurrentStep = TutorialSteps[CurrentTutorialStep];
TutorialTaskObject = NewObject<UTutorialTask_Base>(this, CurrentStep.TutorialTaskClass);
TutorialTaskObject->Execute(GetWorld()->GetAuthGameMode());
}

View File

@ -0,0 +1,69 @@
// 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(BlueprintType)
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();
//创建task
void CreateTask();
protected:
FDelegateHandle LevelLoadedDelegateHandle;
UPROPERTY(BlueprintAssignable)
FOnStepComplete OnStepComplete;
UPROPERTY(BlueprintReadOnly)
bool bTutorialing;
TArray<FTutorialStep> TutorialSteps;
int32 CurrentTutorialStep = 0;
FString DialogueUIPath;
UPROPERTY()
class UDialogueWidget_Base* DialogueWidget;
UPROPERTY()
UUserWidget* TutorialWidget;
UPROPERTY()
class UTutorialTask_Base* TutorialTaskObject;
};

View File

@ -0,0 +1,21 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "TutorialTask_Base.h"
#include "ProjectFish/Gameplay/Subsystem/TutorialManagerSubsystem.h"
// void UTutorialTask_Base::BeginDestroy()
// {
// UObject::BeginDestroy();
// //task 删除的时候自动完成该引导step
// if (GetWorld())
// GetWorld()->GetGameInstance()->GetSubsystem<UTutorialManagerSubsystem>()->CompleteTutorialStep();
// }
void UTutorialTask_Base::Execute_Implementation(class AGameModeBase* GameMode)
{
}
void UTutorialTask_Base::BeforTutorialComplete_Implementation()
{
}

View File

@ -0,0 +1,28 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "UObject/Object.h"
#include "TutorialTask_Base.generated.h"
/**
*
*/
UCLASS(Blueprintable)
class PROJECTFISH_API UTutorialTask_Base : public UObject
{
GENERATED_BODY()
public:
/** UObject */
//virtual void BeginDestroy() override;
UFUNCTION(BlueprintNativeEvent, Category= "TutorialTask")
void Execute(class AGameModeBase* GameMode);
virtual void Execute_Implementation(class AGameModeBase* GameMode);
UFUNCTION(BlueprintNativeEvent, Category= "TutorialTask")
void BeforTutorialComplete();
virtual void BeforTutorialComplete_Implementation();
};

View File

@ -0,0 +1,34 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "TutorialTask_CheckShipContainer.h"
#include "GameFramework/GameModeBase.h"
#include "ProjectFish/Gameplay/Subsystem/GameInfoManager.h"
#include "ProjectFish/Gameplay/Subsystem/TutorialManagerSubsystem.h"
void UTutorialTask_CheckShipContainer::Execute_Implementation(class AGameModeBase* GameMode)
{
Super::Execute_Implementation(GameMode);
if (GameMode)
{
GameMode->GetGameInstance()->GetSubsystem<UGameInfoManager>()->GetPlayerInfo()
->OnShipContainerUpdate.AddDynamic(this, &UTutorialTask_CheckShipContainer::OnShipContainerUpdate);
}
}
void UTutorialTask_CheckShipContainer::BeforTutorialComplete_Implementation()
{
Super::BeforTutorialComplete_Implementation();
}
void UTutorialTask_CheckShipContainer::OnShipContainerUpdate()
{
//只有3个物品时才完成当前的引导步骤
int CurrentItemCount = GetOuter()->GetWorld()->GetGameInstance()->GetSubsystem<UGameInfoManager>()->GetPlayerInfo()->ShipContainerItems.Num();
if (CurrentItemCount == ReqItemsCount)
{
Cast<UTutorialManagerSubsystem>(GetOuter())->CompleteTutorialStep();
}
}

View File

@ -0,0 +1,26 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "TutorialTask_Base.h"
#include "TutorialTask_CheckShipContainer.generated.h"
/**
*
*/
UCLASS()
class PROJECTFISH_API UTutorialTask_CheckShipContainer : public UTutorialTask_Base
{
GENERATED_BODY()
virtual void Execute_Implementation(class AGameModeBase* GameMode) override;
virtual void BeforTutorialComplete_Implementation() override;
UFUNCTION()
void OnShipContainerUpdate();
protected:
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "TutorialTask_CheckShipContainer")
int32 ReqItemsCount;
};

View File

@ -0,0 +1,37 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "TutorialTask_GamePause.h"
#include "Kismet/GameplayStatics.h"
void UTutorialTask_GamePause::Execute_Implementation(class AGameModeBase* GameMode)
{
Super::Execute_Implementation(GameMode);
// LevelLoadedDelegateHandle = FCoreUObjectDelegates::PostLoadMapWithWorld.AddUObject(
// this,
// &UTutorialTask_GamePause::OnLevelLoaded
// );
UGameplayStatics::SetGamePaused(this, true);
}
void UTutorialTask_GamePause::BeforTutorialComplete_Implementation()
{
Super::BeforTutorialComplete_Implementation();
UGameplayStatics::SetGamePaused(this, false);
}
void UTutorialTask_GamePause::OnLevelLoaded(UWorld* World)
{
// FCoreUObjectDelegates::PostLoadMapWithWorld.Remove(LevelLoadedDelegateHandle);
// FTimerHandle LambdaTimerHandle;
// GetWorld()->GetTimerManager().SetTimer(
// LambdaTimerHandle,
// [this]()
// {
// UGameplayStatics::SetGamePaused(this, true);
// },
// 1.5f,
// false
// );
}

View File

@ -0,0 +1,25 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "TutorialTask_Base.h"
#include "TutorialTask_GamePause.generated.h"
/**
*
*/
UCLASS()
class PROJECTFISH_API UTutorialTask_GamePause : public UTutorialTask_Base
{
GENERATED_BODY()
public:
virtual void Execute_Implementation(class AGameModeBase* GameMode) override;
virtual void BeforTutorialComplete_Implementation() override;
void OnLevelLoaded(UWorld* World);
private:
FDelegateHandle LevelLoadedDelegateHandle;
};

View File

@ -6,6 +6,7 @@ public class ProjectFish : ModuleRules
{ {
public ProjectFish(ReadOnlyTargetRules Target) : base(Target) public ProjectFish(ReadOnlyTargetRules Target) : base(Target)
{ {
PrivateDependencyModuleNames.AddRange(new string[] { "Dialogue" });
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
OptimizeCode = CodeOptimization.Never; OptimizeCode = CodeOptimization.Never;
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine",

View File

@ -23,7 +23,7 @@ void USkillManager::Tick(float DeltaTime)
bool USkillManager::IsTickable() const bool USkillManager::IsTickable() const
{ {
return !bGameEnd && IsValid(GameMode) && GameMode->GetBattleIsBegin(); return !bGameEnd && IsValid(GameMode) && GameMode->GetBattleIsBegin() && !GameMode->IsPaused();
} }
TStatId USkillManager::GetStatId() const TStatId USkillManager::GetStatId() const

View File

@ -3,6 +3,7 @@
#include "FishFunctionLibrary.h" #include "FishFunctionLibrary.h"
#include "EngineUtils.h"
#include "Kismet/GameplayStatics.h" #include "Kismet/GameplayStatics.h"
#include "ProjectFish/Data/ContainerInfo.h" #include "ProjectFish/Data/ContainerInfo.h"
@ -25,3 +26,20 @@ int32 UFishFunctionLibrary::GetContainerItemHeight(const UObject* WorldContextOb
{ {
return ContainerItem.GetItemHeight(); return ContainerItem.GetItemHeight();
} }
AActor* UFishFunctionLibrary::GetActorOfClass(const UObject* WorldContextObject, TSubclassOf<AActor> ActorClass)
{
if (ActorClass)
{
if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull))
{
for (TActorIterator<AActor> It(World, ActorClass); It; ++It)
{
AActor* Actor = *It;
return Actor;
}
}
}
return nullptr;
}

View File

@ -23,4 +23,7 @@ public:
UFUNCTION(BlueprintPure, Category = "FishFunctionLibrary", meta=(WorldContext="WorldContextObject")) UFUNCTION(BlueprintPure, Category = "FishFunctionLibrary", meta=(WorldContext="WorldContextObject"))
static int32 GetContainerItemHeight(const UObject* WorldContextObject, FContainerItem ContainerItem); static int32 GetContainerItemHeight(const UObject* WorldContextObject, FContainerItem ContainerItem);
UFUNCTION(BlueprintCallable, Category="Actor")
static AActor* GetActorOfClass(const UObject* WorldContextObject, TSubclassOf<AActor> ActorClass);
}; };

View File

@ -0,0 +1,4 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "DialogueWidget_Base.h"

View 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;
};

View File

@ -0,0 +1,5 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "TutorialControlWidget_Base.h"

View File

@ -0,0 +1,22 @@
// 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 TutorialHideUI(const TArray<FName>& UINames);
// UFUNCTION(BlueprintImplementableEvent)
// void TutorialShowUI(const TArray<FName>& UINames);
};