Files
MRCS/Assets/_MrCs/Scripts/Manager/GameManager.cs

1415 lines
41 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;
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;
/// <summary>
/// 游戏状态
/// </summary>
public enum GameState
{
None = 0,
Playing = 1,
/// <summary>
/// 胜利
/// </summary>
Victory = 2,
/// <summary>
/// 失败
/// </summary>
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<EnemyType, Dictionary<int, EnemyInfo>> EnemyInfos = new Dictionary<EnemyType, Dictionary<int, EnemyInfo>>();
// 枪械信息集合
public Dictionary<GunType, Dictionary<int, GunInfo>> GunInfos = new Dictionary<GunType, Dictionary<int, GunInfo>>();
/// <summary>
/// 所有敌人UI
/// </summary>
public Dictionary<int, PlayerUI> EnemyUIList = new Dictionary<int, PlayerUI>();
public Dictionary<string, SettleInfo> SettleInfos = new Dictionary<string, SettleInfo>();
public List<Player> allPlayers = new List<Player>();
// 游玩结束时间
[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<Vector3> smoothedPathRed = new List<Vector3>();
private List<Vector3> smoothedPathBlue = new List<Vector3>();
// 指引系统目标位置
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;
}
}
/// <summary>
/// 创建门
/// </summary>
[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);
}
/// <summary>
/// 同步门的位置给所有客户端
/// </summary>
[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<DamageNumberNetwork>();
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<CoroutineTaskManager>.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<Player>();
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<AirdropPlane>();
airdropPlaneScript.OnSpawn(endPos);
}
[Server]
public void CreateAirDropItem(int id, Vector3 startPos)
{
GameObject airdropItem = Instantiate(airdropItems[id], startPos, quaternion.identity);
NetworkServer.Spawn(airdropItem);
}
/// <summary>
/// 痛击场上所有敌方单位
/// </summary>
[Command(requiresAuthority = false)]
public void DamageAllEnemy()
{
}
/// <summary>
/// 更新配置表
/// </summary>
public void UpdateConf()
{
string text = Resources.Load<TextAsset>("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<int, GunInfo> infoList);
if (infoList == null)
{
Dictionary<int, GunInfo> list = new Dictionary<int, GunInfo>();
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<PlayerUI>();
//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<CoroutineTaskManager>.Instance.WaitSecondTodo(() =>
{
foreach (var item in allPlayers)
{
RpcPlayerRoundEnd(item.connectionToClient);
}
}, 1f, this);
CreateRoundPlaceDoorPre();
MonoSingleton<CoroutineTaskManager>.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<CoroutineTaskManager>.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<CoroutineTaskManager>.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<Transform>())
{
if (child == curPlace.transform) continue; // 跳过根节点
Collider childCollider = child.GetComponent<Collider>();
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
/// <summary>
/// 红色方加分
/// </summary>
/// <param name="score"></param>
public void AddRedScore(int score)
{
RedRoundScore += score;
}
/// <summary>
/// 蓝色方加分
/// </summary>
/// <param name="score"></param>
public void AddBlueScore(int score)
{
BlueRoundScore += score;
}
#endregion
#region - 线
/// <summary>
/// 初始化指引箭头(每个客户端独立)
/// </summary>
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<GuideArrowPath>();
guideArrowComponentBlue = guideArrowInstanceBlue.GetComponent<GuideArrowPath>();
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);
}
}
/// <summary>
/// 尝试查找门并开始指引(客户端使用)
/// </summary>
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("客户端:未找到两个门,等待门位置同步...");
}
}
}
/// <summary>
/// 更新指引箭头位置(实时跟随玩家移动)
/// </summary>
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;
}
}
/// <summary>
/// 查找门的位置(客户端使用)
/// </summary>
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<NetworkIdentity>();
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<Vector3> GenerateBezierCurve(Vector3 start, Vector3 end, float curveHeight)
{
List<Vector3> curvePoints = new List<Vector3>();
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<Vector3> CalculateObstacleAvoidancePath(Vector3 start, Vector3 end)
{
List<Vector3> path = new List<Vector3>();
path.Add(start);
// 尝试寻找最佳绕行点
Vector3 bypassPoint = FindOptimalBypassPoint(start, end);
if (bypassPoint != start)
{
// 如果有绕行点,构建曲线路径
List<Vector3> curve1 = GenerateBezierCurve(start, bypassPoint, 0.3f);
List<Vector3> 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;
}
/// <summary>
/// 显示指引箭头(本地调用)
/// </summary>
public void ShowGuideArrow()
{
if (guideArrowComponentRed != null && guideArrowComponentBlue != null &&
GameLocal.Ins.self != null && isGuideInitialized)
{
guideArrowComponentRed.ShowPath();
guideArrowComponentBlue.ShowPath();
isGuideArrowActive = true;
Debug.Log("指引箭头已显示2条线");
}
}
/// <summary>
/// 开始指引(在创建门后调用)
/// </summary>
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();
}
/// <summary>
/// 隐藏指引箭头(本地调用)
/// </summary>
public void HideGuideArrow()
{
if (guideArrowComponentRed != null && guideArrowComponentBlue != null && isGuideInitialized)
{
guideArrowComponentRed.ClosePath();
guideArrowComponentBlue.ClosePath();
isGuideArrowActive = false;
Debug.Log("指引箭头已隐藏2条线");
}
}
/// <summary>
/// 清理指引箭头实例
/// </summary>
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;
}
/// <summary>
/// RPC调用停止所有客户端的指引
/// </summary>
[ClientRpc]
public void RpcStopAllGuide()
{
StopGuide();
}
/// <summary>
/// 停止指引(在游戏开始或删除门时调用)
/// </summary>
public void StopGuide()
{
HideGuideArrow();
}
#endregion
}