Unity中较为完善的NetworkManager

通过其他地方调用http-post登陆请求,发送token,使用token进行验证,验证后进行socket-tcp长连接

cs 复制代码
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using Cysharp.Threading.Tasks;
using Game.Protocol;
using Game.Runtime.Migu;
using Google.Protobuf;
using UnityEngine;
using UnityEngine.Networking;

public class NetworkManager : MonoSingleton<NetworkManager>
{
    [Header("网络设置")] 
    // 本地 xxx.xx.xxx.xx
    // 公网 xxx.xxx.x.xx
    public string serverIP = "xxx.xxx.x.xx";
    public int serverPort = xxxx;
    public string httpServerUrl = "http://xxx.xxx.x.xx:xxxx"; // HTTP服务器地址

    [Header("心跳设置")] 
    public float heartbeatInterval = 30f;                   // 心跳间隔30秒
    private long _lastHeartbeatSendTime;                    // 上次心跳发送时间戳
    
    [Header("协议设置")]
    public string protocolVersion = "1.0.0";                // 协议版本
    public uint clientSeq = 1;                              // 客户端序列号计数器
    
    private Socket clientSocket;                            // 本机客户端Socket
    private int _expectedBodyLength = -1;                   // 期望的消息体长度
    private List<byte> _receiveBuffer = new();              // 累积接收缓冲区
    private bool isConnected;                               // 连接状态标志位
    private Queue<BasePacket> messageQueue = new();         // 处理消息队列(改为BasePacket)
    private object queueLock = new();                       // 消息队列线程锁
    private Thread receiveThread;                           // 后台持续接收消息线程
    private Dictionary<uint, Action<BasePacket>> pendingRequests = new(); // 待处理请求的映射表
    private object pendingLock = new();                     // 待处理请求锁

    public bool isUserInfoReceived = false;                 // 是否已收到用户信息
    
    #region 生命周期

    protected override void Awake()
    {
        base.Awake();
        DontDestroyOnLoad(gameObject);
    }

    private void Start()
    {
        //Connect();
        //ConnectServerWithMiguInfo();
    }

    private void Update()
    {
        ProcessMessageQueue();
    }

    protected override void OnDestroy()
    {
        Disconnect();
    }

    protected override void OnApplicationQuit()
    {
        base.OnApplicationQuit();
        Disconnect();
    }

    #endregion

    /// <summary>
    /// 向服务端发送从快游拿到的信息建立连接
    /// </summary>
    public void ConnectServerWithMiguInfo()
    {
        PlayerData data = SDKManager.Instance.MiguPlayerData;
        LoginAndConnect(data.token, data.userId, Application.version);
    }
    
    // 初始化连接
    private void Connect()
    {
        try
        {
            clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            clientSocket.Connect(serverIP, serverPort);
            isConnected = true;

            receiveThread = new Thread(ReceiveData);
            receiveThread.IsBackground = true;
            receiveThread.Start();

            Debug.Log("连接至服务器成功");

            // 发送握手请求
            SendHandshakeRequest();

            // 持续发送心跳消息
            StartHeartbeat();

            // 把userId,token,accountId带过去
            AccountInfo accountInfo = GameManager.Instance.accountInfo;

            // 验证accountInfo是否为空
            if (accountInfo == null)
            {
                Debug.LogError("AccountInfo为空,无法发送登录请求");
                return;
            }

            // Socket的登录请求
            SendLoginRequest(accountInfo.userId, accountInfo.token, accountInfo.secretUserId, accountInfo.accountId,
                accountInfo.deviceId);
            
            // 拿到当前玩家的信息并储存
            SendUserInfoReq(GameManager.Instance.accountInfo.accountId);
        }
        catch (Exception e)
        {
            Debug.Log(e.Message);
        }
    }

    // 断开连接
    private void Disconnect()
    {
        try
        {
            isConnected = false;
            StopHeartbeat();

            if (clientSocket != null)
            {
                if (clientSocket.Connected) clientSocket.Shutdown(SocketShutdown.Both);
                clientSocket.Close();
                clientSocket.Dispose();
                clientSocket = null;
            }

            receiveThread?.Join(1000); // 等待接收线程结束

            Debug.Log("与服务器断开连接");
        }
        catch (Exception e)
        {
            Debug.LogError($"断开连接错误: {e.Message}");
        }
    }

    // 持续接收来自服务器的消息 后台线程
    private void ReceiveData()
    {
        // 创建接收缓冲区,每次最多接收1024字节
        var buffer = new byte[1024];

        while (isConnected && clientSocket != null && clientSocket.Connected)
        {
            try
            {
                // 从socket接收数据,该方法会阻塞直到有数据到达或连接关闭
                var bytesRead = clientSocket.Receive(buffer);
                Debug.Log($"收到{bytesRead}字节数的消息");
                
                // 检查接收到的字节数
                if (bytesRead > 0)
                {
                    // 创建正确大小的数组存放本次接收的数据
                    var newData = new byte[bytesRead];
                    Array.Copy(buffer, newData, bytesRead);
                    // 将新数据添加到累积缓冲区
                    _receiveBuffer.AddRange(newData);

                    // 处理累积缓冲区中的数据
                    ProcessReceivedData();
                }
                else
                {
                    // 连接已关闭
                    Debug.Log("服务器连接已关闭");
                    isConnected = false;
                    break;
                }
            }
            catch (SocketException se)
            {
                Debug.LogError($"网络接收错误: {se.Message}");
                isConnected = false;
                break;
            }
            catch (Exception e)
            {
                Debug.LogError($"接收数据异常: {e.Message}");
                isConnected = false;
                break;
            }
        }

        Debug.Log("接收线程结束");
        Disconnect();
    }

