(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

@ -1,7 +1,7 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using Utils;
using Utils; // 假设此命名空间包含MonoSingleton
namespace Base
{
@ -25,120 +25,239 @@ namespace Base
private bool _pause = false;
public bool Pause
{
get
{
return _pause;
}
get => _pause;
set
{
if (value)
if (value != _pause) // 逻辑修改说明避免重复设置Time.timeScale
{
Time.timeScale = 0;
Time.timeScale = value ? 0 : 1;
_pause = value;
}
else
{
Time.timeScale = 1;
}
_pause = value;
}
}
public List<ITickPhysics> tickPhysics = new();
public List<ITick> ticks = new();
public List<ITickUI> tickUIs = new();
// 修改点 1.1:主列表用于迭代
private readonly List<ITick> _ticks = new();
private readonly List<ITickPhysics> _tickPhysics = new();
private readonly List<ITickUI> _tickUIs = new();
// 修改点 1.1:缓冲列表用于添加
private readonly HashSet<ITick> _ticksToAdd = new(); // 逻辑修改说明使用HashSet避免重复添加提高查找效率
private readonly HashSet<ITickPhysics> _tickPhysicsToAdd = new();
private readonly HashSet<ITickUI> _tickUIsToAdd = new();
// 修改点 1.1:缓冲列表用于移除
private readonly HashSet<ITick> _ticksToRemove = new(); // 逻辑修改说明使用HashSet避免重复移除提高查找效率
private readonly HashSet<ITickPhysics> _tickPhysicsToRemove = new();
private readonly HashSet<ITickUI> _tickUIsToRemove = new();
private void Update()
{
// 逻辑修改说明UI部分的Tick不应受_pause影响
if (!_pause)
foreach (var tick in ticks)
{
// 逻辑修改说明:迭代时使用只读副本或确保不会被修改
// 这里是直接迭代_ticks确保_ticks在Update生命周期内不会被Add/Remove直接修改
// 添加和移除操作通过缓冲区在LateUpdate处理
foreach (var tick in _ticks)
{
tick.Tick();
foreach (var uiTick in tickUIs) uiTick.TickUI();
//if (timer > 1)
//{
// timer -= 1;
// Debug.Log("滴答");
//}
//timer += Time.deltaTime;
}
}
// UI更新通常不受游戏暂停影响例如菜单动画、UI计时器等除非UI本身也需要暂停逻辑。
// 根据需求决定是否受_pause影响。此处假设UI不暂停。
foreach (var uiTick in _tickUIs)
{
uiTick.TickUI();
}
}
private void FixedUpdate()
{
if (!_pause)
foreach (var physicsTick in tickPhysics)
{
foreach (var physicsTick in _tickPhysics)
{
physicsTick.TickPhysics();
}
}
}
// 修改点 1.4在LateUpdate应用缓冲区的更改
private void LateUpdate()
{
ApplyBufferedChanges();
}
// 修改点 3.1OnDestroy保持不变确保事件移除
private void OnDestroy()
{
// 移除事件监听
SceneManager.sceneLoaded -= OnSceneLoaded;
}
protected override void OnStart()
// 修改点 3.1OnStart保持不变负责事件注册和初始化
protected override void OnStart() // MonoSingleton的Awake后调用
{
// 注册场景加载事件
SceneManager.sceneLoaded += OnSceneLoaded;
// 初始化时调用一次
Init();
// 逻辑修改说明Initial清理所有列表而不是填充
Init();
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
// 场景加载完成后调用 Init 方法
Init();
}
/// <summary>
/// 初始化方法
/// 初始化/重置方法。清空所有注册列表和缓冲列表。
/// </summary>
// 修改点 2.1Init() 方法不再负责FindObjectsByType
public void Init()
{
ticks.Clear();
tickPhysics.Clear();
tickUIs.Clear();
_ticks.Clear();
_tickPhysics.Clear();
_tickUIs.Clear();
_ticksToAdd.Clear();
_tickPhysicsToAdd.Clear();
_tickUIsToAdd.Clear();
_ticksToRemove.Clear();
_tickPhysicsToRemove.Clear();
_tickUIsToRemove.Clear();
// 逻辑修改说明:移除 FindObjectsByType 逻辑
// 对象应通过其各自的 OnEnable/OnDisable 生命周期来注册/反注册
foreach (var obj in FindObjectsByType<MonoBehaviour>(FindObjectsSortMode.None))
{
if (obj is ITick tickObj) ticks.Add(tickObj);
if (obj is ITickPhysics physicsObj) tickPhysics.Add(physicsObj);
if (obj is ITickUI uiObj) tickUIs.Add(uiObj);
if (obj is ITick tickObj) _ticks.Add(tickObj);
if (obj is ITickPhysics physicsObj) _tickPhysics.Add(physicsObj);
if (obj is ITickUI uiObj) _tickUIs.Add(uiObj);
}
}
// 修改点 1.2 & 1.3将更改放入缓冲并在LateUpdate统一处理
public static void AddTick(ITick tick)
{
if (Instance != null && !Instance.ticks.Contains(tick))
Instance.ticks.Add(tick);
if (Instance != null && tick != null) // 逻辑修改说明添加null检查
{
Instance._ticksToAdd.Add(tick);
Instance._ticksToRemove.Remove(tick); // 逻辑修改说明:如果在待移除列表,则先移除
}
}
public static void RemoveTick(ITick tick)
{
if (Instance != null)
Instance.ticks.Remove(tick);
if (Instance != null && tick != null) // 逻辑修改说明添加null检查
{
Instance._ticksToRemove.Add(tick);
Instance._ticksToAdd.Remove(tick); // 逻辑修改说明:如果在待添加列表,则先移除
}
}
public static void AddTickPhysics(ITickPhysics physics)
{
if (Instance != null && !Instance.tickPhysics.Contains(physics))
Instance.tickPhysics.Add(physics);
if (Instance != null && physics != null)
{
Instance._tickPhysicsToAdd.Add(physics);
Instance._tickPhysicsToRemove.Remove(physics);
}
}
public static void RemoveTickPhysics(ITickPhysics physics)
{
if (Instance != null)
Instance.tickPhysics.Remove(physics);
if (Instance != null && physics != null)
{
Instance._tickPhysicsToRemove.Add(physics);
Instance._tickPhysicsToAdd.Remove(physics);
}
}
public static void AddTickUI(ITickUI ui)
{
if (Instance != null && !Instance.tickUIs.Contains(ui))
Instance.tickUIs.Add(ui);
if (Instance != null && ui != null)
{
Instance._tickUIsToAdd.Add(ui);
Instance._tickUIsToRemove.Remove(ui);
}
}
public static void RemoveTickUI(ITickUI ui)
{
if (Instance != null)
Instance.tickUIs.Remove(ui);
if (Instance != null && ui != null)
{
Instance._tickUIsToRemove.Add(ui);
Instance._tickUIsToAdd.Remove(ui);
}
}
// 修改点 1.3:应用缓冲区的更改
private void ApplyBufferedChanges()
{
// 先处理移除
if (_ticksToRemove.Count > 0)
{
foreach (var tick in _ticksToRemove)
{
_ticks.Remove(tick);
}
_ticksToRemove.Clear();
}
if (_tickPhysicsToRemove.Count > 0)
{
foreach (var physicsTick in _tickPhysicsToRemove)
{
_tickPhysics.Remove(physicsTick);
}
_tickPhysicsToRemove.Clear();
}
if (_tickUIsToRemove.Count > 0)
{
foreach (var uiTick in _tickUIsToRemove)
{
_tickUIs.Remove(uiTick);
}
_tickUIsToRemove.Clear();
}
// 后处理添加
if (_ticksToAdd.Count > 0)
{
foreach (var tick in _ticksToAdd)
{
if (!_ticks.Contains(tick)) // 逻辑修改说明:避免重复添加到主列表
{
_ticks.Add(tick);
}
}
_ticksToAdd.Clear();
}
if (_tickPhysicsToAdd.Count > 0)
{
foreach (var physicsTick in _tickPhysicsToAdd)
{
if (!_tickPhysics.Contains(physicsTick))
{
_tickPhysics.Add(physicsTick);
}
}
_tickPhysicsToAdd.Clear();
}
if (_tickUIsToAdd.Count > 0)
{
foreach (var uiTick in _tickUIsToAdd)
{
if (!_tickUIs.Contains(uiTick))
{
_tickUIs.Add(uiTick);
}
}
_tickUIsToAdd.Clear();
}
}
}
}
}

