Files
Zombie/Assets/_Zombie/Scripts/GameManager.cs
2026-03-14 14:47:44 +08:00

1261 lines
35 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 BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Engines;
using Common;
using DarkTonic.MasterAudio;
using DragonLi.Core;
using LitJson;
using Mirror;
using NaughtyAttributes;
using Pathfinding;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.PlayerLoop;
using UnityEngine.UIElements;
using XPlugin.Data.JsonLiteDB;
using static XPlugin.Data.JsonLiteDB.JsonLiteDB;
using Random = UnityEngine.Random;
/// <summary>
/// 游戏状态
/// </summary>
public enum GameState
{
None = 0,
Playing = 1,
/// <summary>
/// 胜利
/// </summary>
Victory = 2,
/// <summary>
/// 失败
/// </summary>
Failur = 3,
Settle = 4,
/// <summary>
/// 准备阶段
/// </summary>
Wave = 5,
}
public class GameManager : NetworkBehaviour
{
public static GameManager Ins { get; private set; }
#region
public GameObject DoorPre;
public GameObject gunPropPre;
public GameObject itemPropPre;
// 炮塔预制体集合
//public GameObject[] TowerPres;
// 怪物预制集合
public GameObject[] EnemyPres;
public GameObject explosionPre;
// 怪物ui预制体
public GameObject EnemyUIPre;
#endregion
// json数据库
private JsonLiteDB DB;
// 怪物信息集合
public Dictionary<EnemyType, EnemyInfo> EnemyInfos = new Dictionary<EnemyType, EnemyInfo>();
// 炮塔信息集合
public Dictionary<TowerType, Dictionary<int, TowerInfo>> TowerInfos = new Dictionary<TowerType, Dictionary<int, TowerInfo>>();
// 枪械信息集合
public Dictionary<GunType, GunInfo> GunInfos = new Dictionary<GunType, GunInfo>();
// 子弹信息集合
public Dictionary<BulletType, BulletInfo> BulletInfos = new Dictionary<BulletType, BulletInfo>();
// 防线信息
public List<DefenseInfo> DefenseInfos = new List<DefenseInfo>();
// 剧情信息
public Dictionary<int, CombatUnitInfo> CombatUnitInfos = new Dictionary<int, CombatUnitInfo>();
private List<GameObject> curGunPropList = new List<GameObject>();
public List<GameObject> curItemPropList = new List<GameObject>();
public Dictionary<int, Player> players = new Dictionary<int, Player>();
/// <summary>
/// 敌人自增
/// </summary>
private int enemyIndex = 0;
/// <summary>
/// 所有敌人
/// </summary>
public Dictionary<int, Enemy> EnemyList = new Dictionary<int, Enemy>();
/// <summary>
/// 所有敌人UI
/// </summary>
public Dictionary<int, EnemyUI> EnemyUIList = new Dictionary<int, EnemyUI>();
/// <summary>
/// 炮塔自增
/// </summary>
public int towerIndex = 0;
/// <summary>
/// 所有炮塔
/// </summary>
public Dictionary<int, Tower> TowerList = new Dictionary<int, Tower>();
public Dictionary<TowerType, List<Tower>> TowerList2 = new Dictionary<TowerType, List<Tower>>();
public Dictionary<string, SettleInfo> SettleInfos = new Dictionary<string, SettleInfo>();
[SyncVar]
public bool isStart;
/// <summary>
/// 波次自增
/// </summary>
[SyncVar]
public int roundIndex = 0;
// 游玩结束时间
[NonSerialized]
[SyncVar]
public long vistEnd = 0;
[SyncVar]
public string settleData = "";
[SyncVar]
public GameState gameState = GameState.None;
[SoundGroup] public string JointIn;
[SoundGroup] public string roundSound;
public int roundWaveTime;
public int curRoundWaveTime;
// 新增:指引系统
public GameObject GuideArrowPre; // 指引箭头预制体
private GameObject guideArrowInstance; // 指引箭头实例
private GuideArrowPath guideArrowComponent; // 指引箭头组件
private bool isGuideArrowActive = false;
private Vector3 lastPlayerPosition; // 记录玩家上一次的位置
private float updatePathThreshold = 0.5f; // 更新路径的阈值(玩家移动超过这个距离才更新路径)
private float updatePathCooldown = 0.3f; // 更新路径的冷却时间
private float lastPathUpdateTime = 0f;
private Vector3[] lastPathPoints; // 上一次的路径点
private bool isPathSmoothed = false;
private List<Vector3> smoothedPath = new List<Vector3>();
void Awake()
{
Ins = this;
}
void Start()
{
// 新增:初始化指引系统
InitializeGuideArrow();
if (isClient)
{
isStart = false;
CreateDoor();
}
}
public void Update()
{
// 更新指引箭头位置
UpdateGuideArrowPosition();
if (gameState == GameState.Playing)
{
GameLocal.Ins.curGameTime+=Time.deltaTime;
}
}
/// <summary>
/// 创建门
/// </summary>
public void CreateDoor()
{
UpdateConf();
if (!isServer)
return;
GameObject door = Instantiate(DoorPre);
door.transform.position = GameLocal.Ins.doorPos.position.ReflectVectorXOZ();
NetworkServer.Spawn(door);
// 延迟开始指引,确保玩家和门都已初始化
CoroutineTaskManager.Instance.WaitSecondTodo(() =>
{
StartGuide();
}, 0.5f);
}
/// <summary>
/// 创建初回合
/// </summary>
public void CreateFirstRound()
{
RpcShowHUD();
roundIndex = 0;
roundWaveTime = 15;
curRoundWaveTime = 12;
SetGameState(GameState.Wave);
RpcMessageRound();
}
/// <summary>
/// 创建下一回合
/// </summary>
public void CreateNextRound()
{
roundIndex++;
SetGameState(GameState.Wave);
roundWaveTime = 15;
curRoundWaveTime = 12;
RpcMessageRound();
}
[Server]
public void GameStart()
{
gameState = GameState.Playing;
vistEnd = (long)(DateTime.Now.Subtract(new DateTime(1970, 1, 1))).TotalSeconds + GameLocal.Ins.vistAllTime;
isStart = true;
ChangeBgmRpc(1);
// CreateTowers();
AstarPath.active.Scan();
// CreateFirstRound();
CreateFirstRound();
// 延迟1s后给所有人发放武器
CoroutineTaskManager.Instance.WaitSecondTodo(() =>
{
GivePistol();
}, 1f);
CoroutineTaskManager.Instance.WaitSecondTodo(() =>
{
GameOver(GameState.Settle);
}, GameLocal.Ins.vistAllTime);
}
[ClientRpc]
public void GivePistol()
{
Debug.Log("创建武器中...");
GameLocal.Ins.self.PickUpGun(GunType.Pistol, -999);
}
[Server]
public void SetGameState(GameState state)
{
gameState = state;
}
//1.游戏时间到
//2.所有怪物被消灭
//3.玩家被消灭
//以上都要跳出结算
[Server]
public void GameOver(GameState state)
{
RpcShow();
DamageAllEnemy();
gameState = state;
isStart = false;
// gameState = state;
foreach (string key in SettleInfos.Keys)
{
string[] titles = { "初露锋芒", "小菜一碟", "熟手", "痛并快乐着", "不死传说"
,"最强守护者", "独孤求败", "佛系玩家", "午时已到", "神枪手", "弹无虚发", "团队护盾", "我叫MT", "钢铁之躯","道具大师","行走的仓库","永不停歇" };
SetTitle(key, titles[Random.Range(0, titles.Length)]);
SetPlayerName(key);
}
string jsonStr = JsonMapper.ToJson(SettleInfos);
settleData = jsonStr;
MonoSingleton<CoroutineTaskManager>.Instance.WaitSecondTodo(() =>
{
Debug.Log("游戏结束5秒后退出");
Application.Quit();
}, 60f);
// //结算页面
// RpcShow();
// if (blueScore >= redScore)
// {
// RpcShowWin(true);
// }
// else
// {
// RpcShowWin(false);
// }
}
[ClientRpc]
public void RpcShowWin(bool isBlue)
{
}
[ClientRpc]
public void RpcShow()
{
SettlePanel.Show();
}
/// <summary>
/// 下一波进攻开始
/// </summary>
[Server]
public void RpcMessageRound()
{
if (!isStart)
return;
CoroutineTaskManager.Instance.WaitSecondTodo(() =>
{
if (!isStart)
return;
ShowHudRoundTime();
}, curRoundWaveTime);
CoroutineTaskManager.Instance.WaitSecondTodo(() =>
{
if (!isStart)
return;
DesGunProList();
GetRoundEnemy();
PlaySound2DRPC(roundSound);
SetGameState(GameState.Playing);
}, roundWaveTime);
}
[ClientRpc]
public void ShowHudRoundTime()
{
DragonLi.Core.EventDispatcher.TriggerEvent("NewWaveStart", roundIndex);
}
[ClientRpc]
public void ChangeBgmRpc(int i)
{
GameLocal.Ins.BGMState.StateChange(i);
}
public int curRoundEnemyCount;
/// <summary>
/// 创建敌方单位
/// </summary>
IEnumerator CreateEnemy(CombatUnitInfo info)
{
curRoundEnemyCount = info.EnemyTypeList.Length;
if (GameLocal.Ins.enemyStartPos.Length == 0)
{
Debug.LogError("没有设置敌人出生点!");
yield break;
}
for (int i = 0; i < info.EnemyTypeList.Length; i++)
{
GameObject enemy = Instantiate(EnemyPres[info.EnemyTypeList[i]]);
NetworkServer.Spawn(enemy);
// 循环使用出生点
int posId = i % GameLocal.Ins.enemyStartPos.Length;
int posX = Random.Range(-1, 2);
int posZ = Random.Range(-1, 2);
enemy.transform.position = GameLocal.Ins.enemyStartPos[posId].position + new Vector3(posX, 0, posZ);
enemy.transform.eulerAngles = Vector3.zero;
enemyIndex++;
Enemy enemyScript = enemy.GetComponent<Enemy>();
enemyScript.OnSpawn(enemyIndex, (EnemyType)info.EnemyTypeList[i], 1);
EnemyList.Add(enemyIndex, enemyScript);
yield return new WaitForSeconds(info.BornTime);
}
CloseWu();
}
[ClientRpc]
public void CloseWu()
{
GameLocal.Ins.CloseWu();
}
public void CreateBoss()
{
GameObject enemy = Instantiate(EnemyPres[(int)EnemyType.ZombieBoss]);
NetworkServer.Spawn(enemy);
// FIX: Check if enemyStartPos has elements before accessing
if (GameLocal.Ins.enemyStartPos.Length == 0)
{
Debug.LogError("No enemy start positions configured!");
return;
}
int posId = Random.Range(0, GameLocal.Ins.enemyStartPos.Length); // Use actual length
int posX = Random.Range(-1, 2);
int posZ = Random.Range(-1, 2);
enemy.transform.position = GameLocal.Ins.enemyStartPos[posId].position + new Vector3(posX, 0, posZ);
enemy.transform.eulerAngles = new Vector3(0, 0, 0);
enemyIndex++;
Enemy enemyScript = enemy.GetComponent<Enemy>();
enemyScript.OnSpawn(enemyIndex, EnemyType.ZombieBoss, 1);
EnemyList.Add(enemyIndex, enemyScript);
curRoundEnemyCount++;
}
public void CreateExplosion(Transform pos)
{
if (!isServer)
return;
var ex = Instantiate(explosionPre, pos.position, Quaternion.identity);
NetworkServer.Spawn(ex);
ex.GetComponent<Explosion>().Init(0);
}
[SyncVar]
public int gunProp1;
[SyncVar]
public int gunProp2;
[Server]
public void CreateGunProp()
{
if (!isStart)
return;
List<int> numbers = new List<int>() { 1, 2, 3, 4, 5 };
gunProp1 = numbers[Random.Range(0, numbers.Count)];
numbers.Remove(gunProp1);
gunProp2 = numbers[Random.Range(0, numbers.Count)];
var gun1 = Instantiate(gunPropPre, GameLocal.Ins.gunPropPos[0]);
NetworkServer.Spawn(gun1);
gun1.GetComponent<WeaponProp>().Init((GunType)gunProp1);
curGunPropList.Add(gun1);
var gun2 = Instantiate(gunPropPre, GameLocal.Ins.gunPropPos[1]);
NetworkServer.Spawn(gun2);
gun2.GetComponent<WeaponProp>().Init((GunType)gunProp2);
curGunPropList.Add(gun2);
}
public void CreateItemProp(Transform pos)
{
if (!isStart)
return;
var item = Instantiate(itemPropPre);
NetworkServer.Spawn(item);
item.transform.position = new Vector3(pos.position.x, 0.5f, pos.position.z);
item.GetComponent<ItemProp>().SetItemProp(ItemPropType.Hp);
curItemPropList.Add(item);
}
/// <summary>
///删除枪械
/// </summary>
public void DesGunProList()
{
if (curGunPropList.Count <= 0)
return;
NetworkServer.Destroy(curGunPropList[1]);
NetworkServer.Destroy(curGunPropList[0]);
curGunPropList.Clear();
}
public void DesItemProp(GameObject item)
{
if (curItemPropList.Count <= 0)
return;
item.GetComponent<ItemProp>().isDeal = true;
curItemPropList.Remove(item);
NetworkServer.Destroy(item);
}
public List<Enemy> GetRoundEnemy()
{
List<Enemy> curEnemyList = new List<Enemy>();
if (roundIndex <= 6)
{
var combatUnitInfo = CombatUnitInfos[roundIndex];
StartCoroutine(CreateEnemy(combatUnitInfo));
}
else
{
int curId = roundIndex;
CombatUnitInfo info = new CombatUnitInfo(curId, curId, GenerateMonsterTypes(), 1);
StartCoroutine(CreateEnemy(info));
}
if (roundIndex % 5 == 0 && roundIndex != 0)
{
CreateBoss();
}
return curEnemyList;
}
public int[] GenerateMonsterTypes()
{
int[] weights = { 2, 6, 1, 3 };
List<int> monsterTypes = new List<int>();
int totalWeight = 0;
int targetWeight = Random.Range(24, 27);// * players.Count; // 24-26
int targetCount = Random.Range(6, 10);// * players.Count; // 4-7
//if (targetCount > 15)
//{
// targetCount = 15;
// targetCount = 15;
//}
// 随机添加怪物直到接近目标
while (monsterTypes.Count < targetCount && totalWeight < targetWeight)
{
int index = GetRandomMonsterIndex(weights);
int weight = weights[index];
// 如果剩余位置不够,跳出
if (monsterTypes.Count == targetCount)
break;
// 如果加上这个怪物不会超过目标权重,添加
if (totalWeight + weight <= targetWeight)
{
monsterTypes.Add(index);
totalWeight += weight;
}
else
{
break;
}
}
// 如果权重还没达到目标,填充最小权重怪物
while (totalWeight < targetWeight && monsterTypes.Count < targetCount)
{
int minIndex = GetMinWeightIndex(weights);
monsterTypes.Add(minIndex);
totalWeight += weights[minIndex];
}
return monsterTypes.ToArray();
}
// 根据权重随机选择怪物
private int GetRandomMonsterIndex(int[] weights)
{
int sumWeight = 0;
foreach (var w in weights) sumWeight += w;
int randomValue = Random.Range(0, sumWeight);
int currentSum = 0;
for (int i = 0; i < weights.Length; i++)
{
currentSum += weights[i];
if (randomValue < currentSum)
return i;
}
return 0;
}
// 获取最小权重怪物的索引
private int GetMinWeightIndex(int[] weights)
{
int minIndex = 0;
for (int i = 1; i < weights.Length; i++)
{
if (weights[i] < weights[minIndex])
minIndex = i;
}
return minIndex;
}
/// <summary>
/// 删除敌方单位
/// </summary>
[Server]
public void DeleteEnemy(int id, Transform pos)
{
GameObject enemy = EnemyList[id].gameObject;
// int hpItemPropIndex = 101;//道具爆率
// int randomItemPropIndex = Random.Range(1, 101);
//
// if(randomItemPropIndex<hpItemPropIndex)
// CreateItemProp(enemy.transform);
EnemyList.Remove(id);
curRoundEnemyCount--;
NetworkServer.Destroy(enemy);
if (curRoundEnemyCount <= 0)
{
CreateGunProp();
CreateNextRound();
OpenWu();
}
}
[ClientRpc]
public void OpenWu()
{
GameLocal.Ins.OpenWu();
}
/// <summary>
/// 痛击场上所有敌方单位
/// </summary>
[Command(requiresAuthority = false)]
public void DamageAllEnemy()
{
foreach (Enemy enemy in EnemyList.Values)
{
enemy.ApplyDamage(999999, null, null);
}
}
/// <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);
roundWaveTime = 15;
curRoundWaveTime = 12;
TableReader infoReader = DB["EnemysInfo"].GetReader();
EnemyInfos.Clear();
while (infoReader.Read())
{
EnemyInfo info = new EnemyInfo(infoReader);
EnemyInfos.Add((EnemyType)info.Type, info);
}
infoReader = DB["TowersInfo"].GetReader();
while (infoReader.Read())
{
TowerInfo info = new TowerInfo(infoReader);
TowerInfos.TryGetValue((TowerType)info.Type, out Dictionary<int, TowerInfo> infoList);
if (infoList == null)
{
Dictionary<int, TowerInfo> list = new Dictionary<int, TowerInfo>();
list.Add(info.Lvl, info);
TowerInfos.Add((TowerType)info.Type, list);
}
else
{
infoList.Add(info.Lvl, info);
}
}
infoReader = DB["GunsInfo"].GetReader();
GunInfos.Clear();
while (infoReader.Read())
{
GunInfo info = new GunInfo(infoReader);
GunInfos.Add((GunType)info.Type, info);
}
infoReader = DB["BulletsInfo"].GetReader();
BulletInfos.Clear();
while (infoReader.Read())
{
BulletInfo info = new BulletInfo(infoReader);
BulletInfos.Add((BulletType)info.Type, info);
}
infoReader = DB["DefensesInfo"].GetReader();
while (infoReader.Read())
{
DefenseInfo info = new DefenseInfo(infoReader);
DefenseInfos.Add(info);
}
infoReader = DB["StoryProgressInfo"].GetReader();
while (infoReader.Read())
{
CombatUnitInfo info = new CombatUnitInfo(infoReader);
CombatUnitInfos.Add(info.Belong, info);
CombatUnitInfos.TryGetValue(info.Belong, out info);
}
Debug.Log("游戏数值更新 -> complete");
}
/// <summary>
/// 获取范围内敌人
/// </summary>
public int GetRangeEnemyId(Vector3 pos, float radius)
{
int res = -1;
foreach (Enemy enemy in EnemyList.Values)
{
float dis = Vector3.Distance(enemy.transform.position, pos);
if (dis <= radius)
{
radius = dis;
res = enemy.id;
break;
}
}
return res;
}
/// <summary>
/// 获取最近的玩家
/// </summary>
/// <returns></returns>
public GameObject GetPlayer()
{
if (players == null || players.Count == 0)
return null;
GameObject nearestPlayer = null;
float minDistance = float.MaxValue;
foreach (var player in players.Values)
{
if (player == null) continue;
// 计算XZ平面距离使用你的 ReflectVectorXOZ 方法)
float dis = Vector3.Distance(
player.transform.position.ReflectVectorXOZ(),
transform.position.ReflectVectorXOZ()
);
if (dis < minDistance)
{
minDistance = dis;
nearestPlayer = player.gameObject;
}
}
return nearestPlayer;
}
#region
/// <summary>
/// 初始化指引箭头
/// </summary>
private void InitializeGuideArrow()
{
// 检查预制体是否存在
if (GuideArrowPre == null)
{
Debug.LogError("GuideArrowPre 预制体未分配!");
return;
}
// 实例化指引箭头预制体
guideArrowInstance = Instantiate(GuideArrowPre);
guideArrowInstance.name = "GuideArrow";
// 获取指引箭头组件
guideArrowComponent = guideArrowInstance.GetComponent<GuideArrowPath>();
if (guideArrowComponent == null)
{
Debug.LogError("GuideArrowPre 预制体上没有找到 GuideArrowPath 组件!");
Destroy(guideArrowInstance);
guideArrowInstance = null;
return;
}
// 设置指引箭头的层级和高度
guideArrowComponent.obstacleMask = LayerMask.GetMask("Default"); // 根据实际障碍物层级设置
guideArrowComponent.pathHeight = 0.5f; // 设置路径显示高度
// 初始隐藏指引
guideArrowComponent.ClosePath();
// 可选:设置父物体,保持场景整洁
guideArrowInstance.transform.SetParent(this.transform);
// 初始化玩家位置
if (GameLocal.Ins.self != null)
{
lastPlayerPosition = GameLocal.Ins.self.transform.position;
}
}
/// <summary>
/// 更新指引箭头位置(实时跟随玩家移动)
/// </summary>
// 修改 UpdateGuideArrowPosition 方法
private void UpdateGuideArrowPosition()
{
if (!isGuideArrowActive || GameLocal.Ins.doorPos == null || GameLocal.Ins.self == null)
return;
// 获取玩家当前位置
Vector3 currentPlayerPosition = GameLocal.Ins.self.transform.position;
// 检查冷却时间
if (Time.time - lastPathUpdateTime < updatePathCooldown)
return;
// 检查玩家是否移动了足够远的距离
float distanceMoved = Vector3.Distance(currentPlayerPosition, lastPlayerPosition);
if (distanceMoved > updatePathThreshold)
{
// 获取门的位置
Vector3 doorPosition = GameLocal.Ins.doorPos.transform.position;
// 使用曲线检测和路径平滑
UpdatePathWithCurveDetection(currentPlayerPosition, doorPosition);
// 更新记录的位置和时间
lastPlayerPosition = currentPlayerPosition;
lastPathUpdateTime = Time.time;
}
}
// 新增:使用曲线检测和路径平滑的方法
private void UpdatePathWithCurveDetection(Vector3 start, Vector3 end)
{
if (guideArrowComponent == null)
return;
// 1. 检测是否为直接可见路径
if (IsDirectPathClear(start, end))
{
// 直接路径,使用简单的贝塞尔曲线
smoothedPath = GenerateBezierCurve(start, end, 0.2f);
guideArrowComponent.SetPath(smoothedPath); // 这里使用 List<Vector3> 参数
isPathSmoothed = true;
return;
}
// 2. 间接路径,使用优化的绕路算法
List<Vector3> newPath = CalculateObstacleAvoidancePath(start, end);
if (newPath != null && newPath.Count > 1)
{
// 应用路径平滑
smoothedPath = SmoothPath(newPath);
guideArrowComponent.SetPath(smoothedPath); // 这里使用 List<Vector3> 参数
isPathSmoothed = true;
lastPathPoints = smoothedPath.ToArray();
}
}
// 新增:检查直接路径是否畅通
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, guideArrowComponent.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, guideArrowComponent.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;
}
// 新增:路径平滑算法
private List<Vector3> SmoothPath(List<Vector3> rawPath)
{
if (rawPath.Count < 3)
return rawPath;
List<Vector3> smoothed = new List<Vector3>();
smoothed.Add(rawPath[0]);
// 使用简单的平均平滑
for (int i = 1; i < rawPath.Count - 1; i++)
{
Vector3 smoothedPoint = (rawPath[i - 1] + rawPath[i] + rawPath[i + 1]) / 3f;
smoothed.Add(smoothedPoint);
}
smoothed.Add(rawPath[rawPath.Count - 1]);
return smoothed;
}
/// <summary>
/// 显示指引箭头
/// </summary>
public void ShowGuideArrow()
{
if (guideArrowComponent != null && GameLocal.Ins.doorPos != null && GameLocal.Ins.self != null)
{
guideArrowComponent.ShowPath();
isGuideArrowActive = true;
// 初始化路径
Vector3 playerPosition = GameLocal.Ins.self.transform.position;
Vector3 doorPosition = GameLocal.Ins.doorPos.transform.position;
UpdatePathWithCurveDetection(playerPosition, doorPosition);
// 记录初始位置
lastPlayerPosition = playerPosition;
}
}
/// <summary>
/// 隐藏指引箭头
/// </summary>
public void HideGuideArrow()
{
if (guideArrowComponent != null)
{
guideArrowComponent.ClosePath();
isGuideArrowActive = false;
}
}
/// <summary>
/// 开始指引(在创建门后调用)
/// </summary>
public void StartGuide()
{
if (GameLocal.Ins.doorPos != null && GameLocal.Ins.self != null)
{
ShowGuideArrow();
// 初始化路径
Vector3 playerPosition = GameLocal.Ins.self.transform.position;
Vector3 doorPosition = GameLocal.Ins.doorPos.transform.position;
// 使用新的路径计算方法
UpdatePathWithCurveDetection(playerPosition, doorPosition);
// 记录初始位置
lastPlayerPosition = playerPosition;
}
}
/// <summary>
/// 停止指引(在游戏开始或删除门时调用)
/// </summary>
public void StopGuide()
{
HideGuideArrow();
}
/// <summary>
/// 清理指引箭头实例
/// </summary>
public void CleanupGuideArrow()
{
if (guideArrowInstance != null)
{
Destroy(guideArrowInstance);
guideArrowInstance = null;
guideArrowComponent = null;
isGuideArrowActive = false;
}
}
#endregion
[Server]
public void CreateEnemyUI(Enemy enemy)
{
GameObject enemyUI = Instantiate(EnemyUIPre);
NetworkServer.Spawn(enemyUI);
EnemyUI enemyUIScript = enemyUI.GetComponent<EnemyUI>();
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();
}
#region
/// <summary>
/// 修复炮塔
/// </summary>
public void RepairTower()
{
}
/// <summary>
/// 进入联控
/// </summary>
[Server]
public void JoinControlTower(int playerIndex)
{
PlayJointMusic();
// 修改所有炮塔模式
foreach (Tower tower in TowerList.Values)
{
if (tower.controllerId == -1)
{
tower.controllerId = playerIndex;
tower.mode = TowerMode.Joint;
}
}
TriggerEvent("JoinControlStart", playerIndex);
CoroutineTaskManager.Instance.WaitSecondTodo(() =>
{
ChangeBgmRpc(1);
foreach (Tower tower in TowerList.Values)
{
if (tower.mode == TowerMode.Joint)
{
tower.controllerId = playerIndex;
tower.mode = tower.originalMode;
}
TriggerEvent("JoinControlEnd", playerIndex);
}
}, 30f);
}
#endregion
[ClientRpc]
public void TriggerEvent(string key, int param)
{
DragonLi.Core.EventDispatcher.TriggerEvent(key, param);
}
public void AddScore(string index, int damage)
{
if (SettleInfos.ContainsKey(index))
{
SettleInfos[index].Score += damage;
}
string jsonStr = JsonMapper.ToJson(SettleInfos);
settleData = jsonStr;
}
public void SetTitle(string index, string title)
{
if (SettleInfos.ContainsKey(index))
{
SettleInfos[index].Title = title;
}
Debug.Log(SettleInfos);
}
public void SetPlayerName(string index)
{
if (SettleInfos.ContainsKey(index))
{
SettleInfos[index].playerName = int.Parse(index);
}
}
[Server]
public void AddData(int playerIndex)
{
string key = playerIndex.ToString();
if (!SettleInfos.ContainsKey(key))
{
SettleInfos.Add(key, new SettleInfo());
}
}
/// <summary>
/// 移除玩家数据(当玩家断开连接时调用)
/// </summary>
[Server]
public void RemovePlayerData(int playerIndex)
{
string key = playerIndex.ToString();
if (SettleInfos.ContainsKey(key))
{
SettleInfos.Remove(key);
}
if (players.ContainsKey(playerIndex))
{
players.Remove(playerIndex);
}
}
public string SerializeSettleInfos()
{
return JsonUtility.ToJson(SettleInfos);
}
#region
public string GetLessTimeStr()
{
string res = "";
if (isStart)
{
res = FormatTime((int)(vistEnd - DateTime.Now.Subtract(new DateTime(1970, 1, 1)).TotalSeconds));
}
return res;
}
public int GetLessTimeSeconds()
{
return (int)(vistEnd - DateTime.Now.Subtract(new DateTime(1970, 1, 1)).TotalSeconds);
}
// 时分秒
public string FormatTime(int 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
public void PlaySound2DRPC(string sound)
{
MasterAudio.PlaySound(sound);
}
[ClientRpc]
public void PlaySound3DRPC(string sound, Transform tran, bool isStop)
{
if (isStop)
MasterAudio.StopAllSoundsOfTransform(tran);
MasterAudio.PlaySound3DAtTransform(sound, tran);
}
public void PlayJointMusic()
{
MasterAudio.PlaySound(JointIn);
GameLocal.Ins.BGMState.StateChange(2);
}
}