    // 处理接收到的数据,解析完整消息
    private void ProcessReceivedData()
    {
        try
        {
            // 循环处理所有完整消息
            while (_receiveBuffer.Count >= 4) // 至少要有消息头
            {
                // 如果还没有解析出消息体长度,先解析消息头
                if (_expectedBodyLength == -1)
                {
                    // 读取消息头(4字节)
                    var headBytes = _receiveBuffer.GetRange(0, 4).ToArray();
                    
                    // 手动读取并转换大端序消息头
                    _expectedBodyLength = (headBytes[0] << 24) | 
                                          (headBytes[1] << 16) | 
                                          (headBytes[2] << 8) | 
                                          headBytes[3];

                    // 添加长度验证
                    if (_expectedBodyLength < 0 || _expectedBodyLength > 10 * 1024 * 1024) // 限制10MB
                    {
                        Debug.LogError($"无效的消息长度: {_expectedBodyLength}");
                        Disconnect();
                        return;
                    }
                }

                // 检查是否收到了完整的消息体
                if (_receiveBuffer.Count >= 4 + _expectedBodyLength)
                {
                    // 提取消息体
                    var bodyBytes = _receiveBuffer.GetRange(4, _expectedBodyLength).ToArray();
                    
                    // 使用protobuf解析BasePacket
                    var packet = BasePacket.Parser.ParseFrom(bodyBytes);
                    
                    lock (queueLock)
                    {
                        messageQueue.Enqueue(packet);
                    }

                    // 从缓冲区中移除已处理的数据
                    _receiveBuffer.RemoveRange(0, 4 + _expectedBodyLength);

                    // 重置期望长度,准备处理下一条消息
                    _expectedBodyLength = -1;
                }
                else
                {
                    // 数据不完整,等待下次接收
                    break;
                }
            }
        }
        catch (Exception e)
        {
            Debug.LogError($"处理接收数据错误: {e.Message}");
            Disconnect();
        }
    }

    // 处理消息队列
    private void ProcessMessageQueue()
    {
        lock (queueLock)
        {
            while (messageQueue.Count > 0)
            {
                var packet = messageQueue.Dequeue();
                HandlePacket(packet);
            }
        }
    }

    // 根据消息类型处理消息
    private void HandlePacket(BasePacket packet)
    {
        try
        {
            var header = packet.Header;
            var command = header.Command;
            var seq = header.Seq;
            var errorCode = header.ErrorCode;
            var errorMsg = header.ErrorMsg;

            // 检查错误码
            if (errorCode != 0)
            {
                Debug.LogError($"收到错误响应: command={command}, errorCode={errorCode}, errorMsg={errorMsg}");
                
                // 触发对应的错误回调
                lock (pendingLock)
                {
                    if (pendingRequests.ContainsKey(seq))
                    {
                        pendingRequests[seq]?.Invoke(packet);
                        pendingRequests.Remove(seq);
                    }
                }
                return;
            }

            Debug.Log($"接收到消息,命令字{command}");
            // 根据命令字处理不同类型的消息
            switch (command)
            {
                // 系统消息
                // 心跳消息
                case (int)Command.HeartbeatResp:
                    HandleHeartbeatResp(packet);
                    break;
                    
                // 握手消息回应
                case (int)Command.HandshakeResp:
                    HandleHandshakeResp(packet);
                    break;
                    
                // 认证消息
                case (int)Command.LoginResp:
                    HandleLoginResp(packet);
                    break;
                
                // 处理玩家信息
                case (int)Command.UserInfoResp:
                    HandleUserInfoResp(packet);
                    break;
                
                // 登出消息
                case (int)Command.LogoutResp:
                    HandleLogoutResp(packet);
                    break;
                    
                // 玩家数据消息
                // 切换关卡
                // case (int)Command.ChangeLevelResp:
                //     HandleChangeLevelResp(packet);
                //     break;
                    
                // 切换角色
                // case (int)Command.ChangePlayerResp:
                //     HandleChangeCharacterResp(packet);
                //     break;
                    
                // 通过关卡
                // case (int)Command.PassLevelResp:
                //     HandlePassLevelResp(packet);
                //     break;
                    
                // ?? 预留 玩家升级推送通知
                case (int)Command.PlayerLevelUpNotify:
                    HandlePlayerLevelUpNotify(packet);
                    break;
                    
                // ?? 玩家某个角色升星推送
                case (int)Command.CharacterStarUpNotify:
                    HandlePlayerStarUpNotify(packet);
                    break;
                
                // ?? 已废弃?? 角色升星
                // case (int)Command.PlayerStarUpResp:
                //     HandlePlayerStarUpResp(packet);
                //     break;
                    
                // 解锁角色
                // case (int)Command.UnlockPlayerResp:
                //     HandleUnlockPlayerResp(packet);
                //     break;
                    
                // ?? 预留 使用道具
                case (int)Command.UseItemResp:
                    HandleUseItemResp(packet);
                    break;
                    
                // 红点系统消息
                case (int)Command.RedDotResp:
                    HandleRedDotResp(packet);
                    break;
                    
                case (int)Command.RedDotUpdate:
                    HandleRedDotUpdate(packet);
                    break;
                    
                default:
                    Debug.LogWarning($"未知的命令字: {command}");
                    break;
            }
            
            // 触发对应的请求回调
            lock (pendingLock)
            {
                if (pendingRequests.ContainsKey(seq))
                {
                    pendingRequests[seq]?.Invoke(packet);
                    pendingRequests.Remove(seq);
                }
            }
        }
        catch (Exception e)
        {
            Debug.LogError($"处理消息时出错: {e.Message}, 堆栈: {e.StackTrace}");
        }
    }

    #region 消息处理函数

    // 处理心跳响应
    private void HandleHeartbeatResp(BasePacket packet)
    {
        var heartbeatResp = HeartbeatResp.Parser.ParseFrom(packet.Body);
        Debug.Log($"收到心跳响应: 客户端时间={heartbeatResp.ClientTime}, 服务器时间={heartbeatResp.ServerTime}");
    
        // 计算单向延迟(可能不准)
        long oneWayDelay = heartbeatResp.ServerTime - heartbeatResp.ClientTime;
    
        // 计算往返延迟(更准确)
        long currentTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
        long roundTripDelay = currentTime - heartbeatResp.ClientTime;
    
        Debug.Log($"单向延迟: {oneWayDelay}ms");
        Debug.Log($"往返延迟(RTT): {roundTripDelay}ms");
    }

    // 处理握手响应
    private void HandleHandshakeResp(BasePacket packet)
    {
        var handshakeResp = HandshakeResp.Parser.ParseFrom(packet.Body);
        Debug.Log($"握手成功: 服务器版本={handshakeResp.ServerVersion}, 会话密钥={handshakeResp.SessionKey}");
        
        // 握手成功后可以发送登录请求
        // SendPlayerConnectRequest();
    }

    // 处理玩家连接响应
    private void HandleLoginResp(BasePacket packet)
    {
        var loginResp = LoginResp.Parser.ParseFrom(packet.Body);
        Debug.Log($"玩家连接成功: AccountID={loginResp.AccountId}, 账号ID={loginResp.UserId}, 昵称={loginResp.Nickname}, 等级={loginResp.Level}, 登录时间={loginResp.LoginTime}");
        
        // 处理红点信息
        foreach (var redDot in loginResp.RedDots)
        {
            Debug.Log($"红点系统: ID={redDot.SystemId}, 状态={redDot.HasRedDot}");
        }
    }
    
