Okay, so we'd like to implement Click to Move, and that's going to involve our player controller.
For now, I'm going to close all other tabs except or a player controller and the CPP.
So header and CPP files here.
Now for our player controller, I'm going to take some inspiration from the top down template, except
we'll be making some changes and doing things a little bit differently.
We're going to need a few internal variables to the player controller so they can be private.
Now what variables do we need?
Well, if we click on a particular location, we're going to want to cache that destination.
So I'm going to make an F vector called cached destination.
Now, I'd also like to keep track of how long we've pressed the mouse button before we release so we
can know if it was a short press.
So we're going to create a float to keep track of that.
And that's going to be the amount of time that we've been sort of following the mouse cursor, right?
Because as long as we're holding the left mouse button down, we're following the cursor, whatever
that location is, under the mouse cursor.
So as we're following, we're going to be incrementing this time and I'm going to be calling it follow
time.
We'll go ahead and make sure these are initialized.
I'll initialize the vector to just the zero vector.
And I'll initialize follow time to zero.
Now we need to know the short press threshold.
Right.
How long do we hold our mouse cursor down before releasing it?
And how much time until we no longer consider that to be a short press?
So we'll make a float called Short Press Threshold.
And we'll initialize that to zero.
Now if we're auto running.
In other words, it was a short press and we like to generate some path points and have our character
moving along some kind of a spline that we're going to create that's going to be when we're auto running.
So I'd like to know when that's the case.
So I'm going to create a Boolean called Be Auto Running and initialize it to False.
And that way we can call add movement input every single frame.
As long as this boolean is true, as long as we should be auto running.
And as long as we're auto running, we're going to be getting closer and closer to our destination,
at which point we should stop auto running.
How close we get to that destination should be a parameter we can change.
I'm going to make it a float called auto run acceptance radius and I'll initialize that to 50 point
F, but we can always change that.
In fact, we can make this one a property with edit defaults only in case we'd like to change this value.
Okay, so we have a number of variables.
We're also going to want a spline.
A spline or a spline component is going to allow us to generate a nice smooth curve out of a series
of f vector world locations.
That's what I'd like to use here.
So I'm going to, first of all, forward declare its type, and that's going to be a spline component.
So we're going to say class and it's a U spline component.
That's the type.
We're going to make a variable to store one of these.
So down here at the bottom, we'll make a T object pointer of type U spline component and this will
be just spline.
I'd like it to be simple.
We'll call this spline and I'd like it to get a U property.
I'd like it to be visible anywhere.
I'd like to see it in the details panel.
And if we have a spline component variable, we need to construct that.
We do that in the constructor.
So here in the Ora Player controller constructor, we can construct that spline component.
So we'll say spline equals and we'll use create default Subobject with u spline component.
I'm going to let writer include that for me.
And there it is.
It's in components.
Spline component.
And we'll just call it spline.
No need to attach it to anything.
We're going to set its spline points as we generate paths.
Now let's make sure our other variables are initialized.
Our short press threshold actually should be a non zero value, right?
This is in seconds.
We're going to say it's a short press.
If you clicked for less than, let's say 0.5 f seconds, follow time can be initialized to zero.
Auto running should be false and auto run acceptance radius can be 50.
So these are all looking good and we could if we wanted to just initialize those in the constructor
If you wanted to do it that way, that's totally fine.
Okay.
So we have a few variables that we'd like to use.
So let's think about this from the perspective of playing the game.
What happens when we click in the world?
Well, the left mouse button is going to be one of the inputs that's kind of special.
It's different than the others in that we're going to use it for Click to Move, but we're also going
to use it to activate abilities if they're assigned to the left mouse button.
So we need to know if we left click, do we want to run or do we want to activate the ability?
We need some kind of criteria to tell us whether we should do one or the other.
Well, we know if we're clicking whether or not our mouse cursor is on top of an enemy because we're
highlighting that enemy and our cursor trace and any given frame, this actor is a pointer that will
hold either an AI enemy interface if we're hovering over an enemy or anything implementing this interface.
And if we're not, it's going to be null.
So any given frame, as soon as we click with our mouse, we can know whether or not we're targeting
something, if we're targeting something.
In other words, our mouse cursor is hovering over an enemy, then we can go ahead and activate our
ability or try to anyway.
We can get our ability system component and tell it we're trying to activate an ability, but if we're
not targeting something, we should not do that.
So how do we handle this situation?
Well, we can have a Boolean for whether or not we're targeting something.
Let's add that and we'll put this down here with our other associated variables.
It'll be a bool and we're going to call it be targeting.
We'll set it to false by default.
And as soon as we've clicked, as soon as we've pressed, we want to see if we're targeting something.
And that's going to be if this actor is a valid pointer, if it's not a null pointer.
So what we can do is set be targeting here in ability input tag press, we can say be targeting equals
us and let's just use the ternary operator.
We'll say this actor question mark.
True.
Otherwise false.
Whoops, false.
So in other words, if this actor is not a null pointer, then this ternary operator expression returns
true.
If it's a null pointer, then it returns false.
And if we want this to look even more explicit, we can say this actor equals null pointer.
Now if ability input tag pressed is being called, that means we just clicked with our mouse.
So we should not be auto running.
We're going to set be auto running to false because we don't know yet if it's a short press.
If it's a short press, we're going to auto run.
But we don't know that yet until we've released the mouse button.
Okay.
So that's going to be all we need to do in ability input tag pressed.
Now, what about for when we're holding it down?
An ability input tag held?
Well, it kind of matters what the input tag is if it's the left mouse button.
Right.
In fact, it kind of matters here in ability input tag pressed whether we're pressing the left mouse
button, we should probably check that.
Let's check it here.
We'll say if input tag and we can use dot matches tag if we want to do that.
But we do have to match tag exact if we're going to do that.
And what are we checking to see if we match?
Well, we need to get our gameplay tags, don't we?
So we can get F or a gameplay tags double colon get now.
Right.
Is going to include that header for me.
So make sure you have that included.
And from this we can get the input tags and we need input tag LMB for left mouse button.
So really if we're matching this tag, then we are concerned with whether or not we're targeting and
so on.
So we're going to place those in an If check.
And we're going to use this same if check here in ability input tag held.
So I'm going to go ahead and just place that if check here removing the inner contents of it.
And we want to try to activate the ability if the input tag is not the left mouse button.
So really we could say if not input tag matches, tag exact input tag and be then we can attempt to
activate the ability.
And if we're trying to activate the ability, we're not trying to auto run or move our character at
all.
We just want to activate the ability.
So in this first if statement, I'm just going to check if get ask if we have a valid ability system
component, then we'll try to activate the ability or at least tell it that we're holding an input with
this input tag and I can remove that part down there.
So while we're holding this down, if it's not the left mouse button, say it's the one key or the right
mouse button, we're just going to tell the ability system component, hey, inputs being pressed.
So do your thing.
Check to see if it's active.
If it's not, activate it and so on.
Now, otherwise, if this is the left mouse button, well, now we're concerned about running, right?
And if we're holding the mouse button down, I want to know if we're targeting and this right here is
only for if we're not pressing the left mouse button down, but if we are pressing the left mouse button
down and we're targeting in other words, we're hovering over an enemy, well, we want to do this as
well.
So we're going to say if be targeting.
Then we want to do this too.
We want to activate the ability.
Now, if we've activated it already up here, we should return.
So we'll return right here.
Whether the ability system component is valid or not.
But here, if we're targeting and we've made it this far, well, then we are pressing the left mouse
button and we want to activate the ability.
But if be targeting is false.
Now we're concerned with actually our click to move behavior.
So while we're holding it down, if we made it all the way this far, this is where our click to move
behavior belongs.
Otherwise, up here, we're concerned with activating abilities, right?
So here in the else case here, we're going to first of all, increment our follow time.
So we're holding our left mouse button down.
We're going to be adding get world get delta seconds.
To our follow time.
As long as we're pressing that left mouse button and as we're pressing the left mouse button, we're
going to get the world location, the destination that we want to move towards.
So that requires us to have a hit result.
F hit results.
We're going to call it hit and we're going to get the hit result under the mouse cursor.
Now we can say if and call get hit result.
Under mouse cursor.
And what are we going to trace against?
Well, for now, let's just trace against each collision channel visibility.
Not physics body, but EQ visibility.
There it is.
Do we want to trace complex?
I'm going to pass in false so we don't trace over complex collision and then we pass in the hit result
there.
So if we make it inside this if statement because get hit result under cursor returns a boolean if it's
successful then inside this hit result we're going to cache that destination by setting our cached destination
equal to hit dot location.
We can also use hit dot impact point in the case of a line trace their the same.
If it were a sphere trace they'd be different.
It would be the point at which the surface of the sphere hits versus the center of the sphere.
But we can use impact point or location here.
Now, after we've gotten our cached destination, we need to call add movement input.
As we're holding the mouse down, we're getting a destination to move towards.
We're going to want to move towards it.
Now to call add movement input, we need our controlled pawn.
So what we're going to do is we're going to make an if statement and say a pawn controlled pawn equals
get pawn.
That way, if it returns null, we won't do anything.
If it doesn't return null, we have a valid controlled pawn and we need to call add movement input and
pass in a direction which means we need a world direction.
Let's say const F vector world direction.
And what we can do is we can take our cached destination and subtract our controlled pawns location.
So controlled pawn get actor location.
But this is the vector from the controlled pawn to the cached destination.
So we want to pass in a normalized vector.
So I'm going to wrap these in parentheses and call dot get safe.
Normal.
So now that we have this world direction, we can take our controlled pawn and call add movement input
and we're going to pass that world direction in.
We don't want to scale it by anything other than one that's a float, so I'll make it one dot f And
in fact, scale value has a default value of one, so we don't even need to pass that in.
So that's all we need to do.
And this is going to handle holding the mouse button down to move.
And it's only going to work if we're not targeting something.
This is something we can test out right away.
So let's go ahead and just make sure this works.
And this should work in multiplayer as we're calling add movement input here.
Now we're not handling path point generation, so we can't just click and release and expect it to run
to its location magically.
We'll do that next.
But for now, let's test out what we've got so far.
So if I press play and if I click and hold, I see nothing happens.
But good thing we're running in debug mode.
We can place some break points.
So let's see the input tag.
I believe it's going to be the left mouse button tag.
So I don't know.
However, if be targeting is going to be true or false, it should be false.
I'm expecting it to be false as as soon as I press this.
If the tag matches the left mouse button, we should make it in here.
I'm going to place a break point here as well, so let's see what happens.
So ability input tag pressed.
The input tag is the left mouse button and we'll make it into here.
And let's actually go one line farther.
It's being set to true and that's because easy mistake to make it's if this actor is null pointer be
targeting is set to true we actually want be targeting set to true if it's not a null pointer right
that would mean we're hovering over something so silly mistake to make but an easy fix and really we
want be targeting to be true if this actor is valid.
In other words we don't need this equals null pointer there that was a mistake.
So removing that we can try this again.
Okay, well, press play.
We'll click and hold and it's working.
And this should work in multiplayer.
I'm going to go ahead and set the number of players to two.
We'll play as a listen server.
And I'll try to move with my client.
And it works.
And our movement is nice and smooth.
I've noticed I'm not getting my messages when picking up pickups on the client.
We'll fix that very soon.
But movement is looking good.
Excellent.
So this is a good stopping point for this video.
And the next video will handle actually making the character move, including moving around obstacles
and things when we click and release quickly.
So we'll have a short press.
So we're going to implement short press behavior next.
Excellent job and I'll see you soon.