Merge remote-tracking branch 'gitea/master' into thr_dev/dev2
# Conflicts: # Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMemberMetaDataSet.cs
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1e580c430b3308419647d63c73b21c2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace XericUI.Form
|
||||
{
|
||||
/// <summary>
|
||||
/// 自动隐藏滑动方向。
|
||||
/// 窗口贴边后可沿此方向滑出屏幕,鼠标进入对应边缘区域后滑回。
|
||||
/// </summary>
|
||||
public enum AutoHideDirection
|
||||
{
|
||||
Left,
|
||||
Right,
|
||||
Up,
|
||||
Down
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6441e2d1b03e7a942a1c958f979e9d3d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,39 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace XericUI.Form
|
||||
{
|
||||
/// <summary>
|
||||
/// 窗体悬停目标接口。
|
||||
/// 实现此接口的组件可注册到 UIFormManager,由管理器统一检测鼠标悬停状态。
|
||||
/// </summary>
|
||||
public interface IUIFormTarget
|
||||
{
|
||||
/// <summary>
|
||||
/// 进入范围(屏幕空间 Rect,原点为左下角,单位为像素)。
|
||||
/// 鼠标进入此范围时触发 OnPointerEnter。
|
||||
/// </summary>
|
||||
Rect GetEnterRect();
|
||||
|
||||
/// <summary>
|
||||
/// 退出范围(屏幕空间 Rect,原点为左下角,单位为像素)。
|
||||
/// 应大于等于 EnterRect,鼠标离开此范围时触发 OnPointerExit,避免边界抖动。
|
||||
/// </summary>
|
||||
Rect GetExitRect();
|
||||
|
||||
/// <summary>
|
||||
/// 鼠标进入范围内时由 UIFormManager 调用。
|
||||
/// </summary>
|
||||
void OnPointerEnter();
|
||||
|
||||
/// <summary>
|
||||
/// 鼠标离开退出范围时由 UIFormManager 调用。
|
||||
/// </summary>
|
||||
void OnPointerExit();
|
||||
|
||||
/// <summary>
|
||||
/// 此目标是否处于激活状态。
|
||||
/// 返回 false 时管理器跳过此目标的检测。
|
||||
/// </summary>
|
||||
bool IsActive { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 756fa65ea23a49d4ea270f1697aaf89f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,480 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace XericUI.Form
|
||||
{
|
||||
#region 枚举与结构体
|
||||
|
||||
public enum FormRangeSpace
|
||||
{
|
||||
RelativeOffset,
|
||||
CanvasLocal,
|
||||
AnchorToSelf,
|
||||
AnchorToCanvas
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public struct RangeRect
|
||||
{
|
||||
[Tooltip("坐标空间")]
|
||||
public FormRangeSpace space;
|
||||
|
||||
[Tooltip("范围定义")]
|
||||
public Rect rect;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// UI 窗体控制器 — 挂载到窗口根 GameObject 上,自身即为完整窗口。
|
||||
/// 支持全屏/还原、边缘吸附、自动隐藏、拖拽移动、置顶。
|
||||
/// 通过 WindowEntry 与 Manager 通信。
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric UI Vessel/Form/UI Form Controller")]
|
||||
[RequireComponent(typeof(RectTransform))]
|
||||
public class UIFormController : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler, IPointerDownHandler
|
||||
{
|
||||
#region 序列化字段
|
||||
|
||||
[Header("窗口基本设置")]
|
||||
[SerializeField, Tooltip("窗口标题")]
|
||||
private string m_WindowTitle = "Window";
|
||||
|
||||
[Header("边缘吸附")]
|
||||
[SerializeField, Tooltip("启用边缘自动吸附")]
|
||||
private bool m_EnableAutoSnap = true;
|
||||
|
||||
[SerializeField, Tooltip("吸附阈值(像素)")]
|
||||
private float m_SnapThreshold = 8f;
|
||||
|
||||
[Header("自动隐藏")]
|
||||
[SerializeField, Tooltip("启用自动隐藏")]
|
||||
private bool m_EnableAutoHide;
|
||||
|
||||
[SerializeField, Tooltip("自动隐藏滑动方向")]
|
||||
private AutoHideDirection m_AutoHideDirection = AutoHideDirection.Left;
|
||||
|
||||
[Header("调试")]
|
||||
[SerializeField, Tooltip("编辑器调试模式")]
|
||||
private bool m_DebugMode = true;
|
||||
|
||||
[SerializeField, Tooltip("输出事件日志")]
|
||||
private bool m_DebugLogEvents;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 公开属性
|
||||
|
||||
public string WindowTitle => m_WindowTitle;
|
||||
public bool IsMaximized => _windowState == WindowState.Maximized;
|
||||
public bool IsEdgeHidden => _windowState == WindowState.EdgeHidden;
|
||||
public WindowState CurrentState => _windowState;
|
||||
public bool AutoSnapEnabled => m_EnableAutoSnap;
|
||||
public bool AutoHideEnabled => m_EnableAutoHide;
|
||||
public AutoHideDirection HideDirection => m_AutoHideDirection;
|
||||
public bool DebugMode => m_DebugMode;
|
||||
|
||||
/// <summary>窗口数据 Entry(由 Manager 在 CreateWindow 时注入)</summary>
|
||||
public WindowEntry Entry
|
||||
{
|
||||
get => _entry;
|
||||
set
|
||||
{
|
||||
_entry = value;
|
||||
if (_entry != null)
|
||||
m_WindowTitle = _entry.Title;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>父级 Manager 引用</summary>
|
||||
public UIFormManager Manager => _manager;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 内部状态
|
||||
|
||||
private RectTransform _rectTransform;
|
||||
private Canvas _canvas;
|
||||
private RectTransform _canvasRt;
|
||||
private UIFormManager _manager;
|
||||
private WindowEntry _entry;
|
||||
|
||||
private WindowState _windowState = WindowState.Normal;
|
||||
|
||||
// 拖拽
|
||||
private Vector2 _dragOffset;
|
||||
|
||||
// 全屏还原
|
||||
private Vector2 _savePos;
|
||||
private Vector2 _saveSize;
|
||||
private Vector2 _saveAnchorMin;
|
||||
private Vector2 _saveAnchorMax;
|
||||
|
||||
// 边缘隐藏
|
||||
private Vector2 _edgeVisiblePos;
|
||||
private Vector2 _edgeHiddenPos;
|
||||
private Coroutine _autoHideCoroutine;
|
||||
|
||||
// 悬停
|
||||
private bool _isHovering;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 生命周期
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
_rectTransform = (RectTransform)transform;
|
||||
_manager = GetComponentInParent<UIFormManager>();
|
||||
_canvas = GetComponentInParent<Canvas>();
|
||||
_canvasRt = _canvas != null ? (RectTransform)_canvas.transform : null;
|
||||
}
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
StopAutoHideMonitor();
|
||||
|
||||
if (_windowState == WindowState.EdgeHidden && _manager != null)
|
||||
_manager.UnregisterEdgeHidden(_entry);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
UnsubscribeEditorUpdate();
|
||||
#endif
|
||||
_isHovering = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region === 公开 API ===
|
||||
|
||||
public void ToggleMaximize()
|
||||
{
|
||||
if (IsMaximized)
|
||||
RestoreFromFullscreen();
|
||||
else
|
||||
MaximizeToFullscreen();
|
||||
|
||||
if (_entry != null)
|
||||
_entry.State = _windowState;
|
||||
}
|
||||
|
||||
public void Minimize()
|
||||
{
|
||||
if (_windowState == WindowState.Minimized) return;
|
||||
if (_manager == null || _entry == null) return;
|
||||
_manager.MinimizeWindow(_entry.Id);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (_manager != null && _entry != null)
|
||||
_manager.DestroyWindow(_entry.Id);
|
||||
else if (Application.isPlaying)
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
public void SetAutoSnapEnabled(bool enabled) => m_EnableAutoSnap = enabled;
|
||||
public void SetAutoHideEnabled(bool enabled)
|
||||
{
|
||||
m_EnableAutoHide = enabled;
|
||||
if (!enabled && _windowState == WindowState.EdgeHidden)
|
||||
SlideInFromEdge();
|
||||
}
|
||||
|
||||
public void SetAutoHideDirection(AutoHideDirection direction) => m_AutoHideDirection = direction;
|
||||
|
||||
/// <summary>置顶窗口</summary>
|
||||
public void MoveToFront()
|
||||
{
|
||||
transform.SetAsLastSibling();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region === 全屏/还原 ===
|
||||
|
||||
private void MaximizeToFullscreen()
|
||||
{
|
||||
if (_windowState == WindowState.Maximized) return;
|
||||
|
||||
_savePos = _rectTransform.anchoredPosition;
|
||||
_saveSize = _rectTransform.sizeDelta;
|
||||
_saveAnchorMin = _rectTransform.anchorMin;
|
||||
_saveAnchorMax = _rectTransform.anchorMax;
|
||||
|
||||
_rectTransform.anchorMin = Vector2.zero;
|
||||
_rectTransform.anchorMax = Vector2.one;
|
||||
_rectTransform.offsetMin = Vector2.zero;
|
||||
_rectTransform.offsetMax = Vector2.zero;
|
||||
|
||||
_windowState = WindowState.Maximized;
|
||||
}
|
||||
|
||||
private void RestoreFromFullscreen()
|
||||
{
|
||||
if (_windowState != WindowState.Maximized) return;
|
||||
|
||||
_rectTransform.anchorMin = _saveAnchorMin;
|
||||
_rectTransform.anchorMax = _saveAnchorMax;
|
||||
_rectTransform.anchoredPosition = _savePos;
|
||||
_rectTransform.sizeDelta = _saveSize;
|
||||
|
||||
_windowState = WindowState.Normal;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region === 拖拽 & 点击聚焦 ===
|
||||
|
||||
public void OnPointerDown(PointerEventData eventData)
|
||||
{
|
||||
// 点击窗口任意位置 → 置顶
|
||||
transform.SetAsLastSibling();
|
||||
if (_manager != null && _entry != null)
|
||||
_manager.MoveToFront(_entry.Id);
|
||||
}
|
||||
|
||||
public void OnBeginDrag(PointerEventData eventData)
|
||||
{
|
||||
if (_windowState == WindowState.EdgeHidden || _windowState == WindowState.Minimized) return;
|
||||
|
||||
if (IsMaximized)
|
||||
{
|
||||
RestoreFromFullscreen();
|
||||
_rectTransform.anchoredPosition = eventData.position;
|
||||
}
|
||||
|
||||
_dragOffset = _rectTransform.anchoredPosition - eventData.position;
|
||||
}
|
||||
|
||||
public void OnDrag(PointerEventData eventData)
|
||||
{
|
||||
if (_windowState == WindowState.EdgeHidden || _windowState == WindowState.Minimized) return;
|
||||
|
||||
_rectTransform.anchoredPosition = eventData.position + _dragOffset;
|
||||
|
||||
if (m_EnableAutoSnap)
|
||||
CheckEdgeSnap();
|
||||
}
|
||||
|
||||
public void OnEndDrag(PointerEventData eventData)
|
||||
{
|
||||
if (_windowState == WindowState.EdgeHidden || _windowState == WindowState.Minimized) return;
|
||||
|
||||
if (m_EnableAutoHide && IsSnappedToEdge())
|
||||
AutoHideToEdge();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region === 边缘吸附 ===
|
||||
|
||||
private void CheckEdgeSnap()
|
||||
{
|
||||
if (_canvasRt == null) return;
|
||||
Vector2 canvasSize = _canvasRt.rect.size;
|
||||
Vector2 pos = _rectTransform.anchoredPosition;
|
||||
Vector2 size = _rectTransform.rect.size;
|
||||
|
||||
if (Mathf.Abs(pos.x) < m_SnapThreshold)
|
||||
pos.x = 0;
|
||||
else if (Mathf.Abs(pos.x + size.x - canvasSize.x) < m_SnapThreshold)
|
||||
pos.x = canvasSize.x - size.x;
|
||||
|
||||
_rectTransform.anchoredPosition = pos;
|
||||
}
|
||||
|
||||
private bool IsSnappedToEdge()
|
||||
{
|
||||
if (_canvasRt == null) return false;
|
||||
Vector2 canvasSize = _canvasRt.rect.size;
|
||||
Vector2 pos = _rectTransform.anchoredPosition;
|
||||
Vector2 size = _rectTransform.rect.size;
|
||||
return Mathf.Abs(pos.x) < 1f || Mathf.Abs(pos.x + size.x - canvasSize.x) < 1f;
|
||||
}
|
||||
|
||||
public AutoHideDirection GetClosestEdge()
|
||||
{
|
||||
if (_canvasRt == null) return AutoHideDirection.Left;
|
||||
Vector2 canvasSize = _canvasRt.rect.size;
|
||||
Vector2 pos = _rectTransform.anchoredPosition;
|
||||
Vector2 size = _rectTransform.rect.size;
|
||||
float distLeft = Mathf.Abs(pos.x);
|
||||
float distRight = Mathf.Abs(pos.x + size.x - canvasSize.x);
|
||||
return distLeft < distRight ? AutoHideDirection.Left : AutoHideDirection.Right;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region === 自动隐藏 ===
|
||||
|
||||
private void AutoHideToEdge()
|
||||
{
|
||||
if (_windowState == WindowState.EdgeHidden || _canvasRt == null) return;
|
||||
|
||||
Vector2 canvasSize = _canvasRt.rect.size;
|
||||
Vector2 pos = _rectTransform.anchoredPosition;
|
||||
Vector2 size = _rectTransform.rect.size;
|
||||
|
||||
_edgeVisiblePos = pos;
|
||||
|
||||
AutoHideDirection dir = m_AutoHideDirection;
|
||||
if (dir == AutoHideDirection.Left || dir == AutoHideDirection.Right)
|
||||
dir = GetClosestEdge();
|
||||
|
||||
_edgeHiddenPos = dir switch
|
||||
{
|
||||
AutoHideDirection.Left => new Vector2(-size.x, pos.y),
|
||||
AutoHideDirection.Right => new Vector2(canvasSize.x, pos.y),
|
||||
AutoHideDirection.Down => new Vector2(pos.x, -size.y),
|
||||
AutoHideDirection.Up => new Vector2(pos.x, canvasSize.y),
|
||||
_ => pos
|
||||
};
|
||||
|
||||
_rectTransform.anchoredPosition = _edgeHiddenPos;
|
||||
_windowState = WindowState.EdgeHidden;
|
||||
|
||||
if (_manager != null && _entry != null)
|
||||
_manager.RegisterEdgeHidden(_entry, dir);
|
||||
}
|
||||
|
||||
public void SlideInFromEdge()
|
||||
{
|
||||
if (_windowState != WindowState.EdgeHidden) return;
|
||||
StopAutoHideMonitor();
|
||||
_rectTransform.anchoredPosition = _edgeVisiblePos;
|
||||
_windowState = WindowState.Normal;
|
||||
|
||||
if (_manager != null)
|
||||
_manager.UnregisterEdgeHidden(_entry);
|
||||
|
||||
_autoHideCoroutine = StartCoroutine(MonitorMouseExit());
|
||||
}
|
||||
|
||||
private IEnumerator MonitorMouseExit()
|
||||
{
|
||||
while (_windowState == WindowState.Normal && m_EnableAutoHide)
|
||||
{
|
||||
yield return null;
|
||||
Rect screenRect = GetWindowScreenRect();
|
||||
if (!screenRect.Contains(Input.mousePosition))
|
||||
{
|
||||
AutoHideToEdge();
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void StopAutoHideMonitor()
|
||||
{
|
||||
if (_autoHideCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_autoHideCoroutine);
|
||||
_autoHideCoroutine = null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region === 窗口屏幕范围 ===
|
||||
|
||||
public Rect GetWindowScreenRect()
|
||||
{
|
||||
if (_rectTransform == null || _canvas == null) return Rect.zero;
|
||||
Vector3[] corners = new Vector3[4];
|
||||
_rectTransform.GetWorldCorners(corners);
|
||||
Camera cam = _canvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : _canvas.worldCamera;
|
||||
Vector2 min = Vector2.positiveInfinity, max = Vector2.negativeInfinity;
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
Vector2 sp = cam != null
|
||||
? RectTransformUtility.WorldToScreenPoint(cam, corners[i])
|
||||
: (Vector2)corners[i];
|
||||
min = Vector2.Min(min, sp);
|
||||
max = Vector2.Max(max, sp);
|
||||
}
|
||||
return new Rect(min.x, min.y, max.x - min.x, max.y - min.y);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 编辑器调试
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[System.NonSerialized] private bool _editorDebugSubscribed;
|
||||
[System.NonSerialized] private Vector2 _editorMouseScreenPos;
|
||||
|
||||
private void SubscribeEditorUpdate()
|
||||
{
|
||||
if (_editorDebugSubscribed) return;
|
||||
UnityEditor.SceneView.duringSceneGui += OnSceneGUI;
|
||||
_editorDebugSubscribed = true;
|
||||
}
|
||||
|
||||
private void UnsubscribeEditorUpdate()
|
||||
{
|
||||
if (!_editorDebugSubscribed) return;
|
||||
UnityEditor.SceneView.duringSceneGui -= OnSceneGUI;
|
||||
_editorDebugSubscribed = false;
|
||||
}
|
||||
|
||||
protected virtual void OnValidate()
|
||||
{
|
||||
if (m_DebugMode) SubscribeEditorUpdate();
|
||||
else UnsubscribeEditorUpdate();
|
||||
}
|
||||
|
||||
private void OnSceneGUI(UnityEditor.SceneView sceneView)
|
||||
{
|
||||
if (this == null) { UnsubscribeEditorUpdate(); return; }
|
||||
if (Application.isPlaying || !m_DebugMode) return;
|
||||
|
||||
Vector2 guiPos = Event.current.mousePosition;
|
||||
_editorMouseScreenPos = GUIUtility.GUIToScreenPoint(guiPos);
|
||||
|
||||
Rect rect = GetWindowScreenRect();
|
||||
_isHovering = rect.Contains(_editorMouseScreenPos);
|
||||
}
|
||||
|
||||
protected virtual void OnDrawGizmosSelected()
|
||||
{
|
||||
if (!m_DebugMode || _canvas == null || _canvasRt == null) return;
|
||||
Rect screenRect = GetWindowScreenRect();
|
||||
if (screenRect.width <= 0 || screenRect.height <= 0) return;
|
||||
|
||||
Color borderColor = _isHovering
|
||||
? new Color(0f, 1f, 0f, 0.6f)
|
||||
: new Color(0f, 0.7f, 1f, 0.3f);
|
||||
|
||||
DrawScreenRectOnCanvas(screenRect, borderColor, _canvas, _canvasRt);
|
||||
}
|
||||
|
||||
private static void DrawScreenRectOnCanvas(Rect screenRect, Color color, Canvas canvas, RectTransform canvasRt)
|
||||
{
|
||||
if (screenRect.width <= 0 || screenRect.height <= 0) return;
|
||||
Gizmos.color = color;
|
||||
Camera uiCam = canvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : canvas.worldCamera;
|
||||
Vector3 p0 = Stw(new Vector3(screenRect.xMin, screenRect.yMin), uiCam, canvasRt);
|
||||
Vector3 p1 = Stw(new Vector3(screenRect.xMax, screenRect.yMin), uiCam, canvasRt);
|
||||
Vector3 p2 = Stw(new Vector3(screenRect.xMax, screenRect.yMax), uiCam, canvasRt);
|
||||
Vector3 p3 = Stw(new Vector3(screenRect.xMin, screenRect.yMax), uiCam, canvasRt);
|
||||
Gizmos.DrawLine(p0, p1); Gizmos.DrawLine(p1, p2);
|
||||
Gizmos.DrawLine(p2, p3); Gizmos.DrawLine(p3, p0);
|
||||
}
|
||||
|
||||
private static Vector3 Stw(Vector3 sp, Camera cam, RectTransform rt)
|
||||
{
|
||||
RectTransformUtility.ScreenPointToWorldPointInRectangle(rt, (Vector2)sp, cam, out Vector3 wp);
|
||||
return wp;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ffb69c61a83ce7b4ca1eadba0f145096
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,91 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace XericUI.Form
|
||||
{
|
||||
/// <summary>
|
||||
/// 任务栏控制器 — 监听 UIFormManager 的窗口创建/销毁/状态变更事件,
|
||||
/// 动态创建/销毁 UITaskBarItem 图标。
|
||||
/// </summary>
|
||||
public class UITaskBar : MonoBehaviour
|
||||
{
|
||||
[Header("绑定")]
|
||||
[SerializeField, Tooltip("窗口管理器引用")]
|
||||
private UIFormManager m_Manager;
|
||||
|
||||
[SerializeField, Tooltip("任务栏图标项预制体")]
|
||||
private UITaskBarItem m_ItemPrefab;
|
||||
|
||||
[SerializeField, Tooltip("图标项的父容器")]
|
||||
private Transform m_ItemsContainer;
|
||||
|
||||
private readonly Dictionary<string, UITaskBarItem> _items = new Dictionary<string, UITaskBarItem>();
|
||||
|
||||
#region 生命周期
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (m_Manager == null)
|
||||
{
|
||||
Debug.LogError("[UITaskBar] 未绑定 UIFormManager!", this);
|
||||
return;
|
||||
}
|
||||
|
||||
m_Manager.OnWindowCreated += OnWindowCreated;
|
||||
m_Manager.OnWindowDestroyed += OnWindowDestroyed;
|
||||
m_Manager.OnWindowStateChanged += OnWindowStateChanged;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (m_Manager != null)
|
||||
{
|
||||
m_Manager.OnWindowCreated -= OnWindowCreated;
|
||||
m_Manager.OnWindowDestroyed -= OnWindowDestroyed;
|
||||
m_Manager.OnWindowStateChanged -= OnWindowStateChanged;
|
||||
}
|
||||
|
||||
foreach (var item in _items.Values)
|
||||
{
|
||||
if (item != null)
|
||||
Destroy(item.gameObject);
|
||||
}
|
||||
_items.Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 事件回调
|
||||
|
||||
private void OnWindowCreated(WindowEntry entry)
|
||||
{
|
||||
if (entry == null || !entry.ShowInTaskBar) return;
|
||||
if (m_ItemPrefab == null || m_ItemsContainer == null) return;
|
||||
|
||||
var item = Instantiate(m_ItemPrefab, m_ItemsContainer);
|
||||
item.Bind(entry, m_Manager);
|
||||
_items[entry.Id] = item;
|
||||
}
|
||||
|
||||
private void OnWindowDestroyed(WindowEntry entry)
|
||||
{
|
||||
if (entry == null) return;
|
||||
|
||||
if (_items.TryGetValue(entry.Id, out var item))
|
||||
{
|
||||
if (item != null)
|
||||
Destroy(item.gameObject);
|
||||
_items.Remove(entry.Id);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnWindowStateChanged(WindowEntry entry)
|
||||
{
|
||||
if (entry == null) return;
|
||||
|
||||
// 状态变更时触发 Repaint(进度、高亮等由 Item 自己的 Update 处理)
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 08764fa4decbd8b4b9f16f7236d36baf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,108 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace XericUI.Form
|
||||
{
|
||||
/// <summary>
|
||||
/// 任务栏图标项 — 显示单个窗口的图标、标题、进度条和聚焦高亮。
|
||||
/// 点击时通过 UIFormManager 切换窗口显示/隐藏并置顶。
|
||||
/// </summary>
|
||||
public class UITaskBarItem : MonoBehaviour
|
||||
{
|
||||
[Header("控件引用")]
|
||||
[SerializeField, Tooltip("窗口图标")]
|
||||
private Image m_Icon;
|
||||
|
||||
[SerializeField, Tooltip("窗口标题文本")]
|
||||
private Text m_TitleText;
|
||||
|
||||
[SerializeField, Tooltip("进度条 Image(fillAmount)")]
|
||||
private Image m_ProgressBar;
|
||||
|
||||
[SerializeField, Tooltip("聚焦高亮 Image")]
|
||||
private Image m_Highlight;
|
||||
|
||||
[SerializeField, Tooltip("按钮组件")]
|
||||
private Button m_Button;
|
||||
|
||||
private WindowEntry _entry;
|
||||
private UIFormManager _manager;
|
||||
|
||||
public WindowEntry Entry => _entry;
|
||||
|
||||
#region 初始化
|
||||
|
||||
public void Bind(WindowEntry entry, UIFormManager manager)
|
||||
{
|
||||
_entry = entry;
|
||||
_manager = manager;
|
||||
|
||||
if (m_Icon != null && entry.Icon != null)
|
||||
m_Icon.sprite = entry.Icon;
|
||||
|
||||
if (m_Icon != null)
|
||||
m_Icon.color = entry.TintColor;
|
||||
|
||||
if (m_TitleText != null)
|
||||
m_TitleText.text = entry.Title;
|
||||
|
||||
if (m_Button != null)
|
||||
m_Button.onClick.AddListener(OnClick);
|
||||
|
||||
RefreshHighlight();
|
||||
RefreshProgress();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (m_Button != null)
|
||||
m_Button.onClick.RemoveListener(OnClick);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 交互
|
||||
|
||||
private void OnClick()
|
||||
{
|
||||
if (_entry == null || _manager == null) return;
|
||||
|
||||
_manager.ToggleWindow(_entry.Id);
|
||||
_manager.MoveToFront(_entry.Id);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 每帧同步
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_entry == null) return;
|
||||
|
||||
if (m_TitleText != null && m_TitleText.text != _entry.Title)
|
||||
m_TitleText.text = _entry.Title;
|
||||
|
||||
if (m_Icon != null && m_Icon.color != _entry.TintColor)
|
||||
m_Icon.color = _entry.TintColor;
|
||||
|
||||
RefreshProgress();
|
||||
RefreshHighlight();
|
||||
}
|
||||
|
||||
private void RefreshProgress()
|
||||
{
|
||||
if (m_ProgressBar == null) return;
|
||||
m_ProgressBar.fillAmount = _entry.Progress;
|
||||
m_ProgressBar.gameObject.SetActive(_entry.ShowProgress && _entry.Progress > 0f);
|
||||
}
|
||||
|
||||
private void RefreshHighlight()
|
||||
{
|
||||
if (m_Highlight == null || _manager == null) return;
|
||||
var focused = _manager.GetFocusedEntry();
|
||||
m_Highlight.gameObject.SetActive(focused == _entry);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bf298c8b67bb4d345a69728112b72229
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,159 @@
|
||||
#if dUI_TextMeshPro
|
||||
using UI_Text = TMPro.TextMeshProUGUI;
|
||||
#else
|
||||
using UI_Text = UnityEngine.UI.Text;
|
||||
#endif
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace XericUI.Form
|
||||
{
|
||||
/// <summary>
|
||||
/// 窗口标题栏 — 独立子组件,挂载在 UIFormController 的子对象上。
|
||||
/// Start 时向上查找父级 UIFormController,绑定按钮/Toggle 事件到其公开 API。
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric UI Vessel/Form/UI Title Bar")]
|
||||
public class UITitleBar : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
|
||||
{
|
||||
[Header("控件引用")]
|
||||
[SerializeField, Tooltip("窗口图标")]
|
||||
private Image m_IconImage;
|
||||
|
||||
[SerializeField, Tooltip("标题文本")]
|
||||
private UI_Text m_TitleText;
|
||||
|
||||
[SerializeField, Tooltip("关闭按钮")]
|
||||
private Button m_CloseButton;
|
||||
|
||||
[SerializeField, Tooltip("最大化 Toggle(isOn=全屏)")]
|
||||
private Toggle m_MaximizeToggle;
|
||||
|
||||
[SerializeField, Tooltip("最小化按钮")]
|
||||
private Button m_MinimizeButton;
|
||||
|
||||
[SerializeField, Tooltip("自动吸附 Toggle")]
|
||||
private Toggle m_AutoSnapToggle;
|
||||
|
||||
[SerializeField, Tooltip("自动隐藏 Toggle")]
|
||||
private Toggle m_AutoHideToggle;
|
||||
|
||||
private UIFormController _controller;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_controller = GetComponentInParent<UIFormController>();
|
||||
|
||||
if (_controller == null)
|
||||
{
|
||||
Debug.LogWarning($"[UITitleBar] 未在父级找到 UIFormController: {name}", this);
|
||||
return;
|
||||
}
|
||||
|
||||
BindEvents();
|
||||
RefreshUI();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_controller == null) return;
|
||||
|
||||
if (m_TitleText != null && m_TitleText.text != _controller.WindowTitle)
|
||||
m_TitleText.text = _controller.WindowTitle;
|
||||
|
||||
// 同步最大化 Toggle 状态
|
||||
if (m_MaximizeToggle != null)
|
||||
{
|
||||
bool maxed = _controller.IsMaximized;
|
||||
if (m_MaximizeToggle.isOn != maxed)
|
||||
m_MaximizeToggle.SetIsOnWithoutNotify(maxed);
|
||||
}
|
||||
|
||||
// 同步图标
|
||||
if (m_IconImage != null && _controller.Entry != null)
|
||||
{
|
||||
if (_controller.Entry.Icon != null && m_IconImage.sprite != _controller.Entry.Icon)
|
||||
m_IconImage.sprite = _controller.Entry.Icon;
|
||||
if (m_IconImage.color != _controller.Entry.TintColor)
|
||||
m_IconImage.color = _controller.Entry.TintColor;
|
||||
}
|
||||
}
|
||||
|
||||
#region 事件绑定
|
||||
|
||||
private void BindEvents()
|
||||
{
|
||||
if (m_CloseButton != null)
|
||||
m_CloseButton.onClick.AddListener(() => _controller.Close());
|
||||
|
||||
if (m_MaximizeToggle != null)
|
||||
m_MaximizeToggle.onValueChanged.AddListener(_ => _controller.ToggleMaximize());
|
||||
|
||||
if (m_MinimizeButton != null)
|
||||
m_MinimizeButton.onClick.AddListener(() => _controller.Minimize());
|
||||
|
||||
if (m_AutoSnapToggle != null)
|
||||
{
|
||||
m_AutoSnapToggle.isOn = _controller.AutoSnapEnabled;
|
||||
m_AutoSnapToggle.onValueChanged.AddListener(v => _controller.SetAutoSnapEnabled(v));
|
||||
}
|
||||
|
||||
if (m_AutoHideToggle != null)
|
||||
{
|
||||
m_AutoHideToggle.isOn = _controller.AutoHideEnabled;
|
||||
m_AutoHideToggle.onValueChanged.AddListener(v => _controller.SetAutoHideEnabled(v));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 拖拽转发
|
||||
|
||||
public void OnBeginDrag(PointerEventData eventData)
|
||||
{
|
||||
if (_controller != null)
|
||||
_controller.OnBeginDrag(eventData);
|
||||
}
|
||||
|
||||
public void OnDrag(PointerEventData eventData)
|
||||
{
|
||||
if (_controller != null)
|
||||
_controller.OnDrag(eventData);
|
||||
}
|
||||
|
||||
public void OnEndDrag(PointerEventData eventData)
|
||||
{
|
||||
if (_controller != null)
|
||||
_controller.OnEndDrag(eventData);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UI 刷新
|
||||
|
||||
private void RefreshUI()
|
||||
{
|
||||
if (m_TitleText != null)
|
||||
m_TitleText.text = _controller.WindowTitle;
|
||||
|
||||
if (m_MaximizeToggle != null)
|
||||
m_MaximizeToggle.isOn = _controller.IsMaximized;
|
||||
|
||||
if (m_AutoSnapToggle != null)
|
||||
m_AutoSnapToggle.isOn = _controller.AutoSnapEnabled;
|
||||
|
||||
if (m_AutoHideToggle != null)
|
||||
m_AutoHideToggle.isOn = _controller.AutoHideEnabled;
|
||||
|
||||
// 图标
|
||||
if (m_IconImage != null && _controller.Entry != null && _controller.Entry.Icon != null)
|
||||
{
|
||||
m_IconImage.sprite = _controller.Entry.Icon;
|
||||
m_IconImage.color = _controller.Entry.TintColor;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f5e6e4624592b3248a4c65c33cd21d5e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace XericUI.Form
|
||||
{
|
||||
/// <summary>
|
||||
/// 窗口当前状态。
|
||||
/// </summary>
|
||||
public enum WindowState
|
||||
{
|
||||
/// <summary>普通窗口化</summary>
|
||||
Normal,
|
||||
|
||||
/// <summary>全屏最大化</summary>
|
||||
Maximized,
|
||||
|
||||
/// <summary>最小化(隐藏并保存在 Manager 中)</summary>
|
||||
Minimized,
|
||||
|
||||
/// <summary>边缘自动隐藏(贴边滑出屏幕)</summary>
|
||||
EdgeHidden
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 692b97f1c71f9414b8bb68dd6ed33718
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,350 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace XericUI.Form
|
||||
{
|
||||
/// <summary>
|
||||
/// UI 窗体管理器 — 窗口管理中心。挂载在 Canvas 下作为所有窗口的父容器。
|
||||
/// 通过 Entry 驱动管理窗口生命周期、层级排序、委托事件发布。
|
||||
/// 支持任务栏和边缘触发隐藏窗口。
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric UI Vessel/Form/UI Form Manager")]
|
||||
[DefaultExecutionOrder(32000)]
|
||||
public class UIFormManager : MonoBehaviour
|
||||
{
|
||||
[Header("边缘触发")]
|
||||
[SerializeField, Tooltip("边缘触发区域大小")]
|
||||
private Vector2 m_EdgeTriggerSize = new Vector2(80, 60);
|
||||
|
||||
[Header("调试")]
|
||||
[SerializeField, Tooltip("输出事件日志")]
|
||||
private bool m_EnableDebugLog;
|
||||
|
||||
#region === 委托事件 ===
|
||||
|
||||
/// <summary>窗口创建</summary>
|
||||
public event WindowEntryEventHandler OnWindowCreated;
|
||||
|
||||
/// <summary>窗口销毁</summary>
|
||||
public event WindowEntryEventHandler OnWindowDestroyed;
|
||||
|
||||
/// <summary>窗口状态变更</summary>
|
||||
public event WindowEntryEventHandler OnWindowStateChanged;
|
||||
|
||||
/// <summary>窗口获得焦点(置顶)</summary>
|
||||
public event WindowEntryEventHandler OnWindowFocused;
|
||||
|
||||
#endregion
|
||||
|
||||
#region === 内部状态 ===
|
||||
|
||||
private readonly Dictionary<string, WindowEntry> _entries = new Dictionary<string, WindowEntry>();
|
||||
private readonly List<WindowEntry> _orderedEntries = new List<WindowEntry>();
|
||||
private string _focusedEntryId;
|
||||
|
||||
// 边缘隐藏(按方向分组)
|
||||
private readonly Dictionary<AutoHideDirection, List<WindowEntry>> _edgeHidden = new()
|
||||
{
|
||||
{ AutoHideDirection.Left, new List<WindowEntry>() },
|
||||
{ AutoHideDirection.Right, new List<WindowEntry>() },
|
||||
{ AutoHideDirection.Up, new List<WindowEntry>() },
|
||||
{ AutoHideDirection.Down, new List<WindowEntry>() },
|
||||
};
|
||||
private AutoHideDirection? _lastEdgeTrigger;
|
||||
|
||||
#endregion
|
||||
|
||||
#region === 生命周期 ===
|
||||
|
||||
protected virtual void Update()
|
||||
{
|
||||
CheckEdgeTriggers();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region === 程序化窗口创建 / 销毁 ===
|
||||
|
||||
/// <summary>程序化创建一个窗口实例并返回其 Entry</summary>
|
||||
public WindowEntry CreateWindow(RectTransform prefab, string title = null, Sprite icon = null)
|
||||
{
|
||||
if (prefab == null)
|
||||
{
|
||||
Debug.LogError("[UIFormManager] CreateWindow: prefab 为空", this);
|
||||
return null;
|
||||
}
|
||||
|
||||
var instance = Instantiate(prefab, transform);
|
||||
instance.name = title ?? prefab.name;
|
||||
|
||||
var controller = instance.GetComponent<UIFormController>();
|
||||
var entry = new WindowEntry
|
||||
{
|
||||
Title = title ?? prefab.name,
|
||||
Icon = icon,
|
||||
Prefab = prefab,
|
||||
Controller = controller,
|
||||
State = WindowState.Normal,
|
||||
};
|
||||
|
||||
if (controller != null)
|
||||
controller.Entry = entry;
|
||||
|
||||
_entries[entry.Id] = entry;
|
||||
_orderedEntries.Add(entry);
|
||||
|
||||
MoveToFront(entry.Id);
|
||||
|
||||
NotifyCreated(entry);
|
||||
|
||||
if (m_EnableDebugLog)
|
||||
Debug.Log($"[UIFormManager] 创建窗口: {entry.Title} (ID={entry.Id})");
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/// <summary>销毁窗口及其所有子窗口</summary>
|
||||
public void DestroyWindow(string entryId)
|
||||
{
|
||||
if (!_entries.TryGetValue(entryId, out var entry)) return;
|
||||
|
||||
// 级联关闭子窗口
|
||||
CascadeCloseChildren(entry);
|
||||
|
||||
UnregisterEdgeHiddenForEntry(entry);
|
||||
|
||||
_orderedEntries.Remove(entry);
|
||||
_entries.Remove(entryId);
|
||||
|
||||
if (_focusedEntryId == entryId)
|
||||
_focusedEntryId = null;
|
||||
|
||||
var controller = entry.Controller;
|
||||
entry.Controller = null;
|
||||
|
||||
NotifyDestroyed(entry);
|
||||
|
||||
if (controller != null && Application.isPlaying)
|
||||
Destroy(controller.gameObject);
|
||||
}
|
||||
|
||||
private void CascadeCloseChildren(WindowEntry entry)
|
||||
{
|
||||
for (int i = entry.ChildEntries.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var child = entry.ChildEntries[i];
|
||||
DestroyWindow(child.Id);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region === 显示控制 ===
|
||||
|
||||
/// <summary>显示窗口并置顶</summary>
|
||||
public void ShowWindow(string entryId)
|
||||
{
|
||||
if (!TryGetEntry(entryId, out var entry)) return;
|
||||
if (entry.Controller == null) return;
|
||||
|
||||
entry.Controller.gameObject.SetActive(true);
|
||||
MoveToFront(entryId);
|
||||
}
|
||||
|
||||
/// <summary>隐藏窗口</summary>
|
||||
public void HideWindow(string entryId)
|
||||
{
|
||||
if (!TryGetEntry(entryId, out var entry)) return;
|
||||
if (entry.Controller == null) return;
|
||||
|
||||
entry.Controller.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
/// <summary>切换窗口显示/隐藏</summary>
|
||||
public void ToggleWindow(string entryId)
|
||||
{
|
||||
if (!TryGetEntry(entryId, out var entry)) return;
|
||||
if (entry.Controller == null) return;
|
||||
|
||||
if (entry.State == WindowState.Minimized)
|
||||
{
|
||||
RestoreWindow(entryId);
|
||||
}
|
||||
else if (entry.Controller.gameObject.activeSelf)
|
||||
{
|
||||
MinimizeWindow(entryId);
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowWindow(entryId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>将窗口置顶</summary>
|
||||
public void MoveToFront(string entryId)
|
||||
{
|
||||
if (!TryGetEntry(entryId, out var entry)) return;
|
||||
if (entry.Controller == null) return;
|
||||
|
||||
entry.Controller.transform.SetAsLastSibling();
|
||||
|
||||
_orderedEntries.Remove(entry);
|
||||
_orderedEntries.Add(entry);
|
||||
|
||||
_focusedEntryId = entryId;
|
||||
|
||||
NotifyFocused(entry);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region === 最小化 / 恢复 ===
|
||||
|
||||
/// <summary>最小化窗口</summary>
|
||||
public void MinimizeWindow(string entryId)
|
||||
{
|
||||
if (!TryGetEntry(entryId, out var entry)) return;
|
||||
if (entry.Controller == null) return;
|
||||
if (entry.State == WindowState.Minimized) return;
|
||||
|
||||
entry.Controller.gameObject.SetActive(false);
|
||||
entry.State = WindowState.Minimized;
|
||||
|
||||
NotifyStateChanged(entry);
|
||||
}
|
||||
|
||||
/// <summary>恢复已最小化的窗口</summary>
|
||||
public void RestoreWindow(string entryId)
|
||||
{
|
||||
if (!TryGetEntry(entryId, out var entry)) return;
|
||||
if (entry.Controller == null) return;
|
||||
if (entry.State != WindowState.Minimized) return;
|
||||
|
||||
ShowWindow(entryId);
|
||||
entry.State = WindowState.Normal;
|
||||
|
||||
NotifyStateChanged(entry);
|
||||
}
|
||||
|
||||
public void ToggleMaximize(string entryId)
|
||||
{
|
||||
if (!TryGetEntry(entryId, out var entry)) return;
|
||||
if (entry.Controller == null) return;
|
||||
|
||||
entry.Controller.ToggleMaximize();
|
||||
NotifyStateChanged(entry);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region === 查询 ===
|
||||
|
||||
public WindowEntry GetEntry(string id)
|
||||
{
|
||||
_entries.TryGetValue(id, out var entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
public IReadOnlyList<WindowEntry> GetAllEntries() => _orderedEntries;
|
||||
|
||||
public WindowEntry GetFocusedEntry()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_focusedEntryId)) return null;
|
||||
return GetEntry(_focusedEntryId);
|
||||
}
|
||||
|
||||
public int EntryCount => _entries.Count;
|
||||
|
||||
#endregion
|
||||
|
||||
#region === 边缘隐藏 ===
|
||||
|
||||
public void RegisterEdgeHidden(WindowEntry entry, AutoHideDirection direction)
|
||||
{
|
||||
if (entry == null) return;
|
||||
var list = _edgeHidden[direction];
|
||||
if (!list.Contains(entry))
|
||||
list.Add(entry);
|
||||
}
|
||||
|
||||
public void UnregisterEdgeHidden(WindowEntry entry)
|
||||
{
|
||||
if (entry == null) return;
|
||||
foreach (var list in _edgeHidden.Values)
|
||||
list.Remove(entry);
|
||||
}
|
||||
|
||||
private void UnregisterEdgeHiddenForEntry(WindowEntry entry)
|
||||
{
|
||||
UnregisterEdgeHidden(entry);
|
||||
}
|
||||
|
||||
private void CheckEdgeTriggers()
|
||||
{
|
||||
Vector2 mousePos = Input.mousePosition;
|
||||
float sw = Screen.width;
|
||||
float sh = Screen.height;
|
||||
|
||||
bool leftTrigger = mousePos.x < m_EdgeTriggerSize.x;
|
||||
bool rightTrigger = mousePos.x > (sw - m_EdgeTriggerSize.x);
|
||||
bool bottomTrigger = mousePos.y < m_EdgeTriggerSize.y;
|
||||
bool topTrigger = mousePos.y > (sh - m_EdgeTriggerSize.y);
|
||||
|
||||
AutoHideDirection? activeTrigger = null;
|
||||
if (leftTrigger) activeTrigger = AutoHideDirection.Left;
|
||||
else if (rightTrigger) activeTrigger = AutoHideDirection.Right;
|
||||
else if (bottomTrigger) activeTrigger = AutoHideDirection.Down;
|
||||
else if (topTrigger) activeTrigger = AutoHideDirection.Up;
|
||||
|
||||
if (activeTrigger.HasValue && activeTrigger != _lastEdgeTrigger)
|
||||
{
|
||||
ShowEdgeWindows(activeTrigger.Value);
|
||||
}
|
||||
|
||||
if (!activeTrigger.HasValue)
|
||||
_lastEdgeTrigger = null;
|
||||
}
|
||||
|
||||
public void ShowEdgeWindows(AutoHideDirection direction)
|
||||
{
|
||||
_lastEdgeTrigger = direction;
|
||||
var list = _edgeHidden[direction];
|
||||
|
||||
for (int i = list.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var entry = list[i];
|
||||
if (entry?.Controller == null)
|
||||
{
|
||||
list.RemoveAt(i);
|
||||
continue;
|
||||
}
|
||||
entry.Controller.SlideInFromEdge();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region === 内部通知 ===
|
||||
|
||||
private void NotifyCreated(WindowEntry entry) => OnWindowCreated?.Invoke(entry);
|
||||
private void NotifyDestroyed(WindowEntry entry) => OnWindowDestroyed?.Invoke(entry);
|
||||
private void NotifyStateChanged(WindowEntry entry) => OnWindowStateChanged?.Invoke(entry);
|
||||
private void NotifyFocused(WindowEntry entry) => OnWindowFocused?.Invoke(entry);
|
||||
|
||||
#endregion
|
||||
|
||||
#region === 工具 ===
|
||||
|
||||
private bool TryGetEntry(string id, out WindowEntry entry)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
entry = null;
|
||||
return false;
|
||||
}
|
||||
return _entries.TryGetValue(id, out entry);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dfd535659e1184e42b4d38016ccd70ec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,102 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace XericUI.Form
|
||||
{
|
||||
/// <summary>
|
||||
/// 窗口数据描述对象 — 由 UIFormManager 创建和持有。
|
||||
/// 描述一个窗口实例的标识信息、运行时引用、显示状态和层级关系。
|
||||
/// </summary>
|
||||
public class WindowEntry
|
||||
{
|
||||
#region 基本标识
|
||||
|
||||
/// <summary>唯一 ID(GUID)</summary>
|
||||
public string Id { get; }
|
||||
|
||||
/// <summary>窗口标题</summary>
|
||||
public string Title;
|
||||
|
||||
/// <summary>窗口图标(任务栏和标题栏使用)</summary>
|
||||
public Sprite Icon;
|
||||
|
||||
/// <summary>任务栏图标着色</summary>
|
||||
public Color TintColor = Color.white;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 实例引用
|
||||
|
||||
/// <summary>运行时窗口实例</summary>
|
||||
public UIFormController Controller;
|
||||
|
||||
/// <summary>窗口预制体引用</summary>
|
||||
public RectTransform Prefab;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 显示状态
|
||||
|
||||
/// <summary>当前窗口状态</summary>
|
||||
public WindowState State;
|
||||
|
||||
/// <summary>是否在任务栏上显示图标</summary>
|
||||
public bool ShowInTaskBar = true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 层级关系
|
||||
|
||||
/// <summary>父窗口 Entry(关闭父窗口时级联关闭子窗口)</summary>
|
||||
public WindowEntry ParentEntry { get; private set; }
|
||||
|
||||
/// <summary>子窗口 Entry 列表</summary>
|
||||
public readonly List<WindowEntry> ChildEntries = new List<WindowEntry>();
|
||||
|
||||
/// <summary>添加子窗口依赖</summary>
|
||||
public void AddChild(WindowEntry child)
|
||||
{
|
||||
if (child == null || ChildEntries.Contains(child)) return;
|
||||
child.ParentEntry = this;
|
||||
ChildEntries.Add(child);
|
||||
}
|
||||
|
||||
/// <summary>移除子窗口依赖</summary>
|
||||
public void RemoveChild(WindowEntry child)
|
||||
{
|
||||
if (child == null) return;
|
||||
child.ParentEntry = null;
|
||||
ChildEntries.Remove(child);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 进度
|
||||
|
||||
/// <summary>任务栏进度 (0-1)</summary>
|
||||
public float Progress;
|
||||
|
||||
/// <summary>是否在任务栏显示进度条</summary>
|
||||
public bool ShowProgress;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 构造
|
||||
|
||||
public WindowEntry(string id)
|
||||
{
|
||||
Id = id ?? System.Guid.NewGuid().ToString();
|
||||
}
|
||||
|
||||
public WindowEntry() : this(null) { }
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region 委托
|
||||
|
||||
/// <summary>窗口 Entry 事件委托</summary>
|
||||
public delegate void WindowEntryEventHandler(WindowEntry entry);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 174c44278748c9a48b2d6600a450f65a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a12c0014da358274285d451595dcf212
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 材质属性访问器 — 通过 Material.Get/Set 系列方法读写 Shader 属性
|
||||
/// </summary>
|
||||
public class MaterialPropertyAccessor : IAvrgValueAccessor
|
||||
{
|
||||
private readonly Material _material;
|
||||
private readonly string _propName;
|
||||
private readonly ShaderPropertyType _shaderType;
|
||||
|
||||
public Type ValueType { get; }
|
||||
public bool IsReadOnly { get; }
|
||||
|
||||
public MaterialPropertyAccessor(
|
||||
Material material,
|
||||
string propName,
|
||||
ShaderPropertyType shaderType,
|
||||
Type valueType,
|
||||
bool isReadOnly = false)
|
||||
{
|
||||
_material = material;
|
||||
_propName = propName;
|
||||
_shaderType = shaderType;
|
||||
ValueType = valueType;
|
||||
IsReadOnly = isReadOnly;
|
||||
}
|
||||
|
||||
public object GetValue()
|
||||
{
|
||||
if (_material == null) return null;
|
||||
|
||||
return _shaderType switch
|
||||
{
|
||||
ShaderPropertyType.Float or ShaderPropertyType.Range => _material.GetFloat(_propName),
|
||||
ShaderPropertyType.Int => _material.GetInt(_propName),
|
||||
ShaderPropertyType.Color => _material.GetColor(_propName),
|
||||
ShaderPropertyType.Vector => _material.GetVector(_propName),
|
||||
ShaderPropertyType.Texture => _material.GetTexture(_propName),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
public void SetValue(object value)
|
||||
{
|
||||
if (IsReadOnly || _material == null) return;
|
||||
|
||||
switch (_shaderType)
|
||||
{
|
||||
case ShaderPropertyType.Float:
|
||||
case ShaderPropertyType.Range:
|
||||
_material.SetFloat(_propName, Convert.ToSingle(value));
|
||||
break;
|
||||
case ShaderPropertyType.Int:
|
||||
_material.SetInt(_propName, Convert.ToInt32(value));
|
||||
break;
|
||||
case ShaderPropertyType.Color:
|
||||
_material.SetColor(_propName, (Color)value);
|
||||
break;
|
||||
case ShaderPropertyType.Vector:
|
||||
_material.SetVector(_propName, (Vector4)value);
|
||||
break;
|
||||
case ShaderPropertyType.Texture:
|
||||
_material.SetTexture(_propName, value as Texture);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public string FormatDisplay(object value)
|
||||
{
|
||||
if (value == null) return "None";
|
||||
if (value is Texture tex && tex != null) return tex.name;
|
||||
return value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 材质属性访问器工厂 — 持有 Shader 属性名和类型,创建时绑定 Material 实例
|
||||
/// </summary>
|
||||
public class MaterialPropertyAccessorFactory : IAvrgValueAccessorFactory
|
||||
{
|
||||
private readonly string _propName;
|
||||
private readonly ShaderPropertyType _shaderType;
|
||||
private readonly Type _valueType;
|
||||
private readonly bool _isReadOnly;
|
||||
|
||||
public MaterialPropertyAccessorFactory(
|
||||
string propName,
|
||||
ShaderPropertyType shaderType,
|
||||
Type valueType,
|
||||
bool isReadOnly = false)
|
||||
{
|
||||
_propName = propName;
|
||||
_shaderType = shaderType;
|
||||
_valueType = valueType;
|
||||
_isReadOnly = isReadOnly;
|
||||
}
|
||||
|
||||
public IAvrgValueAccessor CreateAccessor(object targetInstance)
|
||||
{
|
||||
var material = targetInstance as Material;
|
||||
if (material == null) return null;
|
||||
return new MaterialPropertyAccessor(material, _propName, _shaderType, _valueType, _isReadOnly);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 174ea317a2a5e8e44a0d96c6d7e43bd5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 反射值访问器 — 通过 FieldInfo / PropertyInfo 读写 C# 对象成员
|
||||
/// </summary>
|
||||
public class ReflectionValueAccessor : IAvrgValueAccessor
|
||||
{
|
||||
private readonly MemberInfo _memberInfo;
|
||||
private object _targetInstance;
|
||||
|
||||
public Type ValueType { get; }
|
||||
public bool IsReadOnly { get; }
|
||||
|
||||
public ReflectionValueAccessor(MemberInfo memberInfo, object targetInstance)
|
||||
{
|
||||
_memberInfo = memberInfo;
|
||||
_targetInstance = targetInstance;
|
||||
|
||||
ValueType = memberInfo switch
|
||||
{
|
||||
FieldInfo fi => fi.FieldType,
|
||||
PropertyInfo pi => pi.PropertyType,
|
||||
_ => typeof(object)
|
||||
};
|
||||
|
||||
IsReadOnly = memberInfo is PropertyInfo pi2 && !pi2.CanWrite;
|
||||
}
|
||||
|
||||
public object GetValue()
|
||||
{
|
||||
if (_targetInstance == null) return null;
|
||||
return _memberInfo switch
|
||||
{
|
||||
FieldInfo fi => fi.GetValue(_targetInstance),
|
||||
PropertyInfo pi => pi.GetValue(_targetInstance),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
public void SetValue(object value)
|
||||
{
|
||||
if (IsReadOnly || _targetInstance == null) return;
|
||||
switch (_memberInfo)
|
||||
{
|
||||
case FieldInfo fi:
|
||||
fi.SetValue(_targetInstance, value);
|
||||
break;
|
||||
case PropertyInfo pi:
|
||||
pi.SetValue(_targetInstance, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public string FormatDisplay(object value) => value?.ToString() ?? "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 反射值访问器工厂 — 持有 MemberInfo,创建时绑定 TargetInstance
|
||||
/// </summary>
|
||||
public class ReflectionAccessorFactory : IAvrgValueAccessorFactory
|
||||
{
|
||||
private readonly MemberInfo _memberInfo;
|
||||
|
||||
public ReflectionAccessorFactory(MemberInfo memberInfo)
|
||||
{
|
||||
_memberInfo = memberInfo;
|
||||
}
|
||||
|
||||
public IAvrgValueAccessor CreateAccessor(object targetInstance)
|
||||
{
|
||||
return new ReflectionValueAccessor(_memberInfo, targetInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 74ef75bd607ec5242a6bb9bb985bf436
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 为 Dropdown UI 提供预设选项
|
||||
///
|
||||
/// 使用示例:
|
||||
/// [XuiavrgFieldDropdownValue("选项A", "选项B", "选项C", MergeMode = PresetOptionsMergeMode.Union)]
|
||||
/// public string quality = "选项A";
|
||||
///
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
|
||||
public class XuiavrgFieldDropdownValueAttribute : Attribute, IXuiavrgAttribute
|
||||
{
|
||||
/// <summary>预设选项数组</summary>
|
||||
public readonly string[] Options;
|
||||
|
||||
/// <summary>与已有选项(如 enum 名)的合并模式</summary>
|
||||
public PresetOptionsMergeMode MergeMode { get; set; } = PresetOptionsMergeMode.Replace;
|
||||
|
||||
public XuiavrgFieldDropdownValueAttribute(params string[] options)
|
||||
{
|
||||
Options = options ?? Array.Empty<string>();
|
||||
}
|
||||
public XuiavrgFieldDropdownValueAttribute(PresetOptionsMergeMode mergeMode, params string[] options)
|
||||
{
|
||||
MergeMode = mergeMode;
|
||||
Options = options ?? Array.Empty<string>();
|
||||
}
|
||||
|
||||
public void BindMetaData(AvrgMemberMetaData metaData)
|
||||
{
|
||||
if (metaData == null) return;
|
||||
|
||||
metaData.PresetOptions = Options;
|
||||
metaData.OptionsMergeMode = MergeMode;
|
||||
|
||||
// 自动设置 Tag 为 Dropdown
|
||||
if (metaData.Tag == XuiavrgFieldTag.None)
|
||||
metaData.Tag = XuiavrgFieldTag.Dropdown;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 917ba2bd8d59cce469d2c90b2201c529
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 为 ToggleGroup UI 提供预设选项
|
||||
///
|
||||
/// 使用示例:
|
||||
/// [XuiavrgFieldToggleGroupValue("低", "中", "高", MergeMode = PresetOptionsMergeMode.Replace)]
|
||||
/// public string detailLevel = "中";
|
||||
///
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
|
||||
public class XuiavrgFieldToggleGroupValueAttribute : Attribute, IXuiavrgAttribute
|
||||
{
|
||||
/// <summary>预设选项数组</summary>
|
||||
public readonly string[] Options;
|
||||
|
||||
/// <summary>与已有选项(如 enum 名)的合并模式</summary>
|
||||
public PresetOptionsMergeMode MergeMode { get; set; } = PresetOptionsMergeMode.Replace;
|
||||
|
||||
public XuiavrgFieldToggleGroupValueAttribute(params string[] options)
|
||||
{
|
||||
Options = options ?? Array.Empty<string>();
|
||||
}
|
||||
public XuiavrgFieldToggleGroupValueAttribute(PresetOptionsMergeMode mergeMode, params string[] options)
|
||||
{
|
||||
MergeMode = mergeMode;
|
||||
Options = options ?? Array.Empty<string>();
|
||||
}
|
||||
|
||||
public void BindMetaData(AvrgMemberMetaData metaData)
|
||||
{
|
||||
if (metaData == null) return;
|
||||
|
||||
metaData.PresetOptions = Options;
|
||||
metaData.OptionsMergeMode = MergeMode;
|
||||
|
||||
// 自动设置 Tag 为 ToggleGroup
|
||||
if (metaData.Tag == XuiavrgFieldTag.None)
|
||||
metaData.Tag = XuiavrgFieldTag.ToggleGroup;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6dac053ad1c4ab043a815cb9b3c64d06
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -8,12 +8,11 @@ namespace XericUI.ReflectionUIGenerator
|
||||
/// 将一个复合类型(如 Vector2)拆分为多个子属性,分别绑定到不同的 UI 元素
|
||||
///
|
||||
/// 使用示例:
|
||||
/// [XuiavrgFieldEntryGroup("speed")]
|
||||
/// [XuiavrgSplit("x", "最小")]
|
||||
/// [XuiavrgSplit("y", "最大")]
|
||||
/// public Vector2 speedRange = new Vector2(0, 100);
|
||||
///
|
||||
/// 上述代码创建一个 "speed" 编组的 Entry,内部包含两个子绑定:
|
||||
/// 上述代码创建一个编组 Entry,内部包含两个子元数据:
|
||||
/// speedRange.x 绑定到 UI 的"最小"输入,speedRange.y 绑定到"最大"输入
|
||||
/// 子绑定通过 XUIAvrgMDValue.BindingSlots 与预制体上的 UI 组件匹配
|
||||
/// </summary>
|
||||
@@ -39,13 +38,12 @@ namespace XericUI.ReflectionUIGenerator
|
||||
|
||||
public void BindMetaData(AvrgMemberMetaData metaData)
|
||||
{
|
||||
// 创建子元数据
|
||||
// 创建子元数据 — 纯元数据,不持有实例
|
||||
var subMetaData = new AvrgMemberMetaData
|
||||
{
|
||||
MemberName = $"{metaData.MemberName}.{PropertyPath}",
|
||||
PropertyPath = PropertyPath,
|
||||
LabelName = DisplayLabel ?? PropertyPath,
|
||||
TargetInstance = null, // 由运行时闭包解析
|
||||
Hide = metaData.Hide,
|
||||
Required = metaData.Required,
|
||||
FieldOrder = metaData.FieldOrder,
|
||||
@@ -74,7 +72,6 @@ namespace XericUI.ReflectionUIGenerator
|
||||
if (parentType == null || string.IsNullOrEmpty(propertyPath))
|
||||
return null;
|
||||
|
||||
// 仅支持单层路径
|
||||
if (propertyPath.Contains('/'))
|
||||
return null;
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 样式命名空间特性 — 为元数据指定 SuperStyleSheet 查询时的命名空间。
|
||||
///
|
||||
/// 可标记在类上(作为该类型所有字段的默认命名空间),
|
||||
/// 也可标记在字段/属性上(覆盖单个字段的命名空间)。
|
||||
///
|
||||
/// 类级标记先应用(设置默认值),字段级标记后应用(可覆盖),
|
||||
/// 因此字段级 [XuiavrgStyleNamespace] 优先于类级。
|
||||
///
|
||||
/// 默认命名空间为 "default"。
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
|
||||
public class XuiavrgStyleNamespaceAttribute : Attribute, IXuiavrgAttribute
|
||||
{
|
||||
public readonly string StyleNamespace;
|
||||
|
||||
/// <param name="styleNamespace">SuperStyleSheet 命名空间名称(如 "my-theme")</param>
|
||||
public XuiavrgStyleNamespaceAttribute(string styleNamespace)
|
||||
{
|
||||
StyleNamespace = styleNamespace;
|
||||
}
|
||||
|
||||
public void BindMetaData(AvrgMemberMetaData metaData)
|
||||
{
|
||||
if (metaData != null)
|
||||
metaData.StyleNamespace = StyleNamespace;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee38b82f1ec947d4da1981bac698fe96
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,77 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 绑定名称前缀树(Trie),支持通过完整路径或成员名后缀快速查找 AvrgMvvmBinding。
|
||||
///
|
||||
/// 使用场景:
|
||||
/// - Find("XericUI.MyData.speed") → 完整路径精确命中
|
||||
/// - FindByMemberName("speed") → 沿 Trie 叶节点查找末段匹配
|
||||
///
|
||||
/// 在 BuildMDVM 时构建,绑定到 AvrgMvvmBindingCollection。
|
||||
/// </summary>
|
||||
internal class AvrgBindingTrie
|
||||
{
|
||||
private class Node
|
||||
{
|
||||
public AvrgMvvmBinding Binding;
|
||||
public readonly Dictionary<string, Node> Children = new();
|
||||
}
|
||||
|
||||
private readonly Node _root = new();
|
||||
private readonly Dictionary<string, AvrgMvvmBinding> _memberNameIndex = new();
|
||||
|
||||
public void Insert(string fullPath, AvrgMvvmBinding binding)
|
||||
{
|
||||
if (string.IsNullOrEmpty(fullPath) || binding == null) return;
|
||||
|
||||
var parts = fullPath.Split('.');
|
||||
var current = _root;
|
||||
foreach (var part in parts)
|
||||
{
|
||||
if (!current.Children.TryGetValue(part, out var child))
|
||||
{
|
||||
child = new Node();
|
||||
current.Children[part] = child;
|
||||
}
|
||||
current = child;
|
||||
}
|
||||
current.Binding = binding;
|
||||
|
||||
// 同时建成员名索引(末段)
|
||||
string memberName = parts[parts.Length - 1];
|
||||
if (!_memberNameIndex.ContainsKey(memberName))
|
||||
_memberNameIndex[memberName] = binding;
|
||||
}
|
||||
|
||||
/// <summary>通过完整路径精确查找(如 "XericUI.MyData.speed")</summary>
|
||||
public AvrgMvvmBinding Find(string fullPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(fullPath)) return null;
|
||||
|
||||
var parts = fullPath.Split('.');
|
||||
var current = _root;
|
||||
foreach (var part in parts)
|
||||
{
|
||||
if (!current.Children.TryGetValue(part, out current))
|
||||
return null;
|
||||
}
|
||||
return current?.Binding;
|
||||
}
|
||||
|
||||
/// <summary>通过成员名查找(如 "speed")— O(1) 直接索引</summary>
|
||||
public AvrgMvvmBinding FindByMemberName(string memberName)
|
||||
{
|
||||
_memberNameIndex.TryGetValue(memberName, out var binding);
|
||||
return binding;
|
||||
}
|
||||
|
||||
/// <summary>清除所有索引</summary>
|
||||
public void Clear()
|
||||
{
|
||||
_root.Children.Clear();
|
||||
_memberNameIndex.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0fbb9c5ea2b8b5644a49559e7b6260e0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,57 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 单个成员的元数据数据结构
|
||||
/// 记录从反射 + Attribute 标记中提取的所有信息
|
||||
/// 纯数据描述 — 不持有反射信息(MemberInfo)和实例引用(TargetInstance)
|
||||
///
|
||||
/// 数据的实际读写通过 IAvrgValueAccessorFactory 在 BuildMDVM 阶段创建的 IAvrgValueAccessor 完成。
|
||||
/// 这使得同一个元数据模板可复用于 C# 反射对象、Material Shader 属性等不同数据源。
|
||||
/// </summary>
|
||||
public class AvrgMemberMetaData
|
||||
{
|
||||
// ============ 反射信息 ============
|
||||
// ============ 基本标识 ============
|
||||
|
||||
/// <summary>字段/属性名</summary>
|
||||
/// <summary>字段/属性名(或 Shader 属性名如 "_MainTex")</summary>
|
||||
public string MemberName { get; set; }
|
||||
|
||||
/// <summary>反射信息 (FieldInfo 或 PropertyInfo)</summary>
|
||||
public MemberInfo MemberInfo { get; set; }
|
||||
|
||||
/// <summary>值类型</summary>
|
||||
public Type ValueType { get; set; }
|
||||
|
||||
/// <summary>是否为属性 (false 为字段)</summary>
|
||||
public bool IsProperty { get; set; }
|
||||
|
||||
/// <summary>绑定的目标实例 (可选,Step1 时可空)</summary>
|
||||
public object TargetInstance { get; set; }
|
||||
|
||||
// ============ 拆分属性支持 ============
|
||||
|
||||
/// <summary>
|
||||
/// 子属性路径(仅拆分属性有效)
|
||||
/// 用于从父值中访问子字段/属性,如 "x"、"y"、"width"
|
||||
/// </summary>
|
||||
public string PropertyPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 拆分子元数据列表
|
||||
/// 由 [XuiavrgSplit] 特性填充,每个子元数据对应一个拆分属性
|
||||
/// </summary>
|
||||
public List<AvrgMemberMetaData> SubMetaDataList { get; set; }
|
||||
|
||||
// ============ 从 IXuiavrgAttribute 融合的元数据 ============
|
||||
|
||||
/// <summary>显示标签</summary>
|
||||
/// <summary>UI 显示标签</summary>
|
||||
public string LabelName { get; set; }
|
||||
|
||||
/// <summary>单位文本</summary>
|
||||
/// <summary>单位文本(如 "m/s")</summary>
|
||||
public string UnitLabelName { get; set; }
|
||||
|
||||
/// <summary>格式化字符串(如 "{0:F2}")</summary>
|
||||
public string FormatText { get; set; }
|
||||
|
||||
/// <summary>值类型</summary>
|
||||
public Type ValueType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 样式表命名空间(用于 SuperStyleSheet 样式查询)
|
||||
/// 默认 "default"。可由类级或字段级 [XuiavrgStyleNamespace] 覆写
|
||||
/// </summary>
|
||||
public string StyleNamespace { get; set; } = "default";
|
||||
|
||||
// ============ UI 映射 ============
|
||||
|
||||
/// <summary>功能标记</summary>
|
||||
public XuiavrgFieldTag Tag { get; set; } = XuiavrgFieldTag.None;
|
||||
|
||||
@@ -67,21 +52,12 @@ namespace XericUI.ReflectionUIGenerator
|
||||
/// <summary>是否在 UI 中隐藏</summary>
|
||||
public bool Hide { get; set; }
|
||||
|
||||
/// <summary>值变更时触发的反射调用方法名</summary>
|
||||
public string OnTriggerCall { get; set; }
|
||||
// ============ 默认值与验证 ============
|
||||
|
||||
/// <summary>
|
||||
/// 类的初始默认值(由类字段初始化器决定)
|
||||
/// 例如 public float speed = 10f; → DefaultValue = 10f
|
||||
/// </summary>
|
||||
/// <summary>默认值(用于 UI 初始化和回退参考)</summary>
|
||||
public object DefaultValue { get; set; }
|
||||
|
||||
// ============ 属性检查 (由 [PropertyCheck] 填充) ============
|
||||
|
||||
/// <summary>
|
||||
/// 判断依据标识
|
||||
/// 默认使用 ValueType.FullName 与 AvrgValueRangeConfig 中的条目匹配
|
||||
/// </summary>
|
||||
/// <summary>属性检查依据标识(默认使用 ValueType.FullName)</summary>
|
||||
public string PropertyCheckKey { get; set; }
|
||||
|
||||
/// <summary>是否必填</summary>
|
||||
@@ -99,47 +75,46 @@ namespace XericUI.ReflectionUIGenerator
|
||||
/// <summary>是否启用无效值状态检查</summary>
|
||||
public bool ValidateInvalid { get; set; }
|
||||
|
||||
// ============ 原始 Attribute 引用 ============
|
||||
/// <summary>Shader Range 属性下限(null 表示非 Range 属性)</summary>
|
||||
public float? RangeMin { get; set; }
|
||||
|
||||
/// <summary>Shader Range 属性上限(null 表示非 Range 属性)</summary>
|
||||
public float? RangeMax { get; set; }
|
||||
|
||||
// ============ 拆分属性支持 ============
|
||||
|
||||
/// <summary>
|
||||
/// 收集到的所有 IXuiavrgAttribute 列表
|
||||
/// 保留原始引用,供扩展和自定义处理使用
|
||||
/// 子属性路径(仅拆分属性有效)
|
||||
/// 用于从父值中访问子字段/属性,如 "x"、"y"、"width"
|
||||
/// </summary>
|
||||
public string PropertyPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 拆分子元数据列表
|
||||
/// 由 [XuiavrgSplit] 特性填充
|
||||
/// </summary>
|
||||
public List<AvrgMemberMetaData> SubMetaDataList { get; set; }
|
||||
|
||||
// ============ 预设选项 (Dropdown/ToggleGroup) ============
|
||||
|
||||
/// <summary>预设选项列表</summary>
|
||||
public IReadOnlyList<string> PresetOptions { get; set; }
|
||||
|
||||
/// <summary>预设选项合并模式</summary>
|
||||
public PresetOptionsMergeMode OptionsMergeMode { get; set; }
|
||||
|
||||
// ============ 扩展 ============
|
||||
|
||||
/// <summary>收集到的所有 IXuiavrgAttribute 列表</summary>
|
||||
public List<IXuiavrgAttribute> Attributes { get; set; } = new List<IXuiavrgAttribute>();
|
||||
|
||||
/// <summary>
|
||||
/// 从绑定目标获取值
|
||||
/// </summary>
|
||||
public object GetValue()
|
||||
{
|
||||
if (TargetInstance == null) return null;
|
||||
switch (MemberInfo)
|
||||
{
|
||||
case PropertyInfo pi:
|
||||
return pi.GetValue(TargetInstance);
|
||||
case FieldInfo fi:
|
||||
return fi.GetValue(TargetInstance);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// ============ ★ 核心变更 — 值访问器工厂 ============
|
||||
|
||||
/// <summary>
|
||||
/// 设置值到绑定目标
|
||||
/// 值访问器工厂
|
||||
/// 在元数据提取阶段设置(持有 MemberInfo 或 Shader 属性信息)
|
||||
/// 在 BuildMDVM 阶段调用 CreateAccessor(targetInstance) 创建真正的读写通道
|
||||
/// </summary>
|
||||
public void SetValue(object value)
|
||||
{
|
||||
if (TargetInstance == null) return;
|
||||
switch (MemberInfo)
|
||||
{
|
||||
case PropertyInfo pi:
|
||||
if (pi.CanWrite)
|
||||
pi.SetValue(TargetInstance, value);
|
||||
break;
|
||||
case FieldInfo fi:
|
||||
fi.SetValue(TargetInstance, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
public IAvrgValueAccessorFactory AccessorFactory { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 元数据集
|
||||
/// 包含从对象反射提取的完整元数据,以及用于生成 MVVM 绑定的 BuildMDVM() 方法
|
||||
/// 包含从对象/材质反射提取的完整元数据,以及用于生成 MVVM 绑定的 BuildMDVM() 方法
|
||||
/// </summary>
|
||||
public class AvrgMemberMetaDataSet
|
||||
{
|
||||
@@ -33,7 +33,6 @@ namespace XericUI.ReflectionUIGenerator
|
||||
/// </summary>
|
||||
internal void MergeGroupEntries()
|
||||
{
|
||||
// 提取编组和非编组
|
||||
var grouped = _metaDataList
|
||||
.Where(m => !string.IsNullOrEmpty(m.GroupPath))
|
||||
.GroupBy(m => m.GroupPath)
|
||||
@@ -47,27 +46,20 @@ namespace XericUI.ReflectionUIGenerator
|
||||
|
||||
foreach (var gEntry in grouped)
|
||||
{
|
||||
// 编组内第一个成员作为主元数据
|
||||
var first = gEntry.First();
|
||||
first.SubMetaDataList ??= new List<AvrgMemberMetaData>();
|
||||
|
||||
foreach (var member in gEntry)
|
||||
{
|
||||
if (member == first) continue;
|
||||
|
||||
// 成员自身的 Split 子属性合并到父级
|
||||
if (member.SubMetaDataList?.Count > 0)
|
||||
{
|
||||
if (first.SubMetaDataList == null)
|
||||
first.SubMetaDataList = new List<AvrgMemberMetaData>();
|
||||
first.SubMetaDataList.AddRange(member.SubMetaDataList);
|
||||
|
||||
// Merge: 成员自身作为子属性加入,保留独立 MemberInfo/TargetInstance
|
||||
first.SubMetaDataList.Add(member);
|
||||
}
|
||||
}
|
||||
merged.Add(first);
|
||||
}
|
||||
|
||||
// 非编组直接追加
|
||||
merged.AddRange(nonGrouped);
|
||||
|
||||
_metaDataList = merged;
|
||||
}
|
||||
|
||||
@@ -85,9 +77,10 @@ namespace XericUI.ReflectionUIGenerator
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为元数据生成对应的 MVVM 值绑定模板
|
||||
/// 为元数据生成对应的 MVVM 值绑定
|
||||
/// 通过 AccessorFactory 创建 IAvrgValueAccessor 完成数据读写
|
||||
/// </summary>
|
||||
/// <param name="businessTarget">可选的业务目标对象(如果元数据中没有绑定目标,则在此传入)</param>
|
||||
/// <param name="businessTarget">目标实例(C# 对象、Material 等)</param>
|
||||
/// <param name="resolverConfig">可选的冲突解决配置</param>
|
||||
/// <returns>绑定集合</returns>
|
||||
public AvrgMvvmBindingCollection BuildMDVM(
|
||||
@@ -95,69 +88,78 @@ namespace XericUI.ReflectionUIGenerator
|
||||
AvrgMvvmResolverConfig resolverConfig = null)
|
||||
{
|
||||
var bindingCollection = new AvrgMvvmBindingCollection();
|
||||
bindingCollection.SourceMetaDataSet = this;
|
||||
var scheduler = new AvrgMvvmScheduler(resolverConfig);
|
||||
|
||||
// ★ 构建前缀树索引
|
||||
var trie = new AvrgBindingTrie();
|
||||
string typePrefix = businessTarget != null
|
||||
? businessTarget.GetType().FullName + "."
|
||||
: "";
|
||||
|
||||
foreach (var metaData in _metaDataList)
|
||||
{
|
||||
// 确定目标实例
|
||||
var target = metaData.TargetInstance ?? businessTarget;
|
||||
if (target == null)
|
||||
if (metaData.AccessorFactory == null) continue;
|
||||
if (businessTarget == null)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"[Xuiavrg] 元数据 '{metaData.MemberName}' 缺少 targetInstance,跳过绑定");
|
||||
continue;
|
||||
}
|
||||
|
||||
var accessor = metaData.AccessorFactory.CreateAccessor(businessTarget);
|
||||
if (accessor == null) continue;
|
||||
|
||||
// 创建主绑定
|
||||
var binding = new AvrgMvvmBinding
|
||||
{
|
||||
MetaData = metaData,
|
||||
ReadFunc = () => metaData.GetValue(),
|
||||
ReadFunc = () => accessor.GetValue(),
|
||||
Accessor = accessor,
|
||||
};
|
||||
|
||||
var capturedBinding = binding;
|
||||
binding.WriteAction = (val) =>
|
||||
{
|
||||
metaData.SetValue(val);
|
||||
scheduler.MarkDataDirty(capturedBinding);
|
||||
};
|
||||
|
||||
// 如果元数据有拆分子属性,创建子绑定
|
||||
if (accessor.IsReadOnly)
|
||||
{
|
||||
binding.WriteAction = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
binding.WriteAction = (val) =>
|
||||
{
|
||||
accessor.SetValue(val);
|
||||
scheduler.MarkDataDirty(capturedBinding);
|
||||
};
|
||||
}
|
||||
|
||||
// 拆分属性子绑定
|
||||
if (metaData.SubMetaDataList?.Count > 0)
|
||||
{
|
||||
binding.SubBindings = new List<AvrgMvvmBinding>();
|
||||
|
||||
foreach (var subMetaData in metaData.SubMetaDataList)
|
||||
{
|
||||
string subPath = subMetaData.PropertyPath;
|
||||
if (string.IsNullOrEmpty(subPath)) continue;
|
||||
|
||||
// 创建子属性访问器
|
||||
var subAccessor = new AvrgSubPropertyAccessor(
|
||||
accessor, subPath, subMetaData);
|
||||
|
||||
var subBinding = new AvrgMvvmBinding
|
||||
{
|
||||
MetaData = subMetaData,
|
||||
};
|
||||
|
||||
// Merge 路径:子字段有自己的 MemberInfo/TargetInstance(来自编组成员)
|
||||
if (subMetaData.MemberInfo != null && subMetaData.TargetInstance != null)
|
||||
{
|
||||
var capturedSub = subMetaData;
|
||||
subBinding.ReadFunc = () => capturedSub.GetValue();
|
||||
subBinding.WriteAction = (val) =>
|
||||
Accessor = subAccessor,
|
||||
ReadFunc = () => ReadSubProperty(accessor.GetValue(), subPath),
|
||||
WriteAction = (val) =>
|
||||
{
|
||||
capturedSub.SetValue(val);
|
||||
scheduler.MarkDataDirty(capturedBinding);
|
||||
};
|
||||
}
|
||||
// Split 路径:通过 PropertyPath 从父值访问(来自 XuiavrgSplit)
|
||||
else if (!string.IsNullOrEmpty(subMetaData.PropertyPath))
|
||||
{
|
||||
string subPath = subMetaData.PropertyPath;
|
||||
subBinding.ReadFunc = () => ReadSubProperty(metaData.GetValue(), subPath);
|
||||
subBinding.WriteAction = (val) =>
|
||||
{
|
||||
var parentVal = metaData.GetValue();
|
||||
var parentVal = accessor.GetValue();
|
||||
var modifiedParent = WriteSubProperty(parentVal, subPath, val);
|
||||
metaData.SetValue(modifiedParent);
|
||||
accessor.SetValue(modifiedParent);
|
||||
scheduler.MarkDataDirty(capturedBinding);
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
continue; // 无法创建绑定,跳过
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
binding.SubBindings.Add(subBinding);
|
||||
}
|
||||
@@ -166,24 +168,24 @@ namespace XericUI.ReflectionUIGenerator
|
||||
// 注册到调度器
|
||||
scheduler.Register(binding);
|
||||
bindingCollection.Add(binding);
|
||||
|
||||
// ★ 插入前缀树(完整路径: Type.FullName.MemberName)
|
||||
string fullPath = typePrefix + metaData.MemberName;
|
||||
trie.Insert(fullPath, binding);
|
||||
}
|
||||
|
||||
bindingCollection.SetTrie(trie);
|
||||
bindingCollection.AttachScheduler(scheduler);
|
||||
return bindingCollection;
|
||||
}
|
||||
|
||||
// ============ 子属性访问辅助方法 ============
|
||||
|
||||
/// <summary>
|
||||
/// 从父值中读取子属性(支持单层路径如 "x"、"y")
|
||||
/// 例如:ReadSubProperty(Vector2(1,2), "x") → 1f
|
||||
/// </summary>
|
||||
private static object ReadSubProperty(object parentValue, string propertyPath)
|
||||
{
|
||||
if (parentValue == null || string.IsNullOrEmpty(propertyPath))
|
||||
return null;
|
||||
|
||||
// 仅支持单层路径
|
||||
if (propertyPath.Contains('/'))
|
||||
{
|
||||
Debug.LogWarning($"[Xuiavrg] 暂不支持多层嵌套路径 '{propertyPath}'");
|
||||
@@ -201,10 +203,6 @@ namespace XericUI.ReflectionUIGenerator
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改父值中的子属性并返回修改后的父值
|
||||
/// 适用于值类型(struct)和引用类型
|
||||
/// </summary>
|
||||
private static object WriteSubProperty(object parentValue, string propertyPath, object value)
|
||||
{
|
||||
if (parentValue == null || string.IsNullOrEmpty(propertyPath))
|
||||
@@ -240,8 +238,6 @@ namespace XericUI.ReflectionUIGenerator
|
||||
/// <summary>
|
||||
/// 对当前元数据集执行属性检查验证
|
||||
/// </summary>
|
||||
/// <param name="valueRangeConfig">值范围配置(可选,不传入则不执行范围检查)</param>
|
||||
/// <returns>验证结果集合</returns>
|
||||
public AvrgValidationResultSet Validate(AvrgValueRangeConfig valueRangeConfig = null)
|
||||
{
|
||||
var resultSet = new AvrgValidationResultSet();
|
||||
@@ -251,7 +247,6 @@ namespace XericUI.ReflectionUIGenerator
|
||||
if (metaData.Hide)
|
||||
continue;
|
||||
|
||||
// 只有标记了 PropertyCheck 的才验证
|
||||
if (!metaData.Required && !metaData.ValidateReasonable
|
||||
&& !metaData.ValidateWarning && !metaData.ValidateInvalid)
|
||||
{
|
||||
@@ -259,13 +254,13 @@ namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
MetaData = metaData,
|
||||
Result = AvrgMemberCheckResult.Skipped,
|
||||
CurrentValueString = metaData.GetValue()?.ToString(),
|
||||
CurrentValueString = metaData.DefaultValue?.ToString(),
|
||||
Message = "未标记属性检查",
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
var value = metaData.GetValue();
|
||||
var value = metaData.DefaultValue;
|
||||
var checkResult = CheckSingleMember(metaData, value, valueRangeConfig);
|
||||
resultSet.Results.Add(checkResult);
|
||||
}
|
||||
@@ -273,9 +268,6 @@ namespace XericUI.ReflectionUIGenerator
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证单个成员
|
||||
/// </summary>
|
||||
private AvrgMemberValidationResult CheckSingleMember(
|
||||
AvrgMemberMetaData metaData,
|
||||
object value,
|
||||
@@ -287,7 +279,6 @@ namespace XericUI.ReflectionUIGenerator
|
||||
CurrentValueString = value?.ToString() ?? "(null)",
|
||||
};
|
||||
|
||||
// 根据 CheckKey 查找值范围规则
|
||||
AvrgValueRangeEntry rangeEntry = null;
|
||||
string checkKey = !string.IsNullOrEmpty(metaData.PropertyCheckKey)
|
||||
? metaData.PropertyCheckKey
|
||||
@@ -298,22 +289,19 @@ namespace XericUI.ReflectionUIGenerator
|
||||
rangeEntry = rangeConfig.FindEntry(checkKey);
|
||||
}
|
||||
|
||||
// ——— 优先级 1: 空值检查 (Required) ———
|
||||
// 优先级 1: 空值检查 (Required)
|
||||
if (metaData.Required)
|
||||
{
|
||||
bool isEmpty = false;
|
||||
|
||||
// 引用类型 null
|
||||
if (value == null)
|
||||
{
|
||||
isEmpty = true;
|
||||
}
|
||||
// 字符串 null/空
|
||||
else if (value is string str)
|
||||
{
|
||||
isEmpty = string.IsNullOrEmpty(str);
|
||||
}
|
||||
// 使用 ValueRangeEntry 的 IsEmpty 判断
|
||||
else if (rangeEntry != null && !rangeEntry.skipEmptyCheck)
|
||||
{
|
||||
isEmpty = rangeEntry.IsEmpty(value);
|
||||
@@ -327,7 +315,6 @@ namespace XericUI.ReflectionUIGenerator
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有范围配置,无法继续检查
|
||||
if (rangeEntry == null)
|
||||
{
|
||||
result.Result = AvrgMemberCheckResult.Valid;
|
||||
@@ -335,7 +322,7 @@ namespace XericUI.ReflectionUIGenerator
|
||||
return result;
|
||||
}
|
||||
|
||||
// ——— 优先级 2: 合理值检查(最高优先级,命中即通过) ———
|
||||
// 优先级 2: 合理值检查
|
||||
if (metaData.ValidateReasonable && rangeEntry.IsReasonable(value))
|
||||
{
|
||||
result.Result = AvrgMemberCheckResult.Valid;
|
||||
@@ -343,7 +330,7 @@ namespace XericUI.ReflectionUIGenerator
|
||||
return result;
|
||||
}
|
||||
|
||||
// ——— 优先级 3: 无效值检查 ———
|
||||
// 优先级 3: 无效值检查
|
||||
if (metaData.ValidateInvalid && rangeEntry.IsInvalid(value))
|
||||
{
|
||||
result.Result = AvrgMemberCheckResult.Invalid;
|
||||
@@ -351,7 +338,7 @@ namespace XericUI.ReflectionUIGenerator
|
||||
return result;
|
||||
}
|
||||
|
||||
// ——— 优先级 4: 警告值检查 ———
|
||||
// 优先级 4: 警告值检查
|
||||
if (metaData.ValidateWarning && rangeEntry.IsWarning(value))
|
||||
{
|
||||
result.Result = AvrgMemberCheckResult.Warning;
|
||||
@@ -359,7 +346,6 @@ namespace XericUI.ReflectionUIGenerator
|
||||
return result;
|
||||
}
|
||||
|
||||
// ——— 最终: 通过 ———
|
||||
result.Result = AvrgMemberCheckResult.Valid;
|
||||
result.Message = "通过";
|
||||
return result;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
@@ -20,6 +19,12 @@ namespace XericUI.ReflectionUIGenerator
|
||||
/// <summary>写入值到数据源的委托</summary>
|
||||
public Action<object> WriteAction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 值访问器引用(在 BuildMDVM 时创建,供 UI 组件使用)
|
||||
/// 提供 ValueType / IsReadOnly / FormatDisplay 等扩展能力
|
||||
/// </summary>
|
||||
public IAvrgValueAccessor Accessor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 拆分属性子绑定列表
|
||||
/// 由 [XuiavrgSplit] 特性展开生成,每个子绑定对应一个拆分后的子属性
|
||||
@@ -40,21 +45,75 @@ namespace XericUI.ReflectionUIGenerator
|
||||
/// <summary>数据侧写入的缓存值</summary>
|
||||
public object DataCachedValue { get; set; }
|
||||
|
||||
// ============ 验证状态 ============
|
||||
|
||||
private ValidationState _validationState = ValidationState.Normal;
|
||||
|
||||
/// <summary>当前验证状态</summary>
|
||||
public ValidationState CurrentValidationState => _validationState;
|
||||
|
||||
/// <summary>验证状态变更事件 — UI 组件订阅以切换样式</summary>
|
||||
public event Action<ValidationState> OnValidationStateChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 校验值是否满足元数据约束
|
||||
/// 返回:Normal(通过)、Warning(放行但警告)、Invalid(驳回)
|
||||
/// </summary>
|
||||
public ValidationState ValidateValue(object value)
|
||||
{
|
||||
if (MetaData == null) return ValidationState.Normal;
|
||||
|
||||
// 1. 空值检查(Required)
|
||||
if (MetaData.Required && (value == null || (value is string s && string.IsNullOrEmpty(s))))
|
||||
return ValidationState.Invalid;
|
||||
|
||||
// 2. 范围检查(RangeMin/RangeMax)
|
||||
if (value is IConvertible)
|
||||
{
|
||||
float num = Convert.ToSingle(value);
|
||||
|
||||
if (MetaData.RangeMin.HasValue && num < MetaData.RangeMin.Value)
|
||||
return ValidationState.Invalid;
|
||||
|
||||
if (MetaData.RangeMax.HasValue && num > MetaData.RangeMax.Value)
|
||||
return ValidationState.Invalid;
|
||||
}
|
||||
|
||||
return ValidationState.Normal;
|
||||
}
|
||||
|
||||
/// <summary>更新验证状态并触发回调(仅状态变化时触发)</summary>
|
||||
internal void SetValidationState(ValidationState newState)
|
||||
{
|
||||
if (_validationState != newState)
|
||||
{
|
||||
_validationState = newState;
|
||||
OnValidationStateChanged?.Invoke(_validationState);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>重置验证状态为 Normal(不触发事件)</summary>
|
||||
internal void ResetValidationState()
|
||||
{
|
||||
_validationState = ValidationState.Normal;
|
||||
}
|
||||
|
||||
// ============ UI 绑定 ============
|
||||
|
||||
/// <summary>UI 值变更回调委托 — UI 组件订阅此回调以更新显示</summary>
|
||||
public Action<object> OnUIDataChanged;
|
||||
/// <summary>UI 值变更回调委托 — UI 组件订阅此回调以更新显示(附带验证状态)</summary>
|
||||
public Action<object, ValidationState> OnUIDataChanged;
|
||||
|
||||
/// <summary>通知 UI 更新值</summary>
|
||||
public void NotifyUIUpdate(object value)
|
||||
/// <summary>通知 UI 更新值(带验证状态)</summary>
|
||||
public void NotifyUIUpdate(object value, ValidationState state)
|
||||
{
|
||||
OnUIDataChanged?.Invoke(value);
|
||||
OnUIDataChanged?.Invoke(value, state);
|
||||
}
|
||||
|
||||
/// <summary>清除所有 UI 变更订阅</summary>
|
||||
public void ClearUIEvent()
|
||||
{
|
||||
OnUIDataChanged = null;
|
||||
OnValidationStateChanged = null;
|
||||
}
|
||||
|
||||
// ============ 便捷方法 ============
|
||||
|
||||
@@ -4,12 +4,13 @@ using System.Collections.Generic;
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 绑定集合 — 统一管理所有 AvrgMvvmBinding
|
||||
/// 绑定集合 — 统一管理所有 AvrgMvvmBinding,提供字符串索引器和前缀树快速查找
|
||||
/// </summary>
|
||||
public class AvrgMvvmBindingCollection : IEnumerable<AvrgMvvmBinding>
|
||||
{
|
||||
private List<AvrgMvvmBinding> _bindings = new List<AvrgMvvmBinding>();
|
||||
private AvrgMvvmScheduler _scheduler;
|
||||
private AvrgBindingTrie _trie;
|
||||
|
||||
/// <summary>
|
||||
/// 绑定的元数据集引用(可选)
|
||||
@@ -21,6 +22,11 @@ namespace XericUI.ReflectionUIGenerator
|
||||
/// </summary>
|
||||
public int Count => _bindings.Count;
|
||||
|
||||
/// <summary>
|
||||
/// 调度器引用(在 BuildMDVM 时设置)
|
||||
/// </summary>
|
||||
public AvrgMvvmScheduler Scheduler => _scheduler;
|
||||
|
||||
/// <summary>
|
||||
/// 添加绑定
|
||||
/// </summary>
|
||||
@@ -29,16 +35,53 @@ namespace XericUI.ReflectionUIGenerator
|
||||
_bindings.Add(binding);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置前缀树索引(由 BuildMDVM 构建后注入)
|
||||
/// </summary>
|
||||
internal void SetTrie(AvrgBindingTrie trie)
|
||||
{
|
||||
_trie = trie;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定索引的绑定
|
||||
/// </summary>
|
||||
public AvrgMvvmBinding this[int index] => _bindings[index];
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定成员名称的绑定
|
||||
/// 通过成员名访问绑定(如 bindingCollection["speed"])
|
||||
/// get: 从数据源读取当前值
|
||||
/// set: 走完整的 MVVM 管线:WriteFromData → MarkDataDirty → Flush → UI 同步
|
||||
/// </summary>
|
||||
public object this[string memberName]
|
||||
{
|
||||
get
|
||||
{
|
||||
var binding = FindByMemberName(memberName);
|
||||
return binding?.ReadFromDataSource();
|
||||
}
|
||||
set
|
||||
{
|
||||
var binding = FindByMemberName(memberName);
|
||||
if (binding != null && _scheduler != null)
|
||||
{
|
||||
binding.WriteFromData(value);
|
||||
_scheduler.MarkDataDirty(binding);
|
||||
_scheduler.Flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定成员名称的绑定(优先查 Trie,回退线性查找)
|
||||
/// </summary>
|
||||
public AvrgMvvmBinding FindByMemberName(string memberName)
|
||||
{
|
||||
if (_trie != null)
|
||||
{
|
||||
var found = _trie.Find(memberName) ?? _trie.FindByMemberName(memberName);
|
||||
if (found != null) return found;
|
||||
}
|
||||
return _bindings.Find(b => b.MetaData?.MemberName == memberName);
|
||||
}
|
||||
|
||||
@@ -72,6 +115,7 @@ namespace XericUI.ReflectionUIGenerator
|
||||
public void Clear()
|
||||
{
|
||||
_bindings.Clear();
|
||||
_trie?.Clear();
|
||||
_scheduler?.Clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,11 @@ namespace XericUI.ReflectionUIGenerator
|
||||
///
|
||||
/// 冲突解决策略:
|
||||
/// - 仅数据脏 → 通知 UI 更新
|
||||
/// - 仅 UI 脏 → 写入数据源
|
||||
/// - 仅 UI 脏 → 写入数据源(写入前校验,Invalid 驳回)
|
||||
/// - 双方都脏 → 走配置的冲突解决策略
|
||||
///
|
||||
/// 验证拦截:在 CommitToDataSource 前通过 Binding.ValidateValue() 校验。
|
||||
/// Invalid 值被驳回不写入,仅通知 UI 进入无效状态。
|
||||
/// </summary>
|
||||
public class AvrgMvvmScheduler
|
||||
{
|
||||
@@ -79,20 +82,36 @@ namespace XericUI.ReflectionUIGenerator
|
||||
|
||||
if (uiDirty && dataDirty)
|
||||
{
|
||||
// 双方都脏 — 走冲突解决
|
||||
// 双方都脏 — 走冲突解决(内部含验证)
|
||||
ResolveConflict(binding);
|
||||
}
|
||||
else if (dataDirty)
|
||||
{
|
||||
// 仅数据侧更新 → 通知 UI
|
||||
// 仅数据侧更新 → 验证后通知 UI
|
||||
var value = binding.DataCachedValue ?? binding.ReadFromDataSource();
|
||||
var state = binding.ValidateValue(value);
|
||||
binding.SetValidationState(state);
|
||||
|
||||
binding.CommitToDataSource(value);
|
||||
binding.NotifyUIUpdate(value);
|
||||
binding.NotifyUIUpdate(value, state);
|
||||
}
|
||||
else if (uiDirty)
|
||||
{
|
||||
// 仅 UI 侧更新 → 写入数据源
|
||||
binding.CommitToDataSource(binding.UICachedValue);
|
||||
// 仅 UI 侧更新 → 验证后写入数据源
|
||||
var value = binding.UICachedValue;
|
||||
var state = binding.ValidateValue(value);
|
||||
|
||||
if (state == ValidationState.Invalid)
|
||||
{
|
||||
// ★ 无效值:驳回不写入,仅通知 UI 进入无效态
|
||||
binding.SetValidationState(state);
|
||||
binding.NotifyUIUpdate(value, state);
|
||||
return;
|
||||
}
|
||||
|
||||
binding.SetValidationState(state);
|
||||
binding.CommitToDataSource(value);
|
||||
// UI 是发起方,不再通知自身(但验证状态变更需通知)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,8 +125,10 @@ namespace XericUI.ReflectionUIGenerator
|
||||
var resolvedValue = OnConflictDetected.Invoke(binding, DefaultStrategy);
|
||||
if (resolvedValue != null)
|
||||
{
|
||||
var state = binding.ValidateValue(resolvedValue);
|
||||
binding.SetValidationState(state);
|
||||
binding.CommitToDataSource(resolvedValue);
|
||||
binding.NotifyUIUpdate(resolvedValue);
|
||||
binding.NotifyUIUpdate(resolvedValue, state);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -120,8 +141,10 @@ namespace XericUI.ReflectionUIGenerator
|
||||
// 2. 检查 ResolverConfig 中的自定义策略
|
||||
if (_resolverConfig != null && _resolverConfig.TryResolve(binding, out object configResolvedValue))
|
||||
{
|
||||
var state = binding.ValidateValue(configResolvedValue);
|
||||
binding.SetValidationState(state);
|
||||
binding.CommitToDataSource(configResolvedValue);
|
||||
binding.NotifyUIUpdate(configResolvedValue);
|
||||
binding.NotifyUIUpdate(configResolvedValue, state);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -129,38 +152,70 @@ namespace XericUI.ReflectionUIGenerator
|
||||
switch (DefaultStrategy)
|
||||
{
|
||||
case ConflictResolutionStrategy.PreferUI:
|
||||
binding.CommitToDataSource(binding.UICachedValue);
|
||||
binding.NotifyUIUpdate(binding.UICachedValue);
|
||||
break;
|
||||
|
||||
case ConflictResolutionStrategy.PreferData:
|
||||
var dataValue = binding.DataCachedValue ?? binding.ReadFromDataSource();
|
||||
binding.CommitToDataSource(dataValue);
|
||||
binding.NotifyUIUpdate(dataValue);
|
||||
break;
|
||||
|
||||
case ConflictResolutionStrategy.CompareValues:
|
||||
// 比较UI值和数据值
|
||||
object uiVal = binding.UICachedValue;
|
||||
object dataVal = binding.DataCachedValue ?? binding.ReadFromDataSource();
|
||||
|
||||
if (Equals(uiVal, dataVal))
|
||||
{
|
||||
// 值相同,忽略冲突
|
||||
binding.NotifyUIUpdate(dataVal);
|
||||
var val = binding.UICachedValue;
|
||||
var state = binding.ValidateValue(val);
|
||||
if (state == ValidationState.Invalid)
|
||||
{
|
||||
binding.SetValidationState(state);
|
||||
binding.NotifyUIUpdate(val, state);
|
||||
return;
|
||||
}
|
||||
binding.SetValidationState(state);
|
||||
binding.CommitToDataSource(val);
|
||||
binding.NotifyUIUpdate(val, state);
|
||||
break;
|
||||
}
|
||||
|
||||
// 值不同,回退到 PreferUI
|
||||
binding.CommitToDataSource(binding.UICachedValue);
|
||||
binding.NotifyUIUpdate(binding.UICachedValue);
|
||||
Debug.LogWarning($"[AvrgMvvmScheduler] 值冲突(CompareValues回退PreferUI): {binding.MetaData?.MemberName}");
|
||||
break;
|
||||
case ConflictResolutionStrategy.PreferData:
|
||||
{
|
||||
var dataValue = binding.DataCachedValue ?? binding.ReadFromDataSource();
|
||||
var state = binding.ValidateValue(dataValue);
|
||||
binding.SetValidationState(state);
|
||||
binding.CommitToDataSource(dataValue);
|
||||
binding.NotifyUIUpdate(dataValue, state);
|
||||
break;
|
||||
}
|
||||
|
||||
case ConflictResolutionStrategy.CompareValues:
|
||||
{
|
||||
object uiVal = binding.UICachedValue;
|
||||
object dataVal = binding.DataCachedValue ?? binding.ReadFromDataSource();
|
||||
|
||||
if (Equals(uiVal, dataVal))
|
||||
{
|
||||
// 值相同,忽略冲突
|
||||
var state2 = binding.ValidateValue(dataVal);
|
||||
binding.SetValidationState(state2);
|
||||
binding.NotifyUIUpdate(dataVal, state2);
|
||||
break;
|
||||
}
|
||||
|
||||
// 值不同,回退到 PreferUI
|
||||
var val = binding.UICachedValue;
|
||||
var state = binding.ValidateValue(val);
|
||||
if (state == ValidationState.Invalid)
|
||||
{
|
||||
binding.SetValidationState(state);
|
||||
binding.NotifyUIUpdate(val, state);
|
||||
return;
|
||||
}
|
||||
binding.SetValidationState(state);
|
||||
binding.CommitToDataSource(val);
|
||||
binding.NotifyUIUpdate(val, state);
|
||||
Debug.LogWarning($"[AvrgMvvmScheduler] 值冲突(CompareValues回退PreferUI): {binding.MetaData?.MemberName}");
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
binding.CommitToDataSource(binding.UICachedValue);
|
||||
binding.NotifyUIUpdate(binding.UICachedValue);
|
||||
break;
|
||||
{
|
||||
var val = binding.UICachedValue;
|
||||
var state = binding.ValidateValue(val);
|
||||
binding.SetValidationState(state);
|
||||
binding.CommitToDataSource(val);
|
||||
binding.NotifyUIUpdate(val, state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 子属性值访问器 — 通过父访问器 + PropertyPath 访问结构的子字段
|
||||
///
|
||||
/// 用于拆分属性([XuiavrgSplit])场景,如 Vector3 的 x/y/z 分量。
|
||||
/// GetValue 通过反射读取父值的子字段/属性;SetValue 由绑定层的 WriteAction 处理。
|
||||
/// </summary>
|
||||
internal class AvrgSubPropertyAccessor : IAvrgValueAccessor
|
||||
{
|
||||
private readonly IAvrgValueAccessor _parentAccessor;
|
||||
private readonly string _propertyPath;
|
||||
private readonly AvrgMemberMetaData _metaData;
|
||||
|
||||
public AvrgSubPropertyAccessor(
|
||||
IAvrgValueAccessor parentAccessor,
|
||||
string propertyPath,
|
||||
AvrgMemberMetaData metaData)
|
||||
{
|
||||
_parentAccessor = parentAccessor ?? throw new ArgumentNullException(nameof(parentAccessor));
|
||||
_propertyPath = propertyPath ?? throw new ArgumentNullException(nameof(propertyPath));
|
||||
_metaData = metaData;
|
||||
}
|
||||
|
||||
public Type ValueType => _metaData?.ValueType ?? typeof(float);
|
||||
|
||||
public bool IsReadOnly => _parentAccessor.IsReadOnly;
|
||||
|
||||
public object GetValue()
|
||||
{
|
||||
var parentValue = _parentAccessor.GetValue();
|
||||
if (parentValue == null || string.IsNullOrEmpty(_propertyPath))
|
||||
return null;
|
||||
|
||||
var type = parentValue.GetType();
|
||||
var field = type.GetRuntimeField(_propertyPath);
|
||||
if (field != null) return field.GetValue(parentValue);
|
||||
|
||||
var prop = type.GetRuntimeProperty(_propertyPath);
|
||||
return prop?.GetValue(parentValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 子属性设置由绑定层的 WriteAction 统一处理(ReadSubProperty/WriteSubProperty),
|
||||
/// 此方法不应被直接调用。
|
||||
/// </summary>
|
||||
public void SetValue(object value)
|
||||
{
|
||||
var parentValue = _parentAccessor.GetValue();
|
||||
if (parentValue == null) return;
|
||||
|
||||
var type = parentValue.GetType();
|
||||
var field = type.GetRuntimeField(_propertyPath);
|
||||
if (field != null)
|
||||
{
|
||||
field.SetValue(parentValue, value);
|
||||
_parentAccessor.SetValue(parentValue);
|
||||
return;
|
||||
}
|
||||
|
||||
var prop = type.GetRuntimeProperty(_propertyPath);
|
||||
if (prop != null && prop.CanWrite)
|
||||
{
|
||||
prop.SetValue(parentValue, value);
|
||||
_parentAccessor.SetValue(parentValue);
|
||||
}
|
||||
}
|
||||
|
||||
public string FormatDisplay(object value)
|
||||
{
|
||||
if (_metaData?.FormatText != null && value is IFormattable formattable)
|
||||
return formattable.ToString(_metaData.FormatText, null);
|
||||
return value?.ToString() ?? "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c0ab49f7e492a8d448cdcc9f214325f6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a2aac04058602749be2f5153db24b69
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 预设选项合并模式
|
||||
/// 用于 Dropdown/ToggleGroup 的预设选项与已有选项(如 enum 名)的合并策略
|
||||
/// </summary>
|
||||
public enum PresetOptionsMergeMode
|
||||
{
|
||||
/// <summary>完全覆盖现有选项</summary>
|
||||
Replace,
|
||||
|
||||
/// <summary>保留两者交集</summary>
|
||||
Intersection,
|
||||
|
||||
/// <summary>保留两者并集(去重合并)</summary>
|
||||
Union,
|
||||
|
||||
/// <summary>从现有选项中移除预设选项(差集)</summary>
|
||||
Difference,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 值访问器 — 统一的数据读写通道
|
||||
/// 解耦"数据的物理存储方式"和"业务层的读写需求"
|
||||
///
|
||||
/// C# 反射对象和 Material Shader 属性通过各自实现此接口,
|
||||
/// 向 MVVM 绑定层暴露完全相同的数据交互约定。
|
||||
/// </summary>
|
||||
public interface IAvrgValueAccessor
|
||||
{
|
||||
/// <summary>值的 C# 类型</summary>
|
||||
Type ValueType { get; }
|
||||
|
||||
/// <summary>是否只读(如 Shader Keywords 不可运行时修改)</summary>
|
||||
bool IsReadOnly { get; }
|
||||
|
||||
/// <summary>从数据源读取当前值</summary>
|
||||
object GetValue();
|
||||
|
||||
/// <summary>将值写入数据源(IsReadOnly 时忽略)</summary>
|
||||
void SetValue(object value);
|
||||
|
||||
/// <summary>将值格式化为 UI 显示字符串</summary>
|
||||
string FormatDisplay(object value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 值访问器工厂 — 在"元数据模板"阶段创建,延迟到"绑定实例"时生产 Accessor
|
||||
///
|
||||
/// 设计意图:
|
||||
/// 1. 元数据提取阶段:只描述"如何访问数据",不绑定具体实例
|
||||
/// 2. BuildMDVM 阶段:传入实例,通过 Factory 生产 Accessor
|
||||
/// 3. 同一套元数据模板可复用于不同实例
|
||||
/// </summary>
|
||||
public interface IAvrgValueAccessorFactory
|
||||
{
|
||||
/// <summary>为给定目标实例创建值访问器</summary>
|
||||
/// <param name="targetInstance">目标实例(C# 对象、Material 等)</param>
|
||||
/// <returns>值访问器实例,targetInstance 类型不匹配时返回 null</returns>
|
||||
IAvrgValueAccessor CreateAccessor(object targetInstance);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b6a7d2f0806496e45b383e7d26eca214
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 值校验状态枚举
|
||||
/// Normal = 正常(通过校验),Warning = 警告(值在警告范围但放行),Invalid = 无效(值被驳回不写入)
|
||||
/// </summary>
|
||||
public enum ValidationState
|
||||
{
|
||||
Normal = 0,
|
||||
Warning = 1,
|
||||
Invalid = 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e20b793a32d670a4d95e20189dd27694
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -74,24 +74,33 @@ namespace XericUI.ReflectionUIGenerator
|
||||
/// <summary>
|
||||
/// 字段临时 Entry 实现类
|
||||
/// 作为数据对象和 UI 组件之间的中间缓存层
|
||||
///
|
||||
/// UI 交互事件的分配通过 XUIAvrgMDValue.BindToAccessor() 的多态机制完成,
|
||||
/// 各类 XUIAvrgMDValue 子类自行管理其专属 UI 组件的交互订阅。
|
||||
/// </summary>
|
||||
public class XuiFieldTempEntry : IFieldEntry
|
||||
{
|
||||
#region 对象池
|
||||
|
||||
internal static ObjectPool<XuiFieldTempEntry> Pool { get; private set; }
|
||||
|
||||
static XuiFieldTempEntry()
|
||||
private static ObjectPool<XuiFieldTempEntry> _pool;
|
||||
internal static ObjectPool<XuiFieldTempEntry> Pool
|
||||
{
|
||||
Pool = new ObjectPool<XuiFieldTempEntry>(
|
||||
createFunc: () => new XuiFieldTempEntry(),
|
||||
actionOnGet: entry => entry.OnEntryCreate(),
|
||||
actionOnRelease: entry => entry.OnEntryRelease(),
|
||||
actionOnDestroy: entry => entry.OnEntryDestroy(),
|
||||
collectionCheck: false,
|
||||
defaultCapacity: 32,
|
||||
maxSize: 512
|
||||
);
|
||||
get
|
||||
{
|
||||
if (_pool == null)
|
||||
{
|
||||
_pool = new ObjectPool<XuiFieldTempEntry>(
|
||||
createFunc: () => new XuiFieldTempEntry(),
|
||||
actionOnGet: entry => entry.OnEntryCreate(),
|
||||
actionOnRelease: entry => entry.OnEntryRelease(),
|
||||
actionOnDestroy: entry => entry.OnEntryDestroy(),
|
||||
collectionCheck: false,
|
||||
defaultCapacity: 32,
|
||||
maxSize: 512
|
||||
);
|
||||
}
|
||||
return _pool;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>从池中获取 Entry 实例</summary>
|
||||
@@ -113,7 +122,7 @@ namespace XericUI.ReflectionUIGenerator
|
||||
private GameObject _uiInstance;
|
||||
private GameObject _boundPrefab;
|
||||
private int _fieldOrder;
|
||||
private bool _uiBound; // 防止重复绑定
|
||||
private bool _uiBound;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -166,9 +175,6 @@ namespace XericUI.ReflectionUIGenerator
|
||||
|
||||
#region 初始化方法
|
||||
|
||||
/// <summary>
|
||||
/// 设置 Entry 基本属性
|
||||
/// </summary>
|
||||
public void Initialize(string entryName, XuiavrgFieldTag tag = XuiavrgFieldTag.None, int tagIndex = 0)
|
||||
{
|
||||
_entryName = entryName;
|
||||
@@ -179,18 +185,12 @@ namespace XericUI.ReflectionUIGenerator
|
||||
_uiBound = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置父 Entry(用于嵌套)
|
||||
/// </summary>
|
||||
public void SetParent(IFieldEntry parent)
|
||||
{
|
||||
_parent = parent;
|
||||
_entryPath = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加子 Entry
|
||||
/// </summary>
|
||||
public void AddChild(XuiFieldTempEntry child)
|
||||
{
|
||||
if (child == null) return;
|
||||
@@ -198,9 +198,6 @@ namespace XericUI.ReflectionUIGenerator
|
||||
_children.Add(child);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加 MVVM 绑定到此 Entry
|
||||
/// </summary>
|
||||
public void AddBinding(AvrgMvvmBinding binding, AvrgMvvmScheduler scheduler)
|
||||
{
|
||||
if (binding == null) return;
|
||||
@@ -253,7 +250,7 @@ namespace XericUI.ReflectionUIGenerator
|
||||
|
||||
/// <summary>
|
||||
/// 将 XUIAvrgMDValue 与 MVVM 绑定连接
|
||||
/// 支持主绑定 + 拆分属性子绑定(通过 BindingSlots)
|
||||
/// 通过多态 BindToAccessor 让 UI 子类自行管理交互事件和显示更新
|
||||
/// </summary>
|
||||
private void BindComponentWithBinding(XUIAvrgMDValue mdValue)
|
||||
{
|
||||
@@ -261,39 +258,27 @@ namespace XericUI.ReflectionUIGenerator
|
||||
|
||||
var primaryBinding = _bindings[0];
|
||||
|
||||
// ——— 1. 主绑定:数据 → UI ———
|
||||
primaryBinding.OnUIDataChanged += value => UpdateUIComponent(mdValue, value);
|
||||
// ★ 多态调用: UI 子类自行处理交互事件和显示更新
|
||||
mdValue.BindToAccessor(primaryBinding.Accessor, primaryBinding, _scheduler);
|
||||
|
||||
// ——— 主绑定:UI → 数据 ———
|
||||
WireUpUIEvents(mdValue, primaryBinding);
|
||||
|
||||
// ——— 2. 拆分属性子绑定匹配 ———
|
||||
// 拆分属性子绑定处理
|
||||
if (primaryBinding.SubBindings?.Count > 0 && mdValue.AvailableBindingSlots?.Count > 0)
|
||||
{
|
||||
// 收集所有子节点上的 XUIAvrgMDValue
|
||||
var childMDValues = new List<XUIAvrgMDValue>();
|
||||
mdValue.GetComponentsInChildren(true, childMDValues);
|
||||
|
||||
foreach (var slot in mdValue.AvailableBindingSlots)
|
||||
{
|
||||
// 按 propertyName 匹配子绑定
|
||||
var subBinding = primaryBinding.SubBindings.Find(
|
||||
sb => sb.MetaData?.PropertyPath == slot.propertyName);
|
||||
if (subBinding == null) continue;
|
||||
|
||||
// 在子节点中查找匹配 Tag 的 XUIAvrgMDValue
|
||||
foreach (var childMD in childMDValues)
|
||||
{
|
||||
if (childMD == mdValue) continue; // 跳过自身
|
||||
if (childMD == mdValue) continue;
|
||||
if (childMD.EntryTag == slot.tag || slot.tag == XuiavrgFieldTag.None)
|
||||
{
|
||||
// 子绑定:数据 → UI
|
||||
subBinding.OnUIDataChanged += value => UpdateUIComponent(childMD, value);
|
||||
|
||||
// 子绑定:UI → 数据(订阅子节点的交互事件)
|
||||
WireUpUIEvents(childMD, subBinding);
|
||||
|
||||
// 广播子元数据到子节点样式组件
|
||||
childMD.BindToAccessor(subBinding.Accessor, subBinding, _scheduler);
|
||||
childMD.InitializeWithMetaData(subBinding.MetaData);
|
||||
break;
|
||||
}
|
||||
@@ -301,104 +286,10 @@ namespace XericUI.ReflectionUIGenerator
|
||||
}
|
||||
}
|
||||
|
||||
// 广播主元数据到 XUIAvrgMDValue(触发 IXUIAvrgMDValueStyle 子组件)
|
||||
// 广播主元数据到样式组件
|
||||
mdValue.InitializeWithMetaData(primaryBinding.MetaData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 订阅 UI 组件的交互事件 → 写入绑定
|
||||
/// </summary>
|
||||
private void WireUpUIEvents(XUIAvrgMDValue mdValue, AvrgMvvmBinding binding)
|
||||
{
|
||||
if (mdValue.TryGetComponent(out UnityEngine.UI.Button uiButton))
|
||||
{
|
||||
uiButton.onClick.AddListener(() =>
|
||||
{
|
||||
binding.WriteFromUI(true);
|
||||
_scheduler?.MarkUIDirty(binding);
|
||||
});
|
||||
}
|
||||
|
||||
if (mdValue.TryGetComponent(out UnityEngine.UI.Toggle uiToggle))
|
||||
{
|
||||
uiToggle.onValueChanged.AddListener(val =>
|
||||
{
|
||||
binding.WriteFromUI(val);
|
||||
_scheduler?.MarkUIDirty(binding);
|
||||
});
|
||||
}
|
||||
|
||||
if (mdValue.TryGetComponent(out UnityEngine.UI.Slider uiSlider))
|
||||
{
|
||||
uiSlider.onValueChanged.AddListener(val =>
|
||||
{
|
||||
binding.WriteFromUI(val);
|
||||
_scheduler?.MarkUIDirty(binding);
|
||||
});
|
||||
}
|
||||
|
||||
if (mdValue.TryGetComponent(out UnityEngine.UI.InputField uiInput))
|
||||
{
|
||||
uiInput.onEndEdit.AddListener(val =>
|
||||
{
|
||||
binding.WriteFromUI(val);
|
||||
_scheduler?.MarkUIDirty(binding);
|
||||
});
|
||||
}
|
||||
|
||||
if (mdValue.TryGetComponent(out UnityEngine.UI.Dropdown uiDropdown))
|
||||
{
|
||||
uiDropdown.onValueChanged.AddListener(val =>
|
||||
{
|
||||
binding.WriteFromUI(val);
|
||||
_scheduler?.MarkUIDirty(binding);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新 UI 组件的显示值(通过 XUIAvrgMDValue 所在 GameObject 上的具体组件)
|
||||
/// </summary>
|
||||
private static void UpdateUIComponent(XUIAvrgMDValue mdValue, object value)
|
||||
{
|
||||
if (mdValue == null || value == null) return;
|
||||
|
||||
// Text
|
||||
if (mdValue.TryGetComponent(out UnityEngine.UI.Text uiText))
|
||||
{
|
||||
uiText.text = value.ToString();
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle
|
||||
if (value is bool boolVal && mdValue.TryGetComponent(out UnityEngine.UI.Toggle uiToggle))
|
||||
{
|
||||
uiToggle.isOn = boolVal;
|
||||
return;
|
||||
}
|
||||
|
||||
// Slider
|
||||
if (value is float floatVal && mdValue.TryGetComponent(out UnityEngine.UI.Slider uiSlider))
|
||||
{
|
||||
uiSlider.value = floatVal;
|
||||
return;
|
||||
}
|
||||
|
||||
// InputField
|
||||
if (mdValue.TryGetComponent(out UnityEngine.UI.InputField uiInput))
|
||||
{
|
||||
uiInput.text = value.ToString();
|
||||
return;
|
||||
}
|
||||
|
||||
// Dropdown
|
||||
if (value is int intVal && mdValue.TryGetComponent(out UnityEngine.UI.Dropdown uiDropdown))
|
||||
{
|
||||
uiDropdown.value = intVal;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 生命周期
|
||||
@@ -417,15 +308,14 @@ namespace XericUI.ReflectionUIGenerator
|
||||
foreach (var binding in _bindings)
|
||||
{
|
||||
var value = binding.ReadFromDataSource();
|
||||
binding.NotifyUIUpdate(value);
|
||||
binding.NotifyUIUpdate(value, ValidationState.Normal);
|
||||
|
||||
// 子绑定也做初始值推送
|
||||
if (binding.SubBindings?.Count > 0)
|
||||
{
|
||||
foreach (var subBinding in binding.SubBindings)
|
||||
{
|
||||
var subValue = subBinding.ReadFromDataSource();
|
||||
subBinding.NotifyUIUpdate(subValue);
|
||||
subBinding.NotifyUIUpdate(subValue, ValidationState.Normal);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -446,7 +336,6 @@ namespace XericUI.ReflectionUIGenerator
|
||||
foreach (var binding in _bindings)
|
||||
{
|
||||
binding.ClearUIEvent();
|
||||
// 子绑定也清理
|
||||
if (binding.SubBindings?.Count > 0)
|
||||
{
|
||||
foreach (var sub in binding.SubBindings)
|
||||
|
||||
@@ -11,41 +11,56 @@ namespace XericUI.ReflectionUIGenerator
|
||||
public enum XuiavrgFieldTag
|
||||
{
|
||||
None = 0,
|
||||
|
||||
|
||||
/// <summary>标题文本 / Label</summary>
|
||||
Label = 1 << 0,
|
||||
|
||||
|
||||
/// <summary>内容值 / 数值上下文</summary>
|
||||
Context = 1 << 1,
|
||||
|
||||
|
||||
/// <summary>单位文本</summary>
|
||||
Unit = 1 << 2,
|
||||
|
||||
|
||||
/// <summary>布尔开关 / Toggle</summary>
|
||||
Toggle = 1 << 3,
|
||||
|
||||
|
||||
/// <summary>按钮触发器</summary>
|
||||
Button = 1 << 4,
|
||||
|
||||
|
||||
/// <summary>滑块 / 进度条</summary>
|
||||
Slider = 1 << 5,
|
||||
|
||||
|
||||
/// <summary>下拉选择</summary>
|
||||
Dropdown = 1 << 6,
|
||||
|
||||
|
||||
/// <summary>输入值 / InputField</summary>
|
||||
Input = 1 << 7,
|
||||
|
||||
|
||||
/// <summary>单选项 / Radio</summary>
|
||||
Select = 1 << 8,
|
||||
|
||||
|
||||
/// <summary>符号 / 图标</summary>
|
||||
Sign = 1 << 9,
|
||||
|
||||
|
||||
/// <summary>编辑区域 / 多行文本</summary>
|
||||
Edit = 1 << 10,
|
||||
|
||||
|
||||
/// <summary>输出区域 / 只读展示</summary>
|
||||
Output = 1 << 11,
|
||||
|
||||
/// <summary>颜色 / Color</summary>
|
||||
Color = 1 << 12,
|
||||
|
||||
/// <summary>纹理预览 / Texture</summary>
|
||||
Texture = 1 << 13,
|
||||
|
||||
/// <summary>向量值组 / Vector (Vector2/3/4)</summary>
|
||||
Vector = 1 << 14,
|
||||
|
||||
/// <summary>开关组 / ToggleGroup</summary>
|
||||
ToggleGroup = 1 << 15,
|
||||
|
||||
/// <summary>只读展示 / ReadOnly</summary>
|
||||
ReadOnly = 1 << 16,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +85,20 @@ namespace XericUI.ReflectionUIGenerator
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity 生命周期
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
XuiavrgStyleDefaults.EnsureDefaults();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
ClearAll();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 核心生命周期
|
||||
|
||||
public void BuildFromMetaDataSet(AvrgMemberMetaDataSet metaDataSet,
|
||||
@@ -113,16 +127,17 @@ namespace XericUI.ReflectionUIGenerator
|
||||
|
||||
if (bindingCollection != null)
|
||||
{
|
||||
var matchedBindings = bindingCollection.FindByMemberName(metaData.MemberName);
|
||||
if (matchedBindings != null)
|
||||
var scheduler = bindingCollection.Scheduler;
|
||||
var matchedBinding = bindingCollection.FindByMemberName(metaData.MemberName);
|
||||
if (matchedBinding != null)
|
||||
{
|
||||
entry.AddBinding(matchedBindings, null);
|
||||
entry.AddBinding(matchedBinding, scheduler);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var binding in bindingCollection)
|
||||
{
|
||||
entry.AddBinding(binding, null);
|
||||
entry.AddBinding(binding, scheduler);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -142,13 +157,14 @@ namespace XericUI.ReflectionUIGenerator
|
||||
|
||||
if (CurrentEntries != null && bindingCollection != null)
|
||||
{
|
||||
var scheduler = bindingCollection.Scheduler;
|
||||
foreach (var entry in CurrentEntries)
|
||||
{
|
||||
if (entry is XuiFieldTempEntry tempEntry)
|
||||
{
|
||||
var matched = bindingCollection.FindByMemberName(entry.EntryName);
|
||||
if (matched != null)
|
||||
tempEntry.AddBinding(matched, null);
|
||||
tempEntry.AddBinding(matched, scheduler);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -443,12 +459,7 @@ namespace XericUI.ReflectionUIGenerator
|
||||
|
||||
_leafGroupMap.Clear();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
ClearAll();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -2,6 +2,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
@@ -18,19 +20,25 @@ namespace XericUI.ReflectionUIGenerator
|
||||
yield return property;
|
||||
}
|
||||
|
||||
// ============ C# 对象: 元数据提取(纯元数据,不绑定实例)============
|
||||
|
||||
/// <summary>
|
||||
/// 从任意对象提取 AvrgMemberMetaData 元数据集
|
||||
/// 反射识别成员、识别 IXuiavrgAttribute 标记、执行标记绑定的完整流程
|
||||
/// 从类型提取 AvrgMemberMetaData 元数据集(不含实例绑定)
|
||||
/// 用于构建元数据模板,可复用于不同实例
|
||||
/// </summary>
|
||||
/// <param name="source">待提取的数据对象</param>
|
||||
/// <returns>提取完成的元数据集</returns>
|
||||
public static AvrgMemberMetaDataSet ExtractAvrgMemberMetaData(this object source)
|
||||
/// <param name="type">待提取的类型</param>
|
||||
/// <returns>纯元数据集(无实例引用,由 AccessorFactory 持有反射信息)</returns>
|
||||
public static AvrgMemberMetaDataSet ExtractAvrgMemberMetaData(this Type type)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
if (type == null)
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
|
||||
var metaDataSet = new AvrgMemberMetaDataSet();
|
||||
var type = source.GetType();
|
||||
|
||||
// ★ 收集类级别 IXuiavrgAttribute — 作为所有字段的基础模板
|
||||
var classAttributes = type.GetCustomAttributes()
|
||||
.OfType<IXuiavrgAttribute>()
|
||||
.ToList();
|
||||
|
||||
foreach (var memberInfo in type.GetRuntimeMemberInfo())
|
||||
{
|
||||
@@ -39,55 +47,39 @@ namespace XericUI.ReflectionUIGenerator
|
||||
if (hideAttr != null && hideAttr.Hide)
|
||||
continue;
|
||||
|
||||
// 创建元数据
|
||||
// 识别值类型
|
||||
Type valueType = memberInfo switch
|
||||
{
|
||||
PropertyInfo pi => pi.PropertyType,
|
||||
FieldInfo fi => fi.FieldType,
|
||||
_ => typeof(object)
|
||||
};
|
||||
|
||||
// 创建元数据 — 不持有 MemberInfo 和 TargetInstance
|
||||
var metaData = new AvrgMemberMetaData
|
||||
{
|
||||
MemberName = memberInfo.Name,
|
||||
MemberInfo = memberInfo,
|
||||
TargetInstance = source,
|
||||
ValueType = valueType,
|
||||
AccessorFactory = new ReflectionAccessorFactory(memberInfo),
|
||||
PropertyCheckKey = valueType?.FullName,
|
||||
};
|
||||
|
||||
// 识别值类型和 IsProperty
|
||||
switch (memberInfo)
|
||||
// ★ 第1步: 先应用类级特性(设置默认值)
|
||||
foreach (var classAttr in classAttributes)
|
||||
{
|
||||
case PropertyInfo pi:
|
||||
metaData.IsProperty = true;
|
||||
metaData.ValueType = pi.PropertyType;
|
||||
break;
|
||||
case FieldInfo fi:
|
||||
metaData.IsProperty = false;
|
||||
metaData.ValueType = fi.FieldType;
|
||||
break;
|
||||
classAttr.BindMetaData(metaData);
|
||||
}
|
||||
|
||||
// 提取字段默认值(类初始值)
|
||||
// Unity 序列化字段可以通过反射读取实例上的当前值作为"默认"参考
|
||||
// 但更准确的在类声明阶段的初始值需要通过运行时观察或 Fallback 判断
|
||||
// 此处策略:如果 TargetInstance 非 null,直接 ReadValue 作为 DefaultValue
|
||||
if (source != null && memberInfo is FieldInfo fi2)
|
||||
{
|
||||
metaData.DefaultValue = fi2.GetValue(source);
|
||||
}
|
||||
else if (source != null && memberInfo is PropertyInfo pi2 && pi2.CanRead)
|
||||
{
|
||||
metaData.DefaultValue = pi2.GetValue(source);
|
||||
}
|
||||
|
||||
// 设置 PropertyCheckKey 默认值(用类型全称)
|
||||
if (metaData.ValueType != null)
|
||||
metaData.PropertyCheckKey = metaData.ValueType.FullName;
|
||||
|
||||
// 收集所有 IXuiavrgAttribute
|
||||
var attributes = memberInfo.GetCustomAttributes()
|
||||
// ★ 第2步: 再应用字段级特性(可覆盖类级设置)
|
||||
var fieldAttributes = memberInfo.GetCustomAttributes()
|
||||
.OfType<IXuiavrgAttribute>()
|
||||
.ToList();
|
||||
|
||||
metaData.Attributes = attributes;
|
||||
metaData.Attributes = fieldAttributes;
|
||||
|
||||
// 按序执行 BindMetaData
|
||||
foreach (var attr in attributes)
|
||||
foreach (var fieldAttr in fieldAttributes)
|
||||
{
|
||||
attr.BindMetaData(metaData);
|
||||
fieldAttr.BindMetaData(metaData);
|
||||
}
|
||||
|
||||
metaDataSet.Add(metaData);
|
||||
@@ -103,11 +95,147 @@ namespace XericUI.ReflectionUIGenerator
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从任意对象提取 AvrgMemberMetaData 元数据集的泛型版本
|
||||
/// 从对象实例提取元数据并一步生成绑定(便捷方法)
|
||||
/// 内部调用 Type.ExtractAvrgMemberMetaData() + BuildMDVM()
|
||||
/// </summary>
|
||||
public static AvrgMemberMetaDataSet ExtractAvrgMemberMetaData<T>(this T source)
|
||||
public static AvrgMvvmBindingCollection BuildBindingsFromObject(
|
||||
this object source,
|
||||
AvrgMvvmResolverConfig resolverConfig = null)
|
||||
{
|
||||
return ExtractAvrgMemberMetaData((object)source);
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
|
||||
var metaDataSet = source.GetType().ExtractAvrgMemberMetaData();
|
||||
return metaDataSet.BuildMDVM(source, resolverConfig);
|
||||
}
|
||||
|
||||
// ============ Material: 元数据提取 ============
|
||||
|
||||
/// <summary>
|
||||
/// 从 Material 的 Shader 属性中提取元数据集
|
||||
/// </summary>
|
||||
/// <param name="material">目标 Material</param>
|
||||
/// <returns>纯元数据集(AccessorFactory 持有 MaterialPropertyAccessorFactory)</returns>
|
||||
public static AvrgMemberMetaDataSet ExtractFromMaterial(this Material material)
|
||||
{
|
||||
if (material == null)
|
||||
throw new ArgumentNullException(nameof(material));
|
||||
|
||||
var shader = material.shader;
|
||||
if (shader == null)
|
||||
{
|
||||
Debug.LogError("[Xuiavrg] Material 没有关联的 Shader");
|
||||
return null;
|
||||
}
|
||||
|
||||
var metaDataSet = new AvrgMemberMetaDataSet();
|
||||
int propCount = shader.GetPropertyCount();
|
||||
|
||||
for (int i = 0; i < propCount; i++)
|
||||
{
|
||||
var propName = shader.GetPropertyName(i);
|
||||
var shaderType = shader.GetPropertyType(i);
|
||||
var desc = shader.GetPropertyDescription(i);
|
||||
var flags = shader.GetPropertyFlags(i);
|
||||
|
||||
// 跳过隐藏属性
|
||||
if ((flags & UnityEngine.Rendering.ShaderPropertyFlags.HideInInspector) != 0)
|
||||
continue;
|
||||
|
||||
// ShaderPropertyType → C# Type + Tag
|
||||
(Type valueType, XuiavrgFieldTag tag) = MapShaderPropertyType(shaderType, propName);
|
||||
|
||||
var metaData = new AvrgMemberMetaData
|
||||
{
|
||||
MemberName = propName,
|
||||
ValueType = valueType,
|
||||
LabelName = !string.IsNullOrEmpty(desc) ? desc : FormatPropertyName(propName),
|
||||
Tag = tag,
|
||||
AccessorFactory = new MaterialPropertyAccessorFactory(propName, shaderType, valueType),
|
||||
};
|
||||
|
||||
// 读取当前值作为 DefaultValue
|
||||
metaData.DefaultValue = ReadMaterialProperty(material, propName, shaderType);
|
||||
|
||||
// Range 属性:提取范围限制
|
||||
if (shaderType == ShaderPropertyType.Range)
|
||||
{
|
||||
var limits = shader.GetPropertyRangeLimits(i);
|
||||
metaData.RangeMin = limits.x;
|
||||
metaData.RangeMax = limits.y;
|
||||
}
|
||||
|
||||
metaDataSet.Add(metaData);
|
||||
}
|
||||
|
||||
// ===== Shader Keywords 提取(只读)=====
|
||||
try
|
||||
{
|
||||
var keywordSpace = shader.keywordSpace;
|
||||
foreach (var kw in keywordSpace.keywords)
|
||||
{
|
||||
bool isEnabled = material.IsKeywordEnabled(kw.name);
|
||||
var kwMetaData = new AvrgMemberMetaData
|
||||
{
|
||||
MemberName = $"kwd_{kw.name}",
|
||||
ValueType = typeof(bool),
|
||||
LabelName = $"[Keyword] {kw.name}",
|
||||
Tag = XuiavrgFieldTag.Toggle,
|
||||
DefaultValue = isEnabled,
|
||||
AccessorFactory = new MaterialPropertyAccessorFactory(
|
||||
kw.name,
|
||||
ShaderPropertyType.Float, // Keywords 不是标准属性,用 Float 占位
|
||||
typeof(bool),
|
||||
isReadOnly: true),
|
||||
};
|
||||
metaDataSet.Add(kwMetaData);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogWarning($"[Xuiavrg] 提取 Shader Keywords 失败: {e.Message}");
|
||||
}
|
||||
|
||||
metaDataSet.SortByFieldOrder();
|
||||
return metaDataSet;
|
||||
}
|
||||
|
||||
private static (Type valueType, XuiavrgFieldTag tag) MapShaderPropertyType(
|
||||
ShaderPropertyType shaderType, string propName)
|
||||
{
|
||||
return shaderType switch
|
||||
{
|
||||
ShaderPropertyType.Float => (typeof(float), XuiavrgFieldTag.Input),
|
||||
ShaderPropertyType.Range => (typeof(float), XuiavrgFieldTag.Slider),
|
||||
ShaderPropertyType.Int => (typeof(int), XuiavrgFieldTag.Input),
|
||||
ShaderPropertyType.Color => (typeof(Color), XuiavrgFieldTag.Color),
|
||||
ShaderPropertyType.Vector => (typeof(Vector4), XuiavrgFieldTag.Vector),
|
||||
ShaderPropertyType.Texture => (typeof(Texture), XuiavrgFieldTag.Texture),
|
||||
_ => (typeof(object), XuiavrgFieldTag.None),
|
||||
};
|
||||
}
|
||||
|
||||
private static object ReadMaterialProperty(Material material, string propName,
|
||||
ShaderPropertyType shaderType)
|
||||
{
|
||||
return shaderType switch
|
||||
{
|
||||
ShaderPropertyType.Float or ShaderPropertyType.Range => material.GetFloat(propName),
|
||||
ShaderPropertyType.Int => material.GetInt(propName),
|
||||
ShaderPropertyType.Color => material.GetColor(propName),
|
||||
ShaderPropertyType.Vector => material.GetVector(propName),
|
||||
ShaderPropertyType.Texture => material.GetTexture(propName),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 属性名格式化(_MainTex → MainTex, _ShadowColor → Shadow Color)
|
||||
/// </summary>
|
||||
private static string FormatPropertyName(string propName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(propName)) return propName;
|
||||
return propName.TrimStart('_').Replace('_', ' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c4541939417af914bab5ce5fcc60b4b7
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,49 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Color 值组件 — 通过 Image 显示颜色,可选点击弹出颜色选择器
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI Action Vessel/Values/XUIAvrgMDColorValue")]
|
||||
public class XUIAvrgMDColorValue : XUIAvrgMDValue
|
||||
{
|
||||
[SerializeField, Tooltip("关联的 Image 组件(未设置则自动查找)")]
|
||||
private Image _colorImage;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_colorImage == null) _colorImage = GetComponent<Image>();
|
||||
}
|
||||
|
||||
public override void BindToAccessor(
|
||||
IAvrgValueAccessor accessor,
|
||||
AvrgMvvmBinding binding,
|
||||
AvrgMvvmScheduler scheduler)
|
||||
{
|
||||
CurrentAccessor = accessor;
|
||||
CurrentBinding = binding;
|
||||
|
||||
if (binding == null || accessor == null) return;
|
||||
|
||||
binding.OnUIDataChanged += (value, state) =>
|
||||
{
|
||||
UpdateValueDisplay(value, state);
|
||||
};
|
||||
binding.OnValidationStateChanged += OnValidationStateChanged;
|
||||
|
||||
var initialValue = accessor.GetValue();
|
||||
UpdateValueDisplay(initialValue, ValidationState.Normal);
|
||||
}
|
||||
|
||||
protected override void UpdateValueDisplay(object value, ValidationState state)
|
||||
{
|
||||
// 值显示:刷新 Image.color
|
||||
if (_colorImage != null && value is Color c)
|
||||
_colorImage.color = c;
|
||||
|
||||
base.UpdateValueDisplay(value, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 529d91d01ee305847a76f071fc11ad88
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,175 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Dropdown 值组件 — 处理 string / int / enum 类型的下拉选择交互
|
||||
/// 预设选项来源:元数据的 PresetOptions 或 enum 类型的枚举名
|
||||
///
|
||||
/// 选项动态创建:OnPoolGet 时根据元数据 PresetOptions 构建选项列表,
|
||||
/// BindToAccessor 时通过 AddOptions() 填充到 Dropdown 组件。
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI Action Vessel/Values/XUIAvrgMDDropdownValue")]
|
||||
public class XUIAvrgMDDropdownValue : XUIAvrgMDValue
|
||||
{
|
||||
[SerializeField, Tooltip("关联的 Dropdown 组件(未设置则自动查找)")]
|
||||
#if dUI_TextMeshPro
|
||||
private TMPro.TMP_Dropdown _dropdown;
|
||||
#else
|
||||
private Dropdown _dropdown;
|
||||
#endif
|
||||
|
||||
[Header("选项动态创建")]
|
||||
[SerializeField, Tooltip("选项项预制体 Transform(可选,用于自定义选项创建逻辑)")]
|
||||
private Transform _optionItemPrefab;
|
||||
|
||||
private List<string> _options;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
#if dUI_TextMeshPro
|
||||
if (_dropdown == null) _dropdown = GetComponent<TMPro.TMP_Dropdown>();
|
||||
#else
|
||||
if (_dropdown == null) _dropdown = GetComponent<Dropdown>();
|
||||
#endif
|
||||
}
|
||||
|
||||
// ============ 对象池生命周期 ============
|
||||
|
||||
public override void OnPoolGet(AvrgMemberMetaData metaData)
|
||||
{
|
||||
base.OnPoolGet(metaData);
|
||||
_options = BuildOptions(metaData?.ValueType);
|
||||
|
||||
// 动态创建选项对象(子类可覆写)
|
||||
CreateOptionItems();
|
||||
}
|
||||
|
||||
public override void OnPoolRelease()
|
||||
{
|
||||
if (_dropdown != null)
|
||||
_dropdown.onValueChanged.RemoveAllListeners();
|
||||
|
||||
DestroyOptionItems();
|
||||
_options = null;
|
||||
base.OnPoolRelease();
|
||||
}
|
||||
|
||||
// ============ 动态选项创建 ============
|
||||
|
||||
/// <summary>
|
||||
/// 根据预设选项动态创建选项子对象
|
||||
/// 子类可覆写以实现自定义选项创建(如图标、颜色等)
|
||||
/// </summary>
|
||||
protected virtual void CreateOptionItems()
|
||||
{
|
||||
if (_optionItemPrefab == null || _options == null) return;
|
||||
|
||||
for (int i = 0; i < _options.Count; i++)
|
||||
{
|
||||
var item = Instantiate(_optionItemPrefab, _optionItemPrefab.parent);
|
||||
item.name = $"Option_{i}_{_options[i]}";
|
||||
item.gameObject.SetActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 销毁动态创建的选项子对象
|
||||
/// </summary>
|
||||
protected virtual void DestroyOptionItems()
|
||||
{
|
||||
if (_optionItemPrefab == null) return;
|
||||
|
||||
var parent = _optionItemPrefab.parent;
|
||||
for (int i = parent.childCount - 1; i >= 0; i--)
|
||||
{
|
||||
var child = parent.GetChild(i);
|
||||
if (child != _optionItemPrefab)
|
||||
Destroy(child.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 核心绑定 ============
|
||||
|
||||
public override void BindToAccessor(
|
||||
IAvrgValueAccessor accessor,
|
||||
AvrgMvvmBinding binding,
|
||||
AvrgMvvmScheduler scheduler)
|
||||
{
|
||||
CurrentAccessor = accessor;
|
||||
CurrentBinding = binding;
|
||||
|
||||
if (binding == null || accessor == null || _dropdown == null) return;
|
||||
|
||||
// 填充选项
|
||||
_dropdown.ClearOptions();
|
||||
_dropdown.AddOptions(_options);
|
||||
|
||||
// UI → 数据
|
||||
_dropdown.onValueChanged.AddListener(index =>
|
||||
{
|
||||
if (index >= 0 && index < _options.Count)
|
||||
{
|
||||
object convertedVal = ConvertOptionToValue(_options[index], accessor.ValueType);
|
||||
binding.WriteFromUI(convertedVal);
|
||||
scheduler?.MarkUIDirty(binding);
|
||||
}
|
||||
});
|
||||
|
||||
// 数据 → UI(带验证状态)
|
||||
binding.OnUIDataChanged += (value, state) =>
|
||||
{
|
||||
int idx = FindOptionIndex(value);
|
||||
if (idx >= 0)
|
||||
_dropdown.SetValueWithoutNotify(idx);
|
||||
|
||||
base.UpdateValueDisplay(value, state);
|
||||
};
|
||||
|
||||
// 验证状态变更
|
||||
binding.OnValidationStateChanged += OnValidationStateChanged;
|
||||
|
||||
// 首次同步
|
||||
var initialValue = accessor.GetValue();
|
||||
int initialIdx = FindOptionIndex(initialValue);
|
||||
if (initialIdx >= 0)
|
||||
_dropdown.SetValueWithoutNotify(initialIdx);
|
||||
UpdateValueDisplay(initialValue, ValidationState.Normal);
|
||||
}
|
||||
|
||||
private List<string> BuildOptions(Type valueType)
|
||||
{
|
||||
if (BoundMetaData?.PresetOptions != null && BoundMetaData.PresetOptions.Count > 0)
|
||||
return BoundMetaData.PresetOptions.ToList();
|
||||
|
||||
if (valueType != null && valueType.IsEnum)
|
||||
return Enum.GetNames(valueType).ToList();
|
||||
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
private int FindOptionIndex(object value)
|
||||
{
|
||||
if (value == null || _options == null || _options.Count == 0) return -1;
|
||||
|
||||
string valueStr = value.ToString();
|
||||
for (int i = 0; i < _options.Count; i++)
|
||||
{
|
||||
if (_options[i] == valueStr) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static object ConvertOptionToValue(string option, Type targetType)
|
||||
{
|
||||
if (targetType == typeof(string)) return option;
|
||||
if (targetType == typeof(int) && int.TryParse(option, out int intVal)) return intVal;
|
||||
if (targetType != null && targetType.IsEnum)
|
||||
return Enum.Parse(targetType, option);
|
||||
return option;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0328bccdc7751da418bd32d7ba8cb0b4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,167 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// InputField 值组件 — 处理 string / float / int / double 类型的文本输入交互
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI Action Vessel/Values/XUIAvrgMDInputFieldValue")]
|
||||
public class XUIAvrgMDInputFieldValue : XUIAvrgMDValue
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("关联的 InputField 组件")]
|
||||
#if dUI_TextMeshPro
|
||||
private TMPro.TMP_InputField _inputField;
|
||||
#else
|
||||
private InputField _inputField;
|
||||
#endif
|
||||
|
||||
[Header("InputField 专属插槽")]
|
||||
[SerializeField, Tooltip("增加按钮(点击按步进值 +)")]
|
||||
private Button _incrementButton;
|
||||
|
||||
[SerializeField, Tooltip("减少按钮(点击按步进值 -)")]
|
||||
private Button _decrementButton;
|
||||
|
||||
private const float DefaultStep = 1f;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
#if dUI_TextMeshPro
|
||||
if (_inputField == null) _inputField = GetComponent<TMPro.TMP_InputField>();
|
||||
#else
|
||||
if (_inputField == null) _inputField = GetComponent<InputField>();
|
||||
#endif
|
||||
}
|
||||
|
||||
// ============ 对象池生命周期 ============
|
||||
|
||||
public override void OnPoolRelease()
|
||||
{
|
||||
if (_incrementButton != null)
|
||||
_incrementButton.onClick.RemoveAllListeners();
|
||||
if (_decrementButton != null)
|
||||
_decrementButton.onClick.RemoveAllListeners();
|
||||
if (_inputField != null)
|
||||
_inputField.onEndEdit.RemoveAllListeners();
|
||||
|
||||
base.OnPoolRelease();
|
||||
}
|
||||
|
||||
// ============ 核心绑定 ============
|
||||
|
||||
public override void BindToAccessor(
|
||||
IAvrgValueAccessor accessor,
|
||||
AvrgMvvmBinding binding,
|
||||
AvrgMvvmScheduler scheduler)
|
||||
{
|
||||
CurrentAccessor = accessor;
|
||||
CurrentBinding = binding;
|
||||
|
||||
if (binding == null || accessor == null || _inputField == null) return;
|
||||
|
||||
// 根据类型设置输入模式
|
||||
#if dUI_TextMeshPro
|
||||
if (accessor.ValueType == typeof(float) || accessor.ValueType == typeof(int)
|
||||
|| accessor.ValueType == typeof(double))
|
||||
{
|
||||
_inputField.contentType = TMPro.TMP_InputField.ContentType.DecimalNumber;
|
||||
}
|
||||
#else
|
||||
if (accessor.ValueType == typeof(float) || accessor.ValueType == typeof(int)
|
||||
|| accessor.ValueType == typeof(double))
|
||||
{
|
||||
_inputField.contentType = InputField.ContentType.DecimalNumber;
|
||||
}
|
||||
#endif
|
||||
|
||||
// 增减按钮
|
||||
if (_incrementButton != null)
|
||||
_incrementButton.onClick.AddListener(OnIncrementClicked);
|
||||
if (_decrementButton != null)
|
||||
_decrementButton.onClick.AddListener(OnDecrementClicked);
|
||||
|
||||
// UI → 数据
|
||||
_inputField.onEndEdit.AddListener(val =>
|
||||
{
|
||||
object convertedVal = ConvertStringToValue(val, accessor.ValueType);
|
||||
binding.WriteFromUI(convertedVal);
|
||||
scheduler?.MarkUIDirty(binding);
|
||||
});
|
||||
|
||||
// 数据 → UI(带验证状态)
|
||||
binding.OnUIDataChanged += (value, state) =>
|
||||
{
|
||||
_inputField.SetTextWithoutNotify(accessor.FormatDisplay(value));
|
||||
base.UpdateValueDisplay(value, state);
|
||||
};
|
||||
|
||||
// 验证状态变更
|
||||
binding.OnValidationStateChanged += OnValidationStateChanged;
|
||||
|
||||
// 首次同步
|
||||
var initialValue = accessor.GetValue();
|
||||
_inputField.SetTextWithoutNotify(accessor.FormatDisplay(initialValue));
|
||||
UpdateValueDisplay(initialValue, ValidationState.Normal);
|
||||
}
|
||||
|
||||
// ============ 步进 ============
|
||||
|
||||
private void OnIncrementClicked()
|
||||
{
|
||||
if (CurrentAccessor?.IsReadOnly == true || _inputField == null) return;
|
||||
if (!float.TryParse(_inputField.text, out float current)) return;
|
||||
float newVal = current + DefaultStep;
|
||||
_inputField.text = CurrentAccessor.FormatDisplay(
|
||||
ConvertValueForAccessor(newVal, CurrentAccessor.ValueType));
|
||||
}
|
||||
|
||||
private void OnDecrementClicked()
|
||||
{
|
||||
if (CurrentAccessor?.IsReadOnly == true || _inputField == null) return;
|
||||
if (!float.TryParse(_inputField.text, out float current)) return;
|
||||
float newVal = current - DefaultStep;
|
||||
_inputField.text = CurrentAccessor.FormatDisplay(
|
||||
ConvertValueForAccessor(newVal, CurrentAccessor.ValueType));
|
||||
}
|
||||
|
||||
// ============ 验证状态回调 ============
|
||||
|
||||
protected override void OnValidationStateChanged(ValidationState state)
|
||||
{
|
||||
base.OnValidationStateChanged(state);
|
||||
|
||||
// InputField 文字颜色随验证状态变化(通过 Text 组件)
|
||||
if (_inputField != null)
|
||||
{
|
||||
var textComponent = _inputField.textComponent;
|
||||
if (textComponent != null)
|
||||
{
|
||||
textComponent.color = state == ValidationState.Invalid
|
||||
? Color.red
|
||||
: state == ValidationState.Warning
|
||||
? new Color(1f, 0.92f, 0.016f)
|
||||
: Color.white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static object ConvertValueForAccessor(float val, Type targetType)
|
||||
{
|
||||
if (targetType == typeof(int)) return (int)val;
|
||||
if (targetType == typeof(double)) return (double)val;
|
||||
return val;
|
||||
}
|
||||
|
||||
private static object ConvertStringToValue(string s, Type targetType)
|
||||
{
|
||||
if (targetType == typeof(string)) return s;
|
||||
if (targetType == typeof(float) && float.TryParse(s, out float fVal)) return fVal;
|
||||
if (targetType == typeof(int) && int.TryParse(s, out int iVal)) return iVal;
|
||||
if (targetType == typeof(double) && double.TryParse(s, out double dVal)) return dVal;
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 66e550f9b0ba63448bf6add335a317a8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,75 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Label 只读值组件 — 纯文本展示,无交互控件
|
||||
/// 拥有自己的 _valueDisplayText 用于显示值
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI Action Vessel/Values/XUIAvrgMDLabelValue")]
|
||||
public class XUIAvrgMDLabelValue : XUIAvrgMDValue
|
||||
{
|
||||
[SerializeField, Tooltip("值显示文本 — 显示当前字段的值")]
|
||||
#if dUI_TextMeshPro
|
||||
private TMPro.TMP_Text _valueDisplayText;
|
||||
#else
|
||||
private UnityEngine.UI.Text _valueDisplayText;
|
||||
#endif
|
||||
|
||||
public override void OnPoolRelease()
|
||||
{
|
||||
if (_valueDisplayText != null) _valueDisplayText.text = "";
|
||||
base.OnPoolRelease();
|
||||
}
|
||||
|
||||
public override void BindToAccessor(
|
||||
IAvrgValueAccessor accessor,
|
||||
AvrgMvvmBinding binding,
|
||||
AvrgMvvmScheduler scheduler)
|
||||
{
|
||||
CurrentAccessor = accessor;
|
||||
CurrentBinding = binding;
|
||||
|
||||
if (binding == null || accessor == null) return;
|
||||
|
||||
binding.OnUIDataChanged += (value, state) =>
|
||||
{
|
||||
UpdateValueDisplay(value, state);
|
||||
};
|
||||
binding.OnValidationStateChanged += OnValidationStateChanged;
|
||||
|
||||
var initialValue = accessor.GetValue();
|
||||
UpdateValueDisplay(initialValue, ValidationState.Normal);
|
||||
}
|
||||
|
||||
protected override void UpdateValueDisplay(object value, ValidationState state)
|
||||
{
|
||||
if (_valueDisplayText != null)
|
||||
{
|
||||
_valueDisplayText.text = CurrentAccessor?.FormatDisplay(value) ?? value?.ToString() ?? "";
|
||||
|
||||
_valueDisplayText.color = state == ValidationState.Invalid
|
||||
? Color.red
|
||||
: state == ValidationState.Warning
|
||||
? new Color(1f, 0.92f, 0.016f)
|
||||
: Color.white;
|
||||
}
|
||||
|
||||
base.UpdateValueDisplay(value, state);
|
||||
}
|
||||
|
||||
protected override void OnValidationStateChanged(ValidationState state)
|
||||
{
|
||||
base.OnValidationStateChanged(state);
|
||||
|
||||
if (_valueDisplayText != null)
|
||||
{
|
||||
_valueDisplayText.color = state == ValidationState.Invalid
|
||||
? Color.red
|
||||
: state == ValidationState.Warning
|
||||
? new Color(1f, 0.92f, 0.016f)
|
||||
: Color.white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 64afed910d0f0f0408c6672fa3dd6917
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,229 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Slider 值组件 — 处理 float / int / double 类型的滑块交互
|
||||
/// 适用 Shader Range 属性或标记了 [XuiavrgFieldTag(Slider)] 的字段
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI Action Vessel/Values/XUIAvrgMDSliderValue")]
|
||||
public class XUIAvrgMDSliderValue : XUIAvrgMDValue
|
||||
{
|
||||
[SerializeField, Tooltip("关联的 Slider 组件(未设置则自动查找)")]
|
||||
private Slider _slider;
|
||||
|
||||
[Header("Slider 专属插槽")]
|
||||
[SerializeField, Tooltip("值显示文本 — 显示当前 Slider 数值")]
|
||||
#if dUI_TextMeshPro
|
||||
private TMPro.TMP_Text _valueDisplayText;
|
||||
#else
|
||||
private UnityEngine.UI.Text _valueDisplayText;
|
||||
#endif
|
||||
|
||||
[SerializeField, Tooltip("最小值文本 — 显示 Slider 下限")]
|
||||
#if dUI_TextMeshPro
|
||||
private TMPro.TMP_Text _minValueText;
|
||||
#else
|
||||
private UnityEngine.UI.Text _minValueText;
|
||||
#endif
|
||||
|
||||
[SerializeField, Tooltip("最大值文本 — 显示 Slider 上限")]
|
||||
#if dUI_TextMeshPro
|
||||
private TMPro.TMP_Text _maxValueText;
|
||||
#else
|
||||
private UnityEngine.UI.Text _maxValueText;
|
||||
#endif
|
||||
|
||||
[SerializeField, Tooltip("增加按钮(点击按步进值 +)")]
|
||||
private Button _incrementButton;
|
||||
|
||||
[SerializeField, Tooltip("减少按钮(点击按步进值 -)")]
|
||||
private Button _decrementButton;
|
||||
|
||||
private const float DefaultStep = 0.1f;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_slider == null) _slider = GetComponent<Slider>();
|
||||
}
|
||||
|
||||
// ============ 对象池生命周期 ============
|
||||
|
||||
public override void OnPoolGet(AvrgMemberMetaData metaData)
|
||||
{
|
||||
base.OnPoolGet(metaData);
|
||||
|
||||
if (_slider == null) return;
|
||||
|
||||
// 从元数据初始化 Slider 参数
|
||||
if (metaData?.RangeMin.HasValue == true)
|
||||
_slider.minValue = metaData.RangeMin.Value;
|
||||
if (metaData?.RangeMax.HasValue == true)
|
||||
_slider.maxValue = metaData.RangeMax.Value;
|
||||
_slider.wholeNumbers = metaData?.ValueType == typeof(int);
|
||||
}
|
||||
|
||||
public override void OnPoolRelease()
|
||||
{
|
||||
if (_incrementButton != null)
|
||||
_incrementButton.onClick.RemoveAllListeners();
|
||||
if (_decrementButton != null)
|
||||
_decrementButton.onClick.RemoveAllListeners();
|
||||
if (_slider != null)
|
||||
_slider.onValueChanged.RemoveAllListeners();
|
||||
|
||||
// 重置子类文本
|
||||
if (_valueDisplayText != null) _valueDisplayText.text = "";
|
||||
if (_minValueText != null) _minValueText.text = "";
|
||||
if (_maxValueText != null) _maxValueText.text = "";
|
||||
|
||||
base.OnPoolRelease();
|
||||
}
|
||||
|
||||
// ============ 核心绑定 ============
|
||||
|
||||
public override void BindToAccessor(
|
||||
IAvrgValueAccessor accessor,
|
||||
AvrgMvvmBinding binding,
|
||||
AvrgMvvmScheduler scheduler)
|
||||
{
|
||||
CurrentAccessor = accessor;
|
||||
CurrentBinding = binding;
|
||||
|
||||
if (binding == null || accessor == null || _slider == null) return;
|
||||
|
||||
// 初始化上下限文本
|
||||
ApplyRangeTexts();
|
||||
|
||||
// 增减按钮
|
||||
if (_incrementButton != null)
|
||||
_incrementButton.onClick.AddListener(OnIncrementClicked);
|
||||
if (_decrementButton != null)
|
||||
_decrementButton.onClick.AddListener(OnDecrementClicked);
|
||||
|
||||
// UI → 数据
|
||||
_slider.onValueChanged.AddListener(val =>
|
||||
{
|
||||
object convertedVal = ConvertValueForAccessor(val, accessor.ValueType);
|
||||
binding.WriteFromUI(convertedVal);
|
||||
scheduler?.MarkUIDirty(binding);
|
||||
|
||||
RefreshValueText(convertedVal);
|
||||
});
|
||||
|
||||
// 数据 → UI(带验证状态)
|
||||
binding.OnUIDataChanged += (value, state) =>
|
||||
{
|
||||
float sliderVal = Convert.ToSingle(value);
|
||||
_slider.SetValueWithoutNotify(sliderVal);
|
||||
|
||||
RefreshValueText(value);
|
||||
ApplyValueTextColor(state);
|
||||
|
||||
base.UpdateValueDisplay(value, state);
|
||||
};
|
||||
|
||||
// 验证状态变更
|
||||
binding.OnValidationStateChanged += OnValidationStateChanged;
|
||||
|
||||
// 首次同步
|
||||
var initialValue = accessor.GetValue();
|
||||
if (initialValue != null)
|
||||
_slider.SetValueWithoutNotify(Convert.ToSingle(initialValue));
|
||||
|
||||
RefreshValueText(initialValue);
|
||||
UpdateValueDisplay(initialValue ?? 0f, ValidationState.Normal);
|
||||
}
|
||||
|
||||
// ============ 值显示 ============
|
||||
|
||||
protected override void UpdateValueDisplay(object value, ValidationState state)
|
||||
{
|
||||
ApplyValueTextColor(state);
|
||||
base.UpdateValueDisplay(value, state);
|
||||
}
|
||||
|
||||
private void RefreshValueText(object value)
|
||||
{
|
||||
if (_valueDisplayText == null || CurrentAccessor == null) return;
|
||||
_valueDisplayText.text = CurrentAccessor.FormatDisplay(value);
|
||||
}
|
||||
|
||||
private void ApplyValueTextColor(ValidationState state)
|
||||
{
|
||||
if (_valueDisplayText == null) return;
|
||||
|
||||
_valueDisplayText.color = state == ValidationState.Invalid
|
||||
? Color.red
|
||||
: state == ValidationState.Warning
|
||||
? new Color(1f, 0.92f, 0.016f)
|
||||
: Color.white;
|
||||
}
|
||||
|
||||
private void ApplyRangeTexts()
|
||||
{
|
||||
if (_minValueText != null)
|
||||
_minValueText.text = CurrentAccessor?.FormatDisplay(_slider.minValue) ?? _slider.minValue.ToString();
|
||||
if (_maxValueText != null)
|
||||
_maxValueText.text = CurrentAccessor?.FormatDisplay(_slider.maxValue) ?? _slider.maxValue.ToString();
|
||||
}
|
||||
|
||||
// ============ 步进值 ============
|
||||
|
||||
private float GetStepValue()
|
||||
{
|
||||
return _slider.wholeNumbers ? 1f : DefaultStep;
|
||||
}
|
||||
|
||||
private void OnIncrementClicked()
|
||||
{
|
||||
if (CurrentAccessor?.IsReadOnly == true || _slider == null) return;
|
||||
float step = GetStepValue();
|
||||
float current = Convert.ToSingle(CurrentAccessor.GetValue());
|
||||
float newVal = Mathf.Clamp(current + step, _slider.minValue, _slider.maxValue);
|
||||
_slider.value = newVal;
|
||||
}
|
||||
|
||||
private void OnDecrementClicked()
|
||||
{
|
||||
if (CurrentAccessor?.IsReadOnly == true || _slider == null) return;
|
||||
float step = GetStepValue();
|
||||
float current = Convert.ToSingle(CurrentAccessor.GetValue());
|
||||
float newVal = Mathf.Clamp(current - step, _slider.minValue, _slider.maxValue);
|
||||
_slider.value = newVal;
|
||||
}
|
||||
|
||||
// ============ 验证状态回调 ============
|
||||
|
||||
protected override void OnValidationStateChanged(ValidationState state)
|
||||
{
|
||||
base.OnValidationStateChanged(state);
|
||||
|
||||
// Slider 填充色随验证状态变化
|
||||
if (_slider != null)
|
||||
{
|
||||
var fillArea = _slider.fillRect;
|
||||
if (fillArea != null && fillArea.TryGetComponent<Image>(out var fill))
|
||||
{
|
||||
fill.color = state switch
|
||||
{
|
||||
ValidationState.Invalid => Color.red,
|
||||
ValidationState.Warning => new Color(1f, 0.92f, 0.016f),
|
||||
_ => Color.white
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
ApplyValueTextColor(state);
|
||||
}
|
||||
|
||||
private static object ConvertValueForAccessor(float val, Type targetType)
|
||||
{
|
||||
if (targetType == typeof(int)) return (int)val;
|
||||
if (targetType == typeof(double)) return (double)val;
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2b55bbe984d79124ba933f6d7cc2232f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,60 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Texture 值组件 — 通过 RawImage 显示纹理
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI Action Vessel/Values/XUIAvrgMDTextureValue")]
|
||||
public class XUIAvrgMDTextureValue : XUIAvrgMDValue
|
||||
{
|
||||
[SerializeField, Tooltip("关联的 RawImage 组件(未设置则自动查找)")]
|
||||
private RawImage _rawImage;
|
||||
|
||||
#if dUI_TextMeshPro
|
||||
[SerializeField, Tooltip("纹理名称标签(可选)")]
|
||||
private TMPro.TMP_Text _nameLabel;
|
||||
#endif
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_rawImage == null) _rawImage = GetComponent<RawImage>();
|
||||
}
|
||||
|
||||
public override void BindToAccessor(
|
||||
IAvrgValueAccessor accessor,
|
||||
AvrgMvvmBinding binding,
|
||||
AvrgMvvmScheduler scheduler)
|
||||
{
|
||||
CurrentAccessor = accessor;
|
||||
CurrentBinding = binding;
|
||||
|
||||
if (binding == null || accessor == null) return;
|
||||
|
||||
binding.OnUIDataChanged += (value, state) =>
|
||||
{
|
||||
UpdateValueDisplay(value, state);
|
||||
};
|
||||
binding.OnValidationStateChanged += OnValidationStateChanged;
|
||||
|
||||
var initialValue = accessor.GetValue();
|
||||
UpdateValueDisplay(initialValue, ValidationState.Normal);
|
||||
}
|
||||
|
||||
protected override void UpdateValueDisplay(object value, ValidationState state)
|
||||
{
|
||||
// 值显示:刷新 RawImage.texture
|
||||
if (_rawImage != null && value is Texture tex)
|
||||
{
|
||||
_rawImage.texture = tex;
|
||||
#if dUI_TextMeshPro
|
||||
if (_nameLabel != null)
|
||||
_nameLabel.text = tex?.name ?? "";
|
||||
#endif
|
||||
}
|
||||
|
||||
base.UpdateValueDisplay(value, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0655c30e41dc2884f91f4acb4666a271
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,203 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// ToggleGroup 值组件 — 处理 string / int / enum 类型的单选组交互
|
||||
/// 适用于标记了 [XuiavrgFieldToggleGroupValue] 的字段
|
||||
///
|
||||
/// 选项动态创建:OnPoolGet 时根据元数据 PresetOptions 数量,
|
||||
/// 从 _toggleItemPrefab 实例化 Toggle 子对象。
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI Action Vessel/Values/XUIAvrgMDToggleGroupValue")]
|
||||
public class XUIAvrgMDToggleGroupValue : XUIAvrgMDValue
|
||||
{
|
||||
[SerializeField, Tooltip("关联的 ToggleGroup 组件(未设置则自动查找)")]
|
||||
private ToggleGroup _toggleGroup;
|
||||
|
||||
[Header("选项动态创建")]
|
||||
[SerializeField, Tooltip("Toggle 项预制体 — 在 OnPoolGet 时按预设选项数量实例化")]
|
||||
private Transform _toggleItemPrefab;
|
||||
|
||||
[SerializeField, Tooltip("选项容器 Transform — 动态创建的 Toggle 挂载到此节点下")]
|
||||
private Transform _optionsContainer;
|
||||
|
||||
private List<Toggle> _toggles = new List<Toggle>();
|
||||
private List<string> _optionLabels = new List<string>();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_toggleGroup == null) _toggleGroup = GetComponent<ToggleGroup>();
|
||||
}
|
||||
|
||||
// ============ 对象池生命周期 ============
|
||||
|
||||
public override void OnPoolGet(AvrgMemberMetaData metaData)
|
||||
{
|
||||
base.OnPoolGet(metaData);
|
||||
|
||||
// 构建选项列表
|
||||
BuildOptions(metaData?.ValueType);
|
||||
|
||||
// 动态创建 Toggle 项
|
||||
CreateToggleItems();
|
||||
}
|
||||
|
||||
public override void OnPoolRelease()
|
||||
{
|
||||
// 清理动态创建的 Toggle
|
||||
foreach (var toggle in _toggles)
|
||||
{
|
||||
if (toggle != null)
|
||||
toggle.onValueChanged.RemoveAllListeners();
|
||||
}
|
||||
DestroyToggleItems();
|
||||
|
||||
_toggles.Clear();
|
||||
_optionLabels.Clear();
|
||||
|
||||
base.OnPoolRelease();
|
||||
}
|
||||
|
||||
// ============ 动态 Toggle 创建 ============
|
||||
|
||||
/// <summary>
|
||||
/// 根据 _optionLabels 数量,从 _toggleItemPrefab 实例化 Toggle 子对象
|
||||
/// </summary>
|
||||
private void CreateToggleItems()
|
||||
{
|
||||
var parent = _optionsContainer != null ? _optionsContainer : transform;
|
||||
if (_toggleItemPrefab == null || _optionLabels.Count == 0) return;
|
||||
|
||||
for (int i = 0; i < _optionLabels.Count; i++)
|
||||
{
|
||||
var item = Instantiate(_toggleItemPrefab, parent);
|
||||
item.name = $"Toggle_{i}_{_optionLabels[i]}";
|
||||
item.gameObject.SetActive(true);
|
||||
|
||||
var toggle = item.GetComponent<Toggle>();
|
||||
if (toggle != null)
|
||||
{
|
||||
toggle.group = _toggleGroup;
|
||||
_toggles.Add(toggle);
|
||||
|
||||
// 设置标签文本
|
||||
var label = item.GetComponentInChildren<Text>(true);
|
||||
if (label != null)
|
||||
label.text = _optionLabels[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 销毁动态创建的所有 Toggle 子对象
|
||||
/// </summary>
|
||||
private void DestroyToggleItems()
|
||||
{
|
||||
var parent = _optionsContainer != null ? _optionsContainer : transform;
|
||||
|
||||
foreach (var toggle in _toggles)
|
||||
{
|
||||
if (toggle != null)
|
||||
Destroy(toggle.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildOptions(System.Type valueType)
|
||||
{
|
||||
_optionLabels.Clear();
|
||||
|
||||
// 1. 从元数据 PresetOptions
|
||||
if (BoundMetaData?.PresetOptions != null && BoundMetaData.PresetOptions.Count > 0)
|
||||
{
|
||||
_optionLabels.AddRange(BoundMetaData.PresetOptions);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. enum 类型
|
||||
if (valueType != null && valueType.IsEnum)
|
||||
{
|
||||
_optionLabels.AddRange(System.Enum.GetNames(valueType));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 核心绑定 ============
|
||||
|
||||
public override void BindToAccessor(
|
||||
IAvrgValueAccessor accessor,
|
||||
AvrgMvvmBinding binding,
|
||||
AvrgMvvmScheduler scheduler)
|
||||
{
|
||||
CurrentAccessor = accessor;
|
||||
CurrentBinding = binding;
|
||||
|
||||
if (binding == null || accessor == null || _toggleGroup == null || _toggles.Count == 0) return;
|
||||
|
||||
// 只读模式下禁用所有 Toggle
|
||||
if (accessor.IsReadOnly)
|
||||
{
|
||||
foreach (var t in _toggles)
|
||||
t.interactable = false;
|
||||
}
|
||||
|
||||
// 为每个 Toggle 注册事件
|
||||
for (int i = 0; i < _toggles.Count && i < _optionLabels.Count; i++)
|
||||
{
|
||||
int idx = i;
|
||||
_toggles[i].onValueChanged.AddListener(isOn =>
|
||||
{
|
||||
if (isOn)
|
||||
{
|
||||
object convertedVal = ConvertOptionToValue(_optionLabels[idx], accessor.ValueType);
|
||||
binding.WriteFromUI(convertedVal);
|
||||
scheduler?.MarkUIDirty(binding);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 数据 → UI(带验证状态)
|
||||
binding.OnUIDataChanged += (value, state) =>
|
||||
{
|
||||
string valueStr = value?.ToString();
|
||||
for (int i = 0; i < _toggles.Count && i < _optionLabels.Count; i++)
|
||||
{
|
||||
if (_optionLabels[i] == valueStr)
|
||||
{
|
||||
_toggles[i].SetIsOnWithoutNotify(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
base.UpdateValueDisplay(value, state);
|
||||
};
|
||||
|
||||
// 验证状态变更
|
||||
binding.OnValidationStateChanged += OnValidationStateChanged;
|
||||
|
||||
// 首次同步
|
||||
var initialValue = accessor.GetValue();
|
||||
string initStr = initialValue?.ToString();
|
||||
for (int i = 0; i < _toggles.Count && i < _optionLabels.Count; i++)
|
||||
{
|
||||
if (_optionLabels[i] == initStr)
|
||||
{
|
||||
_toggles[i].SetIsOnWithoutNotify(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static object ConvertOptionToValue(string option, System.Type targetType)
|
||||
{
|
||||
if (targetType == typeof(string)) return option;
|
||||
if (targetType == typeof(int) && int.TryParse(option, out int intVal)) return intVal;
|
||||
if (targetType != null && targetType.IsEnum)
|
||||
return System.Enum.Parse(targetType, option);
|
||||
return option;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 330c7c83db7ef3141a8097a27d2cf61d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,98 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Toggle 值组件 — 处理 bool 类型的开关交互
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI Action Vessel/Values/XUIAvrgMDToggleValue")]
|
||||
public class XUIAvrgMDToggleValue : XUIAvrgMDValue
|
||||
{
|
||||
[SerializeField, Tooltip("关联的 Toggle 组件(未设置则自动查找)")]
|
||||
private Toggle _toggle;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_toggle == null) _toggle = GetComponent<Toggle>();
|
||||
}
|
||||
|
||||
// ============ 对象池生命周期 ============
|
||||
|
||||
public override void OnPoolRelease()
|
||||
{
|
||||
if (_toggle != null)
|
||||
_toggle.onValueChanged.RemoveAllListeners();
|
||||
base.OnPoolRelease();
|
||||
}
|
||||
|
||||
// ============ 核心绑定 ============
|
||||
|
||||
public override void BindToAccessor(
|
||||
IAvrgValueAccessor accessor,
|
||||
AvrgMvvmBinding binding,
|
||||
AvrgMvvmScheduler scheduler)
|
||||
{
|
||||
CurrentAccessor = accessor;
|
||||
CurrentBinding = binding;
|
||||
|
||||
if (binding == null || accessor == null || _toggle == null) return;
|
||||
|
||||
// 只读模式下禁用交互
|
||||
_toggle.interactable = !accessor.IsReadOnly;
|
||||
|
||||
// UI → 数据
|
||||
_toggle.onValueChanged.AddListener(val =>
|
||||
{
|
||||
binding.WriteFromUI(val);
|
||||
scheduler?.MarkUIDirty(binding);
|
||||
});
|
||||
|
||||
// 数据 → UI(带验证状态)
|
||||
binding.OnUIDataChanged += (value, state) =>
|
||||
{
|
||||
bool isOn = value is bool b && b;
|
||||
_toggle.SetIsOnWithoutNotify(isOn);
|
||||
ApplyToggleColor(isOn, state);
|
||||
|
||||
base.UpdateValueDisplay(value, state);
|
||||
};
|
||||
|
||||
// 验证状态变更
|
||||
binding.OnValidationStateChanged += OnValidationStateChanged;
|
||||
|
||||
// 首次同步
|
||||
var initialValue = accessor.GetValue();
|
||||
bool initialOn = initialValue is bool bv && bv;
|
||||
_toggle.SetIsOnWithoutNotify(initialOn);
|
||||
ApplyToggleColor(initialOn, ValidationState.Normal);
|
||||
UpdateValueDisplay(initialValue, ValidationState.Normal);
|
||||
}
|
||||
|
||||
// ============ 验证状态回调 ============
|
||||
|
||||
protected override void OnValidationStateChanged(ValidationState state)
|
||||
{
|
||||
base.OnValidationStateChanged(state);
|
||||
|
||||
// Switch 验证状态也影响 targetGraphic 颜色
|
||||
if (_toggle != null)
|
||||
{
|
||||
ApplyToggleColor(_toggle.isOn, state);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyToggleColor(bool isOn, ValidationState state)
|
||||
{
|
||||
var target = _toggle?.targetGraphic;
|
||||
if (target == null) return;
|
||||
|
||||
if (state == ValidationState.Invalid)
|
||||
target.color = Color.red;
|
||||
else if (state == ValidationState.Warning)
|
||||
target.color = new Color(1f, 0.92f, 0.016f);
|
||||
else
|
||||
target.color = isOn ? Color.green : new Color(0.5f, 0.5f, 0.5f, 1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 00fd149c9a8dacd478a5adcad14a11f2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,259 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// MDVM 值组件基类 — 预制体上的标记组件
|
||||
///
|
||||
/// 职责:
|
||||
/// 1. 提供 EntryTag/TagIndex 供 XuiFieldTempEntry 匹配
|
||||
/// 2. 持有绑定的元数据引用,管理验证状态
|
||||
/// 3. 通过 virtual BindToAccessor() 让子类自行管理 UI 交互事件
|
||||
/// 4. 对象池生命周期:OnPoolGet(meta) 初始化、OnPoolRelease() 重置
|
||||
/// 5. 定义绑定槽(BindingSlots),供拆分属性子绑定匹配
|
||||
/// 6. 基类统一管理标签文本(名称 + 只读 + 警告标记)和单位可见性
|
||||
/// 值显示由各子类自行实现
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI Action Vessel/XUIAvrgMDValue")]
|
||||
public class XUIAvrgMDValue : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// 绑定槽定义 — 用于拆分属性(Split)的子绑定匹配
|
||||
/// propertyName 匹配 XuiavrgSplit 的 PropertyPath
|
||||
/// tag 在预制体内查找对应子节点的 XUIAvrgMDValue
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class AvrgBindingSlot
|
||||
{
|
||||
[Tooltip("绑定的子属性名(匹配 XuiavrgSplit 的 PropertyPath,如 \"x\"、\"y\")")]
|
||||
public string propertyName;
|
||||
|
||||
[Tooltip("子属性在预制体上的 Tag 标记,用于查找子节点 XUIAvrgMDValue")]
|
||||
public XuiavrgFieldTag tag = XuiavrgFieldTag.Context;
|
||||
}
|
||||
|
||||
// ============ 匹配标记 ============
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("功能标记,用于与 Entry 的 Tag 匹配")]
|
||||
private XuiavrgFieldTag _entryTag = XuiavrgFieldTag.None;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("同 Tag 多实例时区分(0 表示不限制)")]
|
||||
private int _tagIndex;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("拆分属性子绑定槽列表 — 用于 XuiavrgSplit 的子属性匹配")]
|
||||
private List<AvrgBindingSlot> _bindingSlots = new List<AvrgBindingSlot>();
|
||||
|
||||
// ============ UI 文本插槽 ============
|
||||
|
||||
[Header("UI 基类插槽")]
|
||||
[SerializeField, Tooltip("字段标签文本 — 显示 \"名称 (只读) ⚠警告\",为 null 则不显示标签")]
|
||||
#if dUI_TextMeshPro
|
||||
protected TMPro.TMP_Text _fieldLabelText;
|
||||
#else
|
||||
protected UnityEngine.UI.Text _fieldLabelText;
|
||||
#endif
|
||||
|
||||
[SerializeField, Tooltip("单位文本 — 可为 null")]
|
||||
#if dUI_TextMeshPro
|
||||
protected TMPro.TMP_Text _unitText;
|
||||
#else
|
||||
protected UnityEngine.UI.Text _unitText;
|
||||
#endif
|
||||
|
||||
[SerializeField, Tooltip("单位容器 Transform — 单位文本为空时 SetActive(false) 隐藏")]
|
||||
protected Transform _unitTransform;
|
||||
|
||||
// ============ 公共属性 ============
|
||||
|
||||
/// <summary>绑定的元数据引用</summary>
|
||||
public AvrgMemberMetaData BoundMetaData { get; protected set; }
|
||||
|
||||
/// <summary>当前关联的值访问器</summary>
|
||||
public IAvrgValueAccessor CurrentAccessor { get; protected set; }
|
||||
|
||||
/// <summary>当前关联的 MVVM 绑定</summary>
|
||||
public AvrgMvvmBinding CurrentBinding { get; protected set; }
|
||||
|
||||
/// <summary>当前验证状态</summary>
|
||||
public ValidationState CurrentValidationState { get; protected set; } = ValidationState.Normal;
|
||||
|
||||
public XuiavrgFieldTag EntryTag => _entryTag;
|
||||
public int TagIndex => _tagIndex;
|
||||
|
||||
/// <summary>只读访问绑定槽列表</summary>
|
||||
public IReadOnlyList<AvrgBindingSlot> AvailableBindingSlots => _bindingSlots;
|
||||
|
||||
// ============ 对象池生命周期 ============
|
||||
|
||||
/// <summary>
|
||||
/// 从对象池取出时调用 — 注入元数据并初始化子属性
|
||||
/// 子类可覆写以初始化专属组件参数(Slider.minValue/maxValue 等)
|
||||
/// </summary>
|
||||
public virtual void OnPoolGet(AvrgMemberMetaData metaData)
|
||||
{
|
||||
BoundMetaData = metaData;
|
||||
CurrentValidationState = ValidationState.Normal;
|
||||
|
||||
// 单位可见性:无单位文本则隐藏单位节点
|
||||
ApplyUnitVisibility();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回收到对象池时调用 — 重置所有状态到初始值
|
||||
/// 子类可覆写以清理专属组件事件
|
||||
/// </summary>
|
||||
public virtual void OnPoolRelease()
|
||||
{
|
||||
CurrentAccessor = null;
|
||||
CurrentBinding = null;
|
||||
BoundMetaData = null;
|
||||
CurrentValidationState = ValidationState.Normal;
|
||||
|
||||
// 重置 UI 文本
|
||||
if (_fieldLabelText != null) _fieldLabelText.text = "";
|
||||
if (_unitText != null) _unitText.text = "";
|
||||
|
||||
// 重置单位可见性
|
||||
if (_unitTransform != null) _unitTransform.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
// ============ 核心绑定接口 ============
|
||||
|
||||
/// <summary>
|
||||
/// 由 XuiFieldTempEntry 调用,将值访问器和 MVVM 绑定连接到此 UI 组件
|
||||
/// </summary>
|
||||
public virtual void BindToAccessor(
|
||||
IAvrgValueAccessor accessor,
|
||||
AvrgMvvmBinding binding,
|
||||
AvrgMvvmScheduler scheduler)
|
||||
{
|
||||
CurrentAccessor = accessor;
|
||||
CurrentBinding = binding;
|
||||
|
||||
if (binding == null || accessor == null) return;
|
||||
|
||||
// 数据 → UI(带验证状态)
|
||||
binding.OnUIDataChanged += (value, state) =>
|
||||
{
|
||||
UpdateValueDisplay(value, state);
|
||||
};
|
||||
|
||||
// 验证状态变更
|
||||
binding.OnValidationStateChanged += OnValidationStateChanged;
|
||||
|
||||
// 首次同步
|
||||
var initialValue = accessor.GetValue();
|
||||
UpdateValueDisplay(initialValue, ValidationState.Normal);
|
||||
}
|
||||
|
||||
// ============ UI 更新 ============
|
||||
|
||||
/// <summary>
|
||||
/// 根据值和验证状态更新 UI 显示
|
||||
/// 子类必须覆写以处理专属组件更新(Slider.value, Image.color 等),
|
||||
/// 并在覆写中调用 base.UpdateValueDisplay(value, state) 以刷新标签。
|
||||
/// </summary>
|
||||
protected virtual void UpdateValueDisplay(object value, ValidationState state)
|
||||
{
|
||||
// 1. 字段标签合成
|
||||
if (_fieldLabelText != null)
|
||||
_fieldLabelText.text = BuildLabel(state);
|
||||
|
||||
// 2. 单位文本 + 可见性
|
||||
ApplyUnitText();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 合成标签文本:名称 + (只读) + 警告标记
|
||||
/// </summary>
|
||||
protected string BuildLabel(ValidationState state)
|
||||
{
|
||||
var meta = BoundMetaData;
|
||||
var sb = new StringBuilder();
|
||||
sb.Append(meta?.LabelName ?? meta?.MemberName ?? "");
|
||||
|
||||
// 必填标记
|
||||
if (meta != null && meta.Required)
|
||||
sb.Append(" *");
|
||||
|
||||
// 只读标记
|
||||
if (CurrentAccessor?.IsReadOnly == true)
|
||||
sb.Append(" (只读)");
|
||||
|
||||
// 警告/无效标记(用富文本颜色)
|
||||
if (state == ValidationState.Warning)
|
||||
sb.Append(" <color=yellow>⚠警告</color>");
|
||||
else if (state == ValidationState.Invalid)
|
||||
sb.Append(" <color=red>⚠无效值</color>");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新单位文本并控制可见性
|
||||
/// </summary>
|
||||
protected void ApplyUnitText()
|
||||
{
|
||||
string unit = BoundMetaData?.UnitLabelName;
|
||||
bool hasUnit = !string.IsNullOrEmpty(unit);
|
||||
|
||||
if (_unitText != null)
|
||||
_unitText.text = hasUnit ? unit : "";
|
||||
|
||||
if (_unitTransform != null)
|
||||
_unitTransform.gameObject.SetActive(hasUnit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 仅刷新单位可见性(不更新文本内容)
|
||||
/// </summary>
|
||||
protected void ApplyUnitVisibility()
|
||||
{
|
||||
if (_unitTransform == null) return;
|
||||
|
||||
string unit = BoundMetaData?.UnitLabelName;
|
||||
_unitTransform.gameObject.SetActive(!string.IsNullOrEmpty(unit));
|
||||
}
|
||||
|
||||
// ============ 验证状态回调 ============
|
||||
|
||||
/// <summary>
|
||||
/// 验证状态变更回调 — 子类可覆写以实现专属样式切换
|
||||
/// 如 Slider 填充色变黄/红、InputField 文字变色等
|
||||
/// </summary>
|
||||
protected virtual void OnValidationStateChanged(ValidationState state)
|
||||
{
|
||||
CurrentValidationState = state;
|
||||
|
||||
// 刷新标签文本(警告/无效标记在标签上)
|
||||
if (_fieldLabelText != null)
|
||||
_fieldLabelText.text = BuildLabel(state);
|
||||
}
|
||||
|
||||
// ============ 元数据广播 ============
|
||||
|
||||
/// <summary>
|
||||
/// 由绑定系统调用,传递元数据并广播到 IXUIAvrgMDValueStyle 子组件
|
||||
/// </summary>
|
||||
public void InitializeWithMetaData(AvrgMemberMetaData metaData)
|
||||
{
|
||||
BoundMetaData = metaData;
|
||||
|
||||
if (metaData == null) return;
|
||||
|
||||
var styles = GetComponentsInChildren<IXUIAvrgMDValueStyle>(true);
|
||||
foreach (var style in styles)
|
||||
{
|
||||
if (style is MonoBehaviour mb && mb.gameObject == gameObject)
|
||||
continue;
|
||||
|
||||
style.ApplyMetaData(metaData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 向量值组件 — 处理 Vector2 / Vector3 / Vector4 类型的多字段输入
|
||||
/// 子节点按 BindingSlots 绑定各分量(如 "x", "y", "z", "w")
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI Action Vessel/Values/XUIAvrgMDVectorValue")]
|
||||
public class XUIAvrgMDVectorValue : XUIAvrgMDValue
|
||||
{
|
||||
[SerializeField, Tooltip("X 分量 InputField(可选,也可通过 BindingSlots 的子节点绑定)")]
|
||||
private InputField _inputX;
|
||||
|
||||
[SerializeField, Tooltip("Y 分量 InputField")]
|
||||
private InputField _inputY;
|
||||
|
||||
[SerializeField, Tooltip("Z 分量 InputField(Vector3/Vector4 时使用)")]
|
||||
private InputField _inputZ;
|
||||
|
||||
[SerializeField, Tooltip("W 分量 InputField(Vector4 时使用)")]
|
||||
private InputField _inputW;
|
||||
|
||||
// ============ 对象池生命周期 ============
|
||||
|
||||
public override void OnPoolGet(AvrgMemberMetaData metaData)
|
||||
{
|
||||
base.OnPoolGet(metaData);
|
||||
|
||||
int componentCount = GetComponentCount(metaData?.ValueType);
|
||||
SetInputActive(_inputX, 0, componentCount);
|
||||
SetInputActive(_inputY, 1, componentCount);
|
||||
SetInputActive(_inputZ, 2, componentCount);
|
||||
SetInputActive(_inputW, 3, componentCount);
|
||||
}
|
||||
|
||||
public override void OnPoolRelease()
|
||||
{
|
||||
if (_inputX != null) _inputX.onEndEdit.RemoveAllListeners();
|
||||
if (_inputY != null) _inputY.onEndEdit.RemoveAllListeners();
|
||||
if (_inputZ != null) _inputZ.onEndEdit.RemoveAllListeners();
|
||||
if (_inputW != null) _inputW.onEndEdit.RemoveAllListeners();
|
||||
|
||||
base.OnPoolRelease();
|
||||
}
|
||||
|
||||
// ============ 核心绑定 ============
|
||||
|
||||
public override void BindToAccessor(
|
||||
IAvrgValueAccessor accessor,
|
||||
AvrgMvvmBinding binding,
|
||||
AvrgMvvmScheduler scheduler)
|
||||
{
|
||||
CurrentAccessor = accessor;
|
||||
CurrentBinding = binding;
|
||||
|
||||
if (binding == null || accessor == null) return;
|
||||
|
||||
// 为每个输入注册事件
|
||||
BindInputField(_inputX, 0, accessor, binding, scheduler);
|
||||
BindInputField(_inputY, 1, accessor, binding, scheduler);
|
||||
BindInputField(_inputZ, 2, accessor, binding, scheduler);
|
||||
BindInputField(_inputW, 3, accessor, binding, scheduler);
|
||||
|
||||
// 数据 → UI
|
||||
binding.OnUIDataChanged += (value, state) =>
|
||||
{
|
||||
UpdateAllInputs(value);
|
||||
base.UpdateValueDisplay(value, state);
|
||||
};
|
||||
|
||||
binding.OnValidationStateChanged += OnValidationStateChanged;
|
||||
|
||||
// 首次同步
|
||||
var initialValue = accessor.GetValue();
|
||||
UpdateAllInputs(initialValue);
|
||||
UpdateValueDisplay(initialValue, ValidationState.Normal);
|
||||
}
|
||||
|
||||
// ============ 输入绑定 ============
|
||||
|
||||
private void BindInputField(InputField input, int componentIndex,
|
||||
IAvrgValueAccessor accessor, AvrgMvvmBinding binding, AvrgMvvmScheduler scheduler)
|
||||
{
|
||||
if (input == null) return;
|
||||
|
||||
input.onEndEdit.AddListener(val =>
|
||||
{
|
||||
if (float.TryParse(val, out float num))
|
||||
{
|
||||
var currentVal = accessor.GetValue();
|
||||
var modified = SetVectorComponent(currentVal, componentIndex, num);
|
||||
binding.WriteFromUI(modified);
|
||||
scheduler?.MarkUIDirty(binding);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateAllInputs(object value)
|
||||
{
|
||||
if (value == null) return;
|
||||
|
||||
if (_inputX != null && _inputX.gameObject.activeSelf)
|
||||
_inputX.SetTextWithoutNotify(GetVectorComponent(value, 0).ToString("F2"));
|
||||
|
||||
if (_inputY != null && _inputY.gameObject.activeSelf)
|
||||
_inputY.SetTextWithoutNotify(GetVectorComponent(value, 1).ToString("F2"));
|
||||
|
||||
if (_inputZ != null && _inputZ.gameObject.activeSelf)
|
||||
_inputZ.SetTextWithoutNotify(GetVectorComponent(value, 2).ToString("F2"));
|
||||
|
||||
if (_inputW != null && _inputW.gameObject.activeSelf)
|
||||
_inputW.SetTextWithoutNotify(GetVectorComponent(value, 3).ToString("F2"));
|
||||
}
|
||||
|
||||
private static int GetComponentCount(System.Type type)
|
||||
{
|
||||
if (type == typeof(Vector2)) return 2;
|
||||
if (type == typeof(Vector3)) return 3;
|
||||
if (type == typeof(Vector4)) return 4;
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static float GetVectorComponent(object vec, int index)
|
||||
{
|
||||
return vec switch
|
||||
{
|
||||
Vector2 v2 => index == 0 ? v2.x : v2.y,
|
||||
Vector3 v3 => index == 0 ? v3.x : index == 1 ? v3.y : v3.z,
|
||||
Vector4 v4 => index == 0 ? v4.x : index == 1 ? v4.y : index == 2 ? v4.z : v4.w,
|
||||
_ => 0f
|
||||
};
|
||||
}
|
||||
|
||||
private static object SetVectorComponent(object vec, int index, float value)
|
||||
{
|
||||
return vec switch
|
||||
{
|
||||
Vector2 v2 => index == 0 ? new Vector2(value, v2.y) : new Vector2(v2.x, value),
|
||||
Vector3 v3 => index == 0 ? new Vector3(value, v3.y, v3.z)
|
||||
: index == 1 ? new Vector3(v3.x, value, v3.z) : new Vector3(v3.x, v3.y, value),
|
||||
Vector4 v4 => index == 0 ? new Vector4(value, v4.y, v4.z, v4.w)
|
||||
: index == 1 ? new Vector4(v4.x, value, v4.z, v4.w)
|
||||
: index == 2 ? new Vector4(v4.x, v4.y, value, v4.w)
|
||||
: new Vector4(v4.x, v4.y, v4.z, value),
|
||||
_ => vec
|
||||
};
|
||||
}
|
||||
|
||||
private static void SetInputActive(InputField input, int index, int componentCount)
|
||||
{
|
||||
if (input != null)
|
||||
input.gameObject.SetActive(index < componentCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 128edc3f2c56f784d8a3acca12c960b4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,53 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Xuiavrg 样式默认值注入
|
||||
///
|
||||
/// 在应用启动时通过 StyleManager.AddDynamicStyle() 将 xuiavrg 各组件的
|
||||
/// 默认样式参数注入到 "default" 命名空间下。用户可在 .xsss 文件中覆写。
|
||||
///
|
||||
/// 触发时机:XuiavrgInspectorManager.OnEnable() 调用 EnsureDefaults()
|
||||
/// </summary>
|
||||
public static class XuiavrgStyleDefaults
|
||||
{
|
||||
private static bool _defaultsInjected;
|
||||
private const string NS = "default";
|
||||
|
||||
/// <summary>确保 "default" 命名空间下有所有的 xuiavrg 默认样式值(幂等)</summary>
|
||||
public static void EnsureDefaults()
|
||||
{
|
||||
if (_defaultsInjected) return;
|
||||
_defaultsInjected = true;
|
||||
|
||||
// ===== 基类通用 =====
|
||||
AddIfMissing(NS, "xuiavrg/base/stepValue", 1f);
|
||||
AddIfMissing(NS, "xuiavrg/base/readOnlyPrefix", "[只读] ");
|
||||
AddIfMissing(NS, "xuiavrg/base/warningColor", Color.yellow);
|
||||
|
||||
// ===== Slider =====
|
||||
AddIfMissing(NS, "xuiavrg/slider/defaultStep", 0.1f);
|
||||
AddIfMissing(NS, "xuiavrg/slider/minStep", 0.001f);
|
||||
AddIfMissing(NS, "xuiavrg/slider/maxStep", 1000f);
|
||||
|
||||
// ===== InputField =====
|
||||
AddIfMissing(NS, "xuiavrg/input/stepValue", 1f);
|
||||
|
||||
// ===== Toggle =====
|
||||
AddIfMissing(NS, "xuiavrg/toggle/onColor", Color.green);
|
||||
AddIfMissing(NS, "xuiavrg/toggle/offColor", new Color(0.5f, 0.5f, 0.5f, 1f));
|
||||
|
||||
// ===== Vector =====
|
||||
AddIfMissing(NS, "xuiavrg/vector/layoutDirection", "Horizontal");
|
||||
|
||||
XericLibrary.Runtime.SuperStyleSheet.StyleManager.ForceRefresh();
|
||||
}
|
||||
|
||||
private static void AddIfMissing(string ns, string path, object value)
|
||||
{
|
||||
if (XericLibrary.Runtime.SuperStyleSheet.StyleManager.HasStyle(ns, path)) return;
|
||||
XericLibrary.Runtime.SuperStyleSheet.StyleManager.AddDynamicStyle(ns, path, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d14548d93d7669346a061384defd8460
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,76 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// MDVM 值组件 — 统一 UI 脚本组件
|
||||
/// 替换所有 XericUIrAttributeXXX 作为预制体上的标记组件
|
||||
///
|
||||
/// 职责:
|
||||
/// 1. 提供 EntryTag/TagIndex 供 XuiFieldTempEntry 匹配
|
||||
/// 2. 持有绑定的 AvrgMemberMetaData 引用
|
||||
/// 3. 初始化时广播元数据到所有子节点上的 IXUIAvrgMDValueStyle
|
||||
/// 4. 定义绑定槽(BindingSlots),供拆分属性子绑定匹配
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI Action Vessel/XUIAvrgMDValue")]
|
||||
public class XUIAvrgMDValue : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// 绑定槽定义 — 用于拆分属性(Split)的子绑定匹配
|
||||
/// propertyName 匹配 XuiavrgSplit 的 PropertyPath
|
||||
/// tag 在预制体内查找对应子节点的 XUIAvrgMDValue
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class AvrgBindingSlot
|
||||
{
|
||||
[Tooltip("绑定的子属性名(匹配 XuiavrgSplit 的 PropertyPath,如 \"x\"、\"y\")")]
|
||||
public string propertyName;
|
||||
|
||||
[Tooltip("子属性在预制体上的 Tag 标记,用于查找子节点 XUIAvrgMDValue")]
|
||||
public XuiavrgFieldTag tag = XuiavrgFieldTag.Context;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("功能标记,用于与 Entry 的 Tag 匹配")]
|
||||
private XuiavrgFieldTag _entryTag = XuiavrgFieldTag.None;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("同 Tag 多实例时区分(0 表示不限制)")]
|
||||
private int _tagIndex;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("拆分属性子绑定槽列表 — 用于 XuiavrgSplit 的子属性匹配")]
|
||||
private List<AvrgBindingSlot> _bindingSlots = new List<AvrgBindingSlot>();
|
||||
|
||||
/// <summary>绑定的元数据引用</summary>
|
||||
public AvrgMemberMetaData BoundMetaData { get; private set; }
|
||||
|
||||
public XuiavrgFieldTag EntryTag => _entryTag;
|
||||
public int TagIndex => _tagIndex;
|
||||
|
||||
/// <summary>只读访问绑定槽列表</summary>
|
||||
public IReadOnlyList<AvrgBindingSlot> AvailableBindingSlots => _bindingSlots;
|
||||
|
||||
/// <summary>
|
||||
/// 由绑定系统调用,传递元数据并广播到 IXUIAvrgMDValueStyle 子组件
|
||||
/// </summary>
|
||||
public void InitializeWithMetaData(AvrgMemberMetaData metaData)
|
||||
{
|
||||
BoundMetaData = metaData;
|
||||
|
||||
if (metaData == null) return;
|
||||
|
||||
// 查找子节点上的所有样式扩展组件并广播元数据
|
||||
var styles = GetComponentsInChildren<IXUIAvrgMDValueStyle>(true);
|
||||
foreach (var style in styles)
|
||||
{
|
||||
// 跳过自身(XUIAvrgMDValue 不是 IXUIAvrgMDValueStyle)
|
||||
if (style is MonoBehaviour mb && mb.gameObject == gameObject)
|
||||
continue;
|
||||
|
||||
style.ApplyMetaData(metaData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 785b681a65a87f54e9ade90261da6d87
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a7f23b52396c0c840a4f927a3468a0c9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f1ad7693cfe6cf3448535dce51381df1
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3acab8b5655671b46b9d971bcea8f458
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,170 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a25c9ab25f90433458e244e933003acf
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMasterTextureLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Windows Store Apps
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Server
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: WebGL
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,170 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e3c21de628216474e86c67023a4bde1c
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMasterTextureLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Windows Store Apps
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Server
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: WebGL
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1,170 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 883ec87f7811c344abe8d513d6d168ef
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMasterTextureLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Windows Store Apps
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Server
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: WebGL
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,170 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c6de31d1cb551b4d92a47d7de33d6c1
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMasterTextureLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 2
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 40, y: 40, z: 40, w: 40}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Windows Store Apps
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Server
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: WebGL
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 1537655665
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,170 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1d381dc371569114399174f217d02e57
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMasterTextureLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 2
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Windows Store Apps
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Server
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: WebGL
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
@@ -0,0 +1,170 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 05a970b527718854085e371889ed1337
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMasterTextureLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 2
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Windows Store Apps
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Server
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: WebGL
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
@@ -0,0 +1,170 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e03ba78232beded4791e0471c383898e
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMasterTextureLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 2
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Windows Store Apps
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Server
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: WebGL
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,170 @@
|
||||
fileFormatVersion: 2
|
||||
guid: df71a3bc7da099c48b8c6d4cd6bd4fa8
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMasterTextureLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 2
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Windows Store Apps
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Server
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: WebGL
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,170 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4a639d65a6d7d9f48b46143dbe12bbc7
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMasterTextureLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 2
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Windows Store Apps
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Server
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: WebGL
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
@@ -0,0 +1,170 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ab609e22c3f1fb644a9a80d06b4ea35b
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMasterTextureLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 2
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Windows Store Apps
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Server
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: WebGL
|
||||
maxTextureSize: 8192
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||