Welcome back.
So now that we have all these secondary attributes, we need a way for them to be derived from other
attributes.
In other words, if armor is dependent on resilience, then every time resilience changes, I want my
armor to change accordingly.
Now, the way we can do this is by applying an infinite gameplay effect somewhere at the beginning of
the game, perhaps when we apply all of our startup effects in our character class.
So we're going to take care of that.
We're going to make an infinite gameplay effect that will set all of our secondary attributes as soon
as their base attributes change.
We're going to add this to our character class.
Let's go into the public folder, into character and in Aura Character Base.
Let's see what we've got here.
It looks like we have a gameplay effect class for the default primary attributes.
We can just make one of these for the default secondary attributes.
I'll go ahead and copy this line and paste a copy of it, but we'll change this to default secondary
attributes.
So now we have a class to apply here.
Now it's important that we initialize these attributes after the primary attributes have already been
initialized.
Why?
Because the secondary attributes depend on the primary attributes.
So those attributes need to have their values set first.
However, if this is going to be an infinite gameplay effect, then it will always be in effect.
Whenever the primary attributes change, the secondary attributes should adjust accordingly.
So we need to apply this effect.
How do we do that?
Well, we're handling it in a character.
Let's go to the private section and the character folder and open our character and we see that in init
ability actor info we're calling initialize primary attributes.
Well, we don't have that defined here.
It's defined in the base character class.
So let's open a character base and here it is.
Initialize primary attributes.
Well, we can do something similar for the secondary attributes.
We can have an initialize secondary attributes.
Let's go ahead and do that.
So back in our character base, I'm going to make another function for the secondary attributes.
It's going to be void and it will be called initialize secondary attributes and it will be const.
Let's go ahead and generate the definition for it.
And it's going to be very similar to initialized primary attributes.
We're going to check our ability system components.
Let's copy that line and paste it.
We're also going to check our class, the default secondary attributes.
So let's go ahead and check that default secondary attributes.
After that, we can make a context handle with make effect context.
Let's copy that line and then we can make a spec handle, make outgoing spec passing in default secondary
attributes and our context handle.
So we'll copy this line.
And at this point I'm realizing we're copying and pasting a lot of the same stuff, which is an indicator
that we should probably refactor some things into a handy function so we're not repeating ourselves.
So we'll keep that in mind.
Now the last line is to apply the gameplay effect spec using to target, and we're passing in the ability
system component.
We'll go ahead and paste that in there.
So this is a lot of almost the same stuff.
The only difference is the actual gameplay effect class that we're using.
Everything else is the same.
So I think this is a good opportunity to refactor this into a reusable function so that way we don't
have to have two functions that basically do the same thing.
They could be one function that just takes in an input parameter for the thing that's not constant,
right?
For the thing that doesn't change.
So why don't we do that?
Let's go to our character base and instead of initialize primary attributes and initialize secondary
attributes, why don't we make a new function?
And this can be general, It can be simply to apply an effect to self.
So we can call this apply effect to self and this can take a subclass of you gameplay effect called
gameplay effect class and we can make it versatile enough to take in a level so float level and other
than that it's just going to do what we're already doing here.
Basically apply an effect to self.
So why don't we just go ahead and generate the definition for this?
And it's going to be what these two functions down here are doing.
So we can just copy one of them and paste it here.
But instead of checking default primary attributes, we're checking the effect class we're passing in.
We're making an effect context.
We're making an outgoing spec only instead of a specific gameplay effect class, we're using the one
passed in and instead of hard coding one for the level, we'll go ahead and use that level passed in
and then we're applying the effect to the self.
So that's really all we have to do here.
And I think instead of having two separate functions that pretty much do the same thing, we can just
have one function to initialize all of our attributes.
Why don't we go ahead and just make that function and then we can replace these two?
So I'm going to make a void function called initialize default attributes.
And looks like I had a typo there and this will take no inputs and this will handle initializing all
the attributes.
So all it has to do is call apply effect to self.
So we're going to call apply effect to self passing in the default primary attributes with one for the
level.
And then we can call apply effect to self passing in the default secondary attributes.
Like so.
And now we only need apply effect to self and initialize default attributes.
We don't need a separate function for each, so I'm going to remove, initialize primary and initialize
secondary attributes.
And back in the header file I'm going to remove those function declarations.
Now we just have to clean functions and apply effect to self.
Could be a const function.
So let's go ahead and make that const to maintain const correctness.
And now we know that it doesn't change the values of member variables.
And now we have initialized default attributes by the way, that could be const as well.
Let's go ahead and make these both const.
And now we only need to call initialize default attributes.
So we're going to need to go back to Aura character.
And instead of calling this function, which no longer exists, we're going to call initialize default
attributes and that should take care of it.
And now, as long as our default secondary attributes to subclass of has a value set, then this should
work.
And if it doesn't have a value set, well, we're going to get a crash because we're using a check assert
here.
So the next step is to create our gameplay effect.
It's going to be an infinite gameplay effect that's going to set the values of our secondary attributes
and they're going to be backed by the primary or other secondary attributes.
Okay.
And now that things are looking good in the editor, let's just clean up a couple things in the attributes
set.
Let's go up to our constructor where we're initializing Max Health and Max Mana.
We now no longer need to do that as those will be driven by our infinite gameplay effect.
We're just initializing health and mana and we're even going to get rid of these in the future as well.
And then another thing we did in a previous video was we commented out the clamping that we did here
in pre attribute change and the clamping we did in post gameplay effect execute, we're going to reinstate
that enforcement.
So the clamping is there and with that we can compile and save.
And here in the editor we just need to make our infinite gameplay effect.
So we'll go into content blueprints, ability system and gameplay effects.
And here we have a folder for primary attributes.
Now it only has a single effect in it and we can make another folder for secondary and have a single
effect in that.
But that's a little silly.
We don't need one folder per gameplay effect.
I'm going to rename this folder to default attributes.
And then we'll have our default primary attributes.
This is GE or primary attributes.
We're going to have another gameplay effect.
And this one can be GE or secondary attributes.
Now let's open the aura, secondary attributes.
And the first thing I'm going to do is set its duration.
This one is going to be infinite because I want this to constantly be, in effect any anytime one of
my backing attributes changes.
I want to see the change reflected in the derived attributes.
Now, this isn't going to check every frame or anything like that.
It's just going to trigger and response to one of the backing attributes changing.
And before we start adding modifiers, I'm going to make sure to add this aura secondary attributes
to the aura character.
So we'll go to blueprints, character, Aura, BP Aura, character.
And we're going to want to add or a secondary attributes.
So down here in attributes, here's the default secondary attributes.
I'm going to choose GE Aura, secondary attributes, compile and save.
So let's go to Aura, secondary attributes, and we're going to add a modifier.
And expand it.
And the attribute that I'd like to affect is going to be let's choose armor, and here's how derived
attributes have to work.
They have to be infinite and the modifier OP has to be override.
If you want this armor value to be based on a calculation that involves the other attributes.
And for the modifier magnitude, of course we're going to choose attribute based and then we can choose
which attribute backs this attribute, right?
So armor is going to be dependent on the backing attributes.
I'm going to choose resilience and we have to not snapshot.
We'll learn about Snapshotting later for derived attributes.
We do not snapshot and the attribute source will be the target.
In this case, the target and the source are the same, so it doesn't matter.
But I'm going to choose Target and the mathematics involved is up to us.
This is a game design decision.
So what should the armor be?
We know that our resilience starts off at I believe it's 12.
I can double check.
Armour's resilience starts off at 12.
Yes.
And by the way, because we have this applied effect now.
Armour is 12 as well, because it's backed by the resilience attribute without any coefficients or anything.
So it's just equal to resilience at this point.
Notice the base value is zero, but the current value, because this is an infinite gameplay effect,
is 12 and that's the value that will be used in the game if we query the armour.
Now that's a 1 to 1 relationship.
If resilience is 12, armour is 12, but that doesn't have to be the case.
Armour could be a base value of, say, six.
If we want a base value, we would say six should be the post-multiply additive value and then our coefficient
can be some fraction or it could be greater than one if we want that.
And that's going to be multiplied by resilience, right?
So if resilience is 12, let's say that we want our armour to be half the resilience plus six, that
would be really it would just be 12 again, right?
If we go back and press play and we do the math.
Armour is 12, but that's because it's half the resilience.
Resilience is 12.
Half of that is six plus a base value of six.
So it's 12 anyway, but we could have the coefficient be 0.25, for example.
And then we have a base value of six plus a quarter of the resilience.
If resilience is 12, then a quarter of that is three, and then our armour will come out to nine.
So if we press play show debug.
Armor is nine.
See how this works So we can choose a very basic mathematical calculation using these coefficients.
We could even add a pre multiply additive value if we want a little bit higher of an armor rating for
a resilience of 12.
Let's say we add one to it first and that way we'll have resilience plus one.
Actually, to make the math easier, let's do resilience plus two.
That gives us 12.
Plus two is 14, right?
And then our coefficient is 25.
That's three and a half and then plus six.
So six plus three and a half.
That's nine and a half.
And if we go ahead and see it, 9.5 is the armor C, So based on whatever values we do here, then we're
going to get a different result.
And as the resilience changes, our armor is going to change accordingly.
And we can test that.
We can test with our test actor here because our test actor is using a test effect and we can use that
test effect to change the resilience and see the change in armor, for example, so we can go to blueprints,
actor, test actor and go to test attribute based.
And here we're testing out things, right?
We can change these modifiers.
It doesn't really matter which ones we change.
Let's just leave those and we'll add a modifier to change resilience.
So we're going to choose resilience.
We're going to use Add and we're going to add a value of two to the resilience.
So now we can check out how our armor changes once the resilience goes up by two.
So our armor is 9.5.
Ah, resilience is 12.
We enter the volume, resilience goes up to 14.
And look at this, armor goes up to ten.
Pretty cool.
Now this is what RPG games are all about.
We change things and other things change.
Everything is related by mathematical equations.
And rather than just pulling numbers out of the air and plugging them in and saying, Well, I'd like
this to be 0.25 and this to be two and this to be six.
Ideally, a game development team is going to test these things out.
In fact, it's not uncommon for an RPG game to be prototyped as a board game first before being made
into a video game.
And then these types of mathematical relationships can be fleshed out at the board game stage before
the video game stage.
That's a pretty common thing to do.
The point is that these values are not just random, they all affect the game.
And for the game to be balanced, they all have to be balanced well, which means playtesting testing
the game out, seeing how things feel and so on.
So it helps to have these values mapped out, at least have some initial values decided upon, Right?
And then as the game gets tested and you sort of get a feel for how things work, then you can hone
in on the sweet spot for these values.
And keep in mind that the values are probably going to change.
Sometimes you might have these coefficients and values different depending on, say, the level of the
gameplay effect, which may depend on the level of the character.
It gets complicated, right?
And that's why each of these can be tied to a curve table and those curve tables can adjust these values
as the gameplay effect changes.
So for this though, we're going to just choose some values if you like.
You can choose the same coefficient and pre and post multiply values for all of the secondary attributes
if you want.
If you don't want to sit down and start thinking about values, you can do that.
But what you're going to do is you're going to add a modifier here for all of your secondary attributes
and you're going to base them on primary or other secondary attributes, according to the plan laid
out, either by you yourself, if you're designing your own attributes or in the previous slide.
So remember this slide.
Now, if you want to copy these, which are pretty common, I mean, this is inspired by some of my
favorite RPG games.
So a lot of games use relationships similar to these, right?
So you can't really go wrong if you use these, but you could also choose to design this game the way
you want to.
But if you want to use these, then go ahead and take some notes or take a screenshot of this and save
it or just write it down.
And as you're adding your modifiers to our infinite gameplay effect, take a look at each of these secondary
attributes and their backing attribute, which is the gray little tag to the left of each attribute
and choose coefficients and pre and post multiply values for each of them.
That's going to be your quest.
So your quest is to add the modifiers for each of your secondary attributes.
These are going to be attribute based, and this means you're going to do a little bit of planning and
designing, right?
So figure out the relationships that these attributes should have.
And then I'd like you to change a primary attribute value using our test actor with our test actor's
gameplay effect so that we can change a primary attribute value and see one of those derived attribute
values change in response.
So this is going to probably take a little bit of time.
I recommend you take a piece of paper, maybe you have a notebook of notes for this course and map out
your relationships for each of these attribute values.
Map out the coefficients pre and post multiply constants and decide how you'd like your secondary attributes
to change based on the primaries.
So a little bit of game design going on here.
So pause the video and conquer this quest now.
Okay.
So we're ready to tackle this quest.
And before we do, I'd kind of like to share with you a little bit of my design process.
So here's a page from some notes that I've jotted down during the design stages for this project.
And as you can see, this looks very much like brainstorming.
It's messy, and you'll notice that it's filled with a lot of estimations right back of the envelope
calculations or equations, just ballpark figures that I felt might be appropriate in gameplay.
And these are of course things that can be adjusted as we continue to test out gameplay in the project.
Now this is very commonly what my notes look like.
I very much like to write notes by hand.
I also use tools on the computer, but when I'm brainstorming I like to sit down and kind of just play
around with ideas.
And you'll notice that this page contains things that made it into the final project and things that
didn't.
You'll see that I have speculation, ideas I think out loud when I'm taking my notes.
I think about possibilities, things that may or may not be a good idea.
I just jot them down and then later pick and choose which things I'd like to keep.
So this was my thought process.
And with these values, let's go ahead and implement our modifiers for the derived attributes gameplay
effect.
So I have my coefficient pre and post multiply values and this is all for my armor attributes and it's
backed by resilience and I think it's looking good.
So I'm going to go ahead and minimize that index in my modifiers array and add the next one and the
next one is going to be for armor penetration.
So I'm going to go ahead and add that one.
Now, each of these is backed by a single attribute, but we've seen that we can have multiple modifiers
offers for any given attribute that we'd like to change.
So keep that in mind.
One attribute can be backed by multiple other attributes if we want.
Now, recall that our first modifier was set to override for the modifier OP.
These are all going to need to be override, so I'm going to set this next one for armor penetration
to override modifier magnitude will be set to attribute based and the backing attribute for this one
I'm going to expand that is also resilience.
Now, the values I've chosen for this one are a pre multiply additive value of one, a coefficient of
0.15 and a post multiply additive value of three.
We're going to set the attribute source to target and we're not going to snapshot and there's armor
penetration.
Let's move on.
Our third is going to be block chance.
So the attribute will be block chance, it'll be override.
We're setting this to attribute based.
The backing attribute is going to be in this case armor.
So attribute to capture will be armor.
And I want this to have a post multiply additive value of four and a coefficient of 0.25.
With no pre-multiply additive value.
We're going to have our attribute source set to target and we're done.
Let's add our next modifier and this will be for critical hit chance.
So we'll choose critical hit chance.
It's going to be override.
And let me just make sure.
Yes, my block chance was set to override.
It's very important to keep double checking these things as one incorrect setting could make things
not work.
Right.
So for critical hit Chance, we're going to have our modifier attribute based.
Our backing attribute in this case is going to be armor penetration.
So we'll set our backing attribute to armor penetration and attribute source to target.
And I'd like to have a post-multiply additive value of to a coefficient of 0.25 without a pre-multiply
additive value.
And that's it.
That's critical hit chance.
Now for the next one and this will be critical hit resistance.
So selecting that one, making this override, just going to double check.
I did that for the last one and I did.
This will be attribute based.
We'll get our backing attribute and for critical hit resistance looks like I skipped critical hit damage
that's okay we'll get that next critical hit resistance is going to be backed by armor so attribute
to capture will be armor capture from the target and critical hit resistance is going to be equal to
a post-multiply additive value of ten.
That's a base value of ten plus 0.25 times the armor.
Okay, so that's critical.
Hit resistance.
It looks like I missed.
These don't have to be in any particular order necessarily, but in my slide I had critical hit damage
in between chance and resistance.
That's okay.
We'll do critical hit damage next.
So for this one, we'll choose critical hit damage.
It's going to be override.
And again, I'm just going to double check, make sure that I set that for the last one.
That's something that I tend to forget.
That's one of the ones I commonly forget.
It's going to be attribute based backing attribute for critical hit damage is going to be armor penetration.
So attribute to capture is armor penetration capturing from the target and for critical hit damage,
this is going to be equal to five plus 1.5 times armor penetration.
So we have these base values, right?
And you would think that the base values should go up as the game progresses.
And that's why we have curve tables.
And so that's something to think about.
How are these values going to scale up with the game?
Right now, they are scaling up in the sense that as the backing attribute gets larger, so does the
secondary attribute.
But of course the proportions can be scaled by these curve tables.
So something to keep in mind.
All right.
So that's critical hit damage.
Let's get our next one.
This will be health regeneration.
So let's get health regeneration.
It's going to be override.
We're going to set this to attribute based.
Our backing attribute for health regeneration is going to be vigor.
We're going to capture from the target.
And for health regeneration, it's going to be set to a base value of one.
This is how much we're going to regenerate per second plus 0.1 times vigor.
That's how it's going to work.
That's how the equation looks.
Health regeneration equals one plus 0.1 times vigor.
And there's our health regeneration.
Next is going to be mana regeneration.
So the attribute will be mana regeneration.
It's going to be override.
It will be attribute based.
The backing attribute for mana regeneration, though, is going to be intelligence captured from the
target.
And this will be it'll be similar to health regeneration post multiply will be one coefficient will
be 0.1.
So basically every second we're going to gain one man up plus 10% of our intelligence.
And there's our mana regeneration.
Next we have our max health and Max Mana.
So let's add those two.
I'm going to first add Max health and this will be override.
It's going to be attribute based.
We'll set our backing attribute.
And for max health, that's going to be vigor.
So we're going to choose vigor capturing from the target and max health.
I'd like to have a base value of 80, but we're going to add 2.5 times the vigor.
So coefficient is going to be 2.5.
So in other words, our max health is going to be 80 plus vigor times 2.5.
So let's just say for math sake, vigor is ten, then ten times 2.5 is 25.
25 plus 80 is going to be 105.
So if vigor is ten health max, value will be 105.
That's how it's going to work.
And I'd like to do something similar for Max Mana.
So let's add our ninth element here.
That's going to be Max Mana.
It's going to be override and it'll be attribute based.
The backing attribute for Max Mana is going to be intelligence.
We'll capture from the target and intelligence.
I'd like to have a base value of 50 and we'll have a coefficient of two.
So Max Mana is 50 plus two times intelligence.
So if intelligence is 15, then we'll have a max mana of 50 plus two times 15.
In other words, 80.
That's how it would work.
And that's our max mana.
Okay, perfect.
So let's go ahead and compile and save.
We have quite a few changes, so I'm going to hit save all.
So we save this test attribute and now we can simply test it out.
I can show debug.
And here's the thing.
Once we start having lots and lots of attributes, well, our screen is kind of too crowded to show
them all.
We have too many secondary attributes all being affected by these modifiers on an infinite gameplay
effect, so it's kind of hard to see, but we can see our armor is 9.5, our armor penetration 4.95
critical hit resistance critical hit chance.
We can see these numbers for some of them.
We see our max health is 102.5 and this is cool but we're quickly seeing the limitations of show debug
ability system, aren't we?
We're seeing these limitations because in a serious game where we have these types of mechanics, it's
going to be hard to show them all in a debug setting.
And that's why like most RPG games, we're going to want a menu that shows us the values of our attributes.
We're going to want an attribute menu now that's going to be a widget that we can show to the screen,
which means we'll probably want a button that we can click to show that attribute menu.
So those are going to be the next steps for us is a nice way to visualize our attributes primary and
secondary in a nice menu that will be responsive and also have some options that will allow us to maybe
adjust our attributes as or a levels up.
She is going to gain some attribute points that she can spend on various attributes and then customize
the build for this specific character.
So these are common to most RPG games and that's what our project is going to have.
So those will be the things we'll handle next.
For now, excellent job.
We now have derived attributes, attributes that are derived from other attributes and those relationships
are mapped out here in our modifiers based on our attribute based modifier coefficient and other parameters.
Now sometimes you might want an arbitrarily complex calculation and also your calculation may be dependent
on other factors, not just other attributes, but other variables associated with the character.
And for that we need to use something a bit more sophisticated than an attribute based modifier.
So we'll have to use something like a custom calculation class.
And that's another thing that we're going to get into in the videos to come.
So just a few things to look forward to for now.
Excellent job and I'll see you in the next video.