yyl
2026-02-05 a29fffa93dc66ff45589f8fd1f8584f7bcb9fdad
h5 tcp switch to websocket
5个文件已修改
32个文件已添加
2693 ■■■■■ 已修改文件
Main/Component/UI/Effect/PostEffectsBase.cs 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/NetworkPackage/ClientSocketAdapter.cs 186 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/NetworkPackage/ClientSocketAdapter.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/NetworkPackage/GameNetSystem.cs 162 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/NetworkPackage/INetworkService.cs 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/NetworkPackage/INetworkService.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/NetworkPackage/Network.meta 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/NetworkPackage/WebSocketNetworkService.cs 466 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/NetworkPackage/WebSocketNetworkService.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/Platform.meta 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/Platform/AdManager.cs 198 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/Platform/AdManager.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/Platform/DeviceProfile.cs 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/Platform/DeviceProfile.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/Platform/DouyinPlatform.cs 193 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/Platform/DouyinPlatform.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/Platform/IPlatformService.cs 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/Platform/IPlatformService.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/Platform/PlatformConfig.cs 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/Platform/PlatformConfig.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/Platform/PlatformData.cs 147 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/Platform/PlatformData.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/Platform/PlatformFactory.cs 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/Platform/PlatformFactory.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/Platform/PlatformType.cs 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/Platform/PlatformType.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/Platform/SafeAreaAdapter.cs 154 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/Platform/SafeAreaAdapter.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/Platform/StandalonePlatform.cs 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/Platform/StandalonePlatform.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/Platform/VivoPlatform.cs 193 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/Platform/VivoPlatform.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/Platform/WeChatPlatform.cs 256 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/Platform/WeChatPlatform.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Main.asmdef 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Main.cs 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Utility/DeviceUtility.cs 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Component/UI/Effect/PostEffectsBase.cs
@@ -109,8 +109,9 @@
        protected bool CheckSupport (bool needDepth)
        {
            isSupported = true;
            supportHDRTextures = SystemInfo.SupportsRenderTextureFormat(RenderTextureFormat.ARGBHalf);
            supportDX11 = SystemInfo.graphicsShaderLevel >= 50 && SystemInfo.supportsComputeShaders;
            // Unity 2022+ - Modern devices support HDR and compute shaders
            supportHDRTextures = true;
            supportDX11 = true;
   //         if (!SystemInfo.supportsImageEffects)
            //{
@@ -118,10 +119,10 @@
   //             return false;
   //         }
            if (needDepth && !SystemInfo.SupportsRenderTextureFormat (RenderTextureFormat.Depth))
            if (needDepth)
            {
                NotSupported ();
                return false;
                // Modern Unity versions support depth textures by default
                GetComponent<Camera>().depthTextureMode |= DepthTextureMode.Depth;
            }
            if (needDepth)
Main/Core/NetworkPackage/ClientSocketAdapter.cs
New file
@@ -0,0 +1,186 @@
using Cysharp.Threading.Tasks;
using System;
using UnityEngine;
/// <summary>
/// ClientSocket 适配器 - 将 ClientSocket 适配为 INetworkService 接口
/// (不修改已验证的 ClientSocket 代码)
/// </summary>
public class ClientSocketAdapter : INetworkService
{
    private ClientSocket clientSocket;
    private NetworkState _state = NetworkState.Disconnected;
    public NetworkState State
    {
        get => _state;
        private set
        {
            if (_state != value)
            {
                _state = value;
                OnStateChanged?.Invoke(value);
            }
        }
    }
    /// <summary>
    /// 是否已连接(适配 ClientSocket.connected)
    /// </summary>
    public bool IsConnected => clientSocket != null && clientSocket.connected;
    /// <summary>
    /// 最后收包时间(适配 ClientSocket.lastPackageTime)
    /// </summary>
    public DateTime LastPackageTime => clientSocket?.lastPackageTime ?? DateTime.MinValue;
    public event Action<NetworkState> OnStateChanged;
    /// <summary>
    /// 构造函数
    /// </summary>
    public ClientSocketAdapter(ServerType type)
    {
        clientSocket = new ClientSocket(type);
        Debug.Log($"[ClientSocketAdapter] 初始化 TCP Socket({type})");
    }
    /// <summary>
    /// 连接到 TCP 服务器(适配 ClientSocket.Connect)
    /// </summary>
    public async UniTask<bool> ConnectAsync(string url)
    {
        try
        {
            State = NetworkState.Connecting;
            // 解析 URL(支持 "ip:port" 或 "tcp://ip:port")
            string ip;
            int port;
            ParseUrl(url, out ip, out port);
            Debug.Log($"[ClientSocketAdapter] 连接 {ip}:{port}");
            // 使用 UniTask 包装回调
            var tcs = new UniTaskCompletionSource<bool>();
            clientSocket.Connect(ip, port, (success) =>
            {
                if (success)
                {
                    State = NetworkState.Connected;
                    Debug.Log("[ClientSocketAdapter] 连接成功");
                }
                else
                {
                    State = NetworkState.Disconnected;
                    Debug.LogError("[ClientSocketAdapter] 连接失败");
                }
                tcs.TrySetResult(success);
            });
            return await tcs.Task;
        }
        catch (Exception ex)
        {
            Debug.LogError($"[ClientSocketAdapter] 连接异常: {ex.Message}");
            State = NetworkState.Disconnected;
            return false;
        }
    }
    /// <summary>
    /// 断开连接(异步)
    /// </summary>
    public async UniTask DisconnectAsync()
    {
        if (clientSocket != null)
        {
            clientSocket.CloseConnect();
            State = NetworkState.Disconnected;
        }
        await UniTask.CompletedTask;
    }
    /// <summary>
    /// 断开连接(同步,适配 ClientSocket.CloseConnect)
    /// </summary>
    public void Disconnect()
    {
        if (clientSocket != null)
        {
            clientSocket.CloseConnect();
            State = NetworkState.Disconnected;
        }
    }
    /// <summary>
    /// 发送协议包(适配 ClientSocket.SendInfo)
    /// </summary>
    public void SendInfo(GameNetPackBasic protocol)
    {
        if (!IsConnected)
        {
            Debug.LogError("[ClientSocketAdapter] 未连接,无法发送协议包");
            return;
        }
        clientSocket.SendInfo(protocol);
    }
    /// <summary>
    /// 发送二进制数据(适配 ClientSocket.SendInfo)
    /// </summary>
    public void SendInfo(byte[] data)
    {
        if (!IsConnected)
        {
            Debug.LogError("[ClientSocketAdapter] 未连接,无法发送数据");
            return;
        }
        clientSocket.SendInfo(data);
    }
    /// <summary>
    /// 发送二进制数据(异步)
    /// </summary>
    public async UniTask SendAsync(byte[] data)
    {
        SendInfo(data);
        await UniTask.CompletedTask;
    }
    /// <summary>
    /// 获取内部的 ClientSocket(兼容性访问)
    /// </summary>
    public ClientSocket GetClientSocket()
    {
        return clientSocket;
    }
    /// <summary>
    /// 解析 URL
    /// </summary>
    private void ParseUrl(string url, out string ip, out int port)
    {
        // 默认端口
        port = 8080;
        // 移除协议前缀
        if (url.StartsWith("tcp://"))
        {
            url = url.Substring(6);
        }
        // 分割 IP 和端口
        var parts = url.Split(':');
        ip = parts[0];
        if (parts.Length > 1 && int.TryParse(parts[1], out int parsedPort))
        {
            port = parsedPort;
        }
    }
}
Main/Core/NetworkPackage/ClientSocketAdapter.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 529c690109d07de41aa851e793469e7d
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/Core/NetworkPackage/GameNetSystem.cs
@@ -2,14 +2,24 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
public enum NetworkType
{
    TCP,        // 使用原有的 TCP Socket
    WebSocket   // 使用 WebSocket(小游戏平台)
}
public class GameNetSystem : Singleton<GameNetSystem>
{
    //限制客户端的下一个包是登录包C0101_tagCPlayerLogin,如果不是登录包不允许发送
    bool waitLogin = false; //等待发送登录包,如果有其他包直接屏蔽,避免断线重连的情况发了攻击包之类的
    //等待服务端0403的包后才能发其他的功能包,只有C0123_tagCClientPackVersion 和 C0101_tagCPlayerLogin 可以发送  
    bool waitLoginMap = false;
    bool waitLoginMap = false;
    // 网络服务接口(统一支持 TCP 和 WebSocket)
    private INetworkService networkService;
    NetUpdateBehaviour m_NetUpdateBehaviour;
    NeverConnectState neverConnectState;
    AccountLoginState accountLoginState;
@@ -74,12 +84,26 @@
        }
    }
    private ClientSocket mainSocket;
    public bool mainSocketConnected { get { return mainSocket == null ? false : mainSocket.connected; } }
    private ClientSocket mainSocket;  // 保留兼容,但不再直接使用
    public bool mainSocketConnected
    {
        get { return networkService?.IsConnected ?? false; }
    }
    public float timeSinceMainSocketLastProtocol
    {
        get { return mainSocket == null ? Time.time : (float)(DateTime.Now - mainSocket.lastPackageTime).TotalSeconds; }
        get
        {
            if (networkService == null)
                return Time.time;
            var lastTime = networkService.LastPackageTime;
            if (lastTime == DateTime.MinValue)
                return Time.time; // 从未收包
            return (float)(DateTime.Now - lastTime).TotalSeconds;
        }
    }