View File

@ -202,8 +202,6 @@ namespace Base
protected override void OnStart()
{
SceneManager.sceneLoaded += OnSceneLoaded;
RegisterAllWindows();
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)

View File

@ -19,41 +19,61 @@ namespace CameraControl
private Vector3 _dragOrigin; // 拖拽操作的起始世界坐标
private bool _isDragging; // 标记摄像机是否正在被拖拽
private Camera _camera; // 当前场景中的主摄像机引用
private Camera _cachedCamera; // 缓存的场景摄像机引用
/// <summary>
/// 获取当前生效的场景摄像机。懒加载并缓存。
/// </summary>
public Camera CurrentCamera
{
get
{
if (!_cachedCamera)
{
Debug.Log("[CameraControl] Attempting to get main camera...");
_cachedCamera = Camera.main; // 尝试获取主摄像机
if (!_cachedCamera) // 如果主摄像机不存在或未标记,则尝试查找场景中的第一个摄像机
{
Debug.LogWarning("[CameraControl] Main camera not found, attempting to find first object of type Camera.");
_cachedCamera = FindFirstObjectByType<Camera>();
}
if (!_cachedCamera)
{
Debug.LogError("[CameraControl] No available camera found in scene! CameraControl functionality will be limited.");
}
else
{
Debug.Log($"[CameraControl] Camera cached: {_cachedCamera.name}");
}
}
return _cachedCamera;
}
}
private int dimensionId; // 当前摄像机控制器关注的维度索引
private string[] dimensionList; // 维度名称列表
/// <summary>
/// MonoSingleton 的 OnStart 方法,在单例首次创建并激活时调用,早于普通的 Start 方法。
/// 用于初始化摄像机缓存。
/// </summary>
protected override void OnStart()
{
Debug.Log("[CameraControl] OnStart called. Subscribing to OnFocusedDimensionChanged event.");
Program.Instance.OnFocusedDimensionChanged += Init;
}
/// <summary>
/// 当脚本实例被销毁时调用。
/// 用于取消订阅 Program.Instance 的事件,防止内存泄漏。
/// </summary>
private void OnDestroy()
{
// 在OnDestroy时进行Program.Instance检查是合理的因为Program实例可能已被销毁。
if (Program.Instance != null)
{
Program.Instance.OnFocusedDimensionChanged -= Init;
}
Debug.Log("[CameraControl] OnDestroy called. Unsubscribing from OnFocusedDimensionChanged event.");
Program.Instance.OnFocusedDimensionChanged -= Init;
}
/// <summary>
/// 脚本实例在第一次帧更新之前被启用时调用。
/// 用于订阅 Program.Instance 的事件,并进行初始设置。
/// </summary>
private void Start()
{
// 在Start时进行Program.Instance检查是合理的防止CameraControl比Program实例更早启动。
if (Program.Instance != null)
{
Program.Instance.OnFocusedDimensionChanged += Init; // 订阅聚焦维度改变事件
Init(); // 首次调用初始化,根据当前聚焦维度设置摄像机状态
}
else
{
Debug.LogError("CameraControl 的 Start 方法中 Program.Instance 为空。请检查 Program 单例的初始化顺序。摄像机控制功能将无法初始化。");
}
}
/// <summary>
/// 根据指定的维度对象初始化摄像机控制器。
@ -62,36 +82,21 @@ namespace CameraControl
/// <param name="obj">当前聚焦的维度对象。</param>
private void Init(Dimension obj)
{
// 修改点 1: 统一 Unity 对象 `null` 检查,移除冗余 `_camera == null`
// 确保相机引用有效,如果为空则尝试获取主相机
if (!_camera)
{
_camera = Camera.main;
if (!_camera) // 再次检查获取是否成功
{
_camera = FindFirstObjectByType<Camera>();
}
}
Debug.Log($"[CameraControl] Init(Dimension obj) called. Focused Dimension: {(obj != null ? obj.name : "null")}");
if (!_camera) // 如果到此为止仍未获取到相机,记录错误并返回
dimensionList = Program.Instance.Dimensions;
Debug.Log($"[CameraControl] Dimension list updated. Count: {(dimensionList != null ? dimensionList.Length.ToString() : "null")}");
if (!CurrentCamera) // 如果摄像机仍未找到,记录错误并返回
{
Debug.LogError("场景中未找到摄像机!CameraControl 功能将无法完全初始化。");
// 如果没有相机,后续依赖相机的逻辑都无法执行,直接返回。
// dimensionList 的获取如果逻辑上不依赖相机,可以放在此处之外。
// 但当前上下文中Init的主要目的是初始化相机视角等无相机则无意义。
Debug.LogError("[CameraControl] No camera found! CameraControl functionality will not be fully initialized.");
return;
}
// 修改点 2: 移除对 `Program.Instance` 的冗余 `null` 检查
// 修改点 3: `Init` 方法中 `dimensionList` 的获取位置
// 总是获取最新的维度列表,因为外部维度状态可能会变化
dimensionList = Program.Instance.Dimensions;
// 处理 obj 为 null 的情况
// 处理 obj 为 null 的情况 - 此时 dimensionList 已更新
if (!obj)
{
Debug.LogWarning("Init 方法在聚焦维度为空的情况下被调用。维度列表已更新,但摄像机状态未根据特定维度设置。");
// 此时 dimensionId 仍然是默认值或上次设置的值,不进行 cameraPosition 的设置。
Debug.LogWarning("[CameraControl] Init method called with a null focused dimension. Dimension list updated, but camera state not set based on a specific dimension.");
return;
}
@ -100,15 +105,24 @@ namespace CameraControl
if (focusedIndex != -1)
{
dimensionId = focusedIndex;
Debug.Log($"[CameraControl] Focused dimension '{obj.name}' found at index {dimensionId}.");
}
else
{
Debug.LogWarning($"聚焦维度 '{obj.name}' 未在维度列表中找到。回退到 ID 0");
Debug.LogWarning($"[CameraControl] Focused dimension '{obj.name}' not found in the dimension list. Falling back to ID 0.");
dimensionId = 0; // 找不到时,回退到第一个维度,避免数组越界
}
// 设置摄像机位置到当前聚焦维度的位置 (移除 Program.Instance 的 null 检查)
_camera.transform.position = Program.Instance.FocusedDimension.cameraPosition;
// 设置摄像机位置到当前聚焦维度的位置
if (Program.Instance.FocusedDimension != null)
{
CurrentCamera.transform.position = Program.Instance.FocusedDimension.cameraPosition;
Debug.Log($"[CameraControl] Camera position set to focused dimension's camera position: {CurrentCamera.transform.position}");
}
else
{
Debug.LogWarning("[CameraControl] Focused dimension is null when trying to set camera position. Camera position not explicitly updated based on a dimension.");
}
}
/// <summary>
@ -116,7 +130,7 @@ namespace CameraControl
/// </summary>
private void Init()
{
// 移除 Program.Instance 的 null 检查
Debug.Log("[CameraControl] Init() called (no parameters). Calling Init(Program.Instance.FocusedDimension).");
Init(Program.Instance.FocusedDimension);
}
@ -126,41 +140,44 @@ namespace CameraControl
/// </summary>
public void NextDimension()
{
// 修改点 1: 统一 Unity 对象 `null` 检查
if (!_camera)
Debug.Log("[CameraControl] NextDimension called.");
if (!CurrentCamera)
{
Debug.LogWarning("摄像机引用为空,无法切换维度。");
Debug.LogWarning("[CameraControl] Camera reference is null, cannot switch dimensions.");
return;
}
if (dimensionList == null || dimensionList.Length == 0)
{
Debug.LogWarning("维度列表为空。");
Debug.LogWarning("[CameraControl] Dimension list is empty or uninitialized, cannot switch dimensions.");
return;
}
// 1. 保存当前摄像机的实际位置到当前维度 (移除 Program.Instance 的 null 检查)
// 1. 保存当前摄像机的实际位置到当前维度
if (dimensionId >= 0 && dimensionId < dimensionList.Length)
{
var currentDimension = Program.Instance.GetDimension(dimensionList[dimensionId]);
if (currentDimension != null) // currentDimension 可能是普通 C# 对象,所以此处 null 检查是必要的
if (currentDimension != null)
{
currentDimension.cameraPosition = _camera.transform.position;
currentDimension.cameraPosition = CurrentCamera.transform.position;
Debug.Log($"[CameraControl] Saved current camera position {CurrentCamera.transform.position} to dimension '{dimensionList[dimensionId]}'.");
}
else
{
Debug.LogWarning($"无法找到 ID 为 {dimensionId} ({dimensionList[dimensionId]}) 的维度对象,无法保存摄像机位置。");
Debug.LogWarning($"[CameraControl] Could not find Dimension object for ID {dimensionId} ({dimensionList[dimensionId]}), cannot save camera position.");
}
}
else
{
Debug.LogWarning($"当前维度ID ({dimensionId}) 超出范围,无法保存摄像机位置 (维度列表长度: {dimensionList.Length})");
Debug.LogWarning($"[CameraControl] Current dimension ID ({dimensionId}) is out of range, cannot save camera position (Dimension list length: {dimensionList.Length}).");
}
// 2. 更新 dimensionId形成循环切换
// 2. 更新 dimensionId, 形成循环切换
dimensionId = (dimensionId + 1) % dimensionList.Length;
Debug.Log($"[CameraControl] Updated dimensionId to {dimensionId}.");
// 3. 更新聚焦维度,摄像机位置的更新将由事件触发的 Init 方法处理
SetCameraPositionForDimension(dimensionId);
SetFocusedDimensionById(dimensionId);
}
/// <summary>
@ -168,21 +185,22 @@ namespace CameraControl
/// 摄像机位置的实际更新将通过 Program.Instance.OnFocusedDimensionChanged 事件在 Init(Dimension obj) 方法中处理。
/// </summary>
/// <param name="id">要设置的维度ID。</param>
private void SetCameraPositionForDimension(int id)
private void SetFocusedDimensionById(int id)
{
// 修改点 1: 统一 Unity 对象 `null` 检查
if (!_camera)
Debug.Log($"[CameraControl] SetFocusedDimensionById called with ID: {id}.");
if (!CurrentCamera)
{
Debug.LogWarning("摄像机引用为空,无法设置摄像机位置。");
Debug.LogWarning("[CameraControl] Camera reference is null, cannot set camera position.");
return;
}
if (dimensionList == null || id < 0 || id >= dimensionList.Length)
{
Debug.LogWarning($"维度ID {id} 超出范围或维度列表为空。");
Debug.LogWarning($"[CameraControl] Dimension ID {id} is out of range or dimension list is empty, cannot set focused dimension.");
return;
}
// 移除 Program.Instance 的 null 检查
Program.Instance.SetFocusedDimension(dimensionList[id]);
Debug.Log($"[CameraControl] Program.Instance.SetFocusedDimension called for '{dimensionList[id]}'.");
}
/// <summary>
@ -190,20 +208,19 @@ namespace CameraControl
/// </summary>
public void Tick()
{
// 修改点 1: 统一 Unity 对象 `null` 检查
if (!_camera) return; // 确保相机存在
if (!CurrentCamera) return; // 确保相机存在
// 当没有拖拽且存在聚焦实体时,摄像机跟随聚焦实体 (移除 Program.Instance 的 null 检查)
// 当没有拖拽且存在聚焦实体时,摄像机跟随聚焦实体
if (!_isDragging && Program.Instance.FocusedEntity)
{
var targetPosition = new Vector3(
Program.Instance.FocusedEntity.Position.x,
Program.Instance.FocusedEntity.Position.y,
_camera.transform.position.z);
CurrentCamera.transform.position.z);
// 使用 deltaTime 进行平滑的摄像机跟随
_camera.transform.position = Vector3.Lerp(
_camera.transform.position,
CurrentCamera.transform.position = Vector3.Lerp(
CurrentCamera.transform.position,
targetPosition,
Time.deltaTime * _focusLerpSpeed);
}
@ -211,6 +228,7 @@ namespace CameraControl
// 按下 Tab 键时切换到下一个维度
if (Input.GetKeyDown(KeyCode.Tab))
{
Debug.Log("[CameraControl] Tab key pressed. Calling NextDimension().");
NextDimension();
}
}
@ -220,8 +238,7 @@ namespace CameraControl
/// </summary>
public void TickUI()
{
// 修改点 1: 统一 Unity 对象 `null` 检查
if (!_camera) // 确保相机存在
if (!CurrentCamera) // 确保相机存在
return;
HandleMiddleMouseDrag(); // 处理鼠标中键拖拽
@ -234,11 +251,15 @@ namespace CameraControl
/// <param name="position">要设置的摄像机位置。</param>
public void SetPosition(Vector3 position)
{
// 修改点 1: 统一 Unity 对象 `null` 检查
if (_camera) // if (!_camera) 比 if (_camera == null) 更优雅
_camera.transform.position = position;
if (CurrentCamera)
{
CurrentCamera.transform.position = position;
Debug.Log($"[CameraControl] Camera position explicitly set to: {position}.");
}
else
Debug.LogWarning("摄像机引用为空,无法设置位置。");
{
Debug.LogWarning("[CameraControl] Camera reference is null, cannot set position.");
}
}
/// <summary>
@ -246,17 +267,18 @@ namespace CameraControl
/// </summary>
private void HandleMiddleMouseDrag()
{
// 修改点 1: 统一 Unity 对象 `null` 检查
if (!_camera) return; // 确保相机存在
if (!CurrentCamera) return; // 确保相机存在
// 开始拖拽:检测鼠标中键按下
if (Input.GetMouseButtonDown(2)) // 鼠标中键
{
_dragOrigin = _camera.ScreenToWorldPoint(Input.mousePosition);
_dragOrigin = CurrentCamera.ScreenToWorldPoint(Input.mousePosition);
_isDragging = true;
// 如果有聚焦实体,则在开始拖拽时取消聚焦,暂停跟随 (移除 Program.Instance 的 null 检查)
Debug.Log($"[CameraControl] Mouse middle button down. Starting drag from world point: {_dragOrigin}.");
// 如果有聚焦实体,则在开始拖拽时取消聚焦,暂停跟随
if (Program.Instance.FocusedEntity)
{
Debug.Log("[CameraControl] Focused entity detected. Setting FocusedEntity to null to pause follow.");
Program.Instance.SetFocusedEntity(null);
}
}
@ -264,15 +286,15 @@ namespace CameraControl
// 拖拽中:根据鼠标移动更新摄像机位置
if (Input.GetMouseButton(2) && _isDragging)
{
var difference = _dragOrigin - _camera.ScreenToWorldPoint(Input.mousePosition);
_camera.transform.position += difference;
var difference = _dragOrigin - CurrentCamera.ScreenToWorldPoint(Input.mousePosition);
CurrentCamera.transform.position += difference;
}
// 结束拖拽:检测鼠标中键抬起
if (Input.GetMouseButtonUp(2))
{
_isDragging = false;
// 拖拽结束后,当前代码不会自动重新聚焦实体。如果需要此行为,应在此处添加逻辑。
Debug.Log("[CameraControl] Mouse middle button up. Dragging ended.");
}
}
@ -281,23 +303,15 @@ namespace CameraControl
/// </summary>
private void HandleMouseZoom()
{
// 修改点 1: 统一 Unity 对象 `null` 检查
if (!_camera) return; // 确保相机存在
if (!CurrentCamera) return; // 确保相机存在
var scroll = Input.GetAxis("Mouse ScrollWheel");
if (scroll == 0) return; // 没有滚轮滚动,则返回
// 根据滚轮输入调整正交摄像机的尺寸,并限制在最小和最大缩放之间
var newSize = _camera.orthographicSize - scroll * _zoomSpeed;
_camera.orthographicSize = Mathf.Clamp(newSize, _minZoom, _maxZoom);
}
/// <summary>
/// MonoSingleton 的 OnStart 方法,可在单例首次创建时添加额外初始化逻辑。
/// </summary>
protected override void OnStart()
{
// 当前为空,是合理的。
var newSize = CurrentCamera.orthographicSize - scroll * _zoomSpeed;
CurrentCamera.orthographicSize = Mathf.Clamp(newSize, _minZoom, _maxZoom);
// Debug.Log($"[CameraControl] Mouse scroll wheel. New orthographicSize: {CurrentCamera.orthographicSize}"); // Too frequent for general logging
}
}
}

