feat: 角色展示v1.2
This commit is contained in:
@@ -23,8 +23,9 @@ namespace SceneView
|
||||
public float buttonHeight = 50;
|
||||
|
||||
private string originalText = "";
|
||||
|
||||
|
||||
private GameObject? _targetGameObject; // 保存当前节点代表的GameObject
|
||||
private bool _childrenGenerated = false; // 标记子节点和组件是否已经生成
|
||||
private int _nodeDepth = 0; // 当前节点的深度
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
@@ -50,12 +51,14 @@ namespace SceneView
|
||||
rectTransform.sizeDelta = new Vector2(0, buttonHeight);
|
||||
rectTransform.anchorMin = Vector2.up;
|
||||
rectTransform.anchorMax = Vector2.one;
|
||||
rectTransform.pivot = new Vector2(0.5f, 1); // 确保轴心点在顶部中央,便于布局
|
||||
|
||||
var buttonConfig = ButtonConfig.Default;
|
||||
buttonConfig.RectConfig.Pivot = new Vector2(0.5f, 1);
|
||||
buttonConfig.RectConfig.AnchorMin = new Vector2(0, 1);
|
||||
buttonConfig.RectConfig.AnchorMax = new Vector2(1, 1);
|
||||
buttonConfig.RectConfig.SizeDelta = new Vector2(0, buttonHeight);
|
||||
buttonConfig.BackgroundColor = new Color(0.2f, 0.2f, 0.2f, 1f); // 默认背景色
|
||||
|
||||
// 创建按钮并检查是否已存在
|
||||
if (label == null)
|
||||
@@ -70,6 +73,7 @@ namespace SceneView
|
||||
}
|
||||
|
||||
text.alignment = TextAlignmentOptions.MidlineLeft;
|
||||
text.color = Color.white; // 确保文本颜色可见
|
||||
}
|
||||
|
||||
if (objectEnable == null)
|
||||
@@ -77,14 +81,15 @@ namespace SceneView
|
||||
var button = ControlUtilities.CreateButton(rectTransform, buttonConfig, null);
|
||||
objectEnable = button.button;
|
||||
objectText = button.text;
|
||||
button.text.text = "-";
|
||||
button.button.image.color = Color.yellow;
|
||||
objectText.text = ""; // 默认文本为空,使用颜色作指示
|
||||
objectText.color = Color.black; // 确保文本颜色可见
|
||||
button.button.image.color = Color.yellow; // 默认颜色
|
||||
var rect = objectEnable.GetComponent<RectTransform>();
|
||||
rect.anchorMin = Vector2.one;
|
||||
rect.anchorMax = Vector2.one;
|
||||
rect.pivot = Vector2.one / 2;
|
||||
rect.pivot = new Vector2(1, 1); // 右上角锚点
|
||||
rect.sizeDelta = new Vector2(30, 30);
|
||||
rect.anchoredPosition = new Vector2(-25, -25);
|
||||
rect.anchoredPosition = new Vector2(-10, -10); // 距离右上角10像素
|
||||
}
|
||||
|
||||
// 检查子对象是否存在
|
||||
@@ -115,12 +120,15 @@ namespace SceneView
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 确保子内容一开始是隐藏的 (懒加载的初始状态)
|
||||
child.SetActive(false);
|
||||
|
||||
childRectTransform.anchorMin = new Vector2(0, 1);
|
||||
childRectTransform.anchorMax = new Vector2(1, 1);
|
||||
childRectTransform.pivot = new Vector2(0.5f, 1);
|
||||
childRectTransform.offsetMax = new Vector2(0, -buttonHeight);
|
||||
childRectTransform.offsetMin = new Vector2(0, 0);
|
||||
childRectTransform.offsetMax = new Vector2(0, -buttonHeight); // 占据主按钮以下的空间
|
||||
childRectTransform.offsetMin = new Vector2(0, 0); // 确保底部没有额外偏移
|
||||
|
||||
verticalLayout = child.GetComponent<VerticalLayoutGroup>();
|
||||
var sizeFitter = child.GetComponent<ContentSizeFitter>();
|
||||
@@ -144,121 +152,119 @@ namespace SceneView
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 调整布局组参数,确保正确计算
|
||||
verticalLayout.padding.left = (int)(buttonHeight * 0.5f); // 增加左边距以缩进
|
||||
verticalLayout.padding.top = 2;
|
||||
verticalLayout.padding.bottom = 4;
|
||||
verticalLayout.spacing = 2;
|
||||
verticalLayout.childControlHeight = false;
|
||||
verticalLayout.childControlWidth = true;
|
||||
verticalLayout.childControlHeight = false; // 由ContentSizeFitter控制子节点高度
|
||||
verticalLayout.childControlWidth = true; // 子节点宽度按父级宽度自适应
|
||||
verticalLayout.childForceExpandHeight = false; // 不要强制展开高度
|
||||
verticalLayout.childAlignment = TextAnchor.UpperLeft; // 确保子元素从上开始排列
|
||||
|
||||
sizeFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
}
|
||||
|
||||
public void InsertChild(RectTransform childRectTransform)
|
||||
// 修改为只初始化当前节点,不立即生成子内容
|
||||
public IEnumerator InitializeNode(GameObject? targetObject, int depth = 0)
|
||||
{
|
||||
if (child != null && childRectTransform != null)
|
||||
// 将子节点添加到 content 容器中
|
||||
childRectTransform.SetParent(child.transform, false);
|
||||
_targetGameObject = targetObject;
|
||||
_nodeDepth = depth;
|
||||
|
||||
// 清除之前的监听器,避免重复
|
||||
label?.onClick.RemoveAllListeners();
|
||||
objectEnable?.onClick.RemoveAllListeners();
|
||||
|
||||
if (targetObject == null) // 代表这是场景根节点
|
||||
{
|
||||
originalText = $"场景 {SceneManager.GetActiveScene().name}";
|
||||
if (text) text.text = originalText + " →"; // 默认收拢,显示右箭头
|
||||
label?.onClick.AddListener(OnExpand); // 场景节点点击展开自身
|
||||
objectEnable?.gameObject.SetActive(false); // 场景节点没有Enable/Disable功能
|
||||
yield break; // 场景节点不立即生成根对象,等待点击展开
|
||||
}
|
||||
|
||||
// 过滤掉特定Canvas
|
||||
if (targetObject == null || targetObject.name == CanvasControl.ViewCanvasName)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
originalText = GetIndentedName(targetObject.name, depth);
|
||||
if (text != null) text.text = originalText + " →"; // 默认收拢,显示右箭头
|
||||
|
||||
// 注册展开事件
|
||||
label?.onClick.AddListener(OnExpand);
|
||||
|
||||
// 注册对象启用/禁用事件
|
||||
objectEnable?.gameObject.SetActive(true);
|
||||
objectEnable?.onClick.AddListener(() => OnObjectEnable(_targetGameObject, objectEnable, objectText));
|
||||
UpdateObjButton(_targetGameObject, objectEnable, objectText);
|
||||
|
||||
// 确保子内容一开始是隐藏的
|
||||
if (child) child.SetActive(false);
|
||||
yield return StartCoroutine(DelayedUpdateHeight()); // 初始化时异步更新高度
|
||||
}
|
||||
|
||||
private void OnExpand(GameObject go)
|
||||
{
|
||||
if (!go) return;
|
||||
CanvasControl.FocusGameObject = go;
|
||||
Expand();
|
||||
}
|
||||
private void Expand()
|
||||
// 修改 OnExpand 方法以实现懒加载
|
||||
private void OnExpand()
|
||||
{
|
||||
if (child == null)
|
||||
{
|
||||
Debug.LogError("Child content not found.");
|
||||
return;
|
||||
}
|
||||
CanvasControl.FocusGameObject=_targetGameObject;
|
||||
|
||||
child.SetActive(!child.activeSelf);
|
||||
|
||||
UpdateHeight();
|
||||
|
||||
// 更新名称以模拟动画效果
|
||||
if (child.activeSelf)
|
||||
// 如果已经生成,直接切换显示状态
|
||||
if (_childrenGenerated)
|
||||
{
|
||||
if (text != null) text.text = originalText + " ↓"; // 下三角符号
|
||||
ToggleExpansionVisuals();
|
||||
StartCoroutine(DelayedUpdateHeight()); // 每次展开/收拢后异步更新高度
|
||||
}
|
||||
else
|
||||
{
|
||||
if (text != null) text.text = originalText + " →"; // 右三角符号
|
||||
else // 如果子内容未生成,则异步生成
|
||||
{
|
||||
ToggleExpansionVisuals(); // 生成完成后再切换显示状态 (展开或根据后续逻辑)
|
||||
// 在生成子节点之前,确保子内容容器是激活的
|
||||
child.SetActive(true); // <--- 新增这行,在生成子节点之前激活容器
|
||||
|
||||
StartCoroutine(GenerateChildrenContent(_targetGameObject, _nodeDepth + 1, () =>
|
||||
{
|
||||
_childrenGenerated = true;
|
||||
// 注意:ToggleExpansionVisuals 会再次调用 child.SetActive()
|
||||
// 如果是展开状态,它会保持 true。如果是收拢,则设置为 false。
|
||||
StartCoroutine(DelayedUpdateHeight()); // 生成并展开后异步更新高度
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateHeight()
|
||||
|
||||
// 新增方法:生成子内容(组件和子GameObject)
|
||||
private IEnumerator GenerateChildrenContent(GameObject? targetObject, int depth, Action? onComplete = null)
|
||||
{
|
||||
if (child == null || childRectTransform == null || rectTransform == null)
|
||||
{
|
||||
Debug.LogError("Failed to update height. Child, ChildRectTransform, or RectTransform is null.");
|
||||
return;
|
||||
}
|
||||
ClearChildNodes(); // 在生成之前清空所有旧的子节点
|
||||
|
||||
if (child.activeSelf)
|
||||
rectTransform.sizeDelta =
|
||||
new Vector2(rectTransform.sizeDelta.x, buttonHeight + childRectTransform.sizeDelta.y);
|
||||
else
|
||||
rectTransform.sizeDelta = new Vector2(rectTransform.sizeDelta.x, buttonHeight);
|
||||
|
||||
if (parent != null)
|
||||
if (targetObject == null) // 场景根节点,需要遍历根GameObject
|
||||
{
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(parent.childRectTransform);
|
||||
parent.UpdateHeight();
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator DisplayGameObjectStructureCoroutineImpl(GameObject targetObject, int depth,
|
||||
Action onComplete = null)
|
||||
{
|
||||
if (targetObject == null)
|
||||
{
|
||||
// 如果目标对象为空,则遍历场景中的所有对象
|
||||
var rootObjects = SceneManager.GetActiveScene().GetRootGameObjects();
|
||||
var totalRootObjects = rootObjects.Length;
|
||||
var completedRootObjects = 0;
|
||||
originalText = $"场景{SceneManager.GetActiveScene().name}";
|
||||
if (text) text.text = originalText;
|
||||
foreach (var rootObject in rootObjects)
|
||||
{
|
||||
if (!rootObject) continue;
|
||||
var childNode = new GameObject($"{rootObject.name}").AddComponent<TreeViewNode>();
|
||||
if (!rootObject || rootObject.name == CanvasControl.ViewCanvasName) continue;
|
||||
|
||||
var childNodeGO = new GameObject(rootObject.name);
|
||||
var childNode = childNodeGO.AddComponent<TreeViewNode>();
|
||||
childNode.transform.SetParent(childRectTransform, false);
|
||||
childNode.parent = this;
|
||||
childNode.CreateUI();
|
||||
StartCoroutine(childNode.DisplayGameObjectStructureCoroutineImpl(rootObject, depth + 1,
|
||||
() =>
|
||||
{
|
||||
completedRootObjects++;
|
||||
if (completedRootObjects == totalRootObjects)
|
||||
{
|
||||
// 所有根对象及其子对象都已处理完成
|
||||
onComplete?.Invoke();
|
||||
Expand();
|
||||
}
|
||||
}));
|
||||
yield return null;
|
||||
childNode.CreateUI(); // 确保UI元素已创建
|
||||
// yield return StartCoroutine(childNode.InitializeNode(rootObject, depth)); // 浅初始化子节点
|
||||
yield return null; // 每处理一个子节点就yield一次,分散帧压力
|
||||
}
|
||||
|
||||
onComplete?.Invoke();
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (targetObject.name == CanvasControl.ViewCanvasName)
|
||||
yield break;
|
||||
|
||||
label?.onClick.RemoveAllListeners();
|
||||
label?.onClick.AddListener(() =>OnExpand(targetObject));
|
||||
ClearChildNodes();
|
||||
|
||||
originalText = GetIndentedName(targetObject.name, depth);
|
||||
if (text != null) text.text = originalText;
|
||||
|
||||
objectEnable.onClick.AddListener(() => OnObjectEnable(targetObject, objectEnable, objectText));
|
||||
UpdateObjButton(targetObject, objectEnable, objectText);
|
||||
|
||||
// 生成当前GameObject的组件按钮
|
||||
var components = targetObject.GetComponents<Behaviour>();
|
||||
foreach (var component in components)
|
||||
{
|
||||
@@ -270,8 +276,9 @@ namespace SceneView
|
||||
|
||||
var config = ButtonConfig.Default;
|
||||
config.RectConfig.SizeDelta = new Vector2(0, 40);
|
||||
config.BackgroundColor = Color.green;
|
||||
config.Text = new string(' ', (depth + 1) * 2) + component.GetType().Name;
|
||||
config.BackgroundColor = component.enabled ? new Color(0.2f, 0.4f, 0.2f, 1f) : new Color(0.4f, 0.2f, 0.2f, 1f); // 根据组件启用状态设置颜色
|
||||
config.Text = GetIndentedName(component.GetType().Name, depth, true); // 组件也需要缩进
|
||||
config.TextColor = Color.white;
|
||||
var buttonBack = ControlUtilities.CreateButton(childRectTransform, config, null);
|
||||
if (buttonBack.button == null)
|
||||
{
|
||||
@@ -281,16 +288,11 @@ namespace SceneView
|
||||
|
||||
var button = buttonBack.button;
|
||||
var comp = component;
|
||||
buttonBack.button.onClick.AddListener(() => OnButtonClick(button, comp));
|
||||
yield return null;
|
||||
button.onClick.AddListener(() => OnButtonClick(button, comp));
|
||||
yield return null; // 每创建一个组件按钮就yield一次
|
||||
}
|
||||
|
||||
// 计数器,用于跟踪子协程的数量
|
||||
var childCount = targetObject.transform.childCount;
|
||||
var completedChildren = 0;
|
||||
var allChildrenExpanded = false;
|
||||
|
||||
// 遍历目标对象的所有子对象
|
||||
// 遍历目标对象的所有子对象并创建新的TreeViewNode(浅初始化)
|
||||
foreach (Transform childTransform in targetObject.transform)
|
||||
{
|
||||
if (childTransform == null)
|
||||
@@ -299,73 +301,119 @@ namespace SceneView
|
||||
continue;
|
||||
}
|
||||
|
||||
var childNode = new GameObject($"{childTransform.gameObject.name}").AddComponent<TreeViewNode>();
|
||||
var childNodeGO = new GameObject(childTransform.gameObject.name);
|
||||
var childNode = childNodeGO.AddComponent<TreeViewNode>();
|
||||
childNode.transform.SetParent(childRectTransform, false);
|
||||
childNode.parent = this;
|
||||
childNode.CreateUI();
|
||||
StartCoroutine(childNode.DisplayGameObjectStructureCoroutineImpl(childTransform.gameObject, depth + 1,
|
||||
() =>
|
||||
{
|
||||
completedChildren++;
|
||||
if (completedChildren == childCount) allChildrenExpanded = true;
|
||||
}));
|
||||
yield return null;
|
||||
childNode.CreateUI(); // 确保UI元素已创建
|
||||
// yield return StartCoroutine(childNode.InitializeNode(childTransform.gameObject, depth)); // 浅初始化子节点
|
||||
yield return null; // 每处理一个子节点就yield一次
|
||||
}
|
||||
|
||||
// 等待所有子协程完成
|
||||
while (!allChildrenExpanded && childCount > 0) yield return null;
|
||||
|
||||
// 调用完成回调
|
||||
onComplete?.Invoke();
|
||||
|
||||
// 在所有子节点初始化完毕后调用Expand
|
||||
Expand();
|
||||
}
|
||||
|
||||
public IEnumerator DisplayGameObjectStructureCoroutine(GameObject targetObject, int depth = 0)
|
||||
private void ToggleExpansionVisuals()
|
||||
{
|
||||
yield return DisplayGameObjectStructureCoroutineImpl(targetObject, depth);
|
||||
if (child == null) return;
|
||||
|
||||
// 切换 child 的激活状态
|
||||
child.SetActive(!child.activeSelf);
|
||||
|
||||
if (text != null)
|
||||
{
|
||||
if (child.activeSelf)
|
||||
{
|
||||
text.text = originalText + " ↓"; // 下三角符号表示展开
|
||||
}
|
||||
else
|
||||
{
|
||||
text.text = originalText + " →"; // 右三角符号表示收拢
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string GetIndentedName(string name, int depth)
|
||||
// 异步更新高度,给布局系统一帧时间来计算
|
||||
private IEnumerator DelayedUpdateHeight()
|
||||
{
|
||||
// 等待一帧,让 LayoutGroup 和 ContentSizeFitter 完成计算
|
||||
yield return null;
|
||||
InternalUpdateHeight();
|
||||
}
|
||||
|
||||
private void InternalUpdateHeight()
|
||||
{
|
||||
if (rectTransform == null || child == null || childRectTransform == null)
|
||||
{
|
||||
return; // Editor退出时可能为null
|
||||
}
|
||||
|
||||
float currentChildHeight = 0f;
|
||||
if (child.activeSelf && _childrenGenerated)
|
||||
{
|
||||
// 强制重建 childRectTransform 的布局,以获取准确的 PreferredHeight
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(childRectTransform);
|
||||
currentChildHeight = LayoutUtility.GetPreferredHeight(childRectTransform);
|
||||
}
|
||||
|
||||
// 设置主RectTransform的高度 = 按钮高度 + 子内容高度
|
||||
rectTransform.sizeDelta = new Vector2(rectTransform.sizeDelta.x, buttonHeight + currentChildHeight);
|
||||
|
||||
// Debug.Log($"Node: {gameObject.name}, childActive: {child.activeSelf}, childrenGenerated: {_childrenGenerated}, currentChildHeight: {currentChildHeight}, finalNodeHeight: {rectTransform.sizeDelta.y}");
|
||||
|
||||
// 如果有父节点,通知父节点重新计算布局
|
||||
if (parent != null)
|
||||
{
|
||||
// 强制重建父节点的 childRectTransform 的布局
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(parent.childRectTransform);
|
||||
// 递归父节点异步更新高度
|
||||
StartCoroutine(parent.DelayedUpdateHeight());
|
||||
}
|
||||
}
|
||||
|
||||
// 调整 GetIndentedName 以支持组件类型的缩进
|
||||
private string GetIndentedName(string name, int depth, bool isComponent = false)
|
||||
{
|
||||
var indent = "";
|
||||
for (var i = 0; i < depth; i++) indent += $"{i}_";
|
||||
int baseIndent = depth; // GameObject的深度
|
||||
if (isComponent) baseIndent++; // 组件比GameObject多一级缩进
|
||||
|
||||
return $"{indent}_{name}";
|
||||
// 增加额外的左边距,以产生层级感
|
||||
for (var i = 0; i < baseIndent; i++)
|
||||
{
|
||||
indent += " ";
|
||||
}
|
||||
return $"{indent}{name}";
|
||||
}
|
||||
|
||||
public void ClearChildNodes()
|
||||
{
|
||||
if (childRectTransform != null)
|
||||
foreach (Transform childRectTransform in this.childRectTransform)
|
||||
Destroy(childRectTransform.gameObject);
|
||||
{
|
||||
// 遍历并销毁所有子对象
|
||||
for (int i = childRectTransform.childCount - 1; i >= 0; i--)
|
||||
{
|
||||
Destroy(childRectTransform.GetChild(i).gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnButtonClick(Button button, Behaviour component)
|
||||
{
|
||||
if (button == null)
|
||||
{
|
||||
Debug.LogError("button is null");
|
||||
return;
|
||||
}
|
||||
|
||||
if (component == null)
|
||||
{
|
||||
Debug.LogError("component is null");
|
||||
return;
|
||||
}
|
||||
if (button == null || component == null) return;
|
||||
|
||||
// 切换组件的启用状态
|
||||
var isEnabled = !component.enabled;
|
||||
component.enabled = isEnabled;
|
||||
// 根据组件的启用状态修改按钮背景颜色
|
||||
button.image.color = isEnabled ? Color.green : Color.red;
|
||||
button.image.color = isEnabled ? new Color(0.2f, 0.4f, 0.2f, 1f) : new Color(0.4f, 0.2f, 0.2f, 1f);
|
||||
}
|
||||
|
||||
private void OnObjectEnable(GameObject gameObject, Button button, TMP_Text text)
|
||||
private void OnObjectEnable(GameObject? gameObject, Button? button, TMP_Text? text)
|
||||
{
|
||||
Debug.Log($"设置{gameObject.name}={!gameObject.activeSelf}");
|
||||
if (gameObject == null || button == null || text == null) return;
|
||||
|
||||
Debug.Log($"设置{gameObject.name} active = {!gameObject.activeSelf}");
|
||||
gameObject.SetActive(!gameObject.activeSelf);
|
||||
UpdateObjButton(gameObject, button, text);
|
||||
}
|
||||
@@ -375,13 +423,14 @@ namespace SceneView
|
||||
if (gameObject.activeSelf)
|
||||
{
|
||||
button.image.color = Color.green;
|
||||
text.text = "√";
|
||||
text.text = "√";
|
||||
}
|
||||
else
|
||||
{
|
||||
button.image.color = Color.red;
|
||||
text.text = "×";
|
||||
text.text = "×";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user