Welcome back.
Now, our system is looking pretty good so far.
We have the ability to broadcast our initial values, and we've done so with our primary attributes.
We've added those to our map that maps gameplay tags to function pointers, and those pointers point
to our static accessor functions.
Now this is great for initial values.
That's something that we get broadcast when our widget is constructed.
Our attribute menu calls, broadcast initial values.
It's blueprint callable, but we also need to update the values as they change.
So if strength changes, we want to broadcast the latest value of strength up to our widgets so they
can update.
Now, in order to do that, we could have a callback function or even just a lambda that will broadcast
the value of our attributes as soon as they change.
As we know that the ability system component has delegates that it broadcasts when they change.
Now, we would bind those callbacks or lambdas here in bind callbacks to dependencies and it would look
like this.
We would take our ability system component and we would use get gameplay attribute value change delegate,
which requires a gameplay attribute.
And we could pass one in, which means we'd need to cast to or attribute set.
So we would do something like take this line here and paste it there and then use that attribute set
to call one of the attribute accessors like get strength attribute, for instance.
This returns a delegate that we can bind a callback or a lambda to.
So for example, let's say we called add lambda here and inside these parentheses we could create a
lambda.
Here's our square brackets, our parentheses and the lambda body.
Now we do need to worry about the lambda signature.
But before we fill that out, instead of just getting our strength attribute, why don't we just wrap
this in a for loop and loop through all the attributes?
We can do that.
We can say for we can do an auto reference called pair and we can loop through the attribute set tags.
Two attributes.
Now, if we loop through this map, we can take these lines here.
I can control C to cut them and simply paste them in and I can just remove them here.
So now these lines are inside of the for loop.
And now that we're in this for loop and we have access to each pair, which maps the tag to the attribute
accessor, we can just call that attribute accessor.
Here we can call pair dot value with parentheses for the function call.
And now we're getting the gameplay attribute value change delegate for every single attribute in our
map.
Now we can worry about this lambda here.
Now for get gameplay attribute value change delegate whatever we bind to this delegate needs the correct
signature.
Right?
So what was the signature that our function or lambda needed to have?
Well, it needed to take a const reference to F on attribute change data.
So we have a const reference, we'll call that data.
And now we can do what we like in here.
Now, really all we need to do is get our attribute info, get an F or a attribute info struct, make
sure that its attribute value is correct and broadcast that struct up to any widgets.
So just like we've done down here, we can make a local F or attribute info called info and we can get
our attribute info data asset and find attribute info for tag.
I'm going to copy that line and paste it here.
Now attribute info has red squiggles.
We have to capture this if we want to use it.
Now we also have red squiggles on pair dot key.
Well, we can capture the pair.
Now you can capture by value by reference.
Really?
Whichever you want.
We don't want to capture by reference because by the time this attribute changes and the delegate gets
broadcast, this pair variable, which is local to this for loop will have long since gone out of scope.
So we just want to capture pair by value.
This will cause the lambda to have a copy stored locally within it.
So we have a copy of the key, which is the gameplay tag.
That's what we want.
Now we want to set the attribute info attribute value.
So we're going to say info dot attribute value and we want the current numerical value.
Well we can get pair dot value.
That's a.
Pointer.
We're going to call that function to get the attribute itself and from the attribute we'll call get
numeric value into which we must pass the attribute set I can pass in as however, that means I must
capture the attribute set.
So not only do we capture pair, we're also going to capture as as well.
So that pointer will be there.
So now we have info.
Its attribute value is filled in, we can broadcast it.
I can just copy this line and paste it here.
Now, whenever the attribute changes, the lambda will be called in response to the attribute change
delegate broadcast.
So now we just need to make sure this is working.
Whenever any of our attributes change, we want to make sure that that change is reflected in our attribute
menu.
But let's also go ahead and add the rest of our attributes to the tags to Attributes Map.
We have our primary attributes.
Why don't we get all of our secondary attributes added to it as well?
So we'll go back to Aura attribute set and do that.
Now we've only added to this map gameplay tags and attribute accessors for the primary attributes.
Let's do it for all the attributes, shall we?
So this will get a primary attributes comment, but let's do this for all of the secondary attributes
as well.
So I'm going to go ahead and do that.
Now.
We know what to do, right?
We put the gameplay tag and the attribute accessor, so let's go ahead and time lapse this part.
Okay.
We now have all of our attributes, primary and secondary, that we want to see in our attribute menu,
and our widget controller is going to make sure that our attribute menu will receive data whenever those
attributes change.
So now we can go ahead and test this out and make sure that our attributes are updating as they change.
So the first thing I want to do is open our attribute menu.
Make sure we get all the attributes and that their values are correct and it looks like we do.
There's all of our secondary attributes.
There's all of our primaries.
They're all here.
And that looks great.
Now I'd like to change the values of them so that we can make sure that those changes are reflected
in the attribute menu.
Now we do have our test actor, which is applying a test effect, right?
GE test attribute based.
We can go back to that and open it.
And if we scroll down, we see that it's setting our health, but now we can change any attribute we
want.
So what I'm going to do is I'm going to remove all the modifiers by hitting the trashcan.
And add one new modifier and change one of my attributes.
Let's just change strength.
For example.
We'll add to it with modifier op add and we'll add a value of 15 to it.
Let's compile that.
And now if I press play, I see that my strength is ten, and if I walk into this test effect actor
now my strength is 25.
Now that's great.
It worked when the attribute menu was closed, but let's make sure that that change happens if the attribute
menu is already open.
So here we see strength is ten, and as we walk into the effect actor, it goes up to 25.
And we can also check to make sure that our derived attributes are updating as their backing attributes
change.
For example, armor and armor penetration are dependent on resilience.
Well, let's take our test attribute and change this to add to resilience.
Instead, let's see how that works.
So I'm going to open my menu.
Here's resilience.
It's 12.
Armor and armor penetration are nine and four, respectively.
And as I walk into the effect actor, not only did resilience go up to 27, but armor went up to 13
and armor penetration went up to seven.
So we see that all of our mathematical relationships are kept up and everything looks great.
So now we have this nice, robust system where our attribute menu has widgets, each of which have their
own attribute tags, and they all respond to delegates broadcast up from our widget controller and they
can check that info.
And if it has the tag that those widgets have assigned to them, they'll use that info to update themselves.
So this looks really good.
And if we hadn't gone through the trouble of making that map, then we'd have to manually bind callbacks
or lambdas to each of our 14 attributes, which is a lot of work.
And it would make this file really large for no reason.
Now it looks like I've got an include for or a gameplay tags.
I could probably remove that.
It looks like it's not being used.
Okay, this is looking good.
Now, before we wrap up, I think there's one last thing we can do.
I see some repeated code here.
This doesn't follow the dry principle, so I can create a function that will perform these three lines
of code.
All it really needs is a gameplay tag and a gameplay attribute.
Let's go ahead and make that function real quick.
So an attribute menu widget controller will make this a private function.
Just internal to this class, It'll be void.
And we'll call this.
Oh, why don't we call it broadcast attribute info?
So broadcast attribute info it will take a const gameplay tag.
Called attribute tag and a const F gameplay attribute.
That'll also be a reference and we'll call this attribute.
Let's go ahead and generate the definition and we'll perform these three lines that we've repeated ourselves
with.
Now find attribute info for tag needs the gameplay tag that's going to be attribute tag.
We'll pass that in and then get numeric value that comes from the attribute.
So we're going to replace pair dot value with attribute.
And this just needs an attribute set.
We don't need it to be cast to aura attribute set.
We can just pass in attribute set and this can be a const function.
I'll go ahead and make a const like so.
And now broadcast attribute info can be reused in both of these other functions.
So we'll call it here.
Broadcast Attribute info.
This one needs the attribute tag that's pair key and the attribute itself.
That's pair dot value, but we're calling the value function like so.
And I can get rid of these three lines and I can call the function down here as well.
Broadcast attribute info.
We can use pair key and pair dot value.
Calling the function and we can get rid of these three lines.
Okay.
I'll go ahead and remove the empty space here and we're checking attribute info here.
Might as well check it up here as well.
So now we have a nice function.
We can call and we can test this out as well.
I can go ahead and close out of the editor saving all.
And I can run this in debug mode.
Before I do, I'm noticing that we're no longer using as here in our capture list.
We don't need to capture that and we can go ahead and hit debug and test this out.
So back in the editor, I'm going to open up these assets and make sure that I'm still changing my resilience
with my test effect actor.
I can take a look, make sure all of these look correct and up.
My resilience there looks good.
I can even do it multiple times.
And it looks like things are working.
This is great.
So kind of a lot of work to get a small result, but the result of this is actually not insignificant.
We now have code that's reusable.
We have code that doesn't need to change.
If we change something in our attribute set, such as adding new attributes.
All we have to do in our attribute set is make sure that our attributes are associated with their attribute
tags, which we do here in the attribute set constructor.
So as long as we want an attribute associated with a given tag, we just add it to our map here and
then we can always loop through that map if we want to do something for all attributes.
So this is a very powerful thing and it saved us a lot of tedious repetition, which is always good.
So excellent job.
We now have an attribute set.
It's looking good.
It's now showing attribute values and responding to their changes, which is all great.
Now we have this notion of gaining points and spending them on attributes to upgrade them and we'll
implement that as we keep developing this project.
But for now, our attribute set is looking good.
Aside from probably some visual tweaks that we may wish to make, the framework is there.
So once again, excellent job and I'll see you in the next video.