Working with Data in Unreal Engine 5 | Unreal Fest 2022

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
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]
Info
Channel: Unreal Engine
Views: 32,143
Rating: undefined out of 5
Keywords: Unreal Engine, Epic Games, UE4, Unreal, Game Engine, Game Dev, Game Development
Id: HOpyZ8552oA
Channel Id: undefined
Length: 46min 43sec (2803 seconds)
Published: Thu Nov 03 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.