1261 lines
35 KiB
C#
1261 lines
35 KiB
C#
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);
|
||
}
|
||
} |