    // 请求获得玩家业务信息
    private void HandleUserInfoResp(BasePacket packet)
    {
        var userInfo = UserInfoResp.Parser.ParseFrom(packet.Body);
        // 更新玩家信息 在本地保存一份临时变量
        GameManager.Instance.userInfo = new UserInfo()
        {
            accountId = userInfo.AccountId,
            userName = userInfo.UserName,
            userId = userInfo.UserId,
            rankLevel = userInfo.RankLevel,
            level = userInfo.Level,
            ownedCharacterList = new List<PlayerMessage>(userInfo.OwnedCharacterList),
            avatarFrameId = userInfo.AvatarFrameId,
            lastLoginTime = userInfo.LastLoginTime,
            gold = userInfo.Gold,
            lastChosenLevelId = userInfo.LastChosenLevelId,
            maxLevelId = userInfo.MaxLevelId,
        };

        // 狗策划需求 进来上次选择关卡是玩过的最大关卡
        GameManager.Instance.userInfo.lastChosenLevelId = userInfo.MaxLevelId;
        GameManager.Instance.CurrentGameData.lastChosenLevelId = userInfo.MaxLevelId;

        Debug.Log($"从服务器拿角色数据,收到的数据:{userInfo.ToString()}\n, {GameManager.Instance.userInfo.ToString()}");
        isUserInfoReceived = true;
    }

    // 处理玩家断开响应
    private void HandleLogoutResp(BasePacket packet)
    {
        var disconnectResp = LogoutResp.Parser.ParseFrom(packet.Body);
        Debug.Log($"玩家断开连接: 用户ID={disconnectResp.UserId}, 时间={disconnectResp.LogoutTime}");
    }

    // 处理切换关卡响应
    private void HandleChangeLevelResp(BasePacket packet)
    {
        var changeLevelResp = ChangeLevelResp.Parser.ParseFrom(packet.Body);
        bool canPlay = changeLevelResp.CanPlay;
    }

    // 处理切换角色响应
    private void HandleChangeCharacterResp(BasePacket packet)
    {
        var changePlayerResp = ChangePlayerResp.Parser.ParseFrom(packet.Body);
        
        string unlockedCharacters = string.Join(", ", changePlayerResp.UnlockedPlayers);
    }

    // 处理通过关卡响应
    private void HandlePassLevelResp(BasePacket packet)
    {
        var passLevelResp = PassLevelResp.Parser.ParseFrom(packet.Body);
    }

    // 处理玩家升级通知
    private void HandlePlayerLevelUpNotify(BasePacket packet)
    {
        var levelUpNotify = PlayerLevelUpNotify.Parser.ParseFrom(packet.Body);
        
        string unlockedSystems = string.Join(", ", levelUpNotify.UnlockedSystems);
        Debug.Log($"玩家升级通知: {levelUpNotify.OldLevel}→{levelUpNotify.NewLevel}, " +
                  $"当前经验={levelUpNotify.CurrentExp}, 当前金币={levelUpNotify.CurrentCoins}, " +
                  $"解锁系统={unlockedSystems}");
    }

    // 处理玩家某个角色升星
    private void HandlePlayerStarUpNotify(BasePacket packet)
    {
        Debug.Log("接收到角色升星推送");
        var starUpResp = PlayerStarUpResp.Parser.ParseFrom(packet.Body);

        Debug.Log($"接收到的原始消息:{starUpResp}");
        Debug.Log($"升星角色的id:{starUpResp.PlayerId}, 原星级:{starUpResp.OldStar}, 升级到的星级:{starUpResp.NewStar}({(int)starUpResp.NewStar})");

        PlayerMessage data =
            GameManager.Instance.userInfo.ownedCharacterList
                .Find(data => data.PlayerId == starUpResp.PlayerId);

        // 更新本地角色星级
        if (data != null)
        {
            data.PlayerStar = starUpResp.NewStar;
            // 如果直接进入可能会出问题
            if (GameManager.Instance.CurrentGameData.currentSelectedCharacter.id == starUpResp.PlayerId)
            {
                GameManager.Instance.CurrentGameData.currentSelectedCharacter.star = (int)starUpResp.NewStar;
            }
            Debug.Log($"更新本地记录:角色id:{data.PlayerId}升星为:{starUpResp}星");
        }
        else
        {
            Debug.Log($"发的东西在我本地找不到这个角色,升个蛋的星:playerId:{starUpResp.PlayerId}");
        }
        
        UIManager.Instance.ShowPanel<AddLevelPanel>("addLevelPanel", (panel) =>
        {
            panel.SetStarUpData(starUpResp.PlayerId,(int)starUpResp.OldStar, (int)starUpResp.NewStar);
        });
    }

    // 处理角色升星响应
    private void HandlePlayerStarUpResp(BasePacket packet)
    {
        var starUpResp = PlayerStarUpResp.Parser.ParseFrom(packet.Body);
    }

    // 处理解锁角色响应
    private void HandleUnlockPlayerResp(BasePacket packet)
    {
        var unlockResp = UnlockPlayerResp.Parser.ParseFrom(packet.Body);
    }

    // 处理使用道具响应
    private void HandleUseItemResp(BasePacket packet)
    {
        var useItemResp = UseItemResp.Parser.ParseFrom(packet.Body);
        Debug.Log($"使用道具响应: 道具ID={useItemResp.ItemId}, 结果={useItemResp.Success}, " +
                  $"使用数量={useItemResp.UsedCount}, 剩余数量={useItemResp.RemainingCount}");
    }

    // 处理红点状态响应
    private void HandleRedDotResp(BasePacket packet)
    {
        var redDotResp = RedDotResp.Parser.ParseFrom(packet.Body);
        foreach (var redDot in redDotResp.RedDots)
        {
            Debug.Log($"红点状态: 系统ID={redDot.SystemId}, 状态={redDot.HasRedDot}");
        }
    }

    // 处理红点状态更新
    private void HandleRedDotUpdate(BasePacket packet)
    {
        var redDotUpdate = RedDotUpdate.Parser.ParseFrom(packet.Body);
        foreach (var redDot in redDotUpdate.RedDots)
        {
            Debug.Log($"红点更新: 系统ID={redDot.SystemId}, 状态={redDot.HasRedDot}");
        }
    }

    #endregion

    #region 消息发送函数

