(client) feat:实现热重载,实现多维度,实现武器,实现掉落物,实现状态UI,实现攻击AI (#44)
Co-authored-by: zzdxxz <2079238449@qq.com> Co-committed-by: zzdxxz <2079238449@qq.com>
This commit is contained in:
16
Client/Assets/Scripts/UI/BarUI.cs
Normal file
16
Client/Assets/Scripts/UI/BarUI.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UI
|
||||
{
|
||||
public class BarUI:MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Image image;
|
||||
|
||||
public float Progress
|
||||
{
|
||||
get => image.fillAmount;
|
||||
set => image.fillAmount = value;
|
||||
}
|
||||
}
|
||||
}
|
3
Client/Assets/Scripts/UI/BarUI.cs.meta
Normal file
3
Client/Assets/Scripts/UI/BarUI.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a5026699f3a94f029628af90ccd8fa8d
|
||||
timeCreated: 1756183343
|
@ -1,8 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Data;
|
||||
using Base;
|
||||
using Data; // 确保 Data 命名空间包含 DefBase, CharacterDef, MonsterDef, BuildingDef, ItemDef, EventDef
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace UI
|
||||
{
|
||||
@ -22,96 +21,117 @@ namespace UI
|
||||
|
||||
private void Init()
|
||||
{
|
||||
InitReloadGameButton();
|
||||
InitEvent();
|
||||
InitCharacter();
|
||||
InitMonster();
|
||||
InitBuilding();
|
||||
InitItem();
|
||||
InitWeapon();
|
||||
}
|
||||
|
||||
private void InitReloadGameButton()
|
||||
{
|
||||
var button = InstantiatePrefab(buttonTemplate, menuContent.transform);
|
||||
button.Label = "热重载Def";
|
||||
button.AddListener(HotReload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通用的初始化定义按钮的方法。
|
||||
/// </summary>
|
||||
/// <typeparam name="TDef">定义的类型,必须继承自 Data.DefBase。</typeparam>
|
||||
/// <param name="titleLabel">菜单部分的标题。</param>
|
||||
/// <param name="noDefMessage">当没有定义时显示的提示信息。</param>
|
||||
/// <param name="buttonTextSelector">用于从 TDef 获取按钮显示文本的函数。</param>
|
||||
/// <param name="buttonAction">点击按钮时执行的动作,传入对应的 TDef 对象。</param>
|
||||
private void InitDefineButtons<TDef>(string titleLabel, string noDefMessage, System.Func<TDef, string> buttonTextSelector, System.Action<TDef> buttonAction) where TDef : Define
|
||||
{
|
||||
var title = InstantiatePrefab(textTemplate, menuContent.transform);
|
||||
title.Label = titleLabel;
|
||||
|
||||
var defList = Managers.DefineManager.Instance.QueryNamedDefinesByType<TDef>();
|
||||
if (defList == null || defList.Length == 0)
|
||||
{
|
||||
var noDefTitle = InstantiatePrefab(textTemplate, menuContent.transform);
|
||||
noDefTitle.Label = noDefMessage;
|
||||
noDefTitle.text.color = Color.red;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var def in defList)
|
||||
{
|
||||
var button = InstantiatePrefab(buttonTemplate, menuContent.transform);
|
||||
button.Label = buttonTextSelector(def);
|
||||
// 确保 lambda 捕获的是循环当前迭代的 def 变量,而不是循环变量本身
|
||||
var currentDef = def;
|
||||
button.AddListener(() => buttonAction(currentDef));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InitEvent()
|
||||
{
|
||||
var title = InstantiatePrefab(textTemplate, menuContent.transform);
|
||||
title.Label = "事件菜单";
|
||||
|
||||
title = InstantiatePrefab(textTemplate, menuContent.transform);
|
||||
title.Label = "未定义任何事件";
|
||||
title.text.color = Color.red;
|
||||
// for (int i = 0; i < 30; i++)
|
||||
// {
|
||||
// var button= InstantiatePrefab(buttonTemplate, menuContent.transform);
|
||||
// button.text.text = i.ToString();
|
||||
// }
|
||||
|
||||
// 假设存在 Data.EventDef 类型,且它继承自 Data.DefBase,并包含一个可作为标签的字段。
|
||||
// 如果事件触发逻辑不同于生成实体,需要在此处定义相应的回调。
|
||||
InitDefineButtons<EventDef>(
|
||||
"事件菜单",
|
||||
"未定义任何事件",
|
||||
// 假设 EventDef 也有 label 字段作为按钮文本
|
||||
def => def.label,
|
||||
eventDef =>
|
||||
{
|
||||
// TODO: 在这里实现事件触发逻辑
|
||||
Debug.Log($"触发事件: {eventDef.label}");
|
||||
// 示例: Managers.EventManager.Instance.TriggerEvent(eventDef.id);
|
||||
});
|
||||
}
|
||||
|
||||
private void InitCharacter()
|
||||
{
|
||||
var title = InstantiatePrefab(textTemplate, menuContent.transform);
|
||||
title.Label = "生成人物";
|
||||
|
||||
var defList = Managers.DefineManager.Instance.QueryNamedDefinesByType<Data.CharacterDef>();
|
||||
if (defList == null || defList.Length == 0)
|
||||
{
|
||||
title = InstantiatePrefab(textTemplate, menuContent.transform);
|
||||
title.Label = "未定义任何角色";
|
||||
title.text.color = Color.red;
|
||||
}
|
||||
else
|
||||
foreach (var def in defList)
|
||||
{
|
||||
var button = InstantiatePrefab(buttonTemplate, menuContent.transform);
|
||||
button.Label = def.label;
|
||||
var pawnDef = def;
|
||||
button.AddListener(() => GenerateEntityCallback(pawnDef));
|
||||
}
|
||||
|
||||
InitDefineButtons<CharacterDef>(
|
||||
"生成人物",
|
||||
"未定义任何角色",
|
||||
def => def.label,
|
||||
GenerateEntityCallback);
|
||||
}
|
||||
|
||||
private void InitMonster()
|
||||
{
|
||||
var title = InstantiatePrefab(textTemplate, menuContent.transform);
|
||||
title.Label = "生成怪物";
|
||||
|
||||
var defList = Managers.DefineManager.Instance.QueryNamedDefinesByType<Data.MonsterDef>();
|
||||
if (defList == null || defList.Length == 0)
|
||||
{
|
||||
title = InstantiatePrefab(textTemplate, menuContent.transform);
|
||||
title.Label = "未定义任何怪物";
|
||||
title.text.color = Color.red;
|
||||
}
|
||||
else
|
||||
foreach (var def in defList)
|
||||
{
|
||||
var button = InstantiatePrefab(buttonTemplate, menuContent.transform);
|
||||
button.Label = def.label;
|
||||
var pawnDef = def;
|
||||
button.AddListener(() => GenerateEntityCallback(pawnDef));
|
||||
}
|
||||
InitDefineButtons<MonsterDef>(
|
||||
"生成怪物",
|
||||
"未定义任何怪物",
|
||||
def => def.label,
|
||||
GenerateMonsterEntityCallback);
|
||||
}
|
||||
|
||||
private void InitBuilding()
|
||||
{
|
||||
var title = InstantiatePrefab(textTemplate, menuContent.transform);
|
||||
title.Label = "生成建筑";
|
||||
|
||||
var defList = Managers.DefineManager.Instance.QueryNamedDefinesByType<Data.BuildingDef>();
|
||||
if (defList == null || defList.Length == 0)
|
||||
{
|
||||
title = InstantiatePrefab(textTemplate, menuContent.transform);
|
||||
title.Label = "未定义任何建筑";
|
||||
title.text.color = Color.red;
|
||||
}
|
||||
else
|
||||
foreach (var def in defList)
|
||||
{
|
||||
var button = InstantiatePrefab(buttonTemplate, menuContent.transform);
|
||||
button.Label = def.label;
|
||||
var pawnDef = def;
|
||||
button.AddListener(() => GenerateBuildingCallback(pawnDef));
|
||||
}
|
||||
InitDefineButtons<BuildingDef>(
|
||||
"生成建筑",
|
||||
"未定义任何建筑",
|
||||
def => def.label,
|
||||
GenerateBuildingCallback);
|
||||
}
|
||||
|
||||
private void InitItem()
|
||||
{
|
||||
InitDefineButtons<ItemDef>(
|
||||
"生成掉落物",
|
||||
"未定义任何物品",
|
||||
def => def.label,
|
||||
GeneratePickupCallback);
|
||||
}
|
||||
private void InitWeapon()
|
||||
{
|
||||
InitDefineButtons<WeaponDef>(
|
||||
"生成武器",
|
||||
"未定义任何武器",
|
||||
def => def.label,
|
||||
GeneratePickupCallback);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 通用的实例化函数,返回实例化的预制件脚本组件。
|
||||
/// </summary>
|
||||
@ -145,23 +165,47 @@ namespace UI
|
||||
{
|
||||
entityPlacementUI.currentAction = () =>
|
||||
{
|
||||
Managers.EntityManage.Instance.GenerateEntity(entityDef, Utils.MousePosition.GetWorldPosition());
|
||||
Managers.EntityManage.Instance.GenerateEntity(Program.Instance.FocusedDimensionId,entityDef, Utils.MousePosition.GetWorldPosition());
|
||||
};
|
||||
entityPlacementUI.Prompt = $"当前生成器:\n名称:{entityDef.label}\n描述:{entityDef.description}";
|
||||
entityPlacementUI.snapEnabled = false;
|
||||
Base.UIInputControl.Instance.Show(entityPlacementUI);
|
||||
UIInputControl.Instance.Show(entityPlacementUI);
|
||||
}
|
||||
private void GenerateMonsterEntityCallback(MonsterDef monsterDef)
|
||||
{
|
||||
entityPlacementUI.currentAction = () =>
|
||||
{
|
||||
Managers.EntityManage.Instance.GenerateMonsterEntity(Program.Instance.FocusedDimensionId,monsterDef, Utils.MousePosition.GetWorldPosition());
|
||||
};
|
||||
entityPlacementUI.Prompt = $"当前生成器:\n名称:{monsterDef.label}\n描述:{monsterDef.description}";
|
||||
entityPlacementUI.snapEnabled = false;
|
||||
UIInputControl.Instance.Show(entityPlacementUI);
|
||||
}
|
||||
|
||||
private void GenerateBuildingCallback(BuildingDef def)
|
||||
{
|
||||
entityPlacementUI.currentAction = () =>
|
||||
{
|
||||
Managers.EntityManage.Instance.GenerateBuildingEntity(def, Utils.MousePosition.GetSnappedWorldPosition());
|
||||
Managers.EntityManage.Instance.GenerateBuildingEntity(Program.Instance.FocusedDimensionId,def, Utils.MousePosition.GetSnappedWorldPosition());
|
||||
};
|
||||
entityPlacementUI.Prompt = $"当前生成器:\n名称:{def.label}\n描述:{def.description}";
|
||||
entityPlacementUI.snapEnabled = true;
|
||||
Base.UIInputControl.Instance.Show(entityPlacementUI);
|
||||
UIInputControl.Instance.Show(entityPlacementUI);
|
||||
}
|
||||
private void GeneratePickupCallback(ItemDef itemDef)
|
||||
{
|
||||
entityPlacementUI.currentAction = () =>
|
||||
{
|
||||
Managers.EntityManage.Instance.GeneratePickupEntity(Program.Instance.FocusedDimensionId,itemDef, Utils.MousePosition.GetWorldPosition());
|
||||
};
|
||||
entityPlacementUI.Prompt = $"当前生成器:\n名称:{itemDef.label}\n描述:{itemDef.description}";
|
||||
entityPlacementUI.snapEnabled = false;
|
||||
UIInputControl.Instance.Show(entityPlacementUI);
|
||||
}
|
||||
private void HotReload()
|
||||
{
|
||||
UIInputControl.Instance.HideAll();
|
||||
Program.Instance.needLoad = true;
|
||||
SceneManager.LoadScene(0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
275
Client/Assets/Scripts/UI/EquipmentUI.cs
Normal file
275
Client/Assets/Scripts/UI/EquipmentUI.cs
Normal file
@ -0,0 +1,275 @@
|
||||
using System.Collections.Generic;
|
||||
using Base;
|
||||
using Entity;
|
||||
using UnityEngine;
|
||||
// 确保 Character 类在此命名空间下
|
||||
|
||||
namespace UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 负责管理和显示角色的装备用户界面。
|
||||
/// 该组件会监听当前关注实体的变化,并根据所关注角色的库存数据动态更新装备槽位的显示,
|
||||
/// 同时采用对象池技术高效管理 ItemUI 实例的创建和复用。
|
||||
/// 除了显示,现在还支持通过滚轮选择物品,并同步更新焦点角色的 CurrentSelected 字段。
|
||||
/// </summary>
|
||||
public class EquipmentUI : MonoBehaviour, ITick
|
||||
{
|
||||
[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选中状态
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
3
Client/Assets/Scripts/UI/EquipmentUI.cs.meta
Normal file
3
Client/Assets/Scripts/UI/EquipmentUI.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4432817f903945648bdc0b3a4ba19adc
|
||||
timeCreated: 1756195128
|
@ -1,4 +1,6 @@
|
||||
using Base;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace UI
|
||||
{
|
||||
@ -9,7 +11,7 @@ namespace UI
|
||||
Base.UIInputControl.Instance.Hide(this);
|
||||
}
|
||||
|
||||
public void ExitButton()
|
||||
public static void ExitButton()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
UnityEditor.EditorApplication.isPlaying = false; // 停止编辑器播放模式
|
||||
@ -18,9 +20,16 @@ namespace UI
|
||||
#endif
|
||||
}
|
||||
|
||||
public void SettingsButton()
|
||||
public static void SettingsButton()
|
||||
{
|
||||
|
||||
UIInputControl.Instance.Show("SettingUI");
|
||||
}
|
||||
|
||||
public static void ReturnMainMenu()
|
||||
{
|
||||
if (SceneManager.GetActiveScene().buildIndex == 0)
|
||||
return;
|
||||
SceneManager.LoadScene(0);
|
||||
}
|
||||
}
|
||||
}
|
91
Client/Assets/Scripts/UI/ItemUI.cs
Normal file
91
Client/Assets/Scripts/UI/ItemUI.cs
Normal file
@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using Base;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UI
|
||||
{
|
||||
public class ItemUI:MonoBehaviour,IPointerEnterHandler,IPointerExitHandler,IPointerClickHandler,ITick
|
||||
{
|
||||
[SerializeField] private Image textureUI;
|
||||
[SerializeField] private TMP_Text countUI;
|
||||
[SerializeField] private TMP_Text nameUI;
|
||||
[SerializeField] private GameObject selectedOutline;
|
||||
|
||||
private Entity.InventorySlot _item;
|
||||
private float timer = 0;
|
||||
private float switchTime = 0;
|
||||
private int texturePtr = 0;
|
||||
|
||||
public event Action OnPlayerSelect;
|
||||
|
||||
public bool Select
|
||||
{
|
||||
get => selectedOutline.activeSelf;
|
||||
set => selectedOutline.SetActive(value);
|
||||
}
|
||||
|
||||
public int SlotIndex { get; private set; } = -1;
|
||||
|
||||
public void Init(Entity.InventorySlot item,int index)
|
||||
{
|
||||
if (item == 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;
|
||||
}
|
||||
else
|
||||
{
|
||||
switchTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
3
Client/Assets/Scripts/UI/ItemUI.cs.meta
Normal file
3
Client/Assets/Scripts/UI/ItemUI.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d651a3517184abcb4aff78629eb7946
|
||||
timeCreated: 1756195181
|
@ -42,7 +42,7 @@ namespace UI
|
||||
// 如果日志数量减少,清理多余的条目
|
||||
if (logs.Count < _lastLogCount)
|
||||
{
|
||||
for (int i = logs.Count; i < _lastLogCount; i++)
|
||||
for (var i = logs.Count; i < _lastLogCount; i++)
|
||||
{
|
||||
Destroy(_logItems[i].gameObject);
|
||||
}
|
||||
@ -51,7 +51,7 @@ namespace UI
|
||||
}
|
||||
|
||||
// 更新现有条目
|
||||
for (int i = 0; i < Math.Min(logs.Count, _logItems.Count); i++)
|
||||
for (var i = 0; i < Math.Min(logs.Count, _logItems.Count); i++)
|
||||
{
|
||||
UpdateLogEntry(_logItems[i], logs[logs.Count - 1 - i]);
|
||||
}
|
||||
@ -59,7 +59,7 @@ namespace UI
|
||||
// 添加新的条目
|
||||
if (logs.Count > _lastLogCount)
|
||||
{
|
||||
for (int i = _lastLogCount; i < logs.Count; i++)
|
||||
for (var i = _lastLogCount; i < logs.Count; i++)
|
||||
{
|
||||
CreateLogEntry(logs[logs.Count - 1 - i]);
|
||||
}
|
||||
@ -85,7 +85,7 @@ namespace UI
|
||||
logItem.Label = entry.ToString();
|
||||
|
||||
// 设置文本颜色(根据日志类型)
|
||||
if (logColors.TryGetValue(entry.Type, out Color color))
|
||||
if (logColors.TryGetValue(entry.Type, out var color))
|
||||
{
|
||||
logItem.text.color = color;
|
||||
}
|
||||
|
24
Client/Assets/Scripts/UI/PlayerStateUI.cs
Normal file
24
Client/Assets/Scripts/UI/PlayerStateUI.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UI
|
||||
{
|
||||
public class PlayerStateUI:MonoBehaviour,Base.ITick
|
||||
{
|
||||
[SerializeField] private BarUI focusedEntityHP;
|
||||
[SerializeField] private BarUI lastEntityHP;
|
||||
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
var focusedEntity = Program.Instance.FocusedEntity;
|
||||
if (focusedEntity && focusedEntity.entityDef != null)
|
||||
{
|
||||
focusedEntityHP.Progress =
|
||||
(float)focusedEntity.attributes.health / focusedEntity.entityDef.attributes.health;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
3
Client/Assets/Scripts/UI/PlayerStateUI.cs.meta
Normal file
3
Client/Assets/Scripts/UI/PlayerStateUI.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2fcb0352f5bd43a49b164e24a38447e3
|
||||
timeCreated: 1756183287
|
75
Client/Assets/Scripts/UI/SettingUI.cs
Normal file
75
Client/Assets/Scripts/UI/SettingUI.cs
Normal file
@ -0,0 +1,75 @@
|
||||
using System.Globalization;
|
||||
using Base;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UI
|
||||
{
|
||||
public class SettingUI : UIBase
|
||||
{
|
||||
[SerializeField] private Scrollbar globalVolume;
|
||||
[SerializeField] private Toggle developerMode;
|
||||
[SerializeField] private Toggle friendlyFire;
|
||||
[SerializeField] private Toggle showMiniMap;
|
||||
[SerializeField] private TMP_Dropdown windowMode;
|
||||
[SerializeField] private TMP_Dropdown windowResolution;
|
||||
[SerializeField] private TMP_InputField progressStepDuration;
|
||||
[SerializeField] private TMP_InputField exitAnimationDuration;
|
||||
|
||||
Base.Setting.GameSettings currentSettings;
|
||||
|
||||
public override void Show()
|
||||
{
|
||||
base.Show();
|
||||
currentSettings = Base.Setting.Instance.CurrentSettings;
|
||||
globalVolume.value = currentSettings.globalVolume;
|
||||
developerMode.isOn = currentSettings.developerMode;
|
||||
friendlyFire.isOn = currentSettings.friendlyFire;
|
||||
showMiniMap.isOn = currentSettings.showMiniMap;
|
||||
|
||||
progressStepDuration.text = currentSettings.progressStepDuration.ToString(CultureInfo.InvariantCulture);
|
||||
exitAnimationDuration.text = currentSettings.exitAnimationDuration.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
windowResolution.ClearOptions();
|
||||
var options = new System.Collections.Generic.List<TMP_Dropdown.OptionData>();
|
||||
foreach (var resolution in Base.Setting.CommonResolutions)
|
||||
{
|
||||
options.Add(new TMP_Dropdown.OptionData(resolution.ToString()));
|
||||
}
|
||||
windowResolution.AddOptions(options);
|
||||
if (currentSettings.windowResolution != null)
|
||||
{
|
||||
var resolutionIndex = System.Array.FindIndex(Base.Setting.CommonResolutions, r => r == currentSettings.windowResolution);
|
||||
windowResolution.value = resolutionIndex >= 0 ? resolutionIndex : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
windowResolution.value = 0;
|
||||
}
|
||||
}
|
||||
public void CancelSettings()
|
||||
{
|
||||
UIInputControl.Instance.Hide(this);
|
||||
}
|
||||
public void ApplySettings()
|
||||
{
|
||||
currentSettings.globalVolume = globalVolume.value;
|
||||
currentSettings.developerMode = developerMode.isOn;
|
||||
currentSettings.friendlyFire = friendlyFire.isOn;
|
||||
currentSettings.currentWindowMode = (Base.Setting.WindowMode)windowMode.value;
|
||||
currentSettings.windowResolution = Base.Setting.CommonResolutions[windowResolution.value];
|
||||
currentSettings.progressStepDuration = float.Parse(progressStepDuration.text, CultureInfo.InvariantCulture);
|
||||
currentSettings.exitAnimationDuration = float.Parse(exitAnimationDuration.text, CultureInfo.InvariantCulture);
|
||||
currentSettings.showMiniMap = showMiniMap.isOn;
|
||||
Base.Setting.Instance.CurrentSettings = currentSettings;
|
||||
Base.Setting.Instance.Apply();
|
||||
}
|
||||
public void EnterSetting()
|
||||
{
|
||||
ApplySettings();
|
||||
UIInputControl.Instance.Hide(this);
|
||||
Base.Setting.Instance.SaveSettings();
|
||||
}
|
||||
}
|
||||
}
|
3
Client/Assets/Scripts/UI/SettingUI.cs.meta
Normal file
3
Client/Assets/Scripts/UI/SettingUI.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7f4127efa613491894a9b1a7e9918a26
|
||||
timeCreated: 1755600172
|
Reference in New Issue
Block a user