using System; using System.Collections; using System.Collections.Generic; using System.Linq; using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Engines; using Common; using DamageNumbersPro; using DragonLi.Core; using Mirror; using UnityEngine; using UnityEngine.PlayerLoop; using XPlugin.Data.JsonLiteDB; using static XPlugin.Data.JsonLiteDB.JsonLiteDB; using LitJson; using Pathfinding; using DarkTonic.MasterAudio; using NaughtyAttributes; using Unity.Mathematics; using UnityEngine.UIElements; using EventDispatcher = DragonLi.Core.EventDispatcher; using Random = UnityEngine.Random; /// /// 游戏状态 /// public enum GameState { None = 0, Playing = 1, /// /// 胜利 /// Victory = 2, /// /// 失败 /// Failur = 3, Settle, Waiting = 5, } public class GameManager : NetworkBehaviour { public static GameManager Ins { get; private set; } #region 预制体 public GameObject redDoorPre; public GameObject blueDoorPre; public GameObject AirdropPlanePre;//空投飞机 public GameObject[] airdropItems;//空头道具 public GameObject startGameItemPre; public GameObject damageNumberPre; public GameObject redDamageNumberPre; // 怪物ui预制体 public GameObject EnemyUIPre; public GameObject roundPlaceDoorPre; #endregion #region 实例化后物体 public GameObject redDoor; public GameObject blueDoor; #endregion // json数据库 private JsonLiteDB DB; // 怪物信息集合 public Dictionary> EnemyInfos = new Dictionary>(); // 枪械信息集合 public Dictionary> GunInfos = new Dictionary>(); /// /// 所有敌人UI /// public Dictionary EnemyUIList = new Dictionary(); public Dictionary SettleInfos = new Dictionary(); public List allPlayers = new List(); // 游玩结束时间 [NonSerialized] [SyncVar] public long vistEnd = 0; [NonSerialized] [SyncVar] public long vistRoundEnd = 0; [NonSerialized] [SyncVar] private int roundAllTime; [SyncVar] public string settleData = ""; [SyncVar] [NonSerialized] public int CurRoundIndex; [SyncVar] public GameState gameState = GameState.None; [NonSerialized] [SyncVar] public int BlueScore = 0; [NonSerialized] [SyncVar] public int RedScore = 0; [NonSerialized] [SyncVar] public int RedRoundScore = 0; [NonSerialized] [SyncVar] public int BlueRoundScore = 0; [NonSerialized] [SyncVar] public int JoinPlayerCount; [Header("指引系统")] public GameObject GuideArrowPre; // 指引箭头预制体 private GameObject guideArrowInstanceRed; // 指向红门的指引箭头实例 private GameObject guideArrowInstanceBlue; // 指向蓝门的指引箭头实例 private GuideArrowPath guideArrowComponentRed; // 红门指引箭头组件 private GuideArrowPath guideArrowComponentBlue; // 蓝门指引箭头组件 private bool isGuideArrowActive = false; private Vector3 lastPlayerPosition; // 记录玩家上一次的位置 private float updatePathThreshold = 0.5f; // 更新路径的阈值(玩家移动超过这个距离才更新路径) private float updatePathCooldown = 0.3f; // 更新路径的冷却时间 private float lastPathUpdateTime = 0f; private Vector3[] lastPathPointsRed; // 上一次的红门路径点 private Vector3[] lastPathPointsBlue; // 上一次的蓝门路径点 private bool isPathSmoothed = false; private List smoothedPathRed = new List(); private List smoothedPathBlue = new List(); // 指引系统目标位置 private Vector3 guideTargetPositionRed = Vector3.zero; private Vector3 guideTargetPositionBlue = Vector3.zero; // 是否已初始化指引系统 private bool isGuideInitialized = false; // 是否已经同步门位置 private bool isDoorPositionSynced = false; // 调试用:强制显示指引 public bool forceShowGuide = false; void Awake() { Ins = this; } void Start() { // 延迟初始化指引系统,等待网络连接 CoroutineTaskManager.Instance.WaitSecondTodo(() => { InitializeGuideArrow(); }, 2f); if (isClient) { UpdateConf(); } if (isServer) { // CreateDoor(GameLocal.Ins.redDoorPos, GameLocal.Ins.blueDoorPos, GameLocal.Ins.startGameItemPos); // // CoroutineTaskManager.Instance.WaitSecondTodo(() => // { // StartGuide(); // }, 2f); roundAllTime =GameLocal.Ins.vistAllTime / 3; } } /// /// 创建门 /// [Server] public void CreateDoor(Transform redPos, Transform bluePos, Transform startGamePos) { SetGameState(GameState.Waiting); if (!isServer) return; //创建红色门 redDoor = Instantiate(redDoorPre, redPos.position, Quaternion.identity); NetworkServer.Spawn(redDoor); //创建蓝色门 blueDoor = Instantiate(blueDoorPre, bluePos.position, Quaternion.identity); NetworkServer.Spawn(blueDoor); // 等待一帧确保物体同步,然后同步门位置 CoroutineTaskManager.Instance.WaitSecondTodo(() => { // 同步门的位置给所有客户端 RpcSyncDoorPosition(redPos.position, bluePos.position); Debug.Log("服务器:门位置已同步到客户端"); // 服务器端也初始化指引 CoroutineTaskManager.Instance.WaitSecondTodo(() => { StartGuide(); }, 0.5f); }, 0.5f); } /// /// 同步门的位置给所有客户端 /// [ClientRpc] public void RpcSyncDoorPosition(Vector3 redDoorPos, Vector3 blueDoorPos) { // 客户端接收门的位置 guideTargetPositionRed = redDoorPos; guideTargetPositionBlue = blueDoorPos; isDoorPositionSynced = true; Debug.Log("客户端:收到门位置同步,红门: " + guideTargetPositionRed + ",蓝门: " + guideTargetPositionBlue); // 如果指引系统已经初始化,立即开始指引 if (isGuideInitialized) { CoroutineTaskManager.Instance.WaitSecondTodo(() => { StartGuide(); }, 0.3f); } } [ClientRpc] public void RpcShowDamageNumber(float damage, Vector3 pos, bool isCritical) { GameObject prefab = isCritical ? redDamageNumberPre : damageNumberPre; if (prefab == null) return; GameObject obj = Instantiate(prefab, pos, Quaternion.identity); var dmg = obj.GetComponent(); if (dmg != null) { dmg.worldPos = pos; dmg.damageValue = isCritical ? damage * 2 : damage; dmg.OnStartClient(); } Destroy(obj, 10f); } [Server] public void GameStart() { Debug.Log("开始游戏"); vistEnd = (long)(DateTime.Now.Subtract(new DateTime(1970, 1, 1))).TotalSeconds + GameLocal.Ins.vistAllTime; vistRoundEnd = (long)(DateTime.Now.Subtract(new DateTime(1970, 1, 1))).TotalSeconds + roundAllTime; _airdropTime = 25; _curAirdropTime = _airdropTime; ChangeBgmRpc(1); StartAirDrop(); // 停止所有客户端的指引 RpcStopAllGuide(); // 延迟1s后给所有人发放武器 CoroutineTaskManager.Instance.WaitSecondTodo(() => { foreach (var player in allPlayers) { if (player != null) { player.HideInvincible(); ShowTeamUI(player.connectionToClient, (int)player.teamType); } } RpcShowSettle(); }, 1f); StartRoundTime(); } [TargetRpc] public void ShowTeamUI(NetworkConnectionToClient conn, int teamId) { EventDispatcher.TriggerEvent("ChangeTeam", teamId); } [Server] public void StartRoundTime() { CurRoundIndex++; CoroutineTaskManager.Instance.WaitSecondTodo(() => { if (gameState == GameState.Waiting) return; StopRound(); }, roundAllTime); } [Server] public void RegisterPlayer(Player p) { if (!allPlayers.Contains(p)) allPlayers.Add(p); } // ★ 玩家离开游戏时调用(在Player.OnStopServer里调用) [Server] public void UnRegisterPlayer(Player p) { if (allPlayers.Contains(p)) allPlayers.Remove(p); } // ★ 游戏开始时调用 → 自动分配队伍 [Server] public void AutoAssignTeams() { int redCount = 0; int blueCount = 0; // 统计已选队伍玩家 foreach (var p in allPlayers) { if (p.teamType == TeamType.Red) redCount++; else if (p.teamType == TeamType.Blue) blueCount++; } int totalPlayers = allPlayers.Count; // ----------------------------- // ★ 第一步:强制平衡队伍(如果人数>=2 且某队==0) // ----------------------------- if (totalPlayers >= 2) { if (redCount == 0 && blueCount > 0) { // 需要从蓝队分一半人到红队 int need = Mathf.Max(1, blueCount / 2); foreach (var p in allPlayers) { if (need <= 0) break; if (p.teamType == TeamType.Blue) { p.teamType = TeamType.Red; need--; redCount++; blueCount--; } } } else if (blueCount == 0 && redCount > 0) { // 需要从红队分一半人到蓝队 int need = Mathf.Max(1, redCount / 2); foreach (var p in allPlayers) { if (need <= 0) break; if (p.teamType == TeamType.Red) { p.teamType = TeamType.Blue; need--; blueCount++; redCount--; } } } } // ----------------------------- // ★ 第二步:给未分配队伍(NONE)的玩家自动分配 // ----------------------------- foreach (var p in allPlayers) { if (p.teamType != TeamType.None) continue; if (redCount < blueCount) { p.teamType = TeamType.Red; redCount++; } else if (blueCount < redCount) { p.teamType = TeamType.Blue; blueCount++; } else { // 平局 → 随机 if (Random.value > 0.5f) { p.teamType = TeamType.Red; redCount++; } else { p.teamType = TeamType.Blue; blueCount++; } } // 给队伍分配武器 GivePistol(p.index); } } [Server] public void CheckGameStart(int playerIndex) { var isStartGame = playerIndex == GameLocal.Ins.addPlayerIndex; if (isStartGame) { BlueRoundScore = 0; RedRoundScore = 0; // 停止所有客户端的指引 RpcStopAllGuide(); NetworkServer.Destroy(redDoor); NetworkServer.Destroy(blueDoor); RpcShowHUD(); MonoSingleton.Instance.WaitSecondTodo(() => { GameStart(); if (gameState == GameState.Playing) return; AutoAssignTeams(); gameState = GameState.Playing; }, 3f, this); } } [ClientRpc] public void RpcShowSettle() { GameLocal.Ins.Settle.SetActive(true); } [ClientRpc] public void GivePistol(int index) { Debug.Log("创建武器中..."); foreach (var item in NetworkServer.connections.Values) { Player player = item.identity.GetComponent(); if (player != null && player.index == index) { player.GivePistol(); player.GiveArmor(); } } } [Server] public void SetGameState(GameState state) { gameState = state; } [Server] public void GameOver() { gameState = GameState.Settle; } [ClientRpc] public void RpcShow() { SettlePanel.Show(); } [ClientRpc] public void ChangeBgmRpc(int i) { GameLocal.Ins.BGMState.StateChange(i); } [Server] public void CreateAirdropPlane(Vector3 startPos, Vector3 endPos) { GameObject airdropPlane = Instantiate(AirdropPlanePre, startPos, quaternion.identity); NetworkServer.Spawn(airdropPlane); AirdropPlane airdropPlaneScript = airdropPlane.GetComponent(); airdropPlaneScript.OnSpawn(endPos); } [Server] public void CreateAirDropItem(int id, Vector3 startPos) { GameObject airdropItem = Instantiate(airdropItems[id], startPos, quaternion.identity); NetworkServer.Spawn(airdropItem); } /// /// 痛击场上所有敌方单位 /// [Command(requiresAuthority = false)] public void DamageAllEnemy() { } /// /// 更新配置表 /// public void UpdateConf() { string text = Resources.Load("Data").text; if (text != null) { ParseGameJson(text); } } public void ParseGameJson(string text) { DB = new JsonLiteDB(); DB.Load(text); TableReader infoReader = DB["GunsInfo"].GetReader(); while (infoReader.Read()) { GunInfo info = new GunInfo(infoReader); GunInfos.TryGetValue((GunType)info.Type, out Dictionary infoList); if (infoList == null) { Dictionary list = new Dictionary(); list.Add(info.Lvl, info); GunInfos.Add((GunType)info.Type, list); } else { infoList.Add(info.Lvl, info); } } Debug.Log("游戏数值更新 -> complete"); } [Server] public void CreateEnemyUI(Enemy enemy) { GameObject enemyUI = Instantiate(EnemyUIPre); NetworkServer.Spawn(enemyUI); PlayerUI enemyUIScript = enemyUI.GetComponent(); //enemyUIScript.Init(enemy); EnemyUIList.Add(enemy.id, enemyUIScript); } [Server] public void DeleteEnemyUI(int id) { GameObject enemyUI = EnemyUIList[id].gameObject; EnemyUIList.Remove(id); NetworkServer.Destroy(enemyUI); } [ClientRpc] public void RpcShowHUD() { HUDPanel.Show(); } [Server] public void AddData(int playerIndex) { SettleInfos.Add(playerIndex.ToString(), new SettleInfo()); } #region 工具 public string GetLessTimeStr() { string res = ""; if (gameState == GameState.Playing) { res = FormatTime((int)(vistEnd - DateTime.Now.Subtract(new DateTime(1970, 1, 1)).TotalSeconds)); } return res; } public string GetRoundLessTimeStr() { string res = ""; if (gameState == GameState.Playing) { res = FormatTime((int)(vistRoundEnd - DateTime.Now.Subtract(new DateTime(1970, 1, 1)).TotalSeconds)); } return res; } public int GetLessTimeSeconds() { if (isServer) { return (int)(vistEnd - NetworkTime.time); } else { return (int)(vistEnd - NetworkClient.connection.remoteTimeStamp); } } // 时分秒 public string FormatTime(int totalSeconds) { //确保时间不为负 totalSeconds = Mathf.Max(0, totalSeconds); int hours = totalSeconds / 3600; // string hh = hours < 10 ? "0" + hours : hours.ToString(); int minutes = (totalSeconds - hours * 3600) / 60; string mm = minutes < 10f ? "0" + minutes : minutes.ToString(); int seconds = totalSeconds - hours * 3600 - minutes * 60; string ss = seconds < 10 ? "0" + seconds : seconds.ToString(); return string.Format("{0}:{1}", mm, ss); } #endregion [TargetRpc] public void PlaySound2DRPC(NetworkConnection target, string sound) { MasterAudio.PlaySound(sound); } public void PlaySound2D(string sound) { foreach (var item in allPlayers) { PlaySound2DRPC(item.connectionToClient, sound); } } [TargetRpc] public void PlaySound3DRPC(NetworkConnection target, string sound, Transform pos) { MasterAudio.PlaySound3DAtTransform(sound, pos); } public void PlaySound3D(string sound, Transform target) { foreach (var item in allPlayers) { PlaySound3DRPC(item.connectionToClient, sound, target); } } [SyncVar] private int syncRemainingTime; private float _airdropTime;//空投时间 private float _curAirdropTime;//当前空头时间 private bool _isStartAirdrop;//是否开始倒计时空投 public void StartAirDrop() { _isStartAirdrop = true; } public void StopAirDrop() { _isStartAirdrop = false; } void Update() { if (isServer && gameState == GameState.Playing && _isStartAirdrop) { //服务器计算剩余时间并同步 //syncRemainingTime = GetLessTimeSeconds(); _curAirdropTime += Time.deltaTime; if (_curAirdropTime >= _airdropTime) { int airdropPosId = Random.Range(0, 4); int endAirdropPosId = 0; if (airdropPosId == 0) endAirdropPosId = 1; if (airdropPosId == 1) endAirdropPosId = 0; if (airdropPosId == 2) endAirdropPosId = 3; if (airdropPosId == 3) endAirdropPosId = 2; CreateAirdropPlane(GameLocal.Ins.startPlanePos[airdropPosId].position, GameLocal.Ins.startPlanePos[endAirdropPosId].position); _curAirdropTime = 0; StopAirDrop(); } } if (gameState == GameState.Playing) { GameLocal.Ins.curGameTime+= Time.deltaTime; } if (isServer) { if (Input.GetKeyDown(KeyCode.Space)) { StopRound(); } } // 调试:强制显示指引(测试用) if (forceShowGuide && Input.GetKeyDown(KeyCode.G) && isGuideInitialized && !isGuideArrowActive) { Debug.Log("强制显示指引"); StartGuide(); } // 更新指引箭头位置 UpdateGuideArrowPosition(); } [Server] public void StartRound() { gameState = GameState.Playing; ChangeBgmRpc(CurRoundIndex + 1); vistRoundEnd = (long)(DateTime.Now.Subtract(new DateTime(1970, 1, 1))).TotalSeconds + roundAllTime; foreach (var item in allPlayers) { item.HideInvincible(); } StartRoundTime(); } [Server] public void StopRound() { gameState = GameState.Waiting; ChangeBgmRpc(0); if (CurRoundIndex >= 3) gameState = GameState.Settle; if (BlueRoundScore > RedRoundScore) { BlueScore += 1; } if (BlueRoundScore < RedRoundScore) { RedScore += 1; } if (BlueRoundScore == RedRoundScore && BlueRoundScore != 0 && RedRoundScore != 0) { RedScore += 1; BlueScore += 1; } foreach (var item in allPlayers) { item.ClearGun(); } MonoSingleton.Instance.WaitSecondTodo(() => { foreach (var item in allPlayers) { RpcPlayerRoundEnd(item.connectionToClient); } }, 1f, this); CreateRoundPlaceDoorPre(); MonoSingleton.Instance.WaitSecondTodo(() => { if (gameState == GameState.Settle) return; BlueRoundScore = 0; RedRoundScore = 0; GameLocal.Ins.ClearObject(); foreach (var player in allPlayers) { RpcPlayerWaitRoundEnd(player.connectionToClient); } }, 10f, this); MonoSingleton.Instance.WaitSecondTodo(() => { if (gameState == GameState.Settle) return; StartRound(); }, 13f, this); } [TargetRpc] public void RpcPlayerRoundEnd(NetworkConnection target) { EventDispatcher.TriggerEvent("NewRoundStart", CurRoundIndex == 3 ? BlueScore : BlueRoundScore, CurRoundIndex == 3 ? RedScore : RedRoundScore, CurRoundIndex); } [TargetRpc] public void RpcPlayerWaitRoundEnd(NetworkConnection target) { GameLocal.Ins.RoundUI.SetActive(false); EventDispatcher.TriggerEvent("NewWaveStart"); } public Player GetPlayerByIndex(int index) { return allPlayers.FirstOrDefault(p => p.index == index); } #region 回合地图 [Server] public void CreateRoundPlaceDoorPre() { //创建吸收场地黑洞 var roundPlaceDoor = Instantiate(roundPlaceDoorPre, GameLocal.Ins.roundPlaceDoorPos.position, Quaternion.identity); roundPlaceDoor.transform.eulerAngles = GameLocal.Ins.roundPlaceDoorPos.eulerAngles; NetworkServer.Spawn(roundPlaceDoor); foreach (var player in allPlayers) { PlaceFly(player.connectionToClient, roundPlaceDoor.transform.position); } MonoSingleton.Instance.WaitSecondTodo(() => { if (roundPlaceDoor != null) NetworkServer.Destroy(roundPlaceDoor); if (gameState == GameState.Settle) return; // RPC:通知客户端执行地图升起动画 foreach (var player in allPlayers) { RpcMapRise(player.connectionToClient); } }, 8f, this); } [TargetRpc] public void PlaceFly(NetworkConnection target, Vector3 blackHolePos) { GameObject curPlace = GameLocal.Ins.CurPlace(CurRoundIndex); foreach (Transform child in curPlace.GetComponentsInChildren()) { if (child == curPlace.transform) continue; // 跳过根节点 Collider childCollider = child.GetComponent(); if (childCollider != null) childCollider.enabled = false; GameLocal.Ins.AddObject(child, blackHolePos); } } [TargetRpc] public void RpcMapRise(NetworkConnection target) { StartCoroutine(MapRiseCoroutine()); } private IEnumerator MapRiseCoroutine() { GameLocal.Ins.ShowPlace(CurRoundIndex + 1); GameObject newMap = GameLocal.Ins.CurPlace(CurRoundIndex + 1); // 新地图预先放在场景里 Vector3 startPos = newMap.transform.position; Vector3 endPos = new Vector3(startPos.x, 0f, startPos.z); float duration = 2f; float t = 0f; while (t < duration) { t += Time.deltaTime; float progress = t / duration; // 使用平滑插值(可改成 easeOut) float smooth = Mathf.SmoothStep(0, 1, progress); newMap.transform.position = Vector3.Lerp(startPos, endPos, smooth); yield return null; } newMap.transform.position = endPos; } #endregion #region 分数系统 /// /// 红色方加分 /// /// public void AddRedScore(int score) { RedRoundScore += score; } /// /// 蓝色方加分 /// /// public void AddBlueScore(int score) { BlueRoundScore += score; } #endregion #region 指引系统(联机版本)- 双线版本 /// /// 初始化指引箭头(每个客户端独立) /// private void InitializeGuideArrow() { // 检查预制体是否存在 if (GuideArrowPre == null) { Debug.LogError("GuideArrowPre 预制体未分配!"); return; } // 实例化两个指引箭头预制体(本地实例,不会网络同步) guideArrowInstanceRed = Instantiate(GuideArrowPre); guideArrowInstanceRed.name = "GuideArrow_Red"; guideArrowInstanceBlue = Instantiate(GuideArrowPre); guideArrowInstanceBlue.name = "GuideArrow_Blue"; // 获取指引箭头组件 guideArrowComponentRed = guideArrowInstanceRed.GetComponent(); guideArrowComponentBlue = guideArrowInstanceBlue.GetComponent(); if (guideArrowComponentRed == null || guideArrowComponentBlue == null) { Debug.LogError("GuideArrowPre 预制体上没有找到 GuideArrowPath 组件!"); if (guideArrowInstanceRed != null) Destroy(guideArrowInstanceRed); if (guideArrowInstanceBlue != null) Destroy(guideArrowInstanceBlue); guideArrowInstanceRed = null; guideArrowInstanceBlue = null; return; } // 初始化玩家位置 if (GameLocal.Ins.self != null) { lastPlayerPosition = GameLocal.Ins.self.transform.position; } isGuideInitialized = true; Debug.Log("指引系统初始化完成,创建了2个指引箭头,等待门位置同步..."); // 如果已经收到门位置同步,立即开始指引 if (isDoorPositionSynced && guideTargetPositionRed != Vector3.zero && guideTargetPositionBlue != Vector3.zero) { CoroutineTaskManager.Instance.WaitSecondTodo(() => { StartGuide(); }, 0.5f); } // 如果是客户端,尝试查找网络上的门 else if (!isServer) { CoroutineTaskManager.Instance.WaitSecondTodo(() => { TryFindDoorsAndStartGuide(); }, 1f); } } /// /// 尝试查找门并开始指引(客户端使用) /// private void TryFindDoorsAndStartGuide() { if (isGuideInitialized && !isGuideArrowActive) { Vector3[] doorPositions = FindDoorPositions(); if (doorPositions[0] != Vector3.zero && doorPositions[1] != Vector3.zero) { guideTargetPositionRed = doorPositions[0]; guideTargetPositionBlue = doorPositions[1]; StartGuide(); } else { Debug.Log("客户端:未找到两个门,等待门位置同步..."); } } } /// /// 更新指引箭头位置(实时跟随玩家移动) /// private void UpdateGuideArrowPosition() { if (!isGuideArrowActive || GameLocal.Ins.self == null || !isGuideInitialized) return; // 获取玩家当前位置 Vector3 currentPlayerPosition = GameLocal.Ins.self.transform.position; // 检查冷却时间 if (Time.time - lastPathUpdateTime < updatePathCooldown) return; // 检查玩家是否移动了足够远的距离 float distanceMoved = Vector3.Distance(currentPlayerPosition, lastPlayerPosition); if (distanceMoved > updatePathThreshold) { // 使用门位置(客户端本地存储的) Vector3 doorPositionRed = guideTargetPositionRed; Vector3 doorPositionBlue = guideTargetPositionBlue; // 如果门位置为零向量(未初始化),尝试查找网络上的门 if (doorPositionRed == Vector3.zero || doorPositionBlue == Vector3.zero) { Vector3[] doorPositions = FindDoorPositions(); if (doorPositions[0] == Vector3.zero || doorPositions[1] == Vector3.zero) { return; // 没有找到门,不更新路径 } else { guideTargetPositionRed = doorPositions[0]; guideTargetPositionBlue = doorPositions[1]; } } // 使用曲线检测和路径平滑,分别计算两条路径 UpdatePathWithCurveDetection(currentPlayerPosition, doorPositionRed, doorPositionBlue); // 更新记录的位置和时间 lastPlayerPosition = currentPlayerPosition; lastPathUpdateTime = Time.time; } } /// /// 查找门的位置(客户端使用) /// private Vector3[] FindDoorPositions() { Vector3[] doorPositions = new Vector3[2] { Vector3.zero, Vector3.zero }; // 查找所有门对象 GameObject[] redDoors = GameObject.FindGameObjectsWithTag("RedDoor"); GameObject[] blueDoors = GameObject.FindGameObjectsWithTag("BlueDoor"); // 如果没有标签,尝试通过名称查找 if (redDoors.Length == 0) { redDoors = GameObject.FindGameObjectsWithTag("RedDoor"); } if (blueDoors.Length == 0) { blueDoors = GameObject.FindGameObjectsWithTag("BlueDoor"); } // 如果还是没有找到,尝试通过名称查找 if (redDoors.Length == 0) { GameObject redDoorObj = GameObject.Find("RedDoor(Clone)"); if (redDoorObj != null) redDoors = new GameObject[] { redDoorObj }; } if (blueDoors.Length == 0) { GameObject blueDoorObj = GameObject.Find("BlueDoor(Clone)"); if (blueDoorObj != null) blueDoors = new GameObject[] { blueDoorObj }; } // 查找所有网络对象 NetworkIdentity[] allNetObjs = FindObjectsOfType(); if (redDoors.Length == 0) { foreach (var netObj in allNetObjs) { if (netObj.gameObject.name.Contains("RedDoor") || netObj.gameObject.name.Contains("redDoor")) { doorPositions[0] = netObj.transform.position; break; } } } else { doorPositions[0] = redDoors[0].transform.position; } if (blueDoors.Length == 0) { foreach (var netObj in allNetObjs) { if (netObj.gameObject.name.Contains("BlueDoor") || netObj.gameObject.name.Contains("blueDoor")) { doorPositions[1] = netObj.transform.position; break; } } } else { doorPositions[1] = blueDoors[0].transform.position; } if (doorPositions[0] != Vector3.zero && doorPositions[1] != Vector3.zero) { Debug.Log("客户端:找到门位置,红门: " + doorPositions[0] + ",蓝门: " + doorPositions[1]); } return doorPositions; } // 新增:使用曲线检测和路径平滑的方法,计算两条路径 private void UpdatePathWithCurveDetection(Vector3 start, Vector3 endRed, Vector3 endBlue) { if (guideArrowComponentRed == null || guideArrowComponentBlue == null) return; // 1. 检测是否为直接可见路径 bool isPathClearRed = IsDirectPathClear(start, endRed); bool isPathClearBlue = IsDirectPathClear(start, endBlue); // 2. 计算红门路径 if (isPathClearRed) { smoothedPathRed = GenerateBezierCurve(start, endRed, 0.2f); } else { smoothedPathRed = CalculateObstacleAvoidancePath(start, endRed); } // 3. 计算蓝门路径 if (isPathClearBlue) { smoothedPathBlue = GenerateBezierCurve(start, endBlue, 0.2f); } else { smoothedPathBlue = CalculateObstacleAvoidancePath(start, endBlue); } // 4. 设置路径 if (smoothedPathRed != null && smoothedPathRed.Count > 1) { guideArrowComponentRed.SetPath(smoothedPathRed); } if (smoothedPathBlue != null && smoothedPathBlue.Count > 1) { guideArrowComponentBlue.SetPath(smoothedPathBlue); } isPathSmoothed = true; } // 新增:检查直接路径是否畅通 private bool IsDirectPathClear(Vector3 start, Vector3 end) { Vector3 direction = (end - start).normalized; float distance = Vector3.Distance(start, end); // 使用射线检测,同时检查多个点 int checkPoints = Mathf.CeilToInt(distance / 0.5f); for (int i = 0; i <= checkPoints; i++) { float t = (float)i / checkPoints; Vector3 checkPoint = Vector3.Lerp(start, end, t); // 检查周围小范围的碰撞 if (Physics.CheckSphere(checkPoint, 0.3f, guideArrowComponentRed.obstacleMask)) { return false; } } return true; } // 新增:生成贝塞尔曲线路径 private List GenerateBezierCurve(Vector3 start, Vector3 end, float curveHeight) { List curvePoints = new List(); int segments = 20; // 曲线分段数 // 计算控制点(在中间稍微抬起形成曲线) Vector3 controlPoint = (start + end) * 0.5f + Vector3.up * curveHeight; for (int i = 0; i <= segments; i++) { float t = (float)i / segments; // 二次贝塞尔曲线公式 Vector3 point = (1 - t) * (1 - t) * start + 2 * (1 - t) * t * controlPoint + t * t * end; curvePoints.Add(point); } return curvePoints; } // 新增:优化后的绕障碍物路径计算 private List CalculateObstacleAvoidancePath(Vector3 start, Vector3 end) { List path = new List(); path.Add(start); // 尝试寻找最佳绕行点 Vector3 bypassPoint = FindOptimalBypassPoint(start, end); if (bypassPoint != start) { // 如果有绕行点,构建曲线路径 List curve1 = GenerateBezierCurve(start, bypassPoint, 0.3f); List curve2 = GenerateBezierCurve(bypassPoint, end, 0.3f); path.AddRange(curve1.Skip(1)); path.AddRange(curve2.Skip(1)); } else { // 没有找到绕行点,使用简单的曲线 path = GenerateBezierCurve(start, end, 0.2f); } return path; } // 新增:寻找最优绕行点 private Vector3 FindOptimalBypassPoint(Vector3 from, Vector3 to) { Vector3 direction = (to - from).normalized; float distance = Vector3.Distance(from, to); // 定义多个探测方向 Vector3[] probeDirections = new Vector3[] { Vector3.Cross(direction, Vector3.up).normalized, // 右侧 -Vector3.Cross(direction, Vector3.up).normalized, // 左侧 (Vector3.Cross(direction, Vector3.up).normalized + Vector3.up * 0.3f).normalized, // 右上 (-Vector3.Cross(direction, Vector3.up).normalized + Vector3.up * 0.3f).normalized // 左上 }; float[] probeDistances = new float[] { 2f, 3f, 4f, 5f }; Vector3 bestBypassPoint = from; float bestScore = float.MaxValue; foreach (Vector3 probeDir in probeDirections) { foreach (float probeDist in probeDistances) { Vector3 probePoint = from + probeDir * probeDist; // 检查探测点是否可行 if (!Physics.CheckSphere(probePoint, 0.5f, guideArrowComponentRed.obstacleMask)) { // 计算路径分数(距离 + 转向角度) float pathLength = Vector3.Distance(from, probePoint) + Vector3.Distance(probePoint, to); float angleCost = Vector3.Angle(probePoint - from, to - probePoint) * 0.1f; float score = pathLength + angleCost; if (score < bestScore) { bestScore = score; bestBypassPoint = probePoint; } } } } return bestBypassPoint; } /// /// 显示指引箭头(本地调用) /// public void ShowGuideArrow() { if (guideArrowComponentRed != null && guideArrowComponentBlue != null && GameLocal.Ins.self != null && isGuideInitialized) { guideArrowComponentRed.ShowPath(); guideArrowComponentBlue.ShowPath(); isGuideArrowActive = true; Debug.Log("指引箭头已显示(2条线)"); } } /// /// 开始指引(在创建门后调用) /// public void StartGuide() { if (!isGuideInitialized) { Debug.LogWarning("指引系统未初始化,无法开始指引"); return; } if (GameLocal.Ins.self == null) { Debug.LogWarning("玩家对象未找到,无法开始指引"); return; } // 获取门位置 Vector3 doorPositionRed = guideTargetPositionRed; Vector3 doorPositionBlue = guideTargetPositionBlue; if (doorPositionRed == Vector3.zero || doorPositionBlue == Vector3.zero) { Vector3[] doorPositions = FindDoorPositions(); if (doorPositions[0] == Vector3.zero || doorPositions[1] == Vector3.zero) { Debug.LogWarning("未找到两个门位置,指引无法开始。"); return; } else { guideTargetPositionRed = doorPositions[0]; guideTargetPositionBlue = doorPositions[1]; } } Debug.Log("开始指引,红门位置: " + guideTargetPositionRed + ",蓝门位置: " + guideTargetPositionBlue + ",玩家位置: " + GameLocal.Ins.self.transform.position); // 初始化路径 Vector3 playerPosition = GameLocal.Ins.self.transform.position; UpdatePathWithCurveDetection(playerPosition, guideTargetPositionRed, guideTargetPositionBlue); // 记录初始位置 lastPlayerPosition = playerPosition; // 显示指引 ShowGuideArrow(); } /// /// 隐藏指引箭头(本地调用) /// public void HideGuideArrow() { if (guideArrowComponentRed != null && guideArrowComponentBlue != null && isGuideInitialized) { guideArrowComponentRed.ClosePath(); guideArrowComponentBlue.ClosePath(); isGuideArrowActive = false; Debug.Log("指引箭头已隐藏(2条线)"); } } /// /// 清理指引箭头实例 /// public void CleanupGuideArrow() { if (guideArrowInstanceRed != null) { Destroy(guideArrowInstanceRed); guideArrowInstanceRed = null; } if (guideArrowInstanceBlue != null) { Destroy(guideArrowInstanceBlue); guideArrowInstanceBlue = null; } guideArrowComponentRed = null; guideArrowComponentBlue = null; isGuideArrowActive = false; isGuideInitialized = false; } /// /// RPC调用:停止所有客户端的指引 /// [ClientRpc] public void RpcStopAllGuide() { StopGuide(); } /// /// 停止指引(在游戏开始或删除门时调用) /// public void StopGuide() { HideGuideArrow(); } #endregion }