This commit is contained in:
2026-09-17 03:06:38 +08:00
parent 185f23482e
commit b808170da9
38 changed files with 4199 additions and 143 deletions
@@ -118,6 +118,18 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
/// <summary>是否正在退出倒计时(期间屏蔽帧同步和爆奖特效)</summary>
private bool _isLeavingCountdown = false;
private Coroutine _leaveCountdownCoroutine;
[Header("无操作自动退出")]
[Tooltip("无操作总时长(秒),走完自动退出回大厅(<=0 关闭该功能)")]
public float idleExitTotalSeconds = 70f;
[Tooltip("最后多少秒才显示倒计时面板(autoExitGamePanel)并逐秒倒数,之前全程无提示")]
public float idleExitPanelShowSeconds = 10f;
/// <summary>无操作计时是否生效(资源加载完成后才开始计时,OnHide/离房时失效)</summary>
private bool _idleExitArmed = false;
/// <summary>最近一次玩家操作的时间(Time.realtimeSinceStartup,含切后台时间)</summary>
private float _lastOperationTime = 0f;
/// <summary>警告文本当前显示的剩余秒数(-1=未显示;避免每帧刷新 TMP 文本)</summary>
private int _idleWarnShownSeconds = -1;
[Header("游戏场景")]
public string sceneName;
public GameTypeEnum gameType;
@@ -181,6 +193,8 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
public Toggle toggle_Lock;
public Toggle toggle_Three;
public Toggle toggle_Shuang;
// ⭐ 双倍开火:toggle_Shuang 开启后计时 shuangDuration 秒自动关闭(DaoJiShi 驱动),
// child0 的 Image(Filled 360°) 做环形倒计时显示(GetShuangFillImage 懒获取)
float shuangDuration = 15f;
@@ -189,14 +203,15 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
public Toggle toggle_BaoZou;
public TextMeshProUGUI tmp_BaoZouCount;
public Image img_BaoZou;
float frenzyDuration = 5f; // 疯狂状态持续时间
[Header("狂暴/冰封持续时间(秒,可在面板手动设置)")]
public float frenzyDuration = 15f; // 疯狂状态持续时间
public float currFrenzyTime = 0f;
public Toggle toggle_BingFeng;
public TextMeshProUGUI tmp_BingFengCount;
public Image img_BingFeng;
public GameObject bingFengEffect; // 冰封特效预制体(首次Instantiate,之后激活/隐藏)
private GameObject _bingFengInstance; // 克隆出来的实例
float bingFengDuration = 15f; // 冰封状态持续时间(服务端 FishFrozen 延长 15 秒,客户端写死 15s 倒计时)
public float bingFengDuration = 15f; // 冰封状态持续时间(服务端 FishFrozen 延长 15 秒,可在面板调整)
public float currBingFengTime = 0f;
private Dictionary<Component, object> _frozenAnimSpeeds = new Dictionary<Component, object>();
@@ -243,7 +258,10 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
public GameObject bulletParent;
float changeTurretTime = 0f;
bool isChangingTurret = false;
[Header("超时自动退出倒计时")]
public GameObject autoExitGamePanel; //倒计时面板
public TextMeshProUGUI tmp_ExitDaoJiShi; //里面显示的是这个 n秒后自动关闭
public Button btn_CloseAutoExitGamePanel; //退出游戏按钮
// XJMH 长按炮台扇形切换
[Header("XJMH 炮台长按切换")]
[Tooltip("长按炮台触发扇形切换的阈值(秒)")]
@@ -279,6 +297,9 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
protected override void OnShow(object args = null)
{
base.OnShow(args);
// ⭐ 无操作自动退出:加载期间不计时,LoadResources 完成后重新开始计;确保倒计时面板初始隐藏
_idleExitArmed = false;
HideIdleExitWarning();
LoadResources();
RefreshGGKCount();
//UIManager.Instance.ShowBoard("太好看了");
@@ -352,7 +373,11 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
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;
// ⭐ 进入游戏圆圈:6 秒后自动消失(点击屏幕立即消失的逻辑在 HandleInput)
StartCoroutine(HideMySitAnimAfter(6f));
}
}
else
{
@@ -478,6 +503,10 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
UnhookOpenPTLongPress();
UnhookAutoAttackOff();
// ⭐ 无操作自动退出:面板隐藏即停止计时
_idleExitArmed = false;
HideIdleExitWarning();
UnLoadResources();
// 场景可能未加载或已卸载,做保护避免重复卸载报错
var scene = SceneManager.GetSceneByName(sceneName);
@@ -604,6 +633,10 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
if (btn_ChangeRoom != null)
btn_ChangeRoom.onClick.AddListener(StartChangeRoom);
// ⭐ 无操作自动退出:关闭倒计时面板并重新计时(未拖引用时为null,做保护)
if (btn_CloseAutoExitGamePanel != null)
btn_CloseAutoExitGamePanel.onClick.AddListener(OnCloseAutoExitPanel);
// ⭐ 左侧道具按钮:开启/关闭 obj_PropPanel(一级列表由 PropConfigData 配置构建,
// 点击一级道具在 tmp_PropTwoPanel 展开二级子道具)
if (btn_Prop != null)
@@ -726,6 +759,124 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
if (btn_QueRen != null) btn_QueRen.interactable = true;
}
#region 无操作自动退出(60秒静默 → 最后10秒倒计时面板 → 70秒自动回大厅)
/// <summary>
/// ⭐ 每帧记录玩家操作(触摸/鼠标/键盘,含 UI 点击),超时自动走离房流程回大厅。
/// 机器人/其他玩家的同步数据不算操作;退出确认面板开着时超时同样生效。
/// 只在资源加载完成后计时(加载慢不会被踢);离房流程进行中不重复触发。
/// 流程:前 idleExitTotalSeconds - idleExitPanelShowSeconds(默认60秒)全程无提示
/// → 最后 idleExitPanelShowSeconds(默认10秒)才弹出倒计时面板(autoExitGamePanel)
/// 逐秒倒数 → 走满 idleExitTotalSeconds(默认70秒)自动退出回大厅;
/// 期间任意操作或点 btn_CloseAutoExitGamePanel → 关闭面板并重新计时。
/// </summary>
private void UpdateIdleAutoExit()
{
if (idleExitTotalSeconds <= 0f || idleExitPanelShowSeconds <= 0f)
{
HideIdleExitWarning();
return;
}
// 任何触摸/鼠标/键盘输入(含点在 UI 上)都算一次操作
if (IsAnyOperationThisFrame())
{
_lastOperationTime = Time.realtimeSinceStartup;
// 有操作 → 立即隐藏倒计时面板
HideIdleExitWarning();
}
// 计时未生效(加载中/已隐藏)或已在离房流程中 → 跳过
if (!_idleExitArmed || _leaveRoomResponseTcs != null) return;
float idleElapsed = Time.realtimeSinceStartup - _lastOperationTime;
// 面板弹出时刻:总时长 - 面板显示时长(70 - 10 = 60 秒)
float panelShowAt = idleExitTotalSeconds - idleExitPanelShowSeconds;
if (idleElapsed >= idleExitTotalSeconds)
{
HideIdleExitWarning();
Debug.Log($"[IdleExit] 连续 {idleExitTotalSeconds} 秒无操作,自动退出游戏回大厅");
// 退出确认面板还开着 → 先关掉,再走离房流程
if (_isLeavingCountdown) CancelLeaveCountdown();
RequestLeaveRoom();
return;
}
// 只有最后 idleExitPanelShowSeconds 秒才显示面板;之前保持隐藏
if (idleElapsed >= panelShowAt)
ShowIdleExitWarning(Mathf.CeilToInt(idleExitTotalSeconds - idleElapsed));
else
HideIdleExitWarning();
}
/// <summary>显示/刷新无操作退出倒计时面板(仅在剩余秒数变化时刷新文本,避免每帧 GC)</summary>
private void ShowIdleExitWarning(int remainingSeconds)
{
// 弹出倒计时面板(未拖面板引用时退化为只显示文本)
if (autoExitGamePanel != null)
{
if (!autoExitGamePanel.activeSelf)
autoExitGamePanel.SetActive(true);
}
else if (tmp_ExitDaoJiShi != null && !tmp_ExitDaoJiShi.gameObject.activeSelf)
{
tmp_ExitDaoJiShi.gameObject.SetActive(true);
}
if (tmp_ExitDaoJiShi != null && _idleWarnShownSeconds != remainingSeconds)
{
_idleWarnShownSeconds = remainingSeconds;
tmp_ExitDaoJiShi.text =
$"({remainingSeconds}秒后自动退出)";
}
}
/// <summary>隐藏无操作退出倒计时面板(有操作/关闭按钮/开始离房/面板隐藏时调用)</summary>
private void HideIdleExitWarning()
{
if (autoExitGamePanel != null)
{
if (autoExitGamePanel.activeSelf)
{
autoExitGamePanel.SetActive(false);
_idleWarnShownSeconds = -1;
}
return;
}
if (tmp_ExitDaoJiShi != null && tmp_ExitDaoJiShi.gameObject.activeSelf)
{
tmp_ExitDaoJiShi.gameObject.SetActive(false);
_idleWarnShownSeconds = -1;
}
}
/// <summary>btn_CloseAutoExitGamePanel:关闭倒计时面板并从头重新计时</summary>
private void OnCloseAutoExitPanel()
{
HideIdleExitWarning();
_lastOperationTime = Time.realtimeSinceStartup;
}
/// <summary>本帧是否有玩家输入:触摸/鼠标按下、按住、松开 或 任意按键</summary>
private bool IsAnyOperationThisFrame()
{
var input = GetInput();
if (input.isPressed || input.wasPressedThisFrame || input.wasReleasedThisFrame)
return true;
var keyboard = Keyboard.current;
return keyboard != null && keyboard.anyKey.isPressed;
}
/// <summary>开始无操作计时(LoadResources 完成后调用,计时从当前时刻重新起算)</summary>
private void ArmIdleAutoExit()
{
_lastOperationTime = Time.realtimeSinceStartup;
_idleExitArmed = true;
}
#endregion
public virtual void OnDestroy()
{
EventCenter.Instance.RemoveEventListener<SyncGameData>(ConstEventString.SyncGameData_Rotate, SyncGameData_Rotate);
@@ -873,8 +1024,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
protected virtual void Update()
{
UpdateIdleAutoExit();
HandleAutoRotate();
HandleInput();
@@ -991,6 +1141,9 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
ClearChangeRoomBar();
else if (JinDuTiao != null)
JinDuTiao.Hide();
// ⭐ 无操作自动退出:加载完成,从现在开始计无操作时间
ArmIdleAutoExit();
}
public async void UnLoadResources()
@@ -2074,6 +2227,14 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
}
/// <summary>进入游戏圆圈 mySitAnim:延时自动消失(6 秒没点屏幕也消失;点击屏幕立即消失在 HandleInput)</summary>
private IEnumerator HideMySitAnimAfter(float delay)
{
yield return new WaitForSeconds(delay);
if (mySitAnim != null && mySitAnim.gameObject.activeSelf)
mySitAnim.gameObject.SetActive(false);
}
private void HandleInput()
{
// ⭐ 退出倒计时期间屏蔽本地玩家手动输入(旋转/开火/锁定),其他玩家和机器人不受影响
@@ -3123,7 +3284,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
MonsterContral m = kv.Value;
if (m == null || !m.gameObject.activeInHierarchy) continue;
if (selected.Contains(m.guid)) continue; // 跳过已选中的
// ⭐ 路径进度过滤:刚出生(< 0.2)或即将离场(> 0.8)的鱼不作为目标
// ⭐ 路径进度过滤:刚出生(< 0.1)或即将离场(> 0.9)的鱼不作为目标
if (!IsFishPathLockable(m)) continue;
// ⭐ 可锁定过滤:出场效果期间/needLockEvent 鱼未就绪 → 跳过换下一条
if (!IsMonsterLockable(m)) continue;
@@ -3444,20 +3605,23 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
// ⭐ 用世界坐标(Z=0平面),和 bot AI 一致,否则距离计算会异常(出现 3.4E+38 之类)
Vector3 origin = currSitData.turretWorldPos;
// ⭐ 规则:特殊Boss / Boss鱼 / 特殊鱼 为高价值组(按优先级 + 距离最近),
// 黄金鱼 / 普通鱼 为普通组(随机选一只)
// 高价值组有目标时只从高价值组选;高价值组无目标时从普通组随机
// ⭐ 规则:严格按优先级分层,高价值到低价值:
// 特殊Boss(5) > Boss鱼(4) > 特殊鱼(3) > 黄金鱼(2) > 普通鱼(1)
// - 特殊Boss/Boss鱼/特殊鱼:同层取距离最近
// - 黄金鱼/普通鱼:各自独立分层,同层随机选一只
// 高层有目标时绝不选低层
MonsterContral best = null;
int bestPri = -1;
float bestSqr = float.MaxValue;
var normalList = new List<MonsterContral>();
var goldList = new List<MonsterContral>(); // 黄金鱼(2) 单独一层
var smallList = new List<MonsterContral>(); // 普通鱼(1) 单独一层
foreach (var kv in CreateFishManager.Instance.fishDatas_hashCodekey)
{
MonsterContral m = kv.Value;
if (m == null || !m.gameObject.activeInHierarchy) continue;
// ⭐ 路径进度过滤:刚出生(< 0.2)或即将离场(> 0.8)的鱼不作为自动锁定目标
// ⭐ 路径进度过滤:刚出生(< 0.1)或即将离场(> 0.9)的鱼不作为自动锁定目标
if (!IsFishPathLockable(m)) continue;
// ⭐ 可锁定过滤:出场效果期间/needLockEvent 鱼未就绪 → 跳过换下一条
if (!IsMonsterLockable(m)) continue;
@@ -3475,19 +3639,27 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
best = m;
}
}
// 普通组:黄金鱼(2) / 普通鱼(1) → 先收集,后面随机
// ⭐ 黄金鱼(2) / 普通鱼(1) 分开收集,不再混在一个列表里
else if (pri == 2)
{
goldList.Add(m);
}
else
{
normalList.Add(m);
smallList.Add(m);
}
}
// 高价值组有目标 → 直接返回最高价值(同价值最近)的目标
// ① 特殊Boss/Boss鱼/特殊鱼 有目标 → 返回最高价值(同价值最近)的目标
if (best != null) return best;
// 高价值组无目标 → 从普通组随机选一只
if (normalList.Count > 0)
return normalList[Random.Range(0, normalList.Count)];
// ② 没有特殊鱼及以上 → 黄金鱼层随机选一只
if (goldList.Count > 0)
return goldList[Random.Range(0, goldList.Count)];
// ③ 没有黄金鱼 → 普通鱼层随机选一只
if (smallList.Count > 0)
return smallList[Random.Range(0, smallList.Count)];
return null;
}
@@ -4489,7 +4661,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
{
var m = kv.Value;
if (m == null || !m.gameObject.activeInHierarchy) continue;
// ⭐ 路径进度过滤:刚出生(< 0.2)或即将离场(> 0.8)的 Boss 不作为目标
// ⭐ 路径进度过滤:刚出生(< 0.1)或即将离场(> 0.9)的 Boss 不作为目标
if (!IsFishPathLockable(m)) continue;
int ft = (int)m.fishenum;
@@ -4820,7 +4992,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
{
var m = kv.Value;
if (m == null || !m.gameObject.activeInHierarchy) continue;
// ⭐ 路径进度过滤:刚出生(< 0.2)或即将离场(> 0.8)的鱼不作为目标
// ⭐ 路径进度过滤:刚出生(< 0.1)或即将离场(> 0.9)的鱼不作为目标
if (!IsFishPathLockable(m)) continue;
Vector3 fishPos = m.transform.position;
@@ -4867,7 +5039,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
var m = kv.Value;
if (m == null || !m.gameObject.activeInHierarchy) continue;
if (m == exclude) continue;
// ⭐ 路径进度过滤:刚出生(< 0.2)或即将离场(> 0.8)的鱼不作为目标
// ⭐ 路径进度过滤:刚出生(< 0.1)或即将离场(> 0.9)的鱼不作为目标
if (!IsFishPathLockable(m)) continue;
Vector3 fishPos = m.transform.position;