/* ======================== 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.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; [Header("我在这")] public GameObject mySitAnim; [Header("Skill")] public Dictionary dic_Skill_Toggle = new Dictionary();//开关型技能 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 float bingFengDuration = 5f; // 冰封状态持续时间 public float currBingFengTime = 0f; public List list_Sits = new List(); public SitPosData currSitData; [Header("攻击参数")] public float attackTime = 0.5f; float clickTime = 0; 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(); //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.cannonMultiple, 0, item.userId); } 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 自动加载 currSitData.lockFX.transform.GetChild(0).gameObject.SetActive(false); currSitData.lockFX.transform.GetChild(1).gameObject.SetActive(true); if (currSitData.turretParent != null) mySitAnim.GetComponent().anchoredPosition = currSitData.turretParent.GetComponent().anchoredPosition; } else { JoinGame(roomJoinNotice.position, roomJoinNotice.gold, roomJoinNotice.cannonMultiple, 0, roomJoinNotice.userId); } } else { UIManager.Instance.ShowBoardError("获取信息失败"); } } 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(); 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_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_FreezeFish, SyncGameData_FreezeFish); btn_TuJian.onClick.AddListener(() => { subUIManager.ShowAsync(UIPanel.GameTuJian); StartCoroutine(CloseSettingList()); }); } 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_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; CreateFishManager.Instance.FreezeAllFish(); SyncFreeze(true); } else { // 手动关闭 → 解冻 if (CreateFishManager.Instance.isFrozen) { CreateFishManager.Instance.UnfreezeAllFish(); 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() { HandleInput(); HandleAutoRotate(); CheckLockTargetValid();//锁定目标失效自动清除 HandleAutoFire(); // 自动开火真正发射 HandleLock(); UpdateLockFX(); HandleAutoLock(); LoopLaser(); SettingClose(); DaoJiShi(); } 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; // ⭐ 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; } 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; float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg - 90f; if (sit.IsFlipped) angle = (angle + 180f) % 360f; sit.SetRotation(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; 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) { 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; 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) { 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) private void HandleLock() { CrossPlatformInput input = GetInput(); if (!toggle_Lock.isOn) return; // 只有按下那一帧才触发锁定 if (!input.wasReleasedThisFrame&&!input.wasPressedThisFrame) return; // ⭐ UI 点击过滤 if (IsPointerOverUI(input.position)) return; Vector2 worldPos = Camera.main.ScreenToWorldPoint(input.position); RaycastHit2D hit = Physics2D.Raycast(worldPos, Vector2.zero, 50, LayerMask.GetMask("Fish")); if (hit.collider != null) { MonsterContral monster = hit.collider.GetComponentInParent(); currSitData.SetLockTarget(monster, hit.collider.transform); SyncLock(new List { monster?.guid }, monster.colliderList.IndexOf(hit.collider.transform)); } else { currSitData.ClearLock(); SyncTurretState(TurretState.Idle); SyncLock(null); } } private void UpdateLockFX() { foreach (var sit in list_Sits) { if (!sit.isLock || sit.monCotl_Lock == null) continue; Vector3 pos = Vector3.zero; if (!sit.monCotl_Lock_Position.gameObject.activeInHierarchy) { foreach (Transform t in sit.monCotl_Lock.colliderList) { if (t.gameObject.activeInHierarchy) { sit.monCotl_Lock_Position = t; pos = sit.monCotl_Lock_Position.position; break; } } if (!sit.monCotl_Lock_Position.gameObject.activeInHierarchy) { if (sit.lockFX.activeSelf) sit.lockFX.SetActive(false); } } else { pos = sit.monCotl_Lock_Position.position; if (!sit.lockFX.activeSelf) sit.lockFX.SetActive(true); } pos.z = Camera.main.transform.position.z + 1; sit.lockFX.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(子弹系统) 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; 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_FireBullit { 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 }; //WebSocketSendList.SyncGameData(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); } private 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 = data.colliderIndex != -1?monster.colliderList[ data.colliderIndex]: 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_FreezeFish(SyncGameData sync) { if (sync.sitPos == currSitData.posID) return; // 自己发的不管 var data = FrameDataParser.Parse(sync.frameData); if (data.isFreeze) CreateFishManager.Instance.FreezeAllFish(); else CreateFishManager.Instance.UnfreezeAllFish(); } #endregion #region 玩家加入/退出(PlayerJoinModule) public void JoinGame(int sitNum, long goldNum, long consumeGold, int currentTurretIndex, string user = 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_TurretConsumeNum.text = consumeGold.ToString(); // 显式注册本地玩家座位 if (sit.user == GameManager.Instance?.user?.id) SitPosData.localPlayerSit = sit; sit.gameObject.SetActive(true); // 兜底:确保炮台加载(OnEnable 可能因 cannonUsing 为空而跳过) var cannonUsing = GameManager.Instance.user?.cannonUsing; if (cannonUsing != null && int.TryParse(cannonUsing.number, out int num)) sit.LoadTurret(num); } } public void TuiChuHHWK() { WebSocketSendList.LeaveRoom(GameManager.Instance.gameData.currRoomData.roomId, currSitData.user); CreateFishManager.Instance.fangzhu = false; } #endregion }