Unity3d UGUI如何优雅的实现Web框架(Vue/Rect)类似数据绑定功能(含源码)

前言

Unity3d的UGUI系统与Web前端开发中常见的数据绑定和属性绑定机制有所不同。UGUI是一个相对简单和基础的UI系统,并不内置像Web前端(例如 Vue.js或React中)那样的双向数据绑定或自动更新UI的机制。UGUI是一种比较传统的 UI 系统,它更侧重于基于事件的UI更新和手动控制视图的更新。在 UGUI中,如果数据变化了,开发者需要手动更新UI元素(例如文本、按钮状态、进度条等)。这种方式虽然灵活,但需要开发者自己处理每个UI更新的时机和逻辑。
对比数据可以通过双大括号 {{}} 语法直接绑定到模板中,无需手动处理DOM元素;而Unity3d的Text修改文字内容则需要通过Text.text属性来修改,对比起来比较麻烦,特比实在数据变量频繁变更的情况下。要是在Unity的UGUI中实现了数据绑定,可以提高代码冗余、提高UI数据更新开发效率、解耦数据和UI的关联。目前实现的功能有Text内{{}}绑定数据、颜色绑定(Graphic.color)、图片绑定(Image.sprite)、列表绑定和属性绑定等功能。

尽管它有着诸多的优势,如减少了代码冗余、提高了开发效率和提高了应用的可维护性。它通过解耦视图和数据,使得开发者能够更关注业务逻辑,而不是繁琐的 UI 更新操作。这使得开发者能够更加专注于应用的核心功能,提升了代码质量和可扩展性。但是数据绑定也有一些挑战,特别是性能优化方面(尤其是在大规模数据或复杂 UI 交互时)。

关注并私信 U3D数据绑定 免费获取源码(底部公众号)。

效果

文字绑定和列表刷新:
在这里插入图片描述

滑动条绑定:

在这里插入图片描述

列表绑定:

在这里插入图片描述

颜色绑定:

在这里插入图片描述

实现

大致实现思路如下:
1.先建立一个类(DataContext)作为管理键值对的容器,它允许通过键(key)来访问和修改与之相关联的值(value)。这个类还支持在值发生更改时触发一个事件(Changed),事件触发时,会传递触发变化的键。

using System;
using System.Collections.Generic;public class DataContext
{public event Action<string> contextChanged = delegate { };private IDictionary<string, object> m_ActiveBinds = new Dictionary<string, object>();public bool ContainsKey(string key){return m_ActiveBinds.ContainsKey(key);}
}

2.再建立一个DataBindContext类,管理和更新数据绑定的类。它通过 DataContext 来存储数据,并在数据变化时通知相关的 UI 组件更新。:当数据变化时,BindChanged 方法会被触发,自动更新所有依赖于该数据的 UI 组件。

using UnityEngine;//数据绑定类
public class DataBindContext : MonoBehaviour
{private DataContext m_DataContext;public object this[string key]{get { return m_DataContext[key]; }set{if (m_DataContext == null){m_DataContext = new DataContext();m_DataContext.contextChanged += BindChanged;}m_DataContext[key] = value;}}public void BindChanged(string key){var children = GetComponentsInChildren<IBindable>();if (children == null)return;for (var i = 0; i < children.Length; i++)if (string.IsNullOrEmpty(children[i].key) || children[i].key == key)children[i].Bind(m_DataContext);}}

3.再建立IBindable 接口,IBindable 提供了一个统一的接口来进行绑定键的数据更新,包括key属性(用于绑定的key值)和Bind(DataContext context)方法,Bind方法接收一个 DataContext 参数,UI 组件通过此方法将数据模型绑定到自身,监听数据变化,并在数据变化时自动更新UI。

public interface IBindable
{string key { get; }void Bind(DataContext context);
}

Text绑定

