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.rhkb.cn/news/497374.html

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

相关文章

影刀进阶应用 | 知乎发布想法

文章目录 影刀进阶应用 | 知乎发布想法一、流程流程解释&#xff1a; 二、单条想法发布2.1 素材生产2.2 **进入发布流程**2.3 **输入文本**2.4 插入图片2.5 发布查看 三、批量发布 影刀进阶应用 | 知乎发布想法 一、流程 流程解释&#xff1a; 素材生产 &#xff1a;用AI生成待…

Win Server远程提示密码到期

背景 由于安全问题&#xff0c;取消了堡垒机直接托管资产的模式&#xff0c;需要用户通过堡垒机先登录操作机&#xff0c;然后通过操作机运维对应服务器 问题现象 调整运维模式后&#xff0c;发现原来堡垒机直接运维服务器时候&#xff0c;用户密码到期&#xff0c;可以要求…

GXUOJ-算法-第二次作业

1.矩阵连&#xff08;链&#xff09;乘 问题描述 GXUOJ | 矩阵连乘 代码解答 #include<bits/stdc.h> using namespace std;const int N50; int m[N][N]; int p[N]; int n;int main(){cin>>n;//m[i][j] 存储的是从第 i 个矩阵到第 j 个矩阵这一段矩阵链相乘的最小…

在线学习平台-项目技术点-前台

报错解决方法 1、P166-尚硅谷_在线教育_Nuxt整合错误_nuxt friendly-errors-CSDN博客 2、P168 3、P170 4、P173 npm remove axios npm install axios0.18.0 1、服务端渲染技术NUXT 1.1服务端渲染SSR 服务端渲染又称SSR (Server Side Render)是在服务端完成页面的内容&…

【探花交友】day03—MongoDB基础

目录 课程介绍 1、通用设置 1.1 需求分析 1.2 查询通用设置 1.2 陌生人问题 1.3 通知设置 1.4 黑名单管理 2、MongoDB简介 1.1、MongoDB简介 1.2、MongoDB的特点 1.3 数据类型 3、MongoDB入门 2.1、数据库以及表的操作 2.2、新增数据 2.3、更新数据 2.4、删除数…

基于Spring Boot + Vue3实现的在线商品竞拍管理系统源码+文档

前言 基于Spring Boot Vue3实现的在线商品竞拍管理系统是一种现代化的前后端分离架构的应用程序&#xff0c;它结合了Java后端框架Spring Boot和JavaScript前端框架Vue.js的最新版本&#xff08;Vue 3&#xff09;。该系统允许用户在线参与商品竞拍&#xff0c;并提供管理后台…

JVM学习-内存结构(二)

一、堆 1.定义 2.堆内存溢出问题 1.演示 -Xmx设置堆大小 3.堆内存的诊断 3.1介绍 1&#xff0c;2都是命令行工具&#xff08;可直接在ideal运行时&#xff0c;在底下打开终端&#xff0c;输入命令&#xff09; 1可以拿到Java进程的进程ID&#xff0c;2 jmap只能查询某一个时…

在线学习平台-项目技术点-后台

目录 1、主键生成策略 1.1自动增长-AUTO INCREMENT 1.2UUID 1.3Redis生成ID 2、MyBatis-plus 2.1自动填充 2.2悲观锁、乐观锁 2.3性能分析插件 3.ResponseBody和RequestBody 4.es6语法 4.1let变量和const变量 4.2解构赋值&#xff08;数组和对象解构 4.3模板字符串…

Redis 实战篇 ——《黑马点评》(上)

《引言》 在进行了前面关于 Redis 基础篇及其客户端的学习之后&#xff0c;开始着手进行实战篇的学习。因内容很多&#xff0c;所以将会分为【 上 中 下 】三篇记录学习的内容与在学习的过程中解决问题的方法。Redis 实战篇的内容我写的很详细&#xff0c;为了能写的更好也付出…

MySQL数据库——常见的几种锁分类

