Files
5.24BuYu/Assets/MyGame/Scripts/Manager/GameManager.cs
T

671 lines
26 KiB
C#
Raw Normal View History

2026-07-10 23:26:42 +08:00
using Assets.MyGame.Scripts.Net;
2026-02-03 11:35:19 +08:00
using Assets.MyGame.Scripts.Net.API;
2026-04-29 01:18:39 +08:00
using Assets.MyGame.Scripts.Net.API.GameConfig_SubClass;
2026-04-25 18:53:04 +08:00
using CameraShake;
2026-02-03 11:35:19 +08:00
using Newtonsoft.Json;
2026-03-09 09:25:01 +08:00
using System;
2026-07-25 21:04:58 +08:00
using System.Collections;
2026-04-29 01:18:39 +08:00
using System.Collections.Generic;
2026-07-26 22:00:38 +08:00
using System.IO;
2026-06-08 21:23:40 +08:00
using System.Threading.Tasks;
2025-12-11 16:40:24 +08:00
using UnityEngine;
2026-03-18 17:26:14 +08:00
using UnityEngine.InputSystem;
2026-04-17 18:30:51 +08:00
using UnityEngine.Rendering;
2026-02-03 11:35:19 +08:00
using UnityEngine.SceneManagement;
2026-04-21 23:02:51 +08:00
using UnityEngine.UI;
using UnityEngine.Video;
2025-12-11 16:40:24 +08:00
public class GameManager : MonoSingleton<GameManager>
{
2026-09-18 08:15:08 +08:00
public Camera cam;
2026-06-16 23:45:31 +08:00
[Header("人机")]
2026-07-05 16:07:22 +08:00
public bool enableBot = true; // 人机总开关
public BotConfig botConfig; // 人机AI详细配置(Inspector可调,本地测试用)
2026-07-14 02:48:54 +08:00
[Tooltip("是否使用后端下发的 BotConfig。false=用 Inspector 本地配置(默认,方便测试);true=用后端 GameConfig 返回的 botConfig 覆盖本地")]
public bool useRemoteBotConfig = false;
2026-06-16 23:45:31 +08:00
2026-07-29 22:59:26 +08:00
[Header("日志文件")]
[Tooltip("运行时开关:是否实际生成日志文件。需要同时启用 ENABLE_LOG_FILE 预编译指令才生效。\ntrue=生成日志文件;false=不生成(运行时关闭)")]
public bool enableLogFile = true;
2026-07-30 22:09:55 +08:00
[Header("爆奖模式")]
[Tooltip("其他玩家爆奖处理方式。\ntrue=新模式(not-self 分流:座位显示 img_BaoJiangZhong,等待帧同步 baoJiangEnd 后再显示最终 BJEndUI 加分);\nfalse=旧模式(所有玩家走相同流程,直接克隆 effect_DieAward 播完整特效)")]
public bool useNotSelfBaoJiangMode = false;
2026-02-03 11:35:19 +08:00
public GameConfig gameConfig;
2026-07-09 14:29:17 +08:00
public ActivityConfig activityConfig;
2026-06-30 23:57:40 +08:00
public Dictionary<string, FishTypeConfig> dic_fishTypeConfigs = new Dictionary<string, FishTypeConfig>();
2026-04-13 18:30:55 +08:00
public UserData user;
2026-03-09 09:25:01 +08:00
public GameData gameData = new GameData();
2026-02-03 11:35:19 +08:00
public Scene scene;
2026-06-21 18:20:55 +08:00
/// <summary>
/// 头像更新事件(换了头像后触发,所有头像控件统一刷新)
/// </summary>
2026-06-26 17:26:10 +08:00
/// <summary>头像更新事件</summary>
2026-06-21 18:20:55 +08:00
public event Action<Sprite> OnAvatarChanged;
2026-07-05 02:10:44 +08:00
/// <summary>头像框更新事件</summary>
public event Action<Sprite> OnAvatarFrameChanged;
2026-06-26 17:26:10 +08:00
/// <summary>用户数据更新事件(金币/钻石/等级等发生变化时触发)</summary>
public event Action OnUserDataChanged;
2026-06-21 18:20:55 +08:00
/// <summary>头像字典(name → sprite),从 Resources/Avatar 加载</summary>
private Dictionary<string, Sprite> avatarSpriteDict = new Dictionary<string, Sprite>();
2026-07-05 02:10:44 +08:00
/// <summary>头像框字典(name → sprite),从 Resources/AvatarK 加载</summary>
private Dictionary<string, Sprite> avatarFrameDict = new Dictionary<string, Sprite>();
2026-06-21 18:20:55 +08:00
2026-06-21 22:38:18 +08:00
/// <summary>默认头像(新账号无头像时使用)</summary>
private Sprite defaultAvatar;
2026-07-05 02:10:44 +08:00
/// <summary>默认头像框</summary>
private Sprite defaultAvatarFrame;
2026-06-21 22:38:18 +08:00
2026-06-21 18:20:55 +08:00
/// <summary>触发头像更新事件</summary>
public void TriggerAvatarChanged(Sprite sprite)
{
OnAvatarChanged?.Invoke(sprite);
}
2026-04-29 01:18:39 +08:00
public CameraShaker cameraShake;
2026-04-26 20:29:22 +08:00
public Camera ZhuCam;
// // 指定此方法在加载时立即调用,在显示启动动画前之前执行
// [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)]
// private static void BeforeSplashScreen()
// {
// //平台适配
//#if UNITY_WEBGL
// // 如果在 WebGL 平台上运行,立即停止启动动画
// SplashScreen.Stop(SplashScreen.StopBehavior.StopImmediate);
//#else
// // 如果不是在 WebGL 平台上,启动一个异步任务来停止启动动画
// System.Threading.Tasks.Task.Run(() =>
// {
// SplashScreen.Stop(SplashScreen.StopBehavior.StopImmediate);
// });
//#endif
// }
2026-06-21 18:20:55 +08:00
/// <summary>
/// 初始化头像缓存(从 Resources/Avatar 加载所有Sprite)
/// </summary>
public void InitAvatarCache()
{
avatarSpriteDict.Clear();
var sprites = Resources.LoadAll<Sprite>("Avatar");
foreach (var s in sprites)
avatarSpriteDict[s.name] = s;
}
/// <summary>
/// 根据名称获取头像 Sprite
/// </summary>
public Sprite GetAvatarSprite(string avatarName)
{
if (avatarSpriteDict.Count == 0) InitAvatarCache();
2026-06-21 22:38:18 +08:00
if (string.IsNullOrEmpty(avatarName) || !avatarSpriteDict.TryGetValue(avatarName, out var sprite))
sprite = GetDefaultAvatar();
2026-06-21 18:20:55 +08:00
return sprite;
}
2026-06-21 22:38:18 +08:00
private Sprite GetDefaultAvatar()
{
if (defaultAvatar == null && avatarSpriteDict.Count > 0)
{
// 取缓存中第一个头像作为默认
foreach (var kv in avatarSpriteDict)
{
defaultAvatar = kv.Value;
break;
}
}
return defaultAvatar;
}
2026-06-21 18:20:55 +08:00
/// <summary>
/// 更换头像:上传服务端 → 更新本地 → 触发全局刷新
/// </summary>
public void ApplyAvatar(string avatarUrl)
{
APIRequestList.UpdateAvatar(avatarUrl, (result) =>
{
user.BaseInfo.avatarUrl = avatarUrl;
user.BaseInfo.avatarSprite = null;
var sprite = GetAvatarSprite(avatarUrl);
TriggerAvatarChanged(sprite);
});
}
2026-07-05 02:10:44 +08:00
// ==================== 头像框 ====================
/// <summary>触发头像框更新事件</summary>
public void TriggerAvatarFrameChanged(Sprite sprite)
{
OnAvatarFrameChanged?.Invoke(sprite);
}
/// <summary>
/// 初始化头像框缓存(从 Resources/AvatarK 加载所有Sprite)
/// </summary>
public void InitAvatarFrameCache()
{
avatarFrameDict.Clear();
var sprites = Resources.LoadAll<Sprite>("AvatarK");
foreach (var s in sprites)
avatarFrameDict[s.name] = s;
}
/// <summary>
/// 根据名称获取头像框 Sprite
/// </summary>
public Sprite GetAvatarFrameSprite(string avatarFrameName)
{
if (avatarFrameDict.Count == 0) InitAvatarFrameCache();
if (string.IsNullOrEmpty(avatarFrameName) || !avatarFrameDict.TryGetValue(avatarFrameName, out var sprite))
sprite = GetDefaultAvatarFrame();
return sprite;
}
private Sprite GetDefaultAvatarFrame()
{
if (defaultAvatarFrame == null && avatarFrameDict.Count > 0)
{
foreach (var kv in avatarFrameDict)
{
defaultAvatarFrame = kv.Value;
break;
}
}
return defaultAvatarFrame;
}
/// <summary>
/// 更换头像框:上传服务端 → 更新本地 → 触发全局刷新
/// </summary>
public void ApplyAvatarFrame(string avatarFrameName)
{
APIRequestList.UpdateAvatarFrame(avatarFrameName, (result) =>
{
user.BaseInfo.avatarFrame = avatarFrameName;
var sprite = GetAvatarFrameSprite(avatarFrameName);
TriggerAvatarFrameChanged(sprite);
});
}
2026-04-13 18:30:55 +08:00
public void UpdataUserData(UserData user)
{
2026-06-30 23:57:40 +08:00
this.user = user;
OnUserDataChanged?.Invoke();
2026-04-13 18:30:55 +08:00
}
2026-06-08 21:23:40 +08:00
protected override void Awake()
2025-12-26 11:15:56 +08:00
{
2026-07-05 17:39:30 +08:00
// ⭐ botConfig 默认初始化,避免 null 导致技能系统不跑
if (botConfig == null) botConfig = new BotConfig();
2026-06-21 00:07:26 +08:00
// ⭐ 游戏启动时加载已保存的画质和帧率设置
int quality = PlayerPrefs.GetInt("GraphicsQuality", 1);
2026-08-05 14:18:41 +08:00
// applyExpensiveChanges=false:URP 下切换画质会重建渲染管线,立即应用(true)易触发 GPU 重置闪退
QualitySettings.SetQualityLevel(quality, false);
2026-06-21 00:07:26 +08:00
int fps = PlayerPrefs.GetInt("TargetFps", 1);
2026-08-05 14:18:41 +08:00
// 中高档统一为 60 帧。移动端强制垂直同步,45 不可用(60Hz 整数倍才合法),故中档按 60 处理。
2026-06-21 00:07:26 +08:00
Application.targetFrameRate = fps switch
{
0 => 30,
1 => 60,
2026-08-05 14:18:41 +08:00
2 => 60,
2026-06-21 00:07:26 +08:00
_ => 60
};
2026-06-30 23:57:40 +08:00
2026-07-25 16:13:21 +08:00
// ⭐ 防止手机屏幕自动黑屏休眠
Screen.sleepTimeout = SleepTimeout.NeverSleep;
2026-07-29 22:59:26 +08:00
#if ENABLE_LOG_FILE
2026-07-26 22:00:38 +08:00
// ⭐ 启动日志文件自动保存:把所有 Debug.Log/Warning/Error 写入 persistentDataPath/Logs/
2026-07-29 22:59:26 +08:00
// 需要在 Player Settings → Scripting Define Symbols 中添加 ENABLE_LOG_FILE 才会编译
// 运行时可通过 Inspector 的 enableLogFile 勾选框关闭
if (enableLogFile) InitLogFileWriter();
#endif
2026-07-26 22:00:38 +08:00
2026-06-30 23:57:40 +08:00
AudioResourcesData audioResourcesData = new AudioResourcesData();
2026-06-08 21:23:40 +08:00
InItLoad();
//TurretResourcesData turretResourcesData = new TurretResourcesData();
2026-02-26 11:05:56 +08:00
Application.runInBackground = true;
2026-04-03 09:28:13 +08:00
2025-12-26 11:15:56 +08:00
base.Awake();
2026-06-30 23:57:40 +08:00
cameraShake = Camera.main.GetComponent<CameraShaker>();
2026-03-09 09:25:01 +08:00
gameData.currUser = "123";
2026-02-03 11:35:19 +08:00
//string json = Resources.Load<TextAsset>("Config/GameConfig").text;
//gameConfig = JsonConvert.DeserializeObject<APIResponse<GameConfig>>(json).data;
2026-06-30 23:57:40 +08:00
APIManager.Response<GameConfig>(APIType.GameConfig_Get,
res =>
2026-04-29 01:18:39 +08:00
{
2026-06-30 23:57:40 +08:00
gameConfig = res;
var v = CreateFishManager.Instance.CreateDic;
foreach (var item in gameConfig.fishTypeConfigs)
{
dic_fishTypeConfigs[item.fishNumber] = item;
}
2026-07-14 02:48:54 +08:00
// ⭐ 后端 BotConfig 覆盖本地(开关开启且后端返回了 botConfig)
if (useRemoteBotConfig && gameConfig.botConfig != null)
{
botConfig = gameConfig.botConfig;
Debug.Log("[BotConfig] 已使用后端下发的配置覆盖本地");
}
2026-06-30 23:57:40 +08:00
});
2026-02-03 11:35:19 +08:00
2026-07-09 14:29:17 +08:00
APIRequestList.GetActivityConfig(res => { activityConfig = res; });
2026-03-09 09:25:01 +08:00
}
2026-07-26 22:00:38 +08:00
2026-07-29 22:59:26 +08:00
#if ENABLE_LOG_FILE
// ============================ 日志文件系统 ============================
// 启用方法: Player Settings → Other Settings → Scripting Define Symbols 添加 ENABLE_LOG_FILE
// 关闭方法: 删除该符号,或 Inspector 中 enableLogFile 取消勾选
// 说明: LogGoldFlow 用 [Conditional] 标记,未定义 ENABLE_LOG_FILE 时所有调用点会被编译器自动移除,零开销
2026-07-26 22:00:38 +08:00
private StreamWriter _logFileWriter;
2026-07-29 22:59:26 +08:00
private StreamWriter _goldFlowWriter;
2026-08-01 00:49:49 +08:00
private StreamWriter _fishFlowWriter;
2026-07-29 22:59:26 +08:00
2026-07-26 22:00:38 +08:00
private void InitLogFileWriter()
{
try
{
string logDir = Path.Combine(Application.persistentDataPath, "Logs");
if (!Directory.Exists(logDir)) Directory.CreateDirectory(logDir);
string logPath = Path.Combine(logDir, DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss") + ".log");
_logFileWriter = new StreamWriter(logPath, true) { AutoFlush = true };
Application.logMessageReceived += OnLogMessageReceived;
Debug.Log($"[GameManager] 日志文件已创建: {logPath}");
2026-07-29 17:17:14 +08:00
InitGoldFlowWriter(logDir);
2026-08-01 00:49:49 +08:00
InitFishFlowWriter(logDir);
2026-07-26 22:00:38 +08:00
}
catch (Exception e)
{
Debug.LogError($"[GameManager] 初始化日志文件失败: {e.Message}");
}
}
2026-08-01 00:49:49 +08:00
private void InitFishFlowWriter(string logDir)
{
try
{
string fishLogPath = Path.Combine(logDir, "鱼生命周期_" + DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss") + ".log");
_fishFlowWriter = new StreamWriter(fishLogPath, true) { AutoFlush = true };
_fishFlowWriter.WriteLine($"# 鱼生命周期日志 - 启动于 {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
_fishFlowWriter.WriteLine($"# 当前玩家: {user?.BaseInfo?.userName ?? "未登录"}({user?.id ?? "未登录"})");
_fishFlowWriter.WriteLine($"# 说明: 记录鱼的创建/注册、回收/销毁、guid冲突、找不到鱼等事件,用于排查鱼阵鱼无法销毁的问题");
_fishFlowWriter.WriteLine($"# 分类: CREATE=创建注册 | REMOVE=成功回收 | NOT_FOUND=找不到鱼(异常) | KILL_FX=带特效击杀 | COLLISION=guid冲突 | POOL_GET=从池复用 | POOL_NEW=新建实例 | POOL_RETURN=回收到池");
_fishFlowWriter.WriteLine($"# 格式: 时间|service=client|INFO|FishFlow|[分类] guid=xxx instId=xxx 描述 dictCount=xxx");
_fishFlowWriter.WriteLine(new string('-', 80));
Debug.Log($"[GameManager] 鱼生命周期日志文件已创建: {fishLogPath}");
}
catch (Exception e)
{
Debug.LogError($"[GameManager] 初始化鱼生命周期日志失败: {e.Message}");
}
}
2026-07-29 17:17:14 +08:00
private void InitGoldFlowWriter(string logDir)
{
try
{
string goldLogPath = Path.Combine(logDir, "金币流水_" + DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss") + ".log");
_goldFlowWriter = new StreamWriter(goldLogPath, true) { AutoFlush = true };
2026-07-29 22:59:26 +08:00
// ===== 文件头:只记录当前玩家自己(进游戏前可能 user 还没拉到,做 null 保护) =====
string userName = user?.BaseInfo?.userName ?? "未登录";
string userId = user?.id ?? "未登录";
2026-07-29 17:17:14 +08:00
_goldFlowWriter.WriteLine($"# 金币流水日志 - 启动于 {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
2026-07-29 22:59:26 +08:00
_goldFlowWriter.WriteLine($"# 当前玩家: {userName}({userId})");
_goldFlowWriter.WriteLine($"# 说明: 只记录当前玩家自己的金币变动,不记录其他玩家");
_goldFlowWriter.WriteLine($"# 格式: 时间|service=client|INFO|GoldFlow|[分类] 玩家=名称(ID) 事件=target 描述");
_goldFlowWriter.WriteLine($"# 分类: 收到加钱/已加钱/收到扣钱/已扣钱/金币流水(通用)");
2026-07-29 17:17:14 +08:00
_goldFlowWriter.WriteLine(new string('-', 80));
2026-07-29 22:59:26 +08:00
2026-07-29 17:17:14 +08:00
Debug.Log($"[GameManager] 金币流水日志文件已创建: {goldLogPath}");
}
catch (Exception e)
{
Debug.LogError($"[GameManager] 初始化金币流水日志失败: {e.Message}");
}
}
2026-07-26 22:00:38 +08:00
private void OnLogMessageReceived(string condition, string stackTrace, LogType type)
{
if (_logFileWriter == null) return;
try
{
string prefix = type switch
{
LogType.Error => "[ERROR]",
LogType.Assert => "[ASSERT]",
LogType.Warning => "[WARN]",
LogType.Log => "[LOG]",
LogType.Exception => "[EXCEPTION]",
_ => "[LOG]"
};
_logFileWriter.WriteLine($"{DateTime.Now:HH:mm:ss.fff} {prefix} {condition}");
if (type == LogType.Error || type == LogType.Exception)
{
_logFileWriter.WriteLine($" Stack: {stackTrace}");
}
}
2026-07-29 22:59:26 +08:00
catch { }
2026-07-26 22:00:38 +08:00
}
private void OnDestroy()
{
if (_logFileWriter != null)
{
Application.logMessageReceived -= OnLogMessageReceived;
try { _logFileWriter.Flush(); _logFileWriter.Close(); } catch { }
_logFileWriter = null;
}
2026-07-29 17:17:14 +08:00
if (_goldFlowWriter != null)
{
try { _goldFlowWriter.Flush(); _goldFlowWriter.Close(); } catch { }
_goldFlowWriter = null;
}
2026-08-01 00:49:49 +08:00
if (_fishFlowWriter != null)
{
try { _fishFlowWriter.Flush(); _fishFlowWriter.Close(); } catch { }
_fishFlowWriter = null;
}
2026-07-26 22:00:38 +08:00
}
2026-07-29 22:59:26 +08:00
#endif
// ============================ 金币流水日志(与后端逐条对比) ============================
// 拆成4类,方便和后端日志逐行对比:
// - 收到扣钱信息(推送): 后端发来扣钱通知,客户端尚未执行
// - 已扣钱(本地执行): 客户端本地已扣完金币
// - 收到加钱信息(推送): 后端发来加钱通知,客户端尚未执行
// - 已加钱(本地执行): 客户端本地已加完金币
//
// 控制方式:
// 1. 编译时: 未定义 ENABLE_LOG_FILE 符号时,所有调用被编译器自动移除([Conditional]特性),零开销
// 2. 运行时: 即使编译了,Inspector 中 enableLogFile=false 也不会写入文件
//
// 通用记录(无关扣/加,如退出房间)继续用 LogGoldFlow
/// <summary>记录"收到扣钱信息"(后端推送,尚未执行)。如:后端推送 SyncPlayerGold 差值为负</summary>
[System.Diagnostics.Conditional("ENABLE_LOG_FILE")]
public static void LogGoldFlowReceiveCost(string eventName, string description)
{
#if ENABLE_LOG_FILE
WriteGoldFlow("收到扣钱", eventName, description);
#endif
}
/// <summary>记录"已扣钱"(本地已执行扣金币)。如:开火 ConsumeGold 扣完</summary>
[System.Diagnostics.Conditional("ENABLE_LOG_FILE")]
public static void LogGoldFlowAppliedCost(string eventName, string description)
{
#if ENABLE_LOG_FILE
WriteGoldFlow("已扣钱", eventName, description);
#endif
}
/// <summary>记录"收到加钱信息"(后端推送,尚未执行)。如:RoomJoinNotice/RoomInfo 推送金币</summary>
[System.Diagnostics.Conditional("ENABLE_LOG_FILE")]
public static void LogGoldFlowReceiveGain(string eventName, string description)
{
#if ENABLE_LOG_FILE
WriteGoldFlow("收到加钱", eventName, description);
#endif
}
/// <summary>记录"已加钱"(本地已执行加金币)。如:杀鱼 KillFish 加完、进房 JoinGame 设置初始金币</summary>
[System.Diagnostics.Conditional("ENABLE_LOG_FILE")]
public static void LogGoldFlowAppliedGain(string eventName, string description)
{
#if ENABLE_LOG_FILE
WriteGoldFlow("已加钱", eventName, description);
#endif
}
/// <summary>通用金币流水记录(无关扣/加,如退出房间)。需要明确分类时请用上面4个方法</summary>
[System.Diagnostics.Conditional("ENABLE_LOG_FILE")]
public static void LogGoldFlow(string eventName, string description)
{
#if ENABLE_LOG_FILE
WriteGoldFlow("金币流水", eventName, description);
#endif
}
#if ENABLE_LOG_FILE
/// <summary>金币流水核心写入(私有)。所有公开 LogGoldFlow* 方法最终都走这里</summary>
private static void WriteGoldFlow(string category, string eventName, string description)
{
var inst = Instance;
if (inst == null || !inst.enableLogFile || inst._goldFlowWriter == null) return;
try
{
string userName = inst.user?.BaseInfo?.userName ?? "未知";
string userId = inst.user?.id ?? "未知";
string timeStr = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
inst._goldFlowWriter.WriteLine($"{timeStr}|service=client|INFO|GoldFlow|[{category}] 玩家={userName}({userId}) 事件={eventName} {description}");
}
catch { }
}
#endif
2026-07-26 22:00:38 +08:00
2026-08-01 00:49:49 +08:00
// ============================ 鱼生命周期日志(排查鱼阵鱼无法销毁问题) ============================
// 分类:
// CREATE 鱼创建并注册到字典(RegisterFishInstance)
// REMOVE 鱼从字典成功移除(Id_DieFish_NotFX 成功路径)
// NOT_FOUND 找不到鱼(Id_DieFish_NotFX 失败路径 —— 这就是bug)
// KILL_FX 带特效击杀(Id_DieFish_FX)
// COLLISION guid 冲突(新鱼覆盖旧鱼,旧鱼变野鱼)
// POOL_GET 从池复用鱼对象
// POOL_NEW 新建鱼实例(池中没有)
// POOL_RETURN 回收鱼对象到池
//
// 控制方式同金币流水: ENABLE_LOG_FILE 编译时 + enableLogFile 运行时
/// <summary>
/// 记录鱼生命周期事件。instId 传 0 表示不需要实例ID。
/// </summary>
[System.Diagnostics.Conditional("ENABLE_LOG_FILE")]
public static void LogFishFlow(string category, string guid, string description, int instId = 0)
{
#if ENABLE_LOG_FILE
WriteFishFlow(category, guid, description, instId);
#endif
}
#if ENABLE_LOG_FILE
/// <summary>鱼生命周期核心写入(私有)</summary>
private static void WriteFishFlow(string category, string guid, string description, int instId)
{
var inst = Instance;
if (inst == null || !inst.enableLogFile || inst._fishFlowWriter == null) return;
try
{
string timeStr = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
string instStr = instId > 0 ? $" instId={instId}" : "";
// dictCount 从 CreateFishManager 取,这里只负责写文件
inst._fishFlowWriter.WriteLine($"{timeStr}|service=client|INFO|FishFlow|[{category}] guid={guid}{instStr} {description}");
}
catch { }
}
#endif
2026-06-08 21:23:40 +08:00
public async Task InItLoad()
{
await TurretResourcesData.LoadTurretAssetsData();
}
2026-03-09 09:25:01 +08:00
public void StartGame(Action action)
2025-12-13 18:18:28 +08:00
{
2026-02-03 11:35:19 +08:00
2025-12-26 11:15:56 +08:00
switch (gameData.currGameType)
{
case GameType.JQQD:
break;
case GameType.defaultPanel:
UIManager.Instance.ShowAsync(UIPanel.MainGamePanelDefault);
break;
2026-03-12 10:48:53 +08:00
case GameType.FLZY:
//UIManager.Instance.ShowAsync(UIPanel.MainGamePanelFLZY);
2026-07-10 23:26:42 +08:00
ConnectGameSocket(action);
2025-12-26 11:15:56 +08:00
break;
2026-05-07 03:46:03 +08:00
case GameType.YYSC:
2026-07-10 23:26:42 +08:00
ConnectGameSocket(action);
2025-12-26 11:15:56 +08:00
break;
case GameType.ZGXQ:
2026-07-10 23:26:42 +08:00
//UIManager.Instance.ShowAsync(UIPanel.MainGamePanelZGXQ);
2026-02-13 19:23:01 +08:00
//这里设置触发棋盘的初始化
2025-12-26 11:15:56 +08:00
break;
case GameType.XJMH:
2026-07-10 23:26:42 +08:00
ConnectGameSocket(action);
2025-12-26 11:15:56 +08:00
break;
case GameType.HHWK:
2026-03-18 17:26:14 +08:00
//UIManager.Instance.ShowAsync(UIPanel.MainGamePanelDefault);
2026-07-10 23:26:42 +08:00
ConnectGameSocket(action);
2025-12-26 11:15:56 +08:00
break;
default:
break;
}
2026-03-09 09:25:01 +08:00
2025-12-13 18:18:28 +08:00
}
2026-07-10 23:26:42 +08:00
/// <summary>
/// 通过 /api/Room/Assign 获取房间专属 socket 地址后连接 game socket
/// </summary>
private void ConnectGameSocket(Action action)
{
string roomId = gameData.currRoomData?.roomId;
if (string.IsNullOrEmpty(roomId))
{
#if UNITY_EDITOR
Debug.LogWarning("[GameManager] currRoomData.roomId 为空,使用默认 game socket 地址");
#endif
WebSocketManager.Instance.Connect(SocketType.game, action);
return;
}
APIRequestList.RoomAssign(roomId, url =>
{
if (!string.IsNullOrEmpty(url))
WebSocketManager.Instance.Connect(SocketType.game, url, action);
else
WebSocketManager.Instance.Connect(SocketType.game, action);
});
}
2026-07-20 22:32:52 +08:00
/// <summary>
/// 快速加入:用 /api/Room/QuickJoin 返回的 url 直接连 game socket(无需再调 Assign)
/// </summary>
/// <param name="url">QuickJoin 返回的房间专属 socket 地址</param>
/// <param name="action">连接成功后的回调(一般是 JoinRoom(roomId, userId, -1))</param>
public void StartGameQuickJoin(string url, Action action)
{
switch (gameData.currGameType)
{
case GameType.FLZY:
case GameType.YYSC:
case GameType.XJMH:
case GameType.HHWK:
if (!string.IsNullOrEmpty(url))
WebSocketManager.Instance.Connect(SocketType.game, url, action);
else
WebSocketManager.Instance.Connect(SocketType.game, action);
break;
default:
WebSocketManager.Instance.Connect(SocketType.game, action);
break;
}
}
2025-12-11 16:40:24 +08:00
// Start is called before the first frame update
2026-04-21 23:02:51 +08:00
public Button btn_Skip;
public VideoClip startVideoClip;
2026-07-25 21:04:58 +08:00
IEnumerator yanchi(LoadingPanel loadingPanel)
{
yield return new WaitForSeconds(5);
2026-08-01 15:27:12 +08:00
if (btn_Skip != null) btn_Skip.gameObject.SetActive(true);
2026-07-25 21:04:58 +08:00
yield return new WaitForSeconds(23f);
2026-08-01 15:27:12 +08:00
// ⭐ 防御:loadingPanel 可能已被 UIManager.CloseAll 销毁(如 Logout 时)
if (loadingPanel != null)
loadingPanel.LoadBar(100);
2026-07-25 21:04:58 +08:00
}
2025-12-11 16:40:24 +08:00
void Start()
{
2026-07-25 21:04:58 +08:00
btn_Skip.gameObject.SetActive(false);
2026-04-21 23:02:51 +08:00
var v = UIManager.Instance.ShowLoadPanel(startVideoClip);
2026-07-25 21:04:58 +08:00
//UIManager.Instance.ShowAsync(UIPanel.LoginPanel, null, (a) =>
//{
// v.LoadBar(100);
//}, 28);
StartCoroutine(yanchi(v));
2026-04-21 23:02:51 +08:00
btn_Skip.onClick.AddListener(() =>
{
2026-06-30 23:57:40 +08:00
UIManager.Instance.StopShowPanel();
2026-07-25 21:04:58 +08:00
//UIManager.Instance.ShowAsync(UIPanel.LoginPanel, null, null, 0);
2026-04-21 23:02:51 +08:00
v.LoadBar(100);
});
2025-12-11 16:40:24 +08:00
}
// Update is called once per frame
void Update()
{
2026-03-18 17:26:14 +08:00
#if UNITY_EDITOR
if (Keyboard.current.qKey.wasPressedThisFrame)
{
UIManager.Instance.ShowBoardGongGao("太有实力了dadadadwawafdsdrgdfghdsufhkjghjksdfdgdffghgjhgykhjkhjghjfg");
}
#endif
2025-12-26 11:15:56 +08:00
2026-07-24 01:37:11 +08:00
//// FPS 统计
//fpsFrameCount++;
//fpsTimer += Time.unscaledDeltaTime;
//if (fpsTimer >= 0.5f)
//{
// fps = Mathf.RoundToInt(fpsFrameCount / fpsTimer);
// fpsFrameCount = 0;
// fpsTimer = 0f;
//}
2026-07-11 19:05:27 +08:00
}
2026-07-24 01:37:11 +08:00
//// ===== FPS 显示 =====
//private int fpsFrameCount;
//private float fpsTimer;
//private int fps;
2026-07-11 19:05:27 +08:00
2026-07-24 01:37:11 +08:00
//void OnGUI()
//{
// // 左上角显示帧数
// GUIStyle style = new GUIStyle(GUI.skin.label)
// {
// fontSize = 28,
// fontStyle = FontStyle.Bold
// };
// style.normal.textColor = fps >= 50 ? Color.green : (fps >= 30 ? Color.yellow : Color.red);
// GUI.Label(new Rect(10, 10, 200, 40), "FPS: " + fps, style);
//}
2026-06-28 16:48:18 +08:00
public void Logout(Action onDone = null)
{
2026-08-01 15:27:12 +08:00
// ⭐ 标记登出中:阻止 WebSocket 关闭后触发自动重连(避免访问已 null 的 user)
WebSocketManager.Instance.isLoggingOut = true;
// 关闭所有 WebSocket 连接(内部会先 StopAllReconnects 停止所有重连协程)
2026-06-28 16:48:18 +08:00
WebSocketManager.Instance.CloseAll();
// 清空用户数据
user = null;
// 关闭所有 UI
UIManager.Instance.CloseAll();
2026-08-01 15:27:12 +08:00
// ⭐ 登出流程完成,立即恢复重连开关(不依赖 ShowAsync 回调,避免 UI 异常导致永不重置)
WebSocketManager.Instance.isLoggingOut = false;
2026-06-28 16:48:18 +08:00
// 切换到登录界面
UIManager.Instance.ShowAsync(UIPanel.LoginPanel, null, (panel) =>
{
onDone?.Invoke();
});
}
2025-12-11 16:40:24 +08:00
}