Physics.Raycast is great for things like line-of-sight, determining
where a projectile will land, and something like this: where I need to know whether
or not the player can interact with the object that's in front of them. But
sometimes Raycasts can be too precise and leave the player with an
unresponsive experience, as we can see with these three objects in the snow.
When I get close to one of them we can see that I have to be very precise with
where I place the cursor if I want to interact with this object. And honestly
that just doesn't feel very good. So in this video I'm gonna show you how to use
the Dot Product in order to get a more responsive and flexible experience when
using Rays. My name is Charles and this is Infallible Code; a channel designed to
help you become a better game developer. If you'd like to learn more about Unity,
programming, and game development, then be sure to subscribe and hit that bell icon
so you'll be notified whenever a new tutorial is made available. So we've seen
this in action why don't we pull back and take a look
at how this all comes together in the editor. If we take a quick look at the
hierarchy, we can see that all the logic lives in this Selection Manager object,
which consists of the SelectionManager script and three other MonoBehaviours
that the SelectionManager delegates all of its responsibilities to. Why don't we
go ahead and open up the SelectionManager in our code editor. Now the
SelectionManager consists of three different types of interfaces which it
delegates all of its responsibilities to. There's an IRayProvider, an
Iselector, and an ISelectionResponse. The one we're concerned with is the
ISelector. The one it's currently using is the RaycastBasedTagSelector. As we
can seein it's Check method, it basically uses Physics.Raycast to determine
whether or not a selectable object has been selected by the player, and then
returns that to the SelectionManager where the selection manager handles the
selection response. So for the sake of this video, in order to provide a more
responsive experience to the player we basically need to re-implement our own
ISelector that uses the dot product to determine whether or not an object
has been selected. Why don't we go ahead and do that now. I'm gonna create a new
class called ResponsiveSelector and I'm gonna make it derived from
MonoBehaviour and ISelector. Now of course we need to implement the required
methods. All we need to implement is the Check and GetSelection. So for the
selection I'm just going to create a private Transform field that I'm going
to return in the GetSelection method. And then in the Check method, that's where
we're actually going to do the logic to populate that selection. But the first
thing we want to do is set the selection to null every time Check is called
because we need to reset the selection to make way for the new one. Or if there
is no selection, then we just need to return null. Now here's where we
implement our dot product logic. But before we do that I think this bears a
little bit of whiteboarding. So if we strip everything away from the scene,
what we really need to focus on are the viewport and the selectable object. And
here we have a side view of that. So right now all we're doing is casting
a ray from the center of the viewport into the scene and determining whether
or not that ray collides with a selectable object. Now in the case of
larger objects that works just fine. But with more complex objects, like our
pieces of grass in the snow, this methodology can lead to an unresponsive
feel. If the cursor moves just out of range of one of the leaves, then you're
no longer selecting the object. We need a more sophisticated solution in order to
get that responsive feel that we're going after, and that's where the dot
product comes in. Now there are tons of videos on dot product because it's a
complicated topic, and I highly recommend checking those out if you'd like to
learn more. But for this video we're just going to focus on the definition that's
provided by Unity in their documentation. And that is the dot product is a float
value equal to the magnitudes of two vectors multiplied together and then
multiplied by the cosine of the angle between them. For normalized vectors dot
returns one if they point in exactly the same direction,
negative one if they point in completely opposite directions, and zero if the
vectors are perpendicular. Now that last part is what we're going to leverage in
our code in order to provide responsive feel. Let's take a look at our side view
again and imagine two vectors: one to represent the direction that the
viewport is currently facing and the other to represent an arrow that goes
from the center of the viewport to the object. Now when we take the dot product
of these two vectors we can see that the result is 1 when the player is looking
directly at the object and 0 when the player is looking away from the object.
And we can use this value as a sort of look percentage to determine whether or
not the player is looking at the object. That value coupled with a threshold that
we can configure from the editor will allow us to provide the player with a
more responsive feel so they don't actually need to be looking directly at
an object but just close enough to make sense.
So hopefully that made sense. If not I highly encourage looking at blog posts
and other videos about dot product (I'll post some in the description) and if it
did make sense then I think we're ready to jump into the code. So let's get
started with what we know. We know we need to calculate a look percentage
using the dot product, which we can get by calling Vector3.Dot. Now this
method takes two vectors, so why don't we go ahead and create two local variables
that I'll initialize to zero for now. And we also know that we want them to be
normalized. If you remember from that second half of the definition, normalized
vectors are going to give us a value between 0 and 1 which is what we can use
as our look percentage. So what will these vectors actually be? Well the first
one is easy. We can initialize it using ray.direction. In this case the ray's
direction represents the direction in which the viewport is currently facing.
But the second one is a little more complicated. We need to take the
difference between the position of the selectable object and the center of the
viewport for the second vector, but we don't currently have a reference to any
of the selectable objects. And in fact we're gonna need a reference to all of
the selectable objects in the scene so we can loop through them and calculate
the look percentage for each one. So what I'm going to do is add a Serialized
Field that's a list of Transforms that are going to represent all of the
selectable objects in the scene. And I'm just going to call it selectable. So now
we can wrap this little bit of logic with a for-loop that basically just
loops through all the selectables. Perfect so for the second vector what I'll do is
call the current selectable's position
minus the origin of the ray. Alright so that should give us the look percentage
of each selectable object in the scene. Now this is great and I want to
keep chugging along, but before we do I kind of want to see this in action. I'd
like to be able to visualize these values so I can get a sense of what our
threshold needs to be. So to that end I've created an object called selectable
that has a text property that we can use to display the look percentage in
the actual scene. So let's go back to the responsive selector class and instead of
Transforms I'm gonna go ahead and reference the selectable object. And we
need to fix our code real quick. And now I can go ahead and set the current
selectable's look percentage so now when we run the project we'll be able to
see the look percentage of each selectable object in the scene. Let's
switch back to Unity and set that up. Alright if we go over to the hierarchy we
can see that we have a canvas object that is currently disabled and if we
enable it we can see in the scene we have some number labels that will
represent our look percentage. But we're not done yet. We need to go back to the
hierarchy and select the SelectionManager and remove the RaycastBasedTagSelector
and replace it with our new Responsive- Selector that we just created.
And then update the ResponsiveSelector's list of selectable's. So I'm going to
lock this in place and select all of the objects in our scene that are selectable
and drag them into that list. Alright now we're ready to run the scene. Alright
so right away we can see that as I look around the scene that the look
percentage values update in real time. And the idea again is that the closer
I'm looking towards an object the closer that look percentage gets to one or a
hundred percent. And the further away I look away from an object the closer that
value gets down to zero. So we want to choose a look percentage that allows us
to make this object selectable when I'm looking close to it. Not necessarily
directly at it, but let's say around here at 0.98 or 0.6. So why don't we switch
back to the code editor and add the code that'll allow us to do that. So back in
the code editor let's go ahead and add a new SerializedField called "threshold".
This is the value that we'll use in order to determine whether or not the
object is selectable or not. Now we can take that threshold into account during
each loop. If the look percentage is greater than the threshold, then we know
that we potentially have a selection. Nut we're not done there because multiple
objects might meet this criteria at the same time. So realistically we really
want to keep track of the closest selectable. So I'm going to add a
variable called "closest" and we're going to use that in this same condition to
determine whether or not this object is going to be selectable or not.
So if the current look percentage is greater than the closest one then we'll
go ahead and set closest to that value and we'll update the selection with the
current selectable. Perfect! Before I switch back to unity I'm just gonna give
threshold a default value of 0.97 because I've just found that
that's a pretty responsive threshold. Alright.
Let's switch back to Unity and run the scene. So now I no longer have to look
directly at an object in order to make it selectable. I can just look close
enough. And we can see here that these objects are becoming selectable when my
cursor falls within that threshold, and it feels much more responsive. Especially
when we go on over to our small objects in the snow here. This feels great! If I
wanted to harvest these resources, I'd feel much more at ease and the
experience would be much better than it was before where I'd have to walk up to it
and make sure I was pointing directly at the collider of the object. Now I could
just get close enough and press my harvest button and I'm good to go.
Well that's all there is to it! It's crazy how one simple algorithm change like that
can dramatically change the way the game feels to the player. And thanks to our
SOLID code it was really easy to implement. Well that's it for this video.
As always, thank you guys for watching and I'll catch you in the next video.
Thank you to all of my patrons and a special shout out to Dark Rush
Photography, Justin Hurst, NZ, Richard Stanz, Sean Cerry, Thomas, WayneGlows, and
Yakov.