SOLID Principles in Unity

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
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.
Info
Channel: Infallible Code
Views: 75,938
Rating: undefined out of 5
Keywords: unity solid, solid principles, unity solid principles, single responsibility principle, game programming patterns, unity, unity clean code, unity dependency inversion, unity inversion of control, unity technologies, unity tutorial, unity tutorial c#, game programming unity, unity3d solid, unity3d solid principles, game dev, game development, game programming, unity3d, gamedev, solid design principles, c#, tutorial, how to, learn
Id: QDldZWvNK_E
Channel Id: undefined
Length: 14min 58sec (898 seconds)
Published: Wed Apr 17 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.