h5 tcp switch to websocket
| | |
| | | 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) |
| | | //{ |
| | |
| | | // 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) |
| New file |
| | |
| | | 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; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 529c690109d07de41aa851e793469e7d |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| | |
| | | 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; |
| | |
| | | } |
| | | } |
| | | |
| | | 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; |
| | | } |
| | | } |
| | | |
| | | |
| | |
| | | { |
| | | try |
| | | { |
| | | if (mainSocketConnected) |
| | | if (networkService != null && networkService.IsConnected) |
| | | { |
| | | mainSocket.CloseConnect(); |
| | | networkService.Disconnect(); |
| | | } |
| | | } |
| | | catch (System.Exception ex) |
| | |
| | | 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,如果不是登录包不允许发送 |
| | |
| | | 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) |
| | |
| | | } |
| | | } |
| | | |
| | | 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); |
| | | } |
| | | } |
| | | |
| | |
| | | { |
| | | try |
| | | { |
| | | if (mainSocket != null) |
| | | if (networkService != null) |
| | | { |
| | | mainSocket.CloseConnect(); |
| | | networkService.Disconnect(); |
| | | } |
| | | |
| | | mainProtocolQueue.Clear(); |
| | |
| | | { |
| | | try |
| | | { |
| | | if (mainSocket != null) |
| | | if (networkService != null) |
| | | { |
| | | mainSocket.CloseConnect(); |
| | | networkService.Disconnect(); |
| | | } |
| | | |
| | | mainProtocolQueue.Clear(); |
| | |
| | | { |
| | | SDKUtils.Instance.RoleLoginOut(); |
| | | |
| | | if (mainSocket != null) |
| | | if (networkService != null) |
| | | { |
| | | mainSocket.CloseConnect(); |
| | | networkService.Disconnect(); |
| | | } |
| | | |
| | | mainProtocolQueue.Clear(); |
| | |
| | | |
| | | void OnUpdate() |
| | | { |
| | | if (networkService != null) |
| | | { |
| | | if (networkService is WebSocketNetworkService wsService) { wsService.Update(); } |
| | | } |
| | | |
| | | lock (this) |
| | | { |
| | | while (mainProtocolQueue.Count > 0) |
| New file |
| | |
| | | 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 // 已关闭 |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 74d34d5ef1dcb5e4bae375bc3dd8cd41 |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 683353faee5b8104da00dafdc791bbed |
| | | folderAsset: yes |
| | | DefaultImporter: |
| | | externalObjects: {} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | 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 |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 963e73ef1640ad24ca0948e6cb9d2198 |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 49b4d42a5d166244e87a2ea12de035ca |
| | | folderAsset: yes |
| | | DefaultImporter: |
| | | externalObjects: {} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | 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); |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: c7a55e0786384324ab07a78c25054280 |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | 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; |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 27f18f1ecea04c3418b4cdc4192a62df |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | 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 |
| | | }; |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 9ec9f394fc27b9d4b99eb0e84792b12d |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | 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); |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 46aa75e3d41e74a4387775b45e1704b1 |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | 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" } |
| | | }; |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 825296e797695694a9758bb01fd0f8df |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | 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 |
| | | }; |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: e35f891c3088a09449c8504068b9412b |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | 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 |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: ecd57f320592c7b4cb442c34af4dc38c |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | /// <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 |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 7af8ceb1c332b66438af6bc120883b48 |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | 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 |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: d199b151833c2084c8b240e09d6eab7f |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | 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 |
| | | }; |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 486271bbe3382a240a8ee78f5a2ad64f |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | 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 |
| | | }; |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 72d9b476010653d42ac0315d07acf865 |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | 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", |
| | | _ => "" |
| | | }; |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 89e47741ded740c48893b331d65d26a4 |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| | |
| | | "GUID:72d1fea872bd7a449bf3818f2b0a6708", |
| | | "GUID:05d41852e29aa5141a64e3d2d5339981", |
| | | "GUID:9ad05b610be6c974590152128a8b5b6e", |
| | | "GUID:d51b17ee17bf72443860693b4f9c20af" |
| | | "GUID:d51b17ee17bf72443860693b4f9c20af", |
| | | "GUID:04376767bc1f3b428aefa3d20743e819" |
| | | ], |
| | | "includePlatforms": [], |
| | | "excludePlatforms": [], |
| | |
| | | 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>
|
| | |
| | | #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 |
| | | } |
| | | |
| | |
| | | #if UNITY_IOS |
| | | return UnityEngine.iOS.Device.systemVersion; |
| | | #else |
| | | return SystemInfo.operatingSystem; |
| | | return Application.platform.ToString(); |
| | | #endif |
| | | } |
| | | |
| | |
| | | #if UNITY_IOS |
| | | return UnityEngine.iOS.Device.generation.ToString(); |
| | | #else |
| | | return SystemInfo.deviceName; |
| | | return Application.productName; |
| | | #endif |
| | | } |
| | | |
| | |
| | | #if UNITY_IOS |
| | | return UnityEngine.iOS.Device.generation.ToString(); |
| | | #else |
| | | return SystemInfo.deviceModel; |
| | | return Application.platform.ToString(); |
| | | #endif |
| | | } |
| | | |