>>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.