View File

@ -195,6 +195,8 @@ namespace Entity
protected virtual void InitWeaponAnimator()
{
if (!weaponAnimator)
return;
for (var i = 0; i < weaponAnimator.transform.childCount; i++)
{
Destroy(weaponAnimator.transform.GetChild(i).gameObject);
@ -527,8 +529,6 @@ namespace Entity
// 暂时设定为:如果没有武器,则不进行攻击
if (currentWeapon == null)
{
// 可以在这里添加一个默认的徒手攻击逻辑,或者播放一个“不能攻击”的提示
Debug.Log($"{name} 没有装备武器,无法攻击。");
return;
}

View File

@ -49,13 +49,6 @@ namespace Item
$"ItemResource: Failed to load sprite for texture '{texture}' for item '{def.defName}'.");
}
}
else
{
// 仅当 textures 为 null 时警告,因为它可能是预期的数据状态(无图标)
Debug.LogWarning(
$"ItemResource: ItemDef '{def.defName}' has a null textures array. No icons will be loaded.");
}
// 逻辑修改Icon 属性现在是 IReadOnlyList<Sprite> 类型,确保外部只读访问
Icon = sprites.AsReadOnly(); // 使用 AsReadOnly() 获得一个只读包装器
}

View File

@ -14,14 +14,21 @@ namespace Logging
public string Message;
public string StackTrace;
public override string ToString() =>
$"[{Timestamp:HH:mm:ss}] [{Type}] {Message}" +
(Type == LogType.Exception ? $"\n{StackTrace}" : "");
public override string ToString()
{
// 逻辑修改扩展条件使LogType.Error和LogType.Assert也能显示堆栈信息
if (Type == LogType.Exception || Type == LogType.Error || Type == LogType.Assert)
{
return $"[{Timestamp:HH:mm:ss}] [{Type}] {Message}\n{StackTrace}";
}
return $"[{Timestamp:HH:mm:ss}] [{Type}] {Message}";
}
}
private static readonly Queue<LogEntry> _logs = new Queue<LogEntry>();
private static readonly object _lock = new object(); // 线程锁
private static int _maxLogs = 1000; // 默认容量
private static bool _isInitialized = false; // 逻辑修改:添加一个私有标志来跟踪是否已初始化
// 最大日志容量属性
public static int MaxLogs
@ -35,10 +42,40 @@ namespace Logging
}
}
// 逻辑修改:添加 [RuntimeInitializeOnLoadMethod] 特性,确保 Init() 方法在 Unity 启动时自动执行
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
public static void Init()
{
Application.logMessageReceivedThreaded += HandleLog;
// 确保线程安全地检查和设置初始化状态
lock (_lock)
{
if (_isInitialized)
{
Debug.LogWarning("[LogCapturer] Init() called multiple times. LogCapturer is already initialized.");
return;
}
Application.logMessageReceivedThreaded += HandleLog;
_isInitialized = true;
}
}
// 逻辑修改:添加一个 Shutdown 方法来解除事件注册和清理
public static void Shutdown()
{
// 确保线程安全地检查和解除注册
lock (_lock)
{
if (!_isInitialized)
{
Debug.LogWarning("[LogCapturer] Shutdown() called but LogCapturer was not initialized.");
return;
}
Application.logMessageReceivedThreaded -= HandleLog;
_isInitialized = false;
_logs.Clear(); // 逻辑修改:在关闭时清空所有捕获的日志,确保下次启用时是全新状态。
}
}
// 日志处理回调
@ -88,4 +125,4 @@ namespace Logging
}
}
}
}
}