详细介绍MySQL的几种常见锁分类&#xff0c;如&#xff1a;表级锁、行级锁、页面锁、悲观锁、乐观锁、共享锁、排他锁、Gap-锁等。 文章目录 按锁粒度分表级锁行级锁页面锁锁与索引关系 按加锁机制分【逻辑上的锁】悲观锁乐观锁版本号机制CAS&#xff08;Compare and Swap&…

数据库sql语句单表查询

简单的增删改查操作 select count(*) from user where accountadmin and password123456 select count(*) from user where account"admin" insert into user(account,password) values ("admin","777") update user set password "666&…

OpenCV和PyQt的应用

1.创建一个 PyQt 应用程序&#xff0c;该应用程序能够&#xff1a; 使用 OpenCV 加载一张图像。在 PyQt 的窗口中显示这张图像。提供四个按钮&#xff08;QPushButton&#xff09;&#xff1a; 一个用于将图像转换为灰度图一个用于将图像恢复为原始彩色图一个用于将图像进行翻…

电路元件与电路基本定理

电流、电压和电功率 电流 1 定义&#xff1a; 带电质点的有序运动形成电流 。 单位时间内通过导体横截面的电量定义为电流强度&#xff0c; 简称电流&#xff0c;用符号 i 表示&#xff0c;其数学表达式为&#xff1a;&#xff08;i单位&#xff1a;安培&#xff08;A&#x…

win11中win加方向键失效的原因

1、可能是你把win键锁了&#xff1a; 解决办法&#xff1a;先按Fn键&#xff0c;再按win键 2、可能是可能是 贴靠窗口设置 中将贴靠窗口关闭了&#xff0c;只需要将其打开就好了

十二月第五周python

第一个程序&#xff0c;熟悉转换器&#xff0c;把加法计算器变成exe# // 1,制作加法计算器&#xff0c; # 输入两个数字得到相加结果并输出aint(input("输入数字&#xff1a;"))#int()是把输入的内容转换成整数&#xff0c; bint(input("输入数字&#xff1a;&…

pyqt和pycharm环境搭建

安装 python安装&#xff1a; https://www.python.org/downloads/release/python-3913/ python3.9.13 64位(记得勾选Path环境变量) pycharm安装&#xff1a; https://www.jetbrains.com/pycharm/download/?sectionwindows community免费版 换源&#xff1a; pip config se…

Lottie动画源码解析

Lottie是一个很成熟的开源动画框架&#xff0c;它支持直接使用从AE导出的动画文件&#xff0c;在不同平台均可快速使用&#xff0c;大大减轻了程序员的工作量&#xff0c;也让复杂的动画成为可能。该动画文件使用Json格式来描述内容&#xff0c;可以大大缩减文件的体积。在Andr…

Cadence学习笔记 16 HDMI接口布局

基于Cadence 17.4&#xff0c;四层板4路HDMI电路 更多Cadence学习笔记&#xff1a;Cadence学习笔记 1 原理图库绘制Cadence学习笔记 2 PCB封装绘制Cadence学习笔记 3 MCU主控原理图绘制Cadence学习笔记 4 单片机原理图绘制Cadence学习笔记 5 四路HDMI原理图绘制Cadence学习笔记…

微服务篇-深入了解 MinIO 文件服务器(你还在使用阿里云 0SS 对象存储图片服务?教你使用 MinIO 文件服务器:实现从部署到具体使用)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 MinIO 文件服务器概述 1.1 MinIO 使用 Docker 部署 1.2 MinIO 控制台的使用 2.0 使用 Java 操作 MinIO 3.0 使用 minioClient 对象的方法 3.1 判断桶是否存在 3.2…

logback之pattern详解以及源码分析

目录 &#xff08;一&#xff09;pattern关键字介绍 &#xff08;二&#xff09;源码分析 &#xff08;一&#xff09;pattern关键字介绍 %d或%date&#xff1a;表示日期&#xff0c;可配置格式化%d{yyyy-MM-dd HH:mm:ss} %r或%relative&#xff1a;也是日期&#xff0c;不过…