Welcome back.
In this video, we're going to take a look at the effect context and make sure that we understand completely
what's going on with the gameplay effect context.
Now, the effect context is something that lives with a gameplay effect and seems to carry along some
information, but thus far it isn't really all that clear to us exactly what information is being carried
along.
So we're going to take a deeper dive into the effect context in this video and see what it can do for
us and find its limitations and start brainstorming about how we can get around those limitations.
Now I'm here in the aura attribute set, set effect properties function, and we know that we have the
effect context handle here.
In this function, we're setting it on our props parameter, an input of type F effect properties.
So here in set effect properties, we're setting that props dot effect context handle.
By getting it from the effect spec which we have in our gameplay effect mod callback data so we can
always get that effect context handle using get context.
Now we know that the effect context handle has the effect context inside of it.
So what I'd like to do is sort of pause execution right here.
We can pause execution right here as soon as this property is being set and see exactly what that effect
context has.
By the time we've made it this far into the attribute set.
So I'd like to do that.
I'm going to click the debug button and what I think I'll do is launch a fireball as our fireball is
applying a gameplay effect.
It's our G damage effect.
So I'd like to see basically what the context handle is carrying along by the time we apply that gameplay
effect.
So I'm going to press play and I'm going to come back into the editor and place a breakpoint here.
I press play and then placed the breakpoint here just because I don't want to have to hit resume for
the first time it gets called.
So now that we have the game running, we have our breakpoint here.
I'm going to launch a fireball at one of the enemies.
And as soon as we apply that gameplay effect, we have our breakpoint reached and we have our effect
context handle set.
Now this has some data so we can take a look at it either by hovering over or taking a look down here
at the bottom right.
And we know that effect.
Context handles have a data property and that data is the gameplay effect context itself.
It's a shared pointer, but we can see all that the data has.
And we're going to notice if we take a look at data and look at all the fields, we'll see that some
of them are set and some of them are not set.
A lot of these are empty or null pointer So what exactly is set?
That's the first question we'd like to answer.
And the second question is why?
Why are those properties set and how come the others are not?
So we're going to answer both of those questions in this video.
So looking at these fields under data, we see that there's an instigator.
And if we expand that, we can see what the instigator is.
The instigator is an actor itself.
We can see that it has a player state, an ability system component and an attribute set.
And these seem to be set.
So we have the BP or a player state here.
We have the ability system component and attribute set.
And we seem to see that there is a level set to one.
We also see that there is an effect causer and the effect causer seems to be set to BP or a character.
So something is setting our instigator to or a player state and the effect causer to or a character
for some reason.
And after these two instigator and effect causer, we have a bunch of other things that seem to be not
set to anything.
We have ability S.d.o set to null pointer.
We have ability instance not replicated set to null pointer ability level seems to be set to one, but
that must be some kind of default.
We also have the source object and now we've seen source object before.
We've set this before on our effect context for some of our effects.
Now the effect that we're applying here that we currently have, we can tell what that effect is by
hovering over effect spec.
And here it is.
It's GE damage, right?
So we're applying GE damage and we know that we're creating the effect context in our projectile spell
ability.
So back to data here we see that source object is null instigator ability system component looks like
that's being set.
So that's one of the ones that is set actors looks like a t array of t weak object pointers.
This array appears to be empty.
We also have a hit result set to null.
It's a pointer t shared pointer to a hit result.
We have a world origin all zeroed out and for booleans has world origin replicate source object, replicate
instigator and replicate effect causer.
And we're going to see exactly why these have the values that they have here as well.
So the first question is what's setting these properties that are set?
We know that source object won't be set unless we set it ourselves.
We've done that before.
But instigator effect, causer and instigator ability system component seem to be getting set somehow.
So it's time to investigate.
So I'm going to hit stop and we're going to answer these questions.
Now, the first thing we should look at is where we're creating the effect context that we just looked
at.
We can go to private ability system abilities and open aura projectile spell.
I'm going to go ahead and close all tabs and open aura projectile spell.
And here we can see where we're making the effect context for our G damage effect we're applying here.
We see that we're calling make outgoing spec, which requires the effect context.
And for that context we're passing in whatever we get returned from make effect context.
So whatever make effect context is doing for us, whatever it's setting by default, that's what we're
getting and we've seen what it's doing for us.
We've seen that it's set the effect causer and instigator as well as instigator ability system component
so we can investigate what this function is doing exactly by peeking that definition.
I'm going to go to the definition for it.
Here it is.
And we see that it's making a context handle using the gameplay effect context, handle constructor
and passing in whatever is returned from this function.
U ability system globals Alec Gameplay effect Context.
Now this is pretty simple.
If we go to this declaration, we see that it's just calling new here.
That's all that it's doing.
Really nice function ability system globals, lots of hard work you're doing for us.
So that's what it's doing and it's passing that into the constructor, creating a new context.
It's taking the ability actor info, which is a member variable of this class.
What class are we in?
We're in the ability system component.
That's where make effect context is.
So we're checking that this ability system components, ability actor info is valid here.
So in other words, we can't make an effect context if we don't have valid info.
And then we're taking that context and calling add instigator.
So the gameplay effect context handle class has this add instigator and it looks like it takes two inputs
in instigator and in effect causer.
And for the instigator input parameter, we're getting our ability actor info and getting the owner
actor from it for the effect causer to this function.
The second input we're passing in ability actor info Avatar actor.
So this is why those properties are set because we're calling add instigator and then this function
simply returns the context.
So let's see what Add instigator is doing for us.
I'm going to go to that definition, and this is a function on the effect context handle, right?
So it makes sense that the effect context handle is going to reach into its own data property and simply
set something on that.
So.
Was just calling an instigator, passing in those inputs that we passed in to add instigator.
So what does the effect context class itself do when calling add instigator?
Let's go one level deeper and here we can see that it's doing a few things.
For one, the gameplay effect context is setting a member variable instigator to the instigator passed
in.
Remember what was passed into this?
Right?
We can see by going to the ability system component make effect context, we can see that we're passing
in the owner actor.
So if this were, say, Aura's ability system component, this would be the player state.
And of course for the second input parameter, that Avatar actor is the aura character itself, right?
So we can see here that we're setting the instigator on the effect context to this in instigator, which
for Aura's case is going to be the player state.
Now it's also calling can actor reference be replicated passing that instigator in and if we scroll
up just a little bit we can see that function and how it's determining the result of this.
It's taking the actor making sure it isn't null it's calling is full name stable for networking which
dig deeper if you want to see what that does.
And then it's seeing if the actor is supported for networking.
Again, dig deeper if you want to see what that does, and it also checks to see if the actor is replicated.
So it's doing a few things and after it calls that function, it sets a boolean on the effect context
called replicate instigator.
So this is how it determines that that instigator is a replicated variable.
Now that is why we saw that particular boolean.
I don't know if you looked at those values, but this particular boolean is definitely set, right?
So those two properties on the effect context are already set just from us making one.
Now it also has set effect causer.
If we peek the definition of this, we can see that set effect causer is setting the effect causer variable.
So apparently the gameplay effect context has an effect causer and then it's setting this be replicate
effect causer and it's calling that same function that determines whether or not that actor is a replicated
actor.
So we have that also set for us that explains why that property is set.
So the instigator in effect causer, those are set by default for us just by making an effect.
Context.
Now the gameplay effect context has an instigator ability system component pointer.
It's a weak object pointer.
These are all weak object pointers and we'll see why in a second.
And what it does is it first nulls it out before using ability system globals and calling get ability
system component from actor a ability system globals function and then it sets that instigator ability
system component to that.
So that's another property that was already set for us.
So this add instigator function sets those properties for us and that's why in aura projectile spell,
just calling make effect context results in those properties being set.
And that's why we saw those when we placed a breakpoint and paused execution right there in our attribute
set.
Okay, so we've seen why those properties that were set are set.
That's because make effect context sets those for us.
So the question is, what about all those other properties?
How come make effect context doesn't just fill in the whole effect context?
Well, the effect context doesn't necessarily need to be fully filled out.
It doesn't need all of its properties filled out.
But there are other variables in the effect context struct that we could set if we wanted to notice
that the effect context constructor sets the ability level to a default value of one.
That's why we saw that one when we were checking out that effect context.
We see that world origin is forced to zero force in it to zero and we have booleans that are set to
false here.
Now the effect context also has a constructor that can take in an instigator and effect causer.
And all that really does is zeros out and falses out some of these properties and then calls add instigator.
So we've seen what Add instigator does for us.
So nothing really too spectacular there.
So let's take a look at what else the effect context has.
It has a virtual void Get owned gameplay tags which says returns a list of gameplay tags applicable
to this object, defaults to the owner's tabs and we can always take a look at this.
Get owned gameplay tags function if we want and we can see that it casts the instigator to an AI gameplay
tag asset interface, which does have a couple of functions for getting owned gameplay tags in fact.
It has jet owned gameplay tags.
So what this function does is it sees if the instigator implements the gameplay tag asset interface
and calls, get own gameplay tags on it.
Otherwise, if it doesn't implement that interface, we just get the instigator ability system component
and then get own gameplay tags from that.
So that's all that get own gameplay tags does for us.
Add instigator.
We've seen what that does.
What about set ability?
Let's take a look at set ability.
So here we go.
It's checking the ability that we pass in so it takes one in by a const pointer and then it sets three
variables on the effect context.
So set ability sets, ability instance not replicated.
And what is it setting it with?
Well, it's taking the ability passed in and it's performing a const cast.
We've seen what const casts do in previous sections.
The const cast casts away the const ness.
That's kind of a tongue twister, but we're passing in a const pointer, const, cast, un const.
It.
It casts away the constness and then the function makes a weak object pointer and then sets ability
instance not replicated to that.
So basically this is just a setter for this variable.
It also sets the ability cd0.
So it's taking this game play ability, calling Getclass on it and calling get default object on it.
And this is going to return the class default object for that CDO.
And then we're calling it ability level and setting ability level.
So that sets that property on the effect context.
So set ability sets these three properties for us.
Moving on, we have a getter for the instigator.
It just calls get on the instigator and returns it.
We have a get ability function and if we go to this definition, we see that it does basically the same
thing.
It gets the ability CDO calls get on it.
So why get instigator is defined here but get ability is defined in the CPP file.
Who knows?
I can only imagine it's because this has been worked on by multiple people and some people have different
styles.
So we've seen what get ability does.
We have get ability instance not replicated, which as you can imagine going to the definition, we
can see that it just calls get on that variable.
We also have ability level just returns the ability level.
We also have a getter for the ability system component of the instigator, also for the effect causer.
And then we have a setter for the effect causer.
And by setting the effect causer, we see that we're setting be replicate effect causer so we don't
have to set that manually.
This setter is going to take care of that for us.
Get original instigator is just going to return the instigator.
That's all it does.
This is virtual.
So keep in mind that the virtual versions of these can be overridden if we subclass this.
So again, some more getters we've seen what Add source object does and there's a getter for that.
Now add actors is interesting because it takes in a array of weak object pointers to actors and there's
a B reset that has the default value of false.
So basically add actors is going to check be reset and make sure that the actors array passed in isn't
empty and it's going to reset the array so it'll be emptied out and then actors will have this array
tacked on to it.
So in effect, context has an array of actors that can be set for any reason.
So if you have an effect and you want to stick an array of actors into it, you can stick that in the
gameplay effect context.
And then later on down the pipeline you can access it.
Same thing with the hit result and there's an add hit result that allows you to reset the hit result
with an optional boolean.
And if you do pass in true for that it'll reset the hit result and also set has world origin to false
and then it takes that hit result and sets the hit result variable to that.
And this is creating a shared pointer.
So the effect context stores the hit result as a pointer and we'll see why very shortly.
But if we've reset that hit result, then it's going to add the world origin and add origin.
It's just going to set this world origin vector equal to that hit result trace start.
And then we have net serialize and we're going to talk more about net serialize in a future video when
we have to create some of this stuff on our own.
But this just specifies how this gameplay effect context struct will be serialized when it has to be
sent across the network.
And lastly, we have a to string which really just gets the instigator and returns its name if it's
not null, otherwise it returns none.
And we have a is locally controlled effect context which checks the instigator casts it to a pawn and
if the pawn cast fails, it then tries to cast the effect causer to a pawn and sets that to this local
pawn variable.
And if that's valid, then it calls is locally controlled on that pawn.
So it first tries the instigator.
If that fails, then it tries the effect causer.
If that fails, this function returns false.
And if it doesn't fail, we return is locally controlled on the Pon is locally controlled player does
the same thing, except it then takes the pawn and gets its controller and then calls is local player
controller.
Here's the Add origin which just sets this world origin variable, which happens to be an f vector equal
to whatever we pass in here and then it sets.
This B has world origin boolean to true.
Now we've seen get owned gameplay tags, we know what that does.
And after that, in this file we see net serialized for the context handle.
So we're starting to see things for the context handle.
So at this point we've seen all of the gameplay effect, context, struct and now we know the types
of information that can be stored on it if we want to pass that information along.
So that's pretty cool.
We can create one of these in or a projectile spell and fill it in with more data if we'd like, and
then test this system out, see what those values are equal to in our attribute set.
If we like to do that.
And then we'll go ahead and wrap this video up and discuss possible ways to make our effect context
even more useful.
If we'd like to pass along more interesting data that doesn't exist in it yet.
So rather than calling make effect context right here and make outgoing spec directly, I'd like to
make an effect context right here.
So this will be an F gameplay effect context and actually this is a context handle that we need to make.
So we're going to make effect, context, handle and set it equal to the result of source ask, make
effect context.
And now we have an effect context handle that we can pass in right here into the function make outgoing
spec and now that we have an effect context handle, let's think about the different properties on here
that we can pass along.
So back to gameplay effect types, let's scroll down a little bit to the variables that exist in this
class.
They're all protected.
And let's take a look at this comment right here.
It says, The object pointers here have to be weak.
So weak object pointers because contexts aren't necessarily tracked by garbage collection in all cases.
Now, it would help if you understood what a weak object pointer does.
Weak object pointers are smart pointers.
They're wrappers that can store pointers and a weak object pointer has the property of not taking ownership
of the pointer that it stores.
In other words, it does not participate in reference counting.
You see, smart pointers are reference counted as soon as an object no longer has any references to
it, any pointers that point to it, it's automatically deleted.
That's why smart pointers are useful now.
Weak object pointers have the property of not being reference counted, so reference counting to delete
an object or extend its lifetime for that matter is not affected by a weak object pointer.
And the reason these are weak pointers is because well, it says right here not all effect contexts
are necessarily tracked by garbage collection.
In fact, ours is not here in or a projectile spell.
This is a local variable.
It's not tracked by garbage collection.
And here we can see our different properties and we already know about these.
But here are some useful comments that give us more information.
Here's the instigator.
It says instigator actor, the actor that owns the ability system component.
And we've seen that this gets set for us automatically.
And in the case of Aura, we saw that it was set to the player state which owns our aura ability system
component.
So that's correct.
The effect causer is the physical actor that actually did the damage can be a weapon or a projectile.
In our case it was aura because that was set to the avatar actor.
We have the ability CDO that says the CDO that is responsible for this effect context in parentheses
replicated.
So we've seen that there are different instancing policies for gameplay abilities.
Some are instanced, some are not.
And the class default object is we've seen the result of that get default object function call.
The ability instance not replicated is a pointer to a gameplay ability.
It specifically has the not replicated value property and it says the ability instance that is responsible
for this effect context not replicated.
So we have replicated and not replicated.
We've seen that the ability level is just set to one by default.
We've seen the source object and we can set that, although now we know that if we set this equal to,
say, the Avatar actor, well, we already know that the effect causer is set to that.
So that would be redundant, wouldn't it?
The ability system component that's bound to the instigator is the instigator ability system component.
We've seen that that is set for us.
It's not replicated as well.
We have a array of weak object pointers to actors, which is empty.
We've seen that nothing happens to this array.
It's not set, but it says actors referenced by this context.
So that's for whatever reason we want.
Same for this hit result.
It just has a hit result right there for us.
It says trace information may be null pointer In many cases, yes.
In all cases unless we set it same with the world origin if we set the hit result.
Interestingly the world origin will be set to the trace start of that hit result.
And then we have these Uint eight which are treated like booleans.
We call this a bit field.
Essentially this guarantees that a bit array of contiguous bits will be populated by these variables
which can be checked as if they're booleans, they can be treated like booleans, but they just take
up a single bit altogether.
They all take up at least a minimum of eight bits in a row.
So just because there's four of them here doesn't mean that it only takes up four bits at least one
uint, eight worth of memory is taken, but this allows us to not have one, two, three, four booleans,
each of which would be one byte.
A byte is eight bits.
So as you can see, this is a good deal smaller than using booleans.
It's just an optimization.
Sometimes you'll see it in the engine, sometimes you may even use it yourself and that's it.
That's all of these properties.
And just for fun, let's set some of these, at least the ones we know that aren't already set.
We know instigator and effect causer are already set, and if we want to jog our memories, we can go
back into the attribute set and we can run in debug mode and pause execution right here and see exactly
which properties are not already set for us.
Okay.
I'm going to press play.
Before I launch a fireball, I'm going to place that breakpoint and then launch the fireball.
And let's take a look at our effect context in the handle.
Here's data We know that instigator is set effect causer is set ability.
CTO is not set, but we saw that this could be set automatically if we go into gameplay effect types
and scroll up a bit, we can see that set ability will set ability for us.
So all we need to do is pass in a new gameplay ability into set ability.
So right here in aura projectile spell, we can take our effect context handle and call set ability
and we can pass in the ability well we're inside of our ability or a projectile spell.
So if we pass in this then set ability will take that pointer and what's it going to do with it.
It's going to set ability instance, not replicated ability.
Oh, and even ability level.
So just calling that takes care of three of those.
Okay.
So back to aura attribute set, let's go back to looking at the data here.
We know that ability will be set for us.
So will ability instance not replicated and ability level.
What about source object?
Well, we've seen that before.
We were setting this to the avatar, but that's actually what effect Causer is set to, isn't it?
So if we wanted to, we can set the source object to the avatar.
We could also set it to anything that we'd like to call the source object.
If we go back to Aura projectile spell, we could even set the source object to the projectile itself
so we could take effect.
Context handle call add source object and pass in projectile.
Let's go back and look at our data here.
So we see that we're now going to be setting the source object.
We know that instigator ability system component is set.
What about the actor's array?
Well, at this point, we're just kind of filling things in because we can I don't know if Aura Projectile
spell really has really enough actors to add to the array, but if we like, we could we could take
effect context, handle dot add actors and we could pass in a T array.
We could make a T array.
And we know this is t week object pointers.
And the type is a actor.
We can make this actors here.
And we could add something to the actors.
What are we going to add?
Anything we want.
I'll just pass in projectile just because I can and pass actors in.
Okay.
What else can we set?
We're running out of things here, but let's just see.
We have a hit result right now.
A hit result.
This is a shared pointer to a hit result.
So how are we going to set that?
Well, we've seen how it was done in gameplay effect types.
We can see that it's making a shared pointer here and storing that in hit result.
But add hit result just takes in a hit result and an optional boolean.
So we could just pass one in f hit result hit result and take the effect context handle dot add hit
result.
And pass in hit results, which we know is empty anyway.
Okay.
Looking at data, world origin will be set because we're adding a hit result and then the rest of these
will be set as a result of setting the others.
So we basically set all we can here on this effect context handle.
And really a lot of these are just kind of silly.
We're just adding them because we can, but this allows us to check to see if that information is being
passed along.
So just to prove that this hit result is working, we're going to take hit result dot location and set
that because we do have that here.
It's the projectile target location.
We'll set that location here in the hit result.
Why not?
So now we can run in debug mode and just see how much of this data we can take a look at by placing
a breakpoint.
Okay.
I'm going to press play.
I'm going to stick a breakpoint in the attribute set after we get the effect context handle.
I'm going to launch a fireball.
And we're going to hover over context handle.
And take a look at the data here.
And we're going to see that we have a lot more information.
First of all, the four booleans down here all have true or one set for their value.
Here we see that the instigator is set to or a player state.
The effect causer is set to or a character the ability CTO is set to default to firebolt.
Now we have access to the class here.
We've seen how much we can get information from the class default object.
And because this is a firebolt, it has a projectile class.
That's the firebolt there.
We have the ability instance not replicated.
There's that Firebolt ability instance.
We have the ability level now set to the actual level of the ability.
Here's the source object.
We set that to the projectile itself and we can see that that's Bpy Firebolt cool.
And there are lots of properties that we have access to there.
Now here's the instigator ability system component that's set actors.
We have an array and if we take a look the Firebolt itself, we added that projectile to that actor's
array just because we could.
Here's the hit result.
And if we take a look at the location.
Here's the location we passed through the location of the projectiles target.
So that's there.
And it looks like world origin was set to zero.
That's because it was taken from that hit result.
And that's not something that we set on the hit result.
And then, of course, these four booleans, we've seen that setting these properties with the setters
results in the effect context, setting these properties for us automatically.
Okay, cool.
So now we've seen basically the extent of the usefulness of the gameplay effect context struct, and
it probably has more inside of it than you realized.
Right?
It's good to know about all these things because it's better to take advantage of what's already there
rather than trying to add more code to do something that you want to do if you needed to do something
in addition to this.
But there are some limitations, right?
What if we want to send across information that's not in here?
For one, I'd like to know in my aura attribute set by the time we get to post gameplay effect, execute
whether or not we got a critical hit, whether or not we got to block our gameplay effect.
Context doesn't have a boolean here for whether it was a critical hit.
It does not have a boolean for whether it was a block.
So those are limitations of the effect context.
It doesn't have custom things that we want inside of it.
However, it has a lot of virtual functions which seems to indicate that we could subclass this struct
and that is what I'd like to do in the videos to come so that we can have even more information set
in our gameplay effect context.
So that's what's on the menu and we'll get into that in the videos to come.
So nice informative video, great job sticking through with all of our investigations and studying of
how this class works and what it does.
And I'll see you in the next video.