    /// <summary>
    /// 发送BasePacket消息到服务器
    /// </summary>
    /// <param name="packet">封装好的BasePacket消息</param>
    /// <param name="callback">响应回调(可选)</param>
    public void Send(BasePacket packet, Action<BasePacket> callback = null)
    {
        try
        {
            if (!isConnected || clientSocket == null || !clientSocket.Connected)
            {
                Debug.LogWarning("连接已断开,无法发送消息");
                return;
            }

            var header = packet.Header;
            var seq = header.Seq;
            
            // 注册回调
            if (callback != null && seq > 0)
            {
                lock (pendingLock)
                {
                    pendingRequests[seq] = callback;
                }
            }

            // 使用 protobuf 序列化消息
            var bodyData = packet.ToByteArray();
            var bodyLength = bodyData.Length;

            // 手动创建大端序的消息头
            byte[] headData = new byte[4];
            headData[0] = (byte)(bodyLength >> 24);
            headData[1] = (byte)(bodyLength >> 16);
            headData[2] = (byte)(bodyLength >> 8);
            headData[3] = (byte)bodyLength;

            // 合并消息头和消息体
            var totalData = new byte[headData.Length + bodyData.Length];
            
            Buffer.BlockCopy(headData, 0, totalData, 0, headData.Length);
            Buffer.BlockCopy(bodyData, 0, totalData, headData.Length, bodyData.Length);
            
            clientSocket.Send(totalData);

            Debug.Log($"发送消息成功,命令字: {header.Command}, 序列号: {seq}, 消息体长度: {bodyLength}");
        }
        catch (Exception exception)
        {
            Debug.LogError($"发送消息错误: {exception.Message}");
            
            // 清理待处理请求
            lock (pendingLock)
            {
                if (packet.Header.Seq > 0)
                {
                    pendingRequests.Remove(packet.Header.Seq);
                }
            }
            
            Disconnect();
        }
    }

    // 发送握手请求
    private void SendHandshakeRequest()
    {
        var handshakeReq = new HandshakeReq
        {
            ClientVersion = protocolVersion,
            Platform = Application.platform.ToString(),
            DeviceId = SystemInfo.deviceUniqueIdentifier
        };

        uint seq = GetNextSeq();
        var packet = CreateBasePacket((int)Command.HandshakeReq, seq, handshakeReq);
        Send(packet, HandleHandshakeResp);
    }

    // 发送心跳包
    private void SendHeartbeat()
    {
        if (!IsConnected())
        {
            Debug.LogWarning("连接已断开,跳过心跳发送");
            StopHeartbeat();
            return;
        }

        try
        {
            // 记录上次发送时间
            _lastHeartbeatSendTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
            var heartbeatReq = new HeartbeatReq
            {
                ClientTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
            };

            uint seq = GetNextSeq();
            var packet = CreateBasePacket((int)Command.HeartbeatReq, seq, heartbeatReq);
            Send(packet, HandleHeartbeatResp);
        }
        catch (Exception e)
        {
            Debug.LogError($"发送心跳包失败: {e.Message}");
            // 如果心跳发送失败,可能连接已断开
            Disconnect();
        }
    }

    // 发送玩家登录请求
    public void SendLoginRequest(string userId, string token, string secretUserId, long accountId, string deviceId)
    {
        var connectReq = new LoginReq()
        {
            UserId = userId,
            Token = token,
            Platform = Application.platform.ToString(),
            DeviceId = deviceId,
            SecretUserId = secretUserId,
            AccountId = accountId,
        };

        uint seq = GetNextSeq();
        var packet = CreateBasePacket((int)Command.LoginReq, seq, connectReq);
        Send(packet, HandleLoginResp);
    }

    // 获取玩家数据信息
    public void SendUserInfoReq(long accountId)
    {
        var userInfoReq = new UserInfoReq()
        {
            AccountId = accountId
        };
        
        uint seq = GetNextSeq();
        var packet = CreateBasePacket((int)Command.UserInfoReq, seq, userInfoReq);
        Send(packet);
    }
    
    // 发送切换关卡请求
    public void SendChangeLevelRequest(int targetLevelId, Action<BasePacket> callback = null)
    {
        var changeLevelReq = new ChangeLevelReq
        {
            TargetLevelId = targetLevelId
        };

        uint seq = GetNextSeq();
        var packet = CreateBasePacket((int)Command.ChangeLevelReq, seq, changeLevelReq);
        Send(packet, callback);
    }

    // 发送切换角色请求
    public void SendChangeCharacterRequest(long targetCharacterId, Action<BasePacket> callback = null)
    {
        var changeCharacterReq = new ChangePlayerReq()
        {
            TargetPlayerId = targetCharacterId
        };

        uint seq = GetNextSeq();
        var packet = CreateBasePacket((int)Command.ChangePlayerReq, seq, changeCharacterReq);
        Send(packet, callback);
    }

    // 发送通过关卡请求
    public void SendPassLevelRequest(int levelId, long playerId, int star, bool isPass,int score = 0, int timeUsed = 0, Action<BasePacket> callback = null)
    {
        var passLevelReq = new PassLevelReq
        {
            LevelId = levelId,
            PlayerId = playerId,
            PlayerStar = star,
            IsPass = isPass,
            Score = score,
            TimeUsed = timeUsed
        };

        Debug.Log($"发送消息:{passLevelReq}");
        uint seq = GetNextSeq();
        var packet = CreateBasePacket((int)Command.PassLevelReq, seq, passLevelReq);
        Send(packet, callback);
    }

    // 发送角色升星请求
    public void SendCharacterStarUpRequest(long playerId, Action<BasePacket> callback = null)
    {
        var starUpReq = new PlayerStarUpReq()
        {
            PlayerId = playerId
        };

        uint seq = GetNextSeq();
        var packet = CreateBasePacket((int)Command.PlayerStarUpReq, seq, starUpReq);
        Send(packet, callback);
    }

    // 发送解锁角色请求
    public void SendUnlockCharacterRequest(long characterId, Action<BasePacket> callback = null)
    {
        var unlockReq = new UnlockPlayerReq()
        {
            PlayerId = characterId
        };

        uint seq = GetNextSeq();
        var packet = CreateBasePacket((int)Command.UnlockPlayerReq, seq, unlockReq);
        Send(packet, callback);
    }

    // 发送使用道具请求
    public void SendUseItemRequest(int itemId, int count = 1, int targetId = 0, Action<BasePacket> callback = null)
    {
        var useItemReq = new UseItemReq
        {
            ItemId = itemId,
            Count = count,
            TargetId = targetId
        };

        uint seq = GetNextSeq();
        var packet = CreateBasePacket((int)Command.UseItemReq, seq, useItemReq);
        Send(packet, callback);
    }

