In this video we're going to learn about
the SOLID principles a refactoring this
MonoBehaviours so it's more flexible and
easier to use. My name is Charles and
this is Infallible Code; a channel
designed to help you become a better
game developer. If you want to learn more
about programming, Unity, and Game
Development then be sure to subscribe to
this channel and hit that notification
icon so you'll be notified when every
tutorial is made available. Now before we
dive into the actual code refactoring
why don't we take a quick moment to look
at the example scene because it's
important to understand why we actually
need to refactor it before we touch the
code. So first things first why don't we
go ahead and run the scene. So the scene
itself is very simple. It consists of a
basic environment that was created using
3D models that I found for free on the
Unity asset store, as well as the FPS
player controller that comes bundled
with the Unity standard assets package.
But what I want us to focus on is the
functionality that's provided by the
script that we'll be refactoring in this
tutorial, which is the SelectionManager
script.
Whenever I mouse over one of these
dark gray objects we can see that the
SelectionManager script replaces its
default material with a special
highlight material that's configurable
in the editor. That's great and all but I'd
really like to replace this behavior
with something different. For instance I
found an asset on the Unity asset
store that outlines GameObjects in the
scene. I think that would look much
cooler. But unfortunately the way the
SelectionManager code was written, it's
not very extensible. And it would be very
hard to work that code in. So what we're
gonna have to do is use the SOLID
principles to refactor the SelectionManager
SelectionManager so it's implementation can
support any behavior that we want moving
forward. Why don't we switch over to the
code editor and do that now. This is the
SelectionManager script. It's
responsible for all of the behavior that
we just saw in the scene, and it does it
all in its Update method. Now when
thinking about the SOLID principles the
first one that comes to mind is the
Single Responsibility Principle. But what
exactly is the selection manager
responsible for? Why don't we break it
down using some comments
to figure that out. The first thing that
the selection manager is responsible for
is creating a ray and casting it into
the scene. The ray is used in the second
responsibility which is determining what
was actually selected. In this case the
selection manager uses a tag comparison
to determine what the ray actually hit
and whether or not that hit is actually
selectable. The last thing the selection
manager is responsible for right now is
responding to deselection and selection.
And that actually occurs in two
different places, but it really is the
same responsibility. We can copy that
down here as well. So we can see that the
selection manager is responsible for
three different things but for this
tutorial we're only interested in
refactoring one of them, and that's the
response to selection and deselection. So
why don't we wrap this section in the
middle with a region just so we can get
it out of the way. Alright so we can
just minimize that and now we've
isolated the script to the two pieces
that we actually care about. These two
bits of code that handle selection and
deselection. So eventually we want to apply
the Single Responsibility Principle to
this script by taking each of these
responsibilities and moving them out
into their own interfaces so that the
selection manager is responsible for one
thing; delegating all of the tasks that
are required for selection management.
But, again, for right now we're just gonna
focus on the selection and deselection
response. And, looking at this code and
thinking about the refactoring, the next
SOLID principle comes to mind is the
Open-Closed Principle. So what we're
going to do is replace this logic right
here and right here with logic that,
instead of updating the material of the
selection with a highlight material,
instead it's gonna use a Unity asset
that I found on the asset store to just
outline the material. The logic looks
completely different so in order to do
that we're gonna actually have to delete
this code and replace it with the new
logic. But I don't want to do that
because that breaks the Open-closed
Principle which says that classes should
be open for extension and closed for
modification.
I don't want to actually have to modify
this class code. Instead I'd like to be
able to implement a new class that would
allow me to modify the behavior without
losing the old behavior. So what we're
gonna do is just take this logic here
and just pull it up into its own class.
And I'm using Rider as my code editor
and with Rider, and I believe Resharper
as well, we can do that in a really cool
way. So the first thing I'm gonna do is
I'm going to take these two bits of
logic and move them into their own
methods. And we can do that using the
extract method refactoring. But first I'm
gonna take this reference to selection,
which is a private member variable, and
I'm gonna move it out into its own local
variable. Just so it's out of the way and
this code is as isolated as possible.
Now I can select this logic and I can
extract it into a method. We're gonna
call this OnDeselect and, again, I want
to make sure that this transform is
being passed in as a parameter. Now we
can select next and we can see that
all that logic has been pushed into its
own method. And you know what? I'm gonna
do the same thing for OnSelect. Move
this into its own variable, grab it and
do the refactoring to extract the method.
We're gonna call this OnSelect, and of
course make sure that this transform is
being passed in as a parameter. Alright
now these are safely out of the way and
we could begin the process of moving
them into their own class. Before we move
them into a class we need a class to
move them to. So what I'm gonna do is I'm
gonna add a class that we haven't
created yet. We're gonna call this
HighlightSelectionResponse. And I'm
gonna just name the variable selectionResponse.
And then I'm going to use
Rider to create the type for me.
Alright we can see it's been created down
here. I'm gonna zoom in and we're gonna
make this inherit from MonoBehaviour.
Perfect! Now before we make the move we
need to make sure that this
HighlightSelectionResponse has all of the member
variables it needs. Namely it needs a
reference to defaultMaterial and
highlightMaterial because now that
logic is going to belong to the
HighlightSelectionResponse. So what
we're gonna do is scroll up to the top
and we're going to just copy these two
variables
and move them into the
HighlightSelectionResponse class. And since we're
now gonna need to be able to reference
them in the SelectionManager, I'm going
to temporarily make them public.
Now instead of referencing the
SelectionManager's materials, I can go ahead and
reference the selectionResponse's
default and highlight materials. Alright
perfect. So all that that we just did
serves the purpose of allowing us to move
these two functions easily into the
HighlightSelectionResponse class. So
what I'm gonna do is select the
OnSelect method and I'm gonna do the
refactoring to move this instance method
into the HighlightSelectionResponse
class. And I'm gonna do the same thing
for onSelect. Perfect! So now all this
logic has been completely moved out of
the SelectionManager class, which means
we can go ahead and delete these two
material fields. And now if we want we
can go ahead and just inline these
selection references. Beautiful! That
looks much cleaner. However, believe it or
not, we haven't yet applied the
Open-Closed Principle yet because if I
wanted to change the behavior of the
SelectionManager I'd still actually
have to modify this code because it
depends too tightly on this
HighlightSelectionResponse. So what we're gonna do
is apply another SOLID principle the
Liskov Substitution Principle. And what
we're gonna do here is take this
HighlightSelectionResponse class and
we're gonna derive an interface from it
so that instead of depending on this
particular implementation we're gonna
rely on an interface. And the interface
is going to be completely replaceable by
any subtype that wants to alter the
behavior of this program. So in Rider I
can come down to HighlightSelectionResponse
and I can go ahead and extract
an interface. I want to make sure that I
include the OnSelect and OnDeselect
methods, and we just want to name this
ISelectionResponse. We want to give it a
more generic name because the selection
response is going to eventually do more
than just highlight but in this tutorial
it's gonna outline and maybe in the
future it's going to do other things
like pull the object into an
inventory or something like that. So
let's click Next and we can see that now
we've extracted an interface. And now we
have a contract for other subtypes to
adhere to, which would allow us to
implement whatever behavior we want and
ensure that the SelectionManager logic
never changes. Because all the
SelectionManager should be responsible for is
delegating the tasks and in this case
it's delegating the tasks of what to do
when a selection occurs and what to do
on deselection as well. So in order
to complete this refactoring we need
to go ahead and replace this
HighlightSelectionResponse with the ISelectionResponse.
I'm gonna use the use base type
where possible refactoring here, and it's
gonna replace that with our
ISelectionResponse. And of course we need
to implement the Unity Awake method here
so that we can go ahead and set that
value using the GetComponent method. We
will expect that anything that
implements ISelectionResponse is a
MonoBehaviour so we can grab it from
the SelectionManager GameObject and
use it in our code. Alright so now that
all this is in place why don't we go
ahead and extract these into their own
class files. And now the
SelectionManager is looking much cleaner, and
we've taken one step in our refactoring
to making this class much more SOLID. So
why don't we hop back into Unity set up
our scene and test this code out. So the
first thing we're gonna do is open up
the SelectionManager and update it to
use the new HighlightSelectionResponse
MonoBehavior. And just like before
we're gonna need to set the highlight
materials. So I'm going to use the
Object@Selected and Object@Default
for these materials. And now believe it
or not that's it. We're ready to press
play and we can look around our scene
and ensure that all of our objects are
still being selected and highlighted and
the behavior has not been broken in any
way by refactoring. So now we can
actually switch back to the code editor
and implement some new behavior very
easily using the SOLID principles. The
SelectionManager now adheres to the
Open-Closed Principle which means that
now we should be able to implement
ISelectionResponse
in any way we want. And as long as we set
the SelectionManager's selection
response to that new implementation the
behavior should be modified. Why don't we
go ahead and do that now.
I'm gonna create a new class called
OutlinesSelectionResponse and I'm
gonna make it derive from both
MonoBehaviour
and I want it to implement ISelectionResponse.
Now we can very easily drop in
some logic into these two methods. So
what I'm gonna do is I'm gonna try to
grab an Outline object from the
selection, and that's from that free
asset that I mentioned earlier, and once
we have the Outline OnSelect what I'm
gonna do is set the outline width to 10.
And on deselect I'm gonna copy the same
logic in here. I'm just gonna set it to 0.
And for each of these we should
probably check to make sure that the
outline is actually there. So I'm gonna
check that it just does not equal null for
both. And we're done! We've implemented
brand new behavior without having to
modify the SelectionManager. And this
actually brings me to another one of the
SOLID principles, the Interface
Segregation Principle. So that says that
many client specific interfaces are
better than one general purpose
interface that's selection manager if we
had extracted that into its own
interface would have been one
general-purpose interface to manage
everything but now we split it up and
this I selection response interface is a
part of this very client specific
broken-up piece of software that's
decoupled so that it can manage its own
thing and selection manager can manage
its own thing and then moving forward as
we start to break out those
responsibilities each one of those
responsibilities will get their own
interface now before we switch back to
unity and tests out our new behavior we
should probably talk about the last
solid principle the D which is
dependency inversion principle and that
states that classes should depend upon
abstractions and not concretions now
before when we were referencing the
highlight selection response class that
was depending on a concrete
implementation but now all the selection
manager does is depend on an abstraction
of
election response it doesn't know how
it's gonna response selection all it
knows is that on deselect it's going to
delegate that on deselect action
somewhere else in the code and same for
on select and as we just saw we
implement their brand-new response and
what we're gonna see is that instead of
highlighting the objects we're now going
to outline them using another asset so
let's switch back to unity and see that
in action back in unity to set up this
code we're gonna need to do two things
first we're gonna remove the highlights
election response script since we're no
longer gonna be using that
implementation we're going to use the
outline selection response instead and
next what we're gonna do is select the
four objects in our scene and make sure
that we've added the outline script to
them that came with that asset and I'm
gonna set the color to green because
that's my favorite color
so that now whenever we mouse over one
of these objects the selection manager
is going to use the outline selection
response to set the width between five
and zero depending on whether or not the
object has been selected or deselected
let's see what that looks like
all right mouse over the sphere ID sure
enough it's been outlined so thanks to
our refactoring and the solid principles
we were able to introduce brand new
behavior with just a couple lines of
code in the next video we're gonna
rewrite our selection determination
logic so it's a little bit more
sophisticated you won't want to miss it
so thanks for watching and I'll see you
in that video.