上下文
因此,在对 UI 工具包进行了一些研究之后,我决定将其用于我的项目,因为我非常习惯 CSS,并且鄙视 Unity3D 提供的“默认/旧版”UI 系统。
不幸的是,它还处于开发的早期阶段,他们似乎正在朝着“编辑器 GUI”方向发展。不管怎样,我选择它是因为它设计 UI 的速度比我使用通常的“Unity3D UI”方法更快。
目标
我的目标相对简单,我想创建一个基于某些变量更新 UI 的行为,同时隐藏/抽象此行为(换句话说,我想将 UI 的值绑定(bind)到数据类上的某个整数) .
将来,由于我慢慢地将所有内容迁移到多人游戏环境,这将变得更加复杂
主要问题
经过几个小时的搜索/阅读文档,并强行解决问题,我设法找到了一个简单的解决方案,由于我硬编码了一些非常相似的行为,我将缩写该解决方案:
GameObject trackCanvas = GameObject.Find("Canvas");
UIDocument docum= trackCanvas.GetComponent<UIDocument>();
Label foodUI = docum.rootVisualElement.Q<Label>(name: "FoodVar");
if(playerResources!= null){
SerializedObject pR = new SerializedObject(playerResources);
SerializedProperty foodProp = pR.FindProperty("food");
foodUI.BindProperty(foodProp);
}else{
foodUI.Unbind();
}
非常简单的解决方案,而且更好,但我节省了一些思考时间。这一切都像一个魅力,直到我尝试构建它......我看到多个与导入 UnityEditor 相关的错误,我开始删除它(因为我几乎导入所有内容,并且仅在 CleanUp 上我开始看到什么是必要的或不需要的)
不幸的是,在这个脚本上我不能,在重新阅读了细则上的文档(这不是那么好......)后,我读到 SerializedObject 不能在“生产/运行时/构建”版本中使用,因为它依赖于UnityEditor 不会存在于最终产品中。
这真的让我很恼火,因为这是一个非常 Eloquent 解决方案。
后缀
该手册似乎暗示有一些方法可以使用 UnityEditor 命名空间。不幸的是,从他们非常模糊的教程中,我无法弄清楚它是如何工作的(我提到了坦克示例,它似乎只使用了 unityeditor,因为他们希望能够在编辑模式下绑定(bind)内容,而绑定(bind)本身似乎是通过 uxml 完成的)
我尝试了一些方法,但这一切似乎都脱离了上下文,就像有一些serializedField会神奇地与uxml绑定(bind),只是因为绑定(bind)路径与变量名相同
然后我想,如果 Unity 不希望我在运行时模式下使用编辑器的东西,我会强制它,所以复制粘贴它的一些类,然后以某种方式破解它应该不难。不幸的是,Unity 不仅拥有严格的专有许可证,不允许您以任何方式修改其软件,而且一些注释、函数等......都受到保护(尤其是他们使用的 C 内容)
然后我考虑手动完成,我得到了两个选择:
只需将 food.value = resources.food 放入某种更新中,并希望当我将其迁移到多人游戏环境时不会产生任何类型的问题
或者做一些更复杂的事情,比如我称之为更新 UI 的某种委托(delegate),理论上效率更高,因为我只更新需要的内容。
自从我开始做即时战略游戏以来,我认为这些值(value)观会不断变化,所以我对两者都抱有很大分歧。让我想要坚持已经完成的解决方案
当我讨厌文档的结构、浏览源代码有多么困难,以及文档的长度与 CSS 非常相似的行为时,这让我感到压力更大
TL;博士:
UI 工具包中是否有不依赖 Unity 编辑器的 BindProperty() 替代方案?
最佳答案
您可以创建一个包装类来保存您的值,只要包装的值发生更改,它就可以调用一个事件。
public interface IProperty<T> : IProperty
{
new event Action<T> ValueChanged;
new T Value { get; }
}
public interface IProperty
{
event Action<object> ValueChanged;
object Value { get; }
}
[Serializable]
public class Property<T> : IProperty<T>
{
public event Action<T> ValueChanged;
event Action<object> IProperty.ValueChanged
{
add => valueChanged += value;
remove => valueChanged -= value;
}
[SerializeField]
private T value;
public T Value
{
get => value;
set
{
if(EqualityComparer<T>.Default.Equals(this.value, value))
{
return;
}
this.value = value;
ValueChanged?.Invoke(value);
valueChanged?.Invoke(value);
}
}
object IProperty.Value => value;
private Action<object> valueChanged;
public Property(T value) => this.value = value;
public static explicit operator Property<T>(T value) => new Property<T>(value);
public static implicit operator T(Property<T> binding) => binding.value;
}
此后,您可以创建类似于 Unity 自己的 BindProperty 的自定义扩展方法,该方法与此包装器一起使用,而不是 SerializedProperty。
public static class RuntimeBindingExtensions
{
private static readonly Dictionary<VisualElement, List<(IProperty property, Action<object> binding)>> propertyBindings = new Dictionary<VisualElement, List<(IProperty property, Action<object> binding)>>();
public static void BindProperty(this TextElement element, IProperty property)
{
if(!propertyBindings.TryGetValue(element, out var bindingsList))
{
bindingsList = new List<(IProperty, Action<object>)>();
propertyBindings.Add(element, bindingsList);
}
Action<object> onPropertyValueChanged = OnPropertyValueChanged;
bindingsList.Add((property, onPropertyValueChanged));
property.ValueChanged += onPropertyValueChanged;
OnPropertyValueChanged(property.Value);
void OnPropertyValueChanged(object newValue)
{
element.text = newValue?.ToString() ?? "";
}
}
public static void UnbindProperty(this TextElement element, IProperty property)
{
if(!propertyBindings.TryGetValue(element, out var bindingsList))
{
return;
}
for(int i = bindingsList.Count - 1; i >= 0; i--)
{
var propertyBinding = bindingsList[i];
if(propertyBinding.property == property)
{
propertyBinding.property.ValueChanged -= propertyBinding.binding;
bindingsList.RemoveAt(i);
}
}
}
public static void UnbindAllProperties(this TextElement element)
{
if(!propertyBindings.TryGetValue(element, out var bindingsList))
{
return;
}
foreach(var propertyBinding in bindingsList)
{
propertyBinding.property.ValueChanged -= propertyBinding.binding;
}
bindingsList.Clear();
}
}
用法:
public class PlayerResources
{
public Property<int> food;
}
if(playerResources != null)
{
foodUI.BindProperty(playerResources.food);
}
更新:还添加了用于取消绑定(bind)属性的扩展方法,并使 BindProperty 立即更新元素上的文本。
关于c# - 有没有一种方法可以在运行时轻松地将数据绑定(bind)到UI,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73595753/