    // 发送红点状态请求
    public void SendRedDotRequest(int[] systemIds = null)
    {
        var redDotReq = new RedDotReq();
        if (systemIds != null && systemIds.Length > 0)
        {
            redDotReq.SystemIds.AddRange(systemIds);
        }

        uint seq = GetNextSeq();
        var packet = CreateBasePacket((int)Command.RedDotReq, seq, redDotReq);
        Send(packet, HandleRedDotResp);
    }

    // 游戏退出
    public void SendLogoutRequest()
    {
        var logoutReq = new LogoutReq()
        {
            Reason = 1,
            UserId = GameManager.Instance.accountInfo.userId
        };
        
        uint seq = GetNextSeq();
        var packet = CreateBasePacket((int)Command.LogoutReq, seq, logoutReq);
        Send(packet);
    }
    
    #endregion

    #region 工具函数

    /// <summary>
    /// 创建BasePacket
    /// </summary>
    /// <param name="command">命令字</param>
    /// <param name="seq">序列号</param>
    /// <param name="businessMessage">业务消息</param>
    /// <param name="routeKey">路由键(可选)</param>
    /// <returns>BasePacket实例</returns>
    private BasePacket CreateBasePacket(int command, uint seq, IMessage businessMessage, string routeKey = "")
    {
        // 构造协议头
        var header = new PacketHeader
        {
            Command = (uint)command,
            Seq = seq,
            ErrorCode = 0,
            ErrorMsg = "",
            RouteKey = routeKey,
            Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
            Version = protocolVersion,
            Signature = ""  // 签名
        };

        // 构造BasePacket
        var packet = new BasePacket
        {
            Header = header,
            Body = ByteString.CopyFrom(businessMessage.ToByteArray())
        };

        return packet;
    }

    /// <summary>
    /// 获取下一个序列号
    /// </summary>
    private uint GetNextSeq()
    {
        uint seq = clientSeq;
        clientSeq++;
        
        // 防止溢出
        if (clientSeq == 0)
        {
            clientSeq = 1;
        }
        
        return seq;
    }

    // 启动心跳机制
    private void StartHeartbeat()
    {
        // 使用InvokeRepeating每30秒发送一次心跳
        InvokeRepeating(nameof(SendHeartbeat), heartbeatInterval, heartbeatInterval);
        Debug.Log($"心跳机制已启动,间隔: {heartbeatInterval}秒");
    }

    // 停止心跳机制
    private void StopHeartbeat()
    {
        CancelInvoke(nameof(SendHeartbeat));
        Debug.Log("心跳机制已停止");
    }

    /// <summary>
    /// 获取当前连接状态
    /// </summary>
    public bool IsConnected()
    {
        return isConnected && clientSocket != null && clientSocket.Connected;
    }

    #endregion
    
    #region HTTP请求功能
    
    // HTTP登录请求的数据结构
    [System.Serializable]
    private class LoginRequestData
    {
        public string token;
        public string userId;
        public string version;
        public string deviceId;
        public string platform;
    }
    
    // 服务器返回的统一结果包装类
    [System.Serializable]
    private class ServerResult<T>
    {
        public int code;
        public string message;
        public T data;
        public bool success;
    }

    // HTTP登录响应的具体数据类
    [System.Serializable]
    private class LoginResponseData
    {
        public long accountId;
        public string userId;
        public bool isNewUser;
        public string message;
    }
    
    /// <summary>
    /// 发送HTTP登录请求
    /// </summary>
    public void SendHttpLoginRequest(string token, string userId, string version = "0.01", string deviceId = "", string platform = "", Action<string> onSuccess = null, Action<string> onFailure = null)
    {
        // 构造登录URL
        string loginUrl = $"{httpServerUrl}/api/auth/login";
        
        // 创建登录请求数据
        var loginData = new LoginRequestData
        {
            token = token,
            deviceId = deviceId,
            version = version,
            platform = platform,
            userId = userId
        };
        
        // 将数据序列化为JSON
        string jsonData = JsonUtility.ToJson(loginData);
        
        // 使用 UniTask 异步发送 HTTP 请求
        SendHttpPostRequestAsync(loginUrl, jsonData, onSuccess, onFailure).Forget();
    }
    
