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; // 需要更新的红点状态
}
相关推荐
作孽就得先起床2 小时前
unity webGL导出.glb模型
unity·c#·游戏引擎·webgl
心前阳光5 小时前
Unity发布运行在PICO4的安卓程序
android·unity·游戏引擎
孟无岐5 小时前
【Laya】Scene3D 介绍
typescript·游戏引擎·游戏程序·laya
sunfove5 小时前
Python制作小游戏:用线性代数思想构建 2048 游戏引擎
python·线性代数·游戏引擎
孟无岐5 小时前
【Laya】Sprite3D 介绍
typescript·游戏引擎·游戏程序·laya
Howrun7775 小时前
虚幻引擎_创建组件
游戏引擎·虚幻
Jessica巨人5 小时前
pico相关插件介绍
unity
leo__5205 小时前
基于模糊控制的MATLAB避障算法实现
算法·matlab·unity
WinstonJQ1 天前
AirSim无人机仿真入门(一):实现无人机的起飞与降落
python·机器人·游戏引擎·ue4·无人机