2026-04-11 15:10:40 +08:00

1014 lines
44 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using RuntimeData;
using Logic;
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 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<uint, GridRenderer> ROGridMap;
public Dictionary<uint, CityInfoRenderer> ROCityInfoMap;
public Dictionary<uint, UnitRenderer> 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交互部分 --------//
//移动和攻击的高亮RenderDataset中存放了所有需要更新的对象
public HashSet<uint> HighlightGridIdSet = new HashSet<uint>();
public bool HighlightGridIdSetRenderMark = false;
public HashSet<uint> HighlightUnitIdSet = new HashSet<uint>();
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 = 0.5f;
// 添加移动相关的成员变量
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 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<uint, GridRenderer>();
ROCityInfoMap = new Dictionary<uint, CityInfoRenderer>();
ROUnitMap = new Dictionary<uint, UnitRenderer>();
// 初始化主要的渲染模块(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)
{
Dispose();
Initialize(main,mapData);
_instance.InGameBubbleManager.OnGameStart();
}
//InGame游戏结束时的生命周期
public void OnMatchEnd()
{
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<Transform>();
//清理战场把上一局Grid里面留下的东西清理干净
ClearAllChildren(_unitRenderMap);
ClearAllChildren(_gridRenderMap);
ClearAllChildren(_cityInfoRenderMap);
ClearAllChildren(HintRenderMap);
ClearAllChildren(ROMap.transform.Find("ProjectileMap"));
_unitPrefab = Resources.Load<GameObject>($"Prefab/unitPrefab");
_gridPrefab = Resources.Load<GameObject>($"Prefab/tilePrefab");
_cityInfoPrefab = Resources.Load<GameObject>($"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<CameraController>();
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;
// 计算预测伤害
int myDmg = Table.Instance.CalcDamage(_mapData, SelectUnitData, targetUnit);
int counterDmg = Table.Instance.CalcCounterDamage(_mapData, SelectUnitData, targetUnit);
bool canCounter = Main.UnitLogic.CanCounter(_mapData, SelectUnitData, targetUnit);
// 显示:敌方头上显示我方对其伤害(红色),我方头上显示反击伤害(橙色)
if (ROUnitMap.TryGetValue(targetUnit.Id, out var targetRenderer))
targetRenderer.ShowDamagePreview(myDmg, Color.red);
if (canCounter && ROUnitMap.TryGetValue(SelectUnitData.Id, out var selfRenderer))
selfRenderer.ShowDamagePreview(counterDmg, 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()
{
foreach (var unitData in Main.MapData.UnitMap.UnitList)
if(!ROUnitMap.ContainsKey(unitData.Id))
{
//生成单位图像
ROUnitMap[unitData.Id] = new UnitRenderer(_unitPrefab,_unitRenderMap,unitData.Id);
//立刻更新每个unit的视觉
ROUnitMap[unitData.Id].InstantUpdateUnit(true);
}
}
//当projectileMap出现新的对象时新建对象
public void RenderUpdateProjectileMap()
{
}
// 初次渲染地图
public void FirstRenderMap()
{
//初次渲染grid
RenderUpdateGridMap();
//初次渲染city
RenderUpdateCityMap();
//初次渲染所有unit
RenderUpdateUnitMap();
}
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,
Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unitData.UnitType,unitData.GiantType,unitData.UnitLevel,out var info2)?info2.AttackRange:0) * 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);
var targetGridDataList = Main.MapData.GridMap.GetAroundGridDataSet_NOCENTER(r,r,gridData);
bool hasUtsuhoBase2 = unitData.GetSkill(SkillType.UtsuhoBase, out var _);
foreach(var targetGridData in targetGridDataList)
{
//如果不在视野 跳过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<SpriteRenderer>();
//把小人图像位置下移一点
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<Vector2Int>();
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<int> bestDirections = new List<int>();
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<GridData> queue = new Queue<GridData>();
HashSet<uint> visited = new HashSet<uint>();
Dictionary<uint, int> steps = new Dictionary<uint, int>();
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;
var set = mapData.GridMap.GetAroundGridDataSet_NOCENTER(3, 3, grid);
foreach (var aroundGrid in set)
{
//如果是玩家的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;
}
}
}