Welcome back.
Now that our spell has cooled down, implemented, it's time to be able to visualize that cooldown.
So as soon as I start to cast spells, my spells are going to be committed at one point or another.
And as soon as that spell has its cooldown committed, I'd like to show something down here for its
spell Globe icon.
I'd like it to get dark so we know that it's not possible to cast that spell, as well as show the cooldown.
How much time is left.
Now there are ways to query your active gameplay effects to see how much time is left on their cooldown.
So we'll be looking at that, but we're going to need a way to query that from within our Globe widget
blueprint.
In other words, if we go into UI and into spell globes, here's our spell Globe widget blueprint.
We're going to need to to be able to respond somehow to our cooldown here so we can set properties from
within our event graph.
Now the way that I'd like to handle this is I'd like to have a special node, an async node, a task
of sorts that allows me to do something when our cooldown begins and do something else when our cooldown
ends.
And if that task, that node that I'd like to create is capable of returning the cooldown duration at
the time that a cooldown begins, then we can handle updating our cooldown time.
The numbers that we show here from within our spell globe widget blueprint itself as there's really
no need to query a spell or abilities cooldown over and over.
Right?
All we need to do is know when the cooldown begins, how much cooldown time there is and when the cooldown
ends.
And we can handle updating a sort of countdown timer ourselves.
You see, we have to think about performance, right?
We don't have to query our ability system component every single frame during this update.
So in order to make one of those nodes, I'd like to make an async task.
Now we've seen this kind of thing in gameplay abilities and to remind ourselves we can go to ability
system or abilities, fire Firebolt and open a firebolt and play montage and wait is an asynchronous
node and wait gameplay event is an asynchronous node.
And on top of that we made our own asynchronous node target data under mouse.
It just happens that valid data is executed immediately.
But for something like play montage and wait.
We have multiple execution pins and we know how these work.
On the Cplusplus side, these pins are delegates and once those delegates are broadcast, those execution
pins are executed.
So I'd like to make a node that lets us wait for a cooldown to begin and to end.
Now we need to use this asynchronous node that we're going to create from within our widget blueprint,
not from within an ability.
So ability tasks are made for use inside of an ability, but other types of tasks.
Blueprint async tasks are made to be used in any event graph.
They don't have to be used inside of abilities.
So we're going to create one of those and learn how these work.
So the first step is to create a new Cplusplus class for our async task.
And I'm going to go into my ability system folder and we'll make a new folder for our async tasks.
So I'm going to create a new Cplusplus class, choose all classes and add a blueprint.
Async node will be based on Blueprint async action base.
So we're going to choose Blueprint async action base and within the Ability system folder, I'm going
to make another folder called Async Tasks, and this async task is going to be designed for listening
or waiting for a cooldown.
And we want to be informed when a cooldown begins and ends.
In other words, a cooldown change so we can call this wait cooldown change, or we can call it listen
for cooldown change.
Either of those will work.
I'm going to call it wait cooldown, change and create the class and I'll go ahead and close the editor
down and save all.
And let's open our new Cplusplus class.
I'm going to close all tabs.
Go into the ability system folder and here's my async tasks folder and here's wait cooldown change,
here's the header file and we have our CPP file.
Now, these are a lot like ability tasks in that we need delegates to broadcast and those delegates
will become execution pins.
And we also need a static function that can construct an instance of this class.
So we have a couple things to create.
Now, the first thing I'm going to create is a delegate.
I'm going to declare a dynamic multicast delegate, and this delegate is going to broadcast a value.
The value is going to be that cooldown duration, right?
How long that cooldown should be.
So our spell glow widget blueprint knows what value to show.
So I'm going to use declare dynamic multicast.
Delegate one param.
I'm going to call this f cooldown change signature and it's going to broadcast a float and we'll call
this time remaining as this will be how much time is left in the cooldown.
Now I'm going to make a public section and we'll have two of these cooldown change signatures.
In other words, I want to execution pins in my async node one for when our cooldown starts and one
for when our cooldown ends.
So I'm going to say f cooldown, change signature cooldown start and I'll have an F cooldown, change
signature cooldown end.
And these can be blueprint assignable so they get a new property with blueprint assignable both of them.
So now we'll have an async task that will have two execution pins in addition to its regular output
execution pin.
And these will be executed once we broadcast those delegates.
So now we need that static function, that factory that will construct one of these.
So we're going to say static.
It's going to return a wait cooldown, change object, and that's going to be returned by Pointer.
And we're going to call this wait for cooldown change.
Now wait for cooldown.
Change is going to need to know some information.
It needs the ability system component so it can listen for when something changes on that ability system
component, specifically when a cooldown event has been applied.
So the first input will be the ability system component.
So it's going to be you ability system component.
Now I'd like to forward declare that so we don't have to include it here.
It's a pointer and we're going to call that ability system component.
Now it also needs to know the cooldown gameplay tag as this specific node is going to be used in the
spell globe.
The spell globe needs to know what type of cooldown it's listening for, and these will be unique to
the ability, the spell being cast.
So it's going to take a const F gameplay tag reference called cooldown tag and it will only listen for
that cooldown tag specifically.
So we have a couple of inputs now it gets a new function.
Obviously it has to be blueprint callable and because this is a task, this particular function is an
internal function.
These tasks, much like the ability task, result in creating an async task node.
This function will be used to create an instance and that node, when executed, will result from that.
So to specify that this is an internal use function, we use meta equals blueprint internal use only
and set that equal to true.
So there's our static factory function.
Now if we're going to be listening for delegate broadcasts when this particular task ends.
In other words, when that widget is destroyed, then we can clean up any resources in void end task.
So we'll create this particular function and make it you function blueprint callable so that we can
clean up anything when the task is done.
Now, as soon as wait for cooldown, change is called and it receives an ability system component and
a gameplay tag.
We would like to store that in a member variable, so I'm going to make a protected section here.
And in the protected section we're going to have a T object pointer to store the new ability system
component.
So I'm going to call this don't need that asterisk, I'm going to call it simply ASC.
And this gets a new property.
So it will be garbage collected or not, depending on the lifetime of the owner.
We'll also have an gameplay tag.
And this will be our cooldown tag.
And I'll call the input parameter for weight cooldown change in cooldown tag.
So there's no discrepancy between the names.
So back in the CPP file, it looks like we haven't generated the definition yet, but we'll call it
in cooldown tag.
So the gameplay tag is not using the same name as that input parameter and we can go ahead and include
the header for gameplay tag container.
Okay.
So we have our cooldown tag.
So let's start thinking about how we want to implement, wait for cooldown, change.
Let's go ahead and generate the definition and this is going to be similar to how our ability tasks
work.
Essentially we create a new object based on weight cooldown change.
Now in ability tasks.
We used a function to construct our new task.
If we look at target data under mouse and create target data under mouse, we used new ability task,
but we're not making an ability task in weight cooldown change.
I'm going to close that tab.
We're making a new object, so we use new object for this.
We're going to say you weight cooldown, change, We're going to call this weight cooldown change.
And we use new object, which is a template requiring the type it's going to be you wait, cooldown,
change.
So we call it like that.
And once we've created one, we can set its member variables.
We need to set that as key.
So we're going to say, Wait, cooldown change ASC equals we're going to set it to the ability system
component and wait cooldown, change cooldown, tag.
We're going to take that tag.
We're going to set it to the gameplay tag passed in in cooldown tag.
So we'll go ahead and set that as well.
Now, right away, if the ability system component isn't valid and for that matter, if the cooldown
tag passed in is not valid, well then we can just call end task and just end the whole thing.
So end task is something I'd like to generate a definition for and I'll just call that.
So we're going to say if is valid, we'll check that ability system component and we'll end the task
and return if that's not valid.
So we'll put a not there, but we're going to have an Or as well and we'll have our in cooldown tag
and it's a gameplay tag.
So those have their own is valid.
We're going to go ahead and put not is valid there.
If any of those happen, we'll return.
But I'm going to have to include the ability system component H header file.
So if this condition happens then I'm going to take my newly created wait cooldown change and just call
it end task and then we'll return null pointer.
So that takes care of an early out.
Now if we get past this point we have a valid ability system component and cooldown tag that was passed
in here.
So what do we want to do?
We want to do something when this ability system component has a cooldown event added to it.
And we also want to do something when that ability system component has a cooldown removed.
So how can we do that?
Well, we can know if a cooldown tag has been added by subscribing to a specific delegate on the ability
system component so we can take the ability system component that's been passed in and we can use register
gameplay tag event.
Now we've used this before to listen for tag events, haven't we?
See if you can rack your brain and remember where we've used this before.
If you go to Aura enemy.
Remember, we have this hit react tag changed.
And if we search for that, we bound to this and begin play for the enemy.
We use register gameplay tag event binding the enemies hit react tag change so that way it knows when
it's hit reacting or not based on when the tag count changes, when it's greater than zero or when it
becomes zero.
We'll use that same concept here.
So I'd like a function to call as a callback when the cooldown tag has changed on our ability system
component.
So this is going to be similar to that function.
Hit react tag changed.
And remember, hit react tag changed took a gameplay tag and a int 32 called a new count.
Now this delegate that our callback will bind to needs this to be a const gameplay tag without being
a const reference strangely.
Right.
So that's the kind of signature we have to have.
We're going to make our function with that signature.
So we're going to put this down here in the protected section and we'll call this function void cooldown
tag changed.
Just like hit react tag changed.
So it'll take a const gameplay tag.
Called in cooldown tag just like for weight cooldown change and also it'll take in an int 32 called
new count.
So we can go ahead and generate the definition and we can register gameplay tag event.
Now register gameplay tag event takes a gameplay tag.
So we're going to be listening for a specific tag.
We know the tag we want to listen for.
It's our cooldown tag.
We're going to use that.
So in cooldown tag will be the first input.
Now we also have a gameplay tag event type, right?
So if we type a gameplay tag event type double colon, we can see that we can use any count change or
new or removed.
I'd like to use new or removed.
So this function call will return that delegate and at the very end here we can use Dot and we can add
you object and bind to the cooldown tag changed.
Now we're within a static function, so we can't just use this as this doesn't really mean anything
in a static function.
This returns a pointer to the current object we're in, but static functions mean well.
We can call this even when one of these doesn't even exist.
What we have to do is use the user object that we just created.
That's our wait cooldown change object.
So we're going to pass in wait cooldown, change instead of this.
And the function that we created are callback.
You wait cooldown, change, cooldown tag changed.
And that's passed in by address.
So the address of operator followed by that function.
So that's what we're going to bind there.
So I'll go ahead and just show the whole line here.
And if we like, we can put these on their own lines so we can really see what's going on.
There we go.
So we're calling registered gameplay tag event passing.
In the cooldown tag we'd like to listen for we're going to use new or removed and we're adding the function
on our newly created wait cooldown change object called cooldown Tag changed.
Now what we do after creating one of these for our async task is we return that object.
So we're going to say return, wait, cooldown, change, and now we'll have a blueprint async task
node that will result in calling this function and it'll set it all up.
That task created will have its ability system component and cooldown tag set and it'll also have its
cooldown tag changed callback bound to that ability system component when it broadcasts that delegate
now an end task, we can clean this up.
We can actually remove that specific delegate from the ability system component.
Well we can do is we can take our AC variable as we're storing that ability system component after all,
and we can take that register gameplay tag event passing in the cooldown tag and the gameplay tag event
type that we used which was new or removed.
This will get that delegate and we can call remove.
We can actually call remove all on it using this because it requires the user object.
So what it'll do is it'll get that ability system component and remove that callback from its delegate
list.
So delegates keep a list of all functions they should call when they're broadcast.
This will remove it.
So this kind of cleans that up.
Now, before we go and use the AC, we could be safe about it and say if is valid AC, we can even just
return.
So if not is valid, simply return and then we can remove that delegate.
Now these async tasks have a function called set ready to destroy, and if we hover over it, we can
see it says call when the action is completely done.
This makes the action free to delete and will unregister it with the game instance.
So we can do that in our end task and we can also call Mark as garbage and that does what it sounds
like.
It marks it for deletion.
Okay, so what do we do in cooldown?
Tag changed?
Well, we know we have delegates that we're going to want to broadcast, right?
And when the cooldown tag has changed, this is the situation where we can know if the cooldown has
been removed, how well we can check that new count.
If new count is equal to zero, then we know that the tag has been removed so we can use one of our
delegates.
The cooldown end delegate.
Cooldown, end.
And we can broadcast.
And this requires that time remaining.
Right.
So what do we broadcast for that time remaining?
Well, we're out of time, right?
The cooldown has zero seconds left on it, which means the cooldown is over.
So that's how we can broadcast that delegate and execute that cooldown end execution pin.
But what about cooldown begin?
How can we know when that has happened?
Well, for that we can use a different delegate that gets broadcast on the ability system component.
That delegate is broadcast whenever a gameplay effect is added so that delegate we can bind it here
is we're going to take our ability system component and get on active gameplay effect added delegate
to self.
We've used this one as well and we can right click and we can just check it out.
It says called on both client and server whenever a duration based GE is added.
So we actually haven't used this one specifically.
We've used the one that's called whenever a gameplay effect is applied to self on gameplay effect,
apply delegate to self.
We've used this one which is only called on the server and we use that because this is for all effects,
instant duration, infinite whatever.
But this one is for only duration based gameplay effects and it's called on both client and server.
So that's great.
We don't have to worry about any rpcs or anything like that.
So this is going to be a really nice delegate to use.
Now we're going to use that and we're going to bind a callback to it, but that means we need a callback,
right?
So if we go to the declaration or usages, we right click on the type of this delegate and go to declaration
or usages.
We can see that a callback for this delegate needs to take in an ability system component, a gameplay
effect spec and an active gameplay effect handle.
So it's going to need three inputs.
So that's what our callback is going to need to have.
So let's go ahead and create that callback function.
We'll have it right here, we'll call it void on active effect added.
Now this is going to take a new ability system component pointer.
We can call it Target ASC.
And if we go back and look, we see that next it needs a const gameplay effect spec reference.
So that will be the next input.
We're going to call this spec applied.
And finally we need the active gameplay effect handle.
So we'll put that there.
And this will be called active effect handle.
So let's generate the definition for this.
There we go.
We see that this is an incomplete type, of course.
It's an active gameplay effect.
Handle that.
We can include that.
And this can't be a reference.
So we can just include it in the h file here.
Looks like it created our function definition inline.
That's not what I wanted.
So first I'm going to cut it and paste it into the CPP file right there.
And then in the header file I'm going to include the header for that type and it's active gameplay effect
handle dot h.
So that'll take care of that red squiggle.
And now we have on active effect added.
We're going to bind this.
So back up to wait for cooldown change.
We have this delegate.
We're going to call, add, object and pass in the instance that we just created of this class that's
wait, cooldown, change.
So that's the user object.
And then the address of the function callback we created on active effect added That's a fully qualified
function with the class name and everything.
So this is what we'll use to know when an effect has been added.
So we'll say to know when a cooldown effect has been applied and this will be to know when a cooldown
has ended In parentheses.
Cooldown tag has been removed.
So now what do we do then in this callback?
Well, we're going to broadcast, right?
We need to broadcast our delegate cooldown start, but this also requires the time remaining.
So how do we get that time remaining?
Well, we can check the gameplay effect using our spec applied and we're going to check the gameplay
effect for its granted tags and we're also going to check its asset tags and see if any of those contain
a cooldown tag and specifically our cooldown tag.
That's our member variable.
So we can take the spec applied.
So we say spec applied dot and we can get all asset tags.
Now this requires a gameplay tag container so we can make one for gameplay tag container and we'll call
this asset tags and we can pass that in.
Now we can also get all granted tags and we'll do the same thing.
We're going to first create an gameplay tag container and we'll call this granted tags and we'll take
our spec applied and we'll call get all granted tags passing in, granted tags.
So now we have to gameplay tag containers and we can check to see if any of them contain our cooldown
tag so we can say if asset tags dot has tag and we'll use has tag exact checking for the cooldown tag,
but it might be in the granted tag.
So we're going to say or granted tags dot has tag exact and this will also check for cooldown tags.
So if either of them have that tag, then we know that the effect that has just been applied is a cooldown
effect.
So in that case we want to broadcast our cooldown, start delegate, but how do we get that time remaining?
That's the question.
Well, the ability system component has functions for doing that.
We can take the ask and there's a function called get active effects duration.
There's get active effect, event set, end time, time remaining and so on.
So if we call get active effects time remaining, well, what does it take?
It takes a gameplay effect query.
So I'm going to comment this out as we create that query and it's an gameplay effect query, we can
call it gameplay effect query.
And what do we set this equal to?
Well, there's a static function to make a gameplay effect query.
It exists on gameplay effect query and it's called make query and there's a bunch of different kinds
of make query.
In fact, we can make a query to specify match all effect tags match all owning tags match any effect
tags.
What we want is match any owning tags.
So if we get active effects time remaining, our query will say get any effects that match this specific
cooldown tag.
And if we set this up right, all of our gameplay abilities that have cooldowns should each use a unique
cooldown tag and that way this will work.
Now we're going to check to see if we match the specific cooldown tag.
But notice that this requires a gameplay tag container.
So we need a container that contains just our cooldown tag.
Well, there's a handy function to create a gameplay tag container that just contains one tag.
We can do so by taking the cooldown tag and we can call get single tag container.
So this function returns a container with the single tag, the one that we called this from.
So there we have it.
We have a query and our ability system component has the function we can call get active effects time
remaining.
We can pass in that gameplay effect query and this function will return some information for us.
What does it return?
Well, it returns a t array of floats.
Interesting, right?
It returns a t array of floats.
Now, why is it a t array?
Why isn't it just a float?
Well, that's because this function will return the time remaining for all gameplay effects that have
any of the owning tags.
So we could have multiple active gameplay effects going on at the same time.
And if any of them have any owning tags that match this container, then they'll be in this array.
So we need to create a t array of floats, but we know it's only going to have one.
So t array of floats.
We're going to call this times remaining, but we know it's only going to have one.
So what we'll say is if time's remaining dot num is greater than zero, really it should be one.
Then we know the first element of it is our cooldown time remaining so we can say float time remaining
equals times remaining of zero.
So as you can see, this is actually quite a bit more versatile.
We could get all the active effects that have a specific cooldown tag and we'll have all of their times
remaining, but we're only going to have one cooldown tag per type at a time, right?
We'll only have one fireball cooldown tag applied at a time.
At least we should.
Now, there's a way to just in case we have multiples, right?
For some reason we have multiple tags at the same time that maybe haven't been cleared out in time.
Right.
We could get the time remaining from this array with the greatest.
Time just to make sure that we're getting the right one.
So as a safeguard, we're going to write a little algorithm that will get out of this array.
As long as it's greater than zero, we'll get the greatest time remaining.
So to do that, we make a little bit of a maximum algorithm.
We make an INT 32 called index, and we started off at zero.
So we start at the first index of our array and then we have a float called Longest or Highest.
Time and we start that off at the time at the first index.
So we say time's remaining.
Of zero.
And then we do a for loop.
So we're going to say for n 32 I equals zero, we're going to loop through.
I is less than times remaining dot num.
We'll do I plus plus.
So regular old for loop and we see if time's remaining at I is greater than highest time.
So if it is this will be a if.
Then we know that that is higher than highest time.
So we say highest time equals times remaining of I.
And then we know that the index is I.
So your standard find the maximum value in an array algorithm.
This is something that is pretty common.
So by the end of this for loop, we're going to know that the correct time.
Time remaining is going to be the highest time.
So just in case it's not the first element, element zero, it's going to be highest time.
And we, of course, don't really need index, do we?
We know the index of the highest time, but we don't really actually use it.
So all we do is step through the array and if any element is higher than highest time, it replaces
highest time and by the end time remaining as high as time.
So we really don't even need time remaining.
We just have highest time and we can just call that time remaining.
Right?
So time remaining.
Like so.
And once we know time remaining, well, we can finally broadcast our delegate, which is called cooldown
start.
So we can say cooldown, start dot broadcast and we can broadcast time remaining.
Okay.
So that was kind of a lot to set up.
But now that we have this class, let's go ahead and run and see what happens when we try to create
one of these tasks in Blueprint.
So we have a couple of errors here.
Missing type specifier int assumed that's wait cool down change dot H.
This means we have an issue with the types that we're using.
We have gameplay effects spec we haven't included that we're going to go ahead and forward declare.
That's a struct f gameplay effect spec.
So that fixed it.
Let's go ahead and run and launch the editor.
Okay.
So I'm going to get my spell globe back open and go to the event graph here.
And what I'd like to do is as soon as our widget controller is set, I'd like to create one of these
tasks we just created and the function that we expose to blueprint is called Wait for Cooldown Change.
We're going to call, wait for cooldown, change.
And here it is.
It's under the category.
Wait, cooldown, change.
And here's our async task.
And look, here's cooldown start, here's cooldown end and here's time remaining.
Now the thing is we need to know what cooldown tag to listen for.
So really each spell globe needs to have its own cooldown tag assigned to it.
Now the problem with that is we don't know what the cooldown tag is until our spell globe has received
ability info.
That's when it knows what ability is assigned to it.
So it's going to need to receive that ability info perhaps in our info struct.
If I break it.
We have an ability tag, an input tag.
We now kind of need a cooldown tag as well, so we'll do that in the next video.
For now though, let's just hard code a cooldown tag.
I'm going to make a little gameplay tag and set this to the only cooldown that will be receiving that's
going to be cooldown Fire Firebolt.
And we'll add this to our sequence here and we're going to need the ability system component.
So how are we going to get that?
Well, remember, we have a widget controller, right?
And it's cast to BP overlay widget controller.
So if we get our BP overlay widget controller, remember that it has an ability system component, we
can pass that in.
Isn't it convenient to have a widget controller that has the ability system component?
It's super convenient.
So what we can do now is just test this out.
Remember, we have lots of spell globes and none of them are checking against the cooldown tag.
So we'll see lots of print strings if we use a print string here.
But I still want to do it.
I want to print cooldown, start when the cooldown starts and I want to print.
Cooldown and when it ends, so cooldown end here.
Let's just see what happens when we save all press play and what happens when we start a cooldown.
So we have to cast a spell.
So there we have it.
And it was kind of quick, but if we go into the folder that has our ability and specifically our cooldown,
we can change the cooldown.
Let's make it longer.
0.5 seconds is hard to test.
I'm going to make it three seconds.
So that way we should see if we launch a firebolt we see start three seconds later, we should see end
and there's end.
So all of our spell globes now know when a cooldown has started and ended specifically for the firebolt.
And if this were some other gameplay tag, let's pass in damage instead.
Well then it's not going to do anything right because there are no effects that have that damage tag
where specifically listening for the cooldown tag.
Now, this is great, but each of our spell globes needs to know what cooldown tag it should listen
for.
So it's going to need its own cooldown tag variable and it's going to need to receive that when it receives
ability info.
So that's what we'll do in the next video because this video has already gotten quite long.
So in the next video we'll make each spell globe know which cooldown it's listening for and then we'll
handle updating its cooldown timer and showing this visually in the HUD.
Excellent job and I'll see you in the next video.