using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; using RuntimeData; using Logic; using Logic.Action; using Logic.AI; using Logic.CrashSight; using TH1_Anim; using TH1_Core.Managers; using TH1_Logic.Core; using TH1_Presentation.Sequencer.Task; using TH1_Renderer; using TH1Resource; using UnityEngine.UI; namespace TH1Renderer { public class MapRenderer { private Main _main; private static List _aroundBuf; private MapData _mapData; private Transform _unitRenderMap; private Transform _gridRenderMap; public Transform HintRenderMap; //BubbleManager public InGameBubbleManager InGameBubbleManager; Transform _cityInfoRenderMap; public CameraController CameraController; public EffectManager EffectManager; //特效管理器,管理目前所有特效 public ProjectileManager ProjectileManager; private Transform _projectileRenderMap; //存储map对应的主要GameObject public GameObject ROMap; private GameObject _unitPrefab; //承载单位图像的prefab private GameObject _gridPrefab; //承载grid的prefab private GameObject _cityInfoPrefab; //承载城市图形信息、人口条的prefab public Dictionary ROGridMap; public Dictionary ROCityInfoMap; public Dictionary ROUnitMap; bool aniFlagExplorer = false; //用于探险家动画的标识bool GameObject explorerUnit; //用于存储探险家图像 Vector2Int explorerNowPos; //用于存储探险家Data当前的位置 // 临时探索者游戏对象 private GameObject _temporaryExplorer = null; private Vector3 _temporaryExplorerStartPos; private float _temporaryExplorerTime = 0f; private bool _temporaryExplorerActive = false; //-------- 表现层数据map交互部分 --------// //移动和攻击的高亮RenderData,set中存放了所有需要更新的对象 public HashSet HighlightGridIdSet = new HashSet(); public bool HighlightGridIdSetRenderMark = false; public HashSet HighlightUnitIdSet = new HashSet(); public bool HighlightUnitIdSetRenderMark = false; public UnitData SelectUnitData; //-------- 伤害预测悬停 --------// private uint _dmgPreviewHoverGridId; private float _dmgPreviewHoverTimer; private bool _dmgPreviewShowing; private uint _dmgPreviewSelfUnitId; private uint _dmgPreviewTargetUnitId; private const float DmgPreviewHoverThreshold = 1.0f; // 添加移动相关的成员变量 private Vector3 _explorerMoveStartPos; private Vector3 _explorerMoveTargetPos; private float _explorerMoveTime; private float _explorerMoveDuration; private bool _explorerIsMoving; // 添加成员变量来记录上一个位置 private Vector2Int _explorerPreviousPosition = new Vector2Int(-1, -1); // 初始化为无效位置 // 添加成员变量来跟踪移动次数和存储当前目标格子 private int _explorerMoveCount = 0; private int _explorerMaxMoves = 15; private GridData _currentTargetGridData; private static MapRenderer _instance; public static MapRenderer Instance { get { if (_instance == null) { Debug.LogError("MapRenderer not initialized. Call Initialize(main, mapData) first."); } return _instance; } } public static MapRenderer Current => _instance; public static void Initialize(Main main, MapData mapData) { if (_instance != null) { Debug.LogWarning("MapRenderer already initialized. Reinitializing..."); } _instance = new MapRenderer(main, mapData); } public static void Dispose() { _instance = null; } public static void TryUpdate() { if (_instance != null) { _instance.Update(); } } private MapRenderer(Main main, MapData mapData) { _main = main; _mapData = mapData; ROMap = main.ROMapRenderer; //初始化Data->Renderer的dict ROGridMap = new Dictionary(); ROCityInfoMap = new Dictionary(); ROUnitMap = new Dictionary(); // 初始化主要的渲染模块(grid city unit projectileManager) InitGridCityUnitObject(); //初始化相机模块 InitCameraObject(); //初始化BubbleManager InGameBubbleManager = new InGameBubbleManager(); InGameBubbleManager.Init(ROMap.transform.Find("HintMap")); } //当退出游戏或者结束游戏的时候出发,用来清理一些视觉对象 //InGame游戏开始的生命周期 public static void OnMatchStart(Main main, MapData mapData) { UIBlockCameraDrag.ResetState(); Dispose(); Initialize(main,mapData); _instance.InGameBubbleManager.OnGameStart(); // 根据地图尺寸动态计算相机边界 if (_instance.CameraController != null && mapData?.MapConfig != null) { _instance.CameraController.CalculateBoundsFromMapSize(mapData.MapConfig.Width, mapData.MapConfig.Height); } } //InGame游戏结束时的生命周期 public void OnMatchEnd() { UIBlockCameraDrag.ResetState(); InGameBubbleManager.OnGameClosed(); } private void ClearAllChildren(Transform parent) { if (parent == null) return; for (int i = parent.childCount - 1; i >= 0; i--) { GameObject.Destroy(parent.GetChild(i).gameObject); } } public bool InitGridCityUnitObject() { //重新建立新的关联 if (ROMap == null) return false; _unitRenderMap = ROMap.transform.Find("UnitMap"); _gridRenderMap = ROMap.transform.Find("GridMap"); _cityInfoRenderMap = ROMap.transform.Find("CityInfoMap")?.GetComponent(); //清理战场,把上一局Grid里面留下的东西清理干净 ClearAllChildren(_unitRenderMap); ClearAllChildren(_gridRenderMap); ClearAllChildren(_cityInfoRenderMap); ClearAllChildren(HintRenderMap); ClearAllChildren(ROMap.transform.Find("ProjectileMap")); _unitPrefab = Resources.Load($"Prefab/unitPrefab"); _gridPrefab = Resources.Load($"Prefab/tilePrefab"); _cityInfoPrefab = Resources.Load($"Prefab/cityInfoMapPrefab"); //建立子模块的manager,目前仅有projectile //TODO 这里要改成ProjectManager依赖注入,然后要上面ClearALL要把Projectilemanager算进去 ProjectileManager = new ProjectileManager(); ProjectileManager.Init(ROMap.transform.Find("ProjectileMap")); return true; } public bool InitCameraObject() { var cameraObject = GameObject.Find("Main Camera"); if (cameraObject == null) return false; CameraController = cameraObject.GetComponent(); return true; } public void Update() { //处理所有飞行道具。这个不会被暂停 ProjectileManager.Update(); //HintManager InGameBubbleManager.Update(); // 处理临时探索者的浮动动画 if (_temporaryExplorerActive && _temporaryExplorer != null) { _temporaryExplorerTime += Time.deltaTime; float yOffset = 0.2f * Mathf.Sin(2f * _temporaryExplorerTime); // 0.2f是浮动幅度,2f是浮动频率 // 如果正在移动,则不应用浮动效果 if (!_explorerIsMoving) { _temporaryExplorer.transform.position = new Vector3( _temporaryExplorerStartPos.x, _temporaryExplorerStartPos.y + yOffset, _temporaryExplorerStartPos.z ); } } //-------- 处理UI数据 ---------// //处理高亮 if (HighlightGridIdSetRenderMark) { HighlightGridIdSetRenderMark = false; foreach (var gid in HighlightGridIdSet) ROGridMap[gid].RenderUpdataHighlight(); HighlightGridIdSet.Clear(); } if (HighlightUnitIdSetRenderMark) { HighlightUnitIdSetRenderMark = false; foreach (var uid in HighlightUnitIdSet) if(ROUnitMap.TryGetValue(uid, out var unit)) unit.RenderUpdataHighlight(); HighlightUnitIdSet.Clear(); } //处理CityMap或者UnitMap新增/删除的city和unit if (_mapData.CityMap.CityMapRenderMark) { _mapData.CityMap.CityMapRenderMark = false; RenderUpdateCityMap(); } //-------- 处理子节点的Update -------// foreach (var roUnit in ROUnitMap.Values) roUnit.Update(); foreach (var roGrid in ROGridMap.Values) roGrid.Update(); foreach (var roCityInfo in ROCityInfoMap.Values) roCityInfo.Update(); //伤害预测悬停检测 UpdateDamagePreview(); } #region [-------------------- Damage Preview --------------------] private void UpdateDamagePreview() { // 前提:有选中的己方单位 if (SelectUnitData == null || !SelectUnitData.IsAlive()) { if (_dmgPreviewShowing) ClearDamagePreview(); return; } // 获取鼠标当前grid if (Camera.main == null) return; Vector3 mouseWorldPos = Camera.main.ScreenToWorldPoint(Input.mousePosition); Vector2Int cellPos = Table.Instance.WorldToGrid(mouseWorldPos); if (cellPos.x < 0 || cellPos.x >= _mapData.MapConfig.Width || cellPos.y < 0 || cellPos.y >= _mapData.MapConfig.Height) { if (_dmgPreviewShowing) ClearDamagePreview(); return; } _mapData.GridMap.GetGridDataByPos(cellPos.x, cellPos.y, out var hoverGrid); if (hoverGrid == null) { if (_dmgPreviewShowing) ClearDamagePreview(); return; } // 格子变了 → 清除并重置计时 if (hoverGrid.Id != _dmgPreviewHoverGridId) { if (_dmgPreviewShowing) ClearDamagePreview(); _dmgPreviewHoverGridId = hoverGrid.Id; _dmgPreviewHoverTimer = 0f; return; } // 已经在显示中,不需要重复计算 if (_dmgPreviewShowing) return; // 累加计时 _dmgPreviewHoverTimer += Time.deltaTime; if (_dmgPreviewHoverTimer < DmgPreviewHoverThreshold) return; // 到达0.5s,检查是否有敌方可见单位 var selfPlayer = _mapData.PlayerMap.SelfPlayerData; if (!hoverGrid.VisibleUnit(_mapData, selfPlayer, out var targetUnit)) return; // 必须是敌方单位(非同盟) if (_mapData.SameUnionByUnitId(SelectUnitData.Id, targetUnit.Id)) return; // 必须在视野内 if (!selfPlayer.Sight.CheckIsInSight(hoverGrid.Id)) return; // 通过DeepCopy map + 模拟真实攻击来获取准确伤害预测 int attackerHpBefore = SelectUnitData.Health; int defenderHpBefore = targetUnit.Health; AIActionScoreCalculator.RefreshCalMap(_mapData); var calMap = AIActionScoreCalculator.CalMap; calMap.DeepCopy(_mapData); if (!calMap.GetPlayerDataByUnitId(SelectUnitData.Id, out var calPlayer)) return; if (!calMap.UnitMap.GetUnitDataByUnitId(SelectUnitData.Id, out var calAttacker)) return; if (!calMap.UnitMap.GetUnitDataByUnitId(targetUnit.Id, out var calDefender)) return; var attackId = new CommonActionId { ActionType = CommonActionType.UnitAttack }; var attackAction = new UnitAttackAction(attackId); var param = new CommonActionParams( mapData: calMap, playerData: calPlayer, unitData: calAttacker, targetUnit: calDefender, mainObjectType: MainObjectType.Unit ); param.OnParamChanged(); attackAction.ExecuteWithoutFullActionPeriod(param); // 从模拟结果读取双方实际伤害 int dmgToTarget = defenderHpBefore - (calDefender.IsAlive() ? calDefender.Health : 0); int dmgToSelf = attackerHpBefore - (calAttacker.IsAlive() ? calAttacker.Health : 0); // 显示:敌方头上显示我方对其伤害(红色) if (dmgToTarget > 0 && ROUnitMap.TryGetValue(targetUnit.Id, out var targetRenderer)) targetRenderer.ShowDamagePreview(dmgToTarget, Color.red); // 己方头上显示反击伤害(橙色) if (dmgToSelf > 0 && ROUnitMap.TryGetValue(SelectUnitData.Id, out var selfRenderer)) selfRenderer.ShowDamagePreview(dmgToSelf, new Color(1f, 0.5f, 0f)); _dmgPreviewShowing = true; _dmgPreviewSelfUnitId = SelectUnitData.Id; _dmgPreviewTargetUnitId = targetUnit.Id; } public void ClearDamagePreview() { if (!_dmgPreviewShowing) return; if (ROUnitMap.TryGetValue(_dmgPreviewTargetUnitId, out var targetRenderer)) targetRenderer.HideDamagePreview(); if (ROUnitMap.TryGetValue(_dmgPreviewSelfUnitId, out var selfRenderer)) selfRenderer.HideDamagePreview(); _dmgPreviewShowing = false; _dmgPreviewSelfUnitId = 0; _dmgPreviewTargetUnitId = 0; } #endregion //当gridMap出现新的对象时,新建对象 public void RenderUpdateGridMap() { foreach (var gridData in Main.MapData.GridMap.GridList) { ROGridMap[gridData.Id] = new GridRenderer(_gridPrefab, _gridRenderMap, gridData.Id, Main.MapData, _main); } } //当cityMap出现新的对象时,新建对象 public void RenderUpdateCityMap() { foreach (var cityData in Main.MapData.CityMap.CityList) if(!ROCityInfoMap.ContainsKey(cityData.Id)) { //生成城镇图像 RenderUpdateCityBuildings(cityData.Id); //生成城镇名称和人口条 ROCityInfoMap[cityData.Id] = new CityInfoRenderer(_cityInfoPrefab,_cityInfoRenderMap,cityData.Id,Main.MapData,_main); cityData.SetCityRenderer(Main.MapData); } } public void UpdateCityInfoAllCoinTech() { foreach (var cityInfo in ROCityInfoMap.Values) cityInfo.InstantUpdateCityCoinTechCulture(); } //当unitMap出现新的对象时,新建对象 public void RenderUpdateUnitMap(bool showoffNewUnit = true) { foreach (var unitData in Main.MapData.UnitMap.UnitList) { if (unitData == null) continue; if(!ROUnitMap.ContainsKey(unitData.Id)) { //生成单位图像 var unitRenderer = new UnitRenderer(_unitPrefab,_unitRenderMap,unitData.Id); if (!unitRenderer.IsValid) continue; ROUnitMap[unitData.Id] = unitRenderer; //立刻更新每个unit的视觉 if (showoffNewUnit) unitRenderer.InstantUpdateUnit(true); else unitRenderer.InstantDisappear(); } } } public void RepairUnitRenderersToData(MapData mapData, List rendererOnlyUnitIds, List dataOnlyUnitIds) { if (mapData != Main.MapData || mapData?.UnitMap == null || ROUnitMap == null) return; if (PresentationManager.Busy) return; if (rendererOnlyUnitIds != null) { foreach (var unitId in rendererOnlyUnitIds) { if (!ROUnitMap.TryGetValue(unitId, out var orphanRenderer)) continue; if (orphanRenderer != null) orphanRenderer.Die(); else ROUnitMap.Remove(unitId); } } if (dataOnlyUnitIds != null) { foreach (var unitId in dataOnlyUnitIds) { if (!mapData.UnitMap.GetUnitDataByUnitId(unitId, out var unitData) || unitData == null) continue; if (ROUnitMap.TryGetValue(unitId, out var existingRenderer)) { if (existingRenderer != null && existingRenderer.IsValid) continue; if (existingRenderer != null) existingRenderer.Die(); else ROUnitMap.Remove(unitId); } var unitRenderer = new UnitRenderer(_unitPrefab, _unitRenderMap, unitData.Id); if (!unitRenderer.IsValid) continue; ROUnitMap[unitData.Id] = unitRenderer; unitRenderer.InstantUpdateUnit(true); } } if (SelectUnitData != null && !mapData.UnitMap.GetUnitDataByUnitId(SelectUnitData.Id, out _)) SelectUnitData = null; } //当projectileMap出现新的对象时,新建对象 public void RenderUpdateProjectileMap() { } // 初次渲染地图 public void FirstRenderMap() { //初次渲染grid RenderUpdateGridMap(); //初次渲染city RenderUpdateCityMap(); //初次渲染所有unit RenderUpdateUnitMap(); } // 一键修复:当 UnitRenderer / CityInfoRenderer / GridRenderer 与数据层不一致时 // (孤儿渲染器、漏创建的渲染器、贴图/血量显示错位等),调用此方法可重建一致性。 // 供设置面板的"修复画面"按钮调用。 public void RepairAllRenderers() { if (Main.MapData == null) return; var mapData = Main.MapData; // ---------- UnitRenderer ---------- // 1) 清除孤儿:ROUnitMap 中存在但数据层已删除的单位 var unitOrphans = new List(); foreach (var kv in ROUnitMap) { if (!mapData.UnitMap.GetUnitDataByUnitId(kv.Key, out _)) unitOrphans.Add(kv.Key); } foreach (var id in unitOrphans) { if (ROUnitMap.TryGetValue(id, out var orphan)) orphan.Die(); // Die() 内部会从 ROUnitMap 移除并 Destroy } // 2) 补齐缺失:数据层有但 ROUnitMap 没有的 foreach (var unitData in mapData.UnitMap.UnitList) { if (unitData == null) continue; if (!ROUnitMap.ContainsKey(unitData.Id)) { var unitRenderer = new UnitRenderer(_unitPrefab, _unitRenderMap, unitData.Id); if (unitRenderer.IsValid) ROUnitMap[unitData.Id] = unitRenderer; } } // 3) 刷新所有存活单位的视觉(位置 + 图像 + 信息) foreach (var unitData in mapData.UnitMap.UnitList) { if (ROUnitMap.TryGetValue(unitData.Id, out var ur)) { ur.InstantUpdateUnitPos(); ur.InstantUpdateUnit(true); ur.RenderUpdateUnitInfo(); ur.SyncStatusWithUnitSkills(); } } // ---------- CityInfoRenderer ---------- // 1) 清除孤儿 var cityOrphans = new List(); foreach (var kv in ROCityInfoMap) { if (!mapData.CityMap.GetCityById(kv.Key, out _)) cityOrphans.Add(kv.Key); } foreach (var id in cityOrphans) { if (ROCityInfoMap.TryGetValue(id, out var orphan)) { orphan.Dispose(); ROCityInfoMap.Remove(id); } } // 2) 补齐缺失 + 刷新全部(复用 RenderUpdateCityMap 的补齐逻辑) RenderUpdateCityMap(); foreach (var cityInfo in ROCityInfoMap.Values) { cityInfo.InstantUpdateCityInfo(); cityInfo.InstantUpdateCityCoinTechCulture(); } // ---------- GridRenderer ---------- // 格子数量不变,逐个刷新显示 + 边界 foreach (var gridData in mapData.GridMap.GridList) { if (ROGridMap.TryGetValue(gridData.Id, out var gr)) { gr.InstantUpdateGrid(); gr.UpdateBorder(); } } // ---------- 残余交互状态 ---------- HighlightGridIdSet.Clear(); HighlightGridIdSetRenderMark = true; HighlightUnitIdSet.Clear(); HighlightUnitIdSetRenderMark = true; SelectUnitData = null; ClearDamagePreview(); LogSystem.LogInfo($"[RepairAllRenderers] done. units={ROUnitMap.Count} cities={ROCityInfoMap.Count} grids={ROGridMap.Count}"); } public void RefreshVisualTheme() { if (Main.MapData == null) return; foreach (var gridData in Main.MapData.GridMap.GridList) { if (ROGridMap.TryGetValue(gridData.Id, out var gridRenderer)) gridRenderer.InstantUpdateGrid(); } foreach (var unitRenderer in ROUnitMap.Values) unitRenderer.InstantUpdateUnit(true); foreach (var cityInfo in ROCityInfoMap.Values) cityInfo.InstantUpdateCityInfo(); } public void RenderUpdateBorders() //更新全地图的所有边界 { foreach (var gridData in Main.MapData.GridMap.GridList) ROGridMap[gridData.Id].UpdateBorder(); } private void RenderUpdateCityBuildings(uint cityId) //更新主城建筑的渲染 { Main.MapData.GetGridIdByCityId(cityId, out var gridId); ROGridMap[gridId].RenderUpdateCityBuilding(cityId); } public bool SetUnitAllMoveAttackTargetHighlight(uint uid) //渲染所有可移动位置的高亮,其中可以攻击的位置要标红,如果是自己人或者敌人在移动范围内但是不在攻击范围内,则不能高亮 { //Step #1 计算MoveInfo bool ret = false; Main.UnitLogic.CalcUnitMoveInfo(Main.MapData, uid); Main.MapData.UnitMap.GetUnitDataByUnitId(uid, out var unitData); Main.MapData.GetGridDataByUnitId(uid, out var gridData); Main.MapData.GetPlayerDataByUnitId(uid, out var playerData); //unitLogic.DebugOutputMoveInfo(); int r = Mathf.Max((Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unitData.UnitType,unitData.GiantType,unitData.UnitLevel,out var info)?info.MoveRange:0) * 3 + 1, unitData.GetAttackRange(Main.MapData) * 2); //Step #2 遍历所有范围内格子,逐个判定是attack move还是别的 //Step #2 - 1 如果有citytransport或者是allytransport或者moriyaknightmove,要把r设置为全图大小 if(unitData.IsCanTransport()) r = (int)(_mapData.MapConfig.Width > _mapData.MapConfig.Height ? _mapData.MapConfig.Width : _mapData.MapConfig.Height); var targetGridDataList = Main.MapData.GridMap.GetAroundGridDataSet_NOCENTER(r,r,gridData); //Step #2 - 2 特殊处理kanako if (unitData.GetSkill(SkillType.KANAKOBATTLEFIELDPRO, out var _) && unitData.GetSkill(SkillType.KANAKOSITTING, out var _)) { foreach (var tmpunit in Main.MapData.UnitMap.UnitList) if(tmpunit.Player(Main.MapData) == playerData && (tmpunit.GiantType != GiantType.None || tmpunit.UnitType == UnitType.MoriyaHebi)) { if (tmpunit.UnitType == UnitType.MoriyaHebi && tmpunit.UnitLevel < 3) continue; if (tmpunit.Grid(Main.MapData).Feature != TerrainFeature.Mountain) continue; var tmpSet = Main.MapData.GridMap.GetAroundGridDataSet_NOCENTER(1,1,tmpunit.Grid(Main.MapData)); foreach(var tmpGrid in tmpSet) targetGridDataList.Add(tmpGrid); } } bool hasUtsuhoBase = unitData.GetSkill(SkillType.UtsuhoBase, out var _); foreach(var targetGridData in targetGridDataList) { //如果不在视野 跳过(UtsuhoBase无视视野限制) if (!hasUtsuhoBase && !playerData.Sight.CheckIsInSight(targetGridData.Id)) continue; // --------------------------------------------------------------------------------------------------------别找了 在这呢 -------------------------------------------------------------------------------------------------------- //获得该格子具体的可操作情况(移动?攻击?友方施法?) var sig = Main.UnitLogic.CheckUnitCanMoveOrAttack(Main.MapData, unitData, targetGridData); //如果是移动目标,且unit的MP>0 if ((sig is MoveAttackType.Move or MoveAttackType.MoveToPort or MoveAttackType.MoveAshore or MoveAttackType.MoveTeleport) && unitData.GetActionPoint(ActionPointType.Move) > 0) { ret = true; ROGridMap[targetGridData.Id].SetMoveHighlight(true); } //如果是攻击目标,且unit的AP>0 if (sig == MoveAttackType.Attack && unitData.GetActionPoint(ActionPointType.Attack) > 0 && !unitData.IsLimitSelfAttack(Main.MapData)) { ret = true; if (!targetGridData.MainSelfPlayerVisibleUnit( out var unitDataB)) continue; //如果可以杀死,做一个提示 if (Table.Instance.CalcDamage(Main.MapData, unitData, unitDataB) >= unitDataB.Health) { targetGridData.Renderer(Main.MapData)?.PlayVFXInSight(new GridVFXParams(GridVFXType.DieHint),GridVFXPlayType.Play); } //如果会被杀死 else if(Main.UnitLogic.CanCounter(Main.MapData, unitData, unitDataB) && Table.Instance.CalcCounterDamage(Main.MapData, unitData,unitDataB) >= unitData.Health && unitData.UnitFullType.UnitType != UnitType.Minder) { targetGridData.Renderer(Main.MapData)?.PlayVFXInSight(new GridVFXParams(GridVFXType.CounterDieHint),GridVFXPlayType.Play); } ROUnitMap[unitDataB.Id].SetAttackHighlight(true); } //如果是友军目标,且unit的AP>0 if (sig == MoveAttackType.Ally && unitData.GetActionPoint(ActionPointType.Attack) > 0) { ret = true; if (!targetGridData.MainSelfPlayerVisibleUnit(out var unitDataB)) continue; ROUnitMap[unitDataB.Id].SetAllyHighlight(true); } //如果是攻击ground目标,且unit的AP>0 if (sig == MoveAttackType.AttackGround && unitData.GetActionPoint(ActionPointType.Attack) > 0) { ret = true; ROGridMap[targetGridData.Id].SetAttackHighlight(true); } // UtsuhoBase特殊处理:移动范围内有单位的格子也显示可移动高亮 if (sig == MoveAttackType.None && unitData.GetSkill(SkillType.UtsuhoBase, out var _) && unitData.GetActionPoint(ActionPointType.Move) > 0 && Main.UnitLogic.IsFinalReachable(targetGridData.Pos.X, targetGridData.Pos.Y)) { ret = true; ROGridMap[targetGridData.Id].SetMoveHighlight(true); } } return ret; } //返回一个Unit是否存在移动或者攻击目标 public bool CheckUnitHasMoveAttackTarget(uint uid) { Main.UnitLogic.CalcUnitMoveInfo(Main.MapData, uid); Main.MapData.UnitMap.GetUnitDataByUnitId(uid, out var unitData); Main.MapData.GetGridDataByUnitId(uid, out var gridData); Main.MapData.GetPlayerDataByUnitId(uid, out var playerData); if (unitData == null || gridData == null || playerData == null) return false; if (unitData.GetActionPoint(ActionPointType.Attack) <= 0 && unitData.GetActionPoint(ActionPointType.Move) <= 0) return false; //unitLogic.DebugOutputMoveInfo(); int r = Mathf.Max((Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unitData.UnitType,unitData.GiantType,unitData.UnitLevel,out var info)?info.MoveRange:0) * 2, Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unitData.UnitType,unitData.GiantType,unitData.UnitLevel,out var info2)?info2.AttackRange:0); _aroundBuf ??= new List(); _aroundBuf.Clear(); Main.MapData.GridMap.GetAroundGridDataSet_NOCENTER(r,r,gridData, _aroundBuf); bool hasUtsuhoBase2 = unitData.GetSkill(SkillType.UtsuhoBase, out var _); foreach(var targetGridData in _aroundBuf) { //如果不在视野 跳过(UtsuhoBase无视视野限制) if (!hasUtsuhoBase2 && !playerData.Sight.CheckIsInSight(targetGridData.Id)) continue; var sig = Main.UnitLogic.CheckUnitCanMoveOrAttack(Main.MapData, unitData, targetGridData); //如果是移动目标,且unit的MP>0 if ((sig == MoveAttackType.Move || sig == MoveAttackType.MoveToPort || sig == MoveAttackType.MoveAshore) && unitData.GetActionPoint(ActionPointType.Move) > 0) return true; //如果是攻击目标,且unit的AP>0 if (sig == MoveAttackType.Attack && unitData.GetActionPoint(ActionPointType.Attack) > 0 && !unitData.IsLimitSelfAttack(Main.MapData)) return true; //如果是Ally目标,且unit的AP>0 if (sig == MoveAttackType.Ally && unitData.GetActionPoint(ActionPointType.Attack) > 0 ) return true; //如果是AttackGround,且unit的AP>0 if (sig == MoveAttackType.AttackGround && unitData.GetActionPoint(ActionPointType.Attack) > 0 ) return true; // UtsuhoBase特殊处理:移动范围内有单位的格子也视为有目标 if (sig == MoveAttackType.None && unitData.GetSkill(SkillType.UtsuhoBase, out var _) && unitData.GetActionPoint(ActionPointType.Move) > 0 && Main.UnitLogic.IsFinalReachable(targetGridData.Pos.X, targetGridData.Pos.Y)) return true; } return false; } //返回一个Unit是否当前可以占领遗迹/城市/收集starfish public bool CheckUnitHasSpecialUnitActionTarget(uint uid) { if (!_mapData.UnitMap.GetUnitDataByUnitId(uid, out var unit)) return false; if (!_mapData.GetGridDataByUnitId(uid, out var grid)) return false; if (!_mapData.GetPlayerDataByUnitId(uid, out var player)) return false; if (unit.GetActionPoint(ActionPointType.Capture) <= 0) return false; //宝藏 if (grid.Resource == ResourceType.Treasure) return true; //海星 if (grid.Resource == ResourceType.Starfish && player.TechTree.CheckIfHasTech(TechType.Navigation)) return true; //城市 if (grid.Resource == ResourceType.CityCenter) { _mapData.GetPlayerDataByTerritoryGridId(grid.Id, out var gridPlayer); //村庄或者他国城市 if (gridPlayer == null) return true; if (gridPlayer.Id != player.Id) return true; } return false; } // 创建临时探索者并向视野最少的方向移动 public void CreateTemporaryExplorer(MapData map,GridData gridData, float maxDuration) { // Step #1 鲁棒性判断 if (!Main.MapData.GetPlayerDataByTerritoryGridId(gridData.Id, out var playerData)) { Debug.LogWarning("无法获取玩家数据,无法创建探索者"); return; } // Step #2 如果不是自己的玩家,直接计算10步探索并更新视野,不进行渲染 // 重置移动计数和当前目标格子 _explorerMoveCount = 0; _currentTargetGridData = gridData; _explorerPreviousPosition = new Vector2Int(-1, -1); GameObject spriteObject; //如果是真人玩家,提前设置对象 if (playerData.IsSelfPlayer()) { _temporaryExplorer = new GameObject("TemporaryExplorer"); _temporaryExplorer.transform.position = Table.Instance.GridToWorld(gridData, "isUnit"); _temporaryExplorer.transform.SetParent(_unitRenderMap, false); spriteObject = new GameObject("UnitSprite"); spriteObject.transform.SetParent(_temporaryExplorer.transform, false); SpriteRenderer spriteRenderer = spriteObject.AddComponent(); //把小人图像位置下移一点 spriteObject.transform.localScale = new Vector3(0.8f, 0.8f, 0.8f); var t = spriteObject.transform.localPosition; t.y -= 0.4f; spriteObject.transform.localPosition = t; // 设置explorer的sprite UnitType explorerUnitType = UnitType.Warrior; // 使用战士单位类型 if (Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(explorerUnitType, GiantType.None, 0, out var unitTypeInfo) && Table.Instance.UnitTypeDataAssets.GetUnitSpriteByInfo(unitTypeInfo, playerData, out var sprite)) spriteRenderer.sprite = sprite; } // 直接计算15步探索 for (int i = 0; i < _explorerMaxMoves; i++) { // 如果当前目标格子为空,结束探索 if (_currentTargetGridData == null) break; // 计算下一步移动 Vector2Int currentPos = new Vector2Int(_currentTargetGridData.Pos.X, _currentTargetGridData.Pos.Y); // 保存当前位置作为上一个位置 Vector2Int prevPos = currentPos; _explorerPreviousPosition = prevPos; // 计算下一步移动方向(不渲染,只计算) CalculateNextExplorerMove(map,_currentTargetGridData, playerData, out var nextGridData); //Step #5 处理视觉(只有自己玩家才会做 if (playerData.IsSelfPlayer()) { // 如果无法移动,结束探索 TODO 有一点点风险,之后处理吧 现在无法移动就改成原地移动了 if (nextGridData == null) nextGridData = _currentTargetGridData; bool lastMove = i + 1 == _explorerMaxMoves; FragmentMoveExplorerType moveType = FragmentMoveExplorerType.NormalMove; if(i == 0) moveType = FragmentMoveExplorerType.FirstMove; if (i + 1 == _explorerMaxMoves) moveType = FragmentMoveExplorerType.LastMove; var data = FragmentDataFactory.Create(FragmentType.MoveExplorer,_temporaryExplorer,_currentTargetGridData,nextGridData,moveType); PresentationManager.EnqueueTask(new FragmentSequencerTask(FragmentFactory.Create(FragmentType.MoveExplorer,data))); } //Step #6 更新视野 var radius = 1; if (nextGridData.Feature == TerrainFeature.Mountain) radius = 2; Main.PlayerLogic.UpdateSightByRadius_LogicView(Main.MapData, playerData, nextGridData, radius); // 更新当前目标格子 _currentTargetGridData = nextGridData; _explorerMoveCount++; } // 完成探索后解锁输入 ,只有是玩家才需要解锁输入 if(playerData.Id == Main.MapData.PlayerMap.SelfPlayerId) _main.InputLogic.UnlockInput(); } // 修复GetDirectionIndex方法中的错误 private int GetDirectionIndex(int dx, int dy) { // 将dx和dy归一化为-1, 0, 1 int nx = dx == 0 ? 0 : (dx > 0 ? 1 : -1); int ny = dy == 0 ? 0 : (dy > 0 ? 1 : -1); // 根据归一化后的方向确定索引 switch (nx) { case 1: switch (ny) { case 1: return 1; // 右上 case 0: return 0; // 右 case -1: return 7; // 右下 } break; case 0: switch (ny) { case 1: return 2; // 上 case -1: return 6; // 下 } break; case -1: switch (ny) { case 1: return 3; // 左上 case 0: return 4; // 左 case -1: return 5; // 左下 } break; } // 默认返回右方向 return 0; } // 根据方向索引获取移动方向 private Vector2Int GetMoveDirectionFromIndex(int dirIndex) { switch (dirIndex) { case 0: return new Vector2Int(1, 0); // 右 case 1: return new Vector2Int(1, 1); // 右上 case 2: return new Vector2Int(0, 1); // 上 case 3: return new Vector2Int(-1, 1); // 左上 case 4: return new Vector2Int(-1, 0); // 左 case 5: return new Vector2Int(-1, -1); // 左下 case 6: return new Vector2Int(0, -1); // 下 case 7: return new Vector2Int(1, -1); // 右下 default: return new Vector2Int(0, 0); // 默认不移动 } } // -------------------------- 以下是一组给explorere计算使用的参数 ---------------------------- private bool CheckCanMove(PlayerData player, Vector2Int pos) { if (!Main.MapData.GridMap.GetGridDataByPos(pos.x, pos.y, out var grid)) return false; return Main.UnitLogic.CheckUnitAbleForGrid_OfflineStatus(Main.MapData,player,null, grid); } //返回一个格子的"探索值“,即最近的未探索格子的寻路距离 private int CalcExploreValue(MapData map,PlayerData player,Vector2Int startPos) { int[,] CanMove = new int[map.MapConfig.Width, map.MapConfig.Height]; for(int i = 0;i < map.MapConfig.Width;i++) for (int j = 0; j < map.MapConfig.Height; j++) CanMove[i, j] = -1; // 2. 边界和起始点有效性检查 if (startPos.x < 0 || startPos.x >= 30 || startPos.y < 0 || startPos.y >= 30 || !CheckCanMove(player,startPos)) { return 1000 ; // 起始点不合法,直接返回 } // 3. 初始化队列并将起点加入 var queue = new Queue(); queue.Enqueue(startPos); CanMove[startPos.x, startPos.y] = 0; // 标记起点已访问 // 4. BFS主循环 while (queue.Count > 0) { var current = queue.Dequeue(); // 检查8个方向的邻居 for (int i = 0; i < 8; i++) { var dir = GetMoveDirectionFromIndex(i); var neighbor = current + dir; // 5. 检查邻居的合法性:在界内、之前未访问过、且可以通过CheckCan if (neighbor.x < 0 || neighbor.x >= map.MapConfig.Width || neighbor.y < 0 || neighbor.y >= map.MapConfig.Height) continue; if (CanMove[neighbor.x, neighbor.y] != -1) continue; if (!map.GridMap.GetGridDataByPos(neighbor.x, neighbor.y, out var gridData)) continue; //找到一个没有开视野的格子,返回! if (!player.Sight.CheckIsInSight(gridData.Id)) { return CanMove[current.x, current.y]; } if(CheckCanMove(player, neighbor)) { CanMove[neighbor.x, neighbor.y] = CanMove[current.x, current.y] + 1; // 标记为可达/已访问 queue.Enqueue(neighbor); // 加入队列等待处理 } } } return 999; } // -------------------------- 以上 ---------------------------- // 新增辅助方法:计算下一步移动但不执行渲染 private bool CalculateNextExplorerMove(MapData map,GridData startGridData, PlayerData playerData, out GridData nextGridData) { nextGridData = null; // 当前位置 Vector2Int currentPos = new Vector2Int(startGridData.Pos.X, startGridData.Pos.Y); // 地图尺寸 int mapWidth = (int)Main.MapData.MapConfig.Width; int mapHeight = (int)Main.MapData.MapConfig.Height; // 存储每个方向的最近未探索格子距离 float[] directionMinDistance = new float[8]; for (int i = 0; i < 8; i++) { directionMinDistance[i] = float.MaxValue; // 初始化为最大值 } // 找出未探索格子最近的方向 int minDistance = 1000; List bestDirections = new List(); for (int i = 0; i < 8; i++) { Vector2Int dirVector = GetMoveDirectionFromIndex(i); Vector2Int nextPos = currentPos + dirVector; //Step #1 该方向首先得能走,不能走直接continue if (!Main.MapData.GridMap.GetGridDataByPos(nextPos.x, nextPos.y, out var tempNextGridData)) continue; if(!IsTerrainPassable(tempNextGridData, playerData)) continue; //Step #2 获得改方向的探索值 int v = CalcExploreValue(map, playerData, nextPos); if (v < minDistance) { bestDirections.Clear(); bestDirections.Add(i); minDistance = v; } else if(v == minDistance) { bestDirections.Add(i); } } // 如果没有找到最佳方向,尝试任何可行方向(除了上一个位置) if (bestDirections.Count == 0) { for (int i = 0; i < 8; i++) { // 计算该方向的下一个位置 Vector2Int dirVector2 = GetMoveDirectionFromIndex(i); Vector2Int nextPos = currentPos + dirVector2; // 检查该位置是否在地图范围内 if (nextPos.x < 0 || nextPos.x >= mapWidth || nextPos.y < 0 || nextPos.y >= mapHeight) continue; // 检查该位置是否是上一个位置 /*if (_explorerPreviousPosition.x == nextPos.x && _explorerPreviousPosition.y == nextPos.y) continue;*/ // 检查该位置是否可通行 GridData tempNextGridData; if (!Main.MapData.GridMap.GetGridDataByPos(nextPos.x, nextPos.y, out tempNextGridData)) continue; // 检查地形是否可通行 if (!IsTerrainPassable(tempNextGridData, playerData)) continue; bestDirections.Add(i); } } // 如果仍然没有找到可行方向,返回失败 if (bestDirections.Count == 0) { return false; } // 随机选择一个最佳方向 var value = map.Net.GetRandom(map).Next(0, bestDirections.Count); int selectedDirIndex = bestDirections[value]; // 根据选择的方向确定移动的目标位置 Vector2Int finalMoveDir = GetMoveDirectionFromIndex(selectedDirIndex); Vector2Int targetPos = currentPos + finalMoveDir; // 获取目标格子数据 return Main.MapData.GridMap.GetGridDataByPos(targetPos.x, targetPos.y, out nextGridData); } // 新增方法:检查地形是否可通行 private bool IsTerrainPassable(GridData gridData, PlayerData playerData) { // 检查地形是否可通行 if (gridData.Terrain == TerrainType.DeepSea && !playerData.TechTree.CheckIfHasTechAtom(TechAtom.UnitSkillOCEANMOVE)) return false; if (gridData.Terrain == TerrainType.ShallowSea && !playerData.TechTree.CheckIfHasTechAtom(TechAtom.UnitSkillWATERMOVE)) return false; if (gridData.Feature == TerrainFeature.Mountain && !playerData.TechTree.CheckIfHasTechAtom(TechAtom.UnitSkillMOUNTAINMOVE)) return false; return true; } // 新增方法:使用BFS检查格子是否可达 private bool IsGridReachable(GridData startGrid, GridData targetGrid, PlayerData playerData, int maxSteps) { // 如果起点和终点相同,直接返回true if (startGrid.Id == targetGrid.Id) return true; // 如果目标格子地形不可通行,直接返回false if (!IsTerrainPassable(targetGrid, playerData)) return false; // 使用BFS搜索 Queue queue = new Queue(); HashSet visited = new HashSet(); Dictionary steps = new Dictionary(); queue.Enqueue(startGrid); visited.Add(startGrid.Id); steps[startGrid.Id] = 0; while (queue.Count > 0) { GridData current = queue.Dequeue(); int currentSteps = steps[current.Id]; // 如果已经超过最大步数,跳过 if (currentSteps >= maxSteps) continue; // 获取当前格子的八个方向的相邻格子 Vector2Int currentPos = new Vector2Int(current.Pos.X, current.Pos.Y); for (int i = 0; i < 8; i++) { Vector2Int dirVector = GetMoveDirectionFromIndex(i); Vector2Int nextPos = currentPos + dirVector; // 检查位置是否在地图范围内 if (nextPos.x < 0 || nextPos.x >= Main.MapData.MapConfig.Width || nextPos.y < 0 || nextPos.y >= Main.MapData.MapConfig.Height) continue; // 获取相邻格子数据 if (!Main.MapData.GridMap.GetGridDataByPos(nextPos.x, nextPos.y, out var nextGrid)) continue; // 如果已经访问过,跳过 if (visited.Contains(nextGrid.Id)) continue; // 检查地形是否可通行 if (!IsTerrainPassable(nextGrid, playerData)) continue; // 如果找到目标格子,返回true if (nextGrid.Id == targetGrid.Id) return true; // 将相邻格子加入队列 queue.Enqueue(nextGrid); visited.Add(nextGrid.Id); steps[nextGrid.Id] = currentSteps + 1; } } // 搜索完毕仍未找到目标格子,返回false return false; } public bool UpdateAroundHighlight(MapData mapData, GridData grid) { //如果不是真map,不操作 if (mapData != Main.MapData) return false; // 使用局部List,避免与CheckUnitHasMoveAttackTarget等方法共享static _aroundBuf导致foreach中集合被修改 var localBuf = new List(); mapData.GridMap.GetAroundGridDataSet_NOCENTER(3, 3, grid, localBuf); foreach (var aroundGrid in localBuf) { //如果是玩家的unit 那么更新高亮 if (aroundGrid.MainSelfPlayerVisibleUnit( out var unit) && mapData.GetPlayerIdByUnitId(unit.Id,out var pid) && pid == mapData.PlayerMap.SelfPlayerId) unit.Renderer(mapData)?.InstantUpdateUnit(true); } return true; } } }