@@ -107,9 +131,9 @@
    {
        try
        {
            if (mainSocketConnected)
            if (networkService != null && networkService.IsConnected)
            {
                mainSocket.CloseConnect();
                networkService.Disconnect();
            }
        }
        catch (System.Exception ex)
@@ -117,16 +141,98 @@
            Debug.Log(ex);
        }
        mainSocket = new ClientSocket(ServerType.Main);
        mainProtocolQueue.Clear();
        mainSocket.Connect(ip, port, (bool ok) =>
        // 根据 Main.CurrentNetworkType 选择网络类型
        Debug.Log($"[GameNetSystem] 连接服务器 {ip}:{port},使用{(Main.CurrentNetworkType == NetworkType.WebSocket ? "WebSocket" : "TCP")}");
        if (Main.CurrentNetworkType == NetworkType.WebSocket)
        {
            // 使用 WebSocket
            ConnectWithWebSocket(ip, port, onConnected);
        }
        else
        {
            // 使用 TCP Socket(通过适配器)
            ConnectWithTCP(ip, port, onConnected);
        }
    }
    private async void ConnectWithTCP(string ip, int port, Action<bool> onConnected)
    {
        try
        {
            // 创建 TCP Socket 适配器(不修改 ClientSocket)
            networkService = new ClientSocketAdapter(ServerType.Main);
            // 订阅状态事件
            networkService.OnStateChanged += OnNetworkStateChanged;
            // 连接
            string url = $"{ip}:{port}";
            Debug.Log($"[GameNetSystem] TCP 开始连接: {url}");
            bool success = await networkService.ConnectAsync(url);
            Debug.Log($"[GameNetSystem] TCP 连接{(success ? "成功" : "失败")}: {url}");
            // 兼容:保留 mainSocket 引用(但通过 networkService 访问)
            mainSocket = (networkService as ClientSocketAdapter)?.GetClientSocket();
            if (onConnected != null)
            {
                onConnected(ok);
                onConnected(success);
            }
        });
        }
        catch (System.Exception ex)
        {
            Debug.LogError($"[GameNetSystem] TCP 连接异常: {ex.Message}");
            if (onConnected != null)
            {
                onConnected(false);
            }
        }
    }
    private async void ConnectWithWebSocket(string ip, int port, Action<bool> onConnected)
    {
        try
        {
            // 创建 WebSocket 服务(传入 ServerType,与 ClientSocket 一致)
            networkService = new WebSocketNetworkService(ServerType.Main);
            // 订阅状态事件
            networkService.OnStateChanged += OnNetworkStateChanged;
            string url = $"ws://{ip}:{port}";
            Debug.Log($"[GameNetSystem] WebSocket 开始连接: {url}");
            bool success = await networkService.ConnectAsync(url);
            Debug.Log($"[GameNetSystem] WebSocket 连接{(success ? "成功" : "失败")}: {url}");
            if (onConnected != null)
            {
                onConnected(success);
            }
        }
        catch (System.Exception ex)
        {
            Debug.LogError($"[GameNetSystem] WebSocket 连接异常: {ex.Message}");
            if (onConnected != null)
            {
                onConnected(false);
            }
        }
    }
    private void OnNetworkStateChanged(NetworkState state)
    {
        Debug.Log($"[GameNetSystem] 网络状态变化: {state}");
        // 断开连接时触发断线处理
        if (state == NetworkState.Disconnected || state == NetworkState.Closed)
        {
            netState = NetState.DisConnected;
        }
    }
    //限制客户端的下一个包是登录包C0101_tagCPlayerLogin,如果不是登录包不允许发送
@@ -152,18 +258,17 @@
    public void SendCachePackage()
    {
        int cnt = sendQueue.Count;
        if (mainSocket != null)
        while (sendQueue.Count > 0)
        {
            while (sendQueue.Count > 0)
            {
                SendInfo(sendQueue.Dequeue());
            }
            SendInfo(sendQueue.Dequeue());
        }
        Debug.LogError($"重点提醒:0403登录后 发送缓存包数量 {cnt} 个");
        Debug.Log($"重点提醒:0403登录后 发送缓存包数量 {cnt} 个");
    }
    public void SendInfo(GameNetPackBasic protocol)
    {
        Debug.LogError("protocol to send: " + protocol.ToString());
        if (waitLogin)
        {
            if (protocol is not C0101_tagCPlayerLogin)
@@ -184,18 +289,18 @@
            }
        }
        if (mainSocket != null)
        if (networkService != null)
        {
            mainSocket.SendInfo(protocol);
            networkService.SendInfo(protocol);
            DebugPkgCache.Push(protocol);
        }
    }
    public void SendInfo(byte[] vBytes)
    {
        if (mainSocket != null)
        if (networkService != null)
        {
            mainSocket.SendInfo(vBytes);
            networkService.SendInfo(vBytes);
        }
    }
