Unite Austin 2017 - S.O.L.I.D. Unity

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] I'm on the hour now so welcome to unity solid and hopefully I don't end up disconnecting the video while we're in the middle of running this but I like to be able to walk around a little bit so this is a presentation on unity solid and so what is that solid is a set of five principles for programming that you can apply it to just about any language especially any object-oriented language but I do want to express our make a clarification this one is not just about solid solid development which by the way it helps you build larger programs and make them more manageable over time so what I really want to end up getting to by the end of this is talking about dependency injection and unit testing through visual studio where we're talking about test cases taking milliseconds instead of minutes all right so first on the presentation I'm Dan violet sack Miller this guy not the cat one of the things if you've seen some of my stuff before or if you haven't on LinkedIn learning or lynda.com there is a real-time strategy game in unity that you can learn how to build in about four hours with artificial intelligence enemies explosions battles textures resources it's a lot of fun I'm also Microsoft MVP particularly for my work in unity and I also have a couple books out the latest on unity AI and then also on I have meeting reminders as well also a book on X and a and C sharp and that one's out and available for free all right and right now I'm working for a company called productive edge which is pretty awesome because since I joined there everything has been unity3d so it's just been phenomenal if you want to find out more ask me after all right so you didn't come to here to learn about me you came here to learn about Bob so we'll find out what Bob is doing in a little bit we're also going to be talking about solid and the order is a lie a lot of place people try to present this to following in order but when I was trying to review this to come up with the best way to convey it I found that changing the order of it actually helped out a lot so we'll be going out of order and then finally we'll be getting to dependency injection in unit testing and when I was running test runs on this presentation was going anywhere from 50 minutes to an hour and five minutes so I'm hoping to get to keep this closer to the 50 minutes this time so we have time for Q&A however as I understand I'm the last presentation so it goes over a little bit that's not the end of the world all right oh yeah so these are important so dependency injection in unit testing everything kind of leads to this all right but we'll start out with solid and we'll start out with Bob this is Bob Bob is happy why is Bob happy because he was told to make a video game so great I'm in my day job I get to make a video game which I'm sure is actually most of us in here right now anyway so he gets to make a video game six months later they end up going to the testers of doom well actually they were just regular testers but his product really wasn't that fun when they tried it all right so the players disliked it so what happens it went back to the drawing board and keep in mind this is after six months of work already went into the programming so changes were requested instead of a rocket ship it was requested that they now use a submarine with clown all right so Bob had problems he couldn't see what these problems were yet but he knew that they were there they were there starting to build up in the background let's follow his journey and find out what happens so he would add a new class and suddenly find out he had to make lots of changes all over in his program to deal with this he tried using some interfaces reusing them but he was finding it was really costly on time and not giving him a lot of reward he also and raise if I can get a show of hands in here how many people have seen this you update one line and 12 bugs show up anyone see more than 4,000 bugs show up because they changed my mind oh I got a couple hands here like maybe five or six people that's awesome okay he would also add new features but they started breaking old ones you know a lot of these things you're probably thinking yourself you've seen this or parts of it or friends who have gone through this and you're thinking this is just something part of program is this this is what we deal with all the time but that's part of what solid helps prevent not completely not a hundred percent there is no perfect way to do code there's always some other architecture that will come up and follow the next one to solve problems from the current one but solid is one of the best standards that I found today all right a chart extending classes but that broke them and nothing was testable I want to stress this point because testing also came up a couple other points there was someone else who worked at unity a Holley who she was giving a presentation you can find it in the the YouTube videos when they come out but she was also doing stuff on test Runner and she was making a lot of emphasis - here - but I want to reiterate this I just want to make it sound really important because it is you write a piece of code you change one line in it that line has the power to influence every other part of the program whether you realize it or not there's always that thing where it's like well of course it can error out if the user uses it that way that's not what I designed it for so the point is is that every time you put I'll change inside of your code every time you add a new line you are putting risk in there that everything can break and you might not find it it might not be part of what you're testing immediately so he's got lots of spots where he wrote a couple lines of code and then you can see the fire starting to build up he doesn't see it it's behind his monitor then more stuff is building up oh he's starting to see it okay well wait I can deal with it here but he's already put layers upon layers of code on top of that so to fix that error he has to change fundamental portions of an entire program if he had testing available he could have spotted these errors as soon as he wrote the code or almost immediately after having written the code all right so let's get into the bulk of this solid so solid is an acronym means five different things SRP for some single responsibility open closed Liskov substitution interface segregation and dependency inversion you've also heard the word dependency injection which we'll get to but there are difference and that'll be explained as well okay so let's get into some of the details so we'll start with the pendency inversion principle it makes sense we'll start at the bottom of the acronym remember Bob Bob would add a new class and suddenly find that there were lots of changes he had to add all over his code to deal with it so let's find out some let's find out why or what was happening a lot of you have probably had something similar in your code a class called shot it's a mono behavior it's dealing with a shot that was fired and managing it it has a piece of information we'll assume there's more but it has damage so this damage is being used all over the place in his code he's got enemy he's got blast trigger the idea being an enemy you know takes a hit they started taking damage you've got your blaster which might be a wall or a trap door that opens up if you shoot it and then there's also score because in his system he has this thing to decide how many points you get based on the amount of damage you deal okay well that's great that works but in this fix this repair that he has to do after six months after the testers came in which by the way if you have the opportunity to get your testers from day one the that's really where you need to have your programming or your testing right away and continuously through the entire project all right but the testers and everything they decided they wanted to add a rocket shot so he created a rocket shot it's also a monobehaviour it has damage it operates the same way as a shot does but it has a few extra features that alter it at some fundamental level of how it moves or it's physics or something but the problem is that all these other classes enemy blast triggers score they're all looking at shut they know shot they don't know rocket shot and keep in mind there's a lot of ways you can fix this problem and most of you probably identified and thought of like oh yeah well we just do this to fix it so we'll get to some of them and keep in mind busy it's not his solution is not the best solution but it'll work ok so he starts to try to fix the code he goes around and he sees okay well we've got apply shot damage and he's going into the score and it says shot shot well he changes that to expect an object so now he can receive objects of any type and deal with it but his code has to look at what type of object it is he suddenly asked to decide is shot a shot or is shot a rocket shot and then deal with it appropriately in each case that works his code ran at the end it dealt with the problem but what we're actually doing here is we're hiding problems we're making it so if we ever add another shot we now have to go through this problem of finding all the other places that use it again and now one of them is expecting an object so there's no clear expectancy for that there's nothing to go ahead and say hey if you're adding a new shot type you need to go here here here and here to update everything so we've added a chance for more bugs so let's take a look at this Bob was looking at the code and someone walked over to issue oh look there that over his shoulder and said wow whose code are you working on that's pretty bad and he said yeah and that guy said what why don't use a dependency inversion principle and he said to fix this and the guys like yeah I'll do that you're right that's exactly how we should fix this and then ten minutes after the guy leaves he looks it up on Wikipedia which says this the conventional dependency relationships established from high-level policy setting modules independent of low-level dependency modules are reversed thus rendering high level modules independent of the low-level module implementation details that's pretty obvious isn't it yeah I'm getting a lot of this in the audience this is supposed to be a question mark all right so loosely translated it means use it interfaces or abstracts all right so here's what he did to fix the problem he ended up creating a single shot behavior that shot became part of same thing with rocket shot it's now inheriting from shot behavior now this is something of a fairly obvious thing he still has to go through the code and fix the places where it was using shot - now you shot behavior but he won't have to go through this again he fixes it this way once and then every time he adds a new shot he inherits from shop behavior and all those other classes continue to work so dependency injection means don't depend on concrete concrete means very specific instances so you aren't calling out the shot class you're calling out shot behavior the abstraction of it also interfaces are ideal but abstract classes work as well and dependency inversion principle in unity the unity inspector does not support interfaces now I've seen a lot of code showing up today where interfaces were starting to be used and it wasn't seeing if that was actually becoming part of the inspector which would be awesome if it does but so far in my history I haven't seen that work so that means that typically if you want something in the inspector to respect it so you can drag an object over to it and link things up for your designers rippling things you have to use abstract abstract classes and but that only means if it needs to be designer friendly and keep everything designer friendly I made the mistake of writing code that was all done it was code driven script to build up everything in the game and you run into a lot of issues that way because you just threw out designers you can no longer have a graphics person come in add stuff you can't have other people start to shape the levels for you they all have to come through you the programmer so keep everything designer friendly as a designer friendly as possible ok next principle interface segregation principle so here's Bob again he went through an experience where when he was reusing his interfaces it was costly to him it was expensive on his time it it took a lot of time for him to build this up and keep adding this stuff every time he used the interface now the thing is is that most of the time you hear that using interfaces it can actually save you time you're making code reuse so you don't have to do stuff over and over again but there's the flip side of that let's see what he experienced he had this idea he had this idea that for his system he would have almost every object be able to save information about itself so that it could be sent off to the file system to store temporarily to the player preferences if need be sent out to over a UDP connection for some real-time video game or sent out via a web service or TCP to get some other type of connection going to the can use it in some other form so to support all of those he ended up creating an interface called AI content IO and AI content IO has a method in their load his string load his object load is generic type load his byte array load is Jason but that meant that every time he adds this interface to another class in his code he has to rewrite functions for every part of that and I know he could write some static class functions to make it a little bit more universal but there might still be some generics yes there's some specifics he has to deal with different in every class so that created a lot of work for him and again the guy came over his shoulder and said wow that should be fixed with interface segregation principle so he looked it up ISP splits interfaces that are very large into smaller and more specific ones that clients will have will only have to know about sorry so that clients will only have to know about the methods that are of interest to them now there's a couple ways to look at this so I'm looking at this from I'll tell you both aspects but in general it means that an interface should really only have one member or one member purpose it should have one thing that it's doing only one reason to fail a couple strengths of that you're inside of intellisense and you're trying to figure out which function from this interface do you want well there's only two functions or there's only one function that's obvious you go to some other classes you load up an instance of it and now you want to find the method that you want and there's a hundred methods inside of it and you have to figure out which one and do you want this method or do you want this method B or want this method string or do you want what is it that you want it takes more time to figure out so you put in interfaces that simplify things so in the spirit of simplifying the interfaces he starts doing this he goes to his interface and splits it out into five separate interfaces one that returns a string i/o I string i/o which loads a string another one for I Jason IO and for the few methods that they actually do need to interact and save to everything he can still have his class inherit from all the interfaces all five of those interfaces so you can have as many interfaces as you want to attach to an object but for most of the time he only needs one or two of them alright so ISP use interfaces keep everything small and focused so even if you are putting in an abstract class put an interface in front of that and try to use the interfaces more than you do the abstract or abstract classes and a class can have a lot of interfaces but just the principle of keeping it simple keeping it small that really helps save your code from a lot of bugs and in UV remember and interfaces are not traditionally as supported in the inspector so if you want to actually have something you have to use the abstract version of the class and you have to tell it use this abstract class to go ahead and reference whatever it is I want however internal methods can so if you have any framework that's running behind the scenes you can use interfaces to pass information back and forth to each other and it saves a lot on potential bugs in the future okay next one single responsibility finally an easier one but I was kind of surprised by the Wikipedia entry when I found it okay so Bob changed one line of code and suddenly there were 12 new bugs we've all gone through this or almost every one I think there might have been four people in the audience who didn't okay bob has his classes 1% of classes are doing 99% of the work these are monolith classes they're huge they do lots of stuff in fact it gets confusing because of the amount of stuff that it does I mean look at this it's managing hero speed and enemy speed okay wait a minute it also has a list of enemies so we have enemies we have enemies speed we have health and Max hells is that the hero's health is that the players health or the enemies health we don't know it's not that straightforward we can kind of start figuring out okay well there's a list of enemies attached to this so that probably means is that the heroes health but we're having to do extra work to figure out what on earth this class actually does or these fields do that introduces more bugs because we might use the wrong thing or use this that something that is out there that looks right for the wrong purpose so having this code that does lots and lots of stuff introduces more chances for bugs so again he looked it up single responsibility in Wikipedia it says every module or class should have responsibility over a single part of the functionality provided by the software and that responsibility should be entirely encapsulated by the class okay so the general recap of that one is that a class should only have one thing that it does only one reason for failure so when you're writing a class if it does ten things that's not following this you've introduced a chance where there's a lot of overlap a lot of potential bugs alright so what does he do he ends up going into his code and he starts splitting these things out he makes more he makes more files like unit health and the max health and regular health shows up in unit health and then he's got unit movement and smoke manager enemymanager and all this stuff that's dealing with things he split it out to make it simpler so when he wants to find something he can look it up now I will say this if you don't organize your structures in Visual Studio you will end up with points where you have 80 classes in a single folder and just trying to find soar through that it's a pain so you still have to organize your folders - all right so srp a class does one thing it has one reason for failure and this also promotes simple classes small classes so when when you just have a couple lines of code it's a heck of a lot easier to figure out if something's going to error out then if you have a hundred lines of code in a method if you live a lot at a method with a hundred lines of code you can just spot the error instantaneously that was a really bad error all right and then SRP and unity think of this like components you could you can develop a game object by starting to drag in the ability to move where it responds to the character controller and then add gravity a rigidbody and then you can add another one to go ahead and deal with point score and another one to deal with power-ups and another one to deal with temporary flight boosts or something in your character you can shape your character very easily by applying lots of smaller behaviors to them or you could add one class that does like ten different things or a hundred different things and because of that overlap and code because that class is doing multiple things you're gonna have lines of code where you change one thing and it changes something else that's completely unrelated alright open closed principle now I'm using a demo and here that for this one explanation in this one that actually is used in like 50 percent of the articles on the web that explain it and the same problem also goes for Liskov substitution principle exact same problem exact same solution so I'm gonna focus on the differences alright but open closed principle let's start with that so he started adding new features and it started to break old ones yeah that makes sense we see that happen so here's exactly what he had he had a class called rectangle shape and it inherited from shape behavior he was starting to try to follow some other principles that he was seeing he was trying to put some abstraction in front of it and he was thinking okay well every object or every shape is typically gonna have a height and width for the amount of space it takes so that's reasonable all I had hiding width and then later on he says okay well I need to find the area of this well all right Ali will make a separate function or a separate class called shape tools and shape tools I'm going to set this up and make an area function in there so anytime we want to we can just say hey give me this the area of this shape we pass in a rectangle and it works because works that's great until they needed a circle class so they added the circle class and the circle while a height and width can still tell you information about it it's really just it's what was it the diameter for height and diameter for width but it doesn't really define what the circle is and it sure as heck doesn't so this does not work for the forgetting the area so we have the height we have the width that's no longer gonna work but that's okay he can fix this for OCP here for open closed principle he's going in there well this is not open closed principle yet this is his attempt to fix the problem he goes into here and he sees a float doesn't work so let's fix this he replaces this with a new method so it's still taking shape behavior just like we had shot before except we turned that and he tried to turn that into object in this case he is using a common type between them but he still has to do this call to find out is it a rectangle shape okay well fine then I'll get the height and width is it a circle shape well I'll get the I'll get the radius and I'll do radius times radius times pi or radius squared times pi so that works that does actually work his code will run and he did this extra step as well to make his code defensive to help it protect itself from error in the future because he's learning a little bit all right and he's throwing an exception so if it nothing returned anything yet it throws an exception and not implemented exception saying that whatever type was passed in is not implemented in shaped tools that area that's great that's an awesome error it tells you exactly which class which method is broken so you know exactly where to go to fix it except when you don't because you're actually just mocking the player or the the second developer who took your code in the form of a dll and doesn't have access to the source code so if they are going through a dll and that's how you released it without source or something like that they get this really clear message that tells them you can't fix a thing that's horrible ok so let's take a look at OCP OCP on wikipedia says software entities classes modules functions etc should be open for extension but closed for modification so what does that really translate down to and by the way keep in mind there's a lot more nuances than what I'm telling you I'm giving you a really fast version of this all right but once a class is written you shouldn't have to write it again or you shouldn't have to change the code you've already theoretically got unit tests in place or had testers test things about it so you've already got it tested if you change something in that class you are introducing the chance for that class to fail and all of the tests that happened prior to it to mean nothing at that point and have to be rerun to validate okay so you should be able to keep a Clutton not have to edit a class but still be able to expand it so what does that mean how do we apply that well first off we get rid of shape tools there is no way as thinking of this as a dll because where you don't have source code but even if you do have access to the source code it's still a problem having to go back in and change a function to change something that's already existed already tested so he got rid of this and he put area as an abstract method directly under shaped behavior that means that every time somebody expands somebody extends the ability of shape behavior they create a new shape they put in a new class it now is going to have you their responsibility at that time to reconstruct how area works they have to reconstruct it so that means that even if I'm in a dll or I'm using a DLL that has this shape tools or shape behavior I can still get the correct area defined when I add my polygon shape or triangle shape or crescent shape or whatever it is I'm adding alright so and this just shows it again so in the rectangle class he's overriding area doesn't there in the circle shape he overrides area and does it here so he's actually able to keep his code cleaner and more organized so instead of having this one mass where he has to like figure out like all this extra stuff that's just going on with architecture at all he can now just look in the method for circle shape find area and there it is there's less hoops he has to jump through less because less chances for errors or bugs to start making their way into the code all right so OCP you should be able to expand your code without having to edit the old code you should be able to depend on what was already there unless of course there's a bug that just says flat-out that this code does not work in that case of course you have to go when you have to fix that all right Liskov substitution principle as I mentioned before they discipline and open-closed principle tend to use the exact same problem and solution the exact same problem the exact same solution in half the articles that are on the Internet so the one that we just went through for open closed that was the same thing for Liskov substitution so the real problem just becomes well how is it different what else is list Goff's do so let's find out all right extending the classes broke them he was trying to extend to the classes but that still ended up breaking somehow we'll follow the open-closed principle with this example but we're gonna see an exception otherwise okay so he has game board and I can't remember the original place that I found it I think it was an N MSDN article but game board game board is the general idea of how he's managing his game he's got a 2d array of game tiles and he's managing them with a set tile int they passes in an X in a Y and sets the tile and that sets it so that that works except they decided what would be really awesome is that there are penny heaven there they're bonus levels they're bonus levels are gonna be in 3d that all adds a whole new element well he's like okay well this function this class game board 3d it still manages the game board 90% of the code is still gonna be the same so I'm just going to inherit from game board alright so I've got a lot of the functions that crossover except well that one doesn't give me a 3d array of data so I have to manage that I have to create my own tiles 3d and then in addition to that I also have to create a sent tile function XYZ and pass the tile in there okay well this code can work you can sure you can see it but this code can work it can work inside the code but every time he uses his code he has to fully understand how it works underneath the hood to understand that when he's calling this that he's using it specifically as a 3d game board not the 2d game board if he ever passes the game board 3d into gate function expecting game board which he can do it will be looking at the 2d asset it'll be looking at the 2d fields and interacting with 2d which never gets set when you're working with the 3d side of it so that's created a whole nother chance for bugs to happen because now it's it's just tiles don't exist you know that you loaded them but they're not there you don't know what's happening so not only is that a bug but it's one of the ones that become harder to catch so this one is always fun to be able to try to get a nice clear explanation especially from Wikipedia if s is a subtype of T than objects of type T may be replaced with objects of type s ie an object of type T may be substituted with any object of type s without altering any of the desirable properties of T correctness tasks performed etcetera clear okay naturally he died of confusion all right so what does this mean really what it means is if there are two different types and they have the same base class then they should both work as that base class or as that base type so if I inherit from string I should still be able to pass my class into any method that works with a string and still expect it to work okay so in this case Liskov a little bit different open closed principle is actually telling giving us a slightly different rule it's telling us not only when to apply inheritance principles but it's telling us when not to we need to be able to take game board so we take game board over here and we say well we're not going to hear it from that anymore we're just going to go back to monobehaviour and treat this as a whole separate class it's 3d it's not 2d even though in general a lot of the operations are the same that doesn't matter the code is significantly enough different that if you tried to use them changeably it would not work it would error the code alright so LSP you need to be able to trust whatever type you have you're using as whatever base type it is if it says it's a string it should operate as a string and you shouldn't need x-ray glasses you should never have to look at the source code of a class to understand what it does or how it works in general you shouldn't need to understand what you need to whatever well yeah you do kind of need to know how to wire it up but you shouldn't have to look at it to realize that you can't use the 3d tile in the 2d system alright now we're into the area that I like the best do wow this actually ran fast cool dependency injection so first thing dependency injection is not dependency inversion it depends on it there's a lot of dependencies here but di which is what I'll call it from now on di does not depend on or does not is not dependency inversion so dependency inversion says things like we can have classes or we want to be able to operate against the interface or the abstraction not the concrete type we don't want shot we want shot behavior or we want I shot behavior we want to enter we want to use that because that can be replaced we can do a new class to replace it in our code without ever having to change the old code we don't have to retest it we don't have to alter the existing test cases they all still work but that's kind of a weakness and solid because those objects still have to be constructed somewhere so if you're creating shot you still have to say somewhere in your code hey create shot give me an instance of shot and that has to create it at that point you can pass it to an interface and use it and you can follow the rest of solid but there's no work there's no special handling for how you construct it dependency injection takes it that next step where it says I don't want my class to know which one it's constructing if I create a DLL that has this whole game in it and then someone else creates this DLL plug-in for it they should be able to override shot and put in their own shot there so every time someone constructs a new it uses theirs instead of mine all right so one of the cool things about this is that instead of saying equals new game board and this is a snippet of code from the actual video game that I'm gonna reference in a minute instead of saying get board equals new game board they're gonna say it equals D I get eye game board D I will take care of constructing it and dealing with the construction of it ninjetta is a common framework that actually deals with this kind of stuff for the.net side it doesn't work in unity because almost all injection systems don't work in unity unless you specifically design them to it because unity needs to control the constructor I'll explain why in a couple minutes but I do want to point something out down at the bottom there is this link don't worry you don't have to rush to run to get it that written down or anything if you want to but it'll be on every slide from here on out that is a reference to a set of code that's a github right now so you can see source code for a dependency injection system that works for this an example project of that was written originally in unity and then using unity standards all the scripts just sitting inside of there and no solid or anything like that and then also using solid dependency injection and unit testing and it's the exact same game you run it and visually it's the exact same experience for the player and that's an example it's just a chain reaction game in the chain reaction game you click an object of one color and it starts a chain reaction to all the touching objects of the same color so a fairly basic game kind of fun to play clicking the yellow in the corner and Aulus and they all start vanishing it's kind of fun alright so another thing that dependency injection does that's really awesome dependency injection has the word injection in it injection means that you shouldn't have to do it it will inject things for you what does that mean well instead of saying I gameboard equals new game board we're going to say public eye game board board get set a property that we do nothing to set and dependency injection system will do it for us when the object gets constructed so and in this framework you can look at all the source code of how that stuff works but I'll explain a few more things of how to use it if you want to try it out all right so one of the things to be able to get to dependency injection is to use Visual Studio and create dll's except that's not you can actually still do this all inside of unity directly but I am showing the point of getting this inside of Visual Studio and starting to build as dll's because that gives you unit testing and much faster control over all your testability and easier testing in my opinion at least to be able to run all that stuff now if you do you just create a DLL in unity or in Visual Studio and then you just go to the properties of it and you can choose which base library of unity you want to use it already has you should be you had to select like unity there dotnet 3.5 or dotnet 2 and understand which classes and methods you couldn't actually use when it got back into unity but now they actually have this all to find out so you can just choose which one you want if you're going to the web then unity 3 5 web base class libraries or whatever is out there now so unity support works really well inside a visual studio ok chain reaction is split into several library or several assemblies and you have control over them a couple of them so behaviors live in Lib test the ones that you don't typically control are IOC which is where the dependency injection framework lives and then unity base which is actually where we have a series of wrappers to take control over game objects in unity and transforms and all sorts of other objects that you'll use in unity so you can actually take advantage of a dependency injection system ok so the ones that you do get to use the this is a new structure of taking a look at how you format your unity project so there's a couple small layers large layers how are you look at the behaviors this is like your views typically in a view like an MVC model for game development your view doesn't really have the logic it doesn't understand the base principles of how things work in the game it does understand how to show things it understands the logic of I'm moving faster than X speed then start adding sparks or some other effect but the game logic is held separate so in this first of all view models that typically treated scriptable objects if you haven't worked with scriptable objects there is a really awesome video on it from one of the 2016 unite stuff presentations that you should check out on YouTube it's phenomenal and you should start using it but that's really about like setting configurations that you can swap easily in the inspector and then views these are the objects that we're using inside of the game so we have a ball we have fade reactor we have the game we have Mouse manager reactor base and a bunch of other stuff so these are our classes they are controlling the stuff they actually have mono behaviors attached to them sort of ok so instead of monobehaviour any class that we use uses inject behavior and you've got the source code for this so it's actually just an abstract class that inherits from monobehaviour itself and then does its own steps to deal with the injection so a couple things that stand out about it is we've got game controller in it which is an eye game controller and we're setting that as get set well what does that mean that means as soon as this gets constructed the DI system the dependency invert injection system is going to run through find game controller see if it has a list or something to bound to it and it'll just put it in there for you it'll find the real reference to it and just throw it in the code so your class no longer cares about how it needs to find it or any details about how it finds it it just asks for it and it's it appears it also has another one game configuration this is one of the scriptable objects I was talking about that one gets set on the unity side which means that unity is going to do that and once you get to the start function that's where it shows up so I want to show you a couple things that go into this so and to get to there anywhere in a library in an assembly like the behaviors assembly the Lib assembly there will be something that inherited bindings configuration and basically what happens is the first time you ask you call on dependency Injection it's going to go through every assembly find every class that is an eye binding configuration order them and then call the setup function so all you have to do is just put a class in there you don't reference it anywhere you just put the class in and it'll find it when it starts and so in this one we're saying eyeball controller now reference is a singleton two ball controller and what singleton means I'll get to in a couple minutes but we're also dealing with game controller and mouse manager so we're wiring all these things up so anytime something asks for it it will get the instance we're defining what instance it is so what does this mean for solid this means that in solid if we want to change something out we don't have to go to that specific class and start altering the class we can now make a new instance or a new type of that class that inherits from the same interface and rebind it so we can test with an entirely new class and not destroy all our old code okay important note dependency injection does not work well in unity I mentioned this earlier and that's because unity needs to control the constructors and if you haven't seen this there's some other unite presentations that deal with this as well but there is a dotnet side and a C++ side the C++ engine can be dealt with a little faster and has a lot of different reasons for why they do this but it has two sets of objects for each code every object we create at least on the components something inheriting from unity this collection of objects and then we've got the dotnet object so what does this mean this means that they need to control the constructor that's why we don't get to they have to wire up their objects so when the way they construct the or we tell them to give us a new behavior they are constructing a C++ object and a dotnet object and linking them together that's what they're doing how they do it and why all the details I don't really know but that's the nature of how it works so what does this do this means that if you're not inside of the unity engine executing and the unity dll does not count as the unity engine but if you are testing in Visual Studio say for example it doesn't work because you don't have the unity engine side running if you are testing with ninja or if you're using ninja to constructive it can't it's not designed to handle it and you can't hack it you can get it to work but in principle and inject is not designed to keep a static reference of itself open which is one of the things that we have to do in unity to keep everything working all right and of course the dependency injection system as I mentioned comes with the collection of wrappers so already deal with certain things like game objects and stuff like that and scene manager so you can call on this and make it an interface that you can interact with in your class instead of an actual having to deal with the scene manager those wrappers are how we make this unit testable now one thing I'll point out is this doesn't have all the classes in unity and so if you want if you if you use it you'll find that there's a bunch missing and you have to end up putting them in if you're gonna use it there's a big reason for that I have a component I have an asset on the asset store right now and it only works in one specific instance of unity like version 5.4 dot something or whatever and that was one where it actually has interfaces and wrappers for every single class every single method inside of unity and actually wrote code that started to strip all the information out the reflection actually build up a whole new set of code to do this and I had to hack a few pieces together to make it work at the end but every time that a new version of unity came out there were some minor portions of it that would change in and the code just literally would not compile every time new versions of unity came out so ideally what I say is that if you do this if it's not there you add it but otherwise don't get ahead of yourself don't start adding the hundred functions that might be sitting inside of one of the unity objects only add the ones you need and then it only takes a couple minutes to add a wrapper for something okay so this is that same class that I was looking at over there where we have this game controller we have this game in unity it's a mono behavior or was an injection behavior and inside of it as soon as the constructor fires as soon as the constructor fires it binds itself as a singleton it binds itself so it says anytime anything else now asks for eye game give it give me give this class or this instance of that class so unity constructed it we still have the scene or this game object in the scene inspectors or editors designers they can still work with it and unity created it they they now have it available so anything else can use it and then also we have the start method so remember that we have this configuration that I had mentioned a little bit about before that earlier that configuration element that's set by unity so that's not available until a start method gets called so as soon as the start method gets called I add that and that's how we get scriptable objects to work in this as well to start transferring information over okay so I mentioned before I'd tell you how singleton is work there's a couple binding options that work in this system one is binding as a singleton which basically means static it's a constant reference it's always there you ask for a reference to I game it'll give you the same one every time you ask for it so the exact same memory exact same reference point and then there's also conversion because sometimes we have to do extra work to wrap things so if we want to get a transform out of unity and into our game logic we actually have to call a conversion remember how I said that unity takes your when you want to construct a new object unity builds its dotnet object and then builds your C++ object I don't know the order but it does both of those and then wires them together we do the exact same thing we take your dotnet object we let unity construct it and then we put it inside of a wrapper so that's what the dependency injection does and it uses it uses a conversion which is basically a method that gets called so that gets wrapped up inside of there and then you can bind to a type which basically just says anytime that you could ask for this give me a new instance so if I set that to shot for instance I could have a hundred different instances of I shot running around in the game alright and this is an example of some of the code that's actually inside dependency injection we're gonna get to unit testing in just a second but dependency injection this is one of the methods this is inside the ball controller of the game that game that you were actually seeing on the screen a few minutes ago we're passing in a reference an interface for ball not the actual ball object itself an interface and we're working with game config we're getting game config from this and we're also getting a list of additional ball references from the game from the game controller that we just got so we've got all this information that we're starting to get and we're just using interfaces we're not calling on anything from unity directly which means our logic is completely separate you anyone see a fault with that for on my screen a point where I actually am using unity code yeah vector 3 dot distance yes I'm using vector 3 dot distance in this that is a unity function but vector 3 and variety and all the other structure in the system they don't have a mirroring dotnet chimera C++ component at least not one that's is tightly coupled as what we find with game with monobehaviour or transform so because of that we can actually use some of those objects we don't have to rebuild all the functionality or wrap all the functionality of vector 3 which is great that saves us a lot of work ok so unit testing last spot nothing was testable remember this problem that the last one of the problems Bob was facing so the idea is is that remember and I'm trying to drive this home strongly that anytime you put in a line of code anytime you make any change all of your testing everything that you have checked is suddenly invalidated everything because you've all seen this where you do something in one spot of code and it changes something else and it takes you an hour to figure out why did that even affect this because our code is interrelated it all talks to each other through this giant web of code so the point is is that you may think you're doing a really good job that you're being very careful with your code but it's still not guaranteed you push it into production cuz it's like oh I just need to change this one little thing push it out to production soon as it's out there you're like oh crap that broke this you didn't even think about it but that's what happened so then the especially notable is when you to make a change in code and all of a sudden it's like three months later before the point that you actually start depending on that code in the certain way that breaks it and by that point you have so many layers that it doesn't really matter you have to throw it half your architecture and start to figure everything out again because it just doesn't work the way you wanted it and I'm sure you've seen that too where all the sudden it's just like there's some error that comes up that I have to throw out a decent amount of work because it just doesn't work the way I wanted it to it wasn't efficient it wasn't effective it wasn't fun all right so test all the things this is awesome you can test a hundred percent of your code not true you can test all of your game logic 100 percent of your game logic can be tested and you can get code coverage but the views and the view models remember that's actual mono behaviors and scriptable objects for that use test Runner and that was part of one of the other presentations that was given earlier today that I got to see him that was awesome but the test Runner lets you test behaviors in unity but the behaviors that take such to work that you have to do extra cleanup it actually has to load in a visual environment so it can render and process all these objects and you're not really testing isolated things so every time you try to test something in there there's probably a dozen lines of code at least that it's affecting so when you find an error with it it could be a lot wider of a problem it could be hiding in a lot more places you did tests on the other hand or testing one method there testing one method testing one set of expectations' against it for one set of results so with that you know exactly which method exactly which class exactly which part of it you're trying to break or guarantee works and also with testing especially in Visual Studio you get mock frameworks what does that mean you can mock an interface so you don't have to create this fake testing class for all of your interfaces you can just say hey mock I game controller and it will create this fake object that does what you tell it you can tell it oh there's this material name that's in there okay well any time it's asked someone asks for material say it's right and you can set that and this object will then act like that interface for your test cases now here's an example from the unity solid project again link is at the bottom so in this one I'm using a triple a method of testing when I say triple a I'm not talking about Triple A games I'm talking about a range act assert so you'll see that in here let's starting with a range but I also want to point out I'm testing the ball controller and I'm calling testing the method start reaction now I'm putting an underscore and then I'm saying I'm giving it what is the expectation well the expectation is I'm giving it one ball with the material or that's what I'm arranging and then how do I sit expect it to respond to that how do I expect us to respond to this environment this configuration well if I'm giving it one ball with the material I expect it to trigger a reaction for that ball just one ball no others and what's really cool here is I'm actually using the mock framework right now but I have and actually I can zoom in no go back go forward there so I can zoom in on this and you can see I'm using game controller here but I'm not are not the real game controller it's just a mock of it so it created this fake one for me and then I have some helpers in here I created a helper function which that's kind of covering up I'm creating this helper function and inside of that I tell it hey create a list of ten different balls in here and so it does it it creates these and they're all interface based they're all mocked out they're fake and I'm telling it okay take that list and now give me set them in a row one meter apart each and we can make the assumption all of these are one meter in size or one unit in size and set all material names red except take this first one and we'll remember we're just working with this mocked version of this ball list we're taking the first one and we're changing one of the get properties we're saying hey find the property called material name and any more time someone calls it haven't returned blue so visualize this for a second I just set up the game board and I have the bottom row filled in with 10 different balls or ten different elements in there all of them are red but one of them is blue I just created this visual example entirely in logic without touching the real unity code that means that this test can run inside a visual studio it can run where it takes milliseconds where I don't have to create render scenes I don't have to load a test scene I don't have to load 3d objects and all the behaviors associated with it I can test just this one thing now here's the cool thing after I have set up this game controller and I fill in all of these settings about it - all the stuff that's going on in the game I then bind it back to I game controller - the object from that mocked version so now anytime anywhere in the code asks for the game controller it is going to get this version and remember dependency injection is loading all this stuff for us so what does that mean that means when I'm loading the ball controller and the ball controller has its own reference it's going to try getting to game controller it's getting this one hit get to this one so my testing becomes really easily it's isolated to here I don't have to do all these extra classes and all this extra work to get it to work except for maybe a ball helper to simplify it some other helper methods okay so moving on in there the last part of that test for the arrangement is that I tell it okay go construct the ball controller I never mocked out the ball controller I'm getting the real class for ball controller when dependency injection gets it it goes to the property it sets that game controller that we put in there and then we tell it to act this is that triple-a part arranged we've arranged the test we've arranged the environment as we need it to happen or as we need to be and then we act on it the action was calling that method from the ball controller start reaction so it showed you the method a little bit earlier and showed you that's what this was Ted this test was called or starting with and then they pass in the ball list so it's got this I'm sorry not the ball list but that first ball the one that was blue so the way this works remember it's a chain reaction then you click on one ball and it'll find all the touching ones that are next to it and then destroy them you know some interesting ways shrinking fading whatever and with a time delay between each so what we're gonna do is we're gonna say hey this Mach fade which is the destructor I had set up for it make them fade out was activate destruction called and was it called only once we are verifying it so we just verified that the activate destructure was called only once even though there are 10 balls all in a row that should have been able to work or this should have been able to blow up except for the fact they're different materials I just tested a real in game instance without ever loading unity let me show you something really cool this is the time it took to run it the first one took 368 milliseconds I would say that might be partially just to getting Visual Studio loaded sometimes they're still extra things going on in threads that slow down the first test or two second one 46 milliseconds milliseconds for an entire instance of a game test where it loads stuff up that is fast and the code it's just straightforward it's code even if you write this as you're doing it it becomes easy I'm sure the first one might take you an hour or two just to kind of figure out get your feet wet make it work but then once you get that going it's it only takes a couple minutes to start adding another test case another instance it becomes really simple and really easy especially when you're following solid and you have simpler classes simpler methods less stuff that they're doing the unit tests become easier and then you don't have to worry as much you'd make a change in your code okay great I'll go hit the test runner runs all the code it's back in like 30 seconds everything worked except for this one thing so okay but not only did I see what was wrong check out the name it's a start reaction so I know exactly the method I know exactly what I was trying to do and I know exactly what reaction failed so I have a very clear path to see the entire way so I just click on that it takes me into the code and I can fix stuff really fast and easy or at least identify where the problem originated or seemed to originate okay so second to last slide and I have two minutes left cool so this allows your code to now run in standard build servers like ms TFS perforce get and you can use continuous integration on those they run the end unit test or ms test and you can use either they both work and just about any other test framework that you can use in Visual Studio they work and my test framework by the way is written in dotnet 6.0 or dotnet 4.6 sorry so it's using the latest dotnet version and because the test cases itself that's going against my library that doesn't really have all this tethered control from unity in it gets to use whatever framework I want to I can use the latest dotnet code for my tests so I can enjoy some of the faster features to be able to get stuff out it also gives you metrics like code coverage you can see how much code are you actually hitting when you're testing this you can also get performance on each part of that so you can break it down and see this method and this loop inside of this method suddenly took 30 seconds while the rest of it took 0.1 seconds and 30 seconds for any method to execute inside of a game is usually a very bad thing unless you're downloading an update on another thread or something alright so your time for testing is shrunken we're talking about seconds here milliseconds and usually you can just run a test suite that's against your specific area so you say all the tests against this class that I'm editing and before you check in you run the full test suite that might take a minute or two and you get back all the information right away and of course depending on where you're checking it in you also get all the information back from the server so the server can send out an email to your team saying hey Bob broke the build again alright so rule of thumb if the test is hard to put in there if you're trying to do a unit test and it's becoming really difficult because you have to get this object faked out somehow and get it to pretend like you have access to the filesystem but you don't want that because you're actually testing it on a server so it'll break everything and then it's got a load of this object and this object and this object you have to let's come up with some artificial way to do all that then you're not actually following solid unit testing is really easy when you start following solid okay and help-wanted this is open source on github I started it about a year ago uploaded a few months ago but it's been in use in a couple major projects I can't say who or where but think large amusement parks that will start have some of the stuff coming out in a few months the but I could use help on this people anyone who wants to get involved just try it out even experiment with it yourselves if you don't want to just apply it you can apply solid without having to use this but if you want the unit test the dependency injection in unity then get it from here and help out any way you want to we could use documentation week use expansion and testing all sorts of stuff all right and then so if you want to use that that's here's the link to that if you want to connect to me Here I am on LinkedIn unityconnect and also that that reference to creating your own realtime strategy game i mentioned before there's a link to that as well and i do want to also thank edge experiencial the company I work for since I started there it's been all unity3d they're doing lots of stuff with holographics and VR and just major major stuff and it's been a lot of fun if you have questions about that feel free to ask me specifically I want to give a little bit of thanks just to Vitaly who did the design on a lot of these pages and Sarah and Matt because they sat through with me to kind of go through and try to make this presentation good and then I also want to thank my wife because she's pregnant right now and she's staying home with our toddler so that I can come come here and have fun at this presentation all right so the floor is open to questions but please use the microphone so anyone on YouTube watching on YouTube can actually find out what they were that was cool does unity solid work on a DS for Mac or is it just Visual Studio Windows I'm not a hundred percent certain but it should operate on anything that runs dotnet code so so far I've seen it seems to have worked in everything I've tried it in hi I wonder how much overhead and complexity using this principle will add to the rail game that's a really good question so how much extra work how much overhead plus performance and performance good question okay so to answer the first one how much did it actually take in overhead to build it and to be honest if it's your first time doing it it's gonna suck just like learning almost any technology don't do it for your real project make a proof of concept first kind of experiment go through like a sample where you take one of your old video games and convert it to solid do something where you practice solid first don't just try it because if you don't get all the little parts of it right it does start to make things complex and if you're using it wrong it can get ugly but when you use it right it's this great experience of faster simpler code regarding the speed the the issues that we run into with performance honestly we did not notice any now I'm not gonna say I was not working on any triple-a games with us this was being used it was being used for some intense stuff we had like 24 machines that were all networked in real time doing all sorts of stuff simultaneously but it's we didn't notice any performance degradation from it but I can't say that we actually ran it through any major specs thank you I was wondering how you're testing workflow works in VR how you could even simulate user interaction in VR or you know find weird bugs that happen when the user tries to do something crazy in VR sadly I can't say that I've tied it into VR directly we've used this on several projects but we haven't used it on the VR ones yet so I intend to and I hope to have some better answers for it and even some demos to go up on github but for right now I can't actually respond to that yet thank you regarding the you said that you haven't had any major issues with this are you also referring to the project that you just had with the 24 connecting computers we haven't run into every single project you said with the 24 connected computers yes this system hasn't run into any problems when you're running that one no no I mean occasionally run into issues where someone who doesn't have a solid background jumps on to the project and then they're they're gonna have some complications trying to figure it out okay what if there was a single developer if it's a single developer I mean well one of my rules of thumb is if you can develop it in two weeks and be done with the project great don't worry about any special framework just hard code everything do everything to get it done and just get it out the door like a proof of concept but anything larger anything you think you might come back to in six months or hand off to another developer you really want to have some sort of intelligent framework sitting on top of it but other people can go to stant find standards online on how they're supposed to work with it well it's been six months and we're still working on it so thanks Dan okay yes so I thought it was pretty creative how you took mvvm principles and applied it to unity I haven't quite seen it that way before I mean we came from a typical Windows background and I know that's common there but anyways in regards to solid you mentioned that you used single responsibilities and you try and tie two components to game objects but do you have a best practices for how to interact in between game objects that are in the hierarchy especially like an editor well yeah for one I mean you create fields to go ahead and tie them in at any point where you're gonna have a designer with the ability to carry things over you take that a bit sorry any time that you have a designer that you want you want the designers to be able to pass stuff so you're talking about hierarchy architecture yeah your view model the the game the stuff that's actually the mono behavior that can carry objects into it so you can just have the you can set up a field that says hey use this type of monobehaviour this abstracted version of it for instance and they can you can just set them or have it say hey go through my children and find out what types of objects I have in here and pull out all the objects of this type okay so you can still use all those things from the monobehaviour itself and just use all the standards inside of it with the test runner example that you used that what you're doing that entirely in Visual Studio not using unities unit tester it looked like look you were using visual Studios so do you get issues when you're building new builds in unity does it mess up the solution and lose references to those tests or does it just maintain that or well the tests have nothing to do with unit with unities test runner so with that there is no test run or solution in there at all it is like use mentioned it is 100% Visual Studio for the unit test that one I don't remember if it was MS tester and unit I think I was using any unit for it okay but yeah it's completely separate and there were no issues that I found in being able to maintain that reference it's basically the view MA or the I want to say the view the mono behaviors job to go ahead and tie those together okay thank you welcome any other questions all right [Applause] [Music]
Info
Channel: Unity
Views: 56,414
Rating: undefined out of 5
Keywords:
Id: eIf3-aDTOOA
Channel Id: undefined
Length: 67min 18sec (4038 seconds)
Published: Tue Nov 14 2017
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.