// BubbleData.cs using System; using System.Collections.Generic; using System.Linq; using Logic; using Logic.Action; using RuntimeData; using TH1_Core.Events; using TH1_Core.Managers; using TH1_Logic.Core; using TH1_Logic.MatchConfig; using TH1Renderer; using UnityEngine; using UnityEngine.Tilemaps; using Object = UnityEngine.Object; using Random = System.Random; namespace TH1_Renderer { public enum BubbleType { None, Gather, Capture, HeroUpgrade, Upgrade, Examine, HeroSelect } //TODO 做BubbleData序列化相关的工作 [Serializable] public struct BubbleData { public GridData Grid; public UnitData Unit; //public HintType HintType; public BubbleType BubbleType; // 用于显示自定义图标(如英雄头像) public Sprite CustomSprite; public BubbleData(GridData grid , BubbleType bubbleType, Sprite customSprite = null) { Grid = grid; grid.MainSelfPlayerVisibleUnit(out Unit); BubbleType = bubbleType; CustomSprite = customSprite; } } [Serializable] public class InGameBubbleManager { //public HashSet HintSet = new(); //private Dictionary _idToHintDict = new(); private uint _bubbleRendererIdCounter; private GameObject _prefabROHint; private Transform _selfTransform; //private Dictionary _ROHintRendererDict = new(); //存储可用的bubbleRendererId private HashSet _availableRenderer; private Dictionary _dict; //gid -> rendererid private Dictionary _gridHasBubble; //每次开启一局游戏前,都要初始化的模块 public void Init(Transform selfTransform) { _bubbleRendererIdCounter = 0; _prefabROHint = Resources.Load("Prefab/bubblePrefab"); _selfTransform = selfTransform; _availableRenderer = new HashSet(); _dict = new Dictionary(); _gridHasBubble = new Dictionary(); } //每局游戏开始前都要更新的 public void OnGameStart() { _bubbleRendererIdCounter = 0; _availableRenderer = new HashSet(); _dict = new Dictionary(); _gridHasBubble = new Dictionary(); foreach (Transform child in _selfTransform.transform) { // 对每个子对象调用Destroy Object.Destroy(child.gameObject); } } public void OnGameClosed() { CloseAllBubble(); ClearAllRenderer(); } /// /// 关卡限制变动时(OnMatchLimitsChanged)的统一刷新入口。 /// bubble 由每帧 Update 自然刷新,所以本方法当前为空实现——保留它是为了: /// 1) 让 UIEventManagerBinder 能把 bubble 也纳入"统一入口"派发; /// 2) 万一未来 Update 改为非每帧驱动,可以在此立即触发一次 bubble 重算。 /// public void RefreshLimitEffects() { // 当前 Update 已经每帧刷新 bubble,无需在此重复触发。 } public void ClearAllRenderer() { foreach (var t in _dict) t.Value.Destroy(); _dict.Clear(); } public uint BubbleDataIdGenerator() { return _bubbleRendererIdCounter++; } //每帧调用所有hintRenderer的update,并且将已经结束的Renderer回收 public void Update() { if (Main.MapData == null) return; if (!Main.MapData.CurPlayer?.IsSelfPlayer() ?? true) return; //遍历所有unit,查看哪些单位需要更新bubble显示 foreach (var unit in Main.MapData.UnitMap.UnitList) { //如果不是本机玩家 if (!unit.Player(Main.MapData, out var player)||!player.IsSelfPlayer()) continue; if(!unit.Grid(Main.MapData,out var grid)) continue; //准备升级行为的参数 var upgradeAction = ActionLogicFactory.GetActionLogic(new CommonActionId() { ActionType = CommonActionType.UnitAction, UnitActionType = UnitActionType.Upgrade }); var heroUpgradeAction = ActionLogicFactory.GetActionLogic(new CommonActionId() { ActionType = CommonActionType.UnitAction, UnitActionType = UnitActionType.HeroUpgrade }); var captureAction = ActionLogicFactory.GetActionLogic(new CommonActionId() { ActionType = CommonActionType.UnitAction, UnitActionType = UnitActionType.Capture }); var starfishAction = ActionLogicFactory.GetActionLogic(new CommonActionId() { ActionType = CommonActionType.UnitAction, UnitActionType = UnitActionType.Gather }); var treasureAction = ActionLogicFactory.GetActionLogic(new CommonActionId() { ActionType = CommonActionType.UnitAction, UnitActionType = UnitActionType.Examine }); var actionParam = new CommonActionParams(mapData: Main.MapData, playerData: Main.MapData.PlayerMap.SelfPlayerData, unitData: unit, gridData: unit.Grid(Main.MapData)); //先判断是否已经有按钮,如果有 bool upgrade = upgradeAction.CheckCan(actionParam); bool heroUpgrade = heroUpgradeAction.CheckCan(actionParam); bool capture = captureAction.CheckCan(actionParam); bool treasure = treasureAction.CheckCan(actionParam); bool starfish = starfishAction.CheckCan(actionParam); bool showBubble = upgrade || heroUpgrade || capture || treasure || starfish; BubbleType bubbleType = BubbleType.Upgrade; if(heroUpgrade)bubbleType = BubbleType.HeroUpgrade; if(capture)bubbleType = BubbleType.Capture; if(treasure)bubbleType = BubbleType.Examine; if(starfish)bubbleType = BubbleType.Gather; //判断是否能执行升级行为,可以的话,播放bubble if (showBubble) { if (_gridHasBubble.ContainsKey(grid.Id)) continue; if(ShowBubble(new BubbleData(grid,bubbleType),out var rendererId)) _gridHasBubble.Add(grid.Id,rendererId); } //如果不应该播放bubble但是有个bubble,关掉他 //注:HeroSelect类型的气泡不依赖单位Action,需要单独检查 if(!showBubble) { if (_gridHasBubble.ContainsKey(grid.Id)) { // 获取现有气泡类型,跳过HeroSelect类型 if (_dict.TryGetValue(_gridHasBubble[grid.Id], out var existingRenderer)) { var existingBubbleData = existingRenderer.GetBubbleData(); if (existingBubbleData.BubbleType == BubbleType.HeroSelect) continue; // HeroSelect气泡不由单位Action控制 existingRenderer.ForceClose(); } RecycleBubble(_gridHasBubble[grid.Id],grid.Id); } } } //遍历现在所有存在bubble的格子,如果格子上没有unit,让这个bubble自动关闭 //注:HeroSelect类型的气泡不依赖单位,跳过检查 var deleteList = new List(); foreach (var t in _gridHasBubble) { if (!Main.MapData.GridMap.GetGridDataByGid(t.Key, out var grid)) continue; // 获取bubble类型 BubbleType bubbleType = BubbleType.None; if (_dict.TryGetValue(t.Value, out var renderer)) { bubbleType = renderer.GetBubbleData().BubbleType; } if (bubbleType == BubbleType.HeroSelect) { var selfPlayer = Main.MapData.PlayerMap?.SelfPlayerData; var canShowHeroSelectBubble = ShouldShowHeroSelectBubble(Main.MapData, selfPlayer); if (!canShowHeroSelectBubble) { renderer.ForceClose(); deleteList.Add(t.Key); } continue; } if (!grid.MainSelfPlayerVisibleUnit( out var _)) { renderer.ForceClose(); deleteList.Add(t.Key); } } //回收这些bubble foreach (var t in deleteList) if(_gridHasBubble.TryGetValue(t,out var v)) RecycleBubble(v,t); } public bool ShowBubble(BubbleData data,out uint rendererId) { //Step #1 获取一个可用的renderer var id = GetAvailableRenderer(); _availableRenderer.Remove(id); if (!_dict.TryGetValue(id, out var renderer)) { Debug.Log($"Fatal error Showbubble can't find dict key id = {id}"); rendererId = 0; return false; } rendererId = renderer.Id; //Step #2 设置对应的renderer return renderer.SetInfo(data,onClick:ClickBubble); } public uint GetAvailableRenderer() { //如果池子里没有可用对象,新创建一个 if (_availableRenderer.Count == 0) { var renderer = new BubbleRenderer(BubbleDataIdGenerator(),_prefabROHint,_selfTransform); _availableRenderer.Add(renderer.Id); _dict[renderer.Id] = renderer; return renderer.Id; } //否则随便给一个可以用的 return _availableRenderer.First(); } public void CloseAllBubble() { //遍历所有renderer,查询是否有不在available set里的,有的话就强制关闭 foreach (var t in _dict) if (!_availableRenderer.Contains(t.Key)) { t.Value.ForceClose(); _availableRenderer.Add(t.Key); } _gridHasBubble.Clear(); } //被动由逻辑触发 public void RecycleBubble(uint rendererId,uint gid) { _availableRenderer.Add(rendererId); _gridHasBubble.Remove(gid); } //由玩家主动触发 public void ClickBubble(uint rendererId,uint gid) { // 获取气泡类型 var isHeroSelectBubble = false; if (_dict.TryGetValue(rendererId, out var renderer)) { isHeroSelectBubble = renderer.GetBubbleData().BubbleType == BubbleType.HeroSelect; } RecycleBubble(rendererId,gid); //UI收回 Main.Instance.MapInteractionLogic.CancelAllHighlight(); // HeroSelect气泡不发送HideAllUIInfo,因为会关闭刚打开的Hero界面 if (!isHeroSelectBubble) { EventManager.Publish(new HideAllUIInfo(){}); } } // HeroSelect 气泡的显示条件:有待选英雄,或者可以购买 SecondHero/ThirdHero 文化卡(且尚未拥有) private static bool ShouldShowHeroSelectBubble(MapData mapData, PlayerData playerData) { if (mapData == null || playerData == null) return false; // Tutor 模式 PlayerCannotSelectHero 限制:屏蔽 HeroSelect 气泡入口。 // 与底部栏 Hero 按钮的限制保持一致,避免玩家绕开按钮屏蔽从地图气泡进入英雄界面。 // 限制运行时被任务解除后,由 OnMatchLimitsChanged 事件统一驱动;同时每帧 Update 也会 // 自然刷新 bubble,事件回调作为正式接入点保留(见 RefreshLimitEffects)。 if (mapData.MatchSettlement != null && mapData.MatchSettlement.SettlementType == MatchSettlementType.Tutor && mapData.MapConfig?.MatchLimits != null && mapData.MapConfig.MatchLimits.Contains(MatchLimitType.PlayerCannotSelectHero)) return false; if (playerData.PlayerHeroData?.GetHeroButtonHint() == true) return true; var cultureInfo = playerData.PlayerCultureInfo; if (cultureInfo == null) return false; if (CanBuyHeroCultureCard(mapData, playerData, cultureInfo, CultureCardType.SecondHero)) return true; if (CanBuyHeroCultureCard(mapData, playerData, cultureInfo, CultureCardType.ThirdHero)) return true; return false; } private static bool CanBuyHeroCultureCard(MapData mapData, PlayerData playerData, PlayerCultureInfo cultureInfo, CultureCardType cardType) { // 已经拥有则不需要再买 if (cultureInfo.CultureCardList != null && cultureInfo.CultureCardList.Contains(cardType)) return false; return cultureInfo.CheckCanBuyCultureCard(mapData, playerData, cardType); } public void TurnStartSetBubble(MapData mapData, PlayerData playerData) { //Debug.Log($"[Bubble] TurnStartSetBubble called for player {playerData.Id}, IsSelfPlayer={playerData.IsSelfPlayer()}"); // 只处理本机玩家 if (!playerData.IsSelfPlayer()) { //Debug.Log("[Bubble] Not self player, returning"); return; } // 检查是否有待选择的英雄 var heroData = playerData.PlayerHeroData; //Debug.Log($"[Bubble] PlayerHeroData is null: {heroData == null}"); //if (heroData != null) //{ // Debug.Log($"[Bubble] HeroCount={heroData.HeroCount}, MaxHeroCount={heroData.MaxHeroCount}, GetHeroButtonHint={heroData.GetHeroButtonHint()}"); //} if (!ShouldShowHeroSelectBubble(mapData, playerData)) { //Debug.Log("[Bubble] No hero to select and no hero culture card buyable, returning"); return; } // 获取玩家首都位置 if (!mapData.GetCapitalCityDataByPlayerId(playerData.Id, out var capitalCity)) { //Debug.Log("[Bubble] No capital city found, returning"); return; } //Debug.Log($"[Bubble] Found capital city: {capitalCity?.Id}"); var capitalGrid = capitalCity.Grid(mapData); if (capitalGrid == null) { //Debug.Log("[Bubble] Capital grid is null, returning"); return; } //Debug.Log($"[Bubble] Capital grid: {capitalGrid.Id}"); // 获取英雄按钮的sprite var heroSprite = playerData.PlayerHeroData.GetHeroButtonSprite(); //Debug.Log($"[Bubble] Hero sprite is null: {heroSprite == null}"); // 创建BubbleData并显示 var bubbleData = new BubbleData(capitalGrid, BubbleType.HeroSelect, heroSprite); //Debug.Log($"[Bubble] Created BubbleData for HeroSelect at grid {capitalGrid.Id}"); // 如果该位置已有bubble,先关闭它 if (_gridHasBubble.ContainsKey(capitalGrid.Id)) { //Debug.Log($"[Bubble] Grid {capitalGrid.Id} already has bubble, recycling"); if (_dict.TryGetValue(_gridHasBubble[capitalGrid.Id], out var existingRenderer)) existingRenderer.ForceClose(); RecycleBubble(_gridHasBubble[capitalGrid.Id], capitalGrid.Id); } // 显示新bubble //Debug.Log("[Bubble] Calling ShowBubble..."); if (ShowBubble(bubbleData, out var rendererId)) { _gridHasBubble.Add(capitalGrid.Id, rendererId); //Debug.Log($"[Bubble] Successfully showed bubble with rendererId {rendererId}"); } else { //Debug.Log("[Bubble] ShowBubble returned false!"); } } } }