View File

@ -9,7 +9,7 @@ namespace Logging
public void Init()
{
Logging.UnityLogger.Init();
LogCapturer.Init();
// LogCapturer.Init();
}
public void Clear()
{

View File

@ -352,7 +352,6 @@ namespace Managers
// 如果结果为空,则返回 null。
if (result.Count == 0)
{
Debug.LogWarning($"查询失败:未找到定义类型 '{defineType}'");
return null;
}

View File

@ -50,21 +50,10 @@ namespace Map
var rootObj = new GameObject($"_Entities_{id}");
rootObj.transform.SetParent(this.transform); // 将其作为Dimension对象的子对象
DimensionRoot = rootObj.transform;
// 3. 注册此维度到 Program
if (Program.Instance != null) // 检查单例是否仍然存在
{
Program.Instance.RegisterDimension(this);
}
else
{
Debug.LogError(
"[Dimension] Program.Instance is null during Dimension Awake. Cannot register dimension.", this);
}
Program.Instance.RegisterDimension(this);
// 5. 处理 defaultOpen 逻辑设置Program的焦点维度
// 确保在自身注册到 Program 之后再设置焦点,这样 Program 内部才能找到它
if (defaultOpen && Program.Instance != null)
if (defaultOpen)
{
Program.Instance.SetFocusedDimension(_dimensionId);
}

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()
{