/* ======================== 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() 同步锁定 ├── SyncLaserState() 同步激光状态 └── 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.Threading.Tasks; using TMPro; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.EventSystems; using UnityEngine.InputSystem; using UnityEngine.SceneManagement; using UnityEngine.UI; using static UnityEngine.AddressableAssets.Addressables; 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_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 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 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) { 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; currSitData = list_Sits[roomJoinNotice.position]; //currSitData.ChangeTurret(1); currSitData.lockFX.transform.GetChild(0).gameObject.SetActive(false); currSitData.lockFX.transform.GetChild(1).gameObject.SetActive(true); mySitAnim.GetComponent().anchoredPosition = currSitData.currentTurret.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 生命周期 public override void Awake() { base.Awake(); 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); } 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); } 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) { SyncGameData sgd = new SyncGameData { sitPos = currSitData.posID, frameType = FrameDataType.changeAutoTarget, time = BeijingTimeManager.Instance.CurrentTimestampMs, frameData = new FrameData_ChangeAutoTarget { targetGuid = null } }; WebSocketSendList.SyncGameData(sgd); } }); } } public void OnEnable() { } private void Update() { HandleInput(); HandleAutoRotate(); HandleLock(); // 锁定点击入口 HandleAutoFire(); // 自动开火真正发射 UpdateLockFX(); HandleAutoLock(); LoopLaser(); } private void UpdateLockFX() { foreach (var sit in list_Sits) { if (!sit.isLock || sit.monCotl_Lock == null) continue; Vector3 pos = sit.monCotl_Lock_Position.position; pos.z = Camera.main.transform.position.z + 1; sit.lockFX.transform.position = pos; if (!sit.lockFX.activeSelf) sit.lockFX.SetActive(true); } } #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 void HandleInput() { var touch = Touchscreen.current?.primaryTouch; if (touch == null) return; // 按下 if (touch.press.wasPressedThisFrame) { isChangingTurret = CheckTouchIsChangeTurret(); clickTime = attackTime; if (mySitAnim != null && mySitAnim.gameObject.activeSelf) mySitAnim.gameObject.SetActive(false); if (!isChangingTurret) currSitData.ChangeState(TurretState.Attack); } // 长按 if (touch.press.isPressed) { if (isChangingTurret) { HandleChangeTurretHold(); return; } HandleRotate(); TryFire(); } // 松开 if (touch.press.wasReleasedThisFrame) { if (isChangingTurret) { isChangingTurret = false; changeTurretTime = 0; CloseChangeTurretUI(); return; } currSitData.ChangeState(TurretState.Idle); } } private void HandleChangeTurretHold() { changeTurretTime += Time.deltaTime; if (changeTurretTime > 1f) { if (!currSitData.changeTurretList.activeSelf) currSitData.changeTurretList.SetActive(true); } } private bool CheckTouchIsChangeTurret() { PointerEventData pointerData = new PointerEventData(EventSystem.current); pointerData.position = Touchscreen.current.primaryTouch.position.ReadValue(); 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() { if (toggle_Lock.isOn && currSitData.monCotl_Lock != null) { RotateToLockTarget(currSitData); } else { RotateToTouch(currSitData); } } private void RotateToTouch(SitPosData sit) { Vector2 touchPos = Touchscreen.current.primaryTouch.position.ReadValue(); RectTransformUtility.ScreenPointToLocalPointInRectangle( sit.tra_CurrTurret.transform.parent as RectTransform, touchPos, null, out Vector2 localPos); Vector2 dir = localPos - sit.tra_CurrTurret.GetComponent().anchoredPosition; float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg - 90f; 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; sit.SetRotation(angle); } private void HandleAutoRotate() { // 自动开火关闭 → 不自动旋转 if (!toggle_SkillAuto.isOn) return; // 锁定模式优先 if (toggle_Lock.isOn && currSitData.isLock) { RotateToLockTarget(currSitData); 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; } currSitData.SetRotation(autoRotateAngle); } 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 ③ 攻击系统(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) { Vector2 startPos = sit.GetBulletStartPos(); if (isSanShe) { CreateBullet(sit, startPos, angle - 20, isDouble, isLock); CreateBullet(sit, startPos, angle, isDouble, isLock); CreateBullet(sit, startPos, angle + 20, isDouble, isLock); } else { CreateBullet(sit, startPos, angle, isDouble, isLock); } AudioManager.Instance.PlayFX(bulletClip); } private bool isLaser = false; private void StartLaser() { isLaser = true; currSitData.ChangeState(TurretState.LaserStart); SyncLaserState(TurretState.LaserStart); } private void LoopLaser() { if (!isLaser) return; // TODO: 激光持续伤害 / 特效 } private void StopLaser() { isLaser = false; currSitData.ChangeState(TurretState.LaserEnd); SyncLaserState(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); } #endregion #region 锁定系统(LockModule) private void HandleLock() { if (!toggle_Lock.isOn) return; if (Touchscreen.current.primaryTouch.press.wasPressedThisFrame) { Vector2 worldPos = Camera.main.ScreenToWorldPoint( Touchscreen.current.primaryTouch.position.ReadValue()); 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(monster); } else { currSitData.ClearLock(); SyncLock(null); } } } private void HandleAutoLock() { if (!toggle_SkillAuto.isOn) return; if (toggle_Lock.isOn) return; // 手动锁定优先 // TODO: 自动锁定最近鱼 / 最大鱼 } //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) { 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 = Guid.NewGuid().ToString(); if (isLock) bullet.Lock(sit.monCotl_Lock); 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) { SyncGameData sgd = new SyncGameData { sitPos = currSitData.posID, frameType = FrameDataType.fireBullet, time = BeijingTimeManager.Instance.CurrentTimestampMs, frameData = new FrameData_FireBullit { angle = angle, gold = currSitData.goldNum, isSanShe = isSanShe, twoRTruuect = isDouble, isSuoDing = isLock, auto = toggle_SkillAuto.isOn } }; WebSocketSendList.SyncGameData(sgd); } private void SyncLock(MonsterContral monster) { SyncGameData sgd = new SyncGameData { sitPos = currSitData.posID, frameType = FrameDataType.changeAutoTarget, time = BeijingTimeManager.Instance.CurrentTimestampMs, frameData = new FrameData_ChangeAutoTarget { targetGuid = monster?.guid, targetPostName = monster == null ? null : monster.name } }; WebSocketSendList.SyncGameData(sgd); } private void SyncLaserState(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 index) { SyncGameData sgd = new SyncGameData { sitPos = currSitData.posID, frameType = FrameDataType.changeTurret, time = BeijingTimeManager.Instance.CurrentTimestampMs, frameData = new FrameData_ChangeTurret { turretIndex = index } }; 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(SyncGameData sync) { if (sync.sitPos == currSitData.posID) return; var data = FrameDataParser.Parse(sync.frameData); FireBullet(list_Sits[sync.sitPos], data.angle, data.isSanShe, data.twoRTruuect, data.isSuoDing); } 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]; Transform hitPos = monster.transform.Find(data.targetPostName); 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].ChangeTurret(data.turretIndex); } #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.gameObject.SetActive(true); 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(); sit.ChangeTurret(currentTurretIndex); } } public void TuiChuHHWK() { WebSocketSendList.LeaveRoom(GameManager.Instance.gameData.currRoomData.roomId, currSitData.user); CreateFishManager.Instance.fangzhu = false; } #endregion }