Search Unity

Programming architectures

Discussion in 'Scripting' started by magwo, Dec 29, 2009.

  1. magwo

    magwo

    Joined:
    May 20, 2009
    Posts:
    402
    Hi!

    I'd like to hear some experiences from different programming approaches used in Unity. What is, in your opinion, a good and natural way to structure large sets of components and classes that interact?

    For example.. do you use a lot of .Net events and delegates? If so, do you worry about when/how the events are triggered - in FixedUpdate() or Update() etc? Do you make extensive use of interfaces? Or do you prefer deep inheritance trees?


    As a working example.. consider the following architectural problem:
    Tank shoots projectile at crate, which explodes and kills human enemy standing nearby. The kill needs to be credited to the player controlling the tank which is controlling several weapons.

    How would you structure the components to make this kill tracable?
     
  2. magwo

    magwo

    Joined:
    May 20, 2009
    Posts:
    402
    To illustrate the issue a bit more, a quote from IRC:


     
  3. damien_oconnell

    damien_oconnell

    Joined:
    Dec 24, 2009
    Posts:
    58
    Having played around with Unity and C# for the last few days I think I already get what NCarter is talking about when he says he wants to get back to a more component orientated structure. Using class inheritance you are basically making it less clear within the Unity interface how your program is put together. I don't really see the problem with using interfaces as in very layman terms it's just a way to ensure that one class can guarantee it can talk to another.

    When I watched the 3d Buzz Unity tutorials a few days ago Buzz did mentioned another point that all class variables should be made private or protected. While I can see the reasoning, it kind of defeats in my eyes a lot of what makes Unity so cool to work with.

    How i see it, it would have been more logical if all public variables exposed within the Unity's interface could only be set within Unity, and by default were given some kind of null object so Unity would generate errors if it had not been set. That way if the connection was broken and remade you would know about it and not just be completely oblivious to the fact.

    I'm a complete Unity and C# noob, I've only been using both for a few days now and do really consider myself more an artist than a programmer so please forgive my very simplistic views on the subject. I do however really look forward to other peoples thoughts on this topic.

    Regards,
    Damien
     
  4. ooppari

    ooppari

    Joined:
    Jan 29, 2009
    Posts:
    68
    Quite complicated question :D. And everyone is having their own way to deal things.

    I don't use inheritance ( at least not much ), few exceptions, but those are usually selfexplaining ( enemybase for message receiving with different kind of enemies, handle scoreboarding for enemies etc. ).

    Mostly I use one script / main feature set. I divide features to feature sets, which are handled via one script ( example player movement, player interactions with other object, AI ). That suits my purposes. Most files are quite small, file names are selfexplaining..

    My way to deal with thing uses usually state machines ( enum based ). All my interacting gameObjects are having script, which handles states. If/When interaction occurs, which affects to my gameObject, then state changes and actions are done. I know. State machines are like hell to modify later on and quite difficult to maintain, but usually I figure states and stuff in UML.

    Also I have gameObjects inside container game objects quite many times to ease up structuring and finding issues, which are related.

    Private/ protected class members is also in my list. I use public setters and getters to affect to class member states if needed. Just to avoid unclear issues. It's some times hell to find some error from codes if everyscript can set other scripts variable state as easily as..well something..

    edit. There are way too many aspects to think in overall architecture how to arrange scripts and how to arrange gameobject, how gameobject are interacting and communicating, how gameobject affects to HUD, which handles scores / game states and so on. I hope that there would be simple way to handle everything, that would really be something :D.
     
    escape0707 likes this.
  5. bibbinator

    bibbinator

    Joined:
    Nov 20, 2009
    Posts:
    507
    I'm no Unity expert yet, but I also don't use inheritance much and prefer component based architecture.

    For me, even in UnityScript I keep everything marked private if possible and let the controller script attached to each object manage itself and I don't use global "managers" if I can help it. The key for me is to allow emergent gameplay.

    For example, I throw a grenade and an npc picks it up and throws it back at me killing everybody in my bunker. Who gets credit/penalized for the killing of my teammates? Or perhaps I throw myself on the grenade to save my teammates.

    This question may have different answers depending upon the game, so I would rather the script attached to the grenade's controller decide what to do. It could keep track of the owner over it's life, or it could even keep an array pushing each owner in turn into it so it has a record of all previous owners. The possibilities are limitless and I only know enough to know I can't possibly code every scenario; it needs to run on its own. In the scenario above, I could see that I killed myself with my own grenade but on looking through the array could see it wasn't a clumsy mistake but rather an act of honor.

    So using components and messages are always preferred in my opinion, and much more reusable too.

    I guess to answer your question more explicitly, I would create a controller script for each type of object that is self-contained as possible. I would have a small set of public functions that can except messages/calls from the outside of game related events, and I would actually keep my scoring logic in the object script and not in the scoreboard because only the object could have the specialized knowledge to know what has happened. E.g. assign blame or credit.
     
    escape0707 likes this.
  6. magwo

    magwo

    Joined:
    May 20, 2009
    Posts:
    402
    When you say messages, do you mean specifically stuff like SendMessageUpwards() and BroadcastMessage() and similar Unity functionality using call-by-string?

    I have a hard time accepting that programming model, as it makes refactoring more time consuming, and it can lead to silent errors that are hard to debug, if you're doing lots of call-by-string.


    I'm thinking about writing an extended GetComponent() which works with any class or interface - not just stuff that inherit Component. That way I can do things like:
    Code (csharp):
    1. GetComponent(typeof(IMyInterface))
    That way I think you get looser coupling as scripts only know about each other through interfaces, not actual implementations. However I guess it becomes a problem when you need to have public serialized references to other specific components. I sometimes wish that Unity didn't enforce the Component thing so strictly in the editor.
     
  7. bibbinator

    bibbinator

    Joined:
    Nov 20, 2009
    Posts:
    507
    Of course there is no one size fits all solution, but if you look at the frequency of updates, I think message passing in the case you mentioned is a good choice. Even in an action packed game, you're probably looking at only 1-2 messages per frame max.

    How many messages could possibly be sent in one frame? I mean, how many grenades explode each frame? how many tanks run over somebody? These are infrequent events right? And seldom happen simultaneously. And to support emergent gameplay this is a good solution. Unity can send way more messages than you need for events like this.

    On the other hand, the moment you code an interface it can do almost the same thing, but it requires that caller explicitly knows about how the callee is going to use it. How about letting AI know what happened? What about sound? etc., etc. each will need to be called.

    For example, when a grenade explodes, the sound manager could play the correct sound without having to explicitly call it or thinking about the interface. You'll also have less connections to manage. What happens if the object you're interfaced to is destroyed? Dynamically calling GetComponent all the time will chew up a lot of cycles.

    Anyway, if you feel strongly about using an interface and you're already comfortable with that, then go with that :) I was just trying to give you ideas about how you can build something scalable and reusable.
     
  8. magwo

    magwo

    Joined:
    May 20, 2009
    Posts:
    402
    My issue with string-based event broadcasting isn't so much with the performance as with the obscurity and lowered clarity in the code. But it does loosen coupling in a quick-and-dirty way.

    Also I saw that there's an option to get an error message if your string-based call didn't find a method to call.. which makes it slightly less hackish I suppose. I have previously stayed away from this built-in event structure, but maybe I should give it a try.
     
  9. bibbinator

    bibbinator

    Joined:
    Nov 20, 2009
    Posts:
    507
    What could be clearer than a string message compared to what inevitably gets implemented as a generic function name with some sort of code/id to tell it what happened?

    So wouldn't you use strings in any case even with a direct interface? There's nothing worse than trying to maintain a giant enum table of message IDs and keeping that synced with all the files and team members. Better to suffer a few minor spelling error on string message typos.

    Even on old PS2 sports titles we did, we always used a system that would broadcast things like:

    Broadcast("HomeTeam2PointBasketMade")
    Broadcast("HomeTeam3PointBasketMade")
    Broadcast("AwayTeamFoul")
    etc.

    Then, we can play the right sound to reflect what happened rather than a huge set of if/else statements in the basket code, update the scoreboard, trigger animations of high-fives for the right team, etc. etc. all without knowing who is actually listening and allowing systems to be implemented organically and specialized to the job at hand.

    For example, the animation, sound and scoring modules could be implemented even though the messages weren't being broadcast or vice-versa. Nothing crashed, it just didn't work until both a sender and receiver were present.

    For low frequency interactions, this is a really solid system.

    Anyway, good luck! Unity makes it easy to try stuff out.
     
  10. bibbinator

    bibbinator

    Joined:
    Nov 20, 2009
    Posts:
    507
    I should also add that the messaging in Unity works a bit different than other publisher/subscriber based message systems. If you want to give messaging a try, you might also consider developing your own message system based on publish/subscribe. It is efficient and gives you the ability to tailor it to your needs.

    Cheers
     
  11. ooppari

    ooppari

    Joined:
    Jan 29, 2009
    Posts:
    68
    Hmm, I would say that giant enum table is more reliable in use than string based message delivery. That way you usually get compiler error when typing enum wrong instead of hunting down typo during runtime ( which is much more time consuming, than compiler error ).

    If all enums are arranged in one file, it's usually as clear as string based systems. Also if team knows rules how update is done, this is not an issue. Also I don't use SendMessage or Broadcasts almost ever :D, I much more prefer to use direct function calls.

    But as I mentioned in first message, this is just my opinion :D
     
  12. magwo

    magwo

    Joined:
    May 20, 2009
    Posts:
    402
    So you don't use any .Net events at all?


    I'm kind of buying the ease of the Send/Broadcast thing.. however I think it goes quite messy when you reuse components in different places, and they want to provide different kind of information in the object argument. So you'll end up with a bunch of different OnEnemyDestroyed functions which expect entirely different objects.

    Coupling is loose until you start passing more detailed info to Send/Broadcast - then you end up with tightly coupled components with highly specific parameter data. :[
     
  13. magwo

    magwo

    Joined:
    May 20, 2009
    Posts:
    402
    Hmm I kind of get the feeling that you need some inversion-of-control for these event structures. Then you should be able to fully reuse these components, because you can externalize the creation and handling of the specific event for a specific game, while the event triggering and listening components remain the same for all usages. Or am I missing something?

    Edit: Maybe I'm just overthinking this whole thing. Code reuse and generality is nice but...
     
  14. prime31

    prime31

    Joined:
    Oct 9, 2008
    Posts:
    6,426
    I'm chiming in a bit late here, but here is what I have done to solve this problem: first I looked at the wiki which has a ton of more efficient messaging systems than the string based sendMessage. None of them suited my needs so I essentially created a simple messaging system that is very efficient (designed for the iPhone) that uses the same ideas Apples NSNotificationCenter if you are familiar with it.

    It is really simple. The main gist of it is that there is a singleton called the NotificationCenter. There is one delegate with a signature of:
    Code (csharp):
    1. delegate void OnNotificationDelegate( Notification note );
    Any classes that care about an event just call something like:

    Code (csharp):
    1. NotificationCenter.defaultCenter.addListener( someFunctionName, NotificationType.SomeMessageType );
    The class that wants to listen to a notification would just have a method signature like this:
    Code (csharp):
    1. private void someFunctionName( Notification note ){}
    This does require an enum for each notification type which for the iPhone is a good thing. Posting a notification is equally easy:
    Code (csharp):
    1. Notification note = new Notification( NotificationType.SomeMessageType, someUserInfoObject );
    2. NotificationCenter.defaultCenter.postNotification( note );
    You then get the ability to subclass Notification to make any specific notification with strongly typed ivars. If anyone is interested in the code I have a very simple sample project that just fires and listens to some notifications. It is really simple though weighing in at under 100 lines so it isn't anything spectacular but it is quite performant and fits my needs perfectly.
     
  15. ooppari

    ooppari

    Joined:
    Jan 29, 2009
    Posts:
    68
    Yes. no :D. I don't trust em. That's main reason. I have tried those in real use, but end up at some point to refactorto get back to use direct function calls or similar.

    Perhaps I'm just too sceptic with issues like that.
     
  16. magwo

    magwo

    Joined:
    May 20, 2009
    Posts:
    402
    Yes, but I think that the enuming and matching subclassing is too much manual work. If it's not super-easy to create new event structures, you probably won't do it as much as you'd like to.

    Maybe this is useful:
    http://www.unifycommunity.com/wiki/index.php?title=CSharpMessenger

    The biggest caveat with that approach seems to be in that you can never specify to listen to events only from a certain source. Maybe it can be extended with that functionality.
     
  17. prime31

    prime31

    Joined:
    Oct 9, 2008
    Posts:
    6,426
    When I add a new event in my system it is literally just adding a single enum. Dead easy and only takes a second. The only time subclassing the Notification class is needed is when a single Object parameter isn't enough information for the event. In practice, you never actually have to subclass it. Adding a source is also a very easy addition that I chose not to do for my particular uses.
     
  18. steddyman

    steddyman

    Joined:
    Apr 10, 2009
    Posts:
    253
    I'd be interested in your NotificationCentre code uprise.

    Thanks
     
  19. prime31

    prime31

    Joined:
    Oct 9, 2008
    Posts:
    6,426
    Ill post a really simple sample project with it when I get back home.
     
  20. ooppari

    ooppari

    Joined:
    Jan 29, 2009
    Posts:
    68
    Surely there is plenty of other possibilities to handle message change implementation like this. I think that "observer pattern c#" will bring plenty of suitable design patterns when using google :D.

    But still easiest thing with Unity might be those .Net events. Then any other implementation might take several hundreds more codelines, but might be more reliable in use.
     
  21. prime31

    prime31

    Joined:
    Oct 9, 2008
    Posts:
    6,426
    ooppari, there are tons of ways to do everything in .NET. The key is to use something that fits the use case. You can search Google all day and come up with tons of observer patterns that work fabulously for a desktop application. The stakes are raised ten-fold when you are dealing with a game that is running at 30+ frames per second and even more so for the iPhone. The simplest solution is usually the best and if you are writing several hundred lines of code for an observer/event system for a game you can most certainly simplify it.

    .NET events have some limitations that wouldn't work for my particular game and they have some performance overhead (they use delegates behind the scenes plus some other stuff). What I came up with stripped things down to the bare essentials: a singleton that manages sending and listening to all events.
     
  22. steddyman

    steddyman

    Joined:
    Apr 10, 2009
    Posts:
    253
    I'd be interested in your NotificationCentre code uprise.

    Thanks
     
  23. ooppari

    ooppari

    Joined:
    Jan 29, 2009
    Posts:
    68
    @ uprise78

    I know, that's why it's so difficult to find suitable solution, because need is defining best solution to messaging systems also. As in other issues also. Simple Design pattern solution might be really fast to implement but really slow in use ( Now speaking about non-built-in version ).

    That's why built-in system might be best because it's already simplified into use. It has already tested interface to use. It even might contain lots of optimizations to gain best possible performance.

    But if messaging system is biggest bottle neck in game/app, then you have to find better solution :D.

    Btw, how much time do you use to take care memory allocation exeptions and surviving out from those situations?
     
  24. prime31

    prime31

    Joined:
    Oct 9, 2008
    Posts:
    6,426
    On the iPhone I don't do any exception handling and turn it off in the build settings altogether. There just isn't enough juice in that little phone to run the whole .NET VM and handle exceptions.
     
  25. magwo

    magwo

    Joined:
    May 20, 2009
    Posts:
    402
    Please do it in this thread, I'm curious aswell. :)
     
  26. prime31

    prime31

    Joined:
    Oct 9, 2008
    Posts:
    6,426
    Attached is a sample project with the NotificationCenter and a Notification subclass. Just open the scene and click play. All the action is in the console though so don't bother looking on the screen.
     

    Attached Files:

  27. magwo

    magwo

    Joined:
    May 20, 2009
    Posts:
    402
    Had a quick browse of the code.. while it's quite nice generally, I still don't like this passing of object references, and don't like having to subclass to pass different data. I'm currently more attracted by the CSharpMessenger that uses generics.. if it would be possible to add some more error checking and robustness to it.
     
  28. prime31

    prime31

    Joined:
    Oct 9, 2008
    Posts:
    6,426
    I like the generic approach a million times better. The problem with the iPhone is we don't have generics so we are stuck with boxing/unboxing casting/isinsting all over the place.

    I try to make all my reusable code modules avoid box/unbox/cast/isinst as much as possible...at least until we get generics on the iPhone one day.
     
  29. magwo

    magwo

    Joined:
    May 20, 2009
    Posts:
    402
    Currently working on an improved version of the CSharpMessenger, which focuses heavily on detecting programmer error - specifically not allowing different signatures for the same event type, which could cause a lot of silent bugs in the generic event system.

    The reason this happens is because, not only does Messenger and Messenger<T> get different event listener dictionaries.. Messenger<float> and Messenger<double> also get different dictionaries.. which can and probably will cause bugs and programmer confusion when they aren't getting the events they expect, because they're listening for the wrong event signature. Also there's a potential for adding and removing listeners with different parameter signature, which will cause bugs.

    It's not possible to prevent all types of potential errors, but most of them are detectable if you use the same listener dictionary for all events.
     
  30. magwo

    magwo

    Joined:
    May 20, 2009
    Posts:
    402
  31. steddyman

    steddyman

    Joined:
    Apr 10, 2009
    Posts:
    253
    Thanks uprise

    My target platform is the iPhone too, so any of the more advanced scripts using Generics are of no use to me.

    I came across this script on the Wiki which is similar to yours, but written in javascript and much simpler to implement since it is using messaging. The overhead is practically nothing:

    http://www.unifycommunity.com/wiki/index.php?title=NotificationCenter

    What are the benefits of using your script? Is using Delegates much faster than SendMessage?
     
  32. prime31

    prime31

    Joined:
    Oct 9, 2008
    Posts:
    6,426
    I haven't benchmarked yet but everything I have read points to sendMessage being very slow on the iPhone. I started out testing all the scripts on the wiki and ended up just taking ideas from them to make simplest, fastest solution avoiding boxing/unboxing/casting as much as possible.
     
  33. magwo

    magwo

    Joined:
    May 20, 2009
    Posts:
    402
    I think I might put this generics-based message/delegate system to wide-scale use in my heli game... will see how it fares.
     
  34. UnityAndI

    UnityAndI

    Joined:
    Dec 18, 2009
    Posts:
    7
    hi,

    I am trying out CSharpMessenger as well as your extended version.

    How do I pass multiple ( 2 or 3 ) arguments? I'm not very familiar with callbacks (which is where my answer lies I guess :) ),

    thanks for these classes by the way!

    Andy.
     
  35. UnityAndI

    UnityAndI

    Joined:
    Dec 18, 2009
    Posts:
    7
    Okay, think I found the answer by looking at the classes:

    Example of a class in which you want to dispatch an event:
    Code (csharp):
    1. public class Dispatcher : MonoBehaviour {
    2.  
    3.     private float speed = 10;
    4.     private float lastSpeed;
    5.     private float counter;
    6.    
    7.     void Update () {
    8.        
    9.         speed++;
    10.         counter++;
    11.        
    12.         if (speed >= lastSpeed )
    13.         {
    14.             Messenger<float, float>.Broadcast("speed changed", speed, counter,  MessengerMode.DONT_REQUIRE_LISTENER);
    15.         }
    16.    
    17.     }
    18.    
    19. }
    And then listen for it (and remove the listener after 3 secs) in another class:
    Code (csharp):
    1. public class ListenerScript: MonoBehaviour  {
    2.    
    3.     public float speed;
    4.     public float counter;
    5.    
    6.     void Start()
    7.     {
    8.         Invoke( "OnDisable", 3 );  
    9.     }
    10.    
    11.     void OnEnable()
    12.     {
    13.         Messenger<float, float>.AddListener("speed changed", OnSpeedChanged);
    14.     }
    15.    
    16.     void OnDisable()
    17.     {
    18.         Messenger<float, float>.RemoveListener("speed changed", OnSpeedChanged);
    19.         Debug.Log( "ListenerScript > listener removed");
    20.     }
    21.    
    22.     void OnSpeedChanged(float speed, float counter)
    23.     {
    24.         this.speed = speed;
    25.         this.counter = counter;
    26.         Debug.Log("ListenerScript > speed = " + speed + ", counter = " + counter );
    27.     }
    28. }
    If I did anything funny, please let me know,

    thanks,
    Andy.
     
  36. Davaris

    Davaris

    Joined:
    Jan 27, 2010
    Posts:
    68
    This is wonderful. :D

    I was wondering, can you have as many of these listeners as you want, or will too many of them slow your game down?
     
  37. careyagimon

    careyagimon

    Joined:
    Dec 20, 2007
    Posts:
    209
    I just try to make the smallest, most cohesive and reusable scripts possible. I then try to limit SendMessage calls to events between different GameObjects and to events that represent/initiate distinct state changes that are relevant to many types of gameplay entities. For example "Damage" or "TouchedBy" or "ZappedByShrinkRay" and "KilledBy" are useful events to many different types of objects.

    If one component needs to communicate with another component every frame, it's probably already highly coupled so I will allow for a direct reference to the other component to avoid SendMessage calls.

    For the original post example. I might have a DamageMessage class that would have an "originator" variable. The DamageMessage class lets me change what info is passed around without changing code in any other scripts/classes. Anything that I would want to take damage would have a script called "HitPoints" attached to it.

    Hitpoints would have a public method of "Damage(newDamageMsg : DamageMessage)" When HitPoints has determined that it has take too much damage, say at HP<0, it Broadcasts a message " "KilledBy", newDamageMsg.GetOriginator()" which any other component on that GameObject can receive.

    I might then have a "DestroyObjectWhenKilled" script that could receive the KilledBy message and handle any GameObject deconstruction.

    For something that has a point/score value, I would attach a script called say "GivePointsWhenKilled" that would also receive the KilledBy message and would call something like "ScoreManager.AddToScore(killer, myPointValue)".

    That exploding crate would have a script called "OnDeathSpawnExplosion". This script would receive the KilledBy(killer) message. This script would have a public explosion var to select the explosion prefab and a boolean var called "UseDamageOriginator"(there is prolly a more descriptive name) that if set would send to an instantiated explosion a message like "SetOriginatorTo(killer)". The explosion would use the same Damage(damageMsg) interface to affect anything around it.

    From there I would organize more general scripts to be more visible and more specific scripts to be less visible. ie Use "@script AddComponentMenu" to put them in top level menus or in sub-menus as appropriate.

    By keeping each script specific and well named and by making a script's less local effects analogous to real-world interactions, a prefab's behavior is easily and extensively modifiable while remaining quickly identifiable and predictable. All the event/message/receiver relationships are highly visible in the editor and don't require any API to manage. Less coupled interactions also tend to happen less frequently, making the performance penalty of SendMessage less of an issue.

    It's also worth mentioning that you shouldn't assume SendMessage is going to be a performance problem until you have measured it in real world situations. Said another way: Don't prematurely optimize.
     
  38. kodagames

    kodagames

    Joined:
    Jul 8, 2009
    Posts:
    548
    Last edited: Dec 18, 2010
    escape0707 likes this.
  39. DrakharStudio

    DrakharStudio

    Joined:
    Mar 2, 2012
    Posts:
    8
    We know this is an old post, but we were looking for a notification center and came across this post. As Prime31 pointed out, the solution depends on the use case. Prime31's solution is the way which fitted in our case. We have adapted it a bit, to avoid the creation of ArrayLists, and use a delegate array and the standard syntax (+=, -= and != null), thus avoiding the foreach (which is automatically done by the CLR)

    Here you have the adapted version

    View attachment $notificationcenter_172.unitypackage
     
    Last edited: Mar 9, 2012
  40. dansav

    dansav

    Joined:
    Sep 22, 2005
    Posts:
    510
    Do any of the above solutions work in the following situation:

    I'm trying to communicate back and forth from a csharp script to a javascript.
    I can't use direct communication because the csharp script is a plugin and can't see the javascript -- compiling order won't help here.

    Also in another situation I only have the string name of the method. Can any of these systems work with the string name of the method?

    Thanks,

    Dan