using asap.core; using cfg; using game; using LitJson; using System; using System.Collections.Generic; using System.Linq; using UniRx; using UnityEngine; using UnityEngine.AddressableAssets; namespace GameCore { public class GuideNextEvent { } public class AddIndexEvent { public bool CompleteGuide; public int guideSubIndex; public bool isAutoNext; } /// /// 废弃 /// public struct OnEventTriggerGuide { public OnEventTriggerGuide(string panelName) { this.panelName = panelName; } public string panelName; } public class GuideSaveData { public int guideSubIndex = 0; public bool isFinish = false; public string groupName; } /// /// 新手引导数据中心。外部触发主要有三类路径: /// 1) 各 UI/玩法在打开或 OnEnable 时调用 ,按配置表首步 panelName 与触发条件尝试开组; /// 2) 面板打开后通过 GContext.Publish(new OnEventTriggerGuide(panelName)) 驱动 ,推进「当前组」下一步或回退尝试 ; /// 3) 业务显式调用 / / / 等完成指定流程。 /// 存档由 (登录拉取)与 (写 PlayFab)维护。 /// public class GuideDataCenter : IDisposable { Tables _tables; PlayerData playerData; public GuideDataCenter(cfg.Tables tables, PlayerData playerData) { this._tables = tables; this.playerData = playerData; } public string CurPanelName; public Dictionary GuideSaveDataDic = new Dictionary(); public GuideSaveData curSaveData; public GuidanceGroupDefine[] guidanceGroupDefines; public GuidanceDefine curDefine; public string popupPanelNode; public GuidanceGroupDefine curGroupDefine; public GuidanceDefineSetting guidanceDefineSetting; //待释放 IDisposable disposable; IDisposable disposable2; IDisposable disposable3; /// /// GM/调试:清空所有引导组进度并标记为已完成。典型入口:DebugFunc 控制台 guide.clear。 /// public void GMClearGuide() { foreach (var item in guidanceGroupDefines) { if (!GuideSaveDataDic.TryGetValue(item.groupName.ToString(), out GuideSaveData _saveData)) { _saveData = new GuideSaveData(); _saveData.groupName = item.groupName.ToString(); GuideSaveDataDic.Add(item.groupName.ToString(), _saveData); } _saveData.isFinish = true; } GContext.Publish(new ConditionTypeEvent(ConditionType.CompleteGuide, 0)); curDefine = null; curGroupDefine = null; Save(); } /// /// 加载引导配置、校正未完成组的断点、订阅 。由 FishingStage 等阶段在进游戏后调用。 /// public async void Init() { guidanceDefineSetting = (GuidanceDefineSetting)await Addressables.LoadAssetAsync("GuidanceConfig"); if (guidanceDefineSetting != null) { bool isUpdate = false; guidanceGroupDefines = guidanceDefineSetting.guidanceGroupDefines; List Skip = guidanceDefineSetting.SkipGroupNames; bool isSkip = playerData.lv >= guidanceDefineSetting.SkipLevel; if (GuideSaveDataDic == null) { GuideSaveDataDic = new Dictionary(); } else { GuideSaveData _saveData; GuidanceDefine _define; Dictionary keyValuePairs = new Dictionary(); foreach (var item in guidanceGroupDefines) { if (GuideSaveDataDic.TryGetValue(item.groupName.ToString(), out _saveData)) { if (!_saveData.isFinish && _saveData.guideSubIndex > 0) { for (int i = _saveData.guideSubIndex; i < item.guidanceDefines.Length; i++) { _define = item.guidanceDefines[i]; if (_define.isKey) { _saveData.guideSubIndex = _define.JumpIndex; if (_saveData.guideSubIndex < item.guidanceDefines.Length) { _saveData.isFinish = false; curSaveData = _saveData; curGroupDefine = item; curDefine = curGroupDefine.guidanceDefines[_saveData.guideSubIndex]; } else { _saveData.isFinish = true; GContext.Publish(new ConditionTypeEvent(ConditionType.CompleteGuide, 1)); } break; } else { _saveData.isFinish = true; } } isUpdate = true; } keyValuePairs[item.groupName.ToString()] = _saveData; } else if (isSkip && Skip.Contains(item.groupName)) { _saveData = new GuideSaveData(); _saveData.groupName = item.groupName.ToString(); _saveData.isFinish = true; keyValuePairs[item.groupName.ToString()] = _saveData; isUpdate = true; } } GuideSaveDataDic = keyValuePairs; if (isUpdate) { Save(); } } if (disposable2 == null) { disposable2 = GContext.OnEvent().Subscribe(TriggerGuideNext); } } } /// /// 查询某引导组是否已走完(非触发入口,供红点/解锁逻辑等读取)。 /// public bool GetIsFinish(string groupName) { return GuideSaveDataDic.ContainsKey(groupName) && GuideSaveDataDic[groupName].isFinish; } /// /// 外部最常用的开组入口:传入配置里与该引导第一步对应的逻辑面板名(如 HomePanel、FishingPanel_Advanced)。 /// 若当前已在进行中的组且本步 panelName 匹配,会内部调用 推进; /// 否则遍历配置,按 GuideTriggerConditions 校验后可能 开新组。 /// 用于部分条件(如第二根鱼竿、营地相关)排除从首页路径误触; /// 当前宿主界面根节点的 GameObject.name(可与配置里逻辑面板名不同),写入 / 引导层查找节点;不需要可留空。 /// 调用方示例:各 Panel OnEnable、FishingUpdateSysHomePanel、活动玩法面板等(工程内大量 InspectTriggerGuide("…"))。 /// public bool InspectTriggerGuide(string panelName, string prePanelName = "", string hostGameObjectName = "") { if (prePanelName.Contains("FishingRodPanel")) { return false; } if (guidanceDefineSetting == null) { return false; } if (curGroupDefine != null && GuideSaveDataDic.TryGetValue(curGroupDefine.groupName.ToString(), out var guideSaveData) //&& !guideSaveData.isFinish && curGroupDefine.guidanceDefines.Length > guideSaveData.guideSubIndex && panelName == curGroupDefine.guidanceDefines[guideSaveData.guideSubIndex].panelName) { CurPanelName = hostGameObjectName; TriggerGuideNext(new OnEventTriggerGuide(panelName)); return true; } PlayerFishData playerFishData = GContext.container.Resolve(); GuidanceGroupDefine guidanceGroupDefine; for (int i = 0; i < guidanceGroupDefines.Length; i++) { guidanceGroupDefine = guidanceGroupDefines[i]; bool isFinish = GetIsFinish(guidanceGroupDefine.groupName.ToString()); if (isFinish || panelName != guidanceGroupDefine.guidanceDefines[0].panelName) { continue; } if (CheckTriggerGuideFinish(guidanceGroupDefine)) { continue; } int count; switch (guidanceGroupDefine.TriggerConditionsType) { case GuideTriggerConditions.Next: break; case GuideTriggerConditions.First_login: break; case GuideTriggerConditions.OpenSystem_Shop: if (GContext.container.Resolve().IsShopOpen == false) { continue; } break; //case GuideTriggerConditions.UnlockSystem_Album: // if (GContext.container.Resolve().theme == null) // { // continue; // } // break; case GuideTriggerConditions.Level_2: if (playerData.lv >= 2) { break; } continue; case GuideTriggerConditions.Group_Finish: if (GuideSaveDataDic.TryGetValue(guidanceGroupDefine.TriggerConditionsParameter, out var _data) && _data.isFinish) { break; } continue; case GuideTriggerConditions.GetDiffFishCount: if (int.TryParse(guidanceGroupDefine.TriggerConditionsParameter, out count) && playerFishData.GetDiffFishCount() >= count) { if (playerFishData.GetDiffFishCount() > count) { TriggerGuideFinish(guidanceGroupDefine); continue; } break; } continue; case GuideTriggerConditions.GetFishCount: if (int.TryParse(guidanceGroupDefine.TriggerConditionsParameter, out count) && playerFishData.GetAnglingCount() >= count) { if (playerFishData.GetAnglingCount() > count) { TriggerGuideFinish(guidanceGroupDefine); continue; } break; } continue; case GuideTriggerConditions.FirstClickMap_AtMap2: if (playerData.lastMapId == _tables.TbMapData.DataList[1].ID) { break; } continue; case GuideTriggerConditions.AccountLevel: if (int.TryParse(guidanceGroupDefine.TriggerConditionsParameter, out count) && playerData.lv >= count) { break; } continue; case GuideTriggerConditions.UnlockSystem: if (int.TryParse(guidanceGroupDefine.TriggerConditionsParameter, out count) && GContext.container.Resolve().GetSystemOpen(count)) { break; } continue; case GuideTriggerConditions.UnlockEvent: string[] str = guidanceGroupDefine.TriggerConditionsParameter.Split('-'); if (str.Length == 2) { if (int.TryParse(str[0], out count) && int.TryParse(str[1], out int count2) && GContext.container.Resolve().GetEvent(count, count2) > 0) { break; } } else if (str.Length == 3) { if (int.TryParse(str[0], out count) && int.TryParse(str[1], out int count2) && int.TryParse(str[2], out int count3) && GContext.container.Resolve().GetEventRedirectID(count, count2) == count3) { break; } } //if (int.TryParse(guidanceGroupDefine.TriggerConditionsParameter, out count) && GContext.container.Resolve().GetSystemOpen(count)) //{ // break; //} continue; case GuideTriggerConditions.MaxMultiplierCount: if (playerData.IsOpenMagnification && int.TryParse(guidanceGroupDefine.TriggerConditionsParameter, out count) && playerData.ContainsOrGreaterRatio(count)) { break; } continue; case GuideTriggerConditions.GetSecondRod: if (!prePanelName.Contains("HomePanel") && playerFishData.IsOpenRod && playerFishData.GetRodAllCount() > 1) { break; } continue; case GuideTriggerConditions.RodAscendAvailable: if (!prePanelName.Contains("HomePanel") && playerFishData.IsOpenRod && playerFishData.GetRodCanUp()) { break; } continue; case GuideTriggerConditions.RodUpgradeAvailable: if (!prePanelName.Contains("HomePanel") && playerFishData.IsOpenRod && playerFishData.GetRodCanUpLevel()) { break; } continue; case GuideTriggerConditions.BuyPack: if (int.TryParse(guidanceGroupDefine.TriggerConditionsParameter, out count) && GContext.container.Resolve().GetAllPackCount(count) > 0) { break; } continue; case GuideTriggerConditions.BoxOpenCondition: FishingBoxDataProvier fishingBoxData = GContext.container.Resolve(); if (fishingBoxData.IsUnclocked && fishingBoxData.GetBoxCount(fishingBoxData.GetIDByQuality(3)) > 0) { break; } continue; case GuideTriggerConditions.GoBuilding: if (GContext.container.Resolve().GoBuilding()) { break; } continue; case GuideTriggerConditions.GoBuilding02: if (playerData.lv >= 12) { TriggerGuideFinish(guidanceGroupDefine); Debug.Log("TriggerGuideFinish(guidanceGroupDefine);"); continue; } if (GContext.container.Resolve().GoBuilding02(101012)) { Debug.Log("TriggerGuideFinish();"); break; } continue; default: continue; } if (curGroupDefine == guidanceGroupDefine) { return false; } if (panelName == guidanceGroupDefine.guidanceDefines[0].panelName) { TriggerGuide(guidanceGroupDefine, hostGameObjectName); return true; } } return false; } /// /// 业务侧直接将某组标记为已完成并持久化。典型场景:活动面板在条件满足时跳过引导(如药水活动 EventPotionPanel)。 /// public void MarkGuideFinished(GroupName groupName) { string key = groupName.ToString(); if (GetIsFinish(key)) return; if (!GuideSaveDataDic.TryGetValue(key, out var saveData)) { saveData = new GuideSaveData(); saveData.groupName = key; GuideSaveDataDic.Add(key, saveData); } saveData.isFinish = true; Save(); } /// /// 移除某组本地存档(下次可重新触发)。用于活动期重置、调试面板输入组名清除等(如 Smash/Slap 活动 Manager、DeBugPanel)。 /// public void ClearGuide(string groupName) { if (GuideSaveDataDic.ContainsKey(groupName)) { GuideSaveDataDic.Remove(groupName); Save(); } } /// /// 特殊屏蔽的新手引导:满足条件时直接调用 ,避免再弹出。 /// /// bool CheckTriggerGuideFinish(GuidanceGroupDefine group) { bool isFinishPass = false; //判断是否直接完成 if (group.groupName == GroupName.Chanllenge) { // isFinishPass = !GContext.container.Resolve().IsFirstOpen; } if (isFinishPass) { TriggerGuideFinish(group); } return isFinishPass; } void TriggerGuideFinish(GuidanceGroupDefine group) { string groupName = group.groupName.ToString(); if (!GuideSaveDataDic.TryGetValue(groupName, out var guideSaveData)) { guideSaveData = new GuideSaveData(); guideSaveData.groupName = groupName; GuideSaveDataDic.Add(groupName, guideSaveData); } guideSaveData.guideSubIndex = group.guidanceDefines.Length; guideSaveData.isFinish = true; Save(); } /// /// 不按「遍历条件」、直接指定组名开组: 须与配置中 GroupName 字符串一致, 须等于该组第一步的 GuidanceDefine.panelName。 /// 为 false 时若该组已完成则不再触发。 /// :宿主界面 GameObject.name,供引导定位;可空。 /// 典型调用:CampPanel 固定引导、FishingUpdateSys 鱼逃脱、DataCenter.FishingEventData 事件引导、EventSmashPanel/EventMatchGamePanel/EventCubeMeltPanel 等活动显式开组。 /// public bool TriggerGuide(string guidanceGroupKey, string panelName, bool loop = true, string hostGameObjectName = "") { if (guidanceDefineSetting == null) { return false; } GuidanceGroupDefine guidanceGroupDefine; if (!loop) { bool isFinish = GetIsFinish(guidanceGroupKey); if (isFinish) { return false; } } for (int i = 0; i < guidanceGroupDefines.Length; i++) { guidanceGroupDefine = guidanceGroupDefines[i]; if (guidanceGroupDefine.groupName.ToString() == guidanceGroupKey) { if (panelName != guidanceGroupDefine.guidanceDefines[0].panelName) { return false; } TriggerGuide(guidanceGroupKey, hostGameObjectName, guidanceGroupDefine); return true; } } return false; } /// /// 的内部实现:延迟后写入存档、设当前组/步并弹出 GuidancePanel。 /// async void TriggerGuide(string guidanceGroupKey, string hostGameObjectName, GuidanceGroupDefine guidanceGroupDefine) { await System.Threading.Tasks.Task.Delay(200); curSaveData = new GuideSaveData(); curSaveData.groupName = guidanceGroupKey; GuideSaveDataDic[guidanceGroupKey] = curSaveData; curGroupDefine = guidanceGroupDefine; curDefine = curGroupDefine.guidanceDefines[0]; ShowPanel(hostGameObjectName); curSaveData.guideSubIndex++; } /// /// 由 在条件满足时调用,初始化该组进度并打开引导界面。 /// void TriggerGuide(GuidanceGroupDefine group, string hostGameObjectName) { string groupName = group.groupName.ToString(); if (!GuideSaveDataDic.TryGetValue(groupName, out var guideSaveData)) { guideSaveData = new GuideSaveData(); guideSaveData.groupName = groupName; GuideSaveDataDic.Add(groupName, guideSaveData); } if (guideSaveData.isFinish) { return; } guideSaveData.guideSubIndex = 0; curSaveData = guideSaveData; curGroupDefine = group; curDefine = group.guidanceDefines[0]; ShowPanel(hostGameObjectName); curSaveData.guideSubIndex++; } /// /// 订阅 GContext.OnEvent<OnEventTriggerGuide>()(在 中注册)。 /// 典型发布方:CampPanelFishingPanel_AdvancedFishingRewardPanelBuildActFishingRodRewardPopupPanel 等在面板打开时 Publish(new OnEventTriggerGuide(GetType().Name))。 /// 有当前组且 panel 匹配时推进子步骤;否则回退为 尝试新开组。 /// public void TriggerGuideNext(OnEventTriggerGuide eventTriggerGuide) { if (curGroupDefine != null && GuideSaveDataDic.TryGetValue(curGroupDefine.groupName.ToString(), out var guideSaveData)) { if (curGroupDefine.guidanceDefines.Length <= curSaveData.guideSubIndex || eventTriggerGuide.panelName != curGroupDefine.guidanceDefines[guideSaveData.guideSubIndex].panelName) { return; } curDefine = curGroupDefine.guidanceDefines[guideSaveData.guideSubIndex]; if (curDefine.completeType == CompleteType.PanelOpen) { string uIType = curDefine.completeTypeParameter; if (!string.IsNullOrEmpty(uIType) && UIManager.Instance.ContainsUI(uIType)) { curSaveData.guideSubIndex++; AddIndex(); } else { disposable = GContext.OnEvent().Subscribe(data => { if (uIType == data.uIType && data.show) { curSaveData.guideSubIndex++; AddIndex(); } }); } } else { ShowPanel(CurPanelName); curSaveData.guideSubIndex++; } } else { InspectTriggerGuide(eventTriggerGuide.panelName); } } /// /// 推进当前引导步骤或结束整组:关闭 GuidancePanel、发布 AddIndexEvent、必要时链式 接下一组。 /// 用户点击完成由 GuidancePanel 调用;玩法内跳过/条件达成可传 为 true(如 FishingUpdateSysFishingRewardPanel)。 /// public void AddIndex(bool is_pass = false) { if (curDefine == null) { return; } if (disposable != null) { disposable.Dispose(); disposable = null; } if (disposable3 != null) { disposable3.Dispose(); disposable3 = null; } UIManager.Instance.HideUI(UITypes.GuidancePanel); bool isAutoNext = curDefine.isAutoNext; AddIndexEvent addIndexEvent = new AddIndexEvent(); addIndexEvent.isAutoNext = isAutoNext; if (curGroupDefine.guidanceDefines.Length <= curSaveData.guideSubIndex || is_pass) { #if AGG using (var e = GEvent.GameEvent("guide_complete")) { e.AddContent("guide_id", curGroupDefine.groupName.ToString()) .AddContent("is_pass", is_pass); } #endif string panelName = curDefine.panelName; curDefine = null; curGroupDefine = null; curSaveData.isFinish = true; Save(); if (isAutoNext) { InspectTriggerGuide(panelName, panelName); } GContext.Publish(new ConditionTypeEvent(ConditionType.CompleteGuide, 1)); addIndexEvent.CompleteGuide = true; addIndexEvent.guideSubIndex = curSaveData.guideSubIndex; GContext.Publish(addIndexEvent); } else { Save(); var define = curGroupDefine.guidanceDefines[curSaveData.guideSubIndex]; if (isAutoNext && define.panelName == curDefine.panelName) { curDefine = define; ShowPanel(CurPanelName); curSaveData.guideSubIndex++; } else if (!isAutoNext) { for (int i = curSaveData.guideSubIndex; i < curGroupDefine.guidanceDefines.Length; i++) { var _define = curGroupDefine.guidanceDefines[i]; if (_define.isKey) { curSaveData.isFinish = false; break; } else { curSaveData.isFinish = true; } } } addIndexEvent.guideSubIndex = curSaveData.guideSubIndex; GContext.Publish(addIndexEvent); } } /// /// 显示引导蒙层:按需订阅 ,记录 并打开 GuidancePanel。 /// async void ShowPanel(string PanelName) { if (curDefine.fingerType != FingerType.None) { disposable3 = GContext.OnEvent().Subscribe(GuideNextEvent); } CurPanelName = PanelName; await UIManager.Instance.ShowUI(UITypes.GuidancePanel); } /// /// 打开轻量引导弹层 GuidancePopupPanel(如钓场张力/收线提示节点)。调用方:FishingUpdateSysGuidancePanel 配置中的 popup。 /// public async void ShowGuidancePopupPanel(string nodeName) { popupPanelNode = nodeName; if (!string.IsNullOrEmpty(popupPanelNode)) { await UIManager.Instance.ShowUI(UITypes.GuidancePopupPanel); UIManager.Instance.HideUI(UITypes.GuidancePanel); } } /// /// 手指类引导收到 后进入下一步(内部订阅,非外部直接调用)。 /// void GuideNextEvent(GuideNextEvent guideLongEvent) { if (curDefine != null && curDefine.fingerType != FingerType.None) { AddIndex(); } } /// /// 判断当前是否正在展示指向 的引导;若无当前引导会先试 。 /// 用于玩法逻辑在更新循环里 gate 住交互(如首页、列表等)。 /// public bool IsGuide(string panelName) { bool isGuide = curDefine != null; if (!isGuide) { InspectTriggerGuide(panelName); } return curDefine != null && curDefine.panelName == panelName; } /// /// 精确判断当前步:指定组、子步骤索引与面板名(如 FishingUpdateSys 中按 Fish_02 / FishEscape 步显示手指)。 /// 条件满足时会调用 尝试补触发。 /// public bool IsGuide(string panelName, int index, GroupName groupName) { bool isGuide = curDefine != null; if (isGuide && curGroupDefine.groupName == groupName && curSaveData.guideSubIndex == index) { InspectTriggerGuide(panelName); return true; } return false;// curDefine != null && curDefine.panelName == panelName; } /// /// 从持久化字符串恢复 。由 PlayerData 同步云端字段 GuideSaveDataNew 时调用。 /// public void SetData(string value) { string[] groupStrings = value.Split(';'); foreach (var groupString in groupStrings) { string[] strings = groupString.Split('|'); if (strings.Length == 3) { GuideSaveData saveData = new GuideSaveData(); saveData.groupName = strings[0]; if (int.TryParse(strings[1], out int guideSubIndex)) { saveData.guideSubIndex = guideSubIndex; } saveData.isFinish = strings[2] == "1"; GuideSaveDataDic[saveData.groupName] = saveData; } } } /// /// 将当前所有组进度写入 PlayFab 用户数据键 GuideSaveDataNew。 /// public void Save() { List strings = GuideSaveDataDic.Select(kv => kv.Key + "|" + kv.Value.guideSubIndex + "|" + (kv.Value.isFinish ? "1" : "0")).ToList(); string value = string.Join(";", strings); PlayFabMgr.Instance.UpdateUserDataValue("GuideSaveDataNew", value); } public void Dispose() { disposable?.Dispose(); disposable = null; disposable2?.Dispose(); disposable2 = null; disposable3?.Dispose(); disposable3 = null; } } }