Welcome back.
So we know that our broadcast is working.
We can broadcast the initial values to the widgets, any widgets that bind to our attribute info delegate.
Now, so far, all we're doing is getting our strength attribute.
In fact, we're just getting the strength attribute gameplay tag from our gameplay tags and finding
the attribute info struct from our attribute info data asset.
And then we're setting the attribute value for that by getting our attributes set and getting the numerical
value of the strength.
And once we package all that up into the info struct, we broadcast it up to the widget side so the
widgets can receive it.
Now if we follow this pattern, we have to do this manually for each individual attribute and we have
a lot of them.
In other words, we'd have to say take this local variable info and perhaps rename it to strength info.
So we'd call it strength info and instead of info we're using strength info to set the attribute value
to the actual strength value and then broadcast strength info.
And then we have to copy these lines and paste them below and call this next one intelligence info and
we'd have to find attribute info for the tag and we'd have to change this tag to attributes, primary
intelligence, and then we'd have to take intelligence info and set its attribute value to the value
of intelligence.
So we'd take our attribute set and call it intelligence, and then we'd take our attribute info, delegate
and broadcast intelligence info, and then we'd have to copy this again and do it again for resilience
and vigor.
And then into our secondary attributes.
We'd start with armor, armor penetration and so on.
As you can see, this becomes a very lengthy and tedious and repetitive process.
Now that's okay.
It would work, but it'd be a lot nicer if we didn't have to do that, because every time we add a new
attribute to our attribute set, not only do we have all the boilerplate code to add in the attribute
set class, but then we'd have a bunch of boilerplate stuff to add to our widget controller and that
sucks.
We don't want to have to do that.
Instead, I'd like to just have some kind of generic algorithm right here that doesn't care about what
the attributes are or how many.
There are, however many attributes there are in the attributes set.
I want them all broadcast.
I want to get all of their attribute info from the attribute info data asset, no matter how many or
what they are and just broadcast them all and let the widgets receive them and sort out the information
from there.
So how do we do this?
Well, there are a number of ways, and we're going to start with the easiest way, or perhaps, in
my opinion, the easiest way, and then we're going to refine it and make it more sophisticated.
But you have to start somewhere.
And so where we're going to start is I'd like to make a map.
I'd like to make a map data structure, mapping gameplay tags to something, right?
Something like gameplay attributes.
But the thing is, our attribute set has the attribute accessors to get those gameplay attributes.
Here's our attribute set, here's the attribute accessors.
And thanks to that we have a getter for the gameplay attribute.
Now I don't want to map tags to the gameplay attribute data, I don't want to map tags to the float
value.
That is the attributes numerical value.
I want the gameplay attribute.
Why?
Because there are delegates broadcast when attributes change and those broadcast gameplay attributes.
And I want a map that maps tags to those attributes.
But the only way to get those attributes is through the getters like taking our attribute set and calling
get strength attribute.
And this returns an gameplay attribute.
Now this is a static function and whenever I want my attribute, I want to get it through this static
function.
And if I'd like a map that maps gameplay tags to say this static function, I could use a delegate.
So for example, I can go into aura, attribute set and scroll all the way up to the top and I can declare
a delegate here that's capable of having a function bound to it, such as my.
Attribute accessors those static functions that return gameplay attributes.
Now, if we're going to bind one of those, we have to use a delegate that can bind a function that
returns a value.
For example, we can declare a delegate.
It doesn't need to be dynamic, it doesn't need to be multicast.
It can just be a delegate.
And we have the option for ret val or return value.
If we declare a delegate that has a return value, we put the type of the return value first.
Now I want to be able to bind a function to this that can return an gameplay attribute.
In other words, the return type is F gameplay attribute and I'm going to call this delegate F attribute
signature.
So this delegate type is a delegate that can have a function bound to it, which takes zero arguments
but returns a value of type gameplay attribute.
So then what we can do is we can have a map that maps gameplay tags to these delegates and then we should
be able to always access that map.
And using a gameplay tag, we can find the static function that returns the gameplay attribute.
Okay, so let's make that map.
So we'll make this map here.
I'm going to put it just above all of our attributes.
This will be a t map mapping from gameplay tag to our delegate type we just declared, which is F attribute
signature.
So we'll go ahead and make that map and we'll call this tags two attributes.
Now all that we need to do is just add a key value pair to map gameplay tags to each of our attributes
and we can do that in the attribute set constructor.
So I'm going to open the attribute set constructor here and we're going to add to our tags two attributes
map.
So we're going to call Add and we'll start by adding a couple of our attributes using their tags.
Now to get our attribute tags, we have to include the header file where those are defined and that's
in our gameplay tags.
Dot h.
So if we include that, we can access them.
And I'd like to just make an F or a gameplay tags local variable.
I'll make it a const for gameplay tags reference called gameplay tags and call f or a gameplay tags
get.
And now we can access those gameplay tags and I'm going to add two tags to attributes gameplay tags
dot and we can use attributes.
Primary strength.
Now Tags two attributes takes a gameplay tag for the key and a delegate for the value, and that delegate
has the type F attribute signature.
So we'll call this strength delegate and we can pass that in strength delegate.
Now we haven't bound anything to strength delegate, but it's capable of having a function bound to
it that can return a gameplay attribute struct.
Now our accessors are static, so if we're going to bind one of those static functions to strength delegate,
we have to take strength delegate and we have to use bind static.
This is one of the bind functions for delegates, specifically for static functions and it can take
a static function as an input so we can pass in you or a attribute set, get strength attribute just
like that.
So bind static can take that static function.
Now we're in or attribute set so we don't have to fully qualify the name writers graying it out telling
me that that's redundant we could just pass in get strength attribute that's totally fine.
And now our map has a key value pair the attributes Primary strength gameplay tag is the key and the
value is this delegate with get strength attribute bound to it.
So yes, this adds another boilerplate step to the attribute set, but the attribute set already has
boilerplate steps, so one more is okay, but this removes the necessity to have boilerplate in the
widget controller.
Here's why.
Let's say we have all of our attributes in this map.
I'm not going to add them all right now I'm just going to add the next one for intelligence.
So I'll make an attribute signature called intelligence delegate.
I'll take intelligence, delegate and use bind, static and bind, get intelligence attribute to it.
Then in our tags to Attributes map, I'm going to add gameplay tags, attributes, primary intelligence.
And for the value, it's going to be the intelligence delegate.
Now our map has two key value pairs mapping gameplay tags to the delegate with the attribute accessor
bound to it.
So I just have these two attributes.
Just imagine that I bound all of them.
Okay?
And now it's going to be easier in our widget controller because we don't have to do this manually for
each attribute anymore.
Now all we need to do is loop through that map.
So here's what we can do.
We can make a for loop and I can say auto reference pair and we're going to loop through the attribute
set tags, two attributes map and looping through this tags.
Two attributes map.
What do we want to do?
Well, we want to get the attribute info so we can take our attribute info.
And call find attribute info for tag.
And the tag is something we have in this loop because we have the pear and the pear maps gameplay tags
to attributes.
So for the attribute we pass in our pear key that's the gameplay attribute and find attribute info for
tag will return that for attribute info.
So we'll make a non-const f or attribute info called info and initialize it with this.
But we also need the actual attribute value.
So we need to take info dot attribute value and we need to set that.
Well, we have access to the attribute itself because our pear dot value is a delegate and our delegate
has a function bound to it.
Each of these has the attribute accessor bound to it.
Now because it's a delegate, we need to execute that delegate.
So we call dot execute on it.
That is going to result in returning a gameplay attribute because we're executing the function bound
to it.
In other words, we could create a local gameplay attribute called attribute.
I'm just going to call it Attr and initialize it with this expression that works because executing this
delegate will execute that attribute accessor which returns a gameplay attribute.
But attribute value is a float, so we need the actual value of that attribute.
Well, we can get that from any attribute.
What we do is we take that attribute just to make this clear.
I have the attribute here.
It's called Attr and we call get numeric value.
Now get numeric value requires an attribute set.
Why?
Because the gameplay attribute itself is returned from a static function.
So we need to know which attribute set that this attribute is associated with.
Well, we have the attribute set its A's right, so we can pass that in.
Now attribute here doesn't have to be a local variable.
We could simply take this expression here and replace attribute here with it and remove that line.
That's fine as well.
But now we have our attribute info and we've set its attribute value and we can broadcast it so we can
take our attribute info delegate that we were broadcasting before and we can broadcast the attribute
info.
So now broadcast initial values doesn't have to know how many attributes there are, doesn't have to
know what attributes there are.
It doesn't really matter as long as we can loop through this map and as long as all of the attributes
we care about are in this map.
Now, so far, this map just contains two pairs mapping the gameplay tag to the delegate that has the
attribute accessor bound to it strength and intelligence.
So we can see if this works.
By running this, I'm going to go ahead and hit the debug button.
And now back in the editor.
I'm just simply going to press play and open the attribute menu.
And now check this out.
We now have strength and we have intelligence with their values.
Everything else is blank, but at least we have those two because we added those to our map and we're
now using the map to drive these values.
So this is a great step in the right direction because the attribute menu widget controller now is pretty
automated and generic.
And as long as we add the correct attribute tags and use our delegate, we can fill in that map and
the attribute set here.
But this is actually a little clunky.
You see, if we have a new attribute that we want in this map, we have three whole lines of code here.
We have to make a delegate.
We have to bind a static function to it.
And then we have to add that to the map.
And because this is a gameplay tag mapped to a delegate, it then over here in the widget controller,
we have to loop through the map and call execute on each of these values.
This just makes it all a little bit clunky and sort of inconvenient.
Now what if we didn't have to use a delegate at all?
If we go back into our attribute set constructor, what are we doing here when we call bind static?
Well, we're passing in a function and bind.
Static takes in an input of this type.
Here it says type name T base, static delegate instance and a whole bunch of really kind of horrendous
template input parameters.
It looks a little bit messy, right?
It looks a little crazy.
But really, though, this type that binds static takes is something that we could use as our type for
our map.
Really, if we could just somehow wrap our minds around this template based static delegate instance.
Well, let's go into Aura attribute set for a second and let's just see what happens if we get rid of
that delegate altogether.
And instead of mapping a gameplay tag to a delegate, what if we map a gameplay tag to that type that
binds static takes?
Really this t based static delegate instance?
If we look at the comment there, it says binds a raw C plus plus pointer global function delegate.
These delegates are really just types that can store function pointers.
And in fact, if I just click on this here and right, or at least I can just copy this t static delegate
instance, I'm going to copy it like this.
T Tbase static delegate instance.
I'm going to copy that name, come back into aura attribute set and just paste it right here and I'm
going to right click on it and go to declaration or usages so I can just see it here.
So what's going on here?
Well, we see that it's defined as a class and it's a template.
And we have a declaration right here with a comment.
It says implements a delegate binding for regular cplusplus functions.
And we see where the classes defined here that this is a template class with a template type parameter
called RET val type.
To me that sounds like return value type.
And then we have type name with triple dots called param types.
Now the triple dots is known as a variadic template type parameter.
Variadic meaning there's a variable number of arguments you can pass in any number of arguments.
The number of arguments is variable.
Then we have a user policy.
We have no idea what that is.
Right.
And then another variadic template input parameter of type var types.
Now here in the angle brackets, we see that we have ret val type parentheses, param types, dot dot
dot.
Now this is in the form of a function signature, specifically a function pointer return value type
parentheses parameter types.
And look at this.
We have this using which is basically saying let's create this func pointer alias and set it equal to
this type here because this type here is so hideous.
We want to use a func pointer as a sort of alias for it.
Using like this is a lot like typedef.
It's creating this f func pointer alias.
And this right here is a function pointer with the return type of ret val type.
That's a template input and an input parameter list that's variable depends on what you pass into the
template when you create a T based static delegate instance.
So all of this I know is a lot to take in.
It doesn't matter really how well we understand this.
As long as we understand that we can create a T based static delegate instance specifying a function
type.
In other words, specifying what the function's return type should be, what its input parameter types
should be.
And of course we need to specify a user policy.
So how do we create one of these that's capable of receiving a function or a function pointer of the
type we're interested in, one that takes in no inputs and returns an gameplay attribute?
Well, here's what we're going to do.
We're going to make a T based static delegate instance.
We're going to open up these template angle brackets.
And look, the first one is the function type.
Well, the function type that we're interested in is a function that returns an F gameplay attribute
and takes zero input parameters.
So this is the type for a function with that signature.
Now we need a user policy though.
What's that?
Well, this is referring to a delegate user policy and there just happens to exist in F default delegate
set user policy and we can use that.
We can even check out its usages.
And here in delegate base dot h, here's that struct and there are some comments talking about how when
you extend delegates you should implement a policy struct like this and pass it as the second template
argument to t delegate and t multicast delegate this policy struct containing three classes called and
we have some info here and for our purposes we can just use the default policy.
Now what we really want here is a function pointer t base static delegate instance has a public function
pointer.
Basically this is a member that we can use that assembles those template inputs into a function pointer
with the correct signature.
So what we can do is use the double colons and use F func pointer.
And now we have this type which is a function pointer that can take zero input parameters and return
an gameplay attribute.
So we could call this function pointer if we wanted to.
Now C plus plus function pointers have pretty awful syntax as we've seen.
This function pointer is a type alias for this, which is the syntax for a function pointer, for a
function that returns the retval type and takes in the arguments that we specify.
So now that we have this function pointer, we have the ability to bind a function to it that returns
an gameplay attribute and takes zero inputs.
We can prove that by going into our attribute set CP and taking our function pointer.
We called it function pointer and setting it equal to get intelligence attribute for example.
So this is essentially a variable that can hold a function with this signature kind of crazy, right?
And if you were to take function pointer and call it with parentheses, well, it would return an gameplay
attribute.
Like this.
So that's kind of cool.
What are the implications of this?
Well, we can now get away with not having to bind a delegate anymore.
We can simply use this type.
So if I take this type here and replace F attribute signature now tags to attributes maps, gameplay
tags to well function pointers with this specific signature.
So I can go back to or attribute set and not even bother with the delegate.
Instead we can simply add the gameplay tag and the static function directly get strength attribute.
We don't need the parentheses there, it's just get strength attribute without the parentheses passing
in the function name returns that function's address its pointer.
So same with our intelligence.
We don't need a delegate.
We can just pass in get intelligence attribute without calling it just the function pointer.
Now this is a lot more compact and we can also get rid of that delegate altogether and aura attribute
set.
We can scroll up to the top and completely nuke that one.
No need for that anymore.
But now our tags two attributes map has an awful type.
Right?
This looks pretty bad and function pointer was just for demonstration purposes.
I'm going to get rid of that.
So now the map type is a little scary, but now it's just tags two function pointers.
And if we go back to attribute menu widget controller, notice that our key value pair is no longer
mapping gameplay tags to delegates, but it's mapping gameplay tags to pointers.
And this is the type for a function pointer of this particular signature.
So now when we want to get the attribute value, we just have to take the value which is a function
pointer.
In other words, pair value to this is a function pointer, right?
And to call the function using the function pointer, we use simply parentheses like a regular function
call.
And this will give us that attribute from which we can call get numeric value passing in the attribute
set.
So now we no longer need that execute call that we had to tack on because before it was a delegate.
Now it's just a function pointer we can call it, and get that value.
So it's a little bit cleaner, a little bit nicer.
Why don't we just check to make sure that this can run and that we get our strength and intelligence
values?
Okay, so pressing play.
Opening the attribute menu.
We have strength and intelligence and it looks great.
Okay.
So we're a step in the right direction, I think.
Notice that auto deduces the pair to this type.
So it's reducing that big scary type down to this, just a simple function pointer.
So that's kind of interesting, right?
What if we took that and replaced this big scary type with just that?
If we go ahead and just run in debug mode like this.
And we go ahead and press play and open up that menu.
Well, same thing.
It works.
It works just fine.
So really, we can get away with not using that big scary type and we can just use the raw cplusplus
syntax for a function pointer.
So this might have been a learning experience for you, but you didn't know that you could map gameplay
tags to function pointers.
Kind of cool.
Now, oftentimes function pointer syntax is usually pretty nasty.
I mean, nobody likes to look at function pointer syntax.
It's usually something that we hide away.
It's like that ugly decoration in your home that you stick in the storage under your stairs.
It's just something you don't want to look at.
And there are ways to hide this away in favor of something a little bit more attractive.
So one way to do that is creating an alias.
So we'd like to hide this, but I'm going to control Z back to where we had it here when it was our
base static delegate instance.
And this is the type that I'd like to sweep under the rug and hide in favor of a more attractive identifier.
So here's what I'm going to do just above the class.
Outside of it, we're not in the class anymore.
I'm going to make a typedef.
So typedef creates a type definition.
It says take some type that perhaps you feel is extremely ugly and painful to look at like this one
and then give me an alias that I can use instead.
So attribute func Pointer, for example.
And this now attribute funk pointer is equivalent to this big monster right here.
So I can take Attribute Funk pointer and I can replace this with it.
Now.
Unreal Coding standards say that a typedef should have the prefix of the thing that it represents.
Now it represents this t based static delegate instance, which is a template type, but it's not really
a template type anymore.
We're specifying that type to be gameplay attribute.
So we're going to use an F as the prefix rather than a t.
A T would imply that this attribute funk pointer is a template, but it's not.
It's specific to this signature here.
So now that we have F attribute func pointer, we can replace this big scary type here in the map with
f attribute func pointer instead.
And now this looks a little bit easier to deal with and we can just throw that typedef up there and
forget about it.
Now that's one way, but T based static delegate instance is a template.
And what if we wanted this to be more generic?
Because right now it's pretty specific to functions with this signature?
Well, we can't make this alias a template, but modern cplusplus has ways to achieve that.
Here's how we can achieve an alias that is a template.
First of all, we say template and in angle brackets we say class t t is the template type parameter
and we use the using keyword.
So we say using and we create our alias name.
It's going to be a true template now.
So we're going to call this T, we'll call it T attribute func pointer and we set it equal to and then
we use that big hideous type.
So I'm going to paste it there.
But the difference here is that this the function type, it's now our template type and there's a semicolon
there.
But if this is a template type alias, we have to use type name here.
That's just the syntax for Cplusplus.
So now this t attribute func pointer is an alias for this big ugly type only.
It's a template, so it's essentially a function pointer capable of storing the address of a function
of any function signature we choose.
So now our map can map from gameplay tags to t attribute func pointers, but being a template, we have
to specify the template type and the template type is the whole function signature for.
So that's going to be F gameplay attribute with parentheses.
This is a function that returns an F gameplay attribute and takes zero inputs.
And now our t attribute func pointer is actually more versatile.
In fact, it doesn't really have anything to do with attributes, it just takes static functions.
So this could be renamed to t static func pointer and if we rename it there we have to rename it here.
So anyway, that's just some modern cplusplus that we can use if we want our type alias to be a true
template and then we can use it to create a function pointer that can point to any type of function.
We just specify it here and the video is getting a little long, so we're going to wrap it up pretty
quickly.
But just to show you what I mean, we could make a T static function pointer to point to any function
signature.
So say we want it to point to a function that returns a float and takes in an int 32 as well as a float
and another N 32.
And we call this.
Random function pointer.
Well, this function pointer is capable of storing the address of a function with this signature, which
would look like this.
It would have a float return type.
We'll call this random function and it'll take an int 32.
We can even say int 32 I it can take a float, we'll call it F and an int 32 called I to.
Just as an example, and I'll just provide the function definition here.
Just return zero dot f and this can be a static function and random function pointer is capable of storing
its address.
So just really quick, I'm going to go into the attribute set constructor and say random function pointer
equals random function and we can even call it, we can say float f equals and we can take that random
function pointer and call it and it requires an int, a float and an int.
Isn't that crazy?
So it's just an example showing that our type alias is more versatile than just being able to store
a function with a specific signature, which is what our delegate initially was capable of.
It could only bind a function with a specific signature, but now we have this t static function pointer
that can bind to any function.
So pretty cool.
So now that our tags to attributes map looks like this and we have this type alias.
I'll go ahead and leave this comment here for now and say.
TypeDef is specific to the game play attribute.
Signature, but t static func pointer is generic to any signature chosen.
Okay.
So the last thing to do is just to make sure this is working and just to show that this is a great deal
better and easier to set up.
We can take our tags to attributes, map and add and let's add gameplay tags, dot resilience.
We'll get that tag and we'll use get resilience attribute without calling the function.
We're passing in the pointer again and we'll do vigor as well.
So take tags two attributes dot add adding its gameplay tag so there's vigor and we'll use get vigor
attribute.
Now this adds it to the map, and that's all we have to do because our widget controller simply loops
through the map.
So let's go ahead and test this out.
And pressing play and opening our menu.
We have strength, intelligence, resilience and vigor with all the correct values.
So now we've set ourselves up to be able to expand this system a lot easier.
When we add new attributes, we don't have to touch the code in the widget controller.
We touch the attribute set, which is where the attributes are defined.
This is the only place that we should have to do anything different when we add a new type of attribute.
And all we need to do now is the added step of adding that attribute to our map, which maps gameplay
tags to attribute accessors, specifically the static functions that return those gameplay attributes.
We can pass those functions straight in as pointers to our map.
Pretty sophisticated.
So as just a quick recap, this is possible because we've created a static function pointer to static
func pointer which is a template and it's a type alias for this big scary expression where T is the
function signature and we have a map mapping gameplay tags to t static func pointers specifying the
type that function signature to be a function that returns an gameplay attribute and takes zero input
parameters.
We call this tags two attributes and in our attribute set constructor we're creating that association
between gameplay tags and the attribute accessors so that way we can always access the gameplay attribute
by simply using the tags.
Two attributes map and we're doing that in the widget controller by simply looping through this map,
finding the attribute info for that particular gameplay tag.
That's the key in the pairs.
And then we're using the value that function pointer, calling it to get the attribute and from the
attribute getting its numerical value as that's a function on gameplay attributes that requires us to
pass in the attribute set.
We set that on our attribute info and broadcast it up so our widgets can receive it and show that info.
So this is all really nice, but this is only broadcasting initial values.
What about when the values change?
Well, that's something we're going to have to take care of and we'll take care of that next.
So thank you for sticking with me.
I know this was a lengthy lecture, but there was a lot to learn about here.
And now we have some very useful and a bit more advanced cplusplus skills in our tool belt that we can
use.
So excellent job and I'll see you soon.