Text内采用“{{}}”绑定数据,是最最常用的数据绑定,例如{{Number:N0}}{{Test}}绑定了Number和Test的键值,当检测到数据变更会进行刷新。
显示前:
在这里插入图片描述

代码调用变更

DataBindContext.instance?.SetKeyValue("Number", 57729);
DataBindContext.instance?.SetKeyValue("Test", "测试绑定");

显示后:
在这里插入图片描述

Text的绑定实现就是通过实现IBindable接口的Bind方法,在其中将匹配的字符串进行替换变量数值,代码如下:

 m_Text.text = Regex.Replace(m_OriginalText, @"\{\{[^}]*}}", m =>
{var target = m.Value.Substring(2, m.Value.Length - 4).Split(':');var key = target[0];if (context.ContainsKey(key)) {var val = context[key];if (target.Length == 2 && val is IFormattable) {var format = target[1];return ((IFormattable) val).ToString(format, CultureInfo.CurrentCulture);}return val.ToString();
}return "";
});

属性绑定

属性绑定是将两个UGUI的组件属性直接做一个关联,当然关联之前开发者也需要了解两个属性间值是否真的能关联匹配。这里以Slider的value 关联到 Text的text属性为例:
在这里插入图片描述

这样运行时候Slider的value属性就会同步到Text.text显示:
在这里插入图片描述

同时属性绑定可以选择方向和多种更新同步方式:

在这里插入图片描述

不同更新同步模式的代码:

private void Update()
{if (m_Update == UpdateMethod.OnUpdate) {UpdateBind();}
}private void FixedUpdate()
{if (m_Update == UpdateMethod.OnFixedUpdate) {UpdateBind();}
}private void LateUpdate()
{if (m_Update == UpdateMethod.OnLateUpdate) {UpdateBind();}
}

属性更新实现:

public void UpdateBind()
{if (m_SourceProperty == null || m_DestinationProperty == null){return;}if (m_CachedSourceProperty == null || m_CachedSourceProperty.Name != m_SourceProperty|| m_CachedDestinationProperty == null || m_CachedDestinationProperty.Name != m_DestinationProperty){Cache();}switch (m_Direction){case Direction.SourceUpdatesDestination:if (m_CachedDestinationProperty.PropertyType == typeof(string)){m_CachedDestinationProperty.SetValue(m_Destination, m_CachedSourceProperty.GetValue(m_Source, null).ToString(),null);}else{m_CachedDestinationProperty.SetValue(m_Destination, m_CachedSourceProperty.GetValue(m_Source, null), null);}break;case Direction.DestinationUpdatesSource:if (m_CachedSourceProperty.PropertyType == typeof(string)){m_CachedSourceProperty.SetValue(m_Source, m_CachedDestinationProperty.GetValue(m_Destination, null).ToString(),null);}else{m_CachedSourceProperty.SetValue(m_Source, m_CachedDestinationProperty.GetValue(m_Destination, null), null);}break;}
}public void Cache()
{m_CachedSourceProperty = m_Source.GetType().GetProperty(m_SourceProperty);m_CachedDestinationProperty = m_Destination.GetType().GetProperty(m_DestinationProperty);
}

图片绑定

[SerializeField]
[Header("绑定对象")]
private Image m_Image;
[SerializeField]
[Header("绑定键名")]
private string m_Key;public string key
{get { return m_Key; }
}public void Bind(DataContext context)
{if (context.ContainsKey(m_Key)){m_Image.sprite = (Sprite)context[m_Key];}
}

颜色绑定

[SerializeField]
[Header("绑定对象")]
private Graphic m_Graphic;
[SerializeField]
[Header("绑定键名")]
private string m_Key;public string key
{get { return m_Key; }
}public void Bind(DataContext context)
{if (context.ContainsKey(m_Key)){m_Graphic.color = (Color)context[m_Key];}
}

滑动条绑定