    /// <summary>
    /// 发送通用的 HTTP POST 请求 - 使用 UniTask 异步方法
    /// </summary>
    /// <param name="url">请求 URL</param>
    /// <param name="jsonData">JSON 格式的请求体</param>
    /// <param name="onSuccess">成功回调</param>
    /// <param name="onFailure">失败回调</param>
    /// <param name="contentType">Content-Type,默认为 application/json</param>
    private async UniTask SendHttpPostRequestAsync(string url, string jsonData, Action<string> onSuccess, Action<string> onFailure, string contentType = "application/json")
    {
        Debug.Log($"发送 HTTP POST 请求到: {url}");
        Debug.Log($"请求数据: {jsonData}");
        
        try
        {
            // 创建 UnityWebRequest
            using (UnityWebRequest request = new UnityWebRequest(url, "POST"))
            {
                // 设置请求头
                request.SetRequestHeader("Content-Type", contentType);
                
                // 将 JSON 数据转换为字节数组
                byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonData);
                request.uploadHandler = new UploadHandlerRaw(bodyRaw);
                request.downloadHandler = new DownloadHandlerBuffer();
                
                // 设置超时时间
                request.timeout = 10;
                
                // 发送请求 - 使用 UniTask 异步等待
                await request.SendWebRequest().ToUniTask();
                
                // 处理响应
                if (request.result == UnityWebRequest.Result.Success)
                {
                    string responseText = request.downloadHandler.text;
                    Debug.Log($"HTTP 请求成功: {responseText}");
                    
                    // 解析响应
                    try
                    {
                        // 尝试解析为 JSON
                        var responseData = JsonUtility.FromJson<LoginResponseData>(responseText);
                        if (responseData != null)
                        {
                            onSuccess?.Invoke(responseText);
                        }
                        else
                        {
                            // 直接返回原始响应
                            Debug.LogWarning($"json 为空: {responseData}");
                        }
                    }
                    catch (Exception e)
                    {
                        // JSON 解析失败,返回原始响应
                        Debug.LogWarning($"JSON 解析失败: {e.Message}");
                        onSuccess?.Invoke(responseText);
                    }
                }
                else
                {
                    string errorMsg = $"HTTP 请求失败: {request.error}";
                    if (request.responseCode != 0)
                    {
                        errorMsg += $", 状态码: {request.responseCode}";
                    }
                    
                    Debug.LogError(errorMsg);
                    onFailure?.Invoke(errorMsg);
                }
            }
        }
        catch (Exception e)
        {
            string errorMsg = $"HTTP 请求异常: {e.Message}";
            Debug.LogError(errorMsg);
            onFailure?.Invoke(errorMsg);
        }
    }
    
    /// <summary>
    /// 发送 HTTP GET 请求
    /// </summary>
    /// <param name="url">请求 URL</param>
    /// <param name="onSuccess">成功回调</param>
    /// <param name="onFailure">失败回调</param>
    public void SendHttpGetRequest(string url, Action<string> onSuccess = null, Action<string> onFailure = null)
    {
        // 使用 UniTask 异步发送 HTTP GET 请求
        SendHttpGetRequestAsync(url, onSuccess, onFailure).Forget();
    }
    
    /// <summary>
    /// 发送 HTTP GET 请求 - 使用 UniTask 异步方法
    /// </summary>
    private async UniTask SendHttpGetRequestAsync(string url, Action<string> onSuccess, Action<string> onFailure)
    {
        Debug.Log($"发送 HTTP GET 请求到: {url}");
        
        try
        {
            using (UnityWebRequest request = UnityWebRequest.Get(url))
            {
                request.timeout = 10;
                
                // 发送请求 - 使用 UniTask 异步等待
                await request.SendWebRequest().ToUniTask();
                
                if (request.result == UnityWebRequest.Result.Success)
                {
                    string responseText = request.downloadHandler.text;
                    Debug.Log($"HTTP GET 请求成功: {responseText}");
                    onSuccess?.Invoke(responseText);
                }
                else
                {
                    string errorMsg = $"HTTP GET 请求失败: {request.error}";
                    if (request.responseCode != 0)
                    {
                        errorMsg += $", 状态码: {request.responseCode}";
                    }
                    
                    Debug.LogError(errorMsg);
                    onFailure?.Invoke(errorMsg);
                }
            }
        }
        catch (Exception e)
        {
            string errorMsg = $"HTTP GET 请求异常: {e.Message}";
            Debug.LogError(errorMsg);
            onFailure?.Invoke(errorMsg);
        }
    }
    
    #endregion
    
    #region 公共方法
    
    /// <summary>
    /// 开始 Socket 连接
    /// </summary>
    public void StartSocketConnection()
    {
        Connect();
    }
    
    /// <summary>
    /// 停止 Socket 连接
    /// </summary>
    public void StopSocketConnection()
    {
        Disconnect();
    }
    
    /// <summary>
    /// 使用 HTTP 登录后连接 Socket
    /// </summary>
    public void LoginAndConnect(string token, string userId, string version, string deviceId = "", string platform = "")
    {
        // 通过 HTTP 登录获取 token
        SendHttpLoginRequest(token, userId, version, deviceId, platform, 
            (response) => 
            {
                Debug.Log($"登录成功: {response}");
                
                // 解析响应获取 token
                try
                {
                    // 使用 ServerResult<LoginResponseData> 来解析嵌套的 JSON 结构
                    var serverResult = JsonUtility.FromJson<ServerResult<LoginResponseData>>(response);
                    
                    if (serverResult != null && serverResult.success && serverResult.data != null)
                    {
                        var loginResponse = serverResult.data;
                        Debug.Log($"获得服务器 http 返回消息: accountId:{loginResponse.accountId}, userId:{loginResponse.userId}, isNewUser:{loginResponse.isNewUser}");
                        
                        // 确保 GameManager.Instance.accountInfo 不为空
                        if (GameManager.Instance.accountInfo == null)
                        {
                            GameManager.Instance.accountInfo = new AccountInfo();
                        }
                        
                        // 设置账号信息
                        GameManager.Instance.accountInfo.accountId = loginResponse.accountId;
                        GameManager.Instance.accountInfo.userId = loginResponse.userId;
                        GameManager.Instance.accountInfo.isNewUser = loginResponse.isNewUser;
                        
                        // 从 SDKManager 获取其他信息
                        if (SDKManager.Instance != null && SDKManager.Instance.MiguPlayerData != null)
                        {
                            var miguData = SDKManager.Instance.MiguPlayerData;
                            GameManager.Instance.accountInfo.token = miguData.token;
                            GameManager.Instance.accountInfo.secretUserId = miguData.secretUserId;
                            GameManager.Instance.accountInfo.deviceId = miguData.deviceId;
                            GameManager.Instance.accountInfo.platform = platform;
                        }
                        else
                        {
                            Debug.LogWarning("SDKManager.Instance.MiguPlayerData 为空,使用传入的参数");
                            GameManager.Instance.accountInfo.token = token;
                            GameManager.Instance.accountInfo.deviceId = deviceId;
                            GameManager.Instance.accountInfo.platform = platform;
                        }
                        
                        StartSocketConnection();
                    }
                    else
                    {
                        Debug.LogError($"登录响应解析失败或返回错误: code={serverResult?.code}, message={serverResult?.message}");
                    }
                }
                catch (Exception e)
                {
                    Debug.LogError($"解析登录响应失败: {e.Message}");
                    Debug.LogError($"堆栈跟踪: {e.StackTrace}");
                }
            },
            (error) => 
            {
                Debug.LogError($"登录失败: {error}");
            });
    }
    
    #endregion
}

proto文件如下

bash 复制代码
syntax = "proto3";

package game.protocol;

option java_package = "game.protocol";  
option java_multiple_files = true;
option csharp_namespace = "Game.Protocol";

// ====== 基础消息结构 (base.proto) ======

// 错误码枚举
enum ErrorCode {
  SUCCESS = 0;                    // 成功

  // 系统级错误 1-999
  SYSTEM_ERROR = 1;               // 系统错误
  PROTOCOL_ERROR = 2;             // 协议错误
  SERVER_BUSY = 3;                // 服务器繁忙
  MAINTENANCE = 4;                // 服务器维护中

  // 连接相关错误 1000-1999
  CONNECTION_EXPIRED = 1001;      // 连接已过期
  TOO_MANY_CONNECTIONS = 1002;    // 连接数过多
  INVALID_SESSION = 1003;         // 无效会话

  // 认证相关错误 2000-2999
  AUTH_FAILED = 2001;             // 认证失败
  TOKEN_EXPIRED = 2002;           // token过期
  ACCOUNT_BANNED = 2003;          // 账号被封禁
  VERSION_TOO_OLD = 2004;         // 版本过旧

  // 业务逻辑错误 3000-3999
  INSUFFICIENT_RESOURCES = 3001;  // 资源不足
  OPERATION_TOO_FREQUENT = 3002;  // 操作过于频繁
  TARGET_NOT_FOUND = 3003;        // 目标不存在
  CONDITION_NOT_MET = 3004;       // 条件不满足
}

// 命令字枚举
enum Command {
	NONE = 0;

