Good day, everyone. I am Jack Condon. And I'm an evangelist,
working for Epic Games. I live in Australia. And it's really cool to be
able to come over to the USA to be at Unreal Fest. So hello, everyone. Today, we're going to
be talking about working with data in Unreal Engine. So what is this talk? Well, we're going to be looking
at different ways we can manage data within Unreal Engine. The focus is going
to be on holding data in ways that allow better
collaboration with designers. And, ultimately, something
that I'd like to get is better empathy as well. The aim is not only
to educate programmers on the new tools or tools that
are available in the engine but, also, potentially to allow
artists and designers to see potential workflows that they
could implement themselves or request. I guess, the ultimate
goal, though, would be to try to
understand the considerations of different methods,
in line with whatever project requirements you have,
which I'll dive into a bit later. So how to get the
most out of this talk, well, I wanted to make the point
that it's about awareness, OK? And by that, I mean, I'm going
to be moving through content really, really fast. If you don't capture a specific
bit of syntax or something like that, don't you worry,
OK, because that's not really the point of this. It's, really, to understand
the bigger picture of how all this fits together. So I guess, that's
another way of saying, I hope you've had
your morning coffee. I would encourage you, however,
to compensate for this, I want you to use your phones
during this presentation. So I've littered
it with QR codes throughout the
presentation that, if you want to go dive deep in
something, you totally can, right? What I want you to
do is conceptualize this like a shopping list,
like a shopping aisle. And anything that you want to
pick up and explore later, just grab the QR code. Put it in your little
imaginary cart. And look at it later. And, also, if you see something
that you already know, maybe, you can consider
that like bingo and just check it
off in your head. So the agenda for
today, we're going to be defining problems
and considerations when we talk about
data-driven design and how we might want
to think about data. We're going to be looking at
data tables and composite data tables-- ignore combined data
tables, that's wrong-- JSON and HTTP requests, UObjects
and data assets, curve tables, the data registry. And I'm going to leave you
with a few other resources. So what do we need to
think about when we first start a project,
and we're thinking about data-driven design? What are all the considerations? I wanted to warm up today
by throwing a few questions that you need to consider. But, obviously,
there is a lot more that we need to
comprehend when we go into the beginning
of a project. So let's have a look at some. Probably, the biggest one and
the first one I'd talk about is, how do you construct systems
that minimize onboarding? How do you construct systems
that have system transparency? And what I mean by
system transparency is, I want to talk about
a relatable feeling. When you go into
a project, maybe, after the first two months
it's already been running, you're getting an
idea of the systems. And you're like, I
want to do my job. But I also don't want
to break the build and be in trouble by everyone. And so, what we want to do is
make tools that can communicate and, also, make people
feel safe in that space and productive, right? We want to look at ways we
can see big data relationships and the big picture
all at a glance, right? We might have to factor
in testing, automation, validation. And we probably
should be, as well as scalable data management. Does it work with 10
assets, cool, 1,000? Yeah, great, 10,000, 100,000,
a million, so on, and so forth? And if we have all those
assets, maybe, we're thinking about content
versioning and management as well, right? Another concern
you may have is, do you need to represent data
externally to your binary? Do you need to represent
profiles on a web page? What about integration
to platforms? Another one that's important
is user-generated content, packing-- patching mods, DLC. How are you going
to scale updates in your game or project? And, finally,
cooking time, if you have lots and lots of
assets, what are some things that you can do to,
ultimately, deal with that? So what's the answer
to these questions? Well, there is no right answer,
as, realistically, a project is a combination of all
of these questions and probably a lot more. The best way I think is to be
prepared and aware of tools that you have available and,
of course, communication. But I'm going to be focusing on
the former of those two points, trying to give an
overview of all the best tools that you have available
to achieve your outcomes. So with that
understood, let's dive into our first amazing tool. It's one that, probably,
folks are already familiar with, or at least the UX of it. And that is, of
course, the data table. So the data table is the
most relatable data type for designers, as it
allows looking at data akin to, like, a CSV
or a spreadsheet. It's got a tabular format. That's what we call that. Basically, it allows USTRUCTS to
be ordered and stored in rows, and that these rows can be
then accessed by row name. But it's worth noting that
we can also address data as individual rows in
both C++ and Blueprint. I would say, its
most magical ability is that it allows importing
and exporting of JSON in CSV. And this is amazing, because
it allows designers to bring their own processes into that. A lot of designers
and a lot of people really like working in Excel. They have agency over
that space, right? So this tool can create
pipelines between that and. With JSON, we can easily
construct other tools in other programs as well,
if there is a need for it. It's also worth noting,
it's a single binary asset. It cooks all together. So how do we go about
making a data table? Well, it's pretty simple. First, we need to start
by describing a struct. We're going to add a little
thing to it called the parent type of the table row base. And we're also going to add
Blueprint type, in order to use this struct in
Blueprint as a variable. And that's it. We're good to dive into the
Editor after doing this. Now, this can be done with
Blueprint structs as well, it's worth noting. But I'm not going to show
that to preserve time. I guess it's worth noting, I'm
going to be showing Blueprint where available and,
in other times, C++. But if there is a
Blueprint implementation, you can assume that
there's going to be a C++ implementation. It's just not enough
time to cover both. And the other thing is-- that I wanted to
say is that we're going to be working with
this structure a lot. It's like-- I guess, it
describes an item from an RPG, something like that. But when we're talking
about all these concepts, I want you to have an open mind. It's not just like
an inventory system. It could be abilities. It could be cinematic data. It could be anything that
data can be in your project. So just try to see
how this can be applied in many different
scenarios, right? So how do you make a data table? This is pretty simple. We just go to Miscellaneous. We create a data table. We define the-- we select the
struct that we used before. And we're good to go. We can start entering data. It's as simple as that. So it's very intuitive
to work with. And we can see that
tabular format there. I just wanted to show, as
well, how we access those. So, basically, we can make a
variable type called dataTable. And what that will do
is, it will give us a dropdown list of all the data
tables in our project, right? Which can be overwhelming if
you have too many data tables, but I'll get to that later. From there, we can
access that data table by grabbing a row name and
getting the data out of it. So here, I'm getting all the row
names, giving them a shuffle, and, basically, just
printing the item name. So I wanted to show you that we
can also handle individual rows in a really intuitive format. So I'm going to use a data
table row handle to do this. And it's very similar. We select a data table
just like before. But now we have a
list of all the rows that are actually
in our data tables. So this is a really
intuitive way to work. We can be very specific
within our large data tables. And we can see that
that's copied over. I wanted to show you what
that designer experience might be like. And I'm using a Editor
Utility Widget to do that. Forgive my design skills. But these are assets from the
action RPG that I've assembled. And I guess, this is a point
that I'm going to make later. But I wanted to allude to it
now that, we can represent data with Edit Utility Widgets. And what that means
is we can actually change the view of
how we look at data. Right now, this is a
read-only widget, right? But there's nothing
that would stop me from actually making that a tool
that, then, I could construct for development, right? So I did mention
filtering before. I want you to look at these
two examples of data tables. One has five data tables
in the list, this one. I think it's five. It is. It's also got a game
tags table in there. But I don't want that. What I want is only
inventory items. I want to be able to
communicate to the design team, and programmers as well,
that this variable is going to be used for this struct. Don't try to utilize
it in a different way. And to do that,
it's really easy. All I need to do is
add this meta tag, RowType and then write
out the struct's name. So let's have a deeper
look at data tables. So data tables allow us
to really easy compare rows of data in a tabular
format that should be familiar for anyone, right? They also handle important
export of JSON and CSV. And this is awesome, because
it allows external development from designers, or
external tools development, I should say. However, just be really
careful with this method, because it creates more than
one single source of authority. And what I mean by that
is, if we do an import, and then we do a bug fix quickly
before a release or something, in that data table,
and then someone else goes and changes the source
data, and they re-import that, you're not
merging that data. You're actually going
to be overriding it. So you need to bring
your own processes when dealing with this. And one other point,
it's quick to work with. It's easy to add extra data. And there's not much onboarding. So that can be really
appropriate in some scenarios. But there's no inheritance
support on the struct itself. And this can be tricky
for some solutions. I want to flag this again. Why is this so good? Well, not only are
you-- if you've got a lot of data
tables, are you reducing the need to scroll
through giant lists of things. But it's about that
communication of intent. It's about, also,
reducing human error. So I'd really-- if there's
one takeaway on data tables, I think that's one of
the really good ones. And, of course,
something that I'm going to bang on about
like a broken record when I talk about data is that,
be careful of hard references in data table. This can lead to a very
explosive source of load times, OK? We try to load our
data table, and it references the rest
of our game, we're going to have a very bad time. But they're also
one user per edit, because it's a single
binary file, right? So that can be
problematic, because it means that only one designer
can work on that data table at once, right? However, we can use
composite data tables to get around that problem. So composite data
tables is super easy. All we need to do is-- in this example, I've got
a data table of three rows. I've got another
one with one row. And what I'm going to do is
make a composite data table by Misc Composite Data Table. I'm going to have to still
define the struct type. That should feel
pretty familiar. But after this, I'm able
to add all the data tables I'd like to merge together. And what you'll see is
it'll populate a list with the different sources. So composite data tables allow-- work identically to a
regular data table, in terms of reference in accessing data. So we don't even need
to update our code, if we wanted to move over
to a solution like this. But it also enables us to manage
concepts and add communication to where an item might
be found, rather than one giant mega list, right? In this example, I had
the term "Rare Items" and "Misc Items"
as my data table. But I think you can see
how that concept could apply in a lot of
different scenarios. But I think, best of all,
it allows multiple people to work on these
data assets at once. So one designer can
work on one data table in one area of the game. And someone else can
work on another one. And you get much
less collisions. What if data needs to
be changed post release? What about LiveOps? What about if we need to access
our data from a web browser and a built binary, for example,
like, maybe a live scoring system? Well, that's where HTTP
requests come into play. So Unreal Engine allows us to
make HTTP calls to external web servers. And this all works in
Editor and at runtime. So here's an example of the API
we're going to be using today. It's from catfacts.ninja. Catfacts.ninja's goal is
to share random cat facts about cats, which is something
that I find very important. I guess, resources
like this one are really good for
just understanding how these systems work. It's designed to prototype. What we're looking at here
is the open API specification that will help us know
how to actually interact with this API. And here, I've made a
Editor Utility Blidget. It has three ways of accessing
data from catfacts.ninja. We're going to be looking at
each one, then, pros and cons. So the first one I wanted
to start with is the C++ implementation. And we're going to
be using a Blueprint async action-based parent type
to handle the HTTP request and do a type conversion within
that node to a UE struct. So Blueprint action async
bases, despite being very hard to say
live on stage, are one of my favorite
classes in Unreal. They're a really fantastic way
to handle latent tasks, as they both encapsulate the
code into a single node, and they provide a
callback back to Blueprint. To make one, we're going to make
a parent of Blueprint action async action base, where we're-- and when we're done,
it'll be represented as a node in Blueprint
with that little clock there that tells the user
that it is a latent task. So-- sorry, there we go. So from there, we'll need
to add some simple overrides and functions. Starting with activate, this
is where we start the task that we're performing. In this case, it's
a HTTP request. We're also going to need a
static Blueprint callable function with input
parameters to make it accessible in our
Blueprint project. Additionally,
we're going to need a multicast dynamic delegate
to return the information when we're ready. In our static function,
all we need to do is create a new Blueprint
async action base of the class and then configure
its parameters. In Activate, we're
going to actually set up our HTTP request. So this is exciting. You'll see verbs and headers. And people who are
familiar with HTTP requests are probably all over this. We're then going to
bind the delegate to return the request when
it's done and kick it off. Finally, we're going
to process our request and desterilize a JSON data. So we're going to be using JSON
to object, to automatically convert our JSON object
into a USTRUCT, which is really, really awesome. To do this, however,
we need to set up a USTRUCT that replicates
the open API schema we will provide it exactly. Then Unreal will do the rest. So it's worth noting, this also
supports nested JSON objects. You just need to
make a new USTRUCT, and name it appropriately,
and plug it into your USTRUCT. So what can we learn about
the C++ implementation? Well, I just wanted to plug
a Blueprint async action base again. It's a really fantastic
node, when you need to do these latent functions. But it's awesome,
because we're also able to handle the JSON
marshaling into a USTRUCT within the node itself. And this means we're
not exposing JSON to our designers, which
is a personal preference. So the JSON object to USTRUCT
is a really useful way to do these type
conversions, while keeping the schema structure of
the original JSON the same. And it even works with
those nested structs. So it's very powerful. However, what I
would say is it can be really painful to write
out all these types in Unreal Engine, especially,
with large APIs, right? And it can also introduce
a lot of human error. So I just want to say, finally,
my experience with web APIs is that they seldom
remain the same. Even if it's an
internal resource, there's a lot of shift in them. And I think, because
of that, what will happen is that your data
models will go out of sync. And JSON object to USTRUCT
will ultimately fail, right? So one approach could be to use
the deprecation functionality in Unreal. But you might want to consider
a more indirect approach to marshaling that data,
where you only take on what your game exactly needs. And you separate
those two models. Next, we're going to look at
the Blueprint implementation. It's the same as the
other HTTP requests. You can see a
Blueprint node here. It's got our favorite friends. We've got verbs. We got headers. We've got a URL. We're putting the parameter
directly in the URL, in this case. But what you'll notice,
with this example, is we're returning
a JSON object. And with reference
to our schema, again, we're able to
work out that we're able to get our
very important cat fact by referencing it by its
JSON name, in this case, fact. So the Blueprint implementation
is super available and easy to use for designers or
anyone in the team, right? And this makes it perfect for
prototyping and testing APIs. And it's awesome that,
now, Jason objects are supported in 5.1, which
means that we can prototype and send JSON information to
other programs super easily. But I wanted to make the point
that, potentially, consider marshaling to more UE-- conventional UE types,
rather than staying in JSON, once you have your
request response. Like, a Blueprint
static function library could be a really
good way to do that in a Blueprint-only
project, for instance. And, of course, you'll need the
new plugins enabled on there, on the screen. Next, let's look
at my favorite way to access web server
information, Web API. So Web API is a plugin
available in 5.1. In this example, note
that I've already got an async node available to us. And note that it's also
returning a struct similar to our first C++
implementation, right? But guess what? I did all this without
writing a line of code, which is very, very cool. How is this possible? Web API, so all I need to
do is download the schema and drag it into my
UE Content Browser. And this will make
a new Web API asset. So I'm going to download it. We can have a look at that. It's basically
just a JSON struct that defines all the functions,
and what they return, and what we're going to
expect, et cetera, et cetera. I drag that in. And, bang, there
is our new asset. So the window on the left is a
representation of that schema API, the Open API schema. And on the right is our Unreal
types and implementation of all those functions. All I need to do is
generate, when I'm ready. And there we go. I've got the full
schema available to use. So why use Web API? Well, I think the biggest
thing is it's combining those advantages from C++, with the
speed of working in Blueprints. It also avoids the need to
write out all those structs. And this is a huge timesaver. And it's, obviously, going to
reduce human error as well. It's probably the
best format, now, to allow testing and prototyping
easier than ever, right? And it's worth noting that,
because you're making these types in C++, you're able
to reuse them in the rest of your project. So you get that
portability as well. So with these
toolsets, I hope you can see that working
with external live data is easier than ever in 5.1. As a bonus tip of information,
for the IoT enthusiasts, I wanted to point
out, we also have an experimental
implementation of MQTT, which is another common web format. So Blueprint assets, let's
get back to other ways we can hold data
in Unreal Engine. This is an alternative to
data tables, if you like. They're often sometimes
called raw UObjects. You might have heard that. Basically, it's
a Blueprint class that inherits from UObject that
we don't intend to instance. Rather, we want to trade
in the default values, OK? It's simple to use and extend. It's really powerful, even
in Blueprint-only projects. And just to note,
the design patterns that I'm going to
be showing today are accessing the assets
default properties. But you can instance them and
do really powerful things. It's just outside the
scope of this talk. So to make them,
it's pretty easy. We're going to make a
new Blueprint class. And we're going to
inherit from UObject. Then we're going to name it. And we're going to
add some variables that we'd like to fill it with. In this case, it's going
to be our inventory struct that we started
working with at the beginning. This class can be
considered our base class. And we want to make child
classes of this class, in order to start allowing
us to fill in some data. In this case, I'm going to
make a sword and an axe. In regards to our
data table, each one of these individual
UAssets here can be considered as a row of
data, if that makes sense. And, of course, the same thing
can be done with a base C++ class as well. Let's have a look at how we can
reference our fancy new items. So to do this, I'm just going
to make a regular all kind of consider this, maybe,
like a backpack or a loot box in the game. And what I'm going to do
is I'm going to use a class pointer to our base class. Now, this way, we'll
be able to reference child classes of this type. But we're going to be really
professional in this case and use a soft class pointer. And this means that, when
our actor we're working with is loaded, it will not
be automatically loaded by the reference
Blueprint asset, OK? So let's go ahead and
make a quick function here that resolves this off pointer
and, ultimately, gets the data. The secret sauce here is
that getClassDefaults. And if we have a look
at the Reference Viewer, I want to be really clear how
the soft pointer thing works. So we can see, in
the Reference Viewer, I will have a reference
to the base class, because we're resolving
that soft pointer, right? So we're always going
to have that reference. However, any Blueprint asset
in the data that we care about will be a soft reference
shown in the Reference Viewer as a pink line. And so, we can see this
when we add a reference to our sword or our axe. The pink line means that it
won't be loaded automatically when we load that base actor. So here, we can see what
it's like for designers to use this type with
our favorite little UMG representation. This should look
pretty familiar, just looks like selecting a
texture, or a static mesh, or something like that. But you might notice
that we've still got this base class over here. And that's not good, because
we don't want that to ever be used in actual production. So what we can do is we can add
this Abstract Class tick box. And that means that it will no
longer show up in that menu. We can do that with subclasses
as well, as needs be. And we can do this
in C++ as well, with the abstract
class in our UClass. Now, your designers
might be upset, because they can't
look at the data all together in a tableau view. But don't worry, because
we have that covered. The Bulk Property Matrix
can provide this insight. So what I'm going
to do is I'm going to add the variables
that I'd like to see in my tabular format. And then I'm able to sort and
look at all this data at once. And not only that, the UX
experience is, in some ways, better, because I'm able to
edit it directly in line. Or you'll see here, I'm able
to select a group of one type and, actually, update all
their variables at once. So this is a really
powerful way to work. So let's enrich what we've
learned about Blueprint assets. It's worth noting that
each UAsset is cooked. And it's going to
require memory. And if you have a
lot of these, it might end up adding
to your cooking time. Also, consider that we do
have all those advantages of the tabular format with
the Property Matrix, however, with some limitations
between the two with the Property Matrix
and the data table. They're a little bit
more time consuming to set up and can
be a little bit more intimidating for some
designers to step into. But they support
inheritance, which is useful, as you might want to change
the class as it moves through, for certain solutions. But the coolest
thing about this, actually, is data inheritance. So we're familiar with
material instances and the way, if you make an instance
of an instance, it will copy over those
properties, right? And if we change the base
class-- the parent class, it will replicate down
to the child class, unless we override it. Well, we can do the
exact same thing with the structure, which can
be really efficient for design. I'd also say, of course,
consider tooling over the top of this with
edits Utility Widget, so details customization. This is only a base pass
of what Unreal gives you. And, once again, I really
think it's important. Unlike a data table
that's quite static, people can add variables here. And that's great. It's great to give
agency to people. But at the same
time, they can also end up adding a lot
of hard references. I really think
it's worth teaching the entire team, everyone about
the concept of soft references to scale for this. I'm seeing some smiles in
the audience as I say this. I think it's reflective of
some experiences, potentially. And unlike data
tables, they also support instance properties. And I'm going to be going
into that in a moment. So the next topic I
wanted to talk about is one of my personal favorites. It's very much
like the BP asset. Only, it's purpose built
for holding static data. And that is the data asset. I guess the name gives it away. They work very
similar to BP assets. But they're totally purpose
built. And they live on disk. They're cooked. And then they live
on the disk, right? They're loaded on demand
as they're needed. And they also
support inheritance but in a slightly different way. We can do this at a class level
but not at a per data instance level. And I'll go into
that in the examples. But, ultimately, they have
a much cleaner workflow, meaning, the experience for
designers is more transparent, which is something that we're
always trying to achieve. This does come at a cost. And you'll see that when
we talk about, maybe, you can't override a function
within that specific set of data. But you don't
always want to make these really complex mechanisms,
just because you can. In a lot of cases, this is the
perfect class for your project. So let's go ahead and make one. We're going to make a new C++
class that parents from Data Asset. And then we're going to
give it some properties. In this case, it's going to
be our inventory item struct. Because we've come to
know and love that struct. And then we can go
back into the Editor, after I write out some code. I find that I'm much faster
at writing code and compiling in video format, than
I am in real life. I don't know how I do that. It's a little trick. So if I go over
to Miscellaneous, and I go to "Data Assets"-- this magenta color, maybe-- then, I'm able to select the
class that I just generated. And that's going to make
myself a data asset. And I'm ready to
fill it full of data. So this is really
similar to the BP asset, in that we can look at
it like a row in a data table. Again, it's that concept. So let's go ahead
and use this asset in a new actor,
similar to our chest object we were
playing with before, or backpack, whatever you like. And you'll see that
I've got a problem here. I don't know if anyone
saw the C++ code. I made a mistake. And I actually did
this while recording. But I decided to leave it. I can't find this
asset in the Editor. Uh-oh, so what I
missed was I forgot to put Blueprint type
in the UClass, which will mean that this is now
accessible to Blueprint as variable. OK, so I'm happy now. I'm going to make-- I want to reference this class. I want to get some
information out of it. I want to show what
that's going to be like. And you'll notice
that we're going to use an instance of
this data asset so-- rather than a class pointer. And that's because,
in truth, we're not really able to
instance this data asset. We're more pointing to
information on disk. So think about it like the
getDefaultsOnly from before. But it's happening
under the hood. Now, we could use a instance,
and just reference that data, and load it all in. But I'm going to be
very professional. You might notice here, I'm
using a soft pointer again, a soft object pointer
to reference that data and avoid automatically loading
this asset when it comes in, very important. So what's great is
that the designers can see a list of all
these data assets. We don't need to worry
about abstract classes. It's very simple. It's very functional. It's very easy to use. Of course, you can
also do that async, if you're feeling really clever. So here's our Editor
widget experience again, to see how that looks. It's very similar. We don't need to worry
about the abstract classes. But it's very similar to picking
a texture or whatever else. We can also add
functions as well. So in this example, I'm
going to add some getters and restrict access to
that struct directly. So I'm just going to
generate some code there. And then, back in Blueprint, I'm
going to have to fix that up. And that's really
powerful, right? So getter is pretty simple. But I can actually do
some fairly complex things that will be revealed
in the Blueprint side. They also support
Blueprint diffing. And this is really important. When you're working
on a big project, you're trying to balance
a lot of different data, this view will actually show
on the left, all the changes. We're able to step through
them and compare values. And it's worth noting
that we can also diff BP assets as well. So in this view, we're
able to compare, yep, that we've made this item more
expensive for whatever reason. And while we're on
the topic of diffing, it's worth noting that we
can even diff data tables. But, of course, the format
is going to be slightly more intimidating. So let's look a bit closer
at our data set assets. I want to say that they've
got most of the advantages and considerations
of the BP asset, only that they're
purpose built. Therefore, they're much clearer
to work with. They require minimal amounts
of C++ to get up and running. And unlike data tables-- but unlike data tables,
they don't natively support importing and
exporting of data. However, you can add
that in by building your own factory or other tools
that you might want to use. This is also the case
of BP assets as well. And they do support inheritance
but not at that kind of level I was talking about,
when we're talking about the instancing of data. We can only add to
that class base. So there are some
limitations there. But what I would say
is, designers love them, because you can change
their values in pi, while you're actually
playing the game. And it will update,
which is super useful when you're trying to find
the right value for something. As well, they support
diffing, which is great. We saw that. And, once again, I'm
just going to plug-- this is the base point. Really, I would always
consider, can we use a Editor Blueprint
to actually change the experience of how we're
looking and augmenting that data? Use software references
wherever possible. Sorry, every point, it's
going to keep coming up. But absolutely, this
is just like BP assets. You really want to be
educating your team on this. And like BP assets, they also
support instance properties. But what are those? So instant properties
are an amazing way to add properties or actions
to your data arsenal. Essentially, it allows
us to attach UObject to another object and
edit its default exposed parameters in line in the
Details panel right there. So you can see, in this
example to the right, we are specifying a class. And then that-- we're able to
access the class properties and set them in
the Details panel. And, additionally, because
it supports inheritance, it allows to do some
really neat design patterns that I want to show off today. So here, we have a
type that supports two options in the dropdown. We've got "Single Item." And we've got "Stack of Items." And when we select
"Single Item," we can see that we have
more properties to access. In this case, we've
got a reference to our data asset of items
that we've been building. We also see, if we
select stack, we have an extra property that
has propagated into our UI. And that's because
"Stack of Item" inherits from the "Single Item"
class and adds a extra N32, called "Amount of Items." Our data asset that contains
this instance property does so by adding the
UObject-derived class with the instance
UProperty specifier. The classes it's
referencing has a little bit of extra magic in its set up. Do take note of its
UClass specifiers. We've got DefaultToInstanced. And we've got the main
one, EditInLineNew. This indicates that the
object of this class can be created within the
Unreal Editor Property window, which is the exact
behavior we're after. I've also used a
display name, which I think is really
good for this type, because it means that we can
display that in the dropdown menu for designers. It's all about communicating
the tools' intent, once again, with normal language rather
than a class reference. And we've got a note. We've got a reference
to our asset as well. So we can see that a
single item, therefore, allows us to add a reference
to one of our items that we've been making. But let's have a look
at "Stack of Items." "Stack of Items" inherits
from our previous UObject but adds on that in 32. And what's really
fantastic here is that we're creating
module options for ways to propagate data. And this works on array too. So I guess, in this instance, by
using array, we've accidentally made a chest of items, or,
like, a backpack full of items. So you can see that here. I'm going to add another
type, wonderful, wonderful, wonderful, very good. So let's look a bit deeper
at instance properties. They're really fantastic for
adding optional parameters to structs and objects,
like the example. They're really good
for lists of actions. And a great example of this can
be seen in the modular gameplay framework here on
the right, where we can add a whole bunch
of different actions that we might want to perform. But they don't work
with data tables. And this is because,
they need to live on an object in the game, OK? So there is that
limitation there. They can be really useful for
creating chains of references or a tree hierarchy. When you get them to
reference themselves, you can create node-like trees,
which is really interesting. But it's worth noting that
the object that's instance will be fully loaded
when its owner is loaded, so very powerful tool. We've been looking at ways
of storing data for a while. But I wanted to talk a
little bit about how data is represented to designers. So meta tags can be used to
change the way information is displayed, through showing
and hiding details or enabling and disabling input. They can be used to
hold extra information about a UProperty in general. And you could use them
to store information on your own systems. An example of this would be
tagging a bunch of UProperties on a data asset with
a certain meta tag. And, then, you could go
through and grab them all out, if they had that mega
tag, to actually generate a report or something. But be aware that you can
only do that in the Editor in vanilla UE. In this example, we
can see that the struct amount is only visible in our
inventory system, has a stack. Therefore, we're
decluttering the Editor with unneeded information. And to do this,
it's pretty simple. We just want "Edit Conditions"
and "Edit Condition Hides." You can see in "Edit
Condition," we're referring to a ball that's
pointing back to a ball that we're using in the struct. Inline balls are
really useful as well. They allow us to put the same-- they allow us to put the
ball on the same line as the stackable item count. And what's really cool
about this is we're now forming a relationship
between those two properties. We're communicating our
intent much clearer. And this is pretty
simple as well. We just use "Edit Inline
Condition" toggle. And then we point back the "Edit
Condition" to that ball again. So how does this help
designers and teams? Well, they provide an
opportunity for your tools to communicate their intent. They also provide
basic data validation by controlling access
to data and, generally, improve the UX for designers. And as mentioned, you can
also create your own meta tags for use in the Editor. And this is really powerful
with your own Editor Utility Widgets, as you can access
that data or their Details Customization. But keep in mind,
once again, you're going to need to modify
the engine just slightly, if you want to
use that in build. And just speaking
about data validation, I want to plug this
amazing plugin. What plugin, the Data
Validation Plugin. So the concept here is to stop
issues before they happen. We're all human. We all make mistakes,
even me once. The data validation
plugin allows designers feedback as they work. Have they made a mistake? Can we identify that? Can we tell them? Well, we probably should. And what we want to
do is stop issues well before they go on to push
their work to a team. Or we can even
put a check inside of build pipeline to actually
ensure all the custom rules sets are valid. Have you ever wanted to
store a bunch of curves or allow designers to
balance scale properties external to the Editor? Well, if you didn't, you
will want to now, I hope. Let's me introduce
you to Curve Table. So, basically, they
allow a list of curves. They also support importing
and export of curve data from external tools, just
like data tables, OK? They're very similar
to data tables. They support linear,
constant, and cubic curves. And you can create combined
curve data tables, just like-- sorry, composite curve
data tables, just like composite data tables. So you can scale them
that way as well. You can also access curves
from a curve data table, just like you can
with a data table. So we can access a specific
curve in the same way. So here's an example of making
a curve table inside the Editor. We can find a curve table in our
miscellaneous section in 5.1. We'll need to specify a
linear, constant, or cubic. And then from there, we have
a very intuitive interface for accessing our data. And here's an example of
us importing this directly from CSV. So importing is the
same as a data table. Only, we need to tell Unreal
that this is going to be a data curve table instead. Then it's going to be like,
linear, constant, or cubic? And I'm going to be like,
linear, because I like it. And that's it. After this, we are still able
to modify the data in Unreal. Unreal will even suggest
values based on interpolation, which I think is really cool. But do be aware that
you have that issue, like before, with a single
source of authority, just like data tables. Additionally, curved
data tables work really well with scalable floats. Basically, this is
a type that allows the combination of a
float and a data curve row that will allow you to both
scale and preview the output. So this is really great when
you want consistent scaling of a curve in a project,
but at a per asset change or difference. So let's have a look
at them together. So what I want you
to do is notice the float value multiplies
against the curve data and provides a preview slider. See, we can slide
that up and down. We can change that value. And now going to
double the preview, and so on, and so forth. They're really,
really quite cool. So accessing information
on them is really simple. We just need to provide
the level or the x-value of our curve, in this case. So data curve
considerations, well, I think they're really great for
values that scale in your game, especially, when you want
to get feedback of values over x value, in this case. But they also allow the
importing and exporting of data, which is
a really great way to work with this
format of data. So you may have noticed that
some items in our example haven't had their
item type filled. And, maybe, you haven't. But this is starting
to really bug me. So I want to fix that right now
with our friend, Gameplay Tags. So, basically, they
are like FNames that are globally predefined. And they allow a easy
selection inside the Editor UI. So you can see up there that
we've got this UI interface to actually select the type. And, basically, this
avoids human error. But they also come
with some bonuses. Like, we can have
tag containers that allow us to have more than one
type of tag in a single type. They also have an
inherent hierarchy. So we can find
relationships between tags. For example, we can
say, is this a child of this tag or something? A lot of people don't know this. They're also supported
the Reference Viewer. So an example of this is, we
could look them up and say, what are all instances
that have the acid FTag, or something like that, and see
all the objects in our project. They also support ray directors. If we do need to
make changes, we're not going to break the build,
which, of course, is important. And they can be baked on to
index IDs for replication. So they can be really cheap
in a multiplayer game, in this sense. So let's look at an example
of gameplay tags in use. Here, I'm going
to make a variable with the type, Gameplay Tag. And it will allow me to select
tags from the tag hierarchy. Next, I'll make a tag container. And this type will allow
me to select multiple tags. So as well, I said
the gameplay tags come with a bunch
of useful functions. And in the example
I'm about to show, I'm going to compare the tag
to the gameplay container tag and, basically, see if
it's in that container. Gameplay tags are also super
easy to add to your project. All you need to do is make a
data table of gameplay tags. And then you can add this
in your project settings. They can also be added
into your ini as well. So I'll show an
example both of these. Basically, here, I'm
making the data table. This is a bit dry. I'm doing it as fast as
recorded Jack can move. And I'm going to add it
into my project settings. And yeah, that's great. They're probably there. So that's really cool. But from here, I can also add
them directly into the ini, just by writing them out and
clicking Enter, simple as that. So let's look at
an example where I've tried all to use everything
you've seen here today at once, just to
give a bit of context about how these
things go together. So here, you'll see that
we've got our familiar struct. And I'm filling out
that asset data. We got a texture. We got some stuff. But then, I'm using instance
properties over here. And right now, I've got this
variable called Charged tags. So maybe this weapon
has some magical powers. We've got a charge limit. I'm using scalable
float for that. I've, then, selected another
type, which is damage, right? So I'm actually adding in
this data as I might need it. Then I'm going to add another
one that's static mesh. And I'm not actually suggesting
you should make your items or inventory like this. I'm just really trying to
present how these things might flow together. So the Asset Registry
is the last thing that I wanted to talk about and
one of our most powerful tools and features that we
have at our disposal. They not only help with
system architecture, but they are also
implicit UX improvements for designers working with data
and the Editor in the asset-- with the Asset Registry. So the Asset Registry
can be seen as metadata about an asset themselves. Take a look, for
instance, at our example here, where we've got this
variable "Max Players." And, then, over here on our
tooltip in the Content Browser, we can see that coming up. And that's what the
asset registry is doing. And you've probably
seen this before. For example, when you
hover over a static mesh, you're able to
say its try count. But the Asset
Registry is not just for generating
cool tooltips, even though that is quite awesome. We can use it at runtime
and in the Editor to load and filter data. So in the example
to the right, we could use this query to query
what map would be appropriate for an eight player
match, without actually loading the map or extra data. So let's look at a case study. Here, we're able to visualize
metadata by hovering over our beloved inventory object. But that's not only
what we can do. We can actually filter in
the Content Browser as well. So I can be like, item
cost equals 10 or 50. And it will show
that, because now it's part of the Content
Browser's search system. And to implement this,
all we need to do is add
AssetRegistrySearchable tags. But this only works
on types where a direct conversion
to AssetRegistry tag actually makes sense. So in this case, I'm not
using the struct anymore, because how would you
define a struct as a tag? You're going to
need to define that. And in order to handle that,
like structs, or in fact, any arbitrary tag you'd like
to add, all you need to do is override
GetAssetRegistry tags. You want to include a call
to the parent function to grab the parent. And then we can add
any tag we like. In this case, we're just getting
the item names for the struct and letting UE know that
it's alphabetical type. But we can also do it
with numerical data so, like, item
cost in this case. And we can use that for
sorting in the example there. Here, we're going to
use the data registry to load in some of our items,
based on a simple tag check. Let's have a look
at how we do this. With a reference to
our asset registry, we can get all
actors by class, OK? But the class is
not actually loaded. You'll notice that we've
got this blue pin instead. And that means that
we're just looking at the metadata of this asset. We're then going to compare its
tags against the gameplay tag. And if it's one
that we want, we're going to get the
soft asset reference. And we're going to load
that into UMG over here. So we can look at
the data table, as well, in a tabular format
within the Content Browser, with the Column View. And we can also sort this data. So here's our
tabular view again, that we can look to sort assets. And then we can use filtering
in order to further refine that. The asset order-- the
Asset Audit window can also do a really
good job of this as well. So this is really only
scratching the surface. Let's have a look
at what we covered. It's a super quick library
for referencing data. We can use the Content Browser-- we can use the Content
Browser as a way to view all the data at
once in a Column View. Or we could use the
Asset Audit window. We can control how we
find data and objects with certain metadata at
runtime or in the Editor. And so, this is really
useful for DLC content, for instance, where, if your
logic for finding what items are appropriate at any one
time is going through the asset registry, well, then
if you add extra data assets from a downloaded
or external source, they're going to get
referenced and pulled in, without changing your code. Final note, the Asset Registry
is async on the Editor. However, it is synchronous
in a game build. So, sometimes, when
you're building tools, you just want to
be aware of that. So what's next? I'm going to conclude here. I would say, follow
up on these types. Understand their
strengths and limitations. Be open to finding the right
solution to your project. If you're really interested
in customization, then, I'd check out Details
Panel customization, to go into that further. And if you're interested to
get more out of data tables, I'd have a look at the
new Data Registry plugin. And, ultimately, if
you to learn more, Lyra is a really good source of
seeing how all these things can fit together. And, additionally, you
can have a closer look at the Gameplay Ability
Framework or the Gameplay Modular Features plugin. I guess, I wanted to close
with this thought, though. If we're making systems,
if we're making data, we always want to
be empathetic to how that data is going to
be represented, as well as fit in the system. So I think that's what we
really need to think about, when we look at all these types,
and how they fit together, and how they're
represented, in order to save huge
efficiencies on projects. And, ultimately,
it means that we're going to have a better
time developing. So with that, thank
you very much. I don't think we have
enough time for QA. But I'll be around at the
festival, at Unreal Fest. And everyone have
a wonderful time. [APPLAUSE]