private Slider m_Slider;[Header("绑定键名")]
public string m_Key;public string key
{get { return m_Key; }
}
public void Bind(DataContext context)
{if (m_Slider == null)m_Slider = GetComponent<Slider>();if (context.ContainsKey(key)){m_Slider.value = (float)context[key];}
}

列表绑定

列表的绑定其实需要预设节点名称和绑定键名等设置,如下:

[SerializeField]
[Header("列表节点预设")]
private GameObject m_ItemPrefab;
[SerializeField]
[Header("节点键名")]
public string m_ItemKey;
[SerializeField]
[Header("绑定键名")]
public string m_Key;

数据绑定刷新的代码如下:

if (m_ItemPrefab == null)
{Debug.LogWarning("节点预设为空,无法绑定列表!");return;
}m_Context = new DataContext();if (context.ContainsKey(m_Key))
{var list = (ObservableList)context[m_Key];if (list.Count > itemObjList.Count){for (int i = itemObjList.Count; i < list.Count; i++){GameObject go = GameObject.Instantiate(m_ItemPrefab);go.transform.SetParent(transform, false);go.transform.localScale = Vector3.one;go.transform.localEulerAngles = Vector3.zero;go.transform.name = i.ToString("D4") + "item";itemObjList.Add(go);}}elsefor (int i = list.Count; i < itemObjList.Count; i++)itemObjList[i].SetActive(false);for (int i = 0; i < list.Count; i++){var itemData = list[i];var item = itemObjList[i];var bindables = item.GetComponentsInChildren<IBindable>(true);var properties = itemData.GetType().GetProperties();var model = item.GetComponent<IModel>();if (model != null)model.model = itemData;for (var j = 0; j < properties.Length; j++){var p = properties[j];m_Context[m_ItemKey + "." + p.Name] = p.GetValue(itemData, null);}for (var j = 0; j < bindables.Length; j++)bindables[j].Bind(m_Context);itemObjList[i].SetActive(true);}}

其核心思路就是存在该键变更时,根据列表数据显示或者隐藏、并刷新所有子节点。通过键的值以列表的形式,节点不够时克隆节点的预设,生成节点,将单个节点的数据根据绑定配置刷新到对应的组件上,直到所有节点刷新完毕。

同时预设的节点数据需要与定义的类型结构统一,这里以排行榜为例,其数据结构如下:

class RankItem
{public int index { get; set; }public string name { get; set; }public float score { get; set; }
}

index、name和score分别表示排名、玩家名称和分数。
所以单个节点的预设的绑定应该如下配置:
在这里插入图片描述

列表的键采用Ranks,同时通过如下代码生成假数据:

int count = 10;
for (int i = count; i > 0; i--)
{m_RankItems.Add(new RankItem{index = count - i + 1,score = i * 100 + Random.Range(0, 7.6f),name = "玩家名称" + (count - i + 1)});
}DataBindContext.instance?.SetKeyValue("Ranks", m_RankItems);

绑定列表的配置最终如下图:
在这里插入图片描述

绑定与变更

绑定值变更数据的键值变更采用如下代码:

DataBindContext m_Context["键名"] =;

单例模式也可采用来修改值:

DataBindContext.instance?.SetKeyValue("键名",);

演示功能

这里简单搭建一个覆盖功能的UI:

在这里插入图片描述

挂上对应的脚本(注意DataBindContext 需要挂在最外层)后运行效果:
在这里插入图片描述

项目源码

https://download.csdn.net/download/qq_33789001/90195629

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/64288.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OptimisticLock

想象你和你的朋友去了一家很受欢迎的餐厅。你们想要点一份特别的菜品——这家餐厅的招牌菜&#xff0c;但因为这道菜非常受欢迎&#xff0c;所以它的状态可能会随时变化&#xff08;比如售罄或重新上架&#xff09;。 传统方式&#xff08;悲观锁&#xff09; 通常情况下&…