  // 系统消息 1-999
  HEARTBEAT_REQ = 1;      // 心跳请求
  HEARTBEAT_RESP = 2;     // 心跳响应
  HANDSHAKE_REQ = 3;      // 握手建立连接
  HANDSHAKE_RESP = 4;     // 握手响应

  // 认证消息 1000-1999
  LOGIN_REQ = 1001;       // 登录请求
  LOGIN_RESP = 1002;      // 登录响应
  LOGOUT_REQ = 1003;      // 登出请求
  LOGOUT_RESP = 1004;     // 登出响应

	// 玩家数据 2000-2999 请求
	USER_INFO_REQ = 2001;         // 玩家信息请求
	CHANGE_LEVEL_REQ = 2003;        // 切换关卡请求
	CHANGE_PLAYER_REQ = 2005;    // 切换角色请求
	PASS_LEVEL_REQ = 2007;          // 通过关卡请求
	PLAYER_STAR_UP_REQ = 2009;   // 角色升星请求
	UNLOCK_PLAYER_REQ = 2011;    // 解锁角色请求
	USE_ITEM_REQ = 2013;            // 使用道具请求

	// 玩家数据 2000-2999 响应
	USER_INFO_RESP = 2002;        // 玩家信息响应
	CHANGE_LEVEL_RESP = 2004;       // 切换关卡响应
	CHANGE_PLAYER_RESP = 2006;   // 切换角色响应
	PASS_LEVEL_RESP = 2008;         // 通过关卡响应
	PLAYER_STAR_UP_RESP = 2010;  // 角色升星响应
	UNLOCK_PLAYER_RESP = 2012;   // 解锁角色响应
	USE_ITEM_RESP = 2014;           // 使用道具响应

	PLAYER_LEVEL_UP_NOTIFY = 2015;  // 玩家升级通知(推送)
	CHARACTER_STAR_UP_NOTIFY = 2016;	// 角色升星推送
  

  // 红点系统 3000-3099
  RED_DOT_REQ = 3001;         // 红点状态请求
  RED_DOT_RESP = 3002;        // 红点状态响应
  RED_DOT_UPDATE = 3003;      // 红点状态更新(服务端推送)

  // 匹配对战 4000-4999
  MATCH_START_REQ = 4001;     // 开始匹配
  MATCH_START_RESP = 4002;    // 开始匹配响应
  MATCH_CANCEL_REQ = 4003;    // 取消匹配
  MATCH_CANCEL_RESP = 4004;   // 取消匹配响应
  MATCH_SUCCESS_NOTIFY = 4005; // 匹配成功通知

  // 战斗相关 5000-5999
  BATTLE_READY_REQ = 5001;    // 战斗准备
  BATTLE_ACTION_REQ = 5002;   // 战斗操作
  BATTLE_ACTION_RESP = 5003;  // 战斗操作响应
  BATTLE_FINISH_NOTIFY = 5004; // 战斗结束通知
}

// 通用协议头
message PacketHeader {
  uint32 command = 1;     // 指令号
  uint32 seq = 2;         // 序列号(请求-响应匹配)
  int32 error_code = 3;   // 错误码(0=成功)
  string error_msg = 4;   // 错误信息
  string route_key = 5;   // 路由键(用户ID等)
  int64 timestamp = 6;    // 时间戳
  string version = 7;     // 协议版本
  string signature = 8;   // 签名(防篡改)
}

// 基础数据包结构
message BasePacket {
  PacketHeader header = 1;
  bytes body = 2;  // 具体业务消息的序列化结果
}

// ====== 通用数据结构 ======
// 查询玩家请求
message UserInfoReq {
   int64 account_id = 1;  // 目标玩家ID
}

// 一个角色的详细信息
message PlayerMessage{
	int64 player_id = 1;			// 角色id
	PlayerStar player_star = 2;		// 角色星级
	int32 current_exp = 3;			// 角色当前经验值
	int32 next_star_max_exp = 4;	// 角色到下一个星级的总经验值 150/300
}

// 玩家基础信息
message UserInfoResp {
   int64 account_id = 1;           // 玩家ID
   string user_name = 2;         // 玩家名称
   int64 user_id = 3;           // 账号ID
   int32 rank_level = 4;           // 段位等级 预留
   int32 level = 5;                   // 玩家等级 预留
   //repeated int32 owned_character_ids = 6; // 玩家已解锁/拥有的角色ID列表
   //map<int64, PlayerStar> owned_character_dict = 6 ; // 玩家已解锁角色及其对应最高星级哈希表
   repeated PlayerMessage owned_character_list = 6;	// 玩家已解锁角色对应详细信息链表
   int32 avatar_frame_id = 7;      // 当前头像框ID(对应account表的avatar_frame_id)
   int64 last_login_time = 8;     // 最后登录时间戳(对应account表的last_login_time)
   int32 gold = 9;                  // 玩家金币
   int32 last_chosen_level_id = 10;		// 上次选择的地图的id
   int32 max_level_id = 11; 		// 最高通过关卡id
}

// 红点信息
message RedDotInfo {
  int32 system_id = 1;            // 系统ID
  bool has_red_dot = 2;           // 是否有红点
}

// ====== 系统消息模块 (system.proto) ======

// 心跳请求
message HeartbeatReq {
  int64 client_time = 1;          // 客户端时间戳
}

// 心跳响应
message HeartbeatResp {
  int64 client_time = 1;  // 客户端时间
  int64 server_time = 2;  // 服务器时间
}

// 握手请求
message HandshakeReq {
  string client_version = 1;      // 客户端版本
  string platform = 2;            // 平台(ios/android/pc)
  string device_id = 3;           // 设备ID
}

// 握手响应
message HandshakeResp {
  string server_version = 1;      // 服务器版本
  int64 server_time = 2;          // 服务器时间
  string session_key = 3;         // 会话密钥
}

// ====== 认证消息模块 (auth.proto) ======

// 登录请求
message LoginReq {
  string user_id = 1;         // 账号
  string token = 2;           // 登录令牌
  string platform = 3;        // 平台
  string device_id = 4;       // 设备ID
  string secret_user_id = 5;  // 用户ID_secrect
  int64 account_id = 6;      // 用户id
}

// 登录响应
message LoginResp {
  uint64 account_id = 1;      // 账号ID
  uint64 user_id = 2;         // 用户ID
  string nickname = 3;        // 昵称
  int32 level = 4;            // 等级
  int64 login_time = 5;       // 登录时间
  repeated RedDotInfo red_dots = 6; // 红点信息
}