@@ -226,9 +331,9 @@
    {
        try
        {
            if (mainSocket != null)
            if (networkService != null)
            {
                mainSocket.CloseConnect();
                networkService.Disconnect();
            }
            mainProtocolQueue.Clear();
@@ -250,9 +355,9 @@
    {
        try
        {
            if (mainSocket != null)
            if (networkService != null)
            {
                mainSocket.CloseConnect();
                networkService.Disconnect();
            }
            mainProtocolQueue.Clear();
@@ -275,9 +380,9 @@
        {
            SDKUtils.Instance.RoleLoginOut();
            if (mainSocket != null)
            if (networkService != null)
            {
                mainSocket.CloseConnect();
                networkService.Disconnect();
            }
            mainProtocolQueue.Clear();
@@ -309,6 +414,11 @@
    void OnUpdate()
    {
        if (networkService != null)
        {
            if (networkService is WebSocketNetworkService wsService) { wsService.Update(); }
        }
        lock (this)
        {
            while (mainProtocolQueue.Count > 0)
Main/Core/NetworkPackage/INetworkService.cs
New file
@@ -0,0 +1,71 @@
using Cysharp.Threading.Tasks;
using System;
/// <summary>
/// 网络服务接口 - 统一封装 Socket 和 WebSocket(适配 ClientSocket API)
/// </summary>
public interface INetworkService
{
    /// <summary>
    /// 网络连接状态
    /// </summary>
    NetworkState State { get; }
    /// <summary>
    /// 是否已连接(适配 ClientSocket.connected)
    /// </summary>
    bool IsConnected { get; }
    /// <summary>
    /// 最后收包时间(适配 ClientSocket.lastPackageTime)
    /// </summary>
    System.DateTime LastPackageTime { get; }
    /// <summary>
    /// 连接到服务器
    /// </summary>
    /// <param name="url">服务器地址(ws:// 或 wss:// 对于 WebSocket)</param>
    UniTask<bool> ConnectAsync(string url);
    /// <summary>
    /// 断开连接(异步)
    /// </summary>
    UniTask DisconnectAsync();
    /// <summary>
    /// 断开连接(同步,适配 ClientSocket.CloseConnect)
    /// </summary>
    void Disconnect();
    /// <summary>
    /// 发送协议包(适配 ClientSocket.SendInfo)
    /// </summary>
    void SendInfo(GameNetPackBasic protocol);
    /// <summary>
    /// 发送二进制数据(适配 ClientSocket.SendInfo)
    /// </summary>
    void SendInfo(byte[] data);
    /// <summary>
    /// 发送二进制数据(异步)
    /// </summary>
    UniTask SendAsync(byte[] data);
    /// <summary>
    /// 连接状态改变事件
    /// </summary>
    event Action<NetworkState> OnStateChanged;
}
/// <summary>
/// 网络连接状态
/// </summary>
public enum NetworkState
{
    Disconnected,   // 未连接
    Connecting,     // 连接中
    Connected,      // 已连接
    Reconnecting,   // 重连中
    Closed          // 已关闭
}
Main/Core/NetworkPackage/INetworkService.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 74d34d5ef1dcb5e4bae375bc3dd8cd41
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/Core/NetworkPackage/Network.meta
New file
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 683353faee5b8104da00dafdc791bbed
folderAsset: yes
DefaultImporter:
  externalObjects: {}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/Core/NetworkPackage/WebSocketNetworkService.cs
New file
@@ -0,0 +1,466 @@
using Cysharp.Threading.Tasks;
using NativeWebSocket;
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
/// <summary>
/// WebSocket 网络服务 - 用于小游戏平台
/// </summary>
public class WebSocketNetworkService : INetworkService
{
    private WebSocket webSocket;
    private NetworkState _state = NetworkState.Disconnected;
    private string currentUrl;
    // 统计信息(与 ClientSocket 对齐)
    private long getBytesTotal = 0;   // 接收的数据总量
    private long sendBytesTotal = 0;  // 发送的数据总量
    private DateTime m_LastPackageTime = DateTime.MinValue;
    /// <summary>
    /// 最后收包时间(实现接口)
    /// </summary>
    public DateTime LastPackageTime { get { return m_LastPackageTime; } }
    // 发送队列(与 ClientSocket 对齐)
    private Queue<byte[]> sendQueue = new Queue<byte[]>();
    private bool isSending = false;
    // 编解码器(与 ClientSocket 对齐)
    private GameNetEncode encoder = new GameNetEncode();
    // 服务器类型(与 ClientSocket 对齐)
    private ServerType socketType = ServerType.Main;
    /// <summary>
    /// 构造函数(与 ClientSocket 对齐)
    /// </summary>
    public WebSocketNetworkService(ServerType type)
    {
        this.socketType = type;
    }
    public NetworkState State
    {
        get => _state;
        private set
        {
            if (_state != value)
            {
                _state = value;
                OnStateChanged?.Invoke(value);
            }
        }
    }
    public bool IsConnected => State == NetworkState.Connected;
    public event Action<NetworkState> OnStateChanged;
    /// <summary>
    /// 连接到 WebSocket 服务器
    /// </summary>
    public async UniTask<bool> ConnectAsync(string url)
    {
        if (IsConnected)
        {
            Debug.LogWarning($"[WebSocket] 已经连接到 {currentUrl}");
            return true;
        }
        try
        {
            State = NetworkState.Connecting;
            currentUrl = url;
            Debug.Log($"[WebSocket] 开始连接: {url}");
            // 创建 WebSocket 连接(不传 headers,使用默认行为)
            webSocket = new WebSocket(url);
            // 注册事件监听
            webSocket.OnOpen += OnWebSocketOpen;
            webSocket.OnMessage += OnWebSocketMessage;
            webSocket.OnError += OnWebSocketError;
            webSocket.OnClose += OnWebSocketClose;
            // 开始连接
            Debug.Log($"[WebSocket] 调用 Connect()...");
            // 启动连接任务(不等待它完成,因为它会一直运行到连接关闭)
            var connectTask = webSocket.Connect();
            // 等待 OnOpen 触发(最多5秒)
            var timeout = DateTime.Now.AddSeconds(5);
            while (State != NetworkState.Connected && DateTime.Now < timeout)
            {
                await UniTask.Yield();
            }
            Debug.Log($"[WebSocket] 等待结束,当前状态: {State}, WebSocket.State: {webSocket.State}");
            if (IsConnected)
            {
                Debug.Log("[WebSocket] ✓ 连接成功");
                return true;
            }
            else
            {
                Debug.LogError($"[WebSocket] ✗ 连接超时或失败,State={State}, WebSocket.State={webSocket?.State}");
                return false;
            }
        }
        catch (Exception ex)
        {
            Debug.LogError($"[WebSocket] 连接异常: {ex.Message}\n{ex.StackTrace}");
            return false;
        }
    }
    /// <summary>
    /// 断开连接(异步)
    /// </summary>
    public async UniTask DisconnectAsync()
    {
        Debug.LogWarning($"[WebSocket] DisconnectAsync 被调用! State={State}");
        Debug.LogWarning($"[WebSocket] 调用堆栈:\n{System.Environment.StackTrace}");
        if (webSocket != null)
        {
            // 清空发送队列
            lock (sendQueue)
            {
                sendQueue.Clear();
                isSending = false;
            }
            await webSocket.Close();
            webSocket = null;
        }
        State = NetworkState.Disconnected;
    }
    /// <summary>
    /// 断开连接(同步,适配 ClientSocket.CloseConnect)
    /// </summary>
    public void Disconnect()
    {
        Debug.LogWarning($"[WebSocket] Disconnect 被调用! State={State}");
        Debug.LogWarning($"[WebSocket] 调用堆栈:\n{System.Environment.StackTrace}");
        DisconnectAsync().Forget();
    }
    /// <summary>
    /// 发送协议包(与 ClientSocket.SendInfo 完全对齐)
    /// </summary>
    public void SendInfo(GameNetPackBasic protocol)
    {
        if (!IsConnected)
        {
            Debug.LogError("[WebSocket] 未连接,无法发送协议包");
            return;
        }
        if (protocol == null)
        {
            Debug.LogError("[WebSocket] 要发的信息对象为空");
            return;
        }
        try
        {
            // 1. 准备协议数据
            if (protocol.combineBytes == null)
            {
                protocol.WriteToBytes();
            }
            // 2. 组合数据(与 ClientSocket.SendInfo 一致)
            protocol.CombineDatas(encoder);
#if UNITY_EDITOR
            // 3. 记录包信息到 NetPackageWindow(与 ClientSocket 对齐)
            NetPkgCtl.RecordPackage(socketType, protocol.vInfoCont, NetPackagetType.Client,
                protocol.ToString(), FieldPrint.PrintFields(protocol), FieldPrint.PrintFieldsExpand(protocol, true));
#endif
            byte[] data = protocol.dataBytes;
            // 4. 加密
            byte[] encryptedData = encoder.BaseXorAdd(data);
            // 5. 添加包头(6字节:0xFF 0xCC + 4字节长度小端序,与 ClientSocket 一致)
            int bodyLength = encryptedData.Length;
            byte[] sendData = new byte[bodyLength + 6];
            sendData[0] = 0xFF;  // 与 ClientSocket 一致
            sendData[1] = 0xCC;  // 与 ClientSocket 一致
            // 使用 BitConverter 小端序(与 ClientSocket 一致)
            byte[] lengthBytes = BitConverter.GetBytes(bodyLength);
            Array.Copy(lengthBytes, 0, sendData, 2, 4);
            Array.Copy(encryptedData, 0, sendData, 6, bodyLength);
            // 6. 更新统计(与 ClientSocket 对齐)
            sendBytesTotal += sendData.Length;
            // 7. 发送(异步)
            SendAsync(sendData).Forget();
        }
        catch (Exception ex)
        {
            Debug.LogError($"[WebSocket] 发送协议包异常: {ex.Message}\n{ex.StackTrace}");
        }
    }
    /// <summary>
    /// 发送二进制数据(同步重载,适配 ClientSocket.SendInfo)
    /// </summary>
    public void SendInfo(byte[] data)
    {
        SendAsync(data).Forget();
    }
    /// <summary>
    /// 发送二进制数据(带队列机制,与 ClientSocket 对齐)
    /// </summary>
    public async UniTask SendAsync(byte[] data)
    {
        if (!IsConnected)
        {
            Debug.LogError("[WebSocket] 未连接,无法发送数据");
            return;
        }
        if (data == null || data.Length == 0)
        {
            Debug.LogError("[WebSocket] 发送数据为空");
            return;
        }
        // 检查消息大小(最大 64KB)
        if (data.Length > 65536)
        {
            Debug.LogError($"[WebSocket] 消息过大: {data.Length} bytes(最大 64KB)");
            return;
        }
        // 队列机制:如果正在发送,加入队列
        lock (sendQueue)
        {
            if (isSending)
            {
                sendQueue.Enqueue(data);
                Debug.Log($"[WebSocket] 数据加入发送队列,队列长度: {sendQueue.Count}");
                return;
            }
            else
            {
                isSending = true;
            }
        }
        // 发送数据
        await SendBytesInternal(data);
    }
    /// <summary>
    /// 内部发送方法(处理队列)
    /// </summary>
    private async UniTask SendBytesInternal(byte[] data)
    {
        try
        {
            await webSocket.Send(data);
            sendBytesTotal += data.Length;
            Debug.Log($"[WebSocket] 发送成功: {data.Length} bytes,总发送: {sendBytesTotal} bytes");
            // 处理队列中的下一个消息
            byte[] nextData = null;
            lock (sendQueue)
            {
                if (sendQueue.Count > 0)
                {
                    nextData = sendQueue.Dequeue();
                }
                else
                {
                    isSending = false;
                }
            }
            if (nextData != null)
            {
                await SendBytesInternal(nextData);
            }
        }
        catch (Exception ex)
        {
            Debug.LogError($"[WebSocket] 发送失败: {ex.Message}");
            lock (sendQueue)
            {
                isSending = false;
            }
        }
    }
    /// <summary>
    /// WebSocket 连接成功回调
    /// </summary>
    private void OnWebSocketOpen()
    {
        Debug.Log($"[WebSocket] ✓✓✓ OnOpen 事件触发 - 连接成功: {currentUrl}");
        Debug.Log($"[WebSocket] WebSocket.State: {webSocket?.State}");
        State = NetworkState.Connected;
        Debug.Log($"[WebSocket] 当前状态已更新为: {State}");
    }
    /// <summary>
    /// WebSocket 消息接收回调(与 ClientSocket.ReadInfo 对齐)
    /// </summary>
    private void OnWebSocketMessage(byte[] data)
    {
        // 完整的包处理流程(与 ClientSocket.ReadInfo 对齐)
        try
        {
            if (data == null || data.Length < 6)
            {
                Debug.LogError($"[WebSocket] 收到无效数据包: {data?.Length ?? 0} bytes");
                return;
            }
            // 更新统计信息
            getBytesTotal += data.Length;
            Debug.Log($"[WebSocket] 收到原始消息: {data.Length} bytes");
            // 检查包头(0xFF 0xCC,与 ClientSocket 一致)
            if (data[0] != 0xFF || data[1] != 0xCC)
            {
                Debug.LogError($"[WebSocket] 包头错误: 0x{data[0]:X2} 0x{data[1]:X2},期望 0xFF 0xCC");
                return;
            }
            // 读取包体长度(小端序,与 ClientSocket 一致)
            int bodyLength = BitConverter.ToInt32(data, 2);
            if (data.Length < bodyLength + 6)
            {
                Debug.LogError($"[WebSocket] 包长度不匹配: 声明 {bodyLength + 6}, 实际 {data.Length}");
                return;
            }
            // 提取加密的包体(跳过6字节包头)
            byte[] encryptedBody = new byte[bodyLength];
            Array.Copy(data, 6, encryptedBody, 0, bodyLength);
            // 解密包体(与 ClientSocket.ReadInfo 一致)
            byte[] decryptedData = encoder.BaseXorSub(encryptedBody);
            // 解析 CMD(前2字节)
            byte[] cmdBytes = new byte[2];
            Array.Copy(decryptedData, 0, cmdBytes, 0, 2);
            var cmd = (ushort)((ushort)(cmdBytes[0] << 8) + cmdBytes[1]);
            Debug.Log($"[WebSocket] 解密成功,CMD=0x{cmd:X4}");
            bool isRegist = false;
            // 转换协议包(与 ClientSocket.ReadInfo 一致)
            if (PackageRegedit.Contain(cmd))
            {
                GameNetPackBasic pack = PackageRegedit.TransPack(socketType, cmd, decryptedData);
                if (pack != null)
                {
                    Debug.Log($"[WebSocket] 收到协议: {pack.GetType().Name}");
                    m_LastPackageTime = DateTime.Now;
                    // 直接推送到 GameNetSystem(与 ClientSocket 一致)
                    GameNetSystem.Instance.PushPackage(pack, socketType);
                    isRegist = true;
                }
            }
            // 未注册封包处理(与 ClientSocket 一致)
            if (!isRegist)
            {
#if UNITY_EDITOR
                PackageRegedit.TransPack(socketType, cmd, decryptedData);
#endif
            }
        }
        catch (Exception ex)
        {
            Debug.LogError($"[WebSocket] 消息处理异常: {ex.Message}\n{ex.StackTrace}");
        }
    }
    /// <summary>
    /// WebSocket 错误回调
    /// </summary>
    private void OnWebSocketError(string error)
    {
        Debug.LogError($"[WebSocket] ✗ OnError 事件触发: {error}");
        Debug.LogError($"[WebSocket] 当前状态: {State}, WebSocket.State: {webSocket?.State}");
    }
    /// <summary>
    /// WebSocket 关闭回调(不自动重连,与 ClientSocket 一致)
    /// </summary>
    private void OnWebSocketClose(WebSocketCloseCode code)
    {
        Debug.LogError($"[WebSocket] ⊗ OnClose 事件触发!");
        Debug.LogError($"[WebSocket] 关闭代码: {code} ({(int)code})");
        Debug.LogError($"[WebSocket] 之前状态: {State}\n");
        // 判断是谁关闭的
        if (code == WebSocketCloseCode.Normal)
        {
            Debug.LogWarning("[WebSocket] → 正常关闭(客户端主动)");
        }
        else if (code == WebSocketCloseCode.Abnormal)
        {
            Debug.LogError("[WebSocket] → 异常关闭(连接丢失或服务器断开)");
        }
        else
        {
            Debug.LogError($"[WebSocket] → 其他原因关闭: {code}");
        }
        State = NetworkState.Disconnected;
        // 注意:不自动重连,与 ClientSocket 一致,由 GameNetSystem 控制重连逻辑
        Debug.Log("[WebSocket] 连接已断开,等待 GameNetSystem 处理");
    }
    /// <summary>
    /// 获取接收统计信息
    /// </summary>
    public long GetBytesTotal() => getBytesTotal;
    /// <summary>
    /// 获取发送统计信息
    /// </summary>
    public long GetSendBytesTotal() => sendBytesTotal;
    /// <summary>
    /// 获取发送队列长度
    /// </summary>
    public int GetSendQueueCount()
    {
        lock (sendQueue)
        {
            return sendQueue.Count;
        }
    }
    /// <summary>
    /// 每帧处理 WebSocket 消息(必须在 MonoBehaviour 的 Update 中调用)
    /// </summary>
    public void Update()
    {
#if !UNITY_WEBGL || UNITY_EDITOR
        webSocket?.DispatchMessageQueue();
#endif
    }
}
Main/Core/NetworkPackage/WebSocketNetworkService.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 963e73ef1640ad24ca0948e6cb9d2198
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/Core/Platform.meta
New file
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 49b4d42a5d166244e87a2ea12de035ca
folderAsset: yes
DefaultImporter:
  externalObjects: {}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/Core/Platform/AdManager.cs
New file
@@ -0,0 +1,198 @@
using System;
using Cysharp.Threading.Tasks;
using UnityEngine;
/// <summary>
/// 广告管理器 - 统一封装各平台广告调用
/// </summary>
public class AdManager : MonoBehaviour
{
    private static AdManager _instance;
    public static AdManager Instance
    {
        get
        {
            if (_instance == null)
            {
                var go = new GameObject("[AdManager]");
                _instance = go.AddComponent<AdManager>();
                DontDestroyOnLoad(go);
            }
            return _instance;
        }
    }
    [Header("广告配置")]
    [Tooltip("视频广告冷却时间(秒)")]
    public float videoAdCooldown = 30f;
    [Tooltip("是否启用广告")]
    public bool enableAds = true;
    // 广告事件
    public Action<AdResult> OnVideoAdCompleted;
    public Action<string> OnAdError;
    // 内部状态
    private IPlatformService _platform;
    private float _lastVideoAdTime = 0f;
    private bool _isShowingAd = false;
    private void Awake()
    {
        if (_instance != null && _instance != this)
        {
            Destroy(gameObject);
            return;
        }
        _instance = this;
        DontDestroyOnLoad(gameObject);
    }
    /// <summary>
    /// 初始化广告管理器
    /// </summary>
    public void Initialize(IPlatformService platform)
    {
        _platform = platform;
        Debug.Log("[AdManager] 广告管理器初始化完成");
    }
    /// <summary>
    /// 显示激励视频广告
    /// </summary>
    /// <param name="onSuccess">观看完成回调</param>
    /// <param name="onFail">失败回调</param>
    public async UniTask<bool> ShowRewardedVideoAd(Action onSuccess = null, Action onFail = null)
    {
        if (!enableAds)
        {
            Debug.LogWarning("[AdManager] 广告已禁用");
            onFail?.Invoke();
            return false;
        }
        if (_platform == null)
        {
            Debug.LogError("[AdManager] 平台服务未初始化");
            onFail?.Invoke();
            return false;
        }
        if (_isShowingAd)
        {
            Debug.LogWarning("[AdManager] 已有广告正在播放");
            onFail?.Invoke();
            return false;
        }
        // 检查冷却时间
        float timeSinceLastAd = Time.time - _lastVideoAdTime;
        if (timeSinceLastAd < videoAdCooldown)
        {
            float remainingTime = videoAdCooldown - timeSinceLastAd;
            Debug.LogWarning($"[AdManager] 广告冷却中,剩余 {remainingTime:F0} 秒");
            OnAdError?.Invoke($"广告冷却中,请等待 {remainingTime:F0} 秒");
            onFail?.Invoke();
            return false;
        }
        _isShowingAd = true;
        try
        {
            Debug.Log("[AdManager] 开始播放激励视频广告");
            AdResult result = await _platform.ShowAdAsync(AdType.Video);
            if (result.Success && result.Completed)
            {
                Debug.Log("[AdManager] 用户观看完广告");
                _lastVideoAdTime = Time.time;
                OnVideoAdCompleted?.Invoke(result);
                onSuccess?.Invoke();
                return true;
            }
            else
            {
                Debug.LogWarning($"[AdManager] 广告播放失败: {result.ErrorMessage}");
                OnAdError?.Invoke(result.ErrorMessage);
                onFail?.Invoke();
                return false;
            }
        }
        catch (Exception e)
        {
            Debug.LogError($"[AdManager] 广告播放异常: {e.Message}");
            OnAdError?.Invoke(e.Message);
            onFail?.Invoke();
            return false;
        }
        finally
        {
            _isShowingAd = false;
        }
    }
    /// <summary>
    /// 显示横幅广告
    /// </summary>
    public async UniTask<bool> ShowBannerAd()
    {
        if (!enableAds || _platform == null)
            return false;
        try
        {
            AdResult result = await _platform.ShowAdAsync(AdType.Banner);
            return result.Success;
        }
        catch (Exception e)
        {
            Debug.LogError($"[AdManager] 横幅广告显示失败: {e.Message}");
            return false;
        }
    }
    /// <summary>
    /// 显示插屏广告
    /// </summary>
    public async UniTask<bool> ShowInterstitialAd()
    {
        if (!enableAds || _platform == null)
            return false;
        try
        {
            AdResult result = await _platform.ShowAdAsync(AdType.Interstitial);
            return result.Success;
        }
        catch (Exception e)
        {
            Debug.LogError($"[AdManager] 插屏广告显示失败: {e.Message}");
            return false;
        }
    }
    /// <summary>
    /// 检查是否可以播放广告
    /// </summary>
    public bool CanShowAd()
    {
        if (!enableAds || _platform == null || _isShowingAd)
            return false;
        float timeSinceLastAd = Time.time - _lastVideoAdTime;
        return timeSinceLastAd >= videoAdCooldown;
    }
    /// <summary>
    /// 获取广告冷却剩余时间
    /// </summary>
    public float GetCooldownRemaining()
    {
        float timeSinceLastAd = Time.time - _lastVideoAdTime;
        float remaining = videoAdCooldown - timeSinceLastAd;
        return Mathf.Max(0f, remaining);
    }
}
Main/Core/Platform/AdManager.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c7a55e0786384324ab07a78c25054280
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/Core/Platform/DeviceProfile.cs
New file
@@ -0,0 +1,71 @@
using System;
using UnityEngine;
/// <summary>
/// 性能档次枚举
/// </summary>
public enum PerformanceLevel
{
        /// <summary>低端设备(内存 < 2GB,2019年前机型)</summary>
        Low,
        /// <summary>中端设备(内存 2-4GB,2019-2021年机型)</summary>
        Medium,
        /// <summary>高端设备(内存 > 4GB,2021年后旗舰)</summary>
        High
}
/// <summary>
/// 设备性能档次和屏幕信息
/// </summary>
[Serializable]
public class DeviceProfile
{
        /// <summary>性能档次</summary>
        public PerformanceLevel PerformanceLevel = PerformanceLevel.Medium;
        /// <summary>总内存大小(MB)</summary>
        public long TotalMemory;
        /// <summary>屏幕宽度(像素)</summary>
        public int ScreenWidth;
        /// <summary>屏幕高度(像素)</summary>
        public int ScreenHeight;
        /// <summary>安全区域</summary>
        public Rect SafeArea;
        /// <summary>屏幕DPI</summary>
        public float DPI;
        /// <summary>设备型号</summary>
        public string DeviceModel;
        /// <summary>
        /// 从Unity SystemInfo检测设备配置
        /// </summary>
        public static DeviceProfile DetectFromSystem()
        {
            var profile = new DeviceProfile
            {
                TotalMemory = UnityEngine.SystemInfo.systemMemorySize,
                ScreenWidth = Screen.width,
                ScreenHeight = Screen.height,
                SafeArea = Screen.safeArea,
                DPI = Screen.dpi,
                DeviceModel = UnityEngine.SystemInfo.deviceModel
            };
            // 根据内存判定性能档次
            if (profile.TotalMemory < 2048)
                profile.PerformanceLevel = PerformanceLevel.Low;
            else if (profile.TotalMemory < 4096)
                profile.PerformanceLevel = PerformanceLevel.Medium;
            else
                profile.PerformanceLevel = PerformanceLevel.High;
            return profile;
        }
    }
Main/Core/Platform/DeviceProfile.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 27f18f1ecea04c3418b4cdc4192a62df
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/Core/Platform/DouyinPlatform.cs
New file
@@ -0,0 +1,193 @@
using System;
using System.Runtime.InteropServices;
using Cysharp.Threading.Tasks;
using UnityEngine;
/// <summary>
/// 抖音小游戏平台实现
/// </summary>
public class DouyinPlatform : IPlatformService
{
    #if UNITY_WEBGL && !UNITY_EDITOR
    // JavaScript 插件方法声明
    [DllImport("__Internal")]
    private static extern bool TT_Init();
    [DllImport("__Internal")]
    private static extern void TT_Login(string callbackObjectName, string callbackMethodName);
    [DllImport("__Internal")]
    private static extern string TT_GetSystemInfo();
    [DllImport("__Internal")]
    private static extern void TT_ShareAppMessage(string title, string imageUrl);
    [DllImport("__Internal")]
    private static extern void TT_CreateRewardedVideoAd(string adUnitId, string callbackObjectName, string callbackMethodName);
    [DllImport("__Internal")]
    private static extern void TT_SetStorageSync(string key, string value);
    [DllImport("__Internal")]
    private static extern string TT_GetStorageSync(string key);
    [DllImport("__Internal")]
    private static extern void TT_VibrateShort();
    [DllImport("__Internal")]
    private static extern void TT_VibrateLong();
    #endif
    private SystemInfo _cachedSystemInfo;
    private bool _isInitialized = false;
    public async UniTask<bool> InitAsync()
    {
        Debug.Log("[DouyinPlatform] 初始化抖音小游戏平台");
        #if UNITY_WEBGL && !UNITY_EDITOR
        try
        {
            _isInitialized = TT_Init();
            if (_isInitialized)
            {
                _cachedSystemInfo = GetSystemInfo();
                Debug.Log("[DouyinPlatform] 初始化成功");
            }
            return _isInitialized;
        }
        catch (Exception e)
        {
            Debug.LogError($"[DouyinPlatform] 初始化失败: {e.Message}");
            return false;
        }
        #else
        await UniTask.Delay(100);
        _isInitialized = true;
        _cachedSystemInfo = CreateMockSystemInfo();
        return true;
        #endif
    }
    public PlatformType GetPlatformType()
    {
        return PlatformType.Douyin;
    }
    public async UniTask<LoginResult> LoginAsync()
    {
        Debug.Log("[DouyinPlatform] 开始登录");
        #if UNITY_WEBGL && !UNITY_EDITOR
        // TODO: 实现抖音登录逻辑
        await UniTask.Delay(500);
        return new LoginResult { Success = true, UserId = "douyin_user" };
        #else
        await UniTask.Delay(500);
        return new LoginResult { Success = true, UserId = "douyin_test_user" };
        #endif
    }
    public async UniTask<bool> ShareAsync(ShareData shareData)
    {
        Debug.Log($"[DouyinPlatform] 分享: {shareData.Title}");
        #if UNITY_WEBGL && !UNITY_EDITOR
        TT_ShareAppMessage(shareData.Title, shareData.ImageUrl);
        #endif
        await UniTask.Delay(300);
        return true;
    }
    public async UniTask<AdResult> ShowAdAsync(AdType adType)
    {
        Debug.Log($"[DouyinPlatform] 显示广告: {adType}");
        #if UNITY_WEBGL && !UNITY_EDITOR
        // TODO: 实现抖音广告逻辑
        #endif
        await UniTask.Delay(1000);
        return new AdResult { Success = true, Completed = true };
    }
    public async UniTask<bool> SaveDataAsync(string key, string value)
    {
        #if UNITY_WEBGL && !UNITY_EDITOR
        TT_SetStorageSync(key, value);
        #endif
        await UniTask.Yield();
        return true;
    }
    public async UniTask<string> LoadDataAsync(string key)
    {
        #if UNITY_WEBGL && !UNITY_EDITOR
        string value = TT_GetStorageSync(key);
        await UniTask.Yield();
        return value;
        #else
        await UniTask.Yield();
        return null;
        #endif
    }
    public async UniTask<string> DownloadFileAsync(string url, string localPath, Action<float> onProgress = null)
    {
        Debug.Log($"[DouyinPlatform] 下载文件: {url}");
        await UniTask.Delay(500);
        return localPath;
    }
    public SystemInfo GetSystemInfo()
    {
        if (_cachedSystemInfo != null)
            return _cachedSystemInfo;
        #if UNITY_WEBGL && !UNITY_EDITOR
        try
        {
            string jsonInfo = TT_GetSystemInfo();
            _cachedSystemInfo = JsonUtility.FromJson<SystemInfo>(jsonInfo);
            return _cachedSystemInfo;
        }
        catch (Exception e)
        {
            Debug.LogError($"[DouyinPlatform] 获取系统信息失败: {e.Message}");
            return CreateMockSystemInfo();
        }
        #else
        return CreateMockSystemInfo();
        #endif
    }
    public void Vibrate(VibrationType vibrationType)
    {
        #if UNITY_WEBGL && !UNITY_EDITOR
        switch (vibrationType)
        {
            case VibrationType.Light:
            case VibrationType.Medium:
                TT_VibrateShort();
                break;
            case VibrationType.Heavy:
                TT_VibrateLong();
                break;
        }
        #endif
    }
    private SystemInfo CreateMockSystemInfo()
    {
        return new SystemInfo
        {
            DeviceModel = UnityEngine.SystemInfo.deviceModel,
            PlatformVersion = "Douyin 1.0.0",
            ScreenWidth = Screen.width,
            ScreenHeight = Screen.height,
            SafeArea = SafeAreaData.FromRect(Screen.safeArea),
            PixelRatio = Screen.dpi / 160f
        };
    }
}
Main/Core/Platform/DouyinPlatform.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9ec9f394fc27b9d4b99eb0e84792b12d
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/Core/Platform/IPlatformService.cs
New file
@@ -0,0 +1,75 @@
using System;
using Cysharp.Threading.Tasks;
/// <summary>
/// 平台服务接口,封装各小游戏平台的 API 差异
/// </summary>
public interface IPlatformService
{
        /// <summary>
        /// 初始化平台 SDK
        /// </summary>
        /// <returns>初始化是否成功</returns>
        UniTask<bool> InitAsync();
        /// <summary>
        /// 获取平台类型
        /// </summary>
        PlatformType GetPlatformType();
        /// <summary>
        /// 登录(获取用户授权)
        /// </summary>
        /// <returns>登录结果</returns>
        UniTask<LoginResult> LoginAsync();
        /// <summary>
        /// 分享内容
        /// </summary>
        /// <param name="shareData">分享数据</param>
        /// <returns>分享是否成功</returns>
        UniTask<bool> ShareAsync(ShareData shareData);
        /// <summary>
        /// 显示广告
        /// </summary>
        /// <param name="adType">广告类型</param>
        /// <returns>广告结果</returns>
        UniTask<AdResult> ShowAdAsync(AdType adType);
        /// <summary>
        /// 保存数据到本地存储
        /// </summary>
        /// <param name="key">键</param>
        /// <param name="value">值(JSON 字符串)</param>
        /// <returns>保存是否成功</returns>
        UniTask<bool> SaveDataAsync(string key, string value);
        /// <summary>
        /// 从本地存储加载数据
        /// </summary>
        /// <param name="key">键</param>
        /// <returns>值(JSON 字符串),如果不存在返回 null</returns>
        UniTask<string> LoadDataAsync(string key);
        /// <summary>
        /// 下载文件到本地
        /// </summary>
        /// <param name="url">远程 URL</param>
        /// <param name="localPath">本地路径</param>
        /// <param name="onProgress">进度回调</param>
        /// <returns>本地文件路径,失败返回 null</returns>
        UniTask<string> DownloadFileAsync(string url, string localPath, Action<float> onProgress = null);
        /// <summary>
        /// 获取系统信息
        /// </summary>
        /// <returns>系统信息</returns>
        SystemInfo GetSystemInfo();
        /// <summary>
        /// 震动反馈
        /// </summary>
    /// <param name="vibrationType">震动类型</param>
    void Vibrate(VibrationType vibrationType);
}
Main/Core/Platform/IPlatformService.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 46aa75e3d41e74a4387775b45e1704b1
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/Core/Platform/PlatformConfig.cs
New file
@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
/// <summary>
/// 平台配置类
/// </summary>
[Serializable]
public class PlatformConfig
{
        /// <summary>平台类型</summary>
        public PlatformType PlatformType;
        /// <summary>首包大小限制(MB)</summary>
        public int FirstPackageSizeLimit = 4;
        /// <summary>单个分包大小限制(MB)</summary>
        public int SubPackageSizeLimit = 20;
        /// <summary>总资源限制(MB)</summary>
        public int TotalResourceLimit = 50;
        /// <summary>支持的特性列表</summary>
        public List<string> SupportedFeatures = new List<string>();
        /// <summary>
        /// 获取微信平台默认配置
        /// </summary>
        public static PlatformConfig GetWeChatConfig()
        {
            return new PlatformConfig
            {
                PlatformType = PlatformType.WeChat,
                FirstPackageSizeLimit = 4,
                SubPackageSizeLimit = 20,
                TotalResourceLimit = 50,
                SupportedFeatures = new List<string> { "OpenDataContext", "Ad", "Share", "SaveData" }
            };
        }
        /// <summary>
        /// 获取抖音平台默认配置
        /// </summary>
        public static PlatformConfig GetDouyinConfig()
        {
            return new PlatformConfig
            {
                PlatformType = PlatformType.Douyin,
                FirstPackageSizeLimit = 4,
                SubPackageSizeLimit = 20,
                TotalResourceLimit = 50,
                SupportedFeatures = new List<string> { "Ad", "Share", "SaveData" }
            };
        }
        /// <summary>
        /// 获取vivo平台默认配置
        /// </summary>
        public static PlatformConfig GetVivoConfig()
        {
            return new PlatformConfig
            {
                PlatformType = PlatformType.Vivo,
                FirstPackageSizeLimit = 10,
                SubPackageSizeLimit = int.MaxValue,
                TotalResourceLimit = 100,
                SupportedFeatures = new List<string> { "SaveData" }
            };
        }
        /// <summary>
        /// 获取编辑器测试配置
        /// </summary>
        public static PlatformConfig GetStandaloneConfig()
        {
            return new PlatformConfig
            {
                PlatformType = PlatformType.Standalone,
                FirstPackageSizeLimit = int.MaxValue,
                SubPackageSizeLimit = int.MaxValue,
                TotalResourceLimit = int.MaxValue,
                SupportedFeatures = new List<string> { "OpenDataContext", "Ad", "Share", "SaveData" }
            };
        }
    }
Main/Core/Platform/PlatformConfig.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 825296e797695694a9758bb01fd0f8df
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/Core/Platform/PlatformData.cs
New file
@@ -0,0 +1,147 @@
using System;
using UnityEngine;
/// <summary>
/// 广告类型枚举
/// </summary>
public enum AdType
{
        /// <summary>视频广告(激励视频)</summary>
        Video,
        /// <summary>横幅广告</summary>
        Banner,
        /// <summary>插屏广告</summary>
        Interstitial
}
/// <summary>
/// 震动类型枚举
/// </summary>
public enum VibrationType
{
        /// <summary>轻量震动(15ms)</summary>
        Light,
        /// <summary>中等震动(30ms)</summary>
        Medium,
        /// <summary>重量震动(50ms)</summary>
        Heavy
}
/// <summary>
/// 登录结果
/// </summary>
[Serializable]
public class LoginResult
{
        /// <summary>登录是否成功</summary>
        public bool Success;
        /// <summary>用户ID(平台唯一标识)</summary>
        public string UserId;
        /// <summary>用户昵称</summary>
        public string Nickname;
        /// <summary>用户头像URL</summary>
        public string AvatarUrl;
        /// <summary>错误消息(如果失败)</summary>
        public string ErrorMessage;
}
/// <summary>
/// 分享数据
/// </summary>
[Serializable]
public class ShareData
{
        /// <summary>分享标题</summary>
        public string Title;
        /// <summary>分享描述</summary>
        public string Description;
        /// <summary>分享图片URL</summary>
        public string ImageUrl;
        /// <summary>分享页面路径(可选)</summary>
        public string PagePath;
}
/// <summary>
/// 广告结果
/// </summary>
[Serializable]
public class AdResult
{
        /// <summary>广告是否成功展示</summary>
        public bool Success;
        /// <summary>用户是否看完广告(针对激励视频)</summary>
        public bool Completed;
        /// <summary>错误消息(如果失败)</summary>
        public string ErrorMessage;
}
/// <summary>
/// 系统信息
/// </summary>
[Serializable]
public class SystemInfo
{
        /// <summary>设备型号</summary>
        public string DeviceModel;
        /// <summary>系统版本</summary>
        public string SystemVersion;
        /// <summary>平台版本(如微信版本)</summary>
        public string PlatformVersion;
        /// <summary>屏幕宽度(像素)</summary>
        public int ScreenWidth;
        /// <summary>屏幕高度(像素)</summary>
        public int ScreenHeight;
        /// <summary>安全区域</summary>
        public SafeAreaData SafeArea;
        /// <summary>设备像素比</summary>
        public float PixelRatio;
}
/// <summary>
/// 安全区域数据
/// </summary>
[Serializable]
public class SafeAreaData
{
        public float Left;
        public float Top;
        public float Right;
        public float Bottom;
        public float Width;
        public float Height;
        /// <summary>
        /// 从Unity Screen.safeArea创建
        /// </summary>
        public static SafeAreaData FromRect(Rect safeArea)
        {
            return new SafeAreaData
            {
                Left = safeArea.x,
                Top = safeArea.y,
                Right = safeArea.x + safeArea.width,
                Bottom = safeArea.y + safeArea.height,
                Width = safeArea.width,
                Height = safeArea.height
            };
        }
    }
Main/Core/Platform/PlatformData.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e35f891c3088a09449c8504068b9412b
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/Core/Platform/PlatformFactory.cs
New file
@@ -0,0 +1,85 @@
using UnityEngine;
/// <summary>
/// 平台工厂类,用于创建对应平台的实现
/// </summary>
public static class PlatformFactory
{
        private static IPlatformService _currentPlatform;
        private static PlatformType? _forcedPlatformType;
        /// <summary>
        /// 获取当前平台实例
        /// </summary>
        public static IPlatformService GetCurrent()
        {
            if (_currentPlatform == null)
            {
                _currentPlatform = CreatePlatform();
            }
            return _currentPlatform;
        }
        /// <summary>
        /// 强制指定平台类型(用于测试)
        /// </summary>
        public static void ForcePlatform(PlatformType platformType)
        {
            _forcedPlatformType = platformType;
            _currentPlatform = null; // 重置当前平台
        }
        /// <summary>
        /// 创建平台实例
        /// </summary>
        private static IPlatformService CreatePlatform()
        {
            PlatformType platformType = DetectPlatformType();
            switch (platformType)
            {
                case PlatformType.WeChat:
                    Debug.Log("[PlatformFactory] 创建微信小游戏平台实例");
                    return new WeChatPlatform();
                case PlatformType.Douyin:
                    Debug.Log("[PlatformFactory] 创建抖音小游戏平台实例");
                    return new DouyinPlatform();
                case PlatformType.Vivo:
                    Debug.Log("[PlatformFactory] 创建vivo小游戏平台实例");
                    return new VivoPlatform();
                case PlatformType.Standalone:
                default:
                    Debug.Log("[PlatformFactory] 创建编辑器测试平台实例");
                    return new StandalonePlatform();
            }
        }
        /// <summary>
        /// 检测当前平台类型
        /// </summary>
        private static PlatformType DetectPlatformType()
        {
            // 如果强制指定了平台类型,使用强制类型
            if (_forcedPlatformType.HasValue)
            {
                Debug.Log($"[PlatformFactory] 使用强制指定的平台类型: {_forcedPlatformType.Value}");
                return _forcedPlatformType.Value;
            }
            #if UNITY_EDITOR
            // Unity 编辑器环境
            return PlatformType.Standalone;
            #elif UNITY_WEBGL
            // WebGL 平台,需要通过 JavaScript 检测具体平台
            // TODO: 通过 Application.ExternalEval 检测微信、抖音等平台
            // 暂时默认返回微信
            return PlatformType.WeChat;
            #else
            // 其他平台默认为 Standalone
            return PlatformType.Standalone;
            #endif
        }
    }
Main/Core/Platform/PlatformFactory.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ecd57f320592c7b4cb442c34af4dc38c
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/Core/Platform/PlatformType.cs
New file
@@ -0,0 +1,23 @@
/// <summary>
/// 平台类型枚举
/// </summary>
public enum PlatformType
{
    /// <summary>微信小游戏</summary>
    WeChat,
    /// <summary>抖音小游戏</summary>
    Douyin,
    /// <summary>vivo小游戏</summary>
    Vivo,
    /// <summary>OPPO小游戏</summary>
    OPPO,
    /// <summary>快手小游戏</summary>
    Kuaishou,
    /// <summary>编辑器测试模式</summary>
    Standalone
}
Main/Core/Platform/PlatformType.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7af8ceb1c332b66438af6bc120883b48
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/Core/Platform/SafeAreaAdapter.cs
New file
@@ -0,0 +1,154 @@
using UnityEngine;
/// <summary>
/// 安全区域适配组件 - 自动适配刘海屏和异形屏
/// </summary>
[RequireComponent(typeof(RectTransform))]
[ExecuteInEditMode]
public class SafeAreaAdapter : MonoBehaviour
{
    [Header("适配设置")]
    [Tooltip("是否在编辑器模式下模拟安全区域")]
    public bool simulateInEditor = false;
    [Tooltip("适配边缘(Top, Bottom, Left, Right)")]
    public bool adaptTop = true;
    public bool adaptBottom = true;
    public bool adaptLeft = true;
    public bool adaptRight = true;
    [Header("调试信息")]
    [SerializeField] private Rect currentSafeArea;
    [SerializeField] private Vector2Int screenSize;
    private RectTransform _rectTransform;
    private Rect _lastSafeArea = Rect.zero;
    private Vector2Int _lastScreenSize = Vector2Int.zero;
    private void Awake()
    {
        _rectTransform = GetComponent<RectTransform>();
        ApplySafeArea();
    }
    private void Update()
    {
        // 检测屏幕变化或安全区域变化
        if (HasScreenChanged())
        {
            ApplySafeArea();
        }
    }
    /// <summary>
    /// 检测屏幕是否发生变化
    /// </summary>
    private bool HasScreenChanged()
    {
        Rect safeArea = GetSafeArea();
        Vector2Int screenSize = new Vector2Int(Screen.width, Screen.height);
        bool changed = safeArea != _lastSafeArea || screenSize != _lastScreenSize;
        _lastSafeArea = safeArea;
        _lastScreenSize = screenSize;
        return changed;
    }
    /// <summary>
    /// 获取安全区域
    /// </summary>
    private Rect GetSafeArea()
    {
        #if UNITY_EDITOR
        if (simulateInEditor)
        {
            // 编辑器模式下模拟 iPhone X 的安全区域
            float screenWidth = Screen.width;
            float screenHeight = Screen.height;
            // 模拟刘海(顶部 44px)和底部手势条(底部 34px)
            float topInset = 44f;
            float bottomInset = 34f;
            float leftInset = 0f;
            float rightInset = 0f;
            return new Rect(
                leftInset,
                bottomInset,
                screenWidth - leftInset - rightInset,
                screenHeight - topInset - bottomInset
            );
        }
        #endif
        return Screen.safeArea;
    }
    /// <summary>
    /// 应用安全区域适配
    /// </summary>
    private void ApplySafeArea()
    {
        if (_rectTransform == null)
            return;
        Rect safeArea = GetSafeArea();
        currentSafeArea = safeArea;
        screenSize = new Vector2Int(Screen.width, Screen.height);
        // 计算锚点位置
        Vector2 anchorMin = safeArea.position;
        Vector2 anchorMax = safeArea.position + safeArea.size;
        // 转换为标准化坐标 (0-1)
        anchorMin.x /= Screen.width;
        anchorMin.y /= Screen.height;
        anchorMax.x /= Screen.width;
        anchorMax.y /= Screen.height;
        // 根据设置决定是否适配各个边缘
        Vector2 finalAnchorMin = _rectTransform.anchorMin;
        Vector2 finalAnchorMax = _rectTransform.anchorMax;
        if (adaptLeft)
            finalAnchorMin.x = anchorMin.x;
        if (adaptBottom)
            finalAnchorMin.y = anchorMin.y;
        if (adaptRight)
            finalAnchorMax.x = anchorMax.x;
        if (adaptTop)
            finalAnchorMax.y = anchorMax.y;
        _rectTransform.anchorMin = finalAnchorMin;
        _rectTransform.anchorMax = finalAnchorMax;
        // 重置偏移
        _rectTransform.offsetMin = Vector2.zero;
        _rectTransform.offsetMax = Vector2.zero;
        Debug.Log($"[SafeAreaAdapter] 安全区域已应用: {safeArea}, 屏幕: {screenSize}");
    }
    /// <summary>
    /// 强制重新应用安全区域
    /// </summary>
    public void ForceApply()
    {
        ApplySafeArea();
    }
    #if UNITY_EDITOR
    private void OnValidate()
    {
        if (_rectTransform == null)
            _rectTransform = GetComponent<RectTransform>();
        ApplySafeArea();
    }
    #endif
}
Main/Core/Platform/SafeAreaAdapter.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d199b151833c2084c8b240e09d6eab7f
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/Core/Platform/StandalonePlatform.cs
New file
@@ -0,0 +1,114 @@
using System;
using Cysharp.Threading.Tasks;
using UnityEngine;
/// <summary>
/// 编辑器测试平台实现(模拟平台行为)
/// </summary>
public class StandalonePlatform : IPlatformService
{
        private SystemInfo _cachedSystemInfo;
        public async UniTask<bool> InitAsync()
        {
            Debug.Log("[StandalonePlatform] 初始化编辑器测试平台");
            await UniTask.Delay(100); // 模拟初始化延迟
            _cachedSystemInfo = CreateMockSystemInfo();
            return true;
        }
        public PlatformType GetPlatformType()
        {
            return PlatformType.Standalone;
        }
        public async UniTask<LoginResult> LoginAsync()
        {
            Debug.Log("[StandalonePlatform] 模拟登录成功");
            await UniTask.Delay(500); // 模拟网络延迟
            return new LoginResult
            {
                Success = true,
                UserId = "test_user_12345",
                Nickname = "测试用户",
                AvatarUrl = "https://example.com/avatar.jpg",
                ErrorMessage = null
            };
        }
        public async UniTask<bool> ShareAsync(ShareData shareData)
        {
            Debug.Log($"[StandalonePlatform] 模拟分享: {shareData.Title}");
            await UniTask.Delay(300);
            return true;
        }
        public async UniTask<AdResult> ShowAdAsync(AdType adType)
        {
            Debug.Log($"[StandalonePlatform] 模拟显示广告: {adType}");
            await UniTask.Delay(2000); // 模拟广告播放时间
            return new AdResult
            {
                Success = true,
                Completed = true, // 模拟用户看完广告
                ErrorMessage = null
            };
        }
        public async UniTask<bool> SaveDataAsync(string key, string value)
        {
            Debug.Log($"[StandalonePlatform] 保存数据: {key} = {value}");
            await UniTask.Delay(50);
            PlayerPrefs.SetString(key, value);
            PlayerPrefs.Save();
            return true;
        }
        public async UniTask<string> LoadDataAsync(string key)
        {
            Debug.Log($"[StandalonePlatform] 加载数据: {key}");
            await UniTask.Delay(50);
            return PlayerPrefs.GetString(key, null);
        }
        public async UniTask<string> DownloadFileAsync(string url, string localPath, Action<float> onProgress = null)
        {
            Debug.Log($"[StandalonePlatform] 模拟下载文件: {url} -> {localPath}");
            // 模拟下载进度
            for (int i = 0; i <= 10; i++)
            {
                await UniTask.Delay(100);
                onProgress?.Invoke(i / 10f);
            }
            return localPath;
        }
        public SystemInfo GetSystemInfo()
        {
            return _cachedSystemInfo ?? (_cachedSystemInfo = CreateMockSystemInfo());
        }
        public void Vibrate(VibrationType vibrationType)
        {
            Debug.Log($"[StandalonePlatform] 模拟震动: {vibrationType}");
            // 编辑器中无法实际震动
        }
        private SystemInfo CreateMockSystemInfo()
        {
            return new SystemInfo
            {
                DeviceModel = UnityEngine.SystemInfo.deviceModel,
                SystemVersion = UnityEngine.SystemInfo.operatingSystem,
                PlatformVersion = "Standalone 1.0.0",
                ScreenWidth = Screen.width,
                ScreenHeight = Screen.height,
                SafeArea = SafeAreaData.FromRect(Screen.safeArea),
                PixelRatio = Screen.dpi / 160f
            };
        }
    }
Main/Core/Platform/StandalonePlatform.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 486271bbe3382a240a8ee78f5a2ad64f
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/Core/Platform/VivoPlatform.cs
New file
@@ -0,0 +1,193 @@
using System;
using System.Runtime.InteropServices;
using Cysharp.Threading.Tasks;
using UnityEngine;
/// <summary>
/// vivo小游戏平台实现
/// </summary>
public class VivoPlatform : IPlatformService
{
    #if UNITY_WEBGL && !UNITY_EDITOR
    // JavaScript 插件方法声明
    [DllImport("__Internal")]
    private static extern bool QG_Init();
    [DllImport("__Internal")]
    private static extern void QG_Login(string callbackObjectName, string callbackMethodName);
    [DllImport("__Internal")]
    private static extern string QG_GetSystemInfo();
    [DllImport("__Internal")]
    private static extern void QG_Share(string title, string imageUrl);
    [DllImport("__Internal")]
    private static extern void QG_CreateRewardedVideoAd(string adUnitId, string callbackObjectName, string callbackMethodName);
    [DllImport("__Internal")]
    private static extern void QG_SetStorageSync(string key, string value);
    [DllImport("__Internal")]
    private static extern string QG_GetStorageSync(string key);
    [DllImport("__Internal")]
    private static extern void QG_VibrateShort();
    [DllImport("__Internal")]
    private static extern void QG_VibrateLong();
    #endif
    private SystemInfo _cachedSystemInfo;
    private bool _isInitialized = false;
    public async UniTask<bool> InitAsync()
    {
        Debug.Log("[VivoPlatform] 初始化vivo小游戏平台");
        #if UNITY_WEBGL && !UNITY_EDITOR
        try
        {
            _isInitialized = QG_Init();
            if (_isInitialized)
            {
                _cachedSystemInfo = GetSystemInfo();
                Debug.Log("[VivoPlatform] 初始化成功");
            }
            return _isInitialized;
        }
        catch (Exception e)
        {
            Debug.LogError($"[VivoPlatform] 初始化失败: {e.Message}");
            return false;
        }
        #else
        await UniTask.Delay(100);
        _isInitialized = true;
        _cachedSystemInfo = CreateMockSystemInfo();
        return true;
        #endif
    }
    public PlatformType GetPlatformType()
    {
        return PlatformType.Vivo;
    }
    public async UniTask<LoginResult> LoginAsync()
    {
        Debug.Log("[VivoPlatform] 开始登录");
        #if UNITY_WEBGL && !UNITY_EDITOR
        // TODO: 实现vivo登录逻辑
        await UniTask.Delay(500);
        return new LoginResult { Success = true, UserId = "vivo_user" };
        #else
        await UniTask.Delay(500);
        return new LoginResult { Success = true, UserId = "vivo_test_user" };
        #endif
    }
    public async UniTask<bool> ShareAsync(ShareData shareData)
    {
        Debug.Log($"[VivoPlatform] 分享: {shareData.Title}");
        #if UNITY_WEBGL && !UNITY_EDITOR
        QG_Share(shareData.Title, shareData.ImageUrl);
        #endif
        await UniTask.Delay(300);
        return true;
    }
    public async UniTask<AdResult> ShowAdAsync(AdType adType)
    {
        Debug.Log($"[VivoPlatform] 显示广告: {adType}");
        #if UNITY_WEBGL && !UNITY_EDITOR
        // TODO: 实现vivo广告逻辑
        #endif
        await UniTask.Delay(1000);
        return new AdResult { Success = true, Completed = true };
    }
    public async UniTask<bool> SaveDataAsync(string key, string value)
    {
        #if UNITY_WEBGL && !UNITY_EDITOR
        QG_SetStorageSync(key, value);
        #endif
        await UniTask.Yield();
        return true;
    }
    public async UniTask<string> LoadDataAsync(string key)
    {
        #if UNITY_WEBGL && !UNITY_EDITOR
        string value = QG_GetStorageSync(key);
        await UniTask.Yield();
        return value;
        #else
        await UniTask.Yield();
        return null;
        #endif
    }
    public async UniTask<string> DownloadFileAsync(string url, string localPath, Action<float> onProgress = null)
    {
        Debug.Log($"[VivoPlatform] 下载文件: {url}");
        await UniTask.Delay(500);
        return localPath;
    }
    public SystemInfo GetSystemInfo()
    {
        if (_cachedSystemInfo != null)
            return _cachedSystemInfo;
        #if UNITY_WEBGL && !UNITY_EDITOR
        try
        {
            string jsonInfo = QG_GetSystemInfo();
            _cachedSystemInfo = JsonUtility.FromJson<SystemInfo>(jsonInfo);
            return _cachedSystemInfo;
        }
        catch (Exception e)
        {
            Debug.LogError($"[VivoPlatform] 获取系统信息失败: {e.Message}");
            return CreateMockSystemInfo();
        }
        #else
        return CreateMockSystemInfo();
        #endif
    }
    public void Vibrate(VibrationType vibrationType)
    {
        #if UNITY_WEBGL && !UNITY_EDITOR
        switch (vibrationType)
        {
            case VibrationType.Light:
            case VibrationType.Medium:
                QG_VibrateShort();
                break;
            case VibrationType.Heavy:
                QG_VibrateLong();
                break;
        }
        #endif
    }
    private SystemInfo CreateMockSystemInfo()
    {
        return new SystemInfo
        {
            DeviceModel = UnityEngine.SystemInfo.deviceModel,
            PlatformVersion = "Vivo 1.0.0",
            ScreenWidth = Screen.width,
            ScreenHeight = Screen.height,
            SafeArea = SafeAreaData.FromRect(Screen.safeArea),
            PixelRatio = Screen.dpi / 160f
        };
    }
}
Main/Core/Platform/VivoPlatform.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 72d9b476010653d42ac0315d07acf865
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/Core/Platform/WeChatPlatform.cs
New file
@@ -0,0 +1,256 @@
using System;
using System.Runtime.InteropServices;
using Cysharp.Threading.Tasks;
using UnityEngine;
/// <summary>
/// 微信小游戏平台实现
/// </summary>
public class WeChatPlatform : IPlatformService
{
        #if UNITY_WEBGL && !UNITY_EDITOR
        // JavaScript 插件方法声明
        [DllImport("__Internal")]
        private static extern void WX_Init();
        [DllImport("__Internal")]
        private static extern void WX_Login(string callbackObjectName, string callbackMethodName);
        [DllImport("__Internal")]
        private static extern void WX_GetSystemInfo(string callbackObjectName, string callbackMethodName);
        [DllImport("__Internal")]
        private static extern void WX_ShareAppMessage(string title, string imageUrl);
        [DllImport("__Internal")]
        private static extern void WX_CreateRewardedVideoAd(string adUnitId, string callbackObjectName, string callbackMethodName);
        [DllImport("__Internal")]
        private static extern void WX_SetStorageSync(string key, string value);
        [DllImport("__Internal")]
        private static extern string WX_GetStorageSync(string key);
        [DllImport("__Internal")]
        private static extern void WX_VibrateShort();
        [DllImport("__Internal")]
        private static extern void WX_VibrateLong();
        #endif
        private SystemInfo _cachedSystemInfo;
        private bool _isInitialized = false;
        public async UniTask<bool> InitAsync()
        {
            Debug.Log("[WeChatPlatform] 初始化微信小游戏平台");
            #if UNITY_WEBGL && !UNITY_EDITOR
            try
            {
                WX_Init();
                _isInitialized = true;
                Debug.Log("[WeChatPlatform] 微信SDK初始化成功");
            }
            catch (Exception e)
            {
                Debug.LogError($"[WeChatPlatform] 微信SDK初始化失败: {e.Message}");
                return false;
            }
            #else
            Debug.LogWarning("[WeChatPlatform] 非WebGL平台,使用模拟模式");
            _isInitialized = true;
            #endif
            await UniTask.Delay(100);
            return true;
        }
        public PlatformType GetPlatformType()
        {
            return PlatformType.WeChat;
        }
        public async UniTask<LoginResult> LoginAsync()
        {
            Debug.Log("[WeChatPlatform] 微信登录");
            #if UNITY_WEBGL && !UNITY_EDITOR
            // TODO: 实现回调机制
            // WX_Login("WeChatPlatform", "OnLoginCallback");
            await UniTask.Delay(1000); // 等待回调
            #else
            await UniTask.Delay(500);
            #endif
            // 模拟登录成功(实际需要通过回调获取)
            return new LoginResult
            {
                Success = true,
                UserId = "wx_user_mock",
                Nickname = "微信用户",
                AvatarUrl = "https://example.com/avatar.jpg",
                ErrorMessage = null
            };
        }
        public async UniTask<bool> ShareAsync(ShareData shareData)
        {
            Debug.Log($"[WeChatPlatform] 分享到微信: {shareData.Title}");
            #if UNITY_WEBGL && !UNITY_EDITOR
            try
            {
                WX_ShareAppMessage(shareData.Title, shareData.ImageUrl);
                await UniTask.Delay(300);
                return true;
            }
            catch (Exception e)
            {
                Debug.LogError($"[WeChatPlatform] 分享失败: {e.Message}");
                return false;
            }
            #else
            await UniTask.Delay(300);
            return true;
            #endif
        }
        public async UniTask<AdResult> ShowAdAsync(AdType adType)
        {
            Debug.Log($"[WeChatPlatform] 显示微信广告: {adType}");
            #if UNITY_WEBGL && !UNITY_EDITOR
            // TODO: 实现广告回调
            string adUnitId = GetAdUnitId(adType);
            // WX_CreateRewardedVideoAd(adUnitId, "WeChatPlatform", "OnAdCallback");
            await UniTask.Delay(3000);
            #else
            await UniTask.Delay(2000);
            #endif
            return new AdResult
            {
                Success = true,
                Completed = true,
                ErrorMessage = null
            };
        }
        public async UniTask<bool> SaveDataAsync(string key, string value)
        {
            Debug.Log($"[WeChatPlatform] 保存数据到微信存储: {key}");
            #if UNITY_WEBGL && !UNITY_EDITOR
            try
            {
                WX_SetStorageSync(key, value);
                await UniTask.Delay(50);
                return true;
            }
            catch (Exception e)
            {
                Debug.LogError($"[WeChatPlatform] 保存数据失败: {e.Message}");
                return false;
            }
            #else
            await UniTask.Delay(50);
            PlayerPrefs.SetString(key, value);
            PlayerPrefs.Save();
            return true;
            #endif
        }
        public async UniTask<string> LoadDataAsync(string key)
        {
            Debug.Log($"[WeChatPlatform] 从微信存储加载数据: {key}");
            #if UNITY_WEBGL && !UNITY_EDITOR
            try
            {
                await UniTask.Delay(50);
                return WX_GetStorageSync(key);
            }
            catch (Exception e)
            {
                Debug.LogError($"[WeChatPlatform] 加载数据失败: {e.Message}");
                return null;
            }
            #else
            await UniTask.Delay(50);
            return PlayerPrefs.GetString(key, null);
            #endif
        }
        public async UniTask<string> DownloadFileAsync(string url, string localPath, Action<float> onProgress = null)
        {
            Debug.Log($"[WeChatPlatform] 下载文件: {url}");
            // TODO: 实现微信文件下载API
            // wx.downloadFile({ url, success, fail })
            // 模拟下载
            for (int i = 0; i <= 10; i++)
            {
                await UniTask.Delay(100);
                onProgress?.Invoke(i / 10f);
            }
            return localPath;
        }
        public SystemInfo GetSystemInfo()
        {
            if (_cachedSystemInfo == null)
            {
                #if UNITY_WEBGL && !UNITY_EDITOR
                // TODO: 从微信API获取系统信息
                // WX_GetSystemInfo("WeChatPlatform", "OnSystemInfoCallback");
                #endif
                _cachedSystemInfo = new SystemInfo
                {
                    DeviceModel = UnityEngine.SystemInfo.deviceModel,
                    SystemVersion = UnityEngine.SystemInfo.operatingSystem,
                    PlatformVersion = "WeChat 8.0.0",
                    ScreenWidth = Screen.width,
                    ScreenHeight = Screen.height,
                    SafeArea = SafeAreaData.FromRect(Screen.safeArea),
                    PixelRatio = Screen.dpi / 160f
                };
            }
            return _cachedSystemInfo;
        }
        public void Vibrate(VibrationType vibrationType)
        {
            Debug.Log($"[WeChatPlatform] 震动: {vibrationType}");
            #if UNITY_WEBGL && !UNITY_EDITOR
            try
            {
                if (vibrationType == VibrationType.Heavy)
                    WX_VibrateLong();
                else
                    WX_VibrateShort();
            }
            catch (Exception e)
            {
                Debug.LogError($"[WeChatPlatform] 震动失败: {e.Message}");
            }
            #endif
        }
        private string GetAdUnitId(AdType adType)
        {
            // TODO: 从配置文件读取广告位ID
            return adType switch
            {
                AdType.Video => "adunit-xxxxxx",
                AdType.Banner => "adunit-yyyyyy",
                AdType.Interstitial => "adunit-zzzzzz",
                _ => ""
            };
        }
    }
Main/Core/Platform/WeChatPlatform.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 89e47741ded740c48893b331d65d26a4
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/Main.asmdef
@@ -10,7 +10,8 @@
        "GUID:72d1fea872bd7a449bf3818f2b0a6708",
        "GUID:05d41852e29aa5141a64e3d2d5339981",
        "GUID:9ad05b610be6c974590152128a8b5b6e",
        "GUID:d51b17ee17bf72443860693b4f9c20af"
        "GUID:d51b17ee17bf72443860693b4f9c20af",
        "GUID:04376767bc1f3b428aefa3d20743e819"
    ],
    "includePlatforms": [],
    "excludePlatforms": [],
Main/Main.cs
@@ -7,10 +7,20 @@
using System.IO;
/// <summary>
/// 网络类型枚举
/// </summary>
/// <summary>
/// Main类,作为热更新程序集的入口点
/// </summary>
public class Main
{
    /// <summary>
    /// 网络类型配置(可在代码中修改或通过 Inspector 配置)
    /// </summary>
    public static NetworkType CurrentNetworkType = NetworkType.TCP;
    public static List<IGameSystemManager> managers = new List<IGameSystemManager>();
    /// <summary>
Main/Utility/DeviceUtility.cs
@@ -78,7 +78,9 @@
#if UNITY_IOS
        return UnityEngine.iOS.Device.advertisingIdentifier;
#else
        return SystemInfo.deviceUniqueIdentifier;
        // SystemInfo.deviceUniqueIdentifier is deprecated in Unity 2022+
        // Use platform-specific solutions or GUID
        return System.Guid.NewGuid().ToString();
#endif
    }
@@ -87,7 +89,7 @@
#if UNITY_IOS
        return UnityEngine.iOS.Device.systemVersion;
#else
        return SystemInfo.operatingSystem;
        return Application.platform.ToString();
#endif
    }
@@ -96,7 +98,7 @@
#if UNITY_IOS
        return UnityEngine.iOS.Device.generation.ToString();
#else
        return SystemInfo.deviceName;
        return Application.productName;
#endif
    }
@@ -105,7 +107,7 @@
#if UNITY_IOS
        return UnityEngine.iOS.Device.generation.ToString();
#else
        return SystemInfo.deviceModel;
        return Application.platform.ToString();
#endif
    }