10分钟掌握项目管理核心工具:WBS、甘特图、关键路径法全解析

一、引言 在项目管理的广阔天地里&#xff0c;犹如一场精心编排的交响乐演奏&#xff0c;每个乐器、每个音符都需精准配合才能奏响美妙乐章。而 WBS&#xff08;工作分解结构&#xff09;、甘特图、关键路径法无疑是这场交响乐中的关键乐章&#xff0c;它们从不同维度为项目管…

TCP 和 UDP 的区别:解析网络传输协议

引言 在计算机网络的世界中&#xff0c;TCP&#xff08;Transmission Control Protocol&#xff0c;传输控制协议&#xff09;和 UDP&#xff08;User Datagram Protocol&#xff0c;用户数据报协议&#xff09;是两种极为重要且应用广泛的传输层协议。它们在功能、特性以及适…

代码思想之快慢路径

处理业务代码的过程中&#xff0c;对业务代码有了一些调整&#xff0c;后续发现这是一种代码思想 在一段复杂的逻辑里&#xff0c;我把查询redis操作写在了前面&#xff0c; 业务逻辑&#xff1a; 如果需要不打压就退出本次处理 查询redis拿到商品需要打压的次数 如果次数 …

Java 溯本求源之基础(三十一)——泛型

目录 1. 泛型的定义与基本概念 2. 泛型的优势 3. 泛型的基本语法 3.1 泛型类 3.2 泛型方法 3.3 泛型接口 4. 泛型的边界 4.1 上限通配符&#xff08;? extends T&#xff09; 4.2 下限通配符&#xff08;? super T&#xff09; 5. 泛型的类型擦除 6. 泛型的使用场景…

纯 HTML+CSS+JS 实现一个炫酷的圣诞树动画特效

纯 HTMLCSSJS 实现一个炫酷的圣诞树动画特效 前言 圣诞节快到了&#xff0c;今天给大家带来一个简单但是效果不错的圣诞树动画特效。这个特效完全使用原生 HTML、CSS 和 JavaScript 实现&#xff0c;包含闪烁的星星、随机彩灯等元素&#xff0c;非常适合节日气氛&#xff01;…

Maven:Java项目构建与管理的利器

在Java开发领域&#xff0c;Maven无疑是一个举足轻重的工具。它不仅简化了项目的构建和依赖管理&#xff0c;还促进了团队协作和持续集成。本文将深入探讨Maven的核心功能、基本配置以及在实际项目中的应用。 Maven简介 Maven是Apache基金会下的一个开源项目&#xff0c;旨在…

【ES6复习笔记】Promise对象详解(12)

1. 什么是 Promise&#xff1f; Promise 是 JavaScript 中处理异步操作的一种机制&#xff0c;它可以让异步操作更加容易管理和控制。Promise 对象代表一个异步操作的最终完成或失败&#xff0c;并提供了一种方式来处理操作的结果。 2. Promise 的基本语法 Promise 对象有三…

【RAG实战】语言模型基础

语言模型赋予了计算机理解和生成人类语言的能力。它结合了统计学原理和深度神经网络技术&#xff0c;通过对大量的样本数据进行复杂的概率分布分析来学习语言结构的内在模式和相关性。具体地&#xff0c;语言模型可根据上下文中已出现的词序列&#xff0c;使用概率推断来预测接…

【ES6复习笔记】Map(14)

概念 Map 是 JavaScript 中的一种数据结构&#xff0c;它允许你存储键值对&#xff0c;并且可以通过键来访问对应的值。在本教程中&#xff0c;我们将学习如何声明、添加、删除、获取和遍历 Map 集合。 ES6 提供了 Map 数据结构。它类似于对象&#xff0c;也是键值对的集合。…

富芮坤FR800X系列之PWM输出程序应用设计

