Files
5.24BuYu/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs
T
2026-06-28 16:48:18 +08:00

1922 lines
64 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
======================== 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<string, Toggle> dic_Skill_Toggle = new Dictionary<string, Toggle>();//开关型技能
[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<Component, float> _frozenAnimSpeeds = new Dictionary<Component, float>();
[Header("Skillleft")]
public Button btn_GuaGuaKa;
public TextMeshProUGUI tmp_GGKCount;
public List<SitPosData> list_Sits = new List<SitPosData>();
public SitPosData currSitData;
[Header("攻击参数")]
public float attackTime = 0.5f;
float clickTime = 0;
private Dictionary<int, float> botFireTimer = new Dictionary<int, float>(); // 人机开火计时
public GameObject bullet_Test;
public GameObject bulletParent;
float changeTurretTime = 0f;
bool isChangingTurret = false;
int bulletCount = 0;
public GameObject boardCast_Boss;
AudioClip bulletClip;
public List<int> paoBeiList = new List<int>();
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<WebSocketResponseBase<RoomJoinNotice>>(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<int>();
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<SubUIBase>(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<SyncGameData>(ConstEventString.SyncGameData_Rotate, SyncGameData_Rotate);
EventCenter.Instance.AddEventListener<FireCannonSendData>(ConstEventString.SyncGameData_FireBullet, SyncGameData_FireBullit);
EventCenter.Instance.AddEventListener<SyncGameData>(ConstEventString.SyncGameData_ChangeTargetMonster, SyncGameData_ChangeTargetMonster);
EventCenter.Instance.AddEventListener<SyncGameData>(ConstEventString.SyncGameData_RendromDieFish, SyncGameData_RendromDieFish);
EventCenter.Instance.AddEventListener<SyncGameData>(ConstEventString.SyncGameData_TurretState, SyncGameData_TurretState);
EventCenter.Instance.AddEventListener<SyncGameData>(ConstEventString.SyncGameData_ChangeTurret, SyncGameData_ChangeTurret);
EventCenter.Instance.AddEventListener<SyncGameData>(ConstEventString.SyncGameData_ChangeCannonMultiple, SyncGameData_ChangeCannonMultiple);
EventCenter.Instance.AddEventListener<SyncGameData>(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<SyncGameData>(ConstEventString.SyncGameData_Rotate, SyncGameData_Rotate);
EventCenter.Instance.RemoveEventListener<FireCannonSendData>(ConstEventString.SyncGameData_FireBullet, SyncGameData_FireBullit);
EventCenter.Instance.RemoveEventListener<SyncGameData>(ConstEventString.SyncGameData_ChangeTargetMonster, SyncGameData_ChangeTargetMonster);
EventCenter.Instance.RemoveEventListener<SyncGameData>(ConstEventString.SyncGameData_RendromDieFish, SyncGameData_RendromDieFish);
EventCenter.Instance.RemoveEventListener<SyncGameData>(ConstEventString.SyncGameData_TurretState, SyncGameData_TurretState);
EventCenter.Instance.RemoveEventListener<SyncGameData>(ConstEventString.SyncGameData_ChangeTurret, SyncGameData_ChangeTurret);
EventCenter.Instance.RemoveEventListener<SyncGameData>(ConstEventString.SyncGameData_ChangeCannonMultiple, SyncGameData_ChangeCannonMultiple);
EventCenter.Instance.RemoveEventListener<SyncGameData>(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;
/// <summary>
/// 等待10秒未操作关闭设置面板
/// </summary>
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<AudioClip>("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<RaycastResult> results = new List<RaycastResult>();
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<RaycastResult> results = new List<RaycastResult>();
EventSystem.current.RaycastAll(pointerData, results);
foreach (var result in results)
{
if (result.gameObject.CompareTag("ChangeTurret"))
{
SitPosData sit = result.gameObject.GetComponentInParent<SitPosData>();
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<RectTransform>();
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<RectTransform>();
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<RectTransform>().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<RectTransform>().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<string> bullitIDs,
List<string> 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)
/// <summary>鱼锁定优先级:特殊Boss(5) > Boss鱼(4) > 特殊鱼(3) > 黄金鱼(2) > 普通鱼(1)</summary>
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;
}
}
/// <summary>
/// 查找 fire 时的多目标(三发锁定用)。
/// 第一个目标 = 主锁 monCotl_Lock(打哪锁哪),剩余从 Boss/特殊Boss/特殊鱼/黄金鱼 按优先级补满
/// </summary>
private List<(MonsterContral monster, Transform hitPos)> FindFireTargets(int maxCount)
{
var result = new List<(MonsterContral monster, Transform hitPos)>();
var selected = new HashSet<string>();
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<string, MonsterContral>[]
{
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)
/// <summary>
/// 刷新三发锁定图标:三发+锁定开启时显示3个锁定圈
/// </summary>
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<MonsterContral>();
// currSitData.SetLockTarget(monster, hit.collider.transform);
// // 保留你原来的调用方式:new List<string> { monster?.guid }
// SyncLock(new List<string> { 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<MonsterContral>();
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<string> { 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<string> { target?.guid }, i);
}
}
}
else
{
SyncLock(new List<string> { 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;
/// <summary>
/// 检查指定用户是否已达50发子弹上限
/// </summary>
private bool IsBulletLimitReached(string userId)
{
if (bulletParent == null) return false;
int count = 0;
foreach (Transform child in bulletParent.transform)
{
if (child.TryGetComponent<BullitMove>(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<BullitMove>(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<BullitMove>();
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<string> 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<FrameData_Rotate>(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<FrameData_FireBullet>(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<FrameData_ChangeAutoTarget>(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<FrameData_RandomDieFish>(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<FrameData_TurretState>(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<FrameData_ChangeTurret>(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<FrameData_ChangeCannonMultiple>(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<FrameData_FreezeFish>(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<Spine.Unity.SkeletonAnimation>(true))
{
if (!s.gameObject.activeInHierarchy) continue;
_frozenAnimSpeeds[s] = s.timeScale;
s.timeScale = 0f;
}
foreach (var a in root.GetComponentsInChildren<Animator>(true))
{
if (!a.gameObject.activeInHierarchy) continue;
_frozenAnimSpeeds[a] = a.speed;
a.speed = 0f;
}
foreach (var an in root.GetComponentsInChildren<Animation>(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<UnityEngine.Playables.PlayableDirector>(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);
}
}
}
/// <summary>人机开火同步(房主代发)</summary>
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
}