Files
daluandou/.codely.packages/cn.tuanjie.codely.bridge@1.0.85-785dea6a/Editor/Tauri/CodelyEventTracker.cs
T
huadexuan 53d95802e5 ```
feat(gameplay): 更新单位属性和材质配置

- 调整多个单位的基础生命值和攻击力数值平衡
- 修改CoreBattleGround材质的纹理缩放比例
- 更新地砖材质颜色为白色
- 优化纹理资源设置,调整最大纹理尺寸并添加PlayableAds平台支持
- 修复弓箭手技能目标查找逻辑,改用统一的敌人检测方法
- 为水晶矿添加中立阵营设置和光球视觉效果同步
- 调整预制体中的全局缩放参数
- 为狼人和石巨人添加NavMeshAgent组件支持
- 优化水晶矿血条显示位置计算方式
```
2026-09-20 21:52:31 +08:00

330 lines
11 KiB
C#

#if UNITY_EDITOR_OSX || UNITY_EDITOR_WIN
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading;
using Unity.InternalBridge;
using UnityEditor;
using UnityTcp.Editor.Helpers;
using UnityEngine;
namespace Cn.Tuanjie.Codely.Editor
{
/// <summary>
/// Analytics event tracker. Sends events from Unity to the Tauri backend via
/// HTTP invoke (<c>unity/trackEvent</c>), which forwards them to the remote server.
/// </summary>
public static class CodelyEventTracker
{
public const string InvokeTrackEvent = "unity/trackEvent";
public const string WelcomeWindowShown = "welcome_window_shown";
public const string WelcomeWindowClosed = "welcome_window_closed";
public const string WelcomeButtonClick = "welcome_button_clicked";
public const string CodelyPanelOpened = "codely_panel_opened";
public const string CodelyPanelClosed = "codely_panel_closed";
private static readonly HashSet<string> s_eventsRequiringUserId =
new HashSet<string>
{
WelcomeWindowShown,
WelcomeWindowClosed,
WelcomeButtonClick,
CodelyPanelOpened,
CodelyPanelClosed,
};
private static readonly HttpClient s_httpClient = new HttpClient
{
Timeout = TimeSpan.FromSeconds(10),
};
private static readonly Queue<string> s_pendingTrackPayloads =
new Queue<string>();
private const int MaxPendingTrackPayloads = 50;
public static bool HasPending => s_pendingTrackPayloads.Count > 0;
private static bool? s_isTuanjie;
private static bool s_suppressNextPanelClose;
/// <summary>
/// Skip the next <c>codely_panel_closed</c> event. Use when closing only to
/// immediately reopen (e.g. setup UI → main panel, force reload).
/// </summary>
public static void SuppressNextPanelClose()
{
s_suppressNextPanelClose = true;
}
/// <returns>True when the close event should be skipped.</returns>
public static bool ConsumeSuppressNextPanelClose()
{
if (!s_suppressNextPanelClose)
{
return false;
}
s_suppressNextPanelClose = false;
return true;
}
/// <summary>
/// Flush track events queued before the Tauri HTTP port was available.
/// </summary>
public static void FlushPending(int tauriServerPort = -1)
{
if (!ShouldTrack())
{
s_pendingTrackPayloads.Clear();
return;
}
while (s_pendingTrackPayloads.Count > 0)
{
string payload = s_pendingTrackPayloads.Peek();
if (!SendTrackInvoke(payload, tauriServerPort, synchronous: false))
{
break;
}
s_pendingTrackPayloads.Dequeue();
}
}
public static bool Track(string eventType, string data = null, int tauriServerPort = -1)
{
if (!ShouldTrack())
{
return false;
}
try
{
FlushPending(tauriServerPort);
string userId = s_eventsRequiringUserId.Contains(eventType)
? UnityConnectSession.GetUserId()
: null;
string payload = BuildPayload(eventType, data, userId);
if (SendTrackInvoke(payload, tauriServerPort, synchronous: false))
{
CodelyLogger.Log($"Codely EventTracker: {eventType}");
return true;
}
if (s_pendingTrackPayloads.Count < MaxPendingTrackPayloads)
{
s_pendingTrackPayloads.Enqueue(payload);
CodelyLogger.LogWarning(
$"Codely EventTracker: {eventType} buffered (Tauri port not ready)");
}
else
{
CodelyLogger.LogWarning(
$"Codely EventTracker: {eventType} dropped (pending queue full)");
}
}
catch (Exception ex)
{
CodelyLogger.LogWarning($"Codely EventTracker error: {ex.Message}");
}
return false;
}
/// <summary>
/// Track a lifecycle close event via HTTP invoke. Uses a synchronous request so
/// the event is sent before the Editor window tears down Tauri connectivity.
/// </summary>
public static void TryTrackClose(
string eventType,
int tauriServerPort,
ref bool alreadySent,
bool synchronous = true)
{
if (alreadySent || !ShouldTrack())
{
return;
}
try
{
FlushPending(tauriServerPort);
string userId = s_eventsRequiringUserId.Contains(eventType)
? UnityConnectSession.GetUserId()
: null;
string payload = BuildPayload(eventType, data: null, userId);
if (SendTrackInvoke(payload, tauriServerPort, synchronous))
{
alreadySent = true;
CodelyLogger.Log($"Codely EventTracker: {eventType}");
}
}
catch (Exception ex)
{
CodelyLogger.LogWarning($"Codely EventTracker error: {ex.Message}");
}
}
private static bool ShouldTrack()
{
if (!s_isTuanjie.HasValue)
{
string editorType = TauriUtils.GetEditorType();
s_isTuanjie = editorType == "tuanjie";
}
return s_isTuanjie.Value;
}
private static bool SendTrackInvoke(
string trackPayloadJson,
int tauriServerPort,
bool synchronous)
{
return SendHttpInvoke(
InvokeTrackEvent,
tauriServerPort,
synchronous,
trackPayloadJson);
}
private static bool SendHttpInvoke(
string messageType,
int tauriServerPort,
bool synchronous,
string dataJson = null)
{
int port = ResolveTauriPort(tauriServerPort);
if (port <= 0)
{
return false;
}
try
{
string workspaceDir = TauriUtils.GetWorkspaceDirectory();
string editorType = TauriUtils.GetEditorType();
string messageId = Guid.NewGuid().ToString();
string payload = BuildInvokePayload(
messageType,
messageId,
workspaceDir,
editorType,
dataJson);
string url = $"http://127.0.0.1:{port}/api/tauri/invoke";
var content = new StringContent(payload, Encoding.UTF8, "application/json");
var request = new HttpRequestMessage(HttpMethod.Post, url) { Content = content };
AddInvokeRoutingHeaders(request, workspaceDir, editorType);
if (synchronous)
{
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2)))
{
using (var response = s_httpClient.SendAsync(request, cts.Token).GetAwaiter().GetResult())
{
return response.IsSuccessStatusCode;
}
}
}
_ = s_httpClient.SendAsync(request);
return true;
}
catch (Exception ex)
{
CodelyLogger.LogWarning($"Codely EventTracker: {messageType} invoke failed: {ex.Message}");
return false;
}
}
private static int ResolveTauriPort(int tauriServerPort)
{
if (tauriServerPort > 0)
{
return tauriServerPort;
}
return SessionState.GetInt(TauriUtils.SESSION_STATE_TAURI_PORT_KEY, -1);
}
private static void AddInvokeRoutingHeaders(
HttpRequestMessage request,
string workspaceDir,
string editorType)
{
if (!string.IsNullOrEmpty(workspaceDir))
{
request.Headers.TryAddWithoutValidation(
"X-Workspace-Dir",
Uri.EscapeDataString(workspaceDir));
}
if (!string.IsNullOrEmpty(editorType))
{
request.Headers.TryAddWithoutValidation("X-Editor-Type", editorType);
}
}
private static string BuildInvokePayload(
string messageType,
string messageId,
string workspaceDir,
string editorType,
string dataJson = null)
{
string typeEscaped = EscapeJson(messageType);
string idEscaped = EscapeJson(messageId);
string dirEscaped = EscapeJson(workspaceDir ?? string.Empty);
string editorEscaped = EscapeJson(editorType ?? "unity");
string dataField = string.IsNullOrEmpty(dataJson) ? "null" : dataJson;
return $"{{\"message\":{{\"messageType\":\"{typeEscaped}\",\"messageId\":\"{idEscaped}\",\"data\":{dataField}}},\"workspaceDir\":\"{dirEscaped}\",\"editorType\":\"{editorEscaped}\"}}";
}
private static string BuildPayload(string eventType, string data, string userId = null)
{
var sb = new StringBuilder();
sb.Append("{\"event_type\":\"").Append(EscapeJson(eventType)).Append("\"");
sb.Append(",\"timestamp\":\"").Append(DateTimeOffset.UtcNow.ToString("o")).Append("\"");
if (!string.IsNullOrEmpty(userId))
{
sb.Append(",\"user_id\":\"").Append(EscapeJson(userId)).Append("\"");
}
if (!string.IsNullOrEmpty(data))
{
sb.Append(",\"data\":\"").Append(EscapeJson(data)).Append("\"");
}
sb.Append("}");
return sb.ToString();
}
private static string EscapeJson(string s)
{
if (string.IsNullOrEmpty(s)) return string.Empty;
var sb = new StringBuilder(s.Length);
foreach (char c in s)
{
switch (c)
{
case '"': sb.Append("\\\""); break;
case '\\': sb.Append("\\\\"); break;
case '\n': sb.Append("\\n"); break;
case '\r': sb.Append("\\r"); break;
case '\t': sb.Append("\\t"); break;
default:
if (c < 0x20) sb.AppendFormat("\\u{0:x4}", (int)c);
else sb.Append(c);
break;
}
}
return sb.ToString();
}
}
}
#endif