文章目录 前言1.设计背景2.简介3.如何设计控制调光的接口呢4.硬件设计5.软件设计5.1.软件流程图5.2.软件代码 6.小结 前言 版权归作者所有、未经允许、请勿转载。 读者对象&#xff1a; 本文档主要适用以下工程师&#xff1a; 嵌入式系统工程师 单片机软件工程师 IOT固…

Ftrans数据摆渡系统 搭建安全便捷跨网文件传输通道

一、专业数据摆渡系统对企业的意义 专业的数据摆渡系统对企业具有重要意义&#xff0c;主要体现在以下几个方面‌&#xff1a; 1、‌数据安全性‌&#xff1a;数据摆渡系统通过加密传输、访问控制和审计日志等功能&#xff0c;确保数据在传输和存储过程中的安全性。 2、‌高…

EasyPoi 使用$fe:模板语法生成Word动态行

1 Maven 依赖 <dependency><groupId>cn.afterturn</groupId><artifactId>easypoi-spring-boot-starter</artifactId><version>4.0.0</version> </dependency> 2 application.yml spring:main:allow-bean-definition-over…

定义Shape:打造属于你的独特图形

自定义Shape:打造属于你的独特图形 在Android开发中,自定义图形绘制是一个非常重要的技能,尤其是在需要实现复杂UI或特定设计需求时。Android提供了android.graphics.drawable.shapes包,其中包含了一些基本的形状类,如RectShape、OvalShape等。然而,有时这些基本形状无法…

python 渗透开发工具之SQLMapApi Server不同IP服务启动方式处理 解决方案SqlMapApiServer外网不能访问的情况

目录 说在前面 什么是 SQLMapAPI 说明 sqlmapApi能干什么 sqlmapApi 服务安装相关 kali-sqlmap存放位置 正常启动sqlmap-api server SqlMapApi-Server 解决外网不能访问情况 说在前面 什么是sqlmap 这个在前面已经说过了&#xff0c;如果这个不知道&#xff0c;就可以…

【基础还得练】 KKT 条件

优秀教程-真正理解拉格朗日乘子法和 KKT 条件&#xff1a; link优秀教程-最优化(6)&#xff1a;一般约束优化问题的最优性理论&#xff1a; link KKT条件&#xff08;Karush-Kuhn-Tucker条件&#xff09;是非线性规划中的一组必要条件&#xff0c;在某些情况下也是最优解的充分…

使用 Webpack 优雅的构建微前端应用❕

Module Federation 通常译作“模块联邦”&#xff0c;是 Webpack 5 新引入的一种远程模块动态加载、运行技术。MF 允许我们将原本单个巨大应用按我们理想的方式拆分成多个体积更小、职责更内聚的小应用形式&#xff0c;理想情况下各个应用能够实现独立部署、独立开发(不同应用甚…

Boost之log日志使用

不讲理论&#xff0c;直接上在程序中可用代码&#xff1a; 一、引入Boost模块 开发环境&#xff1a;Visual Studio 2017 Boost库版本&#xff1a;1.68.0 安装方式&#xff1a;Nuget 安装命令&#xff1a; #只安装下面几个即可 Install-package boost -version 1.68.0 Install…

【MySQL】十四,MySQL 8.0的隐藏索引

在MySQL 8.0之前的版本中&#xff0c;索引只能直接删除。如果删除后发现引起了系统故障&#xff0c;又必须进行创建。当表的数据量比较大的时候&#xff0c;这样做的代价就会非常高。 在MySQL 8.0中&#xff0c;提供了隐藏索引。如果想删除某个索引&#xff0c;那么在实际删除…

【ES6复习笔记】解构赋值(2)

介绍 解构赋值是一种非常方便的语法&#xff0c;可以让我们更简洁地从数组和对象中提取值&#xff0c;并且可以应用于很多实际开发场景中。 1. 数组的解构赋值 数组的解构赋值是按照一定模式从数组中提取值&#xff0c;然后对变量进行赋值。下面是一个例子&#xff1a; con…