From a29fffa93dc66ff45589f8fd1f8584f7bcb9fdad Mon Sep 17 00:00:00 2001
From: yyl <yyl>
Date: 星期四, 05 二月 2026 18:41:17 +0800
Subject: [PATCH] h5 tcp switch to websocket
---
Main/Core/Platform/PlatformConfig.cs | 84 +
Main/Core/Platform/DouyinPlatform.cs.meta | 11
Main/Core/Platform/PlatformType.cs.meta | 11
Main/Core/Platform/StandalonePlatform.cs | 114 ++
Main/Core/Platform/IPlatformService.cs | 75 +
Main/Core/Platform/PlatformData.cs | 147 +++
Main/Core/NetworkPackage/WebSocketNetworkService.cs | 466 ++++++++++
Main/Core/NetworkPackage/ClientSocketAdapter.cs.meta | 11
Main/Core/Platform/VivoPlatform.cs.meta | 11
Main/Core/Platform/SafeAreaAdapter.cs.meta | 11
Main/Utility/DeviceUtility.cs | 10
Main/Core/Platform.meta | 8
Main/Core/Platform/DeviceProfile.cs.meta | 11
Main/Core/NetworkPackage/INetworkService.cs | 71 +
Main/Core/Platform/PlatformFactory.cs | 85 +
Main/Core/Platform/PlatformFactory.cs.meta | 11
Main/Main.cs | 10
Main/Core/NetworkPackage/ClientSocketAdapter.cs | 186 ++++
Main/Core/Platform/IPlatformService.cs.meta | 11
Main/Core/Platform/StandalonePlatform.cs.meta | 11
Main/Core/Platform/PlatformConfig.cs.meta | 11
Main/Core/Platform/WeChatPlatform.cs.meta | 11
Main/Core/Platform/AdManager.cs.meta | 11
Main/Component/UI/Effect/PostEffectsBase.cs | 11
Main/Main.asmdef | 3
Main/Core/NetworkPackage/GameNetSystem.cs | 162 +++
Main/Core/Platform/WeChatPlatform.cs | 256 +++++
Main/Core/NetworkPackage/Network.meta | 8
Main/Core/Platform/SafeAreaAdapter.cs | 154 +++
Main/Core/NetworkPackage/INetworkService.cs.meta | 11
Main/Core/Platform/AdManager.cs | 198 ++++
Main/Core/NetworkPackage/WebSocketNetworkService.cs.meta | 11
Main/Core/Platform/DouyinPlatform.cs | 193 ++++
Main/Core/Platform/PlatformType.cs | 23
Main/Core/Platform/DeviceProfile.cs | 71 +
Main/Core/Platform/PlatformData.cs.meta | 11
Main/Core/Platform/VivoPlatform.cs | 193 ++++
37 files changed, 2,657 insertions(+), 36 deletions(-)
diff --git a/Main/Component/UI/Effect/PostEffectsBase.cs b/Main/Component/UI/Effect/PostEffectsBase.cs
index 72294cd..30e544e 100644
--- a/Main/Component/UI/Effect/PostEffectsBase.cs
+++ b/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)
diff --git a/Main/Core/NetworkPackage/ClientSocketAdapter.cs b/Main/Core/NetworkPackage/ClientSocketAdapter.cs
new file mode 100644
index 0000000..45996c7
--- /dev/null
+++ b/Main/Core/NetworkPackage/ClientSocketAdapter.cs
@@ -0,0 +1,186 @@
+using Cysharp.Threading.Tasks;
+using System;
+using UnityEngine;
+
+/// <summary>
+/// ClientSocket 閫傞厤鍣� - 灏� ClientSocket 閫傞厤涓� INetworkService 鎺ュ彛
+/// 锛堜笉淇敼宸查獙璇佺殑 ClientSocket 浠g爜锛�
+/// </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;
+
+ // 瑙f瀽 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>
+ /// 瑙f瀽 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;
+ }
+ }
+}
diff --git a/Main/Core/NetworkPackage/ClientSocketAdapter.cs.meta b/Main/Core/NetworkPackage/ClientSocketAdapter.cs.meta
new file mode 100644
index 0000000..98e1b23
--- /dev/null
+++ b/Main/Core/NetworkPackage/ClientSocketAdapter.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 529c690109d07de41aa851e793469e7d
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/Core/NetworkPackage/GameNetSystem.cs b/Main/Core/NetworkPackage/GameNetSystem.cs
index a0661a3..7bbd6e3 100644
--- a/Main/Core/NetworkPackage/GameNetSystem.cs
+++ b/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鐨勫寘鍚庢墠鑳藉彂鍏朵粬鐨勫姛鑳藉寘锛屽彧鏈塁0123_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)
diff --git a/Main/Core/NetworkPackage/INetworkService.cs b/Main/Core/NetworkPackage/INetworkService.cs
new file mode 100644
index 0000000..f75d5da
--- /dev/null
+++ b/Main/Core/NetworkPackage/INetworkService.cs
@@ -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">鏈嶅姟鍣ㄥ湴鍧�锛坵s:// 鎴� 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 // 宸插叧闂�
+}
diff --git a/Main/Core/NetworkPackage/INetworkService.cs.meta b/Main/Core/NetworkPackage/INetworkService.cs.meta
new file mode 100644
index 0000000..2de0088
--- /dev/null
+++ b/Main/Core/NetworkPackage/INetworkService.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 74d34d5ef1dcb5e4bae375bc3dd8cd41
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/Core/NetworkPackage/Network.meta b/Main/Core/NetworkPackage/Network.meta
new file mode 100644
index 0000000..9fc19a6
--- /dev/null
+++ b/Main/Core/NetworkPackage/Network.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 683353faee5b8104da00dafdc791bbed
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/Core/NetworkPackage/WebSocketNetworkService.cs b/Main/Core/NetworkPackage/WebSocketNetworkService.cs
new file mode 100644
index 0000000..44a7d7b
--- /dev/null
+++ b/Main/Core/NetworkPackage/WebSocketNetworkService.cs
@@ -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);
+
+ // 瑙e瘑鍖呬綋锛堜笌 ClientSocket.ReadInfo 涓�鑷达級
+ byte[] decryptedData = encoder.BaseXorSub(encryptedBody);
+
+ // 瑙f瀽 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] 瑙e瘑鎴愬姛锛孋MD=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] 鍏抽棴浠g爜: {code} ({(int)code})");
+ Debug.LogError($"[WebSocket] 涔嬪墠鐘舵��: {State}\n");
+
+ // 鍒ゆ柇鏄皝鍏抽棴鐨�
+ if (code == WebSocketCloseCode.Normal)
+ {
+ Debug.LogWarning("[WebSocket] 鈫� 姝e父鍏抽棴锛堝鎴风涓诲姩锛�");
+ }
+ 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
+ }
+}
diff --git a/Main/Core/NetworkPackage/WebSocketNetworkService.cs.meta b/Main/Core/NetworkPackage/WebSocketNetworkService.cs.meta
new file mode 100644
index 0000000..dd41989
--- /dev/null
+++ b/Main/Core/NetworkPackage/WebSocketNetworkService.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 963e73ef1640ad24ca0948e6cb9d2198
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/Core/Platform.meta b/Main/Core/Platform.meta
new file mode 100644
index 0000000..b88ab85
--- /dev/null
+++ b/Main/Core/Platform.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 49b4d42a5d166244e87a2ea12de035ca
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/Core/Platform/AdManager.cs b/Main/Core/Platform/AdManager.cs
new file mode 100644
index 0000000..1f8dc7f
--- /dev/null
+++ b/Main/Core/Platform/AdManager.cs
@@ -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] 宸叉湁骞垮憡姝e湪鎾斁");
+ 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);
+ }
+}
diff --git a/Main/Core/Platform/AdManager.cs.meta b/Main/Core/Platform/AdManager.cs.meta
new file mode 100644
index 0000000..8128ea4
--- /dev/null
+++ b/Main/Core/Platform/AdManager.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c7a55e0786384324ab07a78c25054280
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/Core/Platform/DeviceProfile.cs b/Main/Core/Platform/DeviceProfile.cs
new file mode 100644
index 0000000..d907b64
--- /dev/null
+++ b/Main/Core/Platform/DeviceProfile.cs
@@ -0,0 +1,71 @@
+using System;
+using UnityEngine;
+
+/// <summary>
+/// 鎬ц兘妗f鏋氫妇
+/// </summary>
+public enum PerformanceLevel
+{
+ /// <summary>浣庣璁惧锛堝唴瀛� < 2GB锛�2019骞村墠鏈哄瀷锛�</summary>
+ Low,
+
+ /// <summary>涓璁惧锛堝唴瀛� 2-4GB锛�2019-2021骞存満鍨嬶級</summary>
+ Medium,
+
+ /// <summary>楂樼璁惧锛堝唴瀛� > 4GB锛�2021骞村悗鏃楄埌锛�</summary>
+ High
+}
+
+/// <summary>
+/// 璁惧鎬ц兘妗f鍜屽睆骞曚俊鎭�
+/// </summary>
+[Serializable]
+public class DeviceProfile
+{
+ /// <summary>鎬ц兘妗f</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>
+ /// 浠嶶nity 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
+ };
+
+ // 鏍规嵁鍐呭瓨鍒ゅ畾鎬ц兘妗f
+ if (profile.TotalMemory < 2048)
+ profile.PerformanceLevel = PerformanceLevel.Low;
+ else if (profile.TotalMemory < 4096)
+ profile.PerformanceLevel = PerformanceLevel.Medium;
+ else
+ profile.PerformanceLevel = PerformanceLevel.High;
+
+ return profile;
+ }
+ }
diff --git a/Main/Core/Platform/DeviceProfile.cs.meta b/Main/Core/Platform/DeviceProfile.cs.meta
new file mode 100644
index 0000000..c452b89
--- /dev/null
+++ b/Main/Core/Platform/DeviceProfile.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 27f18f1ecea04c3418b4cdc4192a62df
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/Core/Platform/DouyinPlatform.cs b/Main/Core/Platform/DouyinPlatform.cs
new file mode 100644
index 0000000..04adc3d
--- /dev/null
+++ b/Main/Core/Platform/DouyinPlatform.cs
@@ -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
+ };
+ }
+}
diff --git a/Main/Core/Platform/DouyinPlatform.cs.meta b/Main/Core/Platform/DouyinPlatform.cs.meta
new file mode 100644
index 0000000..f7c4bca
--- /dev/null
+++ b/Main/Core/Platform/DouyinPlatform.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9ec9f394fc27b9d4b99eb0e84792b12d
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/Core/Platform/IPlatformService.cs b/Main/Core/Platform/IPlatformService.cs
new file mode 100644
index 0000000..5d6df8f
--- /dev/null
+++ b/Main/Core/Platform/IPlatformService.cs
@@ -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);
+}
diff --git a/Main/Core/Platform/IPlatformService.cs.meta b/Main/Core/Platform/IPlatformService.cs.meta
new file mode 100644
index 0000000..f5ad732
--- /dev/null
+++ b/Main/Core/Platform/IPlatformService.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 46aa75e3d41e74a4387775b45e1704b1
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/Core/Platform/PlatformConfig.cs b/Main/Core/Platform/PlatformConfig.cs
new file mode 100644
index 0000000..eb1a80b
--- /dev/null
+++ b/Main/Core/Platform/PlatformConfig.cs
@@ -0,0 +1,84 @@
+using System;
+using System.Collections.Generic;
+
+/// <summary>
+/// 骞冲彴閰嶇疆绫�
+/// </summary>
+[Serializable]
+public class PlatformConfig
+{
+ /// <summary>骞冲彴绫诲瀷</summary>
+ public PlatformType PlatformType;
+
+ /// <summary>棣栧寘澶у皬闄愬埗锛圡B锛�</summary>
+ public int FirstPackageSizeLimit = 4;
+
+ /// <summary>鍗曚釜鍒嗗寘澶у皬闄愬埗锛圡B锛�</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" }
+ };
+ }
+ }
diff --git a/Main/Core/Platform/PlatformConfig.cs.meta b/Main/Core/Platform/PlatformConfig.cs.meta
new file mode 100644
index 0000000..5de1246
--- /dev/null
+++ b/Main/Core/Platform/PlatformConfig.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 825296e797695694a9758bb01fd0f8df
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/Core/Platform/PlatformData.cs b/Main/Core/Platform/PlatformData.cs
new file mode 100644
index 0000000..0364515
--- /dev/null
+++ b/Main/Core/Platform/PlatformData.cs
@@ -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>
+ /// 浠嶶nity 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
+ };
+ }
+ }
diff --git a/Main/Core/Platform/PlatformData.cs.meta b/Main/Core/Platform/PlatformData.cs.meta
new file mode 100644
index 0000000..671811f
--- /dev/null
+++ b/Main/Core/Platform/PlatformData.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e35f891c3088a09449c8504068b9412b
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/Core/Platform/PlatformFactory.cs b/Main/Core/Platform/PlatformFactory.cs
new file mode 100644
index 0000000..3882365
--- /dev/null
+++ b/Main/Core/Platform/PlatformFactory.cs
@@ -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
+ }
+ }
diff --git a/Main/Core/Platform/PlatformFactory.cs.meta b/Main/Core/Platform/PlatformFactory.cs.meta
new file mode 100644
index 0000000..bf4d94b
--- /dev/null
+++ b/Main/Core/Platform/PlatformFactory.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ecd57f320592c7b4cb442c34af4dc38c
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/Core/Platform/PlatformType.cs b/Main/Core/Platform/PlatformType.cs
new file mode 100644
index 0000000..1abcfc2
--- /dev/null
+++ b/Main/Core/Platform/PlatformType.cs
@@ -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
+}
diff --git a/Main/Core/Platform/PlatformType.cs.meta b/Main/Core/Platform/PlatformType.cs.meta
new file mode 100644
index 0000000..6231e62
--- /dev/null
+++ b/Main/Core/Platform/PlatformType.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 7af8ceb1c332b66438af6bc120883b48
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/Core/Platform/SafeAreaAdapter.cs b/Main/Core/Platform/SafeAreaAdapter.cs
new file mode 100644
index 0000000..79d623f
--- /dev/null
+++ b/Main/Core/Platform/SafeAreaAdapter.cs
@@ -0,0 +1,154 @@
+using UnityEngine;
+
+/// <summary>
+/// 瀹夊叏鍖哄煙閫傞厤缁勪欢 - 鑷姩閫傞厤鍒樻捣灞忓拰寮傚舰灞�
+/// </summary>
+[RequireComponent(typeof(RectTransform))]
+[ExecuteInEditMode]
+public class SafeAreaAdapter : MonoBehaviour
+{
+ [Header("閫傞厤璁剧疆")]
+ [Tooltip("鏄惁鍦ㄧ紪杈戝櫒妯″紡涓嬫ā鎷熷畨鍏ㄥ尯鍩�")]
+ public bool simulateInEditor = false;
+
+ [Tooltip("閫傞厤杈圭紭锛圱op, 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
+}
diff --git a/Main/Core/Platform/SafeAreaAdapter.cs.meta b/Main/Core/Platform/SafeAreaAdapter.cs.meta
new file mode 100644
index 0000000..5139333
--- /dev/null
+++ b/Main/Core/Platform/SafeAreaAdapter.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d199b151833c2084c8b240e09d6eab7f
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/Core/Platform/StandalonePlatform.cs b/Main/Core/Platform/StandalonePlatform.cs
new file mode 100644
index 0000000..f8ba565
--- /dev/null
+++ b/Main/Core/Platform/StandalonePlatform.cs
@@ -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
+ };
+ }
+ }
diff --git a/Main/Core/Platform/StandalonePlatform.cs.meta b/Main/Core/Platform/StandalonePlatform.cs.meta
new file mode 100644
index 0000000..1683c51
--- /dev/null
+++ b/Main/Core/Platform/StandalonePlatform.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 486271bbe3382a240a8ee78f5a2ad64f
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/Core/Platform/VivoPlatform.cs b/Main/Core/Platform/VivoPlatform.cs
new file mode 100644
index 0000000..d44640b
--- /dev/null
+++ b/Main/Core/Platform/VivoPlatform.cs
@@ -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] 鍒濆鍖杤ivo灏忔父鎴忓钩鍙�");
+
+ #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
+ };
+ }
+}
diff --git a/Main/Core/Platform/VivoPlatform.cs.meta b/Main/Core/Platform/VivoPlatform.cs.meta
new file mode 100644
index 0000000..a3b4b4c
--- /dev/null
+++ b/Main/Core/Platform/VivoPlatform.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 72d9b476010653d42ac0315d07acf865
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/Core/Platform/WeChatPlatform.cs b/Main/Core/Platform/WeChatPlatform.cs
new file mode 100644
index 0000000..92d2e5b
--- /dev/null
+++ b/Main/Core/Platform/WeChatPlatform.cs
@@ -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] 闈濿ebGL骞冲彴锛屼娇鐢ㄦā鎷熸ā寮�");
+ _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: 浠庡井淇PI鑾峰彇绯荤粺淇℃伅
+ // 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",
+ _ => ""
+ };
+ }
+ }
diff --git a/Main/Core/Platform/WeChatPlatform.cs.meta b/Main/Core/Platform/WeChatPlatform.cs.meta
new file mode 100644
index 0000000..22c898e
--- /dev/null
+++ b/Main/Core/Platform/WeChatPlatform.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 89e47741ded740c48893b331d65d26a4
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/Main.asmdef b/Main/Main.asmdef
index 2832f27..21be3cf 100644
--- a/Main/Main.asmdef
+++ b/Main/Main.asmdef
@@ -10,7 +10,8 @@
"GUID:72d1fea872bd7a449bf3818f2b0a6708",
"GUID:05d41852e29aa5141a64e3d2d5339981",
"GUID:9ad05b610be6c974590152128a8b5b6e",
- "GUID:d51b17ee17bf72443860693b4f9c20af"
+ "GUID:d51b17ee17bf72443860693b4f9c20af",
+ "GUID:04376767bc1f3b428aefa3d20743e819"
],
"includePlatforms": [],
"excludePlatforms": [],
diff --git a/Main/Main.cs b/Main/Main.cs
index 35335b9..cf517e3 100644
--- a/Main/Main.cs
+++ b/Main/Main.cs
@@ -7,10 +7,20 @@
using System.IO;
/// <summary>
+/// 缃戠粶绫诲瀷鏋氫妇
+/// </summary>
+
+
+/// <summary>
/// Main绫伙紝浣滀负鐑洿鏂扮▼搴忛泦鐨勫叆鍙g偣
/// </summary>
public class Main
{
+ /// <summary>
+ /// 缃戠粶绫诲瀷閰嶇疆锛堝彲鍦ㄤ唬鐮佷腑淇敼鎴栭�氳繃 Inspector 閰嶇疆锛�
+ /// </summary>
+ public static NetworkType CurrentNetworkType = NetworkType.TCP;
+
public static List<IGameSystemManager> managers = new List<IGameSystemManager>();
/// <summary>
diff --git a/Main/Utility/DeviceUtility.cs b/Main/Utility/DeviceUtility.cs
index 518f4d6..3d50384 100644
--- a/Main/Utility/DeviceUtility.cs
+++ b/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
}
--
Gitblit v1.8.0