/* ======================== GameMainPanel_FishBase ======================== 【主控制层职责】 负责:输入 → 旋转 → 攻击 → 子弹 → 锁定 → 网络同步 → 广播处理 不负责:动画(TurretData)、发射点(TurretData)、金币(SitPosData) ======================================================================= ① InputModule(输入系统) ├── HandleInput() 主入口 ├── 按下(PressDown) ├── 长按(PressHold) ├── 松开(PressUp) ├── CheckTouchIsChangeTurret() 判断是否点到“换炮台按钮” └── HandleChangeTurretHold() 长按换炮台按钮 → 打开换炮台面板 ----------------------------------------------------------------------- ② RotateModule(旋转系统) ├── HandleRotate() 手动旋转入口 ├── RotateToTouch() 朝触摸点旋转 ├── RotateToLockTarget() 朝锁定目标旋转 ├── HandleAutoRotate() 自动开火时的自动旋转 └── AutoSweepRotate() 自动扫射(左右摆动) ----------------------------------------------------------------------- ③ FireModule(攻击系统) ├── TryFire() 长按触发攻击(冷却计时) ├── FireBullet() 发射子弹(普通/三射/双倍/锁定) ├── StartLaser() 激光开始(LaserStart) ├── LoopLaser() 激光持续(LaserLoop) └── StopLaser() 激光结束(LaserEnd) ----------------------------------------------------------------------- ④ BulletModule(子弹系统) ├── CreateBullet() 创建子弹实例 ----------------------------------------------------------------------- ⑤ LockModule(锁定系统) ├── HandleLock() 点击锁定 ├── 自动锁定(AutoLock) 可选扩展 ├── CancelLock() 取消锁定 ├── UpdateLockFX() 锁定特效跟随目标 └── SyncLock() 网络同步锁定状态 ----------------------------------------------------------------------- ⑥ NetSyncModule(网络同步) ├── SyncRotate() 同步旋转 ├── SyncFire() 同步发射 ├── SyncLock() 同步锁定 ├── SyncTurretState() 同步激光状态 └── SyncChangeTurret() 同步炮台切换(可选) ----------------------------------------------------------------------- ⑦ NetReceiveModule(服务器广播处理) ├── SyncGameData_Rotate() 广播旋转 ├── SyncGameData_FireBullet() 广播发射 ├── SyncGameData_ChangeTarget() 广播锁定 ├── SyncGameData_ChangeTurret() 广播炮台切换(可选) └── SyncGameData_LaserState() 广播激光状态(可选) ----------------------------------------------------------------------- ⑧ ResourceModule(资源加载) ├── LoadResources() 加载音频/鱼资源/路线 └── UnLoadResources() 卸载资源 ----------------------------------------------------------------------- ⑨ PlayerJoinModule(玩家加入/退出) ├── JoinGame() 玩家加入房间 └── ExitGame() 玩家退出房间 ======================================================================= */ using Assets.MyGame.Scripts.Net; using Assets.MyGame.Scripts.Net.API; using Assets.MyGame.Scripts.Net.APIResponses.Enum; using Assets.MyGame.Scripts.Net.WebResponses; using DG.Tweening; using Newtonsoft.Json; using Spine.Unity; using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using TMPro; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.EventSystems; using UnityEngine.InputSystem; using UnityEngine.Playables; using UnityEngine.SceneManagement; using UnityEngine.UI; public class GameMainPanel_FishBase : GameMainPanel_Base { [Header("游戏场景")] public string sceneName; public GameTypeEnum gameType; [Header("设置")] protected bool isOpenSetting; public Button btn_SettingList; public Transform SittingListParent; public Button btn_TuJian; public Button btn_ChangeTurret; public Button btn_TuiChu; public Button btn_Setting; //[Header("我在这")] public GameObject mySitAnim; public Dictionary dic_Skill_Toggle = new Dictionary();//开关型技能 [Header("Skillbottom")] public Toggle toggle_SkillAuto; public Toggle toggle_Lock; public Toggle toggle_Three; public Toggle toggle_Shuang; public Toggle toggle_BaoZou; public Image img_BaoZou; public float frenzyDuration = 5f; // 疯狂状态持续时间 public float currFrenzyTime = 0f; public Toggle toggle_BingFeng; public Image img_BingFeng; public GameObject bingFengEffect; // 冰封特效预制体(首次Instantiate,之后激活/隐藏) private GameObject _bingFengInstance; // 克隆出来的实例 public float bingFengDuration = 5f; // 冰封状态持续时间 public float currBingFengTime = 0f; private Dictionary _frozenAnimSpeeds = new Dictionary(); [Header("Skillleft")] public Button btn_GuaGuaKa; public TextMeshProUGUI tmp_GGKCount; public List list_Sits = new List(); public SitPosData currSitData; [Header("攻击参数")] public float attackTime = 0.5f; float clickTime = 0; private Dictionary botFireTimer = new Dictionary(); // 人机开火计时 public GameObject bullet_Test; public GameObject bulletParent; float changeTurretTime = 0f; bool isChangingTurret = false; int bulletCount = 0; public GameObject boardCast_Boss; AudioClip bulletClip; public List paoBeiList = new List(); public ProgressBar JinDuTiao; #region UI bool isfirstShow = true; protected override void OnShow(object args = null) { base.OnShow(args); LoadResources(); RefreshGGKCount(); //UIManager.Instance.ShowBoard("太好看了"); var v = JsonConvert.DeserializeObject>(args.ToString()); if (v.arguments.Count > 0) { RoomJoinNotice roomJoinNotice = v.arguments[0]; if (isfirstShow) { int t = GameManager.Instance.gameData.currRoomData.gameType; int l = GameManager.Instance.gameData.currRoomData.roomLevel; foreach (var item in GameManager.Instance.gameConfig.roomConfigs) { if (item.gameType == t && l == GameManager.Instance.gameData.currRoomData.roomLevel) { paoBeiList = item.cannonMultipleList; } } string roomId = GameManager.Instance.gameData.currRoomData.roomId; //string roomId = GameManager.Instance.gameData.currRoomData; foreach (var item in GameManager.Instance.gameData.currRoomData.players) { JoinGame(item.position, item.gold, item.diamond, item.cannonMultiple, 0, item.userId, item.cannonId); } // 生成人机:倒着填空余座位 int botCount = GameManager.Instance.gameData.currRoomData.robotCount; if (botCount > 0 && GameManager.Instance.enableBot) { var usedPositions = new HashSet(); foreach (var p in GameManager.Instance.gameData.currRoomData.players) usedPositions.Add(p.position); // 第一个普通炮台的编号作为默认 int defaultTurret = 1; var turretList = TurretResourcesData.GetTurretDataByType(1); if (turretList != null && turretList.Count > 0) defaultTurret = turretList[0].turretNumber; int botGold = 100000; int botDiamond = 500; int botMultiple = paoBeiList != null && paoBeiList.Count > 0 ? paoBeiList[0] : 100; for (int pos = list_Sits.Count - 1; pos >= 0 && botCount > 0; pos--) { if (usedPositions.Contains(pos)) continue; JoinGame(pos, botGold, botDiamond, botMultiple, 0, "bot_" + pos, defaultTurret.ToString()); var botSit = list_Sits[pos]; if (botSit != null) botSit.isBot = true; botCount--; } } GameManager.Instance.gameData.roomJoinNotice = v.arguments[0]; isfirstShow = false; foreach (var item in list_Sits) { if (item.posID == roomJoinNotice.position) { currSitData = item; } else { item.btn_TurretAdd.gameObject.SetActive(false); item.btn_TurretMinus.gameObject.SetActive(false); } } //// currSitData.ChangeTurret(1); — 已由 SitPosData.OnEnable 自动加载 if (currSitData.currentTurret != null) SyncChangeTurret(currSitData.currentTurret.turretNumber); currSitData.lockFX.transform.GetChild(0).gameObject.SetActive(false); currSitData.lockFX.transform.GetChild(1).gameObject.SetActive(true); if (mySitAnim != null && currSitData.turretParent != null) mySitAnim.transform.position = currSitData.turretParent.position; } else { // 玩家加入 → 如果座位被bot占用则替换 var targetSit = list_Sits.Find(s => s.posID == roomJoinNotice.position); if (targetSit != null && targetSit.isBot) { targetSit.isBot = false; targetSit.gameObject.SetActive(false); } JoinGame(roomJoinNotice.position, roomJoinNotice.gold, roomJoinNotice.diamond, roomJoinNotice.cannonMultiple, 0, roomJoinNotice.userId, roomJoinNotice.cannonId); } } else { UIManager.Instance.ShowBoardError("获取信息失败"); } } private void RefreshGGKCount() { var props = GameManager.Instance.user?.props; int count = 0; if (props != null) { foreach (var p in props) { if (p.number == "40005" && p.count > 0) { count = p.count; break; } } } if (tmp_GGKCount != null) tmp_GGKCount.text = count.ToString(); } protected override void OnHide() { base.OnHide(); UnLoadResources(); SceneManager.UnloadSceneAsync(sceneName); WebSocketManager.Instance.Close(SocketType.game); } #endregion #region 生命周期 private float baseAttackTime; public override void Awake() { base.Awake(); GameManager.Instance.OnUserDataChanged += RefreshGGKCount; baseAttackTime = attackTime; btn_ChangeTurret.onClick.AddListener(() => { var panel = subUIManager.Get(UIPanel.TurretPanel); if (panel != null && panel.gameObject.activeSelf) subUIManager.Hide(UIPanel.TurretPanel); else { subUIManager.ShowAsync(UIPanel.TurretPanel, (ItemPanelSelect.Turrect, true)); StartCoroutine(CloseSettingList()); } }); btn_Setting.onClick.AddListener(() => {subUIManager.ShowAsync(UIPanel.SettingPanel); }); btn_TuiChu.onClick.AddListener(() => { WebSocketSendList.LeaveRoom(GameManager.Instance.gameData.currRoomData.roomId, currSitData.user); CreateFishManager.Instance.fangzhu = false; }); // 服务器广播监听 EventCenter.Instance.AddEventListener(ConstEventString.SyncGameData_Rotate, SyncGameData_Rotate); EventCenter.Instance.AddEventListener(ConstEventString.SyncGameData_FireBullet, SyncGameData_FireBullit); EventCenter.Instance.AddEventListener(ConstEventString.SyncGameData_ChangeTargetMonster, SyncGameData_ChangeTargetMonster); EventCenter.Instance.AddEventListener(ConstEventString.SyncGameData_RendromDieFish, SyncGameData_RendromDieFish); EventCenter.Instance.AddEventListener(ConstEventString.SyncGameData_TurretState, SyncGameData_TurretState); EventCenter.Instance.AddEventListener(ConstEventString.SyncGameData_ChangeTurret, SyncGameData_ChangeTurret); EventCenter.Instance.AddEventListener(ConstEventString.SyncGameData_ChangeCannonMultiple, SyncGameData_ChangeCannonMultiple); EventCenter.Instance.AddEventListener(ConstEventString.SyncGameData_FreezeFish, SyncGameData_FreezeFish); btn_TuJian.onClick.AddListener(() => { subUIManager.ShowAsync(UIPanel.GameTuJian); StartCoroutine(CloseSettingList()); }); btn_GuaGuaKa.onClick.AddListener(() => { subUIManager.ShowAsync(UIPanel.GuaGuaKa); }); } public virtual void OnDestroy() { EventCenter.Instance.RemoveEventListener(ConstEventString.SyncGameData_Rotate, SyncGameData_Rotate); EventCenter.Instance.RemoveEventListener(ConstEventString.SyncGameData_FireBullet, SyncGameData_FireBullit); EventCenter.Instance.RemoveEventListener(ConstEventString.SyncGameData_ChangeTargetMonster, SyncGameData_ChangeTargetMonster); EventCenter.Instance.RemoveEventListener(ConstEventString.SyncGameData_RendromDieFish, SyncGameData_RendromDieFish); EventCenter.Instance.RemoveEventListener(ConstEventString.SyncGameData_TurretState, SyncGameData_TurretState); EventCenter.Instance.RemoveEventListener(ConstEventString.SyncGameData_ChangeTurret, SyncGameData_ChangeTurret); EventCenter.Instance.RemoveEventListener(ConstEventString.SyncGameData_ChangeCannonMultiple, SyncGameData_ChangeCannonMultiple); EventCenter.Instance.RemoveEventListener(ConstEventString.SyncGameData_FreezeFish, SyncGameData_FreezeFish); } public virtual void Start() { btn_SettingList.onClick.AddListener(() => { if (isOpenSetting) StartCoroutine(CloseSettingList()); else StartCoroutine(OpenSettingList()); }); if (toggle_Lock != null) { toggle_Lock.onValueChanged.AddListener(on => { if (!on) { // 本地清理锁定 if (currSitData != null) { currSitData.ClearLock(); SyncTurretState(TurretState.Idle); } // 同步给服务器:取消锁定 SyncGameData sgd = new SyncGameData { sitPos = currSitData.posID, frameType = FrameDataType.changeAutoTarget, time = BeijingTimeManager.Instance.CurrentTimestampMs, frameData = new FrameData_ChangeAutoTarget { targetGuid = null } }; WebSocketSendList.SyncGameData(sgd); } }); } toggle_BaoZou.onValueChanged.AddListener(on => { // UI 开关只是触发,但最终状态以 currGameType 为准 CreateFishManager.Instance.currGameType.isFrenzy = on; SetFrenzy(on); if (on) currFrenzyTime = frenzyDuration; }); toggle_SkillAuto.onValueChanged.AddListener(on => { if (!on) { // 自动开火关闭 → 立即切回 Idle currSitData.ChangeState(TurretState.Idle); SyncTurretState(TurretState.Idle); } }); toggle_BingFeng.onValueChanged.AddListener(on => { if (on) { // 开始冰冻 currBingFengTime = bingFengDuration; if (bingFengEffect != null) { if (_bingFengInstance == null) _bingFengInstance = Instantiate(bingFengEffect); _bingFengInstance.SetActive(true); } CreateFishManager.Instance.FreezeAllFish(); PauseAllFishAnimations(); SyncFreeze(true); } else { // 手动关闭 → 解冻 if (_bingFengInstance != null) _bingFengInstance.SetActive(false); if (CreateFishManager.Instance.isFrozen) { CreateFishManager.Instance.UnfreezeAllFish(); ResumeAllFishAnimations(); SyncFreeze(false); } } }); } public bool isFrenzy; public void SetFrenzy(bool on) { bool frenzy = CreateFishManager.Instance.currGameType.isFrenzy; // 攻击速度翻倍 attackTime = frenzy ? baseAttackTime * 0.5f : baseAttackTime; // 动画倍速 if (currSitData != null && currSitData.currentTurret != null) currSitData.currentTurret.SetFrenzy(frenzy); } public void OnEnable() { } int ina = 0; protected virtual void Update() { HandleAutoRotate(); HandleInput(); CheckLockTargetValid();//锁定目标失效自动清除 HandleAutoFire(); // 自动开火真正发射 HandleLock(); UpdateLockFX(); RefreshTripleLockIcons(); HandleAutoLock(); LoopLaser(); SettingClose(); DaoJiShi(); HandleBotAI(); WebSocketSendList.FlushSyncQueues(Time.deltaTime); } float sittingTime = 10; float currSittingTime; /// /// 等待10秒未操作关闭设置面板 /// public void SettingClose() { //if (isOpenSetting) StartCoroutine(CloseSettingList()); //else StartCoroutine(OpenSettingList()); if (isOpenSetting) { currSittingTime -= Time.deltaTime; if (currSittingTime <= 0) { StartCoroutine(CloseSettingList()); } } else { currSittingTime = 10; } } #endregion #region 资源加载模块 public async void LoadResources() { CreateFishManager.Instance.isResources = false; await AudioResourcesData.LoadAudioGame(); JinDuTiao.SetProgress(0.2f); await MonsterMoveLine.LoadBezier(); JinDuTiao.SetProgress(0.4f); await FishResourcesData.LoadFishAssetsData(); JinDuTiao.SetProgress(0.6f); await Addressables.LoadAssetsAsync("Audio", clip => { if (clip.name == "PW_Shoot") bulletClip = clip; }).Task; JinDuTiao.SetProgress(0.8f); await Task.Delay(100); CreateFishManager.Instance.EnterRoom(gameType); CreateFishManager.Instance.isResources = true; JinDuTiao.SetProgress(0.9f); await Task.Delay(2000); JinDuTiao.SetProgress(1); await Task.Delay(300); JinDuTiao.Hide(); } public async void UnLoadResources() { CreateFishManager.Instance.ExitRoom(); CreateFishManager.Instance.isResources = false; await FishResourcesData.UnLoadFishAssetsData(); await AudioResourcesData.UnLoadAudioGame(); await MonsterMoveLine.UnLoadBezier(); } #endregion #region 输入系统(InputModule) private bool IsPointerOverUI(Vector2 pos) { PointerEventData eventData = new PointerEventData(EventSystem.current); eventData.position = pos; List results = new List(); EventSystem.current.RaycastAll(eventData, results); return results.Count > 0; } public struct CrossPlatformInput { public bool isPressed; public bool wasPressedThisFrame; public bool wasReleasedThisFrame; public Vector2 position; } private CrossPlatformInput GetInput() { CrossPlatformInput input = new CrossPlatformInput(); // ============================ // PC / Editor → 鼠标输入 // ============================ #if UNITY_STANDALONE || UNITY_EDITOR if (Mouse.current != null) { input.isPressed = Mouse.current.leftButton.isPressed; input.wasPressedThisFrame = Mouse.current.leftButton.wasPressedThisFrame; input.wasReleasedThisFrame = Mouse.current.leftButton.wasReleasedThisFrame; input.position = Mouse.current.position.ReadValue(); } #else // ============================ // 移动平台 → 触摸输入 // ============================ var touch = Touchscreen.current?.primaryTouch; if (touch != null) { input.isPressed = touch.press.isPressed; input.wasPressedThisFrame = touch.press.wasPressedThisFrame; input.wasReleasedThisFrame = touch.press.wasReleasedThisFrame; input.position = touch.position.ReadValue(); } #endif return input; } private void HandleInput() { CrossPlatformInput input = GetInput(); if (!input.isPressed && !input.wasPressedThisFrame && !input.wasReleasedThisFrame) return; if (input.wasPressedThisFrame) { // ⭐ UI 点击过滤 if (IsPointerOverUI(input.position)) return; } // 按下 if (input.wasPressedThisFrame) { isChangingTurret = CheckTouchIsChangeTurret(input.position); clickTime = attackTime; if (mySitAnim != null && mySitAnim.gameObject.activeSelf) mySitAnim.gameObject.SetActive(false); if (!isChangingTurret) { currSitData.ChangeState(TurretState.Attack); SyncTurretState(TurretState.Attack); } } // 长按 if (input.isPressed) { if (isChangingTurret) { HandleChangeTurretHold(); return; } // 锁定模式:不手动旋转/开火,交给 HandleAutoRotate + HandleAutoFire if (toggle_Lock.isOn) return; // UI 上不旋转不发射 if (IsPointerOverUI(input.position)) return; HandleRotate(input.position); TryFire(); } // 松开 if (input.wasReleasedThisFrame) { if (isChangingTurret) { isChangingTurret = false; changeTurretTime = 0; CloseChangeTurretUI(); return; } currSitData.ChangeState(TurretState.Idle); SyncTurretState(TurretState.Idle); } } private void HandleChangeTurretHold() { changeTurretTime += Time.deltaTime; if (changeTurretTime > 1f) { // 长按换炮台:已改为按钮打开 TurretPanel } } private bool CheckTouchIsChangeTurret(Vector2 pos) { PointerEventData pointerData = new PointerEventData(EventSystem.current); pointerData.position = pos; List results = new List(); EventSystem.current.RaycastAll(pointerData, results); foreach (var result in results) { if (result.gameObject.CompareTag("ChangeTurret")) { SitPosData sit = result.gameObject.GetComponentInParent(); if (sit == currSitData) return true; } } return false; } private void CloseChangeTurretUI() { // currSitData.changeTurretList.SetActive(false); — 已废弃 changeTurretTime = 0; isChangingTurret = false; } #endregion #region 打开关闭右上角设置选项 public virtual IEnumerator OpenSettingList() { RectTransform tran = SittingListParent.GetComponent(); tran.gameObject.SetActive(true); tran.DOSizeDelta(new Vector2(466, 143), 0.2f); isOpenSetting = true; Vector3 v3 = btn_SettingList.transform.localScale; v3.x = Mathf.Abs(v3.x) * -1; btn_SettingList.transform.localScale = v3; yield break; } public virtual IEnumerator CloseSettingList() { RectTransform tran = SittingListParent.GetComponent(); Vector3 v3 = btn_SettingList.transform.localScale; v3.x = Mathf.Abs(v3.x); btn_SettingList.transform.localScale = v3; tran.DOSizeDelta(new Vector2(0, 143), 0.2f).OnComplete(() => { tran.gameObject.SetActive(false); }); isOpenSetting = false; yield break; } #endregion #region ② 炮台旋转系统(RotateModule) //private void HandleRotate(Vector2 pos) //{ // if (toggle_Lock.isOn && currSitData.monCotl_Lock != null) // { // RotateToLockTarget(currSitData); // } // else // { // RotateToTouch(currSitData, pos); // } //} private void HandleRotate(Vector2 pos) { // ⭐ 只有真正锁定目标时才自动旋转 if (toggle_Lock.isOn && currSitData.isLock && currSitData.monCotl_Lock != null) { RotateToLockTarget(currSitData); } else { // 锁定开关开着但没有锁定目标 → 允许手动旋转 RotateToTouch(currSitData, pos); } } private void RotateToTouch(SitPosData sit, Vector2 pos) { RectTransformUtility.ScreenPointToLocalPointInRectangle( sit.tra_CurrTurret.transform.parent as RectTransform, pos, null, out Vector2 localPos); Vector2 dir = localPos - sit.tra_CurrTurret.GetComponent().anchoredPosition; float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg - 90f; // 上方座位(2,3)炮台预制体默认朝上,加180°让它朝下打鱼 if (sit.IsFlipped) angle = (angle + 180f) % 360f; sit.SetRotation(angle); SyncRotate(angle); } private void RotateToLockTarget(SitPosData sit) { Vector2 screenPos = Camera.main.WorldToScreenPoint(sit.monCotl_Lock_Position.position); RectTransformUtility.ScreenPointToLocalPointInRectangle( sit.tra_CurrTurret.transform.parent as RectTransform, screenPos, null, out Vector2 localPos); Vector2 dir = localPos - sit.tra_CurrTurret.GetComponent().anchoredPosition; // ⭐ 鱼太近时 dir 接近零向量,Atan2 会不稳定导致原地转圈 → 跳过 const float minDistance = 5f; if (dir.sqrMagnitude < minDistance * minDistance) return; float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg - 90f; if (sit.IsFlipped) angle = (angle + 180f) % 360f; sit.SetRotation(angle); SyncRotate(angle); } private void HandleAutoRotate() { // 锁定模式优先 if (toggle_Lock.isOn && currSitData.isLock) { RotateToLockTarget(currSitData); return; } // 自动开火关闭 → 不自动旋转 if (!toggle_SkillAuto.isOn) return; // 自动扫射(左右摆动) AutoSweepRotate(); } private float autoRotateAngle = 0; private float autoRotateSpeed = 40f; private float autoRotateMin = -60f; private float autoRotateMax = 60f; private int autoRotateDir = 1; private void AutoSweepRotate() { autoRotateAngle += autoRotateSpeed * autoRotateDir * Time.deltaTime; if (autoRotateAngle > autoRotateMax) { autoRotateAngle = autoRotateMax; autoRotateDir = -1; } else if (autoRotateAngle < autoRotateMin) { autoRotateAngle = autoRotateMin; autoRotateDir = 1; } // 上方座位加180°,炮台朝下打鱼 float angle = currSitData.IsFlipped ? (autoRotateAngle + 180f) % 360f : autoRotateAngle; currSitData.SetRotation(angle); } private Coroutine rotateCoroutine; private void SmoothRotate(SitPosData sit, float angle) { sit.SetRotation(angle); // 先立即更新角度(逻辑角度) if (rotateCoroutine != null) StopCoroutine(rotateCoroutine); rotateCoroutine = StartCoroutine(RotateIE_Angle(sit, Quaternion.Euler(0, 0, angle))); } private IEnumerator RotateIE_Angle(SitPosData sit, Quaternion target) { float angleThreshold = 0.1f; while (true) { float angleDiff = Quaternion.Angle(sit.tra_CurrTurret.rotation, target); if (angleDiff <= angleThreshold) { sit.tra_CurrTurret.rotation = target; yield break; } sit.tra_CurrTurret.rotation = Quaternion.Lerp( sit.tra_CurrTurret.rotation, target, 0.6f ); yield return null; } } #endregion #region Skill系统(SkillModule) public void DaoJiShi() { // 暴走倒计时 if (toggle_BaoZou != null && toggle_BaoZou.isOn && frenzyDuration > 0) { if (img_BaoZou != null) img_BaoZou.fillAmount = Mathf.Clamp01(currFrenzyTime / frenzyDuration); currFrenzyTime -= Time.deltaTime; if (currFrenzyTime <= 0) { toggle_BaoZou.isOn = false; currFrenzyTime = 0f; if (img_BaoZou != null) img_BaoZou.fillAmount = 1f; } } // 冰封倒计时 if (toggle_BingFeng != null && toggle_BingFeng.isOn && bingFengDuration > 0) { if (img_BingFeng != null) img_BingFeng.fillAmount = Mathf.Clamp01(currBingFengTime / bingFengDuration); currBingFengTime -= Time.deltaTime; if (currBingFengTime <= 0) { toggle_BingFeng.isOn = false; currBingFengTime = 0f; if (img_BingFeng != null) img_BingFeng.fillAmount = 1f; // 解冻由 toggle.onValueChanged(false) 触发,不在此重复 } } } #endregion #region ③ 攻击系统(FireModule) private void TryFire() { clickTime += Time.deltaTime; if (clickTime < attackTime) return; // ⭐ 子弹上限校验(先于扣费和状态切换) if (IsBulletLimitReached(currSitData.user)) { clickTime = 0; // 重置计时,下次再试 return; } clickTime = 0; float angle = currSitData.currentAngle; bool isSanShe = toggle_Three.isOn; bool isDouble = toggle_Shuang.isOn; bool isLock = toggle_Lock.isOn; //FireBullet(currSitData, angle, isSanShe, isDouble, isLock); SyncFire(angle, isSanShe, isDouble, isLock); } private void FireBullet(SitPosData sit, float angle, bool isSanShe, bool isDouble, bool isLock, List bullitIDs, List fireTargetMonsterIDs = null) { Vector2 startPos = sit.GetBulletStartPos(); // ⭐ 三发 + 锁定 + 有多目标:每发子弹瞄准不同的目标 if (isSanShe && isLock && fireTargetMonsterIDs != null && fireTargetMonsterIDs.Count > 0) { for (int i = 0; i < 3 && i < fireTargetMonsterIDs.Count; i++) { // 按 GUID 查找目标 MonsterContral targetMonster = null; Transform targetHitPos = null; if (CreateFishManager.Instance.fishDatas_hashCodekey.TryGetValue(fireTargetMonsterIDs[i], out var m) && m != null && m.gameObject.activeInHierarchy) { targetMonster = m; targetHitPos = m.transform; // 优先用活跃 collider if (m.colliderList != null && m.colliderList.Count > 0) { foreach (var col in m.colliderList) { if (col.gameObject.activeInHierarchy) { targetHitPos = col; break; } } } } if (targetMonster != null) { // 计算朝目标的角度 Vector3 targetWorldPos = targetHitPos.position; Vector2 dir = ((Vector2)targetWorldPos - startPos).normalized; float targetAngle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg - 90f; string bulletId = i < bullitIDs.Count ? bullitIDs[i] : bullitIDs[0]; CreateBullet(sit, startPos, targetAngle, isDouble, true, bulletId, targetMonster, targetHitPos); } } } else if (isSanShe) { CreateBullet(sit, startPos, angle - 20, isDouble, isLock, bullitIDs[0]); CreateBullet(sit, startPos, angle, isDouble, isLock, bullitIDs[1]); CreateBullet(sit, startPos, angle + 20, isDouble, isLock, bullitIDs[2]); } else { CreateBullet(sit, startPos, angle, isDouble, isLock, bullitIDs[0]); } AudioManager.Instance.PlayFX(bulletClip); } private bool isLaser = false; private void StartLaser() { isLaser = true; currSitData.ChangeState(TurretState.LaserStart); SyncTurretState(TurretState.LaserStart); } private void LoopLaser() { if (!isLaser) return; // TODO: 激光持续伤害 / 特效 } private void StopLaser() { isLaser = false; currSitData.ChangeState(TurretState.LaserEnd); SyncTurretState(TurretState.LaserEnd); } private float autoFireTimer = 0f; //private void HandleAutoFire() //{ // if (!toggle_SkillAuto.isOn) return; // if (currSitData == null) return; // autoFireTimer += Time.deltaTime; // if (autoFireTimer < attackTime) return; // autoFireTimer = 0f; // float angle = currSitData.currentAngle; // bool isSanShe = toggle_Three.isOn; // bool isDouble = toggle_Shuang.isOn; // bool isLock = toggle_Lock.isOn; // FireBullet(currSitData, angle, isSanShe, isDouble, isLock); // SyncFire(angle, isSanShe, isDouble, isLock); //} private void HandleAutoFire() { if (currSitData == null) return; bool needAutoFire = toggle_SkillAuto.isOn || (toggle_Lock.isOn && currSitData.isLock && currSitData.lockFX.gameObject.activeSelf); if (!needAutoFire) { autoFireTimer = 0f; return; } autoFireTimer += Time.deltaTime; if (autoFireTimer < attackTime) return; // ⭐ 子弹上限校验(先于状态切换和扣费) if (IsBulletLimitReached(currSitData.user)) { autoFireTimer = 0f; // 重置计时,下次再试 return; } autoFireTimer = 0f; float angle = currSitData.currentAngle; bool isSanShe = toggle_Three.isOn; bool isDouble = toggle_Shuang.isOn; bool isLock = toggle_Lock.isOn; // ⭐ 自动攻击也要播放攻击动画 currSitData.ChangeState(TurretState.Attack); SyncTurretState(TurretState.Attack); //FireBullet(currSitData, angle, isSanShe, isDouble, isLock); SyncFire(angle, isSanShe, isDouble, isLock); } #endregion #region 多目标查找(TripleLock) /// 鱼锁定优先级:特殊Boss(5) > Boss鱼(4) > 特殊鱼(3) > 黄金鱼(2) > 普通鱼(1) private int GetFishLockPriority(MonsterContral monster) { switch (monster.fishenum) { case FishTypeEnum.特殊Boss: return 5; case FishTypeEnum.Boss鱼: return 4; case FishTypeEnum.特殊鱼: return 3; case FishTypeEnum.黄金鱼: return 2; case FishTypeEnum.普通鱼: return 1; default: return 0; } } /// /// 查找 fire 时的多目标(三发锁定用)。 /// 第一个目标 = 主锁 monCotl_Lock(打哪锁哪),剩余从 Boss/特殊Boss/特殊鱼/黄金鱼 按优先级补满 /// private List<(MonsterContral monster, Transform hitPos)> FindFireTargets(int maxCount) { var result = new List<(MonsterContral monster, Transform hitPos)>(); var selected = new HashSet(); if (CreateFishManager.Instance == null) return result; // ① 主锁目标排第一(打哪锁哪) if (currSitData.isLock && currSitData.monCotl_Lock != null && currSitData.monCotl_Lock.gameObject.activeInHierarchy) { Transform pos = currSitData.monCotl_Lock_Position != null && currSitData.monCotl_Lock_Position.gameObject.activeInHierarchy ? currSitData.monCotl_Lock_Position : currSitData.monCotl_Lock.transform; result.Add((currSitData.monCotl_Lock, pos)); selected.Add(currSitData.monCotl_Lock.guid); if (result.Count >= maxCount) return result; } // ② 按优先级从分类字典补满剩余位置 var dicts = new Dictionary[] { CreateFishManager.Instance.fishDatas_hashCodekey_TS_Boss, CreateFishManager.Instance.fishDatas_hashCodekey_Boss, CreateFishManager.Instance.fishDatas_hashCodekey_TS, CreateFishManager.Instance.fishDatas_hashCodekey_HJ, }; foreach (var dict in dicts) { if (dict == null) continue; foreach (var kv in dict) { MonsterContral m = kv.Value; if (m == null || !m.gameObject.activeInHierarchy) continue; if (selected.Contains(m.guid)) continue; // 跳过已选中的 Transform pos = m.transform; if (m.colliderList != null && m.colliderList.Count > 0) { foreach (var col in m.colliderList) { if (col.gameObject.activeInHierarchy) { pos = col; break; } } } result.Add((m, pos)); selected.Add(m.guid); if (result.Count >= maxCount) return result; } } return result; } #endregion #region 锁定系统(LockModule) /// /// 刷新三发锁定图标:三发+锁定开启时显示3个锁定圈 /// private void RefreshTripleLockIcons() { if (currSitData == null) return; bool isTripleLock = toggle_Three.isOn && toggle_Lock.isOn && currSitData.isLock; if (!isTripleLock) { currSitData.HideTripleLockIcons(); return; } // 已经有三发目标 → 跳过 if (currSitData.tripleLockTargets.Count > 0) return; var targets = FindFireTargets(3); if (targets.Count > 0) currSitData.SetTripleLockTargets(targets); } //private void HandleLock() //{ // CrossPlatformInput input = GetInput(); // if (!toggle_Lock.isOn) return; // // 只有按下或抬起的那一帧触发锁定(保留你原来的逻辑) // if (!input.wasReleasedThisFrame && !input.wasPressedThisFrame) return; // // UI 点击过滤 // if (IsPointerOverUI(input.position)) // return; // // 🔧 修正:屏幕坐标转世界坐标需要指定 Z 轴(2D 正交相机专用) // Vector3 mousePos = input.position; // mousePos.z = -Camera.main.transform.position.z; // 关键修正 // Vector2 worldPos = Camera.main.ScreenToWorldPoint(mousePos); // // 🔧 优化:将 LayerMask 缓存为成员变量,避免每次调用 GetMask(非必要,但更好) // // 你可以在 Start 中初始化:fishLayerMask = LayerMask.GetMask("Fish"); // // 这里直接写原方式也可以,但建议如下: // int fishLayer = LayerMask.GetMask("Fish"); // RaycastHit2D hit = Physics2D.Raycast(worldPos, Vector2.zero, 1, fishLayer); // if (hit.collider != null) // { // MonsterContral monster = hit.collider.GetComponentInParent(); // currSitData.SetLockTarget(monster, hit.collider.transform); // // 保留你原来的调用方式:new List { monster?.guid } // SyncLock(new List { monster?.guid }, monster.colliderList.IndexOf(hit.collider.transform)); // } // else // { // currSitData.ClearLock(); // SyncTurretState(TurretState.Idle); // SyncLock(null); // 保留你原来的重载(可能是 SyncLock(object) 或 SyncLock(string)) // } //} private void HandleLock() { CrossPlatformInput input = GetInput(); if (!toggle_Lock.isOn) return; if (!input.wasPressedThisFrame && !input.wasReleasedThisFrame) return; if (IsPointerOverUI(input.position)) return; // 射线从相机出发,指向鼠标点击点 Vector3 mousePos = input.position; mousePos.z = -Camera.main.transform.position.z; Vector2 mouseWorld = Camera.main.ScreenToWorldPoint(mousePos); //Vector2 origin = Camera.main.transform.position; //Vector2 dir = (mouseWorld - origin).normalized; Vector2 dir = Vector2.zero; float distance = 10000f; int fishLayer = LayerMask.GetMask("Fish"); // 获取所有命中(可能有多个) RaycastHit2D[] hits = Physics2D.RaycastAll(mouseWorld, dir, distance, fishLayer); if (hits.Length == 0) { // 未命中:清除锁定 currSitData.ClearLock(); SyncTurretState(TurretState.Idle); SyncLock(null); return; } // 按 Z 轴深度选择最前面的(Z 值最大的,因为相机在负 Z,更大 Z 更近) // 如果您的相机在 Z = -10,物体 Z 范围 0 ~ 10,则 Z 越大越靠近相机 RaycastHit2D bestHit = hits[0]; float bestDepth = bestHit.collider.transform.position.z; foreach (var hit in hits) { float depth = hit.collider.transform.position.z; if (depth > bestDepth) // 更大 Z → 更近 { bestDepth = depth; bestHit = hit; } } // 处理选中的最佳命中 MonsterContral monster = bestHit.collider.GetComponentInParent(); Transform lockTr = (monster.colliderList == null || monster.colliderList.Count == 0) ? (monster.lockPoint==null ? bestHit.collider.transform : monster.lockPoint) : bestHit.collider.transform; currSitData.SetLockTarget(monster, lockTr); SyncLock(new List { monster?.guid }, monster.colliderList.IndexOf(bestHit.collider.transform)); } private void UpdateLockFX() { foreach (var sit in list_Sits) { if (!sit.isLock || sit.monCotl_Lock == null) continue; // 更新主锁定图标 UpdateSingleLockFX(sit, sit.monCotl_Lock, sit.monCotl_Lock_Position, sit.lockFX); // 更新三发锁定额外图标 for (int i = 0; i < sit.lockFX_Triple.Count && i < sit.tripleLockTargets.Count; i++) { var icon = sit.lockFX_Triple[i]; var monster = sit.tripleLockTargets[i]; var hitPos = sit.tripleLockPositions[i]; if (icon == null || monster == null || hitPos == null) continue; UpdateSingleLockFX(sit, monster, hitPos, icon); } } } private void UpdateSingleLockFX(SitPosData sit, MonsterContral monster, Transform hitPos, GameObject icon) { Vector3 pos = Vector3.zero; if (!hitPos.gameObject.activeInHierarchy) { if (monster.colliderList != null && monster.colliderList.Count > 0) { bool found = false; foreach (Transform t in monster.colliderList) { if (t.gameObject.activeInHierarchy) { pos = t.position; found = true; break; } } if (!found) { if (icon.activeSelf) icon.SetActive(false); return; } } else { var lp = monster.lockPoint ?? monster.transform; pos = lp.position; } if (!hitPos.gameObject.activeInHierarchy) { if (icon.activeSelf) icon.SetActive(false); return; } } else { pos = hitPos.position; if (!icon.activeSelf) icon.SetActive(true); } pos.z = Camera.main.transform.position.z + 1; icon.transform.position = pos; } private void CheckLockTargetValid() { if (!currSitData.isLock) return; var monster = currSitData.monCotl_Lock; // 目标无效:被对象池回收 / 不激活 / 被销毁 if (monster == null || !monster.gameObject.activeInHierarchy) { currSitData.ClearLock(); // 清除锁定 SyncTurretState(TurretState.Idle); SyncLock(null); // 同步服务器 // ⭐⭐⭐ 关键:怪死了 → 动画回到 Idle ⭐⭐⭐ currSitData.ChangeState(TurretState.Idle); SyncTurretState(TurretState.Idle); } } private void HandleAutoLock() { // 自动锁定必须满足:自动开火 + 锁定开关开启 if (!toggle_SkillAuto.isOn) return; if (!toggle_Lock.isOn) return; // ✔ 自动锁定必须在锁定开关开启时才工作 // 已经有锁定目标并且还活着,就不重复找 if (currSitData.isLock && currSitData.monCotl_Lock != null && currSitData.monCotl_Lock.gameObject.activeInHierarchy) { return; } MonsterContral target = FindAutoLockTarget(); if (target != null) { currSitData.SetLockTarget(target, target.transform); if (target.colliderList.Count > 0) { for (int i = 0; i < target.colliderList.Count; i++) { if (target.colliderList[i].gameObject.activeInHierarchy) { SyncLock(new List { target?.guid }, i); } } } else { SyncLock(new List { target?.guid }, -1); } } else { currSitData.ClearLock(); SyncTurretState(TurretState.Idle); SyncLock(null); } } private MonsterContral FindAutoLockTarget() { if (CreateFishManager.Instance == null || CreateFishManager.Instance.fishDatas_hashCodekey == null || CreateFishManager.Instance.fishDatas_hashCodekey.Count == 0) return null; MonsterContral best = null; float bestSqr = float.MaxValue; Vector3 origin = currSitData.tra_CurrTurret.position; foreach (var kv in CreateFishManager.Instance.fishDatas_hashCodekey) { MonsterContral m = kv.Value; if (m == null || !m.gameObject.activeInHierarchy) continue; float sqr = (m.transform.position - origin).sqrMagnitude; if (sqr < bestSqr) { bestSqr = sqr; best = m; } } return best; } //private void HandleAutoLock() //{ // if (!toggle_SkillAuto.isOn) return; // if (toggle_Lock.isOn) return; // if (currSitData.isLock) return; // MonsterContral nearest = CreateFishManager.Instance; // if (nearest != null) // { // currSitData.SetLockTarget(nearest, nearest.transform); // SyncLock(nearest); // } //} #endregion #region BulletModule(子弹系统) public int sitBulletMaxCount = 100; /// /// 检查指定用户是否已达50发子弹上限 /// private bool IsBulletLimitReached(string userId) { if (bulletParent == null) return false; int count = 0; foreach (Transform child in bulletParent.transform) { if (child.TryGetComponent(out var bm) && bm.userid == userId) count++; } if (count >= sitBulletMaxCount) { UIManager.Instance.ShowBoardError("子弹已达上限"); return true; } return false; } private BullitMove CreateBullet(SitPosData sit, Vector2 pos, float angle, bool isDouble, bool isLock, string id, MonsterContral lockTarget = null, Transform lockHitPos = null) { // ⭐ 远程座位炮台未加载时补加载 if (sit.currentTurret == null) { sit.LoadTurret(sit.currentTurretNumber); if (sit.currentTurret == null) return null; } GameObject prefab = isDouble ? sit.currentTurret.bulletPrefab_Two : sit.currentTurret.bulletPrefab; // ⭐ 每座位最多100发子弹(先校验,不扣钱) int activeBullets = 0; foreach (Transform child in bulletParent.transform) { if (child.TryGetComponent(out var bm) && bm.userid == sit.user) activeBullets++; } if (activeBullets >= sitBulletMaxCount) { UIManager.Instance.ShowBoardError($"[子弹上限] 座位{sit.posID}已达{sitBulletMaxCount}发上限"); return null; } if (!sit.ConsumeGold(isDouble ? 2 : 1)) return null; var bullet = Instantiate(prefab, pos, Quaternion.Euler(0, 0, angle), bulletParent.transform) .GetComponent(); bullet.userid = sit.user; bullet.playerID = sit.user; bullet.bulletID = id; if (isLock) { if (lockTarget != null) bullet.Lock(lockTarget, lockHitPos); else bullet.Lock(sit.monCotl_Lock, currSitData.monCotl_Lock_Position); } return bullet; } #endregion #region ⑥ 网络同步(NetSyncModule) private void SyncRotate(float angle) { SyncGameData sgd = new SyncGameData { sitPos = currSitData.posID, frameType = FrameDataType.rotateTurrect, time = BeijingTimeManager.Instance.CurrentTimestampMs, frameData = new FrameData_Rotate { angle = angle } }; WebSocketSendList.SyncGameData(sgd); } private void SyncFire(float angle, bool isSanShe, bool isDouble, bool isLock) { var fireData = new FrameData_FireBullet { angle = angle, gold = currSitData.goldNum, isSanShe = isSanShe, twoRTruuect = isDouble, isSuoDing = isLock, auto = toggle_SkillAuto.isOn }; // ⭐ 三发 + 锁定:查找 3 个优先级目标,写入 monsterIDs if (isSanShe && isLock) { var targets = FindFireTargets(3); if (targets.Count > 0) fireData.monsterIDs = targets.Select(t => t.monster.guid).ToList(); } SyncGameData sgd = new SyncGameData { sitPos = currSitData.posID, frameType = FrameDataType.fireBullet, time = BeijingTimeManager.Instance.CurrentTimestampMs, frameData = fireData }; //EnqueueSync(sgd); var roomdata = GameManager.Instance.gameData.currRoomData; var user = GameManager.Instance.user; // 三射 = 3 发,否则 = 1 发 int count = isSanShe ? 3 : 1; // 新版 FireCannon 不需要 timestamp 和 sign WebSocketSendList.FireCannon( roomdata.roomId, user.id, currSitData.consumeGoldNum, count, sgd, "63215f0afa3f5b25" ); // WebSocketSendList.FireCannon(roomdata.roomId, user.id,currSitData.consumeGoldNum,1,BeijingTimeManager.Instance.CurrentTimestamp, "63215f0afa3f5b25", sgd); } private void SyncLock(List monsterguid, int index = -1) { SyncGameData sgd = new SyncGameData { sitPos = currSitData.posID, frameType = FrameDataType.changeAutoTarget, time = BeijingTimeManager.Instance.CurrentTimestampMs, frameData = new FrameData_ChangeAutoTarget { targetGuid = monsterguid, colliderIndex = index, } }; WebSocketSendList.SyncGameData(sgd); } private void SyncTurretState(TurretState state) { SyncGameData sgd = new SyncGameData { sitPos = currSitData.posID, frameType = FrameDataType.changeTurretState, time = BeijingTimeManager.Instance.CurrentTimestampMs, frameData = new FrameData_TurretState { state = state } }; WebSocketSendList.SyncGameData(sgd); } public void SyncChangeTurret(int turretNumber) { SyncGameData sgd = new SyncGameData { sitPos = currSitData.posID, frameType = FrameDataType.changeTurret, time = BeijingTimeManager.Instance.CurrentTimestampMs, frameData = new FrameData_ChangeTurret { turretIndex = turretNumber } }; WebSocketSendList.SyncGameData(sgd); } private void SyncFreeze(bool isFreeze) { SyncGameData sgd = new SyncGameData { sitPos = currSitData.posID, frameType = FrameDataType.freezeFish, time = BeijingTimeManager.Instance.CurrentTimestampMs, frameData = new FrameData_FreezeFish { isFreeze = isFreeze } }; WebSocketSendList.SyncGameData(sgd); } #endregion #region ⑦ 服务器广播处理(NetReceiveModule) public void SyncGameData_Rotate(SyncGameData sync) { if (sync.sitPos == currSitData.posID) return; var data = FrameDataParser.Parse(sync.frameData); SmoothRotate(list_Sits[sync.sitPos], data.angle); } public void SyncGameData_FireBullit(FireCannonSendData sync) { //if (sync.sitPos == currSitData.posID) return; var data = FrameDataParser.Parse(sync.extra.frameData); FireBullet(list_Sits[sync.extra.sitPos], data.angle, data.isSanShe, data.twoRTruuect, data.isSuoDing, sync.bulletIds, data.monsterIDs); } public void SyncGameData_ChangeTargetMonster(SyncGameData sync) { if (sync.sitPos == currSitData.posID) return; var data = FrameDataParser.Parse(sync.frameData); if (data.targetGuid == null) { list_Sits[sync.sitPos].ClearLock(); return; } MonsterContral monster = CreateFishManager.Instance.fishDatas_hashCodekey[data.targetGuid[0]]; Transform hitPos; if (monster.colliderList != null && monster.colliderList.Count > 0 && data.colliderIndex != -1) hitPos = monster.colliderList[data.colliderIndex]; else hitPos = monster.lockPoint ?? monster.transform; list_Sits[sync.sitPos].SetLockTarget(monster, hitPos); } public void SyncGameData_RendromDieFish(SyncGameData sync) { var data = JsonConvert.DeserializeObject(sync.frameData.ToString()); CreateFishManager.Instance.Id_DieListFish_FX( data.guids, data.coinNum, new Vector3(data.x, data.y, data.z), data.count ); } public void SyncGameData_TurretState(SyncGameData sync) { if (sync.sitPos == currSitData.posID) return; var data = FrameDataParser.Parse(sync.frameData); var sit = list_Sits[sync.sitPos]; sit.ChangeState(data.state); } public void SyncGameData_ChangeTurret(SyncGameData sync) { if (sync.sitPos == currSitData.posID) return; var data = FrameDataParser.Parse(sync.frameData); list_Sits[sync.sitPos].LoadTurret(data.turretIndex); } public void SyncGameData_ChangeCannonMultiple(SyncGameData sync) { if (sync.sitPos == currSitData.posID) return; var data = FrameDataParser.Parse(sync.frameData); var sit = list_Sits[sync.sitPos]; sit.consumeGoldNum = data.cannonMultiple; sit.tMP_TurretConsumeNum.text = data.cannonMultiple.ToString(); } public void SyncCannonMultiple(int consumeGold) { if (currSitData == null) return; SyncGameData sgd = new SyncGameData { sitPos = currSitData.posID, frameType = FrameDataType.changeCannonMultiple, time = BeijingTimeManager.Instance.CurrentTimestampMs, frameData = new FrameData_ChangeCannonMultiple { cannonMultiple = consumeGold } }; WebSocketSendList.SyncGameData(sgd); } public void SyncGameData_FreezeFish(SyncGameData sync) { if (sync.sitPos == currSitData.posID) return; // 自己发的不管 var data = FrameDataParser.Parse(sync.frameData); if (data.isFreeze) { CreateFishManager.Instance.FreezeAllFish(); PauseAllFishAnimations(); } else { CreateFishManager.Instance.UnfreezeAllFish(); ResumeAllFishAnimations(); } } #endregion #region 冰冻动画暂停/恢复 private void PauseAllFishAnimations() { var root = CreateFishManager.Instance.fishRoot; if (root == null) return; _frozenAnimSpeeds.Clear(); foreach (var s in root.GetComponentsInChildren(true)) { if (!s.gameObject.activeInHierarchy) continue; _frozenAnimSpeeds[s] = s.timeScale; s.timeScale = 0f; } foreach (var a in root.GetComponentsInChildren(true)) { if (!a.gameObject.activeInHierarchy) continue; _frozenAnimSpeeds[a] = a.speed; a.speed = 0f; } foreach (var an in root.GetComponentsInChildren(true)) { if (!an.gameObject.activeInHierarchy || !an.isPlaying) continue; foreach (AnimationState state in an) state.speed = 0f; _frozenAnimSpeeds[an] = 1f; } foreach (var pd in root.GetComponentsInChildren(true)) { if (!pd.gameObject.activeInHierarchy || pd.state != PlayState.Playing) continue; _frozenAnimSpeeds[pd] = 1f; pd.Pause(); } } private void ResumeAllFishAnimations() { if (_frozenAnimSpeeds.Count == 0) return; foreach (var kv in _frozenAnimSpeeds) { if (kv.Key == null) continue; switch (kv.Key) { case Spine.Unity.SkeletonAnimation s: s.timeScale = kv.Value; break; case Animator a: a.speed = kv.Value; break; case Animation an: foreach (AnimationState state in an) state.speed = 1f; break; case UnityEngine.Playables.PlayableDirector pd: pd.Resume(); break; } } _frozenAnimSpeeds.Clear(); } #endregion #region 玩家加入/退出(PlayerJoinModule) public void JoinGame(int sitNum, long goldNum, long diamondNum, long consumeGold, int currentTurretIndex, string user = null, string cannonId = null) { foreach (var sit in list_Sits) { if (sit.posID != sitNum) continue; sit.user = user ?? GameManager.Instance.user.id; sit.goldNum = goldNum; sit.consumeGoldNum = consumeGold; sit.tMP_GoldNum.text = goldNum.ToString(); sit.tMP_DiamondNum.text = diamondNum.ToString(); sit.tMP_TurretConsumeNum.text = consumeGold.ToString(); if (sit.user == GameManager.Instance?.user?.id) SitPosData.localPlayerSit = sit; sit.gameObject.SetActive(true); if (!string.IsNullOrEmpty(cannonId) && int.TryParse(cannonId, out int turretNum)) sit.LoadTurret(turretNum); } } public void TuiChuHHWK() { WebSocketSendList.LeaveRoom(GameManager.Instance.gameData.currRoomData.roomId, currSitData.user); CreateFishManager.Instance.fangzhu = false; } #endregion #region 人机AI private void HandleBotAI() { // 只有房主控制人机 if (SitPosData.roomOwnerSit == null || currSitData != SitPosData.roomOwnerSit) return; if (!GameManager.Instance.enableBot) return; var fishList = CreateFishManager.Instance; if (fishList == null || fishList.fishDatas_hashCodekey == null) return; foreach (var sit in list_Sits) { if (!sit.isBot || sit.gameObject.activeSelf == false) continue; if (sit.tra_CurrTurret == null) continue; // 找最近鱼 MonsterContral target = null; float bestDist = float.MaxValue; Vector3 turretPos = sit.tra_CurrTurret.position; foreach (var kv in fishList.fishDatas_hashCodekey) { var m = kv.Value; if (m == null || !m.gameObject.activeInHierarchy) continue; float d = (m.transform.position - turretPos).sqrMagnitude; if (d < bestDist) { bestDist = d; target = m; } } if (target == null) continue; // 旋转朝向鱼 Vector3 fishWorldPos = target.transform.position; Vector3 screenPos = Camera.main.WorldToScreenPoint(fishWorldPos); RotateToTouch(sit, screenPos); // 定时开火 if (!botFireTimer.ContainsKey(sit.posID)) botFireTimer[sit.posID] = 0f; botFireTimer[sit.posID] += Time.deltaTime; if (botFireTimer[sit.posID] >= attackTime) { botFireTimer[sit.posID] = 0f; float angle = sit.currentAngle; SyncFire_ForBot(sit, angle); } } } /// 人机开火同步(房主代发) private void SyncFire_ForBot(SitPosData botSit, float angle) { bool isSanShe = toggle_Three.isOn; bool isDouble = toggle_Shuang.isOn; SyncGameData sgd = new SyncGameData { sitPos = botSit.posID, frameType = FrameDataType.fireBullet, time = BeijingTimeManager.Instance.CurrentTimestampMs, frameData = new FrameData_FireBullet { angle = angle, gold = botSit.consumeGoldNum, isSanShe = isSanShe, twoRTruuect = isDouble, isSuoDing = false, } }; WebSocketSendList.SyncGameData(sgd); } #endregion }