(client) feat:UI更新 chore:LogUI性能更好,并且修复反复打开Log消失的bug,删除部分非预期的警告

This commit is contained in:
m0_75251201
2025-08-30 00:26:27 +08:00
parent 34abe845b1
commit 7252698764
58 changed files with 4849 additions and 508 deletions

View File

@ -3,14 +3,67 @@ using UnityEngine.UI;
namespace UI
{
public class BarUI:MonoBehaviour
public class BarUI : MonoBehaviour
{
[SerializeField] private Image image;
[Header("Progress Gradient")] [SerializeField]
private Gradient progressGradient; // 用于定义多色进度渐变
[Header("Editor Preview")] [SerializeField] [Range(0, 1)]
private float _editorProgressPreview = 0f; // 用于在编辑器中预览的进度值
/// <summary>
/// 获取或设置进度条的当前进度 (0-1)。
/// 设置时会同时更新进度条的填充量和根据渐变更新颜色。
/// </summary>
public float Progress
{
get => image.fillAmount;
set => image.fillAmount = value;
set
{
// 在运行时检查image是否已赋值
if (image == null)
{
Debug.LogWarning("BarUI: Image reference is not set! Cannot update progress or color.", this);
return;
}
// 确保进度值在0到1之间防止出现异常情况
float clampedValue = Mathf.Clamp01(value);
image.fillAmount = clampedValue;
// 使用Gradient的Evaluate方法根据进度值获取对应的渐变颜色
// Unity编辑器会自动为Gradient字段初始化一个默认实例但在某些特殊运行时情况下
// 还是可以加一个null检查以增加健壮性。
if (progressGradient != null)
{
image.color = progressGradient.Evaluate(clampedValue);
}
else
{
// 如果梯度未定义(极少发生),则使用默认颜色并发出警告
Debug.LogWarning("BarUI: Progress Gradient is not set! Using default white color.", this);
image.color = Color.white;
}
}
}
// OnValidate是Unity编辑器特有的方法当脚本实例在编辑器中被加载或Inspector中的数据被修改时调用
private void OnValidate()
{
// 只有当存在Image引用时才进行更新避免在编辑器中因未赋值而引发NullReferenceException
if (image != null)
{
// 在编辑器中修改_editorProgressPreview时同步更新实际的Progress
// 这会触发Progress属性的setter进而更新fillAmount和color
Progress = _editorProgressPreview;
}
else
{
// 在编辑器中未分配Image时给出提示防止用户迷惑
Debug.LogWarning("BarUI: Image reference is not assigned. Editor preview disabled.", this);
}
}
}
}

View File

