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

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

275 lines
9.2 KiB
C#

#if UNITY_EDITOR_WIN || UNITY_EDITOR_OSX
using UnityEditor;
using UnityEngine;
using UnityTcp.Editor.Helpers;
namespace UnityTcp.Editor.Native
{
/// <summary>
/// A utility EditorWindow shown during offscreen streaming.
/// Displays a message that the editor is hidden
/// and provides a button to disconnect and restore the editor.
///
/// The native window handle is excluded from hide operations in
/// NativeWindowBridgeHost, so it remains visible while other Unity
/// windows are hidden.
///
/// Focus() calls triggered by popup creation target specific Unity
/// ContainerWindows (not all windows), so this mask window is NOT
/// brought to front by those calls.
/// </summary>
internal class StreamingMaskWindow : EditorWindow
{
private static StreamingMaskWindow s_Instance;
private static bool s_ClosingFromCode;
private GUIStyle _titleStyle;
private GUIStyle _messageStyle;
private GUIStyle _buttonStyle;
private bool _stylesInitialized;
private const float kWindowWidth = 380f;
private const float kWindowHeight = 170f;
#if TUANJIE_1_OR_NEWER || TUANJIE_1
private const string kEditorName = "Tuanjie Editor";
#else
private const string kEditorName = "Unity Editor";
#endif
internal static string WindowTitle => kEditorName + " - Streaming";
internal static StreamingMaskWindow TryGetInstance() => s_Instance;
internal static StreamingMaskWindow ShowMask()
{
if (s_Instance != null)
{
ApplyBottomRightPosition(s_Instance);
s_Instance.ShowUtility();
CodelyLogger.Log("[StreamingMask] Mask window re-shown");
return s_Instance;
}
// After domain reload, s_Instance is null but a serialized
// EditorWindow instance may still exist. Reuse it — Close()+create
// races OnDestroy (Unity Close is often deferred) and the dying
// window would clear the new s_Instance after a few reconnects.
var existing = Resources.FindObjectsOfTypeAll<StreamingMaskWindow>();
StreamingMaskWindow first = null;
foreach (var w in existing)
{
if (w == null) continue;
if (first == null)
{
first = w;
continue;
}
// Extra domain-reload leftovers. Close is deferred, but
// OnDestroy only clears s_Instance when s_Instance == this.
try { w.Close(); }
catch (System.Exception) { }
}
if (first != null)
{
s_Instance = first;
first.titleContent = new GUIContent(WindowTitle);
first.minSize = new Vector2(kWindowWidth, kWindowHeight);
first.maxSize = new Vector2(kWindowWidth, kWindowHeight);
ApplyBottomRightPosition(first);
first.ShowUtility();
CodelyLogger.Log("[StreamingMask] Mask window reused after domain reload");
return first;
}
var win = CreateInstance<StreamingMaskWindow>();
win.titleContent = new GUIContent(WindowTitle);
win.minSize = new Vector2(kWindowWidth, kWindowHeight);
win.maxSize = new Vector2(kWindowWidth, kWindowHeight);
win.ShowUtility();
ApplyBottomRightPosition(win);
s_Instance = win;
CodelyLogger.Log("[StreamingMask] Mask window shown");
return win;
}
internal static void ApplyBottomRightPosition(EditorWindow win)
{
if (win == null) return;
// Bottom-right of the primary screen, just above the taskbar.
// Screen.currentResolution is physical pixels; EditorWindow.position is logical.
const float kTaskbarHeight = 48f;
const float kBottomMargin = 8f;
float dpiScale = EditorGUIUtility.pixelsPerPoint;
float logicalScreenW = Screen.currentResolution.width / dpiScale;
float logicalScreenH = Screen.currentResolution.height / dpiScale;
win.position = new Rect(
logicalScreenW - kWindowWidth,
logicalScreenH - kTaskbarHeight - kWindowHeight - kBottomMargin,
kWindowWidth,
kWindowHeight);
}
/// <summary>
/// Programmatically close the mask window (called from StopOffscreenCapture).
/// Sets a guard flag to prevent OnDestroy from triggering a recursive stop.
/// </summary>
internal static void HideMask()
{
s_ClosingFromCode = true;
try
{
if (s_Instance != null)
{
s_Instance.Close();
}
// Domain reload can clear s_Instance while the serialized
// EditorWindow object still exists. Close any such zombies.
foreach (var w in Resources.FindObjectsOfTypeAll<StreamingMaskWindow>())
{
if (w != null && w != s_Instance)
{
w.Close();
}
}
}
catch (System.Exception ex)
{
CodelyLogger.LogWarning($"[StreamingMask] HideMask close failed: {ex.Message}");
}
finally
{
s_ClosingFromCode = false;
s_Instance = null;
}
CodelyLogger.Log("[StreamingMask] Mask window hidden");
}
internal static bool IsActive => s_Instance != null;
private void OnGUI()
{
InitStyles();
GUILayout.FlexibleSpace();
EditorGUILayout.LabelField(kEditorName, _titleStyle);
EditorGUILayout.Space(8);
EditorGUILayout.LabelField(
"Editor is hidden while streaming via Codely Cowork.",
_messageStyle);
EditorGUILayout.Space(16);
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
if (GUILayout.Button(
"Disconnect & Restore",
_buttonStyle,
GUILayout.Width(200),
GUILayout.Height(32)))
{
EditorApplication.delayCall += () =>
{
NativeWindowBridgeHost.StopOffscreenCapture();
};
}
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
GUILayout.FlexibleSpace();
}
private void OnDestroy()
{
// Close() is often deferred. A dying leftover instance must not
// clear a newer s_Instance or stop an active stream.
if (s_Instance != this)
return;
s_Instance = null;
if (s_ClosingFromCode)
return;
// User closed the mask window manually (e.g. clicked X).
// Treat this as a disconnect request.
if (NativeWindowBridgeHost.IsOffscreenActive)
{
CodelyLogger.Log("[StreamingMask] User closed mask window, stopping offscreen capture");
EditorApplication.delayCall += () =>
{
NativeWindowBridgeHost.StopOffscreenCapture();
};
}
}
private void InitStyles()
{
if (_stylesInitialized) return;
_stylesInitialized = true;
_titleStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 18,
alignment = TextAnchor.MiddleCenter
};
_messageStyle = new GUIStyle(EditorStyles.label)
{
fontSize = 12,
alignment = TextAnchor.MiddleCenter,
wordWrap = true,
normal = { textColor = new Color(0.7f, 0.7f, 0.7f) }
};
_buttonStyle = new GUIStyle(GUI.skin.button)
{
fontSize = 13,
fontStyle = FontStyle.Bold,
padding = new RectOffset(16, 16, 6, 6)
};
}
}
}
#else
using UnityEditor;
namespace UnityTcp.Editor.Native
{
/// <summary>
/// No-op placeholder for non-Windows/macOS editor platforms.
/// This keeps cross-platform compilation valid when shared code
/// references StreamingMaskWindow in common paths.
/// </summary>
internal class StreamingMaskWindow : EditorWindow
{
internal static string WindowTitle => "Editor - Streaming";
internal static StreamingMaskWindow TryGetInstance() => null;
internal static StreamingMaskWindow ShowMask()
{
return null;
}
internal static void ApplyBottomRightPosition(EditorWindow win)
{
}
internal static void HideMask()
{
// Intentionally empty on unsupported platforms.
}
internal static bool IsActive
{
get { return false; }
}
}
}
#endif