Files
5.24BuYu/Assets/MyGame/Scripts/Manager/GameManager.cs
T
2026-07-29 17:17:14 +08:00

510 lines
18 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.
using Assets.MyGame.Scripts.Net;
using Assets.MyGame.Scripts.Net.API;
using Assets.MyGame.Scripts.Net.API.GameConfig_SubClass;
using CameraShake;
using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using UnityEngine.Video;
public class GameManager : MonoSingleton<GameManager>
{
[Header("人机")]
public bool enableBot = true; // 人机总开关
public BotConfig botConfig; // 人机AI详细配置(Inspector可调,本地测试用)
[Tooltip("是否使用后端下发的 BotConfig。false=用 Inspector 本地配置(默认,方便测试);true=用后端 GameConfig 返回的 botConfig 覆盖本地")]
public bool useRemoteBotConfig = false;
public GameConfig gameConfig;
public ActivityConfig activityConfig;
public Dictionary<string, FishTypeConfig> dic_fishTypeConfigs = new Dictionary<string, FishTypeConfig>();
public UserData user;
public GameData gameData = new GameData();
public Scene scene;
/// <summary>
/// 头像更新事件(换了头像后触发,所有头像控件统一刷新)
/// </summary>
/// <summary>头像更新事件</summary>
public event Action<Sprite> OnAvatarChanged;
/// <summary>头像框更新事件</summary>
public event Action<Sprite> OnAvatarFrameChanged;
/// <summary>用户数据更新事件(金币/钻石/等级等发生变化时触发)</summary>
public event Action OnUserDataChanged;
/// <summary>头像字典(name → sprite),从 Resources/Avatar 加载</summary>
private Dictionary<string, Sprite> avatarSpriteDict = new Dictionary<string, Sprite>();
/// <summary>头像框字典(name → sprite),从 Resources/AvatarK 加载</summary>
private Dictionary<string, Sprite> avatarFrameDict = new Dictionary<string, Sprite>();
/// <summary>默认头像(新账号无头像时使用)</summary>
private Sprite defaultAvatar;
/// <summary>默认头像框</summary>
private Sprite defaultAvatarFrame;
/// <summary>触发头像更新事件</summary>
public void TriggerAvatarChanged(Sprite sprite)
{
OnAvatarChanged?.Invoke(sprite);
}
public CameraShaker cameraShake;
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
// }
/// <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();
if (string.IsNullOrEmpty(avatarName) || !avatarSpriteDict.TryGetValue(avatarName, out var sprite))
sprite = GetDefaultAvatar();
return sprite;
}
private Sprite GetDefaultAvatar()
{
if (defaultAvatar == null && avatarSpriteDict.Count > 0)
{
// 取缓存中第一个头像作为默认
foreach (var kv in avatarSpriteDict)
{
defaultAvatar = kv.Value;
break;
}
}
return defaultAvatar;
}
/// <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);
});
}
// ==================== 头像框 ====================
/// <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);
});
}
public void UpdataUserData(UserData user)
{
this.user = user;
OnUserDataChanged?.Invoke();
}
protected override void Awake()
{
// ⭐ botConfig 默认初始化,避免 null 导致技能系统不跑
if (botConfig == null) botConfig = new BotConfig();
// ⭐ 游戏启动时加载已保存的画质和帧率设置
int quality = PlayerPrefs.GetInt("GraphicsQuality", 1);
QualitySettings.SetQualityLevel(quality, true);
int fps = PlayerPrefs.GetInt("TargetFps", 1);
Application.targetFrameRate = fps switch
{
0 => 30,
1 => 60,
2 => -1,
_ => 60
};
// ⭐ 防止手机屏幕自动黑屏休眠
Screen.sleepTimeout = SleepTimeout.NeverSleep;
// ⭐ 启动日志文件自动保存:把所有 Debug.Log/Warning/Error 写入 persistentDataPath/Logs/
InitLogFileWriter();
AudioResourcesData audioResourcesData = new AudioResourcesData();
InItLoad();
//TurretResourcesData turretResourcesData = new TurretResourcesData();
Application.runInBackground = true;
base.Awake();
cameraShake = Camera.main.GetComponent<CameraShaker>();
gameData.currUser = "123";
//string json = Resources.Load<TextAsset>("Config/GameConfig").text;
//gameConfig = JsonConvert.DeserializeObject<APIResponse<GameConfig>>(json).data;
APIManager.Response<GameConfig>(APIType.GameConfig_Get,
res =>
{
gameConfig = res;
var v = CreateFishManager.Instance.CreateDic;
foreach (var item in gameConfig.fishTypeConfigs)
{
dic_fishTypeConfigs[item.fishNumber] = item;
}
// ⭐ 后端 BotConfig 覆盖本地(开关开启且后端返回了 botConfig)
if (useRemoteBotConfig && gameConfig.botConfig != null)
{
botConfig = gameConfig.botConfig;
Debug.Log("[BotConfig] 已使用后端下发的配置覆盖本地");
}
});
APIRequestList.GetActivityConfig(res => { activityConfig = res; });
}
// ⭐ 日志文件写入器:把 Application.logMessageReceived 的日志实时写入文件
private StreamWriter _logFileWriter;
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}");
// 同时初始化金币流水日志文件
InitGoldFlowWriter(logDir);
}
catch (Exception e)
{
Debug.LogError($"[GameManager] 初始化日志文件失败: {e.Message}");
}
}
// ⭐ 金币流水日志写入器:单独写入"金币流水"文件,格式对齐后端日志
private StreamWriter _goldFlowWriter;
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 };
_goldFlowWriter.WriteLine($"# 金币流水日志 - 启动于 {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
_goldFlowWriter.WriteLine($"# 格式: 时间|service=client|INFO|GoldFlow|[金币流水] 玩家=名称(ID) 事件=xxx {描述}");
_goldFlowWriter.WriteLine(new string('-', 80));
Debug.Log($"[GameManager] 金币流水日志文件已创建: {goldLogPath}");
}
catch (Exception e)
{
Debug.LogError($"[GameManager] 初始化金币流水日志失败: {e.Message}");
}
}
/// <summary>
/// 记录金币流水(供全项目调用)
/// 用法: GameManager.LogGoldFlow("KillFish", $"击中鱼原有金币={oldGold} 增加金币=+{addGold} 剩余金币={newGold} 房间={roomId}");
/// </summary>
public static void LogGoldFlow(string eventName, string description)
{
var inst = Instance;
if (inst == null || 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|[金币流水] 玩家={userName}({userId}) 事件={eventName} {description}");
}
catch { /* 写日志失败不影响游戏运行 */ }
}
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}");
}
}
catch { /* 写日志失败不影响游戏运行 */ }
}
private void OnDestroy()
{
if (_logFileWriter != null)
{
Application.logMessageReceived -= OnLogMessageReceived;
try { _logFileWriter.Flush(); _logFileWriter.Close(); } catch { }
_logFileWriter = null;
}
if (_goldFlowWriter != null)
{
try { _goldFlowWriter.Flush(); _goldFlowWriter.Close(); } catch { }
_goldFlowWriter = null;
}
}
public async Task InItLoad()
{
await TurretResourcesData.LoadTurretAssetsData();
}
public void StartGame(Action action)
{
switch (gameData.currGameType)
{
case GameType.JQQD:
break;
case GameType.defaultPanel:
UIManager.Instance.ShowAsync(UIPanel.MainGamePanelDefault);
break;
case GameType.FLZY:
//UIManager.Instance.ShowAsync(UIPanel.MainGamePanelFLZY);
ConnectGameSocket(action);
break;
case GameType.YYSC:
ConnectGameSocket(action);
break;
case GameType.ZGXQ:
//UIManager.Instance.ShowAsync(UIPanel.MainGamePanelZGXQ);
//这里设置触发棋盘的初始化
break;
case GameType.XJMH:
ConnectGameSocket(action);
break;
case GameType.HHWK:
//UIManager.Instance.ShowAsync(UIPanel.MainGamePanelDefault);
ConnectGameSocket(action);
break;
default:
break;
}
}
/// <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);
});
}
/// <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;
}
}
// Start is called before the first frame update
public Button btn_Skip;
public VideoClip startVideoClip;
IEnumerator yanchi(LoadingPanel loadingPanel)
{
yield return new WaitForSeconds(5);
btn_Skip.gameObject.SetActive(true);
yield return new WaitForSeconds(23f);
loadingPanel.LoadBar(100);
}
void Start()
{
btn_Skip.gameObject.SetActive(false);
var v = UIManager.Instance.ShowLoadPanel(startVideoClip);
//UIManager.Instance.ShowAsync(UIPanel.LoginPanel, null, (a) =>
//{
// v.LoadBar(100);
//}, 28);
StartCoroutine(yanchi(v));
btn_Skip.onClick.AddListener(() =>
{
UIManager.Instance.StopShowPanel();
//UIManager.Instance.ShowAsync(UIPanel.LoginPanel, null, null, 0);
v.LoadBar(100);
});
}
// Update is called once per frame
void Update()
{
#if UNITY_EDITOR
if (Keyboard.current.qKey.wasPressedThisFrame)
{
UIManager.Instance.ShowBoardGongGao("太有实力了dadadadwawafdsdrgdfghdsufhkjghjksdfdgdffghgjhgykhjkhjghjfg");
}
#endif
//// FPS 统计
//fpsFrameCount++;
//fpsTimer += Time.unscaledDeltaTime;
//if (fpsTimer >= 0.5f)
//{
// fps = Mathf.RoundToInt(fpsFrameCount / fpsTimer);
// fpsFrameCount = 0;
// fpsTimer = 0f;
//}
}
//// ===== FPS 显示 =====
//private int fpsFrameCount;
//private float fpsTimer;
//private int fps;
//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);
//}
public void Logout(Action onDone = null)
{
// 关闭所有 WebSocket 连接
WebSocketManager.Instance.CloseAll();
// 清空用户数据
user = null;
// 关闭所有 UI
UIManager.Instance.CloseAll();
// 切换到登录界面
UIManager.Instance.ShowAsync(UIPanel.LoginPanel, null, (panel) =>
{
onDone?.Invoke();
});
}
}