(client) feat:添加游玩时UI相关贴图,添加3d模型场景按钮,添加多级调色进度条,添加SVG图片包,添加事件定义以及管理器,添加音频管理器,定义部分怪物,添加通信协议定义;fix:修复维度切换错误,修复LogUI显示不正确 (#55)
Co-authored-by: m0_75251201 <m0_75251201@noreply.gitcode.com> Reviewed-on: Roguelite-Game-Developing-Team/Gen_Hack-and-Slash-Roguelite#55
This commit is contained in:
16
Client/Assets/Scripts/UI/AttackModeUI.cs
Normal file
16
Client/Assets/Scripts/UI/AttackModeUI.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UI
|
||||
{
|
||||
public class AttackModeUI:MonoBehaviour
|
||||
{
|
||||
public UIImageAnimator icon;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
icon.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
3
Client/Assets/Scripts/UI/AttackModeUI.cs.meta
Normal file
3
Client/Assets/Scripts/UI/AttackModeUI.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2d81179f82c542edb275daabff5bfe12
|
||||
timeCreated: 1756781456
|
@ -7,10 +7,63 @@ namespace UI
|
||||
{
|
||||
[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
20
Client/Assets/Scripts/UI/BuffIconListUI.cs
Normal file
20
Client/Assets/Scripts/UI/BuffIconListUI.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using Prefab;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UI
|
||||
{
|
||||
public class BuffIconListUI: MonoBehaviour
|
||||
{
|
||||
public BuffIconUI prefab;
|
||||
public Transform container;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
foreach (Transform child in container)
|
||||
{
|
||||
Destroy(child.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
3
Client/Assets/Scripts/UI/BuffIconListUI.cs.meta
Normal file
3
Client/Assets/Scripts/UI/BuffIconListUI.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f6f033d6fe84e769e101fb355ef3ffa
|
||||
timeCreated: 1756780958
|
142
Client/Assets/Scripts/UI/Button3D.cs
Normal file
142
Client/Assets/Scripts/UI/Button3D.cs
Normal file
@ -0,0 +1,142 @@
|
||||
|
||||
using System.Linq; // 用于可能的LINQ操作,但在此版本中可能不直接使用
|
||||
using TMPro; // 用于 TextMeshPro 组件
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.Serialization; // 用于 UnityEvent
|
||||
|
||||
namespace UI
|
||||
{
|
||||
public class Button3D : MonoBehaviour
|
||||
{
|
||||
[Tooltip("当按钮被点击时触发的UnityEvent。可以在Inspector中配置.")]
|
||||
public UnityEvent OnClick;
|
||||
|
||||
[FormerlySerializedAs("renderer")] public MeshRenderer meshRenderer; // 请确保在Inspector中拖拽赋值
|
||||
public Material outlineMaterial; // 请确保在Inspector中拖拽赋值
|
||||
|
||||
public TMP_Text text; // 文本组件
|
||||
|
||||
private Material[] _originalMaterials; // 用于存储按钮的原始材质,以便正确添加和移除描边
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (meshRenderer == null)
|
||||
{
|
||||
Debug.LogError("Button3D: MeshRenderer is not assigned. Please assign it in the Inspector.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (outlineMaterial == null)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
"Button3D: Outline Material is not assigned. Outline effect will not work. Please assign it in the Inspector.",
|
||||
this);
|
||||
}
|
||||
|
||||
if (text == null)
|
||||
{
|
||||
Debug.LogWarning("Button3D: TMP_Text is not assigned. Text features will not work. Please assign it in the Inspector.", this);
|
||||
// 这里不return,允许按钮无文本运行
|
||||
}
|
||||
|
||||
|
||||
// 存储原始材质数组,以便在添加和移除描边时正确操作
|
||||
// .ToArray() 是为了确保我们拿到的是拷贝,而不是引用,防止外部意外修改
|
||||
_originalMaterials = meshRenderer.materials.ToArray();
|
||||
|
||||
// 确保初始化时没有边框材质
|
||||
// Important: 调用RemoveOutlineMaterialInternal()可能会清除_originalMaterials,所以要确保先保存
|
||||
RemoveOutlineMaterialInternal(); // 这一步会根据_originalMaterials重新设置renderer.materials
|
||||
|
||||
// 初始时隐藏文本
|
||||
if (text != null)
|
||||
{
|
||||
text.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// 文本始终朝向摄像机
|
||||
if (text != null && text.IsActive() && Camera.main != null)
|
||||
{
|
||||
text.transform.LookAt(text.transform.position + Camera.main.transform.rotation * Vector3.forward,
|
||||
Camera.main.transform.rotation * Vector3.up);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMouseEnter()
|
||||
{
|
||||
if (meshRenderer == null) return;
|
||||
|
||||
// 鼠标进入时显示文本
|
||||
if (text != null)
|
||||
{
|
||||
text.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
// 如果没有描边材质,或者渲染器已经有描边材质,就没必要再添加了
|
||||
// 检查当前材质数组长度是否已经大于等于_originalMaterials的长度+1
|
||||
// 这样可以避免重复添加
|
||||
if (outlineMaterial == null || meshRenderer.materials.Length > _originalMaterials.Length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果当前材质数组和_originalMaterials不一致,说明可能被其他逻辑修改了,
|
||||
// 稳妥起见,最好先恢复到_originalMaterials
|
||||
if (!meshRenderer.materials.SequenceEqual(_originalMaterials))
|
||||
{
|
||||
//Debug.LogWarning("Button3D: Renderer materials were unexpectedly modified before OnMouseEnter. Restoring original materials.");
|
||||
// 通常不应该发生,除非有其他脚本在修改
|
||||
meshRenderer.materials = _originalMaterials;
|
||||
}
|
||||
|
||||
// 创建一个新数组,包含原始材质和描边材质
|
||||
var newMaterials = new Material[_originalMaterials.Length + 1];
|
||||
System.Array.Copy(_originalMaterials, newMaterials, _originalMaterials.Length);
|
||||
newMaterials[_originalMaterials.Length] = outlineMaterial;
|
||||
meshRenderer.materials = newMaterials;
|
||||
}
|
||||
|
||||
private void OnMouseExit()
|
||||
{
|
||||
if (meshRenderer == null) return;
|
||||
|
||||
// 鼠标离开时隐藏文本
|
||||
if (text != null)
|
||||
{
|
||||
text.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
// 调用内部方法移除边框材质
|
||||
RemoveOutlineMaterialInternal();
|
||||
}
|
||||
|
||||
private void OnMouseDown()
|
||||
{
|
||||
// 触发UnityEvent
|
||||
OnClick.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除渲染器上的边框材质(如果存在),恢复到按钮的原始材质。
|
||||
/// </summary>
|
||||
private void RemoveOutlineMaterialInternal()
|
||||
{
|
||||
if (meshRenderer == null) return;
|
||||
// 即使_originalMaterials == null(因为Awake中的LogError),也可以安全赋值空数组
|
||||
// 但如果_originalMaterials确实是null,说明Awake有问题,后续操作也可能继续出错
|
||||
if (_originalMaterials == null)
|
||||
{
|
||||
Debug.LogError("Button3D: _originalMaterials is null. Cannot remove outline. Check Awake method.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
// 直接将渲染器材质恢复为_originalMaterials的副本
|
||||
meshRenderer.materials = _originalMaterials.ToArray(); // 使用ToArray()确保赋值一个副本,避免_originalMaterials被意外修改
|
||||
}
|
||||
}
|
||||
}
|
||||
|
2
Client/Assets/Scripts/UI/Button3D.cs.meta
Normal file
2
Client/Assets/Scripts/UI/Button3D.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d5e1709fa6d5c54f93ac068f067e20b
|
16
Client/Assets/Scripts/UI/CoinCountUI.cs
Normal file
16
Client/Assets/Scripts/UI/CoinCountUI.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UI
|
||||
{
|
||||
public class CoinCountUI:MonoBehaviour
|
||||
{
|
||||
public TMP_Text text;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
3
Client/Assets/Scripts/UI/CoinCountUI.cs.meta
Normal file
3
Client/Assets/Scripts/UI/CoinCountUI.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 85cfb4a924a04c6e9f6f7801207dbee3
|
||||
timeCreated: 1756776391
|
@ -5,7 +5,7 @@ using UnityEngine.SceneManagement;
|
||||
|
||||
namespace UI
|
||||
{
|
||||
public class DevMenuUI : UIBase
|
||||
public class DevMenuUI : FullScreenUI
|
||||
{
|
||||
public GameObject menuContent;
|
||||
|
||||
@ -77,9 +77,8 @@ namespace UI
|
||||
InitDefineButtons<EventDef>(
|
||||
"事件菜单",
|
||||
"未定义任何事件",
|
||||
// 假设 EventDef 也有 label 字段作为按钮文本
|
||||
def => def.label,
|
||||
eventDef =>
|
||||
def => def.label,
|
||||
eventDef =>
|
||||
{
|
||||
// TODO: 在这里实现事件触发逻辑
|
||||
Debug.Log($"触发事件: {eventDef.label}");
|
||||
|
@ -1,275 +1,15 @@
|
||||
using Base;
|
||||
using Entity;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
// 确保 Character 类在此命名空间下
|
||||
|
||||
|
||||
namespace UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 负责管理和显示角色的装备用户界面。
|
||||
/// 该组件会监听当前关注实体的变化,并根据所关注角色的库存数据动态更新装备槽位的显示,
|
||||
/// 同时采用对象池技术高效管理 ItemUI 实例的创建和复用。
|
||||
/// 除了显示,现在还支持通过滚轮选择物品,并同步更新焦点角色的 CurrentSelected 字段。
|
||||
/// </summary>
|
||||
public class EquipmentUI : MonoBehaviour, ITick
|
||||
|
||||
public class EquipmentUI : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("所有 ItemUI 实例的父级 GameObject,用于布局。")]
|
||||
private GameObject uiParent;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("用于实例化装备槽位的 ItemUI 预制件。")]
|
||||
private ItemUI itemUIPrefab;
|
||||
|
||||
/// <summary>
|
||||
/// 当前界面所关联和关注的角色实体。
|
||||
/// </summary>
|
||||
private Character focusedEntity;
|
||||
|
||||
/// <summary>
|
||||
/// ItemUI 实例的对象池,用于高效管理和复用 ItemUI。
|
||||
/// </summary>
|
||||
private List<ItemUI> itemUIPool = new();
|
||||
|
||||
/// <summary>
|
||||
/// MonoBehaviour 的 Start 生命周期方法。
|
||||
/// 在此方法中,注册当游戏主要程序中关注的实体发生变化时,调用 <see cref="UpdateFocusedEntity"/> 方法进行更新。
|
||||
/// </summary>
|
||||
private void Start()
|
||||
{
|
||||
Program.Instance.OnFocusedEntityChanged += UpdateFocusedEntity;
|
||||
uiParent.SetActive(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MonoBehaviour 的 OnDestroy 生命周期方法。
|
||||
/// 在此方法中,取消注册所有已订阅的事件监听器,并清理对象池中创建的所有 ItemUI 实例,
|
||||
/// 以防止内存泄漏和不必要的引用。
|
||||
/// </summary>
|
||||
private void OnDestroy()
|
||||
{
|
||||
Program.Instance.OnFocusedEntityChanged -= UpdateFocusedEntity;
|
||||
|
||||
// 如果当前有关注的角色,取消注册其库存改变事件。
|
||||
// 确保 focusedEntity 不为 null 且 Inventory 不为 null。
|
||||
if (focusedEntity != null && focusedEntity.Inventory != null)
|
||||
{
|
||||
focusedEntity.Inventory.OnInventoryChanged -= UpdateUI;
|
||||
}
|
||||
|
||||
// 销毁对象池中所有 ItemUI 的 GameObject。
|
||||
foreach (var itemUI in itemUIPool)
|
||||
{
|
||||
if (itemUI != null && itemUI.gameObject != null)
|
||||
{
|
||||
Destroy(itemUI.gameObject);
|
||||
}
|
||||
}
|
||||
itemUIPool.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当游戏程序中关注的实体发生变化时调用此方法。
|
||||
/// 该方法会更新当前 EquipmentUI 所关联的角色,并相应地注册或取消注册库存改变事件。
|
||||
/// </summary>
|
||||
/// <param name="entity">新的关注实体,可能为 null 或非 Character 类型。</param>
|
||||
private void UpdateFocusedEntity(Entity.Entity entity)
|
||||
{
|
||||
// 如果之前有关注的角色,先取消注册其库存改变事件。
|
||||
// 确保 focusedEntity 不为 null 且 Inventory 不为 null。
|
||||
if (focusedEntity != null && focusedEntity.Inventory != null)
|
||||
{
|
||||
focusedEntity.Inventory.OnInventoryChanged -= UpdateUI;
|
||||
}
|
||||
|
||||
// 尝试将新的实体转换为角色类型。
|
||||
Character newCharacter = entity as Character;
|
||||
if (newCharacter != null)
|
||||
{
|
||||
focusedEntity = newCharacter;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果传入的 entity 不是 Character 类型,或者为 null,则清除当前的 focusedEntity。
|
||||
focusedEntity = null;
|
||||
}
|
||||
|
||||
// 如果现在有关注的角色,注册其库存改变事件。
|
||||
if (focusedEntity != null)
|
||||
{
|
||||
focusedEntity.Inventory.OnInventoryChanged += UpdateUI;
|
||||
}
|
||||
|
||||
// 立即更新UI以反映新的关注实体(或没有关注实体)的状态。
|
||||
UpdateUI();
|
||||
|
||||
// 在更新UI后,确保UI的选中状态与角色当前选中字段同步
|
||||
// 只有当有焦点角色且库存不为空时才更新选中状态
|
||||
if (focusedEntity != null && focusedEntity.Inventory != null && focusedEntity.Inventory.Capacity > 0)
|
||||
{
|
||||
// 确保 CurrentSelected 在有效范围内,否则重置为0
|
||||
if (focusedEntity.CurrentSelected < 0 || focusedEntity.CurrentSelected >= focusedEntity.Inventory.Capacity)
|
||||
{
|
||||
focusedEntity.CurrentSelected = 0;
|
||||
}
|
||||
UpdateSelectionUI(focusedEntity.CurrentSelected);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果没有焦点实体或库存为空,则清空所有选中状态
|
||||
UpdateSelectionUI(-1); // 传入一个无效索引以取消所有选中
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据当前关注角色的库存数据更新装备UI的显示。
|
||||
/// 该方法通过对象池机制高效地管理 ItemUI 实例的创建、复用和禁用。
|
||||
/// </summary>
|
||||
private void UpdateUI()
|
||||
{
|
||||
// 如果没有关注的角色或其库存,则禁用所有 ItemUI。
|
||||
if (focusedEntity == null || focusedEntity.Inventory == null)
|
||||
{
|
||||
foreach (var itemUI in itemUIPool)
|
||||
{
|
||||
if (itemUI != null && itemUI.gameObject != null)
|
||||
{
|
||||
itemUI.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
uiParent.SetActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查用于创建物品UI的预制件是否已在 Inspector 中赋值。
|
||||
if (itemUIPrefab == null)
|
||||
{
|
||||
Debug.LogError("ItemUIPrefab 未在 EquipmentUI 中指定。无法创建物品用户界面。", this);
|
||||
foreach (var itemUI in itemUIPool) itemUI.gameObject.SetActive(false);
|
||||
uiParent.SetActive(false); // 确保父级也被禁用
|
||||
return;
|
||||
}
|
||||
|
||||
int requiredUIs = focusedEntity.Inventory.Capacity;
|
||||
int currentUIPoolSize = itemUIPool.Count; // 当前对象池中 ItemUI 实例的总数。
|
||||
|
||||
// 遍历所有必要的物品槽位,复用对象池中的 ItemUI,或在不足时创建新的 ItemUI。
|
||||
for (int i = 0; i < requiredUIs; i++)
|
||||
{
|
||||
ItemUI itemUI;
|
||||
if (i < currentUIPoolSize)
|
||||
{
|
||||
itemUI = itemUIPool[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
// 使用 Instantiate(GameObject, Transform) 以确保父级设置正确且避免转换问题。
|
||||
var itemObj = Instantiate(itemUIPrefab.gameObject, uiParent.transform);
|
||||
itemUI = itemObj.GetComponent<ItemUI>();
|
||||
itemUIPool.Add(itemUI);
|
||||
currentUIPoolSize++; // 更新池的大小计数。
|
||||
}
|
||||
|
||||
// 确保 ItemUI GameObject 处于激活状态,并使用当前物品槽位的数据进行初始化。
|
||||
itemUI.gameObject.SetActive(true);
|
||||
itemUI.Init(focusedEntity.Inventory.GetSlot(i), i);
|
||||
// 移除此处 itemUI.Select = false; 选中状态将由 UpdateSelectionUI 统一管理
|
||||
}
|
||||
|
||||
// 如果库存槽位数量减少,禁用对象池中多余的 ItemUI 实例。
|
||||
for (int i = requiredUIs; i < currentUIPoolSize; i++)
|
||||
{
|
||||
if (itemUIPool[i] != null && itemUIPool[i].gameObject != null)
|
||||
{
|
||||
itemUIPool[i].gameObject.SetActive(false);
|
||||
itemUIPool[i].Select = false; // 禁用时也确保清除选中状态
|
||||
}
|
||||
}
|
||||
uiParent.SetActive(true);
|
||||
|
||||
// 首次更新UI时,或者当Inventory改变时,需要确保 CurrentSelected 的UI状态是正确的
|
||||
// 但如果 UpdateFocusedEntity 已经处理了,这里可以省略,或者确保只在必要时调用
|
||||
// 考虑到 UpdateUI 也会被 Inventory.OnInventoryChanged 调用,这里再次确保同步是合理的。
|
||||
if (focusedEntity != null && focusedEntity.Inventory != null && focusedEntity.Inventory.Capacity > 0)
|
||||
{
|
||||
UpdateSelectionUI(focusedEntity.CurrentSelected);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateSelectionUI(-1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当当前选中物品改变时,更新所有 ItemUI 的选中状态。
|
||||
/// </summary>
|
||||
/// <param name="selectedItemIndex">当前选中的物品索引。传入 -1 将取消所有 ItemUI 的选中状态。</param>
|
||||
private void UpdateSelectionUI(int selectedItemIndex)
|
||||
{
|
||||
// 如果对象池为空,则无需更新
|
||||
if (itemUIPool == null || itemUIPool.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < itemUIPool.Count; i++)
|
||||
{
|
||||
ItemUI itemUI = itemUIPool[i];
|
||||
if (itemUI != null && itemUI.gameObject != null)
|
||||
{
|
||||
// 只有在 ItemUI 激活状态下才设置其选中状态,避免对禁用UI的操作
|
||||
// 或者如果传入-1,即使激活也全部设置为false
|
||||
if (itemUI.gameObject.activeSelf || selectedItemIndex == -1) // 确保当取消所有选中时,循环到所有激活的,甚至当前禁用的ItemUI
|
||||
{
|
||||
itemUI.Select = (i == selectedItemIndex && itemUI.gameObject.activeSelf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 每帧调用的更新方法,用于处理滚轮输入以选择物品。
|
||||
/// </summary>
|
||||
public void Tick()
|
||||
{
|
||||
// 如果没有焦点实体、没有库存或库存为空,不进行选择操作
|
||||
if (focusedEntity == null || focusedEntity.Inventory == null || focusedEntity.Inventory.Capacity == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float scrollInput = Input.GetAxis("Mouse ScrollWheel");
|
||||
|
||||
if (scrollInput != 0) // 检测到滚轮输入
|
||||
{
|
||||
int currentSelection = focusedEntity.CurrentSelected;
|
||||
int inventoryCapacity = focusedEntity.Inventory.Capacity;
|
||||
|
||||
if (scrollInput > 0) // 滚轮向上,选择前一个
|
||||
{
|
||||
currentSelection--;
|
||||
if (currentSelection < 0)
|
||||
{
|
||||
currentSelection = inventoryCapacity - 1; // 循环到最后一个
|
||||
}
|
||||
}
|
||||
else // 滚轮向下,选择后一个
|
||||
{
|
||||
currentSelection++;
|
||||
if (currentSelection >= inventoryCapacity)
|
||||
{
|
||||
currentSelection = 0; // 循环到第一个
|
||||
}
|
||||
}
|
||||
|
||||
// 如果选择发生变化,则更新焦点实体和UI
|
||||
if (focusedEntity.CurrentSelected != currentSelection)
|
||||
{
|
||||
focusedEntity.CurrentSelected = currentSelection;
|
||||
UpdateSelectionUI(currentSelection); // 更新UI选中状态
|
||||
}
|
||||
}
|
||||
}
|
||||
public ItemUI currentUse;
|
||||
public ItemUI two;
|
||||
public ItemUI three;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
21
Client/Assets/Scripts/UI/FullScreenUI.cs
Normal file
21
Client/Assets/Scripts/UI/FullScreenUI.cs
Normal 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) || Input.GetKeyDown(actionButton))
|
||||
UIInputControl.Instance.Hide(this);
|
||||
}
|
||||
}
|
||||
}
|
2
Client/Assets/Scripts/UI/FullScreenUI.cs.meta
Normal file
2
Client/Assets/Scripts/UI/FullScreenUI.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 92b2dc3c0fc0475596413b883c51e9b2
|
@ -1,91 +1,34 @@
|
||||
using Base;
|
||||
|
||||
using System;
|
||||
using TMPro;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
|
||||
namespace UI
|
||||
{
|
||||
public class ItemUI : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerClickHandler, ITick
|
||||
public class ItemUI:MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Image textureUI;
|
||||
[SerializeField] private TMP_Text countUI;
|
||||
[SerializeField] private TMP_Text nameUI;
|
||||
[SerializeField] private GameObject selectedOutline;
|
||||
public Entity.InventorySlot inventorySlot;
|
||||
public UIImageAnimator icon;
|
||||
|
||||
private Entity.InventorySlot _item;
|
||||
private float timer = 0;
|
||||
private float switchTime = 0;
|
||||
private int texturePtr = 0;
|
||||
|
||||
public event Action OnPlayerSelect;
|
||||
|
||||
public bool Select
|
||||
private void Start()
|
||||
{
|
||||
get => selectedOutline.activeSelf;
|
||||
set => selectedOutline.SetActive(value);
|
||||
icon.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
public int SlotIndex { get; private set; } = -1;
|
||||
|
||||
public void Init(Entity.InventorySlot item, int index)
|
||||
public void SetDisplayItem(Entity.InventorySlot slot)
|
||||
{
|
||||
if (item == null)
|
||||
inventorySlot = slot;
|
||||
if (inventorySlot == null)
|
||||
{
|
||||
switchTime = -1;
|
||||
textureUI.gameObject.SetActive(false);
|
||||
countUI.text = "";
|
||||
nameUI.text = "";
|
||||
return;
|
||||
}
|
||||
|
||||
textureUI.gameObject.SetActive(true);
|
||||
_item = item;
|
||||
textureUI.sprite = item.Item.Icon[0];
|
||||
countUI.text = item.Quantity.ToString();
|
||||
nameUI.text = item.Item.Name;
|
||||
nameUI.gameObject.SetActive(false);
|
||||
Select = false;
|
||||
SlotIndex = index;
|
||||
if (item.Item.FPS > 0)
|
||||
{
|
||||
switchTime = 1f / item.Item.FPS;
|
||||
icon.gameObject.SetActive(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
switchTime = 0;
|
||||
icon.gameObject.SetActive(true);
|
||||
icon.SetSprites(inventorySlot.Item.Icon.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPointerEnter(PointerEventData eventData)
|
||||
{
|
||||
nameUI.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
public void OnPointerExit(PointerEventData eventData)
|
||||
{
|
||||
nameUI.gameObject.SetActive(false);
|
||||
}
|
||||
public void OnPointerClick(PointerEventData eventData)
|
||||
{
|
||||
OnPlayerSelect?.Invoke();
|
||||
}
|
||||
public void Tick()
|
||||
{
|
||||
if (switchTime > 0)
|
||||
{
|
||||
timer += Time.deltaTime;
|
||||
if (timer >= switchTime)
|
||||
{
|
||||
timer -= switchTime;
|
||||
texturePtr++;
|
||||
texturePtr %= _item.Item.Icon.Count;
|
||||
textureUI.sprite = _item.Item.Icon[texturePtr];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
using Prefab;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using Prefab; // 假设 TextPrefab 在 Prefab 命名空间下
|
||||
using TMPro; // 假设 TextPrefab 内部使用了 TextMeshPro
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace UI
|
||||
{
|
||||
public class LogUI : UIBase
|
||||
public class LogUI : FullScreenUI
|
||||
{
|
||||
public Transform contentPanel; // 日志内容容器
|
||||
public TextPrefab textPrefab; // 文本预制体引用
|
||||
@ -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(); // 清空列表引用
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,7 @@
|
||||
using System;
|
||||
using Base;
|
||||
using Map;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UI
|
||||
@ -6,7 +10,13 @@ namespace UI
|
||||
{
|
||||
[SerializeField] private BarUI focusedEntityHP;
|
||||
[SerializeField] private BarUI lastEntityHP;
|
||||
|
||||
[SerializeField] private BarUI BaseBuildingHP;
|
||||
[SerializeField] private MiniMap miniMap;
|
||||
[SerializeField] private EquipmentUI equipmentUI;
|
||||
[SerializeField] private CoinCountUI coinCountUI;
|
||||
[SerializeField] private BuffIconListUI focuseEntityBuffIconList;
|
||||
[SerializeField] private BuffIconListUI lastEntityBuffIconList;
|
||||
[SerializeField] private AttackModeUI attackMode;
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
@ -18,5 +28,52 @@ namespace UI
|
||||
}
|
||||
}
|
||||
|
||||
public void Show()
|
||||
{
|
||||
focusedEntityHP.gameObject.SetActive(true);
|
||||
lastEntityHP.gameObject.SetActive(true);
|
||||
BaseBuildingHP.gameObject.SetActive(true);
|
||||
miniMap.gameObject.SetActive(true);
|
||||
equipmentUI.gameObject.SetActive(true);
|
||||
coinCountUI.gameObject.SetActive(true);
|
||||
focuseEntityBuffIconList.gameObject.SetActive(true);
|
||||
lastEntityBuffIconList.gameObject.SetActive(true);
|
||||
attackMode.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
focusedEntityHP.gameObject.SetActive(false);
|
||||
lastEntityHP.gameObject.SetActive(false);
|
||||
BaseBuildingHP.gameObject.SetActive(false);
|
||||
miniMap.gameObject.SetActive(false);
|
||||
equipmentUI.gameObject.SetActive(false);
|
||||
coinCountUI.gameObject.SetActive(false);
|
||||
focuseEntityBuffIconList.gameObject.SetActive(false);
|
||||
lastEntityBuffIconList.gameObject.SetActive(false);
|
||||
attackMode.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
UIInputControl.Instance.OnWindowVisibilityChanged += UIChange;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
UIInputControl.Instance.OnWindowVisibilityChanged -= UIChange;
|
||||
}
|
||||
|
||||
private void UIChange(UIBase ui, bool open)
|
||||
{
|
||||
if (ui.exclusive && open)
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ using UnityEngine.UI;
|
||||
|
||||
namespace UI
|
||||
{
|
||||
public class SettingUI : UIBase
|
||||
public class SettingUI : FullScreenUI
|
||||
{
|
||||
[SerializeField] private Scrollbar globalVolume;
|
||||
[SerializeField] private Toggle developerMode;
|
||||
@ -47,6 +47,7 @@ namespace UI
|
||||
{
|
||||
windowResolution.value = 0;
|
||||
}
|
||||
windowMode.value = (int)currentSettings.currentWindowMode;
|
||||
}
|
||||
public void CancelSettings()
|
||||
{
|
||||
|
Reference in New Issue
Block a user