UI Toolkit Runtime Data Binding and Logic
Summary
TLDR本视频深入探讨了UI工具包的使用,特别是数据绑定技术。通过展示如何将硬币显示与库存系统中的硬币绑定,并实时更新,视频详细介绍了模型、控制器以及可绑定属性的实现。从Unity中静态数据的细节到物品的序列化和反序列化,再到模型的观察数组和控制器的逻辑处理,视频逐步构建了一个完整的库存系统。此外,还介绍了如何使用视图模型和可绑定属性简化UI Toolkit的绑定过程,并通过实际示例演示了绑定的工作原理。
Takeaways
- 📦 今天我们将深入探讨 UI 工具包,尤其是我们的库存系统和数据绑定。
- 💰 屏幕上展示了如何将硬币显示绑定到库存中的硬币,并在每一帧添加一个硬币。
- 📋 我们从上周的模型和控制器开始,并进一步探讨一些可绑定的属性。
- 🔗 控制器监听视图和模型发布的事件,并决定库存系统的操作。
- 🔍 项目详情类在 Unity 中用于存储所有类型项目的共有属性。
- 📜 项目类表示游戏中的单个项目或项目堆栈,包含 ID、详情引用和数量。
- 🗂 模型包含一个可观察的数组,当数组内容改变时会发布事件。
- 🛠 控制器需要引用视图和模型,并使用构建器模式来确保依赖项非空。
- 🔄 控制器订阅视图和模型的事件,并确保视图在开始时是最新的。
- 🏷 视图模型类用于将数据暴露给视图,同时隐藏模型的细节,通过绑定属性简化数据访问。
Q & A
视频脚本中提到的UI工具箱是什么?
-UI工具箱指的是Unity中用于构建用户界面的一套组件和功能,它允许开发者创建和管理游戏中的UI元素。
数据绑定在UI工具箱中的作用是什么?
-数据绑定允许UI元素与数据模型直接连接,使得UI可以自动更新以反映数据模型中的变化,提高了开发效率和用户体验。
脚本中提到的'flyweight pattern'是什么?
-Flyweight pattern是一种设计模式,用于高效地共享对象,减少内存使用。在脚本中,它被用来让所有相同类型的项目共享相同的Sprite、描述等静态数据。
如何实现Unity中的项目(Item)的序列化和反序列化?
-项目类需要实现特定的方法来支持序列化和反序列化,以便在游戏保存和加载时能够持久化项目的状态。
脚本中提到的'observable array'是什么?
-Observable array是一种特殊的数组,当其内容发生变化时,可以发布事件通知其他系统组件,是实现数据绑定的关键部分。
模型(Model)在脚本中扮演什么角色?
-模型代表游戏中的库存系统,包含所有项目并管理其状态,是控制器(Controller)和视图(View)之间数据交互的核心。
控制器(Controller)在UI工具箱中的功能是什么?
-控制器负责逻辑处理,根据视图和模型发布的事件做出决策,并管理库存系统的行为,如物品的拖放、堆叠等。
脚本中提到的'view model'是什么?
-ViewModel是一种设计模式,用于作为视图和模型之间的中介,它将模型的数据以易于视图使用的方式暴露出来,隐藏了模型的复杂性。
如何使用'bindable property'来实现数据绑定?
-Bindable property是一个可以被视图绑定的属性,它通过getter方法提供数据,并通过特定的属性(如CreatePropertyAttribute)与Unity的UI元素连接。
脚本中提到的Unity的新特性'UI Toolkit'有哪些优势?
-UI Toolkit提供了更现代和灵活的UI构建方式,支持数据绑定,并且整个Unity编辑器现在都在使用这个新系统,显示了其强大的功能和未来的发展方向。
Outlines
😀 UI工具箱和数据绑定探索
本段介绍了UI工具箱的深入使用,包括库存系统的内部工作和数据绑定。演示了如何在屏幕上将硬币显示绑定到库存中的硬币,并计划每帧增加一个硬币。内容回顾了上周实现的库存系统UI,该UI在拖拽时会发布事件。介绍了系统架构,包括控制器、视图和模型的交互。详细讨论了静态数据、物品表示、可观察数组以及模型如何代表游戏中的库存。最后,探讨了控制器如何将一切联系起来,并计划在视频最后讨论模型中的绑定属性。
🛠️ 构建库存系统的控制器
本段聚焦于构建库存系统的控制器,控制器需要知道整个系统的容量,并通过私有构造函数传入。介绍了如何使用构建者模式来确保依赖项不为null,并通过断言来提供调试信息。详细描述了控制器的初始化流程,包括订阅视图和模型的事件,并确保视图与模型保持同步。展示了如何通过构建者模式创建控制器,并连接事件处理程序以处理拖放操作和模型变更。
🔗 探索Unity中的数据绑定
本段讨论了Unity中的数据绑定,特别是如何将模型中的属性绑定到视图。介绍了创建中间层(ViewModel)来简化数据的暴露和绑定。详细解释了如何创建BindableProperty类来实现单向绑定,并通过Unity属性的CreatePropertyAttribute来连接属性和视图元素。展示了如何在ViewModel中使用BindableProperty,并在模型中添加硬币数量属性。最后,介绍了如何在视图中设置数据源并绑定到ViewModel的属性。
🎮 测试数据绑定并展望UI Toolkit的未来
本段通过在控制器中添加公共方法来测试数据绑定是否有效,并在Unity中实时增加硬币数量以验证绑定功能。讨论了UI Toolkit的进一步优化,如版本控制和设置事件以通知视图更新。最后,对UI Toolkit的新特性和Unity编辑器的使用进行了展望,并鼓励观众订阅频道以获取更多相关内容。
Mindmap
Keywords
💡UI工具包
💡数据绑定
💡模型-视图-控制器(MVC)
💡可观察数组
💡Flyweight模式
💡序列化与反序列化
💡Builder模式
💡MonoBehaviour
💡ViewModel
💡Unity属性
Highlights
介绍了UI工具包在库存系统中的深入应用,包括数据绑定。
展示了如何将硬币显示绑定到库存中的硬币,并每秒增加一个硬币。
回顾了上周实现的UI库存系统,该系统在拖拽时发布事件。
详细解释了控制器如何监听视图和模型发布的事件,并做出决策。
深入探讨了模型的内部工作,包括静态数据和可观察数组的使用。
讨论了如何使用享元模式来优化游戏中所有物品的共享属性。
展示了如何在Unity中创建具有引用静态细节的实例化项目。
解释了模型如何使用可观察数组来管理游戏中的库存,并发布变更事件。
介绍了控制器的逻辑,包括如何处理拖放事件和库存操作。
讨论了如何通过构建器模式创建库存控制器,并保持构造函数的私有性。
展示了如何订阅视图和模型的事件,以及如何刷新UI以保持与模型同步。
介绍了如何处理复杂的拖放场景,例如将物品移动到非空槽中。
解释了如何在Unity中使用Inventory类作为MonoBehaviour来设置库存系统。
讨论了数据绑定的概念,以及如何在UI中显示模型中的简单值。
介绍了创建可绑定属性的方法,以及如何在视图模型中使用它们。
展示了如何在Unity中将视图模型与UI元素绑定,以实现数据的动态更新。
讨论了Unity UI Toolkit的新特性和优化,以及它们在编辑器中的应用。
Transcripts
today I want to go a little further with
UI toolkit and explore not just the
inner workings of our inventory system
but data binding as well you can see on
the screen I've bound the coin display
to the coins that are kept in my
inventory and I'm giving myself another
coin every frame we'll start where we
left off last week by covering the model
in the controller and then we'll take it
another step further with some bindable
properties it might be useful to watch
last week's video before watching this
one so I'll put a link for it but it's
not strictly necessary let's get right
into it
last week we implemented a UI for our
inventory system that publishes an event
when one slot is dragged onto another
let's have a look at these other seams
in our diagram in our system the
controller listens to events published
from The View and from the model and
then makes decisions about the inventory
system let's dive into the part that the
player doesn't see we'll start with the
details which is static data that all
items of a particular type share next an
item will be the representation of an
item or stack of items in the game the
items will live in an observable array
that will publish an event when the
contents of the array change the model
represents our inventory in the game and
all of its properties finally we'll look
at the controller and how everything is
wired up together when we're done all
that we'll have a look at binding
properties in the model to the view
let's take a look at one of these item
details in unity these are common
properties to all items of a particular
type type any item that we instance in
the game will have a reference to these
static details this is going to let us
use the flyweight pattern for all of the
items since all squids share the same
Sprite and they can only stack to one
have the same description Etc let's have
a look at this in
code if I take away all my Odin tags
you'll see there's not too much to this
really it's exposing a few properties
there that we can manipulate in the
editor but the most important thing here
is that it has its own create method
this gives us a a little Factory method
to be able to create an instance of an
item that has a reference to these
details at runtime I'm just going to
restore my Odin tags for the rest of the
demo but let's go and have a look at the
item class next so an actual item is
going to have its own ID to represent
itself in our world in our game a
reference to the details and it's going
to have a quantity so in case we need to
stack it in the future we'll add some
methods so that an item knows how to
serialize and deserialize itself for
persistence
now that we're familiar with those
things we can start having a look at the
model now the model is going to house
all of the items inside of an observable
array and if you've been watching this
channel for a while you know about an
observable list already this is very
similar so we're not going to create it
from scratch we'll just walk through it
let's have a look at the interface first
you can see that it's going to publish
an event called any value changed
will'll invoke this event anytime one of
our public methods is called it also
exposes a few properties like count and
the ability just to get whatever's
inside any particular index let's close
that up and we can have a look at the
actual implementation so there's an
array of type T we assign an empty
delegate to the action so that we never
have to check it for null its
Constructor accepts a size as well as an
ey list which could be potential items
to start the inventory with the private
method invoke will invoke the event with
a reference to the items we come down a
little bit further we can see the swap
method uses tupple deconstruction to
just swap one item with another in the
array next we have a clear method that
will empty out our array try add will'll
try to add an item to the inventory in
the first available slot if there is one
try remove we'll look for a specific
item in the array and try to remove it
next we're going to encapsulate those
events that are published by the
observable array with our own event
we'll call that on model changed is just
a wrapper for the event that's one level
deeper down so when someone subscribed
to on model changed they're really
subscribing to the event published by
the array and likewise when they
unsubscribe in the models Constructor we
can pass in some starting item details
as well as the capacity and it'll use
that information to create the new array
iterate over all of the item details
that we've passed in and try to create
and add one new item into our array now
we'll add a few public methods here that
will be operations that we can use to
manipulate our inventory array now these
first few method me don't really have
too much logic they're just passrs
really even the swap method is
essentially a pass through the combined
method will have a little bit more logic
basically it's going to Total the items
in both slots put the total into the
destination slot and clear out the
source slot and that's it for the model
we're going to come back to visit this
again when we talk about binding at the
end of the video now let's have a look
at our controller that's going to do all
the thinking and logic having to do with
our inventory system first of all this
is going to need a reference to both the
view and the model and it's going to
know the capacity of the entire system
these values are going to get passed in
through a private Constructor we're
going to create a builder for this so
we'll keep the Constructor private
normally when assembling a service like
this you want to make sure that none of
the dependencies are actually null
previously we've used techniques like a
preconditions class that would actually
throw exceptions debug. assert will do
something similar except it's not going
to Halt the operation of your program
but it will give you a nice Orange
message in your console log once we're
pass that we can assign the references
to their fields next I want to start a
co- routine that's going to do all my
initialization but this is not a mono
Behavior The View on the other hand is a
mono Behavior so I can run a co- routine
based on that mono Behavior even though
the initialized method is actually part
of a pure C Clause so let's define that
as I numerator initialize the view
already has a method defined to
initialize its own uxl document we just
need to pass it in the capacity so we'll
wait for that to finish and then
continue on by subscribing to events
we're going to fill this part in in a
little bit we'll have to subscribe to
events in the view the model and then we
can just run a refresh to make sure that
the view is up to date when we start
let's quickly put together a builder for
this we're going to pass in a reference
to our view but the model is something
that we'll create within the Builder we
can pass in our starting items here
we'll Define an initial capacity of 20
and then to enforce that we are all
always passing in the view will make it
a parameter of the Constructor we'll
have some additional methods here so
that we can pass in optional item
details if we want starting items we
could also Define a different capacity
than the default if we wanted to finally
in the build method we're going to
create a new model we can start that off
with either the optional starting items
or just an empty array then we just run
the private Constructor and return our
new inventory controller so now we can
build a controller let's connect up
those events I'll just collapse the
Builder section so it's out of the way
so the view has an event called on drop
that we want to subscribe to and handle
the model has its own event on model
changed we also want to be able to
handle that and we'll need one
additional method that just makes sure
that the view is up to date with the
model to refresh our slots what we need
to do is just iterate over our capacity
getting each item from the model if it's
null we want to set that particular slot
in our UI to empty slot otherwise we'll
pass in the details the slot needs to
know about itself which item it's
representing its icon and quantity now
just above that we'll have the handle
model change now when that event fires
it actually sends us a list of all the
items right now I'm just going to do a
blanket refresh view but you could
optimize this a little bit and maybe
just refresh the item slots that
actually were changed our handle drop
method I'll place right above that now
handle drop has to handle various
scenarios when one slot gets dropped
onto another so we'll start with the
simplest ones first and that would be
what happens if you drop a slot onto an
empty slot or you drop it onto the slot
that you actually dragged from in both
cases we can just do a swap you might be
tempted to think let's just bail out of
here if we're dropping a slot onto
itself but actually I do want all the
events to fire right now that's going to
make sure that the view is refreshed but
in the future we might have sound
effects and so on now for more complex
scen scarios where we're moving to a
non-empty slot I first want to know what
type of item this is and that is the ID
that's stored in the item details so
let's grab those IDs out of the details
and we'll just store them locally here
for comparison there's really two
scenarios here one is that in the first
case the IDS match and the item is
allowed to be stacked if it's a
stackable item we're going to use the
models combined method and just pass
those in otherwise we're just swapping
positions we'll use the swap method
we're pretty much done with the
controller logic now if you had been
looking in the repository before I
released this video you probably up to
speed on everything that we've covered
so far really we just need to come over
to the inventory class which is a monob
behavior we're going to expose to Unity
this will allow us to set up everything
we want to construct and have a
reference to the view and then inside of
unity we can link these things up and
use them to create our controller so in
our awake method here we can just use
our Builder to pass in those parameters
run the build method and we're done the
inventory should be running for
us so why don't we jump back into Unity
I'm going to hit controlr refresh my
assets and hit
play there we go we got our inventory on
the screen let's just make sure that
everything is working the way we think
it should stacking is working
great we can swap items around drag into
empty slots yeah drag into itself
perfect okay that's all the functional
we've built so far looking
good so with our inventory looking
pretty good let's start talking about
binding if I want to show a simple value
in my UI that doesn't really have a lot
of logic and just really needs to
display the value of a property that we
have inside the model one thing we can
do is binding I'm going to use coins for
this example so I'm going to add a few
styles to my USS file here so we've got
a parent container called coins it's
very simple just going to display things
in a row it has a child that's going to
be a 40x40 container with a background
image that has my little coin icon and
beside that I'm going to place a
label in our inventory view we're
procedurally creating our UI so right
above where we defined the ghost icon
let's define a coins visual element
that's going to be a child of the
inventory that coins element is going to
need two children as I just mentioned
one is going to be for the label the
label will show how many coins we
actually have so I'll just create a new
label here and I'll add it to the coins
now we don't have any property to
actually bind to this label yet so I'm
just going to put a note in here and
we'll come back to this after we've
actually created something to bind to it
is possible to pass a reference to the
model directly into the view and then
bind to a property on the model directly
but I'm going to create a bit of a
middle man here now often you'll hear
people refer to this as a view model you
see this most often in mvvm archit
textures where a view model exposes data
from the model in such a way that it's
easy to consume by the view it hides all
the details generally you use it to
expose public properties and commands
for binding to support this I'm going to
create a new class bindable property
which will be of any type t a bindable
property will allow us to bind to
Something in the model without actually
exposing the model our view model class
can contain a combination of bindable
properties and just regular properties
too let's start with the bindable
property now I'm going to make this
bindable property a one-way binding that
means I only really need to have a
getter now getter is just a function
that's going to get whatever data it is
that I want to supply now if you wanted
to have a Setter it would kind of go the
reverse so I'll just make a note here
you could add an action of a type T that
would act as a Setter it would just be
the reverse of what we're about to do
here I'm going to give this a private
Constructor that accepts the getter now
let's have a public property here that
will allow us to execute the getter
whenever we want something we're going
to add the create property attribute now
this comes from you can see up top there
using unity. properties this attribute
is going to allow us to connect a
property to a visual element finally
let's have a little static creation
method this will give us a little
shorthand for creating bindable
properties so let's have a look at how
we're going to use this in a view model
type of scenario now the view model I
want to have more information than just
the coins we're already passing the
capacity into the view we might as well
add that to our view model as well then
way we can just pass the model and we
don't have to pass any extra parameters
The View model can really encapsulate
all that data now the coins can be a
bindable property of type string because
I want to be updating the label text to
construct The View model we'll actually
pass in a reference to the model so we
can bind to it and we'll set the
capacity and then create a new bindable
property of type string and that'll be a
bind to the model coins property then as
part of the getter we'll call two string
so that means our model needs to keep
track of our coins now let's jump over
to the model and add one more property
here it can just be an integer for coins
we're close to set up now but when we're
creating our view previously we were
just passing in the capacity let's
change this so that we're actually
passing in a new view model we can just
construct it right here when we call our
initialized view method instead of the
capacity we'll say new view model and
we'll pass in the modeling capacity here
that'll create our new view model The
initialized View signature is expecting
an integer so we're going to have to
make just a couple more adjustments for
that if we jump over to the inventory
view we can replace that with a view
model now that we've changed that we
need to change the reference to size
everywhere that's going to be view
model. capacity and one more spot down
here then over in our storage view
abstract class we also need to change
the method signature down here I'll just
scroll down and change it okay with
those references all fixed up we can now
come back over to the view and actually
bind a property to a visual element to
do that we can set the coins to have a
data source of the view model. coins
this means now that coins and any of its
children have access to that property on
The View model and so we can use the set
binding method on the coins label like
so what we're doing here is binding the
label. text field to the actual bindable
property string. value that property
path is also a class of the unity.
properties namespace now notice at the
bottom here there's different binding
modes we're using two target which means
this is a getter but two Source would be
a Setter you can have two-way and
there's a couple other ones that two
target once will only set it one time
just about code complete but I forgot to
put my icon in here so let's make that a
child of the coins object as well I've
already written all the styling for that
so it should pop my icon into place how
are we going to test this thing what if
we come back over to the controller and
we make a public method to add coins so
we can just taking an amount and then
we'll update our model with that many
coins now that should be fine and out in
the actual inventory class which is a
mono Behavior what if we just add one
more coin to our inventory every frame
that way we'll know for sure if the
binding is
working let's jump back into Unity
refresh and hit play there we go coins
is just going up like crazy perfect
that's exactly what we expect now you
can use the same kind of technique for
health or for anything else the bindings
can be a little bit simpler if you want
to go directly to the model or some
other data structure but if you kind of
want to have a middleman abstraction
like this view model then the binding
property can be very useful for you
there's a few optimizations you can make
to bindings for UI toolkit like
versioning or you can also uh set events
to fire that lets the view know that
it's time to update whatever is
referenced in The Binding so far we've
really just scratched the surface of
what's possible with bindings and some
other new features of UI toolkit and
they seem to be working on it a lot it's
definitely the entire editor is now
using UI toolkit I'm sure we'll be
talking about this more in future videos
as we keep working on various projects
and things so uh hit the like button
subscribe and hit the Bell if you don't
want to miss any of those otherwise
maybe click on one of these boxes on
your screen and I'll see you there
Voir Plus de Vidéos Connexes
Save and persist data with UserDefaults | Todo List #4
Unreal Engine 5 RPG Tutorial Series - #14: Equipment System
Add a ViewModel with @EnvironmentObject in SwiftUI | Todo List #3
Model Predictive Control of Boost Converter
Create a List of Todo items in SwiftUI | Todo List #1
Chatbots with RAG: LangChain Full Walkthrough
5.0 / 5 (0 votes)