Programming Subsystems | Live from HQ | Inside Unreal

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
>>Amanda: Hey, folks, as always, we hope you have had a fantastic week! For Blocktober, the month-long celebration of level design in games, solo developer Gwen Frey shows the evolution of the newly released, 3D puzzler Kine, from core concept all the way to shipping. In her write-up, she covers four distinct phases of development, with insights from each phase that may help you in your own Dev cycles. Check out the blog and accompanying videos. Between stringent regulations and months of laborious training, getting expert ground crew and mechanics up to speed is not easy for the aviation industry. U.S.-based Inlusion, along with FL Technics and Baltic Ground Services, aim to ease the burden with regulation-perfect VR training that eliminates the danger of practicing on real aircraft, saving time and money in the process. On September 25th, over 300 professionals from the architecture and engineering and construction industries convened for the biggest Build: London for Architecture event yet. The speaker showcased and discussed innovative ways real-time technology is being used for AEC projects, including visual twins, immersive design, urban planning and data visualization. Get a full recap on our blog today. And now, for our top weekly karma earners -- MMMarcis, chrudimer, Clockwork Ocean, Shadowriver, Crisp Clover, ThompsonN13, LordStuff, pasotee, T_Sumisaki and Adnoh. These lovelies have been helping folks out on AnswerHub, and thank you all so very much! Alright, it is time for our spotlight. Here we have Flynguin Station, a casual projectile endless runner. Your goal? Save the penguins by rediscovering the secret of flight. Fly as far as possible, level up, upgrade your penguin and customize him to your liking. For our second spotlight, you will see a VR realization of transportation methods from the past built for Vienna's public transit company. The models were created based on hundreds of pictures of the actual real-world vehicles, and then worked into a guided virtual tour. Inspired by popular titles Risk of Rain and Faster Than Light, One Last Planet is a 2D action platformer, sprinkled with narrative decisions and RPG elements, with difficulty that ramps up as time passes. Fight enemies and monsters on the ground and in space today on Itch. Thanks for tuning in. See you all next week! >> Hey everyone, and welcome to the Unreal Engine Livestream. I am your host, Victor Brodin, and today I've got Chris Gagnon here, Lead Editor Programmer on the Engine team, and we are going to talk about subsystems. >>Chris: Awesome. Thank you very much. >>Victor: Nice to have you here. Happy you were able to take some time out of your day to prepare a little presentation for us. >>Chris: Yeah, we'll do a bit of a slideshow here, and then we'll jump into a code example, and then probably do Q and A. Subsystems are not that complicated, really, so it should be relatively quick. >>Victor: Hopefully. We'll see with the questions, and we'll try to answer all of them as we go, and do a bigger, yeah, more Q and A at the end. >>Chris: Okay, awesome. I've got a slide deck up here, and we'll just kind of talk about what subsystems are. So basically, subsystems are automatically instanced Classes with managed lifetimes. So it is kind of a bit of a mouthful, but we'll break it down a little bit. So automatically instanced means exactly that; they are just automatically created here, the instances of the Class are created for you automatically. And the number of instances kind of depends on the lifetime you choose, and we'll kind of get into that later. But basically, you don't have to build the infrastructure to create the instances of your Class; that is kind of part of the value of subsystems in general. And then their lifetimes are managed. So depending on which lifetime you choose -- and we'll get more into that -- but you will be created automatically, you will get initialized and De-initialized along with that lifetime that you've chosen. And as I mentioned, multiple instances will be created, if that makes sense for the subsystem type. So let's dive into kind of like the next things, like, what are those lifetimes? So the lifetimes you can choose -- and I've kind of broken them into two different groups here -- game instance subsystems, local player subsystems, and world subsystems. So those are all kind of your game-centric subsystems, for the most part. And then we also have Editor and Engine subsystems, which most people probably wouldn't use that much, unless you're doing extensions to the Editor, or really low-level Engine sort of systems. But they exist, and they do have utility. So these are the lifetimes we are talking about. When I say a lifetime, let's take game instance, for example, here. You know, I'm going to create my subsystem; it's going to derive from UGameInstanceSubsystem. Then when game instances are created, your subsystems will be created along with it. They will be initialized, and then De-initialized prior to the game instance being destroyed. So that is what the managed lifetime is all about. And this is really nice, because you don't have to worry about, well, where is the right place for me to initialize or De-initialize my subsystem? You don't have to worry about deriving from the game instance subsystem to -- or sorry, the game instance itself to add your subsystem internally, which is something you commonly have to do if you are working in C++, to add those types of modules or systems into the Engine. So we'll probably get back to a lot of that stuff. But I think we'll just kind of jump to a code example here, to really kind of show it directly in place. So I built a little example. I did this kind of yesterday, so apologies if there is any typos or anything silly in here. But you know, it's really as simple as this. I've created a new Class. Basically in this particular case, I just started with the third-person template -- no sorry, first-person template. I was, like, okay, well, what do I want to do? I'm going to make a score subsystem. So I decided that I want to have a small system that would basically, you know, keep track of your score, I had a bit of a concept of multipliers in here. And then I went ahead and kind of built something out of this. So just to give you a sense of what is required in the code, it's really this right here, right? You are creating your new Class, you're deriving from one of those lifetime Classes, so all these other Classes. UGameInstance subsystem -- that is just part of the Engine now. UWorld subsystem, just for clarity, is actually a new one. It's not in 4.23, but will be in 4.24. So I've now inherited from this. I have a couple of simple functions I can override here. For the most part, you don't have to do a whole lot with this parameter. So this is given to you for some other complexities; we can talk about those later. But all I really did was, like, I took my initialized couple of variables I keep, De-initialized, and I cleared out some delegates I had in here. So really, all I provided was a bit of API for incrementing a multiplier, adding score, and getting the total score. Then I had a couple events that I declared here. So one is for when a score is at a particular amount of score, so you do something that gives you 100 points, this function will get called. Then we also have another function for when the score changes. So and this time, it only gives you -- anything can happen that can change the score, and it gives you back the total score. So those are the two events I put together. Now, as you can see, the code is extremely simple. There's a couple of things for increment, adding score. And then this is where our events get called. I called both of them. You know, the idea here is that maybe in the future this becomes more complicated; there are more factors in my score system, and that's why I have two different events. Also, the use case of them are slightly different when we look at the other side. So in general, I've created an extremely simple system here. And if we jump over to the Editor, whoops, shift-O -- nope, is there not a key bind here? >>Victor: Looking for the Content Browser? >>Chris: Yeah. >>Victor: It is on the third tab there. >>Chris: Perfect. So we'll just take a look at something called SimpleScoreTarget. So the thing that the subsystems give you, on top of the fact that you don't have to create that wrapping code to create it and manage its lifetime, you also can get them in Blueprints very easily. So we've created a bunch of custom nodes to do that. So if we just zoom out here a little bit, so -- >>Victor: You didn't have to add any specific metadata? >>Chris: Nope. Just deriving from that Class is enough. We use basically the Unreal Type system for detecting derivatives of those key Classes. Then we generate -- we basically factory up all these accessors nodes. So for example here, I've the score subsystem, and I've called those Add and Increment. And we can kind of go through this Blueprint more later. But I just want to show that. So if I right-click, and I say -- I search for subsystem here, and you can just see, GameInstanceSubsystem, GetScoreSubsystem, and any number of subsystems you might have in your game would show up and be very easy to add in this way. >>Victor: And any Actor has access to this, right? >>Chris: Yes. Well, let's talk a bit more about that. Depending on the subsystem you've created, you need to have the context to understand how to get it. Generally, that context is fairly simple. Blueprints basically always know what World they're in. Whether it is a Widget Blueprint or a regular Blueprint -- they all basically understand what World they're in, or what, in the case of Widget Blueprints, they understand what player owns them. But the bottom line is, all of those can easily get at the global context, and understand how to get at their subsystems for most things. So for example, the game instance -- I mean, there is only one game instance sort of. And that's why subsystems kind of help you. So there's only one game instance, as far as your game goes. But if you are in the case of the Editor, and you are actually doing multi-PIE or something like that, it's actually more complicated. There are multiple game instances; there is one per kind of running subgame inside the Editor. So that is one of the reasons these types of things kind of help you out. These are the types of complexities you don't have to worry about. But they have context, and have context to know what game instance they're in. They know which World they're in. So getting a World Subsystem or a game instance subsystem is really simple. Local player subsystems -- those ones are a little bit more complicated in that there might be multiple local players. Like, if you had a local multiplayer game. And in that case, what it does is you have to just pass it in like a player controller. So local players aren't actually a Blueprintable Class. So you don't really have access, those are kind of a bit more of an underlying thing. But they have a nice simple lifetime, and they only exist on the client, so that kind of simplifies a lot of stuff. But we can only map to a local player from a player controller, which you can get in Blueprint. So in the case of a subsystem that you put on a local player, you would simply be, like, get my subsystem that lives on a local player and you pass in the player controller as kind of the context it needs to be able to find that. >>Victor: I think we even had a screenshot of that in the documentation and the announcement. >>Chris: Exactly. The documentation kind of shows all of them. It doesn't have anything for the World stuff right now, because like I said, that is coming new in 4.24. But yeah, it gives you an example of some of those different ones. But for the most part, it's really simple in Blueprints to get. And it's really nice because -- and one thing we'll see here with this example is, you know, this, for example, this simple score target is literally a cylinder I made. It does some stuff, you know, it's like a target. We're going to shoot it in a minute when we play the game. But the other thing I've done is, we can just kind of minimize that guy for now, and say over here, I made a little HUD as well. If you take a look at this HUD widget, you know, basically this guy has a couple of text blocks -- believe me, it's not that impressive -- but again -- where is it again? So this guy, what he does is, he actually listens to those two events we had. So we registered the score subsystem for the on score change, and this is where we update our total score text. Here we did something a little bit more complicated, but basically registered for the score added event. We grabbed the score subsystem to bind that event. When the event gets called, we get the multiplier, because I basically built a little -- you guys can see that -- a multiplier times score sort of formatted text, and then a little bit further down I color it, just for fun. So the idea is that, you know, I have this subsystem, and it wasn't on a particular Actor. One way of doing this would be to make a particular Blueprint that would manage all this type of stuff. Then you would throw that into your level, or something like that. Not only do you have to remember, all my levels have to have that particular Actor in it for my game to function properly but now everyone has a Get at that Actor. So maybe you have, like, a static Blueprint library that remembers it to make that easier. Or you're looking up an Actor by name -- all these types of things that are more work, first off, and also just a little error-prone. >>Victor: But you have to remember and do it every time, right? >>Chris: Exactly. So a subsystem gives you, once you define the code, you know, you're kind of already in business here. It allows you to really encapsulate that functionality. Subsystems are relatively new, so there are a lot of things in the Engine that, honestly, would be better represented as a subsystem, like if you look at GameInstance, you will notice that there's a bunch of stuff about replays, and there's a bunch of stuff about a number of different other things. In the end, it's, like, wow, this would be a lot cleaner if we took those things and separated them out into different subsystems. Maybe we'll do that over time. But the idea being that, you know, if you are starting fresh from a game especially, if you think in the terms of, you know, these are my different subsystems, these are the different encapsulated pieces of functionality I want, it can really help you organize your code really well. It does require C++ to use subsystems. We don't really have a way of defining a subsystem directly in Blueprint right now. But that said, if you're doing in Blueprints, you could use Blueprint libraries and things like that. But that's what subsystems are kind of meant for. But the idea here is that in C++, which, you know, has benefits over Blueprint, right, it is easier to write code, it is more performant, it is generally easier to debug in a lot of ways. The iteration time is slower. That's where Blueprints definitely win out. But oftentimes, you know, when you're developing a lower-level system, the things you iterate a lot on are the stuff you're seeing in this Blueprint, right? What does my layout look like? What is my text going to do? What effects are going to happen in animations? Those are all things that happen off of events with my subsystems. So in my particular score subsystem here, you know, I basically said, okay, I have a relatively simple score calculation. I have a number of access for some mutators for that. And here are the events that tell the rest of the World about things that are happening. So not only have I created a good event-based code, where I am not ticking and pulling into things, or doing anything kind of bad for performance in that sense. It's also just kind of -- you've set it up such that under -- like on the tail end of all of those events that you expose, and this is kind of just good programming in general -- I've exposed those events where now people can iterate for days on the look and feel, and stuff like that. But that all happens kind of in Blueprints, where that's really good for iteration, but that core code is very protective, kind of thing. It's kind of set in stone. Which it will change over time, don't get me wrong. But the rate at which that change is often much, much slower than, say, the Blueprint type stuff. So not only have you set yourself up for good performance and easy debug-ability of what might become a very complicated system over the lifetime of your game, but you've kind of really split the line there of, hey, here is where all of the kind of like the look and feel -- you know, the niggly stuff is. And here is that core system that has a set of rules, right? This is the API that you can touch. These are the events that occur. And you just set up event-based stuff. Now all that other code that is dangling off it doing stuff is now event-based, and you are not worrying about the performance implications of that. Obviously, if you have a game that starts doing score every single frame, maybe, then we have to go back to our system, think about deferring things, and doing other performance stuff. But that's kind of, like, next level. The idea here was simply, like, all of that's one place. Then if you think of it on the larger scale, the whole project, the entire game, you have many subsystems and they can all kind of talk to each other. But it really helps you encapsulate that code very well. And there are some sub-- there is very few subsystems shipped with the Engine right now. One, for example, and we didn't see, if you will notice when I pulled up the context [INAUDIBLE], because it was an Editor subsystem for plugging into Asset importing and exporting -- this is obviously kind of off on the side, but that's another subsystem we have. And if you use Editor Utility Widgets, you would see those, and you right-click and try to add a node, and you would see the Import Subsystem there. It's just filtered off, because it's Editor only. But we do have, you know, minor use of it in the Editor. And that will continue to grow over time. Subsystems are still relatively new. But they're definitely meant, you know, in a lot of ways, for people writing a game to be, like, hey, here are my key systems. let's encapsulate those and kind of keep them sacred, as it were. >>Victor: They're in full release, right? They are not in a beta stage? >>Chris: They're full release, yes. >>Victor: That's what I thought. >>Chris: I mean, it's one of those things that there's not a lot to them in general. And that might grow over time, or it might add more lifetimes that you might be able to register to them. Though I think we've hit the big ones so far, especially with the addition of UWorld, which was a pretty important one that didn't make the initial release. So we're happy to have that one now. Then kind of the one extra thing -- so let's just kind of quickly go back here -- since we're talking about the deep use of them, we'll just kind of go back to the -- just make sure I don't -- >>Victor: I think that, you know, you need to know the fundamentals before you start diving into making decisions of how you are supposed to build the framework for your game. So I think it's good that we're touching on that. >>Chris: Yeah, and we've touched on some of these things. I just kind of have a little list to remind myself of a couple of points to mention here. But we've touched on a bunch of stuff, right? So it saves programming time. This kind of comes back to, I don't have to write all that connective tissue code. Modularity and consistency -- that's really about the idea that, okay, well, a subsystem gives me a home for a set of functionality, right? If you have -- if you were to just put all of this stuff on GameInstance, it ends up being, well, my GameInstance has a score system and a save data subsystem, and a spawning my target subsystem. Who knows, right? >>Victor: Yep, and now we have four people trying to work on different versions of -- >>Chris: Exactly. Right, exactly. And now you have the possibility. Because they are all in the same place, everything is accessible, right? Like if you're a coder who is trying to make a nice -- a robust system that is defensibly coded, you are making a lot of decisions about, these are my public pieces of API that people can interact with. And this is my private implementation. I put all of those things in one Class, and your friend, who is writing, you know, the save system, might poke into your private data to get something done -- didn't really realize what he was doing, and now he has kind of broken the rules about your stuff. And now you can't logic about the state of your system well. I understand that people are going to call these functions, and these are those things, those functions, and someone else has kind of come poking in underneath the hood, and changing some stuff around. You know, that's a source of bugs, right? So the idea of them being real, separate, compile-able elements helps protect you against that. The other really nice thing here, and we haven't touched on this yet because we are kind of focusing on games a bit -- is if you are trying to make Plugins, right, whether or not it's because you're making Plugins that you want to share across your own games, or you are making a Plugin to put on the Marketplace, or anything like that, you know, it was always the case that you would probably release some code. Like, I've my super-system that I've released, and everyone is super excited about using it. And there would be this disclaimer, right, where it is, like, okay, well yes, download it. Add the Plugin, then take magical Actor and put it in the level. Or take -- you know, instance up one of my system's managers and put that in your game instance, and make sure you call this function on it at this time. There's those things, right, that they kind of come in -- maybe you have a demo one, but then the user is, like, okay, well, I already inherit from GameInstance. So I've got to go and figure out -- put that code from my game, right? So that is a common thing, right? With subsystems, that's kind of a thing of the past, right? The fact that they've included the Plugin means that in our type database, we know of, you know, in my particular case we use score subsystem, so that gets created automatically. It is just there, because they have included that Plugin. It really makes that a lot easier. And because you don't have to force them to put it in, there is just a whole bunch of bugs that can't happen. That people can't mess it up, or put initialization in the wrong place, or do it before or after culling, you know, the super of a particular initialization. You know, all those types of common issues you might have for someone who is just, like, oh, this should be simple, throws it in, and then it is, like, oh, why does this not work? And then forum posts and questions. >>Victor: It also provides something that people might be familiar with, right? So they've used -- they started with subsystem, they used one Plugin, and someone else releases another one. It is, like, oh, I know how to get these references. You know, I know how to run these functions. >>Chris: Yeah, and that talks about the consistency, not just in your code base, but across code bases, right? Like, the idea that if you generally separate things this way, people understand. It's, like, okay, well, I am looking for some set of functionality. Well, generally I start with a subsystem. Like, one issue that I have in Blueprints is that I right-click, and the node list is very, very long. We have a very, very busy global name space, right? You know, that's something, hopefully, we can improve over time. But certainly if you're writing your game with subsystems in mind, you know, like this very quick filter where it is, like, oh, I know how to do everything subsystem, I see my subsystem, I get to the right subsystem. And from there I am doing a contextual drop of a function and I'm not [INAUDIBLE] That's the one kind of thing that Blueprint libraries sort of struggles with, right? Because whenever you create a Blueprint, it's real nice because I can get at it really easily. But now it is part of that global name space. And it is very, very busy, and can be difficult for people to find things. >>Victor: We were mentioning adding custom to one of the function names. The difficulty of them. Someone who is not familiar with our function, accidentally adds that one and goes, hey, what is this? You know, I am looking for something different. >>Chris: Yeah. And the thing is, because it is consistent on the way to get it as well, you know, as I showed kind of in my example, which we should get into more. But in that example, you know, I have UI that was getting some information, or registering some events. And I also had a number of different Actors, I'll show you that I've more than one Actor. It's all very simple. There wasn't any extra stuff. I only made things from my game in the Blueprints, in just that subsystem I wrote over in C++, was just available to everything in a very simple way. Just before we move away from this, I guess, is there anything else? I mean, I'll mention quickly, I guess, that you can access your Python scripts as well, which is not really untrue of any other Blueprint stuff. As soon as you expose something to Blueprints, it's effectively exposed to Python as well. I'm not sure if many of the people watching will use Python often; it is a scripting language we use in the Editor only. >>Victor: We did a stream a couple of months ago with Aaron Carlisle and Jimmy Dale, yeah. >>Chris: Oh, awesome. Awesome. Yeah. It is a great system for -- generally for larger teams, or whatever, but people create customizations to the Editor, and more and more with Editor Utility Widgets in our exposure and more stuff. You can start writing Plugins for the Editor that utilize Python and UMG to create new tools and stuff like that. It's an area that we're still fairly young in, but we are really growing to try to allow teams and different groups to really customize the Editor in a lot of ways. But the one thing that the subsystem gives you is, again, that ability to access a thing. Because just exposing something to Blueprint doesn't necessarily mean you could get it in Python easily, because you needed to get an instance of it somehow. So sometimes that's the missing glue code, and you get that for free with subsystems as well. So we'll just jump back to the Editor here. And we can look at these Assets later if you want, but we'll just kind of jump into PIE here and kind of show you what I did. Basically, I had the idea here of -- I think this is a small amount of score, yeah, like 10 score. And this is, like, 100 score, this increases my multiplier. Oh sorry, this was a loss of score. >>Victor: You don't want to hit that one. >>Chris: So the idea on this one is, like, if you just take a look really quickly over here, this is the simple score guy. Like, it is literally a cylinder, and I just hooked into when it gets hit. My game is obviously not very robust. You can walk a Character into these and trigger them as well, probably not what you would want in a final game. But all I really did was, I exposed a few there, base score, multiplier, and connecting color, and kind of created a -- I created a little mid, so I can change the color of it. Then I have this thing, it's just, like, oh well, if the base score is above 0, I intended for it to be a score increment. Otherwise, use the multiplier. Very, very simple. That kind of drives those three different targets you saw there. Then I kind of went and did something a little bit more complicated -- pull that up here. Because it is super complicated, I called it "complex." But so in this guy, very similar logic here. When it gets hit, it looks at some of the -- I basically duplicated this from the other one, and then kind of went and changed it. But basically, this one can actually do both. So it does a sequence and says, okay, well, if I have a valid score, I add some score. Score can be negative, by the way, we'll see in a second. But and then if the multiplier is non-0, then I also increment the multiplier. So then I have this idea of a Change State. Basically what I do in Change State is, like, I come in and I choose a random number, and then I basically decide if this is going to be a green, positive score thing, purple, I think, is our incrementing your multiplier, then red is some sort of loss of score. And that one, I used a little switch here to say that that's three times as likely as the other two individually. Then I basically set a timer again. Then they basically change between one and three seconds. That's what is happening here. You can see that it's actually running, and that is because, if we turn around in our little level here, I got, like, a little mini game over here. [LAUGHTER] So it's, like, if you shoot the purple ones, you know, the multiplier goes up. And they shoot the red ones, they lose some score, right? People can do this, they end up back at 0. I can try to get my multiplier a little higher and wait for, like, a green thing. Shoot a green, get some score. Then obviously, since they're changing all the time, you mess up, and then back at zero, just like I did. So, you know, there's no connective tissue here at all, right? I've shown you all three of the Classes. I did make it a game, right? It is not much of a game, obviously, but you know. I have a simple target, a complex target -- presumably those could have been actually inheriting Blueprints that shared some code. I didn't do that here, but they're really simple. Then a very simple widget that gives us this little bit of UI up the top corner that shows us the score. There's not much to it, but, you know, I didn't have to write a game instance, really. I didn't have to pass information through my player controller. I didn't have to do a lot of things that you might have to do, you know, if you need to pass data around. On top of it all, it is completely a venture. I mean, I am using a tick -- not a tick, but a timer in the actual -- in this complex one here, just to run this little kind of gameplay logic, and all these things. But again, that is not a tick. So performance wise it's actually pretty good, right? It's only coming in there once every one to three seconds per target, I guess, circle. Little not-textured cylinder. That's kind of it. I think one of the hard things with subsystems is figuring how to use them. Not so much the nuts and bolts I just showed you, because that's relatively simple. But, you know, I have a game I want to make. It's a game with all these features. How do I -- what should be subsystems? What should be Components? Subsystems are not replacements for Components at all. Actually, UWorld subsystems that you're going to get in 4.24 are a great way to pair with a Component. We actually talked about an example just yesterday, where imagine you're making a racing game, and you had checkpoints. Well, checkpoints are a thing that you may very well want a Component on, right? They need to live on a particular Actor type, and they live around the World, so they have location. They have other interesting information. But if I want to -- you know, had I had a game where you're racing, and I wanted to track your positioning, I might want more of a global place to store that, not only because I don't want the different checkpoints to try to talk to each other, they need to kind of talk to a manager to keep track of what's happened and make sure -- they might do arbitration about what one is the next value, like one that's enabled and things like that, right? All that sort of orchestration of something that does live in the World, that does make sense as a Component, might be better as a subsystem, right? Because those Components can very easily be, like, hey, get me the World Subsystem of type -- you know, in this game our checkpoint subsystem, and say, hey, you know, register myself. Or the user just passed through me, you know, whatever. It might then say, oh, go talk to the score subsystem and give them some score, or take some score away, maybe. But the idea being that this is not a replacement for Components, but works very well with Components, in these cases where, you know -- And this is very true of a lot of gameplay. You have a lot of different elements, or maybe even a multi-map, right, where I've different types of checkpoints, or different types of Actors that all talk to the same subsystem. >>Victor: Right. For some reason they might not even be inherited from the same base Component Class, right? And then they would still have access to the same piece of information. Something else I thought of, I was just curious if it might be a good solution for a subsystem, would be, like, a projectile pooling system? That would work really well? >>Chris: Yeah, it could work for that, for sure. I mean, this is the idea that I have a bunch of bullets, bullets are expensive to make, for whatever reason. So I create 100 of them, and then the gun or guns in the game say, hey, I need a bullet because I am about to fire, and it goes and asks this thing for it. Well, how could it do that? I could create an Actor, right? And I could have that Actor have a bunch of children that are all hidden, and whatever. I could do it that way, yes. But then my gun is like, okay, well go get me the named Actor of my bullet manager, whatever. And then do stuff, and it works. But it's certainly not as clean as me having a subsystem that knows, you know -- it can even be more generic, right, where it's, like, oh, I am a pooling subsystem that knows how to pool that Actor, and this Actor. >>Victor: Just for Actors, or Components, yeah. >>Chris: Yeah. It can do all kinds of stuff. Then you say, hey, get that subsystem, hand me off one of these things, and then use it, then you can release it back into the pool when you're done with it, or that could be the bullet itself might release itself back in. Because remember, in that case, it's, like, I have a gun, who might do the acquisition of a bullet. The bullet itself -- the gun might not know or care when the bullet hits the thing but the bullet does. So when the bullet, you know, impacts, does damage, it plays a particle effect, does whatever, and releases itself back into the pool, right? Now I am already talking about my gun Blueprint, maybe, or my gun Class, whatever it is, and the bullet Class is both talking to this subsystem. Well, that's where subsystems are really starting to help, right? They make that easy and clean. So now I have -- that's two subsystems in our game; I've got a score subsystem, I've got a pooling subsystem. Maybe I add a Character customization subsystem where, you know -- or my account subsystem, or that I store what I've unlocked and things like that. Who knows, right? That's what is hard about making games, right? Figuring out all of the pieces you need, and that stuff. Subsystems are definitely a tool to help you accomplish that. They fit well with the idea of encapsulation of one particular kind of set of functionality. You know, we have shown a really simple example here, but really the sky is the limit with it. There's very little overhead to it. Because in the end, they effectively work exactly how you do it anyways, they just do it for you. And, you know, there's a little bit of overhead to looking it up, so whether or not you are doing it in C++ -- like you can, for the record, you can add all this in C++. I could write a C++ subsystem that says "get other subsystem," and then does things with it in C++ or whatever, or a Component written in C++ that does the same thing. They are fully accessible in C++ and in Blueprint. But -- where was I going with that? But the idea being that there is a tiny bit of cost in looking it up, right? You get the subsystem, and it has to look it up in a map by type, which is pretty fast if you are in particular code that you're really worried about the performance of it, you might want to cache that. So in the initialization of something, I get the subsystem, hold on to a pointer to it, then access it directly from there on. You have to be a bit careful with that, but remember one of the things about subsystem is, it's supposed to manage lifetimes. So you know what the lifetime is of particular types of subsystems. So as long as in your situation you're within that lifetime, holding onto a pointer is fine. They are not going to move around on you. >>Victor: You meant grabbing the data from the subsystem, and then caching it inside the Blueprint that -- >>Chris: I was actually talking about caching a pointer to the subsystem in this case. >>Victor: Oh, okay. >>Chris: So you don't have to do the map lookup. We're talking about relatively small gains of performance. But if there's something that you had to look up at a very high rate, that might be worthwhile to you. But again, just an example of when you might want to concern yourself with the lifetimes of the subsystem. But for example, like if I had an Object in the World that doesn't -- a Component maybe that need a subsystem, it can probably cache that pointer, if you thought that was best, performance-wise. I mean, it lives in a UObject or it lives in a World, it should live throughout that lifetime. There's some edge case here, so you might need to protect yourself. But those would only be like tear-down cases and things like that, not functional cases. So you might have to protect yourself from it for crash reasons. >>Victor: Okay. >>Chris: But like, basic checking, for a null pointer. Just for future reference. But you would never really have to worry about that in an actual runtime case, because you are in a World that exists. The only time that it wouldn't work is if you didn't have the subsystem for some reason. Which is pretty much impossible, in that the fact that you can compile that reference means that the subsystem exists. That is not true in Blueprint, right? Because obviously, if I change C++ code, for that code to ever take any effect, I need to be able to compile. And that basically checks the fact that that Class is known. In Blueprints, you could have a Blueprint that referenced a subsystem. You would then disable that Plugin that has that subsystem in it, for example, and now that Blueprint will compile. That's one way you could kind of break data. It's no different than you deleting some API or using a Blueprint, but those cases can happen. >>Victor: And caching it just simply, it's basically just getting a reference through the subsystem and promoting that to a variable, right? >>Chris: Yeah. >>Victor: Now you have it cached and that's, essentially, the only lookup you are mentioning. >>Chris: If you're in Blueprints, I really wouldn't worry about doing that. >>Victor: Okay. >>Chris: Definitely not. I mean, you're talking about optimizations that would only make sense in small, very, very tight loops that, you know -- and you shouldn't be doing that in Blueprints anyways, if you want performance. If you're in C++, then maybe caching makes sense. I would never worry about caching them in Blueprint. That just -- it's not worth the gains. >>Victor: Cool. let's see, could you take a couple of questions, maybe? >>Chris: Yeah, absolutely. >>Victor: Yeah, we actually -- we had a couple of questions initially, but we pretty much talked through all of them already. >>Chris: If there's anything we want to re-cover, or go in deeper, we can do that, too. >>Victor: Yeah, for sure. We definitely got time. Let's see here. "Is it ever intended to allow subsystems to follow tick groups? Or are they always going to be late in order? In some cases, it would be useful to target a subsystem to an actual group." >>Chris: So subsystems don't tick on their own, so you would have to register a tick. You can basically register whatever ticks you need. Generally speaking, registering tick groups -- I don't -- I'm not sure if there's API to directly register to a tick group from C++. Worst case scenario, you could route that through an Actor that had a tick group specific to it. It's not very nice. That is something we might be able to grow in the future. But a lot of times, if you want to do stuff like that, you can definitely kind of create a -- what do I want to say here -- like a manager to deal with that. Let's take a step back here. I think that in this particular question, you want to talk a little bit of what your intent is, right? There are cases where -- I think the user is right, you know, being in a particular tick group means that you are going to tick relative to other Actors, right? Because tick groups are really about when Actors do things, right? Being able to kind of input there, into the right place in that stack, makes a lot of sense. I think that is a valuable thing to be able to do. But there's also a question of, well, maybe it's just that I want my subsystems to be able to do things in certain orders. At that point, you could write code that actually calls a set of functions in a different place. Or another subsystem to do it. You could imagine having my sort of -- or even GameInstance could do it. But some code somewhere that literally just calls a number of functions, or a number of inter-leaved subsystems, like there is nothing keeping you from just writing that code. Or having kind of the idea that a number of dependent processing in Actors or Components that are scheduled in some sort of tic group could call into a subsystem, and have that subsystem do a thing at the right time for them. It kind of really depends on the specifics of what you're trying to accomplish, for me to kind of give the suggestion I think would be best in that case. >>Victor: Alright. Perhaps we'll get a follow-up as we go through the rest of the ones. So this person was wondering, "so subsystem are like Components on an Engine level?" >>Chris: Yep. They very much are. That's why they're not a replacement for Components, right? Components are Components of Actors, right, whereas subsystem are sort of like a Component of the different lifetime. So a Component of the Engine or Editor, GameInstance or local player, or the World. They're not called "Components," we didn't want to have that confusion. Components, terminology-wise, tend to be very tied to ECS systems, [INAUDIBLE] component systems, Object Component systems. So we steered away from that naming. But for all intents and purposes, yes. But that said, Components are a pretty high-level idea, right? The idea of Componentizing, right, any functionality or sense of function. >>Victor: Yeah, competition -- >>Chris: I mean, that's exactly what they are. And they work very well that way, especially with Plugins and modules and things like that, where including the code in, you know, plugs in those Components for the right places. Then you get that sense of functionality. So that's very astute. >>Victor: Are there ever several instances of the subsystem? >>Chris: In the cases of, like, local player, and stuff like that. Only for lifetimes that have multiple instances. So GameInstance -- well, GameInstance is, again, a little bit complex. But in one game, there is only ever one GameInstance. So there is only one -- they are basically singletons. There are, you know, in local player, you might have multiple local players. And that would be multiple -- on subsystem each. So if I had my personal stat subsystem, I would put that on local player, and that's where I would count up how many points I got. Or whatever, like obviously, in my game I used GameInstance for the score subsystem. But, you know, if I wanted to make this couch co-op, or something, I could put one on both local players, and they could score separately. Or I could make my score system be a GameInstance subsystem, and I pass in the scoring player into the API that I make, so that internally I can track multiple players. That might be better if you want them to be tied together, like you want to have, you know, be able to look at the score of both players separately or together, or be able to munge the data different ways, then it might make more sense for it to be one subsystem that happens to organize its own data store for -- >>Victor: Maybe using a T-map or something, to -- >>Chris: Yeah. Or, you can even have a hierarchy of things, where it's, like, you have a local score subsystem on each of the local players, that stores data, and then you have a global score system that knows about those other subsystems and they register in, and then it fields the data from them. You can go as crazy as you want; it really kind of comes down to the complexity of the system you're writing, right? Like anything, as something grows, the value of being able to split it up into different parts grows as well. That's kind of up to you, based on the complexity. The one other case where you might sort of have a multiple instance sort of subsystem is, subsystems don't have to be leaf Classes. You could create an abstract Class -- and you will have to mark it out as abstract, otherwise it will be instanced for you -- but I could create, like -- oh, we don't do this currently in our code, but we could adapt it to be this way. But for example, like source control. There is Perforce and GitHub, and all these other things. I might want to have a base subsystem that defines a bunch of API, and then each of those different source control systems would implement a concrete version of that subsystem. Then you would get one subsystem per source control type. That's one example of a case where you might actually have multiple of the same type. There is only one of each leaf level type. But there is, for all intents and purposes, there's multiple of the base API. And there's API on all of the things that provide subsystems. You can get -- there is list-based one, where you can get all the subsystems of type and pass it of type and it would give you all of them back. >>Victor: That's very nice. Yeah, and we have definitely got a couple more here, so let's keep going here? >>Chris: Yeah, absolutely. >>Victor: Alright. Let's see, oh I guess we might just have covered this. "Is it possible to have multiple instances of a subsystem? Or are they intended to be more like singletons?" >>Chris: Yeah, I mean, just to reiterate, it just depends on the lifetime. Like I said, game instance ones are effectively singletons, except in multi-PIE. That's the one kind of caveat there that there are multiple game instances when you're in multi-PIE. But yeah, they are effectively singletons for game instances. Editor and Engine ones are definitely subsys-- sorry, singletons, effectively. They're not actually real singletons in the C++ coding sense. They're still an instance Class that has a lifetime and stuff. But they are, for the most part, set up once, and only thrown away when the Editor goes away, or the game engine goes away. Most can be useful, especially Editor ones. Obviously you are doing extensions for the Editor, and you want some system to manage something. Engine ones, you know, would be fairly useful, especially if you are writing, like, a Plugin that adds some sort of lower-level functionality to the Engine; something that you would want to work in-Editor and in-Game, as opposed to something that's in game instance, which only exists when the game Engine exists. For example, like if I stop PIE here, right? Stop PIE, my subsystem is gone. It just got destroyed, right? Because the game instance that was running in that PIE window got destroyed, and in turn all the subsystems went away. If I had an Engine subsystem, it would still be around. So you have to decide what you need. That has problems right? I mean, if you're working on a game, you probably don't want to make Engine subsystems, right? Because you don't really want -- because I would run PIE here again, and my Engine subsystem would still be around. I'd have to worry about data polluting back in. But maybe I have something that's low-level enough, like I'm managing save files, and for whatever reason I want it to be an Engine subsystem because I want to be able to cache that data for the next run, just to make PIE start up faster, or something. You know, there are reasons. Especially if you're running at lower-level Engine features. But when you are thinking about game functionality, you would probably want to avoid Engine -- yeah. >>Victor: Maybe more using a, say, game Object, or -- >>Chris: Yeah. Yeah, I mean, there is definitely constructs for some of these things that are probably made more direct. I was just thinking of something that might have value in living beyond PIE, that's sort of game-centric, still. >>Victor: Maybe for virtual production or Editor since when you're usually working in Editor -- >>Chris: And for that, in those cases they're often using the Editor itself. So even an Editor subsystem might work there. But yeah, there's a lot of different use cases, for sure. That's kind of like why we have -- what is it, five or six now? Yeah, five. Five different lifetimed -- different lifetime ones. >>Victor: "Can subsystem be replicated? Can it be shared between server and client?" >>Chris: Not intrinsically. Replication is something that's kind of Actor-centric. Which is maybe something that changes in the future. But as of right now, basically -- replication only happens on Actors. So subsystems can't naturally replicate. It's something that maybe we look into, but it's certainly not on the roadmap, currently. Right now, with respect to replication, you would definitely need to kind of forward a lot of that stuff through a replicated Object to get that type of functionality, which is unfortunate, for sure. It is not the cleanest [INAUDIBLE]. But if you go back to the types of things that have subsystems currently, most of those aren't things that actually live on the server, anyways. Local players only live with one local client. GameInstance lives on the local client as well. Editor and Engine, obviously those ones live everywhere. But so there's options there, either by using other things as your kind of transport mechanism for that type of stuff. Or just doing it a bit more manually within your subsystem. There's no reason why a subsystem couldn't listen for World creation, create an Actor, and then utilize it as a transport layer. Like, you don't have to -- you can still hide away all the complexity. Avoid having to -- oh, we need to go make special Actor in level, otherwise game doesn't work -- you know, you don't have to worry about that type of stuff. You can still kind of do it all. But yeah, there is no intrinsic replication support in subsystems currently. >>Victor: Let's see. "Is there going to be a good tutorial on making the code to create these subsystems?" I would point you to the documentation. There is a -- >>Chris: There's a bit there, yeah. I think that this example is -- you know, we can kind of show that code again, if we wanted. But bring it up, but yeah, it's pretty simple, right? It's just a UObject in this case. But you derive from a particular Class. The tutorial on the website, just search for "Unreal Subsystems," and it's basically going to be your first link. Kind of takes you through the different lifetimes we have, as I just mentioned, UWorld is not on there right now, but it's basically the exact same thing. Just now you go from UWorld subsystem. From there, it's just a Class. Other than the initialize and De-initialize, there is very little API. We can actually just jump up here real quick. Oops, that is not what I wanted to do. Oh, we don't have the -- Well, in any case, I can just talk through it. But Usubsystem has very little API, relatively. So the other real piece of API it has, and it has a default implementation and you don't have to worry about it is, Should Create Subsystem. And that's handy for some things, like for example, you might not want to create a subsystem on every client, for some reason, or you might not want to create it on -- if I was making an Engine subsystem for some reason, but I only wanted it on clients and not dedicated servers, I could basically say, if dedicated server, basically return false, so it won't create that. It gives you a way to override creating a substance, or write some rules about whether or not it creates subsystems. You could even have a subsystem that -- imagine you had a bunch of dependent content in your Plugin. You could write some check conditions about, I've to have this, all this stuff. All these preconditions need to be met before I say yes, I'll make this subsystem. You probably want to throw it an error there, because users are going to be, like, well, where is the subsystem? Why isn't it working? That type of thing. But you might want to do that type of stuff. Then the one other thing that is interesting with this API, just since we had it up, is, the collection base is passed in here. Basically, that is there for a very specific reason. As you use subsystems and create more and more subsystems in your game, you are going to start running into the dependency questions, right? Oh, well, I need my score subsystem to initialize before other subsystems initialize -- you are going to get some of that automatically, based off of the lifetimes you chose. For example, GameInstance subsystems are always going to be initialized prior to World subsystems being initialized, because it comes into existence before the World does. So you get a bit of that. But you might want to be able to control your initialization order. So what you can do is basically call the collection, be, like, Collection.initialize dependency, and then it's a templated Class based on the type. Basically what you're saying is, hey, subsystem, go initialize this first. It's actually kind of a reverse sort of thing. You're not defining an order, so much that you are defining your dependency inside your initialization. Then it will basically recurse down into all the dependencies, do all those, so you can do all that early on in your initialize. Then you do all the depends on them, and you will be in good shape. That is why that's there. In simple use cases like this one, there is no value of it, because I just did it, and I don't have any other subsystems in this thing. And certainly none that a score subsystem depended on. But that's there for you guys to use. >>Victor: You think we'd be able to share the project files? >>Chris: Yeah, sure. Absolutely. >>Victor: We'll make sure we put it up on the forum afterwards. Yeah, nice. Thank you. >>Chris: Yeah, no problem. >>Victor: Let's see. Questions are coming in. That was great. "Fairly new to C++, but subsystems seem like good replacements for manually exposing C++ only coded Blueprint. If so, when do they not replace manually exposing C++ code?" >>Chris: I think that's a great use case. I mean, it kinds of depends what you mean by "manually exposing." I am guessing the user here means that there's something that we didn't expose, right? There's a lot of that, just, you know, stuff that is in C++ that we don't expose, because we just never got to it, didn't see the value in our use cases. Or maybe there is complexities that make it not able to be exposed easily. Writing a subsystem, creating your own functions that kind of effectively wrap that, or whatever -- I mean, great use case of a subsystem. You could also use Blueprint libraries to do that. But again, the subsystem, it houses that functionality. So if you have a number of different things, or you want to have a system that does a bunch of stuff, and happens to also expose those things, because that makes sense in that context -- it gives you better organizational tools. If you are just kind of blindly exposing a number of utility functions, maybe Blueprint utils or Blueprint library would be the way to go for that. But I think subsystems would be great, too. The thing with subsystems is that, I mean, they're a full Class, with state and everything. Blueprint libraries are as well, but they are effectively meant to be stateless, because they are effectively supposed to be static exposures to stuff. So in these cases, these are entirely meant to be a stateful system. Like my score system tracks the internal scores, for example. And that's totally reasonable. If there was some extra functionality of the Engine -- I just had access to it here because I am in C++ first of all, but I could expose some score subsystem that effectively wrapped some underlying Engine functionality, if that makes sense. So yeah, that's totally a valid use case of it, especially when you're linking that with other things that make sense, because then you get that encapsulation of, I have, Get my Subsystem, then I see a very kind of short-form way all the functionality that's there, as opposed to that very sort of busy sort of global name space. >>Victor: Awesome. Then they were asking, is there any way to organize to order initialization? We covered that using the collections. >>Chris: There isn't really -- you can only -- sorry, just a bit more clarity on that one. >>Victor: Yeah. >>Chris: You can define dependency by saying, you know, in this particular case, the initialize code, I would say collection.initialize dependency, and then the subsystems are more initialized, and that would work fine. That only works within one collection. So one collection being one lifetime. So everything that has a lifetime has a collection of subsystems somewhere, so GameInstance has a collection of subsystems -- that's what drives the whole system. So what I can't do is say, oh, GameInstance depends -- you know, [INAUDIBLE] dependency of a World Subsystem. That would not work. But you can't do that anyways, right? Like, you could have a World Subsystem use a GameInstance subsystem, right? That's fine. But you can't declare it as a dependency, because it's not part of the same collection. You can use it just fine, because you know that the initialization order is the way you want it. But if I try to do something the other way around, that would not work. And it doesn't work in general, because a GameInstance -- what World are we talking about? It doesn't know. There's no -- whereas GameInstance is kind of like a thing that owns Worlds, as it were. Worlds don't -- Worlds don't GameInstances, so we can't really do the other direction as a dependency. You can reference in that direction, but you can't depend in that direction. It would be kind of poor architecture, trying to do that anyway. So it makes sense that you can't really do it. >>Victor: Let's see. Sorry if this was covered, but -- >>Chris: No worries. >>Victor: "Can subsystems store Asset references or properties accessible from Content Browser, like Data Asset?? >>Chris: Not -- I mean, okay. I mean, yes, you can kind of force it to do that, definitely. But generally, we try to avoid defining data references in code. There are few examples of it around, but generally try to avoid that. You're creating a fully-quantified path, and it is textural, and it does not respect if you move Assets, so it is generally bad. So generally what I suggest in those cases is, you have -- what I would do is, I would create a Data Asset that had a bunch of stuff. So you would define, like, a UStruct Data Asset, or just another use Class, or maybe a Blueprint, something you tend to make a Blueprint Class of. Depends on what you want to do with the data side. Like, Blueprints gives you data inheritance if you want to have more complicated stuff. But in any case, you have some Asset that defines all that data based off of a structured set of data defined in C++. So I have my data Class that says, you know, I've Int 1 and Int 2. I inherit that, or create a Data Asset of that type, or inherit it in a Blueprint, depending on which one you want to use. Then you define that data. Then in your initialization, probably in, let's say, begin play of your level, or in the initialization you have some callback that something else wants you to do. But anyway, you find someplace to basically say, hey subsystem, here is your Data Asset, and pass it into it. Then the data -- that subsystem goes and initializes, pulls out the data it wants, or holds onto it to look at data later, that type of thing. >>Victor: Then just make sure you do that before you are actually trying to -- >>Chris: Yeah. So then it comes down -- You do have to do the little bit of work there, obviously, to figure out, like, when do I set that initialization? But you're just going to be in a better state there, like referencing data from C++ is something to be avoided, just because there's no hand-holding with it. First of all, you have to figure out how to make a fully-quantified path correctly. Which is not hard, but sometimes not intuitive. Then you're just going to have possible bugs that come out of that, so yeah. I mean, most larger games I've worked on do exactly that. There are some large data store Classes that reference a bunch of stuff, and then My Game's Settings or My Game's References, whatever, and then you pipe that into systems that need it. Sometimes you just have like a global access, because that tends to be this kind of monolithic thing. I actually don't suggest doing that. I suggest splitting your data more. Also, use weak Object pointers, not hard references, because that will mean that you actually load things on demand. >>Victor: I am planning to do a livestream on that, actually, and the difference, and how to use it in Blueprints. >>Chris: Yeah. It is harder. I mean, you have to deal with more stuff. But it really depends. If you're working on a small project, you know, it's generally not that big a deal. But you think of something as large as Fortnite with hundreds of thousands of Assets or whatever. Obviously, if you hard-reference all those, you would have to load every single one when you loaded the Editor, and that would be waiting 30 minutes to start editing a thing. No one wants to do that. So you have to be careful about that. And weak references, using those by default, even though it sometimes means you have to sort of write the hooks and say, okay, time to load this set of things -- is better engineering, especially when you're talking about a project that's going to scale up larger. >>Victor: Yeah. "Can subsystem Objects be serialized/De-serialized?" >>Chris: Yep. They're UObjects. Absolutely. I mean, it's the same thing as anything else. Transient variables won't get -- if it's not a UProperty, it won't get serialized. If it's transient, it won't get serialized. So all of the same rules apply as UProperties, they're just UObjects. And the same thing actually goes with the Blueprint exposure. Obviously, I made all of these, in this example here, UFunctions, because I intended them to call from Blueprints. But if you have functions that aren't UFunctions, they will not be accessible in Blueprints. Which can be nice too, because it kind of gives you three layers, right? This is the same with any Blueprint it's not special to subsystems. But I have UFunction things that are exposed. That's kind of like one layer of accessibility. I could have public things that only C++ could get at, if I had -- you know, let's say I wanted some C++ subsystems to get at some API that I want to be public, but I don't want sort of my artist designer type users to see them, because maybe they are confusing, or not something we want to use at runtime when there's a very specific call pattern -- who knows. And then you have private of sort, of course. >>Victor: "What is the difference between singleton inherited from UObject (add an all-function Blueprint callable and delegates) and subsystem?" >>Chair: Can I read that one? >>Victor: Yeah. >>Chris: That's fine. I just wanted -- you kind of quoted, I wanted to make sure that -- what is the difference between -- I think I understand what the user is asking. And I think the answer there is, not a whole lot, except for all the utility that subsystem is giving you, right? I mean, as far as the functionality goes, a UObject is a UObject is a UObject, right? >>Victor: Yeah. >>Chris: You know, UFunctions work the way UFunctions do. UProperties work the way UProperties do. But getting at it is where the extra work might come in, right? Like, subsystems are giving you those nice Blueprint nodes to really get them by type, and, you know, they're giving you that. They're giving you the fact that they are auto-instance. They're giving you the fact that they have different lifetimes and they have initialized and De-initialized. It's basically all that stuff. Just a regular UObject singleton, you know, just doesn't give you any of that framework. And I'm not saying that the framework for a subsystem is actually that complicated, it is relatively simple. There is some extra complication, especially when it comes to Editor and Engine subsystem, because those are dynamic subsystems. So they actually can be loaded late, based on module-loading stuff. It is a complexity I did not really want to get into. But there's a bit more going on there, whereas with local player and GameInstance subsystems, they are -- if the subsystem doesn't exist, when they get created, they never get created. So if you loaded a Plugin or something, it would not get created in a pre-existing game instance. You would have to stop and start PIE. That said, you can't change Plugins that we started in the other -- like, it's just not a problem you would not run into. But kind of in a more generic sense, there is a bit more going on with Engine and Editor subsystems, because of this ability to be latently loaded. But for the most part, I don't think users have to worry about that too much. But yeah, it's all those little bits of convenience that you get, and the fact that you're not explicitly writing independencies when you do initialize things. That is one of the big benefits. So if I had 20 subsystems of different things in my game, and I -- I could write all the subsystems in the old world, before subsystems. I would still write 20 subsystems if I was encapsulating my functionality in this way. There wouldn't be subsystems in the sense of this new stuff, but then I would have to come along, and I would be, like, okay, I'll derive from my game instance and be, like, my GameInstance, derive some base GameInstance, and then I would create an instance of every one of my subsystems. Now what I've created is this Class with basically dependencies on all that stuff. It's not the end of the world; my game does depend on all that stuff. But now I've defined that I've hard-coded that, right? And if those are in Plugins, well, now I've a thing that references that correctly. And if I didn't want to load it, I would have to, you know, change the code itself if I wanted to disable the Plugin. So the fact that they're auto-instantiated based off of the existence of the Class and type database means that you don't end up with these hard references of these things. And you don't end up with that kind of, like, spaghetti of dependencies in Classes, in those shared Classes. So it allows you to break up your game more. And really, like I said, really helps especially in the Plugin case. Like, the bottom line is, your game is probably not going to remove its score system anytime soon. So you're not too worried about the cost of changing that code, if you have decided to remove it. It's, like, a one-time cost. It's small. But the case of, you know, I'm trying to make Plugins, maybe I want my score system to be used, it's so good. And you guys saw it's got multipliers, it's got adding score. It's pretty awesome So I definitely want that in every single one of my games, so I now I could put that in the Plugin, and move it from game to game. Obviously, you guys will make a much better -- subsystems actually have a lot more value to move. But it's especially nice, you know, if you want to distribute stuff, or share things on the Marketplace. I mean, I think it's an amazing tool for creating encapsulation within the game, ignoring all that stuff. But it's almost needed, you know, if you really want to make a really easy-to-use Plugin. >>Victor: For someone like me who almost 99 percent of the time, I use Blueprint to script all my game logic. I can understand this. >>Chris: Yeah. >>Victor: I don't have to deal with initialization and all of that, right? It's sort of, like, oh, I can actually go ahead and write a subsystem and encapsulate some of my function out of here. And it doesn't even seem too complicated. >>Chris: And one of the users actually mentioned, "I am new to C++," right? And it's, like, well, look at this code. It's not that hard to write. It's pretty simple. If you can write some C++, you can basically write this. You have to learn a little bit of Epic magic with UFunction, UProperty, and I think you're a new user to C++, could write this Class pretty easily. >>Victor: Yeah. >>Chris: And make a sweet little score game, just like I did. But the -- what that user probably can't do, you know, being new to C++, is figure out in the million lines of Unreal 4 what Class they would have to derive from -- and where they should initialize their subsystem. That's not that hard if you know Unreal well. But, I mean, when you're just starting, you don't know that type of stuff. And this helps, I think, just avoid having to learn those things. don't get me wrong, I want everyone in the world to become an expert of Unreal and know all the ins and outs, and all that type of stuff. But let's be realistic. You know, everyone wants to make a game first. And I think this helps you, to some degree, avoid having to learn things that you otherwise don't need to further on. >>Victor: It's easy to get unmotivated as well, when you just sit in the trenches. And you don't necessarily see any progress, right? So having that game idea is -- >>Chris: I mean, I'm a tools engineer. So I go home to write a game, and before I know it, I'm writing localization systems. And I question what I am doing with my weekends. But the game always seems to fall to the wayside, and other things sort of -- >>Victor: But that might be the catalyst, right? >>Chris: Absolutely. Absolutely. >>Victor: For you to get into tools. It's, like, let's make a game where I discover which tools I need to make. >>Chris: Absolutely. Absolutely. >>Victor: Then you will use functional tools. >>Chris: Well, almost every tool came from trying to make an end product, right? It was, like, oh, I was -- you know, the first time someone wanted to make big levels, right? That is where the first level editor came from, right? You know it's like it's always kind of -- you know, tools come out as -- out of demand, right? I want to do more things. I want to do more of some of this, and tools derive out of that, because, you know, you need to ease the workload of doing those new things. What else we got, question-wise? >>Victor: Yeah, we got a few more. We still have a little bit of time. What -- we covered a bit of this but I think it's worth repeating. "Once you create a subsystem, can you access it from anywhere in code? Is there, like, a global getter?" >>Chris: That's a good question. We talked about it, I said yes, you can get them from code. But I didn't really describe it all that well. It depends on the lifetime again. So in Blueprint, you have the nicety of that context I was talking about. So we have a number of Blueprint functions that kind of take care of that for you and make it really easy. So I'm in a Widget Blueprint, for example, and I just say, get the score subsystem. Well, what's really under the hood happening is that the Widget Blueprints are actually owned by a player controller, which lives inside a GameInstance, and the GameInstance has the collection I get that subsystem from. So there is a little bit of plumbing that has been done for you. You kind of have to do that yourself in Blueprints. Or, sorry, in C++. So in C++, it depends on where you are. At Component would probably get its UWorld, get the game instance that the UWorld lives in, and then there is this API, Get Subsystem, they're just templated functions. So there's Get Subsystem, there's Get Subsystem list. We don't expose the list API, I don't think, in Blueprint at all. Maybe we add that -- we have zero examples of list-based ones right now that are actually needed. It's definitely something that was meant more for flushing out multiple implementation APIs, like streaming would be an interesting one. If we added streaming functionality, you might have Twitch and you might have YouTube, right? Those would be two different implementations of some base streaming API that both lived at the same time, and you might want to talk to both of them, or one, or whatever. So that's one example. But in C++, you need to be able to get at the context Object. So depending on what that is, you know, GameInstance is probably one of the more likely ones. UWorld is probably another likely one. But yeah, getting a UWorld from an Object is really easy. Getting a GameInstance from player controllers, from UObjects and from Worlds is all easy. There is a lot of different means to get at those things. So that's generally pretty simple. You either get the context [INAUDIBLE], Get Subsystem, is just a couple of different functions. >>Victor: I had a question about examples. I think we covered quite a few examples already. Checkpoints, pooling systems. Yeah, score systems. Then we have a question, someone is asking, "what is the difference between a Data Asset and a Struct is?" >>Chris: A Data Asset and a Struct? A Data Asset is, I think, a UClass. I would have to look it up. They're not much different. I mean, a Data Asset, basically, is -- the only real difference is that a Data Asset, the Editor looks at, and allows you to create those as an Asset and serialize them in and out. In the end, they're just a bucket of properties, right? So they're the exact same as a Struct in that sense. They're just a bucket of properties, you know, with values, whatever. But a Data Asset, when you derive from a Data Asset, like the Editor knows, oh, Data Asset Class -- that's a thing that we put in menus for you to be able to create. And in turn, you may end up with Assets of them that exist, right? So really, it's just important that there is some plumbing in the Engine that says, ah, Data Asset, okay, well, I'll allow a user to create an instance of that and serialize it out to disc, and back in, and stuff like that. That's really, functionally, the only real difference there. >>Victor: I believe that was our last question. >>Chris: Cool. >>Victor: We covered quite a bit there. >>Chris: Yeah. I talk a lot. >>Victor: That's why you're here. We want to hear you talk, and we want to hear everything that is going on in your mind, someone who actually wrote -- worked on the system and implemented it, and knows why it's important, the use case for it. Why you wanted it, and what you wanted. >>Cris: Yeah, and I think that kind of comes back to the one user's question, it's like, aren't subsystems just, like, Components for different things. I'm, like, yes. Yes, they are. I am a big proponent of Componentization. I think it's just -- you know, it kind of checks a lot of the boxes of just good engineering, right? Keeping things encapsulated, keeping the dependencies down as much as possible. Providing clean APIs by saying, these are the public things you're allowed to access, here is all of the implementation details, you shouldn't need to worry about keeping those separations, and stuff like that. You know, and Componentization in general, and subsystems in turn, just lean into that. And it's all about keeping your architecture clean, you know, thinking about what is the functionality that should live in one place. What do I want to be public, and what is hidden? That's why we created subsystems. It was to enable more of that. Because you can do that, still, right? Even before subsystems came, you could write code that way. It was just -- it was easier to do it the wrong way. Subsystems was really about making it easy to do it the right way, right? So if I derived from GameInstance, I could drop some API in Game instance, and then be done. Or I could derive from GameInstance, create my score manager, create an instance on that, new-up that instance when it initializes, pass through whatever I need to, then write my API over here in a nice, encapsulated place, which was the right way to do it. That was the good way of doing it. But, I mean, the description took three or four times a long, because it was more complicated. It's not a lot of code, it's not extremely difficult, but it just a bunch of boilerplate, right? So subsystems is all about saying, okay, well, that's the right way to do it. let's make that the easy way, right? >>Victor: Yeah. >>Chris: Right? So now you derive from a single Class, write your code, and you're done. Honestly, creating the files and adding them to the project takes more time than anything else at this point, because you're really talking about just public My Class: -- you know, deriving from GameInstance, right? So you know, that was really the main goal, making the right way easy. >>Victor: Then there will be less -- hopefully less problems. >>Chris: The hard thing is still in everyone else's hands, right? The hard thing is defining what subsystems are going to have what API those are going to have, how am I going to implement them? The hard stuff is still there, but hopefully it also empowers sharing stuff more as well, which I think is because of its natural affinity of working very well with Plugins. I think that is going to allow people to make more easily-shared stuff. >>Victor: And they have, I've seen it, I think, even in the first preview release of the subsystems, the VR expansion Plugin developer, immediately moved over, he's like, oh yeah, this is just going to be a subsystem. >>Chris: Right. Because I bet anyone who has written a Plugin for the Marketplace who has a bunch of people using it almost certainly has written a little document that's, like, okay, here are the things you have got to go do to put this thing into your game, right? I mean, for just, like, a content only thing, it is not that complicated, right? Download the Plugin, put it in, and now you have content that you can reference. But when it comes to code, you have to have that little kind of, like, to-do, you know? Download, step one. And then step two through ten of kind of just hooking up the little bits of plumbing. Again, not that complicated, but better if you don't have to do it. So I think it's going to really help there. I think that's, like, a great -- and anyone who knows how to write that and deal with a bunch of people online who made some minor mistake along the way and it didn't work properly. No one wants to remote debug, so, I mean, I can see why someone who writes Plugins would be, like, immediately, like, yes. I want that, because now I don't have to deal with all of that stuff. >>Victor: That's great. Thank you so much for coming in. >>Chris: Awesome. Thanks for having me. >>Chris: Yeah. No, it was -- I learned a lot. I hope all you guys learned a lot as well. A couple of notes before we take the stream offline, we are doing transcripts of all of the livestreams. And so if there was something that we mentioned here today that you remember that we talked about, but you don't really remember where, I mean, we have been here for an hour and 13 minutes now. And we have said a lot. So I know that can be tricky. In usually about a week after the stream, we upload the transcript to YouTube. It's in a link in the description. You can open that, it's a PDF, and you can control-F and search for keywords. What is nice about that is that if you find the keywords you are looking for, you can see the timestamp right next to them, and you can go to the part of the stream where we discussed it. >>Chris: That is great. Just don't hold me responsible for everything. I do make mistakes. [LAUGHTER] >>Victor: We all do. And yeah, usually about a week. And we are doing that, we are backlogging that as well. So we are trying to get most of these things from the last at least a year, and a little bit further back. And the transcripts are used for the captions. But there is also a nice use case of them where you can actually go and search for what we talked about. There is also a cool feature on YouTube where you can actually see it in real-time. So not only -- if you don't like the captions overlaying the screen, especially when we are working in Editor, that can be a little bit annoying, you have got to go toggle them on and off -- you can actually enable an option to see them on the right side, right above Chat, I believe. So if you are watching the VOD. And then you can also click them to go to the specific sentence. So something I like to mention. I know I've been using the transcripts quite a bit. We always have a survey that we put out. Please let's know how we did, what you would like to see in the future. And if you participate and you put your email down, we do a sweepstake where we send out a t-shirt to one lucky winner. I need to hear what you guys want to see, so that we can actually bring you the topics that you are interested in. Make sure you visit our user group page, UnrealEngine.com/user-groups, if you're interested in meeting other developers in your area. If you don't find a user group and you have a group of people who are interested in going to a specific -- for a specific meetup, contact us at Community@UnrealEngine.com, and we will talk to you about everything that it is of becoming a meetup, a user group lead. And we have got some more news coming there real soon, which we are going to be excited to share with you all. Go visit our online communities, forums, the unofficial Discord channel -- I think it is up to 27,000 members now, it just keeps ticking. Good job Nick, and everyone else who is running that. Our Facebook page, as well as Reddit, there are tons of UE 4-related content that you can go and look for, or ask questions when you have problems. The community is happy to help. But remember to try to give as much information as possible in your question. If we don't have that -- maybe someone would know the answer, but the question is just not enough, and they will not have time to sort of try to get to the real question. So try to bring as much information as possible. Just little tips. Our Community Spotlights that we have every week, please go ahead and send everything you are working on with your projects. The forums are a good place to put that, released projects as well as the work in projects part of it. I like to go and look there, and see what everyone is working on. We are still looking for more countdown videos, and they are 30-minute videos sped up to five minutes. So send that to us, together with your logo separately. don't embed that in the video. we will take care of that for you. Then we might go ahead and have you as one of our countdowns. That was the five-minute thing we saw in the beginning, the countdown. >>Chris: Yeah. That was cool, it was like building, like, a Roman-style sort of architecture. A little dilapidated, but it looked really cool. >>Victor: Yeah, and there are a couple of really cool ones, and some people working in sequence there. And you get to see tidbits of their workflows, and it's sped up to five minutes. >>Chris: It is always amazing to see, like, you know, you see the same parts coming in over and over and over, and just with the artist is, like, scaling and changing them, and it turns into something completely different. It is really, really awesome. >>Victor: Yep. I wish I was a better artist, my projects would look a lot better then. I wouldn't need to ask for help. If you're streaming on Twitch, make sure you use the Unreal Engine category, so that we can tune in. And as always, follow us on social media. And a special thanks to Chris today, who was able to come on and talk to us about everything. >>Chris: Had a great time. >>Victor: If you have more questions, go ahead and ask them in the announcement posts on the forum, and if we have time, and if possible, we'll try to go ahead and answer your questions there. Alright, next week we actually have Matt Workman coming to talk about Blender to UE4 workflows. Matt Workman, ST Developer, Cine Tracer. You might have seen him around on the web; he's creating a lot of very cool content, as well as his tool to stage and previs cinematic sets. So he will actually be here in the studio next week, we're very excited to have him. But until then, bye-bye! >>Chris: Bye, guys.
Info
Channel: Unreal Engine
Views: 19,785
Rating: undefined out of 5
Keywords:
Id: v5b1FvKBYzc
Channel Id: undefined
Length: 82min 38sec (4958 seconds)
Published: Thu Oct 17 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.