// 登出请求
message LogoutReq {
  string user_id = 1;
  int32 reason = 2;  // 登出原因 1=主动退出 2=异地登录
}

// 登出响应
message LogoutResp {
  string user_id = 1;
  int64 logout_time = 2;
}

// ====== 玩家数据模块 (player.proto) ======

// 切换关卡请求
message ChangeLevelReq {
  int32 target_level_id = 1;      // 目标关卡ID
}

// 切换关卡响应
message ChangeLevelResp {
  int32 max_level_id = 1;         // 玩家最高解锁的关卡
  int32 current_level_id = 2;     // 当前选择的关卡
  bool can_play = 3;              // 能否游玩该关卡
}

// 切换角色请求
message ChangePlayerReq {
   int64 target_player_id = 1;  // 目标角色ID
}

// 切换角色响应
message ChangePlayerResp {
   int64 current_player_id = 1; // 当前选择的角色ID
   bool is_unlocked = 2;           // 是否已解锁
   PlayerStar current_player_star = 3;	// 如果已解锁,当前选择角色星级
   map<int64, PlayerStar> unlocked_players = 4; // 已解锁的所有角色
}

// 通过关卡请求
message PassLevelReq {
  int32 level_id = 1;             // 通过的关卡ID
  int64 player_id = 2;             // 当前关卡使用的角色id
  int32 player_star = 3;		// 当前关卡使用的角色星级
  bool is_pass = 4;			// 是否成功通关
  int32 score = 5;                // 得分(可选)
  int32 time_used = 6;            // 用时(秒,可选)
}

// 通过关卡响应
message PassLevelResp {
  int32 level_id = 1;             // 通过的关卡ID
  int32 coins_earned = 2;         // s获得的金币
  int32 exp_earned = 3;           // 获得的经验
  bool is_new_record = 4;         // 是否新记录
  int32 new_max_level_id = 5;     // 新的最高解锁关卡
  int32 current_exp = 6;		// 当前最新经验值
  int32 next_star_max_exp = 7;	// 当前星级最大经验值
}

// 玩家升级通知(服务端推送)
message PlayerLevelUpNotify {
  int32 old_level = 1;            // 原等级
  int32 new_level = 2;            // 新等级
  int32 current_exp = 3;          // 当前经验值
  int32 current_coins = 4;        // 当前金币
  repeated int32 unlocked_systems = 5; // 解锁的新系统
}

// 角色升星通知(服务端推送)
message PlayerRoleStarUpNotify {
  int64 player_id = 1;          // 角色ID
  PlayerStar old_star = 2;         // 原星级
  PlayerStar new_star = 3;         // 新星级
  int32 current_exp = 4;      // 当前经验值
  int32 exp_cost = 5;         // 升星消耗的经验值
}

// 角色星级枚举
enum PlayerStar {
   STAR_0 = 0;     // 无星
   STAR_1 = 1;     // 一星
   STAR_2 = 2;     // 二星
   STAR_3 = 3;     // 三星
   STAR_4 = 4;     // 四星
   STAR_5 = 5;     // 五星
}

// 角色升星请求
message PlayerStarUpReq {
   int64 player_id = 1;         // 角色ID
}

// 角色升星响应
message PlayerStarUpResp {
   int64 player_id = 1;         // 角色ID
   PlayerStar old_star = 2;     // 原星级
   PlayerStar new_star = 3;     // 新星级
   int32 coins_cost = 4;           // 消耗的金币
   int32 exp_cost = 5;             // 消耗的经验
   int32 remaining_coins = 6;      // 剩余金币
   int32 remaining_exp = 7;        // 剩余经验
}

// 解锁角色请求
message UnlockPlayerReq {
   int64 player_id = 1;         // 角色ID
}

// 解锁角色响应
message UnlockPlayerResp {
   int64 player_id = 1;         // 角色ID
   bool success = 2;               // 是否成功
   int32 coins_cost = 3;           // 消耗的金币
   int32 remaining_coins = 4;      // 剩余金币
   PlayerStar initial_star = 5; // 初始星级
   int32 next_star_max_exp = 6;	// 下一星级所需经验
}

// 道具信息
message ItemInfo {
  int32 item_id = 1;              // 道具ID
  string item_name = 2;           // 道具名称
  int32 count = 3;                // 数量
  int32 effect_type = 4;          // 效果类型
  int32 effect_value = 5;         // 效果值
  int64 expire_time = 6;          // 过期时间
}

// 使用道具请求
message UseItemReq {
  int32 item_id = 1;              // 道具ID
  int32 count = 2;                // 使用数量
  int32 target_id = 3;            // 目标ID(如角色ID,可选)
}

// 使用道具响应
message UseItemResp {
  int32 item_id = 1;              // 道具ID
  bool success = 2;               // 是否成功
  int32 used_count = 3;           // 使用的数量
  int32 remaining_count = 4;      // 剩余数量
  string effect_description = 5;  // 效果描述
  int64 effect_end_time = 6;      // 效果结束时间
}

// ====== 红点系统模块 (reddot.proto) ======

// 红点状态请求
message RedDotReq {
  repeated int32 system_ids = 1;  // 请求的系统ID列表(空表示请求所有)
}

// 红点状态响应
message RedDotResp {
  repeated RedDotInfo red_dots = 1; // 红点状态列表
}

// 红点状态更新(服务端推送)
message RedDotUpdate {
  repeated RedDotInfo red_dots = 1; // 需要更新的红点状态
}
相关推荐
leo__5201 小时前
单载波中继系统资源分配算法MATLAB仿真程序
算法·matlab·unity
努力长头发的程序猿3 小时前
Unity使用ScriptableObject序列化资源
unity·游戏引擎
mxwin3 小时前
Unity Shader 手写基于 PBR 的 URP Lit Shader 核心光照计算
unity·游戏引擎·shader
小贺儿开发3 小时前
Unity3D 智能云端数字标牌系统
unity·阿里云·人机交互·视频·oss·广告·互动
魔士于安4 小时前
Unity windows 同步 异步 打开文件文件夹工具
游戏·unity·游戏引擎·贴图·模型
笑虾4 小时前
cocos2d-x lua 加载 Cocos Studio 导出的 csb
游戏引擎·lua·cocos2d
魔士于安4 小时前
unity lowpoly 风格 城市 建筑 道路 交通标志
游戏·unity·游戏引擎·贴图·模型
mxwin4 小时前
Unity GPU Shader 性能优化指南
unity·游戏引擎·shader
董董女友16 小时前
unity mcp 配置指南
unity·游戏引擎
垂葛酒肝汤21 小时前
Unity的可视化网格和文字标签
unity·游戏引擎