@ -2,7 +2,8 @@
using System.Linq; // 用于可能的LINQ操作但在此版本中可能不直接使用
using TMPro; // 用于 TextMeshPro 组件
using UnityEngine;
using UnityEngine.Events; // 用于 UnityEvent
using UnityEngine.Events;
using UnityEngine.Serialization; // 用于 UnityEvent
namespace UI
{
@ -11,7 +12,7 @@ namespace UI
[Tooltip("当按钮被点击时触发的UnityEvent。可以在Inspector中配置.")]
public UnityEvent OnClick;
public MeshRenderer renderer; // 请确保在Inspector中拖拽赋值
[FormerlySerializedAs("renderer")] public MeshRenderer meshRenderer; // 请确保在Inspector中拖拽赋值
public Material outlineMaterial; // 请确保在Inspector中拖拽赋值
public TMP_Text text; // 文本组件
@ -20,7 +21,7 @@ namespace UI
private void Awake()
{
if (renderer == null)
if (meshRenderer == null)
{
Debug.LogError("Button3D: MeshRenderer is not assigned. Please assign it in the Inspector.", this);
return;
@ -42,7 +43,7 @@ namespace UI
// 存储原始材质数组,以便在添加和移除描边时正确操作
// .ToArray() 是为了确保我们拿到的是拷贝,而不是引用,防止外部意外修改
_originalMaterials = renderer.materials.ToArray();
_originalMaterials = meshRenderer.materials.ToArray();
// 确保初始化时没有边框材质
// Important: 调用RemoveOutlineMaterialInternal()可能会清除_originalMaterials所以要确保先保存
@ -67,7 +68,7 @@ namespace UI
private void OnMouseEnter()
{
if (renderer == null) return;
if (meshRenderer == null) return;
// 鼠标进入时显示文本
if (text != null)
@ -78,30 +79,30 @@ namespace UI
// 如果没有描边材质,或者渲染器已经有描边材质,就没必要再添加了
// 检查当前材质数组长度是否已经大于等于_originalMaterials的长度+1
// 这样可以避免重复添加
if (outlineMaterial == null || renderer.materials.Length > _originalMaterials.Length)
if (outlineMaterial == null || meshRenderer.materials.Length > _originalMaterials.Length)
{
return;
}
// 如果当前材质数组和_originalMaterials不一致说明可能被其他逻辑修改了
// 稳妥起见最好先恢复到_originalMaterials
if (!renderer.materials.SequenceEqual(_originalMaterials))
if (!meshRenderer.materials.SequenceEqual(_originalMaterials))
{
//Debug.LogWarning("Button3D: Renderer materials were unexpectedly modified before OnMouseEnter. Restoring original materials.");
// 通常不应该发生,除非有其他脚本在修改
renderer.materials = _originalMaterials;
meshRenderer.materials = _originalMaterials;
}
// 创建一个新数组,包含原始材质和描边材质
Material[] newMaterials = new Material[_originalMaterials.Length + 1];
var newMaterials = new Material[_originalMaterials.Length + 1];
System.Array.Copy(_originalMaterials, newMaterials, _originalMaterials.Length);
newMaterials[_originalMaterials.Length] = outlineMaterial;
renderer.materials = newMaterials;
meshRenderer.materials = newMaterials;
}
private void OnMouseExit()
{
if (renderer == null) return;
if (meshRenderer == null) return;
// 鼠标离开时隐藏文本
if (text != null)
@ -124,7 +125,7 @@ namespace UI
/// </summary>
private void RemoveOutlineMaterialInternal()
{
if (renderer == null) return;
if (meshRenderer == null) return;
// 即使_originalMaterials == null因为Awake中的LogError也可以安全赋值空数组
// 但如果_originalMaterials确实是null说明Awake有问题后续操作也可能继续出错
if (_originalMaterials == null)
@ -134,7 +135,7 @@ namespace UI
}
// 直接将渲染器材质恢复为_originalMaterials的副本
renderer.materials = _originalMaterials.ToArray(); // 使用ToArray()确保赋值一个副本避免_originalMaterials被意外修改
meshRenderer.materials = _originalMaterials.ToArray(); // 使用ToArray()确保赋值一个副本避免_originalMaterials被意外修改
}
}
}

View File

@ -5,7 +5,7 @@ using UnityEngine.SceneManagement;
namespace UI
{
public class DevMenuUI : UIBase
public class DevMenuUI : FullScreenUI
{
public GameObject menuContent;

View File

@ -84,7 +84,7 @@ namespace UI
}
// 尝试将新的实体转换为角色类型。
Character newCharacter = entity as Character;
var newCharacter = entity as Character;
if (newCharacter != null)
{
focusedEntity = newCharacter;
@ -151,11 +151,11 @@ namespace UI
return;
}
int requiredUIs = focusedEntity.Inventory.Capacity;
int currentUIPoolSize = itemUIPool.Count; // 当前对象池中 ItemUI 实例的总数。
var requiredUIs = focusedEntity.Inventory.Capacity;
var currentUIPoolSize = itemUIPool.Count; // 当前对象池中 ItemUI 实例的总数。
// 遍历所有必要的物品槽位,复用对象池中的 ItemUI或在不足时创建新的 ItemUI。
for (int i = 0; i < requiredUIs; i++)
for (var i = 0; i < requiredUIs; i++)
{
ItemUI itemUI;
if (i < currentUIPoolSize)
@ -178,7 +178,7 @@ namespace UI
}
// 如果库存槽位数量减少,禁用对象池中多余的 ItemUI 实例。
for (int i = requiredUIs; i < currentUIPoolSize; i++)
for (var i = requiredUIs; i < currentUIPoolSize; i++)
{
if (itemUIPool[i] != null && itemUIPool[i].gameObject != null)
{
@ -213,9 +213,9 @@ namespace UI
return;
}
for (int i = 0; i < itemUIPool.Count; i++)
for (var i = 0; i < itemUIPool.Count; i++)
{
ItemUI itemUI = itemUIPool[i];
var itemUI = itemUIPool[i];
if (itemUI != null && itemUI.gameObject != null)
{
// 只有在 ItemUI 激活状态下才设置其选中状态避免对禁用UI的操作
@ -239,12 +239,12 @@ namespace UI
return;
}
float scrollInput = Input.GetAxis("Mouse ScrollWheel");
var scrollInput = Input.GetAxis("Mouse ScrollWheel");
if (scrollInput != 0) // 检测到滚轮输入
{
int currentSelection = focusedEntity.CurrentSelected;
int inventoryCapacity = focusedEntity.Inventory.Capacity;
var currentSelection = focusedEntity.CurrentSelected;
var inventoryCapacity = focusedEntity.Inventory.Capacity;
if (scrollInput > 0) // 滚轮向上,选择前一个
{

View File

@ -0,0 +1,21 @@
using Base;
using UnityEngine;
namespace UI
{
public class FullScreenUI:UIBase,ITickUI
{
public FullScreenUI()
{
isInputOccupied = true;
exclusive=true;
}
public virtual void TickUI()
{
if(!IsVisible)
return;
if(Input.GetKeyDown(KeyCode.Escape))
UIInputControl.Instance.Hide(this);
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 92b2dc3c0fc0475596413b883c51e9b2

View File

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using Prefab;
using TMPro;
using Prefab; // 假设 TextPrefab 在 Prefab 命名空间下
using TMPro; // 假设 TextPrefab 内部使用了 TextMeshPro
using UnityEngine;
namespace UI
@ -21,66 +21,80 @@ namespace UI
{ LogType.Assert, new Color(0.8f, 0.4f, 1f) }
};
private List<Transform> _logItems = new List<Transform>(); // 已创建的日志条目
// 逻辑修改将_logItems改为存储TextPrefab实例避免频繁GetComponent
private List<TextPrefab> _logItems = new List<TextPrefab>(); // 已创建的日志条目
private int _lastLogCount = 0; // 上次显示的日志数量
private void Start()
// 逻辑修改在Update中周期性检查日志数量变化并刷新UI
private void Update()
{
Logging.LogCapturer.Clear();
// 获取当前LogCapturer中的日志数量最旧在前
var currentCapturerLogCount = Logging.LogCapturer.GetLogs(reverseOrder: false).Count;
if (_lastLogCount != currentCapturerLogCount)
{
RefreshLogDisplay();
}
}
public override void Show()
{
base.Show();
RefreshLogDisplay();
RefreshLogDisplay(); // 首次显示时刷新
}
private void RefreshLogDisplay()
{
var logs = Logging.LogCapturer.GetLogs();
// 逻辑修改:获取"最旧在前,最新在后"的日志列表与UI显示顺序匹配
var currentLogsInCapturer = Logging.LogCapturer.GetLogs(reverseOrder: false);
var newLogCount = currentLogsInCapturer.Count;
var existingUiItemCount = _logItems.Count;
// 如果日志数量减少,清理多余的条目
if (logs.Count < _lastLogCount)
// 逻辑修改处理日志数量减少的情况日志被从LogCapturer的队列前端移除即最旧的日志
if (newLogCount < existingUiItemCount)
{
for (var i = logs.Count; i < _lastLogCount; i++)
// 计算需要移除的UI条目数量
var itemsToRemove = existingUiItemCount - newLogCount;
// 从_logItems的开头移除销毁最旧的UI元素
for (var i = 0; i < itemsToRemove; i++)
{
// 确保销毁对应的GameObject避免内存泄露
Destroy(_logItems[i].gameObject);
}
_logItems.RemoveRange(logs.Count, _logItems.Count - logs.Count);
_logItems.RemoveRange(0, itemsToRemove); // 从列表中移除引用
}
// 更新现有条目
for (var i = 0; i < Math.Min(logs.Count, _logItems.Count); i++)
// 逻辑修改处理日志数量增加的情况LogCapturer中新增日志
else if (newLogCount > existingUiItemCount)
{
UpdateLogEntry(_logItems[i], logs[logs.Count - 1 - i]);
}
// 添加新的条目
if (logs.Count > _lastLogCount)
{
for (var i = _lastLogCount; i < logs.Count; i++)
// 从现有UI条目数量开始创建新的UI条目并添加到_logItems末尾
for (var i = existingUiItemCount; i < newLogCount; i++)
{
CreateLogEntry(logs[logs.Count - 1 - i]);
CreateLogEntry(currentLogsInCapturer[i]); // 创建并添加新的UI元素会自动追加到_logItems
}
}
_lastLogCount = logs.Count;
// 逻辑修改:更新所有现有/剩余的UI条目内容确保与LogCapturer中的数据一致
// 这一步确保了UI元素能准确反映其对应的日志数据处理了任何可能的日志内容更新或初始设置。
for (var i = 0; i < newLogCount; i++)
{
UpdateLogEntry(_logItems[i], currentLogsInCapturer[i]);
}
_lastLogCount = newLogCount; // 更新上次显示的日志数量
}
private void CreateLogEntry(Logging.LogCapturer.LogEntry entry)
{
// 实例化文本预制体
// 实例化文本预制体,并将其添加到内容面板
var logItem = Instantiate(textPrefab, contentPanel);
_logItems.Add(logItem.transform);
_logItems.Add(logItem); // 逻辑修改直接添加TextPrefab实例
UpdateLogEntry(logItem.transform, entry);
UpdateLogEntry(logItem, entry); // 逻辑修改直接传入TextPrefab实例进行更新
}
private void UpdateLogEntry(Transform logItemTransform, Logging.LogCapturer.LogEntry entry)
// 逻辑修改UpdateLogEntry现在直接接收TextPrefab实例更高效
private void UpdateLogEntry(TextPrefab logItem, Logging.LogCapturer.LogEntry entry)
{
var logItem = logItemTransform.GetComponent<TextPrefab>();
// 设置文本内容
logItem.Label = entry.ToString();
@ -96,5 +110,22 @@ namespace UI
logItem.text.alignment = TextAlignmentOptions.TopLeft;
}
// 逻辑修改添加OnDestroy方法来清理动态创建的UI元素
private void OnDestroy()
{
if (_logItems != null)
{
foreach (var item in _logItems)
{
// 检查item及其gameObject是否仍有效以防在其他地方已经被销毁或为空
if (item != null && item.gameObject != null)
{
Destroy(item.gameObject);
}
}
_logItems.Clear(); // 清空列表引用
}
}
}
}
}

View File

@ -47,6 +47,7 @@ namespace UI
{
windowResolution.value = 0;
}
windowMode.value = (int)currentSettings.currentWindowMode;
}
public void CancelSettings()
{