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);
		}
	}
	else
		for (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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

828华为云征文|使用sysbench对Flexus X实例对mysql进行性能测评

目录 一、Flexus X实例概述 1.1?Flexus X实例 1.2?在mysql方面的优势 二、在服务器上安装MySQL 2.1 在宝塔上安装docker 2.2 使用宝塔安装mysql 2.3 准备测试数据库和数据库表 三、安装sysbench并进行性能测试 3.1 使用yum命令sysbench 3.2?运行?sysbench 并进行…

影刀进阶指令 | Kimi (对标ChatGPT)

文章目录 影刀进阶指令 | Kimi &#xff08;对标ChatGPT&#xff09;一. 需求二. 流程三. 实现3.1 流程概览3.2 流程步骤讲解1\. 确定问题2\. 填写问题并发送3\. 检测答案是否出完 四. 运维 影刀进阶指令 | Kimi &#xff08;对标ChatGPT&#xff09; 简单讲讲RPA调用kimi实现…

【教程】通过Docker运行AnythingLLM

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 官方教程&#xff1a;Local Docker Installation ~ AnythingLLM 1、先创建一个目录用于保存anythingllm的持久化文件&#xff1a; sudo mkdir /app su…

游戏引擎学习第65天

回顾我们在模拟区域更改方面的进展 目前我们正在进行游戏的架构调整&#xff0c;目标是建立一个引擎架构。我们正在实施的一个关键变化是引入模拟区域的概念&#xff0c;这样我们可以创建非常大的游戏世界&#xff0c;而这些世界的跨度不必受限于单个浮点变量。 通过这种方式…

【从零开始入门unity游戏开发之——C#篇35】C#自定义类实现Sort自定义排序

文章目录 一、List<T>自带的排序方法1、List<T>调用Sort()排序2、 能够使用 Sort() 方法进行排序的本质 二、自定义类的排序1、通过实现泛型IComparable<T> 接口&#xff08;1&#xff09;示例&#xff08;2&#xff09;直接调用 int 类型的 CompareTo 方法进…

YOLO系列正传(五)YOLOv4论文精解(上):从CSPNet、SPP、PANet到CSPDarknet-53

系列文章 YOLO系列基础 YOLO系列基础合集——小白也看得懂的论文精解-CSDN博客 YOLO系列正传 YOLO系列正传&#xff08;一&#xff09;类别损失与MSE损失函数、交叉熵损失函数-CSDN博客 YOLO系列正传&#xff08;二&#xff09;YOLOv3论文精解(上)——从FPN到darknet-53-C…

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

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

DevOps实战:用Kubernetes和Argo打造自动化CI/CD流程(2)

DevOps实战&#xff1a;用Kubernetes和Argo打造自动化CI/CD流程&#xff08;2&#xff09; 背景 Tips 翻遍国内外的文档&#xff0c;关于 Argo 作为 CI/CD 当前所有开源的文档&#xff0c;博客&#xff0c;argo官方文档。得出的结论是&#xff1a; argo官方给出的例子都相对…

探索Flink动态CEP:杭州银行的实战案例

摘要&#xff1a;本文撰写自杭州银行大数据工程师唐占峰、欧阳武林老师。将介绍 Flink 动态 CEP的定义与核心概念、应用场景、并深入探讨其技术实现并介绍使用方式。主要分为以下几个内容&#xff1a; Flink动态CEP简介 Flink动态CEP的应用场景 Flink动态CEP的技术实现 Flin…

STM32F103RCT6学习之三:串口

1.串口基础 2.串口发送 1&#xff09;基本配置 注意&#xff1a;实现串口通信功能需在keil中设置打开Use Micro LIB&#xff0c;才能通过串口助手观察到串口信息 2)编辑代码 int main(void) {/* USER CODE BEGIN 1 *//* USER CODE END 1 *//* MCU Configuration-------------…

Python中构建终端应用界面利器——Blessed模块

在现代开发中&#xff0c;命令行应用已经不再仅仅是一个简单的文本输入输出工具。随着需求的复杂化和用户体验的重视&#xff0c;终端界面也逐渐成为一个不可忽视的设计环节。 如果你曾经尝试过开发终端UI&#xff0c;可能对传统的 print() 或者 input() 函数感到不满足&#…

OpenHarmony-5.PM 子系统(2)

电池服务组件OpenHarmony-4.1-Release 1.电池服务组件 Battery Manager 提供了电池信息查询的接口&#xff0c;同时开发者也可以通过公共事件监听电池状态和充放电状态的变化。电池服务组件提供如下功能&#xff1a; 电池信息查询。充放电状态查询。关机充电。 电池服务组件架…

Java 网络原理 ①-IO多路复用 || 自定义协议 || XML || JSON

这里是Themberfue 在学习完简单的网络编程后&#xff0c;我们将更加深入网络的学习——HTTP协议、TCP协议、UDP协议、IP协议........... IO多路复用 ✨在上一节基于 TCP 协议 编写应用层代码时&#xff0c;我们通过一个线程处理连接的申请&#xff0c;随后通过多线程或者线程…

基于规则的系统架构:理论与实践

在当今信息化快速发展的时代&#xff0c;企业面临着日益复杂和多变的市场环境&#xff0c;传统的静态系统架构已难以满足快速响应业务变化的需求。基于规则的系统架构&#xff08;Rule-Based System Architecture, RBSA&#xff09;作为一种灵活、可扩展的架构模式&#xff0c;…

记一个itertools排列组合和列表随机排序的例子

朋友不知道哪里弄来了一长串单词列表&#xff0c;一定要搞个单词不重复的组合。那么这个时候我们就可以想到读书时所学的排列组合知识了&#xff0c;而这个在Python中可以怎么实现呢&#xff1f;我记录如下&#xff1a; 使用itertools模块实现排列组合 在 Python 中&#xff…

从0入门自主空中机器人-4-【PX4与Gazebo入门】

前言: 从上一篇的文章 从0入门自主空中机器人-3-【环境与常用软件安装】 | MGodmonkeyの世界 中我们的机载电脑已经安装了系统和常用的软件&#xff0c;这一篇文章中我们入门一下无人机常用的开源飞控PX4&#xff0c;以及ROS中无人机的仿真 1. PX4的安装 1.1 PX4固件代码的下载…

搭建vue项目

一、环境准备 1、安装node node官网&#xff1a;https://nodejs.org/zh-cn 1.1、打开官网&#xff0c;选择“下载”。 1.2、选择版本号&#xff0c;选择系统&#xff0c;根据需要自行选择&#xff0c;上面是命令安装方式&#xff0c;下载是下载安装包。 1.3、检查node安装…

深度学习笔记(5)——目标检测和图像分割

目标检测与图像分割 语义分割:如果没有语义信息,很难正确分类每个像素 解决方案:感知像素周围的语义,帮助正确分类像素 滑窗计算:计算非常低效,图像块的重叠部分会被重复计算很多次 解决方案:转向全卷积 全卷积问题:分类模型会大幅降低特征的分辨率,难以满足分割所需的高分辨…

go语言的成神之路-筑基篇-gin常用功能

第一节-gin参数绑定 目录 第一节-?gin参数绑定 ShouldBind简要概述 功能&#xff1a; 使用场景&#xff1a; 可能的错误&#xff1a; 实例代码 效果展示 第二节-gin文件上传 选择要上传的文件 选择要上传的文件。 效果展示? 代码部分 第三节-gin请求重定向 第…

【Leecode】Leecode刷题之路第93天之复原IP地址

题目出处 93-复原IP地址-题目描述 题目描述 个人解法 思路&#xff1a; todo代码示例&#xff1a;&#xff08;Java&#xff09; todo复杂度分析 todo官方解法 93-复原IP地址-官方解法 方法1&#xff1a;回溯 思路&#xff1a; 代码示例&#xff1a;&#xff08;Java&…