I’m currently trying to learn how to decouple my code better. So I’m looking over the Unite 08 code reuse video and slide. Is this slide THE way to do things in Unity? It mentions that you should never use public gameobject and public component references. Really?! Is this a good rule to follow? Sounds like we’d need to use the Resources class a lot more.
Anyone else have any tips?
I also have a separate question on the same topic:
In trying to decouple my classes, is it a good idea to separate my UnityGUI code from what it actually does? So then I should have 2 scripts for a GUI: “MainMenuGUI” and “MainMenuHandler”. MainMenuGUI purely draws the GUI and fires OnButtonPressed events which the MainMenuHandler respond to? Is this the right way to go?
I would say not really…kinda defeats one of the purposes of using the editor (not having to hardcode everything into scripts). Which would result in less reusability, actually. However, I’m not so sure that spending a lot of effort trying to decouple everything is really the best use of one’s time, since realistically what are the odds that a lot of your code will actually be reused? Especially for games, which generally at least make some attempt at a unique experience.
That’s interesting, thanks. I have another kind of general question. What does coupling actually mean (in Unity)? Let’s say you have a MainMenuController script. And this script does all sorts of things from drawing gui buttons, to loading levels, to reading/writing from PlayerPrefs. Would it be an accurate statement to say that MainMenuController is “coupled” to the GUI, Application and PlayerPrefs classes? What about inheritance, does that count as coupling?
Then I’ve also read that there are different degrees of coupling. Loose, tight, any more? From my understanding here’s what they mean:
Tight coupling: A class requires another class in order to function properly.
Loose coupling: not exactly sure…
Idk, maybe I’m over thinking this. All I know is that so many times when I’m building a game, I run into a brick wall. My scripts start feeling very foggy and I lose mental track of who’s controlling what. Just trying to figure out the cause of this…
Yes, that’s happened to me. I think the main thing is to be consistent about everything; I always end up regretting it when I say, “meh, just one little hack won’t hurt here…”. So I really try not to do any hacks; doing it the “right” way may take longer at first, but it ends up saving me time in the long run. I put “right” in quotes because there isn’t really one right way…as I said, my feeling is that being consistent about your design is more important.
I think the more focused your classes/scripts are, the better. But that’s just a guideline. You can obviously get silly and enter a dark corner of insanity by saying "Oh, my player movement needs to go in a player movement class. I need a player jump class, and a player hit class. And a player turn class. "
Honestly, I shy away from dogmatic rules when it comes to coding. But that’s just a guideline too
You’ll see people say “NEver use static classes.” Whatever
Loosely coupling largely means use of interfaces rather than concrete classes. When something is tightly coupled it requires specific types of objects in order to operate. When it is loosely coupled, it requires only objects which fulfil specified criteria, but it doesn’t really care how those criteria are fulfilled.
For instance, you might have a bunch of classes which move objects, called CharacterMover, CarMover, ProjectileMover, etc. If these are used directly (ie: code which uses them specifies the exact type) then that code can’t be reused on GameObjects which have a different type of XXXMover. On the other hand, if all of your XXXMover objects implement an interface IObjectMover or similar, then the code doesn’t care what kind of XXXMover it is talking to - it knows that it is capable of performing the required behaviour and it knows how to talk to it, and that’s enough. If you write a completely new type of XXXMover later on, as long as it implements IObjectMover you can replace any existing XXXMover with the new class and it’ll just work.
Another approach would be to have a parent class ObjectMover which all XXXMover classes derive from. This has some advantages (common functionality can be easily shared) but also some disadvantages (you can implement multiple interfaces, but you can not inherit from multiple parent classes, so for instance you can implement both IObjectMover and IPickupCollector but you can not inherit from both ObjectMover and PickupCollector). Either approach will work fine if you give proper consideration at design time and solve problems in consistent ways.
But… as has been mentioned, being too rigid about blindly designing for reuse and applying dogmatic rules and so on isn’t necessarily advantageous, because we can’t see the future so we don’t know the exact situations we need to be prepared for. Software design and implementation is a constant learning exercise, so be satisfied with the fact that nothing you write will be perfect but you can learn from your mistakes and make it better next time. I generally go for writing code that gets the job done first and foremost unless I know ahead of time that something will be reused, and take the time to generalise later on should I find out that I need it. Occasionally that bites me in the ass, but the opposite can also be the case.
@Eric5h5: Consistency seems to make sense when I think about it. Because I’m always trying to come up with rules to follow. So in another words, it’s best to be consistent instead of trying to find some perfect rule.
@angrypenguin: Okay, I think I understand it a little better. Loose coupling doesn’t mean some specific thing, but it’s something that can be attained via multiple techniques (e.g. inheritance, interfaces).
@L-Tyrosine: By dispatcher pattern do you mean like a NotificationCenter like system? If this is helping you I’d be curious how you have it set up in Unity.
Software development is so weird. It’s like I know how to do so much stuff in Unity; I’ve practically memorized the whole Unity API! But somehow , some way I almost always get stuck in the middle of a game. Memorizing the Unity API somehow wan’t enough…
Contrary to what books and tutorials will often tell you, software development isn’t about writing code, it’s about solving problems. Knowing a particular language or learning an API are beside the point, they’re just tools. Understanding different types of problems and various approaches to solving them, along with their various pros and cons, are much more important than knowing how to write code.
It provides decoupling by allowing objects to be bound by “interests” not by fixed reference. So for instance if you have a force field device, you will want to make it watch for “Damage” events to minimize it, wherever it comes from, wherever it is the target. This allow to reuse this very same logic as a spaceship device or a personal force field for the player. Also, other object could be watching the same “Damage” event on global level to check player damaging enemies and increase score, and so on.
class myClass {
public GameObject myGameObject;
public myClass(){
myGameObject = NEW GameObject("bla");
}
}
I would do this:
class myClass {
protected GameObject myGameObject;
public myClass(){
myGameObject = NEW GameObject("bla");
}
public GameObject myGameObject{
get {return myGameObject;}
}
}
It would avoid accidents like, by mistake, some one in a team writting this nasty piece of code:
myClass Thingy = new myClass();
myClass.myGameObject = null;
Due to the nature of u nity and destroying GameObject, this still can be dangerous, but it’s just an example. In actual practice I would go much further and only expose via properties any gameobject properties that NEED to be exposed for the main game logic to do it’s thing, not the full GameObject member.
I’m still a bit confused about all the different types of coupling. Here’s one confusion in particular:
In Unity you have the Light, MeshRenderer, and Camera components. Are they coupled to each other? Because obviously one effects another one. If you move a light it affects a renderer which then effects the camera. But on the other hand, none of them “require” any other ones to be active/alive in the scene.
Then you could say the same thing for rigidbodies and colliders. Two colliders, by default, interact with each other, so are they coupled? Perhaps they’re loose coupled? What’s the proper terminology to describe this behavior?
Lets say you have a script called SmoothFollow (like the one in standard assets). It has a public variable of “target” in which it follows. Is it accurate to say that SmoothFollow is “coupled” to a Transform (target)? But the thing is it doesn’t “have” to have it because it won’t cause runtime errors when null. Is this another form of loose coupling?
Maybe I need to take some oop courses or something. Maybe c++ would help, idk…
I’m not 100% sure on this, but I would still call that tight coupling because the SmoothFollow script still requires concrete knowledge of the Transform class. That is to say, if the Transform class did not exist the SmoothFollow script could not compile. The fact that the lack of assignment to that variable doesn’t cause an error is by the by.
SmoothFollow was meant to be a component like any other component in Unity. For example a Joint component which requires Rigidbody to work with.
By saying that SmoothFollow is fully decoupled doesn’t mean that it’s not allowed to depend on any other kind of data type.
In it’s case SmoothFolow requires a Transform as an input data but that input data is actually a generic data type inside Unity: a Transform. The Transform is a Unity built-in data type. So more accurately the “target” transform in this case is just the required input for SmoothFollow to process.
This means that if you take SmoothFollow and place it in another project it will work just fine as long as you provide it with input data: a target Transform.
That’s why SmoothFollow is an example of fully decoupled component in Unity. Cause it doesn’t depend on any other one or more custom component scripts you have.
Another example can be a script called MultiTrigger that requires a trigger collider, has an array of GameObjects and a string property called eventName.
The trigger collider is just a part of the MultiTrigger script that acts as a helper to detect collisions (and again it’s a Unity built-in data type/component).
The public array of GameObjects acts as an input to our MultiTrigger component and the string property is the name of an event to send to all the GameObjects specified, which is also an input data for our script. If something triggers the collider the component will just send the message specified in the string property to all the GameObjects specified in the array using SendMessage(…)
All the dependencies of this component are Unity built-in. Thus also making it a fully decoupled custom Unity component because you can just re-use it in another project as long as you give it the required input.
Hope it clears things a